Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parse tuple arguments from event #2211

Merged
merged 4 commits into from
Jan 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions newsfragments/2211.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed issues with parsing tuples and nested tuples in event logs
25 changes: 20 additions & 5 deletions tests/core/contracts/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
CONTRACT_EMITTER_CODE,
CONTRACT_EMITTER_RUNTIME,
)
from web3._utils.module_testing.emitter_contract_old import (
CONTRACT_EMITTER_ABI_OLD,
CONTRACT_EMITTER_CODE_OLD,
CONTRACT_EMITTER_RUNTIME_OLD,
)
from web3._utils.module_testing.event_contract import (
EVNT_CONTRACT_ABI,
EVNT_CONTRACT_CODE,
Expand Down Expand Up @@ -422,9 +427,20 @@ def EMITTER(EMITTER_CODE,


@pytest.fixture()
def StrictEmitter(w3_strict_abi, EMITTER):
def STRICT_EMITTER():
# Uses an older version of solidity to compile for strict bytes checking.
# See: https://github.com/ethereum/web3.py/issues/2301
return {
'bytecode': CONTRACT_EMITTER_CODE_OLD,
'bytecode_runtime': CONTRACT_EMITTER_RUNTIME_OLD,
'abi': CONTRACT_EMITTER_ABI_OLD,
}


@pytest.fixture()
def StrictEmitter(w3_strict_abi, STRICT_EMITTER):
w3 = w3_strict_abi
return w3.eth.contract(**EMITTER)
return w3.eth.contract(**STRICT_EMITTER)


@pytest.fixture()
Expand All @@ -436,9 +452,7 @@ def strict_emitter(w3_strict_abi,
w3 = w3_strict_abi

wait_for_block(w3)
deploy_txn_hash = StrictEmitter.constructor().transact(
{'from': w3.eth.coinbase, 'gas': 1000000}
)
deploy_txn_hash = StrictEmitter.constructor().transact({'gas': 10000000})
deploy_receipt = wait_for_transaction(w3, deploy_txn_hash)
contract_address = address_conversion_func(deploy_receipt['contractAddress'])

Expand Down Expand Up @@ -975,6 +989,7 @@ class LogTopics:
LogListArgs = _encode_to_topic("LogListArgs(bytes2[],bytes2[])")
LogAddressIndexed = _encode_to_topic("LogAddressIndexed(address,address)")
LogAddressNotIndexed = _encode_to_topic("LogAddressNotIndexed(address,address)")
LogStructArgs = _encode_to_topic("LogStructArgs(uint256,tuple)")


@pytest.fixture()
Expand Down
29 changes: 22 additions & 7 deletions tests/core/contracts/contract_sources/Emitter.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pragma solidity ^0.4.21;
pragma solidity ^0.8.11;


contract Emitter {
Expand All @@ -11,7 +11,6 @@ contract Emitter {
event LogString(string v);
event LogBytes(bytes v);

// Indexed
event LogSingleWithIndex(uint indexed arg0);
event LogSingleAnonymous(uint indexed arg0) anonymous;
event LogDoubleWithIndex(uint arg0, uint indexed arg1);
Expand All @@ -23,6 +22,16 @@ contract Emitter {
event LogAddressIndexed(address indexed arg0, address arg1);
event LogAddressNotIndexed(address arg0, address arg1);

struct NestedTestTuple {
uint c;
}
struct TestTuple {
uint a;
uint b;
NestedTestTuple nested;
}
event LogStructArgs(uint arg0, TestTuple arg1);

enum WhichEvent {
LogAnonymous,
LogNoArguments,
Expand All @@ -41,7 +50,8 @@ contract Emitter {
LogDynamicArgs,
LogListArgs,
LogAddressIndexed,
LogAddressNotIndexed
LogAddressNotIndexed,
LogStructArgs
}

function logNoArgs(WhichEvent which) public {
Expand Down Expand Up @@ -76,11 +86,11 @@ contract Emitter {
else revert("Didn't match any allowable event index");
}

function logDynamicArgs(string arg0, string arg1) public {
function logDynamicArgs(string memory arg0, string memory arg1) public {
emit LogDynamicArgs(arg0, arg1);
}

function logListArgs(bytes2[] arg0, bytes2[] arg1) public {
function logListArgs(bytes2[] memory arg0, bytes2[] memory arg1) public {
emit LogListArgs(arg0, arg1);
}

Expand All @@ -92,11 +102,16 @@ contract Emitter {
emit LogAddressNotIndexed(arg0, arg1);
}

function logBytes(bytes v) public {
function logBytes(bytes memory v) public {
emit LogBytes(v);
}

function logString(string v) public {
function logString(string memory v) public {
emit LogString(v);
}

function logStruct(uint arg0, TestTuple memory arg1) public {
emit LogStructArgs(arg0, arg1);
}
}

106 changes: 106 additions & 0 deletions tests/core/contracts/contract_sources/Emitter_old.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// This older version of the Emitter contract can be use to keep the strict bytes test against it while we work on
// updating the strict bytes checking to be compatible with newer solidity versions.
// # See: https://github.com/ethereum/web3.py/issues/2301

pragma solidity ^0.4.21;


contract Emitter {
event LogAnonymous() anonymous;
event LogNoArguments();
event LogSingleArg(uint arg0);
event LogDoubleArg(uint arg0, uint arg1);
event LogTripleArg(uint arg0, uint arg1, uint arg2);
event LogQuadrupleArg(uint arg0, uint arg1, uint arg2, uint arg3);
event LogString(string v);
event LogBytes(bytes v);

// Indexed
event LogSingleWithIndex(uint indexed arg0);
event LogSingleAnonymous(uint indexed arg0) anonymous;
event LogDoubleWithIndex(uint arg0, uint indexed arg1);
event LogDoubleAnonymous(uint arg0, uint indexed arg1) anonymous;
event LogTripleWithIndex(uint arg0, uint indexed arg1, uint indexed arg2);
event LogQuadrupleWithIndex(uint arg0, uint arg1, uint indexed arg2, uint indexed arg3);
event LogDynamicArgs(string indexed arg0, string arg1);
event LogListArgs(bytes2[] indexed arg0, bytes2[] arg1);
event LogAddressIndexed(address indexed arg0, address arg1);
event LogAddressNotIndexed(address arg0, address arg1);

enum WhichEvent {
LogAnonymous,
LogNoArguments,
LogSingleArg,
LogDoubleArg,
LogTripleArg,
LogQuadrupleArg,
LogSingleAnonymous,
LogSingleWithIndex,
LogDoubleAnonymous,
LogDoubleWithIndex,
LogTripleWithIndex,
LogQuadrupleWithIndex,
LogBytes,
LogString,
LogDynamicArgs,
LogListArgs,
LogAddressIndexed,
LogAddressNotIndexed
}

function logNoArgs(WhichEvent which) public {
if (which == WhichEvent.LogNoArguments) emit LogNoArguments();
else if (which == WhichEvent.LogAnonymous) emit LogAnonymous();
else revert("Didn't match any allowable event index");
}

function logSingle(WhichEvent which, uint arg0) public {
if (which == WhichEvent.LogSingleArg) emit LogSingleArg(arg0);
else if (which == WhichEvent.LogSingleWithIndex) emit LogSingleWithIndex(arg0);
else if (which == WhichEvent.LogSingleAnonymous) emit LogSingleAnonymous(arg0);
else revert("Didn't match any allowable event index");
}

function logDouble(WhichEvent which, uint arg0, uint arg1) public {
if (which == WhichEvent.LogDoubleArg) emit LogDoubleArg(arg0, arg1);
else if (which == WhichEvent.LogDoubleWithIndex) emit LogDoubleWithIndex(arg0, arg1);
else if (which == WhichEvent.LogDoubleAnonymous) emit LogDoubleAnonymous(arg0, arg1);
else revert("Didn't match any allowable event index");
}

function logTriple(WhichEvent which, uint arg0, uint arg1, uint arg2) public {
if (which == WhichEvent.LogTripleArg) emit LogTripleArg(arg0, arg1, arg2);
else if (which == WhichEvent.LogTripleWithIndex) emit LogTripleWithIndex(arg0, arg1, arg2);
else revert("Didn't match any allowable event index");
}

function logQuadruple(WhichEvent which, uint arg0, uint arg1, uint arg2, uint arg3) public {
if (which == WhichEvent.LogQuadrupleArg) emit LogQuadrupleArg(arg0, arg1, arg2, arg3);
else if (which == WhichEvent.LogQuadrupleWithIndex) emit LogQuadrupleWithIndex(arg0, arg1, arg2, arg3);
else revert("Didn't match any allowable event index");
}

function logDynamicArgs(string arg0, string arg1) public {
emit LogDynamicArgs(arg0, arg1);
}

function logListArgs(bytes2[] arg0, bytes2[] arg1) public {
emit LogListArgs(arg0, arg1);
}

function logAddressIndexedArgs(address arg0, address arg1) public {
emit LogAddressIndexed(arg0, arg1);
}

function logAddressNotIndexedArgs(address arg0, address arg1) public {
emit LogAddressNotIndexed(arg0, arg1);
}

function logBytes(bytes v) public {
emit LogBytes(v);
}

function logString(string v) public {
emit LogString(v);
}
}
52 changes: 47 additions & 5 deletions tests/core/contracts/test_extracting_event_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ def Emitter(web3, EMITTER):
@pytest.fixture()
def emitter(web3, Emitter, wait_for_transaction, wait_for_block, address_conversion_func):
wait_for_block(web3)
deploy_txn_hash = Emitter.constructor().transact({'from': web3.eth.coinbase, 'gas': 1000000})
deploy_receipt = web3.eth.wait_for_transaction_receipt(deploy_txn_hash)
deploy_txn_hash = Emitter.constructor().transact({'gas': 10000000})
deploy_receipt = wait_for_transaction(web3, deploy_txn_hash)
fselmo marked this conversation as resolved.
Show resolved Hide resolved
contract_address = address_conversion_func(deploy_receipt['contractAddress'])

bytecode = web3.eth.get_code(contract_address)
Expand Down Expand Up @@ -209,7 +209,6 @@ def test_event_data_extraction_bytes(web3,
emitter,
wait_for_transaction,
emitter_log_topics,
emitter_event_ids,
call_args,
expected_args):
emitter_fn = emitter.functions.logListArgs
Expand Down Expand Up @@ -558,6 +557,22 @@ def test_argument_extraction_strict_bytes_types(w3_strict_abi,
'The event signature did not match the provided ABI',
False,
),
( # nested tuples
'logStruct',
'LogStructArgs',
[1, (2, 3, (4,))],
{'arg0': 1, 'arg1': (2, 3, (4,))},
'The event signature did not match the provided ABI',
True,
),
( # nested tuples
'logStruct',
'LogStructArgs',
[1, (2, 3, (4,))],
{'arg0': 1, 'arg1': (2, 3, (4,))},
'The event signature did not match the provided ABI',
False,
),
)
)
def test_event_rich_log(
Expand All @@ -573,8 +588,13 @@ def test_event_rich_log(
expected_args):

emitter_fn = emitter.functions[contract_fn]
event_id = getattr(emitter_event_ids, event_name)
txn_hash = emitter_fn(event_id, *call_args).transact()
if hasattr(emitter_event_ids, event_name):
event_id = getattr(emitter_event_ids, event_name)
txn_hash = emitter_fn(event_id, *call_args).transact()
else:
# Some tests do not rely on the event_id. Rather than changing this test too much,
fselmo marked this conversation as resolved.
Show resolved Hide resolved
# bypass this here and just call the function with the provided args.
txn_hash = emitter_fn(*call_args).transact()
txn_receipt = wait_for_transaction(web3, txn_hash)

event_instance = emitter.events[event_name]()
Expand Down Expand Up @@ -737,3 +757,25 @@ def test_single_log_processing_with_errors(

with pytest.raises(LogTopicError, match="Expected 1 log topics. Got 0"):
event_instance.processLog(dup_txn_receipt['logs'][0])


def test_get_all_entries_with_nested_tuple_event(web3, emitter):
struct_args_filter = emitter.events.LogStructArgs.createFilter(fromBlock=0)

tx_hash = emitter.functions.logStruct(1, (2, 3, (4, ))).transact({'gas': 100000})
web3.eth.wait_for_transaction_receipt(tx_hash)
txn_receipt = web3.eth.get_transaction_receipt(tx_hash)

entries = struct_args_filter.get_all_entries()

assert entries != []
assert len(entries) == 1

log_entry = entries[0]

assert log_entry.args == {'arg0': 1, 'arg1': (2, 3, (4,))}
assert log_entry.event == 'LogStructArgs'
assert log_entry.blockHash == txn_receipt['blockHash']
assert log_entry.blockNumber == txn_receipt['blockNumber']
assert log_entry.transactionIndex == txn_receipt['transactionIndex']
assert is_same_address(log_entry.address, emitter.address)
4 changes: 2 additions & 2 deletions tests/core/contracts/test_extracting_event_data_old.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ def Emitter(web3, EMITTER):
@pytest.fixture()
def emitter(web3, Emitter, wait_for_transaction, wait_for_block, address_conversion_func):
wait_for_block(web3)
deploy_txn_hash = Emitter.constructor().transact({'from': web3.eth.coinbase, 'gas': 1000000})
deploy_receipt = web3.eth.wait_for_transaction_receipt(deploy_txn_hash)
deploy_txn_hash = Emitter.constructor().transact({'gas': 10000000})
deploy_receipt = wait_for_transaction(web3, deploy_txn_hash)
contract_address = address_conversion_func(deploy_receipt['contractAddress'])

bytecode = web3.eth.get_code(contract_address)
Expand Down
7 changes: 1 addition & 6 deletions tests/core/filtering/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,7 @@ def Emitter(web3, EMITTER):
@pytest.fixture()
def emitter(web3, Emitter, wait_for_transaction, wait_for_block, address_conversion_func):
wait_for_block(web3)
deploy_txn_hash = Emitter.constructor().transact({
'from': web3.eth.coinbase,
'gas': 1000000,
'maxFeePerGas': 10 ** 9,
'maxPriorityFeePerGas': 10 ** 9,
})
deploy_txn_hash = Emitter.constructor().transact({'gas': 10000000})
deploy_receipt = wait_for_transaction(web3, deploy_txn_hash)
contract_address = address_conversion_func(deploy_receipt['contractAddress'])

Expand Down
7 changes: 1 addition & 6 deletions tests/core/filtering/test_contract_data_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,7 @@ def Emitter(web3, EMITTER):
@pytest.fixture(scope="module")
def emitter(web3, Emitter, wait_for_transaction, wait_for_block, address_conversion_func):
wait_for_block(web3)
deploy_txn_hash = Emitter.constructor().transact({
'from': web3.eth.coinbase,
'gas': 1000000,
'maxFeePerGas': 10 ** 9,
'maxPriorityFeePerGas': 10 ** 9,
})
deploy_txn_hash = Emitter.constructor().transact({'gas': 10000000})
deploy_receipt = wait_for_transaction(web3, deploy_txn_hash)
contract_address = address_conversion_func(deploy_receipt['contractAddress'])

Expand Down
2 changes: 1 addition & 1 deletion tests/core/filtering/test_contract_getLogs.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ def test_contract_get_available_events(
"""We can iterate over available contract events"""
contract = emitter
events = list(contract.events)
assert len(events) == 18
assert len(events) == 19


def test_contract_getLogs_all(
Expand Down
7 changes: 1 addition & 6 deletions tests/core/filtering/test_contract_topic_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,7 @@ def Emitter(web3, EMITTER):
@pytest.fixture(scope="module")
def emitter(web3, Emitter, wait_for_transaction, wait_for_block, address_conversion_func):
wait_for_block(web3)
deploy_txn_hash = Emitter.constructor().transact({
'from': web3.eth.coinbase,
'gas': 1000000,
'maxFeePerGas': 10 ** 9,
'maxPriorityFeePerGas': 10 ** 9,
})
deploy_txn_hash = Emitter.constructor().transact({'gas': 10000000})
deploy_receipt = wait_for_transaction(web3, deploy_txn_hash)
contract_address = address_conversion_func(deploy_receipt['contractAddress'])

Expand Down
9 changes: 9 additions & 0 deletions web3/_utils/abi.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,15 @@ def exclude_indexed_event_inputs(event_abi: ABIEvent) -> List[ABIEventParams]:
return [arg for arg in event_abi['inputs'] if arg['indexed'] is False]


def get_normalized_abi_arg_type(abi_arg: ABIEventParams) -> str:
"""
Return the normalized type for the abi argument provided. In order to account for tuple argument
types, this abstraction makes use of `collapse_if_tuple()` to collapse the appropriate component
types within a tuple type, if present.
"""
return collapse_if_tuple(dict(abi_arg))


def filter_by_argument_count(
num_arguments: int, contract_abi: ABI
) -> List[Union[ABIFunction, ABIEvent]]:
Expand Down
Loading