Skip to content

test_top_level_failure_spilled_state_gas()

Documentation for tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_reservoir.py::test_top_level_failure_spilled_state_gas@c74f1a67.

Generate fixtures for these test cases for Amsterdam with:

fill -v tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_reservoir.py::test_top_level_failure_spilled_state_gas --fork Amsterdam

Verify top-level failure handling for spilled state gas, whether the spill is charged in the frame itself, propagated from a successful subcall, or both.

The reservoir covers half an SSTORE's state gas, so each SSTORE charge spills into gas_left. A successful child propagates its state_gas_spilled into the parent, accumulating with the parent's own spill. Refunds are LIFO, so the spilled portion returns to gas_left and only the reservoir-funded portion to the reservoir.

  • REVERT preserves gas_left, so all state gas is refunded and the sender pays only the regular component.
  • Halt refills LIFO then zeros gas_left, so the spill is burned and only the start reservoir survives.
Source code in tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_reservoir.py
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
@pytest.mark.parametrize(
    "spill_source",
    [
        pytest.param("own", id="own_spill"),
        pytest.param("propagated", id="propagated_spill"),
        pytest.param("both", id="own_and_propagated_spill"),
    ],
)
@pytest.mark.parametrize(
    "failure_mode",
    [
        pytest.param("revert", id="revert"),
        pytest.param("halt", id="halt"),
    ],
)
@pytest.mark.valid_from("EIP8037")
def test_top_level_failure_spilled_state_gas(
    state_test: StateTestFiller,
    pre: Alloc,
    fork: Fork,
    failure_mode: str,
    spill_source: str,
) -> None:
    """
    Verify top-level failure handling for spilled state gas, whether
    the spill is charged in the frame itself, propagated from a
    successful subcall, or both.

    The reservoir covers half an SSTORE's state gas, so each SSTORE
    charge spills into `gas_left`. A successful child propagates its
    `state_gas_spilled` into the parent, accumulating with the parent's
    own spill. Refunds are LIFO, so the spilled portion returns to
    `gas_left` and only the reservoir-funded portion to the reservoir.

    - REVERT preserves `gas_left`, so all state gas is refunded and the
      sender pays only the regular component.
    - Halt refills LIFO then zeros `gas_left`, so the spill is burned
      and only the start reservoir survives.
    """
    gas_limit_cap = fork.transaction_gas_limit_cap()
    assert gas_limit_cap is not None
    sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork)
    intrinsic_cost = fork.transaction_intrinsic_cost_calculator()()

    terminator = Op.REVERT(0, 0) if failure_mode == "revert" else Op.INVALID
    has_child = spill_source in ("propagated", "both")
    child_code = Op.SSTORE(0, 1)

    parent_code = Bytecode()
    if spill_source in ("own", "both"):
        parent_code += Op.SSTORE(0, 1)
    child = None
    if has_child:
        child = pre.deploy_contract(code=child_code)
        parent_code += Op.POP(Op.CALL(gas=Op.GAS, address=child))
    parent_code += terminator
    parent = pre.deploy_contract(code=parent_code)

    # Reservoir covers half an SSTORE's state gas, so every SSTORE
    # spills into gas_left.
    reservoir = sstore_state_gas // 2
    tx_gas = gas_limit_cap + reservoir
    total_state = sstore_state_gas * (
        (1 if spill_source in ("own", "both") else 0) + (1 if has_child else 0)
    )

    if failure_mode == "revert":
        # gas_left preserved, all state gas refunded, so the sender
        # pays only the regular component.
        expected_cumulative = (
            intrinsic_cost + parent_code.gas_cost(fork) - total_state
        )
        if has_child:
            expected_cumulative += child_code.gas_cost(fork)
    else:
        # gas_left burned after LIFO refill. The spill returns to
        # gas_left and is consumed, so only the start reservoir
        # survives.
        expected_cumulative = tx_gas - reservoir

    tx = Transaction(
        to=parent,
        state_gas_reservoir=reservoir,
        sender=pre.fund_eoa(),
        expected_receipt=TransactionReceipt(
            cumulative_gas_used=expected_cumulative,
        ),
    )

    post = {parent: Account(storage={})}
    if child is not None:
        post[child] = Account(storage={})
    state_test(pre=pre, post=post, tx=tx)

Parametrized Test Cases

This test generates 6 parametrized test cases across 1 fork.