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

Event aliasing for contract bindings #425

Merged
merged 6 commits into from
Sep 3, 2021
Merged
Show file tree
Hide file tree
Changes from 3 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
15 changes: 15 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,17 @@ impl Context {
}
}

let mut event_aliases = BTreeMap::new();
for (signature, alias) in args.event_aliases.into_iter() {
let alias = syn::parse_str(&alias)?;
if event_aliases.insert(signature.clone(), alias).is_some() {
return Err(anyhow!(
"duplicate method signature '{}' in method aliases",
signature,
));
}
WilfredTA marked this conversation as resolved.
Show resolved Hide resolved
}

let event_derives = args
.event_derives
.iter()
Expand All @@ -167,6 +181,7 @@ impl Context {
contract_name,
method_aliases,
event_derives,
event_aliases,
})
}
}
153 changes: 137 additions & 16 deletions ethers-contract/ethers-contract-abigen/src/contract/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ use syn::Path;
impl Context {
/// Expands each event to a struct + its impl Detokenize block
pub fn events_declaration(&self) -> Result<TokenStream> {
let mut event_aliases = self.event_aliases.clone();
let sorted_events: BTreeMap<_, _> = self.abi.events.clone().into_iter().collect();
let data_types = sorted_events
.values()
.flatten()
.map(|event| self.expand_event(event))
.map(|event| self.expand_event(event, event_aliases.remove(&event.abi_signature())))
WilfredTA marked this conversation as resolved.
Show resolved Hide resolved
.collect::<Result<Vec<_>>>()?;

// only expand enums when multiple events are present
Expand Down Expand Up @@ -52,11 +53,14 @@ impl Context {
/// Generate an enum with a variant for each event
fn expand_events_enum(&self) -> TokenStream {
let sorted_events: BTreeMap<_, _> = self.abi.events.clone().into_iter().collect();

let mut aliases = self.event_aliases.clone();
WilfredTA marked this conversation as resolved.
Show resolved Hide resolved
let variants = sorted_events
.values()
.flatten()
.map(expand_struct_name)
.map(|e| {
let alias = aliases.remove(&e.abi_signature());
expand_struct_name(e, alias)
WilfredTA marked this conversation as resolved.
Show resolved Hide resolved
})
.collect::<Vec<_>>();

let enum_name = self.expand_event_enum_name();
Expand Down Expand Up @@ -124,7 +128,8 @@ impl Context {
let ty = if iter.next().is_some() {
self.expand_event_enum_name()
} else {
expand_struct_name(event)
let alias = self.event_aliases.clone().remove(&event.abi_signature());
expand_struct_name(event, alias)
WilfredTA marked this conversation as resolved.
Show resolved Hide resolved
};

quote! {
Expand Down Expand Up @@ -220,12 +225,17 @@ 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.clone().remove(&event.abi_signature());
WilfredTA marked this conversation as resolved.
Show resolved Hide resolved

let mut name = util::safe_ident(&format!("{}_filter", event.name.to_snake_case()));
WilfredTA marked this conversation as resolved.
Show resolved Hide resolved
if let Some(id) = alias.clone() {
name = util::safe_ident(&format!("{}_filter", id.to_string().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 @@ -239,10 +249,13 @@ impl Context {
/// Expands an ABI event into a single event data type. This can expand either
/// 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);
fn expand_event(&self, event: &Event, sig: Option<Ident>) -> Result<TokenStream> {
let abi_signature = event.abi_signature();
let event_abi_name = event.name.clone();

let params = self.expand_params(event)?;
let event_name = expand_struct_name(&event, sig);

let params = self.expand_params(&event)?;
// expand as a tuple if all fields are anonymous
let all_anonymous_fields = event.inputs.iter().all(|input| input.name.is_empty());
let data_type_definition = if all_anonymous_fields {
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,13 @@ 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 mut name = format!("{}Filter", event.name.to_pascal_case());

if let Some(id) = alias {
name = format!("{}Filter", id.to_string().to_pascal_case());
}
WilfredTA marked this conversation as resolved.
Show resolved Hide resolved
util::ident(&name)
}

Expand Down Expand Up @@ -378,6 +393,49 @@ 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]
WilfredTA marked this conversation as resolved.
Show resolved Hide resolved
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 +489,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 +500,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 +554,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