Repository: gakonst/ethers-rs (rust)

Issue:
abigen: function returning a struct generates rust that wont compile
```solidity
interface Example {
    struct Info {
        uint128 x;
        int24 y;
        int24 z;
        uint256 a;
        int256 b;
        int256 c;
        int256 d;
        uint256 e;
        uint256 f;
    }

    function getById(bytes32 id) external view returns (Info memory);
}
```

generates this

```rust
    #[doc = "Container type for all return fields from the `getById` function
    #[derive(
        Clone,
        Debug,
        Default,
        Eq,
        PartialEq,
        ethers :: contract :: EthAbiType,
        ethers :: contract :: EthAbiCodec,
    )]
    pub struct GetByIdReturn(
        pub  ( // <- this does not compile... should generate and return the Info struct
            u128,
            i32,
            i32,
            ethers::core::types::U256,
            I256,
            I256,
            I256,
            ethers::core::types::U256,
            ethers::core::types::U256,
        ),
    );
```

but should be this

```rust
    #[doc = "Container type for all return fields from the `getById` function
    #[derive(
        Clone,
        Debug,
        Default,
        Eq,
        PartialEq,
        ethers :: contract :: EthAbiType,
        ethers :: contract :: EthAbiCodec,
    )]
    pub struct GetByIdReturn(
        pub Info,
    );
```

Pull request description:
fix(abigen): resolve output struct types correctly
<!--
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 #1509
<!--
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
resolve output types correctly when we expand the function output params
<!--
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 3894802b..11b1641a 100644
--- a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs
+++ b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs
@@ -281,13 +281,9 @@ impl Context {
         util::ident(&format!("{}Calls", self.contract_ident))
     }
 
-    /// Expands to the `name : type` pairs of the function's parameters
-    fn expand_params(
-        &self,
-        fun: &Function,
-        params: &[Param],
-    ) -> Result<Vec<(TokenStream, TokenStream)>> {
-        params
+    /// Expands to the `name : type` pairs of the function's inputs
+    fn expand_input_params(&self, fun: &Function) -> Result<Vec<(TokenStream, TokenStream)>> {
+        fun.inputs
             .iter()
             .enumerate()
             .map(|(idx, param)| {
@@ -298,14 +294,17 @@ impl Context {
             .collect()
     }
 
-    /// Expands to the `name : type` pairs of the function's inputs
-    fn expand_input_params(&self, fun: &Function) -> Result<Vec<(TokenStream, TokenStream)>> {
-        self.expand_params(fun, &fun.inputs)
-    }
-
     /// Expands to the `name : type` pairs of the function's outputs
     fn expand_output_params(&self, fun: &Function) -> Result<Vec<(TokenStream, TokenStream)>> {
-        self.expand_params(fun, &fun.outputs)
+        fun.outputs
+            .iter()
+            .enumerate()
+            .map(|(idx, param)| {
+                let name = util::expand_input_name(idx, &param.name);
+                let ty = self.expand_output_param_type(fun, param, &param.kind)?;
+                Ok((name, ty))
+            })
+            .collect()
     }
 
     /// Expands to the return type of a function


Test patch:
diff --git a/ethers-contract/tests/it/abigen.rs b/ethers-contract/tests/it/abigen.rs
index fd661ad0..9ca7e9a9 100644
--- a/ethers-contract/tests/it/abigen.rs
+++ b/ethers-contract/tests/it/abigen.rs
@@ -659,3 +659,10 @@ fn can_generate_to_string_overload() {
 fn can_generate_large_event() {
     abigen!(NewSale, "ethers-contract/tests/solidity-contracts/sale.json");
 }
+
+#[test]
+fn can_generate_large_output_struct() {
+    abigen!(LargeOutputStruct, "ethers-contract/tests/solidity-contracts/LargeStruct.json");
+
+    let r = GetByIdReturn(Info::default());
+}
diff --git a/ethers-contract/tests/solidity-contracts/LargeStruct.json b/ethers-contract/tests/solidity-contracts/LargeStruct.json
new file mode 100644
index 00000000..04e4531b
--- /dev/null
+++ b/ethers-contract/tests/solidity-contracts/LargeStruct.json
@@ -0,0 +1,68 @@
+[
+  {
+    "inputs": [
+      {
+        "internalType": "bytes32",
+        "name": "id",
+        "type": "bytes32"
+      }
+    ],
+    "name": "getById",
+    "outputs": [
+      {
+        "components": [
+          {
+            "internalType": "uint128",
+            "name": "x",
+            "type": "uint128"
+          },
+          {
+            "internalType": "int24",
+            "name": "y",
+            "type": "int24"
+          },
+          {
+            "internalType": "int24",
+            "name": "z",
+            "type": "int24"
+          },
+          {
+            "internalType": "uint256",
+            "name": "a",
+            "type": "uint256"
+          },
+          {
+            "internalType": "int256",
+            "name": "b",
+            "type": "int256"
+          },
+          {
+            "internalType": "int256",
+            "name": "c",
+            "type": "int256"
+          },
+          {
+            "internalType": "int256",
+            "name": "d",
+            "type": "int256"
+          },
+          {
+            "internalType": "uint256",
+            "name": "e",
+            "type": "uint256"
+          },
+          {
+            "internalType": "uint256",
+            "name": "f",
+            "type": "uint256"
+          }
+        ],
+        "internalType": "struct Many.Info",
+        "name": "",
+        "type": "tuple"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  }
+]
\ No newline at end of file