Skip to content

test_initcode_selfdestruct_keeps_top_frame_state_charge()

Documentation for tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_top_frame_charges.py::test_initcode_selfdestruct_keeps_top_frame_state_charge@26332146.

Generate fixtures for these test cases for Amsterdam with:

fill -v tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_top_frame_charges.py::test_initcode_selfdestruct_keeps_top_frame_state_charge --fork Amsterdam

A creation transaction whose init code SELFDESTRUCTs keeps the top-frame NEW_ACCOUNT state charge consumed.

SELFDESTRUCT is a successful halt: the frame returns no output (an empty deposit, so no deposit charges) and no rollback runs, so the refill machinery that returns state gas on a revert or exceptional halt never triggers — even though the created account is destroyed at the end of the transaction (EIP-6780 same-tx deletion) and its leaf never persists. Deletion itself carries no state-gas credit: freeing state is not refunded.

Where the endowment ends up follows EIP-8246: destruction preserves a nonzero balance, so a self beneficiary leaves a balance-only leaf behind, while sweeping to an external beneficiary (or a zero endowment) removes the account entirely. Sweeping value to a not-yet-existing beneficiary additionally pays the opcode-level NEW_ACCOUNT and ACCOUNT_WRITE for the beneficiary — both the destroyed target's top-frame charge and the sweep's charge stay paid.

The receipt pins the exact total; a regression refilling the top-frame charge shows up as a 183,600 shortfall in cumulative_gas_used and a matching sender refund.

Source code in tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_top_frame_charges.py
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
@pytest.mark.parametrize(
    "beneficiary_kind",
    [
        pytest.param("self", id="self_beneficiary"),
        pytest.param("funded_external", id="funded_external_beneficiary"),
        pytest.param("empty_external", id="empty_external_beneficiary"),
    ],
)
@pytest.mark.parametrize(
    "value",
    [
        pytest.param(0, id="zero_value"),
        pytest.param(1, id="non-zero_value"),
    ],
)
def test_initcode_selfdestruct_keeps_top_frame_state_charge(
    fork: Fork,
    pre: Alloc,
    state_test: StateTestFiller,
    beneficiary_kind: str,
    value: int,
) -> None:
    """
    A creation transaction whose init code ``SELFDESTRUCT``s keeps the
    top-frame ``NEW_ACCOUNT`` state charge consumed.

    ``SELFDESTRUCT`` is a *successful* halt: the frame returns no
    output (an empty deposit, so no deposit charges) and no rollback
    runs, so the refill machinery that returns state gas on a revert or
    exceptional halt never triggers — even though the created account
    is destroyed at the end of the transaction (EIP-6780 same-tx
    deletion) and its leaf never persists. Deletion itself carries no
    state-gas credit: freeing state is not refunded.

    Where the endowment ends up follows EIP-8246: destruction preserves
    a nonzero balance, so a self beneficiary leaves a balance-only leaf
    behind, while sweeping to an external beneficiary (or a zero
    endowment) removes the account entirely. Sweeping value to a
    not-yet-existing beneficiary additionally pays the opcode-level
    ``NEW_ACCOUNT`` and ``ACCOUNT_WRITE`` for the beneficiary — both
    the destroyed target's top-frame charge and the sweep's charge stay
    paid.

    The receipt pins the exact total; a regression refilling the
    top-frame charge shows up as a 183,600 shortfall in
    ``cumulative_gas_used`` and a matching sender refund.
    """
    sender = pre.fund_eoa()
    created = compute_create_address(address=sender, nonce=sender.nonce)

    beneficiary: Address | None = None
    if beneficiary_kind == "self":
        # The created address is warmed for the create frame itself.
        init_code = Op.SELFDESTRUCT.with_metadata(
            address_warm=True, account_new=False
        )(Op.ADDRESS)
    elif beneficiary_kind == "funded_external":
        beneficiary = pre.fund_eoa(amount=EOA_INITIAL_BALANCE)
        init_code = Op.SELFDESTRUCT.with_metadata(
            address_warm=False, account_new=False
        )(beneficiary)
    else:
        beneficiary = pre.nonexistent_account()
        # Sweeping a non-zero balance into a non-existent leaf creates
        # the beneficiary, paying NEW_ACCOUNT (state) and ACCOUNT_WRITE
        # (execution) at the opcode.
        init_code = Op.SELFDESTRUCT.with_metadata(
            address_warm=False, account_new=bool(value)
        )(beneficiary)

    # Combined execution + state execution gas, including any sweep
    # charges modeled by the metadata above.
    exec_gas = init_code.gas_cost(fork)

    intrinsic_gas = fork.transaction_intrinsic_cost_calculator()(
        calldata=init_code,
        contract_creation=True,
        sends_value=bool(value),
        return_cost_deducted_prior_execution=True,
    )
    top_frame_state_gas = fork.transaction_top_frame_state_gas(
        contract_creation=True,
    )
    assert top_frame_state_gas > 0, (
        "a fresh create target must be charged top-frame state gas"
    )
    total_gas = intrinsic_gas + top_frame_state_gas + exec_gas

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

    post: dict[Address, Account | None] = {sender: Account(nonce=1)}
    if beneficiary_kind == "self":
        # EIP-8246: destruction preserves the balance, so a non-zero
        # endowment survives as a balance-only leaf.
        post[created] = (
            Account(balance=value, nonce=0, code=b"") if value else None
        )
    elif beneficiary_kind == "funded_external":
        assert beneficiary is not None
        post[created] = None
        post[beneficiary] = Account(balance=EOA_INITIAL_BALANCE + value)
    else:
        assert beneficiary is not None
        post[created] = None
        # A zero-value sweep does not bring the beneficiary to life.
        post[beneficiary] = Account(balance=value) if value else None

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

Parametrized Test Cases

This test generates 6 parametrized test cases across 1 fork.