feat: function call enums EthCall macro and more (#517)

* fix: do not sort event variants

* style: use deref over clone

* style: refactor some stuff

* feat: add ethcall trait

* feat: include in abigen

* feat: add bare bones eth call derive

* feat: impl EthCall derive

* feat: support enums

* feat: use abigen enum derive

* fix: concrete abi and map errors

* test: first call test

* rustfmt

* chore: use correct trait name on error

* feat: derive display for call structs

* feat: add from conversion

* test: add convert test

* chore: docs and test

* chore: update changelog

* cargo fix

* feat: add unit type derive support and more test

* chore: patch ethabi

* chore: rm ethabi patch

* feat: add encode/decode trait impls

* style: use AsRef<[u8]>

* Update ethers-contract/ethers-contract-abigen/src/contract/methods.rs

Co-authored-by: Georgios Konstantopoulos <me@gakonst.com>

* style: reindent macro body

* test: add tuple event test

Co-authored-by: Georgios Konstantopoulos <me@gakonst.com>
This commit is contained in:
Matthias Seitz 2021-10-18 12:28:38 +02:00 committed by GitHub
parent 071a41605b
commit fb4d9a9ef1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 950 additions and 134 deletions

View File

@ -4,6 +4,7 @@
### Unreleased ### Unreleased
- add `EthCall` trait and derive macro which generates matching structs for contract calls [#517](https://github.com/gakonst/ethers-rs/pull/517)
- `abigen!` now generates `Display` for all events using the new `EthDisplay` macro [#513](https://github.com/gakonst/ethers-rs/pull/513) - `abigen!` now generates `Display` for all events using the new `EthDisplay` macro [#513](https://github.com/gakonst/ethers-rs/pull/513)
- `abigen!` now supports overloaded functions natively [#501](https://github.com/gakonst/ethers-rs/pull/501) - `abigen!` now supports overloaded functions natively [#501](https://github.com/gakonst/ethers-rs/pull/501)
- `abigen!` now supports multiple contracts [#498](https://github.com/gakonst/ethers-rs/pull/498) - `abigen!` now supports multiple contracts [#498](https://github.com/gakonst/ethers-rs/pull/498)

View File

@ -29,6 +29,8 @@ pub struct ExpandedContract {
pub contract: TokenStream, pub contract: TokenStream,
/// All event impls of the contract /// All event impls of the contract
pub events: TokenStream, pub events: TokenStream,
/// All contract call struct related types
pub call_structs: TokenStream,
/// The contract's internal structs /// The contract's internal structs
pub abi_structs: TokenStream, pub abi_structs: TokenStream,
} }
@ -41,6 +43,7 @@ impl ExpandedContract {
imports, imports,
contract, contract,
events, events,
call_structs,
abi_structs, abi_structs,
} = self; } = self;
quote! { quote! {
@ -52,6 +55,7 @@ impl ExpandedContract {
#imports #imports
#contract #contract
#events #events
#call_structs
#abi_structs #abi_structs
} }
} }
@ -111,8 +115,8 @@ impl Context {
// 3. impl block for the event functions // 3. impl block for the event functions
let contract_events = self.event_methods()?; let contract_events = self.event_methods()?;
// 4. impl block for the contract methods // 4. impl block for the contract methods and their corresponding types
let contract_methods = self.methods()?; let (contract_methods, call_structs) = self.methods_and_call_structs()?;
// 5. Declare the structs parsed from the human readable abi // 5. Declare the structs parsed from the human readable abi
let abi_structs_decl = self.abi_structs()?; let abi_structs_decl = self.abi_structs()?;
@ -146,6 +150,7 @@ impl Context {
imports, imports,
contract, contract,
events: events_decl, events: events_decl,
call_structs,
abi_structs: abi_structs_decl, abi_structs: abi_structs_decl,
}) })
} }

View File

@ -5,7 +5,6 @@ use inflector::Inflector;
use proc_macro2::{Ident, 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;
impl Context { impl Context {
/// Expands each event to a struct + its impl Detokenize block /// Expands each event to a struct + its impl Detokenize block
@ -33,9 +32,10 @@ impl Context {
/// 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.iter().collect();
let filter_methods = sorted_events let filter_methods = sorted_events
.values() .values()
.map(std::ops::Deref::deref)
.flatten() .flatten()
.map(|event| self.expand_filter(event)) .map(|event| self.expand_filter(event))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
@ -51,9 +51,9 @@ impl Context {
/// Generate an enum with a variant for each event /// Generate an enum with a variant for each event
fn expand_events_enum(&self) -> TokenStream { fn expand_events_enum(&self) -> TokenStream {
let sorted_events: BTreeMap<_, _> = self.abi.events.clone().into_iter().collect(); let variants = self
.abi
let variants = sorted_events .events
.values() .values()
.flatten() .flatten()
.map(|e| expand_struct_name(e, self.event_aliases.get(&e.abi_signature()).cloned())) .map(|e| expand_struct_name(e, self.event_aliases.get(&e.abi_signature()).cloned()))
@ -65,33 +65,11 @@ impl Context {
let ethers_contract = util::ethers_contract_crate(); let ethers_contract = util::ethers_contract_crate();
quote! { quote! {
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq, #ethers_contract::EthAbiType)]
pub enum #enum_name { pub enum #enum_name {
#(#variants(#variants)),* #(#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 { impl #ethers_contract::EthLogDecode for #enum_name {
fn decode_log(log: &#ethers_core::abi::RawLog) -> Result<Self, #ethers_core::abi::Error> fn decode_log(log: &#ethers_core::abi::RawLog) -> Result<Self, #ethers_core::abi::Error>
where where
@ -153,8 +131,8 @@ impl Context {
/// 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 from expanding a Solidity type as
/// complex types like arrays and strings get emited as hashes when they are /// complex types like arrays and strings get emitted as hashes when they are
/// indexed. /// indexed.
/// If a complex types matches with a struct previously parsed by the AbiParser, /// If a complex types matches with a struct previously parsed by the AbiParser,
/// we can replace it /// we can replace it
@ -213,8 +191,8 @@ impl Context {
}) })
} }
/// Expands an ABI event into name-type pairs for each of its parameters. /// Expands the name-type pairs for the given inputs
fn expand_params(&self, event: &Event) -> Result<Vec<(TokenStream, TokenStream, bool)>> { fn expand_event_params(&self, event: &Event) -> Result<Vec<(TokenStream, TokenStream, bool)>> {
event event
.inputs .inputs
.iter() .iter()
@ -264,7 +242,7 @@ impl Context {
let event_name = expand_struct_name(event, sig); let event_name = expand_struct_name(event, sig);
let params = self.expand_params(event)?; let params = self.expand_event_params(event)?;
// expand as a tuple if all fields are anonymous // expand as a tuple if all fields are anonymous
let all_anonymous_fields = event.inputs.iter().all(|input| input.name.is_empty()); let all_anonymous_fields = event.inputs.iter().all(|input| input.name.is_empty());
let data_type_definition = if all_anonymous_fields { let data_type_definition = if all_anonymous_fields {
@ -273,7 +251,7 @@ impl Context {
expand_data_struct(&event_name, &params) expand_data_struct(&event_name, &params)
}; };
let derives = expand_derives(&self.event_derives); let derives = util::expand_derives(&self.event_derives);
let ethers_contract = util::ethers_contract_crate(); let ethers_contract = util::ethers_contract_crate();
@ -323,16 +301,20 @@ fn expand_data_struct(name: &Ident, params: &[(TokenStream, TokenStream, bool)])
fn expand_data_tuple(name: &Ident, params: &[(TokenStream, TokenStream, bool)]) -> TokenStream { fn expand_data_tuple(name: &Ident, params: &[(TokenStream, TokenStream, bool)]) -> TokenStream {
let fields = params let fields = params
.iter() .iter()
.map(|(_, ty, _)| quote! { pub #ty }) .map(|(_, ty, indexed)| {
if *indexed {
quote! {
#[ethevent(indexed)] pub #ty }
} else {
quote! {
pub #ty }
}
})
.collect::<Vec<_>>(); .collect::<Vec<_>>();
quote! { struct #name( #( #fields ),* ); } quote! { struct #name( #( #fields ),* ); }
} }
fn expand_derives(derives: &[Path]) -> TokenStream {
quote! {#(#derives),*}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -450,7 +432,7 @@ mod tests {
}; };
let cx = test_context(); let cx = test_context();
let params = cx.expand_params(&event).unwrap(); let params = cx.expand_event_params(&event).unwrap();
let name = expand_struct_name(&event, None); let name = expand_struct_name(&event, None);
let definition = expand_data_struct(&name, &params); let definition = expand_data_struct(&name, &params);
@ -482,7 +464,7 @@ mod tests {
}; };
let cx = test_context_with_alias("Foo(bool,address)", "FooAliased"); let cx = test_context_with_alias("Foo(bool,address)", "FooAliased");
let params = cx.expand_params(&event).unwrap(); let params = cx.expand_event_params(&event).unwrap();
let alias = Some(util::ident("FooAliased")); let alias = Some(util::ident("FooAliased"));
let name = expand_struct_name(&event, alias); let name = expand_struct_name(&event, alias);
let definition = expand_data_struct(&name, &params); let definition = expand_data_struct(&name, &params);
@ -515,7 +497,7 @@ mod tests {
}; };
let cx = test_context(); let cx = test_context();
let params = cx.expand_params(&event).unwrap(); let params = cx.expand_event_params(&event).unwrap();
let name = expand_struct_name(&event, None); let name = expand_struct_name(&event, None);
let definition = expand_data_tuple(&name, &params); let definition = expand_data_tuple(&name, &params);
@ -544,7 +526,7 @@ mod tests {
}; };
let cx = test_context_with_alias("Foo(bool,address)", "FooAliased"); let cx = test_context_with_alias("Foo(bool,address)", "FooAliased");
let params = cx.expand_params(&event).unwrap(); let params = cx.expand_event_params(&event).unwrap();
let alias = Some(util::ident("FooAliased")); let alias = Some(util::ident("FooAliased"));
let name = expand_struct_name(&event, alias); let name = expand_struct_name(&event, alias);
let definition = expand_data_tuple(&name, &params); let definition = expand_data_tuple(&name, &params);

View File

@ -18,32 +18,162 @@ use super::{types, util, Context};
/// to the Solidity contract methods. /// to the Solidity contract methods.
impl Context { impl Context {
/// Expands all method implementations /// Expands all method implementations
pub(crate) fn methods(&self) -> Result<TokenStream> { pub(crate) fn methods_and_call_structs(&self) -> Result<(TokenStream, TokenStream)> {
let mut aliases = self.get_method_aliases()?; let aliases = self.get_method_aliases()?;
let sorted_functions: BTreeMap<_, _> = self.abi.functions.clone().into_iter().collect(); let sorted_functions: BTreeMap<_, _> = self.abi.functions.iter().collect();
let functions = sorted_functions let functions = sorted_functions
.values() .values()
.map(std::ops::Deref::deref)
.flatten() .flatten()
.map(|function| { .map(|function| {
let signature = function.abi_signature(); let signature = function.abi_signature();
self.expand_function(function, aliases.remove(&signature)) self.expand_function(function, aliases.get(&signature).cloned())
.with_context(|| format!("error expanding function '{}'", signature)) .with_context(|| format!("error expanding function '{}'", signature))
}) })
.collect::<Result<Vec<_>>>()?; .collect::<Result<Vec<_>>>()?;
Ok(quote! { #( #functions )* }) let function_impls = quote! { #( #functions )* };
let call_structs = self.expand_call_structs(aliases)?;
Ok((function_impls, call_structs))
} }
fn expand_inputs_call_arg_with_structs( /// Expands to the corresponding struct type based on the inputs of the given function
fn expand_call_struct(
&self, &self,
fun: &Function, function: &Function,
) -> Result<(TokenStream, TokenStream)> { alias: Option<&Ident>,
) -> Result<TokenStream> {
let call_name = expand_call_struct_name(function, alias);
let fields = self.expand_input_pairs(function)?;
// expand as a tuple if all fields are anonymous
let all_anonymous_fields = function.inputs.iter().all(|input| input.name.is_empty());
let call_type_definition = if all_anonymous_fields {
// expand to a tuple struct
expand_data_tuple(&call_name, &fields)
} else {
// expand to a struct
expand_data_struct(&call_name, &fields)
};
let function_name = &function.name;
let abi_signature = function.abi_signature();
let doc = format!(
"Container type for all input parameters for the `{}`function with signature `{}` and selector `{:?}`",
function.name,
abi_signature,
function.selector()
);
let abi_signature_doc = util::expand_doc(&doc);
let ethers_contract = util::ethers_contract_crate();
// use the same derives as for events
let derives = util::expand_derives(&self.event_derives);
Ok(quote! {
#abi_signature_doc
#[derive(Clone, Debug, Default, Eq, PartialEq, #ethers_contract::EthCall, #ethers_contract::EthDisplay, #derives)]
#[ethcall( name = #function_name, abi = #abi_signature )]
pub #call_type_definition
})
}
/// Expands all structs
fn expand_call_structs(&self, aliases: BTreeMap<String, Ident>) -> Result<TokenStream> {
let mut struct_defs = Vec::new();
let mut struct_names = Vec::new();
let mut variant_names = Vec::new();
for function in self.abi.functions.values().flatten() {
let signature = function.abi_signature();
let alias = aliases.get(&signature);
struct_defs.push(self.expand_call_struct(function, alias)?);
struct_names.push(expand_call_struct_name(function, alias));
variant_names.push(expand_call_struct_variant_name(function, alias));
}
let struct_def_tokens = quote! {
#(#struct_defs)*
};
if struct_defs.len() <= 1 {
// no need for an enum
return Ok(struct_def_tokens);
}
let ethers_core = util::ethers_core_crate();
let ethers_contract = util::ethers_contract_crate();
let enum_name = self.expand_calls_enum_name();
Ok(quote! {
#struct_def_tokens
#[derive(Debug, Clone, PartialEq, Eq, #ethers_contract::EthAbiType)]
pub enum #enum_name {
#(#variant_names(#struct_names)),*
}
impl #ethers_contract::AbiDecode for #enum_name {
fn decode(data: impl AsRef<[u8]>) -> Result<Self, #ethers_contract::AbiError> {
#(
if let Ok(decoded) = <#struct_names as #ethers_contract::AbiDecode>::decode(data.as_ref()) {
return Ok(#enum_name::#variant_names(decoded))
}
)*
Err(#ethers_core::abi::Error::InvalidData.into())
}
}
impl #ethers_contract::AbiEncode for #enum_name {
fn encode(self) -> Result<#ethers_core::types::Bytes, #ethers_contract::AbiError> {
match self {
#(
#enum_name::#variant_names(element) => element.encode()
),*
}
}
}
impl ::std::fmt::Display for #enum_name {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
match self {
#(
#enum_name::#variant_names(element) => element.fmt(f)
),*
}
}
}
#(
impl ::std::convert::From<#struct_names> for #enum_name {
fn from(var: #struct_names) -> Self {
#enum_name::#variant_names(var)
}
}
)*
})
}
/// The name ident of the calls enum
fn expand_calls_enum_name(&self) -> Ident {
util::ident(&format!("{}Calls", self.contract_name.to_string()))
}
/// Expands to the `name : type` pairs of the function's inputs
fn expand_input_pairs(&self, fun: &Function) -> Result<Vec<(TokenStream, TokenStream)>> {
let mut args = Vec::with_capacity(fun.inputs.len()); let mut args = Vec::with_capacity(fun.inputs.len());
let mut call_args = Vec::with_capacity(fun.inputs.len()); for (idx, param) in fun.inputs.iter().enumerate() {
for (i, param) in fun.inputs.iter().enumerate() { let name = util::expand_input_name(idx, &param.name);
let name = util::expand_input_name(i, &param.name);
let ty = self.expand_input_param(fun, &param.name, &param.kind)?; let ty = self.expand_input_param(fun, &param.name, &param.kind)?;
args.push(quote! { #name: #ty }); args.push((name, ty));
}
Ok(args)
}
/// Expands the arguments for the call that eventually calls the contract
fn expand_contract_call_args(&self, fun: &Function) -> Result<TokenStream> {
let mut call_args = Vec::with_capacity(fun.inputs.len());
for (idx, param) in fun.inputs.iter().enumerate() {
let name = util::expand_input_name(idx, &param.name);
let call_arg = match param.kind { let call_arg = match param.kind {
// this is awkward edge case where the function inputs are a single struct // this is awkward edge case where the function inputs are a single struct
// we need to force this argument into a tuple so it gets expanded to `((#name,))` // we need to force this argument into a tuple so it gets expanded to `((#name,))`
@ -59,14 +189,13 @@ impl Context {
}; };
call_args.push(call_arg); call_args.push(call_arg);
} }
let args = quote! { #( , #args )* };
let call_args = match call_args.len() { let call_args = match call_args.len() {
0 => quote! { () }, 0 => quote! { () },
1 => quote! { #( #call_args )* }, 1 => quote! { #( #call_args )* },
_ => quote! { ( #(#call_args, )* ) }, _ => quote! { ( #(#call_args, )* ) },
}; };
Ok((args, call_args)) Ok(call_args)
} }
fn expand_input_param( fn expand_input_param(
@ -111,13 +240,16 @@ impl Context {
// TODO use structs // TODO use structs
let outputs = expand_fn_outputs(&function.outputs)?; let outputs = expand_fn_outputs(&function.outputs)?;
let _ethers_core = util::ethers_core_crate();
let _ethers_providers = util::ethers_providers_crate();
let ethers_contract = util::ethers_contract_crate(); let ethers_contract = util::ethers_contract_crate();
let result = quote! { #ethers_contract::builders::ContractCall<M, #outputs> }; let result = quote! { #ethers_contract::builders::ContractCall<M, #outputs> };
let (input, arg) = self.expand_inputs_call_arg_with_structs(function)?; let contract_args = self.expand_contract_call_args(function)?;
let function_params = self
.expand_input_pairs(function)?
.into_iter()
.map(|(name, ty)| quote! { #name: #ty });
let function_params = quote! { #( , #function_params )* };
let doc = util::expand_doc(&format!( let doc = util::expand_doc(&format!(
"Calls the contract's `{}` (0x{}) function", "Calls the contract's `{}` (0x{}) function",
@ -127,8 +259,8 @@ impl Context {
Ok(quote! { Ok(quote! {
#doc #doc
pub fn #name(&self #input) -> #result { pub fn #name(&self #function_params) -> #result {
self.0.method_hash(#selector, #arg) self.0.method_hash(#selector, #contract_args)
.expect("method not found (this should never happen)") .expect("method not found (this should never happen)")
} }
}) })
@ -227,6 +359,55 @@ fn expand_selector(selector: Selector) -> TokenStream {
quote! { [#( #bytes ),*] } quote! { [#( #bytes ),*] }
} }
/// Expands to the name of the call struct
fn expand_call_struct_name(function: &Function, alias: Option<&Ident>) -> Ident {
let name = if let Some(id) = alias {
format!("{}Call", id.to_string().to_pascal_case())
} else {
format!("{}Call", function.name.to_pascal_case())
};
util::ident(&name)
}
/// Expands to the name of the call struct
fn expand_call_struct_variant_name(function: &Function, alias: Option<&Ident>) -> Ident {
let name = if let Some(id) = alias {
id.to_string().to_pascal_case()
} else {
function.name.to_pascal_case()
};
util::ident(&name)
}
/// Expands to the tuple struct definition
fn expand_data_tuple(name: &Ident, params: &[(TokenStream, TokenStream)]) -> TokenStream {
let fields = params
.iter()
.map(|(_, ty)| {
quote! {
pub #ty }
})
.collect::<Vec<_>>();
if fields.is_empty() {
quote! { struct #name; }
} else {
quote! { struct #name( #( #fields ),* ); }
}
}
/// Expands to the struct definition of a call struct
fn expand_data_struct(name: &Ident, params: &[(TokenStream, TokenStream)]) -> TokenStream {
let fields = params
.iter()
.map(|(name, ty)| {
quote! { pub #name: #ty }
})
.collect::<Vec<_>>();
quote! { struct #name { #( #fields, )* } }
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use ethers_core::abi::ParamType; use ethers_core::abi::ParamType;

View File

@ -127,8 +127,7 @@ impl Context {
let name = util::ident(name); let name = util::ident(name);
// use the same derives as for events // use the same derives as for events
let derives = &self.event_derives; let derives = util::expand_derives(&self.event_derives);
let derives = quote! {#(#derives),*};
let ethers_contract = util::ethers_contract_crate(); let ethers_contract = util::ethers_contract_crate();
Ok(quote! { Ok(quote! {

View File

@ -117,6 +117,10 @@ pub fn expand_doc(s: &str) -> TokenStream {
} }
} }
pub fn expand_derives(derives: &[Path]) -> TokenStream {
quote! {#(#derives),*}
}
/// Parses the given address string /// Parses the given address string
pub fn parse_address<S>(address_str: S) -> Result<Address> pub fn parse_address<S>(address_str: S) -> Result<Address>
where where

View File

@ -1,9 +1,10 @@
//! Helper functions for deriving `EthAbiType` //! Helper functions for deriving `EthAbiType`
use ethers_contract_abigen::ethers_core_crate; use ethers_contract_abigen::ethers_core_crate;
use proc_macro2::{Ident, TokenStream};
use quote::{quote, quote_spanned}; use quote::{quote, quote_spanned};
use syn::spanned::Spanned as _; use syn::spanned::Spanned as _;
use syn::{parse::Error, Data, DeriveInput, Fields}; use syn::{parse::Error, Data, DeriveInput, Fields, Variant};
/// Generates the tokenize implementation /// Generates the tokenize implementation
pub fn derive_tokenizeable_impl(input: &DeriveInput) -> proc_macro2::TokenStream { pub fn derive_tokenizeable_impl(input: &DeriveInput) -> proc_macro2::TokenStream {
@ -80,17 +81,13 @@ pub fn derive_tokenizeable_impl(input: &DeriveInput) -> proc_macro2::TokenStream
into_token_impl, into_token_impl,
) )
} }
Fields::Unit => { Fields::Unit => return tokenize_unit_type(&input.ident),
return Error::new(
input.span(),
"EthAbiType cannot be derived for empty structs and unit",
)
.to_compile_error();
}
}, },
Data::Enum(_) => { Data::Enum(ref data) => {
return Error::new(input.span(), "EthAbiType cannot be derived for enums") return match tokenize_enum(name, data.variants.iter()) {
.to_compile_error(); Ok(tokens) => tokens,
Err(err) => err.to_compile_error(),
}
} }
Data::Union(_) => { Data::Union(_) => {
return Error::new(input.span(), "EthAbiType cannot be derived for unions") return Error::new(input.span(), "EthAbiType cannot be derived for unions")
@ -169,3 +166,86 @@ pub fn derive_tokenizeable_impl(input: &DeriveInput) -> proc_macro2::TokenStream
{ } { }
} }
} }
fn tokenize_unit_type(name: &Ident) -> TokenStream {
let ethers_core = ethers_core_crate();
quote! {
impl #ethers_core::abi::Tokenizable for #name {
fn from_token(token: #ethers_core::abi::Token) -> Result<Self, #ethers_core::abi::InvalidOutputType> where
Self: Sized {
if let #ethers_core::abi::Token::Tuple(tokens) = token {
if !tokens.is_empty() {
Err(#ethers_core::abi::InvalidOutputType(::std::format!(
"Expected empty tuple, got {:?}",
tokens
)))
} else {
Ok(#name{})
}
} else {
Err(#ethers_core::abi::InvalidOutputType(::std::format!(
"Expected Tuple, got {:?}",
token
)))
}
}
fn into_token(self) -> #ethers_core::abi::Token {
#ethers_core::abi::Token::Tuple(::std::vec::Vec::new())
}
}
impl #ethers_core::abi::TokenizableItem for #name { }
}
}
fn tokenize_enum<'a>(
enum_name: &Ident,
variants: impl Iterator<Item = &'a Variant> + 'a,
) -> Result<TokenStream, Error> {
let ethers_core = ethers_core_crate();
let mut into_tokens = TokenStream::new();
let mut from_tokens = TokenStream::new();
for variant in variants {
if variant.fields.len() > 1 {
return Err(Error::new(
variant.span(),
"EthAbiType cannot be derived for enum variants with multiple fields",
));
}
let var_ident = &variant.ident;
if let Some(field) = variant.fields.iter().next() {
let ty = &field.ty;
from_tokens.extend(quote! {
if let Ok(decoded) = #ty::from_token(token.clone()) {
return Ok(#enum_name::#var_ident(decoded))
}
});
into_tokens.extend(quote! {
#enum_name::#var_ident(element) => element.into_token(),
});
} else {
into_tokens.extend(quote! {
#enum_name::#var_ident(element) => # ethers_core::abi::Token::Tuple(::std::vec::Vec::new()),
});
}
}
Ok(quote! {
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 {
#from_tokens
Err(#ethers_core::abi::InvalidOutputType("Failed to decode all type variants".to_string()))
}
fn into_token(self) -> #ethers_core::abi::Token {
match self {
#into_tokens
}
}
}
impl #ethers_core::abi::TokenizableItem for #enum_name { }
})
}

View File

@ -0,0 +1,254 @@
//! Helper functions for deriving `EthCall`
use ethers_contract_abigen::{ethers_contract_crate, ethers_core_crate};
use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::spanned::Spanned as _;
use syn::{parse::Error, AttrStyle, DeriveInput, Lit, Meta, NestedMeta};
use ethers_core::abi::{param_type::Reader, AbiParser, Function, FunctionExt, Param, ParamType};
use crate::abi_ty;
use crate::utils;
/// Generates the `ethcall` trait support
pub(crate) fn derive_eth_call_impl(input: DeriveInput) -> TokenStream {
// the ethers crates to use
let core_crate = ethers_core_crate();
let contract_crate = ethers_contract_crate();
let name = &input.ident;
let attributes = match parse_call_attributes(&input) {
Ok(attributes) => attributes,
Err(errors) => return errors,
};
let function_call_name = attributes
.name
.map(|(s, _)| s)
.unwrap_or_else(|| input.ident.to_string());
let mut function = if let Some((src, span)) = attributes.abi {
// try to parse as solidity function
if let Ok(fun) = parse_function(&src) {
fun
} else {
// try as tuple
if let Some(inputs) = Reader::read(
src.trim_start_matches("function ")
.trim_start()
.trim_start_matches(&function_call_name),
)
.ok()
.and_then(|param| match param {
ParamType::Tuple(params) => Some(
params
.into_iter()
.map(|kind| Param {
name: "".to_string(),
kind,
internal_type: None,
})
.collect(),
),
_ => None,
}) {
#[allow(deprecated)]
Function {
name: function_call_name.clone(),
inputs,
outputs: vec![],
constant: false,
state_mutability: Default::default(),
}
} else {
return Error::new(span, format!("Unable to determine ABI: {}", src))
.to_compile_error();
}
}
} else {
// // try to determine the abi from the fields
match derive_abi_function_from_fields(&input) {
Ok(event) => event,
Err(err) => return err.to_compile_error(),
}
};
function.name = function_call_name.clone();
let abi = function.abi_signature();
let selector = utils::selector(function.selector());
let decode_impl = derive_decode_impl(&function);
let ethcall_impl = quote! {
impl #contract_crate::EthCall for #name {
fn function_name() -> ::std::borrow::Cow<'static, str> {
#function_call_name.into()
}
fn selector() -> #core_crate::types::Selector {
#selector
}
fn abi_signature() -> ::std::borrow::Cow<'static, str> {
#abi.into()
}
}
impl #contract_crate::AbiDecode for #name {
fn decode(bytes: impl AsRef<[u8]>) -> Result<Self, #contract_crate::AbiError> {
#decode_impl
}
}
};
let tokenize_impl = abi_ty::derive_tokenizeable_impl(&input);
quote! {
#tokenize_impl
#ethcall_impl
}
}
fn derive_decode_impl(function: &Function) -> TokenStream {
let core_crate = ethers_core_crate();
let contract_crate = ethers_contract_crate();
let data_types = function
.inputs
.iter()
.map(|input| utils::param_type_quote(&input.kind));
let data_types_init = quote! {let data_types = [#( #data_types ),*];};
quote! {
let bytes = bytes.as_ref();
if bytes.len() < 4 || bytes[..4] != <Self as #contract_crate::EthCall>::selector() {
return Err(#contract_crate::AbiError::WrongSelector);
}
#data_types_init
let data_tokens = #core_crate::abi::decode(&data_types, &bytes[4..])?;
Ok(<Self as #core_crate::abi::Tokenizable>::from_token( #core_crate::abi::Token::Tuple(data_tokens))?)
}
}
/// Determine the function's ABI by parsing the AST
fn derive_abi_function_from_fields(input: &DeriveInput) -> Result<Function, Error> {
#[allow(deprecated)]
let function = Function {
name: "".to_string(),
inputs: utils::derive_abi_inputs_from_fields(input, "EthCall")?
.into_iter()
.map(|(name, kind)| Param {
name,
kind,
internal_type: None,
})
.collect(),
outputs: vec![],
constant: false,
state_mutability: Default::default(),
};
Ok(function)
}
/// All the attributes the `EthCall` macro supports
#[derive(Default)]
struct EthCallAttributes {
name: Option<(String, Span)>,
abi: Option<(String, Span)>,
}
/// extracts the attributes from the struct annotated with `EthCall`
fn parse_call_attributes(input: &DeriveInput) -> Result<EthCallAttributes, TokenStream> {
let mut result = EthCallAttributes::default();
for a in input.attrs.iter() {
if let AttrStyle::Outer = a.style {
if let Ok(Meta::List(meta)) = a.parse_meta() {
if meta.path.is_ident("ethcall") {
for n in meta.nested.iter() {
if let NestedMeta::Meta(meta) = n {
match meta {
Meta::Path(path) => {
return Err(Error::new(
path.span(),
"unrecognized ethcall parameter",
)
.to_compile_error());
}
Meta::List(meta) => {
return Err(Error::new(
meta.path.span(),
"unrecognized ethcall parameter",
)
.to_compile_error());
}
Meta::NameValue(meta) => {
if meta.path.is_ident("name") {
if let Lit::Str(ref lit_str) = meta.lit {
if result.name.is_none() {
result.name =
Some((lit_str.value(), lit_str.span()));
} else {
return Err(Error::new(
meta.span(),
"name 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("abi") {
if let Lit::Str(ref lit_str) = meta.lit {
if result.abi.is_none() {
result.abi =
Some((lit_str.value(), lit_str.span()));
} else {
return Err(Error::new(
meta.span(),
"abi already specified",
)
.to_compile_error());
}
} else {
return Err(Error::new(
meta.span(),
"abi must be a string",
)
.to_compile_error());
}
} else {
return Err(Error::new(
meta.span(),
"unrecognized ethcall parameter",
)
.to_compile_error());
}
}
}
}
}
}
}
}
}
Ok(result)
}
fn parse_function(abi: &str) -> Result<Function, String> {
let abi = if !abi.trim_start().starts_with("function ") {
format!("function {}", abi)
} else {
abi.to_string()
};
AbiParser::default()
.parse_function(&abi)
.map_err(|err| format!("Failed to parse the function ABI: {:?}", err))
}

View File

@ -133,7 +133,6 @@ pub(crate) fn derive_eth_event_impl(input: DeriveInput) -> TokenStream {
let tokenize_impl = abi_ty::derive_tokenizeable_impl(&input); let tokenize_impl = abi_ty::derive_tokenizeable_impl(&input);
// parse attributes abi into source
quote! { quote! {
#tokenize_impl #tokenize_impl
#ethevent_impl #ethevent_impl
@ -322,47 +321,11 @@ fn derive_decode_from_log_impl(
}) })
} }
/// Determine the event's ABI by parsing the AST
fn derive_abi_event_from_fields(input: &DeriveInput) -> Result<Event, Error> { 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 => {
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 inputs = fields
.iter()
.map(|f| {
let name = f
.ident
.as_ref()
.map(|name| name.to_string())
.unwrap_or_else(|| "".to_string());
utils::find_parameter_type(&f.ty).map(|ty| (name, ty))
})
.collect::<Result<Vec<_>, _>>()?;
let event = Event { let event = Event {
name: "".to_string(), name: "".to_string(),
inputs: inputs inputs: utils::derive_abi_inputs_from_fields(input, "EthEvent")?
.into_iter() .into_iter()
.map(|(name, kind)| EventParam { .map(|(name, kind)| EventParam {
name, name,

View File

@ -9,6 +9,7 @@ use abigen::Contracts;
pub(crate) mod abi_ty; pub(crate) mod abi_ty;
mod abigen; mod abigen;
mod call;
mod display; mod display;
mod event; mod event;
mod spanned; mod spanned;
@ -111,6 +112,9 @@ pub fn derive_abi_type(input: TokenStream) -> TokenStream {
/// # Example /// # Example
/// ///
/// ```ignore /// ```ignore
/// use ethers_contract::EthDisplay;
/// use ethers_core::types::*;
///
/// #[derive(Debug, Clone, EthAbiType, EthDisplay)] /// #[derive(Debug, Clone, EthAbiType, EthDisplay)]
/// struct MyStruct { /// struct MyStruct {
/// addr: Address, /// addr: Address,
@ -161,7 +165,8 @@ pub fn derive_eth_display(input: TokenStream) -> TokenStream {
/// ///
/// # Example /// # Example
/// ```ignore /// ```ignore
/// # use ethers_core::types::Address; /// use ethers_contract::EthCall;
/// use ethers_core::types::Address;
/// ///
/// #[derive(Debug, EthAbiType)] /// #[derive(Debug, EthAbiType)]
/// struct Inner { /// struct Inner {
@ -183,3 +188,70 @@ pub fn derive_abi_event(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput); let input = parse_macro_input!(input as DeriveInput);
TokenStream::from(event::derive_eth_event_impl(input)) TokenStream::from(event::derive_eth_event_impl(input))
} }
/// Derives the `EthCall` and `Tokenizeable` trait for the labeled type.
///
/// Additional arguments can be specified using the `#[ethcall(...)]`
/// attribute:
///
/// For the struct:
///
/// - `name`, `name = "..."`: Overrides the generated `EthCall` function name, default
/// is the
/// struct's name.
/// - `abi`, `abi = "..."`: The ABI signature for the function this call's data
/// corresponds to.
///
/// NOTE: in order to successfully parse the `abi` (`<name>(<args>,...)`) the `<name`>
/// must match either the struct name or the name attribute: `#[ethcall(name ="<name>"]`
///
/// # Example
///
/// ```ignore
/// use ethers_contract::EthCall;
///
/// #[derive(Debug, Clone, EthCall)]
/// #[ethcall(name ="my_call")]
/// struct MyCall {
/// addr: Address,
/// old_value: String,
/// new_value: String,
/// }
/// assert_eq!(
/// MyCall::abi_signature().as_ref(),
/// "my_call(address,string,string)"
/// );
/// ```
///
/// # Example
///
/// Call with struct inputs
///
/// ```ignore
/// use ethers_core::abi::Address;
///
/// #[derive(Debug, Clone, PartialEq, EthAbiType)]
/// struct SomeType {
/// inner: Address,
/// msg: String,
/// }
///
/// #[derive(Debug, PartialEq, EthCall)]
/// #[ethcall(name = "foo", abi = "foo(address,(address,string),string)")]
/// struct FooCall {
/// old_author: Address,
/// inner: SomeType,
/// new_value: String,
/// }
///
/// assert_eq!(
/// FooCall::abi_signature().as_ref(),
/// "foo(address,(address,string),string)"
/// );
/// ```
///
#[proc_macro_derive(EthCall, attributes(ethcall))]
pub fn derive_abi_call(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
TokenStream::from(call::derive_eth_call_impl(input))
}

View File

@ -1,9 +1,12 @@
use ethers_contract_abigen::ethers_core_crate; use ethers_contract_abigen::ethers_core_crate;
use ethers_core::abi::ParamType; use ethers_core::abi::ParamType;
use ethers_core::types::Selector;
use proc_macro2::Literal; use proc_macro2::Literal;
use quote::quote; use quote::quote;
use syn::spanned::Spanned as _; use syn::spanned::Spanned as _;
use syn::{parse::Error, Expr, GenericArgument, Lit, PathArguments, Type}; use syn::{
parse::Error, Data, DeriveInput, Expr, Fields, GenericArgument, Lit, PathArguments, Type,
};
pub fn signature(hash: &[u8]) -> proc_macro2::TokenStream { pub fn signature(hash: &[u8]) -> proc_macro2::TokenStream {
let core_crate = ethers_core_crate(); let core_crate = ethers_core_crate();
@ -11,6 +14,11 @@ pub fn signature(hash: &[u8]) -> proc_macro2::TokenStream {
quote! {#core_crate::types::H256([#( #bytes ),*])} quote! {#core_crate::types::H256([#( #bytes ),*])}
} }
pub fn selector(selector: Selector) -> proc_macro2::TokenStream {
let bytes = selector.iter().copied().map(Literal::u8_unsuffixed);
quote! {[#( #bytes ),*]}
}
/// Parses an int type from its string representation /// Parses an int type from its string representation
pub fn parse_int_param_type(s: &str) -> Option<ParamType> { pub fn parse_int_param_type(s: &str) -> Option<ParamType> {
let size = s let size = s
@ -158,3 +166,49 @@ pub fn find_parameter_type(ty: &Type) -> Result<ParamType, Error> {
)), )),
} }
} }
/// Attempts to determine the ABI Paramtypes from the type's AST
pub fn derive_abi_inputs_from_fields(
input: &DeriveInput,
trait_name: &str,
) -> Result<Vec<(String, ParamType)>, 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 => {
return Err(Error::new(
input.span(),
format!(
"{} cannot be derived for empty structs and unit",
trait_name
),
))
}
},
Data::Enum(_) => {
return Err(Error::new(
input.span(),
format!("{} cannot be derived for enums", trait_name),
));
}
Data::Union(_) => {
return Err(Error::new(
input.span(),
format!("{} cannot be derived for unions", trait_name),
));
}
};
fields
.iter()
.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()
}

View File

@ -7,10 +7,43 @@ use ethers_core::{
}; };
use ethers_providers::{Middleware, PendingTransaction, ProviderError}; use ethers_providers::{Middleware, PendingTransaction, ProviderError};
use std::borrow::Cow;
use std::{fmt::Debug, marker::PhantomData, sync::Arc}; use std::{fmt::Debug, marker::PhantomData, sync::Arc};
use crate::{AbiDecode, AbiEncode};
use ethers_core::abi::{Tokenizable, Tokenize};
use ethers_core::types::Selector;
use ethers_core::utils::id;
use thiserror::Error as ThisError; use thiserror::Error as ThisError;
/// A helper trait for types that represent all call input parameters of a specific function
pub trait EthCall: Tokenizable + AbiDecode + Send + Sync {
/// The name of the function
fn function_name() -> Cow<'static, str>;
/// Retrieves the ABI signature for the call
fn abi_signature() -> Cow<'static, str>;
/// The selector of the function
fn selector() -> Selector {
id(Self::abi_signature())
}
}
impl<T: EthCall> AbiEncode for T {
fn encode(self) -> Result<Bytes, AbiError> {
let tokens = self.into_tokens();
let selector = Self::selector();
let encoded = ethers_core::abi::encode(&tokens);
let encoded: Vec<_> = selector
.iter()
.copied()
.chain(encoded.into_iter())
.collect();
Ok(encoded.into())
}
}
#[derive(ThisError, Debug)] #[derive(ThisError, Debug)]
/// An Error which is thrown when interacting with a smart contract /// An Error which is thrown when interacting with a smart contract
pub enum ContractError<M: Middleware> { pub enum ContractError<M: Middleware> {

View File

@ -0,0 +1,14 @@
use crate::AbiError;
use ethers_core::types::Bytes;
/// Trait for ABI encoding
pub trait AbiEncode {
/// ABI encode the type
fn encode(self) -> Result<Bytes, AbiError>;
}
/// Trait for ABI decoding
pub trait AbiDecode: Sized {
/// Decodes the ABI encoded data
fn decode(bytes: impl AsRef<[u8]>) -> Result<Self, AbiError>;
}

View File

@ -20,7 +20,7 @@ mod base;
pub use base::{decode_function_data, encode_function_data, AbiError, BaseContract}; pub use base::{decode_function_data, encode_function_data, AbiError, BaseContract};
mod call; mod call;
pub use call::ContractError; pub use call::{ContractError, EthCall};
mod factory; mod factory;
pub use factory::ContractFactory; pub use factory::ContractFactory;
@ -31,6 +31,9 @@ pub use event::EthEvent;
mod log; mod log;
pub use log::{decode_logs, EthLogDecode, LogMeta}; pub use log::{decode_logs, EthLogDecode, LogMeta};
mod codec;
pub use codec::{AbiDecode, AbiEncode};
mod stream; mod stream;
mod multicall; mod multicall;
@ -50,7 +53,7 @@ pub use ethers_contract_abigen::Abigen;
#[cfg(any(test, feature = "abigen"))] #[cfg(any(test, feature = "abigen"))]
#[cfg_attr(docsrs, doc(cfg(feature = "abigen")))] #[cfg_attr(docsrs, doc(cfg(feature = "abigen")))]
pub use ethers_contract_derive::{abigen, EthAbiType, EthDisplay, EthEvent}; pub use ethers_contract_derive::{abigen, EthAbiType, EthCall, EthDisplay, EthEvent};
// Hide the Lazy re-export, it's just for convenience // Hide the Lazy re-export, it's just for convenience
#[doc(hidden)] #[doc(hidden)]

View File

@ -1,6 +1,6 @@
#![cfg(feature = "abigen")] #![cfg(feature = "abigen")]
//! Test cases to validate the `abigen!` macro //! Test cases to validate the `abigen!` macro
use ethers_contract::{abigen, EthEvent}; use ethers_contract::{abigen, AbiDecode, AbiEncode, EthEvent};
use ethers_core::abi::{Address, Tokenizable}; use ethers_core::abi::{Address, Tokenizable};
use ethers_core::types::U256; use ethers_core::types::U256;
use ethers_providers::Provider; use ethers_providers::Provider;
@ -163,6 +163,8 @@ fn can_gen_human_readable_with_structs() {
r#"[ r#"[
struct Foo { uint256 x; } struct Foo { uint256 x; }
function foo(Foo memory x) function foo(Foo memory x)
function bar(uint256 x, uint256 y, address addr)
yeet(uint256,uint256,address)
]"#, ]"#,
event_derives(serde::Deserialize, serde::Serialize) event_derives(serde::Deserialize, serde::Serialize)
); );
@ -172,6 +174,33 @@ fn can_gen_human_readable_with_structs() {
let contract = SimpleContract::new(Address::default(), Arc::new(client)); let contract = SimpleContract::new(Address::default(), Arc::new(client));
let f = Foo { x: 100u64.into() }; let f = Foo { x: 100u64.into() };
let _ = contract.foo(f); let _ = contract.foo(f);
let call = BarCall {
x: 1u64.into(),
y: 0u64.into(),
addr: Address::random(),
};
let encoded_call = contract.encode("bar", (call.x, call.y, call.addr)).unwrap();
assert_eq!(encoded_call, call.clone().encode().unwrap());
let decoded_call = BarCall::decode(encoded_call.as_ref()).unwrap();
assert_eq!(call, decoded_call);
let contract_call = SimpleContractCalls::Bar(call);
let decoded_enum = SimpleContractCalls::decode(encoded_call.as_ref()).unwrap();
assert_eq!(contract_call, decoded_enum);
assert_eq!(encoded_call, contract_call.encode().unwrap());
let call = YeetCall(1u64.into(), 0u64.into(), Address::zero());
let encoded_call = contract.encode("yeet", (call.0, call.1, call.2)).unwrap();
assert_eq!(encoded_call, call.clone().encode().unwrap());
let decoded_call = YeetCall::decode(encoded_call.as_ref()).unwrap();
assert_eq!(call, decoded_call);
let contract_call = SimpleContractCalls::Yeet(call.clone());
let decoded_enum = SimpleContractCalls::decode(encoded_call.as_ref()).unwrap();
assert_eq!(contract_call, decoded_enum);
assert_eq!(contract_call, call.into());
assert_eq!(encoded_call, contract_call.encode().unwrap());
} }
#[test] #[test]
@ -192,4 +221,48 @@ fn can_handle_overloaded_functions() {
let _ = contract.get_value(); let _ = contract.get_value();
let _ = contract.get_value_with_other_value(1337u64.into()); let _ = contract.get_value_with_other_value(1337u64.into());
let _ = contract.get_value_with_other_value_and_addr(1337u64.into(), Address::zero()); let _ = contract.get_value_with_other_value_and_addr(1337u64.into(), Address::zero());
let call = GetValueCall;
let encoded_call = contract.encode("getValue", ()).unwrap();
assert_eq!(encoded_call, call.clone().encode().unwrap());
let decoded_call = GetValueCall::decode(encoded_call.as_ref()).unwrap();
assert_eq!(call, decoded_call);
let contract_call = SimpleContractCalls::GetValue(call);
let decoded_enum = SimpleContractCalls::decode(encoded_call.as_ref()).unwrap();
assert_eq!(contract_call, decoded_enum);
assert_eq!(encoded_call, contract_call.encode().unwrap());
let call = GetValueWithOtherValueCall {
other_value: 420u64.into(),
};
let encoded_call = contract
.encode_with_selector([15, 244, 201, 22], call.other_value)
.unwrap();
assert_eq!(encoded_call, call.clone().encode().unwrap());
let decoded_call = GetValueWithOtherValueCall::decode(encoded_call.as_ref()).unwrap();
assert_eq!(call, decoded_call);
let contract_call = SimpleContractCalls::GetValueWithOtherValue(call);
let decoded_enum = SimpleContractCalls::decode(encoded_call.as_ref()).unwrap();
assert_eq!(contract_call, decoded_enum);
assert_eq!(encoded_call, contract_call.encode().unwrap());
let call = GetValueWithOtherValueAndAddrCall {
other_value: 420u64.into(),
addr: Address::random(),
};
let encoded_call = contract
.encode_with_selector([14, 97, 29, 56], (call.other_value, call.addr))
.unwrap();
let decoded_call = GetValueWithOtherValueAndAddrCall::decode(encoded_call.as_ref()).unwrap();
assert_eq!(call, decoded_call);
let contract_call = SimpleContractCalls::GetValueWithOtherValueAndAddr(call);
let decoded_enum = SimpleContractCalls::decode(encoded_call.as_ref()).unwrap();
assert_eq!(contract_call, decoded_enum);
assert_eq!(encoded_call, contract_call.encode().unwrap());
} }

View File

@ -1,9 +1,12 @@
use ethers_contract::EthLogDecode; use ethers_contract::EthLogDecode;
use ethers_contract::{abigen, EthAbiType, EthDisplay, EthEvent}; use ethers_contract::{abigen, AbiDecode, EthAbiType, EthCall, EthDisplay, EthEvent};
use ethers_core::abi::{RawLog, Tokenizable}; use ethers_core::abi::{RawLog, Tokenizable};
use ethers_core::types::Address; use ethers_core::types::Address;
use ethers_core::types::{H160, H256, I256, U128, U256}; use ethers_core::types::{H160, H256, I256, U128, U256};
fn assert_tokenizeable<T: Tokenizable>() {}
fn assert_ethcall<T: EthCall>() {}
#[derive(Debug, Clone, PartialEq, EthAbiType)] #[derive(Debug, Clone, PartialEq, EthAbiType)]
struct ValueChanged { struct ValueChanged {
old_author: Address, old_author: Address,
@ -42,6 +45,22 @@ fn can_detokenize_struct() {
assert_eq!(value, ValueChanged::from_token(token).unwrap()); assert_eq!(value, ValueChanged::from_token(token).unwrap());
} }
#[test]
fn can_derive_abi_type_empty_struct() {
#[derive(Debug, Clone, PartialEq, EthAbiType)]
struct Call();
#[derive(Debug, Clone, PartialEq, EthAbiType)]
struct Call2 {};
#[derive(Debug, Clone, PartialEq, EthAbiType)]
struct Call3;
assert_tokenizeable::<Call>();
assert_tokenizeable::<Call2>();
assert_tokenizeable::<Call3>();
}
#[test] #[test]
fn can_detokenize_nested_structs() { fn can_detokenize_nested_structs() {
let value = ValueChangedWrapper { let value = ValueChangedWrapper {
@ -319,6 +338,27 @@ fn can_decode_event_single_param() {
assert_eq!(event.param1, 123u64.into()); assert_eq!(event.param1, 123u64.into());
} }
#[test]
fn can_decode_event_tuple_single_param() {
#[derive(Debug, PartialEq, EthEvent)]
struct OneParam(#[ethevent(indexed)] U256);
let log = RawLog {
topics: vec![
"bd9bb67345a2fcc8ef3b0857e7e2901f5a0dcfc7fe5e3c10dc984f02842fb7ba"
.parse()
.unwrap(),
"000000000000000000000000000000000000000000000000000000000000007b"
.parse()
.unwrap(),
],
data: vec![],
};
let event = <OneParam as EthLogDecode>::decode_log(&log).unwrap();
assert_eq!(event.0, 123u64.into());
}
#[test] #[test]
fn can_decode_event_with_no_params() { fn can_decode_event_with_no_params() {
#[derive(Debug, PartialEq, EthEvent)] #[derive(Debug, PartialEq, EthEvent)]
@ -389,3 +429,61 @@ fn eth_display_works_for_human_readable() {
}; };
assert_eq!("abc".to_string(), format!("{}", log)); assert_eq!("abc".to_string(), format!("{}", log));
} }
#[test]
fn can_derive_ethcall() {
#[derive(Debug, Clone, EthCall, EthDisplay)]
struct MyStruct {
addr: Address,
old_value: String,
new_value: String,
h: H256,
i: I256,
arr_u8: [u8; 32],
arr_u16: [u16; 32],
v: Vec<u8>,
}
assert_tokenizeable::<MyStruct>();
assert_ethcall::<MyStruct>();
#[derive(Debug, Clone, EthCall, EthDisplay)]
#[ethcall(name = "my_call")]
struct MyCall {
addr: Address,
old_value: String,
new_value: String,
}
assert_eq!(
MyCall::abi_signature().as_ref(),
"my_call(address,string,string)"
);
assert_tokenizeable::<MyCall>();
assert_ethcall::<MyCall>();
}
#[test]
fn can_derive_ethcall_with_nested_structs() {
#[derive(Debug, Clone, PartialEq, EthAbiType)]
struct SomeType {
inner: Address,
msg: String,
}
#[derive(Debug, PartialEq, EthCall)]
#[ethcall(name = "foo", abi = "foo(address,(address,string),string)")]
struct FooCall {
old_author: Address,
inner: SomeType,
new_value: String,
}
assert_eq!(
FooCall::abi_signature().as_ref(),
"foo(address,(address,string),string)"
);
assert_tokenizeable::<FooCall>();
assert_ethcall::<FooCall>();
}

View File

@ -337,14 +337,16 @@ impl AbiParser {
let state_mutability = modifiers.map(detect_state_mutability).unwrap_or_default(); let state_mutability = modifiers.map(detect_state_mutability).unwrap_or_default();
#[allow(deprecated)] Ok(
Ok(Function { #[allow(deprecated)]
name, Function {
inputs, name,
outputs, inputs,
state_mutability, outputs,
constant: false, state_mutability,
}) constant: false,
},
)
} }
fn parse_params(&self, s: &str) -> Result<Vec<(Param, Option<String>)>> { fn parse_params(&self, s: &str) -> Result<Vec<(Param, Option<String>)>> {

View File

@ -59,9 +59,7 @@ fn twos_complement(u: U256) -> U256 {
fn handle_overflow<T>((result, overflow): (T, bool)) -> T { fn handle_overflow<T>((result, overflow): (T, bool)) -> T {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
{ {
if overflow { assert!(!overflow, "overflow");
panic!("overflow");
}
} }
let _ = overflow; let _ = overflow;

View File

@ -483,7 +483,7 @@ pub fn find_parameter_type(ty: &Type) -> Result<ParamType, TokenStream> {
let params = ty let params = ty
.elems .elems
.iter() .iter()
.map(|t| find_parameter_type(t)) .map(find_parameter_type)
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
Ok(ParamType::Tuple(params)) Ok(ParamType::Tuple(params))
} }