web3 7.0.0b5__py3-none-any.whl → 7.0.0b6__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.
- web3/_utils/batching.py +217 -0
- web3/_utils/caching.py +26 -2
- web3/_utils/compat/__init__.py +1 -0
- web3/_utils/events.py +2 -2
- web3/_utils/module_testing/eth_module.py +10 -10
- web3/_utils/module_testing/module_testing_utils.py +13 -0
- web3/_utils/module_testing/web3_module.py +438 -17
- web3/contract/utils.py +112 -4
- web3/eth/async_eth.py +5 -4
- web3/eth/eth.py +5 -4
- web3/exceptions.py +20 -0
- web3/gas_strategies/time_based.py +2 -2
- web3/main.py +21 -9
- web3/manager.py +113 -8
- web3/method.py +29 -9
- web3/middleware/base.py +43 -0
- web3/module.py +47 -7
- web3/providers/async_base.py +55 -23
- web3/providers/base.py +59 -26
- web3/providers/ipc.py +23 -8
- web3/providers/legacy_websocket.py +26 -1
- web3/providers/persistent/async_ipc.py +60 -76
- web3/providers/persistent/persistent.py +134 -10
- web3/providers/persistent/request_processor.py +98 -14
- web3/providers/persistent/websocket.py +43 -66
- web3/providers/rpc/async_rpc.py +19 -1
- web3/providers/rpc/rpc.py +19 -1
- web3/types.py +7 -1
- {web3-7.0.0b5.dist-info → web3-7.0.0b6.dist-info}/LICENSE +1 -1
- {web3-7.0.0b5.dist-info → web3-7.0.0b6.dist-info}/METADATA +31 -20
- {web3-7.0.0b5.dist-info → web3-7.0.0b6.dist-info}/RECORD +33 -32
- {web3-7.0.0b5.dist-info → web3-7.0.0b6.dist-info}/WHEEL +0 -0
- {web3-7.0.0b5.dist-info → web3-7.0.0b6.dist-info}/top_level.txt +0 -0
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import pytest
|
|
2
2
|
from typing import (
|
|
3
|
+
TYPE_CHECKING,
|
|
3
4
|
Any,
|
|
4
5
|
NoReturn,
|
|
5
6
|
Sequence,
|
|
6
|
-
|
|
7
|
+
cast,
|
|
7
8
|
)
|
|
8
9
|
|
|
9
10
|
from eth_typing import (
|
|
11
|
+
Address,
|
|
10
12
|
ChecksumAddress,
|
|
11
13
|
HexAddress,
|
|
12
14
|
HexStr,
|
|
@@ -23,10 +25,23 @@ from web3 import (
|
|
|
23
25
|
from web3._utils.ens import (
|
|
24
26
|
ens_addresses,
|
|
25
27
|
)
|
|
28
|
+
from web3.contract import (
|
|
29
|
+
Contract,
|
|
30
|
+
)
|
|
26
31
|
from web3.exceptions import (
|
|
27
32
|
InvalidAddress,
|
|
33
|
+
MethodNotSupported,
|
|
34
|
+
Web3ValueError,
|
|
35
|
+
)
|
|
36
|
+
from web3.types import (
|
|
37
|
+
BlockData,
|
|
28
38
|
)
|
|
29
39
|
|
|
40
|
+
if TYPE_CHECKING:
|
|
41
|
+
from web3.contract import ( # noqa: F401
|
|
42
|
+
AsyncContract,
|
|
43
|
+
)
|
|
44
|
+
|
|
30
45
|
|
|
31
46
|
class Web3ModuleTest:
|
|
32
47
|
def test_web3_client_version(self, w3: Web3) -> None:
|
|
@@ -223,16 +238,9 @@ class Web3ModuleTest:
|
|
|
223
238
|
),
|
|
224
239
|
),
|
|
225
240
|
)
|
|
226
|
-
@pytest.mark.parametrize(
|
|
227
|
-
"w3",
|
|
228
|
-
(
|
|
229
|
-
Web3,
|
|
230
|
-
AsyncWeb3,
|
|
231
|
-
),
|
|
232
|
-
)
|
|
233
241
|
def test_solidity_keccak(
|
|
234
242
|
self,
|
|
235
|
-
w3:
|
|
243
|
+
w3: "Web3",
|
|
236
244
|
types: Sequence[TypeStr],
|
|
237
245
|
values: Sequence[Any],
|
|
238
246
|
expected: HexBytes,
|
|
@@ -264,16 +272,9 @@ class Web3ModuleTest:
|
|
|
264
272
|
),
|
|
265
273
|
),
|
|
266
274
|
)
|
|
267
|
-
@pytest.mark.parametrize(
|
|
268
|
-
"w3",
|
|
269
|
-
(
|
|
270
|
-
Web3(),
|
|
271
|
-
AsyncWeb3(),
|
|
272
|
-
),
|
|
273
|
-
)
|
|
274
275
|
def test_solidity_keccak_ens(
|
|
275
276
|
self,
|
|
276
|
-
w3:
|
|
277
|
+
w3: "Web3",
|
|
277
278
|
types: Sequence[TypeStr],
|
|
278
279
|
values: Sequence[str],
|
|
279
280
|
expected: HexBytes,
|
|
@@ -313,3 +314,423 @@ class Web3ModuleTest:
|
|
|
313
314
|
|
|
314
315
|
def test_is_connected(self, w3: "Web3") -> None:
|
|
315
316
|
assert w3.is_connected()
|
|
317
|
+
|
|
318
|
+
def test_batch_requests(self, w3: "Web3", math_contract: Contract) -> None:
|
|
319
|
+
with w3.batch_requests() as batch:
|
|
320
|
+
batch.add(w3.eth.get_block(6))
|
|
321
|
+
batch.add(w3.eth.get_block(4))
|
|
322
|
+
batch.add(w3.eth.get_block(2))
|
|
323
|
+
batch.add(w3.eth.get_block(0))
|
|
324
|
+
batch.add(math_contract.functions.multiply7(0))
|
|
325
|
+
|
|
326
|
+
batch.add_mapping(
|
|
327
|
+
{
|
|
328
|
+
math_contract.functions.multiply7: [1, 2, 3],
|
|
329
|
+
w3.eth.get_block: [1, 3, 5],
|
|
330
|
+
}
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
assert len(batch._requests_info) == 11
|
|
334
|
+
responses = batch.execute()
|
|
335
|
+
assert len(responses) == 11
|
|
336
|
+
|
|
337
|
+
# assert proper batch cleanup after execution
|
|
338
|
+
assert batch._requests_info == []
|
|
339
|
+
assert not batch._provider._is_batching
|
|
340
|
+
|
|
341
|
+
# assert batch cannot be added to after execution
|
|
342
|
+
with pytest.raises(
|
|
343
|
+
Web3ValueError,
|
|
344
|
+
match="Batch has already been executed or cancelled",
|
|
345
|
+
):
|
|
346
|
+
batch.add(w3.eth.get_block(5))
|
|
347
|
+
|
|
348
|
+
# assert batch cannot be executed again
|
|
349
|
+
with pytest.raises(
|
|
350
|
+
Web3ValueError,
|
|
351
|
+
match="Batch has already been executed or cancelled",
|
|
352
|
+
):
|
|
353
|
+
batch.execute()
|
|
354
|
+
|
|
355
|
+
# assert can make a request after executing
|
|
356
|
+
block_num = w3.eth.block_number
|
|
357
|
+
assert isinstance(block_num, int)
|
|
358
|
+
|
|
359
|
+
first_four_responses: Sequence[BlockData] = cast(
|
|
360
|
+
Sequence[BlockData], responses[:4]
|
|
361
|
+
)
|
|
362
|
+
assert first_four_responses[0]["number"] == 6
|
|
363
|
+
assert first_four_responses[1]["number"] == 4
|
|
364
|
+
assert first_four_responses[2]["number"] == 2
|
|
365
|
+
assert first_four_responses[3]["number"] == 0
|
|
366
|
+
|
|
367
|
+
responses_five_through_eight: Sequence[int] = cast(
|
|
368
|
+
Sequence[int], responses[4:8]
|
|
369
|
+
)
|
|
370
|
+
assert responses_five_through_eight[0] == 0
|
|
371
|
+
assert responses_five_through_eight[1] == 7
|
|
372
|
+
assert responses_five_through_eight[2] == 14
|
|
373
|
+
assert responses_five_through_eight[3] == 21
|
|
374
|
+
|
|
375
|
+
last_three_responses: Sequence[BlockData] = cast(
|
|
376
|
+
Sequence[BlockData], responses[8:]
|
|
377
|
+
)
|
|
378
|
+
assert last_three_responses[0]["number"] == 1
|
|
379
|
+
assert last_three_responses[1]["number"] == 3
|
|
380
|
+
assert last_three_responses[2]["number"] == 5
|
|
381
|
+
|
|
382
|
+
def test_batch_requests_initialized_as_object(
|
|
383
|
+
self, w3: "Web3", math_contract: Contract
|
|
384
|
+
) -> None:
|
|
385
|
+
batch = w3.batch_requests()
|
|
386
|
+
batch.add(w3.eth.get_block(1))
|
|
387
|
+
batch.add(w3.eth.get_block(2))
|
|
388
|
+
batch.add(math_contract.functions.multiply7(0))
|
|
389
|
+
batch.add_mapping(
|
|
390
|
+
{math_contract.functions.multiply7: [1, 2], w3.eth.get_block: [3, 4]}
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
assert len(batch._requests_info) == 7
|
|
394
|
+
b1, b2, m0, m1, m2, b3, b4 = batch.execute()
|
|
395
|
+
|
|
396
|
+
# assert proper batch cleanup after execution
|
|
397
|
+
assert batch._requests_info == []
|
|
398
|
+
assert not batch._provider._is_batching
|
|
399
|
+
|
|
400
|
+
# assert batch cannot be added to after execution
|
|
401
|
+
with pytest.raises(
|
|
402
|
+
Web3ValueError,
|
|
403
|
+
match="Batch has already been executed or cancelled",
|
|
404
|
+
):
|
|
405
|
+
batch.add(w3.eth.get_block(5))
|
|
406
|
+
|
|
407
|
+
# assert batch cannot be executed again
|
|
408
|
+
with pytest.raises(
|
|
409
|
+
Web3ValueError,
|
|
410
|
+
match="Batch has already been executed or cancelled",
|
|
411
|
+
):
|
|
412
|
+
batch.execute()
|
|
413
|
+
|
|
414
|
+
# assert can make a request after executing
|
|
415
|
+
block_num = w3.eth.block_number
|
|
416
|
+
assert isinstance(block_num, int)
|
|
417
|
+
|
|
418
|
+
assert cast(BlockData, b1)["number"] == 1
|
|
419
|
+
assert cast(BlockData, b2)["number"] == 2
|
|
420
|
+
assert cast(int, m0) == 0
|
|
421
|
+
assert cast(int, m1) == 7
|
|
422
|
+
assert cast(int, m2) == 14
|
|
423
|
+
assert cast(BlockData, b3)["number"] == 3
|
|
424
|
+
assert cast(BlockData, b4)["number"] == 4
|
|
425
|
+
|
|
426
|
+
def test_batch_requests_clear(self, w3: "Web3") -> None:
|
|
427
|
+
with w3.batch_requests() as batch:
|
|
428
|
+
batch.add(w3.eth.get_block(1))
|
|
429
|
+
batch.add(w3.eth.get_block(2))
|
|
430
|
+
|
|
431
|
+
assert len(batch._requests_info) == 2
|
|
432
|
+
batch.clear()
|
|
433
|
+
assert batch._requests_info == []
|
|
434
|
+
|
|
435
|
+
batch.add(w3.eth.get_block(3))
|
|
436
|
+
batch.add(w3.eth.get_block(4))
|
|
437
|
+
|
|
438
|
+
r1, r2 = batch.execute()
|
|
439
|
+
|
|
440
|
+
assert cast(BlockData, r1)["number"] == 3
|
|
441
|
+
assert cast(BlockData, r2)["number"] == 4
|
|
442
|
+
|
|
443
|
+
new_batch = w3.batch_requests()
|
|
444
|
+
new_batch.add(w3.eth.get_block(5))
|
|
445
|
+
|
|
446
|
+
assert len(new_batch._requests_info) == 1
|
|
447
|
+
new_batch.clear()
|
|
448
|
+
assert new_batch._requests_info == []
|
|
449
|
+
|
|
450
|
+
new_batch.add(w3.eth.get_block(6))
|
|
451
|
+
(r3,) = new_batch.execute()
|
|
452
|
+
assert cast(BlockData, r3)["number"] == 6
|
|
453
|
+
|
|
454
|
+
def test_batch_requests_cancel(self, w3: "Web3") -> None:
|
|
455
|
+
# as context manager
|
|
456
|
+
with w3.batch_requests() as batch:
|
|
457
|
+
batch.add(w3.eth.get_block(1))
|
|
458
|
+
batch.cancel()
|
|
459
|
+
with pytest.raises(
|
|
460
|
+
Web3ValueError,
|
|
461
|
+
match="Batch has already been executed or cancelled",
|
|
462
|
+
):
|
|
463
|
+
batch.add(w3.eth.get_block(2))
|
|
464
|
+
with pytest.raises(
|
|
465
|
+
Web3ValueError,
|
|
466
|
+
match="Batch has already been executed or cancelled",
|
|
467
|
+
):
|
|
468
|
+
batch.execute()
|
|
469
|
+
|
|
470
|
+
# can make a request after cancelling
|
|
471
|
+
block_num = w3.eth.block_number
|
|
472
|
+
assert isinstance(block_num, int)
|
|
473
|
+
|
|
474
|
+
# as obj
|
|
475
|
+
new_batch = w3.batch_requests()
|
|
476
|
+
new_batch.add(w3.eth.get_block(1))
|
|
477
|
+
new_batch.cancel()
|
|
478
|
+
with pytest.raises(
|
|
479
|
+
Web3ValueError,
|
|
480
|
+
match="Batch has already been executed or cancelled",
|
|
481
|
+
):
|
|
482
|
+
new_batch.add(w3.eth.get_block(2))
|
|
483
|
+
with pytest.raises(
|
|
484
|
+
Web3ValueError,
|
|
485
|
+
match="Batch has already been executed or cancelled",
|
|
486
|
+
):
|
|
487
|
+
new_batch.execute()
|
|
488
|
+
|
|
489
|
+
# assert can make a request after cancelling
|
|
490
|
+
block_num = w3.eth.block_number
|
|
491
|
+
assert isinstance(block_num, int)
|
|
492
|
+
|
|
493
|
+
def test_batch_requests_raises_for_common_unsupported_methods(
|
|
494
|
+
self, w3: "Web3", math_contract: Contract
|
|
495
|
+
) -> None:
|
|
496
|
+
with w3.batch_requests() as batch:
|
|
497
|
+
with pytest.raises(MethodNotSupported, match="eth_sendTransaction"):
|
|
498
|
+
batch.add(w3.eth.send_transaction({}))
|
|
499
|
+
batch.execute()
|
|
500
|
+
|
|
501
|
+
with w3.batch_requests() as batch:
|
|
502
|
+
with pytest.raises(MethodNotSupported, match="eth_sendTransaction"):
|
|
503
|
+
batch.add(math_contract.functions.multiply7(1).transact({}))
|
|
504
|
+
batch.execute()
|
|
505
|
+
|
|
506
|
+
with w3.batch_requests() as batch:
|
|
507
|
+
with pytest.raises(MethodNotSupported, match="eth_sendRawTransaction"):
|
|
508
|
+
batch.add(w3.eth.send_raw_transaction(b""))
|
|
509
|
+
batch.execute()
|
|
510
|
+
|
|
511
|
+
with w3.batch_requests() as batch:
|
|
512
|
+
with pytest.raises(MethodNotSupported, match="eth_sign"):
|
|
513
|
+
batch.add(w3.eth.sign(Address(b"\x00" * 20)))
|
|
514
|
+
batch.execute()
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
# -- async -- #
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
class AsyncWeb3ModuleTest(Web3ModuleTest):
|
|
521
|
+
# Note: Any test that overrides the synchronous test from `Web3ModuleTest` with
|
|
522
|
+
# an asynchronous test should have the exact same name.
|
|
523
|
+
|
|
524
|
+
@pytest.mark.asyncio
|
|
525
|
+
async def test_web3_client_version(self, async_w3: AsyncWeb3) -> None:
|
|
526
|
+
client_version = await async_w3.client_version
|
|
527
|
+
self._check_web3_client_version(client_version)
|
|
528
|
+
|
|
529
|
+
@pytest.mark.asyncio
|
|
530
|
+
async def test_batch_requests(
|
|
531
|
+
self, async_w3: AsyncWeb3, async_math_contract: "AsyncContract"
|
|
532
|
+
) -> None:
|
|
533
|
+
async with async_w3.batch_requests() as batch:
|
|
534
|
+
batch.add(async_w3.eth.get_block(6))
|
|
535
|
+
batch.add(async_w3.eth.get_block(4))
|
|
536
|
+
batch.add(async_w3.eth.get_block(2))
|
|
537
|
+
batch.add(async_w3.eth.get_block(0))
|
|
538
|
+
|
|
539
|
+
batch.add(async_math_contract.functions.multiply7(0))
|
|
540
|
+
|
|
541
|
+
batch.add_mapping(
|
|
542
|
+
{
|
|
543
|
+
async_math_contract.functions.multiply7: [1, 2, 3],
|
|
544
|
+
async_w3.eth.get_block: [1, 3, 5],
|
|
545
|
+
}
|
|
546
|
+
)
|
|
547
|
+
|
|
548
|
+
assert len(batch._async_requests_info) == 11
|
|
549
|
+
responses = await batch.async_execute()
|
|
550
|
+
assert len(responses) == 11
|
|
551
|
+
|
|
552
|
+
# assert proper batch cleanup after execution
|
|
553
|
+
assert batch._async_requests_info == []
|
|
554
|
+
assert not batch._provider._is_batching
|
|
555
|
+
|
|
556
|
+
# assert batch cannot be added to after execution
|
|
557
|
+
with pytest.raises(
|
|
558
|
+
Web3ValueError,
|
|
559
|
+
match="Batch has already been executed or cancelled",
|
|
560
|
+
):
|
|
561
|
+
batch.add(async_w3.eth.get_block(5))
|
|
562
|
+
|
|
563
|
+
# assert batch cannot be executed again
|
|
564
|
+
with pytest.raises(
|
|
565
|
+
Web3ValueError,
|
|
566
|
+
match="Batch has already been executed or cancelled",
|
|
567
|
+
):
|
|
568
|
+
await batch.async_execute()
|
|
569
|
+
|
|
570
|
+
# assert can make a request after executing
|
|
571
|
+
block_num = await async_w3.eth.block_number
|
|
572
|
+
assert isinstance(block_num, int)
|
|
573
|
+
|
|
574
|
+
first_four_responses: Sequence[BlockData] = cast(
|
|
575
|
+
Sequence[BlockData], responses[:4]
|
|
576
|
+
)
|
|
577
|
+
assert first_four_responses[0]["number"] == 6
|
|
578
|
+
assert first_four_responses[1]["number"] == 4
|
|
579
|
+
assert first_four_responses[2]["number"] == 2
|
|
580
|
+
assert first_four_responses[3]["number"] == 0
|
|
581
|
+
|
|
582
|
+
responses_five_through_eight: Sequence[int] = cast(
|
|
583
|
+
Sequence[int], responses[4:8]
|
|
584
|
+
)
|
|
585
|
+
assert responses_five_through_eight[0] == 0
|
|
586
|
+
assert responses_five_through_eight[1] == 7
|
|
587
|
+
assert responses_five_through_eight[2] == 14
|
|
588
|
+
assert responses_five_through_eight[3] == 21
|
|
589
|
+
|
|
590
|
+
last_three_responses: Sequence[BlockData] = cast(
|
|
591
|
+
Sequence[BlockData], responses[8:]
|
|
592
|
+
)
|
|
593
|
+
assert last_three_responses[0]["number"] == 1
|
|
594
|
+
assert last_three_responses[1]["number"] == 3
|
|
595
|
+
assert last_three_responses[2]["number"] == 5
|
|
596
|
+
|
|
597
|
+
@pytest.mark.asyncio
|
|
598
|
+
async def test_batch_requests_initialized_as_object(
|
|
599
|
+
self, async_w3: AsyncWeb3, async_math_contract: "AsyncContract"
|
|
600
|
+
) -> None:
|
|
601
|
+
batch = async_w3.batch_requests()
|
|
602
|
+
batch.add(async_w3.eth.get_block(1))
|
|
603
|
+
batch.add(async_w3.eth.get_block(2))
|
|
604
|
+
batch.add(async_math_contract.functions.multiply7(0))
|
|
605
|
+
batch.add_mapping(
|
|
606
|
+
{
|
|
607
|
+
async_math_contract.functions.multiply7: [1, 2],
|
|
608
|
+
async_w3.eth.get_block: [3, 4],
|
|
609
|
+
}
|
|
610
|
+
)
|
|
611
|
+
|
|
612
|
+
assert len(batch._async_requests_info) == 7
|
|
613
|
+
b1, b2, m0, m1, m2, b3, b4 = await batch.async_execute()
|
|
614
|
+
|
|
615
|
+
# assert proper batch cleanup after execution
|
|
616
|
+
assert batch._async_requests_info == []
|
|
617
|
+
assert not batch._provider._is_batching
|
|
618
|
+
|
|
619
|
+
# assert batch cannot be added to after execution
|
|
620
|
+
with pytest.raises(
|
|
621
|
+
Web3ValueError,
|
|
622
|
+
match="Batch has already been executed or cancelled",
|
|
623
|
+
):
|
|
624
|
+
batch.add(async_w3.eth.get_block(5))
|
|
625
|
+
|
|
626
|
+
# assert batch cannot be executed again
|
|
627
|
+
with pytest.raises(
|
|
628
|
+
Web3ValueError,
|
|
629
|
+
match="Batch has already been executed or cancelled",
|
|
630
|
+
):
|
|
631
|
+
await batch.async_execute()
|
|
632
|
+
|
|
633
|
+
# assert can make a request after executing
|
|
634
|
+
block_num = await async_w3.eth.block_number
|
|
635
|
+
assert isinstance(block_num, int)
|
|
636
|
+
|
|
637
|
+
assert cast(BlockData, b1)["number"] == 1
|
|
638
|
+
assert cast(BlockData, b2)["number"] == 2
|
|
639
|
+
assert cast(int, m0) == 0
|
|
640
|
+
assert cast(int, m1) == 7
|
|
641
|
+
assert cast(int, m2) == 14
|
|
642
|
+
assert cast(BlockData, b3)["number"] == 3
|
|
643
|
+
assert cast(BlockData, b4)["number"] == 4
|
|
644
|
+
|
|
645
|
+
@pytest.mark.asyncio
|
|
646
|
+
async def test_batch_requests_clear(self, async_w3: AsyncWeb3) -> None:
|
|
647
|
+
async with async_w3.batch_requests() as batch:
|
|
648
|
+
batch.add(async_w3.eth.get_block(1))
|
|
649
|
+
batch.add(async_w3.eth.get_block(2))
|
|
650
|
+
|
|
651
|
+
assert len(batch._async_requests_info) == 2
|
|
652
|
+
batch.clear()
|
|
653
|
+
assert batch._async_requests_info == []
|
|
654
|
+
|
|
655
|
+
batch.add(async_w3.eth.get_block(3))
|
|
656
|
+
batch.add(async_w3.eth.get_block(4))
|
|
657
|
+
|
|
658
|
+
r1, r2 = await batch.async_execute()
|
|
659
|
+
|
|
660
|
+
assert cast(BlockData, r1)["number"] == 3
|
|
661
|
+
assert cast(BlockData, r2)["number"] == 4
|
|
662
|
+
|
|
663
|
+
new_batch = async_w3.batch_requests()
|
|
664
|
+
new_batch.add(async_w3.eth.get_block(5))
|
|
665
|
+
|
|
666
|
+
assert len(new_batch._async_requests_info) == 1
|
|
667
|
+
new_batch.clear()
|
|
668
|
+
assert new_batch._async_requests_info == []
|
|
669
|
+
|
|
670
|
+
new_batch.add(async_w3.eth.get_block(6))
|
|
671
|
+
(r3,) = await new_batch.async_execute()
|
|
672
|
+
assert cast(BlockData, r3)["number"] == 6
|
|
673
|
+
|
|
674
|
+
@pytest.mark.asyncio
|
|
675
|
+
async def test_batch_requests_cancel(self, async_w3: AsyncWeb3) -> None:
|
|
676
|
+
# as context manager
|
|
677
|
+
async with async_w3.batch_requests() as batch:
|
|
678
|
+
batch.add(async_w3.eth.get_block(1))
|
|
679
|
+
batch.cancel()
|
|
680
|
+
with pytest.raises(
|
|
681
|
+
Web3ValueError,
|
|
682
|
+
match="Batch has already been executed or cancelled",
|
|
683
|
+
):
|
|
684
|
+
batch.add(async_w3.eth.get_block(2))
|
|
685
|
+
with pytest.raises(
|
|
686
|
+
Web3ValueError,
|
|
687
|
+
match="Batch has already been executed or cancelled",
|
|
688
|
+
):
|
|
689
|
+
await batch.async_execute()
|
|
690
|
+
|
|
691
|
+
# can make a request after cancelling
|
|
692
|
+
block_num = await async_w3.eth.block_number
|
|
693
|
+
assert isinstance(block_num, int)
|
|
694
|
+
|
|
695
|
+
# as obj
|
|
696
|
+
new_batch = async_w3.batch_requests()
|
|
697
|
+
new_batch.add(async_w3.eth.get_block(1))
|
|
698
|
+
new_batch.cancel()
|
|
699
|
+
with pytest.raises(
|
|
700
|
+
Web3ValueError,
|
|
701
|
+
match="Batch has already been executed or cancelled",
|
|
702
|
+
):
|
|
703
|
+
new_batch.add(async_w3.eth.get_block(2))
|
|
704
|
+
with pytest.raises(
|
|
705
|
+
Web3ValueError,
|
|
706
|
+
match="Batch has already been executed or cancelled",
|
|
707
|
+
):
|
|
708
|
+
await new_batch.async_execute()
|
|
709
|
+
|
|
710
|
+
# can make a request after cancelling
|
|
711
|
+
block_num = await async_w3.eth.block_number
|
|
712
|
+
assert isinstance(block_num, int)
|
|
713
|
+
|
|
714
|
+
@pytest.mark.asyncio
|
|
715
|
+
async def test_batch_requests_raises_for_common_unsupported_methods(
|
|
716
|
+
self, async_w3: AsyncWeb3, async_math_contract: "AsyncContract"
|
|
717
|
+
) -> None:
|
|
718
|
+
async with async_w3.batch_requests() as batch:
|
|
719
|
+
with pytest.raises(MethodNotSupported, match="eth_sendTransaction"):
|
|
720
|
+
batch.add(async_w3.eth.send_transaction({}))
|
|
721
|
+
await batch.async_execute()
|
|
722
|
+
|
|
723
|
+
async with async_w3.batch_requests() as batch:
|
|
724
|
+
with pytest.raises(MethodNotSupported, match="eth_sendTransaction"):
|
|
725
|
+
batch.add(async_math_contract.functions.multiply7(1).transact({}))
|
|
726
|
+
await batch.async_execute()
|
|
727
|
+
|
|
728
|
+
async with async_w3.batch_requests() as batch:
|
|
729
|
+
with pytest.raises(MethodNotSupported, match="eth_sendRawTransaction"):
|
|
730
|
+
batch.add(async_w3.eth.send_raw_transaction(b""))
|
|
731
|
+
await batch.async_execute()
|
|
732
|
+
|
|
733
|
+
async with async_w3.batch_requests() as batch:
|
|
734
|
+
with pytest.raises(MethodNotSupported, match="eth_sign"):
|
|
735
|
+
batch.add(async_w3.eth.sign(Address(b"\x00" * 20)))
|
|
736
|
+
await batch.async_execute()
|
web3/contract/utils.py
CHANGED
|
@@ -9,6 +9,7 @@ from typing import (
|
|
|
9
9
|
Tuple,
|
|
10
10
|
Type,
|
|
11
11
|
Union,
|
|
12
|
+
cast,
|
|
12
13
|
)
|
|
13
14
|
|
|
14
15
|
from eth_abi.exceptions import (
|
|
@@ -16,6 +17,11 @@ from eth_abi.exceptions import (
|
|
|
16
17
|
)
|
|
17
18
|
from eth_typing import (
|
|
18
19
|
ChecksumAddress,
|
|
20
|
+
TypeStr,
|
|
21
|
+
)
|
|
22
|
+
from eth_utils.toolz import (
|
|
23
|
+
compose,
|
|
24
|
+
curry,
|
|
19
25
|
)
|
|
20
26
|
from hexbytes import (
|
|
21
27
|
HexBytes,
|
|
@@ -60,10 +66,50 @@ if TYPE_CHECKING:
|
|
|
60
66
|
AsyncWeb3,
|
|
61
67
|
Web3,
|
|
62
68
|
)
|
|
69
|
+
from web3.providers.persistent import ( # noqa: F401
|
|
70
|
+
PersistentConnectionProvider,
|
|
71
|
+
)
|
|
63
72
|
|
|
64
73
|
ACCEPTABLE_EMPTY_STRINGS = ["0x", b"0x", "", b""]
|
|
65
74
|
|
|
66
75
|
|
|
76
|
+
@curry
|
|
77
|
+
def format_contract_call_return_data_curried(
|
|
78
|
+
async_w3: Union["AsyncWeb3", "Web3"],
|
|
79
|
+
decode_tuples: bool,
|
|
80
|
+
fn_abi: ABIFunction,
|
|
81
|
+
function_identifier: FunctionIdentifier,
|
|
82
|
+
normalizers: Tuple[Callable[..., Any], ...],
|
|
83
|
+
output_types: Sequence[TypeStr],
|
|
84
|
+
return_data: Any,
|
|
85
|
+
) -> Any:
|
|
86
|
+
"""
|
|
87
|
+
Helper function for formatting contract call return data for batch requests. Curry
|
|
88
|
+
with all arguments except `return_data` and process `return_data` once it is
|
|
89
|
+
available.
|
|
90
|
+
"""
|
|
91
|
+
try:
|
|
92
|
+
output_data = async_w3.codec.decode(output_types, return_data)
|
|
93
|
+
except DecodingError as e:
|
|
94
|
+
msg = (
|
|
95
|
+
f"Could not decode contract function call to {function_identifier} "
|
|
96
|
+
f"with return data: {str(return_data)}, output_types: {output_types}"
|
|
97
|
+
)
|
|
98
|
+
raise BadFunctionCallOutput(msg) from e
|
|
99
|
+
|
|
100
|
+
_normalizers = itertools.chain(
|
|
101
|
+
BASE_RETURN_NORMALIZERS,
|
|
102
|
+
normalizers,
|
|
103
|
+
)
|
|
104
|
+
normalized_data = map_abi_data(_normalizers, output_types, output_data)
|
|
105
|
+
|
|
106
|
+
if decode_tuples:
|
|
107
|
+
decoded = named_tree(fn_abi["outputs"], normalized_data)
|
|
108
|
+
normalized_data = recursive_dict_to_namedtuple(decoded)
|
|
109
|
+
|
|
110
|
+
return normalized_data[0] if len(normalized_data) == 1 else normalized_data
|
|
111
|
+
|
|
112
|
+
|
|
67
113
|
def call_contract_function(
|
|
68
114
|
w3: "Web3",
|
|
69
115
|
address: ChecksumAddress,
|
|
@@ -108,6 +154,34 @@ def call_contract_function(
|
|
|
108
154
|
|
|
109
155
|
output_types = get_abi_output_types(fn_abi)
|
|
110
156
|
|
|
157
|
+
provider = w3.provider
|
|
158
|
+
if hasattr(provider, "_is_batching") and provider._is_batching:
|
|
159
|
+
# request_information == ((method, params), response_formatters)
|
|
160
|
+
request_information = tuple(return_data)
|
|
161
|
+
method_and_params = request_information[0]
|
|
162
|
+
|
|
163
|
+
# append return data formatting to result formatters
|
|
164
|
+
current_response_formatters = request_information[1]
|
|
165
|
+
current_result_formatters = current_response_formatters[0]
|
|
166
|
+
updated_result_formatters = compose(
|
|
167
|
+
# contract call return data formatter
|
|
168
|
+
format_contract_call_return_data_curried(
|
|
169
|
+
w3,
|
|
170
|
+
decode_tuples,
|
|
171
|
+
fn_abi,
|
|
172
|
+
function_identifier,
|
|
173
|
+
normalizers,
|
|
174
|
+
output_types,
|
|
175
|
+
),
|
|
176
|
+
current_result_formatters,
|
|
177
|
+
)
|
|
178
|
+
response_formatters = (
|
|
179
|
+
updated_result_formatters, # result formatters
|
|
180
|
+
current_response_formatters[1], # error formatters
|
|
181
|
+
current_response_formatters[2], # null result formatters
|
|
182
|
+
)
|
|
183
|
+
return (method_and_params, response_formatters)
|
|
184
|
+
|
|
111
185
|
try:
|
|
112
186
|
output_data = w3.codec.decode(output_types, return_data)
|
|
113
187
|
except DecodingError as e:
|
|
@@ -319,6 +393,43 @@ async def async_call_contract_function(
|
|
|
319
393
|
|
|
320
394
|
output_types = get_abi_output_types(fn_abi)
|
|
321
395
|
|
|
396
|
+
if async_w3.provider._is_batching:
|
|
397
|
+
contract_call_return_data_formatter = format_contract_call_return_data_curried(
|
|
398
|
+
async_w3,
|
|
399
|
+
decode_tuples,
|
|
400
|
+
fn_abi,
|
|
401
|
+
function_identifier,
|
|
402
|
+
normalizers,
|
|
403
|
+
output_types,
|
|
404
|
+
)
|
|
405
|
+
if async_w3.provider.has_persistent_connection:
|
|
406
|
+
# get the current request id
|
|
407
|
+
provider = cast("PersistentConnectionProvider", async_w3.provider)
|
|
408
|
+
current_request_id = provider._batch_request_counter - 1
|
|
409
|
+
provider._request_processor.append_result_formatter_for_request(
|
|
410
|
+
current_request_id, contract_call_return_data_formatter
|
|
411
|
+
)
|
|
412
|
+
else:
|
|
413
|
+
# request_information == ((method, params), response_formatters)
|
|
414
|
+
request_information = tuple(return_data)
|
|
415
|
+
method_and_params = request_information[0]
|
|
416
|
+
|
|
417
|
+
# append return data formatter to result formatters
|
|
418
|
+
current_response_formatters = request_information[1]
|
|
419
|
+
current_result_formatters = current_response_formatters[0]
|
|
420
|
+
updated_result_formatters = compose(
|
|
421
|
+
contract_call_return_data_formatter,
|
|
422
|
+
current_result_formatters,
|
|
423
|
+
)
|
|
424
|
+
response_formatters = (
|
|
425
|
+
updated_result_formatters, # result formatters
|
|
426
|
+
current_response_formatters[1], # error formatters
|
|
427
|
+
current_response_formatters[2], # null result formatters
|
|
428
|
+
)
|
|
429
|
+
return (method_and_params, response_formatters)
|
|
430
|
+
|
|
431
|
+
return return_data
|
|
432
|
+
|
|
322
433
|
try:
|
|
323
434
|
output_data = async_w3.codec.decode(output_types, return_data)
|
|
324
435
|
except DecodingError as e:
|
|
@@ -350,10 +461,7 @@ async def async_call_contract_function(
|
|
|
350
461
|
decoded = named_tree(fn_abi["outputs"], normalized_data)
|
|
351
462
|
normalized_data = recursive_dict_to_namedtuple(decoded)
|
|
352
463
|
|
|
353
|
-
if len(normalized_data) == 1
|
|
354
|
-
return normalized_data[0]
|
|
355
|
-
else:
|
|
356
|
-
return normalized_data
|
|
464
|
+
return normalized_data[0] if len(normalized_data) == 1 else normalized_data
|
|
357
465
|
|
|
358
466
|
|
|
359
467
|
async def async_transact_with_contract_function(
|
web3/eth/async_eth.py
CHANGED
|
@@ -35,6 +35,9 @@ from web3._utils.async_transactions import (
|
|
|
35
35
|
from web3._utils.blocks import (
|
|
36
36
|
select_method_for_block_identifier,
|
|
37
37
|
)
|
|
38
|
+
from web3._utils.compat import (
|
|
39
|
+
Unpack,
|
|
40
|
+
)
|
|
38
41
|
from web3._utils.fee_utils import (
|
|
39
42
|
async_fee_history_priority_fee,
|
|
40
43
|
)
|
|
@@ -594,10 +597,8 @@ class AsyncEth(BaseEth):
|
|
|
594
597
|
self.w3, current_transaction, new_transaction
|
|
595
598
|
)
|
|
596
599
|
|
|
597
|
-
# todo: Update Any to stricter kwarg checking with TxParams
|
|
598
|
-
# https://github.com/python/mypy/issues/4441
|
|
599
600
|
async def modify_transaction(
|
|
600
|
-
self, transaction_hash: _Hash32, **transaction_params:
|
|
601
|
+
self, transaction_hash: _Hash32, **transaction_params: Unpack[TxParams]
|
|
601
602
|
) -> HexBytes:
|
|
602
603
|
assert_valid_transaction_params(cast(TxParams, transaction_params))
|
|
603
604
|
|
|
@@ -765,7 +766,7 @@ class AsyncEth(BaseEth):
|
|
|
765
766
|
|
|
766
767
|
@overload
|
|
767
768
|
# mypy error: Overloaded function signatures 1 and 2 overlap with incompatible return types # noqa: E501
|
|
768
|
-
def contract(self, address: None = None, **kwargs: Any) -> Type[AsyncContract]: # type: ignore[
|
|
769
|
+
def contract(self, address: None = None, **kwargs: Any) -> Type[AsyncContract]: # type: ignore[overload-overlap] # noqa: E501
|
|
769
770
|
...
|
|
770
771
|
|
|
771
772
|
@overload
|