Skip to main content

Exec syscall in Capsule

Introduction

Exec is a new syscall provided by ckb2021. To understand what exec syscall does, we recommend reading this article first. In short: Exec runs an executable file from specified cell data in the context of an already existing VM, replacing the previous executable. The used cycles do not change, but the code, registers, and memory of the VM are replaced by those of the new program, meaning control flow will never return to the main script.

You can imagine exec as a router. When some conditions are met, the main script will completely hand over control to a certain sub-script.

              ┌--> if State1 --> Exec(Sub-script1)
Main script --+--> if State2 --> Exec(Sub-script2)
└--> if State3 --> Exec(Sub-script3)

Compared with Dynamic libraries, exec has the following significant advantages:

  • All sub-scripts are complete scripts. They can be used alone, or they can be called by exec.
  • Sub-scripts have a separate 4M memory space.

At the same time Exec has the following limitations:

  • Never return.
  • Hard to exchange data between scripts.

When dynamic libraries and exec syscall both meet your needs, we recommend that you use exec instead of dynamic libraries.

In this tutorial, we'll write two scripts in Rust, and exec one script into the other.

Setup the develop environment

Install capsule

The installation steps can refer to here.

Create a project

$ capsule new ckb-exec-demo
(click here to view response)
New project "ckb-exec-demo"
Created file "capsule.toml"
Created file "deployment.toml"
Created file "README.md"
Created file "Cargo.toml"
Created file ".gitignore"
Initialized empty Git repository in /tmp/ckb-exec-demo/.git/
Created "/tmp/ckb-exec-demo"
Created tests
Created library `tests` package
New contract "ckb-exec-demo"
Created binary (application) `ckb-exec-demo` package
Rewrite Cargo.toml
Rewrite capsule.toml
Done

Let's create the second contract named always-failure.

$ cd ckb-exec-demo
$ capsule new-contract always-failure
(click here to view response)
New contract "always-failure"
Created binary (application) `always-failure` package
Rewrite Cargo.toml
Rewrite capsule.toml
Done

Write always-failure sub-script

Put the following code into contracts/always-failure/main.rs. As you can see, the script always returns 42, which means that if the script is used as a lock script, the cell will never be unlocked.

fn program_entry(_argc: u64, _argv: *const *const u8) -> i8 {
return 42;
}

Write exec demo script

Put the following code into contracts/ckb-exec-demo/main.rs.

fn program_entry(_argc: u64, _argv: *const *const u8) -> i8 {
let r = exec(0, Source::CellDep, 0, 0, &[]);
if r != 0 {
// Call exec syscall failed.
return 10;
}
return 0;
}

This script does only one thing: When executing exec(0, Source::CellDep, 0, 0, &[]), CKB-VM will look for the first dep_cell, and execute the code in it.

Testing

We need to deploy the always-failure to a cell, then reference the cell in the testing transaction. Open tests/src/tests.rs:

let always_failure_bin = {
let mut buf = Vec::new();
File::open("../build/debug/always-failure")
.unwrap()
.read_to_end(&mut buf)
.expect("read code");
Bytes::from(buf)
};
let always_failure_out_point = context.deploy_cell(always_failure_bin);
let always_failure_dep = CellDep::new_builder()
.out_point(always_failure_out_point)
.build();

// build transaction
let tx = TransactionBuilder::default()
.input(input)
.outputs(outputs)
.outputs_data(outputs_data.pack())
.cell_dep(lock_script_dep)
// reference to always-failure cell
.cell_dep(always_failure_dep)
.build();
}

let err = context.verify_tx(&tx, MAX_CYCLES).unwrap_err();
// check the return code is 42
assert_script_error(err, 42);

Run capsule test.

(click here to view response)
Finished test [unoptimized + debuginfo] target(s) in 1.71s
Running unittests src/lib.rs (target/debug/deps/tests-c051885699f8b848)
running 1 test
test tests::test_success ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.52s

Other resources

In this article, we use the index to locate sub-scripts. If you want to use the script hash to locate, you can refer to our: