Transient Storage Clearing Helper Collision
Overview
When the IR pipeline generated code to zero a storage value on delete, it named the helper storage_set_to_zero_t_<type> using only the value type, without recording whether the underlying slot was persistent or transient. The compiler then deduplicated helpers by name, so a single generated routine could serve both storage classes. The result is a bidirectional collision: delete transient_x could emit an sstore and corrupt persistent storage, while delete persistent_x could emit a tstore and silently leave persistent state untouched. The bug affects Solidity 0.8.28 through 0.8.33 when compiled with the IR pipeline, was reported by Hexens, and was fixed in 0.8.34. It sits in the adoption window for EIP-1153 transient storage.
A helper collision can only matter if the contract actually uses both storage classes. Sigvex reports this from recovered compiler metadata plus a bytecode precondition: the contract must use persistent storage (sstore or sload) and transient storage (tstore or tload). A contract that touches only one class cannot trigger the collision and is not flagged.
When metadata confirms the IR pipeline was used, the finding is reported at high confidence, because the dual-class bytecode signal is strong corroborating evidence. When the pipeline status is unknown, it is reported at reduced confidence to flag the affected version.
Why This Is an Issue
The two failure directions are both serious. A delete on a transient variable that emits sstore writes a zero into the wrong persistent slot, corrupting state the contract expects to keep across the transaction. A delete on a persistent variable that emits tstore clears only transient memory, so the persistent value the source intended to reset survives unchanged — a stale balance, lock, or flag that the contract believes is cleared. Because the misrouting happens during code generation, source review does not reveal it. Either direction can corrupt accounting or defeat a guard, which is why this is treated as high severity.
How to Resolve
Recompile with a fixed compiler and redeploy.
// Affected: IR-pipeline build on 0.8.28 - 0.8.33
pragma solidity 0.8.30;
contract Mixed {
uint256 transient tloadGuard;
uint256 persistentValue;
function clear() external {
delete tloadGuard; // may emit sstore and corrupt persistent storage
delete persistentValue; // may emit tstore and leave the value unchanged
}
}
// Fixed: compile with 0.8.34 or later
pragma solidity 0.8.34;
If an immediate upgrade is not feasible, audit every delete on a transient variable and every delete on a persistent variable of the same type, and replace them with explicit tstore(slot, 0) or sstore(slot, 0) in inline assembly to bypass the shared helper.
Detection Methodology
The detector checks the recovered compiler version against the affected range and the IR pipeline flag in metadata, suppressing the finding when the IR pipeline is known to have been off. It scans the recovered instruction stream for both storage classes: a persistent read or write, and a transient read or write. Both must be present, since the collision requires a contract that mixes the two classes. The dual-class observation is also fed back into the metadata decision, so the bytecode signal strengthens the version match rather than acting only as a gate. Confidence is high when the IR pipeline is confirmed and is reduced when the pipeline status is unknown or the metadata source is unreliable.
Sample Sigvex Output
{
"detector_id": "solc-2026-02-transient-helper-collision",
"severity": "high",
"confidence": 0.9,
"description": "solc-2026-02-transient-helper-collision: Contract was compiled with Solidity 0.8.30, which is in the affected range [0.8.28, 0.8.34). Runtime corroboration: contract mixes persistent storage (SSTORE/SLOAD) AND transient storage (TSTORE/TLOAD).",
"location": {
"function": "clear",
"offset": 256
}
}
Limitations
False positives are possible: using both storage classes does not prove a colliding zeroing helper was generated, and a contract in range may never delete a value of a type that is shared across classes. Confidence drops when the IR pipeline status is unknown or metadata is unreliable. False negatives occur when compiler metadata cannot be recovered, or when the contract uses only one storage class so the dual-class precondition is not met.
Related Detectors
- Outdated Compiler — general detection of compiler versions with known bugs.
- Storage Write Removal — an earlier compiler bug that silently drops storage writes.
- Conditional Storage Collision — storage-slot collisions at the contract level.