Skip to content

Commit 9a45ae4

Browse files
qu0bclaude
andcommitted
eip-8037: refund execution state gas on top-level failure
EELS patch: on top-level revert or exceptional halt, restore all execution state gas to the reservoir and reset state_gas_used to zero. Mirrors incorporate_child_on_error for child frames. Test: 2-tx blockchain test at 100M where state gas is the binding dimension. TX1 deploys 14 KiB contract (high state gas). TX2 is a failing CREATE whose initcode state gas (GAS_NEW_ACCOUNT) is the observable delta in header.gas_used. This is the opposite of #2595 which tests that state gas IS counted at the top level (#11468). This test verifies state gas is NOT counted at the top level (#11476). Spec change: ethereum/EIPs#11476 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 1a4ef23 commit 9a45ae4

File tree

2 files changed

+114
-0
lines changed

2 files changed

+114
-0
lines changed

src/ethereum/forks/amsterdam/fork.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1051,6 +1051,26 @@ def process_transaction(
10511051

10521052
tx_output = process_message_call(message)
10531053

1054+
# EIP-8037: On top-level revert or exceptional halt, restore all
1055+
# execution state gas to the reservoir. State changes are fully
1056+
# reverted so no state was actually grown — the sender should not
1057+
# pay for state gas. This mirrors incorporate_child_on_error for
1058+
# child frames.
1059+
if tx_output.error:
1060+
tx_output = MessageCallOutput(
1061+
gas_left=tx_output.gas_left,
1062+
state_gas_left=(
1063+
tx_output.state_gas_left + tx_output.state_gas_used
1064+
),
1065+
refund_counter=tx_output.refund_counter,
1066+
logs=tx_output.logs,
1067+
accounts_to_delete=tx_output.accounts_to_delete,
1068+
error=tx_output.error,
1069+
return_data=tx_output.return_data,
1070+
regular_gas_used=tx_output.regular_gas_used,
1071+
state_gas_used=Uint(0),
1072+
)
1073+
10541074
tx_gas_used_before_refund = (
10551075
tx.gas - tx_output.gas_left - tx_output.state_gas_left
10561076
)
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
"""
2+
Opposite of ethereum/execution-specs#2595: state gas refunded on
3+
top-level failure (ethereum/EIPs#11476).
4+
5+
TX1 pushes block_state above block_regular via large deploy.
6+
TX2 is a failing CREATE whose initcode state gas (GAS_NEW_ACCOUNT)
7+
must NOT count in block_state_gas_used.
8+
9+
Tests for [EIP-8037](https://eips.ethereum.org/EIPS/eip-8037).
10+
See: ethereum/EIPs#11476
11+
"""
12+
13+
import pytest
14+
from execution_testing import (
15+
Alloc,
16+
Block,
17+
BlockchainTestFiller,
18+
Environment,
19+
Fork,
20+
Op,
21+
Transaction,
22+
)
23+
24+
from .spec import ref_spec_8037
25+
26+
REFERENCE_SPEC_GIT_PATH = ref_spec_8037.git_path
27+
REFERENCE_SPEC_VERSION = ref_spec_8037.version
28+
29+
30+
@pytest.mark.parametrize(
31+
"state_op",
32+
[
33+
pytest.param(
34+
Op.POP(Op.CALL(gas=100_000, address=0xDEAD, value=1)),
35+
id="call_new_account",
36+
),
37+
pytest.param(
38+
Op.POP(Op.CREATE(value=0, offset=0, size=1)),
39+
id="inner_create",
40+
),
41+
],
42+
)
43+
@pytest.mark.valid_from("Amsterdam")
44+
def test_top_level_failure_refunds_execution_state_gas(
45+
blockchain_test: BlockchainTestFiller,
46+
pre: Alloc,
47+
fork: Fork,
48+
state_op: bytes,
49+
) -> None:
50+
"""
51+
TX1 makes state gas the binding dimension. TX2's initcode state
52+
gas (GAS_NEW_ACCOUNT) must not count in block_state_gas_used
53+
after code deposit failure.
54+
"""
55+
gas_limit_cap = fork.transaction_gas_limit_cap()
56+
assert gas_limit_cap is not None
57+
58+
# --- TX1: Deploy a 14 KiB contract (high state gas) ---
59+
deploy_size = 14_000
60+
tx1_create_state = fork.create_state_gas(code_size=deploy_size)
61+
tx1_gas_limit = gas_limit_cap + tx1_create_state
62+
63+
sender1 = pre.fund_eoa(10**21)
64+
tx1 = Transaction(
65+
to=None,
66+
data=Op.RETURN(0, deploy_size),
67+
gas_limit=tx1_gas_limit,
68+
sender=sender1,
69+
)
70+
71+
# --- TX2: Failing CREATE with initcode state ops ---
72+
oversized = fork.max_code_size() + 232
73+
initcode = state_op + Op.RETURN(0, oversized)
74+
75+
sender2 = pre.fund_eoa(10**21)
76+
tx2 = Transaction(
77+
to=None,
78+
data=initcode,
79+
value=10**18,
80+
gas_limit=gas_limit_cap,
81+
sender=sender2,
82+
)
83+
84+
blockchain_test(
85+
genesis_environment=Environment(gas_limit=100_000_000),
86+
pre=pre,
87+
blocks=[
88+
Block(
89+
txs=[tx1, tx2],
90+
gas_limit=100_000_000,
91+
),
92+
],
93+
post={},
94+
)

0 commit comments

Comments
 (0)