lib.rs 62 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618
  1. //! # Working group module
  2. //! Working group module for the Joystream platform. Version 1.
  3. //! Contains abstract working group workflow.
  4. //!
  5. //! ## Overview
  6. //!
  7. //! The working group module provides working group workflow to use in different modules.
  8. //! It contains extrinsics for the hiring workers, their roles lifecycle and stake management.
  9. //! There is a possibility to hire a special worker - the leader of the working group.
  10. //! Some module operations like 'increase_stake' can be invoked by the worker, others
  11. //! like 'terminate_role' can be invoked by the leader only. The leader himself can be hired and
  12. //! managed only by the council via proposals.
  13. //!
  14. //! Exact working group (eg.: forum working group) should create an instance of the Working group module.
  15. //!
  16. //! ## Supported extrinsics
  17. //! ### Hiring flow
  18. //!
  19. //! - [add_opening](./struct.Module.html#method.add_opening) - Add an opening for a worker/lead role.
  20. //! - [accept_applications](./struct.Module.html#method.accept_applications)- Begin accepting worker/lead applications.
  21. //! - [begin_applicant_review](./struct.Module.html#method.begin_applicant_review) - Begin reviewing worker/lead applications.
  22. //! - [fill_opening](./struct.Module.html#method.fill_opening) - Fill opening for worker/lead.
  23. //! - [withdraw_application](./struct.Module.html#method.withdraw_application) - Withdraw the worker/lead application.
  24. //! - [terminate_application](./struct.Module.html#method.terminate_application) - Terminate the worker/lead application.
  25. //! - [apply_on_opening](./struct.Module.html#method.apply_on_opening) - Apply on a worker/lead opening.
  26. //!
  27. //! ### Roles lifecycle
  28. //!
  29. //! - [update_role_account](./struct.Module.html#method.update_role_account) - Update the role account of the worker/lead.
  30. //! - [update_reward_account](./struct.Module.html#method.update_reward_account) - Update the reward account of the worker/lead.
  31. //! - [update_reward_amount](./struct.Module.html#method.update_reward_amount) - Update the reward amount of the worker/lead.
  32. //! - [leave_role](./struct.Module.html#method.leave_role) - Leave the role by the active worker/lead.
  33. //! - [terminate_role](./struct.Module.html#method.terminate_role) - Terminate the worker/lead role.
  34. //! - [set_mint_capacity](./struct.Module.html#method.set_mint_capacity) - Sets the capacity to enable working group budget.
  35. //!
  36. //! ### Stakes
  37. //!
  38. //! - [slash_stake](./struct.Module.html#method.slash_stake) - Slashes the worker/lead stake.
  39. //! - [decrease_stake](./struct.Module.html#method.decrease_stake) - Decreases the worker/lead stake and returns the remainder to the worker _role_account_.
  40. //! - [increase_stake](./struct.Module.html#method.increase_stake) - Increases the worker/lead stake.
  41. //!
  42. // Ensure we're `no_std` when compiling for Wasm.
  43. #![cfg_attr(not(feature = "std"), no_std)]
  44. // Do not delete! Cannot be uncommented by default, because of Parity decl_module! issue.
  45. //#![warn(missing_docs)]
  46. #[cfg(test)]
  47. mod tests;
  48. mod types;
  49. #[macro_use]
  50. mod errors;
  51. use frame_support::dispatch::{DispatchError, DispatchResult};
  52. use frame_support::storage::IterableStorageMap;
  53. use frame_support::traits::{Currency, ExistenceRequirement, Get, Imbalance, WithdrawReasons};
  54. use frame_support::{decl_event, decl_module, decl_storage, ensure, print, StorageValue};
  55. use sp_arithmetic::traits::{Bounded, One, Zero};
  56. use sp_std::collections::{btree_map::BTreeMap, btree_set::BTreeSet};
  57. use sp_std::vec;
  58. use sp_std::vec::Vec;
  59. use system::{ensure_root, ensure_signed};
  60. use crate::types::ExitInitiationOrigin;
  61. use common::constraints::InputValidationLengthConstraint;
  62. use errors::WrappedError;
  63. pub use errors::Error;
  64. pub use types::{
  65. Application, Opening, OpeningPolicyCommitment, OpeningType, RewardPolicy, RoleStakeProfile,
  66. Worker,
  67. };
  68. /// Stake identifier in staking module
  69. pub type StakeId<T> = <T as stake::Trait>::StakeId;
  70. /// Member identifier in membership::member module
  71. pub type MemberId<T> = <T as membership::Trait>::MemberId;
  72. /// Workaround for BTreeSet type
  73. pub type ApplicationIdSet<T> = BTreeSet<ApplicationId<T>>;
  74. /// Type for the identifier for an opening for a worker/lead.
  75. pub type OpeningId<T> = <T as hiring::Trait>::OpeningId;
  76. /// Type for the identifier for an application as a worker/lead.
  77. pub type ApplicationId<T> = <T as hiring::Trait>::ApplicationId;
  78. /// Balance type of runtime
  79. pub type BalanceOf<T> =
  80. <<T as stake::Trait>::Currency as Currency<<T as system::Trait>::AccountId>>::Balance;
  81. /// Balance type of runtime reward
  82. pub type BalanceOfMint<T> =
  83. <<T as minting::Trait>::Currency as Currency<<T as system::Trait>::AccountId>>::Balance;
  84. /// Balance type of runtime
  85. pub type CurrencyOf<T> = <T as stake::Trait>::Currency;
  86. /// Negative imbalance of runtime.
  87. pub type NegativeImbalance<T> =
  88. <<T as stake::Trait>::Currency as Currency<<T as system::Trait>::AccountId>>::NegativeImbalance;
  89. /// Alias for the worker application id to the worker id dictionary
  90. pub type ApplicationIdToWorkerIdMap<T> = BTreeMap<ApplicationId<T>, WorkerId<T>>;
  91. /// Type identifier for worker role, which must be same as membership actor identifier
  92. pub type WorkerId<T> = <T as membership::Trait>::ActorId;
  93. /// Alias for the application id from the hiring module.
  94. pub type HiringApplicationId<T> = <T as hiring::Trait>::ApplicationId;
  95. // Type simplification
  96. type OpeningInfo<T> = (
  97. OpeningOf<T>,
  98. hiring::Opening<BalanceOf<T>, <T as system::Trait>::BlockNumber, HiringApplicationId<T>>,
  99. );
  100. // Type simplification
  101. type ApplicationInfo<T> = (ApplicationOf<T>, ApplicationId<T>, OpeningOf<T>);
  102. // Type simplification
  103. type RewardSettings<T> = (
  104. <T as minting::Trait>::MintId,
  105. RewardPolicy<BalanceOfMint<T>, <T as system::Trait>::BlockNumber>,
  106. );
  107. // Type simplification
  108. type WorkerOf<T> = Worker<
  109. <T as system::Trait>::AccountId,
  110. <T as recurringrewards::Trait>::RewardRelationshipId,
  111. <T as stake::Trait>::StakeId,
  112. <T as system::Trait>::BlockNumber,
  113. MemberId<T>,
  114. >;
  115. // Type simplification
  116. type OpeningOf<T> = Opening<
  117. <T as hiring::Trait>::OpeningId,
  118. <T as system::Trait>::BlockNumber,
  119. BalanceOf<T>,
  120. ApplicationId<T>,
  121. >;
  122. // Type simplification
  123. type ApplicationOf<T> =
  124. Application<<T as system::Trait>::AccountId, OpeningId<T>, MemberId<T>, HiringApplicationId<T>>;
  125. /// The _Working group_ main _Trait_
  126. pub trait Trait<I: Instance>:
  127. system::Trait
  128. + membership::Trait
  129. + hiring::Trait
  130. + minting::Trait
  131. + stake::Trait
  132. + recurringrewards::Trait
  133. {
  134. /// _Working group_ event type.
  135. type Event: From<Event<Self, I>> + Into<<Self as system::Trait>::Event>;
  136. /// Defines max workers number in the working group.
  137. type MaxWorkerNumberLimit: Get<u32>;
  138. }
  139. decl_event!(
  140. /// _Working group_ events
  141. pub enum Event<T, I>
  142. where
  143. WorkerId = WorkerId<T>,
  144. <T as system::Trait>::AccountId,
  145. OpeningId = OpeningId<T>,
  146. ApplicationId = ApplicationId<T>,
  147. ApplicationIdToWorkerIdMap = ApplicationIdToWorkerIdMap<T>,
  148. RationaleText = Vec<u8>,
  149. MintBalanceOf = minting::BalanceOf<T>,
  150. <T as minting::Trait>::MintId,
  151. {
  152. /// Emits on setting the leader.
  153. /// Params:
  154. /// - Worker id.
  155. LeaderSet(WorkerId),
  156. /// Emits on un-setting the leader.
  157. /// Params:
  158. LeaderUnset(),
  159. /// Emits on terminating the worker.
  160. /// Params:
  161. /// - worker id.
  162. /// - termination rationale text
  163. TerminatedWorker(WorkerId, RationaleText),
  164. /// Emits on terminating the leader.
  165. /// Params:
  166. /// - leader worker id.
  167. /// - termination rationale text
  168. TerminatedLeader(WorkerId, RationaleText),
  169. /// Emits on exiting the worker.
  170. /// Params:
  171. /// - worker id.
  172. /// - exit rationale text
  173. WorkerExited(WorkerId, RationaleText),
  174. /// Emits on updating the role account of the worker.
  175. /// Params:
  176. /// - Id of the worker.
  177. /// - Role account id of the worker.
  178. WorkerRoleAccountUpdated(WorkerId, AccountId),
  179. /// Emits on updating the reward account of the worker.
  180. /// Params:
  181. /// - Member id of the worker.
  182. /// - Reward account id of the worker.
  183. WorkerRewardAccountUpdated(WorkerId, AccountId),
  184. /// Emits on updating the reward amount of the worker.
  185. /// Params:
  186. /// - Id of the worker.
  187. WorkerRewardAmountUpdated(WorkerId),
  188. /// Emits on adding new worker opening.
  189. /// Params:
  190. /// - Opening id
  191. OpeningAdded(OpeningId),
  192. /// Emits on accepting application for the worker opening.
  193. /// Params:
  194. /// - Opening id
  195. AcceptedApplications(OpeningId),
  196. /// Emits on adding the application for the worker opening.
  197. /// Params:
  198. /// - Opening id
  199. /// - Application id
  200. AppliedOnOpening(OpeningId, ApplicationId),
  201. /// Emits on withdrawing the application for the worker/lead opening.
  202. /// Params:
  203. /// - Worker application id
  204. ApplicationWithdrawn(ApplicationId),
  205. /// Emits on terminating the application for the worker/lead opening.
  206. /// Params:
  207. /// - Worker application id
  208. ApplicationTerminated(ApplicationId),
  209. /// Emits on beginning the application review for the worker/lead opening.
  210. /// Params:
  211. /// - Opening id
  212. BeganApplicationReview(OpeningId),
  213. /// Emits on filling the worker opening.
  214. /// Params:
  215. /// - Worker opening id
  216. /// - Worker application id to the worker id dictionary
  217. OpeningFilled(OpeningId, ApplicationIdToWorkerIdMap),
  218. /// Emits on increasing the worker/lead stake.
  219. /// Params:
  220. /// - worker/lead id.
  221. StakeIncreased(WorkerId),
  222. /// Emits on decreasing the worker/lead stake.
  223. /// Params:
  224. /// - worker/lead id.
  225. StakeDecreased(WorkerId),
  226. /// Emits on slashing the worker/lead stake.
  227. /// Params:
  228. /// - worker/lead id.
  229. StakeSlashed(WorkerId),
  230. /// Emits on changing working group mint capacity.
  231. /// Params:
  232. /// - mint id.
  233. /// - new mint balance.
  234. MintCapacityChanged(MintId, MintBalanceOf),
  235. }
  236. );
  237. decl_storage! {
  238. trait Store for Module<T: Trait<I>, I: Instance> as WorkingGroup {
  239. /// The mint currently funding the rewards for this module.
  240. pub Mint get(fn mint) : <T as minting::Trait>::MintId;
  241. /// The current lead.
  242. pub CurrentLead get(fn current_lead) : Option<WorkerId<T>>;
  243. /// Next identifier value for new worker opening.
  244. pub NextOpeningId get(fn next_opening_id): OpeningId<T>;
  245. /// Maps identifier to worker opening.
  246. pub OpeningById get(fn opening_by_id): map hasher(blake2_128_concat)
  247. OpeningId<T> => OpeningOf<T>;
  248. /// Opening human readable text length limits
  249. pub OpeningHumanReadableText get(fn opening_human_readable_text): InputValidationLengthConstraint;
  250. /// Maps identifier to worker application on opening.
  251. pub ApplicationById get(fn application_by_id) : map hasher(blake2_128_concat)
  252. ApplicationId<T> => ApplicationOf<T>;
  253. /// Next identifier value for new worker application.
  254. pub NextApplicationId get(fn next_application_id) : ApplicationId<T>;
  255. /// Worker application human readable text length limits
  256. pub WorkerApplicationHumanReadableText get(fn application_human_readable_text) : InputValidationLengthConstraint;
  257. /// Maps identifier to corresponding worker.
  258. pub WorkerById get(fn worker_by_id) : map hasher(blake2_128_concat)
  259. WorkerId<T> => WorkerOf<T>;
  260. /// Count of active workers.
  261. pub ActiveWorkerCount get(fn active_worker_count): u32;
  262. /// Next identifier for new worker.
  263. pub NextWorkerId get(fn next_worker_id) : WorkerId<T>;
  264. /// Worker exit rationale text length limits.
  265. pub WorkerExitRationaleText get(fn worker_exit_rationale_text) : InputValidationLengthConstraint;
  266. /// Map member id by hiring application id.
  267. /// Required by StakingEventsHandler callback call to refund the balance on unstaking.
  268. pub MemberIdByHiringApplicationId get(fn member_id_by_hiring_application_id):
  269. map hasher(blake2_128_concat) HiringApplicationId<T> => MemberId<T>;
  270. }
  271. add_extra_genesis {
  272. config(phantom): sp_std::marker::PhantomData<I>;
  273. config(storage_working_group_mint_capacity): minting::BalanceOf<T>;
  274. config(opening_human_readable_text_constraint): InputValidationLengthConstraint;
  275. config(worker_application_human_readable_text_constraint): InputValidationLengthConstraint;
  276. config(worker_exit_rationale_text_constraint): InputValidationLengthConstraint;
  277. build(|config: &GenesisConfig<T, I>| {
  278. Module::<T, I>::initialize_working_group(
  279. config.opening_human_readable_text_constraint,
  280. config.worker_application_human_readable_text_constraint,
  281. config.worker_exit_rationale_text_constraint,
  282. config.storage_working_group_mint_capacity)
  283. });
  284. }
  285. }
  286. decl_module! {
  287. /// _Working group_ substrate module.
  288. pub struct Module<T: Trait<I>, I: Instance> for enum Call where origin: T::Origin {
  289. /// Default deposit_event() handler
  290. fn deposit_event() = default;
  291. /// Predefined errors
  292. type Error = Error<T, I>;
  293. /// Exports const - max simultaneous active worker number.
  294. const MaxWorkerNumberLimit: u32 = T::MaxWorkerNumberLimit::get();
  295. // ****************** Roles lifecycle **********************
  296. /// Update the associated role account of the active worker/lead.
  297. #[weight = 10_000_000] // TODO: adjust weight
  298. pub fn update_role_account(
  299. origin,
  300. worker_id: WorkerId<T>,
  301. new_role_account_id: T::AccountId
  302. ) {
  303. // Ensuring worker actually exists
  304. let worker = Self::ensure_worker_exists(&worker_id)?;
  305. // Ensure that origin is signed by member with given id.
  306. ensure_on_wrapped_error!(
  307. membership::Module::<T>::ensure_member_controller_account_signed(origin, &worker.member_id)
  308. )?;
  309. //
  310. // == MUTATION SAFE ==
  311. //
  312. // Update role account
  313. WorkerById::<T, I>::mutate(worker_id, |worker| {
  314. worker.role_account_id = new_role_account_id.clone()
  315. });
  316. // Trigger event
  317. Self::deposit_event(RawEvent::WorkerRoleAccountUpdated(worker_id, new_role_account_id));
  318. }
  319. /// Update the reward account associated with a set reward relationship for the active worker.
  320. #[weight = 10_000_000] // TODO: adjust weight
  321. pub fn update_reward_account(
  322. origin,
  323. worker_id: WorkerId<T>,
  324. new_reward_account_id: T::AccountId
  325. ) {
  326. // Ensure there is a signer which matches role account of worker corresponding to provided id.
  327. let worker = Self::ensure_worker_signed(origin, &worker_id)?;
  328. // Ensure the worker actually has a recurring reward
  329. let relationship_id = Self::ensure_worker_has_recurring_reward(&worker)?;
  330. //
  331. // == MUTATION SAFE ==
  332. //
  333. // Update only the reward account.
  334. ensure_on_wrapped_error!(
  335. recurringrewards::Module::<T>::set_reward_relationship(
  336. relationship_id,
  337. Some(new_reward_account_id.clone()), // new_account
  338. None, // new_payout
  339. None, //new_next_payment_at
  340. None) //new_payout_interval
  341. )?;
  342. // Trigger event
  343. Self::deposit_event(RawEvent::WorkerRewardAccountUpdated(worker_id, new_reward_account_id));
  344. }
  345. /// Update the reward amount associated with a set reward relationship for the active worker.
  346. /// Require signed leader origin or the root (to update leader reward amount).
  347. #[weight = 10_000_000] // TODO: adjust weight
  348. pub fn update_reward_amount(
  349. origin,
  350. worker_id: WorkerId<T>,
  351. new_amount: BalanceOfMint<T>
  352. ) {
  353. // Ensure lead is set and is origin signer or it is the council.
  354. Self::ensure_origin_for_leader(origin, worker_id)?;
  355. // Ensuring worker actually exists
  356. let worker = Self::ensure_worker_exists(&worker_id)?;
  357. // Ensure the worker actually has a recurring reward
  358. let relationship_id = Self::ensure_worker_has_recurring_reward(&worker)?;
  359. //
  360. // == MUTATION SAFE ==
  361. //
  362. // Update only the reward account.
  363. ensure_on_wrapped_error!(
  364. recurringrewards::Module::<T>::set_reward_relationship(
  365. relationship_id,
  366. None, // new_account
  367. Some(new_amount), // new_payout
  368. None, //new_next_payment_at
  369. None) //new_payout_interval
  370. )?;
  371. // Trigger event
  372. Self::deposit_event(RawEvent::WorkerRewardAmountUpdated(worker_id));
  373. }
  374. /// Leave the role by the active worker.
  375. #[weight = 10_000_000] // TODO: adjust weight
  376. pub fn leave_role(
  377. origin,
  378. worker_id: WorkerId<T>,
  379. rationale_text: Vec<u8>
  380. ) {
  381. // Ensure there is a signer which matches role account of worker corresponding to provided id.
  382. let active_worker = Self::ensure_worker_signed(origin, &worker_id)?;
  383. //
  384. // == MUTATION SAFE ==
  385. //
  386. Self::deactivate_worker(
  387. &worker_id,
  388. &active_worker,
  389. &ExitInitiationOrigin::Worker,
  390. &rationale_text
  391. )?;
  392. }
  393. /// Terminate the active worker by the lead.
  394. /// Require signed leader origin or the root (to terminate the leader role).
  395. #[weight = 10_000_000] // TODO: adjust weight
  396. pub fn terminate_role(
  397. origin,
  398. worker_id: WorkerId<T>,
  399. rationale_text: Vec<u8>,
  400. slash_stake: bool,
  401. ) {
  402. let (cloned_origin1, cloned_origin2) = common::origin::double_origin::<T>(origin);
  403. // Ensure lead is set or it is the council terminating the leader.
  404. let exit_origin = Self::ensure_origin_for_leader(cloned_origin1, worker_id)?;
  405. // Ensuring worker actually exists.
  406. let worker = Self::ensure_worker_exists(&worker_id)?;
  407. // Ensure rationale text is valid.
  408. Self::ensure_worker_exit_rationale_text_is_valid(&rationale_text)?;
  409. //
  410. // == MUTATION SAFE ==
  411. //
  412. if slash_stake {
  413. Self::slash_stake(cloned_origin2, worker_id, BalanceOf::<T>::max_value())?;
  414. }
  415. Self::deactivate_worker(
  416. &worker_id,
  417. &worker,
  418. &exit_origin,
  419. &rationale_text
  420. )?;
  421. }
  422. // ****************** Hiring flow **********************
  423. /// Add an opening for a worker role.
  424. /// Require signed leader origin or the root (to add opening for the leader position).
  425. #[weight = 10_000_000] // TODO: adjust weight
  426. pub fn add_opening(
  427. origin,
  428. activate_at: hiring::ActivateOpeningAt<T::BlockNumber>,
  429. commitment: OpeningPolicyCommitment<T::BlockNumber, BalanceOf<T>>,
  430. human_readable_text: Vec<u8>,
  431. opening_type: OpeningType,
  432. ){
  433. Self::ensure_origin_for_opening_type(origin, opening_type)?;
  434. Self::ensure_opening_human_readable_text_is_valid(&human_readable_text)?;
  435. Self::ensure_opening_policy_commitment_is_valid(&commitment)?;
  436. // Add opening
  437. // NB: This call can in principle fail, because the staking policies
  438. // may not respect the minimum currency requirement.
  439. let policy_commitment = commitment.clone();
  440. //
  441. // == MUTATION SAFE ==
  442. //
  443. let opening_id = ensure_on_wrapped_error!(
  444. hiring::Module::<T>::add_opening(
  445. activate_at,
  446. commitment.max_review_period_length,
  447. commitment.application_rationing_policy,
  448. commitment.application_staking_policy,
  449. commitment.role_staking_policy,
  450. human_readable_text,
  451. ))?;
  452. let new_opening_id = NextOpeningId::<T, I>::get();
  453. // Create and add worker opening.
  454. let new_opening_by_id = Opening::<OpeningId<T>, T::BlockNumber, BalanceOf<T>, ApplicationId<T>> {
  455. hiring_opening_id: opening_id,
  456. applications: BTreeSet::new(),
  457. policy_commitment,
  458. opening_type,
  459. };
  460. OpeningById::<T, I>::insert(new_opening_id, new_opening_by_id);
  461. // Update NextOpeningId
  462. NextOpeningId::<T, I>::mutate(|id| *id += <OpeningId<T> as One>::one());
  463. // Trigger event
  464. Self::deposit_event(RawEvent::OpeningAdded(new_opening_id));
  465. }
  466. /// Begin accepting worker applications to an opening that is active.
  467. /// Require signed leader origin or the root (to accept applications for the leader position).
  468. #[weight = 10_000_000] // TODO: adjust weight
  469. pub fn accept_applications(origin, opening_id: OpeningId<T>) {
  470. // Ensure opening exists in this working group
  471. // NB: Even though call to hiring module will have implicit check for
  472. // existence of opening as well, this check is to make sure that the opening is for
  473. // this working group, not something else.
  474. let (opening, _opening) = Self::ensure_opening_exists(&opening_id)?;
  475. Self::ensure_origin_for_opening_type(origin, opening.opening_type)?;
  476. // Attempt to begin accepting applications
  477. // NB: Combined ensure check and mutation in hiring module
  478. //
  479. // == MUTATION SAFE ==
  480. //
  481. ensure_on_wrapped_error!(
  482. hiring::Module::<T>::begin_accepting_applications(opening.hiring_opening_id)
  483. )?;
  484. // Trigger event
  485. Self::deposit_event(RawEvent::AcceptedApplications(opening_id));
  486. }
  487. /// Apply on a worker opening.
  488. #[weight = 10_000_000] // TODO: adjust weight
  489. pub fn apply_on_opening(
  490. origin,
  491. member_id: T::MemberId,
  492. opening_id: OpeningId<T>,
  493. role_account_id: T::AccountId,
  494. opt_role_stake_balance: Option<BalanceOf<T>>,
  495. opt_application_stake_balance: Option<BalanceOf<T>>,
  496. human_readable_text: Vec<u8>
  497. ) {
  498. // Ensure origin which will server as the source account for staked funds is signed
  499. let source_account = ensure_signed(origin)?;
  500. // In absence of a more general key delegation system which allows an account with some funds to
  501. // grant another account permission to stake from its funds, the origin of this call must have the funds
  502. // and cannot specify another arbitrary account as the source account.
  503. // Ensure the source_account is either the controller or root account of member with given id
  504. ensure!(
  505. membership::Module::<T>::ensure_member_controller_account(&source_account, &member_id).is_ok() ||
  506. membership::Module::<T>::ensure_member_root_account(&source_account, &member_id).is_ok(),
  507. Error::<T, I>::OriginIsNeitherMemberControllerOrRoot
  508. );
  509. // Ensure worker opening exists
  510. let (opening, _opening) = Self::ensure_opening_exists(&opening_id)?;
  511. // Ensure that there is sufficient balance to cover stake proposed
  512. Self::ensure_can_make_stake_imbalance(
  513. vec![&opt_role_stake_balance, &opt_application_stake_balance],
  514. &source_account
  515. )
  516. .map_err(|_| Error::<T, I>::InsufficientBalanceToApply)?;
  517. // Ensure application text is valid
  518. Self::ensure_application_text_is_valid(&human_readable_text)?;
  519. // Ensure application can actually be added
  520. ensure_on_wrapped_error!(
  521. hiring::Module::<T>::ensure_can_add_application(
  522. opening.hiring_opening_id,
  523. opt_role_stake_balance,
  524. opt_application_stake_balance)
  525. )?;
  526. // Ensure member does not have an active application to this opening
  527. Self::ensure_member_has_no_active_application_on_opening(
  528. opening.applications,
  529. member_id
  530. )?;
  531. //
  532. // == MUTATION SAFE ==
  533. //
  534. // Make imbalances for staking
  535. let opt_role_stake_imbalance = Self::make_stake_opt_imbalance(&opt_role_stake_balance, &source_account);
  536. let opt_application_stake_imbalance = Self::make_stake_opt_imbalance(&opt_application_stake_balance, &source_account);
  537. // Call hiring module to add application
  538. let add_application = ensure_on_wrapped_error!(
  539. hiring::Module::<T>::add_application(
  540. opening.hiring_opening_id,
  541. opt_role_stake_imbalance,
  542. opt_application_stake_imbalance,
  543. human_readable_text
  544. )
  545. )?;
  546. let hiring_application_id = add_application.application_id_added;
  547. // Save member id to refund the stakes. This piece of date should outlive the 'worker'.
  548. <MemberIdByHiringApplicationId<T, I>>::insert(hiring_application_id, member_id);
  549. // Get id of new worker/lead application
  550. let new_application_id = NextApplicationId::<T, I>::get();
  551. // Make worker/lead application
  552. let application = Application::new(&role_account_id, &opening_id, &member_id, &hiring_application_id);
  553. // Store application
  554. ApplicationById::<T, I>::insert(new_application_id, application);
  555. // Update next application identifier value
  556. NextApplicationId::<T, I>::mutate(|id| *id += <ApplicationId<T> as One>::one());
  557. // Add application to set of application in worker opening
  558. OpeningById::<T, I>::mutate(opening_id, |opening| {
  559. opening.applications.insert(new_application_id);
  560. });
  561. // Trigger event
  562. Self::deposit_event(RawEvent::AppliedOnOpening(opening_id, new_application_id));
  563. }
  564. /// Withdraw the worker application. Can be done by the worker itself only.
  565. #[weight = 10_000_000] // TODO: adjust weight
  566. pub fn withdraw_application(
  567. origin,
  568. application_id: ApplicationId<T>
  569. ) {
  570. // Ensuring worker application actually exists
  571. let (application, _, opening) = Self::ensure_application_exists(&application_id)?;
  572. // Ensure that it is signed
  573. let signer_account = ensure_signed(origin)?;
  574. // Ensure that signer is applicant role account
  575. ensure!(
  576. signer_account == application.role_account_id,
  577. Error::<T, I>::OriginIsNotApplicant
  578. );
  579. //
  580. // == MUTATION SAFE ==
  581. //
  582. // Attempt to deactivate application
  583. // NB: Combined ensure check and mutation in hiring module
  584. ensure_on_wrapped_error!(
  585. hiring::Module::<T>::deactive_application(
  586. application.hiring_application_id,
  587. opening.policy_commitment.exit_role_application_stake_unstaking_period,
  588. opening.policy_commitment.exit_role_stake_unstaking_period
  589. )
  590. )?;
  591. // Trigger event
  592. Self::deposit_event(RawEvent::ApplicationWithdrawn(application_id));
  593. }
  594. /// Terminate the worker application. Can be done by the lead only.
  595. #[weight = 10_000_000] // TODO: adjust weight
  596. pub fn terminate_application(
  597. origin,
  598. application_id: ApplicationId<T>
  599. ) {
  600. // Ensure lead is set and is origin signer
  601. Self::ensure_origin_is_active_leader(origin)?;
  602. // Ensuring worker application actually exists
  603. let (application, _, opening) = Self::ensure_application_exists(&application_id)?;
  604. // Attempt to deactivate application.
  605. // NB: Combined ensure check and mutation in hiring module.
  606. ensure_on_wrapped_error!(
  607. hiring::Module::<T>::deactive_application(
  608. application.hiring_application_id,
  609. opening.policy_commitment.terminate_application_stake_unstaking_period,
  610. opening.policy_commitment.terminate_role_stake_unstaking_period
  611. )
  612. )?;
  613. //
  614. // == MUTATION SAFE ==
  615. //
  616. // Trigger event
  617. Self::deposit_event(RawEvent::ApplicationTerminated(application_id));
  618. }
  619. /// Begin reviewing, and therefore not accepting new applications.
  620. /// Require signed leader origin or the root (to begin review applications for the leader position).
  621. #[weight = 10_000_000] // TODO: adjust weight
  622. pub fn begin_applicant_review(origin, opening_id: OpeningId<T>) {
  623. // Ensure opening exists
  624. // NB: Even though call to hiring modul will have implicit check for
  625. // existence of opening as well, this check is to make sure that the opening is for
  626. // this working group, not something else.
  627. let (opening, _opening) = Self::ensure_opening_exists(&opening_id)?;
  628. Self::ensure_origin_for_opening_type(origin, opening.opening_type)?;
  629. //
  630. // == MUTATION SAFE ==
  631. //
  632. // Attempt to begin review of applications.
  633. // NB: Combined ensure check and mutation in hiring module.
  634. ensure_on_wrapped_error!(
  635. hiring::Module::<T>::begin_review(opening.hiring_opening_id)
  636. )?;
  637. // Trigger event
  638. Self::deposit_event(RawEvent::BeganApplicationReview(opening_id));
  639. }
  640. /// Fill opening for worker/lead.
  641. /// Require signed leader origin or the root (to fill opening for the leader position).
  642. #[weight = 10_000_000] // TODO: adjust weight
  643. pub fn fill_opening(
  644. origin,
  645. opening_id: OpeningId<T>,
  646. successful_application_ids: ApplicationIdSet<T>,
  647. reward_policy: Option<RewardPolicy<minting::BalanceOf<T>, T::BlockNumber>>
  648. ) {
  649. // Ensure worker opening exists
  650. let (opening, _) = Self::ensure_opening_exists(&opening_id)?;
  651. Self::ensure_origin_for_opening_type(origin, opening.opening_type)?;
  652. let potential_worker_number =
  653. Self::active_worker_count() + (successful_application_ids.len() as u32);
  654. ensure!(
  655. potential_worker_number <= T::MaxWorkerNumberLimit::get(),
  656. Error::<T, I>::MaxActiveWorkerNumberExceeded
  657. );
  658. // Cannot hire a lead when another leader exists.
  659. if matches!(opening.opening_type, OpeningType::Leader) {
  660. ensure!(!<CurrentLead<T,I>>::exists(), Error::<T, I>::CannotHireLeaderWhenLeaderExists);
  661. }
  662. // Ensure a mint exists if lead is providing a reward for positions being filled
  663. let create_reward_settings = if let Some(policy) = reward_policy {
  664. // A reward will need to be created so ensure our configured mint exists
  665. let mint_id = Self::mint();
  666. // Make sure valid parameters are selected for next payment at block number
  667. ensure!(policy.next_payment_at_block > <system::Module<T>>::block_number(),
  668. Error::<T, I>::FillOpeningInvalidNextPaymentBlock);
  669. // The verified reward settings to use
  670. Some((mint_id, policy))
  671. } else {
  672. None
  673. };
  674. // Make iterator over successful worker application
  675. let successful_iter = successful_application_ids
  676. .iter()
  677. // recover worker application from id
  678. .map(|application_id| { Self::ensure_application_exists(application_id)})
  679. // remove Err cases, i.e. non-existing applications
  680. .filter_map(|result| result.ok());
  681. // Count number of successful workers provided
  682. let num_provided_successful_application_ids = successful_application_ids.len();
  683. // Ensure all worker applications exist
  684. let number_of_successful_applications = successful_iter
  685. .clone()
  686. .count();
  687. ensure!(
  688. number_of_successful_applications == num_provided_successful_application_ids,
  689. Error::<T, I>::SuccessfulWorkerApplicationDoesNotExist
  690. );
  691. // Attempt to fill opening
  692. let successful_application_ids = successful_iter
  693. .clone()
  694. .map(|(successful_application, _, _)| successful_application.hiring_application_id)
  695. .collect::<BTreeSet<_>>();
  696. // Check for a single application for a leader.
  697. if matches!(opening.opening_type, OpeningType::Leader) {
  698. ensure!(successful_application_ids.len() == 1, Error::<T, I>::CannotHireMultipleLeaders);
  699. }
  700. // NB: Combined ensure check and mutation in hiring module
  701. ensure_on_wrapped_error!(
  702. hiring::Module::<T>::fill_opening(
  703. opening.hiring_opening_id,
  704. successful_application_ids,
  705. opening.policy_commitment.fill_opening_successful_applicant_application_stake_unstaking_period,
  706. opening.policy_commitment.fill_opening_failed_applicant_application_stake_unstaking_period,
  707. opening.policy_commitment.fill_opening_failed_applicant_role_stake_unstaking_period
  708. )
  709. )?;
  710. //
  711. // == MUTATION SAFE ==
  712. //
  713. // Process successful applications
  714. let application_id_to_worker_id = Self::fulfill_successful_applications(
  715. &opening,
  716. create_reward_settings,
  717. successful_iter.collect()
  718. );
  719. // Trigger event
  720. Self::deposit_event(RawEvent::OpeningFilled(opening_id, application_id_to_worker_id));
  721. }
  722. // ****************** Stakes **********************
  723. /// Slashes the worker stake, demands a leader origin. No limits, no actions on zero stake.
  724. /// If slashing balance greater than the existing stake - stake is slashed to zero.
  725. /// Require signed leader origin or the root (to slash the leader stake).
  726. #[weight = 10_000_000] // TODO: adjust weight
  727. pub fn slash_stake(origin, worker_id: WorkerId<T>, balance: BalanceOf<T>) {
  728. // Ensure lead is set or it is the council terminating the leader.
  729. Self::ensure_origin_for_leader(origin, worker_id)?;
  730. // Ensuring worker actually exists.
  731. let worker = Self::ensure_worker_exists(&worker_id)?;
  732. ensure!(balance != <BalanceOf<T>>::zero(), Error::<T, I>::StakeBalanceCannotBeZero);
  733. let stake_profile = worker.role_stake_profile.ok_or(Error::<T, I>::NoWorkerStakeProfile)?;
  734. //
  735. // == MUTATION SAFE ==
  736. //
  737. // This external module call both checks and mutates the state.
  738. ensure_on_wrapped_error!(
  739. <stake::Module<T>>::slash_immediate(
  740. &stake_profile.stake_id,
  741. balance,
  742. false
  743. )
  744. )?;
  745. Self::deposit_event(RawEvent::StakeSlashed(worker_id));
  746. }
  747. /// Decreases the worker/lead stake and returns the remainder to the worker role_account_id.
  748. /// Can be decreased to zero, no actions on zero stake.
  749. /// Require signed leader origin or the root (to decrease the leader stake).
  750. #[weight = 10_000_000] // TODO: adjust weight
  751. pub fn decrease_stake(origin, worker_id: WorkerId<T>, balance: BalanceOf<T>) {
  752. // Ensure lead is set or it is the council terminating the leader.
  753. Self::ensure_origin_for_leader(origin, worker_id)?;
  754. let worker = Self::ensure_worker_exists(&worker_id)?;
  755. ensure!(balance != <BalanceOf<T>>::zero(), Error::<T, I>::StakeBalanceCannotBeZero);
  756. let stake_profile = worker.role_stake_profile.ok_or(Error::<T, I>::NoWorkerStakeProfile)?;
  757. //
  758. // == MUTATION SAFE ==
  759. //
  760. // This external module call both checks and mutates the state.
  761. ensure_on_wrapped_error!(
  762. <stake::Module<T>>::decrease_stake_to_account(
  763. &stake_profile.stake_id,
  764. &worker.role_account_id,
  765. balance
  766. )
  767. )?;
  768. Self::deposit_event(RawEvent::StakeDecreased(worker_id));
  769. }
  770. /// Increases the worker/lead stake, demands a worker origin. Transfers tokens from the worker
  771. /// role_account_id to the stake. No limits on the stake.
  772. #[weight = 10_000_000] // TODO: adjust weight
  773. pub fn increase_stake(origin, worker_id: WorkerId<T>, balance: BalanceOf<T>) {
  774. // Checks worker origin, worker existence
  775. let worker = Self::ensure_worker_signed(origin, &worker_id)?;
  776. ensure!(balance != <BalanceOf<T>>::zero(), Error::<T, I>::StakeBalanceCannotBeZero);
  777. let stake_profile = worker.role_stake_profile.ok_or(Error::<T, I>::NoWorkerStakeProfile)?;
  778. //
  779. // == MUTATION SAFE ==
  780. //
  781. // This external module call both checks and mutates the state.
  782. ensure_on_wrapped_error!(
  783. <stake::Module<T>>::increase_stake_from_account(
  784. &stake_profile.stake_id,
  785. &worker.role_account_id,
  786. balance
  787. )
  788. )?;
  789. Self::deposit_event(RawEvent::StakeIncreased(worker_id));
  790. }
  791. /// Sets the capacity to enable working group budget. Requires root origin.
  792. #[weight = 10_000_000] // TODO: adjust weight
  793. pub fn set_mint_capacity(
  794. origin,
  795. new_capacity: minting::BalanceOf<T>
  796. ) {
  797. ensure_root(origin)?;
  798. let mint_id = Self::mint();
  799. // Technically this is a bug-check and should not be here.
  800. ensure!(<minting::Mints<T>>::contains_key(mint_id), Error::<T, I>::CannotFindMint);
  801. // Mint must exist - it is set at genesis or migration.
  802. let mint = <minting::Module<T>>::mints(mint_id);
  803. let current_capacity = mint.capacity();
  804. //
  805. // == MUTATION SAFE ==
  806. //
  807. if new_capacity != current_capacity {
  808. ensure_on_wrapped_error!(
  809. <minting::Module<T>>::set_mint_capacity(mint_id, new_capacity)
  810. )?;
  811. Self::deposit_event(RawEvent::MintCapacityChanged(mint_id, new_capacity));
  812. }
  813. }
  814. }
  815. }
  816. // ****************** Ensures **********************
  817. impl<T: Trait<I>, I: Instance> Module<T, I> {
  818. fn ensure_opening_policy_commitment_is_valid(
  819. policy_commitment: &OpeningPolicyCommitment<T::BlockNumber, BalanceOf<T>>,
  820. ) -> Result<(), Error<T, I>> {
  821. // Helper function. Ensures that unstaking period is None or non-zero.
  822. fn check_unstaking_period<BlockNumber: PartialEq + Zero, Error>(
  823. unstaking_period: Option<BlockNumber>,
  824. error: Error,
  825. ) -> Result<(), Error> {
  826. if let Some(unstaking_period) = unstaking_period {
  827. ensure!(unstaking_period != Zero::zero(), error);
  828. }
  829. Ok(())
  830. }
  831. // Helper function. Ensures that unstaking period is None or non-zero in the staking_policy.
  832. fn check_staking_policy<Balance, BlockNumber: PartialEq + Zero, Error>(
  833. staking_policy: Option<hiring::StakingPolicy<Balance, BlockNumber>>,
  834. crowded_out_unstaking_period_error: Error,
  835. review_period_unstaking_period_error: Error,
  836. ) -> Result<(), Error> {
  837. if let Some(staking_policy) = staking_policy {
  838. check_unstaking_period(
  839. staking_policy.crowded_out_unstaking_period_length,
  840. crowded_out_unstaking_period_error,
  841. )?;
  842. check_unstaking_period(
  843. staking_policy.review_period_expired_unstaking_period_length,
  844. review_period_unstaking_period_error,
  845. )?;
  846. }
  847. Ok(())
  848. }
  849. // Check all fill_opening unstaking periods.
  850. check_unstaking_period(
  851. policy_commitment.fill_opening_failed_applicant_role_stake_unstaking_period,
  852. Error::<T, I>::FillOpeningFailedApplicantRoleStakeUnstakingPeriodIsZero,
  853. )?;
  854. check_unstaking_period(
  855. policy_commitment.fill_opening_failed_applicant_application_stake_unstaking_period,
  856. Error::<T, I>::FillOpeningFailedApplicantApplicationStakeUnstakingPeriodIsZero,
  857. )?;
  858. check_unstaking_period(
  859. policy_commitment.fill_opening_successful_applicant_application_stake_unstaking_period,
  860. Error::<T, I>::FillOpeningSuccessfulApplicantApplicationStakeUnstakingPeriodIsZero,
  861. )?;
  862. check_unstaking_period(
  863. policy_commitment.exit_role_stake_unstaking_period,
  864. Error::<T, I>::ExitRoleStakeUnstakingPeriodIsZero,
  865. )?;
  866. check_unstaking_period(
  867. policy_commitment.exit_role_application_stake_unstaking_period,
  868. Error::<T, I>::ExitRoleApplicationStakeUnstakingPeriodIsZero,
  869. )?;
  870. check_unstaking_period(
  871. policy_commitment.terminate_role_stake_unstaking_period,
  872. Error::<T, I>::TerminateRoleStakeUnstakingPeriodIsZero,
  873. )?;
  874. check_unstaking_period(
  875. policy_commitment.terminate_application_stake_unstaking_period,
  876. Error::<T, I>::TerminateApplicationStakeUnstakingPeriodIsZero,
  877. )?;
  878. check_staking_policy(
  879. policy_commitment.role_staking_policy.clone(),
  880. Error::<T, I>::RoleStakingPolicyCrowdedOutUnstakingPeriodIsZero,
  881. Error::<T, I>::RoleStakingPolicyReviewPeriodUnstakingPeriodIsZero,
  882. )?;
  883. check_staking_policy(
  884. policy_commitment.application_staking_policy.clone(),
  885. Error::<T, I>::ApplicationStakingPolicyCrowdedOutUnstakingPeriodIsZero,
  886. Error::<T, I>::ApplicationStakingPolicyReviewPeriodUnstakingPeriodIsZero,
  887. )?;
  888. if let Some(application_rationing_policy) =
  889. policy_commitment.application_rationing_policy.clone()
  890. {
  891. ensure!(
  892. application_rationing_policy.max_active_applicants > 0,
  893. Error::<T, I>::ApplicationRationingPolicyMaxActiveApplicantsIsZero
  894. );
  895. }
  896. Ok(())
  897. }
  898. fn ensure_origin_for_opening_type(
  899. origin: T::Origin,
  900. opening_type: OpeningType,
  901. ) -> DispatchResult {
  902. match opening_type {
  903. OpeningType::Worker => {
  904. // Ensure lead is set and is origin signer.
  905. Self::ensure_origin_is_active_leader(origin)
  906. }
  907. OpeningType::Leader => {
  908. // Council proposal.
  909. ensure_root(origin).map_err(|err| err.into())
  910. }
  911. }
  912. }
  913. fn ensure_origin_for_leader(
  914. origin: T::Origin,
  915. worker_id: WorkerId<T>,
  916. ) -> Result<ExitInitiationOrigin, DispatchError> {
  917. let leader_worker_id = Self::ensure_lead_is_set()?;
  918. let (worker_opening_type, exit_origin) = if leader_worker_id == worker_id {
  919. (OpeningType::Leader, ExitInitiationOrigin::Sudo)
  920. } else {
  921. (OpeningType::Worker, ExitInitiationOrigin::Lead)
  922. };
  923. Self::ensure_origin_for_opening_type(origin, worker_opening_type)?;
  924. Ok(exit_origin)
  925. }
  926. fn ensure_lead_is_set() -> Result<WorkerId<T>, Error<T, I>> {
  927. let leader_worker_id = Self::current_lead();
  928. if let Some(leader_worker_id) = leader_worker_id {
  929. Ok(leader_worker_id)
  930. } else {
  931. Err(Error::<T, I>::CurrentLeadNotSet)
  932. }
  933. }
  934. // Checks that provided lead account id belongs to the current working group leader
  935. fn ensure_is_lead_account(lead_account_id: T::AccountId) -> DispatchResult {
  936. let leader_worker_id = Self::ensure_lead_is_set()?;
  937. let leader = Self::worker_by_id(leader_worker_id);
  938. if leader.role_account_id != lead_account_id {
  939. return Err(Error::<T, I>::IsNotLeadAccount.into());
  940. }
  941. Ok(())
  942. }
  943. fn ensure_opening_human_readable_text_is_valid(text: &[u8]) -> DispatchResult {
  944. <OpeningHumanReadableText<I>>::get()
  945. .ensure_valid(
  946. text.len(),
  947. Error::<T, I>::OpeningTextTooShort.into(),
  948. Error::<T, I>::OpeningTextTooLong.into(),
  949. )
  950. .map_err(|e| DispatchError::Other(e))
  951. }
  952. /// Ensures origin is signed by the leader.
  953. pub fn ensure_origin_is_active_leader(origin: T::Origin) -> DispatchResult {
  954. // Ensure is signed
  955. let signer = ensure_signed(origin)?;
  956. Self::ensure_is_lead_account(signer)
  957. }
  958. fn ensure_opening_exists(opening_id: &OpeningId<T>) -> Result<OpeningInfo<T>, Error<T, I>> {
  959. ensure!(
  960. OpeningById::<T, I>::contains_key(opening_id),
  961. Error::<T, I>::OpeningDoesNotExist
  962. );
  963. let opening = OpeningById::<T, I>::get(opening_id);
  964. let hiring_opening = hiring::OpeningById::<T>::get(opening.hiring_opening_id);
  965. Ok((opening, hiring_opening))
  966. }
  967. fn ensure_member_has_no_active_application_on_opening(
  968. applications: ApplicationIdSet<T>,
  969. member_id: T::MemberId,
  970. ) -> Result<(), Error<T, I>> {
  971. for application_id in applications {
  972. let application = ApplicationById::<T, I>::get(application_id);
  973. // Look for application by the member for the opening
  974. if application.member_id != member_id {
  975. continue;
  976. }
  977. // Get application details
  978. let application = <hiring::ApplicationById<T>>::get(application.hiring_application_id);
  979. // Return error if application is in active stage
  980. if application.stage == hiring::ApplicationStage::Active {
  981. return Err(Error::<T, I>::MemberHasActiveApplicationOnOpening);
  982. }
  983. }
  984. // Member does not have any active applications to the opening
  985. Ok(())
  986. }
  987. fn ensure_application_text_is_valid(text: &[u8]) -> DispatchResult {
  988. <WorkerApplicationHumanReadableText<I>>::get()
  989. .ensure_valid(
  990. text.len(),
  991. Error::<T, I>::WorkerApplicationTextTooShort.into(),
  992. Error::<T, I>::WorkerApplicationTextTooLong.into(),
  993. )
  994. .map_err(|e| DispatchError::Other(e))
  995. }
  996. // CRITICAL:
  997. // https://github.com/Joystream/substrate-runtime-joystream/issues/92
  998. // This assumes that ensure_can_withdraw can be done
  999. // for a sum of balance that later will be actually withdrawn
  1000. // using individual terms in that sum.
  1001. // This needs to be fully checked across all possibly scenarios
  1002. // of actual balance, minimum balance limit, reservation, vesting and locking.
  1003. fn ensure_can_make_stake_imbalance(
  1004. opt_balances: Vec<&Option<BalanceOf<T>>>,
  1005. source_account: &T::AccountId,
  1006. ) -> DispatchResult {
  1007. let zero_balance = <BalanceOf<T> as Zero>::zero();
  1008. // Total amount to be staked
  1009. let total_amount = opt_balances.iter().fold(zero_balance, |sum, opt_balance| {
  1010. sum + if let Some(balance) = opt_balance {
  1011. *balance
  1012. } else {
  1013. zero_balance
  1014. }
  1015. });
  1016. if total_amount > zero_balance {
  1017. // Ensure that
  1018. if CurrencyOf::<T>::free_balance(source_account) < total_amount {
  1019. Err(Error::<T, I>::InsufficientBalanceToCoverStake.into())
  1020. } else {
  1021. let new_balance = CurrencyOf::<T>::free_balance(source_account) - total_amount;
  1022. CurrencyOf::<T>::ensure_can_withdraw(
  1023. source_account,
  1024. total_amount,
  1025. WithdrawReasons::all(),
  1026. new_balance,
  1027. )
  1028. }
  1029. } else {
  1030. Ok(())
  1031. }
  1032. }
  1033. fn ensure_application_exists(
  1034. application_id: &ApplicationId<T>,
  1035. ) -> Result<ApplicationInfo<T>, Error<T, I>> {
  1036. ensure!(
  1037. ApplicationById::<T, I>::contains_key(application_id),
  1038. Error::<T, I>::WorkerApplicationDoesNotExist
  1039. );
  1040. let application = ApplicationById::<T, I>::get(application_id);
  1041. let opening = OpeningById::<T, I>::get(application.opening_id);
  1042. Ok((application, *application_id, opening))
  1043. }
  1044. /// Ensures the origin contains signed account that belongs to existing worker.
  1045. pub fn ensure_worker_signed(
  1046. origin: T::Origin,
  1047. worker_id: &WorkerId<T>,
  1048. ) -> Result<WorkerOf<T>, DispatchError> {
  1049. // Ensure that it is signed
  1050. let signer_account = ensure_signed(origin)?;
  1051. // Ensure that id corresponds to active worker
  1052. let worker = Self::ensure_worker_exists(&worker_id)?;
  1053. // Ensure that signer is actually role account of worker
  1054. ensure!(
  1055. signer_account == worker.role_account_id,
  1056. Error::<T, I>::SignerIsNotWorkerRoleAccount
  1057. );
  1058. Ok(worker)
  1059. }
  1060. fn ensure_worker_exists(worker_id: &WorkerId<T>) -> Result<WorkerOf<T>, Error<T, I>> {
  1061. ensure!(
  1062. WorkerById::<T, I>::contains_key(worker_id),
  1063. Error::<T, I>::WorkerDoesNotExist
  1064. );
  1065. let worker = WorkerById::<T, I>::get(worker_id);
  1066. Ok(worker)
  1067. }
  1068. fn ensure_worker_has_recurring_reward(
  1069. worker: &WorkerOf<T>,
  1070. ) -> Result<T::RewardRelationshipId, Error<T, I>> {
  1071. if let Some(relationship_id) = worker.reward_relationship {
  1072. Ok(relationship_id)
  1073. } else {
  1074. Err(Error::<T, I>::WorkerHasNoReward)
  1075. }
  1076. }
  1077. fn ensure_worker_exit_rationale_text_is_valid(text: &[u8]) -> DispatchResult {
  1078. Self::worker_exit_rationale_text()
  1079. .ensure_valid(
  1080. text.len(),
  1081. Error::<T, I>::WorkerExitRationaleTextTooShort.into(),
  1082. Error::<T, I>::WorkerExitRationaleTextTooLong.into(),
  1083. )
  1084. .map_err(|e| DispatchError::Other(e))
  1085. }
  1086. }
  1087. /// Creates default text constraint.
  1088. pub fn default_text_constraint() -> InputValidationLengthConstraint {
  1089. InputValidationLengthConstraint::new(1, 1024)
  1090. }
  1091. impl<T: Trait<I>, I: Instance> Module<T, I> {
  1092. /// Callback from StakingEventsHandler. Refunds unstaked imbalance back to the source account.
  1093. pub fn refund_working_group_stake(
  1094. stake_id: StakeId<T>,
  1095. imbalance: NegativeImbalance<T>,
  1096. ) -> NegativeImbalance<T> {
  1097. if !hiring::ApplicationIdByStakingId::<T>::contains_key(stake_id) {
  1098. print("Working group broken invariant: no stake id in the hiring module.");
  1099. return imbalance;
  1100. }
  1101. let hiring_application_id = hiring::ApplicationIdByStakingId::<T>::get(stake_id);
  1102. if !MemberIdByHiringApplicationId::<T, I>::contains_key(hiring_application_id) {
  1103. // Stake is not related to the working group module.
  1104. return imbalance;
  1105. }
  1106. let member_id = Module::<T, I>::member_id_by_hiring_application_id(hiring_application_id);
  1107. if membership::MembershipById::<T>::contains_key(member_id) {
  1108. let member_profile = membership::MembershipById::<T>::get(member_id);
  1109. let refunding_result = CurrencyOf::<T>::resolve_into_existing(
  1110. &member_profile.controller_account,
  1111. imbalance,
  1112. );
  1113. if refunding_result.is_err() {
  1114. print("Working group broken invariant: cannot refund.");
  1115. // cannot return imbalance here, because of possible double spending.
  1116. return <NegativeImbalance<T>>::zero();
  1117. }
  1118. } else {
  1119. print("Working group broken invariant: no member profile.");
  1120. return imbalance;
  1121. }
  1122. <NegativeImbalance<T>>::zero()
  1123. }
  1124. /// Returns all existing worker id list excluding the current leader worker id.
  1125. pub fn get_regular_worker_ids() -> Vec<WorkerId<T>> {
  1126. let lead_worker_id = Self::current_lead();
  1127. <WorkerById<T, I>>::iter()
  1128. .filter_map(|(worker_id, _)| {
  1129. // Filter the leader worker id if the leader is set.
  1130. lead_worker_id
  1131. .clone()
  1132. .map_or(Some(worker_id), |lead_worker_id| {
  1133. if worker_id == lead_worker_id {
  1134. None
  1135. } else {
  1136. Some(worker_id)
  1137. }
  1138. })
  1139. })
  1140. .collect()
  1141. }
  1142. fn make_stake_opt_imbalance(
  1143. opt_balance: &Option<BalanceOf<T>>,
  1144. source_account: &T::AccountId,
  1145. ) -> Option<NegativeImbalance<T>> {
  1146. if let Some(balance) = opt_balance {
  1147. let withdraw_result = CurrencyOf::<T>::withdraw(
  1148. source_account,
  1149. *balance,
  1150. WithdrawReasons::all(),
  1151. ExistenceRequirement::AllowDeath,
  1152. );
  1153. assert!(withdraw_result.is_ok());
  1154. withdraw_result.ok()
  1155. } else {
  1156. None
  1157. }
  1158. }
  1159. fn deactivate_worker(
  1160. worker_id: &WorkerId<T>,
  1161. worker: &WorkerOf<T>,
  1162. exit_initiation_origin: &ExitInitiationOrigin,
  1163. rationale_text: &[u8],
  1164. ) -> Result<(), Error<T, I>> {
  1165. // Stop any possible recurring rewards
  1166. if let Some(reward_relationship_id) = worker.reward_relationship {
  1167. // Attempt to deactivate
  1168. recurringrewards::Module::<T>::try_to_deactivate_relationship(reward_relationship_id)
  1169. .map_err(|_| Error::<T, I>::RelationshipMustExist)?;
  1170. }; // else: Did not deactivate, there was no reward relationship!
  1171. // Unstake if stake profile exists
  1172. if let Some(ref stake_profile) = worker.role_stake_profile {
  1173. // Determine unstaking period based on who initiated deactivation
  1174. let unstaking_period = match exit_initiation_origin {
  1175. ExitInitiationOrigin::Lead => stake_profile.termination_unstaking_period,
  1176. ExitInitiationOrigin::Sudo => stake_profile.termination_unstaking_period,
  1177. ExitInitiationOrigin::Worker => stake_profile.exit_unstaking_period,
  1178. };
  1179. // Unstake
  1180. ensure_on_wrapped_error!(stake::Module::<T>::initiate_unstaking(
  1181. &stake_profile.stake_id,
  1182. unstaking_period
  1183. ))?;
  1184. }
  1185. // Unset lead if the leader is leaving.
  1186. let leader_worker_id = <CurrentLead<T, I>>::get();
  1187. if let Some(leader_worker_id) = leader_worker_id {
  1188. if leader_worker_id == *worker_id {
  1189. Self::unset_lead();
  1190. }
  1191. }
  1192. // Remove the worker from the storage.
  1193. WorkerById::<T, I>::remove(worker_id);
  1194. Self::decrease_active_worker_counter();
  1195. // Trigger the event
  1196. let event = match exit_initiation_origin {
  1197. ExitInitiationOrigin::Lead => {
  1198. RawEvent::TerminatedWorker(*worker_id, rationale_text.to_vec())
  1199. }
  1200. ExitInitiationOrigin::Worker => {
  1201. RawEvent::WorkerExited(*worker_id, rationale_text.to_vec())
  1202. }
  1203. ExitInitiationOrigin::Sudo => {
  1204. RawEvent::TerminatedLeader(*worker_id, rationale_text.to_vec())
  1205. }
  1206. };
  1207. Self::deposit_event(event);
  1208. Ok(())
  1209. }
  1210. fn initialize_working_group(
  1211. opening_human_readable_text_constraint: InputValidationLengthConstraint,
  1212. worker_application_human_readable_text_constraint: InputValidationLengthConstraint,
  1213. worker_exit_rationale_text_constraint: InputValidationLengthConstraint,
  1214. working_group_mint_capacity: minting::BalanceOf<T>,
  1215. ) {
  1216. // Create a mint.
  1217. let mint_id_result = <minting::Module<T>>::add_mint(working_group_mint_capacity, None);
  1218. if let Ok(mint_id) = mint_id_result {
  1219. <Mint<T, I>>::put(mint_id);
  1220. } else {
  1221. panic!("Failed to create a mint for the working group");
  1222. }
  1223. // Create constraints
  1224. <OpeningHumanReadableText<I>>::put(opening_human_readable_text_constraint);
  1225. <WorkerApplicationHumanReadableText<I>>::put(
  1226. worker_application_human_readable_text_constraint,
  1227. );
  1228. <WorkerExitRationaleText<I>>::put(worker_exit_rationale_text_constraint);
  1229. }
  1230. // Set worker id as a leader id.
  1231. pub(crate) fn set_lead(worker_id: WorkerId<T>) {
  1232. // Update current lead
  1233. <CurrentLead<T, I>>::put(worker_id);
  1234. // Trigger an event
  1235. Self::deposit_event(RawEvent::LeaderSet(worker_id));
  1236. }
  1237. // Evict the currently set lead.
  1238. pub(crate) fn unset_lead() {
  1239. if Self::ensure_lead_is_set().is_ok() {
  1240. // Update current lead
  1241. <CurrentLead<T, I>>::kill();
  1242. Self::deposit_event(RawEvent::LeaderUnset());
  1243. }
  1244. }
  1245. // Processes successful application during the fill_opening().
  1246. fn fulfill_successful_applications(
  1247. opening: &OpeningOf<T>,
  1248. reward_settings: Option<RewardSettings<T>>,
  1249. successful_applications_info: Vec<ApplicationInfo<T>>,
  1250. ) -> BTreeMap<ApplicationId<T>, WorkerId<T>> {
  1251. let mut application_id_to_worker_id = BTreeMap::new();
  1252. successful_applications_info
  1253. .iter()
  1254. .for_each(|(successful_application, id, _)| {
  1255. // Create a reward relationship.
  1256. let reward_relationship = if let Some((mint_id, checked_policy)) =
  1257. reward_settings.clone()
  1258. {
  1259. // Create a new recipient for the new relationship.
  1260. let recipient = <recurringrewards::Module<T>>::add_recipient();
  1261. // Member must exist, since it was checked that it can enter the role.
  1262. let member_profile =
  1263. <membership::Module<T>>::membership(successful_application.member_id);
  1264. // Rewards are deposited in the member's root account.
  1265. let reward_destination_account = member_profile.root_account;
  1266. // Values have been checked so this should not fail!
  1267. let relationship_id = <recurringrewards::Module<T>>::add_reward_relationship(
  1268. mint_id,
  1269. recipient,
  1270. reward_destination_account,
  1271. checked_policy.amount_per_payout,
  1272. checked_policy.next_payment_at_block,
  1273. checked_policy.payout_interval,
  1274. )
  1275. .expect("Failed to create reward relationship!");
  1276. Some(relationship_id)
  1277. } else {
  1278. None
  1279. };
  1280. // Get possible stake for role
  1281. let application =
  1282. hiring::ApplicationById::<T>::get(successful_application.hiring_application_id);
  1283. // Staking profile for worker
  1284. let stake_profile = if let Some(ref stake_id) = application.active_role_staking_id {
  1285. Some(RoleStakeProfile::new(
  1286. stake_id,
  1287. &opening
  1288. .policy_commitment
  1289. .terminate_role_stake_unstaking_period,
  1290. &opening.policy_commitment.exit_role_stake_unstaking_period,
  1291. ))
  1292. } else {
  1293. None
  1294. };
  1295. // Get worker id
  1296. let new_worker_id = <NextWorkerId<T, I>>::get();
  1297. // Construct worker
  1298. let worker = Worker::new(
  1299. &successful_application.member_id,
  1300. &successful_application.role_account_id,
  1301. &reward_relationship,
  1302. &stake_profile,
  1303. );
  1304. // Store a worker
  1305. <WorkerById<T, I>>::insert(new_worker_id, worker);
  1306. Self::increase_active_worker_counter();
  1307. // Update next worker id
  1308. <NextWorkerId<T, I>>::mutate(|id| *id += <WorkerId<T> as One>::one());
  1309. application_id_to_worker_id.insert(*id, new_worker_id);
  1310. // Sets a leader on successful opening when opening is for leader.
  1311. if matches!(opening.opening_type, OpeningType::Leader) {
  1312. Self::set_lead(new_worker_id);
  1313. }
  1314. });
  1315. application_id_to_worker_id
  1316. }
  1317. // Increases active worker counter (saturating).
  1318. fn increase_active_worker_counter() {
  1319. let next_active_worker_count_value = Self::active_worker_count().saturating_add(1);
  1320. <ActiveWorkerCount<I>>::put(next_active_worker_count_value);
  1321. }
  1322. // Decreases active worker counter (saturating).
  1323. fn decrease_active_worker_counter() {
  1324. let next_active_worker_count_value = Self::active_worker_count().saturating_sub(1);
  1325. <ActiveWorkerCount<I>>::put(next_active_worker_count_value);
  1326. }
  1327. }