Skip to content
This repository has been archived by the owner on Oct 19, 2024. It is now read-only.

Commit

Permalink
feat: human readable abi
Browse files Browse the repository at this point in the history
  • Loading branch information
gakonst committed Oct 27, 2020
1 parent c669fbc commit 222622e
Show file tree
Hide file tree
Showing 2 changed files with 289 additions and 0 deletions.
286 changes: 286 additions & 0 deletions ethers-core/src/abi/human_readable.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,286 @@
use super::{
param_type::Reader, Abi, Event, EventParam, Function, Param, ParamType, StateMutability,
};
use std::collections::HashMap;
use thiserror::Error;

/// Parses a "human readable abi" string vector
///
/// ```
/// use ethers::contract::abi;
///
/// let abi = abi::parse([
/// "function x() external view returns (uint256)",
/// ]).unwrap()
/// ```
pub fn parse(input: &[&str]) -> Result<Abi, ParseError> {
let mut abi = Abi {
constructor: None,
functions: HashMap::new(),
events: HashMap::new(),
receive: false,
fallback: false,
};

for line in input {
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("struct") {
panic!("Got tuple");
} else {
panic!("unknown sig")
}
}

Ok(abi)
}

fn parse_event(event: &str) -> Result<Event, ParseError> {
let split: Vec<&str> = event.split("event ").collect();
let split: Vec<&str> = split[1].split('(').collect();
let name = split[0];
let rest = split[1];

let args = rest.replace(")", "");
let anonymous = rest.contains("anonymous");

let inputs = if args.contains(",") {
let args: Vec<&str> = args.split(", ").collect();
args.iter()
.map(|arg| parse_event_arg(arg))
.collect::<Result<Vec<EventParam>, _>>()?
} else {
vec![]
};

Ok(Event {
name: name.to_owned(),
anonymous,
inputs,
})
}

// Parses an event's argument as indexed if neded
fn parse_event_arg(param: &str) -> Result<EventParam, ParseError> {
let tokens: Vec<&str> = param.split(" ").collect();
let kind: ParamType = Reader::read(tokens[0])?;
let (name, indexed) = if tokens.len() == 2 {
(tokens[1], false)
} else {
(tokens[2], true)
};

Ok(EventParam {
name: name.to_owned(),
kind,
indexed,
})
}

fn parse_function(fn_string: &str) -> Result<Function, ParseError> {
let fn_string = fn_string.to_owned();
let delim = if fn_string.starts_with("function ") {
"function "
} else {
" "
};
let split: Vec<&str> = fn_string.split(delim).collect();
let split: Vec<&str> = split[1].split("(").collect();

// function name is the first char
let fn_name = split[0];

// internal args
let args: Vec<&str> = split[1].split(")").collect();
let args: Vec<&str> = args[0].split(", ").collect();
dbg!(&args);
let inputs = args
.into_iter()
.filter(|x| !x.is_empty())
.filter(|x| !x.contains("returns"))
.map(|x| parse_param(x))
.collect::<Result<Vec<Param>, _>>()?;

// return value
let outputs: Vec<Param> = if split.len() > 2 {
let ret = split[2].strip_suffix(")").expect("no right paren");
let ret: Vec<&str> = ret.split(", ").collect();

dbg!(&ret);
ret.into_iter()
// remove modifiers etc
.filter(|x| !x.is_empty())
.map(|x| parse_param(x))
.collect::<Result<Vec<Param>, _>>()?
} else {
vec![]
};

Ok(Function {
name: fn_name.to_owned(),
inputs,
outputs,
// this doesn't really matter
state_mutability: StateMutability::Nonpayable,
})
}

// address x
fn parse_param(param: &str) -> Result<Param, ParseError> {
dbg!(&param);
let mut param = param
.split(" ")
.into_iter()
.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().ok_or(ParseError::Kind)?;
}

Ok(Param {
name: name.to_owned(),
kind,
})
}

#[derive(Error, Debug)]
pub enum ParseError {
#[error("expected data type")]
Kind,

#[error(transparent)]
ParseError(#[from] super::Error),
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn parses_approve() {
let fn_str = "function approve(address _spender, uint256 value) external returns(bool)";
let parsed = 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,);
assert_eq!(parsed.inputs[1].name, "value");
assert_eq!(parsed.inputs[1].kind, ParamType::Uint(256),);
assert_eq!(parsed.outputs[0].name, "");
assert_eq!(parsed.outputs[0].kind, ParamType::Bool);
}

#[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();
assert_eq!(parsed.name, "foo");
assert_eq!(parsed.inputs[0].name, "x");
assert_eq!(
parsed.inputs[0].kind,
ParamType::Array(Box::new(ParamType::Uint(32)))
);
assert_eq!(parsed.outputs[0].name, "");
assert_eq!(parsed.outputs[0].kind, ParamType::Address);
}

#[test]
fn parses_function_empty() {
let fn_str = "function foo()";
let parsed = parse_function(fn_str).unwrap();
assert_eq!(parsed.name, "foo");
assert!(parsed.inputs.is_empty());
assert!(parsed.outputs.is_empty());
}

#[test]
fn parses_event() {
assert_eq!(
parse_event("event Foo(address indexed x, uint y, bytes32[] z)").unwrap(),
Event {
anonymous: false,
name: "Foo".to_owned(),
inputs: vec![
EventParam {
name: "x".to_owned(),
kind: ParamType::Address,
indexed: true
},
EventParam {
name: "y".to_owned(),
kind: ParamType::Uint(256),
indexed: false,
},
EventParam {
name: "z".to_owned(),
kind: ParamType::Array(Box::new(ParamType::FixedBytes(32))),
indexed: false,
},
]
}
);
}

#[test]
fn parses_anonymous_event() {
assert_eq!(
parse_event("event Foo() anonymous").unwrap(),
Event {
anonymous: true,
name: "Foo".to_owned(),
inputs: vec![],
}
);
}

#[test]
fn parse_event_input() {
assert_eq!(
parse_event_arg("address indexed x").unwrap(),
EventParam {
name: "x".to_owned(),
kind: ParamType::Address,
indexed: true
}
);

assert_eq!(
parse_event_arg("address x").unwrap(),
EventParam {
name: "x".to_owned(),
kind: ParamType::Address,
indexed: false
}
);
}

#[test]
fn can_parse_functions() {
[
"function foo(uint256[] memory x) external view returns (address)",
"function bar(uint256[] memory x) returns (address)",
"function bar(uint256[] memory x, uint32 y) returns (address, uint256)",
"function bar(uint256[] memory x)",
"function bar()",
]
.iter()
.for_each(|x| {
parse_function(x).unwrap();
});
}
}
3 changes: 3 additions & 0 deletions ethers-core/src/abi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ pub use ethabi::*;
mod tokens;
pub use tokens::{Detokenize, InvalidOutputType, Tokenizable, TokenizableItem, Tokenize};

mod human_readable;
pub use human_readable::parse as parse_abi;

/// Extension trait for `ethabi::Function`.
pub trait FunctionExt {
/// Compute the method signature in the standard ABI format. This does not
Expand Down

0 comments on commit 222622e

Please sign in to comment.