From 0c18f9b32cd520c5750e2e0c9f4ca7ae56f73f77 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 16 Mar 2021 09:12:32 +0100 Subject: [PATCH] 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 --- .../ethers-contract-derive/src/lib.rs | 85 +++++++++++++++---- ethers-contract/tests/common/derive.rs | 57 +++++++++---- 2 files changed, 106 insertions(+), 36 deletions(-) diff --git a/ethers-contract/ethers-contract-derive/src/lib.rs b/ethers-contract/ethers-contract-derive/src/lib.rs index 37aa94cb..ccaa8b56 100644 --- a/ethers-contract/ethers-contract-derive/src/lib.rs +++ b/ethers-contract/ethers-contract-derive/src/lib.rs @@ -13,7 +13,7 @@ use syn::{ }; 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 spanned::Spanned; @@ -79,9 +79,29 @@ pub fn abigen(input: TokenStream) -> TokenStream { /// /// Additional arguments can be specified using the `#[ethevent(...)]` attribute: /// -/// - `name`, `name = "..."`: Overrides the generated `EthEvent` name, default is the struct's name. -/// - `signature`, `signature = "..."`: The signature as hex string to override the event's signature. +/// - `name`, `name = "..."`: Overrides the generated `EthEvent` name, default is the +/// 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. +/// 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))] pub fn derive_abi_event(input: TokenStream) -> TokenStream { 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()); let (abi, hash) = if let Some((src, span)) = attributes.abi { + // try to parse as solidity event if let Ok(mut event) = parse_event(&src) { event.name = event_name.clone(); (event.abi_signature(), event.signature()) } else { - match src.parse::().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()) + // try as tuple + if let Some(inputs) = Reader::read( + src.trim_start_matches("event ") + .trim_start() + .trim_start_matches(&event_name), + ) + .ok() + .and_then(|param| match param { + ParamType::Tuple(params) => Some( + params + .into_iter() + .map(|kind| EventParam { + name: "".to_string(), + indexed: false, + kind, + }) + .collect(), + ), + _ => None, + }) { + let event = Event { + name: event_name.clone(), + inputs, + anonymous: false, + }; + (event.abi_signature(), event.signature()) + } else { + match src.parse::().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 { diff --git a/ethers-contract/tests/common/derive.rs b/ethers-contract/tests/common/derive.rs index 4f36b505..93534b3f 100644 --- a/ethers-contract/tests/common/derive.rs +++ b/ethers-contract/tests/common/derive.rs @@ -84,7 +84,7 @@ fn can_detokenize_nested_tuple_struct() { #[test] fn can_derive_eth_event() { #[derive(Debug, Clone, PartialEq, EthEvent)] - pub struct ValueChangedEvent { + struct ValueChangedEvent { old_author: Address, new_author: Address, old_value: String, @@ -112,7 +112,7 @@ fn can_derive_eth_event() { fn can_set_eth_event_name_attribute() { #[derive(Debug, PartialEq, EthEvent)] #[ethevent(name = "MyEvent")] - pub struct ValueChangedEvent { + struct ValueChangedEvent { old_author: Address, new_author: Address, old_value: String, @@ -165,19 +165,40 @@ fn can_detect_various_event_abi_types() { ); } -// #[test] -// fn can_set_eth_abi_attribute() { -// #[derive(Debug, Clone, PartialEq, EthAbiType)] -// struct SomeType { -// inner: Address, -// msg: String, -// } -// -// #[derive(Debug, PartialEq, EthEvent)] -// #[ethevent(abi = "ValueChangedEvent(address,(address,string),string)")] -// pub struct ValueChangedEvent { -// old_author: Address, -// inner: SomeType, -// new_value: String, -// } -// } +#[test] +fn can_set_eth_abi_attribute() { + #[derive(Debug, Clone, PartialEq, EthAbiType)] + struct SomeType { + inner: Address, + msg: String, + } + + #[derive(Debug, PartialEq, EthEvent)] + #[ethevent(abi = "ValueChangedEvent(address,(address,string),string)")] + struct ValueChangedEvent { + old_author: Address, + inner: SomeType, + 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() + ); +}