Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add simple UDT example #2

Merged
merged 4 commits into from
Oct 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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