Browse Source

Merge pull request #1911 from Joystream/council_rework

Council rework
Bedeho Mender 4 years ago
parent
commit
be05f16665
51 changed files with 7865 additions and 3447 deletions
  1. 68 46
      Cargo.lock
  2. 3 2
      Cargo.toml
  3. 22 0
      node/src/chain_spec/council_config.rs
  4. 6 21
      node/src/chain_spec/mod.rs
  5. 81 0
      rome-testnet.json
  6. 26 26
      runtime-modules/council/Cargo.toml
  7. 1294 0
      runtime-modules/council/src/lib.rs
  8. 1184 0
      runtime-modules/council/src/mock.rs
  9. 1366 0
      runtime-modules/council/src/tests.rs
  10. 0 374
      runtime-modules/governance/src/council.rs
  11. 0 2154
      runtime-modules/governance/src/election.rs
  12. 0 51
      runtime-modules/governance/src/election_params.rs
  13. 0 15
      runtime-modules/governance/src/lib.rs
  14. 0 179
      runtime-modules/governance/src/mock.rs
  15. 0 88
      runtime-modules/governance/src/sealed_vote.rs
  16. 0 120
      runtime-modules/governance/src/stake.rs
  17. 2 2
      runtime-modules/membership/Cargo.toml
  18. 11 0
      runtime-modules/membership/src/tests/mock.rs
  19. 4 4
      runtime-modules/proposals/codex/Cargo.toml
  20. 3 2
      runtime-modules/proposals/codex/src/lib.rs
  21. 147 17
      runtime-modules/proposals/codex/src/tests/mock.rs
  22. 90 13
      runtime-modules/proposals/codex/src/tests/mod.rs
  23. 8 5
      runtime-modules/proposals/engine/Cargo.toml
  24. 188 48
      runtime-modules/proposals/engine/src/benchmarking.rs
  25. 1 1
      runtime-modules/proposals/engine/src/lib.rs
  26. 133 7
      runtime-modules/proposals/engine/src/tests/mock/mod.rs
  27. 3 3
      runtime-modules/proposals/engine/src/tests/mod.rs
  28. 36 0
      runtime-modules/referendum/Cargo.toml
  29. 885 0
      runtime-modules/referendum/src/lib.rs
  30. 620 0
      runtime-modules/referendum/src/mock.rs
  31. 1005 0
      runtime-modules/referendum/src/tests.rs
  32. 2 2
      runtime-modules/service-discovery/Cargo.toml
  33. 12 1
      runtime-modules/service-discovery/src/mock.rs
  34. 1 1
      runtime-modules/staking-handler/Cargo.toml
  35. 64 32
      runtime-modules/staking-handler/src/lib.rs
  36. 12 0
      runtime-modules/staking-handler/src/mock.rs
  37. 1 1
      runtime-modules/storage/Cargo.toml
  38. 10 0
      runtime-modules/storage/src/tests/mock.rs
  39. 1 1
      runtime-modules/working-group/Cargo.toml
  40. 1 1
      runtime-modules/working-group/src/lib.rs
  41. 11 0
      runtime-modules/working-group/src/tests/mock.rs
  42. 5 3
      runtime/Cargo.toml
  43. 101 0
      runtime/src/constants.rs
  44. 0 15
      runtime/src/integration/proposals/council_elected_handler.rs
  45. 29 37
      runtime/src/integration/proposals/council_origin_validator.rs
  46. 0 2
      runtime/src/integration/proposals/mod.rs
  47. 4 2
      runtime/src/integration/proposals/proposal_encoder.rs
  48. 129 13
      runtime/src/lib.rs
  49. 151 3
      runtime/src/tests/mod.rs
  50. 65 116
      runtime/src/tests/proposals_integration/mod.rs
  51. 80 39
      runtime/src/tests/proposals_integration/working_group_proposals.rs

+ 68 - 46
Cargo.lock

@@ -2354,9 +2354,9 @@ dependencies = [
  "pallet-common",
  "pallet-constitution",
  "pallet-content-directory",
+ "pallet-council",
  "pallet-finality-tracker",
  "pallet-forum",
- "pallet-governance",
  "pallet-grandpa",
  "pallet-im-online",
  "pallet-membership",
@@ -2368,10 +2368,12 @@ dependencies = [
  "pallet-proposals-engine",
  "pallet-randomness-collective-flip",
  "pallet-recurring-reward",
+ "pallet-referendum",
  "pallet-service-discovery",
  "pallet-session",
  "pallet-session-benchmarking",
  "pallet-staking",
+ "pallet-staking-handler",
  "pallet-staking-reward-curve",
  "pallet-storage",
  "pallet-sudo",
@@ -2398,7 +2400,6 @@ dependencies = [
  "sp-std",
  "sp-transaction-pool",
  "sp-version",
- "staking-handler",
  "strum 0.19.5",
  "substrate-wasm-builder-runner",
 ]
@@ -3780,50 +3781,51 @@ dependencies = [
 ]
 
 [[package]]
-name = "pallet-finality-tracker"
-version = "2.0.0"
-source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
+name = "pallet-council"
+version = "1.0.0"
 dependencies = [
  "frame-support",
  "frame-system",
- "impl-trait-for-tuples",
+ "pallet-balances",
+ "pallet-common",
+ "pallet-membership",
+ "pallet-referendum",
+ "pallet-staking-handler",
+ "pallet-timestamp",
  "parity-scale-codec",
+ "rand 0.7.3",
  "serde",
- "sp-finality-tracker",
- "sp-inherents",
+ "sp-arithmetic",
+ "sp-core",
+ "sp-io",
  "sp-runtime",
  "sp-std",
 ]
 
 [[package]]
-name = "pallet-forum"
-version = "4.0.0"
+name = "pallet-finality-tracker"
+version = "2.0.0"
+source = "git+https://github.com/paritytech/substrate.git?rev=a200cdb93c6af5763b9c7bf313fa708764ac88ca#a200cdb93c6af5763b9c7bf313fa708764ac88ca"
 dependencies = [
  "frame-support",
  "frame-system",
- "pallet-common",
- "pallet-timestamp",
+ "impl-trait-for-tuples",
  "parity-scale-codec",
  "serde",
- "sp-arithmetic",
- "sp-core",
- "sp-io",
+ "sp-finality-tracker",
+ "sp-inherents",
  "sp-runtime",
  "sp-std",
 ]
 
 [[package]]
-name = "pallet-governance"
-version = "3.1.0"
+name = "pallet-forum"
+version = "4.0.0"
 dependencies = [
  "frame-support",
  "frame-system",
- "pallet-balances",
  "pallet-common",
- "pallet-membership",
- "pallet-recurring-reward",
  "pallet-timestamp",
- "pallet-token-mint",
  "parity-scale-codec",
  "serde",
  "sp-arithmetic",
@@ -3883,6 +3885,7 @@ dependencies = [
  "frame-system",
  "pallet-balances",
  "pallet-common",
+ "pallet-staking-handler",
  "pallet-timestamp",
  "pallet-working-group",
  "parity-scale-codec",
@@ -3892,7 +3895,6 @@ dependencies = [
  "sp-io",
  "sp-runtime",
  "sp-std",
- "staking-handler",
 ]
 
 [[package]]
@@ -3952,12 +3954,14 @@ dependencies = [
  "pallet-balances",
  "pallet-common",
  "pallet-constitution",
- "pallet-governance",
+ "pallet-council",
  "pallet-membership",
  "pallet-proposals-discussion",
  "pallet-proposals-engine",
  "pallet-recurring-reward",
+ "pallet-referendum",
  "pallet-staking",
+ "pallet-staking-handler",
  "pallet-staking-reward-curve",
  "pallet-timestamp",
  "pallet-token-mint",
@@ -3970,7 +3974,6 @@ dependencies = [
  "sp-runtime",
  "sp-staking",
  "sp-std",
- "staking-handler",
  "strum 0.19.5",
 ]
 
@@ -4002,9 +4005,11 @@ dependencies = [
  "frame-system",
  "pallet-balances",
  "pallet-common",
- "pallet-governance",
+ "pallet-council",
  "pallet-membership",
  "pallet-recurring-reward",
+ "pallet-referendum",
+ "pallet-staking-handler",
  "pallet-timestamp",
  "pallet-token-mint",
  "parity-scale-codec",
@@ -4014,7 +4019,6 @@ dependencies = [
  "sp-io",
  "sp-runtime",
  "sp-std",
- "staking-handler",
 ]
 
 [[package]]
@@ -4045,6 +4049,24 @@ dependencies = [
  "sp-runtime",
 ]
 
+[[package]]
+name = "pallet-referendum"
+version = "1.0.0"
+dependencies = [
+ "frame-support",
+ "frame-system",
+ "pallet-balances",
+ "pallet-common",
+ "parity-scale-codec",
+ "rand 0.7.3",
+ "serde",
+ "sp-arithmetic",
+ "sp-core",
+ "sp-io",
+ "sp-runtime",
+ "sp-std",
+]
+
 [[package]]
 name = "pallet-service-discovery"
 version = "4.0.0"
@@ -4055,6 +4077,7 @@ dependencies = [
  "pallet-common",
  "pallet-membership",
  "pallet-recurring-reward",
+ "pallet-staking-handler",
  "pallet-timestamp",
  "pallet-token-mint",
  "pallet-working-group",
@@ -4065,7 +4088,6 @@ dependencies = [
  "sp-io",
  "sp-runtime",
  "sp-std",
- "staking-handler",
 ]
 
 [[package]]
@@ -4126,6 +4148,23 @@ dependencies = [
  "static_assertions",
 ]
 
+[[package]]
+name = "pallet-staking-handler"
+version = "1.0.0"
+dependencies = [
+ "frame-support",
+ "frame-system",
+ "pallet-balances",
+ "pallet-common",
+ "pallet-timestamp",
+ "parity-scale-codec",
+ "sp-arithmetic",
+ "sp-core",
+ "sp-io",
+ "sp-runtime",
+ "sp-std",
+]
+
 [[package]]
 name = "pallet-staking-reward-curve"
 version = "2.0.0"
@@ -4147,6 +4186,7 @@ dependencies = [
  "pallet-common",
  "pallet-membership",
  "pallet-recurring-reward",
+ "pallet-staking-handler",
  "pallet-timestamp",
  "pallet-token-mint",
  "pallet-working-group",
@@ -4157,7 +4197,6 @@ dependencies = [
  "sp-io",
  "sp-runtime",
  "sp-std",
- "staking-handler",
 ]
 
 [[package]]
@@ -4280,6 +4319,7 @@ dependencies = [
  "pallet-balances",
  "pallet-common",
  "pallet-membership",
+ "pallet-staking-handler",
  "pallet-timestamp",
  "parity-scale-codec",
  "serde",
@@ -4288,7 +4328,6 @@ dependencies = [
  "sp-io",
  "sp-runtime",
  "sp-std",
- "staking-handler",
 ]
 
 [[package]]
@@ -7205,23 +7244,6 @@ version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
 
-[[package]]
-name = "staking-handler"
-version = "1.0.0"
-dependencies = [
- "frame-support",
- "frame-system",
- "pallet-balances",
- "pallet-common",
- "pallet-timestamp",
- "parity-scale-codec",
- "sp-arithmetic",
- "sp-core",
- "sp-io",
- "sp-runtime",
- "sp-std",
-]
-
 [[package]]
 name = "static_assertions"
 version = "1.1.0"

+ 3 - 2
Cargo.toml

@@ -5,11 +5,12 @@ members = [
 	"runtime-modules/proposals/codex",
 	"runtime-modules/proposals/discussion",
 	"runtime-modules/common",
+	"runtime-modules/council",
 	"runtime-modules/forum",
-	"runtime-modules/governance",
 	"runtime-modules/membership",
 	"runtime-modules/memo",
 	"runtime-modules/recurring-reward",
+	"runtime-modules/referendum",
 	"runtime-modules/service-discovery",
 	"runtime-modules/storage",
 	"runtime-modules/token-minting",
@@ -23,4 +24,4 @@ members = [
 
 [profile.release]
 # Substrate runtime requires unwinding.
-panic = "unwind"
+panic = "unwind"

+ 22 - 0
node/src/chain_spec/council_config.rs

@@ -0,0 +1,22 @@
+use node_runtime::council::{CouncilStageUpdate, Trait as CouncilTrait};
+use node_runtime::referendum::ReferendumStage;
+use node_runtime::{CouncilConfig, ReferendumConfig, Runtime};
+
+pub fn create_council_config() -> CouncilConfig {
+    CouncilConfig {
+        stage: CouncilStageUpdate::default(),
+        council_members: vec![],
+        candidates: vec![],
+        announcement_period_nr: 0,
+        budget: 0,
+        next_reward_payments: 0,
+        next_budget_refill: <Runtime as CouncilTrait>::BudgetRefillPeriod::get(),
+    }
+}
+
+pub fn create_referendum_config() -> ReferendumConfig {
+    ReferendumConfig {
+        stage: ReferendumStage::default(),
+        votes: vec![],
+    }
+}

+ 6 - 21
node/src/chain_spec/mod.rs

@@ -30,15 +30,15 @@ use sp_runtime::Perbill;
 
 use node_runtime::{
     membership, wasm_binary_unwrap, AuthorityDiscoveryConfig, BabeConfig, Balance, BalancesConfig,
-    ContentDirectoryConfig, CouncilConfig, CouncilElectionConfig, DataObjectStorageRegistryConfig,
-    DataObjectTypeRegistryConfig, ElectionParameters, ForumConfig, GrandpaConfig, ImOnlineConfig,
-    MembersConfig, SessionConfig, SessionKeys, Signature, StakerStatus, StakingConfig, SudoConfig,
-    SystemConfig, DAYS,
+    ContentDirectoryConfig, DataObjectStorageRegistryConfig, DataObjectTypeRegistryConfig,
+    ForumConfig, GrandpaConfig, ImOnlineConfig, MembersConfig, SessionConfig, SessionKeys,
+    Signature, StakerStatus, StakingConfig, SudoConfig, SystemConfig,
 };
 
 // Exported to be used by chain-spec-builder
 pub use node_runtime::{AccountId, GenesisConfig};
 
+pub mod council_config;
 pub mod forum_config;
 pub mod initial_balances;
 pub mod initial_members;
@@ -264,23 +264,8 @@ pub fn testnet_genesis(
                 })
                 .collect::<Vec<_>>(),
         }),
-        council: Some(CouncilConfig {
-            active_council: vec![],
-            term_ends_at: 1,
-        }),
-        election: Some(CouncilElectionConfig {
-            auto_start: true,
-            election_parameters: ElectionParameters {
-                announcing_period: 2 * DAYS,
-                voting_period: 1 * DAYS,
-                revealing_period: 1 * DAYS,
-                council_size: 6,
-                candidacy_limit: 25,
-                min_council_stake: 1_000,
-                new_term_duration: 10 * DAYS,
-                min_voting_stake: 100,
-            },
-        }),
+        referendum_Instance1: Some(council_config::create_referendum_config()),
+        council: Some(council_config::create_council_config()),
         membership: Some(MembersConfig { members }),
         forum: Some(forum_config),
         data_object_type_registry: Some(DataObjectTypeRegistryConfig {

File diff suppressed because it is too large
+ 81 - 0
rome-testnet.json


+ 26 - 26
runtime-modules/governance/Cargo.toml → runtime-modules/council/Cargo.toml

@@ -1,41 +1,41 @@
 [package]
-name = 'pallet-governance'
-version = '3.1.0'
+name = 'pallet-council'
+version = '1.0.0'
 authors = ['Joystream contributors']
 edition = '2018'
 
 [dependencies]
-serde = { version = "1.0.101", optional = true, features = ["derive"] }
-codec = { package = 'parity-scale-codec', version = '1.3.4', default-features = false, features = ['derive'] }
-sp-std = { package = 'sp-std', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
+codec = { package = 'parity-scale-codec', version = '1.0.0', default-features = false, features = ['derive'] }
+serde = { version = '1.0.101', optional = true}
+sp-runtime = { package = 'sp-runtime', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
+sp-core = { package = 'sp-core', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
+sp-arithmetic = { package = 'sp-arithmetic', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
 frame-support = { package = 'frame-support', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
 frame-system = { package = 'frame-system', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
-sp-arithmetic = { package = 'sp-arithmetic', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
-sp-runtime = { package = 'sp-runtime', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
-pallet-timestamp = { package = 'pallet-timestamp', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
-membership = { package = 'pallet-membership', default-features = false, path = '../membership'}
-minting = { package = 'pallet-token-mint', default-features = false, path = '../token-minting'}
-recurringrewards = { package = 'pallet-recurring-reward', default-features = false, path = '../recurring-reward'}
+sp-std = { package = 'sp-std', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
 common = { package = 'pallet-common', default-features = false, path = '../common'}
+referendum = { package = 'pallet-referendum', default-features = false, path = '../referendum'}
+staking-handler = { package = 'pallet-staking-handler', default-features = false, path = '../staking-handler'}
 
 [dev-dependencies]
-sp-io = { package = 'sp-io', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
-sp-core = { package = 'sp-core', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
 balances = { package = 'pallet-balances', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
+sp-io = { package = 'sp-io', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
+rand = "0.7.3"
+pallet-timestamp = { package = 'pallet-timestamp', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
+membership = { package = 'pallet-membership', default-features = false, path = '../membership'}
 
 [features]
 default = ['std']
 std = [
-	'serde',
-	'codec/std',
-	'sp-std/std',
-	'frame-support/std',
-	'frame-system/std',
-	'sp-arithmetic/std',
-	'sp-runtime/std',
-	'pallet-timestamp/std',
-	'membership/std',
-	'minting/std',
-	'recurringrewards/std',
-	'common/std',
-]
+    'codec/std',
+    'sp-core/std',
+    'serde',
+    'sp-runtime/std',
+    'sp-arithmetic/std',
+    'frame-support/std',
+    'referendum/std',
+    'frame-system/std',
+    'staking-handler/std',
+    'common/std',
+]
+

+ 1294 - 0
runtime-modules/council/src/lib.rs

@@ -0,0 +1,1294 @@
+// TODO: adjust all extrinsic weights
+
+//! # Council module
+//! Council module for the the Joystream platform.
+//!
+//! ## Overview
+//!
+//! The Council module let's privileged network users elect their voted representation.
+//!
+//! Each council cycle is composed of three phases. The default phase is the candidacy announcement
+//! phase, during which users can announce their candidacy to the next council. After a fixed amount
+//! of time (network blocks) candidacy announcement phase concludes, and the next phase starts if a
+//! minimum number of candidates is announced; restarts the announcement phase otherwise. The next
+//! phase is the Election phase, during which users can vote for their selected candidate.
+//! The election itself is handled by the Referendum module. After elections end and a minimum
+//! amount of candidates received votes, a new council is appointed, and the Council module enters
+//! an Idle phase for the fixed amount of time before another round's candidacy announcements begin.
+//!
+//! The module supports requiring staking currency for the both candidacy and voting.
+//!
+//! ## Implementation
+//! When implementing runtime for this module, don't forget to call all ReferendumConnection trait
+//! functions at proper places. See the trait details for more information.
+//!
+//! ## Supported extrinsics
+//! - [announce_candidacy](./struct.Module.html#method.announce_candidacy)
+//! - [release_candidacy_stake](./struct.Module.html#method.release_candidacy_stake)
+//! - [set_candidacy_note](./struct.Module.html#method.set_candidacy_note)
+//! - [set_budget](./struct.Module.html#method.set_budget)
+//! - [plan_budget_refill](./struct.Module.html#method.plan_budget_refill)
+//!
+//! ## Important functions
+//! These functions have to be called by the runtime for the council to work properly.
+//! - [recieve_referendum_results](./trait.ReferendumConnection.html#method.recieve_referendum_results)
+//! - [can_unlock_vote_stake](./trait.ReferendumConnection.html#method.can_unlock_vote_stake)
+//!
+//! ## Dependencies:
+//! - [referendum](../referendum/index.html)
+
+/////////////////// Configuration //////////////////////////////////////////////
+#![cfg_attr(not(feature = "std"), no_std)]
+
+// used dependencies
+use codec::{Decode, Encode};
+use frame_support::traits::{Currency, Get};
+use frame_support::{decl_error, decl_event, decl_module, decl_storage, ensure, error::BadOrigin};
+
+use core::marker::PhantomData;
+use frame_system::{ensure_root, ensure_signed};
+#[cfg(feature = "std")]
+use serde::{Deserialize, Serialize};
+use sp_runtime::traits::{Hash, SaturatedConversion, Saturating};
+use sp_std::vec::Vec;
+
+use common::StakingAccountValidator;
+use referendum::{CastVote, OptionResult, ReferendumManager};
+use staking_handler::StakingHandler;
+
+// declared modules
+mod mock;
+mod tests;
+
+/////////////////// Data Structures ////////////////////////////////////////////
+
+/// Information about council's current state and when it changed the last time.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, PartialEq, Eq, Debug, Default)]
+pub struct CouncilStageUpdate<BlockNumber> {
+    stage: CouncilStage,
+    changed_at: BlockNumber,
+}
+
+/// Possible council states.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, PartialEq, Eq, Debug)]
+pub enum CouncilStage {
+    /// Candidacy announcement period.
+    Announcing(CouncilStageAnnouncing),
+    /// Election of the new council.
+    Election(CouncilStageElection),
+    /// The idle phase - no new council election is running now.
+    Idle,
+}
+
+impl Default for CouncilStage {
+    fn default() -> CouncilStage {
+        CouncilStage::Announcing(CouncilStageAnnouncing {
+            candidates_count: 0,
+        })
+    }
+}
+
+/// Representation for announcing candidacy stage state.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, PartialEq, Eq, Debug, Default)]
+pub struct CouncilStageAnnouncing {
+    candidates_count: u64,
+}
+
+/// Representation for new council members election stage state.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, PartialEq, Eq, Debug, Default)]
+pub struct CouncilStageElection {
+    candidates_count: u64,
+}
+
+/// Candidate representation.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, PartialEq, Eq, Debug, Default, Clone)]
+pub struct Candidate<AccountId, Balance, Hash, VotePower> {
+    staking_account_id: AccountId,
+    reward_account_id: AccountId,
+    cycle_id: u64,
+    stake: Balance,
+    vote_power: VotePower,
+    note_hash: Option<Hash>,
+}
+
+/// Council member representation.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, PartialEq, Eq, Debug, Default, Clone)]
+pub struct CouncilMember<AccountId, MemberId, Balance, BlockNumber> {
+    staking_account_id: AccountId,
+    reward_account_id: AccountId,
+    membership_id: MemberId,
+    stake: Balance,
+    last_payment_block: BlockNumber,
+    unpaid_reward: Balance,
+}
+
+impl<AccountId, MemberId, Balance, BlockNumber>
+    CouncilMember<AccountId, MemberId, Balance, BlockNumber>
+{
+    pub fn member_id(&self) -> &MemberId {
+        &self.membership_id
+    }
+}
+
+impl<AccountId, MemberId, Balance, Hash, VotePower, BlockNumber>
+    From<(
+        Candidate<AccountId, Balance, Hash, VotePower>,
+        MemberId,
+        BlockNumber,
+        Balance,
+    )> for CouncilMember<AccountId, MemberId, Balance, BlockNumber>
+{
+    fn from(
+        from: (
+            Candidate<AccountId, Balance, Hash, VotePower>,
+            MemberId,
+            BlockNumber,
+            Balance,
+        ),
+    ) -> Self {
+        Self {
+            staking_account_id: from.0.staking_account_id,
+            reward_account_id: from.0.reward_account_id,
+            membership_id: from.1,
+            stake: from.0.stake,
+
+            last_payment_block: from.2,
+            unpaid_reward: from.3,
+        }
+    }
+}
+
+/////////////////// Type aliases ///////////////////////////////////////////////
+
+pub type Balance<T> = <<<T as Trait>::Referendum as ReferendumManager<
+    <T as frame_system::Trait>::Origin,
+    <T as frame_system::Trait>::AccountId,
+    <T as common::Trait>::MemberId,
+    <T as frame_system::Trait>::Hash,
+>>::Currency as Currency<<T as frame_system::Trait>::AccountId>>::Balance;
+pub type VotePowerOf<T> = <<T as Trait>::Referendum as ReferendumManager<
+    <T as frame_system::Trait>::Origin,
+    <T as frame_system::Trait>::AccountId,
+    <T as common::Trait>::MemberId,
+    <T as frame_system::Trait>::Hash,
+>>::VotePower;
+pub type CastVoteOf<T> =
+    CastVote<<T as frame_system::Trait>::Hash, Balance<T>, <T as common::Trait>::MemberId>;
+
+pub type CouncilMemberOf<T> = CouncilMember<
+    <T as frame_system::Trait>::AccountId,
+    <T as common::Trait>::MemberId,
+    Balance<T>,
+    <T as frame_system::Trait>::BlockNumber,
+>;
+pub type CandidateOf<T> = Candidate<
+    <T as frame_system::Trait>::AccountId,
+    Balance<T>,
+    <T as frame_system::Trait>::Hash,
+    VotePowerOf<T>,
+>;
+pub type CouncilStageUpdateOf<T> = CouncilStageUpdate<<T as frame_system::Trait>::BlockNumber>;
+
+/////////////////// Trait, Storage, Errors, and Events /////////////////////////
+
+/// The main council trait.
+pub trait Trait: frame_system::Trait + common::Trait {
+    /// The overarching event type.
+    type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;
+
+    /// Referendum used for council elections.
+    type Referendum: ReferendumManager<Self::Origin, Self::AccountId, Self::MemberId, Self::Hash>;
+
+    /// Minimum number of extra candidates needed for the valid election.
+    /// Number of total candidates is equal to council size plus extra candidates.
+    type MinNumberOfExtraCandidates: Get<u64>;
+    /// Council member count
+    type CouncilSize: Get<u64>;
+    /// Minimum stake candidate has to lock
+    type MinCandidateStake: Get<Balance<Self>>;
+
+    /// Identifier for currency lock used for candidacy staking.
+    type CandidacyLock: StakingHandler<Self::AccountId, Balance<Self>, Self::MemberId>;
+    /// Identifier for currency lock used for candidacy staking.
+    type CouncilorLock: StakingHandler<Self::AccountId, Balance<Self>, Self::MemberId>;
+
+    /// Validates staking account ownership for a member.
+    type StakingAccountValidator: common::StakingAccountValidator<Self>;
+
+    /// Duration of annoncing period
+    type AnnouncingPeriodDuration: Get<Self::BlockNumber>;
+    /// Duration of idle period
+    type IdlePeriodDuration: Get<Self::BlockNumber>;
+
+    /// The value elected members will be awarded each block of their reign.
+    type ElectedMemberRewardPerBlock: Get<Balance<Self>>;
+    /// Interval for automatic reward payments.
+    type ElectedMemberRewardPeriod: Get<Self::BlockNumber>;
+
+    /// Amount that will be added to the budget balance on every refill.
+    type BudgetRefillAmount: Get<Balance<Self>>;
+    /// Interval between automatic budget refills.
+    type BudgetRefillPeriod: Get<Self::BlockNumber>;
+
+    /// Checks that the user account is indeed associated with the member.
+    fn is_council_member_account(
+        membership_id: &Self::MemberId,
+        account_id: &<Self as frame_system::Trait>::AccountId,
+    ) -> bool;
+
+    /// Hook called right after the new council is elected.
+    fn new_council_elected(elected_members: &[CouncilMemberOf<Self>]);
+}
+
+/// Trait with functions that MUST be called by the runtime with values received from the
+/// referendum module.
+pub trait ReferendumConnection<T: Trait> {
+    /// Process referendum results. This function MUST be called in runtime's implementation of
+    /// referendum's `process_results()`.
+    fn recieve_referendum_results(
+        winners: &[OptionResult<<T as common::Trait>::MemberId, VotePowerOf<T>>],
+    );
+
+    /// Process referendum results. This function MUST be called in runtime's implementation of
+    /// referendum's `can_release_voting_stake()`.
+    fn can_unlock_vote_stake(vote: &CastVoteOf<T>) -> Result<(), Error<T>>;
+
+    /// Checks that user is indeed candidating. This function MUST be called in runtime's
+    /// implementation of referendum's `is_valid_option_id()`.
+    fn is_valid_candidate_id(membership_id: &T::MemberId) -> bool;
+
+    /// Return current voting power for a selected candidate.
+    fn get_option_power(membership_id: &T::MemberId) -> VotePowerOf<T>;
+
+    /// Recieve vote (power) for a selected candidate.
+    fn increase_option_power(membership_id: &T::MemberId, amount: &VotePowerOf<T>);
+}
+
+decl_storage! {
+    trait Store for Module<T: Trait> as Council {
+        /// Current council voting stage
+        pub Stage get(fn stage) config(): CouncilStageUpdate<T::BlockNumber>;
+
+        /// Current council members
+        pub CouncilMembers get(fn council_members) config(): Vec<CouncilMemberOf<T>>;
+
+        /// Map of all candidates that ever candidated and haven't unstake yet.
+        pub Candidates get(fn candidates) config(): map hasher(blake2_128_concat)
+            T::MemberId => Candidate<T::AccountId, Balance::<T>, T::Hash, VotePowerOf::<T>>;
+
+        /// Index of the current candidacy period. It is incremented everytime announcement period
+        /// starts.
+        pub AnnouncementPeriodNr get(fn announcement_period_nr) config(): u64;
+
+        /// Budget for the council's elected members rewards.
+        pub Budget get(fn budget) config(): Balance<T>;
+
+        /// The next block in which the elected council member rewards will be payed.
+        pub NextRewardPayments get(fn next_reward_payments) config(): T::BlockNumber;
+
+        /// The next block in which the budget will be increased.
+        pub NextBudgetRefill get(fn next_budget_refill) config(): T::BlockNumber;
+    }
+}
+
+decl_event! {
+    pub enum Event<T>
+    where
+        Balance = Balance::<T>,
+        <T as frame_system::Trait>::BlockNumber,
+        <T as common::Trait>::MemberId,
+        <T as frame_system::Trait>::AccountId,
+    {
+        /// New council was elected
+        AnnouncingPeriodStarted(),
+
+        /// Announcing period can't finish because of insufficient candidtate count
+        NotEnoughCandidates(),
+
+        /// Candidates are announced and voting starts
+        VotingPeriodStarted(u64),
+
+        /// New candidate announced
+        NewCandidate(MemberId, Balance),
+
+        /// New council was elected and appointed
+        NewCouncilElected(Vec<MemberId>),
+
+        /// New council was elected and appointed
+        NewCouncilNotElected(),
+
+        /// Candidacy stake that was no longer needed was released
+        CandidacyStakeRelease(MemberId),
+
+        /// Candidate has withdrawn his candidacy
+        CandidacyWithdraw(MemberId),
+
+        /// The candidate has set a new note for their candidacy
+        CandidacyNoteSet(MemberId, Vec<u8>),
+
+        /// The whole reward was paid to the council member.
+        RewardPayment(MemberId, AccountId, Balance, Balance),
+
+        /// Budget balance was changed by the root.
+        BudgetBalanceSet(Balance),
+
+        /// Budget balance was increased by automatic refill.
+        BudgetRefill(Balance),
+
+        /// The next budget refill was planned.
+        BudgetRefillPlanned(BlockNumber),
+    }
+}
+
+decl_error! {
+    /// Council errors
+    pub enum Error for Module<T: Trait> {
+        /// Origin is invalid.
+        BadOrigin,
+
+        /// User tried to announce candidacy outside of the candidacy announcement period.
+        CantCandidateNow,
+
+        /// User tried to release stake outside of the revealing period.
+        CantReleaseStakeNow,
+
+        /// Candidate haven't provided sufficient stake.
+        CandidacyStakeTooLow,
+
+        /// User tried to announce candidacy twice in the same elections.
+        CantCandidateTwice,
+
+        /// User tried to announce candidacy with an account that has the conflicting type of stake
+        /// with candidacy stake and has not enough balance for staking for both purposes.
+        ConflictingStake,
+
+        /// Council member and candidates can't withdraw stake yet.
+        StakeStillNeeded,
+
+        /// User tried to release stake when no stake exists.
+        NoStake,
+
+        /// Insufficient balance for candidacy staking.
+        InsufficientBalanceForStaking,
+
+        /// Candidate can't vote for himself.
+        CantVoteForYourself,
+
+        /// Invalid membership.
+        MemberIdNotMatchAccount,
+
+        /// The combination of membership id and account id is invalid for unstaking an existing
+        /// candidacy stake.
+        InvalidAccountToStakeReuse,
+
+        /// User tried to withdraw candidacy when not candidating.
+        NotCandidatingNow,
+
+        /// Can't withdraw candidacy outside of the candidacy announcement period.
+        CantWithdrawCandidacyNow,
+    }
+}
+
+impl<T: Trait> PartialEq for Error<T> {
+    fn eq(&self, other: &Self) -> bool {
+        self.as_u8() == other.as_u8()
+    }
+}
+
+impl<T: Trait> From<BadOrigin> for Error<T> {
+    fn from(_error: BadOrigin) -> Self {
+        Error::<T>::BadOrigin
+    }
+}
+
+/////////////////// Module definition and implementation ///////////////////////
+
+decl_module! {
+    pub struct Module<T: Trait> for enum Call where origin: T::Origin {
+        /// Predefined errors
+        type Error = Error<T>;
+
+        /// Setup events
+        fn deposit_event() = default;
+
+        /// Minimum number of extra candidates needed for the valid election.
+        /// Number of total candidates is equal to council size plus extra candidates.
+        const MinNumberOfExtraCandidates: u64 = T::MinNumberOfExtraCandidates::get();
+        /// Council member count
+        const CouncilSize: u64 = T::CouncilSize::get();
+        /// Minimum stake candidate has to lock
+        const MinCandidateStake: Balance<T> = T::MinCandidateStake::get();
+        /// Duration of annoncing period
+        const AnnouncingPeriodDuration: T::BlockNumber = T::AnnouncingPeriodDuration::get();
+        /// Duration of idle period
+        const IdlePeriodDuration: T::BlockNumber = T::IdlePeriodDuration::get();
+        /// The value elected members will be awarded each block of their reign.
+        const ElectedMemberRewardPerBlock: Balance<T> = T::ElectedMemberRewardPerBlock::get();
+        /// Interval for automatic reward payments.
+        const ElectedMemberRewardPeriod: T::BlockNumber = T::ElectedMemberRewardPeriod::get();
+        /// Amount that will be added to the budget balance on every refill.
+        const BudgetRefillAmount: Balance<T> = T::BudgetRefillAmount::get();
+        /// Interval between automatic budget refills.
+        const BudgetRefillPeriod: T::BlockNumber = T::BudgetRefillPeriod::get();
+
+        /////////////////// Lifetime ///////////////////////////////////////////
+
+        // No origin so this is a priviledged call
+        fn on_finalize(now: T::BlockNumber) {
+            // council stage progress
+            Self::try_progress_stage(now);
+
+            // budget reward payment + budget refill
+            Self::try_process_budget(now);
+        }
+
+        /////////////////// Election-related ///////////////////////////////////
+
+        /// Subscribe candidate
+        #[weight = 10_000_000]
+        pub fn announce_candidacy(
+                origin,
+                membership_id: T::MemberId,
+                staking_account_id: T::AccountId,
+                reward_account_id: T::AccountId,
+                stake: Balance<T>
+            ) -> Result<(), Error<T>> {
+            // ensure action can be started
+            let (stage_data, previous_staking_account_id) =
+                EnsureChecks::<T>::can_announce_candidacy(
+                    origin,
+                    &membership_id,
+                    &staking_account_id,
+                    &stake
+                )?;
+
+            // prepare candidate
+            let candidate =
+                Self::prepare_new_candidate(staking_account_id, reward_account_id, stake);
+
+            //
+            // == MUTATION SAFE ==
+            //
+            if let Some(tmp_account_id) = previous_staking_account_id {
+                Mutations::<T>::release_candidacy_stake(&membership_id, &tmp_account_id);
+            }
+
+            // update state
+            Mutations::<T>::announce_candidacy(&stage_data, &membership_id, &candidate, &stake);
+
+            // emit event
+            Self::deposit_event(RawEvent::NewCandidate(membership_id, stake));
+
+            Ok(())
+        }
+
+        /// Release candidacy stake that is no longer needed.
+        #[weight = 10_000_000]
+        pub fn release_candidacy_stake(origin, membership_id: T::MemberId)
+            -> Result<(), Error<T>> {
+            let staking_account_id =
+                EnsureChecks::<T>::can_release_candidacy_stake(origin, &membership_id)?;
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            // update state
+            Mutations::<T>::release_candidacy_stake(&membership_id, &staking_account_id);
+
+            // emit event
+            Self::deposit_event(RawEvent::CandidacyStakeRelease(membership_id));
+
+            Ok(())
+        }
+
+        /// Withdraw candidacy and release candidacy stake.
+        #[weight = 10_000_000]
+        pub fn withdraw_candidacy(origin, membership_id: T::MemberId) -> Result<(), Error<T>> {
+            let staking_account_id =
+                EnsureChecks::<T>::can_withdraw_candidacy(origin, &membership_id)?;
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            // update state
+            Mutations::<T>::release_candidacy_stake(&membership_id, &staking_account_id);
+
+            // emit event
+            Self::deposit_event(RawEvent::CandidacyWithdraw(membership_id));
+
+            Ok(())
+        }
+
+        /// Set short description for the user's candidacy. Can be called anytime during user's candidacy.
+        #[weight = 10_000_000]
+        pub fn set_candidacy_note(origin, membership_id: T::MemberId, note: Vec<u8>)
+            -> Result<(), Error<T>> {
+            // ensure action can be started
+            EnsureChecks::<T>::can_set_candidacy_note(origin, &membership_id)?;
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            // calculate note's hash
+            let note_hash = T::Hashing::hash(note.as_slice());
+
+            // update state
+            Mutations::<T>::set_candidacy_note(&membership_id, &note_hash);
+
+            // emit event
+            Self::deposit_event(RawEvent::CandidacyNoteSet(membership_id, note));
+
+            Ok(())
+        }
+
+        /// Sets the budget balance.
+        #[weight = 10_000_000]
+        pub fn set_budget(origin, balance: Balance<T>) -> Result<(), Error<T>> {
+            // ensure action can be started
+            EnsureChecks::<T>::can_set_budget(origin)?;
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            // update state
+            Mutations::<T>::set_budget(&balance);
+
+            // emit event
+            Self::deposit_event(RawEvent::BudgetBalanceSet(balance));
+
+            Ok(())
+        }
+
+        /// Plan the next budget refill.
+        #[weight = 10_000_000]
+        pub fn plan_budget_refill(origin, next_refill: T::BlockNumber) -> Result<(), Error<T>> {
+            // ensure action can be started
+            EnsureChecks::<T>::can_plan_budget_refill(origin)?;
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            // update state
+            Mutations::<T>::plan_budget_refill(&next_refill);
+
+            // emit event
+            Self::deposit_event(RawEvent::BudgetRefillPlanned(next_refill));
+
+            Ok(())
+        }
+    }
+}
+
+/////////////////// Inner logic ////////////////////////////////////////////////
+
+impl<T: Trait> Module<T> {
+    /////////////////// Lifetime ///////////////////////////////////////////
+
+    // Checkout expire of referendum stage.
+    fn try_progress_stage(now: T::BlockNumber) {
+        // election progress
+        match Stage::<T>::get().stage {
+            CouncilStage::Announcing(stage_data) => {
+                if now
+                    == Stage::<T>::get().changed_at + T::AnnouncingPeriodDuration::get() - 1.into()
+                {
+                    Self::end_announcement_period(stage_data);
+                }
+            }
+            CouncilStage::Idle => {
+                if now == Stage::<T>::get().changed_at + T::IdlePeriodDuration::get() - 1.into() {
+                    Self::end_idle_period();
+                }
+            }
+            _ => (),
+        }
+    }
+
+    // Checkout elected council members reward payments.
+    fn try_process_budget(now: T::BlockNumber) {
+        // budget autorefill
+        if now == NextBudgetRefill::<T>::get() {
+            Self::refill_budget(now);
+        }
+
+        // council members rewards
+        if now == NextRewardPayments::<T>::get() {
+            Self::pay_elected_member_rewards(now);
+        }
+    }
+
+    // Finish voting and start ravealing.
+    fn end_announcement_period(stage_data: CouncilStageAnnouncing) {
+        let candidate_count = T::CouncilSize::get() + T::MinNumberOfExtraCandidates::get();
+
+        // reset announcing period when not enough candidates registered
+        if stage_data.candidates_count < candidate_count {
+            Mutations::<T>::start_announcing_period();
+
+            // emit event
+            Self::deposit_event(RawEvent::NotEnoughCandidates());
+
+            return;
+        }
+
+        // update state
+        Mutations::<T>::finalize_announcing_period(&stage_data);
+
+        // emit event
+        Self::deposit_event(RawEvent::VotingPeriodStarted(stage_data.candidates_count));
+    }
+
+    // Conclude election period and elect new council if possible.
+    fn end_election_period(
+        winners: &[OptionResult<<T as common::Trait>::MemberId, VotePowerOf<T>>],
+    ) {
+        let council_size = T::CouncilSize::get();
+        if winners.len() as u64 != council_size {
+            // reset candidacy announcement period
+            Mutations::<T>::start_announcing_period();
+
+            // emit event
+            Self::deposit_event(RawEvent::NewCouncilNotElected());
+
+            return;
+        }
+
+        let now: T::BlockNumber = <frame_system::Module<T>>::block_number();
+
+        // prepare candidates that got elected
+        let elected_members: Vec<CouncilMemberOf<T>> = winners
+            .iter()
+            .map(|item| {
+                let membership_id = item.option_id;
+                let candidate = Candidates::<T>::get(membership_id);
+
+                // clear candidate record and unlock their candidacy stake
+                Mutations::<T>::clear_candidate(&membership_id, &candidate);
+
+                (candidate, membership_id, now, 0.into()).into()
+            })
+            .collect();
+        // prepare council users for event
+        let elected_council_users = elected_members
+            .iter()
+            .map(|item| item.membership_id)
+            .collect();
+
+        // update state
+        Mutations::<T>::elect_new_council(elected_members.as_slice(), now);
+
+        // emit event
+        Self::deposit_event(RawEvent::NewCouncilElected(elected_council_users));
+
+        // trigger new-council-elected hook
+        T::new_council_elected(elected_members.as_slice());
+    }
+
+    // Finish idle period and start new council election cycle (announcing period).
+    fn end_idle_period() {
+        // update state
+        Mutations::<T>::start_announcing_period();
+
+        // emit event
+        Self::deposit_event(RawEvent::AnnouncingPeriodStarted());
+    }
+
+    /////////////////// Budget-related /////////////////////////////////////
+
+    // Refill (increase) the budget's balance.
+    fn refill_budget(now: T::BlockNumber) {
+        // get refill amount
+        let refill_amount = T::BudgetRefillAmount::get();
+
+        // refill budget
+        Mutations::<T>::refill_budget(&refill_amount);
+
+        // calculate next refill block number
+        let refill_period = T::BudgetRefillPeriod::get();
+        let next_refill = now + refill_period;
+
+        // plan next budget refill
+        Mutations::<T>::plan_budget_refill(&next_refill);
+
+        // emit events
+        Self::deposit_event(RawEvent::BudgetRefill(refill_amount));
+        Self::deposit_event(RawEvent::BudgetRefillPlanned(next_refill));
+    }
+
+    // Pay rewards to elected council members.
+    fn pay_elected_member_rewards(now: T::BlockNumber) {
+        let reward_per_block = T::ElectedMemberRewardPerBlock::get();
+        let starting_balance = Budget::<T>::get();
+
+        // pay reward to all council members
+        let new_balance = CouncilMembers::<T>::get().iter().enumerate().fold(
+            starting_balance,
+            |balance, (member_index, council_member)| {
+                // calculate unpaid reward
+                let unpaid_reward =
+                    Calculations::<T>::get_current_reward(&council_member, reward_per_block, now);
+
+                // depleted budget or no accumulated reward to be paid?
+                if balance == 0.into() || unpaid_reward == 0.into() {
+                    // no need to update council member record here; their unpaid reward will be
+                    // recalculated next time rewards are paid
+
+                    // emit event
+                    Self::deposit_event(RawEvent::RewardPayment(
+                        council_member.membership_id,
+                        council_member.reward_account_id.clone(),
+                        0.into(),
+                        unpaid_reward,
+                    ));
+                    return balance;
+                }
+
+                // calculate withdrawable balance
+                let (available_balance, missing_balance) =
+                    Calculations::<T>::payable_reward(&balance, &unpaid_reward);
+
+                // pay reward
+                Mutations::<T>::pay_reward(
+                    member_index,
+                    &council_member.reward_account_id,
+                    &available_balance,
+                    &missing_balance,
+                    &now,
+                );
+
+                // emit event
+                Self::deposit_event(RawEvent::RewardPayment(
+                    council_member.membership_id,
+                    council_member.reward_account_id.clone(),
+                    available_balance,
+                    missing_balance,
+                ));
+
+                // return new balance
+                balance - available_balance
+            },
+        );
+
+        // update state
+        Mutations::<T>::finish_reward_payments(new_balance, now);
+    }
+
+    /////////////////// Utils //////////////////////////////////////////////////
+
+    // Construct a new candidate for council election.
+    fn prepare_new_candidate(
+        staking_account_id: T::AccountId,
+        reward_account_id: T::AccountId,
+        stake: Balance<T>,
+    ) -> CandidateOf<T> {
+        Candidate {
+            staking_account_id,
+            reward_account_id,
+            cycle_id: AnnouncementPeriodNr::get(),
+            stake,
+            vote_power: 0.into(),
+            note_hash: None,
+        }
+    }
+}
+
+impl<T: Trait> ReferendumConnection<T> for Module<T> {
+    // Process candidates' results recieved from the referendum.
+    fn recieve_referendum_results(
+        winners: &[OptionResult<<T as common::Trait>::MemberId, VotePowerOf<T>>],
+    ) {
+        //
+        // == MUTATION SAFE ==
+        //
+
+        // conclude election
+        Self::end_election_period(winners);
+    }
+
+    // Check that it is a proper time to release stake.
+    fn can_unlock_vote_stake(vote: &CastVoteOf<T>) -> Result<(), Error<T>> {
+        let current_voting_cycle_id = AnnouncementPeriodNr::get();
+
+        // allow release for very old votes
+        if current_voting_cycle_id > vote.cycle_id + 1 {
+            return Ok(());
+        }
+
+        // allow release for current cycle only in idle stage
+        if current_voting_cycle_id == vote.cycle_id
+            && !matches!(Stage::<T>::get().stage, CouncilStage::Idle)
+        {
+            return Err(Error::CantReleaseStakeNow);
+        }
+
+        let voting_for_winner = CouncilMembers::<T>::get()
+            .iter()
+            .map(|council_member| council_member.membership_id)
+            .any(|membership_id| vote.vote_for == Some(membership_id));
+
+        // allow release for vote from previous elections only when not voted for winner
+        if current_voting_cycle_id == vote.cycle_id + 1 {
+            // ensure vote was not cast for the one of winning candidates / council members
+            if voting_for_winner {
+                return Err(Error::CantReleaseStakeNow);
+            }
+
+            return Ok(());
+        }
+
+        // at this point vote.cycle_id == current_voting_cycle_id
+
+        // ensure election has ended and voter haven't voted for winner
+        if voting_for_winner || !matches!(Stage::<T>::get().stage, CouncilStage::Idle) {
+            return Err(Error::CantReleaseStakeNow);
+        }
+
+        Ok(())
+    }
+
+    // Checks that user is indeed candidating.
+    fn is_valid_candidate_id(membership_id: &T::MemberId) -> bool {
+        if !Candidates::<T>::contains_key(membership_id) {
+            return false;
+        }
+
+        let candidate = Candidates::<T>::get(membership_id);
+
+        candidate.cycle_id == AnnouncementPeriodNr::get()
+    }
+
+    // Return current voting power for a selected candidate.
+    fn get_option_power(membership_id: &T::MemberId) -> VotePowerOf<T> {
+        if !Candidates::<T>::contains_key(membership_id) {
+            return 0.into();
+        }
+
+        let candidate = Candidates::<T>::get(membership_id);
+
+        candidate.vote_power
+    }
+
+    // Recieve vote (power) for a selected candidate.
+    fn increase_option_power(membership_id: &T::MemberId, amount: &VotePowerOf<T>) {
+        if !Candidates::<T>::contains_key(membership_id) {
+            return;
+        }
+
+        Candidates::<T>::mutate(membership_id, |candidate| candidate.vote_power += *amount);
+    }
+}
+
+/////////////////// Calculations ///////////////////////////////////////////////
+
+struct Calculations<T: Trait> {
+    _dummy: PhantomData<T>, // 0-sized data meant only to bound generic parameters
+}
+
+impl<T: Trait> Calculations<T> {
+    // Calculate current reward for the recipient.
+    fn get_current_reward(
+        council_member: &CouncilMemberOf<T>,
+        reward_per_block: Balance<T>,
+        now: T::BlockNumber,
+    ) -> Balance<T> {
+        // calculate currently unpaid reward for elected council member
+        // previously_unpaid_reward +
+        // (current_block_number - last_payment_block_number) *
+        // reward_per_block
+        council_member.unpaid_reward.saturating_add(
+            now.saturating_sub(council_member.last_payment_block)
+                .saturated_into()
+                .saturating_mul(reward_per_block.saturated_into())
+                .saturated_into(),
+        )
+    }
+
+    // Retrieve current budget's balance and calculate missing balance for reward payment.
+    fn payable_reward(
+        budget_balance: &Balance<T>,
+        reward_amount: &Balance<T>,
+    ) -> (Balance<T>, Balance<T>) {
+        // check if reward has enough balance
+        if reward_amount <= budget_balance {
+            return (*reward_amount, 0.into());
+        }
+
+        // calculate missing balance
+        let missing_balance = reward_amount.saturating_sub(*budget_balance);
+
+        (*budget_balance, missing_balance)
+    }
+}
+
+/////////////////// Mutations //////////////////////////////////////////////////
+
+struct Mutations<T: Trait> {
+    _dummy: PhantomData<T>, // 0-sized data meant only to bound generic parameters
+}
+
+impl<T: Trait> Mutations<T> {
+    /////////////////// Election-related ///////////////////////////////////
+
+    // Change the council stage to candidacy announcing stage.
+    fn start_announcing_period() {
+        let stage_data = CouncilStageAnnouncing {
+            candidates_count: 0,
+        };
+
+        let block_number = <frame_system::Module<T>>::block_number();
+
+        // set stage
+        Stage::<T>::put(CouncilStageUpdate {
+            stage: CouncilStage::Announcing(stage_data),
+            // set next block as the start of next phase (this function is invoke on block
+            // finalization)
+            changed_at: block_number + 1.into(),
+        });
+
+        // increase anouncement cycle id
+        AnnouncementPeriodNr::mutate(|value| *value += 1);
+    }
+
+    // Change the council stage from the announcing to the election stage.
+    fn finalize_announcing_period(stage_data: &CouncilStageAnnouncing) {
+        let extra_winning_target_count = T::CouncilSize::get() - 1;
+
+        // start referendum
+        T::Referendum::force_start(extra_winning_target_count, AnnouncementPeriodNr::get());
+
+        let block_number = <frame_system::Module<T>>::block_number();
+
+        // change council state
+        Stage::<T>::put(CouncilStageUpdate {
+            stage: CouncilStage::Election(CouncilStageElection {
+                candidates_count: stage_data.candidates_count,
+            }),
+            // set next block as the start of next phase (this function is invoke on block finalization)
+            changed_at: block_number + 1.into(),
+        });
+    }
+
+    // Elect new council after successful election.
+    fn elect_new_council(elected_members: &[CouncilMemberOf<T>], now: T::BlockNumber) {
+        let block_number = <frame_system::Module<T>>::block_number();
+
+        // change council state
+        Stage::<T>::mutate(|value| {
+            *value = CouncilStageUpdate {
+                stage: CouncilStage::Idle,
+                changed_at: block_number + 1.into(), // set next block as the start of next phase
+            }
+        });
+
+        // try to pay any unpaid rewards (any unpaid rewards after this will be discarded call)
+        Module::<T>::pay_elected_member_rewards(now);
+
+        // release stakes for previous council members
+        for council_member in CouncilMembers::<T>::get() {
+            T::CouncilorLock::unlock(&council_member.staking_account_id);
+        }
+
+        // set new council
+        CouncilMembers::<T>::put(elected_members.to_vec());
+
+        // setup elected member lock for new council's members
+        for council_member in CouncilMembers::<T>::get() {
+            // lock council member stake
+            T::CouncilorLock::lock(&council_member.staking_account_id, council_member.stake);
+        }
+    }
+
+    // Announce user's candidacy.
+    fn announce_candidacy(
+        stage_data: &CouncilStageAnnouncing,
+        membership_id: &T::MemberId,
+        candidate: &CandidateOf<T>,
+        stake: &Balance<T>,
+    ) {
+        // insert candidate to candidate registery
+        Candidates::<T>::insert(membership_id, candidate.clone());
+
+        // prepare new stage
+        let new_stage_data = CouncilStageAnnouncing {
+            candidates_count: stage_data.candidates_count + 1,
+        };
+
+        // store new candidacy list
+        Stage::<T>::mutate(|value| {
+            *value = CouncilStageUpdate {
+                stage: CouncilStage::Announcing(new_stage_data),
+
+                // keep changed_at (and other values) - stage phase haven't changed
+                ..*value
+            }
+        });
+
+        // lock candidacy stake
+        T::CandidacyLock::lock(&candidate.staking_account_id, *stake);
+    }
+
+    // Release user's stake that was used for candidacy.
+    fn release_candidacy_stake(membership_id: &T::MemberId, account_id: &T::AccountId) {
+        // release stake amount
+        T::CandidacyLock::unlock(&account_id);
+
+        // remove candidate record
+        Candidates::<T>::remove(membership_id);
+    }
+
+    // Set a new candidacy note for a candidate in the current election.
+    fn set_candidacy_note(membership_id: &T::MemberId, note_hash: &T::Hash) {
+        Candidates::<T>::mutate(membership_id, |value| value.note_hash = Some(*note_hash));
+    }
+
+    // Removes member's candidacy record.
+    fn clear_candidate(membership_id: &T::MemberId, candidate: &CandidateOf<T>) {
+        // unlock candidacy stake
+        T::CandidacyLock::unlock(&candidate.staking_account_id);
+
+        // clear candidate record
+        Candidates::<T>::remove(membership_id);
+    }
+
+    /////////////////// Budget-related /////////////////////////////////////////
+
+    // Set budget balance
+    fn set_budget(balance: &Balance<T>) {
+        Budget::<T>::put(balance);
+    }
+
+    // Refill budget's balance.
+    fn refill_budget(refill_amount: &Balance<T>) {
+        Budget::<T>::mutate(|balance| *balance += *refill_amount);
+    }
+
+    // Plan next budget refill.
+    fn plan_budget_refill(refill_at: &T::BlockNumber) {
+        NextBudgetRefill::<T>::put(refill_at);
+    }
+
+    // Pay reward to a single elected council member.
+    fn pay_reward(
+        member_index: usize,
+        account_id: &T::AccountId,
+        amount: &Balance<T>,
+        missing_balance: &Balance<T>,
+        now: &T::BlockNumber,
+    ) {
+        // mint tokens into reward account
+        <<<T as Trait>::Referendum as ReferendumManager<
+            <T as frame_system::Trait>::Origin,
+            <T as frame_system::Trait>::AccountId,
+            <T as common::Trait>::MemberId,
+            <T as frame_system::Trait>::Hash,
+        >>::Currency as Currency<<T as frame_system::Trait>::AccountId>>::deposit_creating(
+            account_id, *amount,
+        );
+
+        // update elected council member
+        CouncilMembers::<T>::mutate(|members| {
+            members[member_index].last_payment_block = *now;
+            members[member_index].unpaid_reward = *missing_balance;
+        });
+    }
+
+    // Save reward-payments-related changes and plan the next reward payout.
+    fn finish_reward_payments(new_balance: Balance<T>, now: T::BlockNumber) {
+        // update budget's balance
+        Budget::<T>::put(new_balance);
+
+        // plan next rewards payment
+        let next_reward_block = now + T::ElectedMemberRewardPeriod::get();
+        NextRewardPayments::<T>::put(next_reward_block);
+    }
+}
+
+/////////////////// Ensure checks //////////////////////////////////////////////
+
+struct EnsureChecks<T: Trait> {
+    _dummy: PhantomData<T>, // 0-sized data meant only to bound generic parameters
+}
+
+impl<T: Trait> EnsureChecks<T> {
+    /////////////////// Common checks //////////////////////////////////////////
+
+    fn ensure_user_membership(
+        origin: T::Origin,
+        membership_id: &T::MemberId,
+    ) -> Result<T::AccountId, Error<T>> {
+        let account_id = ensure_signed(origin)?;
+
+        ensure!(
+            T::is_council_member_account(membership_id, &account_id),
+            Error::MemberIdNotMatchAccount,
+        );
+
+        Ok(account_id)
+    }
+
+    /////////////////// Action checks //////////////////////////////////////////
+
+    // Ensures there is no problem in announcing candidacy.
+    fn can_announce_candidacy(
+        origin: T::Origin,
+        membership_id: &T::MemberId,
+        staking_account_id: &T::AccountId,
+        stake: &Balance<T>,
+    ) -> Result<(CouncilStageAnnouncing, Option<T::AccountId>), Error<T>> {
+        // ensure user's membership
+        Self::ensure_user_membership(origin, membership_id)?;
+
+        // ensure staking account's membership
+        if !T::StakingAccountValidator::is_member_staking_account(
+            &membership_id,
+            &staking_account_id,
+        ) {
+            return Err(Error::MemberIdNotMatchAccount);
+        }
+
+        // ensure there are no conflicting stake types for the account
+        if !T::CandidacyLock::is_account_free_of_conflicting_stakes(&staking_account_id) {
+            return Err(Error::ConflictingStake);
+        }
+
+        let stage_data = match Stage::<T>::get().stage {
+            CouncilStage::Announcing(stage_data) => stage_data,
+            _ => return Err(Error::CantCandidateNow),
+        };
+
+        // when previous candidacy record is present, ensure user is not candidating twice &
+        // prepare old stake for unlocking
+        let mut existing_staking_account_id = None;
+        if Candidates::<T>::contains_key(membership_id) {
+            let candidate = Candidates::<T>::get(membership_id);
+
+            // prevent user from candidating twice in the same election
+            if candidate.cycle_id == AnnouncementPeriodNr::get() {
+                return Err(Error::CantCandidateTwice);
+            }
+
+            // remember old staking account
+            existing_staking_account_id = Some(candidate.staking_account_id);
+        }
+
+        // ensure stake is above minimal threshold
+        if stake < &T::MinCandidateStake::get() {
+            return Err(Error::CandidacyStakeTooLow);
+        }
+
+        // ensure user has enough balance - includes any already locked candidacy stake as it will
+        // be reused
+        if !T::CandidacyLock::is_enough_balance_for_stake(&staking_account_id, *stake) {
+            return Err(Error::InsufficientBalanceForStaking);
+        }
+
+        Ok((stage_data, existing_staking_account_id))
+    }
+
+    // Ensures there is no problem in releasing old candidacy stake.
+    fn can_release_candidacy_stake(
+        origin: T::Origin,
+        membership_id: &T::MemberId,
+    ) -> Result<T::AccountId, Error<T>> {
+        // ensure user's membership
+        Self::ensure_user_membership(origin, membership_id)?;
+
+        // escape when no previous candidacy stake is present
+        if !Candidates::<T>::contains_key(membership_id) {
+            return Err(Error::NoStake);
+        }
+
+        let candidate = Candidates::<T>::get(membership_id);
+
+        // prevent user from releasing candidacy stake during election
+        if candidate.cycle_id == AnnouncementPeriodNr::get()
+            && !matches!(Stage::<T>::get().stage, CouncilStage::Idle)
+        {
+            return Err(Error::StakeStillNeeded);
+        }
+
+        Ok(candidate.staking_account_id)
+    }
+
+    // Ensures there is no problem in withdrawing already announced candidacy.
+    fn can_withdraw_candidacy(
+        origin: T::Origin,
+        membership_id: &T::MemberId,
+    ) -> Result<T::AccountId, Error<T>> {
+        // ensure user's membership
+        Self::ensure_user_membership(origin, membership_id)?;
+
+        // escape when no previous candidacy stake is present
+        if !Candidates::<T>::contains_key(membership_id) {
+            return Err(Error::NotCandidatingNow);
+        }
+
+        let candidate = Candidates::<T>::get(membership_id);
+
+        // ensure candidacy announcing period is running now
+        match Stage::<T>::get().stage {
+            CouncilStage::Announcing(_) => {
+                // ensure candidacy was announced in current election cycle
+                if candidate.cycle_id != AnnouncementPeriodNr::get() {
+                    return Err(Error::NotCandidatingNow);
+                }
+            }
+            _ => return Err(Error::CantWithdrawCandidacyNow),
+        };
+
+        Ok(candidate.staking_account_id)
+    }
+
+    // Ensures there is no problem in setting new note for the candidacy.
+    fn can_set_candidacy_note(
+        origin: T::Origin,
+        membership_id: &T::MemberId,
+    ) -> Result<(), Error<T>> {
+        // ensure user's membership
+        Self::ensure_user_membership(origin, membership_id)?;
+
+        // escape when no previous candidacy stake is present
+        if !Candidates::<T>::contains_key(membership_id) {
+            return Err(Error::NotCandidatingNow);
+        }
+
+        let candidate = Candidates::<T>::get(membership_id);
+
+        // ensure candidacy was announced in current election cycle
+        if candidate.cycle_id != AnnouncementPeriodNr::get() {
+            return Err(Error::NotCandidatingNow);
+        }
+
+        // ensure election hasn't ended yet
+        if let CouncilStage::Idle = Stage::<T>::get().stage {
+            return Err(Error::NotCandidatingNow);
+        }
+
+        Ok(())
+    }
+
+    // Ensures there is no problem in setting the budget balance.
+    fn can_set_budget(origin: T::Origin) -> Result<(), Error<T>> {
+        ensure_root(origin)?;
+
+        Ok(())
+    }
+
+    // Ensures there is no problem in planning next budget refill.
+    fn can_plan_budget_refill(origin: T::Origin) -> Result<(), Error<T>> {
+        ensure_root(origin)?;
+
+        Ok(())
+    }
+}

+ 1184 - 0
runtime-modules/council/src/mock.rs

@@ -0,0 +1,1184 @@
+#![cfg(test)]
+
+/////////////////// Configuration //////////////////////////////////////////////
+use crate::{
+    AnnouncementPeriodNr, Balance, Budget, CandidateOf, Candidates, CouncilMemberOf,
+    CouncilMembers, CouncilStage, CouncilStageAnnouncing, CouncilStageElection, CouncilStageUpdate,
+    CouncilStageUpdateOf, Error, GenesisConfig, Module, NextBudgetRefill, RawEvent,
+    ReferendumConnection, Stage, Trait,
+};
+
+use balances;
+use frame_support::dispatch::DispatchResult;
+use frame_support::traits::{Currency, Get, LockIdentifier, OnFinalize};
+use frame_support::{
+    impl_outer_event, impl_outer_origin, parameter_types, StorageMap, StorageValue,
+};
+use frame_system::{EnsureOneOf, EnsureRoot, EnsureSigned, RawOrigin};
+use rand::Rng;
+use referendum::{
+    Balance as BalanceReferendum, CastVote, OptionResult, ReferendumManager, ReferendumStage,
+    ReferendumStageRevealing,
+};
+use sp_core::H256;
+use sp_io;
+use sp_runtime::traits::Hash;
+use sp_runtime::{
+    testing::Header,
+    traits::{BlakeTwo256, IdentityLookup},
+    Perbill,
+};
+use staking_handler::{LockComparator, StakingManager};
+use std::boxed::Box;
+use std::cell::RefCell;
+use std::collections::BTreeMap;
+use std::marker::PhantomData;
+
+pub const USER_REGULAR_POWER_VOTES: u64 = 0;
+
+pub const POWER_VOTE_STRENGTH: u64 = 10;
+
+// uncomment this when this is moved back here from staking_handler.rs temporary file
+pub const VOTER_BASE_ID: u64 = 4000;
+pub const CANDIDATE_BASE_ID: u64 = VOTER_BASE_ID + VOTER_CANDIDATE_OFFSET;
+pub const VOTER_CANDIDATE_OFFSET: u64 = 1000;
+
+pub const INVALID_USER_MEMBER: u64 = 9999;
+
+// multiplies topup value so that candidate/voter can candidate/vote multiple times
+pub const TOPUP_MULTIPLIER: u64 = 10;
+
+/////////////////// Runtime and Instances //////////////////////////////////////
+// Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted.
+#[derive(Clone, PartialEq, Eq, Debug)]
+pub struct Runtime;
+
+thread_local! {
+    // new council elected recieved by `new_council_elected hook`
+    pub static LAST_COUNCIL_ELECTED_OK: RefCell<(bool, )> = RefCell::new((false, ));
+}
+
+parameter_types! {
+    pub const MinNumberOfExtraCandidates: u64 = 1;
+    pub const AnnouncingPeriodDuration: u64 = 15;
+    pub const IdlePeriodDuration: u64 = 27;
+    pub const CouncilSize: u64 = 3;
+    pub const MinCandidateStake: u64 = 11000;
+    pub const CandidacyLockId: LockIdentifier = *b"council1";
+    pub const CouncilorLockId: LockIdentifier = *b"council2";
+    pub const ElectedMemberRewardPerBlock: u64 = 100;
+    pub const ElectedMemberRewardPeriod: u64 = 10;
+    pub const BudgetRefillAmount: u64 = 1000;
+    // intentionally high number that prevents side-effecting tests other than  budget refill tests
+    pub const BudgetRefillPeriod: u64 = 1000;
+}
+
+impl common::Trait for Runtime {
+    type MemberId = u64;
+    type ActorId = u64;
+}
+
+impl Trait for Runtime {
+    type Event = TestEvent;
+
+    type Referendum = referendum::Module<Runtime, ReferendumInstance>;
+
+    type MinNumberOfExtraCandidates = MinNumberOfExtraCandidates;
+    type CouncilSize = CouncilSize;
+    type AnnouncingPeriodDuration = AnnouncingPeriodDuration;
+    type IdlePeriodDuration = IdlePeriodDuration;
+    type MinCandidateStake = MinCandidateStake;
+
+    type CandidacyLock = StakingManager<Self, CandidacyLockId>;
+    type CouncilorLock = StakingManager<Self, CouncilorLockId>;
+
+    type ElectedMemberRewardPerBlock = ElectedMemberRewardPerBlock;
+    type ElectedMemberRewardPeriod = ElectedMemberRewardPeriod;
+
+    type StakingAccountValidator = ();
+
+    type BudgetRefillAmount = BudgetRefillAmount;
+    type BudgetRefillPeriod = BudgetRefillPeriod;
+
+    fn is_council_member_account(
+        membership_id: &Self::MemberId,
+        account_id: &<Self as frame_system::Trait>::AccountId,
+    ) -> bool {
+        membership_id == account_id
+    }
+
+    fn new_council_elected(elected_members: &[CouncilMemberOf<Self>]) {
+        let is_ok = elected_members == CouncilMembers::<Runtime>::get();
+
+        LAST_COUNCIL_ELECTED_OK.with(|value| {
+            *value.borrow_mut() = (is_ok,);
+        });
+    }
+}
+
+impl common::StakingAccountValidator<Runtime> for () {
+    fn is_member_staking_account(_: &u64, _: &u64) -> bool {
+        true
+    }
+}
+
+/////////////////// Module implementation //////////////////////////////////////
+
+impl_outer_origin! {
+    pub enum Origin for Runtime {}
+}
+
+mod event_mod {
+    pub use crate::Event;
+}
+
+mod referendum_mod {
+    pub use referendum::Event;
+    pub use referendum::Instance0;
+}
+
+mod membership_mod {
+    pub use membership::Event;
+}
+
+impl_outer_event! {
+    pub enum TestEvent for Runtime {
+        event_mod<T>,
+        frame_system<T>,
+        referendum_mod Instance0 <T>,
+        balances_mod<T>,
+        membership_mod<T>,
+    }
+}
+
+parameter_types! {
+    pub const BlockHashCount: u64 = 250;
+    pub const MaximumBlockWeight: u32 = 1024;
+    pub const MaximumBlockLength: u32 = 2 * 1024;
+    pub const AvailableBlockRatio: Perbill = Perbill::one();
+}
+
+impl frame_system::Trait for Runtime {
+    type BaseCallFilter = ();
+    type Origin = Origin;
+    type Index = u64;
+    type BlockNumber = u64;
+    type Call = ();
+    type Hash = H256;
+    type Hashing = BlakeTwo256;
+    type AccountId = u64;
+    type Lookup = IdentityLookup<Self::AccountId>;
+    type Header = Header;
+    type Event = TestEvent;
+    type BlockHashCount = BlockHashCount;
+    type MaximumBlockWeight = MaximumBlockWeight;
+    type DbWeight = ();
+    type BlockExecutionWeight = ();
+    type ExtrinsicBaseWeight = ();
+    type MaximumExtrinsicWeight = ();
+    type MaximumBlockLength = MaximumBlockLength;
+    type AvailableBlockRatio = AvailableBlockRatio;
+    type Version = ();
+    type PalletInfo = ();
+    type AccountData = balances::AccountData<u64>;
+    type OnNewAccount = ();
+    type OnKilledAccount = ();
+    type SystemWeightInfo = ();
+}
+
+/////////////////// Election module ////////////////////////////////////////////
+
+pub type ReferendumInstance = referendum::Instance0;
+
+thread_local! {
+    // global switch for stake locking features; use it to simulate lock fails
+    pub static IS_UNSTAKE_ENABLED: RefCell<(bool, )> = RefCell::new((true, ));
+
+    // global switch used to test is_valid_option_id()
+    pub static IS_OPTION_ID_VALID: RefCell<(bool, )> = RefCell::new((true, ));
+}
+
+parameter_types! {
+    pub const VoteStageDuration: u64 = 19;
+    pub const RevealStageDuration: u64 = 23;
+    pub const MinimumVotingStake: u64 = 10000;
+    pub const MaxSaltLength: u64 = 32; // use some multiple of 8 for ez testing
+    pub const VotingLockId: LockIdentifier = *b"referend";
+    pub const DefaultMembershipPrice: u64 = 100;
+    pub const DefaultInitialInvitationBalance: u64 = 100;
+    pub const MinimumPeriod: u64 = 5;
+}
+
+mod balances_mod {
+    pub use balances::Event;
+}
+
+impl referendum::Trait<ReferendumInstance> for Runtime {
+    type Event = TestEvent;
+
+    type MaxSaltLength = MaxSaltLength;
+
+    type Currency = balances::Module<Self>;
+    type LockId = VotingLockId;
+
+    type ManagerOrigin =
+        EnsureOneOf<Self::AccountId, EnsureSigned<Self::AccountId>, EnsureRoot<Self::AccountId>>;
+
+    type VotePower = u64;
+
+    type VoteStageDuration = VoteStageDuration;
+    type RevealStageDuration = RevealStageDuration;
+
+    type MinimumStake = MinimumVotingStake;
+
+    fn calculate_vote_power(
+        account_id: &<Self as frame_system::Trait>::AccountId,
+        stake: &BalanceReferendum<Self, ReferendumInstance>,
+    ) -> Self::VotePower {
+        let stake: u64 = u64::from(*stake);
+        if *account_id == USER_REGULAR_POWER_VOTES {
+            return stake * POWER_VOTE_STRENGTH;
+        }
+
+        stake
+    }
+
+    fn can_unlock_vote_stake(
+        vote: &CastVote<Self::Hash, BalanceReferendum<Self, ReferendumInstance>, Self::MemberId>,
+    ) -> bool {
+        // trigger fail when requested to do so
+        if !IS_UNSTAKE_ENABLED.with(|value| value.borrow().0) {
+            return false;
+        }
+
+        <Module<Runtime> as ReferendumConnection<Runtime>>::can_unlock_vote_stake(vote).is_ok()
+    }
+
+    fn process_results(winners: &[OptionResult<Self::MemberId, Self::VotePower>]) {
+        let tmp_winners: Vec<OptionResult<Self::MemberId, Self::VotePower>> = winners
+            .iter()
+            .map(|item| OptionResult {
+                option_id: item.option_id,
+                vote_power: item.vote_power.into(),
+            })
+            .collect();
+        <Module<Runtime> as ReferendumConnection<Runtime>>::recieve_referendum_results(
+            tmp_winners.as_slice(),
+        );
+    }
+
+    fn is_valid_option_id(option_index: &u64) -> bool {
+        if !IS_OPTION_ID_VALID.with(|value| value.borrow().0) {
+            return false;
+        }
+
+        <Module<Runtime> as ReferendumConnection<Runtime>>::is_valid_candidate_id(option_index)
+    }
+
+    fn get_option_power(option_id: &u64) -> Self::VotePower {
+        <Module<Runtime> as ReferendumConnection<Runtime>>::get_option_power(option_id)
+    }
+
+    fn increase_option_power(option_id: &u64, amount: &Self::VotePower) {
+        <Module<Runtime> as ReferendumConnection<Runtime>>::increase_option_power(
+            option_id, amount,
+        );
+    }
+}
+
+impl balances::Trait for Runtime {
+    type Balance = u64;
+    type Event = TestEvent;
+    type DustRemoval = ();
+    type ExistentialDeposit = ExistentialDeposit;
+    type AccountStore = frame_system::Module<Self>;
+    type WeightInfo = ();
+    type MaxLocks = MaxLocks;
+}
+
+impl membership::Trait for Runtime {
+    type Event = TestEvent;
+    type DefaultMembershipPrice = DefaultMembershipPrice;
+    type WorkingGroup = ();
+    type DefaultInitialInvitationBalance = DefaultInitialInvitationBalance;
+}
+
+impl common::working_group::WorkingGroupIntegration<Runtime> for () {
+    fn ensure_worker_origin(
+        _origin: <Runtime as frame_system::Trait>::Origin,
+        _worker_id: &<Runtime as common::Trait>::ActorId,
+    ) -> DispatchResult {
+        unimplemented!();
+    }
+
+    fn get_leader_member_id() -> Option<<Runtime as common::Trait>::MemberId> {
+        unimplemented!();
+    }
+}
+
+impl pallet_timestamp::Trait for Runtime {
+    type Moment = u64;
+    type OnTimestampSet = ();
+    type MinimumPeriod = MinimumPeriod;
+    type WeightInfo = ();
+}
+
+impl Runtime {
+    pub fn _feature_option_id_valid(is_valid: bool) -> () {
+        IS_OPTION_ID_VALID.with(|value| {
+            *value.borrow_mut() = (is_valid,);
+        });
+    }
+}
+
+parameter_types! {
+    pub const ExistentialDeposit: u64 = 0;
+    pub const MaxLocks: u32 = 50;
+}
+
+impl LockComparator<<Runtime as balances::Trait>::Balance> for Runtime {
+    fn are_locks_conflicting(
+        _new_lock: &LockIdentifier,
+        _existing_locks: &[LockIdentifier],
+    ) -> bool {
+        false
+    }
+}
+
+/////////////////// Data structures ////////////////////////////////////////////
+
+#[allow(dead_code)]
+#[derive(Clone)]
+pub enum OriginType<AccountId> {
+    Signed(AccountId),
+    //Inherent, <== did not find how to make such an origin yet
+    Root,
+}
+
+#[derive(Clone)]
+pub struct CandidateInfo<T: Trait> {
+    pub origin: OriginType<T::AccountId>,
+    pub account_id: T::MemberId,
+    pub membership_id: T::MemberId,
+    pub candidate: CandidateOf<T>,
+}
+
+#[derive(Clone)]
+pub struct VoterInfo<T: Trait> {
+    pub origin: OriginType<T::AccountId>,
+    pub account_id: T::AccountId,
+    pub commitment: T::Hash,
+    pub salt: Vec<u8>,
+    pub vote_for: u64,
+    pub stake: Balance<T>,
+}
+
+#[derive(Clone)]
+pub struct CouncilSettings<T: Trait> {
+    pub council_size: u64,
+    pub min_candidate_count: u64,
+    pub min_candidate_stake: Balance<T>,
+    pub announcing_stage_duration: T::BlockNumber,
+    pub voting_stage_duration: T::BlockNumber,
+    pub reveal_stage_duration: T::BlockNumber,
+    pub idle_stage_duration: T::BlockNumber,
+    pub election_duration: T::BlockNumber,
+    pub cycle_duration: T::BlockNumber,
+    pub budget_refill_amount: Balance<T>,
+    pub budget_refill_period: T::BlockNumber,
+}
+
+impl<T: Trait> CouncilSettings<T>
+where
+    T::BlockNumber: From<u64>,
+{
+    pub fn extract_settings() -> CouncilSettings<T> {
+        let council_size = T::CouncilSize::get();
+
+        let reveal_stage_duration =
+            <Runtime as referendum::Trait<ReferendumInstance>>::RevealStageDuration::get().into();
+        let announcing_stage_duration = <T as Trait>::AnnouncingPeriodDuration::get();
+        let voting_stage_duration =
+            <Runtime as referendum::Trait<ReferendumInstance>>::VoteStageDuration::get().into();
+        let idle_stage_duration = <T as Trait>::IdlePeriodDuration::get();
+
+        CouncilSettings {
+            council_size,
+            min_candidate_count: council_size + <T as Trait>::MinNumberOfExtraCandidates::get(),
+            min_candidate_stake: T::MinCandidateStake::get(),
+            announcing_stage_duration,
+            voting_stage_duration,
+            reveal_stage_duration,
+            idle_stage_duration: <T as Trait>::IdlePeriodDuration::get(),
+
+            election_duration: reveal_stage_duration
+                + announcing_stage_duration
+                + voting_stage_duration,
+            cycle_duration: reveal_stage_duration
+                + announcing_stage_duration
+                + voting_stage_duration
+                + idle_stage_duration,
+
+            budget_refill_amount: <T as Trait>::BudgetRefillAmount::get(),
+            budget_refill_period: <T as Trait>::BudgetRefillPeriod::get(),
+        }
+    }
+}
+
+#[derive(Clone, PartialEq, Debug)]
+pub enum CouncilCycleInterrupt {
+    BeforeCandidatesAnnounce,
+    AfterCandidatesAnnounce,
+    BeforeVoting,
+    AfterVoting,
+    BeforeRevealing,
+    AfterRevealing,
+}
+
+#[derive(Clone)]
+pub struct CouncilCycleParams<T: Trait> {
+    pub council_settings: CouncilSettings<T>,
+    pub cycle_start_block_number: T::BlockNumber,
+
+    // council members
+    pub expected_initial_council_members: Vec<CouncilMemberOf<T>>,
+
+    // council members after cycle finishes
+    pub expected_final_council_members: Vec<CouncilMemberOf<T>>,
+
+    // candidates announcing their candidacy
+    pub candidates_announcing: Vec<CandidateInfo<T>>,
+
+    // expected list of candidates after announcement period is over
+    pub expected_candidates: Vec<CandidateOf<T>>,
+
+    // voters that will participate in council voting
+    pub voters: Vec<VoterInfo<T>>,
+
+    // info about when should be cycle interrupted (used to customize the test)
+    pub interrupt_point: Option<CouncilCycleInterrupt>,
+}
+
+/////////////////// Util macros ////////////////////////////////////////////////
+macro_rules! escape_checkpoint {
+    ($item:expr, $expected_value:expr) => {
+        if $item == $expected_value {
+            return;
+        }
+    };
+    ($item:expr, $expected_value:expr, $return_value:expr) => {
+        if $item == $expected_value {
+            return $c;
+        }
+    };
+}
+
+/////////////////// Utility mocks //////////////////////////////////////////////
+
+pub fn default_genesis_config() -> GenesisConfig<Runtime> {
+    GenesisConfig::<Runtime> {
+        stage: CouncilStageUpdate::default(),
+        council_members: vec![],
+        candidates: vec![],
+        announcement_period_nr: 0,
+        budget: 0,
+        next_reward_payments: 0,
+        next_budget_refill: <Runtime as Trait>::BudgetRefillPeriod::get(),
+    }
+}
+
+pub fn build_test_externalities(config: GenesisConfig<Runtime>) -> sp_io::TestExternalities {
+    let mut t = frame_system::GenesisConfig::default()
+        .build_storage::<Runtime>()
+        .unwrap();
+
+    config.assimilate_storage(&mut t).unwrap();
+
+    let mut result = Into::<sp_io::TestExternalities>::into(t.clone());
+
+    // Make sure we are not in block 1 where no events are emitted
+    // see https://substrate.dev/recipes/2-appetizers/4-events.html#emitting-events
+    result.execute_with(|| InstanceMockUtils::<Runtime>::increase_block_number(1));
+
+    result
+}
+
+pub struct InstanceMockUtils<T: Trait> {
+    _dummy: PhantomData<T>, // 0-sized data meant only to bound generic parameters
+}
+
+impl<T: Trait> InstanceMockUtils<T>
+where
+    T::AccountId: From<u64>,
+    T::MemberId: From<u64>,
+    T::BlockNumber: From<u64> + Into<u64>,
+    Balance<T>: From<u64> + Into<u64>,
+{
+    pub fn mock_origin(origin: OriginType<T::AccountId>) -> T::Origin {
+        match origin {
+            OriginType::Signed(account_id) => T::Origin::from(RawOrigin::Signed(account_id)),
+            OriginType::Root => RawOrigin::Root.into(),
+            //_ => panic!("not implemented"),
+        }
+    }
+
+    pub fn increase_block_number(increase: u64) -> () {
+        let block_number = frame_system::Module::<T>::block_number();
+
+        for i in 0..increase {
+            let tmp_index: T::BlockNumber = block_number + i.into();
+
+            <Module<T> as OnFinalize<T::BlockNumber>>::on_finalize(tmp_index);
+            <referendum::Module<Runtime, ReferendumInstance> as OnFinalize<
+                <Runtime as frame_system::Trait>::BlockNumber,
+            >>::on_finalize(tmp_index.into());
+
+            frame_system::Module::<T>::set_block_number(tmp_index + 1.into());
+        }
+    }
+
+    // topup currency to the account
+    fn topup_account(account_id: u64, amount: Balance<T>) {
+        let _ = balances::Module::<Runtime>::deposit_creating(&account_id, amount.into());
+    }
+
+    pub fn generate_candidate(index: u64, stake: Balance<T>) -> CandidateInfo<T> {
+        let account_id = CANDIDATE_BASE_ID + index;
+        let origin = OriginType::Signed(account_id.into());
+        let candidate = CandidateOf::<T> {
+            staking_account_id: account_id.into(),
+            reward_account_id: account_id.into(),
+            cycle_id: AnnouncementPeriodNr::get(),
+            stake,
+            vote_power: 0.into(),
+            note_hash: None,
+        };
+
+        Self::topup_account(account_id.into(), stake * TOPUP_MULTIPLIER.into());
+
+        CandidateInfo {
+            origin,
+            candidate,
+            membership_id: account_id.into(),
+            account_id: account_id.into(),
+        }
+    }
+
+    pub fn generate_voter(
+        index: u64,
+        stake: Balance<T>,
+        vote_for_index: u64,
+        cycle_id: u64,
+    ) -> VoterInfo<T> {
+        let account_id = VOTER_BASE_ID + index;
+        let origin = OriginType::Signed(account_id.into());
+        let (commitment, salt) =
+            Self::vote_commitment(&account_id.into(), &vote_for_index.into(), &cycle_id);
+
+        Self::topup_account(account_id.into(), stake);
+
+        VoterInfo {
+            origin,
+            account_id: account_id.into(),
+            commitment,
+            salt,
+            vote_for: vote_for_index,
+            stake,
+        }
+    }
+
+    pub fn generate_salt() -> Vec<u8> {
+        let mut rng = rand::thread_rng();
+
+        rng.gen::<u64>().to_be_bytes().to_vec()
+    }
+
+    pub fn vote_commitment(
+        account_id: &<T as frame_system::Trait>::AccountId,
+        vote_option_index: &<T as common::Trait>::MemberId,
+        cycle_id: &u64,
+    ) -> (T::Hash, Vec<u8>) {
+        let salt = Self::generate_salt();
+
+        (
+            T::Referendum::calculate_commitment(account_id, &salt, &cycle_id, vote_option_index),
+            salt.to_vec(),
+        )
+    }
+}
+
+/////////////////// Mocks of Module's actions //////////////////////////////////
+
+pub struct InstanceMocks<T: Trait> {
+    _dummy: PhantomData<T>, // 0-sized data meant only to bound generic parameters
+}
+
+impl<T: Trait> InstanceMocks<T>
+where
+    T::AccountId: From<u64> + Into<u64>,
+    T::MemberId: From<u64> + Into<u64>,
+    T::BlockNumber: From<u64> + Into<u64>,
+    Balance<T>: From<u64> + Into<u64>,
+
+    T::Hash:
+        From<<Runtime as frame_system::Trait>::Hash> + Into<<Runtime as frame_system::Trait>::Hash>,
+    T::Origin: From<<Runtime as frame_system::Trait>::Origin>
+        + Into<<Runtime as frame_system::Trait>::Origin>,
+    <T::Referendum as ReferendumManager<T::Origin, T::AccountId, T::MemberId, T::Hash>>::VotePower:
+        From<u64> + Into<u64>,
+    T::MemberId: Into<T::AccountId>,
+{
+    pub fn check_announcing_period(
+        expected_update_block_number: T::BlockNumber,
+        expected_state: CouncilStageAnnouncing,
+    ) -> () {
+        // check stage is in proper state
+        assert_eq!(
+            Stage::<T>::get(),
+            CouncilStageUpdateOf::<T> {
+                stage: CouncilStage::Announcing(expected_state),
+                changed_at: expected_update_block_number,
+            },
+        );
+    }
+
+    pub fn check_election_period(
+        expected_update_block_number: T::BlockNumber,
+        expected_state: CouncilStageElection,
+    ) -> () {
+        // check stage is in proper state
+        assert_eq!(
+            Stage::<T>::get(),
+            CouncilStageUpdateOf::<T> {
+                stage: CouncilStage::Election(expected_state),
+                changed_at: expected_update_block_number,
+            },
+        );
+    }
+
+    pub fn check_idle_period(expected_update_block_number: T::BlockNumber) -> () {
+        // check stage is in proper state
+        assert_eq!(
+            Stage::<T>::get(),
+            CouncilStageUpdateOf::<T> {
+                stage: CouncilStage::Idle,
+                changed_at: expected_update_block_number,
+            },
+        );
+    }
+
+    pub fn check_council_members(expect_members: Vec<CouncilMemberOf<T>>) -> () {
+        // check stage is in proper state
+        assert_eq!(CouncilMembers::<T>::get(), expect_members,);
+    }
+
+    pub fn check_referendum_revealing(
+        //        candidate_count: u64,
+        winning_target_count: u64,
+        intermediate_winners: Vec<
+            OptionResult<
+                T::MemberId,
+                <T::Referendum as ReferendumManager<
+                    T::Origin,
+                    T::AccountId,
+                    T::MemberId,
+                    T::Hash,
+                >>::VotePower,
+            >,
+        >,
+        intermediate_results: BTreeMap<
+            u64,
+            <T::Referendum as ReferendumManager<T::Origin, T::AccountId, T::MemberId, T::Hash>>::VotePower,
+        >,
+        expected_update_block_number: T::BlockNumber,
+    ) {
+        // check stage is in proper state
+        assert_eq!(
+            referendum::Stage::<Runtime, ReferendumInstance>::get(),
+            ReferendumStage::Revealing(ReferendumStageRevealing {
+                winning_target_count,
+                started: expected_update_block_number.into(),
+                intermediate_winners: intermediate_winners
+                    .iter()
+                    .map(|item| OptionResult {
+                        option_id: <T::MemberId as Into<u64>>::into(item.option_id),
+                        vote_power: item.vote_power.into(),
+                    })
+                    .collect(),
+                current_cycle_id: AnnouncementPeriodNr::get(),
+            }),
+        );
+
+        // check intermediate results
+        for (key, value) in intermediate_results {
+            let membership_id: T::MemberId = key.into();
+
+            assert!(Candidates::<T>::contains_key(membership_id));
+            assert_eq!(Candidates::<T>::get(membership_id).vote_power, value);
+        }
+    }
+
+    pub fn check_announcing_stake(membership_id: &T::MemberId, amount: Balance<T>) {
+        assert_eq!(Candidates::<T>::contains_key(membership_id), true);
+
+        assert_eq!(Candidates::<T>::get(membership_id).stake, amount);
+    }
+
+    pub fn check_candidacy_note(membership_id: &T::MemberId, note: Option<&[u8]>) {
+        assert_eq!(Candidates::<T>::contains_key(membership_id), true);
+
+        let note_hash = match note {
+            Some(tmp_note) => Some(T::Hashing::hash(tmp_note)),
+            None => None,
+        };
+
+        assert_eq!(Candidates::<T>::get(membership_id).note_hash, note_hash,);
+    }
+
+    pub fn check_budget_refill(expected_balance: Balance<T>, expected_next_refill: T::BlockNumber) {
+        assert_eq!(Budget::<T>::get(), expected_balance,);
+        assert_eq!(NextBudgetRefill::<T>::get(), expected_next_refill,);
+    }
+
+    pub fn check_new_council_elected_hook() {
+        LAST_COUNCIL_ELECTED_OK.with(|value| assert!(value.borrow().0))
+    }
+
+    pub fn set_candidacy_note(
+        origin: OriginType<T::AccountId>,
+        membership_id: T::MemberId,
+        note: &[u8],
+        expected_result: Result<(), Error<T>>,
+    ) {
+        // check method returns expected result
+        assert_eq!(
+            Module::<T>::set_candidacy_note(
+                InstanceMockUtils::<T>::mock_origin(origin),
+                membership_id,
+                note.to_vec()
+            ),
+            expected_result,
+        );
+
+        if expected_result.is_err() {
+            return;
+        }
+        assert_eq!(
+            frame_system::Module::<Runtime>::events()
+                .last()
+                .unwrap()
+                .event,
+            TestEvent::event_mod(RawEvent::CandidacyNoteSet(
+                membership_id.into(),
+                note.into()
+            )),
+        );
+
+        Self::check_candidacy_note(&membership_id, Some(note));
+    }
+
+    pub fn announce_candidacy(
+        origin: OriginType<T::AccountId>,
+        member_id: T::MemberId,
+        stake: Balance<T>,
+        expected_result: Result<(), Error<T>>,
+    ) {
+        // use member id as staking and reward accounts
+        Self::announce_candidacy_raw(
+            origin,
+            member_id,
+            member_id.into(),
+            member_id.into(),
+            stake,
+            expected_result,
+        );
+    }
+
+    pub fn announce_candidacy_raw(
+        origin: OriginType<T::AccountId>,
+        member_id: T::MemberId,
+        staking_account_id: T::AccountId,
+        reward_account_id: T::AccountId,
+        stake: Balance<T>,
+        expected_result: Result<(), Error<T>>,
+    ) {
+        // check method returns expected result
+        assert_eq!(
+            Module::<T>::announce_candidacy(
+                InstanceMockUtils::<T>::mock_origin(origin),
+                member_id,
+                staking_account_id,
+                reward_account_id,
+                stake
+            ),
+            expected_result,
+        );
+
+        if expected_result.is_err() {
+            return;
+        }
+
+        assert_eq!(
+            frame_system::Module::<Runtime>::events()
+                .last()
+                .unwrap()
+                .event,
+            TestEvent::event_mod(RawEvent::NewCandidate(member_id.into(), stake.into())),
+        );
+    }
+
+    pub fn withdraw_candidacy(
+        origin: OriginType<T::AccountId>,
+        member_id: T::MemberId,
+        expected_result: Result<(), Error<T>>,
+    ) {
+        // check method returns expected result
+        assert_eq!(
+            Module::<T>::withdraw_candidacy(InstanceMockUtils::<T>::mock_origin(origin), member_id,),
+            expected_result,
+        );
+
+        if expected_result.is_err() {
+            return;
+        }
+
+        assert_eq!(
+            frame_system::Module::<Runtime>::events()
+                .last()
+                .unwrap()
+                .event,
+            TestEvent::event_mod(RawEvent::CandidacyWithdraw(member_id.into(),)),
+        );
+    }
+
+    pub fn release_candidacy_stake(
+        origin: OriginType<T::AccountId>,
+        member_id: T::MemberId,
+        expected_result: Result<(), Error<T>>,
+    ) {
+        // check method returns expected result
+        assert_eq!(
+            Module::<T>::release_candidacy_stake(
+                InstanceMockUtils::<T>::mock_origin(origin),
+                member_id,
+            ),
+            expected_result,
+        );
+
+        if expected_result.is_err() {
+            return;
+        }
+
+        assert_eq!(
+            frame_system::Module::<Runtime>::events()
+                .last()
+                .unwrap()
+                .event,
+            TestEvent::event_mod(RawEvent::CandidacyStakeRelease(member_id.into(),)),
+        );
+    }
+
+    pub fn vote_for_candidate(
+        origin: OriginType<T::AccountId>,
+        commitment: T::Hash,
+        stake: Balance<T>,
+        expected_result: Result<(), ()>,
+    ) -> () {
+        // check method returns expected result
+        assert_eq!(
+            referendum::Module::<Runtime, ReferendumInstance>::vote(
+                InstanceMockUtils::<T>::mock_origin(origin).into(),
+                commitment.into(),
+                stake.into(),
+            )
+            .is_ok(),
+            expected_result.is_ok(),
+        );
+    }
+
+    pub fn reveal_vote(
+        origin: OriginType<T::AccountId>,
+        salt: Vec<u8>,
+        vote_option: u64,
+        //expected_result: Result<(), referendum::Error<T, ReferendumInstance>>,
+        expected_result: Result<(), ()>,
+    ) -> () {
+        // check method returns expected result
+        assert_eq!(
+            referendum::Module::<Runtime, ReferendumInstance>::reveal_vote(
+                InstanceMockUtils::<T>::mock_origin(origin).into(),
+                salt,
+                vote_option,
+            )
+            .is_ok(),
+            expected_result.is_ok(),
+        );
+    }
+
+    pub fn release_vote_stake(
+        origin: OriginType<<Runtime as frame_system::Trait>::AccountId>,
+        expected_result: Result<(), ()>,
+    ) -> () {
+        // check method returns expected result
+        assert_eq!(
+            referendum::Module::<Runtime, ReferendumInstance>::release_vote_stake(
+                InstanceMockUtils::<Runtime>::mock_origin(origin),
+            )
+            .is_ok(),
+            expected_result.is_ok(),
+        );
+    }
+
+    pub fn set_budget(
+        origin: OriginType<T::AccountId>,
+        amount: Balance<T>,
+        expected_result: Result<(), ()>,
+    ) {
+        // check method returns expected result
+        assert_eq!(
+            Module::<T>::set_budget(InstanceMockUtils::<T>::mock_origin(origin), amount,).is_ok(),
+            expected_result.is_ok(),
+        );
+
+        if expected_result.is_err() {
+            return;
+        }
+
+        assert_eq!(Budget::<T>::get(), amount,);
+
+        assert_eq!(
+            frame_system::Module::<Runtime>::events()
+                .last()
+                .unwrap()
+                .event,
+            TestEvent::event_mod(RawEvent::BudgetBalanceSet(amount.into())),
+        );
+    }
+
+    pub fn plan_budget_refill(
+        origin: OriginType<T::AccountId>,
+        next_refill: T::BlockNumber,
+        expected_result: Result<(), ()>,
+    ) {
+        // check method returns expected result
+        assert_eq!(
+            Module::<T>::plan_budget_refill(
+                InstanceMockUtils::<T>::mock_origin(origin),
+                next_refill,
+            )
+            .is_ok(),
+            expected_result.is_ok(),
+        );
+
+        if expected_result.is_err() {
+            return;
+        }
+
+        assert_eq!(NextBudgetRefill::<T>::get(), next_refill,);
+
+        assert_eq!(
+            frame_system::Module::<Runtime>::events()
+                .last()
+                .unwrap()
+                .event,
+            TestEvent::event_mod(RawEvent::BudgetRefillPlanned(next_refill.into())),
+        );
+    }
+
+    // simulate one council's election cycle
+    pub fn simulate_council_cycle(params: CouncilCycleParams<T>) {
+        let settings = params.council_settings;
+
+        // check initial council members
+        Self::check_council_members(params.expected_initial_council_members.clone());
+
+        // start announcing
+        Self::check_announcing_period(
+            params.cycle_start_block_number,
+            CouncilStageAnnouncing {
+                candidates_count: 0,
+            },
+        );
+
+        escape_checkpoint!(
+            params.interrupt_point.clone(),
+            Some(CouncilCycleInterrupt::BeforeCandidatesAnnounce)
+        );
+
+        // announce candidacy for each candidate
+        params.candidates_announcing.iter().for_each(|candidate| {
+            Self::announce_candidacy(
+                candidate.origin.clone(),
+                candidate.account_id.clone(),
+                settings.min_candidate_stake,
+                Ok(()),
+            );
+        });
+
+        escape_checkpoint!(
+            params.interrupt_point.clone(),
+            Some(CouncilCycleInterrupt::AfterCandidatesAnnounce)
+        );
+
+        // forward to election-voting period
+        InstanceMockUtils::<T>::increase_block_number(
+            settings.announcing_stage_duration.into() + 1,
+        );
+
+        // finish announcing period / start referendum -> will cause period prolongement
+        Self::check_election_period(
+            params.cycle_start_block_number + settings.announcing_stage_duration,
+            CouncilStageElection {
+                candidates_count: params.expected_candidates.len() as u64,
+            },
+        );
+
+        escape_checkpoint!(
+            params.interrupt_point.clone(),
+            Some(CouncilCycleInterrupt::BeforeVoting)
+        );
+
+        // vote with all voters
+        params.voters.iter().for_each(|voter| {
+            Self::vote_for_candidate(
+                voter.origin.clone(),
+                voter.commitment.clone(),
+                voter.stake.clone(),
+                Ok(()),
+            )
+        });
+
+        escape_checkpoint!(
+            params.interrupt_point.clone(),
+            Some(CouncilCycleInterrupt::AfterVoting)
+        );
+
+        // forward to election-revealing period
+        InstanceMockUtils::<T>::increase_block_number(settings.voting_stage_duration.into() + 1);
+
+        // referendum - start revealing period
+        Self::check_referendum_revealing(
+            settings.council_size,
+            vec![],
+            BTreeMap::new(), //<u64, T::VotePower>,
+            params.cycle_start_block_number
+                + settings.announcing_stage_duration
+                + settings.voting_stage_duration,
+        );
+
+        escape_checkpoint!(
+            params.interrupt_point.clone(),
+            Some(CouncilCycleInterrupt::BeforeRevealing)
+        );
+
+        // reveal vote for all voters
+        params.voters.iter().for_each(|voter| {
+            Self::reveal_vote(
+                voter.origin.clone(),
+                voter.salt.clone(),
+                voter.vote_for,
+                Ok(()),
+            );
+        });
+
+        escape_checkpoint!(
+            params.interrupt_point.clone(),
+            Some(CouncilCycleInterrupt::AfterRevealing)
+        );
+
+        // finish election / start idle period
+        InstanceMockUtils::<T>::increase_block_number(settings.reveal_stage_duration.into() + 1);
+        Self::check_idle_period(
+            params.cycle_start_block_number
+                + settings.reveal_stage_duration
+                + settings.announcing_stage_duration
+                + settings.voting_stage_duration,
+        );
+        Self::check_council_members(params.expected_final_council_members.clone());
+
+        // finish idle period
+        InstanceMockUtils::<T>::increase_block_number(settings.idle_stage_duration.into() + 1);
+    }
+
+    // Simulate one full round of council lifecycle (announcing, election, idle). Use it to
+    // quickly test behavior in 2nd, 3rd, etc. cycle.
+    pub fn run_full_council_cycle(
+        start_block_number: T::BlockNumber,
+        expected_initial_council_members: &[CouncilMemberOf<T>],
+        users_offset: u64,
+    ) -> CouncilCycleParams<T> {
+        let council_settings = CouncilSettings::<T>::extract_settings();
+        let vote_stake = <Runtime as referendum::Trait<ReferendumInstance>>::MinimumStake::get();
+
+        // generate candidates
+        let candidates: Vec<CandidateInfo<T>> = (0..(council_settings.min_candidate_count + 1)
+            as u64)
+            .map(|i| {
+                InstanceMockUtils::<T>::generate_candidate(
+                    u64::from(i) + users_offset,
+                    council_settings.min_candidate_stake,
+                )
+            })
+            .collect();
+
+        // prepare candidates that are expected to get into candidacy list
+        let expected_candidates = candidates
+            .iter()
+            .map(|item| item.candidate.clone())
+            .collect();
+
+        let expected_final_council_members: Vec<CouncilMemberOf<T>> = vec![
+            (
+                candidates[3].candidate.clone(),
+                candidates[3].membership_id,
+                start_block_number + council_settings.election_duration - 1.into(),
+                0.into(),
+            )
+                .into(),
+            (
+                candidates[0].candidate.clone(),
+                candidates[0].membership_id,
+                start_block_number + council_settings.election_duration - 1.into(),
+                0.into(),
+            )
+                .into(),
+            (
+                candidates[1].candidate.clone(),
+                candidates[1].membership_id,
+                start_block_number + council_settings.election_duration - 1.into(),
+                0.into(),
+            )
+                .into(),
+        ];
+
+        // generate voter for each 6 voters and give: 4 votes for option D, 3 votes for option A,
+        // and 2 vote for option B, and 1 for option C
+        let votes_map: Vec<u64> = vec![3, 3, 3, 3, 0, 0, 0, 1, 1, 2];
+        let voters = (0..votes_map.len())
+            .map(|index| {
+                InstanceMockUtils::<T>::generate_voter(
+                    index as u64 + users_offset,
+                    vote_stake.into(),
+                    CANDIDATE_BASE_ID + votes_map[index] + users_offset,
+                    AnnouncementPeriodNr::get(),
+                )
+            })
+            .collect();
+
+        let params = CouncilCycleParams {
+            council_settings: CouncilSettings::<T>::extract_settings(),
+            cycle_start_block_number: start_block_number,
+            expected_initial_council_members: expected_initial_council_members.to_vec(),
+            expected_final_council_members,
+            candidates_announcing: candidates.clone(),
+            expected_candidates,
+            voters,
+
+            interrupt_point: None,
+        };
+
+        InstanceMocks::<T>::simulate_council_cycle(params.clone());
+
+        params
+    }
+}

+ 1366 - 0
runtime-modules/council/src/tests.rs

@@ -0,0 +1,1366 @@
+#![cfg(test)]
+
+use super::{
+    AnnouncementPeriodNr, Budget, CouncilMemberOf, CouncilMembers, CouncilStageAnnouncing, Error,
+    Module, Trait,
+};
+use crate::mock::*;
+use frame_support::StorageValue;
+use staking_handler::StakingHandler;
+
+type Mocks = InstanceMocks<Runtime>;
+type MockUtils = InstanceMockUtils<Runtime>;
+
+type CandidacyLock = <Runtime as Trait>::CandidacyLock;
+type CouncilorLock = <Runtime as Trait>::CouncilorLock;
+
+/////////////////// Election-related ///////////////////////////////////////////
+// Test one referendum cycle with succesfull council election
+#[test]
+fn council_lifecycle() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        Mocks::run_full_council_cycle(0, &[], 0);
+    });
+}
+
+// Test that candidacy can be announced only in announce period.
+#[test]
+fn council_candidacy_invalid_time() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let council_settings = CouncilSettings::<Runtime>::extract_settings();
+
+        // generate candidates
+        let candidates: Vec<CandidateInfo<Runtime>> = (0
+            ..(council_settings.min_candidate_count + 1) as u64)
+            .map(|i| {
+                MockUtils::generate_candidate(u64::from(i), council_settings.min_candidate_stake)
+            })
+            .collect();
+        let late_candidate = MockUtils::generate_candidate(
+            u64::from(candidates.len() as u64),
+            council_settings.min_candidate_stake,
+        );
+
+        let expected_candidates = candidates
+            .iter()
+            .map(|item| item.candidate.clone())
+            .collect();
+
+        let params = CouncilCycleParams {
+            council_settings: CouncilSettings::<Runtime>::extract_settings(),
+            cycle_start_block_number: 0,
+            expected_initial_council_members: vec![],
+            expected_final_council_members: vec![], // not needed in this scenario
+            candidates_announcing: candidates.clone(),
+            expected_candidates,
+            voters: vec![],
+
+            // escape before voting
+            interrupt_point: Some(CouncilCycleInterrupt::BeforeVoting),
+        };
+
+        InstanceMocks::simulate_council_cycle(params);
+
+        Mocks::announce_candidacy(
+            late_candidate.origin.clone(),
+            late_candidate.account_id.clone(),
+            late_candidate.candidate.stake.clone(),
+            Err(Error::CantCandidateNow),
+        );
+    });
+}
+
+// Test that minimum candidacy stake is enforced.
+#[test]
+fn council_candidacy_stake_too_low() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let council_settings = CouncilSettings::<Runtime>::extract_settings();
+
+        let insufficient_stake = council_settings.min_candidate_stake - 1;
+        let candidate = MockUtils::generate_candidate(0, insufficient_stake);
+
+        Mocks::announce_candidacy(
+            candidate.origin.clone(),
+            candidate.account_id.clone(),
+            candidate.candidate.stake.clone(),
+            Err(Error::CandidacyStakeTooLow),
+        );
+    });
+}
+
+// Test that candidate can vote for himself.
+#[test]
+fn council_can_vote_for_yourself() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let council_settings = CouncilSettings::<Runtime>::extract_settings();
+        let vote_stake = <Runtime as referendum::Trait<ReferendumInstance>>::MinimumStake::get();
+
+        // generate candidates
+        let candidates: Vec<CandidateInfo<Runtime>> = (0
+            ..(council_settings.min_candidate_count + 1) as u64)
+            .map(|i| {
+                MockUtils::generate_candidate(u64::from(i), council_settings.min_candidate_stake)
+            })
+            .collect();
+
+        let expected_candidates = candidates
+            .iter()
+            .map(|item| item.candidate.clone())
+            .collect();
+
+        let params = CouncilCycleParams {
+            council_settings: council_settings.clone(),
+            cycle_start_block_number: 0,
+            expected_initial_council_members: vec![],
+            expected_final_council_members: vec![], // not needed in this scenario
+            candidates_announcing: candidates.clone(),
+            expected_candidates,
+            voters: vec![],
+
+            // escape before voting
+            interrupt_point: Some(CouncilCycleInterrupt::BeforeVoting),
+        };
+
+        InstanceMocks::simulate_council_cycle(params.clone());
+
+        let self_voting_candidate_index = candidates[0].account_id;
+        let voter = MockUtils::generate_voter(
+            VOTER_CANDIDATE_OFFSET,
+            vote_stake,
+            self_voting_candidate_index,
+            AnnouncementPeriodNr::get(),
+        );
+        Mocks::vote_for_candidate(
+            voter.origin.clone(),
+            voter.commitment.clone(),
+            voter.stake.clone(),
+            Ok(()),
+        );
+
+        // forward to election-revealing period
+        MockUtils::increase_block_number(council_settings.voting_stage_duration + 1);
+
+        Mocks::reveal_vote(
+            voter.origin.clone(),
+            voter.salt.clone(),
+            voter.vote_for,
+            Ok(()),
+        );
+    });
+}
+
+// Test that vote for a succesfull candidate has it's stake locked until the one referendum cycle
+// with succesfull council election
+#[test]
+fn council_vote_for_winner_stakes_longer() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let council_settings = CouncilSettings::<Runtime>::extract_settings();
+
+        // run first election round
+        let params = Mocks::run_full_council_cycle(0, &[], 0);
+        let second_round_user_offset = 100; // some number higher than the number of voters
+
+        let voter_for_winner = params.voters[0].clone();
+        let voter_for_looser = params.voters[params.voters.len() - 1].clone();
+
+        // try to release vote stake
+        Mocks::release_vote_stake(voter_for_winner.origin.clone(), Err(()));
+        Mocks::release_vote_stake(voter_for_looser.origin.clone(), Ok(()));
+
+        // try to release vote stake
+        Mocks::release_vote_stake(voter_for_winner.origin.clone(), Err(()));
+
+        // run second election round
+        Mocks::run_full_council_cycle(
+            council_settings.cycle_duration,
+            &params.expected_final_council_members,
+            second_round_user_offset,
+        );
+
+        // try to release vote stake
+        Mocks::release_vote_stake(voter_for_winner.origin.clone(), Ok(()));
+    });
+}
+
+// Test that only valid members can candidate.
+#[test]
+#[ignore] // ignore until `StakeHandler::is_member_staking_account()` properly implemented
+fn council_candidacy_invalid_member() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let council_settings = CouncilSettings::<Runtime>::extract_settings();
+
+        let stake = council_settings.min_candidate_stake;
+        let candidate = MockUtils::generate_candidate(INVALID_USER_MEMBER, stake);
+
+        Mocks::announce_candidacy(
+            candidate.origin.clone(),
+            candidate.account_id.clone(),
+            candidate.candidate.stake.clone(),
+            Err(Error::MemberIdNotMatchAccount),
+        );
+    });
+}
+
+// Test that candidate can withdraw valid candidacy.
+#[test]
+fn council_candidacy_withdraw_candidacy() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let council_settings = CouncilSettings::<Runtime>::extract_settings();
+
+        let stake = council_settings.min_candidate_stake;
+        let candidate = MockUtils::generate_candidate(0, stake);
+
+        Mocks::announce_candidacy(
+            candidate.origin.clone(),
+            candidate.account_id.clone(),
+            candidate.candidate.stake.clone(),
+            Ok(()),
+        );
+
+        Mocks::withdraw_candidacy(
+            candidate.origin.clone(),
+            candidate.account_id.clone(),
+            Ok(()),
+        );
+    });
+}
+
+// Test that candidate can withdraw valid candidacy.
+#[test]
+fn council_candidacy_release_candidate_stake() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let not_elected_candidate_index = 2;
+
+        let params = Mocks::run_full_council_cycle(0, &[], 0);
+
+        Mocks::release_candidacy_stake(
+            params.candidates_announcing[not_elected_candidate_index]
+                .origin
+                .clone(),
+            params.candidates_announcing[not_elected_candidate_index]
+                .account_id
+                .clone(),
+            Ok(()),
+        );
+    });
+}
+
+// Test that only valid members can candidate.
+#[test]
+fn council_announcement_reset_on_insufficient_candidates() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let council_settings = CouncilSettings::<Runtime>::extract_settings();
+
+        // generate candidates
+        let candidates: Vec<CandidateInfo<Runtime>> = (0
+            ..(council_settings.min_candidate_count - 2) as u64)
+            .map(|i| {
+                MockUtils::generate_candidate(u64::from(i), council_settings.min_candidate_stake)
+            })
+            .collect();
+
+        let params = CouncilCycleParams {
+            council_settings: council_settings.clone(),
+            cycle_start_block_number: 0,
+            expected_initial_council_members: vec![],
+            expected_final_council_members: vec![], // not needed in this scenario
+            candidates_announcing: candidates.clone(),
+            expected_candidates: vec![], // not needed in this scenario
+            voters: vec![],              // not needed in this scenario
+
+            // escape before voting
+            interrupt_point: Some(CouncilCycleInterrupt::AfterCandidatesAnnounce),
+        };
+
+        Mocks::simulate_council_cycle(params.clone());
+
+        // forward to election-voting period
+        MockUtils::increase_block_number(council_settings.announcing_stage_duration + 1);
+
+        // check announcements were reset
+        Mocks::check_announcing_period(
+            params.cycle_start_block_number + council_settings.announcing_stage_duration,
+            CouncilStageAnnouncing {
+                candidates_count: 0,
+            },
+        );
+    });
+}
+
+// Test that announcement phase is reset when not enough candidates to fill council recieved votes
+#[test]
+fn council_announcement_reset_on_not_enough_winners() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let council_settings = CouncilSettings::<Runtime>::extract_settings();
+        let vote_stake = <Runtime as referendum::Trait<ReferendumInstance>>::MinimumStake::get();
+
+        // generate candidates
+        let candidates: Vec<CandidateInfo<Runtime>> = (0..council_settings.min_candidate_count
+            as u64)
+            .map(|i| {
+                MockUtils::generate_candidate(u64::from(i), council_settings.min_candidate_stake)
+            })
+            .collect();
+
+        // prepare candidates that are expected to get into candidacy list
+        let expected_candidates = candidates
+            .iter()
+            .map(|item| item.candidate.clone())
+            .collect();
+
+        // generate voters that vote only for one particular candidate
+        let votes_map: Vec<u64> = vec![3, 3, 3];
+        let voters = (0..votes_map.len())
+            .map(|index| {
+                MockUtils::generate_voter(
+                    index as u64,
+                    vote_stake,
+                    CANDIDATE_BASE_ID + votes_map[index],
+                    AnnouncementPeriodNr::get(),
+                )
+            })
+            .collect();
+
+        let params = CouncilCycleParams {
+            council_settings: council_settings.clone(),
+            cycle_start_block_number: 0,
+            expected_initial_council_members: vec![],
+            expected_final_council_members: vec![], // not needed in this scenario
+            candidates_announcing: candidates.clone(),
+            expected_candidates,
+            voters,
+
+            // escape before voting
+            interrupt_point: Some(CouncilCycleInterrupt::AfterRevealing),
+        };
+
+        Mocks::simulate_council_cycle(params.clone());
+
+        // forward to finish election / start idle period
+        MockUtils::increase_block_number(council_settings.reveal_stage_duration + 1);
+
+        // check announcements were reset
+        Mocks::check_announcing_period(
+            params.cycle_start_block_number
+                + council_settings.announcing_stage_duration
+                + council_settings.voting_stage_duration
+                + council_settings.reveal_stage_duration,
+            CouncilStageAnnouncing {
+                candidates_count: 0,
+            },
+        );
+    });
+}
+
+// Test that two consecutive election rounds can be run and expected council members are elected.
+#[test]
+fn council_two_consecutive_rounds() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let council_settings = CouncilSettings::<Runtime>::extract_settings();
+        let vote_stake = <Runtime as referendum::Trait<ReferendumInstance>>::MinimumStake::get();
+
+        // generate candidates
+        let candidates: Vec<CandidateInfo<Runtime>> = (0
+            ..(council_settings.min_candidate_count + 1) as u64)
+            .map(|i| {
+                MockUtils::generate_candidate(u64::from(i), council_settings.min_candidate_stake)
+            })
+            .collect();
+
+        // prepare candidates that are expected to get into candidacy list
+        let expected_candidates = candidates
+            .iter()
+            .map(|item| item.candidate.clone())
+            .collect();
+
+        let expected_final_council_members: Vec<CouncilMemberOf<Runtime>> = vec![
+            (
+                candidates[3].candidate.clone(),
+                candidates[3].membership_id,
+                council_settings.election_duration - 1,
+                0,
+            )
+                .into(),
+            (
+                candidates[0].candidate.clone(),
+                candidates[0].membership_id,
+                council_settings.election_duration - 1,
+                0,
+            )
+                .into(),
+            (
+                candidates[1].candidate.clone(),
+                candidates[1].membership_id,
+                council_settings.election_duration - 1,
+                0,
+            )
+                .into(),
+        ];
+
+        // generate voter for each 6 voters and give: 4 votes for option D, 3 votes for option A,
+        // and 2 vote for option B, and 1 for option C
+        let votes_map: Vec<u64> = vec![3, 3, 3, 3, 0, 0, 0, 1, 1, 2];
+        let voters = (0..votes_map.len())
+            .map(|index| {
+                MockUtils::generate_voter(
+                    index as u64,
+                    vote_stake,
+                    CANDIDATE_BASE_ID + votes_map[index],
+                    AnnouncementPeriodNr::get(),
+                )
+            })
+            .collect();
+
+        let params = CouncilCycleParams {
+            council_settings: CouncilSettings::<Runtime>::extract_settings(),
+            cycle_start_block_number: 0,
+            expected_initial_council_members: vec![],
+            expected_final_council_members: expected_final_council_members.clone(),
+            candidates_announcing: candidates.clone(),
+            expected_candidates,
+            voters,
+
+            interrupt_point: None,
+        };
+
+        Mocks::simulate_council_cycle(params.clone());
+
+        let votes_map2: Vec<u64> = vec![3, 3, 3, 3, 1, 1, 2];
+        let voters2 = (0..votes_map2.len())
+            .map(|index| {
+                MockUtils::generate_voter(
+                    index as u64,
+                    vote_stake,
+                    CANDIDATE_BASE_ID + votes_map2[index],
+                    AnnouncementPeriodNr::get(),
+                )
+            })
+            .collect();
+
+        let expected_final_council_members2: Vec<CouncilMemberOf<Runtime>> = vec![
+            (
+                candidates[3].candidate.clone(),
+                candidates[3].membership_id,
+                council_settings.cycle_duration + council_settings.election_duration - 1,
+                0,
+            )
+                .into(),
+            (
+                candidates[1].candidate.clone(),
+                candidates[1].membership_id,
+                council_settings.cycle_duration + council_settings.election_duration - 1,
+                0,
+            )
+                .into(),
+            (
+                candidates[2].candidate.clone(),
+                candidates[2].membership_id,
+                council_settings.cycle_duration + council_settings.election_duration - 1,
+                0,
+            )
+                .into(),
+        ];
+
+        let params2 = CouncilCycleParams {
+            expected_initial_council_members: expected_final_council_members,
+            cycle_start_block_number: council_settings.announcing_stage_duration
+                + council_settings.voting_stage_duration
+                + council_settings.reveal_stage_duration
+                + council_settings.idle_stage_duration,
+            voters: voters2,
+            expected_final_council_members: expected_final_council_members2,
+            ..params.clone()
+        };
+
+        Mocks::simulate_council_cycle(params2);
+    });
+}
+
+// Test that repeated candidacy announcement is forbidden.
+#[test]
+fn council_cant_candidate_repeatedly() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let council_settings = CouncilSettings::<Runtime>::extract_settings();
+
+        // generate candidates
+        let candidate = MockUtils::generate_candidate(0, council_settings.min_candidate_stake);
+
+        Mocks::announce_candidacy(
+            candidate.origin.clone(),
+            candidate.membership_id,
+            council_settings.min_candidate_stake,
+            Ok(()),
+        );
+        Mocks::announce_candidacy(
+            candidate.origin.clone(),
+            candidate.membership_id,
+            council_settings.min_candidate_stake,
+            Err(Error::CantCandidateTwice),
+        );
+    });
+}
+
+// Test that candidate's stake is truly locked.
+#[test]
+fn council_candidate_stake_is_locked() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let council_settings = CouncilSettings::<Runtime>::extract_settings();
+
+        // generate candidates
+        let candidates: Vec<CandidateInfo<Runtime>> = (0..1)
+            .map(|i| MockUtils::generate_candidate(i, council_settings.min_candidate_stake))
+            .collect();
+
+        let params = CouncilCycleParams {
+            council_settings: council_settings.clone(),
+            cycle_start_block_number: 0,
+            expected_initial_council_members: vec![],
+            expected_final_council_members: vec![], // not needed in this scenario
+            candidates_announcing: candidates.clone(),
+            expected_candidates: vec![], // not needed in this scenario
+            voters: vec![],              // not needed in this scenario
+
+            // escape before voting
+            interrupt_point: Some(CouncilCycleInterrupt::AfterCandidatesAnnounce),
+        };
+
+        Mocks::simulate_council_cycle(params.clone());
+
+        candidates.iter().for_each(|council_member| {
+            assert_eq!(
+                CandidacyLock::current_stake(&council_member.account_id),
+                council_settings.min_candidate_stake,
+            );
+        });
+    });
+}
+
+// Test that candidate can unstake after not being elected.
+#[test]
+fn council_candidate_stake_can_be_unlocked() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let council_settings = CouncilSettings::<Runtime>::extract_settings();
+        let vote_stake = <Runtime as referendum::Trait<ReferendumInstance>>::MinimumStake::get();
+
+        let not_elected_candidate_index = 2;
+
+        // generate candidates
+        let candidates: Vec<CandidateInfo<Runtime>> = (0
+            ..(council_settings.min_candidate_count + 1) as u64)
+            .map(|i| {
+                MockUtils::generate_candidate(u64::from(i), council_settings.min_candidate_stake)
+            })
+            .collect();
+
+        // prepare candidates that are expected to get into candidacy list
+        let expected_candidates = candidates
+            .iter()
+            .map(|item| item.candidate.clone())
+            .collect();
+
+        let expected_final_council_members: Vec<CouncilMemberOf<Runtime>> = vec![
+            (
+                candidates[3].candidate.clone(),
+                candidates[3].membership_id,
+                council_settings.election_duration - 1,
+                0,
+            )
+                .into(),
+            (
+                candidates[0].candidate.clone(),
+                candidates[0].membership_id,
+                council_settings.election_duration - 1,
+                0,
+            )
+                .into(),
+            (
+                candidates[1].candidate.clone(),
+                candidates[1].membership_id,
+                council_settings.election_duration - 1,
+                0,
+            )
+                .into(),
+        ];
+
+        // generate voter for each 6 voters and give: 4 votes for option D, 3 votes for option A,
+        // and 2 vote for option B, and 1 for option C
+        let votes_map: Vec<u64> = vec![3, 3, 3, 3, 0, 0, 0, 1, 1, 2];
+        let voters = (0..votes_map.len())
+            .map(|index| {
+                MockUtils::generate_voter(
+                    index as u64,
+                    vote_stake,
+                    CANDIDATE_BASE_ID + votes_map[index],
+                    AnnouncementPeriodNr::get(),
+                )
+            })
+            .collect();
+
+        let params = CouncilCycleParams {
+            council_settings: CouncilSettings::<Runtime>::extract_settings(),
+            cycle_start_block_number: 0,
+            expected_initial_council_members: vec![],
+            expected_final_council_members,
+            candidates_announcing: candidates.clone(),
+            expected_candidates,
+            voters,
+
+            interrupt_point: None,
+        };
+
+        Mocks::simulate_council_cycle(params);
+
+        // check that not-elected-member has still his candidacy stake locked
+        assert_eq!(
+            CandidacyLock::current_stake(
+                &candidates[not_elected_candidate_index]
+                    .candidate
+                    .staking_account_id
+            ),
+            council_settings.min_candidate_stake,
+        );
+
+        assert_eq!(
+            Module::<Runtime>::release_candidacy_stake(
+                MockUtils::mock_origin(candidates[not_elected_candidate_index].origin.clone()),
+                candidates[not_elected_candidate_index]
+                    .candidate
+                    .staking_account_id
+            ),
+            Ok(()),
+        );
+
+        // check that candidacy stake is unlocked
+        assert_eq!(
+            CandidacyLock::current_stake(
+                &candidates[not_elected_candidate_index]
+                    .candidate
+                    .staking_account_id
+            ),
+            0,
+        );
+    });
+}
+
+// Test that elected candidate's stake lock is automaticly converted from candidate lock to
+// elected member lock.
+#[test]
+fn council_candidate_stake_automaticly_converted() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let council_settings = CouncilSettings::<Runtime>::extract_settings();
+        let vote_stake = <Runtime as referendum::Trait<ReferendumInstance>>::MinimumStake::get();
+
+        // generate candidates
+        let candidates: Vec<CandidateInfo<Runtime>> = (0
+            ..(council_settings.min_candidate_count + 1) as u64)
+            .map(|i| {
+                MockUtils::generate_candidate(u64::from(i), council_settings.min_candidate_stake)
+            })
+            .collect();
+
+        // prepare candidates that are expected to get into candidacy list
+        let expected_candidates = candidates
+            .iter()
+            .map(|item| item.candidate.clone())
+            .collect();
+
+        let expected_final_council_members: Vec<CouncilMemberOf<Runtime>> = vec![
+            (
+                candidates[3].candidate.clone(),
+                candidates[3].membership_id,
+                council_settings.election_duration - 1,
+                0,
+            )
+                .into(),
+            (
+                candidates[0].candidate.clone(),
+                candidates[0].membership_id,
+                council_settings.election_duration - 1,
+                0,
+            )
+                .into(),
+            (
+                candidates[1].candidate.clone(),
+                candidates[1].membership_id,
+                council_settings.election_duration - 1,
+                0,
+            )
+                .into(),
+        ];
+
+        // generate voter for each 6 voters and give: 4 votes for option D, 3 votes for option A,
+        // and 2 vote for option B, and 1 for option C
+        let votes_map: Vec<u64> = vec![3, 3, 3, 3, 0, 0, 0, 1, 1, 2];
+        let voters = (0..votes_map.len())
+            .map(|index| {
+                MockUtils::generate_voter(
+                    index as u64,
+                    vote_stake,
+                    CANDIDATE_BASE_ID + votes_map[index],
+                    AnnouncementPeriodNr::get(),
+                )
+            })
+            .collect();
+
+        let params = CouncilCycleParams {
+            council_settings: CouncilSettings::<Runtime>::extract_settings(),
+            cycle_start_block_number: 0,
+            expected_initial_council_members: vec![],
+            expected_final_council_members: expected_final_council_members.clone(),
+            candidates_announcing: candidates.clone(),
+            expected_candidates,
+            voters,
+
+            interrupt_point: None,
+        };
+
+        Mocks::simulate_council_cycle(params);
+
+        expected_final_council_members
+            .iter()
+            .for_each(|council_member| {
+                assert_eq!(
+                    CandidacyLock::current_stake(&council_member.staking_account_id),
+                    0
+                );
+
+                assert_eq!(
+                    CouncilorLock::current_stake(&council_member.staking_account_id),
+                    council_settings.min_candidate_stake
+                );
+            });
+    });
+}
+
+// Test that council member stake is locked during council governance.
+#[test]
+fn council_member_stake_is_locked() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let council_settings = CouncilSettings::<Runtime>::extract_settings();
+        let vote_stake = <Runtime as referendum::Trait<ReferendumInstance>>::MinimumStake::get();
+
+        // generate candidates
+        let candidates: Vec<CandidateInfo<Runtime>> = (0
+            ..(council_settings.min_candidate_count + 1) as u64)
+            .map(|i| {
+                MockUtils::generate_candidate(u64::from(i), council_settings.min_candidate_stake)
+            })
+            .collect();
+
+        // prepare candidates that are expected to get into candidacy list
+        let expected_candidates = candidates
+            .iter()
+            .map(|item| item.candidate.clone())
+            .collect();
+
+        let expected_final_council_members: Vec<CouncilMemberOf<Runtime>> = vec![
+            (
+                candidates[3].candidate.clone(),
+                candidates[3].membership_id,
+                council_settings.election_duration - 1,
+                0,
+            )
+                .into(),
+            (
+                candidates[0].candidate.clone(),
+                candidates[0].membership_id,
+                council_settings.election_duration - 1,
+                0,
+            )
+                .into(),
+            (
+                candidates[1].candidate.clone(),
+                candidates[1].membership_id,
+                council_settings.election_duration - 1,
+                0,
+            )
+                .into(),
+        ];
+
+        // generate voter for each 6 voters and give: 4 votes for option D, 3 votes for option A,
+        // and 2 vote for option B, and 1 for option C
+        let votes_map: Vec<u64> = vec![3, 3, 3, 3, 0, 0, 0, 1, 1, 2];
+        let voters = (0..votes_map.len())
+            .map(|index| {
+                MockUtils::generate_voter(
+                    index as u64,
+                    vote_stake,
+                    CANDIDATE_BASE_ID + votes_map[index],
+                    AnnouncementPeriodNr::get(),
+                )
+            })
+            .collect();
+
+        let params = CouncilCycleParams {
+            council_settings: CouncilSettings::<Runtime>::extract_settings(),
+            cycle_start_block_number: 0,
+            expected_initial_council_members: vec![],
+            expected_final_council_members: expected_final_council_members.clone(),
+            candidates_announcing: candidates.clone(),
+            expected_candidates,
+            voters,
+
+            interrupt_point: None,
+        };
+
+        Mocks::simulate_council_cycle(params);
+
+        expected_final_council_members
+            .iter()
+            .for_each(|council_member| {
+                assert_eq!(
+                    CouncilorLock::current_stake(&council_member.staking_account_id),
+                    council_settings.min_candidate_stake
+                );
+            });
+    });
+}
+
+// Test that council member's stake is automaticly released after next council is elected.
+#[test]
+fn council_member_stake_automaticly_unlocked() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let council_settings = CouncilSettings::<Runtime>::extract_settings();
+        let vote_stake = <Runtime as referendum::Trait<ReferendumInstance>>::MinimumStake::get();
+        let not_reelected_candidate_index = 0;
+
+        let params = Mocks::run_full_council_cycle(0, &[], 0);
+
+        let candidates = params.candidates_announcing.clone();
+
+        // 'not reelected member' should have it's stake locked now (he is currently elected member)
+        assert_eq!(
+            CouncilorLock::current_stake(&candidates[not_reelected_candidate_index].account_id),
+            council_settings.min_candidate_stake,
+        );
+
+        let votes_map2: Vec<u64> = vec![3, 3, 3, 3, 1, 1, 2];
+        let voters2 = (0..votes_map2.len())
+            .map(|index| {
+                MockUtils::generate_voter(
+                    index as u64,
+                    vote_stake,
+                    CANDIDATE_BASE_ID + votes_map2[index],
+                    AnnouncementPeriodNr::get(),
+                )
+            })
+            .collect();
+
+        let expected_final_council_members2: Vec<CouncilMemberOf<Runtime>> = vec![
+            (
+                candidates[3].candidate.clone(),
+                candidates[3].membership_id,
+                council_settings.cycle_duration + council_settings.election_duration - 1,
+                0,
+            )
+                .into(),
+            (
+                candidates[1].candidate.clone(),
+                candidates[1].membership_id,
+                council_settings.cycle_duration + council_settings.election_duration - 1,
+                0,
+            )
+                .into(),
+            (
+                candidates[2].candidate.clone(),
+                candidates[2].membership_id,
+                council_settings.cycle_duration + council_settings.election_duration - 1,
+                0,
+            )
+                .into(),
+        ];
+
+        let params2 = CouncilCycleParams {
+            expected_initial_council_members: params.expected_final_council_members.clone(),
+            cycle_start_block_number: council_settings.announcing_stage_duration
+                + council_settings.voting_stage_duration
+                + council_settings.reveal_stage_duration
+                + council_settings.idle_stage_duration,
+            voters: voters2,
+            expected_final_council_members: expected_final_council_members2.clone(),
+            ..params.clone()
+        };
+
+        Mocks::simulate_council_cycle(params2);
+
+        // not reelected member should have it's stake unlocked
+        assert_eq!(
+            CouncilorLock::current_stake(&candidates[not_reelected_candidate_index].account_id),
+            0
+        );
+    });
+}
+
+// Test that user can set and change candidacy note.
+#[test]
+fn council_candidacy_set_note() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let council_settings = CouncilSettings::<Runtime>::extract_settings();
+        let vote_stake = <Runtime as referendum::Trait<ReferendumInstance>>::MinimumStake::get();
+
+        // generate candidates
+        let candidates: Vec<CandidateInfo<Runtime>> = (0
+            ..(council_settings.min_candidate_count + 1) as u64)
+            .map(|i| {
+                MockUtils::generate_candidate(u64::from(i), council_settings.min_candidate_stake)
+            })
+            .collect();
+
+        // prepare candidates that are expected to get into candidacy list
+        let expected_candidates = candidates
+            .iter()
+            .map(|item| item.candidate.clone())
+            .collect();
+
+        // generate voter for each 6 voters and give: 4 votes for option D, 3 votes for option A,
+        // and 2 vote for option B, and 1 for option C
+        let votes_map: Vec<u64> = vec![3, 3, 3, 3, 0, 0, 0, 1, 1, 2];
+        let voters = (0..votes_map.len())
+            .map(|index| {
+                MockUtils::generate_voter(
+                    index as u64,
+                    vote_stake,
+                    CANDIDATE_BASE_ID + votes_map[index],
+                    AnnouncementPeriodNr::get(),
+                )
+            })
+            .collect();
+
+        let params = CouncilCycleParams {
+            council_settings: CouncilSettings::<Runtime>::extract_settings(),
+            cycle_start_block_number: 0,
+            expected_initial_council_members: vec![],
+            expected_final_council_members: vec![],
+            candidates_announcing: candidates.clone(),
+            expected_candidates,
+            voters,
+
+            interrupt_point: Some(CouncilCycleInterrupt::AfterCandidatesAnnounce),
+        };
+
+        Mocks::simulate_council_cycle(params.clone());
+
+        // prepare values for note testing
+        let membership_id = candidates[0].clone().membership_id;
+        let origin = candidates[0].origin.clone();
+        let note1 = "MyNote1".as_bytes();
+        let note2 = "MyNote2".as_bytes();
+        let note3 = "MyNote3".as_bytes();
+        let note4 = "MyNote4".as_bytes();
+
+        // check note is not set yet
+        Mocks::check_candidacy_note(&membership_id, None);
+
+        // set note - announcement stage
+        Mocks::set_candidacy_note(origin.clone(), membership_id.clone(), note1, Ok(()));
+
+        // change note - announcement stage
+        Mocks::set_candidacy_note(origin.clone(), membership_id.clone(), note2, Ok(()));
+
+        // forward to election-voting period
+        MockUtils::increase_block_number(council_settings.announcing_stage_duration + 1);
+
+        // change note - election stage
+        Mocks::set_candidacy_note(origin.clone(), membership_id.clone(), note3, Ok(()));
+
+        // vote with all voters
+        params.voters.iter().for_each(|voter| {
+            Mocks::vote_for_candidate(
+                voter.origin.clone(),
+                voter.commitment.clone(),
+                voter.stake.clone(),
+                Ok(()),
+            )
+        });
+
+        // forward to election-revealing period
+        MockUtils::increase_block_number(council_settings.voting_stage_duration + 1);
+
+        // reveal vote for all voters
+        params.voters.iter().for_each(|voter| {
+            Mocks::reveal_vote(
+                voter.origin.clone(),
+                voter.salt.clone(),
+                voter.vote_for,
+                Ok(()),
+            );
+        });
+
+        // finish election / start idle period
+        MockUtils::increase_block_number(council_settings.reveal_stage_duration + 1);
+        Mocks::check_idle_period(
+            params.cycle_start_block_number
+                + council_settings.reveal_stage_duration
+                + council_settings.announcing_stage_duration
+                + council_settings.voting_stage_duration,
+        );
+
+        // check that note can be changed no longer
+        Mocks::set_candidacy_note(
+            origin.clone(),
+            membership_id.clone(),
+            note4,
+            Err(Error::NotCandidatingNow),
+        );
+    });
+}
+
+// Test that candidating in 2nd council cycle after failed candidacy in 1st cycle releases the 1st
+// cycle's stake.
+#[test]
+fn council_repeated_candidacy_unstakes() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let council_settings = CouncilSettings::<Runtime>::extract_settings();
+        let not_elected_candidate_index = 2;
+
+        // run one council cycle
+        let params = Mocks::run_full_council_cycle(0, &[], 0);
+
+        // forward to next candidacy announcing period
+        MockUtils::increase_block_number(council_settings.idle_stage_duration + 1);
+
+        let candidate = params.candidates_announcing[not_elected_candidate_index].clone();
+
+        // some different value from the previous stake
+        let new_stake = council_settings.min_candidate_stake * 5;
+
+        // check candidacy stake from 1st cycle is locked
+        Mocks::check_announcing_stake(
+            &candidate.membership_id,
+            council_settings.min_candidate_stake,
+        );
+
+        Mocks::announce_candidacy(
+            candidate.origin.clone(),
+            candidate.account_id.clone(),
+            new_stake,
+            Ok(()),
+        );
+
+        // check candidacy
+        Mocks::check_announcing_stake(&candidate.membership_id, new_stake);
+    });
+}
+
+/////////////////// Budget-related /////////////////////////////////////////////
+
+// Test that budget balance can be set from external source.
+#[test]
+fn council_budget_can_be_set() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let balances = [100, 500, 300];
+        let origin = OriginType::Root;
+
+        for balance in &balances {
+            Mocks::set_budget(origin.clone(), *balance, Ok(()));
+        }
+    })
+}
+
+// Test that budget balance can be set from external source.
+#[test]
+fn council_budget_refill_can_be_planned() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let origin = OriginType::Root;
+        let next_refill = 1000;
+
+        Mocks::plan_budget_refill(origin.clone(), next_refill, Ok(()));
+
+        // forward to one block before refill
+        MockUtils::increase_block_number(next_refill - 1);
+
+        // check no refill happened yet
+        Mocks::check_budget_refill(0, next_refill);
+
+        // forward to after block refill
+        MockUtils::increase_block_number(1);
+
+        // check budget was increased
+        Mocks::check_budget_refill(
+            <Runtime as Trait>::BudgetRefillAmount::get(),
+            next_refill + <Runtime as Trait>::BudgetRefillPeriod::get(),
+        );
+    })
+}
+
+// Test that rewards for council members are paid.
+#[test]
+fn council_rewards_are_paid() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let council_settings = CouncilSettings::<Runtime>::extract_settings();
+        let origin = OriginType::Root;
+
+        let sufficient_balance = 10000000;
+
+        Mocks::set_budget(origin.clone(), sufficient_balance, Ok(()));
+
+        // run 1st council cycle
+        let params = Mocks::run_full_council_cycle(0, &[], 0);
+
+        // calculate council member last reward block
+        let last_payment_block = council_settings.cycle_duration
+            + (<Runtime as Trait>::ElectedMemberRewardPeriod::get()
+                - (council_settings.idle_stage_duration
+                    % <Runtime as Trait>::ElectedMemberRewardPeriod::get()))
+            - 1; // -1 because current block is not finalized yet
+        let tmp_council_members: Vec<CouncilMemberOf<Runtime>> = params
+            .expected_final_council_members
+            .iter()
+            .map(|council_member| CouncilMemberOf::<Runtime> {
+                last_payment_block,
+                ..*council_member
+            })
+            .collect();
+
+        // run 2nd council cycle
+        Mocks::run_full_council_cycle(
+            council_settings.cycle_duration,
+            &tmp_council_members.as_slice(),
+            0,
+        );
+    });
+}
+
+// Test that any rewards missed due to insufficient budget balance will be paid off eventually.
+#[test]
+fn council_missed_rewards_are_paid_later() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let council_settings = CouncilSettings::<Runtime>::extract_settings();
+        let origin = OriginType::Root;
+        let reward_period = <Runtime as Trait>::ElectedMemberRewardPeriod::get();
+
+        let insufficient_balance = 0;
+        let sufficient_balance = 10000000;
+
+        // set empty budget
+        Mocks::set_budget(origin.clone(), insufficient_balance, Ok(()));
+
+        // run 1st council cycle
+        Mocks::run_full_council_cycle(0, &[], 0);
+
+        // forward to block after first reward payment
+        MockUtils::increase_block_number(<Runtime as Trait>::ElectedMemberRewardPeriod::get());
+
+        let last_payment_block = council_settings.cycle_duration
+            + (reward_period
+                - (council_settings.idle_stage_duration
+                    % <Runtime as Trait>::ElectedMemberRewardPeriod::get()))
+            - 1; // -1 because current block is not finalized yet
+
+        // check unpaid rewards were discarded
+        for council_member in CouncilMembers::<Runtime>::get() {
+            assert_eq!(council_member.unpaid_reward, 0,);
+            assert_eq!(
+                council_member.last_payment_block,
+                //last_payment_block + reward_period,
+                // -1 because council was elected (last_payment_block set) in on_finalize
+                council_settings.election_duration - 1,
+            );
+        }
+
+        // set sufficitent budget
+        Mocks::set_budget(origin.clone(), sufficient_balance, Ok(()));
+
+        // forward to block after second reward payment
+        MockUtils::increase_block_number(<Runtime as Trait>::ElectedMemberRewardPeriod::get());
+
+        // check unpaid rewards were discarded
+        for council_member in CouncilMembers::<Runtime>::get() {
+            assert_eq!(council_member.unpaid_reward, 0,);
+            assert_eq!(
+                council_member.last_payment_block,
+                last_payment_block + 2 * reward_period,
+            );
+        }
+    });
+}
+
+// Test that any unpaid rewards will be discarded on council depose if budget is still insufficient.
+#[test]
+fn council_discard_remaining_rewards_on_depose() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let council_settings = CouncilSettings::<Runtime>::extract_settings();
+        let origin = OriginType::Root;
+
+        let sufficient_balance = 10000000;
+        let second_cycle_user_offset = 10;
+
+        Mocks::set_budget(origin.clone(), sufficient_balance, Ok(()));
+
+        // run 1st council cycle
+        let params = Mocks::run_full_council_cycle(0, &[], 0);
+
+        // calculate council member last reward block
+        let last_payment_block = council_settings.cycle_duration
+            + (<Runtime as Trait>::ElectedMemberRewardPeriod::get()
+                - (council_settings.idle_stage_duration
+                    % <Runtime as Trait>::ElectedMemberRewardPeriod::get()))
+            - 1; // -1 because current block is not finalized yet
+        let tmp_council_members: Vec<CouncilMemberOf<Runtime>> = params
+            .expected_final_council_members
+            .iter()
+            .map(|council_member| CouncilMemberOf::<Runtime> {
+                last_payment_block,
+                ..*council_member
+            })
+            .collect();
+
+        // check unpaid rewards were discarded
+        for council_member in CouncilMembers::<Runtime>::get() {
+            assert_eq!(council_member.unpaid_reward, 0,);
+        }
+
+        // run 2nd council cycle
+        Mocks::run_full_council_cycle(
+            council_settings.cycle_duration,
+            &tmp_council_members.as_slice(),
+            second_cycle_user_offset,
+        );
+
+        // check unpaid rewards were discarded
+        for council_member in CouncilMembers::<Runtime>::get() {
+            assert_eq!(council_member.unpaid_reward, 0,);
+        }
+    });
+}
+
+// Test that budget is periodicly refilled.
+#[test]
+fn council_budget_auto_refill() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let council_settings = CouncilSettings::<Runtime>::extract_settings();
+        let start_balance = Budget::<Runtime>::get();
+
+        // forward before next refill
+        MockUtils::increase_block_number(council_settings.budget_refill_period - 1);
+
+        assert_eq!(Budget::<Runtime>::get(), start_balance,);
+
+        // forward to next filling
+        MockUtils::increase_block_number(1);
+
+        assert_eq!(
+            Budget::<Runtime>::get(),
+            start_balance + council_settings.budget_refill_amount,
+        );
+
+        // forward to next filling
+        MockUtils::increase_block_number(council_settings.budget_refill_period);
+
+        assert_eq!(
+            Budget::<Runtime>::get(),
+            start_balance + 2 * council_settings.budget_refill_amount,
+        );
+    });
+}
+
+// Test that `staking_account_id` is required to be associated with `membership_id` while
+// `reward_account_id` is not
+#[test]
+fn council_membership_checks() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let council_settings = CouncilSettings::<Runtime>::extract_settings();
+
+        // generate candidates
+        let candidate1 = MockUtils::generate_candidate(0, council_settings.min_candidate_stake);
+        let candidate2 = MockUtils::generate_candidate(1, council_settings.min_candidate_stake);
+
+        // sanity checks
+        assert_ne!(candidate1.membership_id, candidate2.membership_id,);
+        assert_ne!(
+            candidate1.candidate.reward_account_id,
+            candidate2.candidate.reward_account_id,
+        );
+        assert_ne!(
+            candidate1.candidate.staking_account_id,
+            candidate2.candidate.staking_account_id,
+        );
+
+        // TODO: uncomment this once StakingHandler's `is_member_staking_account` is properly
+        // implemented
+        /*
+        // test that staking_account_id has to be associated with membership_id
+        Mocks::announce_candidacy_raw(
+            candidate1.origin.clone(),
+            candidate1.account_id.clone(),
+            candidate2.candidate.staking_account_id.clone(), // second candidate's account id
+            candidate1.candidate.reward_account_id.clone(),
+            candidate1.candidate.stake.clone(),
+            Err(Error::MembershipIdNotMatchAccount),
+        );
+        */
+
+        // test that reward_account_id not associated with membership_id can be used
+        Mocks::announce_candidacy_raw(
+            candidate1.origin.clone(),
+            candidate1.account_id.clone(),
+            candidate1.candidate.staking_account_id.clone(),
+            candidate2.candidate.reward_account_id.clone(), // second candidate's account id
+            candidate1.candidate.stake.clone(),
+            Ok(()),
+        );
+    });
+}
+
+#[test]
+fn council_new_council_elected_hook() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        Mocks::run_full_council_cycle(0, &[], 0);
+
+        Mocks::check_new_council_elected_hook();
+    });
+}

+ 0 - 374
runtime-modules/governance/src/council.rs

@@ -1,374 +0,0 @@
-use frame_support::{debug, decl_event, decl_module, decl_storage, ensure};
-use frame_system::ensure_root;
-use sp_arithmetic::traits::{One, Zero};
-use sp_std::vec;
-use sp_std::vec::Vec;
-
-pub use super::election::{self, CouncilElected, Seat, Seats};
-pub use common::currency::{BalanceOf, GovernanceCurrency};
-
-// Hook For announcing that council term has ended
-pub trait CouncilTermEnded {
-    fn council_term_ended();
-}
-
-impl CouncilTermEnded for () {
-    fn council_term_ended() {}
-}
-
-impl<X: CouncilTermEnded> CouncilTermEnded for (X,) {
-    fn council_term_ended() {
-        X::council_term_ended();
-    }
-}
-
-pub trait Trait: frame_system::Trait + recurringrewards::Trait + GovernanceCurrency {
-    type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;
-
-    type CouncilTermEnded: CouncilTermEnded;
-}
-
-decl_storage! {
-    trait Store for Module<T: Trait> as Council {
-        pub ActiveCouncil get(fn active_council) config(): Seats<T::AccountId, BalanceOf<T>>;
-
-        pub TermEndsAt get(fn term_ends_at) config() : T::BlockNumber = T::BlockNumber::from(1);
-
-        /// The mint that funds council member rewards and spending proposals budget
-        pub CouncilMint get(fn council_mint) : <T as minting::Trait>::MintId;
-
-        /// The reward relationships currently in place. There may not necessarily be a 1-1 correspondance with
-        /// the active council, since there are multiple ways of setting/adding/removing council members, some of which
-        /// do not involve creating a relationship.
-        pub RewardRelationships get(fn reward_relationships) : map hasher(blake2_128_concat)
-            T::AccountId => T::RewardRelationshipId;
-
-        /// Reward amount paid out at each PayoutInterval
-        pub AmountPerPayout get(fn amount_per_payout): minting::BalanceOf<T>;
-
-        /// Optional interval in blocks on which a reward payout will be made to each council member
-        pub PayoutInterval get(fn payout_interval): Option<T::BlockNumber>;
-
-        /// How many blocks after the reward is created, the first payout will be made
-        pub FirstPayoutAfterRewardCreated get(fn first_payout_after_reward_created): T::BlockNumber;
-    }
-    add_extra_genesis {
-        build(|_config: &GenesisConfig<T>| {
-            // Create the council mint.
-            let mint_id_result = <minting::Module<T>>::add_mint(
-                minting::BalanceOf::<T>::from(0),
-                None
-            );
-
-            if let Ok(mint_id) = mint_id_result {
-                <CouncilMint<T>>::put(mint_id);
-            } else {
-                panic!("Failed to create a mint for the council");
-            }
-        });
-    }
-}
-
-// Event for this module.
-decl_event!(
-    pub enum Event<T> where <T as frame_system::Trait>::BlockNumber {
-        CouncilTermEnded(BlockNumber),
-        NewCouncilTermStarted(BlockNumber),
-    }
-);
-
-impl<T: Trait> CouncilElected<Seats<T::AccountId, BalanceOf<T>>, T::BlockNumber> for Module<T> {
-    fn council_elected(seats: Seats<T::AccountId, BalanceOf<T>>, term: T::BlockNumber) {
-        <ActiveCouncil<T>>::put(seats.clone());
-
-        let next_term_ends_at = <frame_system::Module<T>>::block_number() + term;
-
-        <TermEndsAt<T>>::put(next_term_ends_at);
-
-        for seat in seats.iter() {
-            Self::add_reward_relationship(&seat.member, Self::council_mint());
-        }
-
-        Self::deposit_event(RawEvent::NewCouncilTermStarted(next_term_ends_at));
-    }
-}
-
-impl<T: Trait> Module<T> {
-    pub fn is_term_ended() -> bool {
-        <frame_system::Module<T>>::block_number() >= Self::term_ends_at()
-    }
-
-    pub fn is_councilor(sender: &T::AccountId) -> bool {
-        Self::active_council().iter().any(|c| c.member == *sender)
-    }
-
-    fn add_reward_relationship(destination: &T::AccountId, reward_source: T::MintId) {
-        let recipient = <recurringrewards::Module<T>>::add_recipient();
-
-        // When calculating when first payout occurs, add minimum of one block interval to ensure rewards module
-        // has a chance to execute its on_finalize routine.
-        let next_payout_at = frame_system::Module::<T>::block_number()
-            + Self::first_payout_after_reward_created()
-            + T::BlockNumber::one();
-
-        if let Ok(relationship_id) = <recurringrewards::Module<T>>::add_reward_relationship(
-            reward_source,
-            recipient,
-            destination.clone(),
-            Self::amount_per_payout(),
-            next_payout_at,
-            Self::payout_interval(),
-        ) {
-            RewardRelationships::<T>::insert(destination, relationship_id);
-        } else {
-            debug::warn!("Failed to create a reward relationship for council seat");
-        }
-    }
-
-    fn remove_reward_relationships() {
-        for seat in Self::active_council().into_iter() {
-            if RewardRelationships::<T>::contains_key(&seat.member) {
-                let id = Self::reward_relationships(&seat.member);
-                <recurringrewards::Module<T>>::remove_reward_relationship(id);
-            }
-        }
-    }
-
-    fn on_term_ended(now: T::BlockNumber) {
-        // Stop paying out rewards when the term ends.
-        // Note: Is it not simpler to just do a single payout at end of term?
-        // During the term the recurring reward module could unfairly pay some but not all council members
-        // If there is insufficient mint capacity.. so doing it at this point offers more control
-        // and a potentially more fair outcome in such a case.
-        Self::remove_reward_relationships();
-
-        Self::deposit_event(RawEvent::CouncilTermEnded(now));
-
-        T::CouncilTermEnded::council_term_ended();
-    }
-}
-
-decl_module! {
-    pub struct Module<T: Trait> for enum Call where origin: T::Origin {
-        fn deposit_event() = default;
-
-        fn on_finalize(now: T::BlockNumber) {
-            if now == Self::term_ends_at() {
-                Self::on_term_ended(now);
-            }
-        }
-
-        // Privileged methods
-
-        /// Force set a zero staked council. Stakes in existing council seats are not returned.
-        /// Existing council rewards are removed and new council members do NOT get any rewards.
-        /// Avoid using this call if possible, will be deprecated. The term of the new council is
-        /// not extended.
-        #[weight = 10_000_000] // TODO: adjust weight
-        pub fn set_council(origin, accounts: Vec<T::AccountId>) {
-            ensure_root(origin)?;
-
-            // Council is being replaced so remove existing reward relationships if they exist
-            Self::remove_reward_relationships();
-
-            for account in accounts.clone() {
-                Self::add_reward_relationship(&account, Self::council_mint());
-            }
-
-            let new_council: Seats<T::AccountId, BalanceOf<T>> = accounts.into_iter().map(|account| {
-                Seat {
-                    member: account,
-                    stake: BalanceOf::<T>::zero(),
-                    backers: vec![]
-                }
-            }).collect();
-
-            <ActiveCouncil<T>>::put(new_council);
-        }
-
-        /// Adds a zero staked council member. A member added in this way does not get a recurring reward.
-        #[weight = 10_000_000] // TODO: adjust weight
-        fn add_council_member(origin, account: T::AccountId) {
-            ensure_root(origin)?;
-
-            ensure!(!Self::is_councilor(&account), "cannot add same account multiple times");
-
-            Self::add_reward_relationship(&account, Self::council_mint());
-
-            let seat = Seat {
-                member: account,
-                stake: BalanceOf::<T>::zero(),
-                backers: vec![]
-            };
-
-            // add member to existing council
-            <ActiveCouncil<T>>::mutate(|council| council.push(seat));
-        }
-
-        /// Remove a single council member and their reward.
-        #[weight = 10_000_000] // TODO: adjust weight
-        fn remove_council_member(origin, account_to_remove: T::AccountId) {
-            ensure_root(origin)?;
-
-            ensure!(Self::is_councilor(&account_to_remove), "account is not a councilor");
-
-            if RewardRelationships::<T>::contains_key(&account_to_remove) {
-                let relationship_id = Self::reward_relationships(&account_to_remove);
-                <recurringrewards::Module<T>>::remove_reward_relationship(relationship_id);
-            }
-
-            let filtered_council: Seats<T::AccountId, BalanceOf<T>> = Self::active_council()
-                .into_iter()
-                .filter(|c| c.member != account_to_remove)
-                .collect();
-
-            <ActiveCouncil<T>>::put(filtered_council);
-        }
-
-        /// Set blocknumber when council term will end
-        #[weight = 10_000_000] // TODO: adjust weight
-        fn set_term_ends_at(origin, ends_at: T::BlockNumber) {
-            ensure_root(origin)?;
-            ensure!(ends_at > <frame_system::Module<T>>::block_number(), "must set future block number");
-            <TermEndsAt<T>>::put(ends_at);
-        }
-
-        /// Sets the capacity of the the council mint, if it doesn't exist, attempts to
-        /// create a new one.
-        #[weight = 10_000_000] // TODO: adjust weight
-        pub fn set_council_mint_capacity(origin, capacity: minting::BalanceOf<T>) {
-            ensure_root(origin)?;
-
-            minting::Module::<T>::set_mint_capacity(Self::council_mint(), capacity).map_err(<&str>::from)?;
-        }
-
-        /// Attempts to mint and transfer amount to destination account
-        #[weight = 10_000_000] // TODO: adjust weight
-        fn spend_from_council_mint(origin, amount: minting::BalanceOf<T>, destination: T::AccountId) {
-            ensure_root(origin)?;
-
-            minting::Module::<T>::transfer_tokens(Self::council_mint(), amount, &destination)
-                .map_err(<&str>::from)?;
-        }
-
-        /// Sets the council rewards which is only applied on new council being elected.
-        #[weight = 10_000_000] // TODO: adjust weight
-        fn set_council_rewards(
-            origin,
-            amount_per_payout: minting::BalanceOf<T>,
-            payout_interval: Option<T::BlockNumber>,
-            first_payout_after_reward_created: T::BlockNumber
-        ) {
-            ensure_root(origin)?;
-
-            AmountPerPayout::<T>::put(amount_per_payout);
-            FirstPayoutAfterRewardCreated::<T>::put(first_payout_after_reward_created);
-
-            if let Some(payout_interval) = payout_interval {
-                PayoutInterval::<T>::put(payout_interval);
-            } else {
-                PayoutInterval::<T>::take();
-            }
-        }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use crate::mock::*;
-    use crate::DispatchResult;
-    use frame_support::*;
-
-    fn add_council_member_as_root(
-        account: <Test as frame_system::Trait>::AccountId,
-    ) -> DispatchResult {
-        Council::add_council_member(frame_system::RawOrigin::Root.into(), account)
-            .map_err(|e| e.into())
-    }
-
-    #[test]
-    fn add_council_member_test() {
-        initial_test_ext().execute_with(|| {
-            assert!(!Council::is_councilor(&1));
-
-            assert_ok!(add_council_member_as_root(1));
-            assert!(Council::is_councilor(&1));
-
-            assert_ok!(add_council_member_as_root(2));
-            assert!(Council::is_councilor(&1));
-            assert!(Council::is_councilor(&2));
-        });
-    }
-
-    #[test]
-    fn remove_council_member_test() {
-        initial_test_ext().execute_with(|| {
-            assert_ok!(add_council_member_as_root(1));
-            assert_ok!(add_council_member_as_root(2));
-            assert_ok!(add_council_member_as_root(3));
-
-            assert_ok!(Council::remove_council_member(
-                frame_system::RawOrigin::Root.into(),
-                2
-            ));
-
-            assert!(!Council::is_councilor(&2));
-            assert!(Council::is_councilor(&1));
-            assert!(Council::is_councilor(&3));
-        });
-    }
-
-    #[test]
-    fn set_council_test() {
-        initial_test_ext().execute_with(|| {
-            assert_ok!(Council::set_council(
-                frame_system::RawOrigin::Root.into(),
-                vec![4, 5, 6]
-            ));
-            assert!(Council::is_councilor(&4));
-            assert!(Council::is_councilor(&5));
-            assert!(Council::is_councilor(&6));
-        });
-    }
-
-    #[test]
-    fn council_elected_test() {
-        initial_test_ext().execute_with(|| {
-            // Ensure a mint is created so we can create rewards
-            assert_ok!(Council::set_council_mint_capacity(
-                frame_system::RawOrigin::Root.into(),
-                1000
-            ));
-
-            Council::council_elected(
-                vec![
-                    Seat {
-                        member: 5,
-                        stake: 0,
-                        backers: vec![],
-                    },
-                    Seat {
-                        member: 6,
-                        stake: 0,
-                        backers: vec![],
-                    },
-                    Seat {
-                        member: 7,
-                        stake: 0,
-                        backers: vec![],
-                    },
-                ],
-                50 as u64, // <Test as frame_system::Trait>::BlockNumber::from(50)
-            );
-
-            assert!(Council::is_councilor(&5));
-            assert!(Council::is_councilor(&6));
-            assert!(Council::is_councilor(&7));
-
-            assert!(RewardRelationships::<Test>::contains_key(&5));
-            assert!(RewardRelationships::<Test>::contains_key(&6));
-            assert!(RewardRelationships::<Test>::contains_key(&7));
-        });
-    }
-}

+ 0 - 2154
runtime-modules/governance/src/election.rs

@@ -1,2154 +0,0 @@
-//! Council Elections Manager
-//!
-//! # Election Parameters:
-//! We don't currently handle zero periods, zero council term, zero council size and candidacy
-//! limit in any special way. The behaviour in such cases:
-//!
-//! - Setting any period to 0 will mean the election getting stuck in that stage, until force changing
-//! the state.
-//!
-//! - Council Size of 0 - no limit to size of council, all applicants that move beyond
-//! announcing stage would become council members, so effectively the candidacy limit will
-//! be the size of the council, voting and revealing have no impact on final results.
-//!
-//! - If candidacy limit is zero and council size > 0, council_size number of applicants will reach the voting stage.
-//! and become council members, voting will have no impact on final results.
-//!
-//! - If both candidacy limit and council size are zero then all applicant become council members
-//! since no filtering occurs at end of announcing stage.
-//!
-//! We only guard against these edge cases in the [`set_election_parameters`] call.
-//!
-//! [`set_election_parameters`]: struct.Module.html#method.set_election_parameters
-
-// Clippy linter warning
-#![allow(clippy::type_complexity)]
-// disable it because of possible frontend API break
-// TODO: remove post-Constaninople
-
-// Clippy linter warning
-#![allow(clippy::redundant_closure_call)] // disable it because of the substrate lib design
-
-#[cfg(feature = "std")]
-use serde::{Deserialize, Serialize};
-
-use codec::{Decode, Encode};
-use frame_support::traits::{Currency, ReservableCurrency};
-use frame_support::{decl_event, decl_module, decl_storage, ensure};
-use frame_system::{ensure_root, ensure_signed};
-use sp_arithmetic::traits::Zero;
-use sp_runtime::traits::Hash;
-use sp_std::collections::btree_map::BTreeMap;
-use sp_std::ops::Add;
-use sp_std::vec;
-use sp_std::vec::Vec;
-
-use super::sealed_vote::SealedVote;
-use super::stake::Stake;
-
-use super::council;
-use crate::election_params::ElectionParameters;
-pub use common::currency::{BalanceOf, GovernanceCurrency};
-
-use crate::DispatchResult;
-
-pub trait Trait:
-    frame_system::Trait + council::Trait + GovernanceCurrency + membership::Trait
-{
-    type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;
-
-    type CouncilElected: CouncilElected<Seats<Self::AccountId, BalanceOf<Self>>, Self::BlockNumber>;
-}
-
-pub static MSG_CANNOT_CHANGE_PARAMS_DURING_ELECTION: &str = "CannotChangeParamsDuringElection";
-
-#[derive(Clone, Copy, Encode, Decode)]
-pub enum ElectionStage<BlockNumber> {
-    Announcing(BlockNumber),
-    Voting(BlockNumber),
-    Revealing(BlockNumber),
-}
-
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
-#[derive(Encode, Decode, Default, Clone, PartialEq, Eq)]
-pub struct Seat<AccountId, Balance> {
-    pub member: AccountId,
-    pub stake: Balance,
-    pub backers: Vec<Backer<AccountId, Balance>>,
-}
-
-impl<AccountId, Balance> Seat<AccountId, Balance>
-where
-    Balance: Add<Output = Balance> + Copy,
-{
-    pub fn calc_total_stake(&self) -> Balance {
-        self.backers
-            .iter()
-            .fold(self.stake, |acc, backer| acc + backer.stake)
-    }
-}
-
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
-#[derive(Encode, Decode, Default, Clone, PartialEq, Eq)]
-pub struct Backer<AccountId, Balance> {
-    pub member: AccountId,
-    pub stake: Balance,
-}
-
-pub type Seats<AccountId, Balance> = Vec<Seat<AccountId, Balance>>;
-
-// Hook for setting a new council when it is elected
-pub trait CouncilElected<Elected, Term> {
-    fn council_elected(new_council: Elected, term: Term);
-}
-
-impl<Elected, Term> CouncilElected<Elected, Term> for () {
-    fn council_elected(_new_council: Elected, _term: Term) {}
-}
-
-impl<Elected, Term, X: CouncilElected<Elected, Term>> CouncilElected<Elected, Term> for (X,) {
-    fn council_elected(new_council: Elected, term: Term) {
-        X::council_elected(new_council, term);
-    }
-}
-// Chain of handlers.
-impl<
-        Elected: Clone,
-        Term: Clone,
-        X: CouncilElected<Elected, Term>,
-        Y: CouncilElected<Elected, Term>,
-    > CouncilElected<Elected, Term> for (X, Y)
-{
-    fn council_elected(new_council: Elected, term: Term) {
-        X::council_elected(new_council.clone(), term.clone());
-        Y::council_elected(new_council, term);
-    }
-}
-
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
-#[derive(Clone, Copy, Encode, Decode, Default)]
-pub struct TransferableStake<Balance> {
-    seat: Balance,
-    backing: Balance,
-}
-
-// can we use a type alias to overcome name clashes of public types with other modules?
-pub type ElectionStake<T> = Stake<BalanceOf<T>>;
-
-decl_storage! {
-    trait Store for Module<T: Trait> as CouncilElection {
-        // Flag for wether to automatically start an election after a council term ends
-        AutoStart get(fn auto_start) config() : bool = true;
-
-        // Current stage if there is an election running
-        Stage get(fn stage): Option<ElectionStage<T::BlockNumber>>;
-
-        // The election round
-        Round get(fn round): u32;
-
-        ExistingStakeHolders get(fn existing_stake_holders): Vec<T::AccountId>;
-        TransferableStakes get(fn transferable_stakes): map hasher(blake2_128_concat)
-            T::AccountId => TransferableStake<BalanceOf<T>>;
-
-        Applicants get(fn applicants): Vec<T::AccountId>;
-        ApplicantStakes get(fn applicant_stakes): map hasher(blake2_128_concat)
-            T::AccountId => ElectionStake<T>;
-
-        Commitments get(fn commitments): Vec<T::Hash>;
-
-        // TODO value type of this map looks scary, is there any way to simplify the notation?
-        Votes get(fn votes): map hasher(blake2_128_concat)
-            T::Hash => SealedVote<T::AccountId, ElectionStake<T>, T::Hash, T::AccountId>;
-
-        // Current Election Parameters.
-        // Should we replace all the individual values with a single ElectionParameters type?
-        // Having them individually makes it more flexible to add and remove new parameters in future
-        // without dealing with migration issues.
-        AnnouncingPeriod get(fn announcing_period): T::BlockNumber;
-        VotingPeriod get(fn voting_period): T::BlockNumber;
-        RevealingPeriod get(fn revealing_period): T::BlockNumber;
-        CouncilSize get(fn council_size): u32;
-        CandidacyLimit get (fn candidacy_limit): u32;
-        MinCouncilStake get(fn min_council_stake): BalanceOf<T>;
-        NewTermDuration get(fn new_term_duration): T::BlockNumber;
-        MinVotingStake get(fn min_voting_stake): BalanceOf<T>;
-    }
-    add_extra_genesis {
-        config(election_parameters): ElectionParameters<BalanceOf<T>, T::BlockNumber>;
-        build(|config: &GenesisConfig<T>| {
-            config.election_parameters.ensure_valid().expect("Invalid Election Parameters");
-            Module::<T>::set_verified_election_parameters(config.election_parameters);
-        });
-    }
-}
-
-// Event for this module.
-decl_event!(
-    pub enum Event<T> where
-    <T as frame_system::Trait>::BlockNumber,
-    <T as frame_system::Trait>::AccountId,
-    <T as frame_system::Trait>::Hash  {
-        /// A new election started
-        ElectionStarted(),
-        AnnouncingStarted(u32),
-        AnnouncingEnded(),
-        VotingStarted(),
-        VotingEnded(),
-        RevealingStarted(),
-        RevealingEnded(),
-        CouncilElected(BlockNumber),
-        Applied(AccountId),
-        Voted(AccountId, Hash),
-        Revealed(AccountId, Hash, AccountId),
-    }
-);
-
-impl<T: Trait> Module<T> {
-    // HELPERS - IMMUTABLES
-
-    fn council_size_usize() -> usize {
-        Self::council_size() as usize
-    }
-
-    fn candidacy_limit_usize() -> usize {
-        Self::candidacy_limit() as usize
-    }
-
-    fn current_block_number_plus(length: T::BlockNumber) -> T::BlockNumber {
-        <frame_system::Module<T>>::block_number() + length
-    }
-
-    fn can_participate(sender: &T::AccountId) -> bool {
-        !<T as GovernanceCurrency>::Currency::free_balance(sender).is_zero()
-            && <membership::Module<T>>::is_member_account(sender)
-    }
-
-    // PUBLIC IMMUTABLES
-
-    /// Returns true if an election is running
-    pub fn is_election_running() -> bool {
-        Self::stage().is_some()
-    }
-
-    /// Returns block number at which current stage will end if an election is running.
-    pub fn stage_ends_at() -> Option<T::BlockNumber> {
-        if let Some(stage) = Self::stage() {
-            match stage {
-                ElectionStage::Announcing(ends) => Some(ends),
-                ElectionStage::Voting(ends) => Some(ends),
-                ElectionStage::Revealing(ends) => Some(ends),
-            }
-        } else {
-            None
-        }
-    }
-
-    // PRIVATE MUTABLES
-
-    /// Starts an election. Will fail if an election is already running
-    /// Initializes transferable stakes. Assumes election parameters have already been set.
-    fn start_election(current_council: Seats<T::AccountId, BalanceOf<T>>) -> DispatchResult {
-        ensure!(!Self::is_election_running(), "election already in progress");
-        ensure!(
-            Self::existing_stake_holders().is_empty(),
-            "stake holders must be empty"
-        );
-        ensure!(Self::applicants().is_empty(), "applicants must be empty");
-        ensure!(Self::commitments().is_empty(), "commitments must be empty");
-
-        // Take snapshot of seat and backing stakes of an existing council
-        // Its important to note that the election system takes ownership of these stakes, and is responsible
-        // to return any unused stake to original owners at the end of the election.
-        Self::initialize_transferable_stakes(current_council);
-
-        Self::deposit_event(RawEvent::ElectionStarted());
-
-        Self::move_to_announcing_stage();
-        Ok(())
-    }
-
-    /// Sets announcing stage. Can be called from any stage and assumes all preparatory work
-    /// for entering the stage has been performed.
-    /// Bumps the election round.
-    fn move_to_announcing_stage() {
-        let next_round = Round::mutate(|n| {
-            *n += 1;
-            *n
-        });
-
-        let new_stage_ends_at = Self::current_block_number_plus(Self::announcing_period());
-
-        <Stage<T>>::put(ElectionStage::Announcing(new_stage_ends_at));
-
-        Self::deposit_event(RawEvent::AnnouncingStarted(next_round));
-    }
-
-    /// Sets announcing stage. Can be called from any stage and assumes all preparatory work
-    /// for entering the stage has been performed.
-    fn move_to_voting_stage() {
-        let new_stage_ends_at = Self::current_block_number_plus(Self::voting_period());
-
-        <Stage<T>>::put(ElectionStage::Voting(new_stage_ends_at));
-
-        Self::deposit_event(RawEvent::VotingStarted());
-    }
-
-    /// Sets announcing stage. Can be called from any stage and assumes all preparatory work
-    /// for entering the stage has been performed.
-    fn move_to_revealing_stage() {
-        let new_stage_ends_at = Self::current_block_number_plus(Self::revealing_period());
-
-        <Stage<T>>::put(ElectionStage::Revealing(new_stage_ends_at));
-
-        Self::deposit_event(RawEvent::RevealingStarted());
-    }
-
-    /// Sorts applicants by stake, and returns slice of applicants with least stake. Applicants not
-    /// returned in the slice are the top `len` highest staked.
-    fn find_least_staked_applicants(
-        applicants: &mut Vec<T::AccountId>,
-        len: usize,
-    ) -> &[T::AccountId] {
-        if len >= applicants.len() {
-            &[]
-        } else {
-            #[allow(clippy::redundant_closure)] // disable incorrect Clippy linter warning
-            applicants.sort_by_key(|applicant| Self::applicant_stakes(applicant));
-            &applicants[0..applicants.len() - len]
-        }
-    }
-
-    fn on_announcing_ended() {
-        let mut applicants = Self::applicants();
-
-        if applicants.len() < Self::council_size_usize() {
-            // Not enough applicants announced candidacy
-            Self::move_to_announcing_stage();
-        } else {
-            // upper limit on applicants that will move to voting stage
-            let limit = sp_std::cmp::max(Self::council_size_usize(), Self::candidacy_limit_usize());
-            let applicants_to_drop = Self::find_least_staked_applicants(&mut applicants, limit);
-
-            Self::drop_applicants(applicants_to_drop);
-
-            Self::move_to_voting_stage();
-        }
-    }
-
-    fn on_voting_ended() {
-        Self::move_to_revealing_stage();
-    }
-
-    fn on_revealing_ended() {
-        // tally the revealed votes
-        let mut votes = Vec::new();
-
-        for commitment in Self::commitments().iter() {
-            votes.push(Self::votes(commitment));
-        }
-
-        let mut new_council = Self::tally_votes(&votes);
-
-        // Note here that applicants with zero votes dont appear in the tally.
-        // Is an applicant with some votes but less total stake than another applicant with zero votes
-        // more qualified to be on the council?
-        // Consider implications - if a council can be formed purely by staking are we fine with that?
-
-        for applicant in Self::applicants().iter() {
-            if !new_council.contains_key(applicant) {
-                new_council.insert(
-                    applicant.clone(),
-                    Seat {
-                        member: applicant.clone(),
-                        stake: Self::applicant_stakes(applicant).total(),
-                        backers: Vec::new(),
-                    },
-                );
-            }
-        }
-
-        match new_council.len() {
-            ncl if ncl == Self::council_size_usize() => {
-                // all applicants in the tally will form the new council
-            }
-            ncl if ncl > Self::council_size_usize() => {
-                // we have more than enough applicants to form the new council.
-                // select top staked
-                Self::filter_top_staked(&mut new_council, Self::council_size_usize());
-            }
-            _ => {
-                // Not enough applicants with votes to form a council.
-                // This may happen if we didn't add applicants with zero votes to the tally,
-                // or in future if we allow applicants to withdraw candidacy during voting or revealing stages.
-                // or council size was increased during voting, revealing stages.
-            }
-        }
-
-        // unless we want to add more filtering criteria to what is considered a successful election
-        // other than just the minimum stake for candidacy, we have a new council!
-
-        Self::teardown_election(
-            &votes,
-            &new_council,
-            true, /* unlock transferable stakes */
-        );
-
-        let new_council = new_council.into_iter().map(|(_, seat)| seat).collect();
-        T::CouncilElected::council_elected(new_council, Self::new_term_duration());
-
-        Self::deposit_event(RawEvent::CouncilElected(
-            <frame_system::Module<T>>::block_number(),
-        ));
-    }
-
-    fn teardown_election(
-        votes: &[SealedVote<T::AccountId, Stake<BalanceOf<T>>, T::Hash, T::AccountId>],
-        new_council: &BTreeMap<T::AccountId, Seat<T::AccountId, BalanceOf<T>>>,
-        unlock_ts: bool,
-    ) {
-        Self::refund_voting_stakes(&votes, &new_council);
-        Self::clear_votes();
-
-        Self::drop_unelected_applicants(&new_council);
-        Self::clear_applicants();
-
-        if unlock_ts {
-            Self::unlock_transferable_stakes();
-        }
-
-        Self::clear_transferable_stakes();
-
-        <Stage<T>>::kill();
-    }
-
-    fn unlock_transferable_stakes() {
-        // move stakes back to account holder's free balance
-        for stakeholder in Self::existing_stake_holders().iter() {
-            let stake = Self::transferable_stakes(stakeholder);
-            if !stake.seat.is_zero() || !stake.backing.is_zero() {
-                <T as GovernanceCurrency>::Currency::unreserve(
-                    stakeholder,
-                    stake.seat + stake.backing,
-                );
-            }
-        }
-    }
-
-    fn clear_transferable_stakes() {
-        for stakeholder in Self::existing_stake_holders() {
-            <TransferableStakes<T>>::remove(stakeholder);
-        }
-
-        <ExistingStakeHolders<T>>::kill();
-    }
-
-    fn clear_applicants() {
-        for applicant in Self::applicants() {
-            <ApplicantStakes<T>>::remove(applicant);
-        }
-        <Applicants<T>>::kill();
-    }
-
-    fn refund_applicant(applicant: &T::AccountId) {
-        let stake = <ApplicantStakes<T>>::get(applicant);
-
-        // return new stake to account's free balance
-        if !stake.new.is_zero() {
-            <T as GovernanceCurrency>::Currency::unreserve(applicant, stake.new);
-        }
-
-        // return unused transferable stake
-        if !stake.transferred.is_zero() {
-            <TransferableStakes<T>>::mutate(applicant, |transferable| {
-                (*transferable).seat += stake.transferred
-            });
-        }
-    }
-
-    fn drop_applicants(drop: &[T::AccountId]) {
-        let not_dropped: Vec<T::AccountId> = Self::applicants()
-            .into_iter()
-            .filter(|id| !drop.iter().any(|x| *x == *id))
-            .collect();
-
-        for applicant in drop {
-            Self::refund_applicant(applicant);
-            <ApplicantStakes<T>>::remove(applicant);
-        }
-
-        <Applicants<T>>::put(not_dropped);
-    }
-
-    fn drop_unelected_applicants(
-        new_council: &BTreeMap<T::AccountId, Seat<T::AccountId, BalanceOf<T>>>,
-    ) {
-        let applicants_to_drop: Vec<T::AccountId> = Self::applicants()
-            .into_iter()
-            .filter(|applicant| !new_council.contains_key(&applicant))
-            .collect();
-
-        Self::drop_applicants(&applicants_to_drop[..]);
-    }
-
-    fn refund_voting_stakes(
-        sealed_votes: &[SealedVote<T::AccountId, Stake<BalanceOf<T>>, T::Hash, T::AccountId>],
-        new_council: &BTreeMap<T::AccountId, Seat<T::AccountId, BalanceOf<T>>>,
-    ) {
-        for sealed_vote in sealed_votes.iter() {
-            // Do a refund if commitment was not revealed, or the vote was for applicant that did
-            // not get elected to the council
-            // TODO critical: shouldn't we slash the stake in such a case? This is the whole idea behid staking on something: people need to decide carefully and be responsible for their bahavior because they can loose their stake
-            // See https://github.com/Joystream/substrate-node-joystream/issues/4
-            let do_refund = match sealed_vote.get_vote() {
-                Some(applicant) => !new_council.contains_key(&applicant),
-                None => true,
-            };
-
-            if do_refund {
-                // return new stake to account's free balance
-                let SealedVote { voter, stake, .. } = sealed_vote;
-                if !stake.new.is_zero() {
-                    <T as GovernanceCurrency>::Currency::unreserve(voter, stake.new);
-                }
-
-                // return unused transferable stake
-                if !stake.transferred.is_zero() {
-                    <TransferableStakes<T>>::mutate(voter, |transferable| {
-                        (*transferable).backing += stake.transferred
-                    });
-                }
-            }
-        }
-    }
-
-    fn clear_votes() {
-        for commitment in Self::commitments() {
-            <Votes<T>>::remove(commitment);
-        }
-        <Commitments<T>>::kill();
-    }
-
-    fn tally_votes(
-        sealed_votes: &[SealedVote<T::AccountId, Stake<BalanceOf<T>>, T::Hash, T::AccountId>],
-    ) -> BTreeMap<T::AccountId, Seat<T::AccountId, BalanceOf<T>>> {
-        let mut tally: BTreeMap<T::AccountId, Seat<T::AccountId, BalanceOf<T>>> = BTreeMap::new();
-
-        for sealed_vote in sealed_votes.iter() {
-            if let Some(applicant) = sealed_vote.get_vote() {
-                if !tally.contains_key(&applicant) {
-                    // Add new seat
-                    tally.insert(
-                        applicant.clone(),
-                        Seat {
-                            member: applicant.clone(),
-                            stake: Self::applicant_stakes(applicant).total(),
-                            backers: vec![],
-                        },
-                    );
-                }
-                if let Some(seat) = tally.get_mut(&applicant) {
-                    // Add backer to existing seat
-                    seat.backers.push(Backer {
-                        member: sealed_vote.voter.clone(),
-                        stake: sealed_vote.stake.total(),
-                    });
-                }
-            }
-        }
-
-        tally
-    }
-
-    fn filter_top_staked(
-        tally: &mut BTreeMap<T::AccountId, Seat<T::AccountId, BalanceOf<T>>>,
-        limit: usize,
-    ) {
-        if limit >= tally.len() {
-            return;
-        }
-
-        // use ordering in the applicants vector (not ordering resulting from btreemap iteration)
-        let mut seats: Vec<T::AccountId> = Self::applicants()
-            .into_iter()
-            .filter(|id| tally.contains_key(id))
-            .collect();
-
-        // ensure_eq!(seats.len(), tally.len());
-
-        if limit >= seats.len() {
-            // Tally is inconsistent with list of applicants!
-            return;
-        }
-
-        // TODO: order by number of votes, then number of backers
-
-        seats.sort_by_key(|applicant| {
-            tally
-                .get(&applicant)
-                .map_or(Zero::zero(), |seat| seat.calc_total_stake())
-        });
-
-        // seats at bottom of list
-        let filtered_out_seats = &seats[0..seats.len() - limit];
-
-        for id in filtered_out_seats {
-            tally.remove(id);
-        }
-    }
-
-    /// Checks if the current election stage has ended and calls the stage ended handler
-    fn check_if_stage_is_ending(now: T::BlockNumber) {
-        if let Some(stage) = Self::stage() {
-            match stage {
-                ElectionStage::Announcing(ends) => {
-                    if ends == now {
-                        Self::deposit_event(RawEvent::AnnouncingEnded());
-                        Self::on_announcing_ended();
-                    }
-                }
-                ElectionStage::Voting(ends) => {
-                    if ends == now {
-                        Self::deposit_event(RawEvent::VotingEnded());
-                        Self::on_voting_ended();
-                    }
-                }
-                ElectionStage::Revealing(ends) => {
-                    if ends == now {
-                        Self::deposit_event(RawEvent::RevealingEnded());
-                        Self::on_revealing_ended();
-                    }
-                }
-            }
-        }
-    }
-
-    /// Takes a snapshot of the stakes from the current council
-    fn initialize_transferable_stakes(current_council: Seats<T::AccountId, BalanceOf<T>>) {
-        let mut stakeholder_accounts: Vec<T::AccountId> = Vec::new();
-
-        for seat in current_council.into_iter() {
-            let Seat { member, stake, .. } = seat;
-
-            if <TransferableStakes<T>>::contains_key(&member) {
-                <TransferableStakes<T>>::mutate(&member, |transferbale_stake| {
-                    *transferbale_stake = TransferableStake {
-                        seat: transferbale_stake.seat + stake,
-                        backing: transferbale_stake.backing,
-                    }
-                });
-            } else {
-                <TransferableStakes<T>>::insert(
-                    &member,
-                    TransferableStake {
-                        seat: stake,
-                        backing: BalanceOf::<T>::zero(),
-                    },
-                );
-
-                stakeholder_accounts.push(member);
-            }
-
-            for backer in seat.backers.into_iter() {
-                let Backer { member, stake, .. } = backer;
-
-                if <TransferableStakes<T>>::contains_key(&member) {
-                    <TransferableStakes<T>>::mutate(&member, |transferbale_stake| {
-                        *transferbale_stake = TransferableStake {
-                            seat: transferbale_stake.seat,
-                            backing: transferbale_stake.backing + stake,
-                        }
-                    });
-                } else {
-                    <TransferableStakes<T>>::insert(
-                        &member,
-                        TransferableStake {
-                            seat: BalanceOf::<T>::zero(),
-                            backing: stake,
-                        },
-                    );
-
-                    stakeholder_accounts.push(member);
-                }
-            }
-        }
-
-        <ExistingStakeHolders<T>>::put(stakeholder_accounts);
-    }
-
-    fn new_stake_reusing_transferable(
-        transferable: &mut BalanceOf<T>,
-        new_stake: BalanceOf<T>,
-    ) -> Stake<BalanceOf<T>> {
-        let transferred = if *transferable >= new_stake {
-            new_stake
-        } else {
-            *transferable
-        };
-
-        *transferable -= transferred;
-
-        Stake {
-            new: new_stake - transferred,
-            transferred,
-        }
-    }
-
-    fn try_add_applicant(applicant: T::AccountId, stake: BalanceOf<T>) -> DispatchResult {
-        let mut transferable_stake = <TransferableStakes<T>>::get(&applicant);
-
-        let new_stake = Self::new_stake_reusing_transferable(&mut transferable_stake.seat, stake);
-
-        ensure!(
-            <T as GovernanceCurrency>::Currency::can_reserve(&applicant, new_stake.new),
-            "not enough free balance to reserve"
-        );
-
-        ensure!(
-            <T as GovernanceCurrency>::Currency::reserve(&applicant, new_stake.new).is_ok(),
-            "failed to reserve applicant stake!"
-        );
-
-        let applicant_stake = <ApplicantStakes<T>>::get(&applicant);
-        let total_stake = applicant_stake.add(&new_stake);
-
-        if <TransferableStakes<T>>::contains_key(&applicant) {
-            <TransferableStakes<T>>::insert(&applicant, transferable_stake);
-        }
-
-        if !<ApplicantStakes<T>>::contains_key(&applicant) {
-            // insert element at the begining, this gives priority to early applicants
-            // when ordering applicants by stake if stakes are equal
-            <Applicants<T>>::mutate(|applicants| applicants.insert(0, applicant.clone()));
-        }
-
-        <ApplicantStakes<T>>::insert(applicant, total_stake);
-
-        Ok(())
-    }
-
-    fn try_add_vote(
-        voter: T::AccountId,
-        stake: BalanceOf<T>,
-        commitment: T::Hash,
-    ) -> DispatchResult {
-        ensure!(
-            !<Votes<T>>::contains_key(commitment),
-            "duplicate commitment"
-        );
-
-        let mut transferable_stake = <TransferableStakes<T>>::get(&voter);
-
-        let vote_stake =
-            Self::new_stake_reusing_transferable(&mut transferable_stake.backing, stake);
-
-        ensure!(
-            <T as GovernanceCurrency>::Currency::can_reserve(&voter, vote_stake.new),
-            "not enough free balance to reserve"
-        );
-
-        ensure!(
-            <T as GovernanceCurrency>::Currency::reserve(&voter, vote_stake.new).is_ok(),
-            "failed to reserve voting stake!"
-        );
-
-        <Commitments<T>>::mutate(|commitments| commitments.push(commitment));
-
-        <Votes<T>>::insert(
-            commitment,
-            SealedVote::new(voter.clone(), vote_stake, commitment),
-        );
-
-        if <TransferableStakes<T>>::contains_key(&voter) {
-            <TransferableStakes<T>>::insert(&voter, transferable_stake);
-        }
-
-        Ok(())
-    }
-
-    fn try_reveal_vote(
-        voter: T::AccountId,
-        commitment: T::Hash,
-        vote_for: T::AccountId,
-        salt: Vec<u8>,
-    ) -> DispatchResult {
-        ensure!(
-            <Votes<T>>::contains_key(&commitment),
-            "commitment not found"
-        );
-
-        let mut sealed_vote = <Votes<T>>::get(&commitment);
-
-        ensure!(sealed_vote.is_not_revealed(), "vote already revealed");
-        // only voter can reveal their own votes
-        ensure!(sealed_vote.is_owned_by(voter), "only voter can reveal vote");
-        ensure!(
-            <ApplicantStakes<T>>::contains_key(&vote_for),
-            "vote for non-applicant not allowed"
-        );
-
-        let mut salt = salt;
-
-        // Tries to unseal, if salt is invalid will return error
-        sealed_vote.unseal(
-            vote_for,
-            &mut salt,
-            <T as frame_system::Trait>::Hashing::hash,
-        )?;
-
-        // Update the revealed vote
-        <Votes<T>>::insert(commitment, sealed_vote);
-
-        Ok(())
-    }
-
-    fn set_verified_election_parameters(params: ElectionParameters<BalanceOf<T>, T::BlockNumber>) {
-        <AnnouncingPeriod<T>>::put(params.announcing_period);
-        <VotingPeriod<T>>::put(params.voting_period);
-        <RevealingPeriod<T>>::put(params.revealing_period);
-        <MinCouncilStake<T>>::put(params.min_council_stake);
-        <NewTermDuration<T>>::put(params.new_term_duration);
-        CouncilSize::put(params.council_size);
-        CandidacyLimit::put(params.candidacy_limit);
-        <MinVotingStake<T>>::put(params.min_voting_stake);
-    }
-}
-
-decl_module! {
-    pub struct Module<T: Trait> for enum Call where origin: T::Origin {
-        fn deposit_event() = default;
-
-        // No origin so this is a priviledged call
-        fn on_finalize(now: T::BlockNumber) {
-            Self::check_if_stage_is_ending(now);
-        }
-
-        // Member can apply during announcing stage only. On first call a minimum stake will need to be provided.
-        // Member can make subsequent calls during announcing stage to increase their stake.
-        #[weight = 10_000_000] // TODO: adjust weight
-        pub fn apply(origin, stake: BalanceOf<T>) {
-            let sender = ensure_signed(origin)?;
-            ensure!(Self::can_participate(&sender), "Only members can apply to be on council");
-
-            let stage = Self::stage();
-            ensure!(Self::stage().is_some(), "election not running");
-
-            let is_announcing = matches!(stage.unwrap(), ElectionStage::Announcing(_));
-            ensure!(is_announcing, "election not in announcing stage");
-
-            // minimum stake on first attempt to apply
-            if !<ApplicantStakes<T>>::contains_key(&sender) {
-                ensure!(stake >= Self::min_council_stake(), "minimum stake must be provided");
-            }
-
-            Self::try_add_applicant(sender.clone(), stake)?;
-
-            Self::deposit_event(RawEvent::Applied(sender));
-        }
-
-        #[weight = 10_000_000] // TODO: adjust weight
-        fn vote(origin, commitment: T::Hash, stake: BalanceOf<T>) {
-            let sender = ensure_signed(origin)?;
-            ensure!(Self::can_participate(&sender), "Only members can vote for an applicant");
-
-            let stage = Self::stage();
-            ensure!(Self::stage().is_some(), "election not running");
-
-            let is_voting =  matches!(stage.unwrap(), ElectionStage::Voting(_));
-            ensure!(is_voting, "election not in voting stage");
-
-            ensure!(stake >= Self::min_voting_stake(), "voting stake too low");
-            Self::try_add_vote(sender.clone(), stake, commitment)?;
-            Self::deposit_event(RawEvent::Voted(sender, commitment));
-        }
-
-        #[weight = 10_000_000] // TODO: adjust weight
-        fn reveal(origin, commitment: T::Hash, vote: T::AccountId, salt: Vec<u8>) {
-            let sender = ensure_signed(origin)?;
-
-            ensure!(salt.len() <= 32, "salt too large"); // at most 256 bits salt
-
-            let stage = Self::stage();
-            ensure!(Self::stage().is_some(), "election not running");
-
-            let is_revealing = matches!(stage.unwrap(), ElectionStage::Revealing(_));
-            ensure!(is_revealing, "election not in revealing stage");
-
-            Self::try_reveal_vote(sender.clone(), commitment, vote.clone(), salt)?;
-            Self::deposit_event(RawEvent::Revealed(sender, commitment, vote));
-        }
-
-        #[weight = 10_000_000] // TODO: adjust weight
-        fn set_stage_announcing(origin, ends_at: T::BlockNumber) {
-            ensure_root(origin)?;
-            ensure!(ends_at > <frame_system::Module<T>>::block_number(), "must end at future block number");
-            <Stage<T>>::put(ElectionStage::Announcing(ends_at));
-        }
-
-        #[weight = 10_000_000] // TODO: adjust weight
-        fn set_stage_revealing(origin, ends_at: T::BlockNumber) {
-            ensure_root(origin)?;
-            ensure!(ends_at > <frame_system::Module<T>>::block_number(), "must end at future block number");
-            <Stage<T>>::put(ElectionStage::Revealing(ends_at));
-        }
-
-        #[weight = 10_000_000] // TODO: adjust weight
-        fn set_stage_voting(origin, ends_at: T::BlockNumber) {
-            ensure_root(origin)?;
-            ensure!(ends_at > <frame_system::Module<T>>::block_number(), "must end at future block number");
-            <Stage<T>>::put(ElectionStage::Voting(ends_at));
-        }
-
-        /// Sets new election parameters. Some combination of parameters that are not desirable, so
-        /// the parameters are checked for validity.
-        /// The call will fail if an election is in progress. If a council is not being elected for some
-        /// reaon after multiple rounds, force_stop_election() can be called to stop elections and followed by
-        /// set_election_parameters().
-        #[weight = 10_000_000] // TODO: adjust weight
-        pub fn set_election_parameters(origin, params: ElectionParameters<BalanceOf<T>, T::BlockNumber>) {
-            ensure_root(origin)?;
-            ensure!(!Self::is_election_running(), MSG_CANNOT_CHANGE_PARAMS_DURING_ELECTION);
-            params.ensure_valid()?;
-            Self::set_verified_election_parameters(params);
-        }
-
-        #[weight = 10_000_000] // TODO: adjust weight
-        fn force_stop_election(origin) {
-            ensure_root(origin)?;
-            ensure!(Self::is_election_running(), "only running election can be stopped");
-
-            let mut votes = Vec::new();
-            for commitment in Self::commitments() {
-                votes.push(Self::votes(commitment));
-            }
-
-            // no council gets elected
-            let empty_council = BTreeMap::new();
-
-            Self::teardown_election (
-                &votes,
-                &empty_council,
-                false /* do not unlock transferable stakes */
-            );
-        }
-
-        #[weight = 10_000_000] // TODO: adjust weight
-        pub fn force_start_election(origin) {
-            ensure_root(origin)?;
-            Self::start_election(<council::Module<T>>::active_council())?;
-        }
-
-        #[weight = 10_000_000] // TODO: adjust weight
-        fn set_auto_start (origin, flag: bool) {
-            ensure_root(origin)?;
-            AutoStart::put(flag);
-        }
-    }
-}
-
-impl<T: Trait> council::CouncilTermEnded for Module<T> {
-    fn council_term_ended() {
-        if Self::auto_start() {
-            let _ = Self::start_election(<council::Module<T>>::active_council());
-        }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use crate::mock::*;
-    use codec::Encode;
-    use frame_support::traits::OnFinalize;
-    use frame_support::{assert_err, assert_ok};
-    use frame_system::RawOrigin;
-
-    #[test]
-    fn election_starts_when_council_term_ends() {
-        initial_test_ext().execute_with(|| {
-            System::set_block_number(1);
-
-            assert!(Council::is_term_ended());
-            assert!(Election::stage().is_none());
-
-            <Election as council::CouncilTermEnded>::council_term_ended();
-
-            assert!(Election::stage().is_some());
-        });
-    }
-
-    #[test]
-    fn new_stake_reusing_transferable_works() {
-        {
-            let mut transferable = 0;
-            let additional = 100;
-            let new_stake = Election::new_stake_reusing_transferable(&mut transferable, additional);
-            assert_eq!(new_stake.new, 100);
-            assert_eq!(new_stake.transferred, 0);
-        }
-
-        {
-            let mut transferable = 40;
-            let additional = 60;
-            let new_stake = Election::new_stake_reusing_transferable(&mut transferable, additional);
-            assert_eq!(new_stake.new, 20);
-            assert_eq!(new_stake.transferred, 40);
-            assert_eq!(transferable, 0);
-        }
-
-        {
-            let mut transferable = 1000;
-            let additional = 100;
-            let new_stake = Election::new_stake_reusing_transferable(&mut transferable, additional);
-            assert_eq!(new_stake.new, 0);
-            assert_eq!(new_stake.transferred, 100);
-            assert_eq!(transferable, 900);
-        }
-    }
-
-    #[test]
-    fn check_default_params() {
-        // TODO missing test implementation?
-    }
-
-    #[test]
-    fn should_not_start_new_election_if_already_started() {
-        initial_test_ext().execute_with(|| {
-            assert_ok!(Election::start_election(vec![]));
-            assert_err!(
-                Election::start_election(vec![]),
-                "election already in progress"
-            );
-        });
-    }
-
-    fn assert_announcing_period(expected_period: <Test as frame_system::Trait>::BlockNumber) {
-        assert!(
-            Election::is_election_running(),
-            "Election Stage was not set"
-        );
-
-        let election_stage = Election::stage().unwrap();
-
-        match election_stage {
-            election::ElectionStage::Announcing(period) => {
-                assert_eq!(period, expected_period, "Election period not set correctly")
-            }
-            _ => assert!(false, "Election Stage was not correctly set to Announcing"),
-        }
-    }
-
-    #[test]
-    fn start_election_should_work() {
-        initial_test_ext().execute_with(|| {
-            System::set_block_number(1);
-            <AnnouncingPeriod<Test>>::put(20);
-            let prev_round = Election::round();
-
-            assert_ok!(Election::start_election(vec![]));
-
-            // election round is bumped
-            assert_eq!(Election::round(), prev_round + 1);
-
-            // we enter the announcing stage for a specified period
-            assert_announcing_period(1 + Election::announcing_period());
-        });
-    }
-
-    #[test]
-    fn init_transferable_stake_should_work() {
-        initial_test_ext().execute_with(|| {
-            let existing_council = vec![
-                Seat {
-                    member: 1,
-                    stake: 100,
-                    backers: vec![
-                        Backer {
-                            member: 2,
-                            stake: 50,
-                        },
-                        Backer {
-                            member: 3,
-                            stake: 40,
-                        },
-                        Backer {
-                            member: 10,
-                            stake: 10,
-                        },
-                    ],
-                },
-                Seat {
-                    member: 2,
-                    stake: 200,
-                    backers: vec![
-                        Backer {
-                            member: 1,
-                            stake: 10,
-                        },
-                        Backer {
-                            member: 3,
-                            stake: 60,
-                        },
-                        Backer {
-                            member: 20,
-                            stake: 20,
-                        },
-                    ],
-                },
-                Seat {
-                    member: 3,
-                    stake: 300,
-                    backers: vec![
-                        Backer {
-                            member: 1,
-                            stake: 20,
-                        },
-                        Backer {
-                            member: 2,
-                            stake: 40,
-                        },
-                    ],
-                },
-            ];
-
-            Election::initialize_transferable_stakes(existing_council);
-            let mut existing_stake_holders = Election::existing_stake_holders();
-            existing_stake_holders.sort();
-            assert_eq!(existing_stake_holders, vec![1, 2, 3, 10, 20]);
-
-            assert_eq!(Election::transferable_stakes(&1).seat, 100);
-            assert_eq!(Election::transferable_stakes(&1).backing, 30);
-
-            assert_eq!(Election::transferable_stakes(&2).seat, 200);
-            assert_eq!(Election::transferable_stakes(&2).backing, 90);
-
-            assert_eq!(Election::transferable_stakes(&3).seat, 300);
-            assert_eq!(Election::transferable_stakes(&3).backing, 100);
-
-            assert_eq!(Election::transferable_stakes(&10).seat, 0);
-            assert_eq!(Election::transferable_stakes(&10).backing, 10);
-
-            assert_eq!(Election::transferable_stakes(&20).seat, 0);
-            assert_eq!(Election::transferable_stakes(&20).backing, 20);
-        });
-    }
-
-    #[test]
-    fn try_add_applicant_should_work() {
-        initial_test_ext().execute_with(|| {
-            assert!(Election::applicants().len() == 0);
-
-            let applicant = 20 as u64;
-
-            let starting_balance = 1000 as u64;
-            let _ = Balances::deposit_creating(&applicant, starting_balance);
-
-            let stake = 100 as u64;
-
-            assert!(Election::try_add_applicant(applicant, stake).is_ok());
-            assert_eq!(Election::applicants(), vec![applicant]);
-
-            assert_eq!(Election::applicant_stakes(applicant).new, stake);
-            assert_eq!(Election::applicant_stakes(applicant).transferred, 0);
-
-            assert_eq!(Balances::free_balance(&applicant), starting_balance - stake);
-        });
-    }
-
-    #[test]
-    fn increasing_applicant_stake_should_work() {
-        initial_test_ext().execute_with(|| {
-            let applicant = 20 as u64;
-            let starting_stake = 100 as u64;
-
-            <Applicants<Test>>::put(vec![applicant]);
-            <ApplicantStakes<Test>>::insert(
-                applicant,
-                Stake {
-                    new: starting_stake,
-                    transferred: 0,
-                },
-            );
-
-            let additional_stake = 100 as u64;
-            let _ = Balances::deposit_creating(&applicant, additional_stake);
-            assert!(Election::try_add_applicant(applicant, additional_stake).is_ok());
-
-            assert_eq!(
-                Election::applicant_stakes(applicant).new,
-                starting_stake + additional_stake
-            );
-            assert_eq!(Election::applicant_stakes(applicant).transferred, 0)
-        });
-    }
-
-    #[test]
-    fn using_transferable_seat_stake_should_work() {
-        initial_test_ext().execute_with(|| {
-            let applicant = 20 as u64;
-            let _ = Balances::deposit_creating(&applicant, 5000);
-
-            <ExistingStakeHolders<Test>>::put(vec![applicant]);
-            save_transferable_stake(
-                applicant,
-                TransferableStake {
-                    seat: 1000,
-                    backing: 0,
-                },
-            );
-
-            <Applicants<Test>>::put(vec![applicant]);
-            let starting_stake = Stake {
-                new: 100,
-                transferred: 0,
-            };
-            <ApplicantStakes<Test>>::insert(applicant, starting_stake);
-
-            // transferable stake covers new stake
-            assert!(Election::try_add_applicant(applicant, 600).is_ok());
-            assert_eq!(
-                Election::applicant_stakes(applicant).new,
-                starting_stake.new
-            );
-            assert_eq!(Election::applicant_stakes(applicant).transferred, 600);
-            assert_eq!(Election::transferable_stakes(applicant).seat, 400);
-            assert_eq!(Balances::free_balance(applicant), 5000);
-
-            // all remaining transferable stake is consumed and free balance covers remaining stake
-            assert!(Election::try_add_applicant(applicant, 1000).is_ok());
-            assert_eq!(
-                Election::applicant_stakes(applicant).new,
-                starting_stake.new + 600
-            );
-            assert_eq!(Election::applicant_stakes(applicant).transferred, 1000);
-            assert_eq!(Election::transferable_stakes(applicant).seat, 0);
-            assert_eq!(Balances::free_balance(applicant), 4400);
-        });
-    }
-
-    #[test]
-    fn moving_to_voting_without_enough_applicants_should_not_work() {
-        initial_test_ext().execute_with(|| {
-            System::set_block_number(1);
-            <AnnouncingPeriod<Test>>::put(20);
-            CouncilSize::put(10);
-            Election::move_to_announcing_stage();
-            let round = Election::round();
-
-            // add applicants
-            <Applicants<Test>>::put(vec![10, 20, 30]);
-            let stake = Stake {
-                new: 10,
-                transferred: 0,
-            };
-
-            let applicants = Election::applicants();
-
-            for applicant in applicants.iter() {
-                <ApplicantStakes<Test>>::insert(applicant, stake);
-            }
-
-            // make sure we are testing the condition that we don't have enough applicants
-            assert!(Election::council_size_usize() > applicants.len());
-
-            // try to move to voting stage
-            let ann_ends = Election::stage_ends_at().unwrap();
-            System::set_block_number(ann_ends);
-            Election::on_announcing_ended();
-
-            // A new round should have been started
-            assert_eq!(Election::round(), round + 1);
-
-            // A new announcing period started
-            assert_announcing_period(ann_ends + Election::announcing_period());
-
-            // applicants list should be unchanged..
-            assert_eq!(Election::applicants(), applicants);
-        });
-    }
-
-    #[test]
-    fn top_applicants_move_to_voting_stage() {
-        initial_test_ext().execute_with(|| {
-            <Applicants<Test>>::put(vec![10, 20, 30, 40]);
-            let mut applicants = Election::applicants();
-
-            for (i, applicant) in applicants.iter().enumerate() {
-                <ApplicantStakes<Test>>::insert(
-                    applicant,
-                    Stake {
-                        new: (i * 10) as u64,
-                        transferred: 0,
-                    },
-                );
-            }
-
-            let rejected = Election::find_least_staked_applicants(&mut applicants, 3);
-            assert_eq!(rejected.to_vec(), vec![10]);
-
-            <Applicants<Test>>::put(vec![40, 30, 20, 10]);
-            let mut applicants = Election::applicants();
-
-            for applicant in applicants.iter() {
-                <ApplicantStakes<Test>>::insert(
-                    applicant,
-                    Stake {
-                        new: 20,
-                        transferred: 0,
-                    },
-                );
-            }
-
-            // stable sort is preserving order when two elements are equivalent
-            let rejected = Election::find_least_staked_applicants(&mut applicants, 3);
-            assert_eq!(rejected.to_vec(), vec![40]);
-        });
-    }
-
-    #[test]
-    fn refunding_applicant_stakes_should_work() {
-        initial_test_ext().execute_with(|| {
-            let _ = Balances::deposit_creating(&1, 1000);
-            let _ = Balances::deposit_creating(&2, 7000);
-            let _ = Balances::reserve(&2, 5000);
-            let _ = Balances::deposit_creating(&3, 8000);
-            let _ = Balances::reserve(&3, 5000);
-
-            <Applicants<Test>>::put(vec![1, 2, 3]);
-
-            save_transferable_stake(
-                1,
-                TransferableStake {
-                    seat: 50,
-                    backing: 0,
-                },
-            );
-            save_transferable_stake(
-                2,
-                TransferableStake {
-                    seat: 0,
-                    backing: 0,
-                },
-            );
-            save_transferable_stake(
-                3,
-                TransferableStake {
-                    seat: 0,
-                    backing: 0,
-                },
-            );
-
-            <ApplicantStakes<Test>>::insert(
-                1,
-                Stake {
-                    new: 100,
-                    transferred: 200,
-                },
-            );
-
-            <ApplicantStakes<Test>>::insert(
-                2,
-                Stake {
-                    new: 300,
-                    transferred: 400,
-                },
-            );
-
-            <ApplicantStakes<Test>>::insert(
-                3,
-                Stake {
-                    new: 500,
-                    transferred: 600,
-                },
-            );
-
-            Election::drop_applicants(&vec![2, 3][..]);
-
-            assert_eq!(Election::applicants(), vec![1]);
-
-            assert_eq!(Election::applicant_stakes(1).new, 100);
-            assert_eq!(Election::applicant_stakes(1).transferred, 200);
-            assert_eq!(Election::transferable_stakes(1).seat, 50);
-            assert_eq!(Balances::free_balance(&1), 1000);
-
-            //assert_eq!(Election::applicant_stakes(2), Default::default());
-            assert!(!<ApplicantStakes<Test>>::contains_key(2));
-            assert_eq!(Election::transferable_stakes(2).seat, 400);
-            assert_eq!(Balances::free_balance(&2), 2300);
-
-            //assert_eq!(Election::applicant_stakes(3), Default::default());
-            assert!(!<ApplicantStakes<Test>>::contains_key(3));
-            assert_eq!(Election::transferable_stakes(3).seat, 600);
-            assert_eq!(Balances::free_balance(&3), 3500);
-        });
-    }
-
-    #[test]
-    fn voting_should_work() {
-        initial_test_ext().execute_with(|| {
-            let _ = Balances::deposit_creating(&20, 1000);
-            let payload = vec![10u8];
-            let commitment = <Test as frame_system::Trait>::Hashing::hash(&payload[..]);
-
-            assert!(Election::try_add_vote(20, 100, commitment).is_ok());
-
-            assert_eq!(Election::commitments(), vec![commitment]);
-            assert_eq!(Election::votes(commitment).voter, 20);
-            assert_eq!(Election::votes(commitment).commitment, commitment);
-            assert_eq!(
-                Election::votes(commitment).stake,
-                Stake {
-                    new: 100,
-                    transferred: 0,
-                }
-            );
-            assert_eq!(Balances::free_balance(&20), 900);
-        });
-    }
-
-    fn save_transferable_stake(id: u64, stake: TransferableStake<u64>) {
-        <TransferableStakes<Test>>::insert(id, stake);
-    }
-
-    #[test]
-    fn votes_can_be_covered_by_transferable_stake() {
-        initial_test_ext().execute_with(|| {
-            let _ = Balances::deposit_creating(&20, 1000);
-
-            save_transferable_stake(
-                20,
-                TransferableStake {
-                    seat: 0,
-                    backing: 500,
-                },
-            );
-
-            let payload = vec![10u8];
-            let commitment = <Test as frame_system::Trait>::Hashing::hash(&payload[..]);
-
-            assert!(Election::try_add_vote(20, 100, commitment).is_ok());
-
-            assert_eq!(Election::commitments(), vec![commitment]);
-            assert_eq!(Election::votes(commitment).voter, 20);
-            assert_eq!(Election::votes(commitment).commitment, commitment);
-            assert_eq!(
-                Election::votes(commitment).stake,
-                Stake {
-                    new: 0,
-                    transferred: 100,
-                }
-            );
-            assert_eq!(Balances::free_balance(&20), 1000);
-        });
-    }
-
-    #[test]
-    fn voting_without_enough_balance_should_not_work() {
-        initial_test_ext().execute_with(|| {
-            let _ = Balances::deposit_creating(&20, 100);
-
-            save_transferable_stake(
-                20,
-                TransferableStake {
-                    seat: 0,
-                    backing: 500,
-                },
-            );
-
-            let payload = vec![10u8];
-            let commitment = <Test as frame_system::Trait>::Hashing::hash(&payload[..]);
-
-            assert!(Election::try_add_vote(20, 1000, commitment).is_err());
-            assert_eq!(Election::commitments(), vec![]);
-            assert!(!<Votes<Test>>::contains_key(commitment));
-            assert_eq!(Balances::free_balance(&20), 100);
-        });
-    }
-
-    #[test]
-    fn voting_with_existing_commitment_should_not_work() {
-        initial_test_ext().execute_with(|| {
-            let _ = Balances::deposit_creating(&20, 1000);
-
-            save_transferable_stake(
-                20,
-                TransferableStake {
-                    seat: 0,
-                    backing: 500,
-                },
-            );
-
-            let payload = vec![10u8];
-            let commitment = <Test as frame_system::Trait>::Hashing::hash(&payload[..]);
-
-            assert!(Election::try_add_vote(20, 100, commitment).is_ok());
-
-            assert_eq!(Election::commitments(), vec![commitment]);
-            assert_eq!(Election::votes(commitment).voter, 20);
-            assert_eq!(Election::votes(commitment).commitment, commitment);
-            assert_eq!(
-                Election::votes(commitment).stake,
-                Stake {
-                    new: 0,
-                    transferred: 100,
-                }
-            );
-            assert_eq!(Balances::free_balance(&20), 1000);
-
-            assert!(Election::try_add_vote(30, 100, commitment).is_err());
-        });
-    }
-
-    fn make_commitment_for_applicant(
-        applicant: <Test as frame_system::Trait>::AccountId,
-        salt: &mut Vec<u8>,
-    ) -> <Test as frame_system::Trait>::Hash {
-        let mut payload = applicant.encode();
-        payload.append(salt);
-        <Test as frame_system::Trait>::Hashing::hash(&payload[..])
-    }
-
-    #[test]
-    fn revealing_vote_works() {
-        initial_test_ext().execute_with(|| {
-            let applicant = 20 as u64;
-            let salt = vec![128u8];
-            let commitment = make_commitment_for_applicant(applicant, &mut salt.clone());
-            let voter = 10 as u64;
-
-            <ApplicantStakes<Test>>::insert(
-                &applicant,
-                Stake {
-                    new: 0,
-                    transferred: 0,
-                },
-            );
-
-            <Votes<Test>>::insert(
-                &commitment,
-                SealedVote::new(
-                    voter,
-                    Stake {
-                        new: 100,
-                        transferred: 0,
-                    },
-                    commitment,
-                ),
-            );
-
-            assert!(<Votes<Test>>::get(commitment).is_not_revealed());
-            assert!(Election::try_reveal_vote(voter, commitment, applicant, salt).is_ok());
-            assert_eq!(
-                <Votes<Test>>::get(commitment).get_vote().unwrap(),
-                applicant
-            );
-        });
-    }
-
-    #[test]
-    fn revealing_with_bad_salt_should_not_work() {
-        initial_test_ext().execute_with(|| {
-            let applicant = 20 as u64;
-            let salt = vec![128u8];
-            let commitment = make_commitment_for_applicant(applicant, &mut salt.clone());
-            let voter = 10 as u64;
-
-            <ApplicantStakes<Test>>::insert(
-                &applicant,
-                Stake {
-                    new: 0,
-                    transferred: 0,
-                },
-            );
-
-            <Votes<Test>>::insert(
-                &commitment,
-                SealedVote::new(
-                    voter,
-                    Stake {
-                        new: 100,
-                        transferred: 0,
-                    },
-                    commitment,
-                ),
-            );
-
-            assert!(<Votes<Test>>::get(commitment).is_not_revealed());
-            assert!(Election::try_reveal_vote(voter, commitment, applicant, vec![]).is_err());
-            assert!(<Votes<Test>>::get(commitment).is_not_revealed());
-        });
-    }
-
-    #[test]
-    fn revealing_non_matching_commitment_should_not_work() {
-        initial_test_ext().execute_with(|| {
-            let applicant = 20 as u64;
-            let salt = vec![128u8];
-            let commitment = make_commitment_for_applicant(100, &mut salt.clone());
-            let voter = 10 as u64;
-
-            <ApplicantStakes<Test>>::insert(
-                &applicant,
-                Stake {
-                    new: 0,
-                    transferred: 0,
-                },
-            );
-
-            assert!(Election::try_reveal_vote(voter, commitment, applicant, vec![]).is_err());
-        });
-    }
-
-    #[test]
-    fn revealing_for_non_applicant_should_not_work() {
-        initial_test_ext().execute_with(|| {
-            let applicant = 20 as u64;
-            let salt = vec![128u8];
-            let commitment = make_commitment_for_applicant(applicant, &mut salt.clone());
-            let voter = 10 as u64;
-
-            <Votes<Test>>::insert(
-                &commitment,
-                SealedVote::new(
-                    voter,
-                    Stake {
-                        new: 100,
-                        transferred: 0,
-                    },
-                    commitment,
-                ),
-            );
-
-            assert!(<Votes<Test>>::get(commitment).is_not_revealed());
-            assert!(Election::try_reveal_vote(voter, commitment, applicant, vec![]).is_err());
-            assert!(<Votes<Test>>::get(commitment).is_not_revealed());
-        });
-    }
-
-    #[test]
-    fn revealing_by_non_committer_should_not_work() {
-        initial_test_ext().execute_with(|| {
-            let applicant = 20 as u64;
-            let salt = vec![128u8];
-            let commitment = make_commitment_for_applicant(applicant, &mut salt.clone());
-            let voter = 10 as u64;
-            let not_voter = 100 as u64;
-
-            <ApplicantStakes<Test>>::insert(
-                &applicant,
-                Stake {
-                    new: 0,
-                    transferred: 0,
-                },
-            );
-
-            <Votes<Test>>::insert(
-                &commitment,
-                SealedVote::new(
-                    voter,
-                    Stake {
-                        new: 100,
-                        transferred: 0,
-                    },
-                    commitment,
-                ),
-            );
-
-            assert!(<Votes<Test>>::get(commitment).is_not_revealed());
-            assert!(Election::try_reveal_vote(not_voter, commitment, applicant, salt).is_err());
-            assert!(<Votes<Test>>::get(commitment).is_not_revealed());
-        });
-    }
-
-    pub fn mock_votes(
-        mock: Vec<(u64, u64, u64, u64)>,
-    ) -> Vec<SealedVote<u64, Stake<u64>, sp_core::H256, u64>> {
-        let commitment = make_commitment_for_applicant(1, &mut vec![0u8]);
-
-        mock.into_iter()
-            .map(|(voter, stake_ref, stake_tran, applicant)| {
-                SealedVote::new_unsealed(
-                    voter as u64,
-                    Stake {
-                        new: stake_ref,
-                        transferred: stake_tran,
-                    },
-                    commitment,
-                    applicant as u64,
-                )
-            })
-            .collect()
-    }
-
-    #[test]
-    fn vote_tallying_should_work() {
-        initial_test_ext().execute_with(|| {
-            let votes = mock_votes(vec![
-                //  (voter, stake[new], stake[transferred], applicant)
-                (10, 100, 0, 100),
-                (10, 150, 0, 100),
-                (10, 500, 0, 200),
-                (20, 200, 0, 200),
-                (30, 300, 0, 300),
-                (30, 400, 0, 300),
-            ]);
-
-            let tally = Election::tally_votes(&votes);
-
-            assert_eq!(tally.len(), 3);
-
-            assert_eq!(tally.get(&100).unwrap().member, 100);
-            assert_eq!(
-                tally.get(&100).unwrap().backers,
-                vec![
-                    Backer {
-                        member: 10 as u64,
-                        stake: 100 as u64,
-                    },
-                    Backer {
-                        member: 10 as u64,
-                        stake: 150 as u64,
-                    },
-                ]
-            );
-
-            assert_eq!(tally.get(&200).unwrap().member, 200);
-            assert_eq!(
-                tally.get(&200).unwrap().backers,
-                vec![
-                    Backer {
-                        member: 10 as u64,
-                        stake: 500 as u64,
-                    },
-                    Backer {
-                        member: 20 as u64,
-                        stake: 200 as u64,
-                    }
-                ]
-            );
-
-            assert_eq!(tally.get(&300).unwrap().member, 300);
-            assert_eq!(
-                tally.get(&300).unwrap().backers,
-                vec![
-                    Backer {
-                        member: 30 as u64,
-                        stake: 300 as u64,
-                    },
-                    Backer {
-                        member: 30 as u64,
-                        stake: 400 as u64,
-                    }
-                ]
-            );
-        });
-    }
-
-    #[test]
-    fn filter_top_staked_applicants_should_work() {
-        initial_test_ext().execute_with(|| {
-            // filter_top_staked depends on order of applicants
-            <Applicants<Test>>::put(vec![100, 200, 300]);
-
-            {
-                let votes = mock_votes(vec![
-                    //  (voter, stake[new], stake[transferred], applicant)
-                    (10, 100, 0, 100),
-                    (10, 150, 0, 100),
-                    (10, 500, 0, 200),
-                    (20, 200, 0, 200),
-                    (30, 300, 0, 300),
-                    (30, 400, 0, 300),
-                ]);
-
-                let mut tally = Election::tally_votes(&votes);
-                assert_eq!(tally.len(), 3);
-                Election::filter_top_staked(&mut tally, 3);
-                assert_eq!(tally.len(), 3);
-            }
-
-            {
-                let votes = mock_votes(vec![
-                    //  (voter, stake[new], stake[transferred], applicant)
-                    (10, 100, 0, 100),
-                    (10, 150, 0, 100),
-                    (10, 500, 0, 200),
-                    (20, 200, 0, 200),
-                    (30, 300, 0, 300),
-                    (30, 400, 0, 300),
-                ]);
-
-                let mut tally = Election::tally_votes(&votes);
-                assert_eq!(tally.len(), 3);
-                Election::filter_top_staked(&mut tally, 2);
-                assert_eq!(tally.len(), 2);
-                assert!(tally.get(&200).is_some());
-                assert!(tally.get(&300).is_some());
-            }
-        });
-    }
-
-    #[test]
-    fn drop_unelected_applicants_should_work() {
-        initial_test_ext().execute_with(|| {
-            <Applicants<Test>>::put(vec![100, 200, 300]);
-
-            let _ = Balances::deposit_creating(&100, 2000);
-            let _ = Balances::reserve(&100, 1000);
-
-            <ApplicantStakes<Test>>::insert(
-                100,
-                Stake {
-                    new: 20 as u64,
-                    transferred: 50 as u64,
-                },
-            );
-
-            save_transferable_stake(
-                100,
-                TransferableStake {
-                    seat: 100,
-                    backing: 0,
-                },
-            );
-
-            let mut new_council: BTreeMap<u64, Seat<u64, u64>> = BTreeMap::new();
-            new_council.insert(
-                200 as u64,
-                Seat {
-                    member: 200 as u64,
-                    stake: 0 as u64,
-                    backers: vec![],
-                },
-            );
-            new_council.insert(
-                300 as u64,
-                Seat {
-                    member: 300 as u64,
-                    stake: 0 as u64,
-                    backers: vec![],
-                },
-            );
-
-            Election::drop_unelected_applicants(&new_council);
-
-            // applicant dropped
-            assert_eq!(Election::applicants(), vec![200, 300]);
-            assert!(!<ApplicantStakes<Test>>::contains_key(100));
-
-            // and refunded
-            assert_eq!(Election::transferable_stakes(100).seat, 150);
-            assert_eq!(Balances::free_balance(&100), 1020);
-            assert_eq!(Balances::reserved_balance(&100), 980);
-        });
-    }
-
-    #[test]
-    fn refunding_voting_stakes_should_work() {
-        initial_test_ext().execute_with(|| {
-            // voters' balances
-            let _ = Balances::deposit_creating(&10, 6000);
-            let _ = Balances::reserve(&10, 5000);
-            let _ = Balances::deposit_creating(&20, 7000);
-            let _ = Balances::reserve(&20, 5000);
-            let _ = Balances::deposit_creating(&30, 8000);
-            let _ = Balances::reserve(&30, 5000);
-
-            save_transferable_stake(
-                10,
-                TransferableStake {
-                    seat: 0,
-                    backing: 100,
-                },
-            );
-            save_transferable_stake(
-                20,
-                TransferableStake {
-                    seat: 0,
-                    backing: 200,
-                },
-            );
-            save_transferable_stake(
-                30,
-                TransferableStake {
-                    seat: 0,
-                    backing: 300,
-                },
-            );
-
-            let votes = mock_votes(vec![
-                //  (voter, stake[new], stake[transferred], applicant)
-                (10, 100, 20, 100),
-                (20, 200, 40, 100),
-                (30, 300, 60, 100),
-                (10, 500, 70, 200),
-                (20, 600, 80, 200),
-                (30, 700, 90, 200),
-                (10, 800, 100, 300),
-                (20, 900, 120, 300),
-                (30, 1000, 140, 300),
-            ]);
-
-            let mut new_council: BTreeMap<u64, Seat<u64, u64>> = BTreeMap::new();
-            new_council.insert(
-                200 as u64,
-                Seat {
-                    member: 200 as u64,
-                    stake: 0 as u64,
-                    backers: vec![],
-                },
-            );
-            new_council.insert(
-                300 as u64,
-                Seat {
-                    member: 300 as u64,
-                    stake: 0 as u64,
-                    backers: vec![],
-                },
-            );
-
-            Election::refund_voting_stakes(&votes, &new_council);
-
-            assert_eq!(Balances::free_balance(&10), 1100);
-            assert_eq!(Balances::reserved_balance(&10), 4900);
-            assert_eq!(Balances::free_balance(&20), 2200);
-            assert_eq!(Balances::reserved_balance(&20), 4800);
-            assert_eq!(Balances::free_balance(&30), 3300);
-            assert_eq!(Balances::reserved_balance(&30), 4700);
-
-            assert_eq!(Election::transferable_stakes(10).backing, 120);
-            assert_eq!(Election::transferable_stakes(20).backing, 240);
-            assert_eq!(Election::transferable_stakes(30).backing, 360);
-        });
-    }
-
-    #[test]
-    fn unlock_transferable_stakes_should_work() {
-        initial_test_ext().execute_with(|| {
-            <ExistingStakeHolders<Test>>::put(vec![10, 20, 30]);
-
-            let _ = Balances::deposit_creating(&10, 6000);
-            let _ = Balances::reserve(&10, 5000);
-            save_transferable_stake(
-                10,
-                TransferableStake {
-                    seat: 50,
-                    backing: 100,
-                },
-            );
-
-            let _ = Balances::deposit_creating(&20, 7000);
-            let _ = Balances::reserve(&20, 5000);
-            save_transferable_stake(
-                20,
-                TransferableStake {
-                    seat: 60,
-                    backing: 200,
-                },
-            );
-
-            let _ = Balances::deposit_creating(&30, 8000);
-            let _ = Balances::reserve(&30, 5000);
-            save_transferable_stake(
-                30,
-                TransferableStake {
-                    seat: 70,
-                    backing: 300,
-                },
-            );
-
-            Election::unlock_transferable_stakes();
-
-            assert_eq!(Balances::free_balance(&10), 1150);
-            assert_eq!(Balances::free_balance(&20), 2260);
-            assert_eq!(Balances::free_balance(&30), 3370);
-        });
-    }
-
-    #[test]
-    fn council_elected_hook_should_work() {
-        initial_test_ext().execute_with(|| {
-            let mut new_council: BTreeMap<u64, Seat<u64, u64>> = BTreeMap::new();
-            new_council.insert(
-                200 as u64,
-                Seat {
-                    member: 200 as u64,
-                    stake: 10 as u64,
-                    backers: vec![],
-                },
-            );
-            new_council.insert(
-                300 as u64,
-                Seat {
-                    member: 300 as u64,
-                    stake: 20 as u64,
-                    backers: vec![],
-                },
-            );
-
-            assert_eq!(Council::active_council().len(), 0);
-
-            let new_council = new_council
-                .into_iter()
-                .map(|(_, seat)| seat.clone())
-                .collect();
-            <Test as election::Trait>::CouncilElected::council_elected(new_council, 10);
-
-            assert_eq!(Council::active_council().len(), 2);
-        });
-    }
-
-    #[test]
-    fn simulation() {
-        initial_test_ext().execute_with(|| {
-            assert_eq!(Council::active_council().len(), 0);
-            assert!(Election::stage().is_none());
-
-            CouncilSize::put(10);
-            <MinCouncilStake<Test>>::put(50);
-            <AnnouncingPeriod<Test>>::put(10);
-            <VotingPeriod<Test>>::put(10);
-            <RevealingPeriod<Test>>::put(10);
-            CandidacyLimit::put(20);
-            <NewTermDuration<Test>>::put(100);
-            <MinVotingStake<Test>>::put(10);
-
-            for i in 1..30 {
-                let _ = Balances::deposit_creating(&(i as u64), 50000);
-            }
-
-            System::set_block_number(1);
-            assert_ok!(Election::start_election(vec![]));
-
-            for i in 1..20 {
-                if i < 21 {
-                    assert!(Election::apply(Origin::signed(i), 150).is_ok());
-                } else {
-                    assert!(Election::apply(Origin::signed(i + 1000), 150).is_err()); // not enough free balance
-                    assert!(Election::apply(Origin::signed(i), 20).is_err()); // not enough minimum stake
-                }
-            }
-
-            let n = 1 + Election::announcing_period();
-            System::set_block_number(n);
-            let _ = Election::on_finalize(n);
-
-            for i in 1..20 {
-                assert!(Election::vote(
-                    Origin::signed(i),
-                    make_commitment_for_applicant(i, &mut vec![40u8]),
-                    100
-                )
-                .is_ok());
-
-                assert!(Election::vote(
-                    Origin::signed(i),
-                    make_commitment_for_applicant(i, &mut vec![41u8]),
-                    100
-                )
-                .is_ok());
-
-                assert!(Election::vote(
-                    Origin::signed(i),
-                    make_commitment_for_applicant(i + 1000, &mut vec![42u8]),
-                    100
-                )
-                .is_ok());
-            }
-
-            let n = n + Election::voting_period();
-            System::set_block_number(n);
-            let _ = Election::on_finalize(n);
-
-            for i in 1..20 {
-                assert!(Election::reveal(
-                    Origin::signed(i),
-                    make_commitment_for_applicant(i, &mut vec![40u8]),
-                    i,
-                    vec![40u8]
-                )
-                .is_ok());
-                //wrong salt
-                assert!(Election::reveal(
-                    Origin::signed(i),
-                    make_commitment_for_applicant(i, &mut vec![41u8]),
-                    i,
-                    vec![]
-                )
-                .is_err());
-                //vote not for valid applicant
-                assert!(Election::reveal(
-                    Origin::signed(i),
-                    make_commitment_for_applicant(i + 1000, &mut vec![42u8]),
-                    i + 1000,
-                    vec![42u8]
-                )
-                .is_err());
-            }
-
-            let n = n + Election::revealing_period();
-            System::set_block_number(n);
-            let _ = Election::on_finalize(n);
-
-            assert_eq!(
-                Council::active_council().len(),
-                Election::council_size_usize()
-            );
-            for (i, seat) in Council::active_council().iter().enumerate() {
-                assert_eq!(seat.member, (i + 1) as u64);
-            }
-            assert!(Election::stage().is_none());
-
-            // When council term ends.. start a new election.
-            assert_ok!(Election::start_election(vec![]));
-        });
-    }
-
-    #[test]
-    fn setting_election_parameters() {
-        initial_test_ext().execute_with(|| {
-            let default_parameters: ElectionParameters<u64, u64> = ElectionParameters::default();
-            // default all zeros is invalid
-            assert!(default_parameters.ensure_valid().is_err());
-
-            let new_parameters = ElectionParameters {
-                announcing_period: 1,
-                voting_period: 2,
-                revealing_period: 3,
-                council_size: 4,
-                candidacy_limit: 5,
-                min_voting_stake: 6,
-                min_council_stake: 7,
-                new_term_duration: 8,
-            };
-
-            assert_ok!(Election::set_election_parameters(
-                RawOrigin::Root.into(),
-                new_parameters
-            ));
-
-            assert_eq!(
-                <AnnouncingPeriod<Test>>::get(),
-                new_parameters.announcing_period
-            );
-            assert_eq!(<VotingPeriod<Test>>::get(), new_parameters.voting_period);
-            assert_eq!(
-                <RevealingPeriod<Test>>::get(),
-                new_parameters.revealing_period
-            );
-            assert_eq!(
-                <MinCouncilStake<Test>>::get(),
-                new_parameters.min_council_stake
-            );
-            assert_eq!(
-                <NewTermDuration<Test>>::get(),
-                new_parameters.new_term_duration
-            );
-            assert_eq!(CouncilSize::get(), new_parameters.council_size);
-            assert_eq!(CandidacyLimit::get(), new_parameters.candidacy_limit);
-            assert_eq!(
-                <MinVotingStake<Test>>::get(),
-                new_parameters.min_voting_stake
-            );
-        });
-    }
-}

+ 0 - 51
runtime-modules/governance/src/election_params.rs

@@ -1,51 +0,0 @@
-#[cfg(feature = "std")]
-use serde::{Deserialize, Serialize};
-
-use codec::{Decode, Encode};
-use frame_support::ensure;
-use sp_arithmetic::traits::Zero;
-
-use crate::DispatchResult;
-
-pub static MSG_PERIOD_CANNOT_BE_ZERO: &str = "PeriodCannotBeZero";
-pub static MSG_COUNCIL_SIZE_CANNOT_BE_ZERO: &str = "CouncilSizeCannotBeZero";
-pub static MSG_CANDIDACY_LIMIT_WAS_LOWER_THAN_COUNCIL_SIZE: &str =
-    "CandidacyWasLessThanCouncilSize";
-
-/// Combined Election parameters, as argument for set_election_parameters
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-#[derive(Clone, Copy, Encode, Decode, Default, PartialEq, Debug)]
-pub struct ElectionParameters<Balance, BlockNumber> {
-    pub announcing_period: BlockNumber,
-    pub voting_period: BlockNumber,
-    pub revealing_period: BlockNumber,
-    pub council_size: u32,
-    pub candidacy_limit: u32,
-    pub new_term_duration: BlockNumber,
-    pub min_council_stake: Balance,
-    pub min_voting_stake: Balance,
-}
-
-impl<Balance, BlockNumber: PartialOrd + Zero> ElectionParameters<Balance, BlockNumber> {
-    pub fn ensure_valid(&self) -> DispatchResult {
-        self.ensure_periods_are_valid()?;
-        self.ensure_council_size_and_candidacy_limit_are_valid()?;
-        Ok(())
-    }
-
-    fn ensure_periods_are_valid(&self) -> DispatchResult {
-        ensure!(!self.announcing_period.is_zero(), MSG_PERIOD_CANNOT_BE_ZERO);
-        ensure!(!self.voting_period.is_zero(), MSG_PERIOD_CANNOT_BE_ZERO);
-        ensure!(!self.revealing_period.is_zero(), MSG_PERIOD_CANNOT_BE_ZERO);
-        Ok(())
-    }
-
-    fn ensure_council_size_and_candidacy_limit_are_valid(&self) -> DispatchResult {
-        ensure!(self.council_size > 0, MSG_COUNCIL_SIZE_CANNOT_BE_ZERO);
-        ensure!(
-            self.council_size <= self.candidacy_limit,
-            MSG_CANDIDACY_LIMIT_WAS_LOWER_THAN_COUNCIL_SIZE
-        );
-        Ok(())
-    }
-}

+ 0 - 15
runtime-modules/governance/src/lib.rs

@@ -1,15 +0,0 @@
-// Ensure we're `no_std` when compiling for Wasm.
-#![cfg_attr(not(feature = "std"), no_std)]
-
-pub mod council;
-pub mod election;
-pub mod election_params;
-
-mod sealed_vote;
-mod stake;
-
-mod mock;
-
-//TODO: Convert errors to the Substrate decl_error! macro.
-/// Result with string error message. This exists for backward compatibility purpose.
-pub type DispatchResult = Result<(), &'static str>;

+ 0 - 179
runtime-modules/governance/src/mock.rs

@@ -1,179 +0,0 @@
-#![cfg(test)]
-
-pub use super::{council, election};
-pub use common::currency::GovernanceCurrency;
-
-use frame_support::{impl_outer_origin, parameter_types};
-pub use frame_system;
-use sp_core::H256;
-use sp_runtime::{
-    testing::Header,
-    traits::{BlakeTwo256, IdentityLookup},
-    BuildStorage, DispatchResult, Perbill,
-};
-
-impl_outer_origin! {
-    pub enum Origin for Test {}
-}
-
-// Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted.
-#[derive(Clone, PartialEq, Eq, Debug)]
-pub struct Test;
-parameter_types! {
-    pub const BlockHashCount: u64 = 250;
-    pub const MaximumBlockWeight: u32 = 1024;
-    pub const MaximumBlockLength: u32 = 2 * 1024;
-    pub const AvailableBlockRatio: Perbill = Perbill::one();
-    pub const MinimumPeriod: u64 = 5;
-    pub const DefaultMembershipPrice: u64 = 100;
-    pub const DefaultInitialInvitationBalance: u64 = 100;
-}
-
-impl frame_system::Trait for Test {
-    type BaseCallFilter = ();
-    type Origin = Origin;
-    type Call = ();
-    type Index = u64;
-    type BlockNumber = u64;
-    type Hash = H256;
-    type Hashing = BlakeTwo256;
-    type AccountId = u64;
-    type Lookup = IdentityLookup<Self::AccountId>;
-    type Header = Header;
-    type Event = ();
-    type BlockHashCount = BlockHashCount;
-    type MaximumBlockWeight = MaximumBlockWeight;
-    type DbWeight = ();
-    type BlockExecutionWeight = ();
-    type ExtrinsicBaseWeight = ();
-    type MaximumExtrinsicWeight = ();
-    type MaximumBlockLength = MaximumBlockLength;
-    type AvailableBlockRatio = AvailableBlockRatio;
-    type Version = ();
-    type PalletInfo = ();
-    type AccountData = balances::AccountData<u64>;
-    type OnNewAccount = ();
-    type OnKilledAccount = ();
-    type SystemWeightInfo = ();
-}
-
-impl pallet_timestamp::Trait for Test {
-    type Moment = u64;
-    type OnTimestampSet = ();
-    type MinimumPeriod = MinimumPeriod;
-    type WeightInfo = ();
-}
-impl council::Trait for Test {
-    type Event = ();
-
-    type CouncilTermEnded = (Election,);
-}
-impl election::Trait for Test {
-    type Event = ();
-
-    type CouncilElected = (Council,);
-}
-impl common::Trait for Test {
-    type MemberId = u64;
-    type ActorId = u64;
-}
-impl membership::Trait for Test {
-    type Event = ();
-    type DefaultMembershipPrice = DefaultMembershipPrice;
-    type WorkingGroup = ();
-    type DefaultInitialInvitationBalance = ();
-}
-
-impl common::working_group::WorkingGroupIntegration<Test> for () {
-    fn ensure_worker_origin(
-        _origin: <Test as frame_system::Trait>::Origin,
-        _worker_id: &<Test as common::Trait>::ActorId,
-    ) -> DispatchResult {
-        unimplemented!();
-    }
-
-    fn get_leader_member_id() -> Option<<Test as common::Trait>::MemberId> {
-        unimplemented!();
-    }
-}
-
-impl minting::Trait for Test {
-    type Currency = Balances;
-    type MintId = u64;
-}
-impl recurringrewards::Trait for Test {
-    type PayoutStatusHandler = ();
-    type RecipientId = u64;
-    type RewardRelationshipId = u64;
-}
-parameter_types! {
-    pub const ExistentialDeposit: u32 = 0;
-}
-
-impl balances::Trait for Test {
-    type Balance = u64;
-    type DustRemoval = ();
-    type Event = ();
-    type ExistentialDeposit = ExistentialDeposit;
-    type AccountStore = System;
-    type WeightInfo = ();
-    type MaxLocks = ();
-}
-
-impl GovernanceCurrency for Test {
-    type Currency = balances::Module<Self>;
-}
-
-// TODO add a Hook type to capture TriggerElection and CouncilElected hooks
-
-// This function basically just builds a genesis storage key/value store according to
-// our desired mockup.
-pub fn initial_test_ext() -> sp_io::TestExternalities {
-    let mut t = frame_system::GenesisConfig::default()
-        .build_storage::<Test>()
-        .unwrap();
-
-    let members_config_builder = membership::genesis::GenesisConfigBuilder::<Test>::default()
-        .members(vec![
-            // member_id, account_id
-            (0, 1),
-            (1, 2),
-            (2, 3),
-            (3, 4),
-            (4, 5),
-            (5, 6),
-            (6, 7),
-            (7, 8),
-            (8, 9),
-            (9, 10),
-            (10, 11),
-            (11, 12),
-            (12, 13),
-            (13, 14),
-            (14, 15),
-            (15, 16),
-            (16, 17),
-            (17, 18),
-            (18, 19),
-            (19, 20),
-        ]);
-
-    members_config_builder
-        .build()
-        .assimilate_storage(&mut t)
-        .unwrap();
-
-    // build the council config to initialize the mint
-    let council_config = council::GenesisConfig::<Test>::default()
-        .build_storage()
-        .unwrap();
-
-    council_config.assimilate_storage(&mut t).unwrap();
-
-    t.into()
-}
-
-pub type Election = election::Module<Test>;
-pub type Council = council::Module<Test>;
-pub type System = frame_system::Module<Test>;
-pub type Balances = balances::Module<Test>;

+ 0 - 88
runtime-modules/governance/src/sealed_vote.rs

@@ -1,88 +0,0 @@
-use codec::{Decode, Encode};
-use frame_support::ensure;
-use sp_std::vec::Vec;
-
-#[derive(Clone, Copy, Encode, Decode, Default)]
-pub struct SealedVote<AccountId, Stake, Hash, Vote>
-where
-    Vote: Encode,
-    Hash: PartialEq,
-    AccountId: PartialEq,
-{
-    pub voter: AccountId,
-    pub commitment: Hash, // 32 bytes - salted hash of serialized Vote
-    pub stake: Stake,
-    vote: Option<Vote>, // will be set when unsealing
-}
-
-impl<AccountId, Stake, Hash, Vote> SealedVote<AccountId, Stake, Hash, Vote>
-where
-    Vote: Encode,
-    Hash: PartialEq,
-    AccountId: PartialEq,
-{
-    pub fn new(
-        voter: AccountId,
-        stake: Stake,
-        commitment: Hash,
-    ) -> SealedVote<AccountId, Stake, Hash, Vote> {
-        SealedVote {
-            voter,
-            commitment,
-            stake,
-            vote: None,
-        }
-    }
-
-    pub fn new_unsealed(
-        voter: AccountId,
-        stake: Stake,
-        commitment: Hash,
-        vote: Vote,
-    ) -> SealedVote<AccountId, Stake, Hash, Vote> {
-        SealedVote {
-            voter,
-            commitment,
-            stake,
-            vote: Some(vote),
-        }
-    }
-
-    pub fn unseal(
-        &mut self,
-        vote: Vote,
-        salt: &mut Vec<u8>,
-        hasher: fn(&[u8]) -> Hash,
-    ) -> Result<(), &'static str> {
-        // only unseal once
-        ensure!(self.is_not_revealed(), "vote already unsealed");
-
-        // seralize the vote and append the salt
-        let mut payload = vote.encode();
-        payload.append(salt);
-
-        // hash the payload, if it matches the commitment it is a valid revealing of the vote
-        if self.commitment == hasher(&payload) {
-            self.vote = Some(vote);
-            Ok(())
-        } else {
-            Err("invalid salt")
-        }
-    }
-
-    pub fn get_vote(&self) -> &Option<Vote> {
-        &self.vote
-    }
-
-    pub fn is_owned_by(&self, someone: AccountId) -> bool {
-        someone == self.voter
-    }
-
-    pub fn is_revealed(&self) -> bool {
-        self.vote.is_some()
-    }
-
-    pub fn is_not_revealed(&self) -> bool {
-        self.vote.is_none()
-    }
-}

+ 0 - 120
runtime-modules/governance/src/stake.rs

@@ -1,120 +0,0 @@
-use codec::{Decode, Encode};
-use sp_arithmetic::traits::BaseArithmetic;
-use sp_std::cmp::Ordering;
-
-#[derive(Encode, Decode, Clone, Copy, Default, Debug)]
-pub struct Stake<Balance>
-where
-    Balance: Copy + BaseArithmetic,
-{
-    pub new: Balance,
-    pub transferred: Balance,
-}
-
-impl<Balance> Stake<Balance>
-where
-    Balance: Copy + BaseArithmetic,
-{
-    pub fn total(&self) -> Balance {
-        self.new + self.transferred
-    }
-
-    pub fn add(&self, v: &Self) -> Self {
-        Self {
-            new: self.new + v.new,
-            transferred: self.transferred + v.transferred,
-        }
-    }
-}
-
-impl<T: Copy + BaseArithmetic> PartialOrd for Stake<T> {
-    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
-        Some(self.cmp(&other))
-    }
-}
-
-impl<T: Copy + BaseArithmetic> Ord for Stake<T> {
-    fn cmp(&self, other: &Self) -> Ordering {
-        self.total().cmp(&other.total())
-    }
-}
-
-impl<T: Copy + BaseArithmetic> PartialEq for Stake<T> {
-    fn eq(&self, other: &Self) -> bool {
-        self.total() == other.total()
-    }
-}
-
-impl<T: Copy + BaseArithmetic> Eq for Stake<T> {}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn total() {
-        let a: u128 = 4;
-        let b: u128 = 5;
-        let s = Stake {
-            new: a,
-            transferred: b,
-        };
-        assert_eq!(a + b, s.total());
-    }
-
-    #[test]
-    fn adding() {
-        let a1: u128 = 3;
-        let b1: u128 = 2;
-        let a2: u128 = 5;
-        let b2: u128 = 7;
-
-        let s1 = Stake {
-            new: a1,
-            transferred: b1,
-        };
-
-        let s2 = Stake {
-            new: a2,
-            transferred: b2,
-        };
-
-        let sum = s1.add(&s2);
-
-        assert_eq!(sum.new, 8);
-        assert_eq!(sum.transferred, 9);
-    }
-
-    #[test]
-    fn equality() {
-        let a1: u128 = 3;
-        let b1: u128 = 2;
-        let a2: u128 = 2;
-        let b2: u128 = 3;
-        let a3: u128 = 10;
-        let b3: u128 = 10;
-
-        let s1 = Stake {
-            new: a1,
-            transferred: b1,
-        };
-
-        let s2 = s1;
-
-        assert_eq!(s1, s2);
-
-        let s3 = Stake {
-            new: a2,
-            transferred: b2,
-        };
-
-        assert_eq!(s1, s3);
-
-        let s4 = Stake {
-            new: a3,
-            transferred: b3,
-        };
-
-        assert_ne!(s1, s4);
-    }
-}

+ 2 - 2
runtime-modules/membership/Cargo.toml

@@ -19,7 +19,7 @@ common = { package = 'pallet-common', default-features = false, path = '../commo
 [dev-dependencies]
 sp-io = { package = 'sp-io', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
 sp-core = { package = 'sp-core', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
-staking-handler = { package = 'staking-handler', default-features = false, path = '../staking-handler'}
+staking-handler = { package = 'pallet-staking-handler', default-features = false, path = '../staking-handler'}
 working-group = { package = 'pallet-working-group', default-features = false, path = '../working-group'}
 
 [features]
@@ -35,4 +35,4 @@ std = [
 	'pallet-timestamp/std',
 	'balances/std',
 	'common/std',
-]
+]

+ 11 - 0
runtime-modules/membership/src/tests/mock.rs

@@ -2,6 +2,8 @@
 
 pub use crate::{GenesisConfig, Trait};
 
+use staking_handler::LockComparator;
+
 pub use frame_support::traits::{Currency, LockIdentifier};
 use frame_support::{impl_outer_event, impl_outer_origin, parameter_types};
 
@@ -117,6 +119,15 @@ impl working_group::Trait<MembershipWorkingGroupInstance> for Test {
     type WeightInfo = Weights;
 }
 
+impl LockComparator<u64> for Test {
+    fn are_locks_conflicting(
+        _new_lock: &LockIdentifier,
+        _existing_locks: &[LockIdentifier],
+    ) -> bool {
+        false
+    }
+}
+
 // Weights info stub
 pub struct Weights;
 impl working_group::WeightInfo for Weights {

+ 4 - 4
runtime-modules/proposals/codex/Cargo.toml

@@ -15,7 +15,6 @@ frame-system = { package = 'frame-system', default-features = false, git = 'http
 staking = { package = 'pallet-staking', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
 pallet-timestamp = { package = 'pallet-timestamp', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
 balances = { package = 'pallet-balances', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
-governance = { package = 'pallet-governance', default-features = false, path = '../../governance'}
 minting = { package = 'pallet-token-mint', default-features = false, path = '../../token-minting'}
 working-group = { package = 'pallet-working-group', default-features = false, path = '../../working-group'}
 common = { package = 'pallet-common', default-features = false, path = '../../common'}
@@ -31,7 +30,9 @@ sp-staking = { package = 'sp-staking', default-features = false, git = 'https://
 pallet-staking-reward-curve = { package = 'pallet-staking-reward-curve', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
 recurring-rewards = { package = 'pallet-recurring-reward', default-features = false, path = '../../recurring-reward'}
 strum = {version = "0.19", default-features = false}
-staking-handler = { package = 'staking-handler', default-features = false, path = '../../staking-handler'}
+staking-handler = { package = 'pallet-staking-handler', default-features = false, path = '../../staking-handler'}
+referendum = { package = 'pallet-referendum', default-features = false, path = '../../referendum'}
+council = { package = 'pallet-council', default-features = false, path = '../../council'}
 
 [features]
 default = ['std']
@@ -46,11 +47,10 @@ std = [
     'staking/std',
     'pallet-timestamp/std',
     'balances/std',
-    'governance/std',
     'minting/std',
     'working-group/std',
     'common/std',
     'proposals-engine/std',
     'proposals-discussion/std',
     'constitution/std',
-]
+]

+ 3 - 2
runtime-modules/proposals/codex/src/lib.rs

@@ -39,7 +39,7 @@
 //! - [proposals engine](../substrate_proposals_engine_module/index.html)
 //! - [proposals discussion](../substrate_proposals_discussion_module/index.html)
 //! - [membership](../substrate_membership_module/index.html)
-//! - [governance](../substrate_governance_module/index.html)
+//! - [council](../substrate_council_module/index.html)
 //!
 //! ### Notes
 //! The module uses [ProposalEncoder](./trait.ProposalEncoder.html) to encode the proposal using its
@@ -91,8 +91,9 @@ pub trait Trait:
     + proposals_engine::Trait
     + proposals_discussion::Trait
     + common::Trait
-    + governance::election::Trait
     + staking::Trait
+    + common::currency::GovernanceCurrency
+    + minting::Trait
 {
     /// Validates member id and origin combination.
     type MembershipOriginValidator: ActorOriginValidator<

+ 147 - 17
runtime-modules/proposals/codex/src/tests/mock.rs

@@ -3,6 +3,7 @@
 use frame_support::traits::LockIdentifier;
 use frame_support::{impl_outer_dispatch, impl_outer_origin, parameter_types, weights::Weight};
 pub use frame_system;
+use frame_system::{EnsureOneOf, EnsureRoot, EnsureSigned};
 use sp_core::H256;
 use sp_runtime::curve::PiecewiseLinear;
 use sp_runtime::{
@@ -11,9 +12,11 @@ use sp_runtime::{
     DispatchResult, Perbill,
 };
 use sp_staking::SessionIndex;
+use staking_handler::{LockComparator, StakingManager};
 
 use crate::{ProposalDetailsOf, ProposalEncoder, ProposalParameters};
 use proposals_engine::VotersParameters;
+use referendum::Balance as BalanceReferendum;
 use sp_runtime::testing::TestXt;
 
 impl_outer_origin! {
@@ -40,10 +43,6 @@ impl_outer_dispatch! {
     }
 }
 
-impl common::currency::GovernanceCurrency for Test {
-    type Currency = balances::Module<Self>;
-}
-
 impl common::Trait for Test {
     type MemberId = u64;
     type ActorId = u64;
@@ -102,7 +101,7 @@ impl proposals_engine::Trait for Test {
     type VoterOriginValidator = ();
     type TotalVotersCounter = MockVotersParameters;
     type ProposalId = u32;
-    type StakingHandler = staking_handler::StakingManager<Test, LockId>;
+    type StakingHandler = StakingManager<Test, LockId>;
     type CancellationFee = CancellationFee;
     type RejectionFee = RejectionFee;
     type TitleMaxLength = TitleMaxLength;
@@ -158,11 +157,6 @@ impl minting::Trait for Test {
     type MintId = u64;
 }
 
-impl governance::council::Trait for Test {
-    type Event = ();
-    type CouncilTermEnded = ();
-}
-
 impl common::origin::ActorOriginValidator<Origin, u64, u64> for () {
     fn ensure_actor_origin(origin: Origin, _: u64) -> Result<u64, &'static str> {
         let account_id = frame_system::ensure_signed(origin)?;
@@ -210,11 +204,6 @@ impl VotersParameters for MockVotersParameters {
     }
 }
 
-impl governance::election::Trait for Test {
-    type Event = ();
-    type CouncilElected = ();
-}
-
 // The content directory working group instance alias.
 pub type ContentDirectoryWorkingGroupInstance = working_group::Instance3;
 
@@ -231,7 +220,7 @@ pub struct WorkingGroupWeightInfo;
 impl working_group::Trait<ContentDirectoryWorkingGroupInstance> for Test {
     type Event = ();
     type MaxWorkerNumberLimit = MaxWorkerNumberLimit;
-    type StakingHandler = staking_handler::StakingManager<Self, LockId1>;
+    type StakingHandler = StakingManager<Self, LockId1>;
     type StakingAccountValidator = membership::Module<Test>;
     type MemberOriginValidator = ();
     type MinUnstakingPeriodLimit = ();
@@ -314,7 +303,7 @@ impl working_group::WeightInfo for WorkingGroupWeightInfo {
 impl working_group::Trait<StorageWorkingGroupInstance> for Test {
     type Event = ();
     type MaxWorkerNumberLimit = MaxWorkerNumberLimit;
-    type StakingHandler = staking_handler::StakingManager<Self, LockId2>;
+    type StakingHandler = StakingManager<Self, LockId2>;
     type StakingAccountValidator = membership::Module<Test>;
     type MemberOriginValidator = ();
     type MinUnstakingPeriodLimit = ();
@@ -409,6 +398,10 @@ pub(crate) fn default_proposal_parameters() -> ProposalParameters<u64, u64> {
     }
 }
 
+impl common::currency::GovernanceCurrency for Test {
+    type Currency = balances::Module<Self>;
+}
+
 impl crate::Trait for Test {
     type MembershipOriginValidator = ();
     type ProposalEncoder = ();
@@ -426,6 +419,134 @@ impl crate::Trait for Test {
     type AmendConstitutionProposalParameters = DefaultProposalParameters;
 }
 
+parameter_types! {
+    pub const MinNumberOfExtraCandidates: u64 = 1;
+    pub const AnnouncingPeriodDuration: u64 = 15;
+    pub const IdlePeriodDuration: u64 = 27;
+    pub const CouncilSize: u64 = 3;
+    pub const MinCandidateStake: u64 = 11000;
+    pub const CandidacyLockId: LockIdentifier = *b"council1";
+    pub const CouncilorLockId: LockIdentifier = *b"council2";
+    pub const ElectedMemberRewardPerBlock: u64 = 100;
+    pub const ElectedMemberRewardPeriod: u64 = 10;
+    pub const BudgetRefillAmount: u64 = 1000;
+    // intentionally high number that prevents side-effecting tests other than  budget refill tests
+    pub const BudgetRefillPeriod: u64 = 1000;
+}
+
+pub type ReferendumInstance = referendum::Instance0;
+
+impl council::Trait for Test {
+    type Event = ();
+
+    type Referendum = referendum::Module<Test, ReferendumInstance>;
+
+    type MinNumberOfExtraCandidates = MinNumberOfExtraCandidates;
+    type CouncilSize = CouncilSize;
+    type AnnouncingPeriodDuration = AnnouncingPeriodDuration;
+    type IdlePeriodDuration = IdlePeriodDuration;
+    type MinCandidateStake = MinCandidateStake;
+
+    type CandidacyLock = StakingManager<Self, CandidacyLockId>;
+    type CouncilorLock = StakingManager<Self, CouncilorLockId>;
+
+    type ElectedMemberRewardPerBlock = ElectedMemberRewardPerBlock;
+    type ElectedMemberRewardPeriod = ElectedMemberRewardPeriod;
+
+    type BudgetRefillAmount = BudgetRefillAmount;
+    type BudgetRefillPeriod = BudgetRefillPeriod;
+
+    type StakingAccountValidator = ();
+
+    fn is_council_member_account(
+        membership_id: &Self::MemberId,
+        account_id: &<Self as frame_system::Trait>::AccountId,
+    ) -> bool {
+        membership_id == account_id
+    }
+
+    fn new_council_elected(_: &[council::CouncilMemberOf<Self>]) {}
+}
+
+impl common::StakingAccountValidator<Test> for () {
+    fn is_member_staking_account(_: &u64, _: &u64) -> bool {
+        true
+    }
+}
+
+parameter_types! {
+    pub const VoteStageDuration: u64 = 19;
+    pub const RevealStageDuration: u64 = 23;
+    pub const MinimumVotingStake: u64 = 10000;
+    pub const MaxSaltLength: u64 = 32; // use some multiple of 8 for ez testing
+    pub const VotingLockId: LockIdentifier = *b"referend";
+}
+
+impl referendum::Trait<ReferendumInstance> for Test {
+    type Event = ();
+
+    type MaxSaltLength = MaxSaltLength;
+
+    type Currency = balances::Module<Self>;
+    type LockId = VotingLockId;
+
+    type ManagerOrigin =
+        EnsureOneOf<Self::AccountId, EnsureSigned<Self::AccountId>, EnsureRoot<Self::AccountId>>;
+
+    type VotePower = u64;
+
+    type VoteStageDuration = VoteStageDuration;
+    type RevealStageDuration = RevealStageDuration;
+
+    type MinimumStake = MinimumVotingStake;
+
+    fn calculate_vote_power(
+        _: &<Self as frame_system::Trait>::AccountId,
+        _: &BalanceReferendum<Self, ReferendumInstance>,
+    ) -> Self::VotePower {
+        1
+    }
+
+    fn can_unlock_vote_stake(
+        _: &referendum::CastVote<
+            Self::Hash,
+            BalanceReferendum<Self, ReferendumInstance>,
+            Self::MemberId,
+        >,
+    ) -> bool {
+        true
+    }
+
+    fn process_results(winners: &[referendum::OptionResult<Self::MemberId, Self::VotePower>]) {
+        let tmp_winners: Vec<referendum::OptionResult<Self::MemberId, Self::VotePower>> = winners
+            .iter()
+            .map(|item| referendum::OptionResult {
+                option_id: item.option_id,
+                vote_power: item.vote_power.into(),
+            })
+            .collect();
+        <council::Module<Test> as council::ReferendumConnection<Test>>::recieve_referendum_results(
+            tmp_winners.as_slice(),
+        );
+    }
+
+    fn is_valid_option_id(option_index: &u64) -> bool {
+        <council::Module<Test> as council::ReferendumConnection<Test>>::is_valid_candidate_id(
+            option_index,
+        )
+    }
+
+    fn get_option_power(option_id: &u64) -> Self::VotePower {
+        <council::Module<Test> as council::ReferendumConnection<Test>>::get_option_power(option_id)
+    }
+
+    fn increase_option_power(option_id: &u64, amount: &Self::VotePower) {
+        <council::Module<Test> as council::ReferendumConnection<Test>>::increase_option_power(
+            option_id, amount,
+        );
+    }
+}
+
 impl ProposalEncoder<Test> for () {
     fn encode_proposal(_proposal_details: ProposalDetailsOf<Test>) -> Vec<u8> {
         Vec::new()
@@ -467,6 +588,15 @@ impl pallet_timestamp::Trait for Test {
     type WeightInfo = ();
 }
 
+impl LockComparator<<Test as balances::Trait>::Balance> for Test {
+    fn are_locks_conflicting(
+        _new_lock: &LockIdentifier,
+        _existing_locks: &[LockIdentifier],
+    ) -> bool {
+        false
+    }
+}
+
 pub fn initial_test_ext() -> sp_io::TestExternalities {
     let t = frame_system::GenesisConfig::default()
         .build_storage::<Test>()

+ 90 - 13
runtime-modules/proposals/codex/src/tests/mod.rs

@@ -3,10 +3,12 @@ mod mock;
 use frame_support::dispatch::{DispatchError, DispatchResult};
 use frame_support::storage::StorageMap;
 use frame_support::traits::Currency;
+use frame_support::traits::{OnFinalize, OnInitialize};
 use frame_system::RawOrigin;
 
 use common::working_group::WorkingGroup;
 use proposals_engine::ProposalParameters;
+use referendum::ReferendumManager;
 use working_group::Penalty;
 
 use crate::*;
@@ -24,7 +26,10 @@ pub(crate) fn increase_total_balance_issuance_using_account_id(account_id: u64,
     {
         let _ = Balances::deposit_creating(&account_id, balance);
     }
-    assert_eq!(Balances::total_issuance(), initial_balance + balance);
+    assert_eq!(
+        Balances::total_issuance(),
+        initial_balance.saturating_add(balance)
+    );
 }
 
 struct ProposalTestFixture<InsufficientRightsCall, EmptyStakeCall, SuccessfulCall>
@@ -792,6 +797,88 @@ fn slash_stake_with_zero_staking_balance_fails() {
     }
 }
 
+pub fn run_to_block(n: u64) {
+    while System::block_number() < n {
+        System::on_finalize(System::block_number());
+        Module::<Test>::on_finalize(System::block_number());
+        council::Module::<Test>::on_finalize(System::block_number());
+        referendum::Module::<Test, ReferendumInstance>::on_finalize(System::block_number());
+        System::set_block_number(System::block_number() + 1);
+        System::on_initialize(System::block_number());
+        council::Module::<Test>::on_initialize(System::block_number());
+        referendum::Module::<Test, ReferendumInstance>::on_initialize(System::block_number());
+    }
+}
+
+fn setup_council(start_id: u64) {
+    let council_size = <Test as council::Trait>::CouncilSize::get();
+    let candidates_number =
+        council_size + <Test as council::Trait>::MinNumberOfExtraCandidates::get();
+    let candidates: Vec<_> = (start_id..start_id + candidates_number).collect();
+    let council: Vec<_> = (start_id..start_id + council_size).collect();
+    let voters: Vec<_> =
+        (council.last().unwrap() + 1..council.last().unwrap() + 1 + council_size).collect();
+    for id in candidates {
+        increase_total_balance_issuance_using_account_id(id, BalanceOf::<Test>::max_value());
+        council::Module::<Test>::announce_candidacy(
+            RawOrigin::Signed(id).into(),
+            id,
+            id,
+            id,
+            BalanceOf::<Test>::max_value(),
+        )
+        .unwrap();
+    }
+
+    let current_block = System::block_number();
+    run_to_block(current_block + <Test as council::Trait>::AnnouncingPeriodDuration::get());
+
+    for (i, voter_id) in voters.iter().enumerate() {
+        increase_total_balance_issuance_using_account_id(*voter_id, BalanceOf::<Test>::max_value());
+        let commitment = referendum::Module::<Test, ReferendumInstance>::calculate_commitment(
+            voter_id,
+            &[0u8],
+            &0,
+            &council[i],
+        );
+
+        referendum::Module::<Test, ReferendumInstance>::vote(
+            RawOrigin::Signed(*voter_id).into(),
+            commitment,
+            BalanceOf::<Test>::max_value(),
+        )
+        .unwrap();
+    }
+
+    let current_block = System::block_number();
+    run_to_block(
+        current_block + <Test as referendum::Trait<ReferendumInstance>>::VoteStageDuration::get(),
+    );
+
+    for (i, voter_id) in voters.iter().enumerate() {
+        referendum::Module::<Test, ReferendumInstance>::reveal_vote(
+            RawOrigin::Signed(*voter_id).into(),
+            vec![0u8],
+            council[i],
+        )
+        .unwrap();
+    }
+
+    let current_block = System::block_number();
+    run_to_block(
+        current_block + <Test as referendum::Trait<ReferendumInstance>>::RevealStageDuration::get(),
+    );
+
+    let council_members = council::Module::<Test>::council_members();
+    assert_eq!(
+        council_members
+            .iter()
+            .map(|councilor| *councilor.member_id())
+            .collect::<Vec<_>>(),
+        council,
+    );
+}
+
 fn run_slash_stake_with_zero_staking_balance_fails(working_group: WorkingGroup) {
     initial_test_ext().execute_with(|| {
         increase_total_balance_issuance_using_account_id(1, 500000);
@@ -804,12 +891,7 @@ fn run_slash_stake_with_zero_staking_balance_fails(working_group: WorkingGroup)
             exact_execution_block: None,
         };
 
-        let lead_account_id = 20;
-        <governance::council::Module<Test>>::set_council(
-            RawOrigin::Root.into(),
-            vec![lead_account_id],
-        )
-        .unwrap();
+        setup_council(2);
 
         assert_eq!(
             ProposalCodex::create_proposal(
@@ -849,12 +931,7 @@ fn run_decrease_stake_with_zero_staking_balance_fails(working_group: WorkingGrou
             exact_execution_block: None,
         };
 
-        let lead_account_id = 20;
-        <governance::council::Module<Test>>::set_council(
-            RawOrigin::Root.into(),
-            vec![lead_account_id],
-        )
-        .unwrap();
+        setup_council(2);
 
         assert_eq!(
             ProposalCodex::create_proposal(

+ 8 - 5
runtime-modules/proposals/engine/Cargo.toml

@@ -15,31 +15,34 @@ sp-arithmetic = { package = 'sp-arithmetic', default-features = false, git = 'ht
 sp-runtime = { package = 'sp-runtime', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
 balances = { package = 'pallet-balances', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
 common = { package = 'pallet-common', default-features = false, path = '../../common'}
-staking-handler = { package = 'staking-handler', default-features = false, path = '../../staking-handler'}
+staking-handler = { package = 'pallet-staking-handler', default-features = false, path = '../../staking-handler'}
 
 # Benchmark dependencies.
 frame-benchmarking = { package = 'frame-benchmarking', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca', optional = true}
-governance = { package = 'pallet-governance', default-features = false, path = '../../governance', optional = true}
 recurringrewards = { package = 'pallet-recurring-reward', default-features = false, path = '../../recurring-reward', optional = true}
 minting = { package = 'pallet-token-mint', default-features = false, path = '../../token-minting', optional = true}
 membership = { package = 'pallet-membership', default-features = false, path = '../../membership', optional = true}
+council = { package = 'pallet-council', default-features = false, path = '../../council', optional = true}
+referendum = { package = 'pallet-referendum', default-features = false, path = '../../referendum', optional = true}
 
 [dev-dependencies]
 sp-io = { package = 'sp-io', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
 sp-core = { package = 'sp-core', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
-governance = { package = 'pallet-governance', default-features = false, path = '../../governance'}
+council = { package = 'pallet-council', default-features = false, path = '../../council'}
 recurringrewards = { package = 'pallet-recurring-reward', default-features = false, path = '../../recurring-reward'}
 minting = { package = 'pallet-token-mint', default-features = false, path = '../../token-minting'}
 membership = { package = 'pallet-membership', default-features = false, path = '../../membership'}
+referendum = { package = 'pallet-referendum', default-features = false, path = '../../referendum'}
 
 [features]
 default = ['std']
 runtime-benchmarks = [
     'frame-benchmarking',
-    'governance',
     'recurringrewards',
     'minting',
     'membership',
+    'council',
+    'referendum',
 ]
 std = [
 	'serde',
@@ -53,4 +56,4 @@ std = [
 	'balances/std',
     'common/std',
     'staking-handler/std',
-]
+]

+ 188 - 48
runtime-modules/proposals/engine/src/benchmarking.rs

@@ -3,17 +3,21 @@ use super::*;
 use crate::Module as ProposalsEngine;
 use balances::Module as Balances;
 use core::convert::TryInto;
+use council::Module as Council;
 use frame_benchmarking::{account, benchmarks};
 use frame_support::traits::{Currency, OnFinalize, OnInitialize};
 use frame_system::EventRecord;
 use frame_system::Module as System;
 use frame_system::RawOrigin;
-use governance::council::Module as Council;
 use membership::Module as Membership;
+use referendum::Module as Referendum;
+use referendum::ReferendumManager;
 use sp_runtime::traits::{Bounded, One};
 use sp_std::cmp::max;
 use sp_std::prelude::*;
 
+type ReferendumInstance = referendum::Instance1;
+
 const SEED: u32 = 0;
 
 fn get_byte(num: u32, byte_number: u8) -> u8 {
@@ -73,7 +77,12 @@ fn member_funded_account<T: Trait + membership::Trait>(
 
     // Give balance for buying membership
     let _ = Balances::<T>::make_free_balance_be(&account_id, T::Balance::max_value());
+    assert_eq!(
+        Balances::<T>::usable_balance(account_id.clone()),
+        T::Balance::max_value()
+    );
 
+    let member_id = Membership::<T>::members_created();
     let params = membership::BuyMembershipParameters {
         root_account: account_id.clone(),
         controller_account: account_id.clone(),
@@ -84,11 +93,37 @@ fn member_funded_account<T: Trait + membership::Trait>(
         referrer_id: None,
     };
 
+    assert_eq!(
+        Balances::<T>::usable_balance(account_id.clone()),
+        T::Balance::max_value()
+    );
     Membership::<T>::buy_membership(RawOrigin::Signed(account_id.clone()).into(), params).unwrap();
 
+    assert_eq!(
+        Membership::<T>::membership(member_id).controller_account,
+        account_id
+    );
+
+    assert_eq!(
+        Membership::<T>::membership(member_id).root_account,
+        account_id
+    );
     let _ = Balances::<T>::make_free_balance_be(&account_id, T::Balance::max_value());
 
-    (account_id, T::MemberId::from(id.try_into().unwrap()))
+    Membership::<T>::add_staking_account_candidate(
+        RawOrigin::Signed(account_id.clone()).into(),
+        member_id,
+    )
+    .unwrap();
+
+    Membership::<T>::confirm_staking_account(
+        RawOrigin::Signed(account_id.clone()).into(),
+        member_id,
+        account_id.clone(),
+    )
+    .unwrap();
+
+    (account_id, member_id)
 }
 
 fn create_proposal<T: Trait + membership::Trait>(
@@ -161,8 +196,122 @@ fn create_proposal<T: Trait + membership::Trait>(
     (account_id, member_id, proposal_id)
 }
 
+fn run_to_block<T: Trait + council::Trait + referendum::Trait<ReferendumInstance>>(
+    n: T::BlockNumber,
+) {
+    while System::<T>::block_number() < n {
+        let mut current_block_number = System::<T>::block_number();
+
+        System::<T>::on_finalize(current_block_number);
+        ProposalsEngine::<T>::on_finalize(current_block_number);
+        Council::<T>::on_finalize(current_block_number);
+        Referendum::<T, ReferendumInstance>::on_finalize(current_block_number);
+
+        current_block_number += One::one();
+        System::<T>::set_block_number(current_block_number);
+
+        System::<T>::on_initialize(current_block_number);
+        ProposalsEngine::<T>::on_initialize(current_block_number);
+        Council::<T>::on_initialize(current_block_number);
+        Referendum::<T, ReferendumInstance>::on_initialize(current_block_number);
+    }
+}
+
+fn elect_council<
+    T: Trait + membership::Trait + council::Trait + referendum::Trait<ReferendumInstance>,
+>(
+    start_id: u32,
+) -> (Vec<(T::AccountId, T::MemberId)>, u32) {
+    let council_size = <T as council::Trait>::CouncilSize::get();
+    let number_of_extra_candidates = <T as council::Trait>::MinNumberOfExtraCandidates::get();
+
+    let councilor_stake = <T as council::Trait>::MinCandidateStake::get();
+
+    let mut voters = Vec::new();
+    let mut candidates = Vec::new();
+
+    for i in
+        start_id as usize..start_id as usize + (council_size + number_of_extra_candidates) as usize
+    {
+        let (account_id, member_id) =
+            member_funded_account::<T>("councilor", i.try_into().unwrap());
+        Council::<T>::announce_candidacy(
+            RawOrigin::Signed(account_id.clone()).into(),
+            member_id,
+            account_id.clone(),
+            account_id.clone(),
+            councilor_stake,
+        )
+        .unwrap();
+
+        candidates.push((account_id, member_id));
+        voters.push(member_funded_account::<T>(
+            "voter",
+            (council_size + number_of_extra_candidates + i as u64)
+                .try_into()
+                .unwrap(),
+        ));
+    }
+
+    let current_block = System::<T>::block_number();
+    run_to_block::<T>(current_block + <T as council::Trait>::AnnouncingPeriodDuration::get());
+
+    let voter_stake = <T as referendum::Trait<ReferendumInstance>>::MinimumStake::get();
+    let mut council = Vec::new();
+    for i in start_id as usize..start_id as usize + council_size as usize {
+        council.push(candidates[i].clone());
+        let commitment = Referendum::<T, ReferendumInstance>::calculate_commitment(
+            &voters[i].0,
+            &[0u8],
+            &0,
+            &candidates[i].1,
+        );
+        Referendum::<T, ReferendumInstance>::vote(
+            RawOrigin::Signed(voters[i].0.clone()).into(),
+            commitment,
+            voter_stake,
+        )
+        .unwrap();
+    }
+
+    let current_block = System::<T>::block_number();
+    run_to_block::<T>(
+        current_block + <T as referendum::Trait<ReferendumInstance>>::VoteStageDuration::get(),
+    );
+
+    for i in start_id as usize..start_id as usize + council_size as usize {
+        Referendum::<T, ReferendumInstance>::reveal_vote(
+            RawOrigin::Signed(voters[i].0.clone()).into(),
+            vec![0u8],
+            candidates[i].1.clone(),
+        )
+        .unwrap();
+    }
+
+    let current_block = System::<T>::block_number();
+    run_to_block::<T>(
+        current_block + <T as referendum::Trait<ReferendumInstance>>::RevealStageDuration::get(),
+    );
+
+    let council_members = Council::<T>::council_members();
+    assert_eq!(
+        council_members
+            .iter()
+            .map(|m| *m.member_id())
+            .collect::<Vec<_>>(),
+        council.iter().map(|c| c.1).collect::<Vec<_>>()
+    );
+
+    (
+        council,
+        (2 * (council_size + number_of_extra_candidates))
+            .try_into()
+            .unwrap(),
+    )
+}
+
 fn create_multiple_finalized_proposals<
-    T: Trait + governance::council::Trait + membership::Trait,
+    T: Trait + membership::Trait + council::Trait + referendum::Trait<ReferendumInstance>,
 >(
     number_of_proposals: u32,
     constitutionality: u32,
@@ -170,32 +319,24 @@ fn create_multiple_finalized_proposals<
     total_voters: u32,
     grace_period: u32,
 ) -> (Vec<T::AccountId>, Vec<T::ProposalId>) {
-    let mut voters = Vec::new();
-    for i in 0..total_voters {
-        voters.push(member_funded_account::<T>("voter", i));
-    }
-
-    Council::<T>::set_council(
-        RawOrigin::Root.into(),
-        voters
-            .iter()
-            .map(|(account_id, _)| account_id.clone())
-            .collect(),
-    )
-    .unwrap();
+    let (council, last_id): (Vec<(T::AccountId, _)>, _) = elect_council::<T>(0);
 
     let mut proposers = Vec::new();
     let mut proposals = Vec::new();
-    for id in total_voters..number_of_proposals + total_voters {
-        let (proposer_account_id, _, proposal_id) =
-            create_proposal::<T>(id, id - total_voters + 1, constitutionality, grace_period);
+    for proposal_number in 1..number_of_proposals + 1 {
+        let (proposer_account_id, _, proposal_id) = create_proposal::<T>(
+            last_id + proposal_number,
+            proposal_number,
+            constitutionality,
+            grace_period,
+        );
         proposers.push(proposer_account_id);
         proposals.push(proposal_id);
 
-        for (voter_id, member_id) in voters.clone() {
+        for (voter_id, member_id) in council[0..total_voters.try_into().unwrap()].iter() {
             ProposalsEngine::<T>::vote(
                 RawOrigin::Signed(voter_id.clone()).into(),
-                member_id,
+                *member_id,
                 proposal_id,
                 vote_kind.clone(),
                 vec![0u8],
@@ -212,7 +353,7 @@ const MAX_BYTES: u32 = 16384;
 benchmarks! {
     // Note: this is the syntax for this macro can't use "+"
     where_clause {
-        where T: governance::council::Trait, T: membership::Trait
+        where T: membership::Trait, T: council::Trait, T: referendum::Trait<ReferendumInstance>
     }
 
     _ { }
@@ -220,11 +361,9 @@ benchmarks! {
     vote {
         let i in 0 .. MAX_BYTES;
 
-        let (_, _, proposal_id) = create_proposal::<T>(0, 1, 0, 0);
-
-        let (account_voter_id, member_voter_id) = member_funded_account::<T>("voter", 1);
-
-        Council::<T>::set_council(RawOrigin::Root.into(), vec![account_voter_id.clone()]).unwrap();
+        let (council, last_id) = elect_council::<T>(1);
+        let (account_voter_id, member_voter_id) = council[0].clone();
+        let (_, _, proposal_id) = create_proposal::<T>(last_id + 1, 1, 0, 0);
     }: _ (
             RawOrigin::Signed(account_voter_id),
             member_voter_id,
@@ -380,15 +519,8 @@ benchmarks! {
             1,
         );
 
-        let mut current_block_number = System::<T>::block_number();
-
-        System::<T>::on_finalize(current_block_number);
-        System::<T>::on_finalize(current_block_number);
-
-        current_block_number += One::one();
-
-        System::<T>::on_initialize(current_block_number);
-        ProposalsEngine::<T>::on_initialize(current_block_number);
+        run_to_block::<T>(System::<T>::block_number() + One::one());
+        let current_block_number = System::<T>::block_number();
 
         assert_eq!(
             ProposalsEngine::<T>::active_proposal_count(),
@@ -402,12 +534,21 @@ benchmarks! {
                 "All proposals should still be stored"
             );
 
+            assert_eq!(
+                ProposalsEngine::<T>::proposals(proposal_id).status,
+                ProposalStatus::PendingExecution(current_block_number),
+                "Status not updated"
+            );
+
             assert!(
                 DispatchableCallCode::<T>::contains_key(proposal_id),
                 "All dispatchable call code should still be stored"
             );
         }
 
+        let current_block_number = System::<T>::block_number() + One::one();
+        System::<T>::set_block_number(current_block_number);
+
     }: { ProposalsEngine::<T>::on_initialize(current_block_number) }
     verify {
         for proposer_account_id in proposers {
@@ -443,7 +584,7 @@ benchmarks! {
             2,
             VoteKind::Approve,
             1,
-            0,
+            1,
         );
 
     }: { ProposalsEngine::<T>::on_initialize(System::<T>::block_number().into()) }
@@ -482,21 +623,12 @@ benchmarks! {
             i,
             0,
             VoteKind::Reject,
-            max(T::TotalVotersCounter::total_voters_count(), 1),
+            max(T::CouncilSize::get().try_into().unwrap(), 1),
             0,
         );
     }: { ProposalsEngine::<T>::on_initialize(System::<T>::block_number().into()) }
     verify {
-        for proposer_account_id in proposers {
-            assert_eq!(
-                T::StakingHandler::current_stake(&proposer_account_id),
-                Zero::zero(),
-                "Shouldn't have any stake locked"
-            );
-        }
-
         for proposal_id in proposals.iter() {
-
             assert!(
                 !Proposals::<T>::contains_key(proposal_id),
                 "Proposal should not be in store"
@@ -518,6 +650,14 @@ benchmarks! {
             0,
             "There should not be any proposal left active"
         );
+
+        for proposer_account_id in proposers {
+            assert_eq!(
+                T::StakingHandler::current_stake(&proposer_account_id),
+                Zero::zero(),
+                "Shouldn't have any stake locked"
+            );
+        }
     }
 
     on_initialize_slashed {
@@ -527,7 +667,7 @@ benchmarks! {
             i,
             0,
             VoteKind::Slash,
-            max(T::TotalVotersCounter::total_voters_count(), 1),
+            max(T::CouncilSize::get().try_into().unwrap(), 1),
             0,
         );
     }: { ProposalsEngine::<T>::on_initialize(System::<T>::block_number().into()) }

+ 1 - 1
runtime-modules/proposals/engine/src/lib.rs

@@ -188,7 +188,7 @@ pub trait Trait:
     type ProposalId: From<u32> + Parameter + Default + Copy;
 
     /// Provides stake logic implementation.
-    type StakingHandler: StakingHandler<Self>;
+    type StakingHandler: StakingHandler<Self::AccountId, BalanceOf<Self>, MemberId<Self>>;
 
     /// The fee is applied when cancel the proposal. A fee would be slashed (burned).
     type CancellationFee: Get<BalanceOf<Self>>;

+ 133 - 7
runtime-modules/proposals/engine/src/tests/mock/mod.rs

@@ -9,6 +9,7 @@
 use frame_support::traits::LockIdentifier;
 use frame_support::{impl_outer_event, impl_outer_origin, parameter_types, weights::Weight};
 pub use frame_system;
+use frame_system::{EnsureOneOf, EnsureRoot, EnsureSigned};
 use sp_core::H256;
 use sp_runtime::{
     testing::Header,
@@ -20,6 +21,8 @@ pub(crate) mod proposals;
 
 use crate::ProposalObserver;
 pub use proposals::*;
+use referendum::Balance as BalanceReferendum;
+use staking_handler::{LockComparator, StakingManager};
 
 // Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted.
 #[derive(Clone, PartialEq, Eq, Debug)]
@@ -37,8 +40,13 @@ mod membership_mod {
     pub use membership::Event;
 }
 
-mod council {
-    pub use governance::council::Event;
+mod council_mod {
+    pub use council::Event;
+}
+
+mod referendum_mod {
+    pub use referendum::Event;
+    pub use referendum::Instance1;
 }
 
 impl_outer_event! {
@@ -47,8 +55,77 @@ impl_outer_event! {
         engine<T>,
         membership_mod<T>,
         frame_system<T>,
-        council<T>,
+        council_mod<T>,
+        referendum_mod Instance1 <T>,
+    }
+}
+
+parameter_types! {
+    pub const VoteStageDuration: u64 = 19;
+    pub const RevealStageDuration: u64 = 23;
+    pub const MinimumVotingStake: u64 = 10000;
+    pub const MaxSaltLength: u64 = 32; // use some multiple of 8 for ez testing
+    pub const VotingLockId: LockIdentifier = *b"referend";
+    pub const MinimumPeriod: u64 = 5;
+}
+
+impl referendum::Trait<ReferendumInstance> for Test {
+    type Event = TestEvent;
+
+    type MaxSaltLength = MaxSaltLength;
+
+    type Currency = balances::Module<Self>;
+    type LockId = VotingLockId;
+
+    type ManagerOrigin =
+        EnsureOneOf<Self::AccountId, EnsureSigned<Self::AccountId>, EnsureRoot<Self::AccountId>>;
+
+    type VotePower = u64;
+
+    type VoteStageDuration = VoteStageDuration;
+    type RevealStageDuration = RevealStageDuration;
+
+    type MinimumStake = MinimumVotingStake;
+
+    fn calculate_vote_power(
+        _: &<Self as frame_system::Trait>::AccountId,
+        _: &BalanceReferendum<Self, ReferendumInstance>,
+    ) -> Self::VotePower {
+        1
+    }
+
+    fn can_unlock_vote_stake(
+        _: &referendum::CastVote<
+            Self::Hash,
+            BalanceReferendum<Self, ReferendumInstance>,
+            Self::MemberId,
+        >,
+    ) -> bool {
+        true
+    }
+
+    fn process_results(winners: &[referendum::OptionResult<Self::MemberId, Self::VotePower>]) {
+        let tmp_winners: Vec<referendum::OptionResult<Self::MemberId, Self::VotePower>> = winners
+            .iter()
+            .map(|item| referendum::OptionResult {
+                option_id: item.option_id,
+                vote_power: item.vote_power.into(),
+            })
+            .collect();
+        <council::Module<Test> as council::ReferendumConnection<Test>>::recieve_referendum_results(
+            tmp_winners.as_slice(),
+        );
     }
+
+    fn is_valid_option_id(_: &u64) -> bool {
+        true
+    }
+
+    fn get_option_power(_: &u64) -> Self::VotePower {
+        1
+    }
+
+    fn increase_option_power(_: &u64, _: &Self::VotePower) {}
 }
 
 parameter_types! {
@@ -113,7 +190,7 @@ impl crate::Trait for Test {
     type VoterOriginValidator = ();
     type TotalVotersCounter = ();
     type ProposalId = u32;
-    type StakingHandler = staking_handler::StakingManager<Test, LockId>;
+    type StakingHandler = StakingManager<Test, LockId>;
     type CancellationFee = CancellationFee;
     type RejectionFee = RejectionFee;
     type TitleMaxLength = TitleMaxLength;
@@ -189,7 +266,6 @@ parameter_types! {
     pub const MaximumBlockWeight: u32 = 1024;
     pub const MaximumBlockLength: u32 = 2 * 1024;
     pub const AvailableBlockRatio: Perbill = Perbill::one();
-    pub const MinimumPeriod: u64 = 5;
 }
 
 impl frame_system::Trait for Test {
@@ -227,9 +303,53 @@ impl pallet_timestamp::Trait for Test {
     type WeightInfo = ();
 }
 
-impl governance::council::Trait for Test {
+parameter_types! {
+    pub const MinNumberOfExtraCandidates: u64 = 1;
+    pub const AnnouncingPeriodDuration: u64 = 15;
+    pub const IdlePeriodDuration: u64 = 27;
+    pub const CouncilSize: u64 = 4;
+    pub const MinCandidateStake: u64 = 11000;
+    pub const CandidacyLockId: LockIdentifier = *b"council1";
+    pub const CouncilorLockId: LockIdentifier = *b"council2";
+    pub const ElectedMemberRewardPerBlock: u64 = 100;
+    pub const ElectedMemberRewardPeriod: u64 = 10;
+    pub const BudgetRefillAmount: u64 = 1000;
+    // intentionally high number that prevents side-effecting tests other than  budget refill tests
+    pub const BudgetRefillPeriod: u64 = 1000;
+}
+
+type ReferendumInstance = referendum::Instance1;
+
+impl council::Trait for Test {
     type Event = TestEvent;
-    type CouncilTermEnded = ();
+
+    type Referendum = referendum::Module<Test, ReferendumInstance>;
+
+    type MinNumberOfExtraCandidates = MinNumberOfExtraCandidates;
+    type CouncilSize = CouncilSize;
+    type AnnouncingPeriodDuration = AnnouncingPeriodDuration;
+    type IdlePeriodDuration = IdlePeriodDuration;
+    type MinCandidateStake = MinCandidateStake;
+
+    type CandidacyLock = StakingManager<Self, CandidacyLockId>;
+    type CouncilorLock = StakingManager<Self, CouncilorLockId>;
+
+    type ElectedMemberRewardPerBlock = ElectedMemberRewardPerBlock;
+    type ElectedMemberRewardPeriod = ElectedMemberRewardPeriod;
+
+    type BudgetRefillAmount = BudgetRefillAmount;
+    type BudgetRefillPeriod = BudgetRefillPeriod;
+
+    type StakingAccountValidator = membership::Module<Test>;
+
+    fn is_council_member_account(
+        membership_id: &Self::MemberId,
+        account_id: &<Self as frame_system::Trait>::AccountId,
+    ) -> bool {
+        membership::Module::<Self>::membership(membership_id).controller_account == *account_id
+    }
+
+    fn new_council_elected(_: &[council::CouncilMemberOf<Self>]) {}
 }
 
 impl recurringrewards::Trait for Test {
@@ -243,6 +363,12 @@ impl minting::Trait for Test {
     type MintId = u64;
 }
 
+impl LockComparator<<Test as balances::Trait>::Balance> for Test {
+    fn are_locks_conflicting(new_lock: &LockIdentifier, existing_locks: &[LockIdentifier]) -> bool {
+        existing_locks.iter().any(|l| l == new_lock)
+    }
+}
+
 pub fn initial_test_ext() -> sp_io::TestExternalities {
     let t = frame_system::GenesisConfig::default()
         .build_storage::<Test>()

+ 3 - 3
runtime-modules/proposals/engine/src/tests/mod.rs

@@ -1086,7 +1086,7 @@ fn create_dummy_proposal_fail_with_stake_on_empty_account() {
 }
 
 #[test]
-fn create_proposal_fais_with_insufficient_stake_parameters() {
+fn create_proposal_fails_with_insufficient_stake_parameters() {
     initial_test_ext().execute_with(|| {
         let parameters_fixture = ProposalParametersFixture::default();
 
@@ -1107,7 +1107,7 @@ fn create_proposal_fais_with_insufficient_stake_parameters() {
 }
 
 #[test]
-fn create_proposal_fais_with_empty_stake() {
+fn create_proposal_fails_with_empty_stake() {
     initial_test_ext().execute_with(|| {
         let parameters_fixture = ProposalParametersFixture::default().with_required_stake(300);
         let dummy_proposal =
@@ -1118,7 +1118,7 @@ fn create_proposal_fais_with_empty_stake() {
 }
 
 #[test]
-fn create_proposal_fais_with_conflicting_stakes() {
+fn create_proposal_fails_with_conflicting_stakes() {
     initial_test_ext().execute_with(|| {
         let staking_account_id = 1;
 

+ 36 - 0
runtime-modules/referendum/Cargo.toml

@@ -0,0 +1,36 @@
+[package]
+name = 'pallet-referendum'
+version = '1.0.0'
+authors = ['Joystream contributors']
+edition = '2018'
+
+[dependencies]
+codec = { package = 'parity-scale-codec', version = '1.0.0', default-features = false, features = ['derive'] }
+serde = { version = '1.0.101', optional = true}
+sp-runtime = { package = 'sp-runtime', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
+sp-core = { package = 'sp-core', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
+sp-arithmetic = { package = 'sp-arithmetic', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
+frame-support = { package = 'frame-support', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
+frame-system = { package = 'frame-system', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
+sp-std = { package = 'sp-std', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
+common = { package = 'pallet-common', default-features = false, path = '../common'}
+
+[dev-dependencies]
+pallet-balances = { package = 'pallet-balances', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
+sp-io = { package = 'sp-io', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
+rand = "0.7.3"
+
+[features]
+default = ['std']
+std = [
+    'codec/std',
+    'sp-core/std',
+    'sp-std/std',
+    'serde',
+    'sp-runtime/std',
+    'sp-arithmetic/std',
+    'frame-support/std',
+    'frame-system/std',
+    'common/std',
+]
+

+ 885 - 0
runtime-modules/referendum/src/lib.rs

@@ -0,0 +1,885 @@
+// TODO: adjust all extrinsic weights
+
+//! # Referendum module
+//! General voting engine module for the the Joystream platform. Component of the council frame_system.
+//!
+//! ## Overview
+//!
+//! Referendum is an abstract module that enables priviliged network participants to vote on the given topic.
+//! The module has no notion on the topic that is actually voted on focuses and rather focuses on enabling
+//! users to cast their votes and selecting the winning option after voting concludes.
+//!
+//! The voting itself is divided into three phases. In the default Idle phase, the module waits
+//! for the new voting round initiation by the runtime. In the Voting phase, users can submit sealed commitment
+//! of their vote that they can later reveal in the Revealing phase. After the Revealing phase ends,
+//! the Referendum becomes Idle again and waits for the new cycle start.
+//!
+//! The module supports an unlimited number of options for voting and one or multiple winners of the referendum.
+//! Depending on the runtime implementation, users can be required to stake at least a minimum amount of currency,
+//! and the winning options can be decided by the total number of votes received or the total amount staked
+//! behind them.
+//!
+//! ## Supported extrinsics
+//!
+//! - [vote](./struct.Module.html#method.vote)
+//! - [reveal_vote](./struct.Module.html#method.reveal_vote)
+//! - [release_vote_stake](./struct.Module.html#method.release_vote_stake)
+//!
+//! ## Notes
+//! This module is instantiable pallet as described here https://substrate.dev/recipes/3-entrees/instantiable.html
+//! No default instance is provided.
+
+/////////////////// Configuration //////////////////////////////////////////////
+#![cfg_attr(not(feature = "std"), no_std)]
+
+// used dependencies
+use codec::{Codec, Decode, Encode};
+use core::marker::PhantomData;
+use frame_support::traits::{
+    Currency, EnsureOrigin, Get, LockIdentifier, LockableCurrency, WithdrawReason,
+};
+use frame_support::{
+    decl_error, decl_event, decl_module, decl_storage, error::BadOrigin, Parameter, StorageValue,
+};
+use frame_system::ensure_signed;
+#[cfg(feature = "std")]
+use serde::{Deserialize, Serialize};
+use sp_arithmetic::traits::BaseArithmetic;
+use sp_runtime::traits::{MaybeSerialize, Member};
+use sp_std::vec;
+use sp_std::vec::Vec;
+
+// declared modules
+mod mock;
+mod tests;
+
+/////////////////// Data Structures ////////////////////////////////////////////
+
+/// Possible referendum states.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, PartialEq, Eq, Debug)]
+pub enum ReferendumStage<BlockNumber, MemberId, VotePower> {
+    /// The referendum is dormant and waiting to be started by external source.
+    Inactive,
+    /// In the voting stage, users can cast their sealed votes.
+    Voting(ReferendumStageVoting<BlockNumber>),
+    /// In the revealing stage, users can reveal votes they cast in the voting stage.
+    Revealing(ReferendumStageRevealing<BlockNumber, MemberId, VotePower>),
+}
+
+impl<BlockNumber, MemberId, VotePower: Encode + Decode> Default
+    for ReferendumStage<BlockNumber, MemberId, VotePower>
+{
+    fn default() -> ReferendumStage<BlockNumber, MemberId, VotePower> {
+        ReferendumStage::Inactive
+    }
+}
+
+/// Representation for voting stage state.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, PartialEq, Eq, Debug, Default)]
+pub struct ReferendumStageVoting<BlockNumber> {
+    pub started: BlockNumber,      // block in which referendum started
+    pub winning_target_count: u64, // target number of winners
+    pub current_cycle_id: u64,     // index of current election
+}
+
+/// Representation for revealing stage state.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, PartialEq, Eq, Debug, Default)]
+pub struct ReferendumStageRevealing<BlockNumber, MemberId, VotePower> {
+    pub started: BlockNumber,      // block in which referendum started
+    pub winning_target_count: u64, // target number of winners
+    pub intermediate_winners: Vec<OptionResult<MemberId, VotePower>>, // intermediate winning options
+    pub current_cycle_id: u64,                                        // index of current election
+}
+
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, PartialEq, Eq, Debug, Default, Clone)]
+pub struct OptionResult<MemberId, VotePower> {
+    pub option_id: MemberId,
+    pub vote_power: VotePower,
+}
+
+/// Vote cast in referendum. Vote target is concealed until user reveals commitment's proof.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, PartialEq, Eq, Debug, Default)]
+pub struct CastVote<Hash, Currency, MemberId> {
+    pub commitment: Hash, // a commitment that a user submits in the voting stage before revealing what this vote is actually for
+    pub cycle_id: u64,    // current referendum cycle number
+    pub stake: Currency,  // stake locked for vote
+    pub vote_for: Option<MemberId>, // target option this vote favors; is `None` before the vote is revealed
+}
+
+/////////////////// Type aliases ///////////////////////////////////////////////
+
+// `Ez` prefix in some of the following type aliases means *easy* and is meant to create unique short names
+// aliasing existing structs and enums
+
+// types simplifying access to common structs and enums
+pub type Balance<T, I> =
+    <<T as Trait<I>>::Currency as Currency<<T as frame_system::Trait>::AccountId>>::Balance;
+pub type CastVoteOf<T, I> =
+    CastVote<<T as frame_system::Trait>::Hash, Balance<T, I>, <T as common::Trait>::MemberId>;
+pub type ReferendumStageVotingOf<T> =
+    ReferendumStageVoting<<T as frame_system::Trait>::BlockNumber>;
+pub type ReferendumStageRevealingOf<T, I> = ReferendumStageRevealing<
+    <T as frame_system::Trait>::BlockNumber,
+    <T as common::Trait>::MemberId,
+    <T as Trait<I>>::VotePower,
+>;
+pub type OptionResultOf<T, I> =
+    OptionResult<<T as common::Trait>::MemberId, <T as Trait<I>>::VotePower>;
+
+// types aliases for check functions return values
+pub type CanRevealResult<T, I> = (
+    ReferendumStageRevealingOf<T, I>,
+    <T as frame_system::Trait>::AccountId,
+    CastVoteOf<T, I>,
+);
+
+/////////////////// Trait, Storage, Errors, and Events /////////////////////////
+
+/// Trait that should be used by other modules to start the referendum, etc.
+pub trait ReferendumManager<Origin, AccountId, MemberId, Hash> {
+    /// Power of vote(s) used to determine the referendum winner(s).
+    type VotePower: Parameter
+        + Member
+        + BaseArithmetic
+        + Codec
+        + Default
+        + Copy
+        + MaybeSerialize
+        + PartialEq;
+
+    /// Currency for referendum staking.
+    type Currency: LockableCurrency<AccountId>;
+
+    /// Start a new referendum.
+    fn start_referendum(
+        origin: Origin,
+        extra_winning_target_count: u64,
+        cycle_id: u64,
+    ) -> Result<(), ()>;
+
+    /// Start referendum independent of the current state.
+    /// If an election is running before calling this function, it will be discontinued without any winners selected.
+    fn force_start(extra_winning_target_count: u64, cycle_id: u64);
+
+    /// Calculate commitment for a vote.
+    fn calculate_commitment(
+        account_id: &AccountId,
+        salt: &[u8],
+        cycle_id: &u64,
+        vote_option_id: &MemberId,
+    ) -> Hash;
+}
+
+/// The main Referendum module's trait.
+pub trait Trait<I: Instance>: frame_system::Trait + common::Trait {
+    /// The overarching event type.
+    type Event: From<Event<Self, I>> + Into<<Self as frame_system::Trait>::Event>;
+
+    /// Maximum length of vote commitment salt. Use length that ensures uniqueness for hashing e.g. std::u64::MAX.
+    type MaxSaltLength: Get<u64>;
+
+    /// Currency for referendum staking.
+    type Currency: LockableCurrency<Self::AccountId, Moment = Self::BlockNumber>;
+
+    /// Identifier for currency locks used for staking.
+    type LockId: Get<LockIdentifier>;
+
+    /// Origin from which the referendum can be started.
+    type ManagerOrigin: EnsureOrigin<Self::Origin>;
+
+    /// Power of vote(s) used to determine the referendum winner(s).
+    type VotePower: Parameter
+        + Member
+        + BaseArithmetic
+        + Codec
+        + Default
+        + Copy
+        + MaybeSerialize
+        + PartialEq;
+
+    /// Duration of voting stage (number of blocks)
+    type VoteStageDuration: Get<Self::BlockNumber>;
+    /// Duration of revealing stage (number of blocks)
+    type RevealStageDuration: Get<Self::BlockNumber>;
+
+    /// Minimum stake needed for voting
+    type MinimumStake: Get<Balance<Self, I>>;
+
+    /// Calculate the vote's power for user and his stake.
+    fn calculate_vote_power(
+        account_id: &<Self as frame_system::Trait>::AccountId,
+        stake: &Balance<Self, I>,
+    ) -> <Self as Trait<I>>::VotePower;
+
+    /// Checks if user can unlock his stake from the given vote.
+    /// Gives runtime an ability to penalize user for not revealing stake, etc.
+    fn can_unlock_vote_stake(vote: &CastVote<Self::Hash, Balance<Self, I>, Self::MemberId>)
+        -> bool;
+
+    /// Gives runtime an ability to react on referendum result.
+    fn process_results(winners: &[OptionResult<Self::MemberId, Self::VotePower>]);
+
+    /// Check if an option a user is voting for actually exists.
+    fn is_valid_option_id(option_id: &Self::MemberId) -> bool;
+
+    // If the id is a valid alternative, the current total voting mass backing it is returned, otherwise nothing.
+    fn get_option_power(option_id: &Self::MemberId) -> Self::VotePower;
+
+    // Increases voting mass behind given alternative by given amount, if present and return true, otherwise return false.
+    fn increase_option_power(option_id: &Self::MemberId, amount: &Self::VotePower);
+}
+
+decl_storage! {
+    trait Store for Module<T: Trait<I>, I: Instance> as Referendum {
+        /// Current referendum stage.
+        pub Stage get(fn stage) config(): ReferendumStage<T::BlockNumber, T::MemberId, T::VotePower>;
+
+        /// Votes cast in the referendum. A new record is added to this map when a user casts a sealed vote.
+        /// It is modified when a user reveals the vote's commitment proof.
+        /// A record is finally removed when the user unstakes, which can happen during a voting stage or after the current cycle ends.
+        /// A stake for a vote can be reused in future referendum cycles.
+        pub Votes get(fn votes) config(): map hasher(blake2_128_concat) T::AccountId => CastVoteOf<T, I>;
+    }
+}
+
+decl_event! {
+    pub enum Event<T, I>
+    where
+        Balance = Balance<T, I>,
+        <T as frame_system::Trait>::Hash,
+        <T as frame_system::Trait>::AccountId,
+        <T as Trait<I>>::VotePower,
+        <T as common::Trait>::MemberId,
+    {
+        /// Referendum started
+        ReferendumStarted(u64),
+
+        /// Referendum started
+        ReferendumStartedForcefully(u64),
+
+        /// Revealing phase has begun
+        RevealingStageStarted(),
+
+        /// Referendum ended and winning option was selected
+        ReferendumFinished(Vec<OptionResult<MemberId, VotePower>>),
+
+        /// User cast a vote in referendum
+        VoteCast(AccountId, Hash, Balance),
+
+        /// User revealed his vote
+        VoteRevealed(AccountId, MemberId),
+
+        /// User released his stake
+        StakeReleased(AccountId),
+    }
+}
+
+decl_error! {
+    /// Referendum errors
+    pub enum Error for Module<T: Trait<I>, I: Instance> {
+        /// Origin is invalid
+        BadOrigin,
+
+        /// Referendum is not running when expected to
+        ReferendumNotRunning,
+
+        /// Revealing stage is not in progress right now
+        RevealingNotInProgress,
+
+        /// Account can't stake enough currency (now)
+        InsufficientBalanceToStakeCurrency,
+
+        /// Insufficient stake provided to cast a vote
+        InsufficientStake,
+
+        /// Salt and referendum option provided don't correspond to the commitment
+        InvalidReveal,
+
+        /// Vote for not existing option was revealed
+        InvalidVote,
+
+        /// Trying to reveal vote that was not cast
+        VoteNotExisting,
+
+        /// Trying to vote multiple time in the same cycle
+        AlreadyVotedThisCycle,
+
+        /// Invalid time to release the locked stake
+        UnstakingVoteInSameCycle,
+
+        /// Salt is too long
+        SaltTooLong,
+
+        /// Unstaking has been forbidden for the user (at least for now)
+        UnstakingForbidden,
+    }
+}
+
+impl<T: Trait<I>, I: Instance> PartialEq for Error<T, I> {
+    fn eq(&self, other: &Self) -> bool {
+        self.as_u8() == other.as_u8()
+    }
+}
+
+impl<T: Trait<I>, I: Instance> From<BadOrigin> for Error<T, I> {
+    fn from(_error: BadOrigin) -> Self {
+        Error::<T, I>::BadOrigin
+    }
+}
+
+/////////////////// Module definition and implementation ///////////////////////
+
+decl_module! {
+    pub struct Module<T: Trait<I>, I: Instance> for enum Call where origin: T::Origin {
+        /// Predefined errors
+        type Error = Error<T, I>;
+
+        /// Setup events
+        fn deposit_event() = default;
+
+        /// Maximum length of vote commitment salt. Use length that ensures uniqueness for hashing e.g. std::u64::MAX.
+        const MaxSaltLength: u64 = T::MaxSaltLength::get();
+        /// Identifier for currency locks used for staking.
+        const LockId: LockIdentifier = T::LockId::get();
+        /// Duration of voting stage (number of blocks)
+        const VoteStageDuration: T::BlockNumber = T::VoteStageDuration::get();
+        /// Duration of revealing stage (number of blocks)
+        const RevealStageDuration: T::BlockNumber = T::RevealStageDuration::get();
+        /// Minimum stake needed for voting
+        const MinimumStake: Balance<T, I> = T::MinimumStake::get();
+
+        /////////////////// Lifetime ///////////////////////////////////////////
+
+        // No origin so this is a priviledged call
+        fn on_finalize(now: T::BlockNumber) {
+            Self::try_progress_stage(now);
+        }
+
+        /////////////////// User actions ///////////////////////////////////////
+
+        /// Cast a sealed vote in the referendum.
+        #[weight = 10_000_000]
+        pub fn vote(origin, commitment: T::Hash, stake: Balance<T, I>) -> Result<(), Error<T, I>> {
+            // ensure action can be started
+            let (current_cycle_id, account_id) = EnsureChecks::<T, I>::can_vote(origin, &stake)?;
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            // start revealing phase - it can return error when stake fails to lock
+            Mutations::<T, I>::vote(&account_id, &commitment, &stake, &current_cycle_id)?;
+
+            // emit event
+            Self::deposit_event(RawEvent::VoteCast(account_id, commitment, stake));
+
+            Ok(())
+        }
+
+        /// Reveal a sealed vote in the referendum.
+        #[weight = 10_000_000]
+        pub fn reveal_vote(origin, salt: Vec<u8>, vote_option_id: <T as common::Trait>::MemberId) -> Result<(), Error<T, I>> {
+            let (stage_data, account_id, cast_vote) = EnsureChecks::<T, I>::can_reveal_vote::<Self>(origin, &salt, &vote_option_id)?;
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            // reveal the vote - it can return error when stake fails to unlock
+            Mutations::<T, I>::reveal_vote(stage_data, &account_id, &vote_option_id, cast_vote)?;
+
+            // emit event
+            Self::deposit_event(RawEvent::VoteRevealed(account_id, vote_option_id));
+
+            Ok(())
+        }
+
+        /// Release a locked stake.
+        #[weight = 10_000_000]
+        pub fn release_vote_stake(origin) -> Result<(), Error<T, I>> {
+            let account_id = EnsureChecks::<T, I>::can_release_vote_stake(origin)?;
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            // reveal the vote - it can return error when stake fails to unlock
+            Mutations::<T, I>::release_vote_stake(&account_id);
+
+            // emit event
+            Self::deposit_event(RawEvent::StakeReleased(account_id));
+
+            Ok(())
+        }
+    }
+}
+
+/////////////////// Inner logic ////////////////////////////////////////////////
+
+impl<T: Trait<I>, I: Instance> Module<T, I> {
+    /// Checkout expire of referendum stage.
+    fn try_progress_stage(now: T::BlockNumber) {
+        match Stage::<T, I>::get() {
+            ReferendumStage::Inactive => (),
+            ReferendumStage::Voting(stage_data) => {
+                if now == stage_data.started + T::VoteStageDuration::get() - 1.into() {
+                    Self::end_voting_period(stage_data);
+                }
+            }
+            ReferendumStage::Revealing(stage_data) => {
+                if now == stage_data.started + T::RevealStageDuration::get() - 1.into() {
+                    Self::end_reveal_period(stage_data);
+                }
+            }
+        }
+    }
+
+    /// Finish voting and start ravealing.
+    fn end_voting_period(stage_data: ReferendumStageVotingOf<T>) {
+        // start revealing phase
+        Mutations::<T, I>::start_revealing_period(stage_data);
+
+        // emit event
+        Self::deposit_event(RawEvent::RevealingStageStarted());
+    }
+
+    /// Conclude the referendum.
+    fn end_reveal_period(stage_data: ReferendumStageRevealingOf<T, I>) {
+        // conclude referendum
+        let winners = Mutations::<T, I>::conclude_referendum(stage_data);
+
+        // let runtime know about referendum results
+        T::process_results(&winners);
+
+        // emit event
+        Self::deposit_event(RawEvent::ReferendumFinished(winners));
+    }
+}
+
+/////////////////// ReferendumManager //////////////////////////////////////////
+
+impl<T: Trait<I>, I: Instance> ReferendumManager<T::Origin, T::AccountId, T::MemberId, T::Hash>
+    for Module<T, I>
+{
+    type VotePower = T::VotePower;
+    type Currency = T::Currency;
+
+    /// Start new referendum run.
+    fn start_referendum(
+        origin: T::Origin,
+        extra_winning_target_count: u64,
+        cycle_id: u64,
+    ) -> Result<(), ()> {
+        let winning_target_count = extra_winning_target_count + 1;
+
+        // ensure action can be started
+        EnsureChecks::<T, I>::can_start_referendum(origin)?;
+
+        //
+        // == MUTATION SAFE ==
+        //
+
+        // update state
+        Mutations::<T, I>::start_voting_period(&winning_target_count, &cycle_id);
+
+        // emit event
+        Self::deposit_event(RawEvent::ReferendumStarted(winning_target_count));
+
+        Ok(())
+    }
+
+    /// Start referendum independent of the current state.
+    /// If an election is running before calling this function, it will be discontinued without any winners selected.
+    fn force_start(extra_winning_target_count: u64, cycle_id: u64) {
+        let winning_target_count = extra_winning_target_count + 1;
+
+        // remember if referendum is running
+        let referendum_running = !matches!(Stage::<T, I>::get(), ReferendumStage::Inactive);
+
+        // update state
+        Mutations::<T, I>::start_voting_period(&winning_target_count, &cycle_id);
+
+        // emit event
+        if referendum_running {
+            Self::deposit_event(RawEvent::ReferendumStartedForcefully(winning_target_count));
+        } else {
+            Self::deposit_event(RawEvent::ReferendumStarted(winning_target_count));
+        }
+    }
+
+    /// Calculate commitment for a vote.
+    fn calculate_commitment(
+        account_id: &<T as frame_system::Trait>::AccountId,
+        salt: &[u8],
+        cycle_id: &u64,
+        vote_option_id: &<T as common::Trait>::MemberId,
+    ) -> T::Hash {
+        let mut payload = account_id.encode();
+        let mut mut_option_id = vote_option_id.encode();
+        let mut mut_salt = salt.encode(); //.to_vec();
+        let mut mut_cycle_id = cycle_id.encode(); //.to_vec();
+
+        payload.append(&mut mut_option_id);
+        payload.append(&mut mut_salt);
+        payload.append(&mut mut_cycle_id);
+
+        <T::Hashing as sp_runtime::traits::Hash>::hash(&payload)
+    }
+}
+
+/////////////////// Mutations //////////////////////////////////////////////////
+
+struct Mutations<T: Trait<I>, I: Instance> {
+    _dummy: PhantomData<(T, I)>, // 0-sized data meant only to bound generic parameters
+}
+
+impl<T: Trait<I>, I: Instance> Mutations<T, I> {
+    /// Change the referendum stage from inactive to voting stage.
+    fn start_voting_period(winning_target_count: &u64, cycle_id: &u64) {
+        // change referendum state
+        Stage::<T, I>::put(ReferendumStage::Voting(ReferendumStageVoting::<
+            T::BlockNumber,
+        > {
+            started: <frame_system::Module<T>>::block_number() + 1.into(),
+            winning_target_count: *winning_target_count,
+            current_cycle_id: *cycle_id,
+        }));
+    }
+
+    /// Change the referendum stage from inactive to the voting stage.
+    fn start_revealing_period(old_stage: ReferendumStageVotingOf<T>) {
+        // change referendum state
+        Stage::<T, I>::put(ReferendumStage::Revealing(ReferendumStageRevealingOf::<
+            T,
+            I,
+        > {
+            started: <frame_system::Module<T>>::block_number() + 1.into(),
+            winning_target_count: old_stage.winning_target_count,
+            intermediate_winners: vec![],
+            current_cycle_id: old_stage.current_cycle_id,
+        }));
+    }
+
+    /// Conclude referendum, count votes, and select the winners.
+    fn conclude_referendum(
+        revealing_stage: ReferendumStageRevealingOf<T, I>,
+    ) -> Vec<OptionResult<<T as common::Trait>::MemberId, <T as Trait<I>>::VotePower>> {
+        // reset referendum state
+        Stage::<T, I>::put(ReferendumStage::Inactive);
+
+        // return winning option
+        revealing_stage.intermediate_winners
+    }
+
+    /// Cast a user's sealed vote for the current referendum cycle.
+    fn vote(
+        account_id: &<T as frame_system::Trait>::AccountId,
+        commitment: &T::Hash,
+        stake: &Balance<T, I>,
+        current_cycle_id: &u64,
+    ) -> Result<(), Error<T, I>> {
+        // lock stake amount
+        T::Currency::set_lock(
+            T::LockId::get(),
+            account_id,
+            *stake,
+            WithdrawReason::Transfer.into(),
+        );
+
+        // store vote
+        Votes::<T, I>::insert(
+            account_id,
+            CastVote {
+                commitment: *commitment,
+                stake: *stake,
+                cycle_id: *current_cycle_id,
+                vote_for: None,
+            },
+        );
+
+        Ok(())
+    }
+
+    /// Reveal user's vote target and check the commitment proof.
+    fn reveal_vote(
+        stage_data: ReferendumStageRevealingOf<T, I>,
+        account_id: &<T as frame_system::Trait>::AccountId,
+        option_id: &<T as common::Trait>::MemberId,
+        cast_vote: CastVoteOf<T, I>,
+    ) -> Result<(), Error<T, I>> {
+        // prepare new values
+        let vote_power = T::calculate_vote_power(&account_id, &cast_vote.stake);
+        let option_result = OptionResult {
+            option_id: *option_id,
+            vote_power,
+        };
+        // try to insert option to winner list or update it's value when already present
+        let new_winners = Self::try_winner_insert(
+            option_result,
+            &stage_data.intermediate_winners,
+            stage_data.winning_target_count,
+        );
+        let new_stage_data = ReferendumStageRevealing {
+            intermediate_winners: new_winners,
+            ..stage_data
+        };
+
+        // let runtime update option's vote power
+        T::increase_option_power(option_id, &vote_power);
+
+        // store revealed vote
+        Stage::<T, I>::mutate(|stage| *stage = ReferendumStage::Revealing(new_stage_data));
+
+        // remove user commitment to prevent repeated revealing
+        Votes::<T, I>::mutate(account_id, |vote| (*vote).vote_for = Some(*option_id));
+
+        Ok(())
+    }
+
+    /// Release stake associated to the user's last vote.
+    fn release_vote_stake(account_id: &<T as frame_system::Trait>::AccountId) {
+        // lock stake amount
+        T::Currency::remove_lock(T::LockId::get(), account_id);
+
+        // remove vote record
+        Votes::<T, I>::remove(account_id);
+    }
+
+    /// Tries to insert option to the proper place in the winners list. Utility for reaveal_vote() function.
+    fn try_winner_insert(
+        option_result: OptionResultOf<T, I>,
+        current_winners: &[OptionResultOf<T, I>],
+        winning_target_count: u64,
+    ) -> Vec<OptionResultOf<T, I>> {
+        /// Tries to place record to temporary place in the winning list.
+        fn place_record_to_winner_list<T: Trait<I>, I: Instance>(
+            option_result: OptionResultOf<T, I>,
+            current_winners: &[OptionResultOf<T, I>],
+            winning_target_count: u64,
+        ) -> (Vec<OptionResultOf<T, I>>, Option<usize>) {
+            let current_winners_count = current_winners.len();
+
+            // check if option is already somewhere in list
+            let current_winners_index_of_vote_recipient: Option<usize> = current_winners
+                .iter()
+                .enumerate()
+                .find(|(_, value)| option_result.option_id == value.option_id)
+                .map(|(index, _)| index);
+
+            // espace when item is currently not in winning list and still has not enough vote power to make it to already full list
+            if current_winners_index_of_vote_recipient.is_none()
+                && current_winners_count as u64 == winning_target_count
+                && option_result.vote_power <= current_winners[current_winners_count - 1].vote_power
+            {
+                return (current_winners.to_vec(), None);
+            }
+
+            let mut new_winners = current_winners.to_vec();
+
+            // update record in list when it is already present
+            if let Some(index) = current_winners_index_of_vote_recipient {
+                let old_option_total = T::get_option_power(&option_result.option_id);
+                let new_option_total = old_option_total + option_result.vote_power;
+
+                new_winners[index] = OptionResult {
+                    option_id: option_result.option_id,
+                    vote_power: new_option_total,
+                };
+
+                return (new_winners, Some(index));
+            }
+
+            // at this point record needs to be added to list
+
+            // replace last winner if list is already full
+            if current_winners_count as u64 == winning_target_count {
+                let last_index = current_winners_count - 1;
+                new_winners[last_index] = option_result;
+
+                return (new_winners, Some(last_index));
+            }
+
+            // append winner to incomplete list
+            new_winners.push(option_result);
+
+            (new_winners, Some(current_winners_count))
+        }
+
+        // if there are no winners right now return list with only current option
+        if current_winners.is_empty() {
+            return vec![option_result];
+        }
+
+        // get new possibly updated list and record's position in it
+        let (mut new_winners, current_record_index) = place_record_to_winner_list::<T, I>(
+            option_result,
+            current_winners,
+            winning_target_count,
+        );
+
+        // resort list in case it was updated
+        if let Some(index) = current_record_index {
+            for i in (1..=index).rev() {
+                if new_winners[i].vote_power <= new_winners[i - 1].vote_power {
+                    break;
+                }
+
+                new_winners.swap(i, i - 1);
+            }
+        }
+
+        new_winners.to_vec()
+    }
+}
+
+/////////////////// Ensure checks //////////////////////////////////////////////
+
+struct EnsureChecks<T: Trait<I>, I: Instance> {
+    _dummy: PhantomData<(T, I)>, // 0-sized data meant only to bound generic parameters
+}
+
+impl<T: Trait<I>, I: Instance> EnsureChecks<T, I> {
+    /////////////////// Common checks //////////////////////////////////////////
+
+    fn ensure_regular_user(origin: T::Origin) -> Result<T::AccountId, Error<T, I>> {
+        let account_id = ensure_signed(origin)?;
+
+        Ok(account_id)
+    }
+
+    /////////////////// Action checks //////////////////////////////////////////
+
+    fn can_start_referendum(origin: T::Origin) -> Result<(), ()> {
+        T::ManagerOrigin::ensure_origin(origin).map_err(|_| ())?;
+
+        // ensure referendum is not already running
+        match Stage::<T, I>::get() {
+            ReferendumStage::Inactive => Ok(()),
+            _ => Err(()),
+        }?;
+
+        Ok(())
+    }
+
+    fn can_vote(
+        origin: T::Origin,
+        stake: &Balance<T, I>,
+    ) -> Result<(u64, T::AccountId), Error<T, I>> {
+        fn prevent_repeated_vote<T: Trait<I>, I: Instance>(
+            cycle_id: &u64,
+            account_id: &T::AccountId,
+        ) -> Result<(), Error<T, I>> {
+            if !Votes::<T, I>::contains_key(&account_id) {
+                return Ok(());
+            }
+
+            let existing_vote = Votes::<T, I>::get(&account_id);
+
+            // don't allow repeated vote
+            if existing_vote.cycle_id == *cycle_id {
+                return Err(Error::<T, I>::AlreadyVotedThisCycle);
+            }
+
+            Ok(())
+        }
+
+        // ensure superuser requested action
+        let account_id = Self::ensure_regular_user(origin)?;
+
+        // ensure referendum is running
+        let current_cycle_id = match Stage::<T, I>::get() {
+            ReferendumStage::Voting(tmp_stage_data) => tmp_stage_data.current_cycle_id,
+            _ => return Err(Error::ReferendumNotRunning),
+        };
+
+        // prevent repeated vote
+        prevent_repeated_vote::<T, I>(&current_cycle_id, &account_id)?;
+
+        // ensure stake is enough for voting
+        if stake < &T::MinimumStake::get() {
+            return Err(Error::InsufficientStake);
+        }
+
+        // ensure account can lock the stake
+        if T::Currency::total_balance(&account_id) < *stake {
+            return Err(Error::InsufficientBalanceToStakeCurrency);
+        }
+
+        Ok((current_cycle_id, account_id))
+    }
+
+    fn can_reveal_vote<R: ReferendumManager<T::Origin, T::AccountId, T::MemberId, T::Hash>>(
+        origin: T::Origin,
+        salt: &[u8],
+        vote_option_id: &<T as common::Trait>::MemberId,
+    ) -> Result<CanRevealResult<T, I>, Error<T, I>> {
+        // ensure superuser requested action
+        let account_id = Self::ensure_regular_user(origin)?;
+
+        // ensure referendum is running
+        let stage_data = match Stage::<T, I>::get() {
+            ReferendumStage::Revealing(tmp_stage_data) => tmp_stage_data,
+            _ => return Err(Error::RevealingNotInProgress),
+        };
+
+        let cast_vote = Self::ensure_vote_exists(&account_id)?;
+
+        // ask runtime if option is valid
+        if !T::is_valid_option_id(vote_option_id) {
+            return Err(Error::InvalidVote);
+        }
+
+        // ensure vote was cast for the running referendum
+        if stage_data.current_cycle_id != cast_vote.cycle_id {
+            return Err(Error::InvalidVote);
+        }
+
+        // ensure salt is not too long
+        if salt.len() as u64 > T::MaxSaltLength::get() {
+            return Err(Error::SaltTooLong);
+        }
+
+        // ensure commitment corresponds to salt and vote option
+        let commitment = R::calculate_commitment(
+            &account_id,
+            salt,
+            &stage_data.current_cycle_id,
+            vote_option_id,
+        );
+        if commitment != cast_vote.commitment {
+            return Err(Error::InvalidReveal);
+        }
+
+        Ok((stage_data, account_id, cast_vote))
+    }
+
+    fn can_release_vote_stake(origin: T::Origin) -> Result<T::AccountId, Error<T, I>> {
+        // ensure superuser requested action
+        let account_id = Self::ensure_regular_user(origin)?;
+
+        let cast_vote = Self::ensure_vote_exists(&account_id)?;
+
+        // ask runtime if stake can be released
+        if !T::can_unlock_vote_stake(&cast_vote) {
+            return Err(Error::UnstakingForbidden);
+        }
+
+        Ok(account_id)
+    }
+
+    fn ensure_vote_exists(account_id: &T::AccountId) -> Result<CastVoteOf<T, I>, Error<T, I>> {
+        // ensure there is some vote with locked stake
+        if !Votes::<T, I>::contains_key(account_id) {
+            return Err(Error::VoteNotExisting);
+        }
+
+        let cast_vote = Votes::<T, I>::get(account_id);
+
+        Ok(cast_vote)
+    }
+}

+ 620 - 0
runtime-modules/referendum/src/mock.rs

@@ -0,0 +1,620 @@
+#![cfg(test)]
+
+/////////////////// Configuration //////////////////////////////////////////////
+use crate::{
+    Balance, CastVote, Error, Instance, Module, OptionResult, RawEvent, ReferendumManager,
+    ReferendumStage, ReferendumStageRevealing, ReferendumStageVoting, Stage, Trait, Votes,
+};
+
+use frame_support::traits::{Currency, LockIdentifier, OnFinalize};
+use frame_support::{
+    impl_outer_event, impl_outer_origin, parameter_types, StorageMap, StorageValue,
+};
+use frame_system::{EnsureOneOf, EnsureRoot, EnsureSigned, RawOrigin};
+use pallet_balances;
+use rand::Rng;
+use sp_core::H256;
+use sp_runtime::{
+    testing::Header,
+    traits::{BlakeTwo256, IdentityLookup},
+    Perbill,
+};
+use std::cell::RefCell;
+use std::collections::BTreeMap;
+use std::iter::FromIterator;
+use std::marker::PhantomData;
+
+use crate::GenesisConfig;
+
+pub const USER_ADMIN: u64 = 1;
+pub const USER_REGULAR: u64 = 2;
+pub const USER_REGULAR_POWER_VOTES: u64 = 3;
+pub const USER_REGULAR_2: u64 = 4;
+pub const USER_REGULAR_3: u64 = 5;
+
+pub const POWER_VOTE_STRENGTH: u64 = 10;
+
+/////////////////// Runtime and Instances //////////////////////////////////////
+// Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted.
+#[derive(Clone, PartialEq, Eq, Debug)]
+pub struct Runtime;
+
+// module instances
+
+#[derive(Clone, PartialEq, Eq, Debug)]
+pub struct Instance0;
+
+parameter_types! {
+    pub const MaxSaltLength: u64 = 32; // use some multiple of 8 for ez testing
+    pub const VoteStageDuration: u64 = 5;
+    pub const RevealStageDuration: u64 = 7;
+    pub const MinimumStake: u64 = 10000;
+    pub const LockId: LockIdentifier = *b"referend";
+}
+
+thread_local! {
+    pub static IS_UNSTAKE_ENABLED: RefCell<(bool, )> = RefCell::new((true, )); // global switch for stake locking features; use it to simulate lock fails
+    pub static IS_OPTION_ID_VALID: RefCell<(bool, )> = RefCell::new((true, )); // global switch used to test is_valid_option_id()
+
+    // complete intermediate results
+    pub static INTERMEDIATE_RESULTS: RefCell<BTreeMap<u64, <Runtime as Trait<Instance0>>::VotePower>> = RefCell::new(BTreeMap::<u64, <Runtime as Trait<Instance0>>::VotePower>::new());
+}
+
+impl Trait<Instance0> for Runtime {
+    type Event = TestEvent;
+
+    type MaxSaltLength = MaxSaltLength;
+
+    type Currency = pallet_balances::Module<Runtime>;
+    type LockId = LockId;
+
+    type ManagerOrigin =
+        EnsureOneOf<Self::AccountId, EnsureSigned<Self::AccountId>, EnsureRoot<Self::AccountId>>;
+
+    type VotePower = u64;
+
+    type VoteStageDuration = VoteStageDuration;
+    type RevealStageDuration = RevealStageDuration;
+
+    type MinimumStake = MinimumStake;
+
+    fn calculate_vote_power(
+        account_id: &<Self as frame_system::Trait>::AccountId,
+        stake: &Balance<Self, Instance0>,
+    ) -> <Self as Trait<Instance0>>::VotePower {
+        let stake: u64 = u64::from(*stake);
+        if *account_id == USER_REGULAR_POWER_VOTES {
+            return stake * POWER_VOTE_STRENGTH;
+        }
+
+        stake
+    }
+
+    fn can_unlock_vote_stake(
+        _vote: &CastVote<Self::Hash, Balance<Self, Instance0>, Self::MemberId>,
+    ) -> bool {
+        // trigger fail when requested to do so
+        if !IS_UNSTAKE_ENABLED.with(|value| value.borrow().0) {
+            return false;
+        }
+
+        true
+    }
+
+    fn process_results(_winners: &[OptionResult<Self::MemberId, Self::VotePower>]) {
+        // not used right now
+    }
+
+    fn is_valid_option_id(_option_index: &u64) -> bool {
+        if !IS_OPTION_ID_VALID.with(|value| value.borrow().0) {
+            return false;
+        }
+
+        true
+    }
+
+    fn get_option_power(option_id: &u64) -> Self::VotePower {
+        INTERMEDIATE_RESULTS.with(|value| match value.borrow().get(option_id) {
+            Some(vote_power) => *vote_power,
+            None => 0,
+        })
+    }
+
+    fn increase_option_power(option_id: &u64, amount: &Self::VotePower) {
+        INTERMEDIATE_RESULTS.with(|value| {
+            let current = Self::get_option_power(option_id);
+
+            value.borrow_mut().insert(*option_id, amount + current);
+        });
+    }
+}
+
+impl common::Trait for Runtime {
+    type MemberId = u64;
+    type ActorId = u64;
+}
+
+parameter_types! {
+    pub const ExistentialDeposit: u64 = 0;
+    pub const MaxLocks: u32 = 50;
+}
+
+impl pallet_balances::Trait for Runtime {
+    type Balance = u64;
+    type Event = TestEvent;
+    type DustRemoval = ();
+    type ExistentialDeposit = ExistentialDeposit;
+    type AccountStore = frame_system::Module<Self>;
+    type WeightInfo = ();
+    type MaxLocks = MaxLocks;
+}
+
+impl Runtime {
+    pub fn feature_stack_lock(unstake_enabled: bool) -> () {
+        IS_UNSTAKE_ENABLED.with(|value| {
+            *value.borrow_mut() = (unstake_enabled,);
+        });
+    }
+
+    pub fn feature_option_id_valid(is_valid: bool) -> () {
+        IS_OPTION_ID_VALID.with(|value| {
+            *value.borrow_mut() = (is_valid,);
+        });
+    }
+}
+
+/////////////////// Module implementation //////////////////////////////////////
+
+impl_outer_origin! {
+    pub enum Origin for Runtime {}
+}
+
+mod event_mod {
+    pub use super::Instance0;
+    pub use crate::Event;
+}
+
+mod tmp {
+    pub use pallet_balances::Event;
+}
+
+impl_outer_event! {
+    pub enum TestEvent for Runtime {
+        event_mod Instance0 <T>,
+        frame_system<T>,
+        tmp<T>,
+    }
+}
+
+parameter_types! {
+    pub const BlockHashCount: u64 = 250;
+    pub const MaximumBlockWeight: u32 = 1024;
+    pub const MaximumBlockLength: u32 = 2 * 1024;
+    pub const AvailableBlockRatio: Perbill = Perbill::one();
+    pub const MinimumPeriod: u64 = 5;
+}
+
+//#[allow(non_upper_case_globals)] // `decl_storage` macro defines this weird name
+impl Instance for Instance0 {
+    const PREFIX: &'static str = "Instance0";
+}
+
+impl frame_system::Trait for Runtime {
+    type BaseCallFilter = ();
+    type Origin = Origin;
+    type Call = ();
+    type Index = u64;
+    type BlockNumber = u64;
+    type Hash = H256;
+    type Hashing = BlakeTwo256;
+    type AccountId = u64;
+    type Lookup = IdentityLookup<Self::AccountId>;
+    type Header = Header;
+    type Event = TestEvent;
+    type BlockHashCount = BlockHashCount;
+    type MaximumBlockWeight = MaximumBlockWeight;
+    type DbWeight = ();
+    type BlockExecutionWeight = ();
+    type ExtrinsicBaseWeight = ();
+    type MaximumExtrinsicWeight = ();
+    type MaximumBlockLength = MaximumBlockLength;
+    type AvailableBlockRatio = AvailableBlockRatio;
+    type Version = ();
+    type PalletInfo = ();
+    type AccountData = pallet_balances::AccountData<u64>;
+    type OnNewAccount = ();
+    type OnKilledAccount = ();
+    type SystemWeightInfo = ();
+}
+
+/////////////////// Data structures ////////////////////////////////////////////
+
+#[allow(dead_code)]
+#[derive(Clone)]
+pub enum OriginType<AccountId> {
+    Signed(AccountId),
+    //Inherent, <== did not find how to make such an origin yet
+    Root,
+    None,
+}
+
+/////////////////// Utility mocks //////////////////////////////////////////////s
+
+pub fn default_genesis_config() -> GenesisConfig<Runtime, Instance0> {
+    GenesisConfig::<Runtime, Instance0> {
+        stage: ReferendumStage::default(),
+        votes: vec![],
+    }
+}
+
+pub fn build_test_externalities(
+    config: GenesisConfig<Runtime, Instance0>,
+) -> sp_io::TestExternalities {
+    let mut t = frame_system::GenesisConfig::default()
+        .build_storage::<Runtime>()
+        .unwrap();
+
+    config.assimilate_storage(&mut t).unwrap();
+
+    let mut result = Into::<sp_io::TestExternalities>::into(t.clone());
+
+    // Make sure we are not in block 0 where no events are emitted - see https://substrate.dev/recipes/2-appetizers/4-events.html#emitting-events
+    result.execute_with(|| {
+        // topup significant accounts
+        let amount = 40000; // some high enough number to pass all test checks
+        topup_account(USER_ADMIN, amount);
+        topup_account(USER_REGULAR, amount);
+        topup_account(USER_REGULAR_2, amount);
+        topup_account(USER_REGULAR_3, amount);
+        topup_account(USER_REGULAR_POWER_VOTES, amount);
+
+        InstanceMockUtils::<Runtime, Instance0>::increase_block_number(1)
+    });
+
+    result
+}
+
+// topup currency to the account
+fn topup_account(account_id: u64, amount: u64) {
+    let account_id = account_id;
+    let _ = pallet_balances::Module::<Runtime>::deposit_creating(&account_id, amount);
+}
+
+pub struct InstanceMockUtils<T: Trait<I>, I: Instance> {
+    _dummy: PhantomData<(T, I)>, // 0-sized data meant only to bound generic parameters
+}
+
+impl<T: Trait<I>, I: Instance> InstanceMockUtils<T, I>
+where
+    T::BlockNumber: From<u64> + Into<u64>,
+{
+    pub fn mock_origin(origin: OriginType<T::AccountId>) -> T::Origin {
+        match origin {
+            OriginType::Signed(account_id) => T::Origin::from(RawOrigin::Signed(account_id)),
+            OriginType::Root => RawOrigin::Root.into(),
+            OriginType::None => RawOrigin::None.into(),
+            //_ => panic!("not implemented"),
+        }
+    }
+
+    pub fn origin_access<F: Fn(OriginType<T::AccountId>) -> ()>(
+        origin_account_id: T::AccountId,
+        f: F,
+    ) {
+        let config = default_genesis_config();
+
+        build_test_externalities(config).execute_with(|| {
+            let origin = OriginType::Signed(origin_account_id);
+
+            f(origin)
+        });
+    }
+
+    pub fn increase_block_number(increase: u64) -> () {
+        let block_number = frame_system::Module::<T>::block_number();
+
+        for i in 0..increase {
+            let tmp_index: T::BlockNumber = block_number + i.into();
+
+            <Module<T, I> as OnFinalize<T::BlockNumber>>::on_finalize(tmp_index);
+            frame_system::Module::<T>::set_block_number(tmp_index + 1.into());
+        }
+    }
+
+    pub fn calculate_commitment(
+        account_id: &<T as frame_system::Trait>::AccountId,
+        vote_option_index: &<T as common::Trait>::MemberId,
+        cycle_id: &u64,
+    ) -> (T::Hash, Vec<u8>) {
+        Self::calculate_commitment_for_cycle(account_id, &cycle_id, vote_option_index, None)
+    }
+
+    pub fn calculate_commitment_custom_salt(
+        account_id: &<T as frame_system::Trait>::AccountId,
+        vote_option_index: &<T as common::Trait>::MemberId,
+        custom_salt: &[u8],
+        cycle_id: &u64,
+    ) -> (T::Hash, Vec<u8>) {
+        Self::calculate_commitment_for_cycle(
+            account_id,
+            &cycle_id,
+            vote_option_index,
+            Some(custom_salt),
+        )
+    }
+
+    pub fn generate_salt() -> Vec<u8> {
+        let mut rng = rand::thread_rng();
+
+        rng.gen::<u64>().to_be_bytes().to_vec()
+    }
+
+    pub fn calculate_commitment_for_cycle(
+        account_id: &<T as frame_system::Trait>::AccountId,
+        cycle_id: &u64,
+        vote_option_index: &<T as common::Trait>::MemberId,
+        custom_salt: Option<&[u8]>,
+    ) -> (T::Hash, Vec<u8>) {
+        let salt = match custom_salt {
+            Some(tmp_salt) => tmp_salt.to_vec(),
+            None => Self::generate_salt(),
+        };
+
+        (
+            <Module<T, I> as ReferendumManager<
+                <T as frame_system::Trait>::Origin,
+                <T as frame_system::Trait>::AccountId,
+                <T as common::Trait>::MemberId,
+                <T as frame_system::Trait>::Hash,
+            >>::calculate_commitment(account_id, &salt, cycle_id, vote_option_index),
+            salt.to_vec(),
+        )
+    }
+
+    pub fn transform_results(input: Vec<T::VotePower>) -> BTreeMap<u64, T::VotePower> {
+        BTreeMap::from_iter(
+            input
+                .into_iter()
+                .enumerate()
+                .map(|(k, v)| (k as u64, v))
+                .filter(|(_, v)| v != &Into::<T::VotePower>::into(0))
+                .collect::<Vec<(u64, T::VotePower)>>(),
+        )
+    }
+}
+
+/////////////////// Mocks of Module's actions //////////////////////////////////
+
+pub struct InstanceMocks<T: Trait<I>, I: Instance> {
+    _dummy: PhantomData<(T, I)>, // 0-sized data meant only to bound generic parameters
+}
+
+impl InstanceMocks<Runtime, Instance0> {
+    pub fn start_referendum_extrinsic(
+        origin: OriginType<<Runtime as frame_system::Trait>::AccountId>,
+        winning_target_count: u64,
+        cycle_id: u64,
+        expected_result: Result<(), ()>,
+    ) -> () {
+        let extra_winning_target_count = winning_target_count - 1;
+
+        // check method returns expected result
+        assert_eq!(
+            Module::<Runtime, Instance0>::start_referendum(
+                InstanceMockUtils::<Runtime, Instance0>::mock_origin(origin),
+                extra_winning_target_count,
+                cycle_id,
+            ),
+            expected_result,
+        );
+
+        Self::start_referendum_inner(extra_winning_target_count, cycle_id, expected_result)
+    }
+
+    pub fn start_referendum_manager(
+        winning_target_count: u64,
+        cycle_id: u64,
+        expected_result: Result<(), ()>,
+    ) -> () {
+        let extra_winning_target_count = winning_target_count - 1;
+
+        // check method returns expected result
+        assert_eq!(
+            <Module::<Runtime, Instance0> as ReferendumManager<
+                <Runtime as frame_system::Trait>::Origin,
+                <Runtime as frame_system::Trait>::AccountId,
+                <Runtime as common::Trait>::MemberId,
+                <Runtime as frame_system::Trait>::Hash,
+            >>::start_referendum(
+                InstanceMockUtils::<Runtime, Instance0>::mock_origin(OriginType::Root),
+                extra_winning_target_count,
+                cycle_id,
+            )
+            .is_ok(),
+            expected_result.is_ok(),
+        );
+
+        Self::start_referendum_inner(extra_winning_target_count, cycle_id, expected_result)
+    }
+
+    fn start_referendum_inner(
+        extra_winning_target_count: u64,
+        cycle_id: u64,
+        expected_result: Result<(), ()>,
+    ) {
+        if expected_result.is_err() {
+            return;
+        }
+
+        let winning_target_count = extra_winning_target_count + 1;
+        let block_number = frame_system::Module::<Runtime>::block_number();
+
+        assert_eq!(
+            Stage::<Runtime, Instance0>::get(),
+            ReferendumStage::Voting(ReferendumStageVoting {
+                started: block_number + 1, // actual voting starts in the next block (thats why +1)
+                winning_target_count,
+                current_cycle_id: cycle_id,
+            }),
+        );
+
+        InstanceMockUtils::<Runtime, Instance0>::increase_block_number(1);
+
+        assert_eq!(
+            frame_system::Module::<Runtime>::events()
+                .last()
+                .unwrap()
+                .event,
+            TestEvent::from(RawEvent::ReferendumStarted(winning_target_count))
+        );
+    }
+
+    pub fn check_voting_finished(winning_target_count: u64, cycle_id: u64) {
+        let block_number = frame_system::Module::<Runtime>::block_number();
+
+        assert_eq!(
+            Stage::<Runtime, Instance0>::get(),
+            ReferendumStage::Revealing(ReferendumStageRevealing {
+                started: block_number,
+                winning_target_count,
+                intermediate_winners: vec![],
+                current_cycle_id: cycle_id,
+            }),
+        );
+
+        // check event was emitted
+        assert_eq!(
+            frame_system::Module::<Runtime>::events()
+                .last()
+                .unwrap()
+                .event,
+            TestEvent::event_mod_Instance0(RawEvent::RevealingStageStarted())
+        );
+    }
+
+    pub fn check_revealing_finished(
+        expected_winners: Vec<
+            OptionResult<
+                <Runtime as common::Trait>::MemberId,
+                <Runtime as Trait<Instance0>>::VotePower,
+            >,
+        >,
+        expected_referendum_result: BTreeMap<u64, <Runtime as Trait<Instance0>>::VotePower>,
+    ) {
+        assert_eq!(
+            Stage::<Runtime, Instance0>::get(),
+            ReferendumStage::Inactive,
+        );
+
+        // check event was emitted
+        assert_eq!(
+            frame_system::Module::<Runtime>::events()
+                .last()
+                .unwrap()
+                .event,
+            TestEvent::event_mod_Instance0(RawEvent::ReferendumFinished(expected_winners,))
+        );
+
+        INTERMEDIATE_RESULTS.with(|value| assert_eq!(*value.borrow(), expected_referendum_result,));
+    }
+
+    pub fn vote(
+        origin: OriginType<<Runtime as frame_system::Trait>::AccountId>,
+        account_id: <Runtime as frame_system::Trait>::AccountId,
+        commitment: <Runtime as frame_system::Trait>::Hash,
+        stake: Balance<Runtime, Instance0>,
+        cycle_id: u64,
+        expected_result: Result<(), Error<Runtime, Instance0>>,
+    ) -> () {
+        // check method returns expected result
+        assert_eq!(
+            Module::<Runtime, Instance0>::vote(
+                InstanceMockUtils::<Runtime, Instance0>::mock_origin(origin),
+                commitment,
+                stake,
+            ),
+            expected_result,
+        );
+
+        if expected_result.is_err() {
+            return;
+        }
+
+        assert_eq!(
+            Votes::<Runtime, Instance0>::get(account_id),
+            CastVote {
+                commitment,
+                cycle_id: cycle_id.clone(),
+                stake,
+                vote_for: None,
+            },
+        );
+
+        // check event was emitted
+        assert_eq!(
+            frame_system::Module::<Runtime>::events()
+                .last()
+                .unwrap()
+                .event,
+            TestEvent::event_mod_Instance0(RawEvent::VoteCast(account_id, commitment, stake))
+        );
+    }
+
+    pub fn reveal_vote(
+        origin: OriginType<<Runtime as frame_system::Trait>::AccountId>,
+        account_id: <Runtime as frame_system::Trait>::AccountId,
+        salt: Vec<u8>,
+        vote_option_index: u64,
+        expected_result: Result<(), Error<Runtime, Instance0>>,
+    ) -> () {
+        // check method returns expected result
+        assert_eq!(
+            Module::<Runtime, Instance0>::reveal_vote(
+                InstanceMockUtils::<Runtime, Instance0>::mock_origin(origin),
+                salt,
+                vote_option_index,
+            ),
+            expected_result,
+        );
+
+        if expected_result.is_err() {
+            return;
+        }
+
+        // check event was emitted
+        assert_eq!(
+            frame_system::Module::<Runtime>::events()
+                .last()
+                .unwrap()
+                .event,
+            TestEvent::event_mod_Instance0(RawEvent::VoteRevealed(account_id, vote_option_index))
+        );
+    }
+
+    pub fn release_stake(
+        origin: OriginType<<Runtime as frame_system::Trait>::AccountId>,
+        account_id: <Runtime as frame_system::Trait>::AccountId,
+        expected_result: Result<(), Error<Runtime, Instance0>>,
+    ) -> () {
+        // check method returns expected result
+        assert_eq!(
+            Module::<Runtime, Instance0>::release_vote_stake(
+                InstanceMockUtils::<Runtime, Instance0>::mock_origin(origin),
+            ),
+            expected_result,
+        );
+
+        if expected_result.is_err() {
+            return;
+        }
+
+        // check event was emitted
+        assert_eq!(
+            frame_system::Module::<Runtime>::events()
+                .last()
+                .unwrap()
+                .event,
+            TestEvent::event_mod_Instance0(RawEvent::StakeReleased(account_id))
+        );
+    }
+}

+ 1005 - 0
runtime-modules/referendum/src/tests.rs

@@ -0,0 +1,1005 @@
+#![cfg(test)]
+
+use super::{Error, OptionResult, Trait};
+use crate::mock::*;
+
+type Mocks = InstanceMocks<Runtime, Instance0>;
+type MockUtils = InstanceMockUtils<Runtime, Instance0>;
+
+/////////////////// Lifetime - referendum start ////////////////////////////////
+
+/// Test that referendum can be successfully started via extrinsic.
+#[test]
+fn referendum_start() {
+    MockUtils::origin_access(USER_ADMIN, |origin| {
+        let winning_target_count = 1;
+        let cycle_id = 1;
+
+        Mocks::start_referendum_extrinsic(origin, winning_target_count, cycle_id, Ok(()));
+    });
+}
+
+/// Test that referendum can be started via extrinsic only by superuser.
+#[test]
+fn referendum_start_access_restricted() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let winning_target_count = 1;
+        let cycle_id = 1;
+
+        Mocks::start_referendum_extrinsic(
+            OriginType::None,
+            winning_target_count,
+            cycle_id,
+            Err(()),
+        );
+    });
+}
+
+/// Test that referendum can't be started again before it ends first.
+#[test]
+fn referendum_start_forbidden_after_start() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let origin = OriginType::Signed(USER_ADMIN);
+        let options = 1;
+        let winning_target_count = 1;
+        let cycle_id = 1;
+
+        Mocks::start_referendum_extrinsic(
+            origin.clone(),
+            winning_target_count.clone(),
+            cycle_id,
+            Ok(()),
+        );
+        Mocks::start_referendum_extrinsic(origin.clone(), options.clone(), cycle_id, Err(()));
+    });
+}
+
+/////////////////// Lifetime - voting //////////////////////////////////////////
+
+/// Test that a user can successfully vote in the referendum.
+#[test]
+fn voting() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let account_id = USER_ADMIN;
+        let origin = OriginType::Signed(account_id);
+        let cycle_id = 1;
+
+        let winning_target_count = 1;
+        let option_to_vote_for = 0;
+        let stake = <Runtime as Trait<Instance0>>::MinimumStake::get();
+        let (commitment, _) =
+            MockUtils::calculate_commitment(&account_id, &option_to_vote_for, &cycle_id);
+
+        Mocks::start_referendum_extrinsic(
+            origin.clone(),
+            winning_target_count,
+            cycle_id.clone(),
+            Ok(()),
+        );
+
+        Mocks::vote(
+            origin.clone(),
+            account_id,
+            commitment,
+            stake,
+            cycle_id.clone(),
+            Ok(()),
+        );
+    });
+}
+
+/// Test that voting is prohibited outside of the voting stage.
+#[test]
+fn voting_referendum_not_running() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let account_id = USER_ADMIN;
+        let origin = OriginType::Signed(account_id);
+        let cycle_id = 1;
+
+        let winning_target_count = 1;
+        let option_to_vote_for = 0;
+        let stake = <Runtime as Trait<Instance0>>::MinimumStake::get();
+        let (commitment, _) =
+            MockUtils::calculate_commitment(&account_id, &option_to_vote_for, &cycle_id);
+
+        // try to vote before referendum starts
+        Mocks::vote(
+            origin.clone(),
+            account_id,
+            commitment,
+            stake,
+            cycle_id.clone(),
+            Err(Error::ReferendumNotRunning),
+        );
+
+        Mocks::start_referendum_extrinsic(
+            origin.clone(),
+            winning_target_count,
+            cycle_id.clone(),
+            Ok(()),
+        );
+
+        let voting_stage_duration = <Runtime as Trait<Instance0>>::VoteStageDuration::get();
+        MockUtils::increase_block_number(voting_stage_duration + 1);
+
+        // try to vote after voting stage ended
+        Mocks::vote(
+            origin.clone(),
+            account_id,
+            commitment,
+            stake,
+            cycle_id.clone(),
+            Err(Error::ReferendumNotRunning),
+        );
+    });
+}
+
+/// Test that vote will fail when staking too little.
+#[test]
+fn voting_stake_too_low() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let account_id = USER_ADMIN;
+        let origin = OriginType::Signed(account_id);
+        let cycle_id = 1;
+
+        let winning_target_count = 1;
+        let option_to_vote_for = 0;
+        let stake = <Runtime as Trait<Instance0>>::MinimumStake::get() - 1;
+        let (commitment, _) =
+            MockUtils::calculate_commitment(&account_id, &option_to_vote_for, &cycle_id);
+
+        Mocks::start_referendum_extrinsic(
+            origin.clone(),
+            winning_target_count,
+            cycle_id.clone(),
+            Ok(()),
+        );
+        Mocks::vote(
+            origin.clone(),
+            account_id,
+            commitment,
+            stake,
+            cycle_id.clone(),
+            Err(Error::InsufficientStake),
+        );
+    });
+}
+
+/// Test that a user is prevented from voting multiple times in the same cycle.
+#[test]
+fn voting_user_repeated_vote() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let account_id = USER_ADMIN;
+        let origin = OriginType::Signed(account_id);
+        let cycle_id = 1;
+
+        let winning_target_count = 1;
+        let option_to_vote_for = 0;
+        let stake = <Runtime as Trait<Instance0>>::MinimumStake::get();
+        let different_stake = stake * 2;
+        let (commitment, _) =
+            MockUtils::calculate_commitment(&account_id, &option_to_vote_for, &cycle_id);
+
+        Mocks::start_referendum_extrinsic(
+            origin.clone(),
+            winning_target_count,
+            cycle_id.clone(),
+            Ok(()),
+        );
+        Mocks::vote(
+            origin.clone(),
+            account_id,
+            commitment,
+            stake.clone(),
+            cycle_id.clone(),
+            Ok(()),
+        );
+
+        Mocks::vote(
+            origin.clone(),
+            account_id,
+            commitment,
+            different_stake.clone(),
+            cycle_id.clone(),
+            Err(Error::AlreadyVotedThisCycle),
+        );
+    });
+}
+
+/////////////////// Lifetime - voting finish ///////////////////////////////////
+
+/// Test that referendum will indeed finish after expected number of blocks.
+#[test]
+fn finish_voting() {
+    MockUtils::origin_access(USER_ADMIN, |origin| {
+        let winning_target_count = 1;
+        let cycle_id = 1;
+
+        Mocks::start_referendum_extrinsic(
+            origin.clone(),
+            winning_target_count,
+            cycle_id.clone(),
+            Ok(()),
+        );
+
+        let voting_stage_duration = <Runtime as Trait<Instance0>>::VoteStageDuration::get();
+
+        MockUtils::increase_block_number(voting_stage_duration);
+
+        Mocks::check_voting_finished(winning_target_count, cycle_id.clone());
+    });
+}
+
+/////////////////// Lifetime - revealing ///////////////////////////////////////
+
+/// That that a user can reveal his vote.
+#[test]
+fn reveal() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let voting_stage_duration = <Runtime as Trait<Instance0>>::VoteStageDuration::get();
+        let account_id = USER_ADMIN;
+        let origin = OriginType::Signed(account_id);
+        let cycle_id = 1;
+        let winning_target_count = 1;
+
+        let option_to_vote_for = 1;
+        let stake = <Runtime as Trait<Instance0>>::MinimumStake::get();
+        let (commitment, salt) =
+            MockUtils::calculate_commitment(&account_id, &option_to_vote_for, &cycle_id);
+
+        Mocks::start_referendum_extrinsic(
+            origin.clone(),
+            winning_target_count.clone(),
+            cycle_id,
+            Ok(()),
+        );
+        Mocks::vote(
+            origin.clone(),
+            account_id,
+            commitment,
+            stake,
+            cycle_id.clone(),
+            Ok(()),
+        );
+        MockUtils::increase_block_number(voting_stage_duration);
+
+        Mocks::check_voting_finished(winning_target_count, cycle_id);
+        Mocks::reveal_vote(
+            origin.clone(),
+            account_id,
+            salt,
+            option_to_vote_for.clone(),
+            Ok(()),
+        );
+    });
+}
+
+/// Test that a user can't vote outside of the voting stage.
+#[test]
+fn reveal_reveal_stage_not_running() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let voting_stage_duration = <Runtime as Trait<Instance0>>::VoteStageDuration::get();
+        let reveal_stage_duration = <Runtime as Trait<Instance0>>::RevealStageDuration::get();
+        let account_id = USER_ADMIN;
+        let origin = OriginType::Signed(account_id);
+        let cycle_id = 1;
+        let winning_target_count = 1;
+
+        let option_to_vote_for = 1;
+        let stake = <Runtime as Trait<Instance0>>::MinimumStake::get();
+        let (commitment, salt) =
+            MockUtils::calculate_commitment(&account_id, &option_to_vote_for, &cycle_id);
+
+        Mocks::start_referendum_extrinsic(
+            origin.clone(),
+            winning_target_count.clone(),
+            cycle_id,
+            Ok(()),
+        );
+
+        Mocks::reveal_vote(
+            origin.clone(),
+            account_id,
+            salt.clone(),
+            option_to_vote_for.clone(),
+            Err(Error::RevealingNotInProgress),
+        );
+
+        Mocks::vote(
+            origin.clone(),
+            account_id,
+            commitment,
+            stake,
+            cycle_id.clone(),
+            Ok(()),
+        );
+        MockUtils::increase_block_number(voting_stage_duration);
+
+        Mocks::check_voting_finished(winning_target_count, cycle_id);
+        MockUtils::increase_block_number(reveal_stage_duration);
+        Mocks::check_revealing_finished(vec![], MockUtils::transform_results(vec![]));
+
+        Mocks::reveal_vote(
+            origin.clone(),
+            account_id,
+            salt.clone(),
+            option_to_vote_for.clone(),
+            Err(Error::RevealingNotInProgress),
+        );
+    });
+}
+
+/// Test that the revealing stage will finish after expected number of blocks even when no votes were cast.
+#[test]
+fn reveal_no_vote() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let voting_stage_duration = <Runtime as Trait<Instance0>>::VoteStageDuration::get();
+        let reveal_stage_duration = <Runtime as Trait<Instance0>>::RevealStageDuration::get();
+        let account_id = USER_ADMIN;
+        let origin = OriginType::Signed(account_id);
+        let cycle_id = 1;
+        let winning_target_count = 1;
+
+        Mocks::start_referendum_extrinsic(
+            origin.clone(),
+            winning_target_count.clone(),
+            cycle_id,
+            Ok(()),
+        );
+        MockUtils::increase_block_number(voting_stage_duration);
+
+        Mocks::check_voting_finished(winning_target_count, cycle_id);
+        MockUtils::increase_block_number(reveal_stage_duration);
+
+        Mocks::check_revealing_finished(vec![], MockUtils::transform_results(vec![]));
+    });
+}
+
+/// Test that salt used to calculate commitment isn't too long.
+#[test]
+fn reveal_salt_too_long() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let max_salt_length = <Runtime as Trait<Instance0>>::MaxSaltLength::get();
+        let voting_stage_duration = <Runtime as Trait<Instance0>>::VoteStageDuration::get();
+        let account_id = USER_ADMIN;
+        let origin = OriginType::Signed(account_id);
+        let cycle_id = 1;
+        let winning_target_count = 1;
+
+        let mut salt = vec![];
+        for _ in 0..(max_salt_length / 8 + 1) {
+            salt.append(&mut MockUtils::generate_salt());
+        }
+
+        let option_to_vote_for = 1;
+        let stake = <Runtime as Trait<Instance0>>::MinimumStake::get();
+        let (commitment, _) = MockUtils::calculate_commitment_custom_salt(
+            &account_id,
+            &option_to_vote_for,
+            &salt,
+            &cycle_id,
+        );
+
+        Mocks::start_referendum_extrinsic(
+            origin.clone(),
+            winning_target_count.clone(),
+            cycle_id,
+            Ok(()),
+        );
+        Mocks::vote(
+            origin.clone(),
+            account_id,
+            commitment,
+            stake,
+            cycle_id.clone(),
+            Ok(()),
+        );
+        MockUtils::increase_block_number(voting_stage_duration);
+
+        Mocks::check_voting_finished(winning_target_count, cycle_id);
+        Mocks::reveal_vote(
+            origin.clone(),
+            account_id,
+            salt,
+            option_to_vote_for,
+            Err(Error::SaltTooLong),
+        );
+    });
+}
+
+/// Test that revealing of a vote for a not-existing option is rejected.
+#[test]
+fn reveal_invalid_vote() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let voting_stage_duration = <Runtime as Trait<Instance0>>::VoteStageDuration::get();
+        let account_id = USER_ADMIN;
+        let origin = OriginType::Signed(account_id);
+        let cycle_id = 1;
+        let winning_target_count = 1;
+
+        let invalid_option = 1000;
+        let option_to_vote_for = 1;
+        let stake = <Runtime as Trait<Instance0>>::MinimumStake::get();
+        let (commitment, salt) =
+            MockUtils::calculate_commitment(&account_id, &option_to_vote_for, &cycle_id);
+
+        Mocks::start_referendum_extrinsic(
+            origin.clone(),
+            winning_target_count.clone(),
+            cycle_id,
+            Ok(()),
+        );
+        Mocks::vote(
+            origin.clone(),
+            account_id,
+            commitment,
+            stake,
+            cycle_id.clone(),
+            Ok(()),
+        );
+        MockUtils::increase_block_number(voting_stage_duration);
+
+        Runtime::feature_option_id_valid(false);
+
+        Mocks::check_voting_finished(winning_target_count, cycle_id);
+        Mocks::reveal_vote(
+            origin.clone(),
+            account_id,
+            salt,
+            invalid_option,
+            Err(Error::InvalidVote),
+        );
+    });
+}
+
+/// Test that invalid commitment proof is rejected.
+#[test]
+fn reveal_invalid_commitment_proof() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let voting_stage_duration = <Runtime as Trait<Instance0>>::VoteStageDuration::get();
+        let account_id = USER_ADMIN;
+        let origin = OriginType::Signed(account_id);
+        let cycle_id = 1;
+        let winning_target_count = 1;
+
+        let option_to_vote_for = 0;
+        let invalid_option = option_to_vote_for + 1;
+        let stake = <Runtime as Trait<Instance0>>::MinimumStake::get();
+        let (commitment, salt) =
+            MockUtils::calculate_commitment(&account_id, &option_to_vote_for, &cycle_id);
+
+        Mocks::start_referendum_extrinsic(
+            origin.clone(),
+            winning_target_count.clone(),
+            cycle_id,
+            Ok(()),
+        );
+        Mocks::vote(
+            origin.clone(),
+            account_id,
+            commitment,
+            stake,
+            cycle_id.clone(),
+            Ok(()),
+        );
+        MockUtils::increase_block_number(voting_stage_duration);
+
+        Mocks::check_voting_finished(winning_target_count, cycle_id);
+        Mocks::reveal_vote(
+            origin.clone(),
+            account_id,
+            salt,
+            invalid_option,
+            Err(Error::InvalidReveal),
+        );
+    });
+}
+
+/////////////////// Lifetime - revealing finish ////////////////////////////////
+
+/// Test that the revealing stage will finish after expected number of blocks.
+#[test]
+fn finish_revealing_period() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let voting_stage_duration = <Runtime as Trait<Instance0>>::VoteStageDuration::get();
+        let reveal_stage_duration = <Runtime as Trait<Instance0>>::RevealStageDuration::get();
+        let account_id = USER_ADMIN;
+        let origin = OriginType::Signed(account_id);
+        let cycle_id = 1;
+        let winning_target_count = 1;
+
+        let option_to_vote_for = 0;
+        let stake = <Runtime as Trait<Instance0>>::MinimumStake::get();
+        let (commitment, salt) =
+            MockUtils::calculate_commitment(&account_id, &option_to_vote_for, &cycle_id);
+
+        Mocks::start_referendum_extrinsic(
+            origin.clone(),
+            winning_target_count.clone(),
+            cycle_id,
+            Ok(()),
+        );
+        Mocks::vote(
+            origin.clone(),
+            account_id,
+            commitment,
+            stake,
+            cycle_id.clone(),
+            Ok(()),
+        );
+        MockUtils::increase_block_number(voting_stage_duration);
+
+        Mocks::check_voting_finished(winning_target_count, cycle_id);
+        Mocks::reveal_vote(
+            origin.clone(),
+            account_id,
+            salt,
+            option_to_vote_for.clone(),
+            Ok(()),
+        );
+        MockUtils::increase_block_number(reveal_stage_duration);
+
+        Mocks::check_revealing_finished(
+            vec![OptionResult {
+                option_id: option_to_vote_for,
+                vote_power: stake,
+            }],
+            MockUtils::transform_results(vec![1 * stake, 0, 0]),
+        );
+    });
+}
+
+/// Test that voting power is properly accounted for the relevant options.
+#[test]
+fn finish_revealing_period_vote_power() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let voting_stage_duration = <Runtime as Trait<Instance0>>::VoteStageDuration::get();
+        let reveal_stage_duration = <Runtime as Trait<Instance0>>::RevealStageDuration::get();
+        let account_superuser = USER_ADMIN;
+        let account_id1 = USER_REGULAR;
+        let account_id2 = USER_REGULAR_POWER_VOTES;
+        let origin = OriginType::Signed(account_superuser);
+        let origin_voter1 = OriginType::Signed(account_id1);
+        let origin_voter2 = OriginType::Signed(account_id2);
+        let cycle_id = 1;
+        let winning_target_count = 1;
+
+        let option_to_vote_for1 = 0;
+        let option_to_vote_for2 = 1;
+        let stake_bigger = <Runtime as Trait<Instance0>>::MinimumStake::get() * 2;
+        let stake_smaller = <Runtime as Trait<Instance0>>::MinimumStake::get();
+        let (commitment1, salt1) =
+            MockUtils::calculate_commitment(&account_id1, &option_to_vote_for1, &cycle_id);
+        let (commitment2, salt2) =
+            MockUtils::calculate_commitment(&account_id2, &option_to_vote_for2, &cycle_id);
+
+        Mocks::start_referendum_extrinsic(
+            origin.clone(),
+            winning_target_count.clone(),
+            cycle_id,
+            Ok(()),
+        );
+        Mocks::vote(
+            origin_voter1.clone(),
+            account_id1,
+            commitment1,
+            stake_bigger,
+            cycle_id.clone(),
+            Ok(()),
+        ); // vote for first option by regular user
+        Mocks::vote(
+            origin_voter2.clone(),
+            account_id2,
+            commitment2,
+            stake_smaller,
+            cycle_id.clone(),
+            Ok(()),
+        ); // vote for second option by prominent user
+        MockUtils::increase_block_number(voting_stage_duration);
+
+        Mocks::check_voting_finished(winning_target_count, cycle_id);
+        Mocks::reveal_vote(
+            origin_voter1.clone(),
+            account_id1,
+            salt1,
+            option_to_vote_for1,
+            Ok(()),
+        );
+        Mocks::reveal_vote(
+            origin_voter2.clone(),
+            account_id2,
+            salt2,
+            option_to_vote_for2,
+            Ok(()),
+        );
+        MockUtils::increase_block_number(reveal_stage_duration);
+
+        // option 2 should win because prominent user has more powerfull vote with the same stake
+        Mocks::check_revealing_finished(
+            vec![OptionResult {
+                option_id: option_to_vote_for2,
+                vote_power: stake_smaller * POWER_VOTE_STRENGTH,
+            }],
+            MockUtils::transform_results(vec![
+                stake_bigger,
+                stake_smaller * POWER_VOTE_STRENGTH,
+                0,
+            ]),
+        );
+    });
+}
+
+/////////////////// Referendum Winners ////////////////////////////////////////////////////
+
+/// Test that winners are properly selected when no vote is cast.
+#[test]
+fn winners_no_vote_revealed() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let voting_stage_duration = <Runtime as Trait<Instance0>>::VoteStageDuration::get();
+        let reveal_stage_duration = <Runtime as Trait<Instance0>>::RevealStageDuration::get();
+        let origin = OriginType::Signed(USER_ADMIN);
+        let cycle_id = 1;
+        let winning_target_count = 1;
+
+        Mocks::start_referendum_extrinsic(origin.clone(), winning_target_count, cycle_id, Ok(()));
+        MockUtils::increase_block_number(voting_stage_duration);
+        Mocks::check_voting_finished(winning_target_count, cycle_id);
+        MockUtils::increase_block_number(reveal_stage_duration);
+        Mocks::check_revealing_finished(vec![], MockUtils::transform_results(vec![]));
+    });
+}
+
+/// Test that winners are properly selected when there are multiple winners.
+#[test]
+fn winners_multiple_winners() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let voting_stage_duration = <Runtime as Trait<Instance0>>::VoteStageDuration::get();
+        let reveal_stage_duration = <Runtime as Trait<Instance0>>::RevealStageDuration::get();
+        let account_superuser = USER_ADMIN;
+        let account_id1 = USER_REGULAR;
+        let account_id2 = USER_REGULAR_2;
+        let account_id3 = USER_REGULAR_3;
+        let origin = OriginType::Signed(account_superuser);
+        let origin_voter1 = OriginType::Signed(account_id1);
+        let origin_voter2 = OriginType::Signed(account_id2);
+        let origin_voter3 = OriginType::Signed(account_id3);
+        let cycle_id = 1;
+        let winning_target_count = 2;
+
+        let option_to_vote_for1 = 0;
+        let option_to_vote_for2 = 1;
+        let stake = <Runtime as Trait<Instance0>>::MinimumStake::get();
+        let (commitment1, salt1) =
+            MockUtils::calculate_commitment(&account_id1, &option_to_vote_for1, &cycle_id);
+        let (commitment2, salt2) =
+            MockUtils::calculate_commitment(&account_id2, &option_to_vote_for1, &cycle_id);
+        let (commitment3, salt3) =
+            MockUtils::calculate_commitment(&account_id3, &option_to_vote_for2, &cycle_id);
+
+        Mocks::start_referendum_extrinsic(
+            origin.clone(),
+            winning_target_count.clone(),
+            cycle_id,
+            Ok(()),
+        );
+        Mocks::vote(
+            origin_voter1.clone(),
+            account_id1,
+            commitment1,
+            stake,
+            cycle_id.clone(),
+            Ok(()),
+        );
+        Mocks::vote(
+            origin_voter2.clone(),
+            account_id2,
+            commitment2,
+            stake,
+            cycle_id.clone(),
+            Ok(()),
+        );
+        Mocks::vote(
+            origin_voter3.clone(),
+            account_id3,
+            commitment3,
+            stake,
+            cycle_id.clone(),
+            Ok(()),
+        );
+        MockUtils::increase_block_number(voting_stage_duration);
+
+        Mocks::check_voting_finished(winning_target_count, cycle_id);
+
+        Mocks::reveal_vote(
+            origin_voter1.clone(),
+            account_id1,
+            salt1,
+            option_to_vote_for1,
+            Ok(()),
+        );
+        Mocks::reveal_vote(
+            origin_voter2.clone(),
+            account_id2,
+            salt2,
+            option_to_vote_for1,
+            Ok(()),
+        );
+        Mocks::reveal_vote(
+            origin_voter3.clone(),
+            account_id3,
+            salt3,
+            option_to_vote_for2,
+            Ok(()),
+        );
+        MockUtils::increase_block_number(reveal_stage_duration);
+
+        Mocks::check_revealing_finished(
+            vec![
+                OptionResult {
+                    option_id: option_to_vote_for1,
+                    vote_power: 2 * stake,
+                },
+                OptionResult {
+                    option_id: option_to_vote_for2,
+                    vote_power: stake,
+                },
+            ],
+            MockUtils::transform_results(vec![2 * stake, stake, 0]),
+        );
+    });
+}
+
+/// Test that winners are properly selected when there is a important tie.
+/// N-th option and (N+1)-th option has the same amount of votes but only N winners are expected.
+#[test]
+fn winners_multiple_winners_extra() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let voting_stage_duration = <Runtime as Trait<Instance0>>::VoteStageDuration::get();
+        let reveal_stage_duration = <Runtime as Trait<Instance0>>::RevealStageDuration::get();
+        let account_superuser = USER_ADMIN;
+        let account_id1 = USER_REGULAR;
+        let account_id2 = USER_REGULAR_2;
+        let origin = OriginType::Signed(account_superuser);
+        let origin_voter1 = OriginType::Signed(account_id1);
+        let origin_voter2 = OriginType::Signed(account_id2);
+        let cycle_id = 1;
+        let winning_target_count = 1;
+
+        let option_to_vote_for1 = 0;
+        let option_to_vote_for2 = 1;
+        let stake = <Runtime as Trait<Instance0>>::MinimumStake::get();
+        let (commitment1, salt1) =
+            MockUtils::calculate_commitment(&account_id1, &option_to_vote_for1, &cycle_id);
+        let (commitment2, salt2) =
+            MockUtils::calculate_commitment(&account_id2, &option_to_vote_for2, &cycle_id);
+
+        Mocks::start_referendum_extrinsic(
+            origin.clone(),
+            winning_target_count.clone(),
+            cycle_id,
+            Ok(()),
+        );
+        Mocks::vote(
+            origin_voter1.clone(),
+            account_id1,
+            commitment1,
+            stake,
+            cycle_id.clone(),
+            Ok(()),
+        );
+        Mocks::vote(
+            origin_voter2.clone(),
+            account_id2,
+            commitment2,
+            stake,
+            cycle_id.clone(),
+            Ok(()),
+        );
+        MockUtils::increase_block_number(voting_stage_duration);
+
+        Mocks::check_voting_finished(winning_target_count, cycle_id);
+        Mocks::reveal_vote(
+            origin_voter1.clone(),
+            account_id1,
+            salt1,
+            option_to_vote_for1,
+            Ok(()),
+        );
+        Mocks::reveal_vote(
+            origin_voter2.clone(),
+            account_id2,
+            salt2,
+            option_to_vote_for2,
+            Ok(()),
+        );
+        MockUtils::increase_block_number(reveal_stage_duration);
+
+        let expected_winners = vec![OptionResult {
+            option_id: option_to_vote_for1,
+            vote_power: stake,
+        }];
+        assert!((expected_winners.len() as u64) == winning_target_count); // sanity check - check that there will be expected number of winners
+        Mocks::check_revealing_finished(
+            expected_winners,
+            MockUtils::transform_results(vec![stake, stake]),
+        );
+    });
+}
+
+// Test that winners are properly selected when there only votes for fewer options than expected winners.
+#[test]
+fn winners_multiple_not_enough() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let voting_stage_duration = <Runtime as Trait<Instance0>>::VoteStageDuration::get();
+        let reveal_stage_duration = <Runtime as Trait<Instance0>>::RevealStageDuration::get();
+        let account_superuser = USER_ADMIN;
+        let account_id1 = USER_REGULAR;
+        let origin = OriginType::Signed(account_superuser);
+        let origin_voter1 = OriginType::Signed(account_id1);
+        let cycle_id = 1;
+        let winning_target_count = 3;
+
+        let option_to_vote_for = 0;
+        let stake = <Runtime as Trait<Instance0>>::MinimumStake::get();
+        let (commitment1, salt1) =
+            MockUtils::calculate_commitment(&account_id1, &option_to_vote_for, &cycle_id);
+
+        Mocks::start_referendum_extrinsic(
+            origin.clone(),
+            winning_target_count.clone(),
+            cycle_id,
+            Ok(()),
+        );
+        Mocks::vote(
+            origin_voter1.clone(),
+            account_id1,
+            commitment1,
+            stake,
+            cycle_id.clone(),
+            Ok(()),
+        );
+        MockUtils::increase_block_number(voting_stage_duration);
+
+        Mocks::check_voting_finished(winning_target_count, cycle_id);
+        Mocks::reveal_vote(
+            origin_voter1.clone(),
+            account_id1,
+            salt1,
+            option_to_vote_for,
+            Ok(()),
+        );
+        MockUtils::increase_block_number(reveal_stage_duration);
+
+        let expected_winners = vec![OptionResult {
+            option_id: option_to_vote_for,
+            vote_power: stake,
+        }];
+        assert!((expected_winners.len() as u64) < winning_target_count); // sanity check - check that there will be less winners than expected
+        Mocks::check_revealing_finished(
+            expected_winners,
+            MockUtils::transform_results(vec![stake]),
+        );
+    });
+}
+
+/////////////////// Lifetime Releasing stake ///////////////////////////////////
+
+/// Test that referendum stake can be released after the referendum ends.
+#[test]
+fn referendum_release_stake() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let voting_stage_duration = <Runtime as Trait<Instance0>>::VoteStageDuration::get();
+        let reveal_stage_duration = <Runtime as Trait<Instance0>>::RevealStageDuration::get();
+        let account_id = USER_ADMIN;
+        let origin = OriginType::Signed(account_id);
+        let cycle_id1 = 1;
+        let cycle_id2 = 2;
+        let winning_target_count = 1;
+
+        let option_to_vote_for = 0;
+        let stake = <Runtime as Trait<Instance0>>::MinimumStake::get();
+        let (commitment, salt) =
+            MockUtils::calculate_commitment(&account_id, &option_to_vote_for, &cycle_id1);
+
+        Mocks::start_referendum_extrinsic(
+            origin.clone(),
+            winning_target_count.clone(),
+            cycle_id1,
+            Ok(()),
+        );
+        Mocks::vote(
+            origin.clone(),
+            account_id,
+            commitment,
+            stake.clone(),
+            cycle_id1.clone(),
+            Ok(()),
+        );
+        MockUtils::increase_block_number(voting_stage_duration);
+
+        Mocks::check_voting_finished(winning_target_count, cycle_id1);
+        Mocks::reveal_vote(
+            origin.clone(),
+            account_id,
+            salt,
+            option_to_vote_for.clone(),
+            Ok(()),
+        );
+        MockUtils::increase_block_number(reveal_stage_duration);
+
+        Mocks::check_revealing_finished(
+            vec![OptionResult {
+                option_id: option_to_vote_for,
+                vote_power: stake,
+            }],
+            MockUtils::transform_results(vec![stake, 0, 0]),
+        );
+
+        Runtime::feature_stack_lock(false);
+        Mocks::release_stake(origin.clone(), account_id, Err(Error::UnstakingForbidden));
+        Runtime::feature_stack_lock(true);
+
+        // since `account_id` voted for the winner, he can unlock stake only after inactive stage ends
+        Mocks::start_referendum_extrinsic(
+            origin.clone(),
+            winning_target_count.clone(),
+            cycle_id2,
+            Ok(()),
+        );
+
+        Mocks::release_stake(origin.clone(), account_id, Ok(()));
+    });
+}
+
+/////////////////// ReferendumManager //////////////////////////////////////////
+
+/// Test that other runtime modules can start the referendum.
+#[test]
+fn referendum_manager_referendum_start() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let winning_target_count = 1;
+        let cycle_id = 1;
+
+        Mocks::start_referendum_manager(winning_target_count, cycle_id, Ok(()));
+    });
+}

+ 2 - 2
runtime-modules/service-discovery/Cargo.toml

@@ -22,7 +22,7 @@ balances = { package = 'pallet-balances', default-features = false, git = 'https
 minting = { package = 'pallet-token-mint', default-features = false, path = '../token-minting'}
 recurringrewards = { package = 'pallet-recurring-reward', default-features = false, path = '../recurring-reward'}
 common = { package = 'pallet-common', default-features = false, path = '../common'}
-staking-handler = { package = 'staking-handler', default-features = false, path = '../staking-handler'}
+staking-handler = { package = 'pallet-staking-handler', default-features = false, path = '../staking-handler'}
 membership = { package = 'pallet-membership', default-features = false, path = '../membership'}
 
 [features]
@@ -36,4 +36,4 @@ std = [
 	'frame-system/std',
 	'sp-runtime/std',
 	'working-group/std',
-]
+]

+ 12 - 1
runtime-modules/service-discovery/src/mock.rs

@@ -2,6 +2,7 @@
 
 pub use crate::*;
 
+use frame_support::traits::LockIdentifier;
 use frame_support::weights::Weight;
 use frame_support::{impl_outer_event, impl_outer_origin, parameter_types};
 use sp_core::H256;
@@ -10,6 +11,7 @@ use sp_runtime::{
     traits::{BlakeTwo256, IdentityLookup},
     DispatchResult, Perbill,
 };
+use staking_handler::{LockComparator, StakingManager};
 
 mod working_group_mod {
     pub use super::StorageWorkingGroupInstance;
@@ -144,7 +146,7 @@ pub struct WorkingGroupWeightInfo;
 impl working_group::Trait<StorageWorkingGroupInstance> for Test {
     type Event = MetaEvent;
     type MaxWorkerNumberLimit = MaxWorkerNumberLimit;
-    type StakingHandler = staking_handler::StakingManager<Self, LockId1>;
+    type StakingHandler = StakingManager<Self, LockId1>;
     type StakingAccountValidator = membership::Module<Test>;
     type MemberOriginValidator = ();
     type MinUnstakingPeriodLimit = ();
@@ -239,6 +241,15 @@ impl pallet_timestamp::Trait for Test {
     type WeightInfo = ();
 }
 
+impl LockComparator<<Test as balances::Trait>::Balance> for Test {
+    fn are_locks_conflicting(
+        _new_lock: &LockIdentifier,
+        _existing_locks: &[LockIdentifier],
+    ) -> bool {
+        false
+    }
+}
+
 pub fn initial_test_ext() -> sp_io::TestExternalities {
     let t = frame_system::GenesisConfig::default()
         .build_storage::<Test>()

+ 1 - 1
runtime-modules/staking-handler/Cargo.toml

@@ -1,5 +1,5 @@
 [package]
-name = 'staking-handler'
+name = 'pallet-staking-handler'
 version = '1.0.0'
 authors = ['Joystream contributors']
 edition = '2018'

+ 64 - 32
runtime-modules/staking-handler/src/lib.rs

@@ -10,49 +10,56 @@ use frame_support::dispatch::{DispatchError, DispatchResult};
 use frame_support::traits::{Currency, Get, LockIdentifier, LockableCurrency, WithdrawReasons};
 use sp_arithmetic::traits::Zero;
 use sp_std::marker::PhantomData;
+use sp_std::vec::Vec;
 
 #[cfg(test)]
 mod mock;
 #[cfg(test)]
 mod test;
 
-/// Balance alias for `balances` module.
-pub type BalanceOf<T> = <T as pallet_balances::Trait>::Balance;
+/// Trait for (dis)allowing certain stake locks combinations.
+pub trait LockComparator<Balance> {
+    /// Checks if stake lock that is about to be used is conflicting with existing locks.
+    fn are_locks_conflicting(new_lock: &LockIdentifier, existing_locks: &[LockIdentifier]) -> bool;
+}
 
 /// Defines abstract staking handler to manage user stakes for different activities
 /// like adding a proposal. Implementation should use built-in LockableCurrency
 /// and LockIdentifier to lock balance consistently with pallet_staking.
-pub trait StakingHandler<T: frame_system::Trait + common::Trait + pallet_balances::Trait> {
+pub trait StakingHandler<AccountId, Balance, MemberId> {
     /// Locks the specified balance on the account using specific lock identifier.
-    fn lock(account_id: &T::AccountId, amount: BalanceOf<T>);
+    fn lock(account_id: &AccountId, amount: Balance);
 
     /// Removes the specified lock on the account.
-    fn unlock(account_id: &T::AccountId);
+    fn unlock(account_id: &AccountId);
 
     /// Slash the specified balance on the account using specific lock identifier.
     /// No limits, no actions on zero stake.
     /// If slashing balance greater than the existing stake - stake is slashed to zero.
     /// Returns actually slashed balance.
-    fn slash(account_id: &T::AccountId, amount: Option<BalanceOf<T>>) -> BalanceOf<T>;
+    fn slash(account_id: &AccountId, amount: Option<Balance>) -> Balance;
 
     /// Sets the new stake to a given amount.
-    fn set_stake(account_id: &T::AccountId, new_stake: BalanceOf<T>) -> DispatchResult;
+    fn set_stake(account_id: &AccountId, new_stake: Balance) -> DispatchResult;
 
     /// Verifies that there no conflicting stakes on the staking account.
-    fn is_account_free_of_conflicting_stakes(account_id: &T::AccountId) -> bool;
+    fn is_account_free_of_conflicting_stakes(account_id: &AccountId) -> bool;
 
     /// Verifies that staking account balance is sufficient for staking.
     /// During the balance check we should consider already locked stake. Effective balance to check
     /// is 'already locked funds' + 'usable funds'.
-    fn is_enough_balance_for_stake(account_id: &T::AccountId, amount: BalanceOf<T>) -> bool;
+    fn is_enough_balance_for_stake(account_id: &AccountId, amount: Balance) -> bool;
 
     /// Returns the current stake on the account.
-    fn current_stake(account_id: &T::AccountId) -> BalanceOf<T>;
+    fn current_stake(account_id: &AccountId) -> Balance;
 }
 
 /// Implementation of the StakingHandler.
 pub struct StakingManager<
-    T: frame_system::Trait + common::Trait + pallet_balances::Trait,
+    T: frame_system::Trait
+        + pallet_balances::Trait
+        + common::Trait
+        + LockComparator<<T as pallet_balances::Trait>::Balance>,
     LockId: Get<LockIdentifier>,
 > {
     trait_marker: PhantomData<T>,
@@ -60,11 +67,22 @@ pub struct StakingManager<
 }
 
 impl<
-        T: frame_system::Trait + common::Trait + pallet_balances::Trait,
+        T: frame_system::Trait
+            + pallet_balances::Trait
+            + common::Trait
+            + LockComparator<<T as pallet_balances::Trait>::Balance>,
         LockId: Get<LockIdentifier>,
-    > StakingHandler<T> for StakingManager<T, LockId>
+    >
+    StakingHandler<
+        <T as frame_system::Trait>::AccountId,
+        <T as pallet_balances::Trait>::Balance,
+        <T as common::Trait>::MemberId,
+    > for StakingManager<T, LockId>
 {
-    fn lock(account_id: &T::AccountId, amount: BalanceOf<T>) {
+    fn lock(
+        account_id: &<T as frame_system::Trait>::AccountId,
+        amount: <T as pallet_balances::Trait>::Balance,
+    ) {
         <pallet_balances::Module<T>>::set_lock(
             LockId::get(),
             &account_id,
@@ -73,12 +91,15 @@ impl<
         )
     }
 
-    fn unlock(account_id: &T::AccountId) {
+    fn unlock(account_id: &<T as frame_system::Trait>::AccountId) {
         <pallet_balances::Module<T>>::remove_lock(LockId::get(), &account_id);
     }
 
-    fn slash(account_id: &T::AccountId, amount: Option<BalanceOf<T>>) -> BalanceOf<T> {
-        let locks = <pallet_balances::Module<T>>::locks(&account_id);
+    fn slash(
+        account_id: &<T as frame_system::Trait>::AccountId,
+        amount: Option<<T as pallet_balances::Trait>::Balance>,
+    ) -> <T as pallet_balances::Trait>::Balance {
+        let locks = pallet_balances::Module::<T>::locks(&account_id);
 
         let existing_lock = locks.iter().find(|lock| lock.id == LockId::get());
 
@@ -96,7 +117,7 @@ impl<
                 }
             }
 
-            let _ = <pallet_balances::Module<T>>::slash(&account_id, slashable_amount);
+            let _ = pallet_balances::Module::<T>::slash(&account_id, slashable_amount);
 
             actually_slashed_balance = slashable_amount
         }
@@ -104,19 +125,25 @@ impl<
         actually_slashed_balance
     }
 
-    fn set_stake(account_id: &T::AccountId, new_stake: BalanceOf<T>) -> DispatchResult {
+    fn set_stake(
+        account_id: &<T as frame_system::Trait>::AccountId,
+        new_stake: <T as pallet_balances::Trait>::Balance,
+    ) -> DispatchResult {
         let current_stake = Self::current_stake(account_id);
 
-        //Unlock previous stake if its not zero.
-        if current_stake > Zero::zero() {
-            Self::unlock(account_id);
-        }
-
-        if !Self::is_enough_balance_for_stake(account_id, new_stake) {
-            //Restore previous stake if its not zero.
+        // setting zero stake?
+        if new_stake == Zero::zero() {
+            // unlock stake if any
             if current_stake > Zero::zero() {
-                Self::lock(account_id, current_stake);
+                Self::unlock(account_id);
             }
+
+            return Ok(());
+        }
+
+        let usable_balance = <pallet_balances::Module<T>>::usable_balance(account_id);
+
+        if new_stake > current_stake + usable_balance {
             return Err(DispatchError::Other("Not enough balance for a new stake."));
         }
 
@@ -127,17 +154,22 @@ impl<
 
     fn is_account_free_of_conflicting_stakes(account_id: &T::AccountId) -> bool {
         let locks = <pallet_balances::Module<T>>::locks(&account_id);
+        let lock_ids: Vec<LockIdentifier> =
+            locks.iter().map(|balance_lock| balance_lock.id).collect();
 
-        let existing_lock = locks.iter().find(|lock| lock.id == LockId::get());
-
-        existing_lock.is_none()
+        !T::are_locks_conflicting(&LockId::get(), lock_ids.as_slice())
     }
 
-    fn is_enough_balance_for_stake(account_id: &T::AccountId, amount: BalanceOf<T>) -> bool {
+    fn is_enough_balance_for_stake(
+        account_id: &<T as frame_system::Trait>::AccountId,
+        amount: <T as pallet_balances::Trait>::Balance,
+    ) -> bool {
         <pallet_balances::Module<T>>::usable_balance(account_id) >= amount
     }
 
-    fn current_stake(account_id: &T::AccountId) -> BalanceOf<T> {
+    fn current_stake(
+        account_id: &<T as frame_system::Trait>::AccountId,
+    ) -> <T as pallet_balances::Trait>::Balance {
         let locks = <pallet_balances::Module<T>>::locks(&account_id);
 
         let existing_lock = locks.iter().find(|lock| lock.id == LockId::get());

+ 12 - 0
runtime-modules/staking-handler/src/mock.rs

@@ -1,3 +1,5 @@
+use crate::LockComparator;
+use frame_support::traits::LockIdentifier;
 use frame_support::{impl_outer_origin, parameter_types};
 use frame_system;
 use sp_core::H256;
@@ -69,6 +71,16 @@ impl common::Trait for Test {
     type ActorId = u64;
 }
 
+impl LockComparator<<Test as pallet_balances::Trait>::Balance> for Test {
+    fn are_locks_conflicting(new_lock: &LockIdentifier, existing_locks: &[LockIdentifier]) -> bool {
+        // simple check preventing lock reuse
+        existing_locks
+            .iter()
+            .find(|lock| *lock == new_lock)
+            .is_some()
+    }
+}
+
 impl pallet_timestamp::Trait for Test {
     type Moment = u64;
     type OnTimestampSet = ();

+ 1 - 1
runtime-modules/storage/Cargo.toml

@@ -22,7 +22,7 @@ sp-core = { package = 'sp-core', default-features = false, git = 'https://github
 balances = { package = 'pallet-balances', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
 minting = { package = 'pallet-token-mint', default-features = false, path = '../token-minting'}
 recurringrewards = { package = 'pallet-recurring-reward', default-features = false, path = '../recurring-reward'}
-staking-handler = { package = 'staking-handler', default-features = false, path = '../staking-handler'}
+staking-handler = { package = 'pallet-staking-handler', default-features = false, path = '../staking-handler'}
 membership = { package = 'pallet-membership', default-features = false, path = '../membership'}
 
 [features]

+ 10 - 0
runtime-modules/storage/src/tests/mock.rs

@@ -10,6 +10,7 @@ use sp_runtime::{
     traits::{BlakeTwo256, IdentityLookup},
     Perbill,
 };
+use staking_handler::LockComparator;
 
 use crate::data_directory::ContentIdExists;
 use crate::data_object_type_registry::IsActiveDataObjectType;
@@ -313,6 +314,15 @@ impl recurringrewards::Trait for Test {
     type RewardRelationshipId = u64;
 }
 
+impl LockComparator<<Test as balances::Trait>::Balance> for Test {
+    fn are_locks_conflicting(
+        _new_lock: &LockIdentifier,
+        _existing_locks: &[LockIdentifier],
+    ) -> bool {
+        false
+    }
+}
+
 pub struct ExtBuilder {
     first_data_object_type_id: u64,
     first_content_id: u64,

+ 1 - 1
runtime-modules/working-group/Cargo.toml

@@ -14,7 +14,7 @@ sp-arithmetic = { package = 'sp-arithmetic', default-features = false, git = 'ht
 sp-std = { package = 'sp-std', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
 common = { package = 'pallet-common', default-features = false, path = '../common'}
 balances = { package = 'pallet-balances', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
-staking-handler = { package = 'staking-handler', default-features = false, path = '../staking-handler'}
+staking-handler = { package = 'pallet-staking-handler', default-features = false, path = '../staking-handler'}
 
 # Benchmarking
 frame-benchmarking = { package = 'frame-benchmarking', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca', optional = true}

+ 1 - 1
runtime-modules/working-group/src/lib.rs

@@ -102,7 +102,7 @@ pub trait Trait<I: Instance = DefaultInstance>:
     type MaxWorkerNumberLimit: Get<u32>;
 
     /// Stakes and balance locks handler.
-    type StakingHandler: StakingHandler<Self>;
+    type StakingHandler: StakingHandler<Self::AccountId, BalanceOf<Self>, MemberId<Self>>;
 
     /// Validates staking account ownership for a member.
     type StakingAccountValidator: common::StakingAccountValidator<Self>;

+ 11 - 0
runtime-modules/working-group/src/tests/mock.rs

@@ -1,3 +1,4 @@
+use frame_support::traits::LockIdentifier;
 use frame_support::traits::{OnFinalize, OnInitialize};
 use frame_support::weights::Weight;
 use frame_support::{impl_outer_event, impl_outer_origin, parameter_types};
@@ -8,6 +9,7 @@ use sp_runtime::{
     traits::{BlakeTwo256, IdentityLookup},
     Perbill,
 };
+use staking_handler::LockComparator;
 
 use crate::{DefaultInstance, Module, Trait};
 
@@ -104,6 +106,15 @@ impl membership::Trait for Test {
     type DefaultInitialInvitationBalance = ();
 }
 
+impl LockComparator<<Test as balances::Trait>::Balance> for Test {
+    fn are_locks_conflicting(
+        _new_lock: &LockIdentifier,
+        _existing_locks: &[LockIdentifier],
+    ) -> bool {
+        false
+    }
+}
+
 pub type Balances = balances::Module<Test>;
 pub type System = frame_system::Module<Test>;
 

+ 5 - 3
runtime/Cargo.toml

@@ -66,7 +66,8 @@ common = { package = 'pallet-common', default-features = false, path = '../runti
 memo = { package = 'pallet-memo', default-features = false, path = '../runtime-modules/memo'}
 forum = { package = 'pallet-forum', default-features = false, path = '../runtime-modules/forum'}
 membership = { package = 'pallet-membership', default-features = false, path = '../runtime-modules/membership'}
-governance = { package = 'pallet-governance', default-features = false, path = '../runtime-modules/governance'}
+referendum = { package = 'pallet-referendum', default-features = false, path = '../runtime-modules/referendum'}
+council = { package = 'pallet-council', default-features = false, path = '../runtime-modules/council'}
 minting = { package = 'pallet-token-mint', default-features = false, path = '../runtime-modules/token-minting'}
 recurring-rewards = { package = 'pallet-recurring-reward', default-features = false, path = '../runtime-modules/recurring-reward'}
 working-group = { package = 'pallet-working-group', default-features = false, path = '../runtime-modules/working-group'}
@@ -77,7 +78,7 @@ proposals-discussion = { package = 'pallet-proposals-discussion', default-featur
 proposals-codex = { package = 'pallet-proposals-codex', default-features = false, path = '../runtime-modules/proposals/codex'}
 content-directory = { package = 'pallet-content-directory', default-features = false, path = '../runtime-modules/content-directory' }
 pallet_constitution = { package = 'pallet-constitution', default-features = false, path = '../runtime-modules/constitution' }
-staking-handler = { package = 'staking-handler', default-features = false, path = '../runtime-modules/staking-handler'}
+staking-handler = { package = 'pallet-staking-handler', default-features = false, path = '../runtime-modules/staking-handler'}
 
 [dev-dependencies]
 sp-io = { package = 'sp-io', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
@@ -140,7 +141,8 @@ std = [
     'memo/std',
     'forum/std',
     'membership/std',
-    'governance/std',
+    'council/std',
+    'referendum/std',
     'minting/std',
     'recurring-rewards/std',
     'working-group/std',

+ 101 - 0
runtime/src/constants.rs

@@ -1,4 +1,7 @@
 use crate::{BlockNumber, Moment};
+use frame_support::parameter_types;
+use frame_support::traits::LockIdentifier;
+use sp_std::collections::btree_set::BTreeSet;
 
 /// Constants for Babe.
 
@@ -35,6 +38,104 @@ pub const DAYS: BlockNumber = HOURS * 24;
 // 1 in 4 blocks (on average, not counting collisions) will be primary babe blocks.
 pub const PRIMARY_PROBABILITY: (u64, u64) = (1, 4);
 
+parameter_types! {
+    pub const VotingLockId: LockIdentifier = [0; 8];
+    pub const CandidacyLockId: LockIdentifier = [1; 8];
+    pub const CouncilorLockId: LockIdentifier = [2; 8];
+    pub const ProposalsLockId: LockIdentifier = [5; 8];
+    pub const StorageWorkingGroupLockId: LockIdentifier = [6; 8];
+    pub const ContentWorkingGroupLockId: LockIdentifier = [7; 8];
+    pub const ForumGroupLockId: LockIdentifier = [8; 8];
+    pub const MembershipWorkingGroupLockId: LockIdentifier = [9; 8];
+}
+
+lazy_static! {
+    // pairs of allowed lock combinations
+    pub static ref ALLOWED_LOCK_COMBINATIONS: BTreeSet<(LockIdentifier, LockIdentifier)> = [
+        // format: `(lock_id, [all_compatible_lock_ids, ...])`
+        (ForumGroupLockId::get(), [
+            ContentWorkingGroupLockId::get(),
+            StorageWorkingGroupLockId::get(),
+            ProposalsLockId::get(),
+            CandidacyLockId::get(),
+            CouncilorLockId::get(),
+            VotingLockId::get(),
+            MembershipWorkingGroupLockId::get(),
+        ]),
+        (ContentWorkingGroupLockId::get(), [
+            ForumGroupLockId::get(),
+            StorageWorkingGroupLockId::get(),
+            ProposalsLockId::get(),
+            CandidacyLockId::get(),
+            CouncilorLockId::get(),
+            VotingLockId::get(),
+            MembershipWorkingGroupLockId::get(),
+        ]),
+        (StorageWorkingGroupLockId::get(), [
+            ForumGroupLockId::get(),
+            ContentWorkingGroupLockId::get(),
+            ProposalsLockId::get(),
+            CandidacyLockId::get(),
+            CouncilorLockId::get(),
+            VotingLockId::get(),
+            MembershipWorkingGroupLockId::get(),
+        ]),
+        (ProposalsLockId::get(), [
+            ForumGroupLockId::get(),
+            ContentWorkingGroupLockId::get(),
+            StorageWorkingGroupLockId::get(),
+            CandidacyLockId::get(),
+            CouncilorLockId::get(),
+            VotingLockId::get(),
+            MembershipWorkingGroupLockId::get(),
+        ]),
+        (CandidacyLockId::get(), [
+            ForumGroupLockId::get(),
+            ContentWorkingGroupLockId::get(),
+            StorageWorkingGroupLockId::get(),
+            ProposalsLockId::get(),
+            CouncilorLockId::get(),
+            VotingLockId::get(),
+            MembershipWorkingGroupLockId::get(),
+        ]),
+        (CouncilorLockId::get(), [
+            ForumGroupLockId::get(),
+            ContentWorkingGroupLockId::get(),
+            StorageWorkingGroupLockId::get(),
+            ProposalsLockId::get(),
+            CandidacyLockId::get(),
+            VotingLockId::get(),
+            MembershipWorkingGroupLockId::get(),
+        ]),
+        (VotingLockId::get(), [
+            ForumGroupLockId::get(),
+            ContentWorkingGroupLockId::get(),
+            StorageWorkingGroupLockId::get(),
+            ProposalsLockId::get(),
+            CandidacyLockId::get(),
+            CouncilorLockId::get(),
+            MembershipWorkingGroupLockId::get(),
+        ]),
+        (MembershipWorkingGroupLockId::get(), [
+            ForumGroupLockId::get(),
+            ContentWorkingGroupLockId::get(),
+            StorageWorkingGroupLockId::get(),
+            ProposalsLockId::get(),
+            CandidacyLockId::get(),
+            CouncilorLockId::get(),
+            MembershipWorkingGroupLockId::get(),
+        ]),
+    ]
+    .iter()
+    .fold(BTreeSet::new(), |mut acc, item| {
+        for lock_id in &item.1 {
+            acc.insert((item.0, *lock_id));
+        }
+
+        acc
+    });
+}
+
 /// Tests only
 #[cfg(any(feature = "std", test))]
 pub mod currency {

+ 0 - 15
runtime/src/integration/proposals/council_elected_handler.rs

@@ -1,15 +0,0 @@
-#![warn(missing_docs)]
-
-use crate::Runtime;
-use governance::election::CouncilElected;
-
-/// 'Council elected' event handler. Should be applied to the 'election' substrate module.
-/// CouncilEvent is handled by resetting active proposals.
-pub struct CouncilElectedHandler;
-
-impl<Elected, Term> CouncilElected<Elected, Term> for CouncilElectedHandler {
-    fn council_elected(_new_council: Elected, _term: Term) {
-        <proposals_engine::Module<Runtime>>::reject_active_proposals();
-        <proposals_engine::Module<Runtime>>::reactivate_pending_constitutionality_proposals();
-    }
-}

+ 29 - 37
runtime/src/integration/proposals/council_origin_validator.rs

@@ -1,5 +1,6 @@
 #![warn(missing_docs)]
 
+use sp_runtime::SaturatedConversion;
 use sp_std::marker::PhantomData;
 
 use common::origin::ActorOriginValidator;
@@ -14,7 +15,7 @@ pub struct CouncilManager<T> {
     marker: PhantomData<T>,
 }
 
-impl<T: governance::council::Trait + membership::Trait>
+impl<T: council::Trait + membership::Trait>
     ActorOriginValidator<
         <T as frame_system::Trait>::Origin,
         MemberId<T>,
@@ -29,18 +30,23 @@ impl<T: governance::council::Trait + membership::Trait>
     ) -> Result<<T as frame_system::Trait>::AccountId, &'static str> {
         let account_id = <MembershipOriginValidator<T>>::ensure_actor_origin(origin, actor_id)?;
 
-        if <governance::council::Module<T>>::is_councilor(&account_id) {
-            return Ok(account_id);
+        if council::Module::<T>::council_members()
+            .iter()
+            .any(|council_member| council_member.member_id() == &actor_id)
+        {
+            Ok(account_id)
+        } else {
+            Err("Council validation failed: account id doesn't belong to a council member")
         }
-
-        Err("Council validation failed: account id doesn't belong to a council member")
     }
 }
 
-impl<T: governance::council::Trait> VotersParameters for CouncilManager<T> {
+impl<T: council::Trait> VotersParameters for CouncilManager<T> {
     /// Implement total_voters_count() as council size
     fn total_voters_count() -> u32 {
-        <governance::council::Module<T>>::active_council().len() as u32
+        council::Module::<T>::council_members()
+            .len()
+            .saturated_into()
     }
 }
 
@@ -53,10 +59,9 @@ mod tests {
     use proposals_engine::VotersParameters;
     use sp_runtime::AccountId32;
 
+    use crate::tests::elect_council;
     use crate::tests::{initial_test_ext, insert_member};
 
-    type Council = governance::council::Module<Runtime>;
-
     #[test]
     fn council_origin_validator_fails_with_unregistered_member() {
         initial_test_ext().execute_with(|| {
@@ -75,24 +80,20 @@ mod tests {
     fn council_origin_validator_succeeds() {
         initial_test_ext().execute_with(|| {
             let councilor1 = AccountId32::default();
-            let councilor2: [u8; 32] = [2; 32];
-            let councilor3: [u8; 32] = [3; 32];
+            let councilor2: [u8; 32] = [1; 32];
+            let councilor3: [u8; 32] = [2; 32];
 
-            assert!(Council::set_council(
-                frame_system::RawOrigin::Root.into(),
-                vec![councilor1, councilor2.into(), councilor3.into()]
-            )
-            .is_ok());
+            elect_council(
+                vec![councilor1.clone(), councilor2.into(), councilor3.into()],
+                0,
+            );
 
-            let account_id = AccountId32::default();
-            let origin = RawOrigin::Signed(account_id.clone());
-            insert_member(account_id.clone());
-            let member_id = 0; // newly created member_id
+            let origin = RawOrigin::Signed(councilor1.clone());
 
             let validation_result =
-                CouncilManager::<Runtime>::ensure_actor_origin(origin.into(), member_id);
+                CouncilManager::<Runtime>::ensure_actor_origin(origin.into(), 0);
 
-            assert_eq!(validation_result, Ok(account_id));
+            assert_eq!(validation_result, Ok(councilor1));
         });
     }
 
@@ -134,22 +135,13 @@ mod tests {
     #[test]
     fn council_size_calculation_aka_total_voters_count_succeeds() {
         initial_test_ext().execute_with(|| {
+            // Max council size is 3
             let councilor1 = AccountId32::default();
-            let councilor2: [u8; 32] = [2; 32];
-            let councilor3: [u8; 32] = [3; 32];
-            let councilor4: [u8; 32] = [4; 32];
-            assert!(Council::set_council(
-                frame_system::RawOrigin::Root.into(),
-                vec![
-                    councilor1,
-                    councilor2.into(),
-                    councilor3.into(),
-                    councilor4.into()
-                ]
-            )
-            .is_ok());
-
-            assert_eq!(CouncilManager::<Runtime>::total_voters_count(), 4)
+            let councilor2: [u8; 32] = [1; 32];
+            let councilor3: [u8; 32] = [2; 32];
+            elect_council(vec![councilor1, councilor2.into(), councilor3.into()], 0);
+
+            assert_eq!(CouncilManager::<Runtime>::total_voters_count(), 3)
         });
     }
 }

+ 0 - 2
runtime/src/integration/proposals/mod.rs

@@ -1,11 +1,9 @@
 #![warn(missing_docs)]
 
-mod council_elected_handler;
 mod council_origin_validator;
 mod membership_origin_validator;
 mod proposal_encoder;
 
-pub use council_elected_handler::CouncilElectedHandler;
 pub use council_origin_validator::CouncilManager;
 pub use membership_origin_validator::MembershipOriginValidator;
 pub use proposal_encoder::ExtrinsicProposalEncoder;

+ 4 - 2
runtime/src/integration/proposals/proposal_encoder.rs

@@ -36,8 +36,10 @@ impl ProposalEncoder<Runtime> for ExtrinsicProposalEncoder {
             ProposalDetails::Text(text) => {
                 Call::ProposalsCodex(proposals_codex::Call::execute_text_proposal(text))
             }
-            ProposalDetails::Spending(balance, destination) => Call::Council(
-                governance::council::Call::spend_from_council_mint(balance, destination),
+            ProposalDetails::Spending(_balance, _destination) => Call::Council(
+                // TODO: This is an stub since this has been modified in
+                // the proposal branch
+                council::Call::set_budget(0),
             ),
             ProposalDetails::SetValidatorCount(new_validator_count) => Call::Staking(
                 pallet_staking::Call::set_validator_count(new_validator_count),

+ 129 - 13
runtime/src/lib.rs

@@ -32,7 +32,7 @@ use frame_support::weights::{
     Weight,
 };
 use frame_support::{construct_runtime, parameter_types};
-use frame_system::EnsureRoot;
+use frame_system::{EnsureOneOf, EnsureRoot, EnsureSigned};
 use pallet_grandpa::{AuthorityId as GrandpaId, AuthorityList as GrandpaAuthorityList};
 use pallet_im_online::sr25519::AuthorityId as ImOnlineId;
 use pallet_session::historical as pallet_session_historical;
@@ -54,7 +54,9 @@ pub use runtime_api::*;
 
 use integration::proposals::{CouncilManager, ExtrinsicProposalEncoder, MembershipOriginValidator};
 
-use governance::{council, election};
+use council::ReferendumConnection;
+use referendum::{Balance as BalanceReferendum, CastVote, OptionResult};
+use staking_handler::{LockComparator, StakingManager};
 use storage::data_object_storage_registry;
 
 // Node dependencies
@@ -63,13 +65,14 @@ pub use content_directory;
 pub use content_directory::{
     HashedTextMaxLength, InputValidationLengthConstraint, MaxNumber, TextMaxLength, VecMaxLength,
 };
+pub use council;
 pub use forum;
-pub use governance::election_params::ElectionParameters;
 pub use membership;
 #[cfg(any(feature = "std", test))]
 pub use pallet_balances::Call as BalancesCall;
 pub use pallet_staking::StakerStatus;
 pub use proposals_engine::ProposalParameters;
+pub use referendum;
 pub use storage::{data_directory, data_object_type_registry};
 pub use working_group;
 
@@ -464,14 +467,124 @@ impl common::currency::GovernanceCurrency for Runtime {
     type Currency = pallet_balances::Module<Self>;
 }
 
-impl governance::election::Trait for Runtime {
+// The referendum instance alias.
+pub type ReferendumInstance = referendum::Instance1;
+pub type ReferendumModule = referendum::Module<Runtime, ReferendumInstance>;
+pub type CouncilModule = council::Module<Runtime>;
+
+parameter_types! {
+    // referendum parameters
+    pub const MaxSaltLength: u64 = 32;
+    pub const VoteStageDuration: BlockNumber = 5;
+    pub const RevealStageDuration: BlockNumber = 7;
+    pub const MinimumVotingStake: u64 = 10000;
+
+    // council parameteres
+    pub const MinNumberOfExtraCandidates: u64 = 1;
+    pub const AnnouncingPeriodDuration: BlockNumber = 15;
+    pub const IdlePeriodDuration: BlockNumber = 27;
+    pub const CouncilSize: u64 = 3;
+    pub const MinCandidateStake: u64 = 11000;
+    pub const ElectedMemberRewardPerBlock: u64 = 100;
+    pub const ElectedMemberRewardPeriod: BlockNumber = 10;
+    pub const BudgetRefillAmount: u64 = 1000;
+    pub const BudgetRefillPeriod: BlockNumber = 1000;
+}
+
+impl referendum::Trait<ReferendumInstance> for Runtime {
     type Event = Event;
-    type CouncilElected = (Council, integration::proposals::CouncilElectedHandler);
+
+    type MaxSaltLength = MaxSaltLength;
+
+    type Currency = pallet_balances::Module<Self>;
+    type LockId = VotingLockId;
+
+    type ManagerOrigin =
+        EnsureOneOf<Self::AccountId, EnsureSigned<Self::AccountId>, EnsureRoot<Self::AccountId>>;
+
+    type VotePower = BalanceReferendum<Self, ReferendumInstance>;
+
+    type VoteStageDuration = VoteStageDuration;
+    type RevealStageDuration = RevealStageDuration;
+
+    type MinimumStake = MinimumVotingStake;
+
+    fn calculate_vote_power(
+        _account_id: &<Self as frame_system::Trait>::AccountId,
+        stake: &BalanceReferendum<Self, ReferendumInstance>,
+    ) -> Self::VotePower {
+        *stake
+    }
+
+    fn can_unlock_vote_stake(
+        vote: &CastVote<Self::Hash, BalanceReferendum<Self, ReferendumInstance>, Self::MemberId>,
+    ) -> bool {
+        <CouncilModule as ReferendumConnection<Runtime>>::can_unlock_vote_stake(vote).is_ok()
+    }
+
+    fn process_results(winners: &[OptionResult<Self::MemberId, Self::VotePower>]) {
+        let tmp_winners: Vec<OptionResult<Self::MemberId, Self::VotePower>> = winners
+            .iter()
+            .map(|item| OptionResult {
+                option_id: item.option_id,
+                vote_power: item.vote_power,
+            })
+            .collect();
+        <CouncilModule as ReferendumConnection<Runtime>>::recieve_referendum_results(
+            tmp_winners.as_slice(),
+        );
+    }
+
+    fn is_valid_option_id(option_index: &u64) -> bool {
+        <CouncilModule as ReferendumConnection<Runtime>>::is_valid_candidate_id(option_index)
+    }
+
+    fn get_option_power(option_id: &u64) -> Self::VotePower {
+        <CouncilModule as ReferendumConnection<Runtime>>::get_option_power(option_id)
+    }
+
+    fn increase_option_power(option_id: &u64, amount: &Self::VotePower) {
+        <CouncilModule as ReferendumConnection<Runtime>>::increase_option_power(option_id, amount);
+    }
 }
 
-impl governance::council::Trait for Runtime {
+impl council::Trait for Runtime {
     type Event = Event;
-    type CouncilTermEnded = (CouncilElection,);
+
+    type Referendum = ReferendumModule;
+
+    type MinNumberOfExtraCandidates = MinNumberOfExtraCandidates;
+    type CouncilSize = CouncilSize;
+    type AnnouncingPeriodDuration = AnnouncingPeriodDuration;
+    type IdlePeriodDuration = IdlePeriodDuration;
+    type MinCandidateStake = MinCandidateStake;
+
+    type CandidacyLock = StakingManager<Self, CandidacyLockId>;
+    type CouncilorLock = StakingManager<Self, CouncilorLockId>;
+
+    type StakingAccountValidator = Members;
+
+    type ElectedMemberRewardPerBlock = ElectedMemberRewardPerBlock;
+    type ElectedMemberRewardPeriod = ElectedMemberRewardPeriod;
+
+    type BudgetRefillAmount = BudgetRefillAmount;
+    type BudgetRefillPeriod = BudgetRefillPeriod;
+
+    fn is_council_member_account(
+        membership_id: &Self::MemberId,
+        account_id: &<Self as frame_system::Trait>::AccountId,
+    ) -> bool {
+        membership::Module::<Runtime>::ensure_is_controller_account_for_member(
+            membership_id,
+            account_id,
+        )
+        .is_ok()
+    }
+
+    fn new_council_elected(_elected_members: &[council::CouncilMemberOf<Self>]) {
+        <proposals_engine::Module<Runtime>>::reject_active_proposals();
+        <proposals_engine::Module<Runtime>>::reactivate_pending_constitutionality_proposals();
+    }
 }
 
 impl memo::Trait for Runtime {
@@ -594,6 +707,14 @@ impl forum::Trait for Runtime {
     }
 }
 
+impl LockComparator<<Runtime as pallet_balances::Trait>::Balance> for Runtime {
+    fn are_locks_conflicting(new_lock: &LockIdentifier, existing_locks: &[LockIdentifier]) -> bool {
+        existing_locks
+            .iter()
+            .any(|lock| !ALLOWED_LOCK_COMBINATIONS.contains(&(*new_lock, *lock)))
+    }
+}
+
 // The forum working group instance alias.
 pub type ForumWorkingGroupInstance = working_group::Instance1;
 
@@ -613,10 +734,6 @@ parameter_types! {
     pub const StorageWorkingGroupRewardPeriod: u32 = 14400 + 20;
     pub const ContentWorkingGroupRewardPeriod: u32 = 14400 + 30;
     pub const MembershipRewardPeriod: u32 = 14400 + 40;
-    pub const StorageWorkingGroupLockId: LockIdentifier = [6; 8];
-    pub const ContentWorkingGroupLockId: LockIdentifier = [7; 8];
-    pub const ForumGroupLockId: LockIdentifier = [8; 8];
-    pub const MembershipWorkingGroupLockId: LockIdentifier = [9; 8];
 }
 
 // Staking managers type aliases.
@@ -683,7 +800,6 @@ parameter_types! {
     pub const ProposalTitleMaxLength: u32 = 40;
     pub const ProposalDescriptionMaxLength: u32 = 3000;
     pub const ProposalMaxActiveProposalLimit: u32 = 5;
-    pub const ProposalsLockId: LockIdentifier = [5; 8];
 }
 
 impl proposals_engine::Trait for Runtime {
@@ -808,8 +924,8 @@ construct_runtime!(
         RandomnessCollectiveFlip: pallet_randomness_collective_flip::{Module, Call, Storage},
         Sudo: pallet_sudo::{Module, Call, Config<T>, Storage, Event<T>},
         // Joystream
-        CouncilElection: election::{Module, Call, Storage, Event<T>, Config<T>},
         Council: council::{Module, Call, Storage, Event<T>, Config<T>},
+        Referendum: referendum::<Instance1>::{Module, Call, Storage, Event<T>, Config<T>},
         Memo: memo::{Module, Call, Storage, Event<T>},
         Members: membership::{Module, Call, Storage, Event<T>, Config<T>},
         Forum: forum::{Module, Call, Storage, Event<T>, Config<T>},

+ 151 - 3
runtime/src/tests/mod.rs

@@ -6,12 +6,17 @@
 mod proposals_integration;
 mod storage_integration;
 
-use crate::Runtime;
-use frame_support::traits::Currency;
+use crate::{BlockNumber, ReferendumInstance, Runtime};
+use frame_support::traits::{Currency, OnFinalize, OnInitialize};
 use frame_system::RawOrigin;
+use referendum::ReferendumManager;
 use sp_runtime::{AccountId32, BuildStorage};
 
 type Membership = membership::Module<Runtime>;
+type System = frame_system::Module<Runtime>;
+type ProposalsEngine = proposals_engine::Module<Runtime>;
+type Council = council::Module<Runtime>;
+type Referendum = referendum::Module<Runtime, ReferendumInstance>;
 
 pub(crate) fn initial_test_ext() -> sp_io::TestExternalities {
     let mut t = frame_system::GenesisConfig::default()
@@ -19,7 +24,7 @@ pub(crate) fn initial_test_ext() -> sp_io::TestExternalities {
         .unwrap();
 
     // build the council config to initialize the mint
-    let council_config = governance::council::GenesisConfig::<crate::Runtime>::default()
+    let council_config = council::GenesisConfig::<crate::Runtime>::default()
         .build_storage()
         .unwrap();
 
@@ -28,6 +33,118 @@ pub(crate) fn initial_test_ext() -> sp_io::TestExternalities {
     t.into()
 }
 
+fn get_account_membership(account: AccountId32, i: usize) -> u64 {
+    if !Membership::is_member_account(&account) {
+        insert_member(account.clone());
+        set_staking_account(account, i as u64);
+    }
+
+    i as u64
+}
+
+pub(crate) fn elect_council(council: Vec<AccountId32>, cycle_id: u64) {
+    let mut voters = Vec::<AccountId32>::new();
+
+    let councilor_stake: u128 = <Runtime as council::Trait>::MinCandidateStake::get().into();
+    let extra_candidates = <Runtime as council::Trait>::MinNumberOfExtraCandidates::get() + 1;
+    let mut council_member_ids = Vec::new();
+
+    for (i, councilor) in council.iter().enumerate() {
+        increase_total_balance_issuance_using_account_id(
+            councilor.clone().into(),
+            councilor_stake + 1,
+        );
+
+        let member_id = get_account_membership(councilor.clone(), i);
+
+        Council::announce_candidacy(
+            RawOrigin::Signed(councilor.clone()).into(),
+            member_id,
+            councilor.clone(),
+            councilor.clone(),
+            councilor_stake,
+        )
+        .unwrap();
+        voters.push([council.len() as u8 + extra_candidates as u8 + i as u8; 32].into());
+        council_member_ids.push(member_id);
+    }
+
+    for i in council.len()..(council.len() + extra_candidates as usize) {
+        let extra_councilor: AccountId32 = [i as u8; 32].into();
+
+        let member_id = get_account_membership(extra_councilor.clone(), i);
+        Council::release_candidacy_stake(
+            RawOrigin::Signed(extra_councilor.clone()).into(),
+            member_id,
+        )
+        .unwrap_or_else(|err| assert_eq!(err, council::Error::NoStake));
+        increase_total_balance_issuance_using_account_id(
+            extra_councilor.clone().into(),
+            councilor_stake + 1,
+        );
+
+        Council::announce_candidacy(
+            RawOrigin::Signed(extra_councilor.clone()).into(),
+            member_id,
+            extra_councilor.clone(),
+            extra_councilor.clone(),
+            councilor_stake,
+        )
+        .unwrap();
+    }
+
+    let current_block = System::block_number();
+    run_to_block(current_block + <Runtime as council::Trait>::AnnouncingPeriodDuration::get());
+
+    let voter_stake: u128 =
+        <Runtime as referendum::Trait<ReferendumInstance>>::MinimumStake::get().into();
+    for (i, voter) in voters.iter().enumerate() {
+        increase_total_balance_issuance_using_account_id(voter.clone().into(), voter_stake + 1);
+        let commitment = Referendum::calculate_commitment(
+            voter.into(),
+            &[0u8],
+            &cycle_id,
+            &council_member_ids[i],
+        ); //TODO: fixme
+        Referendum::vote(
+            RawOrigin::Signed(voter.clone()).into(),
+            commitment,
+            voter_stake,
+        )
+        .unwrap();
+    }
+
+    let current_block = System::block_number();
+    run_to_block(
+        current_block
+            + <Runtime as referendum::Trait<ReferendumInstance>>::VoteStageDuration::get(),
+    );
+
+    for (i, voter) in voters.iter().enumerate() {
+        Referendum::reveal_vote(
+            RawOrigin::Signed(voter.clone()).into(),
+            vec![0u8],
+            council_member_ids[i].clone(),
+        )
+        .unwrap();
+    }
+
+    let current_block = System::block_number();
+    run_to_block(
+        current_block
+            + <Runtime as referendum::Trait<ReferendumInstance>>::RevealStageDuration::get(),
+    );
+
+    let council_members = council::Module::<Runtime>::council_members();
+    assert_eq!(
+        council_members
+            .iter()
+            .map(|m| *m.member_id())
+            .collect::<Vec<_>>(),
+        council_member_ids
+    );
+}
+
 pub(crate) fn insert_member(account_id: AccountId32) {
     increase_total_balance_issuance_using_account_id(
         account_id.clone(),
@@ -48,6 +165,37 @@ pub(crate) fn insert_member(account_id: AccountId32) {
     Membership::buy_membership(RawOrigin::Signed(account_id.clone()).into(), params).unwrap();
 }
 
+pub(crate) fn set_staking_account(account_id: AccountId32, member_id: u64) {
+    membership::Module::<Runtime>::add_staking_account_candidate(
+        RawOrigin::Signed(account_id.clone()).into(),
+        member_id,
+    )
+    .unwrap();
+
+    membership::Module::<Runtime>::confirm_staking_account(
+        RawOrigin::Signed(account_id.clone()).into(),
+        member_id,
+        account_id.clone(),
+    )
+    .unwrap();
+}
+
+// Recommendation from Parity on testing on_finalize
+// https://substrate.dev/docs/en/next/development/module/tests
+pub(crate) fn run_to_block(n: BlockNumber) {
+    while System::block_number() < n {
+        <System as OnFinalize<BlockNumber>>::on_finalize(System::block_number());
+        <Council as OnFinalize<BlockNumber>>::on_finalize(System::block_number());
+        <Referendum as OnFinalize<BlockNumber>>::on_finalize(System::block_number());
+        <ProposalsEngine as OnFinalize<BlockNumber>>::on_finalize(System::block_number());
+        System::set_block_number(System::block_number() + 1);
+        <System as OnInitialize<BlockNumber>>::on_initialize(System::block_number());
+        <Council as OnInitialize<BlockNumber>>::on_initialize(System::block_number());
+        <Referendum as OnInitialize<BlockNumber>>::on_initialize(System::block_number());
+        <ProposalsEngine as OnInitialize<BlockNumber>>::on_initialize(System::block_number());
+    }
+}
+
 pub(crate) fn increase_total_balance_issuance_using_account_id(
     account_id: AccountId32,
     balance: u128,

+ 65 - 116
runtime/src/tests/proposals_integration/mod.rs

@@ -4,73 +4,52 @@
 
 mod working_group_proposals;
 
-use crate::{BlockNumber, MemberId, ProposalCancellationFee, Runtime};
+use crate::tests::run_to_block;
+use crate::{ProposalCancellationFee, Runtime};
 use codec::Encode;
-use governance::election_params::ElectionParameters;
 use proposals_codex::{GeneralProposalParameters, ProposalDetails};
 use proposals_engine::{
-    ApprovedProposalDecision, BalanceOf, Proposal, ProposalCreationParameters, ProposalParameters,
+    ApprovedProposalDecision, Proposal, ProposalCreationParameters, ProposalParameters,
     ProposalStatus, VoteKind, VotersParameters, VotingResults,
 };
 
 use frame_support::dispatch::{DispatchError, DispatchResult};
-use frame_support::traits::{Currency, OnFinalize, OnInitialize};
+use frame_support::traits::Currency;
 use frame_support::{StorageMap, StorageValue};
 use frame_system::RawOrigin;
 use sp_runtime::AccountId32;
 
-use super::{increase_total_balance_issuance_using_account_id, initial_test_ext, insert_member};
+use super::{
+    increase_total_balance_issuance_using_account_id, initial_test_ext, insert_member,
+    set_staking_account,
+};
 
+use crate::tests::elect_council;
 use crate::CouncilManager;
 
 pub type Balances = pallet_balances::Module<Runtime>;
 pub type System = frame_system::Module<Runtime>;
 pub type ProposalsEngine = proposals_engine::Module<Runtime>;
-pub type Council = governance::council::Module<Runtime>;
-pub type Election = governance::election::Module<Runtime>;
 pub type ProposalCodex = proposals_codex::Module<Runtime>;
 
 fn setup_members(count: u8) {
     for i in 0..count {
         let account_id: [u8; 32] = [i; 32];
         let account_id_converted: AccountId32 = account_id.clone().into();
-        insert_member(account_id_converted);
+        insert_member(account_id_converted.clone());
+        set_staking_account(account_id_converted, i as u64);
     }
 }
 
-fn setup_council() {
+// Max Council size is 3
+fn setup_council(cycle_id: u64) {
     let councilor0 = AccountId32::default();
     let councilor1: [u8; 32] = [1; 32];
     let councilor2: [u8; 32] = [2; 32];
-    let councilor3: [u8; 32] = [3; 32];
-    let councilor4: [u8; 32] = [4; 32];
-    let councilor5: [u8; 32] = [5; 32];
-    assert!(Council::set_council(
-        frame_system::RawOrigin::Root.into(),
-        vec![
-            councilor0,
-            councilor1.into(),
-            councilor2.into(),
-            councilor3.into(),
-            councilor4.into(),
-            councilor5.into()
-        ]
-    )
-    .is_ok());
-}
-
-// Recommendation from Parity on testing on_finalize
-// https://substrate.dev/docs/en/next/development/module/tests
-fn run_to_block(n: BlockNumber) {
-    while System::block_number() < n {
-        <System as OnFinalize<BlockNumber>>::on_finalize(System::block_number());
-        <Election as OnFinalize<BlockNumber>>::on_finalize(System::block_number());
-        <ProposalsEngine as OnFinalize<BlockNumber>>::on_finalize(System::block_number());
-        System::set_block_number(System::block_number() + 1);
-        <System as OnInitialize<BlockNumber>>::on_initialize(System::block_number());
-        <Election as OnInitialize<BlockNumber>>::on_initialize(System::block_number());
-        <ProposalsEngine as OnInitialize<BlockNumber>>::on_initialize(System::block_number());
-    }
+    elect_council(
+        vec![councilor0, councilor1.into(), councilor2.into()],
+        cycle_id,
+    );
 }
 
 struct VoteGenerator {
@@ -100,6 +79,14 @@ impl VoteGenerator {
     }
 
     fn vote(&mut self, vote_kind: VoteKind) -> DispatchResult {
+        let vote_result = ProposalsEngine::vote(
+            frame_system::RawOrigin::Signed(self.current_account_id.clone()).into(),
+            self.current_voter_id,
+            self.proposal_id,
+            vote_kind,
+            Vec::new(),
+        );
+
         if self.auto_increment_voter_id {
             self.current_account_id_seed += 1;
             self.current_voter_id += 1;
@@ -107,13 +94,7 @@ impl VoteGenerator {
             self.current_account_id = account_id.into();
         }
 
-        ProposalsEngine::vote(
-            frame_system::RawOrigin::Signed(self.current_account_id.clone()).into(),
-            self.current_voter_id,
-            self.proposal_id,
-            vote_kind,
-            Vec::new(),
-        )
+        vote_result
     }
 }
 
@@ -324,7 +305,7 @@ fn proposal_cancellation_with_slashes_with_balance_checks_succeeds() {
 fn proposal_reset_succeeds() {
     initial_test_ext().execute_with(|| {
         setup_members(4);
-        setup_council();
+        setup_council(0);
         // create proposal
         let dummy_proposal = DummyProposalFixture::default().with_voting_period(100);
         let proposal_id = dummy_proposal.create_proposal_and_assert(Ok(1)).unwrap();
@@ -348,19 +329,27 @@ fn proposal_reset_succeeds() {
         );
 
         // Ensure council was elected
-        assert_eq!(CouncilManager::<Runtime>::total_voters_count(), 6);
+        assert_eq!(CouncilManager::<Runtime>::total_voters_count(), 3);
 
-        let voted_member_id = 2;
         // Check for votes.
         assert_eq!(
-            ProposalsEngine::vote_by_proposal_by_voter(proposal_id, voted_member_id),
+            ProposalsEngine::vote_by_proposal_by_voter(proposal_id, 0),
+            VoteKind::Reject
+        );
+        assert_eq!(
+            ProposalsEngine::vote_by_proposal_by_voter(proposal_id, 1),
             VoteKind::Abstain
         );
+        assert_eq!(
+            ProposalsEngine::vote_by_proposal_by_voter(proposal_id, 2),
+            VoteKind::Slash
+        );
 
         // Check proposals CouncilElected hook just trigger the election hook (empty council).
         //<Runtime as governance::election::Trait>::CouncilElected::council_elected(Vec::new(), 10);
 
-        elect_single_councilor();
+        end_idle_period();
+        setup_council(1);
 
         let updated_proposal = ProposalsEngine::proposals(proposal_id);
 
@@ -376,41 +365,21 @@ fn proposal_reset_succeeds() {
 
         // No votes could survive cleaning: should be default value.
         assert_eq!(
-            ProposalsEngine::vote_by_proposal_by_voter(proposal_id, voted_member_id),
+            ProposalsEngine::vote_by_proposal_by_voter(proposal_id, 2),
             VoteKind::default()
         );
 
-        // Check council CouncilElected hook. It should set current council. And we elected single councilor.
-        assert_eq!(CouncilManager::<Runtime>::total_voters_count(), 1);
+        // Check council CouncilElected hook. It should set current council.
+        assert_eq!(CouncilManager::<Runtime>::total_voters_count(), 3);
     });
 }
 
-fn elect_single_councilor() {
-    let res = Election::set_election_parameters(
-        RawOrigin::Root.into(),
-        ElectionParameters {
-            announcing_period: 1,
-            voting_period: 1,
-            revealing_period: 1,
-            council_size: 1,
-            candidacy_limit: 10,
-            new_term_duration: 2000000,
-            min_council_stake: 0,
-            min_voting_stake: 0,
-        },
-    );
-    assert_eq!(res, Ok(()));
-
-    let res = Election::force_start_election(RawOrigin::Root.into());
-    assert_eq!(res, Ok(()));
-
-    let councilor1: [u8; 32] = [1; 32];
-    increase_total_balance_issuance_using_account_id(councilor1.clone().into(), 1200000000);
-
-    let res = Election::apply(RawOrigin::Signed(councilor1.into()).into(), 0);
-    assert_eq!(res, Ok(()));
-
-    run_to_block(5);
+// Ends council idle period
+// Preconditions: currently in idle period, idle period started in currnet block
+fn end_idle_period() {
+    let current_block = System::block_number();
+    let idle_period_duration = <Runtime as council::Trait>::IdlePeriodDuration::get();
+    run_to_block(current_block + idle_period_duration);
 }
 
 struct CodexProposalTestFixture<SuccessfulCall>
@@ -421,7 +390,6 @@ where
     member_id: u64,
     setup_environment: bool,
     proposal_id: u32,
-    run_to_block: u32,
 }
 
 impl<SuccessfulCall> CodexProposalTestFixture<SuccessfulCall>
@@ -434,7 +402,6 @@ where
             member_id: 1,
             setup_environment: true,
             proposal_id: 1,
-            run_to_block: 2,
         }
     }
 
@@ -461,13 +428,6 @@ where
             ..self
         }
     }
-
-    fn with_run_to_block(self, run_to_block: u32) -> Self {
-        Self {
-            run_to_block,
-            ..self
-        }
-    }
 }
 
 impl<SuccessfulCall> CodexProposalTestFixture<SuccessfulCall>
@@ -479,39 +439,23 @@ where
 
         if self.setup_environment {
             setup_members(15);
-            setup_council();
+            setup_council(0);
 
             increase_total_balance_issuance_using_account_id(account_id.clone().into(), 1_500_000);
         }
 
         assert_eq!((self.successful_call)(), Ok(()));
 
+        // Council size is 3
         let mut vote_generator = VoteGenerator::new(self.proposal_id);
         vote_generator.vote_and_assert_ok(VoteKind::Approve);
         vote_generator.vote_and_assert_ok(VoteKind::Approve);
         vote_generator.vote_and_assert_ok(VoteKind::Approve);
-        vote_generator.vote_and_assert_ok(VoteKind::Approve);
-        vote_generator.vote_and_assert_ok(VoteKind::Approve);
 
-        run_to_block(self.run_to_block);
+        run_to_block(System::block_number() + 1);
     }
 }
 
-pub fn add_confirmed_staking_account(member_id: MemberId, account_id: AccountId32) {
-    assert!(crate::Members::add_staking_account_candidate(
-        RawOrigin::Signed(account_id.clone()).into(),
-        member_id,
-    )
-    .is_ok());
-
-    assert!(crate::Members::confirm_staking_account(
-        RawOrigin::Signed(account_id.clone()).into(),
-        member_id,
-        account_id,
-    )
-    .is_ok());
-}
-
 #[test]
 fn text_proposal_execution_succeeds() {
     initial_test_ext().execute_with(|| {
@@ -539,16 +483,18 @@ fn text_proposal_execution_succeeds() {
     });
 }
 
+/* TODO: Test will be commented out until Spending proposal is changed with the new
+ * FundingRequest
 #[test]
 fn spending_proposal_execution_succeeds() {
     initial_test_ext().execute_with(|| {
         let member_id = 10;
         let account_id: [u8; 32] = [member_id; 32];
-        let new_balance = <BalanceOf<Runtime>>::from(5555u32);
+        let new_balance = pallet_council::Balance::<Runtime>::from(5555u32);
 
         let target_account_id: [u8; 32] = [12; 32];
 
-        assert!(Council::set_council_mint_capacity(RawOrigin::Root.into(), new_balance).is_ok());
+        assert!(Council::set_budget(RawOrigin::Root.into(), new_balance).is_ok());
 
         let codex_extrinsic_test_fixture = CodexProposalTestFixture::default_for_call(|| {
             let general_proposal_parameters = GeneralProposalParameters::<Runtime> {
@@ -577,6 +523,7 @@ fn spending_proposal_execution_succeeds() {
         assert_eq!(Balances::free_balance(converted_account_id), new_balance);
     });
 }
+*/
 
 #[test]
 fn set_validator_count_proposal_execution_succeeds() {
@@ -640,9 +587,10 @@ fn amend_constitution_proposal_execution_succeeds() {
 #[test]
 fn proposal_reactivation_succeeds() {
     initial_test_ext().execute_with(|| {
-        let starting_block = 0;
         setup_members(5);
-        setup_council();
+        setup_council(0);
+
+        let starting_block = System::block_number();
         // create proposal
         let dummy_proposal = DummyProposalFixture::default()
             .with_voting_period(100)
@@ -650,13 +598,13 @@ fn proposal_reactivation_succeeds() {
         let proposal_id = dummy_proposal.create_proposal_and_assert(Ok(1)).unwrap();
 
         // create some votes
+        // Council size is 3
         let mut vote_generator = VoteGenerator::new(proposal_id);
         vote_generator.vote_and_assert_ok(VoteKind::Approve);
         vote_generator.vote_and_assert_ok(VoteKind::Approve);
         vote_generator.vote_and_assert_ok(VoteKind::Approve);
-        vote_generator.vote_and_assert_ok(VoteKind::Approve);
 
-        run_to_block(2);
+        run_to_block(starting_block + 2);
 
         // check
         let proposal = ProposalsEngine::proposals(proposal_id);
@@ -669,9 +617,10 @@ fn proposal_reactivation_succeeds() {
         );
 
         // Ensure council was elected
-        assert_eq!(CouncilManager::<Runtime>::total_voters_count(), 6);
+        assert_eq!(CouncilManager::<Runtime>::total_voters_count(), 3);
 
-        elect_single_councilor();
+        end_idle_period();
+        setup_council(1);
 
         run_to_block(10);
 
@@ -680,6 +629,6 @@ fn proposal_reactivation_succeeds() {
         assert_eq!(updated_proposal.status, ProposalStatus::Active);
 
         // Check council CouncilElected hook. It should set current council. And we elected single councilor.
-        assert_eq!(CouncilManager::<Runtime>::total_voters_count(), 1);
+        assert_eq!(CouncilManager::<Runtime>::total_voters_count(), 3);
     });
 }

+ 80 - 39
runtime/src/tests/proposals_integration/working_group_proposals.rs

@@ -10,6 +10,7 @@ use strum::IntoEnumIterator;
 use working_group::{Penalty, StakeParameters};
 
 use crate::primitives::{ActorId, MemberId};
+use crate::tests::run_to_block;
 use crate::{
     Balance, BlockNumber, ContentDirectoryWorkingGroup, ContentDirectoryWorkingGroupInstance,
     ContentDirectoryWorkingGroupStakingManager, ForumWorkingGroup, ForumWorkingGroupInstance,
@@ -28,7 +29,6 @@ fn add_opening(
     working_group: WorkingGroup,
 ) -> u64 {
     let expected_proposal_id = sequence_number;
-    let run_to_block = sequence_number * 2;
 
     let opening_id = match working_group {
         WorkingGroup::Content => {
@@ -85,11 +85,28 @@ fn add_opening(
             }),
         )
     })
-    .with_expected_proposal_id(expected_proposal_id)
-    .with_run_to_block(run_to_block);
+    .with_expected_proposal_id(expected_proposal_id);
 
     codex_extrinsic_test_fixture.call_extrinsic_and_assert();
 
+    match working_group {
+        WorkingGroup::Content => assert!(working_group::OpeningById::<
+            Runtime,
+            ContentDirectoryWorkingGroupInstance,
+        >::contains_key(opening_id)),
+        WorkingGroup::Storage => assert!(working_group::OpeningById::<
+            Runtime,
+            StorageWorkingGroupInstance,
+        >::contains_key(opening_id)),
+        WorkingGroup::Forum => assert!(working_group::OpeningById::<
+            Runtime,
+            ForumWorkingGroupInstance,
+        >::contains_key(opening_id)),
+        WorkingGroup::Membership => assert!(working_group::OpeningById::<
+            Runtime,
+            MembershipWorkingGroupInstance,
+        >::contains_key(opening_id)),
+    }
     opening_id
 }
 
@@ -102,7 +119,6 @@ fn fill_opening(
     working_group: WorkingGroup,
 ) {
     let expected_proposal_id = sequence_number;
-    let run_to_block = sequence_number * 2;
 
     let codex_extrinsic_test_fixture = CodexProposalTestFixture::default_for_call(|| {
         let general_proposal_parameters = GeneralProposalParameters::<Runtime> {
@@ -126,8 +142,7 @@ fn fill_opening(
         )
     })
     .disable_setup_enviroment()
-    .with_expected_proposal_id(expected_proposal_id)
-    .with_run_to_block(run_to_block);
+    .with_expected_proposal_id(expected_proposal_id);
 
     codex_extrinsic_test_fixture.call_extrinsic_and_assert();
 }
@@ -141,7 +156,6 @@ fn decrease_stake(
     working_group: WorkingGroup,
 ) {
     let expected_proposal_id = sequence_number;
-    let run_to_block = sequence_number * 2;
 
     let codex_extrinsic_test_fixture = CodexProposalTestFixture::default_for_call(|| {
         let general_proposal_parameters = GeneralProposalParameters::<Runtime> {
@@ -163,8 +177,7 @@ fn decrease_stake(
         )
     })
     .disable_setup_enviroment()
-    .with_expected_proposal_id(expected_proposal_id)
-    .with_run_to_block(run_to_block);
+    .with_expected_proposal_id(expected_proposal_id);
 
     codex_extrinsic_test_fixture.call_extrinsic_and_assert();
 }
@@ -178,7 +191,6 @@ fn slash_stake(
     working_group: WorkingGroup,
 ) {
     let expected_proposal_id = sequence_number;
-    let run_to_block = sequence_number * 2;
 
     let codex_extrinsic_test_fixture = CodexProposalTestFixture::default_for_call(|| {
         let general_proposal_parameters = GeneralProposalParameters::<Runtime> {
@@ -203,8 +215,7 @@ fn slash_stake(
         )
     })
     .disable_setup_enviroment()
-    .with_expected_proposal_id(expected_proposal_id)
-    .with_run_to_block(run_to_block);
+    .with_expected_proposal_id(expected_proposal_id);
 
     codex_extrinsic_test_fixture.call_extrinsic_and_assert();
 }
@@ -218,7 +229,6 @@ fn set_reward(
     working_group: WorkingGroup,
 ) {
     let expected_proposal_id = sequence_number;
-    let run_to_block = sequence_number * 2;
 
     let codex_extrinsic_test_fixture = CodexProposalTestFixture::default_for_call(|| {
         let general_proposal_parameters = GeneralProposalParameters::<Runtime> {
@@ -240,8 +250,7 @@ fn set_reward(
         )
     })
     .disable_setup_enviroment()
-    .with_expected_proposal_id(expected_proposal_id)
-    .with_run_to_block(run_to_block);
+    .with_expected_proposal_id(expected_proposal_id);
 
     codex_extrinsic_test_fixture.call_extrinsic_and_assert();
 }
@@ -258,7 +267,6 @@ fn set_mint_capacity<
     working_group: WorkingGroup,
 ) {
     let expected_proposal_id = sequence_number;
-    let run_to_block = sequence_number * 2;
 
     let codex_extrinsic_test_fixture = CodexProposalTestFixture::default_for_call(|| {
         let general_proposal_parameters = GeneralProposalParameters::<Runtime> {
@@ -276,8 +284,7 @@ fn set_mint_capacity<
         )
     })
     .with_setup_enviroment(setup_environment)
-    .with_expected_proposal_id(expected_proposal_id)
-    .with_run_to_block(run_to_block);
+    .with_expected_proposal_id(expected_proposal_id);
 
     codex_extrinsic_test_fixture.call_extrinsic_and_assert();
 }
@@ -291,7 +298,6 @@ fn terminate_role(
     working_group: WorkingGroup,
 ) {
     let expected_proposal_id = sequence_number;
-    let run_to_block = sequence_number * 2;
 
     let codex_extrinsic_test_fixture = CodexProposalTestFixture::default_for_call(|| {
         let general_proposal_parameters = GeneralProposalParameters::<Runtime> {
@@ -315,8 +321,7 @@ fn terminate_role(
         )
     })
     .disable_setup_enviroment()
-    .with_expected_proposal_id(expected_proposal_id)
-    .with_run_to_block(run_to_block);
+    .with_expected_proposal_id(expected_proposal_id);
 
     codex_extrinsic_test_fixture.call_extrinsic_and_assert();
 }
@@ -507,7 +512,11 @@ fn create_decrease_group_leader_stake_proposal_execution_succeeds() {
 fn run_create_decrease_group_leader_stake_proposal_execution_succeeds<
     T: working_group::Trait<I> + frame_system::Trait + common::Trait + pallet_balances::Trait,
     I: frame_support::traits::Instance,
-    SM: staking_handler::StakingHandler<T>,
+    SM: staking_handler::StakingHandler<
+        <T as frame_system::Trait>::AccountId,
+        <T as pallet_balances::Trait>::Balance,
+        <T as common::Trait>::MemberId,
+    >,
 >(
     working_group: WorkingGroup,
 ) where
@@ -517,10 +526,14 @@ fn run_create_decrease_group_leader_stake_proposal_execution_succeeds<
     <T as pallet_balances::Trait>::Balance: From<u128>,
 {
     initial_test_ext().execute_with(|| {
-        let member_id: MemberId = 1;
+        // Don't use the same member id as a councilor, can lead to conflicting stakes
+        let member_id: MemberId = 14;
+
         let account_id: [u8; 32] = [member_id as u8; 32];
         let stake_amount: Balance = 100;
 
+        increase_total_balance_issuance_using_account_id(account_id.clone().into(), 1_500_000);
+
         let stake_policy = Some(working_group::StakePolicy {
             stake_amount,
             leaving_unstaking_period: 45000, // more than min value
@@ -535,7 +548,7 @@ fn run_create_decrease_group_leader_stake_proposal_execution_succeeds<
 
         let opening_id = add_opening(member_id, account_id, stake_policy, 1, working_group);
 
-        add_confirmed_staking_account(member_id, account_id.into());
+        let old_balance = Balances::usable_balance(&account_id.into());
 
         let apply_result = WorkingGroupInstance::<T, I>::apply_on_opening(
             RawOrigin::Signed(account_id.into()).into(),
@@ -565,12 +578,22 @@ fn run_create_decrease_group_leader_stake_proposal_execution_succeeds<
             working_group,
         );
 
+        let new_balance = Balances::usable_balance(&account_id.into());
+        let stake: working_group::BalanceOf<T> = SM::current_stake(&account_id.into()).into();
+
         let leader_worker_id = WorkingGroupInstance::<T, I>::current_lead().unwrap();
 
-        let old_balance = Balances::usable_balance(&account_id.into());
-        let old_stake = SM::current_stake(&account_id.into());
+        assert_eq!(
+            WorkingGroupInstance::<T, I>::worker_by_id(leader_worker_id)
+                .staking_account_id
+                .unwrap(),
+            account_id.into()
+        );
 
-        assert_eq!(old_stake, stake_amount.into());
+        assert_eq!(stake, working_group::BalanceOf::<T>::from(stake_amount));
+        assert_eq!(new_balance, old_balance - stake_amount);
+
+        let old_balance = new_balance;
 
         let decreasing_stake_amount = 30;
         decrease_stake(
@@ -634,7 +657,11 @@ fn create_slash_group_leader_stake_proposal_execution_succeeds() {
 fn run_create_slash_group_leader_stake_proposal_execution_succeeds<
     T: working_group::Trait<I> + frame_system::Trait,
     I: frame_support::traits::Instance,
-    SM: staking_handler::StakingHandler<T>,
+    SM: staking_handler::StakingHandler<
+        <T as frame_system::Trait>::AccountId,
+        <T as pallet_balances::Trait>::Balance,
+        <T as common::Trait>::MemberId,
+    >,
 >(
     working_group: WorkingGroup,
 ) where
@@ -644,7 +671,9 @@ fn run_create_slash_group_leader_stake_proposal_execution_succeeds<
     <T as pallet_balances::Trait>::Balance: From<u128>,
 {
     initial_test_ext().execute_with(|| {
-        let member_id: MemberId = 1;
+        // Don't use the same member id as a councilor, can lead to conflicting stakes
+        let member_id: MemberId = 14;
+
         let account_id: [u8; 32] = [member_id as u8; 32];
         let stake_amount: Balance = 100;
 
@@ -653,6 +682,8 @@ fn run_create_slash_group_leader_stake_proposal_execution_succeeds<
             leaving_unstaking_period: 45000, // more than min value
         });
 
+        increase_total_balance_issuance_using_account_id(account_id.clone().into(), 1_500_000);
+
         let stake_parameters = Some(
             StakeParameters::<T::AccountId, working_group::BalanceOf<T>> {
                 stake: stake_amount.into(),
@@ -662,8 +693,6 @@ fn run_create_slash_group_leader_stake_proposal_execution_succeeds<
 
         let opening_id = add_opening(member_id, account_id, stake_policy, 1, working_group);
 
-        add_confirmed_staking_account(member_id, account_id.into());
-
         let apply_result = WorkingGroupInstance::<T, I>::apply_on_opening(
             RawOrigin::Signed(account_id.into()).into(),
             working_group::ApplyOnOpeningParameters::<T> {
@@ -924,7 +953,11 @@ fn create_terminate_group_leader_role_proposal_execution_succeeds() {
 fn run_create_terminate_group_leader_role_proposal_execution_succeeds<
     T: working_group::Trait<I> + frame_system::Trait + minting::Trait,
     I: frame_support::traits::Instance,
-    SM: staking_handler::StakingHandler<T>,
+    SM: staking_handler::StakingHandler<
+        <T as frame_system::Trait>::AccountId,
+        <T as pallet_balances::Trait>::Balance,
+        <T as common::Trait>::MemberId,
+    >,
 >(
     working_group: WorkingGroup,
 ) where
@@ -936,7 +969,9 @@ fn run_create_terminate_group_leader_role_proposal_execution_succeeds<
     <T as pallet_balances::Trait>::Balance: From<u128>,
 {
     initial_test_ext().execute_with(|| {
-        let member_id: MemberId = 1;
+        // Don't use the same member id as a councilor, can lead to conflicting stakes
+        let member_id: MemberId = 14;
+
         let account_id: [u8; 32] = [member_id as u8; 32];
         let stake_amount = 100_u128;
 
@@ -945,6 +980,8 @@ fn run_create_terminate_group_leader_role_proposal_execution_succeeds<
             leaving_unstaking_period: 45000, // more than min value
         });
 
+        increase_total_balance_issuance_using_account_id(account_id.clone().into(), 1_500_000);
+
         let stake_parameters = Some(
             StakeParameters::<T::AccountId, working_group::BalanceOf<T>> {
                 stake: stake_amount.into(),
@@ -954,8 +991,6 @@ fn run_create_terminate_group_leader_role_proposal_execution_succeeds<
 
         let opening_id = add_opening(member_id, account_id, stake_policy, 1, working_group);
 
-        add_confirmed_staking_account(member_id, account_id.into());
-
         let apply_result = WorkingGroupInstance::<T, I>::apply_on_opening(
             RawOrigin::Signed(account_id.into()).into(),
             working_group::ApplyOnOpeningParameters::<T> {
@@ -1052,7 +1087,11 @@ fn create_terminate_group_leader_role_proposal_with_slashing_execution_succeeds(
 fn run_create_terminate_group_leader_role_proposal_with_slashing_execution_succeeds<
     T: working_group::Trait<I> + frame_system::Trait + minting::Trait,
     I: frame_support::traits::Instance,
-    SM: staking_handler::StakingHandler<T>,
+    SM: staking_handler::StakingHandler<
+        <T as frame_system::Trait>::AccountId,
+        <T as pallet_balances::Trait>::Balance,
+        <T as common::Trait>::MemberId,
+    >,
 >(
     working_group: WorkingGroup,
 ) where
@@ -1062,7 +1101,9 @@ fn run_create_terminate_group_leader_role_proposal_with_slashing_execution_succe
     <T as pallet_balances::Trait>::Balance: From<u128>,
 {
     initial_test_ext().execute_with(|| {
-        let member_id: MemberId = 1;
+        // Don't use the same member id as a councilor, can lead to conflicting stakes
+        let member_id: MemberId = 14;
+
         let account_id: [u8; 32] = [member_id as u8; 32];
         let stake_amount = 100_u128;
 
@@ -1071,6 +1112,8 @@ fn run_create_terminate_group_leader_role_proposal_with_slashing_execution_succe
             leaving_unstaking_period: 45000, // more than min value
         });
 
+        increase_total_balance_issuance_using_account_id(account_id.clone().into(), 1_500_000);
+
         let stake_parameters = Some(
             StakeParameters::<T::AccountId, working_group::BalanceOf<T>> {
                 stake: stake_amount.into(),
@@ -1080,8 +1123,6 @@ fn run_create_terminate_group_leader_role_proposal_with_slashing_execution_succe
 
         let opening_id = add_opening(member_id, account_id, stake_policy, 1, working_group);
 
-        add_confirmed_staking_account(member_id, account_id.into());
-
         let apply_result = WorkingGroupInstance::<T, I>::apply_on_opening(
             RawOrigin::Signed(account_id.into()).into(),
             working_group::ApplyOnOpeningParameters::<T> {

Some files were not shown because too many files changed in this diff