Skip to content

NAN 008: Defending Wasm in Layers, and Reading EVM State from Wasm ​

Posted by Unique on 2026-09-30. Releases: v2.19.0, v2.20.0. Prior mainnet upgrade: v2.18.1.

NAN 007 < Nibiru Advancement Notes (NANs)

NAN 008 cover

Summary ​

  • A Wasm runtime vulnerability was disclosed to us privately. It's now public as CWA-2026-006.
  • v2.19 made the node binary harder to exploit. Release builds are static PIE binaries, which load at a random address, and use PebbleDB only.
  • v2.20 restricted who can deploy Wasm on mainnet while we prepared the runtime repair.
  • The runtime repair shipped in October with v2.21.
  • Wasm contracts can read EVM state, including simulated eth_call results.
  • One command finds any transaction. nibid q tx accepts a CometBFT or an EVM hash.
  • Sai shows what you pay. Every position shows its borrowing fee, and PnL is shown after fees.

1) Three layers, because no single layer was enough ​

A defect in Wasmer's Singlepass compiler was disclosed to us privately. Singlepass is the compiler CosmWasm uses to turn contract code into machine code. A crafted contract could escape the Wasm sandbox and run native instructions inside the node. The advisory describes the result: unauthorized minting and permanent loss of funds.

The real fix was a new runtime. That takes time to build, test, and roll out to every validator, so we didn't wait for it. We stacked three layers and shipped each one when it was ready:

Three layers of Wasm defense

  1. Harden the binary (v2.19). This makes an escape harder to use.
  2. Restrict deployment (v2.20). This makes it harder to get a malicious contract on chain.
  3. Repair the runtime (v2.21, in October). This removes the defect.

Neither of the first two fixes the bug, and we said so in the changelog. Each one narrows what an attacker can do until the next lands.

2) Layer one: static PIE, and dropping RocksDB ​

Our Linux release binary was statically linked, but it loaded at a fixed address. That makes exploits easier, because an attacker knows where the code lives. v2.19 builds a static position-independent executable (PIE), so the operating system can place it at a random address on each run. Release checks now verify that property in the finished binary rather than trusting compiler flags.

Static PIE has a cost: every native library linked into the binary has to be built for it. RocksDB is a C++ library, and making it work would have meant a separate native build path in the middle of a security response. So the published builds dropped RocksDB and use PebbleDB only.

That was an easy call, because RocksDB was already on its way out. On paper it's faster than Go's LevelDB and a good fit for running a node. In production, it wasn't. Operators hit memory problems deep inside its C code. PebbleDB is pure Go, so Go's garbage collector manages its memory. When something needs changing, we update one Go dependency instead of patching low-level C. We ran both in production, and Pebble was the more stable one.

The only other reason to keep RocksDB was our own indexer, which used to read RocksDB files directly. Heart Monitor moved to PebbleDB too. After that, RocksDB was build complexity with nothing depending on it. PebbleDB had been the default for a long time, and our snapshots and state sync already ran on it. v2.19 finished the move. No one on the network runs RocksDB anymore.

v2.19 shipped as verified binaries first, and the public source followed.

We moved the Wasm VM's release builders from Debian Bullseye to Bookworm in the same stretch. Bullseye reached end of life on August 31, and its package mirrors started failing our CI (PR #2750).

3) Layer two: a temporary deployment guard ​

v2.20 limits Wasm code upload, instantiation, and migration on mainnet to the current root of the x/sudo module, plus existing governance authorization (PR #2762).

Where the check sits mattered most. A Wasm deployment can arrive as a normal message, as a submessage from another contract, or through the EVM's Wasm precompile. We put the guard in the shared keeper entry points that all three go through. Adding it to the message handlers alone would have left two doors open.

The guard doesn't stop contracts already on chain from running, and it doesn't touch execution or queries. Root-only deployment was also a blunt instrument by design. In October, v2.21 replaced it with scoped roles. A deployer role can deploy without holding every other sudo power.

4) Wasm contracts can read EVM state ​

A Wasm contract can query other modules only through an allowlist of Stargate query paths. Nibiru's EVM already served read queries over gRPC, but those paths weren't on the list, so Wasm calls to them failed. v2.20 adds them (PR #2775). A Wasm contract can now read EVM accounts, balances, storage, code, parameters, and FunToken mappings.

The most useful one is EthCall. A Wasm contract can simulate a call to an EVM contract and read the result without committing anything. Wasm apps can now use data that only an EVM contract exposes. They still can't send EVM transactions. The Wasm message handler keeps rejecting those.

Reading EVM state from Wasm

5) One command for either hash ​

A Cosmos transaction has a CometBFT hash. An Ethereum transaction has an EVM hash. If you're chasing a cross-VM issue, you shouldn't have to know which one your tool wants. nibid q tx now accepts either. For EVM activity, it links the EVM transaction to its CometBFT record (PR #2783).

6) Owning distribution ​

Release binaries now feed npm packages and AMD64 and ARM64 container images directly, with checksums verified at each step (PR #2751, PR #2756). The Rust contract workspace moved into the chain repository, and its test suite now runs in the chain's CI (PR #2754), continuing what NAN 006 started. The repository switched from Make to Just, and the Go baseline moved to 1.27 (PR #2776).

7) On Sai: knowing what you pay ​

  • Borrowing fees on every position, and PnL shown after fees, so the number you see is the number you'd get.
  • Points leaderboards, by season and by week.
  • USDT sends on external chains from Wallet Send.
  • stNIBI collateral retired. Sai trades in USDC only.
  • Wallet approvals in mobile in-app browsers work through EIP-6963.

Retiring stNIBI is the kind of change we like making. We added it so Sai could use liquidity already on Nibiru, like staked NIBI, to help early growth. Traders didn't want it. stNIBI was under 1% of Sai's volume and collateral value, and everyone else used USDC.

Supporting a second collateral wasn't cheap. To value it, Sai needed its price across all of history. Accounting had to convert it to dollars, and a lot of contract state was keyed by collateral as well as by market. With USDC alone, Sai can treat a dollar as a dollar, the way centralized exchanges do. The code is simpler and has fewer places for bugs.

Collateral was never the right job for stNIBI anyway. Look at BNB on Binance. Futures there settle in USDT, not BNB. BNB earns its place through holder benefits instead: hold or stake enough and you get lower trading fees and rebates. That's the better model for stNIBI on Sai. Its value should come from benefits for holding it, not from forcing it into the trading path, where it made things harder for us and did nothing for traders.

That's what talking to users is for. It tells you what to build, and it also tells you what to remove. Cutting a feature nobody uses is part of the job.

What's next ​

October finishes the Wasm response with Wasmer 7.4.2 and scoped sudo roles in v2.21. It also brings the consensus engine in-tree as Nibiru BFT, and one-click trading on Sai. That's the next note.

References ​

Pull requests behind each section. The Sai repositories are private, so the text above describes each change on its own.

1) Three layers

2) Layer one: static PIE, and dropping RocksDB

3) Layer two: a temporary deployment guard

  • nibiru#2762: Limit Wasm upload, instantiation, and migration on mainnet.

4) Wasm contracts can read EVM state

  • nibiru#2775: EVM query routes on the Wasm Stargate allowlist, including EthCall.

5) One command for either hash

  • nibiru#2783: nibid q tx accepts CometBFT or EVM hashes.

6) Owning distribution

7) On Sai