refactor: make human readable abi parsing more robust (#225)

* refactor: event parser

* refactor: function parser

* refactor: add constructor parser

* refactor: replace parsers

* style: extract event arg parsing into separate method

* fix: add missing returns statement

* chore(clippy): make clippy happy

* fix: support unnamed event argument parsing
This commit is contained in:
Matthias Seitz 2021-03-12 15:57:46 +01:00 committed by GitHub
parent 08cacfeee8
commit f599ae66f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 311 additions and 105 deletions

View File

@ -1,9 +1,11 @@
use super::{
param_type::Reader, Abi, Event, EventParam, Function, Param, ParamType, StateMutability,
};
use std::collections::HashMap; use std::collections::HashMap;
use thiserror::Error; use thiserror::Error;
use super::{
param_type::Reader, Abi, Constructor, Event, EventParam, Function, Param, StateMutability,
};
/// Parses a "human readable abi" string vector /// Parses a "human readable abi" string vector
/// ///
/// ``` /// ```
@ -22,140 +24,290 @@ pub fn parse(input: &[&str]) -> Result<Abi, ParseError> {
fallback: false, fallback: false,
}; };
for line in input { for mut line in input.iter().map(|s| escape_quotes(s)) {
if line.contains("function") { line = line.trim_start();
if line.starts_with("function") {
let function = parse_function(&line)?; let function = parse_function(&line)?;
abi.functions abi.functions
.entry(function.name.clone()) .entry(function.name.clone())
.or_default() .or_default()
.push(function); .push(function);
} else if line.contains("event") { } else if line.starts_with("event") {
let event = parse_event(&line)?; let event = parse_event(line)?;
abi.events abi.events
.entry(event.name.clone()) .entry(event.name.clone())
.or_default() .or_default()
.push(event); .push(event);
} else if line.starts_with("struct") { } else if line.starts_with("constructor") {
panic!("Got tuple"); abi.constructor = Some(parse_constructor(line)?);
} else { } else {
panic!("unknown sig") return Err(ParseError::ParseError(super::Error::InvalidData));
} }
} }
Ok(abi) Ok(abi)
} }
fn parse_event(event: &str) -> Result<Event, ParseError> { /// Parses an identifier like event or function name
let split: Vec<&str> = event.split("event ").collect(); fn parse_identifier(input: &mut &str) -> Result<String, ParseError> {
let split: Vec<&str> = split[1].split('(').collect(); let mut chars = input.trim_start().chars();
let name = split[0].trim_end(); let mut name = String::new();
let rest = split[1]; let c = chars
.next()
let args = rest.replace(")", ""); .ok_or(ParseError::ParseError(super::Error::InvalidData))?;
let anonymous = rest.contains("anonymous"); if is_first_ident_char(c) {
name.push(c);
let inputs = if args.contains(',') { loop {
let args: Vec<&str> = args.split(", ").collect(); match chars.clone().next() {
args.iter() Some(c) if is_ident_char(c) => {
.map(|arg| parse_event_arg(arg)) chars.next();
.collect::<Result<Vec<EventParam>, _>>()? name.push(c);
} else { }
vec![] _ => break,
}; }
}
Ok(Event { }
name: name.to_owned(), *input = chars.as_str();
anonymous, Ok(name)
inputs,
})
} }
// Parses an event's argument as indexed if neded /// Parses a solidity event declaration from `event <name> (args*) anonymous?`
fn parse_event_arg(param: &str) -> Result<EventParam, ParseError> { fn parse_event(mut event: &str) -> Result<Event, ParseError> {
let tokens: Vec<&str> = param.split(' ').collect(); event = event.trim();
let kind: ParamType = Reader::read(tokens[0])?; if !event.starts_with("event ") {
let (name, indexed) = if tokens.len() == 2 { return Err(ParseError::ParseError(super::Error::InvalidData));
(tokens[1], false) }
} else { event = &event[5..];
(tokens[2], true)
};
Ok(EventParam { let name = parse_identifier(&mut event)?;
name: name.to_owned(), if name.is_empty() {
kind, return Err(ParseError::ParseError(super::Error::InvalidName(
indexed, 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::<Result<Vec<_>, _>>()?
};
return Ok(Event {
name,
inputs,
anonymous,
});
}
Some(' ') | Some('\t') => {
continue;
}
_ => {
return Err(ParseError::ParseError(super::Error::InvalidData));
}
}
}
} }
fn parse_function(fn_string: &str) -> Result<Function, ParseError> { /// Parse a single event param
let fn_string = fn_string.to_owned(); fn parse_event_arg(input: &str) -> Result<EventParam, ParseError> {
let delim = if fn_string.starts_with("function ") { let mut iter = input.trim().rsplitn(3, is_whitespace);
"function " 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 { } else {
" " Ok(EventParam {
}; name: "".to_owned(),
let split: Vec<&str> = fn_string.split(delim).collect(); indexed,
let split: Vec<&str> = split[1].split('(').collect(); kind: Reader::read(name)?,
})
}
}
// function name is the first char fn parse_function(mut input: &str) -> Result<Function, ParseError> {
let fn_name = split[0]; 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(),
)));
}
// internal args let mut iter = input.split(" returns");
let args: Vec<&str> = split[1].split(')').collect();
let args: Vec<&str> = args[0].split(", ").collect();
let inputs = args let parens = iter
.into_iter() .next()
.filter(|x| !x.is_empty()) .ok_or(ParseError::ParseError(super::Error::InvalidData))?
.filter(|x| !x.contains("returns")) .trim_end();
.map(|x| parse_param(x))
.collect::<Result<Vec<Param>, _>>()?;
// return value let mut parens_iter = parens.rsplitn(2, ')');
let outputs: Vec<Param> = if split.len() > 2 { let mut modifiers = parens_iter.next();
let ret = split[2].strip_suffix(")").expect("no right paren");
let ret: Vec<&str> = ret.split(", ").collect();
ret.into_iter() let input_params = if let Some(args) = parens_iter.next() {
// remove modifiers etc args
.filter(|x| !x.is_empty())
.map(|x| parse_param(x))
.collect::<Result<Vec<Param>, _>>()?
} else { } else {
vec![] 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::<Result<Vec<_>, _>>()?;
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::<Result<Vec<_>, _>>()?
} else {
Vec::new()
}; };
let state_mutability = modifiers.map(detect_state_mutability).unwrap_or_default();
#[allow(deprecated)] #[allow(deprecated)]
Ok(Function { Ok(Function {
name: fn_name.to_owned(), name,
inputs, inputs,
outputs, outputs,
// this doesn't really matter state_mutability,
state_mutability: StateMutability::NonPayable,
constant: false, constant: false,
}) })
} }
// address x fn parse_constructor(mut input: &str) -> Result<Constructor, ParseError> {
fn parse_param(param: &str) -> Result<Param, ParseError> { input = input.trim();
let mut param = param if !input.starts_with("constructor") {
.split(' ') return Err(ParseError::ParseError(super::Error::InvalidData));
.filter(|x| !x.contains("memory") || !x.contains("calldata"));
let kind = param.next().ok_or(ParseError::Kind)?;
let kind: ParamType = Reader::read(kind).unwrap();
// strip memory/calldata from the name
// e.g. uint256[] memory x
let mut name = param.next().unwrap_or_default();
if name == "memory" || name == "calldata" {
name = param.next().unwrap_or_default();
} }
input = input[11..]
.trim_start()
.strip_prefix('(')
.ok_or(ParseError::ParseError(super::Error::InvalidData))?;
Ok(Param { let params = input
name: name.to_owned(), .rsplitn(2, ')')
kind, .last()
}) .ok_or(ParseError::ParseError(super::Error::InvalidData))?;
let inputs = params
.split(',')
.filter(|s| !s.is_empty())
.map(parse_param)
.collect::<Result<Vec<_>, _>>()?;
Ok(Constructor { inputs })
}
fn detect_state_mutability(s: &str) -> StateMutability {
if s.contains("pure") {
StateMutability::Pure
} else if s.contains("view") {
StateMutability::View
} else if s.contains("payable") {
StateMutability::Payable
} else {
StateMutability::NonPayable
}
}
fn parse_param(param: &str) -> Result<Param, ParseError> {
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 {
matches!(c, 'a'..='z' | 'A'..='Z' | '_')
}
fn is_ident_char(c: char) -> bool {
matches!(c, 'a'..='z' | 'A'..='Z' | '0'..='9' | '_')
}
fn is_whitespace(c: char) -> bool {
matches!(c, ' ' | '\t')
}
fn escape_quotes(input: &str) -> &str {
input.trim_matches(is_whitespace).trim_matches('\"')
} }
#[derive(Error, Debug)] #[derive(Error, Debug)]
@ -170,6 +322,7 @@ pub enum ParseError {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::abi::ParamType;
#[test] #[test]
fn parses_approve() { fn parses_approve() {
@ -207,10 +360,31 @@ mod tests {
assert!(parsed.outputs.is_empty()); assert!(parsed.outputs.is_empty());
} }
#[test]
fn parses_function_payable() {
let fn_str = "function foo() public payable";
let parsed = 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();
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();
assert_eq!(parsed.state_mutability, StateMutability::Pure);
}
#[test] #[test]
fn parses_event() { fn parses_event() {
assert_eq!( assert_eq!(
parse_event("event Foo (address indexed x, uint y, bytes32[] z)").unwrap(), parse_event(&mut "event Foo (address indexed x, uint y, bytes32[] z)").unwrap(),
Event { Event {
anonymous: false, anonymous: false,
name: "Foo".to_owned(), name: "Foo".to_owned(),
@ -218,7 +392,7 @@ mod tests {
EventParam { EventParam {
name: "x".to_owned(), name: "x".to_owned(),
kind: ParamType::Address, kind: ParamType::Address,
indexed: true indexed: true,
}, },
EventParam { EventParam {
name: "y".to_owned(), name: "y".to_owned(),
@ -230,7 +404,7 @@ mod tests {
kind: ParamType::Array(Box::new(ParamType::FixedBytes(32))), kind: ParamType::Array(Box::new(ParamType::FixedBytes(32))),
indexed: false, indexed: false,
}, },
] ],
} }
); );
} }
@ -238,7 +412,7 @@ mod tests {
#[test] #[test]
fn parses_anonymous_event() { fn parses_anonymous_event() {
assert_eq!( assert_eq!(
parse_event("event Foo() anonymous").unwrap(), parse_event(&mut "event Foo() anonymous").unwrap(),
Event { Event {
anonymous: true, anonymous: true,
name: "Foo".to_owned(), name: "Foo".to_owned(),
@ -247,6 +421,38 @@ mod tests {
); );
} }
#[test]
fn parses_unnamed_event() {
assert_eq!(
parse_event(&mut "event Foo(address)").unwrap(),
Event {
anonymous: false,
name: "Foo".to_owned(),
inputs: vec![EventParam {
name: "".to_owned(),
kind: ParamType::Address,
indexed: false,
}],
}
);
}
#[test]
fn parses_unnamed_indexed_event() {
assert_eq!(
parse_event(&mut "event Foo(address indexed)").unwrap(),
Event {
anonymous: false,
name: "Foo".to_owned(),
inputs: vec![EventParam {
name: "".to_owned(),
kind: ParamType::Address,
indexed: true,
}],
}
);
}
#[test] #[test]
fn parse_event_input() { fn parse_event_input() {
assert_eq!( assert_eq!(
@ -254,7 +460,7 @@ mod tests {
EventParam { EventParam {
name: "x".to_owned(), name: "x".to_owned(),
kind: ParamType::Address, kind: ParamType::Address,
indexed: true indexed: true,
} }
); );
@ -263,7 +469,7 @@ mod tests {
EventParam { EventParam {
name: "x".to_owned(), name: "x".to_owned(),
kind: ParamType::Address, kind: ParamType::Address,
indexed: false indexed: false,
} }
); );
} }
@ -304,7 +510,7 @@ mod tests {
fn can_read_backslashes() { fn can_read_backslashes() {
parse(&[ parse(&[
"\"function setValue(string)\"", "\"function setValue(string)\"",
"\"function getValue() external view (string)\"", "\"function getValue() external view returns(string)\"",
]) ])
.unwrap(); .unwrap();
} }

View File

@ -11,7 +11,7 @@ abigen!(
SimpleContract, SimpleContract,
r#"[ r#"[
function setValue(string) function setValue(string)
function getValue() external view (string) function getValue() external view returns (string)
event ValueChanged(address indexed author, string oldValue, string newValue) event ValueChanged(address indexed author, string oldValue, string newValue)
]"#, ]"#,
event_derives(serde::Deserialize, serde::Serialize) event_derives(serde::Deserialize, serde::Serialize)