Skip to content

Instantly share code, notes, and snippets.

@wangkui0508
Created September 10, 2024 09:26
Show Gist options
  • Select an option

  • Save wangkui0508/276ad3b41542869292f0855d0ef35830 to your computer and use it in GitHub Desktop.

Select an option

Save wangkui0508/276ad3b41542869292f0855d0ef35830 to your computer and use it in GitHub Desktop.

How to use transient storage

TL;DR You must manually make sure the storage is transient within the life cycle of an external function, stead of a transaction. Never let the EVM clear non-zero transient storages.

The Solidity's Blog discussed the composibility problem of transient storage very well. Let's see what it says:

So far the EVM largely guaranteed composable behaviour, since multiple calls into a smart contract within a complex transaction are virtually indistinguishable from multiple calls to the contract stretched over several transactions. However, transient storage allows a violation to this principle and incorrect use may lead to complex bugs that only surface when used across several calls.

EIP1153 break such "indistinguishable". These two cases are different:

  1. multiple calls into a smart contract within a complex transaction: the latter one can read the data which was left by the former one using TSTORE.
  2. multiple calls to the contract stretched over several transactions: the latter one can NOT read the data which was left by the former one using TSTORE.

Currently the clearing happens for all contracts at the same time, when the transaction ends

If you are not careful, EVM will clear non-zero transient storage at the end of each transaction. It is this behavior that breaks "indistinguishable".

If instead it was cleared for a contract as soon as no function belonging to it remained active on the call stack (which could mean multiple resets per transaction), the issue would disappear.

When a composable external function ends, it must manually clear all the transient storage it has set during its execution. Thus, it keeps the "indistinguishable" attribute. When the EVM clears transient storage at the end of the transaction, it just performs NOPs, i.e., clearing zero storage slots.

An example

Let's see an example from pancake-v4. The SettlementGuard library uses tload and tstore and the Vault utilizes this library.

The Vault's lock function is a composable external function. Its flow is as below:

  1. The caller calls lock with call back data.
  2. The Vault set the LOCKER_SLOT transient storage to acquire a lock.
  3. The Vault call back to the caller's lockAcquired function with data.
  4. The caller then calls Vault's other functions: accountAppBalanceDelta, take, mint, sync, settle, settleFor, clear, and burn. All these functions have isLocked modifier and can only work within the life cycle of the lock function. During this process, some transient storage slots are temporarily set and finally cleared.
  5. Before the lock function clears the LOCKER_SLOT slot, the Vault checks if all the transient storage slots are cleared. If not, it reverts.

Before the lock function starts, all the transient slots are zero. After it ends, all the transient slots are zero.

In the accountDelta function, every time a zero CURRENCY_DELTA slot turns non-zero, UNSETTLED_DELTAS_COUNT is increased by one. Every time a non-zero CURRENCY_DELTA slot turns zero, UNSETTLED_DELTAS_COUNT is decreased by one. So, if the UNSETTLED_DELTAS_COUNT slot is zero, then all the CURRENCY_DELTA slots are zeros. This means we only need to check the UNSETTLED_DELTAS_COUNT slot at the end of the lock function.

Now we have an interesting deduction: in SettlementGuard.sol, if we change all the tload(tstore) to sload(sstore), the lock function can also work correctly, with a little more gas.

The conclusion: the safe way to use transient storage is to use tload&tstore only for reducing gas. Never take advantage of its special semantics, i.e., get cleared automatically when the transaction ends. Clear all the non-zero transient storage at the end of a composable external function.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment