Rust链上治理插件库pallet-referenda的使用,实现Substrate区块链公投和投票系统
Referenda Pallet 公投模块
概述
Referenda pallet 负责处理一般利益相关者投票的管理工作。
主要功能说明
-
提交公投:
- 使用
referenda.submit
extrinsic提交新的公投提案 - 需要指定提案来源、提案内容和执行时机
- 使用
-
投票:
- 使用
referenda.vote
extrinsic对公投进行投票 - 支持标准投票(Standard)和分叉投票(Split)两种方式
- 使用
-
公投管理:
- 可以取消、终止或杀死公投
- 公投状态自动跟踪(提交、准备、决策、批准、执行等)
-
投票机制:
- 支持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}`);
}
});
};
*/
注意事项
- 需要根据实际需求配置
TrackInfo
参数,定义不同公投轨道的决策规则 - 需要配合Preimage pallet使用,用于存储大型提案
- 可以结合Conviction Voting pallet实现更复杂的投票机制
- 在生产环境中需要仔细调整参数如SubmissionDeposit等
这个示例展示了如何使用pallet-referenda在Substrate链上实现基本的公投和投票系统。根据实际需求,您可能需要调整配置参数或添加其他功能模块。
1 回复
Rust链上治理插件库pallet-referenda的使用指南
介绍
pallet-referenda
是Substrate区块链框架中的一个链上治理模块,用于实现公投和投票系统。它允许社区成员提出提案并通过投票决定是否执行这些提案,是Substrate链上治理的核心组件之一。
主要功能
- 创建和管理公投提案
- 设置投票机制和阈值
- 跟踪投票状态和结果
- 自动执行通过的公投
使用方法
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!("提案未完成"),
}
}
注意事项
- 确保为
pallet-referenda
配置了适当的起源(origin)检查 - 根据链的需求调整投票参数和轨道设置
- 考虑与其他治理pallet(如
pallet-democracy
、pallet-collective
)的交互 - 测试不同的投票场景以确保系统按预期工作
pallet-referenda
提供了灵活的链上治理机制,可以根据项目需求进行定制化配置,是实现去中心化治理的强大工具。