This article introduces the ckb-contract-std
library; and shows how to rewrite our minimal contract with ckb-contract-std
, to enables syscalls and Vec
, String
.
The previous contract:
#![no_std]
#![no_main]
#![feature(asm)]
#![feature(lang_items)]
#[no_mangle]
pub fn _start() -> ! {
exit(0)
}
/// Exit syscall
pub fn exit(_code: i8) -> ! {
unsafe {
// a0 is _code
asm!("li a7, 93");
asm!("ecall");
}
loop {}
}
#[panic_handler]
fn panic_handler(_: &core::panic::PanicInfo) -> ! {
loop {}
}
#[lang = "eh_personality"]
extern "C" fn eh_personality() {}
#[no_mangle]
pub fn abort() -> ! {
panic!("abort!")
}
Makefile
We compile and run tests again and again in the previous article, for convenient, letās write a Makefile
first:
test: clean build patch
cargo test -- --nocapture
build:
cd contract && cargo build
clean:
cd contract && cargo clean
C := contract/target/riscv64imac-unknown-none-elf/debug/contract
patch:
ckb-binary-patcher -i $C -o $C
The make test
is simple: rebuild the contract binary then run unit tests.
It is worth to notice the patch
task, which calls ckb-binary-patcher
to patch the contract binary; Its a solution for fixing VM buggy instructions, even we developed the CKB-VM diligently, there no bug-free software. As the nature of blockchain, we canāt just fix the VM itself without a hard-fork. A better approach is to patch binary to get across the buggy instruction. You can see this issue for details.
Installing the ckb-binary-patcher
:
cargo install --git https://github.com/xxuejie/ckb-binary-patcher.git
Then type make test
to compile and test contract.
Hidden complexity under macro
Now letās get back to our contract, the code is little complicated for a āhello worldā. Letās slim it, we begin with wrapping _start
function:
#[no_mangle]
pub extern "C" fn _start() -> ! {
exit(main())
}
pub fn main() -> i8 {
// code...
0
}
Now we can write code in the main
function, thatās looking more comfortable, except the _start
function is annoying; we can use a macro to hide the _start
:
#[macro_export]
macro_rules! setup {
($main:path) => {
#[no_mangle]
pub extern "C" fn _start() -> ! {
let f: fn() -> i8 = $main;
ckb_contract_std::syscalls::exit(f())
}
}
}
The setup
macro defines the start
function that the _start
just calls main then exits the program with syscall exit
; our contract is below:
pub fn main() -> i8 {
// code...
0
}
setup!(main);
The Rust macro system is powerful, we can hidden other annoying functions and definitions under the macro; it is the basic idea of ckb-contrac-std
; letās rewrite the whole contract:
#![no_std]
#![no_main]
#![feature(lang_items)]
#![feature(alloc_error_handler)]
#![feature(panic_info_message)]
use ckb_contract_std::setup;
#[no_mangle]
pub fn main() -> i8 {
// code...
0
}
setup!(main);
This code looks suitable for a āhello worldā program. The rustc
requires the definition of the features in the file, so we still need to keep them, but we hide the other functions include a well-implemented panic handler and a global allocator in the setup
macro.
Rewrite contract
Letās try using Vec
, String
from alloc crate, and use the debug syscall to output under the test environment.
/// features...
use alloc::vec;
use ckb_contract_std::{debug, setup};
#[no_mangle]
pub fn main() -> i8 {
let v = vec![0u8; 42];
debug!("{:?}", v.len());
0
}
setup!(main);
// We can see the debug output under test environment.
// ->
// 0x423f33c96845ca512f4c9e9b19015481a4a8db1cf56dd1ddff8cecc17c38ac5d [contract debug] 42
References:
original post https://justjjy.com/CKB-contract-in-Rust-part-2-Rewrite-contract-with-ckb