alloy_dyn_abi/eip712/
parser.rs

1//! EIP-712 specific parsing structures.
2
3// TODO: move to `sol-type-parser`
4
5use crate::{
6    eip712::resolver::{PropertyDef, TypeDef},
7    Error,
8};
9use alloc::vec::Vec;
10use parser::{Error as TypeParserError, TypeSpecifier};
11
12/// A property is a type and a name. Of the form `type name`. E.g.
13/// `uint256 foo` or `(MyStruct[23],bool) bar`.
14#[derive(Clone, Debug, PartialEq, Eq)]
15pub struct PropDef<'a> {
16    /// The prop type specifier.
17    pub ty: TypeSpecifier<'a>,
18    /// The prop name.
19    pub name: &'a str,
20}
21
22impl PropDef<'_> {
23    /// Convert to an owned `PropertyDef`
24    pub fn to_owned(&self) -> PropertyDef {
25        PropertyDef::new(self.ty.span, self.name).unwrap()
26    }
27}
28
29impl<'a> TryFrom<&'a str> for PropDef<'a> {
30    type Error = Error;
31
32    #[inline]
33    fn try_from(input: &'a str) -> Result<Self, Self::Error> {
34        Self::parse(input)
35    }
36}
37
38impl<'a> PropDef<'a> {
39    /// Parse a string into property definition.
40    pub fn parse(input: &'a str) -> Result<Self, Error> {
41        let (ty, name) =
42            input.rsplit_once(' ').ok_or_else(|| Error::invalid_property_def(input))?;
43        Ok(PropDef { ty: ty.trim().try_into()?, name: name.trim() })
44    }
45}
46
47/// Represents a single component type in an EIP-712 `encodeType` type string.
48///
49/// <https://eips.ethereum.org/EIPS/eip-712#definition-of-encodetype>
50#[derive(Clone, Debug, PartialEq, Eq)]
51pub struct ComponentType<'a> {
52    /// The span.
53    pub span: &'a str,
54    /// The name of the component type.
55    pub type_name: &'a str,
56    /// Properties of the component type.
57    pub props: Vec<PropDef<'a>>,
58}
59
60impl<'a> TryFrom<&'a str> for ComponentType<'a> {
61    type Error = Error;
62
63    #[inline]
64    fn try_from(input: &'a str) -> Result<Self, Self::Error> {
65        Self::parse(input)
66    }
67}
68
69impl<'a> ComponentType<'a> {
70    /// Parse a string into a component type.
71    pub fn parse(input: &'a str) -> Result<Self, Error> {
72        let (name, props_str) = input
73            .split_once('(')
74            .ok_or_else(|| Error::TypeParser(TypeParserError::invalid_type_string(input)))?;
75
76        let mut props = vec![];
77        let mut depth = 1; // 1 to account for the ( in the split above
78        let mut last = 0;
79
80        for (i, c) in props_str.chars().enumerate() {
81            match c {
82                '(' => depth += 1,
83                ')' => {
84                    depth -= 1;
85                    if depth == 0 {
86                        let candidate = &props_str[last..i];
87                        if !candidate.is_empty() {
88                            props.push(candidate.try_into()?);
89                        }
90                        last = i + 1;
91                        break;
92                    }
93                }
94                ',' => {
95                    if depth == 1 {
96                        props.push(props_str[last..i].try_into()?);
97                        last = i + 1;
98                    }
99                }
100                _ => {}
101            }
102        }
103
104        Ok(Self { span: &input[..last + name.len() + 1], type_name: name, props })
105    }
106
107    /// Convert to an owned TypeDef.
108    pub fn to_owned(&self) -> TypeDef {
109        TypeDef::new(self.type_name, self.props.iter().map(|p| p.to_owned()).collect()).unwrap()
110    }
111}
112
113/// Represents a list of component types in an EIP-712 `encodeType` type string.
114#[derive(Debug, PartialEq, Eq)]
115pub struct EncodeType<'a> {
116    /// The list of component types.
117    pub types: Vec<ComponentType<'a>>,
118}
119
120impl<'a> TryFrom<&'a str> for EncodeType<'a> {
121    type Error = Error;
122
123    #[inline]
124    fn try_from(input: &'a str) -> Result<Self, Self::Error> {
125        Self::parse(input)
126    }
127}
128
129impl<'a> EncodeType<'a> {
130    /// Parse a string into a list of component types.
131    pub fn parse(input: &'a str) -> Result<Self, Error> {
132        let mut types = vec![];
133        let mut remaining = input;
134
135        while let Ok(t) = ComponentType::parse(remaining) {
136            remaining = &remaining[t.span.len()..];
137            types.push(t);
138        }
139
140        Ok(Self { types })
141    }
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147
148    const EXAMPLE: &str = "Transaction(Person from,Person to,Asset tx)Asset(address token,uint256 amount)Person(address wallet,string name)";
149
150    #[test]
151    fn empty_type() {
152        let empty_domain_type =
153            ComponentType { span: "EIP712Domain()", type_name: "EIP712Domain", props: vec![] };
154        assert_eq!(ComponentType::parse("EIP712Domain()"), Ok(empty_domain_type.clone()));
155
156        assert_eq!(
157            EncodeType::try_from("EIP712Domain()"),
158            Ok(EncodeType { types: vec![empty_domain_type] })
159        );
160    }
161
162    #[test]
163    fn test_component_type() {
164        assert_eq!(
165            ComponentType::parse("Transaction(Person from,Person to,Asset tx)"),
166            Ok(ComponentType {
167                span: "Transaction(Person from,Person to,Asset tx)",
168                type_name: "Transaction",
169                props: vec![
170                    "Person from".try_into().unwrap(),
171                    "Person to".try_into().unwrap(),
172                    "Asset tx".try_into().unwrap(),
173                ],
174            })
175        );
176    }
177
178    #[test]
179    fn test_encode_type() {
180        assert_eq!(
181            EncodeType::parse(EXAMPLE),
182            Ok(EncodeType {
183                types: vec![
184                    "Transaction(Person from,Person to,Asset tx)".try_into().unwrap(),
185                    "Asset(address token,uint256 amount)".try_into().unwrap(),
186                    "Person(address wallet,string name)".try_into().unwrap(),
187                ]
188            })
189        );
190    }
191}