feat: add EthError trait and derive (#1549)
* feat: add EthError trait and derive * update changelog
This commit is contained in:
parent
54bd5253c1
commit
27a184db45
|
@ -87,6 +87,7 @@
|
|||
|
||||
### Unreleased
|
||||
|
||||
- generate error bindings for custom errors [#1549](https://github.com/gakonst/ethers-rs/pull/1549)
|
||||
- Support overloaded events
|
||||
[#1233](https://github.com/gakonst/ethers-rs/pull/1233)
|
||||
- Relax Clone requirements when Arc<Middleware> is used
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
#![deny(missing_docs)]
|
||||
mod common;
|
||||
mod errors;
|
||||
mod events;
|
||||
mod methods;
|
||||
mod structs;
|
||||
mod types;
|
||||
|
||||
use super::{util, Abigen};
|
||||
use crate::contract::structs::InternalStructs;
|
||||
use crate::{
|
||||
contract::{methods::MethodAlias, structs::InternalStructs},
|
||||
rawabi::JsonAbi,
|
||||
};
|
||||
use ethers_core::{
|
||||
abi::{Abi, AbiParser},
|
||||
abi::{Abi, AbiParser, ErrorExt, EventExt},
|
||||
macros::{ethers_contract_crate, ethers_core_crate, ethers_providers_crate},
|
||||
types::Bytes,
|
||||
};
|
||||
use eyre::{eyre, Context as _, Result};
|
||||
|
||||
use crate::contract::methods::MethodAlias;
|
||||
|
||||
use crate::rawabi::JsonAbi;
|
||||
use ethers_core::{abi::EventExt, types::Bytes};
|
||||
use proc_macro2::{Ident, Literal, TokenStream};
|
||||
use quote::quote;
|
||||
use serde::Deserialize;
|
||||
|
@ -34,6 +34,8 @@ pub struct ExpandedContract {
|
|||
pub contract: TokenStream,
|
||||
/// All event impls of the contract
|
||||
pub events: TokenStream,
|
||||
/// All error impls of the contract
|
||||
pub errors: TokenStream,
|
||||
/// All contract call struct related types
|
||||
pub call_structs: TokenStream,
|
||||
/// The contract's internal structs
|
||||
|
@ -43,8 +45,15 @@ pub struct ExpandedContract {
|
|||
impl ExpandedContract {
|
||||
/// Merges everything into a single module
|
||||
pub fn into_tokens(self) -> TokenStream {
|
||||
let ExpandedContract { module, imports, contract, events, call_structs, abi_structs } =
|
||||
self;
|
||||
let ExpandedContract {
|
||||
module,
|
||||
imports,
|
||||
contract,
|
||||
events,
|
||||
call_structs,
|
||||
abi_structs,
|
||||
errors,
|
||||
} = self;
|
||||
quote! {
|
||||
// export all the created data types
|
||||
pub use #module::*;
|
||||
|
@ -53,6 +62,7 @@ impl ExpandedContract {
|
|||
pub mod #module {
|
||||
#imports
|
||||
#contract
|
||||
#errors
|
||||
#events
|
||||
#call_structs
|
||||
#abi_structs
|
||||
|
@ -87,6 +97,9 @@ pub struct Context {
|
|||
/// Manually specified method aliases.
|
||||
method_aliases: BTreeMap<String, MethodAlias>,
|
||||
|
||||
/// Manually specified method aliases.
|
||||
error_aliases: BTreeMap<String, Ident>,
|
||||
|
||||
/// Derives added to event structs and enums.
|
||||
event_derives: Vec<Path>,
|
||||
|
||||
|
@ -125,6 +138,9 @@ impl Context {
|
|||
// 6. Declare the structs parsed from the human readable abi
|
||||
let abi_structs_decl = self.abi_structs()?;
|
||||
|
||||
// 7. declare all error types
|
||||
let errors_decl = self.errors()?;
|
||||
|
||||
let ethers_core = ethers_core_crate();
|
||||
let ethers_contract = ethers_contract_crate();
|
||||
let ethers_providers = ethers_providers_crate();
|
||||
|
@ -145,6 +161,7 @@ impl Context {
|
|||
#contract_methods
|
||||
|
||||
#contract_events
|
||||
|
||||
}
|
||||
|
||||
impl<M : #ethers_providers::Middleware> From<#ethers_contract::Contract<M>> for #name<M> {
|
||||
|
@ -159,6 +176,7 @@ impl Context {
|
|||
imports,
|
||||
contract,
|
||||
events: events_decl,
|
||||
errors: errors_decl,
|
||||
call_structs,
|
||||
abi_structs: abi_structs_decl,
|
||||
})
|
||||
|
@ -226,20 +244,30 @@ impl Context {
|
|||
event_aliases.insert(signature, alias);
|
||||
}
|
||||
|
||||
// also check for overloaded functions not covered by aliases, in which case we simply
|
||||
// also check for overloaded events not covered by aliases, in which case we simply
|
||||
// numerate them
|
||||
for events in abi.events.values() {
|
||||
let not_aliased =
|
||||
events.iter().filter(|ev| !event_aliases.contains_key(&ev.abi_signature()));
|
||||
if not_aliased.clone().count() > 1 {
|
||||
let mut aliases = Vec::new();
|
||||
// overloaded events
|
||||
for (idx, event) in not_aliased.enumerate() {
|
||||
let event_name = format!("{}{}", event.name, idx + 1);
|
||||
aliases.push((event.abi_signature(), events::event_struct_alias(&event_name)));
|
||||
}
|
||||
event_aliases.extend(aliases);
|
||||
}
|
||||
insert_alias_names(
|
||||
&mut event_aliases,
|
||||
events.iter().map(|e| (e.abi_signature(), e.name.as_str())),
|
||||
events::event_struct_alias,
|
||||
);
|
||||
}
|
||||
|
||||
let mut error_aliases = BTreeMap::new();
|
||||
for (signature, alias) in args.error_aliases.into_iter() {
|
||||
let alias = syn::parse_str(&alias)?;
|
||||
error_aliases.insert(signature, alias);
|
||||
}
|
||||
|
||||
// also check for overloaded errors not covered by aliases, in which case we simply
|
||||
// numerate them
|
||||
for errors in abi.errors.values() {
|
||||
insert_alias_names(
|
||||
&mut error_aliases,
|
||||
errors.iter().map(|e| (e.abi_signature(), e.name.as_str())),
|
||||
errors::error_struct_alias,
|
||||
);
|
||||
}
|
||||
|
||||
let event_derives = args
|
||||
|
@ -259,6 +287,7 @@ impl Context {
|
|||
contract_name: args.contract_name,
|
||||
contract_bytecode,
|
||||
method_aliases,
|
||||
error_aliases: Default::default(),
|
||||
event_derives,
|
||||
event_aliases,
|
||||
})
|
||||
|
@ -290,6 +319,31 @@ impl Context {
|
|||
}
|
||||
}
|
||||
|
||||
/// Solidity supports overloading as long as the signature of an event, error, function is unique,
|
||||
/// which results in a mapping `(name -> Vec<Element>)`
|
||||
///
|
||||
///
|
||||
/// This will populate the alias map for the value in the mapping (`Vec<Element>`) via `abi
|
||||
/// signature -> name` using the given aliases and merge it with all names not yet aliased.
|
||||
///
|
||||
/// If the iterator yields more than one element, this will simply numerate them
|
||||
fn insert_alias_names<'a, I, F>(aliases: &mut BTreeMap<String, Ident>, elements: I, get_ident: F)
|
||||
where
|
||||
I: IntoIterator<Item = (String, &'a str)>,
|
||||
F: Fn(&str) -> Ident,
|
||||
{
|
||||
let not_aliased =
|
||||
elements.into_iter().filter(|(sig, _name)| !aliases.contains_key(sig)).collect::<Vec<_>>();
|
||||
if not_aliased.len() > 1 {
|
||||
let mut overloaded_aliases = Vec::new();
|
||||
for (idx, (sig, name)) in not_aliased.into_iter().enumerate() {
|
||||
let unique_name = format!("{}{}", name, idx + 1);
|
||||
overloaded_aliases.push((sig, get_ident(&unique_name)));
|
||||
}
|
||||
aliases.extend(overloaded_aliases);
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse the abi via `Source::parse` and return if the abi defined as human readable
|
||||
fn parse_abi(abi_str: &str) -> Result<(Abi, bool, AbiParser)> {
|
||||
let mut abi_parser = AbiParser::default();
|
||||
|
|
|
@ -1,9 +1,67 @@
|
|||
use super::{util, Context};
|
||||
|
||||
use proc_macro2::TokenStream;
|
||||
use crate::contract::types;
|
||||
use ethers_core::{
|
||||
abi::{Param, ParamType},
|
||||
macros::{ethers_contract_crate, ethers_core_crate, ethers_providers_crate},
|
||||
};
|
||||
use proc_macro2::{Ident, TokenStream};
|
||||
use quote::quote;
|
||||
|
||||
use ethers_core::macros::{ethers_contract_crate, ethers_core_crate, ethers_providers_crate};
|
||||
/// Expands to the `name : type` pairs for the params
|
||||
pub(crate) fn expand_params<'a, F>(
|
||||
params: &[Param],
|
||||
resolve_tuple: F,
|
||||
) -> eyre::Result<Vec<(TokenStream, TokenStream)>>
|
||||
where
|
||||
F: Fn(&str) -> Option<&'a str>,
|
||||
{
|
||||
params
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, param)| {
|
||||
let name = util::expand_input_name(idx, ¶m.name);
|
||||
let ty = expand_param_type(param, ¶m.kind, |s| resolve_tuple(s))?;
|
||||
Ok((name, ty))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// returns the Tokenstream for the corresponding rust type
|
||||
pub(crate) fn expand_param_type<'a, F>(
|
||||
param: &Param,
|
||||
kind: &ParamType,
|
||||
resolve_tuple: F,
|
||||
) -> eyre::Result<TokenStream>
|
||||
where
|
||||
F: Fn(&str) -> Option<&'a str>,
|
||||
{
|
||||
match kind {
|
||||
ParamType::Array(ty) => {
|
||||
let ty = expand_param_type(param, ty, resolve_tuple)?;
|
||||
Ok(quote! {
|
||||
::std::vec::Vec<#ty>
|
||||
})
|
||||
}
|
||||
ParamType::FixedArray(ty, size) => {
|
||||
let ty = expand_param_type(param, ty, resolve_tuple)?;
|
||||
let size = *size;
|
||||
Ok(quote! {[#ty; #size]})
|
||||
}
|
||||
ParamType::Tuple(_) => {
|
||||
let ty = if let Some(rust_struct_name) =
|
||||
param.internal_type.as_ref().and_then(|s| resolve_tuple(s.as_str()))
|
||||
{
|
||||
let ident = util::ident(rust_struct_name);
|
||||
quote! {#ident}
|
||||
} else {
|
||||
types::expand(kind)?
|
||||
};
|
||||
Ok(ty)
|
||||
}
|
||||
_ => types::expand(kind),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn imports(name: &str) -> TokenStream {
|
||||
let doc = util::expand_doc(&format!("{} was auto-generated with ethers-rs Abigen. More information at: https://github.com/gakonst/ethers-rs", name));
|
||||
|
@ -95,3 +153,38 @@ pub(crate) fn struct_declaration(cx: &Context) -> TokenStream {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Expands to the tuple struct definition
|
||||
pub(crate) 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 a struct definition with named fields
|
||||
pub(crate) 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, )* } }
|
||||
}
|
||||
|
|
|
@ -0,0 +1,169 @@
|
|||
//! derive error bindings
|
||||
|
||||
use super::{util, Context};
|
||||
use crate::contract::common::{expand_data_struct, expand_data_tuple, expand_params};
|
||||
use ethers_core::{
|
||||
abi::{ethabi::AbiError, ErrorExt},
|
||||
macros::{ethers_contract_crate, ethers_core_crate},
|
||||
};
|
||||
use eyre::Result;
|
||||
use inflector::Inflector;
|
||||
use proc_macro2::{Ident, TokenStream};
|
||||
use quote::quote;
|
||||
|
||||
impl Context {
|
||||
/// Returns all error declarations
|
||||
pub(crate) fn errors(&self) -> Result<TokenStream> {
|
||||
let data_types = self
|
||||
.abi
|
||||
.errors
|
||||
.values()
|
||||
.flatten()
|
||||
.map(|event| self.expand_error(event))
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
|
||||
// only expand an enum when multiple errors are present
|
||||
let errors_enum_decl = if self.abi.errors.values().flatten().count() > 1 {
|
||||
self.expand_errors_enum()
|
||||
} else {
|
||||
quote! {}
|
||||
};
|
||||
|
||||
Ok(quote! {
|
||||
// HERE
|
||||
#( #data_types )*
|
||||
|
||||
#errors_enum_decl
|
||||
|
||||
// HERE end
|
||||
})
|
||||
}
|
||||
|
||||
/// Expands an ABI error into a single error data type. This can expand either
|
||||
/// into a structure or a tuple in the case where all error parameters are anonymous.
|
||||
fn expand_error(&self, error: &AbiError) -> Result<TokenStream> {
|
||||
let sig = self.error_aliases.get(&error.abi_signature()).cloned();
|
||||
let abi_signature = error.abi_signature();
|
||||
|
||||
let error_name = error_struct_name(&error.name, sig);
|
||||
|
||||
let fields = self.expand_error_params(error)?;
|
||||
|
||||
// expand as a tuple if all fields are anonymous
|
||||
let all_anonymous_fields = error.inputs.iter().all(|input| input.name.is_empty());
|
||||
let data_type_definition = if all_anonymous_fields {
|
||||
// expand to a tuple struct
|
||||
expand_data_tuple(&error_name, &fields)
|
||||
} else {
|
||||
// expand to a struct
|
||||
expand_data_struct(&error_name, &fields)
|
||||
};
|
||||
|
||||
let doc = format!(
|
||||
"Custom Error type `{}` with signature `{}` and selector `{:?}`",
|
||||
error.name,
|
||||
abi_signature,
|
||||
error.selector()
|
||||
);
|
||||
let abi_signature_doc = util::expand_doc(&doc);
|
||||
let ethers_contract = ethers_contract_crate();
|
||||
// use the same derives as for events
|
||||
let derives = util::expand_derives(&self.event_derives);
|
||||
|
||||
let error_name = &error.name;
|
||||
|
||||
Ok(quote! {
|
||||
#abi_signature_doc
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq, #ethers_contract::EthError, #ethers_contract::EthDisplay, #derives)]
|
||||
#[etherror( name = #error_name, abi = #abi_signature )]
|
||||
pub #data_type_definition
|
||||
})
|
||||
}
|
||||
|
||||
/// Expands to the `name : type` pairs of the function's outputs
|
||||
fn expand_error_params(&self, error: &AbiError) -> Result<Vec<(TokenStream, TokenStream)>> {
|
||||
expand_params(&error.inputs, |s| self.internal_structs.get_struct_type(s))
|
||||
}
|
||||
|
||||
/// The name ident of the errors enum
|
||||
fn expand_error_enum_name(&self) -> Ident {
|
||||
util::ident(&format!("{}Errors", self.contract_ident))
|
||||
}
|
||||
|
||||
/// Generate an enum with a variant for each event
|
||||
fn expand_errors_enum(&self) -> TokenStream {
|
||||
let variants = self
|
||||
.abi
|
||||
.errors
|
||||
.values()
|
||||
.flatten()
|
||||
.map(|err| {
|
||||
error_struct_name(&err.name, self.error_aliases.get(&err.abi_signature()).cloned())
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let ethers_core = ethers_core_crate();
|
||||
let ethers_contract = ethers_contract_crate();
|
||||
|
||||
// use the same derives as for events
|
||||
let derives = util::expand_derives(&self.event_derives);
|
||||
let enum_name = self.expand_error_enum_name();
|
||||
|
||||
quote! {
|
||||
#[derive(Debug, Clone, PartialEq, Eq, #ethers_contract::EthAbiType, #derives)]
|
||||
pub enum #enum_name {
|
||||
#(#variants(#variants)),*
|
||||
}
|
||||
|
||||
impl #ethers_core::abi::AbiDecode for #enum_name {
|
||||
fn decode(data: impl AsRef<[u8]>) -> ::std::result::Result<Self, #ethers_core::abi::AbiError> {
|
||||
#(
|
||||
if let Ok(decoded) = <#variants as #ethers_core::abi::AbiDecode>::decode(data.as_ref()) {
|
||||
return Ok(#enum_name::#variants(decoded))
|
||||
}
|
||||
)*
|
||||
Err(#ethers_core::abi::Error::InvalidData.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl #ethers_core::abi::AbiEncode for #enum_name {
|
||||
fn encode(self) -> Vec<u8> {
|
||||
match self {
|
||||
#(
|
||||
#enum_name::#variants(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::#variants(element) => element.fmt(f)
|
||||
),*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#(
|
||||
impl ::std::convert::From<#variants> for #enum_name {
|
||||
fn from(var: #variants) -> Self {
|
||||
#enum_name::#variants(var)
|
||||
}
|
||||
}
|
||||
)*
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Expands an ABI error into an identifier for its event data type.
|
||||
fn error_struct_name(error_name: &str, alias: Option<Ident>) -> Ident {
|
||||
alias.unwrap_or_else(|| util::ident(error_name))
|
||||
}
|
||||
|
||||
/// Returns the alias name for an error
|
||||
pub(crate) fn error_struct_alias(name: &str) -> Ident {
|
||||
util::ident(&name.to_pascal_case())
|
||||
}
|
|
@ -1,18 +1,19 @@
|
|||
use std::collections::{btree_map::Entry, BTreeMap, HashMap};
|
||||
|
||||
use eyre::{Context as _, Result};
|
||||
use inflector::Inflector;
|
||||
use proc_macro2::{Literal, TokenStream};
|
||||
use quote::quote;
|
||||
use syn::Ident;
|
||||
|
||||
use super::{types, util, Context};
|
||||
use crate::contract::common::{
|
||||
expand_data_struct, expand_data_tuple, expand_param_type, expand_params,
|
||||
};
|
||||
use ethers_core::{
|
||||
abi::{Function, FunctionExt, Param, ParamType},
|
||||
macros::{ethers_contract_crate, ethers_core_crate},
|
||||
types::Selector,
|
||||
};
|
||||
|
||||
use super::{types, util, Context};
|
||||
use eyre::{Context as _, Result};
|
||||
use inflector::Inflector;
|
||||
use proc_macro2::{Literal, TokenStream};
|
||||
use quote::quote;
|
||||
use syn::Ident;
|
||||
|
||||
/// The maximum amount of overloaded functions that are attempted to auto aliased with their param
|
||||
/// name. If there is a function that with `NAME_ALIASING_OVERLOADED_FUNCTIONS_CAP` overloads then
|
||||
|
@ -142,7 +143,7 @@ impl Context {
|
|||
}
|
||||
|
||||
/// Expands to the corresponding struct type based on the inputs of the given function
|
||||
fn expand_return_struct(
|
||||
pub fn expand_return_struct(
|
||||
&self,
|
||||
function: &Function,
|
||||
alias: Option<&MethodAlias>,
|
||||
|
@ -296,15 +297,9 @@ impl Context {
|
|||
|
||||
/// Expands to the `name : type` pairs of the function's outputs
|
||||
fn expand_output_params(&self, fun: &Function) -> Result<Vec<(TokenStream, TokenStream)>> {
|
||||
fun.outputs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, param)| {
|
||||
let name = util::expand_input_name(idx, ¶m.name);
|
||||
let ty = self.expand_output_param_type(fun, param, ¶m.kind)?;
|
||||
Ok((name, ty))
|
||||
})
|
||||
.collect()
|
||||
expand_params(&fun.outputs, |s| {
|
||||
self.internal_structs.get_function_output_struct_type(&fun.name, s)
|
||||
})
|
||||
}
|
||||
|
||||
/// Expands to the return type of a function
|
||||
|
@ -388,39 +383,16 @@ impl Context {
|
|||
}
|
||||
}
|
||||
|
||||
/// returns the Tokenstream for the corresponding rust type of the output param
|
||||
/// returns the TokenStream for the corresponding rust type of the output param
|
||||
fn expand_output_param_type(
|
||||
&self,
|
||||
fun: &Function,
|
||||
param: &Param,
|
||||
kind: &ParamType,
|
||||
) -> Result<TokenStream> {
|
||||
match kind {
|
||||
ParamType::Array(ty) => {
|
||||
let ty = self.expand_output_param_type(fun, param, ty)?;
|
||||
Ok(quote! {
|
||||
::std::vec::Vec<#ty>
|
||||
})
|
||||
}
|
||||
ParamType::FixedArray(ty, size) => {
|
||||
let ty = self.expand_output_param_type(fun, param, ty)?;
|
||||
let size = *size;
|
||||
Ok(quote! {[#ty; #size]})
|
||||
}
|
||||
ParamType::Tuple(_) => {
|
||||
let ty = if let Some(rust_struct_name) =
|
||||
param.internal_type.as_ref().and_then(|s| {
|
||||
self.internal_structs.get_function_output_struct_type(&fun.name, s)
|
||||
}) {
|
||||
let ident = util::ident(rust_struct_name);
|
||||
quote! {#ident}
|
||||
} else {
|
||||
types::expand(kind)?
|
||||
};
|
||||
Ok(ty)
|
||||
}
|
||||
_ => types::expand(kind),
|
||||
}
|
||||
expand_param_type(param, kind, |s| {
|
||||
self.internal_structs.get_function_output_struct_type(&fun.name, s)
|
||||
})
|
||||
}
|
||||
|
||||
/// Expands a single function with the given alias
|
||||
|
@ -717,35 +689,6 @@ fn expand_call_struct_variant_name(function: &Function, alias: Option<&MethodAli
|
|||
}
|
||||
}
|
||||
|
||||
/// 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;
|
||||
|
|
|
@ -334,6 +334,11 @@ impl InternalStructs {
|
|||
.map(String::as_str)
|
||||
}
|
||||
|
||||
/// Returns the name of the rust type for the type
|
||||
pub fn get_struct_type(&self, internal_type: &str) -> Option<&str> {
|
||||
self.rust_type_names.get(struct_type_identifier(internal_type)).map(String::as_str)
|
||||
}
|
||||
|
||||
/// Returns the mapping table of abi `internal type identifier -> rust type`
|
||||
pub fn rust_type_names(&self) -> &HashMap<String, String> {
|
||||
&self.rust_type_names
|
||||
|
|
|
@ -72,6 +72,9 @@ pub struct Abigen {
|
|||
|
||||
/// Manually specified event name aliases.
|
||||
event_aliases: HashMap<String, String>,
|
||||
|
||||
/// Manually specified error name aliases.
|
||||
error_aliases: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl Abigen {
|
||||
|
@ -85,6 +88,7 @@ impl Abigen {
|
|||
event_derives: Vec::new(),
|
||||
event_aliases: HashMap::new(),
|
||||
rustfmt: true,
|
||||
error_aliases: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -126,6 +130,17 @@ impl Abigen {
|
|||
self
|
||||
}
|
||||
|
||||
/// Manually adds a solidity error alias to specify what the error struct will be in Rust.
|
||||
#[must_use]
|
||||
pub fn add_error_alias<S1, S2>(mut self, signature: S1, alias: S2) -> Self
|
||||
where
|
||||
S1: Into<String>,
|
||||
S2: Into<String>,
|
||||
{
|
||||
self.error_aliases.insert(signature.into(), alias.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Specify whether or not to format the code using a locally installed copy
|
||||
/// of `rustfmt`.
|
||||
///
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
//! Helper functions for deriving `EthCall`
|
||||
|
||||
use crate::{abi_ty, utils};
|
||||
use crate::{calllike::*, utils, utils::ident};
|
||||
use ethers_core::{
|
||||
abi::{Function, FunctionExt, HumanReadableParser},
|
||||
abi::{FunctionExt, HumanReadableParser},
|
||||
macros::{ethers_contract_crate, ethers_core_crate},
|
||||
};
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{parse::Error, spanned::Spanned as _, AttrStyle, DeriveInput, Lit, Meta, NestedMeta};
|
||||
use syn::{parse::Error, DeriveInput};
|
||||
|
||||
/// Generates the `ethcall` trait support
|
||||
pub(crate) fn derive_eth_call_impl(input: DeriveInput) -> TokenStream {
|
||||
let attributes = match parse_call_attributes(&input) {
|
||||
let attributes = match parse_calllike_attributes(&input, "ethcall") {
|
||||
Ok(attributes) => attributes,
|
||||
Err(errors) => return errors,
|
||||
};
|
||||
|
@ -48,7 +48,7 @@ pub(crate) fn derive_eth_call_impl(input: DeriveInput) -> TokenStream {
|
|||
function.name = function_call_name.clone();
|
||||
let abi = function.abi_signature();
|
||||
let selector = utils::selector(function.selector());
|
||||
let decode_impl = derive_decode_impl_from_function(&function);
|
||||
let decode_impl = derive_decode_impl_from_params(&function.inputs, ident("EthCall"));
|
||||
|
||||
derive_trait_impls(
|
||||
&input,
|
||||
|
@ -59,6 +59,25 @@ pub(crate) fn derive_eth_call_impl(input: DeriveInput) -> TokenStream {
|
|||
)
|
||||
}
|
||||
|
||||
/// Use the `AbiType` trait to determine the correct `ParamType` and signature at runtime
|
||||
fn derive_trait_impls_with_abi_type(
|
||||
input: &DeriveInput,
|
||||
function_call_name: &str,
|
||||
abi_signature: Option<&str>,
|
||||
) -> Result<TokenStream, Error> {
|
||||
let abi_signature = if let Some(abi) = abi_signature {
|
||||
quote! {#abi}
|
||||
} else {
|
||||
utils::derive_abi_signature_with_abi_type(input, function_call_name, "EthCall")?
|
||||
};
|
||||
|
||||
let abi_signature = quote! {
|
||||
#abi_signature.into()
|
||||
};
|
||||
let decode_impl = derive_decode_impl_with_abi_type(input, ident("EthCall"))?;
|
||||
Ok(derive_trait_impls(input, function_call_name, abi_signature, None, decode_impl))
|
||||
}
|
||||
|
||||
/// Generates the EthCall implementation
|
||||
pub fn derive_trait_impls(
|
||||
input: &DeriveInput,
|
||||
|
@ -93,167 +112,11 @@ pub fn derive_trait_impls(
|
|||
#abi_signature
|
||||
}
|
||||
}
|
||||
|
||||
impl #core_crate::abi::AbiDecode for #struct_name {
|
||||
fn decode(bytes: impl AsRef<[u8]>) -> ::std::result::Result<Self, #core_crate::abi::AbiError> {
|
||||
#decode_impl
|
||||
}
|
||||
}
|
||||
|
||||
impl #core_crate::abi::AbiEncode for #struct_name {
|
||||
fn encode(self) -> ::std::vec::Vec<u8> {
|
||||
let tokens = #core_crate::abi::Tokenize::into_tokens(self);
|
||||
let selector = <Self as #contract_crate::EthCall>::selector();
|
||||
let encoded = #core_crate::abi::encode(&tokens);
|
||||
selector
|
||||
.iter()
|
||||
.copied()
|
||||
.chain(encoded.into_iter())
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
let tokenize_impl = abi_ty::derive_tokenizeable_impl(input);
|
||||
let codec_impl = derive_codec_impls(input, decode_impl, ident("EthCall"));
|
||||
|
||||
quote! {
|
||||
#tokenize_impl
|
||||
#ethcall_impl
|
||||
#codec_impl
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates the decode implementation based on the function's input types
|
||||
fn derive_decode_impl_from_function(function: &Function) -> TokenStream {
|
||||
let datatypes = function.inputs.iter().map(|input| utils::param_type_quote(&input.kind));
|
||||
let datatypes_array = quote! {[#( #datatypes ),*]};
|
||||
derive_decode_impl(datatypes_array)
|
||||
}
|
||||
|
||||
/// Generates the decode implementation based on the function's runtime `AbiType` impl
|
||||
fn derive_decode_impl_with_abi_type(input: &DeriveInput) -> Result<TokenStream, Error> {
|
||||
let datatypes_array = utils::derive_abi_parameters_array(input, "EthCall")?;
|
||||
Ok(derive_decode_impl(datatypes_array))
|
||||
}
|
||||
|
||||
fn derive_decode_impl(datatypes_array: TokenStream) -> TokenStream {
|
||||
let core_crate = ethers_core_crate();
|
||||
let contract_crate = ethers_contract_crate();
|
||||
let data_types_init = quote! {let data_types = #datatypes_array;};
|
||||
|
||||
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))?)
|
||||
}
|
||||
}
|
||||
|
||||
/// Use the `AbiType` trait to determine the correct `ParamType` and signature at runtime
|
||||
fn derive_trait_impls_with_abi_type(
|
||||
input: &DeriveInput,
|
||||
function_call_name: &str,
|
||||
abi_signature: Option<&str>,
|
||||
) -> Result<TokenStream, Error> {
|
||||
let abi_signature = if let Some(abi) = abi_signature {
|
||||
quote! {#abi}
|
||||
} else {
|
||||
utils::derive_abi_signature_with_abi_type(input, function_call_name, "EthCall")?
|
||||
};
|
||||
|
||||
let abi_signature = quote! {
|
||||
#abi_signature.into()
|
||||
};
|
||||
let decode_impl = derive_decode_impl_with_abi_type(input)?;
|
||||
Ok(derive_trait_impls(input, function_call_name, abi_signature, None, decode_impl))
|
||||
}
|
||||
|
||||
/// 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)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,174 @@
|
|||
//! Code used by both `call` and `error`
|
||||
|
||||
use crate::{abi_ty, utils};
|
||||
use ethers_core::{
|
||||
abi::Param,
|
||||
macros::{ethers_contract_crate, ethers_core_crate},
|
||||
};
|
||||
use proc_macro2::{Ident, Span, TokenStream};
|
||||
use quote::quote;
|
||||
use syn::{parse::Error, spanned::Spanned as _, AttrStyle, DeriveInput, Lit, Meta, NestedMeta};
|
||||
|
||||
/// All the attributes the `EthCall`/`EthError` macro supports
|
||||
#[derive(Default)]
|
||||
pub struct EthCalllikeAttributes {
|
||||
pub name: Option<(String, Span)>,
|
||||
pub abi: Option<(String, Span)>,
|
||||
}
|
||||
|
||||
/// extracts the attributes from the struct annotated with the given attribute
|
||||
pub fn parse_calllike_attributes(
|
||||
input: &DeriveInput,
|
||||
attr_name: &str,
|
||||
) -> Result<EthCalllikeAttributes, TokenStream> {
|
||||
let mut result = EthCalllikeAttributes::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(attr_name) {
|
||||
for n in meta.nested.iter() {
|
||||
if let NestedMeta::Meta(meta) = n {
|
||||
match meta {
|
||||
Meta::Path(path) => {
|
||||
return Err(Error::new(
|
||||
path.span(),
|
||||
format!("unrecognized {} parameter", attr_name),
|
||||
)
|
||||
.to_compile_error())
|
||||
}
|
||||
Meta::List(meta) => {
|
||||
return Err(Error::new(
|
||||
meta.path.span(),
|
||||
format!("unrecognized {} parameter", attr_name),
|
||||
)
|
||||
.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(),
|
||||
format!("unrecognized {} parameter", attr_name),
|
||||
)
|
||||
.to_compile_error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Generates the decode implementation based on the type's runtime `AbiType` impl
|
||||
pub fn derive_decode_impl_with_abi_type(
|
||||
input: &DeriveInput,
|
||||
trait_ident: Ident,
|
||||
) -> Result<TokenStream, Error> {
|
||||
let datatypes_array = utils::derive_abi_parameters_array(input, &trait_ident.to_string())?;
|
||||
Ok(derive_decode_impl(datatypes_array, trait_ident))
|
||||
}
|
||||
|
||||
/// Generates the decode implementation based on the params
|
||||
pub fn derive_decode_impl_from_params(params: &[Param], trait_ident: Ident) -> TokenStream {
|
||||
let datatypes = params.iter().map(|input| utils::param_type_quote(&input.kind));
|
||||
let datatypes_array = quote! {[#( #datatypes ),*]};
|
||||
derive_decode_impl(datatypes_array, trait_ident)
|
||||
}
|
||||
|
||||
pub fn derive_decode_impl(datatypes_array: TokenStream, trait_ident: Ident) -> TokenStream {
|
||||
let core_crate = ethers_core_crate();
|
||||
let contract_crate = ethers_contract_crate();
|
||||
let data_types_init = quote! {let data_types = #datatypes_array;};
|
||||
|
||||
quote! {
|
||||
let bytes = bytes.as_ref();
|
||||
if bytes.len() < 4 || bytes[..4] != <Self as #contract_crate::#trait_ident>::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))?)
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates the Codec implementation
|
||||
pub fn derive_codec_impls(
|
||||
input: &DeriveInput,
|
||||
decode_impl: TokenStream,
|
||||
trait_ident: Ident,
|
||||
) -> TokenStream {
|
||||
// the ethers crates to use
|
||||
let core_crate = ethers_core_crate();
|
||||
let contract_crate = ethers_contract_crate();
|
||||
let struct_name = &input.ident;
|
||||
|
||||
let codec_impl = quote! {
|
||||
|
||||
impl #core_crate::abi::AbiDecode for #struct_name {
|
||||
fn decode(bytes: impl AsRef<[u8]>) -> ::std::result::Result<Self, #core_crate::abi::AbiError> {
|
||||
#decode_impl
|
||||
}
|
||||
}
|
||||
|
||||
impl #core_crate::abi::AbiEncode for #struct_name {
|
||||
fn encode(self) -> ::std::vec::Vec<u8> {
|
||||
let tokens = #core_crate::abi::Tokenize::into_tokens(self);
|
||||
let selector = <Self as #contract_crate::#trait_ident>::selector();
|
||||
let encoded = #core_crate::abi::encode(&tokens);
|
||||
selector
|
||||
.iter()
|
||||
.copied()
|
||||
.chain(encoded.into_iter())
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
let tokenize_impl = abi_ty::derive_tokenizeable_impl(input);
|
||||
|
||||
quote! {
|
||||
#tokenize_impl
|
||||
#codec_impl
|
||||
}
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
//! Helper functions for deriving `EthError`
|
||||
|
||||
use crate::{calllike::*, utils, utils::ident};
|
||||
use ethers_core::{
|
||||
abi::{ErrorExt, HumanReadableParser},
|
||||
macros::{ethers_contract_crate, ethers_core_crate},
|
||||
};
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{parse::Error, DeriveInput};
|
||||
|
||||
/// Generates the `EthError` trait support
|
||||
pub(crate) fn derive_eth_error_impl(input: DeriveInput) -> TokenStream {
|
||||
let attributes = match parse_calllike_attributes(&input, "etherror") {
|
||||
Ok(attributes) => attributes,
|
||||
Err(errors) => return errors,
|
||||
};
|
||||
|
||||
let error_name = attributes.name.map(|(s, _)| s).unwrap_or_else(|| input.ident.to_string());
|
||||
|
||||
let mut error = if let Some((src, span)) = attributes.abi {
|
||||
let raw_function_sig = src.trim_start_matches("error ").trim_start();
|
||||
// try to parse as solidity error
|
||||
if let Ok(fun) = HumanReadableParser::parse_error(&src) {
|
||||
fun
|
||||
} else {
|
||||
// try to determine the abi by using its fields at runtime
|
||||
return match derive_trait_impls_with_abi_type(
|
||||
&input,
|
||||
&error_name,
|
||||
Some(raw_function_sig),
|
||||
) {
|
||||
Ok(derived) => derived,
|
||||
Err(err) => {
|
||||
Error::new(span, format!("Unable to determine ABI for `{}` : {}", src, err))
|
||||
.to_compile_error()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// try to determine the abi by using its fields at runtime
|
||||
return match derive_trait_impls_with_abi_type(&input, &error_name, None) {
|
||||
Ok(derived) => derived,
|
||||
Err(err) => err.to_compile_error(),
|
||||
}
|
||||
};
|
||||
error.name = error_name.clone();
|
||||
let abi = error.abi_signature();
|
||||
let selector = utils::selector(error.selector());
|
||||
let decode_impl = derive_decode_impl_from_params(&error.inputs, ident("EthError"));
|
||||
|
||||
derive_trait_impls(&input, &error_name, quote! {#abi.into()}, Some(selector), decode_impl)
|
||||
}
|
||||
|
||||
/// Use the `AbiType` trait to determine the correct `ParamType` and signature at runtime
|
||||
fn derive_trait_impls_with_abi_type(
|
||||
input: &DeriveInput,
|
||||
function_call_name: &str,
|
||||
abi_signature: Option<&str>,
|
||||
) -> Result<TokenStream, Error> {
|
||||
let abi_signature = if let Some(abi) = abi_signature {
|
||||
quote! {#abi}
|
||||
} else {
|
||||
utils::derive_abi_signature_with_abi_type(input, function_call_name, "EthError")?
|
||||
};
|
||||
|
||||
let abi_signature = quote! {
|
||||
#abi_signature.into()
|
||||
};
|
||||
let decode_impl = derive_decode_impl_with_abi_type(input, ident("EthError"))?;
|
||||
Ok(derive_trait_impls(input, function_call_name, abi_signature, None, decode_impl))
|
||||
}
|
||||
|
||||
/// Generates the EthError implementation
|
||||
pub fn derive_trait_impls(
|
||||
input: &DeriveInput,
|
||||
function_call_name: &str,
|
||||
abi_signature: TokenStream,
|
||||
selector: Option<TokenStream>,
|
||||
decode_impl: TokenStream,
|
||||
) -> TokenStream {
|
||||
// the ethers crates to use
|
||||
let core_crate = ethers_core_crate();
|
||||
let contract_crate = ethers_contract_crate();
|
||||
let struct_name = &input.ident;
|
||||
|
||||
let selector = selector.unwrap_or_else(|| {
|
||||
quote! {
|
||||
#core_crate::utils::id(Self::abi_signature())
|
||||
}
|
||||
});
|
||||
|
||||
let etherror_impl = quote! {
|
||||
impl #contract_crate::EthError for #struct_name {
|
||||
|
||||
fn error_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_signature
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
let codec_impl = derive_codec_impls(input, decode_impl, ident("EthError"));
|
||||
|
||||
quote! {
|
||||
#etherror_impl
|
||||
#codec_impl
|
||||
}
|
||||
}
|
|
@ -11,8 +11,10 @@ use abigen::Contracts;
|
|||
pub(crate) mod abi_ty;
|
||||
mod abigen;
|
||||
mod call;
|
||||
pub(crate) mod calllike;
|
||||
mod codec;
|
||||
mod display;
|
||||
mod error;
|
||||
mod event;
|
||||
mod spanned;
|
||||
pub(crate) mod utils;
|
||||
|
@ -281,3 +283,40 @@ pub fn derive_abi_call(input: TokenStream) -> TokenStream {
|
|||
let input = parse_macro_input!(input as DeriveInput);
|
||||
TokenStream::from(call::derive_eth_call_impl(input))
|
||||
}
|
||||
|
||||
/// Derives the `EthError` and `Tokenizeable` trait for the labeled type.
|
||||
///
|
||||
/// Additional arguments can be specified using the `#[etherror(...)]`
|
||||
/// 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::EthError;
|
||||
///
|
||||
/// #[derive(Debug, Clone, EthError)]
|
||||
/// #[etherror(name ="my_error")]
|
||||
/// struct MyError {
|
||||
/// addr: Address,
|
||||
/// old_value: String,
|
||||
/// new_value: String,
|
||||
/// }
|
||||
/// assert_eq!(
|
||||
/// MyCall::abi_signature().as_ref(),
|
||||
/// "my_error(address,string,string)"
|
||||
/// );
|
||||
/// ```
|
||||
#[proc_macro_derive(EthError, attributes(etherror))]
|
||||
pub fn derive_abi_error(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
TokenStream::from(error::derive_eth_error_impl(input))
|
||||
}
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
use ethers_core::{abi::ParamType, macros::ethers_core_crate, types::Selector};
|
||||
use proc_macro2::Literal;
|
||||
use proc_macro2::{Ident, Literal, Span};
|
||||
use quote::{quote, quote_spanned};
|
||||
use syn::{
|
||||
parse::Error, spanned::Spanned as _, Data, DeriveInput, Expr, Fields, GenericArgument, Lit,
|
||||
PathArguments, Type,
|
||||
};
|
||||
|
||||
pub fn ident(name: &str) -> Ident {
|
||||
Ident::new(name, Span::call_site())
|
||||
}
|
||||
|
||||
pub fn signature(hash: &[u8]) -> proc_macro2::TokenStream {
|
||||
let core_crate = ethers_core_crate();
|
||||
let bytes = hash.iter().copied().map(Literal::u8_unsuffixed);
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
use ethers_core::{
|
||||
abi::{AbiDecode, AbiEncode, Tokenizable},
|
||||
types::Selector,
|
||||
utils::id,
|
||||
};
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// A helper trait for types that represents a custom error type
|
||||
pub trait EthError: Tokenizable + AbiDecode + AbiEncode + Send + Sync {
|
||||
/// The name of the error
|
||||
fn error_name() -> Cow<'static, str>;
|
||||
|
||||
/// Retrieves the ABI signature for the error
|
||||
fn abi_signature() -> Cow<'static, str>;
|
||||
|
||||
/// The selector of the error
|
||||
fn selector() -> Selector {
|
||||
id(Self::abi_signature())
|
||||
}
|
||||
}
|
|
@ -11,6 +11,9 @@ pub use base::{decode_function_data, encode_function_data, AbiError, BaseContrac
|
|||
mod call;
|
||||
pub use call::{ContractError, EthCall};
|
||||
|
||||
mod error;
|
||||
pub use error::EthError;
|
||||
|
||||
mod factory;
|
||||
pub use factory::{ContractDeployer, ContractFactory};
|
||||
|
||||
|
@ -42,7 +45,9 @@ pub use ethers_contract_abigen::{Abigen, MultiAbigen};
|
|||
|
||||
#[cfg(any(test, feature = "abigen"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "abigen")))]
|
||||
pub use ethers_contract_derive::{abigen, EthAbiCodec, EthAbiType, EthCall, EthDisplay, EthEvent};
|
||||
pub use ethers_contract_derive::{
|
||||
abigen, EthAbiCodec, EthAbiType, EthCall, EthDisplay, EthError, EthEvent,
|
||||
};
|
||||
|
||||
// Hide the Lazy re-export, it's just for convenience
|
||||
#[doc(hidden)]
|
||||
|
|
|
@ -629,6 +629,18 @@ fn can_gen_seaport() {
|
|||
"fulfillAdvancedOrder(((address,address,(uint8,address,uint256,uint256,uint256)[],(uint8,address,uint256,uint256,uint256,address)[],uint8,uint256,uint256,bytes32,uint256,bytes32,uint256),uint120,uint120,bytes,bytes),(uint256,uint8,uint256,uint256,bytes32[])[],bytes32,address)"
|
||||
);
|
||||
assert_eq!(hex::encode(FulfillAdvancedOrderCall::selector()), "e7acab24");
|
||||
|
||||
assert_codec::<SeaportErrors>();
|
||||
let err = SeaportErrors::BadContractSignature(BadContractSignature::default());
|
||||
|
||||
let encoded = err.clone().encode();
|
||||
assert_eq!(err, SeaportErrors::decode(encoded).unwrap());
|
||||
|
||||
let err = SeaportErrors::ConsiderationNotMet(ConsiderationNotMet {
|
||||
order_index: U256::zero(),
|
||||
consideration_index: U256::zero(),
|
||||
shortfall_amount: U256::zero(),
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use ethers_contract::{
|
||||
abigen, EthAbiCodec, EthAbiType, EthCall, EthDisplay, EthEvent, EthLogDecode,
|
||||
abigen, EthAbiCodec, EthAbiType, EthCall, EthDisplay, EthError, EthEvent, EthLogDecode,
|
||||
};
|
||||
use ethers_core::{
|
||||
abi::{AbiDecode, AbiEncode, RawLog, Tokenizable},
|
||||
|
@ -8,6 +8,7 @@ use ethers_core::{
|
|||
|
||||
fn assert_tokenizeable<T: Tokenizable>() {}
|
||||
fn assert_ethcall<T: EthCall>() {}
|
||||
fn assert_etherror<T: EthError>() {}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, EthAbiType)]
|
||||
struct ValueChanged {
|
||||
|
@ -618,3 +619,31 @@ fn can_use_result_name() {
|
|||
|
||||
let _call = ResultCall { result: Result { result: U256::zero() } };
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_derive_etherror() {
|
||||
#[derive(Debug, PartialEq, Eq, EthError)]
|
||||
#[etherror(name = "MyError", abi = "MyError(address,address,string)")]
|
||||
struct MyError {
|
||||
old_author: Address,
|
||||
addr: Address,
|
||||
new_value: String,
|
||||
}
|
||||
|
||||
assert_eq!(MyError::abi_signature().as_ref(), "MyError(address,address,string)");
|
||||
|
||||
assert_tokenizeable::<MyError>();
|
||||
assert_etherror::<MyError>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_use_human_readable_error() {
|
||||
abigen!(
|
||||
ErrContract,
|
||||
r#"[
|
||||
error MyError(address,address,string)
|
||||
]"#,
|
||||
);
|
||||
|
||||
assert_etherror::<MyError>();
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
use ethabi::{Constructor, Event, EventParam, Function, Param, ParamType, StateMutability};
|
||||
use ethabi::{
|
||||
AbiError, Constructor, Event, EventParam, Function, Param, ParamType, StateMutability,
|
||||
};
|
||||
use std::{fmt, iter::Peekable, str::CharIndices};
|
||||
use unicode_xid::UnicodeXID;
|
||||
|
||||
|
@ -320,6 +322,18 @@ impl<'input> HumanReadableParser<'input> {
|
|||
Self::new(input).take_function()
|
||||
}
|
||||
|
||||
/// Parses a [Function] from a human readable form
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use ethers_core::abi::HumanReadableParser;
|
||||
/// let err = HumanReadableParser::parse_error("error MyError(address author, string oldValue, string newValue)").unwrap();
|
||||
/// ```
|
||||
pub fn parse_error(input: &'input str) -> Result<AbiError, LexerError> {
|
||||
Self::new(input).take_error()
|
||||
}
|
||||
|
||||
/// Parses a [Constructor] from a human readable form
|
||||
///
|
||||
/// # Example
|
||||
|
@ -344,6 +358,15 @@ impl<'input> HumanReadableParser<'input> {
|
|||
Self::new(input).take_event()
|
||||
}
|
||||
|
||||
/// Returns the next `Error` and consumes the underlying tokens
|
||||
pub fn take_error(&mut self) -> Result<AbiError, LexerError> {
|
||||
let name = self.take_identifier(Token::Error)?;
|
||||
self.take_open_parenthesis()?;
|
||||
let inputs = self.take_function_params()?;
|
||||
self.take_close_parenthesis()?;
|
||||
Ok(AbiError { name: name.to_string(), inputs })
|
||||
}
|
||||
|
||||
/// Returns the next `Constructor` and consumes the underlying tokens
|
||||
pub fn take_constructor(&mut self) -> Result<Constructor, LexerError> {
|
||||
self.take_next_exact(Token::Constructor)?;
|
||||
|
@ -352,6 +375,7 @@ impl<'input> HumanReadableParser<'input> {
|
|||
self.take_close_parenthesis()?;
|
||||
Ok(Constructor { inputs })
|
||||
}
|
||||
|
||||
/// Returns the next `Function` and consumes the underlying tokens
|
||||
pub fn take_function(&mut self) -> Result<Function, LexerError> {
|
||||
let name = self.take_identifier(Token::Function)?;
|
||||
|
@ -833,6 +857,31 @@ pub enum DataLocation {
|
|||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse_error() {
|
||||
let f = AbiError {
|
||||
name: "MyError".to_string(),
|
||||
inputs: vec![
|
||||
Param { name: "author".to_string(), kind: ParamType::Address, internal_type: None },
|
||||
Param {
|
||||
name: "oldValue".to_string(),
|
||||
kind: ParamType::String,
|
||||
internal_type: None,
|
||||
},
|
||||
Param {
|
||||
name: "newValue".to_string(),
|
||||
kind: ParamType::String,
|
||||
internal_type: None,
|
||||
},
|
||||
],
|
||||
};
|
||||
let parsed = HumanReadableParser::parse_error(
|
||||
"error MyError(address author, string oldValue, string newValue)",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(f, parsed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_constructor() {
|
||||
let f = Constructor {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use ethabi::AbiError;
|
||||
use std::collections::{BTreeMap, HashMap, VecDeque};
|
||||
|
||||
use crate::abi::{
|
||||
|
@ -82,6 +83,17 @@ impl AbiParser {
|
|||
if line.starts_with("event") {
|
||||
let event = self.parse_event(line)?;
|
||||
abi.events.entry(event.name.clone()).or_default().push(event);
|
||||
} else if let Some(err) = line.strip_prefix("error") {
|
||||
// an error is essentially a function without outputs, so we parse as function here
|
||||
let function = match self.parse_function(err) {
|
||||
Ok(function) => function,
|
||||
Err(_) => bail!("Illegal abi `{}`, expected error", line),
|
||||
};
|
||||
if !function.outputs.is_empty() {
|
||||
bail!("Illegal abi `{}`, expected error", line);
|
||||
}
|
||||
let error = AbiError { name: function.name, inputs: function.inputs };
|
||||
abi.errors.entry(error.name.clone()).or_default().push(error);
|
||||
} else if line.starts_with("constructor") {
|
||||
let inputs = self
|
||||
.constructor_inputs(line)?
|
||||
|
@ -103,7 +115,7 @@ impl AbiParser {
|
|||
// function may have shorthand declaration, so it won't start with "function"
|
||||
let function = match self.parse_function(line) {
|
||||
Ok(function) => function,
|
||||
Err(_) => bail!("Illegal abi `{}`", line),
|
||||
Err(_) => bail!("Illegal abi `{}`, expected function", line),
|
||||
};
|
||||
abi.functions.entry(function.name.clone()).or_default().push(function);
|
||||
}
|
||||
|
|
|
@ -70,6 +70,29 @@ impl EventExt for Event {
|
|||
}
|
||||
}
|
||||
|
||||
/// Extension trait for `ethabi::AbiError`.
|
||||
pub trait ErrorExt {
|
||||
/// Compute the method signature in the standard ABI format.
|
||||
fn abi_signature(&self) -> String;
|
||||
|
||||
/// Compute the Keccak256 error selector used by contract ABIs.
|
||||
fn selector(&self) -> Selector;
|
||||
}
|
||||
|
||||
impl ErrorExt for ethabi::AbiError {
|
||||
fn abi_signature(&self) -> String {
|
||||
if self.inputs.is_empty() {
|
||||
return format!("{}()", self.name)
|
||||
}
|
||||
let inputs = self.inputs.iter().map(|p| p.kind.to_string()).collect::<Vec<_>>().join(",");
|
||||
format!("{}({})", self.name, inputs)
|
||||
}
|
||||
|
||||
fn selector(&self) -> Selector {
|
||||
id(self.abi_signature())
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait for types that can be represented in the ethereum ABI.
|
||||
pub trait AbiType {
|
||||
/// The native ABI type this type represents.
|
||||
|
|
Loading…
Reference in New Issue