fork_network_anvil

Documentation for eth_defi.provider.anvil.fork_network_anvil function.

fork_network_anvil(fork_url=None, unlocked_addresses=None, cmd='anvil', port=(19999, 29999, 25), block_time=0, launch_wait_seconds=20.0, attempts=3, hardfork='london', gas_limit=None, steps_tracing=False, test_request_timeout=3.0, fork_block_number=None)

Creates Anvil unit test backend or mainnet fork.

  • Anvil can be used as web3.py test backend instead of EthereumTester. Anvil offers faster execution and tracing - see eth_defi.trace.

  • Forking a mainnet is a common way to test against live deployments. This function invokes anvil command and tells it to fork a given JSON-RPC endpoint.

When called, a subprocess is started on the background. To stop this process, call eth_defi.anvil.AnvilLaunch.close().

This function waits launch_wait_seconds in order to anvil process to start and complete the chain fork.

Unit test backend:

Mainnet fork: Here is an example that forks BNB chain mainnet and transfer 500 BUSD stablecoin to a test account we control:

from eth_defi.anvil import fork_network_anvil
from eth_defi.chain import install_chain_middleware
from eth_defi.gas import node_default_gas_price_strategy

@pytest.fixture()
def large_busd_holder() -> HexAddress:
    # An onchain address with BUSD balance
    # Binance Hot Wallet 6
    return HexAddress(HexStr("0x8894E0a0c962CB723c1976a4421c95949bE2D4E3"))


@pytest.fixture()
def user_1() -> LocalAccount:
    # Create a test account
    return Account.create()


@pytest.fixture()
def anvil_bnb_chain_fork(request, large_busd_holder, user_1, user_2) -> str:
     # Create a testable fork of live BNB chain.
    mainnet_rpc = os.environ["BNB_CHAIN_JSON_RPC"]
    launch = fork_network_anvil(mainnet_rpc, unlocked_addresses=[large_busd_holder])
    try:
        yield launch.json_rpc_url
    finally:
        # Wind down Anvil process after the test is complete
        launch.close(log_level=logging.ERROR)


@pytest.fixture()
def web3(anvil_bnb_chain_fork: str):
    # Set up a local unit testing blockchain
    # https://web3py.readthedocs.io/en/stable/examples.html#contract-unit-tests-in-python
    web3 =  Web3(HTTPProvider(anvil_bnb_chain_fork))
    # Anvil needs POA middlware if parent chain needs POA middleware
    install_chain_middleware(web3)
    web3.eth.set_gas_price_strategy(node_default_gas_price_strategy)
    return web3

def test_anvil_fork_transfer_busd(web3: Web3, large_busd_holder: HexAddress, user_1: LocalAccount):
    # Forks the BNB chain mainnet and transfers from USDC to the user.

    # BUSD deployment on BNB chain
    # https://bscscan.com/token/0xe9e7cea3dedca5984780bafc599bd69add087d56
    busd_details = fetch_erc20_details(web3, "0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56")
    busd = busd_details.contract

    # Transfer 500 BUSD to the user 1
    tx_hash = busd.functions.transfer(user_1.address, 500 * 10**18).transact({"from": large_busd_holder})

    # Because Ganache has instamine turned on by default, we do not need to wait for the transaction
    receipt = web3.eth.get_transaction_receipt(tx_hash)
    assert receipt.status == 1, "BUSD transfer reverted"

    assert busd.functions.balanceOf(user_1.address).call() == 500 * 10**18

See the full example in tests source code.

If anvil refuses to terminate properly, you can kill a process by a port in your terminal:

# Kill any process listening to localhost:19999
kill -SIGKILL $(lsof -ti:19999)

See also

Note

Looks like we have some issues Anvil instance lingering around even after AnvilLaunch.close() if scoped pytest fixtures are used.

Parameters
  • cmd – Override anvil command. If not given we look up from PATH.

  • fork_url (Optional[str]) –

    HTTP JSON-RPC URL of the network we want to fork.

    If not given launch an empty test backend.

  • unlocked_addresses (Optional[list[Union[eth_typing.evm.HexAddress, str]]]) – List of addresses of which ownership we take to allow test code to transact as them

  • port (int | tuple) –

    Localhost port we bind for Anvil JSON-RPC.

    The tuple format is (min port, max port, opening attempts).

    By default, takes a tuple range and tries to open a a random port in the range, until empty found. This allows to run multiple parallel Anvil’s during unit testing with pytest -n auto.

    You can also specify an individual port.

  • launch_wait_seconds – How long we wait anvil to start until giving up

  • block_time

    How long Anvil takes to mine a block. Default is zero: Anvil is in automining mode and creates a new block for each new transaction.

    Set to 1 or higher so that you can poll the transaction as you would do with a live JSON-RPC node.

  • attempts

    How many attempts we do to start anvil.

    Anvil launch may fail without any output. This could be because the given JSON-RPC node is throttling your API requests. In this case we just try few more times again by killing the Anvil process and starting it again.

  • gas_limit (Optional[int]) – Set the block gas limit.

  • hardfork – EVM version to use

  • step_tracing

    Enable Anvil step tracing.

    Needed to get structured logs.

    Only needed on GoEthereum style tracing, not needed for Parity style tracing.

    See https://book.getfoundry.sh/reference/anvil/

  • test_request_timeout – Set the timeout fro the JSON-RPC requests that attempt to determine if Anvil was successfully launched.

  • fork_block_number (Optional[int]) –

    For at a specific block height of the parent chain.

    If not given, fork at the latest block. Needs an archive node to work.

Return type

eth_defi.provider.anvil.AnvilLaunch