sascha1337 / cosmwasm-as-web

CosmWasm contracts in AssemblyScript (experiment)

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

CosmWasm smart contracts in AssemblyScript

image

NOTE: This is purely for study and experimentation. Confio has expressed doubt regarding AssemblyScript's viability as a serious CosmWasm language due to concerns about security.

This repository contains a sample implementation of several CosmWasm smart contracts written in AssemblyScript. We test the behavior of our contracts against cosmwasm-vm-js, a JavaScript-based runtime for CosmWasm that is convenient to instrument and run locally.

Uploaded Contracts

The AssemblyScript contract shown in this repository are uploaded to the following addresses:

Feel free to play around with them -- it's just a simple counter.

Quickstart

  1. First, clone the repository.
$ git clone https://github.com/terran-one/cosmwasm-as
$ cd cosmwasm-as
  1. Install the dependencies. We used yarn, but you can use npm as well.
$ yarn
  1. Run yarn build in the contract directory to build the AssemblyScript Wasm binaries.

NOTE: This compiles using AssemblyScript, then rewrites it using @cosmwasm-as/rewrite-wasm, which uses Binaryen.

$ cd contracts/cw-as-counter
$ yarn build
  1. Run the tests.
$ yarn test

Project Structure

This project was created with the asbuild tool and follows a directory organization similar to other AssemblyScript projects.

cw-as-counter
    ├── assembly/ -- AssemblyScript source root
    │   ├── src/ -- Contract implementation
    │   │   ├── contract.ts -- `contract.rs` analog
    │   │   ├── msg.ts -- `msg.rs` analog
    │   │   └── state.ts -- `state.rs` analog
    │   └── index.ts -- directs compiler on assembling Wasm module -- `lib.rs` analog
    ├── build/
    │   ├── debug.wasm -- Wasm binary: with debug symbols
    │   └── release.wasm -- Wasm binary: production-optimized
    └── tests/
        └── works.test.js -- Simple acceptance test

Architecture

A CosmWasm contract is a Wasm module that adheres to the following structure1.

Wasm Imports

Imports provided by CosmWasm VM
extern "C" {
	#[cfg(feature = "abort")]
	fn abort(source_ptr: u32);

	fn db_read(key: u32) -> u32;
	fn db_write(key: u32, value: u32);
	fn db_remove(key: u32);

	#[cfg(feature = "iterator")]
	fn db_scan(start_ptr: u32, end_ptr: u32, order: i32) -> u32;
	#[cfg(feature = "iterator")]
	fn db_next(iterator_id: u32) -> u32;

	fn addr_validate(source_ptr: u32) -> u32;
	fn addr_canonicalize(source_ptr: u32, destination_ptr: u32) -> u32;
	fn addr_humanize(source_ptr: u32, destination_ptr: u32) -> u32;

	fn secp256k1_verify(message_hash_ptr: u32, signature_ptr: u32, public_key_ptr: u32) -> u32;
	fn secp256k1_recover_pubkey(
		message_hash_ptr: u32,
		signature_ptr: u32,
		recovery_param: u32,
	) -> u64;

	fn ed25519_verify(message_ptr: u32, signature_ptr: u32, public_key_ptr: u32) -> u32;
	fn ed25519_batch_verify(messages_ptr: u32, signatures_ptr: u32, public_keys_ptr: u32) -> u32;

	fn debug(source_ptr: u32);

	fn query_chain(request: u32) -> u32;
}

We declare them inside @cosmwasm-as/std/imports.ts and use where needed inside our library code. Note that since these are quite low level, the end-user consuming the cosmwasm-as API probably won't need to import them directly.

Wasm Exports

Exports expected by CosmWasm VM
Required
extern "C" {
	fn allocate(size: usize) -> u32;
	fn deallocate(pointer: u32);
	fn instantiate(env_ptr: u32, info_ptr: u32, msg_ptr: u32) -> u32;
	fn interface_version_8() -> ();
}

Optional

extern "C" {
	fn execute(env_ptr: u32, info_ptr: u32, msg_ptr: u32) -> u32;
	fn query(env_ptr: u32, msg_ptr: u32) -> u32;

	// TODO: the following have yet to be implemented
	fn migrate(env_ptr: u32, msg_ptr: u32) -> u32;
	fn reply(env_ptr: u32, msg_ptr: u32) -> u32;
	fn sudo(env_ptr: u32, msg_ptr: u32) -> u32;
	fn ibc_channel_open(env_ptr: u32, msg_ptr: u32) -> u32;
	fn ibc_channel_connect(env_ptr: u32, msg_ptr: u32) -> u32;
	fn ibc_channel_close(env_ptr: u32, msg_ptr: u32) -> u32;
	fn ibc_packet_receive(env_ptr: u32, msg_ptr: u32) -> u32;
	fn ibc_packet_ack(env_ptr: u32, msg_ptr: u32) -> u32;
	fn ibc_packet_timeout(env_ptr: u32, msg_ptr: u32) -> u32;
}

This is the main "meat" that is relevant to our implementation, which must get explicitly exported by assembly/index.ts to get picked up by the AssemblyScript compiler. Their implementation resides in @cosmwasm-as/std/exports.ts -- we simply re-export them in our assembly/index.ts:

// assembly/index.ts

// This file is important for the AssemblyScript compiler to correctly construct
// the WASM module, and should be common to all CosmWasm AssemblyScript projects.
// To program your contract, you should modify code in the `./contract` folder.

// Required Wasm exports
import {do_instantiate, do_execute, do_query} from "@cosmwasm-as/std";
import {ExecuteMsg, InstantiateMsg, QueryMsg} from "./src/msg";
import {instantiateFn, executeFn, queryFn} from "./src/contract";

export {
	interface_version_8,
	allocate,
	deallocate,
} from '@cosmwasm-as/std';

export function instantiate(env: i32, info: i32, msg: i32): i32 {
	return do_instantiate<InstantiateMsg>(env, info, msg, instantiateFn);
}

export function execute(env: i32, info: i32, msg: i32): i32 {
	return do_execute<ExecuteMsg>(env, info, msg, executeFn);
}

export function query(env: i32, msg: i32): i32 {
	return do_query<QueryMsg>(env, msg, queryFn);
}

Passing data to/from CosmWasm VM (host environment)

The CosmWasm VM <> contract data protocol is quite simple:

  • VM to contract: values are serialized to JSON and loaded into a pointer into Wasm linear memory, requested by calling allocate.
  • Contract to VM: values are serialized to JSON and a pointer to a Region struct describing the section of VM memory is returned to the VM.

The "start"-ing Region struct consists of only 3 members:

  • offset - the underlying pointer
  • capacity - max size of encoded object
  • length - current length of data stored in memory

Build Changes

We altered the build script slightly to make it work with CosmWasm.

Step 1: Compile AssemblyScript to Wasm

asc assembly/index.ts
	--target debug
	--sourceMap
	--debug
+	--disable bulk-memory
+	--use abort=~lib/@cosmwasm-as/std/as/ABORT
+	--runtime stub
+	--exportStart

A couple changes here:

1. --disable bulk-memory

If not set, Wasmer will complain about missing 0xFC opcode.

2. --use abort=~lib/@cosmwasm-as/std/as/ABORT

AssemblyScript usually requires the host environment to supply env.abort. However, CosmWasm supplies a different function with the same name, so we rewire it according to Simon Warta's example .

3. --runtime stub

To disable garbage collection (GC).

4. --exportStart

Export the (start ...) instruction rather than have it being implicity called.

Step 2: Rewrite binary

We authored a tool using Binaryen to remove the (start ...) instruction and replace it by prepending it to each entrypoint's function body.

cosmwasm-as-rewrite-wasm util/rewrite-wasm [--optimize=1] build/debug.wasm

Copyright

Copyright © 2022 Terran One LLC

Footnotes

  1. Discussed in further detail on the CosmWasm official repository README

About

CosmWasm contracts in AssemblyScript (experiment)


Languages

Language:TypeScript 90.0%Language:JavaScript 10.0%