tempo_precompiles/storage/types/
primitives.rs

1//! Single-word primitives (up-to 32 bytes) implementation for the `Storable trait`.
2
3use alloy::primitives::{Address, U256};
4use revm::interpreter::instructions::utility::{IntoAddress, IntoU256};
5use tempo_precompiles_macros;
6
7use crate::{
8    error::Result,
9    storage::{StorageOps, types::*},
10};
11
12// rust integers: (u)int8, (u)int16, (u)int32, (u)int64, (u)int128
13tempo_precompiles_macros::storable_rust_ints!();
14// alloy integers: U8, I8, U16, I16, U32, I32, U64, I64, U128, I128, U256, I256
15tempo_precompiles_macros::storable_alloy_ints!();
16// alloy fixed bytes: FixedBytes<1>, FixedBytes<2>, ..., FixedBytes<32>
17tempo_precompiles_macros::storable_alloy_bytes!();
18// fixed-size arrays: [T; N] for primitive types T and sizes 1-32
19tempo_precompiles_macros::storable_arrays!();
20// nested arrays: [[T; M]; N] for small primitive types
21tempo_precompiles_macros::storable_nested_arrays!();
22
23// -- MANUAL STORAGE TRAIT IMPLEMENTATIONS -------------------------------------
24
25impl StorableType for bool {
26    const LAYOUT: Layout = Layout::Bytes(1);
27}
28
29impl Storable<1> for bool {
30    #[inline]
31    fn load<S: StorageOps>(storage: &mut S, base_slot: U256, ctx: LayoutCtx) -> Result<Self> {
32        match ctx.packed_offset() {
33            None => storage.sload(base_slot).map(|val| !val.is_zero()),
34            Some(offset) => {
35                let slot = storage.sload(base_slot)?;
36                crate::storage::packing::extract_packed_value(slot, offset, 1)
37            }
38        }
39    }
40
41    #[inline]
42    fn store<S: StorageOps>(&self, storage: &mut S, base_slot: U256, ctx: LayoutCtx) -> Result<()> {
43        let value = if *self { U256::ONE } else { U256::ZERO };
44        match ctx.packed_offset() {
45            None => storage.sstore(base_slot, value),
46            Some(offset) => {
47                let current = storage.sload(base_slot)?;
48                let updated =
49                    crate::storage::packing::insert_packed_value(current, &value, offset, 1)?;
50                storage.sstore(base_slot, updated)
51            }
52        }
53    }
54
55    #[inline]
56    fn to_evm_words(&self) -> Result<[U256; 1]> {
57        Ok([if *self { U256::ONE } else { U256::ZERO }])
58    }
59
60    #[inline]
61    fn from_evm_words(words: [U256; 1]) -> Result<Self> {
62        Ok(!words[0].is_zero())
63    }
64}
65
66impl StorableType for Address {
67    const LAYOUT: Layout = Layout::Bytes(20);
68}
69
70impl Storable<1> for Address {
71    #[inline]
72    fn load<S: StorageOps>(storage: &mut S, base_slot: U256, ctx: LayoutCtx) -> Result<Self> {
73        match ctx.packed_offset() {
74            None => storage.sload(base_slot).map(|val| val.into_address()),
75            Some(offset) => {
76                let slot = storage.sload(base_slot)?;
77                crate::storage::packing::extract_packed_value(slot, offset, 20)
78            }
79        }
80    }
81
82    #[inline]
83    fn store<S: StorageOps>(&self, storage: &mut S, base_slot: U256, ctx: LayoutCtx) -> Result<()> {
84        match ctx.packed_offset() {
85            None => storage.sstore(base_slot, self.into_u256()),
86            Some(offset) => {
87                let current = storage.sload(base_slot)?;
88                let value = self.into_u256();
89                let updated =
90                    crate::storage::packing::insert_packed_value(current, &value, offset, 20)?;
91                storage.sstore(base_slot, updated)
92            }
93        }
94    }
95
96    #[inline]
97    fn to_evm_words(&self) -> Result<[U256; 1]> {
98        Ok([self.into_u256()])
99    }
100
101    #[inline]
102    fn from_evm_words(words: [U256; 1]) -> Result<Self> {
103        Ok(words[0].into_address())
104    }
105}
106
107impl StorageKey for Address {
108    #[inline]
109    fn as_storage_bytes(&self) -> impl AsRef<[u8]> {
110        self.as_slice()
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117    use crate::storage::{
118        PrecompileStorageProvider, StorageOps,
119        hashmap::HashMapStorageProvider,
120        packing::{gen_slot_from, insert_packed_value},
121    };
122    use proptest::prelude::*;
123
124    // -- TEST HELPERS -------------------------------------------------------------
125
126    // Test helper that owns storage and implements StorageOps
127    struct TestContract {
128        address: Address,
129        storage: HashMapStorageProvider,
130    }
131
132    impl StorageOps for TestContract {
133        fn sstore(&mut self, slot: U256, value: U256) -> Result<()> {
134            self.storage.sstore(self.address, slot, value)
135        }
136
137        fn sload(&mut self, slot: U256) -> Result<U256> {
138            self.storage.sload(self.address, slot)
139        }
140    }
141
142    /// Helper to create a test contract with fresh storage.
143    fn setup_test_contract() -> TestContract {
144        TestContract {
145            address: Address::random(),
146            storage: HashMapStorageProvider::new(1),
147        }
148    }
149
150    // Strategy for generating random U256 slot values that won't overflow
151    fn arb_safe_slot() -> impl Strategy<Value = U256> {
152        any::<[u64; 4]>().prop_map(|limbs| {
153            // Ensure we don't overflow by limiting to a reasonable range
154            U256::from_limbs(limbs) % (U256::MAX - U256::from(10000))
155        })
156    }
157
158    // Strategy for generating arbitrary addresses
159    fn arb_address() -> impl Strategy<Value = Address> {
160        any::<[u8; 20]>().prop_map(Address::from)
161    }
162
163    // -- STORAGE TESTS --------------------------------------------------------
164
165    // Generate property tests for all storage types:
166    // - rust integers: (u)int8, (u)int16, (u)int32, (u)int64, (u)int128
167    // - alloy integers: U8, I8, U16, I16, U32, I32, U64, I64, U128, I128, U256, I256
168    // - alloy fixed bytes: FixedBytes<1>, FixedBytes<2>, ..., FixedBytes<32>
169    tempo_precompiles_macros::gen_storable_tests!();
170
171    proptest! {
172        #![proptest_config(ProptestConfig::with_cases(500))]
173
174        #[test]
175        fn test_address(addr in arb_address(), base_slot in arb_safe_slot()) {
176            let mut contract = setup_test_contract();
177
178            // Verify store → load roundtrip
179            addr.store(&mut contract, base_slot, LayoutCtx::FULL)?;
180            let loaded = Address::load(&mut contract, base_slot, LayoutCtx::FULL)?;
181            assert_eq!(addr, loaded, "Address roundtrip failed");
182
183            // Verify delete works
184            Address::delete(&mut contract, base_slot, LayoutCtx::FULL)?;
185            let after_delete = Address::load(&mut contract, base_slot, LayoutCtx::FULL)?;
186            assert_eq!(after_delete, Address::ZERO, "Address not zero after delete");
187
188            // EVM words roundtrip
189            let words = addr.to_evm_words()?;
190            let recovered = Address::from_evm_words(words)?;
191            assert_eq!(addr, recovered, "Address EVM words roundtrip failed");
192        }
193
194        #[test]
195        fn test_bool_values(b in any::<bool>(), base_slot in arb_safe_slot()) {
196            let mut contract = setup_test_contract();
197
198            // Verify store → load roundtrip
199            b.store(&mut contract, base_slot, LayoutCtx::FULL)?;
200            let loaded = bool::load(&mut contract, base_slot, LayoutCtx::FULL)?;
201            assert_eq!(b, loaded, "Bool roundtrip failed for value: {b}");
202
203            // Verify delete works
204            bool::delete(&mut contract, base_slot, LayoutCtx::FULL)?;
205            let after_delete = bool::load(&mut contract, base_slot, LayoutCtx::FULL)?;
206            assert!(!after_delete, "Bool not false after delete");
207
208            // EVM words roundtrip
209            let words = b.to_evm_words()?;
210            let recovered = bool::from_evm_words(words)?;
211            assert_eq!(b, recovered, "Bool EVM words roundtrip failed");
212        }
213    }
214
215    // -- PRIMITIVE SLOT CONTENT VALIDATION TESTS ----------------------------------
216
217    #[test]
218    fn test_u8_at_various_offsets() {
219        let mut contract = setup_test_contract();
220        let base_slot = U256::from(100);
221
222        // Test u8 at offset 0
223        let val0: u8 = 0x42;
224        let mut slot = U256::ZERO;
225        slot = insert_packed_value(slot, &val0, 0, 1).unwrap();
226        contract.sstore(base_slot, slot).unwrap();
227
228        let loaded_slot = contract.sload(base_slot).unwrap();
229        let expected = gen_slot_from(&[
230            "0x42", // offset 0 (1 byte)
231        ]);
232        assert_eq!(loaded_slot, expected);
233
234        // Test u8 at offset 15 (middle)
235        let val15: u8 = 0xAB;
236        slot = U256::ZERO;
237        slot = insert_packed_value(slot, &val15, 15, 1).unwrap();
238        contract.sstore(base_slot + U256::ONE, slot).unwrap();
239
240        let loaded_slot = contract.sload(base_slot + U256::ONE).unwrap();
241        let expected = gen_slot_from(&[
242            "0xAB",                             // offset 15 (1 byte)
243            "0x000000000000000000000000000000", // padding (15 bytes)
244        ]);
245        assert_eq!(loaded_slot, expected);
246
247        // Test u8 at offset 31 (last byte)
248        let val31: u8 = 0xFF;
249        slot = U256::ZERO;
250        slot = insert_packed_value(slot, &val31, 31, 1).unwrap();
251        contract.sstore(base_slot + U256::from(2), slot).unwrap();
252
253        let loaded_slot = contract.sload(base_slot + U256::from(2)).unwrap();
254        let expected = gen_slot_from(&[
255            "0xFF",                                                             // offset 31 (1 byte)
256            "0x00000000000000000000000000000000000000000000000000000000000000", // padding (31 bytes)
257        ]);
258        assert_eq!(loaded_slot, expected);
259    }
260
261    #[test]
262    fn test_u16_at_various_offsets() {
263        let mut contract = setup_test_contract();
264        let base_slot = U256::from(200);
265
266        // Test u16 at offset 0
267        let val0: u16 = 0x1234;
268        let mut slot = U256::ZERO;
269        slot = insert_packed_value(slot, &val0, 0, 2).unwrap();
270        contract.sstore(base_slot, slot).unwrap();
271
272        let loaded_slot = contract.sload(base_slot).unwrap();
273        let expected = gen_slot_from(&[
274            "0x1234", // offset 0 (2 bytes)
275        ]);
276        assert_eq!(loaded_slot, expected);
277
278        // Test u16 at offset 15 (middle)
279        let val15: u16 = 0xABCD;
280        slot = U256::ZERO;
281        slot = insert_packed_value(slot, &val15, 15, 2).unwrap();
282        contract.sstore(base_slot + U256::ONE, slot).unwrap();
283
284        let loaded_slot = contract.sload(base_slot + U256::ONE).unwrap();
285        let expected = gen_slot_from(&[
286            "0xABCD",                           // offset 15 (2 bytes)
287            "0x000000000000000000000000000000", // padding (15 bytes)
288        ]);
289        assert_eq!(loaded_slot, expected);
290
291        // Test u16 at offset 30 (last 2 bytes)
292        let val30: u16 = 0xFFEE;
293        slot = U256::ZERO;
294        slot = insert_packed_value(slot, &val30, 30, 2).unwrap();
295        contract.sstore(base_slot + U256::from(2), slot).unwrap();
296
297        let loaded_slot = contract.sload(base_slot + U256::from(2)).unwrap();
298        let expected = gen_slot_from(&[
299            "0xFFEE",                                                         // offset 30 (2 bytes)
300            "0x000000000000000000000000000000000000000000000000000000000000", // padding (30 bytes)
301        ]);
302        assert_eq!(loaded_slot, expected);
303    }
304
305    #[test]
306    fn test_u32_at_various_offsets() {
307        let mut contract = setup_test_contract();
308        let base_slot = U256::from(300);
309
310        // Test u32 at offset 0
311        let val0: u32 = 0x12345678;
312        let mut slot = U256::ZERO;
313        slot = insert_packed_value(slot, &val0, 0, 4).unwrap();
314        contract.sstore(base_slot, slot).unwrap();
315
316        let loaded_slot = contract.sload(base_slot).unwrap();
317        let expected = gen_slot_from(&[
318            "0x12345678", // offset 0 (4 bytes)
319        ]);
320        assert_eq!(loaded_slot, expected);
321
322        // Test u32 at offset 14
323        let val14: u32 = 0xABCDEF01;
324        slot = U256::ZERO;
325        slot = insert_packed_value(slot, &val14, 14, 4).unwrap();
326        contract.sstore(base_slot + U256::ONE, slot).unwrap();
327
328        let loaded_slot = contract.sload(base_slot + U256::ONE).unwrap();
329        let expected = gen_slot_from(&[
330            "0xABCDEF01",                     // offset 14 (4 bytes)
331            "0x0000000000000000000000000000", // padding (14 bytes)
332        ]);
333        assert_eq!(loaded_slot, expected);
334
335        // Test u32 at offset 28 (last 4 bytes)
336        let val28: u32 = 0xFFEEDDCC;
337        slot = U256::ZERO;
338        slot = insert_packed_value(slot, &val28, 28, 4).unwrap();
339        contract.sstore(base_slot + U256::from(2), slot).unwrap();
340
341        let loaded_slot = contract.sload(base_slot + U256::from(2)).unwrap();
342        let expected = gen_slot_from(&[
343            "0xFFEEDDCC",                                                 // offset 28 (4 bytes)
344            "0x00000000000000000000000000000000000000000000000000000000", // padding (28 bytes)
345        ]);
346        assert_eq!(loaded_slot, expected);
347    }
348
349    #[test]
350    fn test_u64_at_various_offsets() {
351        let mut contract = setup_test_contract();
352        let base_slot = U256::from(400);
353
354        // Test u64 at offset 0
355        let val0: u64 = 0x123456789ABCDEF0;
356        let mut slot = U256::ZERO;
357        slot = insert_packed_value(slot, &val0, 0, 8).unwrap();
358        contract.sstore(base_slot, slot).unwrap();
359
360        let loaded_slot = contract.sload(base_slot).unwrap();
361        let expected = gen_slot_from(&[
362            "0x123456789ABCDEF0", // offset 0 (8 bytes)
363        ]);
364        assert_eq!(loaded_slot, expected);
365
366        // Test u64 at offset 12 (middle)
367        let val12: u64 = 0xFEDCBA9876543210;
368        slot = U256::ZERO;
369        slot = insert_packed_value(slot, &val12, 12, 8).unwrap();
370        contract.sstore(base_slot + U256::ONE, slot).unwrap();
371
372        let loaded_slot = contract.sload(base_slot + U256::ONE).unwrap();
373        let expected = gen_slot_from(&[
374            "0xFEDCBA9876543210",         // offset 12 (8 bytes)
375            "0x000000000000000000000000", // padding (12 bytes)
376        ]);
377        assert_eq!(loaded_slot, expected);
378
379        // Test u64 at offset 24 (last 8 bytes)
380        let val24: u64 = 0xAAAABBBBCCCCDDDD;
381        slot = U256::ZERO;
382        slot = insert_packed_value(slot, &val24, 24, 8).unwrap();
383        contract.sstore(base_slot + U256::from(2), slot).unwrap();
384
385        let loaded_slot = contract.sload(base_slot + U256::from(2)).unwrap();
386        let expected = gen_slot_from(&[
387            "0xAAAABBBBCCCCDDDD",                                 // offset 24 (8 bytes)
388            "0x000000000000000000000000000000000000000000000000", // padding (24 bytes)
389        ]);
390        assert_eq!(loaded_slot, expected);
391    }
392
393    #[test]
394    fn test_u128_at_various_offsets() {
395        let mut contract = setup_test_contract();
396        let base_slot = U256::from(500);
397
398        // Test u128 at offset 0
399        let val0: u128 = 0x123456789ABCDEF0_FEDCBA9876543210;
400        let mut slot = U256::ZERO;
401        slot = insert_packed_value(slot, &val0, 0, 16).unwrap();
402        contract.sstore(base_slot, slot).unwrap();
403
404        let loaded_slot = contract.sload(base_slot).unwrap();
405        let expected = gen_slot_from(&[
406            "0x123456789ABCDEF0FEDCBA9876543210", // offset 0 (16 bytes)
407        ]);
408        assert_eq!(loaded_slot, expected);
409
410        // Test u128 at offset 16 (second half of slot)
411        let val16: u128 = 0xAAAABBBBCCCCDDDD_1111222233334444;
412        slot = U256::ZERO;
413        slot = insert_packed_value(slot, &val16, 16, 16).unwrap();
414        contract.sstore(base_slot + U256::ONE, slot).unwrap();
415
416        let loaded_slot = contract.sload(base_slot + U256::ONE).unwrap();
417        let expected = gen_slot_from(&[
418            "0xAAAABBBBCCCCDDDD1111222233334444", // offset 16 (16 bytes)
419            "0x00000000000000000000000000000000", // padding (16 bytes)
420        ]);
421        assert_eq!(loaded_slot, expected);
422    }
423
424    #[test]
425    fn test_address_at_various_offsets() {
426        let mut contract = setup_test_contract();
427        let base_slot = U256::from(600);
428
429        // Test Address at offset 0
430        let addr0 = Address::from([0x12; 20]);
431        let mut slot = U256::ZERO;
432        slot = insert_packed_value(slot, &addr0, 0, 20).unwrap();
433        contract.sstore(base_slot, slot).unwrap();
434
435        let loaded_slot = contract.sload(base_slot).unwrap();
436        let expected = gen_slot_from(&[
437            "0x1212121212121212121212121212121212121212", // offset 0 (20 bytes)
438        ]);
439        assert_eq!(loaded_slot, expected);
440
441        // Test Address at offset 12 (fits in one slot: 12 + 20 = 32)
442        let addr12 = Address::from([0xAB; 20]);
443        slot = U256::ZERO;
444        slot = insert_packed_value(slot, &addr12, 12, 20).unwrap();
445        contract.sstore(base_slot + U256::ONE, slot).unwrap();
446
447        let loaded_slot = contract.sload(base_slot + U256::ONE).unwrap();
448        let expected = gen_slot_from(&[
449            "0xABABABABABABABABABABABABABABABABABABABAB", // offset 12 (20 bytes)
450            "0x000000000000000000000000",                 // padding (12 bytes)
451        ]);
452        assert_eq!(loaded_slot, expected);
453    }
454
455    #[test]
456    fn test_bool_at_various_offsets() {
457        let mut contract = setup_test_contract();
458        let base_slot = U256::from(700);
459
460        // Test bool at offset 0
461        let val0 = true;
462        let mut slot = U256::ZERO;
463        slot = insert_packed_value(slot, &val0, 0, 1).unwrap();
464        contract.sstore(base_slot, slot).unwrap();
465
466        let loaded_slot = contract.sload(base_slot).unwrap();
467        let expected = gen_slot_from(&[
468            "0x01", // offset 0 (1 byte)
469        ]);
470        assert_eq!(loaded_slot, expected);
471
472        // Test bool at offset 31
473        let val31 = false;
474        slot = U256::ZERO;
475        slot = insert_packed_value(slot, &val31, 31, 1).unwrap();
476        contract.sstore(base_slot + U256::ONE, slot).unwrap();
477
478        let loaded_slot = contract.sload(base_slot + U256::ONE).unwrap();
479        let expected = gen_slot_from(&[
480            "0x00",                                                             // offset 31 (1 byte)
481            "0x00000000000000000000000000000000000000000000000000000000000000", // padding (31 bytes)
482        ]);
483        assert_eq!(loaded_slot, expected);
484    }
485
486    #[test]
487    fn test_u256_fills_entire_slot() {
488        let mut contract = setup_test_contract();
489        let base_slot = U256::from(800);
490
491        // U256 should always fill entire slot (offset must be 0)
492        let val = U256::from(0x123456789ABCDEFu64);
493        val.store(&mut contract, base_slot, LayoutCtx::FULL)
494            .unwrap();
495
496        let loaded_slot = contract.sload(base_slot).unwrap();
497        assert_eq!(loaded_slot, val, "U256 should match slot contents exactly");
498
499        // Verify it's stored as-is (no packing)
500        let recovered = U256::load(&mut contract, base_slot, LayoutCtx::FULL).unwrap();
501        assert_eq!(recovered, val, "U256 load failed");
502    }
503
504    #[test]
505    fn test_primitive_delete_clears_slot() {
506        let mut contract = setup_test_contract();
507        let base_slot = U256::from(900);
508
509        // Store a u64 value
510        let val: u64 = 0x123456789ABCDEF0;
511        val.store(&mut contract, base_slot, LayoutCtx::FULL)
512            .unwrap();
513
514        // Verify slot is non-zero
515        let slot_before = contract.sload(base_slot).unwrap();
516        assert_ne!(
517            slot_before,
518            U256::ZERO,
519            "Slot should be non-zero before delete"
520        );
521
522        // Delete the value
523        u64::delete(&mut contract, base_slot, LayoutCtx::FULL).unwrap();
524
525        // Verify slot is now zero
526        let slot_after = contract.sload(base_slot).unwrap();
527        assert_eq!(slot_after, U256::ZERO, "Slot should be zero after delete");
528
529        // Verify loading returns zero
530        let loaded = u64::load(&mut contract, base_slot, LayoutCtx::FULL).unwrap();
531        assert_eq!(loaded, 0u64, "Loaded value should be 0 after delete");
532    }
533
534    // -- FIXED-SIZE ARRAY TESTS ------------------------------------------------
535
536    #[test]
537    fn test_array_u8_32_single_slot() {
538        let mut contract = setup_test_contract();
539        let base_slot = U256::ZERO;
540
541        // [u8; 32] should pack into exactly 1 slot
542        let data: [u8; 32] = [
543            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
544            25, 26, 27, 28, 29, 30, 31, 32,
545        ];
546
547        // Verify LAYOUT
548        <[u8; 32] as Storable<1>>::validate_layout();
549        assert_eq!(<[u8; 32] as StorableType>::LAYOUT, Layout::Slots(1));
550
551        // Store and load
552        data.store(&mut contract, base_slot, LayoutCtx::FULL)
553            .unwrap();
554        let loaded: [u8; 32] = Storable::load(&mut contract, base_slot, LayoutCtx::FULL).unwrap();
555        assert_eq!(loaded, data, "[u8; 32] roundtrip failed");
556
557        // Verify to_evm_words / from_evm_words
558        let words = data.to_evm_words().unwrap();
559        assert_eq!(words.len(), 1, "[u8; 32] should produce 1 word");
560        let recovered: [u8; 32] = Storable::from_evm_words(words).unwrap();
561        assert_eq!(recovered, data, "[u8; 32] EVM words roundtrip failed");
562
563        // Verify delete
564        <[u8; 32]>::delete(&mut contract, base_slot, LayoutCtx::FULL).unwrap();
565        let slot_value = contract.sload(base_slot).unwrap();
566        assert_eq!(slot_value, U256::ZERO, "Slot not cleared after delete");
567    }
568
569    #[test]
570    fn test_array_u64_5_multi_slot() {
571        let mut contract = setup_test_contract();
572        let base_slot = U256::from(100);
573
574        // [u64; 5] should require 2 slots (5 * 8 = 40 bytes > 32)
575        let data: [u64; 5] = [1, 2, 3, 4, 5];
576
577        // Verify slot count
578        <[u64; 5] as Storable<2>>::validate_layout();
579        assert_eq!(<[u64; 5] as StorableType>::LAYOUT, Layout::Slots(2));
580
581        // Store and load
582        data.store(&mut contract, base_slot, LayoutCtx::FULL)
583            .unwrap();
584        let loaded: [u64; 5] = Storable::load(&mut contract, base_slot, LayoutCtx::FULL).unwrap();
585        assert_eq!(loaded, data, "[u64; 5] roundtrip failed");
586
587        // Verify both slots are used
588        let slot0 = contract.sload(base_slot).unwrap();
589        let slot1 = contract.sload(base_slot + U256::ONE).unwrap();
590        assert_ne!(slot0, U256::ZERO, "Slot 0 should be non-zero");
591        assert_ne!(slot1, U256::ZERO, "Slot 1 should be non-zero");
592
593        // Verify delete clears both slots
594        <[u64; 5]>::delete(&mut contract, base_slot, LayoutCtx::FULL).unwrap();
595        let slot0_after = contract.sload(base_slot).unwrap();
596        let slot1_after = contract.sload(base_slot + U256::ONE).unwrap();
597        assert_eq!(slot0_after, U256::ZERO, "Slot 0 not cleared");
598        assert_eq!(slot1_after, U256::ZERO, "Slot 1 not cleared");
599    }
600
601    #[test]
602    fn test_array_u16_packing() {
603        let mut contract = setup_test_contract();
604        let base_slot = U256::from(200);
605
606        // [u16; 16] should pack into exactly 1 slot (16 * 2 = 32 bytes)
607        let data: [u16; 16] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
608
609        // Verify slot count
610        <[u16; 16] as Storable<1>>::validate_layout();
611        assert_eq!(<[u16; 16] as StorableType>::LAYOUT, Layout::Slots(1));
612
613        // Store and load
614        data.store(&mut contract, base_slot, LayoutCtx::FULL)
615            .unwrap();
616        let loaded: [u16; 16] = Storable::load(&mut contract, base_slot, LayoutCtx::FULL).unwrap();
617        assert_eq!(loaded, data, "[u16; 16] roundtrip failed");
618    }
619
620    #[test]
621    fn test_array_u256_no_packing() {
622        let mut contract = setup_test_contract();
623        let base_slot = U256::from(300);
624
625        // [U256; 3] should use 3 slots (no packing for 32-byte types)
626        let data: [U256; 3] = [U256::from(12345), U256::from(67890), U256::from(111111)];
627
628        // Verify slot count
629        <[U256; 3] as Storable<3>>::validate_layout();
630        assert_eq!(<[U256; 3] as StorableType>::LAYOUT, Layout::Slots(3));
631
632        // Store and load
633        data.store(&mut contract, base_slot, LayoutCtx::FULL)
634            .unwrap();
635        let loaded: [U256; 3] = Storable::load(&mut contract, base_slot, LayoutCtx::FULL).unwrap();
636        assert_eq!(loaded, data, "[U256; 3] roundtrip failed");
637
638        // Verify each element is in its own slot
639        for (i, expected_value) in data.iter().enumerate() {
640            let slot_value = contract.sload(base_slot + U256::from(i)).unwrap();
641            assert_eq!(slot_value, *expected_value, "Slot {i} mismatch");
642        }
643    }
644
645    #[test]
646    fn test_array_address_no_packing() {
647        let mut contract = setup_test_contract();
648        let base_slot = U256::from(400);
649
650        // [Address; 3] should use 3 slots (20 bytes doesn't divide 32 evenly)
651        let data: [Address; 3] = [
652            Address::repeat_byte(0x11),
653            Address::repeat_byte(0x22),
654            Address::repeat_byte(0x33),
655        ];
656
657        // Verify slot count
658        <[Address; 3] as Storable<3>>::validate_layout();
659        assert_eq!(<[Address; 3] as StorableType>::LAYOUT, Layout::Slots(3));
660
661        // Store and load
662        data.store(&mut contract, base_slot, LayoutCtx::FULL)
663            .unwrap();
664        let loaded: [Address; 3] =
665            Storable::load(&mut contract, base_slot, LayoutCtx::FULL).unwrap();
666        assert_eq!(loaded, data, "[Address; 3] roundtrip failed");
667    }
668
669    #[test]
670    fn test_array_empty_single_element() {
671        let mut contract = setup_test_contract();
672        let base_slot = U256::from(500);
673
674        // [u8; 1] should use 1 slot
675        let data: [u8; 1] = [42];
676
677        // Verify slot count
678        <[u8; 1] as Storable<1>>::validate_layout();
679        assert_eq!(<[u8; 1] as StorableType>::LAYOUT, Layout::Slots(1));
680
681        // Store and load
682        data.store(&mut contract, base_slot, LayoutCtx::FULL)
683            .unwrap();
684        let loaded: [u8; 1] = Storable::load(&mut contract, base_slot, LayoutCtx::FULL).unwrap();
685        assert_eq!(loaded, data, "[u8; 1] roundtrip failed");
686    }
687
688    #[test]
689    fn test_nested_array_u8_4x8() {
690        let mut contract = setup_test_contract();
691        let base_slot = U256::from(600);
692
693        // [[u8; 4]; 8] uses 8 slots (one per inner array)
694        // Each inner [u8; 4] gets a full 32-byte slot, even though it only uses 4 bytes
695        // This follows EVM's rule: nested arrays don't pack tightly across boundaries
696        let data: [[u8; 4]; 8] = [
697            [1, 2, 3, 4],
698            [5, 6, 7, 8],
699            [9, 10, 11, 12],
700            [13, 14, 15, 16],
701            [17, 18, 19, 20],
702            [21, 22, 23, 24],
703            [25, 26, 27, 28],
704            [29, 30, 31, 32],
705        ];
706
707        // Verify LAYOUT: 8 slots (one per inner array)
708        <[[u8; 4]; 8] as Storable<8>>::validate_layout();
709        assert_eq!(<[[u8; 4]; 8] as StorableType>::LAYOUT, Layout::Slots(8));
710
711        // Store and load
712        data.store(&mut contract, base_slot, LayoutCtx::FULL)
713            .unwrap();
714        let loaded: [[u8; 4]; 8] =
715            Storable::load(&mut contract, base_slot, LayoutCtx::FULL).unwrap();
716        assert_eq!(loaded, data, "[[u8; 4]; 8] roundtrip failed");
717
718        // Verify to_evm_words / from_evm_words
719        let words = data.to_evm_words().unwrap();
720        assert_eq!(words.len(), 8, "[[u8; 4]; 8] should produce 8 words");
721        let recovered: [[u8; 4]; 8] = Storable::from_evm_words(words).unwrap();
722        assert_eq!(recovered, data, "[[u8; 4]; 8] EVM words roundtrip failed");
723
724        // Verify delete clears all 8 slots
725        <[[u8; 4]; 8]>::delete(&mut contract, base_slot, LayoutCtx::FULL).unwrap();
726        for i in 0..8 {
727            let slot_value = contract.sload(base_slot + U256::from(i)).unwrap();
728            assert_eq!(slot_value, U256::ZERO, "Slot {i} not cleared after delete");
729        }
730    }
731
732    #[test]
733    fn test_nested_array_u16_2x8() {
734        let mut contract = setup_test_contract();
735        let base_slot = U256::from(700);
736
737        // [[u16; 2]; 8] uses 8 slots (one per inner array)
738        // Each inner [u16; 2] gets a full 32-byte slot, even though it only uses 4 bytes
739        // Compare: flat [u16; 16] would pack into 1 slot (16 × 2 = 32 bytes)
740        // But nested arrays don't pack across boundaries in EVM
741        let data: [[u16; 2]; 8] = [
742            [100, 101],
743            [200, 201],
744            [300, 301],
745            [400, 401],
746            [500, 501],
747            [600, 601],
748            [700, 701],
749            [800, 801],
750        ];
751
752        // Verify LAYOUT: 8 slots (one per inner array)
753        <[[u16; 2]; 8] as Storable<8>>::validate_layout();
754        assert_eq!(<[[u16; 2]; 8] as StorableType>::LAYOUT, Layout::Slots(8));
755
756        // Store and load
757        data.store(&mut contract, base_slot, LayoutCtx::FULL)
758            .unwrap();
759        let loaded: [[u16; 2]; 8] =
760            Storable::load(&mut contract, base_slot, LayoutCtx::FULL).unwrap();
761        assert_eq!(loaded, data, "[[u16; 2]; 8] roundtrip failed");
762
763        // Verify to_evm_words / from_evm_words
764        let words = data.to_evm_words().unwrap();
765        assert_eq!(words.len(), 8, "[[u16; 2]; 8] should produce 8 words");
766        let recovered: [[u16; 2]; 8] = Storable::from_evm_words(words).unwrap();
767        assert_eq!(recovered, data, "[[u16; 2]; 8] EVM words roundtrip failed");
768
769        // Verify delete clears all 8 slots
770        <[[u16; 2]; 8]>::delete(&mut contract, base_slot, LayoutCtx::FULL).unwrap();
771        for i in 0..8 {
772            let slot_value = contract.sload(base_slot + U256::from(i)).unwrap();
773            assert_eq!(slot_value, U256::ZERO, "Slot {i} not cleared after delete");
774        }
775    }
776
777    proptest! {
778        #![proptest_config(ProptestConfig::with_cases(500))]
779
780        #[test]
781        fn test_array_u8_32(
782            data in prop::array::uniform32(any::<u8>()),
783            base_slot in arb_safe_slot()
784        ) {
785            let mut contract = setup_test_contract();
786
787            // Store and load
788            data.store(&mut contract, base_slot, LayoutCtx::FULL)?;
789            let loaded: [u8; 32] = Storable::load(&mut contract, base_slot, LayoutCtx::FULL)?;
790            prop_assert_eq!(&loaded, &data, "[u8; 32] roundtrip failed");
791
792            // EVM words roundtrip
793            let words = data.to_evm_words()?;
794            let recovered: [u8; 32] = Storable::from_evm_words(words)?;
795            prop_assert_eq!(&recovered, &data, "[u8; 32] EVM words roundtrip failed");
796
797            // Delete
798            <[u8; 32]>::delete(&mut contract, base_slot, LayoutCtx::FULL)?;
799            let slot_value = contract.sload(base_slot)?;
800            prop_assert_eq!(slot_value, U256::ZERO, "Slot not cleared after delete");
801        }
802
803        #[test]
804        fn test_array_u16_16(
805            data in prop::array::uniform16(any::<u16>()),
806            base_slot in arb_safe_slot()
807        ) {
808            let mut contract = setup_test_contract();
809
810            // Store and load
811            data.store(&mut contract, base_slot, LayoutCtx::FULL)?;
812            let loaded: [u16; 16] = Storable::load(&mut contract, base_slot, LayoutCtx::FULL)?;
813            prop_assert_eq!(&loaded, &data, "[u16; 16] roundtrip failed");
814
815            // EVM words roundtrip
816            let words = data.to_evm_words()?;
817            let recovered: [u16; 16] = Storable::from_evm_words(words)?;
818            prop_assert_eq!(&recovered, &data, "[u16; 16] EVM words roundtrip failed");
819        }
820
821        #[test]
822        fn test_array_u256_5(
823            data in prop::array::uniform5(any::<u64>()).prop_map(|arr| arr.map(U256::from)),
824            base_slot in arb_safe_slot()
825        ) {
826            let mut contract = setup_test_contract();
827
828            // Store and load
829            data.store(&mut contract, base_slot, LayoutCtx::FULL)?;
830            let loaded: [U256; 5] = Storable::load(&mut contract, base_slot, LayoutCtx::FULL)?;
831            prop_assert_eq!(&loaded, &data, "[U256; 5] roundtrip failed");
832
833            // Verify each element is in its own slot
834            for (i, expected_value) in data.iter().enumerate() {
835                let slot_value = contract.sload(base_slot + U256::from(i))?;
836                prop_assert_eq!(slot_value, *expected_value, "Slot {} mismatch", i);
837            }
838
839            // EVM words roundtrip
840            let words = data.to_evm_words()?;
841            let recovered: [U256; 5] = Storable::from_evm_words(words)?;
842            prop_assert_eq!(&recovered, &data, "[U256; 5] EVM words roundtrip failed");
843
844            // Delete
845            <[U256; 5]>::delete(&mut contract, base_slot, LayoutCtx::FULL)?;
846            for i in 0..5 {
847                let slot_value = contract.sload(base_slot + U256::from(i))?;
848                prop_assert_eq!(slot_value, U256::ZERO, "Slot {} not cleared", i);
849            }
850        }
851    }
852}