Skip to content

test_bal_cross_tx_balance_dependency()

Documentation for tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py::test_bal_cross_tx_balance_dependency@c74f1a67.

Generate fixtures for these test cases for Amsterdam with:

fill -v tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py::test_bal_cross_tx_balance_dependency --fork Amsterdam

Verify clients apply Tx1's balance change before executing Tx2 in the same block. Tx1 routes value into a contract; Tx2 invokes the contract which records its SELFBALANCE to storage. A client that parallelizes Tx2 without applying Tx1's balance_changes would record the pre-block balance, yielding a different state root. The selfdestruct variant routes the funds via SELFDESTRUCT from a pre-funded killer contract so the recipient's bytecode never runs in Tx1 — catching any client optimization that ties balance tracking to code execution.

Source code in tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
@pytest.mark.parametrize(
    "funding_method",
    ["direct_call", "selfdestruct"],
)
def test_bal_cross_tx_balance_dependency(
    pre: Alloc,
    blockchain_test: BlockchainTestFiller,
    funding_method: str,
) -> None:
    """
    Verify clients apply Tx1's balance change before executing Tx2 in
    the same block. Tx1 routes value into a contract; Tx2 invokes the
    contract which records its `SELFBALANCE` to storage. A client that
    parallelizes Tx2 without applying Tx1's `balance_changes` would
    record the pre-block balance, yielding a different state root. The
    `selfdestruct` variant routes the funds via SELFDESTRUCT from a
    pre-funded killer contract so the recipient's bytecode never runs
    in Tx1 — catching any client optimization that ties balance
    tracking to code execution.
    """
    transferred = 1
    alice = pre.fund_eoa()
    bob = pre.fund_eoa()

    # Any non-empty calldata triggers the SELFBALANCE record path;
    # empty calldata is the value-receiver path.
    contract = pre.deploy_contract(
        code=Conditional(
            condition=Op.ISZERO(Op.CALLDATASIZE),
            if_true=Op.STOP,
            if_false=Op.SSTORE(0, Op.SELFBALANCE),
        ),
    )

    if funding_method == "direct_call":
        tx_send = Transaction(sender=alice, to=contract, value=transferred)
        send_expectations: dict = {}
    elif funding_method == "selfdestruct":
        killer = pre.deploy_contract(
            code=Op.SELFDESTRUCT(contract),
            balance=transferred,
        )
        tx_send = Transaction(sender=alice, to=killer)
        send_expectations = {
            killer: BalAccountExpectation(
                balance_changes=[
                    BalBalanceChange(block_access_index=1, post_balance=0),
                ],
            ),
        }
    else:
        raise ValueError(f"unknown funding_method: {funding_method}")

    tx_read = Transaction(sender=bob, to=contract, data=b"\x01")

    account_expectations = {
        contract: BalAccountExpectation(
            balance_changes=[
                BalBalanceChange(
                    block_access_index=1, post_balance=transferred
                ),
            ],
            storage_changes=[
                BalStorageSlot(
                    slot=0,
                    slot_changes=[
                        BalStorageChange(
                            block_access_index=2, post_value=transferred
                        ),
                    ],
                ),
            ],
        ),
        **send_expectations,
    }

    blockchain_test(
        pre=pre,
        blocks=[
            Block(
                txs=[tx_send, tx_read],
                expected_block_access_list=BlockAccessListExpectation(
                    account_expectations=account_expectations
                ),
            )
        ],
        post={
            contract: Account(balance=transferred, storage={0: transferred}),
        },
    )

Parametrized Test Cases

This test generates 2 parametrized test cases across 1 fork.