Home

Awesome

CosmWasm VM in JavaScript

This package contains an implementation of the CosmWasm VM that is runnable on Node.js and web browsers that support WebAssembly (currently only tested on V8 browsers like Google Chrome). This allows you to run .wasm binaries intended for CosmWasm without the need for a backend blockchain or Rust toolchain, enabling new ways to instrument and test CosmWasm smart contracts.

NOTE: This package is intended to work with contracts built for CosmWasm v1.0.

NOTE: Although great care has been taken to match the behavior of the original CosmWasm VM (powered by Wasmer), this implementation may not provide identical results and should not be used as a drop-in replacement. Results obtained should be verified against the original implementation for critical use-cases.

Setup

Add the cosmwasm-vm-js package as a dependency in your package.json.

npm install -S @terran-one/cosmwasm-vm-js

or

yarn add @terran-one/cosmwasm-vm-js

Usage

import { readFileSync } from 'fs';
import { VMInstance } from '@terran-one/cosmwasm-vm-js';
import {
  BasicBackendApi,
  BasicKVIterStorage,
  BasicQuerier,
  IBackend,
} from 'cosmwasm-vm-js/backend';

const wasmBytecode = readFileSync('testdata/cosmwasm_vm_test.wasm');
const backend: IBackend = {
  backend_api: new BasicBackendApi('terra'),
  storage: new BasicKVIterStorage(),
  querier: new BasicQuerier(),
};

const vm = new VMInstance(backend);
const mockEnv = {
  block: {
    height: 1337,
    time: '2000000000',
    chain_id: 'columbus-5',
  },
  contract: {
    address: 'terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76',
  },
};

const mockInfo = {
  sender: 'terra1337xewwfv3jdjuz8e0nea9vd8dpugc0k2dcyt3',
  funds: [],
};

describe('CosmWasmVM', () => {
  it('instantiates', async () => {
    await vm.build(wasmBytecode);

    const region = vm.instantiate(mockEnv, mockInfo, { count: 20 });
    console.log(region.json);
    console.log(vm.backend);
    const actual = {
      ok: {
        attributes: [
          { key: 'method', value: 'instantiate' },
          {
            key: 'owner',
            value: 'terra1337xewwfv3jdjuz8e0nea9vd8dpugc0k2dcyt3',
          },
          { key: 'count', value: '20' },
        ],
        data: null,
        events: [],
        messages: [],
      },
    };
    expect(region.json).toEqual(actual);
  });

  it('execute', async () => {
    await vm.build(wasmBytecode);

    let region = vm.instantiate(mockEnv, mockInfo, { count: 20 });
    region = vm.execute(mockEnv, mockInfo, { increment: {} });
    console.log(region.json);
    console.log(vm.backend);
    const actual = {
      ok: {
        attributes: [{ key: 'method', value: 'try_increment' }],
        data: null,
        events: [],
        messages: [],
      },
    };
    expect(region.json).toEqual(actual);
  });
});

How it works

CosmWasm smart contracts are WebAssembly binaries that export certain function symbols called "entrypoints", such as the following:

Users interact and invoke operations on the smart contract by calling the desired entrypoint with arguments. As these are exposed as WebAssembly functions, we should normally be able to call them directly. However, CosmWasm contracts carry some implicit requirements that must be met before we can interact with the contract's functions naturally.

  1. Contracts expect certain symbols to be provided by the VM host (WASM imports).
  2. Contracts need an environment with storage to which it can read and write data.
  3. Contract entrypoints expect to be called with input arguments prepared and allocated into memory in a certain way.
  4. The response of contract entrypoint invocations should be parsed.

cosmwasm-vm-js provides a VM implementation that addresses all of these requirements and exposes a simulated execution environment that can be further customized to enable possibilities such as instrumentation, visualization, debugging, and more.

WASM Imports

The following WASM imports have been implemented according to imports.rs in cosmwasm-vm.

Import NameImplemented?Tested?Notes
db_read:white_check_mark::white_check_mark:
db_write:white_check_mark::white_check_mark:
db_remove:white_check_mark::white_check_mark:
db_scan:white_check_mark::white_check_mark:
db_next:white_check_mark::white_check_mark:
addr_humanize:white_check_mark::white_check_mark:
addr_canonicalize:white_check_mark::white_check_mark:
addr_validate:white_check_mark::white_check_mark:
secp256k1_verify:white_check_mark::white_check_mark:
secp256k1_recover_pubkey:white_check_mark::white_check_mark:
ed25519_verify:white_check_mark::white_check_mark:
ed25519_batch_verify:white_check_mark::white_check_mark:
debug:white_check_mark::white_check_mark:Appends to a list of strings instead of printing to console.
query_chain:white_check_mark::white_check_mark:
abort:white_check_mark::white_check_mark:

Environment & Storage

We provide a simple key-value store with bytes keys and bytes values in BasicKVIterStorage.

WebAssembly Linear Memory

A loaded CosmWasm contract module's linear memory is accessible as WebAssembly.Memory, which can be read as a bytearray through JavaScript's Uint8Array data type.

Passing data from JavaScript to WASM

To invoke entrypoint functions, we need to pass in arguments from JavaScript and load them into WebAssembly linear memory accessible by the contract. Although we can write directly to WebAssembly.Memory, doing this is considered unsafe as we don't know what we might be touching. Instead, we must use the contract's allocate entrypoint which gives us a pointer to a writeable region of linear memory which is recognized by the WASM code.

cosmwasm-vm-js also provides the Region class, which is an analog of the Region type found in cosmwasm-vm.

CosmWasm's Region type

/// Describes some data allocated in Wasm's linear memory.
/// A pointer to an instance of this can be returned over FFI boundaries.
///
/// This is the same as `cosmwasm_std::memory::Region`
/// but defined here to allow Wasmer specific implementation.
#[repr(C)]
#[derive(Default, Clone, Copy, Debug)]
struct Region {
    /// The beginning of the region expressed as bytes from the beginning of the linear memory
    pub offset: u32,
    /// The number of bytes available in this region
    pub capacity: u32,
    /// The number of bytes used in this region
    pub length: u32,
}

CosmWasm contract entrypoints expect their parameters to be pointers to Region structs, which point to the actual data via offset.

arg ---> Region ---> argument data

License

This software is licensed under the MIT License.

Copyright © 2022 Terran One LLC