Skip to content

Commit 00afc6f

Browse files
committed
Change ValueError to custom error,
split tests to handle different MismatchedABI cases, and split error flag tests out
1 parent b332a11 commit 00afc6f

File tree

6 files changed

+156
-73
lines changed

6 files changed

+156
-73
lines changed

tests/core/contracts/conftest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -824,6 +824,7 @@ class LogFunctions:
824824
LogDoubleWithIndex = 9
825825
LogTripleWithIndex = 10
826826
LogQuadrupleWithIndex = 11
827+
LogBytes = 12
827828

828829

829830
@pytest.fixture()

tests/core/contracts/test_extracting_event_data.py

Lines changed: 117 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
STRICT,
1515
WARN,
1616
)
17+
from web3.exceptions import (
18+
LogTopicError,
19+
)
1720

1821

1922
@pytest.fixture()
@@ -89,6 +92,23 @@ def indexed_event_contract(
8992
return indexed_event_contract
9093

9194

95+
@pytest.fixture()
96+
def dup_txn_receipt(
97+
web3,
98+
indexed_event_contract,
99+
wait_for_transaction,
100+
event_contract):
101+
102+
emitter_fn = indexed_event_contract.functions.logTwoEvents
103+
104+
txn_hash = emitter_fn(12345).transact()
105+
wait_for_transaction(web3, txn_hash)
106+
107+
event_contract_fn = event_contract.functions.logTwoEvents
108+
dup_txn_hash = event_contract_fn(12345).transact()
109+
return wait_for_transaction(web3, dup_txn_hash)
110+
111+
92112
@pytest.mark.parametrize(
93113
'contract_fn,event_name,call_args,expected_args',
94114
(
@@ -202,7 +222,6 @@ def test_dynamic_length_argument_extraction(web3,
202222
@pytest.mark.parametrize(
203223
'contract_fn,event_name,call_args,expected_args',
204224
(
205-
('logNoArgs', 'LogAnonymous', [], {}),
206225
('logNoArgs', 'LogNoArguments', [], {}),
207226
('logSingle', 'LogSingleArg', [12345], {'arg0': 12345}),
208227
('logSingle', 'LogSingleWithIndex', [12345], {'arg0': 12345}),
@@ -269,76 +288,112 @@ def test_event_rich_log(
269288
assert is_same_address(rich_log['address'], emitter.address)
270289
assert rich_log['event'] == event_name
271290

291+
rich_logs = event_instance.processReceipt(txn_receipt)
292+
quiet_event = emitter.events['LogBytes']
293+
with pytest.warns(UserWarning,
294+
match='The event signature did not match the provided ABI'):
295+
empty_rich_log = quiet_event().processReceipt(txn_receipt)
296+
assert empty_rich_log == tuple()
297+
298+
299+
def test_nonanonymous_event_abi_mismatch_warning(
300+
web3,
301+
emitter,
302+
emitter_event_ids,
303+
wait_for_transaction):
304+
emitter_fn = emitter.functions.logNoArgs
305+
event_id = getattr(emitter_event_ids, 'LogAnonymous')
306+
txn_hash = emitter_fn(event_id).transact()
307+
txn_receipt = wait_for_transaction(web3, txn_hash)
308+
309+
event_instance = emitter.events.LogAnonymous()
310+
311+
event_instance.processReceipt(txn_receipt)
272312
quiet_event = emitter.events['LogBytes']
273-
empty_rich_log = quiet_event().processReceipt(txn_receipt)
274-
assert empty_rich_log == tuple()
313+
with pytest.warns(UserWarning,
314+
match='Expected non-anonymous event to have 1 or more topics'):
315+
empty_rich_log = quiet_event().processReceipt(txn_receipt)
316+
assert empty_rich_log == tuple()
275317

276318

277-
@pytest.mark.parametrize(
278-
'error_flag',
279-
(
280-
(DISCARD),
281-
(IGNORE),
282-
(STRICT),
283-
(WARN),
284-
('something_else'),
285-
)
286-
)
287-
def test_event_processing_with_caught_errors(
319+
def test_event_processing_with_discard_flag(
288320
web3,
289321
event_contract,
290322
indexed_event_contract,
291-
wait_for_transaction,
292-
error_flag):
323+
dup_txn_receipt,
324+
wait_for_transaction):
293325

294-
emitter_fn = indexed_event_contract.functions.logTwoEvents
326+
event_instance = indexed_event_contract.events.LogSingleWithIndex()
327+
returned_logs = event_instance.processReceipt(dup_txn_receipt, errors=DISCARD)
295328

296-
txn_hash = emitter_fn(12345).transact()
297-
wait_for_transaction(web3, txn_hash)
329+
assert returned_logs == ()
298330

299-
event_contract_fn = event_contract.functions.logTwoEvents
300-
dup_txn_hash = event_contract_fn(12345).transact()
301-
dup_txn_receipt = wait_for_transaction(web3, dup_txn_hash)
331+
332+
def test_event_processing_with_ignore_flag(
333+
web3,
334+
event_contract,
335+
indexed_event_contract,
336+
dup_txn_receipt,
337+
wait_for_transaction):
302338

303339
event_instance = indexed_event_contract.events.LogSingleWithIndex()
340+
returned_logs = event_instance.processReceipt(dup_txn_receipt, errors=IGNORE)
341+
assert len(returned_logs) == 2
304342

305-
if error_flag is DISCARD:
306-
returned_logs = event_instance.processReceipt(dup_txn_receipt, errors=error_flag)
307-
assert returned_logs == ()
308-
elif error_flag is IGNORE:
309-
returned_logs = event_instance.processReceipt(dup_txn_receipt, errors=error_flag)
310-
assert len(returned_logs) == 2
311-
first_log = dict(returned_logs[0])
312-
313-
expected_error = re.compile("Expected 1 log topics. Got 0")
314-
assert expected_error.search(str(first_log['errors'])) is not None
315-
316-
# Check that the returned log is the same as what got sent in, minus the
317-
# added errors field
318-
del first_log['errors']
319-
assert first_log == dup_txn_receipt['logs'][0]
320-
assert is_same_address(first_log['address'], event_contract.address)
321-
322-
# Then, do the same with the other log:
323-
second_log = dict(returned_logs[1])
324-
325-
abi_error = re.compile("The event signature did not match the provided ABI")
326-
assert abi_error.search(str(second_log['errors'])) is not None
327-
328-
# Check that the returned log is the same as what got sent in, minus the
329-
# added errors field
330-
del second_log['errors']
331-
assert second_log == dup_txn_receipt['logs'][1]
332-
assert is_same_address(second_log['address'], event_contract.address)
333-
elif error_flag is WARN:
334-
with pytest.warns(UserWarning, match='Expected 1 log topics. Got 0'):
335-
returned_log = event_instance.processReceipt(dup_txn_receipt, errors=error_flag)
336-
assert len(returned_log) == 0
337-
elif error_flag is STRICT:
338-
with pytest.raises(ValueError, match="Expected 1 log topics. Got 0"):
339-
event_instance.processReceipt(dup_txn_receipt, errors=error_flag)
340-
elif error_flag is 'something_else':
341-
with pytest.raises(AttributeError, match="Error flag must be one of:"):
342-
event_instance.processReceipt(dup_txn_receipt, errors=error_flag)
343-
else:
344-
raise Exception('Unreachable!')
343+
first_log = dict(returned_logs[0])
344+
345+
# Check that the correct error is appended to the log
346+
expected_error = re.compile("Expected 1 log topics. Got 0")
347+
assert expected_error.search(str(first_log['errors'])) is not None
348+
349+
# Check that the returned log is the same as what got sent in,
350+
# except the added errors field
351+
del first_log['errors']
352+
assert first_log == dup_txn_receipt['logs'][0]
353+
assert is_same_address(first_log['address'], event_contract.address)
354+
355+
# Then, do the same with the other log:
356+
second_log = dict(returned_logs[1])
357+
358+
abi_error = re.compile("The event signature did not match the provided ABI")
359+
assert abi_error.search(str(second_log['errors'])) is not None
360+
361+
# Check that the returned log is the same as what got sent in,
362+
# except the added errors field
363+
del second_log['errors']
364+
assert second_log == dup_txn_receipt['logs'][1]
365+
assert is_same_address(second_log['address'], event_contract.address)
366+
367+
368+
def test_event_processing_with_warn_flag(
369+
web3,
370+
indexed_event_contract,
371+
dup_txn_receipt):
372+
373+
event_instance = indexed_event_contract.events.LogSingleWithIndex()
374+
375+
with pytest.warns(UserWarning, match='Expected 1 log topics. Got 0'):
376+
returned_log = event_instance.processReceipt(dup_txn_receipt, errors=WARN)
377+
assert len(returned_log) == 0
378+
379+
380+
def test_event_processing_with_strict_flag(
381+
web3,
382+
indexed_event_contract,
383+
dup_txn_receipt):
384+
385+
event_instance = indexed_event_contract.events.LogSingleWithIndex()
386+
387+
with pytest.raises(LogTopicError, match="Expected 1 log topics. Got 0"):
388+
event_instance.processReceipt(dup_txn_receipt, errors=STRICT)
389+
390+
391+
def test_event_processing_with_invalid_flag(
392+
web3,
393+
indexed_event_contract,
394+
dup_txn_receipt):
395+
396+
event_instance = indexed_event_contract.events.LogSingleWithIndex()
397+
398+
with pytest.raises(AttributeError, match=f"Error flag must be one of: "):
399+
event_instance.processReceipt(dup_txn_receipt, errors='not-a-flag')

web3/_utils/events.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@
4646
AttributeDict,
4747
)
4848
from web3.exceptions import (
49+
InvalidEventABI,
50+
LogTopicError,
4951
MismatchedABI,
5052
)
5153

@@ -176,7 +178,7 @@ def get_event_data(event_abi, log_entry):
176178
log_topic_names = get_abi_input_names({'inputs': log_topics_abi})
177179

178180
if len(log_topics) != len(log_topic_types):
179-
raise ValueError("Expected {0} log topics. Got {1}".format(
181+
raise LogTopicError("Expected {0} log topics. Got {1}".format(
180182
len(log_topic_types),
181183
len(log_topics),
182184
))
@@ -191,9 +193,9 @@ def get_event_data(event_abi, log_entry):
191193
# names and the data argument names.
192194
duplicate_names = set(log_topic_names).intersection(log_data_names)
193195
if duplicate_names:
194-
raise ValueError(
195-
"Invalid Event ABI: The following argument names are duplicated "
196-
"between event inputs: '{0}'".format(', '.join(duplicate_names))
196+
raise InvalidEventABI(
197+
"The following argument names are duplicated "
198+
f"between event inputs: '{', '.join(duplicate_names)}'"
197199
)
198200

199201
decoded_log_data = decode_abi(log_data_types, log_data)

web3/contract.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@
9797
BadFunctionCallOutput,
9898
BlockNumberOutofRange,
9999
FallbackNotFound,
100+
InvalidEventABI,
101+
LogTopicError,
100102
MismatchedABI,
101103
NoABIEventsFound,
102104
NoABIFound,
@@ -1000,33 +1002,36 @@ def processReceipt(self, txn_receipt, errors=WARN):
10001002
return self._parse_logs(txn_receipt, errors)
10011003

10021004
def check_for_valid_error_flag(self, errors):
1003-
keys = [key.upper() for key in EventLogErrorFlags.__members__.keys()]
10041005
try:
10051006
errors.name
10061007
except AttributeError:
1007-
raise AttributeError(f'Error flag must be one of: {keys}')
1008+
raise AttributeError(f'Error flag must be one of: {EventLogErrorFlags.flag_options()}')
10081009

10091010
@to_tuple
10101011
def _parse_logs(self, txn_receipt, errors):
10111012
for log in txn_receipt['logs']:
10121013
try:
10131014
rich_log = get_event_data(self.abi, log)
1014-
except (MismatchedABI, ValueError) as e:
1015+
except (MismatchedABI, LogTopicError, InvalidEventABI, TypeError) as e:
10151016
if errors == DISCARD:
10161017
continue
10171018
elif errors == IGNORE:
10181019
new_log = MutableAttributeDict(log)
1019-
new_log["errors"] = e
1020+
new_log['errors'] = e
10201021
rich_log = AttributeDict(new_log)
10211022
elif errors == WARN:
10221023
warnings.warn(
1023-
f"The log with transaction hash: {log.transactionHash} and "
1024-
f"logIndex: {log.logIndex} encountered the following error "
1025-
f"during processing: {type(e).__name__}({e}). It has been discarded."
1024+
f'The log with transaction hash: {log.transactionHash} and '
1025+
f'logIndex: {log.logIndex} encountered the following error '
1026+
f'during processing: {type(e).__name__}({e}). It has been discarded.'
10261027
)
10271028
continue
10281029
elif errors == STRICT:
10291030
raise e
1031+
else:
1032+
raise AttributeError(
1033+
f'Error flag is required. Must be one of: {EventLogErrorFlags.flag_options}'
1034+
)
10301035
yield rich_log
10311036

10321037
@combomethod

web3/event_log_error_flags.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ class EventLogErrorFlags(Enum):
77
Strict = 'strict'
88
Warn = 'warn'
99

10+
@classmethod
11+
def flag_options(self):
12+
return [key.upper() for key in self.__members__.keys()]
13+
1014

1115
DISCARD = EventLogErrorFlags.Discard
1216
IGNORE = EventLogErrorFlags.Ignore

web3/exceptions.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,3 +149,19 @@ class InfuraKeyNotFound(Exception):
149149
Raised when there is no Infura Project Id set.
150150
"""
151151
pass
152+
153+
154+
class LogTopicError(ValueError):
155+
# Inherits from ValueError for backwards compatibility
156+
"""
157+
Raised when the number of log topics is mismatched.
158+
"""
159+
pass
160+
161+
162+
class InvalidEventABI(ValueError):
163+
# Inherits from ValueError for backwards compatibility
164+
"""
165+
Raised when the event ABI is invalid.
166+
"""
167+
pass

0 commit comments

Comments
 (0)