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}