Skip to content

Commit

Permalink
Handle multiple events with the same name in one contract (rust-ether…
Browse files Browse the repository at this point in the history
…eum#145)

* Handle multiple events with the same name in one contract

This requires indexing the events by signature
instead of name.

* Use human readable event siganture in cli

* Fix typo

* Map name to multiple events, accept name or signature in cli

* Keep `Contract::event`, separate `events_by_name`

* Remove needless `#[allow(deprecated)]`
  • Loading branch information
leoyvens authored and tomusdrw committed May 30, 2019
1 parent ff86668 commit f4e3ec7
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 16 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.

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

0 comments on commit f4e3ec7

Please sign in to comment.