Skip to content

Commit 77e95ef

Browse files
gurukamathnerolationmarioevzspencer-tb
authored
feat(spec-specs,test-tests): update the EIP-2780 implementation (ethereum#3126)
* feat(spec-specs): EIP-2780 charge state-dependent tx costs at the top frame Move every state-dependent charge out of the transaction intrinsic -- which over-charged and then refunded -- and into the top frame, charged lazily against the transaction's pre-state. The intrinsic keeps only state-independent costs and remains the sole validity input: contract creation no longer adds NEW_ACCOUNT, and each authorization adds only REGULAR_PER_AUTH_BASE_COST. At depth 0, process_message now: - Applies the EIP-7702 authorizations via set_delegation, charging per authorization: NEW_ACCOUNT when the authority's leaf does not exist; ACCOUNT_WRITE on the transaction's first write to the authority (the written set is seeded with the sender, so a self-sponsored authority pays nothing and repeated authorizations pay once); and AUTH_BASE once per authority for a net-new delegation indicator, never credited back. - Folds the authorization state gas into the frame baseline (Evm.auth_state_gas_used, rebased state_gas_reservoir) so a dispatch revert or halt cannot refill it -- the delegations, nonce bumps, and authority leaves persist per EIP-7702's dispatch-failure rule. A preparation-phase out-of-gas instead rolls the delegations back, undoes the fold, and halts consuming all gas without dispatching. - Charges the remaining dispatch costs in prepare_dispatch, which must not mutate transaction state: the recipient's or creation target's NEW_ACCOUNT keyed on pre-transaction aliveness (still refillable, since that state rolls back with the frame), and delegation resolution at WARM_ACCESS or COLD_ACCOUNT_ACCESS by target warmth. set_delegation is thereby the last transaction-state mutation before dispatch, so the execution snapshot brackets exactly the state the folded gas paid for. Remove the superseded over-charge/refund plumbing: the intrinsic NEW_ACCOUNT refund paths in fork.py and the vestigial MessageCallOutput state_refund / created_target_alive fields. validate_authorization returns just the authority address. * feat(amsterdam): model EIP-2780 top-frame gas in the test framework Teach execution_testing the EIP-2780 split between intrinsic and top-frame charges: - Intrinsic calculators charge only the state-independent costs: REGULAR_PER_AUTH_BASE_COST per authorization and no NEW_ACCOUNT for contract creation. AUTH_BASE (EIP-8037) and REGULAR_PER_AUTH_BASE_COST (EIP-8038) become named gas costs. - The top-frame calculators (transaction_top_frame_gas_calculator, transaction_top_frame_state_gas) price the per-authorization NEW_ACCOUNT / ACCOUNT_WRITE / AUTH_BASE from the authorizations list threaded into them, the recipient's or creation target's NEW_ACCOUNT, and delegation-target resolution as warm or cold via delegation_warm. - AuthorizationTuple carries creates_account / writes_delegation / first_write annotations (excluded from serialization) so each tuple declares its pre-state effect; ACCOUNT_WRITE keys on first_write -- the transaction's first write to the authority -- rather than leaf creation. The AuthorizationGasInfo structural Protocol lets forks read the annotations without importing test_types. - EIP2780.refund_types() drops AUTHORIZATION_EXISTING_AUTHORITY: Amsterdam charges authorizations by pre-state instead of over-charging and refunding, while Prague's refund coverage keeps the shared enum member. * test(amsterdam): cover the EIP-2780 top-frame charges Pin the EIP-2780 top-frame model end to end in the EIP-2780 suite: - test_authorization_charges.py (new): per-authorization billing keyed on the authority's pre-transaction state -- NEW_ACCOUNT for new leaves; ACCOUNT_WRITE on the transaction's first write (existing authorities pay it, the sender and repeated authorizations do not, tx.to as authority does); AUTH_BASE only for a net-new delegation, at most once per authority, never credited back (pre-tx-delegated re-set and clear-then-set, multiple sets, set/clear cycles). - test_authorization_oog.py (new): charge-by-charge out-of-gas points across the preparation phase -- a prep out-of-gas rolls back every applied delegation and halts without dispatching, while applied authorization state gas survives a dispatch revert or halt (balance-, header-, and reservoir-level pins), with guards that the recipient's NEW_ACCOUNT and in-frame SSTORE state gas still refill when their state rolls back. - test_calldata_floor.py / test_intrinsic_gas_boundary.py: a data-heavy transfer whose EIP-7623/7976 floor (built on the lowered TX_BASE) dominates the decomposed intrinsic, one-below-floor rejection, and the contract-creation boundary on the regular-only intrinsic. The floor-dominating calldata size comes from the shared find_floor_cost_threshold search rather than a hardcoded value. - Migrations: the value-moving tests adopt the top-frame NEW_ACCOUNT for the creation target (charged on success, refilled when the init code reverts), the delegation variants annotate self-sponsored tuples with first_write, the warmth invariants adopt warm/cold delegation-target access, and helpers.py's authorization actions declare the creates_account / writes_delegation / first_write annotations the framework calculators consume. * test(amsterdam): migrate remaining suites to the EIP-2780 top-frame model EIP-2780 moved the contract-creation NEW_ACCOUNT and every state-dependent authorization cost out of the intrinsic and into the top frame, with no over-charge refunds. Migrate the suites that pinned the old model: - eip8037 test_state_gas_set_code.py: authorization tests bill by the declared pre-state annotations; receipts are plain sums with the first-write ACCOUNT_WRITE included, headers are max(block_regular, block_state), and authorization state gas persists on the block's state dimension through every dispatch-failure mode (no refill on revert, halt, or out-of-gas). - eip8037 create cluster (test_state_gas_create.py, test_state_gas_reservoir.py, test_state_gas_pricing.py): gas limits cover the top-frame NEW_ACCOUNT; reject-below-intrinsic keys off the regular-only intrinsic; NEW_ACCOUNT refills on init-code failure and is never charged on collision; AUTH_BASE is measured via transaction_top_frame_state_gas; an existing-authority authorization credits nothing to the reservoir. - eip8038 (test_create_gas.py, test_selfdestruct_gas.py, test_set_code_auth_gas.py, test_set_code_auth_refunds.py, test_fork_transition.py): the same top-frame NEW_ACCOUNT treatment for creates, the reduced refund-free per-authorization charges, and the authorization intrinsic drop across the BPO2->Amsterdam transition. - eip7778 / eip7976: drop the AUTHORIZATION_EXISTING_AUTHORITY refund path (Amsterdam no longer has that refund type) and add the top-frame regular charge to expected receipts. - eip7954 test_max_code_size.py: add the top-frame state gas back into the exact-fit deposit-gas limit so it still lands precisely on the code-deposit boundary. * test(amsterdam): cover BAL inclusion at the top-frame delegation charge The delegation target of a delegated tx.to must enter the block access list only when the EIP-2780 top-frame access charge succeeds. Pin the charge-before-access order of the top-frame dispatch: on out-of-gas the target stays out of the BAL, on success it appears with an empty change set. * feat(amsterdam): EIP-2780 exempt value-bearing recipient authority from ACCOUNT_WRITE When a transaction moves value to its recipient and that recipient is also an authority, seed the recipient into `set_delegation`'s written-set: the value transfer already pays to write the recipient, so applying the authorization is not the transaction's first write to it and no `ACCOUNT_WRITE` is charged. Adopt the framework top-frame calculators in the EIP-2780 authorization charge and value-moving tests (driving the charges through the `first_write` / `writes_delegation` / `creates_account` annotations instead of hand-summed gas constants), and refresh the now value-dependent `ACCOUNT_WRITE` docstrings. * refactor(specs,tests): Remove intrinsic state gas concept * refactor(specs): Remove intrinsic state gas concept * refactor(test-forks): Remove intrinsic state gas * fix(tests): Fix intrinsic state gas usages * fix(tests): correct inert authorization flags in delegation-pointer tests The two migrated delegation-pointer tests re-target an authority that already has a delegation and whose account nonce (1, from the delegation setup) no longer matches the authorization's nonce=0, so the authorization is invalid and charges no top-frame state gas. Setting writes_delegation/first_write to True added a phantom AUTH_BASE to state_gas_reservoir that was silently refunded, contradicting the AuthorizationTuple.first_write contract (False for invalid authorizations) and the spec's AUTH_BASE gate (not delegated_before_tx). Set both flags to False so the reserved state gas matches what the top frame actually charges, and reuse the single authorization object in test_delegation_pointer_new_account_state_gas instead of building a second inline tuple. * fix(test-forks): drop deleted transaction_intrinsic_state_gas call The execute plugin's _compute_deploy_gas_limit still called the removed transaction_intrinsic_state_gas, which failed mypy (and CI static). On every concrete fork that value was zero (BaseFork default, or forced to zero once EIP-2780 moves the created account's NEW_ACCOUNT to the top frame), and the intrinsic calculator already returns the regular-only cost, so the back-out was a no-op. Remove it, keeping the deploy gas limit unchanged. --------- Co-authored-by: Guruprasad Kamath <guru241987@gmail.com> * test(amsterdam): reconcile floor-pinned expectations with header gas accounting After the top-frame decomposition, the calldata floor exceeds the create intrinsic for tiny initcode. The floor pins only the amount billed (receipt cumulative gas); the block header counts pre-refund, pre-floor regular consumption. Build expectations from the pre-floor intrinsic, apply the floor once to the receipt only, and verify the header and receipt separately where they diverge. * fix(spec-specs, tests): exclude unloaded recipient from the BAL * fix(spec-specs, tests): exclude unloaded recipient from the BAL (cherry picked from commit 89d15f3) * refactor(spec-specs): represent unresolved top-frame code as None * test(amsterdam): pin reservoir settlement at each top-frame failure point A transaction whose gas limit exceeds the EIP-7825 cap forms a state-gas reservoir, and how much of it returns depends on where the transaction fails. Two tests drive one many-authorization transaction shape to every failure point: - a preparation out-of-gas (inside set_delegation or at the dispatch charge) rolls everything back and returns the reservoir whole: the sender pays exactly TX_MAX_GAS_LIMIT, however much extra was sent; - an execution failure keeps the applied delegations and their state gas consumed, so the reservoir is preserved only down to the post-authorization baseline -- an exceptional halt burns the full gas limit when the auth state gas exceeds the reservoir, while a revert pays exact usage. The value-bearing variant targets the bn254 pairing precompile (the one empty recipient that executes) so both state-charge classes ride one transaction: the halt scenario sizes the reservoir above the auth state gas to make the recipient NEW_ACCOUNT refill observable in the settlement, pinning gas_used == cap + auth state gas exactly. Also corrects the module docstring, which claimed a preparation-phase OOG consumes the full gas limit -- only true with a zero reservoir. * post review updates * fix(tests): Ported static failing tests --------- Co-authored-by: Toni Wahrstätter <info@toniwahrstaetter.com> Co-authored-by: Mario Vega <marioevz@gmail.com> Co-authored-by: spencer <spencer.tb@ethereum.org>
1 parent a9abd46 commit 77e95ef

42 files changed

Lines changed: 4838 additions & 1943 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -250,13 +250,10 @@ def _compute_deploy_gas_limit(
250250
sstore_state_gas = sstore.state_cost(fork)
251251
sstore_regular_gas = sstore.gas_cost(fork) - sstore_state_gas
252252

253-
# Back out the state gas folded into TX_CREATE.
254-
intrinsic_state_gas = fork.transaction_intrinsic_state_gas(
255-
contract_creation=True
256-
)
257-
intrinsic_regular_gas = (
258-
intrinsic_gas_calculator(calldata=initcode, contract_creation=True)
259-
- intrinsic_state_gas
253+
# The intrinsic cost is now regular-only: the created account's
254+
# NEW_ACCOUNT state gas is charged at the top frame, not folded in.
255+
intrinsic_regular_gas = intrinsic_gas_calculator(
256+
calldata=initcode, contract_creation=True
260257
)
261258

262259
# Regular portion, bound by the gas cap.
@@ -291,8 +288,7 @@ def _compute_deploy_gas_limit(
291288
regular_gas = buffered_regular_gas
292289

293290
# State portion, from the block reservoir.
294-
state_gas = intrinsic_state_gas
295-
state_gas += fork.code_deposit_state_gas(code_size=deploy_code_size)
291+
state_gas = fork.code_deposit_state_gas(code_size=deploy_code_size)
296292
state_gas += storage_slots * sstore_state_gas
297293

298294
deploy_gas_limit = regular_gas + state_gas

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

Lines changed: 28 additions & 16 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
@@ -758,17 +777,6 @@ def transaction_intrinsic_cost_calculator(
758777
"""
759778
pass
760779

761-
@classmethod
762-
def transaction_intrinsic_state_gas(
763-
cls,
764-
*,
765-
contract_creation: bool = False,
766-
authorization_count: int = 0,
767-
) -> int:
768-
"""Return intrinsic state gas (zero pre-Amsterdam)."""
769-
del contract_creation, authorization_count
770-
return 0
771-
772780
@classmethod
773781
def transaction_top_frame_gas_calculator(
774782
cls,
@@ -787,8 +795,11 @@ def fn(
787795
contract_creation: bool = False,
788796
sends_value: bool = False,
789797
recipient_type: RecipientType = RecipientType.CONTRACT,
798+
delegation_warm: bool = False,
799+
authorizations: Sequence[AuthorizationGasInfo] = (),
790800
) -> int:
791801
del contract_creation, sends_value, recipient_type
802+
del delegation_warm, authorizations
792803
return 0
793804

794805
return fn
@@ -800,6 +811,7 @@ def transaction_top_frame_state_gas(
800811
contract_creation: bool = False,
801812
sends_value: bool = False,
802813
recipient_type: RecipientType = RecipientType.CONTRACT,
814+
authorizations: Sequence[AuthorizationGasInfo] = (),
803815
) -> int:
804816
"""
805817
Return the state gas charged at the top-level transaction
@@ -811,7 +823,7 @@ def transaction_top_frame_state_gas(
811823
Defaults to 0 for forks that do not perform such
812824
post-intrinsic preparation.
813825
"""
814-
del contract_creation, sends_value, recipient_type
826+
del contract_creation, sends_value, recipient_type, authorizations
815827
return 0
816828

817829
@classmethod

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

Lines changed: 79 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:
@@ -160,10 +184,14 @@ def transaction_top_frame_gas_calculator(
160184
transaction frame, after intrinsic gas is deducted but before
161185
the EVM dispatches.
162186
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``.
187+
Charges the delegation-target access when the recipient is an
188+
existing delegated account: ``WARM_ACCESS`` when the target is
189+
already warm, otherwise ``COLD_ACCOUNT_ACCESS``. Each
190+
authorization whose application is the transaction's first
191+
write to its authority's leaf (``first_write``) adds
192+
``ACCOUNT_WRITE``. The state-gas portions (empty-recipient and
193+
per-authorization ``NEW_ACCOUNT``, plus ``AUTH_BASE``) are
194+
returned separately by ``transaction_top_frame_state_gas``.
167195
"""
168196
gas_costs = cls.gas_costs()
169197

@@ -172,14 +200,24 @@ def fn(
172200
contract_creation: bool = False,
173201
sends_value: bool = False,
174202
recipient_type: RecipientType = RecipientType.CONTRACT,
203+
delegation_warm: bool = False,
204+
authorizations: Sequence[AuthorizationGasInfo] = (),
175205
) -> int:
176206
del sends_value
177207
if contract_creation:
178208
return 0
179209

210+
regular = 0
180211
if recipient_type == RecipientType.DELEGATION_7702:
181-
return gas_costs.COLD_ACCOUNT_ACCESS
182-
return 0
212+
regular += (
213+
gas_costs.WARM_ACCESS
214+
if delegation_warm
215+
else gas_costs.COLD_ACCOUNT_ACCESS
216+
)
217+
for auth in authorizations:
218+
if auth.first_write:
219+
regular += gas_costs.ACCOUNT_WRITE
220+
return regular
183221

184222
return fn
185223

@@ -190,15 +228,43 @@ def transaction_top_frame_state_gas(
190228
contract_creation: bool = False,
191229
sends_value: bool = False,
192230
recipient_type: RecipientType = RecipientType.CONTRACT,
231+
authorizations: Sequence[AuthorizationGasInfo] = (),
193232
) -> int:
194233
"""
195234
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.
235+
frame. A contract creation charges the created account's
236+
``NEW_ACCOUNT`` here (state-dependent, no longer intrinsic),
237+
assuming a fresh target. Otherwise, charges ``NEW_ACCOUNT`` when
238+
value is transferred to an empty recipient, and each
239+
authorization adds ``NEW_ACCOUNT`` when its authority's account
240+
leaf must be created and ``AUTH_BASE`` when it writes a net-new
241+
delegation indicator.
198242
"""
199243
gas_costs = cls.gas_costs()
200244
if contract_creation:
201-
return 0
202-
if sends_value and recipient_type == RecipientType.EMPTY_ACCOUNT:
203245
return gas_costs.NEW_ACCOUNT
204-
return 0
246+
state = 0
247+
if sends_value and recipient_type == RecipientType.EMPTY_ACCOUNT:
248+
state += gas_costs.NEW_ACCOUNT
249+
for auth in authorizations:
250+
if auth.creates_account:
251+
state += gas_costs.NEW_ACCOUNT
252+
if auth.writes_delegation:
253+
state += gas_costs.AUTH_BASE
254+
return state
255+
256+
@classmethod
257+
def refund_types(cls) -> List[RefundTypes]:
258+
"""
259+
Drop the existing-authority authorization refund.
260+
261+
EIP-2780 charges each authorization's state-dependent cost at the
262+
top frame, keyed on the authority's pre-transaction state, with no
263+
refund. The Prague-era ``AUTHORIZATION_EXISTING_AUTHORITY`` refund
264+
therefore no longer applies.
265+
"""
266+
return [
267+
refund
268+
for refund in super(EIP2780, cls).refund_types()
269+
if refund != RefundTypes.AUTHORIZATION_EXISTING_AUTHORITY
270+
]

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

Lines changed: 1 addition & 24 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
@@ -231,30 +232,6 @@ def fn(opcode: OpcodeBase) -> int:
231232

232233
return fn
233234

234-
@classmethod
235-
def transaction_intrinsic_state_gas(
236-
cls,
237-
*,
238-
contract_creation: bool = False,
239-
authorization_count: int = 0,
240-
) -> int:
241-
"""
242-
Return the intrinsic state gas for a transaction. Creation
243-
adds `STATE_BYTES_PER_NEW_ACCOUNT * cpsb`, and each
244-
authorization adds
245-
`(STATE_BYTES_PER_NEW_ACCOUNT + STATE_BYTES_PER_AUTH_BASE) * cpsb`.
246-
"""
247-
cpsb = cls.cost_per_state_byte()
248-
state_gas = 0
249-
if contract_creation:
250-
state_gas += STATE_BYTES_PER_NEW_ACCOUNT * cpsb
251-
state_gas += (
252-
(STATE_BYTES_PER_NEW_ACCOUNT + STATE_BYTES_PER_AUTH_BASE)
253-
* cpsb
254-
* authorization_count
255-
)
256-
return state_gas
257-
258235
@classmethod
259236
def _calculate_sstore_state_gas(
260237
cls, opcode: OpcodeBase, gas_costs: GasCosts

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
"""

0 commit comments

Comments
 (0)