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, 96, 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::aliases::#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::aliases::#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::aliases::#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::aliases::#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::aliases::#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
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_expr = if *elem_is_packable {
351        quote! { crate::storage::packing::calc_packed_slot_count(#array_size, #elem_byte_count) }
352    } else {
353        // Unpacked: each element uses full slots (assume 1 slot per element for primitives)
354        quote! { #array_size }
355    };
356
357    let load_impl = if *elem_is_packable {
358        gen_packed_array_load(array_size, elem_byte_count)
359    } else {
360        gen_unpacked_array_load(array_size)
361    };
362
363    let store_impl = if *elem_is_packable {
364        gen_packed_array_store(array_size, elem_byte_count)
365    } else {
366        gen_unpacked_array_store()
367    };
368
369    quote! {
370        // Implement StorableType
371        impl crate::storage::StorableType for [#elem_type; #array_size] {
372            // Arrays cannot be packed, so they must take full slots
373            const LAYOUT: crate::storage::Layout = crate::storage::Layout::Slots(#slot_count_expr);
374
375            type Handler = crate::storage::types::array::ArrayHandler<#elem_type, #array_size>;
376
377            fn handle(slot: ::alloy::primitives::U256, ctx: crate::storage::LayoutCtx, address: ::alloy::primitives::Address) -> Self::Handler {
378                debug_assert_eq!(ctx, crate::storage::LayoutCtx::FULL, "Arrays cannot be packed");
379                Self::Handler::new(slot, address)
380            }
381        }
382
383        // Implement Storable with full I/O logic
384        impl crate::storage::Storable for [#elem_type; #array_size] {
385            #[inline]
386            fn load<S: crate::storage::StorageOps>(storage: &S, slot: ::alloy::primitives::U256, ctx: crate::storage::LayoutCtx) -> crate::error::Result<Self> {
387                debug_assert_eq!(
388                    ctx, crate::storage::LayoutCtx::FULL,
389                    "Arrays can only be loaded with LayoutCtx::FULL"
390                );
391
392                use crate::storage::packing::{calc_element_slot, calc_element_offset, extract_from_word};
393                let base_slot = slot;
394                #load_impl
395            }
396
397            #[inline]
398            fn store<S: crate::storage::StorageOps>(&self, storage: &mut S, slot: ::alloy::primitives::U256, ctx: crate::storage::LayoutCtx) -> crate::error::Result<()> {
399                debug_assert_eq!(
400                    ctx, crate::storage::LayoutCtx::FULL,
401                    "Arrays can only be stored with LayoutCtx::FULL"
402                );
403
404                use crate::storage::packing::{calc_element_slot, calc_element_offset, insert_into_word};
405                let base_slot = slot;
406                #store_impl
407            }
408
409            // delete uses the default implementation from the trait
410        }
411
412    }
413}
414
415/// Generate load implementation for packed arrays
416fn gen_packed_array_load(array_size: &usize, elem_byte_count: &usize) -> TokenStream {
417    quote! {
418        let mut result = [Default::default(); #array_size];
419        for i in 0..#array_size {
420            let slot_idx = calc_element_slot(i, #elem_byte_count);
421            let offset = calc_element_offset(i, #elem_byte_count);
422            let slot_addr = base_slot + U256::from(slot_idx);
423            let slot_value = storage.load(slot_addr)?;
424            result[i] = extract_from_word(slot_value, offset, #elem_byte_count)?;
425        }
426        Ok(result)
427    }
428}
429
430/// Generate store implementation for packed arrays
431fn gen_packed_array_store(array_size: &usize, elem_byte_count: &usize) -> TokenStream {
432    quote! {
433        // Determine how many slots we need
434        let slot_count = crate::storage::packing::calc_packed_slot_count(
435            #array_size,
436            #elem_byte_count,
437        );
438
439        // Build slots by packing elements
440        for slot_idx in 0..slot_count {
441            let slot_addr = base_slot + U256::from(slot_idx);
442            let mut slot_value = U256::ZERO;
443
444            // Pack all elements that belong to this slot
445            for i in 0..#array_size {
446                let elem_slot = calc_element_slot(i, #elem_byte_count);
447                if elem_slot == slot_idx {
448                    let offset = calc_element_offset(i, #elem_byte_count);
449                    slot_value = insert_into_word(slot_value, &self[i], offset, #elem_byte_count)?;
450                }
451            }
452
453            storage.store(slot_addr, slot_value)?;
454        }
455        Ok(())
456    }
457}
458
459/// Generate load implementation for unpacked arrays
460fn gen_unpacked_array_load(array_size: &usize) -> TokenStream {
461    quote! {
462        let mut result = [Default::default(); #array_size];
463        for i in 0..#array_size {
464            let elem_slot = base_slot + ::alloy::primitives::U256::from(i);
465            result[i] = crate::storage::Storable::load(storage, elem_slot, crate::storage::LayoutCtx::FULL)?;
466        }
467        Ok(result)
468    }
469}
470
471/// Generate store implementation for unpacked arrays
472fn gen_unpacked_array_store() -> TokenStream {
473    quote! {
474        for (i, elem) in self.iter().enumerate() {
475            let elem_slot = base_slot + ::alloy::primitives::U256::from(i);
476            crate::storage::Storable::store(elem, storage, elem_slot, crate::storage::LayoutCtx::FULL)?;
477        }
478        Ok(())
479    }
480}
481
482/// Generate array implementations for a specific element type
483fn gen_arrays_for_type(
484    elem_type: TokenStream,
485    elem_byte_count: usize,
486    sizes: &[usize],
487) -> Vec<TokenStream> {
488    let elem_is_packable = is_packable(elem_byte_count);
489
490    sizes
491        .iter()
492        .map(|&size| {
493            let config = ArrayConfig {
494                elem_type: elem_type.clone(),
495                array_size: size,
496                elem_byte_count,
497                elem_is_packable,
498            };
499            gen_array_impl(&config)
500        })
501        .collect()
502}
503
504/// Generate `StorableType`, `Storable`, and `StorageKey` for fixed-size arrays of primitive types.
505pub(crate) fn gen_storable_arrays() -> TokenStream {
506    let mut all_impls = Vec::new();
507    let sizes: Vec<usize> = (1..=32).collect();
508
509    // Rust unsigned integers
510    for &bit_size in RUST_INT_SIZES {
511        let type_ident = quote::format_ident!("u{}", bit_size);
512        let byte_count = bit_size / 8;
513        all_impls.extend(gen_arrays_for_type(
514            quote! { #type_ident },
515            byte_count,
516            &sizes,
517        ));
518    }
519
520    // Rust signed integers
521    for &bit_size in RUST_INT_SIZES {
522        let type_ident = quote::format_ident!("i{}", bit_size);
523        let byte_count = bit_size / 8;
524        all_impls.extend(gen_arrays_for_type(
525            quote! { #type_ident },
526            byte_count,
527            &sizes,
528        ));
529    }
530
531    // Alloy unsigned integers
532    for &bit_size in ALLOY_INT_SIZES {
533        let type_ident = quote::format_ident!("U{}", bit_size);
534        let byte_count = bit_size / 8;
535        all_impls.extend(gen_arrays_for_type(
536            quote! { ::alloy::primitives::aliases::#type_ident },
537            byte_count,
538            &sizes,
539        ));
540    }
541
542    // Alloy signed integers
543    for &bit_size in ALLOY_INT_SIZES {
544        let type_ident = quote::format_ident!("I{}", bit_size);
545        let byte_count = bit_size / 8;
546        all_impls.extend(gen_arrays_for_type(
547            quote! { ::alloy::primitives::aliases::#type_ident },
548            byte_count,
549            &sizes,
550        ));
551    }
552
553    // Address (20 bytes, not packable since 32 % 20 != 0)
554    all_impls.extend(gen_arrays_for_type(
555        quote! { ::alloy::primitives::Address },
556        20,
557        &sizes,
558    ));
559
560    // Common FixedBytes types
561    for &byte_size in &[20, 32] {
562        all_impls.extend(gen_arrays_for_type(
563            quote! { ::alloy::primitives::FixedBytes<#byte_size> },
564            byte_size,
565            &sizes,
566        ));
567    }
568
569    quote! {
570        #(#all_impls)*
571    }
572}
573
574/// Generate nested array implementations for common small cases
575pub(crate) fn gen_nested_arrays() -> TokenStream {
576    let mut all_impls = Vec::new();
577
578    // Nested u8 arrays: [[u8; INNER]; OUTER]
579    // Only generate where total slots <= 32
580    for inner in &[2usize, 4, 8, 16] {
581        let inner_slots = inner.div_ceil(32); // u8 packs, so this is ceil(inner/32)
582        let max_outer = 32 / inner_slots.max(1);
583
584        for outer in 1..=max_outer.min(32) {
585            all_impls.extend(gen_arrays_for_type(
586                quote! { [u8; #inner] },
587                inner_slots * 32, // BYTE_COUNT for [u8; inner]
588                &[outer],
589            ));
590        }
591    }
592
593    // Nested u16 arrays
594    for inner in &[2usize, 4, 8] {
595        let inner_slots = (inner * 2).div_ceil(32);
596        let max_outer = 32 / inner_slots.max(1);
597
598        for outer in 1..=max_outer.min(16) {
599            all_impls.extend(gen_arrays_for_type(
600                quote! { [u16; #inner] },
601                inner_slots * 32,
602                &[outer],
603            ));
604        }
605    }
606
607    quote! {
608        #(#all_impls)*
609    }
610}
611
612// -- STRUCT ARRAY IMPLEMENTATIONS ---------------------------------------------
613
614/// Generate array implementations for user-defined structs (multi-slot types).
615///
616/// Unlike primitive arrays, struct arrays:
617/// - Always use unpacked layout (structs span multiple slots)
618/// - Each element occupies `<T>::SLOTS` consecutive slots
619/// - Slot addressing uses multiplication: `base_slot + (i * <T>::SLOTS)`
620///
621/// # Parameters
622///
623/// - `struct_type`: The type path of the struct (e.g., `quote! { MyStruct }`)
624/// - `array_sizes`: Vector of array sizes to generate (e.g., `[1, 2, 4, 8]`)
625///
626/// # Returns
627///
628/// A `TokenStream` containing all the generated array implementations.
629pub(crate) fn gen_struct_arrays(struct_type: TokenStream, array_sizes: &[usize]) -> TokenStream {
630    let impls: Vec<_> = array_sizes
631        .iter()
632        .map(|&size| gen_struct_array_impl(&struct_type, size))
633        .collect();
634
635    quote! {
636        #(#impls)*
637    }
638}
639
640/// Generate a single array implementation for a user-defined struct.
641fn gen_struct_array_impl(struct_type: &TokenStream, array_size: usize) -> TokenStream {
642    // Generate unique module name for this array type
643    let struct_type_str = struct_type
644        .to_string()
645        .replace("::", "_")
646        .replace(['<', '>', ' ', '[', ']', ';'], "_");
647    let mod_ident = quote::format_ident!("__array_{}_{}", struct_type_str, array_size);
648
649    // Generate implementation methods
650    let load_impl = gen_struct_array_load(struct_type, array_size);
651    let store_impl = gen_struct_array_store(struct_type);
652
653    quote! {
654        // Helper module with compile-time constants
655        mod #mod_ident {
656            use super::*;
657            pub const ELEM_SLOTS: usize = <#struct_type as crate::storage::StorableType>::SLOTS;
658            pub const ARRAY_LEN: usize = #array_size;
659            pub const SLOT_COUNT: usize = ARRAY_LEN * ELEM_SLOTS;
660        }
661
662        // Implement StorableType
663        impl crate::storage::StorableType for [#struct_type; #array_size] {
664            const LAYOUT: crate::storage::Layout = crate::storage::Layout::Slots(#mod_ident::SLOT_COUNT);
665
666            type Handler = crate::storage::Slot<Self>;
667
668            fn handle(slot: ::alloy::primitives::U256, ctx: crate::storage::LayoutCtx, address: ::alloy::primitives::Address) -> Self::Handler {
669                crate::storage::Slot::new_with_ctx(slot, ctx, address)
670            }
671        }
672
673        // Implement Storable with full I/O logic
674        impl crate::storage::Storable for [#struct_type; #array_size] {
675            #[inline]
676            fn load<S: crate::storage::StorageOps>(storage: &S, slot: ::alloy::primitives::U256, ctx: crate::storage::LayoutCtx) -> crate::error::Result<Self> {
677                debug_assert_eq!(
678                    ctx, crate::storage::LayoutCtx::FULL,
679                    "Struct arrays can only be loaded with LayoutCtx::FULL"
680                );
681                let base_slot = slot;
682                #load_impl
683            }
684
685            #[inline]
686            fn store<S: crate::storage::StorageOps>(&self, storage: &mut S, slot: ::alloy::primitives::U256, ctx: crate::storage::LayoutCtx) -> crate::error::Result<()> {
687                debug_assert_eq!(
688                    ctx, crate::storage::LayoutCtx::FULL,
689                    "Struct arrays can only be stored with LayoutCtx::FULL"
690                );
691                let base_slot = slot;
692                #store_impl
693            }
694
695            // delete uses the default implementation from the trait
696        }
697
698    }
699}
700
701/// Generate load implementation for struct arrays.
702///
703/// Each element occupies `<T>::SLOTS` consecutive slots.
704fn gen_struct_array_load(struct_type: &TokenStream, array_size: usize) -> TokenStream {
705    quote! {
706        let mut result = [Default::default(); #array_size];
707        for i in 0..#array_size {
708            // Calculate slot for this element: base_slot + (i * element_slot_count)
709            let elem_slot = base_slot.checked_add(
710                ::alloy::primitives::U256::from(i).checked_mul(
711                    ::alloy::primitives::U256::from(<#struct_type as crate::storage::StorableType>::SLOTS)
712                ).ok_or(crate::error::TempoError::SlotOverflow)?
713            ).ok_or(crate::error::TempoError::SlotOverflow)?;
714
715            result[i] = <#struct_type as crate::storage::Storable>::load(storage, elem_slot, crate::storage::LayoutCtx::FULL)?;
716        }
717        Ok(result)
718    }
719}
720
721/// Generate store implementation for struct arrays.
722fn gen_struct_array_store(struct_type: &TokenStream) -> TokenStream {
723    quote! {
724        for (i, elem) in self.iter().enumerate() {
725            // Calculate slot for this element: base_slot + (i * element_slot_count)
726            let elem_slot = base_slot.checked_add(
727                ::alloy::primitives::U256::from(i).checked_mul(
728                    ::alloy::primitives::U256::from(<#struct_type as crate::storage::StorableType>::SLOTS)
729                ).ok_or(crate::error::TempoError::SlotOverflow)?
730            ).ok_or(crate::error::TempoError::SlotOverflow)?;
731
732            <#struct_type as crate::storage::Storable>::store(elem, storage, elem_slot, crate::storage::LayoutCtx::FULL)?;
733        }
734        Ok(())
735    }
736}