Alert Source Discuss
⚠️ Draft Standards Track: Core

EIP-7971: Hard Limits for Transient Storage

Decrease costs for TLOAD, TSTORE and warm SLOAD with a transaction-global limit

Authors Charles Cooper (@charles-cooper), Ben Adams (@benaadams)
Created 2025-06-12
Discussion Link https://ethereum-magicians.org/t/add-eip-hard-limit-and-cost-reduction-for-transient-storage-allocation/24542
Requires EIP-1153

Abstract

This EIP proposes to reduce the gas costs for transient storage operations (TLOAD and TSTORE) and warm storage loads (SLOAD) by implementing constant pricing. To prevent denial-of-service attacks through excessive memory allocation, a transaction-global limit on transient storage slots is introduced. This approach provides lower costs for common use cases while maintaining security against resource exhaustion attacks.

Motivation

EIP-1153 introduced transient storage with gas costs equivalent to warm storage operations (100 gas). The current pricing model presents several limitations:

  1. Reentrancy Protection Cost: At 100 gas per operation, implementing reentrancy locks by default remains expensive enough to discourage universal adoption at the language level, leaving contracts vulnerable to one of the most common attack vectors.
  2. Underutilization: The high cost is still punishing to developers who wish to use transient storage for other legitimate use cases such as temporary approvals, callback metadata, and cross-frame communication within transactions.
  3. Pricing Inconsistency: Transient storage fundamentally requires fewer resources than persistent storage (no disk I/O, no state root updates), yet is priced identically to warm storage operations.

This EIP addresses these issues by implementing constant, lower pricing for transient storage operations while introducing a transaction-global limit to prevent denial-of-service attacks. It also makes the cost of warm storage cheaper. This provides lower costs and enables broader adoption of transient storage, while also providing hard resource limits for clients.

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.

Parameters

This EIP introduces the following parameters:

  • GAS_TLOAD = 5 (tent.)
  • GAS_TSTORE = 12 (tent.)
  • GAS_WARM_SLOAD = 5 (tent.)
  • MAX_TRANSIENT_SLOTS = 131072 (tent.)
  • GAS_TSTORE_ALLOCATE = 24 (tent.)

Gas Cost Changes

  1. The gas cost for TLOAD (opcode 0x5c) is reduced from 100 to GAS_TLOAD.
  2. The base gas cost for TSTORE (opcode 0x5d) is reduced from 100 to GAS_TSTORE.
  3. The gas cost for warm SLOAD (opcode 0x54) is reduced from 100 to GAS_WARM_SLOAD.

Transaction-Global Transient Storage Limit

A transaction-global counter tracks the number of unique transient storage slots written across all contracts during transaction execution:

  1. At the beginning of each transaction, initialize a counter transient_slots_used to 0.
  2. When TSTORE is executed:
    • If the slot has not been written to during this transaction (across any contract), increment transient_slots_used.
    • If transient_slots_used exceeds MAX_TRANSIENT_SLOTS, the transaction MUST exceptionally halt.
  3. The counter persists across all message calls in the transaction.
  4. The counter is reset to 0 at the end of the transaction.

Implementation Note

Implementations MUST track transient storage allocated across all contracts. A slot is considered unique based on the tuple (contract_address, storage_key). Writing to the same slot multiple times within a transaction does not generally increment the counter after the first write (unless the slot gets deallocated with a revert).

Rationale

Constant Pricing with Hard Limit

This EIP implements constant pricing with a hard limit for several reasons:

  1. A hard limit provides guarantees on the total resource consumption which are easier to reason about for clients, rather than needing to perform a calculation as a function of current gas limits to find out what total memory consumption could be.
  2. Common use cases (e.g., reentrancy locks using 1-2 slots) are not penalized for worst case resource usage (like in DOS attacks).
  3. Clients can safely pre-reserve all memory which could be used by transient storage up-front at the beginning of a transaction.

Gas Cost Selection

  • GAS_TLOAD (5 gas): Transient storage reads require only memory access without disk I/O.
  • GAS_WARM_SLOAD (5 gas): Warm storage loads from cache have similar performance characteristics to transient storage reads.
  • GAS_TSTORE (12 gas): Transient storage writes require memory allocation and journaling for revert support.
  • GAS_TSTORE_ALLOCATE (24 gas): Writes to fresh slots require memory allocation, which is more expensive than writing to an existing slot.

Hard Limit Selection

MAX_TRANSIENT_SLOTS of 131072 allows:

  • Sufficient slots for typical user applications
  • Memory usage bounded to approximately 8 MB per transaction (131072 slots * 64 bytes), which prevents OOM-based denial-of-service attacks

Design Alternatives Considered

  1. Per-Contract Limits: Increased complexity in reasoning about resource consumption based on the shape of the call stack.
  2. Superlinear Pricing: Adds complexity, and still punishes “common” (non-DOS) use cases.
  3. No Limit: May allow memory-based DOS attack if transaction-level gas limits change, or if pricing changes in the future.

The benefit of a hard limit is that the resource consumption is bounded in a predictable way even in the presence of other parameter changes in the protocol.

Backwards Compatibility

No known issues, besides the gas cost of existing operations being cheaper.

Test Cases

TBD

Reference Implementation

# Pseudo-code for transaction execution with global transient storage limit

GAS_TLOAD = 5
GAS_TSTORE = 12
GAS_TSTORE_ALLOCATE = 24
GAS_WARM_SLOAD = 5
MAX_TRANSIENT_SLOTS = 131072

class TransactionContext:
    def __init__(self):
        self.transient_storage = {}  # (address, key) -> value
        self.unique_slots = set()    # set of (address, key) tuples
        self.transient_slots_used = 0
    
    def tload(self, address: Address, key: Bytes32) -> Bytes32:
        # Charge gas
        self.charge_gas(GAS_TLOAD)
        
        # Return value or zero
        return self.transient_storage.get((address, key), Bytes32(0))

    def tstore(self, address: Address, key: Bytes32, value: Bytes32):
        # Charge gas
        self.charge_gas(GAS_TSTORE)

        # Check if this is a new unique slot
        slot_id = (address, key)
        if slot_id not in self.unique_slots:
            self.charge_gas(GAS_TSTORE_ALLOCATE)

            self.unique_slots.add(slot_id)

            # Check limit
            if len(self.unique_slots) > MAX_TRANSIENT_SLOTS:
                raise ExceptionalHalt("Transient storage limit exceeded")
        
        # Store value
        self.transient_storage[slot_id] = value
    
    def warm_sload(self, address: Address, key: Bytes32) -> Bytes32:
        # Charge reduced gas for warm storage
        self.charge_gas(GAS_WARM_SLOAD)

        # Load from persistent storage (implementation depends on client)
        return self.load_from_storage(address, key)

Security Considerations

With MAX_TRANSIENT_SLOTS = 131072, maximum memory allocation is bounded to 8MB per transaction (131072 * 64 bytes). Compared to limits under current pricing (100 gas), a 30M gas transaction can allocate up to 300,000 slots (19.2 MB). This EIP reduces the maximum allocated amount by 56%.

Copyright and related rights waived via CC0.

Citation

Please cite this document as:

Charles Cooper (@charles-cooper), Ben Adams (@benaadams), "EIP-7971: Hard Limits for Transient Storage [DRAFT]," Ethereum Improvement Proposals, no. 7971, June 2025. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7971.