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.
mod common;
mod errors;
mod events;
mod methods;
@ -52,14 +51,19 @@ impl ExpandedContract {
abi_structs,
errors,
} = self;
quote! {
// export all the created data types
pub use #module::*;
/// This module was auto-generated with ethers-rs Abigen.
/// More information at: <https://github.com/gakonst/ethers-rs>
#[allow(
clippy::enum_variant_names,
clippy::too_many_arguments,
clippy::upper_case_acronyms,
clippy::type_complexity,
dead_code,
non_camel_case_types,
clippy::upper_case_acronyms
)]
pub mod #module {
#imports
@ -103,7 +107,7 @@ pub struct Context {
error_aliases: BTreeMap<String, Ident>,
/// Derives added to event structs and enums.
event_derives: Vec<Path>,
extra_derives: Vec<Path>,
/// Manually specified event aliases.
event_aliases: BTreeMap<String, Ident>,
@ -122,11 +126,8 @@ impl Context {
let name_mod = util::ident(&util::safe_module_name(&self.contract_name));
let abi_name = self.inline_abi_ident();
// 0. Imports
let imports = common::imports(&name.to_string());
// 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
let events_decl = self.events_declaration()?;
@ -137,7 +138,7 @@ impl Context {
// 4. impl block for the contract methods and their corresponding types
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();
// 6. Declare the structs parsed from the human readable abi
@ -154,9 +155,8 @@ impl Context {
#struct_decl
impl<M: #ethers_providers::Middleware> #name<M> {
/// Creates a new contract instance with the specified `ethers`
/// client at the given `Address`. The contract derefs to a `ethers::Contract`
/// object
/// Creates a new contract instance with the specified `ethers` client at
/// `address`. The contract derefs to a `ethers::Contract` object.
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))
}
@ -166,7 +166,6 @@ impl Context {
#contract_methods
#contract_events
}
impl<M: #ethers_providers::Middleware> From<#ethers_contract::Contract<M>> for #name<M> {
@ -178,7 +177,7 @@ impl Context {
Ok(ExpandedContract {
module: name_mod,
imports,
imports: quote!(),
contract,
events: events_decl,
errors: errors_decl,
@ -282,12 +281,12 @@ impl Context {
);
}
let event_derives = args
let extra_derives = args
.derives
.iter()
.map(|derive| syn::parse_str::<Path>(derive))
.collect::<Result<Vec<_>, _>>()
.context("failed to parse event derives")?;
.wrap_err("failed to parse event derives")?;
Ok(Context {
abi,
@ -301,7 +300,7 @@ impl Context {
contract_deployed_bytecode,
method_aliases,
error_aliases: Default::default(),
event_derives,
extra_derives,
event_aliases,
})
}
@ -335,6 +334,116 @@ impl Context {
pub fn internal_structs_mut(&mut self) -> &mut InternalStructs {
&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,

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::{
common::{expand_data_struct, expand_data_tuple},
types, util, Context,
};
use super::{structs::expand_struct, types, util, Context};
use ethers_core::{
abi::{ethabi::AbiError, ErrorExt},
macros::{ethers_contract_crate, ethers_core_crate},
@ -25,57 +22,46 @@ impl Context {
.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! {}
};
let errors_enum_decl =
if data_types.len() > 1 { Some(self.expand_errors_enum()) } else { None };
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 error_name = &error.name;
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)?;
// 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 data_type_definition = expand_struct(&error_struct_name, &fields, all_anonymous_fields);
let doc_str = format!(
"Custom Error type `{}` with signature `{}` and selector `0x{}`",
error.name,
abi_signature,
hex::encode(&error.selector()[..])
"Custom Error type `{error_name}` with signature `{abi_signature}` and selector `0x{}`",
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! {
#[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)]
pub #data_type_definition
})
@ -95,6 +81,7 @@ impl Context {
/// Generate an enum with a variant for each event
fn expand_errors_enum(&self) -> TokenStream {
let enum_name = self.expand_error_enum_name();
let variants = self
.abi
.errors
@ -105,24 +92,24 @@ impl Context {
})
.collect::<Vec<_>>();
let extra_derives = self.expand_extra_derives();
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)]
#[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 {
#(#variants(#variants)),*
#( #variants(#variants), )*
}
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())
@ -130,33 +117,32 @@ impl Context {
}
impl #ethers_core::abi::AbiEncode for #enum_name {
fn encode(self) -> Vec<u8> {
fn encode(self) -> ::std::vec::Vec<u8> {
match self {
#(
#enum_name::#variants(element) => element.encode()
),*
Self::#variants(element) => #ethers_core::abi::AbiEncode::encode(element),
)*
}
}
}
impl ::std::fmt::Display for #enum_name {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
impl ::core::fmt::Display for #enum_name {
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
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 {
fn from(var: #variants) -> Self {
#enum_name::#variants(var)
impl ::core::convert::From<#variants> for #enum_name {
fn from(value: #variants) -> Self {
Self::#variants(value)
}
}
)*
}
}
}

View File

@ -1,7 +1,9 @@
use super::{types, util, Context};
use crate::util::can_derive_defaults;
//! Events expansion
use super::{structs::expand_event_struct, types, Context};
use crate::util;
use ethers_core::{
abi::{Event, EventExt, Param},
abi::{Event, EventExt},
macros::{ethers_contract_crate, ethers_core_crate},
};
use eyre::Result;
@ -21,11 +23,8 @@ impl Context {
.collect::<Result<Vec<_>>>()?;
// only expand enums when multiple events are present
let events_enum_decl = if sorted_events.values().flatten().count() > 1 {
self.expand_events_enum()
} else {
quote! {}
};
let events_enum_decl =
if data_types.len() > 1 { Some(self.expand_events_enum()) } else { None };
Ok(quote! {
#( #data_types )*
@ -40,8 +39,7 @@ impl Context {
let filter_methods = sorted_events
.values()
.flat_map(std::ops::Deref::deref)
.map(|event| self.expand_filter(event))
.collect::<Vec<_>>();
.map(|event| self.expand_filter(event));
let events_method = self.expand_events_method();
@ -67,21 +65,18 @@ impl Context {
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 extra_derives = self.expand_extra_derives();
let enum_name = self.expand_event_enum_name();
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 {
#(#variants(#variants)),*
#( #variants(#variants), )*
}
impl #ethers_contract::EthLogDecode for #enum_name {
fn decode_log(log: &#ethers_core::abi::RawLog) -> ::std::result::Result<Self, #ethers_core::abi::Error>
where
Self: Sized,
{
fn decode_log(log: &#ethers_core::abi::RawLog) -> ::core::result::Result<Self, #ethers_core::abi::Error> {
#(
if let Ok(decoded) = #variants::decode_log(log) {
return Ok(#enum_name::#variants(decoded))
@ -91,15 +86,23 @@ impl Context {
}
}
impl ::std::fmt::Display for #enum_name {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
impl ::core::fmt::Display for #enum_name {
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
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
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 mut iter = sorted_events.values().flatten();
@ -125,28 +128,35 @@ impl Context {
)
};
quote! {
/// Returns an [`Event`](#ethers_contract::builders::Event) builder for all events of this contract
pub fn events(&self) -> #ethers_contract::builders::Event<Arc<M>, M, #ty> {
self.0.event_with_filter(Default::default())
}
Some(quote! {
/// Returns an `Event` builder for all the events of this contract.
pub fn events(&self) -> #ethers_contract::builders::Event<
::std::sync::Arc<M>,
M,
#ty,
> {
self.0.event_with_filter(::core::default::Default::default())
}
})
} else {
quote! {}
None
}
}
/// Expands into a single method for contracting an event stream.
fn expand_filter(&self, event: &Event) -> TokenStream {
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
// function names
let function_name = if let Some(id) = alias.clone() {
util::safe_ident(&format!("{}_filter", id.to_string().to_snake_case()))
// append `filter` to disambiguate with potentially conflicting function names
let function_name = {
let name = if let Some(ref id) = alias {
id.to_string().to_snake_case()
} 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);
@ -156,7 +166,11 @@ impl Context {
quote! {
#[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()
}
}
@ -166,49 +180,27 @@ impl Context {
/// into a structure or a tuple in the case where all event parameters (topics
/// and data) are anonymous.
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 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
let all_anonymous_fields = event.inputs.iter().all(|input| input.name.is_empty());
let data_type_definition = if all_anonymous_fields {
expand_data_tuple(&event_name, &params)
} else {
expand_data_struct(&event_name, &params)
};
let data_type_definition = expand_event_struct(&struct_name, &fields, all_anonymous_fields);
let derives = util::expand_derives(&self.event_derives);
// rust-std only derives default automatically for arrays len <= 32
// 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)]
let mut extra_derives = self.expand_extra_derives();
if event.inputs.iter().map(|param| &param.kind).all(util::can_derive_default) {
extra_derives.extend(quote!(Default));
}
} else {
quote! {}
};
let ethers_contract = ethers_contract_crate();
Ok(quote! {
#[derive(Clone, Debug, Eq, PartialEq, #ethers_contract::EthEvent, #ethers_contract::EthDisplay, #derives)]
#derive_default
#[ethevent( name = #event_abi_name, abi = #abi_signature )]
#[derive(Clone, Debug, Eq, PartialEq, #ethers_contract::EthEvent, #ethers_contract::EthDisplay, #extra_derives)]
#[ethevent(name = #name, abi = #abi_signature)]
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())
}
/// 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)]
mod tests {
use super::*;
@ -328,7 +280,11 @@ mod tests {
#[doc = "Gets the contract's `Transfer` event"]
pub fn transfer_event_filter(
&self
) -> ::ethers_contract::builders::Event<Arc<M>, M, TransferEventFilter> {
) -> ::ethers_contract::builders::Event<
::std::sync::Arc<M>,
M,
TransferEventFilter,
> {
self.0.event()
}
});
@ -349,7 +305,8 @@ mod tests {
#[doc = "Gets the contract's `Transfer` event"]
pub fn transfer_filter(
&self,
) -> ::ethers_contract::builders::Event<Arc<M>, M, TransferFilter> {
) -> ::ethers_contract::builders::Event<::std::sync::Arc<M>, M, TransferFilter>
{
self.0.event()
}
});
@ -369,7 +326,7 @@ mod tests {
let cx = test_context();
let params = types::expand_event_inputs(&event, &cx.internal_structs).unwrap();
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, {
struct FooFilter {
@ -394,7 +351,7 @@ mod tests {
let params = types::expand_event_inputs(&event, &cx.internal_structs).unwrap();
let alias = Some(util::ident("FooAliased"));
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, {
struct FooAliasedFilter {
@ -418,7 +375,7 @@ mod tests {
let cx = test_context();
let params = types::expand_event_inputs(&event, &cx.internal_structs).unwrap();
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, {
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 alias = Some(util::ident("FooAliased"));
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, {
struct FooAliasedFilter(pub bool, pub ::ethers_core::types::Address);

View File

@ -1,8 +1,7 @@
use super::{
common::{expand_data_struct, expand_data_tuple},
types, Context,
};
use crate::util::{self, can_derive_defaults};
//! Methods expansion
use super::{structs::expand_struct, types, Context};
use crate::util;
use ethers_core::{
abi::{Function, FunctionExt, Param, ParamType},
macros::{ethers_contract_crate, ethers_core_crate},
@ -33,7 +32,7 @@ impl Context {
.map(|function| {
let signature = function.abi_signature();
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<_>>>()?;
@ -50,11 +49,10 @@ impl Context {
}
/// Returns all deploy (constructor) implementations
pub(crate) fn deployment_methods(&self) -> TokenStream {
if self.contract_bytecode.is_none() {
pub(crate) fn deployment_methods(&self) -> Option<TokenStream> {
// don't generate deploy if no bytecode
return quote! {}
}
self.contract_bytecode.as_ref()?;
let ethers_core = ethers_core_crate();
let ethers_contract = ethers_contract_crate();
@ -68,14 +66,14 @@ impl Context {
#bytecode_name.clone().into()
};
let deploy = quote! {
Some(quote! {
/// 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
///
/// Notes:
/// 1. If there are no constructor arguments, you should pass `()` as the argument.
/// 1. The default poll duration is 7 seconds.
/// 1. The default number of confirmations is 1 block.
/// - If there are no constructor arguments, you should pass `()` as the argument.
/// - The default poll duration is 7 seconds.
/// - The default number of confirmations is 1 block.
///
///
/// # Example
@ -92,16 +90,16 @@ impl Context {
/// 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>(
client: ::std::sync::Arc<M>,
constructor_args: T,
) -> ::core::result::Result<#ethers_contract::builders::ContractDeployer<M, Self>, #ethers_contract::ContractError<M>> {
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
@ -110,42 +108,30 @@ impl Context {
function: &Function,
alias: Option<&MethodAlias>,
) -> 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)?;
// expand as a tuple if all fields are anonymous
let all_anonymous_fields = function.inputs.iter().all(|input| input.name.is_empty());
let call_type_definition = if 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 call_type_definition = expand_struct(&struct_name, &fields, all_anonymous_fields);
let function_name = &function.name;
let abi_signature = function.abi_signature();
let doc_str = format!(
"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();
// 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
// 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)]
let mut extra_derives = self.expand_extra_derives();
if util::can_derive_defaults(&function.inputs) {
extra_derives.extend(quote!(Default));
}
} else {
quote! {}
};
let ethers_contract = ethers_contract_crate();
Ok(quote! {
#[doc = #doc_str]
#[derive(Clone, Debug, Eq, PartialEq, #ethers_contract::EthCall, #ethers_contract::EthDisplay, #derives)]
#derive_default
#[derive(Clone, Debug, Eq, PartialEq, #ethers_contract::EthCall, #ethers_contract::EthDisplay, #extra_derives)]
#[ethcall( name = #function_name, abi = #abi_signature )]
pub #call_type_definition
})
@ -156,56 +142,46 @@ impl Context {
&self,
function: &Function,
alias: Option<&MethodAlias>,
) -> Result<TokenStream> {
let name = &function.name;
let struct_name = expand_return_struct_name(function, alias);
let fields = self.expand_output_params(function)?;
) -> Result<Option<TokenStream>> {
// no point in having structs when there is no data returned
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
let all_anonymous_fields = function.outputs.iter().all(|output| output.name.is_empty());
let return_type_definition = if 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 return_type_definition = expand_struct(&struct_name, &fields, all_anonymous_fields);
let abi_signature = function.abi_signature();
let doc_str = format!(
"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 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
// 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)]
let mut extra_derives = self.expand_extra_derives();
if util::can_derive_defaults(&function.inputs) {
extra_derives.extend(quote!(Default));
}
} else {
quote! {}
};
Ok(quote! {
let ethers_contract = ethers_contract_crate();
Ok(Some(quote! {
#[doc = #doc_str]
#[derive(Clone, Debug,Eq, PartialEq, #ethers_contract::EthAbiType, #ethers_contract::EthAbiCodec, #derives)]
#derive_default
#[derive(Clone, Debug, Eq, PartialEq, #ethers_contract::EthAbiType, #ethers_contract::EthAbiCodec, #extra_derives)]
pub #return_type_definition
})
}))
}
/// Expands all call structs
fn expand_call_structs(&self, aliases: BTreeMap<String, MethodAlias>) -> Result<TokenStream> {
let mut struct_defs = Vec::new();
let mut struct_names = Vec::new();
let mut variant_names = Vec::new();
let len = self.abi.functions.len();
let mut struct_defs = Vec::with_capacity(len);
let mut struct_names = Vec::with_capacity(len);
let mut variant_names = Vec::with_capacity(len);
for function in self.abi.functions.values().flatten() {
let signature = function.abi_signature();
let alias = aliases.get(&signature);
@ -214,35 +190,35 @@ impl Context {
variant_names.push(expand_call_struct_variant_name(function, alias));
}
let struct_def_tokens = quote! {
#(#struct_defs)*
};
let struct_def_tokens = quote!(#(#struct_defs)*);
if struct_defs.len() <= 1 {
// no need for an enum
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_contract = ethers_contract_crate();
// use the same derives as for events
let derives = util::expand_derives(&self.event_derives);
let enum_name = self.expand_calls_enum_name();
Ok(quote! {
let tokens = quote! {
#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 {
#(#variant_names(#struct_names)),*
#( #variant_names(#struct_names), )*
}
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())
@ -253,47 +229,47 @@ impl Context {
fn encode(self) -> Vec<u8> {
match self {
#(
#enum_name::#variant_names(element) => element.encode()
),*
Self::#variant_names(element) => #ethers_core::abi::AbiEncode::encode(element),
)*
}
}
}
impl ::std::fmt::Display for #enum_name {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
impl ::core::fmt::Display for #enum_name {
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
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 {
fn from(var: #struct_names) -> Self {
#enum_name::#variant_names(var)
impl ::core::convert::From<#struct_names> for #enum_name {
fn from(value: #struct_names) -> Self {
Self::#variant_names(value)
}
}
)*
};
})
Ok(tokens)
}
/// Expands all return structs
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() {
let signature = function.abi_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),
}
let struct_def_tokens = quote! {
#(#struct_defs)*
};
Ok(struct_def_tokens)
}
Ok(tokens)
}
/// The name ident of the calls enum
@ -597,7 +573,7 @@ impl Context {
fn expand_selector(selector: Selector) -> TokenStream {
let bytes = selector.iter().copied().map(Literal::u8_unsuffixed);
quote! { [#( #bytes ),*] }
quote!([ #( #bytes ),* ])
}
/// Represents the aliases to use when generating method related elements

View File

@ -1,8 +1,7 @@
//! Methods for expanding structs
use crate::{
contract::{types, Context},
util,
};
//! Structs expansion
use super::{types, Context};
use crate::util;
use ethers_core::{
abi::{
struct_def::{FieldDeclaration, FieldType, StructFieldType, StructType},
@ -15,6 +14,7 @@ use inflector::Inflector;
use proc_macro2::TokenStream;
use quote::quote;
use std::collections::{HashMap, VecDeque};
use syn::Ident;
impl Context {
/// 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
fn generate_internal_struct(&self, id: &str) -> Result<TokenStream> {
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
.internal_structs
.rust_type_names
.get(id)
.ok_or_else(|| eyre!("No types found for {}", id))?;
.ok_or_else(|| eyre!("No types found for {id}"))?;
let tuple = self
.internal_structs
.struct_tuples
.get(id)
.ok_or_else(|| eyre!("No types found for {}", id))?
.ok_or_else(|| eyre!("No types found for {id}"))?
.clone();
self.expand_internal_struct(struct_name, sol_struct, tuple)
}
@ -95,33 +95,22 @@ impl Context {
FieldType::Elementary(ty) => types::expand(ty)?,
FieldType::Struct(struct_ty) => types::expand_struct_type(struct_ty),
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 {
fields.push(quote!(pub #ty));
let field_name = if is_tuple {
TokenStream::new()
} else {
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 struct_def = if is_tuple {
quote! {
pub struct #name(
#( #fields ),*
);
}
} else {
quote! {
pub struct #name {
#( #fields ),*
}
}
};
let struct_def = expand_struct(&name, &fields, is_tuple);
let sig = match tuple {
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})`");
// use the same derives as for events
let derives = util::expand_derives(&self.event_derives);
let extra_derives = self.expand_extra_derives();
let ethers_contract = ethers_contract_crate();
Ok(quote! {
#[doc = #doc_str]
#[derive(Clone, Debug, Default, Eq, PartialEq, #ethers_contract::EthAbiType, #ethers_contract::EthAbiCodec, #derives)]
#struct_def
#[derive(Clone, Debug, Default, Eq, PartialEq, #ethers_contract::EthAbiType, #ethers_contract::EthAbiCodec, #extra_derives)]
pub #struct_def
})
}
fn generate_human_readable_struct(&self, name: &str) -> Result<TokenStream> {
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 param_types = Vec::with_capacity(sol_struct.fields().len());
for field in sol_struct.fields() {
@ -162,14 +151,14 @@ impl Context {
.abi_parser
.struct_tuples
.get(name)
.ok_or_else(|| eyre!("No types found for {}", name))?
.ok_or_else(|| eyre!("No types found for {name}"))?
.clone();
let tuple = ParamType::Tuple(tuple);
param_types.push(struct_ty.as_param(tuple));
}
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);
// use the same derives as for events
let derives = &self.event_derives;
let derives = quote! {#(#derives),*};
let mut extra_derives = self.expand_extra_derives();
if param_types.iter().all(util::can_derive_default) {
extra_derives.extend(quote!(Default))
}
let ethers_contract = ethers_contract_crate();
Ok(quote! {
#[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 {
#( #fields ),*
}
@ -580,7 +570,7 @@ fn struct_type_name(name: &str) -> &str {
}
/// `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()
}
@ -592,16 +582,56 @@ fn struct_type_projections(name: &str) -> Vec<String> {
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)]
mod tests {
use super::*;
#[test]
fn can_determine_structs() {
const VERIFIER_ABI: &str =
include_str!("../../../tests/solidity-contracts/verifier_abi.json");
let abi = serde_json::from_str::<RawAbi>(VERIFIER_ABI).unwrap();
let internal = InternalStructs::new(abi);
dbg!(internal.rust_type_names);
let _internal = InternalStructs::new(abi);
}
}

View File

@ -11,7 +11,7 @@ use proc_macro2::{Literal, TokenStream};
use quote::{quote, ToTokens};
/// 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();
match kind {
@ -58,7 +58,7 @@ pub fn expand(kind: &ParamType) -> Result<TokenStream> {
}
/// Expands the event's inputs.
pub fn expand_event_inputs(
pub(crate) fn expand_event_inputs(
event: &Event,
internal_structs: &InternalStructs,
) -> 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
/// 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],
resolve_tuple: F,
) -> 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.
pub fn expand_struct_type(struct_ty: &StructFieldType) -> TokenStream {
pub(crate) fn expand_struct_type(struct_ty: &StructFieldType) -> TokenStream {
match struct_ty {
StructFieldType::Type(ty) => {
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
#![deny(rustdoc::broken_intra_doc_links, missing_docs, unsafe_code)]
#![warn(unreachable_pub)]
#[cfg(test)]
#[allow(missing_docs)]
@ -25,6 +26,8 @@ pub mod multi;
pub use multi::MultiAbigen;
mod source;
#[cfg(all(feature = "online", not(target_arch = "wasm32")))]
pub use source::Explorer;
pub use source::Source;
mod util;

View File

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

View File

@ -3,35 +3,38 @@ use eyre::Result;
use inflector::Inflector;
use proc_macro2::{Ident, Span, TokenStream};
use quote::quote;
use std::path::PathBuf;
use syn::{Ident as SynIdent, Path};
use std::path::{Path, PathBuf};
/// Expands a identifier string into a token.
pub fn ident(name: &str) -> Ident {
/// Creates a new Ident with the given string at [`Span::call_site`].
///
/// # 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())
}
/// Expands an identifier string into a token and appending `_` if the
/// identifier is for a reserved keyword.
/// Expands an identifier string into a token and appending `_` if the identifier is for a reserved
/// keyword.
///
/// Parsing keywords like `self` can fail, in this case we add an underscore.
pub fn safe_ident(name: &str) -> Ident {
syn::parse_str::<SynIdent>(name).unwrap_or_else(|_| ident(&format!("{name}_")))
pub(crate) fn safe_ident(name: &str) -> Ident {
syn::parse_str::<Ident>(name).unwrap_or_else(|_| ident(&format!("{name}_")))
}
/// 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())
}
/// 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())
}
/// respects identifier rules, such as, an identifier must not start with a numeric char
fn safe_identifier_name(name: String) -> String {
if name.starts_with(|c: char| c.is_numeric()) {
pub(crate) fn safe_identifier_name(name: String) -> String {
if name.starts_with(char::is_numeric) {
format!("_{name}")
} else {
name
@ -39,39 +42,46 @@ fn safe_identifier_name(name: String) -> String {
}
/// 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)
safe_ident(&safe_snake_case(name)).to_string()
}
/// 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();
ident(&preserve_underscore_delim(&i, name))
}
/// 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();
ident(&preserve_underscore_delim(&i, name))
}
/// 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 {
alias
.chars()
.take_while(|c| *c == '_')
.chain(ident.chars())
.chain(alias.chars().rev().take_while(|c| *c == '_'))
.collect()
///
/// # Example
///
/// ```ignore
/// # use ethers_contract_abigen::util::preserve_underscore_delim;
/// assert_eq!(
/// preserve_underscore_delim("pascalCase", "__pascalcase__"),
/// "__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.
///
/// Note that this expands the parameter name with `safe_ident`, meaning that
/// 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 {
"" => format!("p{index}"),
n => n.to_snake_case(),
@ -81,18 +91,14 @@ pub fn expand_input_name(index: usize, name: &str) -> TokenStream {
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.
#[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()?)
}
/// 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 resolved = String::new();
@ -107,7 +113,7 @@ pub fn resolve_path(raw: &str) -> Result<PathBuf> {
unprocessed = rest;
}
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
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)
.into_iter()
.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`].
///
/// 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)
}
/// Returns whether the given type can derive [`Default`].
///
/// 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;
match param {
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.
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
N: std::fmt::Display,
T: IntoIterator<Item = &'a ParamType>,
@ -196,7 +202,7 @@ where
}
/// 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(",")
}

View File

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

View File

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