Skip to main content

alloy_sol_macro_expander/expand/
contract.rs

1//! [`ItemContract`] expansion.
2
3use super::{ExpCtxt, anon_name};
4use crate::utils::ExprArray;
5use alloy_sol_macro_input::{ContainsSolAttrs, docs_str, mk_doc};
6use ast::{Item, ItemContract, ItemError, ItemEvent, ItemFunction, SolIdent, Spanned};
7use heck::ToSnakeCase;
8use proc_macro2::{Ident, Span, TokenStream};
9use quote::{format_ident, quote};
10use syn::{Attribute, Result, parse_quote};
11
12/// Expands an [`ItemContract`]:
13///
14/// ```ignore (pseudo-code)
15/// pub mod #name {
16///     #(#items)*
17///
18///     pub enum #{name}Calls {
19///         ...
20///    }
21///
22///     pub enum #{name}Errors {
23///         ...
24///    }
25///
26///     pub enum #{name}Events {
27///         ...
28///    }
29/// }
30/// ```
31pub(super) fn expand(cx: &mut ExpCtxt<'_>, contract: &ItemContract) -> Result<TokenStream> {
32    let ItemContract { name, body, .. } = contract;
33
34    let (sol_attrs, attrs) = contract.split_attrs()?;
35
36    let extra_methods = sol_attrs.extra_methods.or(cx.attrs.extra_methods).unwrap_or(false);
37    let rpc = sol_attrs.rpc.or(cx.attrs.rpc).unwrap_or(false);
38    let abi = sol_attrs.abi.or(cx.attrs.abi).unwrap_or(false);
39    let docs = sol_attrs.docs.or(cx.attrs.docs).unwrap_or(true);
40
41    let bytecode = sol_attrs.bytecode.as_ref().map(|lit| {
42        let name = Ident::new("BYTECODE", lit.span());
43        let hex = lit.value();
44        let bytes = hex::decode(&hex).unwrap();
45        let lit_bytes = proc_macro2::Literal::byte_string(&bytes).with_span(lit.span());
46        quote! {
47            /// The creation / init bytecode of the contract.
48            ///
49            /// ```text
50            #[doc = #hex]
51            /// ```
52            #[rustfmt::skip]
53            #[allow(clippy::all)]
54            pub static #name: alloy_sol_types::private::Bytes =
55                alloy_sol_types::private::Bytes::from_static(#lit_bytes);
56        }
57    });
58    let deployed_bytecode = sol_attrs.deployed_bytecode.as_ref().map(|lit| {
59        let name = Ident::new("DEPLOYED_BYTECODE", lit.span());
60        let hex = lit.value();
61        let bytes = hex::decode(&hex).unwrap();
62        let lit_bytes = proc_macro2::Literal::byte_string(&bytes).with_span(lit.span());
63        quote! {
64            /// The runtime bytecode of the contract, as deployed on the network.
65            ///
66            /// ```text
67            #[doc = #hex]
68            /// ```
69            #[rustfmt::skip]
70            #[allow(clippy::all)]
71            pub static #name: alloy_sol_types::private::Bytes =
72                alloy_sol_types::private::Bytes::from_static(#lit_bytes);
73        }
74    });
75
76    let mut constructor = None;
77    let mut fallback = None;
78    let mut receive = None;
79    let mut functions = Vec::with_capacity(contract.body.len());
80    let mut errors = Vec::with_capacity(contract.body.len());
81    let mut events = Vec::with_capacity(contract.body.len());
82
83    let (mut mod_attrs, item_attrs) =
84        attrs.into_iter().partition::<Vec<_>, _>(|a| a.path().is_ident("doc"));
85    mod_attrs.extend(item_attrs.iter().filter(|a| !a.path().is_ident("derive")).cloned());
86
87    // Expand inner items.
88    let mut item_tokens = TokenStream::new();
89    let prev_cx_attrs = cx.attrs.clone();
90    cx.attrs.merge(&sol_attrs);
91    for item in body {
92        match item {
93            Item::Function(function) => match function.kind {
94                ast::FunctionKind::Function(_) if function.name.is_some() => {
95                    functions.push(function.clone());
96                }
97                ast::FunctionKind::Function(_) => {}
98                ast::FunctionKind::Modifier(_) => {}
99                ast::FunctionKind::Constructor(_) => {
100                    if constructor.is_none() {
101                        constructor = Some(function);
102                    } else {
103                        let msg = "duplicate constructor";
104                        return Err(syn::Error::new(function.span(), msg));
105                    }
106                }
107                ast::FunctionKind::Fallback(_) => {
108                    if fallback.is_none() {
109                        fallback = Some(function);
110                    } else {
111                        let msg = "duplicate fallback function";
112                        return Err(syn::Error::new(function.span(), msg));
113                    }
114                }
115                ast::FunctionKind::Receive(_) => {
116                    if receive.is_none() {
117                        receive = Some(function);
118                    } else {
119                        let msg = "duplicate receive function";
120                        return Err(syn::Error::new(function.span(), msg));
121                    }
122                }
123            },
124            Item::Error(error) => errors.push(error),
125            Item::Event(event) => events.push(event),
126            Item::Variable(var_def) => {
127                if let Some(function) = super::var_def::var_as_function(cx, var_def)? {
128                    functions.push(function);
129                }
130            }
131            _ => {}
132        }
133
134        if item.attrs().is_none() || item_attrs.is_empty() {
135            // avoid cloning item if we don't have to
136            item_tokens.extend(cx.expand_item(item)?);
137        } else {
138            // prepend `item_attrs` to `item.attrs`
139            let mut item = item.clone();
140            item.attrs_mut().expect("is_none checked above").splice(0..0, item_attrs.clone());
141            item_tokens.extend(cx.expand_item(&item)?);
142        }
143    }
144
145    // Remove any `Default` derives.
146    let mut enum_attrs = item_attrs;
147    for attr in &mut enum_attrs {
148        if !attr.path().is_ident("derive") {
149            continue;
150        }
151
152        let derives = alloy_sol_macro_input::parse_derives(attr);
153        let mut derives = derives.into_iter().collect::<Vec<_>>();
154        if derives.is_empty() {
155            continue;
156        }
157
158        let len = derives.len();
159        derives.retain(|derive| !derive.is_ident("Default"));
160        if derives.len() == len {
161            continue;
162        }
163
164        attr.meta = parse_quote! { derive(#(#derives),*) };
165    }
166
167    let enum_expander = CallLikeExpander { cx, contract_name: name.clone(), extra_methods };
168    let errors_enum = (!errors.is_empty()).then(|| {
169        let mut attrs = enum_attrs.clone();
170        let doc_str = format!("Container for all the [`{name}`](self) custom errors.");
171        attrs.push(parse_quote!(#[doc = #doc_str]));
172        attrs.push(parse_quote!(#[derive(Clone)]));
173        let enum_tokens = enum_expander.expand(ToExpand::Errors(&errors), attrs);
174        let builders = generate_error_builders(name, &errors, cx);
175        quote! {
176            #enum_tokens
177            #builders
178        }
179    });
180
181    let events_enum = (!events.is_empty()).then(|| {
182        let mut attrs = enum_attrs.clone();
183        let doc_str = format!("Container for all the [`{name}`](self) events.");
184        attrs.push(parse_quote!(#[doc = #doc_str]));
185        attrs.push(parse_quote!(#[derive(Clone)]));
186        let enum_tokens = enum_expander.expand(ToExpand::Events(&events), attrs);
187        let builders = generate_event_builders(name, &events, cx);
188        quote! {
189            #enum_tokens
190            #builders
191        }
192    });
193
194    let functions_enum = (!functions.is_empty()).then(|| {
195        let mut attrs = enum_attrs;
196        let doc_str = format!("Container for all the [`{name}`](self) function calls.");
197        attrs.push(parse_quote!(#[doc = #doc_str]));
198        attrs.push(parse_quote!(#[derive(Clone)]));
199        let enum_expander = CallLikeExpander { cx, contract_name: name.clone(), extra_methods };
200        enum_expander.expand(ToExpand::Functions(&functions), attrs)
201    });
202
203    // Restore the context attributes now that the errors, events and functions enums have all
204    // been expanded with the contract-level `extra_derives`/`all_derives`. Previously the
205    // functions (calls) enum was expanded after this reset, so it only derived `Clone` while the
206    // errors and events enums (and the call structs themselves) got the user's extra derives.
207    cx.attrs = prev_cx_attrs;
208
209    let mod_descr_doc = (docs && docs_str(&mod_attrs).trim().is_empty())
210        .then(|| mk_doc("Module containing a contract's types and functions."));
211    let mod_iface_doc = (docs && !docs_str(&mod_attrs).contains("```solidity\n"))
212        .then(|| mk_doc(format!("\n\n```solidity\n{contract}\n```")));
213
214    let abi = abi.then(|| {
215        if_json! {
216            use crate::verbatim::verbatim;
217            use super::to_abi;
218
219            let crates = &cx.crates;
220            let constructor = verbatim(&constructor.map(|x| to_abi::constructor(x, cx)), crates);
221            let fallback = verbatim(&fallback.map(|x| to_abi::fallback(x, cx)), crates);
222            let receive = verbatim(&receive.map(|x| to_abi::receive(x, cx)), crates);
223            let functions_map = to_abi::functions_map(&functions, cx);
224            let events_map = to_abi::events_map(&events, cx);
225            let errors_map = to_abi::errors_map(&errors, cx);
226            quote! {
227                /// Contains [dynamic ABI definitions](alloy_sol_types::private::alloy_json_abi) for [this contract](self).
228                pub mod abi {
229                    use super::*;
230                    use alloy_sol_types::private::{alloy_json_abi as json, BTreeMap, String, Vec};
231
232                    /// Returns the ABI for [this contract](super).
233                    pub fn contract() -> json::JsonAbi {
234                        json::JsonAbi {
235                            constructor: constructor(),
236                            fallback: fallback(),
237                            receive: receive(),
238                            functions: functions(),
239                            events: events(),
240                            errors: errors(),
241                        }
242                    }
243
244                    /// Returns the [`Constructor`](json::Constructor) of [this contract](super), if any.
245                    pub fn constructor() -> Option<json::Constructor> {
246                        #constructor
247                    }
248
249                    /// Returns the [`Fallback`](json::Fallback) function of [this contract](super), if any.
250                    pub fn fallback() -> Option<json::Fallback> {
251                        #fallback
252                    }
253
254                    /// Returns the [`Receive`](json::Receive) function of [this contract](super), if any.
255                    pub fn receive() -> Option<json::Receive> {
256                        #receive
257                    }
258
259                    /// Returns a map of all the [`Function`](json::Function)s of [this contract](super).
260                    pub fn functions() -> BTreeMap<String, Vec<json::Function>> {
261                        #functions_map
262                    }
263
264                    /// Returns a map of all the [`Event`](json::Event)s of [this contract](super).
265                    pub fn events() -> BTreeMap<String, Vec<json::Event>> {
266                        #events_map
267                    }
268
269                    /// Returns a map of all the [`Error`](json::Error)s of [this contract](super).
270                    pub fn errors() -> BTreeMap<String, Vec<json::Error>> {
271                        #errors_map
272                    }
273                }
274            }
275        }
276    });
277
278    let rpc = rpc.then(|| {
279        let contract_name = name;
280        let name = format_ident!("{contract_name}Instance");
281        let name_s = name.to_string();
282        let methods = functions.iter().map(|f| call_builder_method(f, cx));
283        let new_fn_doc = format!(
284            "Creates a new wrapper around an on-chain [`{contract_name}`](self) contract instance.\n\
285             \n\
286             See the [wrapper's documentation](`{name}`) for more details."
287        );
288        let struct_doc = format!(
289            "A [`{contract_name}`](self) instance.\n\
290             \n\
291             Contains type-safe methods for interacting with an on-chain instance of the\n\
292             [`{contract_name}`](self) contract located at a given `address`, using a given\n\
293             provider `P`.\n\
294             \n\
295             If the contract bytecode is available (see the [`sol!`](alloy_sol_types::sol!)\n\
296             documentation on how to provide it), the `deploy` and `deploy_builder` methods can\n\
297             be used to deploy a new instance of the contract.\n\
298             \n\
299             See the [module-level documentation](self) for all the available methods."
300        );
301        let (deploy_fn, deploy_method) = bytecode.is_some().then(|| {
302            let deploy_doc_str =
303                "Deploys this contract using the given `provider` and constructor arguments, if any.\n\
304                 \n\
305                 Returns a new instance of the contract, if the deployment was successful.\n\
306                 \n\
307                 For more fine-grained control over the deployment process, use [`deploy_builder`] instead.";
308            let deploy_doc = mk_doc(deploy_doc_str);
309
310            let deploy_builder_doc_str =
311                "Creates a `RawCallBuilder` for deploying this contract using the given `provider`\n\
312                 and constructor arguments, if any.\n\
313                 \n\
314                 This is a simple wrapper around creating a `RawCallBuilder` with the data set to\n\
315                 the bytecode concatenated with the constructor's ABI-encoded arguments.";
316            let deploy_builder_doc = mk_doc(deploy_builder_doc_str);
317
318            let (params, args) = constructor.and_then(|c| {
319                if c.parameters.is_empty() {
320                    return None;
321                }
322
323                let names1 = c.parameters.names().enumerate().map(anon_name);
324                let names2 = names1.clone();
325                let tys = c.parameters.types().map(|ty| {
326                    cx.expand_rust_type(ty)
327                });
328                Some((quote!(#(#names1: #tys),*), quote!(#(#names2,)*)))
329            }).unzip();
330            let deploy_builder_data = if matches!(constructor, Some(c) if !c.parameters.is_empty()) {
331                quote! {
332                    [
333                        &BYTECODE[..],
334                        &alloy_sol_types::SolConstructor::abi_encode(&constructorCall { #args })[..]
335                    ].concat().into()
336                }
337            } else {
338                quote! {
339                    ::core::clone::Clone::clone(&BYTECODE)
340                }
341            };
342
343            (
344                quote! {
345                    #deploy_doc
346                    #[inline]
347                    pub fn deploy<P: alloy_contract::private::Provider<N>, N: alloy_contract::private::Network>(__provider: P, #params)
348                        -> impl ::core::future::Future<Output = alloy_contract::Result<#name<P, N>>>
349                    {
350                        #name::<P, N>::deploy(__provider, #args)
351                    }
352
353                    #deploy_builder_doc
354                    #[inline]
355                    pub fn deploy_builder<P: alloy_contract::private::Provider<N>, N: alloy_contract::private::Network>(__provider: P, #params)
356                        -> alloy_contract::RawCallBuilder<P, N>
357                    {
358                        #name::<P, N>::deploy_builder(__provider, #args)
359                    }
360                },
361                quote! {
362                    #deploy_doc
363                    #[inline]
364                    pub async fn deploy(__provider: P, #params)
365                        -> alloy_contract::Result<#name<P, N>>
366                    {
367                        let call_builder = Self::deploy_builder(__provider, #args);
368                        let contract_address = call_builder.deploy().await?;
369                        Ok(Self::new(contract_address, call_builder.provider))
370                    }
371
372                    #deploy_builder_doc
373                    #[inline]
374                    pub fn deploy_builder(__provider: P, #params)
375                        -> alloy_contract::RawCallBuilder<P, N>
376                    {
377                        alloy_contract::RawCallBuilder::new_raw_deploy(__provider, #deploy_builder_data)
378                    }
379                },
380            )
381        }).unzip();
382
383        let filter_methods = events.iter().map(|&e| {
384            let event_name = cx.overloaded_name(e.into());
385            let name = format_ident!("{event_name}_filter");
386            let doc = format!(
387                "Creates a new event filter for the [`{event_name}`] event.",
388            );
389            quote! {
390                #[doc = #doc]
391                pub fn #name(&self) -> alloy_contract::Event<&P, #event_name, N> {
392                    self.event_filter::<#event_name>()
393                }
394            }
395        });
396
397        let alloy_contract = &cx.crates.contract;
398
399        let generic_p_n = quote!(<P: alloy_contract::private::Provider<N>, N: alloy_contract::private::Network>);
400
401        // if new builtin functions are introduced: updated reserved check in `call_builder_method_function_name`
402        quote! {
403            use #alloy_contract as alloy_contract;
404
405            #[doc = #new_fn_doc]
406            #[inline]
407            pub const fn new #generic_p_n(
408                address: alloy_sol_types::private::Address,
409                __provider: P,
410            ) -> #name<P, N> {
411                #name::<P, N>::new(address, __provider)
412            }
413
414            #deploy_fn
415
416            #[doc = #struct_doc]
417            #[derive(Clone)]
418            pub struct #name<P, N = alloy_contract::private::Ethereum> {
419                address: alloy_sol_types::private::Address,
420                provider: P,
421                _network: ::core::marker::PhantomData<N>,
422            }
423
424            #[automatically_derived]
425            impl<P, N> ::core::fmt::Debug for #name<P, N> {
426                #[inline]
427                fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
428                    f.debug_tuple(#name_s).field(&self.address).finish()
429                }
430            }
431
432            /// Instantiation and getters/setters.
433            impl #generic_p_n #name<P, N> {
434                #[doc = #new_fn_doc]
435                #[inline]
436                pub const fn new(address: alloy_sol_types::private::Address, __provider: P) -> Self {
437                    Self { address, provider: __provider, _network: ::core::marker::PhantomData }
438                }
439
440                #deploy_method
441
442                /// Returns a reference to the address.
443                #[inline]
444                pub const fn address(&self) -> &alloy_sol_types::private::Address {
445                    &self.address
446                }
447
448                /// Sets the address.
449                #[inline]
450                pub fn set_address(&mut self, address: alloy_sol_types::private::Address) {
451                    self.address = address;
452                }
453
454                /// Sets the address and returns `self`.
455                pub fn at(mut self, address: alloy_sol_types::private::Address) -> Self {
456                    self.set_address(address);
457                    self
458                }
459
460                /// Returns a reference to the provider.
461                #[inline]
462                pub const fn provider(&self) -> &P {
463                    &self.provider
464                }
465            }
466
467            impl<P: ::core::clone::Clone, N> #name<&P, N> {
468                /// Clones the provider and returns a new instance with the cloned provider.
469                #[inline]
470                pub fn with_cloned_provider(self) -> #name<P, N> {
471                    #name { address: self.address, provider: ::core::clone::Clone::clone(&self.provider), _network: ::core::marker::PhantomData }
472                }
473            }
474
475            /// Function calls.
476            impl #generic_p_n #name<P, N> {
477                /// Creates a new call builder using this contract instance's provider and address.
478                ///
479                /// Note that the call can be any function call, not just those defined in this
480                /// contract. Prefer using the other methods for building type-safe contract calls.
481                pub fn call_builder<C: alloy_sol_types::SolCall>(&self, call: &C)
482                    -> alloy_contract::SolCallBuilder<&P, C, N>
483                {
484                    alloy_contract::SolCallBuilder::new_sol(&self.provider, &self.address, call)
485                }
486
487                #(#methods)*
488            }
489
490            /// Event filters.
491            impl #generic_p_n #name<P, N> {
492                /// Creates a new event filter using this contract instance's provider and address.
493                ///
494                /// Note that the type can be any event, not just those defined in this contract.
495                /// Prefer using the other methods for building type-safe event filters.
496                pub fn event_filter<E: alloy_sol_types::SolEvent>(&self)
497                    -> alloy_contract::Event<&P, E, N>
498                {
499                    alloy_contract::Event::new_sol(&self.provider, &self.address)
500                }
501
502                #(#filter_methods)*
503            }
504        }
505    });
506
507    let alloy_sol_types = &cx.crates.sol_types;
508
509    let tokens = quote! {
510        #mod_descr_doc
511        #(#mod_attrs)*
512        #mod_iface_doc
513        #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields, clippy::style, clippy::empty_structs_with_brackets)]
514        pub mod #name {
515            use super::*;
516            use #alloy_sol_types as alloy_sol_types;
517
518            #bytecode
519            #deployed_bytecode
520
521            #item_tokens
522
523            #functions_enum
524            #errors_enum
525            #events_enum
526
527            #abi
528
529            #rpc
530        }
531    };
532    Ok(tokens)
533}
534
535// note that item impls generated here do not need to be wrapped in an anonymous
536// constant (`const _: () = { ... };`) because they are in one already
537
538/// Expands a `SolInterface` enum:
539///
540/// ```ignore (pseudo-code)
541/// #name = #{contract_name}Calls | #{contract_name}Errors | #{contract_name}Events;
542///
543/// pub enum #name {
544///    #(#variants(#types),)*
545/// }
546///
547/// impl SolInterface for #name {
548///     ...
549/// }
550///
551/// impl #name {
552///     pub const SELECTORS: &'static [[u8; _]] = &[...];
553/// }
554///
555/// #if extra_methods
556/// #(
557///     impl From<#types> for #name { ... }
558///     impl TryFrom<#name> for #types { ... }
559/// )*
560///
561/// impl #name {
562///     #(
563///         pub fn #is_variant,#as_variant,#as_variant_mut(...) -> ... { ... }
564///     )*
565/// }
566/// #endif
567/// ```
568struct CallLikeExpander<'a> {
569    cx: &'a ExpCtxt<'a>,
570    contract_name: SolIdent,
571    extra_methods: bool,
572}
573
574#[derive(Clone, Debug)]
575struct ExpandData {
576    name: Ident,
577    variants: Vec<Ident>,
578    types: Option<Vec<Ident>>,
579    min_data_len: usize,
580    trait_: Ident,
581    selectors: Vec<ExprArray<u8>>,
582    /// Whether the builtin `#[sol(all_derives)]` traits can be derived on the
583    /// generated enum. Computed from the underlying items' parameter types,
584    /// because the variant type names may be synthetic (overloaded items get
585    /// `_N` suffixes, call variants use `*Call` structs) and thus not
586    /// resolvable as items.
587    can_derive_builtin: bool,
588}
589
590impl ExpandData {
591    fn types(&self) -> &Vec<Ident> {
592        let types = self.types.as_ref().unwrap_or(&self.variants);
593        assert_eq!(types.len(), self.variants.len());
594        types
595    }
596
597    fn sort_by_selector(&mut self) {
598        let len = self.selectors.len();
599        if len <= 1 {
600            return;
601        }
602
603        let prev = self.selectors.clone();
604        self.selectors.sort_unstable();
605        // Arbitrary max length.
606        if len <= 20 && prev == self.selectors {
607            return;
608        }
609
610        let old_variants = self.variants.clone();
611        let old_types = self.types.clone();
612        let new_idxs =
613            prev.iter().map(|selector| self.selectors.iter().position(|s| s == selector).unwrap());
614        for (old, new) in new_idxs.enumerate() {
615            if old == new {
616                continue;
617            }
618
619            self.variants[new] = old_variants[old].clone();
620            if let Some(types) = self.types.as_mut() {
621                types[new] = old_types.as_ref().unwrap()[old].clone();
622            }
623        }
624    }
625}
626
627enum ToExpand<'a> {
628    Functions(&'a [ItemFunction]),
629    Errors(&'a [&'a ItemError]),
630    Events(&'a [&'a ItemEvent]),
631}
632
633impl ToExpand<'_> {
634    fn to_data(&self, expander: &CallLikeExpander<'_>) -> ExpandData {
635        let &CallLikeExpander { cx, ref contract_name, .. } = expander;
636        match self {
637            Self::Functions(functions) => {
638                let variants: Vec<_> =
639                    functions.iter().map(|f| cx.overloaded_name(f.into()).0).collect();
640
641                let types: Vec<_> = variants.iter().map(|name| cx.raw_call_name(name)).collect();
642
643                ExpandData {
644                    name: format_ident!("{contract_name}Calls"),
645                    variants,
646                    types: Some(types),
647                    min_data_len: functions
648                        .iter()
649                        .map(|function| cx.params_base_data_size(&function.parameters))
650                        .min()
651                        .unwrap(),
652                    trait_: format_ident!("SolCall"),
653                    selectors: functions.iter().map(|f| cx.function_selector(f)).collect(),
654                    can_derive_builtin: functions
655                        .iter()
656                        .all(|f| f.parameters.types().all(|ty| cx.can_derive_builtin_traits(ty))),
657                }
658            }
659
660            Self::Errors(errors) => ExpandData {
661                name: format_ident!("{contract_name}Errors"),
662                variants: errors.iter().map(|&error| cx.overloaded_name(error.into()).0).collect(),
663                types: None,
664                min_data_len: errors
665                    .iter()
666                    .map(|error| cx.params_base_data_size(&error.parameters))
667                    .min()
668                    .unwrap(),
669                trait_: format_ident!("SolError"),
670                selectors: errors.iter().map(|e| cx.error_selector(e)).collect(),
671                can_derive_builtin: errors.iter().all(|&error| {
672                    error.parameters.types().all(|ty| cx.can_derive_builtin_traits(ty))
673                }),
674            },
675
676            Self::Events(events) => {
677                let variants: Vec<_> =
678                    events.iter().map(|&event| cx.overloaded_name(event.into()).0).collect();
679
680                ExpandData {
681                    name: format_ident!("{contract_name}Events"),
682                    variants,
683                    types: None,
684                    min_data_len: events
685                        .iter()
686                        .map(|event| cx.params_base_data_size(&event.params()))
687                        .min()
688                        .unwrap(),
689                    trait_: format_ident!("SolEvent"),
690                    selectors: events.iter().map(|e| cx.event_selector(e)).collect(),
691                    can_derive_builtin: events.iter().all(|&event| {
692                        event.parameters.iter().all(|p| cx.can_derive_builtin_traits(&p.ty))
693                    }),
694                }
695            }
696        }
697    }
698}
699
700impl CallLikeExpander<'_> {
701    fn expand(&self, to_expand: ToExpand<'_>, attrs: Vec<Attribute>) -> TokenStream {
702        let data = &to_expand.to_data(self);
703
704        let mut sorted_data = data.clone();
705        sorted_data.sort_by_selector();
706        #[cfg(debug_assertions)]
707        for (i, sv) in sorted_data.variants.iter().enumerate() {
708            let s = &sorted_data.selectors[i];
709
710            let normal_pos = data.variants.iter().position(|v| v == sv).unwrap();
711            let ns = &data.selectors[normal_pos];
712            assert_eq!(s, ns);
713        }
714
715        if let ToExpand::Events(events) = to_expand {
716            return self.expand_events(events, data, &sorted_data, attrs);
717        }
718
719        let def = self.generate_enum(data, &sorted_data, attrs);
720        let ExpandData { name, variants, min_data_len, trait_, .. } = data;
721        let types = data.types();
722        let name_s = name.to_string();
723        let count = data.variants.len();
724
725        let sorted_variants = &sorted_data.variants;
726        let sorted_types = sorted_data.types();
727
728        quote! {
729            #def
730
731            #[automatically_derived]
732            impl alloy_sol_types::SolInterface for #name {
733                const NAME: &'static str = #name_s;
734                const MIN_DATA_LENGTH: usize = #min_data_len;
735                const COUNT: usize = #count;
736
737                #[inline]
738                fn selector(&self) -> [u8; 4] {
739                    match self {#(
740                        Self::#variants(_) => <#types as alloy_sol_types::#trait_>::SELECTOR,
741                    )*}
742                }
743
744                #[inline]
745                fn selector_at(i: usize) -> ::core::option::Option<[u8; 4]> {
746                    Self::SELECTORS.get(i).copied()
747                }
748
749                #[inline]
750                fn valid_selector(selector: [u8; 4]) -> bool {
751                    Self::SELECTORS.binary_search(&selector).is_ok()
752                }
753
754                #[inline]
755                #[allow(non_snake_case)]
756                fn abi_decode_raw(
757                    selector: [u8; 4],
758                    data: &[u8],
759                )-> alloy_sol_types::Result<Self> {
760                    Self::abi_decode_raw_with_config(
761                        selector,
762                        data,
763                        alloy_sol_types::abi::AbiDecoderConfig::default(),
764                    )
765                }
766
767                #[inline]
768                #[allow(non_snake_case)]
769                fn abi_decode_raw_with_config(
770                    selector: [u8; 4],
771                    data: &[u8],
772                    config: alloy_sol_types::abi::AbiDecoderConfig,
773                ) -> alloy_sol_types::Result<Self> {
774                    static DECODE_SHIMS: &[fn(
775                        &[u8],
776                        alloy_sol_types::abi::AbiDecoderConfig,
777                    ) -> alloy_sol_types::Result<#name>] = &[
778                        #({
779                            fn #sorted_variants(
780                                data: &[u8],
781                                config: alloy_sol_types::abi::AbiDecoderConfig,
782                            ) -> alloy_sol_types::Result<#name> {
783                                <#sorted_types as alloy_sol_types::#trait_>::abi_decode_raw_with_config(
784                                    data,
785                                    config,
786                                )
787                                .map(#name::#sorted_variants)
788                            }
789                            #sorted_variants
790                        }),*
791                    ];
792
793                    let Ok(idx) = Self::SELECTORS.binary_search(&selector) else {
794                        return Err(alloy_sol_types::Error::unknown_selector(
795                            <Self as alloy_sol_types::SolInterface>::NAME,
796                            selector,
797                        ));
798                    };
799                    // `SELECTORS` and `DECODE_SHIMS` have the same length and are sorted in the same order.
800                    DECODE_SHIMS[idx](data, config)
801                }
802
803                #[inline]
804                #[allow(non_snake_case)]
805                // TODO: Deprecate in favor of a validating decoder configuration.
806                // #[deprecated(note = "use a validating decoder configuration")]
807                fn abi_decode_raw_validate(
808                    selector: [u8; 4],
809                    data: &[u8],
810                ) -> alloy_sol_types::Result<Self> {
811                    Self::abi_decode_raw_with_config(
812                        selector,
813                        data,
814                        alloy_sol_types::abi::AbiDecoderConfig::new().validate(true),
815                    )
816                }
817
818                #[inline]
819                fn abi_encoded_size(&self) -> usize {
820                    match self {#(
821                        Self::#variants(inner) =>
822                            <#types as alloy_sol_types::#trait_>::abi_encoded_size(inner),
823                    )*}
824                }
825
826                #[inline]
827                fn abi_encode_raw(&self, out: &mut alloy_sol_types::private::Vec<u8>) {
828                    match self {#(
829                        Self::#variants(inner) =>
830                            <#types as alloy_sol_types::#trait_>::abi_encode_raw(inner, out),
831                    )*}
832                }
833            }
834        }
835    }
836
837    fn expand_events(
838        &self,
839        events: &[&ItemEvent],
840        data: &ExpandData,
841        sorted_data: &ExpandData,
842        attrs: Vec<Attribute>,
843    ) -> TokenStream {
844        let def = self.generate_enum(data, sorted_data, attrs);
845        let ExpandData { name, trait_, .. } = data;
846        let name_s = name.to_string();
847        let count = data.variants.len();
848
849        let has_anon = events.iter().any(|e| e.is_anonymous());
850        let has_non_anon = events.iter().any(|e| !e.is_anonymous());
851        assert!(has_anon || has_non_anon, "events shouldn't be empty");
852
853        let e_name = |&e: &&ItemEvent| self.cx.overloaded_name(e.into());
854        let err = quote! {
855            alloy_sol_types::private::Err(alloy_sol_types::Error::InvalidLog {
856                name: <Self as alloy_sol_types::SolEventInterface>::NAME,
857                log: alloy_sol_types::private::Box::new(alloy_sol_types::private::LogData::new_unchecked(
858                    topics.to_vec(),
859                    data.to_vec().into(),
860                )),
861            })
862        };
863        let non_anon_impl = has_non_anon.then(|| {
864            let variants = events.iter().filter(|e| !e.is_anonymous()).map(e_name);
865            let ret = has_anon.then(|| quote!(return));
866            let ret_err = (!has_anon).then_some(&err);
867            quote! {
868                match topics.first().copied() {
869                    #(
870                        Some(<#variants as alloy_sol_types::#trait_>::SIGNATURE_HASH) =>
871                            #ret <#variants as alloy_sol_types::#trait_>::decode_raw_log(topics, data)
872                                .map(Self::#variants),
873                    )*
874                    _ => { #ret_err }
875                }
876            }
877        });
878        let anon_impl = has_anon.then(|| {
879            let variants = events.iter().filter(|e| e.is_anonymous()).map(e_name);
880            quote! {
881                #(
882                    if let Ok(res) = <#variants as alloy_sol_types::#trait_>::decode_raw_log(topics, data) {
883                        return Ok(Self::#variants(res));
884                    }
885                )*
886                #err
887            }
888        });
889
890        let into_impl = {
891            let variants = events.iter().map(e_name);
892            let v2 = variants.clone();
893            quote! {
894                #[automatically_derived]
895                impl alloy_sol_types::private::IntoLogData for #name {
896                    fn to_log_data(&self) -> alloy_sol_types::private::LogData {
897                        match self {#(
898                            Self::#variants(inner) =>
899                            alloy_sol_types::private::IntoLogData::to_log_data(inner),
900                        )*}
901                    }
902
903                    fn into_log_data(self) -> alloy_sol_types::private::LogData {
904                        match self {#(
905                            Self::#v2(inner) =>
906                            alloy_sol_types::private::IntoLogData::into_log_data(inner),
907                        )*}
908                    }
909                }
910            }
911        };
912
913        quote! {
914            #def
915
916            #[automatically_derived]
917            impl alloy_sol_types::SolEventInterface for #name {
918                const NAME: &'static str = #name_s;
919                const COUNT: usize = #count;
920
921                fn decode_raw_log(topics: &[alloy_sol_types::Word], data: &[u8]) -> alloy_sol_types::Result<Self> {
922                    #non_anon_impl
923                    #anon_impl
924                }
925            }
926
927            #into_impl
928        }
929    }
930
931    fn generate_enum(
932        &self,
933        data: &ExpandData,
934        sorted_data: &ExpandData,
935        mut attrs: Vec<Attribute>,
936    ) -> TokenStream {
937        let ExpandData { name, variants, .. } = data;
938        let types = data.types();
939
940        let selectors = &sorted_data.selectors;
941        let sorted_variants = &sorted_data.variants;
942        let sorted_types = sorted_data.types();
943
944        let selector_len = selectors.first().unwrap().array.len();
945        assert!(selectors.iter().all(|s| s.array.len() == selector_len));
946        let selector_type = quote!([u8; #selector_len]);
947
948        self.cx.enum_derives(&mut attrs, data.can_derive_builtin);
949        let trait_ = &data.trait_;
950
951        let mut tokens = quote! {
952            #(#attrs)*
953            pub enum #name {
954                #(
955                    #[allow(missing_docs)]
956                    #variants(#types),
957                )*
958            }
959
960            impl #name {
961                /// All the selectors of this enum.
962                ///
963                /// Note that the selectors might not be in the same order as the variants.
964                /// No guarantees are made about the order of the selectors.
965                ///
966                /// Prefer using `SolInterface` methods instead.
967                // NOTE: This is currently sorted to allow for binary search in `SolInterface`.
968                pub const SELECTORS: &'static [#selector_type] = &[#(#selectors),*];
969
970                /// The names of the variants in the same order as `SELECTORS`.
971                pub const VARIANT_NAMES: &'static [&'static str] = &[#(::core::stringify!(#sorted_variants)),*];
972
973                /// The signatures in the same order as `SELECTORS`.
974                pub const SIGNATURES: &'static [&'static str] = &[#(<#sorted_types as alloy_sol_types::#trait_>::SIGNATURE),*];
975
976                /// Returns the signature for the given selector, if known.
977                #[inline]
978                pub fn signature_by_selector(selector: #selector_type) -> ::core::option::Option<&'static str> {
979                    match Self::SELECTORS.binary_search(&selector) {
980                        ::core::result::Result::Ok(idx) => ::core::option::Option::Some(Self::SIGNATURES[idx]),
981                        ::core::result::Result::Err(_) => ::core::option::Option::None,
982                    }
983                }
984
985                /// Returns the enum variant name for the given selector, if known.
986                #[inline]
987                pub fn name_by_selector(selector: #selector_type) -> ::core::option::Option<&'static str> {
988                    let sig = Self::signature_by_selector(selector)?;
989                    sig.split_once('(').map(|(name, _)| name)
990                }
991            }
992        };
993
994        if self.extra_methods {
995            let conversions =
996                variants.iter().zip(types).map(|(v, t)| generate_variant_conversions(name, v, t));
997            let methods = variants.iter().zip(types).map(generate_variant_methods);
998            tokens.extend(conversions);
999            tokens.extend(quote! {
1000                impl #name {
1001                    #(#methods)*
1002                }
1003            });
1004        }
1005
1006        tokens
1007    }
1008}
1009
1010fn generate_variant_conversions(name: &Ident, variant: &Ident, ty: &Ident) -> TokenStream {
1011    quote! {
1012        #[automatically_derived]
1013        impl ::core::convert::From<#ty> for #name {
1014            #[inline]
1015            fn from(value: #ty) -> Self {
1016                Self::#variant(value)
1017            }
1018        }
1019
1020        #[automatically_derived]
1021        impl ::core::convert::TryFrom<#name> for #ty {
1022            type Error = #name;
1023
1024            #[inline]
1025            fn try_from(value: #name) -> ::core::result::Result<Self, #name> {
1026                match value {
1027                    #name::#variant(value) => ::core::result::Result::Ok(value),
1028                    _ => ::core::result::Result::Err(value),
1029                }
1030            }
1031        }
1032    }
1033}
1034
1035fn generate_variant_methods((variant, ty): (&Ident, &Ident)) -> TokenStream {
1036    let name_snake = snakify(&variant.to_string());
1037
1038    let is_variant = format_ident!("is_{name_snake}");
1039    let is_variant_doc =
1040        format!("Returns `true` if `self` matches [`{variant}`](Self::{variant}).");
1041
1042    let as_variant = format_ident!("as_{name_snake}");
1043    let as_variant_doc = format!(
1044        "Returns an immutable reference to the inner [`{ty}`] if `self` matches [`{variant}`](Self::{variant})."
1045    );
1046
1047    let as_variant_mut = format_ident!("as_{name_snake}_mut");
1048    let as_variant_mut_doc = format!(
1049        "Returns a mutable reference to the inner [`{ty}`] if `self` matches [`{variant}`](Self::{variant})."
1050    );
1051
1052    quote! {
1053        #[doc = #is_variant_doc]
1054        #[inline]
1055        pub const fn #is_variant(&self) -> bool {
1056            ::core::matches!(self, Self::#variant(_))
1057        }
1058
1059        #[doc = #as_variant_doc]
1060        #[inline]
1061        pub const fn #as_variant(&self) -> ::core::option::Option<&#ty> {
1062            match self {
1063                Self::#variant(inner) => ::core::option::Option::Some(inner),
1064                _ => ::core::option::Option::None,
1065            }
1066        }
1067
1068        #[doc = #as_variant_mut_doc]
1069        #[inline]
1070        pub fn #as_variant_mut(&mut self) -> ::core::option::Option<&mut #ty> {
1071            match self {
1072                Self::#variant(inner) => ::core::option::Option::Some(inner),
1073                _ => ::core::option::Option::None,
1074            }
1075        }
1076    }
1077}
1078
1079/// Generate's the call instance's call functions.
1080///
1081/// The are standalone functions and never used by other generated code.
1082fn call_builder_method(f: &ItemFunction, cx: &ExpCtxt<'_>) -> TokenStream {
1083    let name = call_builder_method_function_name(f, cx);
1084    let call_name = cx.call_name(f);
1085    let param_names1 = f.parameters.names().enumerate().map(anon_name);
1086    let param_tys = f.parameters.types().map(|ty| cx.expand_rust_type(ty));
1087    let doc = format!("Creates a new call builder for the [`{name}`] function.");
1088
1089    let call_struct = if f.parameters.is_empty() {
1090        quote! { #call_name }
1091    } else if f.parameters.len() == 1 && f.parameters[0].name.is_none() {
1092        quote! { #call_name(_0) }
1093    } else {
1094        let call_fields = param_names1.clone();
1095        quote! {
1096            #call_name { #(#call_fields),* }
1097        }
1098    };
1099    quote! {
1100        #[doc = #doc]
1101        pub fn #name(&self, #(#param_names1: #param_tys),*) -> alloy_contract::SolCallBuilder<&P, #call_name, N> {
1102            self.call_builder(&#call_struct)
1103        }
1104    }
1105}
1106
1107/// Returns the function name for the `fn <method> -> alloy_contract::SolCallBuilder` function.
1108///
1109/// If this conflicts with any of the builtin function names, a `_call` suffix is added.
1110fn call_builder_method_function_name(f: &ItemFunction, cx: &ExpCtxt<'_>) -> SolIdent {
1111    let call_name_ident = cx.function_name(f);
1112    let name = call_name_ident.as_string();
1113    match name.as_str() {
1114        "new" | "deploy" | "deploy_builder" | "address" | "set_address" | "at" | "provider"
1115        | "call_builder" | "event_filter" => {
1116            SolIdent::new_spanned(&format!("{name}_call"), call_name_ident.span())
1117        }
1118        _ => call_name_ident,
1119    }
1120}
1121
1122/// Generates snake_case constructor helpers on the `{Interface}Errors` enum.
1123fn generate_error_builders(
1124    contract_name: &SolIdent,
1125    errors: &[&ItemError],
1126    cx: &ExpCtxt<'_>,
1127) -> TokenStream {
1128    let enum_name = format_ident!("{contract_name}Errors");
1129    let methods = errors.iter().map(|error| {
1130        let variant_name = cx.overloaded_name((*error).into());
1131        let fn_name = snakify_ident(&variant_name);
1132        let sig = cx.error_signature(error);
1133        let doc = format!("Creates a [`{variant_name}`] error.\n\n```solidity\nerror {sig}\n```");
1134
1135        match error.parameters.len() {
1136            // Unit struct: `error Foo();`
1137            0 => {
1138                quote! {
1139                    #[doc = #doc]
1140                    #[inline]
1141                    pub fn #fn_name() -> Self {
1142                        Self::#variant_name(#variant_name)
1143                    }
1144                }
1145            }
1146            // Single unnamed param: `error Foo(uint256);` → tuple struct
1147            1 if error.parameters[0].name.is_none() => {
1148                let ty = cx.expand_rust_type(&error.parameters[0].ty);
1149                let param_name = format_ident!("_0");
1150                quote! {
1151                    #[doc = #doc]
1152                    #[inline]
1153                    pub fn #fn_name(#param_name: #ty) -> Self {
1154                        Self::#variant_name(#variant_name(#param_name))
1155                    }
1156                }
1157            }
1158            // Named fields: `error Foo(uint256 bar, address baz);`
1159            _ => {
1160                let params: Vec<_> = error
1161                    .parameters
1162                    .iter()
1163                    .enumerate()
1164                    .map(|(i, p)| {
1165                        let sol_name = super::anon_name((i, p.name.as_ref()));
1166                        let param_name = snakify_ident(&sol_name);
1167                        (sol_name, param_name, cx.expand_rust_type(&p.ty))
1168                    })
1169                    .collect();
1170                builder_method(&fn_name, &doc, &variant_name, &params)
1171            }
1172        }
1173    });
1174
1175    quote! {
1176        #[automatically_derived]
1177        impl #enum_name {
1178            #(#methods)*
1179        }
1180    }
1181}
1182
1183/// Generates snake_case constructor helpers on the `{Interface}Events` enum.
1184fn generate_event_builders(
1185    contract_name: &SolIdent,
1186    events: &[&ItemEvent],
1187    cx: &ExpCtxt<'_>,
1188) -> TokenStream {
1189    let enum_name = format_ident!("{contract_name}Events");
1190    let methods = events.iter().map(|event| {
1191        let variant_name = cx.overloaded_name((*event).into());
1192        let fn_name = snakify_ident(&variant_name);
1193        let sig = cx.event_signature(event);
1194        let doc = format!("Creates a [`{variant_name}`] event.\n\n```solidity\nevent {sig}\n```");
1195
1196        if event.parameters.is_empty() {
1197            quote! {
1198                #[doc = #doc]
1199                #[inline]
1200                pub fn #fn_name() -> Self {
1201                    Self::#variant_name(#variant_name)
1202                }
1203            }
1204        } else {
1205            let params: Vec<_> = event
1206                .parameters
1207                .iter()
1208                .enumerate()
1209                .map(|(i, p)| {
1210                    let sol_name = super::anon_name((i, p.name.as_ref()));
1211                    let param_name = snakify_ident(&sol_name);
1212                    (sol_name, param_name, cx.expand_event_param_type(p))
1213                })
1214                .collect();
1215            builder_method(&fn_name, &doc, &variant_name, &params)
1216        }
1217    });
1218
1219    quote! {
1220        #[automatically_derived]
1221        impl #enum_name {
1222            #(#methods)*
1223        }
1224    }
1225}
1226
1227/// Emits a single named-field builder method.
1228fn builder_method(
1229    fn_name: &Ident,
1230    doc: &str,
1231    variant_name: &SolIdent,
1232    params: &[(Ident, Ident, TokenStream)],
1233) -> TokenStream {
1234    let fn_params = params.iter().map(|(_, param_name, ty)| quote!(#param_name: #ty));
1235    let field_inits = params.iter().map(|(sol_name, param_name, _)| quote!(#sol_name: #param_name));
1236    quote! {
1237        #[doc = #doc]
1238        #[inline]
1239        pub fn #fn_name(#(#fn_params),*) -> Self {
1240            Self::#variant_name(#variant_name {
1241                #(#field_inits),*
1242            })
1243        }
1244    }
1245}
1246
1247/// Converts a name to snake_case, falling back to a raw identifier if the
1248/// result is a Rust keyword.
1249fn snakify_ident(name: &impl ToString) -> Ident {
1250    let s = snakify(&name.to_string());
1251    syn::parse_str::<Ident>(&s).unwrap_or_else(|_| Ident::new_raw(&s, Span::call_site()))
1252}
1253
1254/// `heck` doesn't treat numbers as new words, and discards leading underscores.
1255fn snakify(s: &str) -> String {
1256    let leading_n = s.chars().take_while(|c| *c == '_').count();
1257    let (leading, s) = s.split_at(leading_n);
1258    let mut output: Vec<char> = leading.chars().chain(s.to_snake_case().chars()).collect();
1259
1260    let mut num_starts = vec![];
1261    for (pos, c) in output.iter().enumerate() {
1262        if pos != 0
1263            && c.is_ascii_digit()
1264            && !output[pos - 1].is_ascii_digit()
1265            && !output[pos - 1].is_ascii_punctuation()
1266        {
1267            num_starts.push(pos);
1268        }
1269    }
1270    // need to do in reverse, because after inserting, all chars after the point of
1271    // insertion are off
1272    for i in num_starts.into_iter().rev() {
1273        output.insert(i, '_');
1274    }
1275    output.into_iter().collect()
1276}