@leachain/vm-shim

npm version GitHub license

@leachain/vm-shim is a reusable VM shim for Lea-chain WebAssembly modules, providing the necessary host environment for running smart contracts compatibly in both Node.js and browser environments.

Features

  • Environment-Agnostic: Works seamlessly in both Node.js and modern web browsers.
  • Memory Helpers: Includes utility functions (copyToWasm, readFromWasm, malloc) for easier memory management.
  • Secure by Default: Provides a sandboxed environment with no filesystem or network access unless explicitly passed in.
  • Configurable: Allows overriding the default abort handler and extending the environment with custom functions.
  • Typed API: Includes TypeScript definitions for a better developer experience.

Installation

npm install @leachain/vm-shim

Usage

The primary export is createShim, which generates the importObject and exposes helper functions under utils for a WebAssembly instance.

ES Modules (ESM)

import { promises as fs } from 'fs';
import { createShim } from '@leachain/vm-shim';

async function runWasm() {
    // 1. Create the shim instance
    const {
        importObject,
        bindInstance,
        utils: { copyToWasm, readFromWasm }
    } = createShim();

    // 2. Read your Wasm module bytes
    const wasmBytes = await fs.readFile('./path/to/your_contract.wasm');

    // 3. Instantiate the module with the shim's import object
    const { instance } = await WebAssembly.instantiate(wasmBytes, importObject);

    // 4. IMPORTANT: Bind the created instance to the shim
    // This allows host functions and helpers to access Wasm memory and exports.
    bindInstance(instance);

    // 5. Use helpers to interact with Wasm memory
    const data = new TextEncoder().encode("Hello from JS");
    const ptr = copyToWasm(data); // Allocates and copies data

    // 6. Call an exported function from your Wasm module
    const resultPtr = instance.exports.process_data(ptr, data.length);
    
    // 7. Read the result back from Wasm memory
    const result = readFromWasm(resultPtr, 13); // Assuming result is 13 bytes
    console.log(`Wasm function returned: ${new TextDecoder().decode(result)}`);
}

runWasm().catch(console.error);

CommonJS (CJS)

const { promises: fs } = require('fs');
const { createShim } = require('@leachain/vm-shim');

async function runWasm() {
    // 1. Create the shim instance
    const {
        importObject,
        bindInstance,
        utils: { copyToWasm }
    } = createShim();

    // 2. Read your Wasm module bytes
    const wasmBytes = await fs.readFile('./path/to/your_contract.wasm');

    // 3. Instantiate the module
    const { instance } = await WebAssembly.instantiate(wasmBytes, importObject);

    // 4. Bind the instance
    bindInstance(instance);

    // 5. Use a helper and call a Wasm function
    const data = new TextEncoder().encode("Hello");
    const ptr = copyToWasm(data);
    instance.exports.your_function(ptr, data.length);
}

runWasm().catch(console.error);

API Reference

createShim(config?)

  • config <object> (Optional)
    • onAbort <(message: string) => void>: Overrides the default abort behavior. The default logs an error and exits (Node.js) or throws an error (browser).
    • customEnv <object>: An object with custom host functions to add to the env namespace. See Extending the Shim.
  • Returns <object>
    • importObject <object>: The WebAssembly import object. Pass this to WebAssembly.instantiate.
    • bindInstance <(instance: WebAssembly.Instance) => void>: Binds the Wasm instance to the shim. Call after instantiation.
    • print <object>: Colored logging utility with red, orange, green, blue.
    • utils <object>: Helper functions for memory and allocation:
      • copyToWasm(data: Uint8Array): number
      • readFromWasm(ptr: number, length: number): Uint8Array
      • malloc(length: number): number
      • reset(): void

cstring(memory, ptr)

A utility function to read a null-terminated UTF-8 string from the Wasm instance's memory.

  • memory <WebAssembly.Memory>: The exported memory from your Wasm instance (instance.exports.memory).
  • ptr <number>: The pointer (memory address) of the string.
  • Returns <string>: The decoded string.

Default Host Functions

The shim provides several host functions by default, available under the env namespace.

  • __lea_abort(line: number): Handles fatal errors.
  • __lea_log(ptr: number, len: number): Prints a message from Wasm to the host console.
  • __lea_ubsen(...): Handles errors from the Undefined Behavior Sanitizer (UBSan).
  • __lea_randombytes(ptr: number, len: number): Fills a buffer in Wasm memory with secure random bytes.
  • __execution_limit(...), __address_add(...), __execution_stack_add(...): Hooks for blockchain-specific operations.

Extending the Shim

You can add your own host functions to the env namespace by passing a customEnv object in the createShim configuration.

Example: Adding a Custom Function

import { createShim } from '@leachain/vm-shim';

const customEnv = {
  my_custom_function: (arg1, arg2) => {
    console.log(`my_custom_function called with: ${arg1}, ${arg2}`);
    return 42;
  }
};

const { importObject } = createShim({ customEnv });

// ... proceed with Wasm instantiation

Your WebAssembly module can then import and call this function (e.g., extern int my_custom_function(int arg1, int arg2); in C/C++).

Contributing

Contributions are welcome! Please open an issue or submit a pull request for any bugs, features, or improvements.

License

This project is licensed under the ISC License. See the LICENSE file for details.