From 10a19be75f2e8708322b658478a0f0c5c653d714 Mon Sep 17 00:00:00 2001 From: fselmo <fselmo2@gmail.com> Date: Thu, 1 Aug 2024 14:19:10 -0600 Subject: [PATCH 1/9] After checking against EELS backend, minor test updates --- .../test_contract_build_transaction.py | 20 +++++++++---------- .../contracts/test_contract_estimate_gas.py | 5 ++--- .../core/utilities/test_async_transaction.py | 11 +++++----- 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/tests/core/contracts/test_contract_build_transaction.py b/tests/core/contracts/test_contract_build_transaction.py index a49d60effc..1cd480ab36 100644 --- a/tests/core/contracts/test_contract_build_transaction.py +++ b/tests/core/contracts/test_contract_build_transaction.py @@ -186,26 +186,26 @@ def test_build_transaction_with_contract_to_address_supplied_errors( False, ), ( # legacy transaction, explicit gasPrice - {"gasPrice": 22**8}, + {"gasPrice": 22 * 10**8}, (5,), {}, { "data": "0x6abbb3b40000000000000000000000000000000000000000000000000000000000000005", # noqa: E501 "value": 0, - "gasPrice": 22**8, + "gasPrice": 22 * 10**8, "chainId": 131277322940537, }, False, ), ( - {"maxFeePerGas": 22**8, "maxPriorityFeePerGas": 22**8}, + {"maxFeePerGas": 22 * 10**8, "maxPriorityFeePerGas": 22 * 10**8}, (5,), {}, { "data": "0x6abbb3b40000000000000000000000000000000000000000000000000000000000000005", # noqa: E501 "value": 0, - "maxFeePerGas": 22**8, - "maxPriorityFeePerGas": 22**8, + "maxFeePerGas": 22 * 10**8, + "maxPriorityFeePerGas": 22 * 10**8, "chainId": 131277322940537, }, False, @@ -467,26 +467,26 @@ async def test_async_build_transaction_with_contract_to_address_supplied_errors( False, ), ( # legacy transaction, explicit gasPrice - {"gasPrice": 22**8}, + {"gasPrice": 22 * 10**8}, (5,), {}, { "data": "0x6abbb3b40000000000000000000000000000000000000000000000000000000000000005", # noqa: E501 "value": 0, - "gasPrice": 22**8, + "gasPrice": 22 * 10**8, "chainId": 131277322940537, }, False, ), ( - {"maxFeePerGas": 22**8, "maxPriorityFeePerGas": 22**8}, + {"maxFeePerGas": 22 * 10**8, "maxPriorityFeePerGas": 22 * 10**8}, (5,), {}, { "data": "0x6abbb3b40000000000000000000000000000000000000000000000000000000000000005", # noqa: E501 "value": 0, - "maxFeePerGas": 22**8, - "maxPriorityFeePerGas": 22**8, + "maxFeePerGas": 22 * 10**8, + "maxPriorityFeePerGas": 22 * 10**8, "chainId": 131277322940537, }, False, diff --git a/tests/core/contracts/test_contract_estimate_gas.py b/tests/core/contracts/test_contract_estimate_gas.py index 3b8e784297..bf6209d7c4 100644 --- a/tests/core/contracts/test_contract_estimate_gas.py +++ b/tests/core/contracts/test_contract_estimate_gas.py @@ -87,10 +87,9 @@ def test_estimate_gas_sending_ether_to_nonpayable_function( def test_estimate_gas_accepts_latest_block(w3, math_contract, transact): - gas_estimate = math_contract.functions.counter().estimate_gas( + gas_estimate = math_contract.functions.incrementCounter().estimate_gas( block_identifier="latest" ) - txn_hash = transact(contract=math_contract, contract_function="incrementCounter") txn_receipt = w3.eth.wait_for_transaction_receipt(txn_hash) @@ -196,7 +195,7 @@ async def test_async_estimate_gas_sending_ether_to_nonpayable_function( async def test_async_estimate_gas_accepts_latest_block( async_w3, async_math_contract, async_transact ): - gas_estimate = await async_math_contract.functions.counter().estimate_gas( + gas_estimate = await async_math_contract.functions.incrementCounter().estimate_gas( block_identifier="latest" ) diff --git a/tests/core/utilities/test_async_transaction.py b/tests/core/utilities/test_async_transaction.py index 323f1b5190..779f6a8fc3 100644 --- a/tests/core/utilities/test_async_transaction.py +++ b/tests/core/utilities/test_async_transaction.py @@ -74,15 +74,14 @@ async def test_async_fill_transaction_defaults_for_all_params(async_w3): } -@pytest.mark.asyncio() -async def test_async_fill_transaction_defaults_nondynamic_transaction_fee(async_w3): - gasPrice_transaction = { - "gasPrice": 10, +@pytest.mark.asyncio +async def test_async_fill_transaction_defaults_non_dynamic_transaction_fee(async_w3): + gas_price_transaction = { + "gasPrice": 10**9, } default_transaction = await async_fill_transaction_defaults( - async_w3, gasPrice_transaction + async_w3, gas_price_transaction ) - assert none_in_dict(DYNAMIC_FEE_TXN_PARAMS, default_transaction) From f368a19e7f0b382eff1d3df77466e36ae0fe37c9 Mon Sep 17 00:00:00 2001 From: fselmo <fselmo2@gmail.com> Date: Fri, 2 Aug 2024 17:03:09 -0600 Subject: [PATCH 2/9] Implement EELS integration tests (almost all passing); temporarily turn on EELS backend for core tests (all passing). --- conftest.py | 10 +- tests/core/conftest.py | 9 +- .../test_contract_build_transaction.py | 38 ++--- tests/core/eth-module/test_eth_properties.py | 6 +- tests/core/module-class/test_module.py | 5 +- .../core/web3-module/test_web3_inheritance.py | 2 +- tests/integration/ethereum_tester/__init__.py | 0 .../common.py} | 2 +- tests/integration/ethereum_tester/conftest.py | 154 ++++++++++++++++++ .../integration/ethereum_tester/test_eels.py | 88 ++++++++++ .../integration/ethereum_tester/test_pyevm.py | 72 ++++++++ tox.ini | 5 +- web3/_utils/module_testing/eth_module.py | 4 +- web3/providers/eth_tester/defaults.py | 6 +- 14 files changed, 361 insertions(+), 40 deletions(-) create mode 100644 tests/integration/ethereum_tester/__init__.py rename tests/integration/{test_ethereum_tester.py => ethereum_tester/common.py} (99%) create mode 100644 tests/integration/ethereum_tester/conftest.py create mode 100644 tests/integration/ethereum_tester/test_eels.py create mode 100644 tests/integration/ethereum_tester/test_pyevm.py diff --git a/conftest.py b/conftest.py index 06cbbdcacc..0676201d24 100644 --- a/conftest.py +++ b/conftest.py @@ -2,6 +2,10 @@ import time import warnings +from eth_tester import ( + EELSBackend, + EthereumTester, +) import pytest_asyncio from tests.utils import ( @@ -72,14 +76,16 @@ def _wait_for_transaction(w3, txn_hash, timeout=120): @pytest.fixture def w3(): - w3 = Web3(EthereumTesterProvider()) + t = EthereumTester(backend=EELSBackend("cancun")) + w3 = Web3(EthereumTesterProvider(t)) w3.eth.default_account = w3.eth.accounts[0] return w3 @pytest.fixture(scope="module") def w3_non_strict_abi(): - w3 = Web3(EthereumTesterProvider()) + t = EthereumTester(backend=EELSBackend("cancun")) + w3 = Web3(EthereumTesterProvider(t)) w3.eth.default_account = w3.eth.accounts[0] w3.strict_bytes_type_checking = False return w3 diff --git a/tests/core/conftest.py b/tests/core/conftest.py index 4210a1245c..1dc712def0 100644 --- a/tests/core/conftest.py +++ b/tests/core/conftest.py @@ -1,5 +1,9 @@ import pytest +from eth_tester import ( + EELSBackend, + EthereumTester, +) import pytest_asyncio from web3 import ( @@ -118,7 +122,10 @@ def __init__(self, a, b): @pytest_asyncio.fixture async def async_w3(): - w3 = AsyncWeb3(AsyncEthereumTesterProvider()) + t = EthereumTester(backend=EELSBackend("cancun")) + provider = AsyncEthereumTesterProvider() + provider.ethereum_tester = t + w3 = AsyncWeb3(provider) accounts = await w3.eth.accounts w3.eth.default_account = accounts[0] return w3 diff --git a/tests/core/contracts/test_contract_build_transaction.py b/tests/core/contracts/test_contract_build_transaction.py index 1cd480ab36..87f8dcba02 100644 --- a/tests/core/contracts/test_contract_build_transaction.py +++ b/tests/core/contracts/test_contract_build_transaction.py @@ -22,7 +22,7 @@ def test_build_transaction_not_paying_to_nonpayable_function( "value": 0, "maxFeePerGas": 2750000000, "maxPriorityFeePerGas": 10**9, - "chainId": 131277322940537, + "chainId": w3.eth.chain_id, } @@ -49,7 +49,7 @@ def test_build_transaction_with_contract_no_arguments( "value": 0, "maxFeePerGas": 2750000000, "maxPriorityFeePerGas": 10**9, - "chainId": 131277322940537, + "chainId": w3.eth.chain_id, } @@ -77,7 +77,7 @@ def test_build_transaction_with_contract_fallback_function( "value": 0, "maxFeePerGas": 2750000000, "maxPriorityFeePerGas": 10**9, - "chainId": 131277322940537, + "chainId": w3.eth.chain_id, } @@ -95,7 +95,7 @@ def test_build_transaction_with_contract_class_method( "value": 0, "maxFeePerGas": 2750000000, "maxPriorityFeePerGas": 10**9, - "chainId": 131277322940537, + "chainId": w3.eth.chain_id, } @@ -111,7 +111,7 @@ def test_build_transaction_with_contract_default_account_is_set( "value": 0, "maxFeePerGas": 2750000000, "maxPriorityFeePerGas": 10**9, - "chainId": 131277322940537, + "chainId": w3.eth.chain_id, } @@ -130,7 +130,7 @@ def my_gas_price_strategy(w3, transaction_params): "data": "0x5b34b966", "value": 0, "gasPrice": 5, - "chainId": 131277322940537, + "chainId": w3.eth.chain_id, } @@ -168,7 +168,6 @@ def test_build_transaction_with_contract_to_address_supplied_errors( "value": 0, "maxFeePerGas": 2750000000, "maxPriorityFeePerGas": 1000000000, - "chainId": 131277322940537, }, False, ), @@ -181,7 +180,6 @@ def test_build_transaction_with_contract_to_address_supplied_errors( "value": 0, "maxFeePerGas": 2750000000, "maxPriorityFeePerGas": 1000000000, - "chainId": 131277322940537, }, False, ), @@ -193,7 +191,6 @@ def test_build_transaction_with_contract_to_address_supplied_errors( "data": "0x6abbb3b40000000000000000000000000000000000000000000000000000000000000005", # noqa: E501 "value": 0, "gasPrice": 22 * 10**8, - "chainId": 131277322940537, }, False, ), @@ -206,7 +203,6 @@ def test_build_transaction_with_contract_to_address_supplied_errors( "value": 0, "maxFeePerGas": 22 * 10**8, "maxPriorityFeePerGas": 22 * 10**8, - "chainId": 131277322940537, }, False, ), @@ -220,7 +216,6 @@ def test_build_transaction_with_contract_to_address_supplied_errors( "maxFeePerGas": 2750000000, "maxPriorityFeePerGas": 1000000000, "nonce": 7, - "chainId": 131277322940537, }, True, ), @@ -233,7 +228,6 @@ def test_build_transaction_with_contract_to_address_supplied_errors( "value": 20000, "maxFeePerGas": 2750000000, "maxPriorityFeePerGas": 1000000000, - "chainId": 131277322940537, }, False, ), @@ -261,6 +255,7 @@ def test_build_transaction_with_contract_arguments( if skip_testrpc: skip_if_testrpc(w3) + expected["chainId"] = w3.eth.chain_id txn = build_transaction( contract=math_contract, contract_function="incrementCounter", @@ -290,7 +285,7 @@ async def test_async_build_transaction_not_paying_to_nonpayable_function( "value": 0, "maxFeePerGas": 2750000000, "maxPriorityFeePerGas": 10**9, - "chainId": 131277322940537, + "chainId": await async_w3.eth.chain_id, } @@ -319,7 +314,7 @@ async def test_async_build_transaction_with_contract_no_arguments( "value": 0, "maxFeePerGas": 2750000000, "maxPriorityFeePerGas": 10**9, - "chainId": 131277322940537, + "chainId": await async_w3.eth.chain_id, } @@ -349,7 +344,7 @@ async def test_async_build_transaction_with_contract_fallback_function( "value": 0, "maxFeePerGas": 2750000000, "maxPriorityFeePerGas": 10**9, - "chainId": 131277322940537, + "chainId": await async_w3.eth.chain_id, } @@ -371,7 +366,7 @@ async def test_async_build_transaction_with_contract_class_method( "value": 0, "maxFeePerGas": 2750000000, "maxPriorityFeePerGas": 10**9, - "chainId": 131277322940537, + "chainId": await async_w3.eth.chain_id, } @@ -388,7 +383,7 @@ async def test_async_build_transaction_with_contract_default_account_is_set( "value": 0, "maxFeePerGas": 2750000000, "maxPriorityFeePerGas": 10**9, - "chainId": 131277322940537, + "chainId": await async_w3.eth.chain_id, } @@ -408,7 +403,7 @@ def my_gas_price_strategy(async_w3, transaction_params): "data": "0x5b34b966", "value": 0, "gasPrice": 5, - "chainId": 131277322940537, + "chainId": await async_w3.eth.chain_id, } @@ -449,7 +444,6 @@ async def test_async_build_transaction_with_contract_to_address_supplied_errors( "value": 0, "maxFeePerGas": 2750000000, "maxPriorityFeePerGas": 1000000000, - "chainId": 131277322940537, }, False, ), @@ -462,7 +456,6 @@ async def test_async_build_transaction_with_contract_to_address_supplied_errors( "value": 0, "maxFeePerGas": 2750000000, "maxPriorityFeePerGas": 1000000000, - "chainId": 131277322940537, }, False, ), @@ -474,7 +467,6 @@ async def test_async_build_transaction_with_contract_to_address_supplied_errors( "data": "0x6abbb3b40000000000000000000000000000000000000000000000000000000000000005", # noqa: E501 "value": 0, "gasPrice": 22 * 10**8, - "chainId": 131277322940537, }, False, ), @@ -487,7 +479,6 @@ async def test_async_build_transaction_with_contract_to_address_supplied_errors( "value": 0, "maxFeePerGas": 22 * 10**8, "maxPriorityFeePerGas": 22 * 10**8, - "chainId": 131277322940537, }, False, ), @@ -501,7 +492,6 @@ async def test_async_build_transaction_with_contract_to_address_supplied_errors( "maxFeePerGas": 2750000000, "maxPriorityFeePerGas": 1000000000, "nonce": 7, - "chainId": 131277322940537, }, True, ), @@ -514,7 +504,6 @@ async def test_async_build_transaction_with_contract_to_address_supplied_errors( "value": 20000, "maxFeePerGas": 2750000000, "maxPriorityFeePerGas": 1000000000, - "chainId": 131277322940537, }, False, ), @@ -542,6 +531,7 @@ async def test_async_build_transaction_with_contract_with_arguments( if skip_testrpc: async_skip_if_testrpc(async_w3) + expected["chainId"] = await async_w3.eth.chain_id txn = await async_build_transaction( contract=async_math_contract, contract_function="incrementCounter", diff --git a/tests/core/eth-module/test_eth_properties.py b/tests/core/eth-module/test_eth_properties.py index 39333d6045..ac789d4dd4 100644 --- a/tests/core/eth-module/test_eth_properties.py +++ b/tests/core/eth-module/test_eth_properties.py @@ -16,11 +16,9 @@ def async_w3(): def test_eth_chain_id(w3): - assert w3.eth.chain_id == 131277322940537 # from fixture generation file + assert w3.eth.chain_id == w3.provider.eth_tester.chain_id @pytest.mark.asyncio async def test_async_eth_chain_id(async_w3): - assert ( - await async_w3.eth.chain_id == 131277322940537 - ) # from fixture generation file + assert await async_w3.eth.chain_id == async_w3.provider.eth_tester.chain_id diff --git a/tests/core/module-class/test_module.py b/tests/core/module-class/test_module.py index 8fe97b7d7f..d9bb35cdb5 100644 --- a/tests/core/module-class/test_module.py +++ b/tests/core/module-class/test_module.py @@ -37,8 +37,9 @@ def test_attach_methods_to_module(web3_with_external_modules): } ) - assert w3.eth.chain_id == 131277322940537 - assert w3.module1.property1 == 131277322940537 + configured_chain_id = w3.provider.eth_tester.chain_id + assert w3.eth.chain_id == configured_chain_id + assert w3.module1.property1 == configured_chain_id account = w3.eth.accounts[0] assert w3.eth.get_balance(account, "latest") == 1000000000000000000000000 diff --git a/tests/core/web3-module/test_web3_inheritance.py b/tests/core/web3-module/test_web3_inheritance.py index 8201a9ed8b..9de5a2f5b9 100644 --- a/tests/core/web3-module/test_web3_inheritance.py +++ b/tests/core/web3-module/test_web3_inheritance.py @@ -9,4 +9,4 @@ class InheritsFromWeb3(Web3): pass inherited_w3 = InheritsFromWeb3(EthereumTesterProvider()) - assert inherited_w3.eth.chain_id == 131277322940537 + assert inherited_w3.eth.chain_id == inherited_w3.provider.eth_tester.chain_id diff --git a/tests/integration/ethereum_tester/__init__.py b/tests/integration/ethereum_tester/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/integration/test_ethereum_tester.py b/tests/integration/ethereum_tester/common.py similarity index 99% rename from tests/integration/test_ethereum_tester.py rename to tests/integration/ethereum_tester/common.py index 153c66cfbb..86030fc5d5 100644 --- a/tests/integration/test_ethereum_tester.py +++ b/tests/integration/ethereum_tester/common.py @@ -476,7 +476,7 @@ def test_eth_call_old_contract_state( def test_eth_chain_id(self, w3): chain_id = w3.eth.chain_id assert is_integer(chain_id) - assert chain_id == 131277322940537 + assert chain_id == w3.provider.eth_tester.chain_id @disable_auto_mine def test_eth_wait_for_transaction_receipt_unmined( diff --git a/tests/integration/ethereum_tester/conftest.py b/tests/integration/ethereum_tester/conftest.py new file mode 100644 index 0000000000..c0ba0ea943 --- /dev/null +++ b/tests/integration/ethereum_tester/conftest.py @@ -0,0 +1,154 @@ +import pytest + +from eth_typing import ( + ChecksumAddress, +) +from eth_utils import ( + is_checksum_address, + is_dict, +) + +from web3._utils.contract_sources.contract_data._custom_contract_data import ( + EMITTER_ENUM, +) +from web3._utils.contract_sources.contract_data.panic_errors_contract import ( + PANIC_ERRORS_CONTRACT_DATA, +) +from web3._utils.contract_sources.contract_data.storage_contract import ( + STORAGE_CONTRACT_DATA, +) + +# set up the keyfile account with a known address (same from geth setup) +KEYFILE_ACCOUNT_PKEY = ( + "0x58d23b55bc9cdce1f18c2500f40ff4ab7245df9a89505e9b1fa4851f623d241d" +) +KEYFILE_ACCOUNT_ADDRESS = "0xdC544d1AA88Ff8bbd2F2AeC754B1F1e99e1812fd" + + +def _deploy_contract(w3, contract_factory): + deploy_txn_hash = contract_factory.constructor().transact( + {"from": w3.eth.default_account} + ) + deploy_receipt = w3.eth.wait_for_transaction_receipt(deploy_txn_hash) + assert is_dict(deploy_receipt) + contract_address = deploy_receipt["contractAddress"] + assert is_checksum_address(contract_address) + return contract_factory(contract_address) + + +@pytest.fixture(scope="module") +def keyfile_account_pkey(): + yield KEYFILE_ACCOUNT_PKEY + + +@pytest.fixture(scope="module") +def keyfile_account_address(): + yield KEYFILE_ACCOUNT_ADDRESS + + +@pytest.fixture(scope="module") +def math_contract_deploy_txn_hash(w3, math_contract_factory): + deploy_txn_hash = math_contract_factory.constructor().transact( + {"from": w3.eth.default_account} + ) + return deploy_txn_hash + + +@pytest.fixture(scope="module") +def math_contract(w3, math_contract_factory, math_contract_deploy_txn_hash): + deploy_receipt = w3.eth.wait_for_transaction_receipt(math_contract_deploy_txn_hash) + assert is_dict(deploy_receipt) + contract_address = deploy_receipt["contractAddress"] + assert is_checksum_address(contract_address) + return math_contract_factory(contract_address) + + +@pytest.fixture(scope="module") +def math_contract_address(math_contract, address_conversion_func): + return address_conversion_func(math_contract.address) + + +@pytest.fixture(scope="module") +def storage_contract(w3): + contract_factory = w3.eth.contract(**STORAGE_CONTRACT_DATA) + return _deploy_contract(w3, contract_factory) + + +@pytest.fixture(scope="module") +def emitter_contract(w3, emitter_contract_factory): + return _deploy_contract(w3, emitter_contract_factory) + + +@pytest.fixture(scope="module") +def emitter_contract_address(emitter_contract, address_conversion_func): + return address_conversion_func(emitter_contract.address) + + +@pytest.fixture(scope="module") +def empty_block(w3): + w3.testing.mine() + block = w3.eth.get_block("latest") + assert not block["transactions"] + return block + + +@pytest.fixture(scope="module") +def block_with_txn(w3): + txn_hash = w3.eth.send_transaction( + { + "from": ChecksumAddress(w3.eth.default_account), + "to": ChecksumAddress(w3.eth.default_account), + "value": w3.to_wei(1, "gwei"), + "gas": 21000, + "gasPrice": w3.to_wei(1, "gwei"), # needs to be > base_fee post London + } + ) + txn = w3.eth.get_transaction(txn_hash) + block = w3.eth.get_block(txn["blockNumber"]) + return block + + +@pytest.fixture(scope="module") +def mined_txn_hash(block_with_txn): + return block_with_txn["transactions"][0] + + +@pytest.fixture(scope="module") +def block_with_txn_with_log(w3, emitter_contract): + txn_hash = emitter_contract.functions.logDouble( + which=EMITTER_ENUM["LogDoubleWithIndex"], + arg0=12345, + arg1=54321, + ).transact({"from": w3.eth.default_account}) + txn = w3.eth.get_transaction(txn_hash) + block = w3.eth.get_block(txn["blockNumber"]) + return block + + +@pytest.fixture(scope="module") +def txn_hash_with_log(block_with_txn_with_log): + return block_with_txn_with_log["transactions"][0] + + +@pytest.fixture(scope="module") +def revert_contract(w3, revert_contract_factory): + return _deploy_contract(w3, revert_contract_factory) + + +# +# Offchain Lookup Contract Setup +# +@pytest.fixture(scope="module") +def offchain_lookup_contract(w3, offchain_lookup_contract_factory): + return _deploy_contract(w3, offchain_lookup_contract_factory) + + +@pytest.fixture(scope="module") +def panic_errors_contract(w3): + panic_errors_contract_factory = w3.eth.contract(**PANIC_ERRORS_CONTRACT_DATA) + return _deploy_contract(w3, panic_errors_contract_factory) + + +@pytest.fixture +def keyfile_account_address_dual_type(keyfile_account_address, address_conversion_func): + yield keyfile_account_address diff --git a/tests/integration/ethereum_tester/test_eels.py b/tests/integration/ethereum_tester/test_eels.py new file mode 100644 index 0000000000..21f9265163 --- /dev/null +++ b/tests/integration/ethereum_tester/test_eels.py @@ -0,0 +1,88 @@ +import pytest +from typing import ( + cast, +) + +from eth_tester import ( + EELSBackend, + EthereumTester, +) +from eth_typing import ( + ChecksumAddress, +) + +from web3 import ( + Web3, +) +from web3.providers.eth_tester import ( + EthereumTesterProvider, +) +from web3.types import ( # noqa: F401 + BlockData, +) + +from .common import ( + EthereumTesterEthModule, + EthereumTesterNetModule, + EthereumTesterWeb3Module, +) + + +def _eth_tester_state_setup(w3, keyfile_account_address, keyfile_account_pkey): + provider = cast(EthereumTesterProvider, w3.provider) + provider.ethereum_tester.add_account(keyfile_account_pkey) + + # fund the account + w3.eth.send_transaction( + { + "from": ChecksumAddress(w3.eth.default_account), + "to": keyfile_account_address, + "value": w3.to_wei(0.5, "ether"), + "gas": 21000, + "gasPrice": 10**9, # needs to be > base_fee post London + } + ) + + +@pytest.fixture(scope="module") +def eth_tester(): + return EthereumTester(backend=EELSBackend(debug_mode=True)) + + +@pytest.fixture(scope="module") +def w3(eth_tester, keyfile_account_address, keyfile_account_pkey): + _w3 = Web3(EthereumTesterProvider(eth_tester)) + _w3.eth.default_account = _w3.eth.accounts[0] + _eth_tester_state_setup(_w3, keyfile_account_address, keyfile_account_pkey) + return _w3 + + +# -- test classes -- # + + +class TestEthereumTesterWeb3Module(EthereumTesterWeb3Module): + pass + + +class TestEthereumTesterEthModule(EthereumTesterEthModule): + def test_eth_chain_id(self, w3): + chain_id = w3.eth.chain_id + assert chain_id == 1 + + @pytest.mark.xfail(reason="EELS backed does not yet support eth_feeHistory") + def test_eth_fee_history(self, w3: "Web3") -> None: + super().test_eth_fee_history(w3) + + @pytest.mark.xfail(reason="EELS backed does not yet support eth_feeHistory") + def test_eth_fee_history_with_integer( + self, w3: "Web3", empty_block: BlockData + ) -> None: + super().test_eth_fee_history_with_integer(w3, empty_block) + + @pytest.mark.xfail(reason="EELS backed does not yet support eth_feeHistory") + def test_eth_fee_history_no_reward_percentiles(self, w3: "Web3") -> None: + super().test_eth_fee_history_no_reward_percentiles(w3) + + +class TestEthereumTesterNetModule(EthereumTesterNetModule): + pass diff --git a/tests/integration/ethereum_tester/test_pyevm.py b/tests/integration/ethereum_tester/test_pyevm.py new file mode 100644 index 0000000000..b3b70167c5 --- /dev/null +++ b/tests/integration/ethereum_tester/test_pyevm.py @@ -0,0 +1,72 @@ +import pytest +from typing import ( + cast, +) + +from eth_tester import ( + EthereumTester, + PyEVMBackend, +) +from eth_typing import ( + ChecksumAddress, +) + +from web3 import ( + Web3, +) +from web3.providers.eth_tester import ( + EthereumTesterProvider, +) +from web3.types import ( # noqa: F401 + BlockData, +) + +from .common import ( + EthereumTesterEthModule, + EthereumTesterNetModule, + EthereumTesterWeb3Module, +) + + +def _eth_tester_state_setup(w3, keyfile_account_address, keyfile_account_pkey): + provider = cast(EthereumTesterProvider, w3.provider) + provider.ethereum_tester.add_account(keyfile_account_pkey) + + # fund the account + w3.eth.send_transaction( + { + "from": ChecksumAddress(w3.eth.default_account), + "to": keyfile_account_address, + "value": w3.to_wei(0.5, "ether"), + "gas": 21000, + "gasPrice": 10**9, # needs to be > base_fee post London + } + ) + + +@pytest.fixture(scope="module") +def eth_tester(): + return EthereumTester(backend=PyEVMBackend()) + + +@pytest.fixture(scope="module") +def w3(eth_tester, keyfile_account_address, keyfile_account_pkey): + _w3 = Web3(EthereumTesterProvider(eth_tester)) + _w3.eth.default_account = _w3.eth.accounts[0] + _eth_tester_state_setup(_w3, keyfile_account_address, keyfile_account_pkey) + return _w3 + + +# -- test classes -- # + + +class TestEthereumTesterWeb3Module(EthereumTesterWeb3Module): + pass + + +class TestEthereumTesterEthModule(EthereumTesterEthModule): + pass + + +class TestEthereumTesterNetModule(EthereumTesterNetModule): + pass diff --git a/tox.ini b/tox.ini index cc74b5e199..b77b9d16aa 100644 --- a/tox.ini +++ b/tox.ini @@ -20,7 +20,7 @@ allowlist_externals=make,pre-commit install_command=python -m pip install {opts} {packages} usedevelop=True commands= - core: pytest {posargs:tests/core -m "not asyncio"} + core: pytest {posargs:tests/core -m "not asyncio" } core_async: pytest {posargs:tests/core -m asyncio} ens: pytest {posargs:tests/ens --ignore=tests/ens/normalization/test_normalize_name_ensip15.py -n auto --maxprocesses=15} ensip15: pytest {posargs:tests/ens/normalization/test_normalize_name_ensip15.py -q -n auto --maxprocesses=15} @@ -30,7 +30,8 @@ commands= integration-goethereum-http_async: pytest {posargs:tests/integration/go_ethereum/test_goethereum_http.py -k Async -n auto --maxprocesses=15} integration-goethereum-legacy_ws: pytest {posargs:tests/integration/go_ethereum/test_goethereum_legacy_ws.py -n auto --maxprocesses=15} integration-goethereum-ws: pytest {posargs:tests/integration/go_ethereum/test_goethereum_ws -n auto --maxprocesses=15} - integration-ethtester: pytest {posargs:tests/integration/test_ethereum_tester.py -n auto --maxprocesses=15} + integration-ethtester-pyevm: pytest {posargs:tests/integration/ethereum_tester/test_pyevm.py -n auto --maxprocesses=15} + integration-ethtester-eels: pytest {posargs:tests/integration/ethereum_tester/test_eels.py -n auto --maxprocesses=15} docs: make check-docs-ci deps = .[test] diff --git a/web3/_utils/module_testing/eth_module.py b/web3/_utils/module_testing/eth_module.py index e9614cf7b6..b617907d5d 100644 --- a/web3/_utils/module_testing/eth_module.py +++ b/web3/_utils/module_testing/eth_module.py @@ -3348,7 +3348,7 @@ def test_eth_send_transaction_no_gas( "from": keyfile_account_address_dual_type, "to": keyfile_account_address_dual_type, "value": Wei(1), - "maxFeePerGas": Wei(250 * 10**9), + "maxFeePerGas": Wei(2 * 10**9), "maxPriorityFeePerGas": Wei(2 * 10**9), } txn_hash = w3.eth.send_transaction(txn_params) @@ -3836,7 +3836,7 @@ def test_eth_send_raw_transaction( ) -> None: keyfile_account = w3.eth.account.from_key(keyfile_account_pkey) txn = { - "chainId": 131277322940537, # the chainId set for the fixture + "chainId": w3.eth.chain_id, "from": keyfile_account.address, "to": keyfile_account.address, "value": Wei(0), diff --git a/web3/providers/eth_tester/defaults.py b/web3/providers/eth_tester/defaults.py index 57985a7053..b6197c0c37 100644 --- a/web3/providers/eth_tester/defaults.py +++ b/web3/providers/eth_tester/defaults.py @@ -243,7 +243,7 @@ def create_new_account(eth_tester: "EthereumTester") -> HexAddress: "eth": { "protocolVersion": static_return(63), "syncing": static_return(False), - "chainId": static_return(131277322940537), # from fixture generation file + "chainId": call_eth_tester("chain_id"), "feeHistory": call_eth_tester("get_fee_history"), "maxPriorityFeePerGas": static_return(10**9), "blobBaseFee": static_return(10**9), @@ -302,6 +302,10 @@ def create_new_account(eth_tester: "EthereumTester") -> HexAddress: "getTransactionByHash": null_if_transaction_not_found( call_eth_tester("get_transaction_by_hash") ), + "eth_getBlockTransactionCountByHash": not_implemented, + "eth_getBlockTransactionCountByNumber": not_implemented, + "eth_getRawTransactionByBlockNumberAndIndex": not_implemented, + "eth_getRawTransactionByBlockHashAndIndex": not_implemented, "getTransactionByBlockHashAndIndex": get_transaction_by_block_hash_and_index, "getTransactionByBlockNumberAndIndex": get_transaction_by_block_number_and_index, # noqa: E501 "getTransactionReceipt": null_if_transaction_not_found( From 24a5ec5fe2135747af4b18ce21b1e868d251f05b Mon Sep 17 00:00:00 2001 From: fselmo <fselmo2@gmail.com> Date: Mon, 5 Aug 2024 15:45:56 -0600 Subject: [PATCH 3/9] Updates to test suite after running for EELS backend --- conftest.py | 11 ++--- setup.py | 6 ++- tests/conftest.py | 46 +++++++++++++++++++ tests/core/conftest.py | 12 ++--- .../contracts/test_contract_attributes.py | 2 +- .../test_contract_caller_interface.py | 4 +- tests/core/contracts/test_contract_example.py | 13 ++++-- tests/core/contracts/test_contract_init.py | 2 +- .../contracts/test_extracting_event_data.py | 2 +- .../test_extracting_event_data_old.py | 22 +++++++++ tests/core/eth-module/test_accounts.py | 17 ++++--- tests/core/eth-module/test_eth_filter.py | 13 ++++-- tests/core/eth-module/test_eth_properties.py | 15 ++++-- tests/core/filtering/conftest.py | 8 ++-- .../filtering/test_contract_data_filters.py | 8 ++-- .../filtering/test_contract_topic_filters.py | 8 ++-- .../test_existing_filter_instance.py | 2 +- tests/core/filtering/utils.py | 16 ++++--- .../test_name_to_address_middleware.py | 10 +++- .../middleware/test_transaction_signing.py | 20 ++++---- tests/core/module-class/test_module.py | 2 +- tests/core/utilities/conftest.py | 9 ++-- tests/core/utilities/test_abi.py | 2 +- .../core/web3-module/test_web3_inheritance.py | 5 +- tests/ens/conftest.py | 19 +++++--- tests/ens/test_get_text.py | 30 ++++++++---- tests/ens/test_setup_name.py | 4 +- tests/integration/ethereum_tester/common.py | 5 -- .../integration/ethereum_tester/test_eels.py | 2 +- .../integration/ethereum_tester/test_pyevm.py | 4 +- .../test_goethereum_ws/conftest.py | 17 +++++++ tox.ini | 13 ++++-- web3/providers/eth_tester/main.py | 30 ++++++++---- 33 files changed, 264 insertions(+), 115 deletions(-) diff --git a/conftest.py b/conftest.py index 0676201d24..c3d8e7d64f 100644 --- a/conftest.py +++ b/conftest.py @@ -3,7 +3,6 @@ import warnings from eth_tester import ( - EELSBackend, EthereumTester, ) import pytest_asyncio @@ -75,17 +74,15 @@ def _wait_for_transaction(w3, txn_hash, timeout=120): @pytest.fixture -def w3(): - t = EthereumTester(backend=EELSBackend("cancun")) - w3 = Web3(EthereumTesterProvider(t)) +def w3(backend_class): + w3 = Web3(EthereumTesterProvider(EthereumTester(backend=backend_class()))) w3.eth.default_account = w3.eth.accounts[0] return w3 @pytest.fixture(scope="module") -def w3_non_strict_abi(): - t = EthereumTester(backend=EELSBackend("cancun")) - w3 = Web3(EthereumTesterProvider(t)) +def w3_non_strict_abi(backend_class): + w3 = Web3(EthereumTesterProvider(EthereumTester(backend=backend_class()))) w3.eth.default_account = w3.eth.accounts[0] w3.strict_bytes_type_checking = False return w3 diff --git a/setup.py b/setup.py index 4b3f0a722a..a8b7bfdcc1 100644 --- a/setup.py +++ b/setup.py @@ -4,11 +4,15 @@ setup, ) +CUSTOM_ETH_TESTER_BRANCH = " @ git+https://github.com/fselmo/eth-tester@eels-backend" + extras_require = { "tester": [ # Note: ethereum-maintained libraries in this list should be added to the # `install_pre_releases.py` script. - "eth-tester[py-evm]>=0.13.0b1,<0.14.0b1", + f"eth-tester[py-evm]{CUSTOM_ETH_TESTER_BRANCH}", + # if python version >= 3.10, install the eels backend: + f"eth-tester[eels]{CUSTOM_ETH_TESTER_BRANCH} ; python_version >= '3.10'", "py-geth>=5.1.0", ], "dev": [ diff --git a/tests/conftest.py b/tests/conftest.py index 66ea6dd086..9b72b0b074 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,6 +3,9 @@ Type, ) +from eth_tester import ( + PyEVMBackend, +) from eth_utils import ( event_signature_to_log_topic, to_bytes, @@ -19,6 +22,8 @@ RequestMocker, ) +SUPPORTED_ETH_TESTER_BACKENDS = {"pyevm", "eels"} + @pytest.fixture(scope="module", params=[lambda x: to_bytes(hexstr=x), identity]) def address_conversion_func(request): @@ -28,6 +33,47 @@ def address_conversion_func(request): # --- session-scoped constants --- # +def pytest_addoption(parser): + parser.addoption( + "--backend", + action="store", + default=None, + help="Specify the backend for `EthereumTester` to use.", + ) + + +def pytest_collection_modifyitems(config, items): + backend_required_for_tests = any( + "backend_class" in item.fixturenames for item in items + ) + if backend_required_for_tests: + backend = config.getoption("--backend") + if not backend: + raise pytest.UsageError( + "This test run requires a specified a backend via the `--backend` " + "command line option. Supported backends are: " + f"{SUPPORTED_ETH_TESTER_BACKENDS}" + ) + elif backend not in SUPPORTED_ETH_TESTER_BACKENDS: + raise pytest.UsageError(f"Unsupported backend: `{backend}`.") + + +@pytest.fixture(scope="session") +def backend_class(request): + backend = request.config.getoption("--backend") + if backend == "pyevm": + return PyEVMBackend + elif backend == "eels": + # conditionally import since eels is only supported on python >= 3.10 + from eth_tester.backends.eels import ( + EELSBackend, + ) + + return EELSBackend + else: + raise ValueError("Invariant: Unreachable code path.") + + @pytest.fixture(scope="session") def emitter_contract_data(): return EMITTER_CONTRACT_DATA diff --git a/tests/core/conftest.py b/tests/core/conftest.py index 1dc712def0..8497aa1a03 100644 --- a/tests/core/conftest.py +++ b/tests/core/conftest.py @@ -1,7 +1,6 @@ import pytest from eth_tester import ( - EELSBackend, EthereumTester, ) import pytest_asyncio @@ -121,19 +120,16 @@ def __init__(self, a, b): @pytest_asyncio.fixture -async def async_w3(): - t = EthereumTester(backend=EELSBackend("cancun")) - provider = AsyncEthereumTesterProvider() - provider.ethereum_tester = t - w3 = AsyncWeb3(provider) +async def async_w3(backend_class): + w3 = AsyncWeb3(AsyncEthereumTesterProvider(EthereumTester(backend=backend_class()))) accounts = await w3.eth.accounts w3.eth.default_account = accounts[0] return w3 @pytest_asyncio.fixture -async def async_w3_non_strict_abi(): - w3 = AsyncWeb3(AsyncEthereumTesterProvider()) +async def async_w3_non_strict_abi(backend_class): + w3 = AsyncWeb3(AsyncEthereumTesterProvider(EthereumTester(backend=backend_class()))) w3.strict_bytes_type_checking = False accounts = await w3.eth.accounts w3.eth.default_account = accounts[0] diff --git a/tests/core/contracts/test_contract_attributes.py b/tests/core/contracts/test_contract_attributes.py index 53fc296229..79275eb695 100644 --- a/tests/core/contracts/test_contract_attributes.py +++ b/tests/core/contracts/test_contract_attributes.py @@ -6,7 +6,7 @@ ) -@pytest.fixture() +@pytest.fixture def abi(): return """[{"anonymous":false,"inputs":[{"indexed":false,"name":"value","type":"uint256"}],"name":"Increased","type":"function"}, {"anonymous":false,"inputs":[{"indexed":false,"name":"value","type":"uint256"}],"name":"Increased","type":"event"}]""" # noqa: E501 diff --git a/tests/core/contracts/test_contract_caller_interface.py b/tests/core/contracts/test_contract_caller_interface.py index 0c3df9ce90..81b85972eb 100644 --- a/tests/core/contracts/test_contract_caller_interface.py +++ b/tests/core/contracts/test_contract_caller_interface.py @@ -8,12 +8,12 @@ ) -@pytest.fixture() +@pytest.fixture def address(w3): return w3.eth.accounts[1] -@pytest.fixture() +@pytest.fixture def transaction_dict(w3, address): return { "from": address, diff --git a/tests/core/contracts/test_contract_example.py b/tests/core/contracts/test_contract_example.py index e47c9352d9..9eb9a1e7de 100644 --- a/tests/core/contracts/test_contract_example.py +++ b/tests/core/contracts/test_contract_example.py @@ -2,6 +2,9 @@ # of how to write unit tests with web3.py import pytest +from eth_tester import ( + EthereumTester, +) import pytest_asyncio from web3 import ( @@ -15,8 +18,8 @@ @pytest.fixture -def tester_provider(): - return EthereumTesterProvider() +def tester_provider(backend_class): + return EthereumTesterProvider(EthereumTester(backend=backend_class())) @pytest.fixture @@ -118,8 +121,10 @@ def async_eth_tester(): @pytest_asyncio.fixture() -async def async_w3(): - async_w3 = AsyncWeb3(AsyncEthereumTesterProvider()) +async def async_w3(backend_class): + async_w3 = AsyncWeb3( + AsyncEthereumTesterProvider(EthereumTester(backend=backend_class())) + ) accounts = await async_w3.eth.accounts async_w3.eth.default_account = accounts[0] return async_w3 diff --git a/tests/core/contracts/test_contract_init.py b/tests/core/contracts/test_contract_init.py index 7d6dc3f700..e0beacd74c 100644 --- a/tests/core/contracts/test_contract_init.py +++ b/tests/core/contracts/test_contract_init.py @@ -11,7 +11,7 @@ ) -@pytest.fixture() +@pytest.fixture def math_addr(math_contract_factory, address_conversion_func): w3 = math_contract_factory.w3 deploy_txn = math_contract_factory.constructor().transact( diff --git a/tests/core/contracts/test_extracting_event_data.py b/tests/core/contracts/test_extracting_event_data.py index 30fcea0c64..d61f890308 100644 --- a/tests/core/contracts/test_extracting_event_data.py +++ b/tests/core/contracts/test_extracting_event_data.py @@ -28,7 +28,7 @@ ) -@pytest.fixture() +@pytest.fixture def dup_txn_receipt(w3, indexed_event_contract, wait_for_transaction, event_contract): emitter_fn = indexed_event_contract.functions.logTwoEvents diff --git a/tests/core/contracts/test_extracting_event_data_old.py b/tests/core/contracts/test_extracting_event_data_old.py index 12cb2fc0f5..39ef9506bf 100644 --- a/tests/core/contracts/test_extracting_event_data_old.py +++ b/tests/core/contracts/test_extracting_event_data_old.py @@ -9,6 +9,28 @@ ) +@pytest.fixture +def emitter( + w3, + emitter_contract_data, + wait_for_transaction, + wait_for_block, + address_conversion_func, +): + emitter_contract_factory = w3.eth.contract(**emitter_contract_data) + + wait_for_block(w3) + deploy_txn_hash = emitter_contract_factory.constructor().transact({"gas": 10000000}) + deploy_receipt = wait_for_transaction(w3, deploy_txn_hash) + contract_address = address_conversion_func(deploy_receipt["contractAddress"]) + + bytecode = w3.eth.get_code(contract_address) + assert bytecode == emitter_contract_factory.bytecode_runtime + _emitter = emitter_contract_factory(address=contract_address) + assert _emitter.address == contract_address + return _emitter + + @pytest.mark.parametrize( "contract_fn,event_name,call_args,expected_args", ( diff --git a/tests/core/eth-module/test_accounts.py b/tests/core/eth-module/test_accounts.py index 6f935acf4e..78cf51adc6 100644 --- a/tests/core/eth-module/test_accounts.py +++ b/tests/core/eth-module/test_accounts.py @@ -9,6 +9,9 @@ from eth_account.signers.local import ( LocalAccount, ) +from eth_tester import ( + EthereumTester, +) from eth_utils import ( is_bytes, is_checksum_address, @@ -98,9 +101,9 @@ def acct(request, w3): raise Exception("Unreachable!") -@pytest.fixture() -def w3(): - return Web3(EthereumTesterProvider()) +@pytest.fixture +def w3(backend_class): + return Web3(EthereumTesterProvider(EthereumTester(backend=backend_class()))) def test_eth_default_account_is_empty_by_default(w3): @@ -560,9 +563,11 @@ def test_eth_account_sign_and_send_EIP155_transaction_to_eth_tester( # -- async -- # -@pytest.fixture() -def async_w3(): - return AsyncWeb3(AsyncEthereumTesterProvider()) +@pytest.fixture +def async_w3(backend_class): + return AsyncWeb3( + AsyncEthereumTesterProvider(EthereumTester(backend=backend_class())) + ) @patch("web3.eth.BaseEth.account", "wired via BaseEth") diff --git a/tests/core/eth-module/test_eth_filter.py b/tests/core/eth-module/test_eth_filter.py index beac58d431..427e43fb95 100644 --- a/tests/core/eth-module/test_eth_filter.py +++ b/tests/core/eth-module/test_eth_filter.py @@ -1,5 +1,8 @@ import pytest +from eth_tester import ( + EthereumTester, +) import pytest_asyncio from web3 import ( @@ -30,11 +33,11 @@ def test_eth_filter_creates_correct_filter_type(w3): # --- async --- # -@pytest_asyncio.fixture() -async def async_w3(): - provider = AsyncEthereumTesterProvider() - w3 = AsyncWeb3(provider) - return w3 +@pytest_asyncio.fixture +async def async_w3(backend_class): + return AsyncWeb3( + AsyncEthereumTesterProvider(EthereumTester(backend=backend_class())) + ) @pytest.mark.asyncio diff --git a/tests/core/eth-module/test_eth_properties.py b/tests/core/eth-module/test_eth_properties.py index ac789d4dd4..25a12bd6c4 100644 --- a/tests/core/eth-module/test_eth_properties.py +++ b/tests/core/eth-module/test_eth_properties.py @@ -1,5 +1,9 @@ import pytest +from eth_tester import ( + EthereumTester, +) + from web3 import ( AsyncWeb3, ) @@ -9,16 +13,19 @@ @pytest.fixture -def async_w3(): +def async_w3(backend_class): return AsyncWeb3( - AsyncEthereumTesterProvider(), + AsyncEthereumTesterProvider(EthereumTester(backend=backend_class())), ) def test_eth_chain_id(w3): - assert w3.eth.chain_id == w3.provider.eth_tester.chain_id + assert w3.eth.chain_id == w3.provider.ethereum_tester.backend.chain.chain_id @pytest.mark.asyncio async def test_async_eth_chain_id(async_w3): - assert await async_w3.eth.chain_id == async_w3.provider.eth_tester.chain_id + assert ( + await async_w3.eth.chain_id + == async_w3.provider.ethereum_tester.backend.chain.chain_id + ) diff --git a/tests/core/filtering/conftest.py b/tests/core/filtering/conftest.py index bc78b065d4..71f8be92f9 100644 --- a/tests/core/filtering/conftest.py +++ b/tests/core/filtering/conftest.py @@ -22,8 +22,8 @@ params=[True, False], ids=["LocalFilterMiddleware", "node_based_filter"], ) -def w3(request): - return _w3_fixture_logic(request) +def w3(request, backend_class): + return _w3_fixture_logic(request, backend_class) @pytest.fixture @@ -69,8 +69,8 @@ def create_filter(request): params=[True, False], ids=["LocalFilterMiddleware", "node_based_filter"], ) -async def async_w3(request): - return await _async_w3_fixture_logic(request) +async def async_w3(request, backend_class): + return await _async_w3_fixture_logic(request, backend_class) @pytest.fixture diff --git a/tests/core/filtering/test_contract_data_filters.py b/tests/core/filtering/test_contract_data_filters.py index d77c71f6fb..bb514987fd 100644 --- a/tests/core/filtering/test_contract_data_filters.py +++ b/tests/core/filtering/test_contract_data_filters.py @@ -84,8 +84,8 @@ def array_values(draw): params=[True, False], ids=["LocalFilterMiddleware", "node_based_filter"], ) -def w3(request): - return _w3_fixture_logic(request) +def w3(request, backend_class): + return _w3_fixture_logic(request, backend_class) @pytest.fixture(scope="module") @@ -287,8 +287,8 @@ def event_loop(): params=[True, False], ids=["LocalFilterMiddleware", "node_based_filter"], ) -async def async_w3(request): - return await _async_w3_fixture_logic(request) +async def async_w3(request, backend_class): + return await _async_w3_fixture_logic(request, backend_class) @pytest.fixture(scope="module") diff --git a/tests/core/filtering/test_contract_topic_filters.py b/tests/core/filtering/test_contract_topic_filters.py index 870e406223..7c7f0bff8a 100644 --- a/tests/core/filtering/test_contract_topic_filters.py +++ b/tests/core/filtering/test_contract_topic_filters.py @@ -84,8 +84,8 @@ def array_values(draw): params=[True, False], ids=["LocalFilterMiddleware", "node_based_filter"], ) -def w3(request): - return _w3_fixture_logic(request) +def w3(request, backend_class): + return _w3_fixture_logic(request, backend_class) @pytest.fixture(scope="module") @@ -268,8 +268,8 @@ def event_loop(): params=[True, False], ids=["LocalFilterMiddleware", "node_based_filter"], ) -async def async_w3(request): - return await _async_w3_fixture_logic(request) +async def async_w3(request, backend_class): + return await _async_w3_fixture_logic(request, backend_class) @pytest_asyncio.fixture(scope="module") diff --git a/tests/core/filtering/test_existing_filter_instance.py b/tests/core/filtering/test_existing_filter_instance.py index 8ae2cdd2ee..ccf97be687 100644 --- a/tests/core/filtering/test_existing_filter_instance.py +++ b/tests/core/filtering/test_existing_filter_instance.py @@ -7,7 +7,7 @@ ) -@pytest.fixture() +@pytest.fixture def filter_id(w3): block_filter = w3.eth.filter("latest") return block_filter.filter_id diff --git a/tests/core/filtering/utils.py b/tests/core/filtering/utils.py index e97cae895f..742950ebf8 100644 --- a/tests/core/filtering/utils.py +++ b/tests/core/filtering/utils.py @@ -1,3 +1,7 @@ +from eth_tester import ( + EthereumTester, +) + from web3 import ( AsyncWeb3, Web3, @@ -13,10 +17,9 @@ MAX_UINT_256 = 2**256 - 1 -def _w3_fixture_logic(request): +def _w3_fixture_logic(request, backend_class): use_filter_middleware = request.param - provider = EthereumTesterProvider() - w3 = Web3(provider) + w3 = Web3(EthereumTesterProvider(EthereumTester(backend=backend_class()))) w3.eth.default_account = w3.eth.accounts[0] if use_filter_middleware: w3.middleware_onion.add(LocalFilterMiddleware) @@ -45,10 +48,11 @@ def _emitter_fixture_logic( # --- async --- # -async def _async_w3_fixture_logic(request): +async def _async_w3_fixture_logic(request, backend_class): use_filter_middleware = request.param - provider = AsyncEthereumTesterProvider() - async_w3 = AsyncWeb3(provider) + async_w3 = AsyncWeb3( + AsyncEthereumTesterProvider(EthereumTester(backend=backend_class())) + ) accounts = await async_w3.eth.accounts async_w3.eth.default_account = accounts[0] diff --git a/tests/core/middleware/test_name_to_address_middleware.py b/tests/core/middleware/test_name_to_address_middleware.py index e3c9196236..bb55c6949c 100644 --- a/tests/core/middleware/test_name_to_address_middleware.py +++ b/tests/core/middleware/test_name_to_address_middleware.py @@ -1,5 +1,8 @@ import pytest +from eth_tester import ( + EthereumTester, +) import pytest_asyncio from web3 import ( @@ -30,8 +33,11 @@ def address(self, name): @pytest.fixture -def _w3_setup(): - return Web3(provider=EthereumTesterProvider(), middleware=[]) +def _w3_setup(backend_class): + return Web3( + provider=EthereumTesterProvider(EthereumTester(backend=backend_class())), + middleware=[], + ) @pytest.fixture diff --git a/tests/core/middleware/test_transaction_signing.py b/tests/core/middleware/test_transaction_signing.py index ee1030b958..f2cb467074 100644 --- a/tests/core/middleware/test_transaction_signing.py +++ b/tests/core/middleware/test_transaction_signing.py @@ -10,11 +10,13 @@ LocalAccount, ) import eth_keys +from eth_tester import ( + EthereumTester, +) from eth_tester.exceptions import ( ValidationError, ) from eth_utils import ( - ValidationError as EthUtilsValidationError, is_hexstr, to_bytes, to_hex, @@ -226,7 +228,7 @@ def hex_to_bytes(s): ), ( {"gas": 21000, "gasPrice": 0, "value": 1}, - EthUtilsValidationError, + Exception, MIXED_KEY_MIXED_TYPE, ADDRESS_1, ), @@ -244,7 +246,7 @@ def hex_to_bytes(s): ( { "value": 22, - "maxFeePerGas": 20**9, + "maxFeePerGas": 10**9, "maxPriorityFeePerGas": 10**9, }, -1, @@ -310,9 +312,9 @@ def assert_method_and_txn_signed(actual, expected): assert is_hexstr(raw_txn) -@pytest.fixture() -def w3(): - _w3 = Web3(EthereumTesterProvider()) +@pytest.fixture +def w3(backend_class): + _w3 = Web3(EthereumTesterProvider(EthereumTester(backend=backend_class()))) _w3.eth.default_account = _w3.eth.accounts[0] return _w3 @@ -494,8 +496,10 @@ async def async_w3_dummy(request_mocker): @pytest_asyncio.fixture -async def async_w3(): - _async_w3 = AsyncWeb3(AsyncEthereumTesterProvider()) +async def async_w3(backend_class): + _async_w3 = AsyncWeb3( + AsyncEthereumTesterProvider(EthereumTester(backend=backend_class())) + ) accounts = await _async_w3.eth.accounts _async_w3.eth.default_account = accounts[0] return _async_w3 diff --git a/tests/core/module-class/test_module.py b/tests/core/module-class/test_module.py index d9bb35cdb5..df641da306 100644 --- a/tests/core/module-class/test_module.py +++ b/tests/core/module-class/test_module.py @@ -37,7 +37,7 @@ def test_attach_methods_to_module(web3_with_external_modules): } ) - configured_chain_id = w3.provider.eth_tester.chain_id + configured_chain_id = w3.provider.ethereum_tester.backend.chain.chain_id assert w3.eth.chain_id == configured_chain_id assert w3.module1.property1 == configured_chain_id diff --git a/tests/core/utilities/conftest.py b/tests/core/utilities/conftest.py index 8274f13e5c..263f1a722d 100644 --- a/tests/core/utilities/conftest.py +++ b/tests/core/utilities/conftest.py @@ -1,5 +1,9 @@ import pytest +from eth_tester import ( + EthereumTester, +) + from web3.main import ( Web3, ) @@ -9,6 +13,5 @@ @pytest.fixture(scope="module") -def w3(): - provider = EthereumTesterProvider() - return Web3(provider) +def w3(backend_class): + return Web3(EthereumTesterProvider(EthereumTester(backend=backend_class()))) diff --git a/tests/core/utilities/test_abi.py b/tests/core/utilities/test_abi.py index baebbec247..07b9a038eb 100644 --- a/tests/core/utilities/test_abi.py +++ b/tests/core/utilities/test_abi.py @@ -200,7 +200,7 @@ ABI_ERROR = ABIError({"type": "error", "name": "error"}) -@pytest.fixture() +@pytest.fixture def contract_abi() -> ABI: return CONTRACT_ABI diff --git a/tests/core/web3-module/test_web3_inheritance.py b/tests/core/web3-module/test_web3_inheritance.py index 9de5a2f5b9..40d4c15fb2 100644 --- a/tests/core/web3-module/test_web3_inheritance.py +++ b/tests/core/web3-module/test_web3_inheritance.py @@ -9,4 +9,7 @@ class InheritsFromWeb3(Web3): pass inherited_w3 = InheritsFromWeb3(EthereumTesterProvider()) - assert inherited_w3.eth.chain_id == inherited_w3.provider.eth_tester.chain_id + assert ( + inherited_w3.eth.chain_id + == inherited_w3.provider.ethereum_tester.backend.chain.chain_id + ) diff --git a/tests/ens/conftest.py b/tests/ens/conftest.py index 44e47177f1..2207edc07e 100644 --- a/tests/ens/conftest.py +++ b/tests/ens/conftest.py @@ -163,8 +163,8 @@ def ens(ens_setup, mocker): # session scope for performance @pytest.fixture(scope="session") -def ens_setup(): - w3 = Web3(EthereumTesterProvider(EthereumTester())) +def ens_setup(backend_class): + w3 = Web3(EthereumTesterProvider(EthereumTester(backend=backend_class()))) # ** Set up ENS contracts ** @@ -355,7 +355,7 @@ def ens_setup(): return ENS.from_web3(w3, ens_contract.address) -@pytest.fixture() +@pytest.fixture def TEST_ADDRESS(address_conversion_func): return address_conversion_func("0x000000000000000000000000000000000000dEaD") @@ -364,8 +364,10 @@ def TEST_ADDRESS(address_conversion_func): @pytest_asyncio.fixture(scope="session") -def async_w3(): - _async_w3 = AsyncWeb3(AsyncEthereumTesterProvider()) +def async_w3(backend_class): + _async_w3 = AsyncWeb3( + AsyncEthereumTesterProvider(EthereumTester(backend=backend_class())) + ) return _async_w3 @@ -461,8 +463,11 @@ def event_loop(): # add session scope with above session-scoped `event_loop` for better performance @pytest_asyncio.fixture(scope="session") -async def async_ens_setup(async_w3): +async def async_ens_setup(backend_class): # ** Set up ENS contracts ** + async_w3 = AsyncWeb3( + AsyncEthereumTesterProvider(EthereumTester(backend=backend_class())) + ) # remove account that creates ENS, so test transactions don't have write access accounts = await async_w3.eth.accounts @@ -479,7 +484,7 @@ async def async_ens_setup(async_w3): ) reverse_tld_namehash = bytes32( 0xA097F6721CE401E757D1223A763FEF49B8B5F90BB18567DDB86FD205DFF71D34 - ) # noqa: E501 + ) reverser_namehash = bytes32( 0x91D1777781884D03A6757A803996E38DE2A42967FB37EEACA72729271025A9E2 ) diff --git a/tests/ens/test_get_text.py b/tests/ens/test_get_text.py index a04f26b568..1f8414f161 100644 --- a/tests/ens/test_get_text.py +++ b/tests/ens/test_get_text.py @@ -1,7 +1,7 @@ import pytest -from eth_utils import ( - ValidationError as EthUtilsValidationError, +from eth_tester.exceptions import ( + ValidationError as EthTesterValidationError, ) from ens.exceptions import ( @@ -31,9 +31,14 @@ def test_set_text_fails_with_bad_address(ens): address = ens.w3.eth.accounts[2] ens.setup_address("tester.eth", address) zero_address = "0x" + "00" * 20 - with pytest.raises(EthUtilsValidationError): + with pytest.raises(EthTesterValidationError): ens.set_text( - "tester.eth", "url", "http://example.com", transact={"from": zero_address} + "tester.eth", + "url", + "http://example.com", + # add gas so we don't call eth_estimateGas which can fail the transaction + # in a different way + transact={"from": zero_address, "gas": 222_222}, ) # teardown @@ -49,8 +54,8 @@ def test_set_text_pass_in_transaction_dict(ens): "avatar", "example.jpeg", transact={ - "maxFeePerGas": Web3.to_wei(100, "gwei"), - "maxPriorityFeePerGas": Web3.to_wei(100, "gwei"), + "maxFeePerGas": Web3.to_wei(1, "gwei"), + "maxPriorityFeePerGas": Web3.to_wei(1, "gwei"), }, ) assert ens.get_text("tester.eth", "url") == "http://example.com" @@ -102,9 +107,14 @@ async def test_async_set_text_fails_with_bad_address(async_ens): address = accounts[2] await async_ens.setup_address("tester.eth", address) zero_address = "0x" + "00" * 20 - with pytest.raises(EthUtilsValidationError): + with pytest.raises(EthTesterValidationError): await async_ens.set_text( - "tester.eth", "url", "http://example.com", transact={"from": zero_address} + "tester.eth", + "url", + "http://example.com", + # add gas so we don't call eth_estimateGas which can fail the transaction + # in a different way + transact={"from": zero_address, "gas": 222_222}, ) # teardown @@ -125,8 +135,8 @@ async def test_async_set_text_pass_in_transaction_dict(async_ens): "avatar", "example.jpeg", transact={ - "maxFeePerGas": Web3.to_wei(100, "gwei"), - "maxPriorityFeePerGas": Web3.to_wei(100, "gwei"), + "maxFeePerGas": Web3.to_wei(1, "gwei"), + "maxPriorityFeePerGas": Web3.to_wei(1, "gwei"), }, ) assert await async_ens.get_text("tester.eth", "url") == "http://example.com" diff --git a/tests/ens/test_setup_name.py b/tests/ens/test_setup_name.py index cef22d9bf2..752e39b7d4 100644 --- a/tests/ens/test_setup_name.py +++ b/tests/ens/test_setup_name.py @@ -92,7 +92,7 @@ def test_setup_name_default_address(ens): assert not ens.name(new_resolution) assert ens.owner(name) == owner assert ens.address(name) == new_resolution - ens.setup_name(name) + ens.setup_name(name, transact={"gas": 222_222}) assert ens.name(new_resolution) == name ens.setup_name(None, new_resolution) @@ -190,7 +190,7 @@ async def test_async_setup_name_default_address(async_ens): assert not await async_ens.name(new_resolution) assert await async_ens.owner(name) == owner assert await async_ens.address(name) == new_resolution - await async_ens.setup_name(name) + await async_ens.setup_name(name, transact={"gas": 222_222}) assert await async_ens.name(new_resolution) == name await async_ens.setup_name(None, new_resolution) diff --git a/tests/integration/ethereum_tester/common.py b/tests/integration/ethereum_tester/common.py index 86030fc5d5..cedb1e00b8 100644 --- a/tests/integration/ethereum_tester/common.py +++ b/tests/integration/ethereum_tester/common.py @@ -473,11 +473,6 @@ def test_eth_call_old_contract_state( w3, math_contract, keyfile_account_address ) - def test_eth_chain_id(self, w3): - chain_id = w3.eth.chain_id - assert is_integer(chain_id) - assert chain_id == w3.provider.eth_tester.chain_id - @disable_auto_mine def test_eth_wait_for_transaction_receipt_unmined( self, eth_tester, w3, keyfile_account_address_dual_type diff --git a/tests/integration/ethereum_tester/test_eels.py b/tests/integration/ethereum_tester/test_eels.py index 21f9265163..1af4ac75f3 100644 --- a/tests/integration/ethereum_tester/test_eels.py +++ b/tests/integration/ethereum_tester/test_eels.py @@ -46,7 +46,7 @@ def _eth_tester_state_setup(w3, keyfile_account_address, keyfile_account_pkey): @pytest.fixture(scope="module") def eth_tester(): - return EthereumTester(backend=EELSBackend(debug_mode=True)) + return EthereumTester(backend=EELSBackend()) @pytest.fixture(scope="module") diff --git a/tests/integration/ethereum_tester/test_pyevm.py b/tests/integration/ethereum_tester/test_pyevm.py index b3b70167c5..08a157322f 100644 --- a/tests/integration/ethereum_tester/test_pyevm.py +++ b/tests/integration/ethereum_tester/test_pyevm.py @@ -65,7 +65,9 @@ class TestEthereumTesterWeb3Module(EthereumTesterWeb3Module): class TestEthereumTesterEthModule(EthereumTesterEthModule): - pass + def test_eth_chain_id(self, w3): + chain_id = w3.eth.chain_id + assert chain_id == 131277322940537 class TestEthereumTesterNetModule(EthereumTesterNetModule): diff --git a/tests/integration/go_ethereum/test_goethereum_ws/conftest.py b/tests/integration/go_ethereum/test_goethereum_ws/conftest.py index 007aaed0ab..363ead7534 100644 --- a/tests/integration/go_ethereum/test_goethereum_ws/conftest.py +++ b/tests/integration/go_ethereum/test_goethereum_ws/conftest.py @@ -3,6 +3,23 @@ from tests.integration.common import ( COINBASE, ) +from tests.utils import ( + get_open_port, +) +from web3 import ( + AsyncWeb3, + WebSocketProvider, +) + + +@pytest.fixture +def w3(): + """ + Defined for the sake of overriding the `w3` in the `AsyncWeb3ModuleTest` test cases. + """ + return AsyncWeb3(WebSocketProvider()) + + def _geth_command_arguments(base_geth_command_arguments, geth_version): diff --git a/tox.ini b/tox.ini index b77b9d16aa..9de2d8c1b0 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,9 @@ [tox] envlist= - py{38,39,310,311,312,313}-{ens,core,lint,wheel} + py{38,39,310,311,312,313}-{ens,core,lint,wheel}-pyevm + py{310,311,312, 313}-{ens,core}-eels py{38,39,310,311,312,313}-integration-{goethereum,ethtester} + py{310,311,312,313}-integration-ethtester-eels docs benchmark windows-wheel @@ -20,9 +22,12 @@ allowlist_externals=make,pre-commit install_command=python -m pip install {opts} {packages} usedevelop=True commands= - core: pytest {posargs:tests/core -m "not asyncio" } - core_async: pytest {posargs:tests/core -m asyncio} - ens: pytest {posargs:tests/ens --ignore=tests/ens/normalization/test_normalize_name_ensip15.py -n auto --maxprocesses=15} + core-pyevm: pytest {posargs:tests/core -m "not asyncio" --backend=pyevm -n auto --maxprocesses=15} + core-pyevm_async: pytest {posargs:tests/core -m asyncio --backend=pyevm -n auto --maxprocesses=15} + core-eels: pytest {posargs:tests/core -m "not asyncio" --backend=eels -n auto --maxprocesses=15} + core-eels_async: pytest {posargs:tests/core -m asyncio --backend=eels -n auto --maxprocesses=15} + ens-pyevm: pytest {posargs:tests/ens --ignore=tests/ens/normalization/test_normalize_name_ensip15.py --backend=pyevm -n auto --maxprocesses=15} + ens-eels: pytest {posargs:tests/ens --ignore=tests/ens/normalization/test_normalize_name_ensip15.py --backend=eels -n auto --maxprocesses=15} ensip15: pytest {posargs:tests/ens/normalization/test_normalize_name_ensip15.py -q -n auto --maxprocesses=15} integration-goethereum-ipc: pytest {posargs:tests/integration/go_ethereum/test_goethereum_ipc.py -k "not Async" -n auto --maxprocesses=15} integration-goethereum-ipc_async: pytest {posargs:tests/integration/go_ethereum/test_goethereum_ipc.py -k Async -n auto --maxprocesses=15} diff --git a/web3/providers/eth_tester/main.py b/web3/providers/eth_tester/main.py index 48fdd260fb..9ede4e2849 100644 --- a/web3/providers/eth_tester/main.py +++ b/web3/providers/eth_tester/main.py @@ -66,20 +66,30 @@ class AsyncEthereumTesterProvider(AsyncBaseProvider): ethereum_tester_middleware, ) - def __init__(self) -> None: + def __init__( + self, + ethereum_tester: Optional["EthereumTester"] = None, + api_endpoints: Optional[ + Dict[str, Dict[str, Callable[..., RPCResponse]]] + ] = None, + ) -> None: super().__init__() + if not ethereum_tester: + from eth_tester import ( + EthereumTester, + ) - # do not import eth_tester until runtime, it is not a default dependency - from eth_tester import ( - EthereumTester, - ) + ethereum_tester = EthereumTester() - from web3.providers.eth_tester.defaults import ( - API_ENDPOINTS, - ) + if not api_endpoints: + from web3.providers.eth_tester.defaults import ( + API_ENDPOINTS, + ) + + api_endpoints = API_ENDPOINTS - self.ethereum_tester = EthereumTester() - self.api_endpoints = API_ENDPOINTS + self.ethereum_tester = ethereum_tester + self.api_endpoints = api_endpoints async def request_func( self, async_w3: "AsyncWeb3", middleware_onion: "MiddlewareOnion" From 8d3ccadfdca199600b93fa7407877a5b39b61e3f Mon Sep 17 00:00:00 2001 From: fselmo <fselmo2@gmail.com> Date: Fri, 9 Aug 2024 11:37:49 -0600 Subject: [PATCH 4/9] Add pypy test runs to eels backend tests: - Add pypy py-evm CI jobs; refactor testenvs in ``tox.ini``. --- .circleci/config.yml | 96 ++++++++++++++++++++++++++++++++++++++++--- tox.ini | 7 ++-- web3/_utils/module.py | 5 ++- 3 files changed, 98 insertions(+), 10 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 2dcec59499..fcd7a6c2d9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -11,6 +11,10 @@ parameters: type: string common: &common + parameters: + python_exec: + type: string + default: "python" working_directory: ~/repo steps: - checkout @@ -28,6 +32,38 @@ common: &common - restore_cache: keys: - cache-v1-{{ arch }}-{{ .Environment.CIRCLE_JOB }}-{{ checksum "setup.py" }}-{{ checksum "tox.ini" }} + - run: + name: install pypy3 if python_exec is pypy3 + command: | + if [ "<< parameters.python_exec >>" == "pypy3" ]; then + sudo apt-get update + + # If .pyenv already exists, remove and reinstall to get latest version + if [ -d "$HOME/.pyenv" ]; then + echo "Removing existing .pyenv directory..." + rm -rf $HOME/.pyenv + fi + curl https://pyenv.run | bash + export PATH="$HOME/.pyenv/bin:$PATH" + eval "$(pyenv init --path)" + eval "$(pyenv init -)" + eval "$(pyenv virtualenv-init -)" + + # Find the latest PyPy version matching the python minor version + latest_pypy_version=$(pyenv install --list | grep -E "pypy3\.<< parameters.python_minor_version >>" | grep -v "\-src" | tail -1 | tr -d ' ') + echo "Latest PyPy version: $latest_pypy_version" + + # Install the latest PyPy 3.10 version using pyenv if not already installed + pyenv install "$latest_pypy_version" + pyenv global "$latest_pypy_version" + + # Verify the correct PyPy version is being used + pypy3 --version + + # Install pip using the newly installed PyPy version + curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py + pypy3 get-pip.py + fi - run: name: install dependencies command: | @@ -36,7 +72,7 @@ common: &common python web3/scripts/install_pre_releases.py - run: name: run tox - command: python -m tox run -r + command: << parameters.python_exec >> -m tox -r - save_cache: paths: - .hypothesis @@ -141,12 +177,30 @@ jobs: type: string tox_env: type: string + python_exec: + type: string + default: "python" <<: *common docker: - image: cimg/python:3.<< parameters.python_minor_version >> environment: TOXENV: py3<< parameters.python_minor_version >>-<< parameters.tox_env >> + common-pypy: + parameters: + python_minor_version: + type: string + tox_env: + type: string + python_exec: + type: string + default: "pypy3" + <<: *common + docker: + - image: cimg/python:3.<< parameters.python_minor_version >> + environment: + TOXENV: pypy3<< parameters.python_minor_version >>-<< parameters.tox_env >> + geth: parameters: python_minor_version: @@ -224,13 +278,44 @@ workflows: python_minor_version: ["8", "9", "10", "11", "12", "13"] tox_env: [ "lint", - "core", - "core_async", - "ens", + "core-pyevm", + "core-pyevm_async", + "ens-pyevm", "ensip15", "wheel" ] + python_exec: "python" name: "py3<< matrix.python_minor_version >>-<< matrix.tox_env >>" + - common: + matrix: + parameters: + # eels only supports 3.10 and above + python_minor_version: ["10", "11", "12"] + tox_env: [ + "core-eels", + "core-eels_async", + "ens-eels", + "integration-ethtester-eels" + ] + python_exec: "python" + name: "py3<< matrix.python_minor_version >>-<< matrix.tox_env >>" + - common-pypy: + matrix: + parameters: + # eels only supports 3.10 and above; pyenv only has pypy3.10 available + python_minor_version: ["10"] + tox_env: [ + "core-eels", + "core-eels_async", + "ens-eels", + "integration-ethtester-eels", + "core-pyevm", + "core-pyevm_async", + "ens-pyevm", + "integration-ethtester-pyevm" + ] + python_exec: "pypy3" + name: "pypy3<< matrix.python_minor_version >>-<< matrix.tox_env >>" - geth: matrix: parameters: @@ -242,7 +327,7 @@ workflows: "integration-goethereum-http_async", "integration-goethereum-legacy_ws", "integration-goethereum-ws", - "integration-ethtester" + "integration-ethtester-pyevm" ] name: "py3<< matrix.python_minor_version >>-<< matrix.tox_env >>" - docs: @@ -256,7 +341,6 @@ workflows: python_minor_version: ["10", "11", "12", "13"] name: "py3<< matrix.python_minor_version >>-windows-wheel" - nightly: triggers: - schedule: diff --git a/tox.ini b/tox.ini index 9de2d8c1b0..8732161cae 100644 --- a/tox.ini +++ b/tox.ini @@ -1,9 +1,10 @@ [tox] envlist= - py{38,39,310,311,312,313}-{ens,core,lint,wheel}-pyevm - py{310,311,312, 313}-{ens,core}-eels - py{38,39,310,311,312,313}-integration-{goethereum,ethtester} + py{py}{38,39,310,311,312,313}-{ens,core}-pyevm + py{py}{310,311,312,313}-{ens,core}-eels + py{38,39,310,311,312,313}-integration-{goethereum,ethtester-pyevm} py{310,311,312,313}-integration-ethtester-eels + py{38,39,310,311,312,313}-{lint,wheel} docs benchmark windows-wheel diff --git a/web3/_utils/module.py b/web3/_utils/module.py index 63fc151232..bf32e0cdfa 100644 --- a/web3/_utils/module.py +++ b/web3/_utils/module.py @@ -27,7 +27,10 @@ def _validate_init_params_and_return_if_found(module_class: Any) -> List[str]: init_params_raw = list(inspect.signature(module_class.__init__).parameters) module_init_params = [ - param for param in init_params_raw if param not in ["self", "args", "kwargs"] + param + for param in init_params_raw + # pypy uses `obj` and `keywords` instead of `self` and `kwargs`, respectively + if param not in ["self", "obj", "args", "kwargs", "keywords"] ] if len(module_init_params) > 1: From da73cd47647236866cddb6d127cc4d36fe90d72d Mon Sep 17 00:00:00 2001 From: fselmo <fselmo2@gmail.com> Date: Thu, 22 Aug 2024 12:55:06 -0600 Subject: [PATCH 5/9] Make clear what python version is building the wheel from script. --- web3/scripts/release/test_wheel_install.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/web3/scripts/release/test_wheel_install.sh b/web3/scripts/release/test_wheel_install.sh index df2b63ae3e..8573f47d5a 100755 --- a/web3/scripts/release/test_wheel_install.sh +++ b/web3/scripts/release/test_wheel_install.sh @@ -2,6 +2,7 @@ set -e rm -rf build dist +python --version python -m build cd $(mktemp -d) python -m venv venv-test From 9ff49f4775afbd5396b496dd5af2a6b9397fd244 Mon Sep 17 00:00:00 2001 From: fselmo <fselmo2@gmail.com> Date: Tue, 24 Sep 2024 14:18:41 -0600 Subject: [PATCH 6/9] Fix issues with new tests after rebasing --- docs/web3.contract.rst | 2 +- tests/core/contracts/test_contract_build_transaction.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/web3.contract.rst b/docs/web3.contract.rst index 1348012cff..69aee7888e 100644 --- a/docs/web3.contract.rst +++ b/docs/web3.contract.rst @@ -277,7 +277,7 @@ Each Contract Factory exposes the following methods. .. doctest:: contractmethods >>> contract.constructor(1000000).estimate_gas() - 664971 + 664953 .. py:classmethod:: Contract.constructor(*args, **kwargs).build_transaction(transaction=None) :noindex: diff --git a/tests/core/contracts/test_contract_build_transaction.py b/tests/core/contracts/test_contract_build_transaction.py index 87f8dcba02..f00768b202 100644 --- a/tests/core/contracts/test_contract_build_transaction.py +++ b/tests/core/contracts/test_contract_build_transaction.py @@ -63,7 +63,7 @@ def test_build_transaction_with_contract_no_arguments_no_parens( "value": 0, "maxFeePerGas": 2750000000, "maxPriorityFeePerGas": 10**9, - "chainId": 131277322940537, + "chainId": w3.eth.chain_id, } @@ -329,7 +329,7 @@ async def test_async_build_transaction_with_contract_no_arguments_no_parens( "value": 0, "maxFeePerGas": 2750000000, "maxPriorityFeePerGas": 10**9, - "chainId": 131277322940537, + "chainId": await async_w3.eth.chain_id, } From 422811a08b3347536293d8247610c595790cb495 Mon Sep 17 00:00:00 2001 From: fselmo <fselmo2@gmail.com> Date: Sat, 15 Feb 2025 22:20:57 -0700 Subject: [PATCH 7/9] Add py3.13 runs for eels backend --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index fcd7a6c2d9..907750bec2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -290,7 +290,7 @@ workflows: matrix: parameters: # eels only supports 3.10 and above - python_minor_version: ["10", "11", "12"] + python_minor_version: ["10", "11", "12", "13"] tox_env: [ "core-eels", "core-eels_async", From f461a341e7ecfcc3fdcf5b4a87451949b8a390fd Mon Sep 17 00:00:00 2001 From: fselmo <fselmo2@gmail.com> Date: Wed, 21 May 2025 13:53:11 -0600 Subject: [PATCH 8/9] pytest-xdist updates --- .../caching-utils/test_request_caching.py | 22 +++++------ .../middleware/test_eth_tester_middleware.py | 2 +- tox.ini | 38 +++++++++---------- 3 files changed, 31 insertions(+), 31 deletions(-) diff --git a/tests/core/caching-utils/test_request_caching.py b/tests/core/caching-utils/test_request_caching.py index 3727cbecd1..700c5248b2 100644 --- a/tests/core/caching-utils/test_request_caching.py +++ b/tests/core/caching-utils/test_request_caching.py @@ -204,7 +204,7 @@ def test_all_providers_do_not_cache_by_default_and_can_set_caching_properties(pr "threshold", (RequestCacheValidationThreshold.FINALIZED, RequestCacheValidationThreshold.SAFE), ) -@pytest.mark.parametrize("endpoint", BLOCKNUM_IN_PARAMS | BLOCK_IN_RESULT) +@pytest.mark.parametrize("endpoint", sorted(BLOCKNUM_IN_PARAMS | BLOCK_IN_RESULT)) @pytest.mark.parametrize( "blocknum,should_cache", ( @@ -254,7 +254,7 @@ def test_blocknum_validation_against_validation_threshold_when_caching_mainnet( "threshold", (RequestCacheValidationThreshold.FINALIZED, RequestCacheValidationThreshold.SAFE), ) -@pytest.mark.parametrize("endpoint", BLOCKNUM_IN_PARAMS) +@pytest.mark.parametrize("endpoint", sorted(BLOCKNUM_IN_PARAMS)) @pytest.mark.parametrize( "block_id,blocknum,should_cache", ( @@ -297,7 +297,7 @@ def test_block_id_param_caching_mainnet( "threshold", (RequestCacheValidationThreshold.FINALIZED, RequestCacheValidationThreshold.SAFE), ) -@pytest.mark.parametrize("endpoint", BLOCKHASH_IN_PARAMS) +@pytest.mark.parametrize("endpoint", sorted(BLOCKHASH_IN_PARAMS)) @pytest.mark.parametrize( "blocknum,should_cache", ( @@ -360,7 +360,7 @@ def test_request_caching_validation_threshold_defaults( @pytest.mark.parametrize( - "endpoint", BLOCKNUM_IN_PARAMS | BLOCK_IN_RESULT | BLOCKHASH_IN_PARAMS + "endpoint", sorted(BLOCKNUM_IN_PARAMS | BLOCK_IN_RESULT | BLOCKHASH_IN_PARAMS) ) @pytest.mark.parametrize( "time_from_threshold,should_cache", @@ -436,7 +436,7 @@ def test_sync_validation_against_validation_threshold_time_based( ), ) @pytest.mark.parametrize( - "endpoint", BLOCKNUM_IN_PARAMS | BLOCK_IN_RESULT | BLOCKHASH_IN_PARAMS + "endpoint", sorted(BLOCKNUM_IN_PARAMS | BLOCK_IN_RESULT | BLOCKHASH_IN_PARAMS) ) def test_validation_against_validation_threshold_time_based_configured( time_from_threshold, should_cache, chain_id, endpoint, sync_provider, request_mocker @@ -642,7 +642,7 @@ async def test_async_request_caching_does_not_share_state_between_providers( "threshold", (RequestCacheValidationThreshold.FINALIZED, RequestCacheValidationThreshold.SAFE), ) -@pytest.mark.parametrize("endpoint", BLOCKNUM_IN_PARAMS | BLOCK_IN_RESULT) +@pytest.mark.parametrize("endpoint", sorted(BLOCKNUM_IN_PARAMS | BLOCK_IN_RESULT)) @pytest.mark.parametrize( "blocknum,should_cache", ( @@ -689,7 +689,7 @@ async def test_async_blocknum_validation_against_validation_threshold_mainnet( "threshold", (RequestCacheValidationThreshold.FINALIZED, RequestCacheValidationThreshold.SAFE), ) -@pytest.mark.parametrize("endpoint", BLOCKNUM_IN_PARAMS) +@pytest.mark.parametrize("endpoint", sorted(BLOCKNUM_IN_PARAMS)) @pytest.mark.parametrize( "block_id,blocknum,should_cache", ( @@ -735,7 +735,7 @@ async def test_async_block_id_param_caching_mainnet( "threshold", (RequestCacheValidationThreshold.FINALIZED, RequestCacheValidationThreshold.SAFE), ) -@pytest.mark.parametrize("endpoint", BLOCKHASH_IN_PARAMS) +@pytest.mark.parametrize("endpoint", sorted(BLOCKHASH_IN_PARAMS)) @pytest.mark.parametrize( "blocknum,should_cache", ( @@ -794,7 +794,7 @@ async def test_async_request_caching_validation_threshold_defaults( @pytest.mark.asyncio @pytest.mark.parametrize( - "endpoint", BLOCKNUM_IN_PARAMS | BLOCK_IN_RESULT | BLOCKHASH_IN_PARAMS + "endpoint", sorted(BLOCKNUM_IN_PARAMS | BLOCK_IN_RESULT | BLOCKHASH_IN_PARAMS) ) @pytest.mark.parametrize( "time_from_threshold,should_cache", @@ -856,7 +856,7 @@ async def test_async_validation_against_validation_threshold_time_based( @pytest.mark.asyncio @pytest.mark.parametrize( - "endpoint", BLOCKNUM_IN_PARAMS | BLOCK_IN_RESULT | BLOCKHASH_IN_PARAMS + "endpoint", sorted(BLOCKNUM_IN_PARAMS | BLOCK_IN_RESULT | BLOCKHASH_IN_PARAMS) ) @pytest.mark.parametrize("blocknum", ("0x0", "0x1", "0x2", "0x3", "0x4", "0x5")) async def test_async_request_caching_with_validation_threshold_set_to_none( @@ -901,7 +901,7 @@ async def test_async_request_caching_with_validation_threshold_set_to_none( ), ) @pytest.mark.parametrize( - "endpoint", BLOCKNUM_IN_PARAMS | BLOCK_IN_RESULT | BLOCKHASH_IN_PARAMS + "endpoint", sorted(BLOCKNUM_IN_PARAMS | BLOCK_IN_RESULT | BLOCKHASH_IN_PARAMS) ) async def test_async_validation_against_validation_threshold_time_based_configured( time_from_threshold, diff --git a/tests/core/middleware/test_eth_tester_middleware.py b/tests/core/middleware/test_eth_tester_middleware.py index 1b8b834a92..e5f6d6cea9 100644 --- a/tests/core/middleware/test_eth_tester_middleware.py +++ b/tests/core/middleware/test_eth_tester_middleware.py @@ -18,7 +18,7 @@ SAMPLE_ADDRESS = "0x0000000000000000000000000000000000000004" -@pytest.mark.parametrize("block_number", {0, "0x0", "earliest"}) +@pytest.mark.parametrize("block_number", (0, "0x0", "earliest")) def test_get_transaction_count_formatters(w3, block_number): tx_counts = w3.eth.get_transaction_count(w3.eth.accounts[-1], block_number) assert tx_counts == 0 diff --git a/tox.ini b/tox.ini index 8732161cae..26183b3be9 100644 --- a/tox.ini +++ b/tox.ini @@ -10,13 +10,13 @@ envlist= windows-wheel [flake8] -exclude= venv*,.tox,docs,build -extend-ignore=E203,W503 -max-line-length=88 -per-file-ignores=__init__.py:F401 +exclude = venv*,.tox,docs,build +extend-ignore = E203,W503 +max-line-length = 88 +per-file-ignores = __init__.py:F401 [blocklint] -max_issue_threshold=1 +max_issue_threshold = 1 [testenv] allowlist_externals=make,pre-commit @@ -41,7 +41,7 @@ commands= docs: make check-docs-ci deps = .[test] - ; install both `docs` and `test` dependencies for the `docs` environment +; install both `docs` and `test` dependencies for the `docs` environment docs: .[docs] passenv = GETH_BINARY @@ -60,37 +60,37 @@ basepython = py313: python3.13 [testenv:py{38,39,310,311,312,313}-lint] -deps=pre-commit -extras=dev -commands= +deps = pre-commit +extras = dev +commands = pre-commit install pre-commit run --all-files --show-diff-on-failure [testenv:benchmark] -basepython=python -commands= +basepython = python +commands = python {toxinidir}/web3/tools/benchmark/main.py --num-calls 5 python {toxinidir}/web3/tools/benchmark/main.py --num-calls 50 python {toxinidir}/web3/tools/benchmark/main.py --num-calls 100 [testenv:py{38,39,310,311,312,313}-wheel] -deps= +deps = wheel build[virtualenv] -allowlist_externals= +allowlist_externals = /bin/rm /bin/bash -commands= +commands = /bin/bash {toxinidir}/web3/scripts/release/test_wheel_install.sh -skip_install=true +skip_install = true [testenv:windows-wheel] -deps= +deps = wheel build[virtualenv] -allowlist_externals= +allowlist_externals = bash.exe -commands= +commands = bash.exe {toxinidir}/web3/scripts/release/test_windows_wheel_install.sh -skip_install=true +skip_install = true From 3f28363e5bdd1b76fa9a911371228acd0b68ad4d Mon Sep 17 00:00:00 2001 From: fselmo <fselmo2@gmail.com> Date: Thu, 5 Jun 2025 11:38:44 -0600 Subject: [PATCH 9/9] Changes for eth-tester next breaking major version --- tests/core/module-class/test_module.py | 2 +- tests/integration/ethereum_tester/common.py | 12 +- .../integration/ethereum_tester/test_eels.py | 12 +- .../integration/ethereum_tester/test_pyevm.py | 12 +- .../test_goethereum_ws/conftest.py | 5 - web3/_utils/method_formatters.py | 6 +- web3/providers/eth_tester/defaults.py | 6 +- web3/providers/eth_tester/main.py | 259 +++++++++--------- 8 files changed, 157 insertions(+), 157 deletions(-) diff --git a/tests/core/module-class/test_module.py b/tests/core/module-class/test_module.py index df641da306..c4b656441b 100644 --- a/tests/core/module-class/test_module.py +++ b/tests/core/module-class/test_module.py @@ -87,7 +87,7 @@ def test_attach_methods_with_mungers(web3_with_external_modules): } ) - w3.provider.ethereum_tester.mine_block() + w3.provider.ethereum_tester.include_block() assert w3.eth.get_block(0, False)["baseFeePerGas"] == 1000000000 assert w3.eth.get_block(1, False)["baseFeePerGas"] == 875000000 diff --git a/tests/integration/ethereum_tester/common.py b/tests/integration/ethereum_tester/common.py index cedb1e00b8..75d8bdeb90 100644 --- a/tests/integration/ethereum_tester/common.py +++ b/tests/integration/ethereum_tester/common.py @@ -237,18 +237,18 @@ def disable_auto_mine(func): @functools.wraps(func) def func_wrapper(self, eth_tester, *args, **kwargs): snapshot = eth_tester.take_snapshot() - eth_tester.disable_auto_mine_transactions() + eth_tester.disable_auto_transactions_inclusion() try: func(self, eth_tester, *args, **kwargs) finally: - eth_tester.enable_auto_mine_transactions() - eth_tester.mine_block() + eth_tester.enable_auto_transaction_inclusion() + eth_tester.include_block() eth_tester.revert_to_snapshot(snapshot) return func_wrapper -class TestEthereumTesterWeb3Module(Web3ModuleTest): +class EthereumTesterWeb3ModuleTest(Web3ModuleTest): def _check_web3_client_version(self, client_version): assert client_version.startswith("EthereumTester/") @@ -270,7 +270,7 @@ def _check_web3_client_version(self, client_version): ) -class TestEthereumTesterEthModule(EthModuleTest): +class EthereumTesterEthModuleTest(EthModuleTest): test_eth_sign = not_implemented(EthModuleTest.test_eth_sign, MethodUnavailable) test_eth_sign_ens_names = not_implemented( EthModuleTest.test_eth_sign_ens_names, MethodUnavailable @@ -673,5 +673,5 @@ def test_eth_get_balance_with_block_identifier(self, w3: "Web3") -> None: assert later_balance > genesis_balance -class TestEthereumTesterNetModule(NetModuleTest): +class EthereumTesterNetModuleTest(NetModuleTest): pass diff --git a/tests/integration/ethereum_tester/test_eels.py b/tests/integration/ethereum_tester/test_eels.py index 1af4ac75f3..10105c6353 100644 --- a/tests/integration/ethereum_tester/test_eels.py +++ b/tests/integration/ethereum_tester/test_eels.py @@ -22,9 +22,9 @@ ) from .common import ( - EthereumTesterEthModule, - EthereumTesterNetModule, - EthereumTesterWeb3Module, + EthereumTesterEthModuleTest, + EthereumTesterNetModuleTest, + EthereumTesterWeb3ModuleTest, ) @@ -60,11 +60,11 @@ def w3(eth_tester, keyfile_account_address, keyfile_account_pkey): # -- test classes -- # -class TestEthereumTesterWeb3Module(EthereumTesterWeb3Module): +class TestEthereumTesterWeb3Module(EthereumTesterWeb3ModuleTest): pass -class TestEthereumTesterEthModule(EthereumTesterEthModule): +class TestEthereumTesterEthModule(EthereumTesterEthModuleTest): def test_eth_chain_id(self, w3): chain_id = w3.eth.chain_id assert chain_id == 1 @@ -84,5 +84,5 @@ def test_eth_fee_history_no_reward_percentiles(self, w3: "Web3") -> None: super().test_eth_fee_history_no_reward_percentiles(w3) -class TestEthereumTesterNetModule(EthereumTesterNetModule): +class TestEthereumTesterNetModule(EthereumTesterNetModuleTest): pass diff --git a/tests/integration/ethereum_tester/test_pyevm.py b/tests/integration/ethereum_tester/test_pyevm.py index 08a157322f..41deda1b50 100644 --- a/tests/integration/ethereum_tester/test_pyevm.py +++ b/tests/integration/ethereum_tester/test_pyevm.py @@ -22,9 +22,9 @@ ) from .common import ( - EthereumTesterEthModule, - EthereumTesterNetModule, - EthereumTesterWeb3Module, + EthereumTesterEthModuleTest, + EthereumTesterNetModuleTest, + EthereumTesterWeb3ModuleTest, ) @@ -60,15 +60,15 @@ def w3(eth_tester, keyfile_account_address, keyfile_account_pkey): # -- test classes -- # -class TestEthereumTesterWeb3Module(EthereumTesterWeb3Module): +class TestEthereumTesterWeb3Module(EthereumTesterWeb3ModuleTest): pass -class TestEthereumTesterEthModule(EthereumTesterEthModule): +class TestEthereumTesterEthModule(EthereumTesterEthModuleTest): def test_eth_chain_id(self, w3): chain_id = w3.eth.chain_id assert chain_id == 131277322940537 -class TestEthereumTesterNetModule(EthereumTesterNetModule): +class TestEthereumTesterNetModule(EthereumTesterNetModuleTest): pass diff --git a/tests/integration/go_ethereum/test_goethereum_ws/conftest.py b/tests/integration/go_ethereum/test_goethereum_ws/conftest.py index 363ead7534..93aa378c96 100644 --- a/tests/integration/go_ethereum/test_goethereum_ws/conftest.py +++ b/tests/integration/go_ethereum/test_goethereum_ws/conftest.py @@ -3,9 +3,6 @@ from tests.integration.common import ( COINBASE, ) -from tests.utils import ( - get_open_port, -) from web3 import ( AsyncWeb3, WebSocketProvider, @@ -20,8 +17,6 @@ def w3(): return AsyncWeb3(WebSocketProvider()) - - def _geth_command_arguments(base_geth_command_arguments, geth_version): yield from base_geth_command_arguments if geth_version.major == 1: diff --git a/web3/_utils/method_formatters.py b/web3/_utils/method_formatters.py index d66852ed9d..691badf4ff 100644 --- a/web3/_utils/method_formatters.py +++ b/web3/_utils/method_formatters.py @@ -8,6 +8,7 @@ Dict, Iterable, NoReturn, + Optional, Tuple, TypeVar, Union, @@ -1246,9 +1247,12 @@ def apply_module_to_formatters( def get_result_formatters( method_name: RPCEndpoint, - module: "Module", + module: Optional["Module"] = None, ) -> Callable[[RPCResponse], Any]: formatters = combine_formatters((PYTHONIC_RESULT_FORMATTERS,), method_name) + if module is None: + return compose(*formatters) + formatters_requiring_module = combine_formatters( (FILTER_RESULT_FORMATTERS,), method_name ) diff --git a/web3/providers/eth_tester/defaults.py b/web3/providers/eth_tester/defaults.py index b6197c0c37..379fdde449 100644 --- a/web3/providers/eth_tester/defaults.py +++ b/web3/providers/eth_tester/defaults.py @@ -116,7 +116,7 @@ def call_eth_tester( def without_eth_tester( - fn: Callable[[TParams], TReturn] + fn: Callable[[TParams], TReturn], ) -> Callable[["EthereumTester", TParams], TReturn]: # workaround for: https://github.com/pytoolz/cytoolz/issues/103 # @functools.wraps(fn) @@ -127,7 +127,7 @@ def inner(eth_tester: "EthereumTester", params: TParams) -> TReturn: def without_params( - fn: Callable[[TParams], TReturn] + fn: Callable[[TParams], TReturn], ) -> Callable[["EthereumTester", TParams], TReturn]: # workaround for: https://github.com/pytoolz/cytoolz/issues/103 # @functools.wraps(fn) @@ -311,7 +311,7 @@ def create_new_account(eth_tester: "EthereumTester") -> HexAddress: "getTransactionReceipt": null_if_transaction_not_found( compose( apply_formatter_if( - compose(is_null, operator.itemgetter("block_number")), + compose(is_null, operator.itemgetter("blockNumber")), static_return(None), ), call_eth_tester("get_transaction_receipt"), diff --git a/web3/providers/eth_tester/main.py b/web3/providers/eth_tester/main.py index 9ede4e2849..fb973f2f37 100644 --- a/web3/providers/eth_tester/main.py +++ b/web3/providers/eth_tester/main.py @@ -32,16 +32,23 @@ RPCResponse, ) +from ..._utils.method_formatters import ( + get_null_result_formatters, + get_request_formatters, + get_result_formatters, +) from ...exceptions import ( Web3TypeError, ) +from ...method import ( + _apply_request_formatters, +) from ...middleware import ( async_combine_middleware, combine_middleware, ) from .middleware import ( default_transaction_fields_middleware, - ethereum_tester_middleware, ) if TYPE_CHECKING: @@ -59,38 +66,135 @@ ) -class AsyncEthereumTesterProvider(AsyncBaseProvider): - _current_request_id = 0 - _middleware = ( - default_transaction_fields_middleware, - ethereum_tester_middleware, - ) +class BaseEthereumTesterProvider: + """ + Base class for EthereumTesterProvider and AsyncEthereumTesterProvider. + Contains shared logic for making requests to the EthereumTester instance. + """ + + api_endpoints: Optional[Dict[str, Dict[str, Callable[..., RPCResponse]]]] = None def __init__( self, - ethereum_tester: Optional["EthereumTester"] = None, + ethereum_tester: Optional[Union["EthereumTester", "BaseChainBackend"]] = None, api_endpoints: Optional[ Dict[str, Dict[str, Callable[..., RPCResponse]]] ] = None, ) -> None: - super().__init__() - if not ethereum_tester: - from eth_tester import ( - EthereumTester, - ) + # do not import eth_tester until runtime, it is not a default dependency + from eth_tester import EthereumTester # noqa: F811 + from eth_tester.backends.base import ( + BaseChainBackend, + ) - ethereum_tester = EthereumTester() + if ethereum_tester is None: + self.ethereum_tester = EthereumTester() + elif isinstance(ethereum_tester, EthereumTester): + self.ethereum_tester = ethereum_tester + elif isinstance(ethereum_tester, BaseChainBackend): + self.ethereum_tester = EthereumTester(ethereum_tester) + else: + raise Web3TypeError( + "Expected ethereum_tester to be of type `eth_tester.EthereumTester` or " + "a subclass of `eth_tester.backends.base.BaseChainBackend`, " + f"instead received {type(ethereum_tester)}. " + "If you would like a custom eth-tester instance to test with, see the " + "eth-tester documentation. https://github.com/ethereum/eth-tester." + ) - if not api_endpoints: - from web3.providers.eth_tester.defaults import ( + if api_endpoints is None: + # do not import eth_tester derivatives until runtime, + # it is not a default dependency + from .defaults import ( API_ENDPOINTS, ) - api_endpoints = API_ENDPOINTS + self.api_endpoints = API_ENDPOINTS + else: + self.api_endpoints = api_endpoints + + self._request_formatters = get_request_formatters + self._result_formatters = get_result_formatters + self._null_result_formatters = get_null_result_formatters + + self._current_request_id = 0 + self._middleware = (default_transaction_fields_middleware,) + + def _make_request( + self, + method: RPCEndpoint, + params: Any, + api_endpoints: Dict[str, Dict[str, Any]], + ethereum_tester_instance: "EthereumTester", + request_id: str, + ) -> RPCResponse: + # do not import eth_tester derivatives until runtime, + # it is not a default dependency + from eth_tester.exceptions import ( + TransactionFailed, + ) + + namespace, _, endpoint = method.partition("_") + + try: + delegator = api_endpoints[namespace][endpoint] + except KeyError as e: + return self._make_response( + method, e, request_id, message=f"Unknown RPC Endpoint: {method}" + ) + try: + params = ( + _apply_request_formatters(params, self._request_formatters(method)), + ) + response = delegator(ethereum_tester_instance, params[0]) + except NotImplementedError as e: + return self._make_response( + method, + e, + request_id, + message=f"RPC Endpoint has not been implemented: {method}", + ) + except TransactionFailed as e: + first_arg = e.args[0] + try: + # sometimes eth-tester wraps an exception in another exception + raw_error_msg = ( + first_arg + if not isinstance(first_arg, Exception) + else first_arg.args[0] + ) + reason = ( + abi.decode(["string"], raw_error_msg[4:])[0] + if is_bytes(raw_error_msg) + else raw_error_msg + ) + except DecodingError: + reason = first_arg + raise TransactionFailed(f"execution reverted: {reason}") + else: + return self._make_response(method, response, request_id) + + def _make_response( + self, method: RPCEndpoint, result: Any, response_id: str, message: str = "" + ) -> RPCResponse: + if isinstance(result, Exception): + return cast( + RPCResponse, + { + "id": response_id, + "jsonrpc": "2.0", + "error": cast(RPCError, {"code": -32601, "message": message}), + }, + ) + + formatter = self._result_formatters(method) + return cast( + RPCResponse, + {"id": response_id, "jsonrpc": "2.0", "result": formatter(result)}, + ) - self.ethereum_tester = ethereum_tester - self.api_endpoints = api_endpoints +class AsyncEthereumTesterProvider(BaseEthereumTesterProvider, AsyncBaseProvider): async def request_func( self, async_w3: "AsyncWeb3", middleware_onion: "MiddlewareOnion" ) -> Callable[..., Coroutine[Any, Any, RPCResponse]]: @@ -111,7 +215,7 @@ async def request_func( return self._request_func_cache[-1] async def make_request(self, method: RPCEndpoint, params: Any) -> RPCResponse: - response = _make_request( + response = self._make_request( method, params, self.api_endpoints, @@ -125,54 +229,11 @@ async def is_connected(self, show_traceback: bool = False) -> Literal[True]: return True -class EthereumTesterProvider(BaseProvider): - _current_request_id = 0 - _middleware = ( - default_transaction_fields_middleware, - ethereum_tester_middleware, - ) - ethereum_tester = None - api_endpoints: Optional[Dict[str, Dict[str, Callable[..., RPCResponse]]]] = None - - def __init__( - self, - ethereum_tester: Optional[Union["EthereumTester", "BaseChainBackend"]] = None, - api_endpoints: Optional[ - Dict[str, Dict[str, Callable[..., RPCResponse]]] - ] = None, - ) -> None: - # do not import eth_tester until runtime, it is not a default dependency - super().__init__() - from eth_tester import EthereumTester # noqa: F811 - from eth_tester.backends.base import ( - BaseChainBackend, - ) - - if ethereum_tester is None: - self.ethereum_tester = EthereumTester() - elif isinstance(ethereum_tester, EthereumTester): - self.ethereum_tester = ethereum_tester - elif isinstance(ethereum_tester, BaseChainBackend): - self.ethereum_tester = EthereumTester(ethereum_tester) - else: - raise Web3TypeError( - "Expected ethereum_tester to be of type `eth_tester.EthereumTester` or " - "a subclass of `eth_tester.backends.base.BaseChainBackend`, " - f"instead received {type(ethereum_tester)}. " - "If you would like a custom eth-tester instance to test with, see the " - "eth-tester documentation. https://github.com/ethereum/eth-tester." - ) - - if api_endpoints is None: - # do not import eth_tester derivatives until runtime, - # it is not a default dependency - from .defaults import ( - API_ENDPOINTS, - ) - - self.api_endpoints = API_ENDPOINTS - else: - self.api_endpoints = api_endpoints +class EthereumTesterProvider(BaseEthereumTesterProvider, BaseProvider): + """ + Provider for EthereumTester, used for testing and development. + It allows interaction with the EthereumTester instance. + """ def request_func( self, w3: "Web3", middleware_onion: "MiddlewareOnion" @@ -194,7 +255,7 @@ def request_func( return self._request_func_cache[-1] def make_request(self, method: RPCEndpoint, params: Any) -> RPCResponse: - response = _make_request( + response = self._make_request( method, params, self.api_endpoints, @@ -206,63 +267,3 @@ def make_request(self, method: RPCEndpoint, params: Any) -> RPCResponse: def is_connected(self, show_traceback: bool = False) -> Literal[True]: return True - - -def _make_response(result: Any, response_id: str, message: str = "") -> RPCResponse: - if isinstance(result, Exception): - return cast( - RPCResponse, - { - "id": response_id, - "jsonrpc": "2.0", - "error": cast(RPCError, {"code": -32601, "message": message}), - }, - ) - - return cast(RPCResponse, {"id": response_id, "jsonrpc": "2.0", "result": result}) - - -def _make_request( - method: RPCEndpoint, - params: Any, - api_endpoints: Dict[str, Dict[str, Any]], - ethereum_tester_instance: "EthereumTester", - request_id: str, -) -> RPCResponse: - # do not import eth_tester derivatives until runtime, - # it is not a default dependency - from eth_tester.exceptions import ( - TransactionFailed, - ) - - namespace, _, endpoint = method.partition("_") - - try: - delegator = api_endpoints[namespace][endpoint] - except KeyError as e: - return _make_response(e, request_id, message=f"Unknown RPC Endpoint: {method}") - try: - response = delegator(ethereum_tester_instance, params) - except NotImplementedError as e: - return _make_response( - e, - request_id, - message=f"RPC Endpoint has not been implemented: {method}", - ) - except TransactionFailed as e: - first_arg = e.args[0] - try: - # sometimes eth-tester wraps an exception in another exception - raw_error_msg = ( - first_arg if not isinstance(first_arg, Exception) else first_arg.args[0] - ) - reason = ( - abi.decode(["string"], raw_error_msg[4:])[0] - if is_bytes(raw_error_msg) - else raw_error_msg - ) - except DecodingError: - reason = first_arg - raise TransactionFailed(f"execution reverted: {reason}") - else: - return _make_response(response, request_id)