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

feat(abigen): support empty events #2263

Merged
merged 1 commit into from
Mar 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 54 additions & 45 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 @@ -124,10 +122,7 @@ impl EventField {
}
}

fn derive_decode_from_log_impl(
input: &DeriveInput,
event: &Event,
) -> Result<proc_macro2::TokenStream, Error> {
fn derive_decode_from_log_impl(input: &DeriveInput, event: &Event) -> Result<TokenStream, Error> {
let ethers_core = ethers_core_crate();

let fields: Vec<_> = match input.data {
Expand Down Expand Up @@ -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(_) => {
Expand All @@ -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 {
(
Expand Down Expand Up @@ -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()
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()");
}