Rust区块链开发库pallet-treasury的使用,实现Substrate链上资金管理和财政提案功能

Rust区块链开发库pallet-treasury的使用,实现Substrate链上资金管理和财政提案功能

Treasury Pallet

Treasury pallet提供了一个资金"池"(pot),可以由系统中的利益相关者管理,以及一个从这个池中提出支出提案的结构。

概述

Treasury Pallet本身提供了存储资金的池,以及利益相关者提出、批准和拒绝支出的方法。链需要提供一种收集资金的方法(例如通货膨胀、费用)。

举例来说,理事会可以投票决定用一部分区块奖励来资助国库,并使用这些资金支付开发人员。

术语

  • 提案(Proposal): 从资金池中分配资金给受益人的建议。
  • 受益人(Beneficiary): 如果提案获得批准,将从提案中接收资金的账户。
  • 保证金(Deposit): 提案人在提出提案时必须锁定的资金。如果提案被批准或拒绝,保证金将被返还或没收。
  • 资金池(Pot): 由国库pallet积累的未使用资金。

接口

可调度函数

通用支出/提案协议:

  • spend_local - 提出并批准国库资金的支出,可以使用链的原生货币创建支出,利用存储在资金池中的资金
  • spend - 提出并批准国库资金的支出,允许支出国库管理的任何资产类型
  • remove_approval - 强制从批准队列中移除先前批准的提案
  • payout - 领取支出
  • check_status - 检查支出的状态,如果已处理则从存储中移除
  • void_spend - 作废先前批准的支出

完整示例代码

use frame_support::{parameter_types, traits::Currency};
use sp_runtime::traits::AccountIdConversion;

// 定义一些参数类型
parameter_types! {
    pub const TreasuryModuleId: u8 = 10;
    pub const ProposalBond: Permill = Permill::from_percent(5);
    pub const SpendPeriod: BlockNumber = 24 * 30;
    pub const Burn: Permill = Permill::from_percent(50);
}

// 实现Treasury配置
impl treasury::Config for Runtime {
    type Currency = Balances;
    type ApproveOrigin = EnsureRoot<AccountId>;
    type RejectOrigin = EnsureRoot<AccountId>;
    type Event = Event;
    type ModuleId = TreasuryModuleId;
    type ProposalBond = ProposalBond;
    type ProposalBondMinimum = 1;
    type SpendPeriod = SpendPeriod;
    type Burn = Burn;
    type WeightInfo = ();
}

// 创建提案示例
fn create_proposal(origin: OriginFor<Runtime>, value: Balance, beneficiary: AccountId) -> DispatchResult {
    // 确保调用者有权限
    ensure_signed(origin)?;
    
    // 计算需要的保证金
    let bond = Treasury::calculate_bond(value);
    
    // 锁定提案人的保证金
    Balances::reserve(&proposer, bond)?;
    
    // 创建提案
    Treasury::propose_spend(
        origin,
        value,
        beneficiary,
    )
}

// 批准提案示例
fn approve_proposal(origin: OriginFor<Runtime>, proposal_id: ProposalIndex) -> DispatchResult {
    // 确保调用者有批准权限
    Treasury::ApproveOrigin::ensure_origin(origin)?;
    
    // 批准提案
    Treasury::approve_proposal(origin, proposal_id)
}

// 支付提案示例
fn payout_proposal(origin: OriginFor<Runtime>, proposal_id: ProposalIndex) -> DispatchResult {
    // 确保调用者是受益人
    let beneficiary = ensure_signed(origin)?;
    
    // 检查提案状态并支付
    Treasury::payout(origin, proposal_id)
}

// 查询国库余额
fn get_treasury_balance() -> Balance {
    Treasury::pot()
}

完整Demo示例

use frame_support::{decl_module, decl_event, decl_storage, dispatch::DispatchResult};
use frame_system::ensure_signed;
use sp_runtime::traits::AccountIdConversion;

pub trait Config: frame_system::Config + treasury::Config {
    type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;
}

decl_event!(
    pub enum Event<T> where AccountId = <T as frame_system::Config>::AccountId {
        /// 提案已创建
        ProposalCreated(AccountId, Balance, AccountId),
        /// 提案已批准
        ProposalApproved(u32),
        /// 资金已支付
        FundsPaid(AccountId, Balance),
    }
);

decl_storage! {
    trait Store for Module<T: Config> as TreasuryDemo {
        // 存储提案计数器
        pub ProposalCount get(fn proposal_count): u32;
    }
}

decl_module! {
    pub struct Module<T: Config> for enum Call where origin: T::Origin {
        fn deposit_event() = default;

        /// 创建新的资金提案
        #[weight = 10_000]
        pub fn create_treasury_proposal(
            origin,
            value: Balance,
            beneficiary: T::AccountId,
        ) -> DispatchResult {
            let proposer = ensure_signed(origin)?;
            
            // 创建提案
            Treasury::propose_spend(
                frame_system::RawOrigin::Signed(proposer.clone()).into(),
                value,
                beneficiary.clone(),
            )?;
            
            // 更新提案计数
            ProposalCount::put(ProposalCount::get() + 1);
            
            // 发出事件
            Self::deposit_event(RawEvent::ProposalCreated(proposer, value, beneficiary));
            Ok(())
        }

        /// 批准提案
        #[weight = 10_000]
        pub fn approve_treasury_proposal(
            origin,
            proposal_id: treasury::ProposalIndex,
        ) -> DispatchResult {
            // 确保调用者有批准权限
            T::ApproveOrigin::ensure_origin(origin.clone())?;
            
            // 批准提案
            Treasury::approve_proposal(origin, proposal_id)?;
            
            // 发出事件
            Self::deposit_event(RawEvent::ProposalApproved(proposal_id));
            Ok(())
        }

        /// 支付已批准的提案
        #[weight = 10_000]
        pub fn payout_treasury_proposal(
            origin,
            proposal_id: treasury::ProposalIndex,
        ) -> DispatchResult {
            let beneficiary = ensure_signed(origin.clone())?;
            
            // 检查提案状态并支付
            Treasury::payout(origin, proposal_id)?;
            
            // 发出事件
            Self::deposit_event(RawEvent::FundsPaid(beneficiary, Treasury::proposals(proposal_id).value));
            Ok(())
        }

        /// 查询国库余额
        #[weight = 10_000]
        pub fn get_treasury_balance() -> Balance {
            Treasury::pot()
        }
    }
}

安装

在项目目录中运行以下Cargo命令:

cargo add pallet-treasury

或者在Cargo.toml中添加以下行:

pallet-treasury = "41.0.0"

1 回复

Rust区块链开发库pallet-treasury的使用指南

简介

pallet-treasury是Substrate框架中的一个核心模块,用于实现区块链上的资金管理和财政提案功能。它为区块链网络提供了去中心化的财政管理能力,允许链上资金的管理和分配通过民主提案的方式进行。

主要功能

  1. 链上资金管理
  2. 财政提案系统
  3. 支出审批机制
  4. 预算管理

使用方法

1. 在runtime中集成pallet-treasury

首先需要在你的Substrate runtime中集成这个pallet:

// runtime/src/lib.rs

impl pallet_treasury::Config for Runtime {
    type Currency = Balances;
    type ApproveOrigin = EnsureRoot<AccountId>;
    type RejectOrigin = EnsureRoot<AccountId>;
    type Event = Event;
    type OnSlash = ();
    type ProposalBond = ProposalBond;
    type ProposalBondMinimum = ProposalBondMinimum;
    type SpendPeriod = SpendPeriod;
    type Burn = Burn;
    type BurnDestination = ();
    type WeightInfo = ();
    type SpendFunds = ();
}

2. 基本操作示例

提交财政提案

// 假设我们有一个已经初始化的Substrate连接和签名的账户

let proposal = Call::System(frame_system::Call::remark { remark: "Funding community project".into() });
let value = 1000; // 请求的资金数量

let proposal_submit = pallet_treasury::Call::propose_spend {
    value,
    beneficiary: recipient_account_id,
    proposal: Box::new(proposal),
};

api.tx()
    .treasury()
    .propose_spend(value, recipient_account_id, Box::new(proposal))
    .sign_and_submit(&keypair)?;

批准提案

let approve_call = pallet_treasury::Call::approve_proposal { proposal_id: 0 };

api.tx()
    .treasury()
    .approve_proposal(0)
    .sign_and_submit(&root_keypair)?;

拒绝提案

let reject_call = pallet_treasury::Call::reject_proposal { proposal_id: 0 };

api.tx()
    .treasury()
    .reject_proposal(0)
    .sign_and_submit(&root_keypair)?;

执行批准的提案

let payout_call = pallet_treasury::Call::payout_proposal { proposal_id: 0 };

api.tx()
    .treasury()
    .payout_proposal(0)
    .sign_and_submit(&root_keypair)?;

3. 配置参数说明

在runtime配置中,可以设置以下重要参数:

  • ProposalBond: 提案押金比例(通常为5%)
  • ProposalBondMinimum: 最小提案押金
  • SpendPeriod: 支出周期(区块数)
  • Burn: 资金燃烧比例

高级功能

自定义审批逻辑

你可以通过实现Config trait中的ApproveOriginRejectOrigin来自定义审批逻辑:

impl pallet_treasury::Config for Runtime {
    // 使用技术委员会多数票批准
    type ApproveOrigin = pallet_collective::EnsureProportionAtLeast<AccountId, TechnicalCollective, 3, 5>;
    
    // 使用技术委员会任意成员拒绝
    type RejectOrigin = pallet_collective::EnsureMember<AccountId, TechnicalCollective>;
    // ...其他配置
}

资金支出后处理

你可以实现OnSpendFunds trait来在资金支出后执行自定义逻辑:

pub struct SpendHandler;
impl pallet_treasury::SpendFunds for SpendHandler {
    fn on_spend_funds(amount: BalanceOf<T>, beneficiary: &T::AccountId) {
        // 自定义逻辑,如记录日志或触发其他操作
    }
}

impl pallet_treasury::Config for Runtime {
    // ...
    type SpendFunds = SpendHandler;
}

完整示例

下面是一个完整的pallet-treasury使用示例,包含提案提交、审批和执行的全流程:

//! 完整的使用pallet-treasury的示例代码

use frame_support::{decl_module, decl_storage, dispatch::DispatchResult};
use sp_runtime::traits::AccountIdConversion;
use frame_system::{self as system, ensure_signed};

// 1. 定义Treasury模块
pub trait Config: system::Config {
    type Event: From<Event<Self>> + Into<<Self as system::Config>::Event>;
}

decl_storage! {
    trait Store for Module<T: Config> as TreasuryModule {
        // 存储提案
        pub Proposals get(fn proposals): map hasher(blake2_128_concat) u32 => Proposal<T::AccountId, BalanceOf<T>>;
        // 提案计数器
        pub ProposalCount get(fn proposal_count): u32;
    }
}

decl_module! {
    pub struct Module<T: Config> for enum Call where origin: T::Origin {
        type Error = DispatchError;

        // 提交财政提案
        #[weight = 10_000]
        pub fn propose_spend(
            origin,
            #[compact] value: BalanceOf<T>,
            beneficiary: T::AccountId,
            proposal: Box<<T as system::Config>::Call>,
        ) -> DispatchResult {
            let proposer = ensure_signed(origin)?;
            
            // 计算提案押金
            let bond = Self::calculate_bond(value);
            T::Currency::reserve(&proposer, bond)?;
            
            // 存储提案
            let proposal_id = Self::proposal_count();
            Proposals::<T>::insert(proposal_id, Proposal {
                proposer,
                value,
                beneficiary,
                bond,
            });
            
            ProposalCount::<T>::put(proposal_id + 1);
            
            Self::deposit_event(Event::Proposed(proposal_id));
            Ok(())
        }
        
        // 批准提案
        #[weight = 10_000]
        pub fn approve_proposal(origin, #[compact] proposal_id: u32) -> DispatchResult {
            T::ApproveOrigin::ensure_origin(origin)?;
            
            // 验证提案存在
            ensure!(Proposals::<T>::contains_key(proposal_id), Error::<T>::UnknownProposal);
            
            // 标记提案为已批准
            Proposals::<T>::mutate(proposal_id, |proposal| {
                if let Some(ref mut proposal) = proposal {
                    proposal.approved = true;
                }
            });
            
            Self::deposit_event(Event::Approved(proposal_id));
            Ok(())
        }
        
        // 执行已批准的提案
        #[weight = 10_000]
        pub fn payout_proposal(origin, #[compact] proposal_id: u32) -> DispatchResult {
            T::ApproveOrigin::ensure_origin(origin)?;
            
            let proposal = Proposals::<T>::take(proposal_id).ok_or(Error::<T>::UnknownProposal)?;
            ensure!(proposal.approved, Error::<T>::NotApproved);
            
            // 转账资金给受益人
            T::Currency::transfer(&Self::account_id(), &proposal.beneficiary, proposal.value, ExistenceRequirement::KeepAlive)?;
            
            // 退还押金给提案人
            T::Currency::unreserve(&proposal.proposer, proposal.bond);
            
            Self::deposit_event(Event::PaidOut(proposal_id, proposal.beneficiary, proposal.value));
            Ok(())
        }
    }
}

// 2. 在runtime中配置
impl pallet_treasury::Config for Runtime {
    type Currency = Balances;  // 使用Balances模块作为货币
    type ApproveOrigin = EnsureRoot<AccountId>;  // 根账户可以批准
    type RejectOrigin = EnsureRoot<AccountId>;  // 根账户可以拒绝
    type Event = Event;
    type OnSlash = ();  // 不实现slash回调
    type ProposalBond = ConstU32<5>;  // 5%的押金比例
    type ProposalBondMinimum = ConstU128<100>;  // 最小押金100
    type SpendPeriod = ConstU32<100>;  // 支出周期100个区块
    type Burn = ConstU32<0>;  // 不燃烧资金
    type BurnDestination = ();  // 无燃烧目的地
    type WeightInfo = ();  // 默认权重
    type SpendFunds = ();  // 不实现资金支出回调
}

// 3. 使用示例
fn main() {
    // 初始化运行时和API
    let mut runtime = Runtime::new();
    let api = RuntimeApi::new();
    
    // 创建测试账户
    let alice = AccountKeyring::Alice.to_account_id();
    let bob = AccountKeyring::Bob.to_account_id();
    
    // 1. 提交提案
    let proposal = Call::System(frame_system::Call::remark { 
        remark: "社区项目资金".into() 
    });
    
    let submit_tx = api.tx()
        .treasury()
        .propose_spend(1000, bob.clone(), Box::new(proposal))
        .sign_and_submit(&alice);
    
    // 2. 批准提案
    let approve_tx = api.tx()
        .treasury()
        .approve_proposal(0)
        .sign_and_submit(&AccountKeyring::Root.to_account_id());
    
    // 3. 执行提案
    let payout_tx = api.tx()
        .treasury()
        .payout_proposal(0)
        .sign_and_submit(&AccountKeyring::Root.to_account_id());
}

最佳实践

  1. 设置合理的提案押金以防止垃圾提案
  2. 配置适当的审批机制以确保资金安全
  3. 定期审查财政支出
  4. 考虑实现资金使用的透明报告机制

注意事项

  • 财政提案通常需要链上治理流程批准
  • 资金支出可能需要多个签名或多数同意
  • 提案被拒绝时,押金可能会被没收
  • 确保设置合理的支出周期和预算限制

通过pallet-treasury,Substrate链可以实现透明、去中心化的资金管理,这是构建DAO、国库系统或任何需要链上资金管理的应用的基础模块。

回到顶部