Search This Blog

Thursday, June 18, 2026

The Wrong Guarantee

Why Rust Was the Wrong Proxy for Solana On-Chain Competence


On February 2, 2022, an attacker drained approximately $320 million from the Wormhole bridge.[1] Wormhole had undergone multiple third-party audits, including recent Neodyme reports covering the Solana contracts and other components in January 2022 — weeks before the exploit. The code was written in Rust. It compiled without errors. The borrow checker raised no objections. Every memory safety guarantee Rust provides was present and functioning.

The attacker did not touch memory. Reporting at the time indicated that a fix for the vulnerability had been committed publicly but not yet deployed; hours later, the vulnerability was exploited.[1] A deprecated function that silently skipped signature verification had been left in the production code. Signature verification responsibility had been delegated through a chain of functions until it fell through entirely. The attacker had modeled the program's trust boundaries more carefully than its authors had. The program executed. Rust raised no objection. There was nothing for Rust to object to.

This is the essay's premise in a single event: Rust delivered exactly what it promised, and it was irrelevant to what failed.

What On-Chain Programs Actually Require


A Solana on-chain program operates under constraints that anyone who has written firmware or real-time embedded software will recognize immediately.

There is a fixed compute budget. Exceed it and the transaction aborts. The heap and stack are tightly bounded. The runtime serializes accounts into a flat input buffer, executes the program, charges compute units, and aborts if limits are exceeded. Execution is deterministic and sandboxed.[3]

The attacker's goal is not to crash the program. A crash costs them little beyond transaction fees and leaves state unchanged.[2] Their goal is to find gaps in the program's logic — missing ownership checks (not memory ownership), incorrect authority validation, assumptions about account relationships that the code never verifies — and extract value through those gaps while the program executes successfully. In Rust's terms. With a clean borrow checker pass.

The discipline this environment requires is specific: model the trust boundaries of the system exhaustively before writing code; treat every external input as adversarial; design the value-flow invariants of the protocol — signer authority, account identity, mint relationships, PDA derivations, CPI targets, state-machine transitions — before selecting data structures. This is not a coding discipline. It is a design discipline. It predates the editor. It is developed by working in environments that punish its absence immediately and visibly — not environments where the runtime absorbs the consequences quietly. It exists, or it does not, before the compiler is ever invoked.

Rust has no opinion on any of it.
 

What Rust Actually Guarantees

Rust enforces memory ownership. It ensures that memory is not accessed after it is freed, that mutable references do not alias, that data does not race across threads. These are real and valuable properties. In a long-running concurrent system — a database, a browser engine, an operating system — they prevent entire classes of failure that are otherwise expensive and difficult to detect.

In a Solana on-chain program, they are largely beside the point.

The Solana runtime executes SBF programs in a sandboxed, deterministic environment. Programs do not manage threads. The memory model is constrained by the runtime itself. The classes of failure Rust prevents — use-after-free, data races, buffer overflows that corrupt adjacent memory — are not the classes of failure that have cost the Solana ecosystem hundreds of millions of dollars. The failures that have been expensive are semantic: programs that executed correctly in Rust's terms while doing exactly what an attacker constructed them to do.

Rust enforces ownership of memory. Solana programs fail when they do not enforce ownership of value. These are different properties. One is verified at compile time by a tool. The other requires a developer who can enumerate, before writing code, every assumption the program makes about who is allowed to do what, and verify that each assumption is explicitly checked at runtime. The compiler cannot produce this. It cannot notice its absence.
 

How the Ecosystem Institutionalized the Gap


Anchor, Solana's dominant development framework, materially reduces Rust boilerplate through macro-generated account validation. Its own documentation is candid about the tradeoff, warning that Anchor uses "a lot of magic" and that developers deploying to mainnet need to understand what the macros are doing.[4] What the macros generate is the scaffolding of a security model. What they cannot generate is a security model.

Anchor can enforce declared constraints. It cannot infer undeclared invariants.

When the threat is in the boilerplate — when the attacker is exploiting a class of error the macro was written to prevent — Anchor protects the developer. When the threat is in the protocol design itself, when the attack requires exploiting an assumption that was never explicitly stated anywhere in the code, the macro is silent. No compiler error. No audit flag. Nothing. The gap exists in what was never written, and neither the language nor the framework has any mechanism to surface it.

The developer who decorated structs with Anchor macros and shipped a protocol was not writing account validation logic. They were trusting a tool to answer a question — what does this program need to verify before acting? — that requires understanding the economic and adversarial structure of the system being built. The tool answers the mechanical version of that question. The substantive version requires a different kind of thinking entirely. Anchor did not develop it. Rust proficiency did not develop it. The ecosystem provided no systematic path to it, and the compiler did not notice its absence.

This is the pattern the ecosystem repeated at every layer: a formally defensible abstraction consumed without the model that makes it true, transferred into a confidence it was never designed to support. Memory safety confidence transferred into protocol correctness confidence. Framework validation macros transferred into complete threat-model coverage. Audit of written code transferred into audit of missing invariant. Each transfer was invisible. Each cost real money.

The audit culture that developed around this ecosystem inherited the same blind spot. Auditors review what is written against what the language and framework can verify. They were not, systematically, asked to verify whether the protocol model was complete — whether the invariant that needed to exist had been identified in the first place. The Wormhole audits reviewed correct Rust across multiple components and missed a deprecated function that silently dropped a check. Not because the auditors were careless. Because the frame of reference shared by the developers, the framework, and the audit methodology converged on one question: is the Rust correct? The attack surface was at the design layer above it.

A 2025 survey counted approximately twelve Solana smart-contract security analysis tools, compared with 113 for Ethereum.[5] Coverage for semantic and architectural vulnerability classes — the categories that account for the actual losses — remains comparatively immature. This is the tooling expression of an ecosystem that has been asking the wrong question, consistently, at scale.
 

The Exploit Record


The Cashio exploit in March 2022 cost $52 million.[6] The program was written in Rust, unaudited. An attacker constructed fake accounts and exploited missing mint and account validation so that valueless collateral was accepted as backing for real CASH tokens. The validation was not incorrect. It was absent. The program executed successfully. Rust raised no objection because there was nothing for Rust to object to. The memory was fine. The invariant had never been written.

The Wormhole exploit cost $320 million from an audited program. The mechanics are described in the opening. The relevant fact here is the category: not a memory error, not a race condition, not a buffer overflow. A missing check in a trust boundary that the protocol's design assumed existed and the code never verified.

Hacken's 2025 data shows the same pattern at scale: access-control failures accounted for nearly 58% of total Web3 losses across the first three quarters of 2025, while lower-level smart-contract vulnerabilities accounted for a fraction of that.[7] Many of the most consequential failures have been semantic, adversarial, and economic rather than memory-safety failures — audited and unaudited code alike — because the failure mode lives entirely at the design layer, in the space between what the protocol assumed and what the code checked.

 

The Performance Reckoning


The security record is what the mismatch costs in exploits. The performance record is what it costs in compute.

The standard development path for a Solana on-chain program ran through Anchor — full Rust abstractions, Borsh serialization, macro-generated validation. Programs compiled, deployed, and ran. They also ran expensively, and the expense was not visible until it became a competitive constraint.

Pinocchio, released by the Anza team, is a zero-dependency, zero-copy Solana program library written in Rust, explicitly optimized for compute unit consumption and binary size.[8] It is less beginner-friendly than Anchor-style development by design. Its benchmark result on the token program: an 88% to 95% reduction in CU consumption depending on the instruction.[9] Anza's commentary attributes roughly 70% of those compute savings to two changes: replacing the traditional entry point and adopting zero-copy reads directly from the input buffer instead of deserializing through an abstraction layer.[9]

Those changes — reading directly from the flat input buffer, avoiding deserialization — are not optimizations in C. They are the default. The flat buffer is the input; the pointer is how you read it. In the default Rust development path on Solana, this was hidden behind a serialization framework, presented as an implementation detail, and its cost remained invisible until a dedicated library was built specifically to undo it.

Pinocchio is written in Rust. It does not prove that C was the necessary answer. What it proves is more precise: the default ergonomic Solana Rust stack systematically hid compute costs that the environment's constraints make critical. Recovering efficiency required abandoning the abstractions that made Solana development accessible — the same category of abstraction that, in Anchor's case, hid the missing security modeling requirement. The pattern is not coincidental. Abstractions that reduce apparent task complexity also reduce the developer's exposure to what the task actually demands. In an environment where both the compute budget and the attack surface require explicit engagement with that underlying complexity, this is not a tradeoff. It is a mismatch between what the default toolchain exposes and what the environment requires.

The HashMap problem is a small but precise illustration of what that background produces in practice. Rust's ecosystem teaches that HashMap lookup is O(1) — a statement that is mathematically real and architecturally incomplete. The full cost model is that HashMap performs hashing, bucket lookup, probing, and equality comparison on every access; that actual performance depends heavily on key type, load factor, and memory locality; and that for small, bounded key sets, a Vec linear scan is contiguous, branch-light, allocation-light, and often faster in practice. A developer whose mental model of data structures was formed in an environment where collections are opaque runtime objects — where the machine beneath them is never visible and never a concern — will hear "O(1)" and understand "fast." A developer who has written a hot loop in C, examined the assembly output, or profiled cache behavior will understand something more specific and more useful.

A developer of the first kind, reaching for HashMap to store the four to twenty accounts a typical on-chain instruction processes, has made an architectural decision from a slogan rather than from a cost model. The Rust compiler accepted it without comment. The framework raised no objection. The compute budget absorbed the cost quietly, because in most cases it could. The developer received no signal that the decision required examination. The ecosystem had taught them that O(1) was the correct answer, and the toolchain confirmed it by compiling successfully.

This is the pattern the ecosystem institutionalized: abstractions and asymptotic labels consumed without the models that make them meaningful, in an environment where the cost of that omission is measured in compute units rather than in program crashes — and therefore remains invisible to a developer who was never taught to look for it.
 

The Structural Mismatch


Rust was chosen for Solana partly as a quality filter. The borrow checker's demands would select for serious engineers. The steep learning curve would keep out the copy-paste developers who had degraded Ethereum's Solidity ecosystem. The reasoning was that Rust proficiency was a reasonable proxy for engineering quality.

The flaw in this reasoning is that Rust proficiency measures one specific thing: the ability to model memory ownership correctly. That is a property a developer can acquire by learning Rust. It requires no prior knowledge of how memory is laid out, how a CPU cache works, how a protocol's state space should be modeled before writing the first function, or what an adversary can do with a transaction they construct themselves. In the on-chain environment, none of those omissions are visible at compile time. The relevant competence is the ability to model value ownership adversarially — to enumerate, before writing code, the complete set of ways an attacker could cause the program to act on behalf of someone it should not. These are different skills. They are developed through different practices. They are tested by different questions. A developer can satisfy every constraint the Rust compiler imposes and have given no systematic thought to any of them.

The ecosystem compounded this by building its default toolchain — Anchor, Borsh, the standard SDK — in a way that further reduced the developer's contact with the environment's actual demands. The compute constraints were hidden behind serialization layers. The account validation requirements were hidden behind macros. The protocol design questions were never surfaced at all, because neither the language nor the framework has a mechanism to ask them.

The result was an ecosystem that selected for compiler proficiency, rewarded abstraction, and measured correctness at the wrong layer — then watched as the losses accumulated at the layer the selection mechanism never reached.

The Verdict


Rust was not Solana's failure mode. Treating Rust correctness as a proxy for protocol correctness was.

Solana's on-chain environment presents a specific and well-defined threat model: semantic, adversarial, and economic. Many of the most consequential exploits in the ecosystem's history belong to this category. None of them were memory safety failures. Rust's guarantees address memory safety. The two things are orthogonal, and the ecosystem's choice of quality filter, toolchain defaults, audit methodology, and security tooling all reflect a consistent orientation toward the property Rust enforces rather than the property the environment requires.

Within safe Rust's model, it enforces memory ownership correctly. But that model is orthogonal to protocol correctness. Every borrow checker passed. Every program was, in Rust's terms, correct.

The question the environment actually required — is the protocol model complete? have all value-flow invariants been identified and checked? — was never asked by the compiler, was not systematically asked by the framework, was not consistently asked by the auditors, and was not structurally demanded by the development path that the ecosystem built and standardized on.

The compiler had no way to notice. That was always the problem.



[1]: The Verge — Wormhole hack, February 2022
[2]: Solana Documentation — Transactions
[3]: Solana Documentation — Program Limitations
[4]: Anchor Documentation — Security Exploits
[5]: arXiv — Exploring Vulnerabilities and Concerns in Solana Smart Contracts, 2025
[6]: Halborn — Explained: The Cashio Hack, March 2022
[7]: Hacken — TRUST Report 2025
[8]: GitHub — anza-xyz/pinocchio
[9]: Anza — Febo on Pinocchio, P-Token, and Pushing Solana's Limits
[10]: Wormhole Foundation — Security / 3rd Party Security Audits
Wormhole audits — 2022-01-10 Neodyme report