1use 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
12tempo_precompiles_macros::storable_rust_ints!();
14tempo_precompiles_macros::storable_alloy_ints!();
16tempo_precompiles_macros::storable_alloy_bytes!();
18tempo_precompiles_macros::storable_arrays!();
20tempo_precompiles_macros::storable_nested_arrays!();
22
23impl 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 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 fn setup_test_contract() -> TestContract {
144 TestContract {
145 address: Address::random(),
146 storage: HashMapStorageProvider::new(1),
147 }
148 }
149
150 fn arb_safe_slot() -> impl Strategy<Value = U256> {
152 any::<[u64; 4]>().prop_map(|limbs| {
153 U256::from_limbs(limbs) % (U256::MAX - U256::from(10000))
155 })
156 }
157
158 fn arb_address() -> impl Strategy<Value = Address> {
160 any::<[u8; 20]>().prop_map(Address::from)
161 }
162
163 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 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 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 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 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 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 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 #[test]
218 fn test_u8_at_various_offsets() {
219 let mut contract = setup_test_contract();
220 let base_slot = U256::from(100);
221
222 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", ]);
232 assert_eq!(loaded_slot, expected);
233
234 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", "0x000000000000000000000000000000", ]);
245 assert_eq!(loaded_slot, expected);
246
247 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", "0x00000000000000000000000000000000000000000000000000000000000000", ]);
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 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", ]);
276 assert_eq!(loaded_slot, expected);
277
278 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", "0x000000000000000000000000000000", ]);
289 assert_eq!(loaded_slot, expected);
290
291 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", "0x000000000000000000000000000000000000000000000000000000000000", ]);
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 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", ]);
320 assert_eq!(loaded_slot, expected);
321
322 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", "0x0000000000000000000000000000", ]);
333 assert_eq!(loaded_slot, expected);
334
335 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", "0x00000000000000000000000000000000000000000000000000000000", ]);
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 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", ]);
364 assert_eq!(loaded_slot, expected);
365
366 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", "0x000000000000000000000000", ]);
377 assert_eq!(loaded_slot, expected);
378
379 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", "0x000000000000000000000000000000000000000000000000", ]);
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 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", ]);
408 assert_eq!(loaded_slot, expected);
409
410 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", "0x00000000000000000000000000000000", ]);
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 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", ]);
439 assert_eq!(loaded_slot, expected);
440
441 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", "0x000000000000000000000000", ]);
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 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", ]);
470 assert_eq!(loaded_slot, expected);
471
472 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", "0x00000000000000000000000000000000000000000000000000000000000000", ]);
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 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 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 let val: u64 = 0x123456789ABCDEF0;
511 val.store(&mut contract, base_slot, LayoutCtx::FULL)
512 .unwrap();
513
514 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 u64::delete(&mut contract, base_slot, LayoutCtx::FULL).unwrap();
524
525 let slot_after = contract.sload(base_slot).unwrap();
527 assert_eq!(slot_after, U256::ZERO, "Slot should be zero after delete");
528
529 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 #[test]
537 fn test_array_u8_32_single_slot() {
538 let mut contract = setup_test_contract();
539 let base_slot = U256::ZERO;
540
541 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 <[u8; 32] as Storable<1>>::validate_layout();
549 assert_eq!(<[u8; 32] as StorableType>::LAYOUT, Layout::Slots(1));
550
551 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 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 <[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 let data: [u64; 5] = [1, 2, 3, 4, 5];
576
577 <[u64; 5] as Storable<2>>::validate_layout();
579 assert_eq!(<[u64; 5] as StorableType>::LAYOUT, Layout::Slots(2));
580
581 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 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 <[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 let data: [u16; 16] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
608
609 <[u16; 16] as Storable<1>>::validate_layout();
611 assert_eq!(<[u16; 16] as StorableType>::LAYOUT, Layout::Slots(1));
612
613 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 let data: [U256; 3] = [U256::from(12345), U256::from(67890), U256::from(111111)];
627
628 <[U256; 3] as Storable<3>>::validate_layout();
630 assert_eq!(<[U256; 3] as StorableType>::LAYOUT, Layout::Slots(3));
631
632 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 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 let data: [Address; 3] = [
652 Address::repeat_byte(0x11),
653 Address::repeat_byte(0x22),
654 Address::repeat_byte(0x33),
655 ];
656
657 <[Address; 3] as Storable<3>>::validate_layout();
659 assert_eq!(<[Address; 3] as StorableType>::LAYOUT, Layout::Slots(3));
660
661 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 let data: [u8; 1] = [42];
676
677 <[u8; 1] as Storable<1>>::validate_layout();
679 assert_eq!(<[u8; 1] as StorableType>::LAYOUT, Layout::Slots(1));
680
681 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 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 <[[u8; 4]; 8] as Storable<8>>::validate_layout();
709 assert_eq!(<[[u8; 4]; 8] as StorableType>::LAYOUT, Layout::Slots(8));
710
711 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 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 <[[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 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 <[[u16; 2]; 8] as Storable<8>>::validate_layout();
754 assert_eq!(<[[u16; 2]; 8] as StorableType>::LAYOUT, Layout::Slots(8));
755
756 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 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 <[[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 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 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 <[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 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 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 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 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 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 <[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}