Skip to content

Commit

Permalink
fix: 🐛 avoid panic when parsing certain ABI param types
Browse files Browse the repository at this point in the history
Inlined eth_abi::param_type::Reader::read method and rewrote it
to use (less efficient, but correct) tokenizing of tuple arguments
before parsing their types.

Closes: #35
  • Loading branch information
ziegfried committed Mar 11, 2020
1 parent 1c61186 commit f4a2d94
Show file tree
Hide file tree
Showing 5 changed files with 379 additions and 78 deletions.
213 changes: 213 additions & 0 deletions test/abi/wasm.test.ts
Expand Up @@ -94,6 +94,219 @@ test('isValidDataType', () => {
expect(isValidDataType('(int27,bool)')).toBe(false);
});

test('parseSignature panic', () => {
expect(
parseFunctionSignature(
`cancelDebtOffer((address,((uint256,address),(uint8,bytes32,bytes32)),(uint256,uint256,address,(uint8,bytes32,bytes32)),(uint256,uint256,address,(uint8,bytes32,bytes32)),(address,address,uint256,address,uint256,address,address,uint256,address,uint256,address,uint256,address,uint256,uint256,address,bytes32,uint256,uint256,(uint8,bytes32,bytes32),(uint8,bytes32,bytes32),(uint8,bytes32,bytes32))))`
)
).toMatchInlineSnapshot(`
Object {
"inputs": Array [
Object {
"components": Array [
Object {
"type": "address",
},
Object {
"components": Array [
Object {
"components": Array [
Object {
"type": "uint256",
},
Object {
"type": "address",
},
],
"type": "tuple",
},
Object {
"components": Array [
Object {
"type": "uint8",
},
Object {
"type": "bytes32",
},
Object {
"type": "bytes32",
},
],
"type": "tuple",
},
],
"type": "tuple",
},
Object {
"components": Array [
Object {
"type": "uint256",
},
Object {
"type": "uint256",
},
Object {
"type": "address",
},
Object {
"components": Array [
Object {
"type": "uint8",
},
Object {
"type": "bytes32",
},
Object {
"type": "bytes32",
},
],
"type": "tuple",
},
],
"type": "tuple",
},
Object {
"components": Array [
Object {
"type": "uint256",
},
Object {
"type": "uint256",
},
Object {
"type": "address",
},
Object {
"components": Array [
Object {
"type": "uint8",
},
Object {
"type": "bytes32",
},
Object {
"type": "bytes32",
},
],
"type": "tuple",
},
],
"type": "tuple",
},
Object {
"components": Array [
Object {
"type": "address",
},
Object {
"type": "address",
},
Object {
"type": "uint256",
},
Object {
"type": "address",
},
Object {
"type": "uint256",
},
Object {
"type": "address",
},
Object {
"type": "address",
},
Object {
"type": "uint256",
},
Object {
"type": "address",
},
Object {
"type": "uint256",
},
Object {
"type": "address",
},
Object {
"type": "uint256",
},
Object {
"type": "address",
},
Object {
"type": "uint256",
},
Object {
"type": "uint256",
},
Object {
"type": "address",
},
Object {
"type": "bytes32",
},
Object {
"type": "uint256",
},
Object {
"type": "uint256",
},
Object {
"components": Array [
Object {
"type": "uint8",
},
Object {
"type": "bytes32",
},
Object {
"type": "bytes32",
},
],
"type": "tuple",
},
Object {
"components": Array [
Object {
"type": "uint8",
},
Object {
"type": "bytes32",
},
Object {
"type": "bytes32",
},
],
"type": "tuple",
},
Object {
"components": Array [
Object {
"type": "uint8",
},
Object {
"type": "bytes32",
},
Object {
"type": "bytes32",
},
],
"type": "tuple",
},
],
"type": "tuple",
},
],
"type": "tuple",
},
],
"name": "cancelDebtOffer",
"type": "function",
}
`);
});

test('getDataSize', () => {
expect(getDataSize('uint')).toMatchInlineSnapshot(`
Object {
Expand Down
141 changes: 139 additions & 2 deletions wasm/ethabi/src/datatypes.rs
@@ -1,4 +1,5 @@
use ethabi::param_type::{ParamType, Reader};
use ethabi::param_type::ParamType;
use ethabi::Error;

pub fn is_valid_param_type(param_type: &ParamType) -> bool {
use ParamType::*;
Expand All @@ -19,7 +20,7 @@ pub fn is_valid_param_type(param_type: &ParamType) -> bool {
}

pub fn parse_param_type(type_str: &String) -> Result<ParamType, String> {
match Reader::read(type_str.as_str()) {
match read(type_str.as_str()) {
Ok(t) => {
if is_valid_param_type(&t) {
Ok(t)
Expand All @@ -31,6 +32,122 @@ pub fn parse_param_type(type_str: &String) -> Result<ParamType, String> {
}
}

pub fn tokenize(args_str: &str) -> Result<Vec<&str>, Error> {
let mut cur_start = 0;
let mut paren_depth = 0;
let mut tokens = Vec::new();

for (i, ch) in args_str.chars().enumerate() {
match ch {
'(' => {
paren_depth += 1;
}
')' => {
paren_depth -= 1;
if paren_depth < 0 {
return Err(Error::Other(format!("Unbalanced parenthesis")));
}
}
',' => {
if paren_depth == 0 {
let t = &args_str[cur_start..i];
tokens.push(t);
cur_start = i + 1;
}
}
_ => {}
};
}
if paren_depth != 0 {
return Err(Error::Other(String::from("Unbalanced parenthesis")));
}
if cur_start < args_str.len() {
let i = args_str.len();
let t = &args_str[cur_start..i];
tokens.push(t);
} else if cur_start > 0 {
return Err(Error::Other(String::from(
"Unexpected end of argument list",
)));
}
Ok(tokens)
}

pub fn read(name: &str) -> Result<ParamType, Error> {
match name.chars().last() {
// check if it is a tuple
Some(')') => {
if !name.starts_with('(') {
return Err(Error::InvalidName(name.to_owned()));
};

let components = tokenize(&name[1..(name.len() - 1)])?;
let subtypes = components
.iter()
.map(|c| match read(c) {
Ok(p) => Ok(Box::new(p)),
Err(e) => Err(e),
})
.collect::<Result<Vec<Box<ParamType>>, Error>>()?;

return Ok(ParamType::Tuple(subtypes));
}
// check if it is a fixed or dynamic array.
Some(']') => {
// take number part
let num: String = name
.chars()
.rev()
.skip(1)
.take_while(|c| *c != '[')
.collect::<String>()
.chars()
.rev()
.collect();

let count = name.chars().count();
if num.is_empty() {
// we already know it's a dynamic array!
let subtype = read(&name[..count - 2])?;
return Ok(ParamType::Array(Box::new(subtype)));
} else {
// it's a fixed array.
let len = usize::from_str_radix(&num, 10)?;
let subtype = read(&name[..count - num.len() - 2])?;
return Ok(ParamType::FixedArray(Box::new(subtype), len));
}
}
_ => (),
}

let result = match name {
"address" => ParamType::Address,
"bytes" => ParamType::Bytes,
"bool" => ParamType::Bool,
"string" => ParamType::String,
"int" => ParamType::Int(256),
"tuple" => ParamType::Tuple(vec![]),
"uint" => ParamType::Uint(256),
s if s.starts_with("int") => {
let len = usize::from_str_radix(&s[3..], 10)?;
ParamType::Int(len)
}
s if s.starts_with("uint") => {
let len = usize::from_str_radix(&s[4..], 10)?;
ParamType::Uint(len)
}
s if s.starts_with("bytes") => {
let len = usize::from_str_radix(&s[5..], 10)?;
ParamType::FixedBytes(len)
}
_ => {
return Err(Error::InvalidName(name.to_owned()));
}
};

Ok(result)
}

/// Get (minimum) size of encoded data for a given parameter type
pub fn get_data_size(param: &ParamType) -> (usize, bool) {
match param {
Expand All @@ -49,3 +166,23 @@ pub fn get_data_size(param: &ParamType) -> (usize, bool) {
}),
}
}

#[cfg(test)]
mod tests {
use super::parse_param_type;
use crate::ParamType;

#[test]
fn test_parse_param_type() {
assert_eq!(
parse_param_type(&"(uint8,bool)".to_string()).unwrap(),
ParamType::Tuple(vec![
Box::new(ParamType::Uint(8)),
Box::new(ParamType::Bool),
])
);
assert!(
parse_param_type(&"((address,((uint256,address),(uint8,bytes32,bytes32)),(uint256,uint256,address,(uint8,bytes32,bytes32)),(uint256,uint256,address,(uint8,bytes32,bytes32)),(address,address,uint256,address,uint256,address,address,uint256,address,uint256,address,uint256,address,uint256,uint256,address,bytes32,uint256,uint256,(uint8,bytes32,bytes32),(uint8,bytes32,bytes32),(uint8,bytes32,bytes32))))".to_string()).is_ok()
);
}
}

0 comments on commit f4a2d94

Please sign in to comment.