This repository keeps a series of CKB script templates that can be inflated via cargo-generate. Those templates enable a native development flow on mainstream Linux, macOS and Windows machines using stock version of latest stable Rust & clang C compiler.
molecule starting from 0.8.0, switches to bytes instead of Vec
internally to keep data in no_std
environment. However, bytes would require atomic builtins so as to function, this could lead to unsupported CKB-VM instructions (RISC-V A extension instructions to be precise) being generated. There are 2 ways to solve this issue:
- Enable
dummy-atomic
feature inckb-std
crate, also use make sureFULL_RUSTFLAGS
in the contract makefile is updated so-a
is included, for example:FULL_RUSTFLAGS := -C target-feature=+zba,+zbb,+zbc,+zbs,-a $(CUSTOM_RUSTFLAGS)
. Or see this PR for how to changeRUSTFLAGS
. - Enable
bytes_vec
feature inmolecule
crate
ckb-gen-types starting from 0.117.0 is also affected, since ckb-gen-types
has upgraded to molecule
0.8.0 in this version.
The following dependencies are required for the templates:
git
,make
,sed
,bash
,sha256sum
and others Unix utilities. Refer to the documentation for your operating systems for how to install them. Chances are your system might already have them.Rust
: latest stable Rust installed via rustup should work. Make sure you haveriscv64
target installed via:rustup target add riscv64imac-unknown-none-elf
Clang
: make sure you have clang 16+ installed, sample installtion steps for selected platforms are:- Debian / Ubuntu:
wget https://apt.llvm.org/llvm.sh && chmod +x llvm.sh && sudo ./llvm.sh 16 && rm llvm.sh
- Fedora 39+:
sudo dnf -y install clang
- Archlinux:
sudo pacman --noconfirm -Syu clang
- macOS:
brew install llvm@16
- Windows(with [Scoop](scoop install llvm yasm)):
scoop install llvm yasm
- Debian / Ubuntu:
cargo-generate
: You can install this viacargo install cargo-generate
, or follow the steps here
To generate a workspace template, use the following command:
$ cargo generate gh:cryptape/ckb-script-templates workspace
⚠️ Favorite `gh:cryptape/ckb-script-templates` not found in config, using it as a git repository: https://github.com/cryptape/ckb-script-templates.git
🤷 Project Name: my-first-contract-workspace
🔧 Destination: /tmp/my-first-contract-workspace ...
🔧 project-name: my-first-contract-workspace ...
🔧 Generating template ...
🔧 Moving generated files into: `/tmp/my-first-contract-workspace`...
🔧 Initializing a fresh Git repository
✨ Done! New project created /tmp/my-first-contract-workspace
Or you can manually specify the name and skip the prompt:
$ cargo generate gh:cryptape/ckb-script-templates workspace --name my-first-contract-workspace
⚠️ Favorite `gh:cryptape/ckb-script-templates` not found in config, using it as a git repository: https://github.com/cryptape/ckb-script-templates.git
🔧 Destination: /tmp/my-first-contract-workspace ...
🔧 project-name: my-first-contract-workspace ...
🔧 Generating template ...
🔧 Moving generated files into: `/tmp/my-first-contract-workspace`...
🔧 Initializing a fresh Git repository
✨ Done! New project created /tmp/my-first-contract-workspace
This is probably the only longer command you will deal with when using the templates repository. You can save them as an alias in your shell:
$ alias create-ckb-scripts="cargo generate gh:cryptape/ckb-script-templates workspace"
$ create-ckb-scripts
⚠️ Favorite `gh:cryptape/ckb-script-templates` not found in config, using it as a git repository: https://github.com/cryptape/ckb-script-templates.git
🤷 Project Name: my-first-contract-workspace
🔧 Destination: /tmp/my-first-contract-workspace ...
🔧 project-name: my-first-contract-workspace ...
🔧 Generating template ...
🔧 Moving generated files into: `/tmp/my-first-contract-workspace`...
🔧 Initializing a fresh Git repository
✨ Done! New project created /tmp/my-first-contract-workspace
First, navigate to the workspace directory you just created:
$ cd my-first-contract-workspace
First thing you can notice, is that we have created a standard Rust workspace project:
$ tree .
.
├── Cargo.toml
├── Makefile
├── scripts
│ └── find_clang
└── tests
├── Cargo.toml
└── src
├── lib.rs
└── tests.rs
4 directories, 6 files
The only exception here, is that a Makefile
, together with a scripts
folder has been created to simplify contract building.
We can use make generate
to create our first contract:
$ make generate
🤷 Project Name: first-contract
🔧 Destination: /tmp/my-first-contract-workspace/contracts/first-contract ...
🔧 project-name: first-contract ...
🔧 Generating template ...
🔧 Moving generated files into: `/tmp/my-first-contract-workspace/contracts/first-contract`...
🔧 Initializing a fresh Git repository
✨ Done! New project created /tmp/my-first-contract-workspace/contracts/first-contract
You can also supply the contract create name when executing the make task:
$ make generate CRATE=second-contract
🔧 Destination: /tmp/my-first-contract-workspace/contracts/second-contract ...
🔧 project-name: second-contract ...
🔧 Generating template ...
🔧 Moving generated files into: `/tmp/my-first-contract-workspace/contracts/second-contract`...
🔧 Initializing a fresh Git repository
✨ Done! New project created /tmp/my-first-contract-workspace/contracts/second-contract
By default, the newly created crate is using contract template, which is put into contracts
sub-folder, the workspace-level Makefile
assumes all Rust contracts are stored in contracts
folder, and treat crates stored in other folders as dependency-only Rust crates.
But chances are you would want to tweak the default settings in certain scenarios:
- Sometimes you want to use a different template as a contract starting point other than
contract
template. - Sometimes you want to use a different template since you are generating a plain Rust crate, which will also likely be put in a different subfolder other than
contracts
- Note: while you could technically put a Rust contract in a different subfolder other than
contracts
, we don't really recommend this, since the workspace-levelMakefile
is leveraging the convention that all CKB contracts live incontracts
folder.
You can do this by customizing parameters to make generate
:
$ make generate TEMPLATE=atomics-contract # generate a Rust contract crate in contracts subfolder, but use atomics-contract template
$ make DESTINATION=crates # generate a crate in crates folder, and still use the default contract template
$ make generate TEMPLATE=c-wrapper-crate DESTINATION=crates # generate a crate in crates folder, but use c-wrapper-crate template
Ready-to-use templates have been put together for different use cases:
contract
: default contract template you should use if no special requirements are neeeded.stack-reorder-contract
: a contract template that adjusts memory layout so stack lives at lower address, and heap lives at higher address. This way a program would explicitly signal errors when stack space is fully use.c-wrapper-crate
: a crate template that shows how to glue C code in a Rust crate for CKB's contract usage.x64-simulator-crate
: a crate template that contains Rust-only code, but usees ckb-x64-simulator for tests.
There are also deprecated templates kept for historical reasons.
atomics-contract
: a contract template that supports atomic builtins without requiring RISC-V A extension. This template allows you to uselog
,bytes
crate or other code that might deal with atomics. Note that starting from ckb-std v0.16.0, atomic builtins are provided in ckb-std by default. For Rust based scripts, relying the builtin implementation in ckb-std might be better and more smooth idea. We are still keeping this template for now as a hint for a C based solution. It might be removed in future versions.
Certain template might require external modules to be available, for example:
- All C code would require ckb-c-stdlib:
git submodule add https://github.com/nervosnetwork/ckb-c-stdlib deps/ckb-c-stdlib
atomics-contract
requires lib-dummy-atomics:git submodule add https://github.com/xxuejie/lib-dummy-atomics deps/lib-dummy-atomics
stack-reorder-contract
requires ckb-stack-reorg-bootloader:git submodule add https://github.com/xxuejie/ckb-stack-reorg-bootloader deps/ckb-stack-reorg-bootloader
In future versions, we might leverage cargo-generate hooks to add submodules automatically, but for now, manual steps are required.
Note that when you supply the contract crate name, our Makefile
will be smart enough to automatically insert the crate in to Cargo.toml
file, so you don't have to edit it manually.
Now you can build the contracts(or adjust parameters):
$ make build
$ make build MODE=debug # for debug build
$ make build CUSTOM_RUSTFLAGS="" # release build without debug assertions
$ make build CARGO_ARGS="--verbose" # release build with `--verbose` attached to cargo command, you can use other arguments accepted by cargo
$ make build CONTRACT=second-contract # build a single contract
$ make build CLEAN_BUILD_DIR_FIRST=false # keep old untouched binaries
$ make build CLANG=clang-17 # use a specific clang version to build C code
We have prepared a tests
crate where you can also add contract tests. If you want to run the tests, run the following command:
make test
The templates provided here, use the same conventions as ckb-native-build-sample
project, so feel free to refer to the more detailed usage doc in the sample project.
When using this set of templates, we always recommend to use locally installed native versions of LLVM & Rust to build and test your scripts. However, reproducible build is an important part of CKB scripts, which would require locked versions of LLVM & Rust to work. It might not be an easy task when using locally installed versions of compilers.
For the time being, we have prepared a script that does reproducible build via a docker container image. We do want to mention that docker is not necessarily THE way to do reproducible build, nor is it the best way to do reproducible build. There might well be other ways that are better, such as chroot or Nix. It's just that historically, docker has been used in CKB script's build process, and adding a script leveraging docker here, provides an easy solution into the issue.
To do reproducible build, you can use the included script with varying commands:
$ ./scripts/reproducible_build_docker # Clean current repository, used locked LLVM & Rust from a docker container
# to build all contracts, then test the binaries against a checksum file.
$ ./scripts/reproducible_build_docker --update # Update the checksum file with new binaries, could be handy when you have
# made changes to the binaries.
$ ./scripts/reproducible_build_docker --no-clean # Do not clean intermediate files before building, it is not recommended to
# use this but when you really know what you are doing, it could help you save
# some time.
$ ./scripts/reproducible_build_docker --proxy "..." # Setup docker container so it pulls Rust crates using a proxy server
By default, the checksum file is stored in checksums.txt
in the root of the repository. It is strongly recommended that this file is checked into version control, and a CI is setup so reproducible build is always checked in new commits.
In rare cases if you want to simply use a standalone contract crate without a workspace. The standalone-contract template is prepared for you:
$ cargo generate gh:cryptape/ckb-script-templates standalone-contract
⚠️ Favorite `gh:cryptape/ckb-script-templates` not found in config, using it as a git repository: https://github.com/cryptape/ckb-script-templates.git
🤷 Project Name: standalone-first-contract
🔧 Destination: /tmp/standalone-first-contract ...
🔧 project-name: standalone-first-contract ...
🔧 Generating template ...
🔧 Moving generated files into: `/tmp/standalone-first-contract`...
🔧 Initializing a fresh Git repository
✨ Done! New project created /tmp/standalone-first-contract
You can then build and test the standalone crate as usual:
$ cd standalone-first-contract
$ make build
$ make test
$ make check
$ make clippy
The template is tailored built for usage outside of workspace, typically, it is not expected to be used inside a workspace. Feel free to compare it with the default contract
workspace for differences.
This standalone template also has its own test setup, where in a workspace, a dedicated tests
crate will handle most of the testing work.