mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-26 18:07:58 +00:00
Sensible scheduling for referenda (#2753)
* Nonlinear locking and cleanups * Bump runtime version * Minor cleanup * Fix tests * Fix council tests * Fix flaw in turnout counting * Initial work on referendum schedules * Refactor council-democracy interface. * Fix build * Update srml/democracy/src/lib.rs Co-Authored-By: Luke Schoen <ltfschoen@users.noreply.github.com> * Update srml/democracy/src/lib.rs Co-Authored-By: Luke Schoen <ltfschoen@users.noreply.github.com> * Tests compile again * Tests! * Update todo * Fix build * Ensure votes arer not double-counted on member-transitions * Extra logic for normal council changes * Typo * A few grumbles addressed.
This commit is contained in:
@@ -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; }
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
}),
|
||||
|
||||
@@ -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()),
|
||||
|
||||
@@ -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>},
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
}
|
||||
}
|
||||
+618
-107
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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(), || {
|
||||
|
||||
Reference in New Issue
Block a user