Skip to content

Commit 244afe0

Browse files
raxhvlfselmo
andauthored
feat(tests): Extend coverage for BAL (#2897)
* 🧪 test: Extend create endowment * 🧪 test: Extend tx boundary test * feat(tests): 2D parametrize with (tx, withdrawal) for BAL gas limit test --------- Co-authored-by: raxhvl <raxhvl@users.noreply.github.com> Co-authored-by: fselmo <fselmo2@gmail.com>
1 parent 377a3aa commit 244afe0

3 files changed

Lines changed: 142 additions & 68 deletions

File tree

tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py

Lines changed: 126 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
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

tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3197,14 +3197,14 @@ def test_bal_create_and_oog(
31973197
)
31983198

31993199

3200+
@pytest.mark.with_all_create_opcodes
32003201
def test_bal_create_early_failure(
3201-
pre: Alloc,
3202-
blockchain_test: BlockchainTestFiller,
3202+
pre: Alloc, blockchain_test: BlockchainTestFiller, create_opcode: Op
32033203
) -> None:
32043204
"""
3205-
Test BAL with CREATE failure due to insufficient endowment.
3205+
Test BAL with CREATE/CREATE2 failure due to insufficient endowment.
32063206
3207-
Factory (balance=50) attempts CREATE(value=100).
3207+
Factory (balance=50) attempts CREATE/CREATE2(value=100).
32083208
Fails before nonce increment (before track_address).
32093209
Distinct from collision where address IS accessed.
32103210
@@ -3216,38 +3216,36 @@ def test_bal_create_early_failure(
32163216
alice = pre.fund_eoa()
32173217

32183218
factory_balance = 50
3219-
endowment = 100 # More than factory has
3219+
endowment = 100
32203220

3221-
# Simple init code that deploys STOP
32223221
init_code = Initcode(deploy_code=Op.STOP)
32233222
init_code_bytes = bytes(init_code)
32243223

3225-
# Factory code: CREATE(value=endowment) and store result in slot 0
32263224
factory_code = (
3227-
# Push init code to memory
32283225
Op.MSTORE(0, Op.PUSH32(init_code_bytes))
3229-
# SSTORE(0, CREATE(value, offset, size))
32303226
+ Op.SSTORE(
32313227
0x00,
3232-
Op.CREATE(
3233-
value=endowment, # 100 > 50, will fail
3228+
create_opcode(
3229+
value=endowment,
32343230
offset=32 - len(init_code_bytes),
32353231
size=len(init_code_bytes),
32363232
),
32373233
)
32383234
+ Op.STOP
32393235
)
32403236

3241-
# Deploy factory with insufficient balance for the CREATE endowment
32423237
factory = pre.deploy_contract(
32433238
code=factory_code,
32443239
balance=factory_balance,
3245-
storage={0x00: 0xDEAD}, # Initial value to prove SSTORE works
3240+
storage={0x00: 0xDEAD},
32463241
)
32473242

3248-
# Calculate what the contract address WOULD be (but it won't be created)
32493243
would_be_contract_address = compute_create_address(
3250-
address=factory, nonce=1
3244+
address=factory,
3245+
nonce=1,
3246+
salt=0,
3247+
initcode=init_code_bytes,
3248+
opcode=create_opcode,
32513249
)
32523250

32533251
tx = Transaction(
@@ -3266,9 +3264,7 @@ def test_bal_create_early_failure(
32663264
],
32673265
),
32683266
factory: BalAccountExpectation(
3269-
# NO nonce_changes - CREATE failed before increment_nonce
32703267
nonce_changes=[],
3271-
# Storage changes: slot 0 = 0xDEAD → 0 (CREATE returned 0)
32723268
storage_changes=[
32733269
BalStorageSlot(
32743270
slot=0x00,
@@ -3280,8 +3276,6 @@ def test_bal_create_early_failure(
32803276
)
32813277
],
32823278
),
3283-
# Contract address MUST NOT appear in BAL - never accessed
3284-
# (CREATE failed before track_address was called)
32853279
would_be_contract_address: None,
32863280
}
32873281
),
@@ -3292,11 +3286,9 @@ def test_bal_create_early_failure(
32923286
blocks=[block],
32933287
post={
32943288
alice: Account(nonce=1),
3295-
# Factory nonce unchanged (still 1), balance unchanged
32963289
factory: Account(
32973290
nonce=1, balance=factory_balance, storage={0x00: 0}
32983291
),
3299-
# Contract was never created
33003292
would_be_contract_address: Account.NONEXISTENT,
33013293
},
33023294
)

0 commit comments

Comments
 (0)