Skip to content

test_aborted_create_does_not_warm_address()

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

Generate fixtures for these test cases for Amsterdam with:

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

Verify a silently-aborted CREATE does not warm the target address.

When CREATE aborts before spawning the child frame (insufficient balance for the endowment, or nonce overflow), the would-be address is never added to the accessed-addresses set. A subsequent BALANCE of that address is therefore charged the full COLD_ACCOUNT_ACCESS, not WARM_ACCESS.

Source code in tests/amsterdam/eip8038_state_access_gas_cost_increase/test_create_gas.py
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
@pytest.mark.with_all_create_opcodes()
@pytest.mark.parametrize(
    "abort_mode",
    [
        pytest.param("insufficient_balance", id="insufficient_balance"),
        pytest.param("nonce_overflow", id="nonce_overflow"),
        pytest.param(None, id="no_error"),
    ],
)
def test_aborted_create_does_not_warm_address(
    state_test: StateTestFiller,
    pre: Alloc,
    fork: Fork,
    create_opcode: Op,
    abort_mode: str | None,
) -> None:
    """
    Verify a silently-aborted CREATE does not warm the target address.

    When CREATE aborts before spawning the child frame (insufficient
    balance for the endowment, or nonce overflow), the would-be address
    is never added to the accessed-addresses set. A subsequent
    ``BALANCE`` of that address is therefore charged the full
    ``COLD_ACCOUNT_ACCESS``, not ``WARM_ACCESS``.
    """
    init_code = Op.STOP
    init_code_bytes = bytes(init_code)
    init_code_len = len(init_code)

    create_value = 1
    create_call = create_opcode(
        value=create_value, offset=0, size=init_code_len
    )

    # After the aborted CREATE, measure the BALANCE access of the
    # would-be address (passed via calldata).
    # The address should only be warm when the CREATE/CREATE2 opcode
    # successfully reached initcode execution stage.
    address_warm = abort_mode is None
    balance_code = Op.BALANCE(Op.CALLDATALOAD(0), address_warm=address_warm)
    measure = CodeGasMeasure(code=balance_code, extra_stack_items=1)

    setup = Op.MSTORE(
        0,
        int.from_bytes(init_code_bytes, "big") << (256 - 8 * init_code_len),
    )
    factory_code = setup + Op.POP(create_call) + measure

    factory_nonce = 2**64 - 1 if abort_mode == "nonce_overflow" else 1
    factory_balance = create_value
    if abort_mode == "insufficient_balance":
        factory_balance -= 1
    factory = pre.deploy_contract(
        code=factory_code, nonce=factory_nonce, balance=factory_balance
    )

    target_address = compute_create_address(
        address=factory,
        salt=0,
        initcode=init_code_bytes,
        nonce=factory_nonce,
        opcode=create_opcode,
    )

    tx = Transaction(
        to=factory,
        data=Hash(target_address, left_padding=True),
        sender=pre.fund_eoa(),
    )

    # The BALANCE must be cold: in case of error, the aborted CREATE never
    # warmed the would-be address.
    post = {
        factory: Account(storage={0: balance_code.gas_cost(fork)}),
        target_address: Account(nonce=1)
        if abort_mode is None
        else Account.NONEXISTENT,
    }
    state_test(pre=pre, post=post, tx=tx)

Parametrized Test Cases

This test generates 6 parametrized test cases across 1 fork.