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 03eabc3ead
.
* fix: make indexed names optional
* fix: dsproxy name
* fix: rename ethers top level module imports
This commit is contained in:
parent
021f79cb77
commit
816c5fc071
|
@ -79,15 +79,14 @@ impl Context {
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
mod #name_mod {
|
mod #name_mod {
|
||||||
#imports
|
#imports
|
||||||
|
|
||||||
#struct_decl
|
#struct_decl
|
||||||
|
|
||||||
impl<'a, M: Middleware> #name<M> {
|
impl<'a, M: ethers_providers::Middleware> #name<M> {
|
||||||
/// Creates a new contract instance with the specified `ethers`
|
/// Creates a new contract instance with the specified `ethers`
|
||||||
/// client at the given `Address`. The contract derefs to a `ethers::Contract`
|
/// client at the given `Address`. The contract derefs to a `ethers::Contract`
|
||||||
/// object
|
/// object
|
||||||
pub fn new<T: Into<Address>>(address: T, client: Arc<M>) -> Self {
|
pub fn new<T: Into<ethers_core::types::Address>>(address: T, client: ::std::sync::Arc<M>) -> Self {
|
||||||
let contract = Contract::new(address.into(), #abi_name.clone(), client);
|
let contract = ethers_contract::Contract::new(address.into(), #abi_name.clone(), client);
|
||||||
Self(contract)
|
Self(contract)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,11 +15,12 @@ pub(crate) fn imports(name: &str) -> TokenStream {
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use ethers::{
|
use ethers::{
|
||||||
core::{
|
core::{
|
||||||
|
self as ethers_core,
|
||||||
abi::{Abi, Token, Detokenize, InvalidOutputType, Tokenizable},
|
abi::{Abi, Token, Detokenize, InvalidOutputType, Tokenizable},
|
||||||
types::*, // import all the types so that we can codegen for everything
|
types::*, // import all the types so that we can codegen for everything
|
||||||
},
|
},
|
||||||
contract::{Contract, builders::{ContractCall, Event}, Lazy},
|
contract::{self as ethers_contract, Contract, builders::{ContractCall, Event}, Lazy},
|
||||||
providers::Middleware,
|
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 {
|
let abi_parse = if !cx.human_readable {
|
||||||
quote! {
|
quote! {
|
||||||
pub static #abi_name: Lazy<Abi> = Lazy::new(|| serde_json::from_str(#abi)
|
pub static #abi_name: ethers_contract::Lazy<ethers_core::abi::Abi> = ethers_contract::Lazy::new(|| serde_json::from_str(#abi)
|
||||||
.expect("invalid abi"));
|
.expect("invalid abi"));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
quote! {
|
quote! {
|
||||||
pub static #abi_name: Lazy<Abi> = Lazy::new(|| ethers::core::abi::parse_abi_str(#abi)
|
pub static #abi_name: ethers_contract::Lazy<ethers_core::abi::Abi> = ethers_contract::Lazy::new(|| ethers::core::abi::parse_abi_str(#abi)
|
||||||
.expect("invalid abi"));
|
.expect("invalid abi"));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -47,17 +48,17 @@ pub(crate) fn struct_declaration(cx: &Context, abi_name: &proc_macro2::Ident) ->
|
||||||
|
|
||||||
// Struct declaration
|
// Struct declaration
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct #name<M>(Contract<M>);
|
pub struct #name<M>(ethers_contract::Contract<M>);
|
||||||
|
|
||||||
|
|
||||||
// Deref to the inner contract in order to access more specific functions functions
|
// Deref to the inner contract in order to access more specific functions functions
|
||||||
impl<M> std::ops::Deref for #name<M> {
|
impl<M> std::ops::Deref for #name<M> {
|
||||||
type Target = Contract<M>;
|
type Target = ethers_contract::Contract<M>;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target { &self.0 }
|
fn deref(&self) -> &Self::Target { &self.0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<M: Middleware> std::fmt::Debug for #name<M> {
|
impl<M: ethers_providers::Middleware> std::fmt::Debug for #name<M> {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
f.debug_tuple(stringify!(#name))
|
f.debug_tuple(stringify!(#name))
|
||||||
.field(&self.address())
|
.field(&self.address())
|
||||||
|
|
|
@ -2,7 +2,7 @@ use super::{types, util, Context};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use ethers_core::abi::{Event, EventExt, EventParam, Hash, ParamType, SolStruct};
|
use ethers_core::abi::{Event, EventExt, EventParam, Hash, ParamType, SolStruct};
|
||||||
use inflector::Inflector;
|
use inflector::Inflector;
|
||||||
use proc_macro2::{Literal, TokenStream};
|
use proc_macro2::{Ident, Literal, TokenStream};
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use syn::Path;
|
use syn::Path;
|
||||||
|
@ -17,33 +17,123 @@ impl Context {
|
||||||
.map(|event| self.expand_event(event))
|
.map(|event| self.expand_event(event))
|
||||||
.collect::<Result<Vec<_>>>()?;
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
|
||||||
if data_types.is_empty() {
|
// only expand enums when multiple events are present
|
||||||
return Ok(quote! {});
|
let events_enum_decl = if sorted_events.values().flatten().count() > 1 {
|
||||||
}
|
self.expand_events_enum()
|
||||||
|
} else {
|
||||||
|
quote! {}
|
||||||
|
};
|
||||||
|
|
||||||
Ok(quote! {
|
Ok(quote! {
|
||||||
#( #data_types )*
|
#( #data_types )*
|
||||||
|
|
||||||
|
#events_enum_decl
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate the event filter methods for the contract
|
/// Generate the event filter methods for the contract
|
||||||
pub fn event_methods(&self) -> Result<TokenStream> {
|
pub fn event_methods(&self) -> Result<TokenStream> {
|
||||||
let sorted_events: BTreeMap<_, _> = self.abi.events.clone().into_iter().collect();
|
let sorted_events: BTreeMap<_, _> = self.abi.events.clone().into_iter().collect();
|
||||||
let data_types = sorted_events
|
let filter_methods = sorted_events
|
||||||
.values()
|
.values()
|
||||||
.flatten()
|
.flatten()
|
||||||
.map(|event| self.expand_filter(event))
|
.map(|event| self.expand_filter(event))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
if data_types.is_empty() {
|
let events_method = self.expand_events_method();
|
||||||
return Ok(quote! {});
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(quote! {
|
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::<Vec<_>>();
|
||||||
|
|
||||||
|
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<Self, ethers_core::abi::InvalidOutputType> 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<Self, ethers_core::abi::Error>
|
||||||
|
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<M, #ty> {
|
||||||
|
self.0.event_with_filter(Default::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote! {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Expands an event property type.
|
/// Expands an event property type.
|
||||||
///
|
///
|
||||||
/// Note that this is slightly different than an expanding a Solidity type as
|
/// 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>});
|
return Ok(quote! {::std::vec::Vec<#ty>});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
quote! { H256 }
|
quote! { ethers_core::types::H256 }
|
||||||
}
|
}
|
||||||
(ParamType::FixedArray(ty, size), true) => {
|
(ParamType::FixedArray(ty, size), true) => {
|
||||||
if let ParamType::Tuple(..) = **ty {
|
if let ParamType::Tuple(..) = **ty {
|
||||||
|
@ -82,7 +172,7 @@ impl Context {
|
||||||
return Ok(quote! {[#ty; #size]});
|
return Ok(quote! {[#ty; #size]});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
quote! { H256 }
|
quote! { ethers_core::types::H256 }
|
||||||
}
|
}
|
||||||
(ParamType::Tuple(..), true) => {
|
(ParamType::Tuple(..), true) => {
|
||||||
// represents an struct
|
// represents an struct
|
||||||
|
@ -95,11 +185,11 @@ impl Context {
|
||||||
{
|
{
|
||||||
quote! {#ty}
|
quote! {#ty}
|
||||||
} else {
|
} else {
|
||||||
quote! { H256 }
|
quote! { ethers_core::types::H256 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(ParamType::Bytes, true) | (ParamType::String, true) => {
|
(ParamType::Bytes, true) | (ParamType::String, true) => {
|
||||||
quote! { H256 }
|
quote! { ethers_core::types::H256 }
|
||||||
}
|
}
|
||||||
(kind, _) => types::expand(kind)?,
|
(kind, _) => types::expand(kind)?,
|
||||||
})
|
})
|
||||||
|
@ -128,13 +218,12 @@ impl Context {
|
||||||
let name = util::safe_ident(&format!("{}_filter", event.name.to_snake_case()));
|
let name = util::safe_ident(&format!("{}_filter", event.name.to_snake_case()));
|
||||||
// let result = util::ident(&event.name.to_pascal_case());
|
// let result = util::ident(&event.name.to_pascal_case());
|
||||||
let result = expand_struct_name(event);
|
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));
|
let doc = util::expand_doc(&format!("Gets the contract's `{}` event", event.name));
|
||||||
quote! {
|
quote! {
|
||||||
#doc
|
#doc
|
||||||
pub fn #name(&self) -> Event<M, #result> {
|
pub fn #name(&self) -> ethers_contract::builders::Event<M, #result> {
|
||||||
self.0.event(#ev_name).expect("event not found (this should never happen)")
|
self.0.event()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -159,7 +248,7 @@ impl Context {
|
||||||
let event_abi_name = &event.name;
|
let event_abi_name = &event.name;
|
||||||
|
|
||||||
Ok(quote! {
|
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 )]
|
#[ethevent( name = #event_abi_name, abi = #abi_signature )]
|
||||||
pub #data_type_definition
|
pub #data_type_definition
|
||||||
})
|
})
|
||||||
|
@ -210,17 +299,16 @@ impl Context {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Expands an ABI event into an identifier for its event data type.
|
/// 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?
|
// TODO: get rid of `Filter` suffix?
|
||||||
let name = format!("{}Filter", event.name.to_pascal_case());
|
let name = format!("{}Filter", event.name.to_pascal_case());
|
||||||
let event_name = util::ident(&name);
|
util::ident(&name)
|
||||||
quote! { #event_name }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Expands an event data structure from its name-type parameter pairs. Returns
|
/// Expands an event data structure from its name-type parameter pairs. Returns
|
||||||
/// a tuple with the type definition (i.e. the struct declaration) and
|
/// a tuple with the type definition (i.e. the struct declaration) and
|
||||||
/// construction (i.e. code for creating an instance of the event data).
|
/// 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
|
let fields = params
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(name, ty)| quote! { pub #name: #ty })
|
.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.
|
/// Expands an event data named tuple from its name-type parameter pairs.
|
||||||
/// Returns a tuple with the type definition and construction.
|
/// 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
|
let fields = params
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(_, ty)| quote! { pub #ty })
|
.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);
|
let bytes = hash.as_bytes().iter().copied().map(Literal::u8_unsuffixed);
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
H256([#( #bytes ),*])
|
ethers_core::types::H256([#( #bytes ),*])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -296,10 +384,8 @@ mod tests {
|
||||||
let cx = test_context();
|
let cx = test_context();
|
||||||
assert_quote!(cx.expand_filter(&event), {
|
assert_quote!(cx.expand_filter(&event), {
|
||||||
#[doc = "Gets the contract's `Transfer` event"]
|
#[doc = "Gets the contract's `Transfer` event"]
|
||||||
pub fn transfer_filter(&self) -> Event<M, TransferFilter> {
|
pub fn transfer_filter(&self) -> ethers_contract::builders::Event<M, TransferFilter> {
|
||||||
self.0
|
self.0.event()
|
||||||
.event("Transfer")
|
|
||||||
.expect("event not found (this should never happen)")
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -331,7 +417,7 @@ mod tests {
|
||||||
assert_quote!(definition, {
|
assert_quote!(definition, {
|
||||||
struct FooFilter {
|
struct FooFilter {
|
||||||
pub a: bool,
|
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);
|
let definition = expand_data_tuple(&name, ¶ms);
|
||||||
|
|
||||||
assert_quote!(definition, {
|
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()
|
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f".parse().unwrap()
|
||||||
),
|
),
|
||||||
{
|
{
|
||||||
H256([
|
ethers_core::types::H256([
|
||||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
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
|
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31
|
||||||
])
|
])
|
||||||
|
|
|
@ -39,7 +39,7 @@ fn expand_function(function: &Function, alias: Option<Ident>) -> Result<TokenStr
|
||||||
|
|
||||||
let outputs = expand_fn_outputs(&function.outputs)?;
|
let outputs = expand_fn_outputs(&function.outputs)?;
|
||||||
|
|
||||||
let result = quote! { ContractCall<M, #outputs> };
|
let result = quote! { ethers_contract::builders::ContractCall<M, #outputs> };
|
||||||
|
|
||||||
let arg = expand_inputs_call_arg(&function.inputs);
|
let arg = expand_inputs_call_arg(&function.inputs);
|
||||||
let doc = util::expand_doc(&format!(
|
let doc = util::expand_doc(&format!(
|
||||||
|
@ -179,7 +179,7 @@ mod tests {
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
{ , a: bool, b: Address },
|
{ , a: bool, b: ethers_core::types::Address },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -214,7 +214,7 @@ mod tests {
|
||||||
},
|
},
|
||||||
],)
|
],)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
{ (bool, Address) },
|
{ (bool, ethers_core::types::Address) },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ use quote::quote;
|
||||||
|
|
||||||
pub(crate) fn expand(kind: &ParamType) -> Result<TokenStream> {
|
pub(crate) fn expand(kind: &ParamType) -> Result<TokenStream> {
|
||||||
match kind {
|
match kind {
|
||||||
ParamType::Address => Ok(quote! { Address }),
|
ParamType::Address => Ok(quote! { ethers_core::types::Address }),
|
||||||
ParamType::Bytes => Ok(quote! { Vec<u8> }),
|
ParamType::Bytes => Ok(quote! { Vec<u8> }),
|
||||||
ParamType::Int(n) => match n / 8 {
|
ParamType::Int(n) => match n / 8 {
|
||||||
1 => Ok(quote! { i8 }),
|
1 => Ok(quote! { i8 }),
|
||||||
|
@ -22,7 +22,7 @@ pub(crate) fn expand(kind: &ParamType) -> Result<TokenStream> {
|
||||||
3..=4 => Ok(quote! { u32 }),
|
3..=4 => Ok(quote! { u32 }),
|
||||||
5..=8 => Ok(quote! { u64 }),
|
5..=8 => Ok(quote! { u64 }),
|
||||||
9..=16 => Ok(quote! { u128 }),
|
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)),
|
_ => Err(anyhow!("unsupported solidity type uint{}", n)),
|
||||||
},
|
},
|
||||||
ParamType::Bool => Ok(quote! { bool }),
|
ParamType::Bool => Ok(quote! { bool }),
|
||||||
|
|
|
@ -8,8 +8,8 @@ use proc_macro2::{Literal, Span};
|
||||||
use quote::{quote, quote_spanned};
|
use quote::{quote, quote_spanned};
|
||||||
use syn::spanned::Spanned as _;
|
use syn::spanned::Spanned as _;
|
||||||
use syn::{
|
use syn::{
|
||||||
parse::Error, parse_macro_input, AttrStyle, Data, DeriveInput, Expr, Fields, GenericArgument,
|
parse::Error, parse_macro_input, AttrStyle, Data, DeriveInput, Expr, Field, Fields,
|
||||||
Lit, Meta, NestedMeta, PathArguments, Type,
|
GenericArgument, Lit, Meta, NestedMeta, PathArguments, Type,
|
||||||
};
|
};
|
||||||
|
|
||||||
use abigen::{expand, ContractArgs};
|
use abigen::{expand, ContractArgs};
|
||||||
|
@ -79,6 +79,8 @@ pub fn abigen(input: TokenStream) -> TokenStream {
|
||||||
///
|
///
|
||||||
/// Additional arguments can be specified using the `#[ethevent(...)]` attribute:
|
/// Additional arguments can be specified using the `#[ethevent(...)]` attribute:
|
||||||
///
|
///
|
||||||
|
/// For the struct:
|
||||||
|
///
|
||||||
/// - `name`, `name = "..."`: Overrides the generated `EthEvent` name, default is the
|
/// - `name`, `name = "..."`: Overrides the generated `EthEvent` name, default is the
|
||||||
/// struct's name.
|
/// struct's name.
|
||||||
/// - `signature`, `signature = "..."`: The signature as hex string to override the
|
/// - `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.
|
/// - `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
|
/// 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
|
/// 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
|
/// # Example
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
|
/// # use ethers_core::types::Address;
|
||||||
|
///
|
||||||
/// #[derive(Debug, EthAbiType)]
|
/// #[derive(Debug, EthAbiType)]
|
||||||
/// struct Inner {
|
/// struct Inner {
|
||||||
/// inner: Address,
|
/// inner: Address,
|
||||||
|
@ -98,8 +108,10 @@ pub fn abigen(input: TokenStream) -> TokenStream {
|
||||||
/// #[derive(Debug, EthEvent)]
|
/// #[derive(Debug, EthEvent)]
|
||||||
/// #[ethevent(abi = "ValueChangedEvent((address,string),string)")]
|
/// #[ethevent(abi = "ValueChangedEvent((address,string),string)")]
|
||||||
/// struct ValueChangedEvent {
|
/// struct ValueChangedEvent {
|
||||||
/// inner: Inner,
|
/// #[ethevent(indexed, name = "_target")]
|
||||||
|
/// target: Address,
|
||||||
/// msg: String,
|
/// msg: String,
|
||||||
|
/// inner: Inner,
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
#[proc_macro_derive(EthEvent, attributes(ethevent))]
|
#[proc_macro_derive(EthEvent, attributes(ethevent))]
|
||||||
|
@ -113,14 +125,13 @@ pub fn derive_abi_event(input: TokenStream) -> TokenStream {
|
||||||
|
|
||||||
let event_name = attributes
|
let event_name = attributes
|
||||||
.name
|
.name
|
||||||
.map(|(n, _)| n)
|
.map(|(s, _)| s)
|
||||||
.unwrap_or_else(|| input.ident.to_string());
|
.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
|
// try to parse as solidity event
|
||||||
if let Ok(mut event) = parse_event(&src) {
|
if let Ok(event) = parse_event(&src) {
|
||||||
event.name = event_name.clone();
|
event
|
||||||
(event.abi_signature(), event.signature())
|
|
||||||
} else {
|
} else {
|
||||||
// try as tuple
|
// try as tuple
|
||||||
if let Some(inputs) = Reader::read(
|
if let Some(inputs) = Reader::read(
|
||||||
|
@ -142,12 +153,11 @@ pub fn derive_abi_event(input: TokenStream) -> TokenStream {
|
||||||
),
|
),
|
||||||
_ => None,
|
_ => None,
|
||||||
}) {
|
}) {
|
||||||
let event = Event {
|
Event {
|
||||||
name: event_name.clone(),
|
name: event_name.clone(),
|
||||||
inputs,
|
inputs,
|
||||||
anonymous: false,
|
anonymous: false,
|
||||||
};
|
}
|
||||||
(event.abi_signature(), event.signature())
|
|
||||||
} else {
|
} else {
|
||||||
match src.parse::<Source>().and_then(|s| s.get()) {
|
match src.parse::<Source>().and_then(|s| s.get()) {
|
||||||
Ok(abi) => {
|
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
|
// this could be mitigated by getting the ABI of each non elementary type at runtime
|
||||||
// and computing the the signature as `static Lazy::...`
|
// and computing the the signature as `static Lazy::...`
|
||||||
match parse_event(&abi) {
|
match parse_event(&abi) {
|
||||||
Ok(mut event) => {
|
Ok(event) => event,
|
||||||
event.name = event_name.clone();
|
|
||||||
(event.abi_signature(), event.signature())
|
|
||||||
}
|
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
return TokenStream::from(Error::new(span, err).to_compile_error())
|
return TokenStream::from(Error::new(span, err).to_compile_error())
|
||||||
}
|
}
|
||||||
|
@ -173,20 +180,31 @@ pub fn derive_abi_event(input: TokenStream) -> TokenStream {
|
||||||
} else {
|
} else {
|
||||||
// try to determine the abi from the fields
|
// try to determine the abi from the fields
|
||||||
match derive_abi_event_from_fields(&input) {
|
match derive_abi_event_from_fields(&input) {
|
||||||
Ok(mut event) => {
|
Ok(event) => event,
|
||||||
event.name = event_name.clone();
|
|
||||||
(event.abi_signature(), event.signature())
|
|
||||||
}
|
|
||||||
Err(err) => return TokenStream::from(err.to_compile_error()),
|
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 {
|
let signature = if let Some((hash, _)) = attributes.signature_hash {
|
||||||
signature(&hash)
|
signature(&hash)
|
||||||
} else {
|
} else {
|
||||||
signature(hash.as_bytes())
|
signature(hash.as_bytes())
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let anon = attributes.anonymous.map(|(b, _)| b).unwrap_or_default();
|
||||||
|
|
||||||
let ethevent_impl = quote! {
|
let ethevent_impl = quote! {
|
||||||
impl ethers_contract::EthEvent for #name {
|
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> {
|
fn abi_signature() -> ::std::borrow::Cow<'static, str> {
|
||||||
#abi.into()
|
#abi.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn decode_log(log: ðers_core::abi::RawLog) -> Result<Self, ethers_core::abi::Error> 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<Event, Error> {
|
struct EventField {
|
||||||
let types: Vec<_> = match input.data {
|
topic_name: Option<String>,
|
||||||
|
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<proc_macro2::TokenStream, Error> {
|
||||||
|
let fields: Vec<_> = match input.data {
|
||||||
Data::Struct(ref data) => match data.fields {
|
Data::Struct(ref data) => match data.fields {
|
||||||
Fields::Named(ref fields) => fields.named.iter().map(|f| &f.ty).collect(),
|
Fields::Named(ref fields) => {
|
||||||
Fields::Unnamed(ref fields) => fields.unnamed.iter().map(|f| &f.ty).collect(),
|
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::<Vec<u8>>();
|
||||||
|
},
|
||||||
|
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::<Vec<u8>>();
|
||||||
|
},
|
||||||
|
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<Event, Error> {
|
||||||
|
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 => {
|
Fields::Unit => {
|
||||||
return Err(Error::new(
|
return Err(Error::new(
|
||||||
input.span(),
|
input.span(),
|
||||||
|
@ -239,17 +513,24 @@ fn derive_abi_event_from_fields(input: &DeriveInput) -> Result<Event, Error> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let inputs = types
|
let inputs = fields
|
||||||
.iter()
|
.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::<Result<Vec<_>, _>>()?;
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
let event = Event {
|
let event = Event {
|
||||||
name: "".to_string(),
|
name: "".to_string(),
|
||||||
inputs: inputs
|
inputs: inputs
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|kind| EventParam {
|
.map(|(name, kind)| EventParam {
|
||||||
name: "".to_string(),
|
name,
|
||||||
kind,
|
kind,
|
||||||
indexed: false,
|
indexed: false,
|
||||||
})
|
})
|
||||||
|
@ -259,6 +540,55 @@ fn derive_abi_event_from_fields(input: &DeriveInput) -> Result<Event, Error> {
|
||||||
Ok(event)
|
Ok(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_field_attributes(field: &Field) -> Result<(Option<String>, 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<ParamType, Error> {
|
fn find_parameter_type(ty: &Type) -> Result<ParamType, Error> {
|
||||||
match ty {
|
match ty {
|
||||||
Type::Array(ty) => {
|
Type::Array(ty) => {
|
||||||
|
@ -315,13 +645,10 @@ fn find_parameter_type(ty: &Type) -> Result<ParamType, Error> {
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
Ok(ParamType::Tuple(params))
|
Ok(ParamType::Tuple(params))
|
||||||
}
|
}
|
||||||
_ => {
|
_ => Err(Error::new(
|
||||||
eprintln!("Found other types");
|
|
||||||
Err(Error::new(
|
|
||||||
ty.span(),
|
ty.span(),
|
||||||
"Failed to derive proper ABI from fields",
|
"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 {
|
struct Attributes {
|
||||||
name: Option<(String, Span)>,
|
name: Option<(String, Span)>,
|
||||||
abi: Option<(String, Span)>,
|
abi: Option<(String, Span)>,
|
||||||
signature_hash: Option<(Vec<u8>, Span)>,
|
signature_hash: Option<(Vec<u8>, Span)>,
|
||||||
}
|
anonymous: Option<(bool, Span)>,
|
||||||
|
|
||||||
impl Default for Attributes {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
name: None,
|
|
||||||
abi: None,
|
|
||||||
signature_hash: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_attributes(input: &DeriveInput) -> Result<Attributes, proc_macro2::TokenStream> {
|
fn parse_attributes(input: &DeriveInput) -> Result<Attributes, proc_macro2::TokenStream> {
|
||||||
|
@ -525,6 +850,20 @@ fn parse_attributes(input: &DeriveInput) -> Result<Attributes, proc_macro2::Toke
|
||||||
if let NestedMeta::Meta(meta) = n {
|
if let NestedMeta::Meta(meta) = n {
|
||||||
match meta {
|
match meta {
|
||||||
Meta::Path(path) => {
|
Meta::Path(path) => {
|
||||||
|
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(
|
return Err(Error::new(
|
||||||
path.span(),
|
path.span(),
|
||||||
"unrecognized ethevent parameter",
|
"unrecognized ethevent parameter",
|
||||||
|
@ -532,7 +871,6 @@ fn parse_attributes(input: &DeriveInput) -> Result<Attributes, proc_macro2::Toke
|
||||||
.to_compile_error());
|
.to_compile_error());
|
||||||
}
|
}
|
||||||
Meta::List(meta) => {
|
Meta::List(meta) => {
|
||||||
// TODO support raw list
|
|
||||||
return Err(Error::new(
|
return Err(Error::new(
|
||||||
meta.path.span(),
|
meta.path.span(),
|
||||||
"unrecognized ethevent parameter",
|
"unrecognized ethevent parameter",
|
||||||
|
@ -540,7 +878,26 @@ fn parse_attributes(input: &DeriveInput) -> Result<Attributes, proc_macro2::Toke
|
||||||
.to_compile_error());
|
.to_compile_error());
|
||||||
}
|
}
|
||||||
Meta::NameValue(meta) => {
|
Meta::NameValue(meta) => {
|
||||||
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 let Lit::Str(ref lit_str) = meta.lit {
|
||||||
if result.name.is_none() {
|
if result.name.is_none() {
|
||||||
result.name =
|
result.name =
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
use super::{
|
use crate::{
|
||||||
base::{encode_function_data, AbiError, BaseContract},
|
base::{encode_function_data, AbiError, BaseContract},
|
||||||
call::ContractCall,
|
call::ContractCall,
|
||||||
event::Event,
|
event::{EthEvent, Event},
|
||||||
|
EthLogDecode,
|
||||||
};
|
};
|
||||||
|
|
||||||
use ethers_core::{
|
use ethers_core::{
|
||||||
|
@ -108,7 +109,7 @@ use std::{fmt::Debug, marker::PhantomData, sync::Arc};
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
|
/// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// use ethers_core::{abi::Abi, types::Address};
|
/// use ethers_core::{abi::Abi, types::Address};
|
||||||
/// use ethers_contract::Contract;
|
/// use ethers_contract::{Contract, EthEvent};
|
||||||
/// use ethers_providers::{Provider, Http, Middleware};
|
/// use ethers_providers::{Provider, Http, Middleware};
|
||||||
/// use ethers_signers::Wallet;
|
/// use ethers_signers::Wallet;
|
||||||
/// use std::convert::TryFrom;
|
/// use std::convert::TryFrom;
|
||||||
|
@ -119,7 +120,7 @@ use std::{fmt::Debug, marker::PhantomData, sync::Arc};
|
||||||
/// # let client = Provider::<Http>::try_from("http://localhost:8545").unwrap();
|
/// # let client = Provider::<Http>::try_from("http://localhost:8545").unwrap();
|
||||||
/// # let contract = Contract::new(address, abi, client);
|
/// # let contract = Contract::new(address, abi, client);
|
||||||
///
|
///
|
||||||
/// #[derive(Clone, Debug)]
|
/// #[derive(Clone, Debug, EthEvent)]
|
||||||
/// struct ValueChanged {
|
/// struct ValueChanged {
|
||||||
/// old_author: Address,
|
/// old_author: Address,
|
||||||
/// new_author: Address,
|
/// new_author: Address,
|
||||||
|
@ -127,25 +128,8 @@ use std::{fmt::Debug, marker::PhantomData, sync::Arc};
|
||||||
/// new_value: String,
|
/// new_value: String,
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// impl Detokenize for ValueChanged {
|
|
||||||
/// fn from_tokens(tokens: Vec<Token>) -> Result<ValueChanged, InvalidOutputType> {
|
|
||||||
/// 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<ValueChanged> = contract
|
/// let logs: Vec<ValueChanged> = contract
|
||||||
/// .event("ValueChanged")?
|
/// .event()
|
||||||
/// .from_block(0u64)
|
/// .from_block(0u64)
|
||||||
/// .query()
|
/// .query()
|
||||||
/// .await?;
|
/// .await?;
|
||||||
|
@ -179,18 +163,25 @@ impl<M: Middleware> Contract<M> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an [`Event`](crate::builders::Event) builder for the provided event name.
|
/// Returns an [`Event`](crate::builders::Event) builder for the provided event.
|
||||||
pub fn event<D: Detokenize>(&self, name: &str) -> Result<Event<M, D>, Error> {
|
pub fn event<D: EthEvent>(&self) -> Event<M, D> {
|
||||||
|
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<D: EthLogDecode>(&self, filter: Filter) -> Event<M, D> {
|
||||||
|
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<D: EthLogDecode>(&self, name: &str) -> Result<Event<M, D>, Error> {
|
||||||
// get the event's full name
|
// get the event's full name
|
||||||
let event = self.base_contract.abi.event(name)?;
|
let event = self.base_contract.abi.event(name)?;
|
||||||
Ok(Event {
|
Ok(self.event_with_filter(Filter::new().event(&event.abi_signature())))
|
||||||
provider: &self.client,
|
|
||||||
filter: Filter::new()
|
|
||||||
.event(&event.abi_signature())
|
|
||||||
.address(self.address),
|
|
||||||
event: &event,
|
|
||||||
datatype: PhantomData,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a transaction builder for the provided function name. If there are
|
/// Returns a transaction builder for the provided function name. If there are
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::{base::decode_event, stream::EventStream, ContractError};
|
use crate::{stream::EventStream, ContractError, EthLogDecode};
|
||||||
|
|
||||||
use ethers_core::{
|
use ethers_core::{
|
||||||
abi::{Detokenize, Event as AbiEvent},
|
abi::{Detokenize, RawLog},
|
||||||
types::{BlockNumber, Filter, Log, TxHash, ValueOrArray, H256, U64},
|
types::{BlockNumber, Filter, Log, TxHash, ValueOrArray, H256, U64},
|
||||||
};
|
};
|
||||||
use ethers_providers::{FilterWatcher, Middleware, PubsubClient, SubscriptionStream};
|
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
|
/// Retrieves the ABI signature for the event this data corresponds
|
||||||
/// to.
|
/// to.
|
||||||
fn abi_signature() -> Cow<'static, str>;
|
fn abi_signature() -> Cow<'static, str>;
|
||||||
|
|
||||||
|
/// Decodes an Ethereum `RawLog` into an instance of the type.
|
||||||
|
fn decode_log(log: &RawLog) -> Result<Self, ethers_core::abi::Error>
|
||||||
|
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<M: Middleware>(filter: Filter, provider: &M) -> Event<M, Self>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
let filter = filter.event(&Self::abi_signature());
|
||||||
|
Event {
|
||||||
|
filter,
|
||||||
|
provider,
|
||||||
|
datatype: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convenience implementation
|
||||||
|
impl<T: EthEvent> EthLogDecode for T {
|
||||||
|
fn decode_log(log: &RawLog) -> Result<Self, ethers_core::abi::Error>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
T::decode_log(log)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper for managing the event filter before querying or streaming its logs
|
/// Helper for managing the event filter before querying or streaming its logs
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[must_use = "event filters do nothing unless you `query` or `stream` them"]
|
#[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
|
/// The event filter's state
|
||||||
pub filter: Filter,
|
pub filter: Filter,
|
||||||
/// The ABI of the event which is being filtered
|
|
||||||
pub event: &'b AbiEvent,
|
|
||||||
pub(crate) provider: &'a M,
|
pub(crate) provider: &'a M,
|
||||||
|
/// Stores the event datatype
|
||||||
pub(crate) datatype: PhantomData<D>,
|
pub(crate) datatype: PhantomData<D>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Improve these functions
|
// TODO: Improve these functions
|
||||||
impl<M, D: Detokenize> Event<'_, '_, M, D> {
|
impl<M, D: EthLogDecode> Event<'_, M, D> {
|
||||||
/// Sets the filter's `from` block
|
/// Sets the filter's `from` block
|
||||||
#[allow(clippy::wrong_self_convention)]
|
#[allow(clippy::wrong_self_convention)]
|
||||||
pub fn from_block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
|
pub fn from_block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
|
||||||
|
@ -84,10 +114,10 @@ impl<M, D: Detokenize> Event<'_, '_, M, D> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b, M, D> Event<'a, 'b, M, D>
|
impl<'a, M, D> Event<'a, M, D>
|
||||||
where
|
where
|
||||||
M: Middleware,
|
M: Middleware,
|
||||||
D: 'b + Detokenize + Clone,
|
D: EthLogDecode,
|
||||||
{
|
{
|
||||||
/// Returns a stream for the event
|
/// Returns a stream for the event
|
||||||
pub async fn stream(
|
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
|
where
|
||||||
M: Middleware,
|
M: Middleware,
|
||||||
<M as Middleware>::Provider: PubsubClient,
|
<M as Middleware>::Provider: PubsubClient,
|
||||||
D: 'b + Detokenize + Clone,
|
D: EthLogDecode,
|
||||||
{
|
{
|
||||||
/// Returns a subscription for the event
|
/// Returns a subscription for the event
|
||||||
pub async fn subscribe(
|
pub async fn subscribe(
|
||||||
|
@ -137,10 +167,10 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<M, D> Event<'_, '_, M, D>
|
impl<M, D> Event<'_, M, D>
|
||||||
where
|
where
|
||||||
M: Middleware,
|
M: Middleware,
|
||||||
D: Detokenize + Clone,
|
D: EthLogDecode,
|
||||||
{
|
{
|
||||||
/// Queries the blockchain for the selected filter and returns a vector of matching
|
/// Queries the blockchain for the selected filter and returns a vector of matching
|
||||||
/// event logs
|
/// event logs
|
||||||
|
@ -177,7 +207,11 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_log(&self, log: Log) -> Result<D, ContractError<M>> {
|
fn parse_log(&self, log: Log) -> Result<D, ContractError<M>> {
|
||||||
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,9 @@ pub use factory::ContractFactory;
|
||||||
mod event;
|
mod event;
|
||||||
pub use event::EthEvent;
|
pub use event::EthEvent;
|
||||||
|
|
||||||
|
mod log;
|
||||||
|
pub use log::{decode_logs, EthLogDecode};
|
||||||
|
|
||||||
mod stream;
|
mod stream;
|
||||||
|
|
||||||
mod multicall;
|
mod multicall;
|
||||||
|
|
|
@ -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<Self, Error>
|
||||||
|
where
|
||||||
|
Self: Sized;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decodes a series of logs into a vector
|
||||||
|
pub fn decode_logs<T: EthLogDecode>(logs: &[RawLog]) -> Result<Vec<T>, Error> {
|
||||||
|
logs.iter().map(T::decode_log).collect()
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
use ethers::core::types::{H160, H256, I256, U128, U256};
|
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::abi::Tokenizable;
|
||||||
use ethers_core::types::Address;
|
use ethers_core::types::Address;
|
||||||
|
|
||||||
|
@ -195,3 +195,44 @@ fn can_set_eth_abi_attribute() {
|
||||||
ValueChangedEvent2::abi_signature()
|
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()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -4,18 +4,20 @@ use ethers_core::{
|
||||||
types::{Address, Bytes},
|
types::{Address, Bytes},
|
||||||
};
|
};
|
||||||
|
|
||||||
use ethers_contract::{Contract, ContractFactory, EthAbiType};
|
use ethers_contract::{Contract, ContractFactory, EthEvent};
|
||||||
use ethers_core::utils::{GanacheInstance, Solc};
|
use ethers_core::utils::{GanacheInstance, Solc};
|
||||||
use ethers_middleware::signer::SignerMiddleware;
|
use ethers_middleware::signer::SignerMiddleware;
|
||||||
use ethers_providers::{Http, Middleware, Provider};
|
use ethers_providers::{Http, Middleware, Provider};
|
||||||
use ethers_signers::LocalWallet;
|
use ethers_signers::LocalWallet;
|
||||||
use std::{convert::TryFrom, sync::Arc, time::Duration};
|
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
|
// the struct
|
||||||
#[derive(Clone, Debug, EthAbiType)]
|
#[derive(Clone, Debug, EthEvent)]
|
||||||
pub struct ValueChanged {
|
pub struct ValueChanged {
|
||||||
|
#[ethevent(indexed)]
|
||||||
pub old_author: Address,
|
pub old_author: Address,
|
||||||
|
#[ethevent(indexed)]
|
||||||
pub new_author: Address,
|
pub new_author: Address,
|
||||||
pub old_value: String,
|
pub old_value: String,
|
||||||
pub new_value: String,
|
pub new_value: String,
|
||||||
|
|
|
@ -109,8 +109,7 @@ mod eth_tests {
|
||||||
|
|
||||||
// and we can fetch the events
|
// and we can fetch the events
|
||||||
let logs: Vec<ValueChanged> = contract
|
let logs: Vec<ValueChanged> = contract
|
||||||
.event("ValueChanged")
|
.event()
|
||||||
.unwrap()
|
|
||||||
.from_block(0u64)
|
.from_block(0u64)
|
||||||
.topic1(client.address()) // Corresponds to the first indexed parameter
|
.topic1(client.address()) // Corresponds to the first indexed parameter
|
||||||
.query()
|
.query()
|
||||||
|
@ -123,8 +122,7 @@ mod eth_tests {
|
||||||
// and we can fetch the events at a block hash
|
// and we can fetch the events at a block hash
|
||||||
let hash = client.get_block(1).await.unwrap().unwrap().hash.unwrap();
|
let hash = client.get_block(1).await.unwrap().unwrap().hash.unwrap();
|
||||||
let logs: Vec<ValueChanged> = contract
|
let logs: Vec<ValueChanged> = contract
|
||||||
.event("ValueChanged")
|
.event()
|
||||||
.unwrap()
|
|
||||||
.at_block_hash(hash)
|
.at_block_hash(hash)
|
||||||
.topic1(client.address()) // Corresponds to the first indexed parameter
|
.topic1(client.address()) // Corresponds to the first indexed parameter
|
||||||
.query()
|
.query()
|
||||||
|
@ -256,14 +254,14 @@ mod eth_tests {
|
||||||
let contract = deploy(client, abi.clone(), bytecode).await;
|
let contract = deploy(client, abi.clone(), bytecode).await;
|
||||||
|
|
||||||
// We spawn the event listener:
|
// We spawn the event listener:
|
||||||
let event = contract.event::<ValueChanged>("ValueChanged").unwrap();
|
let event = contract.event::<ValueChanged>();
|
||||||
let mut stream = event.stream().await.unwrap();
|
let mut stream = event.stream().await.unwrap();
|
||||||
assert_eq!(stream.id, 1.into());
|
assert_eq!(stream.id, 1.into());
|
||||||
|
|
||||||
// Also set up a subscription for the same thing
|
// Also set up a subscription for the same thing
|
||||||
let ws = Provider::connect(ganache.ws_endpoint()).await.unwrap();
|
let ws = Provider::connect(ganache.ws_endpoint()).await.unwrap();
|
||||||
let contract2 = ethers_contract::Contract::new(contract.address(), abi, ws);
|
let contract2 = ethers_contract::Contract::new(contract.address(), abi, ws);
|
||||||
let event2 = contract2.event::<ValueChanged>("ValueChanged").unwrap();
|
let event2 = contract2.event::<ValueChanged>();
|
||||||
let mut subscription = event2.subscribe().await.unwrap();
|
let mut subscription = event2.subscribe().await.unwrap();
|
||||||
assert_eq!(subscription.id, 2.into());
|
assert_eq!(subscription.id, 2.into());
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
}
|
||||||
|
]
|
|
@ -15,6 +15,18 @@ pub static ADDRESS_BOOK: Lazy<HashMap<U256, Address>> = Lazy::new(|| {
|
||||||
m
|
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
|
// Auto-generated type-safe bindings
|
||||||
pub use dsproxyfactory_mod::*;
|
pub use dsproxyfactory_mod::*;
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
@ -23,7 +35,7 @@ mod dsproxyfactory_mod {
|
||||||
#![allow(unused_imports)]
|
#![allow(unused_imports)]
|
||||||
use ethers_contract::{
|
use ethers_contract::{
|
||||||
builders::{ContractCall, Event},
|
builders::{ContractCall, Event},
|
||||||
Contract, Lazy,
|
Contract, EthEvent, Lazy,
|
||||||
};
|
};
|
||||||
use ethers_core::{
|
use ethers_core::{
|
||||||
abi::{parse_abi, Abi, Detokenize, InvalidOutputType, Token, Tokenizable},
|
abi::{parse_abi, Abi, Detokenize, InvalidOutputType, Token, Tokenizable},
|
||||||
|
@ -64,8 +76,19 @@ mod dsproxyfactory_mod {
|
||||||
.method_hash([41, 113, 3, 136], p0)
|
.method_hash([41, 113, 3, 136], p0)
|
||||||
.expect("method not found (this should never happen)")
|
.expect("method not found (this should never happen)")
|
||||||
}
|
}
|
||||||
#[doc = "Calls the contract's `build` (0xf3701da2) function"]
|
///Calls the contract's `build` (0x8e1a55fc) function
|
||||||
pub fn build(&self, owner: Address) -> ContractCall<M, Address> {
|
pub fn build_with_sender(
|
||||||
|
&self,
|
||||||
|
) -> ethers_contract::builders::ContractCall<M, ethers_core::types::Address> {
|
||||||
|
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<M, ethers_core::types::Address> {
|
||||||
self.0
|
self.0
|
||||||
.method_hash([243, 112, 29, 162], owner)
|
.method_hash([243, 112, 29, 162], owner)
|
||||||
.expect("method not found (this should never happen)")
|
.expect("method not found (this should never happen)")
|
||||||
|
@ -76,60 +99,24 @@ mod dsproxyfactory_mod {
|
||||||
.method_hash([96, 199, 210, 149], ())
|
.method_hash([96, 199, 210, 149], ())
|
||||||
.expect("method not found (this should never happen)")
|
.expect("method not found (this should never happen)")
|
||||||
}
|
}
|
||||||
#[doc = "Gets the contract's `Created` event"]
|
///Gets the contract's `Created` event
|
||||||
pub fn created_filter(&self) -> Event<M, CreatedFilter> {
|
pub fn created_filter(&self) -> ethers_contract::builders::Event<M, CreatedFilter> {
|
||||||
self.0
|
self.0.event()
|
||||||
.event("Created")
|
}
|
||||||
.expect("event not found (this should never happen)")
|
|
||||||
|
/// Returns an [`Event`](ethers_contract::builders::Event) builder for all events of this contract
|
||||||
|
pub fn events(&self) -> ethers_contract::builders::Event<M, CreatedFilter> {
|
||||||
|
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 {
|
pub struct CreatedFilter {
|
||||||
|
#[ethevent(indexed)]
|
||||||
pub sender: Address,
|
pub sender: Address,
|
||||||
|
#[ethevent(indexed)]
|
||||||
pub owner: Address,
|
pub owner: Address,
|
||||||
pub proxy: Address,
|
pub proxy: Address,
|
||||||
pub cache: 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<Token>) -> Result<Self, InvalidOutputType> {
|
|
||||||
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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue