From e7cc2f42c7294220e8ccbf3130b602a428c39660 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 18 Mar 2020 11:28:39 +0100 Subject: [PATCH 01/22] Repot a bit of democracy code --- frame/democracy/src/conviction.rs | 118 +++ frame/democracy/src/lib.rs | 1552 +---------------------------- frame/democracy/src/tests.rs | 1382 +++++++++++++++++++++++++ frame/democracy/src/types.rs | 64 ++ frame/democracy/src/vote.rs | 48 + 5 files changed, 1625 insertions(+), 1539 deletions(-) create mode 100644 frame/democracy/src/conviction.rs create mode 100644 frame/democracy/src/tests.rs create mode 100644 frame/democracy/src/types.rs create mode 100644 frame/democracy/src/vote.rs diff --git a/frame/democracy/src/conviction.rs b/frame/democracy/src/conviction.rs new file mode 100644 index 0000000000000..9117bfd5fe696 --- /dev/null +++ b/frame/democracy/src/conviction.rs @@ -0,0 +1,118 @@ +// Copyright 2017-2020 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 . + +//! The conviction datatype. + +use sp_std::{result::Result, convert::TryFrom}; +use sp_runtime::{RuntimeDebug, traits::{Zero, Bounded, CheckedMul, CheckedDiv}}; +use codec::{Encode, Decode}; + +/// A value denoting the strength of conviction of a vote. +#[derive(Encode, Decode, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug)] +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 { + 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. + pub 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. + pub 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 + } +} diff --git a/frame/democracy/src/lib.rs b/frame/democracy/src/lib.rs index cbc766499fe6e..5316b89915f29 100644 --- a/frame/democracy/src/lib.rs +++ b/frame/democracy/src/lib.rs @@ -152,12 +152,10 @@ #![cfg_attr(not(feature = "std"), no_std)] use sp_std::prelude::*; -use sp_std::{result, convert::TryFrom}; use sp_runtime::{ - RuntimeDebug, DispatchResult, - traits::{Zero, Bounded, CheckedMul, CheckedDiv, EnsureOrigin, Hash, Dispatchable, Saturating}, + DispatchResult, traits::{Zero, Bounded, EnsureOrigin, Hash, Dispatchable, Saturating}, }; -use codec::{Ref, Encode, Decode, Input, Output}; +use codec::{Ref, Decode}; use frame_support::{ decl_module, decl_storage, decl_event, decl_error, ensure, Parameter, IterableStorageMap, weights::SimpleDispatchInfo, @@ -167,10 +165,19 @@ use frame_support::{ } }; use frame_system::{self as system, ensure_signed, ensure_root}; +use frame_support::traits::MigrateAccount; mod vote_threshold; +mod vote; +mod conviction; +mod types; pub use vote_threshold::{Approved, VoteThreshold}; -use frame_support::traits::MigrateAccount; +pub use vote::Vote; +pub use conviction::Conviction; +pub use types::{ReferendumInfo, ProxyState}; + +#[cfg(test)] +mod tests; const DEMOCRACY_ID: LockIdentifier = *b"democrac"; @@ -180,134 +187,11 @@ 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, RuntimeDebug)] -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, RuntimeDebug)] -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(|_| codec::Error::from("Invalid conviction"))?, - }) - } -} - type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; type NegativeImbalanceOf = -<::Currency as Currency<::AccountId>>::NegativeImbalance; + <::Currency as Currency<::AccountId>>::NegativeImbalance; pub trait Trait: frame_system::Trait + Sized { type Proposal: Parameter + Dispatchable; @@ -369,49 +253,6 @@ pub trait Trait: frame_system::Trait + Sized { type Slash: OnUnbalanced>; } -/// Info regarding an ongoing referendum. -#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)] -pub struct ReferendumInfo { - /// When voting on this referendum will end. - end: BlockNumber, - /// The hash of the proposal being voted on. - proposal_hash: Hash, - /// 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_hash: Hash, - threshold: VoteThreshold, - delay: BlockNumber - ) -> Self { - ReferendumInfo { end, proposal_hash, threshold, delay } - } -} - -/// State of a proxy voting account. -#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug)] -pub enum ProxyState { - /// Account is open to becoming a proxy but is not yet assigned. - Open(AccountId), - /// Account is actively being a proxy. - Active(AccountId), -} - -impl ProxyState { - fn as_active(self) -> Option { - match self { - ProxyState::Active(a) => Some(a), - ProxyState::Open(_) => None, - } - } -} - decl_storage! { trait Store for Module as Democracy { /// The number of (public) proposals that have been made so far. @@ -1579,1370 +1420,3 @@ impl Module { Ok(()) } } - -#[cfg(test)] -mod tests { - use super::*; - use std::cell::RefCell; - use frame_support::{ - impl_outer_origin, impl_outer_dispatch, assert_noop, assert_ok, parameter_types, - ord_parameter_types, traits::Contains, weights::Weight, - }; - use sp_core::H256; - use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup, Bounded, BadOrigin}, - testing::Header, Perbill, - }; - use pallet_balances::{BalanceLock, Error as BalancesError}; - use frame_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 where system = frame_system {} - } - - impl_outer_dispatch! { - pub enum Call for Test where origin: Origin { - pallet_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: Weight = 1024; - pub const MaximumBlockLength: u32 = 2 * 1024; - pub const AvailableBlockRatio: Perbill = Perbill::one(); - } - impl frame_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 Event = (); - type BlockHashCount = BlockHashCount; - type MaximumBlockWeight = MaximumBlockWeight; - type MaximumBlockLength = MaximumBlockLength; - type AvailableBlockRatio = AvailableBlockRatio; - type Version = (); - type ModuleToIndex = (); - type AccountData = pallet_balances::AccountData; - type MigrateAccount = (); type OnNewAccount = (); - type OnKilledAccount = (); - } - parameter_types! { - pub const ExistentialDeposit: u64 = 1; - } - impl pallet_balances::Trait for Test { - type Balance = u64; - type Event = (); - type DustRemoval = (); - type ExistentialDeposit = ExistentialDeposit; - type AccountStore = System; - } - 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; - } - ord_parameter_types! { - 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 sorted_members() -> Vec { - vec![1, 2, 3, 4, 5] - } - } - thread_local! { - static PREIMAGE_BYTE_DEPOSIT: RefCell = RefCell::new(0); - } - pub struct PreimageByteDeposit; - impl Get for PreimageByteDeposit { - fn get() -> u64 { PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow()) } - } - impl super::Trait for Test { - type Proposal = Call; - type Event = (); - type Currency = pallet_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; - type PreimageByteDeposit = PreimageByteDeposit; - type Slash = (); - } - - fn new_test_ext() -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); - pallet_balances::GenesisConfig::{ - balances: vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)], - }.assimilate_storage(&mut t).unwrap(); - GenesisConfig::default().assimilate_storage(&mut t).unwrap(); - sp_io::TestExternalities::new(t) - } - - type System = frame_system::Module; - type Balances = pallet_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) -> Vec { - Call::Balances(pallet_balances::Call::set_balance(42, value, 0)).encode() - } - - fn set_balance_proposal_hash(value: u64) -> H256 { - BlakeTwo256::hash(&set_balance_proposal(value)[..]) - } - - fn set_balance_proposal_hash_and_note(value: u64) -> H256 { - let p = set_balance_proposal(value); - let h = BlakeTwo256::hash(&p[..]); - match Democracy::note_preimage(Origin::signed(6), p) { - Ok(_) => (), - Err(x) if x == Error::::DuplicatePreimage.into() => (), - Err(x) => panic!(x), - } - h - } - - fn propose_set_balance(who: u64, value: u64, delay: u64) -> DispatchResult { - Democracy::propose( - Origin::signed(who), - set_balance_proposal_hash(value), - delay - ) - } - - fn propose_set_balance_and_note(who: u64, value: u64, delay: u64) -> DispatchResult { - Democracy::propose( - Origin::signed(who), - set_balance_proposal_hash_and_note(value), - delay - ) - } - - fn next_block() { - System::set_block_number(System::block_number() + 1); - assert_eq!(Democracy::begin_block(System::block_number()), Ok(())); - } - - fn fast_forward_to(n: u64) { - while System::block_number() < n { - next_block(); - } - } - - #[test] - fn missing_preimage_should_fail() { - new_test_ext().execute_with(|| { - System::set_block_number(1); - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash(2), - VoteThreshold::SuperMajorityApprove, - 0 - ); - assert_ok!(Democracy::vote(Origin::signed(1), r, AYE)); - - next_block(); - next_block(); - - assert_eq!(Balances::free_balance(42), 0); - }); - } - - #[test] - fn preimage_deposit_should_be_required_and_returned() { - new_test_ext().execute_with(|| { - System::set_block_number(1); - // fee of 100 is too much. - PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 100); - assert_noop!( - Democracy::note_preimage(Origin::signed(6), vec![0; 500]), - BalancesError::::InsufficientBalance, - ); - // fee of 1 is reasonable. - PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 1); - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 0 - ); - assert_ok!(Democracy::vote(Origin::signed(1), r, AYE)); - - assert_eq!(Balances::reserved_balance(6), 12); - - next_block(); - next_block(); - - assert_eq!(Balances::reserved_balance(6), 0); - assert_eq!(Balances::free_balance(6), 60); - assert_eq!(Balances::free_balance(42), 2); - }); - } - - #[test] - fn preimage_deposit_should_be_reapable_earlier_by_owner() { - new_test_ext().execute_with(|| { - System::set_block_number(1); - PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 1); - assert_ok!(Democracy::note_preimage(Origin::signed(6), set_balance_proposal(2))); - - assert_eq!(Balances::reserved_balance(6), 12); - - next_block(); - assert_noop!( - Democracy::reap_preimage(Origin::signed(6), set_balance_proposal_hash(2)), - Error::::Early - ); - next_block(); - assert_ok!(Democracy::reap_preimage(Origin::signed(6), set_balance_proposal_hash(2))); - - assert_eq!(Balances::free_balance(6), 60); - assert_eq!(Balances::reserved_balance(6), 0); - }); - } - - #[test] - fn preimage_deposit_should_be_reapable() { - new_test_ext().execute_with(|| { - System::set_block_number(1); - assert_noop!( - Democracy::reap_preimage(Origin::signed(5), set_balance_proposal_hash(2)), - Error::::PreimageMissing - ); - - PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 1); - assert_ok!(Democracy::note_preimage(Origin::signed(6), set_balance_proposal(2))); - assert_eq!(Balances::reserved_balance(6), 12); - - next_block(); - next_block(); - next_block(); - assert_noop!( - Democracy::reap_preimage(Origin::signed(5), set_balance_proposal_hash(2)), - Error::::Early - ); - - next_block(); - assert_ok!(Democracy::reap_preimage(Origin::signed(5), set_balance_proposal_hash(2))); - assert_eq!(Balances::reserved_balance(6), 0); - assert_eq!(Balances::free_balance(6), 48); - assert_eq!(Balances::free_balance(5), 62); - }); - } - - #[test] - fn noting_imminent_preimage_for_free_should_work() { - new_test_ext().execute_with(|| { - System::set_block_number(1); - PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 1); - - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash(2), - VoteThreshold::SuperMajorityApprove, - 1 - ); - assert_ok!(Democracy::vote(Origin::signed(1), r, AYE)); - - assert_noop!( - Democracy::note_imminent_preimage(Origin::signed(7), set_balance_proposal(2)), - Error::::NotImminent - ); - - next_block(); - - // Now we're in the dispatch queue it's all good. - assert_ok!(Democracy::note_imminent_preimage(Origin::signed(7), set_balance_proposal(2))); - - next_block(); - - assert_eq!(Balances::free_balance(42), 2); - }); - } - - #[test] - fn reaping_imminent_preimage_should_fail() { - new_test_ext().execute_with(|| { - System::set_block_number(1); - let h = set_balance_proposal_hash_and_note(2); - let r = Democracy::inject_referendum(3, h, VoteThreshold::SuperMajorityApprove, 1); - assert_ok!(Democracy::vote(Origin::signed(1), r, AYE)); - next_block(); - next_block(); - // now imminent. - assert_noop!(Democracy::reap_preimage(Origin::signed(6), h), Error::::Imminent); - }); - } - - #[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), - set_balance_proposal_hash_and_note(1), - )); - assert_ok!(propose_set_balance_and_note(6, 2, 2)); - - fast_forward_to(2); - - // both waiting: external goes first. - assert_eq!( - Democracy::referendum_info(0), - Some(ReferendumInfo { - end: 4, - proposal_hash: set_balance_proposal_hash_and_note(1), - threshold: VoteThreshold::SuperMajorityApprove, - delay: 2 - }) - ); - // replenish external - assert_ok!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash_and_note(3), - )); - - fast_forward_to(4); - - // both waiting: public goes next. - assert_eq!( - Democracy::referendum_info(1), - Some(ReferendumInfo { - end: 6, - proposal_hash: set_balance_proposal_hash_and_note(2), - threshold: VoteThreshold::SuperMajorityApprove, - delay: 2 - }) - ); - // don't replenish public - - fast_forward_to(6); - - // it's external "turn" again, though since public is empty that doesn't really matter - assert_eq!( - Democracy::referendum_info(2), - Some(ReferendumInfo { - end: 8, - proposal_hash: set_balance_proposal_hash_and_note(3), - threshold: VoteThreshold::SuperMajorityApprove, - delay: 2 - }) - ); - // replenish external - assert_ok!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash_and_note(5), - )); - - fast_forward_to(8); - - // external goes again because there's no public waiting. - assert_eq!( - Democracy::referendum_info(3), - Some(ReferendumInfo { - end: 10, - proposal_hash: set_balance_proposal_hash_and_note(5), - threshold: VoteThreshold::SuperMajorityApprove, - delay: 2 - }) - ); - // replenish both - assert_ok!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash_and_note(7), - )); - assert_ok!(propose_set_balance_and_note(6, 4, 2)); - - fast_forward_to(10); - - // public goes now since external went last time. - assert_eq!( - Democracy::referendum_info(4), - Some(ReferendumInfo { - end: 12, - proposal_hash: set_balance_proposal_hash_and_note(4), - threshold: VoteThreshold::SuperMajorityApprove, - delay: 2 - }) - ); - // replenish public again - assert_ok!(propose_set_balance_and_note(6, 6, 2)); - // cancel external - let h = set_balance_proposal_hash_and_note(7); - assert_ok!(Democracy::veto_external(Origin::signed(3), h)); - - fast_forward_to(12); - - // public goes again now since there's no external waiting. - assert_eq!( - Democracy::referendum_info(5), - Some(ReferendumInfo { - end: 14, - proposal_hash: set_balance_proposal_hash_and_note(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_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 2 - ); - assert!(Democracy::referendum_info(r).is_some()); - - assert_noop!(Democracy::emergency_cancel(Origin::signed(3), r), BadOrigin); - 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_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 2 - ); - assert!(Democracy::referendum_info(r).is_some()); - assert_noop!(Democracy::emergency_cancel(Origin::signed(4), r), Error::::AlreadyCanceled); - }); - } - - #[test] - fn veto_external_works() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - assert_ok!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash_and_note(2), - )); - assert!(>::exists()); - - let h = set_balance_proposal_hash_and_note(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), - set_balance_proposal_hash(2), - ), Error::::ProposalBlacklisted); - - fast_forward_to(1); - // fails as we're still in cooloff period. - assert_noop!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash(2), - ), Error::::ProposalBlacklisted); - - fast_forward_to(2); - // works; as we're out of the cooloff period. - assert_ok!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash_and_note(2), - )); - assert!(>::exists()); - - // 3 can't veto the same thing twice. - assert_noop!( - Democracy::veto_external(Origin::signed(3), h.clone()), - Error::::AlreadyVetoed - ); - - // 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), - set_balance_proposal_hash(2), - ), Error::::ProposalBlacklisted); - // different proposal works fine. - assert_ok!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash_and_note(3), - )); - }); - } - - #[test] - fn external_referendum_works() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - assert_noop!( - Democracy::external_propose( - Origin::signed(1), - set_balance_proposal_hash(2), - ), - BadOrigin, - ); - assert_ok!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash_and_note(2), - )); - assert_noop!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash(1), - ), Error::::DuplicateProposal); - fast_forward_to(2); - assert_eq!( - Democracy::referendum_info(0), - Some(ReferendumInfo { - end: 4, - proposal_hash: set_balance_proposal_hash(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), - set_balance_proposal_hash(2) - ), - BadOrigin, - ); - assert_ok!(Democracy::external_propose_majority( - Origin::signed(3), - set_balance_proposal_hash_and_note(2) - )); - fast_forward_to(2); - assert_eq!( - Democracy::referendum_info(0), - Some(ReferendumInfo { - end: 4, - proposal_hash: set_balance_proposal_hash(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), - set_balance_proposal_hash(2) - ), - BadOrigin, - ); - assert_ok!(Democracy::external_propose_default( - Origin::signed(1), - set_balance_proposal_hash_and_note(2) - )); - fast_forward_to(2); - assert_eq!( - Democracy::referendum_info(0), - Some(ReferendumInfo { - end: 4, - proposal_hash: set_balance_proposal_hash(2), - threshold: VoteThreshold::SuperMajorityAgainst, - delay: 2, - }) - ); - }); - } - - #[test] - fn fast_track_referendum_works() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - let h = set_balance_proposal_hash_and_note(2); - assert_noop!(Democracy::fast_track(Origin::signed(5), h, 3, 2), Error::::ProposalMissing); - assert_ok!(Democracy::external_propose_majority( - Origin::signed(3), - set_balance_proposal_hash_and_note(2) - )); - assert_noop!(Democracy::fast_track(Origin::signed(1), h, 3, 2), BadOrigin); - assert_ok!(Democracy::fast_track(Origin::signed(5), h, 0, 0)); - assert_eq!( - Democracy::referendum_info(0), - Some(ReferendumInfo { - end: 1, - proposal_hash: set_balance_proposal_hash_and_note(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 = set_balance_proposal_hash_and_note(2); - assert_ok!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash_and_note(2) - )); - assert_noop!( - Democracy::fast_track(Origin::signed(5), h, 3, 2), - Error::::NotSimpleMajority - ); - }); - } - - #[test] - fn locked_for_should_work() { - new_test_ext().execute_with(|| { - System::set_block_number(1); - assert_ok!(propose_set_balance_and_note(1, 2, 2)); - assert_ok!(propose_set_balance_and_note(1, 4, 4)); - assert_ok!(propose_set_balance_and_note(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_and_note(1, 2, 1)); - assert!(Democracy::referendum_info(0).is_none()); - - // start of 2 => next referendum scheduled. - fast_forward_to(2); - - 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: 4, - proposal_hash: set_balance_proposal_hash_and_note(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(3); - - // referendum still running - assert!(Democracy::referendum_info(0).is_some()); - - // referendum runs during 2 and 3, ends @ start of 4. - fast_forward_to(4); - - assert!(Democracy::referendum_info(0).is_none()); - assert_eq!(Democracy::dispatch_queue(), vec![ - (6, set_balance_proposal_hash_and_note(2), 0) - ]); - - // referendum passes and wait another two blocks for enactment. - fast_forward_to(6); - - 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_and_note(1, 2, 1)); - - // start of 2 => next referendum scheduled. - fast_forward_to(2); - - assert_ok!(Democracy::vote(Origin::signed(1), 0, AYE)); - - fast_forward_to(4); - - assert_eq!(Democracy::dispatch_queue(), vec![ - (6, set_balance_proposal_hash_and_note(2), 0) - ]); - - assert_noop!(Democracy::cancel_queued(Origin::ROOT, 1), Error::::ProposalMissing); - assert_ok!(Democracy::cancel_queued(Origin::ROOT, 0)); - assert_eq!(Democracy::dispatch_queue(), vec![]); - }); - } - - #[test] - fn proxy_should_work() { - new_test_ext().execute_with(|| { - assert_eq!(Democracy::proxy(10), None); - assert!(System::allow_death(&10)); - - assert_noop!(Democracy::activate_proxy(Origin::signed(1), 10), Error::::NotOpen); - - assert_ok!(Democracy::open_proxy(Origin::signed(10), 1)); - assert!(!System::allow_death(&10)); - assert_eq!(Democracy::proxy(10), Some(ProxyState::Open(1))); - - assert_noop!(Democracy::activate_proxy(Origin::signed(2), 10), Error::::WrongOpen); - assert_ok!(Democracy::activate_proxy(Origin::signed(1), 10)); - assert_eq!(Democracy::proxy(10), Some(ProxyState::Active(1))); - - // Can't set when already set. - assert_noop!(Democracy::activate_proxy(Origin::signed(2), 10), Error::::AlreadyProxy); - - // But this works because 11 isn't proxying. - assert_ok!(Democracy::open_proxy(Origin::signed(11), 2)); - assert_ok!(Democracy::activate_proxy(Origin::signed(2), 11)); - assert_eq!(Democracy::proxy(10), Some(ProxyState::Active(1))); - assert_eq!(Democracy::proxy(11), Some(ProxyState::Active(2))); - - // 2 cannot fire 1's proxy: - assert_noop!(Democracy::deactivate_proxy(Origin::signed(2), 10), Error::::WrongProxy); - - // 1 deactivates their proxy: - assert_ok!(Democracy::deactivate_proxy(Origin::signed(1), 10)); - assert_eq!(Democracy::proxy(10), Some(ProxyState::Open(1))); - // but the proxy account cannot be killed until the proxy is closed. - assert!(!System::allow_death(&10)); - - // and then 10 closes it completely: - assert_ok!(Democracy::close_proxy(Origin::signed(10))); - assert_eq!(Democracy::proxy(10), None); - assert!(System::allow_death(&10)); - - // 11 just closes without 2's "permission". - assert_ok!(Democracy::close_proxy(Origin::signed(11))); - assert_eq!(Democracy::proxy(11), None); - assert!(System::allow_death(&11)); - }); - } - - #[test] - fn single_proposal_should_work_with_proxy() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - assert_ok!(propose_set_balance_and_note(1, 2, 1)); - - fast_forward_to(2); - let r = 0; - assert_ok!(Democracy::open_proxy(Origin::signed(10), 1)); - assert_ok!(Democracy::activate_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(6); - 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_and_note(1, 2, 1)); - - fast_forward_to(2); - - // 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(6); - - 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_and_note(1, 2, 1)); - - fast_forward_to(2); - - // 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(6); - - assert_eq!(Balances::free_balance(42), 2); - }); - } - - #[test] - /// If transactor already voted, delegated vote is overwritten. - fn single_proposal_should_work_with_vote_and_delegation() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - - assert_ok!(propose_set_balance_and_note(1, 2, 1)); - - fast_forward_to(2); - - 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(6); - - 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_and_note(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(2); - 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(6); - - assert_eq!(Balances::free_balance(42), 2); - }); - } - - #[test] - /// If transactor voted, delegated vote is overwritten. - fn single_proposal_should_work_with_delegation_and_vote() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - - assert_ok!(propose_set_balance_and_note(1, 2, 1)); - - fast_forward_to(2); - 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(6); - - 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_and_note(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_and_note(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), Error::::ValueLow); - }); - } - - #[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), BalancesError::::InsufficientBalance); - }); - } - - #[test] - fn poor_seconder_should_not_work() { - new_test_ext().execute_with(|| { - System::set_block_number(1); - assert_ok!(propose_set_balance_and_note(2, 2, 11)); - assert_noop!(Democracy::second(Origin::signed(1), 0), BalancesError::::InsufficientBalance); - }); - } - - #[test] - fn runners_up_should_come_after() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - assert_ok!(propose_set_balance_and_note(1, 2, 2)); - assert_ok!(propose_set_balance_and_note(1, 4, 4)); - assert_ok!(propose_set_balance_and_note(1, 3, 3)); - fast_forward_to(2); - assert_ok!(Democracy::vote(Origin::signed(1), 0, AYE)); - fast_forward_to(4); - assert_ok!(Democracy::vote(Origin::signed(1), 1, AYE)); - fast_forward_to(6); - assert_ok!(Democracy::vote(Origin::signed(1), 2, AYE)); - }); - } - - #[test] - fn ooo_inject_referendums_should_work() { - new_test_ext().execute_with(|| { - System::set_block_number(1); - let r1 = Democracy::inject_referendum( - 3, - set_balance_proposal_hash_and_note(3), - VoteThreshold::SuperMajorityApprove, - 0 - ); - let r2 = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 0 - ); - - assert_ok!(Democracy::vote(Origin::signed(1), r2, AYE)); - assert_eq!(Democracy::voters_for(r2), vec![1]); - assert_eq!(Democracy::vote_of((r2, 1)), AYE); - assert_eq!(Democracy::tally(r2), (1, 0, 1)); - - next_block(); - assert_eq!(Balances::free_balance(42), 2); - - assert_ok!(Democracy::vote(Origin::signed(1), r1, AYE)); - assert_eq!(Democracy::voters_for(r1), vec![1]); - assert_eq!(Democracy::vote_of((r1, 1)), AYE); - assert_eq!(Democracy::tally(r1), (1, 0, 1)); - - next_block(); - assert_eq!(Balances::free_balance(42), 3); - }); - } - - #[test] - fn simple_passing_should_work() { - new_test_ext().execute_with(|| { - System::set_block_number(1); - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 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); - 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( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 0 - ); - 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( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 0 - ); - 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( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 0 - ); - - 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( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 1 - ); - 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( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 0 - ); - 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( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 0 - ); - 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( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 0 - ); - 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(), - reasons: pallet_balances::Reasons::Misc, - }]); - assert_eq!(Democracy::locks(2), Some(18)); - assert_eq!(Balances::locks(3), vec![BalanceLock { - id: DEMOCRACY_ID, - amount: u64::max_value(), - reasons: pallet_balances::Reasons::Misc, - }]); - assert_eq!(Democracy::locks(3), Some(10)); - assert_eq!(Balances::locks(4), vec![BalanceLock { - id: DEMOCRACY_ID, - amount: u64::max_value(), - reasons: pallet_balances::Reasons::Misc, - }]); - assert_eq!(Democracy::locks(4), Some(6)); - assert_eq!(Balances::locks(5), vec![]); - - assert_eq!(Balances::free_balance(42), 2); - - assert_noop!(Democracy::unlock(Origin::signed(1), 1), Error::::NotLocked); - - fast_forward_to(5); - assert_noop!(Democracy::unlock(Origin::signed(1), 4), Error::::NotExpired); - fast_forward_to(6); - assert_ok!(Democracy::unlock(Origin::signed(1), 4)); - assert_noop!(Democracy::unlock(Origin::signed(1), 4), Error::::NotLocked); - - fast_forward_to(9); - assert_noop!(Democracy::unlock(Origin::signed(1), 3), Error::::NotExpired); - fast_forward_to(10); - assert_ok!(Democracy::unlock(Origin::signed(1), 3)); - assert_noop!(Democracy::unlock(Origin::signed(1), 3), Error::::NotLocked); - - fast_forward_to(17); - assert_noop!(Democracy::unlock(Origin::signed(1), 2), Error::::NotExpired); - fast_forward_to(18); - assert_ok!(Democracy::unlock(Origin::signed(1), 2)); - assert_noop!(Democracy::unlock(Origin::signed(1), 2), Error::::NotLocked); - }); - } - - #[test] - fn no_locks_without_conviction_should_work() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 0, - ); - assert_ok!(Democracy::vote(Origin::signed(1), r, Vote { - aye: true, - conviction: Conviction::None, - })); - - fast_forward_to(2); - - assert_eq!(Balances::free_balance(42), 2); - assert_eq!(Balances::locks(1), vec![]); - }); - } - - #[test] - fn lock_voting_should_work_with_delegation() { - new_test_ext().execute_with(|| { - System::set_block_number(1); - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 0 - ); - 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); - }); - } -} diff --git a/frame/democracy/src/tests.rs b/frame/democracy/src/tests.rs new file mode 100644 index 0000000000000..2fc9d1fe49812 --- /dev/null +++ b/frame/democracy/src/tests.rs @@ -0,0 +1,1382 @@ +// Copyright 2017-2020 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 . + +//! The crate's tests. + +use super::*; +use std::cell::RefCell; +use codec::Encode; +use frame_support::{ + impl_outer_origin, impl_outer_dispatch, assert_noop, assert_ok, parameter_types, + ord_parameter_types, traits::Contains, weights::Weight, +}; +use sp_core::H256; +use sp_runtime::{ + traits::{BlakeTwo256, IdentityLookup, Bounded, BadOrigin}, + testing::Header, Perbill, +}; +use pallet_balances::{BalanceLock, Error as BalancesError}; +use frame_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 where system = frame_system {} + } + +impl_outer_dispatch! { + pub enum Call for Test where origin: Origin { + pallet_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: Weight = 1024; + pub const MaximumBlockLength: u32 = 2 * 1024; + pub const AvailableBlockRatio: Perbill = Perbill::one(); + } +impl frame_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 Event = (); + type BlockHashCount = BlockHashCount; + type MaximumBlockWeight = MaximumBlockWeight; + type MaximumBlockLength = MaximumBlockLength; + type AvailableBlockRatio = AvailableBlockRatio; + type Version = (); + type ModuleToIndex = (); + type AccountData = pallet_balances::AccountData; + type MigrateAccount = (); type OnNewAccount = (); + type OnKilledAccount = (); +} +parameter_types! { + pub const ExistentialDeposit: u64 = 1; + } +impl pallet_balances::Trait for Test { + type Balance = u64; + type Event = (); + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; +} +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; + } +ord_parameter_types! { + 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 sorted_members() -> Vec { + vec![1, 2, 3, 4, 5] + } +} +thread_local! { + static PREIMAGE_BYTE_DEPOSIT: RefCell = RefCell::new(0); + } +pub struct PreimageByteDeposit; +impl Get for PreimageByteDeposit { + fn get() -> u64 { PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow()) } +} +impl super::Trait for Test { + type Proposal = Call; + type Event = (); + type Currency = pallet_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; + type PreimageByteDeposit = PreimageByteDeposit; + type Slash = (); +} + +fn new_test_ext() -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + pallet_balances::GenesisConfig::{ + balances: vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)], + }.assimilate_storage(&mut t).unwrap(); + GenesisConfig::default().assimilate_storage(&mut t).unwrap(); + sp_io::TestExternalities::new(t) +} + +type System = frame_system::Module; +type Balances = pallet_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) -> Vec { + Call::Balances(pallet_balances::Call::set_balance(42, value, 0)).encode() +} + +fn set_balance_proposal_hash(value: u64) -> H256 { + BlakeTwo256::hash(&set_balance_proposal(value)[..]) +} + +fn set_balance_proposal_hash_and_note(value: u64) -> H256 { + let p = set_balance_proposal(value); + let h = BlakeTwo256::hash(&p[..]); + match Democracy::note_preimage(Origin::signed(6), p) { + Ok(_) => (), + Err(x) if x == Error::::DuplicatePreimage.into() => (), + Err(x) => panic!(x), + } + h +} + +fn propose_set_balance(who: u64, value: u64, delay: u64) -> DispatchResult { + Democracy::propose( + Origin::signed(who), + set_balance_proposal_hash(value), + delay + ) +} + +fn propose_set_balance_and_note(who: u64, value: u64, delay: u64) -> DispatchResult { + Democracy::propose( + Origin::signed(who), + set_balance_proposal_hash_and_note(value), + delay + ) +} + +fn next_block() { + System::set_block_number(System::block_number() + 1); + assert_eq!(Democracy::begin_block(System::block_number()), Ok(())); +} + +fn fast_forward_to(n: u64) { + while System::block_number() < n { + next_block(); + } +} + +#[test] +fn missing_preimage_should_fail() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash(2), + VoteThreshold::SuperMajorityApprove, + 0 + ); + assert_ok!(Democracy::vote(Origin::signed(1), r, AYE)); + + next_block(); + next_block(); + + assert_eq!(Balances::free_balance(42), 0); + }); +} + +#[test] +fn preimage_deposit_should_be_required_and_returned() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + // fee of 100 is too much. + PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 100); + assert_noop!( + Democracy::note_preimage(Origin::signed(6), vec![0; 500]), + BalancesError::::InsufficientBalance, + ); + // fee of 1 is reasonable. + PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 1); + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 0 + ); + assert_ok!(Democracy::vote(Origin::signed(1), r, AYE)); + + assert_eq!(Balances::reserved_balance(6), 12); + + next_block(); + next_block(); + + assert_eq!(Balances::reserved_balance(6), 0); + assert_eq!(Balances::free_balance(6), 60); + assert_eq!(Balances::free_balance(42), 2); + }); +} + +#[test] +fn preimage_deposit_should_be_reapable_earlier_by_owner() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 1); + assert_ok!(Democracy::note_preimage(Origin::signed(6), set_balance_proposal(2))); + + assert_eq!(Balances::reserved_balance(6), 12); + + next_block(); + assert_noop!( + Democracy::reap_preimage(Origin::signed(6), set_balance_proposal_hash(2)), + Error::::Early + ); + next_block(); + assert_ok!(Democracy::reap_preimage(Origin::signed(6), set_balance_proposal_hash(2))); + + assert_eq!(Balances::free_balance(6), 60); + assert_eq!(Balances::reserved_balance(6), 0); + }); +} + +#[test] +fn preimage_deposit_should_be_reapable() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + assert_noop!( + Democracy::reap_preimage(Origin::signed(5), set_balance_proposal_hash(2)), + Error::::PreimageMissing + ); + + PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 1); + assert_ok!(Democracy::note_preimage(Origin::signed(6), set_balance_proposal(2))); + assert_eq!(Balances::reserved_balance(6), 12); + + next_block(); + next_block(); + next_block(); + assert_noop!( + Democracy::reap_preimage(Origin::signed(5), set_balance_proposal_hash(2)), + Error::::Early + ); + + next_block(); + assert_ok!(Democracy::reap_preimage(Origin::signed(5), set_balance_proposal_hash(2))); + assert_eq!(Balances::reserved_balance(6), 0); + assert_eq!(Balances::free_balance(6), 48); + assert_eq!(Balances::free_balance(5), 62); + }); +} + +#[test] +fn noting_imminent_preimage_for_free_should_work() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 1); + + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash(2), + VoteThreshold::SuperMajorityApprove, + 1 + ); + assert_ok!(Democracy::vote(Origin::signed(1), r, AYE)); + + assert_noop!( + Democracy::note_imminent_preimage(Origin::signed(7), set_balance_proposal(2)), + Error::::NotImminent + ); + + next_block(); + + // Now we're in the dispatch queue it's all good. + assert_ok!(Democracy::note_imminent_preimage(Origin::signed(7), set_balance_proposal(2))); + + next_block(); + + assert_eq!(Balances::free_balance(42), 2); + }); +} + +#[test] +fn reaping_imminent_preimage_should_fail() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + let h = set_balance_proposal_hash_and_note(2); + let r = Democracy::inject_referendum(3, h, VoteThreshold::SuperMajorityApprove, 1); + assert_ok!(Democracy::vote(Origin::signed(1), r, AYE)); + next_block(); + next_block(); + // now imminent. + assert_noop!(Democracy::reap_preimage(Origin::signed(6), h), Error::::Imminent); + }); +} + +#[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), + set_balance_proposal_hash_and_note(1), + )); + assert_ok!(propose_set_balance_and_note(6, 2, 2)); + + fast_forward_to(2); + + // both waiting: external goes first. + assert_eq!( + Democracy::referendum_info(0), + Some(ReferendumInfo { + end: 4, + proposal_hash: set_balance_proposal_hash_and_note(1), + threshold: VoteThreshold::SuperMajorityApprove, + delay: 2 + }) + ); + // replenish external + assert_ok!(Democracy::external_propose( + Origin::signed(2), + set_balance_proposal_hash_and_note(3), + )); + + fast_forward_to(4); + + // both waiting: public goes next. + assert_eq!( + Democracy::referendum_info(1), + Some(ReferendumInfo { + end: 6, + proposal_hash: set_balance_proposal_hash_and_note(2), + threshold: VoteThreshold::SuperMajorityApprove, + delay: 2 + }) + ); + // don't replenish public + + fast_forward_to(6); + + // it's external "turn" again, though since public is empty that doesn't really matter + assert_eq!( + Democracy::referendum_info(2), + Some(ReferendumInfo { + end: 8, + proposal_hash: set_balance_proposal_hash_and_note(3), + threshold: VoteThreshold::SuperMajorityApprove, + delay: 2 + }) + ); + // replenish external + assert_ok!(Democracy::external_propose( + Origin::signed(2), + set_balance_proposal_hash_and_note(5), + )); + + fast_forward_to(8); + + // external goes again because there's no public waiting. + assert_eq!( + Democracy::referendum_info(3), + Some(ReferendumInfo { + end: 10, + proposal_hash: set_balance_proposal_hash_and_note(5), + threshold: VoteThreshold::SuperMajorityApprove, + delay: 2 + }) + ); + // replenish both + assert_ok!(Democracy::external_propose( + Origin::signed(2), + set_balance_proposal_hash_and_note(7), + )); + assert_ok!(propose_set_balance_and_note(6, 4, 2)); + + fast_forward_to(10); + + // public goes now since external went last time. + assert_eq!( + Democracy::referendum_info(4), + Some(ReferendumInfo { + end: 12, + proposal_hash: set_balance_proposal_hash_and_note(4), + threshold: VoteThreshold::SuperMajorityApprove, + delay: 2 + }) + ); + // replenish public again + assert_ok!(propose_set_balance_and_note(6, 6, 2)); + // cancel external + let h = set_balance_proposal_hash_and_note(7); + assert_ok!(Democracy::veto_external(Origin::signed(3), h)); + + fast_forward_to(12); + + // public goes again now since there's no external waiting. + assert_eq!( + Democracy::referendum_info(5), + Some(ReferendumInfo { + end: 14, + proposal_hash: set_balance_proposal_hash_and_note(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_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 2 + ); + assert!(Democracy::referendum_info(r).is_some()); + + assert_noop!(Democracy::emergency_cancel(Origin::signed(3), r), BadOrigin); + 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_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 2 + ); + assert!(Democracy::referendum_info(r).is_some()); + assert_noop!(Democracy::emergency_cancel(Origin::signed(4), r), Error::::AlreadyCanceled); + }); +} + +#[test] +fn veto_external_works() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + assert_ok!(Democracy::external_propose( + Origin::signed(2), + set_balance_proposal_hash_and_note(2), + )); + assert!(>::exists()); + + let h = set_balance_proposal_hash_and_note(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), + set_balance_proposal_hash(2), + ), Error::::ProposalBlacklisted); + + fast_forward_to(1); + // fails as we're still in cooloff period. + assert_noop!(Democracy::external_propose( + Origin::signed(2), + set_balance_proposal_hash(2), + ), Error::::ProposalBlacklisted); + + fast_forward_to(2); + // works; as we're out of the cooloff period. + assert_ok!(Democracy::external_propose( + Origin::signed(2), + set_balance_proposal_hash_and_note(2), + )); + assert!(>::exists()); + + // 3 can't veto the same thing twice. + assert_noop!( + Democracy::veto_external(Origin::signed(3), h.clone()), + Error::::AlreadyVetoed + ); + + // 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), + set_balance_proposal_hash(2), + ), Error::::ProposalBlacklisted); + // different proposal works fine. + assert_ok!(Democracy::external_propose( + Origin::signed(2), + set_balance_proposal_hash_and_note(3), + )); + }); +} + +#[test] +fn external_referendum_works() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + assert_noop!( + Democracy::external_propose( + Origin::signed(1), + set_balance_proposal_hash(2), + ), + BadOrigin, + ); + assert_ok!(Democracy::external_propose( + Origin::signed(2), + set_balance_proposal_hash_and_note(2), + )); + assert_noop!(Democracy::external_propose( + Origin::signed(2), + set_balance_proposal_hash(1), + ), Error::::DuplicateProposal); + fast_forward_to(2); + assert_eq!( + Democracy::referendum_info(0), + Some(ReferendumInfo { + end: 4, + proposal_hash: set_balance_proposal_hash(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), + set_balance_proposal_hash(2) + ), + BadOrigin, + ); + assert_ok!(Democracy::external_propose_majority( + Origin::signed(3), + set_balance_proposal_hash_and_note(2) + )); + fast_forward_to(2); + assert_eq!( + Democracy::referendum_info(0), + Some(ReferendumInfo { + end: 4, + proposal_hash: set_balance_proposal_hash(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), + set_balance_proposal_hash(2) + ), + BadOrigin, + ); + assert_ok!(Democracy::external_propose_default( + Origin::signed(1), + set_balance_proposal_hash_and_note(2) + )); + fast_forward_to(2); + assert_eq!( + Democracy::referendum_info(0), + Some(ReferendumInfo { + end: 4, + proposal_hash: set_balance_proposal_hash(2), + threshold: VoteThreshold::SuperMajorityAgainst, + delay: 2, + }) + ); + }); +} + +#[test] +fn fast_track_referendum_works() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + let h = set_balance_proposal_hash_and_note(2); + assert_noop!(Democracy::fast_track(Origin::signed(5), h, 3, 2), Error::::ProposalMissing); + assert_ok!(Democracy::external_propose_majority( + Origin::signed(3), + set_balance_proposal_hash_and_note(2) + )); + assert_noop!(Democracy::fast_track(Origin::signed(1), h, 3, 2), BadOrigin); + assert_ok!(Democracy::fast_track(Origin::signed(5), h, 0, 0)); + assert_eq!( + Democracy::referendum_info(0), + Some(ReferendumInfo { + end: 1, + proposal_hash: set_balance_proposal_hash_and_note(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 = set_balance_proposal_hash_and_note(2); + assert_ok!(Democracy::external_propose( + Origin::signed(2), + set_balance_proposal_hash_and_note(2) + )); + assert_noop!( + Democracy::fast_track(Origin::signed(5), h, 3, 2), + Error::::NotSimpleMajority + ); + }); +} + +#[test] +fn locked_for_should_work() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + assert_ok!(propose_set_balance_and_note(1, 2, 2)); + assert_ok!(propose_set_balance_and_note(1, 4, 4)); + assert_ok!(propose_set_balance_and_note(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_and_note(1, 2, 1)); + assert!(Democracy::referendum_info(0).is_none()); + + // start of 2 => next referendum scheduled. + fast_forward_to(2); + + 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: 4, + proposal_hash: set_balance_proposal_hash_and_note(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(3); + + // referendum still running + assert!(Democracy::referendum_info(0).is_some()); + + // referendum runs during 2 and 3, ends @ start of 4. + fast_forward_to(4); + + assert!(Democracy::referendum_info(0).is_none()); + assert_eq!(Democracy::dispatch_queue(), vec![ + (6, set_balance_proposal_hash_and_note(2), 0) + ]); + + // referendum passes and wait another two blocks for enactment. + fast_forward_to(6); + + 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_and_note(1, 2, 1)); + + // start of 2 => next referendum scheduled. + fast_forward_to(2); + + assert_ok!(Democracy::vote(Origin::signed(1), 0, AYE)); + + fast_forward_to(4); + + assert_eq!(Democracy::dispatch_queue(), vec![ + (6, set_balance_proposal_hash_and_note(2), 0) + ]); + + assert_noop!(Democracy::cancel_queued(Origin::ROOT, 1), Error::::ProposalMissing); + assert_ok!(Democracy::cancel_queued(Origin::ROOT, 0)); + assert_eq!(Democracy::dispatch_queue(), vec![]); + }); +} + +#[test] +fn proxy_should_work() { + new_test_ext().execute_with(|| { + assert_eq!(Democracy::proxy(10), None); + assert!(System::allow_death(&10)); + + assert_noop!(Democracy::activate_proxy(Origin::signed(1), 10), Error::::NotOpen); + + assert_ok!(Democracy::open_proxy(Origin::signed(10), 1)); + assert!(!System::allow_death(&10)); + assert_eq!(Democracy::proxy(10), Some(ProxyState::Open(1))); + + assert_noop!(Democracy::activate_proxy(Origin::signed(2), 10), Error::::WrongOpen); + assert_ok!(Democracy::activate_proxy(Origin::signed(1), 10)); + assert_eq!(Democracy::proxy(10), Some(ProxyState::Active(1))); + + // Can't set when already set. + assert_noop!(Democracy::activate_proxy(Origin::signed(2), 10), Error::::AlreadyProxy); + + // But this works because 11 isn't proxying. + assert_ok!(Democracy::open_proxy(Origin::signed(11), 2)); + assert_ok!(Democracy::activate_proxy(Origin::signed(2), 11)); + assert_eq!(Democracy::proxy(10), Some(ProxyState::Active(1))); + assert_eq!(Democracy::proxy(11), Some(ProxyState::Active(2))); + + // 2 cannot fire 1's proxy: + assert_noop!(Democracy::deactivate_proxy(Origin::signed(2), 10), Error::::WrongProxy); + + // 1 deactivates their proxy: + assert_ok!(Democracy::deactivate_proxy(Origin::signed(1), 10)); + assert_eq!(Democracy::proxy(10), Some(ProxyState::Open(1))); + // but the proxy account cannot be killed until the proxy is closed. + assert!(!System::allow_death(&10)); + + // and then 10 closes it completely: + assert_ok!(Democracy::close_proxy(Origin::signed(10))); + assert_eq!(Democracy::proxy(10), None); + assert!(System::allow_death(&10)); + + // 11 just closes without 2's "permission". + assert_ok!(Democracy::close_proxy(Origin::signed(11))); + assert_eq!(Democracy::proxy(11), None); + assert!(System::allow_death(&11)); + }); +} + +#[test] +fn single_proposal_should_work_with_proxy() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + assert_ok!(propose_set_balance_and_note(1, 2, 1)); + + fast_forward_to(2); + let r = 0; + assert_ok!(Democracy::open_proxy(Origin::signed(10), 1)); + assert_ok!(Democracy::activate_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(6); + 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_and_note(1, 2, 1)); + + fast_forward_to(2); + + // 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(6); + + 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_and_note(1, 2, 1)); + + fast_forward_to(2); + + // 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(6); + + assert_eq!(Balances::free_balance(42), 2); + }); +} + +#[test] +/// If transactor already voted, delegated vote is overwritten. +fn single_proposal_should_work_with_vote_and_delegation() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + + assert_ok!(propose_set_balance_and_note(1, 2, 1)); + + fast_forward_to(2); + + 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(6); + + 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_and_note(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(2); + 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(6); + + assert_eq!(Balances::free_balance(42), 2); + }); +} + +#[test] +/// If transactor voted, delegated vote is overwritten. +fn single_proposal_should_work_with_delegation_and_vote() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + + assert_ok!(propose_set_balance_and_note(1, 2, 1)); + + fast_forward_to(2); + 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(6); + + 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_and_note(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_and_note(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), Error::::ValueLow); + }); +} + +#[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), BalancesError::::InsufficientBalance); + }); +} + +#[test] +fn poor_seconder_should_not_work() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + assert_ok!(propose_set_balance_and_note(2, 2, 11)); + assert_noop!(Democracy::second(Origin::signed(1), 0), BalancesError::::InsufficientBalance); + }); +} + +#[test] +fn runners_up_should_come_after() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + assert_ok!(propose_set_balance_and_note(1, 2, 2)); + assert_ok!(propose_set_balance_and_note(1, 4, 4)); + assert_ok!(propose_set_balance_and_note(1, 3, 3)); + fast_forward_to(2); + assert_ok!(Democracy::vote(Origin::signed(1), 0, AYE)); + fast_forward_to(4); + assert_ok!(Democracy::vote(Origin::signed(1), 1, AYE)); + fast_forward_to(6); + assert_ok!(Democracy::vote(Origin::signed(1), 2, AYE)); + }); +} + +#[test] +fn ooo_inject_referendums_should_work() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + let r1 = Democracy::inject_referendum( + 3, + set_balance_proposal_hash_and_note(3), + VoteThreshold::SuperMajorityApprove, + 0 + ); + let r2 = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 0 + ); + + assert_ok!(Democracy::vote(Origin::signed(1), r2, AYE)); + assert_eq!(Democracy::voters_for(r2), vec![1]); + assert_eq!(Democracy::vote_of((r2, 1)), AYE); + assert_eq!(Democracy::tally(r2), (1, 0, 1)); + + next_block(); + assert_eq!(Balances::free_balance(42), 2); + + assert_ok!(Democracy::vote(Origin::signed(1), r1, AYE)); + assert_eq!(Democracy::voters_for(r1), vec![1]); + assert_eq!(Democracy::vote_of((r1, 1)), AYE); + assert_eq!(Democracy::tally(r1), (1, 0, 1)); + + next_block(); + assert_eq!(Balances::free_balance(42), 3); + }); +} + +#[test] +fn simple_passing_should_work() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 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); + 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( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 0 + ); + 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( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 0 + ); + 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( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 0 + ); + + 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( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 1 + ); + 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( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 0 + ); + 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( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 0 + ); + 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( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 0 + ); + 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(), + reasons: pallet_balances::Reasons::Misc, + }]); + assert_eq!(Democracy::locks(2), Some(18)); + assert_eq!(Balances::locks(3), vec![BalanceLock { + id: DEMOCRACY_ID, + amount: u64::max_value(), + reasons: pallet_balances::Reasons::Misc, + }]); + assert_eq!(Democracy::locks(3), Some(10)); + assert_eq!(Balances::locks(4), vec![BalanceLock { + id: DEMOCRACY_ID, + amount: u64::max_value(), + reasons: pallet_balances::Reasons::Misc, + }]); + assert_eq!(Democracy::locks(4), Some(6)); + assert_eq!(Balances::locks(5), vec![]); + + assert_eq!(Balances::free_balance(42), 2); + + assert_noop!(Democracy::unlock(Origin::signed(1), 1), Error::::NotLocked); + + fast_forward_to(5); + assert_noop!(Democracy::unlock(Origin::signed(1), 4), Error::::NotExpired); + fast_forward_to(6); + assert_ok!(Democracy::unlock(Origin::signed(1), 4)); + assert_noop!(Democracy::unlock(Origin::signed(1), 4), Error::::NotLocked); + + fast_forward_to(9); + assert_noop!(Democracy::unlock(Origin::signed(1), 3), Error::::NotExpired); + fast_forward_to(10); + assert_ok!(Democracy::unlock(Origin::signed(1), 3)); + assert_noop!(Democracy::unlock(Origin::signed(1), 3), Error::::NotLocked); + + fast_forward_to(17); + assert_noop!(Democracy::unlock(Origin::signed(1), 2), Error::::NotExpired); + fast_forward_to(18); + assert_ok!(Democracy::unlock(Origin::signed(1), 2)); + assert_noop!(Democracy::unlock(Origin::signed(1), 2), Error::::NotLocked); + }); +} + +#[test] +fn no_locks_without_conviction_should_work() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 0, + ); + assert_ok!(Democracy::vote(Origin::signed(1), r, Vote { + aye: true, + conviction: Conviction::None, + })); + + fast_forward_to(2); + + assert_eq!(Balances::free_balance(42), 2); + assert_eq!(Balances::locks(1), vec![]); + }); +} + +#[test] +fn lock_voting_should_work_with_delegation() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 0 + ); + 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); + }); +} diff --git a/frame/democracy/src/types.rs b/frame/democracy/src/types.rs new file mode 100644 index 0000000000000..016b872f30b35 --- /dev/null +++ b/frame/democracy/src/types.rs @@ -0,0 +1,64 @@ +// Copyright 2017-2020 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 . + +//! Miscellaneous additional datatypes. + +use codec::{Encode, Decode}; +use sp_runtime::RuntimeDebug; +use crate::vote_threshold::VoteThreshold; + +/// Info regarding an ongoing referendum. +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)] +pub struct ReferendumInfo { + /// When voting on this referendum will end. + pub (crate) end: BlockNumber, + /// The hash of the proposal being voted on. + pub (crate) proposal_hash: Hash, + /// The thresholding mechanism to determine whether it passed. + pub (crate) threshold: VoteThreshold, + /// The delay (in blocks) to wait after a successful referendum before deploying. + pub (crate) delay: BlockNumber, +} + +impl ReferendumInfo { + /// Create a new instance. + pub fn new( + end: BlockNumber, + proposal_hash: Hash, + threshold: VoteThreshold, + delay: BlockNumber + ) -> Self { + ReferendumInfo { end, proposal_hash, threshold, delay } + } +} + +/// State of a proxy voting account. +#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug)] +pub enum ProxyState { + /// Account is open to becoming a proxy but is not yet assigned. + Open(AccountId), + /// Account is actively being a proxy. + Active(AccountId), +} + +impl ProxyState { + pub (crate) fn as_active(self) -> Option { + match self { + ProxyState::Active(a) => Some(a), + ProxyState::Open(_) => None, + } + } +} diff --git a/frame/democracy/src/vote.rs b/frame/democracy/src/vote.rs new file mode 100644 index 0000000000000..a66ac68b3e2eb --- /dev/null +++ b/frame/democracy/src/vote.rs @@ -0,0 +1,48 @@ +// Copyright 2017-2020 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 . + +//! The vote datatype. + +use sp_std::{result::Result, convert::TryFrom}; +use codec::{Encode, EncodeLike, Decode, Output, Input}; +use sp_runtime::RuntimeDebug; +use crate::conviction::Conviction; + +/// A number of lock periods, plus a vote, one way or the other. +#[derive(Copy, Clone, Eq, PartialEq, Default, RuntimeDebug)] +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 EncodeLike for Vote {} + +impl Decode for Vote { + fn decode(input: &mut I) -> Result { + let b = input.read_byte()?; + Ok(Vote { + aye: (b & 0b1000_0000) == 0b1000_0000, + conviction: Conviction::try_from(b & 0b0111_1111) + .map_err(|_| codec::Error::from("Invalid conviction"))?, + }) + } +} From b7434abff66e855374e13cfae470206f79e3be0e Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 18 Mar 2020 19:01:10 +0100 Subject: [PATCH 02/22] Basic logic is drafted --- frame/democracy/src/lib.rs | 303 ++++++++++++++++------------------- frame/democracy/src/types.rs | 94 ++++++++++- 2 files changed, 231 insertions(+), 166 deletions(-) diff --git a/frame/democracy/src/lib.rs b/frame/democracy/src/lib.rs index 5316b89915f29..6913ea29f1bac 100644 --- a/frame/democracy/src/lib.rs +++ b/frame/democracy/src/lib.rs @@ -174,7 +174,7 @@ mod types; pub use vote_threshold::{Approved, VoteThreshold}; pub use vote::Vote; pub use conviction::Conviction; -pub use types::{ReferendumInfo, ProxyState}; +pub use types::{ReferendumInfo, ReferendumStatus, ProxyState, Tally}; #[cfg(test)] mod tests; @@ -276,11 +276,12 @@ decl_storage! { /// Information concerning any given referendum. pub ReferendumInfoOf get(fn referendum_info): map hasher(twox_64_concat) ReferendumIndex - => Option>; + => Option>>; /// Queue of successful referenda to be dispatched. Stored ordered by block number. pub DispatchQueue get(fn dispatch_queue): Vec<(T::BlockNumber, T::Hash, ReferendumIndex)>; /// Get the voters for the current proposal. + /// DEPRECATED pub VotersFor get(fn voters_for): map hasher(twox_64_concat) ReferendumIndex => Vec; @@ -288,8 +289,13 @@ decl_storage! { /// 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::contains_key` first. + /// DEPRECATED pub VoteOf get(fn vote_of): map hasher(twox_64_concat) (ReferendumIndex, T::AccountId) => Vote; + /// All votes for a particular voter. We store the balance for the number of votes that we + /// have recorded. + pub VotesOf: map hasher(twox_64_concat) T::AccountId => Vec<(ReferendumIndex, Vote, BalanceOf)>; + /// Who is able to vote for whom. Value is the fund-holding account, key is the /// vote-transaction-sending account. pub Proxy get(fn proxy): map hasher(twox_64_concat) T::AccountId => Option>; @@ -421,7 +427,11 @@ decl_error! { /// A proxy-pairing was attempted to an account that was open to another account. WrongOpen, /// A proxy-de-pairing was attempted to an account that was not active. - NotActive + NotActive, + /// The given account did not vote on the referendum, + NotVoter, + /// The actor has no permission to conduct the action. + NoPermission, } } @@ -1031,21 +1041,43 @@ decl_module! { /// /// - `target`: The account to remove the lock on. /// - /// Emits `Unlocked`. - /// /// # /// - `O(1)`. /// # #[weight = SimpleDispatchInfo::FixedNormal(10_000)] fn unlock(origin, target: T::AccountId) { ensure_signed(origin)?; + Self::do_unlock(&target); + } - let expiry = Locks::::get(&target).ok_or(Error::::NotLocked)?; - ensure!(expiry <= system::Module::::block_number(), Error::::NotExpired); + /// Unvote tokens that have an expired lock. + /// + /// The dispatch origin of this call must be _Signed_. + /// + /// - `target`: The account to remove the lock on. + /// + /// # + /// - `O(1)`. + /// # + #[weight = SimpleDispatchInfo::FixedNormal(10_000)] + fn unlock(origin, index: RefIndex) { + ensure_signed(origin)?; + Self::do_unvote(&target, index, true); + } - T::Currency::remove_lock(DEMOCRACY_ID, &target); - Locks::::remove(&target); - Self::deposit_event(RawEvent::Unlocked(target)); + /// Unvote tokens that have an expired lock. + /// + /// The dispatch origin of this call must be _Signed_. + /// + /// - `target`: The account to remove the lock on. + /// + /// # + /// - `O(1)`. + /// # + #[weight = SimpleDispatchInfo::FixedNormal(10_000)] + fn unlock_other(origin, target: T::AccountId, index: RefIndex) { + ensure_signed(origin)?; + Self::do_unvote(&target, index, false); } /// Become a proxy. @@ -1074,116 +1106,33 @@ decl_module! { } } +// TODO: migrate referenda to new format. + 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> { + pub fn backing_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 { - >::contains_key(ref_index) - } - - /// Get all referenda currently active. - pub fn active_referenda() - -> Vec<(ReferendumIndex, ReferendumInfo)> - { - let next = Self::lowest_unbaked(); - 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)> { + ) -> Vec<(ReferendumIndex, ReferendumStatus>)> { let next = Self::lowest_unbaked(); let last = Self::referendum_count(); (next..last).into_iter() - .filter_map(|i| Self::referendum_info(i).map(|info| (i, info))) - .filter(|&(_, ref info)| info.end == n) + .map(|i| (i, Self::referendum_info(i))) + .filter_map(|(i, info)| match info { + ReferendumInfo::Ongoing(status) => Some((i, status)), + _ => None, + }) + .filter(|(i, status)| status.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()); } - >::iter() - .filter(|(delegator, (delegate, _))| - *delegate == to && !>::contains_key(&(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")] @@ -1218,16 +1167,90 @@ impl Module { // private. + /// Ok if the given referendum is active, Err otherwise + fn ensure_active(r: ReferendumInfo>) + -> Result>, DispatchError> + { + match r { + ReferendumInfo::Ongoing(s) => Ok(s), + _ => Err(Error::::ReferendumInvalid), + } + } + /// Actually enact a vote, if legit. fn do_vote(who: T::AccountId, ref_index: ReferendumIndex, vote: Vote) -> DispatchResult { - ensure!(Self::is_active_referendum(ref_index), Error::::ReferendumInvalid); - if !>::contains_key((ref_index, &who)) { - >::append_or_insert(ref_index, &[&who][..]); - } - >::insert((ref_index, &who), vote); + let info = ReferendumInfoOf::::get(ref_index).ok_or(Error::::ReferendumInvalid)?; + let mut status = ensure_active(info)?; + let balance = T::Currency::free_balance(&who); + VotesOf::::try_mutate(who, |votes| { + match votes.binary_search_by_key(|i| i.0, ref_index) { + Ok(i) => { + // Shouldn't be possible to fail, but we handle it gracefully. + info.tally.remove(votes[i].1, votes[i].2).ok_or(Error::::Underflow)?; + votes[i].1 = vote; + votes[i].2 = balance; + } + Err(i) => votes.insert(i, Tally::new(vote, balance)), + } + Ok(()) + }); + // Shouldn't be possible to fail, but we handle it gracefully. + info.tally.add(vote, balance).ok_or(Error::::Overflow)?; + // Extend the lock to `balance` (rather than setting it) since we don't know what other + // votes are in place. + T::Currency::extend_lock( + DEMOCRACY_ID, + &a, + balance, + WithdrawReason::Transfer.into() + ); + ReferendumInfoOf::::put(ref_index, ReferendumInfo::Ongoing(status)); + Ok(()) + } + + /// Remove the account's vote for the given referendum if possible. This is possible when: + /// - The referendum has not finished. + /// - The referendum has finished and the voter lost their direction. + /// - The referendum has finished and the voter's lock period is up. + /// + /// This will generally be combined with a call to `unlock`. + fn do_unvote(who: &T::AccountId, ref_index: ReferendumIndex, allow_ongoing: bool) -> DispatchResult { + let mut info = ReferendumInfoOf::::get(ref_index); + VotesOf::::try_mutate(who, |votes| { + let i = votes.binary_search_by_key(|i| i.0, ref_index).map_err(|_| Error::::NotVoter)?; + match info { + Some(ReferendumInfo::Ongoing(ref mut status)) => { + ensure!(allow_ongoing, Error::::NoPermission); + // Shouldn't be possible to fail, but we handle it gracefully. + status.tally.remove(votes[i].1, votes[i].2).ok_or(Error::::Underflow)?; + } + Some(ReferendumInfo::Finished{end, approved}) => if votes[i].1.aye == approved { + // winning side: can only be removed after the lock period ends. + let lock_periods = votes[i].1.conviction.lock_periods(); + let unlock_at = end + T::EnactmentPeriod::get() * lock_periods; + let now = system::Module::::block_number(); + ensure!(now >= unlock_at, Error::::TooEarly); + }, + None => {} // Referendum was cancelled. + } + votes.remove(i); + Ok(()) + })?; + ReferendumInfoOf::::put(ref_index, info); Ok(()) } + /// Rejig the locks on an account. They will never get more stringent but may be reduced from + /// what they are currently. + fn do_unlock(who: &T::AccountId) -> DispatchResult { + let max = VotesOf::::get().into_iter().map(|i| i.2).fold(Zero::zero(), |a, i| a.max(i)); + if max.is_zero() { + T::Currency::remove_lock(DEMOCRACY_ID, &a); + } else { + T::Currency::set_lock(DEMOCRACY_ID, &a, max, WithdrawReason::Transfer.into()); + } + } + /// Start a referendum fn inject_referendum( end: T::BlockNumber, @@ -1237,29 +1260,13 @@ impl Module { ) -> ReferendumIndex { let ref_index = Self::referendum_count(); ReferendumCount::put(ref_index + 1); - let item = ReferendumInfo { end, proposal_hash, threshold, delay }; + let status = ReferendumStatus { end, proposal_hash, threshold, delay, tally: Default::default() }; + let item = ReferendumInfo::Ongoing(status); >::insert(ref_index, item); Self::deposit_event(RawEvent::Started(ref_index, threshold)); ref_index } - /// Remove all info on a referendum. - fn clear_referendum(ref_index: ReferendumIndex) { - >::remove(ref_index); - - LowestUnbaked::mutate(|i| if *i == ref_index { - *i += 1; - let end = ReferendumCount::get(); - while !Self::is_active_referendum(*i) && *i < end { - *i += 1; - } - }); - >::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_hash: T::Hash, index: ReferendumIndex) -> DispatchResult { if let Some((encoded_proposal, who, amount, _)) = >::take(&proposal_hash) { @@ -1313,7 +1320,7 @@ impl Module { 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) + .max_by_key(|x| Self::backing_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); @@ -1342,45 +1349,18 @@ impl Module { fn bake_referendum( now: T::BlockNumber, index: ReferendumIndex, - info: ReferendumInfo - ) -> DispatchResult { - let (approve, against, capital) = Self::tally(index); + status: ReferendumStatus>, + ) -> Result { + let (approve, against, capital) = status.tally; let total_issuance = T::Currency::total_issuance(); - let approved = info.threshold.approved(approve, against, capital, total_issuance); - let enactment_period = T::EnactmentPeriod::get(); - - // 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, lock_periods) 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 - .map(|(a, vote)| (a, vote.conviction.lock_periods())) - .filter(|&(_, lock_periods)| !lock_periods.is_zero()) // Just the lock votes - { - // now plus: the base lock period multiplied by the number of periods this voter - // offered to lock should they win... - let locked_until = now + enactment_period * lock_periods.into(); - Locks::::insert(&a, locked_until); - // ...extend their bondage until at least then. - T::Currency::extend_lock( - DEMOCRACY_ID, - &a, - Bounded::max_value(), - WithdrawReason::Transfer.into() - ); - } - - Self::clear_referendum(index); + let approved = status.threshold.approved(approve, against, capital, total_issuance); if approved { Self::deposit_event(RawEvent::Passed(index)); if info.delay.is_zero() { let _ = Self::enact_proposal(info.proposal_hash, index); } else { - let item = (now + info.delay,info.proposal_hash, index); + let item = (now + status.delay, status.proposal_hash, index); >::mutate(|queue| { let pos = queue.binary_search_by_key(&item.0, |x| x.0).unwrap_or_else(|e| e); queue.insert(pos, item); @@ -1390,7 +1370,7 @@ impl Module { Self::deposit_event(RawEvent::NotPassed(index)); } - Ok(()) + Ok(approved) } /// Current era is ending; we should finish up any proposals. @@ -1404,7 +1384,8 @@ impl Module { // tally up votes for any expiring referenda. for (index, info) in Self::maturing_referenda_at(now).into_iter() { - Self::bake_referendum(now, index, info)?; + let approved = Self::bake_referendum(now, index, info)?; + ReferendumInfoOf::::put(index, ReferendumInfo::Finished { end: now, approved }); } let queue = >::get(); diff --git a/frame/democracy/src/types.rs b/frame/democracy/src/types.rs index 016b872f30b35..848795735ace7 100644 --- a/frame/democracy/src/types.rs +++ b/frame/democracy/src/types.rs @@ -18,11 +18,69 @@ use codec::{Encode, Decode}; use sp_runtime::RuntimeDebug; -use crate::vote_threshold::VoteThreshold; +use sp_runtime::traits::{Zero, Bounded, CheckedAdd, CheckedSub, CheckedMul, CheckedDiv, AppendZerosInput}; +use crate::{Vote, VoteThreshold, Tally}; +/// Info regarding an ongoing referendum. +#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, RuntimeDebug)] +pub struct Tally { + /// The number of aye votes, expressed in terms of post-conviction lock-vote. + pub (crate) ayes: Balance, + /// The number of nay votes, expressed in terms of post-conviction lock-vote. + pub (crate) nays: Balance, + /// The amount of funds currently expressing its opinion. Pre-conviction. + pub (crate) turnout: Balance, +} + +impl< + Balance: From + Zero + Copy + CheckedAdd + CheckedSub + CheckedMul + CheckedDiv + Bounded +> Tally { + pub fn new( + vote: Vote, + balance: Balance, + ) -> Self { + let (votes, turnout) = vote.conviction.votes(balance); + Self { + ayes: if vote.aye { votes } else { Zero::zero() }, + nays: if vote.aye { Zero::zero() } else { votes }, + turnout, + } + } + + /// Increment some amount of votes. + pub fn try_add( + &mut self, + vote: Vote, + balance: Balance, + ) -> Option<()> { + let (votes, turnout) = vote.conviction.votes(balance); + self.turnout = self.turnout.checked_add(&turnout)?; + match vote.aye { + true => self.ayes = self.ayes.checked_add(&votes)?, + false => self.nays = self.nays.checked_add(&votes)?, + }; + Some(()) + } + + /// Decrement some amount of votes. + pub fn try_remove( + &mut self, + vote: Vote, + balance: Balance, + ) -> Option<()> { + let (votes, turnout) = vote.conviction.votes(balance); + self.turnout = self.turnout.checked_sub(&turnout)?; + match vote.aye { + true => self.ayes = self.ayes.checked_sub(&votes)?, + false => self.nays = self.nays.checked_sub(&votes)?, + }; + Some(()) + } +} +/* /// Info regarding an ongoing referendum. #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)] -pub struct ReferendumInfo { +pub struct OldReferendumInfo { /// When voting on this referendum will end. pub (crate) end: BlockNumber, /// The hash of the proposal being voted on. @@ -32,16 +90,42 @@ pub struct ReferendumInfo { /// The delay (in blocks) to wait after a successful referendum before deploying. pub (crate) delay: BlockNumber, } +*/ + +/// Info regarding an ongoing referendum. +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)] +pub struct ReferendumStatus { + /// When voting on this referendum will end. + pub (crate) end: BlockNumber, + /// The hash of the proposal being voted on. + pub (crate) proposal_hash: Hash, + /// The thresholding mechanism to determine whether it passed. + pub (crate) threshold: VoteThreshold, + /// The delay (in blocks) to wait after a successful referendum before deploying. + pub (crate) delay: BlockNumber, + /// The current tally of votes in this referendum. + pub (crate) tally: Tally, +} + +/// Info regarding a referendum, present or past. +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)] +pub enum ReferendumInfo { + /// Referendum is happening, the arg is the block number at which it will end. + Ongoing(ReferendumStatus), + /// Referendum finished at `end`, and has been `approved` or rejected. + Finished{approved: bool, end: BlockNumber}, +} -impl ReferendumInfo { +impl ReferendumInfo { /// Create a new instance. pub fn new( end: BlockNumber, proposal_hash: Hash, threshold: VoteThreshold, - delay: BlockNumber + delay: BlockNumber, ) -> Self { - ReferendumInfo { end, proposal_hash, threshold, delay } + let s = ReferendumStatus{ end, proposal_hash, threshold, delay, tally: Tally::default() }; + ReferendumInfo::Ongoing(s) } } From 3a2c9c087f6589706a5ae9a188b84f9743df8783 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 19 Mar 2020 12:41:21 +0100 Subject: [PATCH 03/22] Lazy democracy builds. --- frame/democracy/src/lib.rs | 155 +++++++++++++++----------- frame/democracy/src/types.rs | 10 +- frame/democracy/src/vote_threshold.rs | 17 +-- 3 files changed, 103 insertions(+), 79 deletions(-) diff --git a/frame/democracy/src/lib.rs b/frame/democracy/src/lib.rs index 6913ea29f1bac..5aeaf509bf39c 100644 --- a/frame/democracy/src/lib.rs +++ b/frame/democracy/src/lib.rs @@ -153,11 +153,11 @@ use sp_std::prelude::*; use sp_runtime::{ - DispatchResult, traits::{Zero, Bounded, EnsureOrigin, Hash, Dispatchable, Saturating}, + DispatchResult, DispatchError, traits::{Zero, Bounded, EnsureOrigin, Hash, Dispatchable, Saturating}, }; use codec::{Ref, Decode}; use frame_support::{ - decl_module, decl_storage, decl_event, decl_error, ensure, Parameter, IterableStorageMap, + decl_module, decl_storage, decl_event, decl_error, ensure, Parameter, weights::SimpleDispatchInfo, traits::{ Currency, ReservableCurrency, LockableCurrency, WithdrawReason, LockIdentifier, Get, @@ -187,8 +187,6 @@ pub type PropIndex = u32; /// A referendum index. pub type ReferendumIndex = u32; -const MAX_RECURSION_LIMIT: u32 = 16; - type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; type NegativeImbalanceOf = <::Currency as Currency<::AccountId>>::NegativeImbalance; @@ -407,7 +405,7 @@ decl_error! { /// Not imminent NotImminent, /// Too early - Early, + TooEarly, /// Imminent Imminent, /// Preimage not found @@ -428,10 +426,16 @@ decl_error! { WrongOpen, /// A proxy-de-pairing was attempted to an account that was not active. NotActive, - /// The given account did not vote on the referendum, + /// The given account did not vote on the referendum. NotVoter, /// The actor has no permission to conduct the action. NoPermission, + /// The account is already delegating. + AlreadyDelegating, + /// An unexpected integer overflow occurred. + Overflow, + /// An unexpected integer underflow occurred. + Underflow, } } @@ -569,7 +573,7 @@ decl_module! { vote: Vote ) -> DispatchResult { let who = ensure_signed(origin)?; - Self::do_vote(who, ref_index, vote) + 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 @@ -591,7 +595,7 @@ decl_module! { ) -> DispatchResult { let who = ensure_signed(origin)?; let voter = Self::proxy(who).and_then(|a| a.as_active()).ok_or(Error::::NotProxy)?; - Self::do_vote(voter, ref_index, vote) + Self::do_vote(&voter, ref_index, vote) } /// Schedule an emergency cancellation of a referendum. Cannot happen twice to the same @@ -608,12 +612,12 @@ decl_module! { fn emergency_cancel(origin, ref_index: ReferendumIndex) { T::CancellationOrigin::ensure_origin(origin)?; - let info = Self::referendum_info(ref_index).ok_or(Error::::BadIndex)?; - let h = info.proposal_hash; + let status = Self::referendum_status(ref_index)?; + let h = status.proposal_hash; ensure!(!>::contains_key(h), Error::::AlreadyCanceled); >::insert(h, true); - Self::clear_referendum(ref_index); + Self::internal_cancel_referendum(ref_index); } /// Schedule a referendum to be tabled once it is legal to schedule an external @@ -770,7 +774,7 @@ decl_module! { #[weight = SimpleDispatchInfo::FixedOperational(10_000)] fn cancel_referendum(origin, #[compact] ref_index: ReferendumIndex) { ensure_root(origin)?; - Self::clear_referendum(ref_index); + Self::internal_cancel_referendum(ref_index); } /// Cancel a proposal queued for enactment. @@ -877,17 +881,21 @@ decl_module! { /// /// - `to`: The account to make a delegate of the sender. /// - `conviction`: The conviction that will be attached to the delegated - /// votes. + /// votes. When the account is undelegated, the funds will be locked for the corresponding + /// number of lock periods. /// /// Emits `Delegated`. /// /// # - /// - 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)); + Delegations::::try_mutate_exists(&who, |d| -> DispatchResult { + ensure!(d.is_none(), Error::::AlreadyDelegating); + *d = Some((to.clone(), conviction)); + Ok(()) + })?; // Currency is locked indefinitely as long as it's delegated. T::Currency::extend_lock( DEMOCRACY_ID, @@ -1025,7 +1033,7 @@ decl_module! { let now = >::block_number(); let (voting, enactment) = (T::VotingPeriod::get(), T::EnactmentPeriod::get()); let additional = if who == old { Zero::zero() } else { enactment }; - ensure!(now >= then + voting + additional, Error::::Early); + ensure!(now >= then + voting + additional, Error::::TooEarly); let queue = >::get(); ensure!(!queue.iter().any(|item| &item.1 == &proposal_hash), Error::::Imminent); @@ -1047,37 +1055,42 @@ decl_module! { #[weight = SimpleDispatchInfo::FixedNormal(10_000)] fn unlock(origin, target: T::AccountId) { ensure_signed(origin)?; - Self::do_unlock(&target); + Self::update_lock(&target); } - /// Unvote tokens that have an expired lock. + /// Remove a vote for a referendum that has not yet ended, or one that has expired, either + /// because the voter lost the referendum or because the conviction period is over or + /// because it was cancelled. /// - /// The dispatch origin of this call must be _Signed_. + /// The dispatch origin of this call must be _Signed_. and the /// - /// - `target`: The account to remove the lock on. + /// - `index`: The index of referendum of the vote to be removed. /// /// # - /// - `O(1)`. + /// - `O(R + log R)` where R is the number of referenda that `target` has voted on. /// # #[weight = SimpleDispatchInfo::FixedNormal(10_000)] - fn unlock(origin, index: RefIndex) { - ensure_signed(origin)?; - Self::do_unvote(&target, index, true); + fn unvote(origin, index: ReferendumIndex) -> DispatchResult { + let who = ensure_signed(origin)?; + Self::cancel_vote(&who, index, UnvoteScope::Any) } - /// Unvote tokens that have an expired lock. + /// Remove a vote for a referendum that has expired, either because it was cancelled, + /// because the voter lost the referendum or because the conviction period is over. /// /// The dispatch origin of this call must be _Signed_. /// - /// - `target`: The account to remove the lock on. + /// - `target`: The account of the vote to be removed; this account must have voted for + /// referendum `index`. + /// - `index`: The index of referendum of the vote to be removed. /// /// # - /// - `O(1)`. + /// - `O(R + log R)` where R is the number of referenda that `target` has voted on. /// # #[weight = SimpleDispatchInfo::FixedNormal(10_000)] - fn unlock_other(origin, target: T::AccountId, index: RefIndex) { + fn reap_vote(origin, target: T::AccountId, index: ReferendumIndex) -> DispatchResult { ensure_signed(origin)?; - Self::do_unvote(&target, index, false); + Self::cancel_vote(&target, index, UnvoteScope::OnlyExpired) } /// Become a proxy. @@ -1108,6 +1121,11 @@ decl_module! { // TODO: migrate referenda to new format. +enum UnvoteScope { + Any, + OnlyExpired, +} + impl Module { // exposed immutables. @@ -1125,11 +1143,11 @@ impl Module { let last = Self::referendum_count(); (next..last).into_iter() .map(|i| (i, Self::referendum_info(i))) - .filter_map(|(i, info)| match info { - ReferendumInfo::Ongoing(status) => Some((i, status)), + .filter_map(|(i, maybe_info)| match maybe_info { + Some(ReferendumInfo::Ongoing(status)) => Some((i, status)), _ => None, }) - .filter(|(i, status)| status.end == n) + .filter(|(_, status)| status.end == n) .collect() } @@ -1162,49 +1180,56 @@ impl Module { /// Remove a referendum. pub fn internal_cancel_referendum(ref_index: ReferendumIndex) { Self::deposit_event(RawEvent::Cancelled(ref_index)); - >::clear_referendum(ref_index); + ReferendumInfoOf::::remove(ref_index); } // private. /// Ok if the given referendum is active, Err otherwise - fn ensure_active(r: ReferendumInfo>) + fn ensure_ongoing(r: ReferendumInfo>) -> Result>, DispatchError> { match r { ReferendumInfo::Ongoing(s) => Ok(s), - _ => Err(Error::::ReferendumInvalid), + _ => Err(Error::::ReferendumInvalid.into()), } } + fn referendum_status(ref_index: ReferendumIndex) + -> Result>, DispatchError> + { + let info = ReferendumInfoOf::::get(ref_index) + .ok_or(Error::::ReferendumInvalid)?; + Self::ensure_ongoing(info) + } + /// Actually enact a vote, if legit. - fn do_vote(who: T::AccountId, ref_index: ReferendumIndex, vote: Vote) -> DispatchResult { - let info = ReferendumInfoOf::::get(ref_index).ok_or(Error::::ReferendumInvalid)?; - let mut status = ensure_active(info)?; - let balance = T::Currency::free_balance(&who); - VotesOf::::try_mutate(who, |votes| { - match votes.binary_search_by_key(|i| i.0, ref_index) { + fn do_vote(who: &T::AccountId, ref_index: ReferendumIndex, vote: Vote) -> DispatchResult { + let mut status = Self::referendum_status(ref_index)?; + let balance = T::Currency::free_balance(who); + VotesOf::::try_mutate(who, |votes| -> DispatchResult { + match votes.binary_search_by_key(&ref_index, |i| i.0) { Ok(i) => { // Shouldn't be possible to fail, but we handle it gracefully. - info.tally.remove(votes[i].1, votes[i].2).ok_or(Error::::Underflow)?; + status.tally.remove(votes[i].1, votes[i].2).ok_or(Error::::Underflow)?; votes[i].1 = vote; votes[i].2 = balance; } - Err(i) => votes.insert(i, Tally::new(vote, balance)), + Err(i) => votes.insert(i, (ref_index, vote, balance)), } + // Shouldn't be possible to fail, but we handle it gracefully. + status.tally.add(vote, balance).ok_or(Error::::Overflow)?; Ok(()) - }); - // Shouldn't be possible to fail, but we handle it gracefully. - info.tally.add(vote, balance).ok_or(Error::::Overflow)?; + })?; // Extend the lock to `balance` (rather than setting it) since we don't know what other // votes are in place. T::Currency::extend_lock( DEMOCRACY_ID, - &a, + who, balance, WithdrawReason::Transfer.into() ); - ReferendumInfoOf::::put(ref_index, ReferendumInfo::Ongoing(status)); + ReferendumInfoOf::::insert(ref_index, ReferendumInfo::Ongoing(status)); Ok(()) } @@ -1214,20 +1239,21 @@ impl Module { /// - The referendum has finished and the voter's lock period is up. /// /// This will generally be combined with a call to `unlock`. - fn do_unvote(who: &T::AccountId, ref_index: ReferendumIndex, allow_ongoing: bool) -> DispatchResult { - let mut info = ReferendumInfoOf::::get(ref_index); - VotesOf::::try_mutate(who, |votes| { - let i = votes.binary_search_by_key(|i| i.0, ref_index).map_err(|_| Error::::NotVoter)?; + fn cancel_vote(who: &T::AccountId, ref_index: ReferendumIndex, scope: UnvoteScope) -> DispatchResult { + let info = ReferendumInfoOf::::get(ref_index); + VotesOf::::try_mutate(who, |votes| -> DispatchResult { + let i = votes.binary_search_by_key(&ref_index, |i| i.0).map_err(|_| Error::::NotVoter)?; match info { - Some(ReferendumInfo::Ongoing(ref mut status)) => { - ensure!(allow_ongoing, Error::::NoPermission); + Some(ReferendumInfo::Ongoing(mut status)) => { + ensure!(matches!(scope, UnvoteScope::Any), Error::::NoPermission); // Shouldn't be possible to fail, but we handle it gracefully. status.tally.remove(votes[i].1, votes[i].2).ok_or(Error::::Underflow)?; + ReferendumInfoOf::::insert(ref_index, ReferendumInfo::Ongoing(status)); } Some(ReferendumInfo::Finished{end, approved}) => if votes[i].1.aye == approved { // winning side: can only be removed after the lock period ends. let lock_periods = votes[i].1.conviction.lock_periods(); - let unlock_at = end + T::EnactmentPeriod::get() * lock_periods; + let unlock_at = end + T::EnactmentPeriod::get() * lock_periods.into(); let now = system::Module::::block_number(); ensure!(now >= unlock_at, Error::::TooEarly); }, @@ -1236,18 +1262,17 @@ impl Module { votes.remove(i); Ok(()) })?; - ReferendumInfoOf::::put(ref_index, info); Ok(()) } /// Rejig the locks on an account. They will never get more stringent but may be reduced from /// what they are currently. - fn do_unlock(who: &T::AccountId) -> DispatchResult { - let max = VotesOf::::get().into_iter().map(|i| i.2).fold(Zero::zero(), |a, i| a.max(i)); + fn update_lock(who: &T::AccountId) { + let max = VotesOf::::get(who).into_iter().map(|i| i.2).fold(BalanceOf::::zero(), |a, i| a.max(i)); if max.is_zero() { - T::Currency::remove_lock(DEMOCRACY_ID, &a); + T::Currency::remove_lock(DEMOCRACY_ID, who); } else { - T::Currency::set_lock(DEMOCRACY_ID, &a, max, WithdrawReason::Transfer.into()); + T::Currency::set_lock(DEMOCRACY_ID, who, max, WithdrawReason::Transfer.into()); } } @@ -1343,7 +1368,6 @@ impl Module { } else { Err(Error::::NoneWaiting)? } - } fn bake_referendum( @@ -1351,14 +1375,13 @@ impl Module { index: ReferendumIndex, status: ReferendumStatus>, ) -> Result { - let (approve, against, capital) = status.tally; let total_issuance = T::Currency::total_issuance(); - let approved = status.threshold.approved(approve, against, capital, total_issuance); + let approved = status.threshold.approved(status.tally, total_issuance); if approved { Self::deposit_event(RawEvent::Passed(index)); - if info.delay.is_zero() { - let _ = Self::enact_proposal(info.proposal_hash, index); + if status.delay.is_zero() { + let _ = Self::enact_proposal(status.proposal_hash, index); } else { let item = (now + status.delay, status.proposal_hash, index); >::mutate(|queue| { @@ -1385,7 +1408,7 @@ impl Module { // tally up votes for any expiring referenda. for (index, info) in Self::maturing_referenda_at(now).into_iter() { let approved = Self::bake_referendum(now, index, info)?; - ReferendumInfoOf::::put(index, ReferendumInfo::Finished { end: now, approved }); + ReferendumInfoOf::::insert(index, ReferendumInfo::Finished { end: now, approved }); } let queue = >::get(); diff --git a/frame/democracy/src/types.rs b/frame/democracy/src/types.rs index 848795735ace7..5d11a2da3d37a 100644 --- a/frame/democracy/src/types.rs +++ b/frame/democracy/src/types.rs @@ -18,8 +18,8 @@ use codec::{Encode, Decode}; use sp_runtime::RuntimeDebug; -use sp_runtime::traits::{Zero, Bounded, CheckedAdd, CheckedSub, CheckedMul, CheckedDiv, AppendZerosInput}; -use crate::{Vote, VoteThreshold, Tally}; +use sp_runtime::traits::{Zero, Bounded, CheckedAdd, CheckedSub, CheckedMul, CheckedDiv}; +use crate::{Vote, VoteThreshold}; /// Info regarding an ongoing referendum. #[derive(Encode, Decode, Default, Clone, PartialEq, Eq, RuntimeDebug)] @@ -48,7 +48,7 @@ impl< } /// Increment some amount of votes. - pub fn try_add( + pub fn add( &mut self, vote: Vote, balance: Balance, @@ -63,7 +63,7 @@ impl< } /// Decrement some amount of votes. - pub fn try_remove( + pub fn remove( &mut self, vote: Vote, balance: Balance, @@ -116,7 +116,7 @@ pub enum ReferendumInfo { Finished{approved: bool, end: BlockNumber}, } -impl ReferendumInfo { +impl ReferendumInfo { /// Create a new instance. pub fn new( end: BlockNumber, diff --git a/frame/democracy/src/vote_threshold.rs b/frame/democracy/src/vote_threshold.rs index 46612af09abc5..135971bcd8250 100644 --- a/frame/democracy/src/vote_threshold.rs +++ b/frame/democracy/src/vote_threshold.rs @@ -21,6 +21,7 @@ use serde::{Serialize, Deserialize}; use codec::{Encode, Decode}; use sp_runtime::traits::{Zero, IntegerSquareRoot}; use sp_std::ops::{Add, Mul, Div, Rem}; +use crate::Tally; /// A means of determining if a vote is past pass threshold. #[derive(Clone, Copy, PartialEq, Eq, Encode, Decode, sp_runtime::RuntimeDebug)] @@ -38,7 +39,7 @@ pub trait Approved { /// Given `approve` votes for and `against` votes against from a total electorate size of /// `electorate` (`electorate - (approve + against)` are abstainers), then returns true if the /// overall outcome is in favor of approval. - fn approved(&self, approve: Balance, against: Balance, voters: Balance, electorate: Balance) -> bool; + fn approved(&self, tally: Tally, electorate: Balance) -> bool; } /// Return `true` iff `n1 / d1 < n2 / d2`. `d1` and `d2` may not be zero. @@ -76,16 +77,16 @@ impl + /// /// We assume each *voter* may cast more than one *vote*, hence `voters` is not necessarily equal to /// `approve + against`. - fn approved(&self, approve: Balance, against: Balance, voters: Balance, electorate: Balance) -> bool { - let sqrt_voters = voters.integer_sqrt(); + fn approved(&self, tally: Tally, electorate: Balance) -> bool { + let sqrt_voters = tally.turnout.integer_sqrt(); let sqrt_electorate = electorate.integer_sqrt(); if sqrt_voters.is_zero() { return false; } match *self { VoteThreshold::SuperMajorityApprove => - compare_rationals(against, sqrt_voters, approve, sqrt_electorate), + compare_rationals(tally.nays, sqrt_voters, tally.ayes, sqrt_electorate), VoteThreshold::SuperMajorityAgainst => - compare_rationals(against, sqrt_electorate, approve, sqrt_voters), - VoteThreshold::SimpleMajority => approve > against, + compare_rationals(tally.nays, sqrt_electorate, tally.ayes, sqrt_voters), + VoteThreshold::SimpleMajority => tally.ayes > tally.nays, } } } @@ -96,7 +97,7 @@ mod tests { #[test] fn should_work() { - assert_eq!(VoteThreshold::SuperMajorityApprove.approved(60, 50, 110, 210), false); - assert_eq!(VoteThreshold::SuperMajorityApprove.approved(100, 50, 150, 210), true); + assert!(!VoteThreshold::SuperMajorityApprove.approved(Tally{ayes: 60, nays: 50, turnout: 110}, 210)); + assert!(VoteThreshold::SuperMajorityApprove.approved(Tally{ayes: 100, nays: 50, turnout: 150}, 210)); } } From fd4144c8115a37ae5130a28b6f4f2cf8be56bd62 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 19 Mar 2020 14:24:21 +0100 Subject: [PATCH 04/22] Add non-locked split-voting and instant-scheduling. --- bin/node/runtime/src/lib.rs | 4 +- frame/democracy/src/lib.rs | 178 ++++++++++++++++++++--------------- frame/democracy/src/tests.rs | 4 +- frame/democracy/src/types.rs | 54 +++++++---- frame/democracy/src/vote.rs | 33 ++++++- 5 files changed, 173 insertions(+), 100 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 5468ee0fb32df..358cdd8d67b82 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -303,7 +303,7 @@ impl pallet_staking::Trait for Runtime { parameter_types! { pub const LaunchPeriod: BlockNumber = 28 * 24 * 60 * MINUTES; pub const VotingPeriod: BlockNumber = 28 * 24 * 60 * MINUTES; - pub const EmergencyVotingPeriod: BlockNumber = 3 * 24 * 60 * MINUTES; + pub const FastTrackVotingPeriod: BlockNumber = 3 * 24 * 60 * MINUTES; pub const MinimumDeposit: Balance = 100 * DOLLARS; pub const EnactmentPeriod: BlockNumber = 30 * 24 * 60 * MINUTES; pub const CooloffPeriod: BlockNumber = 28 * 24 * 60 * MINUTES; @@ -329,7 +329,7 @@ impl pallet_democracy::Trait for Runtime { /// Two thirds of the technical committee can have an ExternalMajority/ExternalDefault vote /// be tabled immediately and with a shorter voting/enactment period. type FastTrackOrigin = pallet_collective::EnsureProportionAtLeast<_2, _3, AccountId, TechnicalCollective>; - type EmergencyVotingPeriod = EmergencyVotingPeriod; + type FastTrackVotingPeriod = FastTrackVotingPeriod; // To cancel a proposal which has been passed, 2/3 of the council must agree to it. type CancellationOrigin = pallet_collective::EnsureProportionAtLeast<_2, _3, AccountId, CouncilCollective>; // Any single technical committee member may veto a coming council proposal, however they can diff --git a/frame/democracy/src/lib.rs b/frame/democracy/src/lib.rs index 5aeaf509bf39c..630ef9e77e355 100644 --- a/frame/democracy/src/lib.rs +++ b/frame/democracy/src/lib.rs @@ -51,9 +51,10 @@ //! account or an external origin) suggests that the system adopt. //! - **Referendum:** A proposal that is in the process of being voted on for //! either acceptance or rejection as a change to the system. -//! - **Proxy:** An account that votes on behalf of a separate "Stash" account +//! - **Proxy:** An account that has full voting power on behalf of a separate "Stash" account //! that holds the funds. -//! - **Delegation:** The act of granting your voting power to the decisions of another account. +//! - **Delegation:** The act of granting your voting power to the decisions of another account for +//! up to a certain conviction. //! //! ### Adaptive Quorum Biasing //! @@ -99,6 +100,8 @@ //! - `reap_preimage` - Removes the preimage for an expired proposal. Will only //! work under the condition that it's the same account that noted it and //! after the voting period, OR it's a different account after the enactment period. +//! - `unvote` - Cancel a previous vote, this must be done by the voter before the vote ends. +//! - `reap_vote` - Cancel some account's expired votes. //! - `unlock` - Unlocks tokens that have an expired lock. //! //! #### Cancellation Origin @@ -172,7 +175,7 @@ mod vote; mod conviction; mod types; pub use vote_threshold::{Approved, VoteThreshold}; -pub use vote::Vote; +pub use vote::{Vote, AccountVote}; pub use conviction::Conviction; pub use types::{ReferendumInfo, ReferendumStatus, ProxyState, Tally}; @@ -227,13 +230,23 @@ pub trait Trait: frame_system::Trait + Sized { /// 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. + /// Origin from which the next majority-carries (or more permissive) referendum may be tabled to + /// vote according to the `FastTrackVotingPeriod` asynchronously in a similar manner to the + /// emergency origin. It retains its threshold method. type FastTrackOrigin: EnsureOrigin; - /// Minimum voting period allowed for an fast-track/emergency referendum. - type EmergencyVotingPeriod: Get; + /// Origin from which the next majority-carries (or more permissive) referendum may be tabled to + /// vote immediately and asynchronously in a similar manner to the emergency origin. It retains + /// its threshold method. + type InstantOrigin: EnsureOrigin; + + /// Indicator for whether an emergency origin is even allowed to happen. Some chains may want + /// to set this permanently to `false`, others may want to condition it on things such as + /// an upgrade having happened recently. + type InstantAllowed: Get; + + /// Minimum voting period allowed for a fast-track referendum. + type FastTrackVotingPeriod: Get; /// Origin from which any referendum may be cancelled in an emergency. type CancellationOrigin: EnsureOrigin; @@ -253,52 +266,46 @@ pub trait Trait: frame_system::Trait + Sized { decl_storage! { trait Store for Module as Democracy { + // TODO: Refactor public proposal queue into its own pallet. /// The number of (public) proposals that have been made so far. pub PublicPropCount get(fn public_prop_count) build(|_| 0 as PropIndex) : PropIndex; /// The public proposals. Unsorted. The second item is the proposal's hash. pub PublicProps get(fn public_props): Vec<(PropIndex, T::Hash, T::AccountId)>; + /// Those who have locked a deposit. + pub DepositOf get(fn deposit_of): + map hasher(twox_64_concat) PropIndex => Option<(BalanceOf, Vec)>; + /// Map of hashes to the proposal preimage, along with who registered it and their deposit. /// The block number is the block at which it was deposited. + // TODO: Refactor Preimages into its own pallet. pub Preimages: map hasher(identity) T::Hash => Option<(Vec, T::AccountId, BalanceOf, T::BlockNumber)>; - /// Those who have locked a deposit. - pub DepositOf get(fn deposit_of): - map hasher(twox_64_concat) PropIndex => Option<(BalanceOf, Vec)>; /// The next free referendum index, aka the number of referenda started so far. pub ReferendumCount get(fn referendum_count) build(|_| 0 as ReferendumIndex): ReferendumIndex; /// The lowest referendum index representing an unbaked referendum. Equal to /// `ReferendumCount` if there isn't a unbaked referendum. pub LowestUnbaked get(fn lowest_unbaked) build(|_| 0 as ReferendumIndex): ReferendumIndex; + /// Information concerning any given referendum. pub ReferendumInfoOf get(fn referendum_info): map hasher(twox_64_concat) ReferendumIndex => Option>>; + + // TODO: Refactor DispatchQueue into its own module. /// Queue of successful referenda to be dispatched. Stored ordered by block number. pub DispatchQueue get(fn dispatch_queue): Vec<(T::BlockNumber, T::Hash, ReferendumIndex)>; - /// Get the voters for the current proposal. - /// DEPRECATED - pub VotersFor get(fn voters_for): - map hasher(twox_64_concat) 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::contains_key` first. - /// DEPRECATED - pub VoteOf get(fn vote_of): map hasher(twox_64_concat) (ReferendumIndex, T::AccountId) => Vote; - /// All votes for a particular voter. We store the balance for the number of votes that we /// have recorded. - pub VotesOf: map hasher(twox_64_concat) T::AccountId => Vec<(ReferendumIndex, Vote, BalanceOf)>; + pub VotesOf: map hasher(twox_64_concat) T::AccountId => Vec<(ReferendumIndex, AccountVote>)>; /// Who is able to vote for whom. Value is the fund-holding account, key is the /// vote-transaction-sending account. pub Proxy get(fn proxy): map hasher(twox_64_concat) T::AccountId => Option>; - /// Get the account (and lock periods) to which another account is delegating vote. + /// Get the account (and conviction) to which another account is delegating vote. pub Delegations get(fn delegations): map hasher(twox_64_concat) T::AccountId => (T::AccountId, Conviction); @@ -436,6 +443,8 @@ decl_error! { Overflow, /// An unexpected integer underflow occurred. Underflow, + /// Too high a balance was provided that the account cannot afford. + TooMuch, } } @@ -444,9 +453,6 @@ impl MigrateAccount for Module { Proxy::::migrate_key_from_blake(a); Locks::::migrate_key_from_blake(a); Delegations::::migrate_key_from_blake(a); - for i in LowestUnbaked::get()..ReferendumCount::get() { - VoteOf::::migrate_key_from_blake((i, a)); - } } } @@ -456,7 +462,6 @@ mod migration { Blacklist::::remove_all(); Cancellations::::remove_all(); for i in LowestUnbaked::get()..ReferendumCount::get() { - VotersFor::::migrate_key_from_blake(i); ReferendumInfoOf::::migrate_key_from_blake(i); } for (p, h, _) in PublicProps::::get().into_iter() { @@ -491,7 +496,7 @@ decl_module! { const MinimumDeposit: BalanceOf = T::MinimumDeposit::get(); /// Minimum voting period allowed for an emergency referendum. - const EmergencyVotingPeriod: T::BlockNumber = T::EmergencyVotingPeriod::get(); + const FastTrackVotingPeriod: T::BlockNumber = T::FastTrackVotingPeriod::get(); /// Period in blocks where an external proposal may not be re-submitted after being vetoed. const CooloffPeriod: T::BlockNumber = T::CooloffPeriod::get(); @@ -562,6 +567,7 @@ decl_module! { /// /// - `ref_index`: The index of the referendum to vote for. /// - `vote`: The vote configuration. + /// - `balance`: The amount of balance to /// /// # /// - `O(1)`. @@ -570,10 +576,10 @@ decl_module! { #[weight = SimpleDispatchInfo::FixedNormal(200_000)] fn vote(origin, #[compact] ref_index: ReferendumIndex, - vote: Vote + vote: AccountVote>, ) -> DispatchResult { let who = ensure_signed(origin)?; - Self::do_vote(&who, ref_index, vote) + Self::try_vote(&who, ref_index, vote) } /// Vote in a referendum on behalf of a stash. If `vote.is_aye()`, the vote is to enact @@ -591,11 +597,11 @@ decl_module! { #[weight = SimpleDispatchInfo::FixedNormal(200_000)] fn proxy_vote(origin, #[compact] ref_index: ReferendumIndex, - vote: Vote + vote: AccountVote>, ) -> DispatchResult { let who = ensure_signed(origin)?; let voter = Self::proxy(who).and_then(|a| a.as_active()).ok_or(Error::::NotProxy)?; - Self::do_vote(&voter, ref_index, vote) + Self::try_vote(&voter, ref_index, vote) } /// Schedule an emergency cancellation of a referendum. Cannot happen twice to the same @@ -692,7 +698,7 @@ decl_module! { /// /// - `proposal_hash`: The hash of the current external proposal. /// - `voting_period`: The period that is allowed for voting on this proposal. Increased to - /// `EmergencyVotingPeriod` if too low. + /// `FastTrackVotingPeriod` if too low. /// - `delay`: The number of block after voting has ended in approval and this should be /// enacted. This doesn't have a minimum amount. /// @@ -709,8 +715,24 @@ decl_module! { voting_period: T::BlockNumber, delay: T::BlockNumber ) { - T::FastTrackOrigin::ensure_origin(origin)?; - let (e_proposal_hash, threshold) = >::get().ok_or(Error::::ProposalMissing)?; + // Rather complicated bit of code to ensure that either: + // - `voting_period` is at least `FastTrackVotingPeriod` and `origin` is `FastTrackOrigin`; or + // - `InstantAllowed` is `true` and `origin` is `InstantOrigin`. + if let Some(ensure_instant) = if voting_period < T::FastTrackVotingPeriod::get() { + Some(origin) + } else { + if let Err(origin) = T::FastTrackOrigin::try_origin(origin) { + Some(origin) + } else { + None + } + } { + ensure!(T::InstantAllowed::get(), Error::::TooEarly); + T::InstantOrigin::ensure_origin(ensure_instant)?; + } + + let (e_proposal_hash, threshold) = >::get() + .ok_or(Error::::ProposalMissing)?; ensure!( threshold != VoteThreshold::SuperMajorityApprove, Error::::NotSimpleMajority, @@ -720,7 +742,7 @@ decl_module! { >::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()); + let period = voting_period.max(T::FastTrackVotingPeriod::get()); Self::inject_referendum(now + period, proposal_hash, threshold, delay); } @@ -1058,6 +1080,30 @@ decl_module! { Self::update_lock(&target); } + /// Become a proxy. + /// + /// This must be called prior to a later `activate_proxy`. + /// + /// Origin must be a Signed. + /// + /// - `target`: The account whose votes will later be proxied. + /// + /// `close_proxy` must be called before the account can be destroyed. + /// + /// # + /// - One extra DB entry. + /// # + #[weight = SimpleDispatchInfo::FixedNormal(100_000)] + fn open_proxy(origin, target: T::AccountId) { + let who = ensure_signed(origin)?; + Proxy::::mutate(&who, |a| { + if a.is_none() { + system::Module::::inc_ref(&who); + } + *a = Some(ProxyState::Open(target)); + }); + } + /// Remove a vote for a referendum that has not yet ended, or one that has expired, either /// because the voter lost the referendum or because the conviction period is over or /// because it was cancelled. @@ -1092,30 +1138,6 @@ decl_module! { ensure_signed(origin)?; Self::cancel_vote(&target, index, UnvoteScope::OnlyExpired) } - - /// Become a proxy. - /// - /// This must be called prior to a later `activate_proxy`. - /// - /// Origin must be a Signed. - /// - /// - `target`: The account whose votes will later be proxied. - /// - /// `close_proxy` must be called before the account can be destroyed. - /// - /// # - /// - One extra DB entry. - /// # - #[weight = SimpleDispatchInfo::FixedNormal(100_000)] - fn open_proxy(origin, target: T::AccountId) { - let who = ensure_signed(origin)?; - Proxy::::mutate(&who, |a| { - if a.is_none() { - system::Module::::inc_ref(&who); - } - *a = Some(ProxyState::Open(target)); - }); - } } } @@ -1204,21 +1226,20 @@ impl Module { } /// Actually enact a vote, if legit. - fn do_vote(who: &T::AccountId, ref_index: ReferendumIndex, vote: Vote) -> DispatchResult { + fn try_vote(who: &T::AccountId, ref_index: ReferendumIndex, vote: AccountVote>) -> DispatchResult { let mut status = Self::referendum_status(ref_index)?; - let balance = T::Currency::free_balance(who); + ensure!(vote.balance() <= T::Currency::free_balance(who), Error::::TooMuch); VotesOf::::try_mutate(who, |votes| -> DispatchResult { match votes.binary_search_by_key(&ref_index, |i| i.0) { Ok(i) => { // Shouldn't be possible to fail, but we handle it gracefully. - status.tally.remove(votes[i].1, votes[i].2).ok_or(Error::::Underflow)?; + status.tally.remove(votes[i].1).ok_or(Error::::Underflow)?; votes[i].1 = vote; - votes[i].2 = balance; } - Err(i) => votes.insert(i, (ref_index, vote, balance)), + Err(i) => votes.insert(i, (ref_index, vote)), } // Shouldn't be possible to fail, but we handle it gracefully. - status.tally.add(vote, balance).ok_or(Error::::Overflow)?; + status.tally.add(vote).ok_or(Error::::Overflow)?; Ok(()) })?; // Extend the lock to `balance` (rather than setting it) since we don't know what other @@ -1226,7 +1247,7 @@ impl Module { T::Currency::extend_lock( DEMOCRACY_ID, who, - balance, + vote.balance(), WithdrawReason::Transfer.into() ); ReferendumInfoOf::::insert(ref_index, ReferendumInfo::Ongoing(status)); @@ -1247,16 +1268,15 @@ impl Module { Some(ReferendumInfo::Ongoing(mut status)) => { ensure!(matches!(scope, UnvoteScope::Any), Error::::NoPermission); // Shouldn't be possible to fail, but we handle it gracefully. - status.tally.remove(votes[i].1, votes[i].2).ok_or(Error::::Underflow)?; + status.tally.remove(votes[i].1).ok_or(Error::::Underflow)?; ReferendumInfoOf::::insert(ref_index, ReferendumInfo::Ongoing(status)); } - Some(ReferendumInfo::Finished{end, approved}) => if votes[i].1.aye == approved { - // winning side: can only be removed after the lock period ends. - let lock_periods = votes[i].1.conviction.lock_periods(); - let unlock_at = end + T::EnactmentPeriod::get() * lock_periods.into(); - let now = system::Module::::block_number(); - ensure!(now >= unlock_at, Error::::TooEarly); - }, + Some(ReferendumInfo::Finished{end, approved}) => + if let Some(lock_periods) = votes[i].1.lock_periods(approved) { + let unlock_at = end + T::EnactmentPeriod::get() * lock_periods.into(); + let now = system::Module::::block_number(); + ensure!(now >= unlock_at, Error::::TooEarly); + }, None => {} // Referendum was cancelled. } votes.remove(i); @@ -1268,7 +1288,9 @@ impl Module { /// Rejig the locks on an account. They will never get more stringent but may be reduced from /// what they are currently. fn update_lock(who: &T::AccountId) { - let max = VotesOf::::get(who).into_iter().map(|i| i.2).fold(BalanceOf::::zero(), |a, i| a.max(i)); + let max = VotesOf::::get(who).into_iter() + .map(|i| i.1.balance()) + .fold(BalanceOf::::zero(), |a, i| a.max(i)); if max.is_zero() { T::Currency::remove_lock(DEMOCRACY_ID, who); } else { diff --git a/frame/democracy/src/tests.rs b/frame/democracy/src/tests.rs index 2fc9d1fe49812..5b94311547dc7 100644 --- a/frame/democracy/src/tests.rs +++ b/frame/democracy/src/tests.rs @@ -90,7 +90,7 @@ impl pallet_balances::Trait for Test { parameter_types! { pub const LaunchPeriod: u64 = 2; pub const VotingPeriod: u64 = 2; - pub const EmergencyVotingPeriod: u64 = 1; + pub const FastTrackVotingPeriod: u64 = 1; pub const MinimumDeposit: u64 = 1; pub const EnactmentPeriod: u64 = 2; pub const CooloffPeriod: u64 = 2; @@ -122,7 +122,7 @@ impl super::Trait for Test { type EnactmentPeriod = EnactmentPeriod; type LaunchPeriod = LaunchPeriod; type VotingPeriod = VotingPeriod; - type EmergencyVotingPeriod = EmergencyVotingPeriod; + type FastTrackVotingPeriod = FastTrackVotingPeriod; type MinimumDeposit = MinimumDeposit; type ExternalOrigin = EnsureSignedBy; type ExternalMajorityOrigin = EnsureSignedBy; diff --git a/frame/democracy/src/types.rs b/frame/democracy/src/types.rs index 5d11a2da3d37a..e6a7a7a9d9081 100644 --- a/frame/democracy/src/types.rs +++ b/frame/democracy/src/types.rs @@ -19,7 +19,7 @@ use codec::{Encode, Decode}; use sp_runtime::RuntimeDebug; use sp_runtime::traits::{Zero, Bounded, CheckedAdd, CheckedSub, CheckedMul, CheckedDiv}; -use crate::{Vote, VoteThreshold}; +use crate::{Vote, VoteThreshold, AccountVote, Conviction}; /// Info regarding an ongoing referendum. #[derive(Encode, Decode, Default, Clone, PartialEq, Eq, RuntimeDebug)] @@ -50,30 +50,50 @@ impl< /// Increment some amount of votes. pub fn add( &mut self, - vote: Vote, - balance: Balance, + vote: AccountVote, ) -> Option<()> { - let (votes, turnout) = vote.conviction.votes(balance); - self.turnout = self.turnout.checked_add(&turnout)?; - match vote.aye { - true => self.ayes = self.ayes.checked_add(&votes)?, - false => self.nays = self.nays.checked_add(&votes)?, - }; + match vote { + AccountVote::Standard { vote, balance } => { + let (votes, turnout) = vote.conviction.votes(balance); + self.turnout = self.turnout.checked_add(&turnout)?; + match vote.aye { + true => self.ayes = self.ayes.checked_add(&votes)?, + false => self.nays = self.nays.checked_add(&votes)?, + } + } + AccountVote::Split { aye, nay } => { + let (aye_votes, aye_turnout) = Conviction::None.votes(aye); + let (nay_votes, nay_turnout) = Conviction::None.votes(nay); + self.turnout = self.turnout.checked_add(&aye_turnout)?.checked_add(&nay_turnout)?; + self.ayes = self.ayes.checked_add(&aye_votes)?; + self.nays = self.nays.checked_add(&nay_votes)?; + } + } Some(()) } /// Decrement some amount of votes. pub fn remove( &mut self, - vote: Vote, - balance: Balance, + vote: AccountVote, ) -> Option<()> { - let (votes, turnout) = vote.conviction.votes(balance); - self.turnout = self.turnout.checked_sub(&turnout)?; - match vote.aye { - true => self.ayes = self.ayes.checked_sub(&votes)?, - false => self.nays = self.nays.checked_sub(&votes)?, - }; + match vote { + AccountVote::Standard { vote, balance } => { + let (votes, turnout) = vote.conviction.votes(balance); + self.turnout = self.turnout.checked_sub(&turnout)?; + match vote.aye { + true => self.ayes = self.ayes.checked_sub(&votes)?, + false => self.nays = self.nays.checked_sub(&votes)?, + } + } + AccountVote::Split { aye, nay } => { + let (aye_votes, aye_turnout) = Conviction::None.votes(aye); + let (nay_votes, nay_turnout) = Conviction::None.votes(nay); + self.turnout = self.turnout.checked_sub(&aye_turnout)?.checked_sub(&nay_turnout)?; + self.ayes = self.ayes.checked_sub(&aye_votes)?; + self.nays = self.nays.checked_sub(&nay_votes)?; + } + } Some(()) } } diff --git a/frame/democracy/src/vote.rs b/frame/democracy/src/vote.rs index a66ac68b3e2eb..f48c4ace44150 100644 --- a/frame/democracy/src/vote.rs +++ b/frame/democracy/src/vote.rs @@ -18,7 +18,7 @@ use sp_std::{result::Result, convert::TryFrom}; use codec::{Encode, EncodeLike, Decode, Output, Input}; -use sp_runtime::RuntimeDebug; +use sp_runtime::{RuntimeDebug, traits::Saturating}; use crate::conviction::Conviction; /// A number of lock periods, plus a vote, one way or the other. @@ -46,3 +46,34 @@ impl Decode for Vote { }) } } + +/// A vote for a referendum of a particular account. +#[derive(Encode, Decode, Copy, Clone, Eq, PartialEq, RuntimeDebug)] +pub enum AccountVote { + /// A standard vote, one-way (approve or reject) with a given amount of conviction. + Standard { vote: Vote, balance: Balance }, + /// A split vote with balances given for both ways, and with no conviction, useful for + /// parachains when voting. + Split { aye: Balance, nay: Balance }, +} + +impl AccountVote { + /// Returns `Some` of the lock periods that the account is locked for, assuming that the + /// referendum passed iff `approved` is `true`. + pub fn lock_periods(self, approved: bool) -> Option { + // winning side: can only be removed after the lock period ends. + match self { + AccountVote::Standard { vote, .. } if vote.aye == approved => + Some(vote.conviction.lock_periods()), + _ => None, + } + } + + /// The total balance involved in this vote. + pub fn balance(self) -> Balance { + match self { + AccountVote::Standard { balance, .. } => balance, + AccountVote::Split { aye, nay } => aye.saturating_add(nay), + } + } +} From 4c07c53892ba2317d8e646f1acc6eec5adb0d35a Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 19 Mar 2020 21:56:41 +0100 Subject: [PATCH 05/22] Introduce delegation that works. --- frame/democracy/src/lib.rs | 324 ++++++++++++++++++++++++++--------- frame/democracy/src/types.rs | 30 +++- frame/democracy/src/vote.rs | 96 ++++++++++- 3 files changed, 358 insertions(+), 92 deletions(-) diff --git a/frame/democracy/src/lib.rs b/frame/democracy/src/lib.rs index 630ef9e77e355..f211b1adb291b 100644 --- a/frame/democracy/src/lib.rs +++ b/frame/democracy/src/lib.rs @@ -175,7 +175,7 @@ mod vote; mod conviction; mod types; pub use vote_threshold::{Approved, VoteThreshold}; -pub use vote::{Vote, AccountVote}; +pub use vote::{Vote, AccountVote, Voting}; pub use conviction::Conviction; pub use types::{ReferendumInfo, ReferendumStatus, ProxyState, Tally}; @@ -293,28 +293,26 @@ decl_storage! { map hasher(twox_64_concat) ReferendumIndex => Option>>; - // TODO: Refactor DispatchQueue into its own module. + // TODO: Refactor DispatchQueue into its own pallet. /// Queue of successful referenda to be dispatched. Stored ordered by block number. pub DispatchQueue get(fn dispatch_queue): Vec<(T::BlockNumber, T::Hash, ReferendumIndex)>; /// All votes for a particular voter. We store the balance for the number of votes that we - /// have recorded. - pub VotesOf: map hasher(twox_64_concat) T::AccountId => Vec<(ReferendumIndex, AccountVote>)>; + /// have recorded. The second item is the total amount of delegations, that will be added. + pub VotingOf: map hasher(twox_64_concat) T::AccountId => Voting, T::AccountId, T::BlockNumber>; /// Who is able to vote for whom. Value is the fund-holding account, key is the /// vote-transaction-sending account. + // TODO: Refactor proxy into its own pallet. pub Proxy get(fn proxy): map hasher(twox_64_concat) T::AccountId => Option>; - /// Get the account (and conviction) to which another account is delegating vote. - pub Delegations get(fn delegations): - map hasher(twox_64_concat) T::AccountId => (T::AccountId, Conviction); - /// Accounts for which there are locks in action which may be removed at some point in the /// future. The value is the block number at which the lock expires and may be removed. pub Locks get(locks): map hasher(twox_64_concat) T::AccountId => Option; /// True if the last referendum tabled was submitted externally. False if it was a public /// proposal. + // TODO: There should be any number of tabling origins, not just public and "external" (council). pub LastTabledWasExternal: bool; /// The referendum to be tabled whenever it would be valid to table an external proposal. @@ -445,6 +443,8 @@ decl_error! { Underflow, /// Too high a balance was provided that the account cannot afford. TooMuch, + /// The operation cannot succeed until all current voting is cleeared. + AlreadyVoting, } } @@ -895,47 +895,39 @@ decl_module! { })?; } - /// Delegate vote. + /// Delegate the voting power (with some given conviction) of the sending account. /// - /// Currency is locked indefinitely for as long as it's delegated. + /// The balance delegated is locked for as long as it's delegated, and thereafter for the + /// time appropriate for the conviction's lock period. /// - /// The dispatch origin of this call must be _Signed_. + /// The dispatch origin of this call must be _Signed_, and the signing account must either: + /// - be delegating already; or + /// - have no voting activity (if there is, then it will need to be removed/consolidated + /// through `reap_vote` or `unvote`). /// - /// - `to`: The account to make a delegate of the sender. - /// - `conviction`: The conviction that will be attached to the delegated - /// votes. When the account is undelegated, the funds will be locked for the corresponding - /// number of lock periods. + /// - `to`: The account whose voting the `target` account's voting power will follow. + /// - `conviction`: The conviction that will be attached to the delegated votes. When the + /// account is undelegated, the funds will be locked for the corresponding period. + /// - `balance`: The amount of the account's balance to be used in delegating. This must + /// not be more than the account's current balance. /// /// Emits `Delegated`. /// /// # /// # #[weight = SimpleDispatchInfo::FixedNormal(500_000)] - pub fn delegate(origin, to: T::AccountId, conviction: Conviction) { + pub fn delegate(origin, to: T::AccountId, conviction: Conviction, balance: BalanceOf) { let who = ensure_signed(origin)?; - Delegations::::try_mutate_exists(&who, |d| -> DispatchResult { - ensure!(d.is_none(), Error::::AlreadyDelegating); - *d = Some((to.clone(), conviction)); - Ok(()) - })?; - // Currency is locked indefinitely as long as it's delegated. - T::Currency::extend_lock( - DEMOCRACY_ID, - &who, - Bounded::max_value(), - WithdrawReason::Transfer.into() - ); - Locks::::remove(&who); - Self::deposit_event(RawEvent::Delegated(who, to)); + Self::try_delegate(who, to, conviction, balance) } - /// Undelegate vote. + /// Undelegate the voting power of the sending account. /// - /// Must be sent from an account that has called delegate previously. - /// The tokens will be reduced from an indefinite lock to the maximum - /// possible according to the conviction of the prior delegation. + /// Tokens may be unlocked following once an amount of time consistent with the lock period + /// of the conviction with which the delegation was issued. /// - /// The dispatch origin of this call must be _Signed_. + /// The dispatch origin of this call must be _Signed_ and the signing account must be + /// currently delegating. /// /// Emits `Undelegated`. /// @@ -945,19 +937,7 @@ decl_module! { #[weight = SimpleDispatchInfo::FixedNormal(500_000)] fn undelegate(origin) { let who = ensure_signed(origin)?; - ensure!(>::contains_key(&who), Error::::NotDelegated); - 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(); - Locks::::insert(&who, locked_until); - T::Currency::set_lock( - DEMOCRACY_ID, - &who, - Bounded::max_value(), - WithdrawReason::Transfer.into(), - ); - Self::deposit_event(RawEvent::Undelegated(who)); + Self::try_undelegate(who) } /// Clears all public proposals. @@ -1118,7 +1098,7 @@ decl_module! { #[weight = SimpleDispatchInfo::FixedNormal(10_000)] fn unvote(origin, index: ReferendumIndex) -> DispatchResult { let who = ensure_signed(origin)?; - Self::cancel_vote(&who, index, UnvoteScope::Any) + Self::try_unvote(&who, index, UnvoteScope::Any) } /// Remove a vote for a referendum that has expired, either because it was cancelled, @@ -1136,7 +1116,61 @@ decl_module! { #[weight = SimpleDispatchInfo::FixedNormal(10_000)] fn reap_vote(origin, target: T::AccountId, index: ReferendumIndex) -> DispatchResult { ensure_signed(origin)?; - Self::cancel_vote(&target, index, UnvoteScope::OnlyExpired) + Self::try_unvote(&target, index, UnvoteScope::OnlyExpired) + } + + /// Delegate the voting power (with some given conviction) of a proxied account. + /// + /// The balance delegated is locked for as long as it's delegated, and thereafter for the + /// time appropriate for the conviction's lock period. + /// + /// The dispatch origin of this call must be _Signed_, and the signing account must have + /// been set as the proxy account for `target`. + /// + /// - `target`: The account whole voting power shall be delegated and whose balance locked. + /// This account must either: + /// - be delegating already; or + /// - have no voting activity (if there is, then it will need to be removed/consolidated + /// through `reap_vote` or `unvote`). + /// - `to`: The account whose voting the `target` account's voting power will follow. + /// - `conviction`: The conviction that will be attached to the delegated votes. When the + /// account is undelegated, the funds will be locked for the corresponding period. + /// - `balance`: The amount of the account's balance to be used in delegating. This must + /// not be more than the account's current balance. + /// + /// Emits `Delegated`. + /// + /// # + /// # + #[weight = SimpleDispatchInfo::FixedNormal(500_000)] + pub fn proxy_delegate(origin, + to: T::AccountId, + conviction: Conviction, + balance: BalanceOf, + ) { + let who = ensure_signed(origin)?; + let target = Self::proxy(who).and_then(|a| a.as_active()).ok_or(Error::::NotProxy)?; + Self::try_delegate(target, to, conviction, balance) + } + + /// Undelegate the voting power of a proxied account. + /// + /// Tokens may be unlocked following once an amount of time consistent with the lock period + /// of the conviction with which the delegation was issued. + /// + /// The dispatch origin of this call must be _Signed_ and the signing account must be a + /// proxy for some other account which is currently delegating. + /// + /// Emits `Undelegated`. + /// + /// # + /// - O(1). + /// # + #[weight = SimpleDispatchInfo::FixedNormal(500_000)] + fn proxy_undelegate(origin) { + let who = ensure_signed(origin)?; + let target = Self::proxy(who).and_then(|a| a.as_active()).ok_or(Error::::NotProxy)?; + Self::try_undelegate(target) } } } @@ -1229,18 +1263,28 @@ impl Module { fn try_vote(who: &T::AccountId, ref_index: ReferendumIndex, vote: AccountVote>) -> DispatchResult { let mut status = Self::referendum_status(ref_index)?; ensure!(vote.balance() <= T::Currency::free_balance(who), Error::::TooMuch); - VotesOf::::try_mutate(who, |votes| -> DispatchResult { - match votes.binary_search_by_key(&ref_index, |i| i.0) { - Ok(i) => { - // Shouldn't be possible to fail, but we handle it gracefully. - status.tally.remove(votes[i].1).ok_or(Error::::Underflow)?; - votes[i].1 = vote; + VotingOf::::try_mutate(who, |voting| -> DispatchResult { + if let Voting::Direct { ref mut votes, delegations, .. } = voting { + match voting.binary_search_by_key(&ref_index, |i| i.0) { + Ok(i) => { + // Shouldn't be possible to fail, but we handle it gracefully. + status.tally.remove(votes[i].1).ok_or(Error::::Underflow)?; + if let Some(approve) = votes[i].1.as_standard() { + status.tally.reduce(approve, delegations, Zero::zero()); + } + votes[i].1 = vote; + } + Err(i) => votes.insert(i, (ref_index, vote)), + } + // Shouldn't be possible to fail, but we handle it gracefully. + status.tally.add(vote).ok_or(Error::::Overflow)?; + if let Some(approve) = vote.as_standard() { + status.tally.increase(approve, delegations, Zero::zero()); } - Err(i) => votes.insert(i, (ref_index, vote)), + Ok(()) + } else { + Err(Error::::AlreadyDelegating) } - // Shouldn't be possible to fail, but we handle it gracefully. - status.tally.add(vote).ok_or(Error::::Overflow)?; - Ok(()) })?; // Extend the lock to `balance` (rather than setting it) since we don't know what other // votes are in place. @@ -1260,41 +1304,153 @@ impl Module { /// - The referendum has finished and the voter's lock period is up. /// /// This will generally be combined with a call to `unlock`. - fn cancel_vote(who: &T::AccountId, ref_index: ReferendumIndex, scope: UnvoteScope) -> DispatchResult { + fn try_unvote(who: &T::AccountId, ref_index: ReferendumIndex, scope: UnvoteScope) -> DispatchResult { let info = ReferendumInfoOf::::get(ref_index); - VotesOf::::try_mutate(who, |votes| -> DispatchResult { - let i = votes.binary_search_by_key(&ref_index, |i| i.0).map_err(|_| Error::::NotVoter)?; - match info { - Some(ReferendumInfo::Ongoing(mut status)) => { - ensure!(matches!(scope, UnvoteScope::Any), Error::::NoPermission); - // Shouldn't be possible to fail, but we handle it gracefully. - status.tally.remove(votes[i].1).ok_or(Error::::Underflow)?; - ReferendumInfoOf::::insert(ref_index, ReferendumInfo::Ongoing(status)); + VotesOf::::try_mutate(who, |voting| -> DispatchResult { + if let Voting::Direct { ref mut votes, delegations, ref mut prior } = voting { + let i = votes.binary_search_by_key(&ref_index, |i| i.0).map_err(|_| Error::::NotVoter)?; + match info { + Some(ReferendumInfo::Ongoing(mut status)) => { + ensure!(matches!(scope, UnvoteScope::Any), Error::::NoPermission); + // Shouldn't be possible to fail, but we handle it gracefully. + status.tally.remove(votes[i].1).ok_or(Error::::Underflow)?; + if let Some(approve) = votes[i].1.as_standard() { + status.tally.reduce(approve, delegations, Zero::zero()); + } + ReferendumInfoOf::::insert(ref_index, ReferendumInfo::Ongoing(status)); + } + Some(ReferendumInfo::Finished{end, approved}) => + if let Some((lock_periods, balance)) = votes[i].1.locked_if(approved) { + let unlock_at = end + T::EnactmentPeriod::get() * lock_periods.into(); + let now = system::Module::::block_number(); + if now < unlock_at { + prior.accumulate(unlock_at, balance) + } + }, + None => {} // Referendum was cancelled. } - Some(ReferendumInfo::Finished{end, approved}) => - if let Some(lock_periods) = votes[i].1.lock_periods(approved) { - let unlock_at = end + T::EnactmentPeriod::get() * lock_periods.into(); - let now = system::Module::::block_number(); - ensure!(now >= unlock_at, Error::::TooEarly); - }, - None => {} // Referendum was cancelled. + votes.remove(i); } - votes.remove(i); Ok(()) })?; Ok(()) } - /// Rejig the locks on an account. They will never get more stringent but may be reduced from - /// what they are currently. + fn increase_upstream_delegation(who: &T::AccountId, amount: BalanceOf) { + VotingOf::::mutate(who, |voting| match voting { + Voting::Delegating { balance, target, conviction, delegations, prior } => + // We don't support second level delegating, so we don't need to do anything more. + *delegations = delegations.saturating_add(amount), + Voting::Direct { votes, delegations, prior } => { + *delegations = delegations.saturating_add(amount); + for &(ref_index, account_vote) in votes.iter() { + if let AccountVote::Standard { vote, .. } = account_vote { + ReferendumInfoOf::::mutate(ref_index, |maybe_info| + if let Some(ReferendumInfo::Ongoing(ref mut status)) = maybe_info { + status.tally.increase(vote.aye, amount, Zero::zero()); + } + ); + } + } + } + }) + } + + fn reduce_upstream_delegation(who: &T::AccountId, amount: BalanceOf) { + VotingOf::::mutate(who, |voting| match voting { + Voting::Delegating { balance, target, conviction, delegations, prior } => + // We don't support second level delegating, so we don't need to do anything more. + *delegations = delegations.saturating_sub(amount), + Voting::Direct { votes, delegations, prior } => { + *delegations = delegations.saturating_sub(amount); + for &(ref_index, account_vote) in votes.iter() { + if let AccountVote::Standard { vote, .. } = account_vote { + ReferendumInfoOf::::mutate(ref_index, |maybe_info| + if let Some(ReferendumInfo::Ongoing(ref mut status)) = maybe_info { + status.tally.reduce(vote.aye, amount, Zero::zero()); + } + ); + } + } + } + }) + } + + /// Attempt to delegate `balance` times `conviction` of voting power from `who` to `target`. + fn try_delegate( + who: T::AccountId, + target: T::AccountId, + conviction: Conviction, + balance: BalanceOf, + ) -> DispatchResult { + ensure!(vote.balance() <= T::Currency::free_balance(who), Error::::TooMuch); + VotingOf::::try_mutate(&who, |voting| -> DispatchResult { + let mut old = Voting::Delegating { + balance, + target: target.clone(), + conviction, + .. Default::default() + }; + sp_std::mem::swap(&mut old, voting); + match old { + Voting::Delegating { balance, target, conviction, delegations, prior } => { + // remove any delegation votes to our current target. + Self::reduce_upstream_delegation(&target, delegations); + voting.delegations = delegations; + voting.prior = prior; + } + Voting::Direct { votes, delegations, prior } => { + // here we just ensure that we're currently idling with no votes recorded. + ensure!(votes.is_empty(), Error::::AlreadyVoting); + voting.delegations = delegations; + voting.prior = prior; + } + } + Self::increase_upstream_delegation(&target, delegations); + Self::deposit_event(Event::::Delegated(who, target)); + }) + } + + /// Attempt to end the current delegation. + fn try_undelegate(who: T::AccountId) -> DispatchResult { + VotingOf::::try_mutate(&who, |voting| -> DispatchResult { + let mut old = Voting::default(); + sp_std::mem::swap(&mut old, voting); + match old { + Voting::Delegating { + balance, + target, + conviction, + delegations, + mut prior, + } => { + // remove any delegation votes to our current target. + Self::reduce_upstream_delegation(&target, delegations); + let now = system::Module::::block_number(); + let lock_periods = conviction.lock_periods().into(); + prior.accumulate(now + T::EnactmentPeriod::get() * lock_periods, balance); + voting.delegations = delegations; + voting.prior = prior; + } + Voting::Direct { .. } => { + return Err(Error::::AlreadyVoting.into()) + } + } + Self::deposit_event(Event::::Undelegated(who)); + }) + } + + /// Rejig the lock on an account. It will never get more stringent (since that would indicate + /// a security hole) but may be reduced from what they are currently. fn update_lock(who: &T::AccountId) { - let max = VotesOf::::get(who).into_iter() - .map(|i| i.1.balance()) - .fold(BalanceOf::::zero(), |a, i| a.max(i)); - if max.is_zero() { + let lock_needed = VotingOf::::mutate(who, |voting| { + voting.rejig(system::Module::::block_number()); + voting.locked_balance() + }); + if lock_needed.is_zero() { T::Currency::remove_lock(DEMOCRACY_ID, who); } else { - T::Currency::set_lock(DEMOCRACY_ID, who, max, WithdrawReason::Transfer.into()); + T::Currency::set_lock(DEMOCRACY_ID, who, lock_needed, WithdrawReason::Transfer.into()); } } diff --git a/frame/democracy/src/types.rs b/frame/democracy/src/types.rs index e6a7a7a9d9081..06eae0f9f7ae8 100644 --- a/frame/democracy/src/types.rs +++ b/frame/democracy/src/types.rs @@ -18,7 +18,7 @@ use codec::{Encode, Decode}; use sp_runtime::RuntimeDebug; -use sp_runtime::traits::{Zero, Bounded, CheckedAdd, CheckedSub, CheckedMul, CheckedDiv}; +use sp_runtime::traits::{Zero, Bounded, CheckedAdd, CheckedSub, CheckedMul, CheckedDiv, Saturating}; use crate::{Vote, VoteThreshold, AccountVote, Conviction}; /// Info regarding an ongoing referendum. @@ -33,8 +33,10 @@ pub struct Tally { } impl< - Balance: From + Zero + Copy + CheckedAdd + CheckedSub + CheckedMul + CheckedDiv + Bounded + Balance: From + Zero + Copy + CheckedAdd + CheckedSub + CheckedMul + CheckedDiv + Bounded + + Saturating > Tally { + /// Create a new tally. pub fn new( vote: Vote, balance: Balance, @@ -47,7 +49,7 @@ impl< } } - /// Increment some amount of votes. + /// Add an account's vote into the tally. pub fn add( &mut self, vote: AccountVote, @@ -72,7 +74,7 @@ impl< Some(()) } - /// Decrement some amount of votes. + /// Remove an account's vote from the tally. pub fn remove( &mut self, vote: AccountVote, @@ -96,6 +98,26 @@ impl< } Some(()) } + + /// Increment some amount of votes. + pub fn increase(&mut self, approve: bool, votes: Balance, capital: Balance) -> Option<()> { + self.turnout = self.turnout.saturating_add(&capital)?; + match approve { + true => self.ayes = self.ayes.saturating_add(&votes)?, + false => self.nays = self.nays.saturating_add(&votes)?, + } + Some(()) + } + + /// Decrement some amount of votes. + pub fn reduce(&mut self, approve: bool, votes: Balance, capital: Balance) -> Option<()> { + self.turnout = self.turnout.saturating_sub(&capital)?; + match approve { + true => self.ayes = self.ayes.saturating_sub(&votes)?, + false => self.nays = self.nays.saturating_sub(&votes)?, + } + Some(()) + } } /* /// Info regarding an ongoing referendum. diff --git a/frame/democracy/src/vote.rs b/frame/democracy/src/vote.rs index f48c4ace44150..823cfbfe33537 100644 --- a/frame/democracy/src/vote.rs +++ b/frame/democracy/src/vote.rs @@ -18,8 +18,9 @@ use sp_std::{result::Result, convert::TryFrom}; use codec::{Encode, EncodeLike, Decode, Output, Input}; -use sp_runtime::{RuntimeDebug, traits::Saturating}; -use crate::conviction::Conviction; +use sp_runtime::{RuntimeDebug, traits::{Saturating, Zero}}; +use crate::{Conviction, ReferendumIndex}; +use crate::vote::Voting::Delegating; /// A number of lock periods, plus a vote, one way or the other. #[derive(Copy, Clone, Eq, PartialEq, Default, RuntimeDebug)] @@ -60,11 +61,11 @@ pub enum AccountVote { impl AccountVote { /// Returns `Some` of the lock periods that the account is locked for, assuming that the /// referendum passed iff `approved` is `true`. - pub fn lock_periods(self, approved: bool) -> Option { + pub fn locked_if(self, approved: bool) -> Option<(u32, Balance)> { // winning side: can only be removed after the lock period ends. match self { AccountVote::Standard { vote, .. } if vote.aye == approved => - Some(vote.conviction.lock_periods()), + Some((vote.conviction.lock_periods(), vote.balance)), _ => None, } } @@ -76,4 +77,91 @@ impl AccountVote { AccountVote::Split { aye, nay } => aye.saturating_add(nay), } } + + /// Returns `Some` with whether the vote is an aye vote if it is standard, otherwise `None` if + /// it is split. + pub fn as_standard(self) -> Option { + match self { + AccountVote::Standard { vote, .. } => Some(vote.aye), + _ => None, + } + } +} + +/// A "prior" lock, i.e. a lock for some now-forgotten reason. +#[derive(Encode, Decode, Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug)] +pub struct PriorLock(BlockNumber, Balance); + +impl PriorLock { + /// Accumulates an additional lock. + pub fn accumulate(&mut self, until: BlockNumber, amount: Balance) { + self.0 = self.0.max(until); + self.1 = self.1.max(amount); + } + + pub fn locked(&self) -> Balance { + self.1 + } + + pub fn rejig(&mut self, now: BlockNumber) { + if now >= self.0 { + self = Default::default(); + } + } +} + +/// An indicator for what an account is doing; it can either be delegating or voting. +#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug)] +pub enum Voting { + /// The account is voting directly. `delegations` is the total amount of post-conviction voting + /// weight that it controls from those that have delegated to it. + Direct { + /// The current votes of the account. + votes: Vec<(ReferendumIndex, AccountVote)>, + /// The total amount of delegations that this account has received. + delegations: Balance, + /// Any pre-existing locks from past voting/delegating activity. + prior: PriorLock, + }, + /// The account is delegating `balance` of its balance to a `target` account with `conviction`. + Delegating { + balance: Balance, + target: AccountId, + conviction: Conviction, + /// The total amount of delegations that this account has received. + delegations: Balance, + /// Any pre-existing locks from past voting/delegating activity. + prior: PriorLock, + }, +} + +impl Default for Voting { + fn default() -> Self { + Voting::Direct { + votes: Vec::new(), + delegations: Zero::zero(), + prior: None, + } + } +} + +impl Voting { + pub fn rejig(&mut self, now: BlockNumber) { + match self { + Voting::Direct { prior, .. } => prior, + Voting::Delegating { prior, .. } => prior, + }.rejig(now); + } + + /// The amount of this account's balance that much currently be locked due to voting. + pub fn locked_balance(&self) -> Balance { + match self { + Voting::Direct { votes, prior, .. } => { + votes.iter() + .map(|i| i.1.balance()) + .fold(prior.locked.map_or_else(Zero::zero, |u| u.0), |a, i| a.max(i)) + } + Voting::Delegating { balance, .. } => balance, + } + } } From 559c9d68ffd7bb8bb4fc5cc5e253b04ec40d5076 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 19 Mar 2020 23:01:05 +0100 Subject: [PATCH 06/22] Builds again. --- frame/democracy/src/lib.rs | 110 +++++++++++++++++++++++------------ frame/democracy/src/types.rs | 12 ++-- frame/democracy/src/vote.rs | 39 ++++++++----- 3 files changed, 104 insertions(+), 57 deletions(-) diff --git a/frame/democracy/src/lib.rs b/frame/democracy/src/lib.rs index f211b1adb291b..ff7b708b3e7c4 100644 --- a/frame/democracy/src/lib.rs +++ b/frame/democracy/src/lib.rs @@ -156,7 +156,7 @@ use sp_std::prelude::*; use sp_runtime::{ - DispatchResult, DispatchError, traits::{Zero, Bounded, EnsureOrigin, Hash, Dispatchable, Saturating}, + DispatchResult, DispatchError, traits::{Zero, EnsureOrigin, Hash, Dispatchable, Saturating}, }; use codec::{Ref, Decode}; use frame_support::{ @@ -443,8 +443,11 @@ decl_error! { Underflow, /// Too high a balance was provided that the account cannot afford. TooMuch, - /// The operation cannot succeed until all current voting is cleeared. - AlreadyVoting, + /// The account is not currently delegating. + NotDelegating, + /// The account currently has votes attached to it and the operation cannot succeed until + /// these are removed, either through `unvote` or `reap_vote`. + VotesExist, } } @@ -452,7 +455,6 @@ impl MigrateAccount for Module { fn migrate_account(a: &T::AccountId) { Proxy::::migrate_key_from_blake(a); Locks::::migrate_key_from_blake(a); - Delegations::::migrate_key_from_blake(a); } } @@ -918,7 +920,7 @@ decl_module! { #[weight = SimpleDispatchInfo::FixedNormal(500_000)] pub fn delegate(origin, to: T::AccountId, conviction: Conviction, balance: BalanceOf) { let who = ensure_signed(origin)?; - Self::try_delegate(who, to, conviction, balance) + Self::try_delegate(who, to, conviction, balance)?; } /// Undelegate the voting power of the sending account. @@ -937,7 +939,7 @@ decl_module! { #[weight = SimpleDispatchInfo::FixedNormal(500_000)] fn undelegate(origin) { let who = ensure_signed(origin)?; - Self::try_undelegate(who) + Self::try_undelegate(who)?; } /// Clears all public proposals. @@ -1084,9 +1086,25 @@ decl_module! { }); } - /// Remove a vote for a referendum that has not yet ended, or one that has expired, either - /// because the voter lost the referendum or because the conviction period is over or - /// because it was cancelled. + /// Remove a vote for a referendum. + /// + /// If: + /// - the referendum was cancelled, or + /// - the referendum is ongoing, or + /// - the referendum has ended such that + /// - the vote of the account was in opposition to the result; or + /// - there was no conviction to the account's vote; or + /// - the account made a split vote + /// ...then the vote is removed cleanly and a following call to `unlock` may result in more + /// funds being available. + /// + /// If, however, the referendum has ended and: + /// - it finished corresponding to the vote of the account, and + /// - the account made a standard vote with conviction, and + /// - the lock period of the conviction is not over + /// ...then the lock will be aggregated into the overall account's lock, which may involve + /// *overlocking* (where the two locks are combined into a single lock that is the maximum + /// of both the amount locked and the time is it locked for). /// /// The dispatch origin of this call must be _Signed_. and the /// @@ -1150,7 +1168,7 @@ decl_module! { ) { let who = ensure_signed(origin)?; let target = Self::proxy(who).and_then(|a| a.as_active()).ok_or(Error::::NotProxy)?; - Self::try_delegate(target, to, conviction, balance) + Self::try_delegate(target, to, conviction, balance)?; } /// Undelegate the voting power of a proxied account. @@ -1170,7 +1188,14 @@ decl_module! { fn proxy_undelegate(origin) { let who = ensure_signed(origin)?; let target = Self::proxy(who).and_then(|a| a.as_active()).ok_or(Error::::NotProxy)?; - Self::try_undelegate(target) + Self::try_undelegate(target)?; + } + + #[weight = SimpleDispatchInfo::FixedNormal(10_000)] + fn proxy_unvote(origin, index: ReferendumIndex) -> DispatchResult { + let who = ensure_signed(origin)?; + let target = Self::proxy(who).and_then(|a| a.as_active()).ok_or(Error::::NotProxy)?; + Self::try_unvote(&target, index, UnvoteScope::Any) } } } @@ -1265,12 +1290,12 @@ impl Module { ensure!(vote.balance() <= T::Currency::free_balance(who), Error::::TooMuch); VotingOf::::try_mutate(who, |voting| -> DispatchResult { if let Voting::Direct { ref mut votes, delegations, .. } = voting { - match voting.binary_search_by_key(&ref_index, |i| i.0) { + match votes.binary_search_by_key(&ref_index, |i| i.0) { Ok(i) => { // Shouldn't be possible to fail, but we handle it gracefully. status.tally.remove(votes[i].1).ok_or(Error::::Underflow)?; if let Some(approve) = votes[i].1.as_standard() { - status.tally.reduce(approve, delegations, Zero::zero()); + status.tally.reduce(approve, *delegations, Zero::zero()); } votes[i].1 = vote; } @@ -1279,11 +1304,11 @@ impl Module { // Shouldn't be possible to fail, but we handle it gracefully. status.tally.add(vote).ok_or(Error::::Overflow)?; if let Some(approve) = vote.as_standard() { - status.tally.increase(approve, delegations, Zero::zero()); + status.tally.increase(approve, *delegations, Zero::zero()); } Ok(()) } else { - Err(Error::::AlreadyDelegating) + Err(Error::::AlreadyDelegating.into()) } })?; // Extend the lock to `balance` (rather than setting it) since we don't know what other @@ -1306,7 +1331,7 @@ impl Module { /// This will generally be combined with a call to `unlock`. fn try_unvote(who: &T::AccountId, ref_index: ReferendumIndex, scope: UnvoteScope) -> DispatchResult { let info = ReferendumInfoOf::::get(ref_index); - VotesOf::::try_mutate(who, |voting| -> DispatchResult { + VotingOf::::try_mutate(who, |voting| -> DispatchResult { if let Voting::Direct { ref mut votes, delegations, ref mut prior } = voting { let i = votes.binary_search_by_key(&ref_index, |i| i.0).map_err(|_| Error::::NotVoter)?; match info { @@ -1315,7 +1340,7 @@ impl Module { // Shouldn't be possible to fail, but we handle it gracefully. status.tally.remove(votes[i].1).ok_or(Error::::Underflow)?; if let Some(approve) = votes[i].1.as_standard() { - status.tally.reduce(approve, delegations, Zero::zero()); + status.tally.reduce(approve, *delegations, Zero::zero()); } ReferendumInfoOf::::insert(ref_index, ReferendumInfo::Ongoing(status)); } @@ -1324,6 +1349,7 @@ impl Module { let unlock_at = end + T::EnactmentPeriod::get() * lock_periods.into(); let now = system::Module::::block_number(); if now < unlock_at { + ensure!(matches!(scope, UnvoteScope::Any), Error::::NoPermission); prior.accumulate(unlock_at, balance) } }, @@ -1338,10 +1364,10 @@ impl Module { fn increase_upstream_delegation(who: &T::AccountId, amount: BalanceOf) { VotingOf::::mutate(who, |voting| match voting { - Voting::Delegating { balance, target, conviction, delegations, prior } => + Voting::Delegating { delegations, .. } => // We don't support second level delegating, so we don't need to do anything more. *delegations = delegations.saturating_add(amount), - Voting::Direct { votes, delegations, prior } => { + Voting::Direct { votes, delegations, .. } => { *delegations = delegations.saturating_add(amount); for &(ref_index, account_vote) in votes.iter() { if let AccountVote::Standard { vote, .. } = account_vote { @@ -1358,10 +1384,10 @@ impl Module { fn reduce_upstream_delegation(who: &T::AccountId, amount: BalanceOf) { VotingOf::::mutate(who, |voting| match voting { - Voting::Delegating { balance, target, conviction, delegations, prior } => + Voting::Delegating { delegations, .. } => // We don't support second level delegating, so we don't need to do anything more. *delegations = delegations.saturating_sub(amount), - Voting::Direct { votes, delegations, prior } => { + Voting::Direct { votes, delegations, .. } => { *delegations = delegations.saturating_sub(amount); for &(ref_index, account_vote) in votes.iter() { if let AccountVote::Standard { vote, .. } = account_vote { @@ -1383,32 +1409,41 @@ impl Module { conviction: Conviction, balance: BalanceOf, ) -> DispatchResult { - ensure!(vote.balance() <= T::Currency::free_balance(who), Error::::TooMuch); + ensure!(balance <= T::Currency::free_balance(&who), Error::::TooMuch); VotingOf::::try_mutate(&who, |voting| -> DispatchResult { let mut old = Voting::Delegating { balance, target: target.clone(), conviction, - .. Default::default() + delegations: Default::default(), + prior: Default::default(), }; sp_std::mem::swap(&mut old, voting); match old { - Voting::Delegating { balance, target, conviction, delegations, prior } => { + Voting::Delegating { delegations, prior, .. } => { // remove any delegation votes to our current target. Self::reduce_upstream_delegation(&target, delegations); - voting.delegations = delegations; - voting.prior = prior; + voting.set_common(delegations, prior); } Voting::Direct { votes, delegations, prior } => { // here we just ensure that we're currently idling with no votes recorded. - ensure!(votes.is_empty(), Error::::AlreadyVoting); - voting.delegations = delegations; - voting.prior = prior; + ensure!(votes.is_empty(), Error::::VotesExist); + voting.set_common(delegations, prior); } } - Self::increase_upstream_delegation(&target, delegations); - Self::deposit_event(Event::::Delegated(who, target)); - }) + Self::increase_upstream_delegation(&target, conviction.votes(balance).0); + // Extend the lock to `balance` (rather than setting it) since we don't know what other + // votes are in place. + T::Currency::extend_lock( + DEMOCRACY_ID, + &who, + balance, + WithdrawReason::Transfer.into() + ); + Ok(()) + })?; + Self::deposit_event(Event::::Delegated(who, target)); + Ok(()) } /// Attempt to end the current delegation. @@ -1429,15 +1464,16 @@ impl Module { let now = system::Module::::block_number(); let lock_periods = conviction.lock_periods().into(); prior.accumulate(now + T::EnactmentPeriod::get() * lock_periods, balance); - voting.delegations = delegations; - voting.prior = prior; + voting.set_common(delegations, prior); } Voting::Direct { .. } => { - return Err(Error::::AlreadyVoting.into()) + return Err(Error::::NotDelegating.into()) } } - Self::deposit_event(Event::::Undelegated(who)); - }) + Ok(()) + })?; + Self::deposit_event(Event::::Undelegated(who)); + Ok(()) } /// Rejig the lock on an account. It will never get more stringent (since that would indicate diff --git a/frame/democracy/src/types.rs b/frame/democracy/src/types.rs index 06eae0f9f7ae8..dda353fed672e 100644 --- a/frame/democracy/src/types.rs +++ b/frame/democracy/src/types.rs @@ -101,20 +101,20 @@ impl< /// Increment some amount of votes. pub fn increase(&mut self, approve: bool, votes: Balance, capital: Balance) -> Option<()> { - self.turnout = self.turnout.saturating_add(&capital)?; + self.turnout = self.turnout.saturating_add(capital); match approve { - true => self.ayes = self.ayes.saturating_add(&votes)?, - false => self.nays = self.nays.saturating_add(&votes)?, + true => self.ayes = self.ayes.saturating_add(votes), + false => self.nays = self.nays.saturating_add(votes), } Some(()) } /// Decrement some amount of votes. pub fn reduce(&mut self, approve: bool, votes: Balance, capital: Balance) -> Option<()> { - self.turnout = self.turnout.saturating_sub(&capital)?; + self.turnout = self.turnout.saturating_sub(capital); match approve { - true => self.ayes = self.ayes.saturating_sub(&votes)?, - false => self.nays = self.nays.saturating_sub(&votes)?, + true => self.ayes = self.ayes.saturating_sub(votes), + false => self.nays = self.nays.saturating_sub(votes), } Some(()) } diff --git a/frame/democracy/src/vote.rs b/frame/democracy/src/vote.rs index 823cfbfe33537..4be532b47d96d 100644 --- a/frame/democracy/src/vote.rs +++ b/frame/democracy/src/vote.rs @@ -20,7 +20,6 @@ use sp_std::{result::Result, convert::TryFrom}; use codec::{Encode, EncodeLike, Decode, Output, Input}; use sp_runtime::{RuntimeDebug, traits::{Saturating, Zero}}; use crate::{Conviction, ReferendumIndex}; -use crate::vote::Voting::Delegating; /// A number of lock periods, plus a vote, one way or the other. #[derive(Copy, Clone, Eq, PartialEq, Default, RuntimeDebug)] @@ -64,8 +63,8 @@ impl AccountVote { pub fn locked_if(self, approved: bool) -> Option<(u32, Balance)> { // winning side: can only be removed after the lock period ends. match self { - AccountVote::Standard { vote, .. } if vote.aye == approved => - Some((vote.conviction.lock_periods(), vote.balance)), + AccountVote::Standard { vote, balance } if vote.aye == approved => + Some((vote.conviction.lock_periods(), balance)), _ => None, } } @@ -92,7 +91,7 @@ impl AccountVote { #[derive(Encode, Decode, Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug)] pub struct PriorLock(BlockNumber, Balance); -impl PriorLock { +impl PriorLock { /// Accumulates an additional lock. pub fn accumulate(&mut self, until: BlockNumber, amount: Balance) { self.0 = self.0.max(until); @@ -105,7 +104,8 @@ impl PriorLock= self.0 { - self = Default::default(); + self.0 = Zero::zero(); + self.1 = Zero::zero(); } } } @@ -135,17 +135,21 @@ pub enum Voting { }, } -impl Default for Voting { +impl Default for Voting { fn default() -> Self { Voting::Direct { votes: Vec::new(), delegations: Zero::zero(), - prior: None, + prior: PriorLock(Zero::zero(), Zero::zero()), } } } -impl Voting { +impl< + Balance: Saturating + Ord + Zero + Copy, + BlockNumber: Ord + Copy + Zero, + AccountId, +> Voting { pub fn rejig(&mut self, now: BlockNumber) { match self { Voting::Direct { prior, .. } => prior, @@ -156,12 +160,19 @@ impl Voting Balance { match self { - Voting::Direct { votes, prior, .. } => { - votes.iter() - .map(|i| i.1.balance()) - .fold(prior.locked.map_or_else(Zero::zero, |u| u.0), |a, i| a.max(i)) - } - Voting::Delegating { balance, .. } => balance, + Voting::Direct { votes, prior, .. } => votes.iter() + .map(|i| i.1.balance()) + .fold(prior.locked(), |a, i| a.max(i)), + Voting::Delegating { balance, .. } => *balance, } } + + pub fn set_common(&mut self, delegations: Balance, prior: PriorLock) { + let (d, p) = match self { + Voting::Direct { ref mut delegations, ref mut prior, .. } => (delegations, prior), + Voting::Delegating { ref mut delegations, ref mut prior, .. } => (delegations, prior), + }; + *d = delegations; + *p = prior; + } } From 531f9c4e5df7f3eb6172f68e43697b9e5c0bd41d Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 19 Mar 2020 23:07:16 +0100 Subject: [PATCH 07/22] Indentation --- frame/democracy/src/tests.rs | 176 +++++++++++++++++------------------ 1 file changed, 88 insertions(+), 88 deletions(-) diff --git a/frame/democracy/src/tests.rs b/frame/democracy/src/tests.rs index 317bf8d9eaf9b..172a0462670d8 100644 --- a/frame/democracy/src/tests.rs +++ b/frame/democracy/src/tests.rs @@ -37,25 +37,25 @@ 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 where system = frame_system {} - } + pub enum Origin for Test where system = frame_system {} +} impl_outer_dispatch! { - pub enum Call for Test where origin: Origin { - pallet_balances::Balances, - democracy::Democracy, - } + pub enum Call for Test where origin: Origin { + pallet_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: Weight = 1024; - pub const MaximumBlockLength: u32 = 2 * 1024; - pub const AvailableBlockRatio: Perbill = Perbill::one(); - } + pub const BlockHashCount: u64 = 250; + pub const MaximumBlockWeight: Weight = 1024; + pub const MaximumBlockLength: u32 = 2 * 1024; + pub const AvailableBlockRatio: Perbill = Perbill::one(); +} impl frame_system::Trait for Test { type Origin = Origin; type Index = u64; @@ -78,8 +78,8 @@ impl frame_system::Trait for Test { type OnKilledAccount = (); } parameter_types! { - pub const ExistentialDeposit: u64 = 1; - } + pub const ExistentialDeposit: u64 = 1; +} impl pallet_balances::Trait for Test { type Balance = u64; type Event = (); @@ -88,20 +88,20 @@ impl pallet_balances::Trait for Test { type AccountStore = System; } parameter_types! { - pub const LaunchPeriod: u64 = 2; - pub const VotingPeriod: u64 = 2; - pub const FastTrackVotingPeriod: u64 = 1; - pub const MinimumDeposit: u64 = 1; - pub const EnactmentPeriod: u64 = 2; - pub const CooloffPeriod: u64 = 2; - } + pub const LaunchPeriod: u64 = 2; + pub const VotingPeriod: u64 = 2; + pub const FastTrackVotingPeriod: u64 = 1; + pub const MinimumDeposit: u64 = 1; + pub const EnactmentPeriod: u64 = 2; + pub const CooloffPeriod: u64 = 2; +} ord_parameter_types! { - 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 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 sorted_members() -> Vec { @@ -109,8 +109,8 @@ impl Contains for OneToFive { } } thread_local! { - static PREIMAGE_BYTE_DEPOSIT: RefCell = RefCell::new(0); - } + static PREIMAGE_BYTE_DEPOSIT: RefCell = RefCell::new(0); +} pub struct PreimageByteDeposit; impl Get for PreimageByteDeposit { fn get() -> u64 { PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow()) } @@ -506,16 +506,16 @@ fn veto_external_works() { assert!(!>::exists()); // fails - same proposal can't be resubmitted. assert_noop!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash(2), - ), Error::::ProposalBlacklisted); + Origin::signed(2), + set_balance_proposal_hash(2), + ), Error::::ProposalBlacklisted); fast_forward_to(1); // fails as we're still in cooloff period. assert_noop!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash(2), - ), Error::::ProposalBlacklisted); + Origin::signed(2), + set_balance_proposal_hash(2), + ), Error::::ProposalBlacklisted); fast_forward_to(2); // works; as we're out of the cooloff period. @@ -539,14 +539,14 @@ fn veto_external_works() { fast_forward_to(3); // same proposal fails as we're still in cooloff assert_noop!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash(2), - ), Error::::ProposalBlacklisted); + Origin::signed(2), + set_balance_proposal_hash(2), + ), Error::::ProposalBlacklisted); // different proposal works fine. assert_ok!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash_and_note(3), - )); + Origin::signed(2), + set_balance_proposal_hash_and_note(3), + )); }); } @@ -558,17 +558,17 @@ fn external_referendum_works() { Democracy::external_propose( Origin::signed(1), set_balance_proposal_hash(2), - ), - BadOrigin, - ); + ), + BadOrigin, + ); assert_ok!(Democracy::external_propose( Origin::signed(2), set_balance_proposal_hash_and_note(2), )); assert_noop!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash(1), - ), Error::::DuplicateProposal); + Origin::signed(2), + set_balance_proposal_hash(1), + ), Error::::DuplicateProposal); fast_forward_to(2); assert_eq!( Democracy::referendum_info(0), @@ -590,13 +590,13 @@ fn external_majority_referendum_works() { Democracy::external_propose_majority( Origin::signed(1), set_balance_proposal_hash(2) - ), - BadOrigin, - ); + ), + BadOrigin, + ); assert_ok!(Democracy::external_propose_majority( - Origin::signed(3), - set_balance_proposal_hash_and_note(2) - )); + Origin::signed(3), + set_balance_proposal_hash_and_note(2) + )); fast_forward_to(2); assert_eq!( Democracy::referendum_info(0), @@ -618,13 +618,13 @@ fn external_default_referendum_works() { Democracy::external_propose_default( Origin::signed(3), set_balance_proposal_hash(2) - ), - BadOrigin, - ); + ), + BadOrigin, + ); assert_ok!(Democracy::external_propose_default( - Origin::signed(1), - set_balance_proposal_hash_and_note(2) - )); + Origin::signed(1), + set_balance_proposal_hash_and_note(2) + )); fast_forward_to(2); assert_eq!( Democracy::referendum_info(0), @@ -1253,25 +1253,25 @@ fn lock_voting_should_work() { 0 ); assert_ok!(Democracy::vote(Origin::signed(1), r, Vote { - aye: false, - conviction: Conviction::Locked5x - })); + aye: false, + conviction: Conviction::Locked5x + })); assert_ok!(Democracy::vote(Origin::signed(2), r, Vote { - aye: true, - conviction: Conviction::Locked4x - })); + aye: true, + conviction: Conviction::Locked4x + })); assert_ok!(Democracy::vote(Origin::signed(3), r, Vote { - aye: true, - conviction: Conviction::Locked3x - })); + aye: true, + conviction: Conviction::Locked3x + })); assert_ok!(Democracy::vote(Origin::signed(4), r, Vote { - aye: true, - conviction: Conviction::Locked2x - })); + aye: true, + conviction: Conviction::Locked2x + })); assert_ok!(Democracy::vote(Origin::signed(5), r, Vote { - aye: false, - conviction: Conviction::Locked1x - })); + aye: false, + conviction: Conviction::Locked1x + })); assert_eq!(Democracy::tally(r), (250, 100, 150)); @@ -1333,9 +1333,9 @@ fn no_locks_without_conviction_should_work() { 0, ); assert_ok!(Democracy::vote(Origin::signed(1), r, Vote { - aye: true, - conviction: Conviction::None, - })); + aye: true, + conviction: Conviction::None, + })); fast_forward_to(2); @@ -1355,22 +1355,22 @@ fn lock_voting_should_work_with_delegation() { 0 ); assert_ok!(Democracy::vote(Origin::signed(1), r, Vote { - aye: false, - conviction: Conviction::Locked5x - })); + aye: false, + conviction: Conviction::Locked5x + })); assert_ok!(Democracy::vote(Origin::signed(2), r, Vote { - aye: true, - conviction: Conviction::Locked4x - })); + aye: true, + conviction: Conviction::Locked4x + })); assert_ok!(Democracy::vote(Origin::signed(3), r, Vote { - aye: true, - conviction: Conviction::Locked3x - })); + 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 - })); + aye: false, + conviction: Conviction::Locked1x + })); assert_eq!(Democracy::tally(r), (250, 100, 150)); From 58fa9b4f090d6bac3a7d9b7848d736253306152f Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 19 Mar 2020 23:14:06 +0100 Subject: [PATCH 08/22] Building. --- frame/democracy/src/lib.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frame/democracy/src/lib.rs b/frame/democracy/src/lib.rs index f32be205510ea..178aaac047ba2 100644 --- a/frame/democracy/src/lib.rs +++ b/frame/democracy/src/lib.rs @@ -168,7 +168,6 @@ use frame_support::{ } }; use frame_system::{self as system, ensure_signed, ensure_root}; -use frame_support::traits::MigrateAccount; mod vote_threshold; mod vote; @@ -267,6 +266,7 @@ pub trait Trait: frame_system::Trait + Sized { decl_storage! { trait Store for Module as Democracy { // TODO: Refactor public proposal queue into its own pallet. + // https://github.com/paritytech/substrate/issues/5322 /// The number of (public) proposals that have been made so far. pub PublicPropCount get(fn public_prop_count) build(|_| 0 as PropIndex) : PropIndex; /// The public proposals. Unsorted. The second item is the proposal's hash. @@ -278,6 +278,7 @@ decl_storage! { /// Map of hashes to the proposal preimage, along with who registered it and their deposit. /// The block number is the block at which it was deposited. // TODO: Refactor Preimages into its own pallet. + // https://github.com/paritytech/substrate/issues/5322 pub Preimages: map hasher(identity) T::Hash => Option<(Vec, T::AccountId, BalanceOf, T::BlockNumber)>; @@ -294,6 +295,7 @@ decl_storage! { => Option>>; // TODO: Refactor DispatchQueue into its own pallet. + // https://github.com/paritytech/substrate/issues/5322 /// Queue of successful referenda to be dispatched. Stored ordered by block number. pub DispatchQueue get(fn dispatch_queue): Vec<(T::BlockNumber, T::Hash, ReferendumIndex)>; @@ -304,6 +306,7 @@ decl_storage! { /// Who is able to vote for whom. Value is the fund-holding account, key is the /// vote-transaction-sending account. // TODO: Refactor proxy into its own pallet. + // https://github.com/paritytech/substrate/issues/5322 pub Proxy get(fn proxy): map hasher(twox_64_concat) T::AccountId => Option>; /// Accounts for which there are locks in action which may be removed at some point in the @@ -313,6 +316,7 @@ decl_storage! { /// True if the last referendum tabled was submitted externally. False if it was a public /// proposal. // TODO: There should be any number of tabling origins, not just public and "external" (council). + // https://github.com/paritytech/substrate/issues/5322 pub LastTabledWasExternal: bool; /// The referendum to be tabled whenever it would be valid to table an external proposal. From ceac70521ac83d53d4641c965e2fd42e65263be1 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 19 Mar 2020 23:54:35 +0100 Subject: [PATCH 09/22] Docs and migration --- frame/democracy/src/lib.rs | 71 ++++++++++++++++-------- frame/democracy/src/types.rs | 23 +++----- frame/support/src/lib.rs | 2 +- frame/support/src/storage/migration.rs | 76 ++++++++++++++++++++++++++ 4 files changed, 134 insertions(+), 38 deletions(-) diff --git a/frame/democracy/src/lib.rs b/frame/democracy/src/lib.rs index 178aaac047ba2..b36117c62655b 100644 --- a/frame/democracy/src/lib.rs +++ b/frame/democracy/src/lib.rs @@ -78,21 +78,33 @@ //! These calls can be made from any externally held account capable of creating //! a signed extrinsic. //! -//! - `propose` - Submits a sensitive action, represented as a hash. -//! Requires a deposit. -//! - `second` - Signals agreement with a proposal, moves it higher on the -//! proposal queue, and requires a matching deposit to the original. -//! - `vote` - Votes in a referendum, either the vote is "Aye" to enact the -//! proposal or "Nay" to keep the status quo. -//! - `proxy_vote` - Votes in a referendum on behalf of a stash account. +//! Basic actions: +//! - `propose` - Submits a sensitive action, represented as a hash. Requires a deposit. +//! - `second` - Signals agreement with a proposal, moves it higher on the proposal queue, and +//! requires a matching deposit to the original. +//! - `vote` - Votes in a referendum, either the vote is "Aye" to enact the proposal or "Nay" to +//! keep the status quo. +//! - `unvote` - Cancel a previous vote, this must be done by the voter before the vote ends. +//! - `delegate` - Delegates the voting power (tokens * conviction) to another account. +//! - `undelegate` - Stops the delegation of voting power to another account. +//! +//! Administration actions that can be done to any account: +//! - `reap_vote` - Remove some account's expired votes. +//! - `unlock` - Redetermine the account's balance lock, potentially making tokens available. +//! +//! Proxy administration: //! - `activate_proxy` - Activates a proxy that is already open to the sender. //! - `close_proxy` - Clears the proxy status, called by the proxy. -//! - `deactivate_proxy` - Deactivates a proxy back to the open status, called by -//! the stash. +//! - `deactivate_proxy` - Deactivates a proxy back to the open status, called by the stash. //! - `open_proxy` - Opens a proxy account on behalf of the sender. -//! - `delegate` - Delegates the voting power (tokens * conviction) to another -//! account. -//! - `undelegate` - Stops the delegation of voting power to another account. +//! +//! Proxy actions: +//! - `proxy_vote` - Votes in a referendum on behalf of a stash account. +//! - `proxy_unvote` - Cancel a previous vote, done on behalf of the voter by a proxy. +//! - `proxy_delegate` - Delegate voting power, done on behalf of the voter by a proxy. +//! - `proxy_undelegate` - Stop delegating voting power, done on behalf of the voter by a proxy. +//! +//! Preimage actions: //! - `note_preimage` - Registers the preimage for an upcoming proposal, requires //! a deposit that is returned once the proposal is enacted. //! - `note_imminent_preimage` - Registers the preimage for an upcoming proposal. @@ -100,9 +112,6 @@ //! - `reap_preimage` - Removes the preimage for an expired proposal. Will only //! work under the condition that it's the same account that noted it and //! after the voting period, OR it's a different account after the enactment period. -//! - `unvote` - Cancel a previous vote, this must be done by the voter before the vote ends. -//! - `reap_vote` - Cancel some account's expired votes. -//! - `unlock` - Unlocks tokens that have an expired lock. //! //! #### Cancellation Origin //! @@ -176,7 +185,7 @@ mod types; pub use vote_threshold::{Approved, VoteThreshold}; pub use vote::{Vote, AccountVote, Voting}; pub use conviction::Conviction; -pub use types::{ReferendumInfo, ReferendumStatus, ProxyState, Tally}; +pub use types::{ReferendumInfo, ReferendumStatus, ProxyState, Tally, UnvoteScope}; #[cfg(test)] mod tests; @@ -486,6 +495,10 @@ decl_module! { fn deposit_event() = default; + fn on_runtime_upgrade() { + Self::migrate(); + } + /// Propose a sensitive action to be taken. /// /// The dispatch origin of this call must be _Signed_ and the sender must @@ -1178,14 +1191,26 @@ decl_module! { } } -// TODO: migrate referenda to new format. - -enum UnvoteScope { - Any, - OnlyExpired, -} - impl Module { + fn migrate() { + use frame_support::{Twox64Concat, migration::{StorageKeyIterator, remove_storage_prefix}}; + remove_storage_prefix(b"Democracy", b"VotesOf", &[]); + remove_storage_prefix(b"Democracy", b"VotersFor", &[]); + remove_storage_prefix(b"Democracy", b"Delegations", &[]); + for (who, (end, proposal_hash, threshold, delay)) + in StorageKeyIterator::< + ReferendumIndex, + (T::BlockNumber, T::Hash, VoteThreshold, T::BlockNumber), + Twox64Concat, + >::new(b"Democracy", b"ReferendumInfoOf").drain() + { + let status = ReferendumStatus { + end, proposal_hash, threshold, delay, tally: Tally::default() + }; + ReferendumInfoOf::::insert(who, ReferendumInfo::Ongoing(status)) + } + } + // exposed immutables. /// Get the amount locked in support of `proposal`; `None` if proposal isn't a valid proposal diff --git a/frame/democracy/src/types.rs b/frame/democracy/src/types.rs index dda353fed672e..0542c9a8e718a 100644 --- a/frame/democracy/src/types.rs +++ b/frame/democracy/src/types.rs @@ -119,20 +119,6 @@ impl< Some(()) } } -/* -/// Info regarding an ongoing referendum. -#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)] -pub struct OldReferendumInfo { - /// When voting on this referendum will end. - pub (crate) end: BlockNumber, - /// The hash of the proposal being voted on. - pub (crate) proposal_hash: Hash, - /// The thresholding mechanism to determine whether it passed. - pub (crate) threshold: VoteThreshold, - /// The delay (in blocks) to wait after a successful referendum before deploying. - pub (crate) delay: BlockNumber, -} -*/ /// Info regarding an ongoing referendum. #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)] @@ -188,3 +174,12 @@ impl ProxyState { } } } + +/// Whether an `unvote` operation is able to make actions that are not strictly always in the +/// interest of an account. +pub enum UnvoteScope { + /// Permitted to do everything. + Any, + /// Permitted to do only the changes that do not need the owner's permission. + OnlyExpired, +} diff --git a/frame/support/src/lib.rs b/frame/support/src/lib.rs index 8fe32cbda9999..f242efecc401e 100644 --- a/frame/support/src/lib.rs +++ b/frame/support/src/lib.rs @@ -72,7 +72,7 @@ pub use self::hash::{ }; pub use self::storage::{ StorageValue, StorageMap, StorageDoubleMap, StoragePrefixedMap, IterableStorageMap, - IterableStorageDoubleMap, + IterableStorageDoubleMap, migration }; pub use self::dispatch::{Parameter, Callable, IsSubType}; pub use sp_runtime::{self, ConsensusEngineId, print, traits::Printable}; diff --git a/frame/support/src/storage/migration.rs b/frame/support/src/storage/migration.rs index df9758ba69839..67df4d04c0b3c 100644 --- a/frame/support/src/storage/migration.rs +++ b/frame/support/src/storage/migration.rs @@ -19,6 +19,7 @@ use sp_std::prelude::*; use codec::{Encode, Decode}; use crate::{StorageHasher, Twox128}; +use crate::hash::ReversibleStorageHasher; /// Utility to iterate through raw items in storage. pub struct StorageIterator { @@ -78,6 +79,72 @@ impl Iterator for StorageIterator { } } +/// Utility to iterate through raw items in storage. +pub struct StorageKeyIterator { + prefix: Vec, + previous_key: Vec, + drain: bool, + _phantom: ::sp_std::marker::PhantomData<(K, T, H)>, +} + +impl StorageKeyIterator { + /// Construct iterator to iterate over map items in `module` for the map called `item`. + pub fn new(module: &[u8], item: &[u8]) -> Self { + Self::with_suffix(module, item, &[][..]) + } + + /// Construct iterator to iterate over map items in `module` for the map called `item`. + pub fn with_suffix(module: &[u8], item: &[u8], suffix: &[u8]) -> Self { + let mut prefix = Vec::new(); + prefix.extend_from_slice(&Twox128::hash(module)); + prefix.extend_from_slice(&Twox128::hash(item)); + prefix.extend_from_slice(suffix); + let previous_key = prefix.clone(); + Self { prefix, previous_key, drain: false, _phantom: Default::default() } + } + + /// Mutate this iterator into a draining iterator; items iterated are removed from storage. + pub fn drain(mut self) -> Self { + self.drain = true; + self + } +} + +impl Iterator + for StorageKeyIterator +{ + type Item = (K, T); + + fn next(&mut self) -> Option<(K, T)> { + loop { + let maybe_next = sp_io::storage::next_key(&self.previous_key) + .filter(|n| n.starts_with(&self.prefix)); + break match maybe_next { + Some(next) => { + self.previous_key = next.clone(); + let mut key_material = H::reverse(&next[self.prefix.len()..]); + match K::decode(&mut key_material) { + Ok(key) => { + let maybe_value = frame_support::storage::unhashed::get::(&next); + match maybe_value { + Some(value) => { + if self.drain { + frame_support::storage::unhashed::kill(&next); + } + Some((key, value)) + } + None => continue, + } + } + Err(_) => continue, + } + } + None => None, + } + } + } +} + /// Get a particular value in storage by the `module`, the map's `item` name and the key `hash`. pub fn have_storage_value(module: &[u8], item: &[u8], hash: &[u8]) -> bool { get_storage_value::<()>(module, item, hash).is_some() @@ -109,3 +176,12 @@ pub fn put_storage_value(module: &[u8], item: &[u8], hash: &[u8], val key[32..].copy_from_slice(hash); frame_support::storage::unhashed::put(&key, &value); } + +/// Get a particular value in storage by the `module`, the map's `item` name and the key `hash`. +pub fn remove_storage_prefix(module: &[u8], item: &[u8], hash: &[u8]) { + let mut key = vec![0u8; 32 + hash.len()]; + key[0..16].copy_from_slice(&Twox128::hash(module)); + key[16..32].copy_from_slice(&Twox128::hash(item)); + key[32..].copy_from_slice(hash); + frame_support::storage::unhashed::kill_prefix(&key) +} From b31683e5376559e0a3ed2b82a30b45fa8abe3929 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 20 Mar 2020 01:03:39 +0100 Subject: [PATCH 10/22] Fix half of the tests --- frame/democracy/src/lib.rs | 18 +-- frame/democracy/src/tests.rs | 282 ++++++++++++++++++++++------------- 2 files changed, 191 insertions(+), 109 deletions(-) diff --git a/frame/democracy/src/lib.rs b/frame/democracy/src/lib.rs index b36117c62655b..12b8e23a4028e 100644 --- a/frame/democracy/src/lib.rs +++ b/frame/democracy/src/lib.rs @@ -91,7 +91,7 @@ //! Administration actions that can be done to any account: //! - `reap_vote` - Remove some account's expired votes. //! - `unlock` - Redetermine the account's balance lock, potentially making tokens available. -//! +//! //! Proxy administration: //! - `activate_proxy` - Activates a proxy that is already open to the sender. //! - `close_proxy` - Clears the proxy status, called by the proxy. @@ -461,6 +461,8 @@ decl_error! { /// The account currently has votes attached to it and the operation cannot succeed until /// these are removed, either through `unvote` or `reap_vote`. VotesExist, + /// The instant referendum origin is currently disallowed. + InstantNotAllowed, } } @@ -706,7 +708,7 @@ decl_module! { fn fast_track(origin, proposal_hash: T::Hash, voting_period: T::BlockNumber, - delay: T::BlockNumber + delay: T::BlockNumber, ) { // Rather complicated bit of code to ensure that either: // - `voting_period` is at least `FastTrackVotingPeriod` and `origin` is `FastTrackOrigin`; or @@ -720,8 +722,8 @@ decl_module! { None } } { - ensure!(T::InstantAllowed::get(), Error::::TooEarly); T::InstantOrigin::ensure_origin(ensure_instant)?; + ensure!(T::InstantAllowed::get(), Error::::InstantNotAllowed); } let (e_proposal_hash, threshold) = >::get() @@ -734,9 +736,7 @@ decl_module! { >::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::FastTrackVotingPeriod::get()); - Self::inject_referendum(now + period, proposal_hash, threshold, delay); + Self::inject_referendum(now + voting_period, proposal_hash, threshold, delay); } /// Veto and blacklist the external proposal hash. @@ -1423,9 +1423,9 @@ impl Module { }; sp_std::mem::swap(&mut old, voting); match old { - Voting::Delegating { delegations, prior, .. } => { + Voting::Delegating { balance, target, conviction, delegations, prior, .. } => { // remove any delegation votes to our current target. - Self::reduce_upstream_delegation(&target, delegations); + Self::reduce_upstream_delegation(&target, conviction.votes(balance).0); voting.set_common(delegations, prior); } Voting::Direct { votes, delegations, prior } => { @@ -1463,7 +1463,7 @@ impl Module { mut prior, } => { // remove any delegation votes to our current target. - Self::reduce_upstream_delegation(&target, delegations); + Self::reduce_upstream_delegation(&target, conviction.votes(balance).0); let now = system::Module::::block_number(); let lock_periods = conviction.lock_periods().into(); prior.accumulate(now + T::EnactmentPeriod::get() * lock_periods, balance); diff --git a/frame/democracy/src/tests.rs b/frame/democracy/src/tests.rs index 172a0462670d8..8735dc2c8c7ff 100644 --- a/frame/democracy/src/tests.rs +++ b/frame/democracy/src/tests.rs @@ -31,10 +31,10 @@ use sp_runtime::{ use pallet_balances::{BalanceLock, Error as BalancesError}; use frame_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 }; +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 where system = frame_system {} @@ -90,7 +90,7 @@ impl pallet_balances::Trait for Test { parameter_types! { pub const LaunchPeriod: u64 = 2; pub const VotingPeriod: u64 = 2; - pub const FastTrackVotingPeriod: u64 = 1; + pub const FastTrackVotingPeriod: u64 = 2; pub const MinimumDeposit: u64 = 1; pub const EnactmentPeriod: u64 = 2; pub const CooloffPeriod: u64 = 2; @@ -101,6 +101,7 @@ ord_parameter_types! { pub const Three: u64 = 3; pub const Four: u64 = 4; pub const Five: u64 = 5; + pub const Six: u64 = 6; } pub struct OneToFive; impl Contains for OneToFive { @@ -110,11 +111,16 @@ impl Contains for OneToFive { } thread_local! { static PREIMAGE_BYTE_DEPOSIT: RefCell = RefCell::new(0); + static INSTANT_ALLOWED: RefCell = RefCell::new(false); } pub struct PreimageByteDeposit; impl Get for PreimageByteDeposit { fn get() -> u64 { PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow()) } } +pub struct InstantAllowed; +impl Get for InstantAllowed { + fn get() -> bool { INSTANT_ALLOWED.with(|v| *v.borrow()) } +} impl super::Trait for Test { type Proposal = Call; type Event = (); @@ -133,6 +139,8 @@ impl super::Trait for Test { type CooloffPeriod = CooloffPeriod; type PreimageByteDeposit = PreimageByteDeposit; type Slash = (); + type InstantOrigin = EnsureSignedBy; + type InstantAllowed = InstantAllowed; } fn new_test_ext() -> sp_io::TestExternalities { @@ -203,6 +211,22 @@ fn fast_forward_to(n: u64) { } } +fn aye(who: u64) -> AccountVote { + AccountVote::Standard { vote: AYE, balance: Balances::free_balance(&who) } +} + +fn nay(who: u64) -> AccountVote { + AccountVote::Standard { vote: NAY, balance: Balances::free_balance(&who) } +} + +fn big_aye(who: u64) -> AccountVote { + AccountVote::Standard { vote: BIG_AYE, balance: Balances::free_balance(&who) } +} + +fn big_nay(who: u64) -> AccountVote { + AccountVote::Standard { vote: BIG_NAY, balance: Balances::free_balance(&who) } +} + #[test] fn missing_preimage_should_fail() { new_test_ext().execute_with(|| { @@ -213,7 +237,7 @@ fn missing_preimage_should_fail() { VoteThreshold::SuperMajorityApprove, 0 ); - assert_ok!(Democracy::vote(Origin::signed(1), r, AYE)); + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); next_block(); next_block(); @@ -240,7 +264,7 @@ fn preimage_deposit_should_be_required_and_returned() { VoteThreshold::SuperMajorityApprove, 0 ); - assert_ok!(Democracy::vote(Origin::signed(1), r, AYE)); + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); assert_eq!(Balances::reserved_balance(6), 12); @@ -265,7 +289,7 @@ fn preimage_deposit_should_be_reapable_earlier_by_owner() { next_block(); assert_noop!( Democracy::reap_preimage(Origin::signed(6), set_balance_proposal_hash(2)), - Error::::Early + Error::::TooEarly ); next_block(); assert_ok!(Democracy::reap_preimage(Origin::signed(6), set_balance_proposal_hash(2))); @@ -293,7 +317,7 @@ fn preimage_deposit_should_be_reapable() { next_block(); assert_noop!( Democracy::reap_preimage(Origin::signed(5), set_balance_proposal_hash(2)), - Error::::Early + Error::::TooEarly ); next_block(); @@ -316,7 +340,7 @@ fn noting_imminent_preimage_for_free_should_work() { VoteThreshold::SuperMajorityApprove, 1 ); - assert_ok!(Democracy::vote(Origin::signed(1), r, AYE)); + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); assert_noop!( Democracy::note_imminent_preimage(Origin::signed(7), set_balance_proposal(2)), @@ -340,7 +364,7 @@ fn reaping_imminent_preimage_should_fail() { System::set_block_number(1); let h = set_balance_proposal_hash_and_note(2); let r = Democracy::inject_referendum(3, h, VoteThreshold::SuperMajorityApprove, 1); - assert_ok!(Democracy::vote(Origin::signed(1), r, AYE)); + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); next_block(); next_block(); // now imminent. @@ -362,12 +386,13 @@ fn external_and_public_interleaving_works() { // both waiting: external goes first. assert_eq!( - Democracy::referendum_info(0), - Some(ReferendumInfo { + Democracy::referendum_status(0), + Ok(ReferendumStatus { end: 4, proposal_hash: set_balance_proposal_hash_and_note(1), threshold: VoteThreshold::SuperMajorityApprove, - delay: 2 + delay: 2, + tally: Tally { ayes: 0, nays: 0, turnout: 0 }, }) ); // replenish external @@ -380,12 +405,13 @@ fn external_and_public_interleaving_works() { // both waiting: public goes next. assert_eq!( - Democracy::referendum_info(1), - Some(ReferendumInfo { + Democracy::referendum_status(1), + Ok(ReferendumStatus { end: 6, proposal_hash: set_balance_proposal_hash_and_note(2), threshold: VoteThreshold::SuperMajorityApprove, - delay: 2 + delay: 2, + tally: Tally { ayes: 0, nays: 0, turnout: 0 }, }) ); // don't replenish public @@ -394,12 +420,13 @@ fn external_and_public_interleaving_works() { // it's external "turn" again, though since public is empty that doesn't really matter assert_eq!( - Democracy::referendum_info(2), - Some(ReferendumInfo { + Democracy::referendum_status(2), + Ok(ReferendumStatus { end: 8, proposal_hash: set_balance_proposal_hash_and_note(3), threshold: VoteThreshold::SuperMajorityApprove, - delay: 2 + delay: 2, + tally: Tally { ayes: 0, nays: 0, turnout: 0 }, }) ); // replenish external @@ -412,12 +439,13 @@ fn external_and_public_interleaving_works() { // external goes again because there's no public waiting. assert_eq!( - Democracy::referendum_info(3), - Some(ReferendumInfo { + Democracy::referendum_status(3), + Ok(ReferendumStatus { end: 10, proposal_hash: set_balance_proposal_hash_and_note(5), threshold: VoteThreshold::SuperMajorityApprove, - delay: 2 + delay: 2, + tally: Tally { ayes: 0, nays: 0, turnout: 0 }, }) ); // replenish both @@ -431,12 +459,13 @@ fn external_and_public_interleaving_works() { // public goes now since external went last time. assert_eq!( - Democracy::referendum_info(4), - Some(ReferendumInfo { + Democracy::referendum_status(4), + Ok(ReferendumStatus { end: 12, proposal_hash: set_balance_proposal_hash_and_note(4), threshold: VoteThreshold::SuperMajorityApprove, - delay: 2 + delay: 2, + tally: Tally { ayes: 0, nays: 0, turnout: 0 }, }) ); // replenish public again @@ -449,18 +478,18 @@ fn external_and_public_interleaving_works() { // public goes again now since there's no external waiting. assert_eq!( - Democracy::referendum_info(5), - Some(ReferendumInfo { + Democracy::referendum_status(5), + Ok(ReferendumStatus { end: 14, proposal_hash: set_balance_proposal_hash_and_note(6), threshold: VoteThreshold::SuperMajorityApprove, - delay: 2 + delay: 2, + tally: Tally { ayes: 0, nays: 0, turnout: 0 }, }) ); }); } - #[test] fn emergency_cancel_should_work() { new_test_ext().execute_with(|| { @@ -471,7 +500,7 @@ fn emergency_cancel_should_work() { VoteThreshold::SuperMajorityApprove, 2 ); - assert!(Democracy::referendum_info(r).is_some()); + assert!(Democracy::referendum_status(r).is_ok()); assert_noop!(Democracy::emergency_cancel(Origin::signed(3), r), BadOrigin); assert_ok!(Democracy::emergency_cancel(Origin::signed(4), r)); @@ -485,8 +514,11 @@ fn emergency_cancel_should_work() { VoteThreshold::SuperMajorityApprove, 2 ); - assert!(Democracy::referendum_info(r).is_some()); - assert_noop!(Democracy::emergency_cancel(Origin::signed(4), r), Error::::AlreadyCanceled); + assert!(Democracy::referendum_status(r).is_ok()); + assert_noop!( + Democracy::emergency_cancel(Origin::signed(4), r), + Error::::AlreadyCanceled, + ); }); } @@ -571,12 +603,13 @@ fn external_referendum_works() { ), Error::::DuplicateProposal); fast_forward_to(2); assert_eq!( - Democracy::referendum_info(0), - Some(ReferendumInfo { + Democracy::referendum_status(0), + Ok(ReferendumStatus { end: 4, proposal_hash: set_balance_proposal_hash(2), threshold: VoteThreshold::SuperMajorityApprove, - delay: 2 + delay: 2, + tally: Tally { ayes: 0, nays: 0, turnout: 0 }, }) ); }); @@ -599,12 +632,13 @@ fn external_majority_referendum_works() { )); fast_forward_to(2); assert_eq!( - Democracy::referendum_info(0), - Some(ReferendumInfo { + Democracy::referendum_status(0), + Ok(ReferendumStatus { end: 4, proposal_hash: set_balance_proposal_hash(2), threshold: VoteThreshold::SimpleMajority, delay: 2, + tally: Tally { ayes: 0, nays: 0, turnout: 0 }, }) ); }); @@ -627,12 +661,13 @@ fn external_default_referendum_works() { )); fast_forward_to(2); assert_eq!( - Democracy::referendum_info(0), - Some(ReferendumInfo { + Democracy::referendum_status(0), + Ok(ReferendumStatus { end: 4, proposal_hash: set_balance_proposal_hash(2), threshold: VoteThreshold::SuperMajorityAgainst, delay: 2, + tally: Tally { ayes: 0, nays: 0, turnout: 0 }, }) ); }); @@ -649,14 +684,43 @@ fn fast_track_referendum_works() { set_balance_proposal_hash_and_note(2) )); assert_noop!(Democracy::fast_track(Origin::signed(1), h, 3, 2), BadOrigin); - assert_ok!(Democracy::fast_track(Origin::signed(5), h, 0, 0)); + assert_ok!(Democracy::fast_track(Origin::signed(5), h, 2, 0)); assert_eq!( - Democracy::referendum_info(0), - Some(ReferendumInfo { + Democracy::referendum_status(0), + Ok(ReferendumStatus { + end: 2, + proposal_hash: set_balance_proposal_hash_and_note(2), + threshold: VoteThreshold::SimpleMajority, + delay: 0, + tally: Tally { ayes: 0, nays: 0, turnout: 0 }, + }) + ); + }); +} + +#[test] +fn instant_referendum_works() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + let h = set_balance_proposal_hash_and_note(2); + assert_noop!(Democracy::fast_track(Origin::signed(5), h, 3, 2), Error::::ProposalMissing); + assert_ok!(Democracy::external_propose_majority( + Origin::signed(3), + set_balance_proposal_hash_and_note(2) + )); + assert_noop!(Democracy::fast_track(Origin::signed(1), h, 3, 2), BadOrigin); + assert_noop!(Democracy::fast_track(Origin::signed(5), h, 1, 0), BadOrigin); + assert_noop!(Democracy::fast_track(Origin::signed(6), h, 1, 0), Error::::InstantNotAllowed); + INSTANT_ALLOWED.with(|v| *v.borrow_mut() = true); + assert_ok!(Democracy::fast_track(Origin::signed(6), h, 1, 0)); + assert_eq!( + Democracy::referendum_status(0), + Ok(ReferendumStatus { end: 1, proposal_hash: set_balance_proposal_hash_and_note(2), threshold: VoteThreshold::SimpleMajority, delay: 0, + tally: Tally { ayes: 0, nays: 0, turnout: 0 }, }) ); }); @@ -679,15 +743,15 @@ fn fast_track_referendum_fails_when_no_simple_majority() { } #[test] -fn locked_for_should_work() { +fn backing_for_should_work() { new_test_ext().execute_with(|| { System::set_block_number(1); assert_ok!(propose_set_balance_and_note(1, 2, 2)); assert_ok!(propose_set_balance_and_note(1, 4, 4)); assert_ok!(propose_set_balance_and_note(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)); + assert_eq!(Democracy::backing_for(0), Some(2)); + assert_eq!(Democracy::backing_for(1), Some(4)); + assert_eq!(Democracy::backing_for(2), Some(3)); }); } @@ -702,31 +766,29 @@ fn single_proposal_should_work() { fast_forward_to(2); let r = 0; - assert_ok!(Democracy::vote(Origin::signed(1), r, AYE)); + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); assert_eq!(Democracy::referendum_count(), 1); assert_eq!( - Democracy::referendum_info(0), - Some(ReferendumInfo { + Democracy::referendum_status(0), + Ok(ReferendumStatus { end: 4, proposal_hash: set_balance_proposal_hash_and_note(2), threshold: VoteThreshold::SuperMajorityApprove, - delay: 2 + delay: 2, + tally: Tally { ayes: 1, nays: 0, turnout: 1 }, }) ); - 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(3); // referendum still running - assert!(Democracy::referendum_info(0).is_some()); + assert!(Democracy::referendum_status(0).is_ok()); // referendum runs during 2 and 3, ends @ start of 4. fast_forward_to(4); - assert!(Democracy::referendum_info(0).is_none()); + assert!(Democracy::referendum_status(0).is_err()); assert_eq!(Democracy::dispatch_queue(), vec![ (6, set_balance_proposal_hash_and_note(2), 0) ]); @@ -747,7 +809,7 @@ fn cancel_queued_should_work() { // start of 2 => next referendum scheduled. fast_forward_to(2); - assert_ok!(Democracy::vote(Origin::signed(1), 0, AYE)); + assert_ok!(Democracy::vote(Origin::signed(1), 0, aye(1))); fast_forward_to(4); @@ -817,11 +879,9 @@ fn single_proposal_should_work_with_proxy() { let r = 0; assert_ok!(Democracy::open_proxy(Origin::signed(10), 1)); assert_ok!(Democracy::activate_proxy(Origin::signed(1), 10)); - assert_ok!(Democracy::proxy_vote(Origin::signed(10), r, AYE)); + assert_ok!(Democracy::proxy_vote(Origin::signed(10), r, aye(1))); - assert_eq!(Democracy::voters_for(r), vec![1]); - assert_eq!(Democracy::vote_of((r, 1)), AYE); - assert_eq!(Democracy::tally(r), (1, 0, 1)); + assert_eq!(Democracy::referendum_status(r).unwrap().tally, Tally { ayes: 1, nays: 0, turnout: 1 }); fast_forward_to(6); assert_eq!(Balances::free_balance(42), 2); @@ -837,15 +897,35 @@ fn single_proposal_should_work_with_delegation() { fast_forward_to(2); - // Delegate vote. - assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::max_value())); - + // Delegate first vote. + assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::None, 20)); 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)); + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + assert_eq!(Democracy::referendum_status(r).unwrap().tally, Tally { ayes: 3, nays: 0, turnout: 1 }); + + // Delegate a second vote. + assert_ok!(Democracy::delegate(Origin::signed(3), 1, Conviction::None, 30)); + assert_eq!(Democracy::referendum_status(r).unwrap().tally, Tally { ayes: 6, nays: 0, turnout: 1 }); + + // Reduce first vote. + assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::None, 10)); + assert_eq!(Democracy::referendum_status(r).unwrap().tally, Tally { ayes: 5, nays: 0, turnout: 1 }); + + // Second vote delegates to first; we don't do tiered delegation, so it doesn't get used. + assert_ok!(Democracy::delegate(Origin::signed(3), 2, Conviction::None, 30)); + assert_eq!(Democracy::referendum_status(r).unwrap().tally, Tally { ayes: 2, nays: 0, turnout: 1 }); + + // Main voter cancels their vote + assert_ok!(Democracy::unvote(Origin::signed(1), r)); + assert_eq!(Democracy::referendum_status(r).unwrap().tally, Tally { ayes: 0, nays: 0, turnout: 0 }); + + // First delegator delegates half funds with conviction; nothing changes yet. + assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::Locked1x, 10)); + assert_eq!(Democracy::referendum_status(r).unwrap().tally, Tally { ayes: 0, nays: 0, turnout: 0 }); + + // Main voter reinstates their vote + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + assert_eq!(Democracy::referendum_status(r).unwrap().tally, Tally { ayes: 11, nays: 0, turnout: 1 }); fast_forward_to(6); @@ -853,6 +933,7 @@ fn single_proposal_should_work_with_delegation() { }); } +/* #[test] fn single_proposal_should_work_with_cyclic_delegation() { new_test_ext().execute_with(|| { @@ -867,7 +948,7 @@ fn single_proposal_should_work_with_cyclic_delegation() { 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_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); assert_eq!(Democracy::voters_for(r), vec![1]); // Delegated vote is counted. @@ -890,9 +971,9 @@ fn single_proposal_should_work_with_vote_and_delegation() { fast_forward_to(2); let r = 0; - assert_ok!(Democracy::vote(Origin::signed(1), r, AYE)); + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); // Vote. - assert_ok!(Democracy::vote(Origin::signed(2), r, AYE)); + assert_ok!(Democracy::vote(Origin::signed(2), r, aye(2)); // Delegate vote. assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::max_value())); assert_eq!(Democracy::voters_for(r), vec![1, 2]); @@ -919,7 +1000,7 @@ fn single_proposal_should_work_with_undelegation() { fast_forward_to(2); let r = 0; - assert_ok!(Democracy::vote(Origin::signed(1), r, AYE)); + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); assert_eq!(Democracy::referendum_count(), 1); assert_eq!(Democracy::voters_for(r), vec![1]); @@ -945,13 +1026,13 @@ fn single_proposal_should_work_with_delegation_and_vote() { fast_forward_to(2); let r = 0; - assert_ok!(Democracy::vote(Origin::signed(1), r, AYE)); + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); // Delegate vote. assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::max_value())); // Vote. - assert_ok!(Democracy::vote(Origin::signed(2), r, AYE)); + assert_ok!(Democracy::vote(Origin::signed(2), r, aye(2)); assert_eq!(Democracy::referendum_count(), 1); assert_eq!(Democracy::voters_for(r), vec![1, 2]); @@ -1030,11 +1111,11 @@ fn runners_up_should_come_after() { assert_ok!(propose_set_balance_and_note(1, 4, 4)); assert_ok!(propose_set_balance_and_note(1, 3, 3)); fast_forward_to(2); - assert_ok!(Democracy::vote(Origin::signed(1), 0, AYE)); + assert_ok!(Democracy::vote(Origin::signed(1), 0, aye(1))); fast_forward_to(4); - assert_ok!(Democracy::vote(Origin::signed(1), 1, AYE)); + assert_ok!(Democracy::vote(Origin::signed(1), 1, aye(1))); fast_forward_to(6); - assert_ok!(Democracy::vote(Origin::signed(1), 2, AYE)); + assert_ok!(Democracy::vote(Origin::signed(1), 2, aye(1))); }); } @@ -1055,7 +1136,7 @@ fn ooo_inject_referendums_should_work() { 0 ); - assert_ok!(Democracy::vote(Origin::signed(1), r2, AYE)); + assert_ok!(Democracy::vote(Origin::signed(1), r2, aye(1))); assert_eq!(Democracy::voters_for(r2), vec![1]); assert_eq!(Democracy::vote_of((r2, 1)), AYE); assert_eq!(Democracy::tally(r2), (1, 0, 1)); @@ -1063,7 +1144,7 @@ fn ooo_inject_referendums_should_work() { next_block(); assert_eq!(Balances::free_balance(42), 2); - assert_ok!(Democracy::vote(Origin::signed(1), r1, AYE)); + assert_ok!(Democracy::vote(Origin::signed(1), r1, aye(1))); assert_eq!(Democracy::voters_for(r1), vec![1]); assert_eq!(Democracy::vote_of((r1, 1)), AYE); assert_eq!(Democracy::tally(r1), (1, 0, 1)); @@ -1083,7 +1164,7 @@ fn simple_passing_should_work() { VoteThreshold::SuperMajorityApprove, 0 ); - assert_ok!(Democracy::vote(Origin::signed(1), r, AYE)); + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); assert_eq!(Democracy::voters_for(r), vec![1]); assert_eq!(Democracy::vote_of((r, 1)), AYE); @@ -1106,7 +1187,7 @@ fn cancel_referendum_should_work() { VoteThreshold::SuperMajorityApprove, 0 ); - assert_ok!(Democracy::vote(Origin::signed(1), r, AYE)); + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); assert_ok!(Democracy::cancel_referendum(Origin::ROOT, r.into())); next_block(); @@ -1126,7 +1207,7 @@ fn simple_failing_should_work() { VoteThreshold::SuperMajorityApprove, 0 ); - assert_ok!(Democracy::vote(Origin::signed(1), r, NAY)); + assert_ok!(Democracy::vote(Origin::signed(1), r, nay(1))); assert_eq!(Democracy::voters_for(r), vec![1]); assert_eq!(Democracy::vote_of((r, 1)), NAY); @@ -1150,12 +1231,12 @@ fn controversial_voting_should_work() { 0 ); - 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_ok!(Democracy::vote(Origin::signed(1), r, big_aye(1))); + assert_ok!(Democracy::vote(Origin::signed(2), r, big_nay(2))); + assert_ok!(Democracy::vote(Origin::signed(3), r, big_nay(3))); + assert_ok!(Democracy::vote(Origin::signed(4), r, big_aye(4))); + assert_ok!(Democracy::vote(Origin::signed(5), r, big_nay(5))); + assert_ok!(Democracy::vote(Origin::signed(6), r, big_aye(6))); assert_eq!(Democracy::tally(r), (110, 100, 210)); @@ -1176,12 +1257,12 @@ fn delayed_enactment_should_work() { VoteThreshold::SuperMajorityApprove, 1 ); - 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_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + assert_ok!(Democracy::vote(Origin::signed(2), r, aye(2))); + assert_ok!(Democracy::vote(Origin::signed(3), r, aye(3))); + assert_ok!(Democracy::vote(Origin::signed(4), r, aye(4))); + assert_ok!(Democracy::vote(Origin::signed(5), r, aye(5))); + assert_ok!(Democracy::vote(Origin::signed(6), r, aye(6))); assert_eq!(Democracy::tally(r), (21, 0, 21)); @@ -1204,8 +1285,8 @@ fn controversial_low_turnout_voting_should_work() { VoteThreshold::SuperMajorityApprove, 0 ); - assert_ok!(Democracy::vote(Origin::signed(5), r, BIG_NAY)); - assert_ok!(Democracy::vote(Origin::signed(6), r, BIG_AYE)); + assert_ok!(Democracy::vote(Origin::signed(5), r, big_nay(5))); + assert_ok!(Democracy::vote(Origin::signed(6), r, big_aye(6))); assert_eq!(Democracy::tally(r), (60, 50, 110)); @@ -1229,9 +1310,9 @@ fn passing_low_turnout_voting_should_work() { VoteThreshold::SuperMajorityApprove, 0 ); - 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_ok!(Democracy::vote(Origin::signed(4), r, big_aye(4))); + assert_ok!(Democracy::vote(Origin::signed(5), r, big_nay(5))); + assert_ok!(Democracy::vote(Origin::signed(6), r, big_aye(6))); assert_eq!(Democracy::tally(r), (100, 50, 150)); @@ -1380,3 +1461,4 @@ fn lock_voting_should_work_with_delegation() { assert_eq!(Balances::free_balance(42), 2); }); } +*/ From 0a1b9aa620c4f863b6d3773aa596c5f762b31733 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 20 Mar 2020 11:04:10 +0100 Subject: [PATCH 11/22] Fix up & repot tests --- frame/democracy/src/lib.rs | 44 +- frame/democracy/src/tests.rs | 1249 +---------------- frame/democracy/src/tests/cancellation.rs | 94 ++ frame/democracy/src/tests/delegation.rs | 174 +++ .../democracy/src/tests/external_proposing.rs | 289 ++++ frame/democracy/src/tests/fast_tracking.rs | 88 ++ frame/democracy/src/tests/lock_voting.rs | 163 +++ frame/democracy/src/tests/proxying.rs | 81 ++ frame/democracy/src/tests/public_proposals.rs | 104 ++ frame/democracy/src/tests/scheduling.rs | 115 ++ frame/democracy/src/tests/voting.rs | 137 ++ 11 files changed, 1292 insertions(+), 1246 deletions(-) create mode 100644 frame/democracy/src/tests/cancellation.rs create mode 100644 frame/democracy/src/tests/delegation.rs create mode 100644 frame/democracy/src/tests/external_proposing.rs create mode 100644 frame/democracy/src/tests/fast_tracking.rs create mode 100644 frame/democracy/src/tests/lock_voting.rs create mode 100644 frame/democracy/src/tests/proxying.rs create mode 100644 frame/democracy/src/tests/public_proposals.rs create mode 100644 frame/democracy/src/tests/scheduling.rs create mode 100644 frame/democracy/src/tests/voting.rs diff --git a/frame/democracy/src/lib.rs b/frame/democracy/src/lib.rs index 12b8e23a4028e..47578ce861d49 100644 --- a/frame/democracy/src/lib.rs +++ b/frame/democracy/src/lib.rs @@ -463,6 +463,8 @@ decl_error! { VotesExist, /// The instant referendum origin is currently disallowed. InstantNotAllowed, + /// Delegation to oneself makes no sense. + Nonsense, } } @@ -1097,7 +1099,8 @@ decl_module! { /// *overlocking* (where the two locks are combined into a single lock that is the maximum /// of both the amount locked and the time is it locked for). /// - /// The dispatch origin of this call must be _Signed_. and the + /// The dispatch origin of this call must be _Signed_, and the signer must have a vote + /// registered for referendum `index`. /// /// - `index`: The index of referendum of the vote to be removed. /// @@ -1105,13 +1108,17 @@ decl_module! { /// - `O(R + log R)` where R is the number of referenda that `target` has voted on. /// # #[weight = SimpleDispatchInfo::FixedNormal(10_000)] - fn unvote(origin, index: ReferendumIndex) -> DispatchResult { + fn remove_vote(origin, index: ReferendumIndex) -> DispatchResult { let who = ensure_signed(origin)?; - Self::try_unvote(&who, index, UnvoteScope::Any) + Self::try_remove_vote(&who, index, UnvoteScope::Any) } - /// Remove a vote for a referendum that has expired, either because it was cancelled, - /// because the voter lost the referendum or because the conviction period is over. + /// Remove a vote for a referendum. + /// + /// If the `target` is equal to the signer, then this function is exactly equivalent to + /// `remove_vote`. If not equal to the signer, then the vote must have expired, + /// either because the referendum was cancelled, because the voter lost the referendum or + /// because the conviction period is over. /// /// The dispatch origin of this call must be _Signed_. /// @@ -1123,9 +1130,10 @@ decl_module! { /// - `O(R + log R)` where R is the number of referenda that `target` has voted on. /// # #[weight = SimpleDispatchInfo::FixedNormal(10_000)] - fn reap_vote(origin, target: T::AccountId, index: ReferendumIndex) -> DispatchResult { - ensure_signed(origin)?; - Self::try_unvote(&target, index, UnvoteScope::OnlyExpired) + fn remove_other_vote(origin, target: T::AccountId, index: ReferendumIndex) -> DispatchResult { + let who = ensure_signed(origin)?; + let scope = if target == who { UnvoteScope::Any } else { UnvoteScope::OnlyExpired }; + Self::try_remove_vote(&target, index, scope) } /// Delegate the voting power (with some given conviction) of a proxied account. @@ -1182,11 +1190,24 @@ decl_module! { Self::try_undelegate(target)?; } + /// Remove a proxied vote for a referendum. + /// + /// Exactly equivalent to `remove_vote` except that it operates on the account that the + /// sender is a proxy for. + /// + /// The dispatch origin of this call must be _Signed_ and the signing account must be a + /// proxy for some other account which has a registered vote for the referendum of `index`. + /// + /// - `index`: The index of referendum of the vote to be removed. + /// + /// # + /// - `O(R + log R)` where R is the number of referenda that `target` has voted on. + /// # #[weight = SimpleDispatchInfo::FixedNormal(10_000)] - fn proxy_unvote(origin, index: ReferendumIndex) -> DispatchResult { + fn proxy_remove_vote(origin, index: ReferendumIndex) -> DispatchResult { let who = ensure_signed(origin)?; let target = Self::proxy(who).and_then(|a| a.as_active()).ok_or(Error::::NotProxy)?; - Self::try_unvote(&target, index, UnvoteScope::Any) + Self::try_remove_vote(&target, index, UnvoteScope::Any) } } } @@ -1332,7 +1353,7 @@ impl Module { /// - The referendum has finished and the voter's lock period is up. /// /// This will generally be combined with a call to `unlock`. - fn try_unvote(who: &T::AccountId, ref_index: ReferendumIndex, scope: UnvoteScope) -> DispatchResult { + fn try_remove_vote(who: &T::AccountId, ref_index: ReferendumIndex, scope: UnvoteScope) -> DispatchResult { let info = ReferendumInfoOf::::get(ref_index); VotingOf::::try_mutate(who, |voting| -> DispatchResult { if let Voting::Direct { ref mut votes, delegations, ref mut prior } = voting { @@ -1412,6 +1433,7 @@ impl Module { conviction: Conviction, balance: BalanceOf, ) -> DispatchResult { + ensure!(who != target, Error::::Nonsense); ensure!(balance <= T::Currency::free_balance(&who), Error::::TooMuch); VotingOf::::try_mutate(&who, |voting| -> DispatchResult { let mut old = Voting::Delegating { diff --git a/frame/democracy/src/tests.rs b/frame/democracy/src/tests.rs index 8735dc2c8c7ff..307d70121772e 100644 --- a/frame/democracy/src/tests.rs +++ b/frame/democracy/src/tests.rs @@ -25,12 +25,23 @@ use frame_support::{ }; use sp_core::H256; use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup, Bounded, BadOrigin}, + traits::{BlakeTwo256, IdentityLookup, BadOrigin}, testing::Header, Perbill, }; use pallet_balances::{BalanceLock, Error as BalancesError}; use frame_system::EnsureSignedBy; +mod cancellation; +mod delegation; +mod external_proposing; +mod fast_tracking; +mod lock_voting; +mod preimage; +mod proxying; +mod public_proposals; +mod scheduling; +mod voting; + 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 }; @@ -227,1238 +238,6 @@ fn big_nay(who: u64) -> AccountVote { AccountVote::Standard { vote: BIG_NAY, balance: Balances::free_balance(&who) } } -#[test] -fn missing_preimage_should_fail() { - new_test_ext().execute_with(|| { - System::set_block_number(1); - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash(2), - VoteThreshold::SuperMajorityApprove, - 0 - ); - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - - next_block(); - next_block(); - - assert_eq!(Balances::free_balance(42), 0); - }); -} - -#[test] -fn preimage_deposit_should_be_required_and_returned() { - new_test_ext().execute_with(|| { - System::set_block_number(1); - // fee of 100 is too much. - PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 100); - assert_noop!( - Democracy::note_preimage(Origin::signed(6), vec![0; 500]), - BalancesError::::InsufficientBalance, - ); - // fee of 1 is reasonable. - PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 1); - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 0 - ); - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - - assert_eq!(Balances::reserved_balance(6), 12); - - next_block(); - next_block(); - - assert_eq!(Balances::reserved_balance(6), 0); - assert_eq!(Balances::free_balance(6), 60); - assert_eq!(Balances::free_balance(42), 2); - }); -} - -#[test] -fn preimage_deposit_should_be_reapable_earlier_by_owner() { - new_test_ext().execute_with(|| { - System::set_block_number(1); - PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 1); - assert_ok!(Democracy::note_preimage(Origin::signed(6), set_balance_proposal(2))); - - assert_eq!(Balances::reserved_balance(6), 12); - - next_block(); - assert_noop!( - Democracy::reap_preimage(Origin::signed(6), set_balance_proposal_hash(2)), - Error::::TooEarly - ); - next_block(); - assert_ok!(Democracy::reap_preimage(Origin::signed(6), set_balance_proposal_hash(2))); - - assert_eq!(Balances::free_balance(6), 60); - assert_eq!(Balances::reserved_balance(6), 0); - }); -} - -#[test] -fn preimage_deposit_should_be_reapable() { - new_test_ext().execute_with(|| { - System::set_block_number(1); - assert_noop!( - Democracy::reap_preimage(Origin::signed(5), set_balance_proposal_hash(2)), - Error::::PreimageMissing - ); - - PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 1); - assert_ok!(Democracy::note_preimage(Origin::signed(6), set_balance_proposal(2))); - assert_eq!(Balances::reserved_balance(6), 12); - - next_block(); - next_block(); - next_block(); - assert_noop!( - Democracy::reap_preimage(Origin::signed(5), set_balance_proposal_hash(2)), - Error::::TooEarly - ); - - next_block(); - assert_ok!(Democracy::reap_preimage(Origin::signed(5), set_balance_proposal_hash(2))); - assert_eq!(Balances::reserved_balance(6), 0); - assert_eq!(Balances::free_balance(6), 48); - assert_eq!(Balances::free_balance(5), 62); - }); -} - -#[test] -fn noting_imminent_preimage_for_free_should_work() { - new_test_ext().execute_with(|| { - System::set_block_number(1); - PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 1); - - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash(2), - VoteThreshold::SuperMajorityApprove, - 1 - ); - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - - assert_noop!( - Democracy::note_imminent_preimage(Origin::signed(7), set_balance_proposal(2)), - Error::::NotImminent - ); - - next_block(); - - // Now we're in the dispatch queue it's all good. - assert_ok!(Democracy::note_imminent_preimage(Origin::signed(7), set_balance_proposal(2))); - - next_block(); - - assert_eq!(Balances::free_balance(42), 2); - }); -} - -#[test] -fn reaping_imminent_preimage_should_fail() { - new_test_ext().execute_with(|| { - System::set_block_number(1); - let h = set_balance_proposal_hash_and_note(2); - let r = Democracy::inject_referendum(3, h, VoteThreshold::SuperMajorityApprove, 1); - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - next_block(); - next_block(); - // now imminent. - assert_noop!(Democracy::reap_preimage(Origin::signed(6), h), Error::::Imminent); - }); -} - -#[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), - set_balance_proposal_hash_and_note(1), - )); - assert_ok!(propose_set_balance_and_note(6, 2, 2)); - - fast_forward_to(2); - - // both waiting: external goes first. - assert_eq!( - Democracy::referendum_status(0), - Ok(ReferendumStatus { - end: 4, - proposal_hash: set_balance_proposal_hash_and_note(1), - threshold: VoteThreshold::SuperMajorityApprove, - delay: 2, - tally: Tally { ayes: 0, nays: 0, turnout: 0 }, - }) - ); - // replenish external - assert_ok!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash_and_note(3), - )); - - fast_forward_to(4); - - // both waiting: public goes next. - assert_eq!( - Democracy::referendum_status(1), - Ok(ReferendumStatus { - end: 6, - proposal_hash: set_balance_proposal_hash_and_note(2), - threshold: VoteThreshold::SuperMajorityApprove, - delay: 2, - tally: Tally { ayes: 0, nays: 0, turnout: 0 }, - }) - ); - // don't replenish public - - fast_forward_to(6); - - // it's external "turn" again, though since public is empty that doesn't really matter - assert_eq!( - Democracy::referendum_status(2), - Ok(ReferendumStatus { - end: 8, - proposal_hash: set_balance_proposal_hash_and_note(3), - threshold: VoteThreshold::SuperMajorityApprove, - delay: 2, - tally: Tally { ayes: 0, nays: 0, turnout: 0 }, - }) - ); - // replenish external - assert_ok!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash_and_note(5), - )); - - fast_forward_to(8); - - // external goes again because there's no public waiting. - assert_eq!( - Democracy::referendum_status(3), - Ok(ReferendumStatus { - end: 10, - proposal_hash: set_balance_proposal_hash_and_note(5), - threshold: VoteThreshold::SuperMajorityApprove, - delay: 2, - tally: Tally { ayes: 0, nays: 0, turnout: 0 }, - }) - ); - // replenish both - assert_ok!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash_and_note(7), - )); - assert_ok!(propose_set_balance_and_note(6, 4, 2)); - - fast_forward_to(10); - - // public goes now since external went last time. - assert_eq!( - Democracy::referendum_status(4), - Ok(ReferendumStatus { - end: 12, - proposal_hash: set_balance_proposal_hash_and_note(4), - threshold: VoteThreshold::SuperMajorityApprove, - delay: 2, - tally: Tally { ayes: 0, nays: 0, turnout: 0 }, - }) - ); - // replenish public again - assert_ok!(propose_set_balance_and_note(6, 6, 2)); - // cancel external - let h = set_balance_proposal_hash_and_note(7); - assert_ok!(Democracy::veto_external(Origin::signed(3), h)); - - fast_forward_to(12); - - // public goes again now since there's no external waiting. - assert_eq!( - Democracy::referendum_status(5), - Ok(ReferendumStatus { - end: 14, - proposal_hash: set_balance_proposal_hash_and_note(6), - threshold: VoteThreshold::SuperMajorityApprove, - delay: 2, - tally: Tally { ayes: 0, nays: 0, turnout: 0 }, - }) - ); - }); -} - -#[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_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 2 - ); - assert!(Democracy::referendum_status(r).is_ok()); - - assert_noop!(Democracy::emergency_cancel(Origin::signed(3), r), BadOrigin); - 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_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 2 - ); - assert!(Democracy::referendum_status(r).is_ok()); - assert_noop!( - Democracy::emergency_cancel(Origin::signed(4), r), - Error::::AlreadyCanceled, - ); - }); -} - -#[test] -fn veto_external_works() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - assert_ok!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash_and_note(2), - )); - assert!(>::exists()); - - let h = set_balance_proposal_hash_and_note(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), - set_balance_proposal_hash(2), - ), Error::::ProposalBlacklisted); - - fast_forward_to(1); - // fails as we're still in cooloff period. - assert_noop!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash(2), - ), Error::::ProposalBlacklisted); - - fast_forward_to(2); - // works; as we're out of the cooloff period. - assert_ok!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash_and_note(2), - )); - assert!(>::exists()); - - // 3 can't veto the same thing twice. - assert_noop!( - Democracy::veto_external(Origin::signed(3), h.clone()), - Error::::AlreadyVetoed - ); - - // 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), - set_balance_proposal_hash(2), - ), Error::::ProposalBlacklisted); - // different proposal works fine. - assert_ok!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash_and_note(3), - )); - }); -} - -#[test] -fn external_referendum_works() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - assert_noop!( - Democracy::external_propose( - Origin::signed(1), - set_balance_proposal_hash(2), - ), - BadOrigin, - ); - assert_ok!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash_and_note(2), - )); - assert_noop!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash(1), - ), Error::::DuplicateProposal); - fast_forward_to(2); - assert_eq!( - Democracy::referendum_status(0), - Ok(ReferendumStatus { - end: 4, - proposal_hash: set_balance_proposal_hash(2), - threshold: VoteThreshold::SuperMajorityApprove, - delay: 2, - tally: Tally { ayes: 0, nays: 0, turnout: 0 }, - }) - ); - }); -} - -#[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), - set_balance_proposal_hash(2) - ), - BadOrigin, - ); - assert_ok!(Democracy::external_propose_majority( - Origin::signed(3), - set_balance_proposal_hash_and_note(2) - )); - fast_forward_to(2); - assert_eq!( - Democracy::referendum_status(0), - Ok(ReferendumStatus { - end: 4, - proposal_hash: set_balance_proposal_hash(2), - threshold: VoteThreshold::SimpleMajority, - delay: 2, - tally: Tally { ayes: 0, nays: 0, turnout: 0 }, - }) - ); - }); -} - -#[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), - set_balance_proposal_hash(2) - ), - BadOrigin, - ); - assert_ok!(Democracy::external_propose_default( - Origin::signed(1), - set_balance_proposal_hash_and_note(2) - )); - fast_forward_to(2); - assert_eq!( - Democracy::referendum_status(0), - Ok(ReferendumStatus { - end: 4, - proposal_hash: set_balance_proposal_hash(2), - threshold: VoteThreshold::SuperMajorityAgainst, - delay: 2, - tally: Tally { ayes: 0, nays: 0, turnout: 0 }, - }) - ); - }); -} - -#[test] -fn fast_track_referendum_works() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - let h = set_balance_proposal_hash_and_note(2); - assert_noop!(Democracy::fast_track(Origin::signed(5), h, 3, 2), Error::::ProposalMissing); - assert_ok!(Democracy::external_propose_majority( - Origin::signed(3), - set_balance_proposal_hash_and_note(2) - )); - assert_noop!(Democracy::fast_track(Origin::signed(1), h, 3, 2), BadOrigin); - assert_ok!(Democracy::fast_track(Origin::signed(5), h, 2, 0)); - assert_eq!( - Democracy::referendum_status(0), - Ok(ReferendumStatus { - end: 2, - proposal_hash: set_balance_proposal_hash_and_note(2), - threshold: VoteThreshold::SimpleMajority, - delay: 0, - tally: Tally { ayes: 0, nays: 0, turnout: 0 }, - }) - ); - }); -} - -#[test] -fn instant_referendum_works() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - let h = set_balance_proposal_hash_and_note(2); - assert_noop!(Democracy::fast_track(Origin::signed(5), h, 3, 2), Error::::ProposalMissing); - assert_ok!(Democracy::external_propose_majority( - Origin::signed(3), - set_balance_proposal_hash_and_note(2) - )); - assert_noop!(Democracy::fast_track(Origin::signed(1), h, 3, 2), BadOrigin); - assert_noop!(Democracy::fast_track(Origin::signed(5), h, 1, 0), BadOrigin); - assert_noop!(Democracy::fast_track(Origin::signed(6), h, 1, 0), Error::::InstantNotAllowed); - INSTANT_ALLOWED.with(|v| *v.borrow_mut() = true); - assert_ok!(Democracy::fast_track(Origin::signed(6), h, 1, 0)); - assert_eq!( - Democracy::referendum_status(0), - Ok(ReferendumStatus { - end: 1, - proposal_hash: set_balance_proposal_hash_and_note(2), - threshold: VoteThreshold::SimpleMajority, - delay: 0, - tally: Tally { ayes: 0, nays: 0, turnout: 0 }, - }) - ); - }); -} - -#[test] -fn fast_track_referendum_fails_when_no_simple_majority() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - let h = set_balance_proposal_hash_and_note(2); - assert_ok!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash_and_note(2) - )); - assert_noop!( - Democracy::fast_track(Origin::signed(5), h, 3, 2), - Error::::NotSimpleMajority - ); - }); -} - -#[test] -fn backing_for_should_work() { - new_test_ext().execute_with(|| { - System::set_block_number(1); - assert_ok!(propose_set_balance_and_note(1, 2, 2)); - assert_ok!(propose_set_balance_and_note(1, 4, 4)); - assert_ok!(propose_set_balance_and_note(1, 3, 3)); - assert_eq!(Democracy::backing_for(0), Some(2)); - assert_eq!(Democracy::backing_for(1), Some(4)); - assert_eq!(Democracy::backing_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_and_note(1, 2, 1)); - assert!(Democracy::referendum_info(0).is_none()); - - // start of 2 => next referendum scheduled. - fast_forward_to(2); - - let r = 0; - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - - assert_eq!(Democracy::referendum_count(), 1); - assert_eq!( - Democracy::referendum_status(0), - Ok(ReferendumStatus { - end: 4, - proposal_hash: set_balance_proposal_hash_and_note(2), - threshold: VoteThreshold::SuperMajorityApprove, - delay: 2, - tally: Tally { ayes: 1, nays: 0, turnout: 1 }, - }) - ); - - fast_forward_to(3); - - // referendum still running - assert!(Democracy::referendum_status(0).is_ok()); - - // referendum runs during 2 and 3, ends @ start of 4. - fast_forward_to(4); - - assert!(Democracy::referendum_status(0).is_err()); - assert_eq!(Democracy::dispatch_queue(), vec![ - (6, set_balance_proposal_hash_and_note(2), 0) - ]); - - // referendum passes and wait another two blocks for enactment. - fast_forward_to(6); - - 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_and_note(1, 2, 1)); - - // start of 2 => next referendum scheduled. - fast_forward_to(2); - - assert_ok!(Democracy::vote(Origin::signed(1), 0, aye(1))); - - fast_forward_to(4); - - assert_eq!(Democracy::dispatch_queue(), vec![ - (6, set_balance_proposal_hash_and_note(2), 0) - ]); - - assert_noop!(Democracy::cancel_queued(Origin::ROOT, 1), Error::::ProposalMissing); - assert_ok!(Democracy::cancel_queued(Origin::ROOT, 0)); - assert_eq!(Democracy::dispatch_queue(), vec![]); - }); -} - -#[test] -fn proxy_should_work() { - new_test_ext().execute_with(|| { - assert_eq!(Democracy::proxy(10), None); - assert!(System::allow_death(&10)); - - assert_noop!(Democracy::activate_proxy(Origin::signed(1), 10), Error::::NotOpen); - - assert_ok!(Democracy::open_proxy(Origin::signed(10), 1)); - assert!(!System::allow_death(&10)); - assert_eq!(Democracy::proxy(10), Some(ProxyState::Open(1))); - - assert_noop!(Democracy::activate_proxy(Origin::signed(2), 10), Error::::WrongOpen); - assert_ok!(Democracy::activate_proxy(Origin::signed(1), 10)); - assert_eq!(Democracy::proxy(10), Some(ProxyState::Active(1))); - - // Can't set when already set. - assert_noop!(Democracy::activate_proxy(Origin::signed(2), 10), Error::::AlreadyProxy); - - // But this works because 11 isn't proxying. - assert_ok!(Democracy::open_proxy(Origin::signed(11), 2)); - assert_ok!(Democracy::activate_proxy(Origin::signed(2), 11)); - assert_eq!(Democracy::proxy(10), Some(ProxyState::Active(1))); - assert_eq!(Democracy::proxy(11), Some(ProxyState::Active(2))); - - // 2 cannot fire 1's proxy: - assert_noop!(Democracy::deactivate_proxy(Origin::signed(2), 10), Error::::WrongProxy); - - // 1 deactivates their proxy: - assert_ok!(Democracy::deactivate_proxy(Origin::signed(1), 10)); - assert_eq!(Democracy::proxy(10), Some(ProxyState::Open(1))); - // but the proxy account cannot be killed until the proxy is closed. - assert!(!System::allow_death(&10)); - - // and then 10 closes it completely: - assert_ok!(Democracy::close_proxy(Origin::signed(10))); - assert_eq!(Democracy::proxy(10), None); - assert!(System::allow_death(&10)); - - // 11 just closes without 2's "permission". - assert_ok!(Democracy::close_proxy(Origin::signed(11))); - assert_eq!(Democracy::proxy(11), None); - assert!(System::allow_death(&11)); - }); -} - -#[test] -fn single_proposal_should_work_with_proxy() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - assert_ok!(propose_set_balance_and_note(1, 2, 1)); - - fast_forward_to(2); - let r = 0; - assert_ok!(Democracy::open_proxy(Origin::signed(10), 1)); - assert_ok!(Democracy::activate_proxy(Origin::signed(1), 10)); - assert_ok!(Democracy::proxy_vote(Origin::signed(10), r, aye(1))); - - assert_eq!(Democracy::referendum_status(r).unwrap().tally, Tally { ayes: 1, nays: 0, turnout: 1 }); - - fast_forward_to(6); - 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_and_note(1, 2, 1)); - - fast_forward_to(2); - - // Delegate first vote. - assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::None, 20)); - let r = 0; - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - assert_eq!(Democracy::referendum_status(r).unwrap().tally, Tally { ayes: 3, nays: 0, turnout: 1 }); - - // Delegate a second vote. - assert_ok!(Democracy::delegate(Origin::signed(3), 1, Conviction::None, 30)); - assert_eq!(Democracy::referendum_status(r).unwrap().tally, Tally { ayes: 6, nays: 0, turnout: 1 }); - - // Reduce first vote. - assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::None, 10)); - assert_eq!(Democracy::referendum_status(r).unwrap().tally, Tally { ayes: 5, nays: 0, turnout: 1 }); - - // Second vote delegates to first; we don't do tiered delegation, so it doesn't get used. - assert_ok!(Democracy::delegate(Origin::signed(3), 2, Conviction::None, 30)); - assert_eq!(Democracy::referendum_status(r).unwrap().tally, Tally { ayes: 2, nays: 0, turnout: 1 }); - - // Main voter cancels their vote - assert_ok!(Democracy::unvote(Origin::signed(1), r)); - assert_eq!(Democracy::referendum_status(r).unwrap().tally, Tally { ayes: 0, nays: 0, turnout: 0 }); - - // First delegator delegates half funds with conviction; nothing changes yet. - assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::Locked1x, 10)); - assert_eq!(Democracy::referendum_status(r).unwrap().tally, Tally { ayes: 0, nays: 0, turnout: 0 }); - - // Main voter reinstates their vote - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - assert_eq!(Democracy::referendum_status(r).unwrap().tally, Tally { ayes: 11, nays: 0, turnout: 1 }); - - fast_forward_to(6); - - 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_and_note(1, 2, 1)); - - fast_forward_to(2); - - // 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(1))); - assert_eq!(Democracy::voters_for(r), vec![1]); - - // Delegated vote is counted. - assert_eq!(Democracy::tally(r), (6, 0, 6)); - - fast_forward_to(6); - - assert_eq!(Balances::free_balance(42), 2); - }); -} - -#[test] -/// If transactor already voted, delegated vote is overwritten. -fn single_proposal_should_work_with_vote_and_delegation() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - - assert_ok!(propose_set_balance_and_note(1, 2, 1)); - - fast_forward_to(2); - - let r = 0; - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - // Vote. - assert_ok!(Democracy::vote(Origin::signed(2), r, aye(2)); - // 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(6); - - 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_and_note(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(2); - let r = 0; - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - - 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(6); - - assert_eq!(Balances::free_balance(42), 2); - }); -} - -#[test] -/// If transactor voted, delegated vote is overwritten. -fn single_proposal_should_work_with_delegation_and_vote() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - - assert_ok!(propose_set_balance_and_note(1, 2, 1)); - - fast_forward_to(2); - let r = 0; - - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - - // Delegate vote. - assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::max_value())); - - // Vote. - assert_ok!(Democracy::vote(Origin::signed(2), r, aye(2)); - - 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(6); - - 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_and_note(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_and_note(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), Error::::ValueLow); - }); -} - -#[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), BalancesError::::InsufficientBalance); - }); -} - -#[test] -fn poor_seconder_should_not_work() { - new_test_ext().execute_with(|| { - System::set_block_number(1); - assert_ok!(propose_set_balance_and_note(2, 2, 11)); - assert_noop!(Democracy::second(Origin::signed(1), 0), BalancesError::::InsufficientBalance); - }); -} - -#[test] -fn runners_up_should_come_after() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - assert_ok!(propose_set_balance_and_note(1, 2, 2)); - assert_ok!(propose_set_balance_and_note(1, 4, 4)); - assert_ok!(propose_set_balance_and_note(1, 3, 3)); - fast_forward_to(2); - assert_ok!(Democracy::vote(Origin::signed(1), 0, aye(1))); - fast_forward_to(4); - assert_ok!(Democracy::vote(Origin::signed(1), 1, aye(1))); - fast_forward_to(6); - assert_ok!(Democracy::vote(Origin::signed(1), 2, aye(1))); - }); -} - -#[test] -fn ooo_inject_referendums_should_work() { - new_test_ext().execute_with(|| { - System::set_block_number(1); - let r1 = Democracy::inject_referendum( - 3, - set_balance_proposal_hash_and_note(3), - VoteThreshold::SuperMajorityApprove, - 0 - ); - let r2 = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 0 - ); - - assert_ok!(Democracy::vote(Origin::signed(1), r2, aye(1))); - assert_eq!(Democracy::voters_for(r2), vec![1]); - assert_eq!(Democracy::vote_of((r2, 1)), AYE); - assert_eq!(Democracy::tally(r2), (1, 0, 1)); - - next_block(); - assert_eq!(Balances::free_balance(42), 2); - - assert_ok!(Democracy::vote(Origin::signed(1), r1, aye(1))); - assert_eq!(Democracy::voters_for(r1), vec![1]); - assert_eq!(Democracy::vote_of((r1, 1)), AYE); - assert_eq!(Democracy::tally(r1), (1, 0, 1)); - - next_block(); - assert_eq!(Balances::free_balance(42), 3); - }); -} - -#[test] -fn simple_passing_should_work() { - new_test_ext().execute_with(|| { - System::set_block_number(1); - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 0 - ); - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - - 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( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 0 - ); - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - 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( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 0 - ); - assert_ok!(Democracy::vote(Origin::signed(1), r, nay(1))); - - 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( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 0 - ); - - assert_ok!(Democracy::vote(Origin::signed(1), r, big_aye(1))); - assert_ok!(Democracy::vote(Origin::signed(2), r, big_nay(2))); - assert_ok!(Democracy::vote(Origin::signed(3), r, big_nay(3))); - assert_ok!(Democracy::vote(Origin::signed(4), r, big_aye(4))); - assert_ok!(Democracy::vote(Origin::signed(5), r, big_nay(5))); - assert_ok!(Democracy::vote(Origin::signed(6), r, big_aye(6))); - - 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( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 1 - ); - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - assert_ok!(Democracy::vote(Origin::signed(2), r, aye(2))); - assert_ok!(Democracy::vote(Origin::signed(3), r, aye(3))); - assert_ok!(Democracy::vote(Origin::signed(4), r, aye(4))); - assert_ok!(Democracy::vote(Origin::signed(5), r, aye(5))); - assert_ok!(Democracy::vote(Origin::signed(6), r, aye(6))); - - 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( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 0 - ); - assert_ok!(Democracy::vote(Origin::signed(5), r, big_nay(5))); - assert_ok!(Democracy::vote(Origin::signed(6), r, big_aye(6))); - - 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( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 0 - ); - assert_ok!(Democracy::vote(Origin::signed(4), r, big_aye(4))); - assert_ok!(Democracy::vote(Origin::signed(5), r, big_nay(5))); - assert_ok!(Democracy::vote(Origin::signed(6), r, big_aye(6))); - - 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( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 0 - ); - 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(), - reasons: pallet_balances::Reasons::Misc, - }]); - assert_eq!(Democracy::locks(2), Some(18)); - assert_eq!(Balances::locks(3), vec![BalanceLock { - id: DEMOCRACY_ID, - amount: u64::max_value(), - reasons: pallet_balances::Reasons::Misc, - }]); - assert_eq!(Democracy::locks(3), Some(10)); - assert_eq!(Balances::locks(4), vec![BalanceLock { - id: DEMOCRACY_ID, - amount: u64::max_value(), - reasons: pallet_balances::Reasons::Misc, - }]); - assert_eq!(Democracy::locks(4), Some(6)); - assert_eq!(Balances::locks(5), vec![]); - - assert_eq!(Balances::free_balance(42), 2); - - assert_noop!(Democracy::unlock(Origin::signed(1), 1), Error::::NotLocked); - - fast_forward_to(5); - assert_noop!(Democracy::unlock(Origin::signed(1), 4), Error::::NotExpired); - fast_forward_to(6); - assert_ok!(Democracy::unlock(Origin::signed(1), 4)); - assert_noop!(Democracy::unlock(Origin::signed(1), 4), Error::::NotLocked); - - fast_forward_to(9); - assert_noop!(Democracy::unlock(Origin::signed(1), 3), Error::::NotExpired); - fast_forward_to(10); - assert_ok!(Democracy::unlock(Origin::signed(1), 3)); - assert_noop!(Democracy::unlock(Origin::signed(1), 3), Error::::NotLocked); - - fast_forward_to(17); - assert_noop!(Democracy::unlock(Origin::signed(1), 2), Error::::NotExpired); - fast_forward_to(18); - assert_ok!(Democracy::unlock(Origin::signed(1), 2)); - assert_noop!(Democracy::unlock(Origin::signed(1), 2), Error::::NotLocked); - }); -} - -#[test] -fn no_locks_without_conviction_should_work() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 0, - ); - assert_ok!(Democracy::vote(Origin::signed(1), r, Vote { - aye: true, - conviction: Conviction::None, - })); - - fast_forward_to(2); - - assert_eq!(Balances::free_balance(42), 2); - assert_eq!(Balances::locks(1), vec![]); - }); -} - -#[test] -fn lock_voting_should_work_with_delegation() { - new_test_ext().execute_with(|| { - System::set_block_number(1); - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 0 - ); - 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); - }); +fn tally(r: ReferendumIndex) -> Tally { + Democracy::referendum_status(r).unwrap().tally } -*/ diff --git a/frame/democracy/src/tests/cancellation.rs b/frame/democracy/src/tests/cancellation.rs new file mode 100644 index 0000000000000..c0e1b8b27ae40 --- /dev/null +++ b/frame/democracy/src/tests/cancellation.rs @@ -0,0 +1,94 @@ +// Copyright 2017-2020 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 . + +//! The tests for cancelation functionality. + +use super::*; + +#[test] +fn cancel_referendum_should_work() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 0 + ); + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + assert_ok!(Democracy::cancel_referendum(Origin::ROOT, r.into())); + + next_block(); + next_block(); + + assert_eq!(Balances::free_balance(42), 0); + }); +} + +#[test] +fn cancel_queued_should_work() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + assert_ok!(propose_set_balance_and_note(1, 2, 1)); + + // start of 2 => next referendum scheduled. + fast_forward_to(2); + + assert_ok!(Democracy::vote(Origin::signed(1), 0, aye(1))); + + fast_forward_to(4); + + assert_eq!(Democracy::dispatch_queue(), vec![ + (6, set_balance_proposal_hash_and_note(2), 0) + ]); + + assert_noop!(Democracy::cancel_queued(Origin::ROOT, 1), Error::::ProposalMissing); + assert_ok!(Democracy::cancel_queued(Origin::ROOT, 0)); + assert_eq!(Democracy::dispatch_queue(), vec![]); + }); +} + +#[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_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 2 + ); + assert!(Democracy::referendum_status(r).is_ok()); + + assert_noop!(Democracy::emergency_cancel(Origin::signed(3), r), BadOrigin); + 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_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 2 + ); + assert!(Democracy::referendum_status(r).is_ok()); + assert_noop!( + Democracy::emergency_cancel(Origin::signed(4), r), + Error::::AlreadyCanceled, + ); + }); +} diff --git a/frame/democracy/src/tests/delegation.rs b/frame/democracy/src/tests/delegation.rs new file mode 100644 index 0000000000000..8d3e90282627c --- /dev/null +++ b/frame/democracy/src/tests/delegation.rs @@ -0,0 +1,174 @@ +// Copyright 2017-2020 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 . + +//! The tests for functionality concerning delegation. + +use super::*; + +#[test] +fn single_proposal_should_work_with_delegation() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + + assert_ok!(propose_set_balance_and_note(1, 2, 1)); + + fast_forward_to(2); + + // Delegate first vote. + assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::None, 20)); + let r = 0; + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + assert_eq!(tally(r), Tally { ayes: 3, nays: 0, turnout: 1 }); + + // Delegate a second vote. + assert_ok!(Democracy::delegate(Origin::signed(3), 1, Conviction::None, 30)); + assert_eq!(tally(r), Tally { ayes: 6, nays: 0, turnout: 1 }); + + // Reduce first vote. + assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::None, 10)); + assert_eq!(tally(r), Tally { ayes: 5, nays: 0, turnout: 1 }); + + // Second vote delegates to first; we don't do tiered delegation, so it doesn't get used. + assert_ok!(Democracy::delegate(Origin::signed(3), 2, Conviction::None, 30)); + assert_eq!(tally(r), Tally { ayes: 2, nays: 0, turnout: 1 }); + + // Main voter cancels their vote + assert_ok!(Democracy::remove_vote(Origin::signed(1), r)); + assert_eq!(tally(r), Tally { ayes: 0, nays: 0, turnout: 0 }); + + // First delegator delegates half funds with conviction; nothing changes yet. + assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::Locked1x, 10)); + assert_eq!(tally(r), Tally { ayes: 0, nays: 0, turnout: 0 }); + + // Main voter reinstates their vote + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + assert_eq!(tally(r), Tally { ayes: 11, nays: 0, turnout: 1 }); + }); +} + +#[test] +fn self_delegation_not_allowed() { + new_test_ext().execute_with(|| { + assert_noop!( + Democracy::delegate(Origin::signed(1), 1, Conviction::None, 10), + Error::::Nonsense, + ); + }); +} + +#[test] +fn cyclic_delegation_should_unwind() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + + assert_ok!(propose_set_balance_and_note(1, 2, 1)); + + fast_forward_to(2); + + // Check behavior with cycle. + assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::None, 20)); + assert_ok!(Democracy::delegate(Origin::signed(3), 2, Conviction::None, 30)); + assert_ok!(Democracy::delegate(Origin::signed(1), 3, Conviction::None, 10)); + let r = 0; + assert_ok!(Democracy::undelegate(Origin::signed(3))); + assert_ok!(Democracy::vote(Origin::signed(3), r, aye(3))); + assert_ok!(Democracy::undelegate(Origin::signed(1))); + assert_ok!(Democracy::vote(Origin::signed(1), r, nay(1))); + + // Delegated vote is counted. + assert_eq!(tally(r), Tally { ayes: 3, nays: 3, turnout: 4 }); + }); +} + +#[test] +fn single_proposal_should_work_with_vote_and_delegation() { + // If transactor already voted, delegated vote is overwritten. + new_test_ext().execute_with(|| { + System::set_block_number(0); + + assert_ok!(propose_set_balance_and_note(1, 2, 1)); + + fast_forward_to(2); + + let r = 0; + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + assert_ok!(Democracy::vote(Origin::signed(2), r, nay(2))); + assert_eq!(tally(r), Tally { ayes: 1, nays: 2, turnout: 3 }); + + // Delegate vote. + assert_ok!(Democracy::remove_vote(Origin::signed(2), r)); + assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::None, 20)); + // Delegated vote replaces the explicit vote. + assert_eq!(tally(r), Tally { ayes: 3, nays: 0, turnout: 1 }); + }); +} + +#[test] +fn single_proposal_should_work_with_undelegation() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + + assert_ok!(propose_set_balance_and_note(1, 2, 1)); + + // Delegate and undelegate vote. + assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::None, 20)); + assert_ok!(Democracy::undelegate(Origin::signed(2))); + + fast_forward_to(2); + let r = 0; + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + + // Delegated vote is not counted. + assert_eq!(tally(r), Tally { ayes: 1, nays: 0, turnout: 1 }); + }); +} + +#[test] +fn single_proposal_should_work_with_delegation_and_vote() { + // If transactor voted, delegated vote is overwritten. + new_test_ext().execute_with(|| { + System::set_block_number(0); + assert_ok!(propose_set_balance_and_note(1, 2, 1)); + fast_forward_to(2); + let r = 0; + + // Delegate, undelegate and vote. + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::None, 20)); + assert_eq!(tally(r), Tally { ayes: 3, nays: 0, turnout: 1 }); + assert_ok!(Democracy::undelegate(Origin::signed(2))); + assert_ok!(Democracy::vote(Origin::signed(2), r, aye(2))); + // Delegated vote is not counted. + assert_eq!(tally(r), Tally { ayes: 3, nays: 0, turnout: 3 }); + }); +} + +#[test] +fn conviction_should_be_honored_in_delegation() { + // If transactor voted, delegated vote is overwritten. + new_test_ext().execute_with(|| { + System::set_block_number(0); + assert_ok!(propose_set_balance_and_note(1, 2, 1)); + fast_forward_to(2); + let r = 0; + + // Delegate, undelegate and vote. + assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::Locked6x, 20)); + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + // Delegated vote is huge. + assert_eq!(tally(r), Tally { ayes: 121, nays: 0, turnout: 1 }); + }); +} diff --git a/frame/democracy/src/tests/external_proposing.rs b/frame/democracy/src/tests/external_proposing.rs new file mode 100644 index 0000000000000..a249a806ee9ed --- /dev/null +++ b/frame/democracy/src/tests/external_proposing.rs @@ -0,0 +1,289 @@ +// Copyright 2017-2020 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 . + +//! The tests for functionality concerning the "external" origin. + +use super::*; + +#[test] +fn veto_external_works() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + assert_ok!(Democracy::external_propose( + Origin::signed(2), + set_balance_proposal_hash_and_note(2), + )); + assert!(>::exists()); + + let h = set_balance_proposal_hash_and_note(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), + set_balance_proposal_hash(2), + ), Error::::ProposalBlacklisted); + + fast_forward_to(1); + // fails as we're still in cooloff period. + assert_noop!(Democracy::external_propose( + Origin::signed(2), + set_balance_proposal_hash(2), + ), Error::::ProposalBlacklisted); + + fast_forward_to(2); + // works; as we're out of the cooloff period. + assert_ok!(Democracy::external_propose( + Origin::signed(2), + set_balance_proposal_hash_and_note(2), + )); + assert!(>::exists()); + + // 3 can't veto the same thing twice. + assert_noop!( + Democracy::veto_external(Origin::signed(3), h.clone()), + Error::::AlreadyVetoed + ); + + // 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), + set_balance_proposal_hash(2), + ), Error::::ProposalBlacklisted); + // different proposal works fine. + assert_ok!(Democracy::external_propose( + Origin::signed(2), + set_balance_proposal_hash_and_note(3), + )); + }); +} + +#[test] +fn external_referendum_works() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + assert_noop!( + Democracy::external_propose( + Origin::signed(1), + set_balance_proposal_hash(2), + ), + BadOrigin, + ); + assert_ok!(Democracy::external_propose( + Origin::signed(2), + set_balance_proposal_hash_and_note(2), + )); + assert_noop!(Democracy::external_propose( + Origin::signed(2), + set_balance_proposal_hash(1), + ), Error::::DuplicateProposal); + fast_forward_to(2); + assert_eq!( + Democracy::referendum_status(0), + Ok(ReferendumStatus { + end: 4, + proposal_hash: set_balance_proposal_hash(2), + threshold: VoteThreshold::SuperMajorityApprove, + delay: 2, + tally: Tally { ayes: 0, nays: 0, turnout: 0 }, + }) + ); + }); +} + +#[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), + set_balance_proposal_hash(2) + ), + BadOrigin, + ); + assert_ok!(Democracy::external_propose_majority( + Origin::signed(3), + set_balance_proposal_hash_and_note(2) + )); + fast_forward_to(2); + assert_eq!( + Democracy::referendum_status(0), + Ok(ReferendumStatus { + end: 4, + proposal_hash: set_balance_proposal_hash(2), + threshold: VoteThreshold::SimpleMajority, + delay: 2, + tally: Tally { ayes: 0, nays: 0, turnout: 0 }, + }) + ); + }); +} + +#[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), + set_balance_proposal_hash(2) + ), + BadOrigin, + ); + assert_ok!(Democracy::external_propose_default( + Origin::signed(1), + set_balance_proposal_hash_and_note(2) + )); + fast_forward_to(2); + assert_eq!( + Democracy::referendum_status(0), + Ok(ReferendumStatus { + end: 4, + proposal_hash: set_balance_proposal_hash(2), + threshold: VoteThreshold::SuperMajorityAgainst, + delay: 2, + tally: Tally { ayes: 0, nays: 0, turnout: 0 }, + }) + ); + }); +} + + +#[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), + set_balance_proposal_hash_and_note(1), + )); + assert_ok!(propose_set_balance_and_note(6, 2, 2)); + + fast_forward_to(2); + + // both waiting: external goes first. + assert_eq!( + Democracy::referendum_status(0), + Ok(ReferendumStatus { + end: 4, + proposal_hash: set_balance_proposal_hash_and_note(1), + threshold: VoteThreshold::SuperMajorityApprove, + delay: 2, + tally: Tally { ayes: 0, nays: 0, turnout: 0 }, + }) + ); + // replenish external + assert_ok!(Democracy::external_propose( + Origin::signed(2), + set_balance_proposal_hash_and_note(3), + )); + + fast_forward_to(4); + + // both waiting: public goes next. + assert_eq!( + Democracy::referendum_status(1), + Ok(ReferendumStatus { + end: 6, + proposal_hash: set_balance_proposal_hash_and_note(2), + threshold: VoteThreshold::SuperMajorityApprove, + delay: 2, + tally: Tally { ayes: 0, nays: 0, turnout: 0 }, + }) + ); + // don't replenish public + + fast_forward_to(6); + + // it's external "turn" again, though since public is empty that doesn't really matter + assert_eq!( + Democracy::referendum_status(2), + Ok(ReferendumStatus { + end: 8, + proposal_hash: set_balance_proposal_hash_and_note(3), + threshold: VoteThreshold::SuperMajorityApprove, + delay: 2, + tally: Tally { ayes: 0, nays: 0, turnout: 0 }, + }) + ); + // replenish external + assert_ok!(Democracy::external_propose( + Origin::signed(2), + set_balance_proposal_hash_and_note(5), + )); + + fast_forward_to(8); + + // external goes again because there's no public waiting. + assert_eq!( + Democracy::referendum_status(3), + Ok(ReferendumStatus { + end: 10, + proposal_hash: set_balance_proposal_hash_and_note(5), + threshold: VoteThreshold::SuperMajorityApprove, + delay: 2, + tally: Tally { ayes: 0, nays: 0, turnout: 0 }, + }) + ); + // replenish both + assert_ok!(Democracy::external_propose( + Origin::signed(2), + set_balance_proposal_hash_and_note(7), + )); + assert_ok!(propose_set_balance_and_note(6, 4, 2)); + + fast_forward_to(10); + + // public goes now since external went last time. + assert_eq!( + Democracy::referendum_status(4), + Ok(ReferendumStatus { + end: 12, + proposal_hash: set_balance_proposal_hash_and_note(4), + threshold: VoteThreshold::SuperMajorityApprove, + delay: 2, + tally: Tally { ayes: 0, nays: 0, turnout: 0 }, + }) + ); + // replenish public again + assert_ok!(propose_set_balance_and_note(6, 6, 2)); + // cancel external + let h = set_balance_proposal_hash_and_note(7); + assert_ok!(Democracy::veto_external(Origin::signed(3), h)); + + fast_forward_to(12); + + // public goes again now since there's no external waiting. + assert_eq!( + Democracy::referendum_status(5), + Ok(ReferendumStatus { + end: 14, + proposal_hash: set_balance_proposal_hash_and_note(6), + threshold: VoteThreshold::SuperMajorityApprove, + delay: 2, + tally: Tally { ayes: 0, nays: 0, turnout: 0 }, + }) + ); + }); +} diff --git a/frame/democracy/src/tests/fast_tracking.rs b/frame/democracy/src/tests/fast_tracking.rs new file mode 100644 index 0000000000000..5ce9b15baf34c --- /dev/null +++ b/frame/democracy/src/tests/fast_tracking.rs @@ -0,0 +1,88 @@ +// Copyright 2017-2020 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 . + +//! The tests for fast-tracking functionality. + +use super::*; + +#[test] +fn fast_track_referendum_works() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + let h = set_balance_proposal_hash_and_note(2); + assert_noop!(Democracy::fast_track(Origin::signed(5), h, 3, 2), Error::::ProposalMissing); + assert_ok!(Democracy::external_propose_majority( + Origin::signed(3), + set_balance_proposal_hash_and_note(2) + )); + assert_noop!(Democracy::fast_track(Origin::signed(1), h, 3, 2), BadOrigin); + assert_ok!(Democracy::fast_track(Origin::signed(5), h, 2, 0)); + assert_eq!( + Democracy::referendum_status(0), + Ok(ReferendumStatus { + end: 2, + proposal_hash: set_balance_proposal_hash_and_note(2), + threshold: VoteThreshold::SimpleMajority, + delay: 0, + tally: Tally { ayes: 0, nays: 0, turnout: 0 }, + }) + ); + }); +} + +#[test] +fn instant_referendum_works() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + let h = set_balance_proposal_hash_and_note(2); + assert_noop!(Democracy::fast_track(Origin::signed(5), h, 3, 2), Error::::ProposalMissing); + assert_ok!(Democracy::external_propose_majority( + Origin::signed(3), + set_balance_proposal_hash_and_note(2) + )); + assert_noop!(Democracy::fast_track(Origin::signed(1), h, 3, 2), BadOrigin); + assert_noop!(Democracy::fast_track(Origin::signed(5), h, 1, 0), BadOrigin); + assert_noop!(Democracy::fast_track(Origin::signed(6), h, 1, 0), Error::::InstantNotAllowed); + INSTANT_ALLOWED.with(|v| *v.borrow_mut() = true); + assert_ok!(Democracy::fast_track(Origin::signed(6), h, 1, 0)); + assert_eq!( + Democracy::referendum_status(0), + Ok(ReferendumStatus { + end: 1, + proposal_hash: set_balance_proposal_hash_and_note(2), + threshold: VoteThreshold::SimpleMajority, + delay: 0, + tally: Tally { ayes: 0, nays: 0, turnout: 0 }, + }) + ); + }); +} + +#[test] +fn fast_track_referendum_fails_when_no_simple_majority() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + let h = set_balance_proposal_hash_and_note(2); + assert_ok!(Democracy::external_propose( + Origin::signed(2), + set_balance_proposal_hash_and_note(2) + )); + assert_noop!( + Democracy::fast_track(Origin::signed(5), h, 3, 2), + Error::::NotSimpleMajority + ); + }); +} diff --git a/frame/democracy/src/tests/lock_voting.rs b/frame/democracy/src/tests/lock_voting.rs new file mode 100644 index 0000000000000..c72ddbfd2ee62 --- /dev/null +++ b/frame/democracy/src/tests/lock_voting.rs @@ -0,0 +1,163 @@ +// Copyright 2017-2020 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 . + +//! The tests for functionality concerning locking and lock-voting. + +use super::*; +use std::convert::TryFrom; + +fn aye(x: u8, balance: u64) -> AccountVote { + AccountVote::Standard { + vote: Vote { aye: true, conviction: Conviction::try_from(x).unwrap() }, + balance + } +} + +fn nay(x: u8, balance: u64) -> AccountVote { + AccountVote::Standard { + vote: Vote { aye: false, conviction: Conviction::try_from(x).unwrap() }, + balance + } +} + +fn the_lock(amount: u64) -> BalanceLock { + BalanceLock { + id: DEMOCRACY_ID, + amount, + reasons: pallet_balances::Reasons::Misc, + } +} + +#[test] +fn lock_voting_should_work() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 0 + ); + assert_ok!(Democracy::vote(Origin::signed(1), r, nay(5, 10))); + assert_ok!(Democracy::vote(Origin::signed(2), r, aye(4, 20))); + assert_ok!(Democracy::vote(Origin::signed(3), r, aye(3, 30))); + assert_ok!(Democracy::vote(Origin::signed(4), r, aye(2, 40))); + assert_ok!(Democracy::vote(Origin::signed(5), r, nay(1, 50))); + assert_eq!(tally(r), Tally { ayes: 250, nays: 100, turnout: 150 }); + + // All balances are currently locked. + for i in 1..=5 { + assert_eq!(Balances::locks(i), vec![the_lock(i * 10)]); + } + + fast_forward_to(2); + + // Referendum passed; 1 and 5 didn't get their way and can now reap and unlock. + assert_ok!(Democracy::remove_vote(Origin::signed(1), r)); + assert_ok!(Democracy::unlock(Origin::signed(1), 1)); + // Anyone can reap and unlock anyone else's in this context. + assert_ok!(Democracy::remove_other_vote(Origin::signed(2), 5, r)); + assert_ok!(Democracy::unlock(Origin::signed(2), 5)); + + // 2, 3, 4 got their way with the vote, so they cannot be reaped by others. + assert_noop!(Democracy::remove_other_vote(Origin::signed(1), 2, r), Error::::NoPermission); + // However, they can be unvoted by the owner, though it will make no difference to the lock. + assert_ok!(Democracy::remove_vote(Origin::signed(2), r)); + assert_ok!(Democracy::unlock(Origin::signed(2), 2)); + + assert_eq!(Balances::locks(1), vec![]); + assert_eq!(Balances::locks(2), vec![the_lock(20)]); + assert_eq!(Balances::locks(3), vec![the_lock(30)]); + assert_eq!(Balances::locks(4), vec![the_lock(40)]); + assert_eq!(Balances::locks(5), vec![]); + assert_eq!(Balances::free_balance(42), 2); + + + fast_forward_to(5); + // No change yet... + assert_noop!(Democracy::remove_other_vote(Origin::signed(1), 4, r), Error::::NoPermission); + assert_ok!(Democracy::unlock(Origin::signed(1), 4)); + assert_eq!(Balances::locks(4), vec![the_lock(40)]); + fast_forward_to(6); + // 4 should now be able to reap and unlock + assert_ok!(Democracy::remove_other_vote(Origin::signed(1), 4, r)); + assert_ok!(Democracy::unlock(Origin::signed(1), 4)); + assert_eq!(Balances::locks(4), vec![]); + + fast_forward_to(9); + assert_noop!(Democracy::remove_other_vote(Origin::signed(1), 3, r), Error::::NoPermission); + assert_ok!(Democracy::unlock(Origin::signed(1), 3)); + assert_eq!(Balances::locks(3), vec![the_lock(30)]); + fast_forward_to(10); + assert_ok!(Democracy::remove_other_vote(Origin::signed(1), 3, r)); + assert_ok!(Democracy::unlock(Origin::signed(1), 3)); + assert_eq!(Balances::locks(3), vec![]); + + // 2 doesn't need to reap_vote here because it was already done before. + fast_forward_to(17); + assert_ok!(Democracy::unlock(Origin::signed(1), 2)); + assert_eq!(Balances::locks(2), vec![the_lock(20)]); + fast_forward_to(18); + assert_ok!(Democracy::unlock(Origin::signed(1), 2)); + assert_eq!(Balances::locks(2), vec![]); + }); +} + +#[test] +fn no_locks_without_conviction_should_work() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 0, + ); + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(0, 10))); + + fast_forward_to(2); + + assert_eq!(Balances::free_balance(42), 2); + assert_ok!(Democracy::remove_other_vote(Origin::signed(2), 1, r)); + assert_ok!(Democracy::unlock(Origin::signed(2), 1)); + assert_eq!(Balances::locks(1), vec![]); + }); +} + +#[test] +fn lock_voting_should_work_with_delegation() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 0 + ); + assert_ok!(Democracy::vote(Origin::signed(1), r, nay(5, 10))); + assert_ok!(Democracy::vote(Origin::signed(2), r, aye(4, 20))); + assert_ok!(Democracy::vote(Origin::signed(3), r, aye(3, 30))); + assert_ok!(Democracy::delegate(Origin::signed(4), 2, Conviction::Locked2x, 40)); + assert_ok!(Democracy::vote(Origin::signed(5), r, nay(1, 50))); + + assert_eq!(tally(r), Tally { ayes: 250, nays: 100, turnout: 110 }); + + next_block(); + next_block(); + + assert_eq!(Balances::free_balance(42), 2); + }); +} diff --git a/frame/democracy/src/tests/proxying.rs b/frame/democracy/src/tests/proxying.rs new file mode 100644 index 0000000000000..0424dca52beca --- /dev/null +++ b/frame/democracy/src/tests/proxying.rs @@ -0,0 +1,81 @@ +// Copyright 2017-2020 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 . + +//! The tests for functionality concerning proxying. + +use super::*; + +#[test] +fn proxy_should_work() { + new_test_ext().execute_with(|| { + assert_eq!(Democracy::proxy(10), None); + assert!(System::allow_death(&10)); + + assert_noop!(Democracy::activate_proxy(Origin::signed(1), 10), Error::::NotOpen); + + assert_ok!(Democracy::open_proxy(Origin::signed(10), 1)); + assert!(!System::allow_death(&10)); + assert_eq!(Democracy::proxy(10), Some(ProxyState::Open(1))); + + assert_noop!(Democracy::activate_proxy(Origin::signed(2), 10), Error::::WrongOpen); + assert_ok!(Democracy::activate_proxy(Origin::signed(1), 10)); + assert_eq!(Democracy::proxy(10), Some(ProxyState::Active(1))); + + // Can't set when already set. + assert_noop!(Democracy::activate_proxy(Origin::signed(2), 10), Error::::AlreadyProxy); + + // But this works because 11 isn't proxying. + assert_ok!(Democracy::open_proxy(Origin::signed(11), 2)); + assert_ok!(Democracy::activate_proxy(Origin::signed(2), 11)); + assert_eq!(Democracy::proxy(10), Some(ProxyState::Active(1))); + assert_eq!(Democracy::proxy(11), Some(ProxyState::Active(2))); + + // 2 cannot fire 1's proxy: + assert_noop!(Democracy::deactivate_proxy(Origin::signed(2), 10), Error::::WrongProxy); + + // 1 deactivates their proxy: + assert_ok!(Democracy::deactivate_proxy(Origin::signed(1), 10)); + assert_eq!(Democracy::proxy(10), Some(ProxyState::Open(1))); + // but the proxy account cannot be killed until the proxy is closed. + assert!(!System::allow_death(&10)); + + // and then 10 closes it completely: + assert_ok!(Democracy::close_proxy(Origin::signed(10))); + assert_eq!(Democracy::proxy(10), None); + assert!(System::allow_death(&10)); + + // 11 just closes without 2's "permission". + assert_ok!(Democracy::close_proxy(Origin::signed(11))); + assert_eq!(Democracy::proxy(11), None); + assert!(System::allow_death(&11)); + }); +} + +#[test] +fn single_proposal_should_work_with_proxy() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + assert_ok!(propose_set_balance_and_note(1, 2, 1)); + + fast_forward_to(2); + let r = 0; + assert_ok!(Democracy::open_proxy(Origin::signed(10), 1)); + assert_ok!(Democracy::activate_proxy(Origin::signed(1), 10)); + assert_ok!(Democracy::proxy_vote(Origin::signed(10), r, aye(1))); + + assert_eq!(tally(r), Tally { ayes: 1, nays: 0, turnout: 1 }); + }); +} diff --git a/frame/democracy/src/tests/public_proposals.rs b/frame/democracy/src/tests/public_proposals.rs new file mode 100644 index 0000000000000..ef687146015aa --- /dev/null +++ b/frame/democracy/src/tests/public_proposals.rs @@ -0,0 +1,104 @@ +// Copyright 2017-2020 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 . + +//! The tests for the public proposal queue. + +use super::*; + +#[test] +fn backing_for_should_work() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + assert_ok!(propose_set_balance_and_note(1, 2, 2)); + assert_ok!(propose_set_balance_and_note(1, 4, 4)); + assert_ok!(propose_set_balance_and_note(1, 3, 3)); + assert_eq!(Democracy::backing_for(0), Some(2)); + assert_eq!(Democracy::backing_for(1), Some(4)); + assert_eq!(Democracy::backing_for(2), Some(3)); + }); +} + +#[test] +fn deposit_for_proposals_should_be_taken() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + assert_ok!(propose_set_balance_and_note(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_and_note(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), Error::::ValueLow); + }); +} + +#[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), BalancesError::::InsufficientBalance); + }); +} + +#[test] +fn poor_seconder_should_not_work() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + assert_ok!(propose_set_balance_and_note(2, 2, 11)); + assert_noop!(Democracy::second(Origin::signed(1), 0), BalancesError::::InsufficientBalance); + }); +} + +#[test] +fn runners_up_should_come_after() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + assert_ok!(propose_set_balance_and_note(1, 2, 2)); + assert_ok!(propose_set_balance_and_note(1, 4, 4)); + assert_ok!(propose_set_balance_and_note(1, 3, 3)); + fast_forward_to(2); + assert_ok!(Democracy::vote(Origin::signed(1), 0, aye(1))); + fast_forward_to(4); + assert_ok!(Democracy::vote(Origin::signed(1), 1, aye(1))); + fast_forward_to(6); + assert_ok!(Democracy::vote(Origin::signed(1), 2, aye(1))); + }); +} diff --git a/frame/democracy/src/tests/scheduling.rs b/frame/democracy/src/tests/scheduling.rs new file mode 100644 index 0000000000000..16caefcf1b6e6 --- /dev/null +++ b/frame/democracy/src/tests/scheduling.rs @@ -0,0 +1,115 @@ +// Copyright 2017-2020 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 . + +//! The tests for functionality concerning normal starting, ending and enacting of referenda. + +use super::*; + +#[test] +fn simple_passing_should_work() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 0 + ); + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + assert_eq!(tally(r), Tally { ayes: 1, nays: 0, turnout: 1 }); + next_block(); + next_block(); + assert_eq!(Balances::free_balance(42), 2); + }); +} + +#[test] +fn simple_failing_should_work() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 0 + ); + assert_ok!(Democracy::vote(Origin::signed(1), r, nay(1))); + assert_eq!(tally(r), Tally { ayes: 0, nays: 1, turnout: 1 }); + + next_block(); + next_block(); + + assert_eq!(Balances::free_balance(42), 0); + }); +} + +#[test] +fn ooo_inject_referendums_should_work() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + let r1 = Democracy::inject_referendum( + 3, + set_balance_proposal_hash_and_note(3), + VoteThreshold::SuperMajorityApprove, + 0 + ); + let r2 = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 0 + ); + + assert_ok!(Democracy::vote(Origin::signed(1), r2, aye(1))); + assert_eq!(tally(r2), Tally { ayes: 1, nays: 0, turnout: 1 }); + + next_block(); + assert_eq!(Balances::free_balance(42), 2); + + assert_ok!(Democracy::vote(Origin::signed(1), r1, aye(1))); + assert_eq!(tally(r1), Tally { ayes: 1, nays: 0, turnout: 1 }); + + next_block(); + assert_eq!(Balances::free_balance(42), 3); + }); +} + +#[test] +fn delayed_enactment_should_work() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 1 + ); + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + assert_ok!(Democracy::vote(Origin::signed(2), r, aye(2))); + assert_ok!(Democracy::vote(Origin::signed(3), r, aye(3))); + assert_ok!(Democracy::vote(Origin::signed(4), r, aye(4))); + assert_ok!(Democracy::vote(Origin::signed(5), r, aye(5))); + assert_ok!(Democracy::vote(Origin::signed(6), r, aye(6))); + + assert_eq!(tally(r), Tally { ayes: 21, nays: 0, turnout: 21 }); + + next_block(); + assert_eq!(Balances::free_balance(42), 0); + + next_block(); + assert_eq!(Balances::free_balance(42), 2); + }); +} diff --git a/frame/democracy/src/tests/voting.rs b/frame/democracy/src/tests/voting.rs new file mode 100644 index 0000000000000..435086be888ca --- /dev/null +++ b/frame/democracy/src/tests/voting.rs @@ -0,0 +1,137 @@ +// Copyright 2017-2020 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 . + +//! The tests for normal voting functionality. + +use super::*; + +#[test] +fn single_proposal_should_work() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + assert_ok!(propose_set_balance_and_note(1, 2, 1)); + assert!(Democracy::referendum_info(0).is_none()); + + // start of 2 => next referendum scheduled. + fast_forward_to(2); + + let r = 0; + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + + assert_eq!(Democracy::referendum_count(), 1); + assert_eq!( + Democracy::referendum_status(0), + Ok(ReferendumStatus { + end: 4, + proposal_hash: set_balance_proposal_hash_and_note(2), + threshold: VoteThreshold::SuperMajorityApprove, + delay: 2, + tally: Tally { ayes: 1, nays: 0, turnout: 1 }, + }) + ); + + fast_forward_to(3); + + // referendum still running + assert!(Democracy::referendum_status(0).is_ok()); + + // referendum runs during 2 and 3, ends @ start of 4. + fast_forward_to(4); + + assert!(Democracy::referendum_status(0).is_err()); + assert_eq!(Democracy::dispatch_queue(), vec![ + (6, set_balance_proposal_hash_and_note(2), 0) + ]); + + // referendum passes and wait another two blocks for enactment. + fast_forward_to(6); + + assert_eq!(Balances::free_balance(42), 2); + }); +} + +#[test] +fn controversial_voting_should_work() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 0 + ); + + assert_ok!(Democracy::vote(Origin::signed(1), r, big_aye(1))); + assert_ok!(Democracy::vote(Origin::signed(2), r, big_nay(2))); + assert_ok!(Democracy::vote(Origin::signed(3), r, big_nay(3))); + assert_ok!(Democracy::vote(Origin::signed(4), r, big_aye(4))); + assert_ok!(Democracy::vote(Origin::signed(5), r, big_nay(5))); + assert_ok!(Democracy::vote(Origin::signed(6), r, big_aye(6))); + + assert_eq!(tally(r), Tally { ayes: 110, nays: 100, turnout: 210 }); + + next_block(); + 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( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 0 + ); + assert_ok!(Democracy::vote(Origin::signed(5), r, big_nay(5))); + assert_ok!(Democracy::vote(Origin::signed(6), r, big_aye(6))); + + assert_eq!(tally(r), Tally { ayes: 60, nays: 50, turnout: 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( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 0 + ); + assert_ok!(Democracy::vote(Origin::signed(4), r, big_aye(4))); + assert_ok!(Democracy::vote(Origin::signed(5), r, big_nay(5))); + assert_ok!(Democracy::vote(Origin::signed(6), r, big_aye(6))); + assert_eq!(tally(r), Tally { ayes: 100, nays: 50, turnout: 150 }); + + next_block(); + next_block(); + assert_eq!(Balances::free_balance(42), 2); + }); +} From 77a84ceb9771bd2807d3687ed2989804a2bad121 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 20 Mar 2020 11:05:47 +0100 Subject: [PATCH 12/22] Fix runtime build --- bin/node/runtime/src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index d7a23ad81a460..7deafabbbd492 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -68,6 +68,7 @@ use impls::{CurrencyToVoteHandler, Author, LinearWeightToFee, TargetedFeeAdjustm /// Constant values used within the runtime. pub mod constants; use constants::{time::*, currency::*}; +use frame_system::Trait; // Make the WASM binary available. #[cfg(feature = "std")] @@ -303,6 +304,7 @@ parameter_types! { pub const LaunchPeriod: BlockNumber = 28 * 24 * 60 * MINUTES; pub const VotingPeriod: BlockNumber = 28 * 24 * 60 * MINUTES; pub const FastTrackVotingPeriod: BlockNumber = 3 * 24 * 60 * MINUTES; + pub const InstantAllowed: bool = true; pub const MinimumDeposit: Balance = 100 * DOLLARS; pub const EnactmentPeriod: BlockNumber = 30 * 24 * 60 * MINUTES; pub const CooloffPeriod: BlockNumber = 28 * 24 * 60 * MINUTES; @@ -328,6 +330,8 @@ impl pallet_democracy::Trait for Runtime { /// Two thirds of the technical committee can have an ExternalMajority/ExternalDefault vote /// be tabled immediately and with a shorter voting/enactment period. type FastTrackOrigin = pallet_collective::EnsureProportionAtLeast<_2, _3, AccountId, TechnicalCollective>; + type InstantOrigin = pallet_collective::EnsureProportionAtLeast<_1, _1, AccountId, TechnicalCollective>; + type InstantAllowed = InstantAllowed; type FastTrackVotingPeriod = FastTrackVotingPeriod; // To cancel a proposal which has been passed, 2/3 of the council must agree to it. type CancellationOrigin = pallet_collective::EnsureProportionAtLeast<_2, _3, AccountId, CouncilCollective>; From ed266873ca0aecd9a77e09999bfa6be07e9fc1d4 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 20 Mar 2020 11:12:15 +0100 Subject: [PATCH 13/22] Update docs --- frame/democracy/src/lib.rs | 6 +++--- frame/democracy/src/vote_threshold.rs | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/frame/democracy/src/lib.rs b/frame/democracy/src/lib.rs index 47578ce861d49..5926645878065 100644 --- a/frame/democracy/src/lib.rs +++ b/frame/democracy/src/lib.rs @@ -455,7 +455,7 @@ decl_error! { /// An unexpected integer underflow occurred. Underflow, /// Too high a balance was provided that the account cannot afford. - TooMuch, + InsufficientFunds, /// The account is not currently delegating. NotDelegating, /// The account currently has votes attached to it and the operation cannot succeed until @@ -1311,7 +1311,7 @@ impl Module { /// Actually enact a vote, if legit. fn try_vote(who: &T::AccountId, ref_index: ReferendumIndex, vote: AccountVote>) -> DispatchResult { let mut status = Self::referendum_status(ref_index)?; - ensure!(vote.balance() <= T::Currency::free_balance(who), Error::::TooMuch); + ensure!(vote.balance() <= T::Currency::free_balance(who), Error::::InsufficientFunds); VotingOf::::try_mutate(who, |voting| -> DispatchResult { if let Voting::Direct { ref mut votes, delegations, .. } = voting { match votes.binary_search_by_key(&ref_index, |i| i.0) { @@ -1434,7 +1434,7 @@ impl Module { balance: BalanceOf, ) -> DispatchResult { ensure!(who != target, Error::::Nonsense); - ensure!(balance <= T::Currency::free_balance(&who), Error::::TooMuch); + ensure!(balance <= T::Currency::free_balance(&who), Error::::InsufficientFunds); VotingOf::::try_mutate(&who, |voting| -> DispatchResult { let mut old = Voting::Delegating { balance, diff --git a/frame/democracy/src/vote_threshold.rs b/frame/democracy/src/vote_threshold.rs index 135971bcd8250..13dc7a81f063d 100644 --- a/frame/democracy/src/vote_threshold.rs +++ b/frame/democracy/src/vote_threshold.rs @@ -70,13 +70,13 @@ fn compare_rationals + Div + Rem + Mul + Div + Rem + Copy> Approved for VoteThreshold { - /// Given `approve` votes for and `against` votes against from a total electorate size of - /// `electorate` of whom `voters` voted (`electorate - voters` are abstainers) then returns true if the - /// overall outcome is in favor of approval. - /// - /// We assume each *voter* may cast more than one *vote*, hence `voters` is not necessarily equal to - /// `approve + against`. +impl< + Balance: IntegerSquareRoot + Zero + Ord + Add + + Mul + Div + + Rem + Copy, +> Approved for VoteThreshold { + /// Given a `tally` of votes and a total size of `electorate`, this returns `true` if the + /// overall outcome is in favor of approval according to `self`'s threshold method. fn approved(&self, tally: Tally, electorate: Balance) -> bool { let sqrt_voters = tally.turnout.integer_sqrt(); let sqrt_electorate = electorate.integer_sqrt(); From 56d3d801fd7174f02570e997a7932d0d17a354ce Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 20 Mar 2020 11:12:53 +0100 Subject: [PATCH 14/22] Docs --- frame/democracy/src/vote_threshold.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/frame/democracy/src/vote_threshold.rs b/frame/democracy/src/vote_threshold.rs index 13dc7a81f063d..fd976b44001cf 100644 --- a/frame/democracy/src/vote_threshold.rs +++ b/frame/democracy/src/vote_threshold.rs @@ -36,9 +36,8 @@ pub enum VoteThreshold { } pub trait Approved { - /// Given `approve` votes for and `against` votes against from a total electorate size of - /// `electorate` (`electorate - (approve + against)` are abstainers), then returns true if the - /// overall outcome is in favor of approval. + /// Given a `tally` of votes and a total size of `electorate`, this returns `true` if the + /// overall outcome is in favor of approval according to `self`'s threshold method. fn approved(&self, tally: Tally, electorate: Balance) -> bool; } @@ -75,8 +74,6 @@ impl< + Mul + Div + Rem + Copy, > Approved for VoteThreshold { - /// Given a `tally` of votes and a total size of `electorate`, this returns `true` if the - /// overall outcome is in favor of approval according to `self`'s threshold method. fn approved(&self, tally: Tally, electorate: Balance) -> bool { let sqrt_voters = tally.turnout.integer_sqrt(); let sqrt_electorate = electorate.integer_sqrt(); From 8cbf87c88a40a9bbe1bacebb611eb96c0cb26605 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 20 Mar 2020 11:15:26 +0100 Subject: [PATCH 15/22] Nits. --- frame/democracy/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frame/democracy/src/lib.rs b/frame/democracy/src/lib.rs index 5926645878065..f79633d88390b 100644 --- a/frame/democracy/src/lib.rs +++ b/frame/democracy/src/lib.rs @@ -564,7 +564,6 @@ decl_module! { /// /// - `ref_index`: The index of the referendum to vote for. /// - `vote`: The vote configuration. - /// - `balance`: The amount of balance to /// /// # /// - `O(1)`. @@ -715,7 +714,7 @@ decl_module! { // Rather complicated bit of code to ensure that either: // - `voting_period` is at least `FastTrackVotingPeriod` and `origin` is `FastTrackOrigin`; or // - `InstantAllowed` is `true` and `origin` is `InstantOrigin`. - if let Some(ensure_instant) = if voting_period < T::FastTrackVotingPeriod::get() { + let maybe_ensure_instant = if voting_period < T::FastTrackVotingPeriod::get() { Some(origin) } else { if let Err(origin) = T::FastTrackOrigin::try_origin(origin) { @@ -723,7 +722,8 @@ decl_module! { } else { None } - } { + }; + if let Some(ensure_instant) = maybe_ensure_instant { T::InstantOrigin::ensure_origin(ensure_instant)?; ensure!(T::InstantAllowed::get(), Error::::InstantNotAllowed); } From 24aa1527b56dbfbf8342135635062abd38548eab Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 20 Mar 2020 12:25:54 +0100 Subject: [PATCH 16/22] Turnout counts full capital --- frame/democracy/src/conviction.rs | 18 +++++++----------- frame/democracy/src/tests/delegation.rs | 24 ++++++++++++------------ frame/democracy/src/tests/proxying.rs | 2 +- frame/democracy/src/tests/scheduling.rs | 10 +++++----- frame/democracy/src/tests/voting.rs | 2 +- 5 files changed, 26 insertions(+), 30 deletions(-) diff --git a/frame/democracy/src/conviction.rs b/frame/democracy/src/conviction.rs index 9117bfd5fe696..6af3ae006f80d 100644 --- a/frame/democracy/src/conviction.rs +++ b/frame/democracy/src/conviction.rs @@ -94,16 +94,13 @@ impl Conviction { pub 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, - ) - } + ( + match self { + Conviction::None => balance.checked_div(&10u8.into()).unwrap_or_else(Zero::zero), + x => balance.checked_mul(&u8::from(x).into()).unwrap_or_else(B::max_value), + }, + balance + ) } } @@ -111,7 +108,6 @@ impl Bounded for Conviction { fn min_value() -> Self { Conviction::None } - fn max_value() -> Self { Conviction::Locked6x } diff --git a/frame/democracy/src/tests/delegation.rs b/frame/democracy/src/tests/delegation.rs index 8d3e90282627c..2f13386bacafe 100644 --- a/frame/democracy/src/tests/delegation.rs +++ b/frame/democracy/src/tests/delegation.rs @@ -31,19 +31,19 @@ fn single_proposal_should_work_with_delegation() { assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::None, 20)); let r = 0; assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - assert_eq!(tally(r), Tally { ayes: 3, nays: 0, turnout: 1 }); + assert_eq!(tally(r), Tally { ayes: 3, nays: 0, turnout: 10 }); // Delegate a second vote. assert_ok!(Democracy::delegate(Origin::signed(3), 1, Conviction::None, 30)); - assert_eq!(tally(r), Tally { ayes: 6, nays: 0, turnout: 1 }); + assert_eq!(tally(r), Tally { ayes: 6, nays: 0, turnout: 10 }); // Reduce first vote. assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::None, 10)); - assert_eq!(tally(r), Tally { ayes: 5, nays: 0, turnout: 1 }); + assert_eq!(tally(r), Tally { ayes: 5, nays: 0, turnout: 10 }); // Second vote delegates to first; we don't do tiered delegation, so it doesn't get used. assert_ok!(Democracy::delegate(Origin::signed(3), 2, Conviction::None, 30)); - assert_eq!(tally(r), Tally { ayes: 2, nays: 0, turnout: 1 }); + assert_eq!(tally(r), Tally { ayes: 2, nays: 0, turnout: 10 }); // Main voter cancels their vote assert_ok!(Democracy::remove_vote(Origin::signed(1), r)); @@ -55,7 +55,7 @@ fn single_proposal_should_work_with_delegation() { // Main voter reinstates their vote assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - assert_eq!(tally(r), Tally { ayes: 11, nays: 0, turnout: 1 }); + assert_eq!(tally(r), Tally { ayes: 11, nays: 0, turnout: 10 }); }); } @@ -89,7 +89,7 @@ fn cyclic_delegation_should_unwind() { assert_ok!(Democracy::vote(Origin::signed(1), r, nay(1))); // Delegated vote is counted. - assert_eq!(tally(r), Tally { ayes: 3, nays: 3, turnout: 4 }); + assert_eq!(tally(r), Tally { ayes: 3, nays: 3, turnout: 40 }); }); } @@ -106,13 +106,13 @@ fn single_proposal_should_work_with_vote_and_delegation() { let r = 0; assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); assert_ok!(Democracy::vote(Origin::signed(2), r, nay(2))); - assert_eq!(tally(r), Tally { ayes: 1, nays: 2, turnout: 3 }); + assert_eq!(tally(r), Tally { ayes: 1, nays: 2, turnout: 30 }); // Delegate vote. assert_ok!(Democracy::remove_vote(Origin::signed(2), r)); assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::None, 20)); // Delegated vote replaces the explicit vote. - assert_eq!(tally(r), Tally { ayes: 3, nays: 0, turnout: 1 }); + assert_eq!(tally(r), Tally { ayes: 3, nays: 0, turnout: 10 }); }); } @@ -132,7 +132,7 @@ fn single_proposal_should_work_with_undelegation() { assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); // Delegated vote is not counted. - assert_eq!(tally(r), Tally { ayes: 1, nays: 0, turnout: 1 }); + assert_eq!(tally(r), Tally { ayes: 1, nays: 0, turnout: 10 }); }); } @@ -148,11 +148,11 @@ fn single_proposal_should_work_with_delegation_and_vote() { // Delegate, undelegate and vote. assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::None, 20)); - assert_eq!(tally(r), Tally { ayes: 3, nays: 0, turnout: 1 }); + assert_eq!(tally(r), Tally { ayes: 3, nays: 0, turnout: 10 }); assert_ok!(Democracy::undelegate(Origin::signed(2))); assert_ok!(Democracy::vote(Origin::signed(2), r, aye(2))); // Delegated vote is not counted. - assert_eq!(tally(r), Tally { ayes: 3, nays: 0, turnout: 3 }); + assert_eq!(tally(r), Tally { ayes: 3, nays: 0, turnout: 30 }); }); } @@ -169,6 +169,6 @@ fn conviction_should_be_honored_in_delegation() { assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::Locked6x, 20)); assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); // Delegated vote is huge. - assert_eq!(tally(r), Tally { ayes: 121, nays: 0, turnout: 1 }); + assert_eq!(tally(r), Tally { ayes: 121, nays: 0, turnout: 10 }); }); } diff --git a/frame/democracy/src/tests/proxying.rs b/frame/democracy/src/tests/proxying.rs index 0424dca52beca..1720044fd1773 100644 --- a/frame/democracy/src/tests/proxying.rs +++ b/frame/democracy/src/tests/proxying.rs @@ -76,6 +76,6 @@ fn single_proposal_should_work_with_proxy() { assert_ok!(Democracy::activate_proxy(Origin::signed(1), 10)); assert_ok!(Democracy::proxy_vote(Origin::signed(10), r, aye(1))); - assert_eq!(tally(r), Tally { ayes: 1, nays: 0, turnout: 1 }); + assert_eq!(tally(r), Tally { ayes: 1, nays: 0, turnout: 10 }); }); } diff --git a/frame/democracy/src/tests/scheduling.rs b/frame/democracy/src/tests/scheduling.rs index 16caefcf1b6e6..81120ea5b56c5 100644 --- a/frame/democracy/src/tests/scheduling.rs +++ b/frame/democracy/src/tests/scheduling.rs @@ -29,7 +29,7 @@ fn simple_passing_should_work() { 0 ); assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - assert_eq!(tally(r), Tally { ayes: 1, nays: 0, turnout: 1 }); + assert_eq!(tally(r), Tally { ayes: 1, nays: 0, turnout: 10 }); next_block(); next_block(); assert_eq!(Balances::free_balance(42), 2); @@ -47,7 +47,7 @@ fn simple_failing_should_work() { 0 ); assert_ok!(Democracy::vote(Origin::signed(1), r, nay(1))); - assert_eq!(tally(r), Tally { ayes: 0, nays: 1, turnout: 1 }); + assert_eq!(tally(r), Tally { ayes: 0, nays: 1, turnout: 10 }); next_block(); next_block(); @@ -74,13 +74,13 @@ fn ooo_inject_referendums_should_work() { ); assert_ok!(Democracy::vote(Origin::signed(1), r2, aye(1))); - assert_eq!(tally(r2), Tally { ayes: 1, nays: 0, turnout: 1 }); + assert_eq!(tally(r2), Tally { ayes: 1, nays: 0, turnout: 10 }); next_block(); assert_eq!(Balances::free_balance(42), 2); assert_ok!(Democracy::vote(Origin::signed(1), r1, aye(1))); - assert_eq!(tally(r1), Tally { ayes: 1, nays: 0, turnout: 1 }); + assert_eq!(tally(r1), Tally { ayes: 1, nays: 0, turnout: 10 }); next_block(); assert_eq!(Balances::free_balance(42), 3); @@ -104,7 +104,7 @@ fn delayed_enactment_should_work() { assert_ok!(Democracy::vote(Origin::signed(5), r, aye(5))); assert_ok!(Democracy::vote(Origin::signed(6), r, aye(6))); - assert_eq!(tally(r), Tally { ayes: 21, nays: 0, turnout: 21 }); + assert_eq!(tally(r), Tally { ayes: 21, nays: 0, turnout: 210 }); next_block(); assert_eq!(Balances::free_balance(42), 0); diff --git a/frame/democracy/src/tests/voting.rs b/frame/democracy/src/tests/voting.rs index 435086be888ca..ac5b3a0631bc3 100644 --- a/frame/democracy/src/tests/voting.rs +++ b/frame/democracy/src/tests/voting.rs @@ -39,7 +39,7 @@ fn single_proposal_should_work() { proposal_hash: set_balance_proposal_hash_and_note(2), threshold: VoteThreshold::SuperMajorityApprove, delay: 2, - tally: Tally { ayes: 1, nays: 0, turnout: 1 }, + tally: Tally { ayes: 1, nays: 0, turnout: 10 }, }) ); From 9846b0b74bda54fe93702c0b53cafb3689660927 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 20 Mar 2020 12:54:57 +0100 Subject: [PATCH 17/22] Delegations could towards capital --- frame/democracy/src/conviction.rs | 15 +++-- frame/democracy/src/lib.rs | 22 +++---- frame/democracy/src/tests/delegation.rs | 18 +++--- frame/democracy/src/tests/lock_voting.rs | 2 +- frame/democracy/src/types.rs | 80 +++++++++++++++++------- frame/democracy/src/vote.rs | 17 ++--- 6 files changed, 94 insertions(+), 60 deletions(-) diff --git a/frame/democracy/src/conviction.rs b/frame/democracy/src/conviction.rs index 6af3ae006f80d..a057ee2a35750 100644 --- a/frame/democracy/src/conviction.rs +++ b/frame/democracy/src/conviction.rs @@ -19,6 +19,7 @@ use sp_std::{result::Result, convert::TryFrom}; use sp_runtime::{RuntimeDebug, traits::{Zero, Bounded, CheckedMul, CheckedDiv}}; use codec::{Encode, Decode}; +use crate::types::Delegations; /// A value denoting the strength of conviction of a vote. #[derive(Encode, Decode, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug)] @@ -93,14 +94,12 @@ impl Conviction { /// The votes of a voter of the given `balance` with our conviction. pub fn votes< B: From + Zero + Copy + CheckedMul + CheckedDiv + Bounded - >(self, balance: B) -> (B, B) { - ( - match self { - Conviction::None => balance.checked_div(&10u8.into()).unwrap_or_else(Zero::zero), - x => balance.checked_mul(&u8::from(x).into()).unwrap_or_else(B::max_value), - }, - balance - ) + >(self, capital: B) -> Delegations { + let votes = match self { + Conviction::None => capital.checked_div(&10u8.into()).unwrap_or_else(Zero::zero), + x => capital.checked_mul(&u8::from(x).into()).unwrap_or_else(B::max_value), + }; + Delegations { votes, capital } } } diff --git a/frame/democracy/src/lib.rs b/frame/democracy/src/lib.rs index f79633d88390b..b9faf98719424 100644 --- a/frame/democracy/src/lib.rs +++ b/frame/democracy/src/lib.rs @@ -185,7 +185,7 @@ mod types; pub use vote_threshold::{Approved, VoteThreshold}; pub use vote::{Vote, AccountVote, Voting}; pub use conviction::Conviction; -pub use types::{ReferendumInfo, ReferendumStatus, ProxyState, Tally, UnvoteScope}; +pub use types::{ReferendumInfo, ReferendumStatus, ProxyState, Tally, UnvoteScope, Delegations}; #[cfg(test)] mod tests; @@ -1319,7 +1319,7 @@ impl Module { // Shouldn't be possible to fail, but we handle it gracefully. status.tally.remove(votes[i].1).ok_or(Error::::Underflow)?; if let Some(approve) = votes[i].1.as_standard() { - status.tally.reduce(approve, *delegations, Zero::zero()); + status.tally.reduce(approve, *delegations); } votes[i].1 = vote; } @@ -1328,7 +1328,7 @@ impl Module { // Shouldn't be possible to fail, but we handle it gracefully. status.tally.add(vote).ok_or(Error::::Overflow)?; if let Some(approve) = vote.as_standard() { - status.tally.increase(approve, *delegations, Zero::zero()); + status.tally.increase(approve, *delegations); } Ok(()) } else { @@ -1364,7 +1364,7 @@ impl Module { // Shouldn't be possible to fail, but we handle it gracefully. status.tally.remove(votes[i].1).ok_or(Error::::Underflow)?; if let Some(approve) = votes[i].1.as_standard() { - status.tally.reduce(approve, *delegations, Zero::zero()); + status.tally.reduce(approve, *delegations); } ReferendumInfoOf::::insert(ref_index, ReferendumInfo::Ongoing(status)); } @@ -1386,7 +1386,7 @@ impl Module { Ok(()) } - fn increase_upstream_delegation(who: &T::AccountId, amount: BalanceOf) { + fn increase_upstream_delegation(who: &T::AccountId, amount: Delegations>) { VotingOf::::mutate(who, |voting| match voting { Voting::Delegating { delegations, .. } => // We don't support second level delegating, so we don't need to do anything more. @@ -1397,7 +1397,7 @@ impl Module { if let AccountVote::Standard { vote, .. } = account_vote { ReferendumInfoOf::::mutate(ref_index, |maybe_info| if let Some(ReferendumInfo::Ongoing(ref mut status)) = maybe_info { - status.tally.increase(vote.aye, amount, Zero::zero()); + status.tally.increase(vote.aye, amount); } ); } @@ -1406,7 +1406,7 @@ impl Module { }) } - fn reduce_upstream_delegation(who: &T::AccountId, amount: BalanceOf) { + fn reduce_upstream_delegation(who: &T::AccountId, amount: Delegations>) { VotingOf::::mutate(who, |voting| match voting { Voting::Delegating { delegations, .. } => // We don't support second level delegating, so we don't need to do anything more. @@ -1417,7 +1417,7 @@ impl Module { if let AccountVote::Standard { vote, .. } = account_vote { ReferendumInfoOf::::mutate(ref_index, |maybe_info| if let Some(ReferendumInfo::Ongoing(ref mut status)) = maybe_info { - status.tally.reduce(vote.aye, amount, Zero::zero()); + status.tally.reduce(vote.aye, amount); } ); } @@ -1447,7 +1447,7 @@ impl Module { match old { Voting::Delegating { balance, target, conviction, delegations, prior, .. } => { // remove any delegation votes to our current target. - Self::reduce_upstream_delegation(&target, conviction.votes(balance).0); + Self::reduce_upstream_delegation(&target, conviction.votes(balance)); voting.set_common(delegations, prior); } Voting::Direct { votes, delegations, prior } => { @@ -1456,7 +1456,7 @@ impl Module { voting.set_common(delegations, prior); } } - Self::increase_upstream_delegation(&target, conviction.votes(balance).0); + Self::increase_upstream_delegation(&target, conviction.votes(balance)); // Extend the lock to `balance` (rather than setting it) since we don't know what other // votes are in place. T::Currency::extend_lock( @@ -1485,7 +1485,7 @@ impl Module { mut prior, } => { // remove any delegation votes to our current target. - Self::reduce_upstream_delegation(&target, conviction.votes(balance).0); + Self::reduce_upstream_delegation(&target, conviction.votes(balance)); let now = system::Module::::block_number(); let lock_periods = conviction.lock_periods().into(); prior.accumulate(now + T::EnactmentPeriod::get() * lock_periods, balance); diff --git a/frame/democracy/src/tests/delegation.rs b/frame/democracy/src/tests/delegation.rs index 2f13386bacafe..c0ca78c108402 100644 --- a/frame/democracy/src/tests/delegation.rs +++ b/frame/democracy/src/tests/delegation.rs @@ -31,19 +31,19 @@ fn single_proposal_should_work_with_delegation() { assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::None, 20)); let r = 0; assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - assert_eq!(tally(r), Tally { ayes: 3, nays: 0, turnout: 10 }); + assert_eq!(tally(r), Tally { ayes: 3, nays: 0, turnout: 30 }); // Delegate a second vote. assert_ok!(Democracy::delegate(Origin::signed(3), 1, Conviction::None, 30)); - assert_eq!(tally(r), Tally { ayes: 6, nays: 0, turnout: 10 }); + assert_eq!(tally(r), Tally { ayes: 6, nays: 0, turnout: 60 }); // Reduce first vote. assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::None, 10)); - assert_eq!(tally(r), Tally { ayes: 5, nays: 0, turnout: 10 }); + assert_eq!(tally(r), Tally { ayes: 5, nays: 0, turnout: 50 }); // Second vote delegates to first; we don't do tiered delegation, so it doesn't get used. assert_ok!(Democracy::delegate(Origin::signed(3), 2, Conviction::None, 30)); - assert_eq!(tally(r), Tally { ayes: 2, nays: 0, turnout: 10 }); + assert_eq!(tally(r), Tally { ayes: 2, nays: 0, turnout: 20 }); // Main voter cancels their vote assert_ok!(Democracy::remove_vote(Origin::signed(1), r)); @@ -55,7 +55,7 @@ fn single_proposal_should_work_with_delegation() { // Main voter reinstates their vote assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - assert_eq!(tally(r), Tally { ayes: 11, nays: 0, turnout: 10 }); + assert_eq!(tally(r), Tally { ayes: 11, nays: 0, turnout: 20 }); }); } @@ -89,7 +89,7 @@ fn cyclic_delegation_should_unwind() { assert_ok!(Democracy::vote(Origin::signed(1), r, nay(1))); // Delegated vote is counted. - assert_eq!(tally(r), Tally { ayes: 3, nays: 3, turnout: 40 }); + assert_eq!(tally(r), Tally { ayes: 3, nays: 3, turnout: 60 }); }); } @@ -112,7 +112,7 @@ fn single_proposal_should_work_with_vote_and_delegation() { assert_ok!(Democracy::remove_vote(Origin::signed(2), r)); assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::None, 20)); // Delegated vote replaces the explicit vote. - assert_eq!(tally(r), Tally { ayes: 3, nays: 0, turnout: 10 }); + assert_eq!(tally(r), Tally { ayes: 3, nays: 0, turnout: 30 }); }); } @@ -148,7 +148,7 @@ fn single_proposal_should_work_with_delegation_and_vote() { // Delegate, undelegate and vote. assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::None, 20)); - assert_eq!(tally(r), Tally { ayes: 3, nays: 0, turnout: 10 }); + assert_eq!(tally(r), Tally { ayes: 3, nays: 0, turnout: 30 }); assert_ok!(Democracy::undelegate(Origin::signed(2))); assert_ok!(Democracy::vote(Origin::signed(2), r, aye(2))); // Delegated vote is not counted. @@ -169,6 +169,6 @@ fn conviction_should_be_honored_in_delegation() { assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::Locked6x, 20)); assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); // Delegated vote is huge. - assert_eq!(tally(r), Tally { ayes: 121, nays: 0, turnout: 10 }); + assert_eq!(tally(r), Tally { ayes: 121, nays: 0, turnout: 30 }); }); } diff --git a/frame/democracy/src/tests/lock_voting.rs b/frame/democracy/src/tests/lock_voting.rs index c72ddbfd2ee62..8301b45b81df3 100644 --- a/frame/democracy/src/tests/lock_voting.rs +++ b/frame/democracy/src/tests/lock_voting.rs @@ -153,7 +153,7 @@ fn lock_voting_should_work_with_delegation() { assert_ok!(Democracy::delegate(Origin::signed(4), 2, Conviction::Locked2x, 40)); assert_ok!(Democracy::vote(Origin::signed(5), r, nay(1, 50))); - assert_eq!(tally(r), Tally { ayes: 250, nays: 100, turnout: 110 }); + assert_eq!(tally(r), Tally { ayes: 250, nays: 100, turnout: 150 }); next_block(); next_block(); diff --git a/frame/democracy/src/types.rs b/frame/democracy/src/types.rs index 0542c9a8e718a..7a145701e99f1 100644 --- a/frame/democracy/src/types.rs +++ b/frame/democracy/src/types.rs @@ -32,6 +32,38 @@ pub struct Tally { pub (crate) turnout: Balance, } +/// Amount of votes and capital placed in delegation for an account. +#[derive(Encode, Decode, Default, Copy, Clone, PartialEq, Eq, RuntimeDebug)] +pub struct Delegations { + /// The number of votes (this is post-conviction). + pub (crate) votes: Balance, + /// The amount of raw capital, used for the turnout. + pub (crate) capital: Balance, +} + +impl Saturating for Delegations { + fn saturating_add(self, o: Self) -> Self { + Self { + votes: self.votes.saturating_add(o.votes), + capital: self.capital.saturating_add(o.capital), + } + } + + fn saturating_sub(self, o: Self) -> Self { + Self { + votes: self.votes.saturating_sub(o.votes), + capital: self.capital.saturating_sub(o.capital), + } + } + + fn saturating_mul(self, o: Self) -> Self { + Self { + votes: self.votes.saturating_mul(o.votes), + capital: self.capital.saturating_mul(o.capital), + } + } +} + impl< Balance: From + Zero + Copy + CheckedAdd + CheckedSub + CheckedMul + CheckedDiv + Bounded + Saturating @@ -41,11 +73,11 @@ impl< vote: Vote, balance: Balance, ) -> Self { - let (votes, turnout) = vote.conviction.votes(balance); + let Delegations { votes, capital } = vote.conviction.votes(balance); Self { ayes: if vote.aye { votes } else { Zero::zero() }, nays: if vote.aye { Zero::zero() } else { votes }, - turnout, + turnout: capital, } } @@ -56,19 +88,19 @@ impl< ) -> Option<()> { match vote { AccountVote::Standard { vote, balance } => { - let (votes, turnout) = vote.conviction.votes(balance); - self.turnout = self.turnout.checked_add(&turnout)?; + let Delegations { votes, capital } = vote.conviction.votes(balance); + self.turnout = self.turnout.checked_add(&capital)?; match vote.aye { true => self.ayes = self.ayes.checked_add(&votes)?, false => self.nays = self.nays.checked_add(&votes)?, } } AccountVote::Split { aye, nay } => { - let (aye_votes, aye_turnout) = Conviction::None.votes(aye); - let (nay_votes, nay_turnout) = Conviction::None.votes(nay); - self.turnout = self.turnout.checked_add(&aye_turnout)?.checked_add(&nay_turnout)?; - self.ayes = self.ayes.checked_add(&aye_votes)?; - self.nays = self.nays.checked_add(&nay_votes)?; + let aye = Conviction::None.votes(aye); + let nay = Conviction::None.votes(nay); + self.turnout = self.turnout.checked_add(&aye.capital)?.checked_add(&nay.capital)?; + self.ayes = self.ayes.checked_add(&aye.votes)?; + self.nays = self.nays.checked_add(&nay.votes)?; } } Some(()) @@ -81,40 +113,40 @@ impl< ) -> Option<()> { match vote { AccountVote::Standard { vote, balance } => { - let (votes, turnout) = vote.conviction.votes(balance); - self.turnout = self.turnout.checked_sub(&turnout)?; + let Delegations { votes, capital } = vote.conviction.votes(balance); + self.turnout = self.turnout.checked_sub(&capital)?; match vote.aye { true => self.ayes = self.ayes.checked_sub(&votes)?, false => self.nays = self.nays.checked_sub(&votes)?, } } AccountVote::Split { aye, nay } => { - let (aye_votes, aye_turnout) = Conviction::None.votes(aye); - let (nay_votes, nay_turnout) = Conviction::None.votes(nay); - self.turnout = self.turnout.checked_sub(&aye_turnout)?.checked_sub(&nay_turnout)?; - self.ayes = self.ayes.checked_sub(&aye_votes)?; - self.nays = self.nays.checked_sub(&nay_votes)?; + let aye = Conviction::None.votes(aye); + let nay = Conviction::None.votes(nay); + self.turnout = self.turnout.checked_sub(&aye.capital)?.checked_sub(&nay.capital)?; + self.ayes = self.ayes.checked_sub(&aye.votes)?; + self.nays = self.nays.checked_sub(&nay.votes)?; } } Some(()) } /// Increment some amount of votes. - pub fn increase(&mut self, approve: bool, votes: Balance, capital: Balance) -> Option<()> { - self.turnout = self.turnout.saturating_add(capital); + pub fn increase(&mut self, approve: bool, delegations: Delegations) -> Option<()> { + self.turnout = self.turnout.saturating_add(delegations.capital); match approve { - true => self.ayes = self.ayes.saturating_add(votes), - false => self.nays = self.nays.saturating_add(votes), + true => self.ayes = self.ayes.saturating_add(delegations.votes), + false => self.nays = self.nays.saturating_add(delegations.votes), } Some(()) } /// Decrement some amount of votes. - pub fn reduce(&mut self, approve: bool, votes: Balance, capital: Balance) -> Option<()> { - self.turnout = self.turnout.saturating_sub(capital); + pub fn reduce(&mut self, approve: bool, delegations: Delegations) -> Option<()> { + self.turnout = self.turnout.saturating_sub(delegations.capital); match approve { - true => self.ayes = self.ayes.saturating_sub(votes), - false => self.nays = self.nays.saturating_sub(votes), + true => self.ayes = self.ayes.saturating_sub(delegations.votes), + false => self.nays = self.nays.saturating_sub(delegations.votes), } Some(()) } diff --git a/frame/democracy/src/vote.rs b/frame/democracy/src/vote.rs index 4be532b47d96d..64753ff62dadb 100644 --- a/frame/democracy/src/vote.rs +++ b/frame/democracy/src/vote.rs @@ -19,7 +19,7 @@ use sp_std::{result::Result, convert::TryFrom}; use codec::{Encode, EncodeLike, Decode, Output, Input}; use sp_runtime::{RuntimeDebug, traits::{Saturating, Zero}}; -use crate::{Conviction, ReferendumIndex}; +use crate::{Conviction, ReferendumIndex, Delegations}; /// A number of lock periods, plus a vote, one way or the other. #[derive(Copy, Clone, Eq, PartialEq, Default, RuntimeDebug)] @@ -119,7 +119,7 @@ pub enum Voting { /// The current votes of the account. votes: Vec<(ReferendumIndex, AccountVote)>, /// The total amount of delegations that this account has received. - delegations: Balance, + delegations: Delegations, /// Any pre-existing locks from past voting/delegating activity. prior: PriorLock, }, @@ -129,18 +129,18 @@ pub enum Voting { target: AccountId, conviction: Conviction, /// The total amount of delegations that this account has received. - delegations: Balance, + delegations: Delegations, /// Any pre-existing locks from past voting/delegating activity. prior: PriorLock, }, } -impl Default for Voting { +impl Default for Voting { fn default() -> Self { Voting::Direct { votes: Vec::new(), - delegations: Zero::zero(), - prior: PriorLock(Zero::zero(), Zero::zero()), + delegations: Default::default(), + prior: PriorLock(Zero::zero(), Default::default()), } } } @@ -167,7 +167,10 @@ impl< } } - pub fn set_common(&mut self, delegations: Balance, prior: PriorLock) { + pub fn set_common(&mut self, + delegations: Delegations, + prior: PriorLock + ) { let (d, p) = match self { Voting::Direct { ref mut delegations, ref mut prior, .. } => (delegations, prior), Voting::Delegating { ref mut delegations, ref mut prior, .. } => (delegations, prior), From c391692daf94f2d1c611c7ec7d23e2fa5c9819e9 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 20 Mar 2020 13:43:03 +0100 Subject: [PATCH 18/22] proxy delegation & proxy unvoting --- frame/democracy/src/tests/proxying.rs | 27 +++++++++++++++++++++++++-- frame/democracy/src/tests/voting.rs | 11 +++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/frame/democracy/src/tests/proxying.rs b/frame/democracy/src/tests/proxying.rs index 1720044fd1773..412adf6be03e7 100644 --- a/frame/democracy/src/tests/proxying.rs +++ b/frame/democracy/src/tests/proxying.rs @@ -65,7 +65,7 @@ fn proxy_should_work() { } #[test] -fn single_proposal_should_work_with_proxy() { +fn voting_and_removing_votes_should_work_with_proxy() { new_test_ext().execute_with(|| { System::set_block_number(0); assert_ok!(propose_set_balance_and_note(1, 2, 1)); @@ -74,8 +74,31 @@ fn single_proposal_should_work_with_proxy() { let r = 0; assert_ok!(Democracy::open_proxy(Origin::signed(10), 1)); assert_ok!(Democracy::activate_proxy(Origin::signed(1), 10)); - assert_ok!(Democracy::proxy_vote(Origin::signed(10), r, aye(1))); + assert_ok!(Democracy::proxy_vote(Origin::signed(10), r, aye(1))); assert_eq!(tally(r), Tally { ayes: 1, nays: 0, turnout: 10 }); + + assert_ok!(Democracy::proxy_remove_vote(Origin::signed(10), r)); + assert_eq!(tally(r), Tally { ayes: 0, nays: 0, turnout: 0 }); }); } + +#[test] +fn delegation_and_undelegation_should_work_with_proxy() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + assert_ok!(propose_set_balance_and_note(1, 2, 1)); + fast_forward_to(2); + let r = 0; + assert_ok!(Democracy::open_proxy(Origin::signed(10), 1)); + assert_ok!(Democracy::activate_proxy(Origin::signed(1), 10)); + assert_ok!(Democracy::vote(Origin::signed(2), r, aye(2))); + + assert_ok!(Democracy::proxy_delegate(Origin::signed(10), 2, Conviction::None, 10)); + assert_eq!(tally(r), Tally { ayes: 3, nays: 0, turnout: 30 }); + + assert_ok!(Democracy::proxy_undelegate(Origin::signed(10))); + assert_eq!(tally(r), Tally { ayes: 2, nays: 0, turnout: 20 }); + }); +} + diff --git a/frame/democracy/src/tests/voting.rs b/frame/democracy/src/tests/voting.rs index ac5b3a0631bc3..559980ca90421 100644 --- a/frame/democracy/src/tests/voting.rs +++ b/frame/democracy/src/tests/voting.rs @@ -18,6 +18,17 @@ use super::*; +#[test] +fn overvoting_should_fail() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + assert_ok!(propose_set_balance_and_note(1, 2, 1)); + fast_forward_to(2); + let r = 0; + assert_noop!(Democracy::vote(Origin::signed(1), r, aye(2)), Error::::InsufficientFunds); + }); +} + #[test] fn single_proposal_should_work() { new_test_ext().execute_with(|| { From f82daed9753211a104bdde1ad2425bd0a7999be6 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 20 Mar 2020 15:49:38 +0100 Subject: [PATCH 19/22] Fix --- frame/democracy/src/vote.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/democracy/src/vote.rs b/frame/democracy/src/vote.rs index 64753ff62dadb..a41eb342aa12c 100644 --- a/frame/democracy/src/vote.rs +++ b/frame/democracy/src/vote.rs @@ -16,7 +16,7 @@ //! The vote datatype. -use sp_std::{result::Result, convert::TryFrom}; +use sp_std::{prelude::*, result::Result, convert::TryFrom}; use codec::{Encode, EncodeLike, Decode, Output, Input}; use sp_runtime::{RuntimeDebug, traits::{Saturating, Zero}}; use crate::{Conviction, ReferendumIndex, Delegations}; From d7069cdd08bf41bdad3a579e312ae70f3ce5b0ce Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 20 Mar 2020 16:10:49 +0100 Subject: [PATCH 20/22] Tests for split-voting --- frame/democracy/src/tests.rs | 7 +++++ frame/democracy/src/tests/delegation.rs | 24 ++++++++++------- frame/democracy/src/tests/voting.rs | 36 ++++++++++++++++++++----- 3 files changed, 50 insertions(+), 17 deletions(-) diff --git a/frame/democracy/src/tests.rs b/frame/democracy/src/tests.rs index 307d70121772e..f2544470aa722 100644 --- a/frame/democracy/src/tests.rs +++ b/frame/democracy/src/tests.rs @@ -222,6 +222,13 @@ fn fast_forward_to(n: u64) { } } +fn begin_referendum() -> ReferendumIndex { + System::set_block_number(0); + assert_ok!(propose_set_balance_and_note(1, 2, 1)); + fast_forward_to(2); + 0 +} + fn aye(who: u64) -> AccountVote { AccountVote::Standard { vote: AYE, balance: Balances::free_balance(&who) } } diff --git a/frame/democracy/src/tests/delegation.rs b/frame/democracy/src/tests/delegation.rs index c0ca78c108402..061a48b587798 100644 --- a/frame/democracy/src/tests/delegation.rs +++ b/frame/democracy/src/tests/delegation.rs @@ -140,11 +140,7 @@ fn single_proposal_should_work_with_undelegation() { fn single_proposal_should_work_with_delegation_and_vote() { // If transactor voted, delegated vote is overwritten. new_test_ext().execute_with(|| { - System::set_block_number(0); - assert_ok!(propose_set_balance_and_note(1, 2, 1)); - fast_forward_to(2); - let r = 0; - + let r = begin_referendum(); // Delegate, undelegate and vote. assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::None, 20)); @@ -160,11 +156,7 @@ fn single_proposal_should_work_with_delegation_and_vote() { fn conviction_should_be_honored_in_delegation() { // If transactor voted, delegated vote is overwritten. new_test_ext().execute_with(|| { - System::set_block_number(0); - assert_ok!(propose_set_balance_and_note(1, 2, 1)); - fast_forward_to(2); - let r = 0; - + let r = begin_referendum(); // Delegate, undelegate and vote. assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::Locked6x, 20)); assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); @@ -172,3 +164,15 @@ fn conviction_should_be_honored_in_delegation() { assert_eq!(tally(r), Tally { ayes: 121, nays: 0, turnout: 30 }); }); } + +#[test] +fn split_vote_delegation_should_be_ignored() { + // If transactor voted, delegated vote is overwritten. + new_test_ext().execute_with(|| { + let r = begin_referendum(); + assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::Locked6x, 20)); + assert_ok!(Democracy::vote(Origin::signed(1), r, AccountVote::Split { aye: 10, nay: 0 })); + // Delegated vote is huge. + assert_eq!(tally(r), Tally { ayes: 1, nays: 0, turnout: 10 }); + }); +} diff --git a/frame/democracy/src/tests/voting.rs b/frame/democracy/src/tests/voting.rs index 559980ca90421..06fde99cbdb5c 100644 --- a/frame/democracy/src/tests/voting.rs +++ b/frame/democracy/src/tests/voting.rs @@ -21,25 +21,47 @@ use super::*; #[test] fn overvoting_should_fail() { new_test_ext().execute_with(|| { - System::set_block_number(0); - assert_ok!(propose_set_balance_and_note(1, 2, 1)); - fast_forward_to(2); - let r = 0; + let r = begin_referendum(); assert_noop!(Democracy::vote(Origin::signed(1), r, aye(2)), Error::::InsufficientFunds); }); } +#[test] +fn split_voting_should_work() { + new_test_ext().execute_with(|| { + let r = begin_referendum(); + let v = AccountVote::Split { aye: 40, nay: 20 }; + assert_noop!(Democracy::vote(Origin::signed(5), r, v), Error::::InsufficientFunds); + let v = AccountVote::Split { aye: 30, nay: 20 }; + assert_ok!(Democracy::vote(Origin::signed(5), r, v)); + + assert_eq!(tally(r), Tally { ayes: 3, nays: 2, turnout: 50 }); + }); +} + +#[test] +fn split_vote_cancellation_should_work() { + new_test_ext().execute_with(|| { + let r = begin_referendum(); + let v = AccountVote::Split { aye: 30, nay: 20 }; + assert_ok!(Democracy::vote(Origin::signed(5), r, v)); + assert_ok!(Democracy::remove_vote(Origin::signed(5), r)); + assert_eq!(tally(r), Tally { ayes: 0, nays: 0, turnout: 0 }); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![]); + }); +} + #[test] fn single_proposal_should_work() { new_test_ext().execute_with(|| { System::set_block_number(0); assert_ok!(propose_set_balance_and_note(1, 2, 1)); - assert!(Democracy::referendum_info(0).is_none()); + let r = 0; + assert!(Democracy::referendum_info(r).is_none()); // start of 2 => next referendum scheduled. fast_forward_to(2); - - let r = 0; assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); assert_eq!(Democracy::referendum_count(), 1); From b7c922feb1eaa55763240673b76e802ae8fd0210 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 20 Mar 2020 16:12:04 +0100 Subject: [PATCH 21/22] Add missing file --- frame/democracy/src/tests/preimage.rs | 164 ++++++++++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 frame/democracy/src/tests/preimage.rs diff --git a/frame/democracy/src/tests/preimage.rs b/frame/democracy/src/tests/preimage.rs new file mode 100644 index 0000000000000..1fb805f72630d --- /dev/null +++ b/frame/democracy/src/tests/preimage.rs @@ -0,0 +1,164 @@ +// Copyright 2017-2020 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 . + +//! The preimage tests. + +use super::*; + +#[test] +fn missing_preimage_should_fail() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash(2), + VoteThreshold::SuperMajorityApprove, + 0 + ); + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + + next_block(); + next_block(); + + assert_eq!(Balances::free_balance(42), 0); + }); +} + +#[test] +fn preimage_deposit_should_be_required_and_returned() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + // fee of 100 is too much. + PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 100); + assert_noop!( + Democracy::note_preimage(Origin::signed(6), vec![0; 500]), + BalancesError::::InsufficientBalance, + ); + // fee of 1 is reasonable. + PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 1); + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 0 + ); + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + + assert_eq!(Balances::reserved_balance(6), 12); + + next_block(); + next_block(); + + assert_eq!(Balances::reserved_balance(6), 0); + assert_eq!(Balances::free_balance(6), 60); + assert_eq!(Balances::free_balance(42), 2); + }); +} + +#[test] +fn preimage_deposit_should_be_reapable_earlier_by_owner() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 1); + assert_ok!(Democracy::note_preimage(Origin::signed(6), set_balance_proposal(2))); + + assert_eq!(Balances::reserved_balance(6), 12); + + next_block(); + assert_noop!( + Democracy::reap_preimage(Origin::signed(6), set_balance_proposal_hash(2)), + Error::::TooEarly + ); + next_block(); + assert_ok!(Democracy::reap_preimage(Origin::signed(6), set_balance_proposal_hash(2))); + + assert_eq!(Balances::free_balance(6), 60); + assert_eq!(Balances::reserved_balance(6), 0); + }); +} + +#[test] +fn preimage_deposit_should_be_reapable() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + assert_noop!( + Democracy::reap_preimage(Origin::signed(5), set_balance_proposal_hash(2)), + Error::::PreimageMissing + ); + + PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 1); + assert_ok!(Democracy::note_preimage(Origin::signed(6), set_balance_proposal(2))); + assert_eq!(Balances::reserved_balance(6), 12); + + next_block(); + next_block(); + next_block(); + assert_noop!( + Democracy::reap_preimage(Origin::signed(5), set_balance_proposal_hash(2)), + Error::::TooEarly + ); + + next_block(); + assert_ok!(Democracy::reap_preimage(Origin::signed(5), set_balance_proposal_hash(2))); + assert_eq!(Balances::reserved_balance(6), 0); + assert_eq!(Balances::free_balance(6), 48); + assert_eq!(Balances::free_balance(5), 62); + }); +} + +#[test] +fn noting_imminent_preimage_for_free_should_work() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 1); + + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash(2), + VoteThreshold::SuperMajorityApprove, + 1 + ); + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + + assert_noop!( + Democracy::note_imminent_preimage(Origin::signed(7), set_balance_proposal(2)), + Error::::NotImminent + ); + + next_block(); + + // Now we're in the dispatch queue it's all good. + assert_ok!(Democracy::note_imminent_preimage(Origin::signed(7), set_balance_proposal(2))); + + next_block(); + + assert_eq!(Balances::free_balance(42), 2); + }); +} + +#[test] +fn reaping_imminent_preimage_should_fail() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + let h = set_balance_proposal_hash_and_note(2); + let r = Democracy::inject_referendum(3, h, VoteThreshold::SuperMajorityApprove, 1); + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + next_block(); + next_block(); + // now imminent. + assert_noop!(Democracy::reap_preimage(Origin::signed(6), h), Error::::Imminent); + }); +} From d585e0aabbdf2f80bbf55dbbc9ac70e430431016 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 20 Mar 2020 17:57:46 +0100 Subject: [PATCH 22/22] Persistent locking. --- frame/democracy/src/lib.rs | 3 +- frame/democracy/src/tests/lock_voting.rs | 201 +++++++++++++++++++++++ 2 files changed, 203 insertions(+), 1 deletion(-) diff --git a/frame/democracy/src/lib.rs b/frame/democracy/src/lib.rs index b9faf98719424..32371c8a3a945 100644 --- a/frame/democracy/src/lib.rs +++ b/frame/democracy/src/lib.rs @@ -1133,7 +1133,8 @@ decl_module! { fn remove_other_vote(origin, target: T::AccountId, index: ReferendumIndex) -> DispatchResult { let who = ensure_signed(origin)?; let scope = if target == who { UnvoteScope::Any } else { UnvoteScope::OnlyExpired }; - Self::try_remove_vote(&target, index, scope) + Self::try_remove_vote(&target, index, scope)?; + Ok(()) } /// Delegate the voting power (with some given conviction) of a proxied account. diff --git a/frame/democracy/src/tests/lock_voting.rs b/frame/democracy/src/tests/lock_voting.rs index 8301b45b81df3..c46bb47b893c3 100644 --- a/frame/democracy/src/tests/lock_voting.rs +++ b/frame/democracy/src/tests/lock_voting.rs @@ -161,3 +161,204 @@ fn lock_voting_should_work_with_delegation() { assert_eq!(Balances::free_balance(42), 2); }); } + +fn setup_three_referenda() -> (u32, u32, u32) { + System::set_block_number(0); + let r1 = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SimpleMajority, + 0 + ); + assert_ok!(Democracy::vote(Origin::signed(5), r1, aye(4, 10))); + + let r2 = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SimpleMajority, + 0 + ); + assert_ok!(Democracy::vote(Origin::signed(5), r2, aye(3, 20))); + + let r3 = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SimpleMajority, + 0 + ); + assert_ok!(Democracy::vote(Origin::signed(5), r3, aye(2, 50))); + + fast_forward_to(2); + + (r1, r2, r3) +} + +#[test] +fn prior_lockvotes_should_be_enforced() { + new_test_ext().execute_with(|| { + let r = setup_three_referenda(); + // r.0 locked 10 until #18. + // r.1 locked 20 until #10. + // r.2 locked 50 until #6. + + fast_forward_to(5); + assert_noop!(Democracy::remove_other_vote(Origin::signed(1), 5, r.2), Error::::NoPermission); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![the_lock(50)]); + fast_forward_to(6); + assert_ok!(Democracy::remove_other_vote(Origin::signed(1), 5, r.2)); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![the_lock(20)]); + fast_forward_to(9); + assert_noop!(Democracy::remove_other_vote(Origin::signed(1), 5, r.1), Error::::NoPermission); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![the_lock(20)]); + fast_forward_to(10); + assert_ok!(Democracy::remove_other_vote(Origin::signed(1), 5, r.1)); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![the_lock(10)]); + fast_forward_to(17); + assert_noop!(Democracy::remove_other_vote(Origin::signed(1), 5, r.0), Error::::NoPermission); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![the_lock(10)]); + fast_forward_to(18); + assert_ok!(Democracy::remove_other_vote(Origin::signed(1), 5, r.0)); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![]); + }); +} + +#[test] +fn single_consolidation_of_lockvotes_should_work_as_before() { + new_test_ext().execute_with(|| { + let r = setup_three_referenda(); + // r.0 locked 10 until #18. + // r.1 locked 20 until #10. + // r.2 locked 50 until #6. + + fast_forward_to(5); + assert_ok!(Democracy::remove_vote(Origin::signed(5), r.2)); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![the_lock(50)]); + fast_forward_to(6); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![the_lock(20)]); + + fast_forward_to(9); + assert_ok!(Democracy::remove_vote(Origin::signed(5), r.1)); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![the_lock(20)]); + fast_forward_to(10); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![the_lock(10)]); + + fast_forward_to(17); + assert_ok!(Democracy::remove_vote(Origin::signed(5), r.0)); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![the_lock(10)]); + fast_forward_to(18); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![]); + }); +} + +#[test] +fn multi_consolidation_of_lockvotes_should_be_conservative() { + new_test_ext().execute_with(|| { + let r = setup_three_referenda(); + // r.0 locked 10 until #18. + // r.1 locked 20 until #10. + // r.2 locked 50 until #6. + + assert_ok!(Democracy::remove_vote(Origin::signed(5), r.2)); + assert_ok!(Democracy::remove_vote(Origin::signed(5), r.1)); + assert_ok!(Democracy::remove_vote(Origin::signed(5), r.0)); + + fast_forward_to(6); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert!(Balances::locks(5)[0].amount >= 20); + + fast_forward_to(10); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert!(Balances::locks(5)[0].amount >= 10); + + fast_forward_to(18); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![]); + }); +} + +#[test] +fn locks_should_persist_from_voting_to_delegation() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SimpleMajority, + 0 + ); + assert_ok!(Democracy::vote(Origin::signed(5), r, aye(4, 10))); + fast_forward_to(2); + assert_ok!(Democracy::remove_vote(Origin::signed(5), r)); + // locked 10 until #18. + + assert_ok!(Democracy::delegate(Origin::signed(5), 1, Conviction::Locked3x, 20)); + // locked 20. + assert!(Balances::locks(5)[0].amount == 20); + + assert_ok!(Democracy::undelegate(Origin::signed(5))); + // locked 20 until #10 + + fast_forward_to(9); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert!(Balances::locks(5)[0].amount == 20); + + fast_forward_to(10); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert!(Balances::locks(5)[0].amount >= 10); + + fast_forward_to(17); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert!(Balances::locks(5)[0].amount >= 10); + + fast_forward_to(18); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![]); + }); +} + +#[test] +fn locks_should_persist_from_delegation_to_voting() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + assert_ok!(Democracy::delegate(Origin::signed(5), 1, Conviction::Locked5x, 5)); + assert_ok!(Democracy::undelegate(Origin::signed(5))); + // locked 5 until #32 + + let r = setup_three_referenda(); + // r.0 locked 10 until #18. + // r.1 locked 20 until #10. + // r.2 locked 50 until #6. + + assert_ok!(Democracy::remove_vote(Origin::signed(5), r.2)); + assert_ok!(Democracy::remove_vote(Origin::signed(5), r.1)); + assert_ok!(Democracy::remove_vote(Origin::signed(5), r.0)); + + fast_forward_to(6); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert!(Balances::locks(5)[0].amount >= 20); + + fast_forward_to(10); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert!(Balances::locks(5)[0].amount >= 10); + + fast_forward_to(18); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert!(Balances::locks(5)[0].amount >= 5); + + fast_forward_to(32); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![]); + }); +}