Skip to content

test_failed_create_tx_refills_top_frame_new_account()

Documentation for tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py::test_failed_create_tx_refills_top_frame_new_account@26332146.

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_failed_create_tx_refills_top_frame_new_account --fork Amsterdam

Verify the top-frame NEW_ACCOUNT of a creation tx is refilled when the initcode fails.

Under EIP-2780 the created account's NEW_ACCOUNT state gas is charged in the top-frame preparation (not the intrinsic), so gas_limit must cover it for the initcode to run at all. When the initcode then fails the whole creation rolls back and no account persists:

  • REVERT preserves gas_left and restore_state_gas returns the spilled NEW_ACCOUNT to it, so the state block nets to zero and only the execution consumption counts as work. The calldata floor tops up the billed amount and the block-level execution gas alike, so receipt and header agree at the greater of consumption and floor: the memory expansion keeps revert above the floor, while the bare revert_floor_bound pins the floor in both.
  • HALT (INVALID) refills the spilled NEW_ACCOUNT to gas_left and then burns all of it, so the sender pays the full gas_limit.
Source code in tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
@pytest.mark.parametrize(
    ("init_code", "floor_binds"),
    [
        pytest.param(
            Op.REVERT(0, 10_000, new_memory_size=10_000),
            False,
            id="revert",
        ),
        pytest.param(Op.REVERT(0, 0), True, id="revert_floor_bound"),
        pytest.param(Op.INVALID, None, id="halt"),
    ],
)
@pytest.mark.valid_from("EIP8037")
def test_failed_create_tx_refills_top_frame_new_account(
    state_test: StateTestFiller,
    pre: Alloc,
    fork: Fork,
    init_code: Bytecode,
    floor_binds: bool | None,
) -> None:
    """
    Verify the top-frame NEW_ACCOUNT of a creation tx is refilled when the
    initcode fails.

    Under EIP-2780 the created account's ``NEW_ACCOUNT`` state gas is
    charged in the top-frame preparation (not the intrinsic), so
    ``gas_limit`` must cover it for the initcode to run at all. When the
    initcode then fails the whole creation rolls back and no account
    persists:

    * REVERT preserves ``gas_left`` and ``restore_state_gas`` returns
      the spilled ``NEW_ACCOUNT`` to it, so the state block nets to zero
      and only the execution consumption counts as work. The calldata floor
      tops up the billed amount and the block-level execution gas alike, so
      receipt and header agree at the greater of consumption and floor:
      the memory expansion keeps ``revert`` above the floor, while the
      bare ``revert_floor_bound`` pins the floor in both.
    * HALT (INVALID) refills the spilled ``NEW_ACCOUNT`` to ``gas_left``
      and then burns all of it, so the sender pays the full ``gas_limit``.
    """
    intrinsic_calc = fork.transaction_intrinsic_cost_calculator()

    intrinsic_execution = intrinsic_calc(
        calldata=bytes(init_code),
        contract_creation=True,
        return_cost_deducted_prior_execution=True,
    )
    # gas_limit must cover the top-frame NEW_ACCOUNT and the initcode's own
    # execution gas so the initcode runs to completion.
    gas_limit = (
        intrinsic_execution
        + fork.transaction_top_frame_state_gas(contract_creation=True)
        + init_code.execution_cost(fork)
        + 1000
    )

    if init_code == Op.INVALID:
        # Exceptional halt burns all gas_left (the refilled NEW_ACCOUNT
        # included).
        expected_gas_used = gas_limit
    else:
        # REVERT refills the spilled NEW_ACCOUNT, netting the state block
        # to zero, so only the execution consumption counts as work. The
        # calldata floor binds the billed amount and the block-level
        # execution gas alike, so receipt and header agree either way.
        execution_consumed = intrinsic_execution + init_code.execution_cost(
            fork
        )
        floor = fork.transaction_data_floor_cost_calculator()(
            data=bytes(init_code), contract_creation=True
        )
        assert (floor > execution_consumed) == floor_binds, (
            "init code lands on the wrong side of the floor"
        )
        expected_gas_used = max(execution_consumed, floor)

    sender = pre.fund_eoa()
    created = compute_create_address(address=sender, nonce=0)

    tx = Transaction(
        to=None,
        data=init_code,
        gas_limit=gas_limit,
        sender=sender,
        expected_receipt=TransactionReceipt(
            cumulative_gas_used=expected_gas_used,
        ),
    )

    state_test(
        pre=pre,
        post={created: Account.NONEXISTENT},
        tx=tx,
        blockchain_test_header_verify=Header(gas_used=expected_gas_used),
    )

Parametrized Test Cases

This test generates 3 parametrized test cases across 1 fork.