Skip to content

Commit 5416cbb

Browse files
fselmospencer-tb
andauthored
fix(spec,test): 7702 refund block-level gas accounting for EIP-8037 (#2816)
* feat(spec): 7702 auth state gas fix * fix(test): fix existing auth tests to expect block-level gas accounting for 7702 refunds * chore(test): un-skip 7702 tests that now no longer fail * fix: change 7702-related 8037 tests to reflect new behavior --------- Co-authored-by: spencer-tb <spencer.tb@ethereum.org>
1 parent 40ce50b commit 5416cbb

9 files changed

Lines changed: 179 additions & 203 deletions

File tree

src/ethereum/forks/amsterdam/fork.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1166,7 +1166,11 @@ def process_transaction(
11661166
destroy_account(tx_state, block_env.coinbase)
11671167

11681168
tx_regular_gas = tx_env.intrinsic_regular_gas + tx_output.regular_gas_used
1169-
tx_state_gas = tx_env.intrinsic_state_gas + tx_output.state_gas_used
1169+
tx_state_gas = (
1170+
tx_env.intrinsic_state_gas
1171+
+ tx_output.state_gas_used
1172+
- tx_output.state_refund
1173+
)
11701174
block_output.block_gas_used += max(
11711175
tx_regular_gas, intrinsic.calldata_floor
11721176
)

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

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,20 +158,29 @@ def calculate_delegation_cost(
158158
return True, delegated_address, delegation_gas_cost
159159

160160

161-
def set_delegation(message: Message) -> None:
161+
def set_delegation(message: Message) -> Uint:
162162
"""
163163
Set the delegation code for the authorities in the message.
164164
165165
For existing accounts, refunds the account-creation component of
166-
state gas to the reservoir (no mutation of intrinsic_state_gas).
166+
state gas to the reservoir (no mutation of intrinsic_state_gas) and
167+
accumulates the same amount as the auth state refund returned to the
168+
caller, so block accounting can subtract it from `tx_state_gas`.
167169
168170
Parameters
169171
----------
170172
message :
171173
Transaction specific items.
172174
175+
Returns
176+
-------
177+
auth_state_refund : `Uint`
178+
Total state gas refunded across all authorizations whose
179+
authority already existed in state.
180+
173181
"""
174182
tx_state = message.tx_env.state
183+
auth_state_refund = Uint(0)
175184
for auth in message.tx_env.authorizations:
176185
if auth.chain_id not in (message.block_env.chain_id, U256(0)):
177186
continue
@@ -202,6 +211,7 @@ def set_delegation(message: Message) -> None:
202211
if account_exists(tx_state, authority):
203212
refund = STATE_BYTES_PER_NEW_ACCOUNT * COST_PER_STATE_BYTE
204213
message.state_gas_reservoir += refund
214+
auth_state_refund += refund
205215

206216
if auth.address == NULL_ADDRESS:
207217
code_to_set = b""
@@ -218,3 +228,5 @@ def set_delegation(message: Message) -> None:
218228
tx_state,
219229
get_account(tx_state, message.code_address).code_hash,
220230
)
231+
232+
return auth_state_refund

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@ class MessageCallOutput:
8787
6. `return_data`: The output of the execution.
8888
7. `regular_gas_used`: Regular gas used during execution.
8989
8. `state_gas_used`: State gas used during execution.
90+
9. `state_refund`: State gas refunded by `set_delegation` for
91+
authorities that already existed in state. Subtracted from
92+
`tx_state_gas` in block accounting so `block.gas_used`
93+
matches the receipt `cumulative_gas_used`.
9094
"""
9195

9296
gas_left: Uint
@@ -98,6 +102,7 @@ class MessageCallOutput:
98102
return_data: Bytes
99103
regular_gas_used: Uint
100104
state_gas_used: Uint
105+
state_refund: Uint
101106

102107

103108
def process_message_call(message: Message) -> MessageCallOutput:
@@ -118,6 +123,7 @@ def process_message_call(message: Message) -> MessageCallOutput:
118123
"""
119124
tx_state = message.tx_env.state
120125
refund_counter = U256(0)
126+
state_refund = Uint(0)
121127
if message.target == Bytes0(b""):
122128
is_collision = account_has_code_or_nonce(
123129
tx_state, message.current_target
@@ -133,12 +139,13 @@ def process_message_call(message: Message) -> MessageCallOutput:
133139
return_data=Bytes(b""),
134140
regular_gas_used=message.gas,
135141
state_gas_used=Uint(0),
142+
state_refund=Uint(0),
136143
)
137144
else:
138145
evm = process_create_message(message)
139146
else:
140147
if message.tx_env.authorizations != ():
141-
set_delegation(message)
148+
state_refund += set_delegation(message)
142149

143150
delegated_address = get_delegated_code_address(message.code)
144151
if delegated_address is not None:
@@ -175,6 +182,7 @@ def process_message_call(message: Message) -> MessageCallOutput:
175182
return_data=evm.output,
176183
regular_gas_used=evm.regular_gas_used,
177184
state_gas_used=evm.state_gas_used,
185+
state_refund=state_refund,
178186
)
179187

180188

tests/amsterdam/eip7778_block_gas_accounting_without_refunds/test_gas_accounting.py

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -192,13 +192,14 @@ def build_refund_tx(
192192
if not exceed_block_gas_limit:
193193
post[refund_tx_sender] = Account(balance=expected_balance)
194194

195-
# block_state_gas_used reflects the full intrinsic_state: the AUTH
196-
# refund adds back to the reservoir (state_gas_left) and does not
197-
# subtract from state_gas_used.
195+
# block_state_gas_used reflects intrinsic_state minus the
196+
# existing-authority auth refund (state_refund), since
197+
# `process_transaction` deducts it from `tx_state_gas` before
198+
# accumulating into `block_state_gas_used`.
198199
return (
199200
receipt_gas_used,
200201
gas_used_pre_refund,
201-
auth_state_gas,
202+
remaining_state_gas,
202203
call_data_floor_cost,
203204
refund_tx,
204205
)
@@ -309,12 +310,16 @@ def test_multi_transaction_gas_accounting(
309310
310311
This tests that clients correctly use pre-refund gas for block accounting.
311312
"""
312-
# Skipped on snøbal -- see test_state_gas_snobal_quirks.py.
313+
# TODO[EIP-8037]: this test's exceed_block_gas_limit branch builds
314+
# `environment_gas_limit = total - 1` from a single combined
315+
# `total_block_gas_used`, but post-fix the auth refund splits the
316+
# regular vs state dimensions further. Reworking the per-dimension
317+
# budget math is out of scope for the auth-refund spec fix; until
318+
# then, skip the AUTHORIZATION_EXISTING_AUTHORITY case here.
313319
if refund_type == RefundTypes.AUTHORIZATION_EXISTING_AUTHORITY:
314320
pytest.skip(
315-
"snøbal spec quirk: EIP-7702 auth refund not deducted from "
316-
"block_state_gas_used; behavior pinned by "
317-
"test_state_gas_snobal_quirks.py"
321+
"AUTHORIZATION_EXISTING_AUTHORITY not yet adapted to the "
322+
"two-dimensional block budget post EIP-8037 auth-refund fix"
318323
)
319324

320325
intrinsic_cost_calc = fork.transaction_intrinsic_cost_calculator()
@@ -454,15 +459,16 @@ def test_varying_calldata_costs(
454459
"since refund is zero when execution reverts"
455460
)
456461

457-
# Skipped on snøbal -- see test_state_gas_snobal_quirks.py.
458462
if refund_type == RefundTypes.AUTHORIZATION_EXISTING_AUTHORITY:
459463
if calldata_test_type == (
460464
CallDataTestType.DATA_FLOOR_BETWEEN_TX_GAS_BEFORE_AND_AFTER
461465
):
462466
pytest.skip(
463-
"snøbal spec quirk: EIP-7702 auth refund routes to "
464-
"state reservoir, collapsing pre/post-refund range "
465-
"(see test_state_gas_snobal_quirks.py)"
467+
"EIP-7702 auth refund routes through state_gas_reservoir "
468+
"and state_refund (deducted from tx_state_gas); it does "
469+
"not feed refund_counter, so receipt gas_used_pre_refund "
470+
"== gas_used_post_refund and no calldata floor can land "
471+
"strictly between them"
466472
)
467473

468474
match refund_type:

0 commit comments

Comments
 (0)