alloy_sol_type_parser/
root.rs

1use crate::{ident::identifier_parser, is_valid_identifier, new_input, Error, Input, Result};
2use core::fmt;
3use winnow::{combinator::trace, stream::Stream, ModalResult, Parser};
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    /// The string underlying this type. The type name.
100    #[inline]
101    pub const fn span(self) -> &'a str {
102        self.0
103    }
104
105    /// Returns `Ok(())` if the type is a basic Solidity type.
106    #[inline]
107    pub fn try_basic_solidity(self) -> Result<()> {
108        match self.0 {
109            "address" | "bool" | "string" | "bytes" | "uint" | "int" | "function" => Ok(()),
110            name => {
111                if let Some(sz) = name.strip_prefix("bytes") {
112                    if let Ok(sz) = sz.parse::<usize>() {
113                        if sz != 0 && sz <= 32 {
114                            return Ok(());
115                        }
116                    }
117                    return Err(Error::invalid_size(name));
118                }
119
120                // fast path both integer types
121                let s = name.strip_prefix('u').unwrap_or(name);
122
123                if let Some(sz) = s.strip_prefix("int") {
124                    if let Ok(sz) = sz.parse::<usize>() {
125                        if sz != 0 && sz <= 256 && sz % 8 == 0 {
126                            return Ok(());
127                        }
128                    }
129                    return Err(Error::invalid_size(name));
130                }
131
132                Err(Error::invalid_type_string(name))
133            }
134        }
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141
142    #[test]
143    fn modified_input() {
144        assert_eq!(RootType::parse("Contract.Enum"), Ok(RootType("uint8")));
145
146        assert_eq!(RootType::parse("int"), Ok(RootType("int256")));
147        assert_eq!(RootType::parse("uint"), Ok(RootType("uint256")));
148    }
149}