lib.rs 59 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582
  1. //! Hiring substrate module for the Joystream platform
  2. //!
  3. //! Public APIs:
  4. //! - add_opening
  5. //! - ensure_can_add_application
  6. //! - add_application
  7. //! - deactivate_application
  8. //! - cancel_opening
  9. //! - fill_opening
  10. //! - begin_review
  11. //! - begin_acception_application
  12. //! - unstaked
  13. //!
  14. //! Dependency: Joystream stake module
  15. // Ensure we're `no_std` when compiling for Wasm.
  16. #![cfg_attr(not(feature = "std"), no_std)]
  17. // Do not delete! Cannot be uncommented by default, because of Parity decl_module! issue.
  18. //#![warn(missing_docs)]
  19. // Test dependencies
  20. #[cfg(all(test, not(target_arch = "wasm32")))]
  21. use mockall::predicate::*;
  22. #[cfg(all(test, not(target_arch = "wasm32")))]
  23. use mockall::*;
  24. use codec::Codec;
  25. use frame_support::storage::IterableStorageMap;
  26. use frame_support::traits::{Currency, Imbalance};
  27. use frame_support::{decl_module, decl_storage, ensure, Parameter};
  28. use sp_arithmetic::traits::{BaseArithmetic, One, Zero};
  29. use sp_runtime::traits::{MaybeSerialize, Member};
  30. use sp_std::cell::RefCell;
  31. use sp_std::collections::btree_map::BTreeMap;
  32. use sp_std::collections::btree_set::BTreeSet;
  33. use sp_std::iter::Iterator;
  34. use sp_std::rc::Rc;
  35. use sp_std::vec::Vec;
  36. use stake::{InitiateUnstakingError, Stake, StakeActionError, StakingError, Trait as StakeTrait};
  37. mod hiring;
  38. #[macro_use]
  39. mod macroes;
  40. mod mock;
  41. mod test;
  42. pub use hiring::*;
  43. /// Main trait of hiring substrate module
  44. pub trait Trait: frame_system::Trait + stake::Trait + Sized {
  45. /// OpeningId type
  46. type OpeningId: Parameter
  47. + Member
  48. + BaseArithmetic
  49. + Codec
  50. + Default
  51. + Copy
  52. + MaybeSerialize
  53. + PartialEq;
  54. /// ApplicationId type
  55. type ApplicationId: Parameter
  56. + Member
  57. + BaseArithmetic
  58. + Codec
  59. + Default
  60. + Copy
  61. + MaybeSerialize
  62. + PartialEq;
  63. /// Type that will handle various staking events
  64. type ApplicationDeactivatedHandler: ApplicationDeactivatedHandler<Self>;
  65. /// Marker type for Stake module handler. Indicates that hiring module uses stake module mock.
  66. type StakeHandlerProvider: StakeHandlerProvider<Self>;
  67. }
  68. decl_storage! {
  69. trait Store for Module<T: Trait> as Hiring {
  70. /// Openings.
  71. pub OpeningById get(fn opening_by_id): map hasher(blake2_128_concat)
  72. T::OpeningId => Opening<BalanceOf<T>, T::BlockNumber, T::ApplicationId>;
  73. /// Identifier for next opening to be added.
  74. pub NextOpeningId get(fn next_opening_id): T::OpeningId;
  75. /// Applications
  76. pub ApplicationById get(fn application_by_id): map hasher(blake2_128_concat)
  77. T::ApplicationId => Application<T::OpeningId, T::BlockNumber, T::StakeId>;
  78. /// Identifier for next application to be added.
  79. pub NextApplicationId get(fn next_application_id): T::ApplicationId;
  80. /// Internal purpose of given stake, i.e. fro what application, and whether for the role or for the application.
  81. pub ApplicationIdByStakingId get(fn stake_purpose_by_staking_id): map hasher(blake2_128_concat)
  82. T::StakeId => T::ApplicationId;
  83. }
  84. }
  85. decl_module! {
  86. /// Main hiring module definition
  87. pub struct Module<T: Trait> for enum Call where origin: T::Origin {
  88. fn on_finalize(now: T::BlockNumber) {
  89. //
  90. // == MUTATION SAFE ==
  91. //
  92. // Change opening from WaitingToBegin stage to Active::AcceptingApplications stage
  93. for (opening_id, opening) in Self::openings_waiting_to_begin_iterator(now) {
  94. let opening_accepting_applications = opening.clone_with_new_active_opening_stage(
  95. hiring::ActiveOpeningStage::AcceptingApplications {
  96. started_accepting_applicants_at_block: now
  97. });
  98. <OpeningById<T>>::insert(opening_id, opening_accepting_applications);
  99. }
  100. // Deactivate opening
  101. for (opening_id,
  102. opening,
  103. (
  104. applications_added,
  105. started_accepting_applicants_at_block,
  106. started_review_period_at_block
  107. )) in Self::openings_expired_review_period_iterator(now) {
  108. //
  109. // Deactivate all applications that are part of this opening
  110. //
  111. // Get unstaking periods
  112. let application_stake_unstaking_period = StakingPolicy::opt_staking_policy_to_review_period_expired_unstaking_period(&opening.application_staking_policy);
  113. let role_stake_unstaking_period = StakingPolicy::opt_staking_policy_to_review_period_expired_unstaking_period(&opening.role_staking_policy);
  114. // Get applications
  115. let applications_map = Self::application_id_iter_to_map(applications_added.iter());
  116. // Deactivate applications
  117. Self::initiate_application_deactivations(
  118. &applications_map,
  119. application_stake_unstaking_period,
  120. role_stake_unstaking_period,
  121. hiring::ApplicationDeactivationCause::ReviewPeriodExpired
  122. );
  123. let deactivated_opening =
  124. opening.clone_with_new_active_opening_stage(
  125. hiring::ActiveOpeningStage::Deactivated {
  126. cause: hiring::OpeningDeactivationCause::ReviewPeriodExpired,
  127. deactivated_at_block: now,
  128. started_accepting_applicants_at_block,
  129. started_review_period_at_block: Some(started_review_period_at_block),
  130. });
  131. <OpeningById<T>>::insert(opening_id, deactivated_opening);
  132. }
  133. }
  134. }
  135. }
  136. /*
  137. * ======== Main API implementation ========
  138. */
  139. // Public API implementation
  140. impl<T: Trait> Module<T> {
  141. /// Add new opening based on given inputs policies.
  142. /// The new Opening instance has stage WaitingToBegin, and is added to openingsById,
  143. /// and has identifier equal to nextOpeningId.
  144. /// The latter is incremented. The used identifier is returned.
  145. pub fn add_opening(
  146. activate_at: ActivateOpeningAt<T::BlockNumber>,
  147. max_review_period_length: T::BlockNumber,
  148. application_rationing_policy: Option<ApplicationRationingPolicy>,
  149. application_staking_policy: Option<StakingPolicy<BalanceOf<T>, T::BlockNumber>>,
  150. role_staking_policy: Option<StakingPolicy<BalanceOf<T>, T::BlockNumber>>,
  151. human_readable_text: Vec<u8>,
  152. ) -> Result<T::OpeningId, AddOpeningError> {
  153. let current_block_height = <frame_system::Module<T>>::block_number();
  154. Self::ensure_can_add_opening(
  155. current_block_height,
  156. activate_at.clone(),
  157. T::Currency::minimum_balance(),
  158. application_rationing_policy.clone(),
  159. application_staking_policy.clone(),
  160. role_staking_policy.clone(),
  161. )?;
  162. // == MUTATION SAFE ==
  163. let new_opening = hiring::Opening::new(
  164. current_block_height,
  165. activate_at,
  166. max_review_period_length,
  167. application_rationing_policy,
  168. application_staking_policy,
  169. role_staking_policy,
  170. human_readable_text,
  171. );
  172. // Get Id for new opening
  173. let new_opening_id = <NextOpeningId<T>>::get();
  174. // Insert opening in storage
  175. <OpeningById<T>>::insert(new_opening_id, new_opening);
  176. // Update NextOpeningId counter
  177. <NextOpeningId<T>>::mutate(|id| *id += T::OpeningId::one());
  178. // Return
  179. Ok(new_opening_id)
  180. }
  181. /// Cancels opening with given identifier, using provided unstaking periods for
  182. /// application and role, as necesary.
  183. pub fn cancel_opening(
  184. opening_id: T::OpeningId,
  185. application_stake_unstaking_period: Option<T::BlockNumber>,
  186. role_stake_unstaking_period: Option<T::BlockNumber>,
  187. ) -> Result<OpeningCancelled, CancelOpeningError> {
  188. // Ensure that the opening exists
  189. let opening =
  190. ensure_opening_exists!(T, opening_id, CancelOpeningError::OpeningDoesNotExist)?;
  191. // Opening is in stage Active.{AcceptingApplications or ReviewPeriod}
  192. let (
  193. active_stage,
  194. applications_added,
  195. active_application_count,
  196. unstaking_application_count,
  197. deactivated_application_count,
  198. ) = ensure_opening_is_active!(
  199. opening.stage,
  200. CancelOpeningError::OpeningNotInCancellableStage
  201. )?;
  202. //
  203. let current_block_height = <frame_system::Module<T>>::block_number(); // move later!
  204. let new_active_stage = active_stage.new_stage_on_cancelling(current_block_height)?;
  205. // Ensure unstaking periods are OK.
  206. ensure_opt_unstaking_period_is_ok!(
  207. application_stake_unstaking_period,
  208. opening.application_staking_policy,
  209. CancelOpeningError::UnstakingPeriodTooShort(StakePurpose::Application),
  210. CancelOpeningError::RedundantUnstakingPeriodProvided(StakePurpose::Application)
  211. )?;
  212. ensure_opt_unstaking_period_is_ok!(
  213. role_stake_unstaking_period,
  214. opening.role_staking_policy,
  215. CancelOpeningError::UnstakingPeriodTooShort(StakePurpose::Role),
  216. CancelOpeningError::RedundantUnstakingPeriodProvided(StakePurpose::Role)
  217. )?;
  218. //
  219. // == MUTATION SAFE ==
  220. //
  221. // Create and store new cancelled opening
  222. let new_opening = Opening {
  223. stage: hiring::OpeningStage::Active {
  224. stage: new_active_stage,
  225. applications_added: applications_added.clone(),
  226. active_application_count,
  227. unstaking_application_count,
  228. deactivated_application_count,
  229. },
  230. ..opening
  231. };
  232. OpeningById::<T>::insert(opening_id, new_opening);
  233. // Map with applications
  234. let applications_map = Self::application_id_iter_to_map(applications_added.iter());
  235. // Initiate deactivation of all active applications
  236. let net_result = Self::initiate_application_deactivations(
  237. &applications_map,
  238. application_stake_unstaking_period,
  239. role_stake_unstaking_period,
  240. hiring::ApplicationDeactivationCause::OpeningCancelled,
  241. );
  242. // Return
  243. Ok(OpeningCancelled {
  244. number_of_unstaking_applications: net_result.number_of_unstaking_applications,
  245. number_of_deactivated_applications: net_result.number_of_deactivated_applications,
  246. })
  247. }
  248. /// Transit opening to the accepting application stage.
  249. /// Applies when given opening is in WaitingToBegin stage.
  250. /// The stage is updated to Active stage with AcceptingApplications substage
  251. pub fn begin_accepting_applications(
  252. opening_id: T::OpeningId,
  253. ) -> Result<(), BeginAcceptingApplicationsError> {
  254. // Ensure that the opening exists
  255. let opening = ensure_opening_exists!(
  256. T,
  257. opening_id,
  258. BeginAcceptingApplicationsError::OpeningDoesNotExist
  259. )?;
  260. // Ensure that it is the waiting to begin stage
  261. opening.stage.ensure_opening_stage_is_waiting_to_begin(
  262. BeginAcceptingApplicationsError::OpeningIsNotInWaitingToBeginStage,
  263. )?;
  264. //
  265. // == MUTATION SAFE ==
  266. //
  267. let current_block_height = <frame_system::Module<T>>::block_number();
  268. // Update state of opening
  269. let new_opening = opening.clone_with_new_active_opening_stage(
  270. hiring::ActiveOpeningStage::AcceptingApplications {
  271. started_accepting_applicants_at_block: current_block_height,
  272. },
  273. );
  274. // Write back opening
  275. <OpeningById<T>>::insert(opening_id, new_opening);
  276. // DONE
  277. Ok(())
  278. }
  279. /// Transit opening to the begin review period stage.
  280. /// Applies when given opening is in Active stage and AcceptingApplications substage.
  281. /// The stage is updated to Active stage and ReviewPeriod substage
  282. pub fn begin_review(opening_id: T::OpeningId) -> Result<(), BeginReviewError> {
  283. // Ensure that the opening exists
  284. let opening = ensure_opening_exists!(T, opening_id, BeginReviewError::OpeningDoesNotExist)?;
  285. // Opening is accepting applications
  286. let (active_stage, _, _, _, _) = ensure_opening_is_active!(
  287. opening.stage,
  288. BeginReviewError::OpeningNotInAcceptingApplicationsStage
  289. )?;
  290. let started_accepting_applicants_at_block = ensure_active_opening_is_accepting_applications!(
  291. active_stage,
  292. BeginReviewError::OpeningNotInAcceptingApplicationsStage
  293. )?;
  294. //
  295. // == MUTATION SAFE ==
  296. //
  297. let current_block_height = <frame_system::Module<T>>::block_number();
  298. let new_opening =
  299. opening.clone_with_new_active_opening_stage(hiring::ActiveOpeningStage::ReviewPeriod {
  300. started_accepting_applicants_at_block,
  301. started_review_period_at_block: current_block_height,
  302. });
  303. // Update to new opening
  304. <OpeningById<T>>::insert(opening_id, new_opening);
  305. Ok(())
  306. }
  307. /// Fill an opening, identified with `opening_id`, currently in the review period.
  308. /// Applies when given opening is in ReviewPeriod stage.
  309. /// Given list of applications are deactivated to under the Hired,
  310. /// all other active applicants are NotHired.
  311. /// Separately for each group,
  312. /// unstaking periods for any applicable application and/or role stake must be provided.
  313. pub fn fill_opening(
  314. opening_id: T::OpeningId,
  315. successful_applications: BTreeSet<T::ApplicationId>,
  316. opt_successful_applicant_application_stake_unstaking_period: Option<T::BlockNumber>,
  317. opt_failed_applicant_application_stake_unstaking_period: Option<T::BlockNumber>,
  318. /* this parameter does not make sense? opt_successful_applicant_role_stake_unstaking_period: Option<T::BlockNumber>, */
  319. opt_failed_applicant_role_stake_unstaking_period: Option<T::BlockNumber>,
  320. ) -> Result<(), FillOpeningError<T>> {
  321. // Ensure that the opening exists
  322. let opening = ensure_opening_exists!(T, opening_id, FillOpeningError::OpeningDoesNotExist)?;
  323. let (active_stage, applications_added, _, _, _) = ensure_opening_is_active!(
  324. opening.stage,
  325. FillOpeningError::OpeningNotInReviewPeriodStage
  326. )?;
  327. // Ensure opening is in review period
  328. let (started_accepting_applicants_at_block, started_review_period_at_block) = active_stage
  329. .ensure_active_opening_is_in_review_period(
  330. FillOpeningError::OpeningNotInReviewPeriodStage,
  331. )?;
  332. //
  333. // Ensure that all unstaking periods are neither too short (0) nor redundant.
  334. //
  335. ensure_opt_unstaking_period_is_ok!(
  336. opt_successful_applicant_application_stake_unstaking_period,
  337. opening.application_staking_policy,
  338. FillOpeningError::UnstakingPeriodTooShort(
  339. StakePurpose::Application,
  340. ApplicationOutcomeInFilledOpening::Success
  341. ),
  342. FillOpeningError::RedundantUnstakingPeriodProvided(
  343. StakePurpose::Application,
  344. ApplicationOutcomeInFilledOpening::Success
  345. )
  346. )?;
  347. ensure_opt_unstaking_period_is_ok!(
  348. opt_failed_applicant_application_stake_unstaking_period,
  349. opening.application_staking_policy,
  350. FillOpeningError::UnstakingPeriodTooShort(
  351. StakePurpose::Application,
  352. ApplicationOutcomeInFilledOpening::Failure
  353. ),
  354. FillOpeningError::RedundantUnstakingPeriodProvided(
  355. StakePurpose::Application,
  356. ApplicationOutcomeInFilledOpening::Failure
  357. )
  358. )?;
  359. ensure_opt_unstaking_period_is_ok!(
  360. opt_failed_applicant_role_stake_unstaking_period,
  361. opening.role_staking_policy,
  362. FillOpeningError::UnstakingPeriodTooShort(
  363. StakePurpose::Role,
  364. ApplicationOutcomeInFilledOpening::Failure
  365. ),
  366. FillOpeningError::RedundantUnstakingPeriodProvided(
  367. StakePurpose::Role,
  368. ApplicationOutcomeInFilledOpening::Failure
  369. )
  370. )?;
  371. // Ensure that all successful applications actually exist
  372. for application_id in &successful_applications {
  373. ensure_application_exists!(
  374. T,
  375. *application_id,
  376. FillOpeningError::ApplicationDoesNotExist(*application_id)
  377. )?;
  378. }
  379. let successful_applications_map =
  380. Self::application_id_iter_to_map(successful_applications.iter());
  381. // Ensure that all successful applications are actually active and associated with the opening
  382. for (application_id, application) in &successful_applications_map {
  383. ensure_eq!(
  384. application.stage,
  385. hiring::ApplicationStage::Active,
  386. FillOpeningError::ApplicationNotInActiveStage(*application_id,)
  387. );
  388. ensure_eq!(
  389. application.opening_id,
  390. opening_id,
  391. FillOpeningError::ApplicationForWrongOpening(*application_id)
  392. );
  393. }
  394. //
  395. // == MUTATION SAFE ==
  396. //
  397. // Deactivate all successful applications, with cause being hired
  398. Self::initiate_application_deactivations(
  399. &successful_applications_map,
  400. opt_successful_applicant_application_stake_unstaking_period,
  401. None,
  402. hiring::ApplicationDeactivationCause::Hired,
  403. );
  404. // Deactivate all unsuccessful applications, with cause being not being hired.
  405. // First get all failed applications by their id.
  406. let failed_applications_map = Self::application_id_iter_to_map(
  407. applications_added.difference(&successful_applications),
  408. );
  409. // Deactivate all successful applications, with cause being not hired
  410. Self::initiate_application_deactivations(
  411. &failed_applications_map,
  412. opt_failed_applicant_application_stake_unstaking_period,
  413. opt_failed_applicant_role_stake_unstaking_period,
  414. hiring::ApplicationDeactivationCause::NotHired,
  415. );
  416. // Grab current block height
  417. let current_block_height = <frame_system::Module<T>>::block_number();
  418. // Get opening with updated counters
  419. let opening_needed_for_data = <OpeningById<T>>::get(opening_id);
  420. // Deactivate opening
  421. let new_opening = opening_needed_for_data.clone_with_new_active_opening_stage(
  422. hiring::ActiveOpeningStage::Deactivated {
  423. cause: OpeningDeactivationCause::Filled,
  424. deactivated_at_block: current_block_height,
  425. started_accepting_applicants_at_block,
  426. started_review_period_at_block: Some(started_review_period_at_block),
  427. },
  428. );
  429. // Write back new opening
  430. <OpeningById<T>>::insert(opening_id, new_opening);
  431. // DONE
  432. Ok(())
  433. }
  434. /// Adds a new application on the given opening, and begins staking for
  435. /// the role, the application or both possibly.
  436. pub fn ensure_can_add_application(
  437. opening_id: T::OpeningId,
  438. opt_role_stake_balance: Option<BalanceOf<T>>,
  439. opt_application_stake_balance: Option<BalanceOf<T>>,
  440. ) -> Result<DestructuredApplicationCanBeAddedEvaluation<T>, AddApplicationError> {
  441. // Ensure that the opening exists
  442. let opening =
  443. ensure_opening_exists!(T, opening_id, AddApplicationError::OpeningDoesNotExist)?;
  444. // Ensure that proposed stakes match the policy of the opening.
  445. let opt_role_stake_balance = ensure_stake_balance_matches_staking_policy!(
  446. &opt_role_stake_balance,
  447. &opening.role_staking_policy,
  448. AddApplicationError::StakeMissingWhenRequired(StakePurpose::Role),
  449. AddApplicationError::StakeProvidedWhenRedundant(StakePurpose::Role),
  450. AddApplicationError::StakeAmountTooLow(StakePurpose::Role)
  451. )?;
  452. let opt_application_stake_balance = ensure_stake_balance_matches_staking_policy!(
  453. &opt_application_stake_balance,
  454. &opening.application_staking_policy,
  455. AddApplicationError::StakeMissingWhenRequired(StakePurpose::Application),
  456. AddApplicationError::StakeProvidedWhenRedundant(StakePurpose::Application),
  457. AddApplicationError::StakeAmountTooLow(StakePurpose::Application)
  458. )?;
  459. // Opening is accepting applications
  460. let (
  461. active_stage,
  462. applications_added,
  463. active_application_count,
  464. unstaking_application_count,
  465. deactivated_application_count,
  466. ) = ensure_opening_is_active!(
  467. opening.stage,
  468. AddApplicationError::OpeningNotInAcceptingApplicationsStage
  469. )?;
  470. active_stage.ensure_active_opening_is_accepting_applications(
  471. AddApplicationError::OpeningNotInAcceptingApplicationsStage,
  472. )?;
  473. // Ensure that the new application would actually make it
  474. let would_get_added_success = ensure_application_would_get_added!(
  475. &opening.application_rationing_policy,
  476. &applications_added,
  477. &opt_role_stake_balance,
  478. &opt_application_stake_balance,
  479. AddApplicationError::NewApplicationWasCrowdedOut
  480. )?;
  481. Ok(DestructuredApplicationCanBeAddedEvaluation {
  482. opening,
  483. active_stage,
  484. applications_added,
  485. active_application_count,
  486. unstaking_application_count,
  487. deactivated_application_count,
  488. would_get_added_success,
  489. })
  490. }
  491. /// Adds a new application on the given opening, and begins staking for
  492. /// the role, the application or both possibly.
  493. pub fn add_application(
  494. opening_id: T::OpeningId,
  495. opt_role_stake_imbalance: Option<NegativeImbalance<T>>,
  496. opt_application_stake_imbalance: Option<NegativeImbalance<T>>,
  497. human_readable_text: Vec<u8>,
  498. ) -> Result<ApplicationAdded<T::ApplicationId>, AddApplicationError> {
  499. let opt_role_stake_balance = Self::create_stake_balance(&opt_role_stake_imbalance);
  500. let opt_application_stake_balance =
  501. Self::create_stake_balance(&opt_application_stake_imbalance);
  502. let can_be_added_destructured = Self::ensure_can_add_application(
  503. opening_id,
  504. opt_role_stake_balance,
  505. opt_application_stake_balance,
  506. )?;
  507. //
  508. // == MUTATION SAFE ==
  509. //
  510. // If required, deactive another application that was crowded out.
  511. if let ApplicationAddedSuccess::CrowdsOutExistingApplication(
  512. id_of_croweded_out_application,
  513. ) = can_be_added_destructured.would_get_added_success
  514. {
  515. // Get relevant unstaking periods
  516. let opt_application_stake_unstaking_period =
  517. hiring::StakingPolicy::opt_staking_policy_to_crowded_out_unstaking_period(
  518. &can_be_added_destructured.opening.application_staking_policy,
  519. );
  520. let opt_role_stake_unstaking_period =
  521. hiring::StakingPolicy::opt_staking_policy_to_crowded_out_unstaking_period(
  522. &can_be_added_destructured.opening.role_staking_policy,
  523. );
  524. // Fetch application
  525. let crowded_out_application = <ApplicationById<T>>::get(id_of_croweded_out_application);
  526. // Initiate actual deactivation
  527. //
  528. // MUST not have been ignored, is runtime invariant, false means code is broken.
  529. // But should we do panic in runtime? Is there safer way?
  530. let deactivation_result = Self::try_to_initiate_application_deactivation(
  531. &crowded_out_application,
  532. id_of_croweded_out_application,
  533. opt_application_stake_unstaking_period,
  534. opt_role_stake_unstaking_period,
  535. hiring::ApplicationDeactivationCause::CrowdedOut,
  536. );
  537. assert_ne!(
  538. deactivation_result,
  539. ApplicationDeactivationInitiationResult::Ignored
  540. );
  541. }
  542. // Get Id for this new application
  543. let new_application_id = <NextApplicationId<T>>::get();
  544. // Possibly initiate staking
  545. let active_role_staking_id =
  546. Self::infallible_opt_stake_initiation(opt_role_stake_imbalance, &new_application_id);
  547. let active_application_staking_id = Self::infallible_opt_stake_initiation(
  548. opt_application_stake_imbalance,
  549. &new_application_id,
  550. );
  551. // Grab current block height
  552. let current_block_height = <frame_system::Module<T>>::block_number();
  553. // Compute index for this new application
  554. let application_index_in_opening =
  555. can_be_added_destructured.calculate_total_application_count();
  556. // Create a new application
  557. let new_application = hiring::Application {
  558. opening_id,
  559. application_index_in_opening,
  560. add_to_opening_in_block: current_block_height,
  561. active_role_staking_id,
  562. active_application_staking_id,
  563. // Stage of new application
  564. stage: hiring::ApplicationStage::Active,
  565. human_readable_text,
  566. };
  567. // Insert into main application map
  568. <ApplicationById<T>>::insert(new_application_id, new_application);
  569. // Update next application id
  570. <NextApplicationId<T>>::mutate(|id| *id += One::one());
  571. // Update counter on opening
  572. // Should reload after possible deactivation in try_to_initiate_application_deactivation
  573. let opening_needed_for_data = <OpeningById<T>>::get(opening_id);
  574. let new_active_stage = opening_needed_for_data
  575. .stage
  576. .clone_with_added_active_application(new_application_id);
  577. <OpeningById<T>>::mutate(opening_id, |opening| {
  578. opening.stage = new_active_stage;
  579. });
  580. let application_id_crowded_out = can_be_added_destructured
  581. .would_get_added_success
  582. .crowded_out_application_id();
  583. // DONE
  584. Ok(ApplicationAdded {
  585. application_id_added: new_application_id,
  586. application_id_crowded_out,
  587. })
  588. }
  589. /// Deactive an active application.
  590. /// Does currently not support slashing
  591. pub fn deactive_application(
  592. application_id: T::ApplicationId,
  593. application_stake_unstaking_period: Option<T::BlockNumber>,
  594. role_stake_unstaking_period: Option<T::BlockNumber>,
  595. ) -> Result<(), DeactivateApplicationError> {
  596. // Check that application id is valid, and if so,
  597. // grab corresponding application and opening.
  598. let (application, opening) = ensure_application_exists!(
  599. T,
  600. application_id,
  601. DeactivateApplicationError::ApplicationDoesNotExist,
  602. auto_fetch_opening
  603. )?;
  604. // Application is active
  605. ensure_eq!(
  606. application.stage,
  607. hiring::ApplicationStage::Active,
  608. DeactivateApplicationError::ApplicationNotActive
  609. );
  610. // Opening is accepting applications
  611. let (active_stage, ..) = ensure_opening_is_active!(
  612. opening.stage,
  613. DeactivateApplicationError::OpeningNotAcceptingApplications
  614. )?;
  615. active_stage.ensure_active_opening_is_accepting_applications(
  616. DeactivateApplicationError::OpeningNotAcceptingApplications,
  617. )?;
  618. // Ensure unstaking periods are OK.
  619. ensure_opt_unstaking_period_is_ok!(
  620. application_stake_unstaking_period,
  621. opening.application_staking_policy,
  622. DeactivateApplicationError::UnstakingPeriodTooShort(StakePurpose::Application),
  623. DeactivateApplicationError::RedundantUnstakingPeriodProvided(StakePurpose::Application)
  624. )?;
  625. ensure_opt_unstaking_period_is_ok!(
  626. role_stake_unstaking_period,
  627. opening.role_staking_policy,
  628. DeactivateApplicationError::UnstakingPeriodTooShort(StakePurpose::Role),
  629. DeactivateApplicationError::RedundantUnstakingPeriodProvided(StakePurpose::Role)
  630. )?;
  631. //
  632. // == MUTATION SAFE ==
  633. //
  634. // Deactive application
  635. let result = Self::try_to_initiate_application_deactivation(
  636. &application,
  637. application_id,
  638. application_stake_unstaking_period,
  639. role_stake_unstaking_period,
  640. hiring::ApplicationDeactivationCause::External,
  641. );
  642. assert_ne!(result, ApplicationDeactivationInitiationResult::Ignored);
  643. // DONE
  644. Ok(())
  645. }
  646. /// The stake, with the given id, was unstaked.
  647. pub fn unstaked(stake_id: T::StakeId) -> UnstakedResult {
  648. // Ignore unstaked
  649. if !<ApplicationIdByStakingId<T>>::contains_key(stake_id) {
  650. return UnstakedResult::StakeIdNonExistent;
  651. }
  652. // Get application
  653. let application_id = <ApplicationIdByStakingId<T>>::get(stake_id);
  654. assert!(<ApplicationById<T>>::contains_key(application_id));
  655. let application = <ApplicationById<T>>::get(application_id);
  656. // Make sure that we are actually unstaking, ignore otherwise.
  657. let (deactivation_initiated, cause) = if let ApplicationStage::Unstaking {
  658. deactivation_initiated,
  659. cause,
  660. } = application.stage
  661. {
  662. (deactivation_initiated, cause)
  663. } else {
  664. return UnstakedResult::ApplicationIsNotUnstaking;
  665. };
  666. //
  667. // == MUTATION SAFE ==
  668. //
  669. // Drop stake from stake to application map
  670. <ApplicationIdByStakingId<T>>::remove(stake_id);
  671. let current_block_height = <frame_system::Module<T>>::block_number();
  672. // New application computed
  673. let mut new_application = application.clone();
  674. let is_now_done_unstaking = new_application.unstake_application(
  675. current_block_height,
  676. deactivation_initiated,
  677. cause,
  678. stake_id,
  679. );
  680. // Update to new application
  681. <ApplicationById<T>>::insert(&application_id, new_application);
  682. // If the application is now finished compeleting any pending unstaking process,
  683. // then we need to update the opening counters, and make the deactivation callback.
  684. if is_now_done_unstaking {
  685. // Update Opening
  686. // We know the stage MUST be active, hence mutate is certain.
  687. <OpeningById<T>>::mutate(application.opening_id, |opening| {
  688. opening.change_opening_stage_after_application_unstaked();
  689. });
  690. // Call handler
  691. T::ApplicationDeactivatedHandler::deactivated(&application_id, cause);
  692. return UnstakedResult::Unstaked;
  693. }
  694. UnstakedResult::UnstakingInProgress
  695. }
  696. }
  697. /*
  698. * === Application Deactivated Handler ======
  699. */
  700. /// Handles application deactivation with a cause
  701. pub trait ApplicationDeactivatedHandler<T: Trait> {
  702. /// An application, with the given id, was fully deactivated, with the
  703. /// given cause, and was put in the inactive state.
  704. fn deactivated(application_id: &T::ApplicationId, cause: hiring::ApplicationDeactivationCause);
  705. }
  706. /// Helper implementation so we can provide multiple handlers by grouping handlers in tuple pairs.
  707. /// For example for three handlers, A, B and C we can set the StakingEventHandler type on the trait to:
  708. /// type StakingEventHandler = ((A, B), C)
  709. impl<T: Trait> ApplicationDeactivatedHandler<T> for () {
  710. fn deactivated(
  711. _application_id: &T::ApplicationId,
  712. _cause: hiring::ApplicationDeactivationCause,
  713. ) {
  714. }
  715. }
  716. /*
  717. * ======== API types bound to the Trait ========
  718. */
  719. /// Error due to attempting to fill an opening.
  720. #[derive(Eq, PartialEq, Clone, Debug)]
  721. pub enum FillOpeningError<T: Trait> {
  722. /// Opening does not exist
  723. OpeningDoesNotExist,
  724. /// Opening is not in review period
  725. OpeningNotInReviewPeriodStage,
  726. /// Provided unstaking period is too short
  727. UnstakingPeriodTooShort(StakePurpose, ApplicationOutcomeInFilledOpening),
  728. /// Provided redundant unstaking period
  729. RedundantUnstakingPeriodProvided(StakePurpose, ApplicationOutcomeInFilledOpening),
  730. /// Application does not exist
  731. ApplicationDoesNotExist(T::ApplicationId),
  732. /// Application is not in active stage
  733. ApplicationNotInActiveStage(T::ApplicationId),
  734. /// Application is not for the opening
  735. ApplicationForWrongOpening(T::ApplicationId),
  736. }
  737. /// Product of ensure_can_add_application()
  738. #[derive(Eq, PartialEq, Clone, Debug)]
  739. pub struct DestructuredApplicationCanBeAddedEvaluation<T: Trait> {
  740. /// Opening object
  741. pub opening: Opening<BalanceOf<T>, T::BlockNumber, T::ApplicationId>,
  742. /// Active opening stage
  743. pub active_stage: ActiveOpeningStage<T::BlockNumber>,
  744. /// Collection of added applicaiton ids
  745. pub applications_added: BTreeSet<T::ApplicationId>,
  746. /// Active applications counter
  747. pub active_application_count: u32,
  748. /// Unstaking applications counter
  749. pub unstaking_application_count: u32,
  750. /// Deactivated applications counter
  751. pub deactivated_application_count: u32,
  752. /// Prospects of application adding
  753. pub would_get_added_success: ApplicationAddedSuccess<T>,
  754. }
  755. impl<T: Trait> DestructuredApplicationCanBeAddedEvaluation<T> {
  756. pub(crate) fn calculate_total_application_count(&self) -> u32 {
  757. // TODO: fix so that `number_of_appliations_ever_added` can be invoked.
  758. // cant do this due to bad design of stage => opening.stage.number_of_appliations_ever_added();
  759. self.active_application_count
  760. + self.unstaking_application_count
  761. + self.deactivated_application_count
  762. }
  763. }
  764. /// Adding application result
  765. #[derive(Eq, PartialEq, Clone, Debug)]
  766. pub enum ApplicationAddedSuccess<T: Trait> {
  767. /// Application was added without side-effects
  768. Unconditionally,
  769. /// Application has crowded out existing application
  770. CrowdsOutExistingApplication(T::ApplicationId),
  771. }
  772. impl<T: Trait> ApplicationAddedSuccess<T> {
  773. pub(crate) fn crowded_out_application_id(&self) -> Option<T::ApplicationId> {
  774. if let ApplicationAddedSuccess::CrowdsOutExistingApplication(id) = self {
  775. Some(*id)
  776. } else {
  777. None
  778. }
  779. }
  780. }
  781. /// Prospects of application. Whether it would be added to the opening.
  782. #[derive(Eq, PartialEq, Clone, Debug)]
  783. pub enum ApplicationWouldGetAddedEvaluation<T: Trait> {
  784. /// Negative prospects
  785. No,
  786. /// Positive prospects
  787. Yes(ApplicationAddedSuccess<T>),
  788. }
  789. /// Balance alias
  790. pub type BalanceOf<T> =
  791. <<T as stake::Trait>::Currency as Currency<<T as frame_system::Trait>::AccountId>>::Balance;
  792. /// Balance alias for staking
  793. pub type NegativeImbalance<T> = <<T as stake::Trait>::Currency as Currency<
  794. <T as frame_system::Trait>::AccountId,
  795. >>::NegativeImbalance;
  796. /*
  797. * ======== ======== ======== ======== =======
  798. * ======== PRIVATE TYPES AND METHODS ========
  799. * ======== ======== ======== ======== =======
  800. */
  801. #[derive(PartialEq, Debug, Clone)]
  802. struct ApplicationsDeactivationsInitiationResult {
  803. number_of_unstaking_applications: u32,
  804. number_of_deactivated_applications: u32,
  805. }
  806. type ApplicationBTreeMap<T> = BTreeMap<
  807. <T as Trait>::ApplicationId,
  808. hiring::Application<
  809. <T as Trait>::OpeningId,
  810. <T as frame_system::Trait>::BlockNumber,
  811. <T as stake::Trait>::StakeId,
  812. >,
  813. >;
  814. #[derive(PartialEq, Debug, Clone)]
  815. enum ApplicationDeactivationInitiationResult {
  816. Ignored, // <= is there a case for kicking this out, making sure that initiation cannot happen when it may fail?
  817. Unstaking,
  818. Deactivated,
  819. }
  820. // Opening and application iterators
  821. impl<T: Trait> Module<T> {
  822. // Iterate through ApplicationById map
  823. fn application_id_iter_to_map<'a>(
  824. application_id_iter: impl Iterator<Item = &'a T::ApplicationId>,
  825. ) -> ApplicationBTreeMap<T> {
  826. application_id_iter
  827. .map(|application_id| {
  828. let application = <ApplicationById<T>>::get(application_id);
  829. (*application_id, application)
  830. })
  831. .collect::<BTreeMap<_, _>>()
  832. }
  833. // Compute iterator of openings waiting to begin
  834. fn openings_waiting_to_begin_iterator(
  835. now: T::BlockNumber,
  836. ) -> impl Iterator<
  837. Item = (
  838. T::OpeningId,
  839. Opening<BalanceOf<T>, T::BlockNumber, T::ApplicationId>,
  840. ),
  841. > {
  842. <OpeningById<T>>::iter().filter_map(move |(opening_id, opening)| {
  843. if let hiring::OpeningStage::WaitingToBegin { begins_at_block } = opening.stage {
  844. if begins_at_block == now {
  845. Some((opening_id, opening))
  846. } else {
  847. None
  848. }
  849. } else {
  850. None
  851. }
  852. })
  853. }
  854. // Compute iterator of openings in expired review period
  855. fn openings_expired_review_period_iterator(
  856. now: T::BlockNumber,
  857. ) -> impl Iterator<
  858. Item = (
  859. T::OpeningId,
  860. Opening<BalanceOf<T>, T::BlockNumber, T::ApplicationId>,
  861. (BTreeSet<T::ApplicationId>, T::BlockNumber, T::BlockNumber),
  862. ),
  863. > {
  864. <OpeningById<T>>::iter().filter_map(move |(opening_id, opening)| {
  865. if let hiring::OpeningStage::Active {
  866. ref stage,
  867. ref applications_added,
  868. ..
  869. } = opening.stage
  870. {
  871. if let hiring::ActiveOpeningStage::ReviewPeriod {
  872. ref started_accepting_applicants_at_block,
  873. ref started_review_period_at_block,
  874. } = stage
  875. {
  876. if now == opening.max_review_period_length + *started_review_period_at_block {
  877. Some((
  878. opening_id,
  879. opening.clone(),
  880. (
  881. applications_added.clone(),
  882. *started_accepting_applicants_at_block,
  883. *started_review_period_at_block,
  884. ),
  885. ))
  886. } else {
  887. None
  888. }
  889. } else {
  890. None
  891. }
  892. } else {
  893. None
  894. }
  895. })
  896. }
  897. }
  898. // Application deactivation logic methods.
  899. impl<T: Trait> Module<T> {
  900. fn initiate_application_deactivations(
  901. applications: &ApplicationBTreeMap<T>,
  902. application_stake_unstaking_period: Option<T::BlockNumber>,
  903. role_stake_unstaking_period: Option<T::BlockNumber>,
  904. cause: ApplicationDeactivationCause,
  905. ) -> ApplicationsDeactivationsInitiationResult {
  906. // Update stage on active applications, and collect result
  907. applications
  908. .iter()
  909. .map(
  910. |(application_id, application)| -> ApplicationDeactivationInitiationResult {
  911. // Initiate deactivations!
  912. Self::try_to_initiate_application_deactivation(
  913. application,
  914. *application_id,
  915. application_stake_unstaking_period,
  916. role_stake_unstaking_period,
  917. cause,
  918. )
  919. },
  920. )
  921. .fold(
  922. // Initiatial reducer value
  923. ApplicationsDeactivationsInitiationResult {
  924. number_of_unstaking_applications: 0,
  925. number_of_deactivated_applications: 0,
  926. },
  927. |acc, deactivation_result| {
  928. // Update accumulator counters based on what actually happened
  929. match deactivation_result {
  930. ApplicationDeactivationInitiationResult::Ignored => acc,
  931. ApplicationDeactivationInitiationResult::Unstaking => {
  932. ApplicationsDeactivationsInitiationResult {
  933. number_of_unstaking_applications: 1 + acc
  934. .number_of_unstaking_applications,
  935. number_of_deactivated_applications: acc
  936. .number_of_deactivated_applications,
  937. }
  938. }
  939. ApplicationDeactivationInitiationResult::Deactivated => {
  940. ApplicationsDeactivationsInitiationResult {
  941. number_of_unstaking_applications: acc
  942. .number_of_unstaking_applications,
  943. number_of_deactivated_applications: 1 + acc
  944. .number_of_deactivated_applications,
  945. }
  946. }
  947. }
  948. },
  949. )
  950. }
  951. /// Initiates
  952. fn try_to_initiate_application_deactivation(
  953. application: &Application<T::OpeningId, T::BlockNumber, T::StakeId>,
  954. application_id: T::ApplicationId,
  955. application_stake_unstaking_period: Option<T::BlockNumber>,
  956. role_stake_unstaking_period: Option<T::BlockNumber>,
  957. cause: hiring::ApplicationDeactivationCause,
  958. ) -> ApplicationDeactivationInitiationResult {
  959. match application.stage {
  960. ApplicationStage::Active => {
  961. // Initiate unstaking of any active application stake
  962. let application_was_unstaked = Self::opt_infallible_unstake(
  963. application.active_application_staking_id,
  964. application_stake_unstaking_period,
  965. );
  966. // Only unstake role stake for a non successful result ie. not Hired
  967. let role_was_unstaked = cause != hiring::ApplicationDeactivationCause::Hired
  968. && Self::opt_infallible_unstake(
  969. application.active_role_staking_id,
  970. role_stake_unstaking_period,
  971. );
  972. // Capture if any unstaking occured at all
  973. let was_unstaked = application_was_unstaked || role_was_unstaked;
  974. // Grab current block height
  975. let current_block_height = <frame_system::Module<T>>::block_number();
  976. /*
  977. * TODO:
  978. * There should be a single transformation based on
  979. * was_unstaked which renders a new value for `application.stage`
  980. * and `opening.stage`, which guarantees to only produces new values
  981. * for given variant values, but the state machine types are currently
  982. * not well organised to support this.
  983. *
  984. * Likewise the construction of hiring::OpeningStage::Active below
  985. * is a wreck because of this.
  986. *
  987. * Issue: https://github.com/Joystream/joystream/issues/36#issuecomment-539567407
  988. */
  989. // Figure out new stage for the application
  990. let new_application_stage = if was_unstaked {
  991. ApplicationStage::Unstaking {
  992. deactivation_initiated: current_block_height,
  993. cause,
  994. }
  995. } else {
  996. ApplicationStage::Inactive {
  997. deactivation_initiated: current_block_height,
  998. deactivated: current_block_height,
  999. cause,
  1000. }
  1001. };
  1002. // Update the application stage
  1003. <ApplicationById<T>>::mutate(application_id, |application| {
  1004. application.stage = new_application_stage;
  1005. });
  1006. // Update counters on opening
  1007. <OpeningById<T>>::mutate(application.opening_id, |opening| {
  1008. // NB: This ugly byref destructuring is same issue as pointed out multiple times now.
  1009. if let hiring::OpeningStage::Active {
  1010. ref stage,
  1011. ref applications_added,
  1012. ref active_application_count,
  1013. ref unstaking_application_count,
  1014. ref deactivated_application_count,
  1015. } = opening.stage
  1016. {
  1017. assert!(*active_application_count > 0);
  1018. let new_active_application_count = active_application_count - 1;
  1019. let new_unstaking_application_count =
  1020. unstaking_application_count + if was_unstaked { 1 } else { 0 };
  1021. let new_deactivated_application_count =
  1022. deactivated_application_count + if was_unstaked { 0 } else { 1 };
  1023. opening.stage = hiring::OpeningStage::Active {
  1024. stage: stage.clone(),
  1025. applications_added: applications_added.clone(),
  1026. active_application_count: new_active_application_count,
  1027. unstaking_application_count: new_unstaking_application_count,
  1028. deactivated_application_count: new_deactivated_application_count,
  1029. };
  1030. } else {
  1031. panic!("opening stage must be 'Active'");
  1032. }
  1033. });
  1034. // Call handler(s)
  1035. if was_unstaked {
  1036. T::ApplicationDeactivatedHandler::deactivated(&application_id, cause);
  1037. }
  1038. // Return conclusion
  1039. if was_unstaked {
  1040. ApplicationDeactivationInitiationResult::Unstaking
  1041. } else {
  1042. ApplicationDeactivationInitiationResult::Deactivated
  1043. }
  1044. }
  1045. _ => ApplicationDeactivationInitiationResult::Ignored,
  1046. }
  1047. }
  1048. /// Tries to unstake, based on a stake id which, if set, MUST
  1049. /// be ready to be unstaked, with an optional unstaking period.
  1050. ///
  1051. /// Returns whether unstaking was actually initiated.
  1052. fn opt_infallible_unstake(
  1053. opt_stake_id: Option<T::StakeId>,
  1054. opt_unstaking_period: Option<T::BlockNumber>,
  1055. ) -> bool {
  1056. if let Some(stake_id) = opt_stake_id {
  1057. // `initiate_unstaking` MUST hold, is runtime invariant, false means code is broken.
  1058. // But should we do panic in runtime? Is there safer way?
  1059. assert!(T::StakeHandlerProvider::staking()
  1060. .initiate_unstaking(&stake_id, opt_unstaking_period)
  1061. .is_ok());
  1062. }
  1063. opt_stake_id.is_some()
  1064. }
  1065. }
  1066. // Stake initiation
  1067. impl<T: Trait> Module<T> {
  1068. fn infallible_opt_stake_initiation(
  1069. opt_imbalance: Option<NegativeImbalance<T>>,
  1070. application_id: &T::ApplicationId,
  1071. ) -> Option<T::StakeId> {
  1072. if let Some(imbalance) = opt_imbalance {
  1073. Some(Self::infallible_stake_initiation_on_application(
  1074. imbalance,
  1075. application_id,
  1076. ))
  1077. } else {
  1078. None
  1079. }
  1080. }
  1081. fn infallible_stake_initiation_on_application(
  1082. imbalance: NegativeImbalance<T>,
  1083. application_id: &T::ApplicationId,
  1084. ) -> T::StakeId {
  1085. // Create stake
  1086. let new_stake_id = T::StakeHandlerProvider::staking().create_stake();
  1087. // Keep track of this stake id to process unstaking callbacks that may
  1088. // be invoked later.
  1089. // NB: We purposefully update state to reflect mapping _before_ initiating staking below
  1090. // in order to be safe from race conditions arising out of third party code executing in callback of staking module.
  1091. // MUST never already be a key for new stake, false means code is broken.
  1092. // But should we do panic in runtime? Is there safer way?
  1093. assert!(!<ApplicationIdByStakingId<T>>::contains_key(new_stake_id));
  1094. <ApplicationIdByStakingId<T>>::insert(new_stake_id, application_id);
  1095. // Initiate staking
  1096. //
  1097. // MUST work, is runtime invariant, false means code is broken.
  1098. // But should we do panic in runtime? Is there safer way?
  1099. assert_eq!(
  1100. T::StakeHandlerProvider::staking().stake(&new_stake_id, imbalance),
  1101. Ok(())
  1102. );
  1103. new_stake_id
  1104. }
  1105. }
  1106. // Conditions for adding application
  1107. impl<T: Trait> Module<T> {
  1108. /// Evaluates prospects for a new application
  1109. ///
  1110. pub(crate) fn would_application_get_added(
  1111. possible_opening_application_rationing_policy: &Option<ApplicationRationingPolicy>,
  1112. opening_applicants: &BTreeSet<T::ApplicationId>,
  1113. opt_role_stake_balance: &Option<BalanceOf<T>>,
  1114. opt_application_stake_balance: &Option<BalanceOf<T>>,
  1115. ) -> ApplicationWouldGetAddedEvaluation<T> {
  1116. // Check whether any rationing policy is set at all, if not
  1117. // then there is no rationing, and any application can get added.
  1118. let application_rationing_policy = if let Some(application_rationing_policy) =
  1119. possible_opening_application_rationing_policy
  1120. {
  1121. application_rationing_policy
  1122. } else {
  1123. return ApplicationWouldGetAddedEvaluation::Yes(
  1124. ApplicationAddedSuccess::Unconditionally,
  1125. );
  1126. };
  1127. // Map with applications
  1128. let applications_map = Self::application_id_iter_to_map(opening_applicants.iter());
  1129. let active_applications_with_stake_iter =
  1130. applications_map
  1131. .iter()
  1132. .filter_map(|(application_id, application)| {
  1133. if application.stage == hiring::ApplicationStage::Active {
  1134. let total_stake =
  1135. Self::get_opt_stake_amount(application.active_role_staking_id)
  1136. + Self::get_opt_stake_amount(
  1137. application.active_application_staking_id,
  1138. );
  1139. Some((application_id, application, total_stake))
  1140. } else {
  1141. None
  1142. }
  1143. });
  1144. // Compute number of active applications
  1145. let number_of_active_applications = active_applications_with_stake_iter.clone().count();
  1146. // Check whether the current number of _active_ applicants is either at or above the maximum
  1147. // limit, if not, then we can add at least one additional application,
  1148. // otherwise we must evaluate whether this new application would specifically get added.
  1149. if (number_of_active_applications as u32)
  1150. < application_rationing_policy.max_active_applicants
  1151. {
  1152. return ApplicationWouldGetAddedEvaluation::Yes(
  1153. ApplicationAddedSuccess::Unconditionally,
  1154. );
  1155. }
  1156. // Here we try to figure out if the new application
  1157. // has sufficient stake to crowd out one of the already
  1158. // active applicants.
  1159. // The total stake of new application
  1160. let total_stake_of_new_application = opt_role_stake_balance.unwrap_or_default()
  1161. + opt_application_stake_balance.unwrap_or_default();
  1162. // The total stake of all current active applications
  1163. let opt_min_item = active_applications_with_stake_iter
  1164. .clone()
  1165. .min_by_key(|(_, _, total_stake)| *total_stake);
  1166. if let Some((application_id, _, lowest_active_total_stake)) = opt_min_item {
  1167. // Finally we compare the two and come up with a final evaluation
  1168. if total_stake_of_new_application <= lowest_active_total_stake {
  1169. ApplicationWouldGetAddedEvaluation::No // stake too low!
  1170. } else {
  1171. ApplicationWouldGetAddedEvaluation::Yes(
  1172. ApplicationAddedSuccess::CrowdsOutExistingApplication(*application_id),
  1173. )
  1174. }
  1175. } else {
  1176. panic!("`number_of_active_applications` (length of `active_applications_iter`) == 0")
  1177. }
  1178. }
  1179. fn get_opt_stake_amount(stake_id: Option<T::StakeId>) -> BalanceOf<T> {
  1180. stake_id.map_or(<BalanceOf<T> as Zero>::zero(), |stake_id| {
  1181. // INVARIANT: stake MUST exist in the staking module
  1182. assert!(T::StakeHandlerProvider::staking().stake_exists(stake_id));
  1183. let stake = T::StakeHandlerProvider::staking().get_stake(stake_id);
  1184. match stake.staking_status {
  1185. // INVARIANT: stake MUST be in the staked state.
  1186. stake::StakingStatus::Staked(staked_state) => staked_state.staked_amount,
  1187. _ => panic!("stake MUST be in the staked state."),
  1188. }
  1189. })
  1190. }
  1191. pub(crate) fn create_stake_balance(
  1192. opt_stake_imbalance: &Option<NegativeImbalance<T>>,
  1193. ) -> Option<BalanceOf<T>> {
  1194. if let Some(ref imbalance) = opt_stake_imbalance {
  1195. Some(imbalance.peek())
  1196. } else {
  1197. None
  1198. }
  1199. }
  1200. /// Performs all necessary check before adding an opening
  1201. pub(crate) fn ensure_can_add_opening(
  1202. current_block_height: T::BlockNumber,
  1203. activate_at: ActivateOpeningAt<T::BlockNumber>,
  1204. minimum_stake_balance: BalanceOf<T>,
  1205. application_rationing_policy: Option<ApplicationRationingPolicy>,
  1206. application_staking_policy: Option<StakingPolicy<BalanceOf<T>, T::BlockNumber>>,
  1207. role_staking_policy: Option<StakingPolicy<BalanceOf<T>, T::BlockNumber>>,
  1208. ) -> Result<(), AddOpeningError> {
  1209. // Check that exact activation is actually in the future
  1210. ensure!(
  1211. match activate_at {
  1212. ActivateOpeningAt::ExactBlock(block_number) => block_number > current_block_height,
  1213. _ => true,
  1214. },
  1215. AddOpeningError::OpeningMustActivateInTheFuture
  1216. );
  1217. if let Some(app_rationing_policy) = application_rationing_policy {
  1218. ensure!(
  1219. app_rationing_policy.max_active_applicants > 0,
  1220. AddOpeningError::ApplicationRationingZeroMaxApplicants
  1221. );
  1222. }
  1223. // Check that staking amounts clear minimum balance required.
  1224. Self::ensure_amount_valid_in_opt_staking_policy(
  1225. application_staking_policy,
  1226. minimum_stake_balance,
  1227. StakePurpose::Application,
  1228. )?;
  1229. // Check that staking amounts clear minimum balance required.
  1230. Self::ensure_amount_valid_in_opt_staking_policy(
  1231. role_staking_policy,
  1232. minimum_stake_balance,
  1233. StakePurpose::Role,
  1234. )?;
  1235. Ok(())
  1236. }
  1237. /// Ensures that optional staking policy prescribes value that clears minimum balance requirement
  1238. pub(crate) fn ensure_amount_valid_in_opt_staking_policy(
  1239. opt_staking_policy: Option<StakingPolicy<BalanceOf<T>, T::BlockNumber>>,
  1240. minimum_stake_balance: BalanceOf<T>,
  1241. stake_purpose: StakePurpose,
  1242. ) -> Result<(), AddOpeningError> {
  1243. if let Some(ref staking_policy) = opt_staking_policy {
  1244. ensure!(
  1245. staking_policy.amount > Zero::zero(),
  1246. AddOpeningError::StakeAmountCannotBeZero(stake_purpose)
  1247. );
  1248. ensure!(
  1249. staking_policy.amount >= minimum_stake_balance,
  1250. AddOpeningError::StakeAmountLessThanMinimumStakeBalance(stake_purpose)
  1251. );
  1252. }
  1253. Ok(())
  1254. }
  1255. }
  1256. /*
  1257. * === Stake module wrappers ======
  1258. */
  1259. /// Defines stake module interface
  1260. #[cfg_attr(all(test, not(target_arch = "wasm32")), automock)]
  1261. pub trait StakeHandler<T: StakeTrait> {
  1262. /// Adds a new Stake which is NotStaked, created at given block, into stakes map.
  1263. fn create_stake(&self) -> T::StakeId;
  1264. /// to the module's account, and the corresponding staked_balance is set to this amount in the new Staked state.
  1265. /// On error, as the negative imbalance is not returned to the caller, it is the caller's responsibility to return the funds
  1266. /// back to the source (by creating a new positive imbalance)
  1267. fn stake(
  1268. &self,
  1269. new_stake_id: &T::StakeId,
  1270. imbalance: NegativeImbalance<T>,
  1271. ) -> Result<(), StakeActionError<stake::StakingError>>;
  1272. /// Checks whether stake exists by its id
  1273. fn stake_exists(&self, stake_id: T::StakeId) -> bool;
  1274. /// Acquires stake by id
  1275. fn get_stake(&self, stake_id: T::StakeId) -> Stake<T::BlockNumber, BalanceOf<T>, T::SlashId>;
  1276. /// Initiate unstaking of a Staked stake.
  1277. fn initiate_unstaking(
  1278. &self,
  1279. stake_id: &T::StakeId,
  1280. unstaking_period: Option<T::BlockNumber>,
  1281. ) -> Result<(), StakeActionError<InitiateUnstakingError>>;
  1282. }
  1283. /// Allows to provide different StakeHandler implementation. Useful for mocks.
  1284. pub trait StakeHandlerProvider<T: Trait> {
  1285. /// Returns StakeHandler. Mock entry point for stake module.
  1286. fn staking() -> Rc<RefCell<dyn StakeHandler<T>>>;
  1287. }
  1288. impl<T: Trait> StakeHandlerProvider<T> for Module<T> {
  1289. /// Returns StakeHandler. Mock entry point for stake module.
  1290. fn staking() -> Rc<RefCell<dyn StakeHandler<T>>> {
  1291. Rc::new(RefCell::new(HiringStakeHandler {}))
  1292. }
  1293. }
  1294. /// Default stake module logic implementation
  1295. pub struct HiringStakeHandler;
  1296. impl<T: Trait> StakeHandler<T> for HiringStakeHandler {
  1297. fn create_stake(&self) -> T::StakeId {
  1298. <stake::Module<T>>::create_stake()
  1299. }
  1300. fn stake(
  1301. &self,
  1302. new_stake_id: &T::StakeId,
  1303. imbalance: NegativeImbalance<T>,
  1304. ) -> Result<(), StakeActionError<StakingError>> {
  1305. <stake::Module<T>>::stake(new_stake_id, imbalance)
  1306. }
  1307. fn stake_exists(&self, stake_id: T::StakeId) -> bool {
  1308. <stake::Stakes<T>>::contains_key(stake_id)
  1309. }
  1310. fn get_stake(&self, stake_id: T::StakeId) -> Stake<T::BlockNumber, BalanceOf<T>, T::SlashId> {
  1311. <stake::Stakes<T>>::get(stake_id)
  1312. }
  1313. fn initiate_unstaking(
  1314. &self,
  1315. stake_id: &T::StakeId,
  1316. unstaking_period: Option<T::BlockNumber>,
  1317. ) -> Result<(), StakeActionError<InitiateUnstakingError>> {
  1318. <stake::Module<T>>::initiate_unstaking(&stake_id, unstaking_period)
  1319. }
  1320. }
  1321. // Proxy implementation of StakeHandler trait to simplify calls via staking() method
  1322. // Allows to get rid of borrow() calls,
  1323. // eg.: T::StakeHandlerProvider::staking().get_stake(stake_id);
  1324. // instead of T::StakeHandlerProvider::staking().borrow().get_stake(stake_id);
  1325. impl<T: Trait> StakeHandler<T> for Rc<RefCell<dyn StakeHandler<T>>> {
  1326. fn create_stake(&self) -> T::StakeId {
  1327. self.borrow().create_stake()
  1328. }
  1329. fn stake(
  1330. &self,
  1331. new_stake_id: &T::StakeId,
  1332. imbalance: NegativeImbalance<T>,
  1333. ) -> Result<(), StakeActionError<StakingError>> {
  1334. self.borrow().stake(new_stake_id, imbalance)
  1335. }
  1336. fn stake_exists(&self, stake_id: T::StakeId) -> bool {
  1337. self.borrow().stake_exists(stake_id)
  1338. }
  1339. fn get_stake(&self, stake_id: T::StakeId) -> Stake<T::BlockNumber, BalanceOf<T>, T::SlashId> {
  1340. self.borrow().get_stake(stake_id)
  1341. }
  1342. fn initiate_unstaking(
  1343. &self,
  1344. stake_id: &T::StakeId,
  1345. unstaking_period: Option<T::BlockNumber>,
  1346. ) -> Result<(), StakeActionError<InitiateUnstakingError>> {
  1347. self.borrow().initiate_unstaking(stake_id, unstaking_period)
  1348. }
  1349. }