alloy_sol_type_parser/
root.rs

1use crate::{Error, Input, Result, ident::identifier_parser, is_valid_identifier, new_input};
2use core::fmt;
3use winnow::{ModalResult, Parser, combinator::trace, stream::Stream};
4
5/// A root type, with no array suffixes. Corresponds to a single, non-sequence
6/// type. This is the most basic type specifier.
7///
8/// Note that this type might modify the input string, so [`span()`](Self::span)
9/// must not be assumed to be the same as the input string.
10///
11/// # Examples
12///
13/// ```
14/// # use alloy_sol_type_parser::RootType;
15/// let root_type = RootType::parse("uint256")?;
16/// assert_eq!(root_type.span(), "uint256");
17///
18/// // Allows unknown types
19/// assert_eq!(RootType::parse("MyStruct")?.span(), "MyStruct");
20///
21/// // No sequences
22/// assert!(RootType::parse("uint256[2]").is_err());
23///
24/// // No tuples
25/// assert!(RootType::parse("(uint256,uint256)").is_err());
26///
27/// // Input string might get modified
28/// assert_eq!(RootType::parse("uint")?.span(), "uint256");
29/// # Ok::<_, alloy_sol_type_parser::Error>(())
30/// ```
31#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
32pub struct RootType<'a>(&'a str);
33
34impl<'a> TryFrom<&'a str> for RootType<'a> {
35    type Error = Error;
36
37    #[inline]
38    fn try_from(value: &'a str) -> Result<Self> {
39        Self::parse(value)
40    }
41}
42
43impl AsRef<str> for RootType<'_> {
44    #[inline]
45    fn as_ref(&self) -> &str {
46        self.0
47    }
48}
49
50impl fmt::Display for RootType<'_> {
51    #[inline]
52    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53        f.write_str(self.0)
54    }
55}
56
57impl<'a> RootType<'a> {
58    /// Create a new root type from a string without checking if it's valid.
59    ///
60    /// # Safety
61    ///
62    /// The string passed in must be a valid Solidity identifier. See
63    /// [`is_valid_identifier`].
64    pub const unsafe fn new_unchecked(s: &'a str) -> Self {
65        debug_assert!(is_valid_identifier(s));
66        Self(s)
67    }
68
69    /// Parse a root type from a string.
70    #[inline]
71    pub fn parse(input: &'a str) -> Result<Self> {
72        Self::parser.parse(new_input(input)).map_err(Error::parser)
73    }
74
75    /// [`winnow`] parser for this type.
76    pub(crate) fn parser(input: &mut Input<'a>) -> ModalResult<Self> {
77        trace("RootType", |input: &mut Input<'a>| {
78            identifier_parser(input).map(|ident| {
79                // Workaround for enums in library function params or returns.
80                // See: https://github.com/alloy-rs/core/pull/386
81                // See ethabi workaround: https://github.com/rust-ethereum/ethabi/blob/b1710adc18f5b771d2d2519c87248b1ba9430778/ethabi/src/param_type/reader.rs#L162-L167
82                if input.starts_with('.') {
83                    let _ = input.next_token();
84                    let _ = identifier_parser(input);
85                    return Self("uint8");
86                }
87
88                // Normalize the `u?int` aliases to the canonical `u?int256`
89                match ident {
90                    "uint" => Self("uint256"),
91                    "int" => Self("int256"),
92                    _ => Self(ident),
93                }
94            })
95        })
96        .parse_next(input)
97    }
98
99    /// Parse a root type from a string.
100    #[cfg(feature = "eip712")]
101    #[inline]
102    pub fn parse_eip712(input: &'a str) -> Result<Self> {
103        Self::eip712_parser.parse(new_input(input)).map_err(Error::parser)
104    }
105
106    /// [`winnow`] parser for EIP-712 types.
107    #[cfg(feature = "eip712")]
108    pub(crate) fn eip712_parser(input: &mut Input<'a>) -> ModalResult<Self> {
109        trace("RootType::eip712", |input: &mut Input<'a>| {
110            use crate::ident::eip712_identifier_parser;
111
112            eip712_identifier_parser(input).map(|ident| {
113                // Workaround for enums in library function params or returns.
114                // See: https://github.com/alloy-rs/core/pull/386
115                // See ethabi workaround: https://github.com/rust-ethereum/ethabi/blob/b1710adc18f5b771d2d2519c87248b1ba9430778/ethabi/src/param_type/reader.rs#L162-L167
116                if input.starts_with('.') {
117                    let _ = input.next_token();
118                    let _ = eip712_identifier_parser(input);
119                    return Self("uint8");
120                }
121
122                // Normalize the `u?int` aliases to the canonical `u?int256`
123                match ident {
124                    "uint" => Self("uint256"),
125                    "int" => Self("int256"),
126                    _ => Self(ident),
127                }
128            })
129        })
130        .parse_next(input)
131    }
132
133    /// The string underlying this type. The type name.
134    #[inline]
135    pub const fn span(self) -> &'a str {
136        self.0
137    }
138
139    /// Returns `Ok(())` if the type is a basic Solidity type.
140    #[inline]
141    pub fn try_basic_solidity(self) -> Result<()> {
142        match self.0 {
143            "address" | "bool" | "string" | "bytes" | "uint" | "int" | "function" => Ok(()),
144            name => {
145                if let Some(sz) = name.strip_prefix("bytes") {
146                    if let Ok(sz) = sz.parse::<usize>() {
147                        if sz != 0 && sz <= 32 {
148                            return Ok(());
149                        }
150                    }
151                    return Err(Error::invalid_size(name));
152                }
153
154                // fast path both integer types
155                let s = name.strip_prefix('u').unwrap_or(name);
156
157                if let Some(sz) = s.strip_prefix("int") {
158                    if let Ok(sz) = sz.parse::<usize>() {
159                        if sz != 0 && sz <= 256 && sz % 8 == 0 {
160                            return Ok(());
161                        }
162                    }
163                    return Err(Error::invalid_size(name));
164                }
165
166                Err(Error::invalid_type_string(name))
167            }
168        }
169    }
170}
171
172#[cfg(test)]
173mod tests {
174    use super::*;
175
176    #[test]
177    fn modified_input() {
178        assert_eq!(RootType::parse("Contract.Enum"), Ok(RootType("uint8")));
179
180        assert_eq!(RootType::parse("int"), Ok(RootType("int256")));
181        assert_eq!(RootType::parse("uint"), Ok(RootType("uint256")));
182    }
183}