Skip to content

test_create_child_revert_refunds_state_gas()

Documentation for tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py::test_create_child_revert_refunds_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_create.py::test_create_child_revert_refunds_state_gas --fork Amsterdam

Verify CREATE/CREATE2 child REVERT refunds parent's account gas.

On REVERT the parent's GAS_NEW_ACCOUNT charge is refunded to the reservoir (on top of the child's state gas returned via incorporate_child_on_error). Block state gas reflects only the probe SSTORE. The spillover variant runs with tx.gas at the cap (reservoir zero), so the state gas charge spills into gas_left and the refund returns to the reservoir (not back to gas_left).

Source code in tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
@pytest.mark.parametrize(
    "gas_limit_mode",
    [
        pytest.param("reservoir", id="with_reservoir"),
        pytest.param("spillover", id="spillover"),
    ],
)
@pytest.mark.with_all_create_opcodes()
@pytest.mark.valid_from("EIP8037")
def test_create_child_revert_refunds_state_gas(
    blockchain_test: BlockchainTestFiller,
    pre: Alloc,
    fork: Fork,
    create_opcode: Op,
    gas_limit_mode: str,
) -> None:
    """
    Verify CREATE/CREATE2 child REVERT refunds parent's account gas.

    On REVERT the parent's `GAS_NEW_ACCOUNT` charge is refunded to
    the reservoir (on top of the child's state gas returned via
    `incorporate_child_on_error`). Block state gas reflects only the
    probe SSTORE. The spillover variant runs with tx.gas at the cap
    (reservoir zero), so the state gas charge spills into `gas_left`
    and the refund returns to the reservoir (not back to `gas_left`).
    """
    gas_limit_cap = fork.transaction_gas_limit_cap()
    assert gas_limit_cap is not None
    gas_costs = fork.gas_costs()
    sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork)
    intrinsic_cost = fork.transaction_intrinsic_cost_calculator()()

    init_code = Op.REVERT(0, 0)
    mstore_value, size = init_code_at_high_bytes(init_code)

    create_call = (
        create_opcode(value=0, offset=0, size=size, salt=0)
        if create_opcode == Op.CREATE2
        else create_opcode(value=0, offset=0, size=size)
    )

    storage = Storage()
    factory_code = (
        Op.MSTORE(0, mstore_value)
        + Op.POP(create_call)
        + Op.SSTORE(storage.store_next(1, "reservoir_ok"), 1)
    )
    factory = pre.deploy_contract(code=factory_code)

    gas_limit = (
        gas_limit_cap
        if gas_limit_mode == "spillover"
        else gas_limit_cap + sstore_state_gas
    )
    tx = Transaction(
        to=factory,
        gas_limit=gas_limit,
        sender=pre.fund_eoa(),
    )

    # CREATE's GAS_NEW_ACCOUNT is refunded on child REVERT. SSTORE's
    # state portion is tracked separately. Child REVERT regular
    # (init_code execution) is propagated via
    # incorporate_child_on_error.
    tx_regular = (
        intrinsic_cost
        + factory_code.gas_cost(fork)
        - gas_costs.NEW_ACCOUNT
        - sstore_state_gas
        + init_code.gas_cost(fork)
    )
    tx_state = sstore_state_gas
    expected = max(tx_regular, tx_state)
    blockchain_test(
        pre=pre,
        blocks=[Block(txs=[tx], header_verify=Header(gas_used=expected))],
        post={factory: Account(storage=storage)},
    )

Parametrized Test Cases

This test generates 4 parametrized test cases across 1 fork.