Skip to content

Commit 5e73bad

Browse files
authored
fix(tests): Fix and add cases for BALs tests for SSTORE OOG conditions (#2297)
* fix(tests): Fix and add cases for BALs tests for SSTORE OOG conditions * chore: add CHANGELOG entry for #2297 * chore: update test case reference for new SSTORE w/ OOG boundary cases * chore: update eels resolver to use latest specs commit
1 parent 131adfe commit 5e73bad

File tree

6 files changed

+69
-27
lines changed

6 files changed

+69
-27
lines changed

.github/configs/eels_resolutions.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,6 @@
5252
"Amsterdam": {
5353
"git_url": "https://github.com/fselmo/execution-specs.git",
5454
"branch": "feat/amsterdam-fork-and-block-access-lists",
55-
"commit": "f3ad59980a68fe5974244f41bcda7b22294bf98b"
55+
"commit": "3496e719b515bc82f35c42f83e78d426d31283ba"
5656
}
5757
}

docs/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Test fixtures for use by clients are available for each release on the [Github r
1919
### 🧪 Test Cases
2020

2121
- 🐞Fix BALs opcode OOG test vectors by updating the Amsterdam commit hash in specs and validating appropriately on the testing side ([#2293](https://github.com/ethereum/execution-spec-tests/pull/2293)).
22+
- ✨Fix test vector for BALs SSTORE with OOG by pointing to updated specs; add new boundary conditions cases for SSTORE w/ OOG ([#2297](https://github.com/ethereum/execution-spec-tests/pull/2297)).
2223

2324
## [v5.3.0](https://github.com/ethereum/execution-spec-tests/releases/tag/v5.3.0) - 2025-10-09
2425

src/ethereum_test_types/block_access_list/expectations.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,9 @@ def _compare_account_expectations(
350350

351351
# Check if explicitly set to empty but actual has values
352352
if not expected_list and actual_list:
353-
raise AssertionError(f"Expected {field_name} to be empty but found {actual_list}")
353+
raise BlockAccessListValidationError(
354+
f"Expected {field_name} to be empty but found {actual_list}"
355+
)
354356

355357
if field_name == "storage_reads":
356358
# storage_reads is a simple list of StorageKey
@@ -365,7 +367,7 @@ def _compare_account_expectations(
365367
actual_idx += 1
366368

367369
if not found:
368-
raise AssertionError(
370+
raise BlockAccessListValidationError(
369371
f"Storage read {expected_read} not found or not in correct order. "
370372
f"Actual reads: {actual_list}"
371373
)
@@ -403,7 +405,7 @@ def _compare_account_expectations(
403405
slot_actual_idx += 1
404406

405407
if not slot_found:
406-
raise AssertionError(
408+
raise BlockAccessListValidationError(
407409
f"Storage change {expected_change} not found "
408410
f"or not in correct order in slot "
409411
f"{expected_slot.slot}. "
@@ -416,7 +418,7 @@ def _compare_account_expectations(
416418
actual_idx += 1
417419

418420
if not found:
419-
raise AssertionError(
421+
raise BlockAccessListValidationError(
420422
f"Storage slot {expected_slot.slot} not found "
421423
f"or not in correct order. Actual slots: "
422424
f"{[s.slot for s in actual_list]}"
@@ -453,7 +455,7 @@ def _compare_account_expectations(
453455
actual_idx += 1
454456

455457
if not found:
456-
raise AssertionError(
458+
raise BlockAccessListValidationError(
457459
f"{item_type.capitalize()} change {exp_tuple} not found "
458460
f"or not in correct order. Actual changes: {actual_tuples}"
459461
)

src/pytest_plugins/eels_resolutions.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,6 @@
5555
"Amsterdam": {
5656
"git_url": "https://github.com/fselmo/execution-specs.git",
5757
"branch": "feat/amsterdam-fork-and-block-access-lists",
58-
"commit": "f3ad59980a68fe5974244f41bcda7b22294bf98b"
58+
"commit": "3496e719b515bc82f35c42f83e78d426d31283ba"
5959
}
6060
}

tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py

Lines changed: 58 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
preventing consensus issues.
1515
"""
1616

17+
from enum import Enum
18+
1719
import pytest
1820

1921
from ethereum_test_forks import Fork
@@ -44,28 +46,45 @@
4446
pytestmark = pytest.mark.valid_from("Amsterdam")
4547

4648

49+
class OutOfGasAt(Enum):
50+
"""
51+
Enumeration of specific gas boundaries where OOG can occur.
52+
"""
53+
54+
EIP_2200_STIPEND = "oog_at_eip2200_stipend"
55+
EIP_2200_STIPEND_PLUS_1 = "oog_at_eip2200_stipend_plus_1"
56+
EXACT_GAS_MINUS_1 = "oog_at_exact_gas_minus_1"
57+
58+
4759
@pytest.mark.parametrize(
48-
"fails_at_sstore", [True, False], ids=["oog_at_sstore", "successful_sstore"]
60+
"out_of_gas_at",
61+
[
62+
OutOfGasAt.EIP_2200_STIPEND,
63+
OutOfGasAt.EIP_2200_STIPEND_PLUS_1,
64+
OutOfGasAt.EXACT_GAS_MINUS_1,
65+
None, # no oog, successful sstore
66+
],
67+
ids=lambda x: x.value if x else "successful_sstore",
4968
)
5069
def test_bal_sstore_and_oog(
5170
pre: Alloc,
5271
blockchain_test: BlockchainTestFiller,
5372
fork: Fork,
54-
fails_at_sstore: bool,
73+
out_of_gas_at: OutOfGasAt | None,
5574
) -> None:
5675
"""
57-
Ensure BAL handles SSTORE and OOG during SSTORE appropriately.
76+
Test BAL recording with SSTORE at various OOG boundaries and success.
77+
78+
1. OOG at EIP-2200 stipend check & implicit SLOAD -> no BAL changes
79+
2. OOG post EIP-2200 stipend check & implicit SLOAD -> storage read in BAL
80+
3. OOG at exact gas minus 1 -> storage read in BAL
81+
4. exact gas (success) -> storage write in BAL
5882
"""
5983
alice = pre.fund_eoa()
6084
gas_costs = fork.gas_costs()
6185

6286
# Create contract that attempts SSTORE to cold storage slot 0x01
63-
storage_contract_code = Bytecode(
64-
Op.PUSH1(0x42) # Value to store
65-
+ Op.PUSH1(0x01) # Storage slot (cold)
66-
+ Op.SSTORE # Store value in slot - this will OOG
67-
+ Op.STOP
68-
)
87+
storage_contract_code = Bytecode(Op.SSTORE(0x01, 0x42))
6988

7089
storage_contract = pre.deploy_contract(code=storage_contract_code)
7190

@@ -75,33 +94,53 @@ def test_bal_sstore_and_oog(
7594
# Costs:
7695
# - PUSH1 (value and slot) = G_VERY_LOW * 2
7796
# - SSTORE cold (to zero slot) = G_STORAGE_SET + G_COLD_SLOAD
78-
sstore_cold_cost = gas_costs.G_STORAGE_SET + gas_costs.G_COLD_SLOAD
97+
sload_cost = gas_costs.G_COLD_SLOAD
98+
sstore_cost = gas_costs.G_STORAGE_SET
99+
sstore_cold_cost = sstore_cost + sload_cost
79100
push_cost = gas_costs.G_VERY_LOW * 2
80-
tx_gas_limit = intrinsic_gas_cost + push_cost + sstore_cold_cost
81-
82-
if fails_at_sstore:
83-
# subtract 1 gas to ensure OOG at SSTORE
84-
tx_gas_limit -= 1
101+
stipend = gas_costs.G_CALL_STIPEND
102+
103+
if out_of_gas_at == OutOfGasAt.EIP_2200_STIPEND:
104+
# 2300 after PUSHes (fails stipend check: 2300 <= 2300)
105+
tx_gas_limit = intrinsic_gas_cost + push_cost + stipend
106+
elif out_of_gas_at == OutOfGasAt.EIP_2200_STIPEND_PLUS_1:
107+
# 2301 after PUSHes (passes stipend, does SLOAD, fails charge_gas)
108+
tx_gas_limit = intrinsic_gas_cost + push_cost + stipend + 1
109+
elif out_of_gas_at == OutOfGasAt.EXACT_GAS_MINUS_1:
110+
# fail at charge_gas() at exact gas - 1 (boundary condition)
111+
tx_gas_limit = intrinsic_gas_cost + push_cost + sstore_cold_cost - 1
112+
else:
113+
# exact gas for successful SSTORE
114+
tx_gas_limit = intrinsic_gas_cost + push_cost + sstore_cold_cost
85115

86116
tx = Transaction(
87117
sender=alice,
88118
to=storage_contract,
89119
gas_limit=tx_gas_limit,
90120
)
91121

122+
# Storage read recorded only if we pass the stipend check and reach
123+
# implicit SLOAD (STIPEND_PLUS_1 and EXACT_GAS_MINUS_1)
124+
expect_storage_read = out_of_gas_at in (
125+
OutOfGasAt.EIP_2200_STIPEND_PLUS_1,
126+
OutOfGasAt.EXACT_GAS_MINUS_1,
127+
)
128+
expect_storage_write = out_of_gas_at is None
129+
92130
block = Block(
93131
txs=[tx],
94132
expected_block_access_list=BlockAccessListExpectation(
95133
account_expectations={
96134
storage_contract: BalAccountExpectation(
97-
storage_changes=[]
98-
if fails_at_sstore
99-
else [
135+
storage_changes=[
100136
BalStorageSlot(
101137
slot=0x01,
102138
slot_changes=[BalStorageChange(tx_index=1, post_value=0x42)],
103139
),
104140
]
141+
if expect_storage_write
142+
else [],
143+
storage_reads=[0x01] if expect_storage_read else [],
105144
)
106145
}
107146
),
@@ -112,7 +151,7 @@ def test_bal_sstore_and_oog(
112151
blocks=[block],
113152
post={
114153
alice: Account(nonce=1),
115-
storage_contract: Account(storage={} if fails_at_sstore else {0x01: 0x42}),
154+
storage_contract: Account(storage={0x01: 0x42} if expect_storage_write else {}),
116155
},
117156
)
118157

tests/amsterdam/eip7928_block_level_access_lists/test_cases.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
| `test_bal_7702_invalid_nonce_authorization` | Ensure BAL handles failed authorization due to wrong nonce | `Relayer` sends sponsored transaction to Bob (10 wei transfer succeeds) but Alice's authorization to delegate to `Oracle` uses incorrect nonce, causing silent authorization failure | BAL **MUST** include Alice with empty changes (account access), Bob with `balance_changes` (receives 10 wei), Relayer with `nonce_changes`. **MUST NOT** include `Oracle` (authorization failed, no delegation) | ✅ Completed |
4040
| `test_bal_7702_invalid_chain_id_authorization` | Ensure BAL handles failed authorization due to wrong chain id | `Relayer` sends sponsored transaction to Bob (10 wei transfer succeeds) but Alice's authorization to delegate to `Oracle` uses incorrect chain id, causing authorization failure before account access | BAL **MUST** include Bob with `balance_changes` (receives 10 wei), Relayer with `nonce_changes`. **MUST NOT** include Alice (authorization fails before loading account) or `Oracle` (authorization failed, no delegation) | ✅ Completed |
4141
| `test_bal_7702_delegated_via_call_opcode` | Ensure BAL captures delegation target when a contract uses *CALL opcodes to call a delegated account | Pre-deployed contract `Alice` delegated to `Oracle`. `Caller` contract uses CALL/CALLCODE/DELEGATECALL/STATICCALL to call `Alice`. Bob sends transaction to `Caller`. | BAL **MUST** include Bob: `nonce_changes`. `Caller`: empty changes (account access). `Alice`: empty changes (account access - delegated account being called). `Oracle`: empty changes (delegation target access). | ✅ Completed |
42-
| `test_bal_sstore_and_oog` | Ensure BAL handles OOG during SSTORE execution correctly | Alice calls contract that attempts `SSTORE` to cold slot `0x01`. Parameterized: (1) OOG at SSTORE opcode (insufficient gas), (2) Successful SSTORE execution. | For OOG case: BAL **MUST NOT** contain slot `0x01` in `storage_changes` since storage wasn't modified. For success case: BAL **MUST** contain slot `0x01` in `storage_changes`. | ✅ Completed |
42+
| `test_bal_sstore_and_oog` | Ensure BAL handles OOG during SSTORE execution at various gas boundaries (EIP-2200 stipend and implicit SLOAD) | Alice calls contract that attempts `SSTORE` to cold slot `0x01`. Parameterized: (1) OOG at EIP-2200 stipend check (2300 gas after PUSH opcodes) - fails before implicit SLOAD, (2) OOG at stipend + 1 (2301 gas) - passes stipend check but fails after implicit SLOAD, (3) OOG at exact gas - 1, (4) Successful SSTORE with exact gas. | For case (1): BAL **MUST NOT** include slot `0x01` in `storage_reads` or `storage_changes` (fails before implicit SLOAD). For cases (2) and (3): BAL **MUST** include slot `0x01` in `storage_reads` (implicit SLOAD occurred) but **MUST NOT** include in `storage_changes` (write didn't complete). For case (4): BAL **MUST** include slot `0x01` in `storage_changes` only (successful write; read is filtered by builder). | ✅ Completed |
4343
| `test_bal_sload_and_oog` | Ensure BAL handles OOG during SLOAD execution correctly | Alice calls contract that attempts `SLOAD` from cold slot `0x01`. Parameterized: (1) OOG at SLOAD opcode (insufficient gas), (2) Successful SLOAD execution. | For OOG case: BAL **MUST NOT** contain slot `0x01` in `storage_reads` since storage wasn't accessed. For success case: BAL **MUST** contain slot `0x01` in `storage_reads`. | ✅ Completed |
4444
| `test_bal_balance_and_oog` | Ensure BAL handles OOG during BALANCE opcode execution correctly | Alice calls contract that attempts `BALANCE` opcode on cold target account. Parameterized: (1) OOG at BALANCE opcode (insufficient gas), (2) Successful BALANCE execution. | For OOG case: BAL **MUST NOT** include target account (wasn't accessed). For success case: BAL **MUST** include target account in `account_changes`. | ✅ Completed |
4545
| `test_bal_extcodesize_and_oog` | Ensure BAL handles OOG during EXTCODESIZE opcode execution correctly | Alice calls contract that attempts `EXTCODESIZE` opcode on cold target contract. Parameterized: (1) OOG at EXTCODESIZE opcode (insufficient gas), (2) Successful EXTCODESIZE execution. | For OOG case: BAL **MUST NOT** include target contract (wasn't accessed). For success case: BAL **MUST** include target contract in `account_changes`. | ✅ Completed |

0 commit comments

Comments
 (0)