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

feat: add evalScript, loadScript, loadFile, parseExtJson #34

Merged
merged 4 commits into from
Jan 14, 2025
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
168 changes: 149 additions & 19 deletions src/ckb_module.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,6 @@
#include "ckb_cell_fs.h"
#include "qjs.h"

#define CHECK2(cond, code) \
do { \
if (!(cond)) { \
err = code; \
printf("checking failed on %s:%d, code = %d", __FILE__, __LINE__, code); \
goto exit; \
} \
} while (0)

#define CHECK(_code) \
do { \
int code = (_code); \
if (code != 0) { \
err = code; \
printf("checking failed on %s:%d, code = %d", __FILE__, __LINE__, code); \
goto exit; \
} \
} while (0)

// For syscalls supporting partial loading, the arguments are described as:
// argument 1: index
// argument 2: source
Expand Down Expand Up @@ -587,6 +568,151 @@ static JSValue mount(JSContext *ctx, JSValueConst this_value, int argc, JSValueC
}
}

JSValue eval_script(JSContext *ctx, const char *str, int len, bool enable_module) {
int eval_flags = enable_module ? JS_EVAL_TYPE_MODULE : (JS_EVAL_FLAG_ASYNC | JS_EVAL_TYPE_GLOBAL);

JSValue val = JS_Eval(ctx, str, len, "<evalScript>", eval_flags);

if (JS_IsException(val)) {
JS_Throw(ctx, val);
return JS_EXCEPTION;
}

// Handle promise states
int promise_state = JS_PromiseState(ctx, val);
if (promise_state >= 0) {
switch (promise_state) {
case JS_PROMISE_REJECTED: {
JSValue error = JS_PromiseResult(ctx, val);
JS_FreeValue(ctx, val);
JS_Throw(ctx, error);
val = JS_EXCEPTION;
break;
}
case JS_PROMISE_FULFILLED: {
JSValue ret = JS_PromiseResult(ctx, val);
JS_FreeValue(ctx, val);
val = ret;
break;
}
case JS_PROMISE_PENDING: {
JS_FreeValue(ctx, val);
val = JS_ThrowInternalError(ctx, "invalid promise state in evalScript: pending");
break;
}
default: {
JS_FreeValue(ctx, val);
printf("unknown promise state: %d", promise_state);
val = JS_ThrowInternalError(ctx, "unknown promise state in evalScript");
break;
}
}
}

// Non-promise result
return val;
}

static JSValue js_eval_script(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
const char *str;
size_t len;
JSValue ret;
bool enable_module = false;

if (argc < 1) {
return JS_ThrowTypeError(ctx, "evalScript requires at least 1 argument");
}

// Get enable_module flag if provided
if (argc > 1) {
enable_module = JS_ToBool(ctx, argv[1]);
}

str = JS_ToCStringLen(ctx, &len, argv[0]);
if (!str) return JS_EXCEPTION;

ret = eval_script(ctx, str, len, enable_module);
JS_FreeCString(ctx, str);
return ret;
}

static JSValue js_load_file(JSContext *ctx, JSValueConst _this_val, int argc, JSValueConst *argv) {
uint8_t *buf = NULL;
const char *filename = NULL;
JSValue ret = JS_EXCEPTION;
size_t buf_len = 0;
int err = 0;

filename = JS_ToCString(ctx, argv[0]);
CHECK2(filename != NULL, -1);

size_t index = 0;
bool use_filesystem = false;
err = load_cell_code_info(&buf_len, &index, &use_filesystem);
CHECK(err);

buf = js_malloc(ctx, buf_len + 1);
CHECK2(buf != NULL, 0);

err = load_cell_code(buf_len, index, buf);
CHECK(err);

if (use_filesystem) {
// don't need to load file system for a single file.
// it should be mounted or initialized before.
FSFile *file_handler = NULL;
err = ckb_get_file(filename, &file_handler);
CHECK(err);
ret = JS_NewStringLen(ctx, file_handler->content, file_handler->size);
} else {
ret = JS_ThrowInternalError(ctx, "loadFile fail: filesystem is disabled.");
}

exit:
if (buf) {
js_free(ctx, buf);
}
if (filename) {
JS_FreeCString(ctx, filename);
}
if (err) {
return JS_EXCEPTION;
} else {
return ret;
}
}

static JSValue js_load_script(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
bool enable_module = false;

// Get enable_module flag if provided
if (argc > 1) {
enable_module = JS_ToBool(ctx, argv[1]);
}

JSValue ret = js_load_file(ctx, this_val, argc, argv);
if (JS_IsException(ret)) return ret;

size_t len = 0;
const char *str = JS_ToCStringLen(ctx, &len, ret);
JS_FreeValue(ctx, ret);
ret = eval_script(ctx, str, len, enable_module);
JS_FreeCString(ctx, str);
return ret;
}

static JSValue js_parse_ext_json(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
JSValue obj;
const char *str;
size_t len;

str = JS_ToCStringLen(ctx, &len, argv[0]);
if (!str) return JS_EXCEPTION;
obj = JS_ParseJSON2(ctx, str, len, "<input>", JS_PARSE_JSON_EXT);
JS_FreeCString(ctx, str);
return obj;
}

static const JSCFunctionListEntry js_ckb_funcs[] = {
JS_CFUNC_DEF("exit", 1, syscall_exit),
JS_CFUNC_DEF("load_tx_hash", 1, syscall_load_tx_hash),
Expand Down Expand Up @@ -615,6 +741,10 @@ static const JSCFunctionListEntry js_ckb_funcs[] = {
JS_CFUNC_DEF("process_id", 0, syscall_process_id),
JS_CFUNC_DEF("load_block_extension", 3, syscall_load_block_extension),
JS_CFUNC_DEF("mount", 2, mount),
JS_CFUNC_DEF("evalScript", 2, js_eval_script),
JS_CFUNC_DEF("loadScript", 2, js_load_script),
JS_CFUNC_DEF("loadFile", 1, js_load_file),
JS_CFUNC_DEF("parseExtJSON", 1, js_parse_ext_json),

// Constants
JS_PROP_INT64_DEF("SOURCE_INPUT", CKB_SOURCE_INPUT, JS_PROP_ENUMERABLE),
Expand Down
1 change: 1 addition & 0 deletions src/ckb_module.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ int read_local_file(char *buf, int size);
int load_cell_code_info_explicit(size_t *buf_size, size_t *index, const uint8_t *code_hash, uint8_t hash_type);
int load_cell_code_info(size_t *buf_size, size_t *index, bool *use_filesystem);
int load_cell_code(size_t buf_size, size_t index, uint8_t *buf);
JSValue eval_script(JSContext *ctx, const char *str, int len, bool enable_module);

#endif // _CKB_MODULE_H_
7 changes: 6 additions & 1 deletion tests/ckb_js_tests/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ all: out \
simple_udt \
fs_mount \
fs_bytecode \
cell_target
cell_target \
load_script

out:
@mkdir -p $(ROOT_DIR)/../../build/bytecode
Expand All @@ -45,6 +46,10 @@ fs_mount:
cd test_data/fs_module_mount && $(FS_PACKER) pack ../../../../build/bytecode/fib_module_mount.fs index.js init.js
cargo run --bin module_mount | ${CKB_DEBUGGER} --tx-file=- -s lock

load_script:
cd test_data/load_script && $(FS_PACKER) pack ../../../../build/bytecode/load_script.fs index.js fib_module.js fib.js
cargo run --bin load_script | ${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

Expand Down
9 changes: 9 additions & 0 deletions tests/ckb_js_tests/src/bin/load_script.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/load_script.json")?;

let json = serde_json::to_string_pretty(&tx).unwrap();
println!("{}", json);
Ok(())
}
63 changes: 63 additions & 0 deletions tests/ckb_js_tests/templates/load_script.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
{
"mock_info": {
"inputs": [
{
"output": {
"capacity": "0x10000000",
"lock": {
"args": "0x0100{{ ref_type js-code-file }}01",
"code_hash": "0x{{ ref_type ckb-js-vm }}",
"hash_type": "type"
},
"type": null
},
"data": "0x"
}
],
"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 ../../../build/bytecode/load_script.fs }}"
}
],
"header_deps": []
},
"tx": {
"outputs": [
{
"capacity": "0x0",
"lock": {
"args": "0x00AE9DF3447C404A645BC48BEA4B7643B95AC5C3AE",
"code_hash": "0x{{ ref_type ckb-js-vm }}",
"hash_type": "type"
}
}
],
"witnesses": [
"0x55000000100000005500000055000000410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
],
"outputs_data": [
"0x"
]
}
}
9 changes: 9 additions & 0 deletions tests/ckb_js_tests/test_data/load_script/fib.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
function fib(n)
{
if (n <= 0)
return 0;
else if (n == 1)
return 1;
else
return fib(n - 1) + fib(n - 2);
}
10 changes: 10 additions & 0 deletions tests/ckb_js_tests/test_data/load_script/fib_module.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import * as ckb from 'ckb';
globalThis.fib2 = function(n) {
if (n <= 0)
return 0;
else if (n == 1)
return 1;
else
return fib(n - 1) + fib(n - 2);
}
// we can't write it like fib.js because the module feature is enabled.
11 changes: 11 additions & 0 deletions tests/ckb_js_tests/test_data/load_script/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import * as ckb from 'ckb';

ckb.loadScript('fib.js');
const result = fib(10);
console.assert(result == 55, "fib(10) != 55");

ckb.loadScript('fib_module.js', true);
console.assert(globalThis.fib2(10) == 55, "fib2(10) != 55");

let code = ckb.loadFile('fib.js');
console.assert(code.includes('function fib'), "load file failed");
1 change: 1 addition & 0 deletions tests/module/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ endef


all:
$(call run,test_ckb.js)
$(call run,test_secp256k1.js)
$(call run,test_hash.js)
$(call run,test_misc.js)
Loading
Loading