ethers_contract_abigen/contract/
methods.rs

1//! Methods expansion
2
3use super::{structs::expand_struct, types, Context};
4use crate::util;
5use ethers_core::{
6    abi::{Function, FunctionExt, Param, ParamType},
7    macros::{ethers_contract_crate, ethers_core_crate},
8    types::Selector,
9};
10use eyre::{Context as _, Result};
11use inflector::Inflector;
12use proc_macro2::{Literal, TokenStream};
13use quote::quote;
14use std::collections::{btree_map::Entry, BTreeMap, HashMap, HashSet};
15use syn::Ident;
16
17/// The maximum amount of overloaded functions that are attempted to auto aliased with their param
18/// name. If there is a function that with `NAME_ALIASING_OVERLOADED_FUNCTIONS_CAP` overloads then
19/// all functions are aliased with their index, like `log0, log1, log2,....`
20const NAME_ALIASING_OVERLOADED_FUNCTIONS_CAP: usize = 3;
21
22/// Expands a context into a method struct containing all the generated bindings
23/// to the Solidity contract methods.
24impl Context {
25    /// Expands all method implementations
26    pub(crate) fn methods_and_call_structs(&self) -> Result<(TokenStream, TokenStream)> {
27        let aliases = self.get_method_aliases()?;
28        let sorted_functions: BTreeMap<_, _> = self.abi.functions.iter().collect();
29        let functions = sorted_functions
30            .values()
31            .flat_map(std::ops::Deref::deref)
32            .map(|function| {
33                let signature = function.abi_signature();
34                self.expand_function(function, aliases.get(&signature).cloned())
35                    .wrap_err_with(|| eyre::eyre!("error expanding function '{signature}'"))
36            })
37            .collect::<Result<Vec<_>>>()?;
38
39        let function_impls = quote! { #( #functions )* };
40        let call_structs = self.expand_call_structs(aliases.clone())?;
41        let return_structs = self.expand_return_structs(aliases)?;
42
43        let all_structs = quote! {
44            #call_structs
45            #return_structs
46        };
47
48        Ok((function_impls, all_structs))
49    }
50
51    /// Returns all deploy (constructor) implementations
52    #[cfg(feature = "providers")]
53    pub(crate) fn deployment_methods(&self) -> Option<TokenStream> {
54        // don't generate deploy if no bytecode
55        self.contract_bytecode.as_ref()?;
56
57        let ethers_core = ethers_core_crate();
58        let ethers_contract = ethers_contract_crate();
59
60        let abi_name = self.inline_abi_ident();
61        let get_abi = quote! {
62            #abi_name.clone()
63        };
64
65        let bytecode_name = self.inline_bytecode_ident();
66        let get_bytecode = quote! {
67            #bytecode_name.clone().into()
68        };
69
70        Some(quote! {
71            /// Constructs the general purpose `Deployer` instance based on the provided constructor arguments and sends it.
72            /// Returns a new instance of a deployer that returns an instance of this contract after sending the transaction
73            ///
74            /// Notes:
75            /// - If there are no constructor arguments, you should pass `()` as the argument.
76            /// - The default poll duration is 7 seconds.
77            /// - The default number of confirmations is 1 block.
78            ///
79            ///
80            /// # Example
81            ///
82            /// Generate contract bindings with `abigen!` and deploy a new contract instance.
83            ///
84            /// *Note*: this requires a `bytecode` and `abi` object in the `greeter.json` artifact.
85            ///
86            /// ```ignore
87            /// # async fn deploy<M: ethers::providers::Middleware>(client: ::std::sync::Arc<M>) {
88            ///     abigen!(Greeter, "../greeter.json");
89            ///
90            ///    let greeter_contract = Greeter::deploy(client, "Hello world!".to_string()).unwrap().send().await.unwrap();
91            ///    let msg = greeter_contract.greet().call().await.unwrap();
92            /// # }
93            /// ```
94            pub fn deploy<T: #ethers_core::abi::Tokenize>(
95                client: ::std::sync::Arc<M>,
96                constructor_args: T,
97            ) -> ::core::result::Result<#ethers_contract::builders::ContractDeployer<M, Self>, #ethers_contract::ContractError<M>> {
98                let factory = #ethers_contract::ContractFactory::new(#get_abi, #get_bytecode, client);
99                let deployer = factory.deploy(constructor_args)?;
100                let deployer = #ethers_contract::ContractDeployer::new(deployer);
101                Ok(deployer)
102            }
103        })
104    }
105
106    /// Expands to the corresponding struct type based on the inputs of the given function
107    fn expand_call_struct(
108        &self,
109        function: &Function,
110        alias: Option<&MethodAlias>,
111    ) -> Result<TokenStream> {
112        let struct_name = expand_call_struct_name(function, alias);
113
114        let fields = self.expand_input_params(function)?;
115        // expand as a tuple if all fields are anonymous
116        let all_anonymous_fields = function.inputs.iter().all(|input| input.name.is_empty());
117        let call_type_definition = expand_struct(&struct_name, &fields, all_anonymous_fields);
118
119        let function_name = &function.name;
120        let abi_signature = function.abi_signature();
121        let doc_str = format!(
122            "Container type for all input parameters for the `{function_name}` function with signature `{abi_signature}` and selector `0x{}`",
123            hex::encode(function.selector())
124        );
125
126        let mut derives = self.expand_extra_derives();
127        let params = function.inputs.iter().map(|param| &param.kind);
128        util::derive_builtin_traits(params, &mut derives, true, true);
129
130        let ethers_contract = ethers_contract_crate();
131
132        Ok(quote! {
133            #[doc = #doc_str]
134            #[derive(Clone, #ethers_contract::EthCall, #ethers_contract::EthDisplay, #derives)]
135            #[ethcall( name = #function_name, abi = #abi_signature )]
136            pub #call_type_definition
137        })
138    }
139
140    /// Expands to the corresponding struct type based on the inputs of the given function
141    pub fn expand_return_struct(
142        &self,
143        function: &Function,
144        alias: Option<&MethodAlias>,
145    ) -> Result<Option<TokenStream>> {
146        // no point in having structs when there is no data returned
147        if function.outputs.is_empty() {
148            return Ok(None)
149        }
150
151        let name = &function.name;
152
153        let struct_name = expand_return_struct_name(function, alias);
154        let fields = self.expand_output_params(function)?;
155        // expand as a tuple if all fields are anonymous
156        let all_anonymous_fields = function.outputs.iter().all(|output| output.name.is_empty());
157        let return_type_definition = expand_struct(&struct_name, &fields, all_anonymous_fields);
158
159        let abi_signature = function.abi_signature();
160        let doc_str = format!(
161            "Container type for all return fields from the `{name}` function with signature `{abi_signature}` and selector `0x{}`",
162            hex::encode(function.selector())
163        );
164
165        let mut derives = self.expand_extra_derives();
166        let params = function.outputs.iter().map(|param| &param.kind);
167        util::derive_builtin_traits(params, &mut derives, true, true);
168
169        let ethers_contract = ethers_contract_crate();
170
171        Ok(Some(quote! {
172            #[doc = #doc_str]
173            #[derive(Clone, #ethers_contract::EthAbiType, #ethers_contract::EthAbiCodec, #derives)]
174            pub #return_type_definition
175        }))
176    }
177
178    /// Expands all call structs
179    fn expand_call_structs(&self, aliases: BTreeMap<String, MethodAlias>) -> Result<TokenStream> {
180        let len = self.abi.functions.len();
181        let mut struct_defs = Vec::with_capacity(len);
182        let mut struct_names = Vec::with_capacity(len);
183        let mut variant_names = Vec::with_capacity(len);
184        for function in self.abi.functions.values().flatten() {
185            let signature = function.abi_signature();
186            let alias = aliases.get(&signature);
187            struct_defs.push(self.expand_call_struct(function, alias)?);
188            struct_names.push(expand_call_struct_name(function, alias));
189            variant_names.push(expand_call_struct_variant_name(function, alias));
190        }
191
192        let struct_def_tokens = quote!(#(#struct_defs)*);
193
194        if struct_defs.len() <= 1 {
195            // no need for an enum
196            return Ok(struct_def_tokens)
197        }
198
199        let mut derives = self.expand_extra_derives();
200        let params =
201            self.abi.functions.values().flatten().flat_map(|f| &f.inputs).map(|param| &param.kind);
202        util::derive_builtin_traits(params, &mut derives, false, true);
203
204        let enum_name = self.expand_calls_enum_name();
205
206        let ethers_core = ethers_core_crate();
207        let ethers_contract = ethers_contract_crate();
208
209        let tokens = quote! {
210            #struct_def_tokens
211
212            #[doc = "Container type for all of the contract's call "]
213            #[derive(Clone, #ethers_contract::EthAbiType, #derives)]
214            pub enum #enum_name {
215                #( #variant_names(#struct_names), )*
216            }
217
218            impl #ethers_core::abi::AbiDecode for #enum_name {
219                fn decode(data: impl AsRef<[u8]>) -> ::core::result::Result<Self, #ethers_core::abi::AbiError> {
220                    let data = data.as_ref();
221                    #(
222                        if let Ok(decoded) = <#struct_names as #ethers_core::abi::AbiDecode>::decode(data) {
223                            return Ok(Self::#variant_names(decoded))
224                        }
225                    )*
226                    Err(#ethers_core::abi::Error::InvalidData.into())
227                }
228            }
229
230            impl #ethers_core::abi::AbiEncode for #enum_name {
231                fn encode(self) -> Vec<u8> {
232                    match self {
233                        #(
234                            Self::#variant_names(element) => #ethers_core::abi::AbiEncode::encode(element),
235                        )*
236                    }
237                }
238            }
239
240            impl ::core::fmt::Display for #enum_name {
241                fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
242                    match self {
243                        #(
244                            Self::#variant_names(element) => ::core::fmt::Display::fmt(element, f),
245                        )*
246                    }
247                }
248            }
249
250            #(
251                impl ::core::convert::From<#struct_names> for #enum_name {
252                    fn from(value: #struct_names) -> Self {
253                        Self::#variant_names(value)
254                    }
255                }
256            )*
257        };
258
259        Ok(tokens)
260    }
261
262    /// Expands all return structs
263    fn expand_return_structs(&self, aliases: BTreeMap<String, MethodAlias>) -> Result<TokenStream> {
264        let mut tokens = TokenStream::new();
265        for function in self.abi.functions.values().flatten() {
266            let signature = function.abi_signature();
267            let alias = aliases.get(&signature);
268            match self.expand_return_struct(function, alias) {
269                Ok(Some(def)) => tokens.extend(def),
270                Ok(None) => {}
271                Err(e) => return Err(e),
272            }
273        }
274        Ok(tokens)
275    }
276
277    /// The name ident of the calls enum
278    fn expand_calls_enum_name(&self) -> Ident {
279        util::ident(&format!("{}Calls", self.contract_ident))
280    }
281
282    /// Expands to the `name : type` pairs of the function's inputs
283    fn expand_input_params(&self, fun: &Function) -> Result<Vec<(TokenStream, TokenStream)>> {
284        types::expand_params(&fun.inputs, |p| {
285            self.internal_structs.get_function_input_struct_type(&fun.name, &p.name)
286        })
287    }
288
289    /// Expands to the `name: type` pairs of the function's outputs
290    fn expand_output_params(&self, fun: &Function) -> Result<Vec<(TokenStream, TokenStream)>> {
291        types::expand_params(&fun.outputs, |p| {
292            p.internal_type
293                .as_deref()
294                .and_then(|s| self.internal_structs.get_function_output_struct_type(&fun.name, s))
295        })
296    }
297
298    /// Expands the arguments for the call that eventually calls the contract
299    fn expand_contract_call_args(&self, fun: &Function) -> TokenStream {
300        let mut call_args = fun.inputs.iter().enumerate().map(|(idx, param)| {
301            let name = util::expand_input_name(idx, &param.name);
302            match param.kind {
303                // this is awkward edge case where the function inputs are a single struct
304                // we need to force this argument into a tuple so it gets expanded to
305                // `((#name,))` this is currently necessary because
306                // internally `flatten_tokens` is called which removes the
307                // outermost `tuple` level and since `((#name))` is not
308                // a rust tuple it doesn't get wrapped into another tuple that will be peeled
309                // off by `flatten_tokens`
310                ParamType::Tuple(_) if fun.inputs.len() == 1 => {
311                    // make sure the tuple gets converted to `Token::Tuple`
312                    quote!((#name,))
313                }
314                _ => name,
315            }
316        });
317
318        match fun.inputs.len() {
319            0 => quote!(()),
320            1 => call_args.next().unwrap(),
321            _ => quote!(( #( #call_args ),* )),
322        }
323    }
324
325    /// Expands a single function with the given alias
326    fn expand_function(
327        &self,
328        function: &Function,
329        alias: Option<MethodAlias>,
330    ) -> Result<TokenStream> {
331        let name = &function.name;
332        let function_name = expand_function_name(function, alias.as_ref());
333        let selector = function.selector();
334
335        let selector_tokens = expand_selector(selector);
336
337        let contract_args = self.expand_contract_call_args(function);
338        let function_params =
339            self.expand_input_params(function)?.into_iter().map(|(name, ty)| quote! { #name: #ty });
340        let function_params = quote! { #( , #function_params )* };
341
342        let outputs = {
343            let mut out = self.expand_output_params(function)?;
344            match out.len() {
345                0 => quote!(()),
346                1 => out.pop().unwrap().1,
347                _ => {
348                    let iter = out.into_iter().map(|(_, ty)| ty);
349                    quote!(( #( #iter ),* ))
350                }
351            }
352        };
353
354        let doc_str =
355            format!("Calls the contract's `{name}` (0x{}) function", hex::encode(selector));
356
357        let ethers_contract = ethers_contract_crate();
358
359        Ok(quote! {
360            #[doc = #doc_str]
361            pub fn #function_name(&self #function_params) -> #ethers_contract::builders::ContractCall<M, #outputs> {
362                self.0.method_hash(#selector_tokens, #contract_args)
363                    .expect("method not found (this should never happen)")
364            }
365        })
366    }
367
368    /// Returns the method aliases, either configured by the user or determined
369    /// based on overloaded functions.
370    ///
371    /// In case of overloaded functions we would follow rust's general
372    /// convention of suffixing the function name with _with
373    ///
374    /// The first function or the function with the least amount of arguments should
375    /// be named as in the ABI, the following functions suffixed with:
376    /// `_with_ + additional_params[0].name + (_and_(additional_params[1+i].name))*`
377    fn get_method_aliases(&self) -> Result<BTreeMap<String, MethodAlias>> {
378        let mut aliases = self.method_aliases.clone();
379
380        // it might be the case that there are functions with different capitalization so we sort
381        // them all by lc name first
382        let mut all_functions = HashMap::new();
383        for function in self.abi.functions() {
384            all_functions
385                .entry(util::safe_snake_case_ident(&function.name))
386                .or_insert_with(Vec::new)
387                .push(function);
388        }
389
390        // find all duplicates, where no aliases where provided
391        for functions in all_functions.values() {
392            if functions.iter().filter(|f| !aliases.contains_key(&f.abi_signature())).count() <= 1 {
393                // no overloads, hence no conflicts
394                continue
395            }
396
397            let num_functions = functions.len();
398            // sort functions by number of inputs asc
399            let mut functions = functions.iter().enumerate().collect::<Vec<_>>();
400            functions.sort_by(|(_, f1), (_, f2)| f1.inputs.len().cmp(&f2.inputs.len()));
401
402            // the `functions` are now mapped with their index as defined in the ABI, but
403            // we always want the zero arg function (`log()`) to be `log0`
404            for (idx, (f_idx, _)) in functions.iter_mut().enumerate() {
405                *f_idx = idx;
406            }
407
408            // the first function will be the function with the least amount of inputs, like log()
409            // and is the baseline for the diff
410            let (first_fun_idx, first_fun) = functions[0];
411
412            // assuming here that if there is an overloaded function with nameless params like
413            // `log;, log(string); log(string, string)` `log()` it should also be
414            // aliased as well with its index to `log0`
415            let mut needs_alias_for_first_fun_using_idx = false;
416
417            // all the overloaded functions together with their diffs compare to the `first_fun`
418            let mut diffs = Vec::new();
419
420            /// helper function that checks if there are any conflicts due to parameter names
421            fn name_conflicts(idx: usize, diffs: &[(usize, Vec<&Param>, &&Function)]) -> bool {
422                let diff = &diffs.iter().find(|(i, _, _)| *i == idx).expect("diff exists").1;
423
424                for (_, other, _) in diffs.iter().filter(|(i, _, _)| *i != idx) {
425                    let (a, b) =
426                        if other.len() > diff.len() { (other, diff) } else { (diff, other) };
427
428                    if a.iter()
429                        .all(|d| b.iter().any(|o| o.name.to_snake_case() == d.name.to_snake_case()))
430                    {
431                        return true
432                    }
433                }
434                false
435            }
436            // compare each overloaded function with the `first_fun`
437            for (idx, overloaded_fun) in functions.into_iter().skip(1) {
438                // keep track of matched params
439                let mut already_matched_param_diff = HashSet::new();
440                // attempt to find diff in the input arguments
441                let mut diff = Vec::new();
442                let mut same_params = true;
443                for (idx, i1) in overloaded_fun.inputs.iter().enumerate() {
444                    // Find the first param that differs and hasn't already been matched as diff
445                    if let Some((pos, _)) = first_fun
446                        .inputs
447                        .iter()
448                        .enumerate()
449                        .filter(|(pos, _)| !already_matched_param_diff.contains(pos))
450                        .find(|(_, i2)| i1 != *i2)
451                    {
452                        already_matched_param_diff.insert(pos);
453                        diff.push(i1);
454                        same_params = false;
455                    } else {
456                        // check for cases like `log(string); log(string, string)` by keep track of
457                        // same order
458                        if same_params && idx + 1 > first_fun.inputs.len() {
459                            diff.push(i1);
460                        }
461                    }
462                }
463                diffs.push((idx, diff, overloaded_fun));
464            }
465
466            for (idx, diff, overloaded_fun) in &diffs {
467                let alias = match diff.len() {
468                    0 => {
469                        // this may happen if there are functions with different casing,
470                        // like `INDEX`and `index`
471
472                        // this should not happen since functions with same
473                        // name and inputs are illegal
474                        eyre::ensure!(
475                            overloaded_fun.name != first_fun.name,
476                            "Function with same name and parameter types defined twice: {}",
477                            overloaded_fun.name
478                        );
479
480                        let overloaded_id = overloaded_fun.name.to_snake_case();
481                        let first_fun_id = first_fun.name.to_snake_case();
482                        if first_fun_id != overloaded_id {
483                            // no conflict
484                            overloaded_id
485                        } else {
486                            let overloaded_alias = MethodAlias {
487                                function_name: util::safe_ident(&overloaded_fun.name),
488                                struct_name: util::safe_ident(&overloaded_fun.name),
489                            };
490                            aliases.insert(overloaded_fun.abi_signature(), overloaded_alias);
491
492                            let first_fun_alias = MethodAlias {
493                                function_name: util::safe_ident(&first_fun.name),
494                                struct_name: util::safe_ident(&first_fun.name),
495                            };
496                            aliases.insert(first_fun.abi_signature(), first_fun_alias);
497                            continue
498                        }
499                    }
500                    1 => {
501                        // single additional input params
502                        if diff[0].name.is_empty() ||
503                            num_functions > NAME_ALIASING_OVERLOADED_FUNCTIONS_CAP ||
504                            name_conflicts(*idx, &diffs)
505                        {
506                            needs_alias_for_first_fun_using_idx = true;
507                            format!("{}{idx}", overloaded_fun.name.to_snake_case())
508                        } else {
509                            format!(
510                                "{}_with_{}",
511                                overloaded_fun.name.to_snake_case(),
512                                diff[0].name.to_snake_case()
513                            )
514                        }
515                    }
516                    _ => {
517                        if diff.iter().any(|d| d.name.is_empty()) ||
518                            num_functions > NAME_ALIASING_OVERLOADED_FUNCTIONS_CAP ||
519                            name_conflicts(*idx, &diffs)
520                        {
521                            needs_alias_for_first_fun_using_idx = true;
522                            format!("{}{idx}", overloaded_fun.name.to_snake_case())
523                        } else {
524                            // 1 + n additional input params
525                            let and = diff
526                                .iter()
527                                .skip(1)
528                                .map(|i| i.name.to_snake_case())
529                                .collect::<Vec<_>>()
530                                .join("_and_");
531                            format!(
532                                "{}_with_{}_and_{}",
533                                overloaded_fun.name.to_snake_case(),
534                                diff[0].name.to_snake_case(),
535                                and
536                            )
537                        }
538                    }
539                };
540                let alias = MethodAlias::new(&alias);
541                aliases.insert(overloaded_fun.abi_signature(), alias);
542            }
543
544            if needs_alias_for_first_fun_using_idx {
545                // insert an alias for the root duplicated call
546                let prev_alias = format!("{}{first_fun_idx}", first_fun.name.to_snake_case());
547
548                let alias = MethodAlias::new(&prev_alias);
549
550                aliases.insert(first_fun.abi_signature(), alias);
551            }
552        }
553
554        // we have to handle the edge cases with underscore prefix and suffix that would get
555        // stripped by Inflector::to_snake_case/pascalCase if there is another function that
556        // would collide we manually add an alias for it eg. abi = ["_a(), a(), a_(),
557        // _a_()"] will generate identical rust functions
558        for (name, functions) in self.abi.functions.iter() {
559            if name.starts_with('_') || name.ends_with('_') {
560                let ident = name.trim_matches('_').trim_end_matches('_');
561                // check for possible collisions after Inflector would remove the underscores
562                if self.abi.functions.contains_key(ident) {
563                    for function in functions {
564                        if let Entry::Vacant(entry) = aliases.entry(function.abi_signature()) {
565                            // use the full name as alias
566                            entry.insert(MethodAlias::new(name.as_str()));
567                        }
568                    }
569                }
570            }
571        }
572        Ok(aliases)
573    }
574}
575
576fn expand_selector(selector: Selector) -> TokenStream {
577    let bytes = selector.iter().copied().map(Literal::u8_unsuffixed);
578    quote!([ #( #bytes ),* ])
579}
580
581/// Represents the aliases to use when generating method related elements
582#[derive(Debug, Clone)]
583pub struct MethodAlias {
584    pub function_name: Ident,
585    pub struct_name: Ident,
586}
587
588impl MethodAlias {
589    pub fn new(alias: &str) -> Self {
590        MethodAlias {
591            function_name: util::safe_snake_case_ident(alias),
592            struct_name: util::safe_pascal_case_ident(alias),
593        }
594    }
595}
596
597fn expand_function_name(function: &Function, alias: Option<&MethodAlias>) -> Ident {
598    if let Some(alias) = alias {
599        alias.function_name.clone()
600    } else {
601        util::safe_ident(&util::safe_snake_case(&function.name))
602    }
603}
604
605/// Expands the name of a struct by a postfix
606fn expand_struct_name_postfix(
607    function: &Function,
608    alias: Option<&MethodAlias>,
609    postfix: &str,
610) -> Ident {
611    let name = if let Some(alias) = alias {
612        format!("{}{postfix}", alias.struct_name)
613    } else {
614        format!("{}{postfix}", util::safe_pascal_case(&function.name))
615    };
616    util::ident(&name)
617}
618
619/// Expands to the name of the call struct
620fn expand_call_struct_name(function: &Function, alias: Option<&MethodAlias>) -> Ident {
621    expand_struct_name_postfix(function, alias, "Call")
622}
623
624/// Expands to the name of the return struct
625fn expand_return_struct_name(function: &Function, alias: Option<&MethodAlias>) -> Ident {
626    expand_struct_name_postfix(function, alias, "Return")
627}
628
629/// Expands to the name of the call struct
630fn expand_call_struct_variant_name(function: &Function, alias: Option<&MethodAlias>) -> Ident {
631    if let Some(alias) = alias {
632        alias.struct_name.clone()
633    } else {
634        util::safe_ident(&util::safe_pascal_case(&function.name))
635    }
636}
637
638#[cfg(test)]
639mod tests {
640    use super::*;
641
642    fn expand_fn_outputs(outputs: &[Param]) -> Result<TokenStream> {
643        match outputs.len() {
644            0 => Ok(quote! { () }),
645            1 => types::expand(&outputs[0].kind),
646            _ => {
647                let types = outputs
648                    .iter()
649                    .map(|param| types::expand(&param.kind))
650                    .collect::<Result<Vec<_>>>()?;
651                Ok(quote! { (#( #types ),*) })
652            }
653        }
654    }
655
656    // packs the argument in a tuple to be used for the contract call
657    fn expand_inputs_call_arg(inputs: &[Param]) -> TokenStream {
658        let names = inputs
659            .iter()
660            .enumerate()
661            .map(|(i, param)| {
662                let name = util::expand_input_name(i, &param.name);
663                match param.kind {
664                    // this is awkward edge case where the function inputs are a single struct
665                    // we need to force this argument into a tuple so it gets expanded to
666                    // `((#name,))` this is currently necessary because
667                    // internally `flatten_tokens` is called which removes the outermost `tuple`
668                    // level and since `((#name))` is not a rust tuple it
669                    // doesn't get wrapped into another tuple that will be peeled off by
670                    // `flatten_tokens`
671                    ParamType::Tuple(_) if inputs.len() == 1 => {
672                        // make sure the tuple gets converted to `Token::Tuple`
673                        quote! {(#name,)}
674                    }
675                    _ => name,
676                }
677            })
678            .collect::<Vec<TokenStream>>();
679        match names.len() {
680            0 => quote! { () },
681            1 => quote! { #( #names )* },
682            _ => quote! { ( #(#names, )* ) },
683        }
684    }
685
686    // converts the function params to name/type pairs
687    fn expand_inputs(inputs: &[Param]) -> Result<TokenStream> {
688        let params = inputs
689            .iter()
690            .enumerate()
691            .map(|(i, param)| {
692                let name = util::expand_input_name(i, &param.name);
693                let kind = types::expand(&param.kind)?;
694                Ok(quote! { #name: #kind })
695            })
696            .collect::<Result<Vec<_>>>()?;
697        Ok(quote! { #( , #params )* })
698    }
699
700    #[test]
701    fn test_expand_inputs_call_arg() {
702        // no inputs
703        let params = vec![];
704        let token_stream = expand_inputs_call_arg(&params);
705        assert_eq!(token_stream.to_string(), "()");
706
707        // single input
708        let params = vec![Param {
709            name: "arg_a".to_string(),
710            kind: ParamType::Address,
711            internal_type: None,
712        }];
713        let token_stream = expand_inputs_call_arg(&params);
714        assert_eq!(token_stream.to_string(), "arg_a");
715
716        // two inputs
717        let params = vec![
718            Param { name: "arg_a".to_string(), kind: ParamType::Address, internal_type: None },
719            Param {
720                name: "arg_b".to_string(),
721                kind: ParamType::Uint(256usize),
722                internal_type: None,
723            },
724        ];
725        let token_stream = expand_inputs_call_arg(&params);
726        assert_eq!(token_stream.to_string(), "(arg_a , arg_b ,)");
727
728        // three inputs
729        let params = vec![
730            Param { name: "arg_a".to_string(), kind: ParamType::Address, internal_type: None },
731            Param {
732                name: "arg_b".to_string(),
733                kind: ParamType::Uint(128usize),
734                internal_type: None,
735            },
736            Param { name: "arg_c".to_string(), kind: ParamType::Bool, internal_type: None },
737        ];
738        let token_stream = expand_inputs_call_arg(&params);
739        assert_eq!(token_stream.to_string(), "(arg_a , arg_b , arg_c ,)");
740    }
741
742    #[test]
743    fn expand_inputs_empty() {
744        assert_quote!(expand_inputs(&[]).unwrap().to_string(), {},);
745    }
746
747    #[test]
748    fn test_expand_inputs() {
749        assert_quote!(
750            expand_inputs(&[
751                Param {
752                    name: "a".to_string(),
753                    kind: ParamType::Bool,
754                    internal_type: None,
755                },
756                Param {
757                    name: "b".to_string(),
758                    kind: ParamType::Address,
759                    internal_type: None,
760                },
761            ])
762            .unwrap(),
763            { , a: bool, b: ::ethers_core::types::Address },
764        );
765    }
766
767    #[test]
768    fn expand_fn_outputs_empty() {
769        assert_quote!(expand_fn_outputs(&[]).unwrap(), { () });
770    }
771
772    #[test]
773    fn expand_fn_outputs_single() {
774        assert_quote!(
775            expand_fn_outputs(&[Param {
776                name: "a".to_string(),
777                kind: ParamType::Bool,
778                internal_type: None,
779            }])
780            .unwrap(),
781            { bool },
782        );
783    }
784
785    #[test]
786    fn expand_fn_outputs_multiple() {
787        assert_quote!(
788            expand_fn_outputs(&[
789                Param { name: "a".to_string(), kind: ParamType::Bool, internal_type: None },
790                Param { name: "b".to_string(), kind: ParamType::Address, internal_type: None },
791            ])
792            .unwrap(),
793            { (bool, ::ethers_core::types::Address) },
794        );
795    }
796}