proposals.rs 56 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578
  1. use codec::{Decode, Encode};
  2. use rstd::prelude::*;
  3. use runtime_io::print;
  4. use runtime_primitives::traits::{Hash, SaturatedConversion, Zero};
  5. #[cfg(feature = "std")]
  6. use serde::{Deserialize, Serialize};
  7. use srml_support::traits::{Currency, Get, ReservableCurrency};
  8. use srml_support::{
  9. decl_event, decl_module, decl_storage, dispatch, ensure, StorageMap, StorageValue,
  10. };
  11. use system::{self, ensure_root, ensure_signed};
  12. #[cfg(test)]
  13. use primitives::storage::well_known_keys;
  14. use super::council;
  15. pub use crate::currency::{BalanceOf, GovernanceCurrency};
  16. use crate::membership;
  17. const DEFAULT_APPROVAL_QUORUM: u32 = 60;
  18. const DEFAULT_MIN_STAKE: u32 = 100;
  19. const DEFAULT_CANCELLATION_FEE: u32 = 5;
  20. const DEFAULT_REJECTION_FEE: u32 = 10;
  21. const DEFAULT_VOTING_PERIOD_IN_DAYS: u32 = 10;
  22. const DEFAULT_VOTING_PERIOD_IN_SECS: u32 = DEFAULT_VOTING_PERIOD_IN_DAYS * 24 * 60 * 60;
  23. const DEFAULT_NAME_MAX_LEN: u32 = 100;
  24. const DEFAULT_DESCRIPTION_MAX_LEN: u32 = 10_000;
  25. const DEFAULT_WASM_CODE_MAX_LEN: u32 = 2_000_000;
  26. const MSG_STAKE_IS_TOO_LOW: &str = "Stake is too low";
  27. const MSG_STAKE_IS_GREATER_THAN_BALANCE: &str = "Balance is too low to be staked";
  28. const MSG_ONLY_MEMBERS_CAN_PROPOSE: &str = "Only members can make a proposal";
  29. const MSG_ONLY_COUNCILORS_CAN_VOTE: &str = "Only councilors can vote on proposals";
  30. const MSG_PROPOSAL_NOT_FOUND: &str = "This proposal does not exist";
  31. const MSG_PROPOSAL_EXPIRED: &str = "Voting period is expired for this proposal";
  32. const MSG_PROPOSAL_FINALIZED: &str = "Proposal is finalized already";
  33. const MSG_YOU_ALREADY_VOTED: &str = "You have already voted on this proposal";
  34. const MSG_YOU_DONT_OWN_THIS_PROPOSAL: &str = "You do not own this proposal";
  35. const MSG_PROPOSAL_STATUS_ALREADY_UPDATED: &str = "Proposal status has been updated already";
  36. const MSG_EMPTY_NAME_PROVIDED: &str = "Proposal cannot have an empty name";
  37. const MSG_EMPTY_DESCRIPTION_PROVIDED: &str = "Proposal cannot have an empty description";
  38. const MSG_EMPTY_WASM_CODE_PROVIDED: &str = "Proposal cannot have an empty WASM code";
  39. const MSG_TOO_LONG_NAME: &str = "Name is too long";
  40. const MSG_TOO_LONG_DESCRIPTION: &str = "Description is too long";
  41. const MSG_TOO_LONG_WASM_CODE: &str = "WASM code is too big";
  42. #[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
  43. #[derive(Encode, Decode, Clone, PartialEq, Eq)]
  44. pub enum ProposalStatus {
  45. /// A new proposal that is available for voting.
  46. Active,
  47. /// If cancelled by a proposer.
  48. Cancelled,
  49. /// Not enough votes and voting period expired.
  50. Expired,
  51. /// To clear the quorum requirement, the percentage of council members with revealed votes
  52. /// must be no less than the quorum value for the given proposal type.
  53. Approved,
  54. Rejected,
  55. /// If all revealed votes are slashes, then the proposal is rejected,
  56. /// and the proposal stake is slashed.
  57. Slashed,
  58. }
  59. impl Default for ProposalStatus {
  60. fn default() -> Self {
  61. ProposalStatus::Active
  62. }
  63. }
  64. use self::ProposalStatus::*;
  65. #[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
  66. #[derive(Encode, Decode, Clone, PartialEq, Eq)]
  67. pub enum VoteKind {
  68. /// Signals presence, but unwillingness to cast judgment on substance of vote.
  69. Abstain,
  70. /// Pass, an alternative or a ranking, for binary, multiple choice
  71. /// and ranked choice propositions, respectively.
  72. Approve,
  73. /// Against proposal.
  74. Reject,
  75. /// Against the proposal, and slash proposal stake.
  76. Slash,
  77. }
  78. impl Default for VoteKind {
  79. fn default() -> Self {
  80. VoteKind::Abstain
  81. }
  82. }
  83. use self::VoteKind::*;
  84. #[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
  85. #[derive(Encode, Decode, Default, Clone, PartialEq, Eq)]
  86. /// Proposal for node runtime update.
  87. pub struct RuntimeUpgradeProposal<AccountId, Balance, BlockNumber, Hash> {
  88. id: u32,
  89. proposer: AccountId,
  90. stake: Balance,
  91. name: Vec<u8>,
  92. description: Vec<u8>,
  93. wasm_hash: Hash,
  94. proposed_at: BlockNumber,
  95. status: ProposalStatus,
  96. }
  97. #[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
  98. #[derive(Encode, Decode, Default, Clone, PartialEq, Eq)]
  99. pub struct TallyResult<BlockNumber> {
  100. proposal_id: u32,
  101. abstentions: u32,
  102. approvals: u32,
  103. rejections: u32,
  104. slashes: u32,
  105. status: ProposalStatus,
  106. finalized_at: BlockNumber,
  107. }
  108. pub trait Trait:
  109. timestamp::Trait + council::Trait + GovernanceCurrency + membership::members::Trait
  110. {
  111. /// The overarching event type.
  112. type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
  113. }
  114. decl_event!(
  115. pub enum Event<T>
  116. where
  117. <T as system::Trait>::Hash,
  118. <T as system::Trait>::BlockNumber,
  119. <T as system::Trait>::AccountId
  120. {
  121. // New events
  122. /// Params:
  123. /// * Account id of a member who proposed.
  124. /// * Id of a newly created proposal after it was saved in storage.
  125. ProposalCreated(AccountId, u32),
  126. ProposalCanceled(AccountId, u32),
  127. ProposalStatusUpdated(u32, ProposalStatus),
  128. /// Params:
  129. /// * Voter - an account id of a councilor.
  130. /// * Id of a proposal.
  131. /// * Kind of vote.
  132. Voted(AccountId, u32, VoteKind),
  133. TallyFinalized(TallyResult<BlockNumber>),
  134. /// * Hash - hash of wasm code of runtime update.
  135. RuntimeUpdated(u32, Hash),
  136. /// Root cancelled proposal
  137. ProposalVetoed(u32),
  138. }
  139. );
  140. decl_storage! {
  141. trait Store for Module<T: Trait> as Proposals {
  142. // Parameters (defaut values could be exported to config):
  143. // TODO rename 'approval_quorum' -> 'quorum_percent' ?!
  144. /// A percent (up to 100) of the council participants
  145. /// that must vote affirmatively in order to pass.
  146. ApprovalQuorum get(approval_quorum) config(): u32 = DEFAULT_APPROVAL_QUORUM;
  147. /// Minimum amount of a balance to be staked in order to make a proposal.
  148. MinStake get(min_stake) config(): BalanceOf<T> =
  149. BalanceOf::<T>::from(DEFAULT_MIN_STAKE);
  150. /// A fee to be slashed (burn) in case a proposer decides to cancel a proposal.
  151. CancellationFee get(cancellation_fee) config(): BalanceOf<T> =
  152. BalanceOf::<T>::from(DEFAULT_CANCELLATION_FEE);
  153. /// A fee to be slashed (burn) in case a proposal was rejected.
  154. RejectionFee get(rejection_fee) config(): BalanceOf<T> =
  155. BalanceOf::<T>::from(DEFAULT_REJECTION_FEE);
  156. /// Max duration of proposal in blocks until it will be expired if not enough votes.
  157. VotingPeriod get(voting_period) config(): T::BlockNumber =
  158. T::BlockNumber::from(DEFAULT_VOTING_PERIOD_IN_SECS /
  159. (<T as timestamp::Trait>::MinimumPeriod::get().saturated_into::<u32>() * 2));
  160. NameMaxLen get(name_max_len) config(): u32 = DEFAULT_NAME_MAX_LEN;
  161. DescriptionMaxLen get(description_max_len) config(): u32 = DEFAULT_DESCRIPTION_MAX_LEN;
  162. WasmCodeMaxLen get(wasm_code_max_len) config(): u32 = DEFAULT_WASM_CODE_MAX_LEN;
  163. // Persistent state (always relevant, changes constantly):
  164. /// Count of all proposals that have been created.
  165. ProposalCount get(proposal_count): u32;
  166. /// Get proposal details by its id.
  167. Proposals get(proposals): map u32 => RuntimeUpgradeProposal<T::AccountId, BalanceOf<T>, T::BlockNumber, T::Hash>;
  168. /// Ids of proposals that are open for voting (have not been finalized yet).
  169. ActiveProposalIds get(active_proposal_ids): Vec<u32> = vec![];
  170. /// Get WASM code of runtime upgrade by hash of its content.
  171. WasmCodeByHash get(wasm_code_by_hash): map T::Hash => Vec<u8>;
  172. VotesByProposal get(votes_by_proposal): map u32 => Vec<(T::AccountId, VoteKind)>;
  173. // TODO Rethink: this can be replaced with: votes_by_proposal.find(|vote| vote.0 == proposer)
  174. VoteByAccountAndProposal get(vote_by_account_and_proposal): map (T::AccountId, u32) => VoteKind;
  175. TallyResults get(tally_results): map u32 => TallyResult<T::BlockNumber>;
  176. }
  177. }
  178. decl_module! {
  179. pub struct Module<T: Trait> for enum Call where origin: T::Origin {
  180. fn deposit_event<T>() = default;
  181. /// Use next code to create a proposal from Substrate UI's web console:
  182. /// ```js
  183. /// post({ sender: runtime.indices.ss58Decode('F7Gh'), call: calls.proposals.createProposal(2500, "0x123", "0x456", "0x789") }).tie(console.log)
  184. /// ```
  185. fn create_proposal(
  186. origin,
  187. stake: BalanceOf<T>,
  188. name: Vec<u8>,
  189. description: Vec<u8>,
  190. wasm_code: Vec<u8>
  191. ) {
  192. let proposer = ensure_signed(origin)?;
  193. ensure!(Self::can_participate(&proposer), MSG_ONLY_MEMBERS_CAN_PROPOSE);
  194. ensure!(stake >= Self::min_stake(), MSG_STAKE_IS_TOO_LOW);
  195. ensure!(!name.is_empty(), MSG_EMPTY_NAME_PROVIDED);
  196. ensure!(name.len() as u32 <= Self::name_max_len(), MSG_TOO_LONG_NAME);
  197. ensure!(!description.is_empty(), MSG_EMPTY_DESCRIPTION_PROVIDED);
  198. ensure!(description.len() as u32 <= Self::description_max_len(), MSG_TOO_LONG_DESCRIPTION);
  199. ensure!(!wasm_code.is_empty(), MSG_EMPTY_WASM_CODE_PROVIDED);
  200. ensure!(wasm_code.len() as u32 <= Self::wasm_code_max_len(), MSG_TOO_LONG_WASM_CODE);
  201. // Lock proposer's stake:
  202. T::Currency::reserve(&proposer, stake)
  203. .map_err(|_| MSG_STAKE_IS_GREATER_THAN_BALANCE)?;
  204. let proposal_id = Self::proposal_count() + 1;
  205. ProposalCount::put(proposal_id);
  206. // See in substrate repo @ srml/contract/src/wasm/code_cache.rs:73
  207. let wasm_hash = T::Hashing::hash(&wasm_code);
  208. let new_proposal = RuntimeUpgradeProposal {
  209. id: proposal_id,
  210. proposer: proposer.clone(),
  211. stake,
  212. name,
  213. description,
  214. wasm_hash,
  215. proposed_at: Self::current_block(),
  216. status: Active
  217. };
  218. if !<WasmCodeByHash<T>>::exists(wasm_hash) {
  219. <WasmCodeByHash<T>>::insert(wasm_hash, wasm_code);
  220. }
  221. <Proposals<T>>::insert(proposal_id, new_proposal);
  222. ActiveProposalIds::mutate(|ids| ids.push(proposal_id));
  223. Self::deposit_event(RawEvent::ProposalCreated(proposer.clone(), proposal_id));
  224. // Auto-vote with Approve if proposer is a councilor:
  225. if Self::is_councilor(&proposer) {
  226. Self::_process_vote(proposer, proposal_id, Approve)?;
  227. }
  228. }
  229. /// Use next code to create a proposal from Substrate UI's web console:
  230. /// ```js
  231. /// post({ sender: runtime.indices.ss58Decode('F7Gh'), call: calls.proposals.voteOnProposal(1, { option: "Approve", _type: "VoteKind" }) }).tie(console.log)
  232. /// ```
  233. fn vote_on_proposal(origin, proposal_id: u32, vote: VoteKind) {
  234. let voter = ensure_signed(origin)?;
  235. ensure!(Self::is_councilor(&voter), MSG_ONLY_COUNCILORS_CAN_VOTE);
  236. ensure!(<Proposals<T>>::exists(proposal_id), MSG_PROPOSAL_NOT_FOUND);
  237. let proposal = Self::proposals(proposal_id);
  238. ensure!(proposal.status == Active, MSG_PROPOSAL_FINALIZED);
  239. let not_expired = !Self::is_voting_period_expired(proposal.proposed_at);
  240. ensure!(not_expired, MSG_PROPOSAL_EXPIRED);
  241. let did_not_vote_before = !<VoteByAccountAndProposal<T>>::exists((voter.clone(), proposal_id));
  242. ensure!(did_not_vote_before, MSG_YOU_ALREADY_VOTED);
  243. Self::_process_vote(voter, proposal_id, vote)?;
  244. }
  245. // TODO add 'reason' why a proposer wants to cancel (UX + feedback)?
  246. /// Cancel a proposal by its original proposer. Some fee will be withdrawn from his balance.
  247. fn cancel_proposal(origin, proposal_id: u32) {
  248. let proposer = ensure_signed(origin)?;
  249. ensure!(<Proposals<T>>::exists(proposal_id), MSG_PROPOSAL_NOT_FOUND);
  250. let proposal = Self::proposals(proposal_id);
  251. ensure!(proposer == proposal.proposer, MSG_YOU_DONT_OWN_THIS_PROPOSAL);
  252. ensure!(proposal.status == Active, MSG_PROPOSAL_FINALIZED);
  253. // Spend some minimum fee on proposer's balance for canceling a proposal
  254. let fee = Self::cancellation_fee();
  255. let _ = T::Currency::slash_reserved(&proposer, fee);
  256. // Return unspent part of remaining staked deposit (after taking some fee)
  257. let left_stake = proposal.stake - fee;
  258. let _ = T::Currency::unreserve(&proposer, left_stake);
  259. Self::_update_proposal_status(proposal_id, Cancelled)?;
  260. Self::deposit_event(RawEvent::ProposalCanceled(proposer, proposal_id));
  261. }
  262. // Called on every block
  263. fn on_finalize(n: T::BlockNumber) {
  264. if let Err(e) = Self::end_block(n) {
  265. print(e);
  266. }
  267. }
  268. /// Cancel a proposal and return stake without slashing
  269. fn veto_proposal(origin, proposal_id: u32) {
  270. ensure_root(origin)?;
  271. ensure!(<Proposals<T>>::exists(proposal_id), MSG_PROPOSAL_NOT_FOUND);
  272. let proposal = Self::proposals(proposal_id);
  273. ensure!(proposal.status == Active, MSG_PROPOSAL_FINALIZED);
  274. let _ = T::Currency::unreserve(&proposal.proposer, proposal.stake);
  275. Self::_update_proposal_status(proposal_id, Cancelled)?;
  276. Self::deposit_event(RawEvent::ProposalVetoed(proposal_id));
  277. }
  278. fn set_approval_quorum(origin, new_value: u32) {
  279. ensure_root(origin)?;
  280. ensure!(new_value > 0, "approval quorom must be greater than zero");
  281. ApprovalQuorum::put(new_value);
  282. }
  283. }
  284. }
  285. impl<T: Trait> Module<T> {
  286. fn current_block() -> T::BlockNumber {
  287. <system::Module<T>>::block_number()
  288. }
  289. fn can_participate(sender: &T::AccountId) -> bool {
  290. !T::Currency::free_balance(sender).is_zero()
  291. && <membership::members::Module<T>>::is_member_account(sender)
  292. }
  293. fn is_councilor(sender: &T::AccountId) -> bool {
  294. <council::Module<T>>::is_councilor(sender)
  295. }
  296. fn councilors_count() -> u32 {
  297. <council::Module<T>>::active_council().len() as u32
  298. }
  299. fn approval_quorum_seats() -> u32 {
  300. (Self::approval_quorum() * Self::councilors_count()) / 100
  301. }
  302. fn is_voting_period_expired(proposed_at: T::BlockNumber) -> bool {
  303. Self::current_block() >= proposed_at + Self::voting_period()
  304. }
  305. fn _process_vote(voter: T::AccountId, proposal_id: u32, vote: VoteKind) -> dispatch::Result {
  306. let new_vote = (voter.clone(), vote.clone());
  307. if <VotesByProposal<T>>::exists(proposal_id) {
  308. // Append a new vote to other votes on this proposal:
  309. <VotesByProposal<T>>::mutate(proposal_id, |votes| votes.push(new_vote));
  310. } else {
  311. // This is the first vote on this proposal:
  312. <VotesByProposal<T>>::insert(proposal_id, vec![new_vote]);
  313. }
  314. <VoteByAccountAndProposal<T>>::insert((voter.clone(), proposal_id), &vote);
  315. Self::deposit_event(RawEvent::Voted(voter, proposal_id, vote));
  316. Ok(())
  317. }
  318. fn end_block(_now: T::BlockNumber) -> dispatch::Result {
  319. // TODO refactor this method
  320. // TODO iterate over not expired proposals and tally
  321. Self::tally()?;
  322. // TODO approve or reject a proposal
  323. Ok(())
  324. }
  325. /// Get the voters for the current proposal.
  326. pub fn tally() -> dispatch::Result {
  327. let councilors: u32 = Self::councilors_count();
  328. let quorum: u32 = Self::approval_quorum_seats();
  329. for &proposal_id in Self::active_proposal_ids().iter() {
  330. let votes = Self::votes_by_proposal(proposal_id);
  331. let mut abstentions: u32 = 0;
  332. let mut approvals: u32 = 0;
  333. let mut rejections: u32 = 0;
  334. let mut slashes: u32 = 0;
  335. for (_, vote) in votes.iter() {
  336. match vote {
  337. Abstain => abstentions += 1,
  338. Approve => approvals += 1,
  339. Reject => rejections += 1,
  340. Slash => slashes += 1,
  341. }
  342. }
  343. let proposal = Self::proposals(proposal_id);
  344. let is_expired = Self::is_voting_period_expired(proposal.proposed_at);
  345. // We need to check that the council is not empty because otherwise,
  346. // if there is no votes on a proposal it will be counted as if
  347. // all 100% (zero) councilors voted on the proposal and should be approved.
  348. let non_empty_council = councilors > 0;
  349. let all_councilors_voted = non_empty_council && votes.len() as u32 == councilors;
  350. let all_councilors_slashed = non_empty_council && slashes == councilors;
  351. let quorum_reached = quorum > 0 && approvals >= quorum;
  352. // Don't approve a proposal right after quorum reached
  353. // if not all councilors casted their votes.
  354. // Instead let other councilors cast their vote
  355. // up until the proposal's expired.
  356. let new_status: Option<ProposalStatus> = if all_councilors_slashed {
  357. Some(Slashed)
  358. } else if all_councilors_voted {
  359. if quorum_reached {
  360. Some(Approved)
  361. } else {
  362. Some(Rejected)
  363. }
  364. } else if is_expired {
  365. if quorum_reached {
  366. Some(Approved)
  367. } else {
  368. // Proposal has been expired and quorum not reached.
  369. Some(Expired)
  370. }
  371. } else {
  372. // Councilors still have time to vote on this proposal.
  373. None
  374. };
  375. // TODO move next block outside of tally to 'end_block'
  376. if let Some(status) = new_status {
  377. Self::_update_proposal_status(proposal_id, status.clone())?;
  378. let tally_result = TallyResult {
  379. proposal_id,
  380. abstentions,
  381. approvals,
  382. rejections,
  383. slashes,
  384. status,
  385. finalized_at: Self::current_block(),
  386. };
  387. <TallyResults<T>>::insert(proposal_id, &tally_result);
  388. Self::deposit_event(RawEvent::TallyFinalized(tally_result));
  389. }
  390. }
  391. Ok(())
  392. }
  393. /// Updates proposal status and removes proposal from active ids.
  394. fn _update_proposal_status(proposal_id: u32, new_status: ProposalStatus) -> dispatch::Result {
  395. let all_active_ids = Self::active_proposal_ids();
  396. let all_len = all_active_ids.len();
  397. let other_active_ids: Vec<u32> = all_active_ids
  398. .into_iter()
  399. .filter(|&id| id != proposal_id)
  400. .collect();
  401. let not_found_in_active = other_active_ids.len() == all_len;
  402. if not_found_in_active {
  403. // Seems like this proposal's status has been updated and removed from active.
  404. Err(MSG_PROPOSAL_STATUS_ALREADY_UPDATED)
  405. } else {
  406. let pid = proposal_id.clone();
  407. match new_status {
  408. Slashed => Self::_slash_proposal(pid)?,
  409. Rejected | Expired => Self::_reject_proposal(pid)?,
  410. Approved => Self::_approve_proposal(pid)?,
  411. Active | Cancelled => { /* nothing */ }
  412. }
  413. ActiveProposalIds::put(other_active_ids);
  414. <Proposals<T>>::mutate(proposal_id, |p| p.status = new_status.clone());
  415. Self::deposit_event(RawEvent::ProposalStatusUpdated(proposal_id, new_status));
  416. Ok(())
  417. }
  418. }
  419. /// Slash a proposal. The staked deposit will be slashed.
  420. fn _slash_proposal(proposal_id: u32) -> dispatch::Result {
  421. let proposal = Self::proposals(proposal_id);
  422. // Slash proposer's stake:
  423. let _ = T::Currency::slash_reserved(&proposal.proposer, proposal.stake);
  424. Ok(())
  425. }
  426. /// Reject a proposal. The staked deposit will be returned to a proposer.
  427. fn _reject_proposal(proposal_id: u32) -> dispatch::Result {
  428. let proposal = Self::proposals(proposal_id);
  429. let proposer = proposal.proposer;
  430. // Spend some minimum fee on proposer's balance to prevent spamming attacks:
  431. let fee = Self::rejection_fee();
  432. let _ = T::Currency::slash_reserved(&proposer, fee);
  433. // Return unspent part of remaining staked deposit (after taking some fee):
  434. let left_stake = proposal.stake - fee;
  435. let _ = T::Currency::unreserve(&proposer, left_stake);
  436. Ok(())
  437. }
  438. /// Approve a proposal. The staked deposit will be returned.
  439. fn _approve_proposal(proposal_id: u32) -> dispatch::Result {
  440. let proposal = Self::proposals(proposal_id);
  441. let wasm_code = Self::wasm_code_by_hash(proposal.wasm_hash);
  442. // Return staked deposit to proposer:
  443. let _ = T::Currency::unreserve(&proposal.proposer, proposal.stake);
  444. // Update wasm code of node's runtime:
  445. <system::Module<T>>::set_code(system::RawOrigin::Root.into(), wasm_code)?;
  446. Self::deposit_event(RawEvent::RuntimeUpdated(proposal_id, proposal.wasm_hash));
  447. Ok(())
  448. }
  449. }
  450. #[cfg(test)]
  451. mod tests {
  452. use super::*;
  453. use primitives::{Blake2Hasher, H256};
  454. use runtime_io::with_externalities;
  455. // The testing primitives are very useful for avoiding having to work with signatures
  456. // or public keys. `u64` is used as the `AccountId` and no `Signature`s are requried.
  457. use runtime_primitives::{
  458. testing::Header,
  459. traits::{BlakeTwo256, IdentityLookup},
  460. Perbill,
  461. };
  462. use srml_support::*;
  463. impl_outer_origin! {
  464. pub enum Origin for Test {}
  465. }
  466. // Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted.
  467. #[derive(Clone, PartialEq, Eq, Debug)]
  468. pub struct Test;
  469. parameter_types! {
  470. pub const BlockHashCount: u64 = 250;
  471. pub const MaximumBlockWeight: u32 = 1024;
  472. pub const MaximumBlockLength: u32 = 2 * 1024;
  473. pub const AvailableBlockRatio: Perbill = Perbill::one();
  474. pub const MinimumPeriod: u64 = 5;
  475. }
  476. impl system::Trait for Test {
  477. type Origin = Origin;
  478. type Index = u64;
  479. type BlockNumber = u64;
  480. type Call = ();
  481. type Hash = H256;
  482. type Hashing = BlakeTwo256;
  483. type AccountId = u64;
  484. type Lookup = IdentityLookup<Self::AccountId>;
  485. type Header = Header;
  486. type WeightMultiplierUpdate = ();
  487. type Event = ();
  488. type BlockHashCount = BlockHashCount;
  489. type MaximumBlockWeight = MaximumBlockWeight;
  490. type MaximumBlockLength = MaximumBlockLength;
  491. type AvailableBlockRatio = AvailableBlockRatio;
  492. type Version = ();
  493. }
  494. impl timestamp::Trait for Test {
  495. type Moment = u64;
  496. type OnTimestampSet = ();
  497. type MinimumPeriod = MinimumPeriod;
  498. }
  499. parameter_types! {
  500. pub const ExistentialDeposit: u32 = 0;
  501. pub const TransferFee: u32 = 0;
  502. pub const CreationFee: u32 = 0;
  503. pub const TransactionBaseFee: u32 = 1;
  504. pub const TransactionByteFee: u32 = 0;
  505. pub const InitialMembersBalance: u32 = 0;
  506. }
  507. impl balances::Trait for Test {
  508. /// The type for recording an account's balance.
  509. type Balance = u64;
  510. /// What to do if an account's free balance gets zeroed.
  511. type OnFreeBalanceZero = ();
  512. /// What to do if a new account is created.
  513. type OnNewAccount = ();
  514. /// The ubiquitous event type.
  515. type Event = ();
  516. type TransactionPayment = ();
  517. type DustRemoval = ();
  518. type TransferPayment = ();
  519. type ExistentialDeposit = ExistentialDeposit;
  520. type TransferFee = TransferFee;
  521. type CreationFee = CreationFee;
  522. type TransactionBaseFee = TransactionBaseFee;
  523. type TransactionByteFee = TransactionByteFee;
  524. type WeightToFee = ();
  525. }
  526. impl council::Trait for Test {
  527. type Event = ();
  528. type CouncilTermEnded = ();
  529. }
  530. impl GovernanceCurrency for Test {
  531. type Currency = balances::Module<Self>;
  532. }
  533. impl membership::members::Trait for Test {
  534. type Event = ();
  535. type MemberId = u32;
  536. type PaidTermId = u32;
  537. type SubscriptionId = u32;
  538. type ActorId = u32;
  539. type InitialMembersBalance = InitialMembersBalance;
  540. }
  541. impl Trait for Test {
  542. type Event = ();
  543. }
  544. type System = system::Module<Test>;
  545. type Balances = balances::Module<Test>;
  546. type Proposals = Module<Test>;
  547. const COUNCILOR1: u64 = 1;
  548. const COUNCILOR2: u64 = 2;
  549. const COUNCILOR3: u64 = 3;
  550. const COUNCILOR4: u64 = 4;
  551. const COUNCILOR5: u64 = 5;
  552. const PROPOSER1: u64 = 11;
  553. const PROPOSER2: u64 = 12;
  554. const NOT_COUNCILOR: u64 = 22;
  555. const ALL_COUNCILORS: [u64; 5] = [COUNCILOR1, COUNCILOR2, COUNCILOR3, COUNCILOR4, COUNCILOR5];
  556. // TODO Figure out how to test Events in test... (low priority)
  557. // mod proposals {
  558. // pub use ::Event;
  559. // }
  560. // impl_outer_event!{
  561. // pub enum TestEvent for Test {
  562. // balances<T>,system<T>,proposals<T>,
  563. // }
  564. // }
  565. // This function basically just builds a genesis storage key/value store according to
  566. // our desired mockup.
  567. fn new_test_ext() -> runtime_io::TestExternalities<Blake2Hasher> {
  568. let mut t = system::GenesisConfig::default()
  569. .build_storage::<Test>()
  570. .unwrap();
  571. // We use default for brevity, but you can configure as desired if needed.
  572. balances::GenesisConfig::<Test>::default()
  573. .assimilate_storage(&mut t)
  574. .unwrap();
  575. let council_mock: council::Seats<u64, u64> = ALL_COUNCILORS
  576. .iter()
  577. .map(|&c| council::Seat {
  578. member: c,
  579. stake: 0u64,
  580. backers: vec![],
  581. })
  582. .collect();
  583. council::GenesisConfig::<Test> {
  584. active_council: council_mock,
  585. term_ends_at: 0,
  586. }
  587. .assimilate_storage(&mut t)
  588. .unwrap();
  589. membership::members::GenesisConfig::<Test> {
  590. default_paid_membership_fee: 0,
  591. members: vec![
  592. (PROPOSER1, "alice".into(), "".into(), "".into()),
  593. (PROPOSER2, "bobby".into(), "".into(), "".into()),
  594. (COUNCILOR1, "councilor1".into(), "".into(), "".into()),
  595. (COUNCILOR2, "councilor2".into(), "".into(), "".into()),
  596. (COUNCILOR3, "councilor3".into(), "".into(), "".into()),
  597. (COUNCILOR4, "councilor4".into(), "".into(), "".into()),
  598. (COUNCILOR5, "councilor5".into(), "".into(), "".into()),
  599. ],
  600. }
  601. .assimilate_storage(&mut t)
  602. .unwrap();
  603. // t.extend(GenesisConfig::<Test>{
  604. // // Here we can override defaults.
  605. // }.build_storage().unwrap().0);
  606. t.into()
  607. }
  608. /// A shortcut to get minimum stake in tests.
  609. fn min_stake() -> u64 {
  610. Proposals::min_stake()
  611. }
  612. /// A shortcut to get cancellation fee in tests.
  613. fn cancellation_fee() -> u64 {
  614. Proposals::cancellation_fee()
  615. }
  616. /// A shortcut to get rejection fee in tests.
  617. fn rejection_fee() -> u64 {
  618. Proposals::rejection_fee()
  619. }
  620. /// Initial balance of Proposer 1.
  621. fn initial_balance() -> u64 {
  622. (min_stake() as f64 * 2.5) as u64
  623. }
  624. fn name() -> Vec<u8> {
  625. b"Proposal Name".to_vec()
  626. }
  627. fn description() -> Vec<u8> {
  628. b"Proposal Description".to_vec()
  629. }
  630. fn wasm_code() -> Vec<u8> {
  631. b"Proposal Wasm Code".to_vec()
  632. }
  633. fn _create_default_proposal() -> dispatch::Result {
  634. _create_proposal(None, None, None, None, None)
  635. }
  636. fn _create_proposal(
  637. origin: Option<u64>,
  638. stake: Option<u64>,
  639. name: Option<Vec<u8>>,
  640. description: Option<Vec<u8>>,
  641. wasm_code: Option<Vec<u8>>,
  642. ) -> dispatch::Result {
  643. Proposals::create_proposal(
  644. Origin::signed(origin.unwrap_or(PROPOSER1)),
  645. stake.unwrap_or(min_stake()),
  646. name.unwrap_or(self::name()),
  647. description.unwrap_or(self::description()),
  648. wasm_code.unwrap_or(self::wasm_code()),
  649. )
  650. }
  651. fn get_runtime_code() -> Option<Vec<u8>> {
  652. storage::unhashed::get_raw(well_known_keys::CODE)
  653. }
  654. macro_rules! assert_runtime_code_empty {
  655. () => {
  656. assert_eq!(get_runtime_code(), Some(vec![]))
  657. };
  658. }
  659. macro_rules! assert_runtime_code {
  660. ($code:expr) => {
  661. assert_eq!(get_runtime_code(), Some($code))
  662. };
  663. }
  664. #[test]
  665. fn check_default_values() {
  666. with_externalities(&mut new_test_ext(), || {
  667. assert_eq!(Proposals::approval_quorum(), DEFAULT_APPROVAL_QUORUM);
  668. assert_eq!(
  669. Proposals::min_stake(),
  670. BalanceOf::<Test>::from(DEFAULT_MIN_STAKE)
  671. );
  672. assert_eq!(
  673. Proposals::cancellation_fee(),
  674. BalanceOf::<Test>::from(DEFAULT_CANCELLATION_FEE)
  675. );
  676. assert_eq!(
  677. Proposals::rejection_fee(),
  678. BalanceOf::<Test>::from(DEFAULT_REJECTION_FEE)
  679. );
  680. assert_eq!(Proposals::name_max_len(), DEFAULT_NAME_MAX_LEN);
  681. assert_eq!(
  682. Proposals::description_max_len(),
  683. DEFAULT_DESCRIPTION_MAX_LEN
  684. );
  685. assert_eq!(Proposals::wasm_code_max_len(), DEFAULT_WASM_CODE_MAX_LEN);
  686. assert_eq!(Proposals::proposal_count(), 0);
  687. assert!(Proposals::active_proposal_ids().is_empty());
  688. });
  689. }
  690. #[test]
  691. fn member_create_proposal() {
  692. with_externalities(&mut new_test_ext(), || {
  693. let _ = Balances::deposit_creating(&PROPOSER1, initial_balance());
  694. assert_ok!(_create_default_proposal());
  695. assert_eq!(Proposals::active_proposal_ids().len(), 1);
  696. assert_eq!(Proposals::active_proposal_ids()[0], 1);
  697. let wasm_hash = BlakeTwo256::hash(&wasm_code());
  698. let expected_proposal = RuntimeUpgradeProposal {
  699. id: 1,
  700. proposer: PROPOSER1,
  701. stake: min_stake(),
  702. name: name(),
  703. description: description(),
  704. wasm_hash,
  705. proposed_at: 1,
  706. status: Active,
  707. };
  708. assert_eq!(Proposals::proposals(1), expected_proposal);
  709. // Check that stake amount has been locked on proposer's balance:
  710. assert_eq!(
  711. Balances::free_balance(PROPOSER1),
  712. initial_balance() - min_stake()
  713. );
  714. assert_eq!(Balances::reserved_balance(PROPOSER1), min_stake());
  715. // TODO expect event ProposalCreated(AccountId, u32)
  716. });
  717. }
  718. #[test]
  719. fn not_member_cannot_create_proposal() {
  720. with_externalities(&mut new_test_ext(), || {
  721. // In this test a proposer has an empty balance
  722. // thus he is not considered as a member.
  723. assert_eq!(
  724. _create_default_proposal(),
  725. Err(MSG_ONLY_MEMBERS_CAN_PROPOSE)
  726. );
  727. });
  728. }
  729. #[test]
  730. fn cannot_create_proposal_with_small_stake() {
  731. with_externalities(&mut new_test_ext(), || {
  732. let _ = Balances::deposit_creating(&PROPOSER1, initial_balance());
  733. assert_eq!(
  734. _create_proposal(None, Some(min_stake() - 1), None, None, None),
  735. Err(MSG_STAKE_IS_TOO_LOW)
  736. );
  737. // Check that balances remain unchanged afer a failed attempt to create a proposal:
  738. assert_eq!(Balances::free_balance(PROPOSER1), initial_balance());
  739. assert_eq!(Balances::reserved_balance(PROPOSER1), 0);
  740. });
  741. }
  742. #[test]
  743. fn cannot_create_proposal_when_stake_is_greater_than_balance() {
  744. with_externalities(&mut new_test_ext(), || {
  745. let _ = Balances::deposit_creating(&PROPOSER1, initial_balance());
  746. assert_eq!(
  747. _create_proposal(None, Some(initial_balance() + 1), None, None, None),
  748. Err(MSG_STAKE_IS_GREATER_THAN_BALANCE)
  749. );
  750. // Check that balances remain unchanged afer a failed attempt to create a proposal:
  751. assert_eq!(Balances::free_balance(PROPOSER1), initial_balance());
  752. assert_eq!(Balances::reserved_balance(PROPOSER1), 0);
  753. });
  754. }
  755. #[test]
  756. fn cannot_create_proposal_with_empty_values() {
  757. with_externalities(&mut new_test_ext(), || {
  758. let _ = Balances::deposit_creating(&PROPOSER1, initial_balance());
  759. // Empty name:
  760. assert_eq!(
  761. _create_proposal(None, None, Some(vec![]), None, None),
  762. Err(MSG_EMPTY_NAME_PROVIDED)
  763. );
  764. // Empty description:
  765. assert_eq!(
  766. _create_proposal(None, None, None, Some(vec![]), None),
  767. Err(MSG_EMPTY_DESCRIPTION_PROVIDED)
  768. );
  769. // Empty WASM code:
  770. assert_eq!(
  771. _create_proposal(None, None, None, None, Some(vec![])),
  772. Err(MSG_EMPTY_WASM_CODE_PROVIDED)
  773. );
  774. });
  775. }
  776. #[test]
  777. fn cannot_create_proposal_with_too_long_values() {
  778. with_externalities(&mut new_test_ext(), || {
  779. let _ = Balances::deposit_creating(&PROPOSER1, initial_balance());
  780. // Too long name:
  781. assert_eq!(
  782. _create_proposal(None, None, Some(too_long_name()), None, None),
  783. Err(MSG_TOO_LONG_NAME)
  784. );
  785. // Too long description:
  786. assert_eq!(
  787. _create_proposal(None, None, None, Some(too_long_description()), None),
  788. Err(MSG_TOO_LONG_DESCRIPTION)
  789. );
  790. // Too long WASM code:
  791. assert_eq!(
  792. _create_proposal(None, None, None, None, Some(too_long_wasm_code())),
  793. Err(MSG_TOO_LONG_WASM_CODE)
  794. );
  795. });
  796. }
  797. fn too_long_name() -> Vec<u8> {
  798. vec![65; Proposals::name_max_len() as usize + 1]
  799. }
  800. fn too_long_description() -> Vec<u8> {
  801. vec![65; Proposals::description_max_len() as usize + 1]
  802. }
  803. fn too_long_wasm_code() -> Vec<u8> {
  804. vec![65; Proposals::wasm_code_max_len() as usize + 1]
  805. }
  806. // -------------------------------------------------------------------
  807. // Cancellation
  808. #[test]
  809. fn owner_cancel_proposal() {
  810. with_externalities(&mut new_test_ext(), || {
  811. let _ = Balances::deposit_creating(&PROPOSER1, initial_balance());
  812. assert_ok!(_create_default_proposal());
  813. assert_ok!(Proposals::cancel_proposal(Origin::signed(PROPOSER1), 1));
  814. assert_eq!(Proposals::proposals(1).status, Cancelled);
  815. assert!(Proposals::active_proposal_ids().is_empty());
  816. // Check that proposer's balance reduced by cancellation fee and other part of his stake returned to his balance:
  817. assert_eq!(
  818. Balances::free_balance(PROPOSER1),
  819. initial_balance() - cancellation_fee()
  820. );
  821. assert_eq!(Balances::reserved_balance(PROPOSER1), 0);
  822. // TODO expect event ProposalCancelled(AccountId, u32)
  823. });
  824. }
  825. #[test]
  826. fn owner_cannot_cancel_proposal_if_its_finalized() {
  827. with_externalities(&mut new_test_ext(), || {
  828. let _ = Balances::deposit_creating(&PROPOSER1, initial_balance());
  829. assert_ok!(_create_default_proposal());
  830. assert_ok!(Proposals::cancel_proposal(Origin::signed(PROPOSER1), 1));
  831. assert_eq!(Proposals::proposals(1).status, Cancelled);
  832. // Get balances updated after cancelling a proposal:
  833. let updated_free_balance = Balances::free_balance(PROPOSER1);
  834. let updated_reserved_balance = Balances::reserved_balance(PROPOSER1);
  835. assert_eq!(
  836. Proposals::cancel_proposal(Origin::signed(PROPOSER1), 1),
  837. Err(MSG_PROPOSAL_FINALIZED)
  838. );
  839. // Check that proposer's balance and locked stake haven't been changed:
  840. assert_eq!(Balances::free_balance(PROPOSER1), updated_free_balance);
  841. assert_eq!(
  842. Balances::reserved_balance(PROPOSER1),
  843. updated_reserved_balance
  844. );
  845. });
  846. }
  847. #[test]
  848. fn not_owner_cannot_cancel_proposal() {
  849. with_externalities(&mut new_test_ext(), || {
  850. let _ = Balances::deposit_creating(&PROPOSER1, initial_balance());
  851. let _ = Balances::deposit_creating(&PROPOSER2, initial_balance());
  852. assert_ok!(_create_default_proposal());
  853. assert_eq!(
  854. Proposals::cancel_proposal(Origin::signed(PROPOSER2), 1),
  855. Err(MSG_YOU_DONT_OWN_THIS_PROPOSAL)
  856. );
  857. });
  858. }
  859. // -------------------------------------------------------------------
  860. // Voting
  861. #[test]
  862. fn councilor_vote_on_proposal() {
  863. with_externalities(&mut new_test_ext(), || {
  864. let _ = Balances::deposit_creating(&PROPOSER1, initial_balance());
  865. assert_ok!(_create_default_proposal());
  866. assert_ok!(Proposals::vote_on_proposal(
  867. Origin::signed(COUNCILOR1),
  868. 1,
  869. Approve
  870. ));
  871. // Check that a vote has been saved:
  872. assert_eq!(Proposals::votes_by_proposal(1), vec![(COUNCILOR1, Approve)]);
  873. assert_eq!(
  874. Proposals::vote_by_account_and_proposal((COUNCILOR1, 1)),
  875. Approve
  876. );
  877. // TODO expect event Voted(PROPOSER1, 1, Approve)
  878. });
  879. }
  880. #[test]
  881. fn councilor_cannot_vote_on_proposal_twice() {
  882. with_externalities(&mut new_test_ext(), || {
  883. let _ = Balances::deposit_creating(&PROPOSER1, initial_balance());
  884. assert_ok!(_create_default_proposal());
  885. assert_ok!(Proposals::vote_on_proposal(
  886. Origin::signed(COUNCILOR1),
  887. 1,
  888. Approve
  889. ));
  890. assert_eq!(
  891. Proposals::vote_on_proposal(Origin::signed(COUNCILOR1), 1, Approve),
  892. Err(MSG_YOU_ALREADY_VOTED)
  893. );
  894. });
  895. }
  896. #[test]
  897. fn autovote_with_approve_when_councilor_creates_proposal() {
  898. with_externalities(&mut new_test_ext(), || {
  899. let _ = Balances::deposit_creating(&COUNCILOR1, initial_balance());
  900. assert_ok!(_create_proposal(Some(COUNCILOR1), None, None, None, None));
  901. // Check that a vote has been sent automatically,
  902. // such as the proposer is a councilor:
  903. assert_eq!(Proposals::votes_by_proposal(1), vec![(COUNCILOR1, Approve)]);
  904. assert_eq!(
  905. Proposals::vote_by_account_and_proposal((COUNCILOR1, 1)),
  906. Approve
  907. );
  908. });
  909. }
  910. #[test]
  911. fn not_councilor_cannot_vote_on_proposal() {
  912. with_externalities(&mut new_test_ext(), || {
  913. let _ = Balances::deposit_creating(&PROPOSER1, initial_balance());
  914. assert_ok!(_create_default_proposal());
  915. assert_eq!(
  916. Proposals::vote_on_proposal(Origin::signed(NOT_COUNCILOR), 1, Approve),
  917. Err(MSG_ONLY_COUNCILORS_CAN_VOTE)
  918. );
  919. });
  920. }
  921. #[test]
  922. fn councilor_cannot_vote_on_proposal_if_it_has_been_cancelled() {
  923. with_externalities(&mut new_test_ext(), || {
  924. let _ = Balances::deposit_creating(&PROPOSER1, initial_balance());
  925. assert_ok!(_create_default_proposal());
  926. assert_ok!(Proposals::cancel_proposal(Origin::signed(PROPOSER1), 1));
  927. assert_eq!(
  928. Proposals::vote_on_proposal(Origin::signed(COUNCILOR1), 1, Approve),
  929. Err(MSG_PROPOSAL_FINALIZED)
  930. );
  931. });
  932. }
  933. #[test]
  934. fn councilor_cannot_vote_on_proposal_if_tally_has_been_finalized() {
  935. with_externalities(&mut new_test_ext(), || {
  936. let _ = Balances::deposit_creating(&PROPOSER1, initial_balance());
  937. assert_ok!(_create_default_proposal());
  938. // All councilors vote with 'Approve' on proposal:
  939. let mut expected_votes: Vec<(u64, VoteKind)> = vec![];
  940. for &councilor in ALL_COUNCILORS.iter() {
  941. expected_votes.push((councilor, Approve));
  942. assert_ok!(Proposals::vote_on_proposal(
  943. Origin::signed(councilor),
  944. 1,
  945. Approve
  946. ));
  947. assert_eq!(
  948. Proposals::vote_by_account_and_proposal((councilor, 1)),
  949. Approve
  950. );
  951. }
  952. assert_eq!(Proposals::votes_by_proposal(1), expected_votes);
  953. System::set_block_number(2);
  954. let _ = Proposals::end_block(2);
  955. assert!(Proposals::active_proposal_ids().is_empty());
  956. assert_eq!(Proposals::proposals(1).status, Approved);
  957. // Try to vote on finalized proposal:
  958. assert_eq!(
  959. Proposals::vote_on_proposal(Origin::signed(COUNCILOR1), 1, Reject),
  960. Err(MSG_PROPOSAL_FINALIZED)
  961. );
  962. });
  963. }
  964. // -------------------------------------------------------------------
  965. // Tally + Outcome:
  966. #[test]
  967. fn approve_proposal_when_all_councilors_approved_it() {
  968. with_externalities(&mut new_test_ext(), || {
  969. let _ = Balances::deposit_creating(&PROPOSER1, initial_balance());
  970. assert_ok!(_create_default_proposal());
  971. // All councilors approved:
  972. let mut expected_votes: Vec<(u64, VoteKind)> = vec![];
  973. for &councilor in ALL_COUNCILORS.iter() {
  974. expected_votes.push((councilor, Approve));
  975. assert_ok!(Proposals::vote_on_proposal(
  976. Origin::signed(councilor),
  977. 1,
  978. Approve
  979. ));
  980. assert_eq!(
  981. Proposals::vote_by_account_and_proposal((councilor, 1)),
  982. Approve
  983. );
  984. }
  985. assert_eq!(Proposals::votes_by_proposal(1), expected_votes);
  986. assert_runtime_code_empty!();
  987. System::set_block_number(2);
  988. let _ = Proposals::end_block(2);
  989. // Check that runtime code has been updated after proposal approved.
  990. assert_runtime_code!(wasm_code());
  991. assert!(Proposals::active_proposal_ids().is_empty());
  992. assert_eq!(Proposals::proposals(1).status, Approved);
  993. assert_eq!(
  994. Proposals::tally_results(1),
  995. TallyResult {
  996. proposal_id: 1,
  997. abstentions: 0,
  998. approvals: ALL_COUNCILORS.len() as u32,
  999. rejections: 0,
  1000. slashes: 0,
  1001. status: Approved,
  1002. finalized_at: 2
  1003. }
  1004. );
  1005. // Check that proposer's stake has been added back to his balance:
  1006. assert_eq!(Balances::free_balance(PROPOSER1), initial_balance());
  1007. assert_eq!(Balances::reserved_balance(PROPOSER1), 0);
  1008. // TODO expect event ProposalStatusUpdated(1, Approved)
  1009. });
  1010. }
  1011. #[test]
  1012. fn approve_proposal_when_all_councilors_voted_and_only_quorum_approved() {
  1013. with_externalities(&mut new_test_ext(), || {
  1014. let _ = Balances::deposit_creating(&PROPOSER1, initial_balance());
  1015. assert_ok!(_create_default_proposal());
  1016. // Only a quorum of councilors approved, others rejected:
  1017. let councilors = Proposals::councilors_count();
  1018. let approvals = Proposals::approval_quorum_seats();
  1019. let rejections = councilors - approvals;
  1020. for i in 0..councilors as usize {
  1021. let vote = if (i as u32) < approvals {
  1022. Approve
  1023. } else {
  1024. Reject
  1025. };
  1026. assert_ok!(Proposals::vote_on_proposal(
  1027. Origin::signed(ALL_COUNCILORS[i]),
  1028. 1,
  1029. vote
  1030. ));
  1031. }
  1032. assert_eq!(Proposals::votes_by_proposal(1).len() as u32, councilors);
  1033. assert_runtime_code_empty!();
  1034. System::set_block_number(2);
  1035. let _ = Proposals::end_block(2);
  1036. // Check that runtime code has been updated after proposal approved.
  1037. assert_runtime_code!(wasm_code());
  1038. assert!(Proposals::active_proposal_ids().is_empty());
  1039. assert_eq!(Proposals::proposals(1).status, Approved);
  1040. assert_eq!(
  1041. Proposals::tally_results(1),
  1042. TallyResult {
  1043. proposal_id: 1,
  1044. abstentions: 0,
  1045. approvals: approvals,
  1046. rejections: rejections,
  1047. slashes: 0,
  1048. status: Approved,
  1049. finalized_at: 2
  1050. }
  1051. );
  1052. // Check that proposer's stake has been added back to his balance:
  1053. assert_eq!(Balances::free_balance(PROPOSER1), initial_balance());
  1054. assert_eq!(Balances::reserved_balance(PROPOSER1), 0);
  1055. // TODO expect event ProposalStatusUpdated(1, Approved)
  1056. });
  1057. }
  1058. #[test]
  1059. fn approve_proposal_when_voting_period_expired_if_only_quorum_voted() {
  1060. with_externalities(&mut new_test_ext(), || {
  1061. let _ = Balances::deposit_creating(&PROPOSER1, initial_balance());
  1062. assert_ok!(_create_default_proposal());
  1063. // Only quorum of councilors approved, other councilors didn't vote:
  1064. let approvals = Proposals::approval_quorum_seats();
  1065. for i in 0..approvals as usize {
  1066. let vote = if (i as u32) < approvals {
  1067. Approve
  1068. } else {
  1069. Slash
  1070. };
  1071. assert_ok!(Proposals::vote_on_proposal(
  1072. Origin::signed(ALL_COUNCILORS[i]),
  1073. 1,
  1074. vote
  1075. ));
  1076. }
  1077. assert_eq!(Proposals::votes_by_proposal(1).len() as u32, approvals);
  1078. assert_runtime_code_empty!();
  1079. let expiration_block = System::block_number() + Proposals::voting_period();
  1080. System::set_block_number(2);
  1081. let _ = Proposals::end_block(2);
  1082. // Check that runtime code has NOT been updated yet,
  1083. // because not all councilors voted and voting period is not expired yet.
  1084. assert_runtime_code_empty!();
  1085. System::set_block_number(expiration_block);
  1086. let _ = Proposals::end_block(expiration_block);
  1087. // Check that runtime code has been updated after proposal approved.
  1088. assert_runtime_code!(wasm_code());
  1089. assert!(Proposals::active_proposal_ids().is_empty());
  1090. assert_eq!(Proposals::proposals(1).status, Approved);
  1091. assert_eq!(
  1092. Proposals::tally_results(1),
  1093. TallyResult {
  1094. proposal_id: 1,
  1095. abstentions: 0,
  1096. approvals: approvals,
  1097. rejections: 0,
  1098. slashes: 0,
  1099. status: Approved,
  1100. finalized_at: expiration_block
  1101. }
  1102. );
  1103. // Check that proposer's stake has been added back to his balance:
  1104. assert_eq!(Balances::free_balance(PROPOSER1), initial_balance());
  1105. assert_eq!(Balances::reserved_balance(PROPOSER1), 0);
  1106. // TODO expect event ProposalStatusUpdated(1, Approved)
  1107. });
  1108. }
  1109. #[test]
  1110. fn reject_proposal_when_all_councilors_voted_and_quorum_not_reached() {
  1111. with_externalities(&mut new_test_ext(), || {
  1112. let _ = Balances::deposit_creating(&PROPOSER1, initial_balance());
  1113. assert_ok!(_create_default_proposal());
  1114. // Less than a quorum of councilors approved, while others abstained:
  1115. let councilors = Proposals::councilors_count();
  1116. let approvals = Proposals::approval_quorum_seats() - 1;
  1117. let abstentions = councilors - approvals;
  1118. for i in 0..councilors as usize {
  1119. let vote = if (i as u32) < approvals {
  1120. Approve
  1121. } else {
  1122. Abstain
  1123. };
  1124. assert_ok!(Proposals::vote_on_proposal(
  1125. Origin::signed(ALL_COUNCILORS[i]),
  1126. 1,
  1127. vote
  1128. ));
  1129. }
  1130. assert_eq!(Proposals::votes_by_proposal(1).len() as u32, councilors);
  1131. assert_runtime_code_empty!();
  1132. System::set_block_number(2);
  1133. let _ = Proposals::end_block(2);
  1134. // Check that runtime code has NOT been updated after proposal slashed.
  1135. assert_runtime_code_empty!();
  1136. assert!(Proposals::active_proposal_ids().is_empty());
  1137. assert_eq!(Proposals::proposals(1).status, Rejected);
  1138. assert_eq!(
  1139. Proposals::tally_results(1),
  1140. TallyResult {
  1141. proposal_id: 1,
  1142. abstentions: abstentions,
  1143. approvals: approvals,
  1144. rejections: 0,
  1145. slashes: 0,
  1146. status: Rejected,
  1147. finalized_at: 2
  1148. }
  1149. );
  1150. // Check that proposer's balance reduced by burnt stake:
  1151. assert_eq!(
  1152. Balances::free_balance(PROPOSER1),
  1153. initial_balance() - rejection_fee()
  1154. );
  1155. assert_eq!(Balances::reserved_balance(PROPOSER1), 0);
  1156. // TODO expect event ProposalStatusUpdated(1, Rejected)
  1157. });
  1158. }
  1159. #[test]
  1160. fn reject_proposal_when_all_councilors_rejected_it() {
  1161. with_externalities(&mut new_test_ext(), || {
  1162. let _ = Balances::deposit_creating(&PROPOSER1, initial_balance());
  1163. assert_ok!(_create_default_proposal());
  1164. // All councilors rejected:
  1165. let mut expected_votes: Vec<(u64, VoteKind)> = vec![];
  1166. for &councilor in ALL_COUNCILORS.iter() {
  1167. expected_votes.push((councilor, Reject));
  1168. assert_ok!(Proposals::vote_on_proposal(
  1169. Origin::signed(councilor),
  1170. 1,
  1171. Reject
  1172. ));
  1173. assert_eq!(
  1174. Proposals::vote_by_account_and_proposal((councilor, 1)),
  1175. Reject
  1176. );
  1177. }
  1178. assert_eq!(Proposals::votes_by_proposal(1), expected_votes);
  1179. assert_runtime_code_empty!();
  1180. System::set_block_number(2);
  1181. let _ = Proposals::end_block(2);
  1182. // Check that runtime code has NOT been updated after proposal rejected.
  1183. assert_runtime_code_empty!();
  1184. assert!(Proposals::active_proposal_ids().is_empty());
  1185. assert_eq!(Proposals::proposals(1).status, Rejected);
  1186. assert_eq!(
  1187. Proposals::tally_results(1),
  1188. TallyResult {
  1189. proposal_id: 1,
  1190. abstentions: 0,
  1191. approvals: 0,
  1192. rejections: ALL_COUNCILORS.len() as u32,
  1193. slashes: 0,
  1194. status: Rejected,
  1195. finalized_at: 2
  1196. }
  1197. );
  1198. // Check that proposer's balance reduced by burnt stake:
  1199. assert_eq!(
  1200. Balances::free_balance(PROPOSER1),
  1201. initial_balance() - rejection_fee()
  1202. );
  1203. assert_eq!(Balances::reserved_balance(PROPOSER1), 0);
  1204. // TODO expect event ProposalStatusUpdated(1, Rejected)
  1205. });
  1206. }
  1207. #[test]
  1208. fn slash_proposal_when_all_councilors_slashed_it() {
  1209. with_externalities(&mut new_test_ext(), || {
  1210. let _ = Balances::deposit_creating(&PROPOSER1, initial_balance());
  1211. assert_ok!(_create_default_proposal());
  1212. // All councilors slashed:
  1213. let mut expected_votes: Vec<(u64, VoteKind)> = vec![];
  1214. for &councilor in ALL_COUNCILORS.iter() {
  1215. expected_votes.push((councilor, Slash));
  1216. assert_ok!(Proposals::vote_on_proposal(
  1217. Origin::signed(councilor),
  1218. 1,
  1219. Slash
  1220. ));
  1221. assert_eq!(
  1222. Proposals::vote_by_account_and_proposal((councilor, 1)),
  1223. Slash
  1224. );
  1225. }
  1226. assert_eq!(Proposals::votes_by_proposal(1), expected_votes);
  1227. assert_runtime_code_empty!();
  1228. System::set_block_number(2);
  1229. let _ = Proposals::end_block(2);
  1230. // Check that runtime code has NOT been updated after proposal slashed.
  1231. assert_runtime_code_empty!();
  1232. assert!(Proposals::active_proposal_ids().is_empty());
  1233. assert_eq!(Proposals::proposals(1).status, Slashed);
  1234. assert_eq!(
  1235. Proposals::tally_results(1),
  1236. TallyResult {
  1237. proposal_id: 1,
  1238. abstentions: 0,
  1239. approvals: 0,
  1240. rejections: 0,
  1241. slashes: ALL_COUNCILORS.len() as u32,
  1242. status: Slashed,
  1243. finalized_at: 2
  1244. }
  1245. );
  1246. // Check that proposer's balance reduced by burnt stake:
  1247. assert_eq!(
  1248. Balances::free_balance(PROPOSER1),
  1249. initial_balance() - min_stake()
  1250. );
  1251. assert_eq!(Balances::reserved_balance(PROPOSER1), 0);
  1252. // TODO expect event ProposalStatusUpdated(1, Slashed)
  1253. // TODO fix: event log assertion doesn't work and return empty event in every record
  1254. // assert_eq!(*System::events().last().unwrap(),
  1255. // EventRecord {
  1256. // phase: Phase::ApplyExtrinsic(0),
  1257. // event: RawEvent::ProposalStatusUpdated(1, Slashed),
  1258. // }
  1259. // );
  1260. });
  1261. }
  1262. // In this case a proposal will be marked as 'Expired'
  1263. // and it will be processed in the same way as if it has been rejected.
  1264. #[test]
  1265. fn expire_proposal_when_not_all_councilors_voted_and_quorum_not_reached() {
  1266. with_externalities(&mut new_test_ext(), || {
  1267. let _ = Balances::deposit_creating(&PROPOSER1, initial_balance());
  1268. assert_ok!(_create_default_proposal());
  1269. // Less than a quorum of councilors approved:
  1270. let approvals = Proposals::approval_quorum_seats() - 1;
  1271. for i in 0..approvals as usize {
  1272. let vote = if (i as u32) < approvals {
  1273. Approve
  1274. } else {
  1275. Slash
  1276. };
  1277. assert_ok!(Proposals::vote_on_proposal(
  1278. Origin::signed(ALL_COUNCILORS[i]),
  1279. 1,
  1280. vote
  1281. ));
  1282. }
  1283. assert_eq!(Proposals::votes_by_proposal(1).len() as u32, approvals);
  1284. assert_runtime_code_empty!();
  1285. let expiration_block = System::block_number() + Proposals::voting_period();
  1286. System::set_block_number(expiration_block);
  1287. let _ = Proposals::end_block(expiration_block);
  1288. // Check that runtime code has NOT been updated after proposal slashed.
  1289. assert_runtime_code_empty!();
  1290. assert!(Proposals::active_proposal_ids().is_empty());
  1291. assert_eq!(Proposals::proposals(1).status, Expired);
  1292. assert_eq!(
  1293. Proposals::tally_results(1),
  1294. TallyResult {
  1295. proposal_id: 1,
  1296. abstentions: 0,
  1297. approvals: approvals,
  1298. rejections: 0,
  1299. slashes: 0,
  1300. status: Expired,
  1301. finalized_at: expiration_block
  1302. }
  1303. );
  1304. // Check that proposer's balance reduced by burnt stake:
  1305. assert_eq!(
  1306. Balances::free_balance(PROPOSER1),
  1307. initial_balance() - rejection_fee()
  1308. );
  1309. assert_eq!(Balances::reserved_balance(PROPOSER1), 0);
  1310. // TODO expect event ProposalStatusUpdated(1, Rejected)
  1311. });
  1312. }
  1313. }