Skip to content

test_precompile_warming()

Documentation for tests/berlin/eip2929_gas_cost_increases/test_precompile_warming.py::test_precompile_warming@ffe48572.

Generate fixtures for these test cases for Bogota with:

fill -v tests/berlin/eip2929_gas_cost_increases/test_precompile_warming.py::test_precompile_warming --fork Bogota

Call BALANCE of precompile addresses before and after a fork.

According to EIP-2929, when a transaction begins, accessed_addresses is initialized to include: - tx.sender, tx.to - and the set of all precompiles

This test verifies that: 1. Precompiles that exist in the predecessor fork are always "warm" (lower gas cost). 2. New precompiles added in a fork are "cold" before the fork and become "warm" after.

Source code in tests/berlin/eip2929_gas_cost_increases/test_precompile_warming.py
@pytest.mark.valid_at_transition_to("Paris", subsequent_forks=True)
@pytest.mark.parametrize_by_fork(
    "address,precompile_in_successor,precompile_in_predecessor",
    precompile_addresses_in_predecessor_successor,
)
@EIPChecklist.Precompile.Test.ForkTransition.Before.Cold(eip=[7951])
@EIPChecklist.Precompile.Test.ForkTransition.After.Warm(eip=[7951])
@pytest.mark.slow()
def test_precompile_warming(
    blockchain_test: BlockchainTestFiller,
    fork: Fork,
    address: Address,
    precompile_in_successor: bool,
    precompile_in_predecessor: bool,
    pre: Alloc,
) -> None:
    """
    Call BALANCE of precompile addresses before and after a fork.

    According to EIP-2929, when a transaction begins, accessed_addresses is
    initialized to include:
    - tx.sender, tx.to
    - and the set of all precompiles

    This test verifies that:
    1. Precompiles that exist in the predecessor fork are always "warm" (lower
        gas cost).
    2. New precompiles added in a fork are "cold" before the fork and become
        "warm" after.
    """
    sender = pre.fund_eoa()
    call_cost_slot = 0

    code = (
        Op.GAS
        + Op.BALANCE(address)
        + Op.POP
        + Op.SSTORE(call_cost_slot, Op.SUB(Op.SWAP1, Op.GAS))
        + Op.STOP
    )
    before = pre.deploy_contract(code, storage={call_cost_slot: 0xDEADBEEF})
    after = pre.deploy_contract(code, storage={call_cost_slot: 0xDEADBEEF})

    # Block before fork
    blocks = [
        Block(
            timestamp=10_000,
            txs=[
                Transaction(
                    sender=sender,
                    to=before,
                    gas_limit=1_000_000,
                )
            ],
        )
    ]

    # Block after fork
    blocks += [
        Block(
            timestamp=20_000,
            txs=[
                Transaction(
                    sender=sender,
                    to=after,
                    gas_limit=1_000_000,
                )
            ],
        )
    ]

    predecessor = get_transition_fork_predecessor(fork)
    successor = get_transition_fork_successor(fork)

    def get_expected_gas(precompile_present: bool, fork: Fork) -> int:
        balance_cost = Op.BALANCE(address_warm=precompile_present).gas_cost(
            fork
        )
        # Overhead: GAS + POP + SUB
        overhead_cost = (Op.GAS + Op.POP + Op.SUB).gas_cost(fork)
        return balance_cost + overhead_cost

    expected_gas_before = get_expected_gas(
        precompile_in_predecessor, predecessor
    )
    expected_gas_after = get_expected_gas(precompile_in_successor, successor)

    post = {
        before: Account(storage={call_cost_slot: expected_gas_before}),
        after: Account(storage={call_cost_slot: expected_gas_after}),
    }

    blockchain_test(
        pre=pre,
        post=post,
        blocks=blocks,
    )

Parametrized Test Cases

This test generates 50 parametrized test cases across 6 forks.