Просмотр исходного кода

Merging council_rework with olympia

conectado 4 лет назад
Родитель
Сommit
168f9a61d3

+ 62 - 53
Cargo.lock

@@ -140,9 +140,9 @@ dependencies = [
 
 [[package]]
 name = "anyhow"
-version = "1.0.35"
+version = "1.0.34"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2c0df63cb2955042487fad3aefd2c6e3ae7389ac5dc1beb28921de0b69f779d4"
+checksum = "bf8dcb5b4bbaa28653b647d8c77bd4ed40183b48882e130c1f1ffb73de069fd7"
 
 [[package]]
 name = "approx"
@@ -641,9 +641,9 @@ checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba"
 
 [[package]]
 name = "cc"
-version = "1.0.66"
+version = "1.0.65"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48"
+checksum = "95752358c8f7552394baf48cd82695b345628ad3f170d607de3ca03b8dacca15"
 dependencies = [
  "jobserver",
 ]
@@ -757,6 +757,15 @@ dependencies = [
  "bitflags",
 ]
 
+[[package]]
+name = "cloudabi"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4344512281c643ae7638bbabc3af17a11307803ec8f0fcad9fae512a8bf36467"
+dependencies = [
+ "bitflags",
+]
+
 [[package]]
 name = "concurrent-queue"
 version = "1.2.2"
@@ -788,9 +797,9 @@ dependencies = [
 
 [[package]]
 name = "const-random"
-version = "0.1.13"
+version = "0.1.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f590d95d011aa80b063ffe3253422ed5aa462af4e9867d43ce8337562bac77c4"
+checksum = "486d435a7351580347279f374cb8a3c16937485441db80181357b7c4d70f17ed"
 dependencies = [
  "const-random-macro",
  "proc-macro-hack",
@@ -798,9 +807,9 @@ dependencies = [
 
 [[package]]
 name = "const-random-macro"
-version = "0.1.13"
+version = "0.1.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "615f6e27d000a2bffbc7f2f6a8669179378fa27ee4d0a509e985dfc0a7defb40"
+checksum = "49a84d8ff70e3ec52311109b019c27672b4c1929e4cf7c18bcf0cd9fb5e230be"
 dependencies = [
  "getrandom 0.2.0",
  "lazy_static",
@@ -1398,7 +1407,7 @@ dependencies = [
  "parity-scale-codec",
  "paste",
  "serde",
- "smallvec 1.5.1",
+ "smallvec 1.5.0",
  "sp-arithmetic",
  "sp-core",
  "sp-inherents",
@@ -2558,7 +2567,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0315ef2f688e33844400b31f11c263f2b3dc21d8b9355c6891c5f185fae43f9a"
 dependencies = [
  "parity-util-mem",
- "smallvec 1.5.1",
+ "smallvec 1.5.0",
 ]
 
 [[package]]
@@ -2587,7 +2596,7 @@ dependencies = [
  "parking_lot 0.10.2",
  "regex",
  "rocksdb",
- "smallvec 1.5.1",
+ "smallvec 1.5.0",
 ]
 
 [[package]]
@@ -2624,9 +2633,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
 
 [[package]]
 name = "libc"
-version = "0.2.81"
+version = "0.2.80"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb"
+checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614"
 
 [[package]]
 name = "libloading"
@@ -2679,7 +2688,7 @@ dependencies = [
  "parity-multiaddr",
  "parking_lot 0.10.2",
  "pin-project 0.4.27",
- "smallvec 1.5.1",
+ "smallvec 1.5.0",
  "wasm-timer",
 ]
 
@@ -2710,7 +2719,7 @@ dependencies = [
  "ring",
  "rw-stream-sink",
  "sha2 0.8.2",
- "smallvec 1.5.1",
+ "smallvec 1.5.0",
  "thiserror",
  "unsigned-varint 0.4.0",
  "void",
@@ -2763,7 +2772,7 @@ dependencies = [
  "prost",
  "prost-build",
  "rand 0.7.3",
- "smallvec 1.5.1",
+ "smallvec 1.5.0",
 ]
 
 [[package]]
@@ -2787,7 +2796,7 @@ dependencies = [
  "prost-build",
  "rand 0.7.3",
  "sha2 0.8.2",
- "smallvec 1.5.1",
+ "smallvec 1.5.0",
  "unsigned-varint 0.4.0",
  "wasm-timer",
 ]
@@ -2804,7 +2813,7 @@ dependencies = [
  "log",
  "prost",
  "prost-build",
- "smallvec 1.5.1",
+ "smallvec 1.5.0",
  "wasm-timer",
 ]
 
@@ -2828,7 +2837,7 @@ dependencies = [
  "prost-build",
  "rand 0.7.3",
  "sha2 0.8.2",
- "smallvec 1.5.1",
+ "smallvec 1.5.0",
  "uint",
  "unsigned-varint 0.4.0",
  "void",
@@ -2852,7 +2861,7 @@ dependencies = [
  "log",
  "net2",
  "rand 0.7.3",
- "smallvec 1.5.1",
+ "smallvec 1.5.0",
  "void",
  "wasm-timer",
 ]
@@ -2957,7 +2966,7 @@ dependencies = [
  "lru 0.6.1",
  "minicbor",
  "rand 0.7.3",
- "smallvec 1.5.1",
+ "smallvec 1.5.0",
  "unsigned-varint 0.5.1",
  "wasm-timer",
 ]
@@ -2973,7 +2982,7 @@ dependencies = [
  "libp2p-core",
  "log",
  "rand 0.7.3",
- "smallvec 1.5.1",
+ "smallvec 1.5.0",
  "void",
  "wasm-timer",
 ]
@@ -3334,9 +3343,9 @@ dependencies = [
 
 [[package]]
 name = "mio"
-version = "0.6.23"
+version = "0.6.22"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4"
+checksum = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430"
 dependencies = [
  "cfg-if 0.1.10",
  "fuchsia-zircon",
@@ -3439,7 +3448,7 @@ dependencies = [
  "futures 0.3.8",
  "log",
  "pin-project 1.0.2",
- "smallvec 1.5.1",
+ "smallvec 1.5.0",
  "unsigned-varint 0.5.1",
 ]
 
@@ -4147,7 +4156,6 @@ dependencies = [
  "frame-system",
  "pallet-balances",
  "pallet-common",
- "pallet-membership",
  "pallet-timestamp",
  "parity-scale-codec",
  "sp-arithmetic",
@@ -4247,7 +4255,7 @@ dependencies = [
  "pallet-transaction-payment-rpc-runtime-api",
  "parity-scale-codec",
  "serde",
- "smallvec 1.5.1",
+ "smallvec 1.5.0",
  "sp-core",
  "sp-io",
  "sp-runtime",
@@ -4416,7 +4424,7 @@ dependencies = [
  "parity-util-mem-derive",
  "parking_lot 0.10.2",
  "primitive-types",
- "smallvec 1.5.1",
+ "smallvec 1.5.0",
  "winapi 0.3.9",
 ]
 
@@ -4500,7 +4508,7 @@ checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb"
 dependencies = [
  "instant",
  "lock_api 0.4.2",
- "parking_lot_core 0.8.1",
+ "parking_lot_core 0.8.0",
 ]
 
 [[package]]
@@ -4523,7 +4531,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b"
 dependencies = [
  "cfg-if 0.1.10",
- "cloudabi",
+ "cloudabi 0.0.3",
  "libc",
  "redox_syscall",
  "rustc_version",
@@ -4538,24 +4546,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3"
 dependencies = [
  "cfg-if 0.1.10",
- "cloudabi",
+ "cloudabi 0.0.3",
  "libc",
  "redox_syscall",
- "smallvec 1.5.1",
+ "smallvec 1.5.0",
  "winapi 0.3.9",
 ]
 
 [[package]]
 name = "parking_lot_core"
-version = "0.8.1"
+version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d7c6d9b8427445284a09c55be860a15855ab580a417ccad9da88f5a06787ced0"
+checksum = "c361aa727dd08437f2f1447be8b59a33b0edd15e0fcee698f935613d9efbca9b"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if 0.1.10",
+ "cloudabi 0.1.0",
  "instant",
  "libc",
  "redox_syscall",
- "smallvec 1.5.1",
+ "smallvec 1.5.0",
  "winapi 0.3.9",
 ]
 
@@ -4923,7 +4932,7 @@ version = "0.5.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c618c47cd3ebd209790115ab837de41425723956ad3ce2e6a7f09890947cacb9"
 dependencies = [
- "cloudabi",
+ "cloudabi 0.0.3",
  "fuchsia-cprng",
  "libc",
  "rand_core 0.3.1",
@@ -5051,7 +5060,7 @@ version = "0.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071"
 dependencies = [
- "cloudabi",
+ "cloudabi 0.0.3",
  "fuchsia-cprng",
  "libc",
  "rand_core 0.4.2",
@@ -5210,9 +5219,9 @@ checksum = "e005d658ad26eacc2b6c506dfde519f4e277e328d0eb3379ca61647d70a8f531"
 
 [[package]]
 name = "ring"
-version = "0.16.19"
+version = "0.16.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "024a1e66fea74c66c66624ee5622a7ff0e4b73a13b4f5c326ddb50c708944226"
+checksum = "70017ed5c555d79ee3538fc63ca09c70ad8f317dcadc1adc2c496b60c22bb24f"
 dependencies = [
  "cc",
  "libc",
@@ -6365,18 +6374,18 @@ checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0"
 
 [[package]]
 name = "serde"
-version = "1.0.118"
+version = "1.0.117"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800"
+checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.118"
+version = "1.0.117"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df"
+checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -6396,9 +6405,9 @@ dependencies = [
 
 [[package]]
 name = "serde_json"
-version = "1.0.60"
+version = "1.0.59"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1500e84d27fe482ed1dc791a56eddc2f230046a040fa908c08bda1d9fb615779"
+checksum = "dcac07dbffa1c65e7f816ab9eba78eb142c6d44410f4eeba1e26e4f5dfa56b95"
 dependencies = [
  "itoa",
  "ryu",
@@ -6559,9 +6568,9 @@ dependencies = [
 
 [[package]]
 name = "smallvec"
-version = "1.5.1"
+version = "1.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ae524f056d7d770e174287294f562e95044c68e88dec909a00d2094805db9d75"
+checksum = "7acad6f34eb9e8a259d3283d1e8c1d34d7415943d4895f65cc73813c7396fc85"
 
 [[package]]
 name = "snow"
@@ -7104,7 +7113,7 @@ dependencies = [
  "parity-scale-codec",
  "parking_lot 0.10.2",
  "rand 0.7.3",
- "smallvec 1.5.1",
+ "smallvec 1.5.0",
  "sp-core",
  "sp-externalities",
  "sp-panic-handler",
@@ -8056,7 +8065,7 @@ dependencies = [
  "serde",
  "serde_json",
  "sharded-slab",
- "smallvec 1.5.1",
+ "smallvec 1.5.0",
  "thread_local",
  "tracing",
  "tracing-core",
@@ -8074,7 +8083,7 @@ dependencies = [
  "hashbrown 0.8.2",
  "log",
  "rustc-hex",
- "smallvec 1.5.1",
+ "smallvec 1.5.0",
 ]
 
 [[package]]
@@ -8412,9 +8421,9 @@ dependencies = [
 
 [[package]]
 name = "webpki"
-version = "0.21.4"
+version = "0.21.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea"
+checksum = "ab146130f5f790d45f82aeeb09e55a256573373ec64409fc19a6fb82fb1032ae"
 dependencies = [
  "ring",
  "untrusted",

+ 6 - 0
runtime-modules/common/src/lib.rs

@@ -42,6 +42,12 @@ pub trait Trait: frame_system::Trait {
         + PartialEq;
 }
 
+/// Validates staking account ownership for a member.
+pub trait StakingAccountValidator<T: Trait> {
+    /// Verifies that staking account bound to the member.
+    fn is_member_staking_account(member_id: &MemberId<T>, account_id: &T::AccountId) -> bool;
+}
+
 /// Defines time in both block number and substrate time abstraction.
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
 #[derive(Clone, Encode, Decode, PartialEq, Eq, Debug, Default)]

+ 8 - 1
runtime-modules/council/src/lib.rs

@@ -52,6 +52,7 @@ 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;
 
@@ -217,6 +218,9 @@ pub trait Trait: frame_system::Trait + common::Trait {
     /// 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
@@ -1146,7 +1150,10 @@ impl<T: Trait> EnsureChecks<T> {
         Self::ensure_user_membership(origin, membership_id)?;
 
         // ensure staking account's membership
-        if !T::CandidacyLock::is_member_staking_account(&membership_id, &staking_account_id) {
+        if !T::StakingAccountValidator::is_member_staking_account(
+            &membership_id,
+            &staking_account_id,
+        ) {
             return Err(Error::MemberIdNotMatchAccount);
         }
 

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

@@ -95,6 +95,8 @@ impl Trait for Runtime {
     type ElectedMemberRewardPerBlock = ElectedMemberRewardPerBlock;
     type ElectedMemberRewardPeriod = ElectedMemberRewardPeriod;
 
+    type StakingAccountValidator = ();
+
     type BudgetRefillAmount = BudgetRefillAmount;
     type BudgetRefillPeriod = BudgetRefillPeriod;
 
@@ -114,6 +116,12 @@ impl Trait for Runtime {
     }
 }
 
+impl common::StakingAccountValidator<Runtime> for () {
+    fn is_member_staking_account(_: &u64, _: &u64) -> bool {
+        true
+    }
+}
+
 /////////////////// Module implementation //////////////////////////////////////
 
 impl_outer_origin! {

+ 154 - 1
runtime-modules/membership/src/lib.rs

@@ -89,7 +89,7 @@ pub type Membership<T> = MembershipObject<<T as frame_system::Trait>::AccountId>
 
 #[derive(Encode, Decode, Default)]
 /// Stored information about a registered user.
-pub struct MembershipObject<AccountId> {
+pub struct MembershipObject<AccountId: Ord> {
     /// User name.
     pub name: Vec<u8>,
 
@@ -121,6 +121,16 @@ pub struct MembershipObject<AccountId> {
     pub invites: u32,
 }
 
+// Contain staking account to member binding and its confirmation.
+#[derive(Encode, Decode, Default)]
+pub struct StakingAccountMemberBinding<MemberId> {
+    /// Member id that we bind account to.
+    pub member_id: MemberId,
+
+    /// Confirmation that an account id is bound to a member.
+    pub confirmed: bool,
+}
+
 // Contains valid or default user details
 struct ValidatedUserInfo {
     name: Vec<u8>,
@@ -226,6 +236,15 @@ decl_error! {
 
         /// Membership working group leader is not set.
         WorkingGroupLeaderNotSet,
+
+        /// Staking account is registered for some member.
+        StakingAccountIsAlreadyRegistered,
+
+        /// Staking account for membership doesn't exist.
+        StakingAccountDoesntExist,
+
+        /// Staking account has already been confirmed.
+        StakingAccountAlreadyConfirmed,
     }
 }
 
@@ -280,6 +299,11 @@ decl_storage! {
         /// Initial invitation balance for the invited member.
         pub InitialInvitationBalance get(fn initial_invitation_balance) : BalanceOf<T> =
             T::DefaultInitialInvitationBalance::get();
+
+        /// Double of a staking account id and member id to the confirmation status.
+        pub(crate) StakingAccountIdMemberStatus get(fn staking_account_id_member_status):
+            map hasher(blake2_128_concat) T::AccountId => StakingAccountMemberBinding<T::MemberId>;
+
     }
     add_extra_genesis {
         config(members) : Vec<genesis::Member<T::MemberId, T::AccountId>>;
@@ -310,6 +334,7 @@ decl_event! {
     pub enum Event<T> where
       <T as common::Trait>::MemberId,
       Balance = BalanceOf<T>,
+      <T as frame_system::Trait>::AccountId,
     {
         MemberRegistered(MemberId),
         MemberProfileUpdated(MemberId),
@@ -321,6 +346,9 @@ decl_event! {
         InitialInvitationBalanceUpdated(Balance),
         LeaderInvitationQuotaUpdated(u32),
         InitialInvitationCountUpdated(u32),
+        StakingAccountAdded(AccountId, MemberId),
+        StakingAccountRemoved(AccountId, MemberId),
+        StakingAccountConfirmed(AccountId, MemberId),
     }
 }
 
@@ -684,6 +712,89 @@ decl_module! {
 
             Self::deposit_event(RawEvent::InitialInvitationCountUpdated(new_invitation_count));
         }
+
+        /// Add staking account candidate for a member.
+        /// The membership must be confirmed before usage.
+        #[weight = 10_000_000] // TODO: adjust weight
+        pub fn add_staking_account_candidate(origin, member_id: T::MemberId) {
+            let staking_account_id = ensure_signed(origin)?;
+
+            ensure!(
+                !Self::staking_account_registered(&staking_account_id),
+                Error::<T>::StakingAccountIsAlreadyRegistered
+            );
+
+            Self::ensure_membership(member_id)?;
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            <StakingAccountIdMemberStatus<T>>::insert(
+                staking_account_id.clone(),
+                StakingAccountMemberBinding {
+                    member_id,
+                    confirmed: false,
+                }
+            );
+
+            Self::deposit_event(RawEvent::StakingAccountAdded(staking_account_id, member_id));
+        }
+
+        /// Remove staking account for a member.
+        #[weight = 10_000_000] // TODO: adjust weight
+        pub fn remove_staking_account(origin, member_id: T::MemberId) {
+            let staking_account_id = ensure_signed(origin)?;
+
+            Self::ensure_membership(member_id)?;
+
+            ensure!(
+                Self::staking_account_registered_for_member(&staking_account_id, &member_id),
+                Error::<T>::StakingAccountDoesntExist
+            );
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            <StakingAccountIdMemberStatus<T>>::remove(staking_account_id.clone());
+
+            Self::deposit_event(RawEvent::StakingAccountRemoved(staking_account_id, member_id));
+        }
+
+        /// Confirm staking account candidate for a member.
+        #[weight = 10_000_000] // TODO: adjust weight
+        pub fn confirm_staking_account(
+            origin,
+            member_id: T::MemberId,
+            staking_account_id: T::AccountId,
+        ) {
+            Self::ensure_member_controller_account_signed(origin, &member_id)?;
+
+            ensure!(
+                Self::staking_account_registered_for_member(&staking_account_id, &member_id),
+                Error::<T>::StakingAccountDoesntExist
+            );
+
+            ensure!(
+                !Self::staking_account_confirmed(&staking_account_id, &member_id),
+                Error::<T>::StakingAccountAlreadyConfirmed
+            );
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            <StakingAccountIdMemberStatus<T>>::insert(
+                staking_account_id.clone(),
+                StakingAccountMemberBinding {
+                    member_id,
+                    confirmed: true,
+                }
+            );
+
+            Self::deposit_event(RawEvent::StakingAccountConfirmed(staking_account_id, member_id));
+        }
     }
 }
 
@@ -857,4 +968,46 @@ impl<T: Trait> Module<T> {
 
         membership_fee.min(referral_cut)
     }
+
+    // Verifies registration of the staking account for ANY member.
+    fn staking_account_registered(staking_account_id: &T::AccountId) -> bool {
+        <StakingAccountIdMemberStatus<T>>::contains_key(staking_account_id)
+    }
+
+    // Verifies registration of the staking account for SOME member.
+    fn staking_account_registered_for_member(
+        staking_account_id: &T::AccountId,
+        member_id: &T::MemberId,
+    ) -> bool {
+        if !Self::staking_account_registered(staking_account_id) {
+            return false;
+        }
+
+        let member_status = Self::staking_account_id_member_status(staking_account_id);
+
+        member_status.member_id == *member_id
+    }
+
+    // Verifies confirmation of the staking account.
+    fn staking_account_confirmed(
+        staking_account_id: &T::AccountId,
+        member_id: &T::MemberId,
+    ) -> bool {
+        if !Self::staking_account_registered_for_member(staking_account_id, member_id) {
+            return false;
+        }
+
+        let member_status = Self::staking_account_id_member_status(staking_account_id);
+
+        member_status.confirmed
+    }
+}
+
+impl<T: Trait> common::StakingAccountValidator<T> for Module<T> {
+    fn is_member_staking_account(
+        member_id: &common::MemberId<T>,
+        account_id: &T::AccountId,
+    ) -> bool {
+        Self::staking_account_confirmed(account_id, member_id)
+    }
 }

+ 118 - 0
runtime-modules/membership/src/tests/fixtures.rs

@@ -553,3 +553,121 @@ impl SetInitialInvitationCountFixture {
         Self { origin, ..self }
     }
 }
+
+pub struct AddStakingAccountFixture {
+    pub origin: RawOrigin<u64>,
+    pub member_id: u64,
+    pub staking_account_id: u64,
+}
+
+impl Default for AddStakingAccountFixture {
+    fn default() -> Self {
+        Self {
+            origin: RawOrigin::Signed(ALICE_ACCOUNT_ID),
+            member_id: ALICE_MEMBER_ID,
+            staking_account_id: ALICE_ACCOUNT_ID,
+        }
+    }
+}
+
+impl AddStakingAccountFixture {
+    pub fn call_and_assert(&self, expected_result: DispatchResult) {
+        let actual_result =
+            Membership::add_staking_account_candidate(self.origin.clone().into(), self.member_id);
+
+        assert_eq!(expected_result, actual_result);
+
+        if actual_result.is_ok() {
+            assert!(<crate::StakingAccountIdMemberStatus<Test>>::contains_key(
+                &self.staking_account_id,
+            ));
+        }
+    }
+
+    pub fn with_origin(self, origin: RawOrigin<u64>) -> Self {
+        Self { origin, ..self }
+    }
+
+    pub fn with_member_id(self, member_id: u64) -> Self {
+        Self { member_id, ..self }
+    }
+}
+
+pub struct RemoveStakingAccountFixture {
+    pub origin: RawOrigin<u64>,
+    pub member_id: u64,
+    pub staking_account_id: u64,
+}
+
+impl Default for RemoveStakingAccountFixture {
+    fn default() -> Self {
+        Self {
+            origin: RawOrigin::Signed(ALICE_ACCOUNT_ID),
+            member_id: ALICE_MEMBER_ID,
+            staking_account_id: ALICE_ACCOUNT_ID,
+        }
+    }
+}
+
+impl RemoveStakingAccountFixture {
+    pub fn call_and_assert(&self, expected_result: DispatchResult) {
+        let actual_result =
+            Membership::remove_staking_account(self.origin.clone().into(), self.member_id);
+
+        assert_eq!(expected_result, actual_result);
+
+        if actual_result.is_ok() {
+            assert!(!<crate::StakingAccountIdMemberStatus<Test>>::contains_key(
+                &self.staking_account_id,
+            ));
+        }
+    }
+
+    pub fn with_origin(self, origin: RawOrigin<u64>) -> Self {
+        Self { origin, ..self }
+    }
+
+    pub fn with_member_id(self, member_id: u64) -> Self {
+        Self { member_id, ..self }
+    }
+}
+
+pub struct ConfirmStakingAccountFixture {
+    pub origin: RawOrigin<u64>,
+    pub member_id: u64,
+    pub staking_account_id: u64,
+}
+
+impl Default for ConfirmStakingAccountFixture {
+    fn default() -> Self {
+        Self {
+            origin: RawOrigin::Signed(ALICE_ACCOUNT_ID),
+            member_id: ALICE_MEMBER_ID,
+            staking_account_id: ALICE_ACCOUNT_ID,
+        }
+    }
+}
+
+impl ConfirmStakingAccountFixture {
+    pub fn call_and_assert(&self, expected_result: DispatchResult) {
+        let actual_result = Membership::confirm_staking_account(
+            self.origin.clone().into(),
+            self.member_id,
+            self.staking_account_id,
+        );
+
+        assert_eq!(expected_result, actual_result);
+
+        if actual_result.is_ok() {
+            assert!(<crate::StakingAccountIdMemberStatus<Test>>::get(&ALICE_ACCOUNT_ID,).confirmed);
+        }
+    }
+
+    pub fn with_origin(self, origin: RawOrigin<u64>) -> Self {
+        Self { origin, ..self }
+    }
+
+    pub fn with_member_id(self, member_id: u64) -> Self {
+        Self { member_id, ..self }
+    }
+}

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

@@ -112,6 +112,7 @@ impl working_group::Trait<MembershipWorkingGroupInstance> for Test {
     type Event = TestEvent;
     type MaxWorkerNumberLimit = MaxWorkerNumberLimit;
     type StakingHandler = staking_handler::StakingManager<Self, LockId>;
+    type StakingAccountValidator = Membership;
     type MemberOriginValidator = ();
     type MinUnstakingPeriodLimit = ();
     type RewardPeriod = ();

+ 207 - 0
runtime-modules/membership/src/tests/mod.rs

@@ -7,6 +7,7 @@ use crate::{Error, Event};
 use fixtures::*;
 use mock::*;
 
+use common::StakingAccountValidator;
 use frame_support::traits::{LockIdentifier, LockableCurrency, WithdrawReasons};
 use frame_support::{assert_ok, StorageMap, StorageValue};
 use frame_system::RawOrigin;
@@ -734,3 +735,209 @@ fn set_initial_invitation_count_fails_with_invalid_origin() {
             .call_and_assert(Err(DispatchError::BadOrigin));
     });
 }
+
+#[test]
+fn add_staking_account_candidate_succeeds() {
+    let initial_members = [(ALICE_MEMBER_ID, ALICE_ACCOUNT_ID)];
+
+    build_test_externalities_with_initial_members(initial_members.to_vec()).execute_with(|| {
+        let starting_block = 1;
+        run_to_block(starting_block);
+
+        AddStakingAccountFixture::default().call_and_assert(Ok(()));
+
+        EventFixture::assert_last_crate_event(Event::<Test>::StakingAccountAdded(
+            ALICE_ACCOUNT_ID,
+            ALICE_MEMBER_ID,
+        ));
+    });
+}
+
+#[test]
+fn add_staking_account_candidate_fails_with_bad_origin() {
+    build_test_externalities().execute_with(|| {
+        AddStakingAccountFixture::default()
+            .with_origin(RawOrigin::None)
+            .call_and_assert(Err(DispatchError::BadOrigin));
+    });
+}
+
+#[test]
+fn add_staking_account_candidate_fails_with_invalid_member_id() {
+    build_test_externalities().execute_with(|| {
+        let initial_balance = DefaultMembershipPrice::get();
+        set_alice_free_balance(initial_balance);
+
+        assert_ok!(buy_default_membership_as_alice());
+        let invalid_member_id = 222;
+
+        AddStakingAccountFixture::default()
+            .with_member_id(invalid_member_id)
+            .call_and_assert(Err(Error::<Test>::MemberProfileNotFound.into()));
+    });
+}
+
+#[test]
+fn add_staking_account_candidate_fails_with_duplicated_staking_account_id() {
+    let initial_members = [(ALICE_MEMBER_ID, ALICE_ACCOUNT_ID)];
+
+    build_test_externalities_with_initial_members(initial_members.to_vec()).execute_with(|| {
+        AddStakingAccountFixture::default().call_and_assert(Ok(()));
+
+        AddStakingAccountFixture::default()
+            .call_and_assert(Err(Error::<Test>::StakingAccountIsAlreadyRegistered.into()));
+    });
+}
+
+#[test]
+fn remove_staking_account_succeeds() {
+    let initial_members = [(ALICE_MEMBER_ID, ALICE_ACCOUNT_ID)];
+
+    build_test_externalities_with_initial_members(initial_members.to_vec()).execute_with(|| {
+        let starting_block = 1;
+        run_to_block(starting_block);
+
+        AddStakingAccountFixture::default().call_and_assert(Ok(()));
+
+        RemoveStakingAccountFixture::default().call_and_assert(Ok(()));
+
+        EventFixture::assert_last_crate_event(Event::<Test>::StakingAccountRemoved(
+            ALICE_ACCOUNT_ID,
+            ALICE_MEMBER_ID,
+        ));
+    });
+}
+
+#[test]
+fn remove_staking_account_fails_with_bad_origin() {
+    build_test_externalities().execute_with(|| {
+        RemoveStakingAccountFixture::default()
+            .with_origin(RawOrigin::None)
+            .call_and_assert(Err(DispatchError::BadOrigin));
+    });
+}
+
+#[test]
+fn remove_staking_account_fails_with_invalid_member_id() {
+    build_test_externalities().execute_with(|| {
+        let initial_balance = DefaultMembershipPrice::get();
+        set_alice_free_balance(initial_balance);
+
+        assert_ok!(buy_default_membership_as_alice());
+        let invalid_member_id = 222;
+
+        RemoveStakingAccountFixture::default()
+            .with_member_id(invalid_member_id)
+            .call_and_assert(Err(Error::<Test>::MemberProfileNotFound.into()));
+    });
+}
+
+#[test]
+fn remove_staking_account_candidate_fails_with_missing_staking_account_id() {
+    let initial_members = [(ALICE_MEMBER_ID, ALICE_ACCOUNT_ID)];
+
+    build_test_externalities_with_initial_members(initial_members.to_vec()).execute_with(|| {
+        RemoveStakingAccountFixture::default()
+            .call_and_assert(Err(Error::<Test>::StakingAccountDoesntExist.into()));
+    });
+}
+
+#[test]
+fn confirm_staking_account_succeeds() {
+    let initial_members = [(ALICE_MEMBER_ID, ALICE_ACCOUNT_ID)];
+
+    build_test_externalities_with_initial_members(initial_members.to_vec()).execute_with(|| {
+        let starting_block = 1;
+        run_to_block(starting_block);
+
+        AddStakingAccountFixture::default().call_and_assert(Ok(()));
+
+        ConfirmStakingAccountFixture::default().call_and_assert(Ok(()));
+
+        EventFixture::assert_last_crate_event(Event::<Test>::StakingAccountConfirmed(
+            ALICE_ACCOUNT_ID,
+            ALICE_MEMBER_ID,
+        ));
+    });
+}
+
+#[test]
+fn confirm_staking_account_fails_on_double_confirmation() {
+    let initial_members = [(ALICE_MEMBER_ID, ALICE_ACCOUNT_ID)];
+
+    build_test_externalities_with_initial_members(initial_members.to_vec()).execute_with(|| {
+        AddStakingAccountFixture::default().call_and_assert(Ok(()));
+
+        ConfirmStakingAccountFixture::default().call_and_assert(Ok(()));
+        ConfirmStakingAccountFixture::default()
+            .call_and_assert(Err(Error::<Test>::StakingAccountAlreadyConfirmed.into()));
+    });
+}
+
+#[test]
+fn confirm_staking_account_fails_with_bad_origin() {
+    build_test_externalities().execute_with(|| {
+        ConfirmStakingAccountFixture::default()
+            .with_origin(RawOrigin::None)
+            .call_and_assert(Err(Error::<Test>::UnsignedOrigin.into()));
+    });
+}
+
+#[test]
+fn confirm_staking_account_fails_with_invalid_member_id() {
+    build_test_externalities().execute_with(|| {
+        let initial_balance = DefaultMembershipPrice::get();
+        set_alice_free_balance(initial_balance);
+
+        assert_ok!(buy_default_membership_as_alice());
+        let invalid_member_id = 222;
+
+        ConfirmStakingAccountFixture::default()
+            .with_member_id(invalid_member_id)
+            .call_and_assert(Err(Error::<Test>::MemberProfileNotFound.into()));
+    });
+}
+
+#[test]
+fn confirm_staking_account_candidate_fails_with_missing_staking_account_id() {
+    let initial_members = [(ALICE_MEMBER_ID, ALICE_ACCOUNT_ID)];
+
+    build_test_externalities_with_initial_members(initial_members.to_vec()).execute_with(|| {
+        ConfirmStakingAccountFixture::default()
+            .call_and_assert(Err(Error::<Test>::StakingAccountDoesntExist.into()));
+    });
+}
+
+#[test]
+fn is_member_staking_account_works() {
+    let initial_members = [(ALICE_MEMBER_ID, ALICE_ACCOUNT_ID)];
+
+    build_test_externalities_with_initial_members(initial_members.to_vec()).execute_with(|| {
+        // Before adding candidate should be false.
+        assert_eq!(
+            Membership::is_member_staking_account(&ALICE_MEMBER_ID, &ALICE_ACCOUNT_ID),
+            false
+        );
+        AddStakingAccountFixture::default().call_and_assert(Ok(()));
+
+        // After adding but before confirmation of the candidate should be false.
+        assert_eq!(
+            Membership::is_member_staking_account(&ALICE_MEMBER_ID, &ALICE_ACCOUNT_ID),
+            false
+        );
+        ConfirmStakingAccountFixture::default().call_and_assert(Ok(()));
+
+        // After confirmation of the candidate should be true.
+        assert_eq!(
+            Membership::is_member_staking_account(&ALICE_MEMBER_ID, &ALICE_ACCOUNT_ID),
+            true
+        );
+
+        // After removing of the staking account should be false.
+        RemoveStakingAccountFixture::default().call_and_assert(Ok(()));
+        assert_eq!(
+            Membership::is_member_staking_account(&ALICE_MEMBER_ID, &ALICE_ACCOUNT_ID),
+            false
+        );
+    });
+}

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

@@ -221,6 +221,7 @@ impl working_group::Trait<ContentDirectoryWorkingGroupInstance> for Test {
     type Event = ();
     type MaxWorkerNumberLimit = MaxWorkerNumberLimit;
     type StakingHandler = StakingManager<Self, LockId1>;
+    type StakingAccountValidator = membership::Module<Test>;
     type MemberOriginValidator = ();
     type MinUnstakingPeriodLimit = ();
     type RewardPeriod = ();
@@ -303,6 +304,7 @@ impl working_group::Trait<StorageWorkingGroupInstance> for Test {
     type Event = ();
     type MaxWorkerNumberLimit = MaxWorkerNumberLimit;
     type StakingHandler = StakingManager<Self, LockId2>;
+    type StakingAccountValidator = membership::Module<Test>;
     type MemberOriginValidator = ();
     type MinUnstakingPeriodLimit = ();
     type RewardPeriod = ();
@@ -454,6 +456,8 @@ impl council::Trait for Test {
     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,
@@ -464,6 +468,12 @@ impl council::Trait for Test {
     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;

+ 14 - 0
runtime-modules/proposals/engine/src/benchmarking.rs

@@ -98,6 +98,7 @@ fn member_funded_account<T: Trait + membership::Trait>(
         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
@@ -109,6 +110,19 @@ fn member_funded_account<T: Trait + membership::Trait>(
     );
     let _ = Balances::<T>::make_free_balance_be(&account_id, T::Balance::max_value());
 
+    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)
 }
 

+ 2 - 0
runtime-modules/proposals/engine/src/tests/mock/mod.rs

@@ -340,6 +340,8 @@ impl council::Trait for Test {
     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,

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

@@ -147,6 +147,7 @@ impl working_group::Trait<StorageWorkingGroupInstance> for Test {
     type Event = MetaEvent;
     type MaxWorkerNumberLimit = MaxWorkerNumberLimit;
     type StakingHandler = StakingManager<Self, LockId1>;
+    type StakingAccountValidator = membership::Module<Test>;
     type MemberOriginValidator = ();
     type MinUnstakingPeriodLimit = ();
     type RewardPeriod = ();

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

@@ -18,7 +18,6 @@ sp-core = { package = 'sp-core', default-features = false, git = 'https://github
 codec = { package = 'parity-scale-codec', version = '1.3.1', default-features = false, features = ['derive'] }
 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'}
 
 [features]
 default = ['std']

+ 1 - 14
runtime-modules/staking-handler/src/lib.rs

@@ -42,9 +42,6 @@ pub trait StakingHandler<AccountId, Balance, MemberId> {
     /// 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;
 
@@ -155,17 +152,7 @@ impl<
         Ok(())
     }
 
-    // Membership support for staking accounts required.
-    fn is_member_staking_account(
-        _member_id: &<T as common::Trait>::MemberId,
-        _account_id: &<T as frame_system::Trait>::AccountId,
-    ) -> bool {
-        true
-    }
-
-    fn is_account_free_of_conflicting_stakes(
-        account_id: &<T as frame_system::Trait>::AccountId,
-    ) -> bool {
+    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();

+ 1 - 29
runtime-modules/staking-handler/src/mock.rs

@@ -6,17 +6,13 @@ use sp_core::H256;
 use sp_runtime::{
     testing::Header,
     traits::{BlakeTwo256, IdentityLookup},
-    DispatchResult, Perbill,
+    Perbill,
 };
 
 impl_outer_origin! {
     pub enum Origin for Test {}
 }
 
-mod membership_mod {
-    pub use membership::Event;
-}
-
 parameter_types! {
     pub const BlockHashCount: u64 = 250;
     pub const MaximumBlockWeight: u32 = 1024;
@@ -75,13 +71,6 @@ impl common::Trait for Test {
     type ActorId = u64;
 }
 
-impl membership::Trait for Test {
-    type Event = ();
-    type DefaultMembershipPrice = DefaultMembershipPrice;
-    type WorkingGroup = ();
-    type DefaultInitialInvitationBalance = ();
-}
-
 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
@@ -92,23 +81,6 @@ impl LockComparator<<Test as pallet_balances::Trait>::Balance> for Test {
     }
 }
 
-impl common::currency::GovernanceCurrency for Test {
-    type Currency = Balances;
-}
-
-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 pallet_timestamp::Trait for Test {
     type Moment = u64;
     type OnTimestampSet = ();

+ 0 - 15
runtime-modules/staking-handler/src/test.rs

@@ -177,21 +177,6 @@ fn is_enough_balance_for_stake_succeeds() {
     });
 }
 
-// Test stub: not implemented yet.
-#[ignore]
-#[test]
-fn is_member_staking_account_succeeds() {
-    build_test_externalities().execute_with(|| {
-        let account_id = 1;
-        let member_id = 1;
-
-        assert!(TestStakingManager::is_member_staking_account(
-            &member_id,
-            &account_id
-        ));
-    });
-}
-
 #[test]
 fn set_stake_succeeds() {
     build_test_externalities().execute_with(|| {

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

@@ -164,7 +164,8 @@ pub struct WorkingGroupWeightInfo;
 impl working_group::Trait<StorageWorkingGroupInstance> for Test {
     type Event = MetaEvent;
     type MaxWorkerNumberLimit = MaxWorkerNumberLimit;
-    type StakingHandler = StakingManager<Self, LockId>;
+    type StakingHandler = staking_handler::StakingManager<Self, LockId>;
+    type StakingAccountValidator = membership::Module<Test>;
     type MemberOriginValidator = ();
     type MinUnstakingPeriodLimit = ();
     type RewardPeriod = ();

+ 15 - 1
runtime-modules/working-group/src/benchmarking.rs

@@ -195,7 +195,21 @@ fn member_funded_account<T: Trait<I> + membership::Trait, I: Instance>(
 
     let _ = Balances::<T>::make_free_balance_be(&account_id, BalanceOf::<T>::max_value());
 
-    (account_id, T::MemberId::from(id.try_into().unwrap()))
+    let member_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 force_missed_reward<T: Trait<I>, I: Instance>() {

+ 8 - 2
runtime-modules/working-group/src/lib.rs

@@ -57,7 +57,7 @@ use types::{ApplicationInfo, WorkerInfo};
 pub use checks::{ensure_origin_is_active_leader, ensure_worker_exists, ensure_worker_signed};
 
 use common::origin::ActorOriginValidator;
-use common::MemberId;
+use common::{MemberId, StakingAccountValidator};
 use frame_support::dispatch::DispatchResult;
 use staking_handler::StakingHandler;
 
@@ -104,6 +104,9 @@ pub trait Trait<I: Instance = DefaultInstance>:
     /// Stakes and balance locks handler.
     type StakingHandler: StakingHandler<Self::AccountId, BalanceOf<Self>, MemberId<Self>>;
 
+    /// Validates staking account ownership for a member.
+    type StakingAccountValidator: common::StakingAccountValidator<Self>;
+
     /// Validates member id and origin combination
     type MemberOriginValidator: ActorOriginValidator<Self::Origin, MemberId<Self>, Self::AccountId>;
 
@@ -385,7 +388,10 @@ decl_module! {
             // Checks external conditions for staking.
             if let Some(sp) = p.stake_parameters.clone() {
                 ensure!(
-                    T::StakingHandler::is_member_staking_account(&p.member_id, &sp.staking_account_id),
+                    T::StakingAccountValidator::is_member_staking_account(
+                        &p.member_id,
+                        &sp.staking_account_id
+                    ),
                     Error::<T, I>::InvalidStakingAccountForMember
                 );
 

+ 9 - 2
runtime-modules/working-group/src/tests/mock.rs

@@ -9,7 +9,7 @@ use sp_runtime::{
     traits::{BlakeTwo256, IdentityLookup},
     Perbill,
 };
-use staking_handler::{LockComparator, StakingManager};
+use staking_handler::LockComparator;
 
 use crate::{DefaultInstance, Module, Trait};
 
@@ -128,13 +128,20 @@ parameter_types! {
 impl Trait for Test {
     type Event = TestEvent;
     type MaxWorkerNumberLimit = MaxWorkerNumberLimit;
-    type StakingHandler = StakingManager<Self, LockId>;
+    type StakingHandler = staking_handler::StakingManager<Self, LockId>;
+    type StakingAccountValidator = ();
     type MemberOriginValidator = ();
     type MinUnstakingPeriodLimit = MinUnstakingPeriodLimit;
     type RewardPeriod = RewardPeriod;
     type WeightInfo = ();
 }
 
+impl common::StakingAccountValidator<Test> for () {
+    fn is_member_staking_account(_: &u64, _: &u64) -> bool {
+        true
+    }
+}
+
 impl crate::WeightInfo for () {
     fn on_initialize_leaving(_: u32) -> Weight {
         0

+ 6 - 0
runtime/src/lib.rs

@@ -562,6 +562,8 @@ impl council::Trait for Runtime {
     type CandidacyLock = StakingManager<Self, CandidacyLockId>;
     type CouncilorLock = StakingManager<Self, CouncilorLockId>;
 
+    type StakingAccountValidator = Members;
+
     type ElectedMemberRewardPerBlock = ElectedMemberRewardPerBlock;
     type ElectedMemberRewardPeriod = ElectedMemberRewardPeriod;
 
@@ -748,6 +750,7 @@ impl working_group::Trait<ForumWorkingGroupInstance> for Runtime {
     type Event = Event;
     type MaxWorkerNumberLimit = MaxWorkerNumberLimit;
     type StakingHandler = ForumWorkingGroupStakingManager;
+    type StakingAccountValidator = Members;
     type MemberOriginValidator = MembershipOriginValidator<Self>;
     type MinUnstakingPeriodLimit = MinUnstakingPeriodLimit;
     type RewardPeriod = ForumWorkingGroupRewardPeriod;
@@ -758,6 +761,7 @@ impl working_group::Trait<StorageWorkingGroupInstance> for Runtime {
     type Event = Event;
     type MaxWorkerNumberLimit = MaxWorkerNumberLimit;
     type StakingHandler = StorageWorkingGroupStakingManager;
+    type StakingAccountValidator = Members;
     type MemberOriginValidator = MembershipOriginValidator<Self>;
     type MinUnstakingPeriodLimit = MinUnstakingPeriodLimit;
     type RewardPeriod = StorageWorkingGroupRewardPeriod;
@@ -768,6 +772,7 @@ impl working_group::Trait<ContentDirectoryWorkingGroupInstance> for Runtime {
     type Event = Event;
     type MaxWorkerNumberLimit = MaxWorkerNumberLimit;
     type StakingHandler = ContentDirectoryWorkingGroupStakingManager;
+    type StakingAccountValidator = Members;
     type MemberOriginValidator = MembershipOriginValidator<Self>;
     type MinUnstakingPeriodLimit = MinUnstakingPeriodLimit;
     type RewardPeriod = ContentWorkingGroupRewardPeriod;
@@ -778,6 +783,7 @@ impl working_group::Trait<MembershipWorkingGroupInstance> for Runtime {
     type Event = Event;
     type MaxWorkerNumberLimit = MaxWorkerNumberLimit;
     type StakingHandler = MembershipWorkingGroupStakingManager;
+    type StakingAccountValidator = Members;
     type MemberOriginValidator = MembershipOriginValidator<Self>;
     type MinUnstakingPeriodLimit = MinUnstakingPeriodLimit;
     type RewardPeriod = MembershipRewardPeriod;

+ 19 - 1
runtime/src/tests/mod.rs

@@ -35,7 +35,8 @@ pub(crate) fn initial_test_ext() -> sp_io::TestExternalities {
 
 fn get_account_membership(account: AccountId32, i: usize) -> u64 {
     if !Membership::is_member_account(&account) {
-        insert_member(account);
+        insert_member(account.clone());
+        set_staking_account(account, i as u64);
     }
 
     i as u64
@@ -53,7 +54,9 @@ pub(crate) fn elect_council(council: Vec<AccountId32>, cycle_id: u64) {
             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,
@@ -162,6 +165,21 @@ 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) {

+ 6 - 2
runtime/src/tests/proposals_integration/mod.rs

@@ -19,7 +19,10 @@ 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;
@@ -33,7 +36,8 @@ 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);
     }
 }
 

+ 1 - 0
runtime/src/tests/proposals_integration/working_group_proposals.rs

@@ -549,6 +549,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);
 
         let old_balance = Balances::usable_balance(&account_id.into());
+
         let apply_result = WorkingGroupInstance::<T, I>::apply_on_opening(
             RawOrigin::Signed(account_id.into()).into(),
             working_group::ApplyOnOpeningParameters::<T> {