Rust质押池管理库pallet-nomination-pools的使用,实现高效安全的权益质押与委托功能
Rust质押池管理库pallet-nomination-pools的使用,实现高效安全的权益质押与委托功能
安装
在项目目录中运行以下Cargo命令:
cargo add pallet-nomination-pools
或者在Cargo.toml中添加以下行:
pallet-nomination-pools = "40.0.0"
使用示例
以下是一个使用pallet-nomination-pools实现质押池管理的完整示例:
use frame_support::traits::{Currency, OnUnbalanced};
use pallet_nomination_pools::{self as pools, PoolId};
use sp_runtime::traits::{AccountIdConversion, Saturating};
use frame_system::Config as SystemConfig;
// 定义你的运行时配置
pub trait Config: pools::Config + SystemConfig {
// 添加你的运行时特定配置
}
// 创建质押池
fn create_pool<T: Config>(
depositor: T::AccountId,
amount: BalanceOf<T>,
root: T::AccountId,
nominator: T::AccountId,
state_toggler: T::AccountId,
) -> Result<PoolId, pools::Error<T>> {
// 检查余额是否足够
let min_create_bond = pools::MinCreateBond::<T>::get();
ensure!(amount >= min_create_bond, pools::Error::<T>::InsufficientBond);
// 创建质押池
pools::Pallet::<T>::create(
depositor,
amount,
root,
nominator,
state_toggler,
)
}
// 加入质押池
fn join_pool<T: Config>(
who: T::AccountId,
pool_id: PoolId,
amount: BalanceOf<T>,
) -> Result<(), pools::Error<T>> {
// 检查池是否存在
ensure!(pools::Pallet::<T>::pool_exists(pool_id), pools::Error::<T>::PoolNotFound);
// 加入池
pools::Pallet::<T>::join(
who,
pool_id,
amount,
)
}
// 提名验证人
fn nominate<T: Config>(
pool_id: PoolId,
validators: Vec<T::AccountId>,
) -> Result<(), pools::Error<T>> {
// 获取池账户
let pool_account = pools::Pallet::<T>::create_pool_account(pool_id);
// 提名验证人
pools::Pallet::<T>::nominate(
pool_account,
validators,
)
}
// 提取奖励
fn claim_payout<T: Config>(
member_account: T::AccountId,
) -> Result<(), pools::Error<T>> {
// 提取奖励
pools::Pallet::<T>::claim_payout(member_account)
}
// 解质押
fn unbond<T: Config>(
member_account: T::AccountId,
unbond_points: BalanceOf<T>,
) -> Result<(), pools::Error<T>> {
// 解质押
pools::Pallet::<T>::unbond(
member_account,
unbond_points,
)
}
完整示例代码
use frame_support::{
assert_ok,
traits::{Currency, OnUnbalanced},
};
use frame_system::Config as SystemConfig;
use pallet_nomination_pools::{self as pools, BondExtra, PoolId};
use sp_core::H256;
use sp_runtime::{
testing::Header,
traits::{AccountIdConversion, BlakeTwo256, IdentityLookup, Saturating},
Perbill,
};
// 定义测试环境类型
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
type Block = frame_system::mocking::MockBlock<Test>;
// 配置测试运行时
frame_support::construct_runtime!(
pub enum Test where
Block = Block,
NodeBlock = Block,
UncheckedExtrinsic = UncheckedExtrinsic,
{
System: frame_system::{Pallet, Call, Config, Storage, Event<T>},
Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>},
Staking: pallet_staking::{Pallet, Call, Storage, Config<T>, Event<T>},
NominationPools: pallet_nomination_pools::{
Pallet, Call, Storage, Event<T>, Config<T>
},
}
);
// 实现系统配置
impl frame_system::Config for Test {
type BaseCallFilter = frame_support::traits::Everything;
type BlockWeights = ();
type BlockLength = ();
type DbWeight = ();
type Origin = Origin;
type Index = u64;
type BlockNumber = u64;
type Hash = H256;
type Call = Call;
type Hashing = BlakeTwo256;
type AccountId = u64;
type Lookup = IdentityLookup<Self::AccountId>;
type Header = Header;
type Event = Event;
type BlockHashCount = ();
type Version = ();
type PalletInfo = PalletInfo;
type AccountData = pallet_balances::AccountData<u64>;
type OnNewAccount = ();
type OnKilledAccount = ();
type SystemWeightInfo = ();
type SS58Prefix = ();
type OnSetCode = ();
type MaxConsumers = frame_support::traits::ConstU32<16>;
}
// 实现余额配置
impl pallet_balances::Config for Test {
type Balance = u64;
type DustRemoval = ();
type Event = Event;
type ExistentialDeposit = frame_support::traits::ConstU64<1>;
type AccountStore = System;
type WeightInfo = ();
type MaxLocks = ();
type MaxReserves = ();
type ReserveIdentifier = [u8; 8];
}
// 实现质押配置
impl pallet_staking::Config for Test {
type Currency = Balances;
type UnixTime = ();
type CurrencyToVote = ();
type RewardRemainder = ();
type Event = Event;
type Slash = ();
type Reward = ();
type SessionsPerEra = ();
type SlashDeferDuration = ();
type AdminOrigin = frame_system::EnsureRoot<Self::AccountId>;
type BondingDuration = frame_support::traits::ConstU32<3>;
type SessionInterface = ();
type EraPayout = ();
type NextNewSession = ();
type MaxNominatorRewardedPerValidator = frame_support::traits::ConstU32<64>;
type OffendingValidatorsThreshold = ();
type WeightInfo = ();
}
// 实现质押池配置
impl pallet_nomination_pools::Config for Test {
type Event = Event;
type WeightInfo = ();
type Currency = Balances;
type Stake = Staking;
type PostUnbondingPoolsWindow = frame_support::traits::ConstU32<10>;
type MaxMetadataLen = frame_support::traits::ConstU32<256>;
type MaxPools = frame_support::traits::ConstU32<10>;
type MaxPoolMembers = frame_support::traits::ConstU32<100>;
type MaxPoolMembersPerPool = frame_support::traits::ConstU32<50>;
type MinerReward = ();
type BalanceToU256 = ();
type U256ToBalance = ();
}
// 测试创建和加入质押池
#[test]
fn test_pool_creation_and_joining() {
// 初始化测试环境
let mut ext = sp_io::TestExternalities::new_empty();
ext.execute_with(|| {
// 创建初始账户
let root = 1;
let depositor = 2;
let member = 3;
// 设置初始余额
Balances::make_free_balance_be(&depositor, 1000);
Balances::make_free_balance_be(&member, 500);
// 创建质押池
assert_ok!(NominationPools::create(
Origin::signed(depositor),
100, // 质押金额
root, // 根账户
root, // 提名账户
root // 状态切换账户
));
// 获取创建的池ID
let pool_id = 1;
// 验证池是否存在
assert!(NominationPools::pool_exists(pool_id));
// 加入质押池
assert_ok!(NominationPools::join(
Origin::signed(member),
pool_id,
200 // 质押金额
));
// 验证成员是否已加入池
assert!(NominationPools::is_pool_member(&member).is_some());
});
}
// 测试提名验证人
#[test]
fn test_nominating_validators() {
let mut ext = sp_io::TestExternalities::new_empty();
ext.execute_with(|| {
// 创建初始账户
let root = 1;
let depositor = 2;
let validator = 10;
// 设置初始余额
Balances::make_free_balance_be(&depositor, 1000);
// 创建质押池
assert_ok!(NominationPools::create(
Origin::signed(depositor),
100,
root,
root,
root
));
let pool_id = 1;
let pool_account = NominationPools::create_pool_account(pool_id);
// 提名验证人
assert_ok!(NominationPools::nominate(
Origin::signed(root),
pool_id,
vec![validator]
));
// 验证提名是否成功
let nominations = Staking::nominations(pool_account);
assert!(nominations.targets.contains(&validator));
});
}
// 测试解质押和提取奖励
#[test]
fn test_unbonding_and_claiming() {
let mut ext = sp_io::TestExternalities::new_empty();
ext.execute_with(|| {
// 创建初始账户
let root = 1;
let depositor = 2;
let member = 3;
// 设置初始余额
Balances::make_free_balance_be(&depositor, 1000);
Balances::make_free_balance_be(&member, 500);
// 创建质押池
assert_ok!(NominationPools::create(
Origin::signed(depositor),
100,
root,
root,
root
));
let pool_id = 1;
// 加入质押池
assert_ok!(NominationPools::join(
Origin::signed(member),
pool_id,
200
));
// 解质押部分金额
assert_ok!(NominationPools::unbond(
Origin::signed(member),
pool_id,
50
));
// 模拟奖励分配
// 提取奖励
assert_ok!(NominationPools::claim_payout(
Origin::signed(member)
));
});
}
功能说明
- 创建质押池:允许用户创建新的质押池,成为池管理员
- 加入质押池:其他用户可以将资金存入池中参与质押
- 提名验证人:池管理员可以提名验证人集合
- 奖励分配:自动分配质押奖励给池成员
- 解质押:成员可以解质押他们的资金
安全特性
- 资金隔离:每个池的资金存储在单独的账户中
- 角色分离:池管理权限可以分配给不同的角色
- 最小质押限制:防止小额质押导致的系统过载
- 解质押冷却期:防止频繁进出质押池
1 回复
Rust质押池管理库pallet-nomination-pools的使用指南
概述
pallet-nomination-pools
是Substrate框架中的一个模块,用于实现高效安全的权益质押(PoS)与委托功能。它允许用户将他们的代币集中在一起,作为一个集体进行质押,同时保持对各自资金的所有权。
主要功能
- 创建和管理质押池
- 允许用户加入现有池进行委托质押
- 自动分配质押奖励
- 提供安全的资金提取机制
- 支持池成员的动态变化
使用方法
1. 创建质押池
use frame_support::traits::Currency;
use pallet_nomination_pools::{PoolId, Config};
// 创建新池的参数
let amount_to_bond: BalanceOf<T> = 1000u32.into(); // 初始绑定金额
let root: T::AccountId = account("root", 0, 0); // 池管理员
let nominator: T::AccountId = account("nominator", 0, 0); // 提名人账户
let bouncer: T::AccountId = account("bouncer", 0, 0); // 资金提取管理者
// 创建新池
let create_pool_result = Pools::create(
origin,
amount_to_bond,
root.clone(),
nominator.clone(),
bouncer.clone(),
);
2. 加入现有质押池
use pallet_nomination_pools::{PoolId, BondExtra};
// 加入池并绑定资金
let pool_id: PoolId = 1; // 要加入的池ID
let amount: BalanceOf<T> = 500u32.into(); // 要绑定的金额
// 加入池
let join_result = Pools::join(
origin,
amount,
pool_id,
);
// 或者使用BondExtra增加绑定
let bond_extra_result = Pools::bond_extra(
origin,
BondExtra::FreeBalance(amount),
);
3. 提取奖励
use pallet_nomination-pools::{PoolId, ClaimPermission};
// 设置奖励领取权限
let set_claim_permission_result = Pools::set_claim_permission(
origin,
ClaimPermission::Permissionless,
);
// 领取奖励
let claim_result = Pools::claim_payout(
origin,
);
4. 解绑和提取资金
use pallet_nomination_pools::{PoolId, UnbondPoolMembers};
// 解绑部分资金
let unbond_amount: BalanceOf<T> = 200u32.into();
let unbond_result = Pools::unbond(
origin,
member_account,
unbond_amount,
);
// 完全解绑并提取资金
let withdraw_result = Pools::withdraw_unbonded(
origin,
member_account,
num_slashing_spans,
);
示例:完整的质押池生命周期
use frame_support::{assert_ok, traits::Currency};
use pallet_nomination_pools::{
BondExtra, ClaimPermission, Config, PoolId, PoolState,
UnbondPoolMembers,
};
// 1. 创建池
assert_ok!(Pools::create(
RuntimeOrigin::signed(root),
1000u32.into(),
root.clone(),
nominator.clone(),
bouncer.clone(),
));
// 2. 成员加入池
assert_ok!(Pools::join(
RuntimeOrigin::signed(member1),
500u32.into(),
1, // 池ID
));
// 3. 绑定额外资金
assert_ok!(Pools::bond_extra(
RuntimeOrigin::signed(member1),
BondExtra::FreeBalance(300u32.into()),
));
// 4. 设置奖励领取权限
assert_ok!(Pools::set_claim_permission(
RuntimeOrigin::signed(member1),
ClaimPermission::Permissionless,
));
// 5. 模拟获得奖励后领取
assert_ok!(Pools::claim_payout(
RuntimeOrigin::signed(member1),
));
// 6. 解绑部分资金
assert_ok!(Pools::unbond(
RuntimeOrigin::signed(member1),
member1.clone(),
200u32.into(),
));
// 7. 提取解绑的资金
assert_ok!(Pools::withdraw_unbonded(
RuntimeOrigin::signed(member1),
member1.clone(),
0,
));
完整示例DEMO
下面是一个更完整的质押池使用示例,展示了从创建到解绑的完整生命周期:
use frame_support::{assert_ok, traits::Currency};
use frame_system::RawOrigin;
use pallet_nomination_pools::{
BondExtra, Call, ClaimPermission, Config, Error, Event, PoolId,
PoolState,
};
use sp_runtime::traits::StaticLookup;
// 定义测试环境
type Test = crate::mock::Test;
// 测试账户
fn account(name: &'static str, index: u32, seed: u32) -> <Test as frame_system::Config>::AccountId {
let entropy = format!("{}{}{}", name, index, seed);
<Test as frame_system::Config>::Hashing::hash(entropy.as_bytes())
.into()
}
#[test]
fn test_full_pool_lifecycle() {
// 初始化测试环境
crate::mock::new_test_ext().execute_with(|| {
// 1. 准备账户
let root = account("root", 0, 0);
let nominator = account("nominator", 0, 0);
let bouncer = account("bouncer", 0, 0);
let member1 = account("member1", 0, 0);
let member2 = account("member2", 0, 0);
// 2. 创建质押池
assert_ok!(Call::<Test>::create {
amount: 1000,
root: root.clone(),
nominator: nominator.clone(),
bouncer: bouncer.clone(),
}.dispatch(RawOrigin::Signed(root.clone()).into()));
// 3. 成员加入池
assert_ok!(Call::<Test>::join {
amount: 500,
pool_id: 1,
}.dispatch(RawOrigin::Signed(member1.clone()).into()));
// 4. 另一个成员加入
assert_ok!(Call::<Test>::join {
amount: 700,
pool_id: 1,
}.dispatch(RawOrigin::Signed(member2.clone()).into()));
// 5. 成员1增加绑定
assert_ok!(Call::<Test>::bond_extra {
extra: BondExtra::FreeBalance(300),
}.dispatch(RawOrigin::Signed(member1.clone()).into()));
// 6. 设置奖励领取权限
assert_ok!(Call::<Test>::set_claim_permission {
permission: ClaimPermission::Permissionless,
}.dispatch(RawOrigin::Signed(member1.clone()).into()));
// 7. 模拟奖励分配后领取奖励
assert_ok!(Call::<Test>::claim_payout {}.dispatch(RawOrigin::Signed(member1.clone()).into()));
// 8. 解绑部分资金
assert_ok!(Call::<Test>::unbond {
member_account: member1.clone(),
points: 200,
}.dispatch(RawOrigin::Signed(member1.clone()).into()));
// 9. 提取解绑的资金
assert_ok!(Call::<Test>::withdraw_unbonded {
member_account: member1.clone(),
num_slashing_spans: 0,
}.dispatch(RawOrigin::Signed(member1.clone()).into()));
// 10. 验证最终状态
let pool = Pools::<Test>::pool(1).unwrap();
assert_eq!(pool.state, PoolState::Open);
});
}
最佳实践
- 池管理:确保选择可信的池管理员和提名人
- 安全:定期检查池的状态和奖励分配
- 解绑周期:注意解绑期(通常需要等待多个era才能提取资金)
- 费用:了解池可能收取的佣金或费用结构
- 监控:监控池的性能和活跃度
注意事项
- 质押操作通常需要多个区块确认
- 解绑资金有冷却期,不能立即提取
- 参与质押可能会使资金暂时无法流动
- 质押奖励会根据网络规则和池表现而变化
通过pallet-nomination-pools
,用户可以安全高效地参与权益质押,而无需自己运行验证节点,同时享受集体质押的好处。