Skip to content

test_receipt_status_top_frame_oog_between_successful_txs()

Documentation for tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_top_frame_charges.py::test_receipt_status_top_frame_oog_between_successful_txs@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_receipt_status_top_frame_oog_between_successful_txs --fork Amsterdam

Pin the failed receipt status of a top-frame OOG transaction that sits between two successful transactions in one block.

A transaction that out-of-gases on a top-frame charge never dispatches into the EVM but is still included and must produce a succeeded=False receipt, committed to the header receiptsRoot. The other top-frame OOG tests place the failing transaction alone in its block, so an implementation that derives the receipt status from stale shared per-block execution state still passes them: the stale value in a fresh block happens to be "failed". Sandwiching the failure between successful transactions makes the status byte load-bearing. (Regression: nimbus-eth1 1f8dd2122 receipted top-frame failures with the previous transaction's status and rejected finalized canonical blocks on glamsterdam-devnet-7 with receiptRoot mismatch.)

The middle transaction passes the intrinsic check but out-of-gases on a top-frame charge before any EVM bytecode runs:

  • create_state_oog: contract creation; the created account's NEW_ACCOUNT state charge fires at the top frame and the gas limit is one short of covering it.
  • new_account_state_oog: value transfer to an empty recipient; the NEW_ACCOUNT state charge fires and the gas limit is one short.
  • delegated_execution_oog: recipient holds an EIP-7702 delegation; the COLD_ACCOUNT_ACCESS execution charge fires and the gas limit is one short.

The failing transaction burns its full gas limit, bumps the sender nonce, and must produce a succeeded=False receipt between two succeeded=True receipts.

Source code in tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_top_frame_charges.py
 827
 828
 829
 830
 831
 832
 833
 834
 835
 836
 837
 838
 839
 840
 841
 842
 843
 844
 845
 846
 847
 848
 849
 850
 851
 852
 853
 854
 855
 856
 857
 858
 859
 860
 861
 862
 863
 864
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
@pytest.mark.parametrize(
    "failure_mode",
    [
        pytest.param(
            TopFrameFailureMode.CREATE_STATE_OOG,
            id="create_state_oog",
        ),
        pytest.param(
            TopFrameFailureMode.NEW_ACCOUNT_STATE_OOG,
            id="new_account_state_oog",
        ),
        pytest.param(
            TopFrameFailureMode.DELEGATED_EXECUTION_OOG,
            id="delegated_execution_oog",
        ),
    ],
)
def test_receipt_status_top_frame_oog_between_successful_txs(
    fork: Fork,
    pre: Alloc,
    blockchain_test: BlockchainTestFiller,
    failure_mode: TopFrameFailureMode,
) -> None:
    """
    Pin the failed receipt status of a top-frame OOG transaction that
    sits between two successful transactions in one block.

    A transaction that out-of-gases on a top-frame charge never
    dispatches into the EVM but is still included and must produce a
    ``succeeded=False`` receipt, committed to the header
    ``receiptsRoot``. The other top-frame OOG tests place the failing
    transaction alone in its block, so an implementation that derives
    the receipt status from stale shared per-block execution state
    still passes them: the stale value in a fresh block happens to be
    "failed". Sandwiching the failure between successful transactions
    makes the status byte load-bearing. (Regression: nimbus-eth1
    ``1f8dd2122`` receipted top-frame failures with the previous
    transaction's status and rejected finalized canonical blocks on
    glamsterdam-devnet-7 with ``receiptRoot mismatch``.)

    The middle transaction passes the intrinsic check but out-of-gases
    on a top-frame charge before any EVM bytecode runs:

    - ``create_state_oog``: contract creation; the created account's
      ``NEW_ACCOUNT`` state charge fires at the top frame and the gas
      limit is one short of covering it.
    - ``new_account_state_oog``: value transfer to an empty recipient;
      the ``NEW_ACCOUNT`` state charge fires and the gas limit is one
      short.
    - ``delegated_execution_oog``: recipient holds an EIP-7702
      delegation; the ``COLD_ACCOUNT_ACCESS`` execution charge fires and
      the gas limit is one short.

    The failing transaction burns its full gas limit, bumps the sender
    nonce, and must produce a ``succeeded=False`` receipt between two
    ``succeeded=True`` receipts.
    """
    gas_price = 1_000_000_000
    value = 1

    sender_initial_balance = 10**18
    ok_sender_1 = pre.fund_eoa(sender_initial_balance)
    ok_sender_2 = pre.fund_eoa(sender_initial_balance)
    fail_sender = pre.fund_eoa(sender_initial_balance)
    # Alive via balance, so the successful transfers to it incur no
    # top-frame charge and consume exactly their intrinsic gas.
    ok_recipient = pre.fund_eoa(amount=1)

    intrinsic_cost = fork.transaction_intrinsic_cost_calculator()

    fail_target: Address | None = None
    fail_target_post: Account | None = None
    if failure_mode is TopFrameFailureMode.CREATE_STATE_OOG:
        intrinsic_gas = intrinsic_cost(
            contract_creation=True,
            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, (
            "contract creation must charge NEW_ACCOUNT at the top frame"
        )
        fail_gas_limit = intrinsic_gas + top_frame_state_gas - 1
        fail_to: Address | None = None
    elif failure_mode is TopFrameFailureMode.NEW_ACCOUNT_STATE_OOG:
        intrinsic_gas = intrinsic_cost(
            sends_value=True,
            recipient_type=RecipientType.EMPTY_ACCOUNT,
            return_cost_deducted_prior_execution=True,
        )
        top_frame_state_gas = fork.transaction_top_frame_state_gas(
            sends_value=True,
            recipient_type=RecipientType.EMPTY_ACCOUNT,
        )
        assert top_frame_state_gas > 0, (
            "value transfer to an empty recipient must charge "
            "NEW_ACCOUNT at the top frame"
        )
        fail_gas_limit = intrinsic_gas + top_frame_state_gas - 1
        fail_to = pre.fund_eoa(amount=0)
        fail_target = fail_to
        # The rolled-back transfer must not bring the recipient into
        # existence.
        fail_target_post = None
    elif failure_mode is TopFrameFailureMode.DELEGATED_EXECUTION_OOG:
        delegated_to = pre.deploy_contract(code=Op.STOP)
        target_code = Spec7702.delegation_designation(delegated_to)
        fail_to = pre.deploy_contract(code=target_code)
        intrinsic_gas = intrinsic_cost(
            recipient_type=RecipientType.DELEGATION_7702,
            return_cost_deducted_prior_execution=True,
        )
        top_frame_gas = fork.transaction_top_frame_gas_calculator()(
            recipient_type=RecipientType.DELEGATION_7702,
        )
        assert top_frame_gas > 0, (
            "a delegated recipient must charge COLD_ACCOUNT_ACCESS "
            "at the top frame"
        )
        fail_gas_limit = intrinsic_gas + top_frame_gas - 1
        fail_target = fail_to
        fail_target_post = Account(balance=0, code=target_code)
    else:
        raise ValueError(f"unhandled failure mode: {failure_mode}")

    # The successful transfers go to an alive EOA: no top-frame charge,
    # no EVM execution, so each consumes exactly its intrinsic gas.
    ok_intrinsic_gas = intrinsic_cost(
        sends_value=True,
        recipient_type=RecipientType.EOA,
        return_cost_deducted_prior_execution=True,
    )
    assert (
        fork.transaction_top_frame_state_gas(
            sends_value=True,
            recipient_type=RecipientType.EOA,
        )
        == 0
    ), "an alive recipient must not incur a top-frame state charge"

    ok_tx_1 = Transaction(
        sender=ok_sender_1,
        to=ok_recipient,
        value=value,
        gas_limit=ok_intrinsic_gas,
        gas_price=gas_price,
        expected_receipt=TransactionReceipt(
            status=1,
            cumulative_gas_used=ok_intrinsic_gas,
        ),
    )
    fail_tx = Transaction(
        sender=fail_sender,
        to=fail_to,
        value=(
            value
            if failure_mode is TopFrameFailureMode.NEW_ACCOUNT_STATE_OOG
            else 0
        ),
        gas_limit=fail_gas_limit,
        gas_price=gas_price,
        expected_receipt=TransactionReceipt(
            status=0,
            gas_used=fail_gas_limit,
            cumulative_gas_used=ok_intrinsic_gas + fail_gas_limit,
        ),
    )
    ok_tx_2 = Transaction(
        sender=ok_sender_2,
        to=ok_recipient,
        value=value,
        gas_limit=ok_intrinsic_gas,
        gas_price=gas_price,
        expected_receipt=TransactionReceipt(
            status=1,
            cumulative_gas_used=2 * ok_intrinsic_gas + fail_gas_limit,
        ),
    )

    ok_sender_final_balance = (
        sender_initial_balance - value - ok_intrinsic_gas * gas_price
    )
    post: dict[Address, Account | None] = {
        ok_sender_1: Account(nonce=1, balance=ok_sender_final_balance),
        ok_sender_2: Account(nonce=1, balance=ok_sender_final_balance),
        ok_recipient: Account(balance=1 + 2 * value),
        # The failing transaction is included: the nonce bumps and the
        # full gas limit is paid, but nothing else happens.
        fail_sender: Account(
            nonce=1,
            balance=sender_initial_balance - fail_gas_limit * gas_price,
        ),
    }
    if failure_mode is TopFrameFailureMode.CREATE_STATE_OOG:
        post[fail_tx.created_contract] = None
    else:
        assert fail_target is not None
        post[fail_target] = fail_target_post

    blockchain_test(
        pre=pre,
        blocks=[Block(txs=[ok_tx_1, fail_tx, ok_tx_2])],
        post=post,
    )

Parametrized Test Cases

This test generates 3 parametrized test cases across 1 fork.