Skip to content

Commit

Permalink
feat: add Type ID implementation (#108)
Browse files Browse the repository at this point in the history
  • Loading branch information
XuJiandong authored Sep 25, 2024
1 parent 5f0a6c3 commit 1317840
Show file tree
Hide file tree
Showing 17 changed files with 382 additions and 19 deletions.
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
9 changes: 6 additions & 3 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,10 +19,10 @@ test-shared-lib:

integration: check

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

check:
cargo check --target ${TARGET} --examples
cargo check --target ${TARGET} --examples --features type-id,build-with-clang

.PHONY: test check
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
3 changes: 2 additions & 1 deletion contracts/ckb-std-tests/src/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,8 @@ fn test_dynamic_loading_c_impl(context: &mut ContextType) {
fn test_vm_version() {
let version = syscalls::vm_version().unwrap();
debug!("vm version: {}", version);
assert_eq!(version, 1);
// currently, version 1(before hardfork) and 2(after hardfork) are both ok
assert!(version == 1 || version == 2);
}

fn test_current_cycles() {
Expand Down
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), +));
};
}
4 changes: 3 additions & 1 deletion src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ pub enum SysError {
MaxVmsSpawned,
/// Max fds has been spawned. Its value is 9.
MaxFdsCreated,

/// Type ID Error
#[cfg(feature = "type-id")]
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;
141 changes: 141 additions & 0 deletions src/type_id.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
//! 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> {
// after this checking, there are 3 cases:
// 1. 0 input cell and 1 output cell, it's minting operation
// 2. 1 input cell and 1 output cell, it's transfer operation
// 3. 1 input cell and 0 output cell, it's burning operation(allowed)
if is_cell_present(1, Source::GroupInput) || is_cell_present(1, Source::GroupOutput) {
return Err(SysError::TypeIDError);
}

// case 1: minting operation
if !is_cell_present(0, Source::GroupInput) {
let index = locate_index()? as u64;
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);
}
}
// case 2 & 3: for the `else` part, it's transfer operation or burning operation
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(())
}
7 changes: 4 additions & 3 deletions test/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
ckb-x64-simulator = "0.7"
ckb-testtool = "0.8.0"
ckb-x64-simulator = "0.9.2"
ckb-testtool = "0.13.1"
serde_json = "1.0"
ckb-mock-tx-types = "0.4.0"
ckb-mock-tx-types = "0.118.0"
blake2b-rs = "0.1.5"
faster-hex = "0.6"
ckb-hash = "0.118.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
3 changes: 2 additions & 1 deletion test/src/contract.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use super::util::dump_mock_tx;
use ckb_testtool::ckb_types::{bytes::Bytes, core::TransactionBuilder, packed::*, prelude::*};
use ckb_testtool::context::Context;
use ckb_x64_simulator::RunningSetup;
use ckb_x64_simulator::{RunningSetup, RunningType};
use std::collections::HashMap;
use std::fs::File;
use std::io::Read;
Expand Down Expand Up @@ -86,6 +86,7 @@ fn it_works() {
script_index: 0,
vm_version: 1,
native_binaries: HashMap::default(),
run_type: Some(RunningType::Executable),
};
dump_mock_tx(test_case_name, &tx, &context, &setup);

Expand Down
13 changes: 8 additions & 5 deletions test/src/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use ckb_testtool::{
},
context::Context,
};
use ckb_x64_simulator::RunningSetup;
use ckb_x64_simulator::{RunningSetup, RunningType};
use std::collections::HashMap;
use std::fs::File;
use std::io::Read;
Expand Down 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 Expand Up @@ -159,6 +161,7 @@ fn test_exec_by_code_hash() {
script_index: 0,
vm_version: 1,
native_binaries,
run_type: Some(RunningType::Executable),
};
dump_mock_tx(test_case_name, &tx, &context, &setup);

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

0 comments on commit 1317840

Please sign in to comment.