Skip to content

Commit e95d7ad

Browse files
committed
fix(test): proper gas accounting across tests; should help with any gas changes
1 parent 2548b06 commit e95d7ad

32 files changed

Lines changed: 720 additions & 219 deletions

File tree

tests/amsterdam/eip7843_slotnum/test_slotnum.py

Lines changed: 47 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
Alloc,
77
Environment,
88
Fork,
9+
Header,
910
Op,
1011
StateTestFiller,
1112
Transaction,
@@ -32,6 +33,7 @@
3233
def test_slotnum_value(
3334
state_test: StateTestFiller,
3435
pre: Alloc,
36+
fork: Fork,
3537
slot_number: int,
3638
) -> None:
3739
"""
@@ -40,27 +42,37 @@ def test_slotnum_value(
4042
The slot number is provided by the consensus layer and should be
4143
accessible via the SLOTNUM opcode (0x4B).
4244
"""
43-
# Store SLOTNUM result at storage key 0
44-
code = Op.SSTORE(0, Op.SLOTNUM)
45+
# Store SLOTNUM result at storage key 0. Metadata pins the
46+
# storage transition (0->slot_number) so `code.gas_cost(fork)`
47+
# picks the right SSTORE branch under EIP-8037's 2D gas model.
48+
code = Op.SSTORE(
49+
key=0,
50+
value=Op.SLOTNUM,
51+
key_warm=False,
52+
original_value=0,
53+
new_value=slot_number,
54+
)
4555
code_address = pre.deploy_contract(code)
4656

57+
intrinsic_cost = fork.transaction_intrinsic_cost_calculator()()
58+
code_state = code.state_cost(fork)
59+
code_regular = code.gas_cost(fork) - code_state
60+
4761
tx = Transaction(
4862
sender=pre.fund_eoa(),
49-
gas_limit=100_000,
63+
gas_limit=intrinsic_cost + code_regular + code_state,
5064
to=code_address,
5165
)
5266

53-
post = {
54-
code_address: Account(
55-
storage={0: slot_number},
56-
),
57-
}
67+
# block.gas_used = max(regular_dimension, state_dimension).
68+
expected_gas_used = max(intrinsic_cost + code_regular, code_state)
5869

5970
state_test(
6071
env=Environment(slot_number=slot_number),
6172
pre=pre,
6273
tx=tx,
63-
post=post,
74+
post={code_address: Account(storage={0: slot_number})},
75+
blockchain_test_header_verify=Header(gas_used=expected_gas_used),
6476
)
6577

6678

@@ -88,25 +100,42 @@ def test_slotnum_gas_cost(
88100
callee_code = Op.SLOTNUM + Op.STOP
89101
callee_address = pre.deterministic_deploy_contract(deploy_code=callee_code)
90102

91-
# Caller calls the callee with limited gas and stores result
92-
caller_code = Op.SSTORE(0, Op.CALL(gas=call_gas, address=callee_address))
103+
# Caller calls the callee with `call_gas`; SSTOREs the call's
104+
# success bit (1 if SLOTNUM had enough gas, 0 if it OOG'd).
105+
sstore_value = 1 if call_succeeds else 0
106+
caller_code = Op.SSTORE(
107+
key=0,
108+
value=Op.CALL(
109+
gas=call_gas,
110+
address=callee_address,
111+
address_warm=False,
112+
),
113+
key_warm=False,
114+
original_value=0,
115+
new_value=sstore_value,
116+
)
93117
caller_address = pre.deploy_contract(caller_code)
94118

119+
intrinsic_cost = fork.transaction_intrinsic_cost_calculator()()
120+
code_state = caller_code.state_cost(fork)
121+
# Static opcode-metadata calc misses the gas burned in the inner
122+
# CALL frame; add it back. `call_gas` is the full forwarded amount
123+
# — for `enough_gas` SLOTNUM consumes it all; for `out_of_gas`
124+
# the OOG burns the entire forwarded budget.
125+
code_regular = caller_code.gas_cost(fork) - code_state + call_gas
126+
95127
tx = Transaction(
96128
sender=pre.fund_eoa(),
97-
gas_limit=100_000,
129+
gas_limit=intrinsic_cost + code_regular + code_state,
98130
to=caller_address,
99131
)
100132

101-
post = {
102-
caller_address: Account(
103-
storage={0: 1 if call_succeeds else 0},
104-
),
105-
}
133+
expected_gas_used = max(intrinsic_cost + code_regular, code_state)
106134

107135
state_test(
108136
env=Environment(slot_number=12345),
109137
pre=pre,
110138
tx=tx,
111-
post=post,
139+
post={caller_address: Account(storage={0: sstore_value})},
140+
blockchain_test_header_verify=Header(gas_used=expected_gas_used),
112141
)

tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py

Lines changed: 50 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -367,35 +367,51 @@ def test_bal_callcode_nested_value_transfer(
367367
"delegated_opcode",
368368
[
369369
pytest.param(
370-
lambda target_addr: Op.DELEGATECALL(
371-
50000, target_addr, 0, 0, 0, 0
370+
lambda target_addr, inner_gas: Op.DELEGATECALL(
371+
inner_gas, target_addr, 0, 0, 0, 0
372372
),
373373
id="delegatecall",
374374
),
375375
pytest.param(
376-
lambda target_addr: Op.CALLCODE(50000, target_addr, 0, 0, 0, 0, 0),
376+
lambda target_addr, inner_gas: Op.CALLCODE(
377+
inner_gas, target_addr, 0, 0, 0, 0, 0
378+
),
377379
id="callcode",
378380
),
379381
],
380382
)
381383
def test_bal_delegated_storage_writes(
382384
pre: Alloc,
383385
blockchain_test: BlockchainTestFiller,
384-
delegated_opcode: Callable[[Address], Op],
386+
delegated_opcode: Callable[[Address, int], Op],
387+
fork: Fork,
385388
) -> None:
386389
"""
387390
Ensure BAL captures delegated storage writes via
388391
DELEGATECALL and CALLCODE.
389392
"""
390393
alice = pre.fund_eoa()
391394

392-
# TargetContract that writes 0x42 to slot 0x01
393-
target_code = Op.SSTORE(0x01, 0x42)
395+
# TargetContract that writes 0x42 to slot 0x01.
396+
# Metadata pins the 0->0x42 transition so the gas calculator
397+
# accounts for SSTORE state gas under EIP-8037.
398+
target_code = Op.SSTORE.with_metadata(
399+
key_warm=False,
400+
original_value=0,
401+
current_value=0,
402+
new_value=0x42,
403+
)(0x01, 0x42)
394404
target_contract = pre.deploy_contract(code=target_code)
395405

406+
# Forward enough inner gas to cover both the regular and (under
407+
# EIP-8037) the spilled state-gas portion of the SSTORE — the
408+
# oracle frame inherits `state_gas_reservoir=0` since the outer
409+
# tx_gas stays below TX_MAX_GAS_LIMIT.
410+
inner_gas = target_code.gas_cost(fork) + 100 # small buffer
411+
396412
# Oracle contract that uses delegated opcode to execute
397413
# TargetContract's code
398-
oracle_code = delegated_opcode(target_contract)
414+
oracle_code = delegated_opcode(target_contract, inner_gas)
399415
oracle_contract = pre.deploy_contract(code=oracle_code)
400416

401417
tx = Transaction(
@@ -2133,6 +2149,7 @@ def test_bal_create_transaction_empty_code(
21332149
def test_bal_cross_tx_storage_revert_to_zero(
21342150
pre: Alloc,
21352151
blockchain_test: BlockchainTestFiller,
2152+
fork: Fork,
21362153
) -> None:
21372154
"""
21382155
Ensure BAL captures storage changes when tx1 writes a non-zero value
@@ -2148,20 +2165,42 @@ def test_bal_cross_tx_storage_revert_to_zero(
21482165
# Contract that writes to slot 0 based on calldata
21492166
contract = pre.deploy_contract(code=Op.SSTORE(0, Op.CALLDATALOAD(0)))
21502167

2168+
# Size each tx_gas_limit precisely against its SSTORE transition
2169+
# under EIP-8037's 2D gas model (regular + state).
2170+
intrinsic_calc = fork.transaction_intrinsic_cost_calculator()
2171+
tx1_data = Hash(0xABCD)
2172+
tx2_data = Hash(0x0)
2173+
tx1_code = Op.SSTORE.with_metadata(
2174+
key_warm=False,
2175+
original_value=0,
2176+
current_value=0,
2177+
new_value=0xABCD,
2178+
)(0, Op.CALLDATALOAD(0))
2179+
tx2_code = Op.SSTORE.with_metadata(
2180+
key_warm=False,
2181+
original_value=0xABCD,
2182+
current_value=0xABCD,
2183+
new_value=0x0,
2184+
)(0, Op.CALLDATALOAD(0))
2185+
21512186
# Tx1: Write slot 0 = 0xABCD
21522187
tx1 = Transaction(
21532188
sender=alice,
21542189
to=contract,
2155-
data=Hash(0xABCD),
2156-
gas_limit=100_000,
2190+
data=tx1_data,
2191+
gas_limit=(
2192+
intrinsic_calc(calldata=tx1_data) + tx1_code.gas_cost(fork)
2193+
),
21572194
)
21582195

21592196
# Tx2: Write slot 0 = 0x0 (revert to zero)
21602197
tx2 = Transaction(
21612198
sender=alice,
21622199
to=contract,
2163-
data=Hash(0x0),
2164-
gas_limit=100_000,
2200+
data=tx2_data,
2201+
gas_limit=(
2202+
intrinsic_calc(calldata=tx2_data) + tx2_code.gas_cost(fork)
2203+
),
21652204
)
21662205

21672206
account_expectations = {

tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_cross_index.py

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
BlockAccessListExpectation,
2222
BlockchainTestFiller,
2323
Bytecode,
24+
Fork,
2425
Op,
2526
Transaction,
2627
)
@@ -192,6 +193,7 @@ def test_bal_consolidation_contract_cross_index(
192193
def test_bal_noop_write_filtering(
193194
pre: Alloc,
194195
blockchain_test: BlockchainTestFiller,
196+
fork: Fork,
195197
) -> None:
196198
"""
197199
Test that NOOP writes (writing same value or 0 to empty) are filtered.
@@ -201,15 +203,37 @@ def test_bal_noop_write_filtering(
201203
2. Writing the same value to a slot doesn't appear in BAL
202204
3. Only actual changes are tracked
203205
"""
206+
# Metadata pins each SSTORE's actual transition so the gas
207+
# calculator picks the right branch under EIP-8037's 2D model.
204208
test_code = Bytecode(
205209
# Write 0 to uninitialized slot 1 (noop)
206-
Op.SSTORE(1, 0)
207-
# Write 42 to slot 2
208-
+ Op.SSTORE(2, 42)
209-
# Write 100 to slot 3 (will be same as pre-state, should be filtered)
210-
+ Op.SSTORE(3, 100)
211-
# Write 200 to slot 4 (different from pre-state 150, should appear)
212-
+ Op.SSTORE(4, 200)
210+
Op.SSTORE.with_metadata(
211+
key_warm=False,
212+
original_value=0,
213+
current_value=0,
214+
new_value=0,
215+
)(1, 0)
216+
# Write 42 to slot 2 (0->42, charges sstore_state_gas)
217+
+ Op.SSTORE.with_metadata(
218+
key_warm=False,
219+
original_value=0,
220+
current_value=0,
221+
new_value=42,
222+
)(2, 42)
223+
# Write 100 to slot 3 (same as pre-state, should be filtered)
224+
+ Op.SSTORE.with_metadata(
225+
key_warm=False,
226+
original_value=100,
227+
current_value=100,
228+
new_value=100,
229+
)(3, 100)
230+
# Write 200 to slot 4 (150->200, regular update)
231+
+ Op.SSTORE.with_metadata(
232+
key_warm=False,
233+
original_value=150,
234+
current_value=150,
235+
new_value=200,
236+
)(4, 200)
213237
)
214238

215239
sender = pre.fund_eoa()
@@ -218,10 +242,11 @@ def test_bal_noop_write_filtering(
218242
storage={3: 100, 4: 150},
219243
)
220244

245+
intrinsic_cost = fork.transaction_intrinsic_cost_calculator()()
221246
tx = Transaction(
222247
sender=sender,
223248
to=test_address,
224-
gas_limit=100_000,
249+
gas_limit=intrinsic_cost + test_code.gas_cost(fork),
225250
)
226251

227252
# Expected BAL should only show actual changes

tests/amsterdam/eip8024_dupn_swapn_exchange/test_swapn.py

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
Account,
1111
Alloc,
1212
Bytecode,
13+
Fork,
14+
Header,
1315
Op,
1416
StateTestFiller,
1517
Transaction,
@@ -142,6 +144,7 @@ def test_swapn_valid_immediates(
142144
def test_swapn_preserves_other_stack_items(
143145
pre: Alloc,
144146
state_test: StateTestFiller,
147+
fork: Fork,
145148
) -> None:
146149
"""Test SWAPN only swaps the specified items, leaving others unchanged."""
147150
sender = pre.fund_eoa()
@@ -151,6 +154,16 @@ def test_swapn_preserves_other_stack_items(
151154
stack_index = 17
152155
stack_height = stack_index + 1 # Need 18 items
153156

157+
# Compute expected storage values (post-swap stack reads).
158+
expected_storage: dict = {}
159+
for i in range(stack_height):
160+
if i == 0:
161+
expected_storage[i] = 0x1000 # Was at bottom, now at top
162+
elif i == stack_height - 1:
163+
expected_storage[i] = 0x1011 # Was at top, now at bottom
164+
else:
165+
expected_storage[i] = 0x1000 + (stack_height - 1 - i)
166+
154167
# Create a stack with 18 distinct values
155168
code = Bytecode()
156169
for i in range(stack_height):
@@ -160,31 +173,39 @@ def test_swapn_preserves_other_stack_items(
160173
# Pass stack index directly - encoder will handle encoding
161174
code += Op.SWAPN[stack_index]
162175

163-
# Store all values to verify only the swapped ones changed
176+
# Store all values; metadata pins each slot's 0->non-zero
177+
# transition so `code.gas_cost(fork)` accounts for SSTORE state
178+
# gas under EIP-8037.
164179
for i in range(stack_height):
165-
code += Op.PUSH1(i) + Op.SSTORE
180+
code += Op.PUSH1(i) + Op.SSTORE.with_metadata(
181+
key_warm=False,
182+
original_value=0,
183+
current_value=0,
184+
new_value=expected_storage[i],
185+
)
166186

167187
code += Op.STOP
168188

169189
contract_address = pre.deploy_contract(code=code)
170190

171-
tx = Transaction(to=contract_address, sender=sender, gas_limit=1_000_000)
191+
intrinsic_cost = fork.transaction_intrinsic_cost_calculator()()
192+
code_state = code.state_cost(fork)
193+
code_regular = code.gas_cost(fork) - code_state
172194

173-
# After swap: position 1 and position 18 are swapped
174-
# Original stack (top to bottom): 0x1011, 0x1010, ..., 0x1001, 0x1000
175-
# After SWAPN[0]: 0x1000, 0x1010, ..., 0x1001, 0x1011
176-
expected_storage = {}
177-
for i in range(stack_height):
178-
if i == 0:
179-
expected_storage[i] = 0x1000 # Was at bottom, now at top
180-
elif i == stack_height - 1:
181-
expected_storage[i] = 0x1011 # Was at top, now at bottom
182-
else:
183-
expected_storage[i] = 0x1000 + (stack_height - 1 - i)
195+
tx = Transaction(
196+
to=contract_address,
197+
sender=sender,
198+
gas_limit=intrinsic_cost + code_regular + code_state,
199+
)
184200

185-
post = {contract_address: Account(storage=expected_storage)}
201+
expected_gas_used = max(intrinsic_cost + code_regular, code_state)
186202

187-
state_test(pre=pre, post=post, tx=tx)
203+
state_test(
204+
pre=pre,
205+
post={contract_address: Account(storage=expected_storage)},
206+
tx=tx,
207+
blockchain_test_header_verify=Header(gas_used=expected_gas_used),
208+
)
188209

189210

190211
def test_swapn_stack_underflow(

0 commit comments

Comments
 (0)