Home / Smart Contract Auditing / Critical Solidity Compiler Bug Triggers High-Severity Warning for Transient Storage Code Generation

Critical Solidity Compiler Bug Triggers High-Severity Warning for Transient Storage Code Generation

On February 11, 2026, blockchain security firm Hexens reported a high-severity bug within the Solidity code generator that impacts compiler versions 0.8.28 through 0.8.33 when utilizing the Intermediate Representation (IR) pipeline. The flaw causes the compiler to emit incorrect bytecode opcodes—erroneously swapping sstore for tstore, or vice versa—when a smart contract clears both a persistent storage variable and a transient storage variable of matching types. This unexpected behavior stems from a name collision in the Yul helper functions generated during compilation, where two distinct operations share a single name and one inadvertently overwrites the other.

While the bug carries a high-severity internal rating due to its potential to corrupt state variables or leave reentrancy locks permanently engaged, its real-world impact is heavily mitigated by specific preconditions. Only projects that explicitly opt into the —via-ir compilation flag are affected, as the flag remains disabled by default. Furthermore, comprehensive cross-chain scans conducted following the disclosure identified a mere three deployed contracts matching the vulnerable pattern across all Ethereum Virtual Machine (EVM)-compatible networks. The Solidity development team, alongside security partners, swiftly notified the respective development teams, and no funds or active public applications were compromised.

The Solidity core team addressed the vulnerability in version 0.8.34, introducing a fix that appends distinct data location markers to Yul helper function names. Developers utilizing any version between 0.8.28 and 0.8.33 with the IR pipeline enabled are strongly urged to update their compiler toolchains and review codebases that execute the delete operation on transient state variables.

Which Contracts Are Affected?

A smart contract is susceptible to this code generation flaw only when a specific set of technical conditions occurs simultaneously within the same compilation context. Crucially, if a codebase does not execute the delete keyword on any transient state variable, the contract remains entirely unaffected. Developers should note that explicitly assigning a zero value—such as writing _lock = 0 directly—bypasses the shared clearing helper mechanism altogether and will not trigger the bug.

The core of the collision mechanism relies on how Solidity matches value types during internal helper generation. The persistent variable involved does not need to be declared using the exact same primitive type as the transient variable. Instead, a collision can occur if a matching value type is nested within the persistent structure, such as an element inside an array or a member within a complex struct.

On the transient side of the operation, there is precisely one code path that generates the tstore variant: executing a delete statement on a transient state variable. Because transient arrays, mappings, and nested structures are not yet natively supported in Solidity, this operation invariably targets a fundamental value type. Conversely, the persistent side can trigger the sstore variant through any operation that clears persistent storage of a matching value type, including deleting persistent variables, clearing mappings, or resetting array elements.

A significant nuance arises when clearing arrays. Operations on arrays in Solidity are generally handled at a slot granularity, treating every storage slot as a uint256 rather than iterating through individual elements. Consequently, operations like deleting a boolean array, a static address array, or a multi-dimensional integer array all invoke the exact same internal clearing helper used by a standard uint256 variable. A contract featuring a persistent boolean array alongside a transient uint256 variable can experience a type collision, even though the declared developer-facing types appear entirely distinct. However, this slot-granularity expansion does not apply to .pop() operations, which utilize the precise element type, nor does it affect struct members smaller than 32 bytes, which employ direct sstore instructions that completely bypass shared helpers.

The architectural layout of a project can also contribute to the vulnerability. The two clearing operations do not need to reside within the exact same contract file. A base contract containing a transient variable combined with a derived contract utilizing a persistent variable or mapping of a matching type is sufficient to provoke the naming collision. Similarly, clearing routines embedded within free functions or internal libraries utilized across the project can introduce the overlapping Yul functions. Finally, both operations must coexist within the same Yul compilation object; a delete statement restricted exclusively to contract creation code, such as a constructor, will not interact with runtime clearing operations because the compiler treats them as independent code generation domains.

The Technical Mechanism of the Bug

The root cause of the bug lies within the IR pipeline’s deduplication of reusable Yul helper functions. During code generation, the compiler relies on an internal component known as MultiUseYulFunctionCollector, which indexes helper functions by their assigned string names. When the compiler requests a function via createFunction, it executes the generation logic on the first encounter of a given name and caches the resulting string block for all subsequent calls.

Under normal operations, this deduplication strategy requires that a function’s name serve as a complete, unambiguous key for its generated behavior. The helper responsible for clearing a storage slot—routinely invoked by the delete keyword—derived its name exclusively from the Solidity type being cleared, generating identifiers such as storage_set_to_zero_t_address for an address type. Crucially, the storage location category—whether persistent or transient—was omitted from the naming scheme. Both persistent and transient clearing operations for the same underlying value type produced identical function names.

As a result, whichever clearing operation the compiler happened to encounter first dictated the implementation details of that shared Yul function. If a persistent storage deletion was processed first, the generated helper relied on the sstore opcode. A subsequent transient deletion encountered later in the compilation tree would then reuse that exact same function, inadvertently emitting an sstore instruction to persistent storage instead of generating a new function utilizing tstore.

The order in which the compiler processes these statements depends heavily on function selector sorting and the internal structure of the Abstract Syntax Tree. Because this code generation phase occurs prior to optimization, running standard optimizer passes does not alter or mitigate the underlying name collision. The official fix implemented in Solidity 0.8.34 resolves the issue by incorporating the storage location directly into the function name generation logic. This creates distinct Yul functions—separating persistent clearing routines from transient ones—and ensures that each storage operation emits the correct underlying EVM opcode.

Real-World Examples and Behavior

To illustrate the practical consequences of this compiler defect, security researchers provided simplified contract models demonstrating the failure in both directions of the collision vector.

In the first scenario, where a persistent clearing operation is processed first, a contract might manage both persistent delegate mappings and a transient execution lock. When the compiler compiles a persistent delete delegates[id] statement before a transient delete _lock statement, it generates the shared helper using sstore. When the transient lock is subsequently cleared, the compiler reuses the persistent function, writing a zero to persistent storage slot zero instead of clearing transient memory. In practice, this results in the primary owner state variable being unexpectedly overwritten with zero during standard execution, while the transient lock remains active and fails to release within the transaction.

In the second scenario, where the transient delete is encountered first, a contract managing persistent approvals alongside a transient execution guard will experience the inverse failure. The transient deletion generates a helper utilizing tstore. When a user subsequently attempts to revoke a persistent approval via delete approvals[id], the compiler reuses the transient helper, emitting a tstore opcode instead of an sstore instruction. Consequently, the persistent approval remains entirely intact on-chain, failing to clear the user’s authorized spending limits despite the execution of the deletion code.

Industry Response and Recommended Actions

Following the initial disclosure by Hexens, a coordinated security response was launched involving prominent web3 security organizations, including SEAL 911, Etherscan, and Dedaub. Using comprehensive blockchain data provided by Etherscan, security analysts scanned all EVM-compatible networks to identify deployed contracts containing the vulnerable compilation pattern. The search revealed only three affected contracts in total. The respective development teams were promptly notified, and subsequent investigations confirmed that none of the impacted contracts were tied to high-value public applications with active external user funds at risk.

The Solidity core team has verified that no other internal helper functions are susceptible to this class of naming collision and has scheduled architectural refactoring to prevent similar compiler-level defects in future releases.

Development teams currently utilizing Solidity versions 0.8.28 through 0.8.33 with the IR pipeline enabled are advised to immediately upgrade their compiler toolchain to version 0.8.34 or later. Projects unable to update immediately should thoroughly audit their codebases to verify whether any smart contracts combine transient state variable deletions with persistent storage clearing operations of matching types. Comparing unoptimized Yul output files generated with the --ir flag between affected versions and version 0.8.34 provides a definitive method for confirming whether a project was impacted by the Yul function naming collision.

Tagged: