Skip to content

Commit

Permalink
Merge branch 'master' into td-script-improve
Browse files Browse the repository at this point in the history
  • Loading branch information
tomusdrw committed Jun 3, 2019
2 parents 6583d73 + f4e3ec7 commit 7f6d7b2
Show file tree
Hide file tree
Showing 7 changed files with 129 additions and 18 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ This library encodes function calls and decodes their output.

[Documentation](https://docs.rs/ethabi)

### Disclaimer

This library intends to support only valid ABIs generated by recent Solidity versions. Specifically, we don't intend to support ABIs that are invalid due to known Solidity bugs or by external libraries that don't strictly follow the specification.
Please make sure to pre-process your ABI in case it's not supported before using it with `ethabi`.

### Installation

- via cargo
Expand Down
1 change: 1 addition & 0 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ serde_derive = "1.0"
docopt = "1.0"
ethabi = { version = "8.0.0", path = "../ethabi" }
error-chain = { version = "0.12.1", default-features = false }
tiny-keccak = "1.0"

[features]
backtrace = ["error-chain/backtrace"]
Expand Down
13 changes: 13 additions & 0 deletions cli/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use std::io;
use {ethabi, docopt, hex};
use ethabi::Hash;

error_chain! {
links {
Expand All @@ -13,4 +14,16 @@ error_chain! {
Docopt(docopt::Error);
Hex(hex::FromHexError);
}

errors {
InvalidSignature(signature: Hash) {
description("Invalid signature"),
display("Invalid signature `{}`", signature),
}

AmbiguousEventName(name: String) {
description("More than one event found for name, try providing the full signature"),
display("Ambiguous event name `{}`", name),
}
}
}
59 changes: 50 additions & 9 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ extern crate serde_derive;
#[macro_use]
extern crate error_chain;
extern crate ethabi;
extern crate tiny_keccak;

mod error;

Expand All @@ -16,7 +17,8 @@ use hex::{ToHex, FromHex};
use ethabi::param_type::{ParamType, Reader};
use ethabi::token::{Token, Tokenizer, StrictTokenizer, LenientTokenizer};
use ethabi::{encode, decode, Contract, Function, Event, Hash};
use error::{Error, ResultExt};
use error::{Error, ErrorKind, ResultExt};
use tiny_keccak::Keccak;

pub const ETHABI: &str = r#"
Ethereum ABI coder.
Expand All @@ -27,7 +29,7 @@ Usage:
ethabi encode params [-v <type> <param>]... [-l | --lenient]
ethabi decode function <abi-path> <function-name> <data>
ethabi decode params [-t <type>]... <data>
ethabi decode log <abi-path> <event-name> [-l <topic>]... <data>
ethabi decode log <abi-path> <event-name-or-signature> [-l <topic>]... <data>
ethabi -h | --help
Options:
Expand All @@ -51,7 +53,7 @@ struct Args {
cmd_log: bool,
arg_abi_path: String,
arg_function_name: String,
arg_event_name: String,
arg_event_name_or_signature: String,
arg_param: Vec<String>,
arg_type: Vec<String>,
arg_data: String,
Expand Down Expand Up @@ -89,7 +91,7 @@ fn execute<S, I>(command: I) -> Result<String, Error> where I: IntoIterator<Item
} else if args.cmd_decode && args.cmd_params {
decode_params(&args.arg_type, &args.arg_data)
} else if args.cmd_decode && args.cmd_log {
decode_log(&args.arg_abi_path, &args.arg_event_name, &args.arg_topic, &args.arg_data)
decode_log(&args.arg_abi_path, &args.arg_event_name_or_signature, &args.arg_topic, &args.arg_data)
} else {
unreachable!()
}
Expand All @@ -102,11 +104,31 @@ fn load_function(path: &str, function: &str) -> Result<Function, Error> {
Ok(function)
}

fn load_event(path: &str, event: &str) -> Result<Event, Error> {
fn load_event(path: &str, name_or_signature: &str) -> Result<Event, Error> {
let file = File::open(path)?;
let contract = Contract::load(file)?;
let event = contract.event(event)?.clone();
Ok(event)
let params_start = name_or_signature.find('(');

match params_start {
// It's a signature.
Some(params_start) => {
let name = &name_or_signature[..params_start];
let signature = hash_signature(name_or_signature);
contract.events_by_name(name)?.iter().find(|event|
event.signature() == signature
).cloned().ok_or(ErrorKind::InvalidSignature(signature).into())
}

// It's a name.
None => {
let events = contract.events_by_name(name_or_signature)?;
match events.len() {
0 => unreachable!(),
1 => Ok(events[0].clone()),
_ => Err(ErrorKind::AmbiguousEventName(name_or_signature.to_owned()).into())
}
}
}
}

fn parse_tokens(params: &[(ParamType, &str)], lenient: bool) -> Result<Vec<Token>, Error> {
Expand Down Expand Up @@ -187,8 +209,8 @@ fn decode_params(types: &[String], data: &str) -> Result<String, Error> {
Ok(result)
}

fn decode_log(path: &str, event: &str, topics: &[String], data: &str) -> Result<String, Error> {
let event = load_event(path, event)?;
fn decode_log(path: &str, name_or_signature: &str, topics: &[String], data: &str) -> Result<String, Error> {
let event = load_event(path, name_or_signature)?;
let topics: Vec<Hash> = topics.into_iter()
.map(|t| t.parse() )
.collect::<Result<_, _>>()?;
Expand All @@ -203,6 +225,16 @@ fn decode_log(path: &str, event: &str, topics: &[String], data: &str) -> Result<
Ok(result)
}


fn hash_signature(sig: &str) -> Hash {
let mut result = [0u8; 32];
let data = sig.replace(" ", "").into_bytes();
let mut sponge = Keccak::new_keccak256();
sponge.update(&data);
sponge.finalize(&mut result);
Hash::from_slice(&result)
}

#[cfg(test)]
mod tests {
use super::execute;
Expand Down Expand Up @@ -287,6 +319,15 @@ bool false";
let command = "ethabi decode log ../res/event.abi Event -l 0000000000000000000000000000000000000000000000000000000000000001 0000000000000000000000004444444444444444444444444444444444444444".split(" ");
let expected =
"a true
b 4444444444444444444444444444444444444444";
assert_eq!(execute(command).unwrap(), expected);
}

#[test]
fn log_decode_signature() {
let command = "ethabi decode log ../res/event.abi Event(bool,address) -l 0000000000000000000000000000000000000000000000000000000000000001 0000000000000000000000004444444444444444444444444444444444444444".split(" ");
let expected =
"a true
b 4444444444444444444444444444444444444444";
assert_eq!(execute(command).unwrap(), expected);
}
Expand Down
24 changes: 17 additions & 7 deletions ethabi/src/contract.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::{io, fmt};
use std::collections::HashMap;
use std::collections::hash_map::Values;
use std::iter::Flatten;
use serde::{Deserialize, Deserializer};
use serde::de::{Visitor, SeqAccess};
use serde_json;
Expand All @@ -14,8 +15,8 @@ pub struct Contract {
pub constructor: Option<Constructor>,
/// Contract functions.
pub functions: HashMap<String, Function>,
/// Contract events.
pub events: HashMap<String, Event>,
/// Contract events, maps signature to event.
pub events: HashMap<String, Vec<Event>>,
/// Contract has fallback function.
pub fallback: bool,
}
Expand Down Expand Up @@ -52,7 +53,7 @@ impl<'a> Visitor<'a> for ContractVisitor {
result.functions.insert(func.name.clone(), func);
},
Operation::Event(event) => {
result.events.insert(event.name.clone(), event);
result.events.entry(event.name.clone()).or_default().push(event);
},
Operation::Fallback => {
result.fallback = true;
Expand Down Expand Up @@ -80,9 +81,18 @@ impl Contract {
self.functions.get(name).ok_or_else(|| ErrorKind::InvalidName(name.to_owned()).into())
}

/// Creates event decoder.
/// Get the contract event named `name`, the first if there are multiple.
pub fn event(&self, name: &str) -> errors::Result<&Event> {
self.events.get(name).ok_or_else(|| ErrorKind::InvalidName(name.to_owned()).into())
self.events.get(name).into_iter()
.flatten()
.next()
.ok_or_else(|| ErrorKind::InvalidName(name.to_owned()).into())
}

/// Get all contract events named `name`.
pub fn events_by_name(&self, name: &str) -> errors::Result<&Vec<Event>> {
self.events.get(name)
.ok_or_else(|| ErrorKind::InvalidName(name.to_owned()).into())
}

/// Iterate over all functions of the contract in arbitrary order.
Expand All @@ -92,7 +102,7 @@ impl Contract {

/// Iterate over all events of the contract in arbitrary order.
pub fn events(&self) -> Events {
Events(self.events.values())
Events(self.events.values().flatten())
}

/// Returns true if contract has fallback
Expand All @@ -113,7 +123,7 @@ impl<'a> Iterator for Functions<'a> {
}

/// Contract events interator.
pub struct Events<'a>(Values<'a, String, Event>);
pub struct Events<'a>(Flatten<Values<'a, String, Vec<Event>>>);

impl<'a> Iterator for Events<'a> {
type Item = &'a Event;
Expand Down
44 changes: 42 additions & 2 deletions ethabi/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,20 @@ impl Event {
Ok(result)
}

// Converts param types for indexed parameters to bytes32 where appropriate
// This applies to strings, arrays and bytes to follow the encoding of
// these indexed param types according to
// https://solidity.readthedocs.io/en/develop/abi-spec.html#encoding-of-indexed-event-parameters
fn convert_topic_param_type(&self, kind: &ParamType) -> ParamType {
match kind {
ParamType::String
| ParamType::Bytes
| ParamType::Array(_)
| ParamType::FixedArray(_, _) => ParamType::FixedBytes(32),
_ => kind.clone()
}
}

/// Parses `RawLog` and retrieves all log params from it.
pub fn parse_log(&self, log: RawLog) -> Result<Log> {
let topics = log.topics;
Expand All @@ -124,7 +138,7 @@ impl Event {
};

let topic_types = topic_params.iter()
.map(|p| p.kind.clone())
.map(|p| self.convert_topic_param_type(&p.kind))
.collect::<Vec<ParamType>>();

let flat_topics = topics.into_iter()
Expand Down Expand Up @@ -201,15 +215,38 @@ mod tests {
name: "d".to_owned(),
kind: ParamType::Address,
indexed: true,
}, EventParam {
name: "e".to_owned(),
kind: ParamType::String,
indexed: true,
}, EventParam {
name: "f".to_owned(),
kind: ParamType::Array(Box::new(ParamType::Int(256))),
indexed: true
}, EventParam {
name: "g".to_owned(),
kind: ParamType::FixedArray(Box::new(ParamType::Address), 5),
indexed: true,
}],
anonymous: false,
};

let log = RawLog {
topics: vec![
long_signature("foo", &[ParamType::Int(256), ParamType::Int(256), ParamType::Address, ParamType::Address]),
long_signature("foo", &[
ParamType::Int(256),
ParamType::Int(256),
ParamType::Address,
ParamType::Address,
ParamType::String,
ParamType::Array(Box::new(ParamType::Int(256))),
ParamType::FixedArray(Box::new(ParamType::Address), 5),
]),
"0000000000000000000000000000000000000000000000000000000000000002".parse().unwrap(),
"0000000000000000000000001111111111111111111111111111111111111111".parse().unwrap(),
"00000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".parse().unwrap(),
"00000000000000000bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb".parse().unwrap(),
"00000000000000000ccccccccccccccccccccccccccccccccccccccccccccccc".parse().unwrap(),
],
data:
("".to_owned() +
Expand All @@ -223,6 +260,9 @@ mod tests {
("b".to_owned(), Token::Int("0000000000000000000000000000000000000000000000000000000000000002".into())),
("c".to_owned(), Token::Address("2222222222222222222222222222222222222222".parse().unwrap())),
("d".to_owned(), Token::Address("1111111111111111111111111111111111111111".parse().unwrap())),
("e".to_owned(), Token::FixedBytes("00000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".from_hex().unwrap())),
("f".to_owned(), Token::FixedBytes("00000000000000000bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb".from_hex().unwrap())),
("g".to_owned(), Token::FixedBytes("00000000000000000ccccccccccccccccccccccccccccccccccccccccccccccc".from_hex().unwrap())),
].into_iter().map(|(name, value)| LogParam { name, value }).collect::<Vec<_>>()});
}
}

0 comments on commit 7f6d7b2

Please sign in to comment.