This proposal surcharges writes to long-unmutated state. It defines a CUTOFF_BLOCK. Any account or storage slot whose EIP-8188 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.
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.
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 and RFC 8174.
This proposal reads the last_written_block field defined in EIP-8188. EIP-8188 owns the encoding and the rules that update that field. This proposal only reads it to classify state.
| 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). Both MUST be greater than zero.
An account or storage slot is classified from its last_written_block:
if last_written_block < CUTOFF_BLOCK:
tier = INACTIVE
else:
tier = ACTIVE
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 and EIP-8037. Reads are unaffected.
The general rule is:
SSTORE recomputes storageRoot, charge INACTIVE_ACCOUNT_WRITE_SURCHARGE if that account is Inactive.INACTIVE_STORAGE_WRITE_SURCHARGE if that slot is Inactive.last_written_block as it stands at the start of the operation, before EIP-8188 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 rules. A contract created and destroyed in the same transaction has no leaf |
| EIP-7702 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's TX_MAX_GAS_LIMIT like any other regular-gas, and it counts toward the block's regular-gas under EIP-8037's two-dimensional accounting. Intrinsic surcharges are folded into intrinsic gas and evaluated at transaction validation.
The surcharge is consumed like ordinary regular-gas:
last_written_block update made by EIP-8188 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 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.
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. Otherwise the schedule would make reviving old state a cheaper way to consume the same node resources than allocating fresh state.
Active and Inactive are write tiers determined by last_written_block relative to CUTOFF_BLOCK. They affect write costs only. EIP-2929'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.
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:
CUTOFF_BLOCK is fixed, so the Inactive set as of that block does not change and the files can be written once and shared.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.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.
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.
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.
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.
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.
This is a backwards-incompatible gas repricing that requires a scheduled network upgrade. It depends on EIP-8188 and must activate at or after the same fork. CUTOFF_BLOCK must be chosen above EIP-8188'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.
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.
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.
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 and related rights waived via CC0.