Skip to content

Commit

Permalink
Add simple UDT example (#2)
Browse files Browse the repository at this point in the history
* Add simple UDT example

* Add script to introduction
  • Loading branch information
XuJiandong authored Oct 12, 2023
1 parent d3d06ca commit 7ad933b
Show file tree
Hide file tree
Showing 10 changed files with 250 additions and 24 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ make all

## Examples

* [Demo Script: Simple UDT](./tests/ckb_js_tests/test_data/simple_udt.js)
* [Fibonacci Number](./tests/examples/fib.js)
* [Calculate PI](./tests/examples/pi_bigint.js)

Expand Down
12 changes: 12 additions & 0 deletions docs/intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,15 @@ 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.

## Script
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.
14 changes: 1 addition & 13 deletions docs/syscalls.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ and `index`.

Example:
```js
ckb.mount(source, index)
ckb.mount(index, source)
```

Arguments: source (the source of the cell to load), index (the index of the cell
Expand Down Expand Up @@ -404,18 +404,6 @@ Return value(s): memory size in bytes

See also: [`ckb_current_memory` syscall](https://github.com/nervosnetwork/rfcs/pull/418/files)

#### ckb.mount
Description: Load the file system in the cell.

Example:
```js
ckb.mount(2, ckb.SOURCE_CELL_DEP)
```

Arguments: index (the index of the cell), source (the source of the cell)

Return value(s): none

## Exported Constants

Most constants here are directly taken from [ckb_consts.h](https://github.com/nervosnetwork/ckb-system-scripts/blob/master/c/ckb_consts.h):
Expand Down
32 changes: 26 additions & 6 deletions quickjs/ckb_module.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,26 @@ static JSValue syscall_exit(JSContext *ctx, JSValueConst this_val, int argc, JSV
return JS_UNDEFINED;
}

static JSValue ThrowError(JSContext *ctx, int32_t error_code, const char *message) {
JSValue obj, ret;
obj = JS_NewError(ctx);
if (unlikely(JS_IsException(obj))) {
/* out of memory: throw JS_NULL to avoid recursing */
obj = JS_NULL;
} else {
JS_DefinePropertyValueStr(ctx, obj, "message", JS_NewString(ctx, message),
JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
JS_DefinePropertyValueStr(ctx, obj, "error_code", JS_NewInt32(ctx, error_code),
JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
}
// TODO
// if (add_backtrace) {
// build_backtrace(ctx, obj, NULL, 0, 0);
// }
ret = JS_Throw(ctx, obj);
return ret;
}

static void my_free(JSRuntime *rt, void *opaque, void *_ptr) { free(opaque); }
struct LoadData;
typedef int (*LoadFunc)(void *addr, uint64_t *len, struct LoadData *data);
Expand All @@ -51,8 +71,8 @@ static JSValue parse_args(JSContext *ctx, LoadData *data, bool has_field, int ar
if (JS_ToInt64(ctx, &index, argv[0])) {
return JS_EXCEPTION;
}
if (JS_ToInt64(ctx, &source, argv[1])) {
return JS_EXCEPTION;
if (JS_ToBigInt64(ctx, &source, argv[1])) {
if (JS_ToInt64(ctx, &source, argv[1])) return JS_EXCEPTION;
}
int var_arg_index = 2;
if (has_field) {
Expand Down Expand Up @@ -112,6 +132,7 @@ static JSValue syscall_load(JSContext *ctx, LoadData *data) {
ret = JS_NewArrayBuffer(ctx, addr, real_len, my_free, addr, false);
exit:
if (err != 0) {
ThrowError(ctx, err, "ckb syscall error");
return JS_EXCEPTION;
} else {
return ret;
Expand Down Expand Up @@ -470,6 +491,7 @@ static JSValue mount(JSContext *ctx, JSValueConst this_value, int argc, JSValueC
uint8_t *addr = JS_GetArrayBuffer(ctx, &psize, buf);
int err = ckb_load_fs(addr, psize);
if (err != 0) {
ThrowError(ctx, SyscallErrorUnknown, "ckb_load_fs failed");
return JS_EXCEPTION;
} else {
return JS_UNDEFINED;
Expand Down Expand Up @@ -515,10 +537,8 @@ int js_init_module_ckb(JSContext *ctx) {
JS_SetPropertyStr(ctx, ckb, "set_content", JS_NewCFunction(ctx, syscall_set_content, "set_content", 1));
JS_SetPropertyStr(ctx, ckb, "get_memory_limit",
JS_NewCFunction(ctx, syscall_get_memory_limit, "get_memory_limit", 0));
JS_SetPropertyStr(ctx, ckb, "current_memory",
JS_NewCFunction(ctx, syscall_current_memory, "current_memory", 0));
JS_SetPropertyStr(ctx, ckb, "mount",
JS_NewCFunction(ctx, mount, "mount", 2));
JS_SetPropertyStr(ctx, ckb, "current_memory", JS_NewCFunction(ctx, syscall_current_memory, "current_memory", 0));
JS_SetPropertyStr(ctx, ckb, "mount", JS_NewCFunction(ctx, mount, "mount", 2));
JS_SetPropertyStr(ctx, ckb, "SOURCE_INPUT", JS_NewInt64(ctx, CKB_SOURCE_INPUT));
JS_SetPropertyStr(ctx, ckb, "SOURCE_OUTPUT", JS_NewInt64(ctx, CKB_SOURCE_OUTPUT));
JS_SetPropertyStr(ctx, ckb, "SOURCE_CELL_DEP", JS_NewInt64(ctx, CKB_SOURCE_CELL_DEP));
Expand Down
2 changes: 1 addition & 1 deletion quickjs/qjs.c
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ void js_std_dump_error(JSContext *ctx) {
void js_std_loop(JSContext *ctx) {
JSContext *ctx1;
int err;
for(;;) {
for (;;) {
err = JS_ExecutePendingJob(JS_GetRuntime(ctx), &ctx1);
if (err <= 0) {
if (err < 0) {
Expand Down
7 changes: 6 additions & 1 deletion tests/ckb_js_tests/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ all: \
cargo_test \
file_system \
syscall \
fs_bytecode
fs_bytecode\
simple_udt\
fs_mount

cargo_test:
cargo test
Expand Down Expand Up @@ -40,6 +42,9 @@ fs_mount:
cd test_data/fs_module_mount && lua ../../../../tools/fs.lua pack ../../../../build/fib_module.bin fib_module.js
cargo run --bin module_mount | ${CKB_DEBUGGER} --tx-file=- -s lock

simple_udt:
cargo run --bin simple_udt | $(CKB_DEBUGGER) --tx-file=- --script-group-type type --cell-type output --cell-index 0

install-lua:
sudo apt install lua5.4

Expand Down
9 changes: 9 additions & 0 deletions tests/ckb_js_tests/src/bin/simple_udt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use ckb_js_tests::read_tx_template;

pub fn main() -> Result<(), Box<dyn std::error::Error>> {
let tx = read_tx_template("templates/simple_udt.json")?;

let json = serde_json::to_string_pretty(&tx).unwrap();
println!("{}", json);
Ok(())
}
72 changes: 72 additions & 0 deletions tests/ckb_js_tests/templates/simple_udt.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
{
"mock_info": {
"inputs": [
{
"output": {
"capacity": "0x10000000",
"lock": {
"args": "0x",
"code_hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"hash_type": "data1"
},
"type": {
"args": "0x0000{{ ref_type js-code-file }}01",
"code_hash": "0x{{ ref_type ckb-js-vm }}",
"hash_type": "type"
}
},
"data": "0x11000000000000000000000000000000"
}
],
"cell_deps": [
{
"output": {
"capacity": "0x10000000",
"lock": {
"args": "0x00AE9DF3447C404A645BC48BEA4B7643B95AC5C3AE",
"code_hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"hash_type": "data1"
},
"type": "{{ def_type ckb-js-vm }}"
},
"data": "0x{{ data ../../../build/ckb-js-vm }}"
},
{
"output": {
"capacity": "0x10000000",
"lock": {
"args": "0x00AE9DF3447C404A645BC48BEA4B7643B95AC5C3AE",
"code_hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"hash_type": "data1"
},
"type": "{{ def_type js-code-file }}"
},
"data": "0x{{ data ../test_data/simple_udt.js }}"
}
],
"header_deps": []
},
"tx": {
"outputs": [
{
"capacity": "0x0",
"lock": {
"args": "0x",
"code_hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"hash_type": "type"
},
"type": {
"args": "0x0000{{ ref_type js-code-file }}01",
"code_hash": "0x{{ ref_type ckb-js-vm }}",
"hash_type": "type"
}
}
],
"witnesses": [
"0x0001020304050607"
],
"outputs_data": [
"0x11000000000000000000000000000000"
]
}
}
114 changes: 114 additions & 0 deletions tests/ckb_js_tests/test_data/simple_udt.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@

const CKB_INDEX_OUT_OF_BOUND = 1;
const ERROR_AMOUNT = -52;

function assert(cond, obj1) {
if (!cond) {
throw Error(obj1);
}
}

function compare_array(a, b) {
if (a.byteLength != b.byteLength) {
return false;
}
for (let i = 0; i < a.byteLength; i++) {
if (a[i] != b[i]) {
return false;
}
}
return true;
}

function unpack_script(buf) {
let script = new Uint32Array(buf);
let raw_data = new Uint8Array(buf);

let full_size = script[0];
assert(full_size == buf.byteLength, 'full_size == buf.byteLength');
let code_hash_offset = script[1];
let code_hash = buf.slice(code_hash_offset, code_hash_offset + 32);
let hash_type_offset = script[2];
let hash_type = raw_data[hash_type_offset];
let args_offset = script[3];
let args = buf.slice(args_offset + 4);
return {'code_hash': code_hash, 'hash_type': hash_type, 'args': args};
}

function* iterate_field(source, field) {
let index = 0;
while (true) {
try {
let ret = ckb.load_cell_by_field(index, source, field);
yield ret;
index++;
} catch (e) {
if (e.error_code == CKB_INDEX_OUT_OF_BOUND) {
break;
} else {
throw e;
}
}
}
}

function* iterate_cell_data(source) {
let index = 0;
while (true) {
try {
let ret = ckb.load_cell_data(index, source);
yield ret;
index++;
} catch (e) {
if (e.error_code == CKB_INDEX_OUT_OF_BOUND) {
break;
} else {
throw e;
}
}
}
}

function main() {
console.log('simple UDT ...');
let buf = ckb.load_script();
let script = unpack_script(buf);
let owner_mode = false;
// ckb-js-vm has leading 35 bytes args
let real_args = script.args.slice(35);
for (let lock_hash of iterate_field(ckb.SOURCE_INPUT, ckb.CKB_CELL_FIELD_LOCK_HASH)) {
if (compare_array(lock_hash, real_args)) {
owner_mode = true;
}
}
if (owner_mode) {
return 0;
}
let input_amount = 0n;

for (let data of iterate_cell_data(ckb.SOURCE_GROUP_INPUT)) {
if (data.byteLength != 16) {
throw `Invalid data length: ${data.byteLength}`;
}
let n = new BigUint64Array(data);
let current_amount = n[0] | (n[1] << 64n);
input_amount += current_amount;
}
let output_amount = 0n;
for (let data of iterate_cell_data(ckb.SOURCE_GROUP_OUTPUT)) {
if (data.byteLength != 16) {
throw `Invalid data length: ${data.byteLength}`;
}
let n = new BigUint64Array(data);
let current_amount = n[0] | (n[1] << 64n);
output_amount += current_amount;
}
console.log(`verifying amount: ${input_amount} and ${output_amount}`);
if (input_amount < output_amount) {
return ERROR_AMOUNT;
}
console.log('Simple UDT quit successfully');
return 0;
}

ckb.exit(main());
11 changes: 8 additions & 3 deletions tests/ckb_js_tests/test_data/syscall.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,15 @@ function expect_array(a, b) {

function must_throw_exception(f) {
let has_exception = false;
let error_code = 0;
try {
f();
} catch (e) {
has_exception = true;
error_code = e.error_code;
}
console.assert(has_exception, 'Error, no exception found');
return error_code;
}

function test_partial_loading(load_func) {
Expand All @@ -35,10 +38,12 @@ function test_partial_loading(load_func) {
data = load_func(0, ckb.SOURCE_OUTPUT, 7, 1);
expect_array(data, ARRAY8.slice(1, 8));

must_throw_exception(() => {
load_func(1001n, ckb.SOURCE_OUTPUT);
let error_code = must_throw_exception(() => {
load_func(1001, ckb.SOURCE_OUTPUT);
});
must_throw_exception(() => {
// CKB_INDEX_OUT_OF_BOUND
console.log(error_code === 1);
error_code = must_throw_exception(() => {
load_func(0, ckb.SOURCE_OUTPUT + 1000n);
});
console.log('test_partial_loading done');
Expand Down

0 comments on commit 7ad933b

Please sign in to comment.