ethers_core/abi/
struct_def.rs

1//! Solidity struct definition parsing support
2use crate::abi::{
3    error::{bail, format_err, Result},
4    human_readable::{is_whitespace, parse_identifier},
5    HumanReadableParser, ParamType,
6};
7
8/// A field declaration inside a struct
9#[derive(Debug, Clone, PartialEq)]
10pub struct FieldDeclaration {
11    pub name: String,
12    pub ty: FieldType,
13}
14
15impl FieldDeclaration {
16    pub fn new(name: String, ty: FieldType) -> Self {
17        Self { name, ty }
18    }
19
20    pub fn name(&self) -> &str {
21        &self.name
22    }
23
24    pub fn r#type(&self) -> &FieldType {
25        &self.ty
26    }
27}
28
29/// A field declaration inside a struct
30#[derive(Debug, Clone, PartialEq)]
31pub enum FieldType {
32    /// Represents elementary types, see [`ParamType`]
33    ///
34    /// Note: tuples will be treated as rust tuples
35    Elementary(ParamType),
36    /// A non elementary type field, treated as user-defined struct
37    Struct(StructFieldType),
38    /// Mapping
39    Mapping(Box<MappingType>),
40}
41
42impl FieldType {
43    /// Whether this field is an elementary [`ParamType`].
44    pub fn is_elementary(&self) -> bool {
45        matches!(self, FieldType::Elementary(_))
46    }
47
48    /// Whether this field is a user-defined struct.
49    pub fn is_struct(&self) -> bool {
50        matches!(self, FieldType::Struct(_))
51    }
52
53    /// Whether this field is a mapping.
54    pub fn is_mapping(&self) -> bool {
55        matches!(self, FieldType::Mapping(_))
56    }
57
58    pub(crate) fn as_struct(&self) -> Option<&StructFieldType> {
59        match self {
60            FieldType::Struct(s) => Some(s),
61            _ => None,
62        }
63    }
64}
65
66#[derive(Debug, Clone, PartialEq)]
67pub struct MappingType {
68    /// key types can be elementary and `bytes` and `string`
69    ///
70    /// Valid `ParamType` variants are:
71    ///     `Address`, `Bytes`, `Int`, `UInt`, `Bool`, `String`, `FixedBytes`,
72    key_type: ParamType,
73    /// The value type of this mapping
74    value_type: FieldType,
75}
76
77/// Represents a elementary field declaration inside a struct with a : `int x`
78#[derive(Debug, Clone, PartialEq)]
79pub struct StructFieldDeclaration {
80    /// The name of the field
81    name: String,
82    /// The type of the field
83    ty: StructFieldType,
84}
85
86/// How the type of a struct field is referenced
87#[derive(Debug, Clone, PartialEq, Eq)]
88pub struct StructType {
89    /// The name of the struct (or rather the name of the rust type)
90    name: String,
91    /// All previous projections up until the name
92    ///
93    /// For `MostOuter.Outer.<name>` this is `vec!["MostOuter", "Outer"]`
94    projections: Vec<String>,
95}
96
97impl StructType {
98    pub fn new(name: String, projections: Vec<String>) -> Self {
99        Self { name, projections }
100    }
101
102    pub fn name(&self) -> &str {
103        &self.name
104    }
105}
106
107/// Represents the type of a field in a struct
108#[derive(Debug, Clone, PartialEq)]
109pub enum StructFieldType {
110    /// A non elementary type field, represents a user defined struct
111    Type(StructType),
112    // Array of user defined type
113    Array(Box<StructFieldType>),
114    // Array with fixed size of user defined type
115    FixedArray(Box<StructFieldType>, usize),
116}
117
118impl StructFieldType {
119    pub fn name(&self) -> &str {
120        match self {
121            StructFieldType::Type(ty) => &ty.name,
122            StructFieldType::Array(ty) => ty.name(),
123            StructFieldType::FixedArray(ty, _) => ty.name(),
124        }
125    }
126
127    pub fn projections(&self) -> &[String] {
128        match self {
129            StructFieldType::Type(ty) => &ty.projections,
130            StructFieldType::Array(ty) => ty.projections(),
131            StructFieldType::FixedArray(ty, _) => ty.projections(),
132        }
133    }
134
135    pub fn identifier(&self) -> String {
136        let name = self.name();
137        let path = self.projections().join(".");
138        if path.is_empty() {
139            name.to_string()
140        } else {
141            format!("{path}.{name}")
142        }
143    }
144
145    pub fn as_param(&self, tuple: ParamType) -> ParamType {
146        match self {
147            StructFieldType::Type(_) => tuple,
148            StructFieldType::Array(ty) => ty.as_param(ParamType::Array(Box::new(tuple))),
149            StructFieldType::FixedArray(ty, size) => {
150                ty.as_param(ParamType::FixedArray(Box::new(tuple), *size))
151            }
152        }
153    }
154
155    /// Parse a struct field declaration
156    ///
157    /// The parsed field is either a `Struct`, `StructArray` or `FixedStructArray`
158    pub fn parse(mut input: &str) -> Result<FieldType> {
159        let mut projections = Vec::new();
160
161        loop {
162            let ty = parse_identifier(&mut input)?;
163            let mut chars = input.chars();
164            match chars.next() {
165                None => {
166                    return Ok(FieldType::Struct(StructFieldType::Type(StructType {
167                        name: ty,
168                        projections,
169                    })))
170                }
171                Some(' ') | Some('\t') | Some('[') => {
172                    // array
173                    let mut size = String::new();
174                    loop {
175                        match chars.next() {
176                            None => bail!("Expected Array `{}`", input),
177                            Some(' ') | Some('\t') => {
178                                if !size.is_empty() {
179                                    bail!(
180                                        "Illegal whitespace in array size after `{}` in `{}`",
181                                        size,
182                                        input
183                                    )
184                                }
185                            }
186                            Some(']') => {
187                                let ty = StructType { name: ty, projections };
188
189                                return if size.is_empty() {
190                                    Ok(FieldType::Struct(StructFieldType::Array(Box::new(
191                                        StructFieldType::Type(ty),
192                                    ))))
193                                } else {
194                                    let size = size.parse().map_err(|_| {
195                                        format_err!("Illegal array size `{}` at `{}`", size, input)
196                                    })?;
197                                    Ok(FieldType::Struct(StructFieldType::FixedArray(
198                                        Box::new(StructFieldType::Type(ty)),
199                                        size,
200                                    )))
201                                }
202                            }
203                            Some(c) => {
204                                if c.is_numeric() {
205                                    size.push(c);
206                                } else {
207                                    bail!("Illegal char `{}` inner array `{}`", c, input)
208                                }
209                            }
210                        }
211                    }
212                }
213                Some('.') => {
214                    input = chars.as_str();
215                    projections.push(ty);
216                }
217                Some(c) => {
218                    bail!("Illegal char `{}` at `{}`", c, input)
219                }
220            }
221        }
222    }
223}
224
225/// Represents a solidity struct
226#[derive(Debug, Clone, PartialEq)]
227pub struct SolStruct {
228    pub name: String,
229    pub fields: Vec<FieldDeclaration>,
230}
231
232impl SolStruct {
233    /// Parse a solidity struct definition
234    ///
235    /// # Example
236    ///
237    /// ```
238    /// # use ethers_core::abi::SolStruct;
239    /// let s = SolStruct::parse("struct MyStruct { uint x; uint y;}").unwrap();
240    /// ```
241    pub fn parse(s: &str) -> Result<Self> {
242        let mut input = s.trim();
243        if !input.starts_with("struct ") {
244            bail!("Not a struct `{}`", input)
245        }
246        input = &input[6..];
247
248        let name = parse_identifier(&mut input)?;
249
250        let mut chars = input.chars();
251
252        loop {
253            match chars.next() {
254                None => bail!("Expected struct"),
255                Some('{') => {
256                    // strip opening and trailing curly bracket
257                    input = chars
258                        .as_str()
259                        .trim()
260                        .strip_suffix('}')
261                        .ok_or_else(|| format_err!("Expected closing `}}` in `{}`", s))?
262                        .trim_end();
263
264                    let fields = if input.is_empty() {
265                        Vec::new()
266                    } else {
267                        input
268                            .split(';')
269                            .filter(|s| !s.is_empty())
270                            .map(parse_struct_field)
271                            .collect::<Result<Vec<_>, _>>()?
272                    };
273                    return Ok(SolStruct { name, fields })
274                }
275                Some(' ') | Some('\t') => continue,
276                Some(c) => {
277                    bail!("Illegal char `{}` at `{}`", c, s)
278                }
279            }
280        }
281    }
282
283    /// Name of this struct
284    pub fn name(&self) -> &str {
285        &self.name
286    }
287
288    /// All the fields of this struct
289    pub fn fields(&self) -> &Vec<FieldDeclaration> {
290        &self.fields
291    }
292
293    /// Returns `true` if a field with an empty name exists
294    pub fn has_nameless_field(&self) -> bool {
295        self.fields.iter().any(|f| f.name.is_empty())
296    }
297
298    /// If the struct only consists of elementary fields, this will return `ParamType::Tuple` with
299    /// all those fields
300    pub fn as_tuple(&self) -> Option<ParamType> {
301        let mut params = Vec::with_capacity(self.fields.len());
302        for field in self.fields() {
303            if let FieldType::Elementary(ref param) = field.ty {
304                params.push(param.clone())
305            } else {
306                return None
307            }
308        }
309        Some(ParamType::Tuple(params))
310    }
311}
312
313/// Strips the identifier of field declaration from the input and returns it
314fn strip_field_identifier(input: &mut &str) -> Result<String> {
315    let mut iter = input.trim_end().rsplitn(2, is_whitespace);
316    let name = iter
317        .next()
318        .ok_or_else(|| format_err!("Expected field identifier"))
319        .map(|mut s| parse_identifier(&mut s))??;
320    *input =
321        iter.next().ok_or_else(|| format_err!("Expected field type in `{}`", input))?.trim_end();
322    Ok(name)
323}
324
325/// Parses a field definition such as `<type> <storageLocation>? <name>`
326fn parse_struct_field(s: &str) -> Result<FieldDeclaration> {
327    let mut input = s.trim_start();
328
329    if !input.starts_with("mapping") {
330        // strip potential defaults
331        input = input
332            .split('=')
333            .next()
334            .ok_or_else(|| format_err!("Expected field definition `{}`", s))?
335            .trim_end();
336    }
337    let name = strip_field_identifier(&mut input)?;
338    Ok(FieldDeclaration { name, ty: parse_field_type(input)? })
339}
340
341fn parse_field_type(s: &str) -> Result<FieldType> {
342    let mut input = s.trim_start();
343    if input.starts_with("mapping") {
344        return Ok(FieldType::Mapping(Box::new(parse_mapping(input)?)))
345    }
346    if input.ends_with(" payable") {
347        // special case for `address payable`
348        input = input[..input.len() - 7].trim_end();
349    }
350    if let Ok(ty) = HumanReadableParser::parse_type(input) {
351        Ok(FieldType::Elementary(ty))
352    } else {
353        // parsing elementary datatype failed, try struct
354        StructFieldType::parse(input.trim_end())
355    }
356}
357
358/// parse a mapping declaration
359fn parse_mapping(s: &str) -> Result<MappingType> {
360    let mut input = s.trim();
361    if !input.starts_with("mapping") {
362        bail!("Not a mapping `{}`", input)
363    }
364    input = input[7..].trim_start();
365    let mut iter = input.trim_start_matches('(').trim_end_matches(')').splitn(2, "=>");
366    let key_type = iter
367        .next()
368        .ok_or_else(|| format_err!("Expected mapping key type at `{}`", input))
369        .map(str::trim)
370        .map(HumanReadableParser::parse_type)??;
371
372    let is_illegal_ty = matches!(
373        &key_type,
374        ParamType::Array(_) | ParamType::FixedArray(_, _) | ParamType::Tuple(_)
375    );
376
377    if is_illegal_ty {
378        bail!("Expected elementary mapping key type at `{}` got {:?}", input, key_type)
379    }
380
381    let value_type = iter
382        .next()
383        .ok_or_else(|| format_err!("Expected mapping value type at `{}`", input))
384        .map(str::trim)
385        .map(parse_field_type)??;
386
387    Ok(MappingType { key_type, value_type })
388}
389
390#[cfg(test)]
391mod tests {
392    use super::*;
393
394    #[test]
395    fn can_parse_simple_struct() {
396        assert_eq!(
397            SolStruct::parse("struct MyStruct{uint256 x; uint256 y;}").unwrap(),
398            SolStruct {
399                name: "MyStruct".to_string(),
400                fields: vec![
401                    FieldDeclaration {
402                        name: "x".to_string(),
403                        ty: FieldType::Elementary(ParamType::Uint(256)),
404                    },
405                    FieldDeclaration {
406                        name: "y".to_string(),
407                        ty: FieldType::Elementary(ParamType::Uint(256)),
408                    },
409                ],
410            }
411        );
412    }
413
414    #[test]
415    fn can_parse_struct() {
416        assert_eq!(
417            SolStruct::parse("struct MyStruct{uint256 x; uint256 y; bytes[] _b; string[10] s; mapping(address => uint256) m;}").unwrap(),
418            SolStruct {
419                name: "MyStruct".to_string(),
420                fields: vec![
421                    FieldDeclaration {
422                        name: "x".to_string(),
423                        ty: FieldType::Elementary(ParamType::Uint(256)),
424                    },
425                    FieldDeclaration {
426                        name: "y".to_string(),
427                        ty: FieldType::Elementary(ParamType::Uint(256)),
428                    },
429                    FieldDeclaration {
430                        name: "_b".to_string(),
431                        ty: FieldType::Elementary(ParamType::Array(Box::new(ParamType::Bytes))),
432                    },
433                    FieldDeclaration {
434                        name: "s".to_string(),
435                        ty: FieldType::Elementary(ParamType::FixedArray(Box::new(ParamType::String), 10)),
436                    },
437                    FieldDeclaration {
438                        name: "m".to_string(),
439                        ty: FieldType::Mapping(Box::new(
440                            MappingType {
441                                key_type: ParamType::Address,
442                                value_type: FieldType::Elementary(ParamType::Uint(256))
443                            }
444                        )),
445                    },
446                ],
447            }
448        );
449    }
450
451    #[test]
452    fn can_parse_struct_projections() {
453        assert_eq!(
454            SolStruct::parse("struct MyStruct{uint256 x; Some.Other.Inner _other;}").unwrap(),
455            SolStruct {
456                name: "MyStruct".to_string(),
457                fields: vec![
458                    FieldDeclaration {
459                        name: "x".to_string(),
460                        ty: FieldType::Elementary(ParamType::Uint(256)),
461                    },
462                    FieldDeclaration {
463                        name: "_other".to_string(),
464                        ty: FieldType::Struct(StructFieldType::Type(StructType {
465                            name: "Inner".to_string(),
466                            projections: vec!["Some".to_string(), "Other".to_string()]
467                        })),
468                    },
469                ],
470            }
471        );
472    }
473
474    #[test]
475    fn can_parse_structs() {
476        [
477            "struct Demo {bytes  x; address payable d;}",
478            "struct Demo2 {bytes[10]  x; mapping(bool=> bool) d; int256 value;}",
479            "struct Struct { Other.MyStruct s;  bool voted;  address delegate; uint vote; }",
480        ]
481        .iter()
482        .for_each(|s| {
483            SolStruct::parse(s).unwrap();
484        });
485    }
486
487    #[test]
488    fn can_parse_mapping_type() {
489        assert_eq!(
490            parse_mapping("mapping(string=> string)").unwrap(),
491            MappingType {
492                key_type: ParamType::String,
493                value_type: FieldType::Elementary(ParamType::String)
494            }
495        );
496    }
497
498    #[test]
499    fn can_parse_nested_mappings() {
500        assert_eq!(
501            parse_mapping("mapping(string=> mapping(string=> string))").unwrap(),
502            MappingType {
503                key_type: ParamType::String,
504                value_type: FieldType::Mapping(Box::new(MappingType {
505                    key_type: ParamType::String,
506                    value_type: FieldType::Elementary(ParamType::String),
507                })),
508            }
509        );
510    }
511
512    #[test]
513    fn can_detect_illegal_mappings_key_type() {
514        [
515            "mapping(string[]=> mapping(string=> string))",
516            "mapping(bytes[10] => bool)",
517            "mapping(uint256[10] => bool)",
518            "mapping(Item=> bool)",
519            "mapping(Item[]=> mapping(address  => bool))",
520        ]
521        .iter()
522        .for_each(|s| {
523            parse_mapping(s).unwrap_err();
524        });
525    }
526
527    #[test]
528    fn can_parse_mappings() {
529        [
530            "mapping(string=> mapping(string=> string))",
531            "mapping(string=> mapping(string=> mapping(string=> mapping(string=> string))))",
532            "mapping(bool=> bool)",
533            "mapping(bytes32 => bool)",
534            "mapping(bytes=> bool)",
535            "mapping(uint256=> mapping(address  => bool))",
536        ]
537        .iter()
538        .for_each(|s| {
539            parse_mapping(s).unwrap();
540        });
541    }
542
543    #[test]
544    fn can_strip_field_ident() {
545        let mut s = "uint256 _myvar,
546                    ";
547        let name = strip_field_identifier(&mut s).unwrap();
548        assert_eq!("_myvar", name);
549        assert_eq!("uint256", s);
550    }
551}