precompile_utils_macro/
derive_codec.rs1use proc_macro::TokenStream;
20use proc_macro2::Span;
21use quote::{quote, quote_spanned};
22use syn::{
23 parse_macro_input, punctuated::Punctuated, spanned::Spanned, DeriveInput, Ident, LitStr, Path,
24 PathSegment, PredicateType, TraitBound, TraitBoundModifier,
25};
26
27pub fn main(input: TokenStream) -> TokenStream {
28 let DeriveInput {
29 ident,
30 mut generics,
31 data,
32 ..
33 } = parse_macro_input!(input as DeriveInput);
34
35 let syn::Data::Struct(syn::DataStruct {
36 fields: syn::Fields::Named(fields),
37 ..
38 }) = data
39 else {
40 return quote_spanned! { ident.span() =>
41 compile_error!("Codec can only be derived for structs with named fields");
42 }
43 .into();
44 };
45 let fields = fields.named;
46
47 if fields.is_empty() {
48 return quote_spanned! { ident.span() =>
49 compile_error!("Codec can only be derived for structs with at least one field");
50 }
51 .into();
52 }
53
54 if let Some(unamed_field) = fields.iter().find(|f| f.ident.is_none()) {
55 return quote_spanned! { unamed_field.ty.span() =>
56 compile_error!("Codec can only be derived for structs with named fields");
57 }
58 .into();
59 }
60
61 let fields_ty: Vec<_> = fields.iter().map(|f| &f.ty).collect();
62 let fields_ident: Vec<_> = fields
63 .iter()
64 .map(|f| f.ident.as_ref().expect("None case checked above"))
65 .collect();
66 let fields_name_lit: Vec<_> = fields_ident
67 .iter()
68 .map(|i| LitStr::new(&i.to_string(), i.span()))
69 .collect();
70
71 let evm_data_trait_path = {
72 let mut segments = Punctuated::<PathSegment, _>::new();
73 segments.push(Ident::new("precompile_utils", Span::call_site()).into());
74 segments.push(Ident::new("solidity", Span::call_site()).into());
75 segments.push(Ident::new("Codec", Span::call_site()).into());
76 Path {
77 leading_colon: Some(Default::default()),
78 segments,
79 }
80 };
81 let where_clause = generics.make_where_clause();
82
83 for ty in &fields_ty {
84 let mut bounds = Punctuated::new();
85 bounds.push(
86 TraitBound {
87 paren_token: None,
88 modifier: TraitBoundModifier::None,
89 lifetimes: None,
90 path: evm_data_trait_path.clone(),
91 }
92 .into(),
93 );
94
95 where_clause.predicates.push(
96 PredicateType {
97 lifetimes: None,
98 bounded_ty: (*ty).clone(),
99 colon_token: Default::default(),
100 bounds,
101 }
102 .into(),
103 );
104 }
105
106 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
107 quote! {
108 impl #impl_generics ::precompile_utils::solidity::codec::Codec for #ident #ty_generics
109 #where_clause {
110 fn read(
111 reader: &mut ::precompile_utils::solidity::codec::Reader
112 ) -> ::precompile_utils::solidity::revert::MayRevert<Self> {
113 use ::precompile_utils::solidity::revert::BacktraceExt as _;
114 let (#(#fields_ident,)*): (#(#fields_ty,)*) = reader
115 .read()
116 .map_in_tuple_to_field(&[#(#fields_name_lit),*])?;
117 Ok(Self {
118 #(#fields_ident,)*
119 })
120 }
121
122 fn write(writer: &mut ::precompile_utils::solidity::codec::Writer, value: Self) {
123 ::precompile_utils::solidity::codec::Codec::write(writer, (#(value.#fields_ident,)*));
124 }
125
126 fn has_static_size() -> bool {
127 <(#(#fields_ty,)*)>::has_static_size()
128 }
129
130 fn signature() -> String {
131 <(#(#fields_ty,)*)>::signature()
132 }
133 }
134 }
135 .into()
136}