feat(spec-specs, tests): initial implementation of EIP-8038 state-access gas cost update#2972
Draft
danceratopz wants to merge 9 commits into
Draft
feat(spec-specs, tests): initial implementation of EIP-8038 state-access gas cost update#2972danceratopz wants to merge 9 commits into
danceratopz wants to merge 9 commits into
Conversation
`TLOAD` and `TSTORE` (EIP-1153) were charged via `WARM_ACCESS` because the two costs happened to coincide at 100 gas. Give them dedicated `OPCODE_TLOAD` and `OPCODE_TSTORE` constants so that state-access repricing does not implicitly reprice transient storage, which is in-memory only. Values are unchanged; EIP-7971 proposes new values for these operations and can update the constants in place when it lands.
Reprice state-access operations per EIP-8038 with provisional values (flat 3x of the legacy schedule, pending final benchmark results): - `WARM_ACCESS` 100 -> 300, `COLD_ACCOUNT_ACCESS` 2600 -> 7800, `COLD_STORAGE_ACCESS` 2100 -> 6300. - New `STORAGE_WRITE` (8400) replaces `COLD_STORAGE_WRITE`: `SSTORE` now always charges the access cost (cold or warm) and additionally charges `STORAGE_WRITE` on the first change to a slot; restoring the original value refunds `STORAGE_WRITE`. `REFUND_STORAGE_CLEAR` 4800 -> 14400. - New `ACCOUNT_WRITE` (20100): `CALL_VALUE` is redefined as `ACCOUNT_WRITE` + `CALL_STIPEND` (22400), and `SELFDESTRUCT` charges `ACCOUNT_WRITE` when a positive balance is sent to an empty account. - New `CREATE_ACCESS` (21000) replaces `REGULAR_GAS_CREATE` for `CREATE`/`CREATE2` and contract-creation transactions. - `EXTCODESIZE`/`EXTCODECOPY` charge an additional `WARM_ACCESS` for the second database read (the code). - Access list costs: `TX_ACCESS_LIST_ADDRESS` 2400 -> 7200, `TX_ACCESS_LIST_STORAGE_KEY` 1900 -> 5700. - EIP-7702 authorizations: the per-authorization intrinsic regular gas is `ACCOUNT_WRITE` + `REGULAR_PER_AUTH_BASE_COST` (replacing `PER_AUTH_BASE_COST`), and `ACCOUNT_WRITE` is refunded to the refund counter when the authority's account leaf already exists. Deviations of provisional values from the EIP's derivation formulas (`CREATE_ACCESS`, `REFUND_STORAGE_CLEAR`, access list costs) are flagged with comments at the constant definitions.
…load Mirror the spec-side change in the EEST fork gas model: `TLOAD` and `TSTORE` were priced via `WARM_SLOAD` because the two costs happened to coincide at 100 gas. Add dedicated `OPCODE_TLOAD` and `OPCODE_TSTORE` gas cost fields, set by the EIP-1153 mixin, so that state-access repricing does not implicitly reprice transient storage. Values are unchanged for all forks.
…as model Fold the EIP-8038 repricing into the EEST EIP-8037 mixin: the two EIPs ship together in Amsterdam and share one gas schedule, and the EIP-8037 compound constants (`AUTH_PER_EMPTY_ACCOUNT`, `TX_CREATE`, `STORAGE_SET`) are derived from the EIP-8038 parameters. Values are provisional (flat 3x), matching the spec. - Reprice the access, storage, call value, refund, and access list constants; model the new `SSTORE` formula (access cost always charged, `STORAGE_WRITE` on first change, refunded on restore). - Expose `ACCOUNT_WRITE` as a new `GasCosts` field (0 before the repricing): tests need it to model the capped regular-gas refund for EIP-7702 authorities with an existing account leaf, separately from the uncapped state refill (`REFUND_AUTH_PER_EXISTING_ACCOUNT`). - `EXTCODESIZE`/`EXTCODECOPY` charge an extra `WARM_ACCESS` for the second database read (the code). - `SELFDESTRUCT` charges `ACCOUNT_WRITE` when a positive balance is sent to an empty account.
Mirror the provisional EIP-8038 values in the test-side constants: - `REGULAR_GAS_CREATE` 9000 -> 21000: the spec's new `CREATE_ACCESS`. - `PER_AUTH_BASE_COST` 7500 -> 33116: total regular intrinsic per EIP-7702 authorization, `ACCOUNT_WRITE` (20100) + `REGULAR_PER_AUTH_BASE_COST` (13016 = 1616 calldata + 3000 ecRecover + 7800 cold account read + 2 x 300 warm writes). - `GAS_COLD_STORAGE_WRITE` 5000 -> 14700: `COLD_STORAGE_ACCESS` (6300) + `STORAGE_WRITE` (8400).
EIP-8038 splits the existing-authority adjustment for EIP-7702 authorizations into two channels with different cap semantics: - The new-account *state* portion is refilled directly to the state gas reservoir (`REFUND_AUTH_PER_EXISTING_ACCOUNT`, uncapped), as before. - The worst-case `ACCOUNT_WRITE` (20100) charged at intrinsic time is refunded via the regular refund counter, subject to the standard 1/5 refund cap. The cap typically binds: per-auth gas used before refund is well below 5 x 20100. Update the tests that model existing-authority refunds accordingly: - `test_auth_refund_block_gas_accounting`: receipt `cumulative_gas_used` subtracts `min(gas_used_before_refund // 5, ACCOUNT_WRITE)` for the `existing_leaf`/`existing_delegation` variants. Header `gas_used` is unchanged: EIP-7778 excludes refund-counter refunds from block accounting. - `test_auth_sender_billing_after_failure`: same capped term; the refund survives the top-level REVERT because delegations (and their refunds) are applied before execution. - EIP-7976 `max_refund` fixture: existing-authority refunds now contribute `ACCOUNT_WRITE` to the capped refund counter under EIP-8037/8038 (previously nothing; pre-8037 the full `REFUND_AUTH_PER_EXISTING_ACCOUNT`). - EIP-7778 `build_refund_tx`: add `ACCOUNT_WRITE` per authorization to the refund counter, outside the revert guard for the same reason as the sender-billing test.
EIP-8038 charges `ACCOUNT_WRITE` (20100) in regular gas when SELFDESTRUCT sends a positive balance to an empty account, alongside the EIP-8037 account-creation state gas. - Rework `test_selfdestruct_new_beneficiary_no_regular_account_creation_cost` into `test_selfdestruct_new_beneficiary_account_write_cost`: the old premise (no regular charge, asserted via a tight budget with 20000 slack) inverted, since `ACCOUNT_WRITE` exceeds the slack and the transaction ran out of gas. The reworked test tags the opcode with `account_new=True` so the framework folds `ACCOUNT_WRITE` plus the state gas into the budget, and tightens the slack to 4000, below the legacy 25000 minus `ACCOUNT_WRITE`: any regular draw beyond `ACCOUNT_WRITE` still runs out of gas, proving the charge is exactly the new parameter and not the legacy combined cost. - `test_selfdestruct_in_create_tx_initcode`: the budget left only 1000 slack above the account-creation state gas, so the new charge made the creation transaction halt. Tag `account_new=True` and drop the now redundant explicit `NEW_ACCOUNT` term.
Under the EIP-8038 SSTORE formula the access cost is always charged and the write cost is added only on the first change to a slot. A cold no-op store therefore costs `COLD_STORAGE_ACCESS` flat; the legacy formula charged cold plus warm (warm was the "else" branch of the write cost). The halt-path gas simulation in `test_parent_state_gas_after_child_failure` kept the legacy `+ WARM_ACCESS` for the factory's cold no-op SSTORE, overstating the expected receipt by exactly 300. The revert variants were unaffected: they compute the same bytecode via `bytecode.gas_cost(fork)`, which uses the framework formula.
Adjust hardcoded gas budgets and boundary parameters that the EIP-8038 repricing pushed over their tuning point. No test semantics change; each value is re-derived for the provisional costs: - `test_delegatecall_child_spill_not_double_charged`: 700k -> 1M; six cold zero-to-nonzero SSTOREs now cost 6 x (14700 regular + 97920 state) = 675720 before intrinsic and call overhead. Still far below the transaction gas cap, preserving the no-reservoir premise. - `test_code_deposit_oog_preserves_parent_reservoir`: forwarded `child_gas` 1M -> 1.5M; the factory's retained 1/64 (~15k) no longer covered its repriced post-CREATE SSTOREs (~21k); now ~23k. The code-deposit OOG premise is unaffected (deposit state gas 6.27M). - `test_failed_create_with_value_no_log` (EIP-7708): 500k -> 800k; the INVALID initcode burns all forwarded gas and the retained 1/64 (~4.3k) no longer covered the cold no-op SSTORE (6300), so the frame halted and the expected transaction-level transfer log disappeared. - `test_bal_create_oog_code_deposit` (EIP-7928): regular headroom 500k -> 1.1M for the same retained-1/64 reason; the parent's halt rolled back the nonce change the block access list expects. - EIP-7976 `intrinsic_gas_data_floor_minimum_delta`: 250 -> 11000; the SSTORE-clear prefix now costs 14706, and the floor must clear the post-refund execution cost (the fixture's own assert diagnosed this). The floor grows 64/byte vs 16/byte intrinsic, so ~9.7k of delta closes the gap; 11000 leaves margin. - EIP-7981 floor-boundary test: minimum calldata size 1000 -> 1700 nonzero bytes; the tripled access-list intrinsic cost (7200 + 10 x 5700) exceeded the floor midpoint the test pins `gas_limit` to. Each nonzero byte closes the gap by 48 gas (floor +64, intrinsic +16), so the premise needs at least 1565 bytes.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## eips/amsterdam/eip-8038 #2972 +/- ##
===========================================================
- Coverage 90.53% 89.00% -1.53%
===========================================================
Files 535 496 -39
Lines 32897 30098 -2799
Branches 3021 2725 -296
===========================================================
- Hits 29782 26788 -2994
- Misses 2596 2835 +239
+ Partials 519 475 -44
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
danceratopz
commented
Jun 11, 2026
Member
Author
There was a problem hiding this comment.
The changes here are because we split regular and state-access gas costs in
#2972 changes/3cadf8299ac440e535c1a61a2eb9b17de8b2dc77 so that we don't incorrectly price transient storage at the new values.
danceratopz
commented
Jun 11, 2026
Comment on lines
+152
to
+153
| OPCODE_TLOAD: int = 0 | ||
| OPCODE_TSTORE: int = 0 |
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.
🗒️ Description
WIP: many failing tests for
--fork Amsterdamoutside of./tests/amsterdam, probably worth getting #2969 in firstImplements EIP-8038: State-access gas cost update in
forks/amsterdam, with provisional values (flat 3x of the legacy schedule) pending final benchmark results:COLD_ACCOUNT_ACCESSACCOUNT_WRITECOLD_STORAGE_ACCESSSTORAGE_WRITEWARM_ACCESSSTORAGE_CLEAR_REFUNDCREATE_ACCESSACCESS_LIST_STORAGE_KEY_COSTACCESS_LIST_ADDRESS_COSTThe commits are organized to be reviewed in order:
refactor(specs): giveTLOAD/TSTOREdedicated gas constants (unchanged at 100) so theWARM_ACCESSbump does not implicitly reprice transient storage, which is in-memory only and not covered by EIP-8038. Forward-compatible with EIP-7971.feat(specs): the repricing itself, including the newSSTOREformula (access cost always charged;STORAGE_WRITEon first change, refunded on restore), theEXT*second-read surcharge, theSELFDESTRUCTACCOUNT_WRITEcharge,CALL_VALUE=ACCOUNT_WRITE+CALL_STIPEND, and the EIP-7702 per-authorization intrinsic cost restructuring with theACCOUNT_WRITErefund for existing authorities.refactor(test-forks): the same transient-storage decoupling in the EEST fork gas model.feat(test-forks): the repricing in the EEST fork gas model (folded into theEIP8037mixin, which shares the gas schedule), including theEXT*/SELFDESTRUCTformula updates and a newGasCosts.ACCOUNT_WRITEfield.chore(tests)commits updating test expectations, grouped by fix type so each can be reviewed against one rule: the EIP-8037 test spec constants; the split EIP-7702 refund channels (uncapped state refill vs cappedACCOUNT_WRITEregular refund); the newSELFDESTRUCTaccount-write charge; the cold no-opSSTOREcost (access only, no warm surcharge) in a hand-rolled formula; and mechanical gas budget/boundary re-tunes that the repricing pushed into OOG. Each commit body documents the rule and the re-derived values.Where the provisional flat-3x values deviate from the EIP's own derivation formulas (
CREATE_ACCESS21,000 vs 26,400;STORAGE_CLEAR_REFUND14,400 vs 14,112; access list costs vs "equal to cold cost"), the deviation is flagged with a comment at the constant definition; the values follow the provisional table and need confirmation before this ships.Deliberately out of scope (tracked as EIP-8037 follow-up work, since the EIP-8037 markdown spec was updated in ethereum/EIPs@
a3749180after the Python implementation merged in #2901):ACCOUNT_WRITErefund for invalid authorizations (new rule 1).pre_delegated/cur_delegateddelegation-indicator refill semantics, including the0 -> a -> 0double refill (new rule 3).Validation:
tests/amsterdam/fills green at Amsterdam, and the EEST unit suite passes.🔗 Related Issues or PRs
test_failed_create_with_value_no_log, EIP-7928test_bal_create_oog_code_deposit) become unnecessary once it lands and can be dropped on rebase. The remaining changes are orthogonal.✅ Checklist
just statictype(scope):.Cute Animal Picture