// Copyright 2017-2019 Parity Technologies (UK) Ltd. // This file is part of Substrate. // Substrate is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // Substrate is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with Substrate. If not, see . //! Democratic system: Handles administration of general stakeholder voting. #![recursion_limit="128"] #![cfg_attr(not(feature = "std"), no_std)] use rstd::prelude::*; use rstd::{result, convert::TryFrom}; use sr_primitives::{ traits::{Zero, Bounded, CheckedMul, CheckedDiv, EnsureOrigin, Hash, Dispatchable}, weights::SimpleDispatchInfo, }; use codec::{Ref, Encode, Decode, Input, Output, Error}; use support::{ decl_module, decl_storage, decl_event, ensure, Parameter, traits::{ Currency, ReservableCurrency, LockableCurrency, WithdrawReason, LockIdentifier, Get, OnFreeBalanceZero } }; use support::dispatch::Result; use system::{ensure_signed, ensure_root}; mod vote_threshold; pub use vote_threshold::{Approved, VoteThreshold}; const DEMOCRACY_ID: LockIdentifier = *b"democrac"; /// A proposal index. pub type PropIndex = u32; /// A referendum index. pub type ReferendumIndex = u32; /// A value denoting the strength of conviction of a vote. #[derive(Encode, Decode, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] #[cfg_attr(feature = "std", derive(Debug))] pub enum Conviction { /// 0.1x votes, unlocked. None, /// 1x votes, locked for an enactment period following a successful vote. Locked1x, /// 2x votes, locked for 2x enactment periods following a successful vote. Locked2x, /// 3x votes, locked for 4x... Locked3x, /// 4x votes, locked for 8x... Locked4x, /// 5x votes, locked for 16x... Locked5x, /// 6x votes, locked for 32x... Locked6x, } impl Default for Conviction { fn default() -> Self { Conviction::None } } impl From for u8 { fn from(c: Conviction) -> u8 { match c { Conviction::None => 0, Conviction::Locked1x => 1, Conviction::Locked2x => 2, Conviction::Locked3x => 3, Conviction::Locked4x => 4, Conviction::Locked5x => 5, Conviction::Locked6x => 6, } } } impl TryFrom for Conviction { type Error = (); fn try_from(i: u8) -> result::Result { Ok(match i { 0 => Conviction::None, 1 => Conviction::Locked1x, 2 => Conviction::Locked2x, 3 => Conviction::Locked3x, 4 => Conviction::Locked4x, 5 => Conviction::Locked5x, 6 => Conviction::Locked6x, _ => return Err(()), }) } } impl Conviction { /// The amount of time (in number of periods) that our conviction implies a successful voter's /// balance should be locked for. fn lock_periods(self) -> u32 { match self { Conviction::None => 0, Conviction::Locked1x => 1, Conviction::Locked2x => 2, Conviction::Locked3x => 4, Conviction::Locked4x => 8, Conviction::Locked5x => 16, Conviction::Locked6x => 32, } } /// The votes of a voter of the given `balance` with our conviction. fn votes< B: From + Zero + Copy + CheckedMul + CheckedDiv + Bounded >(self, balance: B) -> (B, B) { match self { Conviction::None => { let r = balance.checked_div(&10u8.into()).unwrap_or_else(Zero::zero); (r, r) } x => ( balance.checked_mul(&u8::from(x).into()).unwrap_or_else(B::max_value), balance, ) } } } impl Bounded for Conviction { fn min_value() -> Self { Conviction::None } fn max_value() -> Self { Conviction::Locked6x } } const MAX_RECURSION_LIMIT: u32 = 16; /// A number of lock periods, plus a vote, one way or the other. #[derive(Copy, Clone, Eq, PartialEq, Default)] #[cfg_attr(feature = "std", derive(Debug))] pub struct Vote { pub aye: bool, pub conviction: Conviction, } impl Encode for Vote { fn encode_to(&self, output: &mut T) { output.push_byte(u8::from(self.conviction) | if self.aye { 0b1000_0000 } else { 0 }); } } impl codec::EncodeLike for Vote {} impl Decode for Vote { fn decode(input: &mut I) -> core::result::Result { let b = input.read_byte()?; Ok(Vote { aye: (b & 0b1000_0000) == 0b1000_0000, conviction: Conviction::try_from(b & 0b0111_1111) .map_err(|_| Error::from("Invalid conviction"))?, }) } } type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; pub trait Trait: system::Trait + Sized { type Proposal: Parameter + Dispatchable; type Event: From> + Into<::Event>; /// Currency type for this module. type Currency: ReservableCurrency + LockableCurrency; /// The minimum period of locking and the period between a proposal being approved and enacted. /// /// It should generally be a little more than the unstake period to ensure that /// voting stakers have an opportunity to remove themselves from the system in the case where /// they are on the losing side of a vote. type EnactmentPeriod: Get; /// How often (in blocks) new public referenda are launched. type LaunchPeriod: Get; /// How often (in blocks) to check for new votes. type VotingPeriod: Get; /// The minimum amount to be used as a deposit for a public referendum proposal. type MinimumDeposit: Get>; /// Origin from which the next tabled referendum may be forced. This is a normal /// "super-majority-required" referendum. type ExternalOrigin: EnsureOrigin; /// Origin from which the next tabled referendum may be forced; this allows for the tabling of /// a majority-carries referendum. type ExternalMajorityOrigin: EnsureOrigin; /// Origin from which the next tabled referendum may be forced; this allows for the tabling of /// a negative-turnout-bias (default-carries) referendum. type ExternalDefaultOrigin: EnsureOrigin; /// Origin from which the next referendum proposed by the external majority may be immediately /// tabled to vote asynchronously in a similar manner to the emergency origin. It remains a /// majority-carries vote. type FastTrackOrigin: EnsureOrigin; /// Minimum voting period allowed for an fast-track/emergency referendum. type EmergencyVotingPeriod: Get; /// Origin from which any referendum may be cancelled in an emergency. type CancellationOrigin: EnsureOrigin; /// Origin for anyone able to veto proposals. type VetoOrigin: EnsureOrigin; /// Period in blocks where an external proposal may not be re-submitted after being vetoed. type CooloffPeriod: Get; } /// Info regarding an ongoing referendum. #[derive(Encode, Decode, Clone, PartialEq, Eq)] #[cfg_attr(feature = "std", derive(Debug))] pub struct ReferendumInfo { /// When voting on this referendum will end. end: BlockNumber, /// The proposal being voted on. proposal: Proposal, /// The thresholding mechanism to determine whether it passed. threshold: VoteThreshold, /// The delay (in blocks) to wait after a successful referendum before deploying. delay: BlockNumber, } impl ReferendumInfo { /// Create a new instance. pub fn new( end: BlockNumber, proposal: Proposal, threshold: VoteThreshold, delay: BlockNumber ) -> Self { ReferendumInfo { end, proposal, threshold, delay } } } decl_storage! { trait Store for Module as Democracy { /// The number of (public) proposals that have been made so far. pub PublicPropCount get(public_prop_count) build(|_| 0 as PropIndex) : PropIndex; /// The public proposals. Unsorted. pub PublicProps get(public_props): Vec<(PropIndex, T::Proposal, T::AccountId)>; /// Those who have locked a deposit. pub DepositOf get(deposit_of): map PropIndex => Option<(BalanceOf, Vec)>; /// The next free referendum index, aka the number of referenda started so far. pub ReferendumCount get(referendum_count) build(|_| 0 as ReferendumIndex): ReferendumIndex; /// The next referendum index that should be tallied. pub NextTally get(next_tally) build(|_| 0 as ReferendumIndex): ReferendumIndex; /// Information concerning any given referendum. pub ReferendumInfoOf get(referendum_info): map ReferendumIndex => Option<(ReferendumInfo)>; /// Queue of successful referenda to be dispatched. pub DispatchQueue get(dispatch_queue): map T::BlockNumber => Vec>; /// Get the voters for the current proposal. pub VotersFor get(voters_for): map ReferendumIndex => Vec; /// Get the vote in a given referendum of a particular voter. The result is meaningful only /// if `voters_for` includes the voter when called with the referendum (you'll get the /// default `Vote` value otherwise). If you don't want to check `voters_for`, then you can /// also check for simple existence with `VoteOf::exists` first. pub VoteOf get(vote_of): map (ReferendumIndex, T::AccountId) => Vote; /// Who is able to vote for whom. Value is the fund-holding account, key is the /// vote-transaction-sending account. pub Proxy get(proxy): map T::AccountId => Option; /// Get the account (and lock periods) to which another account is delegating vote. pub Delegations get(delegations): linked_map T::AccountId => (T::AccountId, Conviction); /// True if the last referendum tabled was submitted externally. False if it was a public /// proposal. pub LastTabledWasExternal: bool; /// The referendum to be tabled whenever it would be valid to table an external proposal. /// This happens when a referendum needs to be tabled and one of two conditions are met: /// - `LastTabledWasExternal` is `false`; or /// - `PublicProps` is empty. pub NextExternal: Option<(T::Proposal, VoteThreshold)>; /// A record of who vetoed what. Maps proposal hash to a possible existent block number /// (until when it may not be resubmitted) and who vetoed it. pub Blacklist get(blacklist): map T::Hash => Option<(T::BlockNumber, Vec)>; /// Record of all proposals that have been subject to emergency cancellation. pub Cancellations: map T::Hash => bool; } } decl_event!( pub enum Event where Balance = BalanceOf, ::AccountId, ::Hash, ::BlockNumber, { Proposed(PropIndex, Balance), Tabled(PropIndex, Balance, Vec), ExternalTabled, Started(ReferendumIndex, VoteThreshold), Passed(ReferendumIndex), NotPassed(ReferendumIndex), Cancelled(ReferendumIndex), Executed(ReferendumIndex, bool), Delegated(AccountId, AccountId), Undelegated(AccountId), Vetoed(AccountId, Hash, BlockNumber), } ); decl_module! { pub struct Module for enum Call where origin: T::Origin { /// The minimum period of locking and the period between a proposal being approved and enacted. /// /// It should generally be a little more than the unstake period to ensure that /// voting stakers have an opportunity to remove themselves from the system in the case where /// they are on the losing side of a vote. const EnactmentPeriod: T::BlockNumber = T::EnactmentPeriod::get(); /// How often (in blocks) new public referenda are launched. const LaunchPeriod: T::BlockNumber = T::LaunchPeriod::get(); /// How often (in blocks) to check for new votes. const VotingPeriod: T::BlockNumber = T::VotingPeriod::get(); /// The minimum amount to be used as a deposit for a public referendum proposal. const MinimumDeposit: BalanceOf = T::MinimumDeposit::get(); /// Minimum voting period allowed for an emergency referendum. const EmergencyVotingPeriod: T::BlockNumber = T::EmergencyVotingPeriod::get(); /// Period in blocks where an external proposal may not be re-submitted after being vetoed. const CooloffPeriod: T::BlockNumber = T::CooloffPeriod::get(); fn deposit_event() = default; /// Propose a sensitive action to be taken. /// /// # /// - O(1). /// - Two DB changes, one DB entry. /// # #[weight = SimpleDispatchInfo::FixedNormal(5_000_000)] fn propose(origin, proposal: Box, #[compact] value: BalanceOf ) { let who = ensure_signed(origin)?; ensure!(value >= T::MinimumDeposit::get(), "value too low"); T::Currency::reserve(&who, value) .map_err(|_| "proposer's balance too low")?; let index = Self::public_prop_count(); PublicPropCount::put(index + 1); >::insert(index, (value, &[&who][..])); let new_prop = (index, proposal, who); >::append_or_put(&[Ref::from(&new_prop)][..]); Self::deposit_event(RawEvent::Proposed(index, value)); } /// Propose a sensitive action to be taken. /// /// # /// - O(1). /// - One DB entry. /// # #[weight = SimpleDispatchInfo::FixedNormal(5_000_000)] fn second(origin, #[compact] proposal: PropIndex) { let who = ensure_signed(origin)?; let mut deposit = Self::deposit_of(proposal) .ok_or("can only second an existing proposal")?; T::Currency::reserve(&who, deposit.0) .map_err(|_| "seconder's balance too low")?; deposit.1.push(who); >::insert(proposal, deposit); } /// Vote in a referendum. If `vote.is_aye()`, the vote is to enact the proposal; /// otherwise it is a vote to keep the status quo. /// /// # /// - O(1). /// - One DB change, one DB entry. /// # #[weight = SimpleDispatchInfo::FixedNormal(200_000)] fn vote(origin, #[compact] ref_index: ReferendumIndex, vote: Vote ) -> Result { let who = ensure_signed(origin)?; Self::do_vote(who, ref_index, vote) } /// Vote in a referendum on behalf of a stash. If `vote.is_aye()`, the vote is to enact /// the proposal; otherwise it is a vote to keep the status quo. /// /// # /// - O(1). /// - One DB change, one DB entry. /// # #[weight = SimpleDispatchInfo::FixedNormal(200_000)] fn proxy_vote(origin, #[compact] ref_index: ReferendumIndex, vote: Vote ) -> Result { let who = Self::proxy(ensure_signed(origin)?).ok_or("not a proxy")?; Self::do_vote(who, ref_index, vote) } /// Schedule an emergency cancellation of a referendum. Cannot happen twice to the same /// referendum. #[weight = SimpleDispatchInfo::FixedOperational(500_000)] fn emergency_cancel(origin, ref_index: ReferendumIndex) { T::CancellationOrigin::ensure_origin(origin)?; let info = Self::referendum_info(ref_index).ok_or("unknown index")?; let h = T::Hashing::hash_of(&info.proposal); ensure!(!>::exists(h), "cannot cancel the same proposal twice"); >::insert(h, true); Self::clear_referendum(ref_index); } /// Schedule a referendum to be tabled once it is legal to schedule an external /// referendum. #[weight = SimpleDispatchInfo::FixedNormal(5_000_000)] fn external_propose(origin, proposal: Box) { T::ExternalOrigin::ensure_origin(origin)?; ensure!(!>::exists(), "proposal already made"); let proposal_hash = T::Hashing::hash_of(&proposal); if let Some((until, _)) = >::get(proposal_hash) { ensure!(>::block_number() >= until, "proposal still blacklisted"); } >::put((*proposal, VoteThreshold::SuperMajorityApprove)); } /// Schedule a majority-carries referendum to be tabled next once it is legal to schedule /// an external referendum. /// /// Unlike `external_propose`, blacklisting has no effect on this and it may replace a /// pre-scheduled `external_propose` call. #[weight = SimpleDispatchInfo::FixedNormal(5_000_000)] fn external_propose_majority(origin, proposal: Box) { T::ExternalMajorityOrigin::ensure_origin(origin)?; >::put((*proposal, VoteThreshold::SimpleMajority)); } /// Schedule a negative-turnout-bias referendum to be tabled next once it is legal to /// schedule an external referendum. /// /// Unlike `external_propose`, blacklisting has no effect on this and it may replace a /// pre-scheduled `external_propose` call. #[weight = SimpleDispatchInfo::FixedNormal(5_000_000)] fn external_propose_default(origin, proposal: Box) { T::ExternalDefaultOrigin::ensure_origin(origin)?; >::put((*proposal, VoteThreshold::SuperMajorityAgainst)); } /// Schedule the currently externally-proposed majority-carries referendum to be tabled /// immediately. If there is no externally-proposed referendum currently, or if there is one /// but it is not a majority-carries referendum then it fails. /// /// - `proposal_hash`: The hash of the current external proposal. /// - `voting_period`: The period that is allowed for voting on this proposal. /// - `delay`: The number of block after voting has ended in approval and this should be /// enacted. Increased to `EmergencyVotingPeriod` if too low. #[weight = SimpleDispatchInfo::FixedNormal(200_000)] fn fast_track(origin, proposal_hash: T::Hash, voting_period: T::BlockNumber, delay: T::BlockNumber ) { T::FastTrackOrigin::ensure_origin(origin)?; let (proposal, threshold) = >::get().ok_or("no proposal made")?; ensure!(threshold != VoteThreshold::SuperMajorityApprove, "next external proposal not simple majority"); ensure!(proposal_hash == T::Hashing::hash_of(&proposal), "invalid hash"); >::kill(); let now = >::block_number(); // We don't consider it an error if `vote_period` is too low, like `emergency_propose`. let period = voting_period.max(T::EmergencyVotingPeriod::get()); Self::inject_referendum(now + period, proposal, threshold, delay).map(|_| ())?; } /// Veto and blacklist the external proposal hash. #[weight = SimpleDispatchInfo::FixedNormal(200_000)] fn veto_external(origin, proposal_hash: T::Hash) { let who = T::VetoOrigin::ensure_origin(origin)?; if let Some((proposal, _)) = >::get() { ensure!(proposal_hash == T::Hashing::hash_of(&proposal), "unknown proposal"); } else { Err("no external proposal")?; } let mut existing_vetoers = >::get(&proposal_hash) .map(|pair| pair.1) .unwrap_or_else(Vec::new); let insert_position = existing_vetoers.binary_search(&who) .err().ok_or("identity may not veto a proposal twice")?; existing_vetoers.insert(insert_position, who.clone()); let until = >::block_number() + T::CooloffPeriod::get(); >::insert(&proposal_hash, (until, existing_vetoers)); Self::deposit_event(RawEvent::Vetoed(who, proposal_hash, until)); >::kill(); } /// Remove a referendum. #[weight = SimpleDispatchInfo::FixedOperational(10_000)] fn cancel_referendum(origin, #[compact] ref_index: ReferendumIndex) { ensure_root(origin)?; Self::clear_referendum(ref_index); } /// Cancel a proposal queued for enactment. #[weight = SimpleDispatchInfo::FixedOperational(10_000)] fn cancel_queued( origin, #[compact] when: T::BlockNumber, #[compact] which: u32, #[compact] what: ReferendumIndex ) { ensure_root(origin)?; let which = which as usize; let mut items = >::get(when); if items.get(which).and_then(Option::as_ref).map_or(false, |x| x.1 == what) { items[which] = None; >::insert(when, items); } else { Err("proposal not found")? } } fn on_initialize(n: T::BlockNumber) { if let Err(e) = Self::end_block(n) { sr_primitives::print(e); } } /// Specify a proxy. Called by the stash. /// /// # /// - One extra DB entry. /// # #[weight = SimpleDispatchInfo::FixedNormal(100_000)] fn set_proxy(origin, proxy: T::AccountId) { let who = ensure_signed(origin)?; ensure!(!>::exists(&proxy), "already a proxy"); >::insert(proxy, who) } /// Clear the proxy. Called by the proxy. /// /// # /// - One DB clear. /// # #[weight = SimpleDispatchInfo::FixedNormal(100_000)] fn resign_proxy(origin) { let who = ensure_signed(origin)?; >::remove(who); } /// Clear the proxy. Called by the stash. /// /// # /// - One DB clear. /// # #[weight = SimpleDispatchInfo::FixedNormal(100_000)] fn remove_proxy(origin, proxy: T::AccountId) { let who = ensure_signed(origin)?; ensure!(&Self::proxy(&proxy).ok_or("not a proxy")? == &who, "wrong proxy"); >::remove(proxy); } /// Delegate vote. /// /// # /// - One extra DB entry. /// # #[weight = SimpleDispatchInfo::FixedNormal(500_000)] pub fn delegate(origin, to: T::AccountId, conviction: Conviction) { let who = ensure_signed(origin)?; >::insert(&who, (&to, conviction)); // Currency is locked indefinitely as long as it's delegated. T::Currency::extend_lock( DEMOCRACY_ID, &who, Bounded::max_value(), T::BlockNumber::max_value(), WithdrawReason::Transfer.into() ); Self::deposit_event(RawEvent::Delegated(who, to)); } /// Undelegate vote. /// /// # /// - O(1). /// # #[weight = SimpleDispatchInfo::FixedNormal(500_000)] fn undelegate(origin) { let who = ensure_signed(origin)?; ensure!(>::exists(&who), "not delegated"); let (_, conviction) = >::take(&who); // Indefinite lock is reduced to the maximum voting lock that could be possible. let now = >::block_number(); let locked_until = now + T::EnactmentPeriod::get() * conviction.lock_periods().into(); T::Currency::set_lock( DEMOCRACY_ID, &who, Bounded::max_value(), locked_until, WithdrawReason::Transfer.into() ); Self::deposit_event(RawEvent::Undelegated(who)); } } } impl Module { // exposed immutables. /// Get the amount locked in support of `proposal`; `None` if proposal isn't a valid proposal /// index. pub fn locked_for(proposal: PropIndex) -> Option> { Self::deposit_of(proposal).map(|(d, l)| d * (l.len() as u32).into()) } /// Return true if `ref_index` is an on-going referendum. pub fn is_active_referendum(ref_index: ReferendumIndex) -> bool { >::exists(ref_index) } /// Get all referenda currently active. pub fn active_referenda() -> Vec<(ReferendumIndex, ReferendumInfo)> { let next = Self::next_tally(); let last = Self::referendum_count(); (next..last).into_iter() .filter_map(|i| Self::referendum_info(i).map(|info| (i, info))) .collect() } /// Get all referenda ready for tally at block `n`. pub fn maturing_referenda_at( n: T::BlockNumber ) -> Vec<(ReferendumIndex, ReferendumInfo)> { let next = Self::next_tally(); let last = Self::referendum_count(); (next..last).into_iter() .filter_map(|i| Self::referendum_info(i).map(|info| (i, info))) .take_while(|&(_, ref info)| info.end == n) .collect() } /// Get the voters for the current proposal. pub fn tally(ref_index: ReferendumIndex) -> (BalanceOf, BalanceOf, BalanceOf) { let (approve, against, capital): (BalanceOf, BalanceOf, BalanceOf) = Self::voters_for(ref_index) .iter() .map(|voter| ( T::Currency::total_balance(voter), Self::vote_of((ref_index, voter.clone())) )) .map(|(balance, Vote { aye, conviction })| { let (votes, turnout) = conviction.votes(balance); if aye { (votes, Zero::zero(), turnout) } else { (Zero::zero(), votes, turnout) } }).fold( (Zero::zero(), Zero::zero(), Zero::zero()), |(a, b, c), (d, e, f)| (a + d, b + e, c + f) ); let (del_approve, del_against, del_capital) = Self::tally_delegation(ref_index); (approve + del_approve, against + del_against, capital + del_capital) } /// Get the delegated voters for the current proposal. /// I think this goes into a worker once https://github.com/paritytech/substrate/issues/1458 is /// done. fn tally_delegation(ref_index: ReferendumIndex) -> (BalanceOf, BalanceOf, BalanceOf) { Self::voters_for(ref_index).iter().fold( (Zero::zero(), Zero::zero(), Zero::zero()), |(approve_acc, against_acc, turnout_acc), voter| { let Vote { aye, conviction } = Self::vote_of((ref_index, voter.clone())); let (votes, turnout) = Self::delegated_votes( ref_index, voter.clone(), conviction, MAX_RECURSION_LIMIT ); if aye { (approve_acc + votes, against_acc, turnout_acc + turnout) } else { (approve_acc, against_acc + votes, turnout_acc + turnout) } } ) } fn delegated_votes( ref_index: ReferendumIndex, to: T::AccountId, parent_conviction: Conviction, recursion_limit: u32, ) -> (BalanceOf, BalanceOf) { if recursion_limit == 0 { return (Zero::zero(), Zero::zero()); } >::enumerate() .filter(|(delegator, (delegate, _))| *delegate == to && !>::exists(&(ref_index, delegator.clone())) ).fold( (Zero::zero(), Zero::zero()), |(votes_acc, turnout_acc), (delegator, (_delegate, max_conviction))| { let conviction = Conviction::min(parent_conviction, max_conviction); let balance = T::Currency::total_balance(&delegator); let (votes, turnout) = conviction.votes(balance); let (del_votes, del_turnout) = Self::delegated_votes( ref_index, delegator, conviction, recursion_limit - 1 ); (votes_acc + votes + del_votes, turnout_acc + turnout + del_turnout) } ) } // Exposed mutables. #[cfg(feature = "std")] pub fn force_proxy(stash: T::AccountId, proxy: T::AccountId) { >::insert(proxy, stash) } /// Start a referendum. pub fn internal_start_referendum( proposal: T::Proposal, threshold: VoteThreshold, delay: T::BlockNumber ) -> result::Result { >::inject_referendum( >::block_number() + T::VotingPeriod::get(), proposal, threshold, delay ) } /// Remove a referendum. pub fn internal_cancel_referendum(ref_index: ReferendumIndex) { Self::deposit_event(RawEvent::Cancelled(ref_index)); >::clear_referendum(ref_index); } // private. /// Actually enact a vote, if legit. fn do_vote(who: T::AccountId, ref_index: ReferendumIndex, vote: Vote) -> Result { ensure!(Self::is_active_referendum(ref_index), "vote given for invalid referendum."); if !>::exists((ref_index, &who)) { >::append_or_insert(ref_index, &[&who][..]); } >::insert((ref_index, &who), vote); Ok(()) } /// Start a referendum fn inject_referendum( end: T::BlockNumber, proposal: T::Proposal, threshold: VoteThreshold, delay: T::BlockNumber, ) -> result::Result { let ref_index = Self::referendum_count(); if ref_index.checked_sub(1) .and_then(Self::referendum_info) .map(|i| i.end > end) .unwrap_or(false) { Err("Cannot inject a referendum that ends earlier than preceeding referendum")? } ReferendumCount::put(ref_index + 1); let item = ReferendumInfo { end, proposal, threshold, delay }; >::insert(ref_index, item); Self::deposit_event(RawEvent::Started(ref_index, threshold)); Ok(ref_index) } /// Remove all info on a referendum. fn clear_referendum(ref_index: ReferendumIndex) { >::remove(ref_index); >::remove(ref_index); for v in Self::voters_for(ref_index) { >::remove((ref_index, v)); } } /// Enact a proposal from a referendum. fn enact_proposal(proposal: T::Proposal, index: ReferendumIndex) { let ok = proposal.dispatch(system::RawOrigin::Root.into()).is_ok(); Self::deposit_event(RawEvent::Executed(index, ok)); } /// Table the next waiting proposal for a vote. fn launch_next(now: T::BlockNumber) -> Result { if LastTabledWasExternal::take() { Self::launch_public(now).or_else(|_| Self::launch_external(now)) } else { Self::launch_external(now).or_else(|_| Self::launch_public(now)) }.map_err(|_| "No proposals waiting") } /// Table the waiting external proposal for a vote, if there is one. fn launch_external(now: T::BlockNumber) -> Result { if let Some((proposal, threshold)) = >::take() { LastTabledWasExternal::put(true); Self::deposit_event(RawEvent::ExternalTabled); Self::inject_referendum( now + T::VotingPeriod::get(), proposal, threshold, T::EnactmentPeriod::get(), )?; Ok(()) } else { Err("No external proposal waiting") } } /// Table the waiting public proposal with the highest backing for a vote. fn launch_public(now: T::BlockNumber) -> Result { let mut public_props = Self::public_props(); if let Some((winner_index, _)) = public_props.iter() .enumerate() .max_by_key(|x| Self::locked_for((x.1).0).unwrap_or_else(Zero::zero) /* ^^ defensive only: All current public proposals have an amount locked*/) { let (prop_index, proposal, _) = public_props.swap_remove(winner_index); >::put(public_props); if let Some((deposit, depositors)) = >::take(prop_index) { // refund depositors for d in &depositors { T::Currency::unreserve(d, deposit); } Self::deposit_event(RawEvent::Tabled(prop_index, deposit, depositors)); Self::inject_referendum( now + T::VotingPeriod::get(), proposal, VoteThreshold::SuperMajorityApprove, T::EnactmentPeriod::get(), )?; } Ok(()) } else { Err("No public proposals waiting") } } fn bake_referendum( now: T::BlockNumber, index: ReferendumIndex, info: ReferendumInfo ) -> Result { let (approve, against, capital) = Self::tally(index); let total_issuance = T::Currency::total_issuance(); let approved = info.threshold.approved(approve, against, capital, total_issuance); // Logic defined in https://www.slideshare.net/gavofyork/governance-in-polkadot-poc3 // Essentially, we extend the lock-period of the coins behind the winning votes to be the // vote strength times the public delay period from now. for (a, Vote { conviction, .. }) in Self::voters_for(index).into_iter() .map(|a| (a.clone(), Self::vote_of((index, a)))) // ^^^ defensive only: all items come from `voters`; for an item to be in `voters` // there must be a vote registered; qed .filter(|&(_, vote)| vote.aye == approved) // Just the winning coins { // now plus: the base lock period multiplied by the number of periods this voter // offered to lock should they win... let locked_until = now + T::EnactmentPeriod::get() * conviction.lock_periods().into(); // ...extend their bondage until at least then. T::Currency::extend_lock( DEMOCRACY_ID, &a, Bounded::max_value(), locked_until, WithdrawReason::Transfer.into() ); } Self::clear_referendum(index); if approved { Self::deposit_event(RawEvent::Passed(index)); if info.delay.is_zero() { Self::enact_proposal(info.proposal, index); } else { >::append_or_insert( now + info.delay, &[Some((info.proposal, index))][..] ); } } else { Self::deposit_event(RawEvent::NotPassed(index)); } NextTally::put(index + 1); Ok(()) } /// Current era is ending; we should finish up any proposals. // TODO: move to initialize_block #2779 fn end_block(now: T::BlockNumber) -> Result { // pick out another public referendum if it's time. if (now % T::LaunchPeriod::get()).is_zero() { // Errors come from the queue being empty. we don't really care about that, and even if // we did, there is nothing we can do here. let _ = Self::launch_next(now); } // tally up votes for any expiring referenda. for (index, info) in Self::maturing_referenda_at(now).into_iter() { Self::bake_referendum(now, index, info)?; } for (proposal, index) in >::take(now).into_iter().filter_map(|x| x) { Self::enact_proposal(proposal, index); } Ok(()) } } impl OnFreeBalanceZero for Module { fn on_free_balance_zero(who: &T::AccountId) { >::remove(who) } } #[cfg(test)] mod tests { use super::*; use support::{ impl_outer_origin, impl_outer_dispatch, assert_noop, assert_ok, parameter_types, traits::Contains }; use primitives::H256; use sr_primitives::{traits::{BlakeTwo256, IdentityLookup, Bounded}, testing::Header, Perbill}; use balances::BalanceLock; use system::EnsureSignedBy; const AYE: Vote = Vote{ aye: true, conviction: Conviction::None }; const NAY: Vote = Vote{ aye: false, conviction: Conviction::None }; const BIG_AYE: Vote = Vote{ aye: true, conviction: Conviction::Locked1x }; const BIG_NAY: Vote = Vote{ aye: false, conviction: Conviction::Locked1x }; impl_outer_origin! { pub enum Origin for Test {} } impl_outer_dispatch! { pub enum Call for Test where origin: Origin { balances::Balances, democracy::Democracy, } } // Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted. #[derive(Clone, Eq, PartialEq, Debug)] pub struct Test; parameter_types! { pub const BlockHashCount: u64 = 250; pub const MaximumBlockWeight: u32 = 1024; pub const MaximumBlockLength: u32 = 2 * 1024; pub const AvailableBlockRatio: Perbill = Perbill::one(); } impl system::Trait for Test { type Origin = Origin; type Index = u64; type BlockNumber = u64; type Call = (); type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; type Header = Header; type WeightMultiplierUpdate = (); type Event = (); type BlockHashCount = BlockHashCount; type MaximumBlockWeight = MaximumBlockWeight; type MaximumBlockLength = MaximumBlockLength; type AvailableBlockRatio = AvailableBlockRatio; type Version = (); } parameter_types! { pub const ExistentialDeposit: u64 = 0; pub const TransferFee: u64 = 0; pub const CreationFee: u64 = 0; pub const TransactionBaseFee: u64 = 0; pub const TransactionByteFee: u64 = 0; } impl balances::Trait for Test { type Balance = u64; type OnFreeBalanceZero = (); type OnNewAccount = (); type Event = (); type TransactionPayment = (); type TransferPayment = (); type DustRemoval = (); type ExistentialDeposit = ExistentialDeposit; type TransferFee = TransferFee; type CreationFee = CreationFee; type TransactionBaseFee = TransactionBaseFee; type TransactionByteFee = TransactionByteFee; type WeightToFee = (); } parameter_types! { pub const LaunchPeriod: u64 = 2; pub const VotingPeriod: u64 = 2; pub const EmergencyVotingPeriod: u64 = 1; pub const MinimumDeposit: u64 = 1; pub const EnactmentPeriod: u64 = 2; pub const CooloffPeriod: u64 = 2; pub const One: u64 = 1; pub const Two: u64 = 2; pub const Three: u64 = 3; pub const Four: u64 = 4; pub const Five: u64 = 5; } pub struct OneToFive; impl Contains for OneToFive { fn contains(n: &u64) -> bool { *n >= 1 && *n <= 5 } } impl super::Trait for Test { type Proposal = Call; type Event = (); type Currency = balances::Module; type EnactmentPeriod = EnactmentPeriod; type LaunchPeriod = LaunchPeriod; type VotingPeriod = VotingPeriod; type EmergencyVotingPeriod = EmergencyVotingPeriod; type MinimumDeposit = MinimumDeposit; type ExternalOrigin = EnsureSignedBy; type ExternalMajorityOrigin = EnsureSignedBy; type ExternalDefaultOrigin = EnsureSignedBy; type FastTrackOrigin = EnsureSignedBy; type CancellationOrigin = EnsureSignedBy; type VetoOrigin = EnsureSignedBy; type CooloffPeriod = CooloffPeriod; } fn new_test_ext() -> runtime_io::TestExternalities { let mut t = system::GenesisConfig::default().build_storage::().unwrap(); balances::GenesisConfig::{ balances: vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)], vesting: vec![], }.assimilate_storage(&mut t).unwrap(); GenesisConfig::default().assimilate_storage(&mut t).unwrap(); runtime_io::TestExternalities::new(t) } type System = system::Module; type Balances = balances::Module; type Democracy = Module; #[test] fn params_should_work() { new_test_ext().execute_with(|| { assert_eq!(Democracy::referendum_count(), 0); assert_eq!(Balances::free_balance(&42), 0); assert_eq!(Balances::total_issuance(), 210); }); } fn set_balance_proposal(value: u64) -> Call { Call::Balances(balances::Call::set_balance(42, value, 0)) } fn propose_set_balance(who: u64, value: u64, delay: u64) -> super::Result { Democracy::propose( Origin::signed(who), Box::new(set_balance_proposal(value)), delay ) } fn next_block() { assert_eq!(Democracy::end_block(System::block_number()), Ok(())); System::set_block_number(System::block_number() + 1); } fn fast_forward_to(n: u64) { while System::block_number() < n { next_block(); } } #[test] fn external_and_public_interleaving_works() { new_test_ext().execute_with(|| { System::set_block_number(0); assert_ok!(Democracy::external_propose( Origin::signed(2), Box::new(set_balance_proposal(1)), )); assert_ok!(propose_set_balance(6, 2, 2)); fast_forward_to(1); // both waiting: external goes first. assert_eq!( Democracy::referendum_info(0), Some(ReferendumInfo { end: 2, proposal: set_balance_proposal(1), threshold: VoteThreshold::SuperMajorityApprove, delay: 2 }) ); // replenish external assert_ok!(Democracy::external_propose( Origin::signed(2), Box::new(set_balance_proposal(3)), )); fast_forward_to(3); // both waiting: public goes next. assert_eq!( Democracy::referendum_info(1), Some(ReferendumInfo { end: 4, proposal: set_balance_proposal(2), threshold: VoteThreshold::SuperMajorityApprove, delay: 2 }) ); // don't replenish public fast_forward_to(5); // it's external "turn" again, though since public is empty that doesn't really matter assert_eq!( Democracy::referendum_info(2), Some(ReferendumInfo { end: 6, proposal: set_balance_proposal(3), threshold: VoteThreshold::SuperMajorityApprove, delay: 2 }) ); // replenish external assert_ok!(Democracy::external_propose( Origin::signed(2), Box::new(set_balance_proposal(5)), )); fast_forward_to(7); // external goes again because there's no public waiting. assert_eq!( Democracy::referendum_info(3), Some(ReferendumInfo { end: 8, proposal: set_balance_proposal(5), threshold: VoteThreshold::SuperMajorityApprove, delay: 2 }) ); // replenish both assert_ok!(Democracy::external_propose( Origin::signed(2), Box::new(set_balance_proposal(7)), )); assert_ok!(propose_set_balance(6, 4, 2)); fast_forward_to(9); // public goes now since external went last time. assert_eq!( Democracy::referendum_info(4), Some(ReferendumInfo { end: 10, proposal: set_balance_proposal(4), threshold: VoteThreshold::SuperMajorityApprove, delay: 2 }) ); // replenish public again assert_ok!(propose_set_balance(6, 6, 2)); // cancel external let h = BlakeTwo256::hash_of(&set_balance_proposal(7)); assert_ok!(Democracy::veto_external(Origin::signed(3), h)); fast_forward_to(11); // public goes again now since there's no external waiting. assert_eq!( Democracy::referendum_info(5), Some(ReferendumInfo { end: 12, proposal: set_balance_proposal(6), threshold: VoteThreshold::SuperMajorityApprove, delay: 2 }) ); }); } #[test] fn emergency_cancel_should_work() { new_test_ext().execute_with(|| { System::set_block_number(0); let r = Democracy::inject_referendum( 2, set_balance_proposal(2), VoteThreshold::SuperMajorityApprove, 2 ).unwrap(); assert!(Democracy::referendum_info(r).is_some()); assert_noop!(Democracy::emergency_cancel(Origin::signed(3), r), "Invalid origin"); assert_ok!(Democracy::emergency_cancel(Origin::signed(4), r)); assert!(Democracy::referendum_info(r).is_none()); // some time later... let r = Democracy::inject_referendum( 2, set_balance_proposal(2), VoteThreshold::SuperMajorityApprove, 2 ).unwrap(); assert!(Democracy::referendum_info(r).is_some()); assert_noop!(Democracy::emergency_cancel(Origin::signed(4), r), "cannot cancel the same proposal twice"); }); } #[test] fn veto_external_works() { new_test_ext().execute_with(|| { System::set_block_number(0); assert_ok!(Democracy::external_propose( Origin::signed(2), Box::new(set_balance_proposal(2)), )); assert!(>::exists()); let h = BlakeTwo256::hash_of(&set_balance_proposal(2)); assert_ok!(Democracy::veto_external(Origin::signed(3), h.clone())); // cancelled. assert!(!>::exists()); // fails - same proposal can't be resubmitted. assert_noop!(Democracy::external_propose( Origin::signed(2), Box::new(set_balance_proposal(2)), ), "proposal still blacklisted"); fast_forward_to(1); // fails as we're still in cooloff period. assert_noop!(Democracy::external_propose( Origin::signed(2), Box::new(set_balance_proposal(2)), ), "proposal still blacklisted"); fast_forward_to(2); // works; as we're out of the cooloff period. assert_ok!(Democracy::external_propose( Origin::signed(2), Box::new(set_balance_proposal(2)), )); assert!(>::exists()); // 3 can't veto the same thing twice. assert_noop!( Democracy::veto_external(Origin::signed(3), h.clone()), "identity may not veto a proposal twice" ); // 4 vetoes. assert_ok!(Democracy::veto_external(Origin::signed(4), h.clone())); // cancelled again. assert!(!>::exists()); fast_forward_to(3); // same proposal fails as we're still in cooloff assert_noop!(Democracy::external_propose( Origin::signed(2), Box::new(set_balance_proposal(2)), ), "proposal still blacklisted"); // different proposal works fine. assert_ok!(Democracy::external_propose( Origin::signed(2), Box::new(set_balance_proposal(3)), )); }); } #[test] fn external_referendum_works() { new_test_ext().execute_with(|| { System::set_block_number(0); assert_noop!(Democracy::external_propose( Origin::signed(1), Box::new(set_balance_proposal(2)), ), "Invalid origin"); assert_ok!(Democracy::external_propose( Origin::signed(2), Box::new(set_balance_proposal(2)), )); assert_noop!(Democracy::external_propose( Origin::signed(2), Box::new(set_balance_proposal(1)), ), "proposal already made"); fast_forward_to(1); assert_eq!( Democracy::referendum_info(0), Some(ReferendumInfo { end: 2, proposal: set_balance_proposal(2), threshold: VoteThreshold::SuperMajorityApprove, delay: 2 }) ); }); } #[test] fn external_majority_referendum_works() { new_test_ext().execute_with(|| { System::set_block_number(0); assert_noop!(Democracy::external_propose_majority( Origin::signed(1), Box::new(set_balance_proposal(2)) ), "Invalid origin"); assert_ok!(Democracy::external_propose_majority( Origin::signed(3), Box::new(set_balance_proposal(2)) )); fast_forward_to(1); assert_eq!( Democracy::referendum_info(0), Some(ReferendumInfo { end: 2, proposal: set_balance_proposal(2), threshold: VoteThreshold::SimpleMajority, delay: 2, }) ); }); } #[test] fn external_default_referendum_works() { new_test_ext().execute_with(|| { System::set_block_number(0); assert_noop!(Democracy::external_propose_default( Origin::signed(3), Box::new(set_balance_proposal(2)) ), "Invalid origin"); assert_ok!(Democracy::external_propose_default( Origin::signed(1), Box::new(set_balance_proposal(2)) )); fast_forward_to(1); assert_eq!( Democracy::referendum_info(0), Some(ReferendumInfo { end: 2, proposal: set_balance_proposal(2), threshold: VoteThreshold::SuperMajorityAgainst, delay: 2, }) ); }); } #[test] fn fast_track_referendum_works() { new_test_ext().execute_with(|| { System::set_block_number(0); let h = BlakeTwo256::hash_of(&set_balance_proposal(2)); assert_noop!(Democracy::fast_track(Origin::signed(5), h, 3, 2), "no proposal made"); assert_ok!(Democracy::external_propose_majority( Origin::signed(3), Box::new(set_balance_proposal(2)) )); assert_noop!(Democracy::fast_track(Origin::signed(1), h, 3, 2), "Invalid origin"); assert_ok!(Democracy::fast_track(Origin::signed(5), h, 0, 0)); assert_eq!( Democracy::referendum_info(0), Some(ReferendumInfo { end: 1, proposal: set_balance_proposal(2), threshold: VoteThreshold::SimpleMajority, delay: 0, }) ); }); } #[test] fn fast_track_referendum_fails_when_no_simple_majority() { new_test_ext().execute_with(|| { System::set_block_number(0); let h = BlakeTwo256::hash_of(&set_balance_proposal(2)); assert_ok!(Democracy::external_propose( Origin::signed(2), Box::new(set_balance_proposal(2)) )); assert_noop!( Democracy::fast_track(Origin::signed(5), h, 3, 2), "next external proposal not simple majority" ); }); } #[test] fn locked_for_should_work() { new_test_ext().execute_with(|| { System::set_block_number(1); assert_ok!(propose_set_balance(1, 2, 2)); assert_ok!(propose_set_balance(1, 4, 4)); assert_ok!(propose_set_balance(1, 3, 3)); assert_eq!(Democracy::locked_for(0), Some(2)); assert_eq!(Democracy::locked_for(1), Some(4)); assert_eq!(Democracy::locked_for(2), Some(3)); }); } #[test] fn single_proposal_should_work() { new_test_ext().execute_with(|| { System::set_block_number(0); assert_ok!(propose_set_balance(1, 2, 1)); assert!(Democracy::referendum_info(0).is_none()); // end of 0 => next referendum scheduled. fast_forward_to(1); let r = 0; assert_ok!(Democracy::vote(Origin::signed(1), r, AYE)); assert_eq!(Democracy::referendum_count(), 1); assert_eq!( Democracy::referendum_info(0), Some(ReferendumInfo { end: 2, proposal: set_balance_proposal(2), threshold: VoteThreshold::SuperMajorityApprove, delay: 2 }) ); assert_eq!(Democracy::voters_for(r), vec![1]); assert_eq!(Democracy::vote_of((r, 1)), AYE); assert_eq!(Democracy::tally(r), (1, 0, 1)); fast_forward_to(2); // referendum still running assert!(Democracy::referendum_info(0).is_some()); // referendum runs during 1 and 2, ends @ end of 2. fast_forward_to(3); assert!(Democracy::referendum_info(0).is_none()); assert_eq!(Democracy::dispatch_queue(4), vec![ Some((set_balance_proposal(2), 0)) ]); // referendum passes and wait another two blocks for enactment. fast_forward_to(5); assert_eq!(Balances::free_balance(&42), 2); }); } #[test] fn cancel_queued_should_work() { new_test_ext().execute_with(|| { System::set_block_number(0); assert_ok!(propose_set_balance(1, 2, 1)); // end of 0 => next referendum scheduled. fast_forward_to(1); assert_ok!(Democracy::vote(Origin::signed(1), 0, AYE)); fast_forward_to(3); assert_eq!(Democracy::dispatch_queue(4), vec![ Some((set_balance_proposal(2), 0)) ]); assert_noop!(Democracy::cancel_queued(Origin::ROOT, 3, 0, 0), "proposal not found"); assert_noop!(Democracy::cancel_queued(Origin::ROOT, 4, 1, 0), "proposal not found"); assert_ok!(Democracy::cancel_queued(Origin::ROOT, 4, 0, 0)); assert_eq!(Democracy::dispatch_queue(4), vec![None]); }); } #[test] fn proxy_should_work() { new_test_ext().execute_with(|| { assert_eq!(Democracy::proxy(10), None); assert_ok!(Democracy::set_proxy(Origin::signed(1), 10)); assert_eq!(Democracy::proxy(10), Some(1)); // Can't set when already set. assert_noop!(Democracy::set_proxy(Origin::signed(2), 10), "already a proxy"); // But this works because 11 isn't proxying. assert_ok!(Democracy::set_proxy(Origin::signed(2), 11)); assert_eq!(Democracy::proxy(10), Some(1)); assert_eq!(Democracy::proxy(11), Some(2)); // 2 cannot fire 1's proxy: assert_noop!(Democracy::remove_proxy(Origin::signed(2), 10), "wrong proxy"); // 1 fires his proxy: assert_ok!(Democracy::remove_proxy(Origin::signed(1), 10)); assert_eq!(Democracy::proxy(10), None); assert_eq!(Democracy::proxy(11), Some(2)); // 11 resigns: assert_ok!(Democracy::resign_proxy(Origin::signed(11))); assert_eq!(Democracy::proxy(10), None); assert_eq!(Democracy::proxy(11), None); }); } #[test] fn single_proposal_should_work_with_proxy() { new_test_ext().execute_with(|| { System::set_block_number(0); assert_ok!(propose_set_balance(1, 2, 1)); fast_forward_to(1); let r = 0; assert_ok!(Democracy::set_proxy(Origin::signed(1), 10)); assert_ok!(Democracy::proxy_vote(Origin::signed(10), r, AYE)); assert_eq!(Democracy::voters_for(r), vec![1]); assert_eq!(Democracy::vote_of((r, 1)), AYE); assert_eq!(Democracy::tally(r), (1, 0, 1)); fast_forward_to(5); assert_eq!(Balances::free_balance(&42), 2); }); } #[test] fn single_proposal_should_work_with_delegation() { new_test_ext().execute_with(|| { System::set_block_number(0); assert_ok!(propose_set_balance(1, 2, 1)); fast_forward_to(1); // Delegate vote. assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::max_value())); let r = 0; assert_ok!(Democracy::vote(Origin::signed(1), r, AYE)); assert_eq!(Democracy::voters_for(r), vec![1]); assert_eq!(Democracy::vote_of((r, 1)), AYE); // Delegated vote is counted. assert_eq!(Democracy::tally(r), (3, 0, 3)); fast_forward_to(5); assert_eq!(Balances::free_balance(&42), 2); }); } #[test] fn single_proposal_should_work_with_cyclic_delegation() { new_test_ext().execute_with(|| { System::set_block_number(0); assert_ok!(propose_set_balance(1, 2, 1)); fast_forward_to(1); // Check behavior with cycle. assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::max_value())); assert_ok!(Democracy::delegate(Origin::signed(3), 2, Conviction::max_value())); assert_ok!(Democracy::delegate(Origin::signed(1), 3, Conviction::max_value())); let r = 0; assert_ok!(Democracy::vote(Origin::signed(1), r, AYE)); assert_eq!(Democracy::voters_for(r), vec![1]); // Delegated vote is counted. assert_eq!(Democracy::tally(r), (6, 0, 6)); fast_forward_to(5); assert_eq!(Balances::free_balance(&42), 2); }); } #[test] /// If transactor already voted, delegated vote is overwriten. fn single_proposal_should_work_with_vote_and_delegation() { new_test_ext().execute_with(|| { System::set_block_number(0); assert_ok!(propose_set_balance(1, 2, 1)); fast_forward_to(1); let r = 0; assert_ok!(Democracy::vote(Origin::signed(1), r, AYE)); // Vote. assert_ok!(Democracy::vote(Origin::signed(2), r, AYE)); // Delegate vote. assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::max_value())); assert_eq!(Democracy::voters_for(r), vec![1, 2]); assert_eq!(Democracy::vote_of((r, 1)), AYE); // Delegated vote is not counted. assert_eq!(Democracy::tally(r), (3, 0, 3)); fast_forward_to(5); assert_eq!(Balances::free_balance(&42), 2); }); } #[test] fn single_proposal_should_work_with_undelegation() { new_test_ext().execute_with(|| { System::set_block_number(0); assert_ok!(propose_set_balance(1, 2, 1)); // Delegate and undelegate vote. assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::max_value())); assert_ok!(Democracy::undelegate(Origin::signed(2))); fast_forward_to(1); let r = 0; assert_ok!(Democracy::vote(Origin::signed(1), r, AYE)); assert_eq!(Democracy::referendum_count(), 1); assert_eq!(Democracy::voters_for(r), vec![1]); assert_eq!(Democracy::vote_of((r, 1)), AYE); // Delegated vote is not counted. assert_eq!(Democracy::tally(r), (1, 0, 1)); fast_forward_to(5); assert_eq!(Balances::free_balance(&42), 2); }); } #[test] /// If transactor voted, delegated vote is overwriten. fn single_proposal_should_work_with_delegation_and_vote() { new_test_ext().execute_with(|| { System::set_block_number(0); assert_ok!(propose_set_balance(1, 2, 1)); fast_forward_to(1); let r = 0; assert_ok!(Democracy::vote(Origin::signed(1), r, AYE)); // Delegate vote. assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::max_value())); // Vote. assert_ok!(Democracy::vote(Origin::signed(2), r, AYE)); assert_eq!(Democracy::referendum_count(), 1); assert_eq!(Democracy::voters_for(r), vec![1, 2]); assert_eq!(Democracy::vote_of((r, 1)), AYE); // Delegated vote is not counted. assert_eq!(Democracy::tally(r), (3, 0, 3)); fast_forward_to(5); assert_eq!(Balances::free_balance(&42), 2); }); } #[test] fn deposit_for_proposals_should_be_taken() { new_test_ext().execute_with(|| { System::set_block_number(1); assert_ok!(propose_set_balance(1, 2, 5)); assert_ok!(Democracy::second(Origin::signed(2), 0)); assert_ok!(Democracy::second(Origin::signed(5), 0)); assert_ok!(Democracy::second(Origin::signed(5), 0)); assert_ok!(Democracy::second(Origin::signed(5), 0)); assert_eq!(Balances::free_balance(&1), 5); assert_eq!(Balances::free_balance(&2), 15); assert_eq!(Balances::free_balance(&5), 35); }); } #[test] fn deposit_for_proposals_should_be_returned() { new_test_ext().execute_with(|| { System::set_block_number(1); assert_ok!(propose_set_balance(1, 2, 5)); assert_ok!(Democracy::second(Origin::signed(2), 0)); assert_ok!(Democracy::second(Origin::signed(5), 0)); assert_ok!(Democracy::second(Origin::signed(5), 0)); assert_ok!(Democracy::second(Origin::signed(5), 0)); fast_forward_to(3); assert_eq!(Balances::free_balance(&1), 10); assert_eq!(Balances::free_balance(&2), 20); assert_eq!(Balances::free_balance(&5), 50); }); } #[test] fn proposal_with_deposit_below_minimum_should_not_work() { new_test_ext().execute_with(|| { System::set_block_number(1); assert_noop!(propose_set_balance(1, 2, 0), "value too low"); }); } #[test] fn poor_proposer_should_not_work() { new_test_ext().execute_with(|| { System::set_block_number(1); assert_noop!(propose_set_balance(1, 2, 11), "proposer\'s balance too low"); }); } #[test] fn poor_seconder_should_not_work() { new_test_ext().execute_with(|| { System::set_block_number(1); assert_ok!(propose_set_balance(2, 2, 11)); assert_noop!(Democracy::second(Origin::signed(1), 0), "seconder\'s balance too low"); }); } #[test] fn runners_up_should_come_after() { new_test_ext().execute_with(|| { System::set_block_number(0); assert_ok!(propose_set_balance(1, 2, 2)); assert_ok!(propose_set_balance(1, 4, 4)); assert_ok!(propose_set_balance(1, 3, 3)); fast_forward_to(1); assert_ok!(Democracy::vote(Origin::signed(1), 0, AYE)); fast_forward_to(3); assert_ok!(Democracy::vote(Origin::signed(1), 1, AYE)); fast_forward_to(5); assert_ok!(Democracy::vote(Origin::signed(1), 2, AYE)); }); } #[test] fn simple_passing_should_work() { new_test_ext().execute_with(|| { System::set_block_number(1); let r = Democracy::inject_referendum( 1, set_balance_proposal(2), VoteThreshold::SuperMajorityApprove, 0 ).unwrap(); assert_ok!(Democracy::vote(Origin::signed(1), r, AYE)); assert_eq!(Democracy::voters_for(r), vec![1]); assert_eq!(Democracy::vote_of((r, 1)), AYE); assert_eq!(Democracy::tally(r), (1, 0, 1)); next_block(); next_block(); assert_eq!(Balances::free_balance(&42), 2); }); } #[test] fn cancel_referendum_should_work() { new_test_ext().execute_with(|| { System::set_block_number(1); let r = Democracy::inject_referendum( 1, set_balance_proposal(2), VoteThreshold::SuperMajorityApprove, 0 ).unwrap(); assert_ok!(Democracy::vote(Origin::signed(1), r, AYE)); assert_ok!(Democracy::cancel_referendum(Origin::ROOT, r.into())); next_block(); next_block(); assert_eq!(Balances::free_balance(&42), 0); }); } #[test] fn simple_failing_should_work() { new_test_ext().execute_with(|| { System::set_block_number(1); let r = Democracy::inject_referendum( 1, set_balance_proposal(2), VoteThreshold::SuperMajorityApprove, 0 ).unwrap(); assert_ok!(Democracy::vote(Origin::signed(1), r, NAY)); assert_eq!(Democracy::voters_for(r), vec![1]); assert_eq!(Democracy::vote_of((r, 1)), NAY); assert_eq!(Democracy::tally(r), (0, 1, 1)); next_block(); next_block(); assert_eq!(Balances::free_balance(&42), 0); }); } #[test] fn controversial_voting_should_work() { new_test_ext().execute_with(|| { System::set_block_number(1); let r = Democracy::inject_referendum( 1, set_balance_proposal(2), VoteThreshold::SuperMajorityApprove, 0 ).unwrap(); assert_ok!(Democracy::vote(Origin::signed(1), r, BIG_AYE)); assert_ok!(Democracy::vote(Origin::signed(2), r, BIG_NAY)); assert_ok!(Democracy::vote(Origin::signed(3), r, BIG_NAY)); assert_ok!(Democracy::vote(Origin::signed(4), r, BIG_AYE)); assert_ok!(Democracy::vote(Origin::signed(5), r, BIG_NAY)); assert_ok!(Democracy::vote(Origin::signed(6), r, BIG_AYE)); assert_eq!(Democracy::tally(r), (110, 100, 210)); next_block(); next_block(); assert_eq!(Balances::free_balance(&42), 2); }); } #[test] fn delayed_enactment_should_work() { new_test_ext().execute_with(|| { System::set_block_number(1); let r = Democracy::inject_referendum( 1, set_balance_proposal(2), VoteThreshold::SuperMajorityApprove, 1 ).unwrap(); assert_ok!(Democracy::vote(Origin::signed(1), r, AYE)); assert_ok!(Democracy::vote(Origin::signed(2), r, AYE)); assert_ok!(Democracy::vote(Origin::signed(3), r, AYE)); assert_ok!(Democracy::vote(Origin::signed(4), r, AYE)); assert_ok!(Democracy::vote(Origin::signed(5), r, AYE)); assert_ok!(Democracy::vote(Origin::signed(6), r, AYE)); assert_eq!(Democracy::tally(r), (21, 0, 21)); next_block(); assert_eq!(Balances::free_balance(&42), 0); next_block(); assert_eq!(Balances::free_balance(&42), 2); }); } #[test] fn controversial_low_turnout_voting_should_work() { new_test_ext().execute_with(|| { System::set_block_number(1); let r = Democracy::inject_referendum( 1, set_balance_proposal(2), VoteThreshold::SuperMajorityApprove, 0 ).unwrap(); assert_ok!(Democracy::vote(Origin::signed(5), r, BIG_NAY)); assert_ok!(Democracy::vote(Origin::signed(6), r, BIG_AYE)); assert_eq!(Democracy::tally(r), (60, 50, 110)); next_block(); next_block(); assert_eq!(Balances::free_balance(&42), 0); }); } #[test] fn passing_low_turnout_voting_should_work() { new_test_ext().execute_with(|| { assert_eq!(Balances::free_balance(&42), 0); assert_eq!(Balances::total_issuance(), 210); System::set_block_number(1); let r = Democracy::inject_referendum( 1, set_balance_proposal(2), VoteThreshold::SuperMajorityApprove, 0 ).unwrap(); assert_ok!(Democracy::vote(Origin::signed(4), r, BIG_AYE)); assert_ok!(Democracy::vote(Origin::signed(5), r, BIG_NAY)); assert_ok!(Democracy::vote(Origin::signed(6), r, BIG_AYE)); assert_eq!(Democracy::tally(r), (100, 50, 150)); next_block(); next_block(); assert_eq!(Balances::free_balance(&42), 2); }); } #[test] fn lock_voting_should_work() { new_test_ext().execute_with(|| { System::set_block_number(0); let r = Democracy::inject_referendum( 1, set_balance_proposal(2), VoteThreshold::SuperMajorityApprove, 0 ).unwrap(); assert_ok!(Democracy::vote(Origin::signed(1), r, Vote { aye: false, conviction: Conviction::Locked5x })); assert_ok!(Democracy::vote(Origin::signed(2), r, Vote { aye: true, conviction: Conviction::Locked4x })); assert_ok!(Democracy::vote(Origin::signed(3), r, Vote { aye: true, conviction: Conviction::Locked3x })); assert_ok!(Democracy::vote(Origin::signed(4), r, Vote { aye: true, conviction: Conviction::Locked2x })); assert_ok!(Democracy::vote(Origin::signed(5), r, Vote { aye: false, conviction: Conviction::Locked1x })); assert_eq!(Democracy::tally(r), (250, 100, 150)); fast_forward_to(2); assert_eq!(Balances::locks(1), vec![]); assert_eq!(Balances::locks(2), vec![BalanceLock { id: DEMOCRACY_ID, amount: u64::max_value(), until: 17, reasons: WithdrawReason::Transfer.into() }]); assert_eq!(Balances::locks(3), vec![BalanceLock { id: DEMOCRACY_ID, amount: u64::max_value(), until: 9, reasons: WithdrawReason::Transfer.into() }]); assert_eq!(Balances::locks(4), vec![BalanceLock { id: DEMOCRACY_ID, amount: u64::max_value(), until: 5, reasons: WithdrawReason::Transfer.into() }]); assert_eq!(Balances::locks(5), vec![]); assert_eq!(Balances::free_balance(&42), 2); }); } #[test] fn lock_voting_should_work_with_delegation() { new_test_ext().execute_with(|| { System::set_block_number(1); let r = Democracy::inject_referendum( 1, set_balance_proposal(2), VoteThreshold::SuperMajorityApprove, 0 ).unwrap(); assert_ok!(Democracy::vote(Origin::signed(1), r, Vote { aye: false, conviction: Conviction::Locked5x })); assert_ok!(Democracy::vote(Origin::signed(2), r, Vote { aye: true, conviction: Conviction::Locked4x })); assert_ok!(Democracy::vote(Origin::signed(3), r, Vote { aye: true, conviction: Conviction::Locked3x })); assert_ok!(Democracy::delegate(Origin::signed(4), 2, Conviction::Locked2x)); assert_ok!(Democracy::vote(Origin::signed(5), r, Vote { aye: false, conviction: Conviction::Locked1x })); assert_eq!(Democracy::tally(r), (250, 100, 150)); next_block(); next_block(); assert_eq!(Balances::free_balance(&42), 2); }); } }