fix: make EthEvent abi attribute work for tuple inputs (#229)

* fix: try to parse the provided abi as tuple

* test: validate ethevent abi attribute

* docs: document ethevent abi attribute
This commit is contained in:
Matthias Seitz 2021-03-16 09:12:32 +01:00 committed by GitHub
parent 7b10b76e20
commit 0c18f9b32c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 106 additions and 36 deletions

View File

@ -13,7 +13,7 @@ use syn::{
}; };
use abigen::{expand, ContractArgs}; use abigen::{expand, ContractArgs};
use ethers_core::abi::{AbiParser, Event, EventExt, EventParam, ParamType}; use ethers_core::abi::{param_type::Reader, AbiParser, Event, EventExt, EventParam, ParamType};
use hex::FromHex; use hex::FromHex;
use spanned::Spanned; use spanned::Spanned;
@ -79,9 +79,29 @@ pub fn abigen(input: TokenStream) -> TokenStream {
/// ///
/// Additional arguments can be specified using the `#[ethevent(...)]` attribute: /// Additional arguments can be specified using the `#[ethevent(...)]` attribute:
/// ///
/// - `name`, `name = "..."`: Overrides the generated `EthEvent` name, default is the struct's name. /// - `name`, `name = "..."`: Overrides the generated `EthEvent` name, default is the
/// - `signature`, `signature = "..."`: The signature as hex string to override the event's signature. /// struct's name.
/// - `signature`, `signature = "..."`: The signature as hex string to override the
/// event's signature.
/// - `abi`, `abi = "..."`: The ABI signature for the event this event's data corresponds to. /// - `abi`, `abi = "..."`: The ABI signature for the event this event's data corresponds to.
/// The `abi` should be solidity event definition or a tuple of the event's types in case the
/// event has non elementary (other `EthAbiType`) types as members
///
/// # Example
/// ```ignore
/// #[derive(Debug, EthAbiType)]
/// struct Inner {
/// inner: Address,
/// msg: String,
/// }
///
/// #[derive(Debug, EthEvent)]
/// #[ethevent(abi = "ValueChangedEvent((address,string),string)")]
/// struct ValueChangedEvent {
/// inner: Inner,
/// msg: String,
/// }
/// ```
#[proc_macro_derive(EthEvent, attributes(ethevent))] #[proc_macro_derive(EthEvent, attributes(ethevent))]
pub fn derive_abi_event(input: TokenStream) -> TokenStream { pub fn derive_abi_event(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput); let input = parse_macro_input!(input as DeriveInput);
@ -97,28 +117,57 @@ pub fn derive_abi_event(input: TokenStream) -> TokenStream {
.unwrap_or_else(|| input.ident.to_string()); .unwrap_or_else(|| input.ident.to_string());
let (abi, hash) = if let Some((src, span)) = attributes.abi { let (abi, hash) = if let Some((src, span)) = attributes.abi {
// try to parse as solidity event
if let Ok(mut event) = parse_event(&src) { if let Ok(mut event) = parse_event(&src) {
event.name = event_name.clone(); event.name = event_name.clone();
(event.abi_signature(), event.signature()) (event.abi_signature(), event.signature())
} else { } else {
match src.parse::<Source>().and_then(|s| s.get()) { // try as tuple
Ok(abi) => { if let Some(inputs) = Reader::read(
// try to derive the signature from the abi from the parsed abi src.trim_start_matches("event ")
// TODO(mattsse): this will fail for events that contain other non elementary types in their abi .trim_start()
// because the parser doesn't know how to substitute the types .trim_start_matches(&event_name),
// this could be mitigated by getting the ABI of each non elementary type at runtime )
// and computing the the signature as `static Lazy::...` .ok()
match parse_event(&abi) { .and_then(|param| match param {
Ok(mut event) => { ParamType::Tuple(params) => Some(
event.name = event_name.clone(); params
(event.abi_signature(), event.signature()) .into_iter()
} .map(|kind| EventParam {
Err(err) => { name: "".to_string(),
return TokenStream::from(Error::new(span, err).to_compile_error()) indexed: false,
kind,
})
.collect(),
),
_ => None,
}) {
let event = Event {
name: event_name.clone(),
inputs,
anonymous: false,
};
(event.abi_signature(), event.signature())
} else {
match src.parse::<Source>().and_then(|s| s.get()) {
Ok(abi) => {
// try to derive the signature from the abi from the parsed abi
// TODO(mattsse): this will fail for events that contain other non elementary types in their abi
// because the parser doesn't know how to substitute the types
// this could be mitigated by getting the ABI of each non elementary type at runtime
// and computing the the signature as `static Lazy::...`
match parse_event(&abi) {
Ok(mut event) => {
event.name = event_name.clone();
(event.abi_signature(), event.signature())
}
Err(err) => {
return TokenStream::from(Error::new(span, err).to_compile_error())
}
} }
} }
Err(err) => return TokenStream::from(Error::new(span, err).to_compile_error()),
} }
Err(err) => return TokenStream::from(Error::new(span, err).to_compile_error()),
} }
} }
} else { } else {

View File

@ -84,7 +84,7 @@ fn can_detokenize_nested_tuple_struct() {
#[test] #[test]
fn can_derive_eth_event() { fn can_derive_eth_event() {
#[derive(Debug, Clone, PartialEq, EthEvent)] #[derive(Debug, Clone, PartialEq, EthEvent)]
pub struct ValueChangedEvent { struct ValueChangedEvent {
old_author: Address, old_author: Address,
new_author: Address, new_author: Address,
old_value: String, old_value: String,
@ -112,7 +112,7 @@ fn can_derive_eth_event() {
fn can_set_eth_event_name_attribute() { fn can_set_eth_event_name_attribute() {
#[derive(Debug, PartialEq, EthEvent)] #[derive(Debug, PartialEq, EthEvent)]
#[ethevent(name = "MyEvent")] #[ethevent(name = "MyEvent")]
pub struct ValueChangedEvent { struct ValueChangedEvent {
old_author: Address, old_author: Address,
new_author: Address, new_author: Address,
old_value: String, old_value: String,
@ -165,19 +165,40 @@ fn can_detect_various_event_abi_types() {
); );
} }
// #[test] #[test]
// fn can_set_eth_abi_attribute() { fn can_set_eth_abi_attribute() {
// #[derive(Debug, Clone, PartialEq, EthAbiType)] #[derive(Debug, Clone, PartialEq, EthAbiType)]
// struct SomeType { struct SomeType {
// inner: Address, inner: Address,
// msg: String, msg: String,
// } }
//
// #[derive(Debug, PartialEq, EthEvent)] #[derive(Debug, PartialEq, EthEvent)]
// #[ethevent(abi = "ValueChangedEvent(address,(address,string),string)")] #[ethevent(abi = "ValueChangedEvent(address,(address,string),string)")]
// pub struct ValueChangedEvent { struct ValueChangedEvent {
// old_author: Address, old_author: Address,
// inner: SomeType, inner: SomeType,
// new_value: String, new_value: String,
// } }
// }
assert_eq!(
"ValueChangedEvent(address,(address,string),string)",
ValueChangedEvent::abi_signature()
);
#[derive(Debug, PartialEq, EthEvent)]
#[ethevent(
name = "ValueChangedEvent",
abi = "ValueChangedEvent(address,(address,string),string)"
)]
struct ValueChangedEvent2 {
old_author: Address,
inner: SomeType,
new_value: String,
}
assert_eq!(
"ValueChangedEvent(address,(address,string),string)",
ValueChangedEvent2::abi_signature()
);
}