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:
parent
520645c48b
commit
32ad5a6abd
|
@ -46,6 +46,9 @@ pub(crate) struct Context {
|
||||||
|
|
||||||
/// Derives added to event structs and enums.
|
/// Derives added to event structs and enums.
|
||||||
event_derives: Vec<Path>,
|
event_derives: Vec<Path>,
|
||||||
|
|
||||||
|
/// Manually specified event aliases.
|
||||||
|
event_aliases: BTreeMap<String, Ident>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Context {
|
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
|
let event_derives = args
|
||||||
.event_derives
|
.event_derives
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -167,6 +176,7 @@ impl Context {
|
||||||
contract_name,
|
contract_name,
|
||||||
method_aliases,
|
method_aliases,
|
||||||
event_derives,
|
event_derives,
|
||||||
|
event_aliases,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,7 +56,7 @@ impl Context {
|
||||||
let variants = sorted_events
|
let variants = sorted_events
|
||||||
.values()
|
.values()
|
||||||
.flatten()
|
.flatten()
|
||||||
.map(expand_struct_name)
|
.map(|e| expand_struct_name(e, self.event_aliases.get(&e.abi_signature()).cloned()))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let enum_name = self.expand_event_enum_name();
|
let enum_name = self.expand_event_enum_name();
|
||||||
|
@ -124,7 +124,10 @@ impl Context {
|
||||||
let ty = if iter.next().is_some() {
|
let ty = if iter.next().is_some() {
|
||||||
self.expand_event_enum_name()
|
self.expand_event_enum_name()
|
||||||
} else {
|
} else {
|
||||||
expand_struct_name(event)
|
expand_struct_name(
|
||||||
|
event,
|
||||||
|
self.event_aliases.get(&event.abi_signature()).cloned(),
|
||||||
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
|
@ -220,12 +223,18 @@ impl Context {
|
||||||
/// Expands into a single method for contracting an event stream.
|
/// Expands into a single method for contracting an event stream.
|
||||||
fn expand_filter(&self, event: &Event) -> TokenStream {
|
fn expand_filter(&self, event: &Event) -> TokenStream {
|
||||||
let ethers_contract = util::ethers_contract_crate();
|
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
|
// append `filter` to disambiguate with potentially conflicting
|
||||||
// function names
|
// 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, alias);
|
||||||
let result = expand_struct_name(event);
|
|
||||||
|
|
||||||
let doc = util::expand_doc(&format!("Gets the contract's `{}` event", event.name));
|
let doc = util::expand_doc(&format!("Gets the contract's `{}` event", event.name));
|
||||||
quote! {
|
quote! {
|
||||||
|
@ -240,7 +249,11 @@ impl Context {
|
||||||
/// into a structure or a tuple in the case where all event parameters (topics
|
/// into a structure or a tuple in the case where all event parameters (topics
|
||||||
/// and data) are anonymous.
|
/// and data) are anonymous.
|
||||||
fn expand_event(&self, event: &Event) -> Result<TokenStream> {
|
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)?;
|
let params = self.expand_params(event)?;
|
||||||
// expand as a tuple if all fields are anonymous
|
// expand as a tuple if all fields are anonymous
|
||||||
|
@ -252,8 +265,6 @@ impl Context {
|
||||||
};
|
};
|
||||||
|
|
||||||
let derives = expand_derives(&self.event_derives);
|
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();
|
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.
|
/// 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?
|
// 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)
|
util::ident(&name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -378,6 +394,50 @@ mod tests {
|
||||||
Context::from_abigen(Abigen::new("TestToken", "[]").unwrap()).unwrap()
|
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]
|
#[test]
|
||||||
fn expand_transfer_filter() {
|
fn expand_transfer_filter() {
|
||||||
let event = Event {
|
let event = Event {
|
||||||
|
@ -431,7 +491,7 @@ mod tests {
|
||||||
|
|
||||||
let cx = test_context();
|
let cx = test_context();
|
||||||
let params = cx.expand_params(&event).unwrap();
|
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, ¶ms);
|
let definition = expand_data_struct(&name, ¶ms);
|
||||||
|
|
||||||
assert_quote!(definition, {
|
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, ¶ms);
|
||||||
|
|
||||||
|
assert_quote!(definition, {
|
||||||
|
struct FooAliasedFilter {
|
||||||
|
pub a: bool,
|
||||||
|
pub p1: ethers_core::types::Address,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn expand_data_tuple_value() {
|
fn expand_data_tuple_value() {
|
||||||
let event = Event {
|
let event = Event {
|
||||||
|
@ -463,7 +556,7 @@ mod tests {
|
||||||
|
|
||||||
let cx = test_context();
|
let cx = test_context();
|
||||||
let params = cx.expand_params(&event).unwrap();
|
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, ¶ms);
|
let definition = expand_data_tuple(&name, ¶ms);
|
||||||
|
|
||||||
assert_quote!(definition, {
|
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, ¶ms);
|
||||||
|
|
||||||
|
assert_quote!(definition, {
|
||||||
|
struct FooAliasedFilter(pub bool, pub ethers_core::types::Address);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
fn expand_hash_value() {
|
fn expand_hash_value() {
|
||||||
|
|
|
@ -61,6 +61,9 @@ pub struct Abigen {
|
||||||
|
|
||||||
/// Format the code using a locally installed copy of `rustfmt`.
|
/// Format the code using a locally installed copy of `rustfmt`.
|
||||||
rustfmt: bool,
|
rustfmt: bool,
|
||||||
|
|
||||||
|
/// Manually specified event name aliases.
|
||||||
|
event_aliases: HashMap<String, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Abigen {
|
impl Abigen {
|
||||||
|
@ -72,10 +75,22 @@ impl Abigen {
|
||||||
contract_name: contract_name.to_owned(),
|
contract_name: contract_name.to_owned(),
|
||||||
method_aliases: HashMap::new(),
|
method_aliases: HashMap::new(),
|
||||||
event_derives: Vec::new(),
|
event_derives: Vec::new(),
|
||||||
|
event_aliases: HashMap::new(),
|
||||||
rustfmt: true,
|
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
|
/// 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
|
/// will be in Rust. For solidity methods without an alias, the snake cased
|
||||||
/// method name will be used.
|
/// method name will be used.
|
||||||
|
|
Loading…
Reference in New Issue