Skip to content

Commit c230841

Browse files
committed
feat(spec-specs, tests): EIP-8037 - immutable intrinsic state gas for EIP-7702 (#2711)
1 parent ae01f19 commit c230841

2 files changed

Lines changed: 204 additions & 11 deletions

File tree

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

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -163,8 +163,8 @@ def set_delegation(message: Message) -> None:
163163
"""
164164
Set the delegation code for the authorities in the message.
165165
166-
For existing accounts, adjusts intrinsic_state_gas downward since
167-
no account creation is needed (only delegation code write).
166+
For existing accounts, refunds the account-creation component of
167+
state gas to the reservoir (no mutation of intrinsic_state_gas).
168168
169169
Parameters
170170
----------
@@ -199,11 +199,10 @@ def set_delegation(message: Message) -> None:
199199
continue
200200

201201
# For existing accounts, no account creation needed.
202-
# Refund the account creation state gas to the reservoir
203-
# and adjust intrinsic accounting to avoid double-counting.
202+
# Refund the account creation state gas to the reservoir.
203+
# intrinsic_state_gas is immutable after validation.
204204
if account_exists(tx_state, authority):
205205
refund = STATE_BYTES_PER_NEW_ACCOUNT * cost_per_state_byte
206-
message.tx_env.intrinsic_state_gas -= refund
207206
message.state_gas_reservoir += refund
208207

209208
if auth.address == NULL_ADDRESS:

tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_set_code.py

Lines changed: 200 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
Bytecode,
2121
Environment,
2222
Fork,
23+
Header,
2324
Op,
2425
StateTestFiller,
2526
Storage,
@@ -295,11 +296,14 @@ def test_auth_refund_block_gas_accounting(
295296
fork: Fork,
296297
) -> None:
297298
"""
298-
Test block gas accounting with authorization existing-account refund.
299-
300-
The auth refund goes to state_gas_reservoir, not to refund_counter.
301-
Block regular gas is unaffected by the auth refund — it reduces
302-
intrinsic_state_gas, which only affects block_state_gas_used.
299+
Verify block gas accounting with an authorization refund for an
300+
existing account.
301+
302+
The refund for an existing authority goes to the state gas
303+
reservoir and does not alter the intrinsic state gas carried into
304+
block accounting. Block state gas used reflects the worst case
305+
intrinsic state gas component regardless of how many authorities
306+
were existing accounts.
303307
"""
304308
gas_limit_cap = fork.transaction_gas_limit_cap()
305309
assert gas_limit_cap is not None
@@ -326,9 +330,15 @@ def test_auth_refund_block_gas_accounting(
326330
sender=sender,
327331
)
328332

333+
# State gas component dominates the tx regular component, so the
334+
# block header gas_used equals the worst case intrinsic state gas.
335+
# A mutating refund would reduce this value; the immutable behavior
336+
# keeps it at the worst case.
329337
blockchain_test(
330338
pre=pre,
331-
blocks=[Block(txs=[tx])],
339+
blocks=[
340+
Block(txs=[tx], header_verify=Header(gas_used=auth_state_gas))
341+
],
332342
post={},
333343
)
334344

@@ -1022,3 +1032,187 @@ def test_auth_refund_bypasses_one_fifth_cap(
10221032

10231033
post = {contract: Account(storage=storage)}
10241034
state_test(env=env, pre=pre, post=post, tx=tx)
1035+
1036+
1037+
@pytest.mark.parametrize(
1038+
"num_auths",
1039+
[
1040+
pytest.param(1, id="one_auth"),
1041+
pytest.param(3, id="three_auths"),
1042+
],
1043+
)
1044+
@pytest.mark.valid_from("EIP8037")
1045+
def test_existing_account_auth_header_gas_used_uses_worst_case(
1046+
blockchain_test: BlockchainTestFiller,
1047+
pre: Alloc,
1048+
fork: Fork,
1049+
num_auths: int,
1050+
) -> None:
1051+
"""
1052+
Verify the block header gas_used reflects the worst case intrinsic
1053+
state gas when all authorities are existing accounts.
1054+
1055+
Intrinsic state gas is set at transaction validation and does not
1056+
change during execution. When an authorization targets an existing
1057+
account, the account creation component of state gas is refunded
1058+
to the reservoir only and is not subtracted from the intrinsic
1059+
state gas that feeds block accounting.
1060+
"""
1061+
gas_limit_cap = fork.transaction_gas_limit_cap()
1062+
assert gas_limit_cap is not None
1063+
worst_case_state_gas = fork.transaction_intrinsic_state_gas(
1064+
authorization_count=num_auths,
1065+
)
1066+
1067+
contract = pre.deploy_contract(code=Op.STOP)
1068+
1069+
# All authorities exist in pre state.
1070+
authorization_list = [
1071+
AuthorizationTuple(address=contract, nonce=0, signer=pre.fund_eoa())
1072+
for _ in range(num_auths)
1073+
]
1074+
1075+
tx = Transaction(
1076+
to=contract,
1077+
gas_limit=gas_limit_cap + worst_case_state_gas,
1078+
authorization_list=authorization_list,
1079+
sender=pre.fund_eoa(),
1080+
)
1081+
1082+
blockchain_test(
1083+
pre=pre,
1084+
blocks=[
1085+
Block(
1086+
txs=[tx],
1087+
header_verify=Header(gas_used=worst_case_state_gas),
1088+
),
1089+
],
1090+
post={},
1091+
)
1092+
1093+
1094+
@pytest.mark.parametrize(
1095+
"num_existing,num_new",
1096+
[
1097+
pytest.param(1, 1, id="one_existing_one_new"),
1098+
pytest.param(2, 2, id="two_existing_two_new"),
1099+
],
1100+
)
1101+
@pytest.mark.valid_from("EIP8037")
1102+
def test_mixed_auths_header_gas_used_uses_worst_case(
1103+
blockchain_test: BlockchainTestFiller,
1104+
pre: Alloc,
1105+
fork: Fork,
1106+
num_existing: int,
1107+
num_new: int,
1108+
) -> None:
1109+
"""
1110+
Verify the block header gas_used reflects the worst case intrinsic
1111+
state gas across a mix of existing and new account authorizations.
1112+
1113+
Refunds for the existing accounts go to the state gas reservoir,
1114+
and the intrinsic state gas carried into block accounting covers
1115+
the full authorization count as if every authority were a new
1116+
account.
1117+
"""
1118+
gas_limit_cap = fork.transaction_gas_limit_cap()
1119+
assert gas_limit_cap is not None
1120+
num_auths = num_existing + num_new
1121+
worst_case_state_gas = fork.transaction_intrinsic_state_gas(
1122+
authorization_count=num_auths,
1123+
)
1124+
1125+
contract = pre.deploy_contract(code=Op.STOP)
1126+
1127+
authorization_list = []
1128+
for _ in range(num_existing):
1129+
authorization_list.append(
1130+
AuthorizationTuple(
1131+
address=contract,
1132+
nonce=0,
1133+
signer=pre.fund_eoa(),
1134+
)
1135+
)
1136+
for _ in range(num_new):
1137+
authorization_list.append(
1138+
AuthorizationTuple(
1139+
address=contract,
1140+
nonce=0,
1141+
signer=pre.fund_eoa(amount=0),
1142+
)
1143+
)
1144+
1145+
tx = Transaction(
1146+
to=contract,
1147+
gas_limit=gas_limit_cap + worst_case_state_gas,
1148+
authorization_list=authorization_list,
1149+
sender=pre.fund_eoa(),
1150+
)
1151+
1152+
blockchain_test(
1153+
pre=pre,
1154+
blocks=[
1155+
Block(
1156+
txs=[tx],
1157+
header_verify=Header(gas_used=worst_case_state_gas),
1158+
),
1159+
],
1160+
post={},
1161+
)
1162+
1163+
1164+
@pytest.mark.valid_from("EIP8037")
1165+
def test_existing_auth_with_reverted_execution_preserves_intrinsic(
1166+
blockchain_test: BlockchainTestFiller,
1167+
pre: Alloc,
1168+
fork: Fork,
1169+
) -> None:
1170+
"""
1171+
Verify the worst case intrinsic state gas survives both the
1172+
existing account authorization refund and the top level failure
1173+
refund.
1174+
1175+
Scenario: a tx with a single authorization to an existing
1176+
account executes an SSTORE then REVERTs. `set_delegation` adds
1177+
the account creation portion to `state_gas_reservoir` without
1178+
mutating the intrinsic state gas. The top level revert refund
1179+
zeroes execution state gas. Block accounting reflects the worst
1180+
case intrinsic state gas unchanged. Under a mutating
1181+
implementation the intrinsic would be reduced and the block
1182+
header would fall back to the regular gas component.
1183+
"""
1184+
gas_limit_cap = fork.transaction_gas_limit_cap()
1185+
assert gas_limit_cap is not None
1186+
worst_case_state_gas = fork.transaction_intrinsic_state_gas(
1187+
authorization_count=1,
1188+
)
1189+
1190+
contract = pre.deploy_contract(
1191+
code=Op.SSTORE(0, 1) + Op.REVERT(0, 0),
1192+
)
1193+
1194+
# Existing signer: the set_delegation refund is routed to the
1195+
# reservoir. Under the correct spec the intrinsic state gas is
1196+
# not mutated.
1197+
signer = pre.fund_eoa()
1198+
authorization_list = [
1199+
AuthorizationTuple(address=contract, nonce=0, signer=signer),
1200+
]
1201+
1202+
tx = Transaction(
1203+
to=contract,
1204+
gas_limit=gas_limit_cap + worst_case_state_gas,
1205+
authorization_list=authorization_list,
1206+
sender=pre.fund_eoa(),
1207+
)
1208+
1209+
blockchain_test(
1210+
pre=pre,
1211+
blocks=[
1212+
Block(
1213+
txs=[tx],
1214+
header_verify=Header(gas_used=worst_case_state_gas),
1215+
),
1216+
],
1217+
post={contract: Account(storage={})},
1218+
)

0 commit comments

Comments
 (0)