Event aliasing for contract bindings (#425)

* 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 <matthias.seitz@outlook.de>

* Make clippy happy

Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
This commit is contained in:
TannrA 2021-09-03 11:57:40 -04:00 committed by GitHub
parent 520645c48b
commit 32ad5a6abd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 160 additions and 12 deletions

View File

@ -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 {
@ -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()
@ -167,6 +176,7 @@ impl Context {
contract_name,
method_aliases,
event_derives,
event_aliases,
})
}
}

View File

@ -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();
@ -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! {
@ -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! {
@ -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
@ -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();
@ -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)
}
@ -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 {
@ -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, {
@ -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 {
@ -463,7 +556,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_tuple(&name, &params);
assert_quote!(definition, {
@ -471,6 +564,36 @@ mod tests {
});
}
#[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() {

View File

@ -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 {
@ -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.