lib.rs 39 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024
  1. //! # Proposals engine module
  2. //! Proposals `engine` module for the Joystream platform.
  3. //! The main component of the proposals frame_system. Provides methods and extrinsics to create and
  4. //! vote for proposals, inspired by Parity **Democracy module**.
  5. //!
  6. //! ## Overview
  7. //! Proposals `engine` module provides an abstract mechanism to work with proposals: creation,
  8. //! voting, execution, canceling, etc. Proposal execution demands serialized _Dispatchable_ proposal
  9. //! code. It could be any _Dispatchable_ + _Parameter_ type, but most likely, it would be serialized
  10. //! (via Parity _codec_ crate) extrisic call. A proposal stage can be described by
  11. //! its [status](./enum.ProposalStatus.html).
  12. //!
  13. //! ## Proposal lifecycle
  14. //! When a proposal passes
  15. //! [checks](./struct.Module.html#method.ensure_create_proposal_parameters_are_valid)
  16. //! for its [parameters](./struct.ProposalParameters.html) -
  17. //! it can be [created](./struct.Module.html#method.create_proposal).
  18. //! The newly created proposal has _Active_ status. The proposal can be voted on, vetoed or
  19. //! canceled during its _voting period_. Votes can be [different](./enum.VoteKind.html). When the
  20. //! proposal gets enough votes to be approved - the proposal becomes _PendingExecution_ or
  21. //! _PendingConstitutionality_. The proposal could also be slashed or rejected. If the _voting
  22. //! period_ ends with no decision it becomes expired. If the proposal got approved
  23. //! and _grace period_ passed - the `engine` module tries to execute the proposal.
  24. //!
  25. //! ### Notes
  26. //!
  27. //! - The proposal can be [vetoed](./struct.Module.html#method.veto_proposal)
  28. //! anytime before the proposal execution by the _sudo_.
  29. //! - If the _council_ got reelected during the proposal _voting period_ the external handler calls
  30. //! [reject_active_proposals](./struct.Module.html#method.reject_active_proposals) function and
  31. //! all active proposals got rejected and it also calls
  32. //! [reactivate_pending_constitutionality_proposals](./struct.Module.html#method.reactivate_pending_constitutionality_proposals)
  33. //! and proposals with pending constitutionality become active again.
  34. //! - There are different fees to apply for slashed, rejected, expired or cancelled proposals.
  35. //! - On runtime upgrade the proposals code could be obsolete, so we cancel all active proposals
  36. //! with statuses: Active, PendingExecution, PendingConstitutionality using this function
  37. //! [cancel_active_and_pending_proposals](./struct.Module.html#method.cancel_active_and_pending_proposals).
  38. //!
  39. //! ### Important abstract types to be implemented
  40. //! Proposals `engine` module has several abstractions to be implemented in order to work correctly.
  41. //! - _VoterOriginValidator_ - ensure valid voter identity. Voters should have permissions to vote:
  42. //! they should be council members.
  43. //! - [VotersParameters](./trait.VotersParameters.html) - defines total voter number, which is
  44. //! the council size
  45. //! - _ProposerOriginValidator_ - ensure valid proposer identity. Proposers should have permissions
  46. //! to create a proposal: they should be members of the Joystream.
  47. //! - StakingHandler - defines an interface for the staking.
  48. //!
  49. //! A full list of the abstractions can be found [here](./trait.Trait.html).
  50. //!
  51. //! ### Supported extrinsics
  52. //! - [vote](./struct.Module.html#method.vote) - registers a vote for the proposal
  53. //! - [cancel_proposal](./struct.Module.html#method.cancel_proposal) - cancels the proposal
  54. //! (can be canceled only by owner)
  55. //! - [veto_proposal](./struct.Module.html#method.veto_proposal) - vetoes the proposal
  56. //!
  57. //! ### Public API
  58. //! - [create_proposal](./struct.Module.html#method.create_proposal) - creates proposal using
  59. //! provided parameters
  60. //! - [ensure_create_proposal_parameters_are_valid](./struct.Module.html#method.ensure_create_proposal_parameters_are_valid) -
  61. //! ensures that we can create the proposal
  62. //! - [reject_active_proposals](./struct.Module.html#method.reject_active_proposals) - rejects all
  63. //! active proposals.
  64. //! - [reactivate_pending_constitutionality_proposals](./struct.Module.html#method.reactivate_pending_constitutionality_proposals) -
  65. //! reactivate proposals with pending constitutionality.
  66. //! - [cancel_active_and_pending_proposals](./struct.Module.html#method.cancel_active_and_pending_proposals) -
  67. //! cancels all active proposals.
  68. //!
  69. //! ## Usage
  70. //!
  71. //! ```
  72. //! use frame_support::{decl_module, print};
  73. //! use frame_system::ensure_signed;
  74. //! use codec::Encode;
  75. //! use pallet_proposals_engine::{self as engine, ProposalParameters, ProposalCreationParameters};
  76. //!
  77. //! pub trait Trait: engine::Trait + common::membership::MembershipTypes {}
  78. //!
  79. //! decl_module! {
  80. //! pub struct Module<T: Trait> for enum Call where origin: T::Origin {
  81. //! #[weight = 10_000_000]
  82. //! fn executable_proposal(origin) {
  83. //! print("executed!");
  84. //! }
  85. //!
  86. //! #[weight = 10_000_000]
  87. //! pub fn create_spending_proposal(
  88. //! origin,
  89. //! proposer_id: T::MemberId,
  90. //! ) {
  91. //! let account_id = ensure_signed(origin)?;
  92. //! let parameters = ProposalParameters::default();
  93. //! let title = b"Spending proposal".to_vec();
  94. //! let description = b"We need to spend some tokens to support the working group lead."
  95. //! .to_vec();
  96. //! let encoded_proposal_code = <Call<T>>::executable_proposal().encode();
  97. //!
  98. //! <engine::Module<T>>::ensure_create_proposal_parameters_are_valid(
  99. //! &parameters,
  100. //! &title,
  101. //! &description,
  102. //! None,
  103. //! None,
  104. //! proposer_id,
  105. //! )?;
  106. //!
  107. //! let creation_parameters = ProposalCreationParameters {
  108. //! account_id,
  109. //! proposer_id,
  110. //! proposal_parameters : parameters,
  111. //! title,
  112. //! description,
  113. //! staking_account_id: None,
  114. //! encoded_dispatchable_call_code: encoded_proposal_code,
  115. //! exact_execution_block: None,
  116. //! };
  117. //!
  118. //! <engine::Module<T>>::create_proposal(creation_parameters)?;
  119. //! }
  120. //! }
  121. //! }
  122. //! # fn main() {}
  123. //! ```
  124. // Ensure we're `no_std` when compiling for Wasm.
  125. #![cfg_attr(not(feature = "std"), no_std)]
  126. use types::ProposalOf;
  127. pub use types::{
  128. ApprovedProposalDecision, BalanceOf, ExecutionStatus, Proposal, ProposalCodeDecoder,
  129. ProposalCreationParameters, ProposalDecision, ProposalExecutable, ProposalParameters,
  130. ProposalStatus, VoteKind, VotersParameters, VotingResults,
  131. };
  132. pub(crate) mod types;
  133. mod benchmarking;
  134. #[cfg(test)]
  135. mod tests;
  136. use codec::Decode;
  137. use frame_support::dispatch::{DispatchError, DispatchResult, UnfilteredDispatchable};
  138. use frame_support::storage::IterableStorageMap;
  139. use frame_support::traits::Get;
  140. use frame_support::weights::{GetDispatchInfo, Weight};
  141. use frame_support::{
  142. decl_error, decl_event, decl_module, decl_storage, ensure, Parameter, StorageDoubleMap,
  143. };
  144. use frame_system::{ensure_root, RawOrigin};
  145. use sp_arithmetic::traits::{SaturatedConversion, Saturating, Zero};
  146. use sp_std::vec::Vec;
  147. use common::council::CouncilOriginValidator;
  148. use common::membership::MemberOriginValidator;
  149. use common::{MemberId, StakingAccountValidator};
  150. use staking_handler::StakingHandler;
  151. /// Proposals engine WeightInfo.
  152. /// Note: This was auto generated through the benchmark CLI using the `--weight-trait` flag
  153. pub trait WeightInfo {
  154. fn vote(i: u32) -> Weight;
  155. fn cancel_proposal() -> Weight;
  156. fn veto_proposal() -> Weight;
  157. fn on_initialize_immediate_execution_decode_fails(i: u32) -> Weight;
  158. fn on_initialize_pending_execution_decode_fails(i: u32) -> Weight;
  159. fn on_initialize_approved_pending_constitutionality(i: u32) -> Weight;
  160. fn on_initialize_rejected(i: u32) -> Weight;
  161. fn on_initialize_slashed(i: u32) -> Weight;
  162. fn cancel_active_and_pending_proposals(i: u32) -> Weight;
  163. }
  164. type WeightInfoEngine<T> = <T as Trait>::WeightInfo;
  165. /// Proposals engine trait.
  166. pub trait Trait:
  167. frame_system::Trait
  168. + pallet_timestamp::Trait
  169. + common::membership::MembershipTypes
  170. + balances::Trait
  171. {
  172. /// Engine event type.
  173. type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;
  174. /// Validates proposer id and origin combination
  175. type ProposerOriginValidator: MemberOriginValidator<
  176. Self::Origin,
  177. MemberId<Self>,
  178. Self::AccountId,
  179. >;
  180. /// Validates voter id and origin combination
  181. type CouncilOriginValidator: CouncilOriginValidator<
  182. Self::Origin,
  183. MemberId<Self>,
  184. Self::AccountId,
  185. >;
  186. /// Provides data for voting. Defines maximum voters count for the proposal.
  187. type TotalVotersCounter: VotersParameters;
  188. /// Proposal Id type
  189. type ProposalId: From<u32> + Parameter + Default + Copy;
  190. /// Provides stake logic implementation.
  191. type StakingHandler: StakingHandler<Self::AccountId, BalanceOf<Self>, MemberId<Self>>;
  192. /// The fee is applied when cancel the proposal. A fee would be slashed (burned).
  193. type CancellationFee: Get<BalanceOf<Self>>;
  194. /// The fee is applied when the proposal gets rejected. A fee would be slashed (burned).
  195. type RejectionFee: Get<BalanceOf<Self>>;
  196. /// Defines max allowed proposal title length.
  197. type TitleMaxLength: Get<u32>;
  198. /// Defines max allowed proposal description length.
  199. type DescriptionMaxLength: Get<u32>;
  200. /// Defines max simultaneous active proposals number.
  201. type MaxActiveProposalLimit: Get<u32>;
  202. /// Proposals executable code. Can be instantiated by external module Call enum members.
  203. type DispatchableCallCode: Parameter
  204. + UnfilteredDispatchable<Origin = Self::Origin>
  205. + GetDispatchInfo
  206. + Default;
  207. /// Proposal state change observer.
  208. type ProposalObserver: ProposalObserver<Self>;
  209. /// Weight information for extrinsics in this pallet.
  210. type WeightInfo: WeightInfo;
  211. /// Validates staking account ownership for a member.
  212. type StakingAccountValidator: common::StakingAccountValidator<Self>;
  213. }
  214. /// Proposal state change observer.
  215. pub trait ProposalObserver<T: Trait> {
  216. /// Should be called on proposal removing.
  217. fn proposal_removed(proposal_id: &T::ProposalId);
  218. }
  219. /// Nesting implementation.
  220. impl<T: Trait, X: ProposalObserver<T>, Y: ProposalObserver<T>> ProposalObserver<T> for (X, Y) {
  221. fn proposal_removed(proposal_id: &<T as Trait>::ProposalId) {
  222. X::proposal_removed(proposal_id);
  223. Y::proposal_removed(proposal_id);
  224. }
  225. }
  226. decl_event!(
  227. /// Proposals engine events
  228. pub enum Event<T>
  229. where
  230. <T as Trait>::ProposalId,
  231. MemberId = MemberId<T>,
  232. <T as frame_system::Trait>::BlockNumber,
  233. {
  234. /// Emits on proposal creation.
  235. /// Params:
  236. /// - Member id of a proposer.
  237. /// - Id of a newly created proposal after it was saved in storage.
  238. ProposalCreated(MemberId, ProposalId),
  239. /// Emits on proposal creation.
  240. /// Params:
  241. /// - Id of a proposal.
  242. /// - New proposal status.
  243. ProposalStatusUpdated(ProposalId, ProposalStatus<BlockNumber>),
  244. /// Emits on getting a proposal status decision.
  245. /// Params:
  246. /// - Id of a proposal.
  247. /// - Proposal decision
  248. ProposalDecisionMade(ProposalId, ProposalDecision),
  249. /// Emits on proposal execution.
  250. /// Params:
  251. /// - Id of a updated proposal.
  252. /// - Proposal execution status.
  253. ProposalExecuted(ProposalId, ExecutionStatus),
  254. /// Emits on voting for the proposal
  255. /// Params:
  256. /// - Voter - member id of a voter.
  257. /// - Id of a proposal.
  258. /// - Kind of vote.
  259. /// - Rationale.
  260. Voted(MemberId, ProposalId, VoteKind, Vec<u8>),
  261. /// Emits on a proposal being cancelled
  262. /// Params:
  263. /// - Member Id of the proposer
  264. /// - Id of the proposal
  265. ProposalCancelled(MemberId, ProposalId),
  266. }
  267. );
  268. decl_error! {
  269. /// Engine module predefined errors
  270. pub enum Error for Module<T: Trait>{
  271. /// Proposal cannot have an empty title"
  272. EmptyTitleProvided,
  273. /// Proposal cannot have an empty body
  274. EmptyDescriptionProvided,
  275. /// Title is too long
  276. TitleIsTooLong,
  277. /// Description is too long
  278. DescriptionIsTooLong,
  279. /// The proposal does not exist
  280. ProposalNotFound,
  281. /// Proposal is finalized already
  282. ProposalFinalized,
  283. /// The proposal have been already voted on
  284. AlreadyVoted,
  285. /// Not an author
  286. NotAuthor,
  287. /// Max active proposals number exceeded
  288. MaxActiveProposalNumberExceeded,
  289. /// Stake cannot be empty with this proposal
  290. EmptyStake,
  291. /// Stake should be empty for this proposal
  292. StakeShouldBeEmpty,
  293. /// Stake differs from the proposal requirements
  294. StakeDiffersFromRequired,
  295. /// Approval threshold cannot be zero
  296. InvalidParameterApprovalThreshold,
  297. /// Slashing threshold cannot be zero
  298. InvalidParameterSlashingThreshold,
  299. /// Require root origin in extrinsics
  300. RequireRootOrigin,
  301. /// Disallow to cancel the proposal if there are any votes on it.
  302. ProposalHasVotes,
  303. /// Exact execution block cannot be zero.
  304. ZeroExactExecutionBlock,
  305. /// Exact execution block cannot be less than current_block.
  306. InvalidExactExecutionBlock,
  307. /// There is not enough balance for a stake.
  308. InsufficientBalanceForStake,
  309. /// The conflicting stake discovered. Cannot stake.
  310. ConflictingStakes,
  311. /// Staking account doesn't belong to a member.
  312. InvalidStakingAccountForMember,
  313. }
  314. }
  315. // Storage for the proposals engine module
  316. decl_storage! {
  317. pub trait Store for Module<T: Trait> as ProposalEngine{
  318. /// Map proposal by its id.
  319. pub Proposals get(fn proposals): map hasher(blake2_128_concat)
  320. T::ProposalId => ProposalOf<T>;
  321. /// Count of all proposals that have been created.
  322. pub ProposalCount get(fn proposal_count): u32;
  323. /// Map proposal executable code by proposal id.
  324. pub DispatchableCallCode get(fn proposal_codes): map hasher(blake2_128_concat)
  325. T::ProposalId => Vec<u8>;
  326. /// Count of active proposals.
  327. pub ActiveProposalCount get(fn active_proposal_count): u32;
  328. /// Double map for preventing duplicate votes. Should be cleaned after usage.
  329. pub VoteExistsByProposalByVoter get(fn vote_by_proposal_by_voter):
  330. double_map hasher(blake2_128_concat) T::ProposalId, hasher(blake2_128_concat) MemberId<T> => VoteKind;
  331. }
  332. }
  333. decl_module! {
  334. /// 'Proposal engine' substrate module
  335. pub struct Module<T: Trait> for enum Call where origin: T::Origin {
  336. /// Predefined errors
  337. type Error = Error<T>;
  338. /// Emits an event. Default substrate implementation.
  339. fn deposit_event() = default;
  340. /// Exports const - the fee is applied when cancel the proposal. A fee would be slashed (burned).
  341. const CancellationFee: BalanceOf<T> = T::CancellationFee::get();
  342. /// Exports const - the fee is applied when the proposal gets rejected. A fee would
  343. /// be slashed (burned).
  344. const RejectionFee: BalanceOf<T> = T::RejectionFee::get();
  345. /// Exports const - max allowed proposal title length.
  346. const TitleMaxLength: u32 = T::TitleMaxLength::get();
  347. /// Exports const - max allowed proposal description length.
  348. const DescriptionMaxLength: u32 = T::DescriptionMaxLength::get();
  349. /// Exports const - max simultaneous active proposals number.
  350. const MaxActiveProposalLimit: u32 = T::MaxActiveProposalLimit::get();
  351. /// Block Initialization. Perform voting period check, vote result tally, approved proposals
  352. /// grace period checks, and proposal execution.
  353. /// # <weight>
  354. ///
  355. /// ## Weight
  356. /// `O (P + I)` where:
  357. /// - `P` is the weight of all executed proposals
  358. /// - `I` is the weight of the worst branch for anything else in `on_initialize`
  359. /// - DB:
  360. /// - O(1) doesn't depend on the state
  361. /// # </weight>
  362. fn on_initialize() -> Weight {
  363. // `process_proposal` returns the weight of the executed proposals. The weight of the
  364. // executed proposals doesn't include any access to the store or calculation that
  365. // `on_initialize` does. Therefore, to get the total weight of `on_initialize` we need
  366. // to add the weight of the execution of `on_intialize` to the weight returned by
  367. // `process_proposal`.
  368. // To be safe, we use the worst possible case for `on_initialize`, meaning that there
  369. // are as many proposals active as possible and they all take the worst possible branch.
  370. // Maximum Weight of all possible worst case scenarios
  371. let maximum_branch_weight = Self::weight_of_worst_on_initialize_branch();
  372. // Weight of the executed proposals
  373. let executed_proposals_weight = Self::process_proposals();
  374. // total_weight = executed_proposals_weight + maximum_branch_weight
  375. executed_proposals_weight.saturating_add(maximum_branch_weight)
  376. }
  377. /// Vote extrinsic. Conditions: origin must allow votes.
  378. ///
  379. /// <weight>
  380. ///
  381. /// ## Weight
  382. /// `O (R)` where:
  383. /// - `R` is the length of `rationale`
  384. /// - DB:
  385. /// - O(1) doesn't depend on the state or paraemters
  386. /// # </weight>
  387. #[weight = WeightInfoEngine::<T>::vote(rationale.len().saturated_into())]
  388. pub fn vote(
  389. origin,
  390. voter_id: MemberId<T>,
  391. proposal_id: T::ProposalId,
  392. vote: VoteKind,
  393. rationale: Vec<u8>, // we use it on the query node side.
  394. ) {
  395. T::CouncilOriginValidator::ensure_member_consulate(origin, voter_id)?;
  396. ensure!(<Proposals<T>>::contains_key(proposal_id), Error::<T>::ProposalNotFound);
  397. let mut proposal = Self::proposals(proposal_id);
  398. ensure!(
  399. matches!(proposal.status, ProposalStatus::Active{..}),
  400. Error::<T>::ProposalFinalized
  401. );
  402. let did_not_vote_before = !<VoteExistsByProposalByVoter<T>>::contains_key(
  403. proposal_id,
  404. voter_id,
  405. );
  406. ensure!(did_not_vote_before, Error::<T>::AlreadyVoted);
  407. proposal.voting_results.add_vote(vote.clone());
  408. //
  409. // == MUTATION SAFE ==
  410. //
  411. <Proposals<T>>::insert(proposal_id, proposal);
  412. <VoteExistsByProposalByVoter<T>>::insert(proposal_id, voter_id, vote.clone());
  413. Self::deposit_event(RawEvent::Voted(voter_id, proposal_id, vote, rationale));
  414. }
  415. /// Cancel a proposal by its original proposer.
  416. ///
  417. /// <weight>
  418. ///
  419. /// ## Weight
  420. /// `O (L)` where:
  421. /// - `L` is the total number of locks in `Balances`
  422. /// - DB:
  423. /// - O(1) doesn't depend on the state or parameters
  424. /// # </weight>
  425. #[weight = WeightInfoEngine::<T>::cancel_proposal()]
  426. pub fn cancel_proposal(origin, proposer_id: MemberId<T>, proposal_id: T::ProposalId) {
  427. T::ProposerOriginValidator::ensure_member_controller_account_origin(origin, proposer_id)?;
  428. ensure!(<Proposals<T>>::contains_key(proposal_id), Error::<T>::ProposalNotFound);
  429. let proposal = Self::proposals(proposal_id);
  430. ensure!(proposer_id == proposal.proposer_id, Error::<T>::NotAuthor);
  431. ensure!(
  432. matches!(proposal.status, ProposalStatus::Active{..}),
  433. Error::<T>::ProposalFinalized
  434. );
  435. ensure!(proposal.voting_results.no_votes_yet(), Error::<T>::ProposalHasVotes);
  436. //
  437. // == MUTATION SAFE ==
  438. //
  439. Self::finalize_proposal(proposal_id, proposal, ProposalDecision::Canceled);
  440. Self::deposit_event(RawEvent::ProposalCancelled(proposer_id, proposal_id));
  441. }
  442. /// Veto a proposal. Must be root.
  443. ///
  444. /// <weight>
  445. ///
  446. /// ## Weight
  447. /// `O (1)` doesn't depend on the state or parameters
  448. /// - DB:
  449. /// - O(1) doesn't depend on the state or parameters
  450. /// # </weight>
  451. #[weight = WeightInfoEngine::<T>::veto_proposal()]
  452. pub fn veto_proposal(origin, proposal_id: T::ProposalId) {
  453. ensure_root(origin)?;
  454. ensure!(<Proposals<T>>::contains_key(proposal_id), Error::<T>::ProposalNotFound);
  455. let proposal = Self::proposals(proposal_id);
  456. // Note: we don't need to check if the proposal is active pending execution or
  457. // or pending constitutionality since if it in the storage `Proposals` it follows
  458. // that it is in one of those states.
  459. //
  460. // == MUTATION SAFE ==
  461. //
  462. Self::finalize_proposal(proposal_id, proposal, ProposalDecision::Vetoed);
  463. }
  464. }
  465. }
  466. impl<T: Trait> Module<T> {
  467. /// Create proposal. Requires 'proposal origin' membership.
  468. pub fn create_proposal(
  469. creation_params: ProposalCreationParameters<
  470. T::BlockNumber,
  471. BalanceOf<T>,
  472. MemberId<T>,
  473. T::AccountId,
  474. >,
  475. ) -> Result<T::ProposalId, DispatchError> {
  476. Self::ensure_create_proposal_parameters_are_valid(
  477. &creation_params.proposal_parameters,
  478. &creation_params.title,
  479. &creation_params.description,
  480. creation_params.staking_account_id.clone(),
  481. creation_params.exact_execution_block,
  482. creation_params.proposer_id,
  483. )?;
  484. //
  485. // == MUTATION SAFE ==
  486. //
  487. let next_proposal_count_value = Self::proposal_count() + 1;
  488. let new_proposal_id = next_proposal_count_value;
  489. let proposal_id = T::ProposalId::from(new_proposal_id);
  490. // Lock stake balance for proposal if the stake is required.
  491. if let Some(stake_balance) = creation_params.proposal_parameters.required_stake {
  492. if let Some(staking_account_id) = creation_params.staking_account_id.clone() {
  493. T::StakingHandler::lock(&staking_account_id, stake_balance);
  494. }
  495. }
  496. let new_proposal = Proposal {
  497. activated_at: Self::current_block(),
  498. parameters: creation_params.proposal_parameters,
  499. proposer_id: creation_params.proposer_id,
  500. status: ProposalStatus::Active,
  501. voting_results: VotingResults::default(),
  502. exact_execution_block: creation_params.exact_execution_block,
  503. nr_of_council_confirmations: 0,
  504. staking_account_id: creation_params.staking_account_id,
  505. };
  506. <Proposals<T>>::insert(proposal_id, new_proposal);
  507. <DispatchableCallCode<T>>::insert(
  508. proposal_id,
  509. creation_params.encoded_dispatchable_call_code,
  510. );
  511. ProposalCount::put(next_proposal_count_value);
  512. Self::increase_active_proposal_counter();
  513. Self::deposit_event(RawEvent::ProposalCreated(
  514. creation_params.proposer_id,
  515. proposal_id,
  516. ));
  517. Ok(proposal_id)
  518. }
  519. /// Performs all checks for the proposal creation:
  520. /// - title, body lengths
  521. /// - max active proposal
  522. /// - provided parameters: approval_threshold_percentage and slashing_threshold_percentage > 0
  523. /// - provided stake balance and parameters.required_stake are valid
  524. pub fn ensure_create_proposal_parameters_are_valid(
  525. parameters: &ProposalParameters<T::BlockNumber, BalanceOf<T>>,
  526. title: &[u8],
  527. description: &[u8],
  528. staking_account_id: Option<T::AccountId>,
  529. exact_execution_block: Option<T::BlockNumber>,
  530. member_id: T::MemberId,
  531. ) -> DispatchResult {
  532. ensure!(!title.is_empty(), Error::<T>::EmptyTitleProvided);
  533. ensure!(
  534. title.len() as u32 <= T::TitleMaxLength::get(),
  535. Error::<T>::TitleIsTooLong
  536. );
  537. ensure!(
  538. !description.is_empty(),
  539. Error::<T>::EmptyDescriptionProvided
  540. );
  541. ensure!(
  542. description.len() as u32 <= T::DescriptionMaxLength::get(),
  543. Error::<T>::DescriptionIsTooLong
  544. );
  545. ensure!(
  546. (Self::active_proposal_count()) < T::MaxActiveProposalLimit::get(),
  547. Error::<T>::MaxActiveProposalNumberExceeded
  548. );
  549. ensure!(
  550. parameters.approval_threshold_percentage > 0,
  551. Error::<T>::InvalidParameterApprovalThreshold
  552. );
  553. ensure!(
  554. parameters.slashing_threshold_percentage > 0,
  555. Error::<T>::InvalidParameterSlashingThreshold
  556. );
  557. // Check stake parameters.
  558. if staking_account_id.is_some() && parameters.required_stake.is_none() {
  559. return Err(Error::<T>::StakeShouldBeEmpty.into());
  560. }
  561. if let Some(stake_balance) = parameters.required_stake {
  562. if let Some(staking_account_id) = staking_account_id {
  563. ensure!(
  564. T::StakingAccountValidator::is_member_staking_account(
  565. &member_id,
  566. &staking_account_id
  567. ),
  568. Error::<T>::InvalidStakingAccountForMember
  569. );
  570. ensure!(
  571. T::StakingHandler::is_account_free_of_conflicting_stakes(&staking_account_id),
  572. Error::<T>::ConflictingStakes
  573. );
  574. ensure!(
  575. T::StakingHandler::is_enough_balance_for_stake(
  576. &staking_account_id,
  577. stake_balance
  578. ),
  579. Error::<T>::InsufficientBalanceForStake
  580. );
  581. } else {
  582. return Err(Error::<T>::EmptyStake.into());
  583. }
  584. }
  585. // Check execution block.
  586. if let Some(execution_block) = exact_execution_block {
  587. if execution_block == Zero::zero() {
  588. return Err(Error::<T>::ZeroExactExecutionBlock.into());
  589. }
  590. let now = Self::current_block();
  591. if execution_block < now + parameters.grace_period + parameters.voting_period {
  592. return Err(Error::<T>::InvalidExactExecutionBlock.into());
  593. }
  594. }
  595. Ok(())
  596. }
  597. /// Rejects all active proposals.
  598. /// Possible application includes new council elections.
  599. pub fn reject_active_proposals() {
  600. // Filter active proposals and reject them.
  601. <Proposals<T>>::iter()
  602. .filter_map(|(proposal_id, proposal)| {
  603. if proposal.status.is_active_proposal() {
  604. return Some((proposal_id, proposal));
  605. }
  606. None
  607. })
  608. .for_each(|(proposal_id, proposal)| {
  609. Self::finalize_proposal(proposal_id, proposal, ProposalDecision::Rejected);
  610. });
  611. }
  612. /// Cancels all active, pending execution and pending constitutionality proposals.
  613. /// No fee applies.Possible application includes the runtime upgrade.
  614. pub fn cancel_active_and_pending_proposals() -> Weight {
  615. let active_proposal_count = Self::active_proposal_count();
  616. // Filter active proposals and reject them.
  617. <Proposals<T>>::iter()
  618. .filter_map(|(proposal_id, proposal)| {
  619. if proposal.status.is_active_or_pending_execution()
  620. || proposal.status.is_pending_constitutionality_proposal()
  621. {
  622. return Some((proposal_id, proposal));
  623. }
  624. None
  625. })
  626. .for_each(|(proposal_id, proposal)| {
  627. Self::finalize_proposal(proposal_id, proposal, ProposalDecision::CanceledByRuntime);
  628. });
  629. <WeightInfoEngine<T>>::cancel_active_and_pending_proposals(
  630. active_proposal_count.saturated_into(),
  631. )
  632. }
  633. /// Reactivate proposals with pending constitutionality.
  634. /// Possible application includes new council elections.
  635. pub fn reactivate_pending_constitutionality_proposals() {
  636. // Filter pending constitutionality proposals, calculate new proposals and update the state.
  637. <Proposals<T>>::iter()
  638. .filter_map(|(proposal_id, mut proposal)| {
  639. if proposal.status.is_pending_constitutionality_proposal() {
  640. proposal.activated_at = Self::current_block();
  641. proposal.status = ProposalStatus::Active;
  642. // Resets votes for a proposal.
  643. proposal.reset_proposal_votes();
  644. return Some((proposal_id, proposal));
  645. }
  646. None
  647. })
  648. .for_each(|(proposal_id, proposal)| {
  649. <VoteExistsByProposalByVoter<T>>::remove_prefix(&proposal_id);
  650. <Proposals<T>>::insert(proposal_id, proposal.clone());
  651. // fire the proposal status update event
  652. Self::deposit_event(RawEvent::ProposalStatusUpdated(
  653. proposal_id,
  654. proposal.status,
  655. ));
  656. });
  657. }
  658. }
  659. impl<T: Trait> Module<T> {
  660. // Helper to calculate the weight of the worst `on_initialize` branch
  661. fn weight_of_worst_on_initialize_branch() -> Weight {
  662. let max_active_proposals = T::MaxActiveProposalLimit::get();
  663. // Weight when all the proposals are immediatly approved and executed
  664. let immediate_execution_branch_weight =
  665. WeightInfoEngine::<T>::on_initialize_immediate_execution_decode_fails(
  666. max_active_proposals,
  667. );
  668. let pending_execution_branch_weight =
  669. WeightInfoEngine::<T>::on_initialize_pending_execution_decode_fails(
  670. max_active_proposals,
  671. );
  672. // Weight when all the proposals are approved and pending constitutionality
  673. let approved_pending_constitutionality_branch_weight =
  674. WeightInfoEngine::<T>::on_initialize_approved_pending_constitutionality(
  675. max_active_proposals,
  676. );
  677. // Weight when all proposals are rejected
  678. let rejected_branch_weight =
  679. WeightInfoEngine::<T>::on_initialize_rejected(max_active_proposals);
  680. // Weight when all proposals are slashed
  681. let slashed_branch_weight =
  682. WeightInfoEngine::<T>::on_initialize_slashed(max_active_proposals);
  683. // Maximum Weight of all possible worst case scenarios
  684. immediate_execution_branch_weight
  685. .max(pending_execution_branch_weight)
  686. .max(approved_pending_constitutionality_branch_weight)
  687. .max(rejected_branch_weight)
  688. .max(slashed_branch_weight)
  689. }
  690. // Wrapper-function over System::block_number()
  691. fn current_block() -> T::BlockNumber {
  692. <frame_system::Module<T>>::block_number()
  693. }
  694. // Executes proposal code.
  695. // Returns the weight of the proposal(wether execution failed or not) or 0 if the proposal
  696. // couldn't be decoded.
  697. fn execute_proposal(proposal_id: T::ProposalId) -> Weight {
  698. let proposal_code = Self::proposal_codes(proposal_id);
  699. let proposal_code_result = T::DispatchableCallCode::decode(&mut &proposal_code[..]);
  700. let mut execution_code_weight = 0;
  701. let execution_status = match proposal_code_result {
  702. Ok(proposal_code) => {
  703. execution_code_weight = proposal_code.get_dispatch_info().weight;
  704. if let Err(dispatch_error) =
  705. proposal_code.dispatch_bypass_filter(T::Origin::from(RawOrigin::Root))
  706. {
  707. ExecutionStatus::failed_execution(Self::parse_dispatch_error(
  708. dispatch_error.error,
  709. ))
  710. } else {
  711. ExecutionStatus::Executed
  712. }
  713. }
  714. Err(_) => ExecutionStatus::failed_execution("Decoding error"),
  715. };
  716. Self::deposit_event(RawEvent::ProposalExecuted(proposal_id, execution_status));
  717. Self::remove_proposal_data(&proposal_id);
  718. execution_code_weight
  719. }
  720. // Computes a finalized proposal:
  721. // - update proposal status fields (status, finalized_at),
  722. // - increment constitutionality level of the proposal.
  723. // Performs all side-effect actions on proposal finalization:
  724. // - slash and unstake proposal stake if stake exists,
  725. // - fire an event,
  726. // - update or delete proposal state.
  727. // Executes the proposal if it ready.
  728. // If proposal was executed returns its weight otherwise it returns 0.
  729. fn finalize_proposal(
  730. proposal_id: T::ProposalId,
  731. proposal: ProposalOf<T>,
  732. proposal_decision: ProposalDecision,
  733. ) -> Weight {
  734. // fire the proposal decision event
  735. Self::deposit_event(RawEvent::ProposalDecisionMade(
  736. proposal_id,
  737. proposal_decision.clone(),
  738. ));
  739. let mut executed_weight = 0;
  740. // deal with stakes if necessary
  741. if proposal_decision
  742. != ProposalDecision::Approved(ApprovedProposalDecision::PendingConstitutionality)
  743. {
  744. let slash_balance =
  745. Self::calculate_slash_balance(&proposal_decision, &proposal.parameters);
  746. Self::slash_and_unstake(proposal.staking_account_id.clone(), slash_balance);
  747. }
  748. // update approved proposal or remove otherwise
  749. if let ProposalDecision::Approved(approved_proposal_decision) = proposal_decision {
  750. let now = Self::current_block();
  751. let mut finalized_proposal = proposal;
  752. finalized_proposal.increase_constitutionality_level();
  753. finalized_proposal.status = ProposalStatus::approved(approved_proposal_decision, now);
  754. // fire the proposal status update event
  755. Self::deposit_event(RawEvent::ProposalStatusUpdated(
  756. proposal_id,
  757. finalized_proposal.status.clone(),
  758. ));
  759. // immediately execute proposal if it ready for execution or save it for the future otherwise.
  760. if finalized_proposal.is_ready_for_execution(now) {
  761. executed_weight = Self::execute_proposal(proposal_id);
  762. } else {
  763. <Proposals<T>>::insert(proposal_id, finalized_proposal);
  764. }
  765. } else {
  766. Self::remove_proposal_data(&proposal_id);
  767. }
  768. executed_weight
  769. }
  770. // Slashes the stake and perform unstake only in case of existing stake.
  771. fn slash_and_unstake(staking_account_id: Option<T::AccountId>, slash_balance: BalanceOf<T>) {
  772. // only if stake exists
  773. if let Some(staking_account_id) = staking_account_id {
  774. if !slash_balance.is_zero() {
  775. T::StakingHandler::slash(&staking_account_id, Some(slash_balance));
  776. }
  777. T::StakingHandler::unlock(&staking_account_id);
  778. }
  779. }
  780. // Calculates required slash based on finalization ProposalDecisionStatus and proposal parameters.
  781. // Method visibility allows testing.
  782. pub(crate) fn calculate_slash_balance(
  783. proposal_decision: &ProposalDecision,
  784. proposal_parameters: &ProposalParameters<T::BlockNumber, BalanceOf<T>>,
  785. ) -> BalanceOf<T> {
  786. match proposal_decision {
  787. ProposalDecision::Rejected | ProposalDecision::Expired => T::RejectionFee::get(),
  788. ProposalDecision::Approved { .. }
  789. | ProposalDecision::Vetoed
  790. | ProposalDecision::CanceledByRuntime => BalanceOf::<T>::zero(),
  791. ProposalDecision::Canceled => T::CancellationFee::get(),
  792. ProposalDecision::Slashed => proposal_parameters
  793. .required_stake
  794. .clone()
  795. .unwrap_or_else(BalanceOf::<T>::zero), // stake if set or zero
  796. }
  797. }
  798. // Increases active proposal counter.
  799. fn increase_active_proposal_counter() {
  800. let next_active_proposal_count_value = Self::active_proposal_count() + 1;
  801. ActiveProposalCount::put(next_active_proposal_count_value);
  802. }
  803. // Decreases active proposal counter down to zero. Decreasing below zero has no effect.
  804. fn decrease_active_proposal_counter() {
  805. let current_active_proposal_counter = Self::active_proposal_count();
  806. if current_active_proposal_counter > 0 {
  807. let next_active_proposal_count_value = current_active_proposal_counter - 1;
  808. ActiveProposalCount::put(next_active_proposal_count_value);
  809. };
  810. }
  811. // Parse dispatchable execution result.
  812. fn parse_dispatch_error(error: DispatchError) -> &'static str {
  813. match error {
  814. DispatchError::BadOrigin => error.into(),
  815. DispatchError::Other(msg) => msg,
  816. DispatchError::CannotLookup => error.into(),
  817. DispatchError::Module {
  818. index: _,
  819. error: _,
  820. message: msg,
  821. } => msg.unwrap_or("Dispatch error."),
  822. }
  823. }
  824. // Clean proposal data. Remove proposal, votes from the storage.
  825. fn remove_proposal_data(proposal_id: &T::ProposalId) {
  826. <Proposals<T>>::remove(proposal_id);
  827. <DispatchableCallCode<T>>::remove(proposal_id);
  828. <VoteExistsByProposalByVoter<T>>::remove_prefix(&proposal_id);
  829. Self::decrease_active_proposal_counter();
  830. T::ProposalObserver::proposal_removed(proposal_id);
  831. }
  832. /// Perform voting period check, vote result tally, approved proposals
  833. /// grace period checks, and proposal execution.
  834. /// Returns the total weight of all the executed proposals or 0 if none was executed.
  835. fn process_proposals() -> Weight {
  836. // Collect all proposals.
  837. let proposals = <Proposals<T>>::iter().collect::<Vec<_>>();
  838. let now = Self::current_block();
  839. let mut executed_weight = 0;
  840. for (proposal_id, proposal) in proposals {
  841. match proposal.status {
  842. // Try to determine a decision for an active proposal.
  843. ProposalStatus::Active => {
  844. let decision_status = proposal
  845. .define_proposal_decision(T::TotalVotersCounter::total_voters_count(), now);
  846. // If decision is calculated for a proposal - finalize it.
  847. if let Some(decision_status) = decision_status {
  848. executed_weight.saturating_add(Self::finalize_proposal(
  849. proposal_id,
  850. proposal,
  851. decision_status,
  852. ));
  853. }
  854. }
  855. // Execute the proposal code if the proposal is ready for execution.
  856. ProposalStatus::PendingExecution(_) => {
  857. if proposal.is_ready_for_execution(now) {
  858. executed_weight =
  859. executed_weight.saturating_add(Self::execute_proposal(proposal_id));
  860. }
  861. }
  862. // Skip the proposal until it gets reactivated.
  863. ProposalStatus::PendingConstitutionality => {}
  864. }
  865. }
  866. executed_weight
  867. }
  868. }