diff --git a/.github/configs/eels_resolutions.json b/.github/configs/eels_resolutions.json index 8c5a216428c..eb2c57b5404 100644 --- a/.github/configs/eels_resolutions.json +++ b/.github/configs/eels_resolutions.json @@ -52,6 +52,6 @@ "Amsterdam": { "git_url": "https://github.com/fselmo/execution-specs.git", "branch": "feat/amsterdam-fork-and-block-access-lists", - "commit": "f3ad59980a68fe5974244f41bcda7b22294bf98b" + "commit": "3496e719b515bc82f35c42f83e78d426d31283ba" } } diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 64f5cdd9da6..85079f5ece0 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -19,6 +19,7 @@ Test fixtures for use by clients are available for each release on the [Github r ### ๐Ÿงช Test Cases - ๐Ÿž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)). +- โœจ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)). ## [v5.3.0](https://github.com/ethereum/execution-spec-tests/releases/tag/v5.3.0) - 2025-10-09 diff --git a/src/ethereum_test_types/block_access_list/expectations.py b/src/ethereum_test_types/block_access_list/expectations.py index 4d9ecc7fa49..ac53ea3e838 100644 --- a/src/ethereum_test_types/block_access_list/expectations.py +++ b/src/ethereum_test_types/block_access_list/expectations.py @@ -350,7 +350,9 @@ def _compare_account_expectations( # Check if explicitly set to empty but actual has values if not expected_list and actual_list: - raise AssertionError(f"Expected {field_name} to be empty but found {actual_list}") + raise BlockAccessListValidationError( + f"Expected {field_name} to be empty but found {actual_list}" + ) if field_name == "storage_reads": # storage_reads is a simple list of StorageKey @@ -365,7 +367,7 @@ def _compare_account_expectations( actual_idx += 1 if not found: - raise AssertionError( + raise BlockAccessListValidationError( f"Storage read {expected_read} not found or not in correct order. " f"Actual reads: {actual_list}" ) @@ -403,7 +405,7 @@ def _compare_account_expectations( slot_actual_idx += 1 if not slot_found: - raise AssertionError( + raise BlockAccessListValidationError( f"Storage change {expected_change} not found " f"or not in correct order in slot " f"{expected_slot.slot}. " @@ -416,7 +418,7 @@ def _compare_account_expectations( actual_idx += 1 if not found: - raise AssertionError( + raise BlockAccessListValidationError( f"Storage slot {expected_slot.slot} not found " f"or not in correct order. Actual slots: " f"{[s.slot for s in actual_list]}" @@ -453,7 +455,7 @@ def _compare_account_expectations( actual_idx += 1 if not found: - raise AssertionError( + raise BlockAccessListValidationError( f"{item_type.capitalize()} change {exp_tuple} not found " f"or not in correct order. Actual changes: {actual_tuples}" ) diff --git a/src/pytest_plugins/eels_resolutions.json b/src/pytest_plugins/eels_resolutions.json index f1303268116..e95b1d823e9 100644 --- a/src/pytest_plugins/eels_resolutions.json +++ b/src/pytest_plugins/eels_resolutions.json @@ -55,6 +55,6 @@ "Amsterdam": { "git_url": "https://github.com/fselmo/execution-specs.git", "branch": "feat/amsterdam-fork-and-block-access-lists", - "commit": "f3ad59980a68fe5974244f41bcda7b22294bf98b" + "commit": "3496e719b515bc82f35c42f83e78d426d31283ba" } } diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py index 449f36cdede..40f16089a3d 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py @@ -14,6 +14,8 @@ preventing consensus issues. """ +from enum import Enum + import pytest from ethereum_test_forks import Fork @@ -44,28 +46,45 @@ pytestmark = pytest.mark.valid_from("Amsterdam") +class OutOfGasAt(Enum): + """ + Enumeration of specific gas boundaries where OOG can occur. + """ + + EIP_2200_STIPEND = "oog_at_eip2200_stipend" + EIP_2200_STIPEND_PLUS_1 = "oog_at_eip2200_stipend_plus_1" + EXACT_GAS_MINUS_1 = "oog_at_exact_gas_minus_1" + + @pytest.mark.parametrize( - "fails_at_sstore", [True, False], ids=["oog_at_sstore", "successful_sstore"] + "out_of_gas_at", + [ + OutOfGasAt.EIP_2200_STIPEND, + OutOfGasAt.EIP_2200_STIPEND_PLUS_1, + OutOfGasAt.EXACT_GAS_MINUS_1, + None, # no oog, successful sstore + ], + ids=lambda x: x.value if x else "successful_sstore", ) def test_bal_sstore_and_oog( pre: Alloc, blockchain_test: BlockchainTestFiller, fork: Fork, - fails_at_sstore: bool, + out_of_gas_at: OutOfGasAt | None, ) -> None: """ - Ensure BAL handles SSTORE and OOG during SSTORE appropriately. + Test BAL recording with SSTORE at various OOG boundaries and success. + + 1. OOG at EIP-2200 stipend check & implicit SLOAD -> no BAL changes + 2. OOG post EIP-2200 stipend check & implicit SLOAD -> storage read in BAL + 3. OOG at exact gas minus 1 -> storage read in BAL + 4. exact gas (success) -> storage write in BAL """ alice = pre.fund_eoa() gas_costs = fork.gas_costs() # Create contract that attempts SSTORE to cold storage slot 0x01 - storage_contract_code = Bytecode( - Op.PUSH1(0x42) # Value to store - + Op.PUSH1(0x01) # Storage slot (cold) - + Op.SSTORE # Store value in slot - this will OOG - + Op.STOP - ) + storage_contract_code = Bytecode(Op.SSTORE(0x01, 0x42)) storage_contract = pre.deploy_contract(code=storage_contract_code) @@ -75,13 +94,24 @@ def test_bal_sstore_and_oog( # Costs: # - PUSH1 (value and slot) = G_VERY_LOW * 2 # - SSTORE cold (to zero slot) = G_STORAGE_SET + G_COLD_SLOAD - sstore_cold_cost = gas_costs.G_STORAGE_SET + gas_costs.G_COLD_SLOAD + sload_cost = gas_costs.G_COLD_SLOAD + sstore_cost = gas_costs.G_STORAGE_SET + sstore_cold_cost = sstore_cost + sload_cost push_cost = gas_costs.G_VERY_LOW * 2 - tx_gas_limit = intrinsic_gas_cost + push_cost + sstore_cold_cost - - if fails_at_sstore: - # subtract 1 gas to ensure OOG at SSTORE - tx_gas_limit -= 1 + stipend = gas_costs.G_CALL_STIPEND + + if out_of_gas_at == OutOfGasAt.EIP_2200_STIPEND: + # 2300 after PUSHes (fails stipend check: 2300 <= 2300) + tx_gas_limit = intrinsic_gas_cost + push_cost + stipend + elif out_of_gas_at == OutOfGasAt.EIP_2200_STIPEND_PLUS_1: + # 2301 after PUSHes (passes stipend, does SLOAD, fails charge_gas) + tx_gas_limit = intrinsic_gas_cost + push_cost + stipend + 1 + elif out_of_gas_at == OutOfGasAt.EXACT_GAS_MINUS_1: + # fail at charge_gas() at exact gas - 1 (boundary condition) + tx_gas_limit = intrinsic_gas_cost + push_cost + sstore_cold_cost - 1 + else: + # exact gas for successful SSTORE + tx_gas_limit = intrinsic_gas_cost + push_cost + sstore_cold_cost tx = Transaction( sender=alice, @@ -89,19 +119,28 @@ def test_bal_sstore_and_oog( gas_limit=tx_gas_limit, ) + # Storage read recorded only if we pass the stipend check and reach + # implicit SLOAD (STIPEND_PLUS_1 and EXACT_GAS_MINUS_1) + expect_storage_read = out_of_gas_at in ( + OutOfGasAt.EIP_2200_STIPEND_PLUS_1, + OutOfGasAt.EXACT_GAS_MINUS_1, + ) + expect_storage_write = out_of_gas_at is None + block = Block( txs=[tx], expected_block_access_list=BlockAccessListExpectation( account_expectations={ storage_contract: BalAccountExpectation( - storage_changes=[] - if fails_at_sstore - else [ + storage_changes=[ BalStorageSlot( slot=0x01, slot_changes=[BalStorageChange(tx_index=1, post_value=0x42)], ), ] + if expect_storage_write + else [], + storage_reads=[0x01] if expect_storage_read else [], ) } ), @@ -112,7 +151,7 @@ def test_bal_sstore_and_oog( blocks=[block], post={ alice: Account(nonce=1), - storage_contract: Account(storage={} if fails_at_sstore else {0x01: 0x42}), + storage_contract: Account(storage={0x01: 0x42} if expect_storage_write else {}), }, ) diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_cases.md b/tests/amsterdam/eip7928_block_level_access_lists/test_cases.md index 8a344fbc4fe..945330b628e 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_cases.md +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_cases.md @@ -39,7 +39,7 @@ | `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 | | `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 | | `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 | -| `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 | +| `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 | | `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 | | `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 | | `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 |