Skip to content
This repository has been archived by the owner on Oct 19, 2024. It is now read-only.

Commit

Permalink
feat(abigen): support empty events
Browse files Browse the repository at this point in the history
  • Loading branch information
mattsse committed Mar 15, 2023
1 parent 18a049b commit b7e8984
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 41 deletions.
95 changes: 54 additions & 41 deletions ethers-contract/ethers-contract-derive/src/event.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
//! 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::{
parse::Error, spanned::Spanned, AttrStyle, Data, DeriveInput, Field, Fields, Lit, Meta,
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<TokenStream, Error> {
let name = &input.ident;
Expand Down Expand Up @@ -127,7 +125,7 @@ impl EventField {
fn derive_decode_from_log_impl(
input: &DeriveInput,
event: &Event,
) -> Result<proc_macro2::TokenStream, Error> {
) -> Result<TokenStream, Error> {
let ethers_core = ethers_core_crate();

let fields: Vec<_> = match input.data {
Expand Down Expand Up @@ -159,10 +157,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(_) => {
Expand All @@ -173,34 +169,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 {
Expand Down Expand Up @@ -234,6 +202,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()
Expand Down
28 changes: 28 additions & 0 deletions ethers-contract/tests/it/derive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -670,3 +670,31 @@ fn derives_abi_name() {
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef".parse().unwrap()
);
}

// <https://github.com/gakonst/ethers-rs/issues/2261>
#[test]
fn derive_empty_events() {
#[derive(Debug, EthEvent)]
#[ethevent(abi = "EmptyEvent()")]
struct EmptyEvent;

let log = RawLog { topics: vec![EmptyEvent::signature()], data: vec![] };
let event = <EmptyEvent as EthLogDecode>::decode_log(&log).unwrap();

let log = RawLog { topics: vec![EmptyEvent::signature()], data: vec![0] };
assert!(<EmptyEvent as EthLogDecode>::decode_log(&log).is_err());

let log = RawLog { topics: vec![EmptyEvent::signature(), H256::random()], data: vec![0] };
assert!(<EmptyEvent as EthLogDecode>::decode_log(&log).is_err());

assert_eq!(EmptyEvent::abi_signature(), "EmptyEvent()");

abigen!(
DummyContract,
r#"[
event EmptyEvent2()
]"#,
);

assert_eq!(EmptyEvent2Filter::abi_signature(), "EmptyEvent2()");
}

0 comments on commit b7e8984

Please sign in to comment.