Skip to content

test_create_tx_gas_boundary()

Documentation for tests/amsterdam/eip8038_state_access_gas_cost_increase/test_create_gas.py::TestCreateTxGasBoundary::test_create_tx_gas_boundary@343274cc.

Generate fixtures for these test cases for Amsterdam with:

fill -v tests/amsterdam/eip8038_state_access_gas_cost_increase/test_create_gas.py::TestCreateTxGasBoundary::test_create_tx_gas_boundary --fork Amsterdam

Test the contract-creating-transaction gas boundary under EIP-8038.

Four scenarios pin the boundary, mirroring EIP-3860's TestContractCreationGasUsage but with the EIP-8037/8038 2D gas split:

  1. too_little_intrinsic_gas: one below the total intrinsic; the transaction is rejected (INTRINSIC_GAS_TOO_LOW).
  2. exact_intrinsic_gas: exactly the intrinsic; the tx is valid but the initcode runs out of execution gas.
  3. too_little_execution_gas: one below the full execution gas; creation fails but the tx is valid.
  4. exact_execution_gas: exactly the full execution gas; creation succeeds.
Source code in tests/amsterdam/eip8038_state_access_gas_cost_increase/test_create_gas.py
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
class TestCreateTxGasBoundary:
    """
    Test the contract-creating-transaction gas boundary under EIP-8038.

    Four scenarios pin the boundary, mirroring EIP-3860's
    ``TestContractCreationGasUsage`` but with the EIP-8037/8038 2D gas
    split:

    1. ``too_little_intrinsic_gas``: one below the total intrinsic; the
       transaction is rejected (``INTRINSIC_GAS_TOO_LOW``).
    2. ``exact_intrinsic_gas``: exactly the intrinsic; the tx is valid
       but the initcode runs out of execution gas.
    3. ``too_little_execution_gas``: one below the full execution gas;
       creation fails but the tx is valid.
    4. ``exact_execution_gas``: exactly the full execution gas; creation
       succeeds.
    """

    @pytest.fixture
    def initcode(self) -> Initcode:
        """Return a small initcode that deposits a multi-byte contract."""
        # Deploy 32 bytes (STOP + 31 padding) so code-deposit state gas
        # is non-zero and the code-deposit branch is exercised.
        return Initcode(
            deploy_code=Op.STOP + Op.INVALID * 31, initcode_length=64
        )

    @pytest.fixture
    def tx_access_list(self) -> List[AccessList]:
        """
        Return an access list to raise the intrinsic gas cost above the
        EIP-7623 floor data cost, mirroring EIP-3860's fixture.
        """
        return [
            AccessList(address=Address(i), storage_keys=[])
            for i in range(1, 642)
        ]

    @pytest.fixture
    def exact_intrinsic_gas(
        self,
        fork: Fork,
        initcode: Initcode,
        tx_access_list: List[AccessList],
    ) -> int:
        """Return the total (execution + state) intrinsic tx gas cost."""
        calc = fork.transaction_intrinsic_cost_calculator()
        return calc(
            calldata=initcode,
            contract_creation=True,
            access_list=tx_access_list,
        )

    @pytest.fixture
    def exact_execution_gas(
        self, fork: Fork, exact_intrinsic_gas: int, initcode: Initcode
    ) -> int:
        """
        Return the total execution gas: intrinsic plus the top-frame
        ``NEW_ACCOUNT`` plus the initcode execution gas plus the
        code-deposit gas.

        Under EIP-2780 the created account's ``NEW_ACCOUNT`` state gas
        moved out of the intrinsic and into the top frame, so it is added
        explicitly here (the intrinsic is execution-only).

        ``deployment_gas`` is fork-aware: under EIP-8037 it splits the
        deposit into the keccak word cost (execution) and the per-byte cost
        (state), while on a fork without state-byte metering it is the
        flat execution per-byte deposit cost. The single call is therefore
        correct in either regime.
        """
        execution = exact_intrinsic_gas + fork.transaction_top_frame_state_gas(
            contract_creation=True
        )
        execution += initcode.evm_gas(fork)
        execution += initcode.deployment_gas(fork)
        return execution

    @pytest.mark.parametrize(
        "gas_test_case",
        [
            pytest.param(
                "too_little_intrinsic_gas", marks=pytest.mark.exception_test
            ),
            pytest.param("exact_intrinsic_gas"),
            pytest.param("too_little_execution_gas"),
            pytest.param("exact_execution_gas"),
        ],
    )
    @EIPChecklist.GasCostChanges.Test.OutOfGas()
    def test_create_tx_gas_boundary(
        self,
        state_test: StateTestFiller,
        pre: Alloc,
        fork: Fork,
        initcode: Initcode,
        tx_access_list: List[AccessList],
        exact_intrinsic_gas: int,
        exact_execution_gas: int,
        gas_test_case: str,
    ) -> None:
        """Drive a creation tx at each of the four gas boundary points."""
        sender = pre.fund_eoa()
        create_address = compute_create_address(address=sender, nonce=0)

        if gas_test_case == "too_little_intrinsic_gas":
            gas_limit = exact_intrinsic_gas - 1
        elif gas_test_case == "exact_intrinsic_gas":
            gas_limit = exact_intrinsic_gas
        elif gas_test_case == "too_little_execution_gas":
            gas_limit = exact_execution_gas - 1
        else:
            gas_limit = exact_execution_gas

        tx_error = (
            TransactionException.INTRINSIC_GAS_TOO_LOW
            if gas_test_case == "too_little_intrinsic_gas"
            else None
        )

        succeeds = gas_test_case == "exact_execution_gas"
        post = {
            create_address: (
                Account(code=initcode.deploy_code)
                if succeeds
                else Account.NONEXISTENT
            )
        }

        tx = Transaction(
            to=None,
            data=initcode,
            access_list=tx_access_list,
            gas_limit=gas_limit,
            error=tx_error,
            sender=sender,
        )

        # 2D block accounting: gas_used = max(execution, state). Under
        # EIP-2780 the state axis carries the fresh target's top-frame
        # NEW_ACCOUNT and (when the deposit succeeds) the per-byte
        # code-deposit gas.
        if tx_error is not None:
            header_verify = None
        elif succeeds:
            # Fresh target: top-frame NEW_ACCOUNT plus the per-byte code
            # deposit are the state-gas axis; the rest is execution.
            state_used = fork.transaction_top_frame_state_gas(
                contract_creation=True
            )
            state_used += fork.code_deposit_state_gas(
                code_size=len(initcode.deploy_code)
            )
            execution_used = gas_limit - state_used
            header_verify = Header(gas_used=max(execution_used, state_used))
        else:
            # exact_intrinsic / too_little_execution: the top-frame
            # NEW_ACCOUNT (and any deposit) cannot be covered, the whole
            # preparation rolls back, and all gas is burned as execution.
            header_verify = Header(gas_used=gas_limit)

        state_test(
            pre=pre,
            post=post,
            tx=tx,
            blockchain_test_header_verify=header_verify,
        )

test_create_tx_gas_boundary(state_test, pre, fork, initcode, tx_access_list, exact_intrinsic_gas, exact_execution_gas, gas_test_case)

Drive a creation tx at each of the four gas boundary points.

Source code in tests/amsterdam/eip8038_state_access_gas_cost_increase/test_create_gas.py
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
@pytest.mark.parametrize(
    "gas_test_case",
    [
        pytest.param(
            "too_little_intrinsic_gas", marks=pytest.mark.exception_test
        ),
        pytest.param("exact_intrinsic_gas"),
        pytest.param("too_little_execution_gas"),
        pytest.param("exact_execution_gas"),
    ],
)
@EIPChecklist.GasCostChanges.Test.OutOfGas()
def test_create_tx_gas_boundary(
    self,
    state_test: StateTestFiller,
    pre: Alloc,
    fork: Fork,
    initcode: Initcode,
    tx_access_list: List[AccessList],
    exact_intrinsic_gas: int,
    exact_execution_gas: int,
    gas_test_case: str,
) -> None:
    """Drive a creation tx at each of the four gas boundary points."""
    sender = pre.fund_eoa()
    create_address = compute_create_address(address=sender, nonce=0)

    if gas_test_case == "too_little_intrinsic_gas":
        gas_limit = exact_intrinsic_gas - 1
    elif gas_test_case == "exact_intrinsic_gas":
        gas_limit = exact_intrinsic_gas
    elif gas_test_case == "too_little_execution_gas":
        gas_limit = exact_execution_gas - 1
    else:
        gas_limit = exact_execution_gas

    tx_error = (
        TransactionException.INTRINSIC_GAS_TOO_LOW
        if gas_test_case == "too_little_intrinsic_gas"
        else None
    )

    succeeds = gas_test_case == "exact_execution_gas"
    post = {
        create_address: (
            Account(code=initcode.deploy_code)
            if succeeds
            else Account.NONEXISTENT
        )
    }

    tx = Transaction(
        to=None,
        data=initcode,
        access_list=tx_access_list,
        gas_limit=gas_limit,
        error=tx_error,
        sender=sender,
    )

    # 2D block accounting: gas_used = max(execution, state). Under
    # EIP-2780 the state axis carries the fresh target's top-frame
    # NEW_ACCOUNT and (when the deposit succeeds) the per-byte
    # code-deposit gas.
    if tx_error is not None:
        header_verify = None
    elif succeeds:
        # Fresh target: top-frame NEW_ACCOUNT plus the per-byte code
        # deposit are the state-gas axis; the rest is execution.
        state_used = fork.transaction_top_frame_state_gas(
            contract_creation=True
        )
        state_used += fork.code_deposit_state_gas(
            code_size=len(initcode.deploy_code)
        )
        execution_used = gas_limit - state_used
        header_verify = Header(gas_used=max(execution_used, state_used))
    else:
        # exact_intrinsic / too_little_execution: the top-frame
        # NEW_ACCOUNT (and any deposit) cannot be covered, the whole
        # preparation rolls back, and all gas is burned as execution.
        header_verify = Header(gas_used=gas_limit)

    state_test(
        pre=pre,
        post=post,
        tx=tx,
        blockchain_test_header_verify=header_verify,
    )

Parametrized Test Cases

This test generates 4 parametrized test cases across 1 fork.