diff --git a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs index 58a2aaff..69f59ce9 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs @@ -1,8 +1,9 @@ use std::collections::{btree_map::Entry, BTreeMap, HashMap}; use super::{types, util, Context}; -use crate::contract::common::{ - expand_data_struct, expand_data_tuple, expand_param_type, expand_params, +use crate::{ + contract::common::{expand_data_struct, expand_data_tuple, expand_param_type, expand_params}, + util::can_derive_defaults, }; use ethers_core::{ abi::{Function, FunctionExt, Param, ParamType}, @@ -134,9 +135,20 @@ impl Context { // 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) + let derive_default = if can_derive_defaults(&function.inputs) { + quote! { + #[derive(Default)] + } + } else { + quote! {} + }; + Ok(quote! { #abi_signature_doc - #[derive(Clone, Debug, Default, Eq, PartialEq, #ethers_contract::EthCall, #ethers_contract::EthDisplay, #derives)] + #[derive(Clone, Debug, Eq, PartialEq, #ethers_contract::EthCall, #ethers_contract::EthDisplay, #derives)] + #derive_default #[ethcall( name = #function_name, abi = #abi_signature )] pub #call_type_definition }) @@ -175,9 +187,20 @@ impl Context { // 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) + let derive_default = if can_derive_defaults(&function.outputs) { + quote! { + #[derive(Default)] + } + } else { + quote! {} + }; + Ok(quote! { #abi_signature_doc - #[derive(Clone, Debug, Default, Eq, PartialEq, #ethers_contract::EthAbiType, #ethers_contract::EthAbiCodec, #derives)] + #[derive(Clone, Debug,Eq, PartialEq, #ethers_contract::EthAbiType, #ethers_contract::EthAbiCodec, #derives)] + #derive_default pub #return_type_definition }) } diff --git a/ethers-contract/ethers-contract-abigen/src/util.rs b/ethers-contract/ethers-contract-abigen/src/util.rs index ec465104..5a2f775b 100644 --- a/ethers-contract/ethers-contract-abigen/src/util.rs +++ b/ethers-contract/ethers-contract-abigen/src/util.rs @@ -1,11 +1,12 @@ -use ethers_core::types::Address; -use std::path::PathBuf; - +use ethers_core::{ + abi::{Param, ParamType}, + types::Address, +}; use eyre::Result; use inflector::Inflector; use proc_macro2::{Ident, Literal, Span, TokenStream}; use quote::quote; - +use std::path::PathBuf; use syn::{Ident as SynIdent, Path}; /// Expands a identifier string into a token. @@ -185,10 +186,42 @@ pub fn json_files(root: impl AsRef) -> Vec { .collect() } +/// rust-std derives `Default` automatically only for arrays len <= 32 +/// +/// Returns whether the corresponding struct can derive `Default` +pub fn can_derive_defaults(params: &[Param]) -> bool { + params.iter().map(|param| ¶m.kind).all(can_derive_default) +} + +pub fn can_derive_default(param: &ParamType) -> bool { + const MAX_SUPPORTED_LEN: usize = 32; + match param { + ParamType::FixedBytes(len) => *len <= MAX_SUPPORTED_LEN, + ParamType::FixedArray(ty, len) => { + if *len > MAX_SUPPORTED_LEN { + false + } else { + can_derive_default(ty) + } + } + ParamType::Tuple(params) => params.iter().all(can_derive_default), + _ => true, + } +} + #[cfg(test)] mod tests { use super::*; + #[test] + fn can_detect_non_default() { + let param = ParamType::FixedArray(Box::new(ParamType::Uint(64)), 128); + assert!(!can_derive_default(¶m)); + + let param = ParamType::FixedArray(Box::new(ParamType::Uint(64)), 32); + assert!(can_derive_default(¶m)); + } + #[test] fn can_resolve_path() { let raw = "./$ENV_VAR"; diff --git a/ethers-contract/tests/it/abigen.rs b/ethers-contract/tests/it/abigen.rs index 273498d0..2d924be3 100644 --- a/ethers-contract/tests/it/abigen.rs +++ b/ethers-contract/tests/it/abigen.rs @@ -11,10 +11,14 @@ use ethers_middleware::SignerMiddleware; use ethers_providers::{MockProvider, Provider}; use ethers_signers::{LocalWallet, Signer}; use ethers_solc::Solc; -use std::{convert::TryFrom, sync::Arc}; +use std::{ + convert::{TryFrom, TryInto}, + sync::Arc, +}; fn assert_codec() {} fn assert_tokenizeable() {} +fn assert_call() {} #[test] fn can_gen_human_readable() { @@ -237,6 +241,9 @@ fn can_gen_human_readable_with_structs() { assert_eq!(contract_call, decoded_enum); assert_eq!(contract_call, call.into()); assert_eq!(encoded_call, contract_call.encode()); + + assert_call::(); + assert_call::(); } #[test] @@ -301,6 +308,10 @@ fn can_handle_overloaded_functions() { let _contract_call = SimpleContractCalls::SetValue0(call); let call = SetValue1Call("message".to_string(), "message".to_string()); let _contract_call = SimpleContractCalls::SetValue1(call); + + assert_call::(); + assert_call::(); + assert_call::(); } #[test] @@ -695,3 +706,17 @@ fn gen_complex_function() { fn can_gen_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"); + + impl Default for CallWithLongArrayCall { + fn default() -> Self { + Self { long_array: [0; 128] } + } + } + + let _call = CallWithLongArrayCall::default(); + assert_call::(); +} diff --git a/ethers-contract/tests/solidity-contracts/large-array.json b/ethers-contract/tests/solidity-contracts/large-array.json new file mode 100644 index 00000000..8d681240 --- /dev/null +++ b/ethers-contract/tests/solidity-contracts/large-array.json @@ -0,0 +1,15 @@ +[ + { + "inputs": [ + { + "internalType": "uint64[128]", + "name": "longArray", + "type": "uint64[128]" + } + ], + "name": "callWithLongArray", + "outputs": [], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file