Skip to content

Commit 6af5dc8

Browse files
author
Stuart Reed
committed
Additional utility functions useful for get_event_data
Build filter params using `construct_event_filter_params` Find ABIs for events and functions with `find_matching_event_abi`, `find_matching_fn_abi` Fix imports
1 parent c924492 commit 6af5dc8

File tree

7 files changed

+220
-112
lines changed

7 files changed

+220
-112
lines changed

docs/web3.contract.rst

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1178,6 +1178,87 @@ Utils
11781178
'_debatingPeriod': 604800,
11791179
'_newCurator': True})
11801180
1181+
.. py:method:: web3.contract.utils.get_function_info(fn_name, abi_codec, contract_abi, fn_abi, args, kwargs)
1182+
1183+
Get function signature and information from a contract ABI given a name, contract
1184+
ABI, and arguments.
1185+
1186+
If the function exists and its signature matches the provided arguments, the
1187+
function ABI, selector and arguments will be returned.
1188+
1189+
Inspect a contract function from an ABI as follows:
1190+
1191+
.. code-block:: python
1192+
1193+
# Contract ABI with a single "counter" function
1194+
contract_abi = [
1195+
{
1196+
"inputs": [],
1197+
"name": "counter",
1198+
"outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}],
1199+
"stateMutability": "view",
1200+
"type": "function",
1201+
},
1202+
]
1203+
1204+
>>> web3.contract.utils.get_function_info("counter", w3.codec, contract_abi)
1205+
({'inputs': [], 'name': 'counter', 'outputs': [{'internalType': 'uint256', 'name': '', 'type': 'uint256'}], 'stateMutability': 'view', 'type': 'function'}, '0x61bc221a', ())
1206+
1207+
.. py:method:: web3.contract.utils.get_event_data(abi_codec, event_abi, log_entry)
1208+
1209+
Get decoded event data from a log entry using an event ABI.
1210+
1211+
For example, to find all WETH "Transfer" events in the latest block:
1212+
1213+
.. code-block:: python
1214+
1215+
WETH_ADDRESS = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'
1216+
WETH_ABI = '[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"guy","type":"address"},{"name":"wad","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"src","type":"address"},{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"wad","type":"uint256"}],"name":"withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"deposit","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"},{"name":"","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":true,"name":"guy","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":true,"name":"dst","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"dst","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Withdrawal","type":"event"}]'
1217+
1218+
# Obtain an event ABI for the "Transfer" event
1219+
event_abi = find_matching_event_abi(WETH_ABI, "Transfer")
1220+
1221+
# Build keccak signatures for event filter_params
1222+
data_filter_params, filter_params = construct_event_filter_params(
1223+
event_abi,
1224+
w3.codec,
1225+
contract_address=WETH_ADDRESS,
1226+
fromBlock="latest",
1227+
)
1228+
1229+
# call JSON-RPC API to get logs
1230+
logs = w3.eth.get_logs(filter_params)
1231+
1232+
# convert raw binary data to Python proxy objects as described by ABI:
1233+
all_event_logs = tuple(get_event_data(w3.codec, event_abi, entry) for entry in logs)
1234+
1235+
print(all_event_logs)
1236+
>>> {'topics': ['0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'], 'address': '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', 'fromBlock': 'latest'}
1237+
logs: [AttributeDict({'address': '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', 'blockHash': HexBytes('0xf72be5db2280ddc56ce01405f606a99593f95efbf7ebb4ab4df6ba860248410c'), 'blockNumber': 19414349, 'data':
1238+
HexBytes('0x00000000000000000000000000000000000000000000000000499d1f0dbe4c00'), 'logIndex': 26, 'removed': False, 'topics': [HexBytes('0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef')
1239+
, HexBytes('0x0000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad'), HexBytes('0x000000000000000000000000b9ed555632c308f0f44489500045a9afba73473c')], 'transactionHash': HexBytes('0xaaf772c057
1240+
20be70081aefefcbf88fbe5740a3090afdc1a651b5e4d0e3d8ab75'), 'transactionIndex': 25})]
1241+
events: (AttributeDict({'args': AttributeDict({'src': '0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD', 'dst': '0xB9ed555632c308f0f44489500045A9AFba73473c', 'wad': 20720430000000000}), 'event': 'Transfer', 'l
1242+
ogIndex': 26, 'transactionIndex': 25, 'transactionHash': HexBytes('0xaaf772c05720be70081aefefcbf88fbe5740a3090afdc1a651b5e4d0e3d8ab75'), 'address': '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', 'blockHash
1243+
': HexBytes('0xf72be5db2280ddc56ce01405f606a99593f95efbf7ebb4ab4df6ba860248410c'), 'blockNumber': 19414349}),)
1244+
1245+
1246+
See also: :class:`web3.contract.ContractEvents` for obtaining event logs from a Contract instance.
1247+
1248+
.. py:method:: web3.contract.utils.find_matching_event_abi(contract_abi, event_name, argument_names)
1249+
1250+
Find the ABI for the event with the provided name. Useful when filtering logs to find event data.
1251+
1252+
Returns ValueError if the event does not exist.
1253+
1254+
.. py:method:: web3.contract.utils.construct_event_filter_params(event_abi, abi_codec, contract_address, argument_filters, topics, fromBlock, toBlock, address)
1255+
1256+
Convert human readable filter inputs to their keccak signatures. Often used when
1257+
filtering events using the JSON-RPC :meth:`web3.eth.get_logs() <web3.eth.Eth.get_logs>` API.
1258+
1259+
See :doc:`filters` for more examples using filter params.
1260+
1261+
11811262
ContractCaller
11821263
--------------
11831264

tests/core/contracts/test_contract_function_info.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ def test_get_function_info_for_math_contract(
4040
w3, math_contract_abi, fn_name, args, kwargs, fn_selector, fn_arguments
4141
):
4242
fn_info_abi, fn_info_selector, fn_info_arguments = get_function_info(
43-
# fn_info = get_function_info(
4443
fn_name,
4544
w3.codec,
4645
contract_abi=math_contract_abi,

tests/core/utilities/test_construct_event_filter_params.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import pytest
22

3-
from web3._utils.filters import (
3+
from web3.contract.utils import (
44
construct_event_filter_params,
55
)
66

web3/_utils/contracts.py

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,8 @@
4343
abi_to_signature,
4444
check_if_arguments_can_be_encoded,
4545
filter_by_argument_count,
46-
filter_by_argument_name,
4746
filter_by_encodability,
4847
filter_by_name,
49-
filter_by_type,
5048
get_abi_input_types,
5149
get_aligned_abi_inputs,
5250
get_fallback_func_abi,
@@ -80,7 +78,6 @@
8078
)
8179
from web3.types import (
8280
ABI,
83-
ABIEvent,
8481
ABIFunction,
8582
BlockIdentifier,
8683
BlockNumber,
@@ -116,31 +113,6 @@ def extract_argument_types(*args: Sequence[Any]) -> str:
116113
return ",".join(collapsed_args)
117114

118115

119-
def find_matching_event_abi(
120-
abi: ABI,
121-
event_name: Optional[str] = None,
122-
argument_names: Optional[Sequence[str]] = None,
123-
) -> ABIEvent:
124-
filters = [
125-
functools.partial(filter_by_type, "event"),
126-
]
127-
128-
if event_name is not None:
129-
filters.append(functools.partial(filter_by_name, event_name))
130-
131-
if argument_names is not None:
132-
filters.append(functools.partial(filter_by_argument_name, argument_names))
133-
134-
event_abi_candidates = pipe(abi, *filters)
135-
136-
if len(event_abi_candidates) == 1:
137-
return event_abi_candidates[0]
138-
elif not event_abi_candidates:
139-
raise ValueError("No matching events found")
140-
else:
141-
raise ValueError("Multiple events found")
142-
143-
144116
def find_matching_fn_abi(
145117
abi: ABI,
146118
abi_codec: ABICodec,

web3/_utils/filters.py

Lines changed: 0 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,9 @@
33
Any,
44
Callable,
55
Collection,
6-
Dict,
76
Iterator,
87
List,
98
Optional,
10-
Sequence,
119
Tuple,
1210
Union,
1311
)
@@ -19,13 +17,11 @@
1917
parse as parse_type_string,
2018
)
2119
from eth_typing import (
22-
ChecksumAddress,
2320
HexStr,
2421
TypeStr,
2522
)
2623
from eth_utils import (
2724
is_hex,
28-
is_list_like,
2925
is_string,
3026
is_text,
3127
)
@@ -43,18 +39,11 @@
4339
from web3._utils.events import (
4440
AsyncEventFilterBuilder,
4541
EventFilterBuilder,
46-
construct_event_data_set,
47-
construct_event_topic_set,
48-
)
49-
from web3._utils.validation import (
50-
validate_address,
5142
)
5243
from web3.exceptions import (
5344
Web3ValidationError,
5445
)
5546
from web3.types import (
56-
ABIEvent,
57-
BlockIdentifier,
5847
FilterParams,
5948
LogReceipt,
6049
RPCEndpoint,
@@ -65,72 +54,6 @@
6554
from web3.eth import Eth # noqa: F401
6655

6756

68-
def construct_event_filter_params(
69-
event_abi: ABIEvent,
70-
abi_codec: ABICodec,
71-
contract_address: Optional[ChecksumAddress] = None,
72-
argument_filters: Optional[Dict[str, Any]] = None,
73-
topics: Optional[Sequence[HexStr]] = None,
74-
fromBlock: Optional[BlockIdentifier] = None,
75-
toBlock: Optional[BlockIdentifier] = None,
76-
address: Optional[ChecksumAddress] = None,
77-
) -> Tuple[List[List[Optional[HexStr]]], FilterParams]:
78-
filter_params: FilterParams = {}
79-
topic_set: Sequence[HexStr] = construct_event_topic_set(
80-
event_abi, abi_codec, argument_filters
81-
)
82-
83-
if topics is not None:
84-
if len(topic_set) > 1:
85-
raise TypeError(
86-
"Merging the topics argument with topics generated "
87-
"from argument_filters is not supported."
88-
)
89-
topic_set = topics
90-
91-
if len(topic_set) == 1 and is_list_like(topic_set[0]):
92-
# type ignored b/c list-like check on line 88
93-
filter_params["topics"] = topic_set[0] # type: ignore
94-
else:
95-
filter_params["topics"] = topic_set
96-
97-
if address and contract_address:
98-
if is_list_like(address):
99-
filter_params["address"] = [address] + [contract_address]
100-
elif is_string(address):
101-
filter_params["address"] = (
102-
[address, contract_address]
103-
if address != contract_address
104-
else [address]
105-
)
106-
else:
107-
raise ValueError(
108-
f"Unsupported type for `address` parameter: {type(address)}"
109-
)
110-
elif address:
111-
filter_params["address"] = address
112-
elif contract_address:
113-
filter_params["address"] = contract_address
114-
115-
if "address" not in filter_params:
116-
pass
117-
elif is_list_like(filter_params["address"]):
118-
for addr in filter_params["address"]:
119-
validate_address(addr)
120-
else:
121-
validate_address(filter_params["address"])
122-
123-
if fromBlock is not None:
124-
filter_params["fromBlock"] = fromBlock
125-
126-
if toBlock is not None:
127-
filter_params["toBlock"] = toBlock
128-
129-
data_filters_set = construct_event_data_set(event_abi, abi_codec, argument_filters)
130-
131-
return data_filters_set, filter_params
132-
133-
13457
class BaseFilter:
13558
callbacks: List[Callable[..., Any]] = None
13659
stopped = False

web3/contract/base_contract.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@
4747
from web3._utils.contracts import (
4848
decode_transaction_data,
4949
encode_abi,
50-
find_matching_event_abi,
5150
find_matching_fn_abi,
5251
get_function_info,
5352
prepare_transaction,
@@ -67,9 +66,6 @@
6766
EventFilterBuilder,
6867
is_dynamic_sized_type,
6968
)
70-
from web3._utils.filters import (
71-
construct_event_filter_params,
72-
)
7369
from web3._utils.function_identifiers import (
7470
FallbackFn,
7571
ReceiveFn,
@@ -78,6 +74,8 @@
7874
BASE_RETURN_NORMALIZERS,
7975
)
8076
from web3.contract.utils import (
77+
construct_event_filter_params,
78+
find_matching_event_abi,
8179
get_event_data,
8280
)
8381
from web3.datastructures import (

0 commit comments

Comments
 (0)