refactor(abigen): derives, struct expansion (#2160)

* refactor: abigen derives

* refactor: struct expansion

* refactor: structs 2

* chore: abigen derives and visibility

* refactor: abigen docs

* refactor: structs 3

* chore: doc

* chore: structs conditional default

* refactor: method expansion

* refactor: final doc

* refactor: expansions, add docs, extra From impl

* refactor: final struct expansion

* feat(types): implement bytes::Bytes static methods

* feat: use static Bytes for bytecode

* merge artifact

* refactor: event input expansion

* refactor: common expand params

* refactor: method params expansion

* refactor: struct fields expansion

* refactor: types array expansion

* mergings

* cleanup

* flatten

* selector fmt

* chore: clippy

* refactor: common, imports
This commit is contained in:
DaniPopes 2023-02-20 01:53:29 +01:00 committed by GitHub
parent 8d511dbd64
commit 6336e96995
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 495 additions and 580 deletions

View File

@ -1,6 +1,5 @@
//! Contains types to generate Rust bindings for Solidity contracts. //! Contains types to generate Rust bindings for Solidity contracts.
mod common;
mod errors; mod errors;
mod events; mod events;
mod methods; mod methods;
@ -52,14 +51,19 @@ impl ExpandedContract {
abi_structs, abi_structs,
errors, errors,
} = self; } = self;
quote! { quote! {
// export all the created data types
pub use #module::*; pub use #module::*;
/// This module was auto-generated with ethers-rs Abigen.
/// More information at: <https://github.com/gakonst/ethers-rs>
#[allow( #[allow(
clippy::enum_variant_names,
clippy::too_many_arguments, clippy::too_many_arguments,
clippy::upper_case_acronyms,
clippy::type_complexity,
dead_code,
non_camel_case_types, non_camel_case_types,
clippy::upper_case_acronyms
)] )]
pub mod #module { pub mod #module {
#imports #imports
@ -103,7 +107,7 @@ pub struct Context {
error_aliases: BTreeMap<String, Ident>, error_aliases: BTreeMap<String, Ident>,
/// Derives added to event structs and enums. /// Derives added to event structs and enums.
event_derives: Vec<Path>, extra_derives: Vec<Path>,
/// Manually specified event aliases. /// Manually specified event aliases.
event_aliases: BTreeMap<String, Ident>, event_aliases: BTreeMap<String, Ident>,
@ -122,11 +126,8 @@ impl Context {
let name_mod = util::ident(&util::safe_module_name(&self.contract_name)); let name_mod = util::ident(&util::safe_module_name(&self.contract_name));
let abi_name = self.inline_abi_ident(); let abi_name = self.inline_abi_ident();
// 0. Imports
let imports = common::imports(&name.to_string());
// 1. Declare Contract struct // 1. Declare Contract struct
let struct_decl = common::struct_declaration(self); let struct_decl = self.struct_declaration();
// 2. Declare events structs & impl FromTokens for each event // 2. Declare events structs & impl FromTokens for each event
let events_decl = self.events_declaration()?; let events_decl = self.events_declaration()?;
@ -137,7 +138,7 @@ impl Context {
// 4. impl block for the contract methods and their corresponding types // 4. impl block for the contract methods and their corresponding types
let (contract_methods, call_structs) = self.methods_and_call_structs()?; let (contract_methods, call_structs) = self.methods_and_call_structs()?;
// 5. generate deploy function if // 5. The deploy method, only if the contract has a bytecode object
let deployment_methods = self.deployment_methods(); let deployment_methods = self.deployment_methods();
// 6. Declare the structs parsed from the human readable abi // 6. Declare the structs parsed from the human readable abi
@ -154,9 +155,8 @@ impl Context {
#struct_decl #struct_decl
impl<M: #ethers_providers::Middleware> #name<M> { impl<M: #ethers_providers::Middleware> #name<M> {
/// Creates a new contract instance with the specified `ethers` /// Creates a new contract instance with the specified `ethers` client at
/// client at the given `Address`. The contract derefs to a `ethers::Contract` /// `address`. The contract derefs to a `ethers::Contract` object.
/// object
pub fn new<T: Into<#ethers_core::types::Address>>(address: T, client: ::std::sync::Arc<M>) -> Self { pub fn new<T: Into<#ethers_core::types::Address>>(address: T, client: ::std::sync::Arc<M>) -> Self {
Self(#ethers_contract::Contract::new(address.into(), #abi_name.clone(), client)) Self(#ethers_contract::Contract::new(address.into(), #abi_name.clone(), client))
} }
@ -166,19 +166,18 @@ 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> {
fn from(contract: #ethers_contract::Contract<M>) -> Self { fn from(contract: #ethers_contract::Contract<M>) -> Self {
Self::new(contract.address(), contract.client()) Self::new(contract.address(), contract.client())
} }
} }
}; };
Ok(ExpandedContract { Ok(ExpandedContract {
module: name_mod, module: name_mod,
imports, imports: quote!(),
contract, contract,
events: events_decl, events: events_decl,
errors: errors_decl, errors: errors_decl,
@ -282,12 +281,12 @@ impl Context {
); );
} }
let event_derives = args let extra_derives = args
.derives .derives
.iter() .iter()
.map(|derive| syn::parse_str::<Path>(derive)) .map(|derive| syn::parse_str::<Path>(derive))
.collect::<Result<Vec<_>, _>>() .collect::<Result<Vec<_>, _>>()
.context("failed to parse event derives")?; .wrap_err("failed to parse event derives")?;
Ok(Context { Ok(Context {
abi, abi,
@ -301,7 +300,7 @@ impl Context {
contract_deployed_bytecode, contract_deployed_bytecode,
method_aliases, method_aliases,
error_aliases: Default::default(), error_aliases: Default::default(),
event_derives, extra_derives,
event_aliases, event_aliases,
}) })
} }
@ -335,6 +334,116 @@ impl Context {
pub fn internal_structs_mut(&mut self) -> &mut InternalStructs { pub fn internal_structs_mut(&mut self) -> &mut InternalStructs {
&mut self.internal_structs &mut self.internal_structs
} }
/// Expands `self.extra_derives` into a comma separated list to be inserted in a
/// `#[derive(...)]` attribute.
pub(crate) fn expand_extra_derives(&self) -> TokenStream {
let extra_derives = &self.extra_derives;
quote!(#( #extra_derives, )*)
}
/// Generates the token stream for the contract's ABI, bytecode and struct declarations.
pub(crate) fn struct_declaration(&self) -> TokenStream {
let name = &self.contract_ident;
let ethers_core = ethers_core_crate();
let ethers_contract = ethers_contract_crate();
let abi = {
let abi_name = self.inline_abi_ident();
let abi = &self.abi_str;
let (doc_str, parse) = if self.human_readable {
// Human readable: use abi::parse_abi_str
let doc_str = "The parsed human-readable ABI of the contract.";
let parse = quote!(#ethers_core::abi::parse_abi_str(__ABI));
(doc_str, parse)
} else {
// JSON ABI: use serde_json::from_str
let doc_str = "The parsed JSON ABI of the contract.";
let parse = quote!(#ethers_core::utils::__serde_json::from_str(__ABI));
(doc_str, parse)
};
quote! {
#[rustfmt::skip]
const __ABI: &str = #abi;
// This never fails as we are parsing the ABI in this macro
#[doc = #doc_str]
pub static #abi_name: #ethers_contract::Lazy<#ethers_core::abi::Abi> =
#ethers_contract::Lazy::new(|| #parse.expect("ABI is always valid"));
}
};
let bytecode = self.contract_bytecode.as_ref().map(|bytecode| {
let bytecode = bytecode.iter().copied().map(Literal::u8_unsuffixed);
let bytecode_name = self.inline_bytecode_ident();
quote! {
#[rustfmt::skip]
const __BYTECODE: &[u8] = &[ #( #bytecode ),* ];
#[doc = "The bytecode of the contract."]
pub static #bytecode_name: #ethers_core::types::Bytes = #ethers_core::types::Bytes::from_static(__BYTECODE);
}
});
let deployed_bytecode = self.contract_deployed_bytecode.as_ref().map(|bytecode| {
let bytecode = bytecode.iter().copied().map(Literal::u8_unsuffixed);
let bytecode_name = self.inline_deployed_bytecode_ident();
quote! {
#[rustfmt::skip]
const __DEPLOYED_BYTECODE: &[u8] = &[ #( #bytecode ),* ];
#[doc = "The deployed bytecode of the contract."]
pub static #bytecode_name: #ethers_core::types::Bytes = #ethers_core::types::Bytes::from_static(__DEPLOYED_BYTECODE);
}
});
quote! {
// The `Lazy` ABI
#abi
// The static Bytecode, if present
#bytecode
// The static deployed Bytecode, if present
#deployed_bytecode
// Struct declaration
pub struct #name<M>(#ethers_contract::Contract<M>);
// Manual implementation since `M` is stored in `Arc<M>` and does not need to be `Clone`
impl<M> ::core::clone::Clone for #name<M> {
fn clone(&self) -> Self {
Self(::core::clone::Clone::clone(&self.0))
}
}
// Deref to the inner contract to have access to all its methods
impl<M> ::core::ops::Deref for #name<M> {
type Target = #ethers_contract::Contract<M>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<M> ::core::ops::DerefMut for #name<M> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
// `<name>(<address>)`
impl<M> ::core::fmt::Debug for #name<M> {
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
f.debug_tuple(stringify!(#name))
.field(&self.address())
.finish()
}
}
}
}
} }
/// Solidity supports overloading as long as the signature of an event, error, function is unique, /// Solidity supports overloading as long as the signature of an event, error, function is unique,

View File

@ -1,156 +0,0 @@
use super::Context;
use ethers_core::macros::{ethers_contract_crate, ethers_core_crate, ethers_providers_crate};
use proc_macro2::{Ident, Literal, TokenStream};
use quote::quote;
pub(crate) fn imports(name: &str) -> TokenStream {
let doc_str = format!("{name} was auto-generated with ethers-rs Abigen. More information at: <https://github.com/gakonst/ethers-rs>");
let ethers_core = ethers_core_crate();
let ethers_providers = ethers_providers_crate();
let ethers_contract = ethers_contract_crate();
quote! {
#![allow(clippy::enum_variant_names)]
#![allow(dead_code)]
#![allow(clippy::type_complexity)]
#![allow(unused_imports)]
#![doc = #doc_str]
use std::sync::Arc;
use #ethers_core::{
abi::{Abi, Token, Detokenize, InvalidOutputType, Tokenizable},
types::*, // import all the types so that we can codegen for everything
};
use #ethers_contract::{Contract, builders::{ContractCall, Event}, Lazy};
use #ethers_providers::Middleware;
}
}
/// Generates the token stream for the contract's ABI, bytecode and struct declarations.
pub(crate) fn struct_declaration(cx: &Context) -> TokenStream {
let name = &cx.contract_ident;
let ethers_core = ethers_core_crate();
let ethers_contract = ethers_contract_crate();
let abi = {
let abi_name = cx.inline_abi_ident();
let abi = &cx.abi_str;
let (doc_str, parse) = if cx.human_readable {
// Human readable: use abi::parse_abi_str
let doc_str = "The parsed human-readable ABI of the contract.";
let parse = quote!(#ethers_core::abi::parse_abi_str(__ABI));
(doc_str, parse)
} else {
// JSON ABI: use serde_json::from_str
let doc_str = "The parsed JSON ABI of the contract.";
let parse = quote!(#ethers_core::utils::__serde_json::from_str(__ABI));
(doc_str, parse)
};
quote! {
#[rustfmt::skip]
const __ABI: &str = #abi;
// This never fails as we are parsing the ABI in this macro
#[doc = #doc_str]
pub static #abi_name: #ethers_contract::Lazy<#ethers_core::abi::Abi> =
#ethers_contract::Lazy::new(|| #parse.expect("ABI is always valid"));
}
};
let bytecode = cx.contract_bytecode.as_ref().map(|bytecode| {
let bytecode = bytecode.iter().copied().map(Literal::u8_unsuffixed);
let bytecode_name = cx.inline_bytecode_ident();
quote! {
#[rustfmt::skip]
const __BYTECODE: &[u8] = &[ #( #bytecode ),* ];
#[doc = "The bytecode of the contract."]
pub static #bytecode_name: #ethers_core::types::Bytes = #ethers_core::types::Bytes::from_static(__BYTECODE);
}
});
let deployed_bytecode = cx.contract_deployed_bytecode.as_ref().map(|bytecode| {
let bytecode = bytecode.iter().copied().map(Literal::u8_unsuffixed);
let bytecode_name = cx.inline_deployed_bytecode_ident();
quote! {
#[rustfmt::skip]
const __DEPLOYED_BYTECODE: &[u8] = &[ #( #bytecode ),* ];
#[doc = "The deployed bytecode of the contract."]
pub static #bytecode_name: #ethers_core::types::Bytes = #ethers_core::types::Bytes::from_static(__DEPLOYED_BYTECODE);
}
});
quote! {
// The `Lazy` ABI
#abi
// The static Bytecode, if present
#bytecode
// The static deployed Bytecode, if present
#deployed_bytecode
// Struct declaration
pub struct #name<M>(#ethers_contract::Contract<M>);
impl<M> Clone for #name<M> {
fn clone(&self) -> Self {
#name(self.0.clone())
}
}
// Deref to the inner contract in order to access more specific functions functions
impl<M> std::ops::Deref for #name<M> {
type Target = #ethers_contract::Contract<M>;
fn deref(&self) -> &Self::Target { &self.0 }
}
impl<M> std::fmt::Debug for #name<M> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.debug_tuple(stringify!(#name))
.field(&self.address())
.finish()
}
}
}
}
/// 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

@ -1,9 +1,6 @@
//! derive error bindings //! Custom errors expansion
use super::{ use super::{structs::expand_struct, types, util, Context};
common::{expand_data_struct, expand_data_tuple},
types, util, Context,
};
use ethers_core::{ use ethers_core::{
abi::{ethabi::AbiError, ErrorExt}, abi::{ethabi::AbiError, ErrorExt},
macros::{ethers_contract_crate, ethers_core_crate}, macros::{ethers_contract_crate, ethers_core_crate},
@ -25,57 +22,46 @@ impl Context {
.collect::<Result<Vec<_>>>()?; .collect::<Result<Vec<_>>>()?;
// only expand an enum when multiple errors are present // only expand an enum when multiple errors are present
let errors_enum_decl = if self.abi.errors.values().flatten().count() > 1 { let errors_enum_decl =
self.expand_errors_enum() if data_types.len() > 1 { Some(self.expand_errors_enum()) } else { None };
} else {
quote! {}
};
Ok(quote! { Ok(quote! {
// HERE
#( #data_types )* #( #data_types )*
#errors_enum_decl #errors_enum_decl
// HERE end
}) })
} }
/// Expands an ABI error into a single error data type. This can expand either /// 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. /// into a structure or a tuple in the case where all error parameters are anonymous.
fn expand_error(&self, error: &AbiError) -> Result<TokenStream> { fn expand_error(&self, error: &AbiError) -> Result<TokenStream> {
let sig = self.error_aliases.get(&error.abi_signature()).cloned(); let error_name = &error.name;
let abi_signature = error.abi_signature(); let abi_signature = error.abi_signature();
let error_name = error_struct_name(&error.name, sig); let alias_opt = self.error_aliases.get(&abi_signature).cloned();
let error_struct_name = error_struct_name(&error.name, alias_opt);
let fields = self.expand_error_params(error)?; let fields = self.expand_error_params(error)?;
// expand as a tuple if all fields are anonymous // expand as a tuple if all fields are anonymous
let all_anonymous_fields = error.inputs.iter().all(|input| input.name.is_empty()); let all_anonymous_fields = error.inputs.iter().all(|input| input.name.is_empty());
let data_type_definition = if all_anonymous_fields { let data_type_definition = expand_struct(&error_struct_name, &fields, 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_str = format!( let doc_str = format!(
"Custom Error type `{}` with signature `{}` and selector `0x{}`", "Custom Error type `{error_name}` with signature `{abi_signature}` and selector `0x{}`",
error.name, hex::encode(error.selector())
abi_signature,
hex::encode(&error.selector()[..])
); );
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; let mut extra_derives = self.expand_extra_derives();
if util::can_derive_defaults(&error.inputs) {
extra_derives.extend(quote!(Default));
}
let ethers_contract = ethers_contract_crate();
Ok(quote! { Ok(quote! {
#[doc = #doc_str] #[doc = #doc_str]
#[derive(Clone, Debug, Default, Eq, PartialEq, #ethers_contract::EthError, #ethers_contract::EthDisplay, #derives)] #[derive(Clone, Debug, Eq, PartialEq, #ethers_contract::EthError, #ethers_contract::EthDisplay, #extra_derives)]
#[etherror(name = #error_name, abi = #abi_signature)] #[etherror(name = #error_name, abi = #abi_signature)]
pub #data_type_definition pub #data_type_definition
}) })
@ -95,6 +81,7 @@ impl Context {
/// Generate an enum with a variant for each event /// Generate an enum with a variant for each event
fn expand_errors_enum(&self) -> TokenStream { fn expand_errors_enum(&self) -> TokenStream {
let enum_name = self.expand_error_enum_name();
let variants = self let variants = self
.abi .abi
.errors .errors
@ -105,58 +92,57 @@ impl Context {
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let extra_derives = self.expand_extra_derives();
let ethers_core = ethers_core_crate(); let ethers_core = ethers_core_crate();
let ethers_contract = ethers_contract_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! { quote! {
#[derive(Debug, Clone, PartialEq, Eq, #ethers_contract::EthAbiType, #derives)] #[doc = "Container type for all of the contract's custom errors"]
#[derive(Debug, Clone, PartialEq, Eq, #ethers_contract::EthAbiType, #extra_derives)]
pub enum #enum_name { pub enum #enum_name {
#(#variants(#variants)),* #( #variants(#variants), )*
} }
impl #ethers_core::abi::AbiDecode for #enum_name { impl #ethers_core::abi::AbiDecode for #enum_name {
fn decode(data: impl AsRef<[u8]>) -> ::std::result::Result<Self, #ethers_core::abi::AbiError> { fn decode(data: impl AsRef<[u8]>) -> ::core::result::Result<Self, #ethers_core::abi::AbiError> {
#( let data = data.as_ref();
if let Ok(decoded) = <#variants as #ethers_core::abi::AbiDecode>::decode(data.as_ref()) { #(
return Ok(#enum_name::#variants(decoded)) if let Ok(decoded) = <#variants as #ethers_core::abi::AbiDecode>::decode(data) {
return Ok(Self::#variants(decoded))
}
)*
Err(#ethers_core::abi::Error::InvalidData.into())
}
}
impl #ethers_core::abi::AbiEncode for #enum_name {
fn encode(self) -> ::std::vec::Vec<u8> {
match self {
#(
Self::#variants(element) => #ethers_core::abi::AbiEncode::encode(element),
)*
} }
)*
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 { impl ::core::fmt::Display for #enum_name {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
match self { match self {
#( #(
#enum_name::#variants(element) => element.fmt(f) Self::#variants(element) => ::core::fmt::Display::fmt(element, f)
),* ),*
}
} }
} }
}
#( #(
impl ::std::convert::From<#variants> for #enum_name { impl ::core::convert::From<#variants> for #enum_name {
fn from(var: #variants) -> Self { fn from(value: #variants) -> Self {
#enum_name::#variants(var) Self::#variants(value)
}
} }
} )*
)*
} }
} }
} }

View File

@ -1,7 +1,9 @@
use super::{types, util, Context}; //! Events expansion
use crate::util::can_derive_defaults;
use super::{structs::expand_event_struct, types, Context};
use crate::util;
use ethers_core::{ use ethers_core::{
abi::{Event, EventExt, Param}, abi::{Event, EventExt},
macros::{ethers_contract_crate, ethers_core_crate}, macros::{ethers_contract_crate, ethers_core_crate},
}; };
use eyre::Result; use eyre::Result;
@ -21,11 +23,8 @@ impl Context {
.collect::<Result<Vec<_>>>()?; .collect::<Result<Vec<_>>>()?;
// only expand enums when multiple events are present // only expand enums when multiple events are present
let events_enum_decl = if sorted_events.values().flatten().count() > 1 { let events_enum_decl =
self.expand_events_enum() if data_types.len() > 1 { Some(self.expand_events_enum()) } else { None };
} else {
quote! {}
};
Ok(quote! { Ok(quote! {
#( #data_types )* #( #data_types )*
@ -40,8 +39,7 @@ impl Context {
let filter_methods = sorted_events let filter_methods = sorted_events
.values() .values()
.flat_map(std::ops::Deref::deref) .flat_map(std::ops::Deref::deref)
.map(|event| self.expand_filter(event)) .map(|event| self.expand_filter(event));
.collect::<Vec<_>>();
let events_method = self.expand_events_method(); let events_method = self.expand_events_method();
@ -67,22 +65,19 @@ impl Context {
let ethers_core = ethers_core_crate(); let ethers_core = ethers_core_crate();
let ethers_contract = ethers_contract_crate(); let ethers_contract = ethers_contract_crate();
// use the same derives as for events let extra_derives = self.expand_extra_derives();
let derives = util::expand_derives(&self.event_derives);
let enum_name = self.expand_event_enum_name(); let enum_name = self.expand_event_enum_name();
quote! { quote! {
#[derive(Debug, Clone, PartialEq, Eq, #ethers_contract::EthAbiType, #derives)] #[doc = "Container type for all of the contract's events"]
#[derive(Debug, Clone, PartialEq, Eq, #ethers_contract::EthAbiType, #extra_derives)]
pub enum #enum_name { pub enum #enum_name {
#(#variants(#variants)),* #( #variants(#variants), )*
} }
impl #ethers_contract::EthLogDecode for #enum_name { impl #ethers_contract::EthLogDecode for #enum_name {
fn decode_log(log: &#ethers_core::abi::RawLog) -> ::std::result::Result<Self, #ethers_core::abi::Error> fn decode_log(log: &#ethers_core::abi::RawLog) -> ::core::result::Result<Self, #ethers_core::abi::Error> {
where #(
Self: Sized,
{
#(
if let Ok(decoded) = #variants::decode_log(log) { if let Ok(decoded) = #variants::decode_log(log) {
return Ok(#enum_name::#variants(decoded)) return Ok(#enum_name::#variants(decoded))
} }
@ -91,15 +86,23 @@ impl Context {
} }
} }
impl ::std::fmt::Display for #enum_name { impl ::core::fmt::Display for #enum_name {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
match self { match self {
#( #(
#enum_name::#variants(element) => element.fmt(f) Self::#variants(element) => ::core::fmt::Display::fmt(element, f),
),* )*
} }
} }
} }
#(
impl ::core::convert::From<#variants> for #enum_name {
fn from(value: #variants) -> Self {
Self::#variants(value)
}
}
)*
} }
} }
@ -109,7 +112,7 @@ impl Context {
} }
/// Expands the `events` function that bundles all declared events of this contract /// Expands the `events` function that bundles all declared events of this contract
fn expand_events_method(&self) -> TokenStream { fn expand_events_method(&self) -> Option<TokenStream> {
let sorted_events: BTreeMap<_, _> = self.abi.events.clone().into_iter().collect(); let sorted_events: BTreeMap<_, _> = self.abi.events.clone().into_iter().collect();
let mut iter = sorted_events.values().flatten(); let mut iter = sorted_events.values().flatten();
@ -125,28 +128,35 @@ impl Context {
) )
}; };
quote! { Some(quote! {
/// Returns an [`Event`](#ethers_contract::builders::Event) builder for all events of this contract /// Returns an `Event` builder for all the events of this contract.
pub fn events(&self) -> #ethers_contract::builders::Event<Arc<M>, M, #ty> { pub fn events(&self) -> #ethers_contract::builders::Event<
self.0.event_with_filter(Default::default()) ::std::sync::Arc<M>,
M,
#ty,
> {
self.0.event_with_filter(::core::default::Default::default())
} }
} })
} else { } else {
quote! {} None
} }
} }
/// Expands into a single method for contracting an event stream. /// Expands into a single method for contracting an event stream.
fn expand_filter(&self, event: &Event) -> TokenStream { fn expand_filter(&self, event: &Event) -> TokenStream {
let name = &event.name; let name = &event.name;
let alias = self.event_aliases.get(&event.abi_signature()).cloned(); let sig = event.abi_signature();
let alias = self.event_aliases.get(&sig).cloned();
// append `filter` to disambiguate with potentially conflicting // append `filter` to disambiguate with potentially conflicting function names
// function names let function_name = {
let function_name = if let Some(id) = alias.clone() { let name = if let Some(ref id) = alias {
util::safe_ident(&format!("{}_filter", id.to_string().to_snake_case())) id.to_string().to_snake_case()
} else { } else {
util::safe_ident(&format!("{}_filter", event.name.to_snake_case())) name.to_snake_case()
};
util::safe_ident(&format!("{name}_filter"))
}; };
let struct_name = event_struct_name(name, alias); let struct_name = event_struct_name(name, alias);
@ -156,7 +166,11 @@ impl Context {
quote! { quote! {
#[doc = #doc_str] #[doc = #doc_str]
pub fn #function_name(&self) -> #ethers_contract::builders::Event<Arc<M>, M, #struct_name> { pub fn #function_name(&self) -> #ethers_contract::builders::Event<
::std::sync::Arc<M>,
M,
#struct_name
> {
self.0.event() self.0.event()
} }
} }
@ -166,49 +180,27 @@ impl Context {
/// into a structure or a tuple in the case where all event parameters (topics /// into a structure or a tuple in the case where all event parameters (topics
/// and data) are anonymous. /// and data) are anonymous.
fn expand_event(&self, event: &Event) -> Result<TokenStream> { fn expand_event(&self, event: &Event) -> Result<TokenStream> {
let sig = self.event_aliases.get(&event.abi_signature()).cloned(); let name = &event.name;
let abi_signature = event.abi_signature(); let abi_signature = event.abi_signature();
let event_abi_name = event.name.clone(); let alias = self.event_aliases.get(&abi_signature).cloned();
let event_name = event_struct_name(&event.name, sig); let struct_name = event_struct_name(name, alias);
let params = types::expand_event_inputs(event, &self.internal_structs)?; let fields = types::expand_event_inputs(event, &self.internal_structs)?;
// expand as a tuple if all fields are anonymous // expand as a tuple if all fields are anonymous
let all_anonymous_fields = event.inputs.iter().all(|input| input.name.is_empty()); let all_anonymous_fields = event.inputs.iter().all(|input| input.name.is_empty());
let data_type_definition = if all_anonymous_fields { let data_type_definition = expand_event_struct(&struct_name, &fields, all_anonymous_fields);
expand_data_tuple(&event_name, &params)
} else {
expand_data_struct(&event_name, &params)
};
let derives = util::expand_derives(&self.event_derives); let mut extra_derives = self.expand_extra_derives();
if event.inputs.iter().map(|param| &param.kind).all(util::can_derive_default) {
// rust-std only derives default automatically for arrays len <= 32 extra_derives.extend(quote!(Default));
// for large array types we skip derive(Default) <https://github.com/gakonst/ethers-rs/issues/1640> }
let derive_default = if can_derive_defaults(
&event
.inputs
.iter()
.map(|param| Param {
name: param.name.clone(),
kind: param.kind.clone(),
internal_type: None,
})
.collect::<Vec<_>>(),
) {
quote! {
#[derive(Default)]
}
} else {
quote! {}
};
let ethers_contract = ethers_contract_crate(); let ethers_contract = ethers_contract_crate();
Ok(quote! { Ok(quote! {
#[derive(Clone, Debug, Eq, PartialEq, #ethers_contract::EthEvent, #ethers_contract::EthDisplay, #derives)] #[derive(Clone, Debug, Eq, PartialEq, #ethers_contract::EthEvent, #ethers_contract::EthDisplay, #extra_derives)]
#derive_default #[ethevent(name = #name, abi = #abi_signature)]
#[ethevent( name = #event_abi_name, abi = #abi_signature )]
pub #data_type_definition pub #data_type_definition
}) })
} }
@ -231,46 +223,6 @@ pub(crate) fn event_struct_alias(event_name: &str) -> Ident {
util::ident(&event_name.to_pascal_case()) util::ident(&event_name.to_pascal_case())
} }
/// Expands an event data structure from its name-type parameter pairs. Returns
/// a tuple with the type definition (i.e. the struct declaration) and
/// construction (i.e. code for creating an instance of the event data).
fn expand_data_struct(name: &Ident, params: &[(TokenStream, TokenStream, bool)]) -> TokenStream {
let fields = params
.iter()
.map(|(name, ty, indexed)| {
if *indexed {
quote! {
#[ethevent(indexed)]
pub #name: #ty
}
} else {
quote! { pub #name: #ty }
}
})
.collect::<Vec<_>>();
quote! { struct #name { #( #fields, )* } }
}
/// Expands an event data named tuple from its name-type parameter pairs.
/// Returns a tuple with the type definition and construction.
fn expand_data_tuple(name: &Ident, params: &[(TokenStream, TokenStream, bool)]) -> TokenStream {
let fields = params
.iter()
.map(|(_, ty, indexed)| {
if *indexed {
quote! {
#[ethevent(indexed)] pub #ty }
} else {
quote! {
pub #ty }
}
})
.collect::<Vec<_>>();
quote! { struct #name( #( #fields ),* ); }
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -328,7 +280,11 @@ mod tests {
#[doc = "Gets the contract's `Transfer` event"] #[doc = "Gets the contract's `Transfer` event"]
pub fn transfer_event_filter( pub fn transfer_event_filter(
&self &self
) -> ::ethers_contract::builders::Event<Arc<M>, M, TransferEventFilter> { ) -> ::ethers_contract::builders::Event<
::std::sync::Arc<M>,
M,
TransferEventFilter,
> {
self.0.event() self.0.event()
} }
}); });
@ -349,7 +305,8 @@ mod tests {
#[doc = "Gets the contract's `Transfer` event"] #[doc = "Gets the contract's `Transfer` event"]
pub fn transfer_filter( pub fn transfer_filter(
&self, &self,
) -> ::ethers_contract::builders::Event<Arc<M>, M, TransferFilter> { ) -> ::ethers_contract::builders::Event<::std::sync::Arc<M>, M, TransferFilter>
{
self.0.event() self.0.event()
} }
}); });
@ -369,7 +326,7 @@ mod tests {
let cx = test_context(); let cx = test_context();
let params = types::expand_event_inputs(&event, &cx.internal_structs).unwrap(); let params = types::expand_event_inputs(&event, &cx.internal_structs).unwrap();
let name = event_struct_name(&event.name, None); let name = event_struct_name(&event.name, None);
let definition = expand_data_struct(&name, &params); let definition = expand_event_struct(&name, &params, false);
assert_quote!(definition, { assert_quote!(definition, {
struct FooFilter { struct FooFilter {
@ -394,7 +351,7 @@ mod tests {
let params = types::expand_event_inputs(&event, &cx.internal_structs).unwrap(); let params = types::expand_event_inputs(&event, &cx.internal_structs).unwrap();
let alias = Some(util::ident("FooAliased")); let alias = Some(util::ident("FooAliased"));
let name = event_struct_name(&event.name, alias); let name = event_struct_name(&event.name, alias);
let definition = expand_data_struct(&name, &params); let definition = expand_event_struct(&name, &params, false);
assert_quote!(definition, { assert_quote!(definition, {
struct FooAliasedFilter { struct FooAliasedFilter {
@ -418,7 +375,7 @@ mod tests {
let cx = test_context(); let cx = test_context();
let params = types::expand_event_inputs(&event, &cx.internal_structs).unwrap(); let params = types::expand_event_inputs(&event, &cx.internal_structs).unwrap();
let name = event_struct_name(&event.name, None); let name = event_struct_name(&event.name, None);
let definition = expand_data_tuple(&name, &params); let definition = expand_event_struct(&name, &params, true);
assert_quote!(definition, { assert_quote!(definition, {
struct FooFilter(pub bool, pub ::ethers_core::types::Address); struct FooFilter(pub bool, pub ::ethers_core::types::Address);
@ -440,7 +397,7 @@ mod tests {
let params = types::expand_event_inputs(&event, &cx.internal_structs).unwrap(); let params = types::expand_event_inputs(&event, &cx.internal_structs).unwrap();
let alias = Some(util::ident("FooAliased")); let alias = Some(util::ident("FooAliased"));
let name = event_struct_name(&event.name, alias); let name = event_struct_name(&event.name, alias);
let definition = expand_data_tuple(&name, &params); let definition = expand_event_struct(&name, &params, true);
assert_quote!(definition, { assert_quote!(definition, {
struct FooAliasedFilter(pub bool, pub ::ethers_core::types::Address); struct FooAliasedFilter(pub bool, pub ::ethers_core::types::Address);

View File

@ -1,8 +1,7 @@
use super::{ //! Methods expansion
common::{expand_data_struct, expand_data_tuple},
types, Context, use super::{structs::expand_struct, types, Context};
}; use crate::util;
use crate::util::{self, can_derive_defaults};
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},
@ -33,7 +32,7 @@ impl Context {
.map(|function| { .map(|function| {
let signature = function.abi_signature(); let signature = function.abi_signature();
self.expand_function(function, aliases.get(&signature).cloned()) self.expand_function(function, aliases.get(&signature).cloned())
.with_context(|| format!("error expanding function '{signature}'")) .wrap_err_with(|| eyre::eyre!("error expanding function '{signature}'"))
}) })
.collect::<Result<Vec<_>>>()?; .collect::<Result<Vec<_>>>()?;
@ -50,11 +49,10 @@ impl Context {
} }
/// Returns all deploy (constructor) implementations /// Returns all deploy (constructor) implementations
pub(crate) fn deployment_methods(&self) -> TokenStream { pub(crate) fn deployment_methods(&self) -> Option<TokenStream> {
if self.contract_bytecode.is_none() { // don't generate deploy if no bytecode
// don't generate deploy if no bytecode self.contract_bytecode.as_ref()?;
return quote! {}
}
let ethers_core = ethers_core_crate(); let ethers_core = ethers_core_crate();
let ethers_contract = ethers_contract_crate(); let ethers_contract = ethers_contract_crate();
@ -68,14 +66,14 @@ impl Context {
#bytecode_name.clone().into() #bytecode_name.clone().into()
}; };
let deploy = quote! { Some(quote! {
/// Constructs the general purpose `Deployer` instance based on the provided constructor arguments and sends it. /// Constructs the general purpose `Deployer` instance based on the provided constructor arguments and sends it.
/// Returns a new instance of a deployer that returns an instance of this contract after sending the transaction /// Returns a new instance of a deployer that returns an instance of this contract after sending the transaction
/// ///
/// Notes: /// Notes:
/// 1. If there are no constructor arguments, you should pass `()` as the argument. /// - If there are no constructor arguments, you should pass `()` as the argument.
/// 1. The default poll duration is 7 seconds. /// - The default poll duration is 7 seconds.
/// 1. The default number of confirmations is 1 block. /// - The default number of confirmations is 1 block.
/// ///
/// ///
/// # Example /// # Example
@ -86,22 +84,22 @@ impl Context {
/// ///
/// ```ignore /// ```ignore
/// # async fn deploy<M: ethers::providers::Middleware>(client: ::std::sync::Arc<M>) { /// # async fn deploy<M: ethers::providers::Middleware>(client: ::std::sync::Arc<M>) {
/// abigen!(Greeter,"../greeter.json"); /// abigen!(Greeter, "../greeter.json");
/// ///
/// let greeter_contract = Greeter::deploy(client, "Hello world!".to_string()).unwrap().send().await.unwrap(); /// let greeter_contract = Greeter::deploy(client, "Hello world!".to_string()).unwrap().send().await.unwrap();
/// let msg = greeter_contract.greet().call().await.unwrap(); /// let msg = greeter_contract.greet().call().await.unwrap();
/// # } /// # }
/// ``` /// ```
pub fn deploy<T: #ethers_core::abi::Tokenize >(client: ::std::sync::Arc<M>, constructor_args: T) -> ::std::result::Result<#ethers_contract::builders::ContractDeployer<M, Self>, #ethers_contract::ContractError<M>> { pub fn deploy<T: #ethers_core::abi::Tokenize>(
let factory = #ethers_contract::ContractFactory::new(#get_abi, #get_bytecode, client); client: ::std::sync::Arc<M>,
let deployer = factory.deploy(constructor_args)?; constructor_args: T,
let deployer = #ethers_contract::ContractDeployer::new(deployer); ) -> ::core::result::Result<#ethers_contract::builders::ContractDeployer<M, Self>, #ethers_contract::ContractError<M>> {
Ok(deployer) let factory = #ethers_contract::ContractFactory::new(#get_abi, #get_bytecode, client);
let deployer = factory.deploy(constructor_args)?;
let deployer = #ethers_contract::ContractDeployer::new(deployer);
Ok(deployer)
} }
})
};
deploy
} }
/// 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
@ -110,42 +108,30 @@ impl Context {
function: &Function, function: &Function,
alias: Option<&MethodAlias>, alias: Option<&MethodAlias>,
) -> Result<TokenStream> { ) -> Result<TokenStream> {
let call_name = expand_call_struct_name(function, alias); let struct_name = expand_call_struct_name(function, alias);
let fields = self.expand_input_params(function)?; let fields = self.expand_input_params(function)?;
// expand as a tuple if all fields are anonymous // expand as a tuple if all fields are anonymous
let all_anonymous_fields = function.inputs.iter().all(|input| input.name.is_empty()); let all_anonymous_fields = function.inputs.iter().all(|input| input.name.is_empty());
let call_type_definition = if all_anonymous_fields { let call_type_definition = expand_struct(&struct_name, &fields, all_anonymous_fields);
// expand to a tuple struct
expand_data_tuple(&call_name, &fields)
} else {
// expand to a struct
expand_data_struct(&call_name, &fields)
};
let function_name = &function.name; let function_name = &function.name;
let abi_signature = function.abi_signature(); let abi_signature = function.abi_signature();
let doc_str = format!( let doc_str = format!(
"Container type for all input parameters for the `{function_name}` function with signature `{abi_signature}` and selector `0x{}`", "Container type for all input parameters for the `{function_name}` function with signature `{abi_signature}` and selector `0x{}`",
hex::encode(&function.selector()[..]) hex::encode(function.selector())
); );
let ethers_contract = ethers_contract_crate(); let mut extra_derives = self.expand_extra_derives();
// use the same derives as for events if util::can_derive_defaults(&function.inputs) {
let derives = util::expand_derives(&self.event_derives); extra_derives.extend(quote!(Default));
}
// rust-std only derives default automatically for arrays len <= 32 let ethers_contract = ethers_contract_crate();
// for large array types we skip derive(Default) <https://github.com/gakonst/ethers-rs/issues/1640>
let derive_default = if can_derive_defaults(&function.inputs) {
quote! {
#[derive(Default)]
}
} else {
quote! {}
};
Ok(quote! { Ok(quote! {
#[doc = #doc_str] #[doc = #doc_str]
#[derive(Clone, Debug, Eq, PartialEq, #ethers_contract::EthCall, #ethers_contract::EthDisplay, #derives)] #[derive(Clone, Debug, Eq, PartialEq, #ethers_contract::EthCall, #ethers_contract::EthDisplay, #extra_derives)]
#derive_default
#[ethcall( name = #function_name, abi = #abi_signature )] #[ethcall( name = #function_name, abi = #abi_signature )]
pub #call_type_definition pub #call_type_definition
}) })
@ -156,56 +142,46 @@ impl Context {
&self, &self,
function: &Function, function: &Function,
alias: Option<&MethodAlias>, alias: Option<&MethodAlias>,
) -> Result<TokenStream> { ) -> Result<Option<TokenStream>> {
let name = &function.name;
let struct_name = expand_return_struct_name(function, alias);
let fields = self.expand_output_params(function)?;
// no point in having structs when there is no data returned // no point in having structs when there is no data returned
if function.outputs.is_empty() { if function.outputs.is_empty() {
return Ok(TokenStream::new()) return Ok(None)
} }
let name = &function.name;
let struct_name = expand_return_struct_name(function, alias);
let fields = self.expand_output_params(function)?;
// expand as a tuple if all fields are anonymous // expand as a tuple if all fields are anonymous
let all_anonymous_fields = function.outputs.iter().all(|output| output.name.is_empty()); let all_anonymous_fields = function.outputs.iter().all(|output| output.name.is_empty());
let return_type_definition = if all_anonymous_fields { let return_type_definition = expand_struct(&struct_name, &fields, all_anonymous_fields);
// expand to a tuple struct
expand_data_tuple(&struct_name, &fields)
} else {
// expand to a struct
expand_data_struct(&struct_name, &fields)
};
let abi_signature = function.abi_signature(); let abi_signature = function.abi_signature();
let doc_str = format!( let doc_str = format!(
"Container type for all return fields from the `{name}` function with signature `{abi_signature}` and selector `0x{}`", "Container type for all return fields from the `{name}` function with signature `{abi_signature}` and selector `0x{}`",
hex::encode(&function.selector()[..]) hex::encode(function.selector())
); );
let mut extra_derives = self.expand_extra_derives();
if util::can_derive_defaults(&function.inputs) {
extra_derives.extend(quote!(Default));
}
let ethers_contract = ethers_contract_crate(); let ethers_contract = ethers_contract_crate();
// use the same derives as for events
let derives = util::expand_derives(&self.event_derives);
// rust-std only derives default automatically for arrays len <= 32 Ok(Some(quote! {
// for large array types we skip derive(Default) <https://github.com/gakonst/ethers-rs/issues/1640>
let derive_default = if can_derive_defaults(&function.outputs) {
quote! {
#[derive(Default)]
}
} else {
quote! {}
};
Ok(quote! {
#[doc = #doc_str] #[doc = #doc_str]
#[derive(Clone, Debug,Eq, PartialEq, #ethers_contract::EthAbiType, #ethers_contract::EthAbiCodec, #derives)] #[derive(Clone, Debug, Eq, PartialEq, #ethers_contract::EthAbiType, #ethers_contract::EthAbiCodec, #extra_derives)]
#derive_default
pub #return_type_definition pub #return_type_definition
}) }))
} }
/// Expands all call structs /// Expands all call structs
fn expand_call_structs(&self, aliases: BTreeMap<String, MethodAlias>) -> Result<TokenStream> { fn expand_call_structs(&self, aliases: BTreeMap<String, MethodAlias>) -> Result<TokenStream> {
let mut struct_defs = Vec::new(); let len = self.abi.functions.len();
let mut struct_names = Vec::new(); let mut struct_defs = Vec::with_capacity(len);
let mut variant_names = Vec::new(); let mut struct_names = Vec::with_capacity(len);
let mut variant_names = Vec::with_capacity(len);
for function in self.abi.functions.values().flatten() { for function in self.abi.functions.values().flatten() {
let signature = function.abi_signature(); let signature = function.abi_signature();
let alias = aliases.get(&signature); let alias = aliases.get(&signature);
@ -214,86 +190,86 @@ impl Context {
variant_names.push(expand_call_struct_variant_name(function, alias)); variant_names.push(expand_call_struct_variant_name(function, alias));
} }
let struct_def_tokens = quote! { let struct_def_tokens = quote!(#(#struct_defs)*);
#(#struct_defs)*
};
if struct_defs.len() <= 1 { if struct_defs.len() <= 1 {
// no need for an enum // no need for an enum
return Ok(struct_def_tokens) return Ok(struct_def_tokens)
} }
let extra_derives = self.expand_extra_derives();
let enum_name = self.expand_calls_enum_name();
let ethers_core = ethers_core_crate(); let ethers_core = ethers_core_crate();
let ethers_contract = ethers_contract_crate(); let ethers_contract = ethers_contract_crate();
// use the same derives as for events let tokens = quote! {
let derives = util::expand_derives(&self.event_derives);
let enum_name = self.expand_calls_enum_name();
Ok(quote! {
#struct_def_tokens #struct_def_tokens
#[derive(Debug, Clone, PartialEq, Eq, #ethers_contract::EthAbiType, #derives)] #[doc = "Container type for all of the contract's call "]
#[derive(Debug, Clone, PartialEq, Eq, #ethers_contract::EthAbiType, #extra_derives)]
pub enum #enum_name { pub enum #enum_name {
#(#variant_names(#struct_names)),* #( #variant_names(#struct_names), )*
} }
impl #ethers_core::abi::AbiDecode for #enum_name { impl #ethers_core::abi::AbiDecode for #enum_name {
fn decode(data: impl AsRef<[u8]>) -> ::std::result::Result<Self, #ethers_core::abi::AbiError> { fn decode(data: impl AsRef<[u8]>) -> ::core::result::Result<Self, #ethers_core::abi::AbiError> {
#( let data = data.as_ref();
if let Ok(decoded) = <#struct_names as #ethers_core::abi::AbiDecode>::decode(data.as_ref()) { #(
return Ok(#enum_name::#variant_names(decoded)) if let Ok(decoded) = <#struct_names as #ethers_core::abi::AbiDecode>::decode(data) {
return Ok(Self::#variant_names(decoded))
}
)*
Err(#ethers_core::abi::Error::InvalidData.into())
}
}
impl #ethers_core::abi::AbiEncode for #enum_name {
fn encode(self) -> Vec<u8> {
match self {
#(
Self::#variant_names(element) => #ethers_core::abi::AbiEncode::encode(element),
)*
} }
)*
Err(#ethers_core::abi::Error::InvalidData.into())
}
}
impl #ethers_core::abi::AbiEncode for #enum_name {
fn encode(self) -> Vec<u8> {
match self {
#(
#enum_name::#variant_names(element) => element.encode()
),*
} }
} }
}
impl ::std::fmt::Display for #enum_name { impl ::core::fmt::Display for #enum_name {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
match self { match self {
#( #(
#enum_name::#variant_names(element) => element.fmt(f) Self::#variant_names(element) => ::core::fmt::Display::fmt(element, f),
),* )*
}
} }
} }
}
#( #(
impl ::std::convert::From<#struct_names> for #enum_name { impl ::core::convert::From<#struct_names> for #enum_name {
fn from(var: #struct_names) -> Self { fn from(value: #struct_names) -> Self {
#enum_name::#variant_names(var) Self::#variant_names(value)
}
} }
} )*
)* };
}) Ok(tokens)
} }
/// Expands all return structs /// Expands all return structs
fn expand_return_structs(&self, aliases: BTreeMap<String, MethodAlias>) -> Result<TokenStream> { fn expand_return_structs(&self, aliases: BTreeMap<String, MethodAlias>) -> Result<TokenStream> {
let mut struct_defs = Vec::new(); let mut tokens = TokenStream::new();
for function in self.abi.functions.values().flatten() { for function in self.abi.functions.values().flatten() {
let signature = function.abi_signature(); let signature = function.abi_signature();
let alias = aliases.get(&signature); let alias = aliases.get(&signature);
struct_defs.push(self.expand_return_struct(function, alias)?); match self.expand_return_struct(function, alias) {
Ok(Some(def)) => tokens.extend(def),
Ok(None) => {}
Err(e) => return Err(e),
}
} }
Ok(tokens)
let struct_def_tokens = quote! {
#(#struct_defs)*
};
Ok(struct_def_tokens)
} }
/// The name ident of the calls enum /// The name ident of the calls enum
@ -597,7 +573,7 @@ impl Context {
fn expand_selector(selector: Selector) -> TokenStream { fn expand_selector(selector: Selector) -> TokenStream {
let bytes = selector.iter().copied().map(Literal::u8_unsuffixed); let bytes = selector.iter().copied().map(Literal::u8_unsuffixed);
quote! { [#( #bytes ),*] } quote!([ #( #bytes ),* ])
} }
/// Represents the aliases to use when generating method related elements /// Represents the aliases to use when generating method related elements

View File

@ -1,8 +1,7 @@
//! Methods for expanding structs //! Structs expansion
use crate::{
contract::{types, Context}, use super::{types, Context};
util, use crate::util;
};
use ethers_core::{ use ethers_core::{
abi::{ abi::{
struct_def::{FieldDeclaration, FieldType, StructFieldType, StructType}, struct_def::{FieldDeclaration, FieldType, StructFieldType, StructType},
@ -15,6 +14,7 @@ use inflector::Inflector;
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use quote::quote; use quote::quote;
use std::collections::{HashMap, VecDeque}; use std::collections::{HashMap, VecDeque};
use syn::Ident;
impl Context { impl Context {
/// Generate corresponding types for structs parsed from a human readable ABI /// Generate corresponding types for structs parsed from a human readable ABI
@ -51,17 +51,17 @@ impl Context {
/// Generates the type definition for the name that matches the given identifier /// Generates the type definition for the name that matches the given identifier
fn generate_internal_struct(&self, id: &str) -> Result<TokenStream> { fn generate_internal_struct(&self, id: &str) -> Result<TokenStream> {
let sol_struct = let sol_struct =
self.internal_structs.structs.get(id).ok_or_else(|| eyre!("struct not found"))?; self.internal_structs.structs.get(id).ok_or_else(|| eyre!("Struct not found"))?;
let struct_name = self let struct_name = self
.internal_structs .internal_structs
.rust_type_names .rust_type_names
.get(id) .get(id)
.ok_or_else(|| eyre!("No types found for {}", id))?; .ok_or_else(|| eyre!("No types found for {id}"))?;
let tuple = self let tuple = self
.internal_structs .internal_structs
.struct_tuples .struct_tuples
.get(id) .get(id)
.ok_or_else(|| eyre!("No types found for {}", id))? .ok_or_else(|| eyre!("No types found for {id}"))?
.clone(); .clone();
self.expand_internal_struct(struct_name, sol_struct, tuple) self.expand_internal_struct(struct_name, sol_struct, tuple)
} }
@ -95,33 +95,22 @@ impl Context {
FieldType::Elementary(ty) => types::expand(ty)?, FieldType::Elementary(ty) => types::expand(ty)?,
FieldType::Struct(struct_ty) => types::expand_struct_type(struct_ty), FieldType::Struct(struct_ty) => types::expand_struct_type(struct_ty),
FieldType::Mapping(_) => { FieldType::Mapping(_) => {
eyre::bail!("Mapping types in struct `{}` are not supported {:?}", name, field) eyre::bail!("Mapping types in struct `{name}` are not supported")
} }
}; };
if is_tuple { let field_name = if is_tuple {
fields.push(quote!(pub #ty)); TokenStream::new()
} else { } else {
let field_name = util::safe_ident(&field.name().to_snake_case()); let field_name = util::safe_ident(&field.name().to_snake_case());
fields.push(quote! { pub #field_name: #ty }); quote!(#field_name)
} };
fields.push((field_name, ty));
} }
let name = util::ident(name); let name = util::ident(name);
let struct_def = if is_tuple { let struct_def = expand_struct(&name, &fields, is_tuple);
quote! {
pub struct #name(
#( #fields ),*
);
}
} else {
quote! {
pub struct #name {
#( #fields ),*
}
}
};
let sig = match tuple { let sig = match tuple {
ParamType::Tuple(ref types) if !types.is_empty() => util::abi_signature_types(types), ParamType::Tuple(ref types) if !types.is_empty() => util::abi_signature_types(types),
@ -129,20 +118,20 @@ impl Context {
}; };
let doc_str = format!("`{name}({sig})`"); let doc_str = format!("`{name}({sig})`");
// use the same derives as for events let extra_derives = self.expand_extra_derives();
let derives = util::expand_derives(&self.event_derives);
let ethers_contract = ethers_contract_crate(); let ethers_contract = ethers_contract_crate();
Ok(quote! { Ok(quote! {
#[doc = #doc_str] #[doc = #doc_str]
#[derive(Clone, Debug, Default, Eq, PartialEq, #ethers_contract::EthAbiType, #ethers_contract::EthAbiCodec, #derives)] #[derive(Clone, Debug, Default, Eq, PartialEq, #ethers_contract::EthAbiType, #ethers_contract::EthAbiCodec, #extra_derives)]
#struct_def pub #struct_def
}) })
} }
fn generate_human_readable_struct(&self, name: &str) -> Result<TokenStream> { fn generate_human_readable_struct(&self, name: &str) -> Result<TokenStream> {
let sol_struct = let sol_struct =
self.abi_parser.structs.get(name).ok_or_else(|| eyre!("struct not found"))?; self.abi_parser.structs.get(name).ok_or_else(|| eyre!("Struct `{name}` not found"))?;
let mut fields = Vec::with_capacity(sol_struct.fields().len()); let mut fields = Vec::with_capacity(sol_struct.fields().len());
let mut param_types = Vec::with_capacity(sol_struct.fields().len()); let mut param_types = Vec::with_capacity(sol_struct.fields().len());
for field in sol_struct.fields() { for field in sol_struct.fields() {
@ -162,14 +151,14 @@ impl Context {
.abi_parser .abi_parser
.struct_tuples .struct_tuples
.get(name) .get(name)
.ok_or_else(|| eyre!("No types found for {}", name))? .ok_or_else(|| eyre!("No types found for {name}"))?
.clone(); .clone();
let tuple = ParamType::Tuple(tuple); let tuple = ParamType::Tuple(tuple);
param_types.push(struct_ty.as_param(tuple)); param_types.push(struct_ty.as_param(tuple));
} }
FieldType::Mapping(_) => { FieldType::Mapping(_) => {
eyre::bail!("Mapping types in struct `{}` are not supported {:?}", name, field) eyre::bail!("Mapping types in struct `{name}` are not supported")
} }
} }
} }
@ -178,15 +167,16 @@ impl Context {
let name = util::ident(name); let name = util::ident(name);
// use the same derives as for events let mut extra_derives = self.expand_extra_derives();
let derives = &self.event_derives; if param_types.iter().all(util::can_derive_default) {
let derives = quote! {#(#derives),*}; extra_derives.extend(quote!(Default))
}
let ethers_contract = ethers_contract_crate(); let ethers_contract = ethers_contract_crate();
Ok(quote! { Ok(quote! {
#[doc = #abi_signature] #[doc = #abi_signature]
#[derive(Clone, Debug, Default, Eq, PartialEq, #ethers_contract::EthAbiType, #ethers_contract::EthAbiCodec, #derives)] #[derive(Clone, Debug, Eq, PartialEq, #ethers_contract::EthAbiType, #ethers_contract::EthAbiCodec, #extra_derives)]
pub struct #name { pub struct #name {
#( #fields ),* #( #fields ),*
} }
@ -580,7 +570,7 @@ fn struct_type_name(name: &str) -> &str {
} }
/// `Pairing.G2Point` -> `Pairing.G2Point` /// `Pairing.G2Point` -> `Pairing.G2Point`
pub fn struct_type_identifier(name: &str) -> &str { fn struct_type_identifier(name: &str) -> &str {
name.trim_start_matches("struct ").split('[').next().unwrap() name.trim_start_matches("struct ").split('[').next().unwrap()
} }
@ -592,16 +582,56 @@ fn struct_type_projections(name: &str) -> Vec<String> {
iter.rev().map(str::to_string).collect() iter.rev().map(str::to_string).collect()
} }
pub(crate) fn expand_struct(
name: &Ident,
fields: &[(TokenStream, TokenStream)],
is_tuple: bool,
) -> TokenStream {
_expand_struct(name, fields.iter().map(|(a, b)| (a, b, false)), is_tuple)
}
pub(crate) fn expand_event_struct(
name: &Ident,
fields: &[(TokenStream, TokenStream, bool)],
is_tuple: bool,
) -> TokenStream {
_expand_struct(name, fields.iter().map(|(a, b, c)| (a, b, *c)), is_tuple)
}
fn _expand_struct<'a>(
name: &Ident,
fields: impl Iterator<Item = (&'a TokenStream, &'a TokenStream, bool)>,
is_tuple: bool,
) -> TokenStream {
let fields = fields.map(|(field, ty, indexed)| {
(field, ty, if indexed { Some(quote!(#[ethevent(indexed)])) } else { None })
});
let fields = if let Some(0) = fields.size_hint().1 {
// unit struct
quote!(;)
} else if is_tuple {
// tuple struct
let fields = fields.map(|(_, ty, indexed)| quote!(#indexed pub #ty));
quote!(( #( #fields ),* );)
} else {
// struct
let fields = fields.map(|(field, ty, indexed)| quote!(#indexed pub #field: #ty));
quote!({ #( #fields, )* })
};
quote!(struct #name #fields)
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
#[test] #[test]
fn can_determine_structs() { fn can_determine_structs() {
const VERIFIER_ABI: &str = const VERIFIER_ABI: &str =
include_str!("../../../tests/solidity-contracts/verifier_abi.json"); include_str!("../../../tests/solidity-contracts/verifier_abi.json");
let abi = serde_json::from_str::<RawAbi>(VERIFIER_ABI).unwrap(); let abi = serde_json::from_str::<RawAbi>(VERIFIER_ABI).unwrap();
let internal = InternalStructs::new(abi); let _internal = InternalStructs::new(abi);
dbg!(internal.rust_type_names);
} }
} }

View File

@ -11,7 +11,7 @@ use proc_macro2::{Literal, TokenStream};
use quote::{quote, ToTokens}; use quote::{quote, ToTokens};
/// Expands a ParamType Solidity type to its Rust equivalent. /// Expands a ParamType Solidity type to its Rust equivalent.
pub fn expand(kind: &ParamType) -> Result<TokenStream> { pub(crate) fn expand(kind: &ParamType) -> Result<TokenStream> {
let ethers_core = ethers_core_crate(); let ethers_core = ethers_core_crate();
match kind { match kind {
@ -58,7 +58,7 @@ pub fn expand(kind: &ParamType) -> Result<TokenStream> {
} }
/// Expands the event's inputs. /// Expands the event's inputs.
pub fn expand_event_inputs( pub(crate) fn expand_event_inputs(
event: &Event, event: &Event,
internal_structs: &InternalStructs, internal_structs: &InternalStructs,
) -> Result<Vec<(TokenStream, TokenStream, bool)>> { ) -> Result<Vec<(TokenStream, TokenStream, bool)>> {
@ -121,7 +121,7 @@ fn expand_event_input(
/// Expands `params` to `(name, type)` tokens pairs, while resolving tuples' types using the given /// Expands `params` to `(name, type)` tokens pairs, while resolving tuples' types using the given
/// function. /// function.
pub fn expand_params<'a, 'b, F: Fn(&'a Param) -> Option<&'b str>>( pub(crate) fn expand_params<'a, 'b, F: Fn(&'a Param) -> Option<&'b str>>(
params: &'a [Param], params: &'a [Param],
resolve_tuple: F, resolve_tuple: F,
) -> Result<Vec<(TokenStream, TokenStream)>> { ) -> Result<Vec<(TokenStream, TokenStream)>> {
@ -160,7 +160,7 @@ fn expand_resolved<'a, 'b, F: Fn(&'a Param) -> Option<&'b str>>(
} }
/// Expands to the Rust struct type. /// Expands to the Rust struct type.
pub fn expand_struct_type(struct_ty: &StructFieldType) -> TokenStream { pub(crate) fn expand_struct_type(struct_ty: &StructFieldType) -> TokenStream {
match struct_ty { match struct_ty {
StructFieldType::Type(ty) => { StructFieldType::Type(ty) => {
let ty = util::ident(&ty.name().to_pascal_case()); let ty = util::ident(&ty.name().to_pascal_case());

View File

@ -8,6 +8,7 @@
//! [abigen]: https://docs.rs/ethers/latest/ethers/contract/macro.abigen.html //! [abigen]: https://docs.rs/ethers/latest/ethers/contract/macro.abigen.html
#![deny(rustdoc::broken_intra_doc_links, missing_docs, unsafe_code)] #![deny(rustdoc::broken_intra_doc_links, missing_docs, unsafe_code)]
#![warn(unreachable_pub)]
#[cfg(test)] #[cfg(test)]
#[allow(missing_docs)] #[allow(missing_docs)]
@ -25,6 +26,8 @@ pub mod multi;
pub use multi::MultiAbigen; pub use multi::MultiAbigen;
mod source; mod source;
#[cfg(all(feature = "online", not(target_arch = "wasm32")))]
pub use source::Explorer;
pub use source::Source; pub use source::Source;
mod util; mod util;

View File

@ -9,10 +9,14 @@ use url::Url;
/// An [etherscan](https://etherscan.io)-like blockchain explorer. /// An [etherscan](https://etherscan.io)-like blockchain explorer.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Explorer { pub enum Explorer {
/// <https://etherscan.io>
#[default] #[default]
Etherscan, Etherscan,
/// <https://bscscan.com>
Bscscan, Bscscan,
/// <https://polygonscan.com>
Polygonscan, Polygonscan,
/// <https://snowtrace.io>
Snowtrace, Snowtrace,
} }

View File

@ -3,35 +3,38 @@ use eyre::Result;
use inflector::Inflector; use inflector::Inflector;
use proc_macro2::{Ident, Span, TokenStream}; use proc_macro2::{Ident, Span, TokenStream};
use quote::quote; use quote::quote;
use std::path::PathBuf; use std::path::{Path, PathBuf};
use syn::{Ident as SynIdent, Path};
/// Expands a identifier string into a token. /// Creates a new Ident with the given string at [`Span::call_site`].
pub fn ident(name: &str) -> Ident { ///
/// # Panics
///
/// If the input string is neither a keyword nor a legal variable name.
pub(crate) fn ident(name: &str) -> Ident {
Ident::new(name, Span::call_site()) Ident::new(name, Span::call_site())
} }
/// Expands an identifier string into a token and appending `_` if the /// Expands an identifier string into a token and appending `_` if the identifier is for a reserved
/// identifier is for a reserved keyword. /// keyword.
/// ///
/// Parsing keywords like `self` can fail, in this case we add an underscore. /// Parsing keywords like `self` can fail, in this case we add an underscore.
pub fn safe_ident(name: &str) -> Ident { pub(crate) fn safe_ident(name: &str) -> Ident {
syn::parse_str::<SynIdent>(name).unwrap_or_else(|_| ident(&format!("{name}_"))) syn::parse_str::<Ident>(name).unwrap_or_else(|_| ident(&format!("{name}_")))
} }
/// Converts a `&str` to `snake_case` `String` while respecting identifier rules /// Converts a `&str` to `snake_case` `String` while respecting identifier rules
pub fn safe_snake_case(ident: &str) -> String { pub(crate) fn safe_snake_case(ident: &str) -> String {
safe_identifier_name(ident.to_snake_case()) safe_identifier_name(ident.to_snake_case())
} }
/// Converts a `&str` to `PascalCase` `String` while respecting identifier rules /// Converts a `&str` to `PascalCase` `String` while respecting identifier rules
pub fn safe_pascal_case(ident: &str) -> String { pub(crate) fn safe_pascal_case(ident: &str) -> String {
safe_identifier_name(ident.to_pascal_case()) safe_identifier_name(ident.to_pascal_case())
} }
/// respects identifier rules, such as, an identifier must not start with a numeric char /// respects identifier rules, such as, an identifier must not start with a numeric char
fn safe_identifier_name(name: String) -> String { pub(crate) fn safe_identifier_name(name: String) -> String {
if name.starts_with(|c: char| c.is_numeric()) { if name.starts_with(char::is_numeric) {
format!("_{name}") format!("_{name}")
} else { } else {
name name
@ -39,39 +42,46 @@ fn safe_identifier_name(name: String) -> String {
} }
/// converts invalid rust module names to valid ones /// converts invalid rust module names to valid ones
pub fn safe_module_name(name: &str) -> String { pub(crate) fn safe_module_name(name: &str) -> String {
// handle reserve words used in contracts (eg Enum is a gnosis contract) // handle reserve words used in contracts (eg Enum is a gnosis contract)
safe_ident(&safe_snake_case(name)).to_string() safe_ident(&safe_snake_case(name)).to_string()
} }
/// Expands an identifier as snakecase and preserve any leading or trailing underscores /// Expands an identifier as snakecase and preserve any leading or trailing underscores
pub fn safe_snake_case_ident(name: &str) -> Ident { pub(crate) fn safe_snake_case_ident(name: &str) -> Ident {
let i = name.to_snake_case(); let i = name.to_snake_case();
ident(&preserve_underscore_delim(&i, name)) ident(&preserve_underscore_delim(&i, name))
} }
/// Expands an identifier as pascal case and preserve any leading or trailing underscores /// Expands an identifier as pascal case and preserve any leading or trailing underscores
pub fn safe_pascal_case_ident(name: &str) -> Ident { pub(crate) fn safe_pascal_case_ident(name: &str) -> Ident {
let i = name.to_pascal_case(); let i = name.to_pascal_case();
ident(&preserve_underscore_delim(&i, name)) ident(&preserve_underscore_delim(&i, name))
} }
/// Reapplies leading and trailing underscore chars to the ident /// Reapplies leading and trailing underscore chars to the ident
/// Example `ident = "pascalCase"; alias = __pascalcase__` -> `__pascalCase__` ///
pub fn preserve_underscore_delim(ident: &str, alias: &str) -> String { /// # Example
alias ///
.chars() /// ```ignore
.take_while(|c| *c == '_') /// # use ethers_contract_abigen::util::preserve_underscore_delim;
.chain(ident.chars()) /// assert_eq!(
.chain(alias.chars().rev().take_while(|c| *c == '_')) /// preserve_underscore_delim("pascalCase", "__pascalcase__"),
.collect() /// "__pascalCase__"
/// );
/// ```
pub(crate) fn preserve_underscore_delim(ident: &str, original: &str) -> String {
let is_underscore = |c: &char| *c == '_';
let pre = original.chars().take_while(is_underscore);
let post = original.chars().rev().take_while(is_underscore);
pre.chain(ident.chars()).chain(post).collect()
} }
/// Expands a positional identifier string that may be empty. /// Expands a positional identifier string that may be empty.
/// ///
/// Note that this expands the parameter name with `safe_ident`, meaning that /// Note that this expands the parameter name with `safe_ident`, meaning that
/// identifiers that are reserved keywords get `_` appended to them. /// identifiers that are reserved keywords get `_` appended to them.
pub fn expand_input_name(index: usize, name: &str) -> TokenStream { pub(crate) fn expand_input_name(index: usize, name: &str) -> TokenStream {
let name_str = match name { let name_str = match name {
"" => format!("p{index}"), "" => format!("p{index}"),
n => n.to_snake_case(), n => n.to_snake_case(),
@ -81,18 +91,14 @@ pub fn expand_input_name(index: usize, name: &str) -> TokenStream {
quote! { #name } quote! { #name }
} }
pub fn expand_derives(derives: &[Path]) -> TokenStream {
quote! {#(#derives),*}
}
/// Perform a blocking HTTP GET request and return the contents of the response as a String. /// Perform a blocking HTTP GET request and return the contents of the response as a String.
#[cfg(all(feature = "online", not(target_arch = "wasm32")))] #[cfg(all(feature = "online", not(target_arch = "wasm32")))]
pub fn http_get(url: impl reqwest::IntoUrl) -> Result<String> { pub(crate) fn http_get(url: impl reqwest::IntoUrl) -> Result<String> {
Ok(reqwest::blocking::get(url)?.text()?) Ok(reqwest::blocking::get(url)?.text()?)
} }
/// Replaces any occurrences of env vars in the `raw` str with their value /// Replaces any occurrences of env vars in the `raw` str with their value
pub fn resolve_path(raw: &str) -> Result<PathBuf> { pub(crate) fn resolve_path(raw: &str) -> Result<PathBuf> {
let mut unprocessed = raw; let mut unprocessed = raw;
let mut resolved = String::new(); let mut resolved = String::new();
@ -107,7 +113,7 @@ pub fn resolve_path(raw: &str) -> Result<PathBuf> {
unprocessed = rest; unprocessed = rest;
} }
None => { None => {
eyre::bail!("Unable to parse a variable from \"{}\"", tail) eyre::bail!("Unable to parse a variable from \"{tail}\"")
} }
} }
} }
@ -149,7 +155,7 @@ fn take_while(s: &str, mut predicate: impl FnMut(char) -> bool) -> (&str, &str)
} }
/// Returns a list of absolute paths to all the json files under the root /// Returns a list of absolute paths to all the json files under the root
pub fn json_files(root: impl AsRef<std::path::Path>) -> Vec<PathBuf> { pub(crate) fn json_files(root: impl AsRef<Path>) -> Vec<PathBuf> {
walkdir::WalkDir::new(root) walkdir::WalkDir::new(root)
.into_iter() .into_iter()
.filter_map(Result::ok) .filter_map(Result::ok)
@ -162,14 +168,14 @@ pub fn json_files(root: impl AsRef<std::path::Path>) -> Vec<PathBuf> {
/// Returns whether all the given parameters can derive [`Default`]. /// Returns whether all the given parameters can derive [`Default`].
/// ///
/// rust-std derives `Default` automatically only for arrays len <= 32 /// rust-std derives `Default` automatically only for arrays len <= 32
pub fn can_derive_defaults<'a>(params: impl IntoIterator<Item = &'a Param>) -> bool { pub(crate) fn can_derive_defaults<'a>(params: impl IntoIterator<Item = &'a Param>) -> bool {
params.into_iter().map(|param| &param.kind).all(can_derive_default) params.into_iter().map(|param| &param.kind).all(can_derive_default)
} }
/// Returns whether the given type can derive [`Default`]. /// Returns whether the given type can derive [`Default`].
/// ///
/// rust-std derives `Default` automatically only for arrays len <= 32 /// rust-std derives `Default` automatically only for arrays len <= 32
pub fn can_derive_default(param: &ParamType) -> bool { pub(crate) fn can_derive_default(param: &ParamType) -> bool {
const MAX_SUPPORTED_LEN: usize = 32; const MAX_SUPPORTED_LEN: usize = 32;
match param { match param {
ParamType::FixedBytes(len) => *len <= MAX_SUPPORTED_LEN, ParamType::FixedBytes(len) => *len <= MAX_SUPPORTED_LEN,
@ -186,7 +192,7 @@ pub fn can_derive_default(param: &ParamType) -> bool {
} }
/// Returns the formatted Solidity ABI signature. /// Returns the formatted Solidity ABI signature.
pub fn abi_signature<'a, N, T>(name: N, types: T) -> String pub(crate) fn abi_signature<'a, N, T>(name: N, types: T) -> String
where where
N: std::fmt::Display, N: std::fmt::Display,
T: IntoIterator<Item = &'a ParamType>, T: IntoIterator<Item = &'a ParamType>,
@ -196,7 +202,7 @@ where
} }
/// Returns the Solidity stringified ABI types joined by a single comma. /// Returns the Solidity stringified ABI types joined by a single comma.
pub fn abi_signature_types<'a, T: IntoIterator<Item = &'a ParamType>>(types: T) -> String { pub(crate) fn abi_signature_types<'a, T: IntoIterator<Item = &'a ParamType>>(types: T) -> String {
types.into_iter().map(ToString::to_string).collect::<Vec<_>>().join(",") types.into_iter().map(ToString::to_string).collect::<Vec<_>>().join(",")
} }

View File

@ -21,9 +21,9 @@ use syn::{
}; };
/// A series of `ContractArgs` separated by `;` /// A series of `ContractArgs` separated by `;`
#[cfg_attr(test, derive(Debug))] #[derive(Clone, Debug)]
pub(crate) struct Contracts { pub(crate) struct Contracts {
inner: Vec<(Span, ContractArgs)>, pub(crate) inner: Vec<(Span, ContractArgs)>,
} }
impl Contracts { impl Contracts {
@ -57,7 +57,7 @@ impl Parse for Contracts {
} }
/// Contract procedural macro arguments. /// Contract procedural macro arguments.
#[cfg_attr(test, derive(Debug, Eq, PartialEq))] #[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct ContractArgs { pub(crate) struct ContractArgs {
name: String, name: String,
abi: String, abi: String,
@ -128,7 +128,7 @@ impl ParseInner for ContractArgs {
} }
/// A single procedural macro parameter. /// A single procedural macro parameter.
#[cfg_attr(test, derive(Debug, Eq, PartialEq))] #[derive(Clone, Debug, PartialEq, Eq)]
enum Parameter { enum Parameter {
Methods(Vec<Method>), Methods(Vec<Method>),
Derives(Vec<String>), Derives(Vec<String>),
@ -189,7 +189,7 @@ impl Parse for Parameter {
} }
/// An explicitely named contract method. /// An explicitely named contract method.
#[cfg_attr(test, derive(Debug, Eq, PartialEq))] #[derive(Clone, Debug, PartialEq, Eq)]
struct Method { struct Method {
signature: String, signature: String,
alias: String, alias: String,

View File

@ -205,9 +205,9 @@ fn derive_decode_from_log_impl(
// decode // decode
let (signature_check, flat_topics_init, topic_tokens_len_check) = if event.anonymous { let (signature_check, flat_topics_init, topic_tokens_len_check) = if event.anonymous {
( (
quote! {}, None,
quote! { quote! {
let flat_topics = topics.iter().flat_map(|t| t.as_ref().to_vec()).collect::<Vec<u8>>(); let flat_topics = topics.iter().flat_map(|t| t.as_ref().to_vec()).collect::<Vec<u8>>();
}, },
quote! { quote! {
if topic_tokens.len() != topics.len() { if topic_tokens.len() != topics.len() {
@ -217,12 +217,12 @@ fn derive_decode_from_log_impl(
) )
} else { } else {
( (
quote! { Some(quote! {
let event_signature = topics.get(0).ok_or(#ethers_core::abi::Error::InvalidData)?; let event_signature = topics.get(0).ok_or(#ethers_core::abi::Error::InvalidData)?;
if event_signature != &Self::signature() { if event_signature != &Self::signature() {
return Err(#ethers_core::abi::Error::InvalidData); return Err(#ethers_core::abi::Error::InvalidData);
} }
}, }),
quote! { quote! {
let flat_topics = topics.iter().skip(1).flat_map(|t| t.as_ref().to_vec()).collect::<Vec<u8>>(); let flat_topics = topics.iter().skip(1).flat_map(|t| t.as_ref().to_vec()).collect::<Vec<u8>>();
}, },