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

Commit

Permalink
feat: add EthError trait and derive (#1549)
Browse files Browse the repository at this point in the history
* feat: add EthError trait and derive

* update changelog
  • Loading branch information
mattsse authored Aug 2, 2022
1 parent 54bd525 commit 27a184d
Show file tree
Hide file tree
Showing 19 changed files with 892 additions and 266 deletions.
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

0 comments on commit 27a184d

Please sign in to comment.