web3 7.0.0b2__py3-none-any.whl → 7.7.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. ens/__init__.py +13 -2
  2. ens/_normalization.py +4 -4
  3. ens/async_ens.py +27 -15
  4. ens/base_ens.py +3 -1
  5. ens/contract_data.py +2 -2
  6. ens/ens.py +10 -7
  7. ens/exceptions.py +16 -29
  8. ens/specs/nf.json +1 -1
  9. ens/specs/normalization_spec.json +1 -1
  10. ens/utils.py +24 -32
  11. web3/__init__.py +23 -12
  12. web3/_utils/abi.py +157 -263
  13. web3/_utils/async_transactions.py +34 -20
  14. web3/_utils/batching.py +217 -0
  15. web3/_utils/blocks.py +6 -2
  16. web3/_utils/caching/__init__.py +12 -0
  17. web3/_utils/caching/caching_utils.py +433 -0
  18. web3/_utils/caching/request_caching_validation.py +287 -0
  19. web3/_utils/compat/__init__.py +2 -3
  20. web3/_utils/contract_sources/compile_contracts.py +1 -1
  21. web3/_utils/contract_sources/contract_data/ambiguous_function_contract.py +42 -0
  22. web3/_utils/contract_sources/contract_data/arrays_contract.py +3 -3
  23. web3/_utils/contract_sources/contract_data/bytes_contracts.py +5 -5
  24. web3/_utils/contract_sources/contract_data/constructor_contracts.py +7 -7
  25. web3/_utils/contract_sources/contract_data/contract_caller_tester.py +3 -3
  26. web3/_utils/contract_sources/contract_data/emitter_contract.py +3 -3
  27. web3/_utils/contract_sources/contract_data/event_contracts.py +50 -5
  28. web3/_utils/contract_sources/contract_data/extended_resolver.py +3 -3
  29. web3/_utils/contract_sources/contract_data/fallback_function_contract.py +3 -3
  30. web3/_utils/contract_sources/contract_data/function_name_tester_contract.py +3 -3
  31. web3/_utils/contract_sources/contract_data/math_contract.py +3 -3
  32. web3/_utils/contract_sources/contract_data/offchain_lookup.py +3 -3
  33. web3/_utils/contract_sources/contract_data/offchain_resolver.py +3 -3
  34. web3/_utils/contract_sources/contract_data/panic_errors_contract.py +3 -3
  35. web3/_utils/contract_sources/contract_data/payable_tester.py +3 -3
  36. web3/_utils/contract_sources/contract_data/receive_function_contracts.py +5 -5
  37. web3/_utils/contract_sources/contract_data/reflector_contracts.py +3 -3
  38. web3/_utils/contract_sources/contract_data/revert_contract.py +3 -3
  39. web3/_utils/contract_sources/contract_data/simple_resolver.py +3 -3
  40. web3/_utils/contract_sources/contract_data/storage_contract.py +3 -3
  41. web3/_utils/contract_sources/contract_data/string_contract.py +3 -3
  42. web3/_utils/contract_sources/contract_data/tuple_contracts.py +5 -5
  43. web3/_utils/contracts.py +172 -220
  44. web3/_utils/datatypes.py +5 -1
  45. web3/_utils/decorators.py +6 -1
  46. web3/_utils/empty.py +1 -1
  47. web3/_utils/encoding.py +16 -12
  48. web3/_utils/error_formatters_utils.py +5 -3
  49. web3/_utils/events.py +78 -72
  50. web3/_utils/fee_utils.py +1 -3
  51. web3/_utils/filters.py +24 -22
  52. web3/_utils/formatters.py +2 -2
  53. web3/_utils/http.py +8 -2
  54. web3/_utils/http_session_manager.py +314 -0
  55. web3/_utils/math.py +14 -15
  56. web3/_utils/method_formatters.py +161 -34
  57. web3/_utils/module.py +2 -1
  58. web3/_utils/module_testing/__init__.py +3 -2
  59. web3/_utils/module_testing/eth_module.py +736 -583
  60. web3/_utils/module_testing/go_ethereum_debug_module.py +128 -0
  61. web3/_utils/module_testing/module_testing_utils.py +81 -24
  62. web3/_utils/module_testing/persistent_connection_provider.py +702 -220
  63. web3/_utils/module_testing/utils.py +114 -33
  64. web3/_utils/module_testing/web3_module.py +438 -17
  65. web3/_utils/normalizers.py +13 -11
  66. web3/_utils/rpc_abi.py +10 -22
  67. web3/_utils/threads.py +8 -7
  68. web3/_utils/transactions.py +32 -25
  69. web3/_utils/type_conversion.py +5 -1
  70. web3/_utils/validation.py +20 -17
  71. web3/beacon/__init__.py +5 -0
  72. web3/beacon/api_endpoints.py +3 -0
  73. web3/beacon/async_beacon.py +29 -6
  74. web3/beacon/beacon.py +24 -6
  75. web3/contract/__init__.py +7 -0
  76. web3/contract/async_contract.py +285 -82
  77. web3/contract/base_contract.py +556 -258
  78. web3/contract/contract.py +295 -84
  79. web3/contract/utils.py +251 -55
  80. web3/datastructures.py +49 -34
  81. web3/eth/__init__.py +7 -0
  82. web3/eth/async_eth.py +89 -69
  83. web3/eth/base_eth.py +7 -3
  84. web3/eth/eth.py +43 -66
  85. web3/exceptions.py +158 -83
  86. web3/gas_strategies/time_based.py +8 -6
  87. web3/geth.py +53 -184
  88. web3/main.py +77 -17
  89. web3/manager.py +362 -95
  90. web3/method.py +43 -15
  91. web3/middleware/__init__.py +17 -0
  92. web3/middleware/attrdict.py +12 -22
  93. web3/middleware/base.py +55 -2
  94. web3/middleware/filter.py +45 -23
  95. web3/middleware/formatting.py +6 -3
  96. web3/middleware/names.py +4 -1
  97. web3/middleware/signing.py +15 -6
  98. web3/middleware/stalecheck.py +2 -1
  99. web3/module.py +61 -25
  100. web3/providers/__init__.py +21 -0
  101. web3/providers/async_base.py +87 -32
  102. web3/providers/base.py +77 -32
  103. web3/providers/eth_tester/__init__.py +5 -0
  104. web3/providers/eth_tester/defaults.py +2 -55
  105. web3/providers/eth_tester/main.py +41 -15
  106. web3/providers/eth_tester/middleware.py +16 -17
  107. web3/providers/ipc.py +41 -17
  108. web3/providers/legacy_websocket.py +26 -1
  109. web3/providers/persistent/__init__.py +7 -0
  110. web3/providers/persistent/async_ipc.py +61 -121
  111. web3/providers/persistent/persistent.py +323 -16
  112. web3/providers/persistent/persistent_connection.py +54 -5
  113. web3/providers/persistent/request_processor.py +136 -56
  114. web3/providers/persistent/subscription_container.py +56 -0
  115. web3/providers/persistent/subscription_manager.py +233 -0
  116. web3/providers/persistent/websocket.py +29 -92
  117. web3/providers/rpc/__init__.py +5 -0
  118. web3/providers/rpc/async_rpc.py +73 -18
  119. web3/providers/rpc/rpc.py +73 -30
  120. web3/providers/rpc/utils.py +1 -13
  121. web3/scripts/install_pre_releases.py +33 -0
  122. web3/scripts/parse_pygeth_version.py +16 -0
  123. web3/testing.py +4 -4
  124. web3/tracing.py +9 -5
  125. web3/types.py +141 -74
  126. web3/utils/__init__.py +64 -5
  127. web3/utils/abi.py +790 -10
  128. web3/utils/address.py +8 -0
  129. web3/utils/async_exception_handling.py +20 -11
  130. web3/utils/caching.py +34 -4
  131. web3/utils/exception_handling.py +9 -12
  132. web3/utils/subscriptions.py +285 -0
  133. {web3-7.0.0b2.dist-info → web3-7.7.0.dist-info}/LICENSE +1 -1
  134. web3-7.7.0.dist-info/METADATA +130 -0
  135. web3-7.7.0.dist-info/RECORD +171 -0
  136. {web3-7.0.0b2.dist-info → web3-7.7.0.dist-info}/WHEEL +1 -1
  137. web3/_utils/caching.py +0 -155
  138. web3/_utils/contract_sources/contract_data/address_reflector.py +0 -29
  139. web3/_utils/module_testing/go_ethereum_personal_module.py +0 -300
  140. web3/_utils/request.py +0 -265
  141. web3-7.0.0b2.dist-info/METADATA +0 -106
  142. web3-7.0.0b2.dist-info/RECORD +0 -163
  143. /web3/_utils/{function_identifiers.py → abi_element_identifiers.py} +0 -0
  144. {web3-7.0.0b2.dist-info → web3-7.7.0.dist-info}/top_level.txt +0 -0
@@ -16,18 +16,30 @@ from typing import (
16
16
  )
17
17
  import warnings
18
18
 
19
+ from eth_abi.exceptions import (
20
+ InsufficientDataBytes,
21
+ )
19
22
  from eth_typing import (
23
+ ABI,
24
+ ABIElement,
25
+ ABIEvent,
26
+ ABIFunction,
20
27
  Address,
21
28
  ChecksumAddress,
22
29
  HexStr,
23
30
  )
24
31
  from eth_utils import (
32
+ abi_to_signature,
25
33
  add_0x_prefix,
26
34
  combomethod,
27
35
  encode_hex,
36
+ filter_abi_by_type,
28
37
  function_abi_to_4byte_selector,
38
+ get_normalized_abi_inputs,
29
39
  is_list_like,
30
40
  is_text,
41
+ keccak,
42
+ to_bytes,
31
43
  to_tuple,
32
44
  )
33
45
  from hexbytes import (
@@ -35,21 +47,20 @@ from hexbytes import (
35
47
  )
36
48
 
37
49
  from web3._utils.abi import (
38
- abi_to_signature,
39
- check_if_arguments_can_be_encoded,
40
50
  fallback_func_abi_exists,
41
- filter_by_type,
42
- get_constructor_abi,
51
+ find_constructor_abi_element_by_type,
52
+ get_abi_element_signature,
53
+ get_name_from_abi_element_identifier,
43
54
  is_array_type,
44
- merge_args_and_kwargs,
45
55
  receive_func_abi_exists,
46
56
  )
57
+ from web3._utils.abi_element_identifiers import (
58
+ FallbackFn,
59
+ ReceiveFn,
60
+ )
47
61
  from web3._utils.contracts import (
48
62
  decode_transaction_data,
49
63
  encode_abi,
50
- find_matching_event_abi,
51
- find_matching_fn_abi,
52
- get_function_info,
53
64
  prepare_transaction,
54
65
  )
55
66
  from web3._utils.datatypes import (
@@ -59,6 +70,7 @@ from web3._utils.empty import (
59
70
  empty,
60
71
  )
61
72
  from web3._utils.encoding import (
73
+ hexstr_if_str,
62
74
  to_4byte_hex,
63
75
  to_hex,
64
76
  )
@@ -71,10 +83,6 @@ from web3._utils.events import (
71
83
  from web3._utils.filters import (
72
84
  construct_event_filter_params,
73
85
  )
74
- from web3._utils.function_identifiers import (
75
- FallbackFn,
76
- ReceiveFn,
77
- )
78
86
  from web3._utils.normalizers import (
79
87
  BASE_RETURN_NORMALIZERS,
80
88
  )
@@ -83,16 +91,19 @@ from web3.datastructures import (
83
91
  MutableAttributeDict,
84
92
  )
85
93
  from web3.exceptions import (
86
- ABIEventFunctionNotFound,
94
+ ABIEventNotFound,
95
+ ABIFallbackNotFound,
87
96
  ABIFunctionNotFound,
88
- FallbackNotFound,
97
+ ABIReceiveNotFound,
89
98
  InvalidEventABI,
90
99
  LogTopicError,
91
100
  MismatchedABI,
92
- NoABIEventsFound,
93
101
  NoABIFound,
94
102
  NoABIFunctionsFound,
103
+ Web3AttributeError,
104
+ Web3TypeError,
95
105
  Web3ValidationError,
106
+ Web3ValueError,
96
107
  )
97
108
  from web3.logs import (
98
109
  DISCARD,
@@ -102,17 +113,21 @@ from web3.logs import (
102
113
  EventLogErrorFlags,
103
114
  )
104
115
  from web3.types import (
105
- ABI,
106
- ABIEvent,
107
- ABIFunction,
116
+ ABIElementIdentifier,
108
117
  BlockIdentifier,
109
118
  EventData,
110
119
  FilterParams,
111
- FunctionIdentifier,
120
+ LogReceipt,
112
121
  TContractFn,
113
122
  TxParams,
114
123
  TxReceipt,
115
124
  )
125
+ from web3.utils.abi import (
126
+ _get_any_abi_signature_with_name,
127
+ check_if_arguments_can_be_encoded,
128
+ get_abi_element,
129
+ get_abi_element_info,
130
+ )
116
131
 
117
132
  if TYPE_CHECKING:
118
133
  from web3 import ( # noqa: F401
@@ -120,12 +135,19 @@ if TYPE_CHECKING:
120
135
  Web3,
121
136
  )
122
137
 
123
- from .async_contract import AsyncContractFunction # noqa: F401
124
- from .contract import ContractFunction # noqa: F401
138
+ from .async_contract import ( # noqa: F401
139
+ AsyncContractEvent,
140
+ AsyncContractFunction,
141
+ )
142
+ from .contract import ( # noqa: F401
143
+ ContractEvent,
144
+ ContractFunction,
145
+ )
125
146
 
126
147
 
127
148
  class BaseContractEvent:
128
- """Base class for contract events
149
+ """
150
+ Base class for contract events
129
151
 
130
152
  An event accessed via the api `contract.events.myEvents(*args, **kwargs)`
131
153
  is a subclass of this class.
@@ -133,29 +155,70 @@ class BaseContractEvent:
133
155
 
134
156
  address: ChecksumAddress = None
135
157
  event_name: str = None
158
+ name: str = None
159
+ abi_element_identifier: ABIElementIdentifier = None
160
+ signature: str = None
136
161
  w3: Union["Web3", "AsyncWeb3"] = None
137
162
  contract_abi: ABI = None
138
163
  abi: ABIEvent = None
164
+ argument_names: Tuple[str, ...] = tuple()
165
+ argument_types: Tuple[str, ...] = tuple()
166
+ args: Any = None
167
+ kwargs: Any = None
168
+ _topic: HexStr = None
139
169
 
140
- def __init__(self, *argument_names: Tuple[str]) -> None:
141
- if argument_names is None:
142
- # https://github.com/python/mypy/issues/6283
143
- self.argument_names = tuple() # type: ignore
144
- else:
170
+ def __init__(self, *argument_names: str, abi: Optional[ABIEvent] = None) -> None:
171
+ self.abi_element_identifier = type(self).__name__
172
+ self.name = get_name_from_abi_element_identifier(self.abi_element_identifier)
173
+ self.event_name = self.name
174
+
175
+ if abi:
176
+ self.abi = abi
177
+
178
+ self.signature = abi_to_signature(self.abi)
179
+
180
+ if argument_names:
145
181
  self.argument_names = argument_names
146
182
 
147
- self.abi = self._get_event_abi()
183
+ event_inputs = self.abi.get("inputs", [])
184
+ self.argument_names = tuple([input.get("name", None) for input in event_inputs])
185
+ self.argument_types = tuple([input["type"] for input in event_inputs])
148
186
 
149
- @classmethod
187
+ def __repr__(self) -> str:
188
+ if self.abi:
189
+ return f"<Event {abi_to_signature(self.abi)}>"
190
+ return f"<Event {get_abi_element_signature(self.abi_element_identifier)}>"
191
+
192
+ @property
193
+ def topic(self) -> HexStr:
194
+ if self._topic is None:
195
+ self._topic = encode_hex(keccak(text=self.signature))
196
+ return self._topic
197
+
198
+ @combomethod
150
199
  def _get_event_abi(cls) -> ABIEvent:
151
- return find_matching_event_abi(cls.contract_abi, event_name=cls.event_name)
200
+ if cls.abi:
201
+ return cls.abi
202
+
203
+ return cast(
204
+ ABIEvent,
205
+ get_abi_element(
206
+ filter_abi_by_type("event", cls.contract_abi),
207
+ cls.abi_element_identifier,
208
+ abi_codec=cls.w3.codec,
209
+ ),
210
+ )
211
+
212
+ def _set_event_info(self) -> None:
213
+ self.abi = self._get_event_abi()
152
214
 
153
215
  @combomethod
154
216
  def process_receipt(
155
217
  self, txn_receipt: TxReceipt, errors: EventLogErrorFlags = WARN
156
218
  ) -> Iterable[EventData]:
157
- return self._parse_logs(txn_receipt, errors)
219
+ return self._parse_logs(txn_receipt=txn_receipt, errors=errors)
158
220
 
221
+ @combomethod
159
222
  @to_tuple
160
223
  def _parse_logs(
161
224
  self, txn_receipt: TxReceipt, errors: EventLogErrorFlags
@@ -163,14 +226,20 @@ class BaseContractEvent:
163
226
  try:
164
227
  errors.name
165
228
  except AttributeError:
166
- raise AttributeError(
229
+ raise Web3AttributeError(
167
230
  f"Error flag must be one of: {EventLogErrorFlags.flag_options()}"
168
231
  )
169
232
 
170
233
  for log in txn_receipt["logs"]:
171
234
  try:
172
235
  rich_log = get_event_data(self.w3.codec, self.abi, log)
173
- except (MismatchedABI, LogTopicError, InvalidEventABI, TypeError) as e:
236
+ except (
237
+ MismatchedABI,
238
+ LogTopicError,
239
+ InvalidEventABI,
240
+ TypeError,
241
+ InsufficientDataBytes,
242
+ ) as e:
174
243
  if errors == DISCARD:
175
244
  continue
176
245
  elif errors == IGNORE:
@@ -185,13 +254,14 @@ class BaseContractEvent:
185
254
  f"The log with transaction hash: {log['transactionHash']!r} "
186
255
  f"and logIndex: {log['logIndex']} encountered the following "
187
256
  f"error during processing: {type(e).__name__}({e}). It has "
188
- "been discarded."
257
+ "been discarded.",
258
+ stacklevel=2,
189
259
  )
190
260
  continue
191
261
  yield rich_log
192
262
 
193
263
  @combomethod
194
- def process_log(self, log: HexStr) -> EventData:
264
+ def process_log(self, log: LogReceipt) -> EventData:
195
265
  return get_event_data(self.w3.codec, self.abi, log)
196
266
 
197
267
  @combomethod
@@ -199,12 +269,12 @@ class BaseContractEvent:
199
269
  self,
200
270
  abi: ABIEvent,
201
271
  argument_filters: Optional[Dict[str, Any]] = None,
202
- fromBlock: Optional[BlockIdentifier] = None,
203
- toBlock: Optional[BlockIdentifier] = None,
204
- blockHash: Optional[HexBytes] = None,
272
+ from_block: Optional[BlockIdentifier] = None,
273
+ to_block: Optional[BlockIdentifier] = None,
274
+ block_hash: Optional[HexBytes] = None,
205
275
  ) -> FilterParams:
206
276
  if not self.address:
207
- raise TypeError(
277
+ raise Web3TypeError(
208
278
  "This method can be only called on "
209
279
  "an instated contract with an address"
210
280
  )
@@ -214,11 +284,12 @@ class BaseContractEvent:
214
284
 
215
285
  _filters = dict(**argument_filters)
216
286
 
217
- blkhash_set = blockHash is not None
218
- blknum_set = fromBlock is not None or toBlock is not None
287
+ blkhash_set = block_hash is not None
288
+ blknum_set = from_block is not None or to_block is not None
219
289
  if blkhash_set and blknum_set:
220
290
  raise Web3ValidationError(
221
- "blockHash cannot be set at the same time as fromBlock or toBlock"
291
+ "`block_hash` cannot be set at the same time as "
292
+ "`from_block` or `to_block`"
222
293
  )
223
294
 
224
295
  # Construct JSON-RPC raw filter presentation based on human readable
@@ -228,19 +299,21 @@ class BaseContractEvent:
228
299
  self.w3.codec,
229
300
  contract_address=self.address,
230
301
  argument_filters=_filters,
231
- fromBlock=fromBlock,
232
- toBlock=toBlock,
302
+ from_block=from_block,
303
+ to_block=to_block,
233
304
  address=self.address,
234
305
  )
235
306
 
236
- if blockHash is not None:
237
- event_filter_params["blockHash"] = blockHash
307
+ if block_hash is not None:
308
+ event_filter_params["blockHash"] = block_hash
238
309
 
239
310
  return event_filter_params
240
311
 
241
312
  @classmethod
242
- def factory(cls, class_name: str, **kwargs: Any) -> PropertyCheckingFactory:
243
- return PropertyCheckingFactory(class_name, (cls,), kwargs)
313
+ def factory(
314
+ cls, class_name: str, **kwargs: Any
315
+ ) -> Union["ContractEvent", "AsyncContractEvent"]:
316
+ return PropertyCheckingFactory(class_name, (cls,), kwargs)()
244
317
 
245
318
  @staticmethod
246
319
  def check_for_forbidden_api_filter_arguments(
@@ -251,12 +324,12 @@ class BaseContractEvent:
251
324
  for filter_name, filter_value in _filters.items():
252
325
  _input = name_indexed_inputs[filter_name]
253
326
  if is_array_type(_input["type"]):
254
- raise TypeError(
327
+ raise Web3TypeError(
255
328
  "createFilter no longer supports array type filter arguments. "
256
329
  "see the build_filter method for filtering array type filters."
257
330
  )
258
331
  if is_list_like(filter_value) and is_dynamic_sized_type(_input["type"]):
259
- raise TypeError(
332
+ raise Web3TypeError(
260
333
  "createFilter no longer supports setting filter argument options "
261
334
  "for dynamic sized types. See the build_filter method for setting "
262
335
  "filters with the match_any method."
@@ -314,15 +387,15 @@ class BaseContractEvent:
314
387
  def _set_up_filter_builder(
315
388
  self,
316
389
  argument_filters: Optional[Dict[str, Any]] = None,
317
- fromBlock: Optional[BlockIdentifier] = None,
318
- toBlock: BlockIdentifier = "latest",
390
+ from_block: Optional[BlockIdentifier] = None,
391
+ to_block: BlockIdentifier = "latest",
319
392
  address: Optional[ChecksumAddress] = None,
320
393
  topics: Optional[Sequence[Any]] = None,
321
394
  filter_builder: Union[EventFilterBuilder, AsyncEventFilterBuilder] = None,
322
395
  ) -> None:
323
- if fromBlock is None:
324
- raise TypeError(
325
- "Missing mandatory keyword argument to create_filter: fromBlock"
396
+ if from_block is None:
397
+ raise Web3TypeError(
398
+ "Missing mandatory keyword argument to create_filter: `from_block`"
326
399
  )
327
400
 
328
401
  if argument_filters is None:
@@ -330,17 +403,15 @@ class BaseContractEvent:
330
403
 
331
404
  _filters = dict(**argument_filters)
332
405
 
333
- event_abi = self._get_event_abi()
334
-
335
- self.check_for_forbidden_api_filter_arguments(event_abi, _filters)
406
+ self.check_for_forbidden_api_filter_arguments(self.abi, _filters)
336
407
 
337
408
  _, event_filter_params = construct_event_filter_params(
338
- self._get_event_abi(),
409
+ self.abi,
339
410
  self.w3.codec,
340
411
  contract_address=self.address,
341
412
  argument_filters=_filters,
342
- fromBlock=fromBlock,
343
- toBlock=toBlock,
413
+ from_block=from_block,
414
+ to_block=to_block,
344
415
  address=address,
345
416
  topics=topics,
346
417
  )
@@ -348,8 +419,8 @@ class BaseContractEvent:
348
419
  filter_builder.address = cast(
349
420
  ChecksumAddress, event_filter_params.get("address")
350
421
  )
351
- filter_builder.fromBlock = event_filter_params.get("fromBlock")
352
- filter_builder.toBlock = event_filter_params.get("toBlock")
422
+ filter_builder.from_block = event_filter_params.get("fromBlock")
423
+ filter_builder.to_block = event_filter_params.get("toBlock")
353
424
  match_any_vals = {
354
425
  arg: value
355
426
  for arg, value in _filters.items()
@@ -370,7 +441,8 @@ class BaseContractEvent:
370
441
 
371
442
 
372
443
  class BaseContractEvents:
373
- """Class containing contract event objects
444
+ """
445
+ Class containing contract event objects
374
446
 
375
447
  This is available via:
376
448
 
@@ -394,99 +466,128 @@ class BaseContractEvents:
394
466
  self,
395
467
  abi: ABI,
396
468
  w3: Union["Web3", "AsyncWeb3"],
397
- contract_event_type: Type["BaseContractEvent"],
469
+ contract_event_type: Union[Type["ContractEvent"], Type["AsyncContractEvent"]],
398
470
  address: Optional[ChecksumAddress] = None,
399
471
  ) -> None:
400
- if abi:
401
- self.abi = abi
402
- self._events = filter_by_type("event", self.abi)
403
- for event in self._events:
404
- setattr(
405
- self,
406
- event["name"],
407
- contract_event_type.factory(
408
- event["name"],
409
- w3=w3,
410
- contract_abi=self.abi,
411
- address=address,
412
- event_name=event["name"],
413
- ),
414
- )
472
+ self.abi = abi
473
+ self.w3 = w3
474
+ self.address = address
475
+ _events: Sequence[ABIEvent] = None
415
476
 
416
- def __getattr__(self, event_name: str) -> Type["BaseContractEvent"]:
417
- if "_events" not in self.__dict__:
418
- raise NoABIEventsFound(
419
- "The abi for this contract contains no event definitions. ",
420
- "Are you sure you provided the correct contract abi?",
421
- )
422
- elif event_name not in self.__dict__["_events"]:
423
- raise ABIEventFunctionNotFound(
424
- f"The event '{event_name}' was not found in this contract's abi. ",
425
- "Are you sure you provided the correct contract abi?",
477
+ if self.abi:
478
+ _events = sorted(
479
+ filter_abi_by_type("event", self.abi),
480
+ key=lambda evt: (evt["name"], len(evt.get("inputs", []))),
426
481
  )
427
- else:
428
- return super().__getattribute__(event_name)
482
+ for event in _events:
483
+ abi_signature = abi_to_signature(event)
484
+ event_factory = contract_event_type.factory(
485
+ abi_signature,
486
+ w3=self.w3,
487
+ contract_abi=self.abi,
488
+ address=self.address,
489
+ abi=event,
490
+ )
429
491
 
430
- def __getitem__(self, event_name: str) -> Type["BaseContractEvent"]:
431
- return getattr(self, event_name)
492
+ # Set event name on instance if it does not already exist
493
+ if event["name"] not in self.__dict__:
494
+ setattr(self, event["name"], event_factory)
432
495
 
433
- def __iter__(self) -> Iterable[Type["BaseContractEvent"]]:
434
- """Iterate over supported
496
+ # Set underscore prefixed event signature on instance
497
+ # Handles ambiguity in overloaded contract events
498
+ setattr(self, f"_{abi_signature}", event_factory)
435
499
 
436
- :return: Iterable of :class:`ContractEvent`
437
- """
438
- for event in self._events:
439
- yield self[event["name"]]
500
+ if _events:
501
+ self._events = _events
440
502
 
441
503
  def __hasattr__(self, event_name: str) -> bool:
442
504
  try:
443
505
  return event_name in self.__dict__["_events"]
444
- except ABIEventFunctionNotFound:
506
+ except ABIEventNotFound:
445
507
  return False
446
508
 
447
509
 
448
510
  class BaseContractFunction:
449
- """Base class for contract functions
511
+ """
512
+ Base class for contract functions
450
513
 
451
514
  A function accessed via the api `contract.functions.myMethod(*args, **kwargs)`
452
515
  is a subclass of this class.
453
516
  """
454
517
 
455
518
  address: ChecksumAddress = None
456
- function_identifier: FunctionIdentifier = None
519
+ fn_name: str = None
520
+ name: str = None
521
+ signature: str = None
522
+ abi_element_identifier: ABIElementIdentifier = None
457
523
  w3: Union["Web3", "AsyncWeb3"] = None
458
524
  contract_abi: ABI = None
459
525
  abi: ABIFunction = None
460
526
  transaction: TxParams = None
461
527
  arguments: Tuple[Any, ...] = None
462
- decode_tuples: Optional[bool] = False
528
+ decode_tuples: Optional[bool] = None
529
+ argument_names: Tuple[str, ...] = tuple()
530
+ argument_types: Tuple[str, ...] = tuple()
463
531
  args: Any = None
464
532
  kwargs: Any = None
465
533
 
466
534
  def __init__(self, abi: Optional[ABIFunction] = None) -> None:
467
- self.abi = abi
468
- self.fn_name = type(self).__name__
535
+ if not self.abi_element_identifier:
536
+ self.abi_element_identifier = type(self).__name__
469
537
 
470
- def _set_function_info(self) -> None:
471
- if not self.abi:
472
- self.abi = find_matching_fn_abi(
473
- self.contract_abi,
474
- self.w3.codec,
475
- self.function_identifier,
476
- self.args,
477
- self.kwargs,
538
+ self.name = get_name_from_abi_element_identifier(self.abi_element_identifier)
539
+ self.fn_name = self.name
540
+
541
+ if abi:
542
+ self.abi = abi
543
+
544
+ self.signature = abi_to_signature(self.abi)
545
+
546
+ event_inputs = self.abi.get("inputs", [])
547
+ self.argument_names = tuple([input.get("name", None) for input in event_inputs])
548
+ self.argument_types = tuple([input["type"] for input in event_inputs])
549
+
550
+ @combomethod
551
+ def _get_abi(cls) -> ABIFunction:
552
+ if not cls.args and not cls.kwargs:
553
+ # If no args or kwargs are provided, get the ABI element by name
554
+ return cast(
555
+ ABIFunction,
556
+ get_abi_element(
557
+ cls.contract_abi,
558
+ get_abi_element_signature(cls.abi_element_identifier),
559
+ abi_codec=cls.w3.codec,
560
+ ),
478
561
  )
479
- if self.function_identifier in [FallbackFn, ReceiveFn]:
480
- self.selector = encode_hex(b"")
481
- elif is_text(self.function_identifier):
482
- # https://github.com/python/mypy/issues/4976
483
- self.selector = encode_hex(
484
- function_abi_to_4byte_selector(self.abi) # type: ignore
562
+
563
+ return cast(
564
+ ABIFunction,
565
+ get_abi_element(
566
+ cls.contract_abi,
567
+ get_name_from_abi_element_identifier(cls.abi_element_identifier),
568
+ *cls.args,
569
+ abi_codec=cls.w3.codec,
570
+ **cls.kwargs,
571
+ ),
572
+ )
573
+
574
+ def _set_function_info(self) -> None:
575
+ self.selector = encode_hex(b"")
576
+ if self.abi_element_identifier in [
577
+ "fallback",
578
+ "receive",
579
+ FallbackFn,
580
+ ReceiveFn,
581
+ ]:
582
+ self.selector = encode_hex(function_abi_to_4byte_selector(self.abi))
583
+ self.arguments = None
584
+ elif is_text(self.abi_element_identifier):
585
+ self.selector = encode_hex(function_abi_to_4byte_selector(self.abi))
586
+ self.arguments = get_normalized_abi_inputs(
587
+ self.abi, *self.args, **self.kwargs
485
588
  )
486
589
  else:
487
- raise TypeError("Unsupported function identifier")
488
-
489
- self.arguments = merge_args_and_kwargs(self.abi, self.args, self.kwargs)
590
+ raise Web3TypeError("Unsupported function identifier")
490
591
 
491
592
  def _get_call_txparams(self, transaction: Optional[TxParams] = None) -> TxParams:
492
593
  if transaction is None:
@@ -495,26 +596,25 @@ class BaseContractFunction:
495
596
  call_transaction = cast(TxParams, dict(**transaction))
496
597
 
497
598
  if "data" in call_transaction:
498
- raise ValueError("Cannot set 'data' field in call transaction")
599
+ raise Web3ValueError("Cannot set 'data' field in call transaction")
499
600
 
500
601
  if self.address:
501
602
  call_transaction.setdefault("to", self.address)
502
603
  if self.w3.eth.default_account is not empty:
503
- # type ignored b/c check prevents an empty default_account
504
604
  call_transaction.setdefault(
505
605
  "from",
506
- self.w3.eth.default_account, # type: ignore
606
+ cast(ChecksumAddress, self.w3.eth.default_account),
507
607
  )
508
608
 
509
609
  if "to" not in call_transaction:
510
610
  if isinstance(self, type):
511
- raise ValueError(
611
+ raise Web3ValueError(
512
612
  "When using `Contract.[methodtype].[method].call()` from"
513
613
  " a contract factory you "
514
614
  "must provide a `to` address with the transaction"
515
615
  )
516
616
  else:
517
- raise ValueError(
617
+ raise Web3ValueError(
518
618
  "Please ensure that this contract instance has an address."
519
619
  )
520
620
 
@@ -527,24 +627,23 @@ class BaseContractFunction:
527
627
  transact_transaction = cast(TxParams, dict(**transaction))
528
628
 
529
629
  if "data" in transact_transaction:
530
- raise ValueError("Cannot set 'data' field in transact transaction")
630
+ raise Web3ValueError("Cannot set 'data' field in transact transaction")
531
631
 
532
632
  if self.address is not None:
533
633
  transact_transaction.setdefault("to", self.address)
534
634
  if self.w3.eth.default_account is not empty:
535
- # type ignored b/c check prevents an empty default_account
536
635
  transact_transaction.setdefault(
537
- "from", self.w3.eth.default_account # type: ignore
636
+ "from", cast(ChecksumAddress, self.w3.eth.default_account)
538
637
  )
539
638
 
540
639
  if "to" not in transact_transaction:
541
640
  if isinstance(self, type):
542
- raise ValueError(
641
+ raise Web3ValueError(
543
642
  "When using `Contract.transact` from a contract factory you "
544
643
  "must provide a `to` address with the transaction"
545
644
  )
546
645
  else:
547
- raise ValueError(
646
+ raise Web3ValueError(
548
647
  "Please ensure that this contract instance has an address."
549
648
  )
550
649
  return transact_transaction
@@ -556,26 +655,25 @@ class BaseContractFunction:
556
655
  estimate_gas_transaction = cast(TxParams, dict(**transaction))
557
656
 
558
657
  if "data" in estimate_gas_transaction:
559
- raise ValueError("Cannot set 'data' field in estimate_gas transaction")
658
+ raise Web3ValueError("Cannot set 'data' field in estimate_gas transaction")
560
659
  if "to" in estimate_gas_transaction:
561
- raise ValueError("Cannot set to in estimate_gas transaction")
660
+ raise Web3ValueError("Cannot set to in estimate_gas transaction")
562
661
 
563
662
  if self.address:
564
663
  estimate_gas_transaction.setdefault("to", self.address)
565
664
  if self.w3.eth.default_account is not empty:
566
- # type ignored b/c check prevents an empty default_account
567
665
  estimate_gas_transaction.setdefault(
568
- "from", self.w3.eth.default_account # type: ignore
666
+ "from", cast(ChecksumAddress, self.w3.eth.default_account)
569
667
  )
570
668
 
571
669
  if "to" not in estimate_gas_transaction:
572
670
  if isinstance(self, type):
573
- raise ValueError(
671
+ raise Web3ValueError(
574
672
  "When using `Contract.estimate_gas` from a contract factory "
575
673
  "you must provide a `to` address with the transaction"
576
674
  )
577
675
  else:
578
- raise ValueError(
676
+ raise Web3ValueError(
579
677
  "Please ensure that this contract instance has an address."
580
678
  )
581
679
  return estimate_gas_transaction
@@ -587,21 +685,23 @@ class BaseContractFunction:
587
685
  built_transaction = cast(TxParams, dict(**transaction))
588
686
 
589
687
  if "data" in built_transaction:
590
- raise ValueError("Cannot set 'data' field in build transaction")
688
+ raise Web3ValueError("Cannot set 'data' field in build transaction")
591
689
 
592
690
  if not self.address and "to" not in built_transaction:
593
- raise ValueError(
691
+ raise Web3ValueError(
594
692
  "When using `ContractFunction.build_transaction` from a contract "
595
693
  "factory you must provide a `to` address with the transaction"
596
694
  )
597
695
  if self.address and "to" in built_transaction:
598
- raise ValueError("Cannot set 'to' field in contract call build transaction")
696
+ raise Web3ValueError(
697
+ "Cannot set 'to' field in contract call build transaction"
698
+ )
599
699
 
600
700
  if self.address:
601
701
  built_transaction.setdefault("to", self.address)
602
702
 
603
703
  if "to" not in built_transaction:
604
- raise ValueError(
704
+ raise Web3ValueError(
605
705
  "Please ensure that this contract instance has an address."
606
706
  )
607
707
 
@@ -619,18 +719,20 @@ class BaseContractFunction:
619
719
  if self.arguments is not None:
620
720
  _repr += f" bound to {self.arguments!r}"
621
721
  return _repr + ">"
622
- return f"<Function {self.fn_name}>"
722
+ return f"<Function {get_abi_element_signature(self.abi_element_identifier)}>"
623
723
 
624
724
  @classmethod
625
725
  def factory(
626
726
  cls, class_name: str, **kwargs: Any
627
727
  ) -> Union["ContractFunction", "AsyncContractFunction"]:
628
- return PropertyCheckingFactory(class_name, (cls,), kwargs)(kwargs.get("abi"))
728
+ return PropertyCheckingFactory(class_name, (cls,), kwargs)()
629
729
 
630
730
 
631
731
  class BaseContractFunctions:
632
732
  """Class containing contract function objects"""
633
733
 
734
+ _functions: Sequence[ABIFunction] = None
735
+
634
736
  def __init__(
635
737
  self,
636
738
  abi: ABI,
@@ -644,32 +746,38 @@ class BaseContractFunctions:
644
746
  self.abi = abi
645
747
  self.w3 = w3
646
748
  self.address = address
749
+ _functions: Sequence[ABIFunction] = None
647
750
 
648
751
  if self.abi:
649
- self._functions = filter_by_type("function", self.abi)
650
- for func in self._functions:
651
- setattr(
652
- self,
653
- func["name"],
654
- contract_function_class.factory(
655
- func["name"],
656
- w3=self.w3,
657
- contract_abi=self.abi,
658
- address=self.address,
659
- decode_tuples=decode_tuples,
660
- function_identifier=func["name"],
661
- ),
752
+ # Function with least number of inputs is first
753
+ # This ensures ambiguity will always be deterministic
754
+ # Prefer function without arguments if present, otherwise
755
+ # just use the first available
756
+ _functions = sorted(
757
+ filter_abi_by_type("function", self.abi),
758
+ key=lambda fn: (fn["name"], len(fn.get("inputs", []))),
759
+ )
760
+ for func in _functions:
761
+ abi_signature = abi_to_signature(func)
762
+ function_factory = contract_function_class.factory(
763
+ abi_signature,
764
+ w3=self.w3,
765
+ contract_abi=self.abi,
766
+ address=self.address,
767
+ decode_tuples=decode_tuples,
768
+ abi=func,
662
769
  )
663
770
 
664
- def __iter__(self) -> Iterable["ABIFunction"]:
665
- if not hasattr(self, "_functions") or not self._functions:
666
- return
771
+ # Set function name on instance if it does not already exist
772
+ if func["name"] not in self.__dict__:
773
+ setattr(self, func["name"], function_factory)
667
774
 
668
- for func in self._functions:
669
- yield self[func["name"]]
775
+ # Set function signature on instance
776
+ # Handles ambiguity in overloaded contract functions
777
+ setattr(self, f"_{abi_signature}", function_factory)
670
778
 
671
- def __getitem__(self, function_name: str) -> ABIFunction:
672
- return getattr(self, function_name)
779
+ if _functions:
780
+ self._functions = _functions
673
781
 
674
782
  def __hasattr__(self, function_name: str) -> bool:
675
783
  try:
@@ -679,7 +787,8 @@ class BaseContractFunctions:
679
787
 
680
788
 
681
789
  class BaseContract:
682
- """Base class for Contract proxy classes.
790
+ """
791
+ Base class for Contract proxy classes.
683
792
 
684
793
  First you need to create your Contract classes using
685
794
  :meth:`web3.eth.Eth.contract` that takes compiled Solidity contract
@@ -723,44 +832,58 @@ class BaseContract:
723
832
  # Public API
724
833
  #
725
834
  @combomethod
726
- def encodeABI(
835
+ def encode_abi(
727
836
  cls,
728
- fn_name: str,
837
+ abi_element_identifier: str,
729
838
  args: Optional[Any] = None,
730
839
  kwargs: Optional[Any] = None,
731
840
  data: Optional[HexStr] = None,
732
841
  ) -> HexStr:
733
842
  """
734
843
  Encodes the arguments using the Ethereum ABI for the contract function
735
- that matches the given name and arguments..
844
+ that matches the given name and arguments.
736
845
 
737
846
  :param data: defaults to function selector
738
847
  """
739
- fn_abi, fn_selector, fn_arguments = get_function_info(
740
- fn_name,
741
- cls.w3.codec,
742
- contract_abi=cls.abi,
743
- args=args,
744
- kwargs=kwargs,
848
+ args = args or tuple()
849
+ kwargs = kwargs or {}
850
+
851
+ element_info = get_abi_element_info(
852
+ cls.abi,
853
+ abi_element_identifier,
854
+ *args,
855
+ abi_codec=cls.w3.codec,
856
+ **kwargs,
745
857
  )
746
858
 
747
859
  if data is None:
748
- data = fn_selector
860
+ data = element_info["selector"]
749
861
 
750
- return encode_abi(cls.w3, fn_abi, fn_arguments, data)
862
+ return encode_abi(cls.w3, element_info["abi"], element_info["arguments"], data)
751
863
 
864
+ #
865
+ # Functions API
866
+ #
752
867
  @combomethod
753
868
  def all_functions(
754
869
  self,
755
- ) -> "BaseContractFunction":
870
+ ) -> List["BaseContractFunction"]:
871
+ """
872
+ Return all functions in the contract.
873
+ """
756
874
  return self.find_functions_by_identifier(
757
875
  self.abi, self.w3, self.address, lambda _: True
758
876
  )
759
877
 
760
878
  @combomethod
761
879
  def get_function_by_signature(self, signature: str) -> "BaseContractFunction":
880
+ """
881
+ Return a distinct function with matching signature.
882
+ Raises a Web3ValueError if the signature is invalid or if there is no match or
883
+ more than one is found.
884
+ """
762
885
  if " " in signature:
763
- raise ValueError(
886
+ raise Web3ValueError(
764
887
  "Function signature should not contain any spaces. "
765
888
  f"Found spaces in input: {signature}"
766
889
  )
@@ -774,7 +897,12 @@ class BaseContract:
774
897
  return self.get_function_by_identifier(fns, "signature")
775
898
 
776
899
  @combomethod
777
- def find_functions_by_name(self, fn_name: str) -> "BaseContractFunction":
900
+ def find_functions_by_name(self, fn_name: str) -> List["BaseContractFunction"]:
901
+ """
902
+ Return all functions with matching name.
903
+ Raises a Web3ValueError if there is no match or more than one is found.
904
+ """
905
+
778
906
  def callable_check(fn_abi: ABIFunction) -> bool:
779
907
  return fn_abi["name"] == fn_name
780
908
 
@@ -784,6 +912,10 @@ class BaseContract:
784
912
 
785
913
  @combomethod
786
914
  def get_function_by_name(self, fn_name: str) -> "BaseContractFunction":
915
+ """
916
+ Return a distinct function with matching name.
917
+ Raises a Web3ValueError if there is no match or more than one is found.
918
+ """
787
919
  fns = self.find_functions_by_name(fn_name)
788
920
  return self.get_function_by_identifier(fns, "name")
789
921
 
@@ -791,10 +923,15 @@ class BaseContract:
791
923
  def get_function_by_selector(
792
924
  self, selector: Union[bytes, int, HexStr]
793
925
  ) -> "BaseContractFunction":
926
+ """
927
+ Return a distinct function with matching 4byte selector.
928
+ Raises a Web3ValueError if there is no match or more than one is found.
929
+ """
930
+
794
931
  def callable_check(fn_abi: ABIFunction) -> bool:
795
- # typed dict cannot be used w/ a normal Dict
796
- # https://github.com/python/mypy/issues/4976
797
- return encode_hex(function_abi_to_4byte_selector(fn_abi)) == to_4byte_hex(selector) # type: ignore # noqa: E501
932
+ return encode_hex(function_abi_to_4byte_selector(fn_abi)) == to_4byte_hex(
933
+ selector
934
+ )
798
935
 
799
936
  fns = self.find_functions_by_identifier(
800
937
  self.abi, self.w3, self.address, callable_check
@@ -805,9 +942,10 @@ class BaseContract:
805
942
  def decode_function_input(
806
943
  self, data: HexStr
807
944
  ) -> Tuple["BaseContractFunction", Dict[str, Any]]:
808
- # type ignored b/c expects data arg to be HexBytes
809
- data = HexBytes(data) # type: ignore
810
- func = self.get_function_by_selector(data[:4])
945
+ """
946
+ Return a Tuple of the function selector and decoded arguments.
947
+ """
948
+ func = self.get_function_by_selector(HexBytes(data)[:4])
811
949
  arguments = decode_transaction_data(
812
950
  func.abi, data, normalizers=BASE_RETURN_NORMALIZERS
813
951
  )
@@ -815,9 +953,17 @@ class BaseContract:
815
953
 
816
954
  @combomethod
817
955
  def find_functions_by_args(self, *args: Any) -> "BaseContractFunction":
956
+ """
957
+ Return all functions with matching args, checking each argument can be encoded
958
+ with the type.
959
+ """
960
+
818
961
  def callable_check(fn_abi: ABIFunction) -> bool:
819
962
  return check_if_arguments_can_be_encoded(
820
- fn_abi, self.w3.codec, args=args, kwargs={}
963
+ fn_abi,
964
+ *args,
965
+ abi_codec=self.w3.codec,
966
+ **{},
821
967
  )
822
968
 
823
969
  return self.find_functions_by_identifier(
@@ -826,78 +972,119 @@ class BaseContract:
826
972
 
827
973
  @combomethod
828
974
  def get_function_by_args(self, *args: Any) -> "BaseContractFunction":
975
+ """
976
+ Return a distinct function with matching args, checking each argument can be
977
+ encoded with the type.
978
+ Raises a Web3ValueError if there is no match or more than one is found.
979
+ """
829
980
  fns = self.find_functions_by_args(*args)
830
981
  return self.get_function_by_identifier(fns, "args")
831
982
 
832
983
  #
833
- # Private Helpers
984
+ # Events API
834
985
  #
835
- _return_data_normalizers: Tuple[Callable[..., Any], ...] = tuple()
836
-
837
- @classmethod
838
- def _prepare_transaction(
839
- cls,
840
- fn_name: str,
841
- fn_args: Optional[Any] = None,
842
- fn_kwargs: Optional[Any] = None,
843
- transaction: Optional[TxParams] = None,
844
- ) -> TxParams:
845
- return prepare_transaction(
846
- cls.address,
847
- cls.w3,
848
- fn_identifier=fn_name,
849
- contract_abi=cls.abi,
850
- transaction=transaction,
851
- fn_args=fn_args,
852
- fn_kwargs=fn_kwargs,
986
+ @combomethod
987
+ def all_events(self) -> List["BaseContractEvent"]:
988
+ """
989
+ Return all events in the contract.
990
+ """
991
+ return self.find_events_by_identifier(
992
+ self.abi, self.w3, self.address, lambda _: True
853
993
  )
854
994
 
855
- @classmethod
856
- def _find_matching_fn_abi(
857
- cls,
858
- fn_identifier: Optional[str] = None,
859
- args: Optional[Any] = None,
860
- kwargs: Optional[Any] = None,
861
- ) -> ABIFunction:
862
- return find_matching_fn_abi(
863
- cls.abi, cls.w3.codec, fn_identifier=fn_identifier, args=args, kwargs=kwargs
995
+ @combomethod
996
+ def get_event_by_signature(self, signature: str) -> "BaseContractEvent":
997
+ """
998
+ Return a distinct event with matching signature.
999
+ Raises a Web3ValueError if the signature is invalid or if there is no match or
1000
+ more than one is found.
1001
+ """
1002
+
1003
+ def callable_check(event_abi: ABIEvent) -> bool:
1004
+ return abi_to_signature(event_abi) == signature.replace(" ", "")
1005
+
1006
+ events = self.find_events_by_identifier(
1007
+ self.abi, self.w3, self.address, callable_check
864
1008
  )
1009
+ return self.get_event_by_identifier(events, "signature")
865
1010
 
866
- @classmethod
867
- def _find_matching_event_abi(
868
- cls,
869
- event_name: Optional[str] = None,
870
- argument_names: Optional[Sequence[str]] = None,
871
- ) -> ABIEvent:
872
- return find_matching_event_abi(
873
- abi=cls.abi, event_name=event_name, argument_names=argument_names
1011
+ @combomethod
1012
+ def find_events_by_name(self, event_name: str) -> List["BaseContractEvent"]:
1013
+ """
1014
+ Return all events with matching name.
1015
+ Raises a Web3ValueError if there is no match or more than one is found.
1016
+ """
1017
+
1018
+ def callable_check(fn_abi: ABIFunction) -> bool:
1019
+ return fn_abi["name"] == event_name
1020
+
1021
+ return self.find_events_by_identifier(
1022
+ self.abi, self.w3, self.address, callable_check
874
1023
  )
875
1024
 
876
1025
  @combomethod
877
- def _encode_constructor_data(
878
- cls, args: Optional[Any] = None, kwargs: Optional[Any] = None
879
- ) -> HexStr:
880
- constructor_abi = get_constructor_abi(cls.abi)
1026
+ def get_event_by_name(self, event_name: str) -> "BaseContractEvent":
1027
+ """
1028
+ Return a distinct event with matching name.
1029
+ Raises a Web3ValueError if there is no match or more than one is found.
1030
+ """
1031
+ events = self.find_events_by_name(event_name)
1032
+ return self.get_event_by_identifier(events, "name")
881
1033
 
882
- if constructor_abi:
883
- if args is None:
884
- args = tuple()
885
- if kwargs is None:
886
- kwargs = {}
1034
+ @combomethod
1035
+ def find_events_by_selector(
1036
+ self, selector: Union[bytes, int, HexStr]
1037
+ ) -> List["BaseContractEvent"]:
1038
+ """
1039
+ Return all events with matching selector.
1040
+ Raises a Web3ValueError if there is no match or more than one is found.
1041
+ """
887
1042
 
888
- arguments = merge_args_and_kwargs(constructor_abi, args, kwargs)
1043
+ def callable_check(event_abi: ABIEvent) -> bool:
1044
+ return encode_hex(
1045
+ keccak(text=abi_to_signature(event_abi).replace(" ", ""))
1046
+ ) == encode_hex(hexstr_if_str(to_bytes, selector))
889
1047
 
890
- deploy_data = add_0x_prefix(
891
- encode_abi(cls.w3, constructor_abi, arguments, data=cls.bytecode)
1048
+ return self.find_events_by_identifier(
1049
+ self.abi, self.w3, self.address, callable_check
1050
+ )
1051
+
1052
+ @combomethod
1053
+ def get_event_by_selector(
1054
+ self, selector: Union[bytes, int, HexStr]
1055
+ ) -> "BaseContractEvent":
1056
+ """
1057
+ Return a distinct event with matching keccak selector.
1058
+ Raises a Web3ValueError if there is no match or more than one is found.
1059
+ """
1060
+ events = self.find_events_by_selector(selector)
1061
+ return self.get_event_by_identifier(events, "selector")
1062
+
1063
+ @combomethod
1064
+ def find_events_by_topic(self, topic: HexStr) -> List["BaseContractEvent"]:
1065
+ """
1066
+ Return all events with matching topic.
1067
+ Raises a Web3ValueError if there is no match or more than one is found.
1068
+ """
1069
+
1070
+ def callable_check(event_abi: ABIEvent) -> bool:
1071
+ return (
1072
+ encode_hex(keccak(text=abi_to_signature(event_abi).replace(" ", "")))
1073
+ == topic
892
1074
  )
893
- else:
894
- if args is not None or kwargs is not None:
895
- msg = "Constructor args were provided, but no constructor function was provided." # noqa: E501
896
- raise TypeError(msg)
897
1075
 
898
- deploy_data = to_hex(cls.bytecode)
1076
+ return self.find_events_by_identifier(
1077
+ self.abi, self.w3, self.address, callable_check
1078
+ )
899
1079
 
900
- return deploy_data
1080
+ @combomethod
1081
+ def get_event_by_topic(self, topic: HexStr) -> "BaseContractEvent":
1082
+ """
1083
+ Return a distinct event with matching topic.
1084
+ Raises a Web3ValueError if there is no match or more than one is found.
1085
+ """
1086
+ events = self.find_events_by_topic(topic)
1087
+ return self.get_event_by_identifier(events, "topic")
901
1088
 
902
1089
  @combomethod
903
1090
  def find_functions_by_identifier(
@@ -919,6 +1106,26 @@ class BaseContract:
919
1106
  "This method should be implemented in the inherited class"
920
1107
  )
921
1108
 
1109
+ @combomethod
1110
+ def find_events_by_identifier(
1111
+ cls,
1112
+ contract_abi: ABI,
1113
+ w3: Union["Web3", "AsyncWeb3"],
1114
+ address: ChecksumAddress,
1115
+ callable_check: Callable[..., Any],
1116
+ ) -> List[Any]:
1117
+ raise NotImplementedError(
1118
+ "This method should be implemented in the inherited class"
1119
+ )
1120
+
1121
+ @combomethod
1122
+ def get_event_by_identifier(
1123
+ cls, fns: Sequence["BaseContractEvent"], identifier: str
1124
+ ) -> "BaseContractEvent":
1125
+ raise NotImplementedError(
1126
+ "This method should be implemented in the inherited class"
1127
+ )
1128
+
922
1129
  @staticmethod
923
1130
  def get_fallback_function(
924
1131
  abi: ABI,
@@ -927,12 +1134,14 @@ class BaseContract:
927
1134
  address: Optional[ChecksumAddress] = None,
928
1135
  ) -> "BaseContractFunction":
929
1136
  if abi and fallback_func_abi_exists(abi):
1137
+ fallback_abi = filter_abi_by_type("fallback", abi)[0]
930
1138
  return function_type.factory(
931
1139
  "fallback",
932
1140
  w3=w3,
933
1141
  contract_abi=abi,
934
1142
  address=address,
935
- function_identifier=FallbackFn,
1143
+ abi_element_identifier=FallbackFn,
1144
+ abi=fallback_abi,
936
1145
  )()
937
1146
 
938
1147
  return cast(function_type, NonExistentFallbackFunction()) # type: ignore
@@ -945,16 +1154,95 @@ class BaseContract:
945
1154
  address: Optional[ChecksumAddress] = None,
946
1155
  ) -> "BaseContractFunction":
947
1156
  if abi and receive_func_abi_exists(abi):
1157
+ receive_abi = filter_abi_by_type("receive", abi)[0]
948
1158
  return function_type.factory(
949
1159
  "receive",
950
1160
  w3=w3,
951
1161
  contract_abi=abi,
952
1162
  address=address,
953
- function_identifier=ReceiveFn,
1163
+ abi_element_identifier=ReceiveFn,
1164
+ abi=receive_abi,
954
1165
  )()
955
1166
 
956
1167
  return cast(function_type, NonExistentReceiveFunction()) # type: ignore
957
1168
 
1169
+ #
1170
+ # Private Helpers
1171
+ #
1172
+ _return_data_normalizers: Tuple[Callable[..., Any], ...] = tuple()
1173
+
1174
+ @classmethod
1175
+ def _prepare_transaction(
1176
+ cls,
1177
+ abi_element_identifier: ABIElementIdentifier,
1178
+ fn_args: Optional[Any] = None,
1179
+ fn_kwargs: Optional[Any] = None,
1180
+ transaction: Optional[TxParams] = None,
1181
+ ) -> TxParams:
1182
+ return prepare_transaction(
1183
+ cls.address,
1184
+ cls.w3,
1185
+ abi_element_identifier=abi_element_identifier,
1186
+ contract_abi=cls.abi,
1187
+ transaction=transaction,
1188
+ fn_args=fn_args,
1189
+ fn_kwargs=fn_kwargs,
1190
+ )
1191
+
1192
+ @classmethod
1193
+ def _find_matching_fn_abi(
1194
+ cls,
1195
+ fn_identifier: Optional[ABIElementIdentifier] = None,
1196
+ *args: Sequence[Any],
1197
+ **kwargs: Dict[str, Any],
1198
+ ) -> ABIElement:
1199
+ if not args and not kwargs:
1200
+ fn_identifier = get_abi_element_signature(fn_identifier)
1201
+
1202
+ return get_abi_element(
1203
+ cls.abi,
1204
+ fn_identifier,
1205
+ *args,
1206
+ abi_codec=cls.w3.codec,
1207
+ **kwargs,
1208
+ )
1209
+
1210
+ @classmethod
1211
+ def _get_event_abi(
1212
+ cls,
1213
+ event_name: Optional[str] = None,
1214
+ argument_names: Optional[Sequence[str]] = None,
1215
+ ) -> ABIEvent:
1216
+ return cast(
1217
+ ABIEvent,
1218
+ get_abi_element(
1219
+ abi=cls.abi,
1220
+ abi_element_identifier=event_name,
1221
+ argument_names=argument_names,
1222
+ ),
1223
+ )
1224
+
1225
+ @combomethod
1226
+ def _encode_constructor_data(
1227
+ cls, *args: Sequence[Any], **kwargs: Dict[str, Any]
1228
+ ) -> HexStr:
1229
+ constructor_abi = find_constructor_abi_element_by_type(cls.abi)
1230
+
1231
+ if constructor_abi:
1232
+ arguments = get_normalized_abi_inputs(constructor_abi, *args, **kwargs)
1233
+
1234
+ deploy_data = add_0x_prefix(
1235
+ encode_abi(cls.w3, constructor_abi, arguments, data=cls.bytecode)
1236
+ )
1237
+ else:
1238
+ if args or kwargs:
1239
+ msg = "Constructor args were provided, but no constructor function was provided." # noqa: E501
1240
+ raise Web3TypeError(msg)
1241
+
1242
+ deploy_data = to_hex(cls.bytecode)
1243
+
1244
+ return deploy_data
1245
+
958
1246
 
959
1247
  class BaseContractCaller:
960
1248
  """
@@ -980,7 +1268,7 @@ class BaseContractCaller:
980
1268
  """
981
1269
 
982
1270
  # mypy types
983
- _functions: List[Union[ABIFunction, ABIEvent]]
1271
+ _functions: Sequence[ABIFunction]
984
1272
 
985
1273
  def __init__(
986
1274
  self,
@@ -996,6 +1284,11 @@ class BaseContractCaller:
996
1284
  self._functions = []
997
1285
 
998
1286
  def __getattr__(self, function_name: str) -> Any:
1287
+ function_names = [
1288
+ get_name_from_abi_element_identifier(fn["name"])
1289
+ for fn in self._functions
1290
+ if fn.get("type") == "function"
1291
+ ]
999
1292
  if self.abi is None:
1000
1293
  raise NoABIFound(
1001
1294
  "There is no ABI found for this contract.",
@@ -1005,8 +1298,8 @@ class BaseContractCaller:
1005
1298
  "The ABI for this contract contains no function definitions. ",
1006
1299
  "Are you sure you provided the correct contract ABI?",
1007
1300
  )
1008
- elif function_name not in {fn["name"] for fn in self._functions}:
1009
- functions_available = ", ".join([fn["name"] for fn in self._functions])
1301
+ elif get_name_from_abi_element_identifier(function_name) not in function_names:
1302
+ functions_available = ", ".join(function_names)
1010
1303
  raise ABIFunctionNotFound(
1011
1304
  f"The function '{function_name}' was not found in this contract's ABI.",
1012
1305
  " Here is a list of all of the function names found: ",
@@ -1014,11 +1307,17 @@ class BaseContractCaller:
1014
1307
  "Did you mean to call one of those functions?",
1015
1308
  )
1016
1309
  else:
1017
- return super().__getattribute__(function_name)
1310
+ function_identifier = function_name
1018
1311
 
1019
- def __hasattr__(self, event_name: str) -> bool:
1312
+ if "(" not in function_name:
1313
+ function_identifier = _get_any_abi_signature_with_name(
1314
+ function_name, self._functions
1315
+ )
1316
+ return super().__getattribute__(function_identifier)
1317
+
1318
+ def __hasattr__(self, function_name: str) -> bool:
1020
1319
  try:
1021
- return event_name in self.__dict__["_events"]
1320
+ return function_name in self.__dict__["_functions"]
1022
1321
  except ABIFunctionNotFound:
1023
1322
  return False
1024
1323
 
@@ -1060,7 +1359,7 @@ class BaseContractConstructor:
1060
1359
 
1061
1360
  @combomethod
1062
1361
  def _encode_data_in_transaction(self, *args: Any, **kwargs: Any) -> HexStr:
1063
- constructor_abi = get_constructor_abi(self.abi)
1362
+ constructor_abi = find_constructor_abi_element_by_type(self.abi)
1064
1363
 
1065
1364
  if constructor_abi:
1066
1365
  if not args:
@@ -1068,7 +1367,8 @@ class BaseContractConstructor:
1068
1367
  if not kwargs:
1069
1368
  kwargs = {}
1070
1369
 
1071
- arguments = merge_args_and_kwargs(constructor_abi, args, kwargs)
1370
+ arguments = get_normalized_abi_inputs(constructor_abi, *args, **kwargs)
1371
+
1072
1372
  data = add_0x_prefix(
1073
1373
  encode_abi(self.w3, constructor_abi, arguments, data=self.bytecode)
1074
1374
  )
@@ -1088,9 +1388,8 @@ class BaseContractConstructor:
1088
1388
  )
1089
1389
 
1090
1390
  if self.w3.eth.default_account is not empty:
1091
- # type ignored b/c check prevents an empty default_account
1092
1391
  estimate_gas_transaction.setdefault(
1093
- "from", self.w3.eth.default_account # type: ignore
1392
+ "from", cast(ChecksumAddress, self.w3.eth.default_account)
1094
1393
  )
1095
1394
 
1096
1395
  estimate_gas_transaction["data"] = self.data_in_transaction
@@ -1107,9 +1406,8 @@ class BaseContractConstructor:
1107
1406
  )
1108
1407
 
1109
1408
  if self.w3.eth.default_account is not empty:
1110
- # type ignored b/c check prevents an empty default_account
1111
1409
  transact_transaction.setdefault(
1112
- "from", self.w3.eth.default_account # type: ignore
1410
+ "from", cast(ChecksumAddress, self.w3.eth.default_account)
1113
1411
  )
1114
1412
 
1115
1413
  transact_transaction["data"] = self.data_in_transaction
@@ -1128,7 +1426,7 @@ class BaseContractConstructor:
1128
1426
  ) -> None:
1129
1427
  keys_found = transaction.keys() & forbidden_keys
1130
1428
  if keys_found:
1131
- raise ValueError(
1429
+ raise Web3ValueError(
1132
1430
  f"Cannot set '{', '.join(keys_found)}' field(s) in transaction"
1133
1431
  )
1134
1432
 
@@ -1136,7 +1434,7 @@ class BaseContractConstructor:
1136
1434
  class NonExistentFallbackFunction:
1137
1435
  @staticmethod
1138
1436
  def _raise_exception() -> NoReturn:
1139
- raise FallbackNotFound("No fallback function was found in the contract ABI.")
1437
+ raise ABIFallbackNotFound("No fallback function was found in the contract ABI.")
1140
1438
 
1141
1439
  def __getattr__(self, attr: Any) -> Callable[[], None]:
1142
1440
  return self._raise_exception
@@ -1145,7 +1443,7 @@ class NonExistentFallbackFunction:
1145
1443
  class NonExistentReceiveFunction:
1146
1444
  @staticmethod
1147
1445
  def _raise_exception() -> NoReturn:
1148
- raise FallbackNotFound("No receive function was found in the contract ABI.")
1446
+ raise ABIReceiveNotFound("No receive function was found in the contract ABI.")
1149
1447
 
1150
1448
  def __getattr__(self, attr: Any) -> Callable[[], None]:
1151
1449
  return self._raise_exception