From cf8e2391c89cd1b862720289b5aecd2ca499112a Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Mon, 15 Mar 2021 12:49:28 +0100 Subject: [PATCH] feat: add struct parsing support for human readable ABI (#226) * refactor: extract error module and use error macros * feat: add solidity struct parser * refactor: add AbiParse and support struct parsing * test: add more struct parsing tests --- ethers-core/src/abi/error.rs | 25 + ethers-core/src/abi/human_readable.rs | 817 +++++++++++++++++--------- ethers-core/src/abi/mod.rs | 8 +- ethers-core/src/abi/struct_def.rs | 476 +++++++++++++++ 4 files changed, 1038 insertions(+), 288 deletions(-) create mode 100644 ethers-core/src/abi/error.rs create mode 100644 ethers-core/src/abi/struct_def.rs diff --git a/ethers-core/src/abi/error.rs b/ethers-core/src/abi/error.rs new file mode 100644 index 00000000..2cb4a668 --- /dev/null +++ b/ethers-core/src/abi/error.rs @@ -0,0 +1,25 @@ +//! Boilerplate error definitions. +use thiserror::Error; + +/// A type alias for std's Result with the Error as our error type. +pub type Result = std::result::Result; + +#[derive(Error, Debug)] +pub enum ParseError { + #[error("{0}")] + Message(String), + #[error(transparent)] + ParseError(#[from] super::Error), +} + +macro_rules! _format_err { + ($($tt:tt)*) => { + $crate::abi::ParseError::Message(format!($($tt)*)) + }; +} +pub(crate) use _format_err as format_err; + +macro_rules! _bail { + ($($tt:tt)*) => { return Err($crate::abi::error::format_err!($($tt)*)) }; +} +pub(crate) use _bail as bail; diff --git a/ethers-core/src/abi/human_readable.rs b/ethers-core/src/abi/human_readable.rs index 99015248..98ec7142 100644 --- a/ethers-core/src/abi/human_readable.rs +++ b/ethers-core/src/abi/human_readable.rs @@ -1,11 +1,384 @@ -use std::collections::HashMap; +use std::collections::{HashMap, VecDeque}; -use thiserror::Error; - -use super::{ - param_type::Reader, Abi, Constructor, Event, EventParam, Function, Param, StateMutability, +use crate::abi::error::{bail, format_err, ParseError, Result}; +use crate::abi::struct_def::{FieldType, StructFieldType}; +use crate::abi::{ + param_type::Reader, Abi, Constructor, Event, EventParam, Function, Param, ParamType, SolStruct, + StateMutability, }; +/// A parser that turns a "human readable abi" into a `Abi` +pub struct AbiParser { + abi: Abi, + /// solidity structs + structs: HashMap, + /// solidity structs as tuples + struct_tuples: HashMap>, +} + +impl AbiParser { + /// Parses a "human readable abi" string + pub fn parse_str(self, s: &str) -> Result { + self.parse(&s.lines().collect::>()) + } + + /// Parses a "human readable abi" string vector + /// + /// # Example + /// ``` + /// use ethers::abi::AbiParser; + /// + /// let abi = AbiParser::default().parse(&[ + /// "function x() external view returns (uint256)", + /// ]).unwrap(); + /// ``` + pub fn parse(mut self, input: &[&str]) -> Result { + // parse struct first + let (structs, types): (Vec<_>, Vec<_>) = input + .iter() + .map(|s| escape_quotes(s)) + .partition(|s| s.starts_with("struct")); + + for sol in structs { + let s = SolStruct::parse(sol)?; + if self.structs.contains_key(s.name()) { + bail!("Duplicate struct declaration for struct `{}`", s.name()) + } + self.structs.insert(s.name().to_string(), s); + } + self.substitute_structs()?; + + for mut line in types { + line = line.trim_start(); + if line.starts_with("function") { + let function = self.parse_function(&line)?; + self.abi + .functions + .entry(function.name.clone()) + .or_default() + .push(function); + } else if line.starts_with("event") { + let event = self.parse_event(line)?; + self.abi + .events + .entry(event.name.clone()) + .or_default() + .push(event); + } else if line.starts_with("constructor") { + self.abi.constructor = Some(self.parse_constructor(line)?); + } else { + bail!("Illegal abi `{}`", line) + } + } + Ok(self.abi) + } + + /// Substitutes any other struct references within structs with tuples + fn substitute_structs(&mut self) -> Result<()> { + let mut unresolved = self.structs.keys().collect::>(); + let mut sequential_retries = 0; + while let Some(name) = unresolved.pop_front() { + let mut resolved = true; + let sol = &self.structs[name]; + let mut tuple = Vec::with_capacity(sol.fields().len()); + for field in sol.fields() { + match field.r#type() { + FieldType::Elementary(param) => tuple.push(param.clone()), + FieldType::Struct(ty) => { + if let Some(param) = self.struct_tuples.get(ty.name()).cloned() { + tuple.push(ParamType::Tuple(param)) + } else { + resolved = false; + break; + } + } + FieldType::StructArray(ty) => { + if let Some(param) = self.struct_tuples.get(ty.name()).cloned() { + tuple.push(ParamType::Array(Box::new(ParamType::Tuple(param)))) + } else { + resolved = false; + break; + } + } + FieldType::FixedStructArray(ty, size) => { + if let Some(param) = self.struct_tuples.get(ty.name()).cloned() { + tuple.push(ParamType::FixedArray( + Box::new(ParamType::Tuple(param)), + *size, + )) + } else { + resolved = false; + break; + } + } + FieldType::Mapping(_) => { + bail!( + "mappings are not allowed as params in public functions of struct `{}`", + sol.name() + ) + } + } + } + if resolved { + sequential_retries = 0; + self.struct_tuples.insert(sol.name().to_string(), tuple); + } else { + sequential_retries += 1; + if sequential_retries > unresolved.len() { + bail!("No struct definition found for struct `{}`", name) + } + unresolved.push_back(name); + } + } + Ok(()) + } + + /// Link additional structs for parsing + pub fn with_structs(structs: Vec) -> Self { + Self { + abi: Abi { + constructor: None, + functions: HashMap::new(), + events: HashMap::new(), + receive: false, + fallback: false, + }, + structs: structs + .into_iter() + .map(|s| (s.name().to_string(), s)) + .collect(), + struct_tuples: HashMap::new(), + } + } + + /// Parses a solidity event declaration from `event (args*) anonymous?` + fn parse_event(&self, s: &str) -> Result { + let mut event = s.trim(); + if !event.starts_with("event ") { + bail!("Not an event `{}`", s) + } + event = &event[5..]; + + let name = parse_identifier(&mut event)?; + + let mut chars = event.chars(); + + loop { + match chars.next() { + None => bail!("Expected event"), + Some('(') => { + event = chars.as_str().trim(); + let mut anonymous = false; + if event.ends_with("anonymous") { + anonymous = true; + event = event[..event.len() - 9].trim_end(); + } + event = event + .trim() + .strip_suffix(')') + .ok_or_else(|| format_err!("Expected closing `)` in `{}`", s))?; + + let inputs = if event.is_empty() { + Vec::new() + } else { + event + .split(',') + .map(|e| self.parse_event_arg(e)) + .collect::, _>>()? + }; + return Ok(Event { + name, + inputs, + anonymous, + }); + } + Some(' ') | Some('\t') => { + continue; + } + Some(c) => { + bail!("Illegal char `{}` at `{}`", c, s) + } + } + } + } + + /// Parse a single event param + fn parse_event_arg(&self, input: &str) -> Result { + let mut iter = input.trim().rsplitn(3, is_whitespace); + let mut indexed = false; + let mut name = iter + .next() + .ok_or_else(|| format_err!("Empty event param at `{}`", input))?; + + let type_str; + if let Some(mid) = iter.next() { + if let Some(ty) = iter.next() { + if mid != "indexed" { + bail!("Expected indexed keyword at `{}`", input) + } + indexed = true; + type_str = ty; + } else { + if name == "indexed" { + indexed = true; + name = ""; + } + type_str = mid; + } + } else { + type_str = name; + name = ""; + } + + Ok(EventParam { + name: name.to_string(), + indexed, + kind: self.parse_type(type_str)?, + }) + } + + fn parse_function(&mut self, s: &str) -> Result { + let mut input = s.trim(); + if !input.starts_with("function ") { + bail!("Not a function `{}`", input) + } + input = &input[8..]; + let name = parse_identifier(&mut input)?; + + let mut iter = input.split(" returns"); + + let parens = iter + .next() + .ok_or_else(|| format_err!("Invalid function declaration at `{}`", s))? + .trim_end(); + + let mut parens_iter = parens.rsplitn(2, ')'); + let mut modifiers = parens_iter.next(); + + let input_params = if let Some(args) = parens_iter.next() { + args + } else { + modifiers + .take() + .ok_or(ParseError::ParseError(super::Error::InvalidData))? + } + .trim_start() + .strip_prefix('(') + .ok_or(ParseError::ParseError(super::Error::InvalidData))?; + + let inputs = self.parse_params(input_params)?; + + let outputs = if let Some(params) = iter.next() { + let params = params + .trim() + .strip_prefix('(') + .and_then(|s| s.strip_suffix(')')) + .ok_or_else(|| format_err!("Expected parentheses at `{}`", s))?; + self.parse_params(params)? + } else { + Vec::new() + }; + + let state_mutability = modifiers.map(detect_state_mutability).unwrap_or_default(); + + #[allow(deprecated)] + Ok(Function { + name, + inputs, + outputs, + state_mutability, + constant: false, + }) + } + + fn parse_params(&self, s: &str) -> Result> { + s.split(',') + .filter(|s| !s.is_empty()) + .map(|s| self.parse_param(s)) + .collect::, _>>() + } + + fn parse_type(&self, type_str: &str) -> Result { + if let Ok(kind) = Reader::read(type_str) { + Ok(kind) + } else { + // try struct instead + if let Ok(field) = StructFieldType::parse(type_str) { + let struct_ty = field + .as_struct() + .ok_or_else(|| format_err!("Expected struct type `{}`", type_str))?; + let tuple = self + .struct_tuples + .get(struct_ty.name()) + .cloned() + .map(ParamType::Tuple) + .ok_or_else(|| format_err!("Unknown struct `{}`", struct_ty.name()))?; + + match field { + FieldType::Struct(_) => Ok(tuple), + FieldType::StructArray(_) => Ok(ParamType::Array(Box::new(tuple))), + FieldType::FixedStructArray(_, size) => { + Ok(ParamType::FixedArray(Box::new(tuple), size)) + } + _ => bail!("Expected struct type"), + } + } else { + bail!("Failed determine event type `{}`", type_str) + } + } + } + + fn parse_constructor(&self, s: &str) -> Result { + let mut input = s.trim(); + if !input.starts_with("constructor") { + bail!("Not a constructor `{}`", input) + } + input = input[11..] + .trim_start() + .strip_prefix('(') + .ok_or_else(|| format_err!("Expected leading `(` in `{}`", s))?; + + let params = input + .rsplitn(2, ')') + .last() + .ok_or_else(|| format_err!("Expected closing `)` in `{}`", s))?; + + let inputs = self.parse_params(params)?; + + Ok(Constructor { inputs }) + } + + fn parse_param(&self, param: &str) -> Result { + let mut iter = param.trim().rsplitn(3, is_whitespace); + + let mut name = iter + .next() + .ok_or(ParseError::ParseError(super::Error::InvalidData))?; + + let type_str; + if let Some(ty) = iter.last() { + if name == "memory" || name == "calldata" { + name = ""; + } + type_str = ty; + } else { + type_str = name; + name = ""; + } + + Ok(Param { + name: name.to_string(), + kind: self.parse_type(type_str)?, + }) + } +} + +impl Default for AbiParser { + fn default() -> Self { + Self::with_structs(Vec::new()) + } +} + /// Parses a "human readable abi" string vector /// /// ``` @@ -15,46 +388,17 @@ use super::{ /// "function x() external view returns (uint256)", /// ]).unwrap(); /// ``` -pub fn parse(input: &[&str]) -> Result { - let mut abi = Abi { - constructor: None, - functions: HashMap::new(), - events: HashMap::new(), - receive: false, - fallback: false, - }; - - for mut line in input.iter().map(|s| escape_quotes(s)) { - line = line.trim_start(); - if line.starts_with("function") { - let function = parse_function(&line)?; - abi.functions - .entry(function.name.clone()) - .or_default() - .push(function); - } else if line.starts_with("event") { - let event = parse_event(line)?; - abi.events - .entry(event.name.clone()) - .or_default() - .push(event); - } else if line.starts_with("constructor") { - abi.constructor = Some(parse_constructor(line)?); - } else { - return Err(ParseError::ParseError(super::Error::InvalidData)); - } - } - - Ok(abi) +pub fn parse(input: &[&str]) -> Result { + AbiParser::default().parse(input) } /// Parses an identifier like event or function name -fn parse_identifier(input: &mut &str) -> Result { +pub(crate) fn parse_identifier(input: &mut &str) -> Result { let mut chars = input.trim_start().chars(); let mut name = String::new(); let c = chars .next() - .ok_or(ParseError::ParseError(super::Error::InvalidData))?; + .ok_or_else(|| format_err!("Empty identifier in `{}`", input))?; if is_first_ident_char(c) { name.push(c); loop { @@ -67,194 +411,15 @@ fn parse_identifier(input: &mut &str) -> Result { } } } + if name.is_empty() { + return Err(ParseError::ParseError(super::Error::InvalidName( + input.to_string(), + ))); + } *input = chars.as_str(); Ok(name) } -/// Parses a solidity event declaration from `event (args*) anonymous?` -fn parse_event(mut event: &str) -> Result { - event = event.trim(); - if !event.starts_with("event ") { - return Err(ParseError::ParseError(super::Error::InvalidData)); - } - event = &event[5..]; - - let name = parse_identifier(&mut event)?; - if name.is_empty() { - return Err(ParseError::ParseError(super::Error::InvalidName( - event.to_owned(), - ))); - } - - let mut chars = event.chars(); - - loop { - match chars.next() { - None => return Err(ParseError::ParseError(super::Error::InvalidData)), - Some('(') => { - event = chars.as_str().trim(); - let mut anonymous = false; - if event.ends_with("anonymous") { - anonymous = true; - event = event[..event.len() - 9].trim_end(); - } - event = event - .trim() - .strip_suffix(')') - .ok_or(ParseError::ParseError(super::Error::InvalidData))?; - - let inputs = if event.is_empty() { - Vec::new() - } else { - event - .split(',') - .map(parse_event_arg) - .collect::, _>>()? - }; - return Ok(Event { - name, - inputs, - anonymous, - }); - } - Some(' ') | Some('\t') => { - continue; - } - _ => { - return Err(ParseError::ParseError(super::Error::InvalidData)); - } - } - } -} - -/// Parse a single event param -fn parse_event_arg(input: &str) -> Result { - let mut iter = input.trim().rsplitn(3, is_whitespace); - let mut indexed = false; - let mut name = iter - .next() - .ok_or(ParseError::ParseError(super::Error::InvalidData))?; - - if let Some(mid) = iter.next() { - let kind; - if let Some(ty) = iter.next() { - if mid != "indexed" { - return Err(ParseError::ParseError(super::Error::InvalidData)); - } - indexed = true; - kind = Reader::read(ty)?; - } else { - if name == "indexed" { - indexed = true; - name = ""; - } - kind = Reader::read(mid)?; - } - Ok(EventParam { - name: name.to_owned(), - kind, - indexed, - }) - } else { - Ok(EventParam { - name: "".to_owned(), - indexed, - kind: Reader::read(name)?, - }) - } -} - -fn parse_function(mut input: &str) -> Result { - input = input.trim(); - if !input.starts_with("function ") { - return Err(ParseError::ParseError(super::Error::InvalidData)); - } - input = &input[8..]; - let name = parse_identifier(&mut input)?; - if name.is_empty() { - return Err(ParseError::ParseError(super::Error::InvalidName( - input.to_owned(), - ))); - } - - let mut iter = input.split(" returns"); - - let parens = iter - .next() - .ok_or(ParseError::ParseError(super::Error::InvalidData))? - .trim_end(); - - let mut parens_iter = parens.rsplitn(2, ')'); - let mut modifiers = parens_iter.next(); - - let input_params = if let Some(args) = parens_iter.next() { - args - } else { - modifiers - .take() - .ok_or(ParseError::ParseError(super::Error::InvalidData))? - } - .trim_start() - .strip_prefix('(') - .ok_or(ParseError::ParseError(super::Error::InvalidData))?; - - let inputs = input_params - .split(',') - .filter(|s| !s.is_empty()) - .map(parse_param) - .collect::, _>>()?; - - let outputs = if let Some(params) = iter.next() { - let params = params - .trim() - .strip_prefix('(') - .and_then(|s| s.strip_suffix(')')) - .ok_or(ParseError::ParseError(super::Error::InvalidData))?; - params - .split(',') - .filter(|s| !s.is_empty()) - .map(parse_param) - .collect::, _>>()? - } else { - Vec::new() - }; - - let state_mutability = modifiers.map(detect_state_mutability).unwrap_or_default(); - - #[allow(deprecated)] - Ok(Function { - name, - inputs, - outputs, - state_mutability, - constant: false, - }) -} - -fn parse_constructor(mut input: &str) -> Result { - input = input.trim(); - if !input.starts_with("constructor") { - return Err(ParseError::ParseError(super::Error::InvalidData)); - } - input = input[11..] - .trim_start() - .strip_prefix('(') - .ok_or(ParseError::ParseError(super::Error::InvalidData))?; - - let params = input - .rsplitn(2, ')') - .last() - .ok_or(ParseError::ParseError(super::Error::InvalidData))?; - - let inputs = params - .split(',') - .filter(|s| !s.is_empty()) - .map(parse_param) - .collect::, _>>()?; - - Ok(Constructor { inputs }) -} - fn detect_state_mutability(s: &str) -> StateMutability { if s.contains("pure") { StateMutability::Pure @@ -267,42 +432,15 @@ fn detect_state_mutability(s: &str) -> StateMutability { } } -fn parse_param(param: &str) -> Result { - let mut iter = param.trim().rsplitn(3, is_whitespace); - - let name = iter - .next() - .ok_or(ParseError::ParseError(super::Error::InvalidData))?; - - if let Some(ty) = iter.last() { - if name == "memory" || name == "calldata" { - Ok(Param { - name: "".to_owned(), - kind: Reader::read(ty)?, - }) - } else { - Ok(Param { - name: name.to_owned(), - kind: Reader::read(ty)?, - }) - } - } else { - Ok(Param { - name: "".to_owned(), - kind: Reader::read(name)?, - }) - } -} - -fn is_first_ident_char(c: char) -> bool { +pub(crate) fn is_first_ident_char(c: char) -> bool { matches!(c, 'a'..='z' | 'A'..='Z' | '_') } -fn is_ident_char(c: char) -> bool { +pub(crate) fn is_ident_char(c: char) -> bool { matches!(c, 'a'..='z' | 'A'..='Z' | '0'..='9' | '_') } -fn is_whitespace(c: char) -> bool { +pub(crate) fn is_whitespace(c: char) -> bool { matches!(c, ' ' | '\t') } @@ -310,24 +448,14 @@ fn escape_quotes(input: &str) -> &str { input.trim_matches(is_whitespace).trim_matches('\"') } -#[derive(Error, Debug)] -pub enum ParseError { - #[error("expected data type")] - Kind, - - #[error(transparent)] - ParseError(#[from] super::Error), -} - #[cfg(test)] mod tests { use super::*; - use crate::abi::ParamType; #[test] fn parses_approve() { let fn_str = "function approve(address _spender, uint256 value) external returns(bool)"; - let parsed = parse_function(fn_str).unwrap(); + let parsed = AbiParser::default().parse_function(fn_str).unwrap(); assert_eq!(parsed.name, "approve"); assert_eq!(parsed.inputs[0].name, "_spender"); assert_eq!(parsed.inputs[0].kind, ParamType::Address,); @@ -340,7 +468,7 @@ mod tests { #[test] fn parses_function_arguments_return() { let fn_str = "function foo(uint32[] memory x) external view returns (address)"; - let parsed = parse_function(fn_str).unwrap(); + let parsed = AbiParser::default().parse_function(fn_str).unwrap(); assert_eq!(parsed.name, "foo"); assert_eq!(parsed.inputs[0].name, "x"); assert_eq!( @@ -354,7 +482,7 @@ mod tests { #[test] fn parses_function_empty() { let fn_str = "function foo()"; - let parsed = parse_function(fn_str).unwrap(); + let parsed = AbiParser::default().parse_function(fn_str).unwrap(); assert_eq!(parsed.name, "foo"); assert!(parsed.inputs.is_empty()); assert!(parsed.outputs.is_empty()); @@ -363,44 +491,46 @@ mod tests { #[test] fn parses_function_payable() { let fn_str = "function foo() public payable"; - let parsed = parse_function(fn_str).unwrap(); + let parsed = AbiParser::default().parse_function(fn_str).unwrap(); assert_eq!(parsed.state_mutability, StateMutability::Payable); } #[test] fn parses_function_view() { let fn_str = "function foo() external view"; - let parsed = parse_function(fn_str).unwrap(); + let parsed = AbiParser::default().parse_function(fn_str).unwrap(); assert_eq!(parsed.state_mutability, StateMutability::View); } #[test] fn parses_function_pure() { let fn_str = "function foo() pure"; - let parsed = parse_function(fn_str).unwrap(); + let parsed = AbiParser::default().parse_function(fn_str).unwrap(); assert_eq!(parsed.state_mutability, StateMutability::Pure); } #[test] fn parses_event() { assert_eq!( - parse_event(&mut "event Foo (address indexed x, uint y, bytes32[] z)").unwrap(), + AbiParser::default() + .parse_event(&mut "event Foo (address indexed x, uint y, bytes32[] z)") + .unwrap(), Event { anonymous: false, - name: "Foo".to_owned(), + name: "Foo".to_string(), inputs: vec![ EventParam { - name: "x".to_owned(), + name: "x".to_string(), kind: ParamType::Address, indexed: true, }, EventParam { - name: "y".to_owned(), + name: "y".to_string(), kind: ParamType::Uint(256), indexed: false, }, EventParam { - name: "z".to_owned(), + name: "z".to_string(), kind: ParamType::Array(Box::new(ParamType::FixedBytes(32))), indexed: false, }, @@ -412,10 +542,12 @@ mod tests { #[test] fn parses_anonymous_event() { assert_eq!( - parse_event(&mut "event Foo() anonymous").unwrap(), + AbiParser::default() + .parse_event(&mut "event Foo() anonymous") + .unwrap(), Event { anonymous: true, - name: "Foo".to_owned(), + name: "Foo".to_string(), inputs: vec![], } ); @@ -424,12 +556,14 @@ mod tests { #[test] fn parses_unnamed_event() { assert_eq!( - parse_event(&mut "event Foo(address)").unwrap(), + AbiParser::default() + .parse_event(&mut "event Foo(address)") + .unwrap(), Event { anonymous: false, - name: "Foo".to_owned(), + name: "Foo".to_string(), inputs: vec![EventParam { - name: "".to_owned(), + name: "".to_string(), kind: ParamType::Address, indexed: false, }], @@ -440,12 +574,14 @@ mod tests { #[test] fn parses_unnamed_indexed_event() { assert_eq!( - parse_event(&mut "event Foo(address indexed)").unwrap(), + AbiParser::default() + .parse_event(&mut "event Foo(address indexed)") + .unwrap(), Event { anonymous: false, - name: "Foo".to_owned(), + name: "Foo".to_string(), inputs: vec![EventParam { - name: "".to_owned(), + name: "".to_string(), kind: ParamType::Address, indexed: true, }], @@ -456,18 +592,20 @@ mod tests { #[test] fn parse_event_input() { assert_eq!( - parse_event_arg("address indexed x").unwrap(), + AbiParser::default() + .parse_event_arg("address indexed x") + .unwrap(), EventParam { - name: "x".to_owned(), + name: "x".to_string(), kind: ParamType::Address, indexed: true, } ); assert_eq!( - parse_event_arg("address x").unwrap(), + AbiParser::default().parse_event_arg("address x").unwrap(), EventParam { - name: "x".to_owned(), + name: "x".to_string(), kind: ParamType::Address, indexed: false, } @@ -486,10 +624,24 @@ mod tests { ] .iter() .for_each(|x| { - parse_function(x).unwrap(); + AbiParser::default().parse_function(x).unwrap(); }); } + #[test] + fn can_parse_structs_and_functions() { + let abi = &[ + "struct Demo {bytes x; address payable d;}", + "struct Voter { uint weight; bool voted; address delegate; uint vote; }", + "event FireEvent(Voter v, NestedVoter2 n)", + "function foo(uint256[] memory x) external view returns (address)", + "function call(Voter memory voter) returns (address, uint256)", + "struct NestedVoter { Voter voter; bool voted; address delegate; uint vote; }", + "struct NestedVoter2 { NestedVoter[] voter; Voter[10] votes; address delegate; uint vote; }", + ]; + parse(abi).unwrap(); + } + #[test] fn can_parse_params() { [ @@ -502,7 +654,7 @@ mod tests { ] .iter() .for_each(|x| { - parse_param(x).unwrap(); + AbiParser::default().parse_param(x).unwrap(); }); } @@ -514,4 +666,95 @@ mod tests { ]) .unwrap(); } + + #[test] + fn can_substitute_structs() { + let abi = parse(&[ + "struct MyStruct {int y; address _addr;}", + "event FireEvent(MyStruct m, address indexed newOwner)", + ]) + .unwrap(); + assert_eq!( + abi.events["FireEvent"][0].inputs.clone(), + vec![ + EventParam { + name: "m".to_string(), + kind: ParamType::Tuple(vec![ParamType::Int(256), ParamType::Address]), + indexed: false + }, + EventParam { + name: "newOwner".to_string(), + kind: ParamType::Address, + indexed: true + }, + ] + ); + } + + #[test] + fn can_substitute_array_structs() { + let abi = parse(&[ + "struct MyStruct {int y; address _addr;}", + "event FireEvent(MyStruct[] m, MyStruct[10] m2)", + ]) + .unwrap(); + + assert_eq!( + abi.events["FireEvent"][0].inputs.clone(), + vec![ + EventParam { + name: "m".to_string(), + kind: ParamType::Array(Box::new(ParamType::Tuple(vec![ + ParamType::Int(256), + ParamType::Address + ]))), + indexed: false + }, + EventParam { + name: "m2".to_string(), + kind: ParamType::FixedArray( + Box::new(ParamType::Tuple(vec![ + ParamType::Int(256), + ParamType::Address + ])), + 10 + ), + indexed: false + }, + ] + ); + } + + #[test] + fn can_substitute_nested_array_structs() { + let abi = parse(&[ + "struct MyStruct {int y; address _addr;}", + "event FireEvent(MyStruct[] m, MyStructWrapper w)", + "struct MyStructWrapper {MyStruct y; int y; address _addr;}", + ]) + .unwrap(); + + assert_eq!( + abi.events["FireEvent"][0].inputs.clone(), + vec![ + EventParam { + name: "m".to_string(), + kind: ParamType::Array(Box::new(ParamType::Tuple(vec![ + ParamType::Int(256), + ParamType::Address + ]))), + indexed: false + }, + EventParam { + name: "w".to_string(), + kind: ParamType::Tuple(vec![ + ParamType::Tuple(vec![ParamType::Int(256), ParamType::Address]), + ParamType::Int(256), + ParamType::Address + ]), + indexed: false + }, + ] + ); + } } diff --git a/ethers-core/src/abi/mod.rs b/ethers-core/src/abi/mod.rs index 2a1317b7..b09c91be 100644 --- a/ethers-core/src/abi/mod.rs +++ b/ethers-core/src/abi/mod.rs @@ -8,8 +8,14 @@ pub use ethabi::*; mod tokens; pub use tokens::{Detokenize, InvalidOutputType, Tokenizable, TokenizableItem, Tokenize}; +pub mod struct_def; +pub use struct_def::SolStruct; + +mod error; +pub use error::ParseError; + mod human_readable; -pub use human_readable::{parse as parse_abi, ParseError}; +pub use human_readable::{parse as parse_abi, AbiParser}; /// Extension trait for `ethabi::Function`. pub trait FunctionExt { diff --git a/ethers-core/src/abi/struct_def.rs b/ethers-core/src/abi/struct_def.rs new file mode 100644 index 00000000..0440130c --- /dev/null +++ b/ethers-core/src/abi/struct_def.rs @@ -0,0 +1,476 @@ +//! Solidity struct definition parsing support +use crate::abi::error::{bail, format_err, Result}; +use crate::abi::human_readable::{is_whitespace, parse_identifier}; +use crate::abi::{param_type::Reader, ParamType}; + +/// A field declaration inside a struct +#[derive(Debug, Clone, PartialEq)] +pub struct FieldDeclaration { + name: String, + ty: FieldType, +} + +impl FieldDeclaration { + pub fn name(&self) -> &str { + &self.name + } + + pub fn r#type(&self) -> &FieldType { + &self.ty + } +} + +/// A field declaration inside a struct +#[derive(Debug, Clone, PartialEq)] +pub enum FieldType { + /// Represents elementary types, see [`ParamType`] + Elementary(ParamType), + /// A non elementary type field, treated as user defined struct + Struct(StructFieldType), + // Array of user defined type + StructArray(StructFieldType), + // Array with fixed size of user defined type + FixedStructArray(StructFieldType, usize), + /// Mapping + Mapping(Box), +} + +impl FieldType { + pub fn is_mapping(&self) -> bool { + matches!(self, FieldType::Mapping(_)) + } + + pub(crate) fn as_struct(&self) -> Option<&StructFieldType> { + match self { + FieldType::Struct(s) + | FieldType::StructArray(s) + | FieldType::FixedStructArray(s, _) => Some(s), + _ => None, + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct MappingType { + /// key types can be elementary and `bytes` and `string` + /// + /// Valid `ParamType` variants are: + /// `Address`, `Bytes`, `Int`, `UInt`, `Bool`, `String`, `FixedBytes`, + key_type: ParamType, + /// The value type of this mapping + value_type: FieldType, +} + +/// Represents a elementary field declaration inside a struct with a : `int x` +#[derive(Debug, Clone, PartialEq)] +pub struct StructFieldDeclaration { + /// The name of the field + name: String, + /// The type of the field + ty: StructFieldType, +} + +/// How the type of a struct field is referenced +#[derive(Debug, Clone, PartialEq)] +pub struct StructFieldType { + /// The name of the struct + name: String, + /// All previous projections up until the name + /// + /// For `MostOuter.Outer.` this is `vec!["MostOuter", "Outer"]` + projections: Vec, +} + +impl StructFieldType { + pub fn name(&self) -> &str { + &self.name + } + + /// Parse a struct field declaration + /// + /// The parsed field is either a `Struct`, `StructArray` or `FixedStructArray` + pub fn parse(mut input: &str) -> Result { + let mut projections = Vec::new(); + + loop { + let ty = parse_identifier(&mut input)?; + let mut chars = input.chars(); + match chars.next() { + None => { + return Ok(FieldType::Struct(StructFieldType { + name: ty, + projections, + })) + } + Some(' ') | Some('\t') | Some('[') => { + // array + let mut size = String::new(); + loop { + match chars.next() { + None => bail!("Expected Array `{}`", input), + Some(' ') | Some('\t') => { + if !size.is_empty() { + bail!( + "Illegal whitespace in array size after `{}` in `{}`", + size, + input + ) + } + } + Some(']') => { + let ty = StructFieldType { + name: ty, + projections, + }; + + return if size.is_empty() { + Ok(FieldType::StructArray(ty)) + } else { + let size = size.parse().map_err(|_| { + format_err!("Illegal array size `{}` at `{}`", size, input) + })?; + Ok(FieldType::FixedStructArray(ty, size)) + }; + } + Some(c) => { + if c.is_numeric() { + size.push(c); + } else { + bail!("Illegal char `{}` inner array `{}`", c, input) + } + } + } + } + } + Some('.') => { + input = chars.as_str(); + projections.push(ty); + } + Some(c) => { + bail!("Illegal char `{}` at `{}`", c, input) + } + } + } + } +} + +/// Represents a solidity struct +#[derive(Debug, Clone, PartialEq)] +pub struct SolStruct { + name: String, + fields: Vec, +} + +impl SolStruct { + /// Parse a solidity struct definition + /// + /// # Example + /// + /// ``` + /// # use ethers::abi::SolStruct; + /// let s = SolStruct::parse("struct MyStruct { uint x; uint y;}").unwrap(); + /// ``` + pub fn parse(s: &str) -> Result { + let mut input = s.trim(); + if !input.starts_with("struct ") { + bail!("Not a struct `{}`", input) + } + input = &input[6..]; + + let name = parse_identifier(&mut input)?; + + let mut chars = input.chars(); + + loop { + match chars.next() { + None => bail!("Expected struct"), + Some('{') => { + // strip opening and trailing curly bracket + input = chars + .as_str() + .trim() + .strip_suffix('}') + .ok_or_else(|| format_err!("Expected closing `}}` in `{}`", s))? + .trim_end(); + + let fields = if input.is_empty() { + Vec::new() + } else { + input + .split(';') + .filter(|s| !s.is_empty()) + .map(parse_struct_field) + .collect::, _>>()? + }; + return Ok(SolStruct { name, fields }); + } + Some(' ') | Some('\t') => { + continue; + } + Some(c) => { + bail!("Illegal char `{}` at `{}`", c, s) + } + } + } + } + + /// Name of this struct + pub fn name(&self) -> &str { + &self.name + } + + /// All the fields of this struct + pub fn fields(&self) -> &Vec { + &self.fields + } +} + +/// Strips the identifier of field declaration from the input and returns it +fn strip_field_identifier(input: &mut &str) -> Result { + let mut iter = input.trim_end().rsplitn(2, is_whitespace); + let name = iter + .next() + .ok_or_else(|| format_err!("Expected field identifier")) + .map(|mut s| parse_identifier(&mut s))??; + *input = iter + .next() + .ok_or_else(|| format_err!("Expected field type in `{}`", input))? + .trim_end(); + Ok(name) +} + +/// Parses a field definition such as ` ? ` +fn parse_struct_field(s: &str) -> Result { + let mut input = s.trim_start(); + + if !input.starts_with("mapping") { + // strip potential defaults + input = input + .split('=') + .next() + .ok_or_else(|| format_err!("Expected field definition `{}`", s))? + .trim_end(); + } + let name = strip_field_identifier(&mut input)?; + Ok(FieldDeclaration { + name, + ty: parse_field_type(input)?, + }) +} + +fn parse_field_type(s: &str) -> Result { + let mut input = s.trim_start(); + if input.starts_with("mapping") { + return Ok(FieldType::Mapping(Box::new(parse_mapping(input)?))); + } + if input.ends_with(" payable") { + // special case for `address payable` + input = input[..input.len() - 7].trim_end(); + } + if let Ok(ty) = Reader::read(input) { + Ok(FieldType::Elementary(ty)) + } else { + // parsing elementary datatype failed, try struct + StructFieldType::parse(input.trim_end()) + } +} + +/// parse a mapping declaration +fn parse_mapping(s: &str) -> Result { + let mut input = s.trim(); + if !input.starts_with("mapping") { + bail!("Not a mapping `{}`", input) + } + input = &input[7..].trim_start(); + let mut iter = input + .trim_start_matches('(') + .trim_end_matches(')') + .splitn(2, "=>"); + let key_type = iter + .next() + .ok_or_else(|| format_err!("Expected mapping key type at `{}`", input)) + .map(str::trim) + .map(Reader::read)??; + + if let ParamType::Array(_) | ParamType::FixedArray(_, _) | ParamType::Tuple(_) = &key_type { + bail!( + "Expected elementary mapping key type at `{}` got {:?}", + input, + key_type + ) + } + + let value_type = iter + .next() + .ok_or_else(|| format_err!("Expected mapping value type at `{}`", input)) + .map(str::trim) + .map(parse_field_type)??; + + Ok(MappingType { + key_type, + value_type, + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn can_parse_simple_struct() { + assert_eq!( + SolStruct::parse("struct MyStruct{uint256 x; uint256 y;}").unwrap(), + SolStruct { + name: "MyStruct".to_string(), + fields: vec![ + FieldDeclaration { + name: "x".to_string(), + ty: FieldType::Elementary(ParamType::Uint(256)), + }, + FieldDeclaration { + name: "y".to_string(), + ty: FieldType::Elementary(ParamType::Uint(256)), + }, + ], + } + ); + } + + #[test] + fn can_parse_struct() { + assert_eq!( + SolStruct::parse("struct MyStruct{uint256 x; uint256 y; bytes[] _b; string[10] s; mapping(address => uint256) m;}").unwrap(), + SolStruct { + name: "MyStruct".to_string(), + fields: vec![ + FieldDeclaration { + name: "x".to_string(), + ty: FieldType::Elementary(ParamType::Uint(256)), + }, + FieldDeclaration { + name: "y".to_string(), + ty: FieldType::Elementary(ParamType::Uint(256)), + }, + FieldDeclaration { + name: "_b".to_string(), + ty: FieldType::Elementary(ParamType::Array(Box::new(ParamType::Bytes))), + }, + FieldDeclaration { + name: "s".to_string(), + ty: FieldType::Elementary(ParamType::FixedArray(Box::new(ParamType::String), 10)), + }, + FieldDeclaration { + name: "m".to_string(), + ty: FieldType::Mapping(Box::new( + MappingType { + key_type: ParamType::Address, + value_type: FieldType::Elementary(ParamType::Uint(256)) + } + )), + }, + ], + } + ); + } + + #[test] + fn can_parse_struct_projections() { + assert_eq!( + SolStruct::parse("struct MyStruct{uint256 x; Some.Other.Inner _other;}").unwrap(), + SolStruct { + name: "MyStruct".to_string(), + fields: vec![ + FieldDeclaration { + name: "x".to_string(), + ty: FieldType::Elementary(ParamType::Uint(256)), + }, + FieldDeclaration { + name: "_other".to_string(), + ty: FieldType::Struct(StructFieldType { + name: "Inner".to_string(), + projections: vec!["Some".to_string(), "Other".to_string()] + }), + }, + ], + } + ); + } + + #[test] + fn can_parse_structs() { + [ + "struct Demo {bytes x; address payable d;}", + "struct Demo2 {bytes[10] x; mapping(bool=> bool) d; int256 value;}", + "struct Struct { Other.MyStruct s; bool voted; address delegate; uint vote; }", + ] + .iter() + .for_each(|s| { + SolStruct::parse(s).unwrap(); + }); + } + + #[test] + fn can_parse_mapping_type() { + assert_eq!( + parse_mapping("mapping(string=> string)").unwrap(), + MappingType { + key_type: ParamType::String, + value_type: FieldType::Elementary(ParamType::String) + } + ); + } + + #[test] + fn can_parse_nested_mappings() { + assert_eq!( + parse_mapping("mapping(string=> mapping(string=> string))").unwrap(), + MappingType { + key_type: ParamType::String, + value_type: FieldType::Mapping(Box::new(MappingType { + key_type: ParamType::String, + value_type: FieldType::Elementary(ParamType::String), + })), + } + ); + } + + #[test] + fn can_detect_illegal_mappings_key_type() { + [ + "mapping(string[]=> mapping(string=> string))", + "mapping(bytes[10] => bool)", + "mapping(uint256[10] => bool)", + "mapping(Item=> bool)", + "mapping(Item[]=> mapping(address => bool))", + ] + .iter() + .for_each(|s| { + assert!(parse_mapping(s).is_err()); + }); + } + + #[test] + fn can_parse_mappings() { + [ + "mapping(string=> mapping(string=> string))", + "mapping(string=> mapping(string=> mapping(string=> mapping(string=> string))))", + "mapping(bool=> bool)", + "mapping(bytes32 => bool)", + "mapping(bytes=> bool)", + "mapping(uint256=> mapping(address => bool))", + ] + .iter() + .for_each(|s| { + parse_mapping(s).unwrap(); + }); + } + + #[test] + fn can_strip_field_ident() { + let mut s = "uint256 _myvar, + "; + let name = strip_field_identifier(&mut s).unwrap(); + assert_eq!("_myvar", name); + assert_eq!("uint256", s); + } +}