This EIP proposes to reduce the gas costs for transient storage operations (TLOAD and TSTORE) 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.
EIP-1153 introduced transient storage with gas costs equivalent to warm storage operations (100 gas). The current pricing model presents several limitations:
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.
This EIP introduces the following parameters:
| Constant | Value | Description |
|---|---|---|
GAS_TLOAD |
5 | Gas cost of TLOAD |
GAS_TSTORE |
12 | Gas cost of TSTORE |
MAX_TRANSIENT_SLOTS |
131072 | The maximum number of transient slots allowed in a single transaction |
GAS_TSTORE_ALLOCATE |
24 | Cost of additional allocated slots |
TLOAD (opcode 0x5c) is reduced from GAS_WARM_ACCESS (100) to GAS_TLOAD.TSTORE (opcode 0x5d) is reduced from GAS_WARM_ACCESS (100) to GAS_TSTORE.A transaction-global counter tracks the number of unique transient storage slots written across all contracts during transaction execution:
transient_slots_used to 0.TSTORE is executed:transient_slots_used.transient_slots_used exceeds MAX_TRANSIENT_SLOTS, the transaction MUST exceptionally halt.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).
This EIP implements constant pricing with a hard limit for several reasons:
GAS_TLOAD (5 gas): Transient storage reads require only memory access without disk I/O.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.TSTORE currently has a fixed cost. However, writing to fresh slots requires memory allocation, which is more expensive than writing to an existing slot. Therefore, we may consider introducing charging more for the first slot allocation through the parameter GAS_TSTORE_ALLOCATE. However, we would also need to introduce a mechanism to check for the first slot allocation versus subsequent allocations.
This proposal does not yet have finalized numbers. To achieve this, we require benchmarks on the transient memory operations, which are currently in development. Once we collect that data, we will set the final numbers. We will also use this to understand whether the difference in performance justifies pricing new slot allocations differently.
We should note that warm storage loads from cache are expected to have similar performance characteristics to transient storage reads. Therefore, the final parameters should be consistent with EIP-8038.
<– TODO –>
MAX_TRANSIENT_SLOTS of 131072 allows:
The benefit of a hard limit is that the resource consumption is bounded predictably even in the presence of other parameter changes in the protocol.
No known issues, besides the gas cost of existing operations being cheaper.
TBD
This reference implementation includes the mechanism to price TSTORE depending on whether the slot was previously allocated.
# Pseudo-code for transaction execution with global transient storage limit
GAS_TLOAD = 5
GAS_TSTORE = 12
GAS_TSTORE_ALLOCATE = 24
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
With MAX_TRANSIENT_SLOTS = 131072, maximum memory allocation is bounded to 8 MB per transaction (131072 * 64 bytes). Compared to limits under current pricing (100 gas), a 60M gas transaction can allocate up to 600,000 slots (38.4 MB). This EIP reduces the maximum allocated amount by 79%.
Copyright and related rights waived via CC0.