Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,41 @@ def transform(bal: BlockAccessList) -> BlockAccessList:
return transform


def append_empty_slot(
address: Address, slot: int
) -> Callable[[BlockAccessList], BlockAccessList]:
"""
Append an empty BalStorageSlot (no changes) to an account's
storage_changes. Used by invalid-BAL tests to simulate a malformed
entry where a slot is recorded as changed but carries no actual change.
"""

def transform(bal: BlockAccessList) -> BlockAccessList:
from . import BalStorageSlot

found_address = False
new_root = []
for account_change in bal.root:
if account_change.address == address:
found_address = True
new_account = account_change.model_copy(deep=True)
new_account.storage_changes.append(
BalStorageSlot(slot=slot, slot_changes=[])
)
new_root.append(new_account)
else:
new_root.append(account_change)

if not found_address:
raise ValueError(
f"Address {address} not found in BAL to append empty slot"
)

return BlockAccessList(root=new_root)

return transform


def duplicate_account(
address: Address,
) -> Callable[[BlockAccessList], BlockAccessList]:
Expand Down Expand Up @@ -779,6 +814,7 @@ def transform(bal: BlockAccessList) -> BlockAccessList:
"append_account",
"append_change",
"append_storage",
"append_empty_slot",
"duplicate_account",
"reverse_accounts",
"keep_only",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
from execution_testing.test_types.block_access_list.modifiers import (
append_account,
append_change,
append_empty_slot,
append_storage,
duplicate_account,
duplicate_balance_change,
Expand Down Expand Up @@ -1053,6 +1054,72 @@ def test_bal_invalid_extraneous_entries(
)


@pytest.mark.valid_from("Amsterdam")
@pytest.mark.exception_test
@pytest.mark.parametrize(
"pre_storage,oracle_expectation,slot_to_inject",
[
pytest.param(
{},
BalAccountExpectation(
storage_changes=[
BalStorageSlot(
slot=0,
slot_changes=[
BalStorageChange(
block_access_index=1, post_value=0x42
)
],
)
],
),
1,
id="unrelated_slot",
),
pytest.param(
{0: 0x42},
BalAccountExpectation(storage_reads=[0]),
0,
id="demoted_noop",
),
],
)
def test_bal_invalid_empty_slot_changes(
blockchain_test: BlockchainTestFiller,
pre: Alloc,
pre_storage: dict,
oracle_expectation: BalAccountExpectation,
slot_to_inject: int,
) -> None:
"""Reject BAL containing a SlotChanges with an empty slot_changes list."""
alice = pre.fund_eoa()
oracle = pre.deploy_contract(code=Op.SSTORE(0, 0x42), storage=pre_storage)
tx = Transaction(sender=alice, to=oracle, gas_limit=1_000_000)

blockchain_test(
pre=pre,
post=pre,
blocks=[
Block(
txs=[tx],
exception=BlockException.INVALID_BLOCK_ACCESS_LIST,
expected_block_access_list=BlockAccessListExpectation(
account_expectations={
alice: BalAccountExpectation(
nonce_changes=[
BalNonceChange(
block_access_index=1, post_nonce=1
)
],
),
oracle: oracle_expectation,
}
).modify(append_empty_slot(oracle, slot=slot_to_inject)),
)
],
)


@pytest.mark.valid_from("Amsterdam")
@pytest.mark.exception_test
@pytest.mark.parametrize(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@
| `test_bal_7002_request_invalid` | Ensure BAL correctly handles invalid withdrawal request scenarios | Parameterized test with 8 invalid scenarios: (1) insufficient_fee (fee=0), (2) calldata_too_short (55 bytes), (3) calldata_too_long (57 bytes), (4) oog (insufficient gas), (5-7) invalid_call_type (DELEGATECALL/STATICCALL/CALLCODE), (8) contract_reverts. Tests both EOA and contract-based withdrawal requests. | BAL **MUST** include sender with `nonce_changes` at `block_access_index=1`. BAL **MUST** include system contract with `storage_reads` for slots: excess (slot 0), count (slot 1), head (slot 2), tail (slot 3). System contract **MUST NOT** have `storage_changes` (transaction failed, no queue modification). | βœ… Completed |
| `test_bal_invalid_extraneous_entries` | Verify clients reject blocks with any type of extraneous BAL entries | Alice sends 100 wei to Oracle contract (which reads storage slot 0). Charlie is uninvolved in this transaction. A valid BAL is created containing nonce change for Alice, balance change and storage read for Oracle. The BAL is corrupted by adding various extraneous entries: (1) extra_nonce, (2) extra_balance, (3) extra_code, (4) extra_storage_write_touched (slot 0 - already read), (5) extra_storage_write_untouched (slot 1 - not accessed), (6) extra_storage_write_uninvolved_account (Charlie - uninvolved account), (7) extra_account_access (Charlie), (8) extra_storage_read (slot 999). Each tested at block_access_index 1 (same tx), 2 (system tx), 3 (out of bounds). | Block **MUST** be rejected with `INVALID_BLOCK_ACCESS_LIST` exception. Clients **MUST** detect any extraneous entries in BAL. | βœ… Completed |
| `test_bal_invalid_duplicate_entries` | Verify clients reject blocks where BAL violates uniqueness constraints | Oracle writes storage, reads storage, and CREATEs a contract. BAL is corrupted with duplicate entries: (1) duplicate_nonce_change, (2) duplicate_balance_change, (3) duplicate_code_change, (4) duplicate_storage_slot, (5) duplicate_storage_read, (6) duplicate_slot_change, (7) storage_key_in_both_changes_and_reads. | Block **MUST** be rejected with `INVALID_BLOCK_ACCESS_LIST` exception. Each `block_access_index` must appear at most once per change list, each storage key at most once in `storage_changes` and `storage_reads`, and no key in both. | βœ… Completed |
| `test_bal_invalid_empty_slot_changes` | Verify clients reject BAL containing a storage slot entry with no changes | Parametrized: (1) `unrelated_slot`: Oracle writes one slot; BAL is corrupted to include a second, unrelated slot with no changes. (2) `demoted_noop`: Oracle writes a slot back to its existing value (no-op), so the slot is recorded as a read; BAL is corrupted to also record the same slot as a change with no changes. | Block **MUST** be rejected with `INVALID_BLOCK_ACCESS_LIST` exception. A storage slot in `storage_changes` **MUST** have at least one recorded change; a slot accessed without any change belongs in `storage_reads`. | βœ… Completed |
| `test_bal_invalid_missing_withdrawal_account` | Verify clients reject blocks where BAL is missing an account modified only by a withdrawal | Alice sends 5 wei to Bob (1 transaction). Charlie receives 10 gwei withdrawal. BAL modifier removes Charlie's entry entirely. | Block **MUST** be rejected with `INVALID_BLOCK_ACCESS_LIST` exception. Clients **MUST** detect that Charlie's balance was modified by the withdrawal but has no corresponding BAL entry. | βœ… Completed |
| `test_bal_invalid_missing_withdrawal_account_empty_block` | Verify clients reject blocks where BAL is missing a withdrawal-modified account in an empty block | Charlie receives 10 gwei withdrawal in block with no transactions. BAL modifier removes Charlie's entry entirely. | Block **MUST** be rejected with `INVALID_BLOCK_ACCESS_LIST` exception. Clients **MUST** detect withdrawal-modified accounts even when no transactions are present. | βœ… Completed |
| `test_bal_invalid_hash_mismatch` | Verify clients reject blocks where the BAL hash in the header does not match the actual BAL content | Alice sends value to Bob. BAL content is valid but header hash is overridden to a wrong value via `rlp_modifier`. Unlike other invalid BAL tests (which corrupt BAL content with matching hash), this keeps the BAL valid but injects a wrong header hash. | Block **MUST** be rejected with `INVALID_BAL_HASH` or `INVALID_BLOCK_HASH` exception. Clients **MUST** re-derive the BAL from block execution and compare its hash to the header, not just verify the BAL content is self-consistent. | βœ… Completed |
Expand Down
Loading