Skip to content

Commit 63eae5f

Browse files
committed
feat(tests): update EIP-8037 to use source based refunds
1 parent 3b358d9 commit 63eae5f

4 files changed

Lines changed: 217 additions & 194 deletions

File tree

tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_call.py

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1282,30 +1282,32 @@ def test_call_value_to_pre_existing_selfdestructed_account(
12821282
],
12831283
)
12841284
@pytest.mark.valid_from("EIP8037")
1285-
def test_top_level_halt_refunds_total_state_gas(
1285+
def test_top_level_halt_burns_spilled_state_gas(
12861286
blockchain_test: BlockchainTestFiller,
12871287
pre: Alloc,
12881288
fork: Fork,
12891289
child_termination: str,
12901290
reservoir_delta: int,
12911291
) -> None:
12921292
"""
1293-
Verify a top-level halt refunds the total state-gas consumed
1294-
(reservoir-portion + spilled-portion) regardless of child failure
1295-
mode. The parent calls a child that either reverts or halts, then
1296-
INVALIDs at the top level.
1297-
1298-
Per the updated EIP, both child failure modes propagate the full
1299-
`state_gas_used` back through `incorporate_child_on_error`, and
1300-
the top-level halt no longer overrides it. The tx-level error
1301-
handler then folds the residual into the reservoir, so
1302-
`state_gas_left_end = max(reservoir, child_charge)` and
1303-
`tx_gas_used = tx.gas - state_gas_left_end`:
1304-
1305-
- `reservoir < child_charge` (one_short): spill is refunded too,
1306-
`tx_gas_used = gas_limit_cap - (child_charge - reservoir)`.
1307-
- `reservoir >= child_charge`: no spill, `tx_gas_used =
1308-
gas_limit_cap`.
1293+
Verify a top-level halt burns the spilled state gas, so only the
1294+
start reservoir survives. The parent calls a child that reverts or
1295+
halts, then INVALIDs at the top level.
1296+
1297+
Under LIFO refills a frame's spilled state gas refills to
1298+
`gas_left`, which the halt then zeros. Only the reservoir-funded
1299+
portion survives, equal to the reservoir at frame start.
1300+
1301+
That start value equals the sized reservoir R, so for every child
1302+
failure mode and `reservoir_delta`:
1303+
1304+
`state_gas_left_end = R`,
1305+
`tx_gas_used = tx.gas - R = gas_limit_cap`.
1306+
1307+
With the reservoir one short (`reservoir_delta == -1`) the child's
1308+
SSTORE spills one unit from `gas_left`, which is refilled then
1309+
burned by the halt. So `tx_gas_used` stays `gas_limit_cap`. The
1310+
old behavior refunded the spill, giving `gas_limit_cap - 1`.
13091311
"""
13101312
gas_limit_cap = fork.transaction_gas_limit_cap()
13111313
assert gas_limit_cap is not None
@@ -1331,10 +1333,10 @@ def test_top_level_halt_refunds_total_state_gas(
13311333
sender=pre.fund_eoa(),
13321334
)
13331335

1334-
# Policy A halt: state_gas counters preserved through the child
1335-
# halt/revert, parent halt, and tx-level fold.
1336-
# state_gas_left_end = max(reservoir, sstore_state_gas).
1337-
state_gas_left_end = max(reservoir, sstore_state_gas)
1336+
# LIFO refills: the spill refills to `gas_left` and is burned by
1337+
# the halt. Only the sized reservoir survives, so
1338+
# `tx_gas_used = gas_limit_cap`.
1339+
state_gas_left_end = reservoir
13381340
expected_gas_used = tx_gas - state_gas_left_end
13391341

13401342
blockchain_test(

tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py

Lines changed: 51 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -635,24 +635,27 @@ def test_parent_state_gas_after_child_failure(
635635
"""
636636
Test parent state-gas pools after CREATE child failure.
637637
638-
A factory invokes CREATE whose initcode performs an SSTORE
639-
(charging state gas) then either REVERTs or hits INVALID. The
640-
factory's own SSTORE after the failed CREATE acts as the
641-
discriminator that the parent's state-gas accounting (reservoir
642-
and gas_left) is in the expected state.
638+
A factory runs CREATE whose initcode does an SSTORE, then either
639+
REVERTs or hits INVALID. The factory's own SSTORE after the failed
640+
CREATE checks the parent's reservoir and gas_left are correct.
641+
642+
Under EIP-11807 state-gas refunds are LIFO. Gas spilled from
643+
gas_left refunds to gas_left, only the reservoir-funded portion
644+
returns to the reservoir.
643645
644646
Four scenarios cover the gas-pool state space:
645647
646-
- `with_reservoir x revert`: child state gas (new account +
647-
initcode SSTORE) is fully refunded to the parent reservoir on
648-
REVERT.
649-
- `with_reservoir x halt`: HALT resets the child frame to
650-
`(0, R0_child)`; only the reservoir-portion entering the
651-
initcode is returned, any spilled gas stays burned.
652-
- `no_reservoir x revert`: child state gas refunded forms a
653-
fresh reservoir even though `R0_parent` started at 0.
654-
- `no_reservoir x halt`: no phantom reservoir may form; the
655-
factory's post-CREATE SSTORE must spill from gas_left.
648+
- `with_reservoir x revert`: child state gas refills LIFO. The
649+
reservoir-funded portion returns to the parent reservoir, any
650+
spill to the parent gas_left.
651+
- `with_reservoir x halt`: halt refills the child frame LIFO then
652+
burns its gas_left. Only the child's start reservoir survives.
653+
- `no_reservoir x revert`: child state gas spilled wholly from
654+
gas_left, so the LIFO refill returns it there. No phantom
655+
reservoir forms.
656+
- `no_reservoir x halt`: no phantom reservoir forms. The spilled
657+
child state gas is burned with the child gas_left and the
658+
factory's post-CREATE SSTORE spills from gas_left.
656659
"""
657660
gas_limit_cap = fork.transaction_gas_limit_cap()
658661
assert gas_limit_cap is not None
@@ -728,21 +731,21 @@ def test_parent_state_gas_after_child_failure(
728731
initcode_regular_revert = initcode.gas_cost(fork) - sstore_state_gas
729732

730733
if failure_op == Op.INVALID:
731-
# Simulate runtime gas accounting for HALT using fork helpers:
732-
# 1. Initial regular pool capped by transaction_gas_limit_cap;
734+
# Simulate runtime gas for HALT under EIP-11807 LIFO refills:
735+
# 1. Regular pool capped by transaction_gas_limit_cap. The
733736
# remainder forms the state reservoir.
734-
# 2. CREATE op charges new_account state gas (from reservoir
735-
# first, spilled to gas_left otherwise).
736-
# 3. 63/64 retention rule: parent retains gas_left // 64.
737-
# 4. INVALID burns all forwarded regular gas in the child.
738-
# Per the updated EIP, child halt preserves its state-gas
739-
# counters and `incorporate_child_on_error` refunds the
740-
# full child charge — including any spilled portion — to
741-
# the parent's reservoir.
742-
# 5. CREATE failure refunds new_account state gas to the
743-
# parent's state pool (account creation rolled back).
744-
# 6. Factory's post-CREATE SSTORE charges sstore_state_gas
745-
# (state pool first, spilled to gas_left otherwise).
737+
# 2. CREATE charges new_account state gas, reservoir first
738+
# then spilled to gas_left and tracked.
739+
# 3. 63/64 retention: parent keeps gas_left // 64. The
740+
# reservoir is forwarded to the child frame.
741+
# 4. Child initcode SSTORE charges sstore_state_gas, child
742+
# reservoir first then spilled to child gas_left.
743+
# 5. INVALID refills the child frame LIFO then burns its
744+
# gas_left. Only the child's start reservoir survives.
745+
# 6. CREATE failure refills new_account LIFO: the spill to
746+
# parent gas_left, the rest to the parent reservoir.
747+
# 7. Factory post-CREATE SSTORE charges sstore_state_gas,
748+
# reservoir first then spilled to gas_left.
746749
execution_gas = gas_limit - intrinsic_cost
747750
regular_budget = gas_limit_cap - intrinsic_cost
748751
sim_gas_left = min(regular_budget, execution_gas)
@@ -751,26 +754,31 @@ def test_parent_state_gas_after_child_failure(
751754
sim_gas_left -= factory_pre_create_regular
752755
sim_gas_left -= gas_costs.OPCODE_CREATE_BASE + init_code_word_cost
753756

754-
if sim_state_gas_left >= new_account_state_gas:
755-
sim_state_gas_left -= new_account_state_gas
756-
else:
757-
sim_gas_left -= new_account_state_gas - sim_state_gas_left
758-
sim_state_gas_left = 0
757+
# CREATE new_account state gas: reservoir first, spill tracked.
758+
new_account_from_reservoir = min(
759+
sim_state_gas_left, new_account_state_gas
760+
)
761+
new_account_spill = new_account_state_gas - new_account_from_reservoir
762+
sim_state_gas_left -= new_account_from_reservoir
763+
sim_gas_left -= new_account_spill
759764

760-
# `child_reservoir` is what the parent forwards to the child.
761-
# Under Policy A halt, incorporate refunds child.state_gas_used
762-
# + child.state_gas_left = max(sstore, child_reservoir) back to
763-
# the parent. The simulator already implicitly retains
764-
# `child_reservoir` in `sim_state_gas_left`, so the additional
765-
# Policy A refund versus the Policy B "burn the spill" rule is
766-
# `max(0, sstore_state_gas - child_reservoir)`.
765+
# 63/64 retention: parent keeps gas_left // 64. The reservoir
766+
# is forwarded to the child frame and survives on halt.
767767
child_reservoir = sim_state_gas_left
768768
sim_gas_left = sim_gas_left // 64
769-
sim_state_gas_left += max(0, sstore_state_gas - child_reservoir)
770-
sim_state_gas_left += new_account_state_gas
769+
770+
# INVALID burns child gas_left, including any spilled SSTORE
771+
# state gas. Only the forwarded reservoir survives.
772+
sim_state_gas_left = child_reservoir
773+
774+
# CREATE failure refills new_account LIFO: spilled portion to
775+
# gas_left, reservoir-funded portion to the reservoir.
776+
sim_gas_left += new_account_spill
777+
sim_state_gas_left += new_account_from_reservoir
771778

772779
sim_gas_left -= factory_post_create_regular
773780

781+
# Factory post-CREATE SSTORE: reservoir first, spill otherwise.
774782
if sim_state_gas_left >= sstore_state_gas:
775783
sim_state_gas_left -= sstore_state_gas
776784
else:

0 commit comments

Comments
 (0)