Skip to content

Commit e706d04

Browse files
committed
fix(tests): drop brittle header_verify on CALL-to-selfdestructed tests
The three regression-fix tests in commit 4828ae6 used hardcoded empirical `block_regular` dicts (per CREATE/CREATE2 x self/external variant) to discriminate a spurious `GAS_NEW_ACCOUNT` charge on the CALL. The dicts are brittle to any regular-gas constant change and the spurious-charge discriminator is redundant: PR ethereum#2707's own tests (`test_create_selfdestruct_*`) already exercise the refund path. Drop `header_verify` from: test_call_value_to_self_destructed_header_gas_used test_call_value_to_self_destructed_burns_value test_call_zero_value_to_self_destructed_same_tx_account The tests still verify runtime behavior: NONEXISTENT created address and orchestrator balance burned to zero. Also adds a cross-over test for the ethereum#2704 + ethereum#2689 refund composition that PR ethereum#2704 does not exercise directly: test_inner_create_fail_refunds_in_creation_tx (parametrized `outer_outcome` in {succeeds, reverts}, `num_inner_ops` in {1, 3}, `create_opcode` in {CREATE, CREATE2}) Creation tx with `num_inner_ops` inner CREATE/CREATE2 calls whose initcode REVERTs. Each inner CREATE's GAS_NEW_ACCOUNT is refunded by PR ethereum#2704. Outer then succeeds or reverts. block_state == outer intrinsic in both cases; a client that regressed to pre-ethereum#2704 "gas persists" behavior would inflate it by `num_inner_ops * GAS_NEW_ACCOUNT`. Rewrites the inverted-premise test from the closed PR ethereum#2639.
1 parent cd3c9e7 commit e706d04

2 files changed

Lines changed: 141 additions & 73 deletions

File tree

tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_call.py

Lines changed: 14 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1055,34 +1055,13 @@ def test_call_value_to_self_destructed_header_gas_used(
10551055
)
10561056

10571057
# Post-EIP-11532 (PR #2707): the same-tx CREATE + SELFDESTRUCT
1058-
# refund zeroes `block_state_gas_used` at end-of-tx, so the
1059-
# header `gas_used` equals `block_regular`. A spurious
1060-
# `GAS_NEW_ACCOUNT` charge on the CALL would inflate
1061-
# `state_gas_used` by 131,488 and bump the header to that
1062-
# value, so `expected < new_account_state_gas` is the sharp
1063-
# discriminator. Values below are empirical block_regular
1064-
# (they depend only on fork-stable regular gas constants, not
1065-
# on cost_per_state_byte).
1066-
expected_by_variant = {
1067-
(Op.CREATE, "self"): 41864,
1068-
(Op.CREATE, "external"): 44465,
1069-
(Op.CREATE2, "self"): 41873,
1070-
(Op.CREATE2, "external"): 44474,
1071-
}
1072-
expected = expected_by_variant[(create_opcode, selfdestruct_beneficiary)]
1073-
assert expected < new_account_state_gas, (
1074-
"expected must stay below new_account_state_gas for the "
1075-
"spurious-charge discriminator to be sharp"
1076-
)
1077-
1058+
# refund zeroes `block_state_gas_used`. The previous header
1059+
# gas_used assertion (`== new_account_state_gas`) no longer
1060+
# holds. PR #2707's own tests discriminate the refund path,
1061+
# so this test's role reduces to the happy-path smoke check.
10781062
blockchain_test(
10791063
pre=pre,
1080-
blocks=[
1081-
Block(
1082-
txs=[tx],
1083-
header_verify=Header(gas_used=expected),
1084-
),
1085-
],
1064+
blocks=[Block(txs=[tx])],
10861065
post={},
10871066
)
10881067

@@ -1168,33 +1147,13 @@ def test_call_value_to_self_destructed_burns_value(
11681147
)
11691148

11701149
# Post-EIP-11532 (PR #2707): the same-tx CREATE + SELFDESTRUCT
1171-
# refund zeroes `block_state_gas_used` at end-of-tx, so the
1172-
# header reports `block_regular`. A spurious charge on the
1173-
# value-bearing CALL would bump `state_gas_used` by 131,488
1174-
# and push the header above `new_account_state_gas`, so
1175-
# `expected < new_account_state_gas` is the sharp
1176-
# discriminator. `call_value` does not change regular gas
1177-
# (value transfer adds a constant 9,000 to the CALL opcode
1178-
# regardless of amount). Values below are empirical
1179-
# block_regular.
1180-
expected_by_opcode = {
1181-
Op.CREATE: 41864,
1182-
Op.CREATE2: 41873,
1183-
}
1184-
expected = expected_by_opcode[create_opcode]
1185-
assert expected < new_account_state_gas, (
1186-
"expected must stay below new_account_state_gas for the "
1187-
"spurious-charge discriminator to be sharp"
1188-
)
1189-
1150+
# refund zeroes `block_state_gas_used`. The value-burning
1151+
# property is captured by the post state: `created_address`
1152+
# NONEXISTENT (destroyed end-of-tx) and the orchestrator's
1153+
# balance drained to zero.
11901154
blockchain_test(
11911155
pre=pre,
1192-
blocks=[
1193-
Block(
1194-
txs=[tx],
1195-
header_verify=Header(gas_used=expected),
1196-
),
1197-
],
1156+
blocks=[Block(txs=[tx])],
11981157
post={
11991158
created_address: Account.NONEXISTENT,
12001159
orchestrator: Account(balance=0),
@@ -1255,30 +1214,12 @@ def test_call_zero_value_to_self_destructed_same_tx_account(
12551214
)
12561215

12571216
# Post-EIP-11532 (PR #2707): the same-tx CREATE + SELFDESTRUCT
1258-
# refund zeroes `block_state_gas_used`, so header `gas_used`
1259-
# equals `block_regular`. A spurious new-account charge on the
1260-
# zero-value CALL (value-gate broken) would push the header
1261-
# above `new_account_state_gas`, so
1262-
# `expected < new_account_state_gas` is the sharp
1263-
# discriminator. Values below are empirical block_regular.
1264-
expected_by_opcode = {
1265-
Op.CREATE: 35164,
1266-
Op.CREATE2: 35173,
1267-
}
1268-
expected = expected_by_opcode[create_opcode]
1269-
assert expected < new_account_state_gas, (
1270-
"expected must stay below new_account_state_gas for the "
1271-
"spurious-charge discriminator to be sharp"
1272-
)
1273-
1217+
# refund zeroes `block_state_gas_used`. The happy-path
1218+
# storage and runtime behavior is sufficient; the refund
1219+
# itself is discriminated by PR #2707's dedicated tests.
12741220
blockchain_test(
12751221
pre=pre,
1276-
blocks=[
1277-
Block(
1278-
txs=[tx],
1279-
header_verify=Header(gas_used=expected),
1280-
),
1281-
],
1222+
blocks=[Block(txs=[tx])],
12821223
post={},
12831224
)
12841225

tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2084,3 +2084,130 @@ def test_create_stack_depth_state_gas_consumed(
20842084

20852085
post = {recursive: Account(storage=storage)}
20862086
state_test(pre=pre, post=post, tx=tx)
2087+
2088+
2089+
@pytest.mark.parametrize(
2090+
"num_inner_ops",
2091+
[
2092+
pytest.param(1, id="single"),
2093+
pytest.param(3, id="accumulate"),
2094+
],
2095+
)
2096+
@pytest.mark.parametrize(
2097+
"outer_outcome",
2098+
[
2099+
pytest.param("succeeds", id="outer_succeeds"),
2100+
pytest.param("reverts", id="outer_reverts"),
2101+
],
2102+
)
2103+
@pytest.mark.with_all_create_opcodes()
2104+
@pytest.mark.valid_from("EIP8037")
2105+
def test_inner_create_fail_refunds_in_creation_tx(
2106+
blockchain_test: BlockchainTestFiller,
2107+
pre: Alloc,
2108+
fork: Fork,
2109+
create_opcode: Op,
2110+
outer_outcome: str,
2111+
num_inner_ops: int,
2112+
) -> None:
2113+
"""
2114+
Cross-over: PR #2704 CREATE failure refund in a creation-tx context.
2115+
2116+
The original closed PR #2639 test asserted inner CREATE's
2117+
`GAS_NEW_ACCOUNT` PERSISTS on child failure (targeting a
2118+
bal-devnet-3 geth behavior). PR #2704 subsequently changed the
2119+
spec to refund that charge, inverting the original premise.
2120+
This test covers the CURRENT spec behavior in a scenario not
2121+
exercised by PR #2704's own tests (which all use non-creation
2122+
factories):
2123+
2124+
A creation tx (to=None) whose initcode performs `num_inner_ops`
2125+
inner CREATE/CREATE2 calls with REVERT initcode. Each inner
2126+
CREATE charges `GAS_NEW_ACCOUNT` state then refunds it via the
2127+
child-error branch (PR #2704). The outer initcode then
2128+
terminates via RETURN (succeeds) or REVERT.
2129+
2130+
Both outcomes yield `block_state = outer intrinsic` because
2131+
inner refunds net the state gas back to zero; PR #2689's top
2132+
level refund (applied on revert) is a no-op over an already
2133+
zeroed `state_gas_used`.
2134+
2135+
A client that regresses to the pre-#2704 "persist" behavior
2136+
would inflate `block_state` by `num_inner_ops * GAS_NEW_ACCOUNT`.
2137+
The `outer_halts` variant is omitted because `INVALID` absorbs
2138+
all remaining gas into `regular_gas_used`, letting `block_regular`
2139+
dominate the header and dilute the state dimension signal.
2140+
"""
2141+
gas_costs = fork.gas_costs()
2142+
outer_state_gas = fork.create_state_gas(code_size=0)
2143+
2144+
inner_initcode = bytes(Op.REVERT(0, 0))
2145+
2146+
setup = Op.MSTORE(
2147+
0,
2148+
int.from_bytes(inner_initcode, "big")
2149+
<< (256 - 8 * len(inner_initcode)),
2150+
)
2151+
2152+
inner_ops = Bytecode()
2153+
for i in range(num_inner_ops):
2154+
if create_opcode == Op.CREATE2:
2155+
inner_ops += Op.POP(Op.CREATE2(0, 0, len(inner_initcode), i))
2156+
else:
2157+
inner_ops += Op.POP(Op.CREATE(0, 0, len(inner_initcode)))
2158+
2159+
if outer_outcome == "succeeds":
2160+
termination = Op.RETURN(0, 0)
2161+
elif outer_outcome == "reverts":
2162+
termination = Op.REVERT(0, 0)
2163+
else:
2164+
termination = Op.INVALID
2165+
2166+
initcode = setup + inner_ops + termination
2167+
2168+
sender = pre.fund_eoa()
2169+
intrinsic_calc = fork.transaction_intrinsic_cost_calculator()
2170+
intrinsic_total = intrinsic_calc(
2171+
calldata=bytes(initcode), contract_creation=True
2172+
)
2173+
2174+
# Gas budget: enough for each inner CREATE to charge
2175+
# GAS_NEW_ACCOUNT (spill into gas_left, then get refunded on
2176+
# child failure) and for the outer termination to run.
2177+
initcode_gas = initcode.gas_cost(fork)
2178+
per_inner_slack = 2_000
2179+
gas_limit = (
2180+
intrinsic_total
2181+
+ initcode_gas
2182+
+ num_inner_ops * (gas_costs.GAS_NEW_ACCOUNT + per_inner_slack)
2183+
)
2184+
2185+
# Expected: only outer intrinsic remains in block_state. Each
2186+
# inner CREATE's charge is refunded by PR #2704; any residue
2187+
# left in state_gas_used is zeroed on outer failure by PR #2689.
2188+
expected_state = outer_state_gas
2189+
2190+
create_address = compute_create_address(address=sender, nonce=0)
2191+
2192+
tx = Transaction(
2193+
sender=sender,
2194+
to=None,
2195+
data=initcode,
2196+
gas_limit=gas_limit,
2197+
)
2198+
2199+
if outer_outcome == "succeeds":
2200+
post: dict = {create_address: Account(code=b"")}
2201+
else:
2202+
post = {create_address: Account.NONEXISTENT}
2203+
2204+
blockchain_test(
2205+
pre=pre,
2206+
blocks=[
2207+
Block(
2208+
txs=[tx],
2209+
header_verify=Header(gas_used=expected_state),
2210+
),
2211+
],
2212+
post=post,
2213+
)

0 commit comments

Comments
 (0)