EIP-8032 - Tree-Depth-Based Storage Gas Pricing

Created 2025-09-29
Status Draft
Category Core
Type Standards Track
Authors

Abstract

This EIP introduces a mechanism to dynamically price SSTORE operations based on the storage size of a contract. A new optional depth field is added to the account RLP, which tracks an approximation of the storage trie's depth. The gas cost for SSTORE will be augmented by a factor that grows exponentially with this depth field, but only after it crosses a predefined activation threshold. This change aims to align the cost of state growth with the long-term burden it places on the network, thereby disincentivizing state bloat.

Motivation

Ethereum's state size is a growing concern, as it directly impacts node synchronization times, hardware requirements, and overall network health. The current gas model for storage operations does not fully account for the long-term cost of maintaining that state indefinitely. This has led to a situation where it is economically viable to create contracts with vast amounts of storage ("state bloat"), which can be used for low-cost data anchoring or spam, imposing a negative externality on all network participants who must maintain this data.

This proposal aims to address the state growth problem by creating a direct economic link between the size of a contract's storage and the cost to expand it further. By using the trie depth as a proxy for the total storage size, we can introduce a progressive gas fee for SSTORE. This ensures that small-to-medium-sized contracts are unaffected, while contracts that contribute disproportionately to state growth face increasing costs for storage writes. This creates a market-based incentive for developers to be more efficient with their use of state and helps mitigate unsustainable growth patterns.

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 and RFC 8174.

Constants and parameters

Name Value Description
FORK_TIMESTAMP TBD Fork activation timestamp
EXP_FACTOR TBD Exponent gas cost factor
LIN_FACTOR TBD Linear gas cost factor
ACTIVATION_THRESHOLD TBD Activation threshold, chosen to be at ~8GB of data.

Account tree and update rules change

Account RLP descriptors receive an optional depth field, corresponding to the maximum depth written to since FORK_TIMESTAMP.

After block execution and while computing the state root for the account, take note of the maximum depth written to.

While updating the account's root hash, also update its depth fields according to the following rules:

```python= newroot, max_depth = compute_root_and_max_depth() if account.depth < max_depth: account.depth += 1

account.root = new_root

where `compute_root_and_max_depth` is a function that writes all storage changes to the tree, and return the maximum depth that a storage slot has been written to during that process.

### Gas cost changes

At the beginning of a block, when an account is loaded, its `depth` field is available.

The gas cost of an `SSTORE` is computed as such:

```python=
constant_sstore_gas(addr, slot) + LIN_FACTOR * (1 << (4 * EXP_FACTOR * account.depth)) // ACTIVATION_THRESHOLD

Impact on state size

This adds 1 byte per account, as the maximum depth of the MPT is 64, which is lower than 128 and therefore fits in a single RLP byte.

There are currently about 23 million accounts in the state, so we are looking at a current state growth of about 23 MB. This is a high bound, and since our research has shown that most of the accounts are currently inactive, we expect no negative impact as long as the number of new accounts itself isn't a problem.

Rationale

The intent is to create friction in when growing the state size of a contract, thus limiting the number of such contracts. Going over the limit, some contract developers might want to use another contract to start fresh, which comes at the cost of paying for contract creation, and for any call into the previous instance of the contract.

The depth of the tree is chosen over the leaf count, because the storage tree is balanced, owing to the fact that its keys are hashed. Therefore, checking the maximum depth of a write is a stochastic process that on average, will return the size of the tree.

The increase by one at each block is meant to mitigate the effects of an unbalanced tree in case of an "unlucky" contract having a very deep branch while being mostly empty: writes then become more expensive over time. On the other hand, spam accounts that see frequent writes, will see the price increase kick in after just a few blocks.

The depth is chosen as a proxy for the total leaf count, as there is a logarithmic relationship between the two in a balanced tree.

ACTIVATION_THRESHOLD is chosen to not penalize the contracts that are large, but do provide useful value. The idea is to disincentivize spam contracts who grow larger than useful contracts, that are legitimately big due to the fact they bring value to their users and the wider ecosystem. This means that this constant could be increased as the state grows.

Backwards Compatibility

No backward compatibility issues found. Making the depth field optional ensures that the default depth is 0, which is fine since it will be updated on the first write, and not updating it marks that contract as a state expiry target.

This is a backwards incompatible gas repricing that requires a scheduled network upgrade.

Node operators MUST update gas estimation handling to accommodate the new calldata cost rules. Specifically, RPC methods such as eth_estimateGas MUST incorporate the updated formula for gas calculation when encountering an SSTORE.

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

Test Cases

Name Description
Activation check Create a tree with an account that has no depth value and a storage slot at depth > ACTIVATION_TARGET. Write to that slot, and verify that depth is then set to 1. Depending on the value of ACTIVATION_TARGET, this might require a deep collision, so reducing that value for the test is doable.
Threshold check Inverse of the previous test: same with a shallow tree, ensure that the value doesn't increase.
Monotonous increase Run the same test as "Activation Check" but across several blocks in order to confirm that it grows by one each time.

Reference Implementation

TODO

Security Considerations

Needs discussion.

A remark was made that, were many contracts updated to include a depth field in a single block, this would represent writes to about 3k accounts. This could put stress on client databases. Since this change only applies to large contracts, the risk of this happening is already mitigated by the cost of filling the storage of such contracts.

Copyright

Copyright and related rights waived via CC0.