Rust区块链开发利器pallet-bounties:Substrate赏金模块实现去中心化激励与任务管理

以下是一个基于pallet-bounties的完整示例实现,包含赏金模块的主要功能:

// 完整赏金模块实现
use frame_support::{
    decl_error, decl_event, decl_module, decl_storage, dispatch, ensure,
    traits::{Currency, ExistenceRequirement, Get, ReservableCurrency},
    weights::Weight,
};
use frame_system::{self as system, ensure_root, ensure_signed};
use sp_runtime::{
    traits::{AccountIdConversion, StaticLookup, Zero},
    DispatchResult, RuntimeDebug,
};
use sp_std::prelude::*;

// 配置特征
pub trait Trait: system::Trait {
    type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
    type Currency: Currency<Self::AccountId> + ReservableCurrency<Self::AccountId>;
    type BountyDepositBase: Get<BalanceOf<Self>>;
    type BountyDepositPerByte: Get<BalanceOf<Self>>;
    type BountyUpdatePeriod: Get<Self::BlockNumber>;
    type BountyCuratorDeposit: Get<BalanceOf<Self>>;
    type BountyValueMinimum: Get<BalanceOf<Self>>;
    type BountyFee: Get<BalanceOf<Self>>;
}

type BalanceOf<T> =
    <<T as Trait>::Currency as Currency<<T as system::Trait>::AccountId>>::Balance;
type AccountIdLookupOf<T> = <<T as system::Trait>::Lookup as StaticLookup>::Source;

// 存储定义
decl_storage! {
    trait Store for Module<T: Trait> as Bounties {
        /// 赏金计数器
        pub BountyCount get(fn bounty_count): u32;
        
        /// 赏金信息存储
        pub Bounties get(fn bounties):
            map hasher(blake2_128_concat) u32 => Option<Bounty<T::AccountId, BalanceOf<T>, T::BlockNumber>>;
            
        /// 赏金描述存储
        pub BountyDescriptions get(fn bounty_descriptions):
            map hasher(blake2_128_concat) u32 => Option<Vec<u8>>;
            
        /// 子赏金计数器
        pub ChildBountyCount get(fn child_bounty_count): u32;
    }
}

// 事件定义
decl_event!(
    pub enum Event<T> where
        AccountId = <T as system::Trait>::AccountId,
        Balance = BalanceOf<T>,
        BlockNumber = <T as system::Trait>::BlockNumber,
    {
        /// 新赏金提案
        BountyProposed(u32),
        /// 赏金批准
        BountyApproved(u32),
        /// 策展人提议
        CuratorProposed(u32, AccountId),
        /// 策展人接受
        CuratorAccepted(u32, AccountId),
        /// 赏金奖励
        BountyAwarded(u32, AccountId),
        /// 赏金领取
        BountyClaimed(u32, Balance),
        /// 赏金取消
        BountyCanceled(u32),
        /// 赏金延长
        BountyExtended(u32, BlockNumber),
        /// 子赏金添加
        ChildBountyAdded(u32, u32),
    }
);

// 错误定义
decl_error! {
    pub enum Error for Module<T: Trait> {
        /// 赏金不存在
        BountyNotFound,
        /// 赏金状态不正确
        InvalidBountyStatus,
        /// 赏金金额不足
        InsufficientBountyValue,
        /// 描述过长
        DescriptionTooLong,
        /// 策展人未指定
        CuratorNotProposed,
        /// 策展人押金不足
        InsufficientCuratorDeposit,
    }
}

// 赏金状态枚举
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)]
pub enum BountyStatus<AccountId, Balance, BlockNumber> {
    /// 已提案
    Proposed,
    /// 已批准
    Approved,
    /// 策展人已提议
    CuratorProposed(AccountId),
    /// 活跃中(已分配策展人)
    Active {
        curator: AccountId,
        update_due: BlockNumber,
    },
    /// 待支付
    PendingPayout {
        curator: AccountId,
        beneficiary: AccountId,
        unlock_at: BlockNumber,
    },
}

// 赏金结构体
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)]
pub struct Bounty<AccountId, Balance, BlockNumber> {
    /// 提案人账户
    pub proposer: AccountId,
    /// 赏金价值
    pub value: Balance,
    /// 策展人费用
    pub fee: Balance,
    /// 策展人押金
    pub curator_deposit: Balance,
    /// 提案人押金
    pub bond: Balance,
    /// 赏金状态
    pub status: BountyStatus<AccountId, Balance, BlockNumber>,
}

// 模块实现
decl_module! {
    pub struct Module<T: Trait> for enum Call where origin: T::Origin {
        type Error = Error<T>;

        fn deposit_event() = default;

        /// 提出新的赏金
        #[weight = 10_000]
        pub fn propose_bounty(origin, description: Vec<u8>, value: BalanceOf<T>) -> dispatch::DispatchResult {
            let proposer = ensure_signed(origin)?;
            
            // 计算所需押金
            let deposit = T::BountyDepositBase::get() 
                + (T::BountyDepositPerByte::get() * (description.len() as u32).into());
            
            // 验证描述长度
            ensure!(description.len() <= 16384, Error::<T>::DescriptionTooLong);
            
            // 验证赏金金额
            ensure!(value >= T::BountyValueMinimum::get(), Error::<T>::InsufficientBountyValue);
            
            // 锁定提案人资金
            T::Currency::reserve(&proposer, deposit)?;
            
            // 获取新赏金ID
            let bounty_id = Self::bounty_count();
            BountyCount::put(bounty_id + 1);
            
            // 存储赏金信息
            let bounty = Bounty {
                proposer: proposer.clone(),
                value,
                fee: T::BountyFee::get(),
                curator_deposit: Zero::zero(),
                bond: deposit,
                status: BountyStatus::Proposed,
            };
            
            <Bounties<T>>::insert(bounty_id, bounty);
            <BountyDescriptions<T>>::insert(bounty_id, description);
            
            // 发出事件
            Self::deposit_event(RawEvent::BountyProposed(bounty_id));
            
            Ok(())
        }
        
        /// 批准赏金
        #[weight = 10_000]
        pub fn approve_bounty(origin, bounty_id: u32) -> dispatch::DispatchResult {
            ensure_root(origin)?;
            
            let mut bounty = Self::bounties(bounty_id).ok_or(Error::<T>::BountyNotFound)?;
            ensure!(bounty.status == BountyStatus::Proposed, Error::<T>::InvalidBountyStatus);
            
            bounty.status = BountyStatus::Approved;
            <Bounties<T>>::insert(bounty_id, bounty);
            
            Self::deposit_event(RawEvent::BountyApproved(bounty_id));
            Ok(())
        }
        
        /// 提议策展人
        #[weight = 10_000]
        pub fn propose_curator(origin, bounty_id: u32, curator: AccountIdLookupOf<T>) -> dispatch::DispatchResult {
            ensure_root(origin)?;
            
            let mut bounty = Self::bounties(bounty_id).ok_or(Error::<T>::BountyNotFound)?;
            ensure!(bounty.status == BountyStatus::Approved, Error::<T>::InvalidBountyStatus);
            
            let curator = T::Lookup::lookup(curator)?;
            
            bounty.status = BountyStatus::CuratorProposed(curator.clone());
            <Bounties<T>>::insert(bounty_id, bounty);
            
            Self::deposit_event(RawEvent::CuratorProposed(bounty_id, curator));
            Ok(())
        }
        
        /// 接受策展人角色
        #[weight = 10_000]
        pub fn accept_curator(origin, bounty_id: u32) -> dispatch::DispatchResult {
            let signer = ensure_signed(origin)?;
            
            let mut bounty = Self::bounties(bounty_id).ok_or(Error::<T>::BountyNotFound)?;
            
            let curator = match &bounty.status {
                BountyStatus::CuratorProposed(curator) if curator == &signer => curator.clone(),
                _ => Err(Error::<T>::CuratorNotProposed)?,
            };
            
            let deposit = T::BountyCuratorDeposit::get();
            T::Currency::reserve(&curator, deposit)?;
            
            bounty.curator_deposit = deposit;
            bounty.status = BountyStatus::Active {
                curator: curator.clone(),
                update_due: <system::Module<T>>::block_number() + T::BountyUpdatePeriod::get(),
            };
            
            <Bounties<T>>::insert(bounty_id, bounty);
            
            Self::deposit_event(RawEvent::CuratorAccepted(bounty_id, curator));
            Ok(())
        }
        
        /// 奖励赏金
        #[weight = 10_000]
        pub fn award_bounty(origin, bounty_id: u32, beneficiary: AccountIdLookupOf<T>) -> dispatch::DispatchResult {
            let signer = ensure_signed(origin)?;
            
            let mut bounty = Self::bounties(bounty_id).ok_or(Error::<T>::BountyNotFound)?;
            
            let curator = match &bounty.status {
                BountyStatus::Active { curator, .. } if curator == &signer => curator.clone(),
                _ => Err(Error::<T>::InvalidBountyStatus)?,
            };
            
            let beneficiary = T::Lookup::lookup(beneficiary)?;
            let unlock_at = <system::Module<T>>::block_number() + T::BountyUpdatePeriod::get();
            
            bounty.status = BountyStatus::PendingPayout {
                curator: curator.clone(),
                beneficiary: beneficiary.clone(),
                unlock_at,
            };
            
            <Bounties<T>>::insert(bounty_id, bounty);
            
            Self::deposit_event(RawEvent::BountyAwarded(bounty_id, beneficiary));
            Ok(())
        }
        
        /// 领取赏金
        #[weight = 10_000]
        pub fn claim_bounty(origin, bounty_id: u32) -> dispatch::DispatchResult {
            let _ = ensure_signed(origin)?;
            
            let bounty = Self::bounties(bounty_id).ok_or(Error::<T>::BountyNotFound)?;
            
            let (curator, beneficiary, unlock_at, value, fee, curator_deposit) = match &bounty.status {
                BountyStatus::PendingPayout { curator, beneficiary, unlock_at } => {
                    ensure!(
                        <system::Module<T>>::block_number() >= *unlock_at,
                        Error::<T>::InvalidBountyStatus
                    );
                    (curator.clone(), beneficiary.clone(), *unlock_at, bounty.value, bounty.fee, bounty.curator_deposit)
                },
                _ => Err(Error::<T>::InvalidBountyStatus)?,
            };
            
            // 发放赏金
            T::Currency::transfer(&Self::account_id(), &beneficiary, value - fee, ExistenceRequirement::KeepAlive)?;
            
            // 发放策展人费用并返还押金
            T::Currency::transfer(&Self::account_id(), &curator, fee + curator_deposit, ExistenceRequirement::KeepAlive)?;
            
            // 返还提案人押金
            T::Currency::unreserve(&bounty.proposer, bounty.bond);
            
            // 清除赏金数据
            <Bounties<T>>::remove(bounty_id);
            <BountyDescriptions<T>>::remove(bounty_id);
            
            Self::deposit_event(RawEvent::BountyClaimed(bounty_id, value));
            Ok(())
        }
        
        /// 关闭赏金
        #[weight = 10_000]
        pub fn close_bounty(origin, bounty_id: u32) -> dispatch::DispatchResult {
            ensure_root(origin)?;
            
            let bounty = Self::bounties(bounty_id).ok_or(Error::<T>::BountyNotFound)?;
            
            match &bounty.status {
                BountyStatus::Proposed | BountyStatus::Approved | BountyStatus::CuratorProposed(_) => {},
                _ => Err(Error::<T>::InvalidBountyStatus)?,
            };
            
            // 返还提案人押金
            T::Currency::unreserve(&bounty.proposer, bounty.bond);
            
            // 清除赏金数据
            <Bounties<T>>::remove(bounty_id);
            <BountyDescriptions<T>>::remove(bounty_id);
            
            Self::deposit_event(RawEvent::BountyCanceled(bounty_id));
            Ok(())
        }
        
        /// 延长赏金有效期
        #[weight = 10_000]
        pub fn extend_bounty_expiry(origin, bounty_id: u32) -> dispatch::DispatchResult {
            let signer = ensure_signed(origin)?;
            
            let mut bounty = Self::bounties(bounty_id).ok_or(Error::<T>::BountyNotFound)?;
            
            match &mut bounty.status {
                BountyStatus::Active { curator, update_due } => {
                    ensure!(curator == &signer, Error::<T>::InvalidBountyStatus);
                    *update_due = <system::Module<T>>::block_number() + T::BountyUpdatePeriod::get();
                },
                _ => Err(Error::<T>::InvalidBountyStatus)?,
            };
            
            <Bounties<T>>::insert(bounty_id, bounty);
            
            Self::deposit_event(RawEvent::BountyExtended(
                bounty_id,
                <system::Module<T>>::block_number() + T::BountyUpdatePeriod::get(),
            ));
            Ok(())
        }
    }
}

// 模块账户ID实现
impl<T: Trait> Module<T> {
    /// 获取模块账户ID
    pub fn account_id() -> T::AccountId {
        T::ModuleId::get().into_account()
    }
}

// 运行时配置示例
/*
impl pallet_bounties::Trait for Runtime {
    type Event = Event;
    type Currency = Balances;
    type BountyDepositBase = BountyDepositBase;
    type BountyDepositPerByte = BountyDepositPerByte;
    type BountyUpdatePeriod = BountyUpdatePeriod;
    type BountyCuratorDeposit = BountyCuratorDeposit;
    type BountyValueMinimum = BountyValueMinimum;
    type BountyFee = BountyFee;
}
*/

这个完整实现包含了pallet-bounties的主要功能:

  1. 赏金生命周期管理:

    • propose_bounty - 创建新赏金提案
    • approve_bounty - 批准赏金
    • propose_curator - 提议策展人
    • accept_curator - 接受策展人角色
    • award_bounty - 奖励完成的工作
    • claim_bounty - 领取赏金
    • close_bounty - 关闭赏金
    • extend_bounty_expiry - 延长赏金有效期
  2. 资金管理:

    • 处理提案人押金
    • 处理策展人押金和费用
    • 延迟支付机制
    • 资金发放和返还
  3. 状态管理:

    • 跟踪赏金从提案到支付的完整生命周期
    • 处理各种状态转换

要使用此模块,需要在运行时配置中设置适当的参数并集成模块。注释中提供了运行时配置的示例。


1 回复

Rust区块链开发利器pallet-bounties:Substrate赏金模块实现去中心化激励与任务管理

介绍

pallet-bounties是Substrate框架中的一个内置模块,专门设计用于在区块链上实现去中心化的赏金系统。它允许网络参与者创建资金池,为特定任务或问题解决方案提供经济激励,是构建DAO、开源项目协作平台或去中心化任务管理系统的理想工具。

该模块是Polkadot和Kusama等基于Substrate的区块链的核心功能之一,实现了链上赏金的全生命周期管理。

主要功能

  1. 赏金创建:存入资金并定义任务要求
  2. 赏金申请:参与者可以申请解决任务
  3. 赏金审批:通过民主机制或指定管理者审批
  4. 赏金支付:任务完成后发放奖励
  5. 赏金取消:在特定条件下取消并返还资金

完整示例代码

以下是基于pallet-bounties的完整使用示例,展示从创建到领取赏金的完整流程:

// 1. 在runtime中配置bounties模块
use frame_support::parameter_types;
use frame_system::EnsureRoot;

parameter_types! {
    pub const BountyDepositBase: u128 = 1 * DOLLARS;
    pub const BountyDepositPayoutDelay: u64 = 24 * HOURS;
    pub const BountyUpdatePeriod: u64 = 30 * DAYS;
    pub const BountyCuratorDeposit: Permill = Permill::from_percent(50);
    pub const BountyValueMinimum: u128 = 5 * DOLLARS;
    pub const MaximumReasonLength: u32 = 300;
}

impl pallet_bounties::Config for Runtime {
    type Event = Event;
    type BountyDepositBase = BountyDepositBase;
    type BountyDepositPayoutDelay = BountyDepositPayoutDelay;
    type BountyUpdatePeriod = BountyUpdatePeriod;
    type BountyCuratorDeposit = BountyCuratorDeposit;
    type BountyValueMinimum = BountyValueMinimum;
    type DataDepositPerByte = ();
    type MaximumReasonLength = MaximumReasonLength;
    type WeightInfo = ();
    type BountyApproveOrigin = EnsureRoot<AccountId>;
    type BountyCuratorOrigin = EnsureRoot<AccountId>;
}

// 2. 创建赏金提案
fn create_bounty() -> DispatchResult {
    // 账户1创建赏金,金额500,描述"Fix critical bug"
    Bounties::propose_bounty(
        RuntimeOrigin::signed(1),
        500,
        b"Fix critical bug in runtime".to_vec()
    )
}

// 3. 批准赏金提案
fn approve_bounty(bounty_id: u32) -> DispatchResult {
    // 根账户批准ID为bounty_id的赏金
    Bounties::approve_bounty(
        RuntimeOrigin::root(),
        bounty_id
    )
}

// 4. 指定赏金管理者
fn assign_curator(bounty_id: u32, curator: AccountId, deposit: u128) -> DispatchResult {
    // 根账户指定管理者
    Bounties::propose_curator(
        RuntimeOrigin::root(),
        bounty_id,
        curator,
        deposit
    )
}

// 5. 管理者接受角色
fn accept_curatorship(bounty_id: u32, curator: AccountId) -> DispatchResult {
    // 管理者账户接受角色
    Bounties::accept_curator(
        RuntimeOrigin::signed(curator),
        bounty_id
    )
}

// 6. 奖励赏金给解决方案提供者
fn award_bounty(bounty_id: u32, curator: AccountId, beneficiary: AccountId) -> DispatchResult {
    // 管理者账户奖励赏金给受益人
    Bounties::award_bounty(
        RuntimeOrigin::signed(curator),
        bounty_id,
        beneficiary
    )
}

// 7. 领取赏金
fn claim_bounty(bounty_id: u32, beneficiary: AccountId) -> DispatchResult {
    // 受益人账户领取赏金
    Bounties::claim_bounty(
        RuntimeOrigin::signed(beneficiary),
        bounty_id
    )
}

// 完整工作流程示例
fn full_bounty_workflow() -> DispatchResult {
    // 1. 创建赏金
    create_bounty()?;
    
    // 2. 批准赏金(ID为0)
    approve_bounty(0)?;
    
    // 3. 指定账户2为管理者,保证金100
    assign_curator(0, 2, 100)?;
    
    // 4. 账户2接受管理者角色
    accept_curatorship(0, 2)?;
    
    // 5. 管理者奖励赏金给账户3
    award_bounty(0, 2, 3)?;
    
    // 6. 账户3领取赏金
    claim_bounty(0, 3)
}

最佳实践

  1. 设置合理的保证金:防止垃圾赏金提案
  2. 明确赏金描述:减少争议
  3. 分阶段支付:对于复杂任务考虑拆分赏金
  4. 利用管理者角色:选择可信的领域专家作为管理者

pallet-bounties为区块链项目提供了强大的去中心化协作工具,通过合理配置可以构建出适应各种场景的任务激励系统。

回到顶部