ethers_abi/param_type/
reader.rs

1// Copyright 2015-2020 Parity Technologies
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9#[cfg(not(feature = "std"))]
10use crate::no_std_prelude::*;
11use crate::{Error, ParamType};
12
13/// Used to convert param type represented as a string to rust structure.
14pub struct Reader;
15
16impl Reader {
17	/// Converts string to param type.
18	pub fn read(name: &str) -> Result<ParamType, Error> {
19		match name.chars().last() {
20			// check if it is a struct
21			Some(')') => {
22				if !name.starts_with('(') {
23					return Err(Error::InvalidName(name.to_owned()));
24				};
25
26				let mut subtypes = Vec::new();
27				let mut subtuples = Vec::new();
28				let mut nested = 0isize;
29				let mut top_level_paren_open = 0usize;
30				let mut last_item = 1;
31				let mut chars = name.chars().enumerate();
32
33				// Iterate over name and build the nested tuple structure
34				while let Some((mut pos, c)) = chars.next() {
35					match c {
36						'(' => {
37							top_level_paren_open = pos;
38							nested += 1;
39							// If an '(' is encountered within the tuple
40							// insert an empty subtuples vector to be filled
41							if nested > 1 {
42								subtuples.push(vec![]);
43								last_item = pos + 1;
44							}
45						}
46						')' => {
47							nested -= 1;
48
49							// End parsing and return an error if parentheses aren't symmetrical
50							if nested < 0 {
51								return Err(Error::InvalidName(name.to_owned()));
52							}
53							// If there have not been any characters since the last item
54							// increment position without inserting any subtypes
55							else if name[last_item..pos].is_empty() {
56								last_item = pos + 1;
57							}
58							// If the item is in the top level of the tuple insert it into subtypes
59							else if nested == 0 {
60								// check for trailing brackets that indicate array of tuples
61								let sub = &name[last_item..pos];
62								let subtype = Reader::read(sub)?;
63								subtypes.push(subtype);
64								last_item = pos + 1;
65							}
66							// If the item is in a sublevel of the tuple
67							else if nested > 0 {
68								// this makes sure trailing brackets are included for the next step
69								loop {
70									match chars.clone().next() {
71										Some((_, ',')) | Some((_, ')')) | None => break,
72										_ => {
73											// consume the char and shift position
74											chars.next();
75											pos += 1;
76										}
77									}
78								}
79
80								// parse the nested tuple
81								let inner_tuple = &name[top_level_paren_open..=pos];
82								let subtype = Reader::read(inner_tuple)?;
83
84								if nested > 1 {
85									let mut subtuple = core::mem::take(&mut subtuples[(nested - 2) as usize]);
86									subtuple.push(subtype);
87									subtypes.push(ParamType::Tuple(subtuple));
88								} else {
89									subtypes.push(subtype);
90								}
91								last_item = pos + 1;
92							}
93						}
94						',' => {
95							// If there have not been any characters since the last item
96							// increment position without inserting any subtypes
97							if name[last_item..pos].is_empty() {
98								last_item = pos + 1
99							}
100							// If the item is in the top level of the tuple insert it into subtypes
101							else if nested == 1 {
102								let sub = &name[last_item..pos];
103								let subtype = Reader::read(sub)?;
104								subtypes.push(subtype);
105								last_item = pos + 1;
106							}
107							// If the item is in a sublevel of the tuple
108							// insert it into the subtuple vector for the current depth level
109							else if nested > 1 {
110								let sub = &name[last_item..pos];
111								let subtype = Reader::read(sub)?;
112								subtuples[(nested - 2) as usize].push(subtype);
113								last_item = pos + 1;
114							}
115						}
116						_ => (),
117					}
118				}
119				return Ok(ParamType::Tuple(subtypes));
120			}
121			// check if it is a fixed or dynamic array.
122			Some(']') => {
123				// take number part
124				let num: String =
125					name.chars().rev().skip(1).take_while(|c| *c != '[').collect::<String>().chars().rev().collect();
126
127				let count = name.chars().count();
128				return if num.is_empty() {
129					// we already know it's a dynamic array!
130					let subtype = Reader::read(&name[..count - 2])?;
131					Ok(ParamType::Array(Box::new(subtype)))
132				} else {
133					// it's a fixed array.
134					let len = num.parse().map_err(Error::ParseInt)?;
135					let subtype = Reader::read(&name[..count - num.len() - 2])?;
136					Ok(ParamType::FixedArray(Box::new(subtype), len))
137				};
138			}
139			_ => (),
140		}
141
142		let result = match name {
143			"address" => ParamType::Address,
144			"bytes" => ParamType::Bytes,
145			"bool" => ParamType::Bool,
146			"string" => ParamType::String,
147			"int" => ParamType::Int(256),
148			"tuple" => ParamType::Tuple(vec![]),
149			"uint" => ParamType::Uint(256),
150			s if s.starts_with("int") => {
151				let len = s[3..].parse().map_err(Error::ParseInt)?;
152				ParamType::Int(len)
153			}
154			s if s.starts_with("uint") => {
155				let len = s[4..].parse().map_err(Error::ParseInt)?;
156				ParamType::Uint(len)
157			}
158			s if s.starts_with("bytes") => {
159				let len = s[5..].parse().map_err(Error::ParseInt)?;
160				ParamType::FixedBytes(len)
161			}
162			// As discussed in https://github.com/rust-ethereum/ethabi/issues/254,
163			// any type that does not fit the above corresponds to a Solidity
164			// `enum`, and as a result we treat it as a `uint8`. This is a unique
165			// case which occurs in libraries with exported (`public`) Solidity
166			// functions with `enum` parameters.
167			_ => ParamType::Uint(8),
168		};
169
170		Ok(result)
171	}
172}
173
174#[cfg(test)]
175mod tests {
176	use super::Reader;
177	#[cfg(not(feature = "std"))]
178	use crate::no_std_prelude::*;
179	use crate::ParamType;
180
181	#[test]
182	fn test_read_param() {
183		assert_eq!(Reader::read("address").unwrap(), ParamType::Address);
184		assert_eq!(Reader::read("bytes").unwrap(), ParamType::Bytes);
185		assert_eq!(Reader::read("bytes32").unwrap(), ParamType::FixedBytes(32));
186		assert_eq!(Reader::read("bool").unwrap(), ParamType::Bool);
187		assert_eq!(Reader::read("string").unwrap(), ParamType::String);
188		assert_eq!(Reader::read("int").unwrap(), ParamType::Int(256));
189		assert_eq!(Reader::read("uint").unwrap(), ParamType::Uint(256));
190		assert_eq!(Reader::read("int32").unwrap(), ParamType::Int(32));
191		assert_eq!(Reader::read("uint32").unwrap(), ParamType::Uint(32));
192	}
193
194	#[test]
195	fn test_read_array_param() {
196		assert_eq!(Reader::read("address[]").unwrap(), ParamType::Array(Box::new(ParamType::Address)));
197		assert_eq!(Reader::read("uint[]").unwrap(), ParamType::Array(Box::new(ParamType::Uint(256))));
198		assert_eq!(Reader::read("bytes[]").unwrap(), ParamType::Array(Box::new(ParamType::Bytes)));
199		assert_eq!(
200			Reader::read("bool[][]").unwrap(),
201			ParamType::Array(Box::new(ParamType::Array(Box::new(ParamType::Bool))))
202		);
203	}
204
205	#[test]
206	fn test_read_fixed_array_param() {
207		assert_eq!(Reader::read("address[2]").unwrap(), ParamType::FixedArray(Box::new(ParamType::Address), 2));
208		assert_eq!(Reader::read("bool[17]").unwrap(), ParamType::FixedArray(Box::new(ParamType::Bool), 17));
209		assert_eq!(
210			Reader::read("bytes[45][3]").unwrap(),
211			ParamType::FixedArray(Box::new(ParamType::FixedArray(Box::new(ParamType::Bytes), 45)), 3)
212		);
213	}
214
215	#[test]
216	fn test_read_mixed_arrays() {
217		assert_eq!(
218			Reader::read("bool[][3]").unwrap(),
219			ParamType::FixedArray(Box::new(ParamType::Array(Box::new(ParamType::Bool))), 3)
220		);
221		assert_eq!(
222			Reader::read("bool[3][]").unwrap(),
223			ParamType::Array(Box::new(ParamType::FixedArray(Box::new(ParamType::Bool), 3)))
224		);
225	}
226
227	#[test]
228	fn test_read_struct_param() {
229		assert_eq!(
230			Reader::read("(address,bool)").unwrap(),
231			ParamType::Tuple(vec![ParamType::Address, ParamType::Bool])
232		);
233		assert_eq!(
234			Reader::read("(bool[3],uint256)").unwrap(),
235			ParamType::Tuple(vec![ParamType::FixedArray(Box::new(ParamType::Bool), 3), ParamType::Uint(256)])
236		);
237	}
238
239	#[test]
240	fn test_read_nested_struct_param() {
241		assert_eq!(
242			Reader::read("(address,bool,(bool,uint256))").unwrap(),
243			ParamType::Tuple(vec![
244				ParamType::Address,
245				ParamType::Bool,
246				ParamType::Tuple(vec![ParamType::Bool, ParamType::Uint(256)])
247			])
248		);
249	}
250
251	#[test]
252	fn test_read_complex_nested_struct_param() {
253		assert_eq!(
254			Reader::read("(address,bool,(bool,uint256,(bool,uint256)),(bool,uint256))").unwrap(),
255			ParamType::Tuple(vec![
256				ParamType::Address,
257				ParamType::Bool,
258				ParamType::Tuple(vec![
259					ParamType::Bool,
260					ParamType::Uint(256),
261					ParamType::Tuple(vec![ParamType::Bool, ParamType::Uint(256)])
262				]),
263				ParamType::Tuple(vec![ParamType::Bool, ParamType::Uint(256)])
264			])
265		);
266	}
267
268	#[test]
269	fn test_read_nested_tuple_array_param() {
270		assert_eq!(
271			Reader::read("(uint256,bytes32)[]").unwrap(),
272			ParamType::Array(Box::new(ParamType::Tuple(vec![ParamType::Uint(256), ParamType::FixedBytes(32)])))
273		)
274	}
275
276	#[test]
277	fn test_read_inner_tuple_array_param() {
278		use crate::param_type::Writer;
279		let abi = "((uint256,bytes32)[],address)";
280		let read = Reader::read(abi).unwrap();
281
282		let param = ParamType::Tuple(vec![
283			ParamType::Array(Box::new(ParamType::Tuple(vec![ParamType::Uint(256), ParamType::FixedBytes(32)]))),
284			ParamType::Address,
285		]);
286
287		assert_eq!(read, param);
288
289		assert_eq!(abi, Writer::write(&param));
290	}
291}