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:
parent
071a41605b
commit
fb4d9a9ef1
|
@ -4,6 +4,7 @@
|
|||
|
||||
### 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 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)
|
||||
|
|
|
@ -29,6 +29,8 @@ pub struct ExpandedContract {
|
|||
pub contract: TokenStream,
|
||||
/// All event impls of the contract
|
||||
pub events: TokenStream,
|
||||
/// All contract call struct related types
|
||||
pub call_structs: TokenStream,
|
||||
/// The contract's internal structs
|
||||
pub abi_structs: TokenStream,
|
||||
}
|
||||
|
@ -41,6 +43,7 @@ impl ExpandedContract {
|
|||
imports,
|
||||
contract,
|
||||
events,
|
||||
call_structs,
|
||||
abi_structs,
|
||||
} = self;
|
||||
quote! {
|
||||
|
@ -52,6 +55,7 @@ impl ExpandedContract {
|
|||
#imports
|
||||
#contract
|
||||
#events
|
||||
#call_structs
|
||||
#abi_structs
|
||||
}
|
||||
}
|
||||
|
@ -111,8 +115,8 @@ impl Context {
|
|||
// 3. impl block for the event functions
|
||||
let contract_events = self.event_methods()?;
|
||||
|
||||
// 4. impl block for the contract methods
|
||||
let contract_methods = self.methods()?;
|
||||
// 4. impl block for the contract methods and their corresponding types
|
||||
let (contract_methods, call_structs) = self.methods_and_call_structs()?;
|
||||
|
||||
// 5. Declare the structs parsed from the human readable abi
|
||||
let abi_structs_decl = self.abi_structs()?;
|
||||
|
@ -146,6 +150,7 @@ impl Context {
|
|||
imports,
|
||||
contract,
|
||||
events: events_decl,
|
||||
call_structs,
|
||||
abi_structs: abi_structs_decl,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ use inflector::Inflector;
|
|||
use proc_macro2::{Ident, Literal, TokenStream};
|
||||
use quote::quote;
|
||||
use std::collections::BTreeMap;
|
||||
use syn::Path;
|
||||
|
||||
impl Context {
|
||||
/// Expands each event to a struct + its impl Detokenize block
|
||||
|
@ -33,9 +32,10 @@ impl Context {
|
|||
|
||||
/// Generate the event filter methods for the contract
|
||||
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
|
||||
.values()
|
||||
.map(std::ops::Deref::deref)
|
||||
.flatten()
|
||||
.map(|event| self.expand_filter(event))
|
||||
.collect::<Vec<_>>();
|
||||
|
@ -51,9 +51,9 @@ impl Context {
|
|||
|
||||
/// 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
|
||||
let variants = self
|
||||
.abi
|
||||
.events
|
||||
.values()
|
||||
.flatten()
|
||||
.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();
|
||||
|
||||
quote! {
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, #ethers_contract::EthAbiType)]
|
||||
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: &#ethers_core::abi::RawLog) -> Result<Self, #ethers_core::abi::Error>
|
||||
where
|
||||
|
@ -153,8 +131,8 @@ impl Context {
|
|||
|
||||
/// Expands an event property type.
|
||||
///
|
||||
/// Note that this is slightly different than an expanding a Solidity type as
|
||||
/// complex types like arrays and strings get emited as hashes when they are
|
||||
/// Note that this is slightly different from expanding a Solidity type as
|
||||
/// complex types like arrays and strings get emitted as hashes when they are
|
||||
/// indexed.
|
||||
/// If a complex types matches with a struct previously parsed by the AbiParser,
|
||||
/// we can replace it
|
||||
|
@ -213,8 +191,8 @@ impl Context {
|
|||
})
|
||||
}
|
||||
|
||||
/// Expands an ABI event into name-type pairs for each of its parameters.
|
||||
fn expand_params(&self, event: &Event) -> Result<Vec<(TokenStream, TokenStream, bool)>> {
|
||||
/// Expands the name-type pairs for the given inputs
|
||||
fn expand_event_params(&self, event: &Event) -> Result<Vec<(TokenStream, TokenStream, bool)>> {
|
||||
event
|
||||
.inputs
|
||||
.iter()
|
||||
|
@ -264,7 +242,7 @@ impl Context {
|
|||
|
||||
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
|
||||
let all_anonymous_fields = event.inputs.iter().all(|input| input.name.is_empty());
|
||||
let data_type_definition = if all_anonymous_fields {
|
||||
|
@ -273,7 +251,7 @@ impl Context {
|
|||
expand_data_struct(&event_name, ¶ms)
|
||||
};
|
||||
|
||||
let derives = expand_derives(&self.event_derives);
|
||||
let derives = util::expand_derives(&self.event_derives);
|
||||
|
||||
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 {
|
||||
let fields = params
|
||||
.iter()
|
||||
.map(|(_, ty, _)| quote! { pub #ty })
|
||||
.map(|(_, ty, indexed)| {
|
||||
if *indexed {
|
||||
quote! {
|
||||
#[ethevent(indexed)] pub #ty }
|
||||
} else {
|
||||
quote! {
|
||||
pub #ty }
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
quote! { struct #name( #( #fields ),* ); }
|
||||
}
|
||||
|
||||
fn expand_derives(derives: &[Path]) -> TokenStream {
|
||||
quote! {#(#derives),*}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -450,7 +432,7 @@ mod tests {
|
|||
};
|
||||
|
||||
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 definition = expand_data_struct(&name, ¶ms);
|
||||
|
||||
|
@ -482,7 +464,7 @@ mod tests {
|
|||
};
|
||||
|
||||
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 name = expand_struct_name(&event, alias);
|
||||
let definition = expand_data_struct(&name, ¶ms);
|
||||
|
@ -515,7 +497,7 @@ mod tests {
|
|||
};
|
||||
|
||||
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 definition = expand_data_tuple(&name, ¶ms);
|
||||
|
||||
|
@ -544,7 +526,7 @@ mod tests {
|
|||
};
|
||||
|
||||
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 name = expand_struct_name(&event, alias);
|
||||
let definition = expand_data_tuple(&name, ¶ms);
|
||||
|
|
|
@ -18,32 +18,162 @@ use super::{types, util, Context};
|
|||
/// to the Solidity contract methods.
|
||||
impl Context {
|
||||
/// Expands all method implementations
|
||||
pub(crate) fn methods(&self) -> Result<TokenStream> {
|
||||
let mut aliases = self.get_method_aliases()?;
|
||||
let sorted_functions: BTreeMap<_, _> = self.abi.functions.clone().into_iter().collect();
|
||||
pub(crate) fn methods_and_call_structs(&self) -> Result<(TokenStream, TokenStream)> {
|
||||
let aliases = self.get_method_aliases()?;
|
||||
let sorted_functions: BTreeMap<_, _> = self.abi.functions.iter().collect();
|
||||
let functions = sorted_functions
|
||||
.values()
|
||||
.map(std::ops::Deref::deref)
|
||||
.flatten()
|
||||
.map(|function| {
|
||||
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))
|
||||
})
|
||||
.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,
|
||||
fun: &Function,
|
||||
) -> Result<(TokenStream, TokenStream)> {
|
||||
function: &Function,
|
||||
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 call_args = Vec::with_capacity(fun.inputs.len());
|
||||
for (i, param) in fun.inputs.iter().enumerate() {
|
||||
let name = util::expand_input_name(i, ¶m.name);
|
||||
for (idx, param) in fun.inputs.iter().enumerate() {
|
||||
let name = util::expand_input_name(idx, ¶m.name);
|
||||
let ty = self.expand_input_param(fun, ¶m.name, ¶m.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, ¶m.name);
|
||||
let call_arg = match param.kind {
|
||||
// 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,))`
|
||||
|
@ -59,14 +189,13 @@ impl Context {
|
|||
};
|
||||
call_args.push(call_arg);
|
||||
}
|
||||
let args = quote! { #( , #args )* };
|
||||
let call_args = match call_args.len() {
|
||||
0 => quote! { () },
|
||||
1 => quote! { #( #call_args )* },
|
||||
_ => quote! { ( #(#call_args, )* ) },
|
||||
};
|
||||
|
||||
Ok((args, call_args))
|
||||
Ok(call_args)
|
||||
}
|
||||
|
||||
fn expand_input_param(
|
||||
|
@ -111,13 +240,16 @@ impl Context {
|
|||
// TODO use structs
|
||||
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 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!(
|
||||
"Calls the contract's `{}` (0x{}) function",
|
||||
|
@ -127,8 +259,8 @@ impl Context {
|
|||
Ok(quote! {
|
||||
|
||||
#doc
|
||||
pub fn #name(&self #input) -> #result {
|
||||
self.0.method_hash(#selector, #arg)
|
||||
pub fn #name(&self #function_params) -> #result {
|
||||
self.0.method_hash(#selector, #contract_args)
|
||||
.expect("method not found (this should never happen)")
|
||||
}
|
||||
})
|
||||
|
@ -227,6 +359,55 @@ fn expand_selector(selector: Selector) -> TokenStream {
|
|||
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)]
|
||||
mod tests {
|
||||
use ethers_core::abi::ParamType;
|
||||
|
|
|
@ -127,8 +127,7 @@ impl Context {
|
|||
let name = util::ident(name);
|
||||
|
||||
// use the same derives as for events
|
||||
let derives = &self.event_derives;
|
||||
let derives = quote! {#(#derives),*};
|
||||
let derives = util::expand_derives(&self.event_derives);
|
||||
|
||||
let ethers_contract = util::ethers_contract_crate();
|
||||
Ok(quote! {
|
||||
|
|
|
@ -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
|
||||
pub fn parse_address<S>(address_str: S) -> Result<Address>
|
||||
where
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
//! Helper functions for deriving `EthAbiType`
|
||||
|
||||
use ethers_contract_abigen::ethers_core_crate;
|
||||
use proc_macro2::{Ident, TokenStream};
|
||||
use quote::{quote, quote_spanned};
|
||||
use syn::spanned::Spanned as _;
|
||||
use syn::{parse::Error, Data, DeriveInput, Fields};
|
||||
use syn::{parse::Error, Data, DeriveInput, Fields, Variant};
|
||||
|
||||
/// Generates the tokenize implementation
|
||||
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,
|
||||
)
|
||||
}
|
||||
Fields::Unit => {
|
||||
return Error::new(
|
||||
input.span(),
|
||||
"EthAbiType cannot be derived for empty structs and unit",
|
||||
)
|
||||
.to_compile_error();
|
||||
}
|
||||
Fields::Unit => return tokenize_unit_type(&input.ident),
|
||||
},
|
||||
Data::Enum(_) => {
|
||||
return Error::new(input.span(), "EthAbiType cannot be derived for enums")
|
||||
.to_compile_error();
|
||||
Data::Enum(ref data) => {
|
||||
return match tokenize_enum(name, data.variants.iter()) {
|
||||
Ok(tokens) => tokens,
|
||||
Err(err) => err.to_compile_error(),
|
||||
}
|
||||
}
|
||||
Data::Union(_) => {
|
||||
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 { }
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
|
@ -133,7 +133,6 @@ pub(crate) fn derive_eth_event_impl(input: DeriveInput) -> TokenStream {
|
|||
|
||||
let tokenize_impl = abi_ty::derive_tokenizeable_impl(&input);
|
||||
|
||||
// parse attributes abi into source
|
||||
quote! {
|
||||
#tokenize_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> {
|
||||
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 {
|
||||
name: "".to_string(),
|
||||
inputs: inputs
|
||||
inputs: utils::derive_abi_inputs_from_fields(input, "EthEvent")?
|
||||
.into_iter()
|
||||
.map(|(name, kind)| EventParam {
|
||||
name,
|
||||
|
|
|
@ -9,6 +9,7 @@ use abigen::Contracts;
|
|||
|
||||
pub(crate) mod abi_ty;
|
||||
mod abigen;
|
||||
mod call;
|
||||
mod display;
|
||||
mod event;
|
||||
mod spanned;
|
||||
|
@ -111,6 +112,9 @@ pub fn derive_abi_type(input: TokenStream) -> TokenStream {
|
|||
/// # Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// use ethers_contract::EthDisplay;
|
||||
/// use ethers_core::types::*;
|
||||
///
|
||||
/// #[derive(Debug, Clone, EthAbiType, EthDisplay)]
|
||||
/// struct MyStruct {
|
||||
/// addr: Address,
|
||||
|
@ -161,7 +165,8 @@ pub fn derive_eth_display(input: TokenStream) -> TokenStream {
|
|||
///
|
||||
/// # Example
|
||||
/// ```ignore
|
||||
/// # use ethers_core::types::Address;
|
||||
/// use ethers_contract::EthCall;
|
||||
/// use ethers_core::types::Address;
|
||||
///
|
||||
/// #[derive(Debug, EthAbiType)]
|
||||
/// struct Inner {
|
||||
|
@ -183,3 +188,70 @@ pub fn derive_abi_event(input: TokenStream) -> TokenStream {
|
|||
let input = parse_macro_input!(input as DeriveInput);
|
||||
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))
|
||||
}
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
use ethers_contract_abigen::ethers_core_crate;
|
||||
use ethers_core::abi::ParamType;
|
||||
use ethers_core::types::Selector;
|
||||
use proc_macro2::Literal;
|
||||
use quote::quote;
|
||||
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 {
|
||||
let core_crate = ethers_core_crate();
|
||||
|
@ -11,6 +14,11 @@ pub fn signature(hash: &[u8]) -> proc_macro2::TokenStream {
|
|||
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
|
||||
pub fn parse_int_param_type(s: &str) -> Option<ParamType> {
|
||||
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()
|
||||
}
|
||||
|
|
|
@ -7,10 +7,43 @@ use ethers_core::{
|
|||
};
|
||||
use ethers_providers::{Middleware, PendingTransaction, ProviderError};
|
||||
|
||||
use std::borrow::Cow;
|
||||
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;
|
||||
|
||||
/// 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)]
|
||||
/// An Error which is thrown when interacting with a smart contract
|
||||
pub enum ContractError<M: Middleware> {
|
||||
|
|
|
@ -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>;
|
||||
}
|
|
@ -20,7 +20,7 @@ mod base;
|
|||
pub use base::{decode_function_data, encode_function_data, AbiError, BaseContract};
|
||||
|
||||
mod call;
|
||||
pub use call::ContractError;
|
||||
pub use call::{ContractError, EthCall};
|
||||
|
||||
mod factory;
|
||||
pub use factory::ContractFactory;
|
||||
|
@ -31,6 +31,9 @@ pub use event::EthEvent;
|
|||
mod log;
|
||||
pub use log::{decode_logs, EthLogDecode, LogMeta};
|
||||
|
||||
mod codec;
|
||||
pub use codec::{AbiDecode, AbiEncode};
|
||||
|
||||
mod stream;
|
||||
|
||||
mod multicall;
|
||||
|
@ -50,7 +53,7 @@ pub use ethers_contract_abigen::Abigen;
|
|||
|
||||
#[cfg(any(test, 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
|
||||
#[doc(hidden)]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#![cfg(feature = "abigen")]
|
||||
//! 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::types::U256;
|
||||
use ethers_providers::Provider;
|
||||
|
@ -163,6 +163,8 @@ fn can_gen_human_readable_with_structs() {
|
|||
r#"[
|
||||
struct Foo { uint256 x; }
|
||||
function foo(Foo memory x)
|
||||
function bar(uint256 x, uint256 y, address addr)
|
||||
yeet(uint256,uint256,address)
|
||||
]"#,
|
||||
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 f = Foo { x: 100u64.into() };
|
||||
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]
|
||||
|
@ -192,4 +221,48 @@ fn can_handle_overloaded_functions() {
|
|||
let _ = contract.get_value();
|
||||
let _ = contract.get_value_with_other_value(1337u64.into());
|
||||
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());
|
||||
}
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
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::types::Address;
|
||||
use ethers_core::types::{H160, H256, I256, U128, U256};
|
||||
|
||||
fn assert_tokenizeable<T: Tokenizable>() {}
|
||||
fn assert_ethcall<T: EthCall>() {}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, EthAbiType)]
|
||||
struct ValueChanged {
|
||||
old_author: Address,
|
||||
|
@ -42,6 +45,22 @@ fn can_detokenize_struct() {
|
|||
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]
|
||||
fn can_detokenize_nested_structs() {
|
||||
let value = ValueChangedWrapper {
|
||||
|
@ -319,6 +338,27 @@ fn can_decode_event_single_param() {
|
|||
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]
|
||||
fn can_decode_event_with_no_params() {
|
||||
#[derive(Debug, PartialEq, EthEvent)]
|
||||
|
@ -389,3 +429,61 @@ fn eth_display_works_for_human_readable() {
|
|||
};
|
||||
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>();
|
||||
}
|
||||
|
|
|
@ -337,14 +337,16 @@ impl AbiParser {
|
|||
|
||||
let state_mutability = modifiers.map(detect_state_mutability).unwrap_or_default();
|
||||
|
||||
Ok(
|
||||
#[allow(deprecated)]
|
||||
Ok(Function {
|
||||
Function {
|
||||
name,
|
||||
inputs,
|
||||
outputs,
|
||||
state_mutability,
|
||||
constant: false,
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn parse_params(&self, s: &str) -> Result<Vec<(Param, Option<String>)>> {
|
||||
|
|
|
@ -59,9 +59,7 @@ fn twos_complement(u: U256) -> U256 {
|
|||
fn handle_overflow<T>((result, overflow): (T, bool)) -> T {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
if overflow {
|
||||
panic!("overflow");
|
||||
}
|
||||
assert!(!overflow, "overflow");
|
||||
}
|
||||
|
||||
let _ = overflow;
|
||||
|
|
|
@ -483,7 +483,7 @@ pub fn find_parameter_type(ty: &Type) -> Result<ParamType, TokenStream> {
|
|||
let params = ty
|
||||
.elems
|
||||
.iter()
|
||||
.map(|t| find_parameter_type(t))
|
||||
.map(find_parameter_type)
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
Ok(ParamType::Tuple(params))
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue