Repository: gakonst/ethers-rs (rust)

Issue:
Support `FixedArray` with larger than 32 elements in `abigen!`
**Is your feature request related to a problem? Please describe.**
Rust does not implement the `Default` for `[T; N]` where `N` > 32.  So if a solidity function has an input parameter with type `uint256[40]`, `abigen!` can not work well. 

**Describe the solution you'd like**
Introduce a custom trait `CustomDefault`, which has the same behavior as the `Default` trait, except the way to initialize an array `[T; N]`. It simply assumes `T: Clone` and initializes `[T; N]` by `[T::custom_default(); N]`. Then implement `Default` for solidity structs/inputs by `CustomDefault`.

**Describe alternatives you've considered**

**Alternative 1:**
Manually implementing the `Default` trait in the procedure macro. 

**Alternative 2:**
Use a wrapped type for large fixed arrays. Like what you did for `Uint8`. 


Pull request:
fix(abigen): only derive default of no arrays len > 32
<!--
Thank you for your Pull Request. Please provide a description above and review
the requirements below.

Bug fixes and new features should include tests.

Contributors guide: https://github.com/gakonst/ethers-rs/blob/master/CONTRIBUTING.md

The contributors guide includes instructions for running rustfmt and building the
documentation.
-->

## Motivation
Closes #1640

this adds a check whether default can be derived for a call/output struct
rust-std auto derives default only for arrays len <=32

I think it's fine to skip the derive if not possible, since this generates code, the user still can implement it manually
<!--
Explain the context and why you're making that change. What is the problem
you're trying to solve? In some cases there is not a problem and this can be
thought of as being the motivation for your change.
-->

## Solution
skip `derive(Default)` of a type contains a fixed array with more than 32 elements
<!--
Summarize the solution and provide any necessary context needed to understand
the code change.
-->

## PR Checklist

- [x] Added Tests
- [ ] Added Documentation
- [ ] Updated the changelog


Patch:
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) <https://github.com/gakonst/ethers-rs/issues/1640>
+        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) <https://github.com/gakonst/ethers-rs/issues/1640>
+        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<std::path::Path>) -> Vec<PathBuf> {
         .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| &param.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(&param));
+
+        let param = ParamType::FixedArray(Box::new(ParamType::Uint(64)), 32);
+        assert!(can_derive_default(&param));
+    }
+
     #[test]
     fn can_resolve_path() {
         let raw = "./$ENV_VAR";


Test patch:
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<T: AbiDecode + AbiEncode>() {}
 fn assert_tokenizeable<T: Tokenizable>() {}
+fn assert_call<T: AbiEncode + AbiDecode + Default + Tokenizable>() {}
 
 #[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::<BarCall>();
+    assert_call::<YeetCall>();
 }
 
 #[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::<SetValue0Call>();
+    assert_call::<SetValue1Call>();
+    assert_call::<GetValueWithOtherValueAndAddrCall>();
 }
 
 #[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::<CallWithLongArrayCall>();
+}
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