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
This commit is contained in:
parent
e9e26f5e0c
commit
cf8e2391c8
|
@ -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<T, E = ParseError> = std::result::Result<T, E>;
|
||||
|
||||
#[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;
|
|
@ -1,96 +1,171 @@
|
|||
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,
|
||||
};
|
||||
|
||||
/// 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<Abi, ParseError> {
|
||||
let mut abi = Abi {
|
||||
/// A parser that turns a "human readable abi" into a `Abi`
|
||||
pub struct AbiParser {
|
||||
abi: Abi,
|
||||
/// solidity structs
|
||||
structs: HashMap<String, SolStruct>,
|
||||
/// solidity structs as tuples
|
||||
struct_tuples: HashMap<String, Vec<ParamType>>,
|
||||
}
|
||||
|
||||
impl AbiParser {
|
||||
/// Parses a "human readable abi" string
|
||||
pub fn parse_str(self, s: &str) -> Result<Abi> {
|
||||
self.parse(&s.lines().collect::<Vec<_>>())
|
||||
}
|
||||
|
||||
/// 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<Abi> {
|
||||
// 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::<VecDeque<_>>();
|
||||
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<SolStruct>) -> Self {
|
||||
Self {
|
||||
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));
|
||||
},
|
||||
structs: structs
|
||||
.into_iter()
|
||||
.map(|s| (s.name().to_string(), s))
|
||||
.collect(),
|
||||
struct_tuples: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(abi)
|
||||
}
|
||||
|
||||
/// Parses an identifier like event or function name
|
||||
fn parse_identifier(input: &mut &str) -> Result<String, ParseError> {
|
||||
let mut chars = input.trim_start().chars();
|
||||
let mut name = String::new();
|
||||
let c = chars
|
||||
.next()
|
||||
.ok_or(ParseError::ParseError(super::Error::InvalidData))?;
|
||||
if is_first_ident_char(c) {
|
||||
name.push(c);
|
||||
loop {
|
||||
match chars.clone().next() {
|
||||
Some(c) if is_ident_char(c) => {
|
||||
chars.next();
|
||||
name.push(c);
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
*input = chars.as_str();
|
||||
Ok(name)
|
||||
}
|
||||
|
||||
/// Parses a solidity event declaration from `event <name> (args*) anonymous?`
|
||||
fn parse_event(mut event: &str) -> Result<Event, ParseError> {
|
||||
event = event.trim();
|
||||
/// Parses a solidity event declaration from `event <name> (args*) anonymous?`
|
||||
fn parse_event(&self, s: &str) -> Result<Event> {
|
||||
let mut event = s.trim();
|
||||
if !event.starts_with("event ") {
|
||||
return Err(ParseError::ParseError(super::Error::InvalidData));
|
||||
bail!("Not an event `{}`", s)
|
||||
}
|
||||
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)),
|
||||
None => bail!("Expected event"),
|
||||
Some('(') => {
|
||||
event = chars.as_str().trim();
|
||||
let mut anonymous = false;
|
||||
|
@ -101,14 +176,14 @@ fn parse_event(mut event: &str) -> Result<Event, ParseError> {
|
|||
event = event
|
||||
.trim()
|
||||
.strip_suffix(')')
|
||||
.ok_or(ParseError::ParseError(super::Error::InvalidData))?;
|
||||
.ok_or_else(|| format_err!("Expected closing `)` in `{}`", s))?;
|
||||
|
||||
let inputs = if event.is_empty() {
|
||||
Vec::new()
|
||||
} else {
|
||||
event
|
||||
.split(',')
|
||||
.map(parse_event_arg)
|
||||
.map(|e| self.parse_event_arg(e))
|
||||
.collect::<Result<Vec<_>, _>>()?
|
||||
};
|
||||
return Ok(Event {
|
||||
|
@ -120,68 +195,61 @@ fn parse_event(mut event: &str) -> Result<Event, ParseError> {
|
|||
Some(' ') | Some('\t') => {
|
||||
continue;
|
||||
}
|
||||
_ => {
|
||||
return Err(ParseError::ParseError(super::Error::InvalidData));
|
||||
Some(c) => {
|
||||
bail!("Illegal char `{}` at `{}`", c, s)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a single event param
|
||||
fn parse_event_arg(input: &str) -> Result<EventParam, ParseError> {
|
||||
/// Parse a single event param
|
||||
fn parse_event_arg(&self, input: &str) -> Result<EventParam> {
|
||||
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))?;
|
||||
.ok_or_else(|| format_err!("Empty event param at `{}`", input))?;
|
||||
|
||||
let type_str;
|
||||
if let Some(mid) = iter.next() {
|
||||
let kind;
|
||||
if let Some(ty) = iter.next() {
|
||||
if mid != "indexed" {
|
||||
return Err(ParseError::ParseError(super::Error::InvalidData));
|
||||
bail!("Expected indexed keyword at `{}`", input)
|
||||
}
|
||||
indexed = true;
|
||||
kind = Reader::read(ty)?;
|
||||
type_str = ty;
|
||||
} else {
|
||||
if name == "indexed" {
|
||||
indexed = true;
|
||||
name = "";
|
||||
}
|
||||
kind = Reader::read(mid)?;
|
||||
type_str = mid;
|
||||
}
|
||||
Ok(EventParam {
|
||||
name: name.to_owned(),
|
||||
kind,
|
||||
indexed,
|
||||
})
|
||||
} else {
|
||||
type_str = name;
|
||||
name = "";
|
||||
}
|
||||
|
||||
Ok(EventParam {
|
||||
name: "".to_owned(),
|
||||
name: name.to_string(),
|
||||
indexed,
|
||||
kind: Reader::read(name)?,
|
||||
kind: self.parse_type(type_str)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_function(mut input: &str) -> Result<Function, ParseError> {
|
||||
input = input.trim();
|
||||
fn parse_function(&mut self, s: &str) -> Result<Function> {
|
||||
let mut input = s.trim();
|
||||
if !input.starts_with("function ") {
|
||||
return Err(ParseError::ParseError(super::Error::InvalidData));
|
||||
bail!("Not a function `{}`", input)
|
||||
}
|
||||
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))?
|
||||
.ok_or_else(|| format_err!("Invalid function declaration at `{}`", s))?
|
||||
.trim_end();
|
||||
|
||||
let mut parens_iter = parens.rsplitn(2, ')');
|
||||
|
@ -198,23 +266,15 @@ fn parse_function(mut input: &str) -> Result<Function, ParseError> {
|
|||
.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 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(ParseError::ParseError(super::Error::InvalidData))?;
|
||||
params
|
||||
.split(',')
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(parse_param)
|
||||
.collect::<Result<Vec<_>, _>>()?
|
||||
.ok_or_else(|| format_err!("Expected parentheses at `{}`", s))?;
|
||||
self.parse_params(params)?
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
@ -229,30 +289,135 @@ fn parse_function(mut input: &str) -> Result<Function, ParseError> {
|
|||
state_mutability,
|
||||
constant: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_constructor(mut input: &str) -> Result<Constructor, ParseError> {
|
||||
input = input.trim();
|
||||
fn parse_params(&self, s: &str) -> Result<Vec<Param>> {
|
||||
s.split(',')
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(|s| self.parse_param(s))
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
}
|
||||
|
||||
fn parse_type(&self, type_str: &str) -> Result<ParamType> {
|
||||
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<Constructor> {
|
||||
let mut input = s.trim();
|
||||
if !input.starts_with("constructor") {
|
||||
return Err(ParseError::ParseError(super::Error::InvalidData));
|
||||
bail!("Not a constructor `{}`", input)
|
||||
}
|
||||
input = input[11..]
|
||||
.trim_start()
|
||||
.strip_prefix('(')
|
||||
.ok_or(ParseError::ParseError(super::Error::InvalidData))?;
|
||||
.ok_or_else(|| format_err!("Expected leading `(` in `{}`", s))?;
|
||||
|
||||
let params = input
|
||||
.rsplitn(2, ')')
|
||||
.last()
|
||||
.ok_or(ParseError::ParseError(super::Error::InvalidData))?;
|
||||
.ok_or_else(|| format_err!("Expected closing `)` in `{}`", s))?;
|
||||
|
||||
let inputs = params
|
||||
.split(',')
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(parse_param)
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let inputs = self.parse_params(params)?;
|
||||
|
||||
Ok(Constructor { inputs })
|
||||
}
|
||||
|
||||
fn parse_param(&self, param: &str) -> Result<Param> {
|
||||
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
|
||||
///
|
||||
/// ```
|
||||
/// use ethers::abi::parse_abi;
|
||||
///
|
||||
/// let abi = parse_abi(&[
|
||||
/// "function x() external view returns (uint256)",
|
||||
/// ]).unwrap();
|
||||
/// ```
|
||||
pub fn parse(input: &[&str]) -> Result<Abi> {
|
||||
AbiParser::default().parse(input)
|
||||
}
|
||||
|
||||
/// Parses an identifier like event or function name
|
||||
pub(crate) fn parse_identifier(input: &mut &str) -> Result<String> {
|
||||
let mut chars = input.trim_start().chars();
|
||||
let mut name = String::new();
|
||||
let c = chars
|
||||
.next()
|
||||
.ok_or_else(|| format_err!("Empty identifier in `{}`", input))?;
|
||||
if is_first_ident_char(c) {
|
||||
name.push(c);
|
||||
loop {
|
||||
match chars.clone().next() {
|
||||
Some(c) if is_ident_char(c) => {
|
||||
chars.next();
|
||||
name.push(c);
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
if name.is_empty() {
|
||||
return Err(ParseError::ParseError(super::Error::InvalidName(
|
||||
input.to_string(),
|
||||
)));
|
||||
}
|
||||
*input = chars.as_str();
|
||||
Ok(name)
|
||||
}
|
||||
|
||||
fn detect_state_mutability(s: &str) -> StateMutability {
|
||||
|
@ -267,42 +432,15 @@ fn detect_state_mutability(s: &str) -> StateMutability {
|
|||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
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
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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<MappingType>),
|
||||
}
|
||||
|
||||
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.<name>` this is `vec!["MostOuter", "Outer"]`
|
||||
projections: Vec<String>,
|
||||
}
|
||||
|
||||
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<FieldType> {
|
||||
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<FieldDeclaration>,
|
||||
}
|
||||
|
||||
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<Self> {
|
||||
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::<Result<Vec<_>, _>>()?
|
||||
};
|
||||
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<FieldDeclaration> {
|
||||
&self.fields
|
||||
}
|
||||
}
|
||||
|
||||
/// Strips the identifier of field declaration from the input and returns it
|
||||
fn strip_field_identifier(input: &mut &str) -> Result<String> {
|
||||
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 `<type> <storageLocation>? <name>`
|
||||
fn parse_struct_field(s: &str) -> Result<FieldDeclaration> {
|
||||
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<FieldType> {
|
||||
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<MappingType> {
|
||||
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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue