Repository: eth-brownie/brownie
Issue: Issue decoding structs containing strings

### What was wrong?

Attempting to decode a struct where the final value is a `bytes` or `string` raises an `IndexError`.
The issue occurs when using either of the following structs in a return value:

```
    struct Foo {
        address a;
        uint256 b;
        address c;
        uint256 d;
        bytes e;
    }
```

```
    struct Bar {
        address a;
        uint256 b;
        address c;
        uint256 d;
        bytes e;
        string f;
    }
```


Changed files:
brownie/convert/datatypes.py, brownie/convert/utils.py, tests/convert/test_return_value.py

diff --git a/brownie/convert/datatypes.py b/brownie/convert/datatypes.py
index db42914b..1a6a7b8a 100644
--- a/brownie/convert/datatypes.py
+++ b/brownie/convert/datatypes.py
@@ -286,9 +286,16 @@ class ReturnValue(tuple):
         for i in range(len(values)):
             if isinstance(values[i], (tuple, list)) and not isinstance(values[i], ReturnValue):
                 if abi is not None and "components" in abi[i]:
-                    values[i] = ReturnValue(values[i], abi[i]["components"])
+                    if abi[i]["type"] == "tuple":
+                        # tuple
+                        values[i] = ReturnValue(values[i], abi[i]["components"])
+                    else:
+                        # array of tuples
+                        values[i] = ReturnValue(values[i], [abi[i]] * len(values[i]))
                 else:
+                    # array
                     values[i] = ReturnValue(values[i])
+
         self = super().__new__(cls, values)  # type: ignore
         self._abi = abi or []
         self._dict = {i["name"]: values[c] for c, i in enumerate(self._abi) if i["name"]}
diff --git a/brownie/convert/utils.py b/brownie/convert/utils.py
index 62443673..c36b3211 100644
--- a/brownie/convert/utils.py
+++ b/brownie/convert/utils.py
@@ -20,19 +20,19 @@ def get_type_strings(abi_params: List, substitutions: Optional[Dict] = None) ->
     types_list = []
     if substitutions is None:
         substitutions = {}
+
     for i in abi_params:
-        if i["type"] == "tuple":
-            params = get_type_strings(i["components"], substitutions)
-            types_list.append(f"({','.join(params)})")
-        elif i["type"] == "tuple[]":
+        if i["type"].startswith("tuple"):
             params = get_type_strings(i["components"], substitutions)
-            types_list.append(f"({','.join(params)})[]")
+            array_size = i["type"][5:]
+            types_list.append(f"({','.join(params)}){array_size}")
         else:
             type_str = i["type"]
             for orig, sub in substitutions.items():
                 if type_str.startswith(orig):
                     type_str = type_str.replace(orig, sub)
             types_list.append(type_str)
+
     return types_list
 
 
diff --git a/tests/convert/test_return_value.py b/tests/convert/test_return_value.py
index c74f9f7a..7fc51415 100644
--- a/tests/convert/test_return_value.py
+++ b/tests/convert/test_return_value.py
@@ -3,6 +3,7 @@
 import pytest
 
 from brownie.convert.datatypes import EthAddress, HexString, ReturnValue, Wei
+from brownie.project import compile_source
 
 
 @pytest.fixture
@@ -122,3 +123,55 @@ def test_hashable():
 def test_decimals(vypertester):
     ret = vypertester.fixedType("1.234", ["-42", "3.1337"])
     assert ret == ["1.234", "-42", "3.1337"]
+
+
+def test_dynamic_tuple_array(accounts):
+    code = """
+pragma solidity ^0.6.0;
+pragma experimental ABIEncoderV2;
+
+contract Test {
+
+struct Foo { uint256 a; }
+
+Foo[] bar;
+
+function foo() public returns (Foo[] memory a) {
+    bar.push(Foo(1));
+    bar.push(Foo(6));
+    return bar;
+}
+
+}
+"""
+    contract = compile_source(code).Test.deploy({"from": accounts[0]})
+
+    assert contract.foo.call() == [(1,), (6,)]
+
+
+def test_fixed_tuple_array(accounts):
+    code = """
+pragma solidity ^0.6.0;
+pragma experimental ABIEncoderV2;
+
+contract Test {
+
+struct Foo { uint256 a; string b; }
+
+Foo[2][2] bar;
+
+function foo() public returns (Foo[2][2] memory, Foo[2] memory) {
+    bar[0][0].a = 42;
+    bar[0][0].b = "hello";
+    bar[1][1].a = 69;
+    return (bar, bar[1]);
+}
+
+}
+"""
+    contract = compile_source(code).Test.deploy({"from": accounts[0]})
+
+    assert contract.foo.call() == [
+        ([(42, "hello"), (0, "")], [(0, ""), (69, "")]),
+        [(0, ""), (69, "")],
+    ]