3626
3627
3628
3629
3630
3631
3632
3633
3634
3635
3636
3637
3638
3639
3640
3641
3642
3643
3644
3645
3646
3647
3648
3649
3650
3651
3652
3653
3654
3655
3656
3657
3658
3659
3660
3661
3662
3663
3664
3665
3666
3667
3668
3669
3670
3671
3672
3673
3674
3675
3676
3677
3678
3679
3680
3681
3682
3683
3684
3685
3686
3687
3688
3689
3690
3691
3692
3693
3694
3695
3696
3697
3698
3699
3700
3701
3702
3703
3704
3705
3706
3707
3708
3709
3710
3711
3712
3713
3714
3715
3716
3717
3718
3719
3720
3721
3722
3723
3724
3725
3726
3727
3728
3729
3730
3731
3732
3733
3734
3735
3736
3737
3738
3739
3740
3741
3742
3743
3744
3745
3746
3747
3748
3749
3750
3751
3752
3753
3754
3755
3756 | @EIPChecklist.BlockLevelConstraint.Test.Boundary.Under()
@EIPChecklist.BlockLevelConstraint.Test.Boundary.Exact()
@EIPChecklist.BlockLevelConstraint.Test.Boundary.Over()
@pytest.mark.parametrize(
"with_cl_withdrawal",
[
pytest.param(False, id="no_cl_withdrawal"),
pytest.param(True, id="with_cl_withdrawal"),
],
)
@pytest.mark.parametrize(
"with_tx",
[pytest.param(False, id="no_tx"), pytest.param(True, id="with_tx")],
)
@pytest.mark.parametrize(
"boundary_offset",
[
pytest.param(0, id="at_boundary"),
pytest.param(
-1, marks=pytest.mark.exception_test, id="below_boundary"
),
],
)
def test_bal_gas_limit_boundary(
blockchain_test: BlockchainTestFiller,
pre: Alloc,
fork: Fork,
boundary_offset: int,
with_tx: bool,
with_cl_withdrawal: bool,
) -> None:
"""
BAL max-items cap (``bal_items <= block_gas_limit //
BLOCK_ACCESS_LIST_ITEM``) must be enforced on the **final** BAL —
including pre-tx system work (beacon root, history), user txs, and
post-tx system work (CL withdrawals, queue processing).
Orthogonal axes:
- `with_tx`: alice → bob transfer adds 3 items (alice + bob +
coinbase warmed via EIP-3651).
- `with_cl_withdrawal`: EIP-4895 withdrawal to a recipient adds 1
item, processed between txs and the rest of the post-tx system
work. Together they catch clients that validate the cap before
`process_withdrawals` runs.
"""
# Match framework's DEFAULT_BASE_FEE so gas_price == base_fee
# cancels the priority fee (no coinbase balance_change to absorb
# into the expected counts).
base_fee_per_gas = 7
extra_items = 0
txs: list = []
withdrawals: list = []
expected_accounts: dict = {}
post: dict = {}
if with_tx:
alice = pre.fund_eoa()
# Fund bob with 1 wei so the recipient is alive at top-frame
# check time; this avoids the EIP-2780 ``NEW_ACCOUNT`` state
# charge that would otherwise inflate the tx's gas needs past
# the BAL-sized ``block_gas_limit``.
bob = pre.fund_eoa(amount=1)
# alice (sender) + bob (recipient) + coinbase (EIP-3651 warm).
extra_items += 3
txs.append(
Transaction(
sender=alice,
to=bob,
value=1,
gas_price=base_fee_per_gas,
)
)
expected_accounts[alice] = BalAccountExpectation(
nonce_changes=[BalNonceChange(block_access_index=1, post_nonce=1)],
)
expected_accounts[bob] = BalAccountExpectation(
balance_changes=[
BalBalanceChange(block_access_index=1, post_balance=2)
],
)
post[bob] = Account(balance=2)
if with_cl_withdrawal:
charlie = pre.fund_eoa(amount=0)
withdrawal_amount_wei = 10**9 # 1 gwei
# CL withdrawal recipient adds 1 item; processed at
# block_access_index = len(txs) + 1 (post-tx).
extra_items += 1
withdrawals.append(
Withdrawal(index=0, validator_index=0, address=charlie, amount=1)
)
expected_accounts[charlie] = BalAccountExpectation(
balance_changes=[
BalBalanceChange(
block_access_index=len(txs) + 1,
post_balance=withdrawal_amount_wei,
)
],
)
post[charlie] = Account(balance=withdrawal_amount_wei)
total_items = fork.empty_block_bal_item_count() + extra_items
gas_limit = (
total_items * fork.gas_costs().BLOCK_ACCESS_LIST_ITEM + boundary_offset
)
at_boundary = boundary_offset == 0
block = Block(
txs=txs,
withdrawals=withdrawals,
exception=(
None
if at_boundary
else BlockException.BLOCK_ACCESS_LIST_GAS_LIMIT_EXCEEDED
),
expected_block_access_list=(
BlockAccessListExpectation(account_expectations=expected_accounts)
if at_boundary and expected_accounts
else None
),
)
blockchain_test(
pre=pre,
blocks=[block],
post=post if at_boundary else {},
genesis_environment=Environment(
base_fee_per_gas=base_fee_per_gas, gas_limit=gas_limit
),
)
|