diff --git a/setup.py b/setup.py index d23106d225..6f68df4e73 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,7 @@ python_requires='>=3.5, <4', extras_require={ 'tester': [ - "eth-tester[py-evm]==0.1.0-beta.29", + "eth-tester[py-evm]==0.1.0-beta.30", "py-geth>=2.0.1,<3.0.0", ], 'testrpc': ["eth-testrpc>=1.3.3,<2.0.0"], diff --git a/tests/core/filtering/test_contract_createFilter_topic_merging.py b/tests/core/filtering/test_contract_createFilter_topic_merging.py new file mode 100644 index 0000000000..61ed78c173 --- /dev/null +++ b/tests/core/filtering/test_contract_createFilter_topic_merging.py @@ -0,0 +1,19 @@ +import pytest + + +def test_merged_topic_list_event( + web3, + emitter, + emitter_event_ids, + wait_for_transaction): + manual_topics = [ + '0xf16c999b533366ca5138d78e85da51611089cd05749f098d6c225d4cd42ee6ec', # event sig + '0x0000000000000000000000000000000000000000000000000000000000000457', # 1111 + '0x0000000000000000000000000000000000000000000000000000000000000457', # 1111 + '0x0000000000000000000000000000000000000000000000000000000000000457', # 1111 + ] + with pytest.raises(TypeError): + emitter.events.LogTripleWithIndex().createFilter( + fromBlock="latest", + topics=manual_topics, + argument_filters={'arg0': 2222, 'arg1': 2222, 'arg2': 2222}) diff --git a/tests/core/filtering/test_contract_on_event_filtering.py b/tests/core/filtering/test_contract_on_event_filtering.py index 15892b34ea..48112d0f96 100644 --- a/tests/core/filtering/test_contract_on_event_filtering.py +++ b/tests/core/filtering/test_contract_on_event_filtering.py @@ -143,3 +143,52 @@ def test_filter_with_contract_address(web3, emitter, emitter_event_ids, wait_for seen_logs = event_filter.get_new_entries() assert len(seen_logs) == 1 assert seen_logs[0]['transactionHash'] == txn_hash + + +@pytest.mark.parametrize('call_as_instance', (True, False)) +def test_on_sync_filter_with_topic_filter_options( + web3, + emitter, + Emitter, + wait_for_transaction, + emitter_event_ids, + call_as_instance, + create_filter): + + if call_as_instance: + contract = emitter + else: + contract = Emitter + + event_filter = create_filter(contract, ['LogTripleWithIndex', {'filter': { + 'arg1': [1, 2], + 'arg2': [1, 2] + }}]) + + txn_hashes = [] + event_id = emitter_event_ids.LogTripleWithIndex + txn_hashes.append( + emitter.functions.logTriple(event_id, 1, 1, 1).transact() + ) + txn_hashes.append( + emitter.functions.logTriple(event_id, 1, 1, 2).transact() + ) + txn_hashes.append( + emitter.functions.logTriple(event_id, 1, 2, 2).transact() + ) + txn_hashes.append( + emitter.functions.logTriple(event_id, 1, 2, 1).transact() + ) + for txn_hash in txn_hashes: + wait_for_transaction(web3, txn_hash) + + seen_logs = event_filter.get_new_entries() + assert len(seen_logs) == 4 + + post_event_filter = contract.events.LogTripleWithIndex.createFilter( + argument_filters={'arg1': [1, 2], 'arg2': [1, 2]}, + fromBlock=0, + ) + + old_logs = post_event_filter.get_all_entries() + assert len(old_logs) == 4 diff --git a/tests/core/utilities/test_construct_event_filter_params.py b/tests/core/utilities/test_construct_event_filter_params.py index 243025c8b9..244cd4750b 100644 --- a/tests/core/utilities/test_construct_event_filter_params.py +++ b/tests/core/utilities/test_construct_event_filter_params.py @@ -21,10 +21,9 @@ (EVENT_1_ABI, {}, { "topics": ['0xb470a829ed7792f06947f0ca3730a570cb378329ddcf09f2b4efabd6326f51f6'], }), - (EVENT_1_ABI, {'topics': ['should-be-preserved']}, { + (EVENT_1_ABI, {'topics': ['should-overwrite-topics']}, { "topics": [ - ['should-be-preserved'], - ['0xb470a829ed7792f06947f0ca3730a570cb378329ddcf09f2b4efabd6326f51f6'], + 'should-overwrite-topics' ] }), (EVENT_1_ABI, {'contract_address': '0xd3CdA913deB6f67967B99D67aCDFa1712C293601'}, { diff --git a/tests/core/utilities/test_construct_event_topics.py b/tests/core/utilities/test_construct_event_topics.py index 482641e6b3..75b8d52691 100644 --- a/tests/core/utilities/test_construct_event_topics.py +++ b/tests/core/utilities/test_construct_event_topics.py @@ -31,48 +31,46 @@ def hex_and_pad(i): ( EVENT_1_ABI, {}, - [[EVENT_1_TOPIC]], + [EVENT_1_TOPIC], ), ( EVENT_1_ABI, {'arg0': 1}, - [[EVENT_1_TOPIC]], + [EVENT_1_TOPIC], ), ( EVENT_1_ABI, {'arg0': 1, 'arg3': [1, 2]}, - [[EVENT_1_TOPIC]], + [EVENT_1_TOPIC], ), ( EVENT_1_ABI, {'arg1': 1}, [ - [EVENT_1_TOPIC, hex_and_pad(1), None, None], + EVENT_1_TOPIC, hex_and_pad(1) ], ), ( EVENT_1_ABI, {'arg1': [1, 2]}, [ - [EVENT_1_TOPIC, hex_and_pad(1), None, None], - [EVENT_1_TOPIC, hex_and_pad(2), None, None], + EVENT_1_TOPIC, [hex_and_pad(1), hex_and_pad(2)], ], ), ( EVENT_1_ABI, {'arg1': [1], 'arg2': [2]}, [ - [EVENT_1_TOPIC, hex_and_pad(1), hex_and_pad(2), None], + EVENT_1_TOPIC, hex_and_pad(1), hex_and_pad(2), ], ), ( EVENT_1_ABI, {'arg1': [1, 3], 'arg2': [2, 4]}, [ - [EVENT_1_TOPIC, hex_and_pad(1), hex_and_pad(2), None], - [EVENT_1_TOPIC, hex_and_pad(1), hex_and_pad(4), None], - [EVENT_1_TOPIC, hex_and_pad(3), hex_and_pad(2), None], - [EVENT_1_TOPIC, hex_and_pad(3), hex_and_pad(4), None], + EVENT_1_TOPIC, + [hex_and_pad(1), hex_and_pad(3)], + [hex_and_pad(2), hex_and_pad(4)] ], ), ) diff --git a/web3/utils/events.py b/web3/utils/events.py index 8faa86bec8..6f83342e75 100644 --- a/web3/utils/events.py +++ b/web3/utils/events.py @@ -28,6 +28,10 @@ from web3.utils.normalizers import ( BASE_RETURN_NORMALIZERS, ) +from web3.utils.toolz import ( + compose, + curry, +) from .abi import ( exclude_indexed_event_inputs, @@ -71,12 +75,7 @@ def construct_event_topic_set(event_abi, arguments=None): 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) - ] + topics = list(normalize_topic_list([event_topic] + encoded_args)) return topics @@ -221,3 +220,21 @@ def get_event_data(event_abi, log_entry): } return AttributeDict.recursive(event_data) + + +@to_tuple +def pop_singlets(seq): + yield from (i[0] if is_list_like(i) and len(i) == 1 else i for i in seq) + + +@curry +def remove_trailing_from_seq(seq, remove_value=None): + index = len(seq) + while index > 0 and seq[index - 1] == remove_value: + index -= 1 + return seq[:index] + + +normalize_topic_list = compose( + remove_trailing_from_seq(remove_value=None), + pop_singlets,) diff --git a/web3/utils/filters.py b/web3/utils/filters.py index caccf65072..ba85d2484c 100644 --- a/web3/utils/filters.py +++ b/web3/utils/filters.py @@ -26,11 +26,14 @@ def construct_event_filter_params(event_abi, toBlock=None, address=None): filter_params = {} - - if topics is None: - topic_set = construct_event_topic_set(event_abi, argument_filters) - else: - topic_set = [topics] + construct_event_topic_set(event_abi, argument_filters) + topic_set = construct_event_topic_set(event_abi, argument_filters) + + if topics is not None: + if len(topic_set) > 1: + raise TypeError( + "Merging the topics argument with topics generated " + "from argument_filters is not supported.") + topic_set = topics if len(topic_set) == 1 and is_list_like(topic_set[0]): filter_params['topics'] = topic_set[0]