Rust轻量级IC模拟库pocket-ic的使用,高效测试和模拟Internet Computer智能合约

Rust轻量级IC模拟库pocket-ic的使用,高效测试和模拟Internet Computer智能合约

PocketIC是一个用于Internet Computer的本地canister测试解决方案。这个测试库与PocketIC服务器配合使用,允许您与本地IC实例及其上的canister进行交互。

使用PocketIC Rust测试canister就像调用Rust函数一样简单。以下是一个简单示例:

use candid::{Principal, encode_one};
use pocket_ic::PocketIc;

// 2T cycles
const INIT_CYCLES: u128 = 2_000_000_000_000;

#[test]
fn test_counter_canister() {
    let pic = PocketIc::new();

    // 创建一个canister并充值2T cycles
    let canister_id = pic.create_canister();
    pic.add_cycles(canister_id, INIT_CYCLES);

    // 在canister上安装计数器canister wasm文件
    let counter_wasm = todo!();
    pic.install_canister(canister_id, counter_wasm, vec![], None);

    // 对canister进行一些调用
    let reply = call_counter_can(&pic, canister_id, "read");
    assert_eq!(reply, vec![0, 0, 0, 0]);
    let reply = call_counter_can(&pic, canister_id, "write");
    assert_eq!(reply, vec![1, 0, 0, 0]);
    let reply = call_counter_can(&pic, canister_id, "write");
    assert_eq!(reply, vec![2, 0, 0, 0]);
    let reply = call_counter_can(&pic, canister_id, "read");
    assert_eq!(reply, vec![2, 0, 0, 0]);
}

fn call_counter_can(pic: &PocketIc, canister_id: Principal, method: &str) -> Vec<u8> {
    pic.update_call(
        canister_id,
        Principal::anonymous(),
        method,
        encode_one(()).unwrap(),
    )
    .expect("Failed to call counter canister")
}

快速开始

  • 从PocketIC repo下载最新与您使用的库版本兼容的PocketIC服务器
  • 解压下载的文件
  • 在UNIX上:使下载的文件可执行
  • 使用函数PocketIcBuilder::with_server_binary或环境变量POCKET_IC_BIN指定二进制文件的路径
  • 使用cargo add pocket-ic将PocketIC Rust添加到您的项目中
  • 在您的Rust代码中使用use pocket_ic::PocketIc导入PocketIC,并使用let pic = PocketIc::new()创建一个新的PocketIC实例,开始测试!

完整示例

以下是一个更完整的示例,展示如何使用pocket-ic测试一个简单的计数器canister:

use candid::{Principal, encode_one, decode_one};
use pocket_ic::PocketIc;
use std::path::PathBuf;

const INIT_CYCLES: u128 = 2_000_000_000_000;

#[test]
fn test_counter_canister_workflow() {
    // 初始化PocketIC实例
    let pic = PocketIc::new();
    
    // 创建canister
    let canister_id = pic.create_canister();
    pic.add_cycles(canister_id, INIT_CYCLES);
    
    // 加载wasm模块 (实际使用时替换为您的wasm文件路径)
    let wasm_path = PathBuf::from("./target/wasm32-unknown-unknown/release/counter.wasm");
    let counter_wasm = std::fs::read(wasm_path).expect("Could not read wasm file");
    
    // 安装canister
    pic.install_canister(canister_id, counter_wasm, vec![], None);
    
    // 测试初始状态
    let reply: u32 = decode_one(&call_counter(&pic, canister_id, "read")).unwrap();
    assert_eq!(reply, 0);
    
    // 测试增量操作
    let reply: u32 = decode_one(&call_counter(&pic, canister_id, "increment")).unwrap();
    assert_eq!(reply, 1);
    
    let reply: u32 = decode_one(&call_counter(&pic, canister_id, "increment")).unwrap();
    assert_eq!(reply, 2);
    
    // 验证当前值
    let reply: u32 = decode_one(&call_counter(&pic, canister_id, "read")).unwrap();
    assert_eq!(reply, 2);
    
    // 测试重置操作
    let reply: u32 = decode_one(&call_counter(&pic, canister_id, "reset")).unwrap();
    assert_eq!(reply, 0);
    
    // 再次验证
    let reply: u32 = decode_one(&call_counter(&pic, canister_id, "read")).unwrap();
    assert_eq!(reply, 0);
}

fn call_counter(pic: &PocketIc, canister_id: Principal, method: &str) -> Vec<u8> {
    pic.update_call(
        canister_id,
        Principal::anonymous(),
        method,
        encode_one(()).unwrap(),
    )
    .expect(&format!("Failed to call counter canister method: {}", method))
}

这个示例展示了一个完整的测试流程:

  1. 创建新的PocketIC实例
  2. 创建canister并充值cycles
  3. 安装wasm模块
  4. 测试各种canister方法
  5. 验证状态变化

要运行这个测试,您需要先编译您的计数器canister wasm文件,并将其放在预期的路径下。实际项目中,您可能需要调整wasm文件的路径。


1 回复

Rust轻量级IC模拟库pocket-ic的使用:高效测试和模拟Internet Computer智能合约

介绍

pocket-ic是一个轻量级的Rust库,用于在本地环境中模拟Internet Computer(IC)区块链的行为,使开发者能够高效地测试和调试智能合约(canisters),而无需部署到实际的IC网络上。

主要特点:

  • 轻量级且快速,适合本地开发和测试
  • 提供与真实IC网络相似的API接口
  • 支持多canister交互测试
  • 可以模拟IC的持久化状态
  • 开源且易于集成到现有测试流程中

安装方法

在Cargo.toml中添加依赖:

[dependencies]
pocket-ic = "0.15"

或者使用最新GitHub版本:

[dependencies]
pocket-ic = { git = "https://github.com/dfinity/pocket-ic" }

基本使用方法

1. 初始化pocket-ic实例

use pocket_ic::{PocketIc, WasmResult};

fn main() {
    // 创建pocket-ic实例
    let pic = PocketIc::new();
    
    // 创建测试canister
    let canister_id = pic.create_canister();
    pic.add_cycles(canister_id, 1_000_000_000_000); // 添加cycles
    
    println!("Created canister with id: {:?}", canister_id);
}

2. 安装和调用canister

use pocket_ic::{PocketIc, WasmResult};
use candid::{Encode, Decode};

#[test]
fn test_counter_canister() {
    let pic = PocketIc::new();
    let canister_id = pic.create_canister();
    
    // 安装wasm模块 (假设已编译好的counter canister wasm)
    let wasm_module = std::fs::read("counter.wasm").unwrap();
    pic.install_canister(canister_id, wasm_module, vec![], None);
    
    // 调用canister的increment方法
    let _ = pic.update_call(
        canister_id,
        "increment",
        Encode!().unwrap()
    );
    
    // 查询当前计数
    let reply = pic.query_call(
        canister_id,
        "get_count",
        Encode!().unwrap()
    );
    
    let count: u64 = Decode!(&reply).unwrap();
    assert_eq(count, 1);
}

3. 模拟时间流逝

use pocket_ic::{PocketIc, WasmResult};
use std::time::Duration;

#[test]
fn test_timer() {
    let mut pic = PocketIc::new();
    let canister_id = pic.create_canister();
    
    // 安装定时器canister...
    
    // 向前推进时间 (秒)
    pic.advance_time(Duration::from_secs(60));
    
    // 触发定时器检查
    pic.tick();
}

高级功能

1. 多canister交互测试

use pocket_ic::{PocketIc, WasmResult};
use candid::{Encode, Decode};

#[test]
fn test_multi_canister() {
    let pic = PocketIc::new();
    
    // 创建两个canister
    let canister_a = pic.create_canister();
    let canister_b = pic.create_canister();
    
    // 安装canister A和B...
    
    // 从A调用B的方法
    let result = pic.update_call(
        canister_a,
        "call_b",
        Encode!(&canister_b, "some_method").unwrap()
    );
    
    // 验证结果...
}

2. 状态持久化和恢复

use pocket_ic::{PocketIc, WasmResult};

#[test]
fn test_state_persistence() {
    let mut pic = PocketIc::new();
    let canister_id = pic.create_canister();
    
    // 安装和操作canister...
    
    // 获取当前状态快照
    let state = pic.get_state();
    
    // 恢复状态
    pic.set_state(state);
    
    // 验证状态是否恢复...
}

完整示例代码

下面是一个完整的计数器canister测试示例:

use pocket_ic::{PocketIc, WasmResult};
use candid::{Encode, Decode, Principal};

// 定义计数器canister接口
#[derive(candid::CandidType, candid::Deserialize)]
struct CounterInitArg {
    initial_value: u64,
}

// 测试计数器canister
#[test]
fn test_counter_canister_workflow() {
    // 1. 初始化pocket-ic实例
    let pic = PocketIc::new();
    
    // 2. 创建canister
    let canister_id = pic.create_canister();
    pic.add_cycles(canister_id, 1_000_000_000_000);
    
    // 3. 加载wasm模块
    let wasm_module = std::fs::read("counter.wasm")
        .expect("Could not read wasm file");
    
    // 4. 初始化计数器canister,设置初始值为0
    let init_arg = Encode!(&CounterInitArg { initial_value: 0 }).unwrap();
    pic.install_canister(canister_id, wasm_module, init_arg, None);
    
    // 5. 测试增加计数
    let _ = pic.update_call(
        canister_id,
        Principal::anonymous(),
        "increment",
        Encode!().unwrap()
    ).expect("Call to increment failed");
    
    // 6. 查询当前计数
    let reply = pic.query_call(
        canister_id,
        Principal::anonymous(),
        "get_count",
        Encode!().unwrap()
    ).expect("Call to get_count failed");
    
    let count: u64 = Decode!(&reply).unwrap();
    assert_eq!(count, 1, "Counter should be 1 after increment");
    
    // 7. 测试重置功能
    let _ = pic.update_call(
        canister_id,
        Principal::anonymous(),
        "reset",
        Encode!().unwrap()
    ).expect("Call to reset failed");
    
    // 8. 验证重置结果
    let reply = pic.query_call(
        canister_id,
        Principal::anonymous(),
        "get_count",
        Encode!().unwrap()
    ).expect("Call to get_count failed");
    
    let count: u64 = Decode!(&reply).unwrap();
    assert_eq!(count, 0, "Counter should be 0 after reset");
}

最佳实践

  1. 隔离测试:每个测试用例使用独立的pocket-ic实例,避免状态污染
  2. 合理使用cycles:模拟真实的cycles消耗情况
  3. 时间敏感测试:使用advance_timetick测试时间相关逻辑
  4. 错误处理:检查WasmResult中的错误情况

与真实IC网络的区别

  1. 不需要ICP代币或cycles充值
  2. 执行速度更快,没有网络延迟
  3. 持久化是模拟的,重启后会丢失状态
  4. 某些高级功能可能不完全相同

pocket-ic是开发IC智能合约时不可或缺的工具,可以大大提高开发效率和测试覆盖率。

回到顶部