Skip to main content

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)
33///   - `Some(mod_ident)` = storable struct (uses packing module, 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) = packing::get_neighbor_slot_refs(
61                field_idx,
62                all_fields,
63                const_mod,
64                |f| f.name,
65                is_contract,
66            );
67
68            // Calculate `LayoutCtx` based on context
69            let layout_ctx = if is_contract {
70                packing::gen_layout_ctx_expr(
71                    ty,
72                    matches!(field.assigned_slot, SlotAssignment::Manual(_)),
73                    quote! { #const_mod::#slot_const },
74                    quote! { #const_mod::#offset_const },
75                    prev_slot_const_ref,
76                    next_slot_const_ref,
77                )
78            } else {
79                packing::gen_layout_ctx_expr(
80                    ty,
81                    false, // storable fields are always auto-allocated
82                    quote! { #const_mod::#loc_const.offset_slots },
83                    quote! { #const_mod::#loc_const.offset_bytes },
84                    prev_slot_const_ref,
85                    next_slot_const_ref,
86                )
87            };
88
89            quote! {
90                #field_name: <#ty as crate::storage::StorableType>::handle(
91                    #slot_expr, #layout_ctx, address
92                )
93            }
94        }
95        FieldKind::Mapping { key, value } => {
96            quote! {
97                #field_name: <crate::storage::Mapping<#key, #value> as crate::storage::StorableType>::handle(
98                    #slot_expr, crate::storage::LayoutCtx::FULL, address
99                )
100            }
101        }
102    }
103}
104
105/// Generate the transformed struct with handler fields
106pub(crate) fn gen_struct(
107    name: &Ident,
108    vis: &Visibility,
109    allocated_fields: &[LayoutField<'_>],
110) -> proc_macro2::TokenStream {
111    // Generate handler field for each storage variable
112    let handler_fields = allocated_fields.iter().map(gen_handler_field_decl);
113
114    quote! {
115        #vis struct #name {
116            #(#handler_fields,)*
117            address: ::alloy::primitives::Address,
118            storage: crate::storage::StorageCtx,
119        }
120    }
121}
122
123/// Generate the constructor method
124pub(crate) fn gen_constructor(
125    name: &Ident,
126    allocated_fields: &[LayoutField<'_>],
127    address: Option<&Expr>,
128) -> proc_macro2::TokenStream {
129    // Generate handler initializations for each field using the shared helper
130    let field_inits = allocated_fields
131        .iter()
132        .enumerate()
133        .map(|(idx, field)| gen_handler_field_init(field, idx, allocated_fields, None));
134
135    // Generate `pub fn new()` when address is provided
136    let new_fn = address.map(|addr| {
137        quote! {
138            /// Creates an instance of the precompile.
139            ///
140            /// Caution: This does not initialize the account, see [`Self::initialize`].
141            pub fn new() -> Self {
142                Self::__new(#addr)
143            }
144        }
145    });
146
147    quote! {
148        impl #name {
149            #new_fn
150
151            #[inline(always)]
152            fn __new(address: ::alloy::primitives::Address) -> Self {
153                // Run collision detection checks in debug builds
154                #[cfg(debug_assertions)]
155                {
156                    slots::__check_all_collisions();
157                }
158
159                Self {
160                    #(#field_inits,)*
161                    address,
162                    storage: crate::storage::StorageCtx::default(),
163                }
164            }
165
166            #[inline(always)]
167            fn __initialize(&mut self) -> crate::error::Result<()> {
168                let bytecode = ::revm::state::Bytecode::new_legacy(::alloy::primitives::Bytes::from_static(&[0xef]));
169                self.storage.set_code(self.address, bytecode)?;
170
171                Ok(())
172            }
173
174            #[inline(always)]
175            fn emit_event(&mut self, event: impl ::alloy::primitives::IntoLogData) -> crate::error::Result<()> {
176                self.storage.emit_event(self.address, event.into_log_data())
177            }
178
179            #[cfg(any(test, feature = "test-utils"))]
180            pub fn emitted_events(&self) -> &Vec<::alloy::primitives::LogData> {
181                self.storage.get_events(self.address)
182            }
183
184            #[cfg(any(test, feature = "test-utils"))]
185            pub fn clear_emitted_events(&mut self) {
186                self.storage.clear_events(self.address);
187            }
188
189            #[cfg(any(test, feature = "test-utils"))]
190            pub fn assert_emitted_events(&self, expected: Vec<impl ::alloy::primitives::IntoLogData>) {
191                let emitted = self.storage.get_events(self.address);
192                assert_eq!(emitted.len(), expected.len());
193
194                for (i, event) in expected.into_iter().enumerate() {
195                    assert_eq!(emitted[i], event.into_log_data());
196                }
197            }
198        }
199    }
200}
201
202/// Generate the `trait ContractStorage` implementation
203pub(crate) fn gen_contract_storage_impl(name: &Ident) -> proc_macro2::TokenStream {
204    quote! {
205        impl crate::storage::ContractStorage for #name {
206            #[inline(always)]
207            fn address(&self) -> ::alloy::primitives::Address {
208                self.address
209            }
210
211            #[inline(always)]
212            fn storage(&self) -> &crate::storage::StorageCtx {
213                &self.storage
214            }
215
216            #[inline(always)]
217            fn storage_mut(&mut self) -> &mut crate::storage::StorageCtx {
218                &mut self.storage
219            }
220        }
221    }
222}
223
224/// Generate the `slots` module with constants and collision checks
225///
226/// Returns the slots module containing only constants and collision detection functions
227pub(crate) fn gen_slots_module(allocated_fields: &[LayoutField<'_>]) -> proc_macro2::TokenStream {
228    // Generate constants and collision check functions
229    let constants = packing::gen_constants_from_ir(allocated_fields, false);
230    let collision_checks = gen_collision_checks(allocated_fields);
231
232    quote! {
233        pub mod slots {
234            use super::*;
235
236            #constants
237            #collision_checks
238        }
239    }
240}
241
242/// Generate collision check functions for all fields
243fn gen_collision_checks(allocated_fields: &[LayoutField<'_>]) -> proc_macro2::TokenStream {
244    let mut generated = proc_macro2::TokenStream::new();
245    let mut check_fn_calls = Vec::new();
246
247    // Generate collision detection check functions for all fields
248    for (idx, allocated) in allocated_fields.iter().enumerate() {
249        let (check_fn_name, check_fn) =
250            packing::gen_collision_check_fn(idx, allocated, allocated_fields);
251        generated.extend(check_fn);
252        check_fn_calls.push(check_fn_name);
253    }
254
255    // Generate a module initializer that calls all check functions
256    // Always generate the function, even if empty, so the constructor can call it
257    generated.extend(quote! {
258        #[cfg(debug_assertions)]
259        #[inline(always)]
260        pub(super) fn __check_all_collisions() {
261            #(#check_fn_calls();)*
262        }
263    });
264
265    generated
266}
267
268/// Generate a `Default` implementation that calls `Self::new()`.
269///
270/// This is used when `#[contract(Default)]` is specified.
271pub(crate) fn gen_default_impl(name: &Ident) -> proc_macro2::TokenStream {
272    quote! {
273        impl ::core::default::Default for #name {
274            fn default() -> Self {
275                Self::new()
276            }
277        }
278    }
279}