Transient Storage: Revolutionizing Data Handling

25 min read

What is Transient Storage (EIP-1153)?

Transient Storage introduces another data location besides memory, storage, and calldata (return-data and code). The new data location behaves as a key-value store similar to storage with the main difference being that data in transient storage is not permanent. Thus, the transient storage is a temporary storage mechanism intended for Ethereum-based blockchain networks.

Unlike permanent blockchain storage options, it acts as a temporary data repository and is meant to be less expensive. It is also designed to be immediately cleared at the end of a transaction. This method of storing data improves efficiency, reduces costs, and addresses security issues that impact data privacy, integrity, and the overall reliability of blockchain applications.

Transient Storage, which provides a compromise between memory and storage, offers a transient area for information that doesn't require long-term preservation, in contrast to typical storage systems that permanently preserve data on the blockchain. This invention greatly lowers the price of data storage and retrieval while also optimizing the use of blockchain resources.

Moreover, developers can use Transient Storage to build global singletons on-chain for purposes like KYC validation or reentrancy locks without having to pay the cost of ordinary storage. These singletons facilitate information sharing between several components without requiring direct interaction by making transaction information accessible to any contract, regardless of stack depth.

As can be seen in the diagram below, a lending dApp doesn't have to store KYC data directly because a specific DEX can handle KYC validation and exchange this information with the dApp. Many design patterns, like fee-locked ERC20 tokens, KYC, allowlists, are made simpler by the fact that the KYC data is retained transiently after initial validation and is automatically cleared at the conclusion of the transaction. With this strategy, the loan dАpp does not need to be aware of KYC enforcement and that no additional payloads need to be carried around in internal calls.

Beyond the varied applications of transient storage, from technical implementations to more contentious uses, it's contended that employing global singletons within Ethereum transactions is ill-advised, constituting an anti-pattern that could significantly constrain future progress. In the realm of development, global singletons are almost universally acknowledged as an anti-pattern across numerous programming languages. A primary issue associated with singletons is their tendency to disrupt encapsulation. Breaking encapsulation has serious consequences for regular programs, but it is even more dangerous in a permissionless execution environment, where anyone can deploy code and programs regularly interact without trusting each other.

Why do we need EIP-1153?

EIP-1153 presents a framework that greatly improves smart contract execution and design in order to satisfy the requirement for an effective, transient storage solution within Ethereum transactions. In order to address a number of problems with the present storage and refund procedures, this proposal, which was proposed on June 15, 2018, by Alexey Akhunov and Moody Salem, would provide transitional storage that would be available through two new opcodes, TLOAD and TSTORE (TSTORE is not subject to the gas stipend check as defined in EIP-2200), where the "T" stands for "transient." This innovation offers a more affordable and straightforward option to conventional storage methods by enabling storage to be used just for the period of the transaction.

TLOAD (0x5c)

TSTORE (0x5d)

The shortcomings of the current storage system, such as the cap on refunds to one-fifth of transaction gas used since the London fork, the lack of storage refunds on transaction reverts, and the intricacy of the storage refund logic, make EIP-1153 necessary. Due to the difficulties in effectively managing storage slots, these issues not only raise the cost of gas for operations but also make it more difficult to carry out concurrent transactions that interact with the same contract. By avoiding the problems caused by reentrancy locks and the expensive usage of SSTORE for transmitting state information between contract states, it enables safe and economical inter-state communication.

Users should note that the compiler does not yet allow using transient as a data location in high-level Solidity code. Currently, data stored in this location can only be accessed using the TSTORE and TLOAD opcodes in inline assembly.

In conclusion for this section, EIP-1153 offers a strong remedy for the drawbacks and complications of Ethereum's present storage system, promising much reduced gas consumption, simpler smart contract implementations, and an overall simplification of the EVM architecture.

Use Cases

EIP-1153 introduces transient storage, made possible by the addition of two new opcodes, TLOAD and TSTORE, representing a significant improvement in Ethereum's design. Through this advancement, Ethereum's efficiency will be maximized by allowing smart contracts to take advantage of transient storage slots, cleared after every transaction. This technique not only strengthens security and simplifies contract design but also lessens the dependency on persistent storage, which lowers transaction fees by avoiding related expenses and eliminating the need for gas refunds.

Transient Storage opens up new possibilities for smart contracts, including:

  • Reentrancy locks:

Before delving into that how Transient Storage safely manage reentrancy without the high costs associated with traditional storage, it is essential to learn what is reentrancy.

Reentrancy is when a contract has been entered, and hands over the execution flow control to another contract that is entering the first contract again. The dangers of reentrancy stem from data races where the reentered contract’s execution frames are competing for the same storage slots so that inconsistent reads and writes are possible. Typically, this is prevented with reentrancy locks.

Alternatively, contracts could limit reentrancy possibilities by limiting the gas sent with an untrusted call. For example, contracts that only intend to transfer out ETH, commonly use Solidity’s transfer() or vyper’s send() to prevent reentrancy attacks by transferring ETH with only 2300 gas. Currently, that non-reentrancy assumption is enforced by EIP-2200, included in the Constantinople hard fork as a consequence of the “Reentrancy on Constantinople” issue. EIP-2200 causes an SSTORE with less than 2300 gas to fail (even though sufficient gas might be available). Hence, no SSTORE is possible as a result of a transfer() or send() . Therefore, no reentrancy (by its common definition) was possible up until now.

The introduction of transient storage, however, changes the dynamics of reentrancy attacks. Since TSTORE does not have a minimum gas requirement like SSTORE, it could lead to reentrancy scenarios even with low gas, breaking the previously held assumption that low-gas transfers are secure.

One of the use cases that has been proposed for EIP-1153 is using it for Reentrancy locks. However, the use of transient storage for reentrancy locks in smart contract wallets could inadvertently lead to denial-of-service (DOS) risks.

Constructing a reentrancy lock will, in 90% of circumstances, succeed, but it will make this contract less composable (since it can only be called once per transaction), and it will expose all smart contract wallets that communicate with it to a denial of service attack.

The second AA operation will find itself reverting (because of the reentrancy lock) if two are combined into a single transaction and both interact with this contract. This issue will result in the AA user needing to pay for the reverted transaction because, in most AA setups, the smart contract wallet is required to pay transaction fees even for reverted transactions.

The worst part is that the wallet owner has no control over whether a bundler does the action in combination with other activities in the majority of AA schemas, including EIP-4337. This implies that anyone can intentionally attempt to combine these processes, and the wallet owners will have to pay each time one of them fails.

Though less well-liked protocols might choose to take less precautions, developers may choose to take note of this edge situation and remove their reentrancy locks. Until smart contract wallets gain traction, the issue is likely to remain dormant (until the dapp gets popular). It is too late to fix it in both situations.

  • On-chain computable CREATE2 addresses:

Constructor arguments are read from the factory contract instead of passed as part of init code hash.

Uniswap V3 uses this (on-chain computable addresses) to store the constructor parameters of a pool so that the pool init code is always the same, rather than passing the constructor arguments as part of the init code. This makes it cheaper to compute the pool address on-chain.

To do this, Uniswap V3 uses the following steps:

  1. The pool parameters are stored in transient storage
  2. The pool init code is generated from the pool parameters.
  3. The pool is created using the pool init code.
  4. The pool parameters are deleted from transient storage.
  5. The pool init code is a piece of code that is executed when the pool is created. It is used to initialize the pool state.

This is designed to enable developers of ERC20 tokens to apply and approve schemes that are limited to the duration of a single transaction. This implies that the user won't have to worry about this approval "leaking" and being exploited by an attacker in the future when they approve another contract to extract ERC20s from their address.

Since one user controls the entire transaction by definition, this approach should work well for EOA accounts. This creates a false sense of security, though, because an attacker can still launch an attack from within the transaction itself. They can do this by utilizing an operation (if the user is using a smart contract wallet) or a callback, which functions similarly to a reentrancy lock.

The worst smart contract risks are those that remain undetected. In this instance, the contract will typically be shielded by this lone line of defense, although as previously mentioned, it is not very strong.

Without the need for transient storage, this pattern can still be used today. The current implementation would cost a little bit more, but it may be reduced by streamlining the storage refund structure and clearing the approval after the transaction.

  • Fee-on-transfer contracts:

There are many ERC20 tokens (smart contracts). Some tokens will take fees (e.g. STA, PAXG, USDT, USDC) when any transfer of the token is performed. This would enable a user to engage with the contract (on a single transaction) as frequently as desired after paying the tax just once. If a token want to impose a tax but is agnostic about how many "hops" it should take to complete a transaction, this design can be helpful.

How this schema breaks when smart contract wallets interact with them should be immediately apparent. The token contract automatically assumes that all subsequent callers must be tied to the initial user who pays the tax, even though some transfers may have been started by other users.

The only way to use the token is to filter by msg.sender, however this negates the benefit of transient storage because going forward, any transferFrom operations won't identify User 1 as their owner.

If User 1 doesn't call the gated token to release the storage, the token contract has no other means of determining if the user has changed or not. User 1 may make one last call to the token to let it know he's finished, but the token cannot guarantee it.

Since the token's revenue loss is the sole issue, this case doesn't seem to be too problematic. However, keep in mind that this schema is actually imposing restrictions on who can access the gated token. It is believed that developers attempting to implement these patterns will fall victim to similar vulnerabilities if the EIP examples themselves get it wrong and leave it vulnerable to attack.

When executing a L2 message on L1, transient storage is used to represent the L2 state. This implies that the Arbitrum contract can make any arbitrary call and need not presume any interface of the L1 contract. Additionally, the L1 contract can access the L2 state from the call.

The Transient Storage also simplifies complex transaction patterns, such as the "till" pattern and proxy call metadata:

  • “Till” Pattern:

With this pattern callback functions can be implemented more safely and cheaply. This can be done by implementing path independent variables at the start and end of the callback function, and ensuring path independence through transient storage. (the authors of EIP-1153 called this the “Till” pattern)

Similar to the CREATE2 and temporaryApprove patterns, the vulnerabilities described above do not directly impact this pattern. That being said, this is not a generalization.

It should be mentioned that normal storage can still be used to achieve the “till” pattern. The "till" pattern is used by the Uniswap v3 contracts, which are currently being produced.

While it is true that transient storage might lower this pattern's cost, regular storage can also directly benefit from similar pricing reductions.

  • Proxy call metadata:

Pass metadata to an implementation contract without using calldata. This describes using transient storage as a generic carrier for any sort of metadata (like the KYC example from the “What is Transient Storage (EIP-1153)” section). This pattern has the exact same problem as the "fee-on-transfer" pattern: the data leaks across operations, and the contracts involved have no reliable way of controlling for it.

In conclusion for this section, the efficiency of transient storage stems from its design, which does away with the requirement to load the initial value from storage (because it is always considered to be 0), hence streamlining gas accounting regulations by doing away with the requirement for reimbursements. This performance is especially noticeable in use cases such as Arbitrum's encoding of L2 state for L2-to-L1 messages and Uniswap V3. These uses case demonstrate how transient storage can save running expenses and increase the adaptability of Ethereum smart contract development.

Key Security Considerations, Benefits, and Drawbacks of EIP-1153

TSTORE introduces an innovative method for allocating memory on a node at a linear cost. Specifically, TSTORE enables developers to allocate 32 bytes of memory for every 100 gas used, not including any additional operations needed to set up the stack. With a budget of 30 million gas, the maximum memory that can be allocated through TSTORE is approximately 9.15MB, calculated as follows:

In comparison, using the same gas budget, the maximum memory allocation possible with a single MSTORE operation is about 3.75MB, derived from the equation:

However, by allocating 1M gas for memory in each context and resetting the memory expansion cost through calls, it's possible to allocate approximately 700KB per million gas, totaling around 20MB of memory with 30M gas:

Calculations Source

An important note, as written in EIP-1153 is that:

“Smart contract developers should understand the lifetime of transient storage variables before use. Because transient storage is automatically cleared at the end of the transaction, smart contract developers may be tempted to avoid clearing slots as part of a call in order to save gas. However, this could prevent further interactions with the contract in the same transaction (e.g. in the case of re-entrancy locks) or cause other bugs, so smart contract developers should be careful to only leave transient storage slots with nonzero values when those slots are intended to be used by future calls within the same transaction. Otherwise, these opcodes behave exactly the same as SSTORE and SLOAD, so all the usual security considerations apply especially in regard to reentrancy risk.”



“Smart contract developers may also be tempted to use transient storage as an alternative to in-memory mappings. They should be aware that transient storage is not discarded when a call returns or reverts, as is memory, and should prefer memory for these use cases so as not to create unexpected behavior on reentrancy in the same transaction. The necessarily high cost of transient storage over memory should already discourage this usage pattern. Most usages of in-memory mappings can be better implemented with key-sorted lists of entries, and in-memory mappings are rarely required in smart contracts (i.e. the author knows of no known use cases in production).”

Now let's look at some pros and cons of the EIP-11553:

Benefits:

  • Takes care of transient usages without modifying current contracts.
  • Upgrades to protocols take new ideas into independent consideration.
  • Gas accounting regulations are made simpler for clients by not requiring them to load the original value.
  • There is no need to clear storage slots after usage.

Drawbacks:

  • Does not take into account transient usage of storage of existing contracts.
  • New concept for the yellow paper.

Latest News about Transient Storage

Only about a year ago, the transient storage (EIP1153) was implemented in 3/5 EL clients with pending PRs on 2/5. Back then developers had completed all the possible work to support this EIP. (according to Hayden Adams)

The “big move” for Transient Storage was that the latest version of the Solidity compiler (0.8.24) includes support support for Transient Storage (EIP-1153).

In the Solidity 0.8.24 Release Announcement writes the following:

“The Cancun network upgrade provides new features in the form of opcodes and precompiles, which will need to be explicitly used to have any benefits, but also introduces changes in the existing EVM behavior that will automatically affect already deployed contracts. Solidity 0.8.24 adds full Yul-level support for the new opcodes and also Solidity-level builtins for some of them.”

“The hard fork will include the following execution layer changes: • EIP-1153: Transient storage opcodes"
“Transient storage is a long-awaited feature on the EVM level that introduces another data location besides memory, storage, calldata (and return-data and code). The new data location behaves as a key-value store similar to storage with the main difference being that data in transient storage is not permanent, but is scoped to the current transaction only, and is reset to zero when the transaction ends. Consequently, transient storage is as cheap as warm storage access, with both reads and writes priced at 100 gas.”

Conclusion and Outlook

Transient storage facilitates the safe and effective implementation of dApp features. TLOAD/TSTORE is expected to generate a range of use cases following the “Dencun" update, such as reentrancy guards, on-chain computable CREATE2 addresses, single transaction ERC-20 approvales and fee-on-transfer contracts.

However, the operation of transient storage will be new to existing Solidity developers, and unforeseen security issues may arise. As with the standardization of proxy contracts through various security incidents (such as the Parity Wallet Hack), the use of transient storage opcodes should also be actively discussed in terms of standards.

Uniswap V4 is the most representative example of a dApp utilizing transient storage, and it is hoped that a lot of discussion will take place on what security considerations should be made there.

Credits: Hackmd, EIP-1153, Solidity 0.8.24 Release