Переглянути джерело

Merge remote-tracking branch 'shamil/working_group_hirable_lead' into nicaea-types-2nd-update

Leszek Wiesner 4 роки тому
батько
коміт
04b87a908d
30 змінених файлів з 1419 додано та 922 видалено
  1. 1 1
      Cargo.lock
  2. 1 1
      runtime-modules/common/src/lib.rs
  3. 26 0
      runtime-modules/common/src/origin.rs
  4. 0 5
      runtime-modules/common/src/origin_validator.rs
  5. 7 1
      runtime-modules/hiring/src/hiring/staking_policy.rs
  6. 3 22
      runtime-modules/proposals/codex/src/lib.rs
  7. 1 1
      runtime-modules/proposals/codex/src/tests/mock.rs
  8. 1 1
      runtime-modules/proposals/discussion/src/lib.rs
  9. 1 1
      runtime-modules/proposals/engine/src/lib.rs
  10. 1 1
      runtime-modules/proposals/engine/src/tests/mock/mod.rs
  11. 3 3
      runtime-modules/recurring-reward/src/lib.rs
  12. 1 1
      runtime-modules/service-discovery/src/mock.rs
  13. 1 1
      runtime-modules/storage/src/data_directory.rs
  14. 18 6
      runtime-modules/storage/src/tests/data_object_type_registry.rs
  15. 2 2
      runtime-modules/storage/src/tests/mock.rs
  16. 10 0
      runtime-modules/working-group/src/errors.rs
  17. 391 263
      runtime-modules/working-group/src/lib.rs
  18. 241 157
      runtime-modules/working-group/src/tests/fixtures.rs
  19. 200 0
      runtime-modules/working-group/src/tests/hiring_workflow.rs
  20. 50 3
      runtime-modules/working-group/src/tests/mock.rs
  21. 221 271
      runtime-modules/working-group/src/tests/mod.rs
  22. 29 35
      runtime-modules/working-group/src/types.rs
  23. 1 2
      runtime/Cargo.toml
  24. 141 0
      runtime/src/integration/content_working_group.rs
  25. 2 0
      runtime/src/integration/mod.rs
  26. 2 2
      runtime/src/integration/proposals/council_origin_validator.rs
  27. 2 2
      runtime/src/integration/proposals/membership_origin_validator.rs
  28. 1 1
      runtime/src/integration/storage.rs
  29. 49 0
      runtime/src/integration/working_group.rs
  30. 12 139
      runtime/src/lib.rs

+ 1 - 1
Cargo.lock

@@ -1614,7 +1614,7 @@ dependencies = [
 
 [[package]]
 name = "joystream-node-runtime"
-version = "6.16.0"
+version = "6.17.0"
 dependencies = [
  "parity-scale-codec",
  "safe-mix",

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

@@ -3,7 +3,7 @@
 
 pub mod constraints;
 pub mod currency;
-pub mod origin_validator;
+pub mod origin;
 
 use codec::{Decode, Encode};
 #[cfg(feature = "std")]

+ 26 - 0
runtime-modules/common/src/origin.rs

@@ -0,0 +1,26 @@
+use system::RawOrigin;
+
+/// 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>;
+}
+
+// Multiplies the T::Origin.
+// In our current substrate version system::Origin doesn't support clone(),
+// but it will be supported in latest up-to-date substrate version.
+// TODO: delete when T::Origin will support the clone()
+pub fn double_origin<T: system::Trait>(origin: T::Origin) -> (T::Origin, T::Origin) {
+    let coerced_origin = origin.into().ok().unwrap_or(RawOrigin::None);
+
+    let (cloned_origin1, cloned_origin2) = match coerced_origin {
+        RawOrigin::None => (RawOrigin::None, RawOrigin::None),
+        RawOrigin::Root => (RawOrigin::Root, RawOrigin::Root),
+        RawOrigin::Signed(account_id) => (
+            RawOrigin::Signed(account_id.clone()),
+            RawOrigin::Signed(account_id),
+        ),
+    };
+
+    (cloned_origin1.into(), cloned_origin2.into())
+}

+ 0 - 5
runtime-modules/common/src/origin_validator.rs

@@ -1,5 +0,0 @@
-/// 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>;
-}

+ 7 - 1
runtime-modules/hiring/src/hiring/staking_policy.rs

@@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
 
 /// Policy for staking
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-#[derive(Encode, Decode, Debug, Eq, PartialEq, Clone)]
+#[derive(Encode, Decode, Debug, Eq, PartialEq, Clone, Default)]
 pub struct StakingPolicy<Balance, BlockNumber> {
     /// Staking amount
     pub amount: Balance,
@@ -77,3 +77,9 @@ pub enum StakingAmountLimitMode {
     /// Stake should be equal to provided value
     Exact,
 }
+
+impl Default for StakingAmountLimitMode {
+    fn default() -> Self {
+        StakingAmountLimitMode::Exact
+    }
+}

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

@@ -54,7 +54,7 @@ mod proposal_types;
 #[cfg(test)]
 mod tests;
 
-use common::origin_validator::ActorOriginValidator;
+use common::origin::ActorOriginValidator;
 use governance::election_params::ElectionParameters;
 use proposal_engine::ProposalParameters;
 use rstd::clone::Clone;
@@ -65,7 +65,7 @@ use sr_primitives::traits::Zero;
 use srml_support::dispatch::DispatchResult;
 use srml_support::traits::{Currency, Get};
 use srml_support::{decl_error, decl_module, decl_storage, ensure, print};
-use system::{ensure_root, RawOrigin};
+use system::ensure_root;
 
 pub use crate::proposal_types::ProposalsConfigParameters;
 pub use proposal_types::{ProposalDetails, ProposalDetailsOf, ProposalEncoder};
@@ -577,7 +577,7 @@ decl_module! {
             origin,
             wasm: Vec<u8>,
         ) {
-            let (cloned_origin1, cloned_origin2) =  Self::double_origin(origin);
+            let (cloned_origin1, cloned_origin2) = common::origin::double_origin::<T>(origin);
             ensure_root(cloned_origin1)?;
 
             print("Runtime upgrade proposal execution started.");
@@ -590,25 +590,6 @@ decl_module! {
 }
 
 impl<T: Trait> Module<T> {
-    // Multiplies the T::Origin.
-    // In our current substrate version system::Origin doesn't support clone(),
-    // but it will be supported in latest up-to-date substrate version.
-    // TODO: delete when T::Origin will support the clone()
-    fn double_origin(origin: T::Origin) -> (T::Origin, T::Origin) {
-        let coerced_origin = origin.into().ok().unwrap_or(RawOrigin::None);
-
-        let (cloned_origin1, cloned_origin2) = match coerced_origin {
-            RawOrigin::None => (RawOrigin::None, RawOrigin::None),
-            RawOrigin::Root => (RawOrigin::Root, RawOrigin::Root),
-            RawOrigin::Signed(account_id) => (
-                RawOrigin::Signed(account_id.clone()),
-                RawOrigin::Signed(account_id),
-            ),
-        };
-
-        (cloned_origin1.into(), cloned_origin2.into())
-    }
-
     // Generic template proposal builder
     fn create_proposal(
         origin: T::Origin,

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

@@ -123,7 +123,7 @@ impl governance::council::Trait for Test {
     type CouncilTermEnded = ();
 }
 
-impl common::origin_validator::ActorOriginValidator<Origin, u64, u64> for () {
+impl common::origin::ActorOriginValidator<Origin, u64, u64> for () {
     fn ensure_actor_origin(origin: Origin, _: u64) -> Result<u64, &'static str> {
         let account_id = system::ensure_signed(origin)?;
 

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

@@ -56,7 +56,7 @@ use srml_support::{decl_error, decl_event, decl_module, decl_storage, ensure, Pa
 use srml_support::traits::Get;
 use types::{DiscussionPost, DiscussionThread, ThreadCounter};
 
-use common::origin_validator::ActorOriginValidator;
+use common::origin::ActorOriginValidator;
 use srml_support::dispatch::DispatchResult;
 
 type MemberId<T> = <T as membership::members::Trait>::MemberId;

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

@@ -135,7 +135,7 @@ use srml_support::{
 use system::{ensure_root, RawOrigin};
 
 use crate::types::ApprovedProposalData;
-use common::origin_validator::ActorOriginValidator;
+use common::origin::ActorOriginValidator;
 use srml_support::dispatch::Dispatchable;
 
 type MemberId<T> = <T as membership::members::Trait>::MemberId;

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

@@ -124,7 +124,7 @@ impl Default for proposals::Call<Test> {
     }
 }
 
-impl common::origin_validator::ActorOriginValidator<Origin, u64, u64> for () {
+impl common::origin::ActorOriginValidator<Origin, u64, u64> for () {
     fn ensure_actor_origin(origin: Origin, _account_id: u64) -> Result<u64, &'static str> {
         let signed_account_id = system::ensure_signed(origin)?;
 

+ 3 - 3
runtime-modules/recurring-reward/src/lib.rs

@@ -94,10 +94,10 @@ pub struct RewardRelationship<AccountId, Balance, BlockNumber, MintId, Recipient
     mint_id: MintId,
 
     /// Destination account for reward
-    account: AccountId,
+    pub account: AccountId,
 
     /// The payout amount at the next payout
-    amount_per_payout: Balance,
+    pub amount_per_payout: Balance,
 
     /// When set, identifies block when next payout should be processed,
     /// otherwise there is no pending payout
@@ -146,7 +146,7 @@ decl_storage! {
 
         RecipientsCreated get(recipients_created): T::RecipientId;
 
-        RewardRelationships get(reward_relationships): linked_map T::RewardRelationshipId => RewardRelationship<T::AccountId, BalanceOf<T>, T::BlockNumber, T::MintId, T::RecipientId>;
+        pub RewardRelationships get(reward_relationships): linked_map T::RewardRelationshipId => RewardRelationship<T::AccountId, BalanceOf<T>, T::BlockNumber, T::MintId, T::RecipientId>;
 
         RewardRelationshipsCreated get(reward_relationships_created): T::RewardRelationshipId;
     }

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

@@ -158,7 +158,7 @@ pub(crate) fn hire_storage_provider() -> (u64, u64) {
 
     let storage_provider = working_group::Worker {
         member_id: 1,
-        role_account: role_account_id,
+        role_account_id,
         reward_relationship: None,
         role_stake_profile: None,
     };

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

@@ -27,7 +27,7 @@ use sr_primitives::traits::{MaybeSerialize, Member};
 use srml_support::{decl_error, decl_event, decl_module, decl_storage, ensure, Parameter};
 use system::{self, ensure_root};
 
-use common::origin_validator::ActorOriginValidator;
+use common::origin::ActorOriginValidator;
 pub(crate) use common::BlockAndTime;
 
 use crate::data_object_type_registry;

+ 18 - 6
runtime-modules/storage/src/tests/data_object_type_registry.rs

@@ -1,21 +1,33 @@
 #![cfg(test)]
 
 use super::mock::*;
-use crate::StorageWorkingGroup;
+use srml_support::{StorageLinkedMap, StorageValue};
 use system::{self, EventRecord, Phase, RawOrigin};
 
 const DEFAULT_LEADER_ACCOUNT_ID: u64 = 1;
 const DEFAULT_LEADER_MEMBER_ID: u64 = 1;
+const DEFAULT_LEADER_WORKER_ID: u32 = 1;
 
 struct SetLeadFixture;
 impl SetLeadFixture {
     fn set_default_lead() {
-        let set_lead_result = <StorageWorkingGroup<Test>>::set_lead(
-            RawOrigin::Root.into(),
-            DEFAULT_LEADER_MEMBER_ID,
-            DEFAULT_LEADER_ACCOUNT_ID,
+        let worker = working_group::Worker {
+            member_id: DEFAULT_LEADER_MEMBER_ID,
+            role_account_id: DEFAULT_LEADER_ACCOUNT_ID,
+            reward_relationship: None,
+            role_stake_profile: None,
+        };
+
+        // Create the worker.
+        <working_group::WorkerById<Test, StorageWorkingGroupInstance>>::insert(
+            DEFAULT_LEADER_WORKER_ID,
+            worker,
+        );
+
+        // Update current lead.
+        <working_group::CurrentLead<Test, StorageWorkingGroupInstance>>::put(
+            DEFAULT_LEADER_WORKER_ID,
         );
-        assert!(set_lead_result.is_ok());
     }
 }
 

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

@@ -169,7 +169,7 @@ impl crate::data_directory::StorageProviderHelper<Test> for () {
     }
 }
 
-impl common::origin_validator::ActorOriginValidator<Origin, u64, u64> for () {
+impl common::origin::ActorOriginValidator<Origin, u64, u64> for () {
     fn ensure_actor_origin(origin: Origin, _account_id: u64) -> Result<u64, &'static str> {
         let signed_account_id = system::ensure_signed(origin)?;
 
@@ -304,7 +304,7 @@ pub(crate) fn hire_storage_provider() -> (u64, u32) {
 
     let storage_provider = working_group::Worker {
         member_id: 1,
-        role_account: role_account_id,
+        role_account_id,
         reward_relationship: None,
         role_stake_profile: None,
     };

+ 10 - 0
runtime-modules/working-group/src/errors.rs

@@ -14,6 +14,12 @@ decl_error! {
         /// Current lead is not set.
         CurrentLeadNotSet,
 
+        /// There is leader already, cannot hire another one.
+        CannotHireLeaderWhenLeaderExists,
+
+        /// Cannot fill opening with multiple applications.
+        CannotHireMultipleLeaders,
+
         /// Not a lead account.
         IsNotLeadAccount,
 
@@ -235,6 +241,9 @@ decl_error! {
 
         /// Require root origin in extrinsics.
         RequireRootOrigin,
+
+        /// Require signed origin in extrinsics.
+        RequireSignedOrigin,
     }
 }
 
@@ -243,6 +252,7 @@ impl From<system::Error> for Error {
         match error {
             system::Error::Other(msg) => Error::Other(msg),
             system::Error::RequireRootOrigin => Error::RequireRootOrigin,
+            system::Error::RequireSignedOrigin => Error::RequireSignedOrigin,
             _ => Error::Other(error.into()),
         }
     }

+ 391 - 263
runtime-modules/working-group/src/lib.rs

@@ -5,8 +5,13 @@
 //! ## Overview
 //!
 //! The working group module provides working group workflow to use in different modules.
+//! It contains extrinsics for the hiring workers, their roles lifecycle and stake management.
+//! There is a possibility to hire a special worker - the leader of the working group.
+//! Some module operations like 'increase_stake' can be invoked by the worker, others
+//! like 'terminate_role' can be invoked by the leader only. The leader himself can be hired and
+//! managed only by the council via proposals.
+//!
 //! Exact working group (eg.: forum working group) should create an instance of the Working group module.
-//! The Working group module contains extrinsics for the hiring workflow and the roles lifecycle.
 //!
 //! ## Supported extrinsics
 //! ### Hiring flow
@@ -25,14 +30,14 @@
 //! - [update_reward_account](./struct.Module.html#method.update_reward_account) -  Update the reward account of the worker/lead.
 //! - [update_reward_amount](./struct.Module.html#method.update_reward_amount) -  Update the reward amount of the worker/lead.
 //! - [leave_role](./struct.Module.html#method.leave_role) - Leave the role by the active worker/lead.
-//! - [terminate_role](./struct.Module.html#method.terminate_role) - Terminate the worker role by the lead.
+//! - [terminate_role](./struct.Module.html#method.terminate_role) - Terminate the worker/lead role.
 //! - [set_mint_capacity](./struct.Module.html#method.set_mint_capacity) -  Sets the capacity to enable working group budget.
 //!
 //! ### Stakes
 //!
 //! - [slash_stake](./struct.Module.html#method.slash_stake) - Slashes the worker/lead stake.
 //! - [decrease_stake](./struct.Module.html#method.decrease_stake) - Decreases the worker/lead stake and returns the remainder to the worker _role_account_.
-//! - [increase_stake](./struct.Module.html#method.increase_stake) - Increases the worker stake, demands a worker origin.
+//! - [increase_stake](./struct.Module.html#method.increase_stake) - Increases the worker/lead stake.
 //!
 
 // Ensure we're `no_std` when compiling for Wasm.
@@ -51,9 +56,9 @@ use rstd::collections::btree_map::BTreeMap;
 use rstd::collections::btree_set::BTreeSet;
 use rstd::prelude::*;
 use rstd::vec::Vec;
-use sr_primitives::traits::{One, Zero};
-use srml_support::traits::{Currency, ExistenceRequirement, WithdrawReasons};
-use srml_support::{decl_event, decl_module, decl_storage, ensure};
+use sr_primitives::traits::{Bounded, One, Zero};
+use srml_support::traits::{Currency, ExistenceRequirement, Imbalance, WithdrawReasons};
+use srml_support::{decl_event, decl_module, decl_storage, ensure, print};
 use system::{ensure_root, ensure_signed};
 
 use crate::types::ExitInitiationOrigin;
@@ -62,13 +67,10 @@ use errors::WrappedError;
 
 pub use errors::Error;
 pub use types::{
-    Application, Lead, Opening, OpeningPolicyCommitment, OpeningType, RewardPolicy,
-    RoleStakeProfile, Worker,
+    Application, Opening, OpeningPolicyCommitment, OpeningType, RewardPolicy, RoleStakeProfile,
+    Worker,
 };
 
-/// Alias for the _Lead_ type
-pub type LeadOf<T> = Lead<MemberId<T>, <T as system::Trait>::AccountId>;
-
 /// Stake identifier in staking module
 pub type StakeId<T> = <T as stake::Trait>::StakeId;
 
@@ -105,36 +107,22 @@ pub type ApplicationIdToWorkerIdMap<T> = BTreeMap<ApplicationId<T>, WorkerId<T>>
 /// Type identifier for worker role, which must be same as membership actor identifier
 pub type WorkerId<T> = <T as membership::members::Trait>::ActorId;
 
+/// Alias for the application id from the hiring module.
+pub type HiringApplicationId<T> = <T as hiring::Trait>::ApplicationId;
+
 // Type simplification
 type OpeningInfo<T> = (
-    Opening<
-        <T as hiring::Trait>::OpeningId,
-        <T as system::Trait>::BlockNumber,
-        BalanceOf<T>,
-        ApplicationId<T>,
-    >,
-    hiring::Opening<
-        BalanceOf<T>,
-        <T as system::Trait>::BlockNumber,
-        <T as hiring::Trait>::ApplicationId,
-    >,
+    OpeningOf<T>,
+    hiring::Opening<BalanceOf<T>, <T as system::Trait>::BlockNumber, HiringApplicationId<T>>,
 );
 
 // Type simplification
-type ApplicationInfo<T> = (
-    Application<
-        <T as system::Trait>::AccountId,
-        OpeningId<T>,
-        MemberId<T>,
-        <T as hiring::Trait>::ApplicationId,
-    >,
-    ApplicationId<T>,
-    Opening<
-        <T as hiring::Trait>::OpeningId,
-        <T as system::Trait>::BlockNumber,
-        BalanceOf<T>,
-        ApplicationId<T>,
-    >,
+type ApplicationInfo<T> = (ApplicationOf<T>, ApplicationId<T>, OpeningOf<T>);
+
+// Type simplification
+type RewardSettings<T> = (
+    <T as minting::Trait>::MintId,
+    RewardPolicy<BalanceOfMint<T>, <T as system::Trait>::BlockNumber>,
 );
 
 // Type simplification
@@ -146,6 +134,18 @@ type WorkerOf<T> = Worker<
     MemberId<T>,
 >;
 
+// Type simplification
+type OpeningOf<T> = Opening<
+    <T as hiring::Trait>::OpeningId,
+    <T as system::Trait>::BlockNumber,
+    BalanceOf<T>,
+    ApplicationId<T>,
+>;
+
+// Type simplification
+type ApplicationOf<T> =
+    Application<<T as system::Trait>::AccountId, OpeningId<T>, MemberId<T>, HiringApplicationId<T>>;
+
 /// The _Working group_ main _Trait_
 pub trait Trait<I: Instance>:
     system::Trait
@@ -163,7 +163,6 @@ decl_event!(
     /// _Working group_ events
     pub enum Event<T, I>
     where
-        MemberId = MemberId<T>,
         WorkerId = WorkerId<T>,
         <T as membership::members::Trait>::ActorId,
         <T as system::Trait>::AccountId,
@@ -176,15 +175,12 @@ decl_event!(
     {
         /// Emits on setting the leader.
         /// Params:
-        /// - Member id of the leader.
-        /// - Role account id of the leader.
-        LeaderSet(MemberId, AccountId),
+        /// - Worker id.
+        LeaderSet(WorkerId),
 
         /// Emits on un-setting the leader.
         /// Params:
-        /// - Member id of the leader.
-        /// - Role account id of the leader.
-        LeaderUnset(MemberId, AccountId),
+        LeaderUnset(),
 
         /// Emits on terminating the worker.
         /// Params:
@@ -192,6 +188,12 @@ decl_event!(
         /// - termination rationale text
         TerminatedWorker(WorkerId, RationaleText),
 
+        /// Emits on terminating the leader.
+        /// Params:
+        /// - leader worker id.
+        /// - termination rationale text
+        TerminatedLeader(WorkerId, RationaleText),
+
         /// Emits on exiting the worker.
         /// Params:
         /// - worker id.
@@ -281,19 +283,19 @@ decl_storage! {
         pub Mint get(mint) : <T as minting::Trait>::MintId;
 
         /// The current lead.
-        pub CurrentLead get(current_lead) : Option<LeadOf<T>>;
+        pub CurrentLead get(current_lead) : Option<WorkerId<T>>;
 
         /// Next identifier value for new worker opening.
         pub NextOpeningId get(next_opening_id): OpeningId<T>;
 
         /// Maps identifier to worker opening.
-        pub OpeningById get(opening_by_id): linked_map OpeningId<T> => Opening<T::OpeningId, T::BlockNumber, BalanceOf<T>, ApplicationId<T>>;
+        pub OpeningById get(opening_by_id): linked_map OpeningId<T> => OpeningOf<T>;
 
         /// Opening human readable text length limits
         pub OpeningHumanReadableText get(opening_human_readable_text): InputValidationLengthConstraint;
 
         /// Maps identifier to worker application on opening.
-        pub ApplicationById get(application_by_id) : linked_map ApplicationId<T> => Application<T::AccountId, OpeningId<T>, T::MemberId, T::ApplicationId>;
+        pub ApplicationById get(application_by_id) : linked_map ApplicationId<T> => ApplicationOf<T>;
 
         /// Next identifier value for new worker application.
         pub NextApplicationId get(next_application_id) : ApplicationId<T>;
@@ -309,6 +311,11 @@ decl_storage! {
 
         /// Worker exit rationale text length limits.
         pub WorkerExitRationaleText get(worker_exit_rationale_text) : InputValidationLengthConstraint;
+
+        /// Map member id by hiring application id.
+        /// Required by StakingEventsHandler callback call to refund the balance on unstaking.
+        pub MemberIdByHiringApplicationId get(fn member_id_by_hiring_application_id):
+            map HiringApplicationId<T> =>  MemberId<T>;
     }
         add_extra_genesis {
         config(phantom): rstd::marker::PhantomData<I>;
@@ -337,41 +344,6 @@ decl_module! {
 
         // ****************** Roles lifecycle **********************
 
-        /// Introduce a lead when one is not currently set.
-        pub fn set_lead(origin, member_id: T::MemberId, role_account_id: T::AccountId) {
-            ensure_root(origin)?;
-
-            // Construct lead
-            let new_lead = Lead {
-                member_id,
-                role_account_id: role_account_id.clone(),
-            };
-
-            //
-            // == MUTATION SAFE ==
-            //
-
-            // Update current lead
-            <CurrentLead<T, I>>::put(new_lead);
-
-            // Trigger an event
-            Self::deposit_event(RawEvent::LeaderSet(member_id, role_account_id));
-        }
-
-        /// Evict the currently set lead
-        pub fn unset_lead(origin) {
-            ensure_root(origin)?;
-
-            let lead = Self::ensure_lead_is_set()?;
-
-            // == MUTATION SAFE ==
-
-            // Update current lead
-            <CurrentLead<T, I>>::kill();
-
-            Self::deposit_event(RawEvent::LeaderUnset(lead.member_id, lead.role_account_id));
-        }
-
         /// Update the associated role account of the active worker/lead.
         pub fn update_role_account(
             origin,
@@ -392,7 +364,7 @@ decl_module! {
 
             // Update role account
             WorkerById::<T, I>::mutate(worker_id, |worker| {
-                worker.role_account = new_role_account_id.clone()
+                worker.role_account_id = new_role_account_id.clone()
             });
 
             // Trigger event
@@ -430,13 +402,14 @@ decl_module! {
         }
 
         /// Update the reward amount associated with a set reward relationship for the active worker.
+        /// Require signed leader origin or the root (to update leader reward amount).
         pub fn update_reward_amount(
             origin,
             worker_id: WorkerId<T>,
             new_amount: BalanceOfMint<T>
         ) {
-            // Ensure lead is set and is origin signer
-            Self::ensure_origin_is_active_leader(origin)?;
+            // Ensure lead is set and is origin signer or it is the council.
+            Self::ensure_origin_for_leader(origin, worker_id)?;
 
             // Ensuring worker actually exists
             let worker = Self::ensure_worker_exists(&worker_id)?;
@@ -484,36 +457,44 @@ decl_module! {
         }
 
         /// Terminate the active worker by the lead.
+        /// Require signed leader origin or the root (to terminate the leader role).
         pub fn terminate_role(
             origin,
             worker_id: WorkerId<T>,
-            rationale_text: Vec<u8>
+            rationale_text: Vec<u8>,
+            slash_stake: bool,
         ) {
+            let (cloned_origin1, cloned_origin2) = common::origin::double_origin::<T>(origin);
 
-            // Ensure lead is set and is origin signer
-            Self::ensure_origin_is_active_leader(origin)?;
+            // Ensure lead is set or it is the council terminating the leader.
+            let exit_origin = Self::ensure_origin_for_leader(cloned_origin1, worker_id)?;
 
-            // Ensuring worker actually exists
+            // Ensuring worker actually exists.
             let worker = Self::ensure_worker_exists(&worker_id)?;
 
-            // Ensure rationale text is valid
+            // Ensure rationale text is valid.
             Self::ensure_worker_exit_rationale_text_is_valid(&rationale_text)?;
 
             //
             // == MUTATION SAFE ==
             //
 
+            if slash_stake {
+                Self::slash_stake(cloned_origin2, worker_id, BalanceOf::<T>::max_value())?;
+            }
+
             Self::deactivate_worker(
                 &worker_id,
                 &worker,
-                &ExitInitiationOrigin::Lead,
+                &exit_origin,
                 &rationale_text
             )?;
         }
 
         // ****************** Hiring flow **********************
 
-         /// Add an opening for a worker role.
+        /// Add an opening for a worker role.
+        /// Require signed leader origin or the root (to add opening for the leader position).
         pub fn add_opening(
             origin,
             activate_at: hiring::ActivateOpeningAt<T::BlockNumber>,
@@ -550,7 +531,7 @@ decl_module! {
 
             // Create and add worker opening.
             let new_opening_by_id = Opening::<OpeningId<T>, T::BlockNumber, BalanceOf<T>, ApplicationId<T>> {
-                opening_id,
+                hiring_opening_id: opening_id,
                 applications: BTreeSet::new(),
                 policy_commitment,
                 opening_type,
@@ -566,6 +547,7 @@ decl_module! {
         }
 
         /// Begin accepting worker applications to an opening that is active.
+        /// Require signed leader origin or the root (to accept applications for the leader position).
         pub fn accept_applications(origin, opening_id: OpeningId<T>)  {
             // Ensure opening exists in this working group
             // NB: Even though call to hiring module will have implicit check for
@@ -583,7 +565,7 @@ decl_module! {
             //
 
             ensure_on_wrapped_error!(
-                hiring::Module::<T>::begin_accepting_applications(opening.opening_id)
+                hiring::Module::<T>::begin_accepting_applications(opening.hiring_opening_id)
             )?;
 
 
@@ -596,7 +578,7 @@ decl_module! {
             origin,
             member_id: T::MemberId,
             opening_id: OpeningId<T>,
-            role_account: T::AccountId,
+            role_account_id: T::AccountId,
             opt_role_stake_balance: Option<BalanceOf<T>>,
             opt_application_stake_balance: Option<BalanceOf<T>>,
             human_readable_text: Vec<u8>
@@ -620,15 +602,19 @@ decl_module! {
             // Ensure that there is sufficient balance to cover stake proposed
             Self::ensure_can_make_stake_imbalance(
                 vec![&opt_role_stake_balance, &opt_application_stake_balance],
-                &source_account)
-                .map_err(|_| Error::InsufficientBalanceToApply)?;
+                &source_account
+            )
+            .map_err(|_| Error::InsufficientBalanceToApply)?;
 
             // Ensure application text is valid
             Self::ensure_application_text_is_valid(&human_readable_text)?;
 
             // Ensure application can actually be added
             ensure_on_wrapped_error!(
-                hiring::Module::<T>::ensure_can_add_application(opening.opening_id, opt_role_stake_balance, opt_application_stake_balance)
+                hiring::Module::<T>::ensure_can_add_application(
+                    opening.hiring_opening_id,
+                    opt_role_stake_balance,
+                    opt_application_stake_balance)
             )?;
 
             // Ensure member does not have an active application to this opening
@@ -646,23 +632,25 @@ decl_module! {
             let opt_application_stake_imbalance = Self::make_stake_opt_imbalance(&opt_application_stake_balance, &source_account);
 
             // Call hiring module to add application
-            let add_application_result = hiring::Module::<T>::add_application(
-                opening.opening_id,
-                opt_role_stake_imbalance,
-                opt_application_stake_imbalance,
-                human_readable_text
-            );
+            let add_application = ensure_on_wrapped_error!(
+                    hiring::Module::<T>::add_application(
+                    opening.hiring_opening_id,
+                    opt_role_stake_imbalance,
+                    opt_application_stake_imbalance,
+                    human_readable_text
+                )
+            )?;
 
-            // Has to hold
-            assert!(add_application_result.is_ok());
+            let hiring_application_id = add_application.application_id_added;
 
-            let application_id = add_application_result.unwrap().application_id_added;
+            // Save member id to refund the stakes. This piece of date should outlive the 'worker'.
+            <MemberIdByHiringApplicationId<T, I>>::insert(hiring_application_id, member_id);
 
             // Get id of new worker/lead application
             let new_application_id = NextApplicationId::<T, I>::get();
 
             // Make worker/lead application
-            let application = Application::new(&role_account, &opening_id, &member_id, &application_id);
+            let application = Application::new(&role_account_id, &opening_id, &member_id, &hiring_application_id);
 
             // Store application
             ApplicationById::<T, I>::insert(new_application_id, application);
@@ -692,7 +680,7 @@ decl_module! {
 
             // Ensure that signer is applicant role account
             ensure!(
-                signer_account == application.role_account,
+                signer_account == application.role_account_id,
                 Error::OriginIsNotApplicant
             );
 
@@ -705,12 +693,11 @@ decl_module! {
             ensure_on_wrapped_error!(
                 hiring::Module::<T>::deactive_application(
                     application.hiring_application_id,
-                    opening.policy_commitment.exit_worker_role_application_stake_unstaking_period,
-                    opening.policy_commitment.exit_worker_role_stake_unstaking_period
+                    opening.policy_commitment.exit_role_application_stake_unstaking_period,
+                    opening.policy_commitment.exit_role_stake_unstaking_period
                 )
             )?;
 
-
             // Trigger event
             Self::deposit_event(RawEvent::ApplicationWithdrawn(application_id));
         }
@@ -727,13 +714,13 @@ decl_module! {
             // Ensuring worker application actually exists
             let (application, _, opening) = Self::ensure_application_exists(&application_id)?;
 
-            // Attempt to deactivate application
-            // NB: Combined ensure check and mutation in hiring module
+            // Attempt to deactivate application.
+            // NB: Combined ensure check and mutation in hiring module.
             ensure_on_wrapped_error!(
                 hiring::Module::<T>::deactive_application(
                     application.hiring_application_id,
                     opening.policy_commitment.terminate_application_stake_unstaking_period,
-                    opening.policy_commitment.terminate_worker_role_stake_unstaking_period
+                    opening.policy_commitment.terminate_role_stake_unstaking_period
                 )
             )?;
 
@@ -746,6 +733,7 @@ decl_module! {
         }
 
         /// Begin reviewing, and therefore not accepting new applications.
+        /// Require signed leader origin or the root (to begin review applications for the leader position).
         pub fn begin_applicant_review(origin, opening_id: OpeningId<T>) {
             // Ensure opening exists
             // NB: Even though call to hiring modul will have implicit check for
@@ -759,10 +747,10 @@ decl_module! {
             // == MUTATION SAFE ==
             //
 
-            // Attempt to begin review of applications
-            // NB: Combined ensure check and mutation in hiring module
+            // Attempt to begin review of applications.
+            // NB: Combined ensure check and mutation in hiring module.
             ensure_on_wrapped_error!(
-                hiring::Module::<T>::begin_review(opening.opening_id)
+                hiring::Module::<T>::begin_review(opening.hiring_opening_id)
                 )?;
 
             // Trigger event
@@ -770,6 +758,7 @@ decl_module! {
         }
 
         /// Fill opening for worker/lead.
+        /// Require signed leader origin or the root (to fill opening for the leader position).
         pub fn fill_opening(
             origin,
             opening_id: OpeningId<T>,
@@ -781,14 +770,17 @@ decl_module! {
 
             Self::ensure_origin_for_opening_type(origin, opening.opening_type)?;
 
+            // Cannot hire a lead when another leader exists.
+            if matches!(opening.opening_type, OpeningType::Leader) {
+                ensure!(!<CurrentLead<T,I>>::exists(), Error::CannotHireLeaderWhenLeaderExists);
+            }
+
             // Ensure a mint exists if lead is providing a reward for positions being filled
             let create_reward_settings = if let Some(policy) = reward_policy {
+
                 // A reward will need to be created so ensure our configured mint exists
                 let mint_id = Self::mint();
 
-                // Technically this is a bug-check and should not be here.
-                ensure!(<minting::Mints<T>>::exists(mint_id), Error::FillOpeningMintDoesNotExist);
-
                 // Make sure valid parameters are selected for next payment at block number
                 ensure!(policy.next_payment_at_block > <system::Module<T>>::block_number(),
                     Error::FillOpeningInvalidNextPaymentBlock);
@@ -826,10 +818,15 @@ decl_module! {
                                             .map(|(successful_application, _, _)| successful_application.hiring_application_id)
                                             .collect::<BTreeSet<_>>();
 
+            // Check for a single application for a leader.
+            if matches!(opening.opening_type, OpeningType::Leader) {
+                ensure!(successful_application_ids.len() == 1, Error::CannotHireMultipleLeaders);
+            }
+
             // NB: Combined ensure check and mutation in hiring module
             ensure_on_wrapped_error!(
                 hiring::Module::<T>::fill_opening(
-                    opening.opening_id,
+                    opening.hiring_opening_id,
                     successful_application_ids,
                     opening.policy_commitment.fill_opening_successful_applicant_application_stake_unstaking_period,
                     opening.policy_commitment.fill_opening_failed_applicant_application_stake_unstaking_period,
@@ -841,74 +838,12 @@ decl_module! {
             // == MUTATION SAFE ==
             //
 
-            let mut application_id_to_worker_id = BTreeMap::new();
-
-            successful_iter
-            .clone()
-            .for_each(|(successful_application, id, _)| {
-                // Create a reward relationship
-                let reward_relationship = if let Some((mint_id, checked_policy)) = create_reward_settings.clone() {
-
-                    // Create a new recipient for the new relationship
-                    let recipient = <recurringrewards::Module<T>>::add_recipient();
-
-                    // member must exist, since it was checked that it can enter the role
-                    let member_profile = <membership::members::Module<T>>::member_profile(successful_application.member_id).unwrap();
-
-                    // rewards are deposited in the member's root account
-                    let reward_destination_account = member_profile.root_account;
-
-                    // values have been checked so this should not fail!
-                    let relationship_id = <recurringrewards::Module<T>>::add_reward_relationship(
-                        mint_id,
-                        recipient,
-                        reward_destination_account,
-                        checked_policy.amount_per_payout,
-                        checked_policy.next_payment_at_block,
-                        checked_policy.payout_interval,
-                    ).expect("Failed to create reward relationship!");
-
-                    Some(relationship_id)
-                } else {
-                    None
-                };
-
-                // Get possible stake for role
-                let application = hiring::ApplicationById::<T>::get(successful_application.hiring_application_id);
-
-                // Staking profile for worker
-                let stake_profile =
-                    if let Some(ref stake_id) = application.active_role_staking_id {
-                        Some(
-                            RoleStakeProfile::new(
-                                stake_id,
-                                &opening.policy_commitment.terminate_worker_role_stake_unstaking_period,
-                                &opening.policy_commitment.exit_worker_role_stake_unstaking_period
-                            )
-                        )
-                    } else {
-                        None
-                    };
-
-                // Get worker id
-                let new_worker_id = <NextWorkerId<T, I>>::get();
-
-                // Construct worker
-                let worker = Worker::new(
-                    &successful_application.member_id,
-                    &successful_application.role_account,
-                    &reward_relationship,
-                    &stake_profile,
-                );
-
-                // Store a worker
-                <WorkerById<T, I>>::insert(new_worker_id, worker);
-
-                // Update next worker id
-                <NextWorkerId<T, I>>::mutate(|id| *id += <WorkerId<T> as One>::one());
-
-                application_id_to_worker_id.insert(id, new_worker_id);
-            });
+            // Process successful applications
+            let application_id_to_worker_id = Self::fulfill_successful_applications(
+                &opening,
+                create_reward_settings,
+                successful_iter.collect()
+            );
 
             // Trigger event
             Self::deposit_event(RawEvent::OpeningFilled(opening_id, application_id_to_worker_id));
@@ -918,9 +853,12 @@ decl_module! {
 
         /// Slashes the worker stake, demands a leader origin. No limits, no actions on zero stake.
         /// If slashing balance greater than the existing stake - stake is slashed to zero.
+        /// Require signed leader origin or the root (to slash the leader stake).
         pub fn slash_stake(origin, worker_id: WorkerId<T>, balance: BalanceOf<T>) {
-            Self::ensure_origin_is_active_leader(origin)?;
+            // Ensure lead is set or it is the council terminating the leader.
+            Self::ensure_origin_for_leader(origin, worker_id)?;
 
+            // Ensuring worker actually exists.
             let worker = Self::ensure_worker_exists(&worker_id)?;
 
             ensure!(balance != <BalanceOf<T>>::zero(), Error::StakeBalanceCannotBeZero);
@@ -943,10 +881,12 @@ decl_module! {
             Self::deposit_event(RawEvent::StakeSlashed(worker_id));
         }
 
-        /// Decreases the worker/lead stake and returns the remainder to the worker role_account,
-        /// demands a leader origin. Can be decreased to zero, no actions on zero stake.
+        /// Decreases the worker/lead stake and returns the remainder to the worker role_account_id.
+        /// Can be decreased to zero, no actions on zero stake.
+        /// Require signed leader origin or the root (to decrease the leader stake).
         pub fn decrease_stake(origin, worker_id: WorkerId<T>, balance: BalanceOf<T>) {
-            Self::ensure_origin_is_active_leader(origin)?;
+            // Ensure lead is set or it is the council terminating the leader.
+            Self::ensure_origin_for_leader(origin, worker_id)?;
 
             let worker = Self::ensure_worker_exists(&worker_id)?;
 
@@ -962,7 +902,7 @@ decl_module! {
             ensure_on_wrapped_error!(
                 <stake::Module<T>>::decrease_stake_to_account(
                     &stake_profile.stake_id,
-                    &worker.role_account,
+                    &worker.role_account_id,
                     balance
                 )
             )?;
@@ -971,7 +911,7 @@ decl_module! {
         }
 
         /// Increases the worker/lead stake, demands a worker origin. Transfers tokens from the worker
-        /// role_account to the stake. No limits on the stake.
+        /// role_account_id to the stake. No limits on the stake.
         pub fn increase_stake(origin, worker_id: WorkerId<T>, balance: BalanceOf<T>) {
             // Checks worker origin, worker existence
             let worker = Self::ensure_worker_signed(origin, &worker_id)?;
@@ -988,7 +928,7 @@ decl_module! {
             ensure_on_wrapped_error!(
                 <stake::Module<T>>::increase_stake_from_account(
                     &stake_profile.stake_id,
-                    &worker.role_account,
+                    &worker.role_account_id,
                     balance
                 )
             )?;
@@ -996,7 +936,7 @@ decl_module! {
             Self::deposit_event(RawEvent::StakeIncreased(worker_id));
         }
 
-        /// Sets the capacity to enable working group budget.
+        /// Sets the capacity to enable working group budget. Requires root origin.
         pub fn set_mint_capacity(
             origin,
             new_capacity: minting::BalanceOf<T>
@@ -1028,31 +968,9 @@ decl_module! {
     }
 }
 
-impl<T: Trait<I>, I: Instance> Module<T, I> {
-    /// Checks that provided lead account id belongs to the current working group leader
-    pub fn ensure_is_lead_account(lead_account_id: T::AccountId) -> Result<(), Error> {
-        let lead = <CurrentLead<T, I>>::get();
-
-        if let Some(lead) = lead {
-            if lead.role_account_id != lead_account_id {
-                return Err(Error::IsNotLeadAccount);
-            }
-        } else {
-            return Err(Error::CurrentLeadNotSet);
-        }
-
-        Ok(())
-    }
-
-    /// Returns all existing worker id list.
-    pub fn get_all_worker_ids() -> Vec<WorkerId<T>> {
-        <WorkerById<T, I>>::enumerate()
-            .map(|(worker_id, _)| worker_id)
-            .collect()
-
-        //TODO not lead
-    }
+// ****************** Ensures **********************
 
+impl<T: Trait<I>, I: Instance> Module<T, I> {
     fn ensure_origin_for_opening_type(
         origin: T::Origin,
         opening_type: OpeningType,
@@ -1069,16 +987,46 @@ impl<T: Trait<I>, I: Instance> Module<T, I> {
         }
     }
 
-    fn ensure_lead_is_set() -> Result<Lead<MemberId<T>, T::AccountId>, Error> {
-        let lead = <CurrentLead<T, I>>::get();
+    fn ensure_origin_for_leader(
+        origin: T::Origin,
+        worker_id: WorkerId<T>,
+    ) -> Result<ExitInitiationOrigin, Error> {
+        let leader_worker_id = Self::ensure_lead_is_set()?;
 
-        if let Some(lead) = lead {
-            Ok(lead)
+        let (worker_opening_type, exit_origin) = if leader_worker_id == worker_id {
+            (OpeningType::Leader, ExitInitiationOrigin::Sudo)
+        } else {
+            (OpeningType::Worker, ExitInitiationOrigin::Lead)
+        };
+
+        Self::ensure_origin_for_opening_type(origin, worker_opening_type)?;
+
+        Ok(exit_origin)
+    }
+
+    fn ensure_lead_is_set() -> Result<WorkerId<T>, Error> {
+        let leader_worker_id = Self::current_lead();
+
+        if let Some(leader_worker_id) = leader_worker_id {
+            Ok(leader_worker_id)
         } else {
             Err(Error::CurrentLeadNotSet)
         }
     }
 
+    // Checks that provided lead account id belongs to the current working group leader
+    fn ensure_is_lead_account(lead_account_id: T::AccountId) -> Result<(), Error> {
+        let leader_worker_id = Self::ensure_lead_is_set()?;
+
+        let leader = Self::worker_by_id(leader_worker_id);
+
+        if leader.role_account_id != lead_account_id {
+            return Err(Error::IsNotLeadAccount);
+        }
+
+        Ok(())
+    }
+
     fn ensure_opening_human_readable_text_is_valid(text: &[u8]) -> Result<(), Error> {
         <OpeningHumanReadableText<I>>::get()
             .ensure_valid(
@@ -1089,7 +1037,7 @@ impl<T: Trait<I>, I: Instance> Module<T, I> {
             .map_err(|e| e.into())
     }
 
-    // Ensures origin is signed by the leader.
+    /// Ensures origin is signed by the leader.
     pub fn ensure_origin_is_active_leader(origin: T::Origin) -> Result<(), Error> {
         // Ensure is signed
         let signer = ensure_signed(origin)?;
@@ -1105,31 +1053,11 @@ impl<T: Trait<I>, I: Instance> Module<T, I> {
 
         let opening = OpeningById::<T, I>::get(opening_id);
 
-        let hiring_opening = hiring::OpeningById::<T>::get(opening.opening_id);
+        let hiring_opening = hiring::OpeningById::<T>::get(opening.hiring_opening_id);
 
         Ok((opening, hiring_opening))
     }
 
-    fn make_stake_opt_imbalance(
-        opt_balance: &Option<BalanceOf<T>>,
-        source_account: &T::AccountId,
-    ) -> Option<NegativeImbalance<T>> {
-        if let Some(balance) = opt_balance {
-            let withdraw_result = CurrencyOf::<T>::withdraw(
-                source_account,
-                *balance,
-                WithdrawReasons::all(),
-                ExistenceRequirement::AllowDeath,
-            );
-
-            assert!(withdraw_result.is_ok());
-
-            withdraw_result.ok()
-        } else {
-            None
-        }
-    }
-
     fn ensure_member_has_no_active_application_on_opening(
         applications: ApplicationIdSet<T>,
         member_id: T::MemberId,
@@ -1163,7 +1091,7 @@ impl<T: Trait<I>, I: Instance> Module<T, I> {
 
     // CRITICAL:
     // https://github.com/Joystream/substrate-runtime-joystream/issues/92
-    // This assumes that ensure_can_withdraw can be don
+    // This assumes that ensure_can_withdraw can be done
     // for a sum of balance that later will be actually withdrawn
     // using individual terms in that sum.
     // This needs to be fully checked across all possibly scenarios
@@ -1231,7 +1159,7 @@ impl<T: Trait<I>, I: Instance> Module<T, I> {
 
         // Ensure that signer is actually role account of worker
         ensure!(
-            signer_account == worker.role_account,
+            signer_account == worker.role_account_id,
             Error::SignerIsNotWorkerRoleAccount
         );
 
@@ -1259,6 +1187,101 @@ impl<T: Trait<I>, I: Instance> Module<T, I> {
         }
     }
 
+    fn ensure_worker_exit_rationale_text_is_valid(text: &[u8]) -> Result<(), Error> {
+        Self::worker_exit_rationale_text()
+            .ensure_valid(
+                text.len(),
+                Error::WorkerExitRationaleTextTooShort.into(),
+                Error::WorkerExitRationaleTextTooLong.into(),
+            )
+            .map_err(|e| e.into())
+    }
+}
+
+/// Creates default text constraint.
+pub fn default_text_constraint() -> InputValidationLengthConstraint {
+    InputValidationLengthConstraint::new(1, 1024)
+}
+
+impl<T: Trait<I>, I: Instance> Module<T, I> {
+    /// Callback from StakingEventsHandler. Refunds unstaked imbalance back to the source account.
+    pub fn refund_working_group_stake(
+        stake_id: StakeId<T>,
+        imbalance: NegativeImbalance<T>,
+    ) -> NegativeImbalance<T> {
+        if !hiring::ApplicationIdByStakingId::<T>::exists(stake_id) {
+            print("Working group broken invariant: no stake id in the hiring module.");
+            return imbalance;
+        }
+
+        let hiring_application_id = hiring::ApplicationIdByStakingId::<T>::get(stake_id);
+
+        if !MemberIdByHiringApplicationId::<T, I>::exists(hiring_application_id) {
+            // Stake is not related to the working group module.
+            return imbalance;
+        }
+
+        let member_id = Module::<T, I>::member_id_by_hiring_application_id(hiring_application_id);
+
+        if let Some(member_profile) = membership::members::MemberProfile::<T>::get(member_id) {
+            let refunding_result = CurrencyOf::<T>::resolve_into_existing(
+                &member_profile.controller_account,
+                imbalance,
+            );
+
+            if refunding_result.is_err() {
+                print("Working group broken invariant: cannot refund.");
+                // cannot return imbalance here, because of possible double spending.
+                return <NegativeImbalance<T>>::zero();
+            }
+        } else {
+            print("Working group broken invariant: no member profile.");
+            return imbalance;
+        }
+
+        <NegativeImbalance<T>>::zero()
+    }
+
+    /// Returns all existing worker id list excluding the current leader worker id.
+    pub fn get_regular_worker_ids() -> Vec<WorkerId<T>> {
+        let lead_worker_id = Self::current_lead();
+
+        <WorkerById<T, I>>::enumerate()
+            .filter_map(|(worker_id, _)| {
+                // Filter the leader worker id if the leader is set.
+                lead_worker_id
+                    .clone()
+                    .map_or(Some(worker_id), |lead_worker_id| {
+                        if worker_id == lead_worker_id {
+                            None
+                        } else {
+                            Some(worker_id)
+                        }
+                    })
+            })
+            .collect()
+    }
+
+    fn make_stake_opt_imbalance(
+        opt_balance: &Option<BalanceOf<T>>,
+        source_account: &T::AccountId,
+    ) -> Option<NegativeImbalance<T>> {
+        if let Some(balance) = opt_balance {
+            let withdraw_result = CurrencyOf::<T>::withdraw(
+                source_account,
+                *balance,
+                WithdrawReasons::all(),
+                ExistenceRequirement::AllowDeath,
+            );
+
+            assert!(withdraw_result.is_ok());
+
+            withdraw_result.ok()
+        } else {
+            None
+        }
+    }
+
     fn deactivate_worker(
         worker_id: &WorkerId<T>,
         worker: &WorkerOf<T>,
@@ -1278,6 +1301,7 @@ impl<T: Trait<I>, I: Instance> Module<T, I> {
             // Determine unstaking period based on who initiated deactivation
             let unstaking_period = match exit_initiation_origin {
                 ExitInitiationOrigin::Lead => stake_profile.termination_unstaking_period,
+                ExitInitiationOrigin::Sudo => stake_profile.termination_unstaking_period,
                 ExitInitiationOrigin::Worker => stake_profile.exit_unstaking_period,
             };
 
@@ -1288,6 +1312,15 @@ impl<T: Trait<I>, I: Instance> Module<T, I> {
             ))?;
         }
 
+        // Unset lead if the leader is leaving.
+        let leader_worker_id = <CurrentLead<T, I>>::get();
+        if let Some(leader_worker_id) = leader_worker_id {
+            if leader_worker_id == *worker_id {
+                Self::unset_lead();
+            }
+        }
+
+        // Remove the worker from the storage.
         WorkerById::<T, I>::remove(worker_id);
 
         // Trigger the event
@@ -1298,6 +1331,9 @@ impl<T: Trait<I>, I: Instance> Module<T, I> {
             ExitInitiationOrigin::Worker => {
                 RawEvent::WorkerExited(*worker_id, rationale_text.to_vec())
             }
+            ExitInitiationOrigin::Sudo => {
+                RawEvent::TerminatedLeader(*worker_id, rationale_text.to_vec())
+            }
         };
 
         Self::deposit_event(event);
@@ -1305,16 +1341,6 @@ impl<T: Trait<I>, I: Instance> Module<T, I> {
         Ok(())
     }
 
-    fn ensure_worker_exit_rationale_text_is_valid(text: &[u8]) -> Result<(), Error> {
-        Self::worker_exit_rationale_text()
-            .ensure_valid(
-                text.len(),
-                Error::WorkerExitRationaleTextTooShort.into(),
-                Error::WorkerExitRationaleTextTooLong.into(),
-            )
-            .map_err(|e| e.into())
-    }
-
     fn initialize_working_group(
         opening_human_readable_text_constraint: InputValidationLengthConstraint,
         worker_application_human_readable_text_constraint: InputValidationLengthConstraint,
@@ -1337,9 +1363,111 @@ impl<T: Trait<I>, I: Instance> Module<T, I> {
         );
         <WorkerExitRationaleText<I>>::put(worker_exit_rationale_text_constraint);
     }
-}
 
-/// Creates default text constraint.
-pub fn default_text_constraint() -> InputValidationLengthConstraint {
-    InputValidationLengthConstraint::new(1, 1024)
+    // Set worker id as a leader id.
+    pub(crate) fn set_lead(worker_id: WorkerId<T>) {
+        // Update current lead
+        <CurrentLead<T, I>>::put(worker_id);
+
+        // Trigger an event
+        Self::deposit_event(RawEvent::LeaderSet(worker_id));
+    }
+
+    // Evict the currently set lead.
+    pub(crate) fn unset_lead() {
+        if Self::ensure_lead_is_set().is_ok() {
+            // Update current lead
+            <CurrentLead<T, I>>::kill();
+
+            Self::deposit_event(RawEvent::LeaderUnset());
+        }
+    }
+
+    // Processes successful application during the fill_opening().
+    fn fulfill_successful_applications(
+        opening: &OpeningOf<T>,
+        reward_settings: Option<RewardSettings<T>>,
+        successful_applications_info: Vec<ApplicationInfo<T>>,
+    ) -> BTreeMap<ApplicationId<T>, WorkerId<T>> {
+        let mut application_id_to_worker_id = BTreeMap::new();
+
+        successful_applications_info
+            .iter()
+            .for_each(|(successful_application, id, _)| {
+                // Create a reward relationship.
+                let reward_relationship = if let Some((mint_id, checked_policy)) =
+                    reward_settings.clone()
+                {
+                    // Create a new recipient for the new relationship.
+                    let recipient = <recurringrewards::Module<T>>::add_recipient();
+
+                    // Member must exist, since it was checked that it can enter the role.
+                    let member_profile = <membership::members::Module<T>>::member_profile(
+                        successful_application.member_id,
+                    )
+                    .unwrap();
+
+                    // Rewards are deposited in the member's root account.
+                    let reward_destination_account = member_profile.root_account;
+
+                    // Values have been checked so this should not fail!
+                    let relationship_id = <recurringrewards::Module<T>>::add_reward_relationship(
+                        mint_id,
+                        recipient,
+                        reward_destination_account,
+                        checked_policy.amount_per_payout,
+                        checked_policy.next_payment_at_block,
+                        checked_policy.payout_interval,
+                    )
+                    .expect("Failed to create reward relationship!");
+
+                    Some(relationship_id)
+                } else {
+                    None
+                };
+
+                // Get possible stake for role
+                let application =
+                    hiring::ApplicationById::<T>::get(successful_application.hiring_application_id);
+
+                // Staking profile for worker
+                let stake_profile = if let Some(ref stake_id) = application.active_role_staking_id {
+                    Some(RoleStakeProfile::new(
+                        stake_id,
+                        &opening
+                            .policy_commitment
+                            .terminate_role_stake_unstaking_period,
+                        &opening.policy_commitment.exit_role_stake_unstaking_period,
+                    ))
+                } else {
+                    None
+                };
+
+                // Get worker id
+                let new_worker_id = <NextWorkerId<T, I>>::get();
+
+                // Construct worker
+                let worker = Worker::new(
+                    &successful_application.member_id,
+                    &successful_application.role_account_id,
+                    &reward_relationship,
+                    &stake_profile,
+                );
+
+                // Store a worker
+                <WorkerById<T, I>>::insert(new_worker_id, worker);
+
+                // Update next worker id
+                <NextWorkerId<T, I>>::mutate(|id| *id += <WorkerId<T> as One>::one());
+
+                application_id_to_worker_id.insert(*id, new_worker_id);
+
+                // Sets a leader on successful opening when opening is for leader.
+                if matches!(opening.opening_type, OpeningType::Leader) {
+                    Self::set_lead(new_worker_id);
+                }
+            });
+
+        application_id_to_worker_id
+    }
 }

+ 241 - 157
runtime-modules/working-group/src/tests/fixtures.rs

@@ -1,6 +1,7 @@
 use super::mock::{
     Balances, Membership, System, Test, TestEvent, TestWorkingGroup, TestWorkingGroupInstance,
 };
+use crate::tests::fill_worker_position;
 use crate::types::{
     Application, Opening, OpeningPolicyCommitment, OpeningType, RewardPolicy, RoleStakeProfile,
     Worker,
@@ -22,7 +23,7 @@ pub struct IncreaseWorkerStakeFixture {
 impl IncreaseWorkerStakeFixture {
     pub fn default_for_worker_id(worker_id: u64) -> Self {
         let account_id = 1;
-        IncreaseWorkerStakeFixture {
+        Self {
             origin: RawOrigin::Signed(1),
             worker_id,
             balance: 10,
@@ -30,11 +31,11 @@ impl IncreaseWorkerStakeFixture {
         }
     }
     pub fn with_origin(self, origin: RawOrigin<u64>) -> Self {
-        IncreaseWorkerStakeFixture { origin, ..self }
+        Self { origin, ..self }
     }
 
     pub fn with_balance(self, balance: u64) -> Self {
-        IncreaseWorkerStakeFixture { balance, ..self }
+        Self { balance, ..self }
     }
 
     pub fn call_and_assert(&self, expected_result: Result<(), Error>) {
@@ -71,11 +72,12 @@ pub struct TerminateWorkerRoleFixture {
     origin: RawOrigin<u64>,
     text: Vec<u8>,
     constraint: InputValidationLengthConstraint,
+    slash_stake: bool,
 }
 
 impl TerminateWorkerRoleFixture {
     pub fn default_for_worker_id(worker_id: u64) -> Self {
-        TerminateWorkerRoleFixture {
+        Self {
             worker_id,
             origin: RawOrigin::Signed(1),
             text: b"rationale_text".to_vec(),
@@ -83,14 +85,22 @@ impl TerminateWorkerRoleFixture {
                 min: 1,
                 max_min_diff: 20,
             },
+            slash_stake: false,
         }
     }
     pub fn with_origin(self, origin: RawOrigin<u64>) -> Self {
-        TerminateWorkerRoleFixture { origin, ..self }
+        Self { origin, ..self }
     }
 
     pub fn with_text(self, text: Vec<u8>) -> Self {
-        TerminateWorkerRoleFixture { text, ..self }
+        Self { text, ..self }
+    }
+
+    pub fn with_slashing(self) -> Self {
+        Self {
+            slash_stake: true,
+            ..self
+        }
     }
 
     pub fn call_and_assert(&self, expected_result: Result<(), Error>) {
@@ -100,6 +110,7 @@ impl TerminateWorkerRoleFixture {
             self.origin.clone().into(),
             self.worker_id,
             self.text.clone(),
+            self.slash_stake,
         );
         assert_eq!(actual_result, expected_result);
 
@@ -120,13 +131,13 @@ pub(crate) struct LeaveWorkerRoleFixture {
 
 impl LeaveWorkerRoleFixture {
     pub fn default_for_worker_id(worker_id: u64) -> Self {
-        LeaveWorkerRoleFixture {
+        Self {
             worker_id,
             origin: RawOrigin::Signed(1),
         }
     }
     pub fn with_origin(self, origin: RawOrigin<u64>) -> Self {
-        LeaveWorkerRoleFixture { origin, ..self }
+        Self { origin, ..self }
     }
 
     pub fn call_and_assert(&self, expected_result: Result<(), Error>) {
@@ -152,54 +163,72 @@ pub struct UpdateWorkerRewardAmountFixture {
 
 impl UpdateWorkerRewardAmountFixture {
     pub fn default_for_worker_id(worker_id: u64) -> Self {
-        UpdateWorkerRewardAmountFixture {
+        let lead_account_id = get_current_lead_account_id();
+
+        Self {
             worker_id,
-            amount: 100,
-            origin: RawOrigin::Signed(1),
+            amount: 120,
+            origin: RawOrigin::Signed(lead_account_id),
         }
     }
     pub fn with_origin(self, origin: RawOrigin<u64>) -> Self {
-        UpdateWorkerRewardAmountFixture { origin, ..self }
+        Self { origin, ..self }
     }
 
     pub fn call_and_assert(&self, expected_result: Result<(), Error>) {
-        assert_eq!(
-            TestWorkingGroup::update_reward_amount(
-                self.origin.clone().into(),
-                self.worker_id,
-                self.amount
-            ),
-            expected_result
+        let actual_result = TestWorkingGroup::update_reward_amount(
+            self.origin.clone().into(),
+            self.worker_id,
+            self.amount,
         );
+
+        assert_eq!(actual_result.clone(), expected_result);
+
+        if actual_result.is_ok() {
+            let worker = TestWorkingGroup::worker_by_id(self.worker_id);
+            let relationship_id = worker.reward_relationship.unwrap();
+
+            let relationship = recurringrewards::RewardRelationships::<Test>::get(relationship_id);
+
+            assert_eq!(relationship.amount_per_payout, self.amount);
+        }
     }
 }
 pub struct UpdateWorkerRewardAccountFixture {
     worker_id: u64,
-    new_role_account_id: u64,
+    new_reward_account_id: u64,
     origin: RawOrigin<u64>,
 }
 
 impl UpdateWorkerRewardAccountFixture {
-    pub fn default_with_ids(worker_id: u64, new_role_account_id: u64) -> Self {
-        UpdateWorkerRewardAccountFixture {
+    pub fn default_with_ids(worker_id: u64, new_reward_account_id: u64) -> Self {
+        Self {
             worker_id,
-            new_role_account_id,
+            new_reward_account_id,
             origin: RawOrigin::Signed(1),
         }
     }
     pub fn with_origin(self, origin: RawOrigin<u64>) -> Self {
-        UpdateWorkerRewardAccountFixture { origin, ..self }
+        Self { origin, ..self }
     }
 
     pub fn call_and_assert(&self, expected_result: Result<(), Error>) {
-        assert_eq!(
-            TestWorkingGroup::update_reward_account(
-                self.origin.clone().into(),
-                self.worker_id,
-                self.new_role_account_id
-            ),
-            expected_result
+        let actual_result = TestWorkingGroup::update_reward_account(
+            self.origin.clone().into(),
+            self.worker_id,
+            self.new_reward_account_id,
         );
+
+        assert_eq!(actual_result.clone(), expected_result);
+
+        if actual_result.is_ok() {
+            let worker = TestWorkingGroup::worker_by_id(self.worker_id);
+            let relationship_id = worker.reward_relationship.unwrap();
+
+            let relationship = recurringrewards::RewardRelationships::<Test>::get(relationship_id);
+
+            assert_eq!(relationship.account, self.new_reward_account_id);
+        }
     }
 }
 
@@ -211,14 +240,14 @@ pub struct UpdateWorkerRoleAccountFixture {
 
 impl UpdateWorkerRoleAccountFixture {
     pub fn default_with_ids(worker_id: u64, new_role_account_id: u64) -> Self {
-        UpdateWorkerRoleAccountFixture {
+        Self {
             worker_id,
             new_role_account_id,
             origin: RawOrigin::Signed(1),
         }
     }
     pub fn with_origin(self, origin: RawOrigin<u64>) -> Self {
-        UpdateWorkerRoleAccountFixture { origin, ..self }
+        Self { origin, ..self }
     }
 
     pub fn call_and_assert(&self, expected_result: Result<(), Error>) {
@@ -232,22 +261,11 @@ impl UpdateWorkerRoleAccountFixture {
         if actual_result.is_ok() {
             let worker = TestWorkingGroup::worker_by_id(self.worker_id);
 
-            assert_eq!(worker.role_account, self.new_role_account_id);
+            assert_eq!(worker.role_account_id, self.new_role_account_id);
         }
     }
 }
 
-pub struct UnsetLeadFixture;
-impl UnsetLeadFixture {
-    pub fn unset_lead() {
-        assert_eq!(TestWorkingGroup::unset_lead(RawOrigin::Root.into()), Ok(()));
-    }
-
-    pub fn call_and_assert(origin: RawOrigin<u64>, expected_result: Result<(), Error>) {
-        assert_eq!(TestWorkingGroup::unset_lead(origin.into()), expected_result);
-    }
-}
-
 pub fn set_mint_id(mint_id: u64) {
     <crate::Mint<Test, TestWorkingGroupInstance>>::put(mint_id);
 }
@@ -260,7 +278,7 @@ pub struct FillWorkerOpeningFixture {
     origin: RawOrigin<u64>,
     opening_id: u64,
     successful_application_ids: BTreeSet<u64>,
-    role_account: u64,
+    role_account_id: u64,
     reward_policy: Option<RewardPolicy<u64, u64>>,
 }
 
@@ -268,34 +286,41 @@ impl FillWorkerOpeningFixture {
     pub fn default_for_ids(opening_id: u64, application_ids: Vec<u64>) -> Self {
         let application_ids: BTreeSet<u64> = application_ids.iter().map(|x| *x).collect();
 
-        FillWorkerOpeningFixture {
+        Self {
             origin: RawOrigin::Signed(1),
             opening_id,
             successful_application_ids: application_ids,
-            role_account: 1,
+            role_account_id: 1,
             reward_policy: None,
         }
     }
 
     pub fn with_origin(self, origin: RawOrigin<u64>) -> Self {
-        FillWorkerOpeningFixture { origin, ..self }
+        Self { origin, ..self }
     }
 
     pub fn with_reward_policy(self, reward_policy: RewardPolicy<u64, u64>) -> Self {
-        FillWorkerOpeningFixture {
+        Self {
             reward_policy: Some(reward_policy),
             ..self
         }
     }
 
-    pub fn call_and_assert(&self, expected_result: Result<(), Error>) -> u64 {
+    pub fn call(&self) -> Result<u64, Error> {
         let saved_worker_next_id = TestWorkingGroup::next_worker_id();
-        let actual_result = TestWorkingGroup::fill_opening(
+        TestWorkingGroup::fill_opening(
             self.origin.clone().into(),
             self.opening_id,
             self.successful_application_ids.clone(),
             self.reward_policy.clone(),
-        );
+        )?;
+
+        Ok(saved_worker_next_id)
+    }
+
+    pub fn call_and_assert(&self, expected_result: Result<(), Error>) -> u64 {
+        let saved_worker_next_id = TestWorkingGroup::next_worker_id();
+        let actual_result = self.call().map(|_| ());
         assert_eq!(actual_result.clone(), expected_result);
 
         if actual_result.is_ok() {
@@ -315,10 +340,8 @@ impl FillWorkerOpeningFixture {
                     &stake_id,
                     &opening
                         .policy_commitment
-                        .terminate_worker_role_stake_unstaking_period,
-                    &opening
-                        .policy_commitment
-                        .exit_worker_role_stake_unstaking_period,
+                        .terminate_role_stake_unstaking_period,
+                    &opening.policy_commitment.exit_role_stake_unstaking_period,
                 ))
             } else {
                 None
@@ -327,7 +350,7 @@ impl FillWorkerOpeningFixture {
 
             let expected_worker = Worker {
                 member_id: 1,
-                role_account: self.role_account,
+                role_account_id: self.role_account_id,
                 reward_relationship,
                 role_stake_profile,
             };
@@ -348,13 +371,13 @@ pub struct BeginReviewWorkerApplicationsFixture {
 
 impl BeginReviewWorkerApplicationsFixture {
     pub fn default_for_opening_id(opening_id: u64) -> Self {
-        BeginReviewWorkerApplicationsFixture {
+        Self {
             origin: RawOrigin::Signed(1),
             opening_id,
         }
     }
     pub fn with_origin(self, origin: RawOrigin<u64>) -> Self {
-        BeginReviewWorkerApplicationsFixture { origin, ..self }
+        Self { origin, ..self }
     }
     pub fn call_and_assert(&self, expected_result: Result<(), Error>) {
         let actual_result =
@@ -370,16 +393,16 @@ pub struct TerminateApplicationFixture {
 
 impl TerminateApplicationFixture {
     pub fn with_signer(self, account_id: u64) -> Self {
-        TerminateApplicationFixture {
+        Self {
             origin: RawOrigin::Signed(account_id),
             ..self
         }
     }
     pub fn with_origin(self, origin: RawOrigin<u64>) -> Self {
-        TerminateApplicationFixture { origin, ..self }
+        Self { origin, ..self }
     }
     pub fn default_for_application_id(application_id: u64) -> Self {
-        TerminateApplicationFixture {
+        Self {
             origin: RawOrigin::Signed(1),
             worker_application_id: application_id,
         }
@@ -399,16 +422,16 @@ pub struct WithdrawApplicationFixture {
 
 impl WithdrawApplicationFixture {
     pub fn with_signer(self, account_id: u64) -> Self {
-        WithdrawApplicationFixture {
+        Self {
             origin: RawOrigin::Signed(account_id),
             ..self
         }
     }
     pub fn with_origin(self, origin: RawOrigin<u64>) -> Self {
-        WithdrawApplicationFixture { origin, ..self }
+        Self { origin, ..self }
     }
     pub fn default_for_application_id(application_id: u64) -> Self {
-        WithdrawApplicationFixture {
+        Self {
             origin: RawOrigin::Signed(1),
             worker_application_id: application_id,
         }
@@ -427,6 +450,10 @@ pub fn increase_total_balance_issuance_using_account_id(account_id: u64, balance
         <Balances as srml_support::traits::Currency<u64>>::deposit_creating(&account_id, balance);
 }
 
+pub fn get_balance(account_id: u64) -> u64 {
+    <super::mock::Balances as srml_support::traits::Currency<u64>>::total_balance(&account_id)
+}
+
 pub fn setup_members(count: u8) {
     let authority_account_id = 1;
     Membership::set_screening_authority(RawOrigin::Root.into(), authority_account_id).unwrap();
@@ -451,7 +478,7 @@ pub struct ApplyOnWorkerOpeningFixture {
     origin: RawOrigin<u64>,
     member_id: u64,
     worker_opening_id: u64,
-    role_account: u64,
+    role_account_id: u64,
     opt_role_stake_balance: Option<u64>,
     opt_application_stake_balance: Option<u64>,
     human_readable_text: Vec<u8>,
@@ -459,49 +486,64 @@ pub struct ApplyOnWorkerOpeningFixture {
 
 impl ApplyOnWorkerOpeningFixture {
     pub fn with_text(self, text: Vec<u8>) -> Self {
-        ApplyOnWorkerOpeningFixture {
+        Self {
             human_readable_text: text,
             ..self
         }
     }
 
-    pub fn with_role_stake(self, stake: u64) -> Self {
-        ApplyOnWorkerOpeningFixture {
-            opt_role_stake_balance: Some(stake),
+    pub fn with_origin(self, origin: RawOrigin<u64>, member_id: u64) -> Self {
+        Self {
+            origin,
+            member_id,
+            ..self
+        }
+    }
+
+    pub fn with_role_stake(self, stake: Option<u64>) -> Self {
+        Self {
+            opt_role_stake_balance: stake,
             ..self
         }
     }
 
     pub fn with_application_stake(self, stake: u64) -> Self {
-        ApplyOnWorkerOpeningFixture {
+        Self {
             opt_application_stake_balance: Some(stake),
             ..self
         }
     }
 
     pub fn default_for_opening_id(opening_id: u64) -> Self {
-        ApplyOnWorkerOpeningFixture {
+        Self {
             origin: RawOrigin::Signed(1),
             member_id: 1,
             worker_opening_id: opening_id,
-            role_account: 1,
+            role_account_id: 1,
             opt_role_stake_balance: None,
             opt_application_stake_balance: None,
             human_readable_text: b"human_text".to_vec(),
         }
     }
 
-    pub fn call_and_assert(&self, expected_result: Result<(), Error>) -> u64 {
+    pub fn call(&self) -> Result<u64, Error> {
         let saved_application_next_id = TestWorkingGroup::next_application_id();
-        let actual_result = TestWorkingGroup::apply_on_opening(
+        TestWorkingGroup::apply_on_opening(
             self.origin.clone().into(),
             self.member_id,
             self.worker_opening_id,
-            self.role_account,
+            self.role_account_id,
             self.opt_role_stake_balance,
             self.opt_application_stake_balance,
             self.human_readable_text.clone(),
-        );
+        )?;
+
+        Ok(saved_application_next_id)
+    }
+    pub fn call_and_assert(&self, expected_result: Result<(), Error>) -> u64 {
+        let saved_application_next_id = TestWorkingGroup::next_application_id();
+
+        let actual_result = self.call().map(|_| ());
         assert_eq!(actual_result.clone(), expected_result);
 
         if actual_result.is_ok() {
@@ -514,7 +556,7 @@ impl ApplyOnWorkerOpeningFixture {
             let actual_application = TestWorkingGroup::application_by_id(application_id);
 
             let expected_application = Application {
-                role_account: self.role_account,
+                role_account_id: self.role_account_id,
                 opening_id: self.worker_opening_id,
                 member_id: self.member_id,
                 hiring_application_id: application_id,
@@ -537,7 +579,7 @@ pub struct AcceptWorkerApplicationsFixture {
 
 impl AcceptWorkerApplicationsFixture {
     pub fn default_for_opening_id(opening_id: u64) -> Self {
-        AcceptWorkerApplicationsFixture {
+        Self {
             origin: RawOrigin::Signed(1),
             opening_id,
         }
@@ -550,27 +592,82 @@ impl AcceptWorkerApplicationsFixture {
     }
 }
 
-pub struct SetLeadFixture;
+pub struct SetLeadFixture {
+    pub member_id: u64,
+    pub role_account_id: u64,
+    pub worker_id: u64,
+}
+impl Default for SetLeadFixture {
+    fn default() -> Self {
+        SetLeadFixture {
+            member_id: 1,
+            role_account_id: 1,
+            worker_id: 1,
+        }
+    }
+}
+
 impl SetLeadFixture {
-    pub fn set_lead(lead_account_id: u64) {
-        assert_eq!(
-            TestWorkingGroup::set_lead(RawOrigin::Root.into(), 1, lead_account_id),
-            Ok(())
-        );
+    pub fn unset_lead() {
+        TestWorkingGroup::unset_lead();
     }
 
-    pub fn call_and_assert(
-        origin: RawOrigin<u64>,
-        member_id: u64,
-        account_id: u64,
-        expected_result: Result<(), Error>,
-    ) {
-        assert_eq!(
-            TestWorkingGroup::set_lead(origin.into(), member_id, account_id),
-            expected_result
-        );
+    pub fn set_lead(self) {
+        TestWorkingGroup::set_lead(self.worker_id);
+    }
+    pub fn set_lead_with_ids(member_id: u64, role_account_id: u64, worker_id: u64) {
+        Self {
+            member_id,
+            role_account_id,
+            worker_id,
+        }
+        .set_lead();
+    }
+}
+
+pub struct HireLeadFixture {
+    setup_environment: bool,
+    stake: Option<u64>,
+    reward_policy: Option<RewardPolicy<u64, u64>>,
+}
+
+impl Default for HireLeadFixture {
+    fn default() -> Self {
+        Self {
+            setup_environment: true,
+            stake: None,
+            reward_policy: None,
+        }
     }
 }
+impl HireLeadFixture {
+    pub fn with_stake(self, stake: u64) -> Self {
+        Self {
+            stake: Some(stake),
+            ..self
+        }
+    }
+    pub fn with_reward_policy(self, reward_policy: RewardPolicy<u64, u64>) -> Self {
+        Self {
+            reward_policy: Some(reward_policy),
+            ..self
+        }
+    }
+
+    pub fn hire_lead(self) -> u64 {
+        fill_worker_position(
+            self.reward_policy,
+            self.stake,
+            self.setup_environment,
+            OpeningType::Leader,
+            Some(b"leader".to_vec()),
+        )
+    }
+}
+
+pub fn get_worker_by_id(worker_id: u64) -> Worker<u64, u64, u64, u64, u64> {
+    TestWorkingGroup::worker_by_id(worker_id)
+}
 
 pub struct AddWorkerOpeningFixture {
     origin: RawOrigin<u64>,
@@ -582,7 +679,7 @@ pub struct AddWorkerOpeningFixture {
 
 impl Default for AddWorkerOpeningFixture {
     fn default() -> Self {
-        AddWorkerOpeningFixture {
+        Self {
             origin: RawOrigin::Signed(1),
             activate_at: hiring::ActivateOpeningAt::CurrentBlock,
             commitment: <OpeningPolicyCommitment<u64, u64>>::default(),
@@ -597,7 +694,7 @@ impl AddWorkerOpeningFixture {
         self,
         policy_commitment: OpeningPolicyCommitment<u64, u64>,
     ) -> Self {
-        AddWorkerOpeningFixture {
+        Self {
             commitment: policy_commitment,
             ..self
         }
@@ -605,13 +702,8 @@ impl AddWorkerOpeningFixture {
 
     pub fn call_and_assert(&self, expected_result: Result<(), Error>) -> u64 {
         let saved_opening_next_id = TestWorkingGroup::next_opening_id();
-        let actual_result = TestWorkingGroup::add_opening(
-            self.origin.clone().into(),
-            self.activate_at.clone(),
-            self.commitment.clone(),
-            self.human_readable_text.clone(),
-            self.opening_type,
-        );
+        let actual_result = self.call().map(|_| ());
+
         assert_eq!(actual_result.clone(), expected_result);
 
         if actual_result.is_ok() {
@@ -624,7 +716,7 @@ impl AddWorkerOpeningFixture {
             let actual_opening = TestWorkingGroup::opening_by_id(opening_id);
 
             let expected_opening = Opening::<u64, u64, u64, u64> {
-                opening_id,
+                hiring_opening_id: opening_id,
                 applications: BTreeSet::new(),
                 policy_commitment: self.commitment.clone(),
                 opening_type: self.opening_type,
@@ -636,26 +728,39 @@ impl AddWorkerOpeningFixture {
         saved_opening_next_id
     }
 
+    pub fn call(&self) -> Result<u64, Error> {
+        let saved_opening_next_id = TestWorkingGroup::next_opening_id();
+        TestWorkingGroup::add_opening(
+            self.origin.clone().into(),
+            self.activate_at.clone(),
+            self.commitment.clone(),
+            self.human_readable_text.clone(),
+            self.opening_type,
+        )?;
+
+        Ok(saved_opening_next_id)
+    }
+
     pub fn with_text(self, text: Vec<u8>) -> Self {
-        AddWorkerOpeningFixture {
+        Self {
             human_readable_text: text,
             ..self
         }
     }
 
     pub fn with_opening_type(self, opening_type: OpeningType) -> Self {
-        AddWorkerOpeningFixture {
+        Self {
             opening_type,
             ..self
         }
     }
 
     pub fn with_origin(self, origin: RawOrigin<u64>) -> Self {
-        AddWorkerOpeningFixture { origin, ..self }
+        Self { origin, ..self }
     }
 
     pub fn with_activate_at(self, activate_at: hiring::ActivateOpeningAt<u64>) -> Self {
-        AddWorkerOpeningFixture {
+        Self {
             activate_at,
             ..self
         }
@@ -664,43 +769,6 @@ impl AddWorkerOpeningFixture {
 
 pub struct EventFixture;
 impl EventFixture {
-    pub fn assert_crate_events(
-        expected_raw_events: Vec<
-            RawEvent<
-                u64,
-                u64,
-                u64,
-                u64,
-                u64,
-                u64,
-                std::collections::BTreeMap<u64, u64>,
-                Vec<u8>,
-                u64,
-                u64,
-                TestWorkingGroupInstance,
-            >,
-        >,
-    ) {
-        let converted_events = expected_raw_events
-            .iter()
-            .map(|ev| TestEvent::working_group_TestWorkingGroupInstance(ev.clone()))
-            .collect::<Vec<TestEvent>>();
-
-        Self::assert_global_events(converted_events)
-    }
-    pub fn assert_global_events(expected_raw_events: Vec<TestEvent>) {
-        let expected_events = expected_raw_events
-            .iter()
-            .map(|ev| EventRecord {
-                phase: Phase::ApplyExtrinsic(0),
-                event: ev.clone(),
-                topics: vec![],
-            })
-            .collect::<Vec<EventRecord<_, _>>>();
-
-        assert_eq!(System::events(), expected_events);
-    }
-
     pub fn assert_last_crate_event(
         expected_raw_event: RawEvent<
             u64,
@@ -708,7 +776,6 @@ impl EventFixture {
             u64,
             u64,
             u64,
-            u64,
             std::collections::BTreeMap<u64, u64>,
             Vec<u8>,
             u64,
@@ -742,19 +809,22 @@ pub struct DecreaseWorkerStakeFixture {
 impl DecreaseWorkerStakeFixture {
     pub fn default_for_worker_id(worker_id: u64) -> Self {
         let account_id = 1;
-        DecreaseWorkerStakeFixture {
-            origin: RawOrigin::Signed(account_id),
+
+        let lead_account_id = get_current_lead_account_id();
+
+        Self {
+            origin: RawOrigin::Signed(lead_account_id),
             worker_id,
             balance: 10,
             account_id,
         }
     }
     pub fn with_origin(self, origin: RawOrigin<u64>) -> Self {
-        DecreaseWorkerStakeFixture { origin, ..self }
+        Self { origin, ..self }
     }
 
     pub fn with_balance(self, balance: u64) -> Self {
-        DecreaseWorkerStakeFixture { balance, ..self }
+        Self { balance, ..self }
     }
 
     pub fn call_and_assert(&self, expected_result: Result<(), Error>) {
@@ -786,7 +856,7 @@ impl DecreaseWorkerStakeFixture {
     }
 }
 
-fn get_stake_balance(stake: stake::Stake<u64, u64, u64>) -> u64 {
+pub(crate) fn get_stake_balance(stake: stake::Stake<u64, u64, u64>) -> u64 {
     if let stake::StakingStatus::Staked(stake) = stake.staking_status {
         return stake.staked_amount;
     }
@@ -794,6 +864,17 @@ fn get_stake_balance(stake: stake::Stake<u64, u64, u64>) -> u64 {
     panic!("Not staked.");
 }
 
+fn get_current_lead_account_id() -> u64 {
+    let leader_worker_id = TestWorkingGroup::current_lead();
+
+    if let Some(leader_worker_id) = leader_worker_id {
+        let leader = TestWorkingGroup::worker_by_id(leader_worker_id);
+        leader.role_account_id
+    } else {
+        0 // return invalid lead_account_id for testing
+    }
+}
+
 pub struct SlashWorkerStakeFixture {
     origin: RawOrigin<u64>,
     worker_id: u64,
@@ -804,19 +885,22 @@ pub struct SlashWorkerStakeFixture {
 impl SlashWorkerStakeFixture {
     pub fn default_for_worker_id(worker_id: u64) -> Self {
         let account_id = 1;
-        SlashWorkerStakeFixture {
-            origin: RawOrigin::Signed(account_id),
+
+        let lead_account_id = get_current_lead_account_id();
+
+        Self {
+            origin: RawOrigin::Signed(lead_account_id),
             worker_id,
             balance: 10,
             account_id,
         }
     }
     pub fn with_origin(self, origin: RawOrigin<u64>) -> Self {
-        SlashWorkerStakeFixture { origin, ..self }
+        Self { origin, ..self }
     }
 
     pub fn with_balance(self, balance: u64) -> Self {
-        SlashWorkerStakeFixture { balance, ..self }
+        Self { balance, ..self }
     }
 
     pub fn call_and_assert(&self, expected_result: Result<(), Error>) {

+ 200 - 0
runtime-modules/working-group/src/tests/hiring_workflow.rs

@@ -0,0 +1,200 @@
+use crate::tests::fixtures::{
+    create_mint, increase_total_balance_issuance_using_account_id, set_mint_id, setup_members,
+    AddWorkerOpeningFixture, ApplyOnWorkerOpeningFixture, BeginReviewWorkerApplicationsFixture,
+    FillWorkerOpeningFixture, SetLeadFixture,
+};
+use crate::tests::mock::TestWorkingGroup;
+use crate::Error;
+use crate::{OpeningPolicyCommitment, OpeningType, RewardPolicy};
+use system::RawOrigin;
+
+#[derive(Clone)]
+struct HiringWorkflowApplication {
+    stake: Option<u64>,
+    worker_handle: Vec<u8>,
+    origin: RawOrigin<u64>,
+    member_id: u64,
+}
+
+pub struct HiringWorkflow {
+    opening_type: OpeningType,
+    expected_result: Result<(), Error>,
+    role_stake: Option<u64>,
+    applications: Vec<HiringWorkflowApplication>,
+    setup_environment: bool,
+    reward_policy: Option<RewardPolicy<u64, u64>>,
+}
+
+impl Default for HiringWorkflow {
+    fn default() -> Self {
+        Self {
+            opening_type: OpeningType::Worker,
+            expected_result: Ok(()),
+            role_stake: None,
+            applications: Vec::new(),
+            setup_environment: true,
+            reward_policy: None,
+        }
+    }
+}
+
+impl HiringWorkflow {
+    pub fn expect(self, result: Result<(), Error>) -> Self {
+        Self {
+            expected_result: result,
+            ..self
+        }
+    }
+
+    pub fn disable_setup_environment(self) -> Self {
+        Self {
+            setup_environment: false,
+            ..self
+        }
+    }
+
+    pub fn with_setup_environment(self, setup_environment: bool) -> Self {
+        Self {
+            setup_environment,
+            ..self
+        }
+    }
+
+    pub fn with_opening_type(self, opening_type: OpeningType) -> Self {
+        Self {
+            opening_type,
+            ..self
+        }
+    }
+
+    pub fn with_role_stake(self, role_stake: Option<u64>) -> Self {
+        Self { role_stake, ..self }
+    }
+
+    pub fn with_reward_policy(self, reward_policy: Option<RewardPolicy<u64, u64>>) -> Self {
+        Self {
+            reward_policy,
+            ..self
+        }
+    }
+
+    pub fn add_default_application(self) -> Self {
+        let worker_handle = b"default worker handle".to_vec();
+
+        self.add_application(worker_handle)
+    }
+
+    pub fn add_application(self, worker_handle: Vec<u8>) -> Self {
+        self.add_application_with_origin(worker_handle, RawOrigin::Signed(1), 1)
+    }
+
+    pub fn add_application_with_origin(
+        self,
+        worker_handle: Vec<u8>,
+        origin: RawOrigin<u64>,
+        member_id: u64,
+    ) -> Self {
+        let mut applications = self.applications;
+        applications.push(HiringWorkflowApplication {
+            worker_handle,
+            stake: self.role_stake.clone(),
+            origin,
+            member_id,
+        });
+
+        Self {
+            applications,
+            ..self
+        }
+    }
+
+    fn setup_environment(&self) {
+        if matches!(self.opening_type, OpeningType::Worker) {
+            SetLeadFixture::default().set_lead();
+        }
+        increase_total_balance_issuance_using_account_id(1, 10000);
+        setup_members(3);
+        set_mint_id(create_mint());
+    }
+
+    pub fn execute(&self) -> Option<u64> {
+        if self.setup_environment {
+            self.setup_environment()
+        }
+
+        let result = self.fill_worker_position();
+
+        let check_result = result.clone().map(|_| ());
+
+        assert_eq!(check_result, self.expected_result);
+
+        result.ok()
+    }
+
+    fn fill_worker_position(&self) -> Result<u64, Error> {
+        let origin = match self.opening_type {
+            OpeningType::Leader => RawOrigin::Root,
+            OpeningType::Worker => {
+                let leader_worker_id = TestWorkingGroup::current_lead().unwrap();
+                let leader = TestWorkingGroup::worker_by_id(leader_worker_id);
+                let lead_account_id = leader.role_account_id;
+
+                RawOrigin::Signed(lead_account_id)
+            }
+        };
+
+        // create the opening
+        let mut add_worker_opening_fixture = AddWorkerOpeningFixture::default()
+            .with_opening_type(self.opening_type)
+            .with_origin(origin.clone());
+
+        if let Some(stake) = self.role_stake.clone() {
+            add_worker_opening_fixture =
+                add_worker_opening_fixture.with_policy_commitment(OpeningPolicyCommitment {
+                    role_staking_policy: Some(hiring::StakingPolicy {
+                        amount: stake,
+                        amount_mode: hiring::StakingAmountLimitMode::AtLeast,
+                        crowded_out_unstaking_period_length: None,
+                        review_period_expired_unstaking_period_length: None,
+                    }),
+                    ..OpeningPolicyCommitment::default()
+                });
+        }
+
+        let opening_id = add_worker_opening_fixture.call()?;
+
+        // Fill applications.
+        let mut application_ids = Vec::new();
+        for application in self.applications.clone() {
+            let apply_on_worker_opening_fixture =
+                ApplyOnWorkerOpeningFixture::default_for_opening_id(opening_id)
+                    .with_text(application.worker_handle)
+                    .with_origin(application.origin, application.member_id)
+                    .with_role_stake(self.role_stake);
+
+            let application_id = apply_on_worker_opening_fixture.call()?;
+            application_ids.push(application_id);
+        }
+
+        // begin application review
+
+        let begin_review_worker_applications_fixture =
+            BeginReviewWorkerApplicationsFixture::default_for_opening_id(opening_id)
+                .with_origin(origin.clone());
+        begin_review_worker_applications_fixture.call_and_assert(Ok(()));
+
+        // fill opening
+        let mut fill_worker_opening_fixture =
+            FillWorkerOpeningFixture::default_for_ids(opening_id, application_ids)
+                .with_origin(origin.clone());
+
+        if let Some(reward_policy) = self.reward_policy.clone() {
+            fill_worker_opening_fixture =
+                fill_worker_opening_fixture.with_reward_policy(reward_policy);
+        }
+
+        let worker_id = fill_worker_opening_fixture.call()?;
+
+        Ok(worker_id)
+    }
+}

+ 50 - 3
runtime-modules/working-group/src/tests/mock.rs

@@ -1,4 +1,4 @@
-use crate::{Module, Trait};
+use crate::{BalanceOf, Module, NegativeImbalance, Trait};
 use common::constraints::InputValidationLengthConstraint;
 use primitives::H256;
 use sr_primitives::{
@@ -6,7 +6,10 @@ use sr_primitives::{
     traits::{BlakeTwo256, IdentityLookup},
     Perbill,
 };
-use srml_support::{impl_outer_event, impl_outer_origin, parameter_types};
+use srml_support::{
+    impl_outer_event, impl_outer_origin, parameter_types, StorageLinkedMap, StorageMap,
+};
+use std::marker::PhantomData;
 
 impl_outer_origin! {
         pub enum Origin for Test {}
@@ -79,7 +82,7 @@ impl minting::Trait for Test {
 impl stake::Trait for Test {
     type Currency = Balances;
     type StakePoolId = StakePoolId;
-    type StakingEventsHandler = ();
+    type StakingEventsHandler = StakingEventsHandler<Test>;
     type StakeId = u64;
     type SlashId = u64;
 }
@@ -163,3 +166,47 @@ pub fn build_test_externalities() -> runtime_io::TestExternalities {
 
     t.into()
 }
+
+pub struct StakingEventsHandler<T> {
+    pub marker: PhantomData<T>,
+}
+
+impl<T: stake::Trait + crate::Trait<TestWorkingGroupInstance>> stake::StakingEventsHandler<T>
+    for StakingEventsHandler<T>
+{
+    /// Unstake remaining sum back to the source_account_id
+    fn unstaked(
+        stake_id: &<T as stake::Trait>::StakeId,
+        _unstaked_amount: BalanceOf<T>,
+        remaining_imbalance: NegativeImbalance<T>,
+    ) -> NegativeImbalance<T> {
+        // Stake not related to a staked role managed by the hiring module.
+        if !hiring::ApplicationIdByStakingId::<T>::exists(*stake_id) {
+            return remaining_imbalance;
+        }
+
+        let hiring_application_id = hiring::ApplicationIdByStakingId::<T>::get(*stake_id);
+
+        if crate::MemberIdByHiringApplicationId::<T, TestWorkingGroupInstance>::exists(
+            hiring_application_id,
+        ) {
+            return <crate::Module<T, TestWorkingGroupInstance>>::refund_working_group_stake(
+                *stake_id,
+                remaining_imbalance,
+            );
+        }
+
+        remaining_imbalance
+    }
+
+    /// Empty handler for slashing
+    fn slashed(
+        _: &<T as stake::Trait>::StakeId,
+        _: Option<<T as stake::Trait>::SlashId>,
+        _: BalanceOf<T>,
+        _: BalanceOf<T>,
+        remaining_imbalance: NegativeImbalance<T>,
+    ) -> NegativeImbalance<T> {
+        remaining_imbalance
+    }
+}

Різницю між файлами не показано, бо вона завелика
+ 221 - 271
runtime-modules/working-group/src/tests/mod.rs


+ 29 - 35
runtime-modules/working-group/src/types.rs

@@ -6,7 +6,7 @@ use rstd::collections::btree_set::BTreeSet;
 #[cfg(feature = "std")]
 use serde::{Deserialize, Serialize};
 
-/// Terms for slashings applied to a given role.
+/// Terms for slashes applied to a given role.
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
 #[derive(Encode, Decode, Debug, Clone, PartialEq, Eq)]
 pub struct SlashableTerms {
@@ -28,7 +28,7 @@ pub enum SlashingTerms {
     Slashable(SlashableTerms),
 }
 
-/// Must be default constructible because it indirectly is a value in a storage map.
+/// Must be default constructable because it indirectly is a value in a storage map.
 /// ***SHOULD NEVER ACTUALLY GET CALLED, IS REQUIRED TO DUE BAD STORAGE MODEL IN SUBSTRATE***
 impl Default for SlashingTerms {
     fn default() -> Self {
@@ -69,14 +69,14 @@ pub struct OpeningPolicyCommitment<BlockNumber, Balance> {
     /// When terminating a worker: unstaking period for application stake.
     pub terminate_application_stake_unstaking_period: Option<BlockNumber>,
 
-    /// When terminating a worker: unstaking period for role stake.
-    pub terminate_worker_role_stake_unstaking_period: Option<BlockNumber>,
+    /// When terminating a worke/leadr: unstaking period for role stake.
+    pub terminate_role_stake_unstaking_period: Option<BlockNumber>,
 
-    /// When a worker exists: unstaking period for application stake.
-    pub exit_worker_role_application_stake_unstaking_period: Option<BlockNumber>,
+    /// When a worker/lead exists: unstaking period for application stake.
+    pub exit_role_application_stake_unstaking_period: Option<BlockNumber>,
 
-    /// When a worker exists: unstaking period for role stake.
-    pub exit_worker_role_stake_unstaking_period: Option<BlockNumber>,
+    /// When a worker/lead exists: unstaking period for role stake.
+    pub exit_role_stake_unstaking_period: Option<BlockNumber>,
 }
 
 /// An opening for a worker or lead role.
@@ -84,7 +84,7 @@ pub struct OpeningPolicyCommitment<BlockNumber, Balance> {
 #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
 pub struct Opening<OpeningId, BlockNumber, Balance, WorkerApplicationId: core::cmp::Ord> {
     /// Identifier for underlying opening in the hiring module.
-    pub opening_id: OpeningId,
+    pub hiring_opening_id: OpeningId,
 
     /// Set of identifiers for all worker applications ever added.
     pub applications: BTreeSet<WorkerApplicationId>,
@@ -115,23 +115,12 @@ impl Default for OpeningType {
     }
 }
 
-/// Working group lead: worker lead.
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
-pub struct Lead<MemberId, AccountId> {
-    /// Member id of the leader.
-    pub member_id: MemberId,
-
-    /// Account used to authenticate in this role.
-    pub role_account_id: AccountId,
-}
-
-/// An application for the worker role.
+/// An application for the worker/lead role.
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
 #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
 pub struct Application<AccountId, OpeningId, MemberId, ApplicationId> {
     /// Account used to authenticate in this role.
-    pub role_account: AccountId,
+    pub role_account_id: AccountId,
 
     /// Opening on which this application applies.
     pub opening_id: OpeningId,
@@ -148,13 +137,13 @@ impl<AccountId: Clone, OpeningId: Clone, MemberId: Clone, ApplicationId: Clone>
 {
     /// Creates a new worker application using parameters.
     pub fn new(
-        role_account: &AccountId,
+        role_account_id: &AccountId,
         opening_id: &OpeningId,
         member_id: &MemberId,
         application_id: &ApplicationId,
     ) -> Self {
         Application {
-            role_account: role_account.clone(),
+            role_account_id: role_account_id.clone(),
             opening_id: opening_id.clone(),
             member_id: member_id.clone(),
             hiring_application_id: application_id.clone(),
@@ -162,7 +151,7 @@ impl<AccountId: Clone, OpeningId: Clone, MemberId: Clone, ApplicationId: Clone>
     }
 }
 
-/// Role stake information for a worker/ledd.
+/// Role stake information for a worker/lead.
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
 #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
 pub struct RoleStakeProfile<StakeId, BlockNumber> {
@@ -177,7 +166,7 @@ pub struct RoleStakeProfile<StakeId, BlockNumber> {
 }
 
 impl<StakeId: Clone, BlockNumber: Clone> RoleStakeProfile<StakeId, BlockNumber> {
-    /// Creates a new worker role stake profile using stake parameters.
+    /// Creates a new worker/lead role stake profile using stake parameters.
     pub fn new(
         stake_id: &StakeId,
         termination_unstaking_period: &Option<BlockNumber>,
@@ -196,13 +185,16 @@ impl<StakeId: Clone, BlockNumber: Clone> RoleStakeProfile<StakeId, BlockNumber>
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
 #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
 pub struct Worker<AccountId, RewardRelationshipId, StakeId, BlockNumber, MemberId> {
-    /// Member id related to the worker
+    /// Member id related to the worker/lead.
     pub member_id: MemberId,
+
     /// Account used to authenticate in this role.
-    pub role_account: AccountId,
+    pub role_account_id: AccountId,
+
     /// Whether the role has recurring reward, and if so an identifier for this.
     pub reward_relationship: Option<RewardRelationshipId>,
-    /// When set, describes role stake of worker.
+
+    /// When set, describes role stake of the worker/lead.
     pub role_stake_profile: Option<RoleStakeProfile<StakeId, BlockNumber>>,
 }
 
@@ -217,29 +209,31 @@ impl<
     /// Creates a new _Worker_ using parameters.
     pub fn new(
         member_id: &MemberId,
-        role_account: &AccountId,
+        role_account_id: &AccountId,
         reward_relationship: &Option<RewardRelationshipId>,
         role_stake_profile: &Option<RoleStakeProfile<StakeId, BlockNumber>>,
     ) -> Self {
         Worker {
             member_id: member_id.clone(),
-            role_account: role_account.clone(),
+            role_account_id: role_account_id.clone(),
             reward_relationship: reward_relationship.clone(),
             role_stake_profile: role_stake_profile.clone(),
         }
     }
 }
 
-/// Origin of exit initiation on behalf of a curator.'
+/// Origin of exit initiation.
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
 #[derive(Encode, Decode, Debug, Clone, PartialEq)]
 pub enum ExitInitiationOrigin {
-    /// Lead is origin.
+    /// Lead fires the worker.
     Lead,
 
-    /// The curator exiting is the origin.
+    /// Worker leaves the position.
     Worker,
-    //TODO
+
+    /// Council fires the leader.
+    Sudo,
 }
 
 /// The recurring reward if any to be assigned to an actor when filling in the position.

+ 1 - 2
runtime/Cargo.toml

@@ -1,11 +1,10 @@
-
 [package]
 authors = ['Joystream contributors']
 edition = '2018'
 name = 'joystream-node-runtime'
 # Follow convention: https://github.com/Joystream/substrate-runtime-joystream/issues/1
 # {Authoring}.{Spec}.{Impl} of the RuntimeVersion
-version = '6.16.0'
+version = '6.17.0'
 
 [features]
 default = ['std']

+ 141 - 0
runtime/src/integration/content_working_group.rs

@@ -0,0 +1,141 @@
+use crate::{AccountId, Credential, Runtime};
+
+use srml_support::traits::{Currency, Imbalance};
+use srml_support::{parameter_types, StorageLinkedMap, StorageMap};
+
+parameter_types! {
+    pub const CurrentLeadCredential: Credential = 0;
+    pub const AnyActiveCuratorCredential: Credential = 1;
+    pub const AnyActiveChannelOwnerCredential: Credential = 2;
+    pub const PrincipalIdMappingStartsAtCredential: Credential = 1000;
+}
+
+pub struct ContentWorkingGroupCredentials {}
+impl versioned_store_permissions::CredentialChecker<Runtime> for ContentWorkingGroupCredentials {
+    fn account_has_credential(
+        account: &AccountId,
+        credential: <Runtime as versioned_store_permissions::Trait>::Credential,
+    ) -> bool {
+        match credential {
+            // Credentials from 0..999 represents groups or more complex requirements
+            // Current Lead if set
+            credential if credential == CurrentLeadCredential::get() => {
+                match <content_working_group::Module<Runtime>>::ensure_lead_is_set() {
+                    Ok((_, lead)) => lead.role_account == *account,
+                    _ => false,
+                }
+            }
+            // Any Active Curator
+            credential if credential == AnyActiveCuratorCredential::get() => {
+                // Look for a Curator with a matching role account
+                for (_principal_id, principal) in
+                    <content_working_group::PrincipalById<Runtime>>::enumerate()
+                {
+                    if let content_working_group::Principal::Curator(curator_id) = principal {
+                        let curator =
+                            <content_working_group::CuratorById<Runtime>>::get(curator_id);
+                        if curator.role_account == *account
+                            && curator.stage == content_working_group::CuratorRoleStage::Active
+                        {
+                            return true;
+                        }
+                    }
+                }
+
+                false
+            }
+            // Any Active Channel Owner
+            credential if credential == AnyActiveChannelOwnerCredential::get() => {
+                // Look for a ChannelOwner with a matching role account
+                for (_principal_id, principal) in
+                    <content_working_group::PrincipalById<Runtime>>::enumerate()
+                {
+                    if let content_working_group::Principal::ChannelOwner(channel_id) = principal {
+                        let channel =
+                            <content_working_group::ChannelById<Runtime>>::get(channel_id);
+                        if channel.role_account == *account {
+                            return true; // should we also take publishing_status/curation_status into account ?
+                        }
+                    }
+                }
+
+                false
+            }
+            // mapping to working group principal id
+            n if n >= PrincipalIdMappingStartsAtCredential::get() => {
+                <content_working_group::Module<Runtime>>::account_has_credential(
+                    account,
+                    n - PrincipalIdMappingStartsAtCredential::get(),
+                )
+            }
+            _ => false,
+        }
+    }
+}
+
+pub struct ContentWorkingGroupStakingEventHandler {}
+impl stake::StakingEventsHandler<Runtime> for ContentWorkingGroupStakingEventHandler {
+    fn unstaked(
+        stake_id: &<Runtime as stake::Trait>::StakeId,
+        _unstaked_amount: stake::BalanceOf<Runtime>,
+        remaining_imbalance: stake::NegativeImbalance<Runtime>,
+    ) -> stake::NegativeImbalance<Runtime> {
+        if !hiring::ApplicationIdByStakingId::<Runtime>::exists(stake_id) {
+            // Stake not related to a staked role managed by the hiring module
+            return remaining_imbalance;
+        }
+
+        let application_id = hiring::ApplicationIdByStakingId::<Runtime>::get(stake_id);
+
+        if !content_working_group::CuratorApplicationById::<Runtime>::exists(application_id) {
+            // Stake not for a Curator
+            return remaining_imbalance;
+        }
+
+        // Notify the Hiring module - is there a potential re-entrancy bug if
+        // instant unstaking is occuring?
+        hiring::Module::<Runtime>::unstaked(*stake_id);
+
+        // Only notify working group module if non instantaneous unstaking occured
+        if content_working_group::UnstakerByStakeId::<Runtime>::exists(stake_id) {
+            content_working_group::Module::<Runtime>::unstaked(*stake_id);
+        }
+
+        // Determine member id of the curator
+        let curator_application =
+            content_working_group::CuratorApplicationById::<Runtime>::get(application_id);
+        let member_id = curator_application.member_id;
+
+        // get member's profile
+        let member_profile = membership::members::MemberProfile::<Runtime>::get(member_id).unwrap();
+
+        // deposit funds to member's root_account
+        // The application doesn't recorded the original source_account from which staked funds were
+        // provided, so we don't really have another option at the moment.
+        <Runtime as stake::Trait>::Currency::resolve_creating(
+            &member_profile.root_account,
+            remaining_imbalance,
+        );
+
+        stake::NegativeImbalance::<Runtime>::zero()
+    }
+
+    // Handler for slashing event
+    fn slashed(
+        _id: &<Runtime as stake::Trait>::StakeId,
+        _slash_id: Option<<Runtime as stake::Trait>::SlashId>,
+        _slashed_amount: stake::BalanceOf<Runtime>,
+        _remaining_stake: stake::BalanceOf<Runtime>,
+        remaining_imbalance: stake::NegativeImbalance<Runtime>,
+    ) -> stake::NegativeImbalance<Runtime> {
+        // Check if the stake is associated with a hired curator or applicant
+        // if their stake goes below minimum required for the role,
+        // they should get deactivated.
+        // Since we don't currently implement any slash initiation in working group,
+        // there is nothing to do for now.
+
+        // Not interested in transfering the slashed amount anywhere for now,
+        // so return it to next handler.
+        remaining_imbalance
+    }
+}

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

@@ -1,2 +1,4 @@
+pub mod content_working_group;
 pub mod proposals;
 pub mod storage;
+pub mod working_group;

+ 2 - 2
runtime/src/integration/proposals/council_origin_validator.rs

@@ -2,7 +2,7 @@
 
 use rstd::marker::PhantomData;
 
-use common::origin_validator::ActorOriginValidator;
+use common::origin::ActorOriginValidator;
 use proposals_engine::VotersParameters;
 
 use super::{MemberId, MembershipOriginValidator};
@@ -44,7 +44,7 @@ impl<T: governance::council::Trait> VotersParameters for CouncilManager<T> {
 mod tests {
     use super::CouncilManager;
     use crate::Runtime;
-    use common::origin_validator::ActorOriginValidator;
+    use common::origin::ActorOriginValidator;
     use membership::members::UserInfo;
     use proposals_engine::VotersParameters;
     use sr_primitives::AccountId32;

+ 2 - 2
runtime/src/integration/proposals/membership_origin_validator.rs

@@ -2,7 +2,7 @@
 
 use rstd::marker::PhantomData;
 
-use common::origin_validator::ActorOriginValidator;
+use common::origin::ActorOriginValidator;
 use system::ensure_signed;
 
 /// Member of the Joystream organization
@@ -46,7 +46,7 @@ impl<T: crate::members::Trait>
 mod tests {
     use super::MembershipOriginValidator;
     use crate::Runtime;
-    use common::origin_validator::ActorOriginValidator;
+    use common::origin::ActorOriginValidator;
     use membership::members::UserInfo;
     use sr_primitives::AccountId32;
     use system::RawOrigin;

+ 1 - 1
runtime/src/integration/storage.rs

@@ -8,7 +8,7 @@ pub struct StorageProviderHelper;
 
 impl storage::data_directory::StorageProviderHelper<Runtime> for StorageProviderHelper {
     fn get_random_storage_provider() -> Result<ActorId, &'static str> {
-        let ids = crate::StorageWorkingGroup::get_all_worker_ids();
+        let ids = crate::StorageWorkingGroup::get_regular_worker_ids();
 
         let live_ids: Vec<ActorId> = ids
             .into_iter()

+ 49 - 0
runtime/src/integration/working_group.rs

@@ -0,0 +1,49 @@
+use rstd::marker::PhantomData;
+use srml_support::{StorageLinkedMap, StorageMap};
+
+use crate::StorageWorkingGroupInstance;
+use stake::{BalanceOf, NegativeImbalance};
+
+pub struct StakingEventsHandler<T> {
+    pub marker: PhantomData<T>,
+}
+
+impl<T: stake::Trait + working_group::Trait<StorageWorkingGroupInstance>>
+    stake::StakingEventsHandler<T> for StakingEventsHandler<T>
+{
+    /// Unstake remaining sum back to the source_account_id
+    fn unstaked(
+        stake_id: &<T as stake::Trait>::StakeId,
+        _unstaked_amount: BalanceOf<T>,
+        remaining_imbalance: NegativeImbalance<T>,
+    ) -> NegativeImbalance<T> {
+        // Stake not related to a staked role managed by the hiring module.
+        if !hiring::ApplicationIdByStakingId::<T>::exists(*stake_id) {
+            return remaining_imbalance;
+        }
+
+        let hiring_application_id = hiring::ApplicationIdByStakingId::<T>::get(*stake_id);
+
+        if working_group::MemberIdByHiringApplicationId::<T, StorageWorkingGroupInstance>::exists(
+            hiring_application_id,
+        ) {
+            return <working_group::Module<T, StorageWorkingGroupInstance>>::refund_working_group_stake(
+				*stake_id,
+				remaining_imbalance,
+			);
+        }
+
+        remaining_imbalance
+    }
+
+    /// Empty handler for the slashing.
+    fn slashed(
+        _: &<T as stake::Trait>::StakeId,
+        _: Option<<T as stake::Trait>::SlashId>,
+        _: BalanceOf<T>,
+        _: BalanceOf<T>,
+        remaining_imbalance: NegativeImbalance<T>,
+    ) -> NegativeImbalance<T> {
+        remaining_imbalance
+    }
+}

+ 12 - 139
runtime/src/lib.rs

@@ -161,7 +161,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
     spec_name: create_runtime_str!("joystream-node"),
     impl_name: create_runtime_str!("joystream-node"),
     authoring_version: 6,
-    spec_version: 16,
+    spec_version: 17,
     impl_version: 0,
     apis: RUNTIME_API_VERSIONS,
 };
@@ -319,7 +319,7 @@ impl transaction_payment::Trait for Runtime {
     type TransactionBaseFee = TransactionBaseFee;
     type TransactionByteFee = TransactionByteFee;
     type WeightToFee = ();
-    type FeeMultiplierUpdate = (); // FeeMultiplierUpdateHandler;
+    type FeeMultiplierUpdate = ();
 }
 
 impl sudo::Trait for Runtime {
@@ -447,7 +447,10 @@ impl versioned_store::Trait for Runtime {
 
 impl versioned_store_permissions::Trait for Runtime {
     type Credential = Credential;
-    type CredentialChecker = (ContentWorkingGroupCredentials, SudoKeyHasAllCredentials);
+    type CredentialChecker = (
+        integration::content_working_group::ContentWorkingGroupCredentials,
+        SudoKeyHasAllCredentials,
+    );
     type CreateClassPermissionsChecker = ContentLeadOrSudoKeyCanCreateClasses;
 }
 
@@ -462,72 +465,6 @@ impl versioned_store_permissions::CredentialChecker<Runtime> for SudoKeyHasAllCr
     }
 }
 
-parameter_types! {
-    pub const CurrentLeadCredential: Credential = 0;
-    pub const AnyActiveCuratorCredential: Credential = 1;
-    pub const AnyActiveChannelOwnerCredential: Credential = 2;
-    pub const PrincipalIdMappingStartsAtCredential: Credential = 1000;
-}
-
-pub struct ContentWorkingGroupCredentials {}
-impl versioned_store_permissions::CredentialChecker<Runtime> for ContentWorkingGroupCredentials {
-    fn account_has_credential(
-        account: &AccountId,
-        credential: <Runtime as versioned_store_permissions::Trait>::Credential,
-    ) -> bool {
-        match credential {
-            // Credentials from 0..999 represents groups or more complex requirements
-            // Current Lead if set
-            credential if credential == CurrentLeadCredential::get() => {
-                match <content_wg::Module<Runtime>>::ensure_lead_is_set() {
-                    Ok((_, lead)) => lead.role_account == *account,
-                    _ => false,
-                }
-            }
-            // Any Active Curator
-            credential if credential == AnyActiveCuratorCredential::get() => {
-                // Look for a Curator with a matching role account
-                for (_principal_id, principal) in <content_wg::PrincipalById<Runtime>>::enumerate()
-                {
-                    if let content_wg::Principal::Curator(curator_id) = principal {
-                        let curator = <content_wg::CuratorById<Runtime>>::get(curator_id);
-                        if curator.role_account == *account
-                            && curator.stage == content_wg::CuratorRoleStage::Active
-                        {
-                            return true;
-                        }
-                    }
-                }
-
-                false
-            }
-            // Any Active Channel Owner
-            credential if credential == AnyActiveChannelOwnerCredential::get() => {
-                // Look for a ChannelOwner with a matching role account
-                for (_principal_id, principal) in <content_wg::PrincipalById<Runtime>>::enumerate()
-                {
-                    if let content_wg::Principal::ChannelOwner(channel_id) = principal {
-                        let channel = <content_wg::ChannelById<Runtime>>::get(channel_id);
-                        if channel.role_account == *account {
-                            return true; // should we also take publishing_status/curation_status into account ?
-                        }
-                    }
-                }
-
-                false
-            }
-            // mapping to workging group principal id
-            n if n >= PrincipalIdMappingStartsAtCredential::get() => {
-                <content_wg::Module<Runtime>>::account_has_credential(
-                    account,
-                    n - PrincipalIdMappingStartsAtCredential::get(),
-                )
-            }
-            _ => false,
-        }
-    }
-}
-
 // Allow sudo key holder permission to create classes
 pub struct SudoKeyCanCreateClasses {}
 impl versioned_store_permissions::CreateClassPermissionsChecker<Runtime>
@@ -591,80 +528,16 @@ impl stake::Trait for Runtime {
     type Currency = <Self as common::currency::GovernanceCurrency>::Currency;
     type StakePoolId = StakePoolId;
     type StakingEventsHandler = (
-        ContentWorkingGroupStakingEventHandler,
-        crate::integration::proposals::StakingEventsHandler<Self>,
+        crate::integration::content_working_group::ContentWorkingGroupStakingEventHandler,
+        (
+            crate::integration::proposals::StakingEventsHandler<Self>,
+            crate::integration::working_group::StakingEventsHandler<Self>,
+        ),
     );
     type StakeId = u64;
     type SlashId = u64;
 }
 
-pub struct ContentWorkingGroupStakingEventHandler {}
-impl stake::StakingEventsHandler<Runtime> for ContentWorkingGroupStakingEventHandler {
-    fn unstaked(
-        stake_id: &<Runtime as stake::Trait>::StakeId,
-        _unstaked_amount: stake::BalanceOf<Runtime>,
-        remaining_imbalance: stake::NegativeImbalance<Runtime>,
-    ) -> stake::NegativeImbalance<Runtime> {
-        if !hiring::ApplicationIdByStakingId::<Runtime>::exists(stake_id) {
-            // Stake not related to a staked role managed by the hiring module
-            return remaining_imbalance;
-        }
-
-        let application_id = hiring::ApplicationIdByStakingId::<Runtime>::get(stake_id);
-
-        if !content_wg::CuratorApplicationById::<Runtime>::exists(application_id) {
-            // Stake not for a Curator
-            return remaining_imbalance;
-        }
-
-        // Notify the Hiring module - is there a potential re-entrancy bug if
-        // instant unstaking is occuring?
-        hiring::Module::<Runtime>::unstaked(*stake_id);
-
-        // Only notify working group module if non instantaneous unstaking occured
-        if content_wg::UnstakerByStakeId::<Runtime>::exists(stake_id) {
-            content_wg::Module::<Runtime>::unstaked(*stake_id);
-        }
-
-        // Determine member id of the curator
-        let curator_application =
-            content_wg::CuratorApplicationById::<Runtime>::get(application_id);
-        let member_id = curator_application.member_id;
-
-        // get member's profile
-        let member_profile = membership::members::MemberProfile::<Runtime>::get(member_id).unwrap();
-
-        // deposit funds to member's root_account
-        // The application doesn't recorded the original source_account from which staked funds were
-        // provided, so we don't really have another option at the moment.
-        <Runtime as stake::Trait>::Currency::resolve_creating(
-            &member_profile.root_account,
-            remaining_imbalance,
-        );
-
-        stake::NegativeImbalance::<Runtime>::zero()
-    }
-
-    // Handler for slashing event
-    fn slashed(
-        _id: &<Runtime as stake::Trait>::StakeId,
-        _slash_id: Option<<Runtime as stake::Trait>::SlashId>,
-        _slashed_amount: stake::BalanceOf<Runtime>,
-        _remaining_stake: stake::BalanceOf<Runtime>,
-        remaining_imbalance: stake::NegativeImbalance<Runtime>,
-    ) -> stake::NegativeImbalance<Runtime> {
-        // Check if the stake is associated with a hired curator or applicant
-        // if their stake goes below minimum required for the role,
-        // they should get deactivated.
-        // Since we don't currently implement any slash initiation in working group,
-        // there is nothing to do for now.
-
-        // Not interested in transfering the slashed amount anywhere for now,
-        // so return it to next handler.
-        remaining_imbalance
-    }
-}
-
 impl content_wg::Trait for Runtime {
     type Event = Event;
 }
@@ -720,7 +593,7 @@ impl members::Trait for Runtime {
  *
  * ForumUserRegistry could have been implemented directly on
  * the membership module, and likewise ForumUser on Profile,
- * however this approach is more loosley coupled.
+ * however this approach is more loosely coupled.
  *
  * Further exploration required to decide what the long
  * run convention should be.

Деякі файли не було показано, через те що забагато файлів було змінено