Skip to main content

tempo_precompiles_macros/
storable_primitives.rs

1//! Code generation for primitive type storage implementations.
2
3use proc_macro2::TokenStream;
4use quote::quote;
5
6pub(crate) const RUST_INT_SIZES: &[usize] = &[8, 16, 32, 64, 128];
7pub(crate) const ALLOY_INT_SIZES: &[usize] = &[8, 16, 32, 64, 128, 256];
8
9// -- CONFIGURATION TYPES ------------------------------------------------------
10
11/// Strategy for converting to U256
12#[derive(Debug, Clone)]
13enum StorableConversionStrategy {
14    UnsignedRust,
15    UnsignedAlloy(proc_macro2::Ident),
16    SignedRust(proc_macro2::Ident),
17    SignedAlloy(proc_macro2::Ident),
18    FixedBytes(usize),
19}
20
21/// Strategy for converting to storage key bytes
22#[derive(Debug, Clone)]
23enum StorageKeyStrategy {
24    Simple,           // `self.to_be_bytes()`
25    WithSize(usize),  // `self.to_be_bytes::<N>()`
26    SignedRaw(usize), // `self.into_raw().to_be_bytes::<N>()`
27    AsSlice,          // `self.as_slice()`
28}
29
30/// Complete configuration for generating implementations for a type
31#[derive(Debug, Clone)]
32struct TypeConfig {
33    type_path: TokenStream,
34    byte_count: usize,
35    storable_strategy: StorableConversionStrategy,
36    storage_key_strategy: StorageKeyStrategy,
37}
38
39// -- IMPLEMENTATION GENERATORS ------------------------------------------------
40
41/// Generate a `StorableType` implementation
42fn gen_storable_layout_impl(type_path: &TokenStream, byte_count: usize) -> TokenStream {
43    quote! {
44        impl StorableType for #type_path {
45            const LAYOUT: Layout = Layout::Bytes(#byte_count);
46            type Handler = crate::storage::Slot<Self>;
47
48            fn handle(slot: U256, ctx: LayoutCtx, address: ::alloy::primitives::Address) -> Self::Handler {
49                crate::storage::Slot::new_with_ctx(slot, ctx, address)
50            }
51        }
52    }
53}
54
55/// Generate a `StorageKey` implementation based on the conversion strategy
56fn gen_storage_key_impl(type_path: &TokenStream, strategy: &StorageKeyStrategy) -> TokenStream {
57    let conversion = match strategy {
58        StorageKeyStrategy::Simple => quote! { self.to_be_bytes() },
59        StorageKeyStrategy::WithSize(size) => quote! { self.to_be_bytes::<#size>() },
60        StorageKeyStrategy::SignedRaw(size) => quote! { self.into_raw().to_be_bytes::<#size>() },
61        StorageKeyStrategy::AsSlice => quote! { self.as_slice() },
62    };
63
64    quote! {
65        impl StorageKey for #type_path {
66            #[inline]
67            fn as_storage_bytes(&self) -> impl AsRef<[u8]> {
68                #conversion
69            }
70        }
71    }
72}
73
74/// Generate `FromWord` implementation for all primitive types (storage I/O is in `Storable`).
75fn gen_to_word_impl(type_path: &TokenStream, strategy: &StorableConversionStrategy) -> TokenStream {
76    match strategy {
77        StorableConversionStrategy::UnsignedRust => {
78            quote! {
79                impl FromWord for #type_path {
80                    #[inline]
81                    fn to_word(&self) -> ::alloy::primitives::U256 {
82                        ::alloy::primitives::U256::from(*self)
83                    }
84
85                    #[inline]
86                    fn from_word(word: U256) -> crate::error::Result<Self> {
87                        word.try_into().map_err(|_| crate::error::TempoPrecompileError::under_overflow())
88                    }
89                }
90            }
91        }
92        StorableConversionStrategy::UnsignedAlloy(ty) => {
93            quote! {
94                impl FromWord for #type_path {
95                    #[inline]
96                    fn to_word(&self) -> ::alloy::primitives::U256 {
97                        ::alloy::primitives::U256::from(*self)
98                    }
99
100                    #[inline]
101                    fn from_word(word: ::alloy::primitives::U256) -> crate::error::Result<Self> {
102                        // Check if value fits in target type
103                        if word > ::alloy::primitives::U256::from(::alloy::primitives::#ty::MAX) {
104                            return Err(crate::error::TempoPrecompileError::under_overflow());
105                        }
106                        Ok(word.to::<Self>())
107                    }
108                }
109            }
110        }
111        StorableConversionStrategy::SignedRust(unsigned_type) => {
112            quote! {
113                impl FromWord for #type_path {
114                    #[inline]
115                    fn to_word(&self) -> U256 {
116                        // Store as right-aligned unsigned representation
117                        ::alloy::primitives::U256::from(*self as #unsigned_type)
118                    }
119
120                    #[inline]
121                    fn from_word(word: U256) -> crate::error::Result<Self> {
122                        // Extract low bytes as unsigned, then interpret as signed
123                        let unsigned: #unsigned_type = word.try_into()
124                            .map_err(|_| crate::error::TempoPrecompileError::under_overflow())?;
125                        Ok(unsigned as Self)
126                    }
127                }
128            }
129        }
130        StorableConversionStrategy::SignedAlloy(unsigned_type) => {
131            quote! {
132                impl FromWord for #type_path {
133                    #[inline]
134                    fn to_word(&self) -> ::alloy::primitives::U256 {
135                        // Store as right-aligned unsigned representation
136                        ::alloy::primitives::U256::from(self.into_raw())
137                    }
138
139                    #[inline]
140                    fn from_word(word: ::alloy::primitives::U256) -> crate::error::Result<Self> {
141                        // Check if value fits in the unsigned backing type
142                        if word > ::alloy::primitives::U256::from(::alloy::primitives::#unsigned_type::MAX) {
143                            return Err(crate::error::TempoPrecompileError::under_overflow());
144                        }
145                        // Extract low bytes as unsigned, then interpret as signed
146                        let unsigned_val = word.to::<::alloy::primitives::#unsigned_type>();
147                        Ok(Self::from_raw(unsigned_val))
148                    }
149                }
150            }
151        }
152        StorableConversionStrategy::FixedBytes(size) => {
153            quote! {
154                impl FromWord for #type_path {
155                    #[inline]
156                    fn to_word(&self) -> ::alloy::primitives::U256 {
157                        let mut bytes = [0u8; 32];
158                        bytes[32 - #size..].copy_from_slice(&self[..]);
159                        ::alloy::primitives::U256::from_be_bytes(bytes)
160                    }
161
162                    #[inline]
163                    fn from_word(word: ::alloy::primitives::U256) -> crate::error::Result<Self> {
164                        let bytes = word.to_be_bytes::<32>();
165                        let mut fixed_bytes = [0u8; #size];
166                        fixed_bytes.copy_from_slice(&bytes[32 - #size..]);
167                        Ok(Self::from(fixed_bytes))
168                    }
169                }
170            }
171        }
172    }
173}
174
175/// Generate all storage-related impls for a type.
176fn gen_complete_impl_set(config: &TypeConfig) -> TokenStream {
177    let type_path = &config.type_path;
178    let storable_type_impl = gen_storable_layout_impl(type_path, config.byte_count);
179    let storage_key_impl = gen_storage_key_impl(type_path, &config.storage_key_strategy);
180    let to_word_impl = gen_to_word_impl(type_path, &config.storable_strategy);
181
182    let full_word_storable_impl = if config.byte_count < 32 {
183        // `Packable` types are `Storable` via a blanket implementation
184        quote! {
185            impl crate::storage::types::sealed::OnlyPrimitives for #type_path {}
186            impl crate::storage::types::Packable for #type_path {}
187        }
188    } else {
189        // Full-word types need explicit `Storable` impl
190        quote! {
191            impl crate::storage::types::sealed::OnlyPrimitives for #type_path {}
192            impl crate::storage::Storable for #type_path {
193                #[inline]
194                fn load<S: crate::storage::StorageOps>(
195                    storage: &S,
196                    slot: ::alloy::primitives::U256,
197                    _ctx: crate::storage::LayoutCtx
198                ) -> crate::error::Result<Self> {
199                    storage.load(slot).and_then(<Self as crate::storage::types::FromWord>::from_word)
200                }
201
202                #[inline]
203                fn store<S: crate::storage::StorageOps>(
204                    &self,
205                    storage: &mut S,
206                    slot: ::alloy::primitives::U256,
207                    _ctx: crate::storage::LayoutCtx
208                ) -> crate::error::Result<()> {
209                    storage.store(slot, <Self as crate::storage::types::FromWord>::to_word(self))
210                }
211            }
212        }
213    };
214
215    quote! {
216        #storable_type_impl
217        #to_word_impl
218        #storage_key_impl
219        #full_word_storable_impl
220    }
221}
222
223/// Generate `StorableType`, `Packable`, and `StorageKey` for all standard Rust integer types.
224pub(crate) fn gen_storable_rust_ints() -> TokenStream {
225    let mut impls = Vec::with_capacity(RUST_INT_SIZES.len() * 2);
226
227    for size in RUST_INT_SIZES {
228        let unsigned_type = quote::format_ident!("u{}", size);
229        let signed_type = quote::format_ident!("i{}", size);
230        let byte_count = size / 8;
231
232        // Generate unsigned integer configuration and implementation
233        let unsigned_config = TypeConfig {
234            type_path: quote! { #unsigned_type },
235            byte_count,
236            storable_strategy: StorableConversionStrategy::UnsignedRust,
237            storage_key_strategy: StorageKeyStrategy::Simple,
238        };
239        impls.push(gen_complete_impl_set(&unsigned_config));
240
241        // Generate signed integer configuration and implementation
242        let signed_config = TypeConfig {
243            type_path: quote! { #signed_type },
244            byte_count,
245            storable_strategy: StorableConversionStrategy::SignedRust(unsigned_type.clone()),
246            storage_key_strategy: StorageKeyStrategy::Simple,
247        };
248        impls.push(gen_complete_impl_set(&signed_config));
249    }
250
251    quote! {
252        #(#impls)*
253    }
254}
255
256/// Generate `StorableType`, `Packable`, and `StorageKey` for alloy integer types.
257fn gen_alloy_integers() -> Vec<TokenStream> {
258    let mut impls = Vec::with_capacity(ALLOY_INT_SIZES.len() * 2);
259
260    for &size in ALLOY_INT_SIZES {
261        let unsigned_type = quote::format_ident!("U{}", size);
262        let signed_type = quote::format_ident!("I{}", size);
263        let byte_count = size / 8;
264
265        // Generate unsigned integer configuration and implementation
266        let unsigned_config = TypeConfig {
267            type_path: quote! { ::alloy::primitives::#unsigned_type },
268            byte_count,
269            storable_strategy: StorableConversionStrategy::UnsignedAlloy(unsigned_type.clone()),
270            storage_key_strategy: StorageKeyStrategy::WithSize(byte_count),
271        };
272        impls.push(gen_complete_impl_set(&unsigned_config));
273
274        // Generate signed integer configuration and implementation
275        let signed_config = TypeConfig {
276            type_path: quote! { ::alloy::primitives::#signed_type },
277            byte_count,
278            storable_strategy: StorableConversionStrategy::SignedAlloy(unsigned_type.clone()),
279            storage_key_strategy: StorageKeyStrategy::SignedRaw(byte_count),
280        };
281        impls.push(gen_complete_impl_set(&signed_config));
282    }
283
284    impls
285}
286
287/// Generate `StorableType`, `Packable`, and `StorageKey` for `FixedBytes<N>` types.
288fn gen_fixed_bytes(sizes: &[usize]) -> Vec<TokenStream> {
289    let mut impls = Vec::with_capacity(sizes.len());
290
291    for &size in sizes {
292        // Generate FixedBytes configuration and implementation
293        let config = TypeConfig {
294            type_path: quote! { ::alloy::primitives::FixedBytes<#size> },
295            byte_count: size,
296            storable_strategy: StorableConversionStrategy::FixedBytes(size),
297            storage_key_strategy: StorageKeyStrategy::AsSlice,
298        };
299        impls.push(gen_complete_impl_set(&config));
300    }
301
302    impls
303}
304
305/// Generate `StorableType`, `Packable`, and `StorageKey` for `FixedBytes<N>` types.
306pub(crate) fn gen_storable_alloy_bytes() -> TokenStream {
307    let sizes: Vec<usize> = (1..=32).collect();
308    let impls = gen_fixed_bytes(&sizes);
309
310    quote! {
311        #(#impls)*
312    }
313}
314
315/// Generate `StorableType`, `Packable`, and `StorageKey` for all alloy integer types.
316pub(crate) fn gen_storable_alloy_ints() -> TokenStream {
317    let impls = gen_alloy_integers();
318
319    quote! {
320        #(#impls)*
321    }
322}
323
324// -- ARRAY IMPLEMENTATIONS ----------------------------------------------------
325
326/// Configuration for generating array implementations
327#[derive(Debug, Clone)]
328struct ArrayConfig {
329    elem_type: TokenStream,
330    array_size: usize,
331    elem_byte_count: usize,
332    elem_is_packable: bool,
333}
334
335/// Whether a given amount of bytes (primitives only) should be packed, or not.
336fn is_packable(byte_count: usize) -> bool {
337    byte_count < 32 && 32 % byte_count == 0
338}
339
340/// Generate `StorableType`, `Storable`, and `StorageKey` for a fixed-size array.
341fn gen_array_impl(config: &ArrayConfig) -> TokenStream {
342    let ArrayConfig {
343        elem_type,
344        array_size,
345        elem_byte_count,
346        elem_is_packable,
347    } = config;
348
349    // Calculate slot count at compile time
350    let slot_count = if *elem_is_packable {
351        // Packed: multiple elements per slot
352        (*array_size * elem_byte_count).div_ceil(32)
353    } else {
354        // Unpacked: each element uses full slots (assume 1 slot per element for primitives)
355        *array_size
356    };
357
358    let load_impl = if *elem_is_packable {
359        gen_packed_array_load(array_size, elem_byte_count)
360    } else {
361        gen_unpacked_array_load(array_size)
362    };
363
364    let store_impl = if *elem_is_packable {
365        gen_packed_array_store(array_size, elem_byte_count)
366    } else {
367        gen_unpacked_array_store()
368    };
369
370    quote! {
371        // Implement StorableType
372        impl crate::storage::StorableType for [#elem_type; #array_size] {
373            // Arrays cannot be packed, so they must take full slots
374            const LAYOUT: crate::storage::Layout = crate::storage::Layout::Slots(#slot_count);
375
376            type Handler = crate::storage::types::array::ArrayHandler<#elem_type, #array_size>;
377
378            fn handle(slot: ::alloy::primitives::U256, ctx: crate::storage::LayoutCtx, address: ::alloy::primitives::Address) -> Self::Handler {
379                debug_assert_eq!(ctx, crate::storage::LayoutCtx::FULL, "Arrays cannot be packed");
380                Self::Handler::new(slot, address)
381            }
382        }
383
384        // Implement Storable with full I/O logic
385        impl crate::storage::Storable for [#elem_type; #array_size] {
386            #[inline]
387            fn load<S: crate::storage::StorageOps>(storage: &S, slot: ::alloy::primitives::U256, ctx: crate::storage::LayoutCtx) -> crate::error::Result<Self> {
388                debug_assert_eq!(
389                    ctx, crate::storage::LayoutCtx::FULL,
390                    "Arrays can only be loaded with LayoutCtx::FULL"
391                );
392
393                use crate::storage::packing::{calc_element_slot, calc_element_offset, extract_from_word};
394                let base_slot = slot;
395                #load_impl
396            }
397
398            #[inline]
399            fn store<S: crate::storage::StorageOps>(&self, storage: &mut S, slot: ::alloy::primitives::U256, ctx: crate::storage::LayoutCtx) -> crate::error::Result<()> {
400                debug_assert_eq!(
401                    ctx, crate::storage::LayoutCtx::FULL,
402                    "Arrays can only be stored with LayoutCtx::FULL"
403                );
404
405                use crate::storage::packing::{calc_element_slot, calc_element_offset, insert_into_word};
406                let base_slot = slot;
407                #store_impl
408            }
409
410            // delete uses the default implementation from the trait
411        }
412
413    }
414}
415
416/// Generate load implementation for packed arrays
417fn gen_packed_array_load(array_size: &usize, elem_byte_count: &usize) -> TokenStream {
418    quote! {
419        let mut result = [Default::default(); #array_size];
420        for i in 0..#array_size {
421            let slot_idx = calc_element_slot(i, #elem_byte_count);
422            let offset = calc_element_offset(i, #elem_byte_count);
423            let slot_addr = base_slot + U256::from(slot_idx);
424            let slot_value = storage.load(slot_addr)?;
425            result[i] = extract_from_word(slot_value, offset, #elem_byte_count)?;
426        }
427        Ok(result)
428    }
429}
430
431/// Generate store implementation for packed arrays
432fn gen_packed_array_store(array_size: &usize, elem_byte_count: &usize) -> TokenStream {
433    quote! {
434        // Determine how many slots we need
435        let slot_count = (#array_size * #elem_byte_count).div_ceil(32);
436
437        // Build slots by packing elements
438        for slot_idx in 0..slot_count {
439            let slot_addr = base_slot + U256::from(slot_idx);
440            let mut slot_value = U256::ZERO;
441
442            // Pack all elements that belong to this slot
443            for i in 0..#array_size {
444                let elem_slot = calc_element_slot(i, #elem_byte_count);
445                if elem_slot == slot_idx {
446                    let offset = calc_element_offset(i, #elem_byte_count);
447                    slot_value = insert_into_word(slot_value, &self[i], offset, #elem_byte_count)?;
448                }
449            }
450
451            storage.store(slot_addr, slot_value)?;
452        }
453        Ok(())
454    }
455}
456
457/// Generate load implementation for unpacked arrays
458fn gen_unpacked_array_load(array_size: &usize) -> TokenStream {
459    quote! {
460        let mut result = [Default::default(); #array_size];
461        for i in 0..#array_size {
462            let elem_slot = base_slot + ::alloy::primitives::U256::from(i);
463            result[i] = crate::storage::Storable::load(storage, elem_slot, crate::storage::LayoutCtx::FULL)?;
464        }
465        Ok(result)
466    }
467}
468
469/// Generate store implementation for unpacked arrays
470fn gen_unpacked_array_store() -> TokenStream {
471    quote! {
472        for (i, elem) in self.iter().enumerate() {
473            let elem_slot = base_slot + ::alloy::primitives::U256::from(i);
474            crate::storage::Storable::store(elem, storage, elem_slot, crate::storage::LayoutCtx::FULL)?;
475        }
476        Ok(())
477    }
478}
479
480/// Generate array implementations for a specific element type
481fn gen_arrays_for_type(
482    elem_type: TokenStream,
483    elem_byte_count: usize,
484    sizes: &[usize],
485) -> Vec<TokenStream> {
486    let elem_is_packable = is_packable(elem_byte_count);
487
488    sizes
489        .iter()
490        .map(|&size| {
491            let config = ArrayConfig {
492                elem_type: elem_type.clone(),
493                array_size: size,
494                elem_byte_count,
495                elem_is_packable,
496            };
497            gen_array_impl(&config)
498        })
499        .collect()
500}
501
502/// Generate `StorableType`, `Storable`, and `StorageKey` for fixed-size arrays of primitive types.
503pub(crate) fn gen_storable_arrays() -> TokenStream {
504    let mut all_impls = Vec::new();
505    let sizes: Vec<usize> = (1..=32).collect();
506
507    // Rust unsigned integers
508    for &bit_size in RUST_INT_SIZES {
509        let type_ident = quote::format_ident!("u{}", bit_size);
510        let byte_count = bit_size / 8;
511        all_impls.extend(gen_arrays_for_type(
512            quote! { #type_ident },
513            byte_count,
514            &sizes,
515        ));
516    }
517
518    // Rust signed integers
519    for &bit_size in RUST_INT_SIZES {
520        let type_ident = quote::format_ident!("i{}", bit_size);
521        let byte_count = bit_size / 8;
522        all_impls.extend(gen_arrays_for_type(
523            quote! { #type_ident },
524            byte_count,
525            &sizes,
526        ));
527    }
528
529    // Alloy unsigned integers
530    for &bit_size in ALLOY_INT_SIZES {
531        let type_ident = quote::format_ident!("U{}", bit_size);
532        let byte_count = bit_size / 8;
533        all_impls.extend(gen_arrays_for_type(
534            quote! { ::alloy::primitives::#type_ident },
535            byte_count,
536            &sizes,
537        ));
538    }
539
540    // Alloy signed integers
541    for &bit_size in ALLOY_INT_SIZES {
542        let type_ident = quote::format_ident!("I{}", bit_size);
543        let byte_count = bit_size / 8;
544        all_impls.extend(gen_arrays_for_type(
545            quote! { ::alloy::primitives::#type_ident },
546            byte_count,
547            &sizes,
548        ));
549    }
550
551    // Address (20 bytes, not packable since 32 % 20 != 0)
552    all_impls.extend(gen_arrays_for_type(
553        quote! { ::alloy::primitives::Address },
554        20,
555        &sizes,
556    ));
557
558    // Common FixedBytes types
559    for &byte_size in &[20, 32] {
560        all_impls.extend(gen_arrays_for_type(
561            quote! { ::alloy::primitives::FixedBytes<#byte_size> },
562            byte_size,
563            &sizes,
564        ));
565    }
566
567    quote! {
568        #(#all_impls)*
569    }
570}
571
572/// Generate nested array implementations for common small cases
573pub(crate) fn gen_nested_arrays() -> TokenStream {
574    let mut all_impls = Vec::new();
575
576    // Nested u8 arrays: [[u8; INNER]; OUTER]
577    // Only generate where total slots <= 32
578    for inner in &[2usize, 4, 8, 16] {
579        let inner_slots = inner.div_ceil(32); // u8 packs, so this is ceil(inner/32)
580        let max_outer = 32 / inner_slots.max(1);
581
582        for outer in 1..=max_outer.min(32) {
583            all_impls.extend(gen_arrays_for_type(
584                quote! { [u8; #inner] },
585                inner_slots * 32, // BYTE_COUNT for [u8; inner]
586                &[outer],
587            ));
588        }
589    }
590
591    // Nested u16 arrays
592    for inner in &[2usize, 4, 8] {
593        let inner_slots = (inner * 2).div_ceil(32);
594        let max_outer = 32 / inner_slots.max(1);
595
596        for outer in 1..=max_outer.min(16) {
597            all_impls.extend(gen_arrays_for_type(
598                quote! { [u16; #inner] },
599                inner_slots * 32,
600                &[outer],
601            ));
602        }
603    }
604
605    quote! {
606        #(#all_impls)*
607    }
608}
609
610// -- STRUCT ARRAY IMPLEMENTATIONS ---------------------------------------------
611
612/// Generate array implementations for user-defined structs (multi-slot types).
613///
614/// Unlike primitive arrays, struct arrays:
615/// - Always use unpacked layout (structs span multiple slots)
616/// - Each element occupies `<T>::SLOTS` consecutive slots
617/// - Slot addressing uses multiplication: `base_slot + (i * <T>::SLOTS)`
618///
619/// # Parameters
620///
621/// - `struct_type`: The type path of the struct (e.g., `quote! { MyStruct }`)
622/// - `array_sizes`: Vector of array sizes to generate (e.g., `[1, 2, 4, 8]`)
623///
624/// # Returns
625///
626/// A `TokenStream` containing all the generated array implementations.
627pub(crate) fn gen_struct_arrays(struct_type: TokenStream, array_sizes: &[usize]) -> TokenStream {
628    let impls: Vec<_> = array_sizes
629        .iter()
630        .map(|&size| gen_struct_array_impl(&struct_type, size))
631        .collect();
632
633    quote! {
634        #(#impls)*
635    }
636}
637
638/// Generate a single array implementation for a user-defined struct.
639fn gen_struct_array_impl(struct_type: &TokenStream, array_size: usize) -> TokenStream {
640    // Generate unique module name for this array type
641    let struct_type_str = struct_type
642        .to_string()
643        .replace("::", "_")
644        .replace(['<', '>', ' ', '[', ']', ';'], "_");
645    let mod_ident = quote::format_ident!("__array_{}_{}", struct_type_str, array_size);
646
647    // Generate implementation methods
648    let load_impl = gen_struct_array_load(struct_type, array_size);
649    let store_impl = gen_struct_array_store(struct_type);
650
651    quote! {
652        // Helper module with compile-time constants
653        mod #mod_ident {
654            use super::*;
655            pub const ELEM_SLOTS: usize = <#struct_type as crate::storage::StorableType>::SLOTS;
656            pub const ARRAY_LEN: usize = #array_size;
657            pub const SLOT_COUNT: usize = ARRAY_LEN * ELEM_SLOTS;
658        }
659
660        // Implement StorableType
661        impl crate::storage::StorableType for [#struct_type; #array_size] {
662            const LAYOUT: crate::storage::Layout = crate::storage::Layout::Slots(#mod_ident::SLOT_COUNT);
663
664            type Handler = crate::storage::Slot<Self>;
665
666            fn handle(slot: ::alloy::primitives::U256, ctx: crate::storage::LayoutCtx, address: ::alloy::primitives::Address) -> Self::Handler {
667                crate::storage::Slot::new_with_ctx(slot, ctx, address)
668            }
669        }
670
671        // Implement Storable with full I/O logic
672        impl crate::storage::Storable for [#struct_type; #array_size] {
673            #[inline]
674            fn load<S: crate::storage::StorageOps>(storage: &S, slot: ::alloy::primitives::U256, ctx: crate::storage::LayoutCtx) -> crate::error::Result<Self> {
675                debug_assert_eq!(
676                    ctx, crate::storage::LayoutCtx::FULL,
677                    "Struct arrays can only be loaded with LayoutCtx::FULL"
678                );
679                let base_slot = slot;
680                #load_impl
681            }
682
683            #[inline]
684            fn store<S: crate::storage::StorageOps>(&self, storage: &mut S, slot: ::alloy::primitives::U256, ctx: crate::storage::LayoutCtx) -> crate::error::Result<()> {
685                debug_assert_eq!(
686                    ctx, crate::storage::LayoutCtx::FULL,
687                    "Struct arrays can only be stored with LayoutCtx::FULL"
688                );
689                let base_slot = slot;
690                #store_impl
691            }
692
693            // delete uses the default implementation from the trait
694        }
695
696    }
697}
698
699/// Generate load implementation for struct arrays.
700///
701/// Each element occupies `<T>::SLOTS` consecutive slots.
702fn gen_struct_array_load(struct_type: &TokenStream, array_size: usize) -> TokenStream {
703    quote! {
704        let mut result = [Default::default(); #array_size];
705        for i in 0..#array_size {
706            // Calculate slot for this element: base_slot + (i * element_slot_count)
707            let elem_slot = base_slot.checked_add(
708                ::alloy::primitives::U256::from(i).checked_mul(
709                    ::alloy::primitives::U256::from(<#struct_type as crate::storage::StorableType>::SLOTS)
710                ).ok_or(crate::error::TempoError::SlotOverflow)?
711            ).ok_or(crate::error::TempoError::SlotOverflow)?;
712
713            result[i] = <#struct_type as crate::storage::Storable>::load(storage, elem_slot, crate::storage::LayoutCtx::FULL)?;
714        }
715        Ok(result)
716    }
717}
718
719/// Generate store implementation for struct arrays.
720fn gen_struct_array_store(struct_type: &TokenStream) -> TokenStream {
721    quote! {
722        for (i, elem) in self.iter().enumerate() {
723            // Calculate slot for this element: base_slot + (i * element_slot_count)
724            let elem_slot = base_slot.checked_add(
725                ::alloy::primitives::U256::from(i).checked_mul(
726                    ::alloy::primitives::U256::from(<#struct_type as crate::storage::StorableType>::SLOTS)
727                ).ok_or(crate::error::TempoError::SlotOverflow)?
728            ).ok_or(crate::error::TempoError::SlotOverflow)?;
729
730            <#struct_type as crate::storage::Storable>::store(elem, storage, elem_slot, crate::storage::LayoutCtx::FULL)?;
731        }
732        Ok(())
733    }
734}