---
eip: 8296
title: Fixed-Cutoff State Tiering
description: Surcharges writes to state unmutated since a fixed cutoff block
author: Wei Han Ng (@weiihann)
discussions-to: https://ethereum-magicians.org/t/eip-8296-fixed-cutoff-state-tiering/28772
status: Draft
type: Standards Track
category: Core
created: 2026-06-10
requires: 2929, 7702, 7825, 8037, 8038, 8188
---

## Abstract

This proposal surcharges writes to long-unmutated state. It defines a `CUTOFF_BLOCK`. Any account or storage slot whose [EIP-8188](./eip-8188.md) `last_written_block` is below the cutoff is **Inactive** and costs more to write. Everything else is **Active** and priced as today. Inactive state stays in the trie, so no eviction or resurrection mechanism is needed.

## Motivation

State keeps growing, and most of it is cold: written once and never touched again. That cold state still sits in the working set every node maintains, and the cost of mutating it is the same flat price as mutating state that was written recently.

This proposal takes a single cutoff block, chosen by social coordination, that splits state into Active and Inactive once.

A single static boundary also pairs naturally with archiving. The Inactive set is fixed at activation, so it can be serialized once into immutable files. If clients agree on the same file format, those files become interchangeable: a node can drop the cold set from its hot database, hold it as files, and sync it from any peer or mirror without re-deriving it. This mirrors how Ethereum already distributes block history through era files.

## Specification

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119) and [RFC 8174](https://www.rfc-editor.org/rfc/rfc8174).

This proposal reads the `last_written_block` field defined in [EIP-8188](./eip-8188.md). [EIP-8188](./eip-8188.md) owns the encoding and the rules that update that field. This proposal only reads it to classify state.

### Parameters

| Name | Value | Description |
| --- | ---: | --- |
| `CUTOFF_BLOCK` | TBD | Block number below which state is Inactive |
| `INACTIVE_ACCOUNT_WRITE_SURCHARGE` | TBD | Additional gas charged when mutating an Inactive account |
| `INACTIVE_STORAGE_WRITE_SURCHARGE` | TBD | Additional gas charged when mutating an Inactive storage slot |

Each surcharge is added on top of the normal gas for the operation, not a replacement for it (see [Tiered Write Costs](#tiered-write-costs)). Both MUST be greater than zero.

### Tier Determination

An account or storage slot is classified from its `last_written_block`:

```python
if last_written_block < CUTOFF_BLOCK:
    tier = INACTIVE
else:
    tier = ACTIVE
```

### Tiered Write Costs

The surcharge is charged per Inactive leaf that an operation writes. It is in addition to the normal gas for the operation, which is defined by [EIP-8038](./eip-8038.md) and [EIP-8037](./eip-8037.md). Reads are unaffected.

The general rule is:

- For each existing account leaf an operation writes, including the account leaf bumped when an `SSTORE` recomputes `storageRoot`, charge `INACTIVE_ACCOUNT_WRITE_SURCHARGE` if that account is Inactive.
- For each existing storage slot whose value an operation changes, charge `INACTIVE_STORAGE_WRITE_SURCHARGE` if that slot is Inactive.
- Newly-created leaves, such as a new account, a new storage slot, or deployed code, are never surcharged. They are not write-Inactive, and their creation is priced by [EIP-8037](./eip-8037.md).
- The tier of each leaf MUST be determined from its `last_written_block` as it stands at the start of the operation, before [EIP-8188](./eip-8188.md) updates it. The write then sets `last_written_block` to the current block, so the leaf is Active for the rest of the block and is not surcharged again. Each leaf is therefore surcharged at most once per block.

The table below lists, for each state-changing operation, the leaves it can surcharge.

| Operation | Inactive leaves surcharged |
| --- | --- |
| `SSTORE`, existing slot changed | the slot (storage), and the containing account via the `storageRoot` cascade (account) |
| `SSTORE`, new slot (`0` to nonzero) | the containing account via the cascade (account) only. The new slot is creation and pays no storage surcharge |
| `SSTORE`, slot cleared (nonzero to `0`) | the containing account via the cascade (account) only |
| `CALL` / `CALLCODE` with value | the sender and the receiver (account), each if Inactive. A newly created receiver is exempt |
| Value-transfer transaction | the sender and the receiver (account), charged in intrinsic gas |
| nonce increment | the account whose nonce changes (account) |
| `CREATE` / `CREATE2` | the creator account (account). The new contract account, its code, and constructor-set slots are creation and exempt |
| `SELFDESTRUCT` | each Inactive side whose balance changes, per the [EIP-8188](./eip-8188.md) rules. A contract created and destroyed in the same transaction has no leaf |
| [EIP-7702](./eip-7702.md) authorization | the authority account (account), charged in intrinsic gas |
| reads (`SLOAD`, `BALANCE`, `EXT*`, value-less calls) | none |

The surcharge is **regular-gas**, not state-gas, because reviving Inactive state is I/O work rather than state growth. It is therefore subject to [EIP-7825](./eip-7825.md)'s `TX_MAX_GAS_LIMIT` like any other regular-gas, and it counts toward the block's regular-gas under [EIP-8037](./eip-8037.md)'s two-dimensional accounting. Intrinsic surcharges are folded into intrinsic gas and evaluated at transaction validation.

The surcharge is consumed like ordinary regular-gas:

- It is not refundable.
- It is consumed even if the call frame later reverts or halts exceptionally. This is consistent with [EIP-8037](./eip-8037.md), which refills state-gas on revert but not regular-gas.
- If the inactive write occurs inside a frame that later reverts, the `last_written_block` update made by [EIP-8188](./eip-8188.md) reverts with the rest of that frame's state. The surcharge stays consumed, but the leaf remains Inactive, so a later write to it is surcharged again.

### State Creation Is Not Tiered

State creation is never classified as Inactive and never pays the surcharge. The exemption applies to the created leaf only: a new account, a new storage slot, or deployed code. An operation that creates one leaf while writing another existing Inactive leaf still surcharges that existing leaf. For example, `SSTORE 0 -> x` in a dormant contract creates the slot, which pays no storage surcharge, but revives the existing account leaf through the `storageRoot` cascade, which pays the account surcharge. State creation is priced by [EIP-8037](./eip-8037.md).

The inactive surcharge SHOULD be calibrated so that mutating existing Inactive state is not cheaper, in effective block-resource terms, than creating comparable new state under [EIP-8037](./eip-8037.md). Otherwise the schedule would make reviving old state a cheaper way to consume the same node resources than allocating fresh state.

### Disambiguation from EIP-2929 warm/cold

Active and Inactive are write tiers determined by `last_written_block` relative to `CUTOFF_BLOCK`. They affect write costs only. [EIP-2929](./eip-2929.md)'s warm/cold distinction is a within-transaction first-touch concept that continues to apply to reads as before. For a write, first determine the tier, then apply the corresponding write cost.

### Shared File Format for the Inactive Set

This section is RECOMMENDED, not consensus.

Clients SHOULD converge on a single shared file format for the Inactive set as of `CUTOFF_BLOCK`. Such a format SHOULD be:

- **Immutable.** `CUTOFF_BLOCK` is fixed, so the Inactive set as of that block does not change and the files can be written once and shared.
- **Self-verifying against the state root.** The contents are a subset of the state at `CUTOFF_BLOCK`, so both membership (each item's stored `last_written_block`) and values are provable against that state root. A client MUST be able to import the files without trusting their producer.
- **Indexable.** A client SHOULD be able to look up an account or slot without scanning the whole archive.

## Rationale

### Why a single cutoff instead of rolling periods?

A companion proposal buckets write-age into rolling periods so that state ages into the Inactive tier continuously. That requires period parameters and an anchor, and every item's tier moves over time. A single cutoff removes all of that. There is one number to agree on, the classification is a single comparison, and the Inactive set is fixed at activation.

The cost is that newly cold state does not age in on its own. Once state written before `CUTOFF_BLOCK` is the Inactive set, state written after it stays Active forever under this EIP. Re-tightening the boundary is a later social decision: a future EIP advances `CUTOFF_BLOCK`. This is a deliberate trade of automatic coverage for simplicity.

### Why keep Inactive state in the state root?

Evicting Inactive state would let nodes delete it, but it would also pull in the hard parts of state expiry: witnesses to revive expired state and resurrection-conflict handling. Those are the problems that have stalled full state expiry. The proposal still shrinks each node's hot working set, because a client can move the cold set to files and out of the database it touches every block, while the data remains provable against the state root.

### Why surcharge writes?

The surcharge aligns the cost of dragging cold state back onto the hot path with the work it causes. A node that has moved the Inactive set to files has to load an item back before it can be written. Pricing that path above an ordinary write discourages gratuitous churn of long-dead state without forbidding it.

### Why a shared file format?

The value is interoperability. If every client writes the Inactive set the same way, the files are interchangeable and can be distributed over ordinary channels such as HTTP and BitTorrent, the same way era files distribute block history. A node can then obtain the cold set from any mirror instead of re-deriving it, which makes dropping it from the hot database practical. Leaving the byte-level encoding to a separate effort keeps this proposal focused on the consensus surcharge.

### Renewal gaming

Unlike a rolling-period scheme, there is no recurring renewal to game. The cutoff is fixed, so once an item is Active it stays Active with no further writes, and an honest user never pays the surcharge twice.

The residual concern is the opposite direction: an attacker could pay surcharges to drag the Inactive set back into the Active set in bulk, inflating the hot working set this proposal is meant to shrink. Two things bound this. First, it is not cheap. Reviving one item costs `INACTIVE_ACCOUNT_WRITE_SURCHARGE` or `INACTIVE_STORAGE_WRITE_SURCHARGE` on top of the normal write, so reviving the whole set costs that surcharge times its size, and the work is rate-limited by the block gas limit like any other state-touching activity. Second, the effect is not permanent. Advancing `CUTOFF_BLOCK` at a later fork re-classifies as Inactive any state that has since gone cold again, including state an attacker revived and then abandoned. Moving the cutoff forward periodically therefore keeps the long-run Active set small regardless of one-off revival bursts.

## Backwards Compatibility

This is a backwards-incompatible gas repricing that requires a scheduled network upgrade. It depends on [EIP-8188](./eip-8188.md) and must activate at or after the same fork. `CUTOFF_BLOCK` must be chosen above [EIP-8188](./eip-8188.md)'s activation block so that `last_written_block` is meaningful for the comparison.

Wallets and RPC providers must update `eth_estimateGas` to charge the surcharge when a write targets Inactive state.

## Test Cases

<!-- TODO -->

## Security Considerations

### Hot-set inflation between cutoff bumps

Because the cutoff is fixed, the Active set can only grow between forks that advance `CUTOFF_BLOCK`. Any Inactive item that is written becomes Active and stays Active indefinitely, whether it was revived by ordinary use or by an attacker paying the surcharge. Nothing returns idle state to Inactive on its own. This monotonic growth is the core weakness of a fixed cutoff relative to a rolling-period scheme, where state re-enters the Inactive tier automatically as it ages. Keeping the long-run Active set small therefore depends on advancing `CUTOFF_BLOCK` at later forks, and the interval between bumps bounds how far the Active set can drift from the cold set.

### Write-inactivity is not read-inactivity

The cutoff classifies state by write recency only. A storage slot can be read on every block yet never rewritten, leaving it Inactive. The classification must not be read as "cold" for access purposes. A client that moves Inactive state to slower storage or to files should account for read frequency separately, since placing frequently read Inactive state on a slow path could become a denial-of-service vector.

### Choosing the cutoff

`CUTOFF_BLOCK` is a governance choice. Setting it too close to the present makes a large fraction of recently used state Inactive and over-penalizes ordinary activity. Setting it too far in the past leaves little cold state behind the cutoff and blunts the effect. The value should be derived from the observed write-age distribution of state at the time of the fork.

## Copyright

Copyright and related rights waived via [CC0](../LICENSE.md).
