Browse Source

Membership working group integration, merge olympia changes

iorveth 4 years ago
parent
commit
f8b24a5688
71 changed files with 2051 additions and 1335 deletions
  1. 21 11
      .github/workflows/joystream-node-benchmarks.yml
  2. 5 2
      Cargo.lock
  3. 1 1
      node/Cargo.toml
  4. 1 1
      node/src/chain_spec/forum_config.rs
  5. 2 0
      runtime-modules/common/Cargo.toml
  6. 0 27
      runtime-modules/common/src/currency.rs
  7. 0 1
      runtime-modules/common/src/lib.rs
  8. 18 4
      runtime-modules/common/src/origin.rs
  9. 16 2
      runtime-modules/common/src/working_group.rs
  10. 27 9
      runtime-modules/content-directory/src/lib.rs
  11. 14 9
      runtime-modules/content-directory/src/mock.rs
  12. 4 24
      runtime-modules/content-directory/src/permissions.rs
  13. 4 2
      runtime-modules/council/Cargo.toml
  14. 2 7
      runtime-modules/council/src/benchmarking.rs
  15. 53 40
      runtime-modules/council/src/lib.rs
  16. 62 26
      runtime-modules/council/src/mock.rs
  17. 134 26
      runtime-modules/council/src/tests.rs
  18. 4 2
      runtime-modules/forum/Cargo.toml
  19. 77 43
      runtime-modules/forum/src/benchmarking.rs
  20. 50 47
      runtime-modules/forum/src/lib.rs
  21. 36 30
      runtime-modules/forum/src/mock.rs
  22. 1 1
      runtime-modules/membership/Cargo.toml
  23. 49 25
      runtime-modules/membership/src/benchmarking.rs
  24. 71 51
      runtime-modules/membership/src/lib.rs
  25. 42 5
      runtime-modules/membership/src/tests/mock.rs
  26. 79 26
      runtime-modules/membership/src/tests/mod.rs
  27. 4 7
      runtime-modules/proposals/codex/src/lib.rs
  28. 45 22
      runtime-modules/proposals/codex/src/tests/mock.rs
  29. 12 27
      runtime-modules/proposals/codex/src/tests/mod.rs
  30. 3 3
      runtime-modules/proposals/codex/src/types.rs
  31. 1 0
      runtime-modules/proposals/discussion/Cargo.toml
  32. 1 1
      runtime-modules/proposals/discussion/src/benchmarking.rs
  33. 12 8
      runtime-modules/proposals/discussion/src/lib.rs
  34. 42 10
      runtime-modules/proposals/discussion/src/tests/mock.rs
  35. 70 23
      runtime-modules/proposals/engine/src/benchmarking.rs
  36. 65 20
      runtime-modules/proposals/engine/src/lib.rs
  37. 45 22
      runtime-modules/proposals/engine/src/tests/mock/mod.rs
  38. 171 1
      runtime-modules/proposals/engine/src/tests/mod.rs
  39. 3 0
      runtime-modules/proposals/engine/src/types/proposal_statuses.rs
  40. 4 1
      runtime-modules/referendum/Cargo.toml
  41. 11 13
      runtime-modules/referendum/src/benchmarking.rs
  42. 134 93
      runtime-modules/referendum/src/lib.rs
  43. 57 28
      runtime-modules/referendum/src/mock.rs
  44. 27 0
      runtime-modules/referendum/src/tests.rs
  45. 23 3
      runtime-modules/service-discovery/src/mock.rs
  46. 13 6
      runtime-modules/staking-handler/src/lib.rs
  47. 4 4
      runtime-modules/storage/src/data_directory.rs
  48. 1 1
      runtime-modules/storage/src/data_object_storage_registry.rs
  49. 2 2
      runtime-modules/storage/src/data_object_type_registry.rs
  50. 1 1
      runtime-modules/storage/src/tests/data_directory.rs
  51. 23 3
      runtime-modules/storage/src/tests/mock.rs
  52. 70 24
      runtime-modules/working-group/src/benchmarking.rs
  53. 53 17
      runtime-modules/working-group/src/lib.rs
  54. 16 13
      runtime-modules/working-group/src/tests/fixtures.rs
  55. 18 11
      runtime-modules/working-group/src/tests/mock.rs
  56. 21 51
      runtime-modules/working-group/src/tests/mod.rs
  57. 0 11
      runtime-modules/working-group/src/types.rs
  58. 42 55
      runtime/src/constants.rs
  59. 21 0
      runtime/src/integration/proposals/council_manager.rs
  60. 0 147
      runtime/src/integration/proposals/council_origin_validator.rs
  61. 0 104
      runtime/src/integration/proposals/membership_origin_validator.rs
  62. 2 4
      runtime/src/integration/proposals/mod.rs
  63. 3 2
      runtime/src/integration/proposals/proposal_encoder.rs
  64. 32 56
      runtime/src/lib.rs
  65. 45 23
      runtime/src/runtime_api.rs
  66. 18 8
      runtime/src/tests/mod.rs
  67. 17 4
      runtime/src/tests/proposals_integration/mod.rs
  68. 106 53
      runtime/src/tests/proposals_integration/working_group_proposals.rs
  69. 26 18
      runtime/src/weights/proposals_engine.rs
  70. 6 7
      scripts/generate-weights.sh
  71. 8 6
      setup.sh

+ 21 - 11
.github/workflows/joystream-node-checks.yml → .github/workflows/joystream-node-benchmarks.yml

@@ -1,10 +1,10 @@
-name: joystream-node-checks
+name: joystream-node
 on:
   pull_request:
 
 jobs:
-  checks:
-    name: joystream-node checks
+  benchmarking:
+    name: Benchmarking
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v1
@@ -27,11 +27,21 @@ jobs:
       # - name: Check version modified correctly
       #   if: env.GIT_DIFF
 
-      # This Building natively is not really necessary because we have the docker build which
-      # hapens in the run-network-tests workflow which is sufficient!
-      # - name: Build if runtime was modified
-      #   run: |
-      #     ./setup.sh
-      #     yarn cargo-checks
-      #     yarn cargo-build
-      #   if: env.GIT_DIFF
+      - name: Toolchains
+        run: |
+          ./setup.sh
+        if: env.GIT_DIFF
+
+      - name: Build
+        run: |
+          pushd node
+          WASM_BUILD_TOOLCHAIN=nightly-2020-10-06 cargo build --release --features runtime-benchmarks
+          popd
+        if: env.GIT_DIFF
+
+      - name: Generate Weights
+        run: |
+          ./scripts/generate-weights.sh
+          # Show any changes in computed weights
+          git diff
+        if: env.GIT_DIFF

+ 5 - 2
Cargo.lock

@@ -2275,7 +2275,7 @@ dependencies = [
 
 [[package]]
 name = "joystream-node"
-version = "4.0.0"
+version = "4.0.1"
 dependencies = [
  "frame-benchmarking",
  "frame-benchmarking-cli",
@@ -3739,6 +3739,7 @@ version = "4.0.0"
 dependencies = [
  "frame-support",
  "frame-system",
+ "pallet-balances",
  "pallet-timestamp",
  "parity-scale-codec",
  "serde",
@@ -3781,7 +3782,7 @@ dependencies = [
 
 [[package]]
 name = "pallet-council"
-version = "1.0.1"
+version = "1.1.0"
 dependencies = [
  "frame-benchmarking",
  "frame-support",
@@ -3991,6 +3992,7 @@ dependencies = [
  "pallet-balances",
  "pallet-common",
  "pallet-membership",
+ "pallet-staking-handler",
  "pallet-timestamp",
  "parity-scale-codec",
  "serde",
@@ -4046,6 +4048,7 @@ dependencies = [
  "pallet-balances",
  "pallet-common",
  "pallet-membership",
+ "pallet-staking-handler",
  "pallet-timestamp",
  "parity-scale-codec",
  "rand 0.7.3",

+ 1 - 1
node/Cargo.toml

@@ -3,7 +3,7 @@ authors = ['Joystream contributors']
 build = 'build.rs'
 edition = '2018'
 name = 'joystream-node'
-version = '4.0.0'
+version = '4.0.1'
 default-run = "joystream-node"
 
 [[bin]]

+ 1 - 1
node/src/chain_spec/forum_config.rs

@@ -9,7 +9,7 @@ use sp_core::H256;
 use std::{fs, path::Path};
 
 type CategoryId = <Runtime as forum::Trait>::CategoryId;
-type ForumUserId = <Runtime as forum::Trait>::ForumUserId;
+type ForumUserId = forum::ForumUserId<Runtime>;
 type ModeratorId = forum::ModeratorId<Runtime>;
 type ThreadOf = (
     CategoryId,

+ 2 - 0
runtime-modules/common/Cargo.toml

@@ -14,6 +14,7 @@ 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'}
 pallet-timestamp = { package = 'pallet-timestamp', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
 sp-arithmetic = { package = 'sp-arithmetic', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
+balances = { package = 'pallet-balances', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
 
 
 [features]
@@ -28,4 +29,5 @@ std = [
 	'frame-system/std',
 	'pallet-timestamp/std',
 	'sp-arithmetic/std',
+	'balances/std',
 ]

+ 0 - 27
runtime-modules/common/src/currency.rs

@@ -1,27 +0,0 @@
-use sp_runtime::traits::Convert;
-
-/// A structure that converts the currency type into a lossy u64
-/// And back from u128
-pub struct CurrencyToVoteHandler;
-
-impl Convert<u128, u64> for CurrencyToVoteHandler {
-    fn convert(x: u128) -> u64 {
-        if x >> 96 == 0 {
-            x as u64
-        } else {
-            u64::max_value()
-        }
-    }
-}
-
-impl Convert<u128, u128> for CurrencyToVoteHandler {
-    fn convert(x: u128) -> u128 {
-        // if it practically fits in u64
-        if x >> 64 == 0 {
-            x
-        } else {
-            // 0000_0000_FFFF_FFFF_FFFF_FFFF_0000_0000
-            u64::max_value() as u128
-        }
-    }
-}

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

@@ -2,7 +2,6 @@
 #![cfg_attr(not(feature = "std"), no_std)]
 
 pub mod constraints;
-pub mod currency;
 pub mod origin;
 pub mod working_group;
 

+ 18 - 4
runtime-modules/common/src/origin.rs

@@ -1,5 +1,19 @@
-/// Abstract validator for the origin(account_id) and actor_id (eg.: thread author id).
-pub trait ActorOriginValidator<Origin, ActorId, AccountId> {
-    /// Check for valid combination of origin and actor_id.
-    fn ensure_actor_origin(origin: Origin, actor_id: ActorId) -> Result<AccountId, &'static str>;
+use frame_support::dispatch::{DispatchError, DispatchResult};
+
+/// Council validator for the origin(account_id) and member_id.
+pub trait CouncilOriginValidator<Origin, MemberId, AccountId> {
+    /// Check for valid combination of origin and member_id for a councilor.
+    fn ensure_member_consulate(origin: Origin, member_id: MemberId) -> DispatchResult;
+}
+
+/// Membership validator for the origin(account_id) and member_id (eg.: thread author id).
+pub trait MemberOriginValidator<Origin, MemberId, AccountId> {
+    /// Check for valid combination of origin and member_id.
+    fn ensure_member_controller_account_origin(
+        origin: Origin,
+        member_id: MemberId,
+    ) -> Result<AccountId, DispatchError>;
+
+    /// Verifies that provided account is the controller account of the member
+    fn is_member_controller_account(member_id: &MemberId, account_id: &AccountId) -> bool;
 }

+ 16 - 2
runtime-modules/common/src/working_group.rs

@@ -19,8 +19,8 @@ pub enum WorkingGroup {
     Membership,
 }
 
-/// Working group interface to use in the in the pallets with working groups.
-pub trait WorkingGroupIntegration<T: crate::Trait> {
+/// Working group interface to work with its members - workers and leaders.
+pub trait WorkingGroupAuthenticator<T: crate::Trait> {
     /// Validate origin for the worker.
     fn ensure_worker_origin(origin: T::Origin, worker_id: &T::ActorId) -> DispatchResult;
 
@@ -36,3 +36,17 @@ pub trait WorkingGroupIntegration<T: crate::Trait> {
     /// Verifies that given account ID and worker ID belong to the working group member.
     fn is_worker_account_id(account_id: &T::AccountId, worker_id: &T::ActorId) -> bool;
 }
+
+/// Working group interface to work with the its budget.
+pub trait WorkingGroupBudgetHandler<T: balances::Trait> {
+    /// Returns current working group balance.
+    fn get_budget() -> T::Balance;
+
+    /// Sets new working broup balance
+    fn set_budget(new_value: T::Balance);
+}
+
+pub trait MembershipWorkingGroupHelper<T: crate::Trait> {
+    /// Set membership working group lead
+    fn insert_a_lead(opening_id: u32, caller_id: T::AccountId, member_id: T::MemberId) -> T::ActorId;
+}

+ 27 - 9
runtime-modules/content-directory/src/lib.rs

@@ -158,6 +158,8 @@ use sp_std::collections::{btree_map::BTreeMap, btree_set::BTreeSet};
 use sp_std::vec;
 use sp_std::vec::Vec;
 
+use common::origin::MemberOriginValidator;
+
 pub use errors::Error;
 
 use core::debug_assert;
@@ -176,7 +178,7 @@ pub type EntityOf<T> = Entity<
 
 /// Type simplification
 pub type ClassOf<T> =
-    Class<<T as Trait>::EntityId, <T as Trait>::ClassId, <T as ActorAuthenticator>::CuratorGroupId>;
+    Class<<T as Trait>::EntityId, <T as Trait>::ClassId, <T as Trait>::CuratorGroupId>;
 
 /// Type simplification
 pub type StoredPropertyValueOf<T> = StoredPropertyValue<
@@ -189,7 +191,7 @@ pub type StoredPropertyValueOf<T> = StoredPropertyValue<
 pub type CuratorId<T> = common::ActorId<T>;
 
 /// Module configuration trait for this Substrate module.
-pub trait Trait: frame_system::Trait + ActorAuthenticator + common::Trait {
+pub trait Trait: frame_system::Trait + common::Trait {
     /// The overarching event type.
     type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;
 
@@ -235,6 +237,19 @@ pub trait Trait: frame_system::Trait + ActorAuthenticator + common::Trait {
         + PartialEq
         + Ord;
 
+    /// Curator group identifier
+    type CuratorGroupId: Parameter
+        + Member
+        + BaseArithmetic
+        + Codec
+        + Default
+        + Copy
+        + Clone
+        + MaybeSerializeDeserialize
+        + Eq
+        + PartialEq
+        + Ord;
+
     /// Security/configuration constraints
 
     /// Type, representing min & max property name length constraints
@@ -283,7 +298,14 @@ pub trait Trait: frame_system::Trait + ActorAuthenticator + common::Trait {
     type IndividualEntitiesCreationLimit: Get<Self::EntityId>;
 
     /// Working group pallet integration.
-    type WorkingGroup: common::working_group::WorkingGroupIntegration<Self>;
+    type WorkingGroup: common::working_group::WorkingGroupAuthenticator<Self>;
+
+    /// Validates member id and origin combination
+    type MemberOriginValidator: MemberOriginValidator<
+        Self::Origin,
+        common::MemberId<Self>,
+        Self::AccountId,
+    >;
 }
 
 decl_storage! {
@@ -2880,18 +2902,14 @@ impl<T: Trait> Module<T> {
 decl_event!(
     pub enum Event<T>
     where
-        CuratorGroupId = <T as ActorAuthenticator>::CuratorGroupId,
+        CuratorGroupId = <T as Trait>::CuratorGroupId,
         CuratorId = CuratorId<T>,
         ClassId = <T as Trait>::ClassId,
         EntityId = <T as Trait>::EntityId,
         EntityController = EntityController<<T as common::Trait>::MemberId>,
         EntityCreationVoucher = EntityCreationVoucher<T>,
         Status = bool,
-        Actor = Actor<
-            <T as ActorAuthenticator>::CuratorGroupId,
-            CuratorId<T>,
-            <T as common::Trait>::MemberId,
-        >,
+        Actor = Actor<<T as Trait>::CuratorGroupId, CuratorId<T>, <T as common::Trait>::MemberId>,
         Nonce = <T as Trait>::Nonce,
         SideEffects = Option<ReferenceCounterSideEffects<T>>,
         SideEffect = Option<(<T as Trait>::EntityId, EntityReferenceCounterSideEffect)>,

+ 14 - 9
runtime-modules/content-directory/src/mock.rs

@@ -23,7 +23,7 @@ pub type Nonce = <Runtime as Trait>::Nonce;
 pub type Hashed = <Runtime as frame_system::Trait>::Hash;
 
 pub type TestCuratorId = CuratorId<Runtime>;
-pub type CuratorGroupId = <Runtime as ActorAuthenticator>::CuratorGroupId;
+pub type CuratorGroupId = <Runtime as Trait>::CuratorGroupId;
 pub type MemberId = <Runtime as common::Trait>::MemberId;
 
 /// Origins
@@ -252,25 +252,27 @@ impl Trait for Runtime {
     type Nonce = u64;
     type ClassId = u64;
     type EntityId = u64;
+    type CuratorGroupId = u64;
     type PropertyNameLengthConstraint = PropertyNameLengthConstraint;
     type PropertyDescriptionLengthConstraint = PropertyDescriptionLengthConstraint;
     type ClassNameLengthConstraint = ClassNameLengthConstraint;
     type ClassDescriptionLengthConstraint = ClassDescriptionLengthConstraint;
     type MaxNumberOfClasses = MaxNumberOfClasses;
     type MaxNumberOfMaintainersPerClass = MaxNumberOfMaintainersPerClass;
+    type MaxNumberOfCuratorsPerGroup = MaxNumberOfCuratorsPerGroup;
     type MaxNumberOfSchemasPerClass = MaxNumberOfSchemasPerClass;
     type MaxNumberOfPropertiesPerSchema = MaxNumberOfPropertiesPerSchema;
-    type MaxNumberOfEntitiesPerClass = MaxNumberOfEntitiesPerClass;
-    type MaxNumberOfCuratorsPerGroup = MaxNumberOfCuratorsPerGroup;
     type MaxNumberOfOperationsDuringAtomicBatching = MaxNumberOfOperationsDuringAtomicBatching;
     type VecMaxLengthConstraint = VecMaxLengthConstraint;
     type TextMaxLengthConstraint = TextMaxLengthConstraint;
     type HashedTextMaxLengthConstraint = HashedTextMaxLengthConstraint;
+    type MaxNumberOfEntitiesPerClass = MaxNumberOfEntitiesPerClass;
     type IndividualEntitiesCreationLimit = IndividualEntitiesCreationLimit;
     type WorkingGroup = ();
+    type MemberOriginValidator = ();
 }
 
-impl common::working_group::WorkingGroupIntegration<Runtime> for () {
+impl common::working_group::WorkingGroupAuthenticator<Runtime> for () {
     fn ensure_worker_origin(
         _origin: <Runtime as frame_system::Trait>::Origin,
         _worker_id: &<Runtime as common::Trait>::ActorId,
@@ -309,12 +311,15 @@ impl common::Trait for Runtime {
     type ActorId = u64;
 }
 
-impl ActorAuthenticator for Runtime {
-    type CuratorGroupId = u64;
-
-    // Consider lazy_static crate?
+impl common::origin::MemberOriginValidator<Origin, u64, u64> for () {
+    fn ensure_member_controller_account_origin(
+        _origin: Origin,
+        _member_id: u64,
+    ) -> Result<u64, DispatchError> {
+        unimplemented!()
+    }
 
-    fn is_member(member_id: &Self::MemberId, account_id: &Self::AccountId) -> bool {
+    fn is_member_controller_account(member_id: &u64, account_id: &u64) -> bool {
         let unknown_member_account_id = ensure_signed(Origin::signed(UNKNOWN_ORIGIN)).unwrap();
         *member_id < MaxNumberOfEntitiesPerClass::get() && unknown_member_account_id != *account_id
     }

+ 4 - 24
runtime-modules/content-directory/src/permissions.rs

@@ -12,32 +12,11 @@ pub use crate::errors::*;
 use crate::*;
 pub use codec::{Codec, Decode, Encode};
 use core::fmt::Debug;
-use frame_support::{ensure, Parameter};
+use frame_support::ensure;
 #[cfg(feature = "std")]
 pub use serde::{Deserialize, Serialize};
-use sp_arithmetic::traits::BaseArithmetic;
-use sp_runtime::traits::{MaybeSerializeDeserialize, Member};
 
-use common::working_group::WorkingGroupIntegration;
-
-/// Model of authentication manager.
-pub trait ActorAuthenticator: frame_system::Trait + common::Trait {
-    /// Curator group identifier
-    type CuratorGroupId: Parameter
-        + Member
-        + BaseArithmetic
-        + Codec
-        + Default
-        + Copy
-        + Clone
-        + MaybeSerializeDeserialize
-        + Eq
-        + PartialEq
-        + Ord;
-
-    /// Authorize actor as member
-    fn is_member(member_id: &Self::MemberId, account_id: &Self::AccountId) -> bool;
-}
+use common::working_group::WorkingGroupAuthenticator;
 
 /// Ensure curator authorization performed succesfully
 pub fn ensure_curator_auth_success<T: Trait>(
@@ -57,9 +36,10 @@ pub fn ensure_member_auth_success<T: Trait>(
     account_id: &T::AccountId,
 ) -> Result<(), Error<T>> {
     ensure!(
-        T::is_member(member_id, account_id),
+        T::MemberOriginValidator::is_member_controller_account(member_id, &account_id),
         Error::<T>::MemberAuthFailed
     );
+
     Ok(())
 }
 

+ 4 - 2
runtime-modules/council/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = 'pallet-council'
-version = '1.0.1'
+version = '1.1.0'
 authors = ['Joystream contributors']
 edition = '2018'
 
@@ -16,13 +16,13 @@ sp-std = { package = 'sp-std', default-features = false, git = 'https://github.c
 common = { package = 'pallet-common', default-features = false, path = '../common'}
 referendum = { package = 'pallet-referendum', default-features = false, path = '../referendum'}
 staking-handler = { package = 'pallet-staking-handler', default-features = false, path = '../staking-handler'}
+balances = { package = 'pallet-balances', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
 
 #Benchmark dependencies
 frame-benchmarking = { package = 'frame-benchmarking', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca', optional = true}
 membership = { package = 'pallet-membership', default-features = false, path = '../membership', optional = true}
 
 [dev-dependencies]
-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'}
@@ -42,5 +42,7 @@ std = [
     'frame-system/std',
     'staking-handler/std',
     'common/std',
+    'balances/std',
+    'staking-handler/std',
 ]
 

+ 2 - 7
runtime-modules/council/src/benchmarking.rs

@@ -1,7 +1,7 @@
 #![cfg(feature = "runtime-benchmarks")]
 use super::*;
 use frame_benchmarking::{account, benchmarks, Zero};
-use frame_support::traits::{OnFinalize, OnInitialize};
+use frame_support::traits::{Currency, OnFinalize, OnInitialize};
 use frame_system::EventRecord;
 use frame_system::Module as System;
 use frame_system::RawOrigin;
@@ -68,12 +68,7 @@ fn assert_in_events<T: Trait>(generic_event: <T as Trait>::Event) {
 }
 
 fn make_free_balance_be<T: Trait>(account_id: &T::AccountId, balance: Balance<T>) {
-    <<T as Trait>::Referendum as ReferendumManager<
-        <T as frame_system::Trait>::Origin,
-        <T as frame_system::Trait>::AccountId,
-        <T as common::Trait>::MemberId,
-        <T as frame_system::Trait>::Hash,
-    >>::Currency::make_free_balance_be(&account_id, balance);
+    balances::Module::<T>::make_free_balance_be(&account_id, balance);
 }
 
 fn start_announcing_period<T: Trait>() {

+ 53 - 40
runtime-modules/council/src/lib.rs

@@ -47,12 +47,14 @@ use frame_support::weights::Weight;
 use frame_support::{decl_error, decl_event, decl_module, decl_storage, ensure, error::BadOrigin};
 
 use core::marker::PhantomData;
-use frame_system::{ensure_root, ensure_signed};
+use frame_support::dispatch::DispatchResult;
+use frame_system::ensure_root;
 #[cfg(feature = "std")]
 use serde::{Deserialize, Serialize};
 use sp_runtime::traits::{Hash, SaturatedConversion, Saturating};
 use sp_std::vec::Vec;
 
+use common::origin::{CouncilOriginValidator, MemberOriginValidator};
 use common::StakingAccountValidator;
 use referendum::{CastVote, OptionResult, ReferendumManager};
 use staking_handler::StakingHandler;
@@ -168,12 +170,7 @@ impl<AccountId, MemberId, Balance, Hash, VotePower, BlockNumber>
 
 /////////////////// Type aliases ///////////////////////////////////////////////
 
-pub type Balance<T> = <<<T as Trait>::Referendum as ReferendumManager<
-    <T as frame_system::Trait>::Origin,
-    <T as frame_system::Trait>::AccountId,
-    <T as common::Trait>::MemberId,
-    <T as frame_system::Trait>::Hash,
->>::Currency as Currency<<T as frame_system::Trait>::AccountId>>::Balance;
+pub type Balance<T> = <T as balances::Trait>::Balance;
 pub type VotePowerOf<T> = <<T as Trait>::Referendum as ReferendumManager<
     <T as frame_system::Trait>::Origin,
     <T as frame_system::Trait>::AccountId,
@@ -214,8 +211,10 @@ pub trait WeightInfo {
     fn plan_budget_refill() -> Weight;
 }
 
+type CouncilWeightInfo<T> = <T as Trait>::WeightInfo;
+
 /// The main council trait.
-pub trait Trait: frame_system::Trait + common::Trait {
+pub trait Trait: frame_system::Trait + common::Trait + balances::Trait {
     /// The overarching event type.
     type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;
 
@@ -256,14 +255,15 @@ pub trait Trait: frame_system::Trait + common::Trait {
     /// Weight information for extrinsics in this pallet.
     type WeightInfo: WeightInfo;
 
-    /// Checks that the user account is indeed associated with the member.
-    fn is_council_member_account(
-        membership_id: &Self::MemberId,
-        account_id: &<Self as frame_system::Trait>::AccountId,
-    ) -> bool;
-
     /// Hook called right after the new council is elected.
     fn new_council_elected(elected_members: &[CouncilMemberOf<Self>]);
+
+    /// Validates member id and origin combination
+    type MemberOriginValidator: MemberOriginValidator<
+        Self::Origin,
+        common::MemberId<Self>,
+        Self::AccountId,
+    >;
 }
 
 /// Trait with functions that MUST be called by the runtime with values received from the
@@ -412,6 +412,9 @@ decl_error! {
 
         /// Can't withdraw candidacy outside of the candidacy announcement period.
         CantWithdrawCandidacyNow,
+
+        /// The member is not a councilor.
+        NotCouncilor,
     }
 }
 
@@ -485,7 +488,7 @@ decl_module! {
         /// - db:
         ///    - `O(1)` doesn't depend on the state or parameters
         /// # </weight>
-        #[weight = T::WeightInfo::announce_candidacy()]
+        #[weight = CouncilWeightInfo::<T>::announce_candidacy()]
         pub fn announce_candidacy(
                 origin,
                 membership_id: T::MemberId,
@@ -531,7 +534,7 @@ decl_module! {
         /// - db:
         ///    - `O(1)` doesn't depend on the state or parameters
         /// # </weight>
-        #[weight = T::WeightInfo::release_candidacy_stake()]
+        #[weight = CouncilWeightInfo::<T>::release_candidacy_stake()]
         pub fn release_candidacy_stake(origin, membership_id: T::MemberId)
             -> Result<(), Error<T>> {
             let staking_account_id =
@@ -559,7 +562,7 @@ decl_module! {
         /// - db:
         ///    - `O(1)` doesn't depend on the state or parameters
         /// # </weight>
-        #[weight = T::WeightInfo::withdraw_candidacy()]
+        #[weight = CouncilWeightInfo::<T>::withdraw_candidacy()]
         pub fn withdraw_candidacy(origin, membership_id: T::MemberId) -> Result<(), Error<T>> {
             let staking_account_id =
                 EnsureChecks::<T>::can_withdraw_candidacy(origin, &membership_id)?;
@@ -587,7 +590,7 @@ decl_module! {
         /// - db:
         ///    - `O(1)` doesn't depend on the state or parameters
         /// # </weight>
-        #[weight = T::WeightInfo::set_candidacy_note(note.len().saturated_into())]
+        #[weight = CouncilWeightInfo::<T>::set_candidacy_note(note.len().saturated_into())]
         pub fn set_candidacy_note(origin, membership_id: T::MemberId, note: Vec<u8>)
             -> Result<(), Error<T>> {
             // ensure action can be started
@@ -618,7 +621,7 @@ decl_module! {
         /// - db:
         ///    - `O(1)` doesn't depend on the state or parameters
         /// # </weight>
-        #[weight = T::WeightInfo::set_budget()]
+        #[weight = CouncilWeightInfo::<T>::set_budget()]
         pub fn set_budget(origin, balance: Balance<T>) -> Result<(), Error<T>> {
             // ensure action can be started
             EnsureChecks::<T>::can_set_budget(origin)?;
@@ -645,7 +648,7 @@ decl_module! {
         /// - db:
         ///    - `O(1)` doesn't depend on the state or parameters
         /// # </weight>
-        #[weight = T::WeightInfo::plan_budget_refill()]
+        #[weight = CouncilWeightInfo::<T>::plan_budget_refill()]
         pub fn plan_budget_refill(origin, next_refill: T::BlockNumber) -> Result<(), Error<T>> {
             // ensure action can be started
             EnsureChecks::<T>::can_plan_budget_refill(origin)?;
@@ -883,15 +886,17 @@ impl<T: Trait> Module<T> {
 
     fn calculate_on_initialize_weight(mb_candidate_count: Option<u64>) -> Weight {
         // Minimum weight for progress stage
-        let weight = T::WeightInfo::try_progress_stage_idle()
-            .max(T::WeightInfo::try_progress_stage_announcing_restart());
+        let weight = CouncilWeightInfo::<T>::try_progress_stage_idle()
+            .max(CouncilWeightInfo::<T>::try_progress_stage_announcing_restart());
 
         let weight = if let Some(candidate_count) = mb_candidate_count {
             // We can use the candidate count to calculate the worst case
             // if we are in announcement period without an additional storage access
-            weight.max(T::WeightInfo::try_progress_stage_announcing_start_election(
-                candidate_count.saturated_into(),
-            ))
+            weight.max(
+                CouncilWeightInfo::<T>::try_progress_stage_announcing_start_election(
+                    candidate_count.saturated_into(),
+                ),
+            )
         } else {
             // If we don't have the candidate count we only take into account the weight
             // of the functions that doesn't depend on it
@@ -899,7 +904,7 @@ impl<T: Trait> Module<T> {
         };
 
         // Total weight = try progress weight + try process budget weight
-        T::WeightInfo::try_process_budget().saturating_add(weight)
+        CouncilWeightInfo::<T>::try_process_budget().saturating_add(weight)
     }
 }
 
@@ -1184,14 +1189,7 @@ impl<T: Trait> Mutations<T> {
         now: &T::BlockNumber,
     ) {
         // mint tokens into reward account
-        <<<T as Trait>::Referendum as ReferendumManager<
-            <T as frame_system::Trait>::Origin,
-            <T as frame_system::Trait>::AccountId,
-            <T as common::Trait>::MemberId,
-            <T as frame_system::Trait>::Hash,
-        >>::Currency as Currency<<T as frame_system::Trait>::AccountId>>::deposit_creating(
-            account_id, *amount,
-        );
+        let _ = balances::Module::<T>::deposit_creating(account_id, *amount);
 
         // update elected council member
         CouncilMembers::<T>::mutate(|members| {
@@ -1224,12 +1222,11 @@ impl<T: Trait> EnsureChecks<T> {
         origin: T::Origin,
         membership_id: &T::MemberId,
     ) -> Result<T::AccountId, Error<T>> {
-        let account_id = ensure_signed(origin)?;
-
-        ensure!(
-            T::is_council_member_account(membership_id, &account_id),
-            Error::MemberIdNotMatchAccount,
-        );
+        let account_id = T::MemberOriginValidator::ensure_member_controller_account_origin(
+            origin,
+            *membership_id,
+        )
+        .map_err(|_| Error::MemberIdNotMatchAccount)?;
 
         Ok(account_id)
     }
@@ -1389,3 +1386,19 @@ impl<T: Trait> EnsureChecks<T> {
         Ok(())
     }
 }
+
+impl<T: Trait + common::Trait> CouncilOriginValidator<T::Origin, T::MemberId, T::AccountId>
+    for Module<T>
+{
+    fn ensure_member_consulate(origin: T::Origin, member_id: T::MemberId) -> DispatchResult {
+        EnsureChecks::<T>::ensure_user_membership(origin, &member_id)?;
+
+        let is_councilor = Self::council_members()
+            .iter()
+            .any(|council_member| council_member.member_id() == &member_id);
+
+        ensure!(is_councilor, Error::<T>::NotCouncilor);
+
+        Ok(())
+    }
+}

+ 62 - 26
runtime-modules/council/src/mock.rs

@@ -9,17 +9,16 @@ use crate::{
 };
 
 use balances;
-use frame_support::dispatch::DispatchResult;
+use frame_support::dispatch::{DispatchError, DispatchResult};
 use frame_support::traits::{Currency, Get, LockIdentifier, OnFinalize, OnInitialize};
 use frame_support::weights::Weight;
 use frame_support::{
-    impl_outer_event, impl_outer_origin, parameter_types, StorageMap, StorageValue,
+    ensure, impl_outer_event, impl_outer_origin, parameter_types, StorageMap, StorageValue,
 };
-use frame_system::{EnsureOneOf, EnsureRoot, EnsureSigned, RawOrigin};
+use frame_system::{ensure_signed, EnsureOneOf, EnsureRoot, EnsureSigned, RawOrigin};
 use rand::Rng;
 use referendum::{
-    Balance as BalanceReferendum, CastVote, OptionResult, ReferendumManager, ReferendumStage,
-    ReferendumStageRevealing,
+    CastVote, OptionResult, ReferendumManager, ReferendumStage, ReferendumStageRevealing,
 };
 use sp_core::H256;
 use sp_io;
@@ -44,8 +43,6 @@ pub const VOTER_BASE_ID: u64 = 4000;
 pub const CANDIDATE_BASE_ID: u64 = VOTER_BASE_ID + VOTER_CANDIDATE_OFFSET;
 pub const VOTER_CANDIDATE_OFFSET: u64 = 1000;
 
-pub const INVALID_USER_MEMBER: u64 = 9999;
-
 // multiplies topup value so that candidate/voter can candidate/vote multiple times
 pub const TOPUP_MULTIPLIER: u64 = 10;
 
@@ -103,13 +100,6 @@ impl Trait for Runtime {
 
     type WeightInfo = ();
 
-    fn is_council_member_account(
-        membership_id: &Self::MemberId,
-        account_id: &<Self as frame_system::Trait>::AccountId,
-    ) -> bool {
-        membership_id == account_id
-    }
-
     fn new_council_elected(elected_members: &[CouncilMemberOf<Self>]) {
         let is_ok = elected_members == CouncilMembers::<Runtime>::get();
 
@@ -117,11 +107,33 @@ impl Trait for Runtime {
             *value.borrow_mut() = (is_ok,);
         });
     }
+
+    type MemberOriginValidator = ();
+}
+
+impl common::origin::MemberOriginValidator<Origin, u64, u64> for () {
+    fn ensure_member_controller_account_origin(
+        origin: Origin,
+        member_id: u64,
+    ) -> Result<u64, DispatchError> {
+        let account_id = ensure_signed(origin)?;
+
+        ensure!(
+            member_id == account_id,
+            DispatchError::Other("Membership error")
+        );
+
+        Ok(account_id)
+    }
+
+    fn is_member_controller_account(member_id: &u64, account_id: &u64) -> bool {
+        member_id == account_id
+    }
 }
 
 impl common::StakingAccountValidator<Runtime> for () {
-    fn is_member_staking_account(_: &u64, _: &u64) -> bool {
-        true
+    fn is_member_staking_account(member_id: &u64, account_id: &u64) -> bool {
+        *member_id == *account_id
     }
 }
 
@@ -243,6 +255,8 @@ parameter_types! {
     pub const DefaultMembershipPrice: u64 = 100;
     pub const DefaultInitialInvitationBalance: u64 = 100;
     pub const MinimumPeriod: u64 = 5;
+    pub const InvitedMemberLockId: [u8; 8] = [2; 8];
+    pub const MaxWinnerTargetCount: u64 = 10;
 }
 
 mod balances_mod {
@@ -254,23 +268,23 @@ impl referendum::Trait<ReferendumInstance> for Runtime {
 
     type MaxSaltLength = MaxSaltLength;
 
-    type Currency = balances::Module<Self>;
-    type LockId = VotingLockId;
-
     type ManagerOrigin =
         EnsureOneOf<Self::AccountId, EnsureSigned<Self::AccountId>, EnsureRoot<Self::AccountId>>;
 
     type VotePower = u64;
 
     type VoteStageDuration = VoteStageDuration;
+    type StakingHandler = staking_handler::StakingManager<Self, VotingLockId>;
     type RevealStageDuration = RevealStageDuration;
 
     type MinimumStake = MinimumVotingStake;
     type WeightInfo = ReferendumWeightInfo;
 
+    type MaxWinnerTargetCount = MaxWinnerTargetCount;
+
     fn calculate_vote_power(
         account_id: &<Self as frame_system::Trait>::AccountId,
-        stake: &BalanceReferendum<Self, ReferendumInstance>,
+        stake: &Balance<Self>,
     ) -> Self::VotePower {
         let stake: u64 = u64::from(*stake);
         if *account_id == USER_REGULAR_POWER_VOTES {
@@ -280,9 +294,7 @@ impl referendum::Trait<ReferendumInstance> for Runtime {
         stake
     }
 
-    fn can_unlock_vote_stake(
-        vote: &CastVote<Self::Hash, BalanceReferendum<Self, ReferendumInstance>, Self::MemberId>,
-    ) -> bool {
+    fn can_unlock_vote_stake(vote: &CastVote<Self::Hash, Balance<Self>, Self::MemberId>) -> bool {
         // trigger fail when requested to do so
         if !IS_UNSTAKE_ENABLED.with(|value| value.borrow().0) {
             return false;
@@ -366,9 +378,20 @@ impl membership::Trait for Runtime {
     type DefaultMembershipPrice = DefaultMembershipPrice;
     type WorkingGroup = ();
     type DefaultInitialInvitationBalance = DefaultInitialInvitationBalance;
+    type InvitedMemberStakingHandler = staking_handler::StakingManager<Self, InvitedMemberLockId>;
 }
 
-impl common::working_group::WorkingGroupIntegration<Runtime> for () {
+impl common::working_group::WorkingGroupBudgetHandler<Runtime> for () {
+    fn get_budget() -> u64 {
+        unimplemented!()
+    }
+
+    fn set_budget(_new_value: u64) {
+        unimplemented!()
+    }
+}
+
+impl common::working_group::WorkingGroupAuthenticator<Runtime> for () {
     fn ensure_worker_origin(
         _origin: <Runtime as frame_system::Trait>::Origin,
         _worker_id: &<Runtime as common::Trait>::ActorId,
@@ -441,6 +464,7 @@ pub struct CandidateInfo<T: Trait> {
     pub account_id: T::MemberId,
     pub membership_id: T::MemberId,
     pub candidate: CandidateOf<T>,
+    pub auto_topup_amount: Balance<T>,
 }
 
 #[derive(Clone)]
@@ -638,13 +662,16 @@ where
             note_hash: None,
         };
 
-        Self::topup_account(account_id.into(), stake * TOPUP_MULTIPLIER.into());
+        let auto_topup_amount = stake * TOPUP_MULTIPLIER.into();
+
+        Self::topup_account(account_id.into(), auto_topup_amount);
 
         CandidateInfo {
             origin,
             candidate,
             membership_id: account_id.into(),
             account_id: account_id.into(),
+            auto_topup_amount,
         }
     }
 
@@ -825,7 +852,14 @@ where
     }
 
     pub fn check_new_council_elected_hook() {
-        LAST_COUNCIL_ELECTED_OK.with(|value| assert!(value.borrow().0))
+        let result = LAST_COUNCIL_ELECTED_OK.with(|value| assert!(value.borrow().0));
+
+        // clear election sign
+        LAST_COUNCIL_ELECTED_OK.with(|value| {
+            *value.borrow_mut() = (false,);
+        });
+
+        result
     }
 
     pub fn set_candidacy_note(
@@ -1264,3 +1298,5 @@ where
         params
     }
 }
+
+pub type Council = crate::Module<Runtime>;

+ 134 - 26
runtime-modules/council/src/tests.rs

@@ -5,7 +5,10 @@ use super::{
     Module, Trait,
 };
 use crate::mock::*;
+use common::origin::CouncilOriginValidator;
+use frame_support::traits::Currency;
 use frame_support::StorageValue;
+use frame_system::RawOrigin;
 use staking_handler::StakingHandler;
 
 type Mocks = InstanceMocks<Runtime>;
@@ -192,27 +195,6 @@ 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();
-
-    build_test_externalities(config).execute_with(|| {
-        let council_settings = CouncilSettings::<Runtime>::extract_settings();
-
-        let stake = council_settings.min_candidate_stake;
-        let candidate = MockUtils::generate_candidate(INVALID_USER_MEMBER, stake);
-
-        Mocks::announce_candidacy(
-            candidate.origin.clone(),
-            candidate.account_id.clone(),
-            candidate.candidate.stake.clone(),
-            Err(Error::MemberIdNotMatchAccount),
-        );
-    });
-}
-
 // Test that candidate can withdraw valid candidacy.
 #[test]
 fn council_candidacy_withdraw_candidacy() {
@@ -1341,9 +1323,6 @@ fn council_membership_checks() {
             candidate2.candidate.staking_account_id,
         );
 
-        // TODO: uncomment this once StakingHandler's `is_member_staking_account` is properly
-        // implemented
-        /*
         // test that staking_account_id has to be associated with membership_id
         Mocks::announce_candidacy_raw(
             candidate1.origin.clone(),
@@ -1351,9 +1330,8 @@ fn council_membership_checks() {
             candidate2.candidate.staking_account_id.clone(), // second candidate's account id
             candidate1.candidate.reward_account_id.clone(),
             candidate1.candidate.stake.clone(),
-            Err(Error::MembershipIdNotMatchAccount),
+            Err(Error::MemberIdNotMatchAccount),
         );
-        */
 
         // test that reward_account_id not associated with membership_id can be used
         Mocks::announce_candidacy_raw(
@@ -1367,6 +1345,7 @@ fn council_membership_checks() {
     });
 }
 
+// Test that the hook is properly called after a new council is elected.
 #[test]
 fn council_new_council_elected_hook() {
     let config = default_genesis_config();
@@ -1377,3 +1356,132 @@ fn council_new_council_elected_hook() {
         Mocks::check_new_council_elected_hook();
     });
 }
+
+#[test]
+fn council_origin_validator_fails_with_unregistered_member() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let account_id = 10;
+        let origin = RawOrigin::Signed(account_id);
+        let member_id = 1;
+
+        let validation_result = Council::ensure_member_consulate(origin.into(), member_id);
+
+        assert_eq!(
+            validation_result,
+            Err(Error::<Runtime>::MemberIdNotMatchAccount.into())
+        );
+    });
+}
+
+#[test]
+fn council_origin_validator_succeeds() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let councilor1_member_id = 1u64;
+        let councilor1_account_id = 1u64;
+
+        let councilor1 = CouncilMemberOf::<Runtime> {
+            staking_account_id: councilor1_account_id,
+            reward_account_id: councilor1_account_id,
+            membership_id: councilor1_member_id,
+            stake: 0,
+            last_payment_block: 0,
+            unpaid_reward: 0,
+        };
+
+        CouncilMembers::<Runtime>::put(vec![councilor1]);
+
+        let origin = RawOrigin::Signed(councilor1_account_id.clone());
+
+        let validation_result =
+            Council::ensure_member_consulate(origin.into(), councilor1_member_id);
+
+        assert!(validation_result.is_ok());
+    });
+}
+
+#[test]
+fn council_origin_validator_fails_with_not_councilor() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let account_id = 1;
+        let member_id = 1;
+        let origin = RawOrigin::Signed(account_id.clone());
+
+        let validation_result = Council::ensure_member_consulate(origin.into(), member_id);
+
+        assert_eq!(
+            validation_result,
+            Err(Error::<Runtime>::NotCouncilor.into())
+        );
+    });
+}
+
+// Test that rewards for council members are paid as expected even after many council election cycles.
+#[test]
+fn council_many_cycle_rewards() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let council_settings = CouncilSettings::<Runtime>::extract_settings();
+
+        // quite high number of election cycles that will uncover any reward payment iregularities
+        let num_iterations = 100;
+
+        let mut council_members = vec![];
+        let mut auto_topup_amount = 0;
+
+        let origin = OriginType::Root;
+        Mocks::set_budget(origin.clone(), u64::MAX.into(), Ok(()));
+        for i in 0..num_iterations {
+            let tmp_params = Mocks::run_full_council_cycle(
+                i * council_settings.cycle_duration,
+                &council_members,
+                0,
+            );
+
+            auto_topup_amount = tmp_params.candidates_announcing[0].auto_topup_amount;
+            council_members = tmp_params.expected_final_council_members;
+
+            /*
+             *  Since we have enough budget to pay during idle period
+             *  we update the councilor last payment block during the idle period
+             *  that are not accounted in the `run_full_council_cycle` code expected final member
+             */
+
+            // This is the last paid block taking into account the last idle period
+            let last_payment_block = i * council_settings.cycle_duration
+                + council_settings.cycle_duration
+                - (council_settings.idle_stage_duration
+                    % <Runtime as Trait>::ElectedMemberRewardPeriod::get());
+
+            // Update the expected final council from `run_full_council_cycle` to use the current
+            // `last_payment_block`
+            council_members = council_members
+                .into_iter()
+                .map(|councilor| CouncilMemberOf::<Runtime> {
+                    last_payment_block,
+                    ..councilor
+                })
+                .collect();
+        }
+
+        // All blocks are paid except for the first iteration while the council is not elected
+        // that means discounting the idle stage duration. And the last blocks of the last idle
+        // period aren't paid until a full extra reward period passes.
+        let num_blocks_elected = num_iterations * council_settings.cycle_duration
+            - (council_settings.cycle_duration - council_settings.idle_stage_duration) // Unpaid blocks from first cycle
+            - (council_settings.idle_stage_duration // Unpaid blocks from last cycle
+                % <Runtime as Trait>::ElectedMemberRewardPeriod::get());
+
+        assert_eq!(
+            balances::Module::<Runtime>::total_balance(&council_members[0].staking_account_id),
+            num_blocks_elected * <Runtime as Trait>::ElectedMemberRewardPerBlock::get()
+                + num_iterations * auto_topup_amount
+        );
+    });
+}

+ 4 - 2
runtime-modules/forum/Cargo.toml

@@ -22,6 +22,7 @@ balances = { package = 'pallet-balances', default-features = false, git = 'https
 membership = { package = 'pallet-membership', default-features = false, path = '../membership', optional = true}
 working-group = { package = 'pallet-working-group', default-features = false, path = '../working-group', optional = true}
 staking-handler = { package = 'pallet-staking-handler', default-features = false, path = '../staking-handler', optional = true}
+sp-core = { package = 'sp-core', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca', optional = true}
 
 [dev-dependencies]
 sp-core = { package = 'sp-core', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
@@ -37,7 +38,8 @@ runtime-benchmarks = [
 	'membership',
 	'working-group',
 	'staking-handler',
-	'balances'
+	'balances',
+    'sp-core',
 ]
 std = [
 	'serde',
@@ -50,4 +52,4 @@ std = [
 	'sp-io/std',
 	'pallet-timestamp/std',
 	'common/std',
-]
+]

+ 77 - 43
runtime-modules/forum/src/benchmarking.rs

@@ -15,6 +15,29 @@ use working_group::{
     WorkerById,
 };
 
+// We create this trait because we need to be compatible with the runtime
+// in the mock for tests. In that case we need to be able to have `membership_id == account_id`
+// We can't create an account from an `u32` or from a memberhsip_dd,
+// so this trait allows us to get an account id from an u32, in the case of `64` which is what
+// the mock use we get the parameter as a return.
+// In the case of `AccountId32` we use the method provided by `frame_benchmarking` to get an
+// AccountId.
+pub trait CreateAccountId {
+    fn create_account_id(id: u32) -> Self;
+}
+
+impl CreateAccountId for u64 {
+    fn create_account_id(id: u32) -> Self {
+        id.into()
+    }
+}
+
+impl CreateAccountId for sp_core::crypto::AccountId32 {
+    fn create_account_id(id: u32) -> Self {
+        account::<Self>("default", id, SEED)
+    }
+}
+
 // The forum working group instance alias.
 pub type ForumWorkingGroupInstance = working_group::Instance1;
 
@@ -40,10 +63,12 @@ fn assert_last_event<T: Trait>(generic_event: <T as Trait>::Event) {
 }
 
 fn member_funded_account<T: Trait + membership::Trait + balances::Trait>(
-    name: &'static str,
     id: u32,
-) -> (T::AccountId, T::MemberId) {
-    let account_id = account::<T::AccountId>(name, id, SEED);
+) -> (T::AccountId, T::MemberId)
+where
+    T::AccountId: CreateAccountId,
+{
+    let account_id = T::AccountId::create_account_id(id);
     let handle = handle_from_id::<T>(id);
 
     let _ = Balances::<T>::make_free_balance_be(&account_id, BalanceOf::<T>::max_value());
@@ -101,8 +126,11 @@ fn insert_a_worker<
 >(
     job_opening_type: OpeningType,
     id: u64,
-) -> T::AccountId {
-    let (caller_id, member_id) = member_funded_account::<T>("member", id as u32);
+) -> T::AccountId
+where
+    T::AccountId: CreateAccountId,
+{
+    let (caller_id, member_id) = member_funded_account::<T>(id as u32);
 
     let add_worker_origin = match job_opening_type {
         OpeningType::Leader => RawOrigin::Root,
@@ -219,7 +247,7 @@ fn create_new_category<T: Trait>(
 
 fn create_new_thread<T: Trait>(
     account_id: T::AccountId,
-    forum_user_id: T::ForumUserId,
+    forum_user_id: crate::ForumUserId<T>,
     category_id: T::CategoryId,
     title: Vec<u8>,
     text: Vec<u8>,
@@ -239,7 +267,7 @@ fn create_new_thread<T: Trait>(
 
 fn add_thread_post<T: Trait>(
     account_id: T::AccountId,
-    forum_user_id: T::ForumUserId,
+    forum_user_id: crate::ForumUserId<T>,
     category_id: T::CategoryId,
     thread_id: T::ThreadId,
     text: Vec<u8>,
@@ -334,7 +362,13 @@ pub fn generate_categories_tree<T: Trait>(
 }
 
 benchmarks! {
-    where_clause { where T: balances::Trait, T: membership::Trait, T: working_group::Trait<ForumWorkingGroupInstance> }
+    where_clause { where
+        T: balances::Trait,
+        T: membership::Trait,
+        T: working_group::Trait<ForumWorkingGroupInstance> ,
+        T::AccountId: CreateAccountId
+    }
+
     _{  }
 
     create_category{
@@ -668,7 +702,7 @@ benchmarks! {
         let next_thread_id = Module::<T>::next_thread_id();
         let next_post_id = Module::<T>::next_post_id();
 
-    }: _ (RawOrigin::Signed(caller_id), forum_user_id.into(), category_id, title.clone(), text.clone(), poll.clone())
+    }: _ (RawOrigin::Signed(caller_id), forum_user_id.saturated_into(), category_id, title.clone(), text.clone(), poll.clone())
     verify {
 
         // Ensure category num_direct_threads updated successfully.
@@ -679,7 +713,7 @@ benchmarks! {
         let new_thread = Thread {
             category_id,
             title_hash: T::calculate_hash(&title),
-            author_id: forum_user_id.into(),
+            author_id: forum_user_id.saturated_into(),
             archived: false,
             poll,
             // initial posts number
@@ -692,7 +726,7 @@ benchmarks! {
         let new_post = Post {
             thread_id: next_thread_id,
             text_hash: T::calculate_hash(&text),
-            author_id: forum_user_id.into(),
+            author_id: forum_user_id.saturated_into(),
         };
 
         assert_eq!(Module::<T>::post_by_id(next_thread_id, next_post_id), new_post);
@@ -716,14 +750,14 @@ benchmarks! {
 
         // Create thread
         let thread_id = create_new_thread::<T>(
-            caller_id.clone(), forum_user_id.into(), category_id,
+            caller_id.clone(), forum_user_id.saturated_into(), category_id,
             vec![1u8].repeat(MAX_BYTES as usize), vec![1u8].repeat(MAX_BYTES as usize), None
         );
         let mut thread = Module::<T>::thread_by_id(category_id, thread_id);
 
         let text = vec![0u8].repeat(j as usize);
 
-    }: _ (RawOrigin::Signed(caller_id), forum_user_id.into(), category_id, thread_id, text.clone())
+    }: _ (RawOrigin::Signed(caller_id), forum_user_id.saturated_into(), category_id, thread_id, text.clone())
     verify {
         thread.title_hash = T::calculate_hash(&text);
         assert_eq!(Module::<T>::thread_by_id(category_id, thread_id), thread);
@@ -743,7 +777,7 @@ benchmarks! {
         let (category_id, _) = generate_categories_tree::<T>(caller_id.clone(), i, None);
 
         // Create thread
-        let thread_id = create_new_thread::<T>(caller_id.clone(), forum_user_id.into(), category_id, text.clone(), text.clone(), None);
+        let thread_id = create_new_thread::<T>(caller_id.clone(), forum_user_id.saturated_into(), category_id, text.clone(), text.clone(), None);
         let mut thread = Module::<T>::thread_by_id(category_id, thread_id);
         let new_archival_status = true;
 
@@ -768,7 +802,7 @@ benchmarks! {
         let (category_id, _) = generate_categories_tree::<T>(caller_id.clone(), i, None);
 
         // Create thread
-        let thread_id = create_new_thread::<T>(caller_id.clone(), forum_user_id.into(), category_id, text.clone(), text.clone(), None);
+        let thread_id = create_new_thread::<T>(caller_id.clone(), forum_user_id.saturated_into(), category_id, text.clone(), text.clone(), None);
         let mut thread = Module::<T>::thread_by_id(category_id, thread_id);
         let new_archival_status = true;
 
@@ -810,14 +844,14 @@ benchmarks! {
         let text = vec![1u8].repeat(MAX_BYTES as usize);
 
         let thread_id = create_new_thread::<T>(
-            caller_id.clone(), forum_user_id.into(), category_id,
+            caller_id.clone(), forum_user_id.saturated_into(), category_id,
             text.clone(), text.clone(), poll
         );
 
         let mut category = Module::<T>::category_by_id(category_id);
 
         for _ in 0..<<<T as Trait>::MapLimits as StorageLimits>::MaxPostsInThread>::get() - 1 {
-            add_thread_post::<T>(caller_id.clone(), forum_user_id.into(), category_id, thread_id, text.clone());
+            add_thread_post::<T>(caller_id.clone(), forum_user_id.saturated_into(), category_id, thread_id, text.clone());
         }
 
     }: delete_thread(RawOrigin::Signed(caller_id), PrivilegedActor::Lead, category_id, thread_id)
@@ -851,7 +885,7 @@ benchmarks! {
         let text = vec![1u8].repeat(MAX_BYTES as usize);
 
         let thread_id = create_new_thread::<T>(
-            caller_id.clone(), forum_user_id.into(), category_id,
+            caller_id.clone(), forum_user_id.saturated_into(), category_id,
             text.clone(), text.clone(), poll
         );
 
@@ -865,7 +899,7 @@ benchmarks! {
         let mut category = Module::<T>::category_by_id(category_id);
 
         for _ in 0..<<<T as Trait>::MapLimits as StorageLimits>::MaxPostsInThread>::get() - 1 {
-            add_thread_post::<T>(caller_id.clone(), forum_user_id.into(), category_id, thread_id, text.clone());
+            add_thread_post::<T>(caller_id.clone(), forum_user_id.saturated_into(), category_id, thread_id, text.clone());
         }
 
     }: delete_thread(RawOrigin::Signed(caller_id), PrivilegedActor::Moderator(moderator_id), category_id, thread_id)
@@ -915,7 +949,7 @@ benchmarks! {
         };
 
         // Create thread
-        let thread_id = create_new_thread::<T>(caller_id.clone(), forum_user_id.into(), category_id, text.clone(), text.clone(), None);
+        let thread_id = create_new_thread::<T>(caller_id.clone(), forum_user_id.saturated_into(), category_id, text.clone(), text.clone(), None);
         let thread = Module::<T>::thread_by_id(category_id, thread_id);
 
         let mut category = Module::<T>::category_by_id(category_id);
@@ -970,7 +1004,7 @@ benchmarks! {
         };
 
         // Create thread
-        let thread_id = create_new_thread::<T>(caller_id.clone(), forum_user_id.into(), category_id, text.clone(), text.clone(), None);
+        let thread_id = create_new_thread::<T>(caller_id.clone(), forum_user_id.saturated_into(), category_id, text.clone(), text.clone(), None);
         let thread = Module::<T>::thread_by_id(category_id, thread_id);
 
         let moderator_id = ModeratorId::<T>::from(forum_user_id.try_into().unwrap());
@@ -1022,13 +1056,13 @@ benchmarks! {
         let text = vec![1u8].repeat(MAX_BYTES as usize);
 
         let thread_id = create_new_thread::<T>(
-            caller_id.clone(), forum_user_id.into(), category_id,
+            caller_id.clone(), forum_user_id.saturated_into(), category_id,
             text.clone(), text.clone(), poll
         );
 
         let mut thread = Module::<T>::thread_by_id(category_id, thread_id);
 
-    }: _ (RawOrigin::Signed(caller_id), forum_user_id.into(), category_id, thread_id, j - 1)
+    }: _ (RawOrigin::Signed(caller_id), forum_user_id.saturated_into(), category_id, thread_id, j - 1)
     verify {
         // Store new poll alternative statistics
         if let Some(ref mut poll) = thread.poll {
@@ -1075,14 +1109,14 @@ benchmarks! {
 
         let text = vec![1u8].repeat(MAX_BYTES as usize);
         let thread_id = create_new_thread::<T>(
-            caller_id.clone(), (lead_id as u64).into(), category_id,
+            caller_id.clone(), (lead_id as u64).saturated_into(), category_id,
             text.clone(), text.clone(), poll
         );
 
         let mut category = Module::<T>::category_by_id(category_id);
 
         for _ in 0..j {
-            add_thread_post::<T>(caller_id.clone(), (lead_id as u64).into(), category_id, thread_id, text.clone());
+            add_thread_post::<T>(caller_id.clone(), (lead_id as u64).saturated_into(), category_id, thread_id, text.clone());
         }
         let rationale = vec![0u8].repeat(k as usize);
 
@@ -1122,7 +1156,7 @@ benchmarks! {
 
         let text = vec![1u8].repeat(MAX_BYTES as usize);
         let thread_id = create_new_thread::<T>(
-            caller_id.clone(), (lead_id as u64).into(), category_id,
+            caller_id.clone(), (lead_id as u64).saturated_into(), category_id,
             text.clone(), text.clone(), poll
         );
 
@@ -1136,7 +1170,7 @@ benchmarks! {
         let mut category = Module::<T>::category_by_id(category_id);
 
         for _ in 0..j {
-            add_thread_post::<T>(caller_id.clone(), (lead_id as u64).into(), category_id, thread_id, text.clone());
+            add_thread_post::<T>(caller_id.clone(), (lead_id as u64).saturated_into(), category_id, thread_id, text.clone());
         }
         let rationale = vec![0u8].repeat(k as usize);
 
@@ -1170,13 +1204,13 @@ benchmarks! {
 
         // Create thread
         let thread_id = create_new_thread::<T>(
-            caller_id.clone(), forum_user_id.into(), category_id,
+            caller_id.clone(), forum_user_id.saturated_into(), category_id,
             vec![0u8].repeat(MAX_BYTES as usize), vec![0u8].repeat(MAX_BYTES as usize), None
         );
         let mut thread = Module::<T>::thread_by_id(category_id, thread_id);
         let post_id = Module::<T>::next_post_id();
 
-    }: _ (RawOrigin::Signed(caller_id), forum_user_id.into(), category_id, thread_id, text.clone())
+    }: _ (RawOrigin::Signed(caller_id), forum_user_id.saturated_into(), category_id, thread_id, text.clone())
     verify {
         // Ensure thread posts counter updated successfully
         thread.num_direct_posts+=1;
@@ -1186,7 +1220,7 @@ benchmarks! {
         let new_post = Post {
             thread_id,
             text_hash: T::calculate_hash(&text),
-            author_id: forum_user_id.into(),
+            author_id: forum_user_id.saturated_into(),
         };
 
         assert_eq!(Module::<T>::post_by_id(thread_id, post_id), new_post);
@@ -1214,17 +1248,17 @@ benchmarks! {
         let text = vec![1u8].repeat(MAX_BYTES as usize);
 
         let thread_id = create_new_thread::<T>(
-            caller_id.clone(), forum_user_id.into(), category_id,
+            caller_id.clone(), forum_user_id.saturated_into(), category_id,
             text.clone(), text.clone(), poll
         );
 
-        let post_id = add_thread_post::<T>(caller_id.clone(), forum_user_id.into(), category_id, thread_id, text.clone());
+        let post_id = add_thread_post::<T>(caller_id.clone(), forum_user_id.saturated_into(), category_id, thread_id, text.clone());
 
         let react = T::PostReactionId::one();
 
-    }: _ (RawOrigin::Signed(caller_id), forum_user_id.into(), category_id, thread_id, post_id, react)
+    }: _ (RawOrigin::Signed(caller_id), forum_user_id.saturated_into(), category_id, thread_id, post_id, react)
     verify {
-        assert_last_event::<T>(RawEvent::PostReacted(forum_user_id.into(), post_id, react).into());
+        assert_last_event::<T>(RawEvent::PostReacted(forum_user_id.saturated_into(), post_id, react).into());
     }
 
     edit_post_text {
@@ -1247,17 +1281,17 @@ benchmarks! {
         let text = vec![1u8].repeat(MAX_BYTES as usize);
 
         let thread_id = create_new_thread::<T>(
-            caller_id.clone(), forum_user_id.into(), category_id,
+            caller_id.clone(), forum_user_id.saturated_into(), category_id,
             text.clone(), text.clone(), poll
         );
 
-        let post_id = add_thread_post::<T>(caller_id.clone(), forum_user_id.into(), category_id, thread_id, text.clone());
+        let post_id = add_thread_post::<T>(caller_id.clone(), forum_user_id.saturated_into(), category_id, thread_id, text.clone());
 
         let mut post = Module::<T>::post_by_id(thread_id, post_id);
 
         let new_text = vec![0u8].repeat(j as usize);
 
-    }: _ (RawOrigin::Signed(caller_id), forum_user_id.into(), category_id, thread_id, post_id, new_text.clone())
+    }: _ (RawOrigin::Signed(caller_id), forum_user_id.saturated_into(), category_id, thread_id, post_id, new_text.clone())
     verify {
 
         // Ensure post text updated successfully.
@@ -1288,10 +1322,10 @@ benchmarks! {
         let text = vec![1u8].repeat(MAX_BYTES as usize);
 
         let thread_id = create_new_thread::<T>(
-            caller_id.clone(), forum_user_id.into(), category_id,
+            caller_id.clone(), forum_user_id.saturated_into(), category_id,
             text.clone(), text.clone(), poll
         );
-        let post_id = add_thread_post::<T>(caller_id.clone(), forum_user_id.into(), category_id, thread_id, text.clone());
+        let post_id = add_thread_post::<T>(caller_id.clone(), forum_user_id.saturated_into(), category_id, thread_id, text.clone());
 
         let mut thread = Module::<T>::thread_by_id(category_id, thread_id);
 
@@ -1327,10 +1361,10 @@ benchmarks! {
         let text = vec![1u8].repeat(MAX_BYTES as usize);
 
         let thread_id = create_new_thread::<T>(
-            caller_id.clone(), forum_user_id.into(), category_id,
+            caller_id.clone(), forum_user_id.saturated_into(), category_id,
             text.clone(), text.clone(), poll
         );
-        let post_id = add_thread_post::<T>(caller_id.clone(), forum_user_id.into(), category_id, thread_id, text.clone());
+        let post_id = add_thread_post::<T>(caller_id.clone(), forum_user_id.saturated_into(), category_id, thread_id, text.clone());
 
         let mut thread = Module::<T>::thread_by_id(category_id, thread_id);
 
@@ -1375,7 +1409,7 @@ benchmarks! {
         let stickied_ids: Vec<T::ThreadId> = (0..j)
             .into_iter()
             .map(|_| create_new_thread::<T>(
-                caller_id.clone(), forum_user_id.into(), category_id,
+                caller_id.clone(), forum_user_id.saturated_into(), category_id,
                 text.clone(), text.clone(), poll.clone()
             )).collect();
 
@@ -1412,7 +1446,7 @@ benchmarks! {
         let stickied_ids: Vec<T::ThreadId> = (0..j)
             .into_iter()
             .map(|_| create_new_thread::<T>(
-                caller_id.clone(), forum_user_id.into(), category_id,
+                caller_id.clone(), forum_user_id.saturated_into(), category_id,
                 text.clone(), text.clone(), poll.clone()
             )).collect();
 

+ 50 - 47
runtime-modules/forum/src/lib.rs

@@ -18,7 +18,8 @@ use sp_runtime::traits::{MaybeSerialize, Member};
 use sp_runtime::SaturatedConversion;
 use sp_std::prelude::*;
 
-use common::working_group::WorkingGroupIntegration;
+use common::origin::MemberOriginValidator;
+use common::working_group::WorkingGroupAuthenticator;
 
 mod mock;
 mod tests;
@@ -28,6 +29,8 @@ mod benchmarking;
 /// Moderator ID alias for the actor of the system.
 pub type ModeratorId<T> = common::ActorId<T>;
 
+/// Forum user ID alias for the member of the system.
+pub type ForumUserId<T> = common::MemberId<T>;
 type WeightInfoForum<T> = <T as Trait>::WeightInfo;
 
 /// pallet_forum WeightInfo.
@@ -62,16 +65,6 @@ pub trait WeightInfo {
 
 pub trait Trait: frame_system::Trait + pallet_timestamp::Trait + common::Trait {
     type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;
-    type ForumUserId: Parameter
-        + Member
-        + BaseArithmetic
-        + Codec
-        + Default
-        + Copy
-        + MaybeSerialize
-        + PartialEq
-        + From<u64>
-        + Into<u64>;
 
     type CategoryId: Parameter
         + Member
@@ -124,12 +117,14 @@ pub trait Trait: frame_system::Trait + pallet_timestamp::Trait + common::Trait {
     type WeightInfo: WeightInfo;
 
     /// Working group pallet integration.
-    type WorkingGroup: common::working_group::WorkingGroupIntegration<Self>;
+    type WorkingGroup: common::working_group::WorkingGroupAuthenticator<Self>;
 
-    fn is_forum_member(
-        account_id: &<Self as frame_system::Trait>::AccountId,
-        forum_user_id: &Self::ForumUserId,
-    ) -> bool;
+    /// Validates member id and origin combination
+    type MemberOriginValidator: MemberOriginValidator<
+        Self::Origin,
+        common::MemberId<Self>,
+        Self::AccountId,
+    >;
 
     fn calculate_hash(text: &[u8]) -> Self::Hash;
 }
@@ -388,13 +383,16 @@ decl_storage! {
         pub CategoryCounter get(fn category_counter) config(): T::CategoryId;
 
         /// Map thread identifier to corresponding thread.
-        pub ThreadById get(fn thread_by_id) config(): double_map hasher(blake2_128_concat) T::CategoryId, hasher(blake2_128_concat) T::ThreadId => Thread<T::ForumUserId, T::CategoryId, T::Moment, T::Hash>;
+        pub ThreadById get(fn thread_by_id) config(): double_map hasher(blake2_128_concat)
+            T::CategoryId, hasher(blake2_128_concat) T::ThreadId =>
+                Thread<ForumUserId<T>, T::CategoryId, T::Moment, T::Hash>;
 
         /// Thread identifier value to be used for next Thread in threadById.
         pub NextThreadId get(fn next_thread_id) config(): T::ThreadId;
 
         /// Map post identifier to corresponding post.
-        pub PostById get(fn post_by_id) config(): double_map  hasher(blake2_128_concat) T::ThreadId, hasher(blake2_128_concat) T::PostId => Post<T::ForumUserId, T::ThreadId, T::Hash>;
+        pub PostById get(fn post_by_id) config(): double_map  hasher(blake2_128_concat) T::ThreadId,
+            hasher(blake2_128_concat) T::PostId => Post<ForumUserId<T>, T::ThreadId, T::Hash>;
 
         /// Post identifier value to be used for for next post created.
         pub NextPostId get(fn next_post_id) config(): T::PostId;
@@ -415,7 +413,7 @@ decl_event!(
         ModeratorId = ModeratorId<T>,
         <T as Trait>::ThreadId,
         <T as Trait>::PostId,
-        <T as Trait>::ForumUserId,
+        ForumUserId = ForumUserId<T>,
         <T as Trait>::PostReactionId,
     {
         /// A category was introduced
@@ -692,7 +690,7 @@ decl_module! {
         )]
         fn create_thread(
             origin,
-            forum_user_id: T::ForumUserId,
+            forum_user_id: ForumUserId<T>,
             category_id: T::CategoryId,
             title: Vec<u8>,
             text: Vec<u8>,
@@ -766,7 +764,7 @@ decl_module! {
             T::MaxCategoryDepth::get() as u32,
             new_title.len().saturated_into(),
         )]
-        fn edit_thread_title(origin, forum_user_id: T::ForumUserId, category_id: T::CategoryId, thread_id: T::ThreadId, new_title: Vec<u8>) -> DispatchResult {
+        fn edit_thread_title(origin, forum_user_id: ForumUserId<T>, category_id: T::CategoryId, thread_id: T::ThreadId, new_title: Vec<u8>) -> DispatchResult {
             // Ensure data migration is done
             Self::ensure_data_migration_done()?;
 
@@ -920,7 +918,7 @@ decl_module! {
             T::MaxCategoryDepth::get() as u32,
             <T::MapLimits as StorageLimits>::MaxPollAlternativesNumber::get() as u32
         )]
-        fn vote_on_poll(origin, forum_user_id: T::ForumUserId, category_id: T::CategoryId, thread_id: T::ThreadId, index: u32) -> DispatchResult {
+        fn vote_on_poll(origin, forum_user_id: ForumUserId<T>, category_id: T::CategoryId, thread_id: T::ThreadId, index: u32) -> DispatchResult {
             // Ensure data migration is done
             Self::ensure_data_migration_done()?;
 
@@ -1034,7 +1032,7 @@ decl_module! {
             T::MaxCategoryDepth::get() as u32,
             text.len().saturated_into(),
         )]
-        fn add_post(origin, forum_user_id: T::ForumUserId, category_id: T::CategoryId, thread_id: T::ThreadId, text: Vec<u8>) -> DispatchResult {
+        fn add_post(origin, forum_user_id: ForumUserId<T>, category_id: T::CategoryId, thread_id: T::ThreadId, text: Vec<u8>) -> DispatchResult {
             // Ensure data migration is done
             Self::ensure_data_migration_done()?;
 
@@ -1077,7 +1075,7 @@ decl_module! {
         #[weight = WeightInfoForum::<T>::react_post(
             T::MaxCategoryDepth::get() as u32,
         )]
-        fn react_post(origin, forum_user_id: T::ForumUserId, category_id: T::CategoryId, thread_id: T::ThreadId, post_id: T::PostId, react: T::PostReactionId) -> DispatchResult {
+        fn react_post(origin, forum_user_id: ForumUserId<T>, category_id: T::CategoryId, thread_id: T::ThreadId, post_id: T::PostId, react: T::PostReactionId) -> DispatchResult {
             // Ensure data migration is done
             Self::ensure_data_migration_done()?;
 
@@ -1113,7 +1111,7 @@ decl_module! {
             T::MaxCategoryDepth::get() as u32,
             new_text.len().saturated_into(),
         )]
-        fn edit_post_text(origin, forum_user_id: T::ForumUserId, category_id: T::CategoryId, thread_id: T::ThreadId, post_id: T::PostId, new_text: Vec<u8>) -> DispatchResult {
+        fn edit_post_text(origin, forum_user_id: ForumUserId<T>, category_id: T::CategoryId, thread_id: T::ThreadId, post_id: T::PostId, new_text: Vec<u8>) -> DispatchResult {
             // Ensure data migration is done
             Self::ensure_data_migration_done()?;
 
@@ -1228,8 +1226,8 @@ impl<T: Trait> Module<T> {
     pub fn add_new_post(
         thread_id: T::ThreadId,
         text: &[u8],
-        author_id: T::ForumUserId,
-    ) -> (T::PostId, Post<T::ForumUserId, T::ThreadId, T::Hash>) {
+        author_id: ForumUserId<T>,
+    ) -> (T::PostId, Post<ForumUserId<T>, T::ThreadId, T::Hash>) {
         // Make and add initial post
         let new_post_id = <NextPostId<T>>::get();
 
@@ -1300,7 +1298,7 @@ impl<T: Trait> Module<T> {
         category_id: &T::CategoryId,
         thread_id: &T::ThreadId,
         post_id: &T::PostId,
-    ) -> Result<Post<T::ForumUserId, T::ThreadId, T::Hash>, Error<T>> {
+    ) -> Result<Post<ForumUserId<T>, T::ThreadId, T::Hash>, Error<T>> {
         // Make sure post exists
         let post = Self::ensure_post_exists(thread_id, post_id)?;
 
@@ -1313,7 +1311,7 @@ impl<T: Trait> Module<T> {
     fn ensure_post_exists(
         thread_id: &T::ThreadId,
         post_id: &T::PostId,
-    ) -> Result<Post<T::ForumUserId, T::ThreadId, T::Hash>, Error<T>> {
+    ) -> Result<Post<ForumUserId<T>, T::ThreadId, T::Hash>, Error<T>> {
         if !<PostById<T>>::contains_key(thread_id, post_id) {
             return Err(Error::<T>::PostDoesNotExist);
         }
@@ -1327,7 +1325,7 @@ impl<T: Trait> Module<T> {
         category_id: &T::CategoryId,
         thread_id: &T::ThreadId,
         post_id: &T::PostId,
-    ) -> Result<Post<T::ForumUserId, T::ThreadId, T::Hash>, Error<T>> {
+    ) -> Result<Post<ForumUserId<T>, T::ThreadId, T::Hash>, Error<T>> {
         // Ensure the moderator can moderate the category
         Self::ensure_can_moderate_category(account_id, &actor, &category_id)?;
 
@@ -1343,7 +1341,7 @@ impl<T: Trait> Module<T> {
     ) -> Result<
         (
             Category<T::CategoryId, T::ThreadId, T::Hash>,
-            Thread<T::ForumUserId, T::CategoryId, T::Moment, T::Hash>,
+            Thread<ForumUserId<T>, T::CategoryId, T::Moment, T::Hash>,
         ),
         Error<T>,
     > {
@@ -1368,7 +1366,7 @@ impl<T: Trait> Module<T> {
     ) -> Result<
         (
             Category<T::CategoryId, T::ThreadId, T::Hash>,
-            Thread<T::ForumUserId, T::CategoryId, T::Moment, T::Hash>,
+            Thread<ForumUserId<T>, T::CategoryId, T::Moment, T::Hash>,
         ),
         Error<T>,
     > {
@@ -1386,7 +1384,7 @@ impl<T: Trait> Module<T> {
     fn ensure_thread_exists(
         category_id: &T::CategoryId,
         thread_id: &T::ThreadId,
-    ) -> Result<Thread<T::ForumUserId, T::CategoryId, T::Moment, T::Hash>, Error<T>> {
+    ) -> Result<Thread<ForumUserId<T>, T::CategoryId, T::Moment, T::Hash>, Error<T>> {
         if !<ThreadById<T>>::contains_key(category_id, thread_id) {
             return Err(Error::<T>::ThreadDoesNotExist);
         }
@@ -1398,8 +1396,8 @@ impl<T: Trait> Module<T> {
         account_id: T::AccountId,
         category_id: &T::CategoryId,
         thread_id: &T::ThreadId,
-        forum_user_id: &T::ForumUserId,
-    ) -> Result<Thread<T::ForumUserId, T::CategoryId, T::Moment, T::Hash>, Error<T>> {
+        forum_user_id: &ForumUserId<T>,
+    ) -> Result<Thread<ForumUserId<T>, T::CategoryId, T::Moment, T::Hash>, Error<T>> {
         // Check that account is forum member
         Self::ensure_is_forum_user(account_id, &forum_user_id)?;
 
@@ -1413,8 +1411,8 @@ impl<T: Trait> Module<T> {
     }
 
     fn ensure_is_thread_author(
-        thread: &Thread<T::ForumUserId, T::CategoryId, T::Moment, T::Hash>,
-        forum_user_id: &T::ForumUserId,
+        thread: &Thread<ForumUserId<T>, T::CategoryId, T::Moment, T::Hash>,
+        forum_user_id: &ForumUserId<T>,
     ) -> Result<(), Error<T>> {
         ensure!(
             thread.author_id == *forum_user_id,
@@ -1450,9 +1448,10 @@ impl<T: Trait> Module<T> {
     /// Ensure forum user id registered and its account id matched
     fn ensure_is_forum_user(
         account_id: T::AccountId,
-        forum_user_id: &T::ForumUserId,
+        forum_user_id: &ForumUserId<T>,
     ) -> Result<(), Error<T>> {
-        let is_member = T::is_forum_member(&account_id, forum_user_id);
+        let is_member =
+            T::MemberOriginValidator::is_member_controller_account(forum_user_id, &account_id);
 
         ensure!(is_member, Error::<T>::ForumUserIdNotMatchAccount);
         Ok(())
@@ -1476,7 +1475,7 @@ impl<T: Trait> Module<T> {
         actor: &PrivilegedActor<T>,
         category_id: &T::CategoryId,
         thread_id: &T::ThreadId,
-    ) -> Result<Thread<T::ForumUserId, T::CategoryId, T::Moment, T::Hash>, Error<T>> {
+    ) -> Result<Thread<ForumUserId<T>, T::CategoryId, T::Moment, T::Hash>, Error<T>> {
         // Check that account is forum member
         Self::ensure_can_moderate_category(account_id, actor, category_id)?;
 
@@ -1491,7 +1490,7 @@ impl<T: Trait> Module<T> {
         category_id: &T::CategoryId,
         thread_id: &T::ThreadId,
         new_category_id: &T::CategoryId,
-    ) -> Result<Thread<T::ForumUserId, T::CategoryId, T::Moment, T::Hash>, Error<T>> {
+    ) -> Result<Thread<ForumUserId<T>, T::CategoryId, T::Moment, T::Hash>, Error<T>> {
         ensure!(
             category_id != new_category_id,
             Error::<T>::ThreadMoveInvalid,
@@ -1561,8 +1560,12 @@ impl<T: Trait> Module<T> {
         // Get path from parent to root of category tree.
         let category_tree_path = Self::build_category_tree_path(&category_id);
 
-        if category_tree_path.len() == 0 {
-            debug_assert!(false, "Should not fail! {:?}");
+        if category_tree_path.is_empty() {
+            debug_assert!(
+                false,
+                "Should not fail! {:?}",
+                Error::<T>::PathLengthShouldBeGreaterThanZero
+            );
             Err(Error::<T>::PathLengthShouldBeGreaterThanZero)
         } else {
             Ok(category_tree_path)
@@ -1750,7 +1753,7 @@ impl<T: Trait> Module<T> {
 
     fn ensure_can_create_thread(
         account_id: T::AccountId,
-        forum_user_id: &T::ForumUserId,
+        forum_user_id: &ForumUserId<T>,
         category_id: &T::CategoryId,
     ) -> Result<Category<T::CategoryId, T::ThreadId, T::Hash>, Error<T>> {
         // Check that account is forum member
@@ -1767,13 +1770,13 @@ impl<T: Trait> Module<T> {
 
     fn ensure_can_add_post(
         account_id: T::AccountId,
-        forum_user_id: &T::ForumUserId,
+        forum_user_id: &ForumUserId<T>,
         category_id: &T::CategoryId,
         thread_id: &T::ThreadId,
     ) -> Result<
         (
             Category<T::CategoryId, T::ThreadId, T::Hash>,
-            Thread<T::ForumUserId, T::CategoryId, T::Moment, T::Hash>,
+            Thread<ForumUserId<T>, T::CategoryId, T::Moment, T::Hash>,
         ),
         Error<T>,
     > {
@@ -1804,7 +1807,7 @@ impl<T: Trait> Module<T> {
 
     /// Check the vote is valid
     fn ensure_vote_is_valid(
-        thread: Thread<T::ForumUserId, T::CategoryId, T::Moment, T::Hash>,
+        thread: Thread<ForumUserId<T>, T::CategoryId, T::Moment, T::Hash>,
         index: u32,
     ) -> Result<Poll<T::Moment, T::Hash>, Error<T>> {
         // Ensure poll exists

+ 36 - 30
runtime-modules/forum/src/mock.rs

@@ -13,7 +13,7 @@ use frame_support::{impl_outer_event, impl_outer_origin, parameter_types};
 use sp_runtime::{
     testing::Header,
     traits::{BlakeTwo256, Hash, IdentityLookup},
-    Perbill,
+    DispatchError, Perbill,
 };
 
 impl_outer_origin! {
@@ -34,20 +34,6 @@ impl_outer_event! {
     }
 }
 
-pub const ACTOR_ORIGIN_ERROR: &'static str = "Invalid membership";
-
-impl common::origin::ActorOriginValidator<Origin, u64, u64> for () {
-    fn ensure_actor_origin(origin: Origin, member_id: u64) -> Result<u64, &'static str> {
-        let signed_account_id = frame_system::ensure_signed(origin)?;
-
-        if member_id > 10 {
-            return Err(ACTOR_ORIGIN_ERROR);
-        }
-
-        Ok(signed_account_id)
-    }
-}
-
 // Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted.
 #[derive(Clone, PartialEq, Eq, Debug)]
 pub struct Runtime;
@@ -115,6 +101,7 @@ impl common::Trait for Runtime {
 parameter_types! {
     pub const MaxWorkerNumberLimit: u32 = 3;
     pub const LockId: [u8; 8] = [9; 8];
+    pub const InviteMemberLockId: [u8; 8] = [9; 8];
 }
 
 // The forum working group instance alias.
@@ -241,6 +228,7 @@ impl membership::Trait for Runtime {
     type DefaultMembershipPrice = DefaultMembershipPrice;
     type DefaultInitialInvitationBalance = DefaultInitialInvitationBalance;
     type WorkingGroup = working_group::Module<Self, ForumWorkingGroupInstance>;
+    type InvitedMemberStakingHandler = staking_handler::StakingManager<Self, InviteMemberLockId>;
 }
 
 parameter_types! {
@@ -267,7 +255,6 @@ impl StorageLimits for MapLimits {
 
 impl Trait for Runtime {
     type Event = TestEvent;
-    type ForumUserId = u64;
     type CategoryId = u64;
     type ThreadId = u64;
     type PostId = u64;
@@ -276,21 +263,40 @@ impl Trait for Runtime {
 
     type MapLimits = MapLimits;
     type WorkingGroup = ();
+    type MemberOriginValidator = ();
+
+    fn calculate_hash(text: &[u8]) -> Self::Hash {
+        Self::Hashing::hash(text)
+    }
+
     type WeightInfo = ();
+}
 
-    fn is_forum_member(
-        account_id: &<Self as frame_system::Trait>::AccountId,
-        _forum_user_id: &Self::ForumUserId,
-    ) -> bool {
-        *account_id != NOT_FORUM_MEMBER_ORIGIN_ID
+impl common::origin::MemberOriginValidator<Origin, u64, u64> for () {
+    fn ensure_member_controller_account_origin(
+        origin: Origin,
+        member_id: u64,
+    ) -> Result<u64, DispatchError> {
+        let account_id = ensure_signed(origin).unwrap();
+        ensure!(
+            Self::is_member_controller_account(&member_id, &account_id),
+            DispatchError::BadOrigin
+        );
+        Ok(account_id)
     }
 
-    fn calculate_hash(text: &[u8]) -> Self::Hash {
-        Self::Hashing::hash(text)
+    fn is_member_controller_account(member_id: &u64, account_id: &u64) -> bool {
+        let allowed_accounts = [
+            FORUM_LEAD_ORIGIN_ID,
+            NOT_FORUM_LEAD_ORIGIN_ID,
+            NOT_FORUM_LEAD_2_ORIGIN_ID,
+        ];
+
+        allowed_accounts.contains(account_id) && account_id == member_id
     }
 }
 
-impl common::working_group::WorkingGroupIntegration<Runtime> for () {
+impl common::working_group::WorkingGroupAuthenticator<Runtime> for () {
     fn ensure_worker_origin(
         _origin: <Runtime as frame_system::Trait>::Origin,
         _worker_id: &<Runtime as common::Trait>::ActorId,
@@ -539,7 +545,7 @@ pub fn create_category_mock(
 
 pub fn create_thread_mock(
     origin: OriginType,
-    forum_user_id: <Runtime as Trait>::ForumUserId,
+    forum_user_id: ForumUserId<Runtime>,
     category_id: <Runtime as Trait>::CategoryId,
     title: Vec<u8>,
     text: Vec<u8>,
@@ -572,7 +578,7 @@ pub fn create_thread_mock(
 
 pub fn edit_thread_title_mock(
     origin: OriginType,
-    forum_user_id: <Runtime as Trait>::ForumUserId,
+    forum_user_id: ForumUserId<Runtime>,
     category_id: <Runtime as Trait>::CategoryId,
     thread_id: <Runtime as Trait>::PostId,
     new_title: Vec<u8>,
@@ -692,7 +698,7 @@ pub fn update_thread_archival_status_mock(
 
 pub fn create_post_mock(
     origin: OriginType,
-    forum_user_id: <Runtime as Trait>::ForumUserId,
+    forum_user_id: ForumUserId<Runtime>,
     category_id: <Runtime as Trait>::CategoryId,
     thread_id: <Runtime as Trait>::ThreadId,
     text: Vec<u8>,
@@ -721,7 +727,7 @@ pub fn create_post_mock(
 
 pub fn edit_post_text_mock(
     origin: OriginType,
-    forum_user_id: <Runtime as Trait>::ForumUserId,
+    forum_user_id: ForumUserId<Runtime>,
     category_id: <Runtime as Trait>::CategoryId,
     thread_id: <Runtime as Trait>::ThreadId,
     post_id: <Runtime as Trait>::PostId,
@@ -792,7 +798,7 @@ pub fn update_category_membership_of_moderator_mock(
 
 pub fn vote_on_poll_mock(
     origin: OriginType,
-    forum_user_id: <Runtime as Trait>::ForumUserId,
+    forum_user_id: ForumUserId<Runtime>,
     category_id: <Runtime as Trait>::CategoryId,
     thread_id: <Runtime as Trait>::ThreadId,
     index: u32,
@@ -962,7 +968,7 @@ pub fn set_stickied_threads_mock(
 
 pub fn react_post_mock(
     origin: OriginType,
-    forum_user_id: <Runtime as Trait>::ForumUserId,
+    forum_user_id: ForumUserId<Runtime>,
     category_id: <Runtime as Trait>::CategoryId,
     thread_id: <Runtime as Trait>::ThreadId,
     post_id: <Runtime as Trait>::PostId,

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

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

+ 49 - 25
runtime-modules/membership/src/benchmarking.rs

@@ -1,11 +1,11 @@
 #![cfg(feature = "runtime-benchmarks")]
 use super::*;
-// use crate::Module as Membership;
 use crate::{
-    BuyMembershipParameters, MemberIdByHandleHash, MemberIdsByControllerAccountId,
-    MemberIdsByRootAccountId, Membership, MembershipById, MembershipObject, Trait,
+    BuyMembershipParameters, MemberIdByHandleHash, Membership, MembershipById, MembershipObject,
+    Trait,
 };
 use balances::Module as Balances;
+use common::working_group::MembershipWorkingGroupHelper;
 use core::convert::TryInto;
 use frame_benchmarking::{account, benchmarks};
 use frame_support::storage::StorageMap;
@@ -140,10 +140,6 @@ benchmarks! {
             invites: 5,
         };
 
-        assert_eq!(MemberIdsByRootAccountId::<T>::get(&account_id), vec![member_id]);
-
-        assert_eq!(MemberIdsByControllerAccountId::<T>::get(&account_id), vec![member_id]);
-
         assert_eq!(MemberIdByHandleHash::<T>::get(&handle_hash), member_id);
 
         assert_eq!(MembershipById::<T>::get(member_id), membership);
@@ -211,10 +207,6 @@ benchmarks! {
 
         let second_member_id = member_id + T::MemberId::one();
 
-        assert_eq!(MemberIdsByRootAccountId::<T>::get(&account_id), vec![member_id, second_member_id]);
-
-        assert_eq!(MemberIdsByControllerAccountId::<T>::get(&account_id), vec![member_id, second_member_id]);
-
         assert_eq!(MemberIdByHandleHash::<T>::get(second_handle_hash), second_member_id);
 
         assert_eq!(MembershipById::<T>::get(second_member_id), membership);
@@ -294,10 +286,6 @@ benchmarks! {
             invites: 5,
         };
 
-        assert_eq!(MemberIdsByRootAccountId::<T>::get(&new_root_account_id), vec![member_id]);
-
-        assert_eq!(MemberIdsByRootAccountId::<T>::get(&account_id), vec![]);
-
         assert_eq!(MembershipById::<T>::get(member_id), membership);
 
         assert_last_event::<T>(RawEvent::MemberAccountsUpdated(member_id).into());
@@ -327,10 +315,6 @@ benchmarks! {
             invites: 5,
         };
 
-        assert_eq!(MemberIdsByControllerAccountId::<T>::get(&new_controller_account_id), vec![member_id]);
-
-        assert_eq!(MemberIdsByControllerAccountId::<T>::get(&account_id), vec![]);
-
         assert_eq!(MembershipById::<T>::get(member_id), membership);
 
         assert_last_event::<T>(RawEvent::MemberAccountsUpdated(member_id).into());
@@ -362,12 +346,6 @@ benchmarks! {
             invites: 5,
         };
 
-        assert_eq!(MemberIdsByControllerAccountId::<T>::get(&new_controller_account_id), vec![member_id]);
-        assert_eq!(MemberIdsByControllerAccountId::<T>::get(&account_id), vec![]);
-
-        assert_eq!(MemberIdsByRootAccountId::<T>::get(&new_root_account_id), vec![member_id]);
-        assert_eq!(MemberIdsByRootAccountId::<T>::get(&account_id), vec![]);
-
         assert_eq!(MembershipById::<T>::get(member_id), membership);
 
         assert_last_event::<T>(RawEvent::MemberAccountsUpdated(member_id).into());
@@ -432,6 +410,38 @@ benchmarks! {
 
         assert_last_event::<T>(RawEvent::InvitesTransferred(first_member_id, second_member_id, number_of_invites).into());
     }
+
+    set_membership_price {
+        let membership_price: BalanceOf<T> = 1000.into();
+
+    }: _(RawOrigin::Root, membership_price)
+    verify {
+        assert_eq!(Module::<T>::membership_price(), membership_price);
+
+        assert_last_event::<T>(RawEvent::MembershipPriceUpdated(membership_price).into());
+    }
+
+    set_leader_invitation_quota {
+        // Set leader member id
+
+        let member_id = 0;
+
+        let (account_id, member_id) = member_funded_account::<T>("member", member_id);
+
+        // Set leader member id
+        T::WorkingGroup::insert_a_lead(0, account_id, member_id);
+
+        let leader_member_id = T::WorkingGroup::get_leader_member_id();
+
+        let invitation_quota = 100;
+
+    }: _(RawOrigin::Root, invitation_quota)
+    verify {
+
+        assert_eq!(MembershipById::<T>::get(leader_member_id.unwrap()).invites, invitation_quota);
+
+        assert_last_event::<T>(RawEvent::LeaderInvitationQuotaUpdated(invitation_quota).into());
+    }
 }
 
 #[cfg(test)]
@@ -502,4 +512,18 @@ mod tests {
             assert_ok!(test_benchmark_transfer_invites::<Test>());
         });
     }
+
+    #[test]
+    fn set_membership_price() {
+        build_test_externalities().execute_with(|| {
+            assert_ok!(test_benchmark_set_membership_price::<Test>());
+        });
+    }
+
+    #[test]
+    fn set_leader_invitation_quota() {
+        build_test_externalities().execute_with(|| {
+            assert_ok!(test_benchmark_set_leader_invitation_quota::<Test>());
+        });
+    }
 }

+ 71 - 51
runtime-modules/membership/src/lib.rs

@@ -49,15 +49,17 @@ pub mod genesis;
 mod tests;
 
 use codec::{Decode, Encode};
-use frame_support::traits::{Currency, Get};
+use frame_support::dispatch::DispatchError;
+use frame_support::traits::{Currency, Get, WithdrawReason, WithdrawReasons};
 use frame_support::{decl_error, decl_event, decl_module, decl_storage, ensure};
-use frame_system::ensure_root;
-use frame_system::ensure_signed;
+use frame_system::{ensure_root, ensure_signed};
 use sp_arithmetic::traits::{One, Zero};
-use sp_runtime::traits::Hash;
+use sp_runtime::traits::{Hash, Saturating};
 use sp_std::vec::Vec;
 
-use common::working_group::WorkingGroupIntegration;
+use common::origin::MemberOriginValidator;
+use common::working_group::{WorkingGroupAuthenticator, WorkingGroupBudgetHandler};
+use staking_handler::StakingHandler;
 
 // Balance type alias
 type BalanceOf<T> = <T as balances::Trait>::Balance;
@@ -72,10 +74,19 @@ pub trait Trait:
     type DefaultMembershipPrice: Get<BalanceOf<Self>>;
 
     /// Working group pallet integration.
-    type WorkingGroup: common::working_group::WorkingGroupIntegration<Self>;
+    type WorkingGroup: common::working_group::WorkingGroupAuthenticator<Self>
+        + common::working_group::WorkingGroupBudgetHandler<Self>
+        + common::working_group::MembershipWorkingGroupHelper<Self>;
 
     /// Defines the default balance for the invited member.
     type DefaultInitialInvitationBalance: Get<BalanceOf<Self>>;
+
+    /// Staking handler used for invited member staking.
+    type InvitedMemberStakingHandler: StakingHandler<
+        Self::AccountId,
+        BalanceOf<Self>,
+        Self::MemberId,
+    >;
 }
 
 pub(crate) const DEFAULT_MEMBER_INVITES_COUNT: u32 = 5;
@@ -212,6 +223,10 @@ decl_error! {
 
         /// Staking account has already been confirmed.
         StakingAccountAlreadyConfirmed,
+
+        /// Cannot invite a member. Working group balance is not sufficient to set the default
+        /// balance.
+        WorkingGroupBudgetIsNotSufficientForInviting,
     }
 }
 
@@ -225,14 +240,6 @@ decl_storage! {
         pub MembershipById get(fn membership) : map hasher(blake2_128_concat)
             T::MemberId => Membership<T>;
 
-        /// Mapping of a root account id to vector of member ids it controls.
-        pub(crate) MemberIdsByRootAccountId : map hasher(blake2_128_concat)
-            T::AccountId => Vec<T::MemberId>;
-
-        /// Mapping of a controller account id to vector of member ids it controls.
-        pub(crate) MemberIdsByControllerAccountId : map hasher(blake2_128_concat)
-            T::AccountId => Vec<T::MemberId>;
-
         /// Registered unique handles hash and their mapping to their owner.
         pub MemberIdByHandleHash get(fn handles) : map hasher(blake2_128_concat)
             Vec<u8> => T::MemberId;
@@ -381,7 +388,7 @@ decl_module! {
                 return Ok(())
             }
 
-            Self::ensure_member_controller_account_signed(origin, &member_id)?;
+            Self::ensure_member_controller_account_origin_signed(origin, &member_id)?;
 
             let membership = Self::ensure_membership(member_id)?;
 
@@ -430,26 +437,10 @@ decl_module! {
             //
 
             if let Some(root_account) = new_root_account {
-                <MemberIdsByRootAccountId<T>>::mutate(&membership.root_account, |ids| {
-                    ids.retain(|id| *id != member_id);
-                });
-
-                <MemberIdsByRootAccountId<T>>::mutate(&root_account, |ids| {
-                    ids.push(member_id);
-                });
-
                 membership.root_account = root_account;
             }
 
             if let Some(controller_account) = new_controller_account {
-                <MemberIdsByControllerAccountId<T>>::mutate(&membership.controller_account, |ids| {
-                    ids.retain(|id| *id != member_id);
-                });
-
-                <MemberIdsByControllerAccountId<T>>::mutate(&controller_account, |ids| {
-                    ids.push(member_id);
-                });
-
                 membership.controller_account = controller_account;
             }
 
@@ -504,7 +495,7 @@ decl_module! {
             target_member_id: T::MemberId,
             number_of_invites: u32
         ) {
-            Self::ensure_member_controller_account_signed(origin, &source_member_id)?;
+            Self::ensure_member_controller_account_origin_signed(origin, &source_member_id)?;
 
             let source_membership = Self::ensure_membership(source_member_id)?;
             Self::ensure_membership_with_error(
@@ -541,7 +532,7 @@ decl_module! {
             origin,
             params: InviteMembershipParameters<T::AccountId, T::MemberId>
         ) {
-            let membership = Self::ensure_member_controller_account_signed(
+            let membership = Self::ensure_member_controller_account_origin_signed(
                 origin,
                 &params.inviting_member_id
             )?;
@@ -552,6 +543,14 @@ decl_module! {
                 params.handle,
             )?;
 
+            let current_wg_budget = T::WorkingGroup::get_budget();
+            let default_invitation_balance = T::DefaultInitialInvitationBalance::get();
+
+            ensure!(
+                default_invitation_balance <= current_wg_budget,
+                Error::<T>::WorkingGroupBudgetIsNotSufficientForInviting
+            );
+
             //
             // == MUTATION SAFE ==
             //
@@ -568,6 +567,23 @@ decl_module! {
                 membership.invites = membership.invites.saturating_sub(1);
             });
 
+            // Decrease the working group balance.
+            let new_wg_budget = current_wg_budget.saturating_sub(default_invitation_balance);
+            T::WorkingGroup::set_budget(new_wg_budget);
+
+            // Create default balance for the invited member.
+            let _ = balances::Module::<T>::deposit_creating(
+                &params.controller_account,
+                default_invitation_balance
+            );
+
+            // Lock invitation balance. Allow only transaction payments.
+            T::InvitedMemberStakingHandler::lock_with_reasons(
+                &params.controller_account,
+                default_invitation_balance,
+                WithdrawReasons::except(WithdrawReason::TransactionPayment)
+            );
+
             // Fire the event.
             Self::deposit_event(RawEvent::MemberRegistered(member_id));
         }
@@ -696,7 +712,7 @@ decl_module! {
             member_id: T::MemberId,
             staking_account_id: T::AccountId,
         ) {
-            Self::ensure_member_controller_account_signed(origin, &member_id)?;
+            Self::ensure_member_controller_account_origin_signed(origin, &member_id)?;
 
             ensure!(
                 Self::staking_account_registered_for_member(&staking_account_id, &member_id),
@@ -727,7 +743,7 @@ decl_module! {
 
 impl<T: Trait> Module<T> {
     /// Provided that the member_id exists return its membership. Returns error otherwise.
-    pub fn ensure_membership(member_id: T::MemberId) -> Result<Membership<T>, Error<T>> {
+    fn ensure_membership(member_id: T::MemberId) -> Result<Membership<T>, Error<T>> {
         Self::ensure_membership_with_error(member_id, Error::<T>::MemberProfileNotFound)
     }
 
@@ -743,12 +759,6 @@ impl<T: Trait> Module<T> {
         }
     }
 
-    /// Returns true if account is either a member's root or controller account
-    pub fn is_member_account(who: &T::AccountId) -> bool {
-        <MemberIdsByRootAccountId<T>>::contains_key(who)
-            || <MemberIdsByControllerAccountId<T>>::contains_key(who)
-    }
-
     // Ensure possible member handle hash is unique.
     fn ensure_unique_handle_hash(handle_hash: Vec<u8>) -> Result<(), Error<T>> {
         ensure!(
@@ -792,13 +802,6 @@ impl<T: Trait> Module<T> {
             invites: allowed_invites,
         };
 
-        <MemberIdsByRootAccountId<T>>::mutate(root_account, |ids| {
-            ids.push(new_member_id);
-        });
-        <MemberIdsByControllerAccountId<T>>::mutate(controller_account, |ids| {
-            ids.push(new_member_id);
-        });
-
         <MembershipById<T>>::insert(new_member_id, membership);
         <MemberIdByHandleHash<T>>::insert(handle_hash, new_member_id);
 
@@ -807,7 +810,7 @@ impl<T: Trait> Module<T> {
     }
 
     // Ensure origin corresponds to the controller account of the member.
-    fn ensure_member_controller_account_signed(
+    fn ensure_member_controller_account_origin_signed(
         origin: T::Origin,
         member_id: &T::MemberId,
     ) -> Result<Membership<T>, Error<T>> {
@@ -817,8 +820,8 @@ impl<T: Trait> Module<T> {
         Self::ensure_is_controller_account_for_member(member_id, &signer_account_id)
     }
 
-    /// Ensure that given member has given account as the controller account
-    pub fn ensure_is_controller_account_for_member(
+    // Ensure that given member has given account as the controller account
+    fn ensure_is_controller_account_for_member(
         member_id: &T::MemberId,
         account: &T::AccountId,
     ) -> Result<Membership<T>, Error<T>> {
@@ -887,3 +890,20 @@ impl<T: Trait> common::StakingAccountValidator<T> for Module<T> {
         Self::staking_account_confirmed(account_id, member_id)
     }
 }
+
+impl<T: Trait> MemberOriginValidator<T::Origin, T::MemberId, T::AccountId> for Module<T> {
+    fn ensure_member_controller_account_origin(
+        origin: T::Origin,
+        actor_id: T::MemberId,
+    ) -> Result<T::AccountId, DispatchError> {
+        let signer_account_id = ensure_signed(origin).map_err(|_| Error::<T>::UnsignedOrigin)?;
+
+        Self::ensure_is_controller_account_for_member(&actor_id, &signer_account_id)?;
+
+        Ok(signer_account_id)
+    }
+
+    fn is_member_controller_account(member_id: &T::MemberId, account_id: &T::AccountId) -> bool {
+        Self::ensure_is_controller_account_for_member(member_id, account_id).is_ok()
+    }
+}

+ 42 - 5
runtime-modules/membership/src/tests/mock.rs

@@ -2,10 +2,10 @@
 
 pub use crate::{GenesisConfig, Trait};
 
-use staking_handler::LockComparator;
-
 pub use frame_support::traits::{Currency, LockIdentifier};
 use frame_support::{impl_outer_event, impl_outer_origin, parameter_types};
+use sp_std::cell::RefCell;
+use staking_handler::LockComparator;
 
 use crate::tests::fixtures::ALICE_MEMBER_ID;
 pub use frame_system;
@@ -85,6 +85,7 @@ impl pallet_timestamp::Trait for Test {
 parameter_types! {
     pub const ExistentialDeposit: u32 = 0;
     pub const DefaultMembershipPrice: u64 = 100;
+    pub const InvitedMemberLockId: [u8; 8] = [2; 8];
 }
 
 impl balances::Trait for Test {
@@ -224,12 +225,19 @@ impl working_group::WeightInfo for Weights {
     }
 }
 
-impl common::origin::ActorOriginValidator<Origin, u64, u64> for () {
-    fn ensure_actor_origin(origin: Origin, _: u64) -> Result<u64, &'static str> {
+impl common::origin::MemberOriginValidator<Origin, u64, u64> for () {
+    fn ensure_member_controller_account_origin(
+        origin: Origin,
+        _: u64,
+    ) -> Result<u64, DispatchError> {
         let account_id = frame_system::ensure_signed(origin)?;
 
         Ok(account_id)
     }
+
+    fn is_member_controller_account(_member_id: &u64, _account_id: &u64) -> bool {
+        unimplemented!()
+    }
 }
 
 impl Trait for Test {
@@ -237,9 +245,28 @@ impl Trait for Test {
     type DefaultMembershipPrice = DefaultMembershipPrice;
     type WorkingGroup = ();
     type DefaultInitialInvitationBalance = DefaultInitialInvitationBalance;
+    type InvitedMemberStakingHandler = staking_handler::StakingManager<Self, InvitedMemberLockId>;
+}
+
+pub const WORKING_GROUP_BUDGET: u64 = 100;
+
+thread_local! {
+    pub static WG_BUDGET: RefCell<u64> = RefCell::new(WORKING_GROUP_BUDGET);
 }
 
-impl common::working_group::WorkingGroupIntegration<Test> for () {
+impl common::working_group::WorkingGroupBudgetHandler<Test> for () {
+    fn get_budget() -> u64 {
+        WG_BUDGET.with(|val| *val.borrow())
+    }
+
+    fn set_budget(new_value: u64) {
+        WG_BUDGET.with(|val| {
+            *val.borrow_mut() = new_value;
+        });
+    }
+}
+
+impl common::working_group::WorkingGroupAuthenticator<Test> for () {
     fn ensure_worker_origin(
         origin: <Test as frame_system::Trait>::Origin,
         worker_id: &<Test as common::Trait>::ActorId,
@@ -278,6 +305,16 @@ impl common::working_group::WorkingGroupIntegration<Test> for () {
     }
 }
 
+impl common::working_group::MembershipWorkingGroupHelper<Test> for () {
+    fn insert_a_lead(
+        _opening_id: u32,
+        _caller_id: <Test as frame_system::Trait>::AccountId,
+        _member_id: <Test as common::Trait>::MemberId,
+    ) -> <Test as common::Trait>::ActorId {
+        ALICE_MEMBER_ID
+    }
+}
+
 pub struct TestExternalitiesBuilder<T: Trait> {
     system_config: Option<frame_system::GenesisConfig>,
     membership_config: Option<GenesisConfig<T>>,

+ 79 - 26
runtime-modules/membership/src/tests/mod.rs

@@ -7,6 +7,8 @@ use crate::{Error, Event};
 pub use fixtures::*;
 pub use mock::*;
 
+use common::origin::MemberOriginValidator;
+use common::working_group::WorkingGroupBudgetHandler;
 use common::StakingAccountValidator;
 use frame_support::traits::{LockIdentifier, LockableCurrency, WithdrawReasons};
 use frame_support::{assert_ok, StorageMap, StorageValue};
@@ -36,10 +38,6 @@ fn buy_membership_succeeds() {
 
         // controller account initially set to primary account
         assert_eq!(profile.controller_account, ALICE_ACCOUNT_ID);
-        assert_eq!(
-            <crate::MemberIdsByControllerAccountId<Test>>::get(ALICE_ACCOUNT_ID),
-            vec![next_member_id]
-        );
 
         EventFixture::assert_last_crate_event(Event::<Test>::MemberRegistered(next_member_id));
     });
@@ -172,18 +170,7 @@ fn update_profile_accounts_succeeds() {
         let profile = get_membership_by_id(ALICE_MEMBER_ID);
 
         assert_eq!(profile.controller_account, ALICE_NEW_ACCOUNT_ID);
-        assert_eq!(
-            <crate::MemberIdsByControllerAccountId<Test>>::get(&ALICE_NEW_ACCOUNT_ID),
-            vec![ALICE_MEMBER_ID]
-        );
-        assert!(<crate::MemberIdsByControllerAccountId<Test>>::get(&ALICE_ACCOUNT_ID).is_empty());
-
         assert_eq!(profile.root_account, ALICE_NEW_ACCOUNT_ID);
-        assert_eq!(
-            <crate::MemberIdsByRootAccountId<Test>>::get(&ALICE_NEW_ACCOUNT_ID),
-            vec![ALICE_MEMBER_ID]
-        );
-        assert!(<crate::MemberIdsByRootAccountId<Test>>::get(&ALICE_ACCOUNT_ID).is_empty());
 
         EventFixture::assert_last_crate_event(Event::<Test>::MemberAccountsUpdated(
             ALICE_MEMBER_ID,
@@ -206,16 +193,7 @@ fn update_accounts_has_effect_on_empty_account_parameters() {
         let profile = get_membership_by_id(ALICE_MEMBER_ID);
 
         assert_eq!(profile.controller_account, ALICE_ACCOUNT_ID);
-        assert_eq!(
-            <crate::MemberIdsByControllerAccountId<Test>>::get(&ALICE_ACCOUNT_ID),
-            vec![ALICE_MEMBER_ID]
-        );
-
         assert_eq!(profile.root_account, ALICE_ACCOUNT_ID);
-        assert_eq!(
-            <crate::MemberIdsByRootAccountId<Test>>::get(&ALICE_ACCOUNT_ID),
-            vec![ALICE_MEMBER_ID]
-        );
     });
 }
 
@@ -490,15 +468,45 @@ fn invite_member_succeeds() {
 
         // controller account initially set to primary account
         assert_eq!(profile.controller_account, BOB_ACCOUNT_ID);
+
+        let initial_invitation_balance = <Test as Trait>::DefaultInitialInvitationBalance::get();
+        // Working group budget reduced.
+        assert_eq!(
+            WORKING_GROUP_BUDGET - initial_invitation_balance,
+            <Test as Trait>::WorkingGroup::get_budget()
+        );
+
+        // Invited member account filled.
         assert_eq!(
-            <crate::MemberIdsByControllerAccountId<Test>>::get(BOB_ACCOUNT_ID),
-            vec![bob_member_id]
+            initial_invitation_balance,
+            Balances::free_balance(&profile.controller_account)
         );
 
+        // Invited member balance locked.
+        assert_eq!(0, Balances::usable_balance(&profile.controller_account));
+
         EventFixture::assert_last_crate_event(Event::<Test>::MemberRegistered(bob_member_id));
     });
 }
 
+#[test]
+fn invite_member_fails_with_insufficient_working_group_balance() {
+    build_test_externalities().execute_with(|| {
+        let initial_balance = DefaultMembershipPrice::get();
+        set_alice_free_balance(initial_balance);
+
+        assert_ok!(buy_default_membership_as_alice());
+
+        WG_BUDGET.with(|val| {
+            *val.borrow_mut() = 50;
+        });
+
+        InviteMembershipFixture::default().call_and_assert(Err(
+            Error::<Test>::WorkingGroupBudgetIsNotSufficientForInviting.into(),
+        ));
+    });
+}
+
 #[test]
 fn invite_member_fails_with_bad_origin() {
     build_test_externalities().execute_with(|| {
@@ -905,3 +913,48 @@ fn is_member_staking_account_works() {
         );
     });
 }
+
+#[test]
+fn membership_origin_validator_fails_with_unregistered_member() {
+    build_test_externalities().execute_with(|| {
+        let origin = RawOrigin::Signed(ALICE_ACCOUNT_ID);
+        let error = Error::<Test>::MemberProfileNotFound;
+
+        let validation_result =
+            Membership::ensure_member_controller_account_origin(origin.into(), ALICE_MEMBER_ID);
+
+        assert_eq!(validation_result, Err(error.into()));
+    });
+}
+
+#[test]
+fn membership_origin_validator_succeeds() {
+    let initial_members = [(ALICE_MEMBER_ID, ALICE_ACCOUNT_ID)];
+
+    build_test_externalities_with_initial_members(initial_members.to_vec()).execute_with(|| {
+        let account_id = ALICE_ACCOUNT_ID;
+        let origin = RawOrigin::Signed(account_id.clone());
+
+        let validation_result =
+            Membership::ensure_member_controller_account_origin(origin.into(), ALICE_MEMBER_ID);
+
+        assert_eq!(validation_result, Ok(account_id));
+    });
+}
+
+#[test]
+fn membership_origin_validator_fails_with_incompatible_account_id_and_member_id() {
+    let initial_members = [(ALICE_MEMBER_ID, ALICE_ACCOUNT_ID)];
+
+    build_test_externalities_with_initial_members(initial_members.to_vec()).execute_with(|| {
+        let error = Error::<Test>::ControllerAccountRequired;
+
+        let invalid_account_id = BOB_ACCOUNT_ID;
+        let validation_result = Membership::ensure_member_controller_account_origin(
+            RawOrigin::Signed(invalid_account_id.into()).into(),
+            ALICE_MEMBER_ID,
+        );
+
+        assert_eq!(validation_result, Err(error.into()));
+    });
+}

+ 4 - 7
runtime-modules/proposals/codex/src/lib.rs

@@ -70,7 +70,7 @@ pub use crate::types::{
     AddOpeningParameters, FillOpeningParameters, GeneralProposalParams, ProposalDetails,
     ProposalDetailsOf, ProposalEncoder, TerminateRoleParameters,
 };
-use common::origin::ActorOriginValidator;
+use common::origin::MemberOriginValidator;
 use common::MemberId;
 use proposals_discussion::ThreadMode;
 use proposals_engine::{
@@ -93,7 +93,7 @@ pub trait Trait:
     + staking::Trait
 {
     /// Validates member id and origin combination.
-    type MembershipOriginValidator: ActorOriginValidator<
+    type MembershipOriginValidator: MemberOriginValidator<
         Self::Origin,
         MemberId<Self>,
         Self::AccountId,
@@ -301,7 +301,7 @@ decl_module! {
             let proposal_code = T::ProposalEncoder::encode_proposal(proposal_details.clone());
 
             let account_id =
-                T::MembershipOriginValidator::ensure_actor_origin(
+                T::MembershipOriginValidator::ensure_member_controller_account_origin(
                     origin,
                     general_proposal_parameters.member_id
                 )?;
@@ -422,10 +422,7 @@ impl<T: Trait> Module<T> {
                 );
             }
             ProposalDetails::SlashWorkingGroupLeaderStake(_, ref penalty, _) => {
-                ensure!(
-                    penalty.slashing_amount != Zero::zero(),
-                    Error::<T>::SlashingStakeIsZero
-                );
+                ensure!(*penalty != Zero::zero(), Error::<T>::SlashingStakeIsZero);
             }
             ProposalDetails::SetWorkingGroupLeaderReward(..) => {
                 // Note: No checks for this proposal for now

+ 45 - 22
runtime-modules/proposals/codex/src/tests/mock.rs

@@ -15,8 +15,8 @@ use sp_staking::SessionIndex;
 use staking_handler::{LockComparator, StakingManager};
 
 use crate::{ProposalDetailsOf, ProposalEncoder, ProposalParameters};
+use frame_support::dispatch::DispatchError;
 use proposals_engine::VotersParameters;
-use referendum::Balance as BalanceReferendum;
 use sp_runtime::testing::TestXt;
 
 impl_outer_origin! {
@@ -32,6 +32,7 @@ parameter_types! {
     pub const MaximumBlockLength: u32 = 2 * 1024;
     pub const AvailableBlockRatio: Perbill = Perbill::one();
     pub const MinimumPeriod: u64 = 5;
+    pub const InvitedMemberLockId: [u8; 8] = [2; 8];
 }
 
 impl_outer_dispatch! {
@@ -53,9 +54,20 @@ impl membership::Trait for Test {
     type DefaultMembershipPrice = DefaultMembershipPrice;
     type WorkingGroup = ();
     type DefaultInitialInvitationBalance = ();
+    type InvitedMemberStakingHandler = staking_handler::StakingManager<Self, InvitedMemberLockId>;
 }
 
-impl common::working_group::WorkingGroupIntegration<Test> for () {
+impl common::working_group::WorkingGroupBudgetHandler<Test> for () {
+    fn get_budget() -> u64 {
+        unimplemented!()
+    }
+
+    fn set_budget(_new_value: u64) {
+        unimplemented!()
+    }
+}
+
+impl common::working_group::WorkingGroupAuthenticator<Test> for () {
     fn ensure_worker_origin(
         _origin: <Test as frame_system::Trait>::Origin,
         _worker_id: &<Test as common::Trait>::ActorId,
@@ -113,7 +125,7 @@ pub struct MockProposalsEngineWeight;
 impl proposals_engine::Trait for Test {
     type Event = ();
     type ProposerOriginValidator = ();
-    type VoterOriginValidator = ();
+    type CouncilOriginValidator = ();
     type TotalVotersCounter = MockVotersParameters;
     type ProposalId = u32;
     type StakingHandler = StakingManager<Test, LockId>;
@@ -132,7 +144,7 @@ impl proposals_engine::WeightInfo for MockProposalsEngineWeight {
         0
     }
 
-    fn cancel_proposal(_: u32) -> Weight {
+    fn cancel_proposal() -> Weight {
         0
     }
 
@@ -159,6 +171,10 @@ impl proposals_engine::WeightInfo for MockProposalsEngineWeight {
     fn on_initialize_slashed(_: u32) -> Weight {
         0
     }
+
+    fn cancel_active_and_pending_proposals(_: u32) -> u64 {
+        0
+    }
 }
 
 impl Default for crate::Call<Test> {
@@ -167,12 +183,27 @@ impl Default for crate::Call<Test> {
     }
 }
 
-impl common::origin::ActorOriginValidator<Origin, u64, u64> for () {
-    fn ensure_actor_origin(origin: Origin, _: u64) -> Result<u64, &'static str> {
+impl common::origin::MemberOriginValidator<Origin, u64, u64> for () {
+    fn ensure_member_controller_account_origin(
+        origin: Origin,
+        _: u64,
+    ) -> Result<u64, DispatchError> {
         let account_id = frame_system::ensure_signed(origin)?;
 
         Ok(account_id)
     }
+
+    fn is_member_controller_account(member_id: &u64, account_id: &u64) -> bool {
+        member_id == account_id
+    }
+}
+
+impl common::origin::CouncilOriginValidator<Origin, u64, u64> for () {
+    fn ensure_member_consulate(origin: Origin, _: u64) -> DispatchResult {
+        frame_system::ensure_signed(origin)?;
+
+        Ok(())
+    }
 }
 
 parameter_types! {
@@ -459,14 +490,9 @@ impl council::Trait for Test {
     type StakingAccountValidator = ();
     type WeightInfo = CouncilWeightInfo;
 
-    fn is_council_member_account(
-        membership_id: &Self::MemberId,
-        account_id: &<Self as frame_system::Trait>::AccountId,
-    ) -> bool {
-        membership_id == account_id
-    }
-
     fn new_council_elected(_: &[council::CouncilMemberOf<Self>]) {}
+
+    type MemberOriginValidator = ();
 }
 
 impl common::StakingAccountValidator<Test> for () {
@@ -515,6 +541,7 @@ parameter_types! {
     pub const MinimumVotingStake: u64 = 10000;
     pub const MaxSaltLength: u64 = 32; // use some multiple of 8 for ez testing
     pub const VotingLockId: LockIdentifier = *b"referend";
+    pub const MaxWinnerTargetCount: u64 = 10;
 }
 
 impl referendum::Trait<ReferendumInstance> for Test {
@@ -522,9 +549,7 @@ impl referendum::Trait<ReferendumInstance> for Test {
 
     type MaxSaltLength = MaxSaltLength;
 
-    type Currency = balances::Module<Self>;
-    type LockId = VotingLockId;
-
+    type StakingHandler = staking_handler::StakingManager<Self, VotingLockId>;
     type ManagerOrigin =
         EnsureOneOf<Self::AccountId, EnsureSigned<Self::AccountId>, EnsureRoot<Self::AccountId>>;
 
@@ -537,19 +562,17 @@ impl referendum::Trait<ReferendumInstance> for Test {
 
     type WeightInfo = ReferendumWeightInfo;
 
+    type MaxWinnerTargetCount = MaxWinnerTargetCount;
+
     fn calculate_vote_power(
         _: &<Self as frame_system::Trait>::AccountId,
-        _: &BalanceReferendum<Self, ReferendumInstance>,
+        _: &Self::Balance,
     ) -> Self::VotePower {
         1
     }
 
     fn can_unlock_vote_stake(
-        _: &referendum::CastVote<
-            Self::Hash,
-            BalanceReferendum<Self, ReferendumInstance>,
-            Self::MemberId,
-        >,
+        _: &referendum::CastVote<Self::Hash, Self::Balance, Self::MemberId>,
     ) -> bool {
         true
     }

+ 12 - 27
runtime-modules/proposals/codex/src/tests/mod.rs

@@ -9,7 +9,6 @@ use frame_system::RawOrigin;
 use common::working_group::WorkingGroup;
 use proposals_engine::ProposalParameters;
 use referendum::ReferendumManager;
-use working_group::Penalty;
 
 use crate::*;
 use crate::{Error, ProposalDetails};
@@ -62,7 +61,7 @@ where
     fn check_call_for_insufficient_rights(&self) {
         assert_eq!(
             (self.insufficient_rights_call)(),
-            Err(DispatchError::Other("Bad origin"))
+            Err(DispatchError::BadOrigin)
         );
     }
 
@@ -741,23 +740,11 @@ fn run_create_slash_working_group_leader_stake_proposal_common_checks_succeed(
             exact_execution_block: None,
         };
 
-        let slash_lead_details = ProposalDetails::SlashWorkingGroupLeaderStake(
-            0,
-            Penalty {
-                slashing_amount: 10,
-                slashing_text: Vec::new(),
-            },
-            working_group,
-        );
+        let slash_lead_details =
+            ProposalDetails::SlashWorkingGroupLeaderStake(0, 10, working_group);
 
-        let slash_lead_details_success = ProposalDetails::SlashWorkingGroupLeaderStake(
-            10,
-            Penalty {
-                slashing_amount: 10,
-                slashing_text: Vec::new(),
-            },
-            working_group,
-        );
+        let slash_lead_details_success =
+            ProposalDetails::SlashWorkingGroupLeaderStake(10, 10, working_group);
 
         let proposal_fixture = ProposalTestFixture {
             insufficient_rights_call: || {
@@ -817,7 +804,7 @@ fn setup_council(start_id: u64) {
     let candidates: Vec<_> = (start_id..start_id + candidates_number).collect();
     let council: Vec<_> = (start_id..start_id + council_size).collect();
     let voters: Vec<_> =
-        (council.last().unwrap() + 1..council.last().unwrap() + 1 + council_size).collect();
+        (candidates.last().unwrap() + 1..candidates.last().unwrap() + 1 + council_size).collect();
     for id in candidates {
         increase_total_balance_issuance_using_account_id(id, BalanceOf::<Test>::max_value());
         council::Module::<Test>::announce_candidacy(
@@ -834,7 +821,12 @@ fn setup_council(start_id: u64) {
     run_to_block(current_block + <Test as council::Trait>::AnnouncingPeriodDuration::get());
 
     for (i, voter_id) in voters.iter().enumerate() {
+        assert_eq!(Balances::free_balance(*voter_id), 0);
         increase_total_balance_issuance_using_account_id(*voter_id, BalanceOf::<Test>::max_value());
+        assert_eq!(
+            Balances::free_balance(*voter_id),
+            BalanceOf::<Test>::max_value()
+        );
         let commitment = referendum::Module::<Test, ReferendumInstance>::calculate_commitment(
             voter_id,
             &[0u8],
@@ -897,14 +889,7 @@ fn run_slash_stake_with_zero_staking_balance_fails(working_group: WorkingGroup)
             ProposalCodex::create_proposal(
                 RawOrigin::Signed(1).into(),
                 general_proposal_parameters.clone(),
-                ProposalDetails::SlashWorkingGroupLeaderStake(
-                    10,
-                    Penalty {
-                        slashing_amount: 0,
-                        slashing_text: Vec::new()
-                    },
-                    working_group,
-                )
+                ProposalDetails::SlashWorkingGroupLeaderStake(10, 0, working_group)
             ),
             Err(Error::<Test>::SlashingStakeIsZero.into())
         );

+ 3 - 3
runtime-modules/proposals/codex/src/types.rs

@@ -7,7 +7,7 @@ use sp_std::vec::Vec;
 
 use common::working_group::WorkingGroup;
 
-use working_group::{Penalty, StakePolicy};
+use working_group::StakePolicy;
 
 /// Encodes proposal using its details information.
 pub trait ProposalEncoder<T: crate::Trait> {
@@ -52,7 +52,7 @@ pub enum ProposalDetails<BlockNumber, AccountId, Balance, WorkerId> {
     DecreaseWorkingGroupLeaderStake(WorkerId, Balance, WorkingGroup),
 
     /// Slash the working group leader stake.
-    SlashWorkingGroupLeaderStake(WorkerId, Penalty<Balance>, WorkingGroup),
+    SlashWorkingGroupLeaderStake(WorkerId, Balance, WorkingGroup),
 
     /// Set working group leader reward balance.
     SetWorkingGroupLeaderReward(WorkerId, Option<Balance>, WorkingGroup),
@@ -100,7 +100,7 @@ pub struct TerminateRoleParameters<WorkerId, Balance> {
     pub worker_id: WorkerId,
 
     /// Terminate role slash penalty.
-    pub penalty: Option<Penalty<Balance>>,
+    pub penalty: Option<Balance>,
 
     /// Defines working group with the open position.
     pub working_group: WorkingGroup,

+ 1 - 0
runtime-modules/proposals/discussion/Cargo.toml

@@ -24,6 +24,7 @@ sp-runtime = { package = 'sp-runtime', default-features = false, git = 'https://
 pallet-timestamp = { package = 'pallet-timestamp', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
 balances = { package = 'pallet-balances', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
 membership = { package = 'pallet-membership', default-features = false, path = '../../membership'}
+staking-handler = { package = 'pallet-staking-handler', default-features = false, path = '../../staking-handler'}
 
 [features]
 default = ['std']

+ 1 - 1
runtime-modules/proposals/discussion/src/benchmarking.rs

@@ -155,7 +155,7 @@ benchmarks! {
     }
 
     // TODO: Review this after changes to the governance/council are merged:
-    // this extrinsic uses `T::CouncilOriginValidator::ensure_actor_origin`
+    // this extrinsic uses `T::CouncilOriginValidator::ensure_member_controller_account_origin`
     // this is a hook to the runtime. Since the pallet implementation shouldn't have any
     // information on the runtime this hooks should be constant.
     // However, the implementation in the runtime is linear in the number of council members.

+ 12 - 8
runtime-modules/proposals/discussion/src/lib.rs

@@ -62,7 +62,7 @@ use frame_support::{
 use sp_std::clone::Clone;
 use sp_std::vec::Vec;
 
-use common::origin::ActorOriginValidator;
+use common::origin::{CouncilOriginValidator, MemberOriginValidator};
 use common::MemberId;
 use types::{DiscussionPost, DiscussionThread};
 
@@ -112,10 +112,14 @@ pub trait Trait: frame_system::Trait + common::Trait {
     type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;
 
     /// Validates post author id and origin combination
-    type AuthorOriginValidator: ActorOriginValidator<Self::Origin, MemberId<Self>, Self::AccountId>;
+    type AuthorOriginValidator: MemberOriginValidator<Self::Origin, MemberId<Self>, Self::AccountId>;
 
     /// Defines whether the member is an active councilor.
-    type CouncilOriginValidator: ActorOriginValidator<Self::Origin, MemberId<Self>, Self::AccountId>;
+    type CouncilOriginValidator: CouncilOriginValidator<
+        Self::Origin,
+        MemberId<Self>,
+        Self::AccountId,
+    >;
 
     /// Discussion thread Id type
     type ThreadId: From<u64> + Into<u64> + Parameter + Default + Copy;
@@ -201,7 +205,7 @@ decl_module! {
             thread_id : T::ThreadId,
             _text : Vec<u8>
         ) {
-            T::AuthorOriginValidator::ensure_actor_origin(
+            T::AuthorOriginValidator::ensure_member_controller_account_origin(
                 origin.clone(),
                 post_author_id,
             )?;
@@ -249,7 +253,7 @@ decl_module! {
 
             let post_author_id = <PostThreadIdByPostId<T>>::get(&thread_id, &post_id).author_id;
 
-            T::AuthorOriginValidator::ensure_actor_origin(
+            T::AuthorOriginValidator::ensure_member_controller_account_origin(
                 origin,
                 post_author_id,
             )?;
@@ -282,7 +286,7 @@ decl_module! {
             thread_id : T::ThreadId,
             mode : ThreadMode<MemberId<T>>
         ) {
-            T::AuthorOriginValidator::ensure_actor_origin(origin.clone(), member_id)?;
+            T::AuthorOriginValidator::ensure_member_controller_account_origin(origin.clone(), member_id)?;
 
             ensure!(<ThreadById<T>>::contains_key(thread_id), Error::<T>::ThreadDoesntExist);
 
@@ -296,7 +300,7 @@ decl_module! {
             let thread = Self::thread_by_id(&thread_id);
 
             let is_councilor =
-                    T::CouncilOriginValidator::ensure_actor_origin(origin, member_id)
+                    T::CouncilOriginValidator::ensure_member_consulate(origin, member_id)
                         .is_ok();
             let is_thread_author = thread.author_id == member_id;
 
@@ -374,7 +378,7 @@ impl<T: Trait> Module<T> {
             ThreadMode::Closed(members) => {
                 let is_thread_author = thread_author_id == thread.author_id;
                 let is_councilor =
-                    T::CouncilOriginValidator::ensure_actor_origin(origin, thread_author_id)
+                    T::CouncilOriginValidator::ensure_member_consulate(origin, thread_author_id)
                         .is_ok();
                 let is_allowed_member = members
                     .iter()

+ 42 - 10
runtime-modules/proposals/discussion/src/tests/mock.rs

@@ -2,7 +2,7 @@
 
 pub use frame_system;
 
-use frame_support::traits::{OnFinalize, OnInitialize};
+use frame_support::traits::{LockIdentifier, OnFinalize, OnInitialize};
 use frame_support::{impl_outer_event, impl_outer_origin, parameter_types, weights::Weight};
 use sp_core::H256;
 use sp_runtime::{
@@ -11,8 +11,12 @@ use sp_runtime::{
     DispatchResult, Perbill,
 };
 
-use crate::ActorOriginValidator;
+use crate::CouncilOriginValidator;
+use crate::MemberOriginValidator;
 use crate::WeightInfo;
+use frame_support::dispatch::DispatchError;
+
+use staking_handler::LockComparator;
 
 impl_outer_origin! {
     pub enum Origin for Test {}
@@ -58,6 +62,7 @@ parameter_types! {
     pub const MaxWhiteListSize: u32 = 4;
     pub const DefaultMembershipPrice: u64 = 100;
     pub const DefaultInitialInvitationBalance: u64 = 100;
+    pub const InvitedMemberLockId: [u8; 8] = [2; 8];
 }
 
 impl balances::Trait for Test {
@@ -80,9 +85,29 @@ impl membership::Trait for Test {
     type DefaultMembershipPrice = DefaultMembershipPrice;
     type WorkingGroup = ();
     type DefaultInitialInvitationBalance = ();
+    type InvitedMemberStakingHandler = staking_handler::StakingManager<Self, InvitedMemberLockId>;
+}
+
+impl LockComparator<<Test as balances::Trait>::Balance> for Test {
+    fn are_locks_conflicting(
+        _new_lock: &LockIdentifier,
+        _existing_locks: &[LockIdentifier],
+    ) -> bool {
+        false
+    }
+}
+
+impl common::working_group::WorkingGroupBudgetHandler<Test> for () {
+    fn get_budget() -> u64 {
+        unimplemented!()
+    }
+
+    fn set_budget(_new_value: u64) {
+        unimplemented!()
+    }
 }
 
-impl common::working_group::WorkingGroupIntegration<Test> for () {
+impl common::working_group::WorkingGroupAuthenticator<Test> for () {
     fn ensure_worker_origin(
         _origin: <Test as frame_system::Trait>::Origin,
         _worker_id: &<Test as common::Trait>::ActorId,
@@ -134,8 +159,11 @@ impl WeightInfo for () {
     }
 }
 
-impl ActorOriginValidator<Origin, u64, u64> for () {
-    fn ensure_actor_origin(origin: Origin, actor_id: u64) -> Result<u64, &'static str> {
+impl MemberOriginValidator<Origin, u64, u64> for () {
+    fn ensure_member_controller_account_origin(
+        origin: Origin,
+        actor_id: u64,
+    ) -> Result<u64, DispatchError> {
         if frame_system::ensure_none(origin.clone()).is_ok() {
             return Ok(1);
         }
@@ -156,18 +184,22 @@ impl ActorOriginValidator<Origin, u64, u64> for () {
             return Ok(12);
         }
 
-        Err("Invalid author")
+        Err(DispatchError::Other("Invalid author"))
+    }
+
+    fn is_member_controller_account(_member_id: &u64, _account_id: &u64) -> bool {
+        unimplemented!()
     }
 }
 
 pub struct CouncilMock;
-impl ActorOriginValidator<Origin, u64, u64> for CouncilMock {
-    fn ensure_actor_origin(origin: Origin, actor_id: u64) -> Result<u64, &'static str> {
+impl CouncilOriginValidator<Origin, u64, u64> for CouncilMock {
+    fn ensure_member_consulate(origin: Origin, actor_id: u64) -> DispatchResult {
         if actor_id == 2 && frame_system::ensure_signed(origin).unwrap_or_default() == 2 {
-            return Ok(2);
+            return Ok(());
         }
 
-        Err("Not a council")
+        Err(DispatchError::Other("Not a council"))
     }
 }
 

+ 70 - 23
runtime-modules/proposals/engine/src/benchmarking.rs

@@ -189,7 +189,7 @@ fn create_proposal<T: Trait + membership::Trait>(
     );
 
     assert_eq!(
-        T::StakingHandler::current_stake(&account_id),
+        <T as Trait>::StakingHandler::current_stake(&account_id),
         T::Balance::max_value()
     );
 
@@ -405,7 +405,7 @@ benchmarks! {
 
         for lock_number in 1 .. i {
             let (locked_account_id, _) = member_funded_account::<T>("locked_member", lock_number);
-            T::StakingHandler::set_stake(&locked_account_id, One::one()).unwrap();
+            <T as Trait>::StakingHandler::set_stake(&locked_account_id, One::one()).unwrap();
         }
 
     }: _ (RawOrigin::Signed(account_id.clone()), member_id, proposal_id)
@@ -473,7 +473,7 @@ benchmarks! {
     verify {
         for proposer_account_id in proposers {
             assert_eq!(
-                T::StakingHandler::current_stake(&proposer_account_id),
+                <T as Trait>::StakingHandler::current_stake(&proposer_account_id),
                 Zero::zero(),
                 "Should've unlocked all stake"
             );
@@ -497,14 +497,12 @@ benchmarks! {
             );
         }
 
-        if cfg!(test) {
-            for proposal_id in proposals.iter() {
-                assert_in_events::<T>(
-                    RawEvent::ProposalExecuted(
-                        proposal_id.clone(),
-                        ExecutionStatus::failed_execution("Not enough data to fill buffer")).into()
-                );
-            }
+        for proposal_id in proposals.iter() {
+            assert_in_events::<T>(
+                RawEvent::ProposalExecuted(
+                    proposal_id.clone(),
+                    ExecutionStatus::failed_execution("Decoding error")).into()
+            );
         }
     }
 
@@ -553,7 +551,7 @@ benchmarks! {
     verify {
         for proposer_account_id in proposers {
             assert_eq!(
-                T::StakingHandler::current_stake(&proposer_account_id),
+                <T as Trait>::StakingHandler::current_stake(&proposer_account_id),
                 Zero::zero(),
                 "Should've unlocked all stake"
             );
@@ -565,14 +563,12 @@ benchmarks! {
             assert!(!DispatchableCallCode::<T>::contains_key(proposal_id), "Dispatchable code should've been removed");
         }
 
-        if cfg!(test) {
-            for proposal_id in proposals.iter() {
-                assert_in_events::<T>(
-                    RawEvent::ProposalExecuted(
-                        proposal_id.clone(),
-                        ExecutionStatus::failed_execution("Not enough data to fill buffer")).into()
-                );
-            }
+        for proposal_id in proposals.iter() {
+            assert_in_events::<T>(
+                RawEvent::ProposalExecuted(
+                    proposal_id.clone(),
+                    ExecutionStatus::failed_execution("Decoding error")).into()
+            );
         }
     }
 
@@ -591,7 +587,7 @@ benchmarks! {
     verify {
         for proposer_account_id in proposers {
             assert_ne!(
-                T::StakingHandler::current_stake(&proposer_account_id),
+                <T as Trait>::StakingHandler::current_stake(&proposer_account_id),
                 Zero::zero(),
                 "Should've still stake locked"
             );
@@ -653,7 +649,7 @@ benchmarks! {
 
         for proposer_account_id in proposers {
             assert_eq!(
-                T::StakingHandler::current_stake(&proposer_account_id),
+                <T as Trait>::StakingHandler::current_stake(&proposer_account_id),
                 Zero::zero(),
                 "Shouldn't have any stake locked"
             );
@@ -674,7 +670,7 @@ benchmarks! {
     verify {
         for proposer_account_id in proposers {
             assert_eq!(
-                T::StakingHandler::current_stake(&proposer_account_id),
+                <T as Trait>::StakingHandler::current_stake(&proposer_account_id),
                 Zero::zero(),
                 "Shouldn't have any stake locked"
             );
@@ -711,6 +707,50 @@ benchmarks! {
             "There should not be any proposal left active"
         );
     }
+
+    cancel_active_and_pending_proposals {
+        let i in 1 .. T::MaxActiveProposalLimit::get();
+
+        let (proposers, proposals) = create_multiple_finalized_proposals::<T>(
+            i,
+            0,
+            VoteKind::Approve,
+            max(T::CouncilSize::get().try_into().unwrap(), 1),
+            10,
+        );
+    }: { ProposalsEngine::<T>::cancel_active_and_pending_proposals() }
+    verify {
+        for proposal_id in proposals.iter() {
+            assert!(
+                !Proposals::<T>::contains_key(proposal_id),
+                "Proposal should not be in store"
+            );
+
+            assert!(
+                !DispatchableCallCode::<T>::contains_key(proposal_id),
+                "Dispatchable should not be in store"
+            );
+
+            assert_in_events::<T>(
+                RawEvent::ProposalDecisionMade(proposal_id.clone(), ProposalDecision::CanceledByRuntime)
+                    .into()
+            );
+        }
+
+        assert_eq!(
+            ProposalsEngine::<T>::active_proposal_count(),
+            0,
+            "There should not be any proposal left active"
+        );
+
+        for proposer_account_id in proposers {
+            assert_eq!(
+                <T as Trait>::StakingHandler::current_stake(&proposer_account_id),
+                Zero::zero(),
+                "Shouldn't have any stake locked"
+            );
+        }
+    }
 }
 
 #[cfg(test)]
@@ -774,4 +814,11 @@ mod tests {
             assert_ok!(test_benchmark_on_initialize_slashed::<Test>());
         });
     }
+
+    #[test]
+    fn test_cancel_active_and_pending_proposals() {
+        initial_test_ext().execute_with(|| {
+            assert_ok!(test_benchmark_cancel_active_and_pending_proposals::<Test>());
+        });
+    }
 }

+ 65 - 20
runtime-modules/proposals/engine/src/lib.rs

@@ -11,7 +11,8 @@
 //! its [status](./enum.ProposalStatus.html).
 //!
 //! ## Proposal lifecycle
-//! When a proposal passes [checks](./struct.Module.html#method.ensure_create_proposal_parameters_are_valid)
+//! When a proposal passes
+//! [checks](./struct.Module.html#method.ensure_create_proposal_parameters_are_valid)
 //! for its [parameters](./struct.ProposalParameters.html) -
 //! it can be [created](./struct.Module.html#method.create_proposal).
 //! The newly created proposal has _Active_ status. The proposal can be voted on, vetoed or
@@ -26,11 +27,14 @@
 //! - The proposal can be [vetoed](./struct.Module.html#method.veto_proposal)
 //! anytime before the proposal execution by the _sudo_.
 //! - If the _council_ got reelected during the proposal _voting period_ the external handler calls
-//! [reject_active_proposals](./trait.Module.html#method.reject_active_proposals) function and
+//! [reject_active_proposals](./struct.Module.html#method.reject_active_proposals) function and
 //! all active proposals got rejected and it also calls
-//! [reactivate_pending_constitutionality_proposals](./trait.Module.html#method.reactivate_pending_constitutionality_proposals)
+//! [reactivate_pending_constitutionality_proposals](./struct.Module.html#method.reactivate_pending_constitutionality_proposals)
 //! and proposals with pending constitutionality become active again.
 //! - There are different fees to apply for slashed, rejected, expired or cancelled proposals.
+//! - On runtime upgrade the proposals code could be obsolete, so we cancel all active proposals
+//! with statuses: Active, PendingExecution, PendingConstitutionality using this function
+//! [cancel_active_and_pending_proposals](./struct.Module.html#method.cancel_active_and_pending_proposals).
 //!
 //! ### Important abstract types to be implemented
 //! Proposals `engine` module has several abstractions to be implemented in order to work correctly.
@@ -40,7 +44,7 @@
 //! the council size
 //! - _ProposerOriginValidator_ - ensure valid proposer identity. Proposers should have permissions
 //! to create a proposal: they should be members of the Joystream.
-//! - [StakingHandler](./trait.StakingHandler.html) - defines an interface for the staking.
+//! - StakingHandler - defines an interface for the staking.
 //!
 //! A full list of the abstractions can be found [here](./trait.Trait.html).
 //!
@@ -53,12 +57,14 @@
 //! ### Public API
 //! - [create_proposal](./struct.Module.html#method.create_proposal) - creates proposal using
 //! provided parameters
-//! - [ensure_create_proposal_parameters_are_valid](./struct.Module.html#method.ensure_create_proposal_parameters_are_valid)
-//! - ensures that we can create the proposal
-//! - [reject_active_proposals](./trait.Module.html#method.reject_active_proposals) - rejects all
+//! - [ensure_create_proposal_parameters_are_valid](./struct.Module.html#method.ensure_create_proposal_parameters_are_valid) -
+//! ensures that we can create the proposal
+//! - [reject_active_proposals](./struct.Module.html#method.reject_active_proposals) - rejects all
 //! active proposals.
-//! - [reactivate_pending_constitutionality_proposals](./trait.Module.html#method.reactivate_pending_constitutionality_proposals)
-//! - reactivate proposals with pending constitutionality.
+//! - [reactivate_pending_constitutionality_proposals](./struct.Module.html#method.reactivate_pending_constitutionality_proposals) -
+//! reactivate proposals with pending constitutionality.
+//! - [cancel_active_and_pending_proposals](./struct.Module.html#method.cancel_active_and_pending_proposals) -
+//! cancels all active proposals.
 //!
 //! ## Usage
 //!
@@ -145,7 +151,7 @@ use frame_system::{ensure_root, RawOrigin};
 use sp_arithmetic::traits::{SaturatedConversion, Saturating, Zero};
 use sp_std::vec::Vec;
 
-use common::origin::ActorOriginValidator;
+use common::origin::{CouncilOriginValidator, MemberOriginValidator};
 use common::MemberId;
 use staking_handler::StakingHandler;
 
@@ -153,13 +159,14 @@ use staking_handler::StakingHandler;
 /// Note: This was auto generated through the benchmark CLI using the `--weight-trait` flag
 pub trait WeightInfo {
     fn vote(i: u32) -> Weight;
-    fn cancel_proposal(i: u32) -> Weight;
+    fn cancel_proposal() -> Weight;
     fn veto_proposal() -> Weight;
     fn on_initialize_immediate_execution_decode_fails(i: u32) -> Weight;
     fn on_initialize_pending_execution_decode_fails(i: u32) -> Weight;
     fn on_initialize_approved_pending_constitutionality(i: u32) -> Weight;
     fn on_initialize_rejected(i: u32) -> Weight;
     fn on_initialize_slashed(i: u32) -> Weight;
+    fn cancel_active_and_pending_proposals(i: u32) -> Weight;
 }
 
 type WeightInfoEngine<T> = <T as Trait>::WeightInfo;
@@ -172,14 +179,18 @@ pub trait Trait:
     type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;
 
     /// Validates proposer id and origin combination
-    type ProposerOriginValidator: ActorOriginValidator<
+    type ProposerOriginValidator: MemberOriginValidator<
         Self::Origin,
         MemberId<Self>,
         Self::AccountId,
     >;
 
     /// Validates voter id and origin combination
-    type VoterOriginValidator: ActorOriginValidator<Self::Origin, MemberId<Self>, Self::AccountId>;
+    type CouncilOriginValidator: CouncilOriginValidator<
+        Self::Origin,
+        MemberId<Self>,
+        Self::AccountId,
+    >;
 
     /// Provides data for voting. Defines maximum voters count for the proposal.
     type TotalVotersCounter: VotersParameters;
@@ -373,7 +384,8 @@ decl_module! {
         /// Exports const - the fee is applied when cancel the proposal. A fee would be slashed (burned).
         const CancellationFee: BalanceOf<T> = T::CancellationFee::get();
 
-        /// Exports const -  the fee is applied when the proposal gets rejected. A fee would be slashed (burned).
+        /// Exports const -  the fee is applied when the proposal gets rejected. A fee would
+        /// be slashed (burned).
         const RejectionFee: BalanceOf<T> = T::RejectionFee::get();
 
         /// Exports const -  max allowed proposal title length.
@@ -433,12 +445,15 @@ decl_module! {
             vote: VoteKind,
             _rationale: Vec<u8>, // we use it on the query node side.
         ) {
-            T::VoterOriginValidator::ensure_actor_origin(origin, voter_id)?;
+            T::CouncilOriginValidator::ensure_member_consulate(origin, voter_id)?;
 
             ensure!(<Proposals<T>>::contains_key(proposal_id), Error::<T>::ProposalNotFound);
             let mut proposal = Self::proposals(proposal_id);
 
-            ensure!(matches!(proposal.status, ProposalStatus::Active{..}), Error::<T>::ProposalFinalized);
+            ensure!(
+                matches!(proposal.status, ProposalStatus::Active{..}),
+                Error::<T>::ProposalFinalized
+            );
 
             let did_not_vote_before = !<VoteExistsByProposalByVoter<T>>::contains_key(
                 proposal_id,
@@ -468,15 +483,18 @@ decl_module! {
         /// - DB:
         ///    - O(1) doesn't depend on the state or parameters
         /// # </weight>
-        #[weight = WeightInfoEngine::<T>::cancel_proposal(T::MaxLocks::get())]
+        #[weight = WeightInfoEngine::<T>::cancel_proposal()]
         pub fn cancel_proposal(origin, proposer_id: MemberId<T>, proposal_id: T::ProposalId) {
-            T::ProposerOriginValidator::ensure_actor_origin(origin, proposer_id)?;
+            T::ProposerOriginValidator::ensure_member_controller_account_origin(origin, proposer_id)?;
 
             ensure!(<Proposals<T>>::contains_key(proposal_id), Error::<T>::ProposalNotFound);
             let proposal = Self::proposals(proposal_id);
 
             ensure!(proposer_id == proposal.proposer_id, Error::<T>::NotAuthor);
-            ensure!(matches!(proposal.status, ProposalStatus::Active{..}), Error::<T>::ProposalFinalized);
+            ensure!(
+                matches!(proposal.status, ProposalStatus::Active{..}),
+                Error::<T>::ProposalFinalized
+            );
             ensure!(proposal.voting_results.no_votes_yet(), Error::<T>::ProposalHasVotes);
 
             //
@@ -675,6 +693,31 @@ impl<T: Trait> Module<T> {
             });
     }
 
+    /// Cancels all active, pending execution and pending constitutionality proposals.
+    /// No fee applies.Possible application includes the runtime upgrade.
+    pub fn cancel_active_and_pending_proposals() -> Weight {
+        let active_proposal_count = Self::active_proposal_count();
+
+        // Filter active proposals and reject them.
+        <Proposals<T>>::iter()
+            .filter_map(|(proposal_id, proposal)| {
+                if proposal.status.is_active_or_pending_execution()
+                    || proposal.status.is_pending_constitutionality_proposal()
+                {
+                    return Some((proposal_id, proposal));
+                }
+
+                None
+            })
+            .for_each(|(proposal_id, proposal)| {
+                Self::finalize_proposal(proposal_id, proposal, ProposalDecision::CanceledByRuntime);
+            });
+
+        <WeightInfoEngine<T>>::cancel_active_and_pending_proposals(
+            active_proposal_count.saturated_into(),
+        )
+    }
+
     /// Reactivate proposals with pending constitutionality.
     /// Possible application includes new council elections.
     pub fn reactivate_pending_constitutionality_proposals() {
@@ -861,7 +904,9 @@ impl<T: Trait> Module<T> {
     ) -> BalanceOf<T> {
         match proposal_decision {
             ProposalDecision::Rejected | ProposalDecision::Expired => T::RejectionFee::get(),
-            ProposalDecision::Approved { .. } | ProposalDecision::Vetoed => BalanceOf::<T>::zero(),
+            ProposalDecision::Approved { .. }
+            | ProposalDecision::Vetoed
+            | ProposalDecision::CanceledByRuntime => BalanceOf::<T>::zero(),
             ProposalDecision::Canceled => T::CancellationFee::get(),
             ProposalDecision::Slashed => proposal_parameters
                 .required_stake

+ 45 - 22
runtime-modules/proposals/engine/src/tests/mock/mod.rs

@@ -6,6 +6,7 @@
 
 #![cfg(test)]
 
+use frame_support::dispatch::DispatchError;
 use frame_support::traits::LockIdentifier;
 use frame_support::{impl_outer_event, impl_outer_origin, parameter_types, weights::Weight};
 pub use frame_system;
@@ -21,7 +22,6 @@ pub(crate) mod proposals;
 
 use crate::ProposalObserver;
 pub use proposals::*;
-use referendum::Balance as BalanceReferendum;
 use staking_handler::{LockComparator, StakingManager};
 
 // Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted.
@@ -67,6 +67,7 @@ parameter_types! {
     pub const MaxSaltLength: u64 = 32; // use some multiple of 8 for ez testing
     pub const VotingLockId: LockIdentifier = *b"referend";
     pub const MinimumPeriod: u64 = 5;
+    pub const MaxWinnerTargetCount: u64 = 10;
 }
 
 impl referendum::Trait<ReferendumInstance> for Test {
@@ -74,9 +75,7 @@ impl referendum::Trait<ReferendumInstance> for Test {
 
     type MaxSaltLength = MaxSaltLength;
 
-    type Currency = balances::Module<Self>;
-    type LockId = VotingLockId;
-
+    type StakingHandler = staking_handler::StakingManager<Self, VotingLockId>;
     type ManagerOrigin =
         EnsureOneOf<Self::AccountId, EnsureSigned<Self::AccountId>, EnsureRoot<Self::AccountId>>;
 
@@ -89,19 +88,17 @@ impl referendum::Trait<ReferendumInstance> for Test {
 
     type WeightInfo = ReferendumWeightInfo;
 
+    type MaxWinnerTargetCount = MaxWinnerTargetCount;
+
     fn calculate_vote_power(
         _: &<Self as frame_system::Trait>::AccountId,
-        _: &BalanceReferendum<Self, ReferendumInstance>,
+        _: &Self::Balance,
     ) -> Self::VotePower {
         1
     }
 
     fn can_unlock_vote_stake(
-        _: &referendum::CastVote<
-            Self::Hash,
-            BalanceReferendum<Self, ReferendumInstance>,
-            Self::MemberId,
-        >,
+        _: &referendum::CastVote<Self::Hash, Self::Balance, Self::MemberId>,
     ) -> bool {
         true
     }
@@ -183,6 +180,7 @@ parameter_types! {
     pub const LockId: LockIdentifier = [1; 8];
     pub const DefaultMembershipPrice: u64 = 100;
     pub const DefaultInitialInvitationBalance: u64 = 100;
+    pub const InvitedMemberLockId: [u8; 8] = [2; 8];
 }
 
 impl common::Trait for Test {
@@ -195,9 +193,20 @@ impl membership::Trait for Test {
     type DefaultMembershipPrice = DefaultMembershipPrice;
     type WorkingGroup = ();
     type DefaultInitialInvitationBalance = ();
+    type InvitedMemberStakingHandler = staking_handler::StakingManager<Self, InvitedMemberLockId>;
+}
+
+impl common::working_group::WorkingGroupBudgetHandler<Test> for () {
+    fn get_budget() -> u64 {
+        unimplemented!()
+    }
+
+    fn set_budget(_new_value: u64) {
+        unimplemented!()
+    }
 }
 
-impl common::working_group::WorkingGroupIntegration<Test> for () {
+impl common::working_group::WorkingGroupAuthenticator<Test> for () {
     fn ensure_worker_origin(
         _origin: <Test as frame_system::Trait>::Origin,
         _worker_id: &<Test as common::Trait>::ActorId,
@@ -228,7 +237,7 @@ impl common::working_group::WorkingGroupIntegration<Test> for () {
 impl crate::Trait for Test {
     type Event = TestEvent;
     type ProposerOriginValidator = ();
-    type VoterOriginValidator = ();
+    type CouncilOriginValidator = ();
     type TotalVotersCounter = ();
     type ProposalId = u32;
     type StakingHandler = StakingManager<Test, LockId>;
@@ -247,7 +256,7 @@ impl crate::WeightInfo for () {
         0
     }
 
-    fn cancel_proposal(_: u32) -> Weight {
+    fn cancel_proposal() -> Weight {
         0
     }
 
@@ -274,6 +283,10 @@ impl crate::WeightInfo for () {
     fn on_initialize_slashed(_: u32) -> Weight {
         0
     }
+
+    fn cancel_active_and_pending_proposals(_: u32) -> u64 {
+        0
+    }
 }
 
 impl ProposalObserver<Test> for () {
@@ -286,12 +299,27 @@ impl Default for proposals::Call<Test> {
     }
 }
 
-impl common::origin::ActorOriginValidator<Origin, u64, u64> for () {
-    fn ensure_actor_origin(origin: Origin, _account_id: u64) -> Result<u64, &'static str> {
+impl common::origin::MemberOriginValidator<Origin, u64, u64> for () {
+    fn ensure_member_controller_account_origin(
+        origin: Origin,
+        _account_id: u64,
+    ) -> Result<u64, DispatchError> {
         let signed_account_id = frame_system::ensure_signed(origin)?;
 
         Ok(signed_account_id)
     }
+
+    fn is_member_controller_account(_member_id: &u64, _account_id: &u64) -> bool {
+        true
+    }
+}
+
+impl common::origin::CouncilOriginValidator<Origin, u64, u64> for () {
+    fn ensure_member_consulate(origin: Origin, _: u64) -> DispatchResult {
+        frame_system::ensure_signed(origin)?;
+
+        Ok(())
+    }
 }
 
 // If changing count is required, we can upgrade the implementation as shown here:
@@ -384,14 +412,9 @@ impl council::Trait for Test {
     type StakingAccountValidator = membership::Module<Test>;
     type WeightInfo = CouncilWeightInfo;
 
-    fn is_council_member_account(
-        membership_id: &Self::MemberId,
-        account_id: &<Self as frame_system::Trait>::AccountId,
-    ) -> bool {
-        membership::Module::<Self>::membership(membership_id).controller_account == *account_id
-    }
-
     fn new_council_elected(_: &[council::CouncilMemberOf<Self>]) {}
+
+    type MemberOriginValidator = ();
 }
 
 pub struct CouncilWeightInfo;

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

@@ -365,7 +365,7 @@ fn vote_fails_with_insufficient_rights() {
                 VoteKind::Approve,
                 Vec::new()
             ),
-            Err(DispatchError::Other("Bad origin"))
+            Err(DispatchError::BadOrigin)
         );
     });
 }
@@ -901,6 +901,176 @@ fn proposal_execution_postponed_because_of_grace_period() {
     });
 }
 
+#[test]
+fn cancel_active_and_pending_execution_proposal_by_runtime() {
+    initial_test_ext().execute_with(|| {
+        // Events start only from 1 first block. No events on block zero.
+        let starting_block = 1;
+        run_to_block_and_finalize(starting_block);
+
+        let parameters_fixture = ProposalParametersFixture::default().with_grace_period(3);
+
+        // A proposal for pending execution check.
+        let dummy_proposal =
+            DummyProposalFixture::default().with_parameters(parameters_fixture.params());
+        let pending_execution_proposal_id =
+            dummy_proposal.create_proposal_and_assert(Ok(1)).unwrap();
+
+        // A proposal for active status check.
+        let dummy_proposal =
+            DummyProposalFixture::default().with_parameters(parameters_fixture.params());
+        let active_proposal_id = dummy_proposal.create_proposal_and_assert(Ok(2)).unwrap();
+
+        let mut vote_generator = VoteGenerator::new(pending_execution_proposal_id);
+        vote_generator.vote_and_assert_ok(VoteKind::Approve);
+        vote_generator.vote_and_assert_ok(VoteKind::Approve);
+        vote_generator.vote_and_assert_ok(VoteKind::Approve);
+        vote_generator.vote_and_assert_ok(VoteKind::Approve);
+
+        run_to_block(3);
+
+        let pending_execution_proposal =
+            <crate::Proposals<Test>>::get(pending_execution_proposal_id);
+        let active_proposal = <crate::Proposals<Test>>::get(active_proposal_id);
+
+        // ensure proposal has pending execution status
+        assert_eq!(
+            pending_execution_proposal,
+            Proposal {
+                parameters: parameters_fixture.params(),
+                proposer_id: 1,
+                activated_at: starting_block,
+                status: ProposalStatus::approved(
+                    ApprovedProposalDecision::PendingExecution,
+                    starting_block + 1
+                ),
+                voting_results: VotingResults {
+                    abstentions: 0,
+                    approvals: 4,
+                    rejections: 0,
+                    slashes: 0,
+                },
+                exact_execution_block: None,
+                nr_of_council_confirmations: 1,
+                staking_account_id: None,
+            }
+        );
+
+        // ensure proposal has active status
+        assert_eq!(
+            active_proposal,
+            Proposal {
+                parameters: parameters_fixture.params(),
+                proposer_id: 1,
+                activated_at: starting_block,
+                status: ProposalStatus::Active,
+                voting_results: VotingResults {
+                    abstentions: 0,
+                    approvals: 0,
+                    rejections: 0,
+                    slashes: 0,
+                },
+                exact_execution_block: None,
+                nr_of_council_confirmations: 0,
+                staking_account_id: None,
+            }
+        );
+
+        ProposalsEngine::cancel_active_and_pending_proposals();
+
+        EventFixture::assert_events(vec![
+            RawEvent::ProposalCreated(1, pending_execution_proposal_id),
+            RawEvent::ProposalCreated(1, active_proposal_id),
+            RawEvent::Voted(1, pending_execution_proposal_id, VoteKind::Approve),
+            RawEvent::Voted(2, pending_execution_proposal_id, VoteKind::Approve),
+            RawEvent::Voted(3, pending_execution_proposal_id, VoteKind::Approve),
+            RawEvent::Voted(4, pending_execution_proposal_id, VoteKind::Approve),
+            RawEvent::ProposalDecisionMade(
+                pending_execution_proposal_id,
+                ProposalDecision::Approved(ApprovedProposalDecision::PendingExecution),
+            ),
+            RawEvent::ProposalStatusUpdated(
+                pending_execution_proposal_id,
+                ProposalStatus::PendingExecution(starting_block + 1),
+            ),
+            RawEvent::ProposalDecisionMade(active_proposal_id, ProposalDecision::CanceledByRuntime),
+            RawEvent::ProposalDecisionMade(
+                pending_execution_proposal_id,
+                ProposalDecision::CanceledByRuntime,
+            ),
+        ]);
+
+        assert!(!<crate::Proposals<Test>>::contains_key(
+            pending_execution_proposal_id
+        ));
+        assert!(!<crate::Proposals<Test>>::contains_key(active_proposal_id));
+    });
+}
+
+#[test]
+fn cancel_pending_constitutionality_proposal_by_runtime() {
+    initial_test_ext().execute_with(|| {
+        let starting_block = 1;
+        run_to_block_and_finalize(starting_block);
+
+        let parameters_fixture = ProposalParametersFixture::default().with_constitutionality(2);
+        let dummy_proposal =
+            DummyProposalFixture::default().with_parameters(parameters_fixture.params());
+        let proposal_id = dummy_proposal.create_proposal_and_assert(Ok(1)).unwrap();
+
+        // internal active proposal counter check
+        assert_eq!(<ActiveProposalCount>::get(), 1);
+
+        let mut vote_generator = VoteGenerator::new(proposal_id);
+        vote_generator.vote_and_assert_ok(VoteKind::Approve);
+        vote_generator.vote_and_assert_ok(VoteKind::Approve);
+        vote_generator.vote_and_assert_ok(VoteKind::Approve);
+        vote_generator.vote_and_assert_ok(VoteKind::Approve);
+
+        run_to_block_and_finalize(2);
+
+        let proposal = <crate::Proposals<Test>>::get(proposal_id);
+
+        // ensure proposal has pending constitutionality status
+        assert_eq!(
+            proposal,
+            Proposal {
+                parameters: parameters_fixture.params(),
+                proposer_id: 1,
+                activated_at: starting_block,
+                status: ProposalStatus::PendingConstitutionality,
+                voting_results: VotingResults {
+                    abstentions: 0,
+                    approvals: 4,
+                    rejections: 0,
+                    slashes: 0,
+                },
+                exact_execution_block: None,
+                nr_of_council_confirmations: 1,
+                staking_account_id: None,
+            }
+        );
+
+        ProposalsEngine::cancel_active_and_pending_proposals();
+
+        EventFixture::assert_events(vec![
+            RawEvent::ProposalCreated(1, proposal_id),
+            RawEvent::Voted(1, proposal_id, VoteKind::Approve),
+            RawEvent::Voted(2, proposal_id, VoteKind::Approve),
+            RawEvent::Voted(3, proposal_id, VoteKind::Approve),
+            RawEvent::Voted(4, proposal_id, VoteKind::Approve),
+            RawEvent::ProposalDecisionMade(
+                proposal_id,
+                ProposalDecision::Approved(ApprovedProposalDecision::PendingConstitutionality),
+            ),
+            RawEvent::ProposalStatusUpdated(proposal_id, ProposalStatus::PendingConstitutionality),
+            RawEvent::ProposalDecisionMade(proposal_id, ProposalDecision::CanceledByRuntime),
+        ]);
+
+        assert!(!<crate::Proposals<Test>>::contains_key(proposal_id));
+    });
+}
+
 #[test]
 fn proposal_execution_vetoed_successfully_during_the_grace_period() {
     initial_test_ext().execute_with(|| {

+ 3 - 0
runtime-modules/proposals/engine/src/types/proposal_statuses.rs

@@ -68,6 +68,9 @@ pub enum ProposalDecision {
     /// Proposal was withdrawn by its proposer.
     Canceled,
 
+    /// Proposal was canceled by the runtime. No fee applied.
+    CanceledByRuntime,
+
     /// Proposal was vetoed by root.
     Vetoed,
 

+ 4 - 1
runtime-modules/referendum/Cargo.toml

@@ -14,13 +14,14 @@ sp-std = { package = 'sp-std', default-features = false, git = 'https://github.c
 frame-support = { package = 'frame-support', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
 frame-system = { package = 'frame-system', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
 common = { package = 'pallet-common', default-features = false, path = '../common'}
+balances = { package = 'pallet-balances', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
+staking-handler = { package = 'pallet-staking-handler', default-features = false, path = '../staking-handler'}
 
 # Benchmark dependencies
 frame-benchmarking = { package = 'frame-benchmarking', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca', optional = true }
 membership = { package = 'pallet-membership', default-features = false, path = '../membership', optional = true }
 
 [dev-dependencies]
-pallet-balances = { package = 'pallet-balances', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
 sp-io = { package = 'sp-io', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
 rand = "0.7.3"
 pallet-timestamp = { package = 'pallet-timestamp', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
@@ -39,5 +40,7 @@ std = [
     'frame-support/std',
     'frame-system/std',
     'common/std',
+    'staking-handler/std',
+    'balances/std',
 ]
 

+ 11 - 13
runtime-modules/referendum/src/benchmarking.rs

@@ -1,7 +1,7 @@
 #![cfg(feature = "runtime-benchmarks")]
 use super::*;
 use frame_benchmarking::{account, benchmarks_instance, Zero};
-use frame_support::traits::{OnFinalize, OnInitialize};
+use frame_support::traits::{Currency, OnFinalize, OnInitialize};
 use frame_system::EventRecord;
 use frame_system::Module as System;
 use frame_system::RawOrigin;
@@ -29,8 +29,6 @@ fn assert_last_event<T: Trait<I>, I: Instance>(generic_event: <T as Trait<I>>::E
     assert_eq!(event, &system_event);
 }
 
-const MAX_WINNERS: u32 = 50;
-
 fn start_voting_cycle<T: Trait<I>, I: Instance>(winning_target_count: u32) {
     Referendum::<T, I>::force_start(winning_target_count.into(), 0);
     assert_eq!(
@@ -46,7 +44,7 @@ fn start_voting_cycle<T: Trait<I>, I: Instance>(winning_target_count: u32) {
 
 fn funded_account<T: Trait<I>, I: Instance>(name: &'static str, id: u32) -> T::AccountId {
     let account_id = account::<T::AccountId>(name, id, SEED);
-    T::Currency::make_free_balance_be(&account_id, Balance::<T, I>::max_value());
+    balances::Module::<T>::make_free_balance_be(&account_id, BalanceOf::<T>::max_value());
 
     account_id
 }
@@ -97,7 +95,7 @@ fn vote_for<
     voter_id: u32,
     member_option: T::MemberId,
     cycle_id: u32,
-    extra_stake: Balance<T, I>,
+    extra_stake: BalanceOf<T>,
 ) -> (T::AccountId, T::MemberId, T::Hash) {
     let account_id = funded_account::<T, I>(name, voter_id);
     let stake = T::MinimumStake::get() + One::one() + extra_stake;
@@ -145,7 +143,7 @@ fn create_account_and_vote<
     voter_id: u32,
     option: u32,
     cycle_id: u32,
-    extra_stake: Balance<T, I>,
+    extra_stake: BalanceOf<T>,
 ) -> (T::AccountId, T::MemberId, T::Hash) {
     let (account_option, member_option) = member_funded_account::<T, I>(option.into());
     T::create_option(account_option, member_option);
@@ -231,7 +229,7 @@ fn member_funded_account<T: Trait<I> + membership::Trait, I: Instance>(
 
     Membership::<T>::buy_membership(RawOrigin::Signed(account_id.clone()).into(), params).unwrap();
 
-    T::Currency::make_free_balance_be(&account_id, Balance::<T, I>::max_value());
+    balances::Module::<T>::make_free_balance_be(&account_id, BalanceOf::<T>::max_value());
 
     Membership::<T>::add_staking_account_candidate(
         RawOrigin::Signed(account_id.clone()).into(),
@@ -258,7 +256,7 @@ fn add_and_reveal_multiple_votes_and_add_extra_unrevealed_vote<
     target_winners: u32,
     number_of_voters: u32,
     extra_vote_option: u32,
-    extra_stake: Balance<T, I>,
+    extra_stake: BalanceOf<T>,
 ) -> (
     Vec<OptionResult<T::MemberId, T::VotePower>>,
     T::AccountId,
@@ -334,7 +332,7 @@ benchmarks_instance! {
     _ { }
 
     on_initialize_revealing {
-        let i in 0 .. MAX_WINNERS;
+        let i in 0 .. (T::MaxWinnerTargetCount::get() - 1) as u32;
 
         let cycle_id = 0;
         let salt = vec![0u8];
@@ -447,7 +445,7 @@ benchmarks_instance! {
     }
 
     reveal_vote_space_for_new_winner {
-        let i in 0 .. MAX_WINNERS;
+        let i in 0 .. (T::MaxWinnerTargetCount::get() - 1) as u32;
 
         let salt = vec![0u8];
         let vote_option = 2 * (i + 1); // Greater than number of voters + number of candidates
@@ -499,7 +497,7 @@ benchmarks_instance! {
     }
 
     reveal_vote_space_not_in_winners {
-        let i in 0 .. MAX_WINNERS;
+        let i in 0 .. (T::MaxWinnerTargetCount::get() - 1) as u32;
 
         let salt = vec![0u8];
         let vote_option = 2 * (i + 1); // Greater than number of voters + number of candidates
@@ -543,7 +541,7 @@ benchmarks_instance! {
     }
 
     reveal_vote_space_replace_last_winner {
-        let i in 0 .. MAX_WINNERS;
+        let i in 0 .. (T::MaxWinnerTargetCount::get() - 1) as u32;
 
         let salt = vec![0u8];
         let vote_option = 2 * (i + 1); // Greater than number of voters + number of candidates
@@ -594,7 +592,7 @@ benchmarks_instance! {
     }
 
     reveal_vote_already_existing {
-        let i in 0 .. MAX_WINNERS;
+        let i in 0 .. (T::MaxWinnerTargetCount::get() - 1) as u32;
 
         let salt = vec![0u8];
         let vote_option = i;

+ 134 - 93
runtime-modules/referendum/src/lib.rs

@@ -35,19 +35,21 @@
 // used dependencies
 use codec::{Codec, Decode, Encode};
 use core::marker::PhantomData;
-use frame_support::traits::{
-    Currency, EnsureOrigin, Get, LockIdentifier, LockableCurrency, WithdrawReason,
-};
+use frame_support::traits::{EnsureOrigin, Get, WithdrawReason};
 use frame_support::weights::Weight;
 use frame_support::{
-    decl_error, decl_event, decl_module, decl_storage, error::BadOrigin, Parameter, StorageValue,
+    decl_error, decl_event, decl_module, decl_storage, ensure, error::BadOrigin, Parameter,
+    StorageValue,
 };
 use frame_system::ensure_signed;
 use sp_arithmetic::traits::BaseArithmetic;
 use sp_runtime::traits::{MaybeSerialize, Member};
+use sp_runtime::SaturatedConversion;
 use sp_std::vec;
 use sp_std::vec::Vec;
 
+use staking_handler::StakingHandler;
+
 #[cfg(feature = "std")]
 use serde::{Deserialize, Serialize};
 
@@ -95,10 +97,14 @@ pub struct ReferendumStageVoting<BlockNumber> {
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
 #[derive(Encode, Decode, PartialEq, Eq, Debug, Default)]
 pub struct ReferendumStageRevealing<BlockNumber, MemberId, VotePower> {
-    pub started: BlockNumber,      // block in which referendum started
-    pub winning_target_count: u64, // target number of winners
-    pub intermediate_winners: Vec<OptionResult<MemberId, VotePower>>, // intermediate winning options
-    pub current_cycle_id: u64,                                        // index of current election
+    // block in which referendum started
+    pub started: BlockNumber,
+    // target number of winners
+    pub winning_target_count: u64,
+    // intermediate winning options
+    pub intermediate_winners: Vec<OptionResult<MemberId, VotePower>>,
+    // index of current election
+    pub current_cycle_id: u64,
 }
 
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
@@ -112,22 +118,26 @@ pub struct OptionResult<MemberId, VotePower> {
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
 #[derive(Encode, Decode, PartialEq, Eq, Debug, Default)]
 pub struct CastVote<Hash, Currency, MemberId> {
-    pub commitment: Hash, // a commitment that a user submits in the voting stage before revealing what this vote is actually for
-    pub cycle_id: u64,    // current referendum cycle number
-    pub stake: Currency,  // stake locked for vote
-    pub vote_for: Option<MemberId>, // target option this vote favors; is `None` before the vote is revealed
+    // A commitment that a user submits in the voting stage before revealing what this vote is
+    // actually for
+    pub commitment: Hash,
+    // current referendum cycle number
+    pub cycle_id: u64,
+    // stake locked for vote
+    pub stake: Currency,
+    // target option this vote favors; is `None` before the vote is revealed
+    pub vote_for: Option<MemberId>,
 }
 
 /////////////////// Type aliases ///////////////////////////////////////////////
 
-// `Ez` prefix in some of the following type aliases means *easy* and is meant to create unique short names
-// aliasing existing structs and enums
+// `Ez` prefix in some of the following type aliases means *easy* and is meant to create unique
+// short names aliasing existing structs and enums
 
 // types simplifying access to common structs and enums
-pub type Balance<T, I> =
-    <<T as Trait<I>>::Currency as Currency<<T as frame_system::Trait>::AccountId>>::Balance;
-pub type CastVoteOf<T, I> =
-    CastVote<<T as frame_system::Trait>::Hash, Balance<T, I>, <T as common::Trait>::MemberId>;
+pub type BalanceOf<T> = <T as balances::Trait>::Balance;
+pub type CastVoteOf<T> =
+    CastVote<<T as frame_system::Trait>::Hash, BalanceOf<T>, <T as common::Trait>::MemberId>;
 pub type ReferendumStageVotingOf<T> =
     ReferendumStageVoting<<T as frame_system::Trait>::BlockNumber>;
 pub type ReferendumStageRevealingOf<T, I> = ReferendumStageRevealing<
@@ -142,7 +152,7 @@ pub type OptionResultOf<T, I> =
 pub type CanRevealResult<T, I> = (
     ReferendumStageRevealingOf<T, I>,
     <T as frame_system::Trait>::AccountId,
-    CastVoteOf<T, I>,
+    CastVoteOf<T>,
 );
 
 /////////////////// Traits, Storage, Errors, and Events /////////////////////////
@@ -160,7 +170,7 @@ pub trait WeightInfo {
     fn release_vote_stake() -> Weight;
 }
 
-const MAX_WINNERS: u32 = 500; // TODO: Replace with a trait type
+type ReferendumWeightInfo<T, I> = <T as Trait<I>>::WeightInfo;
 
 /// Trait that should be used by other modules to start the referendum, etc.
 pub trait ReferendumManager<Origin, AccountId, MemberId, Hash> {
@@ -174,9 +184,6 @@ pub trait ReferendumManager<Origin, AccountId, MemberId, Hash> {
         + MaybeSerialize
         + PartialEq;
 
-    /// Currency for referendum staking.
-    type Currency: LockableCurrency<AccountId>;
-
     /// Start a new referendum.
     fn start_referendum(
         origin: Origin,
@@ -185,7 +192,10 @@ pub trait ReferendumManager<Origin, AccountId, MemberId, Hash> {
     ) -> Result<(), ()>;
 
     /// Start referendum independent of the current state.
-    /// If an election is running before calling this function, it will be discontinued without any winners selected.
+    /// If an election is running before calling this function, it will be discontinued without
+    /// any winners selected.
+    /// If it is called with a bigger winning target count greated than the max allowed the max
+    /// will be used
     fn force_start(extra_winning_target_count: u64, cycle_id: u64);
 
     /// Calculate commitment for a vote.
@@ -198,18 +208,18 @@ pub trait ReferendumManager<Origin, AccountId, MemberId, Hash> {
 }
 
 /// The main Referendum module's trait.
-pub trait Trait<I: Instance = DefaultInstance>: frame_system::Trait + common::Trait {
+pub trait Trait<I: Instance = DefaultInstance>:
+    frame_system::Trait + common::Trait + balances::Trait
+{
     /// The overarching event type.
     type Event: From<Event<Self, I>> + Into<<Self as frame_system::Trait>::Event>;
 
-    /// Maximum length of vote commitment salt. Use length that ensures uniqueness for hashing e.g. std::u64::MAX.
+    /// Maximum length of vote commitment salt. Use length that ensures uniqueness for hashing
+    /// e.g. std::u64::MAX.
     type MaxSaltLength: Get<u64>;
 
-    /// Currency for referendum staking.
-    type Currency: LockableCurrency<Self::AccountId, Moment = Self::BlockNumber>;
-
-    /// Identifier for currency locks used for staking.
-    type LockId: Get<LockIdentifier>;
+    /// Stakes and balance locks handler.
+    type StakingHandler: StakingHandler<Self::AccountId, BalanceOf<Self>, Self::MemberId>;
 
     /// Origin from which the referendum can be started.
     type ManagerOrigin: EnsureOrigin<Self::Origin>;
@@ -230,21 +240,23 @@ pub trait Trait<I: Instance = DefaultInstance>: frame_system::Trait + common::Tr
     type RevealStageDuration: Get<Self::BlockNumber>;
 
     /// Minimum stake needed for voting
-    type MinimumStake: Get<Balance<Self, I>>;
+    type MinimumStake: Get<BalanceOf<Self>>;
 
     /// Weight information for extrinsics in this pallet.
     type WeightInfo: WeightInfo;
 
+    /// Maximum number of winning target count
+    type MaxWinnerTargetCount: Get<u64>;
+
     /// Calculate the vote's power for user and his stake.
     fn calculate_vote_power(
         account_id: &<Self as frame_system::Trait>::AccountId,
-        stake: &Balance<Self, I>,
+        stake: &BalanceOf<Self>,
     ) -> <Self as Trait<I>>::VotePower;
 
     /// Checks if user can unlock his stake from the given vote.
     /// Gives runtime an ability to penalize user for not revealing stake, etc.
-    fn can_unlock_vote_stake(vote: &CastVote<Self::Hash, Balance<Self, I>, Self::MemberId>)
-        -> bool;
+    fn can_unlock_vote_stake(vote: &CastVote<Self::Hash, BalanceOf<Self>, Self::MemberId>) -> bool;
 
     /// Gives runtime an ability to react on referendum result.
     fn process_results(winners: &[OptionResult<Self::MemberId, Self::VotePower>]);
@@ -252,30 +264,36 @@ pub trait Trait<I: Instance = DefaultInstance>: frame_system::Trait + common::Tr
     /// Check if an option a user is voting for actually exists.
     fn is_valid_option_id(option_id: &Self::MemberId) -> bool;
 
-    // If the id is a valid alternative, the current total voting mass backing it is returned, otherwise nothing.
+    /// If the id is a valid alternative, the current total voting mass backing it is returned,
+    /// otherwise nothing.
     fn get_option_power(option_id: &Self::MemberId) -> Self::VotePower;
 
-    // Increases voting mass behind given alternative by given amount, if present and return true, otherwise return false.
+    /// Increases voting mass behind given alternative by given amount, if present and return true,
+    /// otherwise return false.
     fn increase_option_power(option_id: &Self::MemberId, amount: &Self::VotePower);
 }
 
 decl_storage! {
     trait Store for Module<T: Trait<I>, I: Instance = DefaultInstance> as Referendum {
         /// Current referendum stage.
-        pub Stage get(fn stage) config(): ReferendumStage<T::BlockNumber, T::MemberId, T::VotePower>;
+        pub Stage get(fn stage) config():
+            ReferendumStage<T::BlockNumber, T::MemberId, T::VotePower>;
 
-        /// Votes cast in the referendum. A new record is added to this map when a user casts a sealed vote.
+        /// Votes cast in the referendum. A new record is added to this map when a user casts a
+        /// sealed vote.
         /// It is modified when a user reveals the vote's commitment proof.
-        /// A record is finally removed when the user unstakes, which can happen during a voting stage or after the current cycle ends.
+        /// A record is finally removed when the user unstakes, which can happen during a voting
+        /// stage or after the current cycle ends.
         /// A stake for a vote can be reused in future referendum cycles.
-        pub Votes get(fn votes) config(): map hasher(blake2_128_concat) T::AccountId => CastVoteOf<T, I>;
+        pub Votes get(fn votes) config(): map hasher(blake2_128_concat)
+                                          T::AccountId => CastVoteOf<T>;
     }
 }
 
 decl_event! {
     pub enum Event<T, I = DefaultInstance>
     where
-        Balance = Balance<T, I>,
+        Balance = BalanceOf<T>,
         <T as frame_system::Trait>::Hash,
         <T as frame_system::Trait>::AccountId,
         <T as Trait<I>>::VotePower,
@@ -316,8 +334,11 @@ decl_error! {
         /// Revealing stage is not in progress right now
         RevealingNotInProgress,
 
-        /// Account can't stake enough currency (now)
-        InsufficientBalanceToStakeCurrency,
+        /// Staking account contains conflicting stakes.
+        ConflictStakesOnAccount,
+
+        /// Account Insufficient Free Balance (now)
+        InsufficientBalanceToStake,
 
         /// Insufficient stake provided to cast a vote
         InsufficientStake,
@@ -360,23 +381,23 @@ impl<T: Trait<I>, I: Instance> From<BadOrigin> for Error<T, I> {
 /////////////////// Module definition and implementation ///////////////////////
 
 decl_module! {
-    pub struct Module<T: Trait<I>, I: Instance = DefaultInstance> for enum Call where origin: T::Origin {
+    pub struct Module<T: Trait<I>, I: Instance = DefaultInstance> for enum Call
+        where origin: T::Origin {
         /// Predefined errors
         type Error = Error<T, I>;
 
         /// Setup events
         fn deposit_event() = default;
 
-        /// Maximum length of vote commitment salt. Use length that ensures uniqueness for hashing e.g. std::u64::MAX.
+        /// Maximum length of vote commitment salt. Use length that ensures uniqueness for hashing
+        /// e.g. std::u64::MAX.
         const MaxSaltLength: u64 = T::MaxSaltLength::get();
-        /// Identifier for currency locks used for staking.
-        const LockId: LockIdentifier = T::LockId::get();
         /// Duration of voting stage (number of blocks)
         const VoteStageDuration: T::BlockNumber = T::VoteStageDuration::get();
         /// Duration of revealing stage (number of blocks)
         const RevealStageDuration: T::BlockNumber = T::RevealStageDuration::get();
         /// Minimum stake needed for voting
-        const MinimumStake: Balance<T, I> = T::MinimumStake::get();
+        const MinimumStake: BalanceOf<T> = T::MinimumStake::get();
 
         /////////////////// Lifetime ///////////////////////////////////////////
 
@@ -384,8 +405,10 @@ decl_module! {
         fn on_initialize() -> Weight {
             Self::try_progress_stage(frame_system::Module::<T>::block_number());
 
-            T::WeightInfo::on_initialize_voting()
-                .max(T::WeightInfo::on_initialize_revealing(MAX_WINNERS))
+            ReferendumWeightInfo::<T, I>::on_initialize_voting()
+                .max(ReferendumWeightInfo::<T, I>::on_initialize_revealing(
+                        T::MaxWinnerTargetCount::get().saturated_into()
+                ))
         }
 
         /////////////////// User actions ///////////////////////////////////////
@@ -399,8 +422,8 @@ decl_module! {
         /// - db:
         ///    - `O(1)` doesn't depend on the state or parameters
         /// # </weight>
-        #[weight = T::WeightInfo::vote()]
-        pub fn vote(origin, commitment: T::Hash, stake: Balance<T, I>) -> Result<(), Error<T, I>> {
+        #[weight = ReferendumWeightInfo::<T, I>::vote()]
+        pub fn vote(origin, commitment: T::Hash, stake: BalanceOf<T>) -> Result<(), Error<T, I>> {
             // ensure action can be started
             let (current_cycle_id, account_id) = EnsureChecks::<T, I>::can_vote(origin, &stake)?;
 
@@ -423,13 +446,21 @@ decl_module! {
         ///
         /// ## Weight
         /// `O (W)` where:
-        /// - `W` is the number of `intermediate_winners` stored in the current `Stage::<T, I>::get()`
+        /// - `W` is the number of `intermediate_winners` stored in the current
+        ///     `Stage::<T, I>::get()`
         /// - DB:
         ///    - `O(1)` doesn't depend on the state or parameters
         /// # </weight>
-        #[weight = Module::<T, I>::calculate_reveal_vote_weight(MAX_WINNERS)]
-        pub fn reveal_vote(origin, salt: Vec<u8>, vote_option_id: <T as common::Trait>::MemberId) -> Result<(), Error<T, I>> {
-            let (stage_data, account_id, cast_vote) = EnsureChecks::<T, I>::can_reveal_vote::<Self>(origin, &salt, &vote_option_id)?;
+        #[weight = Module::<T, I>::calculate_reveal_vote_weight(
+            T::MaxWinnerTargetCount::get().saturated_into()
+        )]
+        pub fn reveal_vote(
+            origin,
+            salt: Vec<u8>,
+            vote_option_id: <T as common::Trait>::MemberId
+        ) -> Result<(), Error<T, I>> {
+            let (stage_data, account_id, cast_vote) =
+                EnsureChecks::<T, I>::can_reveal_vote::<Self>(origin, &salt, &vote_option_id)?;
 
             //
             // == MUTATION SAFE ==
@@ -452,7 +483,7 @@ decl_module! {
         /// - db:
         ///    - `O(1)` doesn't depend on the state or parameters
         /// # </weight>
-        #[weight = T::WeightInfo::release_vote_stake()]
+        #[weight = ReferendumWeightInfo::<T, I>::release_vote_stake()]
         pub fn release_vote_stake(origin) -> Result<(), Error<T, I>> {
             let account_id = EnsureChecks::<T, I>::can_release_vote_stake(origin)?;
 
@@ -476,17 +507,19 @@ decl_module! {
 impl<T: Trait<I>, I: Instance> Module<T, I> {
     // Calculate reveal_vote weight
     fn calculate_reveal_vote_weight(number_of_winners: u32) -> Weight {
-        T::WeightInfo::reveal_vote_space_for_new_winner(number_of_winners)
-            .max(T::WeightInfo::reveal_vote_space_not_in_winners(
-                number_of_winners,
-            ))
-            .max(T::WeightInfo::reveal_vote_space_replace_last_winner(
-                number_of_winners,
-            ))
-            .max(T::WeightInfo::reveal_vote_space_replace_last_winner(
-                number_of_winners,
-            ))
-            .max(T::WeightInfo::reveal_vote_already_existing(
+        ReferendumWeightInfo::<T, I>::reveal_vote_space_for_new_winner(number_of_winners)
+            .max(ReferendumWeightInfo::<T, I>::reveal_vote_space_not_in_winners(number_of_winners))
+            .max(
+                ReferendumWeightInfo::<T, I>::reveal_vote_space_replace_last_winner(
+                    number_of_winners,
+                ),
+            )
+            .max(
+                ReferendumWeightInfo::<T, I>::reveal_vote_space_replace_last_winner(
+                    number_of_winners,
+                ),
+            )
+            .max(ReferendumWeightInfo::<T, I>::reveal_vote_already_existing(
                 number_of_winners,
             ))
     }
@@ -536,7 +569,6 @@ impl<T: Trait<I>, I: Instance> ReferendumManager<T::Origin, T::AccountId, T::Mem
     for Module<T, I>
 {
     type VotePower = T::VotePower;
-    type Currency = T::Currency;
 
     // Start new referendum run.
     fn start_referendum(
@@ -549,6 +581,9 @@ impl<T: Trait<I>, I: Instance> ReferendumManager<T::Origin, T::AccountId, T::Mem
         // ensure action can be started
         EnsureChecks::<T, I>::can_start_referendum(origin)?;
 
+        // ensure that the winning target count doesn't go over the limit
+        ensure!(winning_target_count <= T::MaxWinnerTargetCount::get(), ());
+
         //
         // == MUTATION SAFE ==
         //
@@ -563,10 +598,14 @@ impl<T: Trait<I>, I: Instance> ReferendumManager<T::Origin, T::AccountId, T::Mem
     }
 
     // Start referendum independent of the current state.
-    // If an election is running before calling this function, it will be discontinued without any winners selected.
+    // If an election is running before calling this function, it will be discontinued without any
+    // winners selected.
     fn force_start(extra_winning_target_count: u64, cycle_id: u64) {
         let winning_target_count = extra_winning_target_count + 1;
 
+        // If a greater than the max allowed target count is used the max will be used in its place
+        let winning_target_count = winning_target_count.min(T::MaxWinnerTargetCount::get());
+
         // remember if referendum is running
         let referendum_running = !matches!(Stage::<T, I>::get(), ReferendumStage::Inactive);
 
@@ -649,16 +688,11 @@ impl<T: Trait<I>, I: Instance> Mutations<T, I> {
     fn vote(
         account_id: &<T as frame_system::Trait>::AccountId,
         commitment: &T::Hash,
-        stake: &Balance<T, I>,
+        stake: &BalanceOf<T>,
         current_cycle_id: &u64,
     ) -> Result<(), Error<T, I>> {
-        // lock stake amount
-        T::Currency::set_lock(
-            T::LockId::get(),
-            account_id,
-            *stake,
-            WithdrawReason::Transfer.into(),
-        );
+        // Should call after `can_vote`
+        T::StakingHandler::lock_with_reasons(account_id, *stake, WithdrawReason::Transfer.into());
 
         // store vote
         Votes::<T, I>::insert(
@@ -679,7 +713,7 @@ impl<T: Trait<I>, I: Instance> Mutations<T, I> {
         stage_data: ReferendumStageRevealingOf<T, I>,
         account_id: &<T as frame_system::Trait>::AccountId,
         option_id: &<T as common::Trait>::MemberId,
-        cast_vote: CastVoteOf<T, I>,
+        cast_vote: CastVoteOf<T>,
     ) -> Result<(), Error<T, I>> {
         // prepare new values
         let vote_power = T::calculate_vote_power(&account_id, &cast_vote.stake);
@@ -712,14 +746,15 @@ impl<T: Trait<I>, I: Instance> Mutations<T, I> {
 
     // Release stake associated to the user's last vote.
     fn release_vote_stake(account_id: &<T as frame_system::Trait>::AccountId) {
-        // lock stake amount
-        T::Currency::remove_lock(T::LockId::get(), account_id);
+        // unlock stake amount
+        T::StakingHandler::unlock(account_id);
 
         // remove vote record
         Votes::<T, I>::remove(account_id);
     }
 
-    // Tries to insert option to the proper place in the winners list. Utility for reaveal_vote() function.
+    // Tries to insert option to the proper place in the winners list. Utility for reaveal_vote()
+    // function.
     fn try_winner_insert(
         option_result: OptionResultOf<T, I>,
         current_winners: &[OptionResultOf<T, I>],
@@ -740,7 +775,8 @@ impl<T: Trait<I>, I: Instance> Mutations<T, I> {
                 .find(|(_, value)| option_result.option_id == value.option_id)
                 .map(|(index, _)| index);
 
-            // espace when item is currently not in winning list and still has not enough vote power to make it to already full list
+            // espace when item is currently not in winning list and still has not enough vote
+            // power to make it to already full list
             if current_winners_index_of_vote_recipient.is_none()
                 && current_winners_count as u64 == winning_target_count
                 && option_result.vote_power <= current_winners[current_winners_count - 1].vote_power
@@ -837,7 +873,7 @@ impl<T: Trait<I>, I: Instance> EnsureChecks<T, I> {
 
     fn can_vote(
         origin: T::Origin,
-        stake: &Balance<T, I>,
+        stake: &BalanceOf<T>,
     ) -> Result<(u64, T::AccountId), Error<T, I>> {
         fn prevent_repeated_vote<T: Trait<I>, I: Instance>(
             cycle_id: &u64,
@@ -870,14 +906,19 @@ impl<T: Trait<I>, I: Instance> EnsureChecks<T, I> {
         prevent_repeated_vote::<T, I>(&current_cycle_id, &account_id)?;
 
         // ensure stake is enough for voting
-        if stake < &T::MinimumStake::get() {
-            return Err(Error::InsufficientStake);
-        }
+        ensure!(stake >= &T::MinimumStake::get(), Error::InsufficientStake);
 
-        // ensure account can lock the stake
-        if T::Currency::total_balance(&account_id) < *stake {
-            return Err(Error::InsufficientBalanceToStakeCurrency);
-        }
+        // Ensure account doesn't have conflicting stakes
+        ensure!(
+            T::StakingHandler::is_account_free_of_conflicting_stakes(&account_id),
+            Error::ConflictStakesOnAccount
+        );
+
+        // ensure stake is enough for voting
+        ensure!(
+            T::StakingHandler::is_enough_balance_for_stake(&account_id, *stake),
+            Error::InsufficientStake
+        );
 
         Ok((current_cycle_id, account_id))
     }
@@ -941,7 +982,7 @@ impl<T: Trait<I>, I: Instance> EnsureChecks<T, I> {
         Ok(account_id)
     }
 
-    fn ensure_vote_exists(account_id: &T::AccountId) -> Result<CastVoteOf<T, I>, Error<T, I>> {
+    fn ensure_vote_exists(account_id: &T::AccountId) -> Result<CastVoteOf<T>, Error<T, I>> {
         // ensure there is some vote with locked stake
         if !Votes::<T, I>::contains_key(account_id) {
             return Err(Error::VoteNotExisting);

+ 57 - 28
runtime-modules/referendum/src/mock.rs

@@ -2,7 +2,7 @@
 
 /////////////////// Configuration //////////////////////////////////////////////
 use crate::{
-    Balance, CastVote, Error, Instance, Module, OptionResult, RawEvent, ReferendumManager,
+    BalanceOf, CastVote, Error, Instance, Module, OptionResult, RawEvent, ReferendumManager,
     ReferendumStage, ReferendumStageRevealing, ReferendumStageVoting, Stage, Trait, Votes,
     WeightInfo,
 };
@@ -16,7 +16,6 @@ use frame_support::{
     impl_outer_event, impl_outer_origin, parameter_types, StorageMap, StorageValue,
 };
 use frame_system::{EnsureOneOf, EnsureRoot, EnsureSigned, RawOrigin};
-use pallet_balances;
 use rand::Rng;
 use sp_core::H256;
 use sp_runtime::traits::One;
@@ -30,6 +29,8 @@ use std::collections::BTreeMap;
 use std::iter::FromIterator;
 use std::marker::PhantomData;
 
+use staking_handler::LockComparator;
+
 use crate::GenesisConfig;
 
 pub const USER_ADMIN: u64 = 1;
@@ -53,6 +54,7 @@ parameter_types! {
     pub const RevealStageDuration: u64 = 7;
     pub const MinimumStake: u64 = 10000;
     pub const LockId: LockIdentifier = *b"referend";
+    pub const MaxWinnerTargetCount: u64 = 10;
 }
 
 thread_local! {
@@ -63,14 +65,21 @@ thread_local! {
     pub static INTERMEDIATE_RESULTS: RefCell<BTreeMap<u64, <Runtime as Trait>::VotePower>> = RefCell::new(BTreeMap::<u64, <Runtime as Trait>::VotePower>::new());
 }
 
+impl LockComparator<u64> for Runtime {
+    fn are_locks_conflicting(
+        _new_lock: &LockIdentifier,
+        _existing_locks: &[LockIdentifier],
+    ) -> bool {
+        false
+    }
+}
+
 impl Trait for Runtime {
     type Event = TestEvent;
 
     type MaxSaltLength = MaxSaltLength;
 
-    type Currency = pallet_balances::Module<Runtime>;
-    type LockId = LockId;
-
+    type StakingHandler = staking_handler::StakingManager<Self, LockId>;
     type ManagerOrigin =
         EnsureOneOf<Self::AccountId, EnsureSigned<Self::AccountId>, EnsureRoot<Self::AccountId>>;
 
@@ -82,9 +91,11 @@ impl Trait for Runtime {
     type MinimumStake = MinimumStake;
     type WeightInfo = ();
 
+    type MaxWinnerTargetCount = MaxWinnerTargetCount;
+
     fn calculate_vote_power(
         account_id: &<Self as frame_system::Trait>::AccountId,
-        stake: &Balance<Self, DefaultInstance>,
+        stake: &BalanceOf<Self>,
     ) -> <Self as Trait<DefaultInstance>>::VotePower {
         let stake: u64 = u64::from(*stake);
         if *account_id == USER_REGULAR_POWER_VOTES {
@@ -95,7 +106,7 @@ impl Trait for Runtime {
     }
 
     fn can_unlock_vote_stake(
-        _vote: &CastVote<Self::Hash, Balance<Self, DefaultInstance>, Self::MemberId>,
+        _vote: &CastVote<Self::Hash, BalanceOf<Self>, Self::MemberId>,
     ) -> bool {
         // trigger fail when requested to do so
         if !IS_UNSTAKE_ENABLED.with(|value| value.borrow().0) {
@@ -163,6 +174,7 @@ impl WeightInfo for () {
 parameter_types! {
     pub const DefaultMembershipPrice: u64 = 100;
     pub const DefaultInitialInvitationBalance: u64 = 100;
+    pub const InvitedMemberLockId: [u8; 8] = [2; 8];
 }
 
 impl membership::Trait for Runtime {
@@ -170,6 +182,7 @@ impl membership::Trait for Runtime {
     type DefaultMembershipPrice = DefaultMembershipPrice;
     type WorkingGroup = ();
     type DefaultInitialInvitationBalance = DefaultInitialInvitationBalance;
+    type InvitedMemberStakingHandler = staking_handler::StakingManager<Self, InvitedMemberLockId>;
 }
 
 impl pallet_timestamp::Trait for Runtime {
@@ -179,34 +192,29 @@ impl pallet_timestamp::Trait for Runtime {
     type WeightInfo = ();
 }
 
-impl common::working_group::WorkingGroupIntegration<Runtime> for () {
-    fn ensure_worker_origin(
-        _origin: <Runtime as frame_system::Trait>::Origin,
-        _worker_id: &<Runtime as common::Trait>::ActorId,
-    ) -> DispatchResult {
-        unimplemented!()
-    }
-
-    fn ensure_leader_origin(_origin: <Runtime as frame_system::Trait>::Origin) -> DispatchResult {
+impl common::working_group::WorkingGroupBudgetHandler<Runtime> for () {
+    fn get_budget() -> u64 {
         unimplemented!()
     }
 
-    fn ensure_leader_origin(_origin: <Runtime as frame_system::Trait>::Origin) -> DispatchResult {
+    fn set_budget(_new_value: u64) {
         unimplemented!()
     }
+}
 
-    fn get_leader_member_id() -> Option<<Runtime as common::Trait>::MemberId> {
+impl common::working_group::WorkingGroupAuthenticator<Runtime> for () {
+    fn ensure_worker_origin(
+        _origin: <Runtime as frame_system::Trait>::Origin,
+        _worker_id: &<Runtime as common::Trait>::ActorId,
+    ) -> DispatchResult {
         unimplemented!()
     }
 
-    fn is_leader_account_id(_account_id: &<Runtime as frame_system::Trait>::AccountId) -> bool {
+    fn ensure_leader_origin(_origin: <Runtime as frame_system::Trait>::Origin) -> DispatchResult {
         unimplemented!()
     }
 
-    fn is_worker_account_id(
-        _account_id: &<Runtime as frame_system::Trait>::AccountId,
-        _worker_id: &<Runtime as common::Trait>::ActorId,
-    ) -> bool {
+    fn get_leader_member_id() -> Option<<Runtime as common::Trait>::MemberId> {
         unimplemented!()
     }
 
@@ -232,7 +240,7 @@ parameter_types! {
     pub const MaxLocks: u32 = 50;
 }
 
-impl pallet_balances::Trait for Runtime {
+impl balances::Trait for Runtime {
     type Balance = u64;
     type Event = TestEvent;
     type DustRemoval = ();
@@ -268,7 +276,7 @@ mod event_mod {
 }
 
 mod tmp {
-    pub use pallet_balances::Event;
+    pub use balances::Event;
 }
 
 mod membership_mod {
@@ -314,7 +322,7 @@ impl frame_system::Trait for Runtime {
     type AvailableBlockRatio = AvailableBlockRatio;
     type Version = ();
     type PalletInfo = ();
-    type AccountData = pallet_balances::AccountData<u64>;
+    type AccountData = balances::AccountData<u64>;
     type OnNewAccount = ();
     type OnKilledAccount = ();
     type SystemWeightInfo = ();
@@ -370,7 +378,7 @@ pub fn build_test_externalities(
 // topup currency to the account
 fn topup_account(account_id: u64, amount: u64) {
     let account_id = account_id;
-    let _ = pallet_balances::Module::<Runtime>::deposit_creating(&account_id, amount);
+    let _ = balances::Module::<Runtime>::deposit_creating(&account_id, amount);
 }
 
 pub struct InstanceMockUtils<T: Trait<I>, I: Instance> {
@@ -514,6 +522,16 @@ impl InstanceMocks<Runtime, DefaultInstance> {
         Self::start_referendum_inner(extra_winning_target_count, cycle_id, expected_result)
     }
 
+    // checks that winning_target_count equals expected
+    // fails if used outisde of voting stage
+    pub fn check_winning_target_count(winning_target_count: u64) {
+        if let ReferendumStage::Voting(stage_data) = Stage::<Runtime, DefaultInstance>::get() {
+            assert_eq!(stage_data.winning_target_count, winning_target_count);
+        } else {
+            assert!(false);
+        }
+    }
+
     pub fn start_referendum_manager(
         winning_target_count: u64,
         cycle_id: u64,
@@ -540,6 +558,17 @@ impl InstanceMocks<Runtime, DefaultInstance> {
         Self::start_referendum_inner(extra_winning_target_count, cycle_id, expected_result)
     }
 
+    pub fn force_start(winning_target_count: u64, cycle_id: u64) -> () {
+        let extra_winning_target_count = winning_target_count - 1;
+
+        <Module<Runtime> as ReferendumManager<
+            <Runtime as frame_system::Trait>::Origin,
+            <Runtime as frame_system::Trait>::AccountId,
+            <Runtime as common::Trait>::MemberId,
+            <Runtime as frame_system::Trait>::Hash,
+        >>::force_start(extra_winning_target_count, cycle_id);
+    }
+
     fn start_referendum_inner(
         extra_winning_target_count: u64,
         cycle_id: u64,
@@ -622,7 +651,7 @@ impl InstanceMocks<Runtime, DefaultInstance> {
         origin: OriginType<<Runtime as frame_system::Trait>::AccountId>,
         account_id: <Runtime as frame_system::Trait>::AccountId,
         commitment: <Runtime as frame_system::Trait>::Hash,
-        stake: Balance<Runtime, DefaultInstance>,
+        stake: BalanceOf<Runtime>,
         cycle_id: u64,
         expected_result: Result<(), Error<Runtime, DefaultInstance>>,
     ) -> () {

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

@@ -1020,3 +1020,30 @@ fn referendum_manager_referendum_start() {
         Mocks::start_referendum_manager(winning_target_count, cycle_id, Ok(()));
     });
 }
+
+/// Test that trying to start with more than allowed targets fails
+#[test]
+fn referendum_manager_referendum_start_error_with_more_than_allowed_target() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let winning_target_count = <Runtime as Trait>::MaxWinnerTargetCount::get() + 1;
+        let cycle_id = 1;
+
+        Mocks::start_referendum_manager(winning_target_count, cycle_id, Err(()));
+    });
+}
+
+/// Test that forcing the start with more than allowed max winners is capped
+#[test]
+fn referendum_manager_force_start_error_with_more_than_allowed_target() {
+    let config = default_genesis_config();
+
+    build_test_externalities(config).execute_with(|| {
+        let winning_target_count = <Runtime as Trait>::MaxWinnerTargetCount::get() + 5;
+        let cycle_id = 1;
+
+        Mocks::force_start(winning_target_count, cycle_id);
+        Mocks::check_winning_target_count(winning_target_count - 5);
+    });
+}

+ 23 - 3
runtime-modules/service-discovery/src/mock.rs

@@ -2,6 +2,7 @@
 
 pub use crate::*;
 
+use frame_support::dispatch::DispatchError;
 use frame_support::traits::LockIdentifier;
 use frame_support::weights::Weight;
 use frame_support::{impl_outer_event, impl_outer_origin, parameter_types};
@@ -97,9 +98,20 @@ impl membership::Trait for Test {
     type DefaultMembershipPrice = DefaultMembershipPrice;
     type WorkingGroup = ();
     type DefaultInitialInvitationBalance = ();
+    type InvitedMemberStakingHandler = staking_handler::StakingManager<Self, InvitedMemberLockId>;
 }
 
-impl common::working_group::WorkingGroupIntegration<Test> for () {
+impl common::working_group::WorkingGroupBudgetHandler<Test> for () {
+    fn get_budget() -> u64 {
+        unimplemented!()
+    }
+
+    fn set_budget(_new_value: u64) {
+        unimplemented!()
+    }
+}
+
+impl common::working_group::WorkingGroupAuthenticator<Test> for () {
     fn ensure_worker_origin(
         _origin: <Test as frame_system::Trait>::Origin,
         _worker_id: &<Test as common::Trait>::ActorId,
@@ -140,6 +152,7 @@ impl balances::Trait for Test {
 parameter_types! {
     pub const MaxWorkerNumberLimit: u32 = 3;
     pub const LockId1: [u8; 8] = [1; 8];
+    pub const InvitedMemberLockId: [u8; 8] = [2; 8];
 }
 
 pub struct WorkingGroupWeightInfo;
@@ -226,12 +239,19 @@ impl working_group::WeightInfo for WorkingGroupWeightInfo {
     }
 }
 
-impl common::origin::ActorOriginValidator<Origin, u64, u64> for () {
-    fn ensure_actor_origin(origin: Origin, _: u64) -> Result<u64, &'static str> {
+impl common::origin::MemberOriginValidator<Origin, u64, u64> for () {
+    fn ensure_member_controller_account_origin(
+        origin: Origin,
+        _: u64,
+    ) -> Result<u64, DispatchError> {
         let account_id = frame_system::ensure_signed(origin)?;
 
         Ok(account_id)
     }
+
+    fn is_member_controller_account(_member_id: &u64, _account_id: &u64) -> bool {
+        unimplemented!()
+    }
 }
 
 impl pallet_timestamp::Trait for Test {

+ 13 - 6
runtime-modules/staking-handler/src/lib.rs

@@ -28,8 +28,12 @@ pub trait LockComparator<Balance> {
 /// and LockIdentifier to lock balance consistently with pallet_staking.
 pub trait StakingHandler<AccountId, Balance, MemberId> {
     /// Locks the specified balance on the account using specific lock identifier.
+    /// It locks for all withdraw reasons.
     fn lock(account_id: &AccountId, amount: Balance);
 
+    /// Locks the specified balance on the account using specific lock identifier.
+    fn lock_with_reasons(account_id: &AccountId, amount: Balance, reasons: WithdrawReasons);
+
     /// Removes the specified lock on the account.
     fn unlock(account_id: &AccountId);
 
@@ -83,12 +87,15 @@ impl<
         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,
-            amount,
-            WithdrawReasons::all(),
-        )
+        Self::lock_with_reasons(account_id, amount, WithdrawReasons::all())
+    }
+
+    fn lock_with_reasons(
+        account_id: &<T as frame_system::Trait>::AccountId,
+        amount: <T as pallet_balances::Trait>::Balance,
+        reasons: WithdrawReasons,
+    ) {
+        <pallet_balances::Module<T>>::set_lock(LockId::get(), &account_id, amount, reasons)
     }
 
     fn unlock(account_id: &<T as frame_system::Trait>::AccountId) {

+ 4 - 4
runtime-modules/storage/src/data_directory.rs

@@ -32,8 +32,8 @@ use sp_std::vec::Vec;
 #[cfg(feature = "std")]
 use serde::{Deserialize, Serialize};
 
-use common::origin::ActorOriginValidator;
-use common::working_group::WorkingGroupIntegration;
+use common::origin::MemberOriginValidator;
+use common::working_group::WorkingGroupAuthenticator;
 pub(crate) use common::BlockAndTime;
 
 use crate::data_object_type_registry;
@@ -57,7 +57,7 @@ pub trait Trait:
     type IsActiveDataObjectType: data_object_type_registry::IsActiveDataObjectType<Self>;
 
     /// Validates member id and origin combination.
-    type MemberOriginValidator: ActorOriginValidator<Self::Origin, MemberId<Self>, Self::AccountId>;
+    type MemberOriginValidator: MemberOriginValidator<Self::Origin, MemberId<Self>, Self::AccountId>;
 
     type MaxObjectsPerInjection: Get<u32>;
 }
@@ -204,7 +204,7 @@ decl_module! {
             size: u64,
             ipfs_content_id: Vec<u8>
         ) {
-            <T as Trait>::MemberOriginValidator::ensure_actor_origin(
+            <T as Trait>::MemberOriginValidator::ensure_member_controller_account_origin(
                 origin,
                 member_id,
             )?;

+ 1 - 1
runtime-modules/storage/src/data_object_storage_registry.rs

@@ -25,7 +25,7 @@ use sp_arithmetic::traits::BaseArithmetic;
 use sp_runtime::traits::{MaybeSerialize, Member};
 use sp_std::vec::Vec;
 
-use common::working_group::WorkingGroupIntegration;
+use common::working_group::WorkingGroupAuthenticator;
 
 use crate::data_directory::{self, ContentIdExists};
 use crate::StorageProviderId;

+ 2 - 2
runtime-modules/storage/src/data_object_type_registry.rs

@@ -27,7 +27,7 @@ use sp_arithmetic::traits::BaseArithmetic;
 use sp_runtime::traits::{MaybeSerialize, Member};
 use sp_std::vec::Vec;
 
-use common::working_group::WorkingGroupIntegration;
+use common::working_group::WorkingGroupAuthenticator;
 
 const DEFAULT_TYPE_DESCRIPTION: &str = "Default data object type for audio and video content.";
 const DEFAULT_FIRST_DATA_OBJECT_TYPE_ID: u8 = 1;
@@ -48,7 +48,7 @@ pub trait Trait: frame_system::Trait + common::Trait {
         + PartialEq;
 
     /// Working group pallet integration.
-    type WorkingGroup: common::working_group::WorkingGroupIntegration<Self>;
+    type WorkingGroup: common::working_group::WorkingGroupAuthenticator<Self>;
 }
 
 decl_error! {

+ 1 - 1
runtime-modules/storage/src/tests/data_directory.rs

@@ -37,7 +37,7 @@ fn add_content_fails_with_invalid_origin() {
             0,
             vec![1, 3, 3, 7],
         );
-        assert_eq!(res, Err(DispatchError::Other("Bad origin")));
+        assert_eq!(res, Err(DispatchError::BadOrigin));
     });
 }
 

+ 23 - 3
runtime-modules/storage/src/tests/mock.rs

@@ -18,6 +18,7 @@ use crate::data_object_type_registry::IsActiveDataObjectType;
 pub use crate::{data_directory, data_object_storage_registry, data_object_type_registry};
 use frame_support::sp_runtime::DispatchResult;
 
+use frame_support::dispatch::DispatchError;
 use membership;
 
 pub type StorageWorkingGroupInstance = working_group::Instance2;
@@ -155,6 +156,7 @@ parameter_types! {
     pub const LockId: LockIdentifier = [2; 8];
     pub const DefaultMembershipPrice: u64 = 100;
     pub const DefaultInitialInvitationBalance: u64 = 100;
+    pub const InvitedMemberLockId: [u8; 8] = [2; 8];
 }
 
 pub struct WorkingGroupWeightInfo;
@@ -241,12 +243,19 @@ impl working_group::WeightInfo for WorkingGroupWeightInfo {
     }
 }
 
-impl common::origin::ActorOriginValidator<Origin, u64, u64> for () {
-    fn ensure_actor_origin(origin: Origin, _: u64) -> Result<u64, &'static str> {
+impl common::origin::MemberOriginValidator<Origin, u64, u64> for () {
+    fn ensure_member_controller_account_origin(
+        origin: Origin,
+        _: u64,
+    ) -> Result<u64, DispatchError> {
         let account_id = frame_system::ensure_signed(origin)?;
 
         Ok(account_id)
     }
+
+    fn is_member_controller_account(_member_id: &u64, _account_id: &u64) -> bool {
+        unimplemented!()
+    }
 }
 
 impl data_object_type_registry::Trait for Test {
@@ -286,9 +295,20 @@ impl membership::Trait for Test {
     type DefaultMembershipPrice = DefaultMembershipPrice;
     type WorkingGroup = ();
     type DefaultInitialInvitationBalance = ();
+    type InvitedMemberStakingHandler = staking_handler::StakingManager<Self, InvitedMemberLockId>;
+}
+
+impl common::working_group::WorkingGroupBudgetHandler<Test> for () {
+    fn get_budget() -> u64 {
+        unimplemented!()
+    }
+
+    fn set_budget(_new_value: u64) {
+        unimplemented!()
+    }
 }
 
-impl common::working_group::WorkingGroupIntegration<Test> for () {
+impl common::working_group::WorkingGroupAuthenticator<Test> for () {
     fn ensure_worker_origin(
         _origin: <Test as frame_system::Trait>::Origin,
         _worker_id: &<Test as common::Trait>::ActorId,

+ 70 - 24
runtime-modules/working-group/src/benchmarking.rs

@@ -18,7 +18,22 @@ use membership::Module as Membership;
 const SEED: u32 = 0;
 const MAX_BYTES: u32 = 16384;
 
-enum StakingRole {
+impl<T: Trait<I> + membership::Trait, I: Instance> common::working_group::MembershipWorkingGroupHelper<T>
+    for Module<T, I>
+{
+    fn insert_a_lead(opening_id: u32, caller_id: T::AccountId, member_id: T::MemberId) -> T::ActorId {
+        complete_opening::<T, I>(
+            StakingRole::WithStakes,
+            OpeningType::Leader,
+            opening_id,
+            None,
+            caller_id,
+            member_id
+        )
+    }
+}
+
+pub enum StakingRole {
     WithStakes,
     WithoutStakes,
 }
@@ -219,7 +234,7 @@ fn force_missed_reward<T: Trait<I>, I: Instance>() {
     WorkingGroup::<T, _>::on_initialize(curr_block_number);
 }
 
-fn insert_a_worker<T: Trait<I> + membership::Trait, I: Instance>(
+pub fn insert_a_worker<T: Trait<I> + membership::Trait, I: Instance>(
     staking_role: StakingRole,
     job_opening_type: OpeningType,
     id: u32,
@@ -235,12 +250,25 @@ where
 
     let (caller_id, member_id) = member_funded_account::<T, I>("member", id);
 
+    let worker_id = complete_opening::<T, I>(staking_role, job_opening_type, id, lead_id, &caller_id, &member_id);
+
+    (caller_id, worker_id)
+}
+
+pub fn complete_opening<T: Trait<I> + membership::Trait, I: Instance>(
+    staking_role: StakingRole,
+    job_opening_type: OpeningType,
+    id: u32,
+    lead_id: Option<T::AccountId>,
+    caller_id: &T::AccountId,
+    member_id:  &T::MemberId
+) -> WorkerId<T> {
     let (opening_id, application_id) = add_and_apply_opening::<T, I>(
         id,
         &T::Origin::from(add_worker_origin.clone()),
         &staking_role,
-        &caller_id,
-        &member_id,
+        caller_id,
+        member_id,
         &job_opening_type,
     );
 
@@ -261,7 +289,7 @@ where
 
     assert!(WorkerById::<T, I>::contains_key(worker_id));
 
-    (caller_id, worker_id)
+    worker_id
 }
 
 benchmarks_instance! {
@@ -303,13 +331,18 @@ benchmarks_instance! {
         let mut worker_id = Zero::zero();
         for id in application_account_id {
             worker_id += One::one();
-            WorkingGroup::<T, _>::leave_role(RawOrigin::Signed(id).into(), worker_id).unwrap();
+            WorkingGroup::<T, _>::leave_role(
+                    RawOrigin::Signed(id).into(),
+                    worker_id,
+                    Some(vec![0u8]),
+                ).unwrap();
         }
 
         // Worst case scenario one of the leaving workers is the lead
         WorkingGroup::<T, _>::leave_role(
             RawOrigin::Signed(lead_id).into(),
-            lead_worker_id
+            lead_worker_id,
+            Some(vec![0u8]),
         ).unwrap();
 
         for i in 1..successful_application_ids.len() {
@@ -694,11 +727,12 @@ benchmarks_instance! {
             Some(lead_id.clone())
         );
         let slashing_amount = One::one();
-        let penalty = Penalty {
-            slashing_text: vec![0u8; i.try_into().unwrap()],
-            slashing_amount,
-        };
-    }: _(RawOrigin::Signed(lead_id.clone()), worker_id, penalty)
+    }: _(
+        RawOrigin::Signed(lead_id.clone()),
+        worker_id,
+        slashing_amount,
+        Some(vec![0u8; i.try_into().unwrap()])
+    )
     verify {
         assert_last_event::<T, I>(RawEvent::StakeSlashed(worker_id, slashing_amount).into());
     }
@@ -717,11 +751,12 @@ benchmarks_instance! {
         // To be able to pay unpaid reward
         let current_budget = BalanceOf::<T>::max_value();
         WorkingGroup::<T, _>::set_budget(RawOrigin::Root.into(), current_budget).unwrap();
-        let penalty = Penalty {
-            slashing_text: vec![0u8; i.try_into().unwrap()],
-            slashing_amount: One::one(),
-        };
-    }: terminate_role(RawOrigin::Signed(lead_id.clone()), worker_id, Some(penalty))
+    }: terminate_role(
+            RawOrigin::Signed(lead_id.clone()),
+            worker_id,
+            Some(One::one()),
+            Some(vec![0u8; i.try_into().unwrap()])
+        )
     verify {
         assert!(!WorkerById::<T, I>::contains_key(worker_id), "Worker not terminated");
         assert_last_event::<T, I>(RawEvent::TerminatedWorker(worker_id).into());
@@ -735,11 +770,12 @@ benchmarks_instance! {
         let current_budget = BalanceOf::<T>::max_value();
         // To be able to pay unpaid reward
         WorkingGroup::<T, _>::set_budget(RawOrigin::Root.into(), current_budget).unwrap();
-        let penalty = Penalty {
-            slashing_text: vec![0u8; i.try_into().unwrap()],
-            slashing_amount: One::one(),
-        };
-    }: terminate_role(RawOrigin::Root, lead_worker_id, Some(penalty))
+    }: terminate_role(
+            RawOrigin::Root,
+            lead_worker_id,
+            Some(One::one()),
+            Some(vec![0u8; i.try_into().unwrap()])
+        )
     verify {
         assert!(!WorkerById::<T, I>::contains_key(lead_worker_id), "Worker not terminated");
         assert_last_event::<T, I>(RawEvent::TerminatedLeader(lead_worker_id).into());
@@ -901,6 +937,7 @@ benchmarks_instance! {
 
     // This is always worse than leave_role_immediatly
     leave_role_immediatly {
+        let i in 0 .. MAX_BYTES;
         // Worst case scenario there is a lead(this requires **always** more steps)
         // could separate into new branch to tighten weight
         // Also, workers without stake can leave immediatly
@@ -913,7 +950,11 @@ benchmarks_instance! {
             BalanceOf::<T>::max_value()
         ).unwrap();
 
-    }: leave_role(RawOrigin::Signed(caller_id), lead_worker_id)
+    }: leave_role(
+            RawOrigin::Signed(caller_id),
+            lead_worker_id,
+            Some(vec![0u8; i.try_into().unwrap()])
+        )
     verify {
         assert!(!WorkerById::<T, I>::contains_key(lead_worker_id), "Worker hasn't left");
         assert_last_event::<T, I>(RawEvent::WorkerExited(lead_worker_id).into());
@@ -923,6 +964,7 @@ benchmarks_instance! {
     // but since it's so obviously a different branch I think it's a good idea
     // to leave this branch and use tha max between these 2
     leave_role_later {
+        let i in 0 .. MAX_BYTES;
         // Workers with stake can't leave immediatly
         let (caller_id, caller_worker_id) = insert_a_worker::<T, I>(
             StakingRole::WithStakes,
@@ -930,7 +972,11 @@ benchmarks_instance! {
             0,
             None
         );
-    }: leave_role(RawOrigin::Signed(caller_id), caller_worker_id)
+    }: leave_role(
+            RawOrigin::Signed(caller_id),
+            caller_worker_id,
+            Some(vec![0u8; i.try_into().unwrap()])
+        )
     verify {
         assert_eq!(
             WorkingGroup::<T, _>::worker_by_id(caller_worker_id).started_leaving_at,

+ 53 - 17
runtime-modules/working-group/src/lib.rs

@@ -50,13 +50,13 @@ use sp_std::vec::Vec;
 pub use errors::Error;
 pub use types::{
     Application, ApplicationId, ApplyOnOpeningParameters, BalanceOf, Opening, OpeningId,
-    OpeningType, Penalty, StakeParameters, StakePolicy, Worker, WorkerId,
+    OpeningType, StakeParameters, StakePolicy, Worker, WorkerId,
 };
 use types::{ApplicationInfo, WorkerInfo};
 
 pub use checks::{ensure_worker_exists, ensure_worker_signed};
 
-use common::origin::ActorOriginValidator;
+use common::origin::MemberOriginValidator;
 use common::{MemberId, StakingAccountValidator};
 use frame_support::dispatch::DispatchResult;
 use staking_handler::StakingHandler;
@@ -108,7 +108,7 @@ pub trait Trait<I: Instance = DefaultInstance>:
     type StakingAccountValidator: common::StakingAccountValidator<Self>;
 
     /// Validates member id and origin combination
-    type MemberOriginValidator: ActorOriginValidator<Self::Origin, MemberId<Self>, Self::AccountId>;
+    type MemberOriginValidator: MemberOriginValidator<Self::Origin, MemberId<Self>, Self::AccountId>;
 
     /// Defines min unstaking period in the group.
     type MinUnstakingPeriodLimit: Get<Self::BlockNumber>;
@@ -377,7 +377,7 @@ decl_module! {
         #[weight = WeightInfoWorkingGroup::<T, I>::apply_on_opening(p.description.len().saturated_into())]
         pub fn apply_on_opening(origin, p : ApplyOnOpeningParameters<T>) {
             // Ensure the origin of a member with given id.
-            T::MemberOriginValidator::ensure_actor_origin(origin, p.member_id)?;
+            T::MemberOriginValidator::ensure_member_controller_account_origin(origin, p.member_id)?;
 
             // Ensure job opening exists.
             let opening = checks::ensure_opening_exists::<T, I>(&p.opening_id)?;
@@ -518,7 +518,7 @@ decl_module! {
             let worker = checks::ensure_worker_exists::<T, I>(&worker_id)?;
 
             // Ensure the origin of a member with given id.
-            T::MemberOriginValidator::ensure_actor_origin(origin, worker.member_id)?;
+            T::MemberOriginValidator::ensure_member_controller_account_origin(origin, worker.member_id)?;
 
             // Ensure the worker is active.
             ensure!(!worker.is_leaving(), Error::<T, I>::WorkerIsLeaving);
@@ -549,6 +549,7 @@ decl_module! {
         pub fn leave_role(
             origin,
             worker_id: WorkerId<T>,
+            _rationale: Option<Vec<u8>>
         ) {
             // Ensure there is a signer which matches role account of worker corresponding to provided id.
             let worker = checks::ensure_worker_signed::<T, I>(origin, &worker_id)?;
@@ -579,11 +580,12 @@ decl_module! {
         /// - DB:
         ///    - O(1) doesn't depend on the state or parameters
         /// # </weight>
-        #[weight = Module::<T, I>::terminate_role_weight(&penalty)]
+        #[weight = Module::<T, I>::terminate_role_weight(&_rationale)]
         pub fn terminate_role(
             origin,
             worker_id: WorkerId<T>,
-            penalty: Option<Penalty<BalanceOf<T>>>,
+            penalty: Option<BalanceOf<T>>,
+            _rationale: Option<Vec<u8>>,
         ) {
             // Ensure lead is set or it is the council terminating the leader.
             let is_sudo = checks::ensure_origin_for_worker_operation::<T,I>(origin, worker_id)?;
@@ -605,7 +607,7 @@ decl_module! {
 
             if let Some(penalty) = penalty {
                 if let Some(staking_account_id) = worker.staking_account_id.clone() {
-                    Self::slash(worker_id, &staking_account_id, Some(penalty.slashing_amount));
+                    Self::slash(worker_id, &staking_account_id, Some(penalty));
                 }
             }
 
@@ -630,8 +632,13 @@ decl_module! {
         /// - DB:
         ///    - O(1) doesn't depend on the state or parameters
         /// # </weight>
-        #[weight = WeightInfoWorkingGroup::<T, I>::slash_stake(penalty.slashing_text.len().saturated_into())]
-        pub fn slash_stake(origin, worker_id: WorkerId<T>, penalty: Penalty<BalanceOf<T>>) {
+        #[weight = Module::<T, I>::slash_stake_weight(&_rationale)]
+        pub fn slash_stake(
+            origin,
+            worker_id: WorkerId<T>,
+            penalty: BalanceOf<T>,
+            _rationale: Option<Vec<u8>>
+        ) {
             // Ensure lead is set or it is the council slashing the leader.
             checks::ensure_origin_for_worker_operation::<T,I>(origin, worker_id)?;
 
@@ -645,7 +652,7 @@ decl_module! {
             );
 
             ensure!(
-                penalty.slashing_amount != <BalanceOf<T>>::zero(),
+                penalty != <BalanceOf<T>>::zero(),
                 Error::<T, I>::StakeBalanceCannotBeZero
             );
 
@@ -654,7 +661,7 @@ decl_module! {
             //
 
             if let Some(staking_account_id) = worker.staking_account_id {
-                Self::slash(worker_id, &staking_account_id, Some(penalty.slashing_amount))
+                Self::slash(worker_id, &staking_account_id, Some(penalty))
             }
         }
 
@@ -864,7 +871,7 @@ decl_module! {
             //
 
             // Update the budget.
-            <Budget::<T, I>>::put(new_budget);
+            Self::set_working_group_budget(new_budget);
 
             // Trigger event
             Self::deposit_event(RawEvent::BudgetSet(new_budget));
@@ -1045,21 +1052,31 @@ impl<T: Trait<I>, I: Instance> Module<T, I> {
     }
 
     // Calculate weights for terminate_role
-    fn terminate_role_weight(penalty: &Option<Penalty<BalanceOf<T>>>) -> Weight {
+    fn terminate_role_weight(penalty: &Option<Vec<u8>>) -> Weight {
         WeightInfoWorkingGroup::<T, I>::terminate_role_lead(
             penalty
                 .as_ref()
-                .map(|penalty| penalty.slashing_text.len().saturated_into())
+                .map(|penalty| penalty.len().saturated_into())
                 .unwrap_or_default(),
         )
         .max(WeightInfoWorkingGroup::<T, I>::terminate_role_worker(
             penalty
                 .as_ref()
-                .map(|penalty| penalty.slashing_text.len().saturated_into())
+                .map(|penalty| penalty.len().saturated_into())
                 .unwrap_or_default(),
         ))
     }
 
+    // Calculates slash_stake weight
+    fn slash_stake_weight(rationale: &Option<Vec<u8>>) -> Weight {
+        WeightInfoWorkingGroup::<T, I>::slash_stake(
+            rationale
+                .as_ref()
+                .map(|text| text.len().saturated_into())
+                .unwrap_or_default(),
+        )
+    }
+
     // Wrapper-function over frame_system::block_number()
     fn current_block() -> T::BlockNumber {
         <frame_system::Module<T>>::block_number()
@@ -1331,6 +1348,11 @@ impl<T: Trait<I>, I: Instance> Module<T, I> {
         false
     }
 
+    // Sets the working group budget.
+    fn set_working_group_budget(new_budget: BalanceOf<T>) {
+        <Budget<T, I>>::put(new_budget);
+    }
+
     /// Returns all existing worker id list.
     pub fn get_all_worker_ids() -> Vec<WorkerId<T>> {
         <WorkerById<T, I>>::iter()
@@ -1339,7 +1361,9 @@ impl<T: Trait<I>, I: Instance> Module<T, I> {
     }
 }
 
-impl<T: Trait<I>, I: Instance> common::working_group::WorkingGroupIntegration<T> for Module<T, I> {
+impl<T: Trait<I>, I: Instance> common::working_group::WorkingGroupAuthenticator<T>
+    for Module<T, I>
+{
     fn ensure_worker_origin(origin: T::Origin, worker_id: &WorkerId<T>) -> DispatchResult {
         checks::ensure_worker_signed::<T, I>(origin, worker_id).map(|_| ())
     }
@@ -1365,3 +1389,15 @@ impl<T: Trait<I>, I: Instance> common::working_group::WorkingGroupIntegration<T>
             .unwrap_or(false)
     }
 }
+
+impl<T: Trait<I>, I: Instance> common::working_group::WorkingGroupBudgetHandler<T>
+    for Module<T, I>
+{
+    fn get_budget() -> BalanceOf<T> {
+        Self::budget()
+    }
+
+    fn set_budget(new_value: BalanceOf<T>) {
+        Self::set_working_group_budget(new_value);
+    }
+}

+ 16 - 13
runtime-modules/working-group/src/tests/fixtures.rs

@@ -9,8 +9,8 @@ use super::hiring_workflow::HiringWorkflow;
 use super::mock::{Balances, LockId, System, Test, TestEvent, TestWorkingGroup};
 use crate::types::StakeParameters;
 use crate::{
-    Application, ApplyOnOpeningParameters, DefaultInstance, Opening, OpeningType, Penalty,
-    RawEvent, StakePolicy, Worker,
+    Application, ApplyOnOpeningParameters, DefaultInstance, Opening, OpeningType, RawEvent,
+    StakePolicy, Worker,
 };
 
 pub struct EventFixture;
@@ -515,7 +515,7 @@ impl LeaveWorkerRoleFixture {
 
     pub fn call_and_assert(&self, expected_result: DispatchResult) {
         let actual_result =
-            TestWorkingGroup::leave_role(self.origin.clone().into(), self.worker_id);
+            TestWorkingGroup::leave_role(self.origin.clone().into(), self.worker_id, None);
         assert_eq!(actual_result, expected_result);
 
         if actual_result.is_ok() {
@@ -541,7 +541,8 @@ impl LeaveWorkerRoleFixture {
 pub struct TerminateWorkerRoleFixture {
     worker_id: u64,
     origin: RawOrigin<u64>,
-    penalty: Option<Penalty<u64>>,
+    penalty: Option<u64>,
+    rationale: Option<Vec<u8>>,
 }
 
 impl TerminateWorkerRoleFixture {
@@ -550,13 +551,14 @@ impl TerminateWorkerRoleFixture {
             worker_id,
             origin: RawOrigin::Signed(1),
             penalty: None,
+            rationale: None,
         }
     }
     pub fn with_origin(self, origin: RawOrigin<u64>) -> Self {
         Self { origin, ..self }
     }
 
-    pub fn with_penalty(self, penalty: Option<Penalty<u64>>) -> Self {
+    pub fn with_penalty(self, penalty: Option<u64>) -> Self {
         Self { penalty, ..self }
     }
 
@@ -565,6 +567,7 @@ impl TerminateWorkerRoleFixture {
             self.origin.clone().into(),
             self.worker_id,
             self.penalty.clone(),
+            self.rationale.clone(),
         );
         assert_eq!(actual_result, expected_result);
 
@@ -587,7 +590,8 @@ pub struct SlashWorkerStakeFixture {
     origin: RawOrigin<u64>,
     worker_id: u64,
     account_id: u64,
-    penalty: Penalty<u64>,
+    penalty: u64,
+    rationale: Option<Vec<u8>>,
 }
 
 impl SlashWorkerStakeFixture {
@@ -599,10 +603,8 @@ impl SlashWorkerStakeFixture {
         Self {
             origin: RawOrigin::Signed(lead_account_id),
             worker_id,
-            penalty: Penalty {
-                slashing_text: Vec::new(),
-                slashing_amount: 10,
-            },
+            rationale: None,
+            penalty: 10,
             account_id,
         }
     }
@@ -610,7 +612,7 @@ impl SlashWorkerStakeFixture {
         Self { origin, ..self }
     }
 
-    pub fn with_penalty(self, penalty: Penalty<u64>) -> Self {
+    pub fn with_penalty(self, penalty: u64) -> Self {
         Self { penalty, ..self }
     }
 
@@ -620,7 +622,8 @@ impl SlashWorkerStakeFixture {
         let actual_result = TestWorkingGroup::slash_stake(
             self.origin.clone().into(),
             self.worker_id,
-            self.penalty.clone(),
+            self.penalty,
+            self.rationale.clone(),
         );
 
         assert_eq!(actual_result, expected_result);
@@ -629,7 +632,7 @@ impl SlashWorkerStakeFixture {
             // stake decreased
             assert_eq!(
                 old_stake,
-                get_stake_balance(&self.account_id) + self.penalty.slashing_amount
+                get_stake_balance(&self.account_id) + self.penalty
             );
 
             let new_balance = Balances::usable_balance(&self.account_id);

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

@@ -12,6 +12,7 @@ use sp_runtime::{
 use staking_handler::LockComparator;
 
 use crate::{DefaultInstance, Module, Trait};
+use frame_support::dispatch::DispatchError;
 
 impl_outer_origin! {
     pub enum Origin for Test {}
@@ -39,6 +40,7 @@ parameter_types! {
     pub const ExistentialDeposit: u32 = 0;
     pub const DefaultMembershipPrice: u64 = 0;
     pub const DefaultInitialInvitationBalance: u64 = 100;
+    pub const InvitedMemberLockId: [u8; 8] = [2; 8];
 }
 
 // Workaround for https://github.com/rust-lang/rust/issues/26925 - remove when sorted.
@@ -100,14 +102,12 @@ impl membership::Trait for Test {
     type DefaultMembershipPrice = DefaultMembershipPrice;
     type WorkingGroup = Module<Test>;
     type DefaultInitialInvitationBalance = ();
+    type InvitedMemberStakingHandler = staking_handler::StakingManager<Self, InvitedMemberLockId>;
 }
 
 impl LockComparator<<Test as balances::Trait>::Balance> for Test {
-    fn are_locks_conflicting(
-        _new_lock: &LockIdentifier,
-        _existing_locks: &[LockIdentifier],
-    ) -> bool {
-        false
+    fn are_locks_conflicting(new_lock: &LockIdentifier, existing_locks: &[LockIdentifier]) -> bool {
+        existing_locks.to_vec().contains(new_lock)
     }
 }
 
@@ -133,8 +133,8 @@ impl Trait for Test {
 }
 
 impl common::StakingAccountValidator<Test> for () {
-    fn is_member_staking_account(_: &u64, _: &u64) -> bool {
-        true
+    fn is_member_staking_account(_: &u64, account_id: &u64) -> bool {
+        *account_id != STAKING_ACCOUNT_ID_NOT_BOUND_TO_MEMBER
     }
 }
 
@@ -212,21 +212,28 @@ impl crate::WeightInfo for () {
 
 pub const ACTOR_ORIGIN_ERROR: &'static str = "Invalid membership";
 
-impl common::origin::ActorOriginValidator<Origin, u64, u64> for () {
-    fn ensure_actor_origin(origin: Origin, member_id: u64) -> Result<u64, &'static str> {
+impl common::origin::MemberOriginValidator<Origin, u64, u64> for () {
+    fn ensure_member_controller_account_origin(
+        origin: Origin,
+        member_id: u64,
+    ) -> Result<u64, DispatchError> {
         let signed_account_id = frame_system::ensure_signed(origin)?;
 
         if member_id > 10 {
-            return Err(ACTOR_ORIGIN_ERROR);
+            return Err(DispatchError::Other(ACTOR_ORIGIN_ERROR));
         }
 
         Ok(signed_account_id)
     }
+
+    fn is_member_controller_account(_member_id: &u64, _account_id: &u64) -> bool {
+        unimplemented!()
+    }
 }
 
 pub type TestWorkingGroup = Module<Test, DefaultInstance>;
 
-pub const STAKING_ACCOUNT_ID_FOR_FAILED_VALIDITY_CHECK: u64 = 111;
+pub const STAKING_ACCOUNT_ID_NOT_BOUND_TO_MEMBER: u64 = 222;
 pub const STAKING_ACCOUNT_ID_FOR_CONFLICTING_STAKES: u64 = 333;
 pub const STAKING_ACCOUNT_ID_FOR_ZERO_STAKE: u64 = 444;
 

+ 21 - 51
runtime-modules/working-group/src/tests/mod.rs

@@ -13,12 +13,12 @@ use crate::tests::fixtures::{
 };
 use crate::tests::hiring_workflow::HiringWorkflow;
 use crate::tests::mock::{
-    STAKING_ACCOUNT_ID_FOR_CONFLICTING_STAKES, STAKING_ACCOUNT_ID_FOR_FAILED_VALIDITY_CHECK,
-    STAKING_ACCOUNT_ID_FOR_ZERO_STAKE,
+    STAKING_ACCOUNT_ID_FOR_CONFLICTING_STAKES, STAKING_ACCOUNT_ID_FOR_ZERO_STAKE,
+    STAKING_ACCOUNT_ID_NOT_BOUND_TO_MEMBER,
 };
 use crate::types::StakeParameters;
-use crate::{DefaultInstance, Error, OpeningType, Penalty, RawEvent, StakePolicy, Worker};
-use common::working_group::WorkingGroupIntegration;
+use crate::{DefaultInstance, Error, OpeningType, RawEvent, StakePolicy, Worker};
+use common::working_group::WorkingGroupAuthenticator;
 use fixtures::{
     increase_total_balance_issuance_using_account_id, AddOpeningFixture, ApplyOnOpeningFixture,
     EventFixture, FillOpeningFixture, HireLeadFixture, HireRegularWorkerFixture,
@@ -177,7 +177,7 @@ fn apply_on_opening_fails_with_bad_origin() {
         let apply_on_opening_fixture = ApplyOnOpeningFixture::default_for_opening_id(opening_id)
             .with_origin(RawOrigin::None, member_id);
 
-        apply_on_opening_fixture.call_and_assert(Err(DispatchError::Other("Bad origin")));
+        apply_on_opening_fixture.call_and_assert(Err(DispatchError::BadOrigin));
     });
 }
 
@@ -499,7 +499,7 @@ fn update_worker_role_account_fails_with_invalid_origin() {
             UpdateWorkerRoleAccountFixture::default_with_ids(worker_id, 1)
                 .with_origin(RawOrigin::None);
 
-        update_worker_account_fixture.call_and_assert(Err(DispatchError::Other("Bad origin")));
+        update_worker_account_fixture.call_and_assert(Err(DispatchError::BadOrigin));
     });
 }
 
@@ -913,7 +913,6 @@ fn apply_on_opening_fails_stake_amount_check() {
     });
 }
 
-#[ignore] // unlock after implementing members staking accounts
 #[test]
 fn apply_on_opening_fails_invalid_staking_check() {
     build_test_externalities().execute_with(|| {
@@ -925,13 +924,13 @@ fn apply_on_opening_fails_invalid_staking_check() {
 
         increase_total_balance_issuance_using_account_id(account_id, total_balance);
         increase_total_balance_issuance_using_account_id(
-            STAKING_ACCOUNT_ID_FOR_FAILED_VALIDITY_CHECK,
+            STAKING_ACCOUNT_ID_NOT_BOUND_TO_MEMBER,
             total_balance,
         );
 
         let stake_parameters = StakeParameters {
             stake,
-            staking_account_id: STAKING_ACCOUNT_ID_FOR_FAILED_VALIDITY_CHECK,
+            staking_account_id: STAKING_ACCOUNT_ID_NOT_BOUND_TO_MEMBER,
         };
 
         let add_opening_fixture =
@@ -950,7 +949,6 @@ fn apply_on_opening_fails_invalid_staking_check() {
     });
 }
 
-#[ignore] // unlock after implementing conflicting stake
 #[test]
 fn apply_on_opening_fails_with_conflicting_stakes() {
     build_test_externalities().execute_with(|| {
@@ -978,6 +976,10 @@ fn apply_on_opening_fails_with_conflicting_stakes() {
             }));
         let opening_id = add_opening_fixture.call().unwrap();
 
+        let apply_on_opening_fixture = ApplyOnOpeningFixture::default_for_opening_id(opening_id)
+            .with_stake_parameters(Some(stake_parameters.clone()));
+        apply_on_opening_fixture.call_and_assert(Ok(()));
+
         let apply_on_opening_fixture = ApplyOnOpeningFixture::default_for_opening_id(opening_id)
             .with_stake_parameters(Some(stake_parameters));
 
@@ -1134,11 +1136,6 @@ fn terminate_worker_with_slashing_succeeds() {
             leaving_unstaking_period: 10,
         });
 
-        let penalty = Penalty {
-            slashing_amount: stake,
-            slashing_text: Vec::new(),
-        };
-
         increase_total_balance_issuance_using_account_id(account_id, total_balance);
 
         let worker_id = HireRegularWorkerFixture::default()
@@ -1148,8 +1145,7 @@ fn terminate_worker_with_slashing_succeeds() {
         assert_eq!(Balances::usable_balance(&account_id), total_balance - stake);
 
         let terminate_worker_role_fixture =
-            TerminateWorkerRoleFixture::default_for_worker_id(worker_id)
-                .with_penalty(Some(penalty));
+            TerminateWorkerRoleFixture::default_for_worker_id(worker_id).with_penalty(Some(stake));
 
         terminate_worker_role_fixture.call_and_assert(Ok(()));
 
@@ -1164,11 +1160,6 @@ fn terminate_worker_with_slashing_fails_with_no_staking_account() {
         let total_balance = 300;
         let stake = 200;
 
-        let penalty = Penalty {
-            slashing_amount: stake,
-            slashing_text: Vec::new(),
-        };
-
         increase_total_balance_issuance_using_account_id(account_id, total_balance);
 
         let worker_id = HiringWorkflow::default()
@@ -1177,8 +1168,7 @@ fn terminate_worker_with_slashing_fails_with_no_staking_account() {
             .unwrap();
 
         let terminate_worker_role_fixture =
-            TerminateWorkerRoleFixture::default_for_worker_id(worker_id)
-                .with_penalty(Some(penalty));
+            TerminateWorkerRoleFixture::default_for_worker_id(worker_id).with_penalty(Some(stake));
 
         terminate_worker_role_fixture.call_and_assert(Err(
             Error::<Test, DefaultInstance>::CannotChangeStakeWithoutStakingAccount.into(),
@@ -1199,18 +1189,13 @@ fn slash_worker_stake_succeeds() {
         let account_id = 1;
         let total_balance = 300;
         let stake = 200;
-        let slash_stake = 100;
+        let penalty = 100;
 
         let stake_policy = Some(StakePolicy {
             stake_amount: stake,
             leaving_unstaking_period: 10,
         });
 
-        let penalty = Penalty {
-            slashing_amount: slash_stake,
-            slashing_text: Vec::new(),
-        };
-
         increase_total_balance_issuance_using_account_id(account_id, total_balance);
 
         let worker_id = HireRegularWorkerFixture::default()
@@ -1222,7 +1207,7 @@ fn slash_worker_stake_succeeds() {
 
         slash_stake_fixture.call_and_assert(Ok(()));
 
-        EventFixture::assert_last_crate_event(RawEvent::StakeSlashed(worker_id, slash_stake));
+        EventFixture::assert_last_crate_event(RawEvent::StakeSlashed(worker_id, penalty));
     });
 }
 
@@ -1231,12 +1216,7 @@ fn slash_worker_stake_fails_with_no_staking_account() {
     build_test_externalities().execute_with(|| {
         let account_id = 1;
         let total_balance = 300;
-        let slash_stake = 100;
-
-        let penalty = Penalty {
-            slashing_amount: slash_stake,
-            slashing_text: Vec::new(),
-        };
+        let penalty = 100;
 
         increase_total_balance_issuance_using_account_id(account_id, total_balance);
 
@@ -1266,18 +1246,13 @@ fn slash_leader_stake_succeeds() {
 
         let account_id = 1;
         let total_balance = 300;
-        let stake = 200;
+        let penalty = 200;
 
         let stake_policy = Some(StakePolicy {
-            stake_amount: stake,
+            stake_amount: penalty,
             leaving_unstaking_period: 10,
         });
 
-        let penalty = Penalty {
-            slashing_amount: stake,
-            slashing_text: Vec::new(),
-        };
-
         increase_total_balance_issuance_using_account_id(account_id, total_balance);
         let leader_worker_id = HireLeadFixture::default()
             .with_stake_policy(stake_policy)
@@ -1289,7 +1264,7 @@ fn slash_leader_stake_succeeds() {
 
         slash_stake_fixture.call_and_assert(Ok(()));
 
-        EventFixture::assert_last_crate_event(RawEvent::StakeSlashed(leader_worker_id, stake));
+        EventFixture::assert_last_crate_event(RawEvent::StakeSlashed(leader_worker_id, penalty));
     });
 }
 
@@ -1330,11 +1305,6 @@ fn slash_worker_stake_fails_with_zero_balance() {
             leaving_unstaking_period: 10,
         });
 
-        let penalty = Penalty {
-            slashing_amount: 0,
-            slashing_text: Vec::new(),
-        };
-
         increase_total_balance_issuance_using_account_id(account_id, total_balance);
 
         let worker_id = HireRegularWorkerFixture::default()
@@ -1342,7 +1312,7 @@ fn slash_worker_stake_fails_with_zero_balance() {
             .hire();
 
         let slash_stake_fixture =
-            SlashWorkerStakeFixture::default_for_worker_id(worker_id).with_penalty(penalty);
+            SlashWorkerStakeFixture::default_for_worker_id(worker_id).with_penalty(0);
 
         slash_stake_fixture.call_and_assert(Err(
             Error::<Test, DefaultInstance>::StakeBalanceCannotBeZero.into(),

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

@@ -249,14 +249,3 @@ pub type ApplyOnOpeningParameters<T> = ApplyOnOpeningParams<
     <T as frame_system::Trait>::AccountId,
     BalanceOf<T>,
 >;
-
-/// Contains information for the slashing.
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-#[derive(Encode, Decode, Debug, Clone, Default, PartialEq, Eq)]
-pub struct Penalty<Balance> {
-    /// Slashing rationale
-    pub slashing_text: Vec<u8>,
-
-    /// Slashing balance
-    pub slashing_amount: Balance,
-}

+ 42 - 55
runtime/src/constants.rs

@@ -47,84 +47,71 @@ parameter_types! {
     pub const ContentWorkingGroupLockId: LockIdentifier = [7; 8];
     pub const ForumGroupLockId: LockIdentifier = [8; 8];
     pub const MembershipWorkingGroupLockId: LockIdentifier = [9; 8];
+    pub const InvitedMemberLockId: LockIdentifier = [10; 8];
 }
 
+// Staking lock ID used by nomination and validation in the staking pallet.
+// This is a copye because the current Substrate staking lock ID is not exported.
+const STAKING_LOCK_ID: LockIdentifier = *b"staking ";
+
 lazy_static! {
     // pairs of allowed lock combinations
     pub static ref ALLOWED_LOCK_COMBINATIONS: BTreeSet<(LockIdentifier, LockIdentifier)> = [
         // format: `(lock_id, [all_compatible_lock_ids, ...])`
-        (ForumGroupLockId::get(), [
-            ContentWorkingGroupLockId::get(),
-            StorageWorkingGroupLockId::get(),
-            ProposalsLockId::get(),
-            CandidacyLockId::get(),
-            CouncilorLockId::get(),
+        (InvitedMemberLockId::get(), [
             VotingLockId::get(),
-            MembershipWorkingGroupLockId::get(),
-        ]),
-        (ContentWorkingGroupLockId::get(), [
-            ForumGroupLockId::get(),
-            StorageWorkingGroupLockId::get(),
-            ProposalsLockId::get(),
             CandidacyLockId::get(),
             CouncilorLockId::get(),
-            VotingLockId::get(),
-            MembershipWorkingGroupLockId::get(),
-        ]),
-        (StorageWorkingGroupLockId::get(), [
-            ForumGroupLockId::get(),
-            ContentWorkingGroupLockId::get(),
+            STAKING_LOCK_ID,
             ProposalsLockId::get(),
-            CandidacyLockId::get(),
-            CouncilorLockId::get(),
-            VotingLockId::get(),
-            MembershipWorkingGroupLockId::get(),
-        ]),
-        (ProposalsLockId::get(), [
             ForumGroupLockId::get(),
             ContentWorkingGroupLockId::get(),
             StorageWorkingGroupLockId::get(),
+            MembershipWorkingGroupLockId::get(),
+        ].to_vec()),
+        (VotingLockId::get(), [
+            InvitedMemberLockId::get(),
             CandidacyLockId::get(),
             CouncilorLockId::get(),
-            VotingLockId::get(),
-            MembershipWorkingGroupLockId::get(),
-        ]),
-        (CandidacyLockId::get(), [
+            STAKING_LOCK_ID,
+            ProposalsLockId::get(),
             ForumGroupLockId::get(),
             ContentWorkingGroupLockId::get(),
             StorageWorkingGroupLockId::get(),
-            ProposalsLockId::get(),
-            CouncilorLockId::get(),
-            VotingLockId::get(),
             MembershipWorkingGroupLockId::get(),
-        ]),
+        ].to_vec()),
+        (CandidacyLockId::get(), [
+            InvitedMemberLockId::get(),
+            VotingLockId::get(),
+            CouncilorLockId::get(),
+        ].to_vec()),
         (CouncilorLockId::get(), [
-            ForumGroupLockId::get(),
-            ContentWorkingGroupLockId::get(),
-            StorageWorkingGroupLockId::get(),
-            ProposalsLockId::get(),
-            CandidacyLockId::get(),
+            InvitedMemberLockId::get(),
             VotingLockId::get(),
-            MembershipWorkingGroupLockId::get(),
-        ]),
-        (VotingLockId::get(), [
-            ForumGroupLockId::get(),
-            ContentWorkingGroupLockId::get(),
-            StorageWorkingGroupLockId::get(),
-            ProposalsLockId::get(),
             CandidacyLockId::get(),
-            CouncilorLockId::get(),
-            MembershipWorkingGroupLockId::get(),
-        ]),
+        ].to_vec()),
+        // Proposals
+        (ProposalsLockId::get(), [
+            InvitedMemberLockId::get(),
+            VotingLockId::get(),
+        ].to_vec()),
+        // Working Groups
+        (ForumGroupLockId::get(), [
+            InvitedMemberLockId::get(),
+            VotingLockId::get(),
+        ].to_vec()),
+        (ContentWorkingGroupLockId::get(), [
+            InvitedMemberLockId::get(),
+            VotingLockId::get(),
+        ].to_vec()),
+        (StorageWorkingGroupLockId::get(), [
+            InvitedMemberLockId::get(),
+            VotingLockId::get(),
+        ].to_vec()),
         (MembershipWorkingGroupLockId::get(), [
-            ForumGroupLockId::get(),
-            ContentWorkingGroupLockId::get(),
-            StorageWorkingGroupLockId::get(),
-            ProposalsLockId::get(),
-            CandidacyLockId::get(),
-            CouncilorLockId::get(),
-            MembershipWorkingGroupLockId::get(),
-        ]),
+            InvitedMemberLockId::get(),
+            VotingLockId::get(),
+        ].to_vec()),
     ]
     .iter()
     .fold(BTreeSet::new(), |mut acc, item| {

+ 21 - 0
runtime/src/integration/proposals/council_manager.rs

@@ -0,0 +1,21 @@
+#![warn(missing_docs)]
+
+use sp_runtime::SaturatedConversion;
+use sp_std::marker::PhantomData;
+
+use proposals_engine::VotersParameters;
+
+/// Handles work with the council.
+/// Provides implementations for MemberOriginValidator and VotersParameters.
+pub struct CouncilManager<T> {
+    marker: PhantomData<T>,
+}
+
+impl<T: council::Trait> VotersParameters for CouncilManager<T> {
+    /// Implement total_voters_count() as council size
+    fn total_voters_count() -> u32 {
+        council::Module::<T>::council_members()
+            .len()
+            .saturated_into()
+    }
+}

+ 0 - 147
runtime/src/integration/proposals/council_origin_validator.rs

@@ -1,147 +0,0 @@
-#![warn(missing_docs)]
-
-use sp_runtime::SaturatedConversion;
-use sp_std::marker::PhantomData;
-
-use common::origin::ActorOriginValidator;
-use common::MemberId;
-use proposals_engine::VotersParameters;
-
-use super::MembershipOriginValidator;
-
-/// Handles work with the council.
-/// Provides implementations for ActorOriginValidator and VotersParameters.
-pub struct CouncilManager<T> {
-    marker: PhantomData<T>,
-}
-
-impl<T: council::Trait + membership::Trait>
-    ActorOriginValidator<
-        <T as frame_system::Trait>::Origin,
-        MemberId<T>,
-        <T as frame_system::Trait>::AccountId,
-    > for CouncilManager<T>
-{
-    /// Check for valid combination of origin and actor_id. Actor_id should be valid member_id of
-    /// the membership module
-    fn ensure_actor_origin(
-        origin: <T as frame_system::Trait>::Origin,
-        actor_id: MemberId<T>,
-    ) -> Result<<T as frame_system::Trait>::AccountId, &'static str> {
-        let account_id = <MembershipOriginValidator<T>>::ensure_actor_origin(origin, actor_id)?;
-
-        if council::Module::<T>::council_members()
-            .iter()
-            .any(|council_member| council_member.member_id() == &actor_id)
-        {
-            Ok(account_id)
-        } else {
-            Err("Council validation failed: account id doesn't belong to a council member")
-        }
-    }
-}
-
-impl<T: council::Trait> VotersParameters for CouncilManager<T> {
-    /// Implement total_voters_count() as council size
-    fn total_voters_count() -> u32 {
-        council::Module::<T>::council_members()
-            .len()
-            .saturated_into()
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::CouncilManager;
-    use crate::Runtime;
-    use common::origin::ActorOriginValidator;
-    use frame_system::RawOrigin;
-    use proposals_engine::VotersParameters;
-    use sp_runtime::AccountId32;
-
-    use crate::tests::elect_council;
-    use crate::tests::{initial_test_ext, insert_member};
-
-    #[test]
-    fn council_origin_validator_fails_with_unregistered_member() {
-        initial_test_ext().execute_with(|| {
-            let origin = RawOrigin::Signed(AccountId32::default());
-            let member_id = 1;
-            let error = "Membership validation failed: cannot find a profile for a member";
-
-            let validation_result =
-                CouncilManager::<Runtime>::ensure_actor_origin(origin.into(), member_id);
-
-            assert_eq!(validation_result, Err(error));
-        });
-    }
-
-    #[test]
-    fn council_origin_validator_succeeds() {
-        initial_test_ext().execute_with(|| {
-            let councilor1 = AccountId32::default();
-            let councilor2: [u8; 32] = [1; 32];
-            let councilor3: [u8; 32] = [2; 32];
-
-            elect_council(
-                vec![councilor1.clone(), councilor2.into(), councilor3.into()],
-                0,
-            );
-
-            let origin = RawOrigin::Signed(councilor1.clone());
-
-            let validation_result =
-                CouncilManager::<Runtime>::ensure_actor_origin(origin.into(), 0);
-
-            assert_eq!(validation_result, Ok(councilor1));
-        });
-    }
-
-    #[test]
-    fn council_origin_validator_fails_with_incompatible_account_id_and_member_id() {
-        initial_test_ext().execute_with(|| {
-            let account_id = AccountId32::default();
-            let error =
-                "Membership validation failed: given account doesn't match with profile accounts";
-            insert_member(account_id);
-            let member_id = 0; // newly created member_id
-
-            let invalid_account_id: [u8; 32] = [2; 32];
-            let validation_result = CouncilManager::<Runtime>::ensure_actor_origin(
-                RawOrigin::Signed(invalid_account_id.into()).into(),
-                member_id,
-            );
-
-            assert_eq!(validation_result, Err(error));
-        });
-    }
-
-    #[test]
-    fn council_origin_validator_fails_with_not_council_account_id() {
-        initial_test_ext().execute_with(|| {
-            let account_id = AccountId32::default();
-            let origin = RawOrigin::Signed(account_id.clone());
-            let error = "Council validation failed: account id doesn't belong to a council member";
-            insert_member(account_id);
-            let member_id = 0; // newly created member_id
-
-            let validation_result =
-                CouncilManager::<Runtime>::ensure_actor_origin(origin.into(), member_id);
-
-            assert_eq!(validation_result, Err(error));
-        });
-    }
-
-    #[test]
-    fn council_size_calculation_aka_total_voters_count_succeeds() {
-        initial_test_ext().execute_with(|| {
-            // Max council size is 3
-            let councilor1 = AccountId32::default();
-            let councilor2: [u8; 32] = [1; 32];
-            let councilor3: [u8; 32] = [2; 32];
-            elect_council(vec![councilor1, councilor2.into(), councilor3.into()], 0);
-
-            assert_eq!(CouncilManager::<Runtime>::total_voters_count(), 3)
-        });
-    }
-}

+ 0 - 104
runtime/src/integration/proposals/membership_origin_validator.rs

@@ -1,104 +0,0 @@
-#![warn(missing_docs)]
-
-use sp_std::marker::PhantomData;
-
-use common::origin::ActorOriginValidator;
-use common::MemberId;
-use frame_system::ensure_signed;
-
-/// Default membership actor origin validator.
-pub struct MembershipOriginValidator<T> {
-    marker: PhantomData<T>,
-}
-
-impl<T: membership::Trait>
-    ActorOriginValidator<
-        <T as frame_system::Trait>::Origin,
-        MemberId<T>,
-        <T as frame_system::Trait>::AccountId,
-    > for MembershipOriginValidator<T>
-{
-    /// Check for valid combination of origin and actor_id. Actor_id should be valid member_id of
-    /// the membership module
-    fn ensure_actor_origin(
-        origin: <T as frame_system::Trait>::Origin,
-        actor_id: MemberId<T>,
-    ) -> Result<<T as frame_system::Trait>::AccountId, &'static str> {
-        // check valid signed account_id
-        let account_id = ensure_signed(origin)?;
-
-        // check whether actor_id belongs to the registered member
-        let profile_result = <membership::Module<T>>::ensure_membership(actor_id);
-
-        if let Ok(profile) = profile_result {
-            // whether the account_id belongs to the actor
-            if profile.controller_account == account_id {
-                return Ok(account_id);
-            } else {
-                return Err("Membership validation failed: given account doesn't match with profile accounts");
-            }
-        }
-
-        Err("Membership validation failed: cannot find a profile for a member")
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::MembershipOriginValidator;
-    use crate::Runtime;
-    use common::origin::ActorOriginValidator;
-    use frame_system::RawOrigin;
-    use sp_runtime::AccountId32;
-
-    use crate::tests::{initial_test_ext, insert_member};
-
-    #[test]
-    fn membership_origin_validator_fails_with_unregistered_member() {
-        initial_test_ext().execute_with(|| {
-            let origin = RawOrigin::Signed(AccountId32::default());
-            let member_id = 1;
-            let error = "Membership validation failed: cannot find a profile for a member";
-
-            let validation_result =
-                MembershipOriginValidator::<Runtime>::ensure_actor_origin(origin.into(), member_id);
-
-            assert_eq!(validation_result, Err(error));
-        });
-    }
-
-    #[test]
-    fn membership_origin_validator_succeeds() {
-        initial_test_ext().execute_with(|| {
-            let account_id = AccountId32::default();
-            let origin = RawOrigin::Signed(account_id.clone());
-            insert_member(account_id.clone());
-            let member_id = 0; // newly created member_id
-
-            let validation_result =
-                MembershipOriginValidator::<Runtime>::ensure_actor_origin(origin.into(), member_id);
-
-            assert_eq!(validation_result, Ok(account_id));
-        });
-    }
-
-    #[test]
-    fn membership_origin_validator_fails_with_incompatible_account_id_and_member_id() {
-        initial_test_ext().execute_with(|| {
-            let account_id = AccountId32::default();
-            let error =
-                "Membership validation failed: given account doesn't match with profile accounts";
-
-            insert_member(account_id.clone());
-            let member_id = 0; // newly created member_id
-
-            let invalid_account_id: [u8; 32] = [2; 32];
-            let validation_result = MembershipOriginValidator::<Runtime>::ensure_actor_origin(
-                RawOrigin::Signed(invalid_account_id.into()).into(),
-                member_id,
-            );
-
-            assert_eq!(validation_result, Err(error));
-        });
-    }
-}

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

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

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

@@ -151,9 +151,9 @@ where
     // Generic call constructor for the working group 'slash stake'.
     fn create_slash_stake_call(
         worker_id: working_group::WorkerId<T>,
-        penalty: working_group::Penalty<working_group::BalanceOf<T>>,
+        penalty: working_group::BalanceOf<T>,
     ) -> working_group::Call<T, I> {
-        working_group::Call::<T, I>::slash_stake(worker_id, penalty)
+        working_group::Call::<T, I>::slash_stake(worker_id, penalty, None)
     }
 
     // Generic call constructor for the working group 'update reward amount'.
@@ -174,6 +174,7 @@ where
         working_group::Call::<T, I>::terminate_role(
             terminate_role_params.worker_id,
             terminate_role_params.penalty,
+            None, // The rationale is given in the proposal description
         )
     }
 

+ 32 - 56
runtime/src/lib.rs

@@ -53,10 +53,10 @@ pub use primitives::*;
 pub use proposals_configuration::*;
 pub use runtime_api::*;
 
-use integration::proposals::{CouncilManager, ExtrinsicProposalEncoder, MembershipOriginValidator};
+use integration::proposals::{CouncilManager, ExtrinsicProposalEncoder};
 
 use council::ReferendumConnection;
-use referendum::{Balance as BalanceReferendum, CastVote, OptionResult};
+use referendum::{CastVote, OptionResult};
 use staking_handler::{LockComparator, StakingManager};
 use storage::data_object_storage_registry;
 
@@ -69,6 +69,7 @@ pub use content_directory::{
 pub use council;
 pub use forum;
 pub use membership;
+
 #[cfg(any(feature = "std", test))]
 pub use pallet_balances::Call as BalancesCall;
 pub use pallet_staking::StakerStatus;
@@ -355,7 +356,7 @@ parameter_types! {
 impl pallet_staking::Trait for Runtime {
     type Currency = Balances;
     type UnixTime = Timestamp;
-    type CurrencyToVote = common::currency::CurrencyToVoteHandler;
+    type CurrencyToVote = CurrencyToVoteHandler;
     type RewardRemainder = (); // Could be Treasury.
     type Event = Event;
     type Slash = (); // Where to send the slashed funds. Could be Treasury.
@@ -436,33 +437,24 @@ impl content_directory::Trait for Runtime {
     type Nonce = u64;
     type ClassId = u64;
     type EntityId = u64;
+    type CuratorGroupId = u64;
     type PropertyNameLengthConstraint = PropertyNameLengthConstraint;
     type PropertyDescriptionLengthConstraint = PropertyDescriptionLengthConstraint;
     type ClassNameLengthConstraint = ClassNameLengthConstraint;
     type ClassDescriptionLengthConstraint = ClassDescriptionLengthConstraint;
     type MaxNumberOfClasses = MaxNumberOfClasses;
     type MaxNumberOfMaintainersPerClass = MaxNumberOfMaintainersPerClass;
+    type MaxNumberOfCuratorsPerGroup = MaxNumberOfCuratorsPerGroup;
     type MaxNumberOfSchemasPerClass = MaxNumberOfSchemasPerClass;
     type MaxNumberOfPropertiesPerSchema = MaxNumberOfPropertiesPerSchema;
-    type MaxNumberOfEntitiesPerClass = MaxNumberOfEntitiesPerClass;
-    type MaxNumberOfCuratorsPerGroup = MaxNumberOfCuratorsPerGroup;
     type MaxNumberOfOperationsDuringAtomicBatching = MaxNumberOfOperationsDuringAtomicBatching;
     type VecMaxLengthConstraint = VecMaxLengthConstraint;
     type TextMaxLengthConstraint = TextMaxLengthConstraint;
     type HashedTextMaxLengthConstraint = HashedTextMaxLengthConstraint;
+    type MaxNumberOfEntitiesPerClass = MaxNumberOfEntitiesPerClass;
     type IndividualEntitiesCreationLimit = IndividualEntitiesCreationLimit;
     type WorkingGroup = ContentDirectoryWorkingGroup;
-}
-
-impl content_directory::ActorAuthenticator for Runtime {
-    type CuratorGroupId = u64;
-
-    fn is_member(member_id: &Self::MemberId, account_id: &AccountId) -> bool {
-        membership::Module::<Runtime>::ensure_is_controller_account_for_member(
-            member_id, account_id,
-        )
-        .is_ok()
-    }
+    type MemberOriginValidator = Members;
 }
 
 // The referendum instance alias.
@@ -487,6 +479,7 @@ parameter_types! {
     pub const ElectedMemberRewardPeriod: BlockNumber = 10;
     pub const BudgetRefillAmount: u64 = 1000;
     pub const BudgetRefillPeriod: BlockNumber = 1000;
+    pub const MaxWinnerTargetCount: u64 = 10;
 }
 
 impl referendum::Trait<ReferendumInstance> for Runtime {
@@ -494,13 +487,12 @@ impl referendum::Trait<ReferendumInstance> for Runtime {
 
     type MaxSaltLength = MaxSaltLength;
 
-    type Currency = pallet_balances::Module<Self>;
-    type LockId = VotingLockId;
+    type StakingHandler = staking_handler::StakingManager<Self, VotingLockId>;
 
     type ManagerOrigin =
         EnsureOneOf<Self::AccountId, EnsureSigned<Self::AccountId>, EnsureRoot<Self::AccountId>>;
 
-    type VotePower = BalanceReferendum<Self, ReferendumInstance>;
+    type VotePower = Balance;
 
     type VoteStageDuration = VoteStageDuration;
     type RevealStageDuration = RevealStageDuration;
@@ -508,17 +500,16 @@ impl referendum::Trait<ReferendumInstance> for Runtime {
     type MinimumStake = MinimumVotingStake;
 
     type WeightInfo = weights::referendum::WeightInfo;
+    type MaxWinnerTargetCount = MaxWinnerTargetCount;
 
     fn calculate_vote_power(
         _account_id: &<Self as frame_system::Trait>::AccountId,
-        stake: &BalanceReferendum<Self, ReferendumInstance>,
+        stake: &Balance,
     ) -> Self::VotePower {
         *stake
     }
 
-    fn can_unlock_vote_stake(
-        vote: &CastVote<Self::Hash, BalanceReferendum<Self, ReferendumInstance>, Self::MemberId>,
-    ) -> bool {
+    fn can_unlock_vote_stake(vote: &CastVote<Self::Hash, Balance, Self::MemberId>) -> bool {
         <CouncilModule as ReferendumConnection<Runtime>>::can_unlock_vote_stake(vote).is_ok()
     }
 
@@ -572,21 +563,12 @@ impl council::Trait for Runtime {
 
     type WeightInfo = weights::council::WeightInfo;
 
-    fn is_council_member_account(
-        membership_id: &Self::MemberId,
-        account_id: &<Self as frame_system::Trait>::AccountId,
-    ) -> bool {
-        membership::Module::<Runtime>::ensure_is_controller_account_for_member(
-            membership_id,
-            account_id,
-        )
-        .is_ok()
-    }
-
     fn new_council_elected(_elected_members: &[council::CouncilMemberOf<Self>]) {
         <proposals_engine::Module<Runtime>>::reject_active_proposals();
         <proposals_engine::Module<Runtime>>::reactivate_pending_constitutionality_proposals();
     }
+
+    type MemberOriginValidator = Members;
 }
 
 impl memo::Trait for Runtime {
@@ -609,7 +591,7 @@ impl storage::data_directory::Trait for Runtime {
     type ContentId = ContentId;
     type StorageProviderHelper = integration::storage::StorageProviderHelper;
     type IsActiveDataObjectType = DataObjectTypeRegistry;
-    type MemberOriginValidator = MembershipOriginValidator<Self>;
+    type MemberOriginValidator = Members;
     type MaxObjectsPerInjection = MaxObjectsPerInjection;
 }
 
@@ -627,8 +609,9 @@ impl common::Trait for Runtime {
 impl membership::Trait for Runtime {
     type Event = Event;
     type DefaultMembershipPrice = DefaultMembershipPrice;
-    type WorkingGroup = MembershipWorkingGroup;
     type DefaultInitialInvitationBalance = DefaultInitialInvitationBalance;
+    type InvitedMemberStakingHandler = InvitedMemberStakingManager;
+    type WorkingGroup = MembershipWorkingGroup;
 }
 
 parameter_types! {
@@ -657,7 +640,6 @@ impl forum::Trait for Runtime {
     type Event = Event;
     type ThreadId = ThreadId;
     type PostId = PostId;
-    type ForumUserId = ForumUserId;
     type CategoryId = u64;
     type PostReactionId = u64;
     type MaxCategoryDepth = MaxCategoryDepth;
@@ -665,19 +647,12 @@ impl forum::Trait for Runtime {
     type MapLimits = MapLimits;
     type WeightInfo = weights::forum::WeightInfo;
 
-    fn is_forum_member(_account_id: &Self::AccountId, _forum_user_id: &Self::ForumUserId) -> bool {
-        membership::Module::<Runtime>::ensure_is_controller_account_for_member(
-            _forum_user_id,
-            _account_id,
-        )
-        .is_ok()
-    }
-
     fn calculate_hash(text: &[u8]) -> Self::Hash {
         Self::Hashing::hash(text)
     }
 
     type WorkingGroup = ForumWorkingGroup;
+    type MemberOriginValidator = Members;
 }
 
 impl LockComparator<<Runtime as pallet_balances::Trait>::Balance> for Runtime {
@@ -718,13 +693,15 @@ pub type StorageWorkingGroupStakingManager =
     staking_handler::StakingManager<Runtime, StorageWorkingGroupLockId>;
 pub type MembershipWorkingGroupStakingManager =
     staking_handler::StakingManager<Runtime, MembershipWorkingGroupLockId>;
+pub type InvitedMemberStakingManager =
+    staking_handler::StakingManager<Runtime, InvitedMemberLockId>;
 
 impl working_group::Trait<ForumWorkingGroupInstance> for Runtime {
     type Event = Event;
     type MaxWorkerNumberLimit = MaxWorkerNumberLimit;
     type StakingHandler = ForumWorkingGroupStakingManager;
     type StakingAccountValidator = Members;
-    type MemberOriginValidator = MembershipOriginValidator<Self>;
+    type MemberOriginValidator = Members;
     type MinUnstakingPeriodLimit = MinUnstakingPeriodLimit;
     type RewardPeriod = ForumWorkingGroupRewardPeriod;
     type WeightInfo = weights::working_group::WeightInfo;
@@ -735,7 +712,7 @@ impl working_group::Trait<StorageWorkingGroupInstance> for Runtime {
     type MaxWorkerNumberLimit = MaxWorkerNumberLimit;
     type StakingHandler = StorageWorkingGroupStakingManager;
     type StakingAccountValidator = Members;
-    type MemberOriginValidator = MembershipOriginValidator<Self>;
+    type MemberOriginValidator = Members;
     type MinUnstakingPeriodLimit = MinUnstakingPeriodLimit;
     type RewardPeriod = StorageWorkingGroupRewardPeriod;
     type WeightInfo = weights::working_group::WeightInfo;
@@ -746,7 +723,7 @@ impl working_group::Trait<ContentDirectoryWorkingGroupInstance> for Runtime {
     type MaxWorkerNumberLimit = MaxWorkerNumberLimit;
     type StakingHandler = ContentDirectoryWorkingGroupStakingManager;
     type StakingAccountValidator = Members;
-    type MemberOriginValidator = MembershipOriginValidator<Self>;
+    type MemberOriginValidator = Members;
     type MinUnstakingPeriodLimit = MinUnstakingPeriodLimit;
     type RewardPeriod = ContentWorkingGroupRewardPeriod;
     type WeightInfo = weights::working_group::WeightInfo;
@@ -757,7 +734,7 @@ impl working_group::Trait<MembershipWorkingGroupInstance> for Runtime {
     type MaxWorkerNumberLimit = MaxWorkerNumberLimit;
     type StakingHandler = MembershipWorkingGroupStakingManager;
     type StakingAccountValidator = Members;
-    type MemberOriginValidator = MembershipOriginValidator<Self>;
+    type MemberOriginValidator = Members;
     type MinUnstakingPeriodLimit = MinUnstakingPeriodLimit;
     type RewardPeriod = MembershipRewardPeriod;
     type WeightInfo = weights::working_group::WeightInfo;
@@ -777,8 +754,8 @@ parameter_types! {
 
 impl proposals_engine::Trait for Runtime {
     type Event = Event;
-    type ProposerOriginValidator = MembershipOriginValidator<Self>;
-    type VoterOriginValidator = CouncilManager<Self>;
+    type ProposerOriginValidator = Members;
+    type CouncilOriginValidator = Council;
     type TotalVotersCounter = CouncilManager<Self>;
     type ProposalId = u32;
     type StakingHandler = staking_handler::StakingManager<Self, ProposalsLockId>;
@@ -804,8 +781,8 @@ parameter_types! {
 
 impl proposals_discussion::Trait for Runtime {
     type Event = Event;
-    type AuthorOriginValidator = MembershipOriginValidator<Self>;
-    type CouncilOriginValidator = CouncilManager<Self>;
+    type AuthorOriginValidator = Members;
+    type CouncilOriginValidator = Council;
     type ThreadId = ThreadId;
     type PostId = PostId;
     type MaxWhiteListSize = MaxWhiteListSize;
@@ -817,7 +794,7 @@ parameter_types! {
 }
 
 impl proposals_codex::Trait for Runtime {
-    type MembershipOriginValidator = MembershipOriginValidator<Self>;
+    type MembershipOriginValidator = Members;
     type ProposalEncoder = ExtrinsicProposalEncoder;
     type SetValidatorCountProposalParameters = SetValidatorCountProposalParameters;
     type RuntimeUpgradeProposalParameters = RuntimeUpgradeProposalParameters;
@@ -850,8 +827,7 @@ parameter_types! {
     pub const SurchargeReward: Balance = 0; // no reward
 }
 
-/// Forum identifiers for user, moderator and category
-pub type ForumUserId = u64;
+/// Forum identifier for category
 pub type CategoryId = u64;
 
 /// Opaque types. These are used by the CLI to instantiate machinery that don't need to know

+ 45 - 23
runtime/src/runtime_api.rs

@@ -1,12 +1,12 @@
 use frame_support::inherent::{CheckInherentsResult, InherentData};
-use frame_support::traits::{KeyOwnerProofSystem, Randomness};
+use frame_support::traits::{KeyOwnerProofSystem, OnRuntimeUpgrade, Randomness};
 use frame_support::unsigned::{TransactionSource, TransactionValidity};
 use pallet_grandpa::fg_primitives;
 use pallet_transaction_payment_rpc_runtime_api::RuntimeDispatchInfo;
 use sp_api::impl_runtime_apis;
 use sp_core::crypto::KeyTypeId;
 use sp_core::OpaqueMetadata;
-use sp_runtime::traits::{BlakeTwo256, Block as BlockT, NumberFor};
+use sp_runtime::traits::{BlakeTwo256, Block as BlockT, Convert, NumberFor};
 use sp_runtime::{generic, ApplyExtrinsicResult};
 use sp_std::vec::Vec;
 
@@ -16,9 +16,32 @@ use crate::{
     GrandpaId, Hash, Index, RuntimeVersion, Signature, VERSION,
 };
 use crate::{
-    AllModules, AuthorityDiscovery, Babe, Call, Grandpa, Historical, InherentDataExt,
-    RandomnessCollectiveFlip, Runtime, SessionKeys, System, TransactionPayment,
+    AllModules, AuthorityDiscovery, Babe, Balances, Call, Grandpa, Historical, InherentDataExt,
+    ProposalsEngine, RandomnessCollectiveFlip, Runtime, SessionKeys, System, TransactionPayment,
 };
+use frame_support::weights::Weight;
+
+/// Struct that handles the conversion of Balance -> `u64`. This is used for staking's election
+/// calculation.
+pub struct CurrencyToVoteHandler;
+
+impl CurrencyToVoteHandler {
+    fn factor() -> Balance {
+        (Balances::total_issuance() / u64::max_value() as Balance).max(1)
+    }
+}
+
+impl Convert<Balance, u64> for CurrencyToVoteHandler {
+    fn convert(x: Balance) -> u64 {
+        (x / Self::factor()) as u64
+    }
+}
+
+impl Convert<u128, Balance> for CurrencyToVoteHandler {
+    fn convert(x: u128) -> Balance {
+        x * Self::factor()
+    }
+}
 
 /// The SignedExtension to the basic transaction logic.
 pub type SignedExtra = (
@@ -52,33 +75,32 @@ pub type BlockId = generic::BlockId<Block>;
 pub type UncheckedExtrinsic = generic::UncheckedExtrinsic<AccountId, Call, Signature, SignedExtra>;
 
 // Default Executive type without the RuntimeUpgrade
-pub type Executive = frame_executive::Executive<
-    Runtime,
-    Block,
-    frame_system::ChainContext<Runtime>,
-    Runtime,
-    AllModules,
->;
-
-// /// Custom runtime upgrade handler.
-// pub struct CustomOnRuntimeUpgrade;
-// impl OnRuntimeUpgrade for CustomOnRuntimeUpgrade {
-//     fn on_runtime_upgrade() -> Weight {
-//
-//         10_000_000 // TODO: adjust weight
-//     }
-// }
-//
-// /// Executive: handles dispatch to the various modules with CustomOnRuntimeUpgrade.
 // pub type Executive = frame_executive::Executive<
 //     Runtime,
 //     Block,
 //     frame_system::ChainContext<Runtime>,
 //     Runtime,
 //     AllModules,
-//     CustomOnRuntimeUpgrade,
 // >;
 
+/// Custom runtime upgrade handler.
+pub struct CustomOnRuntimeUpgrade;
+impl OnRuntimeUpgrade for CustomOnRuntimeUpgrade {
+    fn on_runtime_upgrade() -> Weight {
+        ProposalsEngine::cancel_active_and_pending_proposals()
+    }
+}
+
+/// Executive: handles dispatch to the various modules with CustomOnRuntimeUpgrade.
+pub type Executive = frame_executive::Executive<
+    Runtime,
+    Block,
+    frame_system::ChainContext<Runtime>,
+    Runtime,
+    AllModules,
+    CustomOnRuntimeUpgrade,
+>;
+
 /// Export of the private const generated within the macro.
 pub const EXPORTED_RUNTIME_API_VERSIONS: sp_version::ApisVec = RUNTIME_API_VERSIONS;
 

+ 18 - 8
runtime/src/tests/mod.rs

@@ -34,12 +34,13 @@ pub(crate) fn initial_test_ext() -> sp_io::TestExternalities {
 }
 
 fn get_account_membership(account: AccountId32, i: usize) -> u64 {
-    if !Membership::is_member_account(&account) {
+    let member_id = i as u64;
+    if Membership::membership(member_id).controller_account != account {
         insert_member(account.clone());
-        set_staking_account(account, i as u64);
+        set_staking_account(account.clone(), account, member_id);
     }
 
-    i as u64
+    member_id
 }
 
 pub(crate) fn elect_council(council: Vec<AccountId32>, cycle_id: u64) {
@@ -65,7 +66,12 @@ pub(crate) fn elect_council(council: Vec<AccountId32>, cycle_id: u64) {
             councilor_stake,
         )
         .unwrap();
-        voters.push([council.len() as u8 + extra_candidates as u8 + i as u8; 32].into());
+        // Make sure to use different voters in each election cycle to prevent problems with
+        // staking
+        voters.push(
+            [(council.len() as u8 + extra_candidates as u8) * (cycle_id as u8 + 1) + i as u8; 32]
+                .into(),
+        );
         council_member_ids.push(member_id);
     }
 
@@ -167,17 +173,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) {
+pub(crate) fn set_staking_account(
+    controller_account_id: AccountId32,
+    staking_account_id: AccountId32,
+    member_id: u64,
+) {
     membership::Module::<Runtime>::add_staking_account_candidate(
-        RawOrigin::Signed(account_id.clone()).into(),
+        RawOrigin::Signed(staking_account_id.clone()).into(),
         member_id,
     )
     .unwrap();
 
     membership::Module::<Runtime>::confirm_staking_account(
-        RawOrigin::Signed(account_id.clone()).into(),
+        RawOrigin::Signed(controller_account_id.clone()).into(),
         member_id,
-        account_id.clone(),
+        staking_account_id.clone(),
     )
     .unwrap();
 }

+ 17 - 4
runtime/src/tests/proposals_integration/mod.rs

@@ -37,7 +37,7 @@ fn setup_members(count: u8) {
         let account_id: [u8; 32] = [i; 32];
         let account_id_converted: AccountId32 = account_id.clone().into();
         insert_member(account_id_converted.clone());
-        set_staking_account(account_id_converted, i as u64);
+        set_staking_account(account_id_converted.clone(), account_id_converted, i as u64);
     }
 }
 
@@ -236,7 +236,7 @@ impl CancelProposalFixture {
 }
 
 /// Main purpose of this integration test: check balance of the member on proposal finalization (cancellation)
-/// It tests StakingEventsHandler integration. Also, membership module is tested during the proposal creation (ActorOriginValidator).
+/// It tests StakingEventsHandler integration. Also, membership module is tested during the proposal creation (MemberOriginValidator).
 #[test]
 fn proposal_cancellation_with_slashes_with_balance_checks_succeeds() {
     initial_test_ext().execute_with(|| {
@@ -534,12 +534,24 @@ fn set_validator_count_proposal_execution_succeeds() {
         let new_validator_count = 8;
         assert_eq!(<pallet_staking::ValidatorCount>::get(), 0);
 
+        setup_members(15);
+        setup_council(0);
+        increase_total_balance_issuance_using_account_id(account_id.clone().into(), 1_500_000);
+
+        let staking_account_id: [u8; 32] = [225u8; 32];
+        increase_total_balance_issuance_using_account_id(staking_account_id.into(), 1_500_000);
+        set_staking_account(
+            account_id.into(),
+            staking_account_id.into(),
+            member_id as u64,
+        );
+
         let codex_extrinsic_test_fixture = CodexProposalTestFixture::default_for_call(|| {
             let general_proposal_parameters = GeneralProposalParameters::<Runtime> {
                 member_id: member_id.into(),
                 title: b"title".to_vec(),
                 description: b"body".to_vec(),
-                staking_account_id: Some(account_id.into()),
+                staking_account_id: Some(staking_account_id.into()),
                 exact_execution_block: None,
             };
 
@@ -548,7 +560,8 @@ fn set_validator_count_proposal_execution_succeeds() {
                 general_proposal_parameters,
                 ProposalDetails::SetValidatorCount(new_validator_count),
             )
-        });
+        })
+        .disable_setup_enviroment();
         codex_extrinsic_test_fixture.call_extrinsic_and_assert();
 
         run_to_block(14410);

+ 106 - 53
runtime/src/tests/proposals_integration/working_group_proposals.rs

@@ -7,7 +7,7 @@ use common::working_group::WorkingGroup;
 use frame_system::RawOrigin;
 use proposals_codex::AddOpeningParameters;
 use strum::IntoEnumIterator;
-use working_group::{Penalty, StakeParameters};
+use working_group::StakeParameters;
 
 use crate::primitives::{ActorId, MemberId};
 use crate::tests::run_to_block;
@@ -66,11 +66,17 @@ fn add_opening(
     };
 
     let codex_extrinsic_test_fixture = CodexProposalTestFixture::default_for_call(|| {
+        let staking_account_id: [u8; 32] = [221u8; 32];
+        increase_total_balance_issuance_using_account_id(
+            staking_account_id.clone().into(),
+            1_500_000,
+        );
+
         let general_proposal_parameters = GeneralProposalParameters::<Runtime> {
             member_id: member_id.into(),
             title: b"title".to_vec(),
             description: b"body".to_vec(),
-            staking_account_id: Some(account_id.into()),
+            staking_account_id: Some(staking_account_id.into()),
             exact_execution_block: None,
         };
 
@@ -120,12 +126,16 @@ fn fill_opening(
 ) {
     let expected_proposal_id = sequence_number;
 
+    let staking_account_id: [u8; 32] = [220u8; 32];
+    increase_total_balance_issuance_using_account_id(staking_account_id.into(), 1_500_000);
+    set_staking_account(account_id.into(), staking_account_id.into(), member_id);
+
     let codex_extrinsic_test_fixture = CodexProposalTestFixture::default_for_call(|| {
         let general_proposal_parameters = GeneralProposalParameters::<Runtime> {
             member_id: member_id.into(),
             title: b"title".to_vec(),
             description: b"body".to_vec(),
-            staking_account_id: Some(account_id.into()),
+            staking_account_id: Some(staking_account_id.into()),
             exact_execution_block: None,
         };
 
@@ -157,12 +167,16 @@ fn decrease_stake(
 ) {
     let expected_proposal_id = sequence_number;
 
+    let staking_account_id: [u8; 32] = [227u8; 32];
+    increase_total_balance_issuance_using_account_id(staking_account_id.into(), 1_500_000);
+    set_staking_account(account_id.into(), staking_account_id.into(), member_id);
+
     let codex_extrinsic_test_fixture = CodexProposalTestFixture::default_for_call(|| {
         let general_proposal_parameters = GeneralProposalParameters::<Runtime> {
             member_id: member_id.into(),
             title: b"title".to_vec(),
             description: b"body".to_vec(),
-            staking_account_id: Some(account_id.into()),
+            staking_account_id: Some(staking_account_id.into()),
             exact_execution_block: None,
         };
 
@@ -185,6 +199,7 @@ fn decrease_stake(
 fn slash_stake(
     member_id: MemberId,
     account_id: [u8; 32],
+    staking_account_id: [u8; 32],
     leader_worker_id: ActorId,
     stake_amount: Balance,
     sequence_number: u32, // action sequence number to align with other actions
@@ -197,7 +212,7 @@ fn slash_stake(
             member_id: member_id.into(),
             title: b"title".to_vec(),
             description: b"body".to_vec(),
-            staking_account_id: Some(account_id.into()),
+            staking_account_id: Some(staking_account_id.into()),
             exact_execution_block: None,
         };
 
@@ -206,10 +221,7 @@ fn slash_stake(
             general_proposal_parameters,
             ProposalDetails::SlashWorkingGroupLeaderStake(
                 leader_worker_id,
-                Penalty {
-                    slashing_amount: stake_amount,
-                    slashing_text: Vec::new(),
-                },
+                stake_amount,
                 working_group,
             ),
         )
@@ -230,12 +242,16 @@ fn set_reward(
 ) {
     let expected_proposal_id = sequence_number;
 
+    let staking_account_id: [u8; 32] = [228u8; 32];
+    increase_total_balance_issuance_using_account_id(staking_account_id.into(), 1_500_000);
+    set_staking_account(account_id.into(), staking_account_id.into(), member_id);
+
     let codex_extrinsic_test_fixture = CodexProposalTestFixture::default_for_call(|| {
         let general_proposal_parameters = GeneralProposalParameters::<Runtime> {
             member_id: member_id.into(),
             title: b"title".to_vec(),
             description: b"body".to_vec(),
-            staking_account_id: Some(account_id.into()),
+            staking_account_id: Some(staking_account_id.into()),
             exact_execution_block: None,
         };
 
@@ -268,12 +284,16 @@ fn set_mint_capacity<
 ) {
     let expected_proposal_id = sequence_number;
 
+    let staking_account_id: [u8; 32] = [224u8; 32];
+    increase_total_balance_issuance_using_account_id(staking_account_id.into(), 1_500_000);
+    set_staking_account(account_id.into(), staking_account_id.into(), member_id);
+
     let codex_extrinsic_test_fixture = CodexProposalTestFixture::default_for_call(|| {
         let general_proposal_parameters = GeneralProposalParameters::<Runtime> {
             member_id: member_id.into(),
             title: b"title".to_vec(),
             description: b"body".to_vec(),
-            staking_account_id: Some(account_id.into()),
+            staking_account_id: Some(staking_account_id.into()),
             exact_execution_block: None,
         };
 
@@ -293,18 +313,22 @@ fn terminate_role(
     member_id: MemberId,
     account_id: [u8; 32],
     leader_worker_id: u64,
-    penalty: Option<Penalty<Balance>>,
+    penalty: Option<u128>,
     sequence_number: u32, // action sequence number to align with other actions
     working_group: WorkingGroup,
 ) {
     let expected_proposal_id = sequence_number;
 
+    let staking_account_id: [u8; 32] = [223u8; 32];
+    increase_total_balance_issuance_using_account_id(staking_account_id.into(), 1_500_000);
+    set_staking_account(account_id.into(), staking_account_id.into(), member_id);
+
     let codex_extrinsic_test_fixture = CodexProposalTestFixture::default_for_call(|| {
         let general_proposal_parameters = GeneralProposalParameters::<Runtime> {
             member_id: member_id.into(),
             title: b"title".to_vec(),
             description: b"body".to_vec(),
-            staking_account_id: Some(account_id.into()),
+            staking_account_id: Some(staking_account_id.into()),
             exact_execution_block: None,
         };
 
@@ -314,7 +338,7 @@ fn terminate_role(
             ProposalDetails::TerminateWorkingGroupLeaderRole(
                 proposals_codex::TerminateRoleParameters {
                     worker_id: leader_worker_id,
-                    penalty: penalty.clone(),
+                    penalty,
                     working_group,
                 },
             ),
@@ -458,7 +482,7 @@ fn run_create_fill_working_group_leader_opening_proposal_execution_succeeds<
 
         fill_opening(
             member_id,
-            account_id,
+            account_id.clone(),
             opening_id,
             expected_application_id,
             2,
@@ -532,22 +556,25 @@ fn run_create_decrease_group_leader_stake_proposal_execution_succeeds<
         let account_id: [u8; 32] = [member_id as u8; 32];
         let stake_amount: Balance = 100;
 
-        increase_total_balance_issuance_using_account_id(account_id.clone().into(), 1_500_000);
+        increase_total_balance_issuance_using_account_id(account_id.into(), 1_500_000);
 
         let stake_policy = Some(working_group::StakePolicy {
             stake_amount,
             leaving_unstaking_period: 45000, // more than min value
         });
 
+        let opening_id = add_opening(member_id, account_id, stake_policy, 1, working_group);
+
+        let staking_account_id: [u8; 32] = [22u8; 32];
+        increase_total_balance_issuance_using_account_id(staking_account_id.into(), 1_500_000);
+        set_staking_account(account_id.into(), staking_account_id.into(), member_id);
         let stake_parameters = Some(
             StakeParameters::<T::AccountId, working_group::BalanceOf<T>> {
                 stake: stake_amount.into(),
-                staking_account_id: account_id.into(),
+                staking_account_id: staking_account_id.into(),
             },
         );
 
-        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(
@@ -571,15 +598,16 @@ fn run_create_decrease_group_leader_stake_proposal_execution_succeeds<
 
         fill_opening(
             member_id,
-            account_id,
+            account_id.clone(),
             opening_id,
             expected_application_id,
             2,
             working_group,
         );
 
-        let new_balance = Balances::usable_balance(&account_id.into());
-        let stake: working_group::BalanceOf<T> = SM::current_stake(&account_id.into()).into();
+        let new_balance = Balances::usable_balance(&staking_account_id.into());
+        let stake: working_group::BalanceOf<T> =
+            SM::current_stake(&staking_account_id.into()).into();
 
         let leader_worker_id = WorkingGroupInstance::<T, I>::current_lead().unwrap();
 
@@ -587,7 +615,7 @@ fn run_create_decrease_group_leader_stake_proposal_execution_succeeds<
             WorkingGroupInstance::<T, I>::worker_by_id(leader_worker_id)
                 .staking_account_id
                 .unwrap(),
-            account_id.into()
+            staking_account_id.into()
         );
 
         assert_eq!(stake, working_group::BalanceOf::<T>::from(stake_amount));
@@ -605,8 +633,9 @@ fn run_create_decrease_group_leader_stake_proposal_execution_succeeds<
             working_group,
         );
 
-        let new_balance = Balances::usable_balance(&account_id.into());
-        let new_stake: working_group::BalanceOf<T> = SM::current_stake(&account_id.into()).into();
+        let new_balance = Balances::usable_balance(&staking_account_id.into());
+        let new_stake: working_group::BalanceOf<T> =
+            SM::current_stake(&staking_account_id.into()).into();
         let converted_stake_amount: working_group::BalanceOf<T> = stake_amount.into();
 
         assert_eq!(
@@ -684,15 +713,19 @@ fn run_create_slash_group_leader_stake_proposal_execution_succeeds<
 
         increase_total_balance_issuance_using_account_id(account_id.clone().into(), 1_500_000);
 
+        let opening_id = add_opening(member_id, account_id, stake_policy, 1, working_group);
+
+        // Setup staking account
+        let staking_account_id: [u8; 32] = [33u8; 32];
+        increase_total_balance_issuance_using_account_id(staking_account_id.into(), 1_500_000);
+        set_staking_account(account_id.into(), staking_account_id.into(), member_id);
         let stake_parameters = Some(
             StakeParameters::<T::AccountId, working_group::BalanceOf<T>> {
                 stake: stake_amount.into(),
-                staking_account_id: account_id.into(),
+                staking_account_id: staking_account_id.into(),
             },
         );
 
-        let opening_id = add_opening(member_id, account_id, stake_policy, 1, working_group);
-
         let apply_result = WorkingGroupInstance::<T, I>::apply_on_opening(
             RawOrigin::Signed(account_id.into()).into(),
             working_group::ApplyOnOpeningParameters::<T> {
@@ -724,23 +757,37 @@ fn run_create_slash_group_leader_stake_proposal_execution_succeeds<
 
         let leader_worker_id = WorkingGroupInstance::<T, I>::current_lead().unwrap();
 
-        let old_balance = Balances::usable_balance(&account_id.into());
-        let old_stake = SM::current_stake(&account_id.into());
+        let old_balance = Balances::usable_balance(&staking_account_id.into());
+        let old_stake = SM::current_stake(&staking_account_id.into());
 
         assert_eq!(old_stake, stake_amount.into());
 
+        // Setup staking account for slashing
+        let staking_account_id_for_slashing: [u8; 32] = [22u8; 32];
+        increase_total_balance_issuance_using_account_id(
+            staking_account_id_for_slashing.into(),
+            1_500_000,
+        );
+        set_staking_account(
+            account_id.into(),
+            staking_account_id_for_slashing.into(),
+            member_id,
+        );
+
         let slashing_stake_amount = 30;
         slash_stake(
             member_id,
-            account_id,
+            account_id.clone(),
+            staking_account_id_for_slashing.clone(),
             leader_worker_id.into(),
             slashing_stake_amount,
             3,
             working_group,
         );
 
-        let new_balance = Balances::usable_balance(&account_id.into());
-        let new_stake: working_group::BalanceOf<T> = SM::current_stake(&account_id.into()).into();
+        let new_balance = Balances::usable_balance(&staking_account_id.into());
+        let new_stake: working_group::BalanceOf<T> =
+            SM::current_stake(&staking_account_id.into()).into();
         let converted_stake_amount: working_group::BalanceOf<T> = stake_amount.into();
 
         assert_eq!(
@@ -799,7 +846,19 @@ fn run_create_set_working_group_mint_capacity_proposal_execution_succeeds<
         let account_id: [u8; 32] = [member_id as u8; 32];
 
         let mint_capacity = 999999;
-        set_mint_capacity::<T, I>(member_id, account_id, mint_capacity, 1, true, working_group);
+
+        setup_members(15);
+        setup_council(0);
+
+        increase_total_balance_issuance_using_account_id(account_id.clone().into(), 1_500_000);
+        set_mint_capacity::<T, I>(
+            member_id,
+            account_id,
+            mint_capacity,
+            1,
+            false,
+            working_group,
+        );
 
         assert_eq!(
             working_group::Module::<T, I>::budget(),
@@ -814,25 +873,25 @@ fn create_set_group_leader_reward_proposal_execution_succeeds() {
     for group in WorkingGroup::iter() {
         match group {
             WorkingGroup::Content => {
-                run_create_set_working_group_mint_capacity_proposal_execution_succeeds::<
+                run_create_set_group_leader_reward_proposal_execution_succeeds::<
                     Runtime,
                     ContentDirectoryWorkingGroupInstance,
                 >(group);
             }
             WorkingGroup::Storage => {
-                run_create_set_working_group_mint_capacity_proposal_execution_succeeds::<
+                run_create_set_group_leader_reward_proposal_execution_succeeds::<
                     Runtime,
                     StorageWorkingGroupInstance,
                 >(group);
             }
             WorkingGroup::Forum => {
-                run_create_set_working_group_mint_capacity_proposal_execution_succeeds::<
+                run_create_set_group_leader_reward_proposal_execution_succeeds::<
                     Runtime,
                     ForumWorkingGroupInstance,
                 >(group);
             }
             WorkingGroup::Membership => {
-                run_create_set_working_group_mint_capacity_proposal_execution_succeeds::<
+                run_create_set_group_leader_reward_proposal_execution_succeeds::<
                     Runtime,
                     MembershipWorkingGroupInstance,
                 >(group);
@@ -856,12 +915,9 @@ fn run_create_set_group_leader_reward_proposal_execution_succeeds<
         let member_id: MemberId = 1;
         let account_id: [u8; 32] = [member_id as u8; 32];
 
-        let stake_policy = Some(working_group::StakePolicy {
-            stake_amount: 100,
-            leaving_unstaking_period: 0,
-        });
+        increase_total_balance_issuance_using_account_id(account_id.clone().into(), 1_500_000);
 
-        let opening_id = add_opening(member_id, account_id, stake_policy, 1, working_group);
+        let opening_id = add_opening(member_id, account_id, None, 1, working_group);
 
         let apply_result = WorkingGroupInstance::<T, I>::apply_on_opening(
             RawOrigin::Signed(account_id.into()).into(),
@@ -882,14 +938,14 @@ fn run_create_set_group_leader_reward_proposal_execution_succeeds<
         let lead = WorkingGroupInstance::<T, I>::current_lead();
         assert!(lead.is_none());
 
-        set_mint_capacity::<T, I>(member_id, account_id, 999999, 3, false, working_group);
+        set_mint_capacity::<T, I>(member_id, account_id, 999999, 2, false, working_group);
 
         fill_opening(
             member_id,
-            account_id,
+            account_id.clone(),
             opening_id,
             expected_application_id,
-            4,
+            3,
             working_group,
         );
 
@@ -901,7 +957,7 @@ fn run_create_set_group_leader_reward_proposal_execution_succeeds<
             account_id,
             leader_worker_id.into(),
             new_reward_amount,
-            5,
+            4,
             working_group,
         );
 
@@ -1011,7 +1067,7 @@ fn run_create_terminate_group_leader_role_proposal_execution_succeeds<
 
         fill_opening(
             member_id,
-            account_id,
+            account_id.clone(),
             opening_id,
             expected_application_id,
             3,
@@ -1143,7 +1199,7 @@ fn run_create_terminate_group_leader_role_proposal_with_slashing_execution_succe
 
         fill_opening(
             member_id,
-            account_id,
+            account_id.clone(),
             opening_id,
             expected_application_id,
             3,
@@ -1161,10 +1217,7 @@ fn run_create_terminate_group_leader_role_proposal_with_slashing_execution_succe
             member_id,
             account_id,
             leader_worker_id.into(),
-            Some(Penalty {
-                slashing_amount: stake_amount.into(),
-                slashing_text: Vec::new(),
-            }),
+            Some(stake_amount),
             4,
             working_group,
         );

+ 26 - 18
runtime/src/weights/proposals_engine.rs

@@ -8,59 +8,67 @@ use frame_support::weights::{constants::RocksDbWeight as DbWeight, Weight};
 pub struct WeightInfo;
 impl proposals_engine::WeightInfo for WeightInfo {
     fn vote(i: u32) -> Weight {
-        (375_240_000 as Weight)
-            .saturating_add((35_000 as Weight).saturating_mul(i as Weight))
+        (485_352_000 as Weight)
+            .saturating_add((39_000 as Weight).saturating_mul(i as Weight))
             .saturating_add(DbWeight::get().reads(4 as Weight))
             .saturating_add(DbWeight::get().writes(2 as Weight))
     }
-    fn cancel_proposal(i: u32) -> Weight {
-        (874_300_000 as Weight)
-            .saturating_add((1_713_000 as Weight).saturating_mul(i as Weight))
+    // WARNING! Some components were not used: ["i"]
+    fn cancel_proposal() -> Weight {
+        (1_126_523_000 as Weight)
             .saturating_add(DbWeight::get().reads(5 as Weight))
             .saturating_add(DbWeight::get().writes(8 as Weight))
     }
     fn veto_proposal() -> Weight {
-        (404_254_000 as Weight)
+        (479_000_000 as Weight)
             .saturating_add(DbWeight::get().reads(4 as Weight))
             .saturating_add(DbWeight::get().writes(8 as Weight))
     }
     fn on_initialize_immediate_execution_decode_fails(i: u32) -> Weight {
-        (22_531_000 as Weight)
-            .saturating_add((578_486_000 as Weight).saturating_mul(i as Weight))
+        (79_260_000 as Weight)
+            .saturating_add((740_840_000 as Weight).saturating_mul(i as Weight))
             .saturating_add(DbWeight::get().reads(3 as Weight))
             .saturating_add(DbWeight::get().reads((4 as Weight).saturating_mul(i as Weight)))
             .saturating_add(DbWeight::get().writes(2 as Weight))
             .saturating_add(DbWeight::get().writes((7 as Weight).saturating_mul(i as Weight)))
     }
     fn on_initialize_pending_execution_decode_fails(i: u32) -> Weight {
-        (31_944_000 as Weight)
-            .saturating_add((274_852_000 as Weight).saturating_mul(i as Weight))
+        (49_200_000 as Weight)
+            .saturating_add((330_580_000 as Weight).saturating_mul(i as Weight))
             .saturating_add(DbWeight::get().reads(2 as Weight))
             .saturating_add(DbWeight::get().reads((2 as Weight).saturating_mul(i as Weight)))
             .saturating_add(DbWeight::get().writes(2 as Weight))
             .saturating_add(DbWeight::get().writes((5 as Weight).saturating_mul(i as Weight)))
     }
     fn on_initialize_approved_pending_constitutionality(i: u32) -> Weight {
-        (50_422_000 as Weight)
-            .saturating_add((250_210_000 as Weight).saturating_mul(i as Weight))
+        (67_720_000 as Weight)
+            .saturating_add((363_000_000 as Weight).saturating_mul(i as Weight))
             .saturating_add(DbWeight::get().reads(2 as Weight))
             .saturating_add(DbWeight::get().reads((1 as Weight).saturating_mul(i as Weight)))
             .saturating_add(DbWeight::get().writes((1 as Weight).saturating_mul(i as Weight)))
     }
     fn on_initialize_rejected(i: u32) -> Weight {
-        (0 as Weight)
-            .saturating_add((884_947_000 as Weight).saturating_mul(i as Weight))
+        (81_920_000 as Weight)
+            .saturating_add((1_041_560_000 as Weight).saturating_mul(i as Weight))
             .saturating_add(DbWeight::get().reads(3 as Weight))
             .saturating_add(DbWeight::get().reads((3 as Weight).saturating_mul(i as Weight)))
             .saturating_add(DbWeight::get().writes(2 as Weight))
-            .saturating_add(DbWeight::get().writes((7 as Weight).saturating_mul(i as Weight)))
+            .saturating_add(DbWeight::get().writes((9 as Weight).saturating_mul(i as Weight)))
     }
     fn on_initialize_slashed(i: u32) -> Weight {
-        (24_867_000 as Weight)
-            .saturating_add((628_899_000 as Weight).saturating_mul(i as Weight))
+        (0 as Weight)
+            .saturating_add((871_510_000 as Weight).saturating_mul(i as Weight))
             .saturating_add(DbWeight::get().reads(3 as Weight))
             .saturating_add(DbWeight::get().reads((3 as Weight).saturating_mul(i as Weight)))
             .saturating_add(DbWeight::get().writes(2 as Weight))
-            .saturating_add(DbWeight::get().writes((7 as Weight).saturating_mul(i as Weight)))
+            .saturating_add(DbWeight::get().writes((9 as Weight).saturating_mul(i as Weight)))
+    }
+    fn cancel_active_and_pending_proposals(i: u32) -> Weight {
+        (120_990_000 as Weight)
+            .saturating_add((505_390_000 as Weight).saturating_mul(i as Weight))
+            .saturating_add(DbWeight::get().reads(2 as Weight))
+            .saturating_add(DbWeight::get().reads((3 as Weight).saturating_mul(i as Weight)))
+            .saturating_add(DbWeight::get().writes(2 as Weight))
+            .saturating_add(DbWeight::get().writes((9 as Weight).saturating_mul(i as Weight)))
     }
 }

+ 6 - 7
scripts/generate-weights.sh

@@ -45,11 +45,10 @@ benchmark() {
 # benchmark pallet_im_online
 
 # Joystrem benchmarks
-# benchmark proposals_discussion
-# benchmark proposals_engine
-# benchmark pallet_constitution
-# benchmark working_group
-# benchmark forum
+benchmark proposals_discussion
+benchmark proposals_engine
+benchmark pallet_constitution
+benchmark working_group
+benchmark council
+benchmark referendum
 benchmark membership
-# benchmark council
-# benchmark referendum

+ 8 - 6
setup.sh

@@ -22,7 +22,7 @@ rustup default 1.47.0
 
 if [[ "$OSTYPE" == "linux-gnu" ]]; then
     sudo apt-get install -y coreutils clang jq curl gcc xz-utils sudo pkg-config unzip clang libc6-dev-i386
-    sudo apt-get install -y docker.io docker-compose
+    sudo apt-get install -y docker.io docker-compose containerd runc
 elif [[ "$OSTYPE" == "darwin"* ]]; then
     brew install b2sum gnu-tar jq curl
     echo "It is recommended to setup Docker desktop from: https://www.docker.com/products/docker-desktop"
@@ -31,10 +31,12 @@ fi
 # Volta nodejs, npm, yarn tools manager
 curl https://get.volta.sh | bash
 
-# After installing volta the .profile and .bash_profile are updated
-# to add it to the PATH, so we start new shell to use it
-env bash -c "volta install node@12"
-env bash -c "volta install yarn"
-env bash -c "volta install npx"
+# source env variables added by Volta
+source ~/.bashrc || :
+source ~/.bash_profile || :
+
+volta install node@12
+volta install yarn
+volta install npx
 
 echo "Open a new terminal to start using newly installed tools"