alloy_sol_macro_expander/expand/
struct.rs

1//! [`ItemStruct`] expansion.
2
3use super::{expand_fields, expand_from_into_tuples, expand_tokenize, ExpCtxt};
4use alloy_sol_macro_input::{mk_doc, ContainsSolAttrs};
5use ast::{Item, ItemStruct, Spanned, Type};
6use proc_macro2::TokenStream;
7use quote::quote;
8use std::num::NonZeroU16;
9use syn::Result;
10
11/// Expands an [`ItemStruct`]:
12///
13/// ```ignore (pseudo-code)
14/// pub struct #name {
15///     #(pub #field_name: #field_type,)*
16/// }
17///
18/// impl SolStruct for #name {
19///     ...
20/// }
21///
22/// // Needed to use in event parameters
23/// impl EventTopic for #name {
24///     ...
25/// }
26/// ```
27pub(super) fn expand(cx: &ExpCtxt<'_>, s: &ItemStruct) -> Result<TokenStream> {
28    let ItemStruct { name, fields, .. } = s;
29
30    let (sol_attrs, mut attrs) = s.split_attrs()?;
31
32    cx.derives(&mut attrs, fields, true);
33    let docs = sol_attrs.docs.or(cx.attrs.docs).unwrap_or(true);
34
35    let (field_types, field_names): (Vec<_>, Vec<_>) =
36        fields.iter().map(|f| (cx.expand_type(&f.ty), f.name.as_ref().unwrap())).unzip();
37
38    let eip712_encode_type_fns = expand_encode_type_fns(cx, fields, name);
39
40    let tokenize_impl = expand_tokenize(fields, cx);
41
42    let encode_data_impl = match fields.len() {
43        0 => unreachable!("struct with zero fields"),
44        1 => {
45            let name = *field_names.first().unwrap();
46            let ty = field_types.first().unwrap();
47            quote!(<#ty as alloy_sol_types::SolType>::eip712_data_word(&self.#name).0.to_vec())
48        }
49        _ => quote! {
50            [#(
51                <#field_types as alloy_sol_types::SolType>::eip712_data_word(&self.#field_names).0,
52            )*].concat()
53        },
54    };
55
56    let alloy_sol_types = &cx.crates.sol_types;
57
58    let attrs = attrs.iter();
59    let convert = expand_from_into_tuples(&name.0, fields, cx);
60    let name_s = name.as_string();
61    let fields = expand_fields(fields, cx);
62
63    let doc = docs.then(|| mk_doc(format!("```solidity\n{s}\n```")));
64    let tokens = quote! {
65        #(#attrs)*
66        #doc
67        #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)]
68        #[derive(Clone)]
69        pub struct #name {
70            #(#fields),*
71        }
72
73        #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields, clippy::style)]
74        const _: () = {
75            use #alloy_sol_types as alloy_sol_types;
76
77            #convert
78
79            #[automatically_derived]
80            impl alloy_sol_types::SolValue for #name {
81                type SolType = Self;
82            }
83
84            #[automatically_derived]
85            impl alloy_sol_types::private::SolTypeValue<Self> for #name {
86                #[inline]
87                fn stv_to_tokens(&self) -> <Self as alloy_sol_types::SolType>::Token<'_> {
88                    #tokenize_impl
89                }
90
91                #[inline]
92                fn stv_abi_encoded_size(&self) -> usize {
93                    if let Some(size) = <Self as alloy_sol_types::SolType>::ENCODED_SIZE {
94                        return size;
95                    }
96
97                    // TODO: Avoid cloning
98                    let tuple = <UnderlyingRustTuple<'_> as ::core::convert::From<Self>>::from(self.clone());
99                    <UnderlyingSolTuple<'_> as alloy_sol_types::SolType>::abi_encoded_size(&tuple)
100                }
101
102                #[inline]
103                fn stv_eip712_data_word(&self) -> alloy_sol_types::Word {
104                    <Self as alloy_sol_types::SolStruct>::eip712_hash_struct(self)
105                }
106
107                #[inline]
108                fn stv_abi_encode_packed_to(&self, out: &mut alloy_sol_types::private::Vec<u8>) {
109                    // TODO: Avoid cloning
110                    let tuple = <UnderlyingRustTuple<'_> as ::core::convert::From<Self>>::from(self.clone());
111                    <UnderlyingSolTuple<'_> as alloy_sol_types::SolType>::abi_encode_packed_to(&tuple, out)
112                }
113
114                #[inline]
115                fn stv_abi_packed_encoded_size(&self) -> usize {
116                    if let Some(size) = <Self as alloy_sol_types::SolType>::PACKED_ENCODED_SIZE {
117                        return size;
118                    }
119
120                    // TODO: Avoid cloning
121                    let tuple = <UnderlyingRustTuple<'_> as ::core::convert::From<Self>>::from(self.clone());
122                    <UnderlyingSolTuple<'_> as alloy_sol_types::SolType>::abi_packed_encoded_size(&tuple)
123                }
124            }
125
126            #[automatically_derived]
127            impl alloy_sol_types::SolType for #name {
128                type RustType = Self;
129                type Token<'a> = <UnderlyingSolTuple<'a> as alloy_sol_types::SolType>::Token<'a>;
130
131                const SOL_NAME: &'static str = <Self as alloy_sol_types::SolStruct>::NAME;
132                const ENCODED_SIZE: Option<usize> =
133                    <UnderlyingSolTuple<'_> as alloy_sol_types::SolType>::ENCODED_SIZE;
134                const PACKED_ENCODED_SIZE: Option<usize> =
135                    <UnderlyingSolTuple<'_> as alloy_sol_types::SolType>::PACKED_ENCODED_SIZE;
136
137                #[inline]
138                fn valid_token(token: &Self::Token<'_>) -> bool {
139                    <UnderlyingSolTuple<'_> as alloy_sol_types::SolType>::valid_token(token)
140                }
141
142                #[inline]
143                fn detokenize(token: Self::Token<'_>) -> Self::RustType {
144                    let tuple = <UnderlyingSolTuple<'_> as alloy_sol_types::SolType>::detokenize(token);
145                    <Self as ::core::convert::From<UnderlyingRustTuple<'_>>>::from(tuple)
146                }
147            }
148
149            #[automatically_derived]
150            impl alloy_sol_types::SolStruct for #name {
151                const NAME: &'static str = #name_s;
152
153                #eip712_encode_type_fns
154
155                #[inline]
156                fn eip712_encode_data(&self) -> alloy_sol_types::private::Vec<u8> {
157                    #encode_data_impl
158                }
159            }
160
161            #[automatically_derived]
162            impl alloy_sol_types::EventTopic for #name {
163                #[inline]
164                fn topic_preimage_length(rust: &Self::RustType) -> usize {
165                    0usize
166                    #(
167                        + <#field_types as alloy_sol_types::EventTopic>::topic_preimage_length(&rust.#field_names)
168                    )*
169                }
170
171                #[inline]
172                fn encode_topic_preimage(rust: &Self::RustType, out: &mut alloy_sol_types::private::Vec<u8>) {
173                    out.reserve(<Self as alloy_sol_types::EventTopic>::topic_preimage_length(rust));
174                    #(
175                        <#field_types as alloy_sol_types::EventTopic>::encode_topic_preimage(&rust.#field_names, out);
176                    )*
177                }
178
179                #[inline]
180                fn encode_topic(rust: &Self::RustType) -> alloy_sol_types::abi::token::WordToken {
181                    let mut out = alloy_sol_types::private::Vec::new();
182                    <Self as alloy_sol_types::EventTopic>::encode_topic_preimage(rust, &mut out);
183                    alloy_sol_types::abi::token::WordToken(
184                        alloy_sol_types::private::keccak256(out)
185                    )
186                }
187            }
188        };
189    };
190    Ok(tokens)
191}
192
193fn expand_encode_type_fns(
194    cx: &ExpCtxt<'_>,
195    fields: &ast::Parameters<syn::token::Semi>,
196    name: &ast::SolIdent,
197) -> TokenStream {
198    // account for UDVTs and enums which do not implement SolStruct
199    let mut fields = fields.clone();
200    fields.visit_types_mut(|ty| {
201        let Type::Custom(name) = ty else { return };
202        match cx.try_item(name) {
203            // keep as custom
204            Some(Item::Struct(_)) | None => {}
205            // convert to underlying
206            Some(Item::Contract(_)) => *ty = Type::Address(ty.span(), None),
207            Some(Item::Enum(_)) => *ty = Type::Uint(ty.span(), NonZeroU16::new(8)),
208            Some(Item::Udt(udt)) => *ty = udt.ty.clone(),
209            Some(item) => {
210                proc_macro_error2::abort!(item.span(), "Invalid type in struct field: {:?}", item)
211            }
212        }
213    });
214
215    let root = fields.eip712_signature(name.as_string());
216
217    let custom = fields.iter().filter(|f| f.ty.has_custom());
218    let n_custom = custom.clone().count();
219
220    let components_impl = if n_custom > 0 {
221        let bits = custom.map(|field| {
222            // need to recurse to find the inner custom type
223            let mut ty = None;
224            field.ty.visit(|field_ty| {
225                if ty.is_none() && field_ty.is_custom() {
226                    ty = Some(field_ty.clone());
227                }
228            });
229            // cannot panic as this field is guaranteed to contain a custom type
230            let ty = cx.expand_type(&ty.unwrap());
231
232            quote! {
233                components.push(<#ty as alloy_sol_types::SolStruct>::eip712_root_type());
234                components.extend(<#ty as alloy_sol_types::SolStruct>::eip712_components());
235            }
236        });
237        let capacity = proc_macro2::Literal::usize_unsuffixed(n_custom);
238        quote! {
239            let mut components = alloy_sol_types::private::Vec::with_capacity(#capacity);
240            #(#bits)*
241            components
242        }
243    } else {
244        quote! { alloy_sol_types::private::Vec::new() }
245    };
246
247    let encode_type_impl_opt = (n_custom == 0).then(|| {
248        quote! {
249            #[inline]
250            fn eip712_encode_type() -> alloy_sol_types::private::Cow<'static, str> {
251                <Self as alloy_sol_types::SolStruct>::eip712_root_type()
252            }
253        }
254    });
255
256    quote! {
257        #[inline]
258        fn eip712_root_type() -> alloy_sol_types::private::Cow<'static, str> {
259            alloy_sol_types::private::Cow::Borrowed(#root)
260        }
261
262        #[inline]
263        fn eip712_components() -> alloy_sol_types::private::Vec<alloy_sol_types::private::Cow<'static, str>> {
264            #components_impl
265        }
266
267        #encode_type_impl_opt
268    }
269}