Skip to content

Commit a4c74e8

Browse files
committed
fix(tests): strict discriminators for CREATE failure refund
Tests 1 and 2 previously used `expected = sstore_state_gas` exact, which held only because `tx_regular` happens to land below `sstore_state_gas` with current constants. Switch to `expected = max(tx_regular, tx_state)` computed defensively from `factory_code.gas_cost(fork)`, matching the pattern in test 4. Test 3 previously used a bare `state_test` + probe SSTORE with `tx.gas = gas_limit_cap + sstore_state_gas`. That leaves enough `gas_left` that the probe SSTORE succeeds via spillover whether the CREATE charge is refunded or not — the test passed even if the spec change were reverted. Rewrite to use the caller-wrapper + tight-gas-tuning pattern from tests 5 and 6 so the probe SSTORE can only succeed via the refunded reservoir. Verified: all 24 variants fail with the spec reverted and pass with the spec applied.
1 parent 1f7cecb commit a4c74e8

1 file changed

Lines changed: 60 additions & 13 deletions

File tree

tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py

Lines changed: 60 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1128,7 +1128,9 @@ def test_create_silent_failure_refunds_state_gas(
11281128
"""
11291129
gas_limit_cap = fork.transaction_gas_limit_cap()
11301130
assert gas_limit_cap is not None
1131+
gas_costs = fork.gas_costs()
11311132
sstore_state_gas = fork.sstore_state_gas()
1133+
intrinsic_cost = fork.transaction_intrinsic_cost_calculator()()
11321134

11331135
mstore_value, size = init_code_at_high_bytes(Op.STOP)
11341136
value = 1 if failure_mode == "insufficient_balance" else 0
@@ -1150,7 +1152,17 @@ def test_create_silent_failure_refunds_state_gas(
11501152
sender=pre.fund_eoa(),
11511153
)
11521154

1153-
expected = sstore_state_gas
1155+
# CREATE's GAS_NEW_ACCOUNT is refunded (silent failure, no child
1156+
# spawned). SSTORE's state portion is tracked separately in
1157+
# tx_state.
1158+
tx_regular = (
1159+
intrinsic_cost
1160+
+ factory_code.gas_cost(fork)
1161+
- gas_costs.GAS_NEW_ACCOUNT
1162+
- sstore_state_gas
1163+
)
1164+
tx_state = sstore_state_gas
1165+
expected = max(tx_regular, tx_state)
11541166
blockchain_test(
11551167
pre=pre,
11561168
blocks=[Block(txs=[tx], header_verify=Header(gas_used=expected))],
@@ -1186,7 +1198,9 @@ def test_create_child_revert_refunds_state_gas(
11861198
"""
11871199
gas_limit_cap = fork.transaction_gas_limit_cap()
11881200
assert gas_limit_cap is not None
1201+
gas_costs = fork.gas_costs()
11891202
sstore_state_gas = fork.sstore_state_gas()
1203+
intrinsic_cost = fork.transaction_intrinsic_cost_calculator()()
11901204

11911205
init_code = Op.REVERT(0, 0)
11921206
mstore_value, size = init_code_at_high_bytes(init_code)
@@ -1198,13 +1212,12 @@ def test_create_child_revert_refunds_state_gas(
11981212
)
11991213

12001214
storage = Storage()
1201-
factory = pre.deploy_contract(
1202-
code=(
1203-
Op.MSTORE(0, mstore_value)
1204-
+ Op.POP(create_call)
1205-
+ Op.SSTORE(storage.store_next(1, "reservoir_ok"), 1)
1206-
),
1215+
factory_code = (
1216+
Op.MSTORE(0, mstore_value)
1217+
+ Op.POP(create_call)
1218+
+ Op.SSTORE(storage.store_next(1, "reservoir_ok"), 1)
12071219
)
1220+
factory = pre.deploy_contract(code=factory_code)
12081221

12091222
gas_limit = (
12101223
gas_limit_cap
@@ -1217,7 +1230,19 @@ def test_create_child_revert_refunds_state_gas(
12171230
sender=pre.fund_eoa(),
12181231
)
12191232

1220-
expected = sstore_state_gas
1233+
# CREATE's GAS_NEW_ACCOUNT is refunded on child REVERT. SSTORE's
1234+
# state portion is tracked separately. Child REVERT regular
1235+
# (init_code execution) is propagated via
1236+
# incorporate_child_on_error.
1237+
tx_regular = (
1238+
intrinsic_cost
1239+
+ factory_code.gas_cost(fork)
1240+
- gas_costs.GAS_NEW_ACCOUNT
1241+
- sstore_state_gas
1242+
+ init_code.gas_cost(fork)
1243+
)
1244+
tx_state = sstore_state_gas
1245+
expected = max(tx_regular, tx_state)
12211246
blockchain_test(
12221247
pre=pre,
12231248
blocks=[Block(txs=[tx], header_verify=Header(gas_used=expected))],
@@ -1246,13 +1271,17 @@ def test_create_child_halt_refunds_state_gas(
12461271
12471272
Exceptional halts (invalid opcode, EIP-3541 invalid prefix)
12481273
consume all forwarded gas as `regular_gas_used`, so block
1249-
accounting cannot strictly discriminate via header gas. The
1250-
probe SSTORE confirms the reservoir holds the refunded
1251-
`GAS_NEW_ACCOUNT` for state-gas-dependent operations.
1274+
accounting cannot strictly discriminate via header gas. Tight
1275+
gas tuning via a caller wrapper leaves the factory with just
1276+
enough `gas_left` to pay the probe SSTORE's regular portion
1277+
but not enough to spill the state portion, so the probe SSTORE
1278+
can only succeed via the refunded reservoir.
12521279
"""
12531280
gas_limit_cap = fork.transaction_gas_limit_cap()
12541281
assert gas_limit_cap is not None
1282+
gas_costs = fork.gas_costs()
12551283
sstore_state_gas = fork.sstore_state_gas()
1284+
new_account_state_gas = gas_costs.GAS_NEW_ACCOUNT
12561285

12571286
init_code: Op | Bytecode
12581287
if failure_mode == "initcode_halt":
@@ -1278,9 +1307,27 @@ def test_create_child_halt_refunds_state_gas(
12781307
),
12791308
)
12801309

1310+
# Tight gas tuning: child halt consumes all forwarded gas as
1311+
# regular_gas_used. Factory retains
1312+
# ~(forwarded - pre_sstore_regular) / 64 after CREATE. Target
1313+
# the discrimination window `(probe_regular,
1314+
# probe_regular + sstore_state_gas)` so the probe SSTORE
1315+
# regular fits but state gas spillover from `gas_left` under
1316+
# the old behavior OOGs.
1317+
pre_sstore_code = Op.MSTORE(0, mstore_value) + Op.POP(create_call)
1318+
pre_sstore_regular = pre_sstore_code.gas_cost(fork) - new_account_state_gas
1319+
probe_code = Op.SSTORE(0, 1)
1320+
probe_regular = probe_code.gas_cost(fork) - sstore_state_gas
1321+
target_gas_left = probe_regular + sstore_state_gas // 2
1322+
forwarded_gas = target_gas_left * 64 + pre_sstore_regular
1323+
# Reservoir sized for CREATE charge only — SSTORE must pull
1324+
# from the refunded reservoir, not from spill.
1325+
caller = pre.deploy_contract(
1326+
code=Op.CALL(gas=forwarded_gas, address=factory)
1327+
)
12811328
tx = Transaction(
1282-
to=factory,
1283-
gas_limit=gas_limit_cap + sstore_state_gas,
1329+
to=caller,
1330+
gas_limit=gas_limit_cap + new_account_state_gas,
12841331
sender=pre.fund_eoa(),
12851332
)
12861333

0 commit comments

Comments
 (0)