Repository: ethereum/web3.py

Issue:
Cannot decode tuple types
* Version: 5.0.0
* Python: 3.6.8
* OS: linux
* `pip freeze` output

```
attrdict==2.0.1                                                                                                                                                                                      [34/41]
attrs==19.1.0
backcall==0.1.0
base58==1.0.3
bleach==3.1.0
certifi==2019.6.16
chardet==3.0.4
cycler==0.10.0
cytoolz==0.10.0
decorator==4.4.0
defusedxml==0.6.0
entrypoints==0.3
eth-abi==2.0.0
eth-account==0.4.0
eth-hash==0.2.0
eth-keyfile==0.5.1
eth-keys==0.2.4
eth-rlp==0.1.2
eth-typing==2.1.0
eth-utils==1.6.2
hexbytes==0.2.0
idna==2.8
ipfshttpclient==0.4.12
ipykernel==5.1.1
ipython==7.7.0
ipython-genutils==0.2.0
ipywidgets==7.5.1
jedi==0.14.1
Jinja2==2.10.1
joblib==0.13.2
jsonschema==2.6.0
jupyter==1.0.0
jupyter-client==5.3.1
jupyter-console==6.0.0
jupyter-core==4.5.0
kiwisolver==1.1.0
lru-dict==1.1.6
MarkupSafe==1.1.1
matplotlib==3.1.1
mistune==0.8.4
multiaddr==0.0.8
nbconvert==5.5.0
nbformat==4.4.0
netaddr==0.7.19
notebook==6.0.0
numpy==1.17.0
pandas==0.25.0
pandocfilters==1.4.2
parsimonious==0.8.1
parso==0.5.1
pexpect==4.7.0
pickleshare==0.7.5
pkg-resources==0.0.0
prometheus-client==0.7.1
prompt-toolkit==2.0.9
protobuf==3.9.0
ptyprocess==0.6.0
pycryptodome==3.8.2
Pygments==2.4.2
pyparsing==2.4.2
pyrsistent==0.15.4
python-dateutil==2.8.0
pytz==2019.2
pyzmq==18.0.2
qtconsole==4.5.2
requests==2.22.0
rlp==1.1.0
scikit-learn==0.21.3
scipy==1.3.0
seaborn==0.9.0
Send2Trash==1.5.0
six==1.12.0
sklearn==0.0
terminado==0.8.2
testpath==0.4.2
toolz==0.10.0
tornado==6.0.3
traitlets==4.3.2
urllib3==1.25.3
varint==1.0.2
wcwidth==0.1.7
web3==5.0.0
webencodings==0.5.1
websockets==7.0
widgetsnbextension==3.5.1
```


### What was wrong?

Please include any of the following that are applicable:

* The code which produced the error
```
transaction = w3.eth.getTransaction('0x7b6a4e8d41ef5471375a5ae0fb34ccde1ef4e1c2cd5f5e83d2ae6e006e29d6f0')
contract.decode_function_input(transaction.input)
```
contract abi available here: https://etherscan.io/address/0xD4B6cd147ad8A0D5376b6FDBa85fE8128C6f0686#code

* The full output of the error
```
---------------------------------------------------------------------------
NoEntriesFound                            Traceback (most recent call last)
<ipython-input-23-cc94eae11dee> in <module>
----> 1 contract.decode_function_input(transaction.input)

~/projects/liquidator/opportunity/venv/lib/python3.6/site-packages/web3/_utils/decorators.py in _wrapper(*args, **kwargs)
     12         def _wrapper(*args, **kwargs):
     13             if obj is not None:
---> 14                 return self.method(obj, *args, **kwargs)
     15             else:
     16                 return self.method(objtype, *args, **kwargs)

~/projects/liquidator/opportunity/venv/lib/python3.6/site-packages/web3/contract.py in decode_function_input(self, data)
    407         names = [x['name'] for x in func.abi['inputs']]
    408         types = [x['type'] for x in func.abi['inputs']]
--> 409         decoded = decode_abi(types, params)
    410         normalized = map_abi_data(BASE_RETURN_NORMALIZERS, types, decoded)
    411         return func, dict(zip(names, normalized))

~/projects/liquidator/opportunity/venv/lib/python3.6/site-packages/eth_abi/codec.py in decode_abi(self, types, data)
    171         decoders = [
    172             self._registry.get_decoder(type_str)
--> 173             for type_str in types
    174         ]
    175 

~/projects/liquidator/opportunity/venv/lib/python3.6/site-packages/eth_abi/codec.py in <listcomp>(.0)
    171         decoders = [
    172             self._registry.get_decoder(type_str)
--> 173             for type_str in types
    174         ]
    175 

~/projects/liquidator/opportunity/venv/lib/python3.6/site-packages/eth_abi/registry.py in get_decoder(self, type_str)
    467     @functools.lru_cache(maxsize=None)
    468     def get_decoder(self, type_str):
--> 469         return self._get_registration(self._decoders, type_str)
    470 
    471     def copy(self):

~/projects/liquidator/opportunity/venv/lib/python3.6/site-packages/eth_abi/registry.py in _get_registration(self, mapping, type_str)
    352 
    353     def _get_registration(self, mapping, type_str):
--> 354         coder = super()._get_registration(mapping, type_str)
    355 
    356         if isinstance(coder, type) and issubclass(coder, BaseCoder):

~/projects/liquidator/opportunity/venv/lib/python3.6/site-packages/eth_abi/registry.py in _get_registration(mapping, type_str)
    334     def _get_registration(mapping, type_str):
    335         try:
--> 336             value = mapping.find(type_str)
    337         except ValueError as e:
    338             if 'No matching' in e.args[0]:

~/projects/liquidator/opportunity/venv/lib/python3.6/site-packages/eth_abi/registry.py in find(self, type_str)
     89             raise NoEntriesFound("No matching entries for '{}' in {}".format(
     90                 type_str,
---> 91                 self._name,
     92             ))
     93 

NoEntriesFound: No matching entries for 'tuple' in decoder registry
```


### How can it be fixed?



Discussion:

Resolving patch:
diff --git a/web3/contract.py b/web3/contract.py
--- a/web3/contract.py
+++ b/web3/contract.py
@@ -32,6 +32,8 @@
     check_if_arguments_can_be_encoded,
     fallback_func_abi_exists,
     filter_by_type,
+    get_abi_input_names,
+    get_abi_input_types,
     get_abi_output_types,
     get_constructor_abi,
     is_array_type,
@@ -404,10 +406,13 @@ def decode_function_input(self, data):
         data = HexBytes(data)
         selector, params = data[:4], data[4:]
         func = self.get_function_by_selector(selector)
-        names = [x['name'] for x in func.abi['inputs']]
-        types = [x['type'] for x in func.abi['inputs']]
+
+        names = get_abi_input_names(func.abi)
+        types = get_abi_input_types(func.abi)
+
         decoded = decode_abi(types, params)
         normalized = map_abi_data(BASE_RETURN_NORMALIZERS, types, decoded)
+
         return func, dict(zip(names, normalized))
 
     @combomethod


Test patch:
diff --git a/tests/core/contracts/test_contract_method_abi_decoding.py b/tests/core/contracts/test_contract_method_abi_decoding.py
--- a/tests/core/contracts/test_contract_method_abi_decoding.py
+++ b/tests/core/contracts/test_contract_method_abi_decoding.py
@@ -11,6 +11,7 @@
 ABI_BYTES = json.loads('[{"constant":false,"inputs":[{"name":"bytesarg","type":"bytes"}],"name":"bytesfunc","outputs":[],"type":"function"}]')  # noqa: E501
 ABI_STRING = json.loads('[{"constant":false,"inputs":[{"name":"stringarg","type":"string"}],"name":"stringfunc","outputs":[],"type":"function"}]')  # noqa: E501
 ABI_ADDRESS = json.loads('[{"constant":false,"inputs":[{"name":"addressarg","type":"address"}],"name":"addressfunc","outputs":[],"type":"function"}]')  # noqa: E501
+ABI_TUPLE = json.loads('[{"constant":false,"inputs":[{"components":[{"name":"owner","type":"address"},{"name":"number","type":"uint256"}],"name":"fromAccount","type":"tuple"},{"components":[{"name":"owner","type":"address"},{"name":"number","type":"uint256"}],"name":"liquidAccount","type":"tuple"},{"components":[{"name":"value","type":"uint256"}],"name":"minLiquidatorRatio","type":"tuple"},{"name":"minValueLiquidated","type":"uint256"},{"name":"owedPreferences","type":"uint256[]"},{"name":"heldPreferences","type":"uint256[]"}],"name":"liquidate","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}]')  # noqa: E501
 a32bytes = b'a'.ljust(32, b'\x00')
 
 
@@ -65,6 +66,19 @@
             'addressfunc',
             {'addressarg': '0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF'},
         ),
+        (
+            ABI_TUPLE,
+            '0xc29a4b71000000000000000000000000bfae42a79ff045659dd0f84e65534f5c4c8100230000000000000000000000000000000000000000000000000000000000000000000000000000000000000000db3d3af153cb02f0bc44621db82289280e93500f94a7d1598c397f6b49ecd5ccbc2b464259b96870063493b0dc7409d0fd9fb9860000000000000000000000000000000000000000000000000429d069189e00000000000000000000000000000000000178287f49c4a1d6622fb2ab40000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001',  # noqa: E501
+            'liquidate',
+            {
+                'fromAccount': ('0xBfae42A79FF045659DD0F84e65534f5c4c810023', 0),
+                'liquidAccount': ('0xdb3d3AF153cB02f0Bc44621Db82289280e93500F', 67238809929330522294664880975001390268660278453875034113630810005818923006342),  # noqa: E501
+                'minLiquidatorRatio': (300000000000000000,),
+                'minValueLiquidated': 500000000000000000000000000000000000000,
+                'owedPreferences': [0, 1, 2],
+                'heldPreferences': [2, 0, 1],
+            },
+        ),
     ),
 )
 def test_contract_abi_decoding(web3, abi, data, method, expected):