1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582 |
- //! Hiring substrate module for the Joystream platform
- //!
- //! Public APIs:
- //! - add_opening
- //! - ensure_can_add_application
- //! - add_application
- //! - deactivate_application
- //! - cancel_opening
- //! - fill_opening
- //! - begin_review
- //! - begin_acception_application
- //! - unstaked
- //!
- //! Dependency: Joystream stake module
- // Ensure we're `no_std` when compiling for Wasm.
- #![cfg_attr(not(feature = "std"), no_std)]
- // Do not delete! Cannot be uncommented by default, because of Parity decl_module! issue.
- //#![warn(missing_docs)]
- // Test dependencies
- #[cfg(all(test, not(target_arch = "wasm32")))]
- use mockall::predicate::*;
- #[cfg(all(test, not(target_arch = "wasm32")))]
- use mockall::*;
- use codec::Codec;
- use frame_support::storage::IterableStorageMap;
- use frame_support::traits::{Currency, Imbalance};
- use frame_support::{decl_module, decl_storage, ensure, Parameter};
- use sp_arithmetic::traits::{BaseArithmetic, One, Zero};
- use sp_runtime::traits::{MaybeSerialize, Member};
- use sp_std::cell::RefCell;
- use sp_std::collections::btree_map::BTreeMap;
- use sp_std::collections::btree_set::BTreeSet;
- use sp_std::iter::Iterator;
- use sp_std::rc::Rc;
- use sp_std::vec::Vec;
- use stake::{InitiateUnstakingError, Stake, StakeActionError, StakingError, Trait as StakeTrait};
- mod hiring;
- #[macro_use]
- mod macroes;
- mod mock;
- mod test;
- pub use hiring::*;
- /// Main trait of hiring substrate module
- pub trait Trait: frame_system::Trait + stake::Trait + Sized {
- /// OpeningId type
- type OpeningId: Parameter
- + Member
- + BaseArithmetic
- + Codec
- + Default
- + Copy
- + MaybeSerialize
- + PartialEq;
- /// ApplicationId type
- type ApplicationId: Parameter
- + Member
- + BaseArithmetic
- + Codec
- + Default
- + Copy
- + MaybeSerialize
- + PartialEq;
- /// Type that will handle various staking events
- type ApplicationDeactivatedHandler: ApplicationDeactivatedHandler<Self>;
- /// Marker type for Stake module handler. Indicates that hiring module uses stake module mock.
- type StakeHandlerProvider: StakeHandlerProvider<Self>;
- }
- decl_storage! {
- trait Store for Module<T: Trait> as Hiring {
- /// Openings.
- pub OpeningById get(fn opening_by_id): map hasher(blake2_128_concat)
- T::OpeningId => Opening<BalanceOf<T>, T::BlockNumber, T::ApplicationId>;
- /// Identifier for next opening to be added.
- pub NextOpeningId get(fn next_opening_id): T::OpeningId;
- /// Applications
- pub ApplicationById get(fn application_by_id): map hasher(blake2_128_concat)
- T::ApplicationId => Application<T::OpeningId, T::BlockNumber, T::StakeId>;
- /// Identifier for next application to be added.
- pub NextApplicationId get(fn next_application_id): T::ApplicationId;
- /// Internal purpose of given stake, i.e. fro what application, and whether for the role or for the application.
- pub ApplicationIdByStakingId get(fn stake_purpose_by_staking_id): map hasher(blake2_128_concat)
- T::StakeId => T::ApplicationId;
- }
- }
- decl_module! {
- /// Main hiring module definition
- pub struct Module<T: Trait> for enum Call where origin: T::Origin {
- fn on_finalize(now: T::BlockNumber) {
- //
- // == MUTATION SAFE ==
- //
- // Change opening from WaitingToBegin stage to Active::AcceptingApplications stage
- for (opening_id, opening) in Self::openings_waiting_to_begin_iterator(now) {
- let opening_accepting_applications = opening.clone_with_new_active_opening_stage(
- hiring::ActiveOpeningStage::AcceptingApplications {
- started_accepting_applicants_at_block: now
- });
- <OpeningById<T>>::insert(opening_id, opening_accepting_applications);
- }
- // Deactivate opening
- for (opening_id,
- opening,
- (
- applications_added,
- started_accepting_applicants_at_block,
- started_review_period_at_block
- )) in Self::openings_expired_review_period_iterator(now) {
- //
- // Deactivate all applications that are part of this opening
- //
- // Get unstaking periods
- let application_stake_unstaking_period = StakingPolicy::opt_staking_policy_to_review_period_expired_unstaking_period(&opening.application_staking_policy);
- let role_stake_unstaking_period = StakingPolicy::opt_staking_policy_to_review_period_expired_unstaking_period(&opening.role_staking_policy);
- // Get applications
- let applications_map = Self::application_id_iter_to_map(applications_added.iter());
- // Deactivate applications
- Self::initiate_application_deactivations(
- &applications_map,
- application_stake_unstaking_period,
- role_stake_unstaking_period,
- hiring::ApplicationDeactivationCause::ReviewPeriodExpired
- );
- let deactivated_opening =
- opening.clone_with_new_active_opening_stage(
- hiring::ActiveOpeningStage::Deactivated {
- cause: hiring::OpeningDeactivationCause::ReviewPeriodExpired,
- deactivated_at_block: now,
- started_accepting_applicants_at_block,
- started_review_period_at_block: Some(started_review_period_at_block),
- });
- <OpeningById<T>>::insert(opening_id, deactivated_opening);
- }
- }
- }
- }
- /*
- * ======== Main API implementation ========
- */
- // Public API implementation
- impl<T: Trait> Module<T> {
- /// Add new opening based on given inputs policies.
- /// The new Opening instance has stage WaitingToBegin, and is added to openingsById,
- /// and has identifier equal to nextOpeningId.
- /// The latter is incremented. The used identifier is returned.
- pub fn add_opening(
- activate_at: ActivateOpeningAt<T::BlockNumber>,
- max_review_period_length: T::BlockNumber,
- application_rationing_policy: Option<ApplicationRationingPolicy>,
- application_staking_policy: Option<StakingPolicy<BalanceOf<T>, T::BlockNumber>>,
- role_staking_policy: Option<StakingPolicy<BalanceOf<T>, T::BlockNumber>>,
- human_readable_text: Vec<u8>,
- ) -> Result<T::OpeningId, AddOpeningError> {
- let current_block_height = <frame_system::Module<T>>::block_number();
- Self::ensure_can_add_opening(
- current_block_height,
- activate_at.clone(),
- T::Currency::minimum_balance(),
- application_rationing_policy.clone(),
- application_staking_policy.clone(),
- role_staking_policy.clone(),
- )?;
- // == MUTATION SAFE ==
- let new_opening = hiring::Opening::new(
- current_block_height,
- activate_at,
- max_review_period_length,
- application_rationing_policy,
- application_staking_policy,
- role_staking_policy,
- human_readable_text,
- );
- // Get Id for new opening
- let new_opening_id = <NextOpeningId<T>>::get();
- // Insert opening in storage
- <OpeningById<T>>::insert(new_opening_id, new_opening);
- // Update NextOpeningId counter
- <NextOpeningId<T>>::mutate(|id| *id += T::OpeningId::one());
- // Return
- Ok(new_opening_id)
- }
- /// Cancels opening with given identifier, using provided unstaking periods for
- /// application and role, as necesary.
- pub fn cancel_opening(
- opening_id: T::OpeningId,
- application_stake_unstaking_period: Option<T::BlockNumber>,
- role_stake_unstaking_period: Option<T::BlockNumber>,
- ) -> Result<OpeningCancelled, CancelOpeningError> {
- // Ensure that the opening exists
- let opening =
- ensure_opening_exists!(T, opening_id, CancelOpeningError::OpeningDoesNotExist)?;
- // Opening is in stage Active.{AcceptingApplications or ReviewPeriod}
- let (
- active_stage,
- applications_added,
- active_application_count,
- unstaking_application_count,
- deactivated_application_count,
- ) = ensure_opening_is_active!(
- opening.stage,
- CancelOpeningError::OpeningNotInCancellableStage
- )?;
- //
- let current_block_height = <frame_system::Module<T>>::block_number(); // move later!
- let new_active_stage = active_stage.new_stage_on_cancelling(current_block_height)?;
- // Ensure unstaking periods are OK.
- ensure_opt_unstaking_period_is_ok!(
- application_stake_unstaking_period,
- opening.application_staking_policy,
- CancelOpeningError::UnstakingPeriodTooShort(StakePurpose::Application),
- CancelOpeningError::RedundantUnstakingPeriodProvided(StakePurpose::Application)
- )?;
- ensure_opt_unstaking_period_is_ok!(
- role_stake_unstaking_period,
- opening.role_staking_policy,
- CancelOpeningError::UnstakingPeriodTooShort(StakePurpose::Role),
- CancelOpeningError::RedundantUnstakingPeriodProvided(StakePurpose::Role)
- )?;
- //
- // == MUTATION SAFE ==
- //
- // Create and store new cancelled opening
- let new_opening = Opening {
- stage: hiring::OpeningStage::Active {
- stage: new_active_stage,
- applications_added: applications_added.clone(),
- active_application_count,
- unstaking_application_count,
- deactivated_application_count,
- },
- ..opening
- };
- OpeningById::<T>::insert(opening_id, new_opening);
- // Map with applications
- let applications_map = Self::application_id_iter_to_map(applications_added.iter());
- // Initiate deactivation of all active applications
- let net_result = Self::initiate_application_deactivations(
- &applications_map,
- application_stake_unstaking_period,
- role_stake_unstaking_period,
- hiring::ApplicationDeactivationCause::OpeningCancelled,
- );
- // Return
- Ok(OpeningCancelled {
- number_of_unstaking_applications: net_result.number_of_unstaking_applications,
- number_of_deactivated_applications: net_result.number_of_deactivated_applications,
- })
- }
- /// Transit opening to the accepting application stage.
- /// Applies when given opening is in WaitingToBegin stage.
- /// The stage is updated to Active stage with AcceptingApplications substage
- pub fn begin_accepting_applications(
- opening_id: T::OpeningId,
- ) -> Result<(), BeginAcceptingApplicationsError> {
- // Ensure that the opening exists
- let opening = ensure_opening_exists!(
- T,
- opening_id,
- BeginAcceptingApplicationsError::OpeningDoesNotExist
- )?;
- // Ensure that it is the waiting to begin stage
- opening.stage.ensure_opening_stage_is_waiting_to_begin(
- BeginAcceptingApplicationsError::OpeningIsNotInWaitingToBeginStage,
- )?;
- //
- // == MUTATION SAFE ==
- //
- let current_block_height = <frame_system::Module<T>>::block_number();
- // Update state of opening
- let new_opening = opening.clone_with_new_active_opening_stage(
- hiring::ActiveOpeningStage::AcceptingApplications {
- started_accepting_applicants_at_block: current_block_height,
- },
- );
- // Write back opening
- <OpeningById<T>>::insert(opening_id, new_opening);
- // DONE
- Ok(())
- }
- /// Transit opening to the begin review period stage.
- /// Applies when given opening is in Active stage and AcceptingApplications substage.
- /// The stage is updated to Active stage and ReviewPeriod substage
- pub fn begin_review(opening_id: T::OpeningId) -> Result<(), BeginReviewError> {
- // Ensure that the opening exists
- let opening = ensure_opening_exists!(T, opening_id, BeginReviewError::OpeningDoesNotExist)?;
- // Opening is accepting applications
- let (active_stage, _, _, _, _) = ensure_opening_is_active!(
- opening.stage,
- BeginReviewError::OpeningNotInAcceptingApplicationsStage
- )?;
- let started_accepting_applicants_at_block = ensure_active_opening_is_accepting_applications!(
- active_stage,
- BeginReviewError::OpeningNotInAcceptingApplicationsStage
- )?;
- //
- // == MUTATION SAFE ==
- //
- let current_block_height = <frame_system::Module<T>>::block_number();
- let new_opening =
- opening.clone_with_new_active_opening_stage(hiring::ActiveOpeningStage::ReviewPeriod {
- started_accepting_applicants_at_block,
- started_review_period_at_block: current_block_height,
- });
- // Update to new opening
- <OpeningById<T>>::insert(opening_id, new_opening);
- Ok(())
- }
- /// Fill an opening, identified with `opening_id`, currently in the review period.
- /// Applies when given opening is in ReviewPeriod stage.
- /// Given list of applications are deactivated to under the Hired,
- /// all other active applicants are NotHired.
- /// Separately for each group,
- /// unstaking periods for any applicable application and/or role stake must be provided.
- pub fn fill_opening(
- opening_id: T::OpeningId,
- successful_applications: BTreeSet<T::ApplicationId>,
- opt_successful_applicant_application_stake_unstaking_period: Option<T::BlockNumber>,
- opt_failed_applicant_application_stake_unstaking_period: Option<T::BlockNumber>,
- /* this parameter does not make sense? opt_successful_applicant_role_stake_unstaking_period: Option<T::BlockNumber>, */
- opt_failed_applicant_role_stake_unstaking_period: Option<T::BlockNumber>,
- ) -> Result<(), FillOpeningError<T>> {
- // Ensure that the opening exists
- let opening = ensure_opening_exists!(T, opening_id, FillOpeningError::OpeningDoesNotExist)?;
- let (active_stage, applications_added, _, _, _) = ensure_opening_is_active!(
- opening.stage,
- FillOpeningError::OpeningNotInReviewPeriodStage
- )?;
- // Ensure opening is in review period
- let (started_accepting_applicants_at_block, started_review_period_at_block) = active_stage
- .ensure_active_opening_is_in_review_period(
- FillOpeningError::OpeningNotInReviewPeriodStage,
- )?;
- //
- // Ensure that all unstaking periods are neither too short (0) nor redundant.
- //
- ensure_opt_unstaking_period_is_ok!(
- opt_successful_applicant_application_stake_unstaking_period,
- opening.application_staking_policy,
- FillOpeningError::UnstakingPeriodTooShort(
- StakePurpose::Application,
- ApplicationOutcomeInFilledOpening::Success
- ),
- FillOpeningError::RedundantUnstakingPeriodProvided(
- StakePurpose::Application,
- ApplicationOutcomeInFilledOpening::Success
- )
- )?;
- ensure_opt_unstaking_period_is_ok!(
- opt_failed_applicant_application_stake_unstaking_period,
- opening.application_staking_policy,
- FillOpeningError::UnstakingPeriodTooShort(
- StakePurpose::Application,
- ApplicationOutcomeInFilledOpening::Failure
- ),
- FillOpeningError::RedundantUnstakingPeriodProvided(
- StakePurpose::Application,
- ApplicationOutcomeInFilledOpening::Failure
- )
- )?;
- ensure_opt_unstaking_period_is_ok!(
- opt_failed_applicant_role_stake_unstaking_period,
- opening.role_staking_policy,
- FillOpeningError::UnstakingPeriodTooShort(
- StakePurpose::Role,
- ApplicationOutcomeInFilledOpening::Failure
- ),
- FillOpeningError::RedundantUnstakingPeriodProvided(
- StakePurpose::Role,
- ApplicationOutcomeInFilledOpening::Failure
- )
- )?;
- // Ensure that all successful applications actually exist
- for application_id in &successful_applications {
- ensure_application_exists!(
- T,
- *application_id,
- FillOpeningError::ApplicationDoesNotExist(*application_id)
- )?;
- }
- let successful_applications_map =
- Self::application_id_iter_to_map(successful_applications.iter());
- // Ensure that all successful applications are actually active and associated with the opening
- for (application_id, application) in &successful_applications_map {
- ensure_eq!(
- application.stage,
- hiring::ApplicationStage::Active,
- FillOpeningError::ApplicationNotInActiveStage(*application_id,)
- );
- ensure_eq!(
- application.opening_id,
- opening_id,
- FillOpeningError::ApplicationForWrongOpening(*application_id)
- );
- }
- //
- // == MUTATION SAFE ==
- //
- // Deactivate all successful applications, with cause being hired
- Self::initiate_application_deactivations(
- &successful_applications_map,
- opt_successful_applicant_application_stake_unstaking_period,
- None,
- hiring::ApplicationDeactivationCause::Hired,
- );
- // Deactivate all unsuccessful applications, with cause being not being hired.
- // First get all failed applications by their id.
- let failed_applications_map = Self::application_id_iter_to_map(
- applications_added.difference(&successful_applications),
- );
- // Deactivate all successful applications, with cause being not hired
- Self::initiate_application_deactivations(
- &failed_applications_map,
- opt_failed_applicant_application_stake_unstaking_period,
- opt_failed_applicant_role_stake_unstaking_period,
- hiring::ApplicationDeactivationCause::NotHired,
- );
- // Grab current block height
- let current_block_height = <frame_system::Module<T>>::block_number();
- // Get opening with updated counters
- let opening_needed_for_data = <OpeningById<T>>::get(opening_id);
- // Deactivate opening
- let new_opening = opening_needed_for_data.clone_with_new_active_opening_stage(
- hiring::ActiveOpeningStage::Deactivated {
- cause: OpeningDeactivationCause::Filled,
- deactivated_at_block: current_block_height,
- started_accepting_applicants_at_block,
- started_review_period_at_block: Some(started_review_period_at_block),
- },
- );
- // Write back new opening
- <OpeningById<T>>::insert(opening_id, new_opening);
- // DONE
- Ok(())
- }
- /// Adds a new application on the given opening, and begins staking for
- /// the role, the application or both possibly.
- pub fn ensure_can_add_application(
- opening_id: T::OpeningId,
- opt_role_stake_balance: Option<BalanceOf<T>>,
- opt_application_stake_balance: Option<BalanceOf<T>>,
- ) -> Result<DestructuredApplicationCanBeAddedEvaluation<T>, AddApplicationError> {
- // Ensure that the opening exists
- let opening =
- ensure_opening_exists!(T, opening_id, AddApplicationError::OpeningDoesNotExist)?;
- // Ensure that proposed stakes match the policy of the opening.
- let opt_role_stake_balance = ensure_stake_balance_matches_staking_policy!(
- &opt_role_stake_balance,
- &opening.role_staking_policy,
- AddApplicationError::StakeMissingWhenRequired(StakePurpose::Role),
- AddApplicationError::StakeProvidedWhenRedundant(StakePurpose::Role),
- AddApplicationError::StakeAmountTooLow(StakePurpose::Role)
- )?;
- let opt_application_stake_balance = ensure_stake_balance_matches_staking_policy!(
- &opt_application_stake_balance,
- &opening.application_staking_policy,
- AddApplicationError::StakeMissingWhenRequired(StakePurpose::Application),
- AddApplicationError::StakeProvidedWhenRedundant(StakePurpose::Application),
- AddApplicationError::StakeAmountTooLow(StakePurpose::Application)
- )?;
- // Opening is accepting applications
- let (
- active_stage,
- applications_added,
- active_application_count,
- unstaking_application_count,
- deactivated_application_count,
- ) = ensure_opening_is_active!(
- opening.stage,
- AddApplicationError::OpeningNotInAcceptingApplicationsStage
- )?;
- active_stage.ensure_active_opening_is_accepting_applications(
- AddApplicationError::OpeningNotInAcceptingApplicationsStage,
- )?;
- // Ensure that the new application would actually make it
- let would_get_added_success = ensure_application_would_get_added!(
- &opening.application_rationing_policy,
- &applications_added,
- &opt_role_stake_balance,
- &opt_application_stake_balance,
- AddApplicationError::NewApplicationWasCrowdedOut
- )?;
- Ok(DestructuredApplicationCanBeAddedEvaluation {
- opening,
- active_stage,
- applications_added,
- active_application_count,
- unstaking_application_count,
- deactivated_application_count,
- would_get_added_success,
- })
- }
- /// Adds a new application on the given opening, and begins staking for
- /// the role, the application or both possibly.
- pub fn add_application(
- opening_id: T::OpeningId,
- opt_role_stake_imbalance: Option<NegativeImbalance<T>>,
- opt_application_stake_imbalance: Option<NegativeImbalance<T>>,
- human_readable_text: Vec<u8>,
- ) -> Result<ApplicationAdded<T::ApplicationId>, AddApplicationError> {
- let opt_role_stake_balance = Self::create_stake_balance(&opt_role_stake_imbalance);
- let opt_application_stake_balance =
- Self::create_stake_balance(&opt_application_stake_imbalance);
- let can_be_added_destructured = Self::ensure_can_add_application(
- opening_id,
- opt_role_stake_balance,
- opt_application_stake_balance,
- )?;
- //
- // == MUTATION SAFE ==
- //
- // If required, deactive another application that was crowded out.
- if let ApplicationAddedSuccess::CrowdsOutExistingApplication(
- id_of_croweded_out_application,
- ) = can_be_added_destructured.would_get_added_success
- {
- // Get relevant unstaking periods
- let opt_application_stake_unstaking_period =
- hiring::StakingPolicy::opt_staking_policy_to_crowded_out_unstaking_period(
- &can_be_added_destructured.opening.application_staking_policy,
- );
- let opt_role_stake_unstaking_period =
- hiring::StakingPolicy::opt_staking_policy_to_crowded_out_unstaking_period(
- &can_be_added_destructured.opening.role_staking_policy,
- );
- // Fetch application
- let crowded_out_application = <ApplicationById<T>>::get(id_of_croweded_out_application);
- // Initiate actual deactivation
- //
- // MUST not have been ignored, is runtime invariant, false means code is broken.
- // But should we do panic in runtime? Is there safer way?
- let deactivation_result = Self::try_to_initiate_application_deactivation(
- &crowded_out_application,
- id_of_croweded_out_application,
- opt_application_stake_unstaking_period,
- opt_role_stake_unstaking_period,
- hiring::ApplicationDeactivationCause::CrowdedOut,
- );
- assert_ne!(
- deactivation_result,
- ApplicationDeactivationInitiationResult::Ignored
- );
- }
- // Get Id for this new application
- let new_application_id = <NextApplicationId<T>>::get();
- // Possibly initiate staking
- let active_role_staking_id =
- Self::infallible_opt_stake_initiation(opt_role_stake_imbalance, &new_application_id);
- let active_application_staking_id = Self::infallible_opt_stake_initiation(
- opt_application_stake_imbalance,
- &new_application_id,
- );
- // Grab current block height
- let current_block_height = <frame_system::Module<T>>::block_number();
- // Compute index for this new application
- let application_index_in_opening =
- can_be_added_destructured.calculate_total_application_count();
- // Create a new application
- let new_application = hiring::Application {
- opening_id,
- application_index_in_opening,
- add_to_opening_in_block: current_block_height,
- active_role_staking_id,
- active_application_staking_id,
- // Stage of new application
- stage: hiring::ApplicationStage::Active,
- human_readable_text,
- };
- // Insert into main application map
- <ApplicationById<T>>::insert(new_application_id, new_application);
- // Update next application id
- <NextApplicationId<T>>::mutate(|id| *id += One::one());
- // Update counter on opening
- // Should reload after possible deactivation in try_to_initiate_application_deactivation
- let opening_needed_for_data = <OpeningById<T>>::get(opening_id);
- let new_active_stage = opening_needed_for_data
- .stage
- .clone_with_added_active_application(new_application_id);
- <OpeningById<T>>::mutate(opening_id, |opening| {
- opening.stage = new_active_stage;
- });
- let application_id_crowded_out = can_be_added_destructured
- .would_get_added_success
- .crowded_out_application_id();
- // DONE
- Ok(ApplicationAdded {
- application_id_added: new_application_id,
- application_id_crowded_out,
- })
- }
- /// Deactive an active application.
- /// Does currently not support slashing
- pub fn deactive_application(
- application_id: T::ApplicationId,
- application_stake_unstaking_period: Option<T::BlockNumber>,
- role_stake_unstaking_period: Option<T::BlockNumber>,
- ) -> Result<(), DeactivateApplicationError> {
- // Check that application id is valid, and if so,
- // grab corresponding application and opening.
- let (application, opening) = ensure_application_exists!(
- T,
- application_id,
- DeactivateApplicationError::ApplicationDoesNotExist,
- auto_fetch_opening
- )?;
- // Application is active
- ensure_eq!(
- application.stage,
- hiring::ApplicationStage::Active,
- DeactivateApplicationError::ApplicationNotActive
- );
- // Opening is accepting applications
- let (active_stage, ..) = ensure_opening_is_active!(
- opening.stage,
- DeactivateApplicationError::OpeningNotAcceptingApplications
- )?;
- active_stage.ensure_active_opening_is_accepting_applications(
- DeactivateApplicationError::OpeningNotAcceptingApplications,
- )?;
- // Ensure unstaking periods are OK.
- ensure_opt_unstaking_period_is_ok!(
- application_stake_unstaking_period,
- opening.application_staking_policy,
- DeactivateApplicationError::UnstakingPeriodTooShort(StakePurpose::Application),
- DeactivateApplicationError::RedundantUnstakingPeriodProvided(StakePurpose::Application)
- )?;
- ensure_opt_unstaking_period_is_ok!(
- role_stake_unstaking_period,
- opening.role_staking_policy,
- DeactivateApplicationError::UnstakingPeriodTooShort(StakePurpose::Role),
- DeactivateApplicationError::RedundantUnstakingPeriodProvided(StakePurpose::Role)
- )?;
- //
- // == MUTATION SAFE ==
- //
- // Deactive application
- let result = Self::try_to_initiate_application_deactivation(
- &application,
- application_id,
- application_stake_unstaking_period,
- role_stake_unstaking_period,
- hiring::ApplicationDeactivationCause::External,
- );
- assert_ne!(result, ApplicationDeactivationInitiationResult::Ignored);
- // DONE
- Ok(())
- }
- /// The stake, with the given id, was unstaked.
- pub fn unstaked(stake_id: T::StakeId) -> UnstakedResult {
- // Ignore unstaked
- if !<ApplicationIdByStakingId<T>>::contains_key(stake_id) {
- return UnstakedResult::StakeIdNonExistent;
- }
- // Get application
- let application_id = <ApplicationIdByStakingId<T>>::get(stake_id);
- assert!(<ApplicationById<T>>::contains_key(application_id));
- let application = <ApplicationById<T>>::get(application_id);
- // Make sure that we are actually unstaking, ignore otherwise.
- let (deactivation_initiated, cause) = if let ApplicationStage::Unstaking {
- deactivation_initiated,
- cause,
- } = application.stage
- {
- (deactivation_initiated, cause)
- } else {
- return UnstakedResult::ApplicationIsNotUnstaking;
- };
- //
- // == MUTATION SAFE ==
- //
- // Drop stake from stake to application map
- <ApplicationIdByStakingId<T>>::remove(stake_id);
- let current_block_height = <frame_system::Module<T>>::block_number();
- // New application computed
- let mut new_application = application.clone();
- let is_now_done_unstaking = new_application.unstake_application(
- current_block_height,
- deactivation_initiated,
- cause,
- stake_id,
- );
- // Update to new application
- <ApplicationById<T>>::insert(&application_id, new_application);
- // If the application is now finished compeleting any pending unstaking process,
- // then we need to update the opening counters, and make the deactivation callback.
- if is_now_done_unstaking {
- // Update Opening
- // We know the stage MUST be active, hence mutate is certain.
- <OpeningById<T>>::mutate(application.opening_id, |opening| {
- opening.change_opening_stage_after_application_unstaked();
- });
- // Call handler
- T::ApplicationDeactivatedHandler::deactivated(&application_id, cause);
- return UnstakedResult::Unstaked;
- }
- UnstakedResult::UnstakingInProgress
- }
- }
- /*
- * === Application Deactivated Handler ======
- */
- /// Handles application deactivation with a cause
- pub trait ApplicationDeactivatedHandler<T: Trait> {
- /// An application, with the given id, was fully deactivated, with the
- /// given cause, and was put in the inactive state.
- fn deactivated(application_id: &T::ApplicationId, cause: hiring::ApplicationDeactivationCause);
- }
- /// Helper implementation so we can provide multiple handlers by grouping handlers in tuple pairs.
- /// For example for three handlers, A, B and C we can set the StakingEventHandler type on the trait to:
- /// type StakingEventHandler = ((A, B), C)
- impl<T: Trait> ApplicationDeactivatedHandler<T> for () {
- fn deactivated(
- _application_id: &T::ApplicationId,
- _cause: hiring::ApplicationDeactivationCause,
- ) {
- }
- }
- /*
- * ======== API types bound to the Trait ========
- */
- /// Error due to attempting to fill an opening.
- #[derive(Eq, PartialEq, Clone, Debug)]
- pub enum FillOpeningError<T: Trait> {
- /// Opening does not exist
- OpeningDoesNotExist,
- /// Opening is not in review period
- OpeningNotInReviewPeriodStage,
- /// Provided unstaking period is too short
- UnstakingPeriodTooShort(StakePurpose, ApplicationOutcomeInFilledOpening),
- /// Provided redundant unstaking period
- RedundantUnstakingPeriodProvided(StakePurpose, ApplicationOutcomeInFilledOpening),
- /// Application does not exist
- ApplicationDoesNotExist(T::ApplicationId),
- /// Application is not in active stage
- ApplicationNotInActiveStage(T::ApplicationId),
- /// Application is not for the opening
- ApplicationForWrongOpening(T::ApplicationId),
- }
- /// Product of ensure_can_add_application()
- #[derive(Eq, PartialEq, Clone, Debug)]
- pub struct DestructuredApplicationCanBeAddedEvaluation<T: Trait> {
- /// Opening object
- pub opening: Opening<BalanceOf<T>, T::BlockNumber, T::ApplicationId>,
- /// Active opening stage
- pub active_stage: ActiveOpeningStage<T::BlockNumber>,
- /// Collection of added applicaiton ids
- pub applications_added: BTreeSet<T::ApplicationId>,
- /// Active applications counter
- pub active_application_count: u32,
- /// Unstaking applications counter
- pub unstaking_application_count: u32,
- /// Deactivated applications counter
- pub deactivated_application_count: u32,
- /// Prospects of application adding
- pub would_get_added_success: ApplicationAddedSuccess<T>,
- }
- impl<T: Trait> DestructuredApplicationCanBeAddedEvaluation<T> {
- pub(crate) fn calculate_total_application_count(&self) -> u32 {
- // TODO: fix so that `number_of_appliations_ever_added` can be invoked.
- // cant do this due to bad design of stage => opening.stage.number_of_appliations_ever_added();
- self.active_application_count
- + self.unstaking_application_count
- + self.deactivated_application_count
- }
- }
- /// Adding application result
- #[derive(Eq, PartialEq, Clone, Debug)]
- pub enum ApplicationAddedSuccess<T: Trait> {
- /// Application was added without side-effects
- Unconditionally,
- /// Application has crowded out existing application
- CrowdsOutExistingApplication(T::ApplicationId),
- }
- impl<T: Trait> ApplicationAddedSuccess<T> {
- pub(crate) fn crowded_out_application_id(&self) -> Option<T::ApplicationId> {
- if let ApplicationAddedSuccess::CrowdsOutExistingApplication(id) = self {
- Some(*id)
- } else {
- None
- }
- }
- }
- /// Prospects of application. Whether it would be added to the opening.
- #[derive(Eq, PartialEq, Clone, Debug)]
- pub enum ApplicationWouldGetAddedEvaluation<T: Trait> {
- /// Negative prospects
- No,
- /// Positive prospects
- Yes(ApplicationAddedSuccess<T>),
- }
- /// Balance alias
- pub type BalanceOf<T> =
- <<T as stake::Trait>::Currency as Currency<<T as frame_system::Trait>::AccountId>>::Balance;
- /// Balance alias for staking
- pub type NegativeImbalance<T> = <<T as stake::Trait>::Currency as Currency<
- <T as frame_system::Trait>::AccountId,
- >>::NegativeImbalance;
- /*
- * ======== ======== ======== ======== =======
- * ======== PRIVATE TYPES AND METHODS ========
- * ======== ======== ======== ======== =======
- */
- #[derive(PartialEq, Debug, Clone)]
- struct ApplicationsDeactivationsInitiationResult {
- number_of_unstaking_applications: u32,
- number_of_deactivated_applications: u32,
- }
- type ApplicationBTreeMap<T> = BTreeMap<
- <T as Trait>::ApplicationId,
- hiring::Application<
- <T as Trait>::OpeningId,
- <T as frame_system::Trait>::BlockNumber,
- <T as stake::Trait>::StakeId,
- >,
- >;
- #[derive(PartialEq, Debug, Clone)]
- enum ApplicationDeactivationInitiationResult {
- Ignored, // <= is there a case for kicking this out, making sure that initiation cannot happen when it may fail?
- Unstaking,
- Deactivated,
- }
- // Opening and application iterators
- impl<T: Trait> Module<T> {
- // Iterate through ApplicationById map
- fn application_id_iter_to_map<'a>(
- application_id_iter: impl Iterator<Item = &'a T::ApplicationId>,
- ) -> ApplicationBTreeMap<T> {
- application_id_iter
- .map(|application_id| {
- let application = <ApplicationById<T>>::get(application_id);
- (*application_id, application)
- })
- .collect::<BTreeMap<_, _>>()
- }
- // Compute iterator of openings waiting to begin
- fn openings_waiting_to_begin_iterator(
- now: T::BlockNumber,
- ) -> impl Iterator<
- Item = (
- T::OpeningId,
- Opening<BalanceOf<T>, T::BlockNumber, T::ApplicationId>,
- ),
- > {
- <OpeningById<T>>::iter().filter_map(move |(opening_id, opening)| {
- if let hiring::OpeningStage::WaitingToBegin { begins_at_block } = opening.stage {
- if begins_at_block == now {
- Some((opening_id, opening))
- } else {
- None
- }
- } else {
- None
- }
- })
- }
- // Compute iterator of openings in expired review period
- fn openings_expired_review_period_iterator(
- now: T::BlockNumber,
- ) -> impl Iterator<
- Item = (
- T::OpeningId,
- Opening<BalanceOf<T>, T::BlockNumber, T::ApplicationId>,
- (BTreeSet<T::ApplicationId>, T::BlockNumber, T::BlockNumber),
- ),
- > {
- <OpeningById<T>>::iter().filter_map(move |(opening_id, opening)| {
- if let hiring::OpeningStage::Active {
- ref stage,
- ref applications_added,
- ..
- } = opening.stage
- {
- if let hiring::ActiveOpeningStage::ReviewPeriod {
- ref started_accepting_applicants_at_block,
- ref started_review_period_at_block,
- } = stage
- {
- if now == opening.max_review_period_length + *started_review_period_at_block {
- Some((
- opening_id,
- opening.clone(),
- (
- applications_added.clone(),
- *started_accepting_applicants_at_block,
- *started_review_period_at_block,
- ),
- ))
- } else {
- None
- }
- } else {
- None
- }
- } else {
- None
- }
- })
- }
- }
- // Application deactivation logic methods.
- impl<T: Trait> Module<T> {
- fn initiate_application_deactivations(
- applications: &ApplicationBTreeMap<T>,
- application_stake_unstaking_period: Option<T::BlockNumber>,
- role_stake_unstaking_period: Option<T::BlockNumber>,
- cause: ApplicationDeactivationCause,
- ) -> ApplicationsDeactivationsInitiationResult {
- // Update stage on active applications, and collect result
- applications
- .iter()
- .map(
- |(application_id, application)| -> ApplicationDeactivationInitiationResult {
- // Initiate deactivations!
- Self::try_to_initiate_application_deactivation(
- application,
- *application_id,
- application_stake_unstaking_period,
- role_stake_unstaking_period,
- cause,
- )
- },
- )
- .fold(
- // Initiatial reducer value
- ApplicationsDeactivationsInitiationResult {
- number_of_unstaking_applications: 0,
- number_of_deactivated_applications: 0,
- },
- |acc, deactivation_result| {
- // Update accumulator counters based on what actually happened
- match deactivation_result {
- ApplicationDeactivationInitiationResult::Ignored => acc,
- ApplicationDeactivationInitiationResult::Unstaking => {
- ApplicationsDeactivationsInitiationResult {
- number_of_unstaking_applications: 1 + acc
- .number_of_unstaking_applications,
- number_of_deactivated_applications: acc
- .number_of_deactivated_applications,
- }
- }
- ApplicationDeactivationInitiationResult::Deactivated => {
- ApplicationsDeactivationsInitiationResult {
- number_of_unstaking_applications: acc
- .number_of_unstaking_applications,
- number_of_deactivated_applications: 1 + acc
- .number_of_deactivated_applications,
- }
- }
- }
- },
- )
- }
- /// Initiates
- fn try_to_initiate_application_deactivation(
- application: &Application<T::OpeningId, T::BlockNumber, T::StakeId>,
- application_id: T::ApplicationId,
- application_stake_unstaking_period: Option<T::BlockNumber>,
- role_stake_unstaking_period: Option<T::BlockNumber>,
- cause: hiring::ApplicationDeactivationCause,
- ) -> ApplicationDeactivationInitiationResult {
- match application.stage {
- ApplicationStage::Active => {
- // Initiate unstaking of any active application stake
- let application_was_unstaked = Self::opt_infallible_unstake(
- application.active_application_staking_id,
- application_stake_unstaking_period,
- );
- // Only unstake role stake for a non successful result ie. not Hired
- let role_was_unstaked = cause != hiring::ApplicationDeactivationCause::Hired
- && Self::opt_infallible_unstake(
- application.active_role_staking_id,
- role_stake_unstaking_period,
- );
- // Capture if any unstaking occured at all
- let was_unstaked = application_was_unstaked || role_was_unstaked;
- // Grab current block height
- let current_block_height = <frame_system::Module<T>>::block_number();
- /*
- * TODO:
- * There should be a single transformation based on
- * was_unstaked which renders a new value for `application.stage`
- * and `opening.stage`, which guarantees to only produces new values
- * for given variant values, but the state machine types are currently
- * not well organised to support this.
- *
- * Likewise the construction of hiring::OpeningStage::Active below
- * is a wreck because of this.
- *
- * Issue: https://github.com/Joystream/joystream/issues/36#issuecomment-539567407
- */
- // Figure out new stage for the application
- let new_application_stage = if was_unstaked {
- ApplicationStage::Unstaking {
- deactivation_initiated: current_block_height,
- cause,
- }
- } else {
- ApplicationStage::Inactive {
- deactivation_initiated: current_block_height,
- deactivated: current_block_height,
- cause,
- }
- };
- // Update the application stage
- <ApplicationById<T>>::mutate(application_id, |application| {
- application.stage = new_application_stage;
- });
- // Update counters on opening
- <OpeningById<T>>::mutate(application.opening_id, |opening| {
- // NB: This ugly byref destructuring is same issue as pointed out multiple times now.
- if let hiring::OpeningStage::Active {
- ref stage,
- ref applications_added,
- ref active_application_count,
- ref unstaking_application_count,
- ref deactivated_application_count,
- } = opening.stage
- {
- assert!(*active_application_count > 0);
- let new_active_application_count = active_application_count - 1;
- let new_unstaking_application_count =
- unstaking_application_count + if was_unstaked { 1 } else { 0 };
- let new_deactivated_application_count =
- deactivated_application_count + if was_unstaked { 0 } else { 1 };
- opening.stage = hiring::OpeningStage::Active {
- stage: stage.clone(),
- applications_added: applications_added.clone(),
- active_application_count: new_active_application_count,
- unstaking_application_count: new_unstaking_application_count,
- deactivated_application_count: new_deactivated_application_count,
- };
- } else {
- panic!("opening stage must be 'Active'");
- }
- });
- // Call handler(s)
- if was_unstaked {
- T::ApplicationDeactivatedHandler::deactivated(&application_id, cause);
- }
- // Return conclusion
- if was_unstaked {
- ApplicationDeactivationInitiationResult::Unstaking
- } else {
- ApplicationDeactivationInitiationResult::Deactivated
- }
- }
- _ => ApplicationDeactivationInitiationResult::Ignored,
- }
- }
- /// Tries to unstake, based on a stake id which, if set, MUST
- /// be ready to be unstaked, with an optional unstaking period.
- ///
- /// Returns whether unstaking was actually initiated.
- fn opt_infallible_unstake(
- opt_stake_id: Option<T::StakeId>,
- opt_unstaking_period: Option<T::BlockNumber>,
- ) -> bool {
- if let Some(stake_id) = opt_stake_id {
- // `initiate_unstaking` MUST hold, is runtime invariant, false means code is broken.
- // But should we do panic in runtime? Is there safer way?
- assert!(T::StakeHandlerProvider::staking()
- .initiate_unstaking(&stake_id, opt_unstaking_period)
- .is_ok());
- }
- opt_stake_id.is_some()
- }
- }
- // Stake initiation
- impl<T: Trait> Module<T> {
- fn infallible_opt_stake_initiation(
- opt_imbalance: Option<NegativeImbalance<T>>,
- application_id: &T::ApplicationId,
- ) -> Option<T::StakeId> {
- if let Some(imbalance) = opt_imbalance {
- Some(Self::infallible_stake_initiation_on_application(
- imbalance,
- application_id,
- ))
- } else {
- None
- }
- }
- fn infallible_stake_initiation_on_application(
- imbalance: NegativeImbalance<T>,
- application_id: &T::ApplicationId,
- ) -> T::StakeId {
- // Create stake
- let new_stake_id = T::StakeHandlerProvider::staking().create_stake();
- // Keep track of this stake id to process unstaking callbacks that may
- // be invoked later.
- // NB: We purposefully update state to reflect mapping _before_ initiating staking below
- // in order to be safe from race conditions arising out of third party code executing in callback of staking module.
- // MUST never already be a key for new stake, false means code is broken.
- // But should we do panic in runtime? Is there safer way?
- assert!(!<ApplicationIdByStakingId<T>>::contains_key(new_stake_id));
- <ApplicationIdByStakingId<T>>::insert(new_stake_id, application_id);
- // Initiate staking
- //
- // MUST work, is runtime invariant, false means code is broken.
- // But should we do panic in runtime? Is there safer way?
- assert_eq!(
- T::StakeHandlerProvider::staking().stake(&new_stake_id, imbalance),
- Ok(())
- );
- new_stake_id
- }
- }
- // Conditions for adding application
- impl<T: Trait> Module<T> {
- /// Evaluates prospects for a new application
- ///
- pub(crate) fn would_application_get_added(
- possible_opening_application_rationing_policy: &Option<ApplicationRationingPolicy>,
- opening_applicants: &BTreeSet<T::ApplicationId>,
- opt_role_stake_balance: &Option<BalanceOf<T>>,
- opt_application_stake_balance: &Option<BalanceOf<T>>,
- ) -> ApplicationWouldGetAddedEvaluation<T> {
- // Check whether any rationing policy is set at all, if not
- // then there is no rationing, and any application can get added.
- let application_rationing_policy = if let Some(application_rationing_policy) =
- possible_opening_application_rationing_policy
- {
- application_rationing_policy
- } else {
- return ApplicationWouldGetAddedEvaluation::Yes(
- ApplicationAddedSuccess::Unconditionally,
- );
- };
- // Map with applications
- let applications_map = Self::application_id_iter_to_map(opening_applicants.iter());
- let active_applications_with_stake_iter =
- applications_map
- .iter()
- .filter_map(|(application_id, application)| {
- if application.stage == hiring::ApplicationStage::Active {
- let total_stake =
- Self::get_opt_stake_amount(application.active_role_staking_id)
- + Self::get_opt_stake_amount(
- application.active_application_staking_id,
- );
- Some((application_id, application, total_stake))
- } else {
- None
- }
- });
- // Compute number of active applications
- let number_of_active_applications = active_applications_with_stake_iter.clone().count();
- // Check whether the current number of _active_ applicants is either at or above the maximum
- // limit, if not, then we can add at least one additional application,
- // otherwise we must evaluate whether this new application would specifically get added.
- if (number_of_active_applications as u32)
- < application_rationing_policy.max_active_applicants
- {
- return ApplicationWouldGetAddedEvaluation::Yes(
- ApplicationAddedSuccess::Unconditionally,
- );
- }
- // Here we try to figure out if the new application
- // has sufficient stake to crowd out one of the already
- // active applicants.
- // The total stake of new application
- let total_stake_of_new_application = opt_role_stake_balance.unwrap_or_default()
- + opt_application_stake_balance.unwrap_or_default();
- // The total stake of all current active applications
- let opt_min_item = active_applications_with_stake_iter
- .clone()
- .min_by_key(|(_, _, total_stake)| *total_stake);
- if let Some((application_id, _, lowest_active_total_stake)) = opt_min_item {
- // Finally we compare the two and come up with a final evaluation
- if total_stake_of_new_application <= lowest_active_total_stake {
- ApplicationWouldGetAddedEvaluation::No // stake too low!
- } else {
- ApplicationWouldGetAddedEvaluation::Yes(
- ApplicationAddedSuccess::CrowdsOutExistingApplication(*application_id),
- )
- }
- } else {
- panic!("`number_of_active_applications` (length of `active_applications_iter`) == 0")
- }
- }
- fn get_opt_stake_amount(stake_id: Option<T::StakeId>) -> BalanceOf<T> {
- stake_id.map_or(<BalanceOf<T> as Zero>::zero(), |stake_id| {
- // INVARIANT: stake MUST exist in the staking module
- assert!(T::StakeHandlerProvider::staking().stake_exists(stake_id));
- let stake = T::StakeHandlerProvider::staking().get_stake(stake_id);
- match stake.staking_status {
- // INVARIANT: stake MUST be in the staked state.
- stake::StakingStatus::Staked(staked_state) => staked_state.staked_amount,
- _ => panic!("stake MUST be in the staked state."),
- }
- })
- }
- pub(crate) fn create_stake_balance(
- opt_stake_imbalance: &Option<NegativeImbalance<T>>,
- ) -> Option<BalanceOf<T>> {
- if let Some(ref imbalance) = opt_stake_imbalance {
- Some(imbalance.peek())
- } else {
- None
- }
- }
- /// Performs all necessary check before adding an opening
- pub(crate) fn ensure_can_add_opening(
- current_block_height: T::BlockNumber,
- activate_at: ActivateOpeningAt<T::BlockNumber>,
- minimum_stake_balance: BalanceOf<T>,
- application_rationing_policy: Option<ApplicationRationingPolicy>,
- application_staking_policy: Option<StakingPolicy<BalanceOf<T>, T::BlockNumber>>,
- role_staking_policy: Option<StakingPolicy<BalanceOf<T>, T::BlockNumber>>,
- ) -> Result<(), AddOpeningError> {
- // Check that exact activation is actually in the future
- ensure!(
- match activate_at {
- ActivateOpeningAt::ExactBlock(block_number) => block_number > current_block_height,
- _ => true,
- },
- AddOpeningError::OpeningMustActivateInTheFuture
- );
- if let Some(app_rationing_policy) = application_rationing_policy {
- ensure!(
- app_rationing_policy.max_active_applicants > 0,
- AddOpeningError::ApplicationRationingZeroMaxApplicants
- );
- }
- // Check that staking amounts clear minimum balance required.
- Self::ensure_amount_valid_in_opt_staking_policy(
- application_staking_policy,
- minimum_stake_balance,
- StakePurpose::Application,
- )?;
- // Check that staking amounts clear minimum balance required.
- Self::ensure_amount_valid_in_opt_staking_policy(
- role_staking_policy,
- minimum_stake_balance,
- StakePurpose::Role,
- )?;
- Ok(())
- }
- /// Ensures that optional staking policy prescribes value that clears minimum balance requirement
- pub(crate) fn ensure_amount_valid_in_opt_staking_policy(
- opt_staking_policy: Option<StakingPolicy<BalanceOf<T>, T::BlockNumber>>,
- minimum_stake_balance: BalanceOf<T>,
- stake_purpose: StakePurpose,
- ) -> Result<(), AddOpeningError> {
- if let Some(ref staking_policy) = opt_staking_policy {
- ensure!(
- staking_policy.amount > Zero::zero(),
- AddOpeningError::StakeAmountCannotBeZero(stake_purpose)
- );
- ensure!(
- staking_policy.amount >= minimum_stake_balance,
- AddOpeningError::StakeAmountLessThanMinimumStakeBalance(stake_purpose)
- );
- }
- Ok(())
- }
- }
- /*
- * === Stake module wrappers ======
- */
- /// Defines stake module interface
- #[cfg_attr(all(test, not(target_arch = "wasm32")), automock)]
- pub trait StakeHandler<T: StakeTrait> {
- /// Adds a new Stake which is NotStaked, created at given block, into stakes map.
- fn create_stake(&self) -> T::StakeId;
- /// to the module's account, and the corresponding staked_balance is set to this amount in the new Staked state.
- /// On error, as the negative imbalance is not returned to the caller, it is the caller's responsibility to return the funds
- /// back to the source (by creating a new positive imbalance)
- fn stake(
- &self,
- new_stake_id: &T::StakeId,
- imbalance: NegativeImbalance<T>,
- ) -> Result<(), StakeActionError<stake::StakingError>>;
- /// Checks whether stake exists by its id
- fn stake_exists(&self, stake_id: T::StakeId) -> bool;
- /// Acquires stake by id
- fn get_stake(&self, stake_id: T::StakeId) -> Stake<T::BlockNumber, BalanceOf<T>, T::SlashId>;
- /// Initiate unstaking of a Staked stake.
- fn initiate_unstaking(
- &self,
- stake_id: &T::StakeId,
- unstaking_period: Option<T::BlockNumber>,
- ) -> Result<(), StakeActionError<InitiateUnstakingError>>;
- }
- /// Allows to provide different StakeHandler implementation. Useful for mocks.
- pub trait StakeHandlerProvider<T: Trait> {
- /// Returns StakeHandler. Mock entry point for stake module.
- fn staking() -> Rc<RefCell<dyn StakeHandler<T>>>;
- }
- impl<T: Trait> StakeHandlerProvider<T> for Module<T> {
- /// Returns StakeHandler. Mock entry point for stake module.
- fn staking() -> Rc<RefCell<dyn StakeHandler<T>>> {
- Rc::new(RefCell::new(HiringStakeHandler {}))
- }
- }
- /// Default stake module logic implementation
- pub struct HiringStakeHandler;
- impl<T: Trait> StakeHandler<T> for HiringStakeHandler {
- fn create_stake(&self) -> T::StakeId {
- <stake::Module<T>>::create_stake()
- }
- fn stake(
- &self,
- new_stake_id: &T::StakeId,
- imbalance: NegativeImbalance<T>,
- ) -> Result<(), StakeActionError<StakingError>> {
- <stake::Module<T>>::stake(new_stake_id, imbalance)
- }
- fn stake_exists(&self, stake_id: T::StakeId) -> bool {
- <stake::Stakes<T>>::contains_key(stake_id)
- }
- fn get_stake(&self, stake_id: T::StakeId) -> Stake<T::BlockNumber, BalanceOf<T>, T::SlashId> {
- <stake::Stakes<T>>::get(stake_id)
- }
- fn initiate_unstaking(
- &self,
- stake_id: &T::StakeId,
- unstaking_period: Option<T::BlockNumber>,
- ) -> Result<(), StakeActionError<InitiateUnstakingError>> {
- <stake::Module<T>>::initiate_unstaking(&stake_id, unstaking_period)
- }
- }
- // Proxy implementation of StakeHandler trait to simplify calls via staking() method
- // Allows to get rid of borrow() calls,
- // eg.: T::StakeHandlerProvider::staking().get_stake(stake_id);
- // instead of T::StakeHandlerProvider::staking().borrow().get_stake(stake_id);
- impl<T: Trait> StakeHandler<T> for Rc<RefCell<dyn StakeHandler<T>>> {
- fn create_stake(&self) -> T::StakeId {
- self.borrow().create_stake()
- }
- fn stake(
- &self,
- new_stake_id: &T::StakeId,
- imbalance: NegativeImbalance<T>,
- ) -> Result<(), StakeActionError<StakingError>> {
- self.borrow().stake(new_stake_id, imbalance)
- }
- fn stake_exists(&self, stake_id: T::StakeId) -> bool {
- self.borrow().stake_exists(stake_id)
- }
- fn get_stake(&self, stake_id: T::StakeId) -> Stake<T::BlockNumber, BalanceOf<T>, T::SlashId> {
- self.borrow().get_stake(stake_id)
- }
- fn initiate_unstaking(
- &self,
- stake_id: &T::StakeId,
- unstaking_period: Option<T::BlockNumber>,
- ) -> Result<(), StakeActionError<InitiateUnstakingError>> {
- self.borrow().initiate_unstaking(stake_id, unstaking_period)
- }
- }
|