From b036e626f3b77998917310600e3d400559600c96 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 16 Mar 2021 17:51:38 +0100 Subject: [PATCH 1/2] fix: support parsing of nested tuple arrays --- ethabi/src/param_type/reader.rs | 74 +++++++++++++++++++++++---------- 1 file changed, 52 insertions(+), 22 deletions(-) diff --git a/ethabi/src/param_type/reader.rs b/ethabi/src/param_type/reader.rs index ee50edf06..68b3cf0fc 100644 --- a/ethabi/src/param_type/reader.rs +++ b/ethabi/src/param_type/reader.rs @@ -24,12 +24,15 @@ impl Reader { let mut subtypes = Vec::new(); let mut subtuples = Vec::new(); let mut nested = 0isize; + let mut top_level_paren_open = 0usize; let mut last_item = 1; + let mut chars = name.chars().enumerate(); // Iterate over name and build the nested tuple structure - for (pos, c) in name.chars().enumerate() { + while let Some((mut pos, c)) = chars.next() { match c { '(' => { + top_level_paren_open = pos; nested += 1; // If an '(' is encountered within the tuple // insert an empty subtuples vector to be filled @@ -40,6 +43,7 @@ impl Reader { } ')' => { nested -= 1; + // End parsing and return an error if parentheses aren't symmetrical if nested < 0 { return Err(Error::InvalidName(name.to_owned())); @@ -51,29 +55,39 @@ impl Reader { } // If the item is in the top level of the tuple insert it into subtypes else if nested == 0 { + // check for trailing brackets that indicate array of tuples let sub = &name[last_item..pos]; let subtype = Reader::read(sub)?; subtypes.push(subtype); last_item = pos + 1; } - // If the item is in a sublevel of the tuple: - // insert it into the subtuple vector for the current depth level - // process all the subtuple vectors created into sub tuples and insert - // them into subtypes + // If the item is in a sublevel of the tuple else if nested > 0 { - let sub = &name[last_item..pos]; - let subtype = Reader::read(sub)?; - subtuples[(nested - 1) as usize].push(subtype); - let initial_tuple_params = subtuples.remove(0); - let tuple_params = subtuples.into_iter().fold( - initial_tuple_params, - |mut tuple_params, nested_param_set| { - tuple_params.push(ParamType::Tuple(nested_param_set)); - tuple_params - }, - ); - subtypes.push(ParamType::Tuple(tuple_params)); - subtuples = Vec::new(); + // this makes sure trailing brackets are included for the next step + loop { + match chars.clone().next() { + Some((_, ',')) | Some((_, ')')) | None => break, + _ => { + // consume the char and shift position + chars.next(); + pos += 1; + } + } + } + + // parse the nested tuple + let inner_tuple = &name[top_level_paren_open..=pos]; + let subtype = Reader::read(inner_tuple)?; + + if nested > 1 { + subtuples[(nested - 2) as usize].push(subtype); + subtypes.push(ParamType::Tuple(std::mem::replace( + &mut subtuples[(nested - 2) as usize], + Vec::new(), + ))); + } else { + subtypes.push(subtype); + } last_item = pos + 1; } } @@ -111,16 +125,16 @@ impl Reader { name.chars().rev().skip(1).take_while(|c| *c != '[').collect::().chars().rev().collect(); let count = name.chars().count(); - if num.is_empty() { + return if num.is_empty() { // we already know it's a dynamic array! let subtype = Reader::read(&name[..count - 2])?; - return Ok(ParamType::Array(Box::new(subtype))); + Ok(ParamType::Array(Box::new(subtype))) } else { // it's a fixed array. let len = usize::from_str_radix(&num, 10)?; let subtype = Reader::read(&name[..count - num.len() - 2])?; - return Ok(ParamType::FixedArray(Box::new(subtype), len)); - } + Ok(ParamType::FixedArray(Box::new(subtype), len)) + }; } _ => (), } @@ -253,4 +267,20 @@ mod tests { ParamType::Array(Box::new(ParamType::Tuple(vec![ParamType::Uint(256), ParamType::FixedBytes(32)]))) ) } + + #[test] + fn test_read_inner_tuple_array_param() { + use crate::param_type::Writer; + let abi = "((uint256,bytes32)[],address)"; + let read = Reader::read(abi).unwrap(); + + let param = ParamType::Tuple(vec![ + ParamType::Array(Box::new(ParamType::Tuple(vec![ParamType::Uint(256), ParamType::FixedBytes(32)]))), + ParamType::Address, + ]); + + assert_eq!(read, param); + + assert_eq!(abi, Writer::write(¶m)); + } } From 494ac6dde5e8610b3ce2bb6d228f05f5153786b8 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 16 Mar 2021 18:01:20 +0100 Subject: [PATCH 2/2] chore: rustfmt --- ethabi/src/function.rs | 5 +++-- ethabi/src/state_mutability.rs | 6 ++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/ethabi/src/function.rs b/ethabi/src/function.rs index ca54e5dee..4637f8497 100644 --- a/ethabi/src/function.rs +++ b/ethabi/src/function.rs @@ -10,8 +10,9 @@ use std::string::ToString; -use crate::signature::short_signature; -use crate::{decode, encode, Bytes, Error, Param, ParamType, Result, StateMutability, Token}; +use crate::{ + decode, encode, signature::short_signature, Bytes, Error, Param, ParamType, Result, StateMutability, Token, +}; use serde::Deserialize; /// Contract function specification. diff --git a/ethabi/src/state_mutability.rs b/ethabi/src/state_mutability.rs index b3a02b851..969f86237 100644 --- a/ethabi/src/state_mutability.rs +++ b/ethabi/src/state_mutability.rs @@ -1,5 +1,7 @@ -use serde::de::{Error, Visitor}; -use serde::{Deserialize, Deserializer}; +use serde::{ + de::{Error, Visitor}, + Deserialize, Deserializer, +}; use std::fmt; /// Whether a function modifies or reads blockchain state