
{"id":154743,"date":"2026-04-23T07:37:03","date_gmt":"2026-04-23T07:37:03","guid":{"rendered":"https:\/\/mycryptomania.com\/?p=154743"},"modified":"2026-04-23T07:37:03","modified_gmt":"2026-04-23T07:37:03","slug":"solidity-first-high-severity-compiler-bug-in-10-years","status":"publish","type":"post","link":"https:\/\/mycryptomania.com\/?p=154743","title":{"rendered":"Solidity First High-Severity Compiler Bug in 10 Years"},"content":{"rendered":"<p><em>A compiler caching bug silently swapped opcodes in production for 18 months. Tests passed. Audits passed. Formal verification passed. Nobody\u00a0noticed.<\/em><\/p>\n<p><em>~8 min\u00a0read<\/em><\/p>\n<p>There\u2019s a class of software bug that sits below every tool designed to find bugs. It lives in the compiler, the program that translates your source code into the instructions your machine actually executes. When the compiler has a bug, your source code can be perfect, your tests can pass, your code review can be thorough, and your deployed binary can still be\u00a0wrong.<\/p>\n<p>This isn\u2019t hypothetical. In February 2026, a team called Hexens, a blockchain security firm that specializes in compiler-level research, found exactly this kind of bug in the Solidity compiler. Solidity is the dominant programming language for Ethereum smart contracts, programs that manage billions of dollars in financial transactions on a public blockchain. The bug, which Hexens named TSTORE Poison, caused the compiler to silently swap storage instructions in the compiled output. When a developer wrote code to clear a temporary variable, the compiler could instead clear a permanent one, wiping out contract ownership, access control flags, or token approval records. When a developer wrote code to clear a permanent variable, the compiler could instead perform a no-op, leaving token approvals active indefinitely.<\/p>\n<p>The bug had been shipping in production compiler releases for eighteen months across six versions. It produced wrong bytecode silently. No warnings. No errors. No test failures. No audit findings.<\/p>\n<p>The Solidity team patched it within a week. The full technical analysis is on the<a href=\"https:\/\/hexens.io\/research\/solidity-compiler-bug-tstore-poison\"> Hexens research page<\/a>, and the Solidity team\u2019s official acknowledgment is<a href=\"https:\/\/soliditylang.org\/blog\/2026\/02\/18\/transient-storage-clearing-helper-collision-bug\/\"> here<\/a>. Hexens discovered the first high-severity Solidity compiler bug since\u00a02016.<\/p>\n<p>I\u2019m writing this for developers who don\u2019t work in blockchain. The bug is interesting on its own, but the underlying pattern (a memoization cache that doesn\u2019t capture all dimensions of variation) is universal. If you\u2019ve ever written a cache key, this story is relevant to\u00a0you.<\/p>\n<h3>The Setup: Memorization in a Code Generator<\/h3>\n<p>Most compilers have an intermediate representation (IR) step. Source code gets translated into an IR, optimizations happen on the IR, and then the IR gets lowered to machine code (or in Solidity\u2019s case, EVM bytecode).<\/p>\n<p>During the source-to-IR step, the Solidity compiler generates small helper functions for repetitive operations: zeroing a storage slot, encoding a value, copying an array. To avoid generating duplicate code, these helpers are cached by name. If the compiler needs a helper called storage_set_to_zero_t_address, it checks the cache. If it exists, return the cached version. If not, generate it, store it, return the\u00a0name.<\/p>\n<p>If you\u2019ve ever written something like this, you\u2019ve used the same\u00a0pattern:<\/p>\n<p>python<\/p>\n<p>_cache = {}<\/p>\n<p>def get_or_create(name, generator):<\/p>\n<p>    if name not in _cache:<\/p>\n<p>        _cache[name] = generator()<\/p>\n<p>    return _cache[name]<\/p>\n<p>It\u2019s correct as long as name uniquely identifies the output of generator(). Two calls with different expected outputs must produce different names. If they don\u2019t, you get a cache collision: the first call\u2019s output gets reused for the second call, silently.<\/p>\n<p>This is the pattern that\u00a0broke.<\/p>\n<h3>What Happened in\u00a0Solidity<\/h3>\n<p>In 2024, Ethereum added a new storage domain called \u201ctransient storage.\u201d Think of it as temporary storage that gets wiped at the end of every transaction, useful for flags, locks, and intermediate calculations that don\u2019t need to persist. The Solidity compiler added native support for it in version 0.8.28: developers could declare transient variables and use standard operations like delete on\u00a0them.<\/p>\n<p>Under the hood, persistent storage and transient storage use different CPU instructions (or \u201copcodes\u201d in Ethereum terminology): sstore writes to persistent storage, tstore writes to transient storage. When the compiler generates a helper function that zeroes a storage slot, it needs to emit the right opcode for the right storage\u00a0domain.<\/p>\n<p>The function that generates this helper accepted two parameters: the variable\u2019s type and the storage location (persistent or transient). It used both parameters to generate the correct code. But the cache key, the function name, only included the\u00a0type:<\/p>\n<p>name = &#8220;storage_set_to_zero_&#8221; + type<\/p>\n<p>\/\/ location is NOT in the name<\/p>\n<p>\/\/ but location IS used in the generated code<\/p>\n<p>So storage_set_to_zero_t_address could mean \u201czero a persistent address slot using sstore\u201d or \u201czero a transient address slot using tstore,\u201d depending on which one was generated first. The second call would always get the first call\u2019s\u00a0version.<\/p>\n<p>A different function in the same codebase, doing essentially the same job for a different operation, already included the storage location in its cache\u00a0key:<\/p>\n<p>name = (&#8220;transient_&#8221; if location == transient else &#8220;&#8221;) + <\/p>\n<p>       &#8220;update_storage_value_&#8221; + type<\/p>\n<p>The correct pattern was right there. It just wasn\u2019t applied everywhere.<\/p>\n<h3>Why Tests Didn\u2019t Catch\u00a0It<\/h3>\n<p>This is the part that\u2019s relevant to every developer, regardless of whether you\u2019ve ever touched blockchain code.<\/p>\n<p><strong>Unit tests<\/strong> verify behavior at the source level. You write a test that calls a function and checks the output. But the bug isn\u2019t in the function\u2019s logic. The source code is correct. The bug is in the compiler\u2019s translation of that source code into executable instructions. Your test runs the compiled bytecode and checks the result, but the result might look plausible even with the wrong opcode. A storage slot getting zeroed when it shouldn\u2019t might not cause an immediate failure. It might cause a subtle state corruption that manifests transactions later, or only under specific conditions.<\/p>\n<p><strong>Integration tests<\/strong> have the same blind spot. They exercise the compiled code, not the compilation process. If the compiler silently swaps one storage instruction for another, the test framework has no mechanism to detect\u00a0it.<\/p>\n<p><strong>Code review<\/strong> operates on source code. No reviewer, no matter how experienced, can catch a bug that only exists in the compiled output. The Solidity source looks correct, because it is correct. The bug is in the transformation.<\/p>\n<p><strong>Static analysis tools<\/strong> parse source code or analyze compiled bytecode against known patterns. This bug doesn\u2019t match any known vulnerability pattern. The bytecode is structurally valid. The only difference is a single opcode value at one point in execution: 0x55 (sstore) versus 0x5d (tstore). Same slot, same value, wrong storage\u00a0domain.<\/p>\n<p><strong>Formal verification<\/strong> (tools like Certora and Halmos in the blockchain world) models the program\u2019s behavior at the source level and mathematically proves properties about it. But it assumes the compiler correctly translates source-level operations. The formal model says \u201cdelete on a transient variable produces a transient store of zero.\u201d If the compiler emits a persistent store instead, the model doesn\u2019t know. The compiler is outside the verification boundary.<\/p>\n<p>This is a fundamental limitation, not a tool-specific one. Formal verification proves properties of a <em>model<\/em>. If the model assumes compiler correctness and the compiler is incorrect, the proof is valid for the model and invalid for the executable. This is true for Solidity, and it\u2019s true for C programs verified against CompCert, and it\u2019s true for any system where the verification happens above the compilation boundary.<\/p>\n<h3>The Cache Key Problem Is Universal<\/h3>\n<p>Strip away the blockchain context, and the bug is a cache key that doesn\u2019t capture all dimensions of variation. This pattern shows up everywhere:<\/p>\n<p><strong>Web applications:<\/strong> A template rendering cache keyed by template name but not by locale. English gets cached first, every subsequent language renders in\u00a0English.<\/p>\n<p><strong>Build systems:<\/strong> A compilation cache keyed by source file hash but not by compiler flags. A debug build gets cached, release builds get debug\u00a0symbols.<\/p>\n<p><strong>API responses:<\/strong> A response cache keyed by endpoint but not by user role. An admin response gets cached, regular users see admin\u00a0data.<\/p>\n<p><strong>ORM query caches:<\/strong> A query cache keyed by the query string but not by the database connection. A query against the read replica gets cached, writes go to the read\u00a0replica.<\/p>\n<p>The Solidity case is more consequential (wrong opcodes in financial infrastructure versus wrong locale in a web app) but the structural pattern is identical. Every memoization system has the same invariant: the cache key must encode every parameter that affects the cached value. When a new parameter is added to an existing system (like \u201cstorage location\u201d was added to the Solidity code generator), every cache key that depends on the new dimension must be updated. If one is missed, you get a silent collision.<\/p>\n<p>The insidious part is that these bugs don\u2019t fail loudly. The cache returns <em>a<\/em> value. It\u2019s just not the <em>right<\/em> value. The system continues operating with plausible-looking but incorrect behavior. In a web app, someone eventually notices the wrong language. In a compiler for financial infrastructure, the wrong opcode might sit in production for 18\u00a0months.<\/p>\n<h3>The Blast Radius of a Compiler\u00a0Bug<\/h3>\n<p>One thing that makes this case study particularly interesting from a software engineering perspective is the blast\u00a0radius.<\/p>\n<p>When you find a bug in an application, you know the scope: it\u2019s that application. When you find a bug in a library, the scope expands to every application that imports it. When you find a bug in a compiler, the scope is every program compiled with the affected version, and the developers of those programs have no way to know they\u2019re affected without recompiling and diffing the\u00a0output.<\/p>\n<p>Hexens used Glider by Hexens, their smart contract analysis engine, to scan over 20 million deployed smart contracts across Ethereum and other compatible blockchains. Of roughly 500,000 compiled with affected compiler versions, four were identified as potentially vulnerable. All were notified privately before any public disclosure.<\/p>\n<p>The scanning problem is unique to blockchain: every deployed smart contract is public and immutable on-chain, which means it\u2019s possible (though computationally expensive) to check every single one. In traditional software, a compiler bug at this severity level would be a CVE and a hope that package managers propagate the fix. In blockchain, you can actually enumerate every affected deployment, if you have the tooling to do\u00a0it.<\/p>\n<h3>What the Solidity Team Did\u00a0Right<\/h3>\n<p>I want to highlight the response because it\u2019s a model for how compiler bugs in critical infrastructure should be\u00a0handled.<\/p>\n<p>Hexens reported the bug on February 11, 2026. The Solidity team confirmed it. A coordinated disclosure process involved a SEAL 911 warroom, a cross-team emergency response group in the Ethereum ecosystem, that brought together the Solidity team, Hexens, Dedaub (another security firm with contract search infrastructure), and other researchers. Every identified affected project was privately notified with specific remediation steps. The patch shipped on February 18, seven days later, as solc\u00a0v0.8.34.<\/p>\n<p>No funds were lost. No public exploit. No drama. Just professionals coordinating a response to an infrastructure-level vulnerability.<\/p>\n<h3>The Takeaway<\/h3>\n<p>If you write caching code (and most of us do, whether it\u2019s template caching, query caching, build caching, or memoization) this bug is a useful reminder.<\/p>\n<p>Every time you add a new parameter to a system that uses memoized results, ask yourself: does the cache key need to change? Is there a codepath where the new parameter affects the output but doesn\u2019t affect the key? Is there an existing function that was written before this parameter existed and hasn\u2019t been\u00a0updated?<\/p>\n<p>In the Solidity compiler, a function written for a single-storage-domain world was extended with a storage location parameter. The function body was updated. The cache key wasn\u2019t. The result was a silent miscompilation that shipped for 18 months in the compiler for a multi-billion-dollar financial ecosystem.<\/p>\n<p>The fix was adding one string prefix to one cache key. The lesson is bigger than the\u00a0fix.<\/p>\n<p><em>The full technical analysis of TSTORE Poison, the Solidity compiler bug described in this article, is available at<\/em><a href=\"https:\/\/hexens.io\/research\/solidity-compiler-bug-tstore-poison\"><em> https:\/\/hexens.io\/research\/solidity-compiler-bug-tstore-poison<\/em><\/a><\/p>\n<p><em>Solidity team official disclosure:<\/em><a href=\"https:\/\/soliditylang.org\/blog\/2026\/02\/18\/transient-storage-clearing-helper-collision-bug\/\"><em> https:\/\/soliditylang.org\/blog\/2026\/02\/18\/transient-storage-clearing-helper-collision-bug\/<\/em><\/a><\/p>\n<p><em>Discovered and reported by Hexens\u200a\u2014<\/em><a href=\"https:\/\/hexens.io\/\"><em>\u200ahttps:\/\/hexens.io<\/em><\/a><\/p>\n<p><a href=\"https:\/\/medium.com\/coinmonks\/when-your-compiler-lies-e522e19b0cec\">Solidity First High-Severity Compiler Bug in 10 Years<\/a> was originally published in <a href=\"https:\/\/medium.com\/coinmonks\">Coinmonks<\/a> on Medium, where people are continuing the conversation by highlighting and responding to this story.<\/p>","protected":false},"excerpt":{"rendered":"<p>A compiler caching bug silently swapped opcodes in production for 18 months. Tests passed. Audits passed. Formal verification passed. Nobody\u00a0noticed. ~8 min\u00a0read There\u2019s a class of software bug that sits below every tool designed to find bugs. It lives in the compiler, the program that translates your source code into the instructions your machine actually [&hellip;]<\/p>\n","protected":false},"author":0,"featured_media":154744,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2],"tags":[],"class_list":["post-154743","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-interesting"],"_links":{"self":[{"href":"https:\/\/mycryptomania.com\/index.php?rest_route=\/wp\/v2\/posts\/154743"}],"collection":[{"href":"https:\/\/mycryptomania.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/mycryptomania.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"replies":[{"embeddable":true,"href":"https:\/\/mycryptomania.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=154743"}],"version-history":[{"count":0,"href":"https:\/\/mycryptomania.com\/index.php?rest_route=\/wp\/v2\/posts\/154743\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/mycryptomania.com\/index.php?rest_route=\/wp\/v2\/media\/154744"}],"wp:attachment":[{"href":"https:\/\/mycryptomania.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=154743"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mycryptomania.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=154743"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mycryptomania.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=154743"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}