Skip to main content

tempo_precompiles/storage/types/
array.rs

1//! Fixed-size array handler for the storage traits.
2//!
3//! # Storage Layout
4//!
5//! Fixed-size arrays `[T; N]` use Solidity-compatible array storage:
6//! - **Base slot**: Arrays start directly at `base_slot` (not at keccak256)
7//! - **Data slots**: Elements are stored sequentially, either packed or unpacked
8//!
9//! ## Packing Strategy
10//!
11//! - **Packed**: When `T::BYTES <= 16`, multiple elements fit in one slot
12//! - **Unpacked**: When `T::BYTES > 16` or doesn't divide 32, each element uses full slot(s)
13
14use alloy::primitives::{Address, U256};
15use std::ops::{Index, IndexMut};
16use tempo_precompiles_macros;
17
18use crate::{
19    error::Result,
20    storage::{
21        Handler, LayoutCtx, Storable, StorableType, packing,
22        types::{HandlerCache, Slot},
23    },
24};
25
26// fixed-size arrays: [T; N] for primitive types T and sizes 1-32
27tempo_precompiles_macros::storable_arrays!();
28// nested arrays: [[T; M]; N] for small primitive types
29tempo_precompiles_macros::storable_nested_arrays!();
30
31/// Type-safe handler for accessing fixed-size arrays `[T; N]` in storage.
32///
33/// Unlike `VecHandler`, arrays have a fixed compile-time size and store elements
34/// directly at the base slot (not at `keccak256(base_slot)`).
35///
36/// # Element Access
37///
38/// Use `at(index)` to get a `Slot<T>` for individual element operations:
39/// - For packed elements (T::BYTES ≤ 16): returns a packed `Slot<T>` with byte offsets
40/// - For unpacked elements: returns a full `Slot<T>` for the element's dedicated slot
41/// - Returns `None` if index is out of bounds
42///
43/// # Example
44///
45/// ```ignore
46/// let handler = <[u8; 32] as StorableType>::handle(base_slot, LayoutCtx::FULL);
47///
48/// // Full array operations
49/// let array = handler.read()?;
50/// handler.write([1; 32])?;
51///
52/// // Individual element operations (at() returns Option, [] panics on OOB)
53/// if let Some(slot) = handler.at(0) {
54///     let elem = slot.read()?;
55///     slot.write(42)?;
56/// }
57/// ```
58#[derive(Debug, Clone)]
59pub struct ArrayHandler<T: StorableType, const N: usize> {
60    base_slot: U256,
61    address: Address,
62    cache: HandlerCache<usize, T::Handler>,
63}
64
65impl<T: StorableType, const N: usize> ArrayHandler<T, N> {
66    /// Creates a new handler for the array at the given base slot and address.
67    #[inline]
68    pub fn new(base_slot: U256, address: Address) -> Self {
69        Self {
70            base_slot,
71            address,
72            cache: HandlerCache::new(),
73        }
74    }
75
76    /// Returns a `Slot` accessor for full-array operations.
77    #[inline]
78    fn as_slot(&self) -> Slot<[T; N]> {
79        Slot::new(self.base_slot, self.address)
80    }
81
82    /// Returns the base storage slot where this array's data is stored.
83    ///
84    /// Single-slot arrays pack all fields into this slot.
85    /// Multi-slot arrays use consecutive slots starting from this base.
86    #[inline]
87    pub fn base_slot(&self) -> ::alloy::primitives::U256 {
88        self.base_slot
89    }
90
91    /// Returns the array size (known at compile time).
92    #[inline]
93    pub const fn len(&self) -> usize {
94        N
95    }
96
97    /// Returns whether the array is empty (always false for N > 0).
98    #[inline]
99    pub const fn is_empty(&self) -> bool {
100        N == 0
101    }
102
103    /// Returns a `Handler` for the element at the given index.
104    ///
105    /// The returned handler automatically handles packing based on `T::BYTES`.
106    /// The handler is computed on first access and cached for subsequent accesses.
107    ///
108    /// Returns `None` if the index is out of bounds (>= N).
109    #[inline]
110    pub fn at(&mut self, index: usize) -> Option<&T::Handler> {
111        if index >= N {
112            return None;
113        }
114        let (base_slot, address) = (self.base_slot, self.address);
115        Some(
116            self.cache
117                .get_or_insert(&index, || Self::compute_handler(base_slot, address, index)),
118        )
119    }
120
121    /// Computes the handler for a given index (unchecked).
122    #[inline]
123    fn compute_handler(base_slot: U256, address: Address, index: usize) -> T::Handler {
124        // Pack small elements into shared slots, use T::SLOTS for multi-slot types
125        let (slot, layout_ctx) = if T::BYTES <= 16 {
126            let location = packing::calc_element_loc(index, T::BYTES);
127            (
128                base_slot + U256::from(location.offset_slots),
129                LayoutCtx::packed(location.offset_bytes),
130            )
131        } else {
132            (base_slot + U256::from(index * T::SLOTS), LayoutCtx::FULL)
133        };
134
135        T::handle(slot, layout_ctx, address)
136    }
137}
138
139impl<T: StorableType, const N: usize> Index<usize> for ArrayHandler<T, N> {
140    type Output = T::Handler;
141
142    /// Returns a reference to the cached handler for the given index.
143    ///
144    /// **WARNING:** Panics if OOB. Caller must ensure that the index is valid.
145    /// For gracefully checked access use `.at(index)` instead.
146    fn index(&self, index: usize) -> &Self::Output {
147        assert!(index < N, "index out of bounds: {index} >= {N}");
148        let (base_slot, address) = (self.base_slot, self.address);
149        self.cache
150            .get_or_insert(&index, || Self::compute_handler(base_slot, address, index))
151    }
152}
153
154impl<T: StorableType, const N: usize> IndexMut<usize> for ArrayHandler<T, N> {
155    /// Returns a mutable reference to the cached handler for the given index.
156    ///
157    /// **WARNING:** Panics if OOB. Caller must ensure that the index is valid.
158    /// For gracefully checked access use `.at(index)` instead.
159    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
160        assert!(index < N, "index out of bounds: {index} >= {N}");
161        let (base_slot, address) = (self.base_slot, self.address);
162        self.cache
163            .get_or_insert_mut(&index, || Self::compute_handler(base_slot, address, index))
164    }
165}
166
167impl<T: StorableType, const N: usize> Handler<[T; N]> for ArrayHandler<T, N>
168where
169    [T; N]: Storable,
170{
171    /// Reads the entire array from storage.
172    #[inline]
173    fn read(&self) -> Result<[T; N]> {
174        self.as_slot().read()
175    }
176
177    /// Writes the entire array to storage.
178    #[inline]
179    fn write(&mut self, value: [T; N]) -> Result<()> {
180        self.as_slot().write(value)
181    }
182
183    /// Deletes the entire array from storage (clears all elements).
184    #[inline]
185    fn delete(&mut self) -> Result<()> {
186        self.as_slot().delete()
187    }
188
189    /// Reads the entire array from transient storage.
190    #[inline]
191    fn t_read(&self) -> Result<[T; N]> {
192        self.as_slot().t_read()
193    }
194
195    /// Writes the entire array to transient storage.
196    #[inline]
197    fn t_write(&mut self, value: [T; N]) -> Result<()> {
198        self.as_slot().t_write(value)
199    }
200
201    /// Deletes the entire array from transient storage (clears all elements).
202    #[inline]
203    fn t_delete(&mut self) -> Result<()> {
204        self.as_slot().t_delete()
205    }
206}
207
208#[cfg(test)]
209mod tests {
210    use super::*;
211    use crate::{
212        storage::{Layout, LayoutCtx, PrecompileStorageProvider, StorageCtx},
213        test_util::setup_storage,
214    };
215    use alloy::primitives::aliases::U96;
216    use proptest::prelude::*;
217
218    // Strategy for generating random U256 slot values that won't overflow
219    fn arb_safe_slot() -> impl Strategy<Value = U256> {
220        any::<[u64; 4]>().prop_map(|limbs| {
221            // Ensure we don't overflow by limiting to a reasonable range
222            U256::from_limbs(limbs) % (U256::MAX - U256::from(10000))
223        })
224    }
225
226    #[test]
227    fn test_array_u8_32_single_slot() {
228        let (mut storage, address) = setup_storage();
229        let base_slot = U256::ZERO;
230
231        // [u8; 32] should pack into exactly 1 slot
232        let data: [u8; 32] = [
233            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
234            25, 26, 27, 28, 29, 30, 31, 32,
235        ];
236
237        // Verify LAYOUT
238        assert_eq!(<[u8; 32] as StorableType>::LAYOUT, Layout::Slots(1));
239
240        // Store and load
241        StorageCtx::enter(&mut storage, || {
242            let mut slot = <[u8; 32]>::handle(base_slot, LayoutCtx::FULL, address);
243            slot.write(data).unwrap();
244            let loaded = slot.read().unwrap();
245            assert_eq!(loaded, data, "[u8; 32] roundtrip failed");
246
247            // Verify delete
248            slot.delete().unwrap();
249        });
250        let slot_value = storage.sload(address, base_slot).unwrap();
251        assert_eq!(slot_value, U256::ZERO, "Slot not cleared after delete");
252    }
253
254    #[test]
255    fn test_array_u64_5_multi_slot() {
256        let (mut storage, address) = setup_storage();
257        let base_slot = U256::from(100);
258
259        // [u64; 5] should require 2 slots (5 * 8 = 40 bytes > 32)
260        let data: [u64; 5] = [1, 2, 3, 4, 5];
261
262        // Verify slot count
263        assert_eq!(<[u64; 5] as StorableType>::LAYOUT, Layout::Slots(2));
264
265        // Store and load
266        StorageCtx::enter(&mut storage, || {
267            let mut slot = <[u64; 5]>::handle(base_slot, LayoutCtx::FULL, address);
268            slot.write(data).unwrap();
269            let loaded = slot.read().unwrap();
270            assert_eq!(loaded, data, "[u64; 5] roundtrip failed");
271        });
272
273        // Verify both slots are used
274        let slot0 = storage.sload(address, base_slot).unwrap();
275        let slot1 = storage.sload(address, base_slot + U256::ONE).unwrap();
276        assert_ne!(slot0, U256::ZERO, "Slot 0 should be non-zero");
277        assert_ne!(slot1, U256::ZERO, "Slot 1 should be non-zero");
278
279        // Verify delete clears both slots
280        StorageCtx::enter(&mut storage, || {
281            let mut slot = <[u64; 5]>::handle(base_slot, LayoutCtx::FULL, address);
282            slot.delete().unwrap();
283        });
284        let slot0_after = storage.sload(address, base_slot).unwrap();
285        let slot1_after = storage.sload(address, base_slot + U256::ONE).unwrap();
286        assert_eq!(slot0_after, U256::ZERO, "Slot 0 not cleared");
287        assert_eq!(slot1_after, U256::ZERO, "Slot 1 not cleared");
288    }
289
290    #[test]
291    fn test_array_u16_packing() {
292        let (mut storage, address) = setup_storage();
293        let base_slot = U256::from(200);
294
295        // [u16; 16] should pack into exactly 1 slot (16 * 2 = 32 bytes)
296        let data: [u16; 16] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
297
298        // Verify slot count
299        assert_eq!(<[u16; 16] as StorableType>::LAYOUT, Layout::Slots(1));
300
301        // Store and load
302        StorageCtx::enter(&mut storage, || {
303            let mut slot = <[u16; 16]>::handle(base_slot, LayoutCtx::FULL, address);
304            slot.write(data).unwrap();
305            let loaded = slot.read().unwrap();
306            assert_eq!(loaded, data, "[u16; 16] roundtrip failed");
307        });
308    }
309
310    #[test]
311    fn test_array_u256_no_packing() {
312        let (mut storage, address) = setup_storage();
313        let base_slot = U256::from(300);
314
315        // [U256; 3] should use 3 slots (no packing for 32-byte types)
316        let data: [U256; 3] = [U256::from(12345), U256::from(67890), U256::from(111111)];
317
318        // Verify slot count
319        assert_eq!(<[U256; 3] as StorableType>::LAYOUT, Layout::Slots(3));
320
321        // Store and load
322        StorageCtx::enter(&mut storage, || {
323            let mut slot = <[U256; 3]>::handle(base_slot, LayoutCtx::FULL, address);
324            slot.write(data).unwrap();
325            let loaded = slot.read().unwrap();
326            assert_eq!(loaded, data, "[U256; 3] roundtrip failed");
327        });
328
329        // Verify each element is in its own slot
330        for (i, expected_value) in data.iter().enumerate() {
331            let slot_value = storage.sload(address, base_slot + U256::from(i)).unwrap();
332            assert_eq!(slot_value, *expected_value, "Slot {i} mismatch");
333        }
334    }
335
336    #[test]
337    fn test_array_address_no_packing() {
338        let (mut storage, address) = setup_storage();
339        let base_slot = U256::from(400);
340
341        // [Address; 3] should use 3 slots (20 bytes doesn't divide 32 evenly)
342        let data: [Address; 3] = [
343            Address::repeat_byte(0x11),
344            Address::repeat_byte(0x22),
345            Address::repeat_byte(0x33),
346        ];
347
348        // Verify slot count
349        assert_eq!(<[Address; 3] as StorableType>::LAYOUT, Layout::Slots(3));
350
351        // Store and load
352        StorageCtx::enter(&mut storage, || {
353            let mut slot = <[Address; 3]>::handle(base_slot, LayoutCtx::FULL, address);
354            slot.write(data).unwrap();
355            let loaded = slot.read().unwrap();
356            assert_eq!(loaded, data, "[Address; 3] roundtrip failed");
357        });
358    }
359    #[test]
360    fn u96_array_packed_layout_matches_solidity() {
361        let (mut storage, address) = setup_storage();
362        let base_slot = U256::from(450);
363
364        StorageCtx::enter(&mut storage, || {
365            let mut handler = <[U96; 2]>::handle(base_slot, LayoutCtx::FULL, address);
366            handler.write([U96::from(1), U96::from(2)]).unwrap();
367        });
368
369        // Both elements packed in slot 0: low 12 bytes = elem 0, next 12 bytes = elem 1.
370        let expected = U256::from(1) | (U256::from(2) << 96);
371        assert_eq!(storage.sload(address, base_slot).unwrap(), expected);
372        assert_eq!(
373            storage.sload(address, base_slot + U256::ONE).unwrap(),
374            U256::ZERO
375        );
376
377        StorageCtx::enter(&mut storage, || {
378            let mut handler = <[U96; 2]>::handle(base_slot, LayoutCtx::FULL, address);
379            // Indexed read now agrees with bulk layout.
380            assert_eq!(handler.at(0).unwrap().read().unwrap(), U96::from(1));
381            assert_eq!(handler.at(1).unwrap().read().unwrap(), U96::from(2));
382
383            // Indexed write to elem 1 only modifies bytes 12..23 of slot 0.
384            handler[1].write(U96::from(3)).unwrap();
385        });
386
387        let after = U256::from(1) | (U256::from(3) << 96);
388        assert_eq!(storage.sload(address, base_slot).unwrap(), after);
389        assert_eq!(
390            storage.sload(address, base_slot + U256::ONE).unwrap(),
391            U256::ZERO
392        );
393    }
394
395    #[test]
396    fn u96_array_5_packed_layout_matches_solidity() {
397        let (mut storage, address) = setup_storage();
398        let base_slot = U256::from(550);
399
400        let data = [
401            U96::from(1u64),
402            U96::from(2u64),
403            U96::from(3u64),
404            U96::from(4u64),
405            U96::from(5u64),
406        ];
407
408        // LAYOUT must report 3 slots (Solidity: ceil(5/2) = 3).
409        assert_eq!(
410            <[U96; 5] as StorableType>::LAYOUT,
411            Layout::Slots(3),
412            "[U96; 5] must occupy 3 slots, not 2 (slot-count bug)"
413        );
414
415        StorageCtx::enter(&mut storage, || {
416            let mut handler = <[U96; 5]>::handle(base_slot, LayoutCtx::FULL, address);
417            handler.write(data).unwrap();
418        });
419
420        // Slot 0: elem0 in low 12 bytes, elem1 in next 12 bytes.
421        let expected_slot0 = U256::from(1) | (U256::from(2) << 96);
422        assert_eq!(
423            storage.sload(address, base_slot).unwrap(),
424            expected_slot0,
425            "slot 0 must hold packed (elem0, elem1)"
426        );
427
428        // Slot 1: elem2 + elem3 packed.
429        let expected_slot1 = U256::from(3) | (U256::from(4) << 96);
430        assert_eq!(
431            storage.sload(address, base_slot + U256::ONE).unwrap(),
432            expected_slot1,
433            "slot 1 must hold packed (elem2, elem3)"
434        );
435
436        // Slot 2: elem4 alone in low 12 bytes (this slot is missed entirely
437        // if the bulk-store loop uses the buggy `(5*12).div_ceil(32) = 2`
438        // slot-count formula).
439        assert_eq!(
440            storage
441                .sload(address, base_slot + U256::from(2u64))
442                .unwrap(),
443            U256::from(5),
444            "slot 2 must hold elem4 in low 12 bytes (slot-count formula must use \
445             ceil(N / itemsPerSlot), not (N*B).div_ceil(32))"
446        );
447
448        // Slot 3 must be untouched.
449        assert_eq!(
450            storage
451                .sload(address, base_slot + U256::from(3u64))
452                .unwrap(),
453            U256::ZERO,
454            "slot 3 must remain untouched"
455        );
456
457        // Whole-array roundtrip and per-index reads must agree with the
458        // packed layout established above.
459        StorageCtx::enter(&mut storage, || {
460            let mut handler = <[U96; 5]>::handle(base_slot, LayoutCtx::FULL, address);
461            assert_eq!(handler.read().unwrap(), data, "bulk read mismatch");
462            for (i, expected) in data.iter().enumerate() {
463                assert_eq!(
464                    handler.at(i).unwrap().read().unwrap(),
465                    *expected,
466                    "indexed read mismatch at i={i}"
467                );
468            }
469
470            // Indexed write to elem 4 must hit slot base+2 only.
471            handler[4].write(U96::from(99u64)).unwrap();
472        });
473        assert_eq!(
474            storage
475                .sload(address, base_slot + U256::from(2u64))
476                .unwrap(),
477            U256::from(99),
478            "indexed write to elem 4 must update slot base+2"
479        );
480        assert_eq!(
481            storage.sload(address, base_slot).unwrap(),
482            expected_slot0,
483            "indexed write to elem 4 must not disturb slot 0"
484        );
485        assert_eq!(
486            storage.sload(address, base_slot + U256::ONE).unwrap(),
487            expected_slot1,
488            "indexed write to elem 4 must not disturb slot 1"
489        );
490    }
491
492    #[test]
493    fn test_array_empty_single_element() {
494        let (mut storage, address) = setup_storage();
495        let base_slot = U256::from(500);
496
497        // [u8; 1] should use 1 slot
498        let data: [u8; 1] = [42];
499
500        // Verify slot count
501        assert_eq!(<[u8; 1] as StorableType>::LAYOUT, Layout::Slots(1));
502
503        // Store and load
504        StorageCtx::enter(&mut storage, || {
505            let mut slot = <[u8; 1]>::handle(base_slot, LayoutCtx::FULL, address);
506            slot.write(data).unwrap();
507            let loaded = slot.read().unwrap();
508            assert_eq!(loaded, data, "[u8; 1] roundtrip failed");
509        });
510    }
511
512    #[test]
513    fn test_nested_array_u8_4x8() {
514        let (mut storage, address) = setup_storage();
515        let base_slot = U256::from(600);
516
517        // [[u8; 4]; 8] uses 8 slots (one per inner array)
518        // Each inner [u8; 4] gets a full 32-byte slot, even though it only uses 4 bytes
519        // This follows EVM's rule: nested arrays don't pack tightly across boundaries
520        let data: [[u8; 4]; 8] = [
521            [1, 2, 3, 4],
522            [5, 6, 7, 8],
523            [9, 10, 11, 12],
524            [13, 14, 15, 16],
525            [17, 18, 19, 20],
526            [21, 22, 23, 24],
527            [25, 26, 27, 28],
528            [29, 30, 31, 32],
529        ];
530
531        // Verify LAYOUT: 8 slots (one per inner array)
532        assert_eq!(<[[u8; 4]; 8] as StorableType>::LAYOUT, Layout::Slots(8));
533
534        // Store and load
535        StorageCtx::enter(&mut storage, || {
536            let mut slot = <[[u8; 4]; 8]>::handle(base_slot, LayoutCtx::FULL, address);
537            slot.write(data).unwrap();
538            let loaded = slot.read().unwrap();
539            assert_eq!(loaded, data, "[[u8; 4]; 8] roundtrip failed");
540
541            // Verify delete clears all 8 slots
542            slot.delete().unwrap();
543        });
544        for i in 0..8 {
545            let slot_value = storage.sload(address, base_slot + U256::from(i)).unwrap();
546            assert_eq!(slot_value, U256::ZERO, "Slot {i} not cleared after delete");
547        }
548    }
549
550    #[test]
551    fn test_nested_array_u16_2x8() {
552        let (mut storage, address) = setup_storage();
553        let base_slot = U256::from(700);
554
555        // [[u16; 2]; 8] uses 8 slots (one per inner array)
556        // Each inner [u16; 2] gets a full 32-byte slot, even though it only uses 4 bytes
557        // Compare: flat [u16; 16] would pack into 1 slot (16 × 2 = 32 bytes)
558        // But nested arrays don't pack across boundaries in EVM
559        let data: [[u16; 2]; 8] = [
560            [100, 101],
561            [200, 201],
562            [300, 301],
563            [400, 401],
564            [500, 501],
565            [600, 601],
566            [700, 701],
567            [800, 801],
568        ];
569
570        // Verify LAYOUT: 8 slots (one per inner array)
571        assert_eq!(<[[u16; 2]; 8] as StorableType>::LAYOUT, Layout::Slots(8));
572
573        // Store and load
574        StorageCtx::enter(&mut storage, || {
575            let mut slot = <[[u16; 2]; 8]>::handle(base_slot, LayoutCtx::FULL, address);
576            slot.write(data).unwrap();
577            let loaded = slot.read().unwrap();
578            assert_eq!(loaded, data, "[[u16; 2]; 8] roundtrip failed");
579
580            // Verify delete clears all 8 slots
581            slot.delete().unwrap();
582        });
583        for i in 0..8 {
584            let slot_value = storage.sload(address, base_slot + U256::from(i)).unwrap();
585            assert_eq!(slot_value, U256::ZERO, "Slot {i} not cleared after delete");
586        }
587    }
588
589    proptest! {
590        #![proptest_config(ProptestConfig::with_cases(500))]
591
592        #[test]
593        fn test_array_u8_32(
594            data in prop::array::uniform32(any::<u8>()),
595            base_slot in arb_safe_slot()
596        ) {
597            let (mut storage, address) = setup_storage();
598
599            // Store and load
600            StorageCtx::enter(&mut storage, || {
601                let mut slot = <[u8; 32]>::handle(base_slot, LayoutCtx::FULL, address);
602                slot.write(data).unwrap();
603                let loaded = slot.read().unwrap();
604                prop_assert_eq!(&loaded, &data, "[u8; 32] roundtrip failed");
605
606                // Delete
607                slot.delete().unwrap();
608                Ok(())
609            })?;
610            let slot_value = storage.sload(address, base_slot).unwrap();
611            prop_assert_eq!(slot_value, U256::ZERO, "Slot not cleared after delete");
612        }
613
614        #[test]
615        fn test_array_u16_16(
616            data in prop::array::uniform16(any::<u16>()),
617            base_slot in arb_safe_slot()
618        ) {
619            let (mut storage, address) = setup_storage();
620
621            // Store and load
622            StorageCtx::enter(&mut storage, || {
623                let mut slot = <[u16; 16]>::handle(base_slot, LayoutCtx::FULL, address);
624                slot.write(data).unwrap();
625                let loaded = slot.read().unwrap();
626                prop_assert_eq!(&loaded, &data, "[u16; 16] roundtrip failed");
627                Ok(())
628            })?;
629        }
630
631        #[test]
632        fn test_array_u256_5(
633            data in prop::array::uniform5(any::<u64>()).prop_map(|arr| arr.map(U256::from)),
634            base_slot in arb_safe_slot()
635        ) {
636            let (mut storage, address) = setup_storage();
637
638            // Store and load
639            StorageCtx::enter(&mut storage, || {
640                let mut slot = <[U256; 5]>::handle(base_slot, LayoutCtx::FULL, address);
641                slot.write(data).unwrap();
642                let loaded = slot.read().unwrap();
643                prop_assert_eq!(&loaded, &data, "[U256; 5] roundtrip failed");
644                Ok(())
645            })?;
646
647            // Verify each element is in its own slot
648            for (i, expected_value) in data.iter().enumerate() {
649                let slot_value = storage.sload(address, base_slot + U256::from(i)).unwrap();
650                prop_assert_eq!(slot_value, *expected_value, "Slot {} mismatch", i);
651            }
652
653            // Delete
654            StorageCtx::enter(&mut storage, || {
655                let mut slot = <[U256; 5]>::handle(base_slot, LayoutCtx::FULL, address);
656                slot.delete().unwrap();
657                Ok::<(), proptest::test_runner::TestCaseError>(())
658            })?;
659            for i in 0..5 {
660                let slot_value = storage.sload(address, base_slot + U256::from(i)).unwrap();
661                prop_assert_eq!(slot_value, U256::ZERO, "Slot {} not cleared", i);
662            }
663        }
664    }
665}