CKB JS VM: Mechanism and Capabilities
The Quick Start guide mentions that JS contracts run inside the ckb-js-vm. This document provides a detailed explanation of ckb-js-vm.
Introduction
CKB contracts are based on the RISC-V architecture, which means most programming languages can be used to develop contracts for CKB. Currently, Rust and C are the most widely used languages for contract development. However, both have relatively high barriers to entry, which discourages many developers from getting started.
We hope to expand support to more languages in order to lower the entry threshold for developers.
Most beginner-friendly languages, such as Python and JavaScript, are interpreted languages. For CKB-VM, this means they must be interpreted and executed inside the virtual machine, which adds an additional layer of complexity.
JavaScript has long been one of the most popular programming languages. In the past, we attempted to run JavaScript on CKB using Duktape, a lightweight JavaScript engine. However, due to poor performance, this effort was not continued.
Later, we developed ckb-lua-vm. From a technical perspective, Lua is well-suited for contract development and offers acceptable performance. Unfortunately, Lua's limited popularity prevented it from gaining widespread adoption.
Building on these past experiences, we created ckb-js-vm, which is based on QuickJS, and built a full set of supporting tools, including:
- JavaScript bindings with TypeScript libraries
- A JavaScript testing framework:
ckb-testtool
(QuickJS is a small and embeddable JavaScript engine. It was created by Fabrice Bellard, the author of QEMU and the original developer of FFmpeg.)
ckb-js-vm args
Explanation
The ckb-js-vm
script structure in molecule is below:
code_hash: <code hash of ckb-js-vm, 32 bytes>
hash_type: <hash type of ckb-js-vm, 1 byte>
args: <ckb-js-vm flags, 2 bytes> <code hash of resource cell, 32 bytes> <hash type of resource cell, 1 byte>
The first 2 bytes are parsed into an int16_t
in C using little-endian format (referred to as ckb-js-vm flags). If
the lowest bit of these flags is set (v & 0x01 == 1
), the file system is enabled. File system functionality will be
described in another section.
The subsequent code_hash
and hash_type
point to a resource cell which may contain:
- A file system
- JavaScript source code
- QuickJS bytecode
When the file system flag is enabled, the resource cell contains a file system that can also include JavaScript code.
For most scenarios, QuickJS bytecode is stored in the resource cell. When an on-chain script requires extra args
,
they can be stored beginning at offset 35 (2 + 32 + 1). Compared to normal on-chain scripts in other languages,
ckb-js-vm requires these extra 35 bytes.
An Example — A Clear Contrast Between JS Contracts and Traditional Contracts
Since JavaScript contracts are executed inside the JS-VM, creating a transaction for them introduces a few differences compared to traditional contracts.
In the simple-lock
example, both Rust and JavaScript implementations of the same contract are provided. Functionally they are identical, but the Rust version is compiled directly into a native executable contract, while the JavaScript version is only compiled into bytecode.
The key differences are as follows:
-
Script
-
Traditional contracts use their own
code_hash + hash_type
. -
JavaScript contracts instead reference the
code_hash + hash_type
of the ckb-js-vm.- Traditional contract: Lock Script
- JavaScript contract: Lock Script
-
-
Args
-
Traditional contracts directly fill in the
args
field. -
JavaScript contracts prepend JS-specific parameters at the beginning of
args
. (This also means when reading arguments inside the contract, you need to offset by 35 bytes.)- Traditional contract: Reading args
- JavaScript contract: Reading args
-