diff --git a/tests/contracts/test_normalization_of_return_types.py b/tests/contracts/test_normalization_of_return_types.py new file mode 100644 index 0000000..6a5258b --- /dev/null +++ b/tests/contracts/test_normalization_of_return_types.py @@ -0,0 +1,35 @@ +import pytest + +from eth_abi import encode_single +from web3.utils.abi import normalize_return_type + + +@pytest.mark.parametrize( + 'data_type,data_value,expected_value', + ( + ( + 'bytes32', + 'arst\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 'arst\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ), + ( + 'address', + 'd3cda913deb6f67967b99d67acdfa1712c293601', + '0xd3cda913deb6f67967b99d67acdfa1712c293601', + ), + ( + 'address[]', + [ + 'd3cda913deb6f67967b99d67acdfa1712c293601', + 'bb9bc244d798123fde783fcc1c72d3bb8c189413', + ], + [ + '0xd3cda913deb6f67967b99d67acdfa1712c293601', + '0xbb9bc244d798123fde783fcc1c72d3bb8c189413', + ], + ), + ) +) +def test_normalizing_return_values(data_type, data_value, expected_value): + actual_value = normalize_return_type(data_type, data_value) + assert actual_value == expected_value diff --git a/tests/utilities/test_construct_event_data_set.py b/tests/utilities/test_construct_event_data_set.py index f48c295..1adc021 100644 --- a/tests/utilities/test_construct_event_data_set.py +++ b/tests/utilities/test_construct_event_data_set.py @@ -1,7 +1,7 @@ import pytest -from web3.utils.abi import ( +from web3.utils.events import ( construct_event_data_set, ) diff --git a/tests/utilities/test_construct_event_topics.py b/tests/utilities/test_construct_event_topics.py index be026bb..47230fd 100644 --- a/tests/utilities/test_construct_event_topics.py +++ b/tests/utilities/test_construct_event_topics.py @@ -1,7 +1,7 @@ import pytest -from web3.utils.abi import ( +from web3.utils.events import ( construct_event_topic_set, ) diff --git a/web3/contract.py b/web3/contract.py index a58cf92..04cf769 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -35,6 +35,7 @@ from web3.utils.abi import ( check_if_arguments_can_be_encoded, function_abi_to_4byte_selector, merge_args_and_kwargs, + normalize_return_type, ) from web3.utils.decorators import ( combomethod, @@ -642,10 +643,11 @@ def call_contract_function(contract, output_data = decode_abi(output_types, return_data) normalized_data = [ - add_0x_prefix(data_value) if data_type == 'address' else data_value + normalize_return_type(data_type, data_value) for data_type, data_value in zip(output_types, output_data) ] + if len(normalized_data) == 1: return normalized_data[0] else: diff --git a/web3/utils/abi.py b/web3/utils/abi.py index 2bccd04..7f0816f 100644 --- a/web3/utils/abi.py +++ b/web3/utils/abi.py @@ -2,10 +2,8 @@ import itertools from eth_abi.abi import ( process_type, - encode_single, ) -from .encoding import encode_hex from .crypto import sha3 from .string import ( coerce_args_to_bytes, @@ -226,85 +224,16 @@ def event_abi_to_log_topic(event_abi): @coerce_return_to_text -def construct_event_topic_set(event_abi, arguments=None): - if arguments is None: - arguments = {} - if isinstance(arguments, (list, tuple)): - if len(arguments) != len(event_abi['inputs']): - raise ValueError( - "When passing an argument list, the number of arguments must " - "match the event constructor." - ) - arguments = { - arg['name']: [arg_value] - for arg, arg_value - in zip(event_abi['inputs'], arguments) - } +def normalize_return_type(data_type, data_value): + try: + base, sub, arrlist = data_type + except ValueError: + base, sub, arrlist = process_type(data_type) - normalized_args = { - key: value if is_array(value) else [value] - for key, value in arguments.items() - } - - event_topic = event_abi_to_log_topic(event_abi) - indexed_args = get_indexed_event_inputs(event_abi) - zipped_abi_and_args = [ - (arg, normalized_args.get(arg['name'], [None])) - for arg in indexed_args - ] - encoded_args = [ - [ - None if option is None else encode_hex(encode_single(arg['type'], option)) - for option in arg_options] - for arg, arg_options in zipped_abi_and_args - ] - - topics = [ - [event_topic] + list(permutation) - if any(value is not None for value in permutation) - else [event_topic] - for permutation in itertools.product(*encoded_args) - ] - return topics - - -@coerce_return_to_text -def construct_event_data_set(event_abi, arguments=None): - if arguments is None: - arguments = {} - if isinstance(arguments, (list, tuple)): - if len(arguments) != len(event_abi['inputs']): - raise ValueError( - "When passing an argument list, the number of arguments must " - "match the event constructor." - ) - arguments = { - arg['name']: [arg_value] - for arg, arg_value - in zip(event_abi['inputs'], arguments) - } - - normalized_args = { - key: value if is_array(value) else [value] - for key, value in arguments.items() - } - - indexed_args = exclude_indexed_event_inputs(event_abi) - zipped_abi_and_args = [ - (arg, normalized_args.get(arg['name'], [None])) - for arg in indexed_args - ] - encoded_args = [ - [ - None if option is None else encode_hex(encode_single(arg['type'], option)) - for option in arg_options] - for arg, arg_options in zipped_abi_and_args - ] - - topics = [ - list(permutation) - if any(value is not None for value in permutation) - else [] - for permutation in itertools.product(*encoded_args) - ] - return topics + if arrlist: + sub_type = (base, sub, arrlist[:-1]) + return [normalize_return_type(sub_type, sub_value) for sub_value in data_value] + elif base == 'address': + return add_0x_prefix(data_value) + else: + return data_value diff --git a/web3/utils/events.py b/web3/utils/events.py index 1468940..45f8276 100644 --- a/web3/utils/events.py +++ b/web3/utils/events.py @@ -3,17 +3,108 @@ import itertools from eth_abi import ( decode_abi, decode_single, + encode_single, +) + +from .encoding import encode_hex +from .types import ( + is_array, +) +from .string import ( + coerce_return_to_text, ) from .abi import ( get_abi_input_types, get_abi_input_names, get_indexed_event_inputs, exclude_indexed_event_inputs, + event_abi_to_log_topic, ) -ABI_EVENT_TYPE_MAP = { -} +@coerce_return_to_text +def construct_event_topic_set(event_abi, arguments=None): + if arguments is None: + arguments = {} + if isinstance(arguments, (list, tuple)): + if len(arguments) != len(event_abi['inputs']): + raise ValueError( + "When passing an argument list, the number of arguments must " + "match the event constructor." + ) + arguments = { + arg['name']: [arg_value] + for arg, arg_value + in zip(event_abi['inputs'], arguments) + } + + normalized_args = { + key: value if is_array(value) else [value] + for key, value in arguments.items() + } + + event_topic = event_abi_to_log_topic(event_abi) + indexed_args = get_indexed_event_inputs(event_abi) + zipped_abi_and_args = [ + (arg, normalized_args.get(arg['name'], [None])) + for arg in indexed_args + ] + encoded_args = [ + [ + None if option is None else encode_hex(encode_single(arg['type'], option)) + for option in arg_options] + for arg, arg_options in zipped_abi_and_args + ] + + topics = [ + [event_topic] + list(permutation) + if any(value is not None for value in permutation) + else [event_topic] + for permutation in itertools.product(*encoded_args) + ] + return topics + + +@coerce_return_to_text +def construct_event_data_set(event_abi, arguments=None): + if arguments is None: + arguments = {} + if isinstance(arguments, (list, tuple)): + if len(arguments) != len(event_abi['inputs']): + raise ValueError( + "When passing an argument list, the number of arguments must " + "match the event constructor." + ) + arguments = { + arg['name']: [arg_value] + for arg, arg_value + in zip(event_abi['inputs'], arguments) + } + + normalized_args = { + key: value if is_array(value) else [value] + for key, value in arguments.items() + } + + indexed_args = exclude_indexed_event_inputs(event_abi) + zipped_abi_and_args = [ + (arg, normalized_args.get(arg['name'], [None])) + for arg in indexed_args + ] + encoded_args = [ + [ + None if option is None else encode_hex(encode_single(arg['type'], option)) + for option in arg_options] + for arg, arg_options in zipped_abi_and_args + ] + + topics = [ + list(permutation) + if any(value is not None for value in permutation) + else [] + for permutation in itertools.product(*encoded_args) + ] + return topics def coerce_event_abi_types_for_decoding(input_types): diff --git a/web3/utils/filters.py b/web3/utils/filters.py index 2f0e83d..5caedd1 100644 --- a/web3/utils/filters.py +++ b/web3/utils/filters.py @@ -6,7 +6,7 @@ from .types import ( is_string, is_array, ) -from .abi import ( +from .events import ( construct_event_topic_set, construct_event_data_set, )