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
|
### 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
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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, ¶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 {
|
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, )* } }
|
||||||
|
}
|
||||||
|
|
|
@ -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 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, ¶m.name);
|
|
||||||
let ty = self.expand_output_param_type(fun, param, ¶m.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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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`.
|
||||||
///
|
///
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
|
|
|
@ -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;
|
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))
|
||||||
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
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)]
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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>();
|
||||||
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
Loading…
Reference in New Issue