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}