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框架中的一个模块,用于实现链上代币的逐步释放和归属期管理功能。它允许项目方设置代币的锁定期和释放计划,确保代币按照预定的时间表逐步释放给接收者。

主要功能

  1. 设置代币的归属计划(vesting schedule)
  2. 按照时间表逐步释放锁定的代币
  3. 管理多个归属计划
  4. 查询当前可释放的代币数量

集成方法

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(())
}

注意事项

  1. 归属计划一旦创建,通常不能修改(除非有管理员权限)
  2. 每个账户可以有多个归属计划
  3. 提取操作需要用户主动调用vest函数
  4. 归属计算是基于区块高度而非绝对时间

pallet-vesting为代币经济模型提供了灵活的归属管理方案,特别适合项目初期代币锁定和逐步释放的场景。通过合理设置归属计划,可以有效防止代币集中抛售,维护项目生态稳定。

回到顶部