3131 StateTestFiller ,
3232 Transaction ,
3333 TransactionException ,
34+ Withdrawal ,
3435 add_kzg_version ,
3536 compute_create_address ,
3637)
@@ -2414,40 +2415,53 @@ def test_bal_create_transaction_empty_code(
24142415 )
24152416
24162417
2417- def test_bal_cross_tx_storage_revert_to_zero (
2418+ @pytest .mark .parametrize (
2419+ "tx2_value" ,
2420+ [
2421+ pytest .param (0x0 , id = "tx2_reverts_to_zero" ),
2422+ pytest .param (0xABCD , id = "tx2_rewrites_same_value" ),
2423+ ],
2424+ )
2425+ def test_bal_cross_tx_storage_write (
24182426 pre : Alloc ,
24192427 blockchain_test : BlockchainTestFiller ,
2428+ tx2_value : int ,
24202429) -> None :
24212430 """
2422- Ensure BAL captures storage changes when tx1 writes a non-zero value
2423- and tx2 reverts it back to zero. This is a regression test for the
2424- blobhash scenario where slot changes were being incorrectly filtered
2425- as net-zero across transaction boundaries.
2431+ Tx1's storage_change must be preserved regardless of tx2's write.
24262432
2427- Tx1: slot 0 = 0x0 -> 0xABCD (change at block_access_index=1)
2428- Tx2: slot 0 = 0xABCD -> 0x0 (change MUST be at block_access_index=2)
2433+ Regression for the blobhash scenario where back-to-pre writes were
2434+ filtered as net-zero across tx boundaries. The same-value case
2435+ additionally exercises the uniqueness rule: a slot in storage_changes
2436+ MUST NOT also appear in storage_reads.
24292437 """
24302438 alice = pre .fund_eoa ()
2439+ tx1_value = 0xABCD
24312440
2432- # Contract that writes to slot 0 based on calldata
24332441 contract = pre .deploy_contract (code = Op .SSTORE (0 , Op .CALLDATALOAD (0 )))
24342442
2435- # Tx1: Write slot 0 = 0xABCD
24362443 tx1 = Transaction (
24372444 sender = alice ,
24382445 to = contract ,
2439- data = Hash (0xABCD ),
2446+ data = Hash (tx1_value ),
24402447 gas_limit = 100_000 ,
24412448 )
24422449
2443- # Tx2: Write slot 0 = 0x0 (revert to zero)
24442450 tx2 = Transaction (
24452451 sender = alice ,
24462452 to = contract ,
2447- data = Hash (0x0 ),
2453+ data = Hash (tx2_value ),
24482454 gas_limit = 100_000 ,
24492455 )
24502456
2457+ slot_changes = [
2458+ BalStorageChange (block_access_index = 1 , post_value = tx1_value ),
2459+ ]
2460+ if tx2_value != tx1_value :
2461+ slot_changes .append (
2462+ BalStorageChange (block_access_index = 2 , post_value = tx2_value )
2463+ )
2464+
24512465 account_expectations = {
24522466 alice : BalAccountExpectation (
24532467 nonce_changes = [
@@ -2457,18 +2471,9 @@ def test_bal_cross_tx_storage_revert_to_zero(
24572471 ),
24582472 contract : BalAccountExpectation (
24592473 storage_changes = [
2460- BalStorageSlot (
2461- slot = 0 ,
2462- slot_changes = [
2463- BalStorageChange (
2464- block_access_index = 1 , post_value = 0xABCD
2465- ),
2466- # CRITICAL: tx2's write to 0x0 MUST appear
2467- # even though it returns slot to original value
2468- BalStorageChange (block_access_index = 2 , post_value = 0x0 ),
2469- ],
2470- ),
2474+ BalStorageSlot (slot = 0 , slot_changes = slot_changes ),
24712475 ],
2476+ storage_reads = [],
24722477 ),
24732478 }
24742479
@@ -2484,7 +2489,7 @@ def test_bal_cross_tx_storage_revert_to_zero(
24842489 ],
24852490 post = {
24862491 alice : Account (nonce = 2 ),
2487- contract : Account (storage = {0 : 0x0 }),
2492+ contract : Account (storage = {0 : tx2_value }),
24882493 },
24892494 )
24902495
@@ -3628,14 +3633,23 @@ def test_bal_lexicographic_address_ordering(
36283633@EIPChecklist .BlockLevelConstraint .Test .Boundary .Under ()
36293634@EIPChecklist .BlockLevelConstraint .Test .Boundary .Exact ()
36303635@EIPChecklist .BlockLevelConstraint .Test .Boundary .Over ()
3636+ @pytest .mark .parametrize (
3637+ "with_cl_withdrawal" ,
3638+ [
3639+ pytest .param (False , id = "no_cl_withdrawal" ),
3640+ pytest .param (True , id = "with_cl_withdrawal" ),
3641+ ],
3642+ )
3643+ @pytest .mark .parametrize (
3644+ "with_tx" ,
3645+ [pytest .param (False , id = "no_tx" ), pytest .param (True , id = "with_tx" )],
3646+ )
36313647@pytest .mark .parametrize (
36323648 "boundary_offset" ,
36333649 [
36343650 pytest .param (0 , id = "at_boundary" ),
36353651 pytest .param (
3636- - 1 ,
3637- marks = pytest .mark .exception_test ,
3638- id = "below_boundary" ,
3652+ - 1 , marks = pytest .mark .exception_test , id = "below_boundary"
36393653 ),
36403654 ],
36413655)
@@ -3644,37 +3658,105 @@ def test_bal_gas_limit_boundary(
36443658 pre : Alloc ,
36453659 fork : Fork ,
36463660 boundary_offset : int ,
3661+ with_tx : bool ,
3662+ with_cl_withdrawal : bool ,
36473663) -> None :
36483664 """
3649- Test the BAL max items gas limit boundary for an empty block.
3650-
3651- The consensus rule requires
3652- ``bal_items <= block_gas_limit // BLOCK_ACCESS_LIST_ITEM``.
3653- The boundary gas limit is derived from the fork's system-contract
3654- BAL footprint. At the boundary the check passes; one below it fails.
3665+ BAL max-items cap (``bal_items <= block_gas_limit //
3666+ BLOCK_ACCESS_LIST_ITEM``) must be enforced on the **final** BAL —
3667+ including pre-tx system work (beacon root, history), user txs, and
3668+ post-tx system work (CL withdrawals, queue processing).
3669+
3670+ Orthogonal axes:
3671+ - `with_tx`: alice → bob transfer adds 3 items (alice + bob +
3672+ coinbase warmed via EIP-3651).
3673+ - `with_cl_withdrawal`: EIP-4895 withdrawal to a recipient adds 1
3674+ item, processed between txs and the rest of the post-tx system
3675+ work. Together they catch clients that validate the cap before
3676+ `process_withdrawals` runs.
36553677 """
3656- # EIP-7928
3657- # bal_items <= block_gas_limit // ITEM_COST
3658- min_gas_limit = (
3659- fork .empty_block_bal_item_count ()
3660- * fork .gas_costs ().BLOCK_ACCESS_LIST_ITEM
3678+ # Match framework's DEFAULT_BASE_FEE so gas_price == base_fee
3679+ # cancels the priority fee (no coinbase balance_change to absorb
3680+ # into the expected counts).
3681+ base_fee_per_gas = 7
3682+
3683+ extra_items = 0
3684+ txs : list = []
3685+ withdrawals : list = []
3686+ expected_accounts : dict = {}
3687+ post : dict = {}
3688+
3689+ if with_tx :
3690+ alice = pre .fund_eoa ()
3691+ bob = pre .fund_eoa (amount = 0 )
3692+ # alice (sender) + bob (recipient) + coinbase (EIP-3651 warm).
3693+ extra_items += 3
3694+ txs .append (
3695+ Transaction (
3696+ sender = alice ,
3697+ to = bob ,
3698+ value = 1 ,
3699+ gas_limit = 21_000 ,
3700+ gas_price = base_fee_per_gas ,
3701+ )
3702+ )
3703+ expected_accounts [alice ] = BalAccountExpectation (
3704+ nonce_changes = [BalNonceChange (block_access_index = 1 , post_nonce = 1 )],
3705+ )
3706+ expected_accounts [bob ] = BalAccountExpectation (
3707+ balance_changes = [
3708+ BalBalanceChange (block_access_index = 1 , post_balance = 1 )
3709+ ],
3710+ )
3711+ post [bob ] = Account (balance = 1 )
3712+
3713+ if with_cl_withdrawal :
3714+ charlie = pre .fund_eoa (amount = 0 )
3715+ withdrawal_amount_wei = 10 ** 9 # 1 gwei
3716+ # CL withdrawal recipient adds 1 item; processed at
3717+ # block_access_index = len(txs) + 1 (post-tx).
3718+ extra_items += 1
3719+ withdrawals .append (
3720+ Withdrawal (index = 0 , validator_index = 0 , address = charlie , amount = 1 )
3721+ )
3722+ expected_accounts [charlie ] = BalAccountExpectation (
3723+ balance_changes = [
3724+ BalBalanceChange (
3725+ block_access_index = len (txs ) + 1 ,
3726+ post_balance = withdrawal_amount_wei ,
3727+ )
3728+ ],
3729+ )
3730+ post [charlie ] = Account (balance = withdrawal_amount_wei )
3731+
3732+ total_items = fork .empty_block_bal_item_count () + extra_items
3733+ gas_limit = (
3734+ total_items * fork .gas_costs ().BLOCK_ACCESS_LIST_ITEM + boundary_offset
36613735 )
3662- gas_limit = min_gas_limit + boundary_offset
36633736
3737+ at_boundary = boundary_offset == 0
36643738 block = Block (
3665- txs = [],
3739+ txs = txs ,
3740+ withdrawals = withdrawals ,
36663741 exception = (
3667- BlockException .BLOCK_ACCESS_LIST_GAS_LIMIT_EXCEEDED
3668- if boundary_offset < 0
3742+ None
3743+ if at_boundary
3744+ else BlockException .BLOCK_ACCESS_LIST_GAS_LIMIT_EXCEEDED
3745+ ),
3746+ expected_block_access_list = (
3747+ BlockAccessListExpectation (account_expectations = expected_accounts )
3748+ if at_boundary and expected_accounts
36693749 else None
36703750 ),
36713751 )
36723752
36733753 blockchain_test (
36743754 pre = pre ,
36753755 blocks = [block ],
3676- post = {},
3677- genesis_environment = Environment (gas_limit = gas_limit ),
3756+ post = post if at_boundary else {},
3757+ genesis_environment = Environment (
3758+ base_fee_per_gas = base_fee_per_gas , gas_limit = gas_limit
3759+ ),
36783760 )
36793761
36803762
0 commit comments