diff --git a/substrate/core/primitives/src/u32_trait.rs b/substrate/core/primitives/src/u32_trait.rs index 3fcdceac4cbbfacea532c498648ccee980a0e36c..d8fac34c301ab4ee78417c1d8c8755e89b53795a 100644 --- a/substrate/core/primitives/src/u32_trait.rs +++ b/substrate/core/primitives/src/u32_trait.rs @@ -21,6 +21,7 @@ pub trait Value { /// The actual value represented by the impl'ing type. const VALUE: u32; } + /// Type representing the value 0 for the `Value` trait. pub struct _0; impl Value for _0 { const VALUE: u32 = 0; } /// Type representing the value 1 for the `Value` trait. @@ -55,22 +56,174 @@ pub struct _14; impl Value for _14 { const VALUE: u32 = 14; } pub struct _15; impl Value for _15 { const VALUE: u32 = 15; } /// Type representing the value 16 for the `Value` trait. pub struct _16; impl Value for _16 { const VALUE: u32 = 16; } +/// Type representing the value 17 for the `Value` trait. +pub struct _17; impl Value for _17 { const VALUE: u32 = 17; } +/// Type representing the value 18 for the `Value` trait. +pub struct _18; impl Value for _18 { const VALUE: u32 = 18; } +/// Type representing the value 19 for the `Value` trait. +pub struct _19; impl Value for _19 { const VALUE: u32 = 19; } +/// Type representing the value 20 for the `Value` trait. +pub struct _20; impl Value for _20 { const VALUE: u32 = 20; } +/// Type representing the value 21 for the `Value` trait. +pub struct _21; impl Value for _21 { const VALUE: u32 = 21; } +/// Type representing the value 22 for the `Value` trait. +pub struct _22; impl Value for _22 { const VALUE: u32 = 22; } +/// Type representing the value 23 for the `Value` trait. +pub struct _23; impl Value for _23 { const VALUE: u32 = 23; } /// Type representing the value 24 for the `Value` trait. pub struct _24; impl Value for _24 { const VALUE: u32 = 24; } +/// Type representing the value 25 for the `Value` trait. +pub struct _25; impl Value for _25 { const VALUE: u32 = 25; } +/// Type representing the value 26 for the `Value` trait. +pub struct _26; impl Value for _26 { const VALUE: u32 = 26; } +/// Type representing the value 27 for the `Value` trait. +pub struct _27; impl Value for _27 { const VALUE: u32 = 27; } +/// Type representing the value 28 for the `Value` trait. +pub struct _28; impl Value for _28 { const VALUE: u32 = 28; } +/// Type representing the value 29 for the `Value` trait. +pub struct _29; impl Value for _29 { const VALUE: u32 = 29; } +/// Type representing the value 30 for the `Value` trait. +pub struct _30; impl Value for _30 { const VALUE: u32 = 30; } +/// Type representing the value 31 for the `Value` trait. +pub struct _31; impl Value for _31 { const VALUE: u32 = 31; } /// Type representing the value 32 for the `Value` trait. pub struct _32; impl Value for _32 { const VALUE: u32 = 32; } +/// Type representing the value 33 for the `Value` trait. +pub struct _33; impl Value for _33 { const VALUE: u32 = 33; } +/// Type representing the value 34 for the `Value` trait. +pub struct _34; impl Value for _34 { const VALUE: u32 = 34; } +/// Type representing the value 35 for the `Value` trait. +pub struct _35; impl Value for _35 { const VALUE: u32 = 35; } +/// Type representing the value 36 for the `Value` trait. +pub struct _36; impl Value for _36 { const VALUE: u32 = 36; } +/// Type representing the value 37 for the `Value` trait. +pub struct _37; impl Value for _37 { const VALUE: u32 = 37; } +/// Type representing the value 38 for the `Value` trait. +pub struct _38; impl Value for _38 { const VALUE: u32 = 38; } +/// Type representing the value 39 for the `Value` trait. +pub struct _39; impl Value for _39 { const VALUE: u32 = 39; } /// Type representing the value 40 for the `Value` trait. pub struct _40; impl Value for _40 { const VALUE: u32 = 40; } +/// Type representing the value 41 for the `Value` trait. +pub struct _41; impl Value for _41 { const VALUE: u32 = 41; } +/// Type representing the value 42 for the `Value` trait. +pub struct _42; impl Value for _42 { const VALUE: u32 = 42; } +/// Type representing the value 43 for the `Value` trait. +pub struct _43; impl Value for _43 { const VALUE: u32 = 43; } +/// Type representing the value 44 for the `Value` trait. +pub struct _44; impl Value for _44 { const VALUE: u32 = 44; } +/// Type representing the value 45 for the `Value` trait. +pub struct _45; impl Value for _45 { const VALUE: u32 = 45; } +/// Type representing the value 46 for the `Value` trait. +pub struct _46; impl Value for _46 { const VALUE: u32 = 46; } +/// Type representing the value 47 for the `Value` trait. +pub struct _47; impl Value for _47 { const VALUE: u32 = 47; } /// Type representing the value 48 for the `Value` trait. pub struct _48; impl Value for _48 { const VALUE: u32 = 48; } +/// Type representing the value 49 for the `Value` trait. +pub struct _49; impl Value for _49 { const VALUE: u32 = 49; } +/// Type representing the value 50 for the `Value` trait. +pub struct _50; impl Value for _50 { const VALUE: u32 = 50; } +/// Type representing the value 51 for the `Value` trait. +pub struct _51; impl Value for _51 { const VALUE: u32 = 51; } +/// Type representing the value 52 for the `Value` trait. +pub struct _52; impl Value for _52 { const VALUE: u32 = 52; } +/// Type representing the value 53 for the `Value` trait. +pub struct _53; impl Value for _53 { const VALUE: u32 = 53; } +/// Type representing the value 54 for the `Value` trait. +pub struct _54; impl Value for _54 { const VALUE: u32 = 54; } +/// Type representing the value 55 for the `Value` trait. +pub struct _55; impl Value for _55 { const VALUE: u32 = 55; } /// Type representing the value 56 for the `Value` trait. pub struct _56; impl Value for _56 { const VALUE: u32 = 56; } +/// Type representing the value 57 for the `Value` trait. +pub struct _57; impl Value for _57 { const VALUE: u32 = 57; } +/// Type representing the value 58 for the `Value` trait. +pub struct _58; impl Value for _58 { const VALUE: u32 = 58; } +/// Type representing the value 59 for the `Value` trait. +pub struct _59; impl Value for _59 { const VALUE: u32 = 59; } +/// Type representing the value 60 for the `Value` trait. +pub struct _60; impl Value for _60 { const VALUE: u32 = 60; } +/// Type representing the value 61 for the `Value` trait. +pub struct _61; impl Value for _61 { const VALUE: u32 = 61; } +/// Type representing the value 62 for the `Value` trait. +pub struct _62; impl Value for _62 { const VALUE: u32 = 62; } +/// Type representing the value 63 for the `Value` trait. +pub struct _63; impl Value for _63 { const VALUE: u32 = 63; } /// Type representing the value 64 for the `Value` trait. pub struct _64; impl Value for _64 { const VALUE: u32 = 64; } +/// Type representing the value 65 for the `Value` trait. +pub struct _65; impl Value for _65 { const VALUE: u32 = 65; } +/// Type representing the value 66 for the `Value` trait. +pub struct _66; impl Value for _66 { const VALUE: u32 = 66; } +/// Type representing the value 67 for the `Value` trait. +pub struct _67; impl Value for _67 { const VALUE: u32 = 67; } +/// Type representing the value 68 for the `Value` trait. +pub struct _68; impl Value for _68 { const VALUE: u32 = 68; } +/// Type representing the value 69 for the `Value` trait. +pub struct _69; impl Value for _69 { const VALUE: u32 = 69; } +/// Type representing the value 70 for the `Value` trait. +pub struct _70; impl Value for _70 { const VALUE: u32 = 70; } +/// Type representing the value 71 for the `Value` trait. +pub struct _71; impl Value for _71 { const VALUE: u32 = 71; } +/// Type representing the value 72 for the `Value` trait. +pub struct _72; impl Value for _72 { const VALUE: u32 = 72; } +/// Type representing the value 73 for the `Value` trait. +pub struct _73; impl Value for _73 { const VALUE: u32 = 73; } +/// Type representing the value 74 for the `Value` trait. +pub struct _74; impl Value for _74 { const VALUE: u32 = 74; } +/// Type representing the value 75 for the `Value` trait. +pub struct _75; impl Value for _75 { const VALUE: u32 = 75; } +/// Type representing the value 76 for the `Value` trait. +pub struct _76; impl Value for _76 { const VALUE: u32 = 76; } +/// Type representing the value 77 for the `Value` trait. +pub struct _77; impl Value for _77 { const VALUE: u32 = 77; } +/// Type representing the value 78 for the `Value` trait. +pub struct _78; impl Value for _78 { const VALUE: u32 = 78; } +/// Type representing the value 79 for the `Value` trait. +pub struct _79; impl Value for _79 { const VALUE: u32 = 79; } /// Type representing the value 80 for the `Value` trait. pub struct _80; impl Value for _80 { const VALUE: u32 = 80; } +/// Type representing the value 81 for the `Value` trait. +pub struct _81; impl Value for _81 { const VALUE: u32 = 81; } +/// Type representing the value 82 for the `Value` trait. +pub struct _82; impl Value for _82 { const VALUE: u32 = 82; } +/// Type representing the value 83 for the `Value` trait. +pub struct _83; impl Value for _83 { const VALUE: u32 = 83; } +/// Type representing the value 84 for the `Value` trait. +pub struct _84; impl Value for _84 { const VALUE: u32 = 84; } +/// Type representing the value 85 for the `Value` trait. +pub struct _85; impl Value for _85 { const VALUE: u32 = 85; } +/// Type representing the value 86 for the `Value` trait. +pub struct _86; impl Value for _86 { const VALUE: u32 = 86; } +/// Type representing the value 87 for the `Value` trait. +pub struct _87; impl Value for _87 { const VALUE: u32 = 87; } +/// Type representing the value 88 for the `Value` trait. +pub struct _88; impl Value for _88 { const VALUE: u32 = 88; } +/// Type representing the value 89 for the `Value` trait. +pub struct _89; impl Value for _89 { const VALUE: u32 = 89; } +/// Type representing the value 90 for the `Value` trait. +pub struct _90; impl Value for _90 { const VALUE: u32 = 90; } +/// Type representing the value 91 for the `Value` trait. +pub struct _91; impl Value for _91 { const VALUE: u32 = 91; } +/// Type representing the value 92 for the `Value` trait. +pub struct _92; impl Value for _92 { const VALUE: u32 = 92; } +/// Type representing the value 93 for the `Value` trait. +pub struct _93; impl Value for _93 { const VALUE: u32 = 93; } +/// Type representing the value 94 for the `Value` trait. +pub struct _94; impl Value for _94 { const VALUE: u32 = 94; } +/// Type representing the value 95 for the `Value` trait. +pub struct _95; impl Value for _95 { const VALUE: u32 = 95; } /// Type representing the value 96 for the `Value` trait. pub struct _96; impl Value for _96 { const VALUE: u32 = 96; } +/// Type representing the value 97 for the `Value` trait. +pub struct _97; impl Value for _97 { const VALUE: u32 = 97; } +/// Type representing the value 98 for the `Value` trait. +pub struct _98; impl Value for _98 { const VALUE: u32 = 98; } +/// Type representing the value 99 for the `Value` trait. +pub struct _99; impl Value for _99 { const VALUE: u32 = 99; } +/// Type representing the value 100 for the `Value` trait. +pub struct _100; impl Value for _100 { const VALUE: u32 = 100; } /// Type representing the value 112 for the `Value` trait. pub struct _112; impl Value for _112 { const VALUE: u32 = 112; } /// Type representing the value 128 for the `Value` trait. @@ -87,3 +240,4 @@ pub struct _256; impl Value for _256 { const VALUE: u32 = 256; } pub struct _384; impl Value for _384 { const VALUE: u32 = 384; } /// Type representing the value 512 for the `Value` trait. pub struct _512; impl Value for _512 { const VALUE: u32 = 512; } + diff --git a/substrate/core/sr-primitives/src/traits.rs b/substrate/core/sr-primitives/src/traits.rs index 596345036079dfb9d24054d53e4633237976e9ea..f412ede0afb9bf1bbf5591b471c4c97c73d51f3c 100644 --- a/substrate/core/sr-primitives/src/traits.rs +++ b/substrate/core/sr-primitives/src/traits.rs @@ -73,7 +73,11 @@ pub trait EnsureOrigin<OuterOrigin> { /// A return type. type Success; /// Perform the origin check. - fn ensure_origin(o: OuterOrigin) -> result::Result<Self::Success, &'static str>; + fn ensure_origin(o: OuterOrigin) -> result::Result<Self::Success, &'static str> { + Self::try_origin(o).map_err(|_| "Invalid origin") + } + /// Perform the origin check. + fn try_origin(o: OuterOrigin) -> result::Result<Self::Success, OuterOrigin>; } /// Means of changing one type into another in a manner dependent on the source type. diff --git a/substrate/node/cli/src/chain_spec.rs b/substrate/node/cli/src/chain_spec.rs index 2ce4a3e656fc5818e62ac510360e460a8cb815bf..bc24de9ddfebd14dece6d89ae3bf786af0696d12 100644 --- a/substrate/node/cli/src/chain_spec.rs +++ b/substrate/node/cli/src/chain_spec.rs @@ -18,7 +18,7 @@ use primitives::{ed25519::Public as AuthorityId, ed25519, sr25519, Pair, crypto::UncheckedInto}; use node_primitives::AccountId; -use node_runtime::{ConsensusConfig, CouncilSeatsConfig, CouncilVotingConfig, DemocracyConfig, +use node_runtime::{ConsensusConfig, CouncilSeatsConfig, DemocracyConfig, SessionConfig, StakingConfig, StakerStatus, TimestampConfig, BalancesConfig, TreasuryConfig, SudoConfig, ContractConfig, GrandpaConfig, IndicesConfig, Permill, Perbill}; pub use node_runtime::GenesisConfig; @@ -132,11 +132,6 @@ fn staging_testnet_config_genesis() -> GenesisConfig { desired_seats: 0, inactive_grace_period: 1, // one additional vote should go by before an inactive voter can be reaped. }), - council_voting: Some(CouncilVotingConfig { - cooloff_period: 4 * DAYS, - voting_period: 1 * DAYS, - enact_delay_period: 0, - }), timestamp: Some(TimestampConfig { minimum_period: SECS_PER_BLOCK / 2, // due to the nature of aura the slots are 2*period }), @@ -312,11 +307,6 @@ pub fn testnet_genesis( desired_seats: (endowed_accounts.len() / 2 - initial_authorities.len()) as u32, inactive_grace_period: 1, }), - council_voting: Some(CouncilVotingConfig { - cooloff_period: 75, - voting_period: 20, - enact_delay_period: 0, - }), timestamp: Some(TimestampConfig { minimum_period: 2, // 2*2=4 second block time. }), diff --git a/substrate/node/executor/src/lib.rs b/substrate/node/executor/src/lib.rs index 8813850bbd05241348e8c44dba9bf2715b5f3f7d..05dc424d496b2fc8c908e84823352c3adb1b3d2e 100644 --- a/substrate/node/executor/src/lib.rs +++ b/substrate/node/executor/src/lib.rs @@ -316,7 +316,6 @@ mod tests { }), democracy: Some(Default::default()), council_seats: Some(Default::default()), - council_voting: Some(Default::default()), timestamp: Some(Default::default()), treasury: Some(Default::default()), contract: Some(Default::default()), diff --git a/substrate/node/runtime/src/lib.rs b/substrate/node/runtime/src/lib.rs index 5daeccf72c7ab1677c05085c96803560a463a431..f0c1a8ae7d3158cc164b0289a767c2ca54916343 100644 --- a/substrate/node/runtime/src/lib.rs +++ b/substrate/node/runtime/src/lib.rs @@ -22,7 +22,7 @@ use rstd::prelude::*; use support::{construct_runtime, parameter_types}; -use substrate_primitives::u32_trait::{_2, _4}; +use substrate_primitives::u32_trait::{_1, _2, _3, _4}; use node_primitives::{ AccountId, AccountIndex, Balance, BlockNumber, Hash, Index, AuthorityId, Signature, AuthoritySignature }; @@ -37,7 +37,7 @@ use runtime_primitives::traits::{ BlakeTwo256, Block as BlockT, DigestFor, NumberFor, StaticLookup, AuthorityIdFor, Convert, }; use version::RuntimeVersion; -use council::{motions as council_motions, voting as council_voting}; +use council::{motions as council_motions}; #[cfg(feature = "std")] use council::seats as council_seats; #[cfg(any(feature = "std", test))] @@ -156,8 +156,10 @@ const BUCKS: Balance = 1_000_000_000_000; 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 MinimumDeposit: Balance = 100 * BUCKS; pub const EnactmentPeriod: BlockNumber = 30 * 24 * 60 * MINUTES; + pub const CooloffPeriod: BlockNumber = 30 * 24 * 60 * MINUTES; } impl democracy::Trait for Runtime { type Proposal = Call; @@ -166,17 +168,21 @@ impl democracy::Trait for Runtime { type EnactmentPeriod = EnactmentPeriod; type LaunchPeriod = LaunchPeriod; type VotingPeriod = VotingPeriod; + type EmergencyVotingPeriod = EmergencyVotingPeriod; type MinimumDeposit = MinimumDeposit; + type ExternalOrigin = council_motions::EnsureProportionAtLeast<_1, _2, AccountId>; + type ExternalMajorityOrigin = council_motions::EnsureProportionAtLeast<_2, _3, AccountId>; + type EmergencyOrigin = council_motions::EnsureProportionAtLeast<_1, _1, AccountId>; + type CancellationOrigin = council_motions::EnsureProportionAtLeast<_2, _3, AccountId>; + type VetoOrigin = council_motions::EnsureMember<AccountId>; + type CooloffPeriod = CooloffPeriod; } impl council::Trait for Runtime { type Event = Event; type BadPresentation = (); type BadReaper = (); -} - -impl council::voting::Trait for Runtime { - type Event = Event; + type OnMembersChanged = CouncilMotions; } impl council::motions::Trait for Runtime { @@ -187,8 +193,8 @@ impl council::motions::Trait for Runtime { impl treasury::Trait for Runtime { type Currency = Balances; - type ApproveOrigin = council_motions::EnsureMembers<_4>; - type RejectOrigin = council_motions::EnsureMembers<_2>; + type ApproveOrigin = council_motions::EnsureMembers<_4, AccountId>; + type RejectOrigin = council_motions::EnsureMembers<_2, AccountId>; type Event = Event; type MintedForSpending = (); type ProposalRejection = (); @@ -236,8 +242,7 @@ construct_runtime!( Staking: staking::{default, OfflineWorker}, Democracy: democracy, Council: council::{Module, Call, Storage, Event<T>}, - CouncilVoting: council_voting, - CouncilMotions: council_motions::{Module, Call, Storage, Event<T>, Origin}, + CouncilMotions: council_motions::{Module, Call, Storage, Event<T>, Origin<T>}, CouncilSeats: council_seats::{Config<T>}, FinalityTracker: finality_tracker::{Module, Call, Inherent}, Grandpa: grandpa::{Module, Call, Storage, Config<T>, Log(), Event<T>}, diff --git a/substrate/srml/contract/src/lib.rs b/substrate/srml/contract/src/lib.rs index 3f05bb8a8fa0e592ba9e401863edae2a4180d0d2..0111ce7840204e6112045579fe32349c6770eceb 100644 --- a/substrate/srml/contract/src/lib.rs +++ b/substrate/srml/contract/src/lib.rs @@ -506,10 +506,10 @@ decl_module! { fn claim_surcharge(origin, dest: T::AccountId, aux_sender: Option<T::AccountId>) { let origin = origin.into(); let (signed, rewarded) = match origin { - Some(system::RawOrigin::Signed(ref account)) if aux_sender.is_none() => { + Ok(system::RawOrigin::Signed(ref account)) if aux_sender.is_none() => { (true, account) }, - Some(system::RawOrigin::None) if aux_sender.is_some() => { + Ok(system::RawOrigin::None) if aux_sender.is_some() => { (false, aux_sender.as_ref().expect("checked above")) }, _ => return Err("Invalid surcharge claim: origin must be signed or \ diff --git a/substrate/srml/council/src/lib.rs b/substrate/srml/council/src/lib.rs index ac5396e4daf428d66ca7ab8d9b26736b7ce712c1..a72e93c9ebc2202457d3b00ae20014528d0c76c7 100644 --- a/substrate/srml/council/src/lib.rs +++ b/substrate/srml/council/src/lib.rs @@ -18,34 +18,42 @@ #![cfg_attr(not(feature = "std"), no_std)] -pub mod voting; pub mod motions; pub mod seats; pub use crate::seats::{Trait, Module, RawEvent, Event, VoteIndex}; +/// Trait for type that can handle incremental changes to a set of account IDs. +pub trait OnMembersChanged<AccountId> { + /// A number of members `new` just joined the set and replaced some `old` ones. + fn on_members_changed(new: &[AccountId], old: &[AccountId]); +} + +impl<T> OnMembersChanged<T> for () { + fn on_members_changed(_new: &[T], _old: &[T]) {} +} + #[cfg(test)] mod tests { // These re-exports are here for a reason, edit with care pub use super::*; pub use runtime_io::with_externalities; use srml_support::{impl_outer_origin, impl_outer_event, impl_outer_dispatch, parameter_types}; - pub use substrate_primitives::H256; - pub use primitives::BuildStorage; - pub use primitives::traits::{BlakeTwo256, IdentityLookup}; - pub use primitives::testing::{Digest, DigestItem, Header}; - pub use substrate_primitives::{Blake2Hasher}; - pub use {seats, motions, voting}; + pub use substrate_primitives::{H256, Blake2Hasher, u32_trait::{_1, _2, _3, _4}}; + pub use primitives::{ + BuildStorage, traits::{BlakeTwo256, IdentityLookup}, testing::{Digest, DigestItem, Header} + }; + pub use {seats, motions}; impl_outer_origin! { pub enum Origin for Test { - motions + motions<T> } } impl_outer_event! { pub enum Event for Test { - balances<T>, democracy<T>, seats<T>, voting<T>, motions<T>, + balances<T>, democracy<T>, seats<T>, motions<T>, } } @@ -86,6 +94,7 @@ mod tests { pub const VotingPeriod: u64 = 3; pub const MinimumDeposit: u64 = 1; pub const EnactmentPeriod: u64 = 0; + pub const CooloffPeriod: u64 = 2; } impl democracy::Trait for Test { type Proposal = Call; @@ -93,22 +102,27 @@ mod tests { type Currency = balances::Module<Self>; type EnactmentPeriod = EnactmentPeriod; type LaunchPeriod = LaunchPeriod; + type EmergencyVotingPeriod = VotingPeriod; type VotingPeriod = VotingPeriod; type MinimumDeposit = MinimumDeposit; + type ExternalOrigin = motions::EnsureProportionAtLeast<_1, _2, u64>; + type ExternalMajorityOrigin = motions::EnsureProportionAtLeast<_2, _3, u64>; + type EmergencyOrigin = motions::EnsureProportionAtLeast<_1, _1, u64>; + type CancellationOrigin = motions::EnsureProportionAtLeast<_2, _3, u64>; + type VetoOrigin = motions::EnsureMember<u64>; + type CooloffPeriod = CooloffPeriod; } impl seats::Trait for Test { type Event = Event; type BadPresentation = (); type BadReaper = (); + type OnMembersChanged = CouncilMotions; } impl motions::Trait for Test { type Origin = Origin; type Proposal = Call; type Event = Event; } - impl voting::Trait for Test { - type Event = Event; - } pub fn new_test_ext(with_council: bool) -> runtime_io::TestExternalities<Blake2Hasher> { let mut t = system::GenesisConfig::<Test>::default().build_storage().unwrap().0; @@ -138,11 +152,6 @@ mod tests { desired_seats: 2, term_duration: 5, }.build_storage().unwrap().0); - t.extend(voting::GenesisConfig::<Test> { - cooloff_period: 2, - voting_period: 1, - enact_delay_period: 0, - }.build_storage().unwrap().0); runtime_io::TestExternalities::new(t) } @@ -150,6 +159,5 @@ mod tests { pub type Balances = balances::Module<Test>; pub type Democracy = democracy::Module<Test>; pub type Council = seats::Module<Test>; - pub type CouncilVoting = voting::Module<Test>; pub type CouncilMotions = motions::Module<Test>; } diff --git a/substrate/srml/council/src/motions.rs b/substrate/srml/council/src/motions.rs index e560fadb9c5a49a5a34e7b5b9d45932a480d868c..4ab4e84c12b779904f2fd3eaf8c6e752ae60ee73 100644 --- a/substrate/srml/council/src/motions.rs +++ b/substrate/srml/council/src/motions.rs @@ -16,21 +16,27 @@ //! Council voting system. -use rstd::prelude::*; -use rstd::result; +use rstd::{prelude::*, result}; use substrate_primitives::u32_trait::Value as U32; use primitives::traits::{Hash, EnsureOrigin}; -use srml_support::dispatch::{Dispatchable, Parameter}; -use srml_support::{StorageValue, StorageMap, decl_module, decl_event, decl_storage, ensure}; -use super::{Trait as CouncilTrait, Module as Council}; +use srml_support::{ + dispatch::{Dispatchable, Parameter}, codec::{Encode, Decode}, + StorageValue, StorageMap, decl_module, decl_event, decl_storage, ensure +}; +use super::{Trait as CouncilTrait, Module as Council, OnMembersChanged}; use system::{self, ensure_signed}; /// Simple index type for proposal counting. pub type ProposalIndex = u32; +/// A number of council members. +/// +/// This also serves as a number of voting members, and since for motions, each council member may +/// vote exactly once, therefore also the number of votes for any given motion. +pub type MemberCount = u32; pub trait Trait: CouncilTrait { /// The outer origin type. - type Origin: From<Origin>; + type Origin: From<RawOrigin<Self::AccountId>>; /// The outer call dispatch type. type Proposal: Parameter + Dispatchable<Origin=<Self as Trait>::Origin>; @@ -42,31 +48,79 @@ pub trait Trait: CouncilTrait { /// Origin for the council module. #[derive(PartialEq, Eq, Clone)] #[cfg_attr(feature = "std", derive(Debug))] -pub enum Origin { - /// It has been condoned by a given number of council members. - Members(u32), +pub enum RawOrigin<AccountId> { + /// It has been condoned by a given number of council members from a given total. + Members(MemberCount, MemberCount), + /// It has been condoned by a single council member. + Member(AccountId), +} + +/// Origin for the council module. +pub type Origin<T> = RawOrigin<<T as system::Trait>::AccountId>; + +#[derive(PartialEq, Eq, Clone, Encode, Decode)] +#[cfg_attr(feature = "std", derive(Debug))] +/// Info for keeping track of a motion being voted on. +pub struct Votes<AccountId> { + /// The proposal's unique index. + index: ProposalIndex, + /// The number of approval votes that are needed to pass the motion. + threshold: MemberCount, + /// The current set of voters that approved it. + ayes: Vec<AccountId>, + /// The current set of voters that rejected it. + nays: Vec<AccountId>, +} + +decl_storage! { + trait Store for Module<T: Trait> as CouncilMotions { + /// The hashes of the active proposals. + pub Proposals get(proposals): Vec<T::Hash>; + /// Actual proposal for a given hash, if it's current. + pub ProposalOf get(proposal_of): map T::Hash => Option<<T as Trait>::Proposal>; + /// Votes on a given proposal, if it is ongoing. + pub Voting get(voting): map T::Hash => Option<Votes<T::AccountId>>; + /// Proposals so far. + pub ProposalCount get(proposal_count): u32; + } } decl_event!( pub enum Event<T> where <T as system::Trait>::Hash, <T as system::Trait>::AccountId { - /// A motion (given hash) has been proposed (by given account) with a threshold (given u32). - Proposed(AccountId, ProposalIndex, Hash, u32), + /// A motion (given hash) has been proposed (by given account) with a threshold (given + /// `MemberCount`). + Proposed(AccountId, ProposalIndex, Hash, MemberCount), /// A motion (given hash) has been voted on by given account, leaving - /// a tally (yes votes and no votes given as u32s respectively). - Voted(AccountId, Hash, bool, u32, u32), + /// a tally (yes votes and no votes given respectively as `MemberCount`). + Voted(AccountId, Hash, bool, MemberCount, MemberCount), /// A motion was approved by the required threshold. Approved(Hash), /// A motion was not approved by the required threshold. Disapproved(Hash), /// A motion was executed; `bool` is true if returned without error. Executed(Hash, bool), + /// A single councillor did some action; `bool` is true if returned without error. + MemberExecuted(Hash, bool), } ); decl_module! { pub struct Module<T: Trait> for enum Call where origin: <T as system::Trait>::Origin { fn deposit_event<T>() = default; - fn propose(origin, #[compact] threshold: u32, proposal: Box<<T as Trait>::Proposal>) { + + /// Dispatch a proposal from a councilor using the `Member` origin. + /// + /// Origin must be a council member. + fn execute(origin, proposal: Box<<T as Trait>::Proposal>) { + let who = ensure_signed(origin)?; + ensure!(Self::is_councillor(&who), "proposer not on council"); + + let proposal_hash = T::Hashing::hash_of(&proposal); + let ok = proposal.dispatch(RawOrigin::Member(who).into()).is_ok(); + Self::deposit_event(RawEvent::MemberExecuted(proposal_hash, ok)); + } + + fn propose(origin, #[compact] threshold: MemberCount, proposal: Box<<T as Trait>::Proposal>) { let who = ensure_signed(origin)?; ensure!(Self::is_councillor(&who), "proposer not on council"); @@ -76,14 +130,16 @@ decl_module! { ensure!(!<ProposalOf<T>>::exists(proposal_hash), "duplicate proposals not allowed"); if threshold < 2 { - let ok = proposal.dispatch(Origin::Members(1).into()).is_ok(); + let seats = <Council<T>>::active_council().len() as MemberCount; + let ok = proposal.dispatch(RawOrigin::Members(1, seats).into()).is_ok(); Self::deposit_event(RawEvent::Executed(proposal_hash, ok)); } else { let index = Self::proposal_count(); <ProposalCount<T>>::mutate(|i| *i += 1); <Proposals<T>>::mutate(|proposals| proposals.push(proposal_hash)); <ProposalOf<T>>::insert(proposal_hash, *proposal); - <Voting<T>>::insert(proposal_hash, (index, threshold, vec![who.clone()], vec![])); + let votes = Votes { index, threshold, ayes: vec![who.clone()], nays: vec![] }; + <Voting<T>>::insert(proposal_hash, votes); Self::deposit_event(RawEvent::Proposed(who, index, proposal_hash, threshold)); } @@ -95,46 +151,46 @@ decl_module! { ensure!(Self::is_councillor(&who), "voter not on council"); let mut voting = Self::voting(&proposal).ok_or("proposal must exist")?; - ensure!(voting.0 == index, "mismatched index"); + ensure!(voting.index == index, "mismatched index"); - let position_yes = voting.2.iter().position(|a| a == &who); - let position_no = voting.3.iter().position(|a| a == &who); + let position_yes = voting.ayes.iter().position(|a| a == &who); + let position_no = voting.nays.iter().position(|a| a == &who); if approve { if position_yes.is_none() { - voting.2.push(who.clone()); + voting.ayes.push(who.clone()); } else { return Err("duplicate vote ignored") } if let Some(pos) = position_no { - voting.3.swap_remove(pos); + voting.nays.swap_remove(pos); } } else { if position_no.is_none() { - voting.3.push(who.clone()); + voting.nays.push(who.clone()); } else { return Err("duplicate vote ignored") } if let Some(pos) = position_yes { - voting.2.swap_remove(pos); + voting.ayes.swap_remove(pos); } } - let yes_votes = voting.2.len() as u32; - let no_votes = voting.3.len() as u32; + let yes_votes = voting.ayes.len() as MemberCount; + let no_votes = voting.nays.len() as MemberCount; Self::deposit_event(RawEvent::Voted(who, proposal, approve, yes_votes, no_votes)); - let threshold = voting.1; - let potential_votes = <Council<T>>::active_council().len() as u32; - let approved = yes_votes >= threshold; - let disapproved = potential_votes.saturating_sub(no_votes) < threshold; + let seats = <Council<T>>::active_council().len() as MemberCount; + let approved = yes_votes >= voting.threshold; + let disapproved = seats.saturating_sub(no_votes) < voting.threshold; if approved || disapproved { if approved { Self::deposit_event(RawEvent::Approved(proposal)); // execute motion, assuming it exists. if let Some(p) = <ProposalOf<T>>::take(&proposal) { - let ok = p.dispatch(Origin::Members(threshold).into()).is_ok(); + let origin = RawOrigin::Members(voting.threshold, seats).into(); + let ok = p.dispatch(origin).is_ok(); Self::deposit_event(RawEvent::Executed(proposal, ok)); } } else { @@ -153,22 +209,6 @@ decl_module! { } } -decl_storage! { - trait Store for Module<T: Trait> as CouncilMotions { - /// The (hashes of) the active proposals. - pub Proposals get(proposals): Vec<T::Hash>; - /// Actual proposal for a given hash, if it's current. - pub ProposalOf get(proposal_of): map T::Hash => Option< <T as Trait>::Proposal >; - /// Votes for a given proposal: (required_yes_votes, yes_voters, no_voters). - pub Voting get(voting): map T::Hash => Option<(ProposalIndex, u32, Vec<T::AccountId>, Vec<T::AccountId>)>; - /// Proposals so far. - pub ProposalCount get(proposal_count): u32; - } - add_extra_genesis { - build(|_, _, _| {}); - } -} - impl<T: Trait> Module<T> { pub fn is_councillor(who: &T::AccountId) -> bool { <Council<T>>::active_council().iter() @@ -176,24 +216,101 @@ impl<T: Trait> Module<T> { } } -/// Ensure that the origin `o` represents at least `n` council members. Returns -/// `Ok` or an `Err` otherwise. -pub fn ensure_council_members<OuterOrigin>(o: OuterOrigin, n: u32) -> result::Result<u32, &'static str> - where OuterOrigin: Into<Option<Origin>> +impl<T: Trait> OnMembersChanged<T::AccountId> for Module<T> { + fn on_members_changed(_new: &[T::AccountId], old: &[T::AccountId]) { + // remove accounts from all current voting in motions. + let mut old = old.to_vec(); + old.sort_unstable(); + for h in Self::proposals().into_iter() { + <Voting<T>>::mutate(h, |v| + if let Some(mut votes) = v.take() { + votes.ayes = votes.ayes.into_iter() + .filter(|i| old.binary_search(i).is_err()) + .collect(); + votes.nays = votes.nays.into_iter() + .filter(|i| old.binary_search(i).is_err()) + .collect(); + *v = Some(votes); + } + ); + } + } +} + +/// Ensure that the origin `o` represents at least `n` council members. Returns `Ok` or an `Err` +/// otherwise. +pub fn ensure_council_members<OuterOrigin, AccountId>(o: OuterOrigin, n: MemberCount) + -> result::Result<MemberCount, &'static str> + where OuterOrigin: Into<result::Result<RawOrigin<AccountId>, OuterOrigin>> { match o.into() { - Some(Origin::Members(x)) if x >= n => Ok(n), + Ok(RawOrigin::Members(x, _)) if x >= n => Ok(n), _ => Err("bad origin: expected to be a threshold number of council members"), } } -pub struct EnsureMembers<N: U32>(::rstd::marker::PhantomData<N>); -impl<O, N: U32> EnsureOrigin<O> for EnsureMembers<N> - where O: Into<Option<Origin>> -{ - type Success = u32; - fn ensure_origin(o: O) -> result::Result<Self::Success, &'static str> { - ensure_council_members(o, N::VALUE) +pub struct EnsureMember<AccountId>(::rstd::marker::PhantomData<AccountId>); +impl< + O: Into<Result<RawOrigin<AccountId>, O>> + From<RawOrigin<AccountId>>, + AccountId +> EnsureOrigin<O> for EnsureMember<AccountId> { + type Success = AccountId; + fn try_origin(o: O) -> Result<Self::Success, O> { + o.into().and_then(|o| match o { + RawOrigin::Member(id) => Ok(id), + r => Err(O::from(r)), + }) + } +} + +pub struct EnsureMembers<N: U32, AccountId>(::rstd::marker::PhantomData<(N, AccountId)>); +impl< + O: Into<Result<RawOrigin<AccountId>, O>> + From<RawOrigin<AccountId>>, + N: U32, + AccountId, +> EnsureOrigin<O> for EnsureMembers<N, AccountId> { + type Success = (MemberCount, MemberCount); + fn try_origin(o: O) -> Result<Self::Success, O> { + o.into().and_then(|o| match o { + RawOrigin::Members(n, m) if n >= N::VALUE => Ok((n, m)), + r => Err(O::from(r)), + }) + } +} + +pub struct EnsureProportionMoreThan<N: U32, D: U32, AccountId>( + ::rstd::marker::PhantomData<(N, D, AccountId)> +); +impl< + O: Into<Result<RawOrigin<AccountId>, O>> + From<RawOrigin<AccountId>>, + N: U32, + D: U32, + AccountId, +> EnsureOrigin<O> for EnsureProportionMoreThan<N, D, AccountId> { + type Success = (); + fn try_origin(o: O) -> Result<Self::Success, O> { + o.into().and_then(|o| match o { + RawOrigin::Members(n, m) if n * D::VALUE > N::VALUE * m => Ok(()), + r => Err(O::from(r)), + }) + } +} + +pub struct EnsureProportionAtLeast<N: U32, D: U32, AccountId>( + ::rstd::marker::PhantomData<(N, D, AccountId)> +); +impl< + O: Into<Result<RawOrigin<AccountId>, O>> + From<RawOrigin<AccountId>>, + N: U32, + D: U32, + AccountId, +> EnsureOrigin<O> for EnsureProportionAtLeast<N, D, AccountId> { + type Success = (); + fn try_origin(o: O) -> Result<Self::Success, O> { + o.into().and_then(|o| match o { + RawOrigin::Members(n, m) if n * D::VALUE >= N::VALUE * m => Ok(()), + r => Err(O::from(r)), + }) } } @@ -203,12 +320,13 @@ mod tests { use super::RawEvent; use crate::tests::*; use crate::tests::{Call, Origin, Event as OuterEvent}; + use primitives::traits::BlakeTwo256; use srml_support::{Hashable, assert_ok, assert_noop}; use system::{EventRecord, Phase}; use hex_literal::hex; #[test] - fn motions_basic_environment_works() { + fn basic_environment_works() { with_externalities(&mut new_test_ext(true), || { System::set_block_number(1); assert_eq!(Balances::free_balance(&42), 0); @@ -221,7 +339,41 @@ mod tests { } #[test] - fn motions_propose_works() { + fn removal_of_old_voters_votes_works() { + with_externalities(&mut new_test_ext(true), || { + System::set_block_number(1); + let proposal = set_balance_proposal(42); + let hash = BlakeTwo256::hash_of(&proposal); + assert_ok!(CouncilMotions::propose(Origin::signed(1), 3, Box::new(proposal.clone()))); + assert_ok!(CouncilMotions::vote(Origin::signed(2), hash.clone(), 0, true)); + assert_eq!( + CouncilMotions::voting(&hash), + Some(Votes { index: 0, threshold: 3, ayes: vec![1, 2], nays: vec![] }) + ); + CouncilMotions::on_members_changed(&[], &[1]); + assert_eq!( + CouncilMotions::voting(&hash), + Some(Votes { index: 0, threshold: 3, ayes: vec![2], nays: vec![] }) + ); + + let proposal = set_balance_proposal(69); + let hash = BlakeTwo256::hash_of(&proposal); + assert_ok!(CouncilMotions::propose(Origin::signed(2), 2, Box::new(proposal.clone()))); + assert_ok!(CouncilMotions::vote(Origin::signed(3), hash.clone(), 1, false)); + assert_eq!( + CouncilMotions::voting(&hash), + Some(Votes { index: 1, threshold: 2, ayes: vec![2], nays: vec![3] }) + ); + CouncilMotions::on_members_changed(&[], &[3]); + assert_eq!( + CouncilMotions::voting(&hash), + Some(Votes { index: 1, threshold: 2, ayes: vec![2], nays: vec![] }) + ); + }); + } + + #[test] + fn propose_works() { with_externalities(&mut new_test_ext(true), || { System::set_block_number(1); let proposal = set_balance_proposal(42); @@ -229,7 +381,10 @@ mod tests { assert_ok!(CouncilMotions::propose(Origin::signed(1), 3, Box::new(proposal.clone()))); assert_eq!(CouncilMotions::proposals(), vec![hash]); assert_eq!(CouncilMotions::proposal_of(&hash), Some(proposal)); - assert_eq!(CouncilMotions::voting(&hash), Some((0, 3, vec![1], Vec::<u64>::new()))); + assert_eq!( + CouncilMotions::voting(&hash), + Some(Votes { index: 0, threshold: 3, ayes: vec![1], nays: vec![] }) + ); assert_eq!(System::events(), vec![ EventRecord { @@ -242,7 +397,7 @@ mod tests { } #[test] - fn motions_ignoring_non_council_proposals_works() { + fn ignoring_non_council_proposals_works() { with_externalities(&mut new_test_ext(true), || { System::set_block_number(1); let proposal = set_balance_proposal(42); @@ -251,7 +406,7 @@ mod tests { } #[test] - fn motions_ignoring_non_council_votes_works() { + fn ignoring_non_council_votes_works() { with_externalities(&mut new_test_ext(true), || { System::set_block_number(1); let proposal = set_balance_proposal(42); @@ -262,7 +417,7 @@ mod tests { } #[test] - fn motions_ignoring_bad_index_council_vote_works() { + fn ignoring_bad_index_council_vote_works() { with_externalities(&mut new_test_ext(true), || { System::set_block_number(3); let proposal = set_balance_proposal(42); @@ -273,16 +428,22 @@ mod tests { } #[test] - fn motions_revoting_works() { + fn revoting_works() { with_externalities(&mut new_test_ext(true), || { System::set_block_number(1); let proposal = set_balance_proposal(42); let hash: H256 = proposal.blake2_256().into(); assert_ok!(CouncilMotions::propose(Origin::signed(1), 2, Box::new(proposal.clone()))); - assert_eq!(CouncilMotions::voting(&hash), Some((0, 2, vec![1], Vec::<u64>::new()))); + assert_eq!( + CouncilMotions::voting(&hash), + Some(Votes { index: 0, threshold: 2, ayes: vec![1], nays: vec![] }) + ); assert_noop!(CouncilMotions::vote(Origin::signed(1), hash.clone(), 0, true), "duplicate vote ignored"); assert_ok!(CouncilMotions::vote(Origin::signed(1), hash.clone(), 0, false)); - assert_eq!(CouncilMotions::voting(&hash), Some((0, 2, Vec::<u64>::new(), vec![1]))); + assert_eq!( + CouncilMotions::voting(&hash), + Some(Votes { index: 0, threshold: 2, ayes: vec![], nays: vec![1] }) + ); assert_noop!(CouncilMotions::vote(Origin::signed(1), hash.clone(), 0, false), "duplicate vote ignored"); assert_eq!(System::events(), vec![ @@ -301,7 +462,7 @@ mod tests { } #[test] - fn motions_disapproval_works() { + fn disapproval_works() { with_externalities(&mut new_test_ext(true), || { System::set_block_number(1); let proposal = set_balance_proposal(42); @@ -330,7 +491,7 @@ mod tests { } #[test] - fn motions_approval_works() { + fn approval_works() { with_externalities(&mut new_test_ext(true), || { System::set_block_number(1); let proposal = set_balance_proposal(42); diff --git a/substrate/srml/council/src/seats.rs b/substrate/srml/council/src/seats.rs index fb93157981643dd055f2982fba061a316aac6543..584a2fb8006696f754a63b2ca2f0f3df8138cfe5 100644 --- a/substrate/srml/council/src/seats.rs +++ b/substrate/srml/council/src/seats.rs @@ -25,6 +25,7 @@ use srml_support::{ }; use democracy; use system::{self, ensure_signed}; +use super::OnMembersChanged; // no polynomial attacks: // @@ -95,6 +96,9 @@ pub trait Trait: democracy::Trait { /// Handler for the unbalanced reduction when slashing an invalid reaping attempt. type BadReaper: OnUnbalanced<NegativeImbalanceOf<Self>>; + + /// What to do when the members change. + type OnMembersChanged: OnMembersChanged<Self::AccountId>; } decl_module! { @@ -269,15 +273,16 @@ decl_module! { } /// Set the desired member count; if lower than the current count, then seats will not be up - /// election when they expire. If more, then a new vote will be started if one is not already - /// in progress. + /// election when they expire. If more, then a new vote will be started if one is not + /// already in progress. fn set_desired_seats(#[compact] count: u32) { <DesiredSeats<T>>::put(count); } - /// Remove a particular member. A tally will happen instantly (if not already in a presentation + /// Remove a particular member from the council. This is effective immediately. + /// + /// Note: A tally should happen instantly (if not already in a presentation /// period) to fill the seat if removal means that the desired members are not met. - /// This is effective immediately. fn remove_member(who: <T::Lookup as StaticLookup>::Source) { let who = T::Lookup::lookup(who)?; let new_council: Vec<(T::AccountId, T::BlockNumber)> = Self::active_council() @@ -285,6 +290,7 @@ decl_module! { .filter(|i| i.0 != who) .collect(); <ActiveCouncil<T>>::put(new_council); + T::OnMembersChanged::on_members_changed(&[], &[who]); } /// Set the presentation duration. If there is currently a vote being presented for, will @@ -392,6 +398,14 @@ impl<T: Trait> Module<T> { <RegisterInfoOf<T>>::exists(who) } + /// Iff the councillor `who` still has a seat at blocknumber `n` returns `true`. + pub fn will_still_be_councillor_at(who: &T::AccountId, n: T::BlockNumber) -> bool { + Self::active_council().iter() + .find(|&&(ref a, _)| a == who) + .map(|&(_, expires)| expires > n) + .unwrap_or(false) + } + /// Determine the block that a vote can happen on which is no less than `n`. pub fn next_vote_from(n: T::BlockNumber) -> T::BlockNumber { let voting_period = Self::voting_period(); @@ -514,7 +528,7 @@ impl<T: Trait> Module<T> { // return bond to winners. let candidacy_bond = Self::candidacy_bond(); - let incoming: Vec<T::AccountId> = leaderboard.iter() + let incoming: Vec<_> = leaderboard.iter() .rev() .take_while(|&&(b, _)| !b.is_zero()) .take(coming as usize) @@ -523,7 +537,9 @@ impl<T: Trait> Module<T> { .inspect(|a| {T::Currency::unreserve(a, candidacy_bond);}) .collect(); let active_council = Self::active_council(); - let outgoing = active_council.iter().take(expiring.len()).map(|a| a.0.clone()).collect(); + let outgoing: Vec<_> = active_council.iter() + .take(expiring.len()) + .map(|a| a.0.clone()).collect(); // set the new council. let mut new_council: Vec<_> = active_council @@ -534,6 +550,8 @@ impl<T: Trait> Module<T> { new_council.sort_by_key(|&(_, expiry)| expiry); <ActiveCouncil<T>>::put(new_council); + T::OnMembersChanged::on_members_changed(&incoming, &outgoing); + // clear all except runners-up from candidate list. let candidates = Self::candidates(); let mut new_candidates = vec![T::AccountId::default(); candidates.len()]; // shrink later. diff --git a/substrate/srml/council/src/voting.rs b/substrate/srml/council/src/voting.rs deleted file mode 100644 index 0137cd6d2fc075641bee3c54f0c786a931587a73..0000000000000000000000000000000000000000 --- a/substrate/srml/council/src/voting.rs +++ /dev/null @@ -1,494 +0,0 @@ -// Copyright 2017-2019 Parity Technologies (UK) Ltd. -// This file is part of Substrate. - -// Substrate is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Substrate is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Substrate. If not, see <http://www.gnu.org/licenses/>. - -//! Council voting system. - -use rstd::prelude::*; -use rstd::borrow::Borrow; -use primitives::traits::{Hash, Zero}; -use runtime_io::print; -use srml_support::dispatch::Result; -use srml_support::{StorageValue, StorageMap, IsSubType, decl_module, decl_storage, decl_event, ensure}; -use {system, democracy}; -use super::{Trait as CouncilTrait, Module as Council}; -use system::ensure_signed; - -pub trait Trait: CouncilTrait { - type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>; -} - -decl_module! { - pub struct Module<T: Trait> for enum Call where origin: T::Origin { - fn deposit_event<T>() = default; - - fn propose(origin, proposal: Box<T::Proposal>) { - let who = ensure_signed(origin)?; - - let expiry = <system::Module<T>>::block_number() + Self::voting_period(); - ensure!(Self::will_still_be_councillor_at(&who, expiry), "proposer would not be on council"); - - let proposal_hash = T::Hashing::hash_of(&proposal); - - ensure!(!<ProposalOf<T>>::exists(proposal_hash), "duplicate proposals not allowed"); - ensure!(!Self::is_vetoed(&proposal_hash), "proposal is vetoed"); - - let mut proposals = Self::proposals(); - proposals.push((expiry, proposal_hash)); - proposals.sort_by_key(|&(expiry, _)| expiry); - Self::set_proposals(&proposals); - - <ProposalOf<T>>::insert(proposal_hash, *proposal); - <ProposalVoters<T>>::insert(proposal_hash, vec![who.clone()]); - <CouncilVoteOf<T>>::insert((proposal_hash, who.clone()), true); - } - - fn vote(origin, proposal: T::Hash, approve: bool) { - let who = ensure_signed(origin)?; - - ensure!(Self::is_councillor(&who), "only councillors may vote on council proposals"); - - if Self::vote_of((proposal, who.clone())).is_none() { - <ProposalVoters<T>>::mutate(proposal, |voters| voters.push(who.clone())); - } - <CouncilVoteOf<T>>::insert((proposal, who), approve); - } - - fn veto(origin, proposal_hash: T::Hash) { - let who = ensure_signed(origin)?; - - ensure!(Self::is_councillor(&who), "only councillors may veto council proposals"); - ensure!(<ProposalVoters<T>>::exists(&proposal_hash), "proposal must exist to be vetoed"); - - let mut existing_vetoers = Self::veto_of(&proposal_hash) - .map(|pair| pair.1) - .unwrap_or_else(Vec::new); - let insert_position = existing_vetoers.binary_search(&who) - .err().ok_or("a councillor may not veto a proposal twice")?; - existing_vetoers.insert(insert_position, who); - Self::set_veto_of( - &proposal_hash, - <system::Module<T>>::block_number() + Self::cooloff_period(), - existing_vetoers - ); - - Self::set_proposals( - &Self::proposals().into_iter().filter(|&(_, h)| h != proposal_hash - ).collect::<Vec<_>>()); - <ProposalVoters<T>>::remove(proposal_hash); - <ProposalOf<T>>::remove(proposal_hash); - for (c, _) in <Council<T>>::active_council() { - <CouncilVoteOf<T>>::remove((proposal_hash, c)); - } - } - - fn set_cooloff_period(#[compact] blocks: T::BlockNumber) { - <CooloffPeriod<T>>::put(blocks); - } - - fn set_voting_period(#[compact] blocks: T::BlockNumber) { - <VotingPeriod<T>>::put(blocks); - } - - fn on_finalize(n: T::BlockNumber) { - if let Err(e) = Self::end_block(n) { - print("Guru meditation"); - print(e); - } - } - } -} - -decl_storage! { - trait Store for Module<T: Trait> as CouncilVoting { - pub CooloffPeriod get(cooloff_period) config(): T::BlockNumber = 1000.into(); - pub VotingPeriod get(voting_period) config(): T::BlockNumber = 3.into(); - /// Number of blocks by which to delay enactment of successful, non-unanimous-council-instigated referendum proposals. - pub EnactDelayPeriod get(enact_delay_period) config(): T::BlockNumber = 0.into(); - pub Proposals get(proposals) build(|_| vec![]): Vec<(T::BlockNumber, T::Hash)>; // ordered by expiry. - pub ProposalOf get(proposal_of): map T::Hash => Option<T::Proposal>; - pub ProposalVoters get(proposal_voters): map T::Hash => Vec<T::AccountId>; - pub CouncilVoteOf get(vote_of): map (T::Hash, T::AccountId) => Option<bool>; - pub VetoedProposal get(veto_of): map T::Hash => Option<(T::BlockNumber, Vec<T::AccountId>)>; - } -} - -decl_event!( - pub enum Event<T> where <T as system::Trait>::Hash { - /// A voting tally has happened for a referendum cancellation vote. - /// Last three are yes, no, abstain counts. - TallyCancelation(Hash, u32, u32, u32), - /// A voting tally has happened for a referendum vote. - /// Last three are yes, no, abstain counts. - TallyReferendum(Hash, u32, u32, u32), - } -); - -impl<T: Trait> Module<T> { - pub fn is_vetoed<B: Borrow<T::Hash>>(proposal: B) -> bool { - Self::veto_of(proposal.borrow()) - .map(|(expiry, _): (T::BlockNumber, Vec<T::AccountId>)| <system::Module<T>>::block_number() < expiry) - .unwrap_or(false) - } - - pub fn will_still_be_councillor_at(who: &T::AccountId, n: T::BlockNumber) -> bool { - <Council<T>>::active_council().iter() - .find(|&&(ref a, _)| a == who) - .map(|&(_, expires)| expires > n) - .unwrap_or(false) - } - - pub fn is_councillor(who: &T::AccountId) -> bool { - <Council<T>>::active_council().iter() - .any(|&(ref a, _)| a == who) - } - - pub fn tally(proposal_hash: &T::Hash) -> (u32, u32, u32) { - Self::generic_tally(proposal_hash, |w: &T::AccountId, p: &T::Hash| Self::vote_of((*p, w.clone()))) - } - - // Private - fn set_veto_of(proposal: &T::Hash, expiry: T::BlockNumber, vetoers: Vec<T::AccountId>) { - <VetoedProposal<T>>::insert(proposal, (expiry, vetoers)); - } - - fn kill_veto_of(proposal: &T::Hash) { - <VetoedProposal<T>>::remove(proposal); - } - - fn take_tally(proposal_hash: &T::Hash) -> (u32, u32, u32) { - Self::generic_tally(proposal_hash, |w: &T::AccountId, p: &T::Hash| <CouncilVoteOf<T>>::take((*p, w.clone()))) - } - - fn generic_tally<F: Fn(&T::AccountId, &T::Hash) -> Option<bool>>(proposal_hash: &T::Hash, vote_of: F) -> (u32, u32, u32) { - let c = <Council<T>>::active_council(); - let (approve, reject) = c.iter() - .filter_map(|&(ref a, _)| vote_of(a, proposal_hash)) - .map(|approve| if approve { (1, 0) } else { (0, 1) }) - .fold((0, 0), |(a, b), (c, d)| (a + c, b + d)); - (approve, reject, c.len() as u32 - approve - reject) - } - - fn set_proposals(p: &Vec<(T::BlockNumber, T::Hash)>) { - <Proposals<T>>::put(p); - } - - fn take_proposal_if_expiring_at(n: T::BlockNumber) -> Option<(T::Proposal, T::Hash)> { - let proposals = Self::proposals(); - match proposals.first() { - Some(&(expiry, hash)) if expiry == n => { - // yes this is horrible, but fixing it will need substantial work in storage. - Self::set_proposals(&proposals[1..].to_vec()); - <ProposalOf<T>>::take(hash).map(|p| (p, hash)) /* defensive only: all queued proposal hashes must have associated proposals*/ - } - _ => None, - } - } - - fn end_block(now: T::BlockNumber) -> Result { - while let Some((proposal, proposal_hash)) = Self::take_proposal_if_expiring_at(now) { - let tally = Self::take_tally(&proposal_hash); - if let Some(&democracy::Call::cancel_referendum(ref_index)) = IsSubType::<democracy::Module<T>>::is_aux_sub_type(&proposal) { - Self::deposit_event(RawEvent::TallyCancelation(proposal_hash, tally.0, tally.1, tally.2)); - if let (_, 0, 0) = tally { - <democracy::Module<T>>::internal_cancel_referendum(ref_index.into()); - } - } else { - Self::deposit_event(RawEvent::TallyReferendum(proposal_hash.clone(), tally.0, tally.1, tally.2)); - if tally.0 > tally.1 + tally.2 { - Self::kill_veto_of(&proposal_hash); - // If there were no nay-votes from the council, then it's weakly uncontroversial; we enact immediately. - let period = match tally.1 { - 0 => Zero::zero(), - _ => Self::enact_delay_period(), - }; - // If all council members voted yes, then it's strongly uncontroversial; we require a negative - // super-majority at referendum in order to defeat it. - let threshold = match tally { - (_, 0, 0) => democracy::VoteThreshold::SuperMajorityAgainst, - _ => democracy::VoteThreshold::SimpleMajority, - }; - <democracy::Module<T>>::internal_start_referendum(proposal, threshold, period).map(|_| ())?; - } - } - } - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::tests::*; - use crate::tests::{Call, Origin}; - use srml_support::{Hashable, assert_ok, assert_noop}; - use democracy::{ReferendumInfo, VoteThreshold}; - - #[test] - fn basic_environment_works() { - with_externalities(&mut new_test_ext(true), || { - System::set_block_number(1); - assert_eq!(Balances::free_balance(&42), 0); - assert_eq!(CouncilVoting::cooloff_period(), 2); - assert_eq!(CouncilVoting::voting_period(), 1); - assert_eq!(CouncilVoting::will_still_be_councillor_at(&1, 1), true); - assert_eq!(CouncilVoting::will_still_be_councillor_at(&1, 10), false); - assert_eq!(CouncilVoting::will_still_be_councillor_at(&4, 10), false); - assert_eq!(CouncilVoting::is_councillor(&1), true); - assert_eq!(CouncilVoting::is_councillor(&4), false); - assert_eq!(CouncilVoting::proposals(), Vec::<(u64, H256)>::new()); - assert_eq!(CouncilVoting::proposal_voters(H256::default()), Vec::<u64>::new()); - assert_eq!(CouncilVoting::is_vetoed(&H256::default()), false); - assert_eq!(CouncilVoting::vote_of((H256::default(), 1)), None); - assert_eq!(CouncilVoting::tally(&H256::default()), (0, 0, 3)); - }); - } - - fn set_balance_proposal(value: u64) -> Call { - Call::Balances(balances::Call::set_balance(42, value.into(), 0)) - } - - fn cancel_referendum_proposal(id: u32) -> Call { - Call::Democracy(democracy::Call::cancel_referendum(id.into())) - } - - #[test] - fn referendum_cancellation_should_work_when_unanimous() { - with_externalities(&mut new_test_ext(true), || { - System::set_block_number(1); - let proposal = set_balance_proposal(42); - assert_ok!(Democracy::internal_start_referendum(proposal.clone(), VoteThreshold::SuperMajorityApprove, 0), 0); - assert_eq!(Democracy::active_referenda(), vec![(0, ReferendumInfo::new(4, proposal, VoteThreshold::SuperMajorityApprove, 0))]); - - let cancellation = cancel_referendum_proposal(0); - let hash = cancellation.blake2_256().into(); - assert_ok!(CouncilVoting::propose(Origin::signed(1), Box::new(cancellation))); - assert_ok!(CouncilVoting::vote(Origin::signed(2), hash, true)); - assert_ok!(CouncilVoting::vote(Origin::signed(3), hash, true)); - assert_eq!(CouncilVoting::proposals(), vec![(2, hash)]); - assert_ok!(CouncilVoting::end_block(System::block_number())); - - System::set_block_number(2); - assert_ok!(CouncilVoting::end_block(System::block_number())); - assert_eq!(Democracy::active_referenda(), vec![]); - assert_eq!(Balances::free_balance(&42), 0); - }); - } - - #[test] - fn referendum_cancellation_should_fail_when_not_unanimous() { - with_externalities(&mut new_test_ext(true), || { - System::set_block_number(1); - let proposal = set_balance_proposal(42); - assert_ok!(Democracy::internal_start_referendum(proposal.clone(), VoteThreshold::SuperMajorityApprove, 0), 0); - - let cancellation = cancel_referendum_proposal(0); - let hash = cancellation.blake2_256().into(); - assert_ok!(CouncilVoting::propose(Origin::signed(1), Box::new(cancellation))); - assert_ok!(CouncilVoting::vote(Origin::signed(2), hash, true)); - assert_ok!(CouncilVoting::vote(Origin::signed(3), hash, false)); - assert_ok!(CouncilVoting::end_block(System::block_number())); - - System::set_block_number(2); - assert_ok!(CouncilVoting::end_block(System::block_number())); - assert_eq!(Democracy::active_referenda(), vec![(0, ReferendumInfo::new(4, proposal, VoteThreshold::SuperMajorityApprove, 0))]); - }); - } - - #[test] - fn referendum_cancellation_should_fail_when_abstentions() { - with_externalities(&mut new_test_ext(true), || { - System::set_block_number(1); - let proposal = set_balance_proposal(42); - assert_ok!(Democracy::internal_start_referendum(proposal.clone(), VoteThreshold::SuperMajorityApprove, 0), 0); - - let cancellation = cancel_referendum_proposal(0); - let hash = cancellation.blake2_256().into(); - assert_ok!(CouncilVoting::propose(Origin::signed(1), Box::new(cancellation))); - assert_ok!(CouncilVoting::vote(Origin::signed(2), hash, true)); - assert_ok!(CouncilVoting::end_block(System::block_number())); - - System::set_block_number(2); - assert_ok!(CouncilVoting::end_block(System::block_number())); - assert_eq!(Democracy::active_referenda(), vec![(0, ReferendumInfo::new(4, proposal, VoteThreshold::SuperMajorityApprove, 0))]); - }); - } - - #[test] - fn veto_should_work() { - with_externalities(&mut new_test_ext(true), || { - System::set_block_number(1); - let proposal = set_balance_proposal(42); - let hash = proposal.blake2_256().into(); - assert_ok!(CouncilVoting::propose(Origin::signed(1), Box::new(proposal.clone()))); - assert_ok!(CouncilVoting::veto(Origin::signed(2), hash)); - assert_eq!(CouncilVoting::proposals().len(), 0); - assert_eq!(Democracy::active_referenda().len(), 0); - }); - } - - #[test] - fn double_veto_should_not_work() { - with_externalities(&mut new_test_ext(true), || { - System::set_block_number(1); - let proposal = set_balance_proposal(42); - let hash = proposal.blake2_256().into(); - assert_ok!(CouncilVoting::propose(Origin::signed(1), Box::new(proposal.clone()))); - assert_ok!(CouncilVoting::veto(Origin::signed(2), hash)); - - System::set_block_number(3); - assert_ok!(CouncilVoting::propose(Origin::signed(1), Box::new(proposal.clone()))); - assert_noop!(CouncilVoting::veto(Origin::signed(2), hash), "a councillor may not veto a proposal twice"); - }); - } - - #[test] - fn retry_in_cooloff_should_not_work() { - with_externalities(&mut new_test_ext(true), || { - System::set_block_number(1); - let proposal = set_balance_proposal(42); - let hash = proposal.blake2_256().into(); - assert_ok!(CouncilVoting::propose(Origin::signed(1), Box::new(proposal.clone()))); - assert_ok!(CouncilVoting::veto(Origin::signed(2), hash)); - - System::set_block_number(2); - assert_noop!(CouncilVoting::propose(Origin::signed(1), Box::new(proposal.clone())), "proposal is vetoed"); - }); - } - - #[test] - fn retry_after_cooloff_should_work() { - with_externalities(&mut new_test_ext(true), || { - System::set_block_number(1); - let proposal = set_balance_proposal(42); - let hash = proposal.blake2_256().into(); - assert_ok!(CouncilVoting::propose(Origin::signed(1), Box::new(proposal.clone()))); - assert_ok!(CouncilVoting::veto(Origin::signed(2), hash)); - - System::set_block_number(3); - assert_ok!(CouncilVoting::propose(Origin::signed(1), Box::new(proposal.clone()))); - assert_ok!(CouncilVoting::vote(Origin::signed(2), hash, false)); - assert_ok!(CouncilVoting::vote(Origin::signed(3), hash, true)); - assert_ok!(CouncilVoting::end_block(System::block_number())); - - System::set_block_number(4); - assert_ok!(CouncilVoting::end_block(System::block_number())); - assert_eq!(CouncilVoting::proposals().len(), 0); - assert_eq!(Democracy::active_referenda(), vec![(0, ReferendumInfo::new(7, set_balance_proposal(42), VoteThreshold::SimpleMajority, 0))]); - }); - } - - #[test] - fn alternative_double_veto_should_work() { - with_externalities(&mut new_test_ext(true), || { - System::set_block_number(1); - let proposal = set_balance_proposal(42); - let hash = proposal.blake2_256().into(); - assert_ok!(CouncilVoting::propose(Origin::signed(1), Box::new(proposal.clone()))); - assert_ok!(CouncilVoting::veto(Origin::signed(2), hash)); - - System::set_block_number(3); - assert_ok!(CouncilVoting::propose(Origin::signed(1), Box::new(proposal.clone()))); - assert_ok!(CouncilVoting::veto(Origin::signed(3), hash)); - assert_eq!(CouncilVoting::proposals().len(), 0); - assert_eq!(Democracy::active_referenda().len(), 0); - }); - } - - #[test] - fn simple_propose_should_work() { - with_externalities(&mut new_test_ext(true), || { - System::set_block_number(1); - let proposal = set_balance_proposal(42); - let hash = proposal.blake2_256().into(); - assert_ok!(CouncilVoting::propose(Origin::signed(1), Box::new(proposal.clone()))); - assert_eq!(CouncilVoting::proposals().len(), 1); - assert_eq!(CouncilVoting::proposal_voters(&hash), vec![1]); - assert_eq!(CouncilVoting::vote_of((hash, 1)), Some(true)); - assert_eq!(CouncilVoting::tally(&hash), (1, 0, 2)); - }); - } - - #[test] - fn unvoted_proposal_should_expire_without_action() { - with_externalities(&mut new_test_ext(true), || { - System::set_block_number(1); - let proposal = set_balance_proposal(42); - assert_ok!(CouncilVoting::propose(Origin::signed(1), Box::new(proposal.clone()))); - assert_eq!(CouncilVoting::tally(&proposal.blake2_256().into()), (1, 0, 2)); - assert_ok!(CouncilVoting::end_block(System::block_number())); - - System::set_block_number(2); - assert_ok!(CouncilVoting::end_block(System::block_number())); - assert_eq!(CouncilVoting::proposals().len(), 0); - assert_eq!(Democracy::active_referenda().len(), 0); - }); - } - - #[test] - fn unanimous_proposal_should_expire_with_biased_referendum() { - with_externalities(&mut new_test_ext(true), || { - System::set_block_number(1); - let proposal = set_balance_proposal(42); - assert_ok!(CouncilVoting::propose(Origin::signed(1), Box::new(proposal.clone()))); - assert_ok!(CouncilVoting::vote(Origin::signed(2), proposal.blake2_256().into(), true)); - assert_ok!(CouncilVoting::vote(Origin::signed(3), proposal.blake2_256().into(), true)); - assert_eq!(CouncilVoting::tally(&proposal.blake2_256().into()), (3, 0, 0)); - assert_ok!(CouncilVoting::end_block(System::block_number())); - - System::set_block_number(2); - assert_ok!(CouncilVoting::end_block(System::block_number())); - assert_eq!(CouncilVoting::proposals().len(), 0); - assert_eq!(Democracy::active_referenda(), vec![(0, ReferendumInfo::new(5, proposal, VoteThreshold::SuperMajorityAgainst, 0))]); - }); - } - - #[test] - fn majority_proposal_should_expire_with_unbiased_referendum() { - with_externalities(&mut new_test_ext(true), || { - System::set_block_number(1); - let proposal = set_balance_proposal(42); - assert_ok!(CouncilVoting::propose(Origin::signed(1), Box::new(proposal.clone()))); - assert_ok!(CouncilVoting::vote(Origin::signed(2), proposal.blake2_256().into(), true)); - assert_ok!(CouncilVoting::vote(Origin::signed(3), proposal.blake2_256().into(), false)); - assert_eq!(CouncilVoting::tally(&proposal.blake2_256().into()), (2, 1, 0)); - assert_ok!(CouncilVoting::end_block(System::block_number())); - - System::set_block_number(2); - assert_ok!(CouncilVoting::end_block(System::block_number())); - assert_eq!(CouncilVoting::proposals().len(), 0); - assert_eq!(Democracy::active_referenda(), vec![(0, ReferendumInfo::new(5, proposal, VoteThreshold::SimpleMajority, 0))]); - }); - } - - #[test] - fn propose_by_public_should_not_work() { - with_externalities(&mut new_test_ext(true), || { - System::set_block_number(1); - let proposal = set_balance_proposal(42); - assert_noop!(CouncilVoting::propose(Origin::signed(4), Box::new(proposal)), "proposer would not be on council"); - }); - } - - #[test] - fn vote_by_public_should_not_work() { - with_externalities(&mut new_test_ext(true), || { - System::set_block_number(1); - let proposal = set_balance_proposal(42); - assert_ok!(CouncilVoting::propose(Origin::signed(1), Box::new(proposal.clone()))); - assert_noop!(CouncilVoting::vote(Origin::signed(4), proposal.blake2_256().into(), true), "only councillors may vote on council proposals"); - }); - } -} diff --git a/substrate/srml/democracy/src/lib.rs b/substrate/srml/democracy/src/lib.rs index 1846f3462d3e46331d7bc642a917893fbb0cb9dd..96d51849c9e9288822356b20f0f4b6e406a448d9 100644 --- a/substrate/srml/democracy/src/lib.rs +++ b/substrate/srml/democracy/src/lib.rs @@ -20,7 +20,7 @@ use rstd::prelude::*; use rstd::{result, convert::TryFrom}; -use primitives::traits::{Zero, Bounded, CheckedMul, CheckedDiv}; +use primitives::traits::{Zero, Bounded, CheckedMul, CheckedDiv, EnsureOrigin, Hash}; use parity_codec::{Encode, Decode, Input, Output}; use srml_support::{ decl_module, decl_storage, decl_event, ensure, @@ -31,7 +31,7 @@ use srml_support::{ } }; use srml_support::dispatch::Result; -use system::ensure_signed; +use system::{ensure_signed, ensure_root}; mod vote_threshold; pub use vote_threshold::{Approved, VoteThreshold}; @@ -188,6 +188,29 @@ pub trait Trait: system::Trait + Sized { /// The minimum amount to be used as a deposit for a public referendum proposal. type MinimumDeposit: Get<BalanceOf<Self>>; + + /// Origin from which the next tabled referendum may be forced. This is a normal + /// "super-majority-required" referendum. + type ExternalOrigin: EnsureOrigin<Self::Origin>; + + /// Origin from which the next tabled referendum may be forced; this allows for the tabling of + /// a majority-carries referendum. + type ExternalMajorityOrigin: EnsureOrigin<Self::Origin>; + + /// Origin from which emergency referenda may be scheduled. + type EmergencyOrigin: EnsureOrigin<Self::Origin>; + + /// Minimum voting period allowed for an emergency referendum. + type EmergencyVotingPeriod: Get<Self::BlockNumber>; + + /// Origin from which any referenda may be cancelled in an emergency. + type CancellationOrigin: EnsureOrigin<Self::Origin>; + + /// Origin for anyone able to veto proposals. + type VetoOrigin: EnsureOrigin<Self::Origin, Success=Self::AccountId>; + + /// Period in blocks where an external proposal may not be re-submitted after being vetoed. + type CooloffPeriod: Get<Self::BlockNumber>; } /// Info regarding an ongoing referendum. @@ -252,13 +275,36 @@ decl_storage! { /// Get the account (and lock periods) to which another account is delegating vote. pub Delegations get(delegations): linked_map T::AccountId => (T::AccountId, Conviction); + + /// True if the last referendum tabled was submitted externally. False if it was a public + /// proposal. + pub LastTabledWasExternal: bool; + + /// The referendum to be tabled whenever it would be valid to table an external proposal. + /// This happens when a referendum needs to be tabled and one of two conditions are met: + /// - `LastTabledWasExternal` is `false`; or + /// - `PublicProps` is empty. + pub NextExternal: Option<(T::Proposal, VoteThreshold)>; + + /// A record of who vetoed what. Maps proposal hash to a possible existent block number + /// (until when it may not be resubmitted) and who vetoed it. + pub Blacklist get(blacklist): map T::Hash => Option<(T::BlockNumber, Vec<T::AccountId>)>; + + /// Record of all proposals that have been subject to emergency cancellation. + pub Cancellations: map T::Hash => bool; } } decl_event!( - pub enum Event<T> where Balance = BalanceOf<T>, <T as system::Trait>::AccountId { + pub enum Event<T> where + Balance = BalanceOf<T>, + <T as system::Trait>::AccountId, + <T as system::Trait>::Hash, + <T as system::Trait>::BlockNumber, + { Proposed(PropIndex, Balance), Tabled(PropIndex, Balance, Vec<AccountId>), + ExternalTabled, Started(ReferendumIndex, VoteThreshold), Passed(ReferendumIndex), NotPassed(ReferendumIndex), @@ -266,6 +312,7 @@ decl_event!( Executed(ReferendumIndex, bool), Delegated(AccountId, AccountId), Undelegated(AccountId), + Vetoed(AccountId, Hash, BlockNumber), } ); @@ -274,8 +321,7 @@ decl_module! { fn deposit_event<T>() = default; /// Propose a sensitive action to be taken. - fn propose( - origin, + fn propose(origin, proposal: Box<T::Proposal>, #[compact] value: BalanceOf<T> ) { @@ -307,7 +353,7 @@ decl_module! { <DepositOf<T>>::insert(proposal, deposit); } - /// Vote in a referendum. If vote is aye, the vote is to enact the proposal; + /// Vote in a referendum. If `vote.is_aye()`, the vote is to enact the proposal; /// otherwise it is a vote to keep the status quo. fn vote(origin, #[compact] ref_index: ReferendumIndex, @@ -317,8 +363,8 @@ decl_module! { Self::do_vote(who, ref_index, vote) } - /// Vote in a referendum on behalf of a stash. If vote is aye, the vote is to enact - /// the proposal; otherwise it is a vote to keep the status quo. + /// Vote in a referendum on behalf of a stash. If `vote.is_aye()`, the vote is to enact + /// the proposal; otherwise it is a vote to keep the status quo. fn proxy_vote(origin, #[compact] ref_index: ReferendumIndex, vote: Vote @@ -327,18 +373,95 @@ decl_module! { Self::do_vote(who, ref_index, vote) } - /// Start a referendum. - fn start_referendum( + /// Schedule an emergency referendum. + /// + /// This will create a new referendum for the `proposal`, approved as long as counted votes + /// exceed `threshold` and, if approved, enacted after the given `delay`. + /// + /// It may be called from either the Root or the Emergency origin. + fn emergency_propose(origin, proposal: Box<T::Proposal>, threshold: VoteThreshold, + voting_period: T::BlockNumber, delay: T::BlockNumber - ) -> Result { + ) { + T::EmergencyOrigin::try_origin(origin) + .map(|_| ()) + .or_else(|origin| ensure_root(origin))?; + let now = <system::Module<T>>::block_number(); + // We don't consider it an error if `vote_period` is too low, but we do enforce the + // minimum. This is primarily due to practicality. If it's an emergency, we don't want + // to introduce more delays than is strictly needed by requiring a potentially costly + // resubmission in the case of a mistakenly low `vote_period`; better to just let the + // referendum take place with the lowest valid value. + let period = voting_period.max(T::EmergencyVotingPeriod::get()); Self::inject_referendum( - <system::Module<T>>::block_number() + T::VotingPeriod::get(), + now + period, *proposal, threshold, delay, - ).map(|_| ()) + ).map(|_| ())?; + } + + /// Schedule an emergency cancellation of a referendum. Cannot happen twice to the same + /// referendum. + fn emergency_cancel(origin, ref_index: ReferendumIndex) { + T::CancellationOrigin::ensure_origin(origin)?; + + let info = Self::referendum_info(ref_index).ok_or("unknown index")?; + let h = T::Hashing::hash_of(&info.proposal); + ensure!(!<Cancellations<T>>::exists(h), "cannot cancel the same proposal twice"); + + <Cancellations<T>>::insert(h, true); + Self::clear_referendum(ref_index); + } + + /// Schedule a referendum to be tabled once it is legal to schedule an external + /// referendum. + fn external_propose(origin, proposal: Box<T::Proposal>) { + T::ExternalOrigin::ensure_origin(origin)?; + ensure!(!<NextExternal<T>>::exists(), "proposal already made"); + let proposal_hash = T::Hashing::hash_of(&proposal); + if let Some((until, _)) = <Blacklist<T>>::get(proposal_hash) { + ensure!(<system::Module<T>>::block_number() >= until, "proposal still blacklisted"); + } + <NextExternal<T>>::put((*proposal, VoteThreshold::SuperMajorityApprove)); + } + + /// Schedule a majority-carries referendum to be tabled next once it is legal to schedule + /// an external referendum. + fn external_propose_majority(origin, proposal: Box<T::Proposal>) { + T::ExternalMajorityOrigin::ensure_origin(origin)?; + ensure!(!<NextExternal<T>>::exists(), "proposal already made"); + let proposal_hash = T::Hashing::hash_of(&proposal); + if let Some((until, _)) = <Blacklist<T>>::get(proposal_hash) { + ensure!(<system::Module<T>>::block_number() >= until, "proposal still blacklisted"); + } + <NextExternal<T>>::put((*proposal, VoteThreshold::SimpleMajority)); + } + + /// Veto and blacklist the external proposal hash. + fn veto_external(origin, proposal_hash: T::Hash) { + let who = T::VetoOrigin::ensure_origin(origin)?; + + if let Some((proposal, _)) = <NextExternal<T>>::get() { + ensure!(proposal_hash == T::Hashing::hash_of(&proposal), "unknown proposal"); + } else { + Err("no external proposal")?; + } + + let mut existing_vetoers = <Blacklist<T>>::get(&proposal_hash) + .map(|pair| pair.1) + .unwrap_or_else(Vec::new); + let insert_position = existing_vetoers.binary_search(&who) + .err().ok_or("identity may not veto a proposal twice")?; + + existing_vetoers.insert(insert_position, who.clone()); + let until = <system::Module<T>>::block_number() + T::CooloffPeriod::get(); + <Blacklist<T>>::insert(&proposal_hash, (until, existing_vetoers)); + + Self::deposit_event(RawEvent::Vetoed(who, proposal_hash, until)); + <NextExternal<T>>::kill(); } /// Remove a referendum. @@ -347,12 +470,19 @@ decl_module! { } /// Cancel a proposal queued for enactment. - pub fn cancel_queued(#[compact] when: T::BlockNumber, #[compact] which: u32) { + fn cancel_queued( + #[compact] when: T::BlockNumber, + #[compact] which: u32, + #[compact] what: ReferendumIndex + ) { let which = which as usize; - <DispatchQueue<T>>::mutate( - when, - |items| if items.len() > which { items[which] = None } - ); + let mut items = <DispatchQueue<T>>::get(when); + if items.get(which).and_then(Option::as_ref).map_or(false, |x| x.1 == what) { + items[which] = None; + <DispatchQueue<T>>::insert(when, items); + } else { + Err("proposal not found")? + } } fn on_finalize(n: T::BlockNumber) { @@ -603,7 +733,34 @@ impl<T: Trait> Module<T> { Self::deposit_event(RawEvent::Executed(index, ok)); } + /// Table the next waiting proposal for a vote. fn launch_next(now: T::BlockNumber) -> Result { + if <LastTabledWasExternal<T>>::take() { + Self::launch_public(now).or_else(|_| Self::launch_external(now)) + } else { + Self::launch_external(now).or_else(|_| Self::launch_public(now)) + }.map_err(|_| "No proposals waiting") + } + + /// Table the waiting external proposal for a vote, if there is one. + fn launch_external(now: T::BlockNumber) -> Result { + if let Some((proposal, threshold)) = <NextExternal<T>>::take() { + <LastTabledWasExternal<T>>::put(true); + Self::deposit_event(RawEvent::ExternalTabled); + Self::inject_referendum( + now + T::VotingPeriod::get(), + proposal, + threshold, + T::EnactmentPeriod::get(), + )?; + Ok(()) + } else { + Err("No external proposal waiting") + } + } + + /// Table the waiting public proposal with the highest backing for a vote. + fn launch_public(now: T::BlockNumber) -> Result { let mut public_props = Self::public_props(); if let Some((winner_index, _)) = public_props.iter() .enumerate() @@ -626,9 +783,11 @@ impl<T: Trait> Module<T> { T::EnactmentPeriod::get(), )?; } + Ok(()) + } else { + Err("No public proposals waiting") } - Ok(()) } fn bake_referendum( @@ -682,11 +841,13 @@ impl<T: Trait> Module<T> { } /// Current era is ending; we should finish up any proposals. - // TODO: move to `initialize_block + // TODO: move to initialize_block #2779 fn end_block(now: T::BlockNumber) -> Result { // pick out another public referendum if it's time. if (now % T::LaunchPeriod::get()).is_zero() { - Self::launch_next(now.clone())?; + // Errors come from the queue being empty. we don't really care about that, and even if + // we did, there is nothing we can do here. + let _ = Self::launch_next(now.clone()); } // tally up votes for any expiring referenda. @@ -712,13 +873,15 @@ mod tests { use super::*; use runtime_io::with_externalities; use srml_support::{ - impl_outer_origin, impl_outer_dispatch, assert_noop, assert_ok, parameter_types + impl_outer_origin, impl_outer_dispatch, assert_noop, assert_ok, parameter_types, + traits::Contains }; use substrate_primitives::{H256, Blake2Hasher}; use primitives::BuildStorage; use primitives::traits::{BlakeTwo256, IdentityLookup, Bounded}; use primitives::testing::{Digest, DigestItem, Header}; use balances::BalanceLock; + use system::EnsureSignedBy; const AYE: Vote = Vote{ aye: true, conviction: Conviction::None }; const NAY: Vote = Vote{ aye: false, conviction: Conviction::None }; @@ -762,19 +925,39 @@ mod tests { type DustRemoval = (); } parameter_types! { - pub const LaunchPeriod: u64 = 1; - pub const VotingPeriod: u64 = 1; + pub const LaunchPeriod: u64 = 2; + pub const VotingPeriod: u64 = 2; + pub const EmergencyVotingPeriod: u64 = 1; pub const MinimumDeposit: u64 = 1; - pub const EnactmentPeriod: u64 = 1; + pub const EnactmentPeriod: u64 = 2; + pub const CooloffPeriod: u64 = 2; + pub const One: u64 = 1; + pub const Two: u64 = 2; + pub const Three: u64 = 3; + pub const Four: u64 = 4; + pub const Five: u64 = 5; + } + pub struct OneToFive; + impl Contains<u64> for OneToFive { + fn contains(n: &u64) -> bool { + *n >= 1 && *n <= 5 + } } - impl Trait for Test { + impl super::Trait for Test { type Proposal = Call; type Event = (); type Currency = balances::Module<Self>; type EnactmentPeriod = EnactmentPeriod; type LaunchPeriod = LaunchPeriod; type VotingPeriod = VotingPeriod; + type EmergencyVotingPeriod = EmergencyVotingPeriod; type MinimumDeposit = MinimumDeposit; + type EmergencyOrigin = EnsureSignedBy<One, u64>; + type ExternalOrigin = EnsureSignedBy<Two, u64>; + type ExternalMajorityOrigin = EnsureSignedBy<Three, u64>; + type CancellationOrigin = EnsureSignedBy<Four, u64>; + type VetoOrigin = EnsureSignedBy<OneToFive, u64>; + type CooloffPeriod = CooloffPeriod; } fn new_test_ext() -> runtime_io::TestExternalities<Blake2Hasher> { @@ -806,13 +989,14 @@ mod tests { } fn set_balance_proposal(value: u64) -> Call { - Call::Balances(balances::Call::set_balance(42, value.into(), 0)) + Call::Balances(balances::Call::set_balance(42, value, 0)) } - fn propose_set_balance(who: u64, value: u64, locked: u64) -> super::Result { + fn propose_set_balance(who: u64, value: u64, delay: u64) -> super::Result { Democracy::propose( Origin::signed(who), - Box::new(set_balance_proposal(value)), locked.into() + Box::new(set_balance_proposal(value)), + delay ) } @@ -821,6 +1005,326 @@ mod tests { System::set_block_number(System::block_number() + 1); } + fn fast_forward_to(n: u64) { + while System::block_number() < n { + next_block(); + } + } + + #[test] + fn external_and_public_interleaving_works() { + with_externalities(&mut new_test_ext(), || { + System::set_block_number(0); + assert_ok!(Democracy::external_propose( + Origin::signed(2), + Box::new(set_balance_proposal(1)), + )); + assert_ok!(propose_set_balance(6, 2, 2)); + + fast_forward_to(1); + + // both waiting: external goes first. + assert_eq!( + Democracy::referendum_info(0), + Some(ReferendumInfo { + end: 2, + proposal: set_balance_proposal(1), + threshold: VoteThreshold::SuperMajorityApprove, + delay: 2 + }) + ); + // replenish external + assert_ok!(Democracy::external_propose( + Origin::signed(2), + Box::new(set_balance_proposal(3)), + )); + + fast_forward_to(3); + + // both waiting: public goes next. + assert_eq!( + Democracy::referendum_info(1), + Some(ReferendumInfo { + end: 4, + proposal: set_balance_proposal(2), + threshold: VoteThreshold::SuperMajorityApprove, + delay: 2 + }) + ); + // don't replenish public + + fast_forward_to(5); + + // it's external "turn" again, though since public is empty that doesn't really matter + assert_eq!( + Democracy::referendum_info(2), + Some(ReferendumInfo { + end: 6, + proposal: set_balance_proposal(3), + threshold: VoteThreshold::SuperMajorityApprove, + delay: 2 + }) + ); + // replenish external + assert_ok!(Democracy::external_propose( + Origin::signed(2), + Box::new(set_balance_proposal(5)), + )); + + fast_forward_to(7); + + // external goes again because there's no public waiting. + assert_eq!( + Democracy::referendum_info(3), + Some(ReferendumInfo { + end: 8, + proposal: set_balance_proposal(5), + threshold: VoteThreshold::SuperMajorityApprove, + delay: 2 + }) + ); + // replenish both + assert_ok!(Democracy::external_propose( + Origin::signed(2), + Box::new(set_balance_proposal(7)), + )); + assert_ok!(propose_set_balance(6, 4, 2)); + + fast_forward_to(9); + + // public goes now since external went last time. + assert_eq!( + Democracy::referendum_info(4), + Some(ReferendumInfo { + end: 10, + proposal: set_balance_proposal(4), + threshold: VoteThreshold::SuperMajorityApprove, + delay: 2 + }) + ); + // replenish public again + assert_ok!(propose_set_balance(6, 6, 2)); + // cancel external + let h = BlakeTwo256::hash_of(&set_balance_proposal(7)); + assert_ok!(Democracy::veto_external(Origin::signed(3), h)); + + fast_forward_to(11); + + // public goes again now since there's no external waiting. + assert_eq!( + Democracy::referendum_info(5), + Some(ReferendumInfo { + end: 12, + proposal: set_balance_proposal(6), + threshold: VoteThreshold::SuperMajorityApprove, + delay: 2 + }) + ); + }); + } + + + #[test] + fn emergency_cancel_should_work() { + with_externalities(&mut new_test_ext(), || { + System::set_block_number(0); + let r = Democracy::inject_referendum( + 2, + set_balance_proposal(2), + VoteThreshold::SuperMajorityApprove, + 2 + ).unwrap(); + assert!(Democracy::referendum_info(r).is_some()); + + assert_noop!(Democracy::emergency_cancel(Origin::signed(3), r), "Invalid origin"); + assert_ok!(Democracy::emergency_cancel(Origin::signed(4), r)); + assert!(Democracy::referendum_info(r).is_none()); + + // some time later... + + let r = Democracy::inject_referendum( + 2, + set_balance_proposal(2), + VoteThreshold::SuperMajorityApprove, + 2 + ).unwrap(); + assert!(Democracy::referendum_info(r).is_some()); + assert_noop!(Democracy::emergency_cancel(Origin::signed(4), r), "cannot cancel the same proposal twice"); + }); + } + + #[test] + fn veto_external_works() { + with_externalities(&mut new_test_ext(), || { + System::set_block_number(0); + assert_ok!(Democracy::external_propose( + Origin::signed(2), + Box::new(set_balance_proposal(2)), + )); + assert!(<NextExternal<Test>>::exists()); + + let h = BlakeTwo256::hash_of(&set_balance_proposal(2)); + assert_ok!(Democracy::veto_external(Origin::signed(3), h.clone())); + // cancelled. + assert!(!<NextExternal<Test>>::exists()); + // fails - same proposal can't be resubmitted. + assert_noop!(Democracy::external_propose( + Origin::signed(2), + Box::new(set_balance_proposal(2)), + ), "proposal still blacklisted"); + + fast_forward_to(1); + // fails as we're still in cooloff period. + assert_noop!(Democracy::external_propose( + Origin::signed(2), + Box::new(set_balance_proposal(2)), + ), "proposal still blacklisted"); + + fast_forward_to(2); + // works; as we're out of the cooloff period. + assert_ok!(Democracy::external_propose( + Origin::signed(2), + Box::new(set_balance_proposal(2)), + )); + assert!(<NextExternal<Test>>::exists()); + + // 3 can't veto the same thing twice. + assert_noop!( + Democracy::veto_external(Origin::signed(3), h.clone()), + "identity may not veto a proposal twice" + ); + + // 4 vetoes. + assert_ok!(Democracy::veto_external(Origin::signed(4), h.clone())); + // cancelled again. + assert!(!<NextExternal<Test>>::exists()); + + fast_forward_to(3); + // same proposal fails as we're still in cooloff + assert_noop!(Democracy::external_propose( + Origin::signed(2), + Box::new(set_balance_proposal(2)), + ), "proposal still blacklisted"); + // different proposal works fine. + assert_ok!(Democracy::external_propose( + Origin::signed(2), + Box::new(set_balance_proposal(3)), + )); + }); + } + + #[test] + fn emergency_referendum_works() { + with_externalities(&mut new_test_ext(), || { + System::set_block_number(0); + assert_noop!(Democracy::emergency_propose( + Origin::signed(6), // invalid + Box::new(set_balance_proposal(2)), + VoteThreshold::SuperMajorityAgainst, + 0, + 0, + ), "bad origin: expected to be a root origin"); + assert_ok!(Democracy::emergency_propose( + Origin::signed(1), + Box::new(set_balance_proposal(2)), + VoteThreshold::SuperMajorityAgainst, + 0, + 0, + )); + assert_eq!( + Democracy::referendum_info(0), + Some(ReferendumInfo { + end: 1, + proposal: set_balance_proposal(2), + threshold: VoteThreshold::SuperMajorityAgainst, + delay: 0 + }) + ); + + assert_ok!(Democracy::vote(Origin::signed(1), 0, AYE)); + fast_forward_to(1); + assert_eq!(Balances::free_balance(&42), 0); + fast_forward_to(2); + assert_eq!(Balances::free_balance(&42), 2); + + assert_ok!(Democracy::emergency_propose( + Origin::signed(1), + Box::new(set_balance_proposal(4)), + VoteThreshold::SuperMajorityAgainst, + 3, + 3 + )); + assert_eq!( + Democracy::referendum_info(1), + Some(ReferendumInfo { + end: 5, + proposal: set_balance_proposal(4), + threshold: VoteThreshold::SuperMajorityAgainst, + delay: 3 + }) + ); + assert_ok!(Democracy::vote(Origin::signed(1), 1, AYE)); + fast_forward_to(8); + assert_eq!(Balances::free_balance(&42), 2); + fast_forward_to(9); + assert_eq!(Balances::free_balance(&42), 4); + }); + } + + #[test] + fn external_referendum_works() { + with_externalities(&mut new_test_ext(), || { + System::set_block_number(0); + assert_noop!(Democracy::external_propose( + Origin::signed(1), + Box::new(set_balance_proposal(2)), + ), "Invalid origin"); + assert_ok!(Democracy::external_propose( + Origin::signed(2), + Box::new(set_balance_proposal(2)), + )); + assert_noop!(Democracy::external_propose( + Origin::signed(2), + Box::new(set_balance_proposal(1)), + ), "proposal already made"); + fast_forward_to(1); + assert_eq!( + Democracy::referendum_info(0), + Some(ReferendumInfo { + end: 2, + proposal: set_balance_proposal(2), + threshold: VoteThreshold::SuperMajorityApprove, + delay: 2 + }) + ); + }); + } + + #[test] + fn external_majority_referendum_works() { + with_externalities(&mut new_test_ext(), || { + System::set_block_number(0); + assert_noop!(Democracy::external_propose_majority( + Origin::signed(1), + Box::new(set_balance_proposal(2)) + ), "Invalid origin"); + assert_ok!(Democracy::external_propose_majority( + Origin::signed(3), + Box::new(set_balance_proposal(2)) + )); + fast_forward_to(1); + assert_eq!( + Democracy::referendum_info(0), + Some(ReferendumInfo { + end: 2, + proposal: set_balance_proposal(2), + threshold: VoteThreshold::SimpleMajority, + delay: 2, + }) + ); + }); + } + #[test] fn locked_for_should_work() { with_externalities(&mut new_test_ext(), || { @@ -837,25 +1341,74 @@ mod tests { #[test] fn single_proposal_should_work() { with_externalities(&mut new_test_ext(), || { - System::set_block_number(1); + System::set_block_number(0); assert_ok!(propose_set_balance(1, 2, 1)); - assert_eq!(Democracy::end_block(System::block_number()), Ok(())); + assert!(Democracy::referendum_info(0).is_none()); + + // end of 0 => next referendum scheduled. + fast_forward_to(1); - System::set_block_number(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: 2, + proposal: set_balance_proposal(2), + threshold: VoteThreshold::SuperMajorityApprove, + delay: 2 + }) + ); assert_eq!(Democracy::voters_for(r), vec![1]); assert_eq!(Democracy::vote_of((r, 1)), AYE); assert_eq!(Democracy::tally(r), (1, 0, 1)); - next_block(); - next_block(); + fast_forward_to(2); + + // referendum still running + assert!(Democracy::referendum_info(0).is_some()); + + // referendum runs during 1 and 2, ends @ end of 2. + fast_forward_to(3); + + assert!(Democracy::referendum_info(0).is_none()); + assert_eq!(Democracy::dispatch_queue(4), vec![ + Some((set_balance_proposal(2), 0)) + ]); + + // referendum passes and wait another two blocks for enactment. + fast_forward_to(5); + assert_eq!(Balances::free_balance(&42), 2); }); } + #[test] + fn cancel_queued_should_work() { + with_externalities(&mut new_test_ext(), || { + System::set_block_number(0); + assert_ok!(propose_set_balance(1, 2, 1)); + + // end of 0 => next referendum scheduled. + fast_forward_to(1); + + assert_ok!(Democracy::vote(Origin::signed(1), 0, AYE)); + + fast_forward_to(3); + + assert_eq!(Democracy::dispatch_queue(4), vec![ + Some((set_balance_proposal(2), 0)) + ]); + + assert_noop!(Democracy::cancel_queued(3, 0, 0), "proposal not found"); + assert_noop!(Democracy::cancel_queued(4, 1, 0), "proposal not found"); + assert_ok!(Democracy::cancel_queued(4, 0, 0)); + assert_eq!(Democracy::dispatch_queue(4), vec![None]); + }); + } + #[test] fn proxy_should_work() { with_externalities(&mut new_test_ext(), || { @@ -889,22 +1442,19 @@ mod tests { #[test] fn single_proposal_should_work_with_proxy() { with_externalities(&mut new_test_ext(), || { - System::set_block_number(1); + System::set_block_number(0); assert_ok!(propose_set_balance(1, 2, 1)); - assert_eq!(Democracy::end_block(System::block_number()), Ok(())); - System::set_block_number(2); + fast_forward_to(1); let r = 0; assert_ok!(Democracy::set_proxy(Origin::signed(1), 10)); assert_ok!(Democracy::proxy_vote(Origin::signed(10), r, AYE)); - assert_eq!(Democracy::referendum_count(), 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(); + fast_forward_to(5); assert_eq!(Balances::free_balance(&42), 2); }); } @@ -912,27 +1462,23 @@ mod tests { #[test] fn single_proposal_should_work_with_delegation() { with_externalities(&mut new_test_ext(), || { - System::set_block_number(1); + System::set_block_number(0); assert_ok!(propose_set_balance(1, 2, 1)); - next_block(); - let r = 0; + fast_forward_to(1); // Delegate vote. assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::max_value())); + let r = 0; assert_ok!(Democracy::vote(Origin::signed(1), r, AYE)); - - assert_eq!(Democracy::referendum_count(), 1); 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)); - next_block(); - next_block(); + fast_forward_to(5); assert_eq!(Balances::free_balance(&42), 2); }); @@ -941,27 +1487,24 @@ mod tests { #[test] fn single_proposal_should_work_with_cyclic_delegation() { with_externalities(&mut new_test_ext(), || { - System::set_block_number(1); + System::set_block_number(0); assert_ok!(propose_set_balance(1, 2, 1)); - next_block(); - let r = 0; + fast_forward_to(1); // Check behavior with cycle. assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::max_value())); assert_ok!(Democracy::delegate(Origin::signed(3), 2, Conviction::max_value())); assert_ok!(Democracy::delegate(Origin::signed(1), 3, Conviction::max_value())); - + let r = 0; assert_ok!(Democracy::vote(Origin::signed(1), r, AYE)); - - assert_eq!(Democracy::referendum_count(), 1); assert_eq!(Democracy::voters_for(r), vec![1]); // Delegated vote is counted. assert_eq!(Democracy::tally(r), (6, 0, 6)); - next_block(); - next_block(); + + fast_forward_to(5); assert_eq!(Balances::free_balance(&42), 2); }); @@ -971,30 +1514,24 @@ mod tests { /// If transactor already voted, delegated vote is overwriten. fn single_proposal_should_work_with_vote_and_delegation() { with_externalities(&mut new_test_ext(), || { - System::set_block_number(1); + System::set_block_number(0); assert_ok!(propose_set_balance(1, 2, 1)); - next_block(); - let r = 0; + fast_forward_to(1); + let r = 0; assert_ok!(Democracy::vote(Origin::signed(1), r, AYE)); - // Vote. assert_ok!(Democracy::vote(Origin::signed(2), r, AYE)); - // Delegate vote. assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::max_value())); - - assert_eq!(Democracy::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)); - next_block(); - next_block(); + fast_forward_to(5); assert_eq!(Balances::free_balance(&42), 2); }); @@ -1003,7 +1540,7 @@ mod tests { #[test] fn single_proposal_should_work_with_undelegation() { with_externalities(&mut new_test_ext(), || { - System::set_block_number(1); + System::set_block_number(0); assert_ok!(propose_set_balance(1, 2, 1)); @@ -1011,7 +1548,7 @@ mod tests { assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::max_value())); assert_ok!(Democracy::undelegate(Origin::signed(2))); - next_block(); + fast_forward_to(1); let r = 0; assert_ok!(Democracy::vote(Origin::signed(1), r, AYE)); @@ -1022,8 +1559,7 @@ mod tests { // Delegated vote is not counted. assert_eq!(Democracy::tally(r), (1, 0, 1)); - next_block(); - next_block(); + fast_forward_to(5); assert_eq!(Balances::free_balance(&42), 2); }); @@ -1033,11 +1569,11 @@ mod tests { /// If transactor voted, delegated vote is overwriten. fn single_proposal_should_work_with_delegation_and_vote() { with_externalities(&mut new_test_ext(), || { - System::set_block_number(1); + System::set_block_number(0); assert_ok!(propose_set_balance(1, 2, 1)); - next_block(); + fast_forward_to(1); let r = 0; assert_ok!(Democracy::vote(Origin::signed(1), r, AYE)); @@ -1055,8 +1591,7 @@ mod tests { // Delegated vote is not counted. assert_eq!(Democracy::tally(r), (3, 0, 3)); - next_block(); - next_block(); + fast_forward_to(5); assert_eq!(Balances::free_balance(&42), 2); }); @@ -1086,7 +1621,7 @@ mod tests { 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!(Democracy::end_block(System::block_number()), Ok(())); + 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); @@ -1125,11 +1660,11 @@ mod tests { assert_ok!(propose_set_balance(1, 2, 2)); assert_ok!(propose_set_balance(1, 4, 4)); assert_ok!(propose_set_balance(1, 3, 3)); - next_block(); + fast_forward_to(1); assert_ok!(Democracy::vote(Origin::signed(1), 0, AYE)); - next_block(); + fast_forward_to(3); assert_ok!(Democracy::vote(Origin::signed(1), 1, AYE)); - next_block(); + fast_forward_to(5); assert_ok!(Democracy::vote(Origin::signed(1), 2, AYE)); }); } @@ -1305,7 +1840,7 @@ mod tests { #[test] fn lock_voting_should_work() { with_externalities(&mut new_test_ext(), || { - System::set_block_number(1); + System::set_block_number(0); let r = Democracy::inject_referendum( 1, set_balance_proposal(2), @@ -1335,31 +1870,29 @@ mod tests { assert_eq!(Democracy::tally(r), (250, 100, 150)); - next_block(); + fast_forward_to(2); assert_eq!(Balances::locks(1), vec![]); assert_eq!(Balances::locks(2), vec![BalanceLock { id: DEMOCRACY_ID, amount: u64::max_value(), - until: 9, + until: 17, reasons: WithdrawReason::Transfer.into() }]); assert_eq!(Balances::locks(3), vec![BalanceLock { id: DEMOCRACY_ID, amount: u64::max_value(), - until: 5, + until: 9, reasons: WithdrawReason::Transfer.into() }]); assert_eq!(Balances::locks(4), vec![BalanceLock { id: DEMOCRACY_ID, amount: u64::max_value(), - until: 3, + until: 5, reasons: WithdrawReason::Transfer.into() }]); assert_eq!(Balances::locks(5), vec![]); - next_block(); - assert_eq!(Balances::free_balance(&42), 2); }); } @@ -1395,28 +1928,6 @@ mod tests { assert_eq!(Democracy::tally(r), (250, 100, 150)); next_block(); - - assert_eq!(Balances::locks(1), vec![]); - assert_eq!(Balances::locks(2), vec![BalanceLock { - id: DEMOCRACY_ID, - amount: u64::max_value(), - until: 9, - reasons: WithdrawReason::Transfer.into() - }]); - assert_eq!(Balances::locks(3), vec![BalanceLock { - id: DEMOCRACY_ID, - amount: u64::max_value(), - until: 5, - reasons: WithdrawReason::Transfer.into() - }]); - assert_eq!(Balances::locks(4), vec![BalanceLock { - id: DEMOCRACY_ID, - amount: u64::max_value(), - until: u64::max_value(), - reasons: WithdrawReason::Transfer.into() - }]); - assert_eq!(Balances::locks(5), vec![]); - next_block(); assert_eq!(Balances::free_balance(&42), 2); diff --git a/substrate/srml/support/src/metadata.rs b/substrate/srml/support/src/metadata.rs index b1da7587be390d18789d0eb1a853a450d5e20790..ed3e443afdcf414cfb718b1b0aafabd9a4e2b535 100644 --- a/substrate/srml/support/src/metadata.rs +++ b/substrate/srml/support/src/metadata.rs @@ -246,7 +246,8 @@ mod tests { mod system { pub trait Trait { - type Origin: Into<Option<RawOrigin<Self::AccountId>>> + From<RawOrigin<Self::AccountId>>; + type Origin: Into<Result<RawOrigin<Self::AccountId>, Self::Origin>> + + From<RawOrigin<Self::AccountId>>; type AccountId; type BlockNumber; } diff --git a/substrate/srml/support/src/origin.rs b/substrate/srml/support/src/origin.rs index 48d4be80c6f984b068c0e882bb6ace3b1be27397..9bc2cab8b9d178630144cfee9d44c3485fef0f1e 100644 --- a/substrate/srml/support/src/origin.rs +++ b/substrate/srml/support/src/origin.rs @@ -112,12 +112,12 @@ macro_rules! impl_outer_origin { $name::system(x) } } - impl Into<Option<$system::Origin<$runtime>>> for $name { - fn into(self) -> Option<$system::Origin<$runtime>> { + impl Into<$crate::rstd::result::Result<$system::Origin<$runtime>, $name>> for $name { + fn into(self) -> $crate::rstd::result::Result<$system::Origin<$runtime>, Self> { if let $name::system(l) = self { - Some(l) + Ok(l) } else { - None + Err(self) } } } @@ -132,12 +132,18 @@ macro_rules! impl_outer_origin { $name::$module(x) } } - impl Into<Option<$module::Origin $( <$generic_param $(, $generic_instance )? > )*>> for $name { - fn into(self) -> Option<$module::Origin $( <$generic_param $(, $generic_instance )? > )*> { + impl Into<$crate::rstd::result::Result< + $module::Origin $( <$generic_param $(, $generic_instance )? > )*, + $name + >> for $name { + fn into(self) -> $crate::rstd::result::Result< + $module::Origin $( <$generic_param $(, $generic_instance )? > )*, + Self + > { if let $name::$module(l) = self { - Some(l) + Ok(l) } else { - None + Err(self) } } } diff --git a/substrate/srml/support/src/traits.rs b/substrate/srml/support/src/traits.rs index 599de27c2ec3de0268d423dfd1bf287969ffe0b5..3b3c63e223ce14831368094bf3b882a929c89f74 100644 --- a/substrate/srml/support/src/traits.rs +++ b/substrate/srml/support/src/traits.rs @@ -26,12 +26,26 @@ use crate::runtime_primitives::traits::{ use super::for_each_tuple; -/// New trait for querying a single fixed value from a type. +/// A trait for querying a single fixed value from a type. pub trait Get<T> { /// Return a constant value. fn get() -> T; } +/// A trait for querying whether a type can be said to statically "contain" a value. Similar +/// in nature to `Get`, except it is designed to be lazy rather than active (you can't ask it to +/// enumerate all values that it contains) and work for multiple values rather than just one. +pub trait Contains<T> { + /// Return `true` if this "contains" the given value `t`. + fn contains(t: &T) -> bool; +} + +impl<V: PartialEq, T: Get<V>> Contains<V> for T { + fn contains(t: &V) -> bool { + &Self::get() == t + } +} + /// The account with the given id was killed. pub trait OnFreeBalanceZero<AccountId> { /// The account was the given id was killed. diff --git a/substrate/srml/support/test/tests/instance.rs b/substrate/srml/support/test/tests/instance.rs index 641ad9f4b56f475b922f13706c28f1910887dcb4..796934777d9139a8ab7f2191e3579e5b260723f6 100644 --- a/substrate/srml/support/test/tests/instance.rs +++ b/substrate/srml/support/test/tests/instance.rs @@ -39,7 +39,8 @@ mod system { use super::*; pub trait Trait: 'static + Eq + Clone { - type Origin: Into<Option<RawOrigin<Self::AccountId>>> + From<RawOrigin<Self::AccountId>>; + type Origin: Into<Result<RawOrigin<Self::AccountId>, Self::Origin>> + + From<RawOrigin<Self::AccountId>>; type BlockNumber; type Digest: Digest<Hash = H256>; type Hash; @@ -100,12 +101,9 @@ mod system { } pub fn ensure_root<OuterOrigin, AccountId>(o: OuterOrigin) -> Result<(), &'static str> - where OuterOrigin: Into<Option<RawOrigin<AccountId>>> + where OuterOrigin: Into<Result<RawOrigin<AccountId>, OuterOrigin>> { - match o.into() { - Some(RawOrigin::Root) => Ok(()), - _ => Err("bad origin: expected to be a root origin"), - } + o.into().map(|_| ()).map_err(|_| "bad origin: expected to be a root origin") } } diff --git a/substrate/srml/system/src/lib.rs b/substrate/srml/system/src/lib.rs index 5a3094d821409e79fc01e696774e3ad03e250a34..b08321f86055f4206d90e3432ee45d135831edaa 100644 --- a/substrate/srml/system/src/lib.rs +++ b/substrate/srml/system/src/lib.rs @@ -78,14 +78,14 @@ use rstd::prelude::*; use rstd::map; use primitives::traits::{self, CheckEqual, SimpleArithmetic, SimpleBitOps, One, Bounded, Lookup, Hash, Member, MaybeDisplay, EnsureOrigin, Digest as DigestT, CurrentHeight, BlockNumberToHash, - MaybeSerializeDebugButNotDeserialize, MaybeSerializeDebug, StaticLookup, + MaybeSerializeDebugButNotDeserialize, MaybeSerializeDebug, StaticLookup }; #[cfg(any(feature = "std", test))] use primitives::traits::Zero; use substrate_primitives::storage::well_known_keys; use srml_support::{ storage, decl_module, decl_event, decl_storage, StorageDoubleMap, StorageValue, - StorageMap, Parameter, for_each_tuple, + StorageMap, Parameter, for_each_tuple, traits::Contains }; use safe_mix::TripletMix; use parity_codec::{Encode, Decode}; @@ -145,7 +145,7 @@ pub fn extrinsics_data_root<H: Hash>(xts: Vec<Vec<u8>>) -> H::Output { pub trait Trait: 'static + Eq + Clone { /// The aggregated `Origin` type used by dispatchable calls. - type Origin: Into<Option<RawOrigin<Self::AccountId>>> + From<RawOrigin<Self::AccountId>>; + type Origin: Into<Result<RawOrigin<Self::AccountId>, Self::Origin>> + From<RawOrigin<Self::AccountId>>; /// Account index (aka nonce) type. This stores the number of previous transactions associated with a sender /// account. @@ -376,40 +376,97 @@ decl_storage! { } pub struct EnsureRoot<AccountId>(::rstd::marker::PhantomData<AccountId>); -impl<O: Into<Option<RawOrigin<AccountId>>>, AccountId> EnsureOrigin<O> for EnsureRoot<AccountId> { +impl< + O: Into<Result<RawOrigin<AccountId>, O>> + From<RawOrigin<AccountId>>, + AccountId, +> EnsureOrigin<O> for EnsureRoot<AccountId> { type Success = (); - fn ensure_origin(o: O) -> Result<Self::Success, &'static str> { - ensure_root(o) + fn try_origin(o: O) -> Result<Self::Success, O> { + o.into().and_then(|o| match o { + RawOrigin::Root => Ok(()), + r => Err(O::from(r)), + }) + } +} + +pub struct EnsureSigned<AccountId>(::rstd::marker::PhantomData<AccountId>); +impl< + O: Into<Result<RawOrigin<AccountId>, O>> + From<RawOrigin<AccountId>>, + AccountId, +> EnsureOrigin<O> for EnsureSigned<AccountId> { + type Success = AccountId; + fn try_origin(o: O) -> Result<Self::Success, O> { + o.into().and_then(|o| match o { + RawOrigin::Signed(who) => Ok(who), + r => Err(O::from(r)), + }) + } +} + +pub struct EnsureSignedBy<Who, AccountId>(::rstd::marker::PhantomData<(Who, AccountId)>); +impl< + O: Into<Result<RawOrigin<AccountId>, O>> + From<RawOrigin<AccountId>>, + Who: Contains<AccountId>, + AccountId: PartialEq + Clone, +> EnsureOrigin<O> for EnsureSignedBy<Who, AccountId> { + type Success = AccountId; + fn try_origin(o: O) -> Result<Self::Success, O> { + o.into().and_then(|o| match o { + RawOrigin::Signed(ref who) if Who::contains(who) => Ok(who.clone()), + r => Err(O::from(r)), + }) + } +} + +pub struct EnsureNone<AccountId>(::rstd::marker::PhantomData<AccountId>); +impl< + O: Into<Result<RawOrigin<AccountId>, O>> + From<RawOrigin<AccountId>>, + AccountId, +> EnsureOrigin<O> for EnsureNone<AccountId> { + type Success = (); + fn try_origin(o: O) -> Result<Self::Success, O> { + o.into().and_then(|o| match o { + RawOrigin::None => Ok(()), + r => Err(O::from(r)), + }) + } +} + +pub struct EnsureNever<T>(::rstd::marker::PhantomData<T>); +impl<O, T> EnsureOrigin<O> for EnsureNever<T> { + type Success = T; + fn try_origin(o: O) -> Result<Self::Success, O> { + Err(o) } } /// Ensure that the origin `o` represents a signed extrinsic (i.e. transaction). /// Returns `Ok` with the account that signed the extrinsic or an `Err` otherwise. pub fn ensure_signed<OuterOrigin, AccountId>(o: OuterOrigin) -> Result<AccountId, &'static str> - where OuterOrigin: Into<Option<RawOrigin<AccountId>>> + where OuterOrigin: Into<Result<RawOrigin<AccountId>, OuterOrigin>> { match o.into() { - Some(RawOrigin::Signed(t)) => Ok(t), + Ok(RawOrigin::Signed(t)) => Ok(t), _ => Err("bad origin: expected to be a signed origin"), } } /// Ensure that the origin `o` represents the root. Returns `Ok` or an `Err` otherwise. pub fn ensure_root<OuterOrigin, AccountId>(o: OuterOrigin) -> Result<(), &'static str> - where OuterOrigin: Into<Option<RawOrigin<AccountId>>> + where OuterOrigin: Into<Result<RawOrigin<AccountId>, OuterOrigin>> { match o.into() { - Some(RawOrigin::Root) => Ok(()), + Ok(RawOrigin::Root) => Ok(()), _ => Err("bad origin: expected to be a root origin"), } } /// Ensure that the origin `o` represents an unsigned extrinsic. Returns `Ok` or an `Err` otherwise. pub fn ensure_none<OuterOrigin, AccountId>(o: OuterOrigin) -> Result<(), &'static str> - where OuterOrigin: Into<Option<RawOrigin<AccountId>>> + where OuterOrigin: Into<Result<RawOrigin<AccountId>, OuterOrigin>> { match o.into() { - Some(RawOrigin::None) => Ok(()), + Ok(RawOrigin::None) => Ok(()), _ => Err("bad origin: expected to be no origin"), } } @@ -753,6 +810,13 @@ mod tests { GenesisConfig::<Test>::default().build_storage().unwrap().0.into() } + #[test] + fn origin_works() { + let o = Origin::from(RawOrigin::<u64>::Signed(1u64)); + let x: Result<RawOrigin<u64>, Origin> = o.into(); + assert_eq!(x, Ok(RawOrigin::<u64>::Signed(1u64))); + } + #[test] fn deposit_event_should_work() { with_externalities(&mut new_test_ext(), || {