Rust区块链资产锁定库pallet-vesting的使用,实现Substrate链上代币逐步释放与归属期管理
Rust区块链资产锁定库pallet-vesting的使用,实现Substrate链上代币逐步释放与归属期管理
Vesting Module概述
一个简单的模块,提供在账户锁定余额上设置线性曲线的方法。该模块确保有一个锁定机制,防止余额因UnvestedFundsAllowedWithdrawReasons
配置值中指定的原因之外的其他原因低于"未归属"金额。
随着时间推移,已归属金额增加,未归属金额减少。但是锁定仍然存在,需要用户明确采取行动来确保锁定金额等于剩余待归属金额。这可以通过可调度函数完成,通常是vest
(发送方代表自己调用)或vest_other
(发送方代表其他账户调用)。
接口
该模块实现了VestingSchedule
trait。
可调度函数
vest
- 更新锁定,根据当前已"归属"金额减少锁定vest_other
- 更新另一个账户的锁定,根据当前已"归属"金额减少锁定
完整示例demo
以下是使用pallet-vesting实现代币逐步释放与归属期管理的完整示例:
// 在runtime/src/lib.rs中添加vesting模块配置
impl pallet_vesting::Config for Runtime {
type Event = Event;
type Currency = Balances;
type BlockNumberToBalance = ConvertInto;
type MinVestedTransfer = MinVestedTransfer;
type WeightInfo = ();
type UnvestedFundsAllowedWithdrawReasons = ();
}
// 创建vesting schedule
use frame_support::traits::Get;
use pallet_vesting::VestingInfo;
// 假设我们有以下参数
let locked_amount = 1000u128;
let per_block_amount = 10u128; // 每个区块释放10个代币
let starting_block = 1u64;
let vesting_schedule = VestingInfo::new(
locked_amount,
per_block_amount,
starting_block
);
// 为账户添加vesting schedule
let account_id = 1;
pallet_vesting::Pallet::<Runtime>::add_vesting_schedule(
&account_id,
vesting_schedule
).unwrap();
// 在之后的区块中,账户可以调用vest来释放已归属的代币
#[test]
fn test_vesting() {
// 初始状态
assert_eq!(Balances::free_balance(&account_id), 0);
assert_eq!(Balances::usable_balance(&account_id), 0);
// 添加vesting schedule
pallet_vesting::Pallet::<Runtime>::add_vesting_schedule(
&account_id,
vesting_schedule
).unwrap();
// 经过50个区块后
run_to_block(50);
// 调用vest释放已归属的代币
pallet_vesting::Pallet::<Runtime>::vest(Origin::signed(account_id)).unwrap();
// 检查余额
let vested = 50 * per_block_amount;
assert_eq!(Balances::free_balance(&account_id), vested);
assert_eq!(Balances::usable_balance(&account_id), vested);
// 检查剩余的vesting schedule
let schedules = pallet_vesting::Pallet::<Runtime>::vesting(&account_id);
assert_eq!(schedules[0].locked(), locked_amount - vested);
}
安装
在项目目录中运行以下Cargo命令:
cargo add pallet-vesting
或者在Cargo.toml中添加:
pallet-vesting = "42.0.0"
许可证: Apache-2.0
1 回复
Rust区块链资产锁定库pallet-vesting使用指南
pallet-vesting
是Substrate框架中的一个模块,用于实现链上代币的逐步释放和归属期管理功能。它允许项目方设置代币的锁定期和释放计划,确保代币按照预定的时间表逐步释放给接收者。
主要功能
- 设置代币的归属计划(vesting schedule)
- 按照时间表逐步释放锁定的代币
- 管理多个归属计划
- 查询当前可释放的代币数量
集成方法
1. 在runtime中引入pallet-vesting
// runtime/src/lib.rs
impl pallet_vesting::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type Currency = Balances; // 使用Balances作为货币类型
type BlockNumberToBalance = ConvertInto; // 区块号到余额的转换
type MinVestedTransfer = MinVestedTransfer; // 最小归属转账量
type WeightInfo = pallet_vesting::weights::SubstrateWeight<Runtime>;
}
2. 在construct_runtime!宏中添加pallet
construct_runtime!(
pub enum Runtime where
Block = Block,
NodeBlock = opaque::Block,
UncheckedExtrinsic = UncheckedExtrinsic
{
// ...其他pallet
Vesting: pallet_vesting::{Pallet, Call, Storage, Config<T>, Event<T>},
}
);
基本使用方法
1. 创建归属计划
use frame_support::dispatch::DispatchResult;
use sp_runtime::traits::Convert;
fn create_vesting_schedule(
who: &AccountId,
locked: Balance,
per_block: Balance,
starting_block: BlockNumber,
) -> DispatchResult {
Vesting::vested_transfer(
RuntimeOrigin::signed(admin_account),
who.clone(),
pallet_vesting::VestingInfo {
locked,
per_block,
starting_block,
},
)
}
2. 查询归属信息
use pallet_vesting::VestingInfo;
// 获取账户的所有归属计划
let schedules = Vesting::vesting(&account_id);
// 计算当前可提取的金额
let vested_now = Vesting::vesting_balance(&account_id);
3. 提取已归属代币
Vesting::vest(RuntimeOrigin::signed(account_id));
完整示例
设置归属计划
use frame_support::traits::Currency;
use pallet_vesting::VestingInfo;
use sp_runtime::traits::Convert;
// 假设1个区块约6秒,1天约14400个区块
// 设置每天释放100个代币,持续100天
let total_locked = 10_000 * UNIT; // 10,000个代币
let per_block = 100 * UNIT / 14400; // 每天100个代币
let starting_block = 1; // 从区块1开始
let vesting_info = VestingInfo::new(
total_locked,
per_block,
starting_block,
);
// 创建归属计划
Vesting::vested_transfer(
RuntimeOrigin::signed(admin_account),
recipient_account,
vesting_info,
)?;
定期提取代币
// 用户调用提取已归属代币
#[transactional]
fn claim_vested(origin: OriginFor<T>) -> DispatchResult {
let who = ensure_signed(origin)?;
// 检查是否有可提取的代币
let vested = Vesting::vesting_balance(&who);
ensure!(vested > Zero::zero(), Error::<T>::NoVestedTokens);
// 提取代币
Vesting::vest(RuntimeOrigin::signed(who))?;
Ok(())
}
高级功能
合并多个归属计划
// 合并账户的所有归属计划
Vesting::merge_schedules(
RuntimeOrigin::signed(account_id),
schedule_index1,
schedule_index2,
)?;
强制取消归属计划
// 管理员强制取消某账户的归属计划
Vesting::force_vested_transfer(
RuntimeOrigin::root(), // 需要root权限
source_account,
target_account,
vesting_info,
)?;
完整示例代码
//! 完整的pallet-vesting使用示例
use frame_support::{dispatch::DispatchResult, traits::Currency};
use frame_system::Config as SystemConfig;
use pallet_vesting::VestingInfo;
use sp_runtime::{
traits::{Convert, Zero},
DispatchError,
};
use sp_std::vec::Vec;
// 1. 定义账户和余额类型
type AccountId = <Runtime as SystemConfig>::AccountId;
type Balance = <Runtime as pallet_balances::Config>::Balance;
type BlockNumber = <Runtime as SystemConfig>::BlockNumber;
// 2. 创建归属计划辅助函数
pub fn create_vesting_schedule(
admin: AccountId,
recipient: AccountId,
total_locked: Balance,
per_block: Balance,
starting_block: BlockNumber,
) -> DispatchResult {
let vesting_info = VestingInfo::new(total_locked, per_block, starting_block);
// 调用pallet-vesting的vested_transfer方法
pallet_vesting::Pallet::<Runtime>::vested_transfer(
frame_system::RawOrigin::Signed(admin).into(),
recipient,
vesting_info,
)
}
// 3. 查询归属信息
pub fn get_vesting_schedules(who: &AccountId) -> Vec<VestingInfo<BlockNumber, Balance>> {
pallet_vesting::Pallet::<Runtime>::vesting(who)
}
pub fn get_vested_balance(who: &AccountId) -> Balance {
pallet_vesting::Pallet::<Runtime>::vesting_balance(who)
}
// 4. 提取已归属代币
pub fn claim_vested(who: AccountId) -> DispatchResult {
pallet_vesting::Pallet::<Runtime>::vest(frame_system::RawOrigin::Signed(who).into())
}
// 5. 合并归属计划
pub fn merge_vesting_schedules(
who: AccountId,
schedule1: u32,
schedule2: u32,
) -> Result<(), DispatchError> {
pallet_vesting::Pallet::<Runtime>::merge_schedules(
frame_system::RawOrigin::Signed(who).into(),
schedule1,
schedule2,
)
}
// 6. 示例使用
fn example_usage() -> DispatchResult {
// 初始化账户
let admin = AccountId::new([1u8; 32]);
let user = AccountId::new([2u8; 32]);
// 设置归属计划参数
let total_locked = 10_000 * 1_000_000_000_000; // 10,000个代币(假设12位小数)
let blocks_per_day = 14_400u32; // 假设1个区块6秒
let per_block = total_locked / (100 * blocks_per_day as Balance); // 100天释放
// 创建归属计划
create_vesting_schedule(admin, user.clone(), total_locked, per_block, 1)?;
// 查询归属信息
let schedules = get_vesting_schedules(&user);
println!("用户归属计划数量: {}", schedules.len());
// 模拟经过一段时间后提取
frame_system::Pallet::<Runtime>::set_block_number(blocks_per_day * 30); // 30天后
// 提取已归属代币
claim_vested(user)?;
Ok(())
}
注意事项
- 归属计划一旦创建,通常不能修改(除非有管理员权限)
- 每个账户可以有多个归属计划
- 提取操作需要用户主动调用
vest
函数 - 归属计算是基于区块高度而非绝对时间
pallet-vesting
为代币经济模型提供了灵活的归属管理方案,特别适合项目初期代币锁定和逐步释放的场景。通过合理设置归属计划,可以有效防止代币集中抛售,维护项目生态稳定。