1414preventing consensus issues.
1515"""
1616
17+ from enum import Enum
18+
1719import pytest
1820
1921from ethereum_test_forks import Fork
4446pytestmark = 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)
5069def 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 = {} if out_of_gas_at is not None else {0x01 : 0x42 }),
116155 },
117156 )
118157
0 commit comments