feat: add EthError trait and derive (#1549)

* feat: add EthError trait and derive

* update changelog
This commit is contained in:
Matthias Seitz 2022-08-02 20:03:52 +02:00 committed by GitHub
parent 54bd5253c1
commit 27a184db45
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 892 additions and 266 deletions

View File

@ -87,6 +87,7 @@
### Unreleased ### Unreleased
- generate error bindings for custom errors [#1549](https://github.com/gakonst/ethers-rs/pull/1549)
- Support overloaded events - Support overloaded events
[#1233](https://github.com/gakonst/ethers-rs/pull/1233) [#1233](https://github.com/gakonst/ethers-rs/pull/1233)
- Relax Clone requirements when Arc<Middleware> is used - Relax Clone requirements when Arc<Middleware> is used

View File

@ -1,22 +1,22 @@
#![deny(missing_docs)] #![deny(missing_docs)]
mod common; mod common;
mod errors;
mod events; mod events;
mod methods; mod methods;
mod structs; mod structs;
mod types; mod types;
use super::{util, Abigen}; use super::{util, Abigen};
use crate::contract::structs::InternalStructs; use crate::{
contract::{methods::MethodAlias, structs::InternalStructs},
rawabi::JsonAbi,
};
use ethers_core::{ use ethers_core::{
abi::{Abi, AbiParser}, abi::{Abi, AbiParser, ErrorExt, EventExt},
macros::{ethers_contract_crate, ethers_core_crate, ethers_providers_crate}, macros::{ethers_contract_crate, ethers_core_crate, ethers_providers_crate},
types::Bytes,
}; };
use eyre::{eyre, Context as _, Result}; 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 proc_macro2::{Ident, Literal, TokenStream};
use quote::quote; use quote::quote;
use serde::Deserialize; use serde::Deserialize;
@ -34,6 +34,8 @@ pub struct ExpandedContract {
pub contract: TokenStream, pub contract: TokenStream,
/// All event impls of the contract /// All event impls of the contract
pub events: TokenStream, pub events: TokenStream,
/// All error impls of the contract
pub errors: TokenStream,
/// All contract call struct related types /// All contract call struct related types
pub call_structs: TokenStream, pub call_structs: TokenStream,
/// The contract's internal structs /// The contract's internal structs
@ -43,8 +45,15 @@ pub struct ExpandedContract {
impl ExpandedContract { impl ExpandedContract {
/// Merges everything into a single module /// Merges everything into a single module
pub fn into_tokens(self) -> TokenStream { pub fn into_tokens(self) -> TokenStream {
let ExpandedContract { module, imports, contract, events, call_structs, abi_structs } = let ExpandedContract {
self; module,
imports,
contract,
events,
call_structs,
abi_structs,
errors,
} = self;
quote! { quote! {
// export all the created data types // export all the created data types
pub use #module::*; pub use #module::*;
@ -53,6 +62,7 @@ impl ExpandedContract {
pub mod #module { pub mod #module {
#imports #imports
#contract #contract
#errors
#events #events
#call_structs #call_structs
#abi_structs #abi_structs
@ -87,6 +97,9 @@ pub struct Context {
/// Manually specified method aliases. /// Manually specified method aliases.
method_aliases: BTreeMap<String, MethodAlias>, method_aliases: BTreeMap<String, MethodAlias>,
/// Manually specified method aliases.
error_aliases: BTreeMap<String, Ident>,
/// Derives added to event structs and enums. /// Derives added to event structs and enums.
event_derives: Vec<Path>, event_derives: Vec<Path>,
@ -125,6 +138,9 @@ impl Context {
// 6. Declare the structs parsed from the human readable abi // 6. Declare the structs parsed from the human readable abi
let abi_structs_decl = self.abi_structs()?; 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_core = ethers_core_crate();
let ethers_contract = ethers_contract_crate(); let ethers_contract = ethers_contract_crate();
let ethers_providers = ethers_providers_crate(); let ethers_providers = ethers_providers_crate();
@ -145,6 +161,7 @@ impl Context {
#contract_methods #contract_methods
#contract_events #contract_events
} }
impl<M : #ethers_providers::Middleware> From<#ethers_contract::Contract<M>> for #name<M> { impl<M : #ethers_providers::Middleware> From<#ethers_contract::Contract<M>> for #name<M> {
@ -159,6 +176,7 @@ impl Context {
imports, imports,
contract, contract,
events: events_decl, events: events_decl,
errors: errors_decl,
call_structs, call_structs,
abi_structs: abi_structs_decl, abi_structs: abi_structs_decl,
}) })
@ -226,20 +244,30 @@ impl Context {
event_aliases.insert(signature, alias); 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 // numerate them
for events in abi.events.values() { for events in abi.events.values() {
let not_aliased = insert_alias_names(
events.iter().filter(|ev| !event_aliases.contains_key(&ev.abi_signature())); &mut event_aliases,
if not_aliased.clone().count() > 1 { events.iter().map(|e| (e.abi_signature(), e.name.as_str())),
let mut aliases = Vec::new(); events::event_struct_alias,
// 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);
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 let event_derives = args
@ -259,6 +287,7 @@ impl Context {
contract_name: args.contract_name, contract_name: args.contract_name,
contract_bytecode, contract_bytecode,
method_aliases, method_aliases,
error_aliases: Default::default(),
event_derives, event_derives,
event_aliases, 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 /// 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)> { fn parse_abi(abi_str: &str) -> Result<(Abi, bool, AbiParser)> {
let mut abi_parser = AbiParser::default(); let mut abi_parser = AbiParser::default();

View File

@ -1,9 +1,67 @@
use super::{util, Context}; 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 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, &param.name);
let ty = expand_param_type(param, &param.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 { 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)); 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, )* } }
}

View File

@ -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())
}

View File

@ -1,18 +1,19 @@
use std::collections::{btree_map::Entry, BTreeMap, HashMap}; use std::collections::{btree_map::Entry, BTreeMap, HashMap};
use eyre::{Context as _, Result}; use super::{types, util, Context};
use inflector::Inflector; use crate::contract::common::{
use proc_macro2::{Literal, TokenStream}; expand_data_struct, expand_data_tuple, expand_param_type, expand_params,
use quote::quote; };
use syn::Ident;
use ethers_core::{ use ethers_core::{
abi::{Function, FunctionExt, Param, ParamType}, abi::{Function, FunctionExt, Param, ParamType},
macros::{ethers_contract_crate, ethers_core_crate}, macros::{ethers_contract_crate, ethers_core_crate},
types::Selector, types::Selector,
}; };
use eyre::{Context as _, Result};
use super::{types, util, Context}; 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 /// 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 /// 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 /// Expands to the corresponding struct type based on the inputs of the given function
fn expand_return_struct( pub fn expand_return_struct(
&self, &self,
function: &Function, function: &Function,
alias: Option<&MethodAlias>, alias: Option<&MethodAlias>,
@ -296,15 +297,9 @@ impl Context {
/// Expands to the `name : type` pairs of the function's outputs /// Expands to the `name : type` pairs of the function's outputs
fn expand_output_params(&self, fun: &Function) -> Result<Vec<(TokenStream, TokenStream)>> { fn expand_output_params(&self, fun: &Function) -> Result<Vec<(TokenStream, TokenStream)>> {
fun.outputs expand_params(&fun.outputs, |s| {
.iter() self.internal_structs.get_function_output_struct_type(&fun.name, s)
.enumerate()
.map(|(idx, param)| {
let name = util::expand_input_name(idx, &param.name);
let ty = self.expand_output_param_type(fun, param, &param.kind)?;
Ok((name, ty))
}) })
.collect()
} }
/// Expands to the return type of a function /// 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( fn expand_output_param_type(
&self, &self,
fun: &Function, fun: &Function,
param: &Param, param: &Param,
kind: &ParamType, kind: &ParamType,
) -> Result<TokenStream> { ) -> Result<TokenStream> {
match kind { expand_param_type(param, kind, |s| {
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) 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),
}
} }
/// Expands a single function with the given alias /// 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)] #[cfg(test)]
mod tests { mod tests {
use ethers_core::abi::ParamType; use ethers_core::abi::ParamType;

View File

@ -334,6 +334,11 @@ impl InternalStructs {
.map(String::as_str) .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` /// Returns the mapping table of abi `internal type identifier -> rust type`
pub fn rust_type_names(&self) -> &HashMap<String, String> { pub fn rust_type_names(&self) -> &HashMap<String, String> {
&self.rust_type_names &self.rust_type_names

View File

@ -72,6 +72,9 @@ pub struct Abigen {
/// Manually specified event name aliases. /// Manually specified event name aliases.
event_aliases: HashMap<String, String>, event_aliases: HashMap<String, String>,
/// Manually specified error name aliases.
error_aliases: HashMap<String, String>,
} }
impl Abigen { impl Abigen {
@ -85,6 +88,7 @@ impl Abigen {
event_derives: Vec::new(), event_derives: Vec::new(),
event_aliases: HashMap::new(), event_aliases: HashMap::new(),
rustfmt: true, rustfmt: true,
error_aliases: Default::default(),
}) })
} }
@ -126,6 +130,17 @@ impl Abigen {
self 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 /// Specify whether or not to format the code using a locally installed copy
/// of `rustfmt`. /// of `rustfmt`.
/// ///

View File

@ -1,17 +1,17 @@
//! Helper functions for deriving `EthCall` //! Helper functions for deriving `EthCall`
use crate::{abi_ty, utils}; use crate::{calllike::*, utils, utils::ident};
use ethers_core::{ use ethers_core::{
abi::{Function, FunctionExt, HumanReadableParser}, abi::{FunctionExt, HumanReadableParser},
macros::{ethers_contract_crate, ethers_core_crate}, macros::{ethers_contract_crate, ethers_core_crate},
}; };
use proc_macro2::{Span, TokenStream}; use proc_macro2::TokenStream;
use quote::quote; 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 /// Generates the `ethcall` trait support
pub(crate) fn derive_eth_call_impl(input: DeriveInput) -> TokenStream { 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, Ok(attributes) => attributes,
Err(errors) => return errors, Err(errors) => return errors,
}; };
@ -48,7 +48,7 @@ pub(crate) fn derive_eth_call_impl(input: DeriveInput) -> TokenStream {
function.name = function_call_name.clone(); function.name = function_call_name.clone();
let abi = function.abi_signature(); let abi = function.abi_signature();
let selector = utils::selector(function.selector()); 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( derive_trait_impls(
&input, &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 /// Generates the EthCall implementation
pub fn derive_trait_impls( pub fn derive_trait_impls(
input: &DeriveInput, input: &DeriveInput,
@ -93,167 +112,11 @@ pub fn derive_trait_impls(
#abi_signature #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! { quote! {
#tokenize_impl
#ethcall_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)
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -11,8 +11,10 @@ use abigen::Contracts;
pub(crate) mod abi_ty; pub(crate) mod abi_ty;
mod abigen; mod abigen;
mod call; mod call;
pub(crate) mod calllike;
mod codec; mod codec;
mod display; mod display;
mod error;
mod event; mod event;
mod spanned; mod spanned;
pub(crate) mod utils; pub(crate) mod utils;
@ -281,3 +283,40 @@ pub fn derive_abi_call(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput); let input = parse_macro_input!(input as DeriveInput);
TokenStream::from(call::derive_eth_call_impl(input)) 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))
}

View File

@ -1,11 +1,15 @@
use ethers_core::{abi::ParamType, macros::ethers_core_crate, types::Selector}; 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 quote::{quote, quote_spanned};
use syn::{ use syn::{
parse::Error, spanned::Spanned as _, Data, DeriveInput, Expr, Fields, GenericArgument, Lit, parse::Error, spanned::Spanned as _, Data, DeriveInput, Expr, Fields, GenericArgument, Lit,
PathArguments, Type, PathArguments, Type,
}; };
pub fn ident(name: &str) -> Ident {
Ident::new(name, Span::call_site())
}
pub fn signature(hash: &[u8]) -> proc_macro2::TokenStream { pub fn signature(hash: &[u8]) -> proc_macro2::TokenStream {
let core_crate = ethers_core_crate(); let core_crate = ethers_core_crate();
let bytes = hash.iter().copied().map(Literal::u8_unsuffixed); let bytes = hash.iter().copied().map(Literal::u8_unsuffixed);

View File

@ -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())
}
}

View File

@ -11,6 +11,9 @@ pub use base::{decode_function_data, encode_function_data, AbiError, BaseContrac
mod call; mod call;
pub use call::{ContractError, EthCall}; pub use call::{ContractError, EthCall};
mod error;
pub use error::EthError;
mod factory; mod factory;
pub use factory::{ContractDeployer, ContractFactory}; pub use factory::{ContractDeployer, ContractFactory};
@ -42,7 +45,9 @@ pub use ethers_contract_abigen::{Abigen, MultiAbigen};
#[cfg(any(test, feature = "abigen"))] #[cfg(any(test, feature = "abigen"))]
#[cfg_attr(docsrs, doc(cfg(feature = "abigen")))] #[cfg_attr(docsrs, doc(cfg(feature = "abigen")))]
pub use ethers_contract_derive::{abigen, 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 // Hide the Lazy re-export, it's just for convenience
#[doc(hidden)] #[doc(hidden)]

View File

@ -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)" "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_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] #[test]

View File

@ -1,5 +1,5 @@
use ethers_contract::{ use ethers_contract::{
abigen, EthAbiCodec, EthAbiType, EthCall, EthDisplay, EthEvent, EthLogDecode, abigen, EthAbiCodec, EthAbiType, EthCall, EthDisplay, EthError, EthEvent, EthLogDecode,
}; };
use ethers_core::{ use ethers_core::{
abi::{AbiDecode, AbiEncode, RawLog, Tokenizable}, abi::{AbiDecode, AbiEncode, RawLog, Tokenizable},
@ -8,6 +8,7 @@ use ethers_core::{
fn assert_tokenizeable<T: Tokenizable>() {} fn assert_tokenizeable<T: Tokenizable>() {}
fn assert_ethcall<T: EthCall>() {} fn assert_ethcall<T: EthCall>() {}
fn assert_etherror<T: EthError>() {}
#[derive(Debug, Clone, PartialEq, Eq, EthAbiType)] #[derive(Debug, Clone, PartialEq, Eq, EthAbiType)]
struct ValueChanged { struct ValueChanged {
@ -618,3 +619,31 @@ fn can_use_result_name() {
let _call = ResultCall { result: Result { result: U256::zero() } }; 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>();
}

View File

@ -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 std::{fmt, iter::Peekable, str::CharIndices};
use unicode_xid::UnicodeXID; use unicode_xid::UnicodeXID;
@ -320,6 +322,18 @@ impl<'input> HumanReadableParser<'input> {
Self::new(input).take_function() 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 /// Parses a [Constructor] from a human readable form
/// ///
/// # Example /// # Example
@ -344,6 +358,15 @@ impl<'input> HumanReadableParser<'input> {
Self::new(input).take_event() 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 /// Returns the next `Constructor` and consumes the underlying tokens
pub fn take_constructor(&mut self) -> Result<Constructor, LexerError> { pub fn take_constructor(&mut self) -> Result<Constructor, LexerError> {
self.take_next_exact(Token::Constructor)?; self.take_next_exact(Token::Constructor)?;
@ -352,6 +375,7 @@ impl<'input> HumanReadableParser<'input> {
self.take_close_parenthesis()?; self.take_close_parenthesis()?;
Ok(Constructor { inputs }) Ok(Constructor { inputs })
} }
/// Returns the next `Function` and consumes the underlying tokens /// Returns the next `Function` and consumes the underlying tokens
pub fn take_function(&mut self) -> Result<Function, LexerError> { pub fn take_function(&mut self) -> Result<Function, LexerError> {
let name = self.take_identifier(Token::Function)?; let name = self.take_identifier(Token::Function)?;
@ -833,6 +857,31 @@ pub enum DataLocation {
mod tests { mod tests {
use super::*; 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] #[test]
fn parse_constructor() { fn parse_constructor() {
let f = Constructor { let f = Constructor {

View File

@ -1,3 +1,4 @@
use ethabi::AbiError;
use std::collections::{BTreeMap, HashMap, VecDeque}; use std::collections::{BTreeMap, HashMap, VecDeque};
use crate::abi::{ use crate::abi::{
@ -82,6 +83,17 @@ impl AbiParser {
if line.starts_with("event") { if line.starts_with("event") {
let event = self.parse_event(line)?; let event = self.parse_event(line)?;
abi.events.entry(event.name.clone()).or_default().push(event); 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") { } else if line.starts_with("constructor") {
let inputs = self let inputs = self
.constructor_inputs(line)? .constructor_inputs(line)?
@ -103,7 +115,7 @@ impl AbiParser {
// function may have shorthand declaration, so it won't start with "function" // function may have shorthand declaration, so it won't start with "function"
let function = match self.parse_function(line) { let function = match self.parse_function(line) {
Ok(function) => function, 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); abi.functions.entry(function.name.clone()).or_default().push(function);
} }

View File

@ -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. /// A trait for types that can be represented in the ethereum ABI.
pub trait AbiType { pub trait AbiType {
/// The native ABI type this type represents. /// The native ABI type this type represents.