feat(specs): EIP-8037 diff-at-call-return state gas charging [DRAFT]#2683
Closed
spencer-tb wants to merge 30 commits intoethereum:eips/amsterdam/eip-8037from
Closed
feat(specs): EIP-8037 diff-at-call-return state gas charging [DRAFT]#2683spencer-tb wants to merge 30 commits intoethereum:eips/amsterdam/eip-8037from
spencer-tb wants to merge 30 commits intoethereum:eips/amsterdam/eip-8037from
Conversation
…se (ethereum#2363) * feat(spec-specs): update EIP-8037 to match latest spec revision * feat(tests): add EIP-8037 state creation gas cost increase tests
Co-authored-by: Ben Adams <thundercat@illyriad.co.uk>
Add sstore_state_gas(), code_deposit_state_gas(), and create_state_gas() calculator methods to Fork. Replace Spec constants and manual gas arithmetic across all EIP-8037 tests with dynamic fork method calls. Remove redundant Op.STOP, hardcoded numbers from docstrings, and add fork: Fork parameter to all test functions that use fork methods.
Test CREATE with max initcode size using proper regular/state gas split via the reservoir. Verifies gas boundary behavior with EIP-8037 two-dimensional metering.
Align EIP-8037 gas constant references with upstream renames: - GAS_STORAGE_UPDATE → GAS_COLD_STORAGE_WRITE - GAS_COLD_SLOAD → GAS_COLD_STORAGE_ACCESS - GAS_WARM_ACCOUNT_ACCESS → GAS_WARM_ACCESS Bump gas_limit on tests added to upstream after EIP-8037 branched, which now need extra gas for EIP-8037 state gas costs.
… format runs in withdrawal request contract tests (ethereum#2532) * fix(tests): prevent tx_gas_limit double-accumulation across fixture format runs in withdrawal request contract tests * fix: mypy * fix: ruff * fix: ruff * chore(tests): refactor fix to fork-aware transactions to prevent mutation * chore(test): add a warning to all tests that could mutate vars; address in later PR Add a lightweight repr-based snapshot hook to the filler plugin that warns whenever any ``pytest.param`` value is mutated during a test run. A subsequent PR could address this by returning values instead of mutating, then flipping the hook to a hard failure. --------- Co-authored-by: fselmo <fselmo2@gmail.com>
…hereum#2526) Co-authored-by: fselmo <fselmo2@gmail.com>
… gas validity test (ethereum#2583) Co-authored-by: Stefan <22667037+qu0b@users.noreply.github.com>
Conditionally increase tx gas_limit (and env gas_limit where needed) when fork >= Amsterdam to account for EIP-8037 state creation gas costs. 137 files, 9 with env gas_limit bumps. Headroom: 2,000,000.
Move MAX_CODE_SIZE check before gas charges, charge keccak hash cost (regular gas) before code deposit state gas, and add tests for over-max code size and reservoir preservation after OOG.
On CREATE/CREATE2 address collision the 63/64 gas allocation is burned but was not added to regular_gas_used, leaving it invisible to 2D block gas accounting. Per EIP-684 collision behaves as an immediate exceptional halt, so the burned gas belongs in the regular dimension.
…and subcall pattern Co-authored-by: Mario Vega <marioevz@gmail.com>
The static test skip list and conftest were a temporary workaround for EIP-8037 gas failures. The ported static tests in tests/ported_static/ replace this approach; failures are tracked in ethereum#2601.
…thereum#2603) * fix(execute): use --sender-fund-refund-gas-limit for all funding txs On EIP-8037 networks, simple value transfers to new accounts require more than 21000 gas due to GAS_NEW_ACCOUNT state gas (112 * cpsb). The default Transaction gas_limit of 21000 causes 'intrinsic gas too low' errors during test setup. Changes: - contracts.py: Use 200000 gas for deterministic factory deployer funding - pre_alloc.py: Pass sender_fund_refund_gas_limit to Alloc and use it for simple EOA funding transactions Usage: --sender-fund-refund-gas-limit 200000 * chore: additional fixes for execute remote funds w/ higher gas limits * fix: bump all gas limits to 200k for EIP-8037 state creation costs - sender.py: bump --sender-fund-refund-gas-limit default 21000 → 200000 - pre_alloc.py: bump funding_gas_limit default 21000 → 200000 - pre_alloc.py: use configurable gas limit for per-test refund txs - execute_recover.py: bump recovery refund gas limit 21000 → 200000 EIP-8037 charges GAS_NEW_ACCOUNT (112 × cost_per_state_byte = 131488) for transfers to new accounts, making 21000 gas insufficient for all funding, refund, and recovery transactions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Felipe Selmo <fselmo2@gmail.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… gas (ethereum#2595) Co-authored-by: spencer-tb <spencer.tb@ethereum.org>
…um#2610) Co-authored-by: spencer-tb <spencer.tb@ethereum.org>
…ode size validation (ethereum#2608) * fix(spec): charge CREATE state gas after initcode size validation Move charge_state_gas(STATE_BYTES_PER_NEW_ACCOUNT) from create()/create2() into generic_create(), after the MAX_INIT_CODE_SIZE check. Previously, state gas was charged before the initcode size check, so a CREATE with oversized initcode would persist state_gas_used equal to the account creation state gas cost (STATE_BYTES_PER_NEW_ACCOUNT * cost_per_state_byte) even though no account was ever created and no state was touched. Reported by @AskDragan (reth): ethereum#2578 (comment) * fix(spec): check static context before gas in CREATE/CREATE2 Move the is_static check from generic_create() into create() and create2(), before stack pops and charge_gas(). This is consistent with SSTORE, CALL, and SELFDESTRUCT which all check static context before any gas charging.
659ad2f to
23602a0
Compare
Replace per-opcode state gas charging with a single state diff computed at each call return point. State gas is charged based on actual state growth (new accounts, storage slots, deployed code) rather than attempted operations. Changes: - Add compute_state_growth_cost() to state_tracker.py - Add diff-at-return logic in process_message() with spillover - Remove charge_state_gas from CREATE, CALL, SSTORE, SELFDESTRUCT - Remove code deposit state gas charge from process_create_message - Simplify incorporate_child_on_error (no state gas restore needed) - Handle negative diffs (state removed credits reservoir) - Saturating subtraction in tx accounting to prevent underflow
- test_block_gas_refund_eip7778_no_block_reduction: set-then-restore SSTORE has net zero state diff, update expected gas_used - test_call_value_transfer_existing_account_no_state_gas: use alive target (amount=1) and forward all gas (Op.GAS) since callee pays state gas at return - test_caller_reservoir_preserved_after_callee_diff_reverts: replaces test_code_deposit_oog_preserves_parent_reservoir, tests caller reservoir preserved when callee reverts due to expensive state diff - test_parent_reverts_when_create_diff_exceeds_budget: replaces test_nested_create_code_deposit_cannot_borrow_parent_gas, tests all-or-nothing CREATE revert when diff exceeds budget
23602a0 to
422e17e
Compare
9755dba to
3e1d7c4
Compare
3e1d7c4 to
44b47cc
Compare
cc2cd47 to
44c4c15
Compare
Contributor
Author
|
Closing as no longer using this spec change! |
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.
Summary
Prototype implementation of diff-at-call-return state gas charging for EIP-8037, as proposed in the #eip-8037 discord channel by @chfast.
Instead of charging state gas at each opcode (CREATE, CALL, SSTORE, SELFDESTRUCT), compute a single state diff at each call return point and charge based on actual state growth. This eliminates most edge cases from Maria's state gas accounting review (points 1-6).
State gas charges at call return draw from the reservoir first, when exhausted, the remainder spills into regular gas, preserving the existing spillover semantics.
Open Spec Questions & Resolution
1. Who pays state gas for value transfer creating a new account?
Resolution: the callee's call frame pays.
Consequence: the caller must forward enough gas to cover state creation costs at the callee's return.
2. How does code deposit failure work?
Resolution: CREATE is all-or-nothing at call return.
Consequence: no partial state where an account exists without code. The call either fully succeeds or fully reverts.
3. Can the reservoir grow beyond its initial value?
Resolution: yes, capped at transaction level.
Test changes
These tests assumed per-opcode state gas charging. With diff-at-return, state gas is computed from the net state diff at call return, which changes gas amounts, failure modes, and who pays.
test_block_gas_refund_eip7778_no_block_reduction: Updated expected gas_used. The set-then-restore SSTORE pattern (0 to 1 to 0) now produces zero state gas because the net state diff is zero. Previously, state gas was charged on the first write and partially refunded via refund_counter.
test_call_value_transfer_existing_account_no_state_gas: Changed target from empty account (amount=0) to alive account (amount=1) and forwarded all gas via Op.GAS. With diff-at-return, the callee pays state gas at its call return, so the callee needs enough gas to cover GAS_NEW_ACCOUNT if the target is not alive.
test_caller_reservoir_preserved_after_callee_diff_reverts: Replaces test_code_deposit_oog_preserves_parent_reservoir. The caller CALLs a factory that does CREATE with large code. The factory's state diff (account + code) exceeds its budget, so the factory reverts. The caller's reservoir is preserved and used for a subsequent SSTORE.
test_parent_reverts_when_create_diff_exceeds_budget: Replaces test_nested_create_code_deposit_cannot_borrow_parent_gas. A wrapper CALLs a factory that does CREATE deploying 15000 bytes. The factory's diff cost (~17.7M) exceeds its gas budget (~16.4M), so the factory reverts. The wrapper verifies the CALL returned 0 (failure).
166/166 blockchain_test passing (excluding pre-existing fork transition failures).
Related