Skip to content

Commit eb80b43

Browse files
spencer-tbfselmo
andauthored
feat(spec-specs, tests): EIP-8037 more refund fixes (ethereum#2823)
Co-authored-by: felipe <fselmo2@gmail.com>
1 parent 85b7f8a commit eb80b43

5 files changed

Lines changed: 343 additions & 121 deletions

File tree

src/ethereum/forks/amsterdam/fork.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from typing import List, Optional, Tuple
1616

1717
from ethereum_rlp import rlp
18-
from ethereum_types.bytes import Bytes
18+
from ethereum_types.bytes import Bytes, Bytes0
1919
from ethereum_types.frozen import slotted_freezable
2020
from ethereum_types.numeric import U64, U256, Uint, ulen
2121

@@ -1076,6 +1076,12 @@ def process_transaction(
10761076
if tx_output.error is not None:
10771077
tx_output.state_gas_left += tx_output.state_gas_used
10781078
tx_output.state_gas_used = Uint(0)
1079+
if isinstance(tx.to, Bytes0):
1080+
new_account_refund = (
1081+
STATE_BYTES_PER_NEW_ACCOUNT * COST_PER_STATE_BYTE
1082+
)
1083+
tx_output.state_gas_left += new_account_refund
1084+
tx_output.state_refund += new_account_refund
10791085
else:
10801086
# Refund state gas for accounts created and destroyed in the
10811087
# same tx (EIP-6780). Covers account, storage, and code.

src/ethereum/forks/amsterdam/vm/__init__.py

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,6 @@ class Evm:
181181
accessed_storage_keys: Set[Tuple[Address, Bytes32]]
182182
regular_gas_used: Uint = Uint(0)
183183
state_gas_used: Uint = Uint(0)
184-
state_gas_refund: Uint = Uint(0)
185184
state_gas_refund_pending: Uint = Uint(0)
186185

187186

@@ -191,10 +190,8 @@ def credit_state_gas_refund(evm: Evm, amount: Uint) -> None:
191190
192191
Clamp the applied portion to this frame's `state_gas_used` — the
193192
matching charge may sit in an ancestor sharing storage via
194-
CALLCODE/DELEGATECALL. Track it in `state_gas_refund` so
195-
`incorporate_child_on_error` can undo the inflation, and defer the
196-
unapplied remainder in `state_gas_refund_pending` for propagation
197-
on success.
193+
CALLCODE/DELEGATECALL. Defer the unapplied remainder in
194+
`state_gas_refund_pending` for propagation on success.
198195
199196
Parameters
200197
----------
@@ -207,18 +204,16 @@ def credit_state_gas_refund(evm: Evm, amount: Uint) -> None:
207204
applied = min(amount, evm.state_gas_used)
208205
evm.state_gas_left += applied
209206
evm.state_gas_used -= applied
210-
evm.state_gas_refund += applied
211207
evm.state_gas_refund_pending += amount - applied
212208

213209

214210
def incorporate_child_on_success(evm: Evm, child_evm: Evm) -> None:
215211
"""
216212
Incorporate the state of a successful `child_evm` into the parent `evm`.
217213
218-
Propagate `state_gas_refund` (inline credits the child applied) so
219-
an ancestor revert can undo the inflation, and apply
220-
`state_gas_refund_pending` (the unapplied remainder) to the parent
221-
via `credit_state_gas_refund`; any leftover propagates further up.
214+
Apply `state_gas_refund_pending` (the unapplied remainder of any
215+
refund credited inside the child) to the parent via
216+
`credit_state_gas_refund`; any leftover propagates further up.
222217
223218
Parameters
224219
----------
@@ -237,7 +232,6 @@ def incorporate_child_on_success(evm: Evm, child_evm: Evm) -> None:
237232
evm.accessed_storage_keys.update(child_evm.accessed_storage_keys)
238233
evm.regular_gas_used += child_evm.regular_gas_used
239234
evm.state_gas_used += child_evm.state_gas_used
240-
evm.state_gas_refund += child_evm.state_gas_refund
241235
credit_state_gas_refund(evm, child_evm.state_gas_refund_pending)
242236

243237

@@ -253,11 +247,11 @@ def incorporate_child_on_error(
253247
that spilled into `gas_left`, is restored to the parent's reservoir and
254248
the child's `state_gas_used` is not accumulated.
255249
256-
Inline state-gas refunds (SSTORE 0 to x to 0, CREATE silent failure)
257-
credited by the child inflated its `state_gas_left`; subtract
258-
`state_gas_refund` from the amount returned to the parent's
259-
reservoir so the inflation does not leak across the error boundary.
260-
`state_gas_refund_pending` is discarded with the child frame.
250+
`state_gas_refund_pending` is discarded with the child frame: any
251+
inline credits the child applied are keyed to charges (its own
252+
SSTORE or CREATE pre-charge) that are themselves rolled back, so
253+
the matching `state_gas_left + state_gas_used` sum already reflects
254+
the correct amount to return to the parent.
261255
262256
Parameters
263257
----------
@@ -268,11 +262,7 @@ def incorporate_child_on_error(
268262
269263
"""
270264
evm.gas_left += child_evm.gas_left
271-
evm.state_gas_left += (
272-
child_evm.state_gas_used
273-
+ child_evm.state_gas_left
274-
- child_evm.state_gas_refund
275-
)
265+
evm.state_gas_left += child_evm.state_gas_used + child_evm.state_gas_left
276266
evm.regular_gas_used += child_evm.regular_gas_used
277267

278268

tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py

Lines changed: 70 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1844,15 +1844,16 @@ def test_create_code_deposit_oog_refunds_state_gas(
18441844
],
18451845
)
18461846
@pytest.mark.valid_from("EIP8037")
1847-
def test_failed_create_tx_state_gas_dominates(
1847+
def test_failed_create_tx_refunds_intrinsic_new_account(
18481848
blockchain_test: BlockchainTestFiller,
18491849
pre: Alloc,
18501850
fork: Fork,
18511851
init_code: Bytecode,
18521852
) -> None:
18531853
"""
1854-
Verify the header gas is set by intrinsic state gas when a
1855-
creation tx fails with a tight regular budget.
1854+
Verify the NEW_ACCOUNT × CPSB portion of intrinsic_state_gas is
1855+
refunded on creation-tx revert/halt, so block state-gas excludes
1856+
it and header gas_used reflects only the regular component.
18561857
"""
18571858
intrinsic_calc = fork.transaction_intrinsic_cost_calculator()
18581859
create_state_gas = fork.create_state_gas(code_size=0)
@@ -1863,9 +1864,12 @@ def test_failed_create_tx_state_gas_dominates(
18631864
intrinsic_regular = intrinsic_total - create_state_gas
18641865
gas_limit = intrinsic_total + 1000
18651866

1866-
assert intrinsic_regular + 1000 < create_state_gas, (
1867-
"tight gas budget must keep block_regular below create_state_gas"
1868-
)
1867+
if init_code == Op.INVALID:
1868+
regular_consumed = gas_limit - intrinsic_total
1869+
else:
1870+
regular_consumed = init_code.regular_cost(fork)
1871+
1872+
expected_gas_used = intrinsic_regular + regular_consumed
18691873

18701874
tx = Transaction(
18711875
to=None,
@@ -1879,7 +1883,54 @@ def test_failed_create_tx_state_gas_dominates(
18791883
blocks=[
18801884
Block(
18811885
txs=[tx],
1882-
header_verify=Header(gas_used=create_state_gas),
1886+
header_verify=Header(gas_used=expected_gas_used),
1887+
),
1888+
],
1889+
post={},
1890+
)
1891+
1892+
1893+
@pytest.mark.pre_alloc_mutable()
1894+
@pytest.mark.valid_from("EIP8037")
1895+
def test_create_tx_collision_refunds_intrinsic_new_account(
1896+
blockchain_test: BlockchainTestFiller,
1897+
pre: Alloc,
1898+
fork: Fork,
1899+
) -> None:
1900+
"""
1901+
Verify the NEW_ACCOUNT × CPSB portion of intrinsic_state_gas is
1902+
refunded on creation-tx address collision, so block state-gas
1903+
excludes it and header gas_used reflects only the regular
1904+
consumption (full forwarded gas, no initcode runs).
1905+
"""
1906+
intrinsic_calc = fork.transaction_intrinsic_cost_calculator()
1907+
create_state_gas = fork.create_state_gas(code_size=0)
1908+
1909+
init_code = Op.STOP
1910+
intrinsic_total = intrinsic_calc(
1911+
calldata=bytes(init_code), contract_creation=True
1912+
)
1913+
gas_limit = intrinsic_total + 1000
1914+
1915+
sender = pre.fund_eoa()
1916+
collision_target = compute_create_address(address=sender, nonce=0)
1917+
pre[collision_target] = Account(nonce=1)
1918+
1919+
expected_gas_used = gas_limit - create_state_gas
1920+
1921+
tx = Transaction(
1922+
to=None,
1923+
data=init_code,
1924+
gas_limit=gas_limit,
1925+
sender=sender,
1926+
)
1927+
1928+
blockchain_test(
1929+
pre=pre,
1930+
blocks=[
1931+
Block(
1932+
txs=[tx],
1933+
header_verify=Header(gas_used=expected_gas_used),
18831934
),
18841935
],
18851936
post={},
@@ -2131,11 +2182,6 @@ def test_inner_create_succeeds_code_deposit_state_gas(
21312182
initcode_gas = initcode.gas_cost(fork)
21322183
gas_limit = intrinsic_total + initcode_gas + inner_code_deposit + 1000
21332184

2134-
if outer_outcome == "succeeds":
2135-
expected_state = outer_state_gas + inner_state_gas
2136-
else:
2137-
expected_state = outer_state_gas
2138-
21392185
create_address = compute_create_address(address=sender, nonce=0)
21402186

21412187
tx = Transaction(
@@ -2147,19 +2193,15 @@ def test_inner_create_succeeds_code_deposit_state_gas(
21472193

21482194
if outer_outcome == "succeeds":
21492195
post: dict = {create_address: Account(code=b"")}
2196+
block = Block(
2197+
txs=[tx],
2198+
header_verify=Header(gas_used=outer_state_gas + inner_state_gas),
2199+
)
21502200
else:
21512201
post = {create_address: Account.NONEXISTENT}
2202+
block = Block(txs=[tx])
21522203

2153-
blockchain_test(
2154-
pre=pre,
2155-
blocks=[
2156-
Block(
2157-
txs=[tx],
2158-
header_verify=Header(gas_used=expected_state),
2159-
),
2160-
],
2161-
post=post,
2162-
)
2204+
blockchain_test(pre=pre, blocks=[block], post=post)
21632205

21642206

21652207
@pytest.mark.parametrize(
@@ -2355,8 +2397,6 @@ def test_inner_create_fail_refunds_in_creation_tx(
23552397
+ num_inner_ops * (gas_costs.NEW_ACCOUNT + per_inner_slack)
23562398
)
23572399

2358-
expected_state = outer_state_gas
2359-
23602400
create_address = compute_create_address(address=sender, nonce=0)
23612401

23622402
tx = Transaction(
@@ -2368,19 +2408,15 @@ def test_inner_create_fail_refunds_in_creation_tx(
23682408

23692409
if outer_outcome == "succeeds":
23702410
post: dict = {create_address: Account(code=b"")}
2411+
block = Block(
2412+
txs=[tx],
2413+
header_verify=Header(gas_used=outer_state_gas),
2414+
)
23712415
else:
23722416
post = {create_address: Account.NONEXISTENT}
2417+
block = Block(txs=[tx])
23732418

2374-
blockchain_test(
2375-
pre=pre,
2376-
blocks=[
2377-
Block(
2378-
txs=[tx],
2379-
header_verify=Header(gas_used=expected_state),
2380-
),
2381-
],
2382-
post=post,
2383-
)
2419+
blockchain_test(pre=pre, blocks=[block], post=post)
23842420

23852421

23862422
@pytest.mark.pre_alloc_mutable

0 commit comments

Comments
 (0)