This document provides guidelines for AI agents working on the ckb-testtool codebase.
ckb-testtool is a Rust library for testing CKB (Nervos Common Knowledge Base) smart contracts.
It provides a Context struct for deploying contracts, building transactions, and verifying them
using CKB-VM. Originally migrated from capsule.
Rust Edition: 2024
Rust Toolchain: 1.85.0 (specified in rust-toolchain)
| Command | Description |
|---|---|
cargo build |
Build the library |
cargo build --features="native-simulator" |
Build with native-simulator |
cargo fmt --all -- --check |
Check formatting |
cargo clippy --all |
Run clippy linting |
cargo test --all |
Run all tests |
cargo test test_name |
Run specific test by name |
cargo test test_name -- --nocapture |
Run test with output |
cargo test --features="native-simulator" --all |
Run with native-simulator |
cargo test --test normal_cases |
Run tests in specific file |
cd tests/test-contracts && make build |
Build test contracts |
rustup target add riscv64imac-unknown-none-elf |
Add RISC-V target |
ckb-testtool/
├── Cargo.toml # Main crate manifest
├── rust-toolchain # Rust version: 1.85.0
├── rustfmt.toml # Formatting configuration
├── src/
│ ├── lib.rs # Crate entry with re-exports and docs
│ ├── context.rs # Main Context struct for testing
│ ├── builtin.rs # Built-in contracts (ALWAYS_SUCCESS)
│ └── tx_verifier.rs # Transaction verification (private)
└── tests/
├── normal_cases.rs # Standard test cases
├── native_simulator_cases.rs # Native simulator tests
└── test-contracts/ # Test contracts workspace
native-simulator: Enables native simulation testing withlibloadingandserde_json
max_width = 100
tab_spaces = 4
reorder_imports = true
reorder_modules = true
use_try_shorthand = trueAlways run cargo fmt before committing.
Order imports as follows:
- Crate-level imports (
crate::) - External crate imports
- Standard library imports (
std::)
Use nested imports for multiple items from the same module:
use crate::tx_verifier::OutputsDataVerifier;
use ckb_types::{
bytes::Bytes,
core::{Capacity, Cycle, TransactionView},
packed::{Byte32, CellDep, CellOutput, OutPoint, Script},
prelude::*,
};
use std::collections::HashMap;| Element | Convention | Example |
|---|---|---|
| Functions | snake_case | deploy_cell, verify_tx |
| Types | PascalCase | Context, Message |
| Variables | snake_case | out_point, data_hash |
| Constants | SCREAMING_SNAKE_CASE | MAX_CYCLES, SIGNATURE_SIZE |
| Modules | snake_case | tx_verifier, context |
- Always specify return types for public functions
- Use explicit types when not obvious from context
- Use lifetime annotations when required
pub fn random_hash() -> Byte32 { ... }
pub fn verify_tx(&self, tx: &TransactionView, max_cycles: u64) -> Result<Cycle, CKBError> { ... }- Use
Result<T, E>for fallible operations - Use
Option<T>for optional returns - Prefer
?operator for error propagation - Use
.expect("descriptive message")for programmer errors - Use
.unwrap_or_else()for complex error handling
pub fn verify_tx(&self, tx: &TransactionView, max_cycles: u64) -> Result<Cycle, CKBError> {
self.verify_tx_consensus(tx)?;
// ...
}
let path = self.get_contract_path(filename).expect("get contract path");- Use
//!for module-level documentation - Use
///for public API documentation - Include
# Examplesections withrust,no_runcode blocks
- Use
#[test]attribute for test functions - Define constants like
MAX_CYCLESat the top - Follow the pattern: setup context -> deploy contracts -> build tx -> verify
const MAX_CYCLES: u64 = 500_0000;
#[test]
fn test_example() {
let mut context = Context::default();
let out_point = context.deploy_cell(contract_bin);
// ... build transaction ...
context.verify_tx(&tx, MAX_CYCLES).expect("pass verification");
}Use underscores for readability: 500_0000, 500_000_000_000
Context::default()- Create new contextcontext.deploy_cell(data)- Deploy contract, returnsOutPointcontext.deploy_cell_by_name(name)- Deploy contract from search pathcontext.build_script(&out_point, args)- Build script from deployed contractcontext.create_cell(output, data)- Create input cellcontext.complete_tx(tx)- Add cell deps automaticallycontext.verify_tx(&tx, max_cycles)- Verify transaction
The CI runs on every push and PR:
- Format check:
cargo fmt --all -- --check - Clippy:
cargo clippy --all - Build:
cargo build - Tests:
cargo test --all - Native simulator tests:
cargo test --features="native-simulator" --all
Ensure all checks pass before submitting PRs.