From 72be3376e2ffaa841bfcff4fc5e411606c61c095 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 16 Mar 2023 00:12:12 +0100 Subject: [PATCH] feat(abigen): support empty events (#2263) --- .../ethers-contract-derive/src/event.rs | 99 ++++++++++--------- ethers-contract/tests/it/derive.rs | 28 ++++++ 2 files changed, 82 insertions(+), 45 deletions(-) diff --git a/ethers-contract/ethers-contract-derive/src/event.rs b/ethers-contract/ethers-contract-derive/src/event.rs index 844bf6a4..e6c8594b 100644 --- a/ethers-contract/ethers-contract-derive/src/event.rs +++ b/ethers-contract/ethers-contract-derive/src/event.rs @@ -1,6 +1,12 @@ //! Helper functions for deriving `EthEvent` +use crate::{abi_ty, utils}; use ethers_contract_abigen::Source; +use ethers_core::{ + abi::{Event, EventExt, EventParam, HumanReadableParser}, + macros::{ethers_contract_crate, ethers_core_crate}, +}; +use hex::FromHex; use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::{ @@ -8,14 +14,6 @@ use syn::{ NestedMeta, }; -use ethers_core::{ - abi::{Event, EventExt, EventParam, HumanReadableParser}, - macros::{ethers_contract_crate, ethers_core_crate}, -}; -use hex::FromHex; - -use crate::{abi_ty, utils}; - /// Generates the `EthEvent` trait support pub(crate) fn derive_eth_event_impl(input: DeriveInput) -> Result { let name = &input.ident; @@ -124,10 +122,7 @@ impl EventField { } } -fn derive_decode_from_log_impl( - input: &DeriveInput, - event: &Event, -) -> Result { +fn derive_decode_from_log_impl(input: &DeriveInput, event: &Event) -> Result { let ethers_core = ethers_core_crate(); let fields: Vec<_> = match input.data { @@ -159,10 +154,8 @@ fn derive_decode_from_log_impl( fields.unnamed.iter().collect() } Fields::Unit => { - return Err(Error::new( - input.span(), - "EthEvent cannot be derived for empty structs and unit", - )) + // Empty structs or unit, no fields + vec![] } }, Data::Enum(_) => { @@ -173,35 +166,6 @@ fn derive_decode_from_log_impl( } }; - let mut event_fields = Vec::with_capacity(fields.len()); - for (index, field) in fields.iter().enumerate() { - let mut param = event.inputs[index].clone(); - - let (topic_name, indexed) = parse_field_attributes(field)?; - if indexed { - param.indexed = true; - } - let topic_name = - param.indexed.then(|| topic_name.or_else(|| Some(param.name.clone()))).flatten(); - - event_fields.push(EventField { topic_name, index, param }); - } - - // convert fields to params list - let topic_types = event_fields - .iter() - .filter(|f| f.is_indexed()) - .map(|f| utils::topic_param_type_quote(&f.param.kind)); - - let topic_types_init = quote! {let topic_types = ::std::vec![#( #topic_types ),*];}; - - let data_types = event_fields - .iter() - .filter(|f| !f.is_indexed()) - .map(|f| utils::param_type_quote(&f.param.kind)); - - let data_types_init = quote! {let data_types = [#( #data_types ),*];}; - // decode let (signature_check, flat_topics_init, topic_tokens_len_check) = if event.anonymous { ( @@ -234,6 +198,51 @@ fn derive_decode_from_log_impl( ) }; + // Event with no fields, can skip decoding + if fields.is_empty() { + return Ok(quote! { + + let #ethers_core::abi::RawLog {topics, data} = log; + + #signature_check + + if topics.len() != 1usize || !data.is_empty() { + return Err(::ethers_core::abi::Error::InvalidData); + } + + #ethers_core::abi::Tokenizable::from_token(#ethers_core::abi::Token::Tuple(::std::vec::Vec::new())).map_err(|_|#ethers_core::abi::Error::InvalidData) + }) + } + + let mut event_fields = Vec::with_capacity(fields.len()); + for (index, field) in fields.iter().enumerate() { + let mut param = event.inputs[index].clone(); + + let (topic_name, indexed) = parse_field_attributes(field)?; + if indexed { + param.indexed = true; + } + let topic_name = + param.indexed.then(|| topic_name.or_else(|| Some(param.name.clone()))).flatten(); + + event_fields.push(EventField { topic_name, index, param }); + } + + // convert fields to params list + let topic_types = event_fields + .iter() + .filter(|f| f.is_indexed()) + .map(|f| utils::topic_param_type_quote(&f.param.kind)); + + let topic_types_init = quote! {let topic_types = ::std::vec![#( #topic_types ),*];}; + + let data_types = event_fields + .iter() + .filter(|f| !f.is_indexed()) + .map(|f| utils::param_type_quote(&f.param.kind)); + + let data_types_init = quote! {let data_types = [#( #data_types ),*];}; + // check if indexed are sorted let tokens_init = if event_fields .iter() diff --git a/ethers-contract/tests/it/derive.rs b/ethers-contract/tests/it/derive.rs index aa0bd1d0..41327bea 100644 --- a/ethers-contract/tests/it/derive.rs +++ b/ethers-contract/tests/it/derive.rs @@ -670,3 +670,31 @@ fn derives_abi_name() { "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef".parse().unwrap() ); } + +// +#[test] +fn derive_empty_events() { + #[derive(Debug, EthEvent)] + #[ethevent(abi = "EmptyEvent()")] + struct EmptyEvent; + + let log = RawLog { topics: vec![EmptyEvent::signature()], data: vec![] }; + let _event = ::decode_log(&log).unwrap(); + + let log = RawLog { topics: vec![EmptyEvent::signature()], data: vec![0] }; + assert!(::decode_log(&log).is_err()); + + let log = RawLog { topics: vec![EmptyEvent::signature(), H256::random()], data: vec![0] }; + assert!(::decode_log(&log).is_err()); + + assert_eq!(EmptyEvent::abi_signature(), "EmptyEvent()"); + + abigen!( + DummyContract, + r#"[ + event EmptyEvent2() + ]"#, + ); + + assert_eq!(EmptyEvent2Filter::abi_signature(), "EmptyEvent2()"); +}