EIP1682 - Storage Rent

# Abstract

This EIP describes a scheme to charge for data in state, and 'archive' data which is no longer being paid for. It also describes how resurrection of 'archived' data happens.

# Motivation

The Ethereum blockchain in its current form is not sustainable because it grows indefinitely. This is true of any blockchain, but Ethereum grows faster than most chains. Many implementation strategies to slow down growth exist. A common strategy is 'state pruning' which discards historical state, keeping only the active copy of contract data and a few recent versions to deal with short-range chain reorganizations. Several implementations also employ compression techniques to keep the active copy of the state as small as possible.

A full node participating in consensus today requires storing large amounts of data even with advanced storage optimizations applied. Future storage requirements are unbounded because any data stored in a contract must be retained forever as dictated by the protocol. This EIP attempts to correct this by adding new consensus rules that put an upper bound on the size of the Ethereum state.

Adding these new rules changes fundamental guarantees of the system and requires a hard fork. Users of Ethereum already pay for the creation and modification of accounts and their storage entries. Under the rules introduced in this EIP, users must also pay to keep accounts accessible. A similar rent scheme was proposed in EIP-103 (opens new window) but rejected even back then because the proposal would've upset peoples expectations. As implementers of Ethereum, we still feel that state rent is the right path to long-term sustainability of the Ethereum blockchain and that its undesirable implications can be overcome with off-protocol tooling and careful design.

# Specification

The cost of storing an account over time is called rent. The amount of rent due depends on the size of the account. The ether that is paid for rent is destroyed. The rent is deducted whenever an account is touched.

rent can be paid from the account's regular balance or from its 'rent balance'. Accounts can be endowed with rent balance through a new EVM opcode. When rent is charged, it is first taken from the rent balance. When rent balance is zero, it is instead charged from the account's regular balance instead.

The reason to separate balance and rent balance is that certain contracts do not accept ether sends, or always send the entire balance off to some other destination. For these cases, a separaterent balance is required.

When an account's balance is insufficient to pay rent, the account becomes inactive. Its storage and contract code are removed. Inactive accounts cannot be interacted with, i.e. it behaves as if it has no contract code.

Inactive accounts can be restored by re-uploading their storage. To restore an inactive account A, a new account B is created with arbitrary code and its storage modified with SSTORE operations until it matches the storage root of A. Account B can restore A through the RESTORETO opcode. This means the cost of restoring an account is equivalent to recreating it via successive SSTORE operations.

# Changes To State

At the top level, a new key size is added to the accounts trie. This key tracks the total number of trie nodes across all accounts, including storage trie nodes. To track rent, the structure of account entries is changed as well.

Before processing the block in which this EIP becomes active, clients iterate the whole state once to count the number of trie nodes and to change the representation of all accounts to the new format.

# Account Representation

account = [nonce, balance, storageroot, codehash, rentbalance, rentblock, storagesize]
1

Each account gets three additional properties: rentbalance, rentblock and storagesize.

The rentbalace field tracks the amount of rent balance available to the account. Upon self-destruction any remaining rent balance is transferred to the beneficiary. Any modification of the account recomputes its current rent balance.

The rentblock field tracks the block number in which the rent balance was last recomputed. Upon creation, this field is initialized with the current block number. rentblock is also updated with the current block number whenever the account is modified.

The storagesize field tracks the amount of storage related to the account. It is a number containing the number of storage slots currently set. The storagesize of an inactive account is zero.

# Charging Rent

There is a new protocol constant MAX_STORAGE_SIZE that specifies the upper bound on the number of state tree nodes:

MAX_STORAGE_SIZE = 2**32 # ~160GB of state
1

A 'storage fee factor' for each block is derived from this constant such that fees increase as the limit is approached.

def storagefee_factor(block):
    ramp = MAX_STORAGE_SIZE / (MAX_STORAGE_SIZE - total_storage_size(block))
    return 2**22 * ramp
1
2
3

When a block is processed, rent is deducted from all accounts modified by transactions in the block after the transactions have been processed. The amount due for each account is based on the account's storage size.

def rent(prestate, poststate, addr, currentblock):
    fee = 0
    for b in range(prestate[addr].rentblock+1, currentblock-1):
        fee += storagefee_factor(b) * prestate[addr].storagesize
    return fee + storagefee_factor(currentblock) * poststate[addr].storagesize

def charge_rent(prestate, poststate, addr, currentblock):
    fee = rent(prestate, poststate, addr, currentblock)
    if fee <= poststate[addr].rentbalance:
        poststate[addr].rentbalance -= fee
    else:
        fee -= poststate[addr].rentbalance
        poststate[addr].rentbalance = 0
        poststate[addr].balance -= min(poststate[addr].balance, fee)
    poststate[addr].rentblock = currentblock
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# New EVM Opcodes

# PAYRENT <amount> <addr>

At any time, the rent balance of an account may be topped up by the PAYRENT opcode. PAYRENT deducts the given amount of ether from the account executing the opcode and adds it to the rent balance of the address specified as beneficiary.

Any participant can pay the rent for any other participant.

Gas cost: TBD

# RENTBALANCE <addr>

The rent balance of an account may be queried through the RENTBALANCE opcode. It pushes the rentbalance field of the given address to the stack.

Gas cost: like EXTCODEHASH.

# SSIZE <addr>

This opcode pushes the storagesize field of the given account to the stack.

Gas cost: like EXTCODEHASH.

# RESTORETO <addr> <codeaddr>

This opcode restores the inactive account at the given address. This is a bit like SELFDESTRUCT but has more specific semantics.

The account at addr must be inactive (i.e. have storagesize zero) and its storageroot must match the storageroot of the contract executing RESTORETO. The codeaddr specifies the address of a contract from which code is taken. The code of the codeaddr account must match the codehash of addr.

If all these preconditions are met, RESTORETO transfers the storage of the account executing the opcode to addr and resets its storagesize to the full size of the storage. The code of addr is restored as well. RESTORETO also transfers any remaining balance and rent balance to addr. The contract executing RESTORETO is deleted.

Gas cost: TBD

# Rationale

# Why do we need a separate rent balance?

Accounts need a separate rent balance because some contracts are non-payable, i.e. they reject regular value transfers. Such contracts might not be able to keep themselves alive, but users of those contracts can keep them alive by paying rent for them.

Having the additional balance also makes things easier for contracts that hold balance on behalf of a user. Consider the canonical crowdfunding example, a contract which changes behavior once a certain balance is reached and which tracks individual user's balances. Deducting rent from the main balance of the contract would mess up the contract's accounting, leaving it unable to pay back users accurately if the threshold balance isn't reached.

# Why restoration?

One of the fundamental guarantees provided by Ethereum is that changes to contract storage can only be made by the contract itself and storage will persist forever. If you hold a token balance in a contract, you'll have those tokens forever. By adding restoration, we can maintain this guarantee to a certain extent.

# Implementation Impact

The proposed changes tries to fit within the existing state transition model. Note that there is no mechanism for deactivating accounts the moment they can't pay rent. Users must touch accounts to ensure their storage is removed because we'd need to track all accounts and their rent requirements in an auxlilary data structure otherwise.

# Backwards Compatibility

TBA

# Test Cases

TBA

# Implementation

TBA

Copyright and related rights waived via CC0 (opens new window).

▲ Powered by Vercel