Skip to content

test_top_frame_charges_self_delegation_oog()

Documentation for tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_warmth_invariants.py::test_top_frame_charges_self_delegation_oog@26332146.

Generate fixtures for these test cases for Amsterdam with:

fill -v tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_warmth_invariants.py::test_top_frame_charges_self_delegation_oog --fork Amsterdam

Recipient holds a pre-existing EIP-7702 delegation pointing back at itself, and the transaction is one gas short of the delegation target's WARM_ACCESS charge.

The target of the resolution is the recipient itself, which is warm as tx.to, so the starved charge is the warm access -- a cold charge here would be a self-delegation warmth bug. The halt lands before dispatch, so the delegation prefix (whose leading 0xef decodes as INVALID) never runs; the sender pays the full gas_limit and no value moves.

Unlike a delegation to a distinct never-accessed account, the delegated address here is the recipient, whose code was already read to discover the delegation: per EIP-7928 it must appear in the block access list exactly once, with no recorded changes.

Source code in tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_warmth_invariants.py
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
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
@pytest.mark.parametrize(
    "value",
    [
        pytest.param(0, id="zero_value"),
        pytest.param(1, id="non-zero_value"),
    ],
)
def test_top_frame_charges_self_delegation_oog(
    fork: Fork,
    pre: Alloc,
    state_test: StateTestFiller,
    value: int,
) -> None:
    """
    Recipient holds a pre-existing EIP-7702 delegation pointing back at
    itself, and the transaction is one gas short of the delegation
    target's ``WARM_ACCESS`` charge.

    The target of the resolution is the recipient itself, which is warm
    as ``tx.to``, so the starved charge is the warm access --
    a cold charge here would be a self-delegation warmth bug. The halt
    lands before dispatch, so the delegation prefix (whose leading
    ``0xef`` decodes as ``INVALID``) never runs; the sender pays the
    full ``gas_limit`` and no value moves.

    Unlike a delegation to a distinct never-accessed account, the
    delegated address here *is* the recipient, whose code was already
    read to discover the delegation: per EIP-7928 it must appear in the
    block access list exactly once, with no recorded changes.
    """
    sender = pre.fund_eoa()

    # Pre-allocate an EOA that delegates to itself. The 1-wei balance
    # keeps the account alive at top-frame check time so the
    # ``NEW_ACCOUNT`` charge does not fire.
    target = pre.fund_eoa(amount=1, delegation="Self")
    target_code = Spec7702.delegation_designation(target)

    intrinsic_gas = fork.transaction_intrinsic_cost_calculator()(
        sends_value=bool(value),
        recipient_type=RecipientType.DELEGATION_7702,
        return_cost_deducted_prior_execution=True,
    )
    top_frame_gas = fork.transaction_top_frame_gas_calculator()(
        sends_value=bool(value),
        recipient_type=RecipientType.DELEGATION_7702,
        delegation_warm=True,
    )

    # One gas short of the warm self-access: the frame halts before
    # dispatching the (self-)delegated code. The receipt pins the full
    # ``gas_limit`` as consumed -- the out-of-gas signature (receipt
    # ``status`` is not verified by the filler).
    gas_limit = intrinsic_gas + top_frame_gas - 1

    tx = Transaction(
        sender=sender,
        to=target,
        value=value,
        gas_limit=gas_limit,
        expected_receipt=TransactionReceipt(
            cumulative_gas_used=gas_limit,
        ),
    )

    post = {
        sender: Account(nonce=1),
        target: Account(balance=1, code=target_code),
    }

    state_test(
        pre=pre,
        tx=tx,
        post=post,
        expected_block_access_list=BlockAccessListExpectation(
            account_expectations={
                target: BalAccountExpectation.empty(),
            }
        ),
    )

Parametrized Test Cases

This test generates 2 parametrized test cases across 1 fork.