1use 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
26tempo_precompiles_macros::storable_arrays!();
28tempo_precompiles_macros::storable_nested_arrays!();
30
31#[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 #[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 #[inline]
78 fn as_slot(&self) -> Slot<[T; N]> {
79 Slot::new(self.base_slot, self.address)
80 }
81
82 #[inline]
87 pub fn base_slot(&self) -> ::alloy::primitives::U256 {
88 self.base_slot
89 }
90
91 #[inline]
93 pub const fn len(&self) -> usize {
94 N
95 }
96
97 #[inline]
99 pub const fn is_empty(&self) -> bool {
100 N == 0
101 }
102
103 #[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 #[inline]
123 fn compute_handler(base_slot: U256, address: Address, index: usize) -> T::Handler {
124 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 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 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 #[inline]
173 fn read(&self) -> Result<[T; N]> {
174 self.as_slot().read()
175 }
176
177 #[inline]
179 fn write(&mut self, value: [T; N]) -> Result<()> {
180 self.as_slot().write(value)
181 }
182
183 #[inline]
185 fn delete(&mut self) -> Result<()> {
186 self.as_slot().delete()
187 }
188
189 #[inline]
191 fn t_read(&self) -> Result<[T; N]> {
192 self.as_slot().t_read()
193 }
194
195 #[inline]
197 fn t_write(&mut self, value: [T; N]) -> Result<()> {
198 self.as_slot().t_write(value)
199 }
200
201 #[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 fn arb_safe_slot() -> impl Strategy<Value = U256> {
220 any::<[u64; 4]>().prop_map(|limbs| {
221 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 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 assert_eq!(<[u8; 32] as StorableType>::LAYOUT, Layout::Slots(1));
239
240 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 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 let data: [u64; 5] = [1, 2, 3, 4, 5];
261
262 assert_eq!(<[u64; 5] as StorableType>::LAYOUT, Layout::Slots(2));
264
265 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 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 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 let data: [u16; 16] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
297
298 assert_eq!(<[u16; 16] as StorableType>::LAYOUT, Layout::Slots(1));
300
301 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 let data: [U256; 3] = [U256::from(12345), U256::from(67890), U256::from(111111)];
317
318 assert_eq!(<[U256; 3] as StorableType>::LAYOUT, Layout::Slots(3));
320
321 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 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 let data: [Address; 3] = [
343 Address::repeat_byte(0x11),
344 Address::repeat_byte(0x22),
345 Address::repeat_byte(0x33),
346 ];
347
348 assert_eq!(<[Address; 3] as StorableType>::LAYOUT, Layout::Slots(3));
350
351 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 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 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 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 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 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 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 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 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 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 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 let data: [u8; 1] = [42];
499
500 assert_eq!(<[u8; 1] as StorableType>::LAYOUT, Layout::Slots(1));
502
503 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 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 assert_eq!(<[[u8; 4]; 8] as StorableType>::LAYOUT, Layout::Slots(8));
533
534 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 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 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 assert_eq!(<[[u16; 2]; 8] as StorableType>::LAYOUT, Layout::Slots(8));
572
573 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 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 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 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 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 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 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 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}