tempo_precompiles_macros/
storable.rs1use proc_macro2::TokenStream;
4use quote::{format_ident, quote};
5use syn::{Data, DeriveInput, Fields, Ident, Type};
6
7use crate::{
8 FieldInfo,
9 layout::{gen_handler_field_decl, gen_handler_field_init},
10 packing::{self, LayoutField, PackingConstants},
11 storable_primitives::gen_struct_arrays,
12 utils::{extract_mapping_types, extract_storable_array_sizes, to_snake_case},
13};
14
15pub(crate) fn derive_impl(input: DeriveInput) -> syn::Result<TokenStream> {
21 let strukt = &input.ident;
23 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
24
25 let fields = match &input.data {
27 Data::Struct(data_struct) => match &data_struct.fields {
28 Fields::Named(fields_named) => &fields_named.named,
29 _ => {
30 return Err(syn::Error::new_spanned(
31 &input.ident,
32 "`Storable` can only be derived for structs with named fields",
33 ));
34 }
35 },
36 _ => {
37 return Err(syn::Error::new_spanned(
38 &input.ident,
39 "`Storable` can only be derived for structs",
40 ));
41 }
42 };
43
44 if fields.is_empty() {
45 return Err(syn::Error::new_spanned(
46 &input.ident,
47 "`Storable` cannot be derived for empty structs",
48 ));
49 }
50
51 let field_infos: Vec<_> = fields
53 .iter()
54 .map(|f| FieldInfo {
55 name: f.ident.as_ref().unwrap().clone(),
56 ty: f.ty.clone(),
57 slot: None,
58 base_slot: None,
59 })
60 .collect();
61
62 let layout_fields = packing::allocate_slots(&field_infos)?;
64
65 let mod_ident = format_ident!("__packing_{}", to_snake_case(&strukt.to_string()));
67 let packing_module = gen_packing_module_from_ir(&layout_fields, &mod_ident);
68
69 let len = fields.len();
71 let (direct_fields, direct_names, mapping_names) = field_infos.iter().fold(
72 (Vec::with_capacity(len), Vec::with_capacity(len), Vec::new()),
73 |mut out, field_info| {
74 if extract_mapping_types(&field_info.ty).is_none() {
75 out.0.push((&field_info.name, &field_info.ty));
77 out.1.push(&field_info.name);
78 } else {
79 out.2.push(&field_info.name);
81 }
82 out
83 },
84 );
85
86 let direct_tys: Vec<_> = direct_fields.iter().map(|(_, ty)| *ty).collect();
88
89 let load_impl = gen_load_impl(&direct_fields, &mod_ident);
91 let store_impl = gen_store_impl(&direct_fields, &mod_ident);
92 let delete_impl = gen_delete_impl(&direct_fields, &mod_ident);
93
94 let handler_struct = gen_handler_struct(strukt, &layout_fields, &mod_ident);
96 let handler_name = format_ident!("{}Handler", strukt);
97
98 let expanded = quote! {
99 #packing_module
100 #handler_struct
101
102 impl #impl_generics crate::storage::StorableType for #strukt #ty_generics #where_clause {
104 const LAYOUT: crate::storage::Layout = crate::storage::Layout::Slots(#mod_ident::SLOT_COUNT);
106
107 const IS_DYNAMIC: bool = #(
109 <#direct_tys as crate::storage::StorableType>::IS_DYNAMIC
110 )||*;
111
112 type Handler = #handler_name;
113
114 fn handle(slot: ::alloy::primitives::U256, _ctx: crate::storage::LayoutCtx, address: ::alloy::primitives::Address) -> Self::Handler {
115 #handler_name::new(slot, address)
116 }
117 }
118
119 impl #impl_generics crate::storage::Storable for #strukt #ty_generics #where_clause {
121 fn load<S: crate::storage::StorageOps>(
122 storage: &S,
123 base_slot: ::alloy::primitives::U256,
124 ctx: crate::storage::LayoutCtx
125 ) -> crate::error::Result<Self> {
126 use crate::storage::Storable;
127 debug_assert_eq!(ctx, crate::storage::LayoutCtx::FULL, "Struct types can only be loaded with LayoutCtx::FULL");
128
129 #load_impl
130
131 Ok(Self {
132 #(#direct_names),*,
133 #(#mapping_names: Default::default()),*
134 })
135 }
136
137 fn store<S: crate::storage::StorageOps>(
138 &self,
139 storage: &mut S,
140 base_slot: ::alloy::primitives::U256,
141 ctx: crate::storage::LayoutCtx
142 ) -> crate::error::Result<()> {
143 use crate::storage::Storable;
144 debug_assert_eq!(ctx, crate::storage::LayoutCtx::FULL, "Struct types can only be stored with LayoutCtx::FULL");
145
146 #store_impl
147
148 Ok(())
149 }
150
151 fn delete<S: crate::storage::StorageOps>(
152 storage: &mut S,
153 base_slot: ::alloy::primitives::U256,
154 ctx: crate::storage::LayoutCtx
155 ) -> crate::error::Result<()> {
156 use crate::storage::Storable;
157 debug_assert_eq!(ctx, crate::storage::LayoutCtx::FULL, "Struct types can only be deleted with LayoutCtx::FULL");
158
159 #delete_impl
160
161 Ok(())
162 }
163 }
164 };
165
166 let array_impls = if let Some(sizes) = extract_storable_array_sizes(&input.attrs)? {
168 let struct_type = quote! { #strukt #ty_generics };
170 gen_struct_arrays(struct_type, &sizes)
171 } else {
172 quote! {}
173 };
174
175 let combined = quote! {
177 #expanded
178 #array_impls
179 };
180
181 Ok(combined)
182}
183
184fn gen_packing_module_from_ir(fields: &[LayoutField<'_>], mod_ident: &Ident) -> TokenStream {
186 let last_field = &fields[fields.len() - 1];
188 let last_slot_const = PackingConstants::new(last_field.name).slot();
189 let packing_constants = packing::gen_constants_from_ir(fields, true);
190 let last_type = &last_field.ty;
191
192 quote! {
193 pub mod #mod_ident {
194 use super::*;
195
196 #packing_constants
197 pub const SLOT_COUNT: usize = (#last_slot_const.saturating_add(
198 ::alloy::primitives::U256::from_limbs([<#last_type as crate::storage::StorableType>::SLOTS as u64, 0, 0, 0])
199 )).as_limbs()[0] as usize;
200 }
201 }
202}
203
204fn gen_handler_struct(
208 struct_name: &Ident,
209 fields: &[LayoutField<'_>],
210 mod_ident: &Ident,
211) -> TokenStream {
212 let handler_name = format_ident!("{}Handler", struct_name);
213
214 let handler_fields = fields.iter().map(gen_handler_field_decl);
216
217 let field_inits = fields
219 .iter()
220 .enumerate()
221 .map(|(idx, field)| gen_handler_field_init(field, idx, fields, Some(mod_ident)));
222
223 quote! {
224 #[derive(Debug, Clone)]
228 pub struct #handler_name {
229 address: ::alloy::primitives::Address,
230 base_slot: ::alloy::primitives::U256,
231 #(#handler_fields,)*
232 }
233
234 impl #handler_name {
235 #[inline]
237 pub fn new(base_slot: ::alloy::primitives::U256, address: ::alloy::primitives::Address) -> Self {
238 Self {
239 base_slot,
240 #(#field_inits,)*
241 address,
242 }
243 }
244
245 #[inline]
250 pub fn base_slot(&self) -> ::alloy::primitives::U256 {
251 self.base_slot
252 }
253
254 #[inline]
256 fn as_slot(&self) -> crate::storage::Slot<#struct_name> {
257 crate::storage::Slot::<#struct_name>::new(
258 self.base_slot,
259 self.address
260 )
261 }
262 }
263
264 impl crate::storage::Handler<#struct_name> for #handler_name {
265 #[inline]
266 fn read(&self) -> crate::error::Result<#struct_name> {
267 self.as_slot().read()
268 }
269
270 #[inline]
271 fn write(&mut self, value: #struct_name) -> crate::error::Result<()> {
272 self.as_slot().write(value)
273 }
274
275 #[inline]
276 fn delete(&mut self) -> crate::error::Result<()> {
277 self.as_slot().delete()
278 }
279
280 #[inline]
282 fn t_read(&self) -> crate::error::Result<#struct_name> {
283 self.as_slot().t_read()
284 }
285
286 #[inline]
288 fn t_write(&mut self, value: #struct_name) -> crate::error::Result<()> {
289 self.as_slot().t_write(value)
290 }
291
292 #[inline]
294 fn t_delete(&mut self) -> crate::error::Result<()> {
295 self.as_slot().t_delete()
296 }
297 }
298 }
299}
300
301fn gen_load_impl(fields: &[(&Ident, &Type)], packing: &Ident) -> TokenStream {
306 if fields.is_empty() {
307 return quote! {};
308 }
309
310 let field_loads = fields.iter().enumerate().map(|(idx, (name, ty))| {
311 let loc_const = PackingConstants::new(name).location();
312
313 let (prev_slot_ref, _) =
314 packing::get_neighbor_slot_refs(idx, fields, packing, |(name, _)| name, false);
315
316 let slot_addr = quote! { base_slot + ::alloy::primitives::U256::from(#packing::#loc_const.offset_slots) };
317 let packed_ctx = quote! { crate::storage::LayoutCtx::packed(#packing::#loc_const.offset_bytes) };
318
319 if let Some(prev_slot_ref) = prev_slot_ref {
320 quote! {
321 let #name = {
322 let curr_offset = #packing::#loc_const.offset_slots;
323 let prev_offset = #prev_slot_ref;
324
325 if <#ty as crate::storage::StorableType>::IS_PACKABLE && curr_offset == prev_offset {
326 let packed = crate::storage::packing::PackedSlot(cached_slot);
328 <#ty as crate::storage::Storable>::load(&packed, ::alloy::primitives::U256::ZERO, #packed_ctx)?
329 } else if <#ty as crate::storage::StorableType>::IS_PACKABLE {
330 cached_slot = storage.load(#slot_addr)?;
332 let packed = crate::storage::packing::PackedSlot(cached_slot);
333 <#ty as crate::storage::Storable>::load(&packed, ::alloy::primitives::U256::ZERO, #packed_ctx)?
334 } else {
335 <#ty as crate::storage::Storable>::load(storage, #slot_addr, crate::storage::LayoutCtx::FULL)?
337 }
338 };
339 }
340 } else {
341 quote! {
343 let #name = if <#ty as crate::storage::StorableType>::IS_PACKABLE {
344 cached_slot = storage.load(#slot_addr)?;
345 let packed = crate::storage::packing::PackedSlot(cached_slot);
346 <#ty as crate::storage::Storable>::load(&packed, ::alloy::primitives::U256::ZERO, #packed_ctx)?
347 } else {
348 <#ty as crate::storage::Storable>::load(storage, #slot_addr, crate::storage::LayoutCtx::FULL)?
349 };
350 }
351 }
352 });
353
354 quote! {
355 let mut cached_slot = ::alloy::primitives::U256::ZERO;
356 #(#field_loads)*
357 }
358}
359
360fn gen_store_impl(fields: &[(&Ident, &Type)], packing: &Ident) -> TokenStream {
365 if fields.is_empty() {
366 return quote! {};
367 }
368
369 let field_stores = fields.iter().enumerate().map(|(idx, (name, ty))| {
370 let loc_const = PackingConstants::new(name).location();
371 let next_ty = fields.get(idx + 1).map(|(_, ty)| *ty);
372
373 let (prev_slot_ref, next_slot_ref) =
374 packing::get_neighbor_slot_refs(idx, fields, packing, |(name, _)| name, false);
375
376 let slot_addr = quote! { base_slot + ::alloy::primitives::U256::from(#packing::#loc_const.offset_slots) };
377 let packed_ctx = quote! { crate::storage::LayoutCtx::packed(#packing::#loc_const.offset_bytes) };
378
379 let should_store = match (&next_slot_ref, next_ty) {
381 (Some(next_slot), Some(next_ty)) => {
382 quote! {
384 #packing::#loc_const.offset_slots != #next_slot
385 || !<#next_ty as crate::storage::StorableType>::IS_PACKABLE
386 }
387 }
388 _ => quote! { true }, };
390
391 if let Some(prev_slot_ref) = prev_slot_ref {
392 quote! {{
393 let curr_offset = #packing::#loc_const.offset_slots;
394 let prev_offset = #prev_slot_ref;
395
396 if <#ty as crate::storage::StorableType>::IS_PACKABLE && curr_offset == prev_offset {
397 let mut packed = crate::storage::packing::PackedSlot(pending_val);
399 <#ty as crate::storage::Storable>::store(&self.#name, &mut packed, ::alloy::primitives::U256::ZERO, #packed_ctx)?;
400 pending_val = packed.0;
401 } else if <#ty as crate::storage::StorableType>::IS_PACKABLE {
402 if let Some(offset) = pending_offset {
404 storage.store(base_slot + ::alloy::primitives::U256::from(offset), pending_val)?;
405 }
406 pending_val = storage.load(#slot_addr)?;
407 pending_offset = Some(curr_offset);
408 let mut packed = crate::storage::packing::PackedSlot(pending_val);
409 <#ty as crate::storage::Storable>::store(&self.#name, &mut packed, ::alloy::primitives::U256::ZERO, #packed_ctx)?;
410 pending_val = packed.0;
411 } else {
412 if let Some(offset) = pending_offset {
414 storage.store(base_slot + ::alloy::primitives::U256::from(offset), pending_val)?;
415 pending_offset = None;
416 }
417 <#ty as crate::storage::Storable>::store(&self.#name, storage, #slot_addr, crate::storage::LayoutCtx::FULL)?;
418 }
419
420 if let Some(offset) = pending_offset && (#should_store) {
422 storage.store(base_slot + ::alloy::primitives::U256::from(offset), pending_val)?;
423 pending_offset = None;
424 }
425 }}
426 } else {
427 quote! {{
429 if <#ty as crate::storage::StorableType>::IS_PACKABLE {
430 pending_val = storage.load(#slot_addr)?;
431 pending_offset = Some(#packing::#loc_const.offset_slots);
432 let mut packed = crate::storage::packing::PackedSlot(pending_val);
433 <#ty as crate::storage::Storable>::store(&self.#name, &mut packed, ::alloy::primitives::U256::ZERO, #packed_ctx)?;
434 pending_val = packed.0;
435
436 if #should_store {
438 storage.store(#slot_addr, pending_val)?;
439 pending_offset = None;
440 }
441 } else {
442 <#ty as crate::storage::Storable>::store(&self.#name, storage, #slot_addr, crate::storage::LayoutCtx::FULL)?;
443 }
444 }}
445 }
446 });
447
448 quote! {
449 let mut pending_val = ::alloy::primitives::U256::ZERO;
450 let mut pending_offset: Option<usize> = None;
451 #(#field_stores)*
452 }
453}
454
455fn gen_delete_impl(fields: &[(&Ident, &Type)], packing: &Ident) -> TokenStream {
457 let dynamic_deletes = fields.iter().map(|(name, ty)| {
459 let loc_const = PackingConstants::new(name).location();
460 quote! {
461 if <#ty as crate::storage::StorableType>::IS_DYNAMIC {
462 <#ty as crate::storage::Storable>::delete(
463 storage,
464 base_slot + ::alloy::primitives::U256::from(#packing::#loc_const.offset_slots),
465 crate::storage::LayoutCtx::FULL
466 )?;
467 }
468 }
469 });
470
471 let is_static_slot = fields.iter().map(|(name, ty)| {
473 let loc_const = PackingConstants::new(name).location();
474 quote! {
475 ((#packing::#loc_const.offset_slots..#packing::#loc_const.offset_slots + <#ty as crate::storage::StorableType>::SLOTS)
476 .contains(&slot_offset) &&
477 !<#ty as crate::storage::StorableType>::IS_DYNAMIC)
478 }
479 });
480
481 quote! {
482 #(#dynamic_deletes)*
483
484 for slot_offset in 0..#packing::SLOT_COUNT {
485 if #(#is_static_slot)||* {
487 storage.store(
488 base_slot + ::alloy::primitives::U256::from(slot_offset),
489 ::alloy::primitives::U256::ZERO
490 )?;
491 }
492 }
493 }
494}