Skip to content
This repository has been archived by the owner on Oct 19, 2024. It is now read-only.

feat: add EthError trait and derive #1549

Merged
merged 2 commits into from
Aug 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@

### Unreleased

- generate error bindings for custom errors [#1549](https://github.com/gakonst/ethers-rs/pull/1549)
- Support overloaded events
[#1233](https://github.com/gakonst/ethers-rs/pull/1233)
- Relax Clone requirements when Arc<Middleware> is used
Expand Down
96 changes: 75 additions & 21 deletions ethers-contract/ethers-contract-abigen/src/contract.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
#![deny(missing_docs)]
mod common;
mod errors;
mod events;
mod methods;
mod structs;
mod types;

use super::{util, Abigen};
use crate::contract::structs::InternalStructs;
use crate::{
contract::{methods::MethodAlias, structs::InternalStructs},
rawabi::JsonAbi,
};
use ethers_core::{
abi::{Abi, AbiParser},
abi::{Abi, AbiParser, ErrorExt, EventExt},
macros::{ethers_contract_crate, ethers_core_crate, ethers_providers_crate},
types::Bytes,
};
use eyre::{eyre, Context as _, Result};

use crate::contract::methods::MethodAlias;

use crate::rawabi::JsonAbi;
use ethers_core::{abi::EventExt, types::Bytes};
use proc_macro2::{Ident, Literal, TokenStream};
use quote::quote;
use serde::Deserialize;
Expand All @@ -34,6 +34,8 @@ pub struct ExpandedContract {
pub contract: TokenStream,
/// All event impls of the contract
pub events: TokenStream,
/// All error impls of the contract
pub errors: TokenStream,
/// All contract call struct related types
pub call_structs: TokenStream,
/// The contract's internal structs
Expand All @@ -43,8 +45,15 @@ pub struct ExpandedContract {
impl ExpandedContract {
/// Merges everything into a single module
pub fn into_tokens(self) -> TokenStream {
let ExpandedContract { module, imports, contract, events, call_structs, abi_structs } =
self;
let ExpandedContract {
module,
imports,
contract,
events,
call_structs,
abi_structs,
errors,
} = self;
quote! {
// export all the created data types
pub use #module::*;
Expand All @@ -53,6 +62,7 @@ impl ExpandedContract {
pub mod #module {
#imports
#contract
#errors
#events
#call_structs
#abi_structs
Expand Down Expand Up @@ -87,6 +97,9 @@ pub struct Context {
/// Manually specified method aliases.
method_aliases: BTreeMap<String, MethodAlias>,

/// Manually specified method aliases.
error_aliases: BTreeMap<String, Ident>,

/// Derives added to event structs and enums.
event_derives: Vec<Path>,

Expand Down Expand Up @@ -125,6 +138,9 @@ impl Context {
// 6. Declare the structs parsed from the human readable abi
let abi_structs_decl = self.abi_structs()?;

// 7. declare all error types
let errors_decl = self.errors()?;

let ethers_core = ethers_core_crate();
let ethers_contract = ethers_contract_crate();
let ethers_providers = ethers_providers_crate();
Expand All @@ -145,6 +161,7 @@ impl Context {
#contract_methods

#contract_events

}

impl<M : #ethers_providers::Middleware> From<#ethers_contract::Contract<M>> for #name<M> {
Expand All @@ -159,6 +176,7 @@ impl Context {
imports,
contract,
events: events_decl,
errors: errors_decl,
call_structs,
abi_structs: abi_structs_decl,
})
Expand Down Expand Up @@ -226,20 +244,30 @@ impl Context {
event_aliases.insert(signature, alias);
}

// also check for overloaded functions not covered by aliases, in which case we simply
// also check for overloaded events not covered by aliases, in which case we simply
// numerate them
for events in abi.events.values() {
let not_aliased =
events.iter().filter(|ev| !event_aliases.contains_key(&ev.abi_signature()));
if not_aliased.clone().count() > 1 {
let mut aliases = Vec::new();
// overloaded events
for (idx, event) in not_aliased.enumerate() {
let event_name = format!("{}{}", event.name, idx + 1);
aliases.push((event.abi_signature(), events::event_struct_alias(&event_name)));
}
event_aliases.extend(aliases);
}
insert_alias_names(
&mut event_aliases,
events.iter().map(|e| (e.abi_signature(), e.name.as_str())),
events::event_struct_alias,
);
}

let mut error_aliases = BTreeMap::new();
for (signature, alias) in args.error_aliases.into_iter() {
let alias = syn::parse_str(&alias)?;
error_aliases.insert(signature, alias);
}

// also check for overloaded errors not covered by aliases, in which case we simply
// numerate them
for errors in abi.errors.values() {
insert_alias_names(
&mut error_aliases,
errors.iter().map(|e| (e.abi_signature(), e.name.as_str())),
errors::error_struct_alias,
);
}

let event_derives = args
Expand All @@ -259,6 +287,7 @@ impl Context {
contract_name: args.contract_name,
contract_bytecode,
method_aliases,
error_aliases: Default::default(),
event_derives,
event_aliases,
})
Expand Down Expand Up @@ -290,6 +319,31 @@ impl Context {
}
}

/// Solidity supports overloading as long as the signature of an event, error, function is unique,
/// which results in a mapping `(name -> Vec<Element>)`
///
///
/// This will populate the alias map for the value in the mapping (`Vec<Element>`) via `abi
/// signature -> name` using the given aliases and merge it with all names not yet aliased.
///
/// If the iterator yields more than one element, this will simply numerate them
fn insert_alias_names<'a, I, F>(aliases: &mut BTreeMap<String, Ident>, elements: I, get_ident: F)
where
I: IntoIterator<Item = (String, &'a str)>,
F: Fn(&str) -> Ident,
{
let not_aliased =
elements.into_iter().filter(|(sig, _name)| !aliases.contains_key(sig)).collect::<Vec<_>>();
if not_aliased.len() > 1 {
let mut overloaded_aliases = Vec::new();
for (idx, (sig, name)) in not_aliased.into_iter().enumerate() {
let unique_name = format!("{}{}", name, idx + 1);
overloaded_aliases.push((sig, get_ident(&unique_name)));
}
aliases.extend(overloaded_aliases);
}
}

/// Parse the abi via `Source::parse` and return if the abi defined as human readable
fn parse_abi(abi_str: &str) -> Result<(Abi, bool, AbiParser)> {
let mut abi_parser = AbiParser::default();
Expand Down
97 changes: 95 additions & 2 deletions ethers-contract/ethers-contract-abigen/src/contract/common.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,67 @@
use super::{util, Context};

use proc_macro2::TokenStream;
use crate::contract::types;
use ethers_core::{
abi::{Param, ParamType},
macros::{ethers_contract_crate, ethers_core_crate, ethers_providers_crate},
};
use proc_macro2::{Ident, TokenStream};
use quote::quote;

use ethers_core::macros::{ethers_contract_crate, ethers_core_crate, ethers_providers_crate};
/// Expands to the `name : type` pairs for the params
pub(crate) fn expand_params<'a, F>(
params: &[Param],
resolve_tuple: F,
) -> eyre::Result<Vec<(TokenStream, TokenStream)>>
where
F: Fn(&str) -> Option<&'a str>,
{
params
.iter()
.enumerate()
.map(|(idx, param)| {
let name = util::expand_input_name(idx, &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 {
let doc = util::expand_doc(&format!("{} was auto-generated with ethers-rs Abigen. More information at: https://github.com/gakonst/ethers-rs", name));
Expand Down Expand Up @@ -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, )* } }
}
Loading