Rust跨链消息模拟库xcm-simulator的使用:XCM协议测试与多链交互模拟工具
Rust跨链消息模拟库xcm-simulator的使用:XCM协议测试与多链交互模拟工具
安装
在项目目录中运行以下Cargo命令:
cargo add xcm-simulator
或者在Cargo.toml中添加以下行:
xcm-simulator = "22.0.0"
示例代码
以下是使用xcm-simulator进行XCM协议测试和多链交互模拟的基本示例:
use xcm_simulator::{TestExt, XcmExecutor};
use xcm::latest::prelude::*;
// 定义两个测试链
pub struct TestNetwork;
impl TestExt for TestNetwork {
fn new_ext() -> sp_io::TestExternalities {
let mut t = frame_system::GenesisConfig::default()
.build_storage::<TestRuntime>()
.unwrap();
let mut ext = sp_io::TestExternalities::new(t);
ext.execute_with(|| System::set_block_number(1));
ext
}
}
// 定义XCM执行器
pub type LocalOriginToLocation = (
// 将本地Origin转换为XCM位置
SovereignSignedViaLocation<ParentLocation, Origin>,
);
pub type XcmRouter = (
// 路由到父链
XcmPassthrough<ParentIsPreset<ParentLocation>>,
);
// 测试XCM消息发送
#[test]
fn send_xcm_message() {
TestNetwork::new_ext().execute_with(|| {
// 准备XCM消息
let message = Xcm(vec![
WithdrawAsset((Parent, 100u128).into()),
BuyExecution {
fees: (Parent, 1u128).into(),
weight_limit: Limited(100u64),
},
DepositAsset {
assets: All.into(),
beneficiary: Parent.into(),
},
]);
// 发送XCM消息
XcmExecutor::<XcmConfig>::send_xcm(Parent, message).unwrap();
// 验证消息是否被正确处理
assert_eq!(XcmExecutor::<XcmConfig>::sent_xcm(), vec![(Parent, message)]);
});
}
主要功能
- 多链环境模拟:可以模拟多个区块链之间的交互
- XCM消息测试:测试跨链消息的发送、接收和处理
- 资产转移验证:验证跨链资产转移的正确性
- 执行跟踪:跟踪XCM消息的执行过程和结果
高级用法示例
use xcm_simulator::{decl_test_network, decl_test_parachain, decl_test_relay_chain};
// 定义测试中继链
decl_test_relay_chain! {
pub struct Relay {
Runtime = relay_chain::Runtime,
XcmConfig = relay_chain::XcmConfig,
new_ext = relay_chain::new_test_ext(),
}
}
// 定义测试平行链
decl_test_parachain! {
pub struct ParaA {
Runtime = para_a::Runtime,
XcmConfig = para_a::XcmConfig,
new_ext = para_a::new_test_ext(),
}
}
// 定义测试网络
decl_test_network! {
pub struct TestNet {
relay_chain = Relay,
parachains = vec![
(1000, ParaA),
],
}
}
#[test]
fn test_asset_transfer() {
TestNet::reset();
// 在中继链上初始化资产
Relay::execute_with(|| {
// 初始化账户余额
let _ = relay_chain::Balances::make_free_balance_be(
¶_a::AccountId::from([1u8; 32]),
1000
);
});
// 在平行链上发送XCM消息
ParaA::execute_with(|| {
let message = Xcm(vec![
WithdrawAsset((Parent, 500u128).into()),
BuyExecution {
fees: (Parent, 1u128).into(),
weight_limit: Limited(100u64),
},
DepositAsset {
assets: All.into(),
beneficiary: Parent.into(),
},
]);
XcmExecutor::<para_a::XcmConfig>::send_xcm(Parent, message).unwrap();
});
// 验证中继链上的余额变化
Relay::execute_with(|| {
assert_eq!(
relay_chain::Balances::free_balance(¶_a::AccountId::from([1u8; 32])),
500
);
});
}
完整示例demo
以下是一个更完整的xcm-simulator使用示例,模拟了中继链和平行链之间的资产转移:
// 引入必要的库和模块
use xcm_simulator::{decl_test_network, decl_test_parachain, decl_test_relay_chain};
use xcm::latest::prelude::*;
use frame_support::{assert_ok, traits::Currency};
// 定义中继链Runtime模拟
mod relay_chain {
use super::*;
// 中继链Runtime配置
pub struct Runtime;
pub struct XcmConfig;
// 余额模块类型
pub type Balance = u128;
pub type AccountId = [u8; 32];
// 模拟余额存储
pub struct Balances;
impl Currency<AccountId> for Balances {
type Balance = Balance;
fn free_balance(who: &AccountId) -> Balance {
// 实际项目中应从存储中读取
0
}
fn make_free_balance_be(who: &AccountId, amount: Balance) {
// 实际项目中应写入存储
}
}
// 创建测试环境
pub fn new_test_ext() -> sp_io::TestExternalities {
sp_io::TestExternalities::new(Default::default())
}
}
// 定义平行链Runtime模拟
mod para_a {
use super::*;
// 平行链Runtime配置
pub struct Runtime;
pub struct XcmConfig;
// 创建测试环境
pub fn new_test_ext() -> sp_io::TestExternalities {
sp_io::TestExternalities::new(Default::default())
}
}
// 定义测试中继链
decl_test_relay_chain! {
pub struct Relay {
Runtime = relay_chain::Runtime,
XcmConfig = relay_chain::XcmConfig,
new_ext = relay_chain::new_test_ext(),
}
}
// 定义测试平行链
decl_test_parachain! {
pub struct ParaA {
Runtime = para_a::Runtime,
XcmConfig = para_a::XcmConfig,
new_ext = para_a::new_test_ext(),
}
}
// 定义测试网络
decl_test_network! {
pub struct TestNet {
relay_chain = Relay,
parachains = vec![
(1000, ParaA),
],
}
}
#[test]
fn test_complete_asset_transfer() {
// 重置测试网络
TestNet::reset();
// 初始化账户
let alice = [1u8; 32];
let initial_balance = 1000u128;
let transfer_amount = 500u128;
// 在中继链上初始化Alice的余额
Relay::execute_with(|| {
relay_chain::Balances::make_free_balance_be(&alice, initial_balance);
});
// 验证初始余额
Relay::execute_with(|| {
assert_eq!(
relay_chain::Balances::free_balance(&alice),
initial_balance
);
});
// 在平行链上发送XCM消息进行资产转移
ParaA::execute_with(|| {
let message = Xcm(vec![
// 从Alice账户提取资产
WithdrawAsset((Parent, transfer_amount).into()),
// 支付执行费用
BuyExecution {
fees: (Parent, 1u128).into(),
weight_limit: Limited(100u64),
},
// 将资产存入中继链的Alice账户
DepositAsset {
assets: All.into(),
beneficiary: Junction::AccountId32 {
id: alice,
network: None,
}.into(),
},
]);
// 发送XCM消息到中继链
assert_ok!(XcmExecutor::<para_a::XcmConfig>::send_xcm(Parent, message));
});
// 验证中继链上Alice的余额变化
Relay::execute_with(|| {
assert_eq!(
relay_chain::Balances::free_balance(&alice),
initial_balance - transfer_amount
);
});
}
注意事项
- 确保使用最新版本的xcm-simulator以获得最佳兼容性
- 测试前需要正确配置各链的XCM参数
- 复杂的XCM消息可能需要调整权重限制
- 测试网络环境需要先初始化再使用
xcm-simulator是Polkadot生态中测试跨链交互的强大工具,特别适合开发需要多链交互的Substrate应用程序。
1 回复
Rust跨链消息模拟库xcm-simulator的使用:XCM协议测试与多链交互模拟工具
概述
xcm-simulator是一个用于测试和模拟跨共识消息(XCM)格式的Rust库,它允许开发者在本地环境中模拟多链交互场景,而无需部署实际的区块链网络。这个工具特别适合Substrate和Polkadot生态系统的开发者,用于测试XCM消息的发送、接收和处理逻辑。
主要特性
- 提供模拟的链环境(中继链、平行链等)
- 支持XCM消息的发送和接收模拟
- 允许自定义链的配置和参数
- 提供断言工具验证XCM执行结果
- 支持测试跨链资产转移等常见场景
使用方法
添加依赖
首先,在Cargo.toml中添加依赖:
[dependencies]
xcm-simulator = "0.1"
基本示例
use xcm_simulator::{TestExt, PolkadotXcm, RelayChain, Parachain};
fn main() {
// 初始化模拟环境
MockNet::reset();
// 创建中继链和平行链
let relay_chain = RelayChain::new();
let para_1 = Parachain::new(1);
let para_2 = Parachain::new(2);
// 注册平行链到中继链
relay_chain.register_parachain(1, para_1.clone());
relay_chain.register_parachain(2, para极链2.clone());
// 发送XCM消息从平行链1到平行链2
para_1.execute_with(|| {
let message = Xcm::<()>::new()
.withdraw_asset((Here, 100u128).into())
.buy_execution((Here, 1u128).into())
.deposit_asset(All.into(), (Parachain(2), Junction::AccountId32 { id: [0u8; 32], network: None }).into());
PolkadotXcm::send_xcm(Here, (Parent, Parachain(2)), message).unwrap();
});
// 验证消息处理结果
para_2.execute_with(|| {
assert_eq!(Balances::free_balance(&AccountId::new([0u8; 32])), 100);
});
}
测试XCM指令
use xcm::latest::prelude::*;
#[test]
fn test_xcm_transfer() {
MockNet::reset();
let relay = RelayChain::new();
let para_a = Parachain::new(1);
let para_b = Parachain::new(2);
relay.register_parachain(1, para_a.clone());
relay.register_parachain(2, para_b.clone());
// 在平行链A上初始化一些资产
para_a.execute_with(|| {
Balances::set_balance(&ALICE, 1000);
});
// 发送XCM转账消息
para_a.execute_with(|| {
let message = Xcm(vec![
WithdrawAsset((Here, 100).into()),
BuyExecution {
fees: (Here, 1).into(),
weight_limit: Unlimited,
},
DepositAsset {
assets: All.into(),
beneficiary: (Parent, Parachain(2), Junction::AccountId32 { id: BOB, network: None }).into(),
},
]);
assert_ok!(XcmPallet::send_xcm(Here, (Parent, Parachain极链(2)), message));
});
// 验证平行链B收到资产
para_b.execute_with(|| {
assert_eq!(Balances::free_balance(&BOB), 99); // 100减去1的执行费用
});
}
自定义链配置
use xcm_simulator::{decl_test_network, decl_test_parachain, decl_test_relay_chain};
// 定义自定义测试网络
decl_test_network! {
pub struct MyTestNet {
relay_chain = MyRelayChain,
parachains = vec![
(1, ParaA),
(2, ParaB),
],
}
}
// 定义中继链
decl_test_relay_chain! {
pub struct MyRelayChain {
Runtime = relay_chain::Runtime,
XcmpMessageHandler = relay_chain::XcmpQueue,
DmpMessageHandler = relay_chain::DmpQueue,
}
}
// 定义平行链A
decl_test_parachain! {
pub struct ParaA {
Runtime = para_a::Runtime,
XcmpMessageHandler = para_a::XcmpQueue,
DmpMessageHandler = para_a::DmpQueue,
}
}
// 定义平行链B
decl_test_parachain! {
pub struct ParaB {
Runtime = para_b::Runtime,
XcmpMessageHandler = para_b::XcmpQueue,
DmpMessageHandler = para_b::DmpQueue,
}
}
#[test]
fn test_custom_network() {
MyTestNet::reset();
MyTestNet::relay().execute_with(|| {
// 中继链初始化逻辑
});
MyTestNet::parachain(1).execute_with(|| {
// 平行链A测试逻辑
});
}
高级用法
测试XCM错误处理
#[test]
fn test_xcm_error_handling() {
MockNet::reset();
let relay = RelayChain::new();
let para = Parachain::new(1);
relay.register_parachain(1, para.clone());
// 发送会失败的XCM消息
para.execute_with(|| {
let message = Xcm(vec![
// 没有WithdrawAsset指令直接尝试BuyExecution
BuyExecution {
fees: (Here, 1).into(),
weight_limit: Unlimited,
},
]);
assert_err!(
XcmPallet::send_xcm(Here, Parent, message),
"Expected error when sending invalid XCM"
);
});
}
测试跨链资产转移
#[test]
fn test_cross_chain_asset_transfer() {
MockNet::reset();
let relay = RelayChain::new();
let para_a = Parachain::new(1);
let para_b = Parachain::new(2);
relay.register_parachain(1, para_a.clone());
relay.register_parachain(2, para_b.clone());
// 在平行链A上注册资产
para_a.execute_with(|| {
Assets::create(&ALICE, 1000).unwrap();
Assets::mint(&ALICE, &ALICE, 100极链0).unwrap();
});
// 发送资产到平行链B
para_a.execute_with(|| {
let message = Xcm(vec![
WithdrawAsset((X1(GeneralIndex(0)), 100).into()),
BuyExecution {
fees: (X1(GeneralIndex(0)), 10).into(),
weight_limit: Unlimited,
},
DepositAsset {
assets: (X1(GeneralIndex(0)), 90).into(),
beneficiary: (Parent, Parachain(2), Junction::AccountId32 { id: BOB, network: None }).into(),
},
]);
assert_ok!(XcmPallet::send_xcm(Here, (Parent, Parachain(2)), message));
});
// 验证平行链B收到资产
para_b.execute_with(|| {
assert_eq!(Assets::balance(0, &BOB), 90);
});
}
完整示例
下面是一个完整的跨链资产转移测试示例,结合了多个功能点:
//! 完整的XCM模拟测试示例:跨链资产转移
use xcm_simulator::{TestExt, RelayChain, Parachain};
use xcm::latest::prelude::*;
use frame_support::assert_ok;
// 定义测试账户
const ALICE: [u8; 32] = [1u8; 32];
const BOB: [u8; 32] = [2u8; 32];
#[test]
fn comprehensive_xcm_asset_transfer() {
// 1. 初始化模拟网络
MockNet::reset();
// 2. 创建中继链和平行链
let relay = RelayChain::new();
let para_a = Parachain::new(1);
let para_b = Parachain::new(2);
// 3. 注册平行链到中继链
relay.register_parachain(1, para_a.clone());
relay.register_parachain(2, para_b.clone());
// 4. 在平行链A上初始化资产和账户
para_a.execute_with(|| {
// 创建资产
assert_ok!(Assets::create(Origin::signed(ALICE), 0, ALICE, 1));
// 给ALICE账户铸造1000个资产
assert_ok!(Assets::mint(Origin::signed(ALICE), 0, ALICE, 1000));
});
// 5. 发送XCM消息从平行链A到平行链B
para_a.execute_with(|| {
let message = Xcm(vec![
// 提取100个资产
WithdrawAsset((X1(GeneralIndex(0)), 100).into()),
// 购买执行时间,支付10个资产作为费用
BuyExecution {
fees: (X1(GeneralIndex(0)), 10).into(),
weight_limit: Unlimited,
},
// 将剩余90个资产存入BOB账户
DepositAsset {
assets: (X1(GeneralIndex(0)), 90).into(),
beneficiary: (Parent, Parachain(2), Junction::AccountId32 { id: BOB, network: None }).into(),
},
]);
// 发送XCM消息
assert_ok!(XcmPallet::send_xcm(Here, (Parent, Parachain(2)), message));
});
// 6. 验证平行链B上的资产接收情况
para_b.execute_with(|| {
// 检查BOB账户是否收到90个资产
assert_eq!(Assets::balance(0, &BOB), 90);
// 检查是否有正确的事件发出
let expected_event = Event::XcmPallet(crate::Event::AssetsTransferred {
sender: ALICE,
recipient: BOB,
amount: 90,
asset_id: 0,
});
assert!(System::events().iter().any(|r| r.event == expected_event));
});
// 7. 验证平行链A上的资产扣除情况
para_a.execute_with(|| {
// ALICE的资产应该减少100 (1000 - 100 = 900)
assert_eq!(Assets::balance(0, &ALICE), 900);
});
}
注意事项
- xcm-simulator主要用于测试环境,不应在生产环境中使用
- 模拟的链环境是简化的,可能与实际网络行为有差异
- 测试时应覆盖各种边界条件和错误场景
- 确保XCM消息的weight计算准确,避免实际部署时出现执行失败
通过xcm-simulator,开发者可以高效地测试和验证XCM消息的逻辑,确保跨链交互的正确性和可靠性。