Rust链上治理插件库pallet-referenda的使用,实现Substrate区块链公投和投票系统

Referenda Pallet 公投模块

概述

Referenda pallet 负责处理一般利益相关者投票的管理工作。

主要功能说明

  1. 提交公投:

    • 使用referenda.submit extrinsic提交新的公投提案
    • 需要指定提案来源、提案内容和执行时机
  2. 投票:

    • 使用referenda.vote extrinsic对公投进行投票
    • 支持标准投票(Standard)和分叉投票(Split)两种方式
  3. 公投管理:

    • 可以取消、终止或杀死公投
    • 公投状态自动跟踪(提交、准备、决策、批准、执行等)
  4. 投票机制:

    • 支持conviction投票(锁定代币增加投票权重)
    • 投票结果根据配置的轨道(track)规则决定是否通过

完整示例代码

// runtime/src/lib.rs

// 1. 引入必要的依赖
pub use pallet_referenda;

// 2. 配置参数
parameter_types! {
    // 最大公投数量
    pub const MaxReferenda: u32 = 100;
    // 最大存款数量
    pub const MaxDeposits: u32 = 100;
    // 提交公投所需的押金
    pub const SubmissionDeposit: Balance = 1 * DOLLARS;
    // 公投轨道信息
    pub const TrackInfo: BoundedVec<(u16, Referenda.TrackInfo), ConstU32<10>> = BoundedVec::new();
}

// 3. 实现Config trait
impl pallet_referenda::Config for Runtime {
    // 权重信息
    type WeightInfo = pallet_referenda::weights::SubstrateWeight<Runtime>;
    // 调用类型
    type Call = Call;
    // 来源类型
    type Origin = Origin;
    // 最大公投数
    type MaxReferenda = MaxReferenda;
    // 最大存款数
    type MaxDeposits = MaxDeposits;
    // 提交押金
    type SubmissionDeposit = SubmissionDeposit;
    // 轨道信息
    type TrackInfo = TrackInfo;
    // 轨道实现
    type Tracks = ();
    // 警报间隔
    type AlarmInterval = ConstU32<1>;
    // 提交来源
    type SubmissionOrigin = EnsureRoot<AccountId>;
    // 取消来源
    type CancellationOrigin = EnsureRoot<AccountId>;
    // 终止来源
    type KillOrigin = EnsureRoot<AccountId>;
    // 惩罚机制
    type Slash = ();
    // 调度器
    type Scheduler = Scheduler;
    // 货币类型
    type Currency = Balances;
    // 注册器
    type Registrar = ();
    // 预映像模块
    type Preimages = Preimage;
}

// 4. 在construct_runtime!宏中添加pallet
construct_runtime!(
    pub enum Runtime where
        Block = Block,
        NodeBlock = opaque::Block,
        UncheckedExtrinsic = UncheckedExtrinsic
    {
        // ...其他pallet
        Referenda: pallet_referenda::{Pallet, Call, Storage, Event<T>} = 30,
    }
);

// 5. 前端调用示例 (使用polkadot.js API)
/*
// 提交公投
const submitReferendum = async (proposal, account) => {
  const tx = api.tx.referenda.submit(
    {
      system: 'Root', // 提交来源
    },
    {
      Legacy: proposal, // 提案内容
    },
    {
      After: 100, // 执行区块
    }
  );
  
  await tx.signAndSend(account, ({ status }) => {
    if (status.isInBlock) {
      console.log(`Referendum submitted at block ${status.asInBlock}`);
    }
  });
};

// 对公投进行投票
const voteOnReferendum = async (referendumIndex, vote, account) => {
  const tx = api.tx.referenda.vote(
    referendumIndex,
    {
      Standard: {
        vote: {
          aye: vote === 'Aye', // 赞成或反对
          conviction: 'None', // 确信度
        },
        balance: 10 * 1e12, // 投票权重
      },
    }
  );
  
  await tx.signAndSend(account, ({ status }) => {
    if (status.isInBlock) {
      console.log(`Vote cast at block ${status.asInBlock}`);
    }
  });
};
*/

注意事项

  1. 需要根据实际需求配置TrackInfo参数,定义不同公投轨道的决策规则
  2. 需要配合Preimage pallet使用,用于存储大型提案
  3. 可以结合Conviction Voting pallet实现更复杂的投票机制
  4. 在生产环境中需要仔细调整参数如SubmissionDeposit等

这个示例展示了如何使用pallet-referenda在Substrate链上实现基本的公投和投票系统。根据实际需求,您可能需要调整配置参数或添加其他功能模块。


1 回复

Rust链上治理插件库pallet-referenda的使用指南

介绍

pallet-referenda是Substrate区块链框架中的一个链上治理模块,用于实现公投和投票系统。它允许社区成员提出提案并通过投票决定是否执行这些提案,是Substrate链上治理的核心组件之一。

主要功能

  1. 创建和管理公投提案
  2. 设置投票机制和阈值
  3. 跟踪投票状态和结果
  4. 自动执行通过的公投

使用方法

1. 在runtime中集成pallet-referenda

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

impl pallet_referenda::Config for Runtime {
    type RuntimeEvent = RuntimeEvent;
    type Scheduler = Scheduler;
    type Currency = Balances;
    type SubmitOrigin = frame_system::EnsureRoot<AccountId>;
    type CancelOrigin = frame_system::EnsureRoot<AccountId>;
    type KillOrigin = frame_system::EnsureRoot<AccountId>;
    type Slash = Treasury;
    type Votes = pallet_conviction_voting::VotesOf<Runtime>;
    type Tally = pallet_conviction_voting::TallyOf<Runtime>;
    type SubmissionDeposit = ConstU128<1000>;
    type MaxQueued = ConstU32<100>;
    type UndecidingTimeout = ConstU32<100>;
    type AlarmInterval = ConstU32<1>;
    type WeightInfo = pallet_referenda::weights::SubstrateWeight<Runtime>;
}

2. 提交公投提案

用户可以通过调用submit函数提交新的公投提案:

let proposal = RuntimeCall::System(frame_system::Call::remark { remark: b"Test proposal".to_vec() });
let _ = Referenda::submit(
    RuntimeOrigin::signed(1),
    Box::new(proposal),
    pallet_referenda::TrackInfo::default(),
);

3. 投票

用户可以使用vote函数对提案进行投票:

let _ = Referenda::vote(
    RuntimeOrigin::signed(2),
    0,  // 提案ID
    pallet_conviction_voting::Vote { aye: true, conviction: pallet_conviction_voting::Conviction::Locked1x },
);

4. 查询公投状态

可以通过存储项查询公投的状态:

let referendum_info = Referenda::referendum_info(0);
match referendum_info {
    Some(pallet_referenda::ReferendumInfo::Ongoing(status)) => {
        println!("Referendum is ongoing: {:?}", status);
    }
    Some(pallet_referenda::ReferendumInfo::Approved(..)) => {
        println!("Referendum was approved");
    }
    Some(pallet_referenda::ReferendumInfo::Rejected(..)) => {
        println!("Referendum was rejected");
    }
    None => {
        println!("Referendum not found");
    }
}

高级配置

自定义投票轨道

可以配置不同的投票轨道来处理不同类型的提案:

pub struct TracksInfo;
impl pallet_referenda::TracksInfo for TracksInfo {
    type Id = u8;
    type RuntimeOrigin = <Runtime as frame_system::Config>::RuntimeOrigin;
    
    fn tracks() -> &'static [(Self::Id, pallet_referenda::TrackInfo)] {
        static DATA: [(u8, pallet_referenda::TrackInfo); 2] = [
            (0, pallet_referenda::TrackInfo {
                name: "root",
                max_deciding: 1,
                decision_deposit: 1000u128.into(),
                prepare_period: 10u32.into(),
                decision_period: 100u32.into(),
                confirm_period: 10u32.into(),
                min_enactment_period: 5u32.into(),
                min_approval: pallet_referenda::Curve::LinearDecreasing {
                    length: Perbill::from_percent(100),
                    floor: Perbill::from_percent(50),
                    ceil: Perbill::from_percent(100),
                },
                min_support: pallet_referenda::Curve::LinearDecreasing {
                    length: Perbill::from perpercent(100),
                    floor: Perbill::from_percent(0),
                    ceil: Perbill::from_percent(50),
                },
            }),
            // 可以添加更多轨道...
        ];
        &DATA
    }
}

完整示例代码

// 导入必要的依赖
use frame_support::{parameter_types, traits::ConstU32};
use frame_system::EnsureRoot;
use pallet_balances;
use pallet_conviction_voting;
use pallet_referenda;
use sp_runtime::{Perbill, traits::AccountIdConversion};

// 1. 配置runtime
parameter_types! {
    pub const SubmissionDeposit: u128 = 1000;
    pub const MaxQueued: u32 = 100;
    pub const UndecidingTimeout: u32 = 100;
    pub const AlarmInterval: u32 = 1;
}

impl pallet_referenda::Config for Runtime {
    type RuntimeEvent = RuntimeEvent;
    type Scheduler = Scheduler;
    type Currency = Balances;
    type SubmitOrigin = EnsureRoot<AccountId>;
    type CancelOrigin = EnsureRoot<AccountId>;
    type KillOrigin = EnsureRoot<AccountId>;
    type Slash = Treasury;
    type Votes = pallet_conviction_voting::VotesOf<Runtime>;
    type Tally = pallet_conviction_voting::TallyOf<Runtime>;
    type SubmissionDeposit = SubmissionDeposit;
    type MaxQueued = ConstU32<100>;
    type UndecidingTimeout = UndecidingTimeout;
    type AlarmInterval = AlarmInterval;
    type WeightInfo = ();
}

// 2. 自定义投票轨道配置
pub struct TracksInfo;
impl pallet_referenda::TracksInfo for TracksInfo {
    type Id = u8;
    type RuntimeOrigin = <Runtime as frame_system::Config>::RuntimeOrigin;
    
    fn tracks() -> &'static [(Self::Id, pallet_referenda::TrackInfo)] {
        static DATA: [(u8, pallet_referenda::TrackInfo); 2] = [
            (0, pallet_referenda::TrackInfo {
                name: "root",
                max_deciding: 1,
                decision_deposit: 1000u128.into(),
                prepare_period: 10u32.into(),
                decision_period: 100u32.into(),
                confirm_period: 10u32.into(),
                min_enactment_period: 5u32.into(),
                min_approval: pallet_referenda::Curve::LinearDecreasing {
                    length: Perbill::from_percent(100),
                    floor: Perbill::from_percent(50),
                    ceil: Perbill::from_percent(100),
                },
                min_support: pallet_referenda::Curve::LinearDecreasing {
                    length: Perbill::from_percent(100),
                    floor: Perbill::from_percent(0),
                    ceil: Perbill::from_percent(50),
                },
            }),
            (1, pallet_referenda::TrackInfo {
                name: "general",
                max_deciding: 5,
                decision_deposit: 500u128.into(),
                prepare_period: 5u32.into(),
                decision_period: 50u32.into(),
                confirm_period: 5u32.into(),
                min_enactment_period: 3u32.into(),
                min_approval: pallet_referenda::Curve::LinearDecreasing {
                    length: Perbill::from_percent(100),
                    floor: Perbill::from_percent(30),
                    ceil: Perbill::from_percent(80),
                },
                min_support: pallet_referenda::Curve::LinearDecreasing {
                    length: Perbill::from_percent(100),
                    floor: Perbill::from_percent(0),
                    ceil: Perbill::from_percent(30),
                },
            }),
        ];
        &DATA
    }
}

// 3. 完整的公投流程示例
fn full_referendum_process() {
    // 创建测试账户
    let alice = 1;
    let bob = 2;
    let charlie = 3;
    
    // 3.1 提交提案
    let proposal = RuntimeCall::System(frame_system::Call::remark { 
        remark: b"Upgrade network protocol".to_vec() 
    });
    
    let submit_result = Referenda::submit(
        RuntimeOrigin::signed(alice),
        Box::new(proposal),
        1,  // 使用general轨道
    );
    
    assert!(submit_result.is_ok());
    
    // 3.2 多个用户投票
    // Alice投票支持(1倍锁定)
    let vote_alice = Referenda::vote(
        RuntimeOrigin::signed(alice),
        0,  // 第一个提案
        pallet_conviction_voting::Vote { 
            aye: true, 
            conviction: pallet_conviction_voting::Conviction::Locked1x 
        },
    );
    assert!(vote_alice.is_ok());
    
    // Bob投票反对(2倍锁定)
    let vote_bob = Referenda::vote(
        RuntimeOrigin::signed(bob),
        0,
        pallet_conviction_voting::Vote { 
            aye: false, 
            conviction: pallet_conviction_voting::Conviction::Locked2x 
        },
    );
    assert!(vote_bob.is_ok());
    
    // Charlie投票支持(无锁定)
    let vote_charlie = Referenda::vote(
        RuntimeOrigin::signed(charlie),
        0,
        pallet_conviction_voting::Vote { 
            aye: true, 
            conviction: pallet_conviction_voting::Conviction::None 
        },
    );
    assert!(vote_charlie.is_ok());
    
    // 3.3 查询提案状态
    let referendum_info = Referenda::referendum_info(0);
    match referendum_info {
        Some(pallet_referenda::ReferendumInfo::Ongoing(status)) => {
            println!("当前投票状态: {:?}", status);
            println!("赞成票: {}", status.tally.ayes);
            println!("反对票: {}", status.tally.nays);
        }
        _ => panic!("提案状态异常"),
    }
    
    // 3.4 模拟投票期结束后的执行
    // 这里需要根据实际链的状态推进区块来触发自动执行
    // 在测试环境中可以手动调用决策函数
    
    // 3.5 查询最终结果
    let final_info = Referenda::referendum_info(0);
    match final_info {
        Some(pallet_referenda::ReferendumInfo::Approved(_, enactment)) => {
            println!("提案已通过,将在区块{}执行", enactment);
        }
        Some(pallet_referenda::ReferendumInfo::Rejected(_)) => {
            println!("提案被拒绝");
        }
        _ => panic!("提案未完成"),
    }
}

注意事项

  1. 确保为pallet-referenda配置了适当的起源(origin)检查
  2. 根据链的需求调整投票参数和轨道设置
  3. 考虑与其他治理pallet(如pallet-democracypallet-collective)的交互
  4. 测试不同的投票场景以确保系统按预期工作

pallet-referenda提供了灵活的链上治理机制,可以根据项目需求进行定制化配置,是实现去中心化治理的强大工具。

回到顶部