浏览代码

Merge branch 'working_group_proposals' of https://github.com/shamil-gadelshin/substrate-runtime-joystream into feature/nicaea-proposals

Gleb Urvanov 4 年之前
父节点
当前提交
f65faaca5f

+ 4 - 3
Cargo.lock

@@ -1614,7 +1614,7 @@ dependencies = [
 
 [[package]]
 name = "joystream-node-runtime"
-version = "6.17.0"
+version = "6.18.0"
 dependencies = [
  "parity-scale-codec",
  "safe-mix",
@@ -4579,7 +4579,7 @@ dependencies = [
 
 [[package]]
 name = "substrate-common-module"
-version = "1.1.0"
+version = "1.2.0"
 dependencies = [
  "parity-scale-codec",
  "serde",
@@ -5106,7 +5106,7 @@ dependencies = [
 
 [[package]]
 name = "substrate-proposals-codex-module"
-version = "2.0.0"
+version = "2.1.0"
 dependencies = [
  "num_enum",
  "parity-scale-codec",
@@ -5134,6 +5134,7 @@ dependencies = [
  "substrate-token-mint-module",
  "substrate-versioned-store",
  "substrate-versioned-store-permissions-module",
+ "substrate-working-group-module",
 ]
 
 [[package]]

+ 28 - 0
node/src/chain_spec.rs

@@ -331,6 +331,34 @@ pub fn testnet_genesis(
             set_lead_proposal_grace_period: cpcp.set_lead_proposal_voting_period,
             spending_proposal_voting_period: cpcp.spending_proposal_voting_period,
             spending_proposal_grace_period: cpcp.spending_proposal_grace_period,
+            add_working_group_opening_proposal_voting_period: cpcp
+                .add_working_group_opening_proposal_voting_period,
+            add_working_group_opening_proposal_grace_period: cpcp
+                .add_working_group_opening_proposal_grace_period,
+            begin_review_working_group_leader_applications_proposal_voting_period: cpcp
+                .begin_review_working_group_leader_applications_proposal_voting_period,
+            begin_review_working_group_leader_applications_proposal_grace_period: cpcp
+                .begin_review_working_group_leader_applications_proposal_grace_period,
+            fill_working_group_leader_opening_proposal_voting_period: cpcp
+                .fill_working_group_leader_opening_proposal_voting_period,
+            fill_working_group_leader_opening_proposal_grace_period: cpcp
+                .fill_working_group_leader_opening_proposal_grace_period,
+            set_working_group_mint_capacity_proposal_voting_period: cpcp
+                .set_content_working_group_mint_capacity_proposal_voting_period,
+            set_working_group_mint_capacity_proposal_grace_period: cpcp
+                .set_content_working_group_mint_capacity_proposal_grace_period,
+            decrease_working_group_leader_stake_proposal_voting_period: cpcp
+                .decrease_working_group_leader_stake_proposal_voting_period,
+            decrease_working_group_leader_stake_proposal_grace_period: cpcp
+                .decrease_working_group_leader_stake_proposal_grace_period,
+            slash_working_group_leader_stake_proposal_voting_period: cpcp
+                .slash_working_group_leader_stake_proposal_voting_period,
+            slash_working_group_leader_stake_proposal_grace_period: cpcp
+                .slash_working_group_leader_stake_proposal_grace_period,
+            set_working_group_leader_reward_proposal_voting_period: cpcp
+                .set_working_group_leader_reward_proposal_voting_period,
+            set_working_group_leader_reward_proposal_grace_period: cpcp
+                .set_working_group_leader_reward_proposal_grace_period,
         }),
     }
 }

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

@@ -1,6 +1,6 @@
 [package]
 name = 'substrate-common-module'
-version = '1.1.0'
+version = '1.2.0'
 authors = ['Joystream contributors']
 edition = '2018'
 

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

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

+ 15 - 0
runtime-modules/common/src/working_group.rs

@@ -0,0 +1,15 @@
+use codec::{Decode, Encode};
+#[cfg(feature = "std")]
+use serde::{Deserialize, Serialize};
+
+/// Defines well-known working groups.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Clone, PartialEq, Eq, Copy, Debug)]
+pub enum WorkingGroup {
+    /* Reserved
+        /// Forum working group: working_group::Instance1.
+        Forum,
+    */
+    /// Storage working group: working_group::Instance2.
+    Storage,
+}

+ 2 - 2
runtime-modules/governance/src/election.rs

@@ -801,7 +801,7 @@ decl_module! {
 
         // Member can apply during announcing stage only. On first call a minimum stake will need to be provided.
         // Member can make subsequent calls during announcing stage to increase their stake.
-        fn apply(origin, stake: BalanceOf<T>) {
+        pub fn apply(origin, stake: BalanceOf<T>) {
             let sender = ensure_signed(origin)?;
             ensure!(Self::can_participate(&sender), "Only members can apply to be on council");
 
@@ -909,7 +909,7 @@ decl_module! {
             );
         }
 
-        fn force_start_election(origin) {
+        pub fn force_start_election(origin) {
             ensure_root(origin)?;
             Self::start_election(<council::Module<T>>::active_council())?;
         }

+ 6 - 5
runtime-modules/hiring/src/hiring/opening.rs

@@ -420,20 +420,21 @@ pub enum OpeningDeactivationCause {
     /// Opening was cancelled during accepting application stage
     CancelledInReviewPeriod,
 
-    /// Opening was cancelled after review period exprired
+    /// Opening was cancelled after review period expired.
     ReviewPeriodExpired,
 
-    /// Opening was filled
+    /// Opening was filled.
     Filled,
 }
 
-/// Safe and explict way of chosing
+/// Defines the moment of the opening activation.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
 #[derive(Encode, Decode, Eq, PartialEq, Clone, Debug)]
 pub enum ActivateOpeningAt<BlockNumber> {
-    /// Activate opening now (current block)
+    /// Activate opening now (current block).
     CurrentBlock,
 
-    /// Activate opening at block number
+    /// Activate opening at block number.
     ExactBlock(BlockNumber),
 }
 

+ 9 - 2
runtime-modules/proposals/codex/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = 'substrate-proposals-codex-module'
-version = '2.0.0'
+version = '2.1.0'
 authors = ['Joystream contributors']
 edition = '2018'
 
@@ -26,6 +26,8 @@ std = [
     'mint/std',
     'common/std',
     'content_working_group/std',
+    'working_group/std',
+    'hiring/std',
 ]
 
 
@@ -132,7 +134,12 @@ default_features = false
 package = 'substrate-content-working-group-module'
 path = '../../content-working-group'
 
-[dev-dependencies.hiring]
+[dependencies.working_group]
+default_features = false
+package = 'substrate-working-group-module'
+path = '../../working-group'
+
+[dependencies.hiring]
 default_features = false
 package = 'substrate-hiring-module'
 path = '../../hiring'

+ 443 - 115
runtime-modules/proposals/codex/src/lib.rs

@@ -12,14 +12,27 @@
 //! module. For each proposal, [its crucial details](./enum.ProposalDetails.html) are saved to the
 //! `ProposalDetailsByProposalId` map.
 //!
-//! ### Supported extrinsics (proposal types)
+//! ### General proposals
 //! - [create_text_proposal](./struct.Module.html#method.create_text_proposal)
 //! - [create_runtime_upgrade_proposal](./struct.Module.html#method.create_runtime_upgrade_proposal)
+//! - [create_set_validator_count_proposal](./struct.Module.html#method.create_set_validator_count_proposal)
+//!
+//! ### Council and election proposals
 //! - [create_set_election_parameters_proposal](./struct.Module.html#method.create_set_election_parameters_proposal)
-//! - [create_set_content_working_group_mint_capacity_proposal](./struct.Module.html#method.create_set_content_working_group_mint_capacity_proposal)
 //! - [create_spending_proposal](./struct.Module.html#method.create_spending_proposal)
+//!
+//! ### Content working group proposals
 //! - [create_set_lead_proposal](./struct.Module.html#method.create_set_lead_proposal)
-//! - [create_set_validator_count_proposal](./struct.Module.html#method.create_set_validator_count_proposal)
+//! - [create_set_content_working_group_mint_capacity_proposal](./struct.Module.html#method.create_set_content_working_group_mint_capacity_proposal)
+//!
+//! ### Working group proposals
+//! - [create_add_working_group_leader_opening_proposal](./struct.Module.html#method.create_add_working_group_leader_opening_proposal)
+//! - [create_begin_review_working_group_leader_applications_proposal](./struct.Module.html#method.create_begin_review_working_group_leader_applications_proposal)
+//! - [create_fill_working_group_leader_opening_proposal](./struct.Module.html#method.create_fill_working_group_leader_opening_proposal)
+//! - [create_set_working_group_mint_capacity_proposal](./struct.Module.html#method.create_set_working_group_mint_capacity_proposal)
+//! - [create_decrease_working_group_leader_stake_proposal](./struct.Module.html#method.create_decrease_working_group_leader_stake_proposal)
+//! - [create_slash_working_group_leader_stake_proposal](./struct.Module.html#method.create_slash_working_group_leader_stake_proposal)
+//! - [create_set_working_group_leader_reward_proposal](./struct.Module.html#method.create_set_working_group_leader_reward_proposal)
 //!
 //! ### Proposal implementations of this module
 //! - execute_text_proposal - prints the proposal to the log
@@ -36,25 +49,27 @@
 //! The module uses [ProposalEncoder](./trait.ProposalEncoder.html) to encode the proposal using
 //! its details. Encoded byte vector is passed to the _proposals engine_ as serialized executable code.
 
-// Clippy linter warning. TODO: remove after the Constaninople release
-#![allow(clippy::type_complexity)]
-// disable it because of possible frontend API break
-
-// Clippy linter warning. TODO: refactor "this function has too many argument"
-#![allow(clippy::too_many_arguments)] // disable it because of possible API break
-
+// `decl_module!` does a lot of recursion and requires us to increase the limit to 256.
+#![recursion_limit = "256"]
 // Ensure we're `no_std` when compiling for Wasm.
 #![cfg_attr(not(feature = "std"), no_std)]
+// Disable this lint warning because Substrate generates function without an alias for the ProposalDetailsOf type.
+#![allow(clippy::too_many_arguments)]
 
 // Do not delete! Cannot be uncommented by default, because of Parity decl_module! issue.
 // #![warn(missing_docs)]
 
+// TODO Working group proposals parameters & default
+// TODO Working group proposals validation limits
+// TODO module comments update.
+
 mod proposal_types;
 
 #[cfg(test)]
 mod tests;
 
 use common::origin::ActorOriginValidator;
+use common::working_group::WorkingGroup;
 use governance::election_params::ElectionParameters;
 use proposal_engine::ProposalParameters;
 use rstd::clone::Clone;
@@ -67,10 +82,14 @@ use srml_support::traits::{Currency, Get};
 use srml_support::{decl_error, decl_module, decl_storage, ensure, print};
 use system::ensure_root;
 
-pub use crate::proposal_types::ProposalsConfigParameters;
+pub use crate::proposal_types::{
+    AddOpeningParameters, FillOpeningParameters, ProposalsConfigParameters,
+};
 pub use proposal_types::{ProposalDetails, ProposalDetailsOf, ProposalEncoder};
 
 // 'Set working group mint capacity' proposal limit
+const WORKING_GROUP_MINT_CAPACITY_MAX_VALUE: u32 = 5_000_000;
+// 'Set content working group mint capacity' proposal limit
 const CONTENT_WORKING_GROUP_MINT_CAPACITY_MAX_VALUE: u32 = 1_000_000;
 // Max allowed value for 'spending' proposal
 const MAX_SPENDING_PROPOSAL_VALUE: u32 = 2_000_000_u32;
@@ -109,6 +128,19 @@ const ELECTION_PARAMETERS_MIN_COUNCIL_STAKE_MIN_VALUE: u32 = 1;
 // min_council_stake max value for the 'set election parameters' proposal
 const ELECTION_PARAMETERS_MIN_COUNCIL_STAKE_MAX_VALUE: u32 = 100_000_u32;
 
+// Data container struct to fix linter warning 'too many arguments for the function' for the
+// create_proposal() function.
+struct CreateProposalParameters<T: Trait> {
+    pub origin: T::Origin,
+    pub member_id: MemberId<T>,
+    pub title: Vec<u8>,
+    pub description: Vec<u8>,
+    pub stake_balance: Option<BalanceOf<T>>,
+    pub proposal_code: Vec<u8>,
+    pub proposal_parameters: ProposalParameters<T::BlockNumber, BalanceOf<T>>,
+    pub proposal_details: ProposalDetailsOf<T>,
+}
+
 /// 'Proposals codex' substrate module Trait
 pub trait Trait:
     system::Trait
@@ -207,11 +239,20 @@ decl_error! {
         /// Invalid council election parameter - announcing_period
         InvalidCouncilElectionParameterAnnouncingPeriod,
 
+        /// Invalid content working group mint capacity parameter
+        InvalidContentWorkingGroupMintCapacity,
+
         /// Invalid working group mint capacity parameter
-        InvalidStorageWorkingGroupMintCapacity,
+        InvalidWorkingGroupMintCapacity,
 
         /// Invalid 'set lead proposal' parameter - proposed lead cannot be a councilor
-        InvalidSetLeadParameterCannotBeCouncilor
+        InvalidSetLeadParameterCannotBeCouncilor,
+
+        /// Invalid 'slash stake proposal' parameter - cannot slash by zero balance.
+        SlashingStakeIsZero,
+
+        /// Invalid 'decrease stake proposal' parameter - cannot decrease by zero balance.
+        DecreasingStakeIsZero,
     }
 }
 
@@ -254,13 +295,7 @@ decl_storage! {
 
         /// Map proposal id to proposal details
         pub ProposalDetailsByProposalId get(fn proposal_details_by_proposal_id):
-            map T::ProposalId => ProposalDetails<
-                BalanceOfMint<T>,
-                BalanceOfGovernanceCurrency<T>,
-                T::BlockNumber,
-                T::AccountId,
-                T::MemberId
-            >;
+            map T::ProposalId => ProposalDetailsOf<T>;
 
         /// Voting period for the 'set validator count' proposal
         pub SetValidatorCountProposalVotingPeriod get(set_validator_count_proposal_voting_period)
@@ -313,6 +348,56 @@ decl_storage! {
 
         /// Grace period for the 'spending' proposal
         pub SpendingProposalGracePeriod get(spending_proposal_grace_period) config(): T::BlockNumber;
+
+        /// Voting period for the 'add working group opening' proposal
+        pub AddWorkingGroupOpeningProposalVotingPeriod get(add_working_group_opening_proposal_voting_period) config(): T::BlockNumber;
+
+        /// Grace period for the 'add working group opening' proposal
+        pub AddWorkingGroupOpeningProposalGracePeriod get(add_working_group_opening_proposal_grace_period) config(): T::BlockNumber;
+
+        /// Voting period for the 'begin review working group leader applications' proposal
+        pub BeginReviewWorkingGroupLeaderApplicationsProposalVotingPeriod get(begin_review_working_group_leader_applications_proposal_voting_period) config(): T::BlockNumber;
+
+        /// Grace period for the 'begin review working group leader applications' proposal
+        pub BeginReviewWorkingGroupLeaderApplicationsProposalGracePeriod get(begin_review_working_group_leader_applications_proposal_grace_period) config(): T::BlockNumber;
+
+        /// Voting period for the 'fill working group leader opening' proposal
+        pub FillWorkingGroupLeaderOpeningProposalVotingPeriod get(fill_working_group_leader_opening_proposal_voting_period) config(): T::BlockNumber;
+
+        /// Grace period for the 'fill working group leader opening' proposal
+        pub FillWorkingGroupLeaderOpeningProposalGracePeriod get(fill_working_group_leader_opening_proposal_grace_period) config(): T::BlockNumber;
+
+        /// Voting period for the 'set working group mint capacity' proposal
+        pub SetWorkingGroupMintCapacityProposalVotingPeriod get(set_working_group_mint_capacity_proposal_voting_period)
+            config(): T::BlockNumber;
+
+        /// Grace period for the 'set working group mint capacity' proposal
+        pub SetWorkingGroupMintCapacityProposalGracePeriod get(set_working_group_mint_capacity_proposal_grace_period)
+            config(): T::BlockNumber;
+
+        /// Voting period for the 'decrease working group leader stake' proposal
+        pub DecreaseWorkingGroupLeaderStakeProposalVotingPeriod get(decrease_working_group_leader_stake_proposal_voting_period)
+            config(): T::BlockNumber;
+
+        /// Grace period for the 'decrease working group leader stake' proposal
+        pub DecreaseWorkingGroupLeaderStakeProposalGracePeriod get(decrease_working_group_leader_stake_proposal_grace_period)
+            config(): T::BlockNumber;
+
+        /// Voting period for the 'slash working group leader stake' proposal
+        pub SlashWorkingGroupLeaderStakeProposalVotingPeriod get(slash_working_group_leader_stake_proposal_voting_period)
+            config(): T::BlockNumber;
+
+        /// Grace period for the 'slash working group leader stake' proposal
+        pub SlashWorkingGroupLeaderStakeProposalGracePeriod get(slash_working_group_leader_stake_proposal_grace_period)
+            config(): T::BlockNumber;
+
+        /// Voting period for the 'set working group leader reward' proposal
+        pub SetWorkingGroupLeaderRewardProposalVotingPeriod get(set_working_group_leader_reward_proposal_voting_period)
+            config(): T::BlockNumber;
+
+        /// Grace period for the 'set working group leader reward' proposal
+        pub SetWorkingGroupLeaderRewardProposalGracePeriod get(set_working_group_leader_reward_proposal_grace_period)
+            config(): T::BlockNumber;
     }
 }
 
@@ -341,20 +426,19 @@ decl_module! {
             ensure!(text.len() as u32 <=  T::TextProposalMaxLength::get(),
                 Error::TextProposalSizeExceeded);
 
-            let proposal_parameters = proposal_types::parameters::text_proposal::<T>();
-            let proposal_details = ProposalDetails::<BalanceOfMint<T>, BalanceOfGovernanceCurrency<T>, T::BlockNumber, T::AccountId, MemberId<T>>::Text(text);
-            let proposal_code = T::ProposalEncoder::encode_proposal(proposal_details.clone());
-
-            Self::create_proposal(
+            let proposal_details = ProposalDetails::Text(text);
+            let params = CreateProposalParameters{
                 origin,
                 member_id,
                 title,
                 description,
                 stake_balance,
-                proposal_code,
-                proposal_parameters,
-                proposal_details,
-            )?;
+                proposal_details: proposal_details.clone(),
+                proposal_parameters: proposal_types::parameters::text_proposal::<T>(),
+                proposal_code: T::ProposalEncoder::encode_proposal(proposal_details)
+            };
+
+            Self::create_proposal(params)?;
         }
 
         /// Create 'Runtime upgrade' proposal type. Runtime upgrade can be initiated only by
@@ -371,20 +455,19 @@ decl_module! {
             ensure!(wasm.len() as u32 <= T::RuntimeUpgradeWasmProposalMaxLength::get(),
                 Error::RuntimeProposalSizeExceeded);
 
-            let proposal_parameters = proposal_types::parameters::runtime_upgrade_proposal::<T>();
             let proposal_details = ProposalDetails::RuntimeUpgrade(wasm);
-            let proposal_code = T::ProposalEncoder::encode_proposal(proposal_details.clone());
-
-            Self::create_proposal(
+            let params = CreateProposalParameters{
                 origin,
                 member_id,
                 title,
                 description,
                 stake_balance,
-                proposal_code,
-                proposal_parameters,
-                proposal_details,
-            )?;
+                proposal_details: proposal_details.clone(),
+                proposal_parameters: proposal_types::parameters::runtime_upgrade_proposal::<T>(),
+                proposal_code: T::ProposalEncoder::encode_proposal(proposal_details)
+            };
+
+            Self::create_proposal(params)?;
         }
 
         /// Create 'Set election parameters' proposal type. This proposal uses `set_election_parameters()`
@@ -402,20 +485,18 @@ decl_module! {
             Self::ensure_council_election_parameters_valid(&election_parameters)?;
 
             let proposal_details = ProposalDetails::SetElectionParameters(election_parameters);
-            let proposal_code = T::ProposalEncoder::encode_proposal(proposal_details.clone());
-            let proposal_parameters =
-                proposal_types::parameters::set_election_parameters_proposal::<T>();
-
-            Self::create_proposal(
+            let params = CreateProposalParameters{
                 origin,
                 member_id,
                 title,
                 description,
                 stake_balance,
-                proposal_code,
-                proposal_parameters,
-                proposal_details,
-            )?;
+                proposal_details: proposal_details.clone(),
+                proposal_parameters: proposal_types::parameters::set_election_parameters_proposal::<T>(),
+                proposal_code: T::ProposalEncoder::encode_proposal(proposal_details)
+            };
+
+            Self::create_proposal(params)?;
         }
 
         /// Create 'Set content working group mint capacity' proposal type.
@@ -430,24 +511,22 @@ decl_module! {
         ) {
             ensure!(
                 mint_balance <= <BalanceOfMint<T>>::from(CONTENT_WORKING_GROUP_MINT_CAPACITY_MAX_VALUE),
-                Error::InvalidStorageWorkingGroupMintCapacity
+                Error::InvalidContentWorkingGroupMintCapacity
             );
 
-            let proposal_parameters =
-                proposal_types::parameters::set_content_working_group_mint_capacity_proposal::<T>();
             let proposal_details = ProposalDetails::SetContentWorkingGroupMintCapacity(mint_balance);
-            let proposal_code = T::ProposalEncoder::encode_proposal(proposal_details.clone());
-
-            Self::create_proposal(
+            let params = CreateProposalParameters{
                 origin,
                 member_id,
                 title,
                 description,
                 stake_balance,
-                proposal_code,
-                proposal_parameters,
-                proposal_details,
-            )?;
+                proposal_details: proposal_details.clone(),
+                proposal_parameters: proposal_types::parameters::set_content_working_group_mint_capacity_proposal::<T>(),
+                proposal_code: T::ProposalEncoder::encode_proposal(proposal_details)
+            };
+
+            Self::create_proposal(params)?;
         }
 
         /// Create 'Spending' proposal type.
@@ -467,21 +546,19 @@ decl_module! {
                 Error::InvalidSpendingProposalBalance
             );
 
-            let proposal_parameters =
-                proposal_types::parameters::spending_proposal::<T>();
             let proposal_details = ProposalDetails::Spending(balance, destination);
-            let proposal_code = T::ProposalEncoder::encode_proposal(proposal_details.clone());
-
-            Self::create_proposal(
+            let params = CreateProposalParameters{
                 origin,
                 member_id,
                 title,
                 description,
                 stake_balance,
-                proposal_code,
-                proposal_parameters,
-                proposal_details,
-            )?;
+                proposal_details: proposal_details.clone(),
+                proposal_parameters: proposal_types::parameters::spending_proposal::<T>(),
+                proposal_code: T::ProposalEncoder::encode_proposal(proposal_details)
+            };
+
+            Self::create_proposal(params)?;
         }
 
         /// Create 'Set lead' proposal type.
@@ -501,22 +578,19 @@ decl_module! {
                     Error::InvalidSetLeadParameterCannotBeCouncilor
                 );
             }
-
-            let proposal_parameters =
-                proposal_types::parameters::set_lead_proposal::<T>();
             let proposal_details = ProposalDetails::SetLead(new_lead);
-            let proposal_code = T::ProposalEncoder::encode_proposal(proposal_details.clone());
-
-            Self::create_proposal(
+            let params = CreateProposalParameters{
                 origin,
                 member_id,
                 title,
                 description,
                 stake_balance,
-                proposal_code,
-                proposal_parameters,
-                proposal_details,
-            )?;
+                proposal_details: proposal_details.clone(),
+                proposal_parameters: proposal_types::parameters::set_lead_proposal::<T>(),
+                proposal_code: T::ProposalEncoder::encode_proposal(proposal_details)
+            };
+
+            Self::create_proposal(params)?;
         }
 
         /// Create 'Evict storage provider' proposal type.
@@ -539,23 +613,240 @@ decl_module! {
                 Error::InvalidValidatorCount
             );
 
-            let proposal_parameters =
-                proposal_types::parameters::set_validator_count_proposal::<T>();
             let proposal_details = ProposalDetails::SetValidatorCount(new_validator_count);
-            let proposal_code = T::ProposalEncoder::encode_proposal(proposal_details.clone());
+            let params = CreateProposalParameters{
+                origin,
+                member_id,
+                title,
+                description,
+                stake_balance,
+                proposal_details: proposal_details.clone(),
+                proposal_parameters: proposal_types::parameters::set_validator_count_proposal::<T>(),
+                proposal_code: T::ProposalEncoder::encode_proposal(proposal_details)
+            };
+
+            Self::create_proposal(params)?;
+        }
+
+        /// Create 'Add working group leader opening' proposal type.
+        /// This proposal uses `add_opening()` extrinsic from the Joystream `working group` module.
+        pub fn create_add_working_group_leader_opening_proposal(
+            origin,
+            member_id: MemberId<T>,
+            title: Vec<u8>,
+            description: Vec<u8>,
+            stake_balance: Option<BalanceOf<T>>,
+            add_opening_parameters: AddOpeningParameters<T::BlockNumber, BalanceOfGovernanceCurrency<T>>,
+        ) {
+
+            let proposal_details = ProposalDetails::AddWorkingGroupLeaderOpening(add_opening_parameters);
+            let params = CreateProposalParameters{
+                origin,
+                member_id,
+                title,
+                description,
+                stake_balance,
+                proposal_details: proposal_details.clone(),
+                proposal_parameters: proposal_types::parameters::add_working_group_leader_opening_proposal::<T>(),
+                proposal_code: T::ProposalEncoder::encode_proposal(proposal_details)
+            };
+
+            Self::create_proposal(params)?;
+        }
+
+        /// Create 'Begin review working group leader applications' proposal type.
+        /// This proposal uses `begin_applicant_review()` extrinsic from the Joystream `working group` module.
+        pub fn create_begin_review_working_group_leader_applications_proposal(
+            origin,
+            member_id: MemberId<T>,
+            title: Vec<u8>,
+            description: Vec<u8>,
+            stake_balance: Option<BalanceOf<T>>,
+            opening_id: working_group::OpeningId<T>,
+            working_group: WorkingGroup,
+        ) {
+
+            let proposal_details = ProposalDetails::BeginReviewWorkingGroupLeaderApplications(opening_id, working_group);
+            let params = CreateProposalParameters{
+                origin,
+                member_id,
+                title,
+                description,
+                stake_balance,
+                proposal_details: proposal_details.clone(),
+                proposal_parameters: proposal_types::parameters::begin_review_working_group_leader_applications_proposal::<T>(),
+                proposal_code: T::ProposalEncoder::encode_proposal(proposal_details)
+            };
+
+            Self::create_proposal(params)?;
+        }
+
+        /// Create 'Fill working group leader opening' proposal type.
+        /// This proposal uses `fill_opening()` extrinsic from the Joystream `working group` module.
+        pub fn create_fill_working_group_leader_opening_proposal(
+            origin,
+            member_id: MemberId<T>,
+            title: Vec<u8>,
+            description: Vec<u8>,
+            stake_balance: Option<BalanceOf<T>>,
+            params: FillOpeningParameters<
+                T::BlockNumber,
+                BalanceOfMint<T>,
+                working_group::OpeningId<T>,
+                working_group::ApplicationId<T>
+            >
+        ) {
+
+            let proposal_details = ProposalDetails::FillWorkingGroupLeaderOpening(params);
+            let params = CreateProposalParameters{
+                origin,
+                member_id,
+                title,
+                description,
+                stake_balance,
+                proposal_details: proposal_details.clone(),
+                proposal_parameters: proposal_types::parameters::fill_working_group_leader_opening_proposal::<T>(),
+                proposal_code: T::ProposalEncoder::encode_proposal(proposal_details)
+            };
+
+            Self::create_proposal(params)?;
+        }
+
+        /// Create 'Set working group mint capacity' proposal type.
+        /// This proposal uses `set_mint_capacity()` extrinsic from the `working-group`  module.
+        pub fn create_set_working_group_mint_capacity_proposal(
+            origin,
+            member_id: MemberId<T>,
+            title: Vec<u8>,
+            description: Vec<u8>,
+            stake_balance: Option<BalanceOf<T>>,
+            mint_balance: BalanceOfMint<T>,
+            working_group: WorkingGroup,
+        ) {
+            ensure!(
+                mint_balance <= <BalanceOfMint<T>>::from(WORKING_GROUP_MINT_CAPACITY_MAX_VALUE),
+                Error::InvalidWorkingGroupMintCapacity
+            );
+
+            let proposal_details = ProposalDetails::SetWorkingGroupMintCapacity(mint_balance, working_group);
+            let params = CreateProposalParameters{
+                origin,
+                member_id,
+                title,
+                description,
+                stake_balance,
+                proposal_details: proposal_details.clone(),
+                proposal_parameters: proposal_types::parameters::set_working_group_mint_capacity_proposal::<T>(),
+                proposal_code: T::ProposalEncoder::encode_proposal(proposal_details)
+            };
+
+            Self::create_proposal(params)?;
+        }
+
+        /// Create 'decrease working group leader stake' proposal type.
+        /// This proposal uses `decrease_stake()` extrinsic from the `working-group`  module.
+        pub fn create_decrease_working_group_leader_stake_proposal(
+            origin,
+            member_id: MemberId<T>,
+            title: Vec<u8>,
+            description: Vec<u8>,
+            stake_balance: Option<BalanceOf<T>>,
+            worker_id: working_group::WorkerId<T>,
+            decreasing_stake: BalanceOf<T>,
+            working_group: WorkingGroup,
+        ) {
+
+            ensure!(decreasing_stake != Zero::zero(), Error::DecreasingStakeIsZero);
+
+            let proposal_details = ProposalDetails::DecreaseWorkingGroupLeaderStake(
+                worker_id,
+                decreasing_stake,
+                working_group
+            );
+
+            let params = CreateProposalParameters{
+                origin,
+                member_id,
+                title,
+                description,
+                stake_balance,
+                proposal_details: proposal_details.clone(),
+                proposal_parameters: proposal_types::parameters::decrease_working_group_leader_stake_proposal::<T>(),
+                proposal_code: T::ProposalEncoder::encode_proposal(proposal_details)
+            };
+
+            Self::create_proposal(params)?;
+        }
+
+        /// Create 'slash working group leader stake' proposal type.
+        /// This proposal uses `slash_stake()` extrinsic from the `working-group`  module.
+        pub fn create_slash_working_group_leader_stake_proposal(
+            origin,
+            member_id: MemberId<T>,
+            title: Vec<u8>,
+            description: Vec<u8>,
+            stake_balance: Option<BalanceOf<T>>,
+            worker_id: working_group::WorkerId<T>,
+            slashing_stake: BalanceOf<T>,
+            working_group: WorkingGroup,
+        ) {
+
+            ensure!(slashing_stake != Zero::zero(), Error::SlashingStakeIsZero);
 
-            Self::create_proposal(
+            let proposal_details = ProposalDetails::SlashWorkingGroupLeaderStake(
+                worker_id,
+                slashing_stake,
+                working_group
+            );
+
+            let params = CreateProposalParameters{
                 origin,
                 member_id,
                 title,
                 description,
                 stake_balance,
-                proposal_code,
-                proposal_parameters,
-                proposal_details,
-            )?;
+                proposal_details: proposal_details.clone(),
+                proposal_parameters: proposal_types::parameters::slash_working_group_leader_stake_proposal::<T>(),
+                proposal_code: T::ProposalEncoder::encode_proposal(proposal_details)
+            };
+
+            Self::create_proposal(params)?;
         }
 
+        /// Create 'set working group leader reward' proposal type.
+        /// This proposal uses `update_reward_amount()` extrinsic from the `working-group`  module.
+        pub fn create_set_working_group_leader_reward_proposal(
+            origin,
+            member_id: MemberId<T>,
+            title: Vec<u8>,
+            description: Vec<u8>,
+            stake_balance: Option<BalanceOf<T>>,
+            worker_id: working_group::WorkerId<T>,
+            reward_amount: BalanceOfMint<T>,
+            working_group: WorkingGroup,
+        ) {
+
+            let proposal_details = ProposalDetails::SetWorkingGroupLeaderReward(
+                worker_id,
+                reward_amount,
+                working_group
+            );
+
+            let params = CreateProposalParameters{
+                origin,
+                member_id,
+                title,
+                description,
+                stake_balance,
+                proposal_details: proposal_details.clone(),
+                proposal_parameters: proposal_types::parameters::set_working_group_leader_reward_proposal::<T>(),
+                proposal_code: T::ProposalEncoder::encode_proposal(proposal_details)
+            };
+
+            Self::create_proposal(params)?;
+        }
+
+
 // *************** Extrinsic to execute
 
         /// Text proposal extrinsic. Should be used as callable object to pass to the `engine` module.
@@ -591,48 +882,39 @@ decl_module! {
 
 impl<T: Trait> Module<T> {
     // Generic template proposal builder
-    fn create_proposal(
-        origin: T::Origin,
-        member_id: MemberId<T>,
-        title: Vec<u8>,
-        description: Vec<u8>,
-        stake_balance: Option<BalanceOf<T>>,
-        proposal_code: Vec<u8>,
-        proposal_parameters: ProposalParameters<T::BlockNumber, BalanceOf<T>>,
-        proposal_details: ProposalDetails<
-            BalanceOfMint<T>,
-            BalanceOfGovernanceCurrency<T>,
-            T::BlockNumber,
-            T::AccountId,
-            T::MemberId,
-        >,
-    ) -> DispatchResult<Error> {
-        let account_id = T::MembershipOriginValidator::ensure_actor_origin(origin, member_id)?;
+    fn create_proposal(params: CreateProposalParameters<T>) -> DispatchResult<Error> {
+        let account_id =
+            T::MembershipOriginValidator::ensure_actor_origin(params.origin, params.member_id)?;
 
         <proposal_engine::Module<T>>::ensure_create_proposal_parameters_are_valid(
-            &proposal_parameters,
-            &title,
-            &description,
-            stake_balance,
+            &params.proposal_parameters,
+            &params.title,
+            &params.description,
+            params.stake_balance,
         )?;
 
-        <proposal_discussion::Module<T>>::ensure_can_create_thread(member_id, &title)?;
+        <proposal_discussion::Module<T>>::ensure_can_create_thread(
+            params.member_id,
+            &params.title,
+        )?;
 
-        let discussion_thread_id =
-            <proposal_discussion::Module<T>>::create_thread(member_id, title.clone())?;
+        let discussion_thread_id = <proposal_discussion::Module<T>>::create_thread(
+            params.member_id,
+            params.title.clone(),
+        )?;
 
         let proposal_id = <proposal_engine::Module<T>>::create_proposal(
             account_id,
-            member_id,
-            proposal_parameters,
-            title,
-            description,
-            stake_balance,
-            proposal_code,
+            params.member_id,
+            params.proposal_parameters,
+            params.title,
+            params.description,
+            params.stake_balance,
+            params.proposal_code,
         )?;
 
         <ThreadIdByProposalId<T>>::insert(proposal_id, discussion_thread_id);
-        <ProposalDetailsByProposalId<T>>::insert(proposal_id, proposal_details);
+        <ProposalDetailsByProposalId<T>>::insert(proposal_id, params.proposal_details);
 
         Ok(())
     }
@@ -783,5 +1065,51 @@ impl<T: Trait> Module<T> {
         <SpendingProposalGracePeriod<T>>::put(T::BlockNumber::from(
             p.spending_proposal_grace_period,
         ));
-    }
+        <AddWorkingGroupOpeningProposalVotingPeriod<T>>::put(T::BlockNumber::from(
+            p.add_working_group_opening_proposal_voting_period,
+        ));
+        <AddWorkingGroupOpeningProposalGracePeriod<T>>::put(T::BlockNumber::from(
+            p.add_working_group_opening_proposal_grace_period,
+        ));
+        <BeginReviewWorkingGroupLeaderApplicationsProposalVotingPeriod<T>>::put(
+            T::BlockNumber::from(
+                p.begin_review_working_group_leader_applications_proposal_voting_period,
+            ),
+        );
+        <BeginReviewWorkingGroupLeaderApplicationsProposalGracePeriod<T>>::put(
+            T::BlockNumber::from(
+                p.begin_review_working_group_leader_applications_proposal_grace_period,
+            ),
+        );
+        <FillWorkingGroupLeaderOpeningProposalVotingPeriod<T>>::put(T::BlockNumber::from(
+            p.fill_working_group_leader_opening_proposal_voting_period,
+        ));
+        <FillWorkingGroupLeaderOpeningProposalGracePeriod<T>>::put(T::BlockNumber::from(
+            p.fill_working_group_leader_opening_proposal_grace_period,
+        ));
+        <SetWorkingGroupMintCapacityProposalVotingPeriod<T>>::put(T::BlockNumber::from(
+            p.set_working_group_mint_capacity_proposal_voting_period,
+        ));
+        <SetWorkingGroupMintCapacityProposalGracePeriod<T>>::put(T::BlockNumber::from(
+            p.set_working_group_mint_capacity_proposal_grace_period,
+        ));
+        <DecreaseWorkingGroupLeaderStakeProposalVotingPeriod<T>>::put(T::BlockNumber::from(
+            p.decrease_working_group_leader_stake_proposal_voting_period,
+        ));
+        <DecreaseWorkingGroupLeaderStakeProposalGracePeriod<T>>::put(T::BlockNumber::from(
+            p.decrease_working_group_leader_stake_proposal_grace_period,
+        ));
+        <SlashWorkingGroupLeaderStakeProposalVotingPeriod<T>>::put(T::BlockNumber::from(
+            p.slash_working_group_leader_stake_proposal_voting_period,
+        ));
+        <SlashWorkingGroupLeaderStakeProposalGracePeriod<T>>::put(T::BlockNumber::from(
+            p.slash_working_group_leader_stake_proposal_grace_period,
+        ));
+        <SetWorkingGroupLeaderRewardProposalVotingPeriod<T>>::put(T::BlockNumber::from(
+            p.set_working_group_leader_reward_proposal_voting_period,
+        ));
+        <SetWorkingGroupLeaderRewardProposalGracePeriod<T>>::put(T::BlockNumber::from(
+            p.set_working_group_leader_reward_proposal_grace_period,
+        ));
+    } //TODO set defaults for new proposals
 }

+ 151 - 3
runtime-modules/proposals/codex/src/proposal_types/mod.rs

@@ -8,6 +8,7 @@ use rstd::vec::Vec;
 use serde::{Deserialize, Serialize};
 
 use crate::ElectionParameters;
+use common::working_group::WorkingGroup;
 
 /// Encodes proposal using its details information.
 pub trait ProposalEncoder<T: crate::Trait> {
@@ -22,12 +23,26 @@ pub type ProposalDetailsOf<T> = ProposalDetails<
     <T as system::Trait>::BlockNumber,
     <T as system::Trait>::AccountId,
     crate::MemberId<T>,
+    working_group::OpeningId<T>,
+    working_group::ApplicationId<T>,
+    crate::BalanceOf<T>,
+    working_group::WorkerId<T>,
 >;
 
 /// Proposal details provide voters the information required for the perceived voting.
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
 #[derive(Encode, Decode, Clone, PartialEq, Debug)]
-pub enum ProposalDetails<MintedBalance, CurrencyBalance, BlockNumber, AccountId, MemberId> {
+pub enum ProposalDetails<
+    MintedBalance,
+    CurrencyBalance,
+    BlockNumber,
+    AccountId,
+    MemberId,
+    OpeningId,
+    ApplicationId,
+    StakeBalance,
+    WorkerId,
+> {
     /// The text of the `text` proposal
     Text(Vec<u8>),
 
@@ -58,16 +73,93 @@ pub enum ProposalDetails<MintedBalance, CurrencyBalance, BlockNumber, AccountId,
     /// It is kept only for backward compatibility in the Pioneer. **********
     /// Role parameters for the `set storage role parameters` proposal
     SetStorageRoleParameters(RoleParameters<CurrencyBalance, BlockNumber>),
+
+    /// Add opening for the working group leader position.
+    AddWorkingGroupLeaderOpening(AddOpeningParameters<BlockNumber, CurrencyBalance>),
+
+    /// Begin review applications for the working group leader position.
+    BeginReviewWorkingGroupLeaderApplications(OpeningId, WorkingGroup),
+
+    /// Fill opening for the working group leader position.
+    FillWorkingGroupLeaderOpening(
+        FillOpeningParameters<BlockNumber, MintedBalance, OpeningId, ApplicationId>,
+    ),
+
+    /// Balance for the `set working group mint capacity` proposal
+    SetWorkingGroupMintCapacity(MintedBalance, WorkingGroup),
+
+    /// Balance for the `decrease working group leader stake` proposal
+    DecreaseWorkingGroupLeaderStake(WorkerId, StakeBalance, WorkingGroup),
+
+    /// Balance for the `slash working group leader stake` proposal
+    SlashWorkingGroupLeaderStake(WorkerId, StakeBalance, WorkingGroup),
+
+    /// Balance for the `set working group leader reward` proposal
+    SetWorkingGroupLeaderReward(WorkerId, MintedBalance, WorkingGroup),
 }
 
-impl<MintedBalance, CurrencyBalance, BlockNumber, AccountId, MemberId> Default
-    for ProposalDetails<MintedBalance, CurrencyBalance, BlockNumber, AccountId, MemberId>
+impl<
+        MintedBalance,
+        CurrencyBalance,
+        BlockNumber,
+        AccountId,
+        MemberId,
+        OpeningId,
+        ApplicationId,
+        StakeBalance,
+        WorkerId,
+    > Default
+    for ProposalDetails<
+        MintedBalance,
+        CurrencyBalance,
+        BlockNumber,
+        AccountId,
+        MemberId,
+        OpeningId,
+        ApplicationId,
+        StakeBalance,
+        WorkerId,
+    >
 {
     fn default() -> Self {
         ProposalDetails::Text(b"invalid proposal details".to_vec())
     }
 }
 
+/// Parameters for the 'fill opening for the leader position' proposal.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Clone, PartialEq, Debug)]
+pub struct FillOpeningParameters<BlockNumber, Balance, OpeningId, ApplicationId> {
+    /// Finalizing opening id.
+    pub opening_id: OpeningId,
+
+    /// Id of the selected application.
+    pub successful_application_id: ApplicationId,
+
+    /// Position reward policy.
+    pub reward_policy: Option<working_group::RewardPolicy<Balance, BlockNumber>>,
+
+    /// Defines working group with the open position.
+    pub working_group: WorkingGroup,
+}
+
+/// Parameters for the 'add opening for the leader position' proposal.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Clone, PartialEq, Debug)]
+pub struct AddOpeningParameters<BlockNumber, Balance> {
+    /// Activate opening at block.
+    pub activate_at: hiring::ActivateOpeningAt<BlockNumber>,
+
+    /// Opening conditions.
+    pub commitment: working_group::OpeningPolicyCommitment<BlockNumber, Balance>,
+
+    /// Opening description.
+    pub human_readable_text: Vec<u8>,
+
+    /// Defines working group with the open position.
+    pub working_group: WorkingGroup,
+}
+
 /// ********** Deprecated during the Nicaea release.
 /// It is kept only for backward compatibility in the Pioneer. **********
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
@@ -150,6 +242,48 @@ pub struct ProposalsConfigParameters {
 
     /// 'Spending' proposal grace period
     pub spending_proposal_grace_period: u32,
+
+    /// 'Add working group opening' proposal voting period
+    pub add_working_group_opening_proposal_voting_period: u32,
+
+    /// 'Add working group opening' proposal grace period
+    pub add_working_group_opening_proposal_grace_period: u32,
+
+    /// 'Begin review working group leader applications' proposal voting period
+    pub begin_review_working_group_leader_applications_proposal_voting_period: u32,
+
+    /// 'Begin review working group leader applications' proposal grace period
+    pub begin_review_working_group_leader_applications_proposal_grace_period: u32,
+
+    /// 'Fill working group leader opening' proposal voting period
+    pub fill_working_group_leader_opening_proposal_voting_period: u32,
+
+    /// 'Fill working group leader opening' proposal grace period
+    pub fill_working_group_leader_opening_proposal_grace_period: u32,
+
+    /// 'Set working group mint capacity' proposal voting period
+    pub set_working_group_mint_capacity_proposal_voting_period: u32,
+
+    /// 'Set working group mint capacity' proposal grace period
+    pub set_working_group_mint_capacity_proposal_grace_period: u32,
+
+    /// 'Decrease working group leader stake' proposal voting period
+    pub decrease_working_group_leader_stake_proposal_voting_period: u32,
+
+    /// 'Decrease working group leader stake' proposal grace period
+    pub decrease_working_group_leader_stake_proposal_grace_period: u32,
+
+    /// 'Slash working group leader stake' proposal voting period
+    pub slash_working_group_leader_stake_proposal_voting_period: u32,
+
+    /// 'Slash working group leader stake' proposal grace period
+    pub slash_working_group_leader_stake_proposal_grace_period: u32,
+
+    /// 'Set working group leader reward' proposal voting period
+    pub set_working_group_leader_reward_proposal_voting_period: u32,
+
+    /// 'Set working group leader reward' proposal grace period
+    pub set_working_group_leader_reward_proposal_grace_period: u32,
 }
 
 impl Default for ProposalsConfigParameters {
@@ -169,6 +303,20 @@ impl Default for ProposalsConfigParameters {
             set_lead_proposal_grace_period: 0u32,
             spending_proposal_voting_period: 72000u32,
             spending_proposal_grace_period: 14400u32,
+            add_working_group_opening_proposal_voting_period: 72000u32,
+            add_working_group_opening_proposal_grace_period: 0u32,
+            begin_review_working_group_leader_applications_proposal_voting_period: 43200u32,
+            begin_review_working_group_leader_applications_proposal_grace_period: 14400u32,
+            fill_working_group_leader_opening_proposal_voting_period: 43200u32,
+            fill_working_group_leader_opening_proposal_grace_period: 0u32,
+            set_working_group_mint_capacity_proposal_voting_period: 43200u32,
+            set_working_group_mint_capacity_proposal_grace_period: 0u32,
+            decrease_working_group_leader_stake_proposal_voting_period: 43200u32,
+            decrease_working_group_leader_stake_proposal_grace_period: 0u32,
+            slash_working_group_leader_stake_proposal_voting_period: 43200u32,
+            slash_working_group_leader_stake_proposal_grace_period: 0u32,
+            set_working_group_leader_reward_proposal_voting_period: 43200u32,
+            set_working_group_leader_reward_proposal_grace_period: 0u32,
         }
     }
 }

+ 100 - 0
runtime-modules/proposals/codex/src/proposal_types/parameters.rs

@@ -97,3 +97,103 @@ pub(crate) fn set_lead_proposal<T: crate::Trait>(
         required_stake: Some(<BalanceOf<T>>::from(50000u32)),
     }
 }
+
+// Proposal parameters for the 'Add working group leader' proposal
+pub(crate) fn add_working_group_leader_opening_proposal<T: crate::Trait>(
+) -> ProposalParameters<T::BlockNumber, BalanceOf<T>> {
+    ProposalParameters {
+        voting_period: <Module<T>>::add_working_group_opening_proposal_voting_period(),
+        grace_period: <Module<T>>::add_working_group_opening_proposal_grace_period(),
+        approval_quorum_percentage: 60,
+        approval_threshold_percentage: 80,
+        slashing_quorum_percentage: 60,
+        slashing_threshold_percentage: 80,
+        required_stake: Some(<BalanceOf<T>>::from(100_000_u32)),
+    }
+}
+
+// Proposal parameters for the 'Begin review working group leader applications' proposal
+pub(crate) fn begin_review_working_group_leader_applications_proposal<T: crate::Trait>(
+) -> ProposalParameters<T::BlockNumber, BalanceOf<T>> {
+    ProposalParameters {
+        voting_period:
+            <Module<T>>::begin_review_working_group_leader_applications_proposal_voting_period(),
+        grace_period:
+            <Module<T>>::begin_review_working_group_leader_applications_proposal_grace_period(),
+        approval_quorum_percentage: 60,
+        approval_threshold_percentage: 75,
+        slashing_quorum_percentage: 60,
+        slashing_threshold_percentage: 80,
+        required_stake: Some(<BalanceOf<T>>::from(25000u32)),
+    }
+}
+
+// Proposal parameters for the 'Fill working group leader opening' proposal
+pub(crate) fn fill_working_group_leader_opening_proposal<T: crate::Trait>(
+) -> ProposalParameters<T::BlockNumber, BalanceOf<T>> {
+    ProposalParameters {
+        voting_period: <Module<T>>::fill_working_group_leader_opening_proposal_voting_period(),
+        grace_period: <Module<T>>::fill_working_group_leader_opening_proposal_grace_period(),
+        approval_quorum_percentage: 60,
+        approval_threshold_percentage: 75,
+        slashing_quorum_percentage: 60,
+        slashing_threshold_percentage: 80,
+        required_stake: Some(<BalanceOf<T>>::from(50000u32)),
+    }
+}
+
+// Proposal parameters for the 'Set working group mint capacity' proposal
+pub(crate) fn set_working_group_mint_capacity_proposal<T: crate::Trait>(
+) -> ProposalParameters<T::BlockNumber, BalanceOf<T>> {
+    ProposalParameters {
+        voting_period: <Module<T>>::set_working_group_mint_capacity_proposal_voting_period(),
+        grace_period: <Module<T>>::set_working_group_mint_capacity_proposal_grace_period(),
+        approval_quorum_percentage: 60,
+        approval_threshold_percentage: 75,
+        slashing_quorum_percentage: 60,
+        slashing_threshold_percentage: 80,
+        required_stake: Some(<BalanceOf<T>>::from(50000u32)),
+    }
+}
+
+// Proposal parameters for the 'Decrease working group leader stake' proposal
+pub(crate) fn decrease_working_group_leader_stake_proposal<T: crate::Trait>(
+) -> ProposalParameters<T::BlockNumber, BalanceOf<T>> {
+    ProposalParameters {
+        voting_period: <Module<T>>::decrease_working_group_leader_stake_proposal_voting_period(),
+        grace_period: <Module<T>>::decrease_working_group_leader_stake_proposal_grace_period(),
+        approval_quorum_percentage: 60,
+        approval_threshold_percentage: 75,
+        slashing_quorum_percentage: 60,
+        slashing_threshold_percentage: 80,
+        required_stake: Some(<BalanceOf<T>>::from(50000u32)),
+    }
+}
+
+// Proposal parameters for the 'Slash working group leader stake' proposal
+pub(crate) fn slash_working_group_leader_stake_proposal<T: crate::Trait>(
+) -> ProposalParameters<T::BlockNumber, BalanceOf<T>> {
+    ProposalParameters {
+        voting_period: <Module<T>>::slash_working_group_leader_stake_proposal_voting_period(),
+        grace_period: <Module<T>>::slash_working_group_leader_stake_proposal_grace_period(),
+        approval_quorum_percentage: 60,
+        approval_threshold_percentage: 75,
+        slashing_quorum_percentage: 60,
+        slashing_threshold_percentage: 80,
+        required_stake: Some(<BalanceOf<T>>::from(50000u32)),
+    }
+}
+
+// Proposal parameters for the 'Set working group leader reward' proposal
+pub(crate) fn set_working_group_leader_reward_proposal<T: crate::Trait>(
+) -> ProposalParameters<T::BlockNumber, BalanceOf<T>> {
+    ProposalParameters {
+        voting_period: <Module<T>>::set_working_group_leader_reward_proposal_voting_period(),
+        grace_period: <Module<T>>::set_working_group_leader_reward_proposal_grace_period(),
+        approval_quorum_percentage: 60,
+        approval_threshold_percentage: 75,
+        slashing_quorum_percentage: 60,
+        slashing_threshold_percentage: 80,
+        required_stake: Some(<BalanceOf<T>>::from(50000u32)),
+    }
+}

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

@@ -13,6 +13,10 @@ use srml_support::dispatch::DispatchResult;
 use crate::proposal_types::ProposalsConfigParameters;
 pub use mock::*;
 
+use common::working_group::WorkingGroup;
+use hiring::ActivateOpeningAt;
+use working_group::OpeningPolicyCommitment;
+
 pub(crate) fn increase_total_balance_issuance(balance: u64) {
     increase_total_balance_issuance_using_account_id(999, balance);
 }
@@ -37,7 +41,7 @@ where
     invalid_stake_call: InvalidStakeCall,
     successful_call: SuccessfulCall,
     proposal_parameters: ProposalParameters<u64, u64>,
-    proposal_details: ProposalDetails<u64, u64, u64, u64, u64>,
+    proposal_details: ProposalDetails<u64, u64, u64, u64, u64, u64, u64, u64, u64>,
 }
 
 impl<InsufficientRightsCall, EmptyStakeCall, InvalidStakeCall, SuccessfulCall>
@@ -466,7 +470,7 @@ fn create_set_election_parameters_call_fails_with_incorrect_parameters() {
 }
 
 #[test]
-fn create_working_group_mint_capacity_proposal_fails_with_invalid_parameters() {
+fn create_content_working_group_mint_capacity_proposal_fails_with_invalid_parameters() {
     initial_test_ext().execute_with(|| {
         increase_total_balance_issuance_using_account_id(1, 500000);
 
@@ -479,7 +483,7 @@ fn create_working_group_mint_capacity_proposal_fails_with_invalid_parameters() {
                 Some(<BalanceOf<Test>>::from(50000u32)),
                 (crate::CONTENT_WORKING_GROUP_MINT_CAPACITY_MAX_VALUE + 1) as u64,
             ),
-            Err(Error::InvalidStorageWorkingGroupMintCapacity)
+            Err(Error::InvalidContentWorkingGroupMintCapacity)
         );
     });
 }
@@ -858,3 +862,532 @@ fn set_default_proposal_parameters_succeeded() {
         );
     });
 }
+
+#[test]
+fn create_add_working_group_leader_opening_proposal_common_checks_succeed() {
+    initial_test_ext().execute_with(|| {
+        let add_opening_parameters = AddOpeningParameters {
+            activate_at: ActivateOpeningAt::CurrentBlock,
+            commitment: OpeningPolicyCommitment::default(),
+            human_readable_text: b"some text".to_vec(),
+            working_group: WorkingGroup::Storage,
+        };
+
+        increase_total_balance_issuance_using_account_id(1, 500000);
+
+        let proposal_fixture = ProposalTestFixture {
+            insufficient_rights_call: || {
+                ProposalCodex::create_add_working_group_leader_opening_proposal(
+                    RawOrigin::None.into(),
+                    1,
+                    b"title".to_vec(),
+                    b"body".to_vec(),
+                    None,
+                    add_opening_parameters.clone(),
+                )
+            },
+            empty_stake_call: || {
+                ProposalCodex::create_add_working_group_leader_opening_proposal(
+                    RawOrigin::Signed(1).into(),
+                    1,
+                    b"title".to_vec(),
+                    b"body".to_vec(),
+                    None,
+                    add_opening_parameters.clone(),
+                )
+            },
+            invalid_stake_call: || {
+                ProposalCodex::create_add_working_group_leader_opening_proposal(
+                    RawOrigin::Signed(1).into(),
+                    1,
+                    b"title".to_vec(),
+                    b"body".to_vec(),
+                    Some(<BalanceOf<Test>>::from(5000u32)),
+                    add_opening_parameters.clone(),
+                )
+            },
+            successful_call: || {
+                ProposalCodex::create_add_working_group_leader_opening_proposal(
+                    RawOrigin::Signed(1).into(),
+                    1,
+                    b"title".to_vec(),
+                    b"body".to_vec(),
+                    Some(<BalanceOf<Test>>::from(100_000_u32)),
+                    add_opening_parameters.clone(),
+                )
+            },
+            proposal_parameters: crate::proposal_types::parameters::add_working_group_leader_opening_proposal::<
+                Test,
+            >(),
+            proposal_details: ProposalDetails::AddWorkingGroupLeaderOpening(add_opening_parameters.clone()),
+        };
+        proposal_fixture.check_all();
+    });
+}
+
+#[test]
+fn create_begin_review_working_group_leader_applications_proposal_common_checks_succeed() {
+    initial_test_ext().execute_with(|| {
+        let opening_id = 1; // random opening id.
+
+        increase_total_balance_issuance_using_account_id(1, 500000);
+
+        let proposal_fixture = ProposalTestFixture {
+            insufficient_rights_call: || {
+                ProposalCodex::create_begin_review_working_group_leader_applications_proposal(
+                    RawOrigin::None.into(),
+                    1,
+                    b"title".to_vec(),
+                    b"body".to_vec(),
+                    None,
+                    opening_id,
+                    WorkingGroup::Storage
+                )
+            },
+            empty_stake_call: || {
+                ProposalCodex::create_begin_review_working_group_leader_applications_proposal(
+                    RawOrigin::Signed(1).into(),
+                    1,
+                    b"title".to_vec(),
+                    b"body".to_vec(),
+                    None,
+                    opening_id,
+                    WorkingGroup::Storage
+                )
+            },
+            invalid_stake_call: || {
+                ProposalCodex::create_begin_review_working_group_leader_applications_proposal(
+                    RawOrigin::Signed(1).into(),
+                    1,
+                    b"title".to_vec(),
+                    b"body".to_vec(),
+                    Some(<BalanceOf<Test>>::from(5000u32)),
+                    opening_id,
+                    WorkingGroup::Storage
+                )
+            },
+            successful_call: || {
+                ProposalCodex::create_begin_review_working_group_leader_applications_proposal(
+                    RawOrigin::Signed(1).into(),
+                    1,
+                    b"title".to_vec(),
+                    b"body".to_vec(),
+                    Some(<BalanceOf<Test>>::from(25000u32)),
+                    opening_id,
+                    WorkingGroup::Storage
+                )
+            },
+            proposal_parameters: crate::proposal_types::parameters::begin_review_working_group_leader_applications_proposal::<
+                Test,
+            >(),
+            proposal_details: ProposalDetails::BeginReviewWorkingGroupLeaderApplications(opening_id,
+                WorkingGroup::Storage),
+        };
+        proposal_fixture.check_all();
+    });
+}
+
+#[test]
+fn create_fill_working_group_leader_opening_proposal_common_checks_succeed() {
+    initial_test_ext().execute_with(|| {
+        let opening_id = 1; // random opening id.
+
+        let fill_opening_parameters = FillOpeningParameters {
+            opening_id,
+            successful_application_id: 1,
+            reward_policy: None,
+            working_group: WorkingGroup::Storage,
+        };
+
+        increase_total_balance_issuance_using_account_id(1, 500000);
+
+        let proposal_fixture = ProposalTestFixture {
+            insufficient_rights_call: || {
+                ProposalCodex::create_fill_working_group_leader_opening_proposal(
+                    RawOrigin::None.into(),
+                    1,
+                    b"title".to_vec(),
+                    b"body".to_vec(),
+                    None,
+                    fill_opening_parameters.clone()
+                )
+            },
+            empty_stake_call: || {
+                ProposalCodex::create_fill_working_group_leader_opening_proposal(
+                    RawOrigin::Signed(1).into(),
+                    1,
+                    b"title".to_vec(),
+                    b"body".to_vec(),
+                    None,
+                    fill_opening_parameters.clone()
+                )
+            },
+            invalid_stake_call: || {
+                ProposalCodex::create_fill_working_group_leader_opening_proposal(
+                    RawOrigin::Signed(1).into(),
+                    1,
+                    b"title".to_vec(),
+                    b"body".to_vec(),
+                    Some(<BalanceOf<Test>>::from(5000u32)),
+                    fill_opening_parameters.clone()
+                )
+            },
+            successful_call: || {
+                ProposalCodex::create_fill_working_group_leader_opening_proposal(
+                    RawOrigin::Signed(1).into(),
+                    1,
+                    b"title".to_vec(),
+                    b"body".to_vec(),
+                    Some(<BalanceOf<Test>>::from(50000u32)),
+                    fill_opening_parameters.clone()
+                )
+            },
+            proposal_parameters: crate::proposal_types::parameters::fill_working_group_leader_opening_proposal::<
+                Test,
+            >(),
+            proposal_details: ProposalDetails::FillWorkingGroupLeaderOpening(fill_opening_parameters.clone()),
+        };
+        proposal_fixture.check_all();
+    });
+}
+
+#[test]
+fn create_working_group_mint_capacity_proposal_fails_with_invalid_parameters() {
+    initial_test_ext().execute_with(|| {
+        increase_total_balance_issuance_using_account_id(1, 500000);
+
+        assert_eq!(
+            ProposalCodex::create_set_working_group_mint_capacity_proposal(
+                RawOrigin::Signed(1).into(),
+                1,
+                b"title".to_vec(),
+                b"body".to_vec(),
+                Some(<BalanceOf<Test>>::from(50000u32)),
+                (crate::WORKING_GROUP_MINT_CAPACITY_MAX_VALUE + 1) as u64,
+                WorkingGroup::Storage,
+            ),
+            Err(Error::InvalidWorkingGroupMintCapacity)
+        );
+    });
+}
+
+#[test]
+fn create_set_working_group_mint_capacity_proposal_common_checks_succeed() {
+    initial_test_ext().execute_with(|| {
+        increase_total_balance_issuance(500000);
+
+        let proposal_fixture = ProposalTestFixture {
+            insufficient_rights_call: || {
+                ProposalCodex::create_set_working_group_mint_capacity_proposal(
+                    RawOrigin::None.into(),
+                    1,
+                    b"title".to_vec(),
+                    b"body".to_vec(),
+                    None,
+                    0,
+                    WorkingGroup::Storage,
+                )
+            },
+            empty_stake_call: || {
+                ProposalCodex::create_set_working_group_mint_capacity_proposal(
+                    RawOrigin::Signed(1).into(),
+                    1,
+                    b"title".to_vec(),
+                    b"body".to_vec(),
+                    None,
+                    0,
+                    WorkingGroup::Storage,
+                )
+            },
+            invalid_stake_call: || {
+                ProposalCodex::create_set_working_group_mint_capacity_proposal(
+                    RawOrigin::Signed(1).into(),
+                    1,
+                    b"title".to_vec(),
+                    b"body".to_vec(),
+                    Some(<BalanceOf<Test>>::from(5000u32)),
+                    0,
+                    WorkingGroup::Storage,
+                )
+            },
+            successful_call: || {
+                ProposalCodex::create_set_working_group_mint_capacity_proposal(
+                    RawOrigin::Signed(1).into(),
+                    1,
+                    b"title".to_vec(),
+                    b"body".to_vec(),
+                    Some(<BalanceOf<Test>>::from(50000u32)),
+                    10,
+                    WorkingGroup::Storage,
+                )
+            },
+            proposal_parameters:
+                crate::proposal_types::parameters::set_working_group_mint_capacity_proposal::<Test>(
+                ),
+            proposal_details: ProposalDetails::SetWorkingGroupMintCapacity(
+                10,
+                WorkingGroup::Storage,
+            ),
+        };
+        proposal_fixture.check_all();
+    });
+}
+
+#[test]
+fn create_decrease_working_group_leader_stake_proposal_common_checks_succeed() {
+    initial_test_ext().execute_with(|| {
+        increase_total_balance_issuance(500000);
+
+        let proposal_fixture = ProposalTestFixture {
+            insufficient_rights_call: || {
+                ProposalCodex::create_decrease_working_group_leader_stake_proposal(
+                    RawOrigin::None.into(),
+                    1,
+                    b"title".to_vec(),
+                    b"body".to_vec(),
+                    None,
+                    0,
+                    10,
+                    WorkingGroup::Storage,
+                )
+            },
+            empty_stake_call: || {
+                ProposalCodex::create_decrease_working_group_leader_stake_proposal(
+                    RawOrigin::Signed(1).into(),
+                    1,
+                    b"title".to_vec(),
+                    b"body".to_vec(),
+                    None,
+                    0,
+                    10,
+                    WorkingGroup::Storage,
+                )
+            },
+            invalid_stake_call: || {
+                ProposalCodex::create_decrease_working_group_leader_stake_proposal(
+                    RawOrigin::Signed(1).into(),
+                    1,
+                    b"title".to_vec(),
+                    b"body".to_vec(),
+                    Some(<BalanceOf<Test>>::from(5000u32)),
+                    0,
+                    10,
+                    WorkingGroup::Storage,
+                )
+            },
+            successful_call: || {
+                ProposalCodex::create_decrease_working_group_leader_stake_proposal(
+                    RawOrigin::Signed(1).into(),
+                    1,
+                    b"title".to_vec(),
+                    b"body".to_vec(),
+                    Some(<BalanceOf<Test>>::from(50000u32)),
+                    10,
+                    10,
+                    WorkingGroup::Storage,
+                )
+            },
+            proposal_parameters:
+                crate::proposal_types::parameters::decrease_working_group_leader_stake_proposal::<
+                    Test,
+                >(),
+            proposal_details: ProposalDetails::DecreaseWorkingGroupLeaderStake(
+                10,
+                10,
+                WorkingGroup::Storage,
+            ),
+        };
+        proposal_fixture.check_all();
+    });
+}
+
+#[test]
+fn create_slash_working_group_leader_stake_proposal_common_checks_succeed() {
+    initial_test_ext().execute_with(|| {
+        increase_total_balance_issuance(500000);
+
+        let proposal_fixture = ProposalTestFixture {
+            insufficient_rights_call: || {
+                ProposalCodex::create_slash_working_group_leader_stake_proposal(
+                    RawOrigin::None.into(),
+                    1,
+                    b"title".to_vec(),
+                    b"body".to_vec(),
+                    None,
+                    0,
+                    10,
+                    WorkingGroup::Storage,
+                )
+            },
+            empty_stake_call: || {
+                ProposalCodex::create_slash_working_group_leader_stake_proposal(
+                    RawOrigin::Signed(1).into(),
+                    1,
+                    b"title".to_vec(),
+                    b"body".to_vec(),
+                    None,
+                    0,
+                    10,
+                    WorkingGroup::Storage,
+                )
+            },
+            invalid_stake_call: || {
+                ProposalCodex::create_slash_working_group_leader_stake_proposal(
+                    RawOrigin::Signed(1).into(),
+                    1,
+                    b"title".to_vec(),
+                    b"body".to_vec(),
+                    Some(<BalanceOf<Test>>::from(5000u32)),
+                    0,
+                    10,
+                    WorkingGroup::Storage,
+                )
+            },
+            successful_call: || {
+                ProposalCodex::create_slash_working_group_leader_stake_proposal(
+                    RawOrigin::Signed(1).into(),
+                    1,
+                    b"title".to_vec(),
+                    b"body".to_vec(),
+                    Some(<BalanceOf<Test>>::from(50000u32)),
+                    10,
+                    10,
+                    WorkingGroup::Storage,
+                )
+            },
+            proposal_parameters:
+                crate::proposal_types::parameters::slash_working_group_leader_stake_proposal::<
+                    Test,
+                >(),
+            proposal_details: ProposalDetails::SlashWorkingGroupLeaderStake(
+                10,
+                10,
+                WorkingGroup::Storage,
+            ),
+        };
+        proposal_fixture.check_all();
+    });
+}
+
+#[test]
+fn slash_stake_with_zero_staking_balance_fails() {
+    initial_test_ext().execute_with(|| {
+        increase_total_balance_issuance_using_account_id(1, 500000);
+
+        let lead_account_id = 20;
+        <governance::council::Module<Test>>::set_council(
+            RawOrigin::Root.into(),
+            vec![lead_account_id],
+        )
+        .unwrap();
+
+        assert_eq!(
+            ProposalCodex::create_slash_working_group_leader_stake_proposal(
+                RawOrigin::Signed(1).into(),
+                1,
+                b"title".to_vec(),
+                b"body".to_vec(),
+                Some(<BalanceOf<Test>>::from(50000u32)),
+                10,
+                0,
+                WorkingGroup::Storage,
+            ),
+            Err(Error::SlashingStakeIsZero)
+        );
+    });
+}
+
+#[test]
+fn decrease_stake_with_zero_staking_balance_fails() {
+    initial_test_ext().execute_with(|| {
+        increase_total_balance_issuance_using_account_id(1, 500000);
+
+        let lead_account_id = 20;
+        <governance::council::Module<Test>>::set_council(
+            RawOrigin::Root.into(),
+            vec![lead_account_id],
+        )
+        .unwrap();
+
+        assert_eq!(
+            ProposalCodex::create_decrease_working_group_leader_stake_proposal(
+                RawOrigin::Signed(1).into(),
+                1,
+                b"title".to_vec(),
+                b"body".to_vec(),
+                Some(<BalanceOf<Test>>::from(50000u32)),
+                10,
+                0,
+                WorkingGroup::Storage,
+            ),
+            Err(Error::DecreasingStakeIsZero)
+        );
+    });
+}
+
+#[test]
+fn create_set_working_group_leader_reward_proposal_common_checks_succeed() {
+    initial_test_ext().execute_with(|| {
+        increase_total_balance_issuance(500000);
+
+        let proposal_fixture = ProposalTestFixture {
+            insufficient_rights_call: || {
+                ProposalCodex::create_set_working_group_leader_reward_proposal(
+                    RawOrigin::None.into(),
+                    1,
+                    b"title".to_vec(),
+                    b"body".to_vec(),
+                    None,
+                    0,
+                    10,
+                    WorkingGroup::Storage,
+                )
+            },
+            empty_stake_call: || {
+                ProposalCodex::create_set_working_group_leader_reward_proposal(
+                    RawOrigin::Signed(1).into(),
+                    1,
+                    b"title".to_vec(),
+                    b"body".to_vec(),
+                    None,
+                    0,
+                    10,
+                    WorkingGroup::Storage,
+                )
+            },
+            invalid_stake_call: || {
+                ProposalCodex::create_set_working_group_leader_reward_proposal(
+                    RawOrigin::Signed(1).into(),
+                    1,
+                    b"title".to_vec(),
+                    b"body".to_vec(),
+                    Some(<BalanceOf<Test>>::from(5000u32)),
+                    0,
+                    10,
+                    WorkingGroup::Storage,
+                )
+            },
+            successful_call: || {
+                ProposalCodex::create_set_working_group_leader_reward_proposal(
+                    RawOrigin::Signed(1).into(),
+                    1,
+                    b"title".to_vec(),
+                    b"body".to_vec(),
+                    Some(<BalanceOf<Test>>::from(50000u32)),
+                    10,
+                    10,
+                    WorkingGroup::Storage,
+                )
+            },
+            proposal_parameters:
+                crate::proposal_types::parameters::set_working_group_leader_reward_proposal::<Test>(
+                ),
+            proposal_details: ProposalDetails::SetWorkingGroupLeaderReward(
+                10,
+                10,
+                WorkingGroup::Storage,
+            ),
+        };
+        proposal_fixture.check_all();
+    });
+}

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

@@ -354,7 +354,7 @@ decl_module! {
             // mutation
 
             <Proposals<T>>::insert(proposal_id, proposal);
-            <VoteExistsByProposalByVoter<T>>::insert( proposal_id, voter_id, vote.clone());
+            <VoteExistsByProposalByVoter<T>>::insert(proposal_id, voter_id, vote.clone());
             Self::deposit_event(RawEvent::Voted(voter_id, proposal_id, vote));
         }
 

+ 2 - 1
runtime-modules/working-group/src/types.rs

@@ -237,7 +237,8 @@ pub enum ExitInitiationOrigin {
 }
 
 /// The recurring reward if any to be assigned to an actor when filling in the position.
-#[derive(Encode, Decode, Clone, Eq, PartialEq, Debug)]
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Clone, PartialEq, Debug)]
 pub struct RewardPolicy<Balance, BlockNumber> {
     /// Balance per payout.
     pub amount_per_payout: Balance,

+ 1 - 1
runtime/Cargo.toml

@@ -4,7 +4,7 @@ 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.17.0'
+version = '6.18.0'
 
 [features]
 default = ['std']

+ 167 - 0
runtime/src/integration/proposals/proposal_encoder.rs

@@ -1,7 +1,11 @@
 use crate::{Call, Runtime};
+use common::working_group::WorkingGroup;
 use proposals_codex::{ProposalDetails, ProposalDetailsOf, ProposalEncoder};
+use working_group::OpeningType;
 
 use codec::Encode;
+use rstd::collections::btree_set::BTreeSet;
+use rstd::marker::PhantomData;
 use rstd::vec::Vec;
 use srml_support::print;
 
@@ -51,6 +55,169 @@ impl ProposalEncoder<Runtime> for ExtrinsicProposalEncoder {
                 print("Error: Calling deprecated SetStorageRoleParameters encoding option.");
                 Vec::new()
             }
+            ProposalDetails::AddWorkingGroupLeaderOpening(add_opening_params) => {
+                let call = match add_opening_params.working_group {
+                    WorkingGroup::Storage => {
+                        Call::StorageWorkingGroup(Wg::create_add_opening_call(add_opening_params))
+                    }
+                };
+
+                call.encode()
+            }
+            ProposalDetails::BeginReviewWorkingGroupLeaderApplications(
+                opening_id,
+                working_group,
+            ) => {
+                let call = match working_group {
+                    WorkingGroup::Storage => Call::StorageWorkingGroup(
+                        Wg::create_begin_review_applications_call(opening_id),
+                    ),
+                };
+
+                call.encode()
+            }
+            ProposalDetails::FillWorkingGroupLeaderOpening(fill_opening_params) => {
+                let call = match fill_opening_params.working_group {
+                    WorkingGroup::Storage => {
+                        Call::StorageWorkingGroup(Wg::create_fill_opening_call(fill_opening_params))
+                    }
+                };
+
+                call.encode()
+            }
+            ProposalDetails::SetWorkingGroupMintCapacity(mint_balance, working_group) => {
+                let call = match working_group {
+                    WorkingGroup::Storage => {
+                        Call::StorageWorkingGroup(Wg::create_set_mint_capacity_call(mint_balance))
+                    }
+                };
+
+                call.encode()
+            }
+            ProposalDetails::DecreaseWorkingGroupLeaderStake(
+                worker_id,
+                decreasing_stake,
+                working_group,
+            ) => {
+                let call = match working_group {
+                    WorkingGroup::Storage => Call::StorageWorkingGroup(
+                        Wg::create_decrease_stake_call(worker_id, decreasing_stake),
+                    ),
+                };
+
+                call.encode()
+            }
+            ProposalDetails::SlashWorkingGroupLeaderStake(
+                worker_id,
+                slashing_stake,
+                working_group,
+            ) => {
+                let call = match working_group {
+                    WorkingGroup::Storage => Call::StorageWorkingGroup(
+                        Wg::create_slash_stake_call(worker_id, slashing_stake),
+                    ),
+                };
+
+                call.encode()
+            }
+            ProposalDetails::SetWorkingGroupLeaderReward(
+                worker_id,
+                reward_amount,
+                working_group,
+            ) => {
+                let call = match working_group {
+                    WorkingGroup::Storage => Call::StorageWorkingGroup(Wg::create_set_reward_call(
+                        worker_id,
+                        reward_amount,
+                    )),
+                };
+
+                call.encode()
+            }
         }
     }
 }
+
+// Working group calls container. It helps to instantiate proper working group instance for calls.
+struct Wg<T, I> {
+    phantom_module: PhantomData<T>,
+    phantom_instance: PhantomData<I>,
+}
+
+impl<T, I> Wg<T, I>
+where
+    T: working_group::Trait<I>,
+    I: working_group::Instance,
+{
+    // Generic call constructor for the add working group opening.
+    fn create_add_opening_call(
+        add_opening_params: proposals_codex::AddOpeningParameters<
+            T::BlockNumber,
+            working_group::BalanceOf<T>,
+        >,
+    ) -> working_group::Call<T, I> {
+        working_group::Call::<T, I>::add_opening(
+            add_opening_params.activate_at,
+            add_opening_params.commitment,
+            add_opening_params.human_readable_text,
+            OpeningType::Leader,
+        )
+    }
+
+    // Generic call constructor for the begin review working group applications.
+    fn create_begin_review_applications_call(
+        opening_id: working_group::OpeningId<T>,
+    ) -> working_group::Call<T, I> {
+        working_group::Call::<T, I>::begin_applicant_review(opening_id)
+    }
+
+    // Generic call constructor for the add working group opening.
+    fn create_fill_opening_call(
+        fill_opening_params: proposals_codex::FillOpeningParameters<
+            T::BlockNumber,
+            minting::BalanceOf<T>,
+            working_group::OpeningId<T>,
+            working_group::ApplicationId<T>,
+        >,
+    ) -> working_group::Call<T, I> {
+        let mut successful_application_ids = BTreeSet::new();
+        successful_application_ids.insert(fill_opening_params.successful_application_id);
+
+        working_group::Call::<T, I>::fill_opening(
+            fill_opening_params.opening_id,
+            successful_application_ids,
+            fill_opening_params.reward_policy,
+        )
+    }
+
+    // Generic call constructor for the working group 'set mit capacity'.
+    fn create_set_mint_capacity_call(
+        mint_balance: working_group::BalanceOfMint<T>,
+    ) -> working_group::Call<T, I> {
+        working_group::Call::<T, I>::set_mint_capacity(mint_balance)
+    }
+
+    // Generic call constructor for the working group 'decrease stake'.
+    fn create_decrease_stake_call(
+        worker_id: working_group::WorkerId<T>,
+        decreasing_stake: working_group::BalanceOf<T>,
+    ) -> working_group::Call<T, I> {
+        working_group::Call::<T, I>::decrease_stake(worker_id, decreasing_stake)
+    }
+
+    // Generic call constructor for the working group 'slash stake'.
+    fn create_slash_stake_call(
+        worker_id: working_group::WorkerId<T>,
+        slashing_stake: working_group::BalanceOf<T>,
+    ) -> working_group::Call<T, I> {
+        working_group::Call::<T, I>::slash_stake(worker_id, slashing_stake)
+    }
+
+    // Generic call constructor for the working group 'update reward amount'.
+    fn create_set_reward_call(
+        worker_id: working_group::WorkerId<T>,
+        reward_amount: working_group::BalanceOfMint<T>,
+    ) -> working_group::Call<T, I> {
+        working_group::Call::<T, I>::update_reward_amount(worker_id, reward_amount)
+    }
+}

+ 1 - 1
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: 17,
+    spec_version: 18,
     impl_version: 0,
     apis: RUNTIME_API_VERSIONS,
 };

+ 191 - 104
runtime/src/tests/proposals_integration.rs → runtime/src/tests/proposals_integration/mod.rs

@@ -2,9 +2,10 @@
 
 #![cfg(test)]
 
+mod working_group_proposals;
+
 use crate::{BlockNumber, ElectionParameters, ProposalCancellationFee, Runtime};
 use codec::Encode;
-use governance::election::CouncilElected;
 use membership::members;
 use proposals_engine::{
     ActiveStake, ApprovedProposalStatus, BalanceOf, Error, FinalizationData, Proposal,
@@ -22,14 +23,14 @@ use super::initial_test_ext;
 
 use crate::CouncilManager;
 
-type Balances = balances::Module<Runtime>;
-type System = system::Module<Runtime>;
-type Membership = membership::members::Module<Runtime>;
-type ProposalsEngine = proposals_engine::Module<Runtime>;
-type Council = governance::council::Module<Runtime>;
-type Election = governance::election::Module<Runtime>;
-type ProposalCodex = proposals_codex::Module<Runtime>;
-type Mint = minting::Module<Runtime>;
+pub type Balances = balances::Module<Runtime>;
+pub type System = system::Module<Runtime>;
+pub type Membership = membership::members::Module<Runtime>;
+pub type ProposalsEngine = proposals_engine::Module<Runtime>;
+pub type Council = governance::council::Module<Runtime>;
+pub type Election = governance::election::Module<Runtime>;
+pub type ProposalCodex = proposals_codex::Module<Runtime>;
+pub type Mint = minting::Module<Runtime>;
 
 fn setup_members(count: u8) {
     let authority_account_id = <Runtime as system::Trait>::AccountId::default();
@@ -89,9 +90,11 @@ pub(crate) fn increase_total_balance_issuance_using_account_id(
 fn run_to_block(n: BlockNumber) {
     while System::block_number() < n {
         <System as OnFinalize<BlockNumber>>::on_finalize(System::block_number());
+        <Election as OnFinalize<BlockNumber>>::on_finalize(System::block_number());
         <ProposalsEngine as OnFinalize<BlockNumber>>::on_finalize(System::block_number());
         System::set_block_number(System::block_number() + 1);
         <System as OnInitialize<BlockNumber>>::on_initialize(System::block_number());
+        <Election as OnInitialize<BlockNumber>>::on_initialize(System::block_number());
         <ProposalsEngine as OnInitialize<BlockNumber>>::on_initialize(System::block_number());
     }
 }
@@ -186,6 +189,16 @@ impl DummyProposalFixture {
         DummyProposalFixture { account_id, ..self }
     }
 
+    fn with_voting_period(self, voting_period: u32) -> Self {
+        DummyProposalFixture {
+            parameters: ProposalParameters {
+                voting_period,
+                ..self.parameters
+            },
+            ..self
+        }
+    }
+
     fn with_stake(self, stake_balance: BalanceOf<Runtime>) -> Self {
         DummyProposalFixture {
             stake_balance: Some(stake_balance),
@@ -339,7 +352,7 @@ fn proposal_reset_succeeds() {
         setup_members(4);
         setup_council();
         // create proposal
-        let dummy_proposal = DummyProposalFixture::default();
+        let dummy_proposal = DummyProposalFixture::default().with_voting_period(100);
         let proposal_id = dummy_proposal.create_proposal_and_assert(Ok(1)).unwrap();
 
         // create some votes
@@ -367,9 +380,17 @@ fn proposal_reset_succeeds() {
         // Ensure council was elected
         assert_eq!(CouncilManager::<Runtime>::total_voters_count(), 6);
 
-        // Check proposals CouncilElected hook
-        // just trigger the election hook, we don't care about the parameters
-        <Runtime as governance::election::Trait>::CouncilElected::council_elected(Vec::new(), 10);
+        let voted_member_id = 2;
+        // Check for votes.
+        assert_eq!(
+            ProposalsEngine::vote_by_proposal_by_voter(proposal_id, voted_member_id),
+            VoteKind::Abstain
+        );
+
+        // Check proposals CouncilElected hook just trigger the election hook (empty council).
+        //<Runtime as governance::election::Trait>::CouncilElected::council_elected(Vec::new(), 10);
+
+        elect_single_councilor();
 
         let updated_proposal = ProposalsEngine::proposals(proposal_id);
 
@@ -383,49 +404,130 @@ fn proposal_reset_succeeds() {
             }
         );
 
-        // Check council CouncilElected hook. It should set current council. And we passed empty council.
-        assert_eq!(CouncilManager::<Runtime>::total_voters_count(), 0);
+        // No votes could survive cleaning: should be default value.
+        assert_eq!(
+            ProposalsEngine::vote_by_proposal_by_voter(proposal_id, voted_member_id),
+            VoteKind::default()
+        );
+
+        // Check council CouncilElected hook. It should set current council. And we elected single councilor.
+        assert_eq!(CouncilManager::<Runtime>::total_voters_count(), 1);
     });
 }
 
+fn elect_single_councilor() {
+    let res = Election::set_election_parameters(
+        RawOrigin::Root.into(),
+        ElectionParameters {
+            announcing_period: 1,
+            voting_period: 1,
+            revealing_period: 1,
+            council_size: 1,
+            candidacy_limit: 10,
+            new_term_duration: 2000000,
+            min_council_stake: 0,
+            min_voting_stake: 0,
+        },
+    );
+    assert_eq!(res, Ok(()));
+
+    let res = Election::force_start_election(RawOrigin::Root.into());
+    assert_eq!(res, Ok(()));
+
+    let councilor1: [u8; 32] = [1; 32];
+    increase_total_balance_issuance_using_account_id(councilor1.clone().into(), 1200000000);
+
+    let res = Election::apply(RawOrigin::Signed(councilor1.into()).into(), 0);
+    assert_eq!(res, Ok(()));
+
+    run_to_block(5);
+}
+
 struct CodexProposalTestFixture<SuccessfulCall>
 where
     SuccessfulCall: Fn() -> DispatchResult<proposals_codex::Error>,
 {
     successful_call: SuccessfulCall,
     member_id: u64,
+    setup_environment: bool,
+    proposal_id: u32,
+    run_to_block: u32,
 }
 
 impl<SuccessfulCall> CodexProposalTestFixture<SuccessfulCall>
 where
     SuccessfulCall: Fn() -> DispatchResult<proposals_codex::Error>,
 {
-    fn call_extrinsic_and_assert(&self) {
-        setup_members(15);
-        setup_council();
+    fn default_for_call(call: SuccessfulCall) -> Self {
+        Self {
+            successful_call: call,
+            member_id: 1,
+            setup_environment: true,
+            proposal_id: 1,
+            run_to_block: 2,
+        }
+    }
 
+    fn disable_setup_enviroment(self) -> Self {
+        Self {
+            setup_environment: false,
+            ..self
+        }
+    }
+
+    fn with_member_id(self, member_id: u64) -> Self {
+        Self { member_id, ..self }
+    }
+
+    fn with_expected_proposal_id(self, expected_proposal_id: u32) -> Self {
+        Self {
+            proposal_id: expected_proposal_id,
+            ..self
+        }
+    }
+
+    fn with_run_to_block(self, run_to_block: u32) -> Self {
+        Self {
+            run_to_block,
+            ..self
+        }
+    }
+}
+
+impl<SuccessfulCall> CodexProposalTestFixture<SuccessfulCall>
+where
+    SuccessfulCall: Fn() -> DispatchResult<proposals_codex::Error>,
+{
+    fn call_extrinsic_and_assert(&self) {
         let account_id: [u8; 32] = [self.member_id as u8; 32];
-        increase_total_balance_issuance_using_account_id(account_id.clone().into(), 500000);
 
-        assert_eq!((self.successful_call)(), Ok(()));
+        if self.setup_environment {
+            setup_members(15);
+            setup_council();
 
-        let proposal_id = 1;
+            increase_total_balance_issuance_using_account_id(account_id.clone().into(), 500000);
+        }
 
-        let mut vote_generator = VoteGenerator::new(proposal_id);
+        assert_eq!((self.successful_call)(), Ok(()));
+
+        let mut vote_generator = VoteGenerator::new(self.proposal_id);
         vote_generator.vote_and_assert_ok(VoteKind::Approve);
         vote_generator.vote_and_assert_ok(VoteKind::Approve);
         vote_generator.vote_and_assert_ok(VoteKind::Approve);
         vote_generator.vote_and_assert_ok(VoteKind::Approve);
         vote_generator.vote_and_assert_ok(VoteKind::Approve);
 
-        run_to_block(2);
+        run_to_block(self.run_to_block);
 
-        let proposal = ProposalsEngine::proposals(proposal_id);
+        let proposal = ProposalsEngine::proposals(self.proposal_id);
 
         assert_eq!(
             proposal,
             Proposal {
-                status: ProposalStatus::approved(ApprovedProposalStatus::Executed, 1),
+                status: ProposalStatus::approved(
+                    ApprovedProposalStatus::Executed,
+                    self.run_to_block - 1
+                ),
                 title: b"title".to_vec(),
                 description: b"body".to_vec(),
                 voting_results: VotingResults {
@@ -446,19 +548,17 @@ fn text_proposal_execution_succeeds() {
         let member_id = 10;
         let account_id: [u8; 32] = [member_id; 32];
 
-        let codex_extrinsic_test_fixture = CodexProposalTestFixture {
-            member_id: member_id as u64,
-            successful_call: || {
-                ProposalCodex::create_text_proposal(
-                    RawOrigin::Signed(account_id.into()).into(),
-                    member_id as u64,
-                    b"title".to_vec(),
-                    b"body".to_vec(),
-                    Some(<BalanceOf<Runtime>>::from(25000u32)),
-                    b"text".to_vec(),
-                )
-            },
-        };
+        let codex_extrinsic_test_fixture = CodexProposalTestFixture::default_for_call(|| {
+            ProposalCodex::create_text_proposal(
+                RawOrigin::Signed(account_id.into()).into(),
+                member_id as u64,
+                b"title".to_vec(),
+                b"body".to_vec(),
+                Some(<BalanceOf<Runtime>>::from(25000u32)),
+                b"text".to_vec(),
+            )
+        })
+        .with_member_id(member_id as u64);
 
         codex_extrinsic_test_fixture.call_extrinsic_and_assert();
     });
@@ -470,19 +570,17 @@ fn set_lead_proposal_execution_succeeds() {
         let member_id = 10;
         let account_id: [u8; 32] = [member_id; 32];
 
-        let codex_extrinsic_test_fixture = CodexProposalTestFixture {
-            member_id: member_id as u64,
-            successful_call: || {
-                ProposalCodex::create_set_lead_proposal(
-                    RawOrigin::Signed(account_id.clone().into()).into(),
-                    member_id as u64,
-                    b"title".to_vec(),
-                    b"body".to_vec(),
-                    Some(<BalanceOf<Runtime>>::from(50000u32)),
-                    Some((member_id as u64, account_id.into())),
-                )
-            },
-        };
+        let codex_extrinsic_test_fixture = CodexProposalTestFixture::default_for_call(|| {
+            ProposalCodex::create_set_lead_proposal(
+                RawOrigin::Signed(account_id.clone().into()).into(),
+                member_id as u64,
+                b"title".to_vec(),
+                b"body".to_vec(),
+                Some(<BalanceOf<Runtime>>::from(50000u32)),
+                Some((member_id as u64, account_id.into())),
+            )
+        })
+        .with_member_id(member_id as u64);
 
         assert!(content_working_group::Module::<Runtime>::ensure_lead_is_set().is_err());
 
@@ -503,20 +601,18 @@ fn spending_proposal_execution_succeeds() {
 
         assert!(Council::set_council_mint_capacity(RawOrigin::Root.into(), new_balance).is_ok());
 
-        let codex_extrinsic_test_fixture = CodexProposalTestFixture {
-            member_id: member_id as u64,
-            successful_call: || {
-                ProposalCodex::create_spending_proposal(
-                    RawOrigin::Signed(account_id.clone().into()).into(),
-                    member_id as u64,
-                    b"title".to_vec(),
-                    b"body".to_vec(),
-                    Some(<BalanceOf<Runtime>>::from(25_000_u32)),
-                    new_balance,
-                    target_account_id.clone().into(),
-                )
-            },
-        };
+        let codex_extrinsic_test_fixture = CodexProposalTestFixture::default_for_call(|| {
+            ProposalCodex::create_spending_proposal(
+                RawOrigin::Signed(account_id.clone().into()).into(),
+                member_id as u64,
+                b"title".to_vec(),
+                b"body".to_vec(),
+                Some(<BalanceOf<Runtime>>::from(25_000_u32)),
+                new_balance,
+                target_account_id.clone().into(),
+            )
+        })
+        .with_member_id(member_id as u64);
 
         assert_eq!(
             Balances::free_balance::<AccountId32>(target_account_id.clone().into()),
@@ -545,19 +641,16 @@ fn set_content_working_group_mint_capacity_execution_succeeds() {
 
         assert_eq!(Mint::get_mint_capacity(mint_id), Ok(0));
 
-        let codex_extrinsic_test_fixture = CodexProposalTestFixture {
-            member_id: member_id as u64,
-            successful_call: || {
-                ProposalCodex::create_set_content_working_group_mint_capacity_proposal(
-                    RawOrigin::Signed(account_id.clone().into()).into(),
-                    member_id as u64,
-                    b"title".to_vec(),
-                    b"body".to_vec(),
-                    Some(<BalanceOf<Runtime>>::from(50000u32)),
-                    new_balance,
-                )
-            },
-        };
+        let codex_extrinsic_test_fixture = CodexProposalTestFixture::default_for_call(|| {
+            ProposalCodex::create_set_content_working_group_mint_capacity_proposal(
+                RawOrigin::Signed(account_id.clone().into()).into(),
+                member_id as u64,
+                b"title".to_vec(),
+                b"body".to_vec(),
+                Some(<BalanceOf<Runtime>>::from(50000u32)),
+                new_balance,
+            )
+        });
 
         codex_extrinsic_test_fixture.call_extrinsic_and_assert();
 
@@ -583,19 +676,16 @@ fn set_election_parameters_proposal_execution_succeeds() {
         };
         assert_eq!(Election::announcing_period(), 0);
 
-        let codex_extrinsic_test_fixture = CodexProposalTestFixture {
-            member_id: member_id as u64,
-            successful_call: || {
-                ProposalCodex::create_set_election_parameters_proposal(
-                    RawOrigin::Signed(account_id.clone().into()).into(),
-                    member_id as u64,
-                    b"title".to_vec(),
-                    b"body".to_vec(),
-                    Some(<BalanceOf<Runtime>>::from(200_000_u32)),
-                    election_parameters,
-                )
-            },
-        };
+        let codex_extrinsic_test_fixture = CodexProposalTestFixture::default_for_call(|| {
+            ProposalCodex::create_set_election_parameters_proposal(
+                RawOrigin::Signed(account_id.clone().into()).into(),
+                member_id as u64,
+                b"title".to_vec(),
+                b"body".to_vec(),
+                Some(<BalanceOf<Runtime>>::from(200_000_u32)),
+                election_parameters,
+            )
+        });
         codex_extrinsic_test_fixture.call_extrinsic_and_assert();
 
         assert_eq!(Election::announcing_period(), 14400);
@@ -611,19 +701,16 @@ fn set_validator_count_proposal_execution_succeeds() {
         let new_validator_count = 8;
         assert_eq!(<staking::ValidatorCount>::get(), 0);
 
-        let codex_extrinsic_test_fixture = CodexProposalTestFixture {
-            member_id: member_id as u64,
-            successful_call: || {
-                ProposalCodex::create_set_validator_count_proposal(
-                    RawOrigin::Signed(account_id.clone().into()).into(),
-                    member_id as u64,
-                    b"title".to_vec(),
-                    b"body".to_vec(),
-                    Some(<BalanceOf<Runtime>>::from(100_000_u32)),
-                    new_validator_count,
-                )
-            },
-        };
+        let codex_extrinsic_test_fixture = CodexProposalTestFixture::default_for_call(|| {
+            ProposalCodex::create_set_validator_count_proposal(
+                RawOrigin::Signed(account_id.clone().into()).into(),
+                member_id as u64,
+                b"title".to_vec(),
+                b"body".to_vec(),
+                Some(<BalanceOf<Runtime>>::from(100_000_u32)),
+                new_validator_count,
+            )
+        });
         codex_extrinsic_test_fixture.call_extrinsic_and_assert();
 
         assert_eq!(<staking::ValidatorCount>::get(), new_validator_count);

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

@@ -0,0 +1,556 @@
+use super::*;
+
+use srml_support::StorageLinkedMap;
+use system::RawOrigin;
+
+use common::working_group::WorkingGroup;
+use hiring::ActivateOpeningAt;
+use proposals_codex::AddOpeningParameters;
+use working_group::{OpeningPolicyCommitment, RewardPolicy};
+
+use crate::{Balance, BlockNumber, StorageWorkingGroupInstance};
+use rstd::collections::btree_set::BTreeSet;
+
+type StorageWorkingGroup = working_group::Module<Runtime, StorageWorkingGroupInstance>;
+
+type Hiring = hiring::Module<Runtime>;
+
+fn add_opening(
+    member_id: u8,
+    account_id: [u8; 32],
+    activate_at: hiring::ActivateOpeningAt<BlockNumber>,
+    opening_policy_commitment: Option<OpeningPolicyCommitment<BlockNumber, u128>>,
+) -> u64 {
+    let opening_id = StorageWorkingGroup::next_opening_id();
+
+    assert!(!<working_group::OpeningById<
+        Runtime,
+        StorageWorkingGroupInstance,
+    >>::exists(opening_id));
+
+    let codex_extrinsic_test_fixture = CodexProposalTestFixture::default_for_call(|| {
+        ProposalCodex::create_add_working_group_leader_opening_proposal(
+            RawOrigin::Signed(account_id.clone().into()).into(),
+            member_id as u64,
+            b"title".to_vec(),
+            b"body".to_vec(),
+            Some(<BalanceOf<Runtime>>::from(100_000_u32)),
+            AddOpeningParameters {
+                activate_at: activate_at.clone(),
+                commitment: opening_policy_commitment
+                    .clone()
+                    .unwrap_or(OpeningPolicyCommitment::default()),
+                human_readable_text: Vec::new(),
+                working_group: WorkingGroup::Storage,
+            },
+        )
+    });
+
+    codex_extrinsic_test_fixture.call_extrinsic_and_assert();
+
+    opening_id
+}
+
+fn begin_review_applications(member_id: u8, account_id: [u8; 32], opening_id: u64) {
+    let codex_extrinsic_test_fixture = CodexProposalTestFixture::default_for_call(|| {
+        ProposalCodex::create_begin_review_working_group_leader_applications_proposal(
+            RawOrigin::Signed(account_id.clone().into()).into(),
+            member_id as u64,
+            b"title".to_vec(),
+            b"body".to_vec(),
+            Some(<BalanceOf<Runtime>>::from(25_000_u32)),
+            opening_id,
+            WorkingGroup::Storage,
+        )
+    })
+    .disable_setup_enviroment()
+    .with_expected_proposal_id(2)
+    .with_run_to_block(3);
+
+    codex_extrinsic_test_fixture.call_extrinsic_and_assert();
+}
+
+fn fill_opening(
+    member_id: u8,
+    account_id: [u8; 32],
+    opening_id: u64,
+    successful_application_id: u64,
+    reward_policy: Option<RewardPolicy<Balance, BlockNumber>>,
+) {
+    let mut expected_proposal_id = 3;
+    let mut run_to_block = 4;
+    if reward_policy.is_some() {
+        set_mint_capacity(member_id, account_id, 999999);
+
+        expected_proposal_id = 4;
+        run_to_block = 5;
+    }
+
+    let codex_extrinsic_test_fixture = CodexProposalTestFixture::default_for_call(|| {
+        ProposalCodex::create_fill_working_group_leader_opening_proposal(
+            RawOrigin::Signed(account_id.clone().into()).into(),
+            member_id as u64,
+            b"title".to_vec(),
+            b"body".to_vec(),
+            Some(<BalanceOf<Runtime>>::from(50_000_u32)),
+            proposals_codex::FillOpeningParameters {
+                opening_id,
+                successful_application_id,
+                reward_policy: reward_policy.clone(),
+                working_group: WorkingGroup::Storage,
+            },
+        )
+    })
+    .disable_setup_enviroment()
+    .with_expected_proposal_id(expected_proposal_id)
+    .with_run_to_block(run_to_block);
+
+    codex_extrinsic_test_fixture.call_extrinsic_and_assert();
+}
+
+fn get_stake_balance(stake: stake::Stake<BlockNumber, Balance, u64>) -> Balance {
+    if let stake::StakingStatus::Staked(stake) = stake.staking_status {
+        return stake.staked_amount;
+    }
+
+    panic!("Not staked.");
+}
+
+fn decrease_stake(
+    member_id: u8,
+    account_id: [u8; 32],
+    leader_worker_id: u64,
+    stake_amount: Balance,
+) {
+    let codex_extrinsic_test_fixture = CodexProposalTestFixture::default_for_call(|| {
+        ProposalCodex::create_decrease_working_group_leader_stake_proposal(
+            RawOrigin::Signed(account_id.clone().into()).into(),
+            member_id as u64,
+            b"title".to_vec(),
+            b"body".to_vec(),
+            Some(<BalanceOf<Runtime>>::from(50_000_u32)),
+            leader_worker_id,
+            stake_amount,
+            WorkingGroup::Storage,
+        )
+    })
+    .disable_setup_enviroment()
+    .with_expected_proposal_id(4)
+    .with_run_to_block(5);
+
+    codex_extrinsic_test_fixture.call_extrinsic_and_assert();
+}
+
+fn slash_stake(member_id: u8, account_id: [u8; 32], leader_worker_id: u64, stake_amount: Balance) {
+    let codex_extrinsic_test_fixture = CodexProposalTestFixture::default_for_call(|| {
+        ProposalCodex::create_slash_working_group_leader_stake_proposal(
+            RawOrigin::Signed(account_id.clone().into()).into(),
+            member_id as u64,
+            b"title".to_vec(),
+            b"body".to_vec(),
+            Some(<BalanceOf<Runtime>>::from(50_000_u32)),
+            leader_worker_id,
+            stake_amount,
+            WorkingGroup::Storage,
+        )
+    })
+    .disable_setup_enviroment()
+    .with_expected_proposal_id(4)
+    .with_run_to_block(5);
+
+    codex_extrinsic_test_fixture.call_extrinsic_and_assert();
+}
+
+fn set_reward(member_id: u8, account_id: [u8; 32], leader_worker_id: u64, reward_amount: Balance) {
+    let codex_extrinsic_test_fixture = CodexProposalTestFixture::default_for_call(|| {
+        ProposalCodex::create_set_working_group_leader_reward_proposal(
+            RawOrigin::Signed(account_id.clone().into()).into(),
+            member_id as u64,
+            b"title".to_vec(),
+            b"body".to_vec(),
+            Some(<BalanceOf<Runtime>>::from(50_000_u32)),
+            leader_worker_id,
+            reward_amount,
+            WorkingGroup::Storage,
+        )
+    })
+    .disable_setup_enviroment()
+    .with_expected_proposal_id(5)
+    .with_run_to_block(6);
+
+    codex_extrinsic_test_fixture.call_extrinsic_and_assert();
+}
+
+fn set_mint_capacity(member_id: u8, account_id: [u8; 32], mint_capacity: Balance) {
+    let mint_id_result = <minting::Module<Runtime>>::add_mint(0, None);
+
+    if let Ok(mint_id) = mint_id_result {
+        <working_group::Mint<Runtime, StorageWorkingGroupInstance>>::put(mint_id);
+    }
+
+    let codex_extrinsic_test_fixture = CodexProposalTestFixture::default_for_call(|| {
+        ProposalCodex::create_set_working_group_mint_capacity_proposal(
+            RawOrigin::Signed(account_id.clone().into()).into(),
+            member_id as u64,
+            b"title".to_vec(),
+            b"body".to_vec(),
+            Some(<BalanceOf<Runtime>>::from(50_000_u32)),
+            mint_capacity,
+            WorkingGroup::Storage,
+        )
+    })
+    .disable_setup_enviroment()
+    .with_expected_proposal_id(3)
+    .with_run_to_block(4);
+
+    codex_extrinsic_test_fixture.call_extrinsic_and_assert();
+}
+
+#[test]
+fn create_add_working_group_leader_opening_proposal_execution_succeeds() {
+    initial_test_ext().execute_with(|| {
+        let member_id = 1;
+        let account_id: [u8; 32] = [member_id; 32];
+
+        let next_opening_id = StorageWorkingGroup::next_opening_id();
+
+        assert!(!<working_group::OpeningById<
+            Runtime,
+            StorageWorkingGroupInstance,
+        >>::exists(next_opening_id));
+
+        let opening_id = add_opening(member_id, account_id, ActivateOpeningAt::CurrentBlock, None);
+
+        // Check for expected opening id.
+        assert_eq!(opening_id, next_opening_id);
+
+        // Check for the new opening creation.
+        assert!(<working_group::OpeningById<
+            Runtime,
+            StorageWorkingGroupInstance,
+        >>::exists(opening_id));
+    });
+}
+
+#[test]
+fn create_begin_review_working_group_leader_applications_proposal_execution_succeeds() {
+    initial_test_ext().execute_with(|| {
+        let member_id = 1;
+        let account_id: [u8; 32] = [member_id; 32];
+
+        let opening_id = add_opening(
+            member_id,
+            account_id.clone(),
+            ActivateOpeningAt::CurrentBlock,
+            None,
+        );
+
+        let opening = StorageWorkingGroup::opening_by_id(opening_id);
+
+        let hiring_opening = Hiring::opening_by_id(opening.hiring_opening_id);
+        assert_eq!(
+            hiring_opening.stage,
+            hiring::OpeningStage::Active {
+                stage: hiring::ActiveOpeningStage::AcceptingApplications {
+                    started_accepting_applicants_at_block: 1
+                },
+                applications_added: BTreeSet::new(),
+                active_application_count: 0,
+                unstaking_application_count: 0,
+                deactivated_application_count: 0
+            }
+        );
+
+        begin_review_applications(member_id, account_id, opening_id);
+
+        let hiring_opening = Hiring::opening_by_id(opening.hiring_opening_id);
+        assert_eq!(
+            hiring_opening.stage,
+            hiring::OpeningStage::Active {
+                stage: hiring::ActiveOpeningStage::ReviewPeriod {
+                    started_accepting_applicants_at_block: 1,
+                    started_review_period_at_block: 2,
+                },
+                applications_added: BTreeSet::new(),
+                active_application_count: 0,
+                unstaking_application_count: 0,
+                deactivated_application_count: 0
+            }
+        );
+    });
+}
+
+#[test]
+fn create_fill_working_group_leader_opening_proposal_execution_succeeds() {
+    initial_test_ext().execute_with(|| {
+        let member_id = 1;
+        let account_id: [u8; 32] = [member_id; 32];
+
+        let opening_id = add_opening(
+            member_id,
+            account_id.clone(),
+            ActivateOpeningAt::CurrentBlock,
+            None,
+        );
+
+        let apply_result = StorageWorkingGroup::apply_on_opening(
+            RawOrigin::Signed(account_id.clone().into()).into(),
+            member_id as u64,
+            opening_id,
+            account_id.clone().into(),
+            None,
+            None,
+            Vec::new(),
+        );
+
+        assert_eq!(apply_result, Ok(()));
+
+        let expected_application_id = 0;
+
+        begin_review_applications(member_id, account_id, opening_id);
+
+        let lead = StorageWorkingGroup::current_lead();
+        assert!(lead.is_none());
+
+        fill_opening(
+            member_id,
+            account_id,
+            opening_id,
+            expected_application_id,
+            None,
+        );
+
+        let lead = StorageWorkingGroup::current_lead();
+        assert!(lead.is_some());
+    });
+}
+
+#[test]
+fn create_decrease_group_leader_stake_proposal_execution_succeeds() {
+    initial_test_ext().execute_with(|| {
+        let member_id = 1;
+        let account_id: [u8; 32] = [member_id; 32];
+        let stake_amount = 100;
+
+        let opening_policy_commitment = OpeningPolicyCommitment {
+            role_staking_policy: Some(hiring::StakingPolicy {
+                amount: 100,
+                amount_mode: hiring::StakingAmountLimitMode::AtLeast,
+                crowded_out_unstaking_period_length: None,
+                review_period_expired_unstaking_period_length: None,
+            }),
+            ..OpeningPolicyCommitment::default()
+        };
+
+        let opening_id = add_opening(
+            member_id,
+            account_id.clone(),
+            ActivateOpeningAt::CurrentBlock,
+            Some(opening_policy_commitment),
+        );
+
+        let apply_result = StorageWorkingGroup::apply_on_opening(
+            RawOrigin::Signed(account_id.clone().into()).into(),
+            member_id as u64,
+            opening_id,
+            account_id.clone().into(),
+            Some(stake_amount),
+            None,
+            Vec::new(),
+        );
+
+        assert_eq!(apply_result, Ok(()));
+
+        let expected_application_id = 0;
+
+        begin_review_applications(member_id, account_id, opening_id);
+
+        let lead = StorageWorkingGroup::current_lead();
+        assert!(lead.is_none());
+
+        fill_opening(
+            member_id,
+            account_id,
+            opening_id,
+            expected_application_id,
+            None,
+        );
+
+        let leader_worker_id = StorageWorkingGroup::current_lead().unwrap();
+
+        let stake_id = 1;
+        let old_balance = Balances::free_balance::<&AccountId32>(&account_id.into());
+        let old_stake = <stake::Module<Runtime>>::stakes(stake_id);
+
+        assert_eq!(get_stake_balance(old_stake), stake_amount);
+
+        let decreasing_stake_amount = 30;
+        decrease_stake(
+            member_id,
+            account_id,
+            leader_worker_id,
+            decreasing_stake_amount,
+        );
+
+        let new_balance = Balances::free_balance::<&AccountId32>(&account_id.into());
+        let new_stake = <stake::Module<Runtime>>::stakes(stake_id);
+
+        assert_eq!(
+            get_stake_balance(new_stake),
+            stake_amount - decreasing_stake_amount
+        );
+        assert_eq!(new_balance, old_balance + decreasing_stake_amount);
+    });
+}
+
+#[test]
+fn create_slash_group_leader_stake_proposal_execution_succeeds() {
+    initial_test_ext().execute_with(|| {
+        let member_id = 1;
+        let account_id: [u8; 32] = [member_id; 32];
+        let stake_amount = 100;
+
+        let opening_policy_commitment = OpeningPolicyCommitment {
+            role_staking_policy: Some(hiring::StakingPolicy {
+                amount: 100,
+                amount_mode: hiring::StakingAmountLimitMode::AtLeast,
+                crowded_out_unstaking_period_length: None,
+                review_period_expired_unstaking_period_length: None,
+            }),
+            ..OpeningPolicyCommitment::default()
+        };
+
+        let opening_id = add_opening(
+            member_id,
+            account_id.clone(),
+            ActivateOpeningAt::CurrentBlock,
+            Some(opening_policy_commitment),
+        );
+
+        let apply_result = StorageWorkingGroup::apply_on_opening(
+            RawOrigin::Signed(account_id.clone().into()).into(),
+            member_id as u64,
+            opening_id,
+            account_id.clone().into(),
+            Some(stake_amount),
+            None,
+            Vec::new(),
+        );
+
+        assert_eq!(apply_result, Ok(()));
+
+        let expected_application_id = 0;
+
+        begin_review_applications(member_id, account_id, opening_id);
+
+        let lead = StorageWorkingGroup::current_lead();
+        assert!(lead.is_none());
+
+        fill_opening(
+            member_id,
+            account_id,
+            opening_id,
+            expected_application_id,
+            None,
+        );
+
+        let leader_worker_id = StorageWorkingGroup::current_lead().unwrap();
+
+        let stake_id = 1;
+        let old_balance = Balances::free_balance::<&AccountId32>(&account_id.into());
+        let old_stake = <stake::Module<Runtime>>::stakes(stake_id);
+
+        assert_eq!(get_stake_balance(old_stake), stake_amount);
+
+        let slashing_stake_amount = 30;
+        slash_stake(
+            member_id,
+            account_id,
+            leader_worker_id,
+            slashing_stake_amount,
+        );
+
+        let new_balance = Balances::free_balance::<&AccountId32>(&account_id.into());
+        let new_stake = <stake::Module<Runtime>>::stakes(stake_id);
+
+        assert_eq!(
+            get_stake_balance(new_stake),
+            stake_amount - slashing_stake_amount
+        );
+        assert_eq!(new_balance, old_balance);
+    });
+}
+
+#[test]
+fn create_set_group_leader_reward_proposal_execution_succeeds() {
+    initial_test_ext().execute_with(|| {
+        let member_id = 1;
+        let account_id: [u8; 32] = [member_id; 32];
+        let stake_amount = 100;
+
+        let opening_policy_commitment = OpeningPolicyCommitment {
+            role_staking_policy: Some(hiring::StakingPolicy {
+                amount: 100,
+                amount_mode: hiring::StakingAmountLimitMode::AtLeast,
+                crowded_out_unstaking_period_length: None,
+                review_period_expired_unstaking_period_length: None,
+            }),
+            ..OpeningPolicyCommitment::default()
+        };
+
+        let opening_id = add_opening(
+            member_id,
+            account_id.clone(),
+            ActivateOpeningAt::CurrentBlock,
+            Some(opening_policy_commitment),
+        );
+
+        let apply_result = StorageWorkingGroup::apply_on_opening(
+            RawOrigin::Signed(account_id.clone().into()).into(),
+            member_id as u64,
+            opening_id,
+            account_id.clone().into(),
+            Some(stake_amount),
+            None,
+            Vec::new(),
+        );
+
+        assert_eq!(apply_result, Ok(()));
+
+        let expected_application_id = 0;
+
+        begin_review_applications(member_id, account_id, opening_id);
+
+        let lead = StorageWorkingGroup::current_lead();
+        assert!(lead.is_none());
+
+        let old_reward_amount = 100;
+        let reward_policy = Some(RewardPolicy {
+            amount_per_payout: old_reward_amount,
+            next_payment_at_block: 9999,
+            payout_interval: None,
+        });
+
+        fill_opening(
+            member_id,
+            account_id,
+            opening_id,
+            expected_application_id,
+            reward_policy,
+        );
+
+        let leader_worker_id = StorageWorkingGroup::current_lead().unwrap();
+
+        let worker = StorageWorkingGroup::worker_by_id(leader_worker_id);
+        let relationship_id = worker.reward_relationship.unwrap();
+
+        let relationship = recurringrewards::RewardRelationships::<Runtime>::get(relationship_id);
+        assert_eq!(relationship.amount_per_payout, old_reward_amount);
+
+        let new_reward_amount = 999;
+        set_reward(member_id, account_id, leader_worker_id, new_reward_amount);
+
+        let relationship = recurringrewards::RewardRelationships::<Runtime>::get(relationship_id);
+        assert_eq!(relationship.amount_per_payout, new_reward_amount);
+    });
+}