Forráskód Böngészése

Merge pull request #1880 from ondratra/council_staking_handler

Council staking handler
shamil-gadelshin 4 éve
szülő
commit
507ef2d798

+ 1 - 1
Cargo.lock

@@ -3776,7 +3776,6 @@ dependencies = [
  "frame-support",
  "frame-system",
  "pallet-balances",
- "pallet-common",
  "pallet-membership",
  "pallet-timestamp",
  "parity-scale-codec",
@@ -3786,6 +3785,7 @@ dependencies = [
  "sp-core",
  "sp-io",
  "sp-runtime",
+ "staking-handler",
  "substrate-referendum-module",
 ]
 

+ 6 - 12
runtime-modules/council/Cargo.toml

@@ -14,18 +14,15 @@ frame-support = { package = 'frame-support', default-features = false, git = 'ht
 frame-system = { package = 'frame-system', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
 
 referendum = { package = 'substrate-referendum-module', default-features = false, path = '../referendum'}
-
-# remove this after `staking_handler.rs` is removed in favor of external crate
-membership = { package = 'pallet-membership', default-features = false, path = '../membership'}
-common = { package = 'pallet-common', default-features = false, path = '../common'}
-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'}
+staking-handler = { package = 'staking-handler', default-features = false, path = '../staking-handler'}
 
 [dev-dependencies]
-# uncomment balances after `staking_handler.rs` is removed in favor of external crate
-#balances = { package = 'pallet-balances', 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']
@@ -39,9 +36,6 @@ std = [
     'frame-support/std',
     'referendum/std',
     'frame-system/std',
-
-    # remove this after `staking_handler.rs` is removed in favor of external crate
-    'membership/std',
-    'common/std',
+    'staking-handler/std',
 ]
 

+ 3 - 5
runtime-modules/council/src/lib.rs

@@ -62,14 +62,12 @@ use sp_runtime::traits::{Hash, MaybeSerialize, Member, SaturatedConversion, Satu
 use std::marker::PhantomData;
 
 use referendum::{CastVote, OptionResult, ReferendumManager};
+use staking_handler::StakingHandler;
 
 // declared modules
 mod mock;
-mod staking_handler;
 mod tests;
 
-use staking_handler::StakingHandler2;
-
 /////////////////// Data Structures ////////////////////////////////////////////
 
 /// Information about council's current state and when it changed the last time.
@@ -222,9 +220,9 @@ pub trait Trait: frame_system::Trait {
     type MinCandidateStake: Get<Balance<Self>>;
 
     /// Identifier for currency lock used for candidacy staking.
-    type CandidacyLock: StakingHandler2<Self::AccountId, Balance<Self>, Self::MembershipId>;
+    type CandidacyLock: StakingHandler<Self::AccountId, Balance<Self>, Self::MembershipId>;
     /// Identifier for currency lock used for candidacy staking.
-    type ElectedMemberLock: StakingHandler2<Self::AccountId, Balance<Self>, Self::MembershipId>;
+    type ElectedMemberLock: StakingHandler<Self::AccountId, Balance<Self>, Self::MembershipId>;
 
     /// Duration of annoncing period
     type AnnouncingPeriodDuration: Get<Self::BlockNumber>;

+ 40 - 9
runtime-modules/council/src/mock.rs

@@ -27,23 +27,24 @@ use sp_runtime::{
     traits::{BlakeTwo256, IdentityLookup},
     Perbill,
 };
+use staking_handler::{LockComparator, StakingManager};
 use std::cell::RefCell;
 use std::collections::BTreeMap;
 use std::marker::PhantomData;
 
-use crate::staking_handler::mocks::{Lock1, Lock2, CANDIDATE_BASE_ID, VOTER_BASE_ID};
-
 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 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;
 
+pub const TOPUP_MULTIPLIER: u64 = 10; // multiplies topup value so that candidate/voter can candidate/vote multiple times
+
 /////////////////// Runtime and Instances //////////////////////////////////////
 // Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted.
 #[derive(Clone, PartialEq, Eq, Debug)]
@@ -68,15 +69,15 @@ impl Trait for Runtime {
 
     type Referendum = referendum::Module<Runtime, ReferendumInstance>;
 
-    type MembershipId = <Lock1 as membership::Trait>::MemberId;
+    type MembershipId = u64;
     type MinNumberOfExtraCandidates = MinNumberOfExtraCandidates;
     type CouncilSize = CouncilSize;
     type AnnouncingPeriodDuration = AnnouncingPeriodDuration;
     type IdlePeriodDuration = IdlePeriodDuration;
     type MinCandidateStake = MinCandidateStake;
 
-    type CandidacyLock = Lock1;
-    type ElectedMemberLock = Lock2;
+    type CandidacyLock = StakingManager<Self, CandidacyLockId>;
+    type ElectedMemberLock = StakingManager<Self, ElectedMemberLockId>;
 
     type ElectedMemberRewardPerBlock = ElectedMemberRewardPerBlock;
     type ElectedMemberRewardPeriod = ElectedMemberRewardPeriod;
@@ -107,12 +108,17 @@ mod referendum_mod {
     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>,
     }
 }
 
@@ -166,6 +172,8 @@ parameter_types! {
     pub const MinimumVotingStake: u64 = 10000;
     pub const MaxSaltLength: u64 = 32; // use some multiple of 8 for ez testing
     pub const ReferendumLockId: LockIdentifier = *b"referend";
+    pub const MembershipFee: u64 = 100;
+    pub const MinimumPeriod: u64 = 5;
 }
 
 mod balances_mod {
@@ -255,6 +263,20 @@ impl balances::Trait for Runtime {
     type MaxLocks = MaxLocks;
 }
 
+impl membership::Trait for Runtime {
+    type Event = TestEvent;
+    type MemberId = u64;
+    type ActorId = u64;
+    type MembershipFee = MembershipFee;
+}
+
+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| {
@@ -268,6 +290,15 @@ parameter_types! {
     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)]
@@ -465,7 +496,7 @@ where
             note_hash: None,
         };
 
-        Self::topup_account(account_id.into(), stake);
+        Self::topup_account(account_id.into(), stake * TOPUP_MULTIPLIER.into());
 
         CandidateInfo {
             origin,

+ 0 - 690
runtime-modules/council/src/staking_handler.rs

@@ -1,690 +0,0 @@
-// TODO: content of this file should be replaced by separate crate with StakingHandler
-// NOTE: only StakingHandler2 is acually used, StakingHandler is original design copied from other development branch
-use frame_support::dispatch::DispatchResult;
-
-/////////////////// Trait definition ///////////////////////////////////////////
-
-/// Member identifier in membership::member module.
-#[allow(dead_code)] // following line throws dead code warning even when it is used (and removal causes error)
-pub type MemberId<T> = <T as membership::Trait>::MemberId;
-
-/// Balance alias for GovernanceCurrency from `common` module. TODO: replace with BalanceOf
-#[allow(dead_code)] // following line throws dead code warning even when it is used (and removal causes error)
-pub type BalanceOf<T> = <T as balances::Trait>::Balance;
-
-// NOTE: this is copy of StakingHandler definition from `proposals_update` development branch
-/// 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 + membership::Trait + balances::Trait> {
-    /// Locks the specified balance on the account using specific lock identifier.
-    fn lock(account_id: &T::AccountId, amount: BalanceOf<T>);
-
-    /// Removes the specified lock on the account.
-    fn unlock(account_id: &T::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>;
-
-    /// Sets the new stake to a given amount.
-    fn set_stake(account_id: &T::AccountId, new_stake: BalanceOf<T>) -> DispatchResult;
-
-    /// Verifies that staking account bound to the member.
-    fn is_member_staking_account(member_id: &MemberId<T>, account_id: &T::AccountId) -> bool;
-
-    /// Verifies that there no conflicting stakes on the staking account.
-    fn is_account_free_of_conflicting_stakes(account_id: &T::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;
-
-    /// Returns the current stake on the account.
-    fn current_stake(account_id: &T::AccountId) -> BalanceOf<T>;
-}
-
-// NOTE: this is
-// 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 StakingHandler2<AccountId, Balance, MemberId> {
-    /// Locks the specified balance on the account using specific lock identifier.
-    fn lock(account_id: &AccountId, amount: Balance);
-
-    /// Removes the specified lock on the account.
-    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: &AccountId, amount: Option<Balance>) -> Balance;
-
-    /// Sets the new stake to a given amount.
-    fn set_stake(account_id: &AccountId, new_stake: Balance) -> DispatchResult;
-
-    /// Verifies that staking account bound to the member.
-    fn is_member_staking_account(member_id: &MemberId, account_id: &AccountId) -> bool;
-
-    /// Verifies that there no conflicting stakes on the staking account.
-    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: &AccountId, amount: Balance) -> bool;
-
-    /// Returns the current stake on the account.
-    fn current_stake(account_id: &AccountId) -> Balance;
-}
-
-/////////////////// Mock implementation ////////////////////////////////////////
-
-#[cfg(test)]
-pub mod mocks {
-    use super::{BalanceOf, StakingHandler, StakingHandler2};
-    use common::currency::GovernanceCurrency;
-    use frame_support::dispatch::DispatchResult;
-    use frame_support::traits::{Currency, LockIdentifier, LockableCurrency, WithdrawReasons};
-    use frame_support::{impl_outer_event, impl_outer_origin, parameter_types};
-    use sp_core::H256;
-    use sp_runtime::{
-        testing::Header,
-        traits::{BlakeTwo256, IdentityLookup},
-        Perbill,
-    };
-    use std::cell::RefCell;
-    use std::collections::HashMap;
-
-    pub type BalanceOfCurrency<T> =
-        <<T as common::currency::GovernanceCurrency>::Currency as Currency<
-            <T as frame_system::Trait>::AccountId,
-        >>::Balance;
-
-    // these lines are originally from mock.rs -> after this file is removed, move it back
-    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 STAKING_ACCOUNT_ID_FOR_FAILED_VALIDITY_CHECK: u64 = 111;
-    pub const STAKING_ACCOUNT_ID_FOR_FAILED_AMOUNT_CHECK: u64 = 222;
-    pub const STAKING_ACCOUNT_ID_FOR_CONFLICTING_STAKES: u64 = 333;
-    pub const STAKING_ACCOUNT_ID_FOR_ZERO_STAKE: u64 = 444;
-    pub const LOCK_ID_1: LockIdentifier = [0; 8];
-    pub const LOCK_ID_2: LockIdentifier = [1; 8];
-
-    // Workaround for https://github.com/rust-lang/rust/issues/26925 - remove when sorted.
-    //#[derive(Clone, PartialEq, Eq, Debug)]
-    //pub struct Lock1;
-
-    #[derive(Clone, PartialEq, Eq, Debug)]
-    pub struct Lock1;
-    #[derive(Clone, PartialEq, Eq, Debug)]
-    pub struct Lock2;
-
-    //pub type Membership = membership::Module<Lock1>;
-    pub type Balances = balances::Module<Lock1>;
-    pub type System = frame_system::Module<Lock1>;
-
-    /////////////////// Lock1 //////////////////////////////////////////////////
-
-    impl StakingHandler<Lock1> for Lock1 {
-        fn lock(
-            account_id: &<Lock1 as frame_system::Trait>::AccountId,
-            amount: BalanceOfCurrency<Lock1>,
-        ) {
-            <Lock1 as GovernanceCurrency>::Currency::set_lock(
-                LOCK_ID_1,
-                &account_id,
-                amount,
-                WithdrawReasons::all(),
-            )
-        }
-
-        fn unlock(account_id: &<Lock1 as frame_system::Trait>::AccountId) {
-            <Lock1 as GovernanceCurrency>::Currency::remove_lock(LOCK_ID_1, &account_id);
-        }
-
-        fn slash(
-            account_id: &<Lock1 as frame_system::Trait>::AccountId,
-            amount: Option<BalanceOfCurrency<Lock1>>,
-        ) -> BalanceOfCurrency<Lock1> {
-            let locks = Balances::locks(&account_id);
-
-            let existing_lock = locks.iter().find(|lock| lock.id == LOCK_ID_1);
-
-            let mut actually_slashed_balance = Default::default();
-            if let Some(existing_lock) = existing_lock {
-                <Self as StakingHandler<Self>>::unlock(&account_id);
-
-                let mut slashable_amount = existing_lock.amount;
-                if let Some(amount) = amount {
-                    if existing_lock.amount > amount {
-                        let new_amount = existing_lock.amount - amount;
-                        <Self as StakingHandler<Self>>::lock(&account_id, new_amount);
-
-                        slashable_amount = amount;
-                    }
-                }
-
-                let _ = Balances::slash(&account_id, slashable_amount);
-
-                actually_slashed_balance = slashable_amount
-            }
-
-            actually_slashed_balance
-        }
-
-        fn set_stake(
-            account_id: &<Lock2 as frame_system::Trait>::AccountId,
-            new_stake: BalanceOf<Lock2>,
-        ) -> DispatchResult {
-            <Self as StakingHandler<Self>>::unlock(account_id);
-            <Self as StakingHandler<Self>>::lock(account_id, new_stake);
-
-            Ok(())
-        }
-
-        fn is_member_staking_account(_member_id: &u64, account_id: &u64) -> bool {
-            if *account_id == STAKING_ACCOUNT_ID_FOR_FAILED_VALIDITY_CHECK {
-                return false;
-            }
-
-            true
-        }
-
-        fn is_account_free_of_conflicting_stakes(account_id: &u64) -> bool {
-            if *account_id == STAKING_ACCOUNT_ID_FOR_CONFLICTING_STAKES {
-                return false;
-            }
-
-            true
-        }
-
-        fn is_enough_balance_for_stake(account_id: &u64, amount: u64) -> bool {
-            if *account_id == STAKING_ACCOUNT_ID_FOR_FAILED_AMOUNT_CHECK || amount > 1000 {
-                return false;
-            }
-
-            true
-        }
-
-        fn current_stake(account_id: &u64) -> u64 {
-            if *account_id == STAKING_ACCOUNT_ID_FOR_ZERO_STAKE {
-                return 0;
-            }
-
-            100 // random non-zero value
-        }
-    }
-
-    /////////////////// StakingHandler2 impl ///////////////////////////////////
-
-    thread_local! {
-        pub static LOCKED_VALUES_LOCK1: RefCell<HashMap<u64, BalanceOfCurrency<Lock1>>> = RefCell::new(HashMap::new());
-        pub static LOCKED_VALUES_LOCK2: RefCell<HashMap<u64, BalanceOfCurrency<Lock2>>> = RefCell::new(HashMap::new());
-    }
-
-    impl
-        StakingHandler2<
-            <Lock1 as frame_system::Trait>::AccountId,
-            BalanceOf<Lock1>,
-            <Lock1 as membership::Trait>::MemberId,
-        > for Lock1
-    {
-        fn lock(
-            account_id: &<Lock1 as frame_system::Trait>::AccountId,
-            amount: BalanceOfCurrency<Lock1>,
-        ) {
-            <Lock1 as GovernanceCurrency>::Currency::set_lock(
-                LOCK_ID_1,
-                &account_id,
-                amount,
-                WithdrawReasons::all(),
-            );
-
-            LOCKED_VALUES_LOCK1.with(|value| value.borrow_mut().insert(*account_id, amount));
-        }
-
-        fn unlock(account_id: &<Lock1 as frame_system::Trait>::AccountId) {
-            <Lock1 as GovernanceCurrency>::Currency::remove_lock(LOCK_ID_1, &account_id);
-
-            LOCKED_VALUES_LOCK1.with(|value| value.borrow_mut().insert(*account_id, 0));
-        }
-
-        fn slash(
-            account_id: &<Lock1 as frame_system::Trait>::AccountId,
-            amount: Option<BalanceOfCurrency<Lock1>>,
-        ) -> BalanceOfCurrency<Lock1> {
-            let locks = Balances::locks(&account_id);
-
-            let existing_lock = locks.iter().find(|lock| lock.id == LOCK_ID_1);
-
-            let mut actually_slashed_balance = Default::default();
-            if let Some(existing_lock) = existing_lock {
-                <Self as StakingHandler2<
-                    <Lock1 as frame_system::Trait>::AccountId,
-                    BalanceOf<Lock1>,
-                    <Lock1 as membership::Trait>::MemberId,
-                >>::unlock(&account_id);
-
-                let mut slashable_amount = existing_lock.amount;
-                if let Some(amount) = amount {
-                    if existing_lock.amount > amount {
-                        let new_amount = existing_lock.amount - amount;
-                        <Self as StakingHandler2<
-                            <Lock1 as frame_system::Trait>::AccountId,
-                            BalanceOf<Lock1>,
-                            <Lock1 as membership::Trait>::MemberId,
-                        >>::lock(&account_id, new_amount);
-
-                        slashable_amount = amount;
-                    }
-                }
-
-                let _ = Balances::slash(&account_id, slashable_amount);
-
-                actually_slashed_balance = slashable_amount
-            }
-
-            actually_slashed_balance
-        }
-
-        fn set_stake(
-            account_id: &<Lock1 as frame_system::Trait>::AccountId,
-            new_stake: BalanceOf<Lock1>,
-        ) -> DispatchResult {
-            <Self as StakingHandler2<
-                <Lock1 as frame_system::Trait>::AccountId,
-                BalanceOf<Lock1>,
-                <Lock1 as membership::Trait>::MemberId,
-            >>::unlock(account_id);
-            <Self as StakingHandler2<
-                <Lock1 as frame_system::Trait>::AccountId,
-                BalanceOf<Lock1>,
-                <Lock1 as membership::Trait>::MemberId,
-            >>::lock(account_id, new_stake);
-
-            Ok(())
-        }
-
-        fn is_member_staking_account(member_id: &u64, account_id: &u64) -> bool {
-            // all possible generated candidates
-            account_id == member_id
-                && account_id >= &CANDIDATE_BASE_ID
-                && account_id < &(CANDIDATE_BASE_ID + VOTER_CANDIDATE_OFFSET)
-        }
-
-        fn is_account_free_of_conflicting_stakes(account_id: &u64) -> bool {
-            if *account_id == STAKING_ACCOUNT_ID_FOR_CONFLICTING_STAKES {
-                return false;
-            }
-
-            true
-        }
-
-        fn is_enough_balance_for_stake(account_id: &u64, _amount: u64) -> bool {
-            if *account_id == STAKING_ACCOUNT_ID_FOR_FAILED_AMOUNT_CHECK {
-                return false;
-            }
-
-            true
-        }
-
-        fn current_stake(account_id: &u64) -> u64 {
-            LOCKED_VALUES_LOCK1.with(|value| match value.borrow().get(account_id) {
-                Some(current_stake) => *current_stake,
-                None => 0,
-            })
-        }
-    }
-
-    impl
-        StakingHandler2<
-            <Lock2 as frame_system::Trait>::AccountId,
-            BalanceOf<Lock2>,
-            <Lock2 as membership::Trait>::MemberId,
-        > for Lock2
-    {
-        fn lock(
-            account_id: &<Lock2 as frame_system::Trait>::AccountId,
-            amount: BalanceOfCurrency<Lock2>,
-        ) {
-            <Lock2 as GovernanceCurrency>::Currency::set_lock(
-                LOCK_ID_2,
-                &account_id,
-                amount,
-                WithdrawReasons::all(),
-            );
-
-            LOCKED_VALUES_LOCK2.with(|value| value.borrow_mut().insert(*account_id, amount));
-        }
-
-        fn unlock(account_id: &<Lock2 as frame_system::Trait>::AccountId) {
-            <Lock2 as GovernanceCurrency>::Currency::remove_lock(LOCK_ID_2, &account_id);
-
-            LOCKED_VALUES_LOCK2.with(|value| value.borrow_mut().insert(*account_id, 0));
-        }
-
-        fn slash(
-            account_id: &<Lock2 as frame_system::Trait>::AccountId,
-            amount: Option<BalanceOfCurrency<Lock2>>,
-        ) -> BalanceOfCurrency<Lock2> {
-            let locks = Balances::locks(&account_id);
-
-            let existing_lock = locks.iter().find(|lock| lock.id == LOCK_ID_2);
-
-            let mut actually_slashed_balance = Default::default();
-            if let Some(existing_lock) = existing_lock {
-                <Self as StakingHandler2<
-                    <Lock2 as frame_system::Trait>::AccountId,
-                    BalanceOf<Lock2>,
-                    <Lock2 as membership::Trait>::MemberId,
-                >>::unlock(&account_id);
-
-                let mut slashable_amount = existing_lock.amount;
-                if let Some(amount) = amount {
-                    if existing_lock.amount > amount {
-                        let new_amount = existing_lock.amount - amount;
-                        <Self as StakingHandler2<
-                            <Lock2 as frame_system::Trait>::AccountId,
-                            BalanceOf<Lock2>,
-                            <Lock2 as membership::Trait>::MemberId,
-                        >>::lock(&account_id, new_amount);
-
-                        slashable_amount = amount;
-                    }
-                }
-
-                let _ = Balances::slash(&account_id, slashable_amount);
-
-                actually_slashed_balance = slashable_amount
-            }
-
-            actually_slashed_balance
-        }
-
-        fn set_stake(
-            account_id: &<Lock2 as frame_system::Trait>::AccountId,
-            new_stake: BalanceOf<Lock2>,
-        ) -> DispatchResult {
-            <Self as StakingHandler2<
-                <Lock2 as frame_system::Trait>::AccountId,
-                BalanceOf<Lock2>,
-                <Lock2 as membership::Trait>::MemberId,
-            >>::unlock(account_id);
-            <Self as StakingHandler2<
-                <Lock2 as frame_system::Trait>::AccountId,
-                BalanceOf<Lock2>,
-                <Lock2 as membership::Trait>::MemberId,
-            >>::lock(account_id, new_stake);
-
-            Ok(())
-        }
-
-        fn is_member_staking_account(member_id: &u64, account_id: &u64) -> bool {
-            // all possible generated candidates
-            account_id == member_id
-                && account_id >= &CANDIDATE_BASE_ID
-                && account_id < &(CANDIDATE_BASE_ID + VOTER_CANDIDATE_OFFSET)
-        }
-
-        fn is_account_free_of_conflicting_stakes(account_id: &u64) -> bool {
-            if *account_id == STAKING_ACCOUNT_ID_FOR_CONFLICTING_STAKES {
-                return false;
-            }
-
-            true
-        }
-
-        fn is_enough_balance_for_stake(account_id: &u64, _amount: u64) -> bool {
-            if *account_id == STAKING_ACCOUNT_ID_FOR_FAILED_AMOUNT_CHECK {
-                return false;
-            }
-
-            true
-        }
-
-        fn current_stake(account_id: &u64) -> u64 {
-            LOCKED_VALUES_LOCK2.with(|value| match value.borrow().get(account_id) {
-                Some(current_stake) => *current_stake,
-                None => 0,
-            })
-        }
-    }
-
-    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 ExistentialDeposit: u32 = 0;
-        pub const MaxLocks: u32 = 50;
-        pub const MembershipFee: u64 = 100;
-    }
-
-    mod membership_mod {
-        pub use membership::Event;
-    }
-
-    impl_outer_event! {
-        pub enum TestEvent for Lock1 {
-            balances<T>,
-            membership_mod<T>,
-            frame_system<T>,
-        }
-    }
-
-    impl membership::Trait for Lock1 {
-        type Event = TestEvent;
-        type MemberId = u64;
-        type ActorId = u64;
-        type MembershipFee = MembershipFee;
-    }
-
-    impl timestamp::Trait for Lock1 {
-        type Moment = u64;
-        type OnTimestampSet = ();
-        type MinimumPeriod = MinimumPeriod;
-        type WeightInfo = ();
-    }
-
-    impl common::currency::GovernanceCurrency for Lock1 {
-        type Currency = Balances;
-    }
-
-    impl balances::Trait for Lock1 {
-        type Balance = u64;
-        type DustRemoval = ();
-        type Event = TestEvent;
-        type ExistentialDeposit = ExistentialDeposit;
-        type AccountStore = System;
-        type WeightInfo = ();
-        type MaxLocks = MaxLocks;
-    }
-
-    impl_outer_origin! {
-        pub enum Origin for Lock1 {}
-    }
-
-    impl frame_system::Trait for Lock1 {
-        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 = ();
-    }
-
-    /////////////////// Lock2 //////////////////////////////////////////////////
-
-    impl StakingHandler<Lock2> for Lock2 {
-        fn lock(
-            account_id: &<Lock2 as frame_system::Trait>::AccountId,
-            amount: BalanceOfCurrency<Lock2>,
-        ) {
-            <Lock2 as GovernanceCurrency>::Currency::set_lock(
-                LOCK_ID_2,
-                &account_id,
-                amount,
-                WithdrawReasons::all(),
-            )
-        }
-
-        fn unlock(account_id: &<Lock2 as frame_system::Trait>::AccountId) {
-            <Lock2 as GovernanceCurrency>::Currency::remove_lock(LOCK_ID_2, &account_id);
-        }
-
-        fn slash(
-            account_id: &<Lock2 as frame_system::Trait>::AccountId,
-            amount: Option<BalanceOfCurrency<Lock2>>,
-        ) -> BalanceOfCurrency<Lock2> {
-            let locks = Balances::locks(&account_id);
-
-            let existing_lock = locks.iter().find(|lock| lock.id == LOCK_ID_2);
-
-            let mut actually_slashed_balance = Default::default();
-            if let Some(existing_lock) = existing_lock {
-                <Self as StakingHandler<Self>>::unlock(&account_id);
-
-                let mut slashable_amount = existing_lock.amount;
-                if let Some(amount) = amount {
-                    if existing_lock.amount > amount {
-                        let new_amount = existing_lock.amount - amount;
-                        <Self as StakingHandler<Self>>::lock(&account_id, new_amount);
-
-                        slashable_amount = amount;
-                    }
-                }
-
-                let _ = Balances::slash(&account_id, slashable_amount);
-
-                actually_slashed_balance = slashable_amount
-            }
-
-            actually_slashed_balance
-        }
-
-        fn set_stake(
-            account_id: &<Lock2 as frame_system::Trait>::AccountId,
-            new_stake: BalanceOf<Lock2>,
-        ) -> DispatchResult {
-            <Self as StakingHandler<Self>>::unlock(account_id);
-            <Self as StakingHandler<Self>>::lock(account_id, new_stake);
-
-            Ok(())
-        }
-
-        fn is_member_staking_account(_member_id: &u64, account_id: &u64) -> bool {
-            if *account_id == STAKING_ACCOUNT_ID_FOR_FAILED_VALIDITY_CHECK {
-                return false;
-            }
-
-            true
-        }
-
-        fn is_account_free_of_conflicting_stakes(account_id: &u64) -> bool {
-            if *account_id == STAKING_ACCOUNT_ID_FOR_CONFLICTING_STAKES {
-                return false;
-            }
-
-            true
-        }
-
-        fn is_enough_balance_for_stake(account_id: &u64, amount: u64) -> bool {
-            if *account_id == STAKING_ACCOUNT_ID_FOR_FAILED_AMOUNT_CHECK || amount > 1000 {
-                return false;
-            }
-
-            true
-        }
-
-        fn current_stake(account_id: &u64) -> u64 {
-            if *account_id == STAKING_ACCOUNT_ID_FOR_ZERO_STAKE {
-                return 0;
-            }
-
-            100 // random non-zero value
-        }
-    }
-
-    impl membership::Trait for Lock2 {
-        type Event = TestEvent;
-        type MemberId = u64;
-        type ActorId = u64;
-        type MembershipFee = MembershipFee;
-    }
-
-    impl timestamp::Trait for Lock2 {
-        type Moment = u64;
-        type OnTimestampSet = ();
-        type MinimumPeriod = MinimumPeriod;
-        type WeightInfo = ();
-    }
-
-    impl common::currency::GovernanceCurrency for Lock2 {
-        type Currency = Balances;
-    }
-
-    impl balances::Trait for Lock2 {
-        type Balance = u64;
-        type DustRemoval = ();
-        type Event = TestEvent;
-        type ExistentialDeposit = ExistentialDeposit;
-        type AccountStore = System;
-        type WeightInfo = ();
-        type MaxLocks = MaxLocks;
-    }
-
-    impl frame_system::Trait for Lock2 {
-        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 = ();
-    }
-}

+ 9 - 5
runtime-modules/council/src/tests.rs

@@ -5,9 +5,8 @@ use super::{
     Module, Trait,
 };
 use crate::mock::*;
-use crate::staking_handler::mocks::{CANDIDATE_BASE_ID, VOTER_CANDIDATE_OFFSET};
-use crate::staking_handler::StakingHandler2;
 use frame_support::StorageValue;
+use staking_handler::StakingHandler;
 
 type Mocks = InstanceMocks<Runtime>;
 type MockUtils = InstanceMockUtils<Runtime>;
@@ -41,6 +40,10 @@ fn council_candidacy_invalid_time() {
                 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()
@@ -63,9 +66,9 @@ fn council_candidacy_invalid_time() {
         InstanceMocks::simulate_council_cycle(params);
 
         Mocks::announce_candidacy(
-            candidates[0].origin.clone(),
-            candidates[0].account_id.clone(),
-            candidates[0].candidate.stake.clone(),
+            late_candidate.origin.clone(),
+            late_candidate.account_id.clone(),
+            late_candidate.candidate.stake.clone(),
             Err(Error::CantCandidateNow),
         );
     });
@@ -190,6 +193,7 @@ fn council_vote_for_winner_stakes_longer() {
 
 // 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();
 

+ 13 - 3
runtime-modules/proposals/codex/src/tests/mock.rs

@@ -11,6 +11,7 @@ use sp_runtime::{
     Perbill,
 };
 use sp_staking::SessionIndex;
+use staking_handler::{LockComparator, StakingManager};
 
 use crate::{ProposalDetailsOf, ProposalEncoder, ProposalParameters};
 use proposals_engine::VotersParameters;
@@ -83,7 +84,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;
@@ -217,7 +218,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 MemberOriginValidator = ();
     type MinUnstakingPeriodLimit = ();
     type RewardPeriod = ();
@@ -299,7 +300,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 MemberOriginValidator = ();
     type MinUnstakingPeriodLimit = ();
     type RewardPeriod = ();
@@ -453,6 +454,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/proposals/engine/src/lib.rs

@@ -178,7 +178,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>>;

+ 11 - 1
runtime-modules/proposals/engine/src/tests/mock/mod.rs

@@ -20,6 +20,7 @@ pub(crate) mod proposals;
 
 use crate::ProposalObserver;
 pub use proposals::*;
+use staking_handler::{LockComparator, StakingManager};
 
 // Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted.
 #[derive(Clone, PartialEq, Eq, Debug)]
@@ -94,7 +95,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;
@@ -224,6 +225,15 @@ 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 {
+        false
+    }
+}
+
 pub fn initial_test_ext() -> sp_io::TestExternalities {
     let t = frame_system::GenesisConfig::default()
         .build_storage::<Test>()

+ 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},
     Perbill,
 };
+use staking_handler::{LockComparator, StakingManager};
 
 // The storage working group instance alias.
 pub type StorageWorkingGroupInstance = working_group::Instance2;
@@ -128,7 +130,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 MemberOriginValidator = ();
     type MinUnstakingPeriodLimit = ();
     type RewardPeriod = ();
@@ -222,6 +224,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>()

+ 72 - 38
runtime-modules/staking-handler/src/lib.rs

@@ -10,55 +10,59 @@ 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;
 
-/// Type alias for member id.
-pub type MemberId<T> = <T as membership::Trait>::MemberId;
-
-/// 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 + membership::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 staking account bound to the member.
-    fn is_member_staking_account(member_id: &MemberId<T>, account_id: &T::AccountId) -> bool;
+    fn is_member_staking_account(member_id: &MemberId, account_id: &AccountId) -> bool;
 
     /// 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 + membership::Trait + pallet_balances::Trait,
+    T: frame_system::Trait
+        + membership::Trait
+        + pallet_balances::Trait
+        + LockComparator<<T as pallet_balances::Trait>::Balance>,
     LockId: Get<LockIdentifier>,
 > {
     trait_marker: PhantomData<T>,
@@ -66,11 +70,22 @@ pub struct StakingManager<
 }
 
 impl<
-        T: frame_system::Trait + membership::Trait + pallet_balances::Trait,
+        T: frame_system::Trait
+            + membership::Trait
+            + pallet_balances::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 membership::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,
@@ -79,12 +94,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());
 
@@ -102,7 +120,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
         }
@@ -110,19 +128,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."));
         }
 
@@ -132,23 +156,33 @@ impl<
     }
 
     // Membership support for staking accounts required.
-    fn is_member_staking_account(_member_id: &MemberId<T>, _account_id: &T::AccountId) -> bool {
+    fn is_member_staking_account(
+        _member_id: &<T as membership::Trait>::MemberId,
+        _account_id: &<T as frame_system::Trait>::AccountId,
+    ) -> bool {
         true
     }
 
-    fn is_account_free_of_conflicting_stakes(account_id: &T::AccountId) -> bool {
+    fn is_account_free_of_conflicting_stakes(
+        account_id: &<T as frame_system::Trait>::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;
@@ -74,6 +76,16 @@ impl membership::Trait for Test {
     type MembershipFee = MembershipFee;
 }
 
+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 common::currency::GovernanceCurrency for Test {
     type Currency = Balances;
 }

+ 11 - 1
runtime-modules/storage/src/tests/mock.rs

@@ -10,6 +10,7 @@ use sp_runtime::{
     traits::{BlakeTwo256, IdentityLookup},
     Perbill,
 };
+use staking_handler::{LockComparator, StakingManager};
 
 use crate::data_directory::ContentIdExists;
 use crate::data_object_type_registry::IsActiveDataObjectType;
@@ -160,7 +161,7 @@ pub struct WorkingGroupWeightInfo;
 impl working_group::Trait<StorageWorkingGroupInstance> for Test {
     type Event = MetaEvent;
     type MaxWorkerNumberLimit = MaxWorkerNumberLimit;
-    type StakingHandler = staking_handler::StakingManager<Self, LockId>;
+    type StakingHandler = StakingManager<Self, LockId>;
     type MemberOriginValidator = ();
     type MinUnstakingPeriodLimit = ();
     type RewardPeriod = ();
@@ -291,6 +292,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/src/lib.rs

@@ -100,7 +100,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 member id and origin combination
     type MemberOriginValidator: ActorOriginValidator<Self::Origin, MemberId<Self>, Self::AccountId>;

+ 12 - 1
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, StakingManager};
 
 use crate::{DefaultInstance, Module, Trait};
 
@@ -102,6 +104,15 @@ impl membership::Trait for Test {
     type MembershipFee = MembershipFee;
 }
 
+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>;
 pub type Membership = membership::Module<Test>;
@@ -116,7 +127,7 @@ parameter_types! {
 impl Trait for Test {
     type Event = TestEvent;
     type MaxWorkerNumberLimit = MaxWorkerNumberLimit;
-    type StakingHandler = staking_handler::StakingManager<Self, LockId>;
+    type StakingHandler = StakingManager<Self, LockId>;
     type MemberOriginValidator = ();
     type MinUnstakingPeriodLimit = MinUnstakingPeriodLimit;
     type RewardPeriod = RewardPeriod;

+ 45 - 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,48 @@ 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 ProposalsLockId: LockIdentifier = [5; 8];
+    pub const StorageWorkingGroupLockId: LockIdentifier = [6; 8];
+    pub const ContentWorkingGroupLockId: LockIdentifier = [7; 8];
+    pub const ForumGroupLockId: LockIdentifier = [8; 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()
+        ]),
+        (ContentWorkingGroupLockId::get(), [
+            ForumGroupLockId::get(),
+            StorageWorkingGroupLockId::get(),
+            ProposalsLockId::get(),
+        ]),
+        (StorageWorkingGroupLockId::get(), [
+            ForumGroupLockId::get(),
+            ContentWorkingGroupLockId::get(),
+            ProposalsLockId::get(),
+        ]),
+        (ProposalsLockId::get(), [
+            ForumGroupLockId::get(),
+            ContentWorkingGroupLockId::get(),
+            StorageWorkingGroupLockId::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 {

+ 9 - 4
runtime/src/lib.rs

@@ -55,6 +55,7 @@ pub use runtime_api::*;
 use integration::proposals::{CouncilManager, ExtrinsicProposalEncoder, MembershipOriginValidator};
 
 use governance::{council, election};
+use staking_handler::LockComparator;
 use storage::data_object_storage_registry;
 
 // Node dependencies
@@ -586,6 +587,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;
 
@@ -601,9 +610,6 @@ parameter_types! {
     pub const ForumWorkingGroupRewardPeriod: u32 = 14400 + 10;
     pub const StorageWorkingGroupRewardPeriod: u32 = 14400 + 20;
     pub const ContentWorkingGroupRewardPeriod: u32 = 14400 + 30;
-    pub const StorageWorkingGroupLockId: LockIdentifier = [6; 8];
-    pub const ContentWorkingGroupLockId: LockIdentifier = [7; 8];
-    pub const ForumGroupLockId: LockIdentifier = [8; 8];
 }
 
 // Staking managers type aliases.
@@ -654,7 +660,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 {

+ 20 - 4
runtime/src/tests/proposals_integration/working_group_proposals.rs

@@ -442,7 +442,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 + membership::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 membership::Trait>::MemberId,
+    >,
 >(
     working_group: WorkingGroup,
 ) where
@@ -560,7 +564,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 membership::Trait>::MemberId,
+    >,
 >(
     working_group: WorkingGroup,
 ) where
@@ -829,7 +837,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 membership::Trait>::MemberId,
+    >,
 >(
     working_group: WorkingGroup,
 ) where
@@ -948,7 +960,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 membership::Trait>::MemberId,
+    >,
 >(
     working_group: WorkingGroup,
 ) where