web3 7.5.0__py3-none-any.whl → 7.6.1__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.
@@ -1,4 +1,3 @@
1
- import copy
2
1
  from typing import (
3
2
  TYPE_CHECKING,
4
3
  Any,
@@ -15,15 +14,16 @@ from typing import (
15
14
 
16
15
  from eth_typing import (
17
16
  ABI,
18
- ABIEvent,
17
+ ABIFunction,
19
18
  ChecksumAddress,
20
19
  )
21
20
  from eth_utils import (
22
21
  combomethod,
23
22
  )
24
23
  from eth_utils.abi import (
24
+ abi_to_signature,
25
+ filter_abi_by_type,
25
26
  get_abi_input_names,
26
- get_all_function_abis,
27
27
  )
28
28
  from eth_utils.toolz import (
29
29
  partial,
@@ -34,6 +34,7 @@ from hexbytes import (
34
34
 
35
35
  from web3._utils.abi import (
36
36
  fallback_func_abi_exists,
37
+ get_name_from_abi_element_identifier,
37
38
  receive_func_abi_exists,
38
39
  )
39
40
  from web3._utils.abi_element_identifiers import (
@@ -48,6 +49,8 @@ from web3._utils.compat import (
48
49
  )
49
50
  from web3._utils.contracts import (
50
51
  async_parse_block_identifier,
52
+ copy_contract_event,
53
+ copy_contract_function,
51
54
  )
52
55
  from web3._utils.datatypes import (
53
56
  PropertyCheckingFactory,
@@ -86,7 +89,10 @@ from web3.contract.utils import (
86
89
  get_function_by_identifier,
87
90
  )
88
91
  from web3.exceptions import (
92
+ ABIEventNotFound,
89
93
  ABIFunctionNotFound,
94
+ MismatchedABI,
95
+ NoABIEventsFound,
90
96
  NoABIFound,
91
97
  NoABIFunctionsFound,
92
98
  Web3AttributeError,
@@ -101,6 +107,9 @@ from web3.types import (
101
107
  TxParams,
102
108
  )
103
109
  from web3.utils.abi import (
110
+ _filter_by_argument_count,
111
+ _get_any_abi_signature_with_name,
112
+ _mismatched_abi_error_diagnosis,
104
113
  get_abi_element,
105
114
  )
106
115
 
@@ -113,16 +122,8 @@ class AsyncContractEvent(BaseContractEvent):
113
122
  # mypy types
114
123
  w3: "AsyncWeb3"
115
124
 
116
- def __call__(self) -> "AsyncContractEvent":
117
- clone = copy.copy(self)
118
-
119
- if not self.abi:
120
- self.abi = cast(
121
- ABIEvent,
122
- get_abi_element(self.contract_abi, self.event_name),
123
- )
124
-
125
- return clone
125
+ def __call__(self, *args: Any, **kwargs: Any) -> "AsyncContractEvent":
126
+ return copy_contract_event(self, *args, **kwargs)
126
127
 
127
128
  @combomethod
128
129
  async def get_logs(
@@ -188,11 +189,9 @@ class AsyncContractEvent(BaseContractEvent):
188
189
  same time as ``from_block`` or ``to_block``
189
190
  :yield: Tuple of :class:`AttributeDict` instances
190
191
  """
191
- event_abi = self._get_event_abi()
192
-
193
192
  # validate ``argument_filters`` if present
194
193
  if argument_filters is not None:
195
- event_arg_names = get_abi_input_names(event_abi)
194
+ event_arg_names = get_abi_input_names(self.abi)
196
195
  if not all(arg in event_arg_names for arg in argument_filters.keys()):
197
196
  raise Web3ValidationError(
198
197
  "When filtering by argument names, all argument names must be "
@@ -200,17 +199,17 @@ class AsyncContractEvent(BaseContractEvent):
200
199
  )
201
200
 
202
201
  _filter_params = self._get_event_filter_params(
203
- event_abi, argument_filters, from_block, to_block, block_hash
202
+ self.abi, argument_filters, from_block, to_block, block_hash
204
203
  )
205
204
  # call JSON-RPC API
206
205
  logs = await self.w3.eth.get_logs(_filter_params)
207
206
 
208
207
  # convert raw binary data to Python proxy objects as described by ABI:
209
208
  all_event_logs = tuple(
210
- get_event_data(self.w3.codec, event_abi, entry) for entry in logs
209
+ get_event_data(self.w3.codec, self.abi, entry) for entry in logs
211
210
  )
212
211
  filtered_logs = self._process_get_logs_argument_filters(
213
- event_abi,
212
+ self.abi,
214
213
  all_event_logs,
215
214
  argument_filters,
216
215
  )
@@ -231,7 +230,7 @@ class AsyncContractEvent(BaseContractEvent):
231
230
  """
232
231
  Create filter object that tracks logs emitted by this contract event.
233
232
  """
234
- filter_builder = AsyncEventFilterBuilder(self._get_event_abi(), self.w3.codec)
233
+ filter_builder = AsyncEventFilterBuilder(self.abi, self.w3.codec)
235
234
  self._set_up_filter_builder(
236
235
  argument_filters,
237
236
  from_block,
@@ -241,9 +240,7 @@ class AsyncContractEvent(BaseContractEvent):
241
240
  filter_builder,
242
241
  )
243
242
  log_filter = await filter_builder.deploy(self.w3)
244
- log_filter.log_entry_formatter = get_event_data(
245
- self.w3.codec, self._get_event_abi()
246
- )
243
+ log_filter.log_entry_formatter = get_event_data(self.w3.codec, self.abi)
247
244
  log_filter.builder = filter_builder
248
245
 
249
246
  return log_filter
@@ -251,18 +248,16 @@ class AsyncContractEvent(BaseContractEvent):
251
248
  @combomethod
252
249
  def build_filter(self) -> AsyncEventFilterBuilder:
253
250
  builder = AsyncEventFilterBuilder(
254
- self._get_event_abi(),
251
+ self.abi,
255
252
  self.w3.codec,
256
- formatter=get_event_data(self.w3.codec, self._get_event_abi()),
253
+ formatter=get_event_data(self.w3.codec, self.abi),
257
254
  )
258
255
  builder.address = self.address
259
256
  return builder
260
257
 
261
258
  @classmethod
262
259
  def factory(cls, class_name: str, **kwargs: Any) -> Self:
263
- return PropertyCheckingFactory(class_name, (cls,), kwargs)(
264
- abi=kwargs.get("abi")
265
- )
260
+ return PropertyCheckingFactory(class_name, (cls,), kwargs)()
266
261
 
267
262
 
268
263
  class AsyncContractEvents(BaseContractEvents):
@@ -271,28 +266,150 @@ class AsyncContractEvents(BaseContractEvents):
271
266
  ) -> None:
272
267
  super().__init__(abi, w3, AsyncContractEvent, address)
273
268
 
269
+ def __iter__(self) -> Iterable["AsyncContractEvent"]:
270
+ if not hasattr(self, "_events") or not self._events:
271
+ return
272
+
273
+ for event in self._events:
274
+ yield self[abi_to_signature(event)]
275
+
276
+ def __getattr__(self, event_name: str) -> "AsyncContractEvent":
277
+ if super().__getattribute__("abi") is None:
278
+ raise NoABIFound(
279
+ "There is no ABI found for this contract.",
280
+ )
281
+ elif "_events" not in self.__dict__ or len(self._events) == 0:
282
+ raise NoABIEventsFound(
283
+ "The abi for this contract contains no event definitions. ",
284
+ "Are you sure you provided the correct contract abi?",
285
+ )
286
+ elif get_name_from_abi_element_identifier(event_name) not in [
287
+ get_name_from_abi_element_identifier(event["name"])
288
+ for event in self._events
289
+ ]:
290
+ raise ABIEventNotFound(
291
+ f"The event '{event_name}' was not found in this contract's abi. ",
292
+ "Are you sure you provided the correct contract abi?",
293
+ )
294
+
295
+ if "(" not in event_name:
296
+ event_name = _get_any_abi_signature_with_name(event_name, self._events)
297
+ else:
298
+ event_name = f"_{event_name}"
299
+
300
+ return super().__getattribute__(event_name)
301
+
302
+ def __getitem__(self, event_name: str) -> "AsyncContractEvent":
303
+ return getattr(self, event_name)
304
+
274
305
 
275
306
  class AsyncContractFunction(BaseContractFunction):
276
307
  # mypy types
277
308
  w3: "AsyncWeb3"
278
309
 
279
310
  def __call__(self, *args: Any, **kwargs: Any) -> "AsyncContractFunction":
280
- clone = copy.copy(self)
281
- if args is None:
282
- clone.args = tuple()
283
- else:
284
- clone.args = args
311
+ # When a function is called, check arguments to obtain the correct function
312
+ # in the contract. self will be used if all args and kwargs are
313
+ # encodable to self.abi, otherwise the correct function is obtained from
314
+ # the contract.
315
+ if (
316
+ self.abi_element_identifier in [FallbackFn, ReceiveFn]
317
+ or self.abi_element_identifier == "constructor"
318
+ ):
319
+ return copy_contract_function(self, *args, **kwargs)
320
+
321
+ all_functions = cast(
322
+ List[ABIFunction],
323
+ filter_abi_by_type(
324
+ "function",
325
+ self.contract_abi,
326
+ ),
327
+ )
328
+ # Filter functions by name to obtain function signatures
329
+ function_name = get_name_from_abi_element_identifier(
330
+ self.abi_element_identifier
331
+ )
332
+ function_abis = [
333
+ function for function in all_functions if function["name"] == function_name
334
+ ]
335
+ num_args = len(args) + len(kwargs)
336
+ function_abis_with_arg_count = cast(
337
+ List[ABIFunction],
338
+ _filter_by_argument_count(
339
+ num_args,
340
+ function_abis,
341
+ ),
342
+ )
285
343
 
286
- if kwargs is None:
287
- clone.kwargs = {}
344
+ if not len(function_abis_with_arg_count):
345
+ # Build an ABI without arguments to determine if one exists
346
+ function_abis_with_arg_count = [
347
+ ABIFunction({"type": "function", "name": function_name})
348
+ ]
349
+
350
+ # Check that arguments in call match a function ABI
351
+ num_attempts = 0
352
+ function_abi_matches = []
353
+ contract_function = None
354
+ for abi in function_abis_with_arg_count:
355
+ try:
356
+ num_attempts += 1
357
+
358
+ # Search for a function ABI that matches the arguments used
359
+ function_abi_matches.append(
360
+ cast(
361
+ ABIFunction,
362
+ get_abi_element(
363
+ function_abis,
364
+ abi_to_signature(abi),
365
+ *args,
366
+ abi_codec=self.w3.codec,
367
+ **kwargs,
368
+ ),
369
+ )
370
+ )
371
+ except MismatchedABI:
372
+ # ignore exceptions
373
+ continue
374
+
375
+ if len(function_abi_matches) == 1:
376
+ function_abi = function_abi_matches[0]
377
+ if abi_to_signature(self.abi) == abi_to_signature(function_abi):
378
+ contract_function = self
379
+ else:
380
+ # Found a match that is not self
381
+ contract_function = AsyncContractFunction.factory(
382
+ abi_to_signature(function_abi),
383
+ w3=self.w3,
384
+ contract_abi=self.contract_abi,
385
+ address=self.address,
386
+ abi_element_identifier=abi_to_signature(function_abi),
387
+ abi=function_abi,
388
+ )
288
389
  else:
289
- clone.kwargs = kwargs
290
- clone._set_function_info()
291
- return clone
390
+ for abi in function_abi_matches:
391
+ if abi_to_signature(self.abi) == abi_to_signature(abi):
392
+ contract_function = self
393
+ break
394
+ else:
395
+ # Raise exception if multiple found
396
+ raise MismatchedABI(
397
+ _mismatched_abi_error_diagnosis(
398
+ function_name,
399
+ self.contract_abi,
400
+ len(function_abi_matches),
401
+ num_args,
402
+ *args,
403
+ abi_codec=self.w3.codec,
404
+ **kwargs,
405
+ )
406
+ )
407
+
408
+ return copy_contract_function(contract_function, *args, **kwargs)
292
409
 
293
410
  @classmethod
294
411
  def factory(cls, class_name: str, **kwargs: Any) -> Self:
295
- return PropertyCheckingFactory(class_name, (cls,), kwargs)(kwargs.get("abi"))
412
+ return PropertyCheckingFactory(class_name, (cls,), kwargs)()
296
413
 
297
414
  async def call(
298
415
  self,
@@ -332,11 +449,13 @@ class AsyncContractFunction(BaseContractFunction):
332
449
 
333
450
  block_id = await async_parse_block_identifier(self.w3, block_identifier)
334
451
 
452
+ abi_element_identifier = abi_to_signature(self.abi)
453
+
335
454
  return await async_call_contract_function(
336
455
  self.w3,
337
456
  self.address,
338
457
  self._return_data_normalizers,
339
- self.abi_element_identifier,
458
+ abi_element_identifier,
340
459
  call_transaction,
341
460
  block_id,
342
461
  self.contract_abi,
@@ -350,10 +469,11 @@ class AsyncContractFunction(BaseContractFunction):
350
469
 
351
470
  async def transact(self, transaction: Optional[TxParams] = None) -> HexBytes:
352
471
  setup_transaction = self._transact(transaction)
472
+ abi_element_identifier = abi_to_signature(self.abi)
353
473
  return await async_transact_with_contract_function(
354
474
  self.address,
355
475
  self.w3,
356
- self.abi_element_identifier,
476
+ abi_element_identifier,
357
477
  setup_transaction,
358
478
  self.contract_abi,
359
479
  self.abi,
@@ -368,10 +488,11 @@ class AsyncContractFunction(BaseContractFunction):
368
488
  state_override: Optional[StateOverride] = None,
369
489
  ) -> int:
370
490
  setup_transaction = self._estimate_gas(transaction)
491
+ abi_element_identifier = abi_to_signature(self.abi)
371
492
  return await async_estimate_gas_for_function(
372
493
  self.address,
373
494
  self.w3,
374
- self.abi_element_identifier,
495
+ abi_element_identifier,
375
496
  setup_transaction,
376
497
  self.contract_abi,
377
498
  self.abi,
@@ -385,10 +506,11 @@ class AsyncContractFunction(BaseContractFunction):
385
506
  self, transaction: Optional[TxParams] = None
386
507
  ) -> TxParams:
387
508
  built_transaction = self._build_transaction(transaction)
509
+ abi_element_identifier = abi_to_signature(self.abi)
388
510
  return await async_build_transaction_for_function(
389
511
  self.address,
390
512
  self.w3,
391
- self.abi_element_identifier,
513
+ abi_element_identifier,
392
514
  built_transaction,
393
515
  self.contract_abi,
394
516
  self.abi,
@@ -439,23 +561,45 @@ class AsyncContractFunctions(BaseContractFunctions):
439
561
  ) -> None:
440
562
  super().__init__(abi, w3, AsyncContractFunction, address, decode_tuples)
441
563
 
564
+ def __iter__(self) -> Iterable["AsyncContractFunction"]:
565
+ if not hasattr(self, "_functions") or not self._functions:
566
+ return
567
+
568
+ for func in self._functions:
569
+ yield self[abi_to_signature(func)]
570
+
442
571
  def __getattr__(self, function_name: str) -> "AsyncContractFunction":
443
- if self.abi is None:
572
+ if super().__getattribute__("abi") is None:
444
573
  raise NoABIFound(
445
574
  "There is no ABI found for this contract.",
446
575
  )
447
- if "_functions" not in self.__dict__:
576
+ elif "_functions" not in self.__dict__ or len(self._functions) == 0:
448
577
  raise NoABIFunctionsFound(
449
578
  "The abi for this contract contains no function definitions. ",
450
579
  "Are you sure you provided the correct contract abi?",
451
580
  )
452
- elif function_name not in self.__dict__["_functions"]:
581
+ elif get_name_from_abi_element_identifier(function_name) not in [
582
+ get_name_from_abi_element_identifier(function["name"])
583
+ for function in self._functions
584
+ ]:
453
585
  raise ABIFunctionNotFound(
454
- f"The function '{function_name}' was not found in this contract's abi.",
455
- " Are you sure you provided the correct contract abi?",
586
+ f"The function '{function_name}' was not found in this ",
587
+ "contract's abi.",
588
+ )
589
+
590
+ if "(" not in function_name:
591
+ function_name = _get_any_abi_signature_with_name(
592
+ function_name, self._functions
456
593
  )
457
594
  else:
458
- return super().__getattribute__(function_name)
595
+ function_name = f"_{function_name}"
596
+
597
+ return super().__getattribute__(
598
+ function_name,
599
+ )
600
+
601
+ def __getitem__(self, function_name: str) -> "AsyncContractFunction":
602
+ return getattr(self, function_name)
459
603
 
460
604
 
461
605
  class AsyncContract(BaseContract):
@@ -491,7 +635,11 @@ class AsyncContract(BaseContract):
491
635
  self.abi, self.w3, self.address, decode_tuples=self.decode_tuples
492
636
  )
493
637
  self.caller = AsyncContractCaller(
494
- self.abi, self.w3, self.address, decode_tuples=self.decode_tuples
638
+ self.abi,
639
+ self.w3,
640
+ self.address,
641
+ decode_tuples=self.decode_tuples,
642
+ contract_functions=self.functions,
495
643
  )
496
644
  self.events = AsyncContractEvents(self.abi, self.w3, self.address)
497
645
  self.fallback = AsyncContract.get_fallback_function(
@@ -523,6 +671,16 @@ class AsyncContract(BaseContract):
523
671
  normalizers=normalizers,
524
672
  ),
525
673
  )
674
+
675
+ if contract.abi:
676
+ for abi in contract.abi:
677
+ abi_name = abi.get("name")
678
+ if abi_name in ["abi", "address"]:
679
+ raise Web3AttributeError(
680
+ f"Contract contains a reserved word `{abi_name}` "
681
+ f"and could not be instantiated."
682
+ )
683
+
526
684
  contract.functions = AsyncContractFunctions(
527
685
  contract.abi, contract.w3, decode_tuples=contract.decode_tuples
528
686
  )
@@ -531,6 +689,7 @@ class AsyncContract(BaseContract):
531
689
  contract.w3,
532
690
  contract.address,
533
691
  decode_tuples=contract.decode_tuples,
692
+ contract_functions=contract.functions,
534
693
  )
535
694
  contract.events = AsyncContractEvents(contract.abi, contract.w3)
536
695
  contract.fallback = AsyncContract.get_fallback_function(
@@ -613,6 +772,7 @@ class AsyncContractCaller(BaseContractCaller):
613
772
  block_identifier: BlockIdentifier = None,
614
773
  ccip_read_enabled: Optional[bool] = None,
615
774
  decode_tuples: Optional[bool] = False,
775
+ contract_functions: Optional[AsyncContractFunctions] = None,
616
776
  ) -> None:
617
777
  super().__init__(abi, w3, address, decode_tuples=decode_tuples)
618
778
 
@@ -620,18 +780,13 @@ class AsyncContractCaller(BaseContractCaller):
620
780
  if transaction is None:
621
781
  transaction = {}
622
782
 
623
- self._functions = get_all_function_abis(self.abi)
624
-
625
- for func in self._functions:
626
- fn = AsyncContractFunction.factory(
627
- func["name"],
628
- w3=w3,
629
- contract_abi=self.abi,
630
- address=self.address,
631
- abi_element_identifier=func["name"],
632
- decode_tuples=decode_tuples,
783
+ if contract_functions is None:
784
+ contract_functions = AsyncContractFunctions(
785
+ abi, w3, address, decode_tuples=decode_tuples
633
786
  )
634
787
 
788
+ self._functions = contract_functions._functions
789
+ for fn in contract_functions.__iter__():
635
790
  caller_method = partial(
636
791
  self.call_function,
637
792
  fn,
@@ -639,8 +794,7 @@ class AsyncContractCaller(BaseContractCaller):
639
794
  block_identifier=block_identifier,
640
795
  ccip_read_enabled=ccip_read_enabled,
641
796
  )
642
-
643
- setattr(self, func["name"], caller_method)
797
+ setattr(self, str(fn.abi_element_identifier), caller_method)
644
798
 
645
799
  def __call__(
646
800
  self,