Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 47 additions & 13 deletions EIPS/eip-8037.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,15 +99,16 @@ Because [EIP-2780](./eip-2780.md) makes intrinsic gas state-independent, no stat

This means that the `state_gas_reservoir` holds gas that exceeds [EIP-7825](./eip-7825.md)'s budget. Additionally, three new counters are introduced:

- `evm_execution_gas_used` encodes the total execution-gas used by the transaction, and it is initialized as `evm_execution_gas_used = 0`.
- `evm_state_gas_used` encodes the total state-gas used by the transaction, and it is initialized as `evm_state_gas_used = 0`.
- `state_gas_from_gas_left` encodes how much of the current frame's `gas_left` has been spent on state-gas charges (i.e., the state-gas not covered by the reservoir). Like `gas_left`, it is frame-local: it is initialized to `0` at the start of each call frame and is used to refill state-gas in last-in, first-out (LIFO) order.
- `state_gas_from_gas_left` encodes how much of the current frame's `gas_left` has been drawn for state-gas charges and not yet credited back (i.e., the state-gas not covered by the reservoir). Like `gas_left`, it is frame-local: it is initialized to `0` at the start of each call frame and is used to refill state-gas in last-in, first-out (LIFO) order.
- `state_gas_baseline` records the value of `state_gas_reservoir` at the start of the state changes a frame's rollback undoes, which for a call frame is its entry. Like `gas_left`, it is frame-local.

The gas counters operate as follows:

- Execution-gas charges deduct from `gas_left` only and increment `evm_execution_gas_used`.
- Execution-gas charges deduct from `gas_left` only.
- State-gas charges deduct from `state_gas_reservoir` first. When the reservoir is exhausted, the remainder deducts from `gas_left` and increments `state_gas_from_gas_left` by that remainder. State-gas charges increment `evm_state_gas_used`.
- If a state creation is undone during execution, the corresponding amount of state-gas is **refilled in last-in, first-out (LIFO) order**: because charges deduct from `state_gas_reservoir` first and from `gas_left` last, refills credit the pool charged last (`gas_left`) first. It is credited to `gas_left` first (decrementing `state_gas_from_gas_left` by the same amount) up to the current value of `state_gas_from_gas_left`, and any remainder is credited to `state_gas_reservoir`. State-gas refills decrement `evm_state_gas_used`. Throughout this specification, "refilling state-gas" refers to this LIFO operation.
- `state_gas_reservoir` is passed to a child frame in full: the 63/64 rule applies to `gas_left` only. The parent keeps none of it while the child runs. On the child's exit it becomes the parent's again. Note: a refill in the child may have raised it above the amount passed in.
- The `GAS` opcode returns `gas_left` only (excluding the reservoir).
- State-gas is metered at the end of all state-mutating opcodes, call-frame boundaries (in case of exceptional halts and reverts) and at the end of the transaction.

Expand Down Expand Up @@ -148,7 +149,7 @@ State-gas accounting for `CALL*` and `CREATE`/`CREATE2` operations is performed

The code-deposit portion (`L × CPSB`) is unaffected by this rule and is charged at code-deposit time as usual.

The same conditional rule applies to top-level contract-creation transactions. Under [EIP-2780](./eip-2780.md) the `STATE_BYTES_PER_NEW_ACCOUNT × CPSB` account-creation charge is not part of intrinsic gas and is not reserved up front; it is applied as a runtime charge in the pre-execution phase, when transaction setup accesses the deployment address (an access [EIP-7928](./eip-7928.md) always records), only if the destination does not already exist. Like any other state charge, it draws from `state_gas_reservoir` first and from `gas_left` once the reservoir is exhausted. If the transaction reverts or halts (including an address collision), any charged portion is refilled by the rules below, and the unspent reservoir is returned to the sender by the normal end-of-transaction settlement.
The same conditional rule applies to top-level contract-creation transactions. Under [EIP-2780](./eip-2780.md) the `STATE_BYTES_PER_NEW_ACCOUNT × CPSB` account-creation charge is not part of intrinsic gas and is not reserved up front; it is applied as a runtime charge in the pre-execution phase, when transaction setup accesses the deployment address (an access [EIP-7928](./eip-7928.md) always records), only if the destination does not already exist. Like any other state charge, it draws from `state_gas_reservoir` first and from `gas_left` once the reservoir is exhausted. If the transaction reverts or halts (including an address collision), any charged portion is undone by the frame's rollback, and the unspent reservoir is returned to the sender by the normal end-of-transaction settlement.

Charges for account creation with `SELFDESTRUCT` are charged at the point where it executes.

Expand All @@ -160,23 +161,46 @@ For an account that existed before the transaction, `SELFDESTRUCT` only transfer

##### Gas accounting for halts and reverts

A call frame's state changes are rolled back on both reverts and exceptional halts, so in both cases the state-gas charged within the frame is refilled in LIFO order (see the reservoir model above). The two cases differ only in how the frame's `gas_left` is treated, exactly as in existing EVM semantics.
A call frame's state changes are rolled back on both reverts and exceptional halts. In both cases the frame's state-gas is restored to its baseline rather than refilled:

When a child frame **succeeds**, its remaining `gas_left` is returned to the parent and its `state_gas_from_gas_left` is added to the parent's `state_gas_from_gas_left` (the state-gas it funded from `gas_left` persists and is now backed by the merged `gas_left`).
```python
net_state_gas = state_gas_baseline - state_gas_reservoir + state_gas_from_gas_left
evm_state_gas_used -= net_state_gas
gas_left += state_gas_from_gas_left
state_gas_from_gas_left = 0
state_gas_reservoir = state_gas_baseline
```

`net_state_gas` is negative for a frame that refilled more than it charged, so `evm_state_gas_used` increases: the rollback also restores the state whose removal the refill credited. The restore assigns `state_gas_reservoir` where a refill credits it, so it is applied before any refill its parent makes for the failed operation. The baseline and frame entry coincide for every frame but the top-level one, whose rollback leaves the [EIP-7702](./eip-7702.md) authorizations applied in the pre-execution phase in place ([EIP-2780](./eip-2780.md)): its baseline sits after their charges and before the account-creation charge for the transaction's target, which the rollback does undo. The two cases differ only in how the frame's `gas_left` is treated, exactly as in existing EVM semantics.

When a child frame **succeeds**, its remaining `gas_left` is returned to the parent and its `state_gas_from_gas_left` is added to the parent's `state_gas_from_gas_left` (the state-gas it funded from `gas_left` persists and is now backed by the merged `gas_left`). Then, with the child's `state_gas_reservoir` merged in, the frame returns state-gas to `gas_left`, up to the amount it has outstanding:

```python
d = min(state_gas_reservoir, state_gas_from_gas_left)
gas_left += d
state_gas_reservoir -= d
state_gas_from_gas_left -= d
```

Note: this undoes no state creation, so `evm_state_gas_used` is unchanged. It only moves gas between the two pools.

This step is needed because a refill may happen in a different frame than the matching charge. The original value in the `SSTORE` table above is the value at transaction start, not at frame entry. A frame may therefore clear a slot allocated by an earlier frame. Its `state_gas_from_gas_left` may be smaller than the refill, and the rest goes to `state_gas_reservoir`, although the charge drew from `gas_left`. The step moves it up on every merge, to the first frame with an outstanding `state_gas_from_gas_left`. If it reaches the top-level frame, it stays in `state_gas_reservoir` and is returned to the sender by the end-of-transaction settlement.

When a child frame **reverts**, all of its state changes are rolled back and the state-gas charged within the frame is refilled: credited to the child's `gas_left` up to `state_gas_from_gas_left`, with any remainder credited to `state_gas_reservoir`. The child's resulting `gas_left` (including the refilled portion) is then returned to the parent. `evm_state_gas_used` is decreased by the refilled amount, while `evm_execution_gas_used` is updated according to the execution gas consumed by the child frame before the revert.
The step applies to successful children only. This requires that a frame that reverts or halts exceptionally returns no state-gas to its parent; the restore to the frame's baseline above provides that. Otherwise the parent receives state-gas for a slot the rollback restored, and a later merge moves it to `gas_left`.

When a child frame **halts exceptionally**, the same refill is applied, but the child's `gas_left` is consumed (set to zero) rather than returned. Because the portion refilled to `gas_left` is part of the consumed `gas_left`, only the portion refilled to `state_gas_reservoir` survives — which is exactly the reservoir's value at the start of the child frame. No separate reservoir-reset rule is therefore required: refilling in LIFO order makes the reservoir whole automatically, while the `gas_left` the parent forwarded to the child is consumed in full as usual.
When a child frame **reverts**, all of its state changes are rolled back, its state-gas is restored as above, and its resulting `gas_left` (including the credited `state_gas_from_gas_left`) is returned to the parent.

The same rules apply when the top-level call frame reverts or halts, treating the frame as a child of the transaction boundary. In particular, for address collisions in contract-creation transactions, `gas_left` is consumed in full and `evm_execution_gas_used` is incremented by the initial `gas_left`; any account-creation state-gas charged for the destination (possible only for a storage-only collision, since a destination with nonce, code, or balance is existent) is refilled by the rules above, and the `state_gas_reservoir` is returned to the sender by the normal end-of-transaction settlement.
When a child frame **halts exceptionally**, the same restore is applied, but the child's `gas_left` is consumed (set to zero) rather than returned, so the credited `state_gas_from_gas_left` is consumed with it.

The same rules apply when the top-level call frame reverts or halts, treating the frame as a child of the transaction boundary. In particular, for address collisions in contract-creation transactions, `gas_left` is consumed in full; any account-creation state-gas charged for the destination (possible only for a storage-only collision, since a destination with nonce, code, or balance is existent) is undone by the frame's rollback, and the `state_gas_reservoir` is returned to the sender by the normal end-of-transaction settlement.

##### Gas accounting for [EIP-7702](./eip-7702.md) authorizations

Gas charges for [EIP-7702](./eip-7702.md) authorizations follow the specification in [EIP-2780](./eip-2780.md).

#### Pre-state and post-state gas validation

[EIP-7928](./eip-7928.md) defines two-phase gas validation for state-accessing opcodes: pre-state costs (determinable without state access) and post-state costs (requiring state access). Under this EIP, the execution-gas portion of these opcodes follows the [EIP-7928](./eip-7928.md) rules unchanged and is charged at opcode time against `gas_left`. The state-gas portion is also charged at the opcode level; it is computed and deducted at the end of the opcode execution, drawing first from `state_gas_reservoir` and then from `gas_left`.
[EIP-7928](./eip-7928.md) defines two-phase gas validation for state-accessing opcodes: pre-state costs (determinable without state access) and post-state costs (requiring state access). Under this EIP, the execution-gas portion of these opcodes follows the [EIP-7928](./eip-7928.md) rules unchanged and is charged at opcode time against `gas_left`. The state-gas portion is also charged at the opcode level, with the timing given for each opcode above.

For `SSTORE`, the `GAS_CALL_STIPEND` pre-state check ([EIP-7928](./eip-7928.md)) applies to `gas_left` only, excluding the `state_gas_reservoir`.

Expand Down Expand Up @@ -209,8 +233,6 @@ Since all state-gas charges are now applied as runtime or execution-time charges

The [EIP-7623](./eip-7623.md) calldata floor participates in block accounting through the `max` term, applied to the execution-gas dimension: calldata is an execution-gas resource, and the floor's bound on worst-case block size holds only if a data-heavy transaction contributes at least the floor to the block's gas accounting. The floor is compared against the transaction's execution-gas portion alone — not the transaction total — so execution gas may absorb the floor exactly as under [EIP-7623](./eip-7623.md), while state-gas spending cannot (see [Calldata floor in block accounting](#calldata-floor-in-block-accounting)).

Note: `tx_gas_used` applies the [EIP-7623](./eip-7623.md) calldata floor after refunds, so the floor can effectively negate part of a refund when `calldata_floor_gas_cost > tx_gas_used_after_refund`. The receipt `cumulative_gas_used` uses this post-floor value.

The block header `gas_used` field is set to:

```python
Expand Down Expand Up @@ -374,7 +396,7 @@ To solve this issue, we only apply this gas limit to execution gas, not state ga

However, we cannot statically enforce an execution gas consumption of `TX_MAX_GAS_LIMIT`, while still allowing a higher state gas consumption, because transactions only have a single gas limit parameter, `tx.gas`. This is solved through a **reservoir model**: at transaction start, `evm_gas` is split into `gas_left` (capped at `TX_MAX_GAS_LIMIT - intrinsic_gas`) and a `state_gas_reservoir` (the overflow). State gas charges draw from the reservoir first, then from `gas_left` when the reservoir is empty. Execution gas charges draw from `gas_left` only. Exceeding the execution gas budget behaves identically to running out of gas — no special error is needed.

State-gas refills are applied in last-in, first-out (LIFO) order: because charges draw from the reservoir first and from `gas_left` last, refills credit `gas_left` first (up to the gas it borrowed, tracked by the frame-local `state_gas_from_gas_left` counter), then the reservoir. Undoing a state creation therefore restores the exact pools the charge drew from, so the two pools never drift into one another — `gas_left` is never inflated beyond `TX_MAX_GAS_LIMIT - intrinsic_gas`, and execution gas is never permanently stranded in the state-only reservoir. This LIFO refill also subsumes the exceptional-halt case: refilling into a child's `gas_left` and then consuming that `gas_left` leaves the reservoir at exactly its start-of-frame value, so no dedicated reservoir-reset rule is needed (see [Gas accounting for halts and reverts](#gas-accounting-for-halts-and-reverts)).
State-gas refills are applied in last-in, first-out (LIFO) order: because charges draw from the reservoir first and from `gas_left` last, refills credit `gas_left` first (up to the gas it borrowed, tracked by the frame-local `state_gas_from_gas_left` counter), then the reservoir. Undoing a state creation therefore restores the exact pools the charge drew from, as long as both happen in the same frame. Because the original value in the `SSTORE` table above is the value at transaction start rather than at frame entry, a refill may land in a frame other than the one that was charged; the counter is frame-local, so the gas then stays in the reservoir while the `gas_left` that funded the charge stays reduced. Returning it to `gas_left` on the merge keeps the two pools from drifting into one another: `gas_left` is never inflated beyond `TX_MAX_GAS_LIMIT - intrinsic_gas`, and state-gas is not left in the reservoir while a frame is still owed it. The same mismatch is why LIFO order alone does not make a frame's rollback correct: a frame may credit a refill it never funded, so rollback is specified as a restore to the frame's baseline (see [Gas accounting for halts and reverts](#gas-accounting-for-halts-and-reverts)).

#### Higher throughput

Expand Down Expand Up @@ -422,6 +444,8 @@ Wallet developers and node operators MUST update gas estimation handling to acco

Users can maintain their usual workflows without modification, as wallet and RPC updates will handle these changes.

Contracts and tools measuring work as the difference between two `GAS` reads can get a negative result. This EIP makes it possible both within a frame and across a call (see [`gas_left` can increase](#gas_left-can-increase)). Contracts already deployed with such metering cannot be fixed.

### Deterministic deployment factories

Some well-known deterministic deployment factories are deployed via pre-signed transactions with a hardcoded gas limit. The classic Nick's method factory, for instance, uses a pre-signed transaction with a 100,000 gas limit. Under this EIP, the state creation cost of deploying these factories increases beyond their hardcoded gas limit, so the original pre-signed deployment transactions will no longer execute successfully. The same applies to other deterministic deployer contracts such as `SingletonFactory`, `Create2Deployer`, `ImmutableCreate2Factory`, and `CreateX`.
Expand Down Expand Up @@ -454,6 +478,16 @@ One potential concern is the cost of creating a new account (`STATE_BYTES_PER_NE

Optimal block construction becomes more complex, since builders must now balance resource usage across multiple dimensions rather than a single gas metric. Sophisticated builders may gain an advantage by applying advanced optimization techniques, raising concerns about further builder centralization. However, practical heuristics (e.g., greedily filling blocks until one resource dimension saturates) remain effective for most use cases. These heuristics limit centralization pressure by keeping block construction feasible for local and less sophisticated builders.

### `gas_left` can increase

Refills credit `gas_left` before `state_gas_reservoir`, so a frame's `gas_left` can be higher after an operation than before it. A contract measuring work as the difference between two `GAS` reads then gets a negative value. This reverts under checked arithmetic and wraps under unchecked arithmetic.

A negative result requires the charge to have drawn from `gas_left`, i.e. the reservoir to be empty. That is the case for every transaction with `tx.gas` up to `TX_MAX_GAS_LIMIT`.

This is already possible in a single frame. Allocating a storage slot that was zero at transaction start and clearing it again refills `STATE_BYTES_PER_STORAGE_SET × CPSB` to `gas_left`. `SSTORE(k, 1); GAS; SSTORE(k, 0); GAS` shows it: the two reads differ by the refill less the cost of the clear. Reads around both stores do not, because the charge and the refill cancel between them.

Returning state-gas on a merge extends this across a call boundary, which no call does today. Metering a sub-call this way is the more common pattern. The amount is not limited to one slot: a merge returns up to the whole `state_gas_from_gas_left` the frame has accumulated. The bound is that `gas_left + state_gas_from_gas_left` never exceeds the frame's `gas_left` at entry.

### Interaction with [ERC-4337](./eip-4337.md) bundles

The `GAS` opcode returns `gas_left` only and cannot observe `state_gas_reservoir`. Bundling protocols that meter sub-call consumption via `gasleft()` deltas (e.g., the [ERC-4337](./eip-4337.md) `EntryPoint`) therefore cannot accurately attribute state-gas usage when it is funded by the reservoir. Because all user operations in a bundle share the transaction's reservoir, one user operation's state-gas refunds can subsidize another's state-gas charges without appearing in either's `gasleft()` delta. Bundlers and `EntryPoint` implementations MUST account for state-gas charges and refunds explicitly rather than relying on `gasleft()` differences alone.
Expand Down
Loading
Loading