From faa580176d718e730151ba6df49f0328118d9cce Mon Sep 17 00:00:00 2001 From: pacrob Date: Tue, 31 Jan 2023 13:31:55 -0700 Subject: [PATCH] catch mismatched abi data and test for namedtuple rename --- tests/core/utilities/test_abi_named_tree.py | 102 ++++++++++++-------- web3/_utils/abi.py | 13 ++- 2 files changed, 70 insertions(+), 45 deletions(-) diff --git a/tests/core/utilities/test_abi_named_tree.py b/tests/core/utilities/test_abi_named_tree.py index 4c6e6b1547..e85952d671 100644 --- a/tests/core/utilities/test_abi_named_tree.py +++ b/tests/core/utilities/test_abi_named_tree.py @@ -1,3 +1,5 @@ +import pytest + from eth_abi.codec import ( ABICodec, ) @@ -11,70 +13,86 @@ foldable_namedtuple, named_tree, ) +from web3.exceptions import ( + MismatchedABI, +) from .test_abi import ( TEST_FUNCTION_ABI, ) -abi = TEST_FUNCTION_ABI["inputs"] -inputs = ( +full_abi_inputs = TEST_FUNCTION_ABI["inputs"] +full_values = ( (1, [2, 3, 4], [(5, 6), (7, 8), (9, 10)]), # Value for s (11, 12), # Value for t 13, # Value for a [[(14, 15), (16, 17)], [(18, 19)]], # Value for b ) -sample_abi_inputs = { - "inputs": [ - { - "components": [ - {"name": "a", "type": "uint256"}, - {"name": "b", "type": "uint256[]"}, - { - "components": [ - {"name": "x", "type": "uint256"}, - {"name": "y", "type": "uint256"}, - ], - "name": "c", - "type": "tuple[]", - }, - ], - "name": "s", - "type": "tuple", - }, - { - "components": [ - {"name": "x", "type": "uint256"}, - {"name": "y", "type": "uint256"}, - ], - "name": "t", - "type": "tuple", - }, - {"name": "a", "type": "uint256"}, - { - "components": [ - {"name": "x", "type": "uint256"}, - {"name": "y", "type": "uint256"}, - ], - "name": "b", - "type": "tuple[][]", - }, - ] -} def test_named_arguments_decode(): - decoded = named_tree(abi, inputs) + decoded = named_tree(full_abi_inputs, full_values) data = dict_to_namedtuple(decoded) - assert data == inputs + assert data == full_values assert data.s.c[2].y == 10 assert data.t.x == 11 assert data.a == 13 +short_abi_inputs_with_disallowed_names = [ + { + "components": [ + {"name": "from", "type": "uint256"}, + {"name": "to", "type": "uint256[]"}, + { + "components": [ + {"name": "_x", "type": "uint256"}, + {"name": "_y", "type": "uint256"}, + ], + "name": "c", + "type": "tuple[]", + }, + ], + "name": "s", + "type": "tuple", + }, +] + +short_values = ((1, [2, 3, 4], [(5, 6), (7, 8), (9, 10)]),) + + +def test_named_arguments_decode_rename(): + decoded = named_tree(short_abi_inputs_with_disallowed_names, short_values) + data = dict_to_namedtuple(decoded) + assert data == short_values + assert data._fields == ("s",) + + # python keyword "from" renamed to "_0" + assert data.s._fields == ("_0", "to", "c") + + # field names starting with "_" - "_x" and "_y" - renamed to "_0" and "_1" + assert data.s.c[0]._fields == ("_0", "_1") + assert data.s.c[2]._1 == 10 + assert data.s.to[1] == 3 + + +@pytest.mark.parametrize( + "values", + ( + ((1, [2, 3, 4], [(5,), (7, 8), (9, 10)]),), + ((1, [2, 3, 4], [(5, 6, 11), (7, 8), (9, 10)]),), + ((1, [(5, 6), (7, 8), (9, 10)]),), + ), +) +def test_named_arguments_decode_with_misshapen_inputs(values): + with pytest.raises(MismatchedABI): + named_tree(short_abi_inputs_with_disallowed_names, values) + + def test_namedtuples_encodable(): registry = default_registry.copy() codec = ABICodec(registry) - kwargs = named_tree(abi, inputs) + kwargs = named_tree(full_abi_inputs, full_values) args = dict_to_namedtuple(kwargs) assert check_if_arguments_can_be_encoded(TEST_FUNCTION_ABI, codec, (), kwargs) assert check_if_arguments_can_be_encoded(TEST_FUNCTION_ABI, codec, args, {}) diff --git a/web3/_utils/abi.py b/web3/_utils/abi.py index 452f0d34f2..97fc19de7f 100644 --- a/web3/_utils/abi.py +++ b/web3/_utils/abi.py @@ -74,6 +74,7 @@ ) from web3.exceptions import ( FallbackNotFound, + MismatchedABI, ) from web3.types import ( ABI, @@ -920,6 +921,7 @@ def named_tree( # TODO how to handle if names and items end up different len # return dict(zip(names, items)) if all(names) else items + return dict(zip(names, items)) @@ -933,15 +935,20 @@ def named_subtree( if abi_type.is_array: item_type = abi_type.item_type.to_type_str() item_abi = {**abi, "type": item_type, "name": ""} - # cast(ABIFunctionParams, item_abi) - # breakpoint() items = [named_subtree(item_abi, item) for item in data] return items if isinstance(abi_type, TupleType): names = [item["name"] for item in abi["components"]] items = [named_subtree(*item) for item in zip(abi["components"], data)] - return dict(zip(names, items)) + + if len(names) == len(data): + return dict(zip(names, items)) + else: + raise MismatchedABI( + f"ABI fields {names} has length {len(names)} but received " + f"data {data} with length {len(data)}" + ) return data