On March 30, 2025, the DeFi protocol SIR.trading was hacked, resulting in the loss of $355,000 at the time of the writing. The attack was caused by a mistake when performing gas optimization using new EIP-1153 - Transient storage opcodes.
Overview
Attacker: https://etherscan.io/address/0x27defcfa6498f957918f407ed8a58eba2884768c
Vulnerable Contract: https://etherscan.io/address/0xb91ae2c8365fd45030aba84a4666c4db074e53e7#code
Transaction attack: https://etherscan.io/tx/0xa05f047ddfdad9126624c4496b5d4a59f961ee7c091e7b4e38cee86f1335736f
EIP-1153 - Transient storage opcodes adds 2 opcodes for manipulating state that behaves almost identically to storage but is discarded after every transaction:
TLOAD
pops one 32-byte word from the top of the stack, treats this value as the address, fetches 32-byte word from the transient storage at that address, and pushes the value on top of the stack.TSTORE
pops two 32-byte words from the top of the stack. The word on the top is the address, and the next is the value.TSTORE
saves the value at the given address in the transient storage.
Addressing is the same as SLOAD
and SSTORE
. i.e. each 32-byte address points to a unique 32-byte word.
Gas cost for TSTORE
is the same as a warm SSTORE
of a dirty slot (i.e. original value is not new value and is not current value, currently 100 gas), and gas cost of TLOAD
is the same as a hot SLOAD
(value has been read before, currently 100 gas). Gas cost cannot be on par with memory access due to transient storage’s interactions with reverts.
All values in transient storage are discarded at the end of the transaction so basically we can use them like a cheaper storage that only last for a transaction. One use-case is the Reentrancy Locks.
Exploit Analysis
In the mint function, the Vault contract of SIR.trading uses transient storage to to store UniswapV3pool address before performing swap.
The pool address stored in transient storage 0x1 is later uses in uniswapV3SwapCallback to verify that the msg.sender is Uniswapv3 pool and then transfer tokens to the pool to performing swap.
The implementation is correct up to this point, but then they need to return the amount to the mint function. They try to save gas costs again by using transient storage: storing the amount in transient storage 0x1 and loading it in the mint function.
This is where the pitfall occurs. The transient storage 0x1 is now overwritten with amount and the transient storage remains for the whole transaction so if in this transaction, we call uniswapV3SwapCallback function again, it will load the 0x1 which is now the amount instead of the pool address. Since the amount can be controlled by crafting arguments in uniswapV3SwapCallback function, attacker bruteforced an address 0x00000000001271551295307acc16ba1e7e0d4281 (amount = 95759995883742311247042417521410689).
The attacker deployed contract 0x00000000001271551295307acc16ba1e7e0d4281 with CREATE2, provided crafted arguments to mint exactly the number of tokens above, successfully manipulating the transient storage 0x1 to the amount (0x00000000001271551295307acc16ba1e7e0d4281). The contract then directly called uniswapV3SwapCallback, bypass the Uniswapv3 pool check and transferred tokens from the vault to the attacker’s wallet.
Conclusion
EIP-1153 - Transient storage opcodes introduces a great way to save gas cost but with the great power comes with great responsibility. 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.
In the SIR.trading case, they did not clear the 0x1 slot after using it to store the uniswapPool and amount, and they also reused the 0x1 for the amount, which led to the exploitation.
Smart contract developers need to carefully understand the new features, especially the breaking changes, before using them. Always remember to review the EIPs carefully, including all security considerations, when implementing them.
Additionally, it is strongly recommended to conduct a security audit, not only for the first release version but also for any new features added in the future. Since the upgrading process can introduce various issues, it should also be thoroughly audited.