Skip to content

test_same_tx_created_selfdestruct_to_fresh_beneficiary()

Documentation for tests/amsterdam/eip8038_state_access_gas_cost_increase/test_selfdestruct_gas.py::test_same_tx_created_selfdestruct_to_fresh_beneficiary@2867859a.

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

EIP-6780: a same-tx-created contract sends value to a fresh beneficiary, charged ACCOUNT_WRITE and creation state gas.

A creation transaction whose initcode SELFDESTRUCTs the new contract to a fresh Address(0xDEAD): the fresh, non-existent beneficiary receives a positive balance, so account_new is true — execution = OPCODE_SELFDESTRUCT_BASE + COLD_ACCOUNT_ACCESS + ACCOUNT_WRITE plus a beneficiary NEW_ACCOUNT on the state axis. The beneficiary creation charge keys on the beneficiary, while the originator (created in this transaction) is still deleted: a Transfer log is emitted (not a Burn).

The net state gas is a single beneficiary NEW_ACCOUNT: the intrinsic creation NEW_ACCOUNT is refunded because the pre-funded created target is alive at message entry (EIP-8037), while the fresh beneficiary's NEW_ACCOUNT persists.

Source code in tests/amsterdam/eip8038_state_access_gas_cost_increase/test_selfdestruct_gas.py
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement()
@pytest.mark.pre_alloc_mutable()
def test_same_tx_created_selfdestruct_to_fresh_beneficiary(
    state_test: StateTestFiller,
    pre: Alloc,
    fork: Fork,
) -> None:
    """
    EIP-6780: a same-tx-created contract sends value to a fresh
    beneficiary, charged ``ACCOUNT_WRITE`` and creation state gas.

    A creation transaction whose initcode SELFDESTRUCTs the new contract
    to a fresh ``Address(0xDEAD)``: the fresh, non-existent beneficiary
    receives a positive balance, so ``account_new`` is true —
    ``execution = OPCODE_SELFDESTRUCT_BASE + COLD_ACCOUNT_ACCESS +
    ACCOUNT_WRITE`` plus a beneficiary ``NEW_ACCOUNT`` on the state
    axis. The
    beneficiary creation charge keys on the beneficiary, while the
    originator (created in this transaction) is still deleted: a
    ``Transfer`` log is emitted (not a ``Burn``).

    The net state gas is a single beneficiary ``NEW_ACCOUNT``: the
    intrinsic creation ``NEW_ACCOUNT`` is refunded because the pre-funded
    created target is alive at message entry (EIP-8037), while the fresh
    beneficiary's ``NEW_ACCOUNT`` persists.
    """
    intrinsic_calc = fork.transaction_intrinsic_cost_calculator()

    amount = 1
    beneficiary = Address(0xDEAD)  # fresh, non-existent
    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 alive at message entry, so the create-tx
    # intrinsic NEW_ACCOUNT is refunded (EIP-8037).
    pre.fund_address(created, amount)

    # Cold beneficiary receiving value: account_new is true.
    init_code = Op.SELFDESTRUCT.with_metadata(
        address_warm=False, account_new=True
    )(beneficiary)
    # The creation NEW_ACCOUNT is refunded (target alive at entry) and is
    # not part of the intrinsic under EIP-2780; only the fresh
    # beneficiary's NEW_ACCOUNT (the SELFDESTRUCT state cost) persists.
    new_account_state_gas = init_code.state_cost(fork)

    intrinsic_execution = intrinsic_calc(
        calldata=bytes(init_code), contract_creation=True
    )
    expected_state = new_account_state_gas
    expected_execution = intrinsic_execution + init_code.execution_cost(fork)
    expected_gas_used = max(expected_execution, expected_state)

    tx = Transaction(
        to=None,
        data=init_code,
        sender=sender,
        # Reservoir holds the beneficiary-creation state gas (above the
        # creation's intrinsic NEW_ACCOUNT) so it does not spill into
        # execution gas.
        state_gas_reservoir=new_account_state_gas,
        expected_receipt=TransactionReceipt(
            logs=[transfer_log(created, beneficiary, amount)]
        ),
    )

    state_test(
        pre=pre,
        # Same-tx-created originator is deleted; the fresh beneficiary is
        # created and credited the originator balance.
        post={
            created: Account.NONEXISTENT,
            beneficiary: Account(balance=amount),
        },
        tx=tx,
        blockchain_test_header_verify=Header(gas_used=expected_gas_used),
    )

Parametrized Test Cases

This test generates 1 parametrized test case across 1 fork.