This project enables developers to write smart contracts in JavaScript on CKB. As we know, C and Rust can be used to write smart contracts on CKB. They can be compiled into RISC-V binary instructions as smart contracts. JavaScript can be run on virtual machines, depending on variable implementations. If these virtual machines are ported to CKB, JavaScript can also be run on CKB.
QuickJS is a famous JavaScript virtual machine implementation by Fabrice Bellard. This project aims to port it to CKB, enabling JavaScript capabilities in CKB programming.
The project is finally compiled into a single binary: ckb-js-vm which can be
found in build
folder after executing:
make
This smart contract can be executed on ckb-vm directly. Without any arguments,
it reads code_hash/hash_type from args
in Script. Then run the JavaScript code(JavaScript
source file or bytecode) in a cell denoted by code_hash/hash_type. Below is
the structure of the ckb-js-vm Script:
code_hash: <code hash of ckb-js-vm, 32 bytes>
hash_type: <hash type of ckb-js-vm, 1 byte>
args: <args, 2 bytes> <code hash of JavaScript code, 32 bytes> <hash type of JavaScript code, 1 byte>
Please note that the first 2 bytes in the args field are reserved for future use.
Smart contracts on ckb-vm can receive arguments, similar to other Linux executables. Depending on the provided arguments, ckb-js-vm behaves differently. There are four command-line options supported by ckb-js-vm:
- -e
- -f
- -c
- -r
Thanks to the power of
exec
or spawn,
ckb-js-vm can easily run with arguments.
When -e is provided, it can accept a string and evaluate it. This behavior is identical to the -e option in the native qjs:
-e --eval EXPR evaluate EXPR
Below is an example about how to use it:
-e 'console.log("hello,world")'
As argument can be very long on ckb-vm(depending on stack size), a very long JavaScript code can be executed via this method.
When -f
is provided, it treats JavaScript code as a file system, rather than a
single JavaScript code file. JavaScript module is based on file system. See more
Simple File System and JavaScript Module.
When -c
is provided, See section below.
When -r
is provided, it can read a local file via ckb-debugger, but this
functionality is intended for testing purposes only. It does not function in a
production environment. For additional examples, please refer to the tests
folder.
When -c
is provided, it can compile a JavaScript source file into JavaScript bytecode with
output as hexadecimal. Below is a recipe about how to compile JavaScript source file:
ckb-debugger --read-file hello.js --bin build/ckb-js-vm -- -c | awk -f $(ROOT_DIR)/../../tools/compile.awk | xxd -r -p > hello.bc
It reads hello.js
and then compiles the JavaScript source file into bytecode in hex
formatting. Then, using the power of awk
and xxd
, it can be converted into
binary. Finally, it is written as hello.bc
.
ckb-js-vm
can transparently run JavaScript bytecode or source files, which can also
be in file systems.
A ckb-js-vm script contains following data structure:
code_hash: <code_hash to ckb-js-vm cell>
hash_type: <hash_type>
args: <ckb-js-vm args, 2 bytes> <code_hash to JavaScript code cell, 32 bytes> <hash_type to JavaScript code cell, 1 byte> <JavaScript code args, variable length>
The tailing bytes are JavaScript code arguments which can be used by JavaScript. Note: 2 bytes ckb-js-vm args are reserved for further use.
The JavaScript code implementation for a "hello, world" costs an approximate expense of 2.9 M cycles. The utilization of the SimpleUDT consumes around 5.1 M cycles(3.4 M for bytecode), while the ckb-lua-vm takes a cost of roughly 2.0 M(1.3 M for bytecode) cycles.
The memory usage of the SimpleUDT is about 139K(130K in heap and 9K in stack).
There are four approaches to integrating ckb-js-vm:
spawn
exec
- Static library
- Dynamic library
Among these options, spawn
is the recommended method for integrating
ckb-js-vm. It's straightforward and easy to use. exec
is similar to spawn
,
but it lacks the ability to maintain the execution context and is no longer
recommended following the availability of spawn
.
Static linking is not practical due to the substantial binary size of ckb-js-vm (around 500K). The limitation on binary size is approximately 600K, leaving less than 100K for additional code.
For dynamic library integration, it also presents memory usage challenges, as it involves three components of memory usage:
- ckb-js-vm binary, approximately 500K.
- Heap memory utilized by
malloc
, which depends on the JavaScript code; typically, 500K is suggested. - Stack memory, which varies but usually 100K is sufficient for most cases.
Overall, 1M bytes will be allocated for the dynamic library, leaving a total usable memory of 4M. This leaves only 3M bytes for the host script. It's important to note that in certain critical scenarios, the peak memory usage can exceed the mentioned limits. Dynamic libraries also face security concerns, as detailed in security.md.