Skip to content
This repository has been archived by the owner on Oct 19, 2024. It is now read-only.

Commit

Permalink
Event aliasing for contract bindings (#425)
Browse files Browse the repository at this point in the history
* contracts: enable event aliases for Abigen

* contract: unit tests for event aliases

* contract: cleanup expand_event function

* Address pr suggestions

* contracts: remove unnecessary clone

Co-authored-by: Matthias Seitz <[email protected]>

* Make clippy happy

Co-authored-by: Matthias Seitz <[email protected]>
  • Loading branch information
WilfredTA and mattsse authored Sep 3, 2021
1 parent 520645c commit 32ad5a6
Show file tree
Hide file tree
Showing 3 changed files with 160 additions and 12 deletions.
10 changes: 10 additions & 0 deletions ethers-contract/ethers-contract-abigen/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ pub(crate) struct Context {

/// Derives added to event structs and enums.
event_derives: Vec<Path>,

/// Manually specified event aliases.
event_aliases: BTreeMap<String, Ident>,
}

impl Context {
Expand Down Expand Up @@ -151,6 +154,12 @@ impl Context {
}
}

let mut event_aliases = BTreeMap::new();
for (signature, alias) in args.event_aliases.into_iter() {
let alias = syn::parse_str(&alias)?;
event_aliases.insert(signature, alias);
}

let event_derives = args
.event_derives
.iter()
Expand All @@ -167,6 +176,7 @@ impl Context {
contract_name,
method_aliases,
event_derives,
event_aliases,
})
}
}
147 changes: 135 additions & 12 deletions ethers-contract/ethers-contract-abigen/src/contract/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ impl Context {
let variants = sorted_events
.values()
.flatten()
.map(expand_struct_name)
.map(|e| expand_struct_name(e, self.event_aliases.get(&e.abi_signature()).cloned()))
.collect::<Vec<_>>();

let enum_name = self.expand_event_enum_name();
Expand Down Expand Up @@ -124,7 +124,10 @@ impl Context {
let ty = if iter.next().is_some() {
self.expand_event_enum_name()
} else {
expand_struct_name(event)
expand_struct_name(
event,
self.event_aliases.get(&event.abi_signature()).cloned(),
)
};

quote! {
Expand Down Expand Up @@ -220,12 +223,18 @@ impl Context {
/// Expands into a single method for contracting an event stream.
fn expand_filter(&self, event: &Event) -> TokenStream {
let ethers_contract = util::ethers_contract_crate();
let alias = self.event_aliases.get(&event.abi_signature()).cloned();

let name = if let Some(id) = alias.clone() {
util::safe_ident(&format!("{}_filter", id.to_string().to_snake_case()))
} else {
util::safe_ident(&format!("{}_filter", event.name.to_snake_case()))
};

// append `filter` to disambiguate with potentially conflicting
// function names
let name = util::safe_ident(&format!("{}_filter", event.name.to_snake_case()));
// let result = util::ident(&event.name.to_pascal_case());
let result = expand_struct_name(event);

let result = expand_struct_name(event, alias);

let doc = util::expand_doc(&format!("Gets the contract's `{}` event", event.name));
quote! {
Expand All @@ -240,7 +249,11 @@ impl Context {
/// into a structure or a tuple in the case where all event parameters (topics
/// and data) are anonymous.
fn expand_event(&self, event: &Event) -> Result<TokenStream> {
let event_name = expand_struct_name(event);
let sig = self.event_aliases.get(&event.abi_signature()).cloned();
let abi_signature = event.abi_signature();
let event_abi_name = event.name.clone();

let event_name = expand_struct_name(event, sig);

let params = self.expand_params(event)?;
// expand as a tuple if all fields are anonymous
Expand All @@ -252,8 +265,6 @@ impl Context {
};

let derives = expand_derives(&self.event_derives);
let abi_signature = event.abi_signature();
let event_abi_name = &event.name;

let ethers_contract = util::ethers_contract_crate();

Expand Down Expand Up @@ -309,9 +320,14 @@ impl Context {
}

/// Expands an ABI event into an identifier for its event data type.
fn expand_struct_name(event: &Event) -> Ident {
fn expand_struct_name(event: &Event, alias: Option<Ident>) -> Ident {
// TODO: get rid of `Filter` suffix?
let name = format!("{}Filter", event.name.to_pascal_case());

let name = if let Some(id) = alias {
format!("{}Filter", id.to_string().to_pascal_case())
} else {
format!("{}Filter", event.name.to_pascal_case())
};
util::ident(&name)
}

Expand Down Expand Up @@ -378,6 +394,50 @@ mod tests {
Context::from_abigen(Abigen::new("TestToken", "[]").unwrap()).unwrap()
}

fn test_context_with_alias(sig: &str, alias: &str) -> Context {
Context::from_abigen(
Abigen::new("TestToken", "[]")
.unwrap()
.add_event_alias(sig, alias),
)
.unwrap()
}

#[test]
#[rustfmt::skip]
fn expand_transfer_filter_with_alias() {
let event = Event {
name: "Transfer".into(),
inputs: vec![
EventParam {
name: "from".into(),
kind: ParamType::Address,
indexed: true,
},
EventParam {
name: "to".into(),
kind: ParamType::Address,
indexed: true,
},
EventParam {
name: "amount".into(),
kind: ParamType::Uint(256),
indexed: false,
},
],
anonymous: false,
};
let sig = "Transfer(address,address,uint256)";
let cx = test_context_with_alias(sig, "TransferEvent");
assert_quote!(cx.expand_filter(&event), {
#[doc = "Gets the contract's `Transfer` event"]
pub fn transfer_event_filter(
&self
) -> ethers_contract::builders::Event<M, TransferEventFilter> {
self.0.event()
}
});
}
#[test]
fn expand_transfer_filter() {
let event = Event {
Expand Down Expand Up @@ -431,7 +491,7 @@ mod tests {

let cx = test_context();
let params = cx.expand_params(&event).unwrap();
let name = expand_struct_name(&event);
let name = expand_struct_name(&event, None);
let definition = expand_data_struct(&name, &params);

assert_quote!(definition, {
Expand All @@ -442,6 +502,39 @@ mod tests {
});
}

#[test]
fn expand_data_struct_with_alias() {
let event = Event {
name: "Foo".into(),
inputs: vec![
EventParam {
name: "a".into(),
kind: ParamType::Bool,
indexed: false,
},
EventParam {
name: String::new(),
kind: ParamType::Address,
indexed: false,
},
],
anonymous: false,
};

let cx = test_context_with_alias("Foo(bool,address)", "FooAliased");
let params = cx.expand_params(&event).unwrap();
let alias = Some(util::ident("FooAliased"));
let name = expand_struct_name(&event, alias);
let definition = expand_data_struct(&name, &params);

assert_quote!(definition, {
struct FooAliasedFilter {
pub a: bool,
pub p1: ethers_core::types::Address,
}
});
}

#[test]
fn expand_data_tuple_value() {
let event = Event {
Expand All @@ -463,14 +556,44 @@ mod tests {

let cx = test_context();
let params = cx.expand_params(&event).unwrap();
let name = expand_struct_name(&event);
let name = expand_struct_name(&event, None);
let definition = expand_data_tuple(&name, &params);

assert_quote!(definition, {
struct FooFilter(pub bool, pub ethers_core::types::Address);
});
}

#[test]
fn expand_data_tuple_value_with_alias() {
let event = Event {
name: "Foo".into(),
inputs: vec![
EventParam {
name: String::new(),
kind: ParamType::Bool,
indexed: false,
},
EventParam {
name: String::new(),
kind: ParamType::Address,
indexed: false,
},
],
anonymous: false,
};

let cx = test_context_with_alias("Foo(bool,address)", "FooAliased");
let params = cx.expand_params(&event).unwrap();
let alias = Some(util::ident("FooAliased"));
let name = expand_struct_name(&event, alias);
let definition = expand_data_tuple(&name, &params);

assert_quote!(definition, {
struct FooAliasedFilter(pub bool, pub ethers_core::types::Address);
});
}

#[test]
#[rustfmt::skip]
fn expand_hash_value() {
Expand Down
15 changes: 15 additions & 0 deletions ethers-contract/ethers-contract-abigen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ pub struct Abigen {

/// Format the code using a locally installed copy of `rustfmt`.
rustfmt: bool,

/// Manually specified event name aliases.
event_aliases: HashMap<String, String>,
}

impl Abigen {
Expand All @@ -72,10 +75,22 @@ impl Abigen {
contract_name: contract_name.to_owned(),
method_aliases: HashMap::new(),
event_derives: Vec::new(),
event_aliases: HashMap::new(),
rustfmt: true,
})
}

/// Manually adds a solidity event alias to specify what the event struct
/// and function name will be in Rust.
pub fn add_event_alias<S1, S2>(mut self, signature: S1, alias: S2) -> Self
where
S1: Into<String>,
S2: Into<String>,
{
self.event_aliases.insert(signature.into(), alias.into());
self
}

/// Manually adds a solidity method alias to specify what the method name
/// will be in Rust. For solidity methods without an alias, the snake cased
/// method name will be used.
Expand Down

0 comments on commit 32ad5a6

Please sign in to comment.