Will ChenWill Chen
← writing

cw-simulaterunning a CosmWasm chain in JavaScript

system design7 min

cw-simulate runs CosmWasm contracts inside a JavaScript simulation of a Cosmos chain. I can upload a compiled contract, instantiate it, send messages, inspect its storage, and reset the entire chain without starting a node or installing a Rust toolchain.

I built it because contract development had a terrible feedback loop. A real chain is deliberately slow and irreversible. Development requires the opposite: run the same broken transaction forty times, return to the same starting state after each attempt, and inspect exactly what changed.

I initially described the project as a debugger. That name led me toward breakpoints and paused execution. CosmWasm contracts do not work that way. Each entry point runs to completion, writes its own state, and returns messages for the chain to execute afterward. The useful core was therefore a small chain runtime with snapshots and tracing. Debugging became one application built on top of it.

I only reproduced what a contract can observe

A local simulator does not need consensus, networking, peers, or a mempool. Contracts cannot observe any of those systems. They can observe the environment passed into an entry point, the host functions available to their WebAssembly module, and the response produced after execution.

A contract response has four parts:

export interface ContractResponse {
  messages: SubMsg[];
  events: Event[];
  attributes: Attribute[];
  data: Binary | null;
}

The simulator needs to process those values the same way a chain does. Everything else can be a much simpler implementation.

CosmWasm contracts compile to WebAssembly. WebAssembly cannot access storage or chain state directly. The host supplies a short list of imports:

  • db_read, db_write, db_remove, db_scan, and db_next for contract storage
  • address validation and conversion
  • signature verification
  • query_chain for reading surrounding chain state

The contract exposes entry points such as instantiate, execute, query, migrate, reply, and sudo. Each call also receives the current block, the contract address, the sender, and any attached funds.

That interface defined the simulator's fidelity boundary. If cw-simulate supplies the same inputs, host functions, and message handling visible to the contract, it can replace the full chain during development.

Contract execution produces work for the chain

A CosmWasm contract cannot synchronously call another contract while retaining its own stack frame. An entry point updates local storage and returns a list of messages. The chain executes those messages after the contract has returned.

The end of an open_position handler in Mirror shows the pattern:

store_position_idx(deps.storage, position_idx + Uint128::from(1u128))?;

Ok(Response::new()
    .add_attributes(vec![
        attr("action", "open_position"),
        attr("position_idx", position_idx.to_string()),
    ])
    .add_messages(messages))

The contract stores its new position index, records attributes, returns the messages it wants processed, and ends. The chain then routes each message to the relevant module or contract.

This execution model changed the tool I needed to build. There is no paused application stack between two contract calls. The state worth inspecting is the state before and after each completed transition, plus the messages that caused the next transition.

cw-simulate therefore records state snapshots and processes a message queue. The interface can show the current state, a diff from the previous snapshot, the emitted events, and the next messages to execute. Resetting restores an earlier snapshot and clears later work.

I separated the runtime from its views

My first UI managed contracts, state history, message history, and execution directly. That made every new view a change to the runtime logic. I pulled the state management into a headless JavaScript package and kept rendering in a separate package.

The project settled into three layers:

cw-vm-js       executes one contract entry point
cw-simulate    runs modules, routes messages, and owns chain state
cw-simulate-ui displays contracts, snapshots, diffs, events, and traces

cw-vm-js implements the CosmWasm host functions around a WebAssembly module. cw-simulate uses that VM inside a larger chain model. The UI only reads the runtime state and sends commands back to it.

This split made the simulator usable without the original interface. Tests, scripts, browser tools, and future views could all drive the same runtime. It also kept debugging features from changing the contract execution model.

The simulator uses the same module boundaries as Cosmos

Cosmos chains are assembled from modules. A module owns state, handles messages addressed to it, and answers queries about that state. The bank module owns balances. The wasm module owns uploaded code and contract instances.

I used the same structure in cw-simulate. The package contains base, bank, and wasm modules. The application configures a chain ID, address prefix, modules, block state, and storage:

import { CWSimulateApp } from "@terran-one/cw-simulate";

const app = new CWSimulateApp({
  chainId: "phoenix-1",
  bech32Prefix: "terra",
});

const codeId = app.wasm.create(sender, wasmBytecode);

let result = await app.wasm.instantiateContract(
  sender,
  funds,
  codeId,
  { count: 0 }
);

result = await app.wasm.executeContract(
  sender,
  funds,
  contractAddress,
  { increment: {} }
);

result = await app.wasm.query(
  contractAddress,
  { get_count: {} }
);

Using the same boundaries made discrepancies easier to locate. If a simulated bank transfer differs from a real one, I know to compare the bank module's message handling. If a contract query differs, I can inspect the wasm module or VM. A custom architecture would have mixed those responsibilities and made every comparison harder.

The object model follows the same rule. Uploaded code receives a code ID. Instantiating that code creates a contract address and storage namespace. The chain owns block height, time, parameters, and module state. Users submit messages to the chain.

Transactional storage makes reset exact

The simulator stores chain state in a prefixed key-value store. Each module and contract receives its own namespace. Transaction wrappers collect changes during execution and commit them only when the complete operation succeeds.

This matters when one contract emits several messages. If a later message fails, the simulator must return to the state before the transaction. Keeping a UI copy of "previous values" would miss writes performed by nested messages or modules. The storage layer already sees every write, so rollback belongs there.

Snapshots use the same mechanism. cw-simulate can retain a committed state, execute more messages, then restore the earlier version. The reset button returns the entire simulated chain to a known point, including module state and contract storage.

That gave me the development loop I wanted: create a starting state once, try a transaction, inspect its effects, reset, change the contract or message, and run it again.

I trace storage through the VM instead of parsing Rust

To debug an arbitrary contract, I needed to know which keys it read and wrote. I first considered extracting that information from Rust source. Static analysis can find obvious storage calls, but Rust gives developers many ways to wrap, re-export, or generate the same operation. A source analyzer would always depend on the coding style of the contract.

Every persistent operation eventually crosses one of five WebAssembly imports. The contract does not implement db_read or db_write; the host does. Instrumenting those functions captures every storage access from every contract language.

The JavaScript VM exposes a replaceable method beneath each wire-level import. cw-simulate overrides those methods and records their arguments and results:

export class CWSimulateVMInstance extends VMInstance {
  constructor(
    public logs: DebugLog[],
    backend: IBackend
  ) {
    super(backend);
  }

  do_db_read(key: Region): Region {
    const result = super.do_db_read(key);

    this.logs.push({
      type: "call",
      fn: "db_read",
      args: { key: key.str },
      result: result.str,
    });

    return result;
  }

  // equivalent overrides for write, remove, scan, and next
}

The contract runs normally and cannot detect the logger. Coverage no longer depends on whether the author used a standard helper, a custom abstraction, generated code, Rust, or another frontend. Every persistent access still crosses the host boundary.

The backend makes the execution environment replaceable

The VM receives three dependencies:

const backend: IBackend = {
  backend_api: new BasicBackendApi("terra"),
  storage: new BasicKVIterStorage(),
  querier: new BasicQuerier(),
};

backend_api implements address and cryptographic operations. storage owns contract state. querier answers questions about the surrounding chain.

I can wrap or replace each dependency. A storage wrapper can record accesses, reject writes matching a predicate, or expose changes to a fuzzer. A custom querier can replay recorded chain state or return controlled responses for a test. A backend API can simulate another address prefix or report every validation call.

This was the basis of OverseerVM, the auditing layer I built around the simulator. The VM records boundary calls, while programmable backend wrappers let an auditor define conditions that fire when a contract touches specific state. A fuzzer can then generate messages and check those conditions across many clean runs.

The important implementation choice was to instrument dependencies supplied by the host. I did not require the target contract to contain logging code or use a particular library. That made the same tools work against contracts written by someone else.

Traces contain prints and host calls

The trace format only needs two entry types:

export type DebugLog = PrintDebugLog | CallDebugLog;

export interface PrintDebugLog {
  type: "print";
  message: string;
}

export type CallDebugLog = {
  type: "call";
  fn: K;
} & CosmWasmAPI[K];

A print entry records a diagnostic message emitted by the contract. A call entry records an import crossing, including its function name, arguments, and result. The first contains what the author chose to report. The second records what the contract actually asked the host to do.

cw-simulate stores traces beside the state transition that produced them. The UI can display a transaction, its events, the storage diff, and the ordered host calls together. Scripts and tests can consume the same trace as structured data.

Keeping the trace in the runtime package also prevents the UI from becoming its owner. A headless test receives the same observations as someone using the visual simulator.

The simulator has a defined accuracy limit

cw-simulate and cw-vm-js reimplement behavior from the chain and reference VM. Passing tests establishes agreement for those cases. It does not prove identical behavior for every contract or every future chain version.

The README states that limitation directly and tells users to verify critical results against the original VM. I also keep differences close to the affected functions. For example, the JavaScript VM collects debug strings in a list while the reference implementation writes them through its own diagnostic path.

That boundary matters because the simulator is intentionally easier to modify than the chain. Its replaceable storage, querier, and import hooks are the source of its value for testing. They also mean consensus behavior remains the responsibility of the real implementation.

cw-simulate runs completed transitions and records everything between them

The finished design is a resettable JavaScript chain around a WebAssembly contract runtime. It executes one contract call to completion, routes the returned messages through Cosmos-style modules, commits or rolls back the resulting state, and records each observable boundary crossing.

That structure came directly from the problem I was solving. I needed rapid contract experiments, so the runtime had to reset exactly. I needed several contracts to interact, so it had to schedule messages and own shared chain state. I needed to inspect arbitrary contracts, so I traced the storage imports every contract must call.

The simulator does not try to pause a contract between source lines. It shows the unit CosmWasm actually executes: a complete state transition, the messages it emitted, the state it changed, and the host operations it used.