Skip to content

Commit 1dc3a0e

Browse files
committed
feat(spec-specs,test-tests): update the EIP-2780 implementation (#3126)
1 parent 3d50745 commit 1dc3a0e

35 files changed

Lines changed: 4099 additions & 1843 deletions

packages/testing/src/execution_testing/forks/base_fork.py

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
Mapping,
1313
Optional,
1414
Protocol,
15+
Sequence,
1516
Set,
1617
Sized,
1718
Type,
@@ -152,17 +153,28 @@ def __call__(
152153
Forks that itemize the value-transfer charge in
153154
intrinsic gas use this flag; ignored by older forks.
154155
recipient_type: Category of the transaction recipient. Forks
155-
that vary intrinsic gas by recipient kind
156-
(e.g. no access cost for precompiles, no value
157-
charge for self-transfers) use this; ignored
158-
by older forks.
156+
that vary intrinsic gas by recipient kind use this;
157+
ignored by older forks.
159158
160159
Returns: Gas cost of a transaction
161160
162161
"""
163162
pass
164163

165164

165+
class AuthorizationGasInfo(Protocol):
166+
"""
167+
Structural view of an EIP-7702 authorization's effect on the
168+
pre-state, used to compute its top-frame gas. The test
169+
``AuthorizationTuple`` satisfies it via its ``creates_account``,
170+
``writes_delegation``, and ``first_write`` fields.
171+
"""
172+
173+
creates_account: bool
174+
writes_delegation: bool
175+
first_write: bool
176+
177+
166178
class TopFrameGasCalculator(Protocol):
167179
"""
168180
A protocol to calculate the additional regular gas charged at the
@@ -185,6 +197,8 @@ def __call__(
185197
contract_creation: bool = False,
186198
sends_value: bool = False,
187199
recipient_type: RecipientType = RecipientType.CONTRACT,
200+
delegation_warm: bool = False,
201+
authorizations: Sequence[AuthorizationGasInfo] = (),
188202
) -> int:
189203
"""
190204
Return the regular gas consumed by top-frame preparation for a
@@ -199,6 +213,11 @@ def __call__(
199213
value.
200214
recipient_type: Category of the transaction recipient.
201215
Drives the conditional charges.
216+
delegation_warm: Whether a delegated recipient's delegation
217+
target is already warm, charging warm rather
218+
than cold access.
219+
authorizations: The transaction's EIP-7702 authorizations;
220+
each contributes its top-frame regular gas.
202221
203222
Returns: Regular gas added by top-frame preparation.
204223
@@ -787,8 +806,11 @@ def fn(
787806
contract_creation: bool = False,
788807
sends_value: bool = False,
789808
recipient_type: RecipientType = RecipientType.CONTRACT,
809+
delegation_warm: bool = False,
810+
authorizations: Sequence[AuthorizationGasInfo] = (),
790811
) -> int:
791812
del contract_creation, sends_value, recipient_type
813+
del delegation_warm, authorizations
792814
return 0
793815

794816
return fn
@@ -800,6 +822,7 @@ def transaction_top_frame_state_gas(
800822
contract_creation: bool = False,
801823
sends_value: bool = False,
802824
recipient_type: RecipientType = RecipientType.CONTRACT,
825+
authorizations: Sequence[AuthorizationGasInfo] = (),
803826
) -> int:
804827
"""
805828
Return the state gas charged at the top-level transaction
@@ -811,7 +834,7 @@ def transaction_top_frame_state_gas(
811834
Defaults to 0 for forks that do not perform such
812835
post-intrinsic preparation.
813836
"""
814-
del contract_creation, sends_value, recipient_type
837+
del contract_creation, sends_value, recipient_type, authorizations
815838
return 0
816839

817840
@classmethod

packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_2780.py

Lines changed: 101 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,16 @@
99
"""
1010

1111
from dataclasses import replace
12-
from typing import List, Sized
12+
from typing import List, Sequence, Sized
1313

1414
from execution_testing.base_types import AccessList
1515
from execution_testing.base_types.conversions import BytesConvertible
1616

1717
from .....recipient_type import RecipientType
1818
from ....base_fork import (
19+
AuthorizationGasInfo,
1920
BaseFork,
21+
RefundTypes,
2022
TopFrameGasCalculator,
2123
TransactionDataFloorCostCalculator,
2224
TransactionIntrinsicCostCalculator,
@@ -112,17 +114,39 @@ def fn(
112114
sends_value: bool = False,
113115
recipient_type: RecipientType = RecipientType.CONTRACT,
114116
) -> int:
117+
# Only the state-independent base cost per authorization is
118+
# charged in the intrinsic; the state-dependent
119+
# account-creation and delegation-write costs are charged at
120+
# the top frame. Exclude the base-fork authorization cost
121+
# and re-add the base cost here.
122+
authorization_count = 0
123+
if authorization_list_or_count is not None:
124+
authorization_count = (
125+
len(authorization_list_or_count)
126+
if isinstance(authorization_list_or_count, Sized)
127+
else authorization_list_or_count
128+
)
129+
115130
intrinsic_cost: int = super_fn(
116131
calldata=calldata,
117132
contract_creation=contract_creation,
118133
access_list=access_list,
119-
authorization_list_or_count=authorization_list_or_count,
134+
authorization_list_or_count=None,
120135
return_cost_deducted_prior_execution=True,
121136
)
137+
intrinsic_cost += (
138+
authorization_count * gas_costs.REGULAR_PER_AUTH_BASE_COST
139+
)
122140

123141
is_self_transfer = recipient_type == RecipientType.SELF
124142

125143
if contract_creation:
144+
# EIP-2780: the created account's NEW_ACCOUNT state gas
145+
# is charged at the top frame, not deducted in the
146+
# intrinsic. The base-fork intrinsic bundles it in, so
147+
# remove it here, mirroring value transfer to an empty
148+
# account whose NEW_ACCOUNT is likewise top-frame.
149+
intrinsic_cost -= gas_costs.NEW_ACCOUNT
126150
if sends_value:
127151
intrinsic_cost += gas_costs.TRANSFER_LOG_COST
128152
elif not is_self_transfer:
@@ -151,6 +175,28 @@ def fn(
151175

152176
return fn
153177

178+
@classmethod
179+
def transaction_intrinsic_state_gas(
180+
cls,
181+
*,
182+
contract_creation: bool = False,
183+
authorization_count: int = 0,
184+
) -> int:
185+
"""
186+
Return the intrinsic state gas for a transaction.
187+
188+
Under EIP-2780 neither authorizations nor contract creation
189+
contribute intrinsic state gas: the authority account-creation
190+
and delegation-write costs and the created account's
191+
``NEW_ACCOUNT`` are all state-dependent and charged at the top
192+
frame instead.
193+
"""
194+
del contract_creation, authorization_count
195+
return super(EIP2780, cls).transaction_intrinsic_state_gas(
196+
contract_creation=False,
197+
authorization_count=0,
198+
)
199+
154200
@classmethod
155201
def transaction_top_frame_gas_calculator(
156202
cls,
@@ -160,10 +206,14 @@ def transaction_top_frame_gas_calculator(
160206
transaction frame, after intrinsic gas is deducted but before
161207
the EVM dispatches.
162208
163-
Charges ``COLD_ACCOUNT_ACCESS`` when the recipient is an
164-
existing delegated account. The empty-recipient
165-
``NEW_ACCOUNT`` charge is state gas, returned separately by
166-
``transaction_top_frame_state_gas``.
209+
Charges the delegation-target access when the recipient is an
210+
existing delegated account: ``WARM_ACCESS`` when the target is
211+
already warm, otherwise ``COLD_ACCOUNT_ACCESS``. Each
212+
authorization whose application is the transaction's first
213+
write to its authority's leaf (``first_write``) adds
214+
``ACCOUNT_WRITE``. The state-gas portions (empty-recipient and
215+
per-authorization ``NEW_ACCOUNT``, plus ``AUTH_BASE``) are
216+
returned separately by ``transaction_top_frame_state_gas``.
167217
"""
168218
gas_costs = cls.gas_costs()
169219

@@ -172,14 +222,24 @@ def fn(
172222
contract_creation: bool = False,
173223
sends_value: bool = False,
174224
recipient_type: RecipientType = RecipientType.CONTRACT,
225+
delegation_warm: bool = False,
226+
authorizations: Sequence[AuthorizationGasInfo] = (),
175227
) -> int:
176228
del sends_value
177229
if contract_creation:
178230
return 0
179231

232+
regular = 0
180233
if recipient_type == RecipientType.DELEGATION_7702:
181-
return gas_costs.COLD_ACCOUNT_ACCESS
182-
return 0
234+
regular += (
235+
gas_costs.WARM_ACCESS
236+
if delegation_warm
237+
else gas_costs.COLD_ACCOUNT_ACCESS
238+
)
239+
for auth in authorizations:
240+
if auth.first_write:
241+
regular += gas_costs.ACCOUNT_WRITE
242+
return regular
183243

184244
return fn
185245

@@ -190,15 +250,43 @@ def transaction_top_frame_state_gas(
190250
contract_creation: bool = False,
191251
sends_value: bool = False,
192252
recipient_type: RecipientType = RecipientType.CONTRACT,
253+
authorizations: Sequence[AuthorizationGasInfo] = (),
193254
) -> int:
194255
"""
195256
Return the state gas charged at the top-level transaction
196-
frame. Charges ``NEW_ACCOUNT`` when value is transferred to an
197-
empty recipient; zero otherwise.
257+
frame. A contract creation charges the created account's
258+
``NEW_ACCOUNT`` here (state-dependent, no longer intrinsic),
259+
assuming a fresh target. Otherwise, charges ``NEW_ACCOUNT`` when
260+
value is transferred to an empty recipient, and each
261+
authorization adds ``NEW_ACCOUNT`` when its authority's account
262+
leaf must be created and ``AUTH_BASE`` when it writes a net-new
263+
delegation indicator.
198264
"""
199265
gas_costs = cls.gas_costs()
200266
if contract_creation:
201-
return 0
202-
if sends_value and recipient_type == RecipientType.EMPTY_ACCOUNT:
203267
return gas_costs.NEW_ACCOUNT
204-
return 0
268+
state = 0
269+
if sends_value and recipient_type == RecipientType.EMPTY_ACCOUNT:
270+
state += gas_costs.NEW_ACCOUNT
271+
for auth in authorizations:
272+
if auth.creates_account:
273+
state += gas_costs.NEW_ACCOUNT
274+
if auth.writes_delegation:
275+
state += gas_costs.AUTH_BASE
276+
return state
277+
278+
@classmethod
279+
def refund_types(cls) -> List[RefundTypes]:
280+
"""
281+
Drop the existing-authority authorization refund.
282+
283+
EIP-2780 charges each authorization's state-dependent cost at the
284+
top frame, keyed on the authority's pre-transaction state, with no
285+
refund. The Prague-era ``AUTHORIZATION_EXISTING_AUTHORITY`` refund
286+
therefore no longer applies.
287+
"""
288+
return [
289+
refund
290+
for refund in super(EIP2780, cls).refund_types()
291+
if refund != RefundTypes.AUTHORIZATION_EXISTING_AUTHORITY
292+
]

packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8037.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ def gas_costs(cls) -> GasCosts:
9292
parent.STORAGE_SET + STATE_BYTES_PER_STORAGE_SET * cpsb
9393
),
9494
NEW_ACCOUNT=new_acct,
95+
AUTH_BASE=STATE_BYTES_PER_AUTH_BASE * cpsb,
9596
TX_CREATE=parent.TX_CREATE + new_acct,
9697
AUTH_PER_EMPTY_ACCOUNT=(
9798
parent.AUTH_PER_EMPTY_ACCOUNT

packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8038.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ def gas_costs(cls) -> GasCosts:
7474
OPCODE_CREATE_BASE=create_access,
7575
TX_CREATE=create_access,
7676
AUTH_PER_EMPTY_ACCOUNT=account_write + regular_per_auth_base_cost,
77+
REGULAR_PER_AUTH_BASE_COST=regular_per_auth_base_cost,
7778
)
7879

7980
@classmethod

packages/testing/src/execution_testing/forks/gas_costs.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ class GasCosts:
4747

4848
# Authorization
4949
AUTH_PER_EMPTY_ACCOUNT: int
50+
# State gas for writing a net-new EIP-7702 delegation indicator;
51+
# 0 before the state-creation repricing introduces it.
52+
AUTH_BASE: int = 0
53+
# State-independent regular gas charged per EIP-7702 authorization
54+
# tuple; 0 before the state-access repricing introduces it.
55+
REGULAR_PER_AUTH_BASE_COST: int = 0
5056

5157
# Utility
5258
MEMORY_PER_WORD: int

packages/testing/src/execution_testing/test_types/transaction_types.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,13 @@ class AuthorizationTuple(AuthorizationTupleGeneric[HexNumber]):
120120

121121
signer: EOA | None = None
122122
secret_key: Hash | None = None
123+
creates_account: bool = Field(False, exclude=True)
124+
writes_delegation: bool = Field(True, exclude=True)
125+
# Whether applying this authorization is the transaction's first
126+
# write to the authority's account leaf. False for a self-sponsored
127+
# authority (the sender is written at inclusion), for repeated
128+
# authorizations on one authority, and for invalid authorizations.
129+
first_write: bool = Field(True, exclude=True)
123130

124131
def model_post_init(self, __context: Any) -> None:
125132
"""

src/ethereum/forks/amsterdam/fork.py

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from typing import Final, List, Optional, Tuple, final
1616

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

@@ -1058,19 +1058,11 @@ def process_transaction(
10581058
effective_gas_fee = tx.gas * effective_gas_price
10591059

10601060
# Split execution gas into gas_left (capped by remaining regular gas
1061-
# budget) and state_gas_reservoir. The contract-creation component
1062-
# of the intrinsic state gas is seeded into the reservoir rather
1063-
# than pre-consumed; it funds the conditional charge at the
1064-
# deployment-address access.
1061+
# budget) and state_gas_reservoir.
10651062
execution_gas = tx.gas - intrinsic_gas
10661063
regular_gas_budget = TX_MAX_GAS_LIMIT - intrinsic.regular
10671064
gas = min(regular_gas_budget, execution_gas)
1068-
create_state_gas = (
1069-
Uint(StateGasCosts.NEW_ACCOUNT)
1070-
if isinstance(tx.to, Bytes0)
1071-
else Uint(0)
1072-
)
1073-
state_gas_reservoir = Uint(execution_gas - gas) + create_state_gas
1065+
state_gas_reservoir = Uint(execution_gas - gas)
10741066

10751067
increment_nonce(tx_state, sender)
10761068

@@ -1143,14 +1135,7 @@ def process_transaction(
11431135
# transfer miner fees
11441136
create_ether(tx_state, block_env.coinbase, U256(transaction_fee))
11451137

1146-
tx_state_gas = (
1147-
int(tx_env.intrinsic_state_gas)
1148-
- int(create_state_gas)
1149-
+ tx_output.state_gas_used
1150-
- int(tx_output.state_refund)
1151-
)
1152-
# Defensive guard for Uint conversion: State refunds never exceed
1153-
# the state charges so the value is non-negative.
1138+
tx_state_gas = int(tx_env.intrinsic_state_gas) + tx_output.state_gas_used
11541139
tx_regular_gas = tx_gas_used_before_refund - Uint(max(0, tx_state_gas))
11551140
block_output.block_gas_used += tx_regular_gas
11561141
block_output.block_state_gas_used += Uint(max(0, tx_state_gas))

0 commit comments

Comments
 (0)