Conversation
Move the state-dependent transaction charges from the intrinsic phase to a runtime gas phase applied as the first frame is entered, per the EIP-2780 update in ethereum/EIPs#11844. Gated on CfgEnv::enable_amsterdam_eip2780; older forks and 2780-disabled configs are unchanged. - Create transactions: the new-account state gas moves out of intrinsic gas and is charged at runtime, only when the deployment target does not already exist. Initcode revert/halt unwinds it LIFO via rollback_state_gas. - EIP-7702: the pessimistic per-auth intrinsic charge and its refunds are replaced by an intrinsic REGULAR_PER_AUTH_BASE_COST (7,816) plus runtime charges per authority: new-account state gas and ACCOUNT_WRITE for a non-existent authority, and delegation-indicator state gas for net-new delegation bytes. - Delegated recipient: the delegation-target access follows the standard EIP-2929 warm/cold model instead of a flat COLD_ACCOUNT_ACCESS. - Running out of gas in the runtime phase no longer invalidates the transaction: it is included as an out-of-gas halt consuming all gas, with all runtime state changes (including applied delegations) reverted.
Merging this PR will degrade performance by 4.07%
|
| Mode | Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|---|
| ❌ | Simulation | transact_1000tx_commit_inner_every_40 |
2.3 ms | 2.4 ms | -5.01% |
| ❌ | Simulation | transact_commit_1000txs |
2.9 ms | 3 ms | -4.14% |
| ❌ | Simulation | transfer_finalize |
17.3 µs | 18 µs | -4.02% |
| ❌ | Simulation | transfer |
14.5 µs | 14.9 µs | -3.1% |
Tip
Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.
Comparing rakita/eip2780-runtime-gas-phase (a0b11be) with main (8a6b28a)
Footnotes
-
1 benchmark was skipped, so the baseline result was used instead. If it was deleted from the codebase, click here and archive it to remove it from the performance reports. ↩
Aligns Amsterdam to the tests-glamsterdam-devnet@v7.0.0 fixtures. All state_tests and blockchain_tests pass (revme statetest/btest). Spec changes: - EIPs#11836: calldata floor anchored on the decomposed intrinsic base (TX_BASE + recipient/value charges) instead of flat TX_BASE. - EIPs#11891: 7702 ACCOUNT_WRITE charged on the first write to each authority, skipped only when already paid (sender, value-bearing recipient, or a preceding valid authorization). - EIPs#11858: CREATE/CREATE2 account-creation state gas charged conditionally at access in the creating frame before the 63/64 split, after the endowment/nonce pre-checks, only when the destination does not exist; refilled LIFO when the create fails. Top-frame charge for create transactions keeps the existing depth-0 path. - EIPs#11854: SSTORE must cover the slot's access cost before the implicit storage read; an unaffordable cold access skips the read so the slot stays out of the block access list. - EIP-8282: builder deposit/exit system-contract addresses updated. Runtime gas phase is now charged incrementally on a gas tracker and stops at the first unaffordable charge: later authorities and a cold delegation target are never loaded, keeping them out of the EIP-7928 BAL; the recipient is read up front so its BAL read survives a runtime out-of-gas. JournalEntry::CodeChange now records the previous code hash/bytecode and restores them on revert. The old revert-to-empty assumption breaks once 7702 delegation set/clear on already-delegated accounts becomes revertible; the corrupted account stayed untouched (state root correct) but leaked a phantom code write into the BAL. statetest runner updates for the v7 fixture format: explicit tx chainId (including type-0), optional secretKey (bad_v_r_s expects rejection), and >u64 nonces no longer panic the keep-going runner.
Move the depth-0 EIP-2780 state-gas decisions out of frame creation into the runtime gas phase (apply_eip2780_runtime_gas), reusing the existing runtime out-of-gas halt: - The recipient new-account charge (value transfer to an empty recipient) and the create transaction's account-creation charge (deployment target does not exist) are decided and affordability- checked at the runtime phase. An unaffordable charge sets runtime_oog and the handler includes the transaction as an out-of-gas halt without entering the frame; a create transaction still bumps the sender nonce on that path. - The decided charge travels to the first frame via FrameInit::state_gas_charge and InitialAndFloorGas::first_frame_state_gas and is recorded on the frame's gas tracker, keeping it refillable on revert/halt. make_call_frame/make_create_frame lose all cfg/journal/ depth logic and only apply the passed charge (defensive backstop on failure). - The runtime halt now returns the EIP-8037 reservoir instead of consuming it, matching the spec's top-frame halt settlement. The CREATE opcode and SSTORE cold-load check drop their EIP-2780 gate: the devnet-7 conditional account-creation charge and the access-cost-before-read ordering are now the sole EIP-8037 behavior, and the pre-devnet-7 unconditional-charge/refill path is removed, so the CREATE refill in return_result is gated purely on the recorded charge. The now-unused Host::is_amsterdam_eip2780_enabled is removed.
Each Amsterdam devnet supersedes the previous one, so the devnet-6 create accounting branches are removed rather than kept behind flag combinations: - The intrinsic `create_state_gas` fold for create transactions (and its `last_frame_result` refill on failed/alive-target creates) is gone; the charge is always decided at the runtime gas phase and recorded on the first frame's tracker. - `target_was_alive` is removed from `CreateFrame`/`CreateOutcome`; the CREATE refill is driven solely by `charged_create_state_gas`. - ee-tests and snapshots updated to the conditional-charge semantics. All glamsterdam devnet-7 v7.0.0 state and blockchain fixtures pass.
Member
Author
|
Will move to glam-devnet-7 branch |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements the EIP-2780 update from ethereum/EIPs#11844 (head
9d2e4e17): state-dependent transaction charges move from the intrinsic phase to a runtime gas phase, applied as the first frame is entered. Intrinsic gas stays the sole input to the validity check.Everything is gated on the existing
CfgEnv::enable_amsterdam_eip2780; older forks and 2780-disabled Amsterdam keep the previous code paths (theeip8037.rsee-tests, which disable the flag, pass unchanged).Changes
create_state_gas(183,600) is no longer intrinsic. It is charged inmake_create_frameat depth 0, on the frame's gas tracker, only when the deployment target does not already exist — so a create to a pre-existing (balance-only) target pays no new-account state gas, and an initcode revert/halt unwinds the charge LIFO viarollback_state_gas. The old top-level refill inlast_frame_resultis gated to the non-2780 path.REGULAR_PER_AUTH_BASE_COST = 7,816(neweip8038::EIP7702_PER_AUTH_BASE_REGULAR) plus runtime charges accumulated while applying the auth list (apply_auth_list_eip2780): a non-existent authority paysSTATE_BYTES_PER_NEW_ACCOUNT × CPSBstate +ACCOUNT_WRITEregular; net-new delegation bytes paySTATE_BYTES_PER_AUTH_BASE × CPSBstate, at most once per authority. No refunds.WARM_ACCESSif pre-warmed, e.g. by an access list) instead of a flatCOLD_ACCOUNT_ACCESS, charged in the runtime phase (apply_eip2780_runtime_gas).apply_eip2780_runtime_gaschecks affordability of the whole runtime phase (newInitialAndFloorGas::checked_initial_gas_and_reservoir). On OOG it reverts a checkpoint spanning the runtime phase — undoing applied delegations — and setsInitialAndFloorGas::runtime_oog;Handler::execution/inspect_executionthen include the transaction as an out-of-gas halt consuming all gas without entering the first frame.initial_regular_gas/initial_state_gas, so they are deducted pre-frame and survive in-frame reverts (delegations persist on in-frame failure). The recipient new-account state gas stays on the frame tracker so a revert rolls it back with the transfer.Spec notes / ambiguities
ACCOUNT_WRITEbullet's "(i.e. the authority differs fromtx.to)" parenthetical conflicts with Test Case 8 and the Interactions section ("ACCOUNT_WRITEfor a non-existent authority"); implemented as!existed, matching the old model's net charges.Testing
gas_paramsunit test for the intrinsic split.cargo nextest run --workspace(default / all / no-default features), clippy, fmt, typos: clean.for_amsterdam: 2663/2666 — the 3 failures (stInitCodeTest create-halt tests) encode the old devnet-6 refill-on-halt semantics; under the new spec the gas_left-drawn portion of the LIFO refill is consumed by the exceptional halt. Fixtures need regeneration for the updated spec.Follow-up: op-revm needs alignment with the
runtime_oogexecution path.