Skip to content

Commit dc02336

Browse files
authored
Contract Events APIs (#3472)
* Contract APIs for event elements in a contract ABI Contract utils for `get_event_by_name`, `get_event_by_signature`, `get_event_by_selector`, `get_event_by_topic` Contract utils for `find_events_by_name`, `find_events_by_signature`, `find_events_by_selector`, `find_events_by_topic` * Newsfragment for #3472 * Apply changes from #3479 * Allow optional parens on events for sync and async contracts * Tests for overlapping function/event names * Corrections per feedback
1 parent 8310263 commit dc02336

13 files changed

+925
-199
lines changed

docs/web3.contract.rst

Lines changed: 174 additions & 54 deletions
Large diffs are not rendered by default.

newsfragments/3472.feature.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
New contract methods to obtain event elements from a contract ABI using a name, signature, selector, or topic.

tests/core/contracts/conftest.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,28 @@ def non_strict_emitter(
217217
return emitter_contract
218218

219219

220+
@pytest.fixture
221+
def emitter(
222+
w3,
223+
emitter_contract_data,
224+
wait_for_transaction,
225+
wait_for_block,
226+
address_conversion_func,
227+
):
228+
emitter_contract_factory = w3.eth.contract(**emitter_contract_data)
229+
230+
wait_for_block(w3)
231+
deploy_txn_hash = emitter_contract_factory.constructor().transact({"gas": 30029121})
232+
deploy_receipt = wait_for_transaction(w3, deploy_txn_hash)
233+
contract_address = address_conversion_func(deploy_receipt["contractAddress"])
234+
235+
bytecode = w3.eth.get_code(contract_address)
236+
assert bytecode == emitter_contract_factory.bytecode_runtime
237+
_emitter = emitter_contract_factory(address=contract_address)
238+
assert _emitter.address == contract_address
239+
return _emitter
240+
241+
220242
# --- event contract --- #
221243

222244

@@ -760,3 +782,27 @@ def async_call(request):
760782
@pytest.fixture
761783
def async_estimate_gas(request):
762784
return async_partial(async_invoke_contract, api_call_desig="estimate_gas")
785+
786+
787+
@pytest_asyncio.fixture
788+
async def async_emitter(
789+
async_w3,
790+
emitter_contract_data,
791+
async_wait_for_transaction,
792+
async_wait_for_block,
793+
address_conversion_func,
794+
):
795+
async_emitter_contract_factory = async_w3.eth.contract(**emitter_contract_data)
796+
797+
await async_wait_for_block(async_w3)
798+
deploy_txn_hash = await async_emitter_contract_factory.constructor().transact(
799+
{"gas": 10000000}
800+
)
801+
deploy_receipt = await async_wait_for_transaction(async_w3, deploy_txn_hash)
802+
contract_address = address_conversion_func(deploy_receipt["contractAddress"])
803+
804+
bytecode = await async_w3.eth.get_code(contract_address)
805+
assert bytecode == async_emitter_contract_factory.bytecode_runtime
806+
_emitter = async_emitter_contract_factory(address=contract_address)
807+
assert _emitter.address == contract_address
808+
return _emitter

tests/core/contracts/test_contract_ambiguous_functions.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,53 @@ def test_find_or_get_functions_by_type(w3, method, args, repr_func, expected):
133133
assert repr_func(function) == expected
134134

135135

136+
def test_get_function_by_name(w3):
137+
FUNCTION_NAME_OVERLAP_ABI = [
138+
{
139+
"anonymous": False,
140+
"inputs": [],
141+
"name": "increment",
142+
"type": "function",
143+
},
144+
{
145+
"anonymous": False,
146+
"inputs": [],
147+
"name": "incrementCount",
148+
"type": "function",
149+
},
150+
]
151+
contract = w3.eth.contract(abi=FUNCTION_NAME_OVERLAP_ABI)
152+
153+
increment_func = contract.get_function_by_name("increment")
154+
increment_count_func = contract.get_function_by_name("incrementCount")
155+
assert repr(increment_func) == "<Function increment()>"
156+
assert repr(increment_count_func) == "<Function incrementCount()>"
157+
158+
159+
def test_get_event_by_name(w3):
160+
EVENT_NAME_OVERLAP_ABI = [
161+
{
162+
"anonymous": False,
163+
"inputs": [],
164+
"name": "Deposit",
165+
"type": "event",
166+
},
167+
{
168+
"anonymous": False,
169+
"inputs": [],
170+
"name": "Deposited",
171+
"type": "event",
172+
},
173+
]
174+
contract = w3.eth.contract(abi=EVENT_NAME_OVERLAP_ABI)
175+
176+
deposit_event = contract.get_event_by_name("Deposit")
177+
deposited_event = contract.get_event_by_name("Deposited")
178+
179+
assert repr(deposit_event) == "<Event Deposit()>"
180+
assert repr(deposited_event) == "<Event Deposited()>"
181+
182+
136183
@pytest.mark.parametrize(
137184
"method,args,expected_message,expected_error",
138185
(
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import pytest
2+
from typing import (
3+
Any,
4+
Callable,
5+
Sequence,
6+
)
7+
8+
from eth_typing import (
9+
ABIEvent,
10+
)
11+
from eth_utils.toolz import (
12+
compose,
13+
curry,
14+
)
15+
16+
from web3 import (
17+
Web3,
18+
)
19+
from web3.contract.async_contract import (
20+
AsyncContractEvent,
21+
)
22+
from web3.contract.contract import (
23+
Contract,
24+
ContractEvent,
25+
)
26+
27+
map_repr = compose(list, curry(map, repr))
28+
29+
30+
@pytest.mark.parametrize(
31+
"method,args,repr_func,expected",
32+
(
33+
(
34+
"all_events",
35+
(),
36+
map_repr,
37+
[
38+
"<Event LogSingleArg(uint256)>",
39+
"<Event LogSingleWithIndex(uint256)>",
40+
],
41+
),
42+
(
43+
"get_event_by_signature",
44+
("LogSingleArg(uint256)",),
45+
repr,
46+
"<Event LogSingleArg(uint256)>",
47+
),
48+
(
49+
"find_events_by_name",
50+
("LogSingleArg",),
51+
map_repr,
52+
[
53+
"<Event LogSingleArg(uint256)>",
54+
],
55+
),
56+
(
57+
"get_event_by_name",
58+
("LogSingleArg",),
59+
repr,
60+
"<Event LogSingleArg(uint256)>",
61+
),
62+
(
63+
"find_events_by_selector",
64+
(
65+
b"\xf7\x0f\xe6\x89\xe2\x90\xd8\xce+*8\x8a\xc2\x8d\xb3o\xbb\x0e\x16\xa6\xd8\x9ch\x04\xc4a\xf6Z\x1b@\xbb\x15", # noqa: E501
66+
),
67+
map_repr,
68+
[
69+
"<Event LogSingleWithIndex(uint256)>",
70+
],
71+
),
72+
(
73+
"get_event_by_selector",
74+
(
75+
b"\xf7\x0f\xe6\x89\xe2\x90\xd8\xce+*8\x8a\xc2\x8d\xb3o\xbb\x0e\x16\xa6\xd8\x9ch\x04\xc4a\xf6Z\x1b@\xbb\x15", # noqa: E501
76+
),
77+
repr,
78+
"<Event LogSingleWithIndex(uint256)>",
79+
),
80+
(
81+
"get_event_by_selector",
82+
(0xF70FE689E290D8CE2B2A388AC28DB36FBB0E16A6D89C6804C461F65A1B40BB15,),
83+
repr,
84+
"<Event LogSingleWithIndex(uint256)>",
85+
),
86+
(
87+
"get_event_by_selector",
88+
("0xf70fe689e290d8ce2b2a388ac28db36fbb0e16a6d89c6804c461f65a1b40bb15",),
89+
repr,
90+
"<Event LogSingleWithIndex(uint256)>",
91+
),
92+
(
93+
"get_event_by_topic",
94+
("0x56d2ef3c5228bf5d88573621e325a4672ab50e033749a601e4f4a5e1dce905d4",),
95+
repr,
96+
"<Event LogSingleArg(uint256)>",
97+
),
98+
),
99+
)
100+
def test_find_or_get_events_by_type(
101+
w3: "Web3",
102+
method: str,
103+
args: Sequence[str],
104+
repr_func: Callable[[Any], str],
105+
expected: str,
106+
event_contract: "Contract",
107+
) -> None:
108+
contract = w3.eth.contract(abi=event_contract.abi)
109+
contract_event = getattr(contract, method)(*args)
110+
assert repr_func(contract_event) == expected
111+
112+
113+
def test_find_events_by_identifier(w3: "Web3", event_contract: "Contract") -> None:
114+
def callable_check(event_abi: ABIEvent) -> bool:
115+
return event_abi["name"] == "LogSingleArg"
116+
117+
contract_events = event_contract.find_events_by_identifier(
118+
event_contract.abi, w3, event_contract.address, callable_check
119+
)
120+
assert [repr(evt) for evt in contract_events] == ["<Event LogSingleArg(uint256)>"]
121+
122+
123+
def test_get_event_by_identifier(w3: "Web3", event_contract: "Contract") -> None:
124+
contract_event = event_contract.get_event_by_identifier(
125+
[event_contract.events.LogSingleArg], "LogSingleArg"
126+
)
127+
assert repr(contract_event) == "<Event LogSingleArg(uint256)>"
128+
129+
130+
def test_events_iterator(math_contract):
131+
all_events = math_contract.all_events()
132+
events_iter = math_contract.events
133+
134+
for event, expected_event in zip(iter(events_iter), all_events):
135+
assert isinstance(event, ContractEvent)
136+
assert event.name == expected_event.name
137+
138+
139+
def test_async_events_iterator(async_math_contract):
140+
all_events = async_math_contract.all_events()
141+
events_iter = async_math_contract.events
142+
143+
for event, expected_event in zip(iter(events_iter), all_events):
144+
assert isinstance(event, AsyncContractEvent)
145+
assert event.name == expected_event.name

0 commit comments

Comments
 (0)