alloy_sol_type_parser/
root.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
use crate::{ident::identifier_parser, is_valid_identifier, new_input, Error, Input, Result};
use core::fmt;
use winnow::{combinator::trace, stream::Stream, PResult, Parser};

/// A root type, with no array suffixes. Corresponds to a single, non-sequence
/// type. This is the most basic type specifier.
///
/// Note that this type might modify the input string, so [`span()`](Self::span)
/// must not be assumed to be the same as the input string.
///
/// # Examples
///
/// ```
/// # use alloy_sol_type_parser::RootType;
/// let root_type = RootType::parse("uint256")?;
/// assert_eq!(root_type.span(), "uint256");
///
/// // Allows unknown types
/// assert_eq!(RootType::parse("MyStruct")?.span(), "MyStruct");
///
/// // No sequences
/// assert!(RootType::parse("uint256[2]").is_err());
///
/// // No tuples
/// assert!(RootType::parse("(uint256,uint256)").is_err());
///
/// // Input string might get modified
/// assert_eq!(RootType::parse("uint")?.span(), "uint256");
/// # Ok::<_, alloy_sol_type_parser::Error>(())
/// ```
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct RootType<'a>(&'a str);

impl<'a> TryFrom<&'a str> for RootType<'a> {
    type Error = Error;

    #[inline]
    fn try_from(value: &'a str) -> Result<Self> {
        Self::parse(value)
    }
}

impl AsRef<str> for RootType<'_> {
    #[inline]
    fn as_ref(&self) -> &str {
        self.0
    }
}

impl fmt::Display for RootType<'_> {
    #[inline]
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(self.0)
    }
}

impl<'a> RootType<'a> {
    /// Create a new root type from a string without checking if it's valid.
    ///
    /// # Safety
    ///
    /// The string passed in must be a valid Solidity identifier. See
    /// [`is_valid_identifier`].
    pub const unsafe fn new_unchecked(s: &'a str) -> Self {
        debug_assert!(is_valid_identifier(s));
        Self(s)
    }

    /// Parse a root type from a string.
    #[inline]
    pub fn parse(input: &'a str) -> Result<Self> {
        Self::parser.parse(new_input(input)).map_err(Error::parser)
    }

    /// [`winnow`] parser for this type.
    pub(crate) fn parser(input: &mut Input<'a>) -> PResult<Self> {
        trace("RootType", |input: &mut Input<'a>| {
            identifier_parser(input).map(|ident| {
                // Workaround for enums in library function params or returns.
                // See: https://github.com/alloy-rs/core/pull/386
                // See ethabi workaround: https://github.com/rust-ethereum/ethabi/blob/b1710adc18f5b771d2d2519c87248b1ba9430778/ethabi/src/param_type/reader.rs#L162-L167
                if input.starts_with('.') {
                    let _ = input.next_token();
                    let _ = identifier_parser(input);
                    return Self("uint8");
                }

                // Normalize the `u?int` aliases to the canonical `u?int256`
                match ident {
                    "uint" => Self("uint256"),
                    "int" => Self("int256"),
                    _ => Self(ident),
                }
            })
        })
        .parse_next(input)
    }

    /// The string underlying this type. The type name.
    #[inline]
    pub const fn span(self) -> &'a str {
        self.0
    }

    /// Returns `Ok(())` if the type is a basic Solidity type.
    #[inline]
    pub fn try_basic_solidity(self) -> Result<()> {
        match self.0 {
            "address" | "bool" | "string" | "bytes" | "uint" | "int" | "function" => Ok(()),
            name => {
                if let Some(sz) = name.strip_prefix("bytes") {
                    if let Ok(sz) = sz.parse::<usize>() {
                        if sz != 0 && sz <= 32 {
                            return Ok(());
                        }
                    }
                    return Err(Error::invalid_size(name));
                }

                // fast path both integer types
                let s = name.strip_prefix('u').unwrap_or(name);

                if let Some(sz) = s.strip_prefix("int") {
                    if let Ok(sz) = sz.parse::<usize>() {
                        if sz != 0 && sz <= 256 && sz % 8 == 0 {
                            return Ok(());
                        }
                    }
                    return Err(Error::invalid_size(name));
                }

                Err(Error::invalid_type_string(name))
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn modified_input() {
        assert_eq!(RootType::parse("Contract.Enum"), Ok(RootType("uint8")));

        assert_eq!(RootType::parse("int"), Ok(RootType("int256")));
        assert_eq!(RootType::parse("uint"), Ok(RootType("uint256")));
    }
}