mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-31 14:31:02 +00:00
Simultaneous referendums and multiple voting variants.
This commit is contained in:
@@ -18,73 +18,203 @@
|
|||||||
|
|
||||||
use rstd::prelude::*;
|
use rstd::prelude::*;
|
||||||
use integer_sqrt::IntegerSquareRoot;
|
use integer_sqrt::IntegerSquareRoot;
|
||||||
use codec::KeyedVec;
|
use codec::{KeyedVec, Slicable, Input, NonTrivialSlicable};
|
||||||
use runtime_support::storage;
|
use runtime_support::storage;
|
||||||
use demo_primitives::{Proposal, AccountId, Hash, BlockNumber};
|
use demo_primitives::{Proposal, AccountId, Hash, BlockNumber};
|
||||||
use runtime::{staking, system, session};
|
use runtime::{staking, system, session};
|
||||||
|
use runtime::staking::Balance;
|
||||||
|
|
||||||
const CURRENT_PROPOSAL: &[u8] = b"dem:pro";
|
pub type PropIndex = u32;
|
||||||
const VOTE_OF: &[u8] = b"dem:vot:";
|
pub type ReferendumIndex = u32;
|
||||||
const VOTERS: &[u8] = b"dem:vtr:";
|
|
||||||
|
|
||||||
pub mod public {
|
pub enum VoteThreshold {
|
||||||
use super::*;
|
SuperMajorityApprove,
|
||||||
|
SuperMajorityAgainst,
|
||||||
|
SimpleMajority,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Slicable for VoteThreshold {
|
||||||
|
fn decode<I: Input>(input: &mut I) -> Option<Self> {
|
||||||
|
u8::decode(input).and_then(|v| match v {
|
||||||
|
0 => Some(VoteThreshold::SuperMajorityApprove),
|
||||||
|
1 => Some(VoteThreshold::SuperMajorityAgainst),
|
||||||
|
2 => Some(VoteThreshold::SimpleMajority),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn using_encoded<R, F: FnOnce(&[u8]) -> R>(&self, f: F) -> R {
|
||||||
|
match *self {
|
||||||
|
VoteThreshold::SuperMajorityApprove => 0u8,
|
||||||
|
VoteThreshold::SuperMajorityAgainst => 1u8,
|
||||||
|
VoteThreshold::SimpleMajority => 2u8,
|
||||||
|
}.using_encoded(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl NonTrivialSlicable for VoteThreshold {}
|
||||||
|
|
||||||
|
impl VoteThreshold {
|
||||||
|
/// Given `approve` votes for and `against` votes against from a total electorate size of
|
||||||
|
/// `electorate` (`electorate - (approve + against)` are abstainers), then returns true if the
|
||||||
|
/// overall outcome is in favour of approval.
|
||||||
|
pub fn approved(&self, approve: Balance, against: Balance, electorate: Balance) -> bool {
|
||||||
|
let voters = approve + against;
|
||||||
|
match *self {
|
||||||
|
VoteThreshold::SuperMajorityApprove =>
|
||||||
|
voters.integer_sqrt() * approve / electorate.integer_sqrt() > against,
|
||||||
|
VoteThreshold::SuperMajorityAgainst =>
|
||||||
|
approve > voters.integer_sqrt() * against / electorate.integer_sqrt(),
|
||||||
|
VoteThreshold::SimpleMajority => approve > against,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// public proposals
|
||||||
|
const PUBLIC_PROP_COUNT: &[u8] = b"dem:cou"; // PropIndex
|
||||||
|
const PUBLIC_PROPS: &[u8] = b"dem:pub"; // Vec<(PropIndex, Proposal)>
|
||||||
|
const LOCKED_FOR: &[u8] = b"dem:loc:"; // PropIndex -> Balance
|
||||||
|
const LAUNCH_PERIOD: &[u8] = b"dem:lau"; // BlockNumber
|
||||||
|
|
||||||
|
// referenda
|
||||||
|
const VOTING_PERIOD: &[u8] = b"dem:per"; // BlockNumber
|
||||||
|
const REFERENDUM_COUNT: &[u8] = b"dem:cou"; // ReferendumIndex
|
||||||
|
const NEXT_TALLY: &[u8] = b"dem:nxt"; // ReferendumIndex
|
||||||
|
const REFERENDUM_INFO_OF: &[u8] = b"dem:pro"; // ReferendumIndex -> (BlockNumber, Proposal, VoteThreshold)
|
||||||
|
const VOTERS: &[u8] = b"dem:vtr:"; // ReferendumIndex -> Vec<AccountId>
|
||||||
|
const VOTE_OF: &[u8] = b"dem:vot:"; // (ReferendumIndex, AccountId) -> bool
|
||||||
|
|
||||||
|
/// How often (in blocks) to check for new votes.
|
||||||
|
pub fn voting_period() -> BlockNumber {
|
||||||
|
storage::get(VOTING_PERIOD)
|
||||||
|
.expect("all core parameters of council module must be in place")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// How often (in blocks) new public referenda are launched.
|
||||||
|
pub fn launch_period() -> BlockNumber {
|
||||||
|
storage::get(LAUNCH_PERIOD)
|
||||||
|
.expect("all core parameters of council module must be in place")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The public proposals. Unsorted.
|
||||||
|
pub fn public_props() -> Vec<(PropIndex, Proposal)> {
|
||||||
|
storage::get_or_default(PUBLIC_PROPS)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the amount locked in support of `proposal`; false if proposal isn't a valid proposal
|
||||||
|
/// index.
|
||||||
|
pub fn locked_for(proposal: PropIndex) -> Option<Balance> {
|
||||||
|
storage::get(&proposal.to_keyed_vec(LOCKED_FOR))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return true if `ref_index` is an on-going referendum.
|
||||||
|
pub fn is_active_referendum(ref_index: ReferendumIndex) -> bool {
|
||||||
|
storage::exists(&ref_index.to_keyed_vec(REFERENDUM_INFO_OF))
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the voters for the current proposal.
|
/// Get the voters for the current proposal.
|
||||||
pub fn voters() -> Vec<AccountId> {
|
pub fn voters_for(ref_index: ReferendumIndex) -> Vec<AccountId> {
|
||||||
storage::get_or_default(VOTERS)
|
storage::get_or_default(&ref_index.to_keyed_vec(VOTERS))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the vote, if Some, of `who`.
|
/// Get the vote, if Some, of `who`.
|
||||||
pub fn vote_of(who: &AccountId) -> Option<bool> {
|
pub fn vote_of(who: &AccountId, ref_index: ReferendumIndex) -> Option<bool> {
|
||||||
storage::get(&who.to_keyed_vec(VOTE_OF))
|
storage::get(&(*who, ref_index).to_keyed_vec(VOTE_OF))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the info concerning the next referendum.
|
||||||
|
pub fn referendum_info(ref_index: ReferendumIndex) -> Option<(BlockNumber, Proposal, VoteThreshold)> {
|
||||||
|
storage::get(&ref_index.to_keyed_vec(REFERENDUM_INFO_OF))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get all referendums ready for tally at block `n`.
|
||||||
|
pub fn maturing_referendums_at(n: BlockNumber) -> Vec<(ReferendumIndex, BlockNumber, Proposal, VoteThreshold)> {
|
||||||
|
let next: ReferendumIndex = storage::get_or_default(NEXT_TALLY);
|
||||||
|
let last: ReferendumIndex = storage::get_or_default(REFERENDUM_COUNT);
|
||||||
|
(next..last).into_iter()
|
||||||
|
.filter_map(|i| referendum_info(i).map(|(n, p, t)| (i, n, p, t)))
|
||||||
|
.take_while(|&(_, block_number, _, _)| block_number == n)
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the voters for the current proposal.
|
/// Get the voters for the current proposal.
|
||||||
pub fn tally() -> (staking::Balance, staking::Balance) {
|
pub fn tally(ref_index: ReferendumIndex) -> (staking::Balance, staking::Balance) {
|
||||||
voters().iter()
|
voters_for(ref_index).iter()
|
||||||
.map(|a| (staking::balance(a), vote_of(a).expect("all items come from `voters`; for an item to be in `voters` there must be a vote registered; qed")))
|
.map(|a| (staking::balance(a), vote_of(a, ref_index).expect("all items come from `voters`; for an item to be in `voters` there must be a vote registered; qed")))
|
||||||
.map(|(bal, vote)| if vote { (bal, 0) } else { (0, bal) })
|
.map(|(bal, vote)| if vote { (bal, 0) } else { (0, bal) })
|
||||||
.fold((0, 0), |(a, b), (c, d)| (a + c, b + d))
|
.fold((0, 0), |(a, b), (c, d)| (a + c, b + d))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Propose a sensitive action to be taken.
|
/// Get the next free referendum index, aka the number of referendums started so far.
|
||||||
pub fn propose(validator: &AccountId, proposal: &Proposal) {
|
pub fn next_free_ref_index() -> ReferendumIndex {
|
||||||
if storage::exists(CURRENT_PROPOSAL) {
|
storage::get_or_default(REFERENDUM_COUNT)
|
||||||
panic!("there may only be one proposal per era.");
|
|
||||||
}
|
|
||||||
storage::put(CURRENT_PROPOSAL, proposal);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Vote for or against the proposal.
|
pub mod public {
|
||||||
pub fn vote(who: &AccountId, era_index: BlockNumber, way: bool) {
|
use super::*;
|
||||||
if era_index != staking::current_era() {
|
|
||||||
panic!("approval vote applied on non-current era.")
|
/// Propose a sensitive action to be taken.
|
||||||
|
pub fn propose(signed: &AccountId, proposal: &Proposal, lock: Balance) {
|
||||||
|
let b = staking::balance(signed);
|
||||||
|
assert!(b >= lock);
|
||||||
|
|
||||||
|
staking::internal::set_balance(signed, b - lock);
|
||||||
|
|
||||||
|
let index: PropIndex = storage::get_or_default(PUBLIC_PROP_COUNT);
|
||||||
|
storage::put(PUBLIC_PROP_COUNT, &(index + 1));
|
||||||
|
storage::put(&index.to_keyed_vec(LOCKED_FOR), &lock);
|
||||||
|
|
||||||
|
let mut props: Vec<(PropIndex, Proposal)> = storage::get_or_default(PUBLIC_PROPS);
|
||||||
|
props.push((index, proposal.clone()));
|
||||||
|
storage::put(PUBLIC_PROPS, &props);
|
||||||
}
|
}
|
||||||
if !storage::exists(CURRENT_PROPOSAL) {
|
|
||||||
panic!("there must be a proposal in order to approve.");
|
/// Propose a sensitive action to be taken.
|
||||||
|
pub fn second(signed: &AccountId, proposal: PropIndex, lock: Balance) {
|
||||||
|
let b = staking::balance(signed);
|
||||||
|
assert!(b >= lock);
|
||||||
|
let key = proposal.to_keyed_vec(LOCKED_FOR);
|
||||||
|
let balance: Balance = storage::get(&key).expect("can only second an existing proposal");
|
||||||
|
|
||||||
|
staking::internal::set_balance(signed, b - lock);
|
||||||
|
storage::put(&key, &(balance + lock));
|
||||||
}
|
}
|
||||||
if staking::balance(who) == 0 {
|
|
||||||
|
/// Vote in a referendum. If `approve_proposal` is true, the vote is to enact the proposal;
|
||||||
|
/// false would be a vote to keep the status quo..
|
||||||
|
pub fn vote(signed: &AccountId, ref_index: ReferendumIndex, approve_proposal: bool) {
|
||||||
|
if !is_active_referendum(ref_index) {
|
||||||
|
panic!("vote given for invalid referendum.")
|
||||||
|
}
|
||||||
|
if staking::balance(signed) == 0 {
|
||||||
panic!("transactor must have balance to signal approval.");
|
panic!("transactor must have balance to signal approval.");
|
||||||
}
|
}
|
||||||
let key = who.to_keyed_vec(VOTE_OF);
|
let key = (*signed, ref_index).to_keyed_vec(VOTE_OF);
|
||||||
if !storage::exists(&key) {
|
if !storage::exists(&key) {
|
||||||
let mut voters = voters();
|
let mut voters = voters_for(ref_index);
|
||||||
voters.push(who.clone());
|
voters.push(signed.clone());
|
||||||
storage::put(VOTERS, &voters);
|
storage::put(VOTERS, &voters);
|
||||||
}
|
}
|
||||||
storage::put(&key, &way);
|
storage::put(&key, &approve_proposal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod privileged {
|
pub mod privileged {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
pub fn clear_proposal() {
|
/// Can be called directly by the council.
|
||||||
for v in public::voters() {
|
pub fn start_referendum(
|
||||||
storage::kill(&v.to_keyed_vec(VOTE_OF));
|
proposal: Proposal,
|
||||||
|
vote_threshold: VoteThreshold
|
||||||
|
) {
|
||||||
|
inject_referendum(system::block_number() + voting_period(), proposal, vote_threshold);
|
||||||
}
|
}
|
||||||
storage::kill(VOTERS);
|
|
||||||
|
/// Remove a referendum.
|
||||||
|
pub fn clear_referendum(ref_index: ReferendumIndex) {
|
||||||
|
for v in voters_for(ref_index) {
|
||||||
|
storage::kill(&(v, ref_index).to_keyed_vec(VOTE_OF));
|
||||||
|
}
|
||||||
|
storage::kill(&ref_index.to_keyed_vec(VOTERS));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,23 +224,51 @@ pub mod internal {
|
|||||||
use dispatch::enact_proposal;
|
use dispatch::enact_proposal;
|
||||||
|
|
||||||
/// Current era is ending; we should finish up any proposals.
|
/// Current era is ending; we should finish up any proposals.
|
||||||
pub fn end_of_an_era() {
|
pub fn end_block() {
|
||||||
// tally up votes for the current proposal, if any. enact if there are sufficient approvals.
|
let now = system::block_number();
|
||||||
if let Some(proposal) = storage::take::<Proposal>(CURRENT_PROPOSAL) {
|
|
||||||
let tally = public::tally();
|
// pick out another public referendum if it's time.
|
||||||
|
if now % launch_period() == 0 {
|
||||||
|
let mut public_props = public_props();
|
||||||
|
if let Some((winner_index, _)) = public_props.iter()
|
||||||
|
.enumerate()
|
||||||
|
.max_by_key(|x| locked_for((x.1).0))
|
||||||
|
{
|
||||||
|
let (_, proposal) = public_props.swap_remove(winner_index);
|
||||||
|
storage::put(PUBLIC_PROPS, &public_props);
|
||||||
|
|
||||||
|
inject_referendum(now + voting_period(), proposal, VoteThreshold::SuperMajorityApprove);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// tally up votes for any expiring referenda.
|
||||||
|
for (index, _, proposal, vote_threshold) in maturing_referendums_at(now) {
|
||||||
|
let (approve, against) = tally(index);
|
||||||
let total_stake = staking::total_stake();
|
let total_stake = staking::total_stake();
|
||||||
privileged::clear_proposal();
|
privileged::clear_referendum(index);
|
||||||
|
if vote_threshold.approved(approve, against, total_stake) {
|
||||||
// TODO: protect against overflows.
|
|
||||||
let threshold = (tally.0 + tally.1).integer_sqrt() * tally.0 / total_stake.integer_sqrt();
|
|
||||||
|
|
||||||
if tally.1 < threshold {
|
|
||||||
enact_proposal(proposal);
|
enact_proposal(proposal);
|
||||||
}
|
}
|
||||||
|
storage::put(NEXT_TALLY, &(index + 1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Start a referendum
|
||||||
|
fn inject_referendum(
|
||||||
|
end: BlockNumber,
|
||||||
|
proposal: Proposal,
|
||||||
|
vote_threshold: VoteThreshold
|
||||||
|
) {
|
||||||
|
let ref_index: ReferendumIndex = storage::get_or_default(REFERENDUM_COUNT);
|
||||||
|
if ref_index > 0 && referendum_info(ref_index - 1).map(|i| i.0 < end).unwrap_or(false) {
|
||||||
|
panic!("Cannot inject a referendum that ends earlier than preceeding referendum");
|
||||||
|
}
|
||||||
|
|
||||||
|
storage::put(REFERENDUM_COUNT, &(ref_index + 1));
|
||||||
|
storage::put(&ref_index.to_keyed_vec(REFERENDUM_INFO_OF), &(end, proposal, vote_threshold));
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@@ -25,21 +25,7 @@ pub trait KeyedVec {
|
|||||||
fn to_keyed_vec(&self, prepend_key: &[u8]) -> Vec<u8>;
|
fn to_keyed_vec(&self, prepend_key: &[u8]) -> Vec<u8>;
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! impl_non_endians {
|
impl<T: Slicable> KeyedVec for T {
|
||||||
( $( $t:ty ),* ) => { $(
|
|
||||||
impl KeyedVec for $t {
|
|
||||||
fn to_keyed_vec(&self, prepend_key: &[u8]) -> Vec<u8> {
|
|
||||||
let mut r = prepend_key.to_vec();
|
|
||||||
r.extend(&self[..]);
|
|
||||||
r
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)* }
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! impl_endians {
|
|
||||||
( $( $t:ty ),* ) => { $(
|
|
||||||
impl KeyedVec for $t {
|
|
||||||
fn to_keyed_vec(&self, prepend_key: &[u8]) -> Vec<u8> {
|
fn to_keyed_vec(&self, prepend_key: &[u8]) -> Vec<u8> {
|
||||||
self.using_encoded(|slice| {
|
self.using_encoded(|slice| {
|
||||||
let mut r = prepend_key.to_vec();
|
let mut r = prepend_key.to_vec();
|
||||||
@@ -48,10 +34,3 @@ macro_rules! impl_endians {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)* }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_endians!(u8, i8, u16, u32, u64, usize, i16, i32, i64, isize);
|
|
||||||
impl_non_endians!([u8; 1], [u8; 2], [u8; 3], [u8; 4], [u8; 5], [u8; 6], [u8; 7], [u8; 8],
|
|
||||||
[u8; 10], [u8; 12], [u8; 14], [u8; 16], [u8; 20], [u8; 24], [u8; 28], [u8; 32], [u8; 40],
|
|
||||||
[u8; 48], [u8; 56], [u8; 64], [u8; 80], [u8; 96], [u8; 112], [u8; 128]);
|
|
||||||
|
|||||||
Reference in New Issue
Block a user