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::abi::parse_abi; /// /// let abi = parse_abi(&[ /// "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 line in input { if line.contains("function") { let function = parse_function(&line)?; abi.functions .entry(function.name.clone()) .or_default() .push(function); } else if line.contains("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 { let split: Vec<&str> = event.split("event ").collect(); let split: Vec<&str> = split[1].split('(').collect(); let name = split[0].trim_end(); 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::, _>>()? } 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 { 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 { 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(); let inputs = args .into_iter() .filter(|x| !x.is_empty()) .filter(|x| !x.contains("returns")) .map(|x| parse_param(x)) .collect::, _>>()?; // return value let outputs: Vec = if split.len() > 2 { let ret = split[2].strip_suffix(")").expect("no right paren"); let ret: Vec<&str> = ret.split(", ").collect(); ret.into_iter() // remove modifiers etc .filter(|x| !x.is_empty()) .map(|x| parse_param(x)) .collect::, _>>()? } 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 { let mut param = param .split(' ') .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(); } 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 foo(address[] memory, bytes memory) returns (bytes memory)", "function bar(uint256[] memory x)", "function bar()", ] .iter() .for_each(|x| { parse_function(x).unwrap(); }); } #[test] fn can_parse_params() { [ "address x", "address", "bytes memory y", "bytes memory", "bytes32[] memory", "bytes32[] memory z", ] .iter() .for_each(|x| { parse_param(x).unwrap(); }); } #[test] fn can_read_backslashes() { parse(&[ "\"function setValue(string)\"", "\"function getValue() external view (string)\"", ]) .unwrap(); } }