ethers_contract_abigen/contract/
structs.rs

1//! Structs expansion
2
3use super::{types, Context};
4use crate::util;
5use ethers_core::{
6    abi::{
7        struct_def::{FieldDeclaration, FieldType, StructFieldType, StructType},
8        Component, HumanReadableParser, ParamType, RawAbi, SolStruct,
9    },
10    macros::ethers_contract_crate,
11};
12use eyre::{eyre, Result};
13use inflector::Inflector;
14use proc_macro2::TokenStream;
15use quote::quote;
16use std::collections::{HashMap, VecDeque};
17use syn::Ident;
18
19impl Context {
20    /// Generate corresponding types for structs parsed from a human readable ABI
21    ///
22    /// NOTE: This assumes that all structs that are potentially used as type for variable are
23    /// in fact present in the `AbiParser`, this is sound because `AbiParser::parse` would have
24    /// failed already
25    pub fn abi_structs(&self) -> Result<TokenStream> {
26        if self.human_readable {
27            self.gen_human_readable_structs()
28        } else {
29            self.gen_internal_structs()
30        }
31    }
32
33    /// In the event of type conflicts this allows for removing a specific struct type.
34    pub fn remove_struct(&mut self, name: &str) {
35        if self.human_readable {
36            self.abi_parser.structs.remove(name);
37        } else {
38            self.internal_structs.structs.remove(name);
39        }
40    }
41
42    /// Returns the type definition for the struct with the given name
43    pub fn struct_definition(&mut self, name: &str) -> Result<TokenStream> {
44        if self.human_readable {
45            self.generate_human_readable_struct(name)
46        } else {
47            self.generate_internal_struct(name)
48        }
49    }
50
51    /// Generates the type definition for the name that matches the given identifier
52    fn generate_internal_struct(&self, id: &str) -> Result<TokenStream> {
53        let sol_struct =
54            self.internal_structs.structs.get(id).ok_or_else(|| eyre!("Struct not found"))?;
55        let struct_name = self
56            .internal_structs
57            .rust_type_names
58            .get(id)
59            .ok_or_else(|| eyre!("No types found for {id}"))?;
60        let tuple = self
61            .internal_structs
62            .struct_tuples
63            .get(id)
64            .ok_or_else(|| eyre!("No types found for {id}"))?;
65        let types = if let ParamType::Tuple(types) = tuple { types } else { unreachable!() };
66        self.expand_internal_struct(struct_name, sol_struct, types)
67    }
68
69    /// Returns the `TokenStream` with all the internal structs extracted form the JSON ABI
70    fn gen_internal_structs(&self) -> Result<TokenStream> {
71        let mut structs = TokenStream::new();
72        let mut ids: Vec<_> = self.internal_structs.structs.keys().collect();
73        ids.sort();
74
75        for id in ids {
76            structs.extend(self.generate_internal_struct(id)?);
77        }
78        Ok(structs)
79    }
80
81    /// Expand all structs parsed from the internal types of the JSON ABI
82    fn expand_internal_struct(
83        &self,
84        name: &str,
85        sol_struct: &SolStruct,
86        types: &[ParamType],
87    ) -> Result<TokenStream> {
88        let mut fields = Vec::with_capacity(sol_struct.fields().len());
89
90        // determines whether we have enough info to create named fields
91        let is_tuple = sol_struct.has_nameless_field();
92
93        for field in sol_struct.fields() {
94            let ty = match field.r#type() {
95                FieldType::Elementary(ty) => types::expand(ty)?,
96                FieldType::Struct(struct_ty) => types::expand_struct_type(struct_ty),
97                FieldType::Mapping(_) => {
98                    eyre::bail!("Mapping types in struct `{name}` are not supported")
99                }
100            };
101
102            let field_name = if is_tuple {
103                TokenStream::new()
104            } else {
105                let field_name = util::safe_ident(&field.name().to_snake_case());
106                quote!(#field_name)
107            };
108            fields.push((field_name, ty));
109        }
110
111        let name = util::ident(name);
112
113        let struct_def = expand_struct(&name, &fields, is_tuple);
114
115        let sig = util::abi_signature_types(types);
116        let doc_str = format!("`{name}({sig})`");
117
118        let mut derives = self.expand_extra_derives();
119        util::derive_builtin_traits_struct(&self.internal_structs, sol_struct, types, &mut derives);
120
121        let ethers_contract = ethers_contract_crate();
122
123        Ok(quote! {
124            #[doc = #doc_str]
125            #[derive(Clone, #ethers_contract::EthAbiType, #ethers_contract::EthAbiCodec, #derives)]
126            pub #struct_def
127        })
128    }
129
130    fn generate_human_readable_struct(&self, name: &str) -> Result<TokenStream> {
131        let sol_struct =
132            self.abi_parser.structs.get(name).ok_or_else(|| eyre!("Struct `{name}` not found"))?;
133        let mut fields = Vec::with_capacity(sol_struct.fields().len());
134        let mut param_types = Vec::with_capacity(sol_struct.fields().len());
135        for field in sol_struct.fields() {
136            let field_name = util::safe_ident(&field.name().to_snake_case());
137            match field.r#type() {
138                FieldType::Elementary(ty) => {
139                    param_types.push(ty.clone());
140                    let ty = types::expand(ty)?;
141                    fields.push(quote! { pub #field_name: #ty });
142                }
143                FieldType::Struct(struct_ty) => {
144                    let ty = types::expand_struct_type(struct_ty);
145                    fields.push(quote! { pub #field_name: #ty });
146
147                    let name = struct_ty.name();
148                    let tuple = self
149                        .abi_parser
150                        .struct_tuples
151                        .get(name)
152                        .ok_or_else(|| eyre!("No types found for {name}"))?
153                        .clone();
154                    let tuple = ParamType::Tuple(tuple);
155
156                    param_types.push(struct_ty.as_param(tuple));
157                }
158                FieldType::Mapping(_) => {
159                    eyre::bail!("Mapping types in struct `{name}` are not supported")
160                }
161            }
162        }
163
164        let abi_signature = util::abi_signature(name, &param_types);
165
166        let name = util::ident(name);
167
168        let mut derives = self.expand_extra_derives();
169        util::derive_builtin_traits(&param_types, &mut derives, true, true);
170
171        let ethers_contract = ethers_contract_crate();
172
173        Ok(quote! {
174            #[doc = #abi_signature]
175            #[derive(Clone, #ethers_contract::EthAbiType, #ethers_contract::EthAbiCodec, #derives)]
176            pub struct #name {
177                #( #fields ),*
178            }
179        })
180    }
181
182    /// Expand all structs parsed from the human readable ABI
183    fn gen_human_readable_structs(&self) -> Result<TokenStream> {
184        let mut structs = TokenStream::new();
185        let mut names: Vec<_> = self.abi_parser.structs.keys().collect();
186        names.sort();
187        for name in names {
188            structs.extend(self.generate_human_readable_struct(name)?);
189        }
190        Ok(structs)
191    }
192}
193
194/// Helper to match `ethabi::Param`s with structs and nested structs
195///
196/// This is currently used to get access to all the unique solidity structs used as function
197/// in/output until `ethabi` supports it as well.
198#[derive(Debug, Clone, Default)]
199pub struct InternalStructs {
200    /// (function name, param name) -> struct which are the identifying properties we get the name
201    /// from ethabi.
202    pub(crate) function_params: HashMap<(String, String), String>,
203
204    /// (function name) -> `Vec<structs>` all structs the function returns
205    pub(crate) outputs: HashMap<String, Vec<String>>,
206
207    /// (event name, idx) -> struct which are the identifying properties we get the name
208    /// from ethabi.
209    ///
210    /// Note: we need to map the index of the event here because events can contain nameless inputs
211    pub(crate) event_params: HashMap<(String, usize), String>,
212
213    /// All the structs extracted from the abi with their identifier as key
214    pub(crate) structs: HashMap<String, SolStruct>,
215
216    /// solidity structs as tuples
217    pub(crate) struct_tuples: HashMap<String, ParamType>,
218
219    /// Contains the names for the rust types (id -> rust type name)
220    pub(crate) rust_type_names: HashMap<String, String>,
221}
222
223impl InternalStructs {
224    /// Creates a new instance with a filled type mapping table based on the abi
225    pub fn new(abi: RawAbi) -> Self {
226        let mut top_level_internal_types = HashMap::new();
227        let mut function_params = HashMap::new();
228        let mut outputs = HashMap::new();
229        let mut event_params = HashMap::new();
230
231        for item in abi
232            .into_iter()
233            .filter(|item| matches!(item.type_field.as_str(), "constructor" | "function" | "event"))
234        {
235            let is_event = item.type_field == "event";
236
237            let name = match item.name {
238                None if item.type_field == "constructor" => Some("constructor".to_owned()),
239                other => other,
240            };
241
242            if let Some(name) = name {
243                for (idx, input) in item.inputs.into_iter().enumerate() {
244                    if let Some(ty) = input
245                        .internal_type
246                        .as_deref()
247                        .filter(|ty| ty.starts_with("struct "))
248                        .map(struct_type_identifier)
249                    {
250                        if is_event {
251                            event_params.insert((name.clone(), idx), ty.to_string());
252                        } else {
253                            function_params
254                                .insert((name.clone(), input.name.clone()), ty.to_string());
255                        }
256                        top_level_internal_types.insert(ty.to_string(), input);
257                    }
258                }
259
260                if is_event {
261                    // no outputs in an event
262                    continue
263                }
264
265                let mut output_structs = Vec::new();
266                for output in item.outputs {
267                    if let Some(ty) = output
268                        .internal_type
269                        .as_deref()
270                        .filter(|ty| ty.starts_with("struct "))
271                        .map(struct_type_identifier)
272                    {
273                        output_structs.push(ty.to_string());
274                        top_level_internal_types.insert(ty.to_string(), output);
275                    }
276                }
277                outputs.insert(name, output_structs);
278            }
279        }
280
281        // turn each top level internal type (function input/output) and their nested types
282        // into a struct will create all structs
283        let mut structs = HashMap::new();
284        for component in top_level_internal_types.values() {
285            insert_structs(&mut structs, component);
286        }
287
288        // determine the `ParamType` representation of each struct
289        let struct_tuples = resolve_struct_tuples(&structs);
290
291        // name -> (id, projections)
292        let mut type_names: HashMap<String, (String, Vec<String>)> =
293            HashMap::with_capacity(structs.len());
294        for id in structs.keys() {
295            let name = struct_type_name(id).to_pascal_case();
296            let projections = struct_type_projections(id);
297            insert_rust_type_name(&mut type_names, name, projections, id.clone());
298        }
299
300        Self {
301            function_params,
302            outputs,
303            structs,
304            event_params,
305            struct_tuples,
306            rust_type_names: type_names
307                .into_iter()
308                .map(|(rust_name, (id, _))| (id, rust_name))
309                .collect(),
310        }
311    }
312
313    /// Returns the name of the rust type that will be generated if the given input is a struct
314    /// NOTE: this does not account for arrays or fixed arrays
315    pub fn get_function_input_struct_type(&self, function: &str, input: &str) -> Option<&str> {
316        self.get_function_input_struct_solidity_id(function, input)
317            .and_then(|id| self.rust_type_names.get(id))
318            .map(String::as_str)
319    }
320
321    /// Returns solidity type identifier as it's used in the ABI.
322    pub fn get_function_input_struct_solidity_id(
323        &self,
324        function: &str,
325        input: &str,
326    ) -> Option<&str> {
327        let key = (function.to_string(), input.to_string());
328        self.function_params.get(&key).map(String::as_str)
329    }
330
331    /// Returns the name of the rust type that will be generated if the given input is a struct
332    /// This takes the index of event's parameter instead of the parameter's name like
333    /// [`Self::get_function_input_struct_type`] does because we can't rely on the name since events
334    /// support nameless parameters NOTE: this does not account for arrays or fixed arrays
335    pub fn get_event_input_struct_type(&self, event: &str, idx: usize) -> Option<&str> {
336        self.get_event_input_struct_solidity_id(event, idx)
337            .and_then(|id| self.rust_type_names.get(id))
338            .map(String::as_str)
339    }
340
341    /// Returns the type identifier as it's used in the solidity ABI
342    pub fn get_event_input_struct_solidity_id(&self, event: &str, idx: usize) -> Option<&str> {
343        let key = (event.to_string(), idx);
344        self.event_params.get(&key).map(String::as_str)
345    }
346
347    /// Returns the name of the rust type that will be generated if the given output is a struct
348    /// NOTE: this does not account for arrays or fixed arrays
349    pub fn get_function_output_struct_type(
350        &self,
351        function: &str,
352        internal_type: &str,
353    ) -> Option<&str> {
354        self.get_function_output_struct_solidity_id(function, internal_type)
355            .and_then(|id| self.rust_type_names.get(id))
356            .map(String::as_str)
357    }
358
359    /// Returns the name of the rust type that will be generated if the given output is a struct
360    /// NOTE: this does not account for arrays or fixed arrays
361    pub fn get_function_output_struct_solidity_id(
362        &self,
363        function: &str,
364        internal_type: &str,
365    ) -> Option<&str> {
366        self.outputs
367            .get(function)
368            .and_then(|outputs| {
369                outputs.iter().find(|s| s.as_str() == struct_type_identifier(internal_type))
370            })
371            .map(String::as_str)
372    }
373
374    /// Returns the name of the rust type for the type
375    pub fn get_struct_type(&self, internal_type: &str) -> Option<&str> {
376        self.rust_type_names.get(struct_type_identifier(internal_type)).map(String::as_str)
377    }
378
379    /// Returns the mapping table of abi `internal type identifier -> rust type`
380    pub fn rust_type_names(&self) -> &HashMap<String, String> {
381        &self.rust_type_names
382    }
383
384    /// Returns all the solidity struct types
385    ///
386    /// These are grouped by their case-sensitive type identifiers extracted from the ABI.
387    pub fn structs_types(&self) -> &HashMap<String, SolStruct> {
388        &self.structs
389    }
390}
391
392/// This will determine the name of the rust type and will make sure that possible collisions are
393/// resolved by adjusting the actual Rust name of the structure, e.g. `LibraryA.Point` and
394/// `LibraryB.Point` to `LibraryAPoint` and `LibraryBPoint`.
395fn insert_rust_type_name(
396    type_names: &mut HashMap<String, (String, Vec<String>)>,
397    mut name: String,
398    mut projections: Vec<String>,
399    id: String,
400) {
401    if let Some((other_id, mut other_projections)) = type_names.remove(&name) {
402        let mut other_name = name.clone();
403        // name collision `A.name` `B.name`, rename to `AName`, `BName`
404        if !other_projections.is_empty() {
405            other_name = format!("{}{other_name}", other_projections.remove(0).to_pascal_case());
406        }
407        insert_rust_type_name(type_names, other_name, other_projections, other_id);
408
409        if !projections.is_empty() {
410            name = format!("{}{name}", projections.remove(0).to_pascal_case());
411        }
412        insert_rust_type_name(type_names, name, projections, id);
413    } else {
414        type_names.insert(name, (id, projections));
415    }
416}
417
418/// Tries to determine the `ParamType::Tuple` for every struct.
419///
420/// If a structure has nested structures, these must be determined first, essentially starting with
421/// structures consisting of only elementary types before moving on to higher level structures, for
422/// example `Proof {point: Point}, Point {x:int, y:int}` start by converting Point into a tuple of
423/// `x` and `y` and then substituting `point` with this within `Proof`.
424fn resolve_struct_tuples(all_structs: &HashMap<String, SolStruct>) -> HashMap<String, ParamType> {
425    let mut params = HashMap::new();
426    let mut structs: VecDeque<_> = all_structs.iter().collect();
427
428    // keep track of how often we retried nested structs
429    let mut sequential_retries = 0;
430    'outer: while let Some((id, ty)) = structs.pop_front() {
431        if sequential_retries > structs.len() {
432            break
433        }
434        if let Some(tuple) = ty.as_tuple() {
435            params.insert(id.to_string(), tuple);
436        } else {
437            // try to substitute all nested struct types with their `ParamTypes`
438            let mut struct_params = Vec::with_capacity(ty.fields.len());
439            for field in ty.fields() {
440                match field.ty {
441                    FieldType::Elementary(ref param) => {
442                        struct_params.push(param.clone());
443                    }
444                    FieldType::Struct(ref field_ty) => {
445                        // nested struct
446                        let ty_id = field_ty.identifier();
447                        if let Some(nested) = params.get(&ty_id).cloned() {
448                            match field_ty {
449                                StructFieldType::Type(_) => struct_params.push(nested),
450                                StructFieldType::Array(_) => {
451                                    struct_params.push(ParamType::Array(Box::new(nested)));
452                                }
453                                StructFieldType::FixedArray(_, size) => {
454                                    struct_params
455                                        .push(ParamType::FixedArray(Box::new(nested), *size));
456                                }
457                            }
458                        } else {
459                            // struct field needs to be resolved first
460                            structs.push_back((id, ty));
461                            sequential_retries += 1;
462                            continue 'outer
463                        }
464                    }
465                    _ => {
466                        unreachable!("mapping types are unsupported")
467                    }
468                }
469            }
470            params.insert(id.to_string(), ParamType::Tuple(struct_params));
471        }
472
473        // we resolved a new param, so we can try all again
474        sequential_retries = 0;
475    }
476    params
477}
478
479/// turns the tuple component into a struct if it's still missing in the map, including all inner
480/// structs
481fn insert_structs(structs: &mut HashMap<String, SolStruct>, tuple: &Component) {
482    if let Some(internal_ty) = tuple.internal_type.as_ref() {
483        let ident = struct_type_identifier(internal_ty);
484        if structs.contains_key(ident) {
485            return
486        }
487        if let Some(fields) = tuple
488            .components
489            .iter()
490            .map(|f| {
491                HumanReadableParser::parse_type(&f.type_field)
492                    .ok()
493                    .and_then(|kind| field(structs, f, kind))
494            })
495            .collect::<Option<Vec<_>>>()
496        {
497            let s = SolStruct { name: ident.to_string(), fields };
498            structs.insert(ident.to_string(), s);
499        }
500    }
501}
502
503/// Determines the type of the field component
504fn field(
505    structs: &mut HashMap<String, SolStruct>,
506    field_component: &Component,
507    kind: ParamType,
508) -> Option<FieldDeclaration> {
509    match kind {
510        ParamType::Array(ty) => {
511            let FieldDeclaration { ty, .. } = field(structs, field_component, *ty)?;
512            match ty {
513                FieldType::Elementary(kind) => {
514                    // this arm represents all the elementary types like address, uint...
515                    Some(FieldDeclaration::new(
516                        field_component.name.clone(),
517                        FieldType::Elementary(ParamType::Array(Box::new(kind))),
518                    ))
519                }
520                FieldType::Struct(ty) => Some(FieldDeclaration::new(
521                    field_component.name.clone(),
522                    FieldType::Struct(StructFieldType::Array(Box::new(ty))),
523                )),
524                _ => {
525                    unreachable!("no mappings types support as function inputs or outputs")
526                }
527            }
528        }
529        ParamType::FixedArray(ty, size) => {
530            let FieldDeclaration { ty, .. } = field(structs, field_component, *ty)?;
531            match ty {
532                FieldType::Elementary(kind) => {
533                    // this arm represents all the elementary types like address, uint...
534                    Some(FieldDeclaration::new(
535                        field_component.name.clone(),
536                        FieldType::Elementary(ParamType::FixedArray(Box::new(kind), size)),
537                    ))
538                }
539                FieldType::Struct(ty) => Some(FieldDeclaration::new(
540                    field_component.name.clone(),
541                    FieldType::Struct(StructFieldType::FixedArray(Box::new(ty), size)),
542                )),
543                _ => {
544                    unreachable!("no mappings types support as function inputs or outputs")
545                }
546            }
547        }
548        ParamType::Tuple(_) => {
549            insert_structs(structs, field_component);
550            let internal_type = field_component.internal_type.as_ref()?;
551            let ty = struct_type_identifier(internal_type);
552            // split the identifier into the name and all projections:
553            // `A.B.C.name` -> name, [A,B,C]
554            let mut idents = ty.rsplit('.');
555            let name = idents.next().unwrap().to_string();
556            let projections = idents.rev().map(str::to_string).collect();
557            Some(FieldDeclaration::new(
558                field_component.name.clone(),
559                FieldType::Struct(StructFieldType::Type(StructType::new(name, projections))),
560            ))
561        }
562        elementary => Some(FieldDeclaration::new(
563            field_component.name.clone(),
564            FieldType::Elementary(elementary),
565        )),
566    }
567}
568
569/// `struct Pairing.G2Point[]` -> `G2Point`
570fn struct_type_name(name: &str) -> &str {
571    struct_type_identifier(name).rsplit('.').next().unwrap()
572}
573
574/// `struct Pairing.G2Point[]` -> `Pairing.G2Point`
575fn struct_type_identifier(name: &str) -> &str {
576    name.trim_start_matches("struct ").split('[').next().unwrap()
577}
578
579/// `struct Pairing.Nested.G2Point[]` -> `[Pairing, Nested]`
580fn struct_type_projections(name: &str) -> Vec<String> {
581    let id = struct_type_identifier(name);
582    let mut iter = id.rsplit('.');
583    iter.next();
584    iter.rev().map(str::to_string).collect()
585}
586
587pub(crate) fn expand_struct(
588    name: &Ident,
589    fields: &[(TokenStream, TokenStream)],
590    is_tuple: bool,
591) -> TokenStream {
592    _expand_struct(name, fields.iter().map(|(a, b)| (a, b, false)), is_tuple)
593}
594
595pub(crate) fn expand_event_struct(
596    name: &Ident,
597    fields: &[(TokenStream, TokenStream, bool)],
598    is_tuple: bool,
599) -> TokenStream {
600    _expand_struct(name, fields.iter().map(|(a, b, c)| (a, b, *c)), is_tuple)
601}
602
603fn _expand_struct<'a>(
604    name: &Ident,
605    fields: impl Iterator<Item = (&'a TokenStream, &'a TokenStream, bool)>,
606    is_tuple: bool,
607) -> TokenStream {
608    let fields = fields.map(|(field, ty, indexed)| {
609        (field, ty, if indexed { Some(quote!(#[ethevent(indexed)])) } else { None })
610    });
611    let fields = if let Some(0) = fields.size_hint().1 {
612        // unit struct
613        quote!(;)
614    } else if is_tuple {
615        // tuple struct
616        let fields = fields.map(|(_, ty, indexed)| quote!(#indexed pub #ty));
617        quote!(( #( #fields ),* );)
618    } else {
619        // struct
620        let fields = fields.map(|(field, ty, indexed)| quote!(#indexed pub #field: #ty));
621        quote!({ #( #fields, )* })
622    };
623
624    quote!(struct #name #fields)
625}
626
627#[cfg(test)]
628mod tests {
629    use super::*;
630
631    #[test]
632    fn can_determine_structs() {
633        const VERIFIER_ABI: &str =
634            include_str!("../../../tests/solidity-contracts/verifier_abi.json");
635        let abi = serde_json::from_str::<RawAbi>(VERIFIER_ABI).unwrap();
636
637        let _internal = InternalStructs::new(abi);
638    }
639}