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 proptest::prelude::*;
216
217 fn arb_safe_slot() -> impl Strategy<Value = U256> {
219 any::<[u64; 4]>().prop_map(|limbs| {
220 U256::from_limbs(limbs) % (U256::MAX - U256::from(10000))
222 })
223 }
224
225 #[test]
226 fn test_array_u8_32_single_slot() {
227 let (mut storage, address) = setup_storage();
228 let base_slot = U256::ZERO;
229
230 let data: [u8; 32] = [
232 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
233 25, 26, 27, 28, 29, 30, 31, 32,
234 ];
235
236 assert_eq!(<[u8; 32] as StorableType>::LAYOUT, Layout::Slots(1));
238
239 StorageCtx::enter(&mut storage, || {
241 let mut slot = <[u8; 32]>::handle(base_slot, LayoutCtx::FULL, address);
242 slot.write(data).unwrap();
243 let loaded = slot.read().unwrap();
244 assert_eq!(loaded, data, "[u8; 32] roundtrip failed");
245
246 slot.delete().unwrap();
248 });
249 let slot_value = storage.sload(address, base_slot).unwrap();
250 assert_eq!(slot_value, U256::ZERO, "Slot not cleared after delete");
251 }
252
253 #[test]
254 fn test_array_u64_5_multi_slot() {
255 let (mut storage, address) = setup_storage();
256 let base_slot = U256::from(100);
257
258 let data: [u64; 5] = [1, 2, 3, 4, 5];
260
261 assert_eq!(<[u64; 5] as StorableType>::LAYOUT, Layout::Slots(2));
263
264 StorageCtx::enter(&mut storage, || {
266 let mut slot = <[u64; 5]>::handle(base_slot, LayoutCtx::FULL, address);
267 slot.write(data).unwrap();
268 let loaded = slot.read().unwrap();
269 assert_eq!(loaded, data, "[u64; 5] roundtrip failed");
270 });
271
272 let slot0 = storage.sload(address, base_slot).unwrap();
274 let slot1 = storage.sload(address, base_slot + U256::ONE).unwrap();
275 assert_ne!(slot0, U256::ZERO, "Slot 0 should be non-zero");
276 assert_ne!(slot1, U256::ZERO, "Slot 1 should be non-zero");
277
278 StorageCtx::enter(&mut storage, || {
280 let mut slot = <[u64; 5]>::handle(base_slot, LayoutCtx::FULL, address);
281 slot.delete().unwrap();
282 });
283 let slot0_after = storage.sload(address, base_slot).unwrap();
284 let slot1_after = storage.sload(address, base_slot + U256::ONE).unwrap();
285 assert_eq!(slot0_after, U256::ZERO, "Slot 0 not cleared");
286 assert_eq!(slot1_after, U256::ZERO, "Slot 1 not cleared");
287 }
288
289 #[test]
290 fn test_array_u16_packing() {
291 let (mut storage, address) = setup_storage();
292 let base_slot = U256::from(200);
293
294 let data: [u16; 16] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
296
297 assert_eq!(<[u16; 16] as StorableType>::LAYOUT, Layout::Slots(1));
299
300 StorageCtx::enter(&mut storage, || {
302 let mut slot = <[u16; 16]>::handle(base_slot, LayoutCtx::FULL, address);
303 slot.write(data).unwrap();
304 let loaded = slot.read().unwrap();
305 assert_eq!(loaded, data, "[u16; 16] roundtrip failed");
306 });
307 }
308
309 #[test]
310 fn test_array_u256_no_packing() {
311 let (mut storage, address) = setup_storage();
312 let base_slot = U256::from(300);
313
314 let data: [U256; 3] = [U256::from(12345), U256::from(67890), U256::from(111111)];
316
317 assert_eq!(<[U256; 3] as StorableType>::LAYOUT, Layout::Slots(3));
319
320 StorageCtx::enter(&mut storage, || {
322 let mut slot = <[U256; 3]>::handle(base_slot, LayoutCtx::FULL, address);
323 slot.write(data).unwrap();
324 let loaded = slot.read().unwrap();
325 assert_eq!(loaded, data, "[U256; 3] roundtrip failed");
326 });
327
328 for (i, expected_value) in data.iter().enumerate() {
330 let slot_value = storage.sload(address, base_slot + U256::from(i)).unwrap();
331 assert_eq!(slot_value, *expected_value, "Slot {i} mismatch");
332 }
333 }
334
335 #[test]
336 fn test_array_address_no_packing() {
337 let (mut storage, address) = setup_storage();
338 let base_slot = U256::from(400);
339
340 let data: [Address; 3] = [
342 Address::repeat_byte(0x11),
343 Address::repeat_byte(0x22),
344 Address::repeat_byte(0x33),
345 ];
346
347 assert_eq!(<[Address; 3] as StorableType>::LAYOUT, Layout::Slots(3));
349
350 StorageCtx::enter(&mut storage, || {
352 let mut slot = <[Address; 3]>::handle(base_slot, LayoutCtx::FULL, address);
353 slot.write(data).unwrap();
354 let loaded = slot.read().unwrap();
355 assert_eq!(loaded, data, "[Address; 3] roundtrip failed");
356 });
357 }
358
359 #[test]
360 fn test_array_empty_single_element() {
361 let (mut storage, address) = setup_storage();
362 let base_slot = U256::from(500);
363
364 let data: [u8; 1] = [42];
366
367 assert_eq!(<[u8; 1] as StorableType>::LAYOUT, Layout::Slots(1));
369
370 StorageCtx::enter(&mut storage, || {
372 let mut slot = <[u8; 1]>::handle(base_slot, LayoutCtx::FULL, address);
373 slot.write(data).unwrap();
374 let loaded = slot.read().unwrap();
375 assert_eq!(loaded, data, "[u8; 1] roundtrip failed");
376 });
377 }
378
379 #[test]
380 fn test_nested_array_u8_4x8() {
381 let (mut storage, address) = setup_storage();
382 let base_slot = U256::from(600);
383
384 let data: [[u8; 4]; 8] = [
388 [1, 2, 3, 4],
389 [5, 6, 7, 8],
390 [9, 10, 11, 12],
391 [13, 14, 15, 16],
392 [17, 18, 19, 20],
393 [21, 22, 23, 24],
394 [25, 26, 27, 28],
395 [29, 30, 31, 32],
396 ];
397
398 assert_eq!(<[[u8; 4]; 8] as StorableType>::LAYOUT, Layout::Slots(8));
400
401 StorageCtx::enter(&mut storage, || {
403 let mut slot = <[[u8; 4]; 8]>::handle(base_slot, LayoutCtx::FULL, address);
404 slot.write(data).unwrap();
405 let loaded = slot.read().unwrap();
406 assert_eq!(loaded, data, "[[u8; 4]; 8] roundtrip failed");
407
408 slot.delete().unwrap();
410 });
411 for i in 0..8 {
412 let slot_value = storage.sload(address, base_slot + U256::from(i)).unwrap();
413 assert_eq!(slot_value, U256::ZERO, "Slot {i} not cleared after delete");
414 }
415 }
416
417 #[test]
418 fn test_nested_array_u16_2x8() {
419 let (mut storage, address) = setup_storage();
420 let base_slot = U256::from(700);
421
422 let data: [[u16; 2]; 8] = [
427 [100, 101],
428 [200, 201],
429 [300, 301],
430 [400, 401],
431 [500, 501],
432 [600, 601],
433 [700, 701],
434 [800, 801],
435 ];
436
437 assert_eq!(<[[u16; 2]; 8] as StorableType>::LAYOUT, Layout::Slots(8));
439
440 StorageCtx::enter(&mut storage, || {
442 let mut slot = <[[u16; 2]; 8]>::handle(base_slot, LayoutCtx::FULL, address);
443 slot.write(data).unwrap();
444 let loaded = slot.read().unwrap();
445 assert_eq!(loaded, data, "[[u16; 2]; 8] roundtrip failed");
446
447 slot.delete().unwrap();
449 });
450 for i in 0..8 {
451 let slot_value = storage.sload(address, base_slot + U256::from(i)).unwrap();
452 assert_eq!(slot_value, U256::ZERO, "Slot {i} not cleared after delete");
453 }
454 }
455
456 proptest! {
457 #![proptest_config(ProptestConfig::with_cases(500))]
458
459 #[test]
460 fn test_array_u8_32(
461 data in prop::array::uniform32(any::<u8>()),
462 base_slot in arb_safe_slot()
463 ) {
464 let (mut storage, address) = setup_storage();
465
466 StorageCtx::enter(&mut storage, || {
468 let mut slot = <[u8; 32]>::handle(base_slot, LayoutCtx::FULL, address);
469 slot.write(data).unwrap();
470 let loaded = slot.read().unwrap();
471 prop_assert_eq!(&loaded, &data, "[u8; 32] roundtrip failed");
472
473 slot.delete().unwrap();
475 Ok(())
476 })?;
477 let slot_value = storage.sload(address, base_slot).unwrap();
478 prop_assert_eq!(slot_value, U256::ZERO, "Slot not cleared after delete");
479 }
480
481 #[test]
482 fn test_array_u16_16(
483 data in prop::array::uniform16(any::<u16>()),
484 base_slot in arb_safe_slot()
485 ) {
486 let (mut storage, address) = setup_storage();
487
488 StorageCtx::enter(&mut storage, || {
490 let mut slot = <[u16; 16]>::handle(base_slot, LayoutCtx::FULL, address);
491 slot.write(data).unwrap();
492 let loaded = slot.read().unwrap();
493 prop_assert_eq!(&loaded, &data, "[u16; 16] roundtrip failed");
494 Ok(())
495 })?;
496 }
497
498 #[test]
499 fn test_array_u256_5(
500 data in prop::array::uniform5(any::<u64>()).prop_map(|arr| arr.map(U256::from)),
501 base_slot in arb_safe_slot()
502 ) {
503 let (mut storage, address) = setup_storage();
504
505 StorageCtx::enter(&mut storage, || {
507 let mut slot = <[U256; 5]>::handle(base_slot, LayoutCtx::FULL, address);
508 slot.write(data).unwrap();
509 let loaded = slot.read().unwrap();
510 prop_assert_eq!(&loaded, &data, "[U256; 5] roundtrip failed");
511 Ok(())
512 })?;
513
514 for (i, expected_value) in data.iter().enumerate() {
516 let slot_value = storage.sload(address, base_slot + U256::from(i)).unwrap();
517 prop_assert_eq!(slot_value, *expected_value, "Slot {} mismatch", i);
518 }
519
520 StorageCtx::enter(&mut storage, || {
522 let mut slot = <[U256; 5]>::handle(base_slot, LayoutCtx::FULL, address);
523 slot.delete().unwrap();
524 Ok::<(), proptest::test_runner::TestCaseError>(())
525 })?;
526 for i in 0..5 {
527 let slot_value = storage.sload(address, base_slot + U256::from(i)).unwrap();
528 prop_assert_eq!(slot_value, U256::ZERO, "Slot {} not cleared", i);
529 }
530 }
531 }
532}