Running smart contracts without a blockchain
CosmWasm is WebAssembly plus a specific list of imports. Once that list is written down it stops being a platform and becomes an interface you can implement.
Motivation
A smart contract on Terra was a program compiled to WebAssembly, and WebAssembly is a smaller thing than most people assume.
It is an instruction format for a machine that has no hands. A WebAssembly module can compute, and it can read and write a block of memory that belongs to it, and that is the end of the list. It cannot open a file, send a packet, or read a clock, because none of those exist inside the machine. Anything else it does, it does by calling a function the host has handed it. Those handed-in functions are called imports, and the functions the module offers back are called exports.
This is a deliberate design and it is why the format shows up wherever untrusted code has to run next to something valuable. The host decides the entire vocabulary. Whatever is not in the import list did not merely fail, it was never expressible.
On the last day of 2021 I wrote down the observation this post is about, which is that CosmWasm is not WebAssembly. It is WebAssembly plus one specific import list and one specific export list. The note says it plainly: cosmwasm is not direct wasm, there are specific structures that encode CosmWasm. And then the consequence I was actually excited about, which is that if you open up experimentation at the wasm level you could make alternative contract languages that run on Terra.
A compilation target is a contract between a producer and a consumer. If you can write down the contract, anything that satisfies it can be a producer. Rust was the only producer in practice because the standard library you write contracts against was a Rust crate, but nothing about the machine required that.
The import list
The list is short enough to read, which is the whole reason any of the rest was possible. I wrote it out later in the README of one of the experiments, quoted from the CosmWasm project's own source, and it runs to fifteen functions.
Here is the whole list, grouped by what each function is for.
| Imports | For | Always present |
|---|---|---|
db_read, db_write, db_remove | reading and writing the contract's own storage | yes |
db_scan, db_next | iterating over stored keys | only with the iterator feature |
addr_validate, addr_canonicalize, addr_humanize | checking and converting addresses | yes |
secp256k1_verify, secp256k1_recover_pubkey | signature checks | yes |
ed25519_verify, ed25519_batch_verify | signature checks | yes |
query_chain | asking the surrounding chain a question | yes |
debug | writing a diagnostic string | yes |
abort | stopping on a failed assertion | only with the abort feature |
Notice how much of it is optional. Three storage functions are unconditional, and the two that iterate are compiled in only if the host advertises the feature, so a host that never needs iteration does not have to implement it.
Going the other way, the contract exports functions for the host to call. Four are required, and
two of those are housekeeping: allocate and deallocate, which let the host place data inside
the contract's memory, plus instantiate, plus a marker function named interface_version_8 that
exists only so the host can refuse a contract built against a different version of the interface.
Everything a contract actually does for a living is optional, including execute and query, and
later migrate, reply and sudo.
That the entry points are optional and the version marker is mandatory tells you what the host cares about. It will not run code it cannot verify it understands, and beyond that it does not mind what the code is for.
Fifteen functions in, a handful out. That is the entire surface between a contract and the world it runs in, and once it is written down as a list, it stops looking like a platform and starts looking like an interface you could implement yourself.
Implementing the host in JavaScript
Which is what cosmwasm-vm-js is. The repository was created in February 2022 and its description
is one sentence: run CosmWasm smart contracts in Node.js and web browsers.
The real VM is written in Rust and driven by Wasmer, and to run a contract you needed a chain, or
at least the chain's software and the Rust toolchain that builds it. Implementing the same import
list in TypeScript removes both. A .wasm file that was compiled for a blockchain will execute in
a browser tab, because the browser already has a WebAssembly engine and the fifteen functions it
needs can be supplied by ordinary JavaScript.
The seam is one interface with three fields:
const backend: IBackend = {
backend_api: new BasicBackendApi('terra'),
storage: new BasicKVIterStorage(),
querier: new BasicQuerier(),
};
const vm = new VMInstance(backend);Everything the contract can reach is behind backend. The address helpers are backend_api, the
five storage functions are storage, and the one chain question is querier. Swap any of the
three for your own implementation and the contract runs against your version without knowing.
In August 2022 I spent a day restructuring the library so it more closely resembled the structure of the Rust library it was imitating, and the reason was not tidiness. If the JavaScript implementation has the same seams in the same places as the real one, then a tool written against one seam has an obvious counterpart on the other side. If the reimplementation invents its own arrangement, every instrument you build is stranded in the fake.
Alternative source languages
Having claimed the target was open, the honest thing was to try to hit it from somewhere other than Rust, so in November 2022 I wrote two contract toolchains in a weekend and reported both results in the same journal entry.
The first was AssemblyScript, a language with TypeScript's syntax that compiles to WebAssembly. It worked, and the property I cared about is that it compiles in the browser, which meant it could sit next to the JavaScript VM and produce something like a JSFiddle for smart contracts: write a contract in a text box, compile it in the page, run it in the page, see the state change. No toolchain, no chain, no install.
I kept it as an experiment rather than a proposal, and the note at the top of its README says why: this is purely for study and experimentation, because Confio, who build CosmWasm, had expressed doubt about AssemblyScript's viability as a serious CosmWasm language on security grounds. That was fair, and two days later my own notes arrived at the same worry from the other direction, that it is easy to make a Wasm language for CosmWasm but you have to think about the security risks and non-determinism.
The second was C++, and my note on it is four words long: do not use.
Where safety belongs
Neither verdict is the point. The sentence that came next is: a contract should not be able to bring down a blockchain, and preventing that belongs at the VM layer rather than in the source language.
That is forced, once you have claimed the target is open. If safety is a property of the language, then safety has to be re-established from scratch for every new language, and every language that gets it slightly wrong is a new way for the chain to halt. Which makes an open compilation target a liability rather than a feature, because each producer you invite is another chance to break the consumer. Put the guarantee in the host instead and it holds no matter who wrote the producer, so adding a language stops being a security event and becomes an ordinary piece of work. Confio's objection to AssemblyScript and my own were both real, and both of them dissolve at the layer below the one they were aimed at.
The target's semantic vocabulary
By December I had stopped asking which languages could hit the target and started asking whether the target was the right shape.
The note is from the sixth. What I wrote is that CosmWasm has a semantics problem, in that the API
is limited in expression, and asked what it would look like to augment it with better semantic
primitives, wasm imports and exports closer to the way humans talk about contracts. Underneath, a
sketch: the low-level set, db_write, db_read, db_scan, mapped up to a meta-level set, save a
variable, check a condition.
Look at the import list again with that in mind. It is a key-value store, some address helpers, some signature checks, and one question. Every idea a contract has about ownership, permission, balances, or validity is something the author built on top of five storage calls, and the machine underneath knows none of it. That is a fine substrate and it is a poor vocabulary, and the gap between them is where contract bugs live.
I did not build the augmented version. What that question turned into instead was a language that compiles down to the existing imports, and a simulator that instruments them, both of which are their own stories.
What it made possible
cosmwasm-vm-js was published on npm, collected forty-four stars, and was pushed until January
2023. That is a small number, and it is the true one.
Its README carries a warning I still think is the most important sentence in the repository: great care was taken to match the behaviour of the original VM, this implementation may not produce identical results, it should not be used as a drop-in replacement, and results should be verified against the original for anything critical.
A reimplementation of a virtual machine is a claim that two systems agree, and no amount of care turns that claim into a proof. Writing the limit into the README is how the claim stays honest, and everything built on top of the JavaScript VM inherits both the convenience and the caveat.