lib.rs 103 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679
  1. // Clippy linter warning. TODO: remove after the Constaninople release
  2. #![allow(clippy::type_complexity)]
  3. // disable it because of possible frontend API break
  4. // Clippy linter warning. TODO: refactor "this function has too many argument"
  5. #![allow(clippy::too_many_arguments)] // disable it because of possible API break
  6. // Ensure we're `no_std` when compiling for Wasm.
  7. #![cfg_attr(not(feature = "std"), no_std)]
  8. #[cfg(test)]
  9. mod tests;
  10. #[cfg(test)]
  11. mod mock;
  12. pub mod genesis;
  13. #[cfg(feature = "std")]
  14. use serde::{Deserialize, Serialize};
  15. use codec::{Decode, Encode};
  16. use frame_support::traits::{Currency, ExistenceRequirement, WithdrawReasons};
  17. use frame_support::{decl_event, decl_module, decl_storage, ensure};
  18. use sp_arithmetic::traits::{One, Zero};
  19. use sp_std::borrow::ToOwned;
  20. use sp_std::collections::{btree_map::BTreeMap, btree_set::BTreeSet};
  21. use sp_std::vec;
  22. use sp_std::vec::Vec;
  23. use system::{ensure_root, ensure_signed};
  24. use common::constraints::InputValidationLengthConstraint;
  25. /// Module configuration trait for this Substrate module.
  26. pub trait Trait:
  27. system::Trait
  28. + minting::Trait
  29. + recurringrewards::Trait
  30. + stake::Trait
  31. + hiring::Trait
  32. + versioned_store_permissions::Trait
  33. + membership::Trait
  34. {
  35. /// The event type.
  36. type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
  37. }
  38. /// Type constraint for identifer used for actors.
  39. pub type ActorId<T> = <T as membership::Trait>::ActorId;
  40. /// Type for identifier for channels.
  41. /// The ChannelId must be capable of behaving like an actor id for membership module,
  42. /// since publishers are identified by their channel id.
  43. pub type ChannelId<T> = ActorId<T>;
  44. /// Type identifier for lead role, which must be same as membership actor identifeir
  45. pub type LeadId<T> = ActorId<T>;
  46. /// Type identifier for curator role, which must be same as membership actor identifeir
  47. pub type CuratorId<T> = ActorId<T>;
  48. /// Type for the identifer for an opening for a curator.
  49. pub type CuratorOpeningId<T> = <T as hiring::Trait>::OpeningId;
  50. /// Tyoe for the indentifier for an application as a curator.
  51. pub type CuratorApplicationId<T> = <T as hiring::Trait>::ApplicationId;
  52. /// Balance type of runtime
  53. pub type BalanceOf<T> =
  54. <<T as stake::Trait>::Currency as Currency<<T as system::Trait>::AccountId>>::Balance;
  55. /// Balance type of runtime
  56. pub type CurrencyOf<T> = <T as stake::Trait>::Currency;
  57. /// Negative imbalance of runtime.
  58. pub type NegativeImbalance<T> =
  59. <<T as stake::Trait>::Currency as Currency<<T as system::Trait>::AccountId>>::NegativeImbalance;
  60. /// Type of mintin reward relationship identifiers
  61. pub type RewardRelationshipId<T> = <T as recurringrewards::Trait>::RewardRelationshipId;
  62. /// Stake identifier in staking module
  63. pub type StakeId<T> = <T as stake::Trait>::StakeId;
  64. /// Type of permissions module prinicipal identifiers
  65. pub type PrincipalId<T> = <T as versioned_store_permissions::Trait>::Credential;
  66. pub type CuratorApplicationIdToCuratorIdMap<T> = BTreeMap<CuratorApplicationId<T>, CuratorId<T>>;
  67. // Workaround for BTreeSet type
  68. pub type CuratorApplicationIdSet<T> = BTreeSet<CuratorApplicationId<T>>;
  69. //TODO: Convert errors to the Substrate decl_error! macro.
  70. /// Result with string error message. This exists for backward compatibility purpose.
  71. pub type DispatchResult = Result<(), &'static str>;
  72. /*
  73. * MOVE ALL OF THESE OUT TO COMMON LATER
  74. */
  75. pub static MSG_CHANNEL_HANDLE_TOO_SHORT: &str = "Channel handle too short.";
  76. pub static MSG_CHANNEL_HANDLE_TOO_LONG: &str = "Channel handle too long.";
  77. pub static MSG_CHANNEL_DESCRIPTION_TOO_SHORT: &str = "Channel description too short";
  78. pub static MSG_CHANNEL_DESCRIPTION_TOO_LONG: &str = "Channel description too long";
  79. pub static MSG_CHANNEL_ID_INVALID: &str = "Channel id invalid";
  80. pub static MSG_CHANNEL_CREATION_DISABLED: &str = "Channel creation currently disabled";
  81. static MSG_CHANNEL_HANDLE_ALREADY_TAKEN: &str = "Channel handle is already taken";
  82. static MSG_CHANNEL_TITLE_TOO_SHORT: &str = "Channel title too short";
  83. static MSG_CHANNEL_TITLE_TOO_LONG: &str = "Channel title too long";
  84. static MSG_CHANNEL_AVATAR_TOO_SHORT: &str = "Channel avatar URL too short";
  85. static MSG_CHANNEL_AVATAR_TOO_LONG: &str = "Channel avatar URL too long";
  86. static MSG_CHANNEL_BANNER_TOO_SHORT: &str = "Channel banner URL too short";
  87. static MSG_CHANNEL_BANNER_TOO_LONG: &str = "Channel banner URL too long";
  88. //static MSG_MEMBER_CANNOT_BECOME_PUBLISHER: &str =
  89. // "Member cannot become a publisher";
  90. static MSG_ORIGIN_DOES_NOT_MATCH_CHANNEL_ROLE_ACCOUNT: &str =
  91. "Origin does not match channel role account";
  92. pub static MSG_CURRENT_LEAD_ALREADY_SET: &str = "Current lead is already set";
  93. pub static MSG_CURRENT_LEAD_NOT_SET: &str = "Current lead is not set";
  94. pub static MSG_ORIGIN_IS_NOT_LEAD: &str = "Origin is not lead";
  95. pub static MSG_ORIGIN_IS_NOT_APPLICANT: &str = "Origin is not applicant";
  96. pub static MSG_CURATOR_OPENING_DOES_NOT_EXIST: &str = "Curator opening does not exist";
  97. pub static MSG_CURATOR_APPLICATION_DOES_NOT_EXIST: &str = "Curator application does not exist";
  98. pub static MSG_INSUFFICIENT_BALANCE_TO_APPLY: &str = "Insufficient balance to apply";
  99. pub static MSG_SUCCESSFUL_CURATOR_APPLICATION_DOES_NOT_EXIST: &str =
  100. "Successful curatora pplication does not exist";
  101. pub static MSG_MEMBER_NO_LONGER_REGISTRABLE_AS_CURATOR: &str =
  102. "Member no longer registrable as curator";
  103. pub static MSG_CURATOR_DOES_NOT_EXIST: &str = "Curator does not exist";
  104. pub static MSG_CURATOR_IS_NOT_ACTIVE: &str = "Curator is not active";
  105. pub static MSG_CURATOR_EXIT_RATIONALE_TEXT_TOO_LONG: &str =
  106. "Curator exit rationale text is too long";
  107. pub static MSG_CURATOR_EXIT_RATIONALE_TEXT_TOO_SHORT: &str =
  108. "Curator exit rationale text is too short";
  109. pub static MSG_CURATOR_APPLICATION_TEXT_TOO_LONG: &str = "Curator application text too long";
  110. pub static MSG_CURATOR_APPLICATION_TEXT_TOO_SHORT: &str = "Curator application text too short";
  111. pub static MSG_SIGNER_IS_NOT_CURATOR_ROLE_ACCOUNT: &str = "Signer is not curator role account";
  112. pub static MSG_UNSTAKER_DOES_NOT_EXIST: &str = "Unstaker does not exist";
  113. pub static MSG_CURATOR_HAS_NO_REWARD: &str = "Curator has no recurring reward";
  114. pub static MSG_CURATOR_NOT_CONTROLLED_BY_MEMBER: &str = "Curator not controlled by member";
  115. pub static MSG_INSUFFICIENT_BALANCE_TO_COVER_STAKE: &str = "Insuffieicnt balance to cover stake";
  116. /*
  117. * The errors below, while in many cases encoding similar outcomes,
  118. * are scoped to the specific extrinsic for which they are used.
  119. * The reason for this is that it will later to easier to convert this
  120. * representation into into the type safe error encoding coming in
  121. * later versions of Substrate.
  122. */
  123. // Errors for `accept_curator_applications`
  124. pub static MSG_ACCEPT_CURATOR_APPLICATIONS_OPENING_DOES_NOT_EXIST: &str = "Opening does not exist";
  125. pub static MSG_ACCEPT_CURATOR_APPLICATIONS_OPENING_IS_NOT_WAITING_TO_BEGIN: &str =
  126. "Opening Is Not in Waiting to begin";
  127. // Errors for `begin_curator_applicant_review`
  128. pub static MSG_BEGIN_CURATOR_APPLICANT_REVIEW_OPENING_DOES_NOT_EXIST: &str =
  129. "Opening does not exist";
  130. pub static MSG_BEGIN_CURATOR_APPLICANT_REVIEW_OPENING_OPENING_IS_NOT_WAITING_TO_BEGIN: &str =
  131. "Opening Is Not in Waiting";
  132. // Errors for `fill_curator_opening`
  133. pub static MSG_FULL_CURATOR_OPENING_OPENING_DOES_NOT_EXIST: &str = "OpeningDoesNotExist";
  134. pub static MSG_FULL_CURATOR_OPENING_OPENING_NOT_IN_REVIEW_PERIOD_STAGE: &str =
  135. "OpeningNotInReviewPeriodStage";
  136. pub static MSG_FULL_CURATOR_OPENING_UNSUCCESSFUL_APPLICATION_STAKE_UNSTAKING_PERIOD_TOO_SHORT:
  137. &str = "Application stake unstaking period for successful applicants too short";
  138. pub static MSG_FULL_CURATOR_OPENING_SUCCESSFUL_APPLICATION_STAKE_UNSTAKING_PERIOD_TOO_SHORT: &str =
  139. "Application stake unstaking period for failed applicants too short";
  140. pub static MSG_FULL_CURATOR_OPENING_SUCCESSFUL_ROLE_STAKE_UNSTAKING_PERIOD_TOO_SHORT: &str =
  141. "Role stake unstaking period for successful applicants too short";
  142. pub static MSG_FULL_CURATOR_OPENING_UNSUCCESSFUL_ROLE_STAKE_UNSTAKING_PERIOD_TOO_SHORT: &str =
  143. "Role stake unstaking period for failed applicants too short";
  144. pub static MSG_FULL_CURATOR_OPENING_SUCCESSFUL_APPLICATION_STAKE_UNSTAKING_PERIOD_REDUNDANT: &str =
  145. "Application stake unstaking period for successful applicants redundant";
  146. pub static MSG_FULL_CURATOR_OPENING_UNSUCCESSFUL_APPLICATION_STAKE_UNSTAKING_PERIOD_REDUNDANT:
  147. &str = "Application stake unstaking period for failed applicants redundant";
  148. pub static MSG_FULL_CURATOR_OPENING_SUCCESSFUL_ROLE_STAKE_UNSTAKING_PERIOD_REDUNDANT: &str =
  149. "Role stake unstaking period for successful applicants redundant";
  150. pub static MSG_FULL_CURATOR_OPENING_UNSUCCESSFUL_ROLE_STAKE_UNSTAKING_PERIOD_REDUNDANT: &str =
  151. "Role stake unstaking period for failed applicants redundant";
  152. pub static MSG_FULL_CURATOR_OPENING_APPLICATION_DOES_NOT_EXIST: &str = "ApplicationDoesNotExist";
  153. pub static MSG_FULL_CURATOR_OPENING_APPLICATION_NOT_ACTIVE: &str = "ApplicationNotInActiveStage";
  154. pub static MSG_FILL_CURATOR_OPENING_INVALID_NEXT_PAYMENT_BLOCK: &str =
  155. "Reward policy has invalid next payment block number";
  156. pub static MSG_FILL_CURATOR_OPENING_MINT_DOES_NOT_EXIST: &str = "Working group mint does not exist";
  157. pub static MSG_FILL_CURATOR_OPENING_APPLICATION_FOR_WRONG_OPENING: &str =
  158. "Applications not for opening";
  159. // Errors for `withdraw_curator_application`
  160. pub static MSG_WITHDRAW_CURATOR_APPLICATION_APPLICATION_DOES_NOT_EXIST: &str =
  161. "ApplicationDoesNotExist";
  162. pub static MSG_WITHDRAW_CURATOR_APPLICATION_APPLICATION_NOT_ACTIVE: &str = "ApplicationNotActive";
  163. pub static MSG_WITHDRAW_CURATOR_APPLICATION_OPENING_NOT_ACCEPTING_APPLICATIONS: &str =
  164. "OpeningNotAcceptingApplications";
  165. pub static MSG_WITHDRAW_CURATOR_APPLICATION_UNSTAKING_PERIOD_TOO_SHORT: &str =
  166. "UnstakingPeriodTooShort ..."; // <== SHOULD REALLY BE TWO SEPARATE, ONE FOR EACH STAKING PURPOSE
  167. pub static MSG_WITHDRAW_CURATOR_APPLICATION_REDUNDANT_UNSTAKING_PERIOD: &str =
  168. "RedundantUnstakingPeriodProvided ...";
  169. // Errors for `create_channel`
  170. pub static MSG_CREATE_CHANNEL_IS_NOT_MEMBER: &str = "Is not a member";
  171. pub static MSG_CREATE_CHANNEL_NOT_CONTROLLER_ACCOUNT: &str =
  172. "Account is not controller account of member";
  173. // Errors for `add_curator_opening`
  174. pub static MSG_ADD_CURATOR_OPENING_ACTIVATES_IN_THE_PAST: &str =
  175. "Opening does not activate in the future";
  176. pub static MSG_ADD_CURATOR_OPENING_ROLE_STAKE_LESS_THAN_MINIMUM: &str =
  177. "Role stake amount less than minimum currency balance";
  178. pub static MSG_ADD_CURATOR_OPENING_APPLIICATION_STAKE_LESS_THAN_MINIMUM: &str =
  179. "Application stake amount less than minimum currency balance";
  180. pub static MSG_ADD_CURATOR_OPENING_OPENING_DOES_NOT_EXIST: &str = "OpeningDoesNotExist";
  181. pub static MSG_ADD_CURATOR_OPENING_STAKE_PROVIDED_WHEN_REDUNDANT: &str =
  182. "StakeProvidedWhenRedundant ..."; // <== SHOULD REALLY BE TWO SEPARATE, ONE FOR EACH STAKING PURPOSE
  183. pub static MSG_ADD_CURATOR_OPENING_STAKE_MISSING_WHEN_REQUIRED: &str =
  184. "StakeMissingWhenRequired ..."; // <== SHOULD REALLY BE TWO SEPARATE, ONE FOR EACH STAKING PURPOSE
  185. pub static MSG_ADD_CURATOR_OPENING_STAKE_AMOUNT_TOO_LOW: &str = "StakeAmountTooLow ..."; // <== SHOULD REALLY BE TWO SEPARATE, ONE FOR EACH STAKING PURPOSE
  186. pub static MSG_ADD_CURATOR_OPENING_OPENING_NOT_IN_ACCEPTING_APPLICATION_STAGE: &str =
  187. "OpeningNotInAcceptingApplicationsStage";
  188. pub static MSG_ADD_CURATOR_OPENING_NEW_APPLICATION_WAS_CROWDED_OUT: &str =
  189. "NewApplicationWasCrowdedOut";
  190. pub static MSG_ADD_CURATOR_OPENING_ZERO_MAX_APPLICANT_COUNT: &str =
  191. "Application rationing has zero max active applicants";
  192. // Errors for `apply_on_curator_opening`
  193. pub static MSG_APPLY_ON_CURATOR_OPENING_UNSIGNED_ORIGIN: &str = "Unsigned origin";
  194. pub static MSG_MEMBER_ID_INVALID: &str = "Member id is invalid";
  195. pub static MSG_SIGNER_NOT_CONTROLLER_ACCOUNT: &str = "Signer does not match controller account";
  196. pub static MSG_ORIGIN_IS_NIETHER_MEMBER_CONTROLLER_OR_ROOT: &str =
  197. "Origin must be controller or root account of member";
  198. pub static MSG_MEMBER_HAS_ACTIVE_APPLICATION_ON_OPENING: &str =
  199. "Member already has an active application on the opening";
  200. pub static MSG_ADD_CURATOR_OPENING_ROLE_STAKE_CANNOT_BE_ZERO: &str =
  201. "Add curator opening role stake cannot be zero";
  202. pub static MSG_ADD_CURATOR_OPENING_APPLICATION_STAKE_CANNOT_BE_ZERO: &str =
  203. "Add curator opening application stake cannot be zero";
  204. /// The exit stage of a lead involvement in the working group.
  205. #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
  206. #[derive(Encode, Decode, Debug, Clone, PartialEq)]
  207. pub struct ExitedLeadRole<BlockNumber> {
  208. /// When exit was initiated.
  209. pub initiated_at_block_number: BlockNumber,
  210. }
  211. /// The stage of the involvement of a lead in the working group.
  212. #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
  213. #[derive(Encode, Decode, Debug, Clone, PartialEq)]
  214. pub enum LeadRoleState<BlockNumber> {
  215. /// Currently active.
  216. Active,
  217. /// No longer active, for some reason
  218. Exited(ExitedLeadRole<BlockNumber>),
  219. }
  220. /// Must be default constructible because it indirectly is a value in a storage map.
  221. /// ***SHOULD NEVER ACTUALLY GET CALLED, IS REQUIRED TO DUE BAD STORAGE MODEL IN SUBSTRATE***
  222. impl<BlockNumber> Default for LeadRoleState<BlockNumber> {
  223. fn default() -> Self {
  224. LeadRoleState::Active
  225. }
  226. }
  227. /// Working group lead: curator lead
  228. /// For now this role is not staked or inducted through an structured process, like the hiring module,
  229. /// hence information about this is missing. Recurring rewards is included, somewhat arbitrarily!
  230. #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
  231. #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
  232. pub struct Lead<AccountId, RewardRelationshipId, BlockNumber, MemberId> {
  233. /// Leader member id,
  234. pub member_id: MemberId,
  235. /// Account used to authenticate in this role,
  236. pub role_account: AccountId,
  237. /// Whether the role has recurring reward, and if so an identifier for this.
  238. pub reward_relationship: Option<RewardRelationshipId>,
  239. /// When was inducted
  240. /// TODO: Add richer information about circumstances of induction, like referencing a council proposal?
  241. pub inducted: BlockNumber,
  242. /// The stage of the involvement of this lead in the working group.
  243. pub stage: LeadRoleState<BlockNumber>,
  244. }
  245. /// Origin of exit initiation on behalf of a curator.'
  246. #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
  247. #[derive(Encode, Decode, Debug, Clone, PartialEq)]
  248. pub enum CuratorExitInitiationOrigin {
  249. /// Lead is origin.
  250. Lead,
  251. /// The curator exiting is the origin.
  252. Curator,
  253. }
  254. /// The exit stage of a curators involvement in the working group.
  255. #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
  256. #[derive(Encode, Decode, Debug, Clone, PartialEq)]
  257. pub struct CuratorExitSummary<BlockNumber> {
  258. /// Origin for exit.
  259. pub origin: CuratorExitInitiationOrigin,
  260. /// When exit was initiated.
  261. pub initiated_at_block_number: BlockNumber,
  262. /// Explainer for why exit was initited.
  263. pub rationale_text: Vec<u8>,
  264. }
  265. impl<BlockNumber: Clone> CuratorExitSummary<BlockNumber> {
  266. pub fn new(
  267. origin: &CuratorExitInitiationOrigin,
  268. initiated_at_block_number: &BlockNumber,
  269. rationale_text: &[u8],
  270. ) -> Self {
  271. CuratorExitSummary {
  272. origin: (*origin).clone(),
  273. initiated_at_block_number: (*initiated_at_block_number).clone(),
  274. rationale_text: rationale_text.to_owned(),
  275. }
  276. }
  277. }
  278. /// The stage of the involvement of a curator in the working group.
  279. #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
  280. #[derive(Encode, Decode, Debug, Clone, PartialEq)]
  281. pub enum CuratorRoleStage<BlockNumber> {
  282. /// Currently active.
  283. Active,
  284. /// Currently unstaking
  285. Unstaking(CuratorExitSummary<BlockNumber>),
  286. /// No longer active and unstaked
  287. Exited(CuratorExitSummary<BlockNumber>),
  288. }
  289. /// Must be default constructible because it indirectly is a value in a storage map.
  290. /// ***SHOULD NEVER ACTUALLY GET CALLED, IS REQUIRED TO DUE BAD STORAGE MODEL IN SUBSTRATE***
  291. impl<BlockNumber> Default for CuratorRoleStage<BlockNumber> {
  292. fn default() -> Self {
  293. CuratorRoleStage::Active
  294. }
  295. }
  296. /// The induction of a curator in the working group.
  297. #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
  298. #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
  299. pub struct CuratorInduction<LeadId, CuratorApplicationId, BlockNumber> {
  300. /// Lead responsible for inducting curator
  301. pub lead: LeadId,
  302. /// Application through which curator was inducted
  303. pub curator_application_id: CuratorApplicationId,
  304. /// When induction occurred
  305. pub at_block: BlockNumber,
  306. }
  307. impl<LeadId: Clone, CuratorApplicationId: Clone, BlockNumber: Clone>
  308. CuratorInduction<LeadId, CuratorApplicationId, BlockNumber>
  309. {
  310. pub fn new(
  311. lead: &LeadId,
  312. curator_application_id: &CuratorApplicationId,
  313. at_block: &BlockNumber,
  314. ) -> Self {
  315. CuratorInduction {
  316. lead: (*lead).clone(),
  317. curator_application_id: (*curator_application_id).clone(),
  318. at_block: (*at_block).clone(),
  319. }
  320. }
  321. }
  322. /// Role stake information for a curator.
  323. #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
  324. #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
  325. pub struct CuratorRoleStakeProfile<StakeId, BlockNumber> {
  326. /// Whether participant is staked, and if so, the identifier for this staking in the staking module.
  327. pub stake_id: StakeId,
  328. /// Unstaking period when terminated.
  329. pub termination_unstaking_period: Option<BlockNumber>,
  330. /// Unstaking period when exiting.
  331. pub exit_unstaking_period: Option<BlockNumber>,
  332. }
  333. impl<StakeId: Clone, BlockNumber: Clone> CuratorRoleStakeProfile<StakeId, BlockNumber> {
  334. pub fn new(
  335. stake_id: &StakeId,
  336. termination_unstaking_period: &Option<BlockNumber>,
  337. exit_unstaking_period: &Option<BlockNumber>,
  338. ) -> Self {
  339. Self {
  340. stake_id: (*stake_id).clone(),
  341. termination_unstaking_period: (*termination_unstaking_period).clone(),
  342. exit_unstaking_period: (*exit_unstaking_period).clone(),
  343. }
  344. }
  345. }
  346. /// Working group participant: curator
  347. /// This role can be staked, have reward and be inducted through the hiring module.
  348. #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
  349. #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
  350. pub struct Curator<
  351. AccountId,
  352. RewardRelationshipId,
  353. StakeId,
  354. BlockNumber,
  355. LeadId,
  356. CuratorApplicationId,
  357. PrincipalId,
  358. > {
  359. /// Account used to authenticate in this role,
  360. pub role_account: AccountId,
  361. /// Whether the role has recurring reward, and if so an identifier for this.
  362. pub reward_relationship: Option<RewardRelationshipId>,
  363. /// When set, describes role stake of curator.
  364. pub role_stake_profile: Option<CuratorRoleStakeProfile<StakeId, BlockNumber>>,
  365. /// The stage of this curator in the working group.
  366. pub stage: CuratorRoleStage<BlockNumber>,
  367. /// How the curator was inducted into the working group.
  368. pub induction: CuratorInduction<LeadId, CuratorApplicationId, BlockNumber>,
  369. /// Permissions module principal id
  370. pub principal_id: PrincipalId,
  371. }
  372. impl<
  373. AccountId: Clone,
  374. RewardRelationshipId: Clone,
  375. StakeId: Clone,
  376. BlockNumber: Clone,
  377. LeadId: Clone,
  378. ApplicationId: Clone,
  379. PrincipalId: Clone,
  380. >
  381. Curator<
  382. AccountId,
  383. RewardRelationshipId,
  384. StakeId,
  385. BlockNumber,
  386. LeadId,
  387. ApplicationId,
  388. PrincipalId,
  389. >
  390. {
  391. pub fn new(
  392. role_account: &AccountId,
  393. reward_relationship: &Option<RewardRelationshipId>,
  394. role_stake_profile: &Option<CuratorRoleStakeProfile<StakeId, BlockNumber>>,
  395. stage: &CuratorRoleStage<BlockNumber>,
  396. induction: &CuratorInduction<LeadId, ApplicationId, BlockNumber>,
  397. //can_update_channel_curation_status: bool,
  398. principal_id: &PrincipalId,
  399. ) -> Self {
  400. Curator {
  401. role_account: (*role_account).clone(),
  402. reward_relationship: (*reward_relationship).clone(),
  403. role_stake_profile: (*role_stake_profile).clone(),
  404. stage: (*stage).clone(),
  405. induction: (*induction).clone(),
  406. //can_update_channel_curation_status: can_update_channel_curation_status,
  407. principal_id: (*principal_id).clone(),
  408. }
  409. }
  410. }
  411. /// An opening for a curator role.
  412. #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
  413. #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
  414. pub struct CuratorOpening<OpeningId, BlockNumber, Balance, CuratorApplicationId: core::cmp::Ord> {
  415. /// Identifer for underlying opening in the hiring module.
  416. pub opening_id: OpeningId,
  417. /// Set of identifiers for all curator applications ever added
  418. pub curator_applications: BTreeSet<CuratorApplicationId>,
  419. /// Commitment to policies in opening.
  420. pub policy_commitment: OpeningPolicyCommitment<BlockNumber, Balance>, /*
  421. * Add other stuff here in the future?
  422. * Like default payment terms, privilidges etc.?
  423. * Not obvious that it serves much of a purpose, they are mutable
  424. * after all, they need to be.
  425. * Revisit.
  426. */
  427. }
  428. /// An application for the curator role.
  429. #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
  430. #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
  431. pub struct CuratorApplication<AccountId, CuratorOpeningId, MemberId, ApplicationId> {
  432. /// Account used to authenticate in this role,
  433. pub role_account: AccountId,
  434. /// Opening on which this application applies
  435. pub curator_opening_id: CuratorOpeningId,
  436. /// Member applying
  437. pub member_id: MemberId,
  438. /// Underlying application in hiring module
  439. pub application_id: ApplicationId,
  440. }
  441. impl<AccountId: Clone, CuratorOpeningId: Clone, MemberId: Clone, ApplicationId: Clone>
  442. CuratorApplication<AccountId, CuratorOpeningId, MemberId, ApplicationId>
  443. {
  444. pub fn new(
  445. role_account: &AccountId,
  446. curator_opening_id: &CuratorOpeningId,
  447. member_id: &MemberId,
  448. application_id: &ApplicationId,
  449. ) -> Self {
  450. CuratorApplication {
  451. role_account: (*role_account).clone(),
  452. curator_opening_id: (*curator_opening_id).clone(),
  453. member_id: (*member_id).clone(),
  454. application_id: (*application_id).clone(),
  455. }
  456. }
  457. }
  458. /// Type of .... .
  459. #[derive(Encode, Decode, Debug, Clone, PartialEq, Eq)]
  460. pub enum CurationActor<CuratorId> {
  461. Lead,
  462. Curator(CuratorId),
  463. }
  464. /*
  465. * BEGIN: =========================================================
  466. * Channel stuff
  467. */
  468. /// Type of channel content.
  469. #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
  470. #[derive(Encode, Decode, Debug, Clone, PartialEq)]
  471. pub enum ChannelContentType {
  472. Video,
  473. Music,
  474. Ebook,
  475. }
  476. /// Must be default constructible because it indirectly is a value in a storage map.
  477. /// ***SHOULD NEVER ACTUALLY GET CALLED, IS REQUIRED TO DUE BAD STORAGE MODEL IN SUBSTRATE***
  478. impl Default for ChannelContentType {
  479. fn default() -> Self {
  480. ChannelContentType::Video
  481. }
  482. }
  483. /// Status of channel, as set by the owner.
  484. /// Is only meant to affect visibility, mutation of channel and child content
  485. /// is unaffected on runtime.
  486. #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
  487. #[derive(Encode, Decode, Debug, Clone, PartialEq)]
  488. pub enum ChannelPublicationStatus {
  489. /// Compliant UIs should render.
  490. Public,
  491. /// Compliant UIs should not render it or any child content.
  492. Unlisted,
  493. }
  494. /// Must be default constructible because it indirectly is a value in a storage map.
  495. /// ***SHOULD NEVER ACTUALLY GET CALLED, IS REQUIRED TO DUE BAD STORAGE MODEL IN SUBSTRATE***
  496. impl Default for ChannelPublicationStatus {
  497. fn default() -> Self {
  498. ChannelPublicationStatus::Public
  499. }
  500. }
  501. /// Status of channel, as set by curators.
  502. /// Is only meant to affect visibility currently, but in the future
  503. /// it will also gate publication of new child content,
  504. /// editing properties, revenue flows, etc.
  505. #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
  506. #[derive(Encode, Decode, Debug, Clone, Copy, PartialEq, Eq)]
  507. pub enum ChannelCurationStatus {
  508. Normal,
  509. Censored,
  510. }
  511. /// Must be default constructible because it indirectly is a value in a storage map.
  512. /// ***SHOULD NEVER ACTUALLY GET CALLED, IS REQUIRED TO DUE BAD STORAGE MODEL IN SUBSTRATE***
  513. impl Default for ChannelCurationStatus {
  514. fn default() -> Self {
  515. ChannelCurationStatus::Normal
  516. }
  517. }
  518. pub type OptionalText = Option<Vec<u8>>;
  519. /// A channel for publishing content.
  520. #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
  521. #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
  522. pub struct Channel<MemberId, AccountId, BlockNumber, PrincipalId> {
  523. /// Whether channel has been verified, in the normal Web2.0 platform sense of being authenticated.
  524. pub verified: bool,
  525. /// Unique channel handle that could be used in channel URL.
  526. pub handle: Vec<u8>,
  527. /// Human readable title of channel. Not required to be unique.
  528. pub title: OptionalText,
  529. /// Human readable description of channel purpose and scope.
  530. pub description: OptionalText,
  531. /// URL of a small avatar (logo) image of this channel.
  532. pub avatar: OptionalText,
  533. /// URL of a big background image of this channel.
  534. pub banner: OptionalText,
  535. /// The type of channel.
  536. pub content: ChannelContentType,
  537. /// Member who owns channel.
  538. pub owner: MemberId,
  539. /// Account used to authenticate as owner.
  540. /// Can be updated through membership role key.
  541. pub role_account: AccountId,
  542. /// Publication status of channel.
  543. pub publication_status: ChannelPublicationStatus,
  544. /// Curation status of channel.
  545. pub curation_status: ChannelCurationStatus,
  546. /// When channel was established.
  547. pub created: BlockNumber,
  548. /// Permissions module principal id
  549. pub principal_id: PrincipalId,
  550. }
  551. /*
  552. * END: =========================================================
  553. * Channel stuff
  554. */
  555. /// Permissions module principal
  556. #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
  557. #[derive(Encode, Decode, Debug, Clone, PartialEq)]
  558. pub enum Principal<CuratorId, ChannelId> {
  559. /// Its sloppy to have this here, less safe,
  560. /// but its not worth the ffort to solve.
  561. Lead,
  562. Curator(CuratorId),
  563. ChannelOwner(ChannelId),
  564. }
  565. /// Must be default constructible because it indirectly is a value in a storage map.
  566. /// ***SHOULD NEVER ACTUALLY GET CALLED, IS REQUIRED TO DUE BAD STORAGE MODEL IN SUBSTRATE***
  567. impl<CuratorId, ChannelId> Default for Principal<CuratorId, ChannelId> {
  568. fn default() -> Self {
  569. Principal::Lead
  570. }
  571. }
  572. /// Terms for slashings applied to a given role
  573. #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
  574. #[derive(Encode, Decode, Debug, Clone, PartialEq, Eq)]
  575. pub struct SlashableTerms {
  576. /// Maximum number of slashes.
  577. pub max_count: u16,
  578. /// Maximum percentage points of remaining stake which may be slashed in a single slash.
  579. pub max_percent_pts_per_time: u16,
  580. }
  581. /// Terms for what slashing can be applied in some context
  582. #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
  583. #[derive(Encode, Decode, Debug, Clone, PartialEq, Eq)]
  584. pub enum SlashingTerms {
  585. Unslashable,
  586. Slashable(SlashableTerms),
  587. }
  588. /// Must be default constructible because it indirectly is a value in a storage map.
  589. /// ***SHOULD NEVER ACTUALLY GET CALLED, IS REQUIRED TO DUE BAD STORAGE MODEL IN SUBSTRATE***
  590. impl Default for SlashingTerms {
  591. fn default() -> Self {
  592. Self::Unslashable
  593. }
  594. }
  595. /// A commitment to the set of policy variables relevant to an opening.
  596. /// An applicant can observe this commitment and be secure that the terms
  597. /// of the application process cannot be changed ex-post.
  598. #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
  599. #[derive(Encode, Decode, Debug, Clone, Default, PartialEq, Eq)]
  600. pub struct OpeningPolicyCommitment<BlockNumber, Balance> {
  601. /// Rationing to be used
  602. pub application_rationing_policy: Option<hiring::ApplicationRationingPolicy>,
  603. /// Maximum length of review period of applications
  604. pub max_review_period_length: BlockNumber,
  605. /// Staking policy for application
  606. pub application_staking_policy: Option<hiring::StakingPolicy<Balance, BlockNumber>>,
  607. /// Staking policy for role itself
  608. pub role_staking_policy: Option<hiring::StakingPolicy<Balance, BlockNumber>>,
  609. /// Slashing terms during role, NOT application itself!
  610. pub role_slashing_terms: SlashingTerms,
  611. /// When filling an opening: Unstaking period for application stake of successful applicants
  612. pub fill_opening_successful_applicant_application_stake_unstaking_period: Option<BlockNumber>,
  613. /// When filling an opening:
  614. pub fill_opening_failed_applicant_application_stake_unstaking_period: Option<BlockNumber>,
  615. /// When filling an opening:
  616. pub fill_opening_failed_applicant_role_stake_unstaking_period: Option<BlockNumber>,
  617. /// When terminating a curator:
  618. pub terminate_curator_application_stake_unstaking_period: Option<BlockNumber>,
  619. /// When terminating a curator:
  620. pub terminate_curator_role_stake_unstaking_period: Option<BlockNumber>,
  621. /// When a curator exists: ..
  622. pub exit_curator_role_application_stake_unstaking_period: Option<BlockNumber>,
  623. /// When a curator exists: ..
  624. pub exit_curator_role_stake_unstaking_period: Option<BlockNumber>,
  625. }
  626. /// Represents a possible unstaker in working group.
  627. #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
  628. #[derive(Encode, Decode, Debug, Eq, PartialEq, Clone, PartialOrd)]
  629. pub enum WorkingGroupUnstaker<LeadId, CuratorId> {
  630. ///
  631. Lead(LeadId),
  632. ///
  633. Curator(CuratorId),
  634. }
  635. /// Must be default constructible because it indirectly is a value in a storage map.
  636. /// ***SHOULD NEVER ACTUALLY GET CALLED, IS REQUIRED TO DUE BAD STORAGE MODEL IN SUBSTRATE***
  637. impl<LeadId: Default, CuratorId> Default for WorkingGroupUnstaker<LeadId, CuratorId> {
  638. fn default() -> Self {
  639. Self::Lead(LeadId::default())
  640. }
  641. }
  642. // ======================================================================== //
  643. // Move section below, this out in its own file later //
  644. // ======================================================================== //
  645. pub struct WrappedError<E> {
  646. // can this be made generic, or does that undermine the whole orhpan rule spirit?
  647. pub error: E,
  648. }
  649. /// ....
  650. macro_rules! ensure_on_wrapped_error {
  651. ($call:expr) => {{
  652. { $call }
  653. .map_err(|err| WrappedError { error: err })
  654. .map_err(<&str>::from)
  655. }};
  656. }
  657. // Add macro here to make this
  658. //derive_from_impl(hiring::BeginAcceptingApplicationsError)
  659. //derive_from_impl(hiring::BeginAcceptingApplicationsError)
  660. impl sp_std::convert::From<WrappedError<hiring::BeginAcceptingApplicationsError>> for &str {
  661. fn from(wrapper: WrappedError<hiring::BeginAcceptingApplicationsError>) -> Self {
  662. match wrapper.error {
  663. hiring::BeginAcceptingApplicationsError::OpeningDoesNotExist => {
  664. MSG_ACCEPT_CURATOR_APPLICATIONS_OPENING_DOES_NOT_EXIST
  665. }
  666. hiring::BeginAcceptingApplicationsError::OpeningIsNotInWaitingToBeginStage => {
  667. MSG_ACCEPT_CURATOR_APPLICATIONS_OPENING_IS_NOT_WAITING_TO_BEGIN
  668. }
  669. }
  670. }
  671. }
  672. impl sp_std::convert::From<WrappedError<hiring::AddOpeningError>> for &str {
  673. fn from(wrapper: WrappedError<hiring::AddOpeningError>) -> Self {
  674. match wrapper.error {
  675. hiring::AddOpeningError::OpeningMustActivateInTheFuture => {
  676. MSG_ADD_CURATOR_OPENING_ACTIVATES_IN_THE_PAST
  677. }
  678. hiring::AddOpeningError::StakeAmountLessThanMinimumStakeBalance(purpose) => {
  679. match purpose {
  680. hiring::StakePurpose::Role => {
  681. MSG_ADD_CURATOR_OPENING_ROLE_STAKE_LESS_THAN_MINIMUM
  682. }
  683. hiring::StakePurpose::Application => {
  684. MSG_ADD_CURATOR_OPENING_APPLIICATION_STAKE_LESS_THAN_MINIMUM
  685. }
  686. }
  687. }
  688. hiring::AddOpeningError::ApplicationRationingZeroMaxApplicants => {
  689. MSG_ADD_CURATOR_OPENING_ZERO_MAX_APPLICANT_COUNT
  690. }
  691. hiring::AddOpeningError::StakeAmountCannotBeZero(purpose) => match purpose {
  692. hiring::StakePurpose::Role => MSG_ADD_CURATOR_OPENING_ROLE_STAKE_CANNOT_BE_ZERO,
  693. hiring::StakePurpose::Application => {
  694. MSG_ADD_CURATOR_OPENING_APPLICATION_STAKE_CANNOT_BE_ZERO
  695. }
  696. },
  697. }
  698. }
  699. }
  700. impl sp_std::convert::From<WrappedError<hiring::BeginReviewError>> for &str {
  701. fn from(wrapper: WrappedError<hiring::BeginReviewError>) -> Self {
  702. match wrapper.error {
  703. hiring::BeginReviewError::OpeningDoesNotExist => {
  704. MSG_BEGIN_CURATOR_APPLICANT_REVIEW_OPENING_DOES_NOT_EXIST
  705. }
  706. hiring::BeginReviewError::OpeningNotInAcceptingApplicationsStage => {
  707. MSG_BEGIN_CURATOR_APPLICANT_REVIEW_OPENING_OPENING_IS_NOT_WAITING_TO_BEGIN
  708. }
  709. }
  710. }
  711. }
  712. impl<T: hiring::Trait> sp_std::convert::From<WrappedError<hiring::FillOpeningError<T>>> for &str {
  713. fn from(wrapper: WrappedError<hiring::FillOpeningError<T>>) -> Self {
  714. match wrapper.error {
  715. hiring::FillOpeningError::<T>::OpeningDoesNotExist => MSG_FULL_CURATOR_OPENING_OPENING_DOES_NOT_EXIST,
  716. hiring::FillOpeningError::<T>::OpeningNotInReviewPeriodStage => MSG_FULL_CURATOR_OPENING_OPENING_NOT_IN_REVIEW_PERIOD_STAGE,
  717. hiring::FillOpeningError::<T>::UnstakingPeriodTooShort(
  718. stake_purpose,
  719. outcome_in_filled_opening,
  720. ) => match stake_purpose {
  721. hiring::StakePurpose::Application => match outcome_in_filled_opening {
  722. hiring::ApplicationOutcomeInFilledOpening::Success => MSG_FULL_CURATOR_OPENING_UNSUCCESSFUL_APPLICATION_STAKE_UNSTAKING_PERIOD_TOO_SHORT,
  723. hiring::ApplicationOutcomeInFilledOpening::Failure => MSG_FULL_CURATOR_OPENING_SUCCESSFUL_APPLICATION_STAKE_UNSTAKING_PERIOD_TOO_SHORT
  724. },
  725. hiring::StakePurpose::Role => match outcome_in_filled_opening {
  726. hiring::ApplicationOutcomeInFilledOpening::Success => MSG_FULL_CURATOR_OPENING_SUCCESSFUL_ROLE_STAKE_UNSTAKING_PERIOD_TOO_SHORT,
  727. hiring::ApplicationOutcomeInFilledOpening::Failure => MSG_FULL_CURATOR_OPENING_UNSUCCESSFUL_ROLE_STAKE_UNSTAKING_PERIOD_TOO_SHORT
  728. },
  729. },
  730. hiring::FillOpeningError::<T>::RedundantUnstakingPeriodProvided(
  731. stake_purpose,
  732. outcome_in_filled_opening,
  733. ) => match stake_purpose {
  734. hiring::StakePurpose::Application => match outcome_in_filled_opening {
  735. hiring::ApplicationOutcomeInFilledOpening::Success => MSG_FULL_CURATOR_OPENING_SUCCESSFUL_APPLICATION_STAKE_UNSTAKING_PERIOD_REDUNDANT,
  736. hiring::ApplicationOutcomeInFilledOpening::Failure => MSG_FULL_CURATOR_OPENING_UNSUCCESSFUL_APPLICATION_STAKE_UNSTAKING_PERIOD_REDUNDANT
  737. },
  738. hiring::StakePurpose::Role => match outcome_in_filled_opening {
  739. hiring::ApplicationOutcomeInFilledOpening::Success => MSG_FULL_CURATOR_OPENING_SUCCESSFUL_ROLE_STAKE_UNSTAKING_PERIOD_REDUNDANT,
  740. hiring::ApplicationOutcomeInFilledOpening::Failure => MSG_FULL_CURATOR_OPENING_UNSUCCESSFUL_ROLE_STAKE_UNSTAKING_PERIOD_REDUNDANT
  741. },
  742. },
  743. hiring::FillOpeningError::<T>::ApplicationDoesNotExist(_application_id) => MSG_FULL_CURATOR_OPENING_APPLICATION_DOES_NOT_EXIST,
  744. hiring::FillOpeningError::<T>::ApplicationNotInActiveStage(_application_id) => MSG_FULL_CURATOR_OPENING_APPLICATION_NOT_ACTIVE,
  745. hiring::FillOpeningError::<T>::ApplicationForWrongOpening(_application_id) => MSG_FILL_CURATOR_OPENING_APPLICATION_FOR_WRONG_OPENING,
  746. }
  747. }
  748. }
  749. impl sp_std::convert::From<WrappedError<hiring::DeactivateApplicationError>> for &str {
  750. fn from(wrapper: WrappedError<hiring::DeactivateApplicationError>) -> Self {
  751. match wrapper.error {
  752. hiring::DeactivateApplicationError::ApplicationDoesNotExist => {
  753. MSG_WITHDRAW_CURATOR_APPLICATION_APPLICATION_DOES_NOT_EXIST
  754. }
  755. hiring::DeactivateApplicationError::ApplicationNotActive => {
  756. MSG_WITHDRAW_CURATOR_APPLICATION_APPLICATION_NOT_ACTIVE
  757. }
  758. hiring::DeactivateApplicationError::OpeningNotAcceptingApplications => {
  759. MSG_WITHDRAW_CURATOR_APPLICATION_OPENING_NOT_ACCEPTING_APPLICATIONS
  760. }
  761. hiring::DeactivateApplicationError::UnstakingPeriodTooShort(_stake_purpose) => {
  762. MSG_WITHDRAW_CURATOR_APPLICATION_UNSTAKING_PERIOD_TOO_SHORT
  763. }
  764. hiring::DeactivateApplicationError::RedundantUnstakingPeriodProvided(
  765. _stake_purpose,
  766. ) => MSG_WITHDRAW_CURATOR_APPLICATION_REDUNDANT_UNSTAKING_PERIOD,
  767. }
  768. }
  769. }
  770. impl sp_std::convert::From<WrappedError<membership::ControllerAccountForMemberCheckFailed>>
  771. for &str
  772. {
  773. fn from(wrapper: WrappedError<membership::ControllerAccountForMemberCheckFailed>) -> Self {
  774. match wrapper.error {
  775. membership::ControllerAccountForMemberCheckFailed::NotMember => {
  776. MSG_CREATE_CHANNEL_IS_NOT_MEMBER
  777. }
  778. membership::ControllerAccountForMemberCheckFailed::NotControllerAccount => {
  779. MSG_CREATE_CHANNEL_NOT_CONTROLLER_ACCOUNT
  780. }
  781. }
  782. }
  783. }
  784. impl sp_std::convert::From<WrappedError<hiring::AddApplicationError>> for &str {
  785. fn from(wrapper: WrappedError<hiring::AddApplicationError>) -> Self {
  786. match wrapper.error {
  787. hiring::AddApplicationError::OpeningDoesNotExist => {
  788. MSG_ADD_CURATOR_OPENING_OPENING_DOES_NOT_EXIST
  789. }
  790. hiring::AddApplicationError::StakeProvidedWhenRedundant(_stake_purpose) => {
  791. MSG_ADD_CURATOR_OPENING_STAKE_PROVIDED_WHEN_REDUNDANT
  792. }
  793. hiring::AddApplicationError::StakeMissingWhenRequired(_stake_purpose) => {
  794. MSG_ADD_CURATOR_OPENING_STAKE_MISSING_WHEN_REQUIRED
  795. }
  796. hiring::AddApplicationError::StakeAmountTooLow(_stake_purpose) => {
  797. MSG_ADD_CURATOR_OPENING_STAKE_AMOUNT_TOO_LOW
  798. }
  799. hiring::AddApplicationError::OpeningNotInAcceptingApplicationsStage => {
  800. MSG_ADD_CURATOR_OPENING_OPENING_NOT_IN_ACCEPTING_APPLICATION_STAGE
  801. }
  802. hiring::AddApplicationError::NewApplicationWasCrowdedOut => {
  803. MSG_ADD_CURATOR_OPENING_NEW_APPLICATION_WAS_CROWDED_OUT
  804. }
  805. }
  806. }
  807. }
  808. impl sp_std::convert::From<WrappedError<membership::MemberControllerAccountDidNotSign>> for &str {
  809. fn from(wrapper: WrappedError<membership::MemberControllerAccountDidNotSign>) -> Self {
  810. match wrapper.error {
  811. membership::MemberControllerAccountDidNotSign::UnsignedOrigin => {
  812. MSG_APPLY_ON_CURATOR_OPENING_UNSIGNED_ORIGIN
  813. }
  814. membership::MemberControllerAccountDidNotSign::MemberIdInvalid => MSG_MEMBER_ID_INVALID,
  815. membership::MemberControllerAccountDidNotSign::SignerControllerAccountMismatch => {
  816. MSG_SIGNER_NOT_CONTROLLER_ACCOUNT
  817. }
  818. }
  819. }
  820. }
  821. /// The recurring reward if any to be assigned to an actor when filling in the position.
  822. #[derive(Encode, Decode, Clone, Eq, PartialEq, Debug)]
  823. pub struct RewardPolicy<Balance, BlockNumber> {
  824. amount_per_payout: Balance,
  825. next_payment_at_block: BlockNumber,
  826. payout_interval: Option<BlockNumber>,
  827. }
  828. // ======================================================================== //
  829. // Move section above, this out in its own file later //
  830. // ======================================================================== //
  831. decl_storage! {
  832. trait Store for Module<T: Trait> as ContentWorkingGroup {
  833. /// The mint currently funding the rewards for this module.
  834. pub Mint get(fn mint) : <T as minting::Trait>::MintId;
  835. /// The current lead.
  836. pub CurrentLeadId get(fn current_lead_id) : Option<LeadId<T>>;
  837. /// Maps identifier to corresponding lead.
  838. pub LeadById get(fn lead_by_id): map hasher(blake2_128_concat)
  839. LeadId<T> => Lead<T::AccountId, T::RewardRelationshipId, T::BlockNumber, T::MemberId>;
  840. /// Next identifier for new current lead.
  841. pub NextLeadId get(fn next_lead_id): LeadId<T>;
  842. /// Maps identifeir to curator opening.
  843. pub CuratorOpeningById get(fn curator_opening_by_id) config(): map hasher(blake2_128_concat)
  844. CuratorOpeningId<T> => CuratorOpening<T::OpeningId, T::BlockNumber, BalanceOf<T>, CuratorApplicationId<T>>;
  845. /// Next identifier valuefor new curator opening.
  846. pub NextCuratorOpeningId get(fn next_curator_opening_id) config(): CuratorOpeningId<T>;
  847. /// Maps identifier to curator application on opening.
  848. pub CuratorApplicationById get(fn curator_application_by_id) config(): map hasher(blake2_128_concat)
  849. CuratorApplicationId<T> => CuratorApplication<T::AccountId, CuratorOpeningId<T>, T::MemberId, T::ApplicationId>;
  850. /// Next identifier value for new curator application.
  851. pub NextCuratorApplicationId get(fn next_curator_application_id) config(): CuratorApplicationId<T>;
  852. /// Maps identifier to corresponding channel.
  853. pub ChannelById get(fn channel_by_id) config(): map hasher(blake2_128_concat)
  854. ChannelId<T> => Channel<T::MemberId, T::AccountId, T::BlockNumber, PrincipalId<T>>;
  855. /// Identifier to be used by the next channel introduced.
  856. pub NextChannelId get(fn next_channel_id) config(): ChannelId<T>;
  857. /// Maps (unique) channel handle to the corresponding identifier for the channel.
  858. /// Mapping is required to allow efficient (O(log N)) on-chain verification that a proposed handle is indeed unique
  859. /// at the time it is being proposed.
  860. pub ChannelIdByHandle get(fn channel_id_by_handle) config(): map hasher(blake2_128_concat)
  861. Vec<u8> => ChannelId<T>;
  862. /// Maps identifier to corresponding curator.
  863. pub CuratorById get(fn curator_by_id) config(): map hasher(blake2_128_concat)
  864. CuratorId<T> => Curator<T::AccountId, T::RewardRelationshipId, T::StakeId, T::BlockNumber, LeadId<T>, CuratorApplicationId<T>, PrincipalId<T>>;
  865. /// Next identifier for new curator.
  866. pub NextCuratorId get(fn next_curator_id) config(): CuratorId<T>;
  867. /// Maps identifier to principal.
  868. pub PrincipalById get(fn principal_by_id) config(): map hasher(blake2_128_concat)
  869. PrincipalId<T> => Principal<CuratorId<T>, ChannelId<T>>;
  870. /// Next identifier for
  871. pub NextPrincipalId get(fn next_principal_id) config(): PrincipalId<T>;
  872. /// Whether it is currently possible to create a channel via `create_channel` extrinsic.
  873. pub ChannelCreationEnabled get(fn channel_creation_enabled) config(): bool;
  874. /// Recover curator by the role stake which is currently unstaking.
  875. pub UnstakerByStakeId get(fn unstaker_by_stake_id) config(): map hasher(blake2_128_concat)
  876. StakeId<T> => WorkingGroupUnstaker<LeadId<T>, CuratorId<T>>;
  877. // Vector length input guards
  878. pub ChannelHandleConstraint get(fn channel_handle_constraint) config(): InputValidationLengthConstraint;
  879. pub ChannelTitleConstraint get(fn channel_title_constraint) config(): InputValidationLengthConstraint;
  880. pub ChannelDescriptionConstraint get(fn channel_description_constraint) config(): InputValidationLengthConstraint;
  881. pub ChannelAvatarConstraint get(fn channel_avatar_constraint) config(): InputValidationLengthConstraint;
  882. pub ChannelBannerConstraint get(fn channel_banner_constraint) config(): InputValidationLengthConstraint;
  883. pub OpeningHumanReadableText get(fn opening_human_readable_text) config(): InputValidationLengthConstraint;
  884. pub CuratorApplicationHumanReadableText get(fn curator_application_human_readable_text) config(): InputValidationLengthConstraint;
  885. pub CuratorExitRationaleText get(fn curator_exit_rationale_text) config(): InputValidationLengthConstraint;
  886. }
  887. add_extra_genesis {
  888. config(mint_capacity): minting::BalanceOf<T>;
  889. build(|config: &GenesisConfig<T>| {
  890. // create mint
  891. let mint_id = <minting::Module<T>>::add_mint(config.mint_capacity, None)
  892. .expect("Failed to create a mint for the content working group");
  893. Mint::<T>::put(mint_id);
  894. });
  895. }
  896. }
  897. decl_event! {
  898. pub enum Event<T> where
  899. ChannelId = ChannelId<T>,
  900. LeadId = LeadId<T>,
  901. CuratorOpeningId = CuratorOpeningId<T>,
  902. CuratorApplicationId = CuratorApplicationId<T>,
  903. CuratorId = CuratorId<T>,
  904. CuratorApplicationIdToCuratorIdMap = CuratorApplicationIdToCuratorIdMap<T>,
  905. MintBalanceOf = minting::BalanceOf<T>,
  906. <T as system::Trait>::AccountId,
  907. <T as minting::Trait>::MintId,
  908. {
  909. ChannelCreated(ChannelId),
  910. ChannelOwnershipTransferred(ChannelId),
  911. LeadSet(LeadId),
  912. LeadUnset(LeadId),
  913. CuratorOpeningAdded(CuratorOpeningId),
  914. AcceptedCuratorApplications(CuratorOpeningId),
  915. BeganCuratorApplicationReview(CuratorOpeningId),
  916. CuratorOpeningFilled(CuratorOpeningId, CuratorApplicationIdToCuratorIdMap),
  917. TerminatedCurator(CuratorId),
  918. AppliedOnCuratorOpening(CuratorOpeningId, CuratorApplicationId),
  919. CuratorExited(CuratorId),
  920. CuratorUnstaking(CuratorId),
  921. CuratorApplicationTerminated(CuratorApplicationId),
  922. CuratorApplicationWithdrawn(CuratorApplicationId),
  923. CuratorRoleAccountUpdated(CuratorId, AccountId),
  924. CuratorRewardAccountUpdated(CuratorId, AccountId),
  925. ChannelUpdatedByCurationActor(ChannelId),
  926. ChannelCreationEnabledUpdated(bool),
  927. MintCapacityIncreased(MintId, MintBalanceOf, MintBalanceOf),
  928. MintCapacityDecreased(MintId, MintBalanceOf, MintBalanceOf),
  929. }
  930. }
  931. decl_module! {
  932. pub struct Module<T: Trait> for enum Call where origin: T::Origin {
  933. fn deposit_event() = default;
  934. /*
  935. * Channel management
  936. */
  937. /// Create a new channel.
  938. #[weight = 10_000_000] // TODO: adjust weight
  939. pub fn create_channel(
  940. origin,
  941. owner: T::MemberId,
  942. role_account: T::AccountId,
  943. content: ChannelContentType,
  944. handle: Vec<u8>,
  945. title: OptionalText,
  946. description: OptionalText,
  947. avatar: OptionalText,
  948. banner: OptionalText,
  949. publication_status: ChannelPublicationStatus
  950. ) {
  951. // Ensure that owner member is signed and can authenticate with signer account
  952. ensure_on_wrapped_error!(
  953. membership::Module::<T>::ensure_member_controller_account_signed(
  954. origin,
  955. &owner
  956. )
  957. )?;
  958. // Ensure it is currently possible to create channels (ChannelCreationEnabled).
  959. ensure!(
  960. ChannelCreationEnabled::get(),
  961. MSG_CHANNEL_CREATION_DISABLED
  962. );
  963. // Ensure channel handle is acceptable length
  964. Self::ensure_channel_handle_is_valid(&handle)?;
  965. // Ensure title is acceptable length
  966. Self::ensure_channel_title_is_valid(&title)?;
  967. // Ensure description is acceptable length
  968. Self::ensure_channel_description_is_valid(&description)?;
  969. // Ensure avatar URL is acceptable length
  970. Self::ensure_channel_avatar_is_valid(&avatar)?;
  971. // Ensure banner URL is acceptable length
  972. Self::ensure_channel_banner_is_valid(&banner)?;
  973. //
  974. // == MUTATION SAFE ==
  975. //
  976. // Make and add new principal
  977. let next_channel_id = NextChannelId::<T>::get();
  978. let principal_id = Self::add_new_principal(&Principal::ChannelOwner(next_channel_id));
  979. // Construct channel
  980. let new_channel = Channel {
  981. verified: false,
  982. handle: handle.clone(),
  983. title,
  984. description,
  985. avatar,
  986. banner,
  987. content,
  988. owner,
  989. role_account,
  990. publication_status,
  991. curation_status: ChannelCurationStatus::Normal,
  992. created: <system::Module<T>>::block_number(),
  993. principal_id,
  994. };
  995. // Add channel to ChannelById under id
  996. ChannelById::<T>::insert(next_channel_id, new_channel);
  997. // Add id to ChannelIdByHandle under handle
  998. ChannelIdByHandle::<T>::insert(handle, next_channel_id);
  999. // Increment NextChannelId
  1000. NextChannelId::<T>::mutate(|id| *id += <ChannelId<T> as One>::one());
  1001. // CREDENTIAL STUFF //
  1002. // Trigger event
  1003. Self::deposit_event(RawEvent::ChannelCreated(next_channel_id));
  1004. }
  1005. /// An owner transfers channel ownership to a new owner.
  1006. ///
  1007. /// Notice that working group participants cannot do this.
  1008. /// Notice that censored or unlisted channel may still be transferred.
  1009. /// Notice that transfers are unilateral, so new owner cannot block. This may be problematic: https://github.com/Joystream/substrate-runtime-joystream/issues/95
  1010. #[weight = 10_000_000] // TODO: adjust weight
  1011. pub fn transfer_channel_ownership(origin, channel_id: ChannelId<T>, new_owner: T::MemberId, new_role_account: T::AccountId) {
  1012. // Ensure channel owner has signed
  1013. let channel = Self::ensure_channel_owner_signed(origin, &channel_id)?;
  1014. //
  1015. // == MUTATION SAFE ==
  1016. //
  1017. // Construct new channel with altered properties
  1018. let new_channel = Channel {
  1019. owner: new_owner,
  1020. role_account: new_role_account,
  1021. ..channel
  1022. };
  1023. // Overwrite entry in ChannelById
  1024. ChannelById::<T>::insert(channel_id, new_channel);
  1025. // Trigger event
  1026. Self::deposit_event(RawEvent::ChannelOwnershipTransferred(channel_id));
  1027. }
  1028. /// Channel owner updates some channel properties
  1029. #[weight = 10_000_000] // TODO: adjust weight
  1030. pub fn update_channel_as_owner(
  1031. origin,
  1032. channel_id: ChannelId<T>,
  1033. new_handle: Option<Vec<u8>>,
  1034. new_title: Option<OptionalText>,
  1035. new_description: Option<OptionalText>,
  1036. new_avatar: Option<OptionalText>,
  1037. new_banner: Option<OptionalText>,
  1038. new_publication_status: Option<ChannelPublicationStatus>
  1039. ) {
  1040. // Ensure channel owner has signed
  1041. Self::ensure_channel_owner_signed(origin, &channel_id)?;
  1042. // If set, ensure handle is acceptable length
  1043. if let Some(ref handle) = new_handle {
  1044. Self::ensure_channel_handle_is_valid(handle)?;
  1045. }
  1046. // If set, ensure title is acceptable length
  1047. if let Some(ref title) = new_title {
  1048. Self::ensure_channel_title_is_valid(title)?;
  1049. }
  1050. // If set, ensure description is acceptable length
  1051. if let Some(ref description) = new_description {
  1052. Self::ensure_channel_description_is_valid(description)?;
  1053. }
  1054. // If set, ensure avatar image URL is acceptable length
  1055. if let Some(ref avatar) = new_avatar {
  1056. Self::ensure_channel_avatar_is_valid(avatar)?;
  1057. }
  1058. // If set, ensure banner image URL is acceptable length
  1059. if let Some(ref banner) = new_banner {
  1060. Self::ensure_channel_banner_is_valid(banner)?;
  1061. }
  1062. //
  1063. // == MUTATION SAFE ==
  1064. //
  1065. Self::update_channel(
  1066. &channel_id,
  1067. None, // verified
  1068. &new_handle,
  1069. &new_title,
  1070. &new_description,
  1071. &new_avatar,
  1072. &new_banner,
  1073. new_publication_status,
  1074. None // curation_status
  1075. );
  1076. }
  1077. /// Update channel as a curation actor
  1078. #[weight = 10_000_000] // TODO: adjust weight
  1079. pub fn update_channel_as_curation_actor(
  1080. origin,
  1081. curation_actor: CurationActor<CuratorId<T>>,
  1082. channel_id: ChannelId<T>,
  1083. new_verified: Option<bool>,
  1084. new_curation_status: Option<ChannelCurationStatus>
  1085. ) {
  1086. // Ensure curation actor signed
  1087. Self::ensure_curation_actor_signed(origin, &curation_actor)?;
  1088. //
  1089. // == MUTATION SAFE ==
  1090. //
  1091. Self::update_channel(
  1092. &channel_id,
  1093. new_verified,
  1094. &None, // handle
  1095. &None, // title
  1096. &None, // description,
  1097. &None, // avatar
  1098. &None, // banner
  1099. None, // publication_status
  1100. new_curation_status
  1101. );
  1102. }
  1103. /// Add an opening for a curator role.
  1104. #[weight = 10_000_000] // TODO: adjust weight
  1105. pub fn add_curator_opening(origin, activate_at: hiring::ActivateOpeningAt<T::BlockNumber>, commitment: OpeningPolicyCommitment<T::BlockNumber, BalanceOf<T>>, human_readable_text: Vec<u8>) {
  1106. // Ensure lead is set and is origin signer
  1107. Self::ensure_origin_is_set_lead(origin)?;
  1108. // Ensure human radable text is valid
  1109. Self::ensure_opening_human_readable_text_is_valid(&human_readable_text)?;
  1110. // Add opening
  1111. // NB: This call can in principle fail, because the staking policies
  1112. // may not respect the minimum currency requirement.
  1113. let policy_commitment = commitment.clone();
  1114. let opening_id = ensure_on_wrapped_error!(
  1115. hiring::Module::<T>::add_opening(
  1116. activate_at,
  1117. commitment.max_review_period_length,
  1118. commitment.application_rationing_policy,
  1119. commitment.application_staking_policy,
  1120. commitment.role_staking_policy,
  1121. human_readable_text,
  1122. ))?;
  1123. //
  1124. // == MUTATION SAFE ==
  1125. //
  1126. let new_curator_opening_id = NextCuratorOpeningId::<T>::get();
  1127. // Create and add curator opening.
  1128. let new_opening_by_id = CuratorOpening {
  1129. opening_id,
  1130. curator_applications: BTreeSet::new(),
  1131. policy_commitment,
  1132. };
  1133. CuratorOpeningById::<T>::insert(new_curator_opening_id, new_opening_by_id);
  1134. // Update NextCuratorOpeningId
  1135. NextCuratorOpeningId::<T>::mutate(|id| *id += <CuratorOpeningId<T> as One>::one());
  1136. // Trigger event
  1137. Self::deposit_event(RawEvent::CuratorOpeningAdded(new_curator_opening_id));
  1138. }
  1139. /// Begin accepting curator applications to an opening that is active.
  1140. #[weight = 10_000_000] // TODO: adjust weight
  1141. pub fn accept_curator_applications(origin, curator_opening_id: CuratorOpeningId<T>) {
  1142. // Ensure lead is set and is origin signer
  1143. Self::ensure_origin_is_set_lead(origin)?;
  1144. // Ensure opening exists in this working group
  1145. // NB: Even though call to hiring modul will have implicit check for
  1146. // existence of opening as well, this check is to make sure that the opening is for
  1147. // this working group, not something else.
  1148. let (curator_opening, _opening) = Self::ensure_curator_opening_exists(&curator_opening_id)?;
  1149. // Attempt to begin accepting applicationsa
  1150. // NB: Combined ensure check and mutation in hiring module
  1151. ensure_on_wrapped_error!(
  1152. hiring::Module::<T>::begin_accepting_applications(curator_opening.opening_id)
  1153. )?;
  1154. //
  1155. // == MUTATION SAFE ==
  1156. //
  1157. // Trigger event
  1158. Self::deposit_event(RawEvent::AcceptedCuratorApplications(curator_opening_id));
  1159. }
  1160. /// Begin reviewing, and therefore not accepting new applications.
  1161. #[weight = 10_000_000] // TODO: adjust weight
  1162. pub fn begin_curator_applicant_review(origin, curator_opening_id: CuratorOpeningId<T>) {
  1163. // Ensure lead is set and is origin signer
  1164. let (_lead_id, _lead) = Self::ensure_origin_is_set_lead(origin)?;
  1165. // Ensure opening exists
  1166. // NB: Even though call to hiring modul will have implicit check for
  1167. // existence of opening as well, this check is to make sure that the opening is for
  1168. // this working group, not something else.
  1169. let (curator_opening, _opening) = Self::ensure_curator_opening_exists(&curator_opening_id)?;
  1170. // Attempt to begin review of applications
  1171. // NB: Combined ensure check and mutation in hiring module
  1172. ensure_on_wrapped_error!(
  1173. hiring::Module::<T>::begin_review(curator_opening.opening_id)
  1174. )?;
  1175. //
  1176. // == MUTATION SAFE ==
  1177. //
  1178. // Trigger event
  1179. Self::deposit_event(RawEvent::BeganCuratorApplicationReview(curator_opening_id));
  1180. }
  1181. /// Fill opening for curator
  1182. #[weight = 10_000_000] // TODO: adjust weight
  1183. pub fn fill_curator_opening(
  1184. origin,
  1185. curator_opening_id: CuratorOpeningId<T>,
  1186. successful_curator_application_ids: CuratorApplicationIdSet<T>,
  1187. reward_policy: Option<RewardPolicy<minting::BalanceOf<T>, T::BlockNumber>>
  1188. ) {
  1189. // Ensure lead is set and is origin signer
  1190. let (lead_id, _lead) = Self::ensure_origin_is_set_lead(origin)?;
  1191. // Ensure curator opening exists
  1192. let (curator_opening, _) = Self::ensure_curator_opening_exists(&curator_opening_id)?;
  1193. // Ensure a mint exists if lead is providing a reward for positions being filled
  1194. let create_reward_settings = if let Some(policy) = reward_policy {
  1195. // A reward will need to be created so ensure our configured mint exists
  1196. let mint_id = Self::mint();
  1197. // Technically this is a bug-check and should not be here.
  1198. ensure!(<minting::Mints<T>>::contains_key(mint_id), MSG_FILL_CURATOR_OPENING_MINT_DOES_NOT_EXIST);
  1199. // Make sure valid parameters are selected for next payment at block number
  1200. ensure!(policy.next_payment_at_block > <system::Module<T>>::block_number(), MSG_FILL_CURATOR_OPENING_INVALID_NEXT_PAYMENT_BLOCK);
  1201. // The verified reward settings to use
  1202. Some((mint_id, policy))
  1203. } else {
  1204. None
  1205. };
  1206. // Make iterator over successful curator application
  1207. let successful_iter = successful_curator_application_ids
  1208. .iter()
  1209. // recover curator application from id
  1210. .map(|curator_application_id| { Self::ensure_curator_application_exists(curator_application_id)})
  1211. // remove Err cases, i.e. non-existing applications
  1212. .filter_map(|result| result.ok());
  1213. // Count number of successful curators provided
  1214. let num_provided_successful_curator_application_ids = successful_curator_application_ids.len();
  1215. // Ensure all curator applications exist
  1216. let number_of_successful_applications = successful_iter
  1217. .clone()
  1218. .count();
  1219. ensure!(
  1220. number_of_successful_applications == num_provided_successful_curator_application_ids,
  1221. MSG_SUCCESSFUL_CURATOR_APPLICATION_DOES_NOT_EXIST
  1222. );
  1223. // Attempt to fill opening
  1224. let successful_application_ids = successful_iter
  1225. .clone()
  1226. .map(|(successful_curator_application, _, _)| successful_curator_application.application_id)
  1227. .collect::<BTreeSet<_>>();
  1228. // NB: Combined ensure check and mutation in hiring module
  1229. ensure_on_wrapped_error!(
  1230. hiring::Module::<T>::fill_opening(
  1231. curator_opening.opening_id,
  1232. successful_application_ids,
  1233. curator_opening.policy_commitment.fill_opening_successful_applicant_application_stake_unstaking_period,
  1234. curator_opening.policy_commitment.fill_opening_failed_applicant_application_stake_unstaking_period,
  1235. curator_opening.policy_commitment.fill_opening_failed_applicant_role_stake_unstaking_period
  1236. )
  1237. )?;
  1238. //
  1239. // == MUTATION SAFE ==
  1240. //
  1241. let current_block = <system::Module<T>>::block_number();
  1242. // For each successful application
  1243. // - create and hold on to curator
  1244. // - register role with membership module
  1245. let mut curator_application_id_to_curator_id = BTreeMap::new();
  1246. successful_iter
  1247. .clone()
  1248. .for_each(|(successful_curator_application, id, _)| {
  1249. // Create a reward relationship
  1250. let reward_relationship = if let Some((mint_id, checked_policy)) = create_reward_settings.clone() {
  1251. // Create a new recipient for the new relationship
  1252. let recipient = <recurringrewards::Module<T>>::add_recipient();
  1253. // member must exist, since it was checked that it can enter the role
  1254. let membership = <membership::Module<T>>::membership(successful_curator_application.member_id);
  1255. // rewards are deposited in the member's root account
  1256. let reward_destination_account = membership.root_account;
  1257. // values have been checked so this should not fail!
  1258. let relationship_id = <recurringrewards::Module<T>>::add_reward_relationship(
  1259. mint_id,
  1260. recipient,
  1261. reward_destination_account,
  1262. checked_policy.amount_per_payout,
  1263. checked_policy.next_payment_at_block,
  1264. checked_policy.payout_interval,
  1265. ).expect("Failed to create reward relationship!");
  1266. Some(relationship_id)
  1267. } else {
  1268. None
  1269. };
  1270. // Get possible stake for role
  1271. let application = hiring::ApplicationById::<T>::get(successful_curator_application.application_id);
  1272. // Staking profile for curator
  1273. let stake_profile =
  1274. if let Some(ref stake_id) = application.active_role_staking_id {
  1275. Some(
  1276. CuratorRoleStakeProfile::new(
  1277. stake_id,
  1278. &curator_opening.policy_commitment.terminate_curator_role_stake_unstaking_period,
  1279. &curator_opening.policy_commitment.exit_curator_role_stake_unstaking_period
  1280. )
  1281. )
  1282. } else {
  1283. None
  1284. };
  1285. // Get curator id
  1286. let new_curator_id = NextCuratorId::<T>::get();
  1287. // Make and add new principal
  1288. let principal_id = Self::add_new_principal(&Principal::Curator(new_curator_id));
  1289. // Construct curator
  1290. let curator = Curator::new(
  1291. &(successful_curator_application.role_account),
  1292. &reward_relationship,
  1293. &stake_profile,
  1294. &CuratorRoleStage::Active,
  1295. &CuratorInduction::new(&lead_id, &id, &current_block),
  1296. //false,
  1297. &principal_id
  1298. );
  1299. // Store curator
  1300. CuratorById::<T>::insert(new_curator_id, curator);
  1301. // Update next curator id
  1302. NextCuratorId::<T>::mutate(|id| *id += <CuratorId<T> as One>::one());
  1303. curator_application_id_to_curator_id.insert(id, new_curator_id);
  1304. });
  1305. // Trigger event
  1306. Self::deposit_event(RawEvent::CuratorOpeningFilled(curator_opening_id, curator_application_id_to_curator_id));
  1307. }
  1308. #[weight = 10_000_000] // TODO: adjust weight
  1309. pub fn withdraw_curator_application(
  1310. origin,
  1311. curator_application_id: CuratorApplicationId<T>
  1312. ) {
  1313. // Ensuring curator application actually exists
  1314. let (curator_application, _, curator_opening) = Self::ensure_curator_application_exists(&curator_application_id)?;
  1315. // Ensure that it is signed
  1316. let signer_account = ensure_signed(origin)?;
  1317. // Ensure that signer is applicant role account
  1318. ensure!(
  1319. signer_account == curator_application.role_account,
  1320. MSG_ORIGIN_IS_NOT_APPLICANT
  1321. );
  1322. // Attempt to deactivate application
  1323. // NB: Combined ensure check and mutation in hiring module
  1324. ensure_on_wrapped_error!(
  1325. hiring::Module::<T>::deactive_application(
  1326. curator_application.application_id,
  1327. curator_opening.policy_commitment.exit_curator_role_application_stake_unstaking_period,
  1328. curator_opening.policy_commitment.exit_curator_role_stake_unstaking_period
  1329. )
  1330. )?;
  1331. //
  1332. // == MUTATION SAFE ==
  1333. //
  1334. // Trigger event
  1335. Self::deposit_event(RawEvent::CuratorApplicationWithdrawn(curator_application_id));
  1336. }
  1337. /// Lead terminate curator application
  1338. #[weight = 10_000_000] // TODO: adjust weight
  1339. pub fn terminate_curator_application(
  1340. origin,
  1341. curator_application_id: CuratorApplicationId<T>
  1342. ) {
  1343. // Ensure lead is set and is origin signer
  1344. Self::ensure_origin_is_set_lead(origin)?;
  1345. // Ensuring curator application actually exists
  1346. let (curator_application, _, curator_opening) = Self::ensure_curator_application_exists(&curator_application_id)?;
  1347. // Attempt to deactivate application
  1348. // NB: Combined ensure check and mutation in hiring module
  1349. ensure_on_wrapped_error!(
  1350. hiring::Module::<T>::deactive_application(
  1351. curator_application.application_id,
  1352. curator_opening.policy_commitment.terminate_curator_application_stake_unstaking_period,
  1353. curator_opening.policy_commitment.terminate_curator_role_stake_unstaking_period
  1354. )
  1355. )?;
  1356. //
  1357. // == MUTATION SAFE ==
  1358. //
  1359. // Trigger event
  1360. Self::deposit_event(RawEvent::CuratorApplicationTerminated(curator_application_id));
  1361. }
  1362. /// Apply on a curator opening.
  1363. #[weight = 10_000_000] // TODO: adjust weight
  1364. pub fn apply_on_curator_opening(
  1365. origin,
  1366. member_id: T::MemberId,
  1367. curator_opening_id: CuratorOpeningId<T>,
  1368. role_account: T::AccountId,
  1369. opt_role_stake_balance: Option<BalanceOf<T>>,
  1370. opt_application_stake_balance: Option<BalanceOf<T>>,
  1371. human_readable_text: Vec<u8>
  1372. ) {
  1373. // Ensure origin which will server as the source account for staked funds is signed
  1374. let source_account = ensure_signed(origin)?;
  1375. // In absense of a more general key delegation system which allows an account with some funds to
  1376. // grant another account permission to stake from its funds, the origin of this call must have the funds
  1377. // and cannot specify another arbitrary account as the source account.
  1378. // Ensure the source_account is either the controller or root account of member with given id
  1379. ensure!(
  1380. membership::Module::<T>::ensure_member_controller_account(&source_account, &member_id).is_ok() ||
  1381. membership::Module::<T>::ensure_member_root_account(&source_account, &member_id).is_ok(),
  1382. MSG_ORIGIN_IS_NIETHER_MEMBER_CONTROLLER_OR_ROOT
  1383. );
  1384. // Ensure curator opening exists
  1385. let (curator_opening, _opening) = Self::ensure_curator_opening_exists(&curator_opening_id)?;
  1386. // Ensure that there is sufficient balance to cover stake proposed
  1387. Self::ensure_can_make_stake_imbalance(
  1388. vec![&opt_role_stake_balance, &opt_application_stake_balance],
  1389. &source_account)
  1390. .map_err(|_err| MSG_INSUFFICIENT_BALANCE_TO_APPLY)?;
  1391. // Ensure application text is valid
  1392. Self::ensure_curator_application_text_is_valid(&human_readable_text)?;
  1393. // Ensure application can actually be added
  1394. ensure_on_wrapped_error!(
  1395. hiring::Module::<T>::ensure_can_add_application(curator_opening.opening_id, opt_role_stake_balance, opt_application_stake_balance)
  1396. )?;
  1397. // Ensure member does not have an active application to this opening
  1398. Self::ensure_member_has_no_active_application_on_opening(
  1399. curator_opening.curator_applications,
  1400. member_id
  1401. )?;
  1402. //
  1403. // == MUTATION SAFE ==
  1404. //
  1405. // Make imbalances for staking
  1406. let opt_role_stake_imbalance = Self::make_stake_opt_imbalance(&opt_role_stake_balance, &source_account);
  1407. let opt_application_stake_imbalance = Self::make_stake_opt_imbalance(&opt_application_stake_balance, &source_account);
  1408. // Call hiring module to add application
  1409. let add_application_result = hiring::Module::<T>::add_application(
  1410. curator_opening.opening_id,
  1411. opt_role_stake_imbalance,
  1412. opt_application_stake_imbalance,
  1413. human_readable_text
  1414. );
  1415. // Has to hold
  1416. assert!(add_application_result.is_ok());
  1417. let application_id = add_application_result.unwrap().application_id_added;
  1418. // Get id of new curator application
  1419. let new_curator_application_id = NextCuratorApplicationId::<T>::get();
  1420. // Make curator application
  1421. let curator_application = CuratorApplication::new(&role_account, &curator_opening_id, &member_id, &application_id);
  1422. // Store application
  1423. CuratorApplicationById::<T>::insert(new_curator_application_id, curator_application);
  1424. // Update next curator application identifier value
  1425. NextCuratorApplicationId::<T>::mutate(|id| *id += <CuratorApplicationId<T> as One>::one());
  1426. // Add application to set of application in curator opening
  1427. CuratorOpeningById::<T>::mutate(curator_opening_id, |curator_opening| {
  1428. curator_opening.curator_applications.insert(new_curator_application_id);
  1429. });
  1430. // Trigger event
  1431. Self::deposit_event(RawEvent::AppliedOnCuratorOpening(curator_opening_id, new_curator_application_id));
  1432. }
  1433. /// An active curator can update the associated role account.
  1434. #[weight = 10_000_000] // TODO: adjust weight
  1435. pub fn update_curator_role_account(
  1436. origin,
  1437. member_id: T::MemberId,
  1438. curator_id: CuratorId<T>,
  1439. new_role_account: T::AccountId
  1440. ) {
  1441. // Ensure that origin is signed by member with given id.
  1442. ensure_on_wrapped_error!(
  1443. membership::Module::<T>::ensure_member_controller_account_signed(origin, &member_id)
  1444. )?;
  1445. //
  1446. // == MUTATION SAFE ==
  1447. //
  1448. // Update role account
  1449. CuratorById::<T>::mutate(curator_id, |curator| {
  1450. curator.role_account = new_role_account.clone()
  1451. });
  1452. // Trigger event
  1453. Self::deposit_event(RawEvent::CuratorRoleAccountUpdated(curator_id, new_role_account));
  1454. }
  1455. /// An active curator can update the reward account associated
  1456. /// with a set reward relationship.
  1457. #[weight = 10_000_000] // TODO: adjust weight
  1458. pub fn update_curator_reward_account(
  1459. origin,
  1460. curator_id: CuratorId<T>,
  1461. new_reward_account: T::AccountId
  1462. ) {
  1463. // Ensure there is a signer which matches role account of curator corresponding to provided id.
  1464. let curator = Self::ensure_active_curator_signed(origin, &curator_id)?;
  1465. // Ensure the curator actually has a recurring reward
  1466. let relationship_id = Self::ensure_curator_has_recurring_reward(&curator)?;
  1467. //
  1468. // == MUTATION SAFE ==
  1469. //
  1470. // Update, only, reward account.
  1471. recurringrewards::Module::<T>::set_reward_relationship(
  1472. relationship_id,
  1473. Some(new_reward_account.clone()), // new_account
  1474. None, // new_payout
  1475. None, //new_next_payment_at
  1476. None //new_payout_interval
  1477. )
  1478. .expect("Must be set, since curator has recurring reward");
  1479. // Trigger event
  1480. Self::deposit_event(RawEvent::CuratorRewardAccountUpdated(curator_id, new_reward_account));
  1481. }
  1482. /// An active curator leaves role
  1483. #[weight = 10_000_000] // TODO: adjust weight
  1484. pub fn leave_curator_role(
  1485. origin,
  1486. curator_id: CuratorId<T>,
  1487. rationale_text: Vec<u8>
  1488. ) {
  1489. // Ensure there is a signer which matches role account of curator corresponding to provided id.
  1490. let active_curator = Self::ensure_active_curator_signed(origin, &curator_id)?;
  1491. //
  1492. // == MUTATION SAFE ==
  1493. //
  1494. Self::deactivate_curator(
  1495. &curator_id,
  1496. &active_curator,
  1497. &CuratorExitInitiationOrigin::Curator,
  1498. &rationale_text
  1499. );
  1500. }
  1501. /// Lead can terminate and active curator
  1502. #[weight = 10_000_000] // TODO: adjust weight
  1503. pub fn terminate_curator_role(
  1504. origin,
  1505. curator_id: CuratorId<T>,
  1506. rationale_text: Vec<u8>
  1507. ) {
  1508. // Ensure lead is set and is origin signer
  1509. Self::ensure_origin_is_set_lead(origin)?;
  1510. // Ensuring curator actually exists and is active
  1511. let curator = Self::ensure_active_curator_exists(&curator_id)?;
  1512. // Ensure rationale text is valid
  1513. Self::ensure_curator_exit_rationale_text_is_valid(&rationale_text)?;
  1514. //
  1515. // == MUTATION SAFE ==
  1516. //
  1517. Self::deactivate_curator(
  1518. &curator_id,
  1519. &curator,
  1520. &CuratorExitInitiationOrigin::Lead,
  1521. &rationale_text
  1522. );
  1523. }
  1524. /// Replace the current lead. First unsets the active lead if there is one.
  1525. /// If a value is provided for new_lead it will then set that new lead.
  1526. /// It is responsibility of the caller to ensure the new lead can be set
  1527. /// to avoid the lead role being vacant at the end of the call.
  1528. #[weight = 10_000_000] // TODO: adjust weight
  1529. pub fn replace_lead(origin, new_lead: Option<(T::MemberId, T::AccountId)>) {
  1530. // Ensure root is origin
  1531. ensure_root(origin)?;
  1532. // Unset current lead first
  1533. if Self::ensure_lead_is_set().is_ok() {
  1534. Self::unset_lead()?;
  1535. }
  1536. // Try to set new lead
  1537. if let Some((member_id, role_account)) = new_lead {
  1538. Self::set_lead(member_id, role_account)?;
  1539. }
  1540. }
  1541. /// Add an opening for a curator role.
  1542. #[weight = 10_000_000] // TODO: adjust weight
  1543. pub fn set_channel_creation_enabled(origin, enabled: bool) {
  1544. // Ensure lead is set and is origin signer
  1545. Self::ensure_origin_is_set_lead(origin)?;
  1546. //
  1547. // == MUTATION SAFE ==
  1548. //
  1549. // Update storage value
  1550. ChannelCreationEnabled::put(enabled);
  1551. // Trigger event
  1552. Self::deposit_event(RawEvent::ChannelCreationEnabledUpdated(enabled));
  1553. }
  1554. /// Add to capacity of current acive mint.
  1555. /// This may be deprecated in the future, since set_mint_capacity is sufficient to
  1556. /// both increase and decrease capacity. Although when considering that it may be executed
  1557. /// by a proposal, given the temporal delay in approving a proposal, it might be more suitable
  1558. /// than set_mint_capacity?
  1559. #[weight = 10_000_000] // TODO: adjust weight
  1560. pub fn increase_mint_capacity(
  1561. origin,
  1562. additional_capacity: minting::BalanceOf<T>
  1563. ) {
  1564. ensure_root(origin)?;
  1565. let mint_id = Self::mint();
  1566. let mint = <minting::Module<T>>::mints(mint_id); // must exist
  1567. let new_capacity = mint.capacity() + additional_capacity;
  1568. <minting::Module<T>>::set_mint_capacity(mint_id, new_capacity).map_err(<&str>::from)?;
  1569. Self::deposit_event(RawEvent::MintCapacityIncreased(
  1570. mint_id, additional_capacity, new_capacity
  1571. ));
  1572. }
  1573. /// Sets the capacity of the current active mint
  1574. #[weight = 10_000_000] // TODO: adjust weight
  1575. pub fn set_mint_capacity(
  1576. origin,
  1577. new_capacity: minting::BalanceOf<T>
  1578. ) {
  1579. ensure_root(origin)?;
  1580. ensure!(<Mint<T>>::exists(), MSG_FILL_CURATOR_OPENING_MINT_DOES_NOT_EXIST);
  1581. let mint_id = Self::mint();
  1582. // Mint must exist - it is set at genesis
  1583. let mint = <minting::Module<T>>::mints(mint_id);
  1584. let current_capacity = mint.capacity();
  1585. if new_capacity != current_capacity {
  1586. // Cannot fail if mint exists
  1587. <minting::Module<T>>::set_mint_capacity(mint_id, new_capacity).map_err(<&str>::from)?;
  1588. if new_capacity > current_capacity {
  1589. Self::deposit_event(RawEvent::MintCapacityIncreased(
  1590. mint_id, new_capacity - current_capacity, new_capacity
  1591. ));
  1592. } else {
  1593. Self::deposit_event(RawEvent::MintCapacityDecreased(
  1594. mint_id, current_capacity - new_capacity, new_capacity
  1595. ));
  1596. }
  1597. }
  1598. }
  1599. }
  1600. }
  1601. impl<T: Trait> versioned_store_permissions::CredentialChecker<T> for Module<T> {
  1602. fn account_has_credential(account: &T::AccountId, id: PrincipalId<T>) -> bool {
  1603. // Check that principal exists
  1604. if !PrincipalById::<T>::contains_key(&id) {
  1605. return false;
  1606. }
  1607. // Get principal
  1608. let principal = PrincipalById::<T>::get(&id);
  1609. // Get possible
  1610. let opt_prinicipal_account = match principal {
  1611. Principal::Lead => {
  1612. // Try to get lead
  1613. match Self::ensure_lead_is_set() {
  1614. Ok((_, lead)) => Some(lead.role_account),
  1615. Err(_) => None,
  1616. }
  1617. }
  1618. Principal::Curator(curator_id) => Some(
  1619. Self::ensure_curator_exists(&curator_id)
  1620. .expect("Curator must exist")
  1621. .role_account,
  1622. ),
  1623. Principal::ChannelOwner(channel_id) => Some(
  1624. Self::ensure_channel_id_is_valid(&channel_id)
  1625. .expect("Channel must exist")
  1626. .role_account,
  1627. ),
  1628. };
  1629. // Compare, possibly set, principal account with the given account
  1630. match opt_prinicipal_account {
  1631. Some(principal_account) => *account == principal_account,
  1632. None => false,
  1633. }
  1634. }
  1635. }
  1636. impl<T: Trait> Module<T> {
  1637. /// Introduce a lead when one is not currently set.
  1638. fn set_lead(member: T::MemberId, role_account: T::AccountId) -> DispatchResult {
  1639. // Ensure there is no current lead
  1640. ensure!(
  1641. <CurrentLeadId<T>>::get().is_none(),
  1642. MSG_CURRENT_LEAD_ALREADY_SET
  1643. );
  1644. let new_lead_id = <NextLeadId<T>>::get();
  1645. //
  1646. // == MUTATION SAFE ==
  1647. //
  1648. // Construct lead
  1649. let new_lead = Lead {
  1650. member_id: member,
  1651. role_account,
  1652. reward_relationship: None,
  1653. inducted: <system::Module<T>>::block_number(),
  1654. stage: LeadRoleState::Active,
  1655. };
  1656. // Store lead
  1657. <LeadById<T>>::insert(new_lead_id, new_lead);
  1658. // Update current lead
  1659. <CurrentLeadId<T>>::put(new_lead_id);
  1660. // Update next lead counter
  1661. <NextLeadId<T>>::mutate(|id| *id += <LeadId<T> as One>::one());
  1662. // Trigger event
  1663. Self::deposit_event(RawEvent::LeadSet(new_lead_id));
  1664. Ok(())
  1665. }
  1666. /// Evict the currently set lead
  1667. fn unset_lead() -> DispatchResult {
  1668. // Ensure there is a lead set
  1669. let (lead_id, lead) = Self::ensure_lead_is_set()?;
  1670. //
  1671. // == MUTATION SAFE ==
  1672. //
  1673. // Update lead stage as exited
  1674. let current_block = <system::Module<T>>::block_number();
  1675. let new_lead = Lead {
  1676. stage: LeadRoleState::Exited(ExitedLeadRole {
  1677. initiated_at_block_number: current_block,
  1678. }),
  1679. ..lead
  1680. };
  1681. <LeadById<T>>::insert(lead_id, new_lead);
  1682. // Update current lead
  1683. <CurrentLeadId<T>>::take();
  1684. // Trigger event
  1685. Self::deposit_event(RawEvent::LeadUnset(lead_id));
  1686. Ok(())
  1687. }
  1688. fn ensure_member_has_no_active_application_on_opening(
  1689. curator_applications: CuratorApplicationIdSet<T>,
  1690. member_id: T::MemberId,
  1691. ) -> Result<(), &'static str> {
  1692. for curator_application_id in curator_applications {
  1693. let curator_application = CuratorApplicationById::<T>::get(curator_application_id);
  1694. // Look for application by the member for the opening
  1695. if curator_application.member_id != member_id {
  1696. continue;
  1697. }
  1698. // Get application details
  1699. let application = <hiring::ApplicationById<T>>::get(curator_application.application_id);
  1700. // Return error if application is in active stage
  1701. if application.stage == hiring::ApplicationStage::Active {
  1702. return Err(MSG_MEMBER_HAS_ACTIVE_APPLICATION_ON_OPENING);
  1703. }
  1704. }
  1705. // Member does not have any active applications to the opening
  1706. Ok(())
  1707. }
  1708. // TODO: convert InputConstraint ensurer routines into macroes
  1709. fn ensure_channel_handle_is_valid(handle: &[u8]) -> DispatchResult {
  1710. ChannelHandleConstraint::get().ensure_valid(
  1711. handle.len(),
  1712. MSG_CHANNEL_HANDLE_TOO_SHORT,
  1713. MSG_CHANNEL_HANDLE_TOO_LONG,
  1714. )?;
  1715. // Has to not already be occupied
  1716. ensure!(
  1717. !ChannelIdByHandle::<T>::contains_key(handle),
  1718. MSG_CHANNEL_HANDLE_ALREADY_TAKEN
  1719. );
  1720. Ok(())
  1721. }
  1722. fn ensure_channel_title_is_valid(text_opt: &OptionalText) -> DispatchResult {
  1723. if let Some(text) = text_opt {
  1724. ChannelTitleConstraint::get().ensure_valid(
  1725. text.len(),
  1726. MSG_CHANNEL_TITLE_TOO_SHORT,
  1727. MSG_CHANNEL_TITLE_TOO_LONG,
  1728. )
  1729. } else {
  1730. Ok(())
  1731. }
  1732. }
  1733. fn ensure_channel_description_is_valid(text_opt: &OptionalText) -> DispatchResult {
  1734. if let Some(text) = text_opt {
  1735. ChannelDescriptionConstraint::get().ensure_valid(
  1736. text.len(),
  1737. MSG_CHANNEL_DESCRIPTION_TOO_SHORT,
  1738. MSG_CHANNEL_DESCRIPTION_TOO_LONG,
  1739. )
  1740. } else {
  1741. Ok(())
  1742. }
  1743. }
  1744. fn ensure_channel_avatar_is_valid(text_opt: &OptionalText) -> DispatchResult {
  1745. if let Some(text) = text_opt {
  1746. ChannelAvatarConstraint::get().ensure_valid(
  1747. text.len(),
  1748. MSG_CHANNEL_AVATAR_TOO_SHORT,
  1749. MSG_CHANNEL_AVATAR_TOO_LONG,
  1750. )
  1751. } else {
  1752. Ok(())
  1753. }
  1754. }
  1755. fn ensure_channel_banner_is_valid(text_opt: &OptionalText) -> DispatchResult {
  1756. if let Some(text) = text_opt {
  1757. ChannelBannerConstraint::get().ensure_valid(
  1758. text.len(),
  1759. MSG_CHANNEL_BANNER_TOO_SHORT,
  1760. MSG_CHANNEL_BANNER_TOO_LONG,
  1761. )
  1762. } else {
  1763. Ok(())
  1764. }
  1765. }
  1766. fn ensure_curator_application_text_is_valid(text: &[u8]) -> DispatchResult {
  1767. CuratorApplicationHumanReadableText::get().ensure_valid(
  1768. text.len(),
  1769. MSG_CURATOR_APPLICATION_TEXT_TOO_SHORT,
  1770. MSG_CURATOR_APPLICATION_TEXT_TOO_LONG,
  1771. )
  1772. }
  1773. fn ensure_curator_exit_rationale_text_is_valid(text: &[u8]) -> DispatchResult {
  1774. CuratorExitRationaleText::get().ensure_valid(
  1775. text.len(),
  1776. MSG_CURATOR_EXIT_RATIONALE_TEXT_TOO_SHORT,
  1777. MSG_CURATOR_EXIT_RATIONALE_TEXT_TOO_LONG,
  1778. )
  1779. }
  1780. fn ensure_opening_human_readable_text_is_valid(text: &[u8]) -> DispatchResult {
  1781. OpeningHumanReadableText::get().ensure_valid(
  1782. text.len(),
  1783. MSG_CHANNEL_DESCRIPTION_TOO_SHORT,
  1784. MSG_CHANNEL_DESCRIPTION_TOO_LONG,
  1785. )
  1786. }
  1787. fn ensure_channel_id_is_valid(
  1788. channel_id: &ChannelId<T>,
  1789. ) -> Result<Channel<T::MemberId, T::AccountId, T::BlockNumber, PrincipalId<T>>, &'static str>
  1790. {
  1791. if ChannelById::<T>::contains_key(channel_id) {
  1792. let channel = ChannelById::<T>::get(channel_id);
  1793. Ok(channel)
  1794. } else {
  1795. Err(MSG_CHANNEL_ID_INVALID)
  1796. }
  1797. }
  1798. pub fn ensure_lead_is_set() -> Result<
  1799. (
  1800. LeadId<T>,
  1801. Lead<T::AccountId, T::RewardRelationshipId, T::BlockNumber, T::MemberId>,
  1802. ),
  1803. &'static str,
  1804. > {
  1805. // Ensure lead id is set
  1806. let lead_id = Self::ensure_lead_id_set()?;
  1807. // If so, grab actual lead
  1808. let lead = <LeadById<T>>::get(lead_id);
  1809. // and return both
  1810. Ok((lead_id, lead))
  1811. }
  1812. fn ensure_lead_id_set() -> Result<LeadId<T>, &'static str> {
  1813. let opt_current_lead_id = <CurrentLeadId<T>>::get();
  1814. if let Some(lead_id) = opt_current_lead_id {
  1815. Ok(lead_id)
  1816. } else {
  1817. Err(MSG_CURRENT_LEAD_NOT_SET)
  1818. }
  1819. }
  1820. fn ensure_origin_is_set_lead(
  1821. origin: T::Origin,
  1822. ) -> Result<
  1823. (
  1824. LeadId<T>,
  1825. Lead<T::AccountId, T::RewardRelationshipId, T::BlockNumber, T::MemberId>,
  1826. ),
  1827. &'static str,
  1828. > {
  1829. // Ensure lead is actually set
  1830. let (lead_id, lead) = Self::ensure_lead_is_set()?;
  1831. // Ensure is signed
  1832. let signer = ensure_signed(origin)?;
  1833. // Ensure signer is lead
  1834. ensure!(signer == lead.role_account, MSG_ORIGIN_IS_NOT_LEAD);
  1835. Ok((lead_id, lead))
  1836. }
  1837. fn ensure_curator_opening_exists(
  1838. curator_opening_id: &CuratorOpeningId<T>,
  1839. ) -> Result<
  1840. (
  1841. CuratorOpening<T::OpeningId, T::BlockNumber, BalanceOf<T>, CuratorApplicationId<T>>,
  1842. hiring::Opening<BalanceOf<T>, T::BlockNumber, <T as hiring::Trait>::ApplicationId>,
  1843. ),
  1844. &'static str,
  1845. > {
  1846. ensure!(
  1847. CuratorOpeningById::<T>::contains_key(curator_opening_id),
  1848. MSG_CURATOR_OPENING_DOES_NOT_EXIST
  1849. );
  1850. let curator_opening = CuratorOpeningById::<T>::get(curator_opening_id);
  1851. let opening = hiring::OpeningById::<T>::get(curator_opening.opening_id);
  1852. Ok((curator_opening, opening))
  1853. }
  1854. fn ensure_curator_exists(
  1855. curator_id: &CuratorId<T>,
  1856. ) -> Result<
  1857. Curator<
  1858. T::AccountId,
  1859. T::RewardRelationshipId,
  1860. T::StakeId,
  1861. T::BlockNumber,
  1862. LeadId<T>,
  1863. T::ApplicationId,
  1864. PrincipalId<T>,
  1865. >,
  1866. &'static str,
  1867. > {
  1868. ensure!(
  1869. CuratorById::<T>::contains_key(curator_id),
  1870. MSG_CURATOR_DOES_NOT_EXIST
  1871. );
  1872. let curator = CuratorById::<T>::get(curator_id);
  1873. Ok(curator)
  1874. }
  1875. fn ensure_unstaker_exists(
  1876. stake_id: &StakeId<T>,
  1877. ) -> Result<WorkingGroupUnstaker<LeadId<T>, CuratorId<T>>, &'static str> {
  1878. ensure!(
  1879. UnstakerByStakeId::<T>::contains_key(stake_id),
  1880. MSG_UNSTAKER_DOES_NOT_EXIST
  1881. );
  1882. let unstaker = UnstakerByStakeId::<T>::get(stake_id);
  1883. Ok(unstaker)
  1884. }
  1885. fn ensure_active_curator_exists(
  1886. curator_id: &CuratorId<T>,
  1887. ) -> Result<
  1888. Curator<
  1889. T::AccountId,
  1890. T::RewardRelationshipId,
  1891. T::StakeId,
  1892. T::BlockNumber,
  1893. LeadId<T>,
  1894. T::ApplicationId,
  1895. PrincipalId<T>,
  1896. >,
  1897. &'static str,
  1898. > {
  1899. // Ensuring curator actually exists
  1900. let curator = Self::ensure_curator_exists(curator_id)?;
  1901. // Ensure curator is still active
  1902. ensure!(
  1903. match curator.stage {
  1904. CuratorRoleStage::Active => true,
  1905. _ => false,
  1906. },
  1907. MSG_CURATOR_IS_NOT_ACTIVE
  1908. );
  1909. Ok(curator)
  1910. }
  1911. fn ensure_active_curator_signed(
  1912. origin: T::Origin,
  1913. curator_id: &CuratorId<T>,
  1914. ) -> Result<
  1915. Curator<
  1916. T::AccountId,
  1917. T::RewardRelationshipId,
  1918. T::StakeId,
  1919. T::BlockNumber,
  1920. LeadId<T>,
  1921. T::ApplicationId,
  1922. PrincipalId<T>,
  1923. >,
  1924. &'static str,
  1925. > {
  1926. // Ensure that it is signed
  1927. let signer_account = ensure_signed(origin)?;
  1928. // Ensure that id corresponds to active curator
  1929. let curator = Self::ensure_active_curator_exists(&curator_id)?;
  1930. // Ensure that signer is actually role account of curator
  1931. ensure!(
  1932. signer_account == curator.role_account,
  1933. MSG_SIGNER_IS_NOT_CURATOR_ROLE_ACCOUNT
  1934. );
  1935. Ok(curator)
  1936. }
  1937. fn ensure_curation_actor_signed(
  1938. origin: T::Origin,
  1939. curation_actor: &CurationActor<CuratorId<T>>,
  1940. ) -> Result<(), &'static str> {
  1941. match curation_actor {
  1942. CurationActor::Lead => {
  1943. // Ensure lead is set and is origin signer
  1944. Self::ensure_origin_is_set_lead(origin).map(|_| ())
  1945. }
  1946. CurationActor::Curator(curator_id) => {
  1947. // Ensure there is a signer which matches role account of curator corresponding to provided id.
  1948. Self::ensure_active_curator_signed(origin, &curator_id).map(|_| ())
  1949. }
  1950. }
  1951. }
  1952. /// Ensure origin is signed by account matching role account corresponding to the channel
  1953. fn ensure_channel_owner_signed(
  1954. origin: T::Origin,
  1955. channel_id: &ChannelId<T>,
  1956. ) -> Result<Channel<T::MemberId, T::AccountId, T::BlockNumber, PrincipalId<T>>, &'static str>
  1957. {
  1958. // Ensure that it is signed
  1959. let signer_account = ensure_signed(origin)?;
  1960. // Ensure channel id is valid
  1961. let channel = Self::ensure_channel_id_is_valid(&channel_id)?;
  1962. // Ensure origin matches channel role account
  1963. ensure!(
  1964. signer_account == channel.role_account,
  1965. MSG_ORIGIN_DOES_NOT_MATCH_CHANNEL_ROLE_ACCOUNT
  1966. );
  1967. Ok(channel)
  1968. }
  1969. fn ensure_curator_application_exists(
  1970. curator_application_id: &CuratorApplicationId<T>,
  1971. ) -> Result<
  1972. (
  1973. CuratorApplication<T::AccountId, CuratorOpeningId<T>, T::MemberId, T::ApplicationId>,
  1974. CuratorApplicationId<T>,
  1975. CuratorOpening<T::OpeningId, T::BlockNumber, BalanceOf<T>, CuratorApplicationId<T>>,
  1976. ),
  1977. &'static str,
  1978. > {
  1979. ensure!(
  1980. CuratorApplicationById::<T>::contains_key(curator_application_id),
  1981. MSG_CURATOR_APPLICATION_DOES_NOT_EXIST
  1982. );
  1983. let curator_application = CuratorApplicationById::<T>::get(curator_application_id);
  1984. let curator_opening = CuratorOpeningById::<T>::get(curator_application.curator_opening_id);
  1985. Ok((
  1986. curator_application,
  1987. *curator_application_id,
  1988. curator_opening,
  1989. ))
  1990. }
  1991. fn ensure_curator_has_recurring_reward(
  1992. curator: &Curator<
  1993. T::AccountId,
  1994. T::RewardRelationshipId,
  1995. T::StakeId,
  1996. T::BlockNumber,
  1997. LeadId<T>,
  1998. T::ApplicationId,
  1999. PrincipalId<T>,
  2000. >,
  2001. ) -> Result<T::RewardRelationshipId, &'static str> {
  2002. ensure!(
  2003. curator.reward_relationship.is_some(),
  2004. MSG_CURATOR_HAS_NO_REWARD
  2005. );
  2006. let relationship_id = curator.reward_relationship.unwrap();
  2007. Ok(relationship_id)
  2008. }
  2009. /// CRITICAL:
  2010. /// https://github.com/Joystream/substrate-runtime-joystream/issues/92
  2011. /// This assumes that ensure_can_withdraw can be don
  2012. /// for a sum of balance that later will be actually withdrawn
  2013. /// using individual terms in that sum.
  2014. /// This needs to be fully checked across all possibly scenarios
  2015. /// of actual balance, minimum balance limit, reservation, vesting and locking.
  2016. fn ensure_can_make_stake_imbalance(
  2017. opt_balances: Vec<&Option<BalanceOf<T>>>,
  2018. source_account: &T::AccountId,
  2019. ) -> Result<(), &'static str> {
  2020. let zero_balance = <BalanceOf<T> as Zero>::zero();
  2021. // Total amount to be staked
  2022. let total_amount = opt_balances.iter().fold(zero_balance, |sum, opt_balance| {
  2023. sum + if let Some(balance) = opt_balance {
  2024. *balance
  2025. } else {
  2026. zero_balance
  2027. }
  2028. });
  2029. if total_amount > zero_balance {
  2030. // Ensure that
  2031. if CurrencyOf::<T>::free_balance(source_account) < total_amount {
  2032. Err(MSG_INSUFFICIENT_BALANCE_TO_COVER_STAKE)
  2033. } else {
  2034. let new_balance = CurrencyOf::<T>::free_balance(source_account) - total_amount;
  2035. CurrencyOf::<T>::ensure_can_withdraw(
  2036. source_account,
  2037. total_amount,
  2038. WithdrawReasons::all(),
  2039. new_balance,
  2040. )
  2041. .map_err(<&str>::from)
  2042. }
  2043. } else {
  2044. Ok(())
  2045. }
  2046. }
  2047. fn make_stake_opt_imbalance(
  2048. opt_balance: &Option<BalanceOf<T>>,
  2049. source_account: &T::AccountId,
  2050. ) -> Option<NegativeImbalance<T>> {
  2051. if let Some(balance) = opt_balance {
  2052. let withdraw_result = CurrencyOf::<T>::withdraw(
  2053. source_account,
  2054. *balance,
  2055. WithdrawReasons::all(),
  2056. ExistenceRequirement::AllowDeath,
  2057. );
  2058. assert!(withdraw_result.is_ok());
  2059. withdraw_result.ok()
  2060. } else {
  2061. None
  2062. }
  2063. }
  2064. fn deactivate_curator(
  2065. curator_id: &CuratorId<T>,
  2066. curator: &Curator<
  2067. T::AccountId,
  2068. T::RewardRelationshipId,
  2069. T::StakeId,
  2070. T::BlockNumber,
  2071. LeadId<T>,
  2072. CuratorApplicationId<T>,
  2073. PrincipalId<T>,
  2074. >,
  2075. exit_initiation_origin: &CuratorExitInitiationOrigin,
  2076. rationale_text: &[u8],
  2077. ) {
  2078. // Stop any possible recurring rewards
  2079. let _did_deactivate_recurring_reward = if let Some(ref reward_relationship_id) =
  2080. curator.reward_relationship
  2081. {
  2082. // Attempt to deactivate
  2083. recurringrewards::Module::<T>::try_to_deactivate_relationship(*reward_relationship_id)
  2084. .expect("Relationship must exist")
  2085. } else {
  2086. // Did not deactivate, there was no reward relationship!
  2087. false
  2088. };
  2089. // When the curator is staked, unstaking must first be initiated,
  2090. // otherwise they can be terminated right away.
  2091. // Create exit summary for this termination
  2092. let current_block = <system::Module<T>>::block_number();
  2093. let curator_exit_summary =
  2094. CuratorExitSummary::new(exit_initiation_origin, &current_block, rationale_text);
  2095. // Determine new curator stage and event to emit
  2096. let (new_curator_stage, unstake_directions, event) = if let Some(ref stake_profile) =
  2097. curator.role_stake_profile
  2098. {
  2099. // Determine unstaknig period based on who initiated deactivation
  2100. let unstaking_period = match curator_exit_summary.origin {
  2101. CuratorExitInitiationOrigin::Lead => stake_profile.termination_unstaking_period,
  2102. CuratorExitInitiationOrigin::Curator => stake_profile.exit_unstaking_period,
  2103. };
  2104. (
  2105. CuratorRoleStage::Unstaking(curator_exit_summary),
  2106. Some((stake_profile.stake_id, unstaking_period)),
  2107. RawEvent::CuratorUnstaking(*curator_id),
  2108. )
  2109. } else {
  2110. (
  2111. CuratorRoleStage::Exited(curator_exit_summary.clone()),
  2112. None,
  2113. match curator_exit_summary.origin {
  2114. CuratorExitInitiationOrigin::Lead => RawEvent::TerminatedCurator(*curator_id),
  2115. CuratorExitInitiationOrigin::Curator => RawEvent::CuratorExited(*curator_id),
  2116. },
  2117. )
  2118. };
  2119. // Update curator
  2120. let new_curator = Curator {
  2121. stage: new_curator_stage,
  2122. ..(curator.clone())
  2123. };
  2124. CuratorById::<T>::insert(curator_id, new_curator);
  2125. // Unstake if directions provided
  2126. if let Some(directions) = unstake_directions {
  2127. // Keep track of curator unstaking
  2128. let unstaker = WorkingGroupUnstaker::Curator(*curator_id);
  2129. UnstakerByStakeId::<T>::insert(directions.0, unstaker);
  2130. // Unstake
  2131. stake::Module::<T>::initiate_unstaking(&directions.0, directions.1)
  2132. .expect("Unstaking must be possible at this time");
  2133. }
  2134. // Trigger event
  2135. Self::deposit_event(event);
  2136. }
  2137. /// Adds the given principal to storage under the returned identifier.
  2138. fn add_new_principal(principal: &Principal<CuratorId<T>, ChannelId<T>>) -> PrincipalId<T> {
  2139. // Get principal id for curator
  2140. let principal_id = NextPrincipalId::<T>::get();
  2141. // Update next principal id value
  2142. NextPrincipalId::<T>::mutate(|id| *id += PrincipalId::<T>::one());
  2143. // Store principal
  2144. PrincipalById::<T>::insert(principal_id, principal);
  2145. // Return id
  2146. principal_id
  2147. }
  2148. fn update_channel(
  2149. channel_id: &ChannelId<T>,
  2150. new_verified: Option<bool>,
  2151. new_handle: &Option<Vec<u8>>,
  2152. new_title: &Option<OptionalText>,
  2153. new_description: &Option<OptionalText>,
  2154. new_avatar: &Option<OptionalText>,
  2155. new_banner: &Option<OptionalText>,
  2156. new_publication_status: Option<ChannelPublicationStatus>,
  2157. new_curation_status: Option<ChannelCurationStatus>,
  2158. ) {
  2159. // Update channel id to handle mapping, if there is a new handle.
  2160. if let Some(ref handle) = new_handle {
  2161. // Remove mapping under old handle
  2162. let current_handle = ChannelById::<T>::get(channel_id).handle;
  2163. ChannelIdByHandle::<T>::remove(current_handle);
  2164. // Establish mapping under new handle
  2165. ChannelIdByHandle::<T>::insert(handle.clone(), channel_id);
  2166. }
  2167. // Update channel
  2168. ChannelById::<T>::mutate(channel_id, |channel| {
  2169. if let Some(ref verified) = new_verified {
  2170. channel.verified = *verified;
  2171. }
  2172. if let Some(ref handle) = new_handle {
  2173. channel.handle = handle.clone();
  2174. }
  2175. if let Some(ref title) = new_title {
  2176. channel.title = title.clone();
  2177. }
  2178. if let Some(ref description) = new_description {
  2179. channel.description = description.clone();
  2180. }
  2181. if let Some(ref avatar) = new_avatar {
  2182. channel.avatar = avatar.clone();
  2183. }
  2184. if let Some(ref banner) = new_banner {
  2185. channel.banner = banner.clone();
  2186. }
  2187. if let Some(ref publication_status) = new_publication_status {
  2188. channel.publication_status = publication_status.clone();
  2189. }
  2190. if let Some(ref curation_status) = new_curation_status {
  2191. channel.curation_status = *curation_status;
  2192. }
  2193. });
  2194. // Trigger event
  2195. Self::deposit_event(RawEvent::ChannelUpdatedByCurationActor(*channel_id));
  2196. }
  2197. /// The stake, with the given id, was unstaked. Infalliable. Has no side effects if stake_id is not relevant
  2198. /// to this module.
  2199. pub fn unstaked(stake_id: StakeId<T>) {
  2200. // Ignore if unstaked doesn't exist
  2201. if !<UnstakerByStakeId<T>>::contains_key(stake_id) {
  2202. return;
  2203. }
  2204. // Unstaker must be in this group
  2205. let unstaker = Self::ensure_unstaker_exists(&stake_id).unwrap();
  2206. // Get curator doing the unstaking,
  2207. // urrently the only possible unstaker in this module.
  2208. let curator_id = if let WorkingGroupUnstaker::Curator(curator_id) = unstaker {
  2209. curator_id
  2210. } else {
  2211. panic!("Should not be possible, only curators unstake in this module currently.");
  2212. };
  2213. // Grab curator from id, unwrap, because this curator _must_ exist.
  2214. let unstaking_curator = Self::ensure_curator_exists(&curator_id).unwrap();
  2215. //
  2216. // == MUTATION SAFE ==
  2217. //
  2218. // Update stage of curator
  2219. let curator_exit_summary =
  2220. if let CuratorRoleStage::Unstaking(summary) = unstaking_curator.stage {
  2221. summary
  2222. } else {
  2223. panic!("Curator must be in unstaking stage.");
  2224. };
  2225. let new_curator = Curator {
  2226. stage: CuratorRoleStage::Exited(curator_exit_summary.clone()),
  2227. ..unstaking_curator
  2228. };
  2229. CuratorById::<T>::insert(curator_id, new_curator);
  2230. // Remove from unstaker
  2231. UnstakerByStakeId::<T>::remove(stake_id);
  2232. // Trigger event
  2233. let event = match curator_exit_summary.origin {
  2234. CuratorExitInitiationOrigin::Lead => RawEvent::TerminatedCurator(curator_id),
  2235. CuratorExitInitiationOrigin::Curator => RawEvent::CuratorExited(curator_id),
  2236. };
  2237. Self::deposit_event(event);
  2238. }
  2239. }