Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: support parsing of nested tuple arrays #1

Merged
merged 2 commits into from
Mar 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions ethabi/src/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
74 changes: 52 additions & 22 deletions ethabi/src/param_type/reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()));
Expand All @@ -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;
}
}
Expand Down Expand Up @@ -111,16 +125,16 @@ impl Reader {
name.chars().rev().skip(1).take_while(|c| *c != '[').collect::<String>().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))
};
}
_ => (),
}
Expand Down Expand Up @@ -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(&param));
}
}
6 changes: 4 additions & 2 deletions ethabi/src/state_mutability.rs
Original file line number Diff line number Diff line change
@@ -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
Expand Down