Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add Type ID implementation #108

Merged
merged 12 commits into from
Sep 25, 2024
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@ jobs:
- name: Push
run: |
cargo login ${{ secrets.CARGO_REGISTRY_TOKEN }}
cargo publish
make publish-crate
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,17 @@ libc = []
# work with `target-feature=-a` Cargo flag
dummy-atomic = []
log = ["dep:log", "dummy-atomic"]
# require `ckb-hash`
type-id = ["ckb-hash", "ckb-types"]


[build-dependencies]
cc = "1.0"

[dependencies]
ckb-types = { package = "ckb-gen-types", version = "0.118", default-features = false, optional = true }
ckb-hash = { version = "0.118", default-features = false, features = ["ckb-contract"], optional = true }

buddy-alloc = { version = "0.5", optional = true }
ckb-x64-simulator = { version = "0.9", optional = true }
gcd = "2.3"
Expand Down
7 changes: 5 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ CC := riscv64-unknown-elf-gcc
default: integration

publish-crate:
cargo publish -p ckb-std
cargo publish --features build-with-clang --target ${TARGET} -p ckb-std

publish-crate-dryrun:
cargo publish --dry-run --features build-with-clang --target ${TARGET} -p ckb-std --allow-dirty

publish: publish-crate

Expand All @@ -16,7 +19,7 @@ test-shared-lib:

integration: check

test:
test: publish-crate-dryrun
make -C test test

check:
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# ckb-std
[![Crates.io](https://img.shields.io/crates/v/ckb-std.svg)](https://crates.io/crates/ckb-std)
[![Crates.io](https://img.shields.io/crates/v/ckb-std.svg)](https://crates.io/crates/ckb-std)

This library contains several modules that help you write CKB contract with Rust.

Expand All @@ -17,6 +17,7 @@ This library contains several modules that help you write CKB contract with Rust
* `default_alloc!` macro: defines global allocator for no-std rust
* `dummy_atomic` module: dummy atomic operations
* `logger` module: colored logger implementation
* `type_id` module: Type ID implementation (feature `type-id`)
### Memory allocator

Default allocator uses a mixed allocation strategy:
Expand Down
1 change: 1 addition & 0 deletions contracts/ckb-std-tests/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ impl From<SysError> for Error {
MaxVmsSpawned => Self::MaxVmsSpawned,
MaxFdsCreated => Self::MaxFdsCreated,
Unknown(err_code) => panic!("unexpected sys error {}", err_code),
_ => panic!("other sys error"),
}
}
}
1 change: 1 addition & 0 deletions contracts/exec-callee/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ impl From<SysError> for Error {
MaxVmsSpawned => Self::MaxVmsSpawned,
MaxFdsCreated => Self::MaxFdsCreated,
Unknown(err_code) => panic!("unexpected sys error {}", err_code),
_ => panic!("other sys error"),
}
}
}
1 change: 1 addition & 0 deletions contracts/exec-caller-by-code-hash/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ impl From<SysError> for Error {
MaxVmsSpawned => Self::MaxVmsSpawned,
MaxFdsCreated => Self::MaxFdsCreated,
Unknown(err_code) => panic!("unexpected sys error {}", err_code),
_ => panic!("other sys error"),
}
}
}
1 change: 1 addition & 0 deletions contracts/exec-caller/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ impl From<SysError> for Error {
MaxVmsSpawned => Self::MaxVmsSpawned,
MaxFdsCreated => Self::MaxFdsCreated,
Unknown(err_code) => panic!("unexpected sys error {}", err_code),
_ => panic!("other sys error"),
}
}
}
1 change: 1 addition & 0 deletions contracts/spawn-callee/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ impl From<SysError> for Error {
MaxVmsSpawned => Self::MaxVmsSpawned,
MaxFdsCreated => Self::MaxFdsCreated,
Unknown(err_code) => panic!("unexpected sys error {}", err_code),
_ => panic!("other sys error"),
}
}
}
1 change: 1 addition & 0 deletions contracts/spawn-caller-by-code-hash/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ impl From<SysError> for Error {
MaxVmsSpawned => Self::MaxVmsSpawned,
MaxFdsCreated => Self::MaxFdsCreated,
Unknown(err_code) => panic!("unexpected sys error {}", err_code),
_ => panic!("other sys error"),
}
}
}
1 change: 1 addition & 0 deletions contracts/spawn-caller/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ impl From<SysError> for Error {
MaxVmsSpawned => Self::MaxVmsSpawned,
MaxFdsCreated => Self::MaxFdsCreated,
Unknown(err_code) => panic!("unexpected sys error {}", err_code),
_ => panic!("other sys error"),
}
}
}
15 changes: 15 additions & 0 deletions examples/type_id.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#![no_std]
#![no_main]

use ckb_std::type_id::check_type_id;
use ckb_std::{default_alloc, entry};

entry!(main);
default_alloc!();

fn main() -> i8 {
match check_type_id(0) {
Ok(_) => 0,
Err(_) => -10,
}
}
2 changes: 0 additions & 2 deletions src/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,9 @@ macro_rules! debug {
macro_rules! debug {

($fmt:literal) => {
#[cfg(std)]
println!("{}", format!($fmt));
};
($fmt:literal, $($args:expr),+) => {
#[cfg(std)]
println!("{}", format!($fmt, $($args), +));
};
}
3 changes: 2 additions & 1 deletion src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ pub enum SysError {
MaxVmsSpawned,
/// Max fds has been spawned. Its value is 9.
MaxFdsCreated,

/// Type ID Error
TypeIDError,
/// Unknown syscall error number
Unknown(u64),
}
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,5 @@ pub mod dummy_atomic;
pub mod logger;
#[cfg(feature = "log")]
pub use log;
#[cfg(feature = "type-id")]
pub mod type_id;
138 changes: 138 additions & 0 deletions src/type_id.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
//! Implementation of Type ID
//!
//! This module provides functionality for validating and checking Type IDs in
//! CKB transactions. It requires "type-id" feature in ckb-std enabled.
//!
//! For more details, see the [Type ID
//! RFC](https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0022-transaction-structure/0022-transaction-structure.md#type-id).
//!
//! Note: Type ID cells are allowed to be burned.
//!
use crate::{
ckb_constants::Source,
error::SysError,
high_level::{load_cell_type_hash, load_input, load_script, load_script_hash, QueryIter},
syscalls::load_cell,
};
use ckb_hash::new_blake2b;
use ckb_types::prelude::Entity;

fn is_cell_present(index: usize, source: Source) -> bool {
let buf = &mut [];
matches!(
load_cell(buf, 0, index, source),
Ok(_) | Err(SysError::LengthNotEnough(_))
)
}

fn locate_index() -> Result<usize, SysError> {
let hash = load_script_hash()?;

let index = QueryIter::new(load_cell_type_hash, Source::Output)
.position(|type_hash| type_hash == Some(hash))
.ok_or(SysError::TypeIDError)?;

Ok(index)
}

///
/// Validates the Type ID in a flexible manner.
///
/// This function performs a low-level validation of the Type ID. It checks for the
/// presence of cells in the transaction and validates the Type ID based on whether
/// it's a minting operation or a transfer.
///
/// # Arguments
///
/// * `type_id` - A 32-byte array representing the Type ID to validate.
///
/// # Returns
///
/// * `Ok(())` if the Type ID is valid.
/// * `Err(SysError::TypeIDError)` if the validation fails.
///
/// # Note
///
/// For most use cases, it's recommended to use the `check_type_id` function instead,
/// which expects the Type ID to be included in the script `args`.
///
/// # Examples
///
/// ```no_run
/// use ckb_std::type_id::validate_type_id;
///
/// let type_id = [0u8; 32];
/// validate_type_id(type_id)?;
/// ```
pub fn validate_type_id(type_id: [u8; 32]) -> Result<(), SysError> {
if is_cell_present(1, Source::GroupInput) || is_cell_present(1, Source::GroupOutput) {
return Err(SysError::TypeIDError);
}

// mint
if !is_cell_present(0, Source::GroupInput) {
let index = locate_index()?;
let input = load_input(0, Source::Input)?;
let mut blake2b = new_blake2b();
blake2b.update(input.as_slice());
blake2b.update(&index.to_le_bytes());
let mut ret = [0; 32];
blake2b.finalize(&mut ret);

if ret != type_id {
return Err(SysError::TypeIDError);
}
}
// For the `else` part, Destroy an old cell with a specific type id and
// create a new cell with the same type id in the same transaction.
Ok(())
}

fn load_id_from_args(offset: usize) -> Result<[u8; 32], SysError> {
let script = load_script()?;
let args = script.as_reader().args();
let args_data = args.raw_data();

args_data
.get(offset..offset + 32)
.ok_or(SysError::TypeIDError)?
.try_into()
.map_err(|_| SysError::TypeIDError)
}

///
/// Validates that the script follows the Type ID rule.
///
/// This function checks if the Type ID (a 32-byte value) stored in the script's `args`
/// at the specified offset is valid according to the Type ID rules.
///
/// # Arguments
///
/// * `offset` - The byte offset in the script's `args` where the Type ID starts.
///
/// # Returns
///
/// * `Ok(())` if the Type ID is valid.
/// * `Err(SysError::TypeIDError)` if the Type ID is invalid or cannot be retrieved.
///
/// # Examples
///
/// ```no_run
/// use ckb_std::type_id::check_type_id;
///
/// fn main() -> Result<(), ckb_std::error::SysError> {
/// // Check the Type ID stored at the beginning of the script args
/// check_type_id(0)?;
/// Ok(())
/// }
/// ```
///
/// # Note
///
/// This function internally calls `load_id_from_args` to retrieve the Type ID
/// and then `validate_type_id` to perform the actual validation.
pub fn check_type_id(offset: usize) -> Result<(), SysError> {
let type_id = load_id_from_args(offset)?;
validate_type_id(type_id)?;
Ok(())
}
1 change: 1 addition & 0 deletions test/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ serde_json = "1.0"
ckb-mock-tx-types = "0.4.0"
blake2b-rs = "0.1.5"
faster-hex = "0.6"
ckb-hash = "0.104.0"
6 changes: 5 additions & 1 deletion test/Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
test: build
test: build build-examples
RUST_LOG=debug cargo test -- --nocapture
make -C simulator build
make -C simulator run
Expand All @@ -8,6 +8,10 @@ build:
make -C shared-lib all-via-docker
cd ../contracts && RUSTFLAGS="-C target-feature=-a" cargo build --target riscv64imac-unknown-none-elf

build-examples:
cd ../examples && RUSTFLAGS="-C target-feature=-a" cargo build --features build-with-clang --example type_id --target riscv64imac-unknown-none-elf --features "type-id"
cd ../examples && RUSTFLAGS="-C target-feature=-a" cargo build --features build-with-clang --example main --target riscv64imac-unknown-none-elf

clean:
rm -rf ../build
cargo clean
Expand Down
1 change: 1 addition & 0 deletions test/simulator/src/exec_callee.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ pub mod error {
MaxVmsSpawned => Self::MaxVmsSpawned,
MaxFdsCreated => Self::MaxFdsCreated,
Unknown(err_code) => panic!("unexpected sys error {}", err_code),
_ => panic!("other sys error"),
}
}
}
Expand Down
1 change: 1 addition & 0 deletions test/simulator/src/exec_caller_by_code_hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ pub mod error {
MaxVmsSpawned => Self::MaxVmsSpawned,
MaxFdsCreated => Self::MaxFdsCreated,
Unknown(err_code) => panic!("unexpected sys error {}", err_code),
_ => panic!("other sys error"),
}
}
}
Expand Down
1 change: 1 addition & 0 deletions test/simulator/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ pub mod error {
MaxVmsSpawned => Self::MaxVmsSpawned,
MaxFdsCreated => Self::MaxFdsCreated,
Unknown(err_code) => panic!("unexpected sys error {}", err_code),
_ => panic!("other sys error"),
}
}
}
Expand Down
10 changes: 6 additions & 4 deletions test/src/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,12 @@ fn test_exec_by_code_hash() {
let mut context = Context::default();
let caller_bin = {
let mut buf = Vec::new();
File::open("../contracts/target/riscv64imac-unknown-none-elf/debug/exec-caller-by-code-hash")
.unwrap()
.read_to_end(&mut buf)
.expect("read code");
File::open(
"../contracts/target/riscv64imac-unknown-none-elf/debug/exec-caller-by-code-hash",
)
.unwrap()
.read_to_end(&mut buf)
.expect("read code");
Bytes::from(buf)
};
let caller_out_point = context.deploy_cell(caller_bin);
Expand Down
2 changes: 2 additions & 0 deletions test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@ mod contract;
#[cfg(test)]
mod exec;
#[cfg(test)]
mod type_id;
#[cfg(test)]
mod util;
Loading