├── .github └── workflows │ └── rust.yml ├── README.md ├── atomics-contract ├── .cargo-generate │ ├── README.md │ └── tests.rs ├── .gitignore ├── Cargo.toml ├── Makefile ├── README.md ├── build.rs ├── cargo-generate.toml └── src │ └── main.rs ├── c-wrapper-crate ├── Cargo.toml ├── README.md ├── build.rs ├── c.c ├── cargo-generate.toml └── src │ └── lib.rs ├── cargo-generate.toml ├── contract-without-simulator ├── .cargo-generate │ ├── README.md │ └── tests.rs ├── .gitignore ├── Cargo.toml ├── Makefile ├── README.md ├── cargo-generate.toml └── src │ └── main.rs ├── contract ├── .cargo-generate │ ├── README.md │ └── tests.rs ├── .gitignore ├── Cargo.toml ├── Makefile ├── README.md ├── cargo-generate.toml └── src │ ├── lib.rs │ └── main.rs ├── native-simulator ├── .gitignore ├── Cargo.toml ├── README.md ├── cargo-generate.toml └── src │ └── lib.rs ├── stack-reorder-contract ├── .cargo-generate │ ├── README.md │ └── tests.rs ├── .gitignore ├── Cargo.toml ├── Makefile ├── README.md ├── bootloader.S ├── build.rs ├── cargo-generate.toml ├── ld_interface.ld └── src │ └── main.rs ├── standalone-contract ├── .gitignore ├── Cargo.toml ├── Makefile ├── README.md ├── cargo-generate.toml ├── scripts │ ├── find_clang │ └── reproducible_build_docker └── src │ ├── main.rs │ └── tests │ ├── mod.rs │ └── tests.rs ├── workspace ├── .gitignore ├── Cargo.toml ├── Makefile ├── README.md ├── cargo-generate.toml ├── scripts │ ├── find_clang │ └── reproducible_build_docker └── tests │ ├── Cargo.toml │ └── src │ ├── lib.rs │ └── tests.rs └── x64-simulator-crate ├── Cargo.toml ├── README.md ├── cargo-generate.toml └── src └── lib.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | ubuntu-build: 14 | 15 | strategy: 16 | matrix: 17 | os: [ubuntu-latest, ubuntu-24.04-arm] 18 | 19 | runs-on: ${{ matrix.os }} 20 | 21 | steps: 22 | - uses: actions/checkout@v3 23 | - name: Install llvm 24 | run: sudo apt update && sudo apt install -y clang llvm # This requires Ubuntu 24.04 or later 25 | - uses: actions-rust-lang/setup-rust-toolchain@v1 26 | - name: Install cargo generate 27 | run: cargo install cargo-generate 28 | - name: Generate workspace 29 | run: cargo generate --path . workspace --name test-workspace 30 | - name: Generate crates && contracts 31 | run: cd test-workspace && 32 | make generate CRATE=clib TEMPLATE=c-wrapper-crate DESTINATION=crates TEMPLATE_TYPE=--path TEMPLATE_REPO=.. && 33 | make generate CRATE=rlib TEMPLATE=x64-simulator-crate DESTINATION=crates TEMPLATE_TYPE=--path TEMPLATE_REPO=.. && 34 | make generate CRATE=c1 TEMPLATE=contract TEMPLATE_TYPE=--path TEMPLATE_REPO=.. && 35 | make generate CRATE=c2 TEMPLATE=atomics-contract TEMPLATE_TYPE=--path TEMPLATE_REPO=.. && 36 | make generate CRATE=c3 TEMPLATE=stack-reorder-contract TEMPLATE_TYPE=--path TEMPLATE_REPO=.. && 37 | make generate CRATE=c4 TEMPLATE=contract-without-simulator TEMPLATE_TYPE=--path TEMPLATE_REPO=.. 38 | - name: Submodules 39 | run: cd test-workspace && 40 | git submodule add https://github.com/nervosnetwork/ckb-c-stdlib deps/ckb-c-stdlib && 41 | git submodule add https://github.com/xxuejie/lib-dummy-atomics deps/lib-dummy-atomics 42 | - name: Lock Rust version 43 | run: cd test-workspace && echo "1.81.0" > rust-toolchain 44 | - name: Install riscv64 target 45 | run: cd test-workspace && rustup target add riscv64imac-unknown-none-elf 46 | - name: Run all checks 47 | run: cd test-workspace && make build test check clippy 48 | - name: Reproducible build runs 49 | run: cd test-workspace && ./scripts/reproducible_build_docker --update && ./scripts/reproducible_build_docker --no-clean 50 | - name: Generate standalone contract 51 | run: cargo generate --path . standalone-contract --name test-contract 52 | - name: Lock Rust version 53 | run: cd test-contract && echo "1.81.0" > rust-toolchain 54 | - name: Run all checks 55 | run: cd test-contract && make build test check clippy 56 | - name: Reproducible build runs 57 | run: cd test-contract && ./scripts/reproducible_build_docker --update && ./scripts/reproducible_build_docker --no-clean 58 | 59 | debian-build: 60 | 61 | runs-on: ubuntu-latest 62 | container: 63 | image: debian:latest 64 | env: 65 | USER: ${{ github.actor }} 66 | 67 | steps: 68 | - name: Install dependencies 69 | run: apt-get update && apt-get -y install curl git build-essential pkg-config libssl-dev lsb-release wget software-properties-common gnupg 70 | - name: Install llvm 18 71 | run: wget https://apt.llvm.org/llvm.sh && chmod +x llvm.sh && ./llvm.sh 18 && rm llvm.sh 72 | - uses: actions-rust-lang/setup-rust-toolchain@v1 73 | with: 74 | components: clippy 75 | target: riscv64imac-unknown-none-elf 76 | - uses: actions/checkout@v3 77 | with: 78 | submodules: true 79 | - name: Install cargo generate 80 | run: cargo install cargo-generate 81 | - name: Generate workspace 82 | run: cargo generate --path . workspace --name test-workspace 83 | - name: Generate crates && contracts 84 | run: cd test-workspace && 85 | make generate CRATE=clib TEMPLATE=c-wrapper-crate DESTINATION=crates TEMPLATE_TYPE=--path TEMPLATE_REPO=.. && 86 | make generate CRATE=rlib TEMPLATE=x64-simulator-crate DESTINATION=crates TEMPLATE_TYPE=--path TEMPLATE_REPO=.. && 87 | make generate CRATE=c1 TEMPLATE=contract TEMPLATE_TYPE=--path TEMPLATE_REPO=.. && 88 | make generate CRATE=c2 TEMPLATE=atomics-contract TEMPLATE_TYPE=--path TEMPLATE_REPO=.. && 89 | make generate CRATE=c3 TEMPLATE=stack-reorder-contract TEMPLATE_TYPE=--path TEMPLATE_REPO=.. && 90 | make generate CRATE=c4 TEMPLATE=contract-without-simulator TEMPLATE_TYPE=--path TEMPLATE_REPO=.. 91 | - name: Submodules 92 | run: cd test-workspace && 93 | git submodule add https://github.com/nervosnetwork/ckb-c-stdlib deps/ckb-c-stdlib && 94 | git submodule add https://github.com/xxuejie/lib-dummy-atomics deps/lib-dummy-atomics 95 | - name: Run all checks 96 | run: cd test-workspace && make build test check clippy 97 | - name: Generate standalone contract 98 | run: cargo generate --path . standalone-contract --name test-contract 99 | - name: Run all checks 100 | run: cd test-contract && make build test check clippy 101 | 102 | fedora-build: 103 | 104 | runs-on: ubuntu-latest 105 | container: 106 | image: fedora:latest 107 | env: 108 | USER: ${{ github.actor }} 109 | 110 | steps: 111 | - name: Install dependencies 112 | run: sudo dnf -y install clang llvm git make openssl-devel 113 | - uses: actions-rust-lang/setup-rust-toolchain@v1 114 | with: 115 | components: clippy 116 | target: riscv64imac-unknown-none-elf 117 | - uses: actions/checkout@v3 118 | with: 119 | submodules: true 120 | - name: Install cargo generate 121 | run: cargo install cargo-generate 122 | - name: Generate workspace 123 | run: cargo generate --path . workspace --name test-workspace 124 | - name: Generate crates && contracts 125 | run: cd test-workspace && 126 | make generate CRATE=clib TEMPLATE=c-wrapper-crate DESTINATION=crates TEMPLATE_TYPE=--path TEMPLATE_REPO=.. && 127 | make generate CRATE=rlib TEMPLATE=x64-simulator-crate DESTINATION=crates TEMPLATE_TYPE=--path TEMPLATE_REPO=.. && 128 | make generate CRATE=c1 TEMPLATE=contract TEMPLATE_TYPE=--path TEMPLATE_REPO=.. && 129 | make generate CRATE=c2 TEMPLATE=atomics-contract TEMPLATE_TYPE=--path TEMPLATE_REPO=.. && 130 | make generate CRATE=c3 TEMPLATE=stack-reorder-contract TEMPLATE_TYPE=--path TEMPLATE_REPO=.. && 131 | make generate CRATE=c4 TEMPLATE=contract-without-simulator TEMPLATE_TYPE=--path TEMPLATE_REPO=.. 132 | - name: Submodules 133 | run: cd test-workspace && 134 | git submodule add https://github.com/nervosnetwork/ckb-c-stdlib deps/ckb-c-stdlib && 135 | git submodule add https://github.com/xxuejie/lib-dummy-atomics deps/lib-dummy-atomics 136 | - name: Run all checks 137 | run: cd test-workspace && make build test check clippy 138 | - name: Generate standalone contract 139 | run: cargo generate --path . standalone-contract --name test-contract 140 | - name: Run all checks 141 | run: cd test-contract && make build test check clippy 142 | 143 | arch-build: 144 | 145 | runs-on: ubuntu-latest 146 | container: 147 | image: archlinux:latest 148 | env: 149 | USER: ${{ github.actor }} 150 | 151 | steps: 152 | - name: Install dependencies 153 | run: pacman --noconfirm -Syu clang llvm git make openssl pkgconf 154 | - uses: actions-rust-lang/setup-rust-toolchain@v1 155 | with: 156 | components: clippy 157 | target: riscv64imac-unknown-none-elf 158 | - uses: actions/checkout@v3 159 | with: 160 | submodules: true 161 | - name: Install cargo generate 162 | run: cargo install cargo-generate 163 | - name: Generate workspace 164 | run: cargo generate --path . workspace --name test-workspace 165 | - name: Generate crates && contracts 166 | run: cd test-workspace && 167 | make generate CRATE=clib TEMPLATE=c-wrapper-crate DESTINATION=crates TEMPLATE_TYPE=--path TEMPLATE_REPO=.. && 168 | make generate CRATE=rlib TEMPLATE=x64-simulator-crate DESTINATION=crates TEMPLATE_TYPE=--path TEMPLATE_REPO=.. && 169 | make generate CRATE=c1 TEMPLATE=contract TEMPLATE_TYPE=--path TEMPLATE_REPO=.. && 170 | make generate CRATE=c2 TEMPLATE=atomics-contract TEMPLATE_TYPE=--path TEMPLATE_REPO=.. && 171 | make generate CRATE=c3 TEMPLATE=stack-reorder-contract TEMPLATE_TYPE=--path TEMPLATE_REPO=.. && 172 | make generate CRATE=c4 TEMPLATE=contract-without-simulator TEMPLATE_TYPE=--path TEMPLATE_REPO=.. 173 | - name: Submodules 174 | run: cd test-workspace && 175 | git submodule add https://github.com/nervosnetwork/ckb-c-stdlib deps/ckb-c-stdlib && 176 | git submodule add https://github.com/xxuejie/lib-dummy-atomics deps/lib-dummy-atomics 177 | - name: Run all checks 178 | run: cd test-workspace && make build test check clippy 179 | - name: Generate standalone contract 180 | run: cargo generate --path . standalone-contract --name test-contract 181 | - name: Run all checks 182 | run: cd test-contract && make build test check clippy 183 | 184 | macos-build: 185 | 186 | runs-on: macos-latest 187 | 188 | steps: 189 | - uses: actions/checkout@v3 190 | with: 191 | submodules: true 192 | - name: Install llvm 18 193 | run: brew install llvm@18 194 | - name: Install riscv64 target 195 | run: rustup target add riscv64imac-unknown-none-elf 196 | - name: Install cargo generate 197 | run: cargo install cargo-generate 198 | - name: Generate workspace 199 | run: cargo generate --path . workspace --name test-workspace 200 | - name: Generate crates && contracts 201 | run: cd test-workspace && 202 | make generate CRATE=clib TEMPLATE=c-wrapper-crate DESTINATION=crates TEMPLATE_TYPE=--path TEMPLATE_REPO=.. && 203 | make generate CRATE=rlib TEMPLATE=x64-simulator-crate DESTINATION=crates TEMPLATE_TYPE=--path TEMPLATE_REPO=.. && 204 | make generate CRATE=c1 TEMPLATE=contract TEMPLATE_TYPE=--path TEMPLATE_REPO=.. && 205 | make generate CRATE=c2 TEMPLATE=atomics-contract TEMPLATE_TYPE=--path TEMPLATE_REPO=.. && 206 | make generate CRATE=c3 TEMPLATE=stack-reorder-contract TEMPLATE_TYPE=--path TEMPLATE_REPO=.. && 207 | make generate CRATE=c4 TEMPLATE=contract-without-simulator TEMPLATE_TYPE=--path TEMPLATE_REPO=.. 208 | - name: Submodules 209 | run: cd test-workspace && 210 | git submodule add https://github.com/nervosnetwork/ckb-c-stdlib deps/ckb-c-stdlib && 211 | git submodule add https://github.com/xxuejie/lib-dummy-atomics deps/lib-dummy-atomics 212 | - name: Run all checks 213 | run: cd test-workspace && make build test check clippy 214 | - name: Generate standalone contract 215 | run: cargo generate --path . standalone-contract --name test-contract 216 | - name: Run all checks 217 | run: cd test-contract && make build test check clippy 218 | 219 | windows-build: 220 | 221 | runs-on: windows-2019 222 | 223 | steps: 224 | - uses: actions/checkout@v3 225 | with: 226 | submodules: true 227 | - name: Install llvm 228 | shell: pwsh 229 | # https://github.com/ScoopInstaller/Install#for-admin 230 | # 231 | # Note that on Windows we cannot choose LLVM 18 here, we have to settle on latest stable LLVM 232 | run: | 233 | iex "& {$(irm get.scoop.sh)} -RunAsAdmin" 234 | scoop install llvm yasm 235 | echo "$env:USERPROFILE\scoop\shims" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append 236 | - name: Install riscv64 target 237 | run: rustup target add riscv64imac-unknown-none-elf 238 | - name: Install cargo generate 239 | run: cargo install cargo-generate 240 | - name: Generate workspace 241 | run: cargo generate --path . workspace --name test-workspace 242 | - name: Generate crates && contracts 243 | run: cd test-workspace && 244 | make generate CRATE=clib TEMPLATE=c-wrapper-crate DESTINATION=crates TEMPLATE_TYPE=--path TEMPLATE_REPO=.. && 245 | make generate CRATE=rlib TEMPLATE=x64-simulator-crate DESTINATION=crates TEMPLATE_TYPE=--path TEMPLATE_REPO=.. && 246 | make generate CRATE=c1 TEMPLATE=contract TEMPLATE_TYPE=--path TEMPLATE_REPO=.. && 247 | make generate CRATE=c2 TEMPLATE=atomics-contract TEMPLATE_TYPE=--path TEMPLATE_REPO=.. && 248 | make generate CRATE=c3 TEMPLATE=stack-reorder-contract TEMPLATE_TYPE=--path TEMPLATE_REPO=.. && 249 | make generate CRATE=c4 TEMPLATE=contract-without-simulator TEMPLATE_TYPE=--path TEMPLATE_REPO=.. 250 | - name: Submodules 251 | run: cd test-workspace && 252 | git submodule add https://github.com/nervosnetwork/ckb-c-stdlib deps/ckb-c-stdlib && 253 | git submodule add https://github.com/xxuejie/lib-dummy-atomics deps/lib-dummy-atomics 254 | - name: Run all checks 255 | run: cd test-workspace && make build test check clippy 256 | - name: Generate standalone contract 257 | run: cargo generate --path . standalone-contract --name test-contract 258 | - name: Run all checks 259 | run: cd test-contract && make build test check clippy 260 | 261 | freebsd-build: 262 | 263 | runs-on: ubuntu-latest 264 | 265 | steps: 266 | - uses: actions/checkout@v3 267 | with: 268 | submodules: true 269 | - name: Build in FreeBSD VM 270 | uses: vmactions/freebsd-vm@v1 271 | with: 272 | usesh: true 273 | prepare: | 274 | set -ex 275 | pkg install -y llvm18 git gmake bash 276 | curl https://sh.rustup.rs -sSf | sh -s -- -y 277 | . $HOME/.cargo/env 278 | rustup target add riscv64imac-unknown-none-elf 279 | cargo install cargo-generate 280 | run: | 281 | set -ex 282 | . $HOME/.cargo/env 283 | cargo generate --path . standalone-contract --name test-contract 284 | cd test-contract 285 | gmake build test check clippy 286 | cd .. 287 | cargo generate --path . workspace --name test-workspace 288 | cd test-workspace 289 | gmake generate CRATE=clib TEMPLATE=c-wrapper-crate DESTINATION=crates TEMPLATE_TYPE=--path TEMPLATE_REPO=.. 290 | gmake generate CRATE=rlib TEMPLATE=x64-simulator-crate DESTINATION=crates TEMPLATE_TYPE=--path TEMPLATE_REPO=.. 291 | gmake generate CRATE=c1 TEMPLATE=contract TEMPLATE_TYPE=--path TEMPLATE_REPO=.. 292 | gmake generate CRATE=c2 TEMPLATE=atomics-contract TEMPLATE_TYPE=--path TEMPLATE_REPO=.. 293 | gmake generate CRATE=c3 TEMPLATE=stack-reorder-contract TEMPLATE_TYPE=--path TEMPLATE_REPO=.. 294 | gmake generate CRATE=c4 TEMPLATE=contract-without-simulator TEMPLATE_TYPE=--path TEMPLATE_REPO=.. 295 | git submodule add https://github.com/nervosnetwork/ckb-c-stdlib deps/ckb-c-stdlib 296 | git submodule add https://github.com/xxuejie/lib-dummy-atomics deps/lib-dummy-atomics 297 | gmake build test check clippy 298 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ckb-script-templates 2 | 3 | This repository keeps a series of CKB script templates that can be inflated via [cargo-generate](https://github.com/cargo-generate/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. 4 | 5 | ## Noticeable Changes 6 | 7 | ### Molecule uses bytes crates 8 | 9 | [molecule](https://github.com/nervosnetwork/molecule) starting from 0.8.0, switches to [bytes](https://crates.io/crates/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: 10 | 11 | * Enable `dummy-atomic` feature in `ckb-std` crate, also use make sure `FULL_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](https://github.com/cryptape/ckb-script-templates/pull/17) for how to change `RUSTFLAGS`. 12 | * Enable `bytes_vec` feature in `molecule` crate 13 | 14 | [ckb-gen-types](https://crates.io/crates/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. 15 | 16 | ## Usage 17 | 18 | ### Dependencies 19 | 20 | The following dependencies are required for the templates: 21 | 22 | * `git`, `make`, `sed`, `bash`, `shasum` 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. 23 | * `Rust`: latest stable Rust installed via [rustup](https://rustup.rs/) should work. Make sure you have `riscv64` target installed via: `rustup target add riscv64imac-unknown-none-elf` 24 | * `Clang`: make sure you have clang 18+ installed, sample installtion steps for selected platforms are: 25 | + Debian / Ubuntu: `wget https://apt.llvm.org/llvm.sh && chmod +x llvm.sh && sudo ./llvm.sh 18 && rm llvm.sh` 26 | + Fedora 39+: `sudo dnf -y install clang` 27 | + Archlinux: `sudo pacman --noconfirm -Syu clang` 28 | + macOS: `brew install llvm@18` 29 | + Windows(with [Scoop](scoop install llvm yasm)): `scoop install llvm yasm` 30 | * `cargo-generate`: You can install this via `cargo install cargo-generate`, or follow the steps [here](https://cargo-generate.github.io/cargo-generate/installation.html) 31 | 32 | ### Creating workspace 33 | 34 | To generate a workspace template, use the following command: 35 | 36 | ``` 37 | $ cargo generate gh:cryptape/ckb-script-templates workspace 38 | ⚠️ Favorite `gh:cryptape/ckb-script-templates` not found in config, using it as a git repository: https://github.com/cryptape/ckb-script-templates.git 39 | 🤷 Project Name: my-first-contract-workspace 40 | 🔧 Destination: /tmp/my-first-contract-workspace ... 41 | 🔧 project-name: my-first-contract-workspace ... 42 | 🔧 Generating template ... 43 | 🔧 Moving generated files into: `/tmp/my-first-contract-workspace`... 44 | 🔧 Initializing a fresh Git repository 45 | ✨ Done! New project created /tmp/my-first-contract-workspace 46 | ``` 47 | 48 | Or you can manually specify the name and skip the prompt: 49 | 50 | ``` 51 | $ cargo generate gh:cryptape/ckb-script-templates workspace --name my-first-contract-workspace 52 | ⚠️ Favorite `gh:cryptape/ckb-script-templates` not found in config, using it as a git repository: https://github.com/cryptape/ckb-script-templates.git 53 | 🔧 Destination: /tmp/my-first-contract-workspace ... 54 | 🔧 project-name: my-first-contract-workspace ... 55 | 🔧 Generating template ... 56 | 🔧 Moving generated files into: `/tmp/my-first-contract-workspace`... 57 | 🔧 Initializing a fresh Git repository 58 | ✨ Done! New project created /tmp/my-first-contract-workspace 59 | ``` 60 | 61 | 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: 62 | 63 | ``` 64 | $ alias create-ckb-scripts="cargo generate gh:cryptape/ckb-script-templates workspace" 65 | $ create-ckb-scripts 66 | ⚠️ Favorite `gh:cryptape/ckb-script-templates` not found in config, using it as a git repository: https://github.com/cryptape/ckb-script-templates.git 67 | 🤷 Project Name: my-first-contract-workspace 68 | 🔧 Destination: /tmp/my-first-contract-workspace ... 69 | 🔧 project-name: my-first-contract-workspace ... 70 | 🔧 Generating template ... 71 | 🔧 Moving generated files into: `/tmp/my-first-contract-workspace`... 72 | 🔧 Initializing a fresh Git repository 73 | ✨ Done! New project created /tmp/my-first-contract-workspace 74 | ``` 75 | 76 | ### Generating contract crates in the workspace 77 | 78 | First, navigate to the workspace directory you just created: 79 | 80 | ``` 81 | $ cd my-first-contract-workspace 82 | ``` 83 | 84 | First thing you can notice, is that we have created a standard Rust workspace project: 85 | 86 | ``` 87 | $ tree . 88 | . 89 | ├── Cargo.toml 90 | ├── Makefile 91 | ├── scripts 92 | │   └── find_clang 93 | └── tests 94 | ├── Cargo.toml 95 | └── src 96 | ├── lib.rs 97 | └── tests.rs 98 | 99 | 4 directories, 6 files 100 | ``` 101 | 102 | The only exception here, is that a `Makefile`, together with a `scripts` folder has been created to simplify contract building. 103 | 104 | We can use `make generate` to create our first contract: 105 | 106 | ``` 107 | $ make generate 108 | 🤷 Project Name: first-contract 109 | 🔧 Destination: /tmp/my-first-contract-workspace/contracts/first-contract ... 110 | 🔧 project-name: first-contract ... 111 | 🔧 Generating template ... 112 | 🔧 Moving generated files into: `/tmp/my-first-contract-workspace/contracts/first-contract`... 113 | 🔧 Initializing a fresh Git repository 114 | ✨ Done! New project created /tmp/my-first-contract-workspace/contracts/first-contract 115 | ``` 116 | 117 | You can also supply the contract create name when executing the make task: 118 | 119 | ``` 120 | $ make generate CRATE=second-contract 121 | 🔧 Destination: /tmp/my-first-contract-workspace/contracts/second-contract ... 122 | 🔧 project-name: second-contract ... 123 | 🔧 Generating template ... 124 | 🔧 Moving generated files into: `/tmp/my-first-contract-workspace/contracts/second-contract`... 125 | 🔧 Initializing a fresh Git repository 126 | ✨ Done! New project created /tmp/my-first-contract-workspace/contracts/second-contract 127 | ``` 128 | 129 | By default, the newly created crate is using [contract](https://github.com/cryptape/ckb-script-templates/tree/main/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. 130 | 131 | But chances are you would want to tweak the default settings in certain scenarios: 132 | 133 | * Sometimes you want to use a different template as a contract starting point other than `contract` template. 134 | * 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` 135 | * **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-level `Makefile` is leveraging the convention that all CKB contracts live in `contracts` folder. 136 | 137 | You can do this by customizing parameters to `make generate`: 138 | 139 | ``` 140 | $ make generate TEMPLATE=atomics-contract # generate a Rust contract crate in contracts subfolder, but use atomics-contract template 141 | $ make DESTINATION=crates # generate a crate in crates folder, and still use the default contract template 142 | $ make generate TEMPLATE=c-wrapper-crate DESTINATION=crates # generate a crate in crates folder, but use c-wrapper-crate template 143 | ``` 144 | 145 | Ready-to-use templates have been put together for different use cases: 146 | 147 | * `contract`: default contract template you should use if no special requirements are neeeded. 148 | * `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. 149 | * `c-wrapper-crate`: a crate template that shows how to glue C code in a Rust crate for CKB's contract usage. 150 | * `x64-simulator-crate`: a crate template that contains Rust-only code, but usees [ckb-x64-simulator](https://github.com/nervosnetwork/ckb-x64-simulator) for tests. 151 | 152 | There are also deprecated templates kept for historical reasons. 153 | 154 | * `atomics-contract`: a contract template that supports atomic builtins without requiring RISC-V A extension. This template allows you to use `log`, `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. 155 | 156 | Certain template might require external modules to be available, for example: 157 | 158 | * All C code would require [ckb-c-stdlib](https://github.com/nervosnetwork/ckb-c-stdlib): `git submodule add https://github.com/nervosnetwork/ckb-c-stdlib deps/ckb-c-stdlib` 159 | * `atomics-contract` requires [lib-dummy-atomics](https://github.com/xxuejie/lib-dummy-atomics): `git submodule add https://github.com/xxuejie/lib-dummy-atomics deps/lib-dummy-atomics` 160 | * `stack-reorder-contract` requires [ckb-stack-reorg-bootloader](https://github.com/xxuejie/ckb-stack-reorg-bootloader): `git submodule add https://github.com/xxuejie/ckb-stack-reorg-bootloader deps/ckb-stack-reorg-bootloader` 161 | 162 | In future versions, we might leverage [cargo-generate hooks](https://cargo-generate.github.io/cargo-generate/templates/scripting.html) to add submodules automatically, but for now, manual steps are required. 163 | 164 | ### Build & Test 165 | 166 | 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. 167 | 168 | Now you can build the contracts(or adjust parameters): 169 | 170 | ``` 171 | $ make build 172 | $ make build MODE=debug # for debug build 173 | $ make build CUSTOM_RUSTFLAGS="" # release build without debug assertions 174 | $ make build CARGO_ARGS="--verbose" # release build with `--verbose` attached to cargo command, you can use other arguments accepted by cargo 175 | $ make build CONTRACT=second-contract # build a single contract 176 | $ make build CLEAN_BUILD_DIR_FIRST=false # keep old untouched binaries 177 | $ make build CLANG=clang-17 # use a specific clang version to build C code 178 | ``` 179 | 180 | We have prepared a `tests` crate where you can also add contract tests. If you want to run the tests, run the following command: 181 | 182 | ``` 183 | make test 184 | ``` 185 | 186 | The templates provided here, use the same conventions as `ckb-native-build-sample` project, so feel free to refer to the more detailed [usage](https://github.com/xxuejie/ckb-native-build-sample?tab=readme-ov-file#usage) doc in the sample project. 187 | 188 | ### Reproducible Build 189 | 190 | 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. 191 | 192 | For the time being, we have prepared a script that does reproducible build via [a docker container image](https://github.com/cryptape/llvm-n-rust). 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. 193 | 194 | To do reproducible build, you can use the included script with varying commands: 195 | 196 | ``` 197 | $ ./scripts/reproducible_build_docker # Clean current repository, used locked LLVM & Rust from a docker container 198 | # to build all contracts, then test the binaries against a checksum file. 199 | 200 | $ ./scripts/reproducible_build_docker --update # Update the checksum file with new binaries, could be handy when you have 201 | # made changes to the binaries. 202 | 203 | $ ./scripts/reproducible_build_docker --no-clean # Do not clean intermediate files before building, it is not recommended to 204 | # use this but when you really know what you are doing, it could help you save 205 | # some time. 206 | 207 | $ ./scripts/reproducible_build_docker --proxy "..." # Setup docker container so it pulls Rust crates using a proxy server 208 | ``` 209 | 210 | 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. 211 | 212 | ### Standalone Contract Crate 213 | 214 | In rare cases if you want to simply use a standalone contract crate without a workspace. The [standalone-contract](https://github.com/cryptape/ckb-script-templates/tree/main/standalone-contract) template is prepared for you: 215 | 216 | ``` 217 | $ cargo generate gh:cryptape/ckb-script-templates standalone-contract 218 | ⚠️ Favorite `gh:cryptape/ckb-script-templates` not found in config, using it as a git repository: https://github.com/cryptape/ckb-script-templates.git 219 | 🤷 Project Name: standalone-first-contract 220 | 🔧 Destination: /tmp/standalone-first-contract ... 221 | 🔧 project-name: standalone-first-contract ... 222 | 🔧 Generating template ... 223 | 🔧 Moving generated files into: `/tmp/standalone-first-contract`... 224 | 🔧 Initializing a fresh Git repository 225 | ✨ Done! New project created /tmp/standalone-first-contract 226 | ``` 227 | 228 | You can then build and test the standalone crate as usual: 229 | 230 | ``` 231 | $ cd standalone-first-contract 232 | $ make build 233 | $ make test 234 | $ make check 235 | $ make clippy 236 | ``` 237 | 238 | 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. 239 | 240 | This standalone template also has its own test setup, where in a workspace, a dedicated `tests` crate will handle most of the testing work. 241 | 242 | ### Native Simulator debug 243 | 244 | The `generate-native-simulator` command in the `Makefile` generates a native simulator. It **requires** the `CRATE` parameter to specify an existing subproject. If the parameter is missing or invalid, the command will fail. 245 | 246 | ```bash 247 | make generate-native-simulator CRATE= 248 | ``` 249 | 250 | To generate a simulator for the `example_crate` subproject: 251 | 252 | ```bash 253 | make generate-native-simulator CRATE=example_crate 254 | ``` 255 | 256 | - The `CRATE` parameter must refer to a subproject. 257 | - Missing subprojects will cause the command to fail. 258 | -------------------------------------------------------------------------------- /atomics-contract/.cargo-generate/README.md: -------------------------------------------------------------------------------- 1 | The files in this directory are used to generate code and will be removed automatically after the project template is generated. 2 | -------------------------------------------------------------------------------- /atomics-contract/.cargo-generate/tests.rs: -------------------------------------------------------------------------------- 1 | 2 | // generated unit test for contract {{project-name}} 3 | #[test] 4 | fn test_{{crate_name}}() { 5 | // deploy contract 6 | let mut context = Context::default(); 7 | let contract_bin: Bytes = Loader::default().load_binary("{{project-name}}"); 8 | let out_point = context.deploy_cell(contract_bin); 9 | 10 | // prepare scripts 11 | let lock_script = context 12 | .build_script(&out_point, Bytes::from(vec![42])) 13 | .expect("script"); 14 | 15 | // prepare cells 16 | let input_out_point = context.create_cell( 17 | CellOutput::new_builder() 18 | .capacity(1000u64.pack()) 19 | .lock(lock_script.clone()) 20 | .build(), 21 | Bytes::new(), 22 | ); 23 | let input = CellInput::new_builder() 24 | .previous_output(input_out_point) 25 | .build(); 26 | let outputs = vec![ 27 | CellOutput::new_builder() 28 | .capacity(500u64.pack()) 29 | .lock(lock_script.clone()) 30 | .build(), 31 | CellOutput::new_builder() 32 | .capacity(500u64.pack()) 33 | .lock(lock_script) 34 | .build(), 35 | ]; 36 | 37 | let outputs_data = vec![Bytes::new(); 2]; 38 | 39 | // build transaction 40 | let tx = TransactionBuilder::default() 41 | .input(input) 42 | .outputs(outputs) 43 | .outputs_data(outputs_data.pack()) 44 | .build(); 45 | let tx = context.complete_tx(tx); 46 | 47 | // run 48 | let cycles = context 49 | .verify_tx(&tx, 10_000_000) 50 | .expect("pass verification"); 51 | println!("consume cycles: {}", cycles); 52 | } 53 | -------------------------------------------------------------------------------- /atomics-contract/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /target 3 | -------------------------------------------------------------------------------- /atomics-contract/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "{{project-name}}" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | ckb-std = { version = "0.17.0", default-features = false, features = ["allocator", "calc-hash", "ckb-types", "libc"] } 8 | log = { version = "0.4.20", default-features = false } 9 | 10 | [build-dependencies] 11 | cc = "1.0" 12 | -------------------------------------------------------------------------------- /atomics-contract/Makefile: -------------------------------------------------------------------------------- 1 | # We cannot use $(shell pwd), which will return unix path format on Windows, 2 | # making it hard to use. 3 | cur_dir = $(dir $(abspath $(firstword $(MAKEFILE_LIST)))) 4 | 5 | TOP := $(cur_dir) 6 | # RUSTFLAGS that are likely to be tweaked by developers. For example, 7 | # while we enable debug logs by default here, some might want to strip them 8 | # for minimal code size / consumed cycles. 9 | CUSTOM_RUSTFLAGS := -C debug-assertions 10 | # RUSTFLAGS that are less likely to be tweaked by developers. Most likely 11 | # one would want to keep the default values here. 12 | FULL_RUSTFLAGS := -C target-feature=+zba,+zbb,+zbc,+zbs,-a $(CUSTOM_RUSTFLAGS) 13 | # Additional cargo args to append here. For example, one can use 14 | # make test CARGO_ARGS="-- --nocapture" so as to inspect data emitted to 15 | # stdout in unit tests 16 | CARGO_ARGS := 17 | MODE := release 18 | # Tweak this to change the clang version to use for building C code. By default 19 | # we use a bash script with somes heuristics to find clang in current system. 20 | CLANG := $(shell $(TOP)/scripts/find_clang) 21 | AR := $(subst clang,llvm-ar,$(CLANG)) 22 | # When this is set to some value, the generated binaries will be copied over 23 | BUILD_DIR := 24 | # Generated binaries to copy. By convention, a Rust crate's directory name will 25 | # likely match the crate name, which is also the name of the final binary. 26 | # However if this is not the case, you can tweak this variable. As the name hints, 27 | # more than one binary is supported here. 28 | BINARIES := $(notdir $(shell pwd)) 29 | 30 | ifeq (release,$(MODE)) 31 | MODE_ARGS := --release 32 | endif 33 | 34 | default: build test 35 | 36 | build: 37 | RUSTFLAGS="$(FULL_RUSTFLAGS)" TARGET_CC="$(CLANG)" TARGET_AR="$(AR)" \ 38 | cargo build --target=riscv64imac-unknown-none-elf $(MODE_ARGS) $(CARGO_ARGS) 39 | @set -eu; \ 40 | if [ "x$(BUILD_DIR)" != "x" ]; then \ 41 | for binary in $(BINARIES); do \ 42 | echo "Copying binary $$binary to build directory"; \ 43 | cp $(TOP)/target/riscv64imac-unknown-none-elf/$(MODE)/$$binary $(TOP)/$(BUILD_DIR); \ 44 | done \ 45 | fi 46 | 47 | # test, check, clippy and fmt here are provided for completeness, 48 | # there is nothing wrong invoking cargo directly instead of make. 49 | test: 50 | cargo test $(CARGO_ARGS) 51 | 52 | check: 53 | cargo check $(CARGO_ARGS) 54 | 55 | clippy: 56 | cargo clippy $(CARGO_ARGS) 57 | 58 | fmt: 59 | cargo fmt $(CARGO_ARGS) 60 | 61 | # Arbitrary cargo command is supported here. For example: 62 | # 63 | # make cargo CARGO_CMD=expand CARGO_ARGS="--ugly" 64 | # 65 | # Invokes: 66 | # cargo expand --ugly 67 | CARGO_CMD := 68 | cargo: 69 | cargo $(CARGO_CMD) $(CARGO_ARGS) 70 | 71 | clean: 72 | cargo clean 73 | 74 | prepare: 75 | rustup target add riscv64imac-unknown-none-elf 76 | 77 | .PHONY: build test check clippy fmt cargo clean prepare 78 | -------------------------------------------------------------------------------- /atomics-contract/README.md: -------------------------------------------------------------------------------- 1 | # {{project-name}} 2 | 3 | TODO: Write this readme 4 | 5 | *This contract was bootstrapped with [ckb-script-templates].* 6 | 7 | [ckb-script-templates]: https://github.com/cryptape/ckb-script-templates 8 | -------------------------------------------------------------------------------- /atomics-contract/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let top = match std::env::var("TOP") { 3 | Ok(top) => top, 4 | Err(_) => "../..".to_string(), 5 | }; 6 | 7 | println!("cargo:rerun-if-changed={}/deps/lib-dummy-atomics/atomics.c", top); 8 | 9 | let target_arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap(); 10 | if target_arch == "riscv64" { 11 | let mut build = cc::Build::new(); 12 | assert!( 13 | build.get_compiler().is_like_clang(), 14 | "Clang must be used as the compiler!" 15 | ); 16 | build 17 | .file(format!("{}/deps/lib-dummy-atomics/atomics.c", top)) 18 | .static_flag(true) 19 | .include(format!("{}/deps/ckb-c-stdlib", top)) 20 | .include(format!("{}/deps/ckb-c-stdlib/libc", top)) 21 | .no_default_flags(true) 22 | .flag("--target=riscv64") 23 | .flag("-march=rv64imc_zba_zbb_zbc_zbs") 24 | .flag("-O3") 25 | .flag("-nostdinc") 26 | .flag("-nostdlib") 27 | .flag("-fvisibility=hidden") 28 | .flag("-fdata-sections") 29 | .flag("-ffunction-sections") 30 | .flag("-Wall") 31 | .flag("-Werror") 32 | .flag("-Wno-unused-parameter") 33 | .define("CKB_DECLARATION_ONLY", None) 34 | .compile("dummy-atomics"); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /atomics-contract/cargo-generate.toml: -------------------------------------------------------------------------------- 1 | [template] 2 | cargo_generate_version = ">=0.16.0" 3 | -------------------------------------------------------------------------------- /atomics-contract/src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(test), no_std)] 2 | #![cfg_attr(not(test), no_main)] 3 | 4 | #[cfg(test)] 5 | extern crate alloc; 6 | 7 | #[cfg(not(test))] 8 | ckb_std::entry!(program_entry); 9 | // By default, the following heap configuration is used: 10 | // * 16KB fixed heap 11 | // * 1.2MB(rounded up to be 16-byte aligned) dynamic heap 12 | // * Minimal memory block in dynamic heap is 64 bytes 13 | // For more details, please refer to ckb-std's default_alloc macro 14 | // and the buddy-alloc alloc implementation. 15 | ckb_std::default_alloc!(16384, 1258306, 64); 16 | 17 | pub fn program_entry() -> i8 { 18 | log::error!("Just a log line"); 19 | 20 | 0 21 | } 22 | -------------------------------------------------------------------------------- /c-wrapper-crate/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "{{project-name}}" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | 8 | [build-dependencies] 9 | cc = "1.0" 10 | -------------------------------------------------------------------------------- /c-wrapper-crate/README.md: -------------------------------------------------------------------------------- 1 | # {{project-name}} 2 | 3 | TODO: Write this readme 4 | 5 | *This crate was bootstrapped with [ckb-script-templates].* 6 | 7 | [ckb-script-templates]: https://github.com/cryptape/ckb-script-templates 8 | -------------------------------------------------------------------------------- /c-wrapper-crate/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("cargo:rerun-if-changed=c.c"); 3 | 4 | let top = match std::env::var("TOP") { 5 | Ok(top) => top, 6 | Err(_) => "../..".to_string(), 7 | }; 8 | 9 | let target_arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap(); 10 | if target_arch == "riscv64" { 11 | let mut build = cc::Build::new(); 12 | assert!( 13 | build.get_compiler().is_like_clang(), 14 | "Clang must be used as the compiler!" 15 | ); 16 | build 17 | .file("c.c") 18 | .static_flag(true) 19 | .include(format!("{}/deps/ckb-c-stdlib", top)) 20 | .include(format!("{}/deps/ckb-c-stdlib/libc", top)) 21 | .no_default_flags(true) 22 | .flag("--target=riscv64") 23 | .flag("-march=rv64imc_zba_zbb_zbc_zbs") 24 | .flag("-O3") 25 | .flag("-nostdinc") 26 | .flag("-nostdlib") 27 | .flag("-fvisibility=hidden") 28 | .flag("-fdata-sections") 29 | .flag("-ffunction-sections") 30 | .flag("-Wall") 31 | .flag("-Werror") 32 | .flag("-Wno-unused-parameter") 33 | .define("CKB_DECLARATION_ONLY", None) 34 | .compile("c-impl"); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /c-wrapper-crate/c.c: -------------------------------------------------------------------------------- 1 | int bar() { 2 | return 42; 3 | } 4 | -------------------------------------------------------------------------------- /c-wrapper-crate/cargo-generate.toml: -------------------------------------------------------------------------------- 1 | [template] 2 | cargo_generate_version = ">=0.16.0" 3 | -------------------------------------------------------------------------------- /c-wrapper-crate/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(test), no_std)] 2 | 3 | #[cfg(target_arch = "riscv64")] 4 | #[link(name = "c-impl", kind = "static")] 5 | extern "C" { 6 | fn bar() -> core::ffi::c_int; 7 | } 8 | 9 | #[cfg(not(target_arch = "riscv64"))] 10 | unsafe fn bar() -> core::ffi::c_int { 11 | unreachable!() 12 | } 13 | 14 | pub fn value() -> u32 { 15 | (unsafe { bar() }) as u32 16 | } 17 | -------------------------------------------------------------------------------- /cargo-generate.toml: -------------------------------------------------------------------------------- 1 | [template] 2 | sub_templates = [ 3 | # Entrypoint templates 4 | "workspace", 5 | "standalone-contract", 6 | # Contract templates in workspace 7 | "contract", 8 | "atomics-contract", 9 | "stack-reorder-contract", 10 | "native-simulator", 11 | # Dependency crate templates 12 | "c-wrapper-crate", 13 | "x64-simulator-crate", 14 | ] 15 | -------------------------------------------------------------------------------- /contract-without-simulator/.cargo-generate/README.md: -------------------------------------------------------------------------------- 1 | The files in this directory are used to generate code and will be removed automatically after the project template is generated. 2 | -------------------------------------------------------------------------------- /contract-without-simulator/.cargo-generate/tests.rs: -------------------------------------------------------------------------------- 1 | 2 | // generated unit test for contract {{project-name}} 3 | #[test] 4 | fn test_{{crate_name}}() { 5 | // deploy contract 6 | let mut context = Context::default(); 7 | let contract_bin: Bytes = Loader::default().load_binary("{{project-name}}"); 8 | let out_point = context.deploy_cell(contract_bin); 9 | 10 | // prepare scripts 11 | let lock_script = context 12 | .build_script(&out_point, Bytes::from(vec![42])) 13 | .expect("script"); 14 | 15 | // prepare cells 16 | let input_out_point = context.create_cell( 17 | CellOutput::new_builder() 18 | .capacity(1000u64.pack()) 19 | .lock(lock_script.clone()) 20 | .build(), 21 | Bytes::new(), 22 | ); 23 | let input = CellInput::new_builder() 24 | .previous_output(input_out_point) 25 | .build(); 26 | let outputs = vec![ 27 | CellOutput::new_builder() 28 | .capacity(500u64.pack()) 29 | .lock(lock_script.clone()) 30 | .build(), 31 | CellOutput::new_builder() 32 | .capacity(500u64.pack()) 33 | .lock(lock_script) 34 | .build(), 35 | ]; 36 | 37 | let outputs_data = vec![Bytes::new(); 2]; 38 | 39 | // build transaction 40 | let tx = TransactionBuilder::default() 41 | .input(input) 42 | .outputs(outputs) 43 | .outputs_data(outputs_data.pack()) 44 | .build(); 45 | let tx = context.complete_tx(tx); 46 | 47 | // run 48 | let cycles = context 49 | .verify_tx(&tx, 10_000_000) 50 | .expect("pass verification"); 51 | println!("consume cycles: {}", cycles); 52 | } 53 | -------------------------------------------------------------------------------- /contract-without-simulator/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /target 3 | -------------------------------------------------------------------------------- /contract-without-simulator/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "{{project-name}}" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | ckb-std = "0.17.0" 8 | -------------------------------------------------------------------------------- /contract-without-simulator/Makefile: -------------------------------------------------------------------------------- 1 | # We cannot use $(shell pwd), which will return unix path format on Windows, 2 | # making it hard to use. 3 | cur_dir = $(dir $(abspath $(firstword $(MAKEFILE_LIST)))) 4 | 5 | TOP := $(cur_dir) 6 | # RUSTFLAGS that are likely to be tweaked by developers. For example, 7 | # while we enable debug logs by default here, some might want to strip them 8 | # for minimal code size / consumed cycles. 9 | CUSTOM_RUSTFLAGS := -C debug-assertions 10 | # RUSTFLAGS that are less likely to be tweaked by developers. Most likely 11 | # one would want to keep the default values here. 12 | FULL_RUSTFLAGS := -C target-feature=+zba,+zbb,+zbc,+zbs,-a $(CUSTOM_RUSTFLAGS) 13 | # Additional cargo args to append here. For example, one can use 14 | # make test CARGO_ARGS="-- --nocapture" so as to inspect data emitted to 15 | # stdout in unit tests 16 | CARGO_ARGS := 17 | MODE := release 18 | # Tweak this to change the clang version to use for building C code. By default 19 | # we use a bash script with somes heuristics to find clang in current system. 20 | CLANG := $(shell $(TOP)/scripts/find_clang) 21 | AR := $(subst clang,llvm-ar,$(CLANG)) 22 | OBJCOPY := $(subst clang,llvm-objcopy,$(CLANG)) 23 | # When this is set to some value, the generated binaries will be copied over 24 | BUILD_DIR := 25 | # Generated binaries to copy. By convention, a Rust crate's directory name will 26 | # likely match the crate name, which is also the name of the final binary. 27 | # However if this is not the case, you can tweak this variable. As the name hints, 28 | # more than one binary is supported here. 29 | BINARIES := $(notdir $(shell pwd)) 30 | 31 | ifeq (release,$(MODE)) 32 | MODE_ARGS := --release 33 | endif 34 | 35 | default: build test 36 | 37 | build: 38 | RUSTFLAGS="$(FULL_RUSTFLAGS)" TARGET_CC="$(CLANG)" TARGET_AR="$(AR)" \ 39 | cargo build --target=riscv64imac-unknown-none-elf $(MODE_ARGS) $(CARGO_ARGS) 40 | @set -eu; \ 41 | if [ "x$(BUILD_DIR)" != "x" ]; then \ 42 | for binary in $(BINARIES); do \ 43 | echo "Copying binary $$binary to build directory"; \ 44 | cp $(TOP)/target/riscv64imac-unknown-none-elf/$(MODE)/$$binary $(TOP)/$(BUILD_DIR); \ 45 | cp $(TOP)/$(BUILD_DIR)/$$binary $(TOP)/$(BUILD_DIR)/$$binary.debug; \ 46 | $(OBJCOPY) --strip-debug --strip-all $(TOP)/$(BUILD_DIR)/$$binary; \ 47 | done \ 48 | fi 49 | 50 | # test, check, clippy and fmt here are provided for completeness, 51 | # there is nothing wrong invoking cargo directly instead of make. 52 | test: 53 | cargo test $(CARGO_ARGS) 54 | 55 | check: 56 | cargo check $(CARGO_ARGS) 57 | 58 | clippy: 59 | cargo clippy $(CARGO_ARGS) 60 | 61 | fmt: 62 | cargo fmt $(CARGO_ARGS) 63 | 64 | # Arbitrary cargo command is supported here. For example: 65 | # 66 | # make cargo CARGO_CMD=expand CARGO_ARGS="--ugly" 67 | # 68 | # Invokes: 69 | # cargo expand --ugly 70 | CARGO_CMD := 71 | cargo: 72 | cargo $(CARGO_CMD) $(CARGO_ARGS) 73 | 74 | clean: 75 | cargo clean 76 | 77 | prepare: 78 | rustup target add riscv64imac-unknown-none-elf 79 | 80 | .PHONY: build test check clippy fmt cargo clean prepare 81 | -------------------------------------------------------------------------------- /contract-without-simulator/README.md: -------------------------------------------------------------------------------- 1 | # {{project-name}} 2 | 3 | TODO: Write this readme 4 | 5 | *This contract was bootstrapped with [ckb-script-templates].* 6 | 7 | [ckb-script-templates]: https://github.com/cryptape/ckb-script-templates 8 | -------------------------------------------------------------------------------- /contract-without-simulator/cargo-generate.toml: -------------------------------------------------------------------------------- 1 | [template] 2 | cargo_generate_version = ">=0.16.0" 3 | -------------------------------------------------------------------------------- /contract-without-simulator/src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(test), no_std)] 2 | #![cfg_attr(not(test), no_main)] 3 | 4 | #[cfg(test)] 5 | extern crate alloc; 6 | 7 | #[cfg(not(test))] 8 | ckb_std::entry!(program_entry); 9 | // By default, the following heap configuration is used: 10 | // * 16KB fixed heap 11 | // * 1.2MB(rounded up to be 16-byte aligned) dynamic heap 12 | // * Minimal memory block in dynamic heap is 64 bytes 13 | // For more details, please refer to ckb-std's default_alloc macro 14 | // and the buddy-alloc alloc implementation. 15 | ckb_std::default_alloc!(16384, 1258306, 64); 16 | 17 | pub fn program_entry() -> i8 { 18 | ckb_std::debug!("This is a sample contract!"); 19 | 20 | 0 21 | } 22 | -------------------------------------------------------------------------------- /contract/.cargo-generate/README.md: -------------------------------------------------------------------------------- 1 | The files in this directory are used to generate code and will be removed automatically after the project template is generated. 2 | -------------------------------------------------------------------------------- /contract/.cargo-generate/tests.rs: -------------------------------------------------------------------------------- 1 | 2 | // generated unit test for contract {{project-name}} 3 | #[test] 4 | fn test_{{crate_name}}() { 5 | // deploy contract 6 | let mut context = Context::default(); 7 | let contract_bin: Bytes = Loader::default().load_binary("{{project-name}}"); 8 | let out_point = context.deploy_cell(contract_bin); 9 | 10 | // prepare scripts 11 | let lock_script = context 12 | .build_script(&out_point, Bytes::from(vec![42])) 13 | .expect("script"); 14 | 15 | // prepare cells 16 | let input_out_point = context.create_cell( 17 | CellOutput::new_builder() 18 | .capacity(1000u64.pack()) 19 | .lock(lock_script.clone()) 20 | .build(), 21 | Bytes::new(), 22 | ); 23 | let input = CellInput::new_builder() 24 | .previous_output(input_out_point) 25 | .build(); 26 | let outputs = vec![ 27 | CellOutput::new_builder() 28 | .capacity(500u64.pack()) 29 | .lock(lock_script.clone()) 30 | .build(), 31 | CellOutput::new_builder() 32 | .capacity(500u64.pack()) 33 | .lock(lock_script) 34 | .build(), 35 | ]; 36 | 37 | let outputs_data = vec![Bytes::new(); 2]; 38 | 39 | // build transaction 40 | let tx = TransactionBuilder::default() 41 | .input(input) 42 | .outputs(outputs) 43 | .outputs_data(outputs_data.pack()) 44 | .build(); 45 | let tx = context.complete_tx(tx); 46 | 47 | // run 48 | let cycles = context 49 | .verify_tx(&tx, 10_000_000) 50 | .expect("pass verification"); 51 | println!("consume cycles: {}", cycles); 52 | } 53 | -------------------------------------------------------------------------------- /contract/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /target 3 | -------------------------------------------------------------------------------- /contract/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "{{project-name}}" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | ckb-std = "0.17.0" 8 | 9 | [features] 10 | library = [] 11 | native-simulator = ["library", "ckb-std/native-simulator"] 12 | -------------------------------------------------------------------------------- /contract/Makefile: -------------------------------------------------------------------------------- 1 | # We cannot use $(shell pwd), which will return unix path format on Windows, 2 | # making it hard to use. 3 | cur_dir = $(dir $(abspath $(firstword $(MAKEFILE_LIST)))) 4 | 5 | TOP := $(cur_dir) 6 | # RUSTFLAGS that are likely to be tweaked by developers. For example, 7 | # while we enable debug logs by default here, some might want to strip them 8 | # for minimal code size / consumed cycles. 9 | CUSTOM_RUSTFLAGS := -C debug-assertions 10 | # RUSTFLAGS that are less likely to be tweaked by developers. Most likely 11 | # one would want to keep the default values here. 12 | FULL_RUSTFLAGS := -C target-feature=+zba,+zbb,+zbc,+zbs,-a $(CUSTOM_RUSTFLAGS) 13 | # Additional cargo args to append here. For example, one can use 14 | # make test CARGO_ARGS="-- --nocapture" so as to inspect data emitted to 15 | # stdout in unit tests 16 | CARGO_ARGS := 17 | MODE := release 18 | # Tweak this to change the clang version to use for building C code. By default 19 | # we use a bash script with somes heuristics to find clang in current system. 20 | CLANG := $(shell $(TOP)/scripts/find_clang) 21 | AR := $(subst clang,llvm-ar,$(CLANG)) 22 | OBJCOPY := $(subst clang,llvm-objcopy,$(CLANG)) 23 | # When this is set to some value, the generated binaries will be copied over 24 | BUILD_DIR := 25 | # Generated binaries to copy. By convention, a Rust crate's directory name will 26 | # likely match the crate name, which is also the name of the final binary. 27 | # However if this is not the case, you can tweak this variable. As the name hints, 28 | # more than one binary is supported here. 29 | BINARIES := $(notdir $(shell pwd)) 30 | 31 | ifeq (release,$(MODE)) 32 | MODE_ARGS := --release 33 | endif 34 | 35 | default: build test 36 | 37 | build: 38 | RUSTFLAGS="$(FULL_RUSTFLAGS)" TARGET_CC="$(CLANG)" TARGET_AR="$(AR)" \ 39 | cargo build --target=riscv64imac-unknown-none-elf $(MODE_ARGS) $(CARGO_ARGS) 40 | @set -eu; \ 41 | if [ "x$(BUILD_DIR)" != "x" ]; then \ 42 | for binary in $(BINARIES); do \ 43 | echo "Copying binary $$binary to build directory"; \ 44 | cp $(TOP)/target/riscv64imac-unknown-none-elf/$(MODE)/$$binary $(TOP)/$(BUILD_DIR); \ 45 | cp $(TOP)/$(BUILD_DIR)/$$binary $(TOP)/$(BUILD_DIR)/$$binary.debug; \ 46 | $(OBJCOPY) --strip-debug --strip-all $(TOP)/$(BUILD_DIR)/$$binary; \ 47 | done \ 48 | fi 49 | 50 | # test, check, clippy and fmt here are provided for completeness, 51 | # there is nothing wrong invoking cargo directly instead of make. 52 | test: 53 | cargo test $(CARGO_ARGS) 54 | 55 | check: 56 | cargo check $(CARGO_ARGS) 57 | 58 | clippy: 59 | cargo clippy $(CARGO_ARGS) 60 | 61 | fmt: 62 | cargo fmt $(CARGO_ARGS) 63 | 64 | # Arbitrary cargo command is supported here. For example: 65 | # 66 | # make cargo CARGO_CMD=expand CARGO_ARGS="--ugly" 67 | # 68 | # Invokes: 69 | # cargo expand --ugly 70 | CARGO_CMD := 71 | cargo: 72 | cargo $(CARGO_CMD) $(CARGO_ARGS) 73 | 74 | clean: 75 | cargo clean 76 | 77 | prepare: 78 | rustup target add riscv64imac-unknown-none-elf 79 | 80 | .PHONY: build test check clippy fmt cargo clean prepare 81 | -------------------------------------------------------------------------------- /contract/README.md: -------------------------------------------------------------------------------- 1 | # {{project-name}} 2 | 3 | TODO: Write this readme 4 | 5 | *This contract was bootstrapped with [ckb-script-templates].* 6 | 7 | [ckb-script-templates]: https://github.com/cryptape/ckb-script-templates 8 | -------------------------------------------------------------------------------- /contract/cargo-generate.toml: -------------------------------------------------------------------------------- 1 | [template] 2 | cargo_generate_version = ">=0.16.0" 3 | -------------------------------------------------------------------------------- /contract/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(feature = "library"), no_std)] 2 | #![allow(special_module_name)] 3 | #![allow(unused_attributes)] 4 | #[cfg(feature = "library")] 5 | mod main; 6 | #[cfg(feature = "library")] 7 | pub use main::program_entry; 8 | 9 | extern crate alloc; 10 | -------------------------------------------------------------------------------- /contract/src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(any(feature = "library", test)), no_std)] 2 | #![cfg_attr(not(test), no_main)] 3 | 4 | #[cfg(any(feature = "library", test))] 5 | extern crate alloc; 6 | 7 | #[cfg(not(any(feature = "library", test)))] 8 | ckb_std::entry!(program_entry); 9 | #[cfg(not(any(feature = "library", test)))] 10 | // By default, the following heap configuration is used: 11 | // * 16KB fixed heap 12 | // * 1.2MB(rounded up to be 16-byte aligned) dynamic heap 13 | // * Minimal memory block in dynamic heap is 64 bytes 14 | // For more details, please refer to ckb-std's default_alloc macro 15 | // and the buddy-alloc alloc implementation. 16 | ckb_std::default_alloc!(16384, 1258306, 64); 17 | 18 | pub fn program_entry() -> i8 { 19 | ckb_std::debug!("This is a sample contract!"); 20 | 21 | 0 22 | } 23 | -------------------------------------------------------------------------------- /native-simulator/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /target 3 | -------------------------------------------------------------------------------- /native-simulator/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "{{project-name}}" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | [dependencies] 8 | {{project-name | append: "@@SIMULATOR_PLACEHOLDER@@" | remove: "-sim@@SIMULATOR_PLACEHOLDER@@"}} = { path = "../../contracts/{{project-name | append: "@@SIMULATOR_PLACEHOLDER@@" | remove: "-sim@@SIMULATOR_PLACEHOLDER@@"}}", features = ["native-simulator"] } 9 | ckb-std = { version = "0.17.0", features = ["native-simulator"] } 10 | 11 | [lib] 12 | crate-type = ["cdylib"] 13 | -------------------------------------------------------------------------------- /native-simulator/README.md: -------------------------------------------------------------------------------- 1 | # {{project-name}} 2 | 3 | TODO: Write this readme 4 | 5 | *This template is used to provide native simulator for a particular contract, and is not designed to be used on its own.* 6 | 7 | *This project was bootstrapped with [ckb-script-templates].* 8 | 9 | 10 | [ckb-script-templates]: https://github.com/cryptape/ckb-script-templates 11 | -------------------------------------------------------------------------------- /native-simulator/cargo-generate.toml: -------------------------------------------------------------------------------- 1 | [template] 2 | cargo_generate_version = ">=0.16.0" 3 | -------------------------------------------------------------------------------- /native-simulator/src/lib.rs: -------------------------------------------------------------------------------- 1 | ckb_std::entry_simulator!({{project-name | append: "@@SIMULATOR_PLACEHOLDER@@" | remove: "-sim@@SIMULATOR_PLACEHOLDER@@" | replace: "-", "_"}}::program_entry); 2 | -------------------------------------------------------------------------------- /stack-reorder-contract/.cargo-generate/README.md: -------------------------------------------------------------------------------- 1 | The files in this directory are used to generate code and will be removed automatically after the project template is generated. 2 | -------------------------------------------------------------------------------- /stack-reorder-contract/.cargo-generate/tests.rs: -------------------------------------------------------------------------------- 1 | 2 | // generated unit test for contract {{project-name}} 3 | #[test] 4 | fn test_{{crate_name}}() { 5 | // deploy contract 6 | let mut context = Context::default(); 7 | let contract_bin: Bytes = Loader::default().load_binary("{{project-name}}"); 8 | let out_point = context.deploy_cell(contract_bin); 9 | 10 | // prepare scripts 11 | let lock_script = context 12 | .build_script(&out_point, Bytes::from(vec![42])) 13 | .expect("script"); 14 | 15 | // prepare cells 16 | let input_out_point = context.create_cell( 17 | CellOutput::new_builder() 18 | .capacity(1000u64.pack()) 19 | .lock(lock_script.clone()) 20 | .build(), 21 | Bytes::new(), 22 | ); 23 | let input = CellInput::new_builder() 24 | .previous_output(input_out_point) 25 | .build(); 26 | let outputs = vec![ 27 | CellOutput::new_builder() 28 | .capacity(500u64.pack()) 29 | .lock(lock_script.clone()) 30 | .build(), 31 | CellOutput::new_builder() 32 | .capacity(500u64.pack()) 33 | .lock(lock_script) 34 | .build(), 35 | ]; 36 | 37 | let outputs_data = vec![Bytes::new(); 2]; 38 | 39 | // build transaction 40 | let tx = TransactionBuilder::default() 41 | .input(input) 42 | .outputs(outputs) 43 | .outputs_data(outputs_data.pack()) 44 | .build(); 45 | let tx = context.complete_tx(tx); 46 | 47 | // run 48 | let cycles = context 49 | .verify_tx(&tx, 10_000_000) 50 | .expect("pass verification"); 51 | println!("consume cycles: {}", cycles); 52 | } 53 | -------------------------------------------------------------------------------- /stack-reorder-contract/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /target 3 | -------------------------------------------------------------------------------- /stack-reorder-contract/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "{{project-name}}" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | ckb-std = "0.17.0" 8 | 9 | [build-dependencies] 10 | cc = "1.0" 11 | -------------------------------------------------------------------------------- /stack-reorder-contract/Makefile: -------------------------------------------------------------------------------- 1 | # We cannot use $(shell pwd), which will return unix path format on Windows, 2 | # making it hard to use. 3 | cur_dir = $(dir $(abspath $(firstword $(MAKEFILE_LIST)))) 4 | 5 | TOP := $(cur_dir) 6 | # RUSTFLAGS that are likely to be tweaked by developers. For example, 7 | # while we enable debug logs by default here, some might want to strip them 8 | # for minimal code size / consumed cycles. 9 | CUSTOM_RUSTFLAGS := -C debug-assertions 10 | # RUSTFLAGS that are less likely to be tweaked by developers. Most likely 11 | # one would want to keep the default values here. 12 | FULL_RUSTFLAGS := -C target-feature=+zba,+zbb,+zbc,+zbs,-a $(CUSTOM_RUSTFLAGS) \ 13 | -C link-arg=-T$(cur_dir)ld_interface.ld 14 | # Additional cargo args to append here. For example, one can use 15 | # make test CARGO_ARGS="-- --nocapture" so as to inspect data emitted to 16 | # stdout in unit tests 17 | CARGO_ARGS := 18 | MODE := release 19 | # Tweak this to change the clang version to use for building C code. By default 20 | # we use a bash script with somes heuristics to find clang in current system. 21 | CLANG := $(shell $(TOP)/scripts/find_clang) 22 | AR := $(subst clang,llvm-ar,$(CLANG)) 23 | # When this is set to some value, the generated binaries will be copied over 24 | BUILD_DIR := 25 | # Generated binaries to copy. By convention, a Rust crate's directory name will 26 | # likely match the crate name, which is also the name of the final binary. 27 | # However if this is not the case, you can tweak this variable. As the name hints, 28 | # more than one binary is supported here. 29 | BINARIES := $(notdir $(shell pwd)) 30 | 31 | ifeq (release,$(MODE)) 32 | MODE_ARGS := --release 33 | endif 34 | 35 | default: build test 36 | 37 | build: 38 | RUSTFLAGS="$(FULL_RUSTFLAGS)" TARGET_CC="$(CLANG)" TARGET_AR="$(AR)" \ 39 | cargo build --target=riscv64imac-unknown-none-elf $(MODE_ARGS) $(CARGO_ARGS) 40 | @set -eu; \ 41 | if [ "x$(BUILD_DIR)" != "x" ]; then \ 42 | for binary in $(BINARIES); do \ 43 | echo "Copying binary $$binary to build directory"; \ 44 | cp $(TOP)/target/riscv64imac-unknown-none-elf/$(MODE)/$$binary $(TOP)/$(BUILD_DIR); \ 45 | done \ 46 | fi 47 | 48 | # This is a newly introduced task to allow adjusting stack size. 49 | # Notice there are also other changes required for a stack-reordering 50 | # contract, for example, FULL_RUSTFLAGS is also altered for new flags. 51 | # One can compare this file with the Makefile for minimal-log sample 52 | # for complete differences 53 | STACK_SIZE := 0x80000 54 | adjust_stack_size: 55 | $(TOP)/deps/ckb-stack-reorg-bootloader/regenerate $(STACK_SIZE) $(shell pwd) 56 | .PHONY: adjust_stack_size 57 | 58 | # test, check, clippy and fmt here are provided for completeness, 59 | # there is nothing wrong invoking cargo directly instead of make. 60 | test: 61 | cargo test $(CARGO_ARGS) 62 | 63 | check: 64 | cargo check $(CARGO_ARGS) 65 | 66 | clippy: 67 | cargo clippy $(CARGO_ARGS) 68 | 69 | fmt: 70 | cargo fmt $(CARGO_ARGS) 71 | 72 | # Arbitrary cargo command is supported here. For example: 73 | # 74 | # make cargo CARGO_CMD=expand CARGO_ARGS="--ugly" 75 | # 76 | # Invokes: 77 | # cargo expand --ugly 78 | CARGO_CMD := 79 | cargo: 80 | cargo $(CARGO_CMD) $(CARGO_ARGS) 81 | 82 | clean: 83 | cargo clean 84 | 85 | prepare: 86 | rustup target add riscv64imac-unknown-none-elf 87 | 88 | .PHONY: build test check clippy fmt cargo clean prepare 89 | -------------------------------------------------------------------------------- /stack-reorder-contract/README.md: -------------------------------------------------------------------------------- 1 | # {{project-name}} 2 | 3 | TODO: Write this readme 4 | 5 | *This contract was bootstrapped with [ckb-script-templates].* 6 | 7 | [ckb-script-templates]: https://github.com/cryptape/ckb-script-templates 8 | -------------------------------------------------------------------------------- /stack-reorder-contract/bootloader.S: -------------------------------------------------------------------------------- 1 | .global __ckb_bootloader_start 2 | __ckb_bootloader_start: 3 | li t4, (0x400000 - 0x80000) /* Difference between new & old stack region */ 4 | mv t1, sp 5 | sub t2, t1, t4 /* t2 points to new stack, while t1 points to old stack */ 6 | ld t0, 0(t1) 7 | sd zero, 0(t1) 8 | sd t0, 0(t2) 9 | addi t1, t1, 8 10 | addi t2, t2, 8 11 | /* Load and correct argv pointers */ 12 | 1: 13 | beqz t0, 2f 14 | ld t6, 0(t1) 15 | sub t6, t6, t4 16 | sd zero, 0(t1) 17 | sd t6, 0(t2) 18 | addi t1, t1, 8 19 | addi t2, t2, 8 20 | addi t0, t0, -1 21 | j 1b 22 | 2: 23 | /* 24 | * Copy remaining data over to new stack, this is essentially a combination 25 | * of memcpy & memset, we do it this way so stack is not used at all. 26 | */ 27 | li t5, 0x400000 28 | 3: 29 | bge t1, t5, 4f 30 | ld t6, 0(t1) 31 | sd zero, 0(t1) 32 | sd t6, 0(t2) 33 | addi t1, t1, 8 34 | addi t2, t2, 8 35 | j 3b 36 | 4: 37 | /* Set new sp, clear used registers */ 38 | sub sp, sp, t4 39 | li t0, 0 40 | li t1, 0 41 | li t2, 0 42 | li t4, 0 43 | li t5, 0 44 | li t6, 0 45 | /* TODO: setup the first page starting as address 0 as executable */ 46 | /* Boot as usual */ 47 | j _start 48 | -------------------------------------------------------------------------------- /stack-reorder-contract/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("cargo:rerun-if-changed=bootloader.S"); 3 | println!("cargo:rerun-if-changed=ld_interface.ld"); 4 | 5 | let target_arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap(); 6 | if target_arch == "riscv64" { 7 | let mut build = cc::Build::new(); 8 | assert!( 9 | build.get_compiler().is_like_clang(), 10 | "Clang must be used as the compiler!" 11 | ); 12 | build 13 | .file("bootloader.S") 14 | .static_flag(true) 15 | .no_default_flags(true) 16 | .flag("--target=riscv64") 17 | .flag("-march=rv64imc_zba_zbb_zbc_zbs") 18 | .flag("-O3") 19 | .compile("bootloader"); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /stack-reorder-contract/cargo-generate.toml: -------------------------------------------------------------------------------- 1 | [template] 2 | cargo_generate_version = ">=0.16.0" 3 | -------------------------------------------------------------------------------- /stack-reorder-contract/ld_interface.ld: -------------------------------------------------------------------------------- 1 | PHDRS { 2 | dummy PT_LOAD FLAGS(5) ; 3 | text PT_LOAD FLAGS(5) ; 4 | data PT_LOAD FLAGS(6) ; 5 | 6 | relro PT_GNU_RELRO FLAGS(4) ; 7 | stack PT_GNU_STACK FLAGS(6) ; 8 | } 9 | 10 | ENTRY(__ckb_bootloader_start) 11 | 12 | SECTIONS { 13 | . = 0x0; 14 | .ckb.vm.dummy : ALIGN(0x1000) { 15 | BYTE(0) 16 | } : dummy 17 | 18 | . = 0x80000; 19 | 20 | .dynsym : { 21 | *(.dynsym) 22 | } 23 | .gnu.hash : { 24 | *(.gnu.hash) 25 | } 26 | .hash : { 27 | *(.hash) 28 | } 29 | .dynstr : { 30 | *(.dynstr) 31 | } 32 | .rela : { 33 | *(.rela) 34 | *(.rela.*) 35 | } 36 | 37 | .text : ALIGN(0x1000) { 38 | *(.text) 39 | *(.text.*) 40 | } : text 41 | 42 | .rodata : { 43 | *(.rodata) 44 | *(.rodata.*) 45 | } : text 46 | 47 | .data : ALIGN(0x1000) { 48 | *(.data) 49 | *(.data.*) 50 | } : data 51 | 52 | .dynamic : { 53 | *(.dynamic) 54 | } : data : dynamic 55 | 56 | .data.rel.ro : ALIGN(0x1000) { 57 | *(.data.rel.ro) 58 | *(.data.rel.ro.*) 59 | } : relro 60 | 61 | .bss : { 62 | *(.bss) 63 | *(.bss.*) 64 | } 65 | 66 | .stack : { 67 | *(.stack) 68 | } : stack 69 | 70 | _end = .; PROVIDE (end = .); 71 | } 72 | -------------------------------------------------------------------------------- /stack-reorder-contract/src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(test), no_std)] 2 | #![cfg_attr(not(test), no_main)] 3 | 4 | #[cfg(test)] 5 | extern crate alloc; 6 | 7 | #[cfg(not(test))] 8 | ckb_std::entry!(program_entry); 9 | // By default, the following heap configuration is used: 10 | // * 16KB fixed heap 11 | // * 1.2MB(rounded up to be 16-byte aligned) dynamic heap 12 | // * Minimal memory block in dynamic heap is 64 bytes 13 | // For more details, please refer to ckb-std's default_alloc macro 14 | // and the buddy-alloc alloc implementation. 15 | ckb_std::default_alloc!(16384, 1258306, 64); 16 | 17 | #[allow(unused_variables, unused_assignments)] 18 | pub fn program_entry() -> i8 { 19 | let mut x: u64; 20 | unsafe { 21 | core::arch::asm!( 22 | "mv {x}, sp", 23 | x = out(reg) x, 24 | ); 25 | } 26 | 27 | ckb_std::debug!("Current SP is {:x}", x); 28 | 29 | 0 30 | } 31 | -------------------------------------------------------------------------------- /standalone-contract/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /target 3 | /tests/failed_txs 4 | -------------------------------------------------------------------------------- /standalone-contract/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "{{project-name}}" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | ckb-std = "0.17.0" 8 | 9 | [dev-dependencies] 10 | ckb-testtool = "0.15.0" 11 | serde_json = "1.0" 12 | -------------------------------------------------------------------------------- /standalone-contract/Makefile: -------------------------------------------------------------------------------- 1 | # We cannot use $(shell pwd), which will return unix path format on Windows, 2 | # making it hard to use. 3 | cur_dir = $(dir $(abspath $(firstword $(MAKEFILE_LIST)))) 4 | 5 | TOP := $(cur_dir) 6 | # RUSTFLAGS that are likely to be tweaked by developers. For example, 7 | # while we enable debug logs by default here, some might want to strip them 8 | # for minimal code size / consumed cycles. 9 | CUSTOM_RUSTFLAGS := -C debug-assertions 10 | # RUSTFLAGS that are less likely to be tweaked by developers. Most likely 11 | # one would want to keep the default values here. 12 | FULL_RUSTFLAGS := -C target-feature=+zba,+zbb,+zbc,+zbs,-a $(CUSTOM_RUSTFLAGS) 13 | # Additional cargo args to append here. For example, one can use 14 | # make test CARGO_ARGS="-- --nocapture" so as to inspect data emitted to 15 | # stdout in unit tests 16 | CARGO_ARGS := 17 | MODE := release 18 | # Tweak this to change the clang version to use for building C code. By default 19 | # we use a bash script with somes heuristics to find clang in current system. 20 | CLANG := $(shell $(TOP)/scripts/find_clang) 21 | AR := $(subst clang,llvm-ar,$(CLANG)) 22 | # When this is set to some value, the generated binaries will be copied over 23 | BUILD_DIR := build/$(MODE) 24 | # Generated binaries to copy. By convention, a Rust crate's directory name will 25 | # likely match the crate name, which is also the name of the final binary. 26 | # However if this is not the case, you can tweak this variable. As the name hints, 27 | # more than one binary is supported here. 28 | BINARIES := {{project-name}} 29 | 30 | ifeq (release,$(MODE)) 31 | MODE_ARGS := --release 32 | endif 33 | 34 | default: build test 35 | 36 | build: 37 | RUSTFLAGS="$(FULL_RUSTFLAGS)" TARGET_CC="$(CLANG)" TARGET_AR="$(AR)" \ 38 | cargo build --target=riscv64imac-unknown-none-elf $(MODE_ARGS) $(CARGO_ARGS) 39 | mkdir -p $(BUILD_DIR) 40 | @set -eu; \ 41 | if [ "x$(BUILD_DIR)" != "x" ]; then \ 42 | for binary in $(BINARIES); do \ 43 | echo "Copying binary $$binary to build directory"; \ 44 | cp $(TOP)/target/riscv64imac-unknown-none-elf/$(MODE)/$$binary $(TOP)/$(BUILD_DIR); \ 45 | done \ 46 | fi 47 | 48 | # test, check, clippy and fmt here are provided for completeness, 49 | # there is nothing wrong invoking cargo directly instead of make. 50 | test: 51 | cargo test $(CARGO_ARGS) 52 | 53 | check: 54 | cargo check $(CARGO_ARGS) 55 | 56 | clippy: 57 | cargo clippy $(CARGO_ARGS) 58 | 59 | fmt: 60 | cargo fmt $(CARGO_ARGS) 61 | 62 | # Arbitrary cargo command is supported here. For example: 63 | # 64 | # make cargo CARGO_CMD=expand CARGO_ARGS="--ugly" 65 | # 66 | # Invokes: 67 | # cargo expand --ugly 68 | CARGO_CMD := 69 | cargo: 70 | cargo $(CARGO_CMD) $(CARGO_ARGS) 71 | 72 | clean: 73 | rm -rf build 74 | cargo clean 75 | 76 | prepare: 77 | rustup target add riscv64imac-unknown-none-elf 78 | 79 | # Generate checksum info for reproducible build 80 | CHECKSUM_FILE := build/checksums-$(MODE).txt 81 | checksum: build 82 | shasum -a 256 build/$(MODE)/* > $(CHECKSUM_FILE) 83 | 84 | .PHONY: build test check clippy fmt cargo clean prepare 85 | -------------------------------------------------------------------------------- /standalone-contract/README.md: -------------------------------------------------------------------------------- 1 | # {{project-name}} 2 | 3 | TODO: Write this readme 4 | 5 | *This contract was bootstrapped with [ckb-script-templates].* 6 | 7 | [ckb-script-templates]: https://github.com/cryptape/ckb-script-templates 8 | -------------------------------------------------------------------------------- /standalone-contract/cargo-generate.toml: -------------------------------------------------------------------------------- 1 | [template] 2 | cargo_generate_version = ">=0.16.0" 3 | -------------------------------------------------------------------------------- /standalone-contract/scripts/find_clang: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # An utility script used to find a binary of clang 16+ 4 | 5 | if [[ -n "${CLANG}" ]]; then 6 | echo "${CLANG}" 7 | exit 0 8 | fi 9 | 10 | # To cope with packaging messes from different distros, we would search 11 | # for a different binary other than clang, then convert it back to clang 12 | # at the end. 13 | SEARCH_TARGET="${SEARCH_TARGET:-llvm-strip}" 14 | 15 | CANDIDATES=("${SEARCH_TARGET}" "${SEARCH_TARGET}-19" "${SEARCH_TARGET}-18" "${SEARCH_TARGET}-17" "${SEARCH_TARGET}-16") 16 | 17 | BREW_PREFIX=$(brew --prefix 2> /dev/null) 18 | if [[ -n "${BREW_PREFIX}" ]]; then 19 | CANDIDATES+=( 20 | "${BREW_PREFIX}/opt/llvm/bin/${SEARCH_TARGET}" 21 | "${BREW_PREFIX}/opt/llvm@19/bin/${SEARCH_TARGET}" 22 | "${BREW_PREFIX}/opt/llvm@18/bin/${SEARCH_TARGET}" 23 | "${BREW_PREFIX}/opt/llvm@17/bin/${SEARCH_TARGET}" 24 | "${BREW_PREFIX}/opt/llvm@16/bin/${SEARCH_TARGET}" 25 | ) 26 | fi 27 | 28 | for candidate in ${CANDIDATES[@]}; do 29 | OUTPUT=$($candidate --version 2> /dev/null | grep 'version [0-9]' | head -n 1 | cut -d'.' -f 1 | grep -o '[0-9][0-9]*') 30 | 31 | if [[ $((OUTPUT)) -ge 16 ]]; then 32 | echo "${candidate/${SEARCH_TARGET}/clang}" 33 | exit 0 34 | fi 35 | done 36 | 37 | >&2 echo "Cannot find clang of version 16+!" 38 | exit 1 39 | -------------------------------------------------------------------------------- /standalone-contract/scripts/reproducible_build_docker: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # An utility script helping with reproducible script builds via docker. 4 | # Note that this utility serves only as one example, docker is not 5 | # necessarily THE way to do reproducible build, nor is it the best way 6 | # to do reproducible build. 7 | set -ex 8 | 9 | DOCKER="${DOCKER:-docker}" 10 | # docker pull docker.io/cryptape/llvm-n-rust:20250117 11 | DOCKER_IMAGE="${DOCKER_IMAGE:-docker.io/cryptape/llvm-n-rust@sha256:12e7821cb9c7cbc8988d5b1d60bcc87da4cedcf3eea32df1d8833328c5a69f88}" 12 | CHECKSUM_FILE_PATH="${CHECKSUM_FILE_PATH:-checksums.txt}" 13 | 14 | # We are parsing command line arguments based on tips from: 15 | # https://stackoverflow.com/a/14203146 16 | 17 | while [[ $# -gt 0 ]]; do 18 | case $1 in 19 | -p|--proxy) 20 | PROXY="$2" 21 | shift # past argument 22 | shift # past value 23 | ;; 24 | -u|--update) 25 | UPDATE="yes" 26 | shift # past argument 27 | ;; 28 | --no-clean) 29 | NOCLEAN="yes" 30 | shift # past argument 31 | ;; 32 | -*|--*) 33 | echo "Unknown option $1" 34 | exit 1 35 | ;; 36 | *) 37 | echo "Unknown argument $1" 38 | exit 1 39 | ;; 40 | esac 41 | done 42 | 43 | if [[ -n "${PROXY}" ]]; then 44 | DOCKER_RUN_ARGS="-e ALL_PROXY=${PROXY} -e HTTPS_PROXY=${PROXY} -e HTTP_PROXY=${PROXY} ${DOCKER_RUN_ARGS}" 45 | fi 46 | 47 | TASKS="" 48 | if [[ "${NOCLEAN}" != "yes" ]]; then 49 | TASKS+=" clean " 50 | fi 51 | 52 | if [[ "${UPDATE}" = "yes" ]]; then 53 | TASKS+=" checksum CHECKSUM_FILE=${CHECKSUM_FILE_PATH} " 54 | else 55 | TASKS+=" build " 56 | fi 57 | 58 | $DOCKER run --rm $DOCKER_RUN_ARGS -v `pwd`:/code $DOCKER_IMAGE make $TASKS 59 | # Reset file ownerships for all files docker might touch 60 | $DOCKER run --rm $DOCKER_RUN_ARGS -e UID=`id -u` -e GID=`id -g` -v `pwd`:/code $DOCKER_IMAGE bash -c 'chown -R -f $UID:$GID checksums.txt build target' 61 | 62 | if [[ "${UPDATE}" = "yes" ]]; then 63 | echo "${CHECKSUM_FILE_PATH} file is updated with latest binary hashes!" 64 | else 65 | shasum -a 256 -c ${CHECKSUM_FILE_PATH} 66 | fi 67 | -------------------------------------------------------------------------------- /standalone-contract/src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(target_arch = "riscv64", no_std)] 2 | #![cfg_attr(not(test), no_main)] 3 | 4 | #[cfg(test)] 5 | extern crate alloc; 6 | 7 | #[cfg(test)] 8 | mod tests; 9 | 10 | #[cfg(not(test))] 11 | ckb_std::entry!(program_entry); 12 | #[cfg(not(test))] 13 | // By default, the following heap configuration is used: 14 | // * 16KB fixed heap 15 | // * 1.2MB(rounded up to be 16-byte aligned) dynamic heap 16 | // * Minimal memory block in dynamic heap is 64 bytes 17 | // For more details, please refer to ckb-std's default_alloc macro 18 | // and the buddy-alloc alloc implementation. 19 | ckb_std::default_alloc!(16384, 1258306, 64); 20 | 21 | pub fn program_entry() -> i8 { 22 | ckb_std::debug!("This is a sample contract!"); 23 | 24 | 0 25 | } 26 | -------------------------------------------------------------------------------- /standalone-contract/src/tests/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use ckb_testtool::{ 4 | ckb_error::Error, 5 | ckb_types::{ 6 | bytes::Bytes, 7 | core::{Cycle, TransactionView}, 8 | }, 9 | context::Context, 10 | }; 11 | use std::env; 12 | use std::fs; 13 | use std::path::PathBuf; 14 | use std::str::FromStr; 15 | 16 | #[cfg(test)] 17 | mod tests; 18 | 19 | // The exact same Loader code from capsule's template, except that 20 | // now we use MODE as the environment variable 21 | const TEST_ENV_VAR: &str = "MODE"; 22 | 23 | pub enum TestEnv { 24 | Debug, 25 | Release, 26 | } 27 | 28 | impl FromStr for TestEnv { 29 | type Err = &'static str; 30 | 31 | fn from_str(s: &str) -> Result { 32 | match s.to_lowercase().as_str() { 33 | "debug" => Ok(TestEnv::Debug), 34 | "release" => Ok(TestEnv::Release), 35 | _ => Err("no match"), 36 | } 37 | } 38 | } 39 | 40 | pub struct Loader(PathBuf); 41 | 42 | impl Default for Loader { 43 | fn default() -> Self { 44 | let test_env = match env::var(TEST_ENV_VAR) { 45 | Ok(val) => val.parse().expect("test env"), 46 | Err(_) => TestEnv::Release, 47 | }; 48 | Self::with_test_env(test_env) 49 | } 50 | } 51 | 52 | impl Loader { 53 | fn with_test_env(env: TestEnv) -> Self { 54 | let load_prefix = match env { 55 | TestEnv::Debug => "debug", 56 | TestEnv::Release => "release", 57 | }; 58 | let mut base_path = match env::var("TOP") { 59 | Ok(val) => { 60 | let mut base_path: PathBuf = val.into(); 61 | base_path.push("build"); 62 | base_path 63 | } 64 | Err(_) => { 65 | let mut base_path = PathBuf::new(); 66 | // cargo may use a different cwd when running tests, for example: 67 | // when running debug in vscode, it will use workspace root as cwd by default, 68 | // when running test by `cargo test`, it will use tests directory as cwd, 69 | // so we need a fallback path 70 | base_path.push("build"); 71 | if !base_path.exists() { 72 | base_path.pop(); 73 | base_path.push(".."); 74 | base_path.push("build"); 75 | } 76 | base_path 77 | } 78 | }; 79 | 80 | base_path.push(load_prefix); 81 | Loader(base_path) 82 | } 83 | 84 | pub fn load_binary(&self, name: &str) -> Bytes { 85 | let mut path = self.0.clone(); 86 | path.push(name); 87 | let result = fs::read(&path); 88 | if result.is_err() { 89 | panic!("Binary {:?} is missing!", path); 90 | } 91 | result.unwrap().into() 92 | } 93 | } 94 | 95 | // This helper method runs Context::verify_tx, but in case error happens, 96 | // it also dumps current transaction to failed_txs folder. 97 | pub fn verify_and_dump_failed_tx( 98 | context: &Context, 99 | tx: &TransactionView, 100 | max_cycles: u64, 101 | ) -> Result { 102 | let result = context.verify_tx(tx, max_cycles); 103 | if result.is_err() { 104 | let mut path = env::current_dir().expect("current dir"); 105 | path.push("failed_txs"); 106 | std::fs::create_dir_all(&path).expect("create failed_txs dir"); 107 | let mock_tx = context.dump_tx(tx).expect("dump failed tx"); 108 | let json = serde_json::to_string_pretty(&mock_tx).expect("json"); 109 | path.push(format!("0x{:x}.json", tx.hash())); 110 | println!("Failed tx written to {:?}", path); 111 | std::fs::write(path, json).expect("write"); 112 | } 113 | result 114 | } 115 | -------------------------------------------------------------------------------- /standalone-contract/src/tests/tests.rs: -------------------------------------------------------------------------------- 1 | use super::Loader; 2 | use ckb_testtool::ckb_types::{ 3 | bytes::Bytes, 4 | core::TransactionBuilder, 5 | packed::*, 6 | prelude::*, 7 | }; 8 | use ckb_testtool::context::Context; 9 | 10 | // Include your tests here 11 | // See https://github.com/xxuejie/ckb-native-build-sample/blob/main/tests/src/tests.rs for more examples 12 | 13 | // generated unit test for contract {{project-name}} 14 | #[test] 15 | fn test_{{crate_name}}() { 16 | // deploy contract 17 | let mut context = Context::default(); 18 | let contract_bin: Bytes = Loader::default().load_binary("{{project-name}}"); 19 | let out_point = context.deploy_cell(contract_bin); 20 | 21 | // prepare scripts 22 | let lock_script = context 23 | .build_script(&out_point, Bytes::from(vec![42])) 24 | .expect("script"); 25 | 26 | // prepare cells 27 | let input_out_point = context.create_cell( 28 | CellOutput::new_builder() 29 | .capacity(1000u64.pack()) 30 | .lock(lock_script.clone()) 31 | .build(), 32 | Bytes::new(), 33 | ); 34 | let input = CellInput::new_builder() 35 | .previous_output(input_out_point) 36 | .build(); 37 | let outputs = vec![ 38 | CellOutput::new_builder() 39 | .capacity(500u64.pack()) 40 | .lock(lock_script.clone()) 41 | .build(), 42 | CellOutput::new_builder() 43 | .capacity(500u64.pack()) 44 | .lock(lock_script) 45 | .build(), 46 | ]; 47 | 48 | let outputs_data = vec![Bytes::new(); 2]; 49 | 50 | // build transaction 51 | let tx = TransactionBuilder::default() 52 | .input(input) 53 | .outputs(outputs) 54 | .outputs_data(outputs_data.pack()) 55 | .build(); 56 | let tx = context.complete_tx(tx); 57 | 58 | // run 59 | let cycles = context 60 | .verify_tx(&tx, 10_000_000) 61 | .expect("pass verification"); 62 | println!("consume cycles: {}", cycles); 63 | } 64 | -------------------------------------------------------------------------------- /workspace/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /target 3 | /tests/failed_txs 4 | -------------------------------------------------------------------------------- /workspace/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | 4 | members = [ 5 | # Please don't remove the following line, we use it to automatically 6 | # detect insertion point for newly generated crates. 7 | # @@INSERTION_POINT@@ 8 | "tests", 9 | ] 10 | 11 | [profile.release] 12 | overflow-checks = true 13 | strip = false 14 | codegen-units = 1 15 | debug = true 16 | -------------------------------------------------------------------------------- /workspace/Makefile: -------------------------------------------------------------------------------- 1 | # We cannot use $(shell pwd), which will return unix path format on Windows, 2 | # making it hard to use. 3 | cur_dir = $(dir $(abspath $(firstword $(MAKEFILE_LIST)))) 4 | 5 | TOP := $(cur_dir) 6 | # RUSTFLAGS that are likely to be tweaked by developers. For example, 7 | # while we enable debug logs by default here, some might want to strip them 8 | # for minimal code size / consumed cycles. 9 | CUSTOM_RUSTFLAGS := -C debug-assertions 10 | # Additional cargo args to append here. For example, one can use 11 | # make test CARGO_ARGS="-- --nocapture" so as to inspect data emitted to 12 | # stdout in unit tests 13 | CARGO_ARGS := 14 | MODE := release 15 | # Tweak this to change the clang version to use for building C code. By default 16 | # we use a bash script with somes heuristics to find clang in current system. 17 | CLANG := $(shell $(TOP)/scripts/find_clang) 18 | # When this is set, a single contract will be built instead of all contracts 19 | CONTRACT := 20 | # By default, we would clean build/{release,debug} folder first, in case old 21 | # contracts are mixed together with new ones, if for some reason you want to 22 | # revert this behavior, you can change this to anything other than true 23 | CLEAN_BUILD_DIR_FIRST := true 24 | BUILD_DIR := build/$(MODE) 25 | 26 | ifeq (release,$(MODE)) 27 | MODE_ARGS := --release 28 | endif 29 | 30 | # Pass setups to child make processes 31 | export CUSTOM_RUSTFLAGS 32 | export TOP 33 | export CARGO_ARGS 34 | export MODE 35 | export CLANG 36 | export BUILD_DIR 37 | 38 | default: build test 39 | 40 | build: 41 | @if [ "x$(CLEAN_BUILD_DIR_FIRST)" = "xtrue" ]; then \ 42 | echo "Cleaning $(BUILD_DIR) directory..."; \ 43 | rm -rf $(BUILD_DIR); \ 44 | fi 45 | mkdir -p $(BUILD_DIR) 46 | @set -eu; \ 47 | if [ "x$(CONTRACT)" = "x" ]; then \ 48 | for contract in $(wildcard contracts/*); do \ 49 | $(MAKE) -e -C $$contract build; \ 50 | done; \ 51 | for crate in $(wildcard crates/*); do \ 52 | cargo build -p $$(basename $$crate) $(MODE_ARGS) $(CARGO_ARGS); \ 53 | done; \ 54 | for sim in $(wildcard native-simulators/*); do \ 55 | cargo build -p $$(basename $$sim) $(CARGO_ARGS); \ 56 | done; \ 57 | else \ 58 | $(MAKE) -e -C contracts/$(CONTRACT) build; \ 59 | cargo build -p $(CONTRACT)-sim; \ 60 | fi; 61 | 62 | # Run a single make task for a specific contract. For example: 63 | # 64 | # make run CONTRACT=stack-reorder TASK=adjust_stack_size STACK_SIZE=0x200000 65 | TASK := 66 | run: 67 | $(MAKE) -e -C contracts/$(CONTRACT) $(TASK) 68 | 69 | # test, check, clippy and fmt here are provided for completeness, 70 | # there is nothing wrong invoking cargo directly instead of make. 71 | test: 72 | cargo test $(CARGO_ARGS) 73 | 74 | check: 75 | cargo check $(CARGO_ARGS) 76 | 77 | clippy: 78 | cargo clippy $(CARGO_ARGS) 79 | 80 | fmt: 81 | cargo fmt $(CARGO_ARGS) 82 | 83 | # Arbitrary cargo command is supported here. For example: 84 | # 85 | # make cargo CARGO_CMD=expand CARGO_ARGS="--ugly" 86 | # 87 | # Invokes: 88 | # cargo expand --ugly 89 | CARGO_CMD := 90 | cargo: 91 | cargo $(CARGO_CMD) $(CARGO_ARGS) 92 | 93 | clean: 94 | rm -rf build 95 | cargo clean 96 | 97 | TEMPLATE_TYPE := --git 98 | TEMPLATE_REPO := https://github.com/cryptape/ckb-script-templates 99 | CRATE := 100 | TEMPLATE := contract 101 | DESTINATION := contracts 102 | generate: 103 | @set -eu; \ 104 | if [ "x$(CRATE)" = "x" ]; then \ 105 | mkdir -p $(DESTINATION); \ 106 | cargo generate $(TEMPLATE_TYPE) $(TEMPLATE_REPO) $(TEMPLATE) \ 107 | --destination $(DESTINATION); \ 108 | GENERATED_DIR=$$(ls -dt $(DESTINATION)/* | head -n 1); \ 109 | if [ -f "$$GENERATED_DIR/.cargo-generate/tests.rs" ]; then \ 110 | cat $$GENERATED_DIR/.cargo-generate/tests.rs >> tests/src/tests.rs; \ 111 | rm -rf $$GENERATED_DIR/.cargo-generate/; \ 112 | fi; \ 113 | sed "s,@@INSERTION_POINT@@,@@INSERTION_POINT@@\n \"$$GENERATED_DIR\"\,," Cargo.toml > Cargo.toml.new; \ 114 | mv Cargo.toml.new Cargo.toml; \ 115 | else \ 116 | mkdir -p $(DESTINATION); \ 117 | cargo generate $(TEMPLATE_TYPE) $(TEMPLATE_REPO) $(TEMPLATE) \ 118 | --destination $(DESTINATION) \ 119 | --name $(CRATE); \ 120 | if [ -f "$(DESTINATION)/$(CRATE)/.cargo-generate/tests.rs" ]; then \ 121 | cat $(DESTINATION)/$(CRATE)/.cargo-generate/tests.rs >> tests/src/tests.rs; \ 122 | rm -rf $(DESTINATION)/$(CRATE)/.cargo-generate/; \ 123 | fi; \ 124 | sed '/@@INSERTION_POINT@@/s/$$/\n "$(DESTINATION)\/$(CRATE)",/' Cargo.toml > Cargo.toml.new; \ 125 | mv Cargo.toml.new Cargo.toml; \ 126 | fi; 127 | 128 | generate-native-simulator: 129 | @set -eu; \ 130 | if [ -z "$(CRATE)" ]; then \ 131 | echo "Error: Must have CRATE="; \ 132 | exit 1; \ 133 | fi; \ 134 | mkdir -p native-simulators; \ 135 | cargo generate $(TEMPLATE_TYPE) $(TEMPLATE_REPO) native-simulator \ 136 | -n $(CRATE)-sim \ 137 | --destination native-simulators; \ 138 | sed '/@@INSERTION_POINT@@/s/$$/\n "native-simulators\/$(CRATE)-sim",/' Cargo.toml > Cargo.toml.new; \ 139 | mv Cargo.toml.new Cargo.toml; \ 140 | if [ ! -f "contracts/$(CRATE)/Cargo.toml" ]; then \ 141 | echo "Warning: This is a non-existent contract and needs to be processed manually"; \ 142 | echo " Otherwise compilation may fail."; \ 143 | fi; 144 | 145 | prepare: 146 | rustup target add riscv64imac-unknown-none-elf 147 | 148 | # Generate checksum info for reproducible build 149 | CHECKSUM_FILE := build/checksums-$(MODE).txt 150 | checksum: build 151 | shasum -a 256 build/$(MODE)/* > $(CHECKSUM_FILE) 152 | 153 | .PHONY: build test check clippy fmt cargo clean prepare checksum 154 | -------------------------------------------------------------------------------- /workspace/README.md: -------------------------------------------------------------------------------- 1 | # {{project-name}} 2 | 3 | TODO: Write this readme 4 | 5 | *This project was bootstrapped with [ckb-script-templates].* 6 | 7 | [ckb-script-templates]: https://github.com/cryptape/ckb-script-templates 8 | -------------------------------------------------------------------------------- /workspace/cargo-generate.toml: -------------------------------------------------------------------------------- 1 | [template] 2 | cargo_generate_version = ">=0.16.0" 3 | -------------------------------------------------------------------------------- /workspace/scripts/find_clang: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # An utility script used to find a binary of clang 16+ 4 | 5 | if [[ -n "${CLANG}" ]]; then 6 | echo "${CLANG}" 7 | exit 0 8 | fi 9 | 10 | # To cope with packaging messes from different distros, we would search 11 | # for a different binary other than clang, then convert it back to clang 12 | # at the end. 13 | SEARCH_TARGET="${SEARCH_TARGET:-llvm-strip}" 14 | 15 | CANDIDATES=("${SEARCH_TARGET}" "${SEARCH_TARGET}-19" "${SEARCH_TARGET}-18" "${SEARCH_TARGET}-17" "${SEARCH_TARGET}-16") 16 | 17 | BREW_PREFIX=$(brew --prefix 2> /dev/null) 18 | if [[ -n "${BREW_PREFIX}" ]]; then 19 | CANDIDATES+=( 20 | "${BREW_PREFIX}/opt/llvm/bin/${SEARCH_TARGET}" 21 | "${BREW_PREFIX}/opt/llvm@19/bin/${SEARCH_TARGET}" 22 | "${BREW_PREFIX}/opt/llvm@18/bin/${SEARCH_TARGET}" 23 | "${BREW_PREFIX}/opt/llvm@17/bin/${SEARCH_TARGET}" 24 | "${BREW_PREFIX}/opt/llvm@16/bin/${SEARCH_TARGET}" 25 | ) 26 | fi 27 | 28 | for candidate in ${CANDIDATES[@]}; do 29 | OUTPUT=$($candidate --version 2> /dev/null | grep 'version [0-9]' | head -n 1 | cut -d'.' -f 1 | grep -o '[0-9][0-9]*') 30 | 31 | if [[ $((OUTPUT)) -ge 16 ]]; then 32 | echo "${candidate/${SEARCH_TARGET}/clang}" 33 | exit 0 34 | fi 35 | done 36 | 37 | >&2 echo "Cannot find clang of version 16+!" 38 | exit 1 39 | -------------------------------------------------------------------------------- /workspace/scripts/reproducible_build_docker: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # An utility script helping with reproducible script builds via docker. 4 | # Note that this utility serves only as one example, docker is not 5 | # necessarily THE way to do reproducible build, nor is it the best way 6 | # to do reproducible build. 7 | set -ex 8 | 9 | DOCKER="${DOCKER:-docker}" 10 | # docker pull docker.io/cryptape/llvm-n-rust:20250117 11 | DOCKER_IMAGE="${DOCKER_IMAGE:-docker.io/cryptape/llvm-n-rust@sha256:12e7821cb9c7cbc8988d5b1d60bcc87da4cedcf3eea32df1d8833328c5a69f88}" 12 | CHECKSUM_FILE_PATH="${CHECKSUM_FILE_PATH:-checksums.txt}" 13 | 14 | # We are parsing command line arguments based on tips from: 15 | # https://stackoverflow.com/a/14203146 16 | 17 | while [[ $# -gt 0 ]]; do 18 | case $1 in 19 | -p|--proxy) 20 | PROXY="$2" 21 | shift # past argument 22 | shift # past value 23 | ;; 24 | -u|--update) 25 | UPDATE="yes" 26 | shift # past argument 27 | ;; 28 | --no-clean) 29 | NOCLEAN="yes" 30 | shift # past argument 31 | ;; 32 | -*|--*) 33 | echo "Unknown option $1" 34 | exit 1 35 | ;; 36 | *) 37 | echo "Unknown argument $1" 38 | exit 1 39 | ;; 40 | esac 41 | done 42 | 43 | if [[ -n "${PROXY}" ]]; then 44 | DOCKER_RUN_ARGS="-e ALL_PROXY=${PROXY} -e HTTPS_PROXY=${PROXY} -e HTTP_PROXY=${PROXY} ${DOCKER_RUN_ARGS}" 45 | fi 46 | 47 | TASKS="" 48 | if [[ "${NOCLEAN}" != "yes" ]]; then 49 | TASKS+=" clean " 50 | fi 51 | 52 | if [[ "${UPDATE}" = "yes" ]]; then 53 | TASKS+=" checksum CHECKSUM_FILE=${CHECKSUM_FILE_PATH} " 54 | else 55 | TASKS+=" build " 56 | fi 57 | 58 | $DOCKER run --rm $DOCKER_RUN_ARGS -v `pwd`:/code $DOCKER_IMAGE make $TASKS 59 | # Reset file ownerships for all files docker might touch 60 | $DOCKER run --rm $DOCKER_RUN_ARGS -e UID=`id -u` -e GID=`id -g` -v `pwd`:/code $DOCKER_IMAGE bash -c 'chown -R -f $UID:$GID checksums.txt build target' 61 | 62 | if [[ "${UPDATE}" = "yes" ]]; then 63 | echo "${CHECKSUM_FILE_PATH} file is updated with latest binary hashes!" 64 | else 65 | shasum -a 256 -c ${CHECKSUM_FILE_PATH} 66 | fi 67 | -------------------------------------------------------------------------------- /workspace/tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tests" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [features] 7 | native-simulator = [ "ckb-testtool/native-simulator" ] 8 | 9 | [dependencies] 10 | ckb-testtool = "0.15.0" 11 | serde_json = "1.0" 12 | -------------------------------------------------------------------------------- /workspace/tests/src/lib.rs: -------------------------------------------------------------------------------- 1 | use ckb_testtool::{ 2 | ckb_error::Error, 3 | ckb_types::{ 4 | bytes::Bytes, 5 | core::{Cycle, TransactionView}, 6 | }, 7 | context::Context, 8 | }; 9 | use std::env; 10 | use std::fs; 11 | use std::path::PathBuf; 12 | use std::str::FromStr; 13 | 14 | #[cfg(test)] 15 | mod tests; 16 | 17 | // The exact same Loader code from capsule's template, except that 18 | // now we use MODE as the environment variable 19 | const TEST_ENV_VAR: &str = "MODE"; 20 | 21 | pub enum TestEnv { 22 | Debug, 23 | Release, 24 | } 25 | 26 | impl FromStr for TestEnv { 27 | type Err = &'static str; 28 | 29 | fn from_str(s: &str) -> Result { 30 | match s.to_lowercase().as_str() { 31 | "debug" => Ok(TestEnv::Debug), 32 | "release" => Ok(TestEnv::Release), 33 | _ => Err("no match"), 34 | } 35 | } 36 | } 37 | 38 | pub struct Loader(PathBuf); 39 | 40 | impl Default for Loader { 41 | fn default() -> Self { 42 | let test_env = match env::var(TEST_ENV_VAR) { 43 | Ok(val) => val.parse().expect("test env"), 44 | Err(_) => TestEnv::Release, 45 | }; 46 | Self::with_test_env(test_env) 47 | } 48 | } 49 | 50 | impl Loader { 51 | fn with_test_env(env: TestEnv) -> Self { 52 | let load_prefix = match env { 53 | TestEnv::Debug => "debug", 54 | TestEnv::Release => "release", 55 | }; 56 | let mut base_path = match env::var("TOP") { 57 | Ok(val) => { 58 | let mut base_path: PathBuf = val.into(); 59 | base_path.push("build"); 60 | base_path 61 | } 62 | Err(_) => { 63 | let mut base_path = PathBuf::new(); 64 | // cargo may use a different cwd when running tests, for example: 65 | // when running debug in vscode, it will use workspace root as cwd by default, 66 | // when running test by `cargo test`, it will use tests directory as cwd, 67 | // so we need a fallback path 68 | base_path.push("build"); 69 | if !base_path.exists() { 70 | base_path.pop(); 71 | base_path.push(".."); 72 | base_path.push("build"); 73 | } 74 | base_path 75 | } 76 | }; 77 | 78 | base_path.push(load_prefix); 79 | Loader(base_path) 80 | } 81 | 82 | pub fn load_binary(&self, name: &str) -> Bytes { 83 | let mut path = self.0.clone(); 84 | path.push(name); 85 | let result = fs::read(&path); 86 | if result.is_err() { 87 | panic!("Binary {:?} is missing!", path); 88 | } 89 | result.unwrap().into() 90 | } 91 | } 92 | 93 | // This helper method runs Context::verify_tx, but in case error happens, 94 | // it also dumps current transaction to failed_txs folder. 95 | pub fn verify_and_dump_failed_tx( 96 | context: &Context, 97 | tx: &TransactionView, 98 | max_cycles: u64, 99 | ) -> Result { 100 | let result = context.verify_tx(tx, max_cycles); 101 | if result.is_err() { 102 | let mut path = env::current_dir().expect("current dir"); 103 | path.push("failed_txs"); 104 | std::fs::create_dir_all(&path).expect("create failed_txs dir"); 105 | let mock_tx = context.dump_tx(tx).expect("dump failed tx"); 106 | let json = serde_json::to_string_pretty(&mock_tx).expect("json"); 107 | path.push(format!("0x{:x}.json", tx.hash())); 108 | println!("Failed tx written to {:?}", path); 109 | std::fs::write(path, json).expect("write"); 110 | } 111 | result 112 | } 113 | -------------------------------------------------------------------------------- /workspace/tests/src/tests.rs: -------------------------------------------------------------------------------- 1 | use crate::Loader; 2 | use ckb_testtool::ckb_types::{ 3 | bytes::Bytes, 4 | core::TransactionBuilder, 5 | packed::*, 6 | prelude::*, 7 | }; 8 | use ckb_testtool::context::Context; 9 | 10 | // Include your tests here 11 | // See https://github.com/xxuejie/ckb-native-build-sample/blob/main/tests/src/tests.rs for more examples 12 | -------------------------------------------------------------------------------- /x64-simulator-crate/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "{{project-name}}" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | ckb-std = "0.17.0" 8 | 9 | # Supporting native tests powered by ckb-x64-simulator 10 | [target.'cfg(all(target_arch = "x86_64", unix))'.dependencies.ckb-std] 11 | version = "0.17.0" 12 | features = ["native-simulator"] 13 | 14 | [target.'cfg(all(target_arch = "x86_64", unix))'.dev-dependencies] 15 | ckb-testtool = "0.15.0" 16 | rusty-fork = "0.3.0" 17 | rand = "0.8.5" 18 | serde_json = "1.0" 19 | tempfile = "3.9.0" 20 | -------------------------------------------------------------------------------- /x64-simulator-crate/README.md: -------------------------------------------------------------------------------- 1 | # {{project-name}} 2 | 3 | TODO: Write this readme 4 | 5 | *This crate was bootstrapped with [ckb-script-templates].* 6 | 7 | [ckb-script-templates]: https://github.com/cryptape/ckb-script-templates 8 | -------------------------------------------------------------------------------- /x64-simulator-crate/cargo-generate.toml: -------------------------------------------------------------------------------- 1 | [template] 2 | cargo_generate_version = ">=0.16.0" 3 | -------------------------------------------------------------------------------- /x64-simulator-crate/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(test), no_std)] 2 | 3 | pub fn foo() -> usize { 4 | ckb_std::high_level::load_witness(0, ckb_std::ckb_constants::Source::Input) 5 | .expect("load_witness") 6 | .len() 7 | } 8 | 9 | // Here we provide a native runnable test sample. The test uses ckb-x64-simulator 10 | // to mock CKB syscalls. 11 | #[cfg(all(test, target_arch = "x86_64", unix))] 12 | mod tests { 13 | use super::*; 14 | use ckb_testtool::ckb_types::{core::TransactionBuilder, prelude::*}; 15 | use ckb_testtool::context::Context; 16 | use rand::{rngs::StdRng, Rng, SeedableRng}; 17 | use rusty_fork::rusty_fork_test; 18 | use std::io::Write; 19 | 20 | // TODO: Right now ckb-x64-simulator has no way of resetting the 21 | // test transaction after initial setup. Hence we have to use this 22 | // circumvent way of testing. Later we would want to fix ckb-x64-simulator 23 | // so test data can be properly mutated, after that, we can switch 24 | // to proptest for testing here. 25 | rusty_fork_test! { 26 | #[test] 27 | fn test_any_data() { 28 | let seed: u64 = match std::env::var("SEED") { 29 | Ok(val) => str::parse(&val).expect("parsing number"), 30 | Err(_) => std::time::SystemTime::now() 31 | .duration_since(std::time::SystemTime::UNIX_EPOCH) 32 | .unwrap() 33 | .as_nanos() as u64, 34 | }; 35 | println!("Seed: {}", seed); 36 | 37 | let mut rng = StdRng::seed_from_u64(seed); 38 | let length = rng.gen_range(0..614400usize); 39 | let data = { 40 | let mut data = vec![0u8; length]; 41 | rng.fill(&mut data[..]); 42 | data 43 | }; 44 | let data_length = data.len(); 45 | 46 | let file = { 47 | // Build a tx using data as a cell 48 | let context = Context::default(); 49 | let tx = TransactionBuilder::default() 50 | .witness(data.pack()) 51 | .build(); 52 | 53 | let mock_tx = context.dump_tx(&tx).expect("dump tx"); 54 | 55 | // Keep the tx in a temporary file, then set the environment 56 | // variable for ckb-x64-simulator 57 | let json = serde_json::to_string_pretty(&mock_tx).expect("json"); 58 | let mut file = tempfile::NamedTempFile::new().expect("tempfile"); 59 | file.write_all(json.as_ref()).expect("write"); 60 | file.flush().expect("flush"); 61 | std::env::set_var("CKB_TX_FILE", file.path()); 62 | file 63 | }; 64 | 65 | assert_eq!(foo(), data_length); 66 | 67 | drop(file); 68 | } 69 | } 70 | } 71 | --------------------------------------------------------------------------------