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. 在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中的ApproveOrigin
和RejectOrigin
来自定义审批逻辑:
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());
}
最佳实践
- 设置合理的提案押金以防止垃圾提案
- 配置适当的审批机制以确保资金安全
- 定期审查财政支出
- 考虑实现资金使用的透明报告机制
注意事项
- 财政提案通常需要链上治理流程批准
- 资金支出可能需要多个签名或多数同意
- 提案被拒绝时,押金可能会被没收
- 确保设置合理的支出周期和预算限制
通过pallet-treasury
,Substrate链可以实现透明、去中心化的资金管理,这是构建DAO、国库系统或任何需要链上资金管理的应用的基础模块。