mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-12 06:31:09 +00:00
4dbc9265ee
This function executes the given closure in a context where the test externalities are set. This makes the srml tests easier to write, as the test externalities need to be created anyway.
2061 lines
62 KiB
Rust
2061 lines
62 KiB
Rust
// 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 <http://www.gnu.org/licenses/>.
|
|
|
|
//! 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<Conviction> 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<u8> for Conviction {
|
|
type Error = ();
|
|
fn try_from(i: u8) -> result::Result<Conviction, ()> {
|
|
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<u8> + 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<T: Output>(&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<I: Input>(input: &mut I) -> core::result::Result<Self, Error> {
|
|
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<T> = <<T as Trait>::Currency as Currency<<T as system::Trait>::AccountId>>::Balance;
|
|
|
|
pub trait Trait: system::Trait + Sized {
|
|
type Proposal: Parameter + Dispatchable<Origin=Self::Origin>;
|
|
type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
|
|
|
|
/// Currency type for this module.
|
|
type Currency: ReservableCurrency<Self::AccountId>
|
|
+ LockableCurrency<Self::AccountId, Moment=Self::BlockNumber>;
|
|
|
|
/// 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<Self::BlockNumber>;
|
|
|
|
/// How often (in blocks) new public referenda are launched.
|
|
type LaunchPeriod: Get<Self::BlockNumber>;
|
|
|
|
/// How often (in blocks) to check for new votes.
|
|
type VotingPeriod: Get<Self::BlockNumber>;
|
|
|
|
/// The minimum amount to be used as a deposit for a public referendum proposal.
|
|
type MinimumDeposit: Get<BalanceOf<Self>>;
|
|
|
|
/// Origin from which the next tabled referendum may be forced. This is a normal
|
|
/// "super-majority-required" referendum.
|
|
type ExternalOrigin: EnsureOrigin<Self::Origin>;
|
|
|
|
/// Origin from which the next tabled referendum may be forced; this allows for the tabling of
|
|
/// a majority-carries referendum.
|
|
type ExternalMajorityOrigin: EnsureOrigin<Self::Origin>;
|
|
|
|
/// 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<Self::Origin>;
|
|
|
|
/// 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<Self::Origin>;
|
|
|
|
/// Minimum voting period allowed for an fast-track/emergency referendum.
|
|
type EmergencyVotingPeriod: Get<Self::BlockNumber>;
|
|
|
|
/// Origin from which any referendum may be cancelled in an emergency.
|
|
type CancellationOrigin: EnsureOrigin<Self::Origin>;
|
|
|
|
/// Origin for anyone able to veto proposals.
|
|
type VetoOrigin: EnsureOrigin<Self::Origin, Success=Self::AccountId>;
|
|
|
|
/// Period in blocks where an external proposal may not be re-submitted after being vetoed.
|
|
type CooloffPeriod: Get<Self::BlockNumber>;
|
|
}
|
|
|
|
/// Info regarding an ongoing referendum.
|
|
#[derive(Encode, Decode, Clone, PartialEq, Eq)]
|
|
#[cfg_attr(feature = "std", derive(Debug))]
|
|
pub struct ReferendumInfo<BlockNumber: Parameter, Proposal: Parameter> {
|
|
/// 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<BlockNumber: Parameter, Proposal: Parameter> ReferendumInfo<BlockNumber, Proposal> {
|
|
/// 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<T: Trait> 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<T>, Vec<T::AccountId>)>;
|
|
|
|
/// 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<T::BlockNumber, T::Proposal>)>;
|
|
/// Queue of successful referenda to be dispatched.
|
|
pub DispatchQueue get(dispatch_queue):
|
|
map T::BlockNumber => Vec<Option<(T::Proposal, ReferendumIndex)>>;
|
|
|
|
/// Get the voters for the current proposal.
|
|
pub VotersFor get(voters_for): map ReferendumIndex => Vec<T::AccountId>;
|
|
|
|
/// 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<T::AccountId>;
|
|
|
|
/// 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<T::AccountId>)>;
|
|
|
|
/// Record of all proposals that have been subject to emergency cancellation.
|
|
pub Cancellations: map T::Hash => bool;
|
|
}
|
|
}
|
|
|
|
decl_event!(
|
|
pub enum Event<T> where
|
|
Balance = BalanceOf<T>,
|
|
<T as system::Trait>::AccountId,
|
|
<T as system::Trait>::Hash,
|
|
<T as system::Trait>::BlockNumber,
|
|
{
|
|
Proposed(PropIndex, Balance),
|
|
Tabled(PropIndex, Balance, Vec<AccountId>),
|
|
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<T: Trait> 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> = 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.
|
|
///
|
|
/// # <weight>
|
|
/// - O(1).
|
|
/// - Two DB changes, one DB entry.
|
|
/// # </weight>
|
|
#[weight = SimpleDispatchInfo::FixedNormal(5_000_000)]
|
|
fn propose(origin,
|
|
proposal: Box<T::Proposal>,
|
|
#[compact] value: BalanceOf<T>
|
|
) {
|
|
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);
|
|
<DepositOf<T>>::insert(index, (value, &[&who][..]));
|
|
|
|
let new_prop = (index, proposal, who);
|
|
<PublicProps<T>>::append_or_put(&[Ref::from(&new_prop)][..]);
|
|
|
|
Self::deposit_event(RawEvent::Proposed(index, value));
|
|
}
|
|
|
|
/// Propose a sensitive action to be taken.
|
|
///
|
|
/// # <weight>
|
|
/// - O(1).
|
|
/// - One DB entry.
|
|
/// # </weight>
|
|
#[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);
|
|
<DepositOf<T>>::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.
|
|
///
|
|
/// # <weight>
|
|
/// - O(1).
|
|
/// - One DB change, one DB entry.
|
|
/// # </weight>
|
|
#[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.
|
|
///
|
|
/// # <weight>
|
|
/// - O(1).
|
|
/// - One DB change, one DB entry.
|
|
/// # </weight>
|
|
#[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!(!<Cancellations<T>>::exists(h), "cannot cancel the same proposal twice");
|
|
|
|
<Cancellations<T>>::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::Proposal>) {
|
|
T::ExternalOrigin::ensure_origin(origin)?;
|
|
ensure!(!<NextExternal<T>>::exists(), "proposal already made");
|
|
let proposal_hash = T::Hashing::hash_of(&proposal);
|
|
if let Some((until, _)) = <Blacklist<T>>::get(proposal_hash) {
|
|
ensure!(<system::Module<T>>::block_number() >= until, "proposal still blacklisted");
|
|
}
|
|
<NextExternal<T>>::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::Proposal>) {
|
|
T::ExternalMajorityOrigin::ensure_origin(origin)?;
|
|
<NextExternal<T>>::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::Proposal>) {
|
|
T::ExternalDefaultOrigin::ensure_origin(origin)?;
|
|
<NextExternal<T>>::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) = <NextExternal<T>>::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");
|
|
|
|
<NextExternal<T>>::kill();
|
|
let now = <system::Module<T>>::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, _)) = <NextExternal<T>>::get() {
|
|
ensure!(proposal_hash == T::Hashing::hash_of(&proposal), "unknown proposal");
|
|
} else {
|
|
Err("no external proposal")?;
|
|
}
|
|
|
|
let mut existing_vetoers = <Blacklist<T>>::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 = <system::Module<T>>::block_number() + T::CooloffPeriod::get();
|
|
<Blacklist<T>>::insert(&proposal_hash, (until, existing_vetoers));
|
|
|
|
Self::deposit_event(RawEvent::Vetoed(who, proposal_hash, until));
|
|
<NextExternal<T>>::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 = <DispatchQueue<T>>::get(when);
|
|
if items.get(which).and_then(Option::as_ref).map_or(false, |x| x.1 == what) {
|
|
items[which] = None;
|
|
<DispatchQueue<T>>::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.
|
|
///
|
|
/// # <weight>
|
|
/// - One extra DB entry.
|
|
/// # </weight>
|
|
#[weight = SimpleDispatchInfo::FixedNormal(100_000)]
|
|
fn set_proxy(origin, proxy: T::AccountId) {
|
|
let who = ensure_signed(origin)?;
|
|
ensure!(!<Proxy<T>>::exists(&proxy), "already a proxy");
|
|
<Proxy<T>>::insert(proxy, who)
|
|
}
|
|
|
|
/// Clear the proxy. Called by the proxy.
|
|
///
|
|
/// # <weight>
|
|
/// - One DB clear.
|
|
/// # </weight>
|
|
#[weight = SimpleDispatchInfo::FixedNormal(100_000)]
|
|
fn resign_proxy(origin) {
|
|
let who = ensure_signed(origin)?;
|
|
<Proxy<T>>::remove(who);
|
|
}
|
|
|
|
/// Clear the proxy. Called by the stash.
|
|
///
|
|
/// # <weight>
|
|
/// - One DB clear.
|
|
/// # </weight>
|
|
#[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");
|
|
<Proxy<T>>::remove(proxy);
|
|
}
|
|
|
|
/// Delegate vote.
|
|
///
|
|
/// # <weight>
|
|
/// - One extra DB entry.
|
|
/// # </weight>
|
|
#[weight = SimpleDispatchInfo::FixedNormal(500_000)]
|
|
pub fn delegate(origin, to: T::AccountId, conviction: Conviction) {
|
|
let who = ensure_signed(origin)?;
|
|
<Delegations<T>>::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.
|
|
///
|
|
/// # <weight>
|
|
/// - O(1).
|
|
/// # </weight>
|
|
#[weight = SimpleDispatchInfo::FixedNormal(500_000)]
|
|
fn undelegate(origin) {
|
|
let who = ensure_signed(origin)?;
|
|
ensure!(<Delegations<T>>::exists(&who), "not delegated");
|
|
let (_, conviction) = <Delegations<T>>::take(&who);
|
|
// Indefinite lock is reduced to the maximum voting lock that could be possible.
|
|
let now = <system::Module<T>>::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<T: Trait> Module<T> {
|
|
// 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<BalanceOf<T>> {
|
|
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 {
|
|
<ReferendumInfoOf<T>>::exists(ref_index)
|
|
}
|
|
|
|
/// Get all referenda currently active.
|
|
pub fn active_referenda()
|
|
-> Vec<(ReferendumIndex, ReferendumInfo<T::BlockNumber, T::Proposal>)>
|
|
{
|
|
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<T::BlockNumber, T::Proposal>)> {
|
|
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<T>, BalanceOf<T>, BalanceOf<T>) {
|
|
let (approve, against, capital):
|
|
(BalanceOf<T>, BalanceOf<T>, BalanceOf<T>) = 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<T>, BalanceOf<T>, BalanceOf<T>) {
|
|
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<T>, BalanceOf<T>) {
|
|
if recursion_limit == 0 { return (Zero::zero(), Zero::zero()); }
|
|
<Delegations<T>>::enumerate()
|
|
.filter(|(delegator, (delegate, _))|
|
|
*delegate == to && !<VoteOf<T>>::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) {
|
|
<Proxy<T>>::insert(proxy, stash)
|
|
}
|
|
|
|
/// Start a referendum.
|
|
pub fn internal_start_referendum(
|
|
proposal: T::Proposal,
|
|
threshold: VoteThreshold,
|
|
delay: T::BlockNumber
|
|
) -> result::Result<ReferendumIndex, &'static str> {
|
|
<Module<T>>::inject_referendum(
|
|
<system::Module<T>>::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));
|
|
<Module<T>>::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 !<VoteOf<T>>::exists((ref_index, &who)) {
|
|
<VotersFor<T>>::append_or_insert(ref_index, &[&who][..]);
|
|
}
|
|
<VoteOf<T>>::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<ReferendumIndex, &'static str> {
|
|
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 };
|
|
<ReferendumInfoOf<T>>::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) {
|
|
<ReferendumInfoOf<T>>::remove(ref_index);
|
|
<VotersFor<T>>::remove(ref_index);
|
|
for v in Self::voters_for(ref_index) {
|
|
<VoteOf<T>>::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)) = <NextExternal<T>>::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);
|
|
<PublicProps<T>>::put(public_props);
|
|
|
|
if let Some((deposit, depositors)) = <DepositOf<T>>::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<T::BlockNumber, T::Proposal>
|
|
) -> 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 {
|
|
<DispatchQueue<T>>::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 <DispatchQueue<T>>::take(now).into_iter().filter_map(|x| x) {
|
|
Self::enact_proposal(proposal, index);
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl<T: Trait> OnFreeBalanceZero<T::AccountId> for Module<T> {
|
|
fn on_free_balance_zero(who: &T::AccountId) {
|
|
<Proxy<T>>::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<Self::AccountId>;
|
|
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<u64> 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<Self>;
|
|
type EnactmentPeriod = EnactmentPeriod;
|
|
type LaunchPeriod = LaunchPeriod;
|
|
type VotingPeriod = VotingPeriod;
|
|
type EmergencyVotingPeriod = EmergencyVotingPeriod;
|
|
type MinimumDeposit = MinimumDeposit;
|
|
type ExternalOrigin = EnsureSignedBy<Two, u64>;
|
|
type ExternalMajorityOrigin = EnsureSignedBy<Three, u64>;
|
|
type ExternalDefaultOrigin = EnsureSignedBy<One, u64>;
|
|
type FastTrackOrigin = EnsureSignedBy<Five, u64>;
|
|
type CancellationOrigin = EnsureSignedBy<Four, u64>;
|
|
type VetoOrigin = EnsureSignedBy<OneToFive, u64>;
|
|
type CooloffPeriod = CooloffPeriod;
|
|
}
|
|
|
|
fn new_test_ext() -> runtime_io::TestExternalities {
|
|
let mut t = system::GenesisConfig::default().build_storage::<Test>().unwrap();
|
|
balances::GenesisConfig::<Test>{
|
|
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<Test>;
|
|
type Balances = balances::Module<Test>;
|
|
type Democracy = Module<Test>;
|
|
|
|
#[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!(<NextExternal<Test>>::exists());
|
|
|
|
let h = BlakeTwo256::hash_of(&set_balance_proposal(2));
|
|
assert_ok!(Democracy::veto_external(Origin::signed(3), h.clone()));
|
|
// cancelled.
|
|
assert!(!<NextExternal<Test>>::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!(<NextExternal<Test>>::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!(!<NextExternal<Test>>::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);
|
|
});
|
|
}
|
|
}
|