Skip to content

test_same_tx_created_selfdestruct_self_burn()

Documentation for tests/amsterdam/eip8038_state_access_gas_cost_increase/test_selfdestruct_gas.py::test_same_tx_created_selfdestruct_self_burn@26332146.

Generate fixtures for these test cases for Amsterdam with:

fill -v tests/amsterdam/eip8038_state_access_gas_cost_increase/test_selfdestruct_gas.py::test_same_tx_created_selfdestruct_self_burn --fork Amsterdam

EIP-6780: a same-tx-created contract SELFDESTRUCTs to itself, charged the warm base only.

A creation transaction whose initcode SELFDESTRUCTs the new contract to ITSELF: the originator is created in this transaction so it is deleted, and because a same-tx-created contract holding balance is alive, account_new is false for the self-beneficiary — execution = OPCODE_SELFDESTRUCT_BASE (warm self, no ACCOUNT_WRITE) and no SELFDESTRUCT state gas.

EIP-8246 removes the SELFDESTRUCT burn, so the self-send is a no-op: the balance stays in the (otherwise emptied) originator and no log is emitted.

No net state gas is charged either way: under EIP-2780 the create-tx NEW_ACCOUNT is a top-frame charge levied only when the target is EMPTY pre-tx, but the pre-funded created target already has a balance, so it is never charged. The self-burn adds no state gas, so the block gas_used is the pure execution consumption regardless of the burn behavior.

Source code in tests/amsterdam/eip8038_state_access_gas_cost_increase/test_selfdestruct_gas.py
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement()
@pytest.mark.pre_alloc_mutable()
def test_same_tx_created_selfdestruct_self_burn(
    state_test: StateTestFiller,
    pre: Alloc,
    fork: Fork,
) -> None:
    """
    EIP-6780: a same-tx-created contract SELFDESTRUCTs to itself, charged
    the warm base only.

    A creation transaction whose initcode SELFDESTRUCTs the new contract
    to ITSELF: the originator is created in this transaction so it is
    deleted, and because a same-tx-created contract holding balance is
    alive, ``account_new`` is false for the self-beneficiary —
    ``execution = OPCODE_SELFDESTRUCT_BASE`` (warm self, no
    ``ACCOUNT_WRITE``) and no
    SELFDESTRUCT state gas.

    EIP-8246 removes the SELFDESTRUCT burn, so the self-send is a no-op:
    the balance stays in the (otherwise emptied) originator and no log is
    emitted.

    No net state gas is charged either way: under EIP-2780 the create-tx
    ``NEW_ACCOUNT`` is a top-frame charge levied only when the target is
    ``EMPTY`` pre-tx, but the pre-funded created target already has a
    balance, so it is never charged. The self-burn adds no state gas, so
    the block ``gas_used`` is the pure execution consumption regardless of
    the burn behavior.
    """
    intrinsic_calc = fork.transaction_intrinsic_cost_calculator()

    amount = 1
    sender = pre.fund_eoa(amount=10**18)
    created = compute_create_address(address=sender, nonce=0)
    # Pre-fund the created address so its balance is present without an
    # in-tx value transfer (which would emit its own Transfer log). The
    # pre-funded target is not EMPTY pre-tx, so the top-frame NEW_ACCOUNT
    # is never charged.
    pre.fund_address(created, amount)

    # Self is the executing account, warm on entry: no cold surcharge.
    init_code = Op.SELFDESTRUCT.with_metadata(address_warm=True)(Op.ADDRESS)

    # Self-beneficiary on a balance-bearing same-tx-created contract is
    # alive: account_new is false, so only the warm base is charged.
    # Creation intrinsic is execution-only under EIP-2780; the pre-existing
    # target adds no top-frame NEW_ACCOUNT and the self-burn adds no state
    # gas, so net state gas is zero. The execution consumption exceeds the
    # decomposed calldata floor, so the floor never pins the billing.
    intrinsic_execution = intrinsic_calc(
        calldata=bytes(init_code),
        contract_creation=True,
        return_cost_deducted_prior_execution=True,
    )
    expected_execution = intrinsic_execution + init_code.execution_cost(fork)
    expected_gas_used = expected_execution

    # EIP-8246 removes the SELFDESTRUCT burn: the self-send is a no-op,
    # the balance stays in the (otherwise emptied) originator, and no
    # log is emitted.
    expected_logs: list[TransactionLog] = []
    created_post = Account(balance=amount, nonce=0, code=b"", storage={})

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

    state_test(
        pre=pre,
        post={created: created_post},
        tx=tx,
    )

Parametrized Test Cases

This test generates 1 parametrized test case across 1 fork.