Skip to content

Commit

Permalink
Change ValueError to custom error,
Browse files Browse the repository at this point in the history
split tests to handle different MismatchedABI cases, and split error flag tests out
  • Loading branch information
kclowes committed Jul 2, 2019
1 parent b332a11 commit 00afc6f
Show file tree
Hide file tree
Showing 6 changed files with 156 additions and 73 deletions.
1 change: 1 addition & 0 deletions tests/core/contracts/conftest.py
Expand Up @@ -824,6 +824,7 @@ class LogFunctions:
LogDoubleWithIndex = 9
LogTripleWithIndex = 10
LogQuadrupleWithIndex = 11
LogBytes = 12


@pytest.fixture()
Expand Down
179 changes: 117 additions & 62 deletions tests/core/contracts/test_extracting_event_data.py
Expand Up @@ -14,6 +14,9 @@
STRICT,
WARN,
)
from web3.exceptions import (
LogTopicError,
)


@pytest.fixture()
Expand Down Expand Up @@ -89,6 +92,23 @@ def indexed_event_contract(
return indexed_event_contract


@pytest.fixture()
def dup_txn_receipt(
web3,
indexed_event_contract,
wait_for_transaction,
event_contract):

emitter_fn = indexed_event_contract.functions.logTwoEvents

txn_hash = emitter_fn(12345).transact()
wait_for_transaction(web3, txn_hash)

event_contract_fn = event_contract.functions.logTwoEvents
dup_txn_hash = event_contract_fn(12345).transact()
return wait_for_transaction(web3, dup_txn_hash)


@pytest.mark.parametrize(
'contract_fn,event_name,call_args,expected_args',
(
Expand Down Expand Up @@ -202,7 +222,6 @@ def test_dynamic_length_argument_extraction(web3,
@pytest.mark.parametrize(
'contract_fn,event_name,call_args,expected_args',
(
('logNoArgs', 'LogAnonymous', [], {}),
('logNoArgs', 'LogNoArguments', [], {}),
('logSingle', 'LogSingleArg', [12345], {'arg0': 12345}),
('logSingle', 'LogSingleWithIndex', [12345], {'arg0': 12345}),
Expand Down Expand Up @@ -269,76 +288,112 @@ def test_event_rich_log(
assert is_same_address(rich_log['address'], emitter.address)
assert rich_log['event'] == event_name

rich_logs = event_instance.processReceipt(txn_receipt)
quiet_event = emitter.events['LogBytes']
with pytest.warns(UserWarning,
match='The event signature did not match the provided ABI'):
empty_rich_log = quiet_event().processReceipt(txn_receipt)
assert empty_rich_log == tuple()


def test_nonanonymous_event_abi_mismatch_warning(
web3,
emitter,
emitter_event_ids,
wait_for_transaction):
emitter_fn = emitter.functions.logNoArgs
event_id = getattr(emitter_event_ids, 'LogAnonymous')
txn_hash = emitter_fn(event_id).transact()
txn_receipt = wait_for_transaction(web3, txn_hash)

event_instance = emitter.events.LogAnonymous()

event_instance.processReceipt(txn_receipt)
quiet_event = emitter.events['LogBytes']
empty_rich_log = quiet_event().processReceipt(txn_receipt)
assert empty_rich_log == tuple()
with pytest.warns(UserWarning,
match='Expected non-anonymous event to have 1 or more topics'):
empty_rich_log = quiet_event().processReceipt(txn_receipt)
assert empty_rich_log == tuple()


@pytest.mark.parametrize(
'error_flag',
(
(DISCARD),
(IGNORE),
(STRICT),
(WARN),
('something_else'),
)
)
def test_event_processing_with_caught_errors(
def test_event_processing_with_discard_flag(
web3,
event_contract,
indexed_event_contract,
wait_for_transaction,
error_flag):
dup_txn_receipt,
wait_for_transaction):

emitter_fn = indexed_event_contract.functions.logTwoEvents
event_instance = indexed_event_contract.events.LogSingleWithIndex()
returned_logs = event_instance.processReceipt(dup_txn_receipt, errors=DISCARD)

txn_hash = emitter_fn(12345).transact()
wait_for_transaction(web3, txn_hash)
assert returned_logs == ()

event_contract_fn = event_contract.functions.logTwoEvents
dup_txn_hash = event_contract_fn(12345).transact()
dup_txn_receipt = wait_for_transaction(web3, dup_txn_hash)

def test_event_processing_with_ignore_flag(
web3,
event_contract,
indexed_event_contract,
dup_txn_receipt,
wait_for_transaction):

event_instance = indexed_event_contract.events.LogSingleWithIndex()
returned_logs = event_instance.processReceipt(dup_txn_receipt, errors=IGNORE)
assert len(returned_logs) == 2

if error_flag is DISCARD:
returned_logs = event_instance.processReceipt(dup_txn_receipt, errors=error_flag)
assert returned_logs == ()
elif error_flag is IGNORE:
returned_logs = event_instance.processReceipt(dup_txn_receipt, errors=error_flag)
assert len(returned_logs) == 2
first_log = dict(returned_logs[0])

expected_error = re.compile("Expected 1 log topics. Got 0")
assert expected_error.search(str(first_log['errors'])) is not None

# Check that the returned log is the same as what got sent in, minus the
# added errors field
del first_log['errors']
assert first_log == dup_txn_receipt['logs'][0]
assert is_same_address(first_log['address'], event_contract.address)

# Then, do the same with the other log:
second_log = dict(returned_logs[1])

abi_error = re.compile("The event signature did not match the provided ABI")
assert abi_error.search(str(second_log['errors'])) is not None

# Check that the returned log is the same as what got sent in, minus the
# added errors field
del second_log['errors']
assert second_log == dup_txn_receipt['logs'][1]
assert is_same_address(second_log['address'], event_contract.address)
elif error_flag is WARN:
with pytest.warns(UserWarning, match='Expected 1 log topics. Got 0'):
returned_log = event_instance.processReceipt(dup_txn_receipt, errors=error_flag)
assert len(returned_log) == 0
elif error_flag is STRICT:
with pytest.raises(ValueError, match="Expected 1 log topics. Got 0"):
event_instance.processReceipt(dup_txn_receipt, errors=error_flag)
elif error_flag is 'something_else':
with pytest.raises(AttributeError, match="Error flag must be one of:"):
event_instance.processReceipt(dup_txn_receipt, errors=error_flag)
else:
raise Exception('Unreachable!')
first_log = dict(returned_logs[0])

# Check that the correct error is appended to the log
expected_error = re.compile("Expected 1 log topics. Got 0")
assert expected_error.search(str(first_log['errors'])) is not None

# Check that the returned log is the same as what got sent in,
# except the added errors field
del first_log['errors']
assert first_log == dup_txn_receipt['logs'][0]
assert is_same_address(first_log['address'], event_contract.address)

# Then, do the same with the other log:
second_log = dict(returned_logs[1])

abi_error = re.compile("The event signature did not match the provided ABI")
assert abi_error.search(str(second_log['errors'])) is not None

# Check that the returned log is the same as what got sent in,
# except the added errors field
del second_log['errors']
assert second_log == dup_txn_receipt['logs'][1]
assert is_same_address(second_log['address'], event_contract.address)


def test_event_processing_with_warn_flag(
web3,
indexed_event_contract,
dup_txn_receipt):

event_instance = indexed_event_contract.events.LogSingleWithIndex()

with pytest.warns(UserWarning, match='Expected 1 log topics. Got 0'):
returned_log = event_instance.processReceipt(dup_txn_receipt, errors=WARN)
assert len(returned_log) == 0


def test_event_processing_with_strict_flag(
web3,
indexed_event_contract,
dup_txn_receipt):

event_instance = indexed_event_contract.events.LogSingleWithIndex()

with pytest.raises(LogTopicError, match="Expected 1 log topics. Got 0"):
event_instance.processReceipt(dup_txn_receipt, errors=STRICT)


def test_event_processing_with_invalid_flag(
web3,
indexed_event_contract,
dup_txn_receipt):

event_instance = indexed_event_contract.events.LogSingleWithIndex()

with pytest.raises(AttributeError, match=f"Error flag must be one of: "):
event_instance.processReceipt(dup_txn_receipt, errors='not-a-flag')
10 changes: 6 additions & 4 deletions web3/_utils/events.py
Expand Up @@ -46,6 +46,8 @@
AttributeDict,
)
from web3.exceptions import (
InvalidEventABI,
LogTopicError,
MismatchedABI,
)

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

if len(log_topics) != len(log_topic_types):
raise ValueError("Expected {0} log topics. Got {1}".format(
raise LogTopicError("Expected {0} log topics. Got {1}".format(
len(log_topic_types),
len(log_topics),
))
Expand All @@ -191,9 +193,9 @@ def get_event_data(event_abi, log_entry):
# names and the data argument names.
duplicate_names = set(log_topic_names).intersection(log_data_names)
if duplicate_names:
raise ValueError(
"Invalid Event ABI: The following argument names are duplicated "
"between event inputs: '{0}'".format(', '.join(duplicate_names))
raise InvalidEventABI(
"The following argument names are duplicated "
f"between event inputs: '{', '.join(duplicate_names)}'"
)

decoded_log_data = decode_abi(log_data_types, log_data)
Expand Down
19 changes: 12 additions & 7 deletions web3/contract.py
Expand Up @@ -97,6 +97,8 @@
BadFunctionCallOutput,
BlockNumberOutofRange,
FallbackNotFound,
InvalidEventABI,
LogTopicError,
MismatchedABI,
NoABIEventsFound,
NoABIFound,
Expand Down Expand Up @@ -1000,33 +1002,36 @@ def processReceipt(self, txn_receipt, errors=WARN):
return self._parse_logs(txn_receipt, errors)

def check_for_valid_error_flag(self, errors):
keys = [key.upper() for key in EventLogErrorFlags.__members__.keys()]
try:
errors.name
except AttributeError:
raise AttributeError(f'Error flag must be one of: {keys}')
raise AttributeError(f'Error flag must be one of: {EventLogErrorFlags.flag_options()}')

@to_tuple
def _parse_logs(self, txn_receipt, errors):
for log in txn_receipt['logs']:
try:
rich_log = get_event_data(self.abi, log)
except (MismatchedABI, ValueError) as e:
except (MismatchedABI, LogTopicError, InvalidEventABI, TypeError) as e:
if errors == DISCARD:
continue
elif errors == IGNORE:
new_log = MutableAttributeDict(log)
new_log["errors"] = e
new_log['errors'] = e
rich_log = AttributeDict(new_log)
elif errors == WARN:
warnings.warn(
f"The log with transaction hash: {log.transactionHash} and "
f"logIndex: {log.logIndex} encountered the following error "
f"during processing: {type(e).__name__}({e}). It has been discarded."
f'The log with transaction hash: {log.transactionHash} and '
f'logIndex: {log.logIndex} encountered the following error '
f'during processing: {type(e).__name__}({e}). It has been discarded.'
)
continue
elif errors == STRICT:
raise e
else:
raise AttributeError(
f'Error flag is required. Must be one of: {EventLogErrorFlags.flag_options}'
)
yield rich_log

@combomethod
Expand Down
4 changes: 4 additions & 0 deletions web3/event_log_error_flags.py
Expand Up @@ -7,6 +7,10 @@ class EventLogErrorFlags(Enum):
Strict = 'strict'
Warn = 'warn'

@classmethod
def flag_options(self):
return [key.upper() for key in self.__members__.keys()]


DISCARD = EventLogErrorFlags.Discard
IGNORE = EventLogErrorFlags.Ignore
Expand Down
16 changes: 16 additions & 0 deletions web3/exceptions.py
Expand Up @@ -149,3 +149,19 @@ class InfuraKeyNotFound(Exception):
Raised when there is no Infura Project Id set.
"""
pass


class LogTopicError(ValueError):
# Inherits from ValueError for backwards compatibility
"""
Raised when the number of log topics is mismatched.
"""
pass


class InvalidEventABI(ValueError):
# Inherits from ValueError for backwards compatibility
"""
Raised when the event ABI is invalid.
"""
pass

0 comments on commit 00afc6f

Please sign in to comment.