tempo_precompiles_macros/
layout.rs

1use crate::{
2    FieldKind,
3    packing::{self, LayoutField, PackingConstants, SlotAssignment},
4};
5use quote::{format_ident, quote};
6use syn::{Expr, Ident, Visibility};
7
8/// Generates a public handler field declaration for a storage field
9pub(crate) fn gen_handler_field_decl(field: &LayoutField<'_>) -> proc_macro2::TokenStream {
10    let field_name = field.name;
11    let handler_type = match &field.kind {
12        FieldKind::Direct(ty) => {
13            quote! { <#ty as crate::storage::StorableType>::Handler }
14        }
15        FieldKind::Mapping { key, value } => {
16            quote! { <crate::storage::Mapping<#key, #value> as crate::storage::StorableType>::Handler }
17        }
18    };
19
20    quote! {
21        pub #field_name: #handler_type
22    }
23}
24
25/// Generates handler field initialization expression
26///
27/// # Parameters
28/// - `field`: the field to initialize
29/// - `field_idx`: the field's index in the allocated fields array
30/// - `all_fields`: all allocated fields (for neighbor slot detection)
31/// - `packing_mod`: optional packing module identifier
32///   - `None` = contract storage (uses `slots` module, inefficient layout)
33///   - `Some(mod_ident)` = storable struct (uses packing module, efficient layout, offsets from `base_slot`)
34pub(crate) fn gen_handler_field_init(
35    field: &LayoutField<'_>,
36    field_idx: usize,
37    all_fields: &[LayoutField<'_>],
38    packing_mod: Option<&Ident>,
39) -> proc_macro2::TokenStream {
40    let field_name = field.name;
41    let consts = PackingConstants::new(field_name);
42    let (loc_const, (slot_const, offset_const)) = (consts.location(), consts.into_tuple());
43
44    let is_contract = packing_mod.is_none();
45
46    // Create slots_module identifier based on context
47    let slots_mod = format_ident!("slots");
48    let const_mod = packing_mod.unwrap_or(&slots_mod);
49
50    // Calculate `Slot` based on context
51    let slot_expr = if is_contract {
52        quote! { #const_mod::#slot_const }
53    } else {
54        quote! { base_slot.saturating_add(::alloy::primitives::U256::from_limbs([#const_mod::#loc_const.offset_slots as u64, 0, 0, 0])) }
55    };
56
57    match &field.kind {
58        FieldKind::Direct(ty) => {
59            // Calculate neighbor slot references for packing detection
60            let (prev_slot_const_ref, next_slot_const_ref) =
61                packing::get_neighbor_slot_refs(field_idx, all_fields, const_mod, |f| f.name);
62
63            // Calculate `LayoutCtx` based on context
64            let layout_ctx = if is_contract {
65                // NOTE(rusowsky): we use the inefficient version for backwards compatibility.
66
67                // TODO(rusowsky): fully embrace `fn gen_layout_ctx_expr` to reduce gas usage.
68                // Note that this requires a hardfork and must be properly coordinated.
69                packing::gen_layout_ctx_expr_inefficient(
70                    ty,
71                    matches!(field.assigned_slot, SlotAssignment::Manual(_)),
72                    quote! { #const_mod::#slot_const },
73                    quote! { #const_mod::#offset_const },
74                    prev_slot_const_ref,
75                    next_slot_const_ref,
76                )
77            } else {
78                packing::gen_layout_ctx_expr(
79                    ty,
80                    false, // storable fields are always auto-allocated
81                    quote! { #const_mod::#loc_const.offset_slots },
82                    quote! { #const_mod::#loc_const.offset_bytes },
83                    prev_slot_const_ref,
84                    next_slot_const_ref,
85                )
86            };
87
88            quote! {
89                #field_name: <#ty as crate::storage::StorableType>::handle(
90                    #slot_expr, #layout_ctx, address
91                )
92            }
93        }
94        FieldKind::Mapping { key, value } => {
95            quote! {
96                #field_name: <crate::storage::Mapping<#key, #value> as crate::storage::StorableType>::handle(
97                    #slot_expr, crate::storage::LayoutCtx::FULL, address
98                )
99            }
100        }
101    }
102}
103
104/// Generate the transformed struct with handler fields
105pub(crate) fn gen_struct(
106    name: &Ident,
107    vis: &Visibility,
108    allocated_fields: &[LayoutField<'_>],
109) -> proc_macro2::TokenStream {
110    // Generate handler field for each storage variable
111    let handler_fields = allocated_fields.iter().map(gen_handler_field_decl);
112
113    quote! {
114        #vis struct #name {
115            #(#handler_fields,)*
116            address: ::alloy::primitives::Address,
117            storage: crate::storage::StorageCtx,
118        }
119    }
120}
121
122/// Generate the constructor method
123pub(crate) fn gen_constructor(
124    name: &Ident,
125    allocated_fields: &[LayoutField<'_>],
126    address: Option<&Expr>,
127) -> proc_macro2::TokenStream {
128    // Generate handler initializations for each field using the shared helper
129    let field_inits = allocated_fields
130        .iter()
131        .enumerate()
132        .map(|(idx, field)| gen_handler_field_init(field, idx, allocated_fields, None));
133
134    // Generate `pub fn new()` when address is provided
135    let new_fn = address.map(|addr| {
136        quote! {
137            /// Creates an instance of the precompile.
138            ///
139            /// Caution: This does not initialize the account, see [`Self::initialize`].
140            pub fn new() -> Self {
141                Self::__new(#addr)
142            }
143        }
144    });
145
146    quote! {
147        impl #name {
148            #new_fn
149
150            #[inline(always)]
151            fn __new(address: ::alloy::primitives::Address) -> Self {
152                // Run collision detection checks in debug builds
153                #[cfg(debug_assertions)]
154                {
155                    slots::__check_all_collisions();
156                }
157
158                Self {
159                    #(#field_inits,)*
160                    address,
161                    storage: crate::storage::StorageCtx::default(),
162                }
163            }
164
165            #[inline(always)]
166            fn __initialize(&mut self) -> crate::error::Result<()> {
167                let bytecode = ::revm::state::Bytecode::new_legacy(::alloy::primitives::Bytes::from_static(&[0xef]));
168                self.storage.set_code(self.address, bytecode)?;
169
170                Ok(())
171            }
172
173            #[inline(always)]
174            fn emit_event(&mut self, event: impl ::alloy::primitives::IntoLogData) -> crate::error::Result<()> {
175                self.storage.emit_event(self.address, event.into_log_data())
176            }
177
178            #[cfg(any(test, feature = "test-utils"))]
179            fn emitted_events(&self) -> &Vec<::alloy::primitives::LogData> {
180                self.storage.get_events(self.address)
181            }
182
183            #[cfg(any(test, feature = "test-utils"))]
184            fn assert_emitted_events(&self, expected: Vec<impl ::alloy::primitives::IntoLogData>) {
185                let emitted = self.storage.get_events(self.address);
186                assert_eq!(emitted.len(), expected.len());
187
188                for (i, event) in expected.into_iter().enumerate() {
189                    assert_eq!(emitted[i], event.into_log_data());
190                }
191            }
192        }
193    }
194}
195
196/// Generate the `trait ContractStorage` implementation
197pub(crate) fn gen_contract_storage_impl(name: &Ident) -> proc_macro2::TokenStream {
198    quote! {
199        impl crate::storage::ContractStorage for #name {
200            #[inline(always)]
201            fn address(&self) -> ::alloy::primitives::Address {
202                self.address
203            }
204
205            #[inline(always)]
206            fn storage(&mut self) -> &mut crate::storage::StorageCtx {
207                &mut self.storage
208            }
209        }
210    }
211}
212
213/// Generate the `slots` module with constants and collision checks
214///
215/// Returns the slots module containing only constants and collision detection functions
216pub(crate) fn gen_slots_module(allocated_fields: &[LayoutField<'_>]) -> proc_macro2::TokenStream {
217    // Generate constants and collision check functions
218    let constants = packing::gen_constants_from_ir(allocated_fields, false);
219    let collision_checks = gen_collision_checks(allocated_fields);
220
221    quote! {
222        pub mod slots {
223            use super::*;
224
225            #constants
226            #collision_checks
227        }
228    }
229}
230
231/// Generate collision check functions for all fields
232fn gen_collision_checks(allocated_fields: &[LayoutField<'_>]) -> proc_macro2::TokenStream {
233    let mut generated = proc_macro2::TokenStream::new();
234    let mut check_fn_calls = Vec::new();
235
236    // Generate collision detection check functions
237    for (idx, allocated) in allocated_fields.iter().enumerate() {
238        if let Some((check_fn_name, check_fn)) =
239            packing::gen_collision_check_fn(idx, allocated, allocated_fields)
240        {
241            generated.extend(check_fn);
242            check_fn_calls.push(check_fn_name);
243        }
244    }
245
246    // Generate a module initializer that calls all check functions
247    // Always generate the function, even if empty, so the constructor can call it
248    generated.extend(quote! {
249        #[cfg(debug_assertions)]
250        #[inline(always)]
251        pub(super) fn __check_all_collisions() {
252            #(#check_fn_calls();)*
253        }
254    });
255
256    generated
257}
258
259/// Generate a `Default` implementation that calls `Self::new()`.
260///
261/// This is used when `#[contract(Default)]` is specified.
262pub(crate) fn gen_default_impl(name: &Ident) -> proc_macro2::TokenStream {
263    quote! {
264        impl ::core::default::Default for #name {
265            fn default() -> Self {
266                Self::new()
267            }
268        }
269    }
270}