From 816c5fc0710ee9c5e488d44742586c48de7379d2 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 19 Mar 2021 16:44:59 +0100 Subject: [PATCH] feat(abigen): extend ethevent trait methods and decoding (#239) * feat: extend EthEvent with decode_log method and support indexed proc macro attributes * test: check that ethevent proc macro attributes compile * docs: document EthEvent proc macro attributes and add example * refactor: change decode_log to take a reference * refactor: use ethers as fully qualified path * feat: add events enum generation * feat: introduce EthLogDecode trait * feat: generate EthLogDecode implementations * refactor: use fully qualified syntax during abigen * fix: switch to new Event builder * fix: make test compile again * test: update failing tests * refactor: rename event function * chore(clippy): make clippy happy * fix: rename the event correctly * fix: add missing indexed attribute * Revert "fix: rename the event correctly" This reverts commit 03eabc3ead9aa1a178ed8464da7f206a647f5c42. * fix: make indexed names optional * fix: dsproxy name * fix: rename ethers top level module imports --- .../ethers-contract-abigen/src/contract.rs | 7 +- .../src/contract/common.rs | 15 +- .../src/contract/events.rs | 146 ++++-- .../src/contract/methods.rs | 6 +- .../src/contract/types.rs | 4 +- .../ethers-contract-derive/src/lib.rs | 449 ++++++++++++++++-- ethers-contract/src/contract.rs | 55 +-- ethers-contract/src/event.rs | 60 ++- ethers-contract/src/lib.rs | 3 + ethers-contract/src/log.rs | 16 + ethers-contract/tests/common/derive.rs | 43 +- ethers-contract/tests/common/mod.rs | 8 +- ethers-contract/tests/contract.rs | 10 +- .../contracts/DsProxyFactory.json | 95 ++++ .../src/transformer/ds_proxy/factory.rs | 89 ++-- 15 files changed, 808 insertions(+), 198 deletions(-) create mode 100644 ethers-contract/src/log.rs create mode 100644 ethers-middleware/contracts/DsProxyFactory.json diff --git a/ethers-contract/ethers-contract-abigen/src/contract.rs b/ethers-contract/ethers-contract-abigen/src/contract.rs index 02a3a582..bf0be544 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract.rs @@ -79,15 +79,14 @@ impl Context { #[allow(clippy::too_many_arguments)] mod #name_mod { #imports - #struct_decl - impl<'a, M: Middleware> #name { + impl<'a, M: ethers_providers::Middleware> #name { /// Creates a new contract instance with the specified `ethers` /// client at the given `Address`. The contract derefs to a `ethers::Contract` /// object - pub fn new>(address: T, client: Arc) -> Self { - let contract = Contract::new(address.into(), #abi_name.clone(), client); + pub fn new>(address: T, client: ::std::sync::Arc) -> Self { + let contract = ethers_contract::Contract::new(address.into(), #abi_name.clone(), client); Self(contract) } diff --git a/ethers-contract/ethers-contract-abigen/src/contract/common.rs b/ethers-contract/ethers-contract-abigen/src/contract/common.rs index 49da608f..ed19b867 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract/common.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract/common.rs @@ -15,11 +15,12 @@ pub(crate) fn imports(name: &str) -> TokenStream { use std::sync::Arc; use ethers::{ core::{ + self as ethers_core, abi::{Abi, Token, Detokenize, InvalidOutputType, Tokenizable}, types::*, // import all the types so that we can codegen for everything }, - contract::{Contract, builders::{ContractCall, Event}, Lazy}, - providers::Middleware, + contract::{self as ethers_contract, Contract, builders::{ContractCall, Event}, Lazy}, + providers::{self as ethers_providers,Middleware}, }; } } @@ -31,12 +32,12 @@ pub(crate) fn struct_declaration(cx: &Context, abi_name: &proc_macro2::Ident) -> let abi_parse = if !cx.human_readable { quote! { - pub static #abi_name: Lazy = Lazy::new(|| serde_json::from_str(#abi) + pub static #abi_name: ethers_contract::Lazy = ethers_contract::Lazy::new(|| serde_json::from_str(#abi) .expect("invalid abi")); } } else { quote! { - pub static #abi_name: Lazy = Lazy::new(|| ethers::core::abi::parse_abi_str(#abi) + pub static #abi_name: ethers_contract::Lazy = ethers_contract::Lazy::new(|| ethers::core::abi::parse_abi_str(#abi) .expect("invalid abi")); } }; @@ -47,17 +48,17 @@ pub(crate) fn struct_declaration(cx: &Context, abi_name: &proc_macro2::Ident) -> // Struct declaration #[derive(Clone)] - pub struct #name(Contract); + pub struct #name(ethers_contract::Contract); // Deref to the inner contract in order to access more specific functions functions impl std::ops::Deref for #name { - type Target = Contract; + type Target = ethers_contract::Contract; fn deref(&self) -> &Self::Target { &self.0 } } - impl std::fmt::Debug for #name { + impl std::fmt::Debug for #name { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { f.debug_tuple(stringify!(#name)) .field(&self.address()) diff --git a/ethers-contract/ethers-contract-abigen/src/contract/events.rs b/ethers-contract/ethers-contract-abigen/src/contract/events.rs index 41a2fa39..dadaed3f 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract/events.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract/events.rs @@ -2,7 +2,7 @@ use super::{types, util, Context}; use anyhow::Result; use ethers_core::abi::{Event, EventExt, EventParam, Hash, ParamType, SolStruct}; use inflector::Inflector; -use proc_macro2::{Literal, TokenStream}; +use proc_macro2::{Ident, Literal, TokenStream}; use quote::quote; use std::collections::BTreeMap; use syn::Path; @@ -17,33 +17,123 @@ impl Context { .map(|event| self.expand_event(event)) .collect::>>()?; - if data_types.is_empty() { - return Ok(quote! {}); - } + // only expand enums when multiple events are present + let events_enum_decl = if sorted_events.values().flatten().count() > 1 { + self.expand_events_enum() + } else { + quote! {} + }; Ok(quote! { #( #data_types )* + + #events_enum_decl }) } /// Generate the event filter methods for the contract pub fn event_methods(&self) -> Result { let sorted_events: BTreeMap<_, _> = self.abi.events.clone().into_iter().collect(); - let data_types = sorted_events + let filter_methods = sorted_events .values() .flatten() .map(|event| self.expand_filter(event)) .collect::>(); - if data_types.is_empty() { - return Ok(quote! {}); - } + let events_method = self.expand_events_method(); Ok(quote! { - #( #data_types )* + #( #filter_methods )* + + #events_method }) } + /// Generate an enum with a variant for each event + fn expand_events_enum(&self) -> TokenStream { + let sorted_events: BTreeMap<_, _> = self.abi.events.clone().into_iter().collect(); + + let variants = sorted_events + .values() + .flatten() + .map(expand_struct_name) + .collect::>(); + + let enum_name = self.expand_event_enum_name(); + + quote! { + #[derive(Debug, Clone, PartialEq, Eq)] + pub enum #enum_name { + #(#variants(#variants)),* + } + + impl ethers_core::abi::Tokenizable for #enum_name { + + fn from_token(token: ethers_core::abi::Token) -> Result where + Self: Sized { + #( + if let Ok(decoded) = #variants::from_token(token.clone()) { + return Ok(#enum_name::#variants(decoded)) + } + )* + Err(ethers_core::abi::InvalidOutputType("Failed to decode all event variants".to_string())) + } + + fn into_token(self) -> ethers_core::abi::Token { + match self { + #( + #enum_name::#variants(element) => element.into_token() + ),* + } + } + } + impl ethers_core::abi::TokenizableItem for #enum_name { } + + impl ethers_contract::EthLogDecode for #enum_name { + fn decode_log(log: ðers_core::abi::RawLog) -> Result + where + Self: Sized, + { + #( + if let Ok(decoded) = #variants::decode_log(log) { + return Ok(#enum_name::#variants(decoded)) + } + )* + Err(ethers_core::abi::Error::InvalidData) + } + } + } + } + + /// The name ident of the events enum + fn expand_event_enum_name(&self) -> Ident { + util::ident(&format!("{}Events", self.contract_name.to_string())) + } + + /// Expands the `events` function that bundles all declared events of this contract + fn expand_events_method(&self) -> TokenStream { + let sorted_events: BTreeMap<_, _> = self.abi.events.clone().into_iter().collect(); + + let mut iter = sorted_events.values().flatten(); + + if let Some(event) = iter.next() { + let ty = if iter.next().is_some() { + self.expand_event_enum_name() + } else { + expand_struct_name(event) + }; + + quote! { + /// Returns an [`Event`](ethers_contract::builders::Event) builder for all events of this contract + pub fn events(&self) -> ethers_contract::builders::Event { + self.0.event_with_filter(Default::default()) + } + } + } else { + quote! {} + } + } + /// Expands an event property type. /// /// Note that this is slightly different than an expanding a Solidity type as @@ -66,7 +156,7 @@ impl Context { return Ok(quote! {::std::vec::Vec<#ty>}); } } - quote! { H256 } + quote! { ethers_core::types::H256 } } (ParamType::FixedArray(ty, size), true) => { if let ParamType::Tuple(..) = **ty { @@ -82,7 +172,7 @@ impl Context { return Ok(quote! {[#ty; #size]}); } } - quote! { H256 } + quote! { ethers_core::types::H256 } } (ParamType::Tuple(..), true) => { // represents an struct @@ -95,11 +185,11 @@ impl Context { { quote! {#ty} } else { - quote! { H256 } + quote! { ethers_core::types::H256 } } } (ParamType::Bytes, true) | (ParamType::String, true) => { - quote! { H256 } + quote! { ethers_core::types::H256 } } (kind, _) => types::expand(kind)?, }) @@ -128,13 +218,12 @@ impl Context { let name = util::safe_ident(&format!("{}_filter", event.name.to_snake_case())); // let result = util::ident(&event.name.to_pascal_case()); let result = expand_struct_name(event); - let ev_name = Literal::string(&event.name); let doc = util::expand_doc(&format!("Gets the contract's `{}` event", event.name)); quote! { #doc - pub fn #name(&self) -> Event { - self.0.event(#ev_name).expect("event not found (this should never happen)") + pub fn #name(&self) -> ethers_contract::builders::Event { + self.0.event() } } } @@ -159,7 +248,7 @@ impl Context { let event_abi_name = &event.name; Ok(quote! { - #[derive(Clone, Debug, Default, Eq, PartialEq, ethers::contract::EthEvent, #derives)] + #[derive(Clone, Debug, Default, Eq, PartialEq, ethers_contract::EthEvent, #derives)] #[ethevent( name = #event_abi_name, abi = #abi_signature )] pub #data_type_definition }) @@ -210,17 +299,16 @@ impl Context { } /// Expands an ABI event into an identifier for its event data type. -fn expand_struct_name(event: &Event) -> TokenStream { +fn expand_struct_name(event: &Event) -> Ident { // TODO: get rid of `Filter` suffix? let name = format!("{}Filter", event.name.to_pascal_case()); - let event_name = util::ident(&name); - quote! { #event_name } + util::ident(&name) } /// Expands an event data structure from its name-type parameter pairs. Returns /// a tuple with the type definition (i.e. the struct declaration) and /// construction (i.e. code for creating an instance of the event data). -fn expand_data_struct(name: &TokenStream, params: &[(TokenStream, TokenStream)]) -> TokenStream { +fn expand_data_struct(name: &Ident, params: &[(TokenStream, TokenStream)]) -> TokenStream { let fields = params .iter() .map(|(name, ty)| quote! { pub #name: #ty }) @@ -231,7 +319,7 @@ fn expand_data_struct(name: &TokenStream, params: &[(TokenStream, TokenStream)]) /// Expands an event data named tuple from its name-type parameter pairs. /// Returns a tuple with the type definition and construction. -fn expand_data_tuple(name: &TokenStream, params: &[(TokenStream, TokenStream)]) -> TokenStream { +fn expand_data_tuple(name: &Ident, params: &[(TokenStream, TokenStream)]) -> TokenStream { let fields = params .iter() .map(|(_, ty)| quote! { pub #ty }) @@ -256,7 +344,7 @@ fn expand_hash(hash: Hash) -> TokenStream { let bytes = hash.as_bytes().iter().copied().map(Literal::u8_unsuffixed); quote! { - H256([#( #bytes ),*]) + ethers_core::types::H256([#( #bytes ),*]) } } @@ -296,10 +384,8 @@ mod tests { let cx = test_context(); assert_quote!(cx.expand_filter(&event), { #[doc = "Gets the contract's `Transfer` event"] - pub fn transfer_filter(&self) -> Event { - self.0 - .event("Transfer") - .expect("event not found (this should never happen)") + pub fn transfer_filter(&self) -> ethers_contract::builders::Event { + self.0.event() } }); } @@ -331,7 +417,7 @@ mod tests { assert_quote!(definition, { struct FooFilter { pub a: bool, - pub p1: Address, + pub p1: ethers_core::types::Address, } }); } @@ -361,7 +447,7 @@ mod tests { let definition = expand_data_tuple(&name, ¶ms); assert_quote!(definition, { - struct FooFilter(pub bool, pub Address); + struct FooFilter(pub bool, pub ethers_core::types::Address); }); } @@ -373,7 +459,7 @@ mod tests { "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f".parse().unwrap() ), { - H256([ + ethers_core::types::H256([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31 ]) diff --git a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs index 4c15d6d7..a33329cd 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs @@ -39,7 +39,7 @@ fn expand_function(function: &Function, alias: Option) -> Result }; + let result = quote! { ethers_contract::builders::ContractCall }; let arg = expand_inputs_call_arg(&function.inputs); let doc = util::expand_doc(&format!( @@ -179,7 +179,7 @@ mod tests { ], ) .unwrap(), - { , a: bool, b: Address }, + { , a: bool, b: ethers_core::types::Address }, ); } @@ -214,7 +214,7 @@ mod tests { }, ],) .unwrap(), - { (bool, Address) }, + { (bool, ethers_core::types::Address) }, ); } } diff --git a/ethers-contract/ethers-contract-abigen/src/contract/types.rs b/ethers-contract/ethers-contract-abigen/src/contract/types.rs index 30efaac2..a1500c73 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract/types.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract/types.rs @@ -5,7 +5,7 @@ use quote::quote; pub(crate) fn expand(kind: &ParamType) -> Result { match kind { - ParamType::Address => Ok(quote! { Address }), + ParamType::Address => Ok(quote! { ethers_core::types::Address }), ParamType::Bytes => Ok(quote! { Vec }), ParamType::Int(n) => match n / 8 { 1 => Ok(quote! { i8 }), @@ -22,7 +22,7 @@ pub(crate) fn expand(kind: &ParamType) -> Result { 3..=4 => Ok(quote! { u32 }), 5..=8 => Ok(quote! { u64 }), 9..=16 => Ok(quote! { u128 }), - 17..=32 => Ok(quote! { U256 }), + 17..=32 => Ok(quote! { ethers_core::types::U256 }), _ => Err(anyhow!("unsupported solidity type uint{}", n)), }, ParamType::Bool => Ok(quote! { bool }), diff --git a/ethers-contract/ethers-contract-derive/src/lib.rs b/ethers-contract/ethers-contract-derive/src/lib.rs index 03f34ce2..2e94a3a3 100644 --- a/ethers-contract/ethers-contract-derive/src/lib.rs +++ b/ethers-contract/ethers-contract-derive/src/lib.rs @@ -8,8 +8,8 @@ use proc_macro2::{Literal, Span}; use quote::{quote, quote_spanned}; use syn::spanned::Spanned as _; use syn::{ - parse::Error, parse_macro_input, AttrStyle, Data, DeriveInput, Expr, Fields, GenericArgument, - Lit, Meta, NestedMeta, PathArguments, Type, + parse::Error, parse_macro_input, AttrStyle, Data, DeriveInput, Expr, Field, Fields, + GenericArgument, Lit, Meta, NestedMeta, PathArguments, Type, }; use abigen::{expand, ContractArgs}; @@ -79,6 +79,8 @@ pub fn abigen(input: TokenStream) -> TokenStream { /// /// Additional arguments can be specified using the `#[ethevent(...)]` attribute: /// +/// For the struct: +/// /// - `name`, `name = "..."`: Overrides the generated `EthEvent` name, default is the /// struct's name. /// - `signature`, `signature = "..."`: The signature as hex string to override the @@ -86,9 +88,17 @@ pub fn abigen(input: TokenStream) -> TokenStream { /// - `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 +/// - `anonymous`: A flag to mark this as an anonymous event +/// +/// For fields: +/// +/// - `indexed`: flag to mark a field as an indexed event input +/// - `name`: override the name of an indexed event input, default is the rust field name /// /// # Example /// ```ignore +/// # use ethers_core::types::Address; +/// /// #[derive(Debug, EthAbiType)] /// struct Inner { /// inner: Address, @@ -98,8 +108,10 @@ pub fn abigen(input: TokenStream) -> TokenStream { /// #[derive(Debug, EthEvent)] /// #[ethevent(abi = "ValueChangedEvent((address,string),string)")] /// struct ValueChangedEvent { -/// inner: Inner, +/// #[ethevent(indexed, name = "_target")] +/// target: Address, /// msg: String, +/// inner: Inner, /// } /// ``` #[proc_macro_derive(EthEvent, attributes(ethevent))] @@ -113,14 +125,13 @@ pub fn derive_abi_event(input: TokenStream) -> TokenStream { let event_name = attributes .name - .map(|(n, _)| n) + .map(|(s, _)| s) .unwrap_or_else(|| input.ident.to_string()); - let (abi, hash) = if let Some((src, span)) = attributes.abi { + let mut event = 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()) + if let Ok(event) = parse_event(&src) { + event } else { // try as tuple if let Some(inputs) = Reader::read( @@ -142,12 +153,11 @@ pub fn derive_abi_event(input: TokenStream) -> TokenStream { ), _ => None, }) { - let event = 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) => { @@ -157,10 +167,7 @@ pub fn derive_abi_event(input: TokenStream) -> TokenStream { // 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()) - } + Ok(event) => event, Err(err) => { return TokenStream::from(Error::new(span, err).to_compile_error()) } @@ -173,20 +180,31 @@ pub fn derive_abi_event(input: TokenStream) -> TokenStream { } else { // try to determine the abi from the fields match derive_abi_event_from_fields(&input) { - Ok(mut event) => { - event.name = event_name.clone(); - (event.abi_signature(), event.signature()) - } + Ok(event) => event, Err(err) => return TokenStream::from(err.to_compile_error()), } }; + event.name = event_name.clone(); + if let Some((anon, _)) = attributes.anonymous.as_ref() { + event.anonymous = *anon; + } + + let decode_log_impl = match derive_decode_from_log_impl(&input, &event) { + Ok(log) => log, + Err(err) => return TokenStream::from(err.to_compile_error()), + }; + + let (abi, hash) = (event.abi_signature(), event.signature()); + let signature = if let Some((hash, _)) = attributes.signature_hash { signature(&hash) } else { signature(hash.as_bytes()) }; + let anon = attributes.anonymous.map(|(b, _)| b).unwrap_or_default(); + let ethevent_impl = quote! { impl ethers_contract::EthEvent for #name { @@ -201,6 +219,14 @@ pub fn derive_abi_event(input: TokenStream) -> TokenStream { fn abi_signature() -> ::std::borrow::Cow<'static, str> { #abi.into() } + + fn decode_log(log: ðers_core::abi::RawLog) -> Result where Self: Sized { + #decode_log_impl + } + + fn is_anonymous() -> bool { + #anon + } } }; @@ -213,11 +239,259 @@ pub fn derive_abi_event(input: TokenStream) -> TokenStream { }) } -fn derive_abi_event_from_fields(input: &DeriveInput) -> Result { - let types: Vec<_> = match input.data { +struct EventField { + topic_name: Option, + index: usize, + param: EventParam, +} + +impl EventField { + fn is_indexed(&self) -> bool { + self.topic_name.is_some() + } +} + +// Converts param types for indexed parameters to bytes32 where appropriate +// This applies to strings, arrays, structs and bytes to follow the encoding of +// these indexed param types according to +// https://solidity.readthedocs.io/en/develop/abi-spec.html#encoding-of-indexed-event-parameters +fn topic_param_type_quote(kind: &ParamType) -> proc_macro2::TokenStream { + match kind { + ParamType::String + | ParamType::Bytes + | ParamType::Array(_) + | ParamType::FixedArray(_, _) + | ParamType::Tuple(_) => quote! {ethers_core::abi::ParamType::FixedBytes(32)}, + ty => param_type_quote(ty), + } +} + +fn param_type_quote(kind: &ParamType) -> proc_macro2::TokenStream { + match kind { + ParamType::Address => { + quote! {ethers_core::abi::ParamType::Address} + } + ParamType::Bytes => { + quote! {ethers_core::abi::ParamType::Bytes} + } + ParamType::Int(size) => { + let size = Literal::usize_suffixed(*size); + quote! {ethers_core::abi::ParamType::Int(#size)} + } + ParamType::Uint(size) => { + let size = Literal::usize_suffixed(*size); + quote! {ethers_core::abi::ParamType::Uint(#size)} + } + ParamType::Bool => { + quote! {ethers_core::abi::ParamType::Bool} + } + ParamType::String => { + quote! {ethers_core::abi::ParamType::String} + } + ParamType::Array(ty) => { + let ty = param_type_quote(&*ty); + quote! {ethers_core::abi::ParamType::Array(Box::new(#ty))} + } + ParamType::FixedBytes(size) => { + let size = Literal::usize_suffixed(*size); + quote! {ethers_core::abi::ParamType::FixedBytes(#size)} + } + ParamType::FixedArray(ty, size) => { + let ty = param_type_quote(&*ty); + let size = Literal::usize_suffixed(*size); + quote! {ethers_core::abi::ParamType::FixedArray(Box::new(#ty),#size)} + } + ParamType::Tuple(tuple) => { + let elements = tuple.iter().map(param_type_quote); + quote! { + ethers_core::abi::ParamType::Tuple( + vec![ + #( #elements ),* + ] + ) + } + } + } +} + +fn derive_decode_from_log_impl( + input: &DeriveInput, + event: &Event, +) -> Result { + let fields: Vec<_> = match input.data { Data::Struct(ref data) => match data.fields { - Fields::Named(ref fields) => fields.named.iter().map(|f| &f.ty).collect(), - Fields::Unnamed(ref fields) => fields.unnamed.iter().map(|f| &f.ty).collect(), + Fields::Named(ref fields) => { + if fields.named.len() != event.inputs.len() { + return Err(Error::new( + fields.span(), + format!( + "EthEvent {}'s fields length don't match with signature inputs {}", + event.name, + event.abi_signature() + ), + )); + } + fields.named.iter().collect() + } + Fields::Unnamed(ref fields) => { + if fields.unnamed.len() != event.inputs.len() { + return Err(Error::new( + fields.span(), + format!( + "EthEvent {}'s fields length don't match with signature inputs {}", + event.name, + event.abi_signature() + ), + )); + } + fields.unnamed.iter().collect() + } + Fields::Unit => { + return Err(Error::new( + input.span(), + "EthEvent cannot be derived for empty structs and unit", + )); + } + }, + Data::Enum(_) => { + return Err(Error::new( + input.span(), + "EthEvent cannot be derived for enums", + )); + } + Data::Union(_) => { + return Err(Error::new( + input.span(), + "EthEvent cannot be derived for unions", + )); + } + }; + + 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 = if param.indexed { + if topic_name.is_none() { + Some(param.name.clone()) + } else { + topic_name + } + } else { + None + }; + + 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| topic_param_type_quote(&f.param.kind)); + + let topic_types_init = quote! {let topic_types = vec![#( #topic_types ),*];}; + + let data_types = event_fields + .iter() + .filter(|f| !f.is_indexed()) + .map(|f| param_type_quote(&f.param.kind)); + + let data_types_init = quote! {let data_types = vec![#( #data_types ),*];}; + + // decode + let (signature_check, flat_topics_init, topic_tokens_len_check) = if event.anonymous { + ( + quote! {}, + quote! { + let flat_topics = topics.iter().flat_map(|t| t.as_ref().to_vec()).collect::>(); + }, + quote! { + if topic_tokens.len() != topics.len() { + return Err(ethers_core::abi::Error::InvalidData); + } + }, + ) + } else { + ( + quote! { + let event_signature = topics.get(0).ok_or(ethers_core::abi::Error::InvalidData)?; + if event_signature != &Self::signature() { + return Err(ethers_core::abi::Error::InvalidData); + } + }, + quote! { + let flat_topics = topics.iter().skip(1).flat_map(|t| t.as_ref().to_vec()).collect::>(); + }, + quote! { + if topic_tokens.is_empty() || topic_tokens.len() != topics.len() - 1 { + return Err(ethers_core::abi::Error::InvalidData); + } + }, + ) + }; + + // check if indexed are sorted + let tokens_init = if event_fields + .iter() + .filter(|f| f.is_indexed()) + .enumerate() + .all(|(idx, f)| f.index == idx) + { + quote! { + let topic_tokens = ethers_core::abi::decode(&topic_types, &flat_topics)?; + #topic_tokens_len_check + let data_tokens = ethers_core::abi::decode(&data_types, &data)?; + let tokens:Vec<_> = topic_tokens.into_iter().chain(data_tokens.into_iter()).collect(); + } + } else { + let swap_tokens = event_fields.iter().map(|field| { + if field.is_indexed() { + quote! { topic_tokens.remove(0) } + } else { + quote! { data_tokens.remove(0) } + } + }); + + quote! { + let mut topic_tokens = ethers_core::abi::decode(&topic_types, &flat_topics)?; + #topic_tokens_len_check + let mut data_tokens = ethers_core::abi::decode(&data_types, &data)?; + let mut tokens = Vec::with_capacity(topics.len() + data_tokens.len()); + #( tokens.push(#swap_tokens); )* + } + }; + + Ok(quote! { + + let ethers_core::abi::RawLog {data, topics} = log; + + #signature_check + + #topic_types_init + #data_types_init + + #flat_topics_init + + #tokens_init + + ethers_core::abi::Detokenize::from_tokens(tokens).map_err(|_|ethers_core::abi::Error::InvalidData) + }) +} + +fn derive_abi_event_from_fields(input: &DeriveInput) -> Result { + let fields: Vec<_> = match input.data { + Data::Struct(ref data) => match data.fields { + Fields::Named(ref fields) => fields.named.iter().collect(), + Fields::Unnamed(ref fields) => fields.unnamed.iter().collect(), Fields::Unit => { return Err(Error::new( input.span(), @@ -239,17 +513,24 @@ fn derive_abi_event_from_fields(input: &DeriveInput) -> Result { } }; - let inputs = types + let inputs = fields .iter() - .map(|ty| find_parameter_type(ty)) + .map(|f| { + let name = f + .ident + .as_ref() + .map(|name| name.to_string()) + .unwrap_or_else(|| "".to_string()); + find_parameter_type(&f.ty).map(|ty| (name, ty)) + }) .collect::, _>>()?; let event = Event { name: "".to_string(), inputs: inputs .into_iter() - .map(|kind| EventParam { - name: "".to_string(), + .map(|(name, kind)| EventParam { + name, kind, indexed: false, }) @@ -259,6 +540,55 @@ fn derive_abi_event_from_fields(input: &DeriveInput) -> Result { Ok(event) } +fn parse_field_attributes(field: &Field) -> Result<(Option, bool), Error> { + let mut indexed = false; + let mut topic_name = None; + for a in field.attrs.iter() { + if let AttrStyle::Outer = a.style { + if let Ok(Meta::List(meta)) = a.parse_meta() { + if meta.path.is_ident("ethevent") { + for n in meta.nested.iter() { + if let NestedMeta::Meta(meta) = n { + match meta { + Meta::Path(path) => { + if path.is_ident("indexed") { + indexed = true; + } else { + return Err(Error::new( + path.span(), + "unrecognized ethevent parameter", + )); + } + } + Meta::List(meta) => { + return Err(Error::new( + meta.path.span(), + "unrecognized ethevent parameter", + )); + } + Meta::NameValue(meta) => { + if meta.path.is_ident("name") { + if let Lit::Str(ref lit_str) = meta.lit { + topic_name = Some(lit_str.value()); + } else { + return Err(Error::new( + meta.span(), + "name attribute must be a string", + )); + } + } + } + } + } + } + } + } + } + } + + Ok((topic_name, indexed)) +} + fn find_parameter_type(ty: &Type) -> Result { match ty { Type::Array(ty) => { @@ -315,13 +645,10 @@ fn find_parameter_type(ty: &Type) -> Result { .collect::, _>>()?; Ok(ParamType::Tuple(params)) } - _ => { - eprintln!("Found other types"); - Err(Error::new( - ty.span(), - "Failed to derive proper ABI from fields", - )) - } + _ => Err(Error::new( + ty.span(), + "Failed to derive proper ABI from fields", + )), } } @@ -496,23 +823,21 @@ fn derive_tokenizeable_impl(input: &DeriveInput) -> proc_macro2::TokenStream { ) } } + + impl<#generic_params> ethers_core::abi::TokenizableItem for #name<#generic_args> + where + #generic_predicates + #tokenize_predicates + { } } } +#[derive(Default)] struct Attributes { name: Option<(String, Span)>, abi: Option<(String, Span)>, signature_hash: Option<(Vec, Span)>, -} - -impl Default for Attributes { - fn default() -> Self { - Self { - name: None, - abi: None, - signature_hash: None, - } - } + anonymous: Option<(bool, Span)>, } fn parse_attributes(input: &DeriveInput) -> Result { @@ -525,6 +850,20 @@ fn parse_attributes(input: &DeriveInput) -> Result { + if let Some(name) = path.get_ident() { + if &*name.to_string() == "anonymous" { + if result.anonymous.is_none() { + result.anonymous = Some((true, name.span())); + continue; + } else { + return Err(Error::new( + name.span(), + "anonymous already specified", + ) + .to_compile_error()); + } + } + } return Err(Error::new( path.span(), "unrecognized ethevent parameter", @@ -532,7 +871,6 @@ fn parse_attributes(input: &DeriveInput) -> Result { - // TODO support raw list return Err(Error::new( meta.path.span(), "unrecognized ethevent parameter", @@ -540,7 +878,26 @@ fn parse_attributes(input: &DeriveInput) -> Result { - if meta.path.is_ident("name") { + if meta.path.is_ident("anonymous") { + if let Lit::Bool(ref bool_lit) = meta.lit { + if result.anonymous.is_none() { + result.anonymous = + Some((bool_lit.value, bool_lit.span())); + } else { + return Err(Error::new( + meta.span(), + "anonymous already specified", + ) + .to_compile_error()); + } + } else { + return Err(Error::new( + meta.span(), + "name must be a string", + ) + .to_compile_error()); + } + } else if meta.path.is_ident("name") { if let Lit::Str(ref lit_str) = meta.lit { if result.name.is_none() { result.name = diff --git a/ethers-contract/src/contract.rs b/ethers-contract/src/contract.rs index 8aa3fddd..c4d4e8c0 100644 --- a/ethers-contract/src/contract.rs +++ b/ethers-contract/src/contract.rs @@ -1,7 +1,8 @@ -use super::{ +use crate::{ base::{encode_function_data, AbiError, BaseContract}, call::ContractCall, - event::Event, + event::{EthEvent, Event}, + EthLogDecode, }; use ethers_core::{ @@ -108,7 +109,7 @@ use std::{fmt::Debug, marker::PhantomData, sync::Arc}; /// ```no_run /// # async fn foo() -> Result<(), Box> { /// use ethers_core::{abi::Abi, types::Address}; -/// use ethers_contract::Contract; +/// use ethers_contract::{Contract, EthEvent}; /// use ethers_providers::{Provider, Http, Middleware}; /// use ethers_signers::Wallet; /// use std::convert::TryFrom; @@ -119,7 +120,7 @@ use std::{fmt::Debug, marker::PhantomData, sync::Arc}; /// # let client = Provider::::try_from("http://localhost:8545").unwrap(); /// # let contract = Contract::new(address, abi, client); /// -/// #[derive(Clone, Debug)] +/// #[derive(Clone, Debug, EthEvent)] /// struct ValueChanged { /// old_author: Address, /// new_author: Address, @@ -127,25 +128,8 @@ use std::{fmt::Debug, marker::PhantomData, sync::Arc}; /// new_value: String, /// } /// -/// impl Detokenize for ValueChanged { -/// fn from_tokens(tokens: Vec) -> Result { -/// let old_author: Address = tokens[1].clone().into_address().unwrap(); -/// let new_author: Address = tokens[1].clone().into_address().unwrap(); -/// let old_value = tokens[2].clone().into_string().unwrap(); -/// let new_value = tokens[3].clone().into_string().unwrap(); -/// -/// Ok(Self { -/// old_author, -/// new_author, -/// old_value, -/// new_value, -/// }) -/// } -/// } -/// -/// /// let logs: Vec = contract -/// .event("ValueChanged")? +/// .event() /// .from_block(0u64) /// .query() /// .await?; @@ -179,18 +163,25 @@ impl Contract { } } - /// Returns an [`Event`](crate::builders::Event) builder for the provided event name. - pub fn event(&self, name: &str) -> Result, Error> { + /// Returns an [`Event`](crate::builders::Event) builder for the provided event. + pub fn event(&self) -> Event { + self.event_with_filter(Filter::new().event(&D::abi_signature())) + } + + /// Returns an [`Event`](crate::builders::Event) builder with the provided filter. + pub fn event_with_filter(&self, filter: Filter) -> Event { + Event { + provider: &self.client, + filter: filter.address(self.address), + datatype: PhantomData, + } + } + + /// Returns an [`Event`](crate::builders::Event) builder with the provided name. + pub fn event_for_name(&self, name: &str) -> Result, Error> { // get the event's full name let event = self.base_contract.abi.event(name)?; - Ok(Event { - provider: &self.client, - filter: Filter::new() - .event(&event.abi_signature()) - .address(self.address), - event: &event, - datatype: PhantomData, - }) + Ok(self.event_with_filter(Filter::new().event(&event.abi_signature()))) } /// Returns a transaction builder for the provided function name. If there are diff --git a/ethers-contract/src/event.rs b/ethers-contract/src/event.rs index 2c4abf69..4e88d608 100644 --- a/ethers-contract/src/event.rs +++ b/ethers-contract/src/event.rs @@ -1,7 +1,7 @@ -use crate::{base::decode_event, stream::EventStream, ContractError}; +use crate::{stream::EventStream, ContractError, EthLogDecode}; use ethers_core::{ - abi::{Detokenize, Event as AbiEvent}, + abi::{Detokenize, RawLog}, types::{BlockNumber, Filter, Log, TxHash, ValueOrArray, H256, U64}, }; use ethers_providers::{FilterWatcher, Middleware, PubsubClient, SubscriptionStream}; @@ -21,22 +21,52 @@ pub trait EthEvent: Detokenize { /// Retrieves the ABI signature for the event this data corresponds /// to. fn abi_signature() -> Cow<'static, str>; + + /// Decodes an Ethereum `RawLog` into an instance of the type. + fn decode_log(log: &RawLog) -> Result + where + Self: Sized; + + /// Returns true if this is an anonymous event + fn is_anonymous() -> bool; + + /// Returns an Event builder for the ethereum event represented by this types ABI signature. + fn new(filter: Filter, provider: &M) -> Event + where + Self: Sized, + { + let filter = filter.event(&Self::abi_signature()); + Event { + filter, + provider, + datatype: PhantomData, + } + } +} + +// Convenience implementation +impl EthLogDecode for T { + fn decode_log(log: &RawLog) -> Result + where + Self: Sized, + { + T::decode_log(log) + } } /// Helper for managing the event filter before querying or streaming its logs #[derive(Debug)] #[must_use = "event filters do nothing unless you `query` or `stream` them"] -pub struct Event<'a: 'b, 'b, M, D> { +pub struct Event<'a, M, D> { /// The event filter's state pub filter: Filter, - /// The ABI of the event which is being filtered - pub event: &'b AbiEvent, pub(crate) provider: &'a M, + /// Stores the event datatype pub(crate) datatype: PhantomData, } // TODO: Improve these functions -impl Event<'_, '_, M, D> { +impl Event<'_, M, D> { /// Sets the filter's `from` block #[allow(clippy::wrong_self_convention)] pub fn from_block>(mut self, block: T) -> Self { @@ -84,10 +114,10 @@ impl Event<'_, '_, M, D> { } } -impl<'a, 'b, M, D> Event<'a, 'b, M, D> +impl<'a, M, D> Event<'a, M, D> where M: Middleware, - D: 'b + Detokenize + Clone, + D: EthLogDecode, { /// Returns a stream for the event pub async fn stream( @@ -110,11 +140,11 @@ where } } -impl<'a, 'b, M, D> Event<'a, 'b, M, D> +impl<'a, M, D> Event<'a, M, D> where M: Middleware, ::Provider: PubsubClient, - D: 'b + Detokenize + Clone, + D: EthLogDecode, { /// Returns a subscription for the event pub async fn subscribe( @@ -137,10 +167,10 @@ where } } -impl Event<'_, '_, M, D> +impl Event<'_, M, D> where M: Middleware, - D: Detokenize + Clone, + D: EthLogDecode, { /// Queries the blockchain for the selected filter and returns a vector of matching /// event logs @@ -177,7 +207,11 @@ where } fn parse_log(&self, log: Log) -> Result> { - Ok(decode_event(self.event, log.topics, log.data)?) + D::decode_log(&RawLog { + topics: log.topics, + data: log.data.to_vec(), + }) + .map_err(From::from) } } diff --git a/ethers-contract/src/lib.rs b/ethers-contract/src/lib.rs index 84a8c87d..6a9df387 100644 --- a/ethers-contract/src/lib.rs +++ b/ethers-contract/src/lib.rs @@ -28,6 +28,9 @@ pub use factory::ContractFactory; mod event; pub use event::EthEvent; +mod log; +pub use log::{decode_logs, EthLogDecode}; + mod stream; mod multicall; diff --git a/ethers-contract/src/log.rs b/ethers-contract/src/log.rs new file mode 100644 index 00000000..bfc54d53 --- /dev/null +++ b/ethers-contract/src/log.rs @@ -0,0 +1,16 @@ +//! Mod of types for ethereum logs +use ethers_core::abi::Error; +use ethers_core::abi::RawLog; + +/// A trait for types (events) that can be decoded from a `RawLog` +pub trait EthLogDecode { + /// decode from a `RawLog` + fn decode_log(log: &RawLog) -> Result + where + Self: Sized; +} + +/// Decodes a series of logs into a vector +pub fn decode_logs(logs: &[RawLog]) -> Result, Error> { + logs.iter().map(T::decode_log).collect() +} diff --git a/ethers-contract/tests/common/derive.rs b/ethers-contract/tests/common/derive.rs index 2757178f..08628611 100644 --- a/ethers-contract/tests/common/derive.rs +++ b/ethers-contract/tests/common/derive.rs @@ -1,5 +1,5 @@ use ethers::core::types::{H160, H256, I256, U128, U256}; -use ethers_contract::{EthAbiType, EthEvent}; +use ethers_contract::{abigen, EthAbiType, EthEvent}; use ethers_core::abi::Tokenizable; use ethers_core::types::Address; @@ -195,3 +195,44 @@ fn can_set_eth_abi_attribute() { ValueChangedEvent2::abi_signature() ); } + +#[test] +fn can_derive_indexed_and_anonymous_attribute() { + #[derive(Debug, PartialEq, EthEvent)] + #[ethevent(anonymous)] + struct ValueChangedEvent { + old_author: Address, + #[ethevent(indexed, name = "newAuthor")] + new_author: Address, + old_value: String, + new_value: String, + } + + assert_eq!( + "ValueChangedEvent(address,address,string,string) anonymous", + ValueChangedEvent::abi_signature() + ); +} + +#[test] +fn can_generate_ethevent_from_json() { + abigen!(DsProxyFactory, + "ethers-middleware/contracts/DsProxyFactory.json", + methods { + build(address) as build_with_owner; + } + ); + + assert_eq!( + "Created(address,address,address,address)", + CreatedFilter::abi_signature() + ); + + assert_eq!( + H256([ + 37, 155, 48, 202, 57, 136, 92, 109, 128, 26, 11, 93, 188, 152, 134, 64, 243, 194, 94, + 47, 55, 83, 31, 225, 56, 197, 197, 175, 137, 85, 212, 27, + ]), + CreatedFilter::signature() + ); +} diff --git a/ethers-contract/tests/common/mod.rs b/ethers-contract/tests/common/mod.rs index fe3f8773..ea1ffca9 100644 --- a/ethers-contract/tests/common/mod.rs +++ b/ethers-contract/tests/common/mod.rs @@ -4,18 +4,20 @@ use ethers_core::{ types::{Address, Bytes}, }; -use ethers_contract::{Contract, ContractFactory, EthAbiType}; +use ethers_contract::{Contract, ContractFactory, EthEvent}; use ethers_core::utils::{GanacheInstance, Solc}; use ethers_middleware::signer::SignerMiddleware; use ethers_providers::{Http, Middleware, Provider}; use ethers_signers::LocalWallet; use std::{convert::TryFrom, sync::Arc, time::Duration}; -// Note: The `EthAbiType` derive macro implements the necessary conversion between `Tokens` and +// Note: The `EthEvent` derive macro implements the necessary conversion between `Tokens` and // the struct -#[derive(Clone, Debug, EthAbiType)] +#[derive(Clone, Debug, EthEvent)] pub struct ValueChanged { + #[ethevent(indexed)] pub old_author: Address, + #[ethevent(indexed)] pub new_author: Address, pub old_value: String, pub new_value: String, diff --git a/ethers-contract/tests/contract.rs b/ethers-contract/tests/contract.rs index 5a17c643..16fface7 100644 --- a/ethers-contract/tests/contract.rs +++ b/ethers-contract/tests/contract.rs @@ -109,8 +109,7 @@ mod eth_tests { // and we can fetch the events let logs: Vec = contract - .event("ValueChanged") - .unwrap() + .event() .from_block(0u64) .topic1(client.address()) // Corresponds to the first indexed parameter .query() @@ -123,8 +122,7 @@ mod eth_tests { // and we can fetch the events at a block hash let hash = client.get_block(1).await.unwrap().unwrap().hash.unwrap(); let logs: Vec = contract - .event("ValueChanged") - .unwrap() + .event() .at_block_hash(hash) .topic1(client.address()) // Corresponds to the first indexed parameter .query() @@ -256,14 +254,14 @@ mod eth_tests { let contract = deploy(client, abi.clone(), bytecode).await; // We spawn the event listener: - let event = contract.event::("ValueChanged").unwrap(); + let event = contract.event::(); let mut stream = event.stream().await.unwrap(); assert_eq!(stream.id, 1.into()); // Also set up a subscription for the same thing let ws = Provider::connect(ganache.ws_endpoint()).await.unwrap(); let contract2 = ethers_contract::Contract::new(contract.address(), abi, ws); - let event2 = contract2.event::("ValueChanged").unwrap(); + let event2 = contract2.event::(); let mut subscription = event2.subscribe().await.unwrap(); assert_eq!(subscription.id, 2.into()); diff --git a/ethers-middleware/contracts/DsProxyFactory.json b/ethers-middleware/contracts/DsProxyFactory.json new file mode 100644 index 00000000..72161d66 --- /dev/null +++ b/ethers-middleware/contracts/DsProxyFactory.json @@ -0,0 +1,95 @@ +[ + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "address" + } + ], + "name": "isProxy", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "cache", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "build", + "outputs": [ + { + "name": "proxy", + "type": "address" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "owner", + "type": "address" + } + ], + "name": "build", + "outputs": [ + { + "name": "proxy", + "type": "address" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "name": "proxy", + "type": "address" + }, + { + "indexed": false, + "name": "cache", + "type": "address" + } + ], + "name": "Created", + "type": "event" + } +] \ No newline at end of file diff --git a/ethers-middleware/src/transformer/ds_proxy/factory.rs b/ethers-middleware/src/transformer/ds_proxy/factory.rs index eefb5739..bd3b042b 100644 --- a/ethers-middleware/src/transformer/ds_proxy/factory.rs +++ b/ethers-middleware/src/transformer/ds_proxy/factory.rs @@ -15,6 +15,18 @@ pub static ADDRESS_BOOK: Lazy> = Lazy::new(|| { m }); +/// +/// Generated with +/// ```ignore +/// # use ethers_contract::abigen; +/// abigen!(DsProxyFactory, +/// "ethers-middleware/contracts/DsProxyFactory.json", +/// methods { +/// build() as build_with_sender; +/// } +/// ); +/// ``` +/// // Auto-generated type-safe bindings pub use dsproxyfactory_mod::*; #[allow(clippy::too_many_arguments)] @@ -23,7 +35,7 @@ mod dsproxyfactory_mod { #![allow(unused_imports)] use ethers_contract::{ builders::{ContractCall, Event}, - Contract, Lazy, + Contract, EthEvent, Lazy, }; use ethers_core::{ abi::{parse_abi, Abi, Detokenize, InvalidOutputType, Token, Tokenizable}, @@ -64,8 +76,19 @@ mod dsproxyfactory_mod { .method_hash([41, 113, 3, 136], p0) .expect("method not found (this should never happen)") } - #[doc = "Calls the contract's `build` (0xf3701da2) function"] - pub fn build(&self, owner: Address) -> ContractCall { + ///Calls the contract's `build` (0x8e1a55fc) function + pub fn build_with_sender( + &self, + ) -> ethers_contract::builders::ContractCall { + self.0 + .method_hash([142, 26, 85, 252], ()) + .expect("method not found (this should never happen)") + } + ///Calls the contract's `build` (0xf3701da2) function + pub fn build( + &self, + owner: ethers_core::types::Address, + ) -> ethers_contract::builders::ContractCall { self.0 .method_hash([243, 112, 29, 162], owner) .expect("method not found (this should never happen)") @@ -76,60 +99,24 @@ mod dsproxyfactory_mod { .method_hash([96, 199, 210, 149], ()) .expect("method not found (this should never happen)") } - #[doc = "Gets the contract's `Created` event"] - pub fn created_filter(&self) -> Event { - self.0 - .event("Created") - .expect("event not found (this should never happen)") + ///Gets the contract's `Created` event + pub fn created_filter(&self) -> ethers_contract::builders::Event { + self.0.event() + } + + /// Returns an [`Event`](ethers_contract::builders::Event) builder for all events of this contract + pub fn events(&self) -> ethers_contract::builders::Event { + self.0.event_with_filter(Default::default()) } } - #[derive(Clone, Debug, Default, Eq, PartialEq)] + #[derive(Clone, Debug, Default, Eq, PartialEq, EthEvent)] + #[ethevent(name = "Created")] pub struct CreatedFilter { + #[ethevent(indexed)] pub sender: Address, + #[ethevent(indexed)] pub owner: Address, pub proxy: Address, pub cache: Address, } - impl CreatedFilter { - #[doc = r" Retrieves the signature for the event this data corresponds to."] - #[doc = r" This signature is the Keccak-256 hash of the ABI signature of"] - #[doc = r" this event."] - pub const fn signature() -> H256 { - H256([ - 37, 155, 48, 202, 57, 136, 92, 109, 128, 26, 11, 93, 188, 152, 134, 64, 243, 194, - 94, 47, 55, 83, 31, 225, 56, 197, 197, 175, 137, 85, 212, 27, - ]) - } - #[doc = r" Retrieves the ABI signature for the event this data corresponds"] - #[doc = r" to. For this event the value should always be:"] - #[doc = r""] - #[doc = "`Created(address,address,address,address)`"] - pub const fn abi_signature() -> &'static str { - "Created(address,address,address,address)" - } - } - impl Detokenize for CreatedFilter { - fn from_tokens(tokens: Vec) -> Result { - if tokens.len() != 4 { - return Err(InvalidOutputType(format!( - "Expected {} tokens, got {}: {:?}", - 4, - tokens.len(), - tokens - ))); - } - #[allow(unused_mut)] - let mut tokens = tokens.into_iter(); - let sender = Tokenizable::from_token(tokens.next().expect("this should never happen"))?; - let owner = Tokenizable::from_token(tokens.next().expect("this should never happen"))?; - let proxy = Tokenizable::from_token(tokens.next().expect("this should never happen"))?; - let cache = Tokenizable::from_token(tokens.next().expect("this should never happen"))?; - Ok(CreatedFilter { - sender, - owner, - proxy, - cache, - }) - } - } }