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))
}
这个示例展示了一个完整的测试流程:
- 创建新的PocketIC实例
- 创建canister并充值cycles
- 安装wasm模块
- 测试各种canister方法
- 验证状态变化
要运行这个测试,您需要先编译您的计数器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");
}
最佳实践
- 隔离测试:每个测试用例使用独立的pocket-ic实例,避免状态污染
- 合理使用cycles:模拟真实的cycles消耗情况
- 时间敏感测试:使用
advance_time
和tick
测试时间相关逻辑 - 错误处理:检查WasmResult中的错误情况
与真实IC网络的区别
- 不需要ICP代币或cycles充值
- 执行速度更快,没有网络延迟
- 持久化是模拟的,重启后会丢失状态
- 某些高级功能可能不完全相同
pocket-ic是开发IC智能合约时不可或缺的工具,可以大大提高开发效率和测试覆盖率。