We reject abstraction
We believe in bytecode
Bytecode is only true form
Let’s code with bytes now
Last week, we dissected smart contract deployment and how to separate init code from runtime code.
This week, we're diving into a recent (Dencun update) and powerful EVM feature: Transient Storage.
We already know the three main data regions in the EVM: the stack which is super fast but only lasts for the current instruction, the memory that resets after each all and the persistent storage.
But there’s a gap:
What if we want to share temporary data between cross contract calls in a single transaction. Specifically in cases where data doesn’t need to persist after the transaction ends.
That’s where Transient Storage comes in.
Transient Storage is like ephemeral memory that survives across calls but gets wiped at the end of the transaction. It’s a key-value store like regular SSTORE
, but it’s cheaper and isolated to the current transaction.
Ethereum introduced two new opcodes (with EIP-1153 in Decun update) to interact with transient storage:
Opcode | Name | Description |
5C |
| Load a value from transient storage |
5D |
| Store a value into transient storage |
These behave exactly like SLOAD
and SSTORE
, except the data is not persistent and can be use across smart contract calls.
Imagine a contract where Contract A sets a flag, calls Contract B, B calls A again, and then A checks that flag. With transient storage, Contract A can do this without writing to permanent storage.
Here’s a simplified outline:
PUSH1 0x01 // Value to write
PUSH1 0x00 // Key to write
TSTORE // Store 1 into transient[0]
CALL // Call logic (Abstracted because have not discussed it here yet)
// Reentry point
PUSH1 0x00 // Key to read
TLOAD // Read from transient[0]
Unlike memory (which resets when there is an external call), this value remains accessible to all internal functions during the transaction.
TSTORE
and TLOAD
usageLet’s store and retrieve a value both in transient and persistent storage to compare:
// Transient Store
PUSH1 0x2A
PUSH1 0x00
TSTORE
// Transient Load
PUSH1 0x00
TLOAD
// Persistent Store
PUSH1 0x2A
PUSH1 0x01
SSTORE
// Persistent Load
PUSH1 0x01
SLOAD
As you can see, the call API is very similar but when the transaction ends, the persistent value at slot 0x01
remains on-chain while the transient value at slot 0x00
is gone.
Re-entrancy Guards: Instead of storing a boolean in storage (expensive), store a flag in transient storage.
Efficient Intermediate Values: Use transient slots to coordinate between multiple contract calls without touching global state nor calldata parameters.
Gas-Efficient Caching: Cache data temporarily across contract calls in the same transaction.
Region | Lifetime | Access Pattern | Gas Cost | Shared Across Calls? |
Stack | One operation | LIFO | Very low | No |
Memory | One transaction (until there is a call) | Byte offset | Low | No |
Storage | Forever | Key-value | High | Yes |
Transient | One transaction | Key-value | Low-medium | Yes |
Write and check using this EVM Disassembler bytecode that:
Stores 0xAA
in transient slot 0x00
Loads it from the same slot
Returns it
We’ve now seen how to manage state across contract calls, next Tuesday, we’ll look at how contracts organize their dispatch logic using functions. Subscribe for more bytes.