fix(abigen): builtin trait derives (#2170)

* fix(abigen): builtin trait derives

* refactor: EthDisplay

* feat(derive): improve type detection

* chore: cleanup

* chore: edition 2021

* chore: clippy

* chore: use unreachable
This commit is contained in:
DaniPopes 2023-02-21 01:27:43 +01:00 committed by GitHub
parent c21362b696
commit 3732de844c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 572 additions and 222 deletions

View File

@ -1,7 +1,7 @@
[package]
name = "ethers-contract"
version = "1.0.2"
edition = "2018"
edition = "2021"
rust-version = "1.64"
authors = ["Georgios Konstantopoulos <me@gakonst.com>"]
license = "MIT OR Apache-2.0"

View File

@ -52,16 +52,15 @@ impl Context {
hex::encode(error.selector())
);
let mut extra_derives = self.expand_extra_derives();
if util::can_derive_defaults(&error.inputs) {
extra_derives.extend(quote!(Default));
}
let mut derives = self.expand_extra_derives();
let params = error.inputs.iter().map(|param| &param.kind);
util::derive_builtin_traits(params, &mut derives, true, true);
let ethers_contract = ethers_contract_crate();
Ok(quote! {
#[doc = #doc_str]
#[derive(Clone, Debug, Eq, PartialEq, #ethers_contract::EthError, #ethers_contract::EthDisplay, #extra_derives)]
#[derive(Clone, #ethers_contract::EthError, #ethers_contract::EthDisplay, #derives)]
#[etherror(name = #error_name, abi = #abi_signature)]
pub #data_type_definition
})
@ -92,14 +91,17 @@ impl Context {
})
.collect::<Vec<_>>();
let extra_derives = self.expand_extra_derives();
let mut derives = self.expand_extra_derives();
let params =
self.abi.errors.values().flatten().flat_map(|err| &err.inputs).map(|param| &param.kind);
util::derive_builtin_traits(params, &mut derives, false, true);
let ethers_core = ethers_core_crate();
let ethers_contract = ethers_contract_crate();
quote! {
#[doc = "Container type for all of the contract's custom errors"]
#[derive(Debug, Clone, PartialEq, Eq, #ethers_contract::EthAbiType, #extra_derives)]
#[derive(Clone, #ethers_contract::EthAbiType, #derives)]
pub enum #enum_name {
#( #variants(#variants), )*
}

View File

@ -62,15 +62,19 @@ impl Context {
})
.collect::<Vec<_>>();
let enum_name = self.expand_event_enum_name();
let mut derives = self.expand_extra_derives();
let params =
self.abi.events.values().flatten().flat_map(|err| &err.inputs).map(|param| &param.kind);
util::derive_builtin_traits(params, &mut derives, false, true);
let ethers_core = ethers_core_crate();
let ethers_contract = ethers_contract_crate();
let extra_derives = self.expand_extra_derives();
let enum_name = self.expand_event_enum_name();
quote! {
#[doc = "Container type for all of the contract's events"]
#[derive(Debug, Clone, PartialEq, Eq, #ethers_contract::EthAbiType, #extra_derives)]
#[derive(Clone, #ethers_contract::EthAbiType, #derives)]
pub enum #enum_name {
#( #variants(#variants), )*
}
@ -191,15 +195,14 @@ impl Context {
let all_anonymous_fields = event.inputs.iter().all(|input| input.name.is_empty());
let data_type_definition = expand_event_struct(&struct_name, &fields, all_anonymous_fields);
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));
}
let mut derives = self.expand_extra_derives();
let params = event.inputs.iter().map(|param| &param.kind);
util::derive_builtin_traits(params, &mut derives, true, true);
let ethers_contract = ethers_contract_crate();
Ok(quote! {
#[derive(Clone, Debug, Eq, PartialEq, #ethers_contract::EthEvent, #ethers_contract::EthDisplay, #extra_derives)]
#[derive(Clone, #ethers_contract::EthEvent, #ethers_contract::EthDisplay, #derives)]
#[ethevent(name = #name, abi = #abi_signature)]
pub #data_type_definition
})

View File

@ -122,16 +122,15 @@ impl Context {
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 mut derives = self.expand_extra_derives();
let params = function.inputs.iter().map(|param| &param.kind);
util::derive_builtin_traits(params, &mut derives, true, true);
let ethers_contract = ethers_contract_crate();
Ok(quote! {
#[doc = #doc_str]
#[derive(Clone, Debug, Eq, PartialEq, #ethers_contract::EthCall, #ethers_contract::EthDisplay, #extra_derives)]
#[derive(Clone, #ethers_contract::EthCall, #ethers_contract::EthDisplay, #derives)]
#[ethcall( name = #function_name, abi = #abi_signature )]
pub #call_type_definition
})
@ -162,16 +161,15 @@ impl Context {
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 mut derives = self.expand_extra_derives();
let params = function.inputs.iter().map(|param| &param.kind);
util::derive_builtin_traits(params, &mut derives, true, true);
let ethers_contract = ethers_contract_crate();
Ok(Some(quote! {
#[doc = #doc_str]
#[derive(Clone, Debug, Eq, PartialEq, #ethers_contract::EthAbiType, #ethers_contract::EthAbiCodec, #extra_derives)]
#[derive(Clone, #ethers_contract::EthAbiType, #ethers_contract::EthAbiCodec, #derives)]
pub #return_type_definition
}))
}
@ -197,7 +195,10 @@ impl Context {
return Ok(struct_def_tokens)
}
let extra_derives = self.expand_extra_derives();
let mut derives = self.expand_extra_derives();
let params =
self.abi.functions.values().flatten().flat_map(|f| &f.inputs).map(|param| &param.kind);
util::derive_builtin_traits(params, &mut derives, false, true);
let enum_name = self.expand_calls_enum_name();
@ -208,7 +209,7 @@ impl Context {
#struct_def_tokens
#[doc = "Container type for all of the contract's call "]
#[derive(Debug, Clone, PartialEq, Eq, #ethers_contract::EthAbiType, #extra_derives)]
#[derive(Clone, #ethers_contract::EthAbiType, #derives)]
pub enum #enum_name {
#( #variant_names(#struct_names), )*
}

View File

@ -61,9 +61,9 @@ impl Context {
.internal_structs
.struct_tuples
.get(id)
.ok_or_else(|| eyre!("No types found for {id}"))?
.clone();
self.expand_internal_struct(struct_name, sol_struct, tuple)
.ok_or_else(|| eyre!("No types found for {id}"))?;
let types = if let ParamType::Tuple(types) = tuple { types } else { unreachable!() };
self.expand_internal_struct(struct_name, sol_struct, types)
}
/// Returns the `TokenStream` with all the internal structs extracted form the JSON ABI
@ -83,7 +83,7 @@ impl Context {
&self,
name: &str,
sol_struct: &SolStruct,
tuple: ParamType,
types: &[ParamType],
) -> Result<TokenStream> {
let mut fields = Vec::with_capacity(sol_struct.fields().len());
@ -112,19 +112,17 @@ impl Context {
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),
_ => String::new(),
};
let sig = util::abi_signature_types(types);
let doc_str = format!("`{name}({sig})`");
let extra_derives = self.expand_extra_derives();
let mut derives = self.expand_extra_derives();
util::derive_builtin_traits_struct(&self.internal_structs, sol_struct, types, &mut derives);
let ethers_contract = ethers_contract_crate();
Ok(quote! {
#[doc = #doc_str]
#[derive(Clone, Debug, Default, Eq, PartialEq, #ethers_contract::EthAbiType, #ethers_contract::EthAbiCodec, #extra_derives)]
#[derive(Clone, #ethers_contract::EthAbiType, #ethers_contract::EthAbiCodec, #derives)]
pub #struct_def
})
}
@ -167,16 +165,14 @@ impl Context {
let name = util::ident(name);
let mut extra_derives = self.expand_extra_derives();
if param_types.iter().all(util::can_derive_default) {
extra_derives.extend(quote!(Default))
}
let mut derives = self.expand_extra_derives();
util::derive_builtin_traits(&param_types, &mut derives, true, true);
let ethers_contract = ethers_contract_crate();
Ok(quote! {
#[doc = #abi_signature]
#[derive(Clone, Debug, Eq, PartialEq, #ethers_contract::EthAbiType, #ethers_contract::EthAbiCodec, #extra_derives)]
#[derive(Clone, #ethers_contract::EthAbiType, #ethers_contract::EthAbiCodec, #derives)]
pub struct #name {
#( #fields ),*
}
@ -231,7 +227,7 @@ impl InternalStructs {
let mut function_params = HashMap::new();
let mut outputs = HashMap::new();
let mut event_params = HashMap::new();
let mut structs = HashMap::new();
for item in abi
.into_iter()
.filter(|item| matches!(item.type_field.as_str(), "constructor" | "function" | "event"))
@ -279,6 +275,7 @@ impl InternalStructs {
// turn each top level internal type (function input/output) and their nested types
// into a struct will create all structs
let mut structs = HashMap::new();
for component in top_level_internal_types.values() {
insert_structs(&mut structs, component);
}
@ -569,7 +566,7 @@ fn struct_type_name(name: &str) -> &str {
struct_type_identifier(name).rsplit('.').next().unwrap()
}
/// `Pairing.G2Point` -> `Pairing.G2Point`
/// `struct Pairing.G2Point[]` -> `Pairing.G2Point`
fn struct_type_identifier(name: &str) -> &str {
name.trim_start_matches("struct ").split('[').next().unwrap()
}

View File

@ -1342,7 +1342,7 @@ contract Enum {
[package]
name = "ethers-contract"
version = "1.0.0"
edition = "2018"
edition = "2021"
rust-version = "1.64"
authors = ["Georgios Konstantopoulos <me@gakonst.com>"]
license = "MIT OR Apache-2.0"
@ -1388,7 +1388,7 @@ contract Enum {
[package]
name = "ethers-contract"
version = "1.0.0"
edition = "2018"
edition = "2021"
rust-version = "1.64"
authors = ["Georgios Konstantopoulos <me@gakonst.com>"]
license = "MIT OR Apache-2.0"
@ -1435,7 +1435,7 @@ contract Enum {
[package]
name = "ethers-contract"
version = "1.0.0"
edition = "2018"
edition = "2021"
rust-version = "1.64"
authors = ["Georgios Konstantopoulos <me@gakonst.com>"]
license = "MIT OR Apache-2.0"
@ -1482,7 +1482,7 @@ contract Enum {
[package]
name = "ethers-contract"
version = "1.0.0"
edition = "2018"
edition = "2021"
rust-version = "1.64"
authors = ["Georgios Konstantopoulos <me@gakonst.com>"]
license = "MIT OR Apache-2.0"

View File

@ -1,4 +1,8 @@
use ethers_core::abi::{Param, ParamType};
use crate::InternalStructs;
use ethers_core::abi::{
struct_def::{FieldType, StructFieldType},
ParamType, SolStruct,
};
use eyre::Result;
use inflector::Inflector;
use proc_macro2::{Ident, Span, TokenStream};
@ -165,28 +169,158 @@ pub(crate) fn json_files(root: impl AsRef<Path>) -> Vec<PathBuf> {
.collect()
}
/// Returns whether all the given parameters can derive [`Default`].
/// Returns whether all the given parameters can derive the builtin traits.
///
/// rust-std derives `Default` automatically only for arrays len <= 32
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)
/// The following traits are only implemented on tuples of arity 12 or less:
///
/// - [PartialEq](https://doc.rust-lang.org/stable/std/cmp/trait.PartialEq.html)
/// - [Eq](https://doc.rust-lang.org/stable/std/cmp/trait.Eq.html)
/// - [PartialOrd](https://doc.rust-lang.org/stable/std/cmp/trait.PartialOrd.html)
/// - [Ord](https://doc.rust-lang.org/stable/std/cmp/trait.Ord.html)
/// - [Debug](https://doc.rust-lang.org/stable/std/fmt/trait.Debug.html)
/// - [Default](https://doc.rust-lang.org/stable/std/default/trait.Default.html)
/// - [Hash](https://doc.rust-lang.org/stable/std/hash/trait.Hash.html)
///
/// while the `Default` trait is only implemented on arrays of length 32 or less.
///
/// Tuple reference: <https://doc.rust-lang.org/stable/std/primitive.tuple.html#trait-implementations-1>
///
/// Array reference: <https://doc.rust-lang.org/stable/std/primitive.array.html>
///
/// `derive_default` should be set to false when calling this for enums.
pub(crate) fn derive_builtin_traits<'a>(
params: impl IntoIterator<Item = &'a ParamType>,
stream: &mut TokenStream,
mut derive_default: bool,
mut derive_others: bool,
) {
for param in params {
derive_default &= can_derive_default(param);
derive_others &= can_derive_builtin_traits(param);
}
extend_derives(stream, derive_default, derive_others);
}
/// Returns whether the given type can derive [`Default`].
///
/// rust-std derives `Default` automatically only for arrays len <= 32
pub(crate) fn can_derive_default(param: &ParamType) -> bool {
const MAX_SUPPORTED_LEN: usize = 32;
/// This has to be a seperate function since a sol struct is converted into a tuple, but for
/// deriving purposes it shouldn't count as one, so we recurse back the struct fields.
pub(crate) fn derive_builtin_traits_struct(
structs: &InternalStructs,
sol_struct: &SolStruct,
params: &[ParamType],
stream: &mut TokenStream,
) {
if sol_struct.fields().iter().any(|field| field.ty.is_struct()) {
let mut def = true;
let mut others = true;
_derive_builtin_traits_struct(structs, sol_struct, params, &mut def, &mut others);
extend_derives(stream, def, others);
} else {
derive_builtin_traits(params, stream, true, true);
}
}
fn _derive_builtin_traits_struct(
structs: &InternalStructs,
sol_struct: &SolStruct,
params: &[ParamType],
def: &mut bool,
others: &mut bool,
) {
let fields = sol_struct.fields();
debug_assert_eq!(fields.len(), params.len());
for (field, ty) in fields.iter().zip(params) {
match &field.ty {
FieldType::Struct(s_ty) => {
// a tuple here is actually a sol struct so we skip it
if !matches!(ty, ParamType::Tuple(_)) {
*def &= can_derive_default(ty);
*others &= can_derive_builtin_traits(ty);
}
let id = s_ty.identifier();
// TODO: InternalStructs does not contain this field's ID if the struct and field
// are in 2 different modules, like in `can_generate_internal_structs_multiple`
if let Some(recursed_struct) = structs.structs.get(&id) {
let recursed_params = get_struct_params(s_ty, ty);
_derive_builtin_traits_struct(
structs,
recursed_struct,
recursed_params,
def,
others,
);
}
}
FieldType::Elementary(ty1) => {
debug_assert_eq!(ty, ty1);
*def &= can_derive_default(ty);
*others &= can_derive_builtin_traits(ty);
}
FieldType::Mapping(_) => unreachable!(),
}
}
}
fn get_struct_params<'a>(s_ty: &StructFieldType, ty: &'a ParamType) -> &'a [ParamType] {
match (s_ty, ty) {
(StructFieldType::Type(_), ParamType::Tuple(params)) => params,
(StructFieldType::Array(s_ty), ParamType::Array(ty)) => get_struct_params(s_ty, ty),
(StructFieldType::FixedArray(s_ty, _), ParamType::FixedArray(ty, _)) => {
get_struct_params(s_ty, ty)
}
_ => unreachable!(),
}
}
fn extend_derives(stream: &mut TokenStream, def: bool, others: bool) {
if def {
stream.extend(quote!(Default,))
}
if others {
stream.extend(quote!(Debug, PartialEq, Eq, Hash))
}
}
const MAX_SUPPORTED_ARRAY_LEN: usize = 32;
const MAX_SUPPORTED_TUPLE_LEN: usize = 12;
/// Whether the given type can derive the `Default` trait.
fn can_derive_default(param: &ParamType) -> bool {
match param {
ParamType::FixedBytes(len) => *len <= MAX_SUPPORTED_LEN,
ParamType::Array(ty) => can_derive_default(ty),
ParamType::FixedBytes(len) => *len <= MAX_SUPPORTED_ARRAY_LEN,
ParamType::FixedArray(ty, len) => {
if *len > MAX_SUPPORTED_LEN {
if *len > MAX_SUPPORTED_ARRAY_LEN {
false
} else {
can_derive_default(ty)
}
}
ParamType::Tuple(params) => params.iter().all(can_derive_default),
ParamType::Tuple(params) => {
if params.len() > MAX_SUPPORTED_TUPLE_LEN {
false
} else {
params.iter().all(can_derive_default)
}
}
_ => true,
}
}
/// Whether the given type can derive the builtin traits listed in [`derive_builtin_traits`], minus
/// `Default`.
fn can_derive_builtin_traits(param: &ParamType) -> bool {
match param {
ParamType::Array(ty) | ParamType::FixedArray(ty, _) => can_derive_builtin_traits(ty),
ParamType::Tuple(params) => {
if params.len() > MAX_SUPPORTED_TUPLE_LEN {
false
} else {
params.iter().all(can_derive_builtin_traits)
}
}
_ => true,
}
}
@ -211,12 +345,24 @@ mod tests {
use super::*;
#[test]
fn can_detect_non_default() {
let param = ParamType::FixedArray(Box::new(ParamType::Uint(64)), 128);
assert!(!can_derive_default(&param));
let param = ParamType::FixedArray(Box::new(ParamType::Uint(64)), 32);
fn can_detect_derives() {
// array
let param = ParamType::FixedArray(Box::new(ParamType::Uint(256)), 32);
assert!(can_derive_default(&param));
assert!(can_derive_builtin_traits(&param));
let param = ParamType::FixedArray(Box::new(ParamType::Uint(256)), 33);
assert!(!can_derive_default(&param));
assert!(can_derive_builtin_traits(&param));
// tuple
let param = ParamType::Tuple(vec![ParamType::Uint(256); 12]);
assert!(can_derive_default(&param));
assert!(can_derive_builtin_traits(&param));
let param = ParamType::Tuple(vec![ParamType::Uint(256); 13]);
assert!(!can_derive_default(&param));
assert!(!can_derive_builtin_traits(&param));
}
#[test]

View File

@ -22,82 +22,117 @@ pub(crate) fn derive_eth_display_impl(input: DeriveInput) -> Result<TokenStream,
}
};
let ethers_core = ethers_core_crate();
let hex_encode = quote! {#ethers_core::utils::hex::encode};
let mut fmts = TokenStream::new();
for (idx, field) in fields.iter().enumerate() {
let ident = field.ident.clone().map(|id| quote! {#id}).unwrap_or_else(|| {
let idx = Index::from(idx);
quote! {#idx}
let mut expressions = TokenStream::new();
for (i, field) in fields.iter().enumerate() {
let ident = field.ident.as_ref().map(|id| quote!(#id)).unwrap_or_else(|| {
let idx = Index::from(i);
quote!(#idx)
});
let tokens = if let Ok(param) = utils::find_parameter_type(&field.ty) {
match param {
ParamType::Address | ParamType::Uint(_) | ParamType::Int(_) => {
quote! {
write!(f, "{:?}", self.#ident)?;
}
}
ParamType::Bytes => {
quote! {
write!(f, "0x{}", #hex_encode(&self.#ident))?;
}
}
ParamType::Bool | ParamType::String => {
quote! {
self.#ident.fmt(f)?;
}
}
ParamType::Tuple(_) => {
quote! {
write!(f, "{:?}", &self.#ident)?;
}
}
ParamType::Array(ty) | ParamType::FixedArray(ty, _) => {
if *ty == ParamType::Uint(8) {
// `u8`
quote! {
write!(f, "0x{}", #hex_encode(&self.#ident[..]))?;
}
} else {
// format as array with `[arr[0].display, arr[1].display,...]`
quote! {
write!(f, "[")?;
for (idx, val) in self.#ident.iter().enumerate() {
write!(f, "{:?}", val)?;
if idx < self.#ident.len() - 1 {
write!(f, ", ")?;
}
}
write!(f, "]")?;
}
}
}
ParamType::FixedBytes(_) => {
quote! {
write!(f, "0x{}", #hex_encode(&self.#ident))?;
}
}
}
if let Ok(param) = utils::find_parameter_type(&field.ty) {
let ethers_core = ethers_core_crate();
let hex_encode = quote!(#ethers_core::utils::hex::encode);
fmt_params_tokens(&param, ident, &mut expressions, &hex_encode);
} else {
// could not detect the parameter type and rely on using debug fmt
quote! {
write!(f, "{:?}", &self.#ident)?;
}
};
fmts.extend(tokens);
if idx < fields.len() - 1 {
fmts.extend(quote! { write!(f, ", ")?;});
fmt_debug_tokens(&ident, &mut expressions);
}
// comma separator
if i < fields.len() - 1 {
let tokens = quote! {
::core::fmt::Write::write_str(f, ", ")?;
};
expressions.extend(tokens);
}
}
let name = &input.ident;
Ok(quote! {
impl ::std::fmt::Display for #name {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
#fmts
impl ::core::fmt::Display for #name {
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
#expressions
Ok(())
}
}
})
}
/// Recursive for tuples len > 12.
fn fmt_params_tokens(
param: &ParamType,
ident: TokenStream,
out: &mut TokenStream,
hex_encode: &TokenStream,
) {
match param {
// Display
ParamType::Bool | ParamType::String | ParamType::Uint(_) | ParamType::Int(_) => {
fmt_display_tokens(&ident, out);
}
// Debug
ParamType::Address => fmt_debug_tokens(&ident, out),
// 0x ++ hex::encode
ParamType::Bytes | ParamType::FixedBytes(_) => hex_encode_tokens(&ident, out, hex_encode),
// Debug or recurse
ParamType::Tuple(params) => {
// Debug is implemented automatically only for tuples with arity <= 12
if params.len() <= 12 {
fmt_debug_tokens(&ident, out);
} else {
for (i, new_param) in params.iter().enumerate() {
let idx = Index::from(i);
let new_ident = quote!(#ident.#idx);
fmt_params_tokens(new_param, new_ident, out, hex_encode);
}
}
}
// 0x ++ hex::encode or DebugList
ParamType::Array(ty) | ParamType::FixedArray(ty, _) => match &**ty {
ParamType::Uint(8) => hex_encode_tokens(&ident, out, hex_encode),
ParamType::Tuple(params) if params.len() > 12 => {
// TODO: Recurse this
let idx = (0..params.len()).map(Index::from);
let tokens = quote! {
let mut list = f.debug_list();
for entry in self.#ident.iter() {
#( list.entry(&entry.#idx); )*
}
list.finish()?;
};
out.extend(tokens);
}
_ => {
let tokens = quote! {
f.debug_list().entries(self.#ident.iter()).finish()?;
};
out.extend(tokens);
}
},
}
}
fn fmt_display_tokens(ident: &TokenStream, out: &mut TokenStream) {
let tokens = quote! {
::core::fmt::Display::fmt(&self.#ident, f)?;
};
out.extend(tokens);
}
fn fmt_debug_tokens(ident: &TokenStream, out: &mut TokenStream) {
let tokens = quote! {
::core::fmt::Debug::fmt(&self.#ident, f)?;
};
out.extend(tokens);
}
fn hex_encode_tokens(ident: &TokenStream, out: &mut TokenStream, hex_encode: &TokenStream) {
let tokens = quote! {
::core::fmt::Write::write_str(f, "0x")?;
::core::fmt::Write::write_str(f, #hex_encode(&self.#ident).as_str())?;
};
out.extend(tokens);
}

View File

@ -21,12 +21,17 @@ pub fn selector(selector: Selector) -> TokenStream {
quote! {[#( #bytes ),*]}
}
/// Parses an int type from its string representation
pub fn parse_int_param_type(s: &str) -> Option<ParamType> {
/// Parses an int / hash type from its string representation
pub fn parse_param_type(s: &str) -> Option<ParamType> {
match s.chars().next() {
Some(c @ 'u') | Some(c @ 'i') => {
Some('H' | 'h') => {
let size = s[1..].parse::<usize>().ok()? / 8;
Some(ParamType::FixedBytes(size))
}
Some(c @ 'U' | c @ 'I' | c @ 'u' | c @ 'i') => {
let size = s[1..].parse::<usize>().ok()?;
if c == 'u' {
if matches!(c, 'U' | 'u') {
Some(ParamType::Uint(size))
} else {
Some(ParamType::Int(size))
@ -100,57 +105,62 @@ pub fn param_type_quote(kind: &ParamType) -> TokenStream {
/// given type
pub fn find_parameter_type(ty: &Type) -> Result<ParamType, Error> {
match ty {
Type::Array(ty) => {
let param = find_parameter_type(ty.elem.as_ref())?;
if let Expr::Lit(ref expr) = ty.len {
Type::Array(arr) => {
let ty = find_parameter_type(&arr.elem)?;
if let Expr::Lit(ref expr) = arr.len {
if let Lit::Int(ref len) = expr.lit {
if let Ok(size) = len.base10_parse::<usize>() {
return Ok(ParamType::FixedArray(Box::new(param), size))
return Ok(ParamType::FixedArray(Box::new(ty), size))
}
}
}
Err(Error::new(ty.span(), "Failed to derive proper ABI from array field"))
Err(Error::new(arr.span(), "Failed to derive proper ABI from array field"))
}
Type::Path(ty) => {
// check for `Vec`
if ty.path.segments.len() == 1 && ty.path.segments[0].ident == "Vec" {
if let PathArguments::AngleBracketed(ref args) = ty.path.segments[0].arguments {
if args.args.len() == 1 {
if let GenericArgument::Type(ref ty) = args.args.iter().next().unwrap() {
return find_parameter_type(ty)
.map(|kind| ParamType::Array(Box::new(kind)))
}
if let Some(segment) = ty.path.segments.iter().find(|s| s.ident == "Vec") {
if let PathArguments::AngleBracketed(ref args) = segment.arguments {
// Vec<T, A?>
debug_assert!(matches!(args.args.len(), 1 | 2));
let ty = args.args.iter().next().unwrap();
if let GenericArgument::Type(ref ty) = ty {
return find_parameter_type(ty).map(|kind| ParamType::Array(Box::new(kind)))
}
}
}
let mut ident = ty.path.get_ident();
if ident.is_none() {
ident = ty.path.segments.last().map(|s| &s.ident);
}
if let Some(ident) = ident {
let ident = ident.to_string().to_lowercase();
return match ident.as_str() {
"address" => Ok(ParamType::Address),
"bytes" => Ok(ParamType::Bytes),
"string" => Ok(ParamType::String),
"bool" => Ok(ParamType::Bool),
"int" | "uint" => Ok(ParamType::Uint(256)),
"h160" => Ok(ParamType::FixedBytes(20)),
"h256" | "secret" | "hash" => Ok(ParamType::FixedBytes(32)),
"h512" | "public" => Ok(ParamType::FixedBytes(64)),
s => parse_int_param_type(s).ok_or_else(|| {
Error::new(ty.span(), "Failed to derive proper ABI from fields")
}),
}
}
Err(Error::new(ty.span(), "Failed to derive proper ABI from fields"))
// match on the last segment of the path
ty.path
.get_ident()
.or_else(|| ty.path.segments.last().map(|s| &s.ident))
.and_then(|ident| {
match ident.to_string().as_str() {
// eth types
"Address" => Some(ParamType::Address),
"Bytes" => Some(ParamType::Bytes),
"Uint8" => Some(ParamType::Uint(8)),
// core types
"String" => Some(ParamType::String),
"bool" => Some(ParamType::Bool),
// usize / isize, shouldn't happen but use max width
"usize" => Some(ParamType::Uint(64)),
"isize" => Some(ParamType::Int(64)),
s => parse_param_type(s),
}
})
.ok_or_else(|| Error::new(ty.span(), "Failed to derive proper ABI from fields"))
}
Type::Tuple(ty) => ty
.elems
.iter()
.map(find_parameter_type)
.collect::<Result<Vec<_>, _>>()
.map(ParamType::Tuple),
_ => Err(Error::new(ty.span(), "Failed to derive proper ABI from fields")),
}
}
@ -267,3 +277,97 @@ pub fn abi_parameters_array(input: &DeriveInput, trait_name: &str) -> Result<Tok
[#( #iter ),*]
})
}
#[cfg(test)]
mod tests {
use super::*;
use syn::parse_quote;
macro_rules! type_test_cases {
($($t:ty => $e:expr),+ $(,)?) => {{
&[
$(
(parse_quote!($t), $e),
)+
]
}};
}
fn arr(ty: ParamType) -> ParamType {
ParamType::Array(Box::new(ty))
}
fn farr(ty: ParamType, len: usize) -> ParamType {
ParamType::FixedArray(Box::new(ty), len)
}
#[test]
fn can_find_params() {
use ParamType as PT;
let test_cases: &[(Type, ParamType)] = type_test_cases! {
u8 => PT::Uint(8),
u16 => PT::Uint(16),
u32 => PT::Uint(32),
u64 => PT::Uint(64),
usize => PT::Uint(64),
u128 => PT::Uint(128),
::ethers::types::U256 => PT::Uint(256),
ethers::types::U256 => PT::Uint(256),
::ethers_core::types::U256 => PT::Uint(256),
ethers_core::types::U256 => PT::Uint(256),
U256 => PT::Uint(256),
i8 => PT::Int(8),
i16 => PT::Int(16),
i32 => PT::Int(32),
i64 => PT::Int(64),
isize => PT::Int(64),
i128 => PT::Int(128),
::ethers::types::I256 => PT::Int(256),
ethers::types::I256 => PT::Int(256),
::ethers_core::types::I256 => PT::Int(256),
ethers_core::types::I256 => PT::Int(256),
I256 => PT::Int(256),
::ethers::types::H160 => PT::FixedBytes(20),
H160 => PT::FixedBytes(20),
::ethers::types::H256 => PT::FixedBytes(32),
H256 => PT::FixedBytes(32),
::ethers::types::H512 => PT::FixedBytes(64),
H512 => PT::FixedBytes(64),
::std::vec::Vec<::ethers_core::types::U256, ::std::alloc::Global> => arr(PT::Uint(256)),
::std::vec::Vec<::ethers_core::types::U256, Global> => arr(PT::Uint(256)),
::std::vec::Vec<::ethers_core::types::U256> => arr(PT::Uint(256)),
::std::vec::Vec<ethers::types::U256> => arr(PT::Uint(256)),
::std::vec::Vec<U256> => arr(PT::Uint(256)),
std::vec::Vec<U256> => arr(PT::Uint(256)),
vec::Vec<U256> => arr(PT::Uint(256)),
Vec<U256> => arr(PT::Uint(256)),
[u64; 8] => farr(PT::Uint(64), 8),
[u64; 16] => farr(PT::Uint(64), 16),
[::ethers_core::types::U256; 2] => farr(PT::Uint(256), 2),
[String; 4] => farr(PT::String, 4),
[Address; 2] => farr(PT::Address, 2),
(String, String, Address) => PT::Tuple(vec![PT::String, PT::String, PT::Address]),
(::ethers_core::types::U256, u8, ::ethers_core::types::Address)
=> PT::Tuple(vec![PT::Uint(256), PT::Uint(8), PT::Address]),
(::ethers::types::Bytes, ::ethers::types::H256, (::ethers::types::Address, ::std::string::String))
=> PT::Tuple(vec![
PT::Bytes,
PT::FixedBytes(32),
PT::Tuple(vec![PT::Address, PT::String])
]),
};
for (ty, expected) in test_cases {
match find_parameter_type(ty) {
Ok(ty) => assert_eq!(ty, *expected),
Err(e) => panic!("{e}: {ty:#?}\n{expected}"),
}
}
}
}

View File

@ -1,26 +1,25 @@
#![cfg(feature = "abigen")]
#![allow(unused)]
//! Test cases to validate the `abigen!` macro
use ethers_contract::{abigen, Abigen, EthCall, EthEvent};
use ethers_contract::{abigen, EthCall, EthEvent};
use ethers_core::{
abi::{AbiDecode, AbiEncode, Address, Tokenizable},
types::{transaction::eip2718::TypedTransaction, Chain, Eip1559TransactionRequest, U256},
types::{transaction::eip2718::TypedTransaction, Eip1559TransactionRequest, U256},
utils::Anvil,
};
use ethers_providers::{MockProvider, Provider};
use ethers_solc::Solc;
use std::{
convert::{TryFrom, TryInto},
sync::Arc,
};
use std::sync::Arc;
fn assert_codec<T: AbiDecode + AbiEncode>() {}
fn assert_tokenizeable<T: Tokenizable>() {}
fn assert_call<T: AbiEncode + AbiDecode + Default + Tokenizable>() {}
fn assert_event<T: EthEvent>() {}
const fn assert_codec<T: AbiDecode + AbiEncode>() {}
const fn assert_tokenizeable<T: Tokenizable>() {}
const fn assert_call<T: AbiEncode + AbiDecode + Default + Tokenizable>() {}
const fn assert_event<T: EthEvent>() {}
const fn assert_clone<T: Clone>() {}
const fn assert_default<T: Default>() {}
const fn assert_builtin<T: std::fmt::Debug + PartialEq + Eq + std::hash::Hash>() {}
#[test]
fn can_gen_human_readable() {
fn can_generate_human_readable() {
abigen!(
SimpleContract,
r#"[
@ -33,12 +32,12 @@ fn can_gen_human_readable() {
}
#[test]
fn can_gen_not_human_readable() {
fn can_generate_not_human_readable() {
abigen!(VerifierAbiHardhatContract, "./tests/solidity-contracts/verifier_abi_hardhat.json");
}
#[test]
fn can_gen_human_readable_multiple() {
fn can_generate_human_readable_multiple() {
abigen!(
SimpleContract1,
r#"[
@ -59,7 +58,7 @@ fn can_gen_human_readable_multiple() {
}
#[test]
fn can_gen_structs_readable() {
fn can_generate_structs_readable() {
abigen!(
SimpleContract,
r#"[
@ -90,7 +89,7 @@ fn can_gen_structs_readable() {
}
#[test]
fn can_gen_structs_with_arrays_readable() {
fn can_generate_structs_with_arrays_readable() {
abigen!(
SimpleContract,
r#"[
@ -171,7 +170,7 @@ fn can_generate_internal_structs_multiple() {
}
#[test]
fn can_gen_return_struct() {
fn can_generate_return_struct() {
abigen!(MultiInputOutput, "ethers-contract/tests/solidity-contracts/MultiInputOutput.json");
fn verify<T: AbiEncode + AbiDecode + Clone + std::fmt::Debug + std::cmp::PartialEq>(
@ -199,7 +198,7 @@ fn can_gen_return_struct() {
}
#[test]
fn can_gen_human_readable_with_structs() {
fn can_generate_human_readable_with_structs() {
abigen!(
SimpleContract,
r#"[
@ -455,7 +454,7 @@ fn can_handle_duplicates_with_same_name() {
}
#[test]
fn can_abigen_console_sol() {
fn can_abican_generate_console_sol() {
abigen!(Console, "ethers-contract/tests/solidity-contracts/console.json",);
}
@ -569,7 +568,7 @@ async fn can_abiencoderv2_output() {
// NOTE: this is commented out because this would result in compiler errors if key not set or
// etherscan API not working #[test]
// fn can_gen_multi_etherscan() {
// fn can_generate_multi_etherscan() {
// abigen!(
// MyContract, "etherscan:0xdAC17F958D2ee523a2206206994597C13D831ec7";
// MyContract2, "etherscan:0x8418bb725b3ac45ec8fff3791dd8b4e0480cc2a2";
@ -581,7 +580,7 @@ async fn can_abiencoderv2_output() {
// }
#[test]
fn can_gen_reserved_word_field_names() {
fn can_generate_reserved_word_field_names() {
abigen!(
Test,
r#"[
@ -636,8 +635,8 @@ async fn can_send_struct_param() {
}
#[test]
fn can_gen_seaport() {
abigen!(Seaport, "./tests/solidity-contracts/seaport.json");
fn can_generate_seaport_1_0() {
abigen!(Seaport, "./tests/solidity-contracts/seaport_1_0.json");
assert_eq!(
FulfillAdvancedOrderCall::abi_signature(),
@ -651,13 +650,58 @@ fn can_gen_seaport() {
let encoded = err.clone().encode();
assert_eq!(err, SeaportErrors::decode(encoded).unwrap());
let err = SeaportErrors::ConsiderationNotMet(ConsiderationNotMet {
let _err = SeaportErrors::ConsiderationNotMet(ConsiderationNotMet {
order_index: U256::zero(),
consideration_index: U256::zero(),
shortfall_amount: U256::zero(),
});
}
#[test]
fn can_generate_seaport_gt1_0() {
mod v1_1 {
use super::*;
abigen!(Seaport1, "./tests/solidity-contracts/seaport_1_1.json");
}
// (address,uint256,uint256,address,address,address,uint256
mod v1_2 {
use super::*;
abigen!(Seaport2, "./tests/solidity-contracts/seaport_1_2.json");
}
mod v1_3 {
use super::*;
abigen!(Seaport3, "./tests/solidity-contracts/seaport_1_3.json");
}
// tuples len <= 12
assert_clone::<v1_1::FulfillAdvancedOrderCall>();
assert_default::<v1_1::FulfillAdvancedOrderCall>();
assert_builtin::<v1_1::FulfillAdvancedOrderCall>();
assert_clone::<v1_2::FulfillAdvancedOrderCall>();
assert_default::<v1_2::FulfillAdvancedOrderCall>();
assert_builtin::<v1_2::FulfillAdvancedOrderCall>();
assert_clone::<v1_3::FulfillAdvancedOrderCall>();
assert_default::<v1_3::FulfillAdvancedOrderCall>();
assert_builtin::<v1_3::FulfillAdvancedOrderCall>();
// tuples len > 12
assert_clone::<v1_1::FulfillBasicOrderCall>();
// assert_default::<v1_1::FulfillBasicOrderCall>();
// assert_builtin::<v1_1::FulfillBasicOrderCall>();
assert_clone::<v1_2::FulfillBasicOrderCall>();
// assert_default::<v1_2::FulfillBasicOrderCall>();
// assert_builtin::<v1_2::FulfillBasicOrderCall>();
assert_clone::<v1_3::FulfillBasicOrderCall>();
// assert_default::<v1_3::FulfillBasicOrderCall>();
// assert_builtin::<v1_3::FulfillBasicOrderCall>();
}
#[test]
fn can_generate_to_string_overload() {
abigen!(
@ -691,11 +735,11 @@ fn can_generate_large_event() {
fn can_generate_large_output_struct() {
abigen!(LargeOutputStruct, "ethers-contract/tests/solidity-contracts/LargeStruct.json");
let r = GetByIdReturn(Info::default());
let _r = GetByIdReturn(Info::default());
}
#[test]
fn gen_complex_function() {
fn can_generate_complex_function() {
abigen!(
WyvernExchangeV1,
r#"[
@ -705,13 +749,13 @@ fn gen_complex_function() {
}
#[test]
fn can_gen_large_tuple_types() {
fn can_generate_large_tuple_types() {
abigen!(LargeTuple, "./tests/solidity-contracts/large_tuple.json");
}
#[test]
fn can_gen_large_tuple_array() {
abigen!(LargeTuple, "./tests/solidity-contracts/large-array.json");
fn can_generate_large_tuple_array() {
abigen!(LargeArray, "./tests/solidity-contracts/large-array.json");
impl Default for CallWithLongArrayCall {
fn default() -> Self {
@ -743,11 +787,11 @@ fn can_handle_overloaded_function_with_array() {
abigen!(
Test,
r#"[
serializeString(string calldata, string calldata, string calldata) external returns (string memory)
serializeString(string calldata, string calldata, string[] calldata) external returns (string memory)
serializeBool(string calldata, string calldata, bool) external returns (string memory)
serializeBool(string calldata, string calldata, bool[] calldata) external returns (string memory)
]"#,
serializeString(string calldata, string calldata, string calldata) external returns (string memory)
serializeString(string calldata, string calldata, string[] calldata) external returns (string memory)
serializeBool(string calldata, string calldata, bool) external returns (string memory)
serializeBool(string calldata, string calldata, bool[] calldata) external returns (string memory)
]"#,
);
}
@ -767,7 +811,7 @@ fn convert_uses_correct_abi() {
// Ensure that `bar` is using the `Bar` ABI internally (this method lookup will panic if `bar`
// is incorrectly using the `Foo` ABI internally).
bar.bar().call();
drop(bar.bar().call());
}
#[test]
@ -779,3 +823,11 @@ fn generates_non_zero_bytecode() {
//sanity check that the bytecode is not the same
assert_ne!(GREETER_BYTECODE, GREETER_DEPLOYED_BYTECODE);
}
#[test]
fn can_generate_hardhat_console() {
abigen!(HardhatConsole, "./tests/solidity-contracts/console.json");
fn exists<T>() {}
exists::<HardhatConsoleCalls>();
}

View File

@ -1,4 +1,5 @@
#![cfg(not(target_arch = "wasm32"))]
#![allow(dead_code)]
#[cfg(feature = "abigen")]
use ethers_core::types::Address;

View File

@ -1,5 +0,0 @@
//! ensure console.sol can be generated via abigen!
ethers_contract::abigen!(HardhatConsole, "./tests/solidity-contracts/console.json",);
fn assert_console_calls(_: &hardhat_console::HardhatConsoleCalls) {}

View File

@ -1,4 +1,5 @@
#![allow(unused)]
pub use crate::common::*;
use ethers_contract::{abigen, ContractFactory, EthAbiType};
use ethers_core::types::{Filter, ValueOrArray, H256};

View File

@ -13,9 +13,9 @@ async fn contract_call_into_future_is_send() {
let client = Arc::new(provider);
let contract = DsProxyFactory::new(Address::zero(), client);
fn is_send<T: Future + Send + 'static>(future: T) -> bool {
true
fn is_send<T: Future + Send + 'static>(future: T) -> T {
future
}
assert!(is_send(contract.cache().into_future()));
is_send(contract.cache().into_future());
}

View File

@ -1,11 +1,10 @@
#![allow(unused)]
#![allow(clippy::extra_unused_type_parameters)]
#[cfg(feature = "abigen")]
mod abigen;
pub(crate) mod common;
#[cfg(feature = "abigen")]
mod console;
#[cfg(feature = "abigen")]
mod contract;
mod contract_call;
fn main() {}
mod contract_call;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -33,13 +33,24 @@ pub enum FieldType {
///
/// Note: tuples will be treated as rust tuples
Elementary(ParamType),
/// A non elementary type field, treated as user defined struct
/// A non elementary type field, treated as user-defined struct
Struct(StructFieldType),
/// Mapping
Mapping(Box<MappingType>),
}
impl FieldType {
/// Whether this field is an elementary [`ParamType`].
pub fn is_elementary(&self) -> bool {
matches!(self, FieldType::Elementary(_))
}
/// Whether this field is a user-defined struct.
pub fn is_struct(&self) -> bool {
matches!(self, FieldType::Struct(_))
}
/// Whether this field is a mapping.
pub fn is_mapping(&self) -> bool {
matches!(self, FieldType::Mapping(_))
}