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
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
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,
XuJiandong marked this conversation as resolved.
Show resolved Hide resolved
/// 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());
XuJiandong marked this conversation as resolved.
Show resolved Hide resolved
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
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
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
Loading