|
@@ -1,2154 +0,0 @@
|
|
|
-//! Council Elections Manager
|
|
|
-//!
|
|
|
-//! # Election Parameters:
|
|
|
-//! We don't currently handle zero periods, zero council term, zero council size and candidacy
|
|
|
-//! limit in any special way. The behaviour in such cases:
|
|
|
-//!
|
|
|
-//! - Setting any period to 0 will mean the election getting stuck in that stage, until force changing
|
|
|
-//! the state.
|
|
|
-//!
|
|
|
-//! - Council Size of 0 - no limit to size of council, all applicants that move beyond
|
|
|
-//! announcing stage would become council members, so effectively the candidacy limit will
|
|
|
-//! be the size of the council, voting and revealing have no impact on final results.
|
|
|
-//!
|
|
|
-//! - If candidacy limit is zero and council size > 0, council_size number of applicants will reach the voting stage.
|
|
|
-//! and become council members, voting will have no impact on final results.
|
|
|
-//!
|
|
|
-//! - If both candidacy limit and council size are zero then all applicant become council members
|
|
|
-//! since no filtering occurs at end of announcing stage.
|
|
|
-//!
|
|
|
-//! We only guard against these edge cases in the [`set_election_parameters`] call.
|
|
|
-//!
|
|
|
-//! [`set_election_parameters`]: struct.Module.html#method.set_election_parameters
|
|
|
-
|
|
|
-// Clippy linter warning
|
|
|
-#![allow(clippy::type_complexity)]
|
|
|
-// disable it because of possible frontend API break
|
|
|
-// TODO: remove post-Constaninople
|
|
|
-
|
|
|
-// Clippy linter warning
|
|
|
-#![allow(clippy::redundant_closure_call)] // disable it because of the substrate lib design
|
|
|
-
|
|
|
-#[cfg(feature = "std")]
|
|
|
-use serde::{Deserialize, Serialize};
|
|
|
-
|
|
|
-use codec::{Decode, Encode};
|
|
|
-use frame_support::traits::{Currency, ReservableCurrency};
|
|
|
-use frame_support::{decl_event, decl_module, decl_storage, ensure};
|
|
|
-use frame_system::{ensure_root, ensure_signed};
|
|
|
-use sp_arithmetic::traits::Zero;
|
|
|
-use sp_runtime::traits::Hash;
|
|
|
-use sp_std::collections::btree_map::BTreeMap;
|
|
|
-use sp_std::ops::Add;
|
|
|
-use sp_std::vec;
|
|
|
-use sp_std::vec::Vec;
|
|
|
-
|
|
|
-use super::sealed_vote::SealedVote;
|
|
|
-use super::stake::Stake;
|
|
|
-
|
|
|
-use super::council;
|
|
|
-use crate::election_params::ElectionParameters;
|
|
|
-pub use common::currency::{BalanceOf, GovernanceCurrency};
|
|
|
-
|
|
|
-use crate::DispatchResult;
|
|
|
-
|
|
|
-pub trait Trait:
|
|
|
- frame_system::Trait + council::Trait + GovernanceCurrency + membership::Trait
|
|
|
-{
|
|
|
- type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;
|
|
|
-
|
|
|
- type CouncilElected: CouncilElected<Seats<Self::AccountId, BalanceOf<Self>>, Self::BlockNumber>;
|
|
|
-}
|
|
|
-
|
|
|
-pub static MSG_CANNOT_CHANGE_PARAMS_DURING_ELECTION: &str = "CannotChangeParamsDuringElection";
|
|
|
-
|
|
|
-#[derive(Clone, Copy, Encode, Decode)]
|
|
|
-pub enum ElectionStage<BlockNumber> {
|
|
|
- Announcing(BlockNumber),
|
|
|
- Voting(BlockNumber),
|
|
|
- Revealing(BlockNumber),
|
|
|
-}
|
|
|
-
|
|
|
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
|
|
|
-#[derive(Encode, Decode, Default, Clone, PartialEq, Eq)]
|
|
|
-pub struct Seat<AccountId, Balance> {
|
|
|
- pub member: AccountId,
|
|
|
- pub stake: Balance,
|
|
|
- pub backers: Vec<Backer<AccountId, Balance>>,
|
|
|
-}
|
|
|
-
|
|
|
-impl<AccountId, Balance> Seat<AccountId, Balance>
|
|
|
-where
|
|
|
- Balance: Add<Output = Balance> + Copy,
|
|
|
-{
|
|
|
- pub fn calc_total_stake(&self) -> Balance {
|
|
|
- self.backers
|
|
|
- .iter()
|
|
|
- .fold(self.stake, |acc, backer| acc + backer.stake)
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
|
|
|
-#[derive(Encode, Decode, Default, Clone, PartialEq, Eq)]
|
|
|
-pub struct Backer<AccountId, Balance> {
|
|
|
- pub member: AccountId,
|
|
|
- pub stake: Balance,
|
|
|
-}
|
|
|
-
|
|
|
-pub type Seats<AccountId, Balance> = Vec<Seat<AccountId, Balance>>;
|
|
|
-
|
|
|
-// Hook for setting a new council when it is elected
|
|
|
-pub trait CouncilElected<Elected, Term> {
|
|
|
- fn council_elected(new_council: Elected, term: Term);
|
|
|
-}
|
|
|
-
|
|
|
-impl<Elected, Term> CouncilElected<Elected, Term> for () {
|
|
|
- fn council_elected(_new_council: Elected, _term: Term) {}
|
|
|
-}
|
|
|
-
|
|
|
-impl<Elected, Term, X: CouncilElected<Elected, Term>> CouncilElected<Elected, Term> for (X,) {
|
|
|
- fn council_elected(new_council: Elected, term: Term) {
|
|
|
- X::council_elected(new_council, term);
|
|
|
- }
|
|
|
-}
|
|
|
-// Chain of handlers.
|
|
|
-impl<
|
|
|
- Elected: Clone,
|
|
|
- Term: Clone,
|
|
|
- X: CouncilElected<Elected, Term>,
|
|
|
- Y: CouncilElected<Elected, Term>,
|
|
|
- > CouncilElected<Elected, Term> for (X, Y)
|
|
|
-{
|
|
|
- fn council_elected(new_council: Elected, term: Term) {
|
|
|
- X::council_elected(new_council.clone(), term.clone());
|
|
|
- Y::council_elected(new_council, term);
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
|
|
|
-#[derive(Clone, Copy, Encode, Decode, Default)]
|
|
|
-pub struct TransferableStake<Balance> {
|
|
|
- seat: Balance,
|
|
|
- backing: Balance,
|
|
|
-}
|
|
|
-
|
|
|
-// can we use a type alias to overcome name clashes of public types with other modules?
|
|
|
-pub type ElectionStake<T> = Stake<BalanceOf<T>>;
|
|
|
-
|
|
|
-decl_storage! {
|
|
|
- trait Store for Module<T: Trait> as CouncilElection {
|
|
|
- // Flag for wether to automatically start an election after a council term ends
|
|
|
- AutoStart get(fn auto_start) config() : bool = true;
|
|
|
-
|
|
|
- // Current stage if there is an election running
|
|
|
- Stage get(fn stage): Option<ElectionStage<T::BlockNumber>>;
|
|
|
-
|
|
|
- // The election round
|
|
|
- Round get(fn round): u32;
|
|
|
-
|
|
|
- ExistingStakeHolders get(fn existing_stake_holders): Vec<T::AccountId>;
|
|
|
- TransferableStakes get(fn transferable_stakes): map hasher(blake2_128_concat)
|
|
|
- T::AccountId => TransferableStake<BalanceOf<T>>;
|
|
|
-
|
|
|
- Applicants get(fn applicants): Vec<T::AccountId>;
|
|
|
- ApplicantStakes get(fn applicant_stakes): map hasher(blake2_128_concat)
|
|
|
- T::AccountId => ElectionStake<T>;
|
|
|
-
|
|
|
- Commitments get(fn commitments): Vec<T::Hash>;
|
|
|
-
|
|
|
- // TODO value type of this map looks scary, is there any way to simplify the notation?
|
|
|
- Votes get(fn votes): map hasher(blake2_128_concat)
|
|
|
- T::Hash => SealedVote<T::AccountId, ElectionStake<T>, T::Hash, T::AccountId>;
|
|
|
-
|
|
|
- // Current Election Parameters.
|
|
|
- // Should we replace all the individual values with a single ElectionParameters type?
|
|
|
- // Having them individually makes it more flexible to add and remove new parameters in future
|
|
|
- // without dealing with migration issues.
|
|
|
- AnnouncingPeriod get(fn announcing_period): T::BlockNumber;
|
|
|
- VotingPeriod get(fn voting_period): T::BlockNumber;
|
|
|
- RevealingPeriod get(fn revealing_period): T::BlockNumber;
|
|
|
- CouncilSize get(fn council_size): u32;
|
|
|
- CandidacyLimit get (fn candidacy_limit): u32;
|
|
|
- MinCouncilStake get(fn min_council_stake): BalanceOf<T>;
|
|
|
- NewTermDuration get(fn new_term_duration): T::BlockNumber;
|
|
|
- MinVotingStake get(fn min_voting_stake): BalanceOf<T>;
|
|
|
- }
|
|
|
- add_extra_genesis {
|
|
|
- config(election_parameters): ElectionParameters<BalanceOf<T>, T::BlockNumber>;
|
|
|
- build(|config: &GenesisConfig<T>| {
|
|
|
- config.election_parameters.ensure_valid().expect("Invalid Election Parameters");
|
|
|
- Module::<T>::set_verified_election_parameters(config.election_parameters);
|
|
|
- });
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-// Event for this module.
|
|
|
-decl_event!(
|
|
|
- pub enum Event<T> where
|
|
|
- <T as frame_system::Trait>::BlockNumber,
|
|
|
- <T as frame_system::Trait>::AccountId,
|
|
|
- <T as frame_system::Trait>::Hash {
|
|
|
- /// A new election started
|
|
|
- ElectionStarted(),
|
|
|
- AnnouncingStarted(u32),
|
|
|
- AnnouncingEnded(),
|
|
|
- VotingStarted(),
|
|
|
- VotingEnded(),
|
|
|
- RevealingStarted(),
|
|
|
- RevealingEnded(),
|
|
|
- CouncilElected(BlockNumber),
|
|
|
- Applied(AccountId),
|
|
|
- Voted(AccountId, Hash),
|
|
|
- Revealed(AccountId, Hash, AccountId),
|
|
|
- }
|
|
|
-);
|
|
|
-
|
|
|
-impl<T: Trait> Module<T> {
|
|
|
- // HELPERS - IMMUTABLES
|
|
|
-
|
|
|
- fn council_size_usize() -> usize {
|
|
|
- Self::council_size() as usize
|
|
|
- }
|
|
|
-
|
|
|
- fn candidacy_limit_usize() -> usize {
|
|
|
- Self::candidacy_limit() as usize
|
|
|
- }
|
|
|
-
|
|
|
- fn current_block_number_plus(length: T::BlockNumber) -> T::BlockNumber {
|
|
|
- <frame_system::Module<T>>::block_number() + length
|
|
|
- }
|
|
|
-
|
|
|
- fn can_participate(sender: &T::AccountId) -> bool {
|
|
|
- !<T as GovernanceCurrency>::Currency::free_balance(sender).is_zero()
|
|
|
- && <membership::Module<T>>::is_member_account(sender)
|
|
|
- }
|
|
|
-
|
|
|
- // PUBLIC IMMUTABLES
|
|
|
-
|
|
|
- /// Returns true if an election is running
|
|
|
- pub fn is_election_running() -> bool {
|
|
|
- Self::stage().is_some()
|
|
|
- }
|
|
|
-
|
|
|
- /// Returns block number at which current stage will end if an election is running.
|
|
|
- pub fn stage_ends_at() -> Option<T::BlockNumber> {
|
|
|
- if let Some(stage) = Self::stage() {
|
|
|
- match stage {
|
|
|
- ElectionStage::Announcing(ends) => Some(ends),
|
|
|
- ElectionStage::Voting(ends) => Some(ends),
|
|
|
- ElectionStage::Revealing(ends) => Some(ends),
|
|
|
- }
|
|
|
- } else {
|
|
|
- None
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // PRIVATE MUTABLES
|
|
|
-
|
|
|
- /// Starts an election. Will fail if an election is already running
|
|
|
- /// Initializes transferable stakes. Assumes election parameters have already been set.
|
|
|
- fn start_election(current_council: Seats<T::AccountId, BalanceOf<T>>) -> DispatchResult {
|
|
|
- ensure!(!Self::is_election_running(), "election already in progress");
|
|
|
- ensure!(
|
|
|
- Self::existing_stake_holders().is_empty(),
|
|
|
- "stake holders must be empty"
|
|
|
- );
|
|
|
- ensure!(Self::applicants().is_empty(), "applicants must be empty");
|
|
|
- ensure!(Self::commitments().is_empty(), "commitments must be empty");
|
|
|
-
|
|
|
- // Take snapshot of seat and backing stakes of an existing council
|
|
|
- // Its important to note that the election system takes ownership of these stakes, and is responsible
|
|
|
- // to return any unused stake to original owners at the end of the election.
|
|
|
- Self::initialize_transferable_stakes(current_council);
|
|
|
-
|
|
|
- Self::deposit_event(RawEvent::ElectionStarted());
|
|
|
-
|
|
|
- Self::move_to_announcing_stage();
|
|
|
- Ok(())
|
|
|
- }
|
|
|
-
|
|
|
- /// Sets announcing stage. Can be called from any stage and assumes all preparatory work
|
|
|
- /// for entering the stage has been performed.
|
|
|
- /// Bumps the election round.
|
|
|
- fn move_to_announcing_stage() {
|
|
|
- let next_round = Round::mutate(|n| {
|
|
|
- *n += 1;
|
|
|
- *n
|
|
|
- });
|
|
|
-
|
|
|
- let new_stage_ends_at = Self::current_block_number_plus(Self::announcing_period());
|
|
|
-
|
|
|
- <Stage<T>>::put(ElectionStage::Announcing(new_stage_ends_at));
|
|
|
-
|
|
|
- Self::deposit_event(RawEvent::AnnouncingStarted(next_round));
|
|
|
- }
|
|
|
-
|
|
|
- /// Sets announcing stage. Can be called from any stage and assumes all preparatory work
|
|
|
- /// for entering the stage has been performed.
|
|
|
- fn move_to_voting_stage() {
|
|
|
- let new_stage_ends_at = Self::current_block_number_plus(Self::voting_period());
|
|
|
-
|
|
|
- <Stage<T>>::put(ElectionStage::Voting(new_stage_ends_at));
|
|
|
-
|
|
|
- Self::deposit_event(RawEvent::VotingStarted());
|
|
|
- }
|
|
|
-
|
|
|
- /// Sets announcing stage. Can be called from any stage and assumes all preparatory work
|
|
|
- /// for entering the stage has been performed.
|
|
|
- fn move_to_revealing_stage() {
|
|
|
- let new_stage_ends_at = Self::current_block_number_plus(Self::revealing_period());
|
|
|
-
|
|
|
- <Stage<T>>::put(ElectionStage::Revealing(new_stage_ends_at));
|
|
|
-
|
|
|
- Self::deposit_event(RawEvent::RevealingStarted());
|
|
|
- }
|
|
|
-
|
|
|
- /// Sorts applicants by stake, and returns slice of applicants with least stake. Applicants not
|
|
|
- /// returned in the slice are the top `len` highest staked.
|
|
|
- fn find_least_staked_applicants(
|
|
|
- applicants: &mut Vec<T::AccountId>,
|
|
|
- len: usize,
|
|
|
- ) -> &[T::AccountId] {
|
|
|
- if len >= applicants.len() {
|
|
|
- &[]
|
|
|
- } else {
|
|
|
- #[allow(clippy::redundant_closure)] // disable incorrect Clippy linter warning
|
|
|
- applicants.sort_by_key(|applicant| Self::applicant_stakes(applicant));
|
|
|
- &applicants[0..applicants.len() - len]
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- fn on_announcing_ended() {
|
|
|
- let mut applicants = Self::applicants();
|
|
|
-
|
|
|
- if applicants.len() < Self::council_size_usize() {
|
|
|
- // Not enough applicants announced candidacy
|
|
|
- Self::move_to_announcing_stage();
|
|
|
- } else {
|
|
|
- // upper limit on applicants that will move to voting stage
|
|
|
- let limit = sp_std::cmp::max(Self::council_size_usize(), Self::candidacy_limit_usize());
|
|
|
- let applicants_to_drop = Self::find_least_staked_applicants(&mut applicants, limit);
|
|
|
-
|
|
|
- Self::drop_applicants(applicants_to_drop);
|
|
|
-
|
|
|
- Self::move_to_voting_stage();
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- fn on_voting_ended() {
|
|
|
- Self::move_to_revealing_stage();
|
|
|
- }
|
|
|
-
|
|
|
- fn on_revealing_ended() {
|
|
|
- // tally the revealed votes
|
|
|
- let mut votes = Vec::new();
|
|
|
-
|
|
|
- for commitment in Self::commitments().iter() {
|
|
|
- votes.push(Self::votes(commitment));
|
|
|
- }
|
|
|
-
|
|
|
- let mut new_council = Self::tally_votes(&votes);
|
|
|
-
|
|
|
- // Note here that applicants with zero votes dont appear in the tally.
|
|
|
- // Is an applicant with some votes but less total stake than another applicant with zero votes
|
|
|
- // more qualified to be on the council?
|
|
|
- // Consider implications - if a council can be formed purely by staking are we fine with that?
|
|
|
-
|
|
|
- for applicant in Self::applicants().iter() {
|
|
|
- if !new_council.contains_key(applicant) {
|
|
|
- new_council.insert(
|
|
|
- applicant.clone(),
|
|
|
- Seat {
|
|
|
- member: applicant.clone(),
|
|
|
- stake: Self::applicant_stakes(applicant).total(),
|
|
|
- backers: Vec::new(),
|
|
|
- },
|
|
|
- );
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- match new_council.len() {
|
|
|
- ncl if ncl == Self::council_size_usize() => {
|
|
|
- // all applicants in the tally will form the new council
|
|
|
- }
|
|
|
- ncl if ncl > Self::council_size_usize() => {
|
|
|
- // we have more than enough applicants to form the new council.
|
|
|
- // select top staked
|
|
|
- Self::filter_top_staked(&mut new_council, Self::council_size_usize());
|
|
|
- }
|
|
|
- _ => {
|
|
|
- // Not enough applicants with votes to form a council.
|
|
|
- // This may happen if we didn't add applicants with zero votes to the tally,
|
|
|
- // or in future if we allow applicants to withdraw candidacy during voting or revealing stages.
|
|
|
- // or council size was increased during voting, revealing stages.
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // unless we want to add more filtering criteria to what is considered a successful election
|
|
|
- // other than just the minimum stake for candidacy, we have a new council!
|
|
|
-
|
|
|
- Self::teardown_election(
|
|
|
- &votes,
|
|
|
- &new_council,
|
|
|
- true, /* unlock transferable stakes */
|
|
|
- );
|
|
|
-
|
|
|
- let new_council = new_council.into_iter().map(|(_, seat)| seat).collect();
|
|
|
- T::CouncilElected::council_elected(new_council, Self::new_term_duration());
|
|
|
-
|
|
|
- Self::deposit_event(RawEvent::CouncilElected(
|
|
|
- <frame_system::Module<T>>::block_number(),
|
|
|
- ));
|
|
|
- }
|
|
|
-
|
|
|
- fn teardown_election(
|
|
|
- votes: &[SealedVote<T::AccountId, Stake<BalanceOf<T>>, T::Hash, T::AccountId>],
|
|
|
- new_council: &BTreeMap<T::AccountId, Seat<T::AccountId, BalanceOf<T>>>,
|
|
|
- unlock_ts: bool,
|
|
|
- ) {
|
|
|
- Self::refund_voting_stakes(&votes, &new_council);
|
|
|
- Self::clear_votes();
|
|
|
-
|
|
|
- Self::drop_unelected_applicants(&new_council);
|
|
|
- Self::clear_applicants();
|
|
|
-
|
|
|
- if unlock_ts {
|
|
|
- Self::unlock_transferable_stakes();
|
|
|
- }
|
|
|
-
|
|
|
- Self::clear_transferable_stakes();
|
|
|
-
|
|
|
- <Stage<T>>::kill();
|
|
|
- }
|
|
|
-
|
|
|
- fn unlock_transferable_stakes() {
|
|
|
- // move stakes back to account holder's free balance
|
|
|
- for stakeholder in Self::existing_stake_holders().iter() {
|
|
|
- let stake = Self::transferable_stakes(stakeholder);
|
|
|
- if !stake.seat.is_zero() || !stake.backing.is_zero() {
|
|
|
- <T as GovernanceCurrency>::Currency::unreserve(
|
|
|
- stakeholder,
|
|
|
- stake.seat + stake.backing,
|
|
|
- );
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- fn clear_transferable_stakes() {
|
|
|
- for stakeholder in Self::existing_stake_holders() {
|
|
|
- <TransferableStakes<T>>::remove(stakeholder);
|
|
|
- }
|
|
|
-
|
|
|
- <ExistingStakeHolders<T>>::kill();
|
|
|
- }
|
|
|
-
|
|
|
- fn clear_applicants() {
|
|
|
- for applicant in Self::applicants() {
|
|
|
- <ApplicantStakes<T>>::remove(applicant);
|
|
|
- }
|
|
|
- <Applicants<T>>::kill();
|
|
|
- }
|
|
|
-
|
|
|
- fn refund_applicant(applicant: &T::AccountId) {
|
|
|
- let stake = <ApplicantStakes<T>>::get(applicant);
|
|
|
-
|
|
|
- // return new stake to account's free balance
|
|
|
- if !stake.new.is_zero() {
|
|
|
- <T as GovernanceCurrency>::Currency::unreserve(applicant, stake.new);
|
|
|
- }
|
|
|
-
|
|
|
- // return unused transferable stake
|
|
|
- if !stake.transferred.is_zero() {
|
|
|
- <TransferableStakes<T>>::mutate(applicant, |transferable| {
|
|
|
- (*transferable).seat += stake.transferred
|
|
|
- });
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- fn drop_applicants(drop: &[T::AccountId]) {
|
|
|
- let not_dropped: Vec<T::AccountId> = Self::applicants()
|
|
|
- .into_iter()
|
|
|
- .filter(|id| !drop.iter().any(|x| *x == *id))
|
|
|
- .collect();
|
|
|
-
|
|
|
- for applicant in drop {
|
|
|
- Self::refund_applicant(applicant);
|
|
|
- <ApplicantStakes<T>>::remove(applicant);
|
|
|
- }
|
|
|
-
|
|
|
- <Applicants<T>>::put(not_dropped);
|
|
|
- }
|
|
|
-
|
|
|
- fn drop_unelected_applicants(
|
|
|
- new_council: &BTreeMap<T::AccountId, Seat<T::AccountId, BalanceOf<T>>>,
|
|
|
- ) {
|
|
|
- let applicants_to_drop: Vec<T::AccountId> = Self::applicants()
|
|
|
- .into_iter()
|
|
|
- .filter(|applicant| !new_council.contains_key(&applicant))
|
|
|
- .collect();
|
|
|
-
|
|
|
- Self::drop_applicants(&applicants_to_drop[..]);
|
|
|
- }
|
|
|
-
|
|
|
- fn refund_voting_stakes(
|
|
|
- sealed_votes: &[SealedVote<T::AccountId, Stake<BalanceOf<T>>, T::Hash, T::AccountId>],
|
|
|
- new_council: &BTreeMap<T::AccountId, Seat<T::AccountId, BalanceOf<T>>>,
|
|
|
- ) {
|
|
|
- for sealed_vote in sealed_votes.iter() {
|
|
|
- // Do a refund if commitment was not revealed, or the vote was for applicant that did
|
|
|
- // not get elected to the council
|
|
|
- // TODO critical: shouldn't we slash the stake in such a case? This is the whole idea behid staking on something: people need to decide carefully and be responsible for their bahavior because they can loose their stake
|
|
|
- // See https://github.com/Joystream/substrate-node-joystream/issues/4
|
|
|
- let do_refund = match sealed_vote.get_vote() {
|
|
|
- Some(applicant) => !new_council.contains_key(&applicant),
|
|
|
- None => true,
|
|
|
- };
|
|
|
-
|
|
|
- if do_refund {
|
|
|
- // return new stake to account's free balance
|
|
|
- let SealedVote { voter, stake, .. } = sealed_vote;
|
|
|
- if !stake.new.is_zero() {
|
|
|
- <T as GovernanceCurrency>::Currency::unreserve(voter, stake.new);
|
|
|
- }
|
|
|
-
|
|
|
- // return unused transferable stake
|
|
|
- if !stake.transferred.is_zero() {
|
|
|
- <TransferableStakes<T>>::mutate(voter, |transferable| {
|
|
|
- (*transferable).backing += stake.transferred
|
|
|
- });
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- fn clear_votes() {
|
|
|
- for commitment in Self::commitments() {
|
|
|
- <Votes<T>>::remove(commitment);
|
|
|
- }
|
|
|
- <Commitments<T>>::kill();
|
|
|
- }
|
|
|
-
|
|
|
- fn tally_votes(
|
|
|
- sealed_votes: &[SealedVote<T::AccountId, Stake<BalanceOf<T>>, T::Hash, T::AccountId>],
|
|
|
- ) -> BTreeMap<T::AccountId, Seat<T::AccountId, BalanceOf<T>>> {
|
|
|
- let mut tally: BTreeMap<T::AccountId, Seat<T::AccountId, BalanceOf<T>>> = BTreeMap::new();
|
|
|
-
|
|
|
- for sealed_vote in sealed_votes.iter() {
|
|
|
- if let Some(applicant) = sealed_vote.get_vote() {
|
|
|
- if !tally.contains_key(&applicant) {
|
|
|
- // Add new seat
|
|
|
- tally.insert(
|
|
|
- applicant.clone(),
|
|
|
- Seat {
|
|
|
- member: applicant.clone(),
|
|
|
- stake: Self::applicant_stakes(applicant).total(),
|
|
|
- backers: vec![],
|
|
|
- },
|
|
|
- );
|
|
|
- }
|
|
|
- if let Some(seat) = tally.get_mut(&applicant) {
|
|
|
- // Add backer to existing seat
|
|
|
- seat.backers.push(Backer {
|
|
|
- member: sealed_vote.voter.clone(),
|
|
|
- stake: sealed_vote.stake.total(),
|
|
|
- });
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- tally
|
|
|
- }
|
|
|
-
|
|
|
- fn filter_top_staked(
|
|
|
- tally: &mut BTreeMap<T::AccountId, Seat<T::AccountId, BalanceOf<T>>>,
|
|
|
- limit: usize,
|
|
|
- ) {
|
|
|
- if limit >= tally.len() {
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- // use ordering in the applicants vector (not ordering resulting from btreemap iteration)
|
|
|
- let mut seats: Vec<T::AccountId> = Self::applicants()
|
|
|
- .into_iter()
|
|
|
- .filter(|id| tally.contains_key(id))
|
|
|
- .collect();
|
|
|
-
|
|
|
- // ensure_eq!(seats.len(), tally.len());
|
|
|
-
|
|
|
- if limit >= seats.len() {
|
|
|
- // Tally is inconsistent with list of applicants!
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- // TODO: order by number of votes, then number of backers
|
|
|
-
|
|
|
- seats.sort_by_key(|applicant| {
|
|
|
- tally
|
|
|
- .get(&applicant)
|
|
|
- .map_or(Zero::zero(), |seat| seat.calc_total_stake())
|
|
|
- });
|
|
|
-
|
|
|
- // seats at bottom of list
|
|
|
- let filtered_out_seats = &seats[0..seats.len() - limit];
|
|
|
-
|
|
|
- for id in filtered_out_seats {
|
|
|
- tally.remove(id);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// Checks if the current election stage has ended and calls the stage ended handler
|
|
|
- fn check_if_stage_is_ending(now: T::BlockNumber) {
|
|
|
- if let Some(stage) = Self::stage() {
|
|
|
- match stage {
|
|
|
- ElectionStage::Announcing(ends) => {
|
|
|
- if ends == now {
|
|
|
- Self::deposit_event(RawEvent::AnnouncingEnded());
|
|
|
- Self::on_announcing_ended();
|
|
|
- }
|
|
|
- }
|
|
|
- ElectionStage::Voting(ends) => {
|
|
|
- if ends == now {
|
|
|
- Self::deposit_event(RawEvent::VotingEnded());
|
|
|
- Self::on_voting_ended();
|
|
|
- }
|
|
|
- }
|
|
|
- ElectionStage::Revealing(ends) => {
|
|
|
- if ends == now {
|
|
|
- Self::deposit_event(RawEvent::RevealingEnded());
|
|
|
- Self::on_revealing_ended();
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// Takes a snapshot of the stakes from the current council
|
|
|
- fn initialize_transferable_stakes(current_council: Seats<T::AccountId, BalanceOf<T>>) {
|
|
|
- let mut stakeholder_accounts: Vec<T::AccountId> = Vec::new();
|
|
|
-
|
|
|
- for seat in current_council.into_iter() {
|
|
|
- let Seat { member, stake, .. } = seat;
|
|
|
-
|
|
|
- if <TransferableStakes<T>>::contains_key(&member) {
|
|
|
- <TransferableStakes<T>>::mutate(&member, |transferbale_stake| {
|
|
|
- *transferbale_stake = TransferableStake {
|
|
|
- seat: transferbale_stake.seat + stake,
|
|
|
- backing: transferbale_stake.backing,
|
|
|
- }
|
|
|
- });
|
|
|
- } else {
|
|
|
- <TransferableStakes<T>>::insert(
|
|
|
- &member,
|
|
|
- TransferableStake {
|
|
|
- seat: stake,
|
|
|
- backing: BalanceOf::<T>::zero(),
|
|
|
- },
|
|
|
- );
|
|
|
-
|
|
|
- stakeholder_accounts.push(member);
|
|
|
- }
|
|
|
-
|
|
|
- for backer in seat.backers.into_iter() {
|
|
|
- let Backer { member, stake, .. } = backer;
|
|
|
-
|
|
|
- if <TransferableStakes<T>>::contains_key(&member) {
|
|
|
- <TransferableStakes<T>>::mutate(&member, |transferbale_stake| {
|
|
|
- *transferbale_stake = TransferableStake {
|
|
|
- seat: transferbale_stake.seat,
|
|
|
- backing: transferbale_stake.backing + stake,
|
|
|
- }
|
|
|
- });
|
|
|
- } else {
|
|
|
- <TransferableStakes<T>>::insert(
|
|
|
- &member,
|
|
|
- TransferableStake {
|
|
|
- seat: BalanceOf::<T>::zero(),
|
|
|
- backing: stake,
|
|
|
- },
|
|
|
- );
|
|
|
-
|
|
|
- stakeholder_accounts.push(member);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- <ExistingStakeHolders<T>>::put(stakeholder_accounts);
|
|
|
- }
|
|
|
-
|
|
|
- fn new_stake_reusing_transferable(
|
|
|
- transferable: &mut BalanceOf<T>,
|
|
|
- new_stake: BalanceOf<T>,
|
|
|
- ) -> Stake<BalanceOf<T>> {
|
|
|
- let transferred = if *transferable >= new_stake {
|
|
|
- new_stake
|
|
|
- } else {
|
|
|
- *transferable
|
|
|
- };
|
|
|
-
|
|
|
- *transferable -= transferred;
|
|
|
-
|
|
|
- Stake {
|
|
|
- new: new_stake - transferred,
|
|
|
- transferred,
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- fn try_add_applicant(applicant: T::AccountId, stake: BalanceOf<T>) -> DispatchResult {
|
|
|
- let mut transferable_stake = <TransferableStakes<T>>::get(&applicant);
|
|
|
-
|
|
|
- let new_stake = Self::new_stake_reusing_transferable(&mut transferable_stake.seat, stake);
|
|
|
-
|
|
|
- ensure!(
|
|
|
- <T as GovernanceCurrency>::Currency::can_reserve(&applicant, new_stake.new),
|
|
|
- "not enough free balance to reserve"
|
|
|
- );
|
|
|
-
|
|
|
- ensure!(
|
|
|
- <T as GovernanceCurrency>::Currency::reserve(&applicant, new_stake.new).is_ok(),
|
|
|
- "failed to reserve applicant stake!"
|
|
|
- );
|
|
|
-
|
|
|
- let applicant_stake = <ApplicantStakes<T>>::get(&applicant);
|
|
|
- let total_stake = applicant_stake.add(&new_stake);
|
|
|
-
|
|
|
- if <TransferableStakes<T>>::contains_key(&applicant) {
|
|
|
- <TransferableStakes<T>>::insert(&applicant, transferable_stake);
|
|
|
- }
|
|
|
-
|
|
|
- if !<ApplicantStakes<T>>::contains_key(&applicant) {
|
|
|
- // insert element at the begining, this gives priority to early applicants
|
|
|
- // when ordering applicants by stake if stakes are equal
|
|
|
- <Applicants<T>>::mutate(|applicants| applicants.insert(0, applicant.clone()));
|
|
|
- }
|
|
|
-
|
|
|
- <ApplicantStakes<T>>::insert(applicant, total_stake);
|
|
|
-
|
|
|
- Ok(())
|
|
|
- }
|
|
|
-
|
|
|
- fn try_add_vote(
|
|
|
- voter: T::AccountId,
|
|
|
- stake: BalanceOf<T>,
|
|
|
- commitment: T::Hash,
|
|
|
- ) -> DispatchResult {
|
|
|
- ensure!(
|
|
|
- !<Votes<T>>::contains_key(commitment),
|
|
|
- "duplicate commitment"
|
|
|
- );
|
|
|
-
|
|
|
- let mut transferable_stake = <TransferableStakes<T>>::get(&voter);
|
|
|
-
|
|
|
- let vote_stake =
|
|
|
- Self::new_stake_reusing_transferable(&mut transferable_stake.backing, stake);
|
|
|
-
|
|
|
- ensure!(
|
|
|
- <T as GovernanceCurrency>::Currency::can_reserve(&voter, vote_stake.new),
|
|
|
- "not enough free balance to reserve"
|
|
|
- );
|
|
|
-
|
|
|
- ensure!(
|
|
|
- <T as GovernanceCurrency>::Currency::reserve(&voter, vote_stake.new).is_ok(),
|
|
|
- "failed to reserve voting stake!"
|
|
|
- );
|
|
|
-
|
|
|
- <Commitments<T>>::mutate(|commitments| commitments.push(commitment));
|
|
|
-
|
|
|
- <Votes<T>>::insert(
|
|
|
- commitment,
|
|
|
- SealedVote::new(voter.clone(), vote_stake, commitment),
|
|
|
- );
|
|
|
-
|
|
|
- if <TransferableStakes<T>>::contains_key(&voter) {
|
|
|
- <TransferableStakes<T>>::insert(&voter, transferable_stake);
|
|
|
- }
|
|
|
-
|
|
|
- Ok(())
|
|
|
- }
|
|
|
-
|
|
|
- fn try_reveal_vote(
|
|
|
- voter: T::AccountId,
|
|
|
- commitment: T::Hash,
|
|
|
- vote_for: T::AccountId,
|
|
|
- salt: Vec<u8>,
|
|
|
- ) -> DispatchResult {
|
|
|
- ensure!(
|
|
|
- <Votes<T>>::contains_key(&commitment),
|
|
|
- "commitment not found"
|
|
|
- );
|
|
|
-
|
|
|
- let mut sealed_vote = <Votes<T>>::get(&commitment);
|
|
|
-
|
|
|
- ensure!(sealed_vote.is_not_revealed(), "vote already revealed");
|
|
|
- // only voter can reveal their own votes
|
|
|
- ensure!(sealed_vote.is_owned_by(voter), "only voter can reveal vote");
|
|
|
- ensure!(
|
|
|
- <ApplicantStakes<T>>::contains_key(&vote_for),
|
|
|
- "vote for non-applicant not allowed"
|
|
|
- );
|
|
|
-
|
|
|
- let mut salt = salt;
|
|
|
-
|
|
|
- // Tries to unseal, if salt is invalid will return error
|
|
|
- sealed_vote.unseal(
|
|
|
- vote_for,
|
|
|
- &mut salt,
|
|
|
- <T as frame_system::Trait>::Hashing::hash,
|
|
|
- )?;
|
|
|
-
|
|
|
- // Update the revealed vote
|
|
|
- <Votes<T>>::insert(commitment, sealed_vote);
|
|
|
-
|
|
|
- Ok(())
|
|
|
- }
|
|
|
-
|
|
|
- fn set_verified_election_parameters(params: ElectionParameters<BalanceOf<T>, T::BlockNumber>) {
|
|
|
- <AnnouncingPeriod<T>>::put(params.announcing_period);
|
|
|
- <VotingPeriod<T>>::put(params.voting_period);
|
|
|
- <RevealingPeriod<T>>::put(params.revealing_period);
|
|
|
- <MinCouncilStake<T>>::put(params.min_council_stake);
|
|
|
- <NewTermDuration<T>>::put(params.new_term_duration);
|
|
|
- CouncilSize::put(params.council_size);
|
|
|
- CandidacyLimit::put(params.candidacy_limit);
|
|
|
- <MinVotingStake<T>>::put(params.min_voting_stake);
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-decl_module! {
|
|
|
- pub struct Module<T: Trait> for enum Call where origin: T::Origin {
|
|
|
- fn deposit_event() = default;
|
|
|
-
|
|
|
- // No origin so this is a priviledged call
|
|
|
- fn on_finalize(now: T::BlockNumber) {
|
|
|
- Self::check_if_stage_is_ending(now);
|
|
|
- }
|
|
|
-
|
|
|
- // Member can apply during announcing stage only. On first call a minimum stake will need to be provided.
|
|
|
- // Member can make subsequent calls during announcing stage to increase their stake.
|
|
|
- #[weight = 10_000_000] // TODO: adjust weight
|
|
|
- pub fn apply(origin, stake: BalanceOf<T>) {
|
|
|
- let sender = ensure_signed(origin)?;
|
|
|
- ensure!(Self::can_participate(&sender), "Only members can apply to be on council");
|
|
|
-
|
|
|
- let stage = Self::stage();
|
|
|
- ensure!(Self::stage().is_some(), "election not running");
|
|
|
-
|
|
|
- let is_announcing = matches!(stage.unwrap(), ElectionStage::Announcing(_));
|
|
|
- ensure!(is_announcing, "election not in announcing stage");
|
|
|
-
|
|
|
- // minimum stake on first attempt to apply
|
|
|
- if !<ApplicantStakes<T>>::contains_key(&sender) {
|
|
|
- ensure!(stake >= Self::min_council_stake(), "minimum stake must be provided");
|
|
|
- }
|
|
|
-
|
|
|
- Self::try_add_applicant(sender.clone(), stake)?;
|
|
|
-
|
|
|
- Self::deposit_event(RawEvent::Applied(sender));
|
|
|
- }
|
|
|
-
|
|
|
- #[weight = 10_000_000] // TODO: adjust weight
|
|
|
- fn vote(origin, commitment: T::Hash, stake: BalanceOf<T>) {
|
|
|
- let sender = ensure_signed(origin)?;
|
|
|
- ensure!(Self::can_participate(&sender), "Only members can vote for an applicant");
|
|
|
-
|
|
|
- let stage = Self::stage();
|
|
|
- ensure!(Self::stage().is_some(), "election not running");
|
|
|
-
|
|
|
- let is_voting = matches!(stage.unwrap(), ElectionStage::Voting(_));
|
|
|
- ensure!(is_voting, "election not in voting stage");
|
|
|
-
|
|
|
- ensure!(stake >= Self::min_voting_stake(), "voting stake too low");
|
|
|
- Self::try_add_vote(sender.clone(), stake, commitment)?;
|
|
|
- Self::deposit_event(RawEvent::Voted(sender, commitment));
|
|
|
- }
|
|
|
-
|
|
|
- #[weight = 10_000_000] // TODO: adjust weight
|
|
|
- fn reveal(origin, commitment: T::Hash, vote: T::AccountId, salt: Vec<u8>) {
|
|
|
- let sender = ensure_signed(origin)?;
|
|
|
-
|
|
|
- ensure!(salt.len() <= 32, "salt too large"); // at most 256 bits salt
|
|
|
-
|
|
|
- let stage = Self::stage();
|
|
|
- ensure!(Self::stage().is_some(), "election not running");
|
|
|
-
|
|
|
- let is_revealing = matches!(stage.unwrap(), ElectionStage::Revealing(_));
|
|
|
- ensure!(is_revealing, "election not in revealing stage");
|
|
|
-
|
|
|
- Self::try_reveal_vote(sender.clone(), commitment, vote.clone(), salt)?;
|
|
|
- Self::deposit_event(RawEvent::Revealed(sender, commitment, vote));
|
|
|
- }
|
|
|
-
|
|
|
- #[weight = 10_000_000] // TODO: adjust weight
|
|
|
- fn set_stage_announcing(origin, ends_at: T::BlockNumber) {
|
|
|
- ensure_root(origin)?;
|
|
|
- ensure!(ends_at > <frame_system::Module<T>>::block_number(), "must end at future block number");
|
|
|
- <Stage<T>>::put(ElectionStage::Announcing(ends_at));
|
|
|
- }
|
|
|
-
|
|
|
- #[weight = 10_000_000] // TODO: adjust weight
|
|
|
- fn set_stage_revealing(origin, ends_at: T::BlockNumber) {
|
|
|
- ensure_root(origin)?;
|
|
|
- ensure!(ends_at > <frame_system::Module<T>>::block_number(), "must end at future block number");
|
|
|
- <Stage<T>>::put(ElectionStage::Revealing(ends_at));
|
|
|
- }
|
|
|
-
|
|
|
- #[weight = 10_000_000] // TODO: adjust weight
|
|
|
- fn set_stage_voting(origin, ends_at: T::BlockNumber) {
|
|
|
- ensure_root(origin)?;
|
|
|
- ensure!(ends_at > <frame_system::Module<T>>::block_number(), "must end at future block number");
|
|
|
- <Stage<T>>::put(ElectionStage::Voting(ends_at));
|
|
|
- }
|
|
|
-
|
|
|
- /// Sets new election parameters. Some combination of parameters that are not desirable, so
|
|
|
- /// the parameters are checked for validity.
|
|
|
- /// The call will fail if an election is in progress. If a council is not being elected for some
|
|
|
- /// reaon after multiple rounds, force_stop_election() can be called to stop elections and followed by
|
|
|
- /// set_election_parameters().
|
|
|
- #[weight = 10_000_000] // TODO: adjust weight
|
|
|
- pub fn set_election_parameters(origin, params: ElectionParameters<BalanceOf<T>, T::BlockNumber>) {
|
|
|
- ensure_root(origin)?;
|
|
|
- ensure!(!Self::is_election_running(), MSG_CANNOT_CHANGE_PARAMS_DURING_ELECTION);
|
|
|
- params.ensure_valid()?;
|
|
|
- Self::set_verified_election_parameters(params);
|
|
|
- }
|
|
|
-
|
|
|
- #[weight = 10_000_000] // TODO: adjust weight
|
|
|
- fn force_stop_election(origin) {
|
|
|
- ensure_root(origin)?;
|
|
|
- ensure!(Self::is_election_running(), "only running election can be stopped");
|
|
|
-
|
|
|
- let mut votes = Vec::new();
|
|
|
- for commitment in Self::commitments() {
|
|
|
- votes.push(Self::votes(commitment));
|
|
|
- }
|
|
|
-
|
|
|
- // no council gets elected
|
|
|
- let empty_council = BTreeMap::new();
|
|
|
-
|
|
|
- Self::teardown_election (
|
|
|
- &votes,
|
|
|
- &empty_council,
|
|
|
- false /* do not unlock transferable stakes */
|
|
|
- );
|
|
|
- }
|
|
|
-
|
|
|
- #[weight = 10_000_000] // TODO: adjust weight
|
|
|
- pub fn force_start_election(origin) {
|
|
|
- ensure_root(origin)?;
|
|
|
- Self::start_election(<council::Module<T>>::active_council())?;
|
|
|
- }
|
|
|
-
|
|
|
- #[weight = 10_000_000] // TODO: adjust weight
|
|
|
- fn set_auto_start (origin, flag: bool) {
|
|
|
- ensure_root(origin)?;
|
|
|
- AutoStart::put(flag);
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-impl<T: Trait> council::CouncilTermEnded for Module<T> {
|
|
|
- fn council_term_ended() {
|
|
|
- if Self::auto_start() {
|
|
|
- let _ = Self::start_election(<council::Module<T>>::active_council());
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-#[cfg(test)]
|
|
|
-mod tests {
|
|
|
- use super::*;
|
|
|
- use crate::mock::*;
|
|
|
- use codec::Encode;
|
|
|
- use frame_support::traits::OnFinalize;
|
|
|
- use frame_support::{assert_err, assert_ok};
|
|
|
- use frame_system::RawOrigin;
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn election_starts_when_council_term_ends() {
|
|
|
- initial_test_ext().execute_with(|| {
|
|
|
- System::set_block_number(1);
|
|
|
-
|
|
|
- assert!(Council::is_term_ended());
|
|
|
- assert!(Election::stage().is_none());
|
|
|
-
|
|
|
- <Election as council::CouncilTermEnded>::council_term_ended();
|
|
|
-
|
|
|
- assert!(Election::stage().is_some());
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn new_stake_reusing_transferable_works() {
|
|
|
- {
|
|
|
- let mut transferable = 0;
|
|
|
- let additional = 100;
|
|
|
- let new_stake = Election::new_stake_reusing_transferable(&mut transferable, additional);
|
|
|
- assert_eq!(new_stake.new, 100);
|
|
|
- assert_eq!(new_stake.transferred, 0);
|
|
|
- }
|
|
|
-
|
|
|
- {
|
|
|
- let mut transferable = 40;
|
|
|
- let additional = 60;
|
|
|
- let new_stake = Election::new_stake_reusing_transferable(&mut transferable, additional);
|
|
|
- assert_eq!(new_stake.new, 20);
|
|
|
- assert_eq!(new_stake.transferred, 40);
|
|
|
- assert_eq!(transferable, 0);
|
|
|
- }
|
|
|
-
|
|
|
- {
|
|
|
- let mut transferable = 1000;
|
|
|
- let additional = 100;
|
|
|
- let new_stake = Election::new_stake_reusing_transferable(&mut transferable, additional);
|
|
|
- assert_eq!(new_stake.new, 0);
|
|
|
- assert_eq!(new_stake.transferred, 100);
|
|
|
- assert_eq!(transferable, 900);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn check_default_params() {
|
|
|
- // TODO missing test implementation?
|
|
|
- }
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn should_not_start_new_election_if_already_started() {
|
|
|
- initial_test_ext().execute_with(|| {
|
|
|
- assert_ok!(Election::start_election(vec![]));
|
|
|
- assert_err!(
|
|
|
- Election::start_election(vec![]),
|
|
|
- "election already in progress"
|
|
|
- );
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- fn assert_announcing_period(expected_period: <Test as frame_system::Trait>::BlockNumber) {
|
|
|
- assert!(
|
|
|
- Election::is_election_running(),
|
|
|
- "Election Stage was not set"
|
|
|
- );
|
|
|
-
|
|
|
- let election_stage = Election::stage().unwrap();
|
|
|
-
|
|
|
- match election_stage {
|
|
|
- election::ElectionStage::Announcing(period) => {
|
|
|
- assert_eq!(period, expected_period, "Election period not set correctly")
|
|
|
- }
|
|
|
- _ => assert!(false, "Election Stage was not correctly set to Announcing"),
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn start_election_should_work() {
|
|
|
- initial_test_ext().execute_with(|| {
|
|
|
- System::set_block_number(1);
|
|
|
- <AnnouncingPeriod<Test>>::put(20);
|
|
|
- let prev_round = Election::round();
|
|
|
-
|
|
|
- assert_ok!(Election::start_election(vec![]));
|
|
|
-
|
|
|
- // election round is bumped
|
|
|
- assert_eq!(Election::round(), prev_round + 1);
|
|
|
-
|
|
|
- // we enter the announcing stage for a specified period
|
|
|
- assert_announcing_period(1 + Election::announcing_period());
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn init_transferable_stake_should_work() {
|
|
|
- initial_test_ext().execute_with(|| {
|
|
|
- let existing_council = vec![
|
|
|
- Seat {
|
|
|
- member: 1,
|
|
|
- stake: 100,
|
|
|
- backers: vec![
|
|
|
- Backer {
|
|
|
- member: 2,
|
|
|
- stake: 50,
|
|
|
- },
|
|
|
- Backer {
|
|
|
- member: 3,
|
|
|
- stake: 40,
|
|
|
- },
|
|
|
- Backer {
|
|
|
- member: 10,
|
|
|
- stake: 10,
|
|
|
- },
|
|
|
- ],
|
|
|
- },
|
|
|
- Seat {
|
|
|
- member: 2,
|
|
|
- stake: 200,
|
|
|
- backers: vec![
|
|
|
- Backer {
|
|
|
- member: 1,
|
|
|
- stake: 10,
|
|
|
- },
|
|
|
- Backer {
|
|
|
- member: 3,
|
|
|
- stake: 60,
|
|
|
- },
|
|
|
- Backer {
|
|
|
- member: 20,
|
|
|
- stake: 20,
|
|
|
- },
|
|
|
- ],
|
|
|
- },
|
|
|
- Seat {
|
|
|
- member: 3,
|
|
|
- stake: 300,
|
|
|
- backers: vec![
|
|
|
- Backer {
|
|
|
- member: 1,
|
|
|
- stake: 20,
|
|
|
- },
|
|
|
- Backer {
|
|
|
- member: 2,
|
|
|
- stake: 40,
|
|
|
- },
|
|
|
- ],
|
|
|
- },
|
|
|
- ];
|
|
|
-
|
|
|
- Election::initialize_transferable_stakes(existing_council);
|
|
|
- let mut existing_stake_holders = Election::existing_stake_holders();
|
|
|
- existing_stake_holders.sort();
|
|
|
- assert_eq!(existing_stake_holders, vec![1, 2, 3, 10, 20]);
|
|
|
-
|
|
|
- assert_eq!(Election::transferable_stakes(&1).seat, 100);
|
|
|
- assert_eq!(Election::transferable_stakes(&1).backing, 30);
|
|
|
-
|
|
|
- assert_eq!(Election::transferable_stakes(&2).seat, 200);
|
|
|
- assert_eq!(Election::transferable_stakes(&2).backing, 90);
|
|
|
-
|
|
|
- assert_eq!(Election::transferable_stakes(&3).seat, 300);
|
|
|
- assert_eq!(Election::transferable_stakes(&3).backing, 100);
|
|
|
-
|
|
|
- assert_eq!(Election::transferable_stakes(&10).seat, 0);
|
|
|
- assert_eq!(Election::transferable_stakes(&10).backing, 10);
|
|
|
-
|
|
|
- assert_eq!(Election::transferable_stakes(&20).seat, 0);
|
|
|
- assert_eq!(Election::transferable_stakes(&20).backing, 20);
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn try_add_applicant_should_work() {
|
|
|
- initial_test_ext().execute_with(|| {
|
|
|
- assert!(Election::applicants().len() == 0);
|
|
|
-
|
|
|
- let applicant = 20 as u64;
|
|
|
-
|
|
|
- let starting_balance = 1000 as u64;
|
|
|
- let _ = Balances::deposit_creating(&applicant, starting_balance);
|
|
|
-
|
|
|
- let stake = 100 as u64;
|
|
|
-
|
|
|
- assert!(Election::try_add_applicant(applicant, stake).is_ok());
|
|
|
- assert_eq!(Election::applicants(), vec![applicant]);
|
|
|
-
|
|
|
- assert_eq!(Election::applicant_stakes(applicant).new, stake);
|
|
|
- assert_eq!(Election::applicant_stakes(applicant).transferred, 0);
|
|
|
-
|
|
|
- assert_eq!(Balances::free_balance(&applicant), starting_balance - stake);
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn increasing_applicant_stake_should_work() {
|
|
|
- initial_test_ext().execute_with(|| {
|
|
|
- let applicant = 20 as u64;
|
|
|
- let starting_stake = 100 as u64;
|
|
|
-
|
|
|
- <Applicants<Test>>::put(vec![applicant]);
|
|
|
- <ApplicantStakes<Test>>::insert(
|
|
|
- applicant,
|
|
|
- Stake {
|
|
|
- new: starting_stake,
|
|
|
- transferred: 0,
|
|
|
- },
|
|
|
- );
|
|
|
-
|
|
|
- let additional_stake = 100 as u64;
|
|
|
- let _ = Balances::deposit_creating(&applicant, additional_stake);
|
|
|
- assert!(Election::try_add_applicant(applicant, additional_stake).is_ok());
|
|
|
-
|
|
|
- assert_eq!(
|
|
|
- Election::applicant_stakes(applicant).new,
|
|
|
- starting_stake + additional_stake
|
|
|
- );
|
|
|
- assert_eq!(Election::applicant_stakes(applicant).transferred, 0)
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn using_transferable_seat_stake_should_work() {
|
|
|
- initial_test_ext().execute_with(|| {
|
|
|
- let applicant = 20 as u64;
|
|
|
- let _ = Balances::deposit_creating(&applicant, 5000);
|
|
|
-
|
|
|
- <ExistingStakeHolders<Test>>::put(vec![applicant]);
|
|
|
- save_transferable_stake(
|
|
|
- applicant,
|
|
|
- TransferableStake {
|
|
|
- seat: 1000,
|
|
|
- backing: 0,
|
|
|
- },
|
|
|
- );
|
|
|
-
|
|
|
- <Applicants<Test>>::put(vec![applicant]);
|
|
|
- let starting_stake = Stake {
|
|
|
- new: 100,
|
|
|
- transferred: 0,
|
|
|
- };
|
|
|
- <ApplicantStakes<Test>>::insert(applicant, starting_stake);
|
|
|
-
|
|
|
- // transferable stake covers new stake
|
|
|
- assert!(Election::try_add_applicant(applicant, 600).is_ok());
|
|
|
- assert_eq!(
|
|
|
- Election::applicant_stakes(applicant).new,
|
|
|
- starting_stake.new
|
|
|
- );
|
|
|
- assert_eq!(Election::applicant_stakes(applicant).transferred, 600);
|
|
|
- assert_eq!(Election::transferable_stakes(applicant).seat, 400);
|
|
|
- assert_eq!(Balances::free_balance(applicant), 5000);
|
|
|
-
|
|
|
- // all remaining transferable stake is consumed and free balance covers remaining stake
|
|
|
- assert!(Election::try_add_applicant(applicant, 1000).is_ok());
|
|
|
- assert_eq!(
|
|
|
- Election::applicant_stakes(applicant).new,
|
|
|
- starting_stake.new + 600
|
|
|
- );
|
|
|
- assert_eq!(Election::applicant_stakes(applicant).transferred, 1000);
|
|
|
- assert_eq!(Election::transferable_stakes(applicant).seat, 0);
|
|
|
- assert_eq!(Balances::free_balance(applicant), 4400);
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn moving_to_voting_without_enough_applicants_should_not_work() {
|
|
|
- initial_test_ext().execute_with(|| {
|
|
|
- System::set_block_number(1);
|
|
|
- <AnnouncingPeriod<Test>>::put(20);
|
|
|
- CouncilSize::put(10);
|
|
|
- Election::move_to_announcing_stage();
|
|
|
- let round = Election::round();
|
|
|
-
|
|
|
- // add applicants
|
|
|
- <Applicants<Test>>::put(vec![10, 20, 30]);
|
|
|
- let stake = Stake {
|
|
|
- new: 10,
|
|
|
- transferred: 0,
|
|
|
- };
|
|
|
-
|
|
|
- let applicants = Election::applicants();
|
|
|
-
|
|
|
- for applicant in applicants.iter() {
|
|
|
- <ApplicantStakes<Test>>::insert(applicant, stake);
|
|
|
- }
|
|
|
-
|
|
|
- // make sure we are testing the condition that we don't have enough applicants
|
|
|
- assert!(Election::council_size_usize() > applicants.len());
|
|
|
-
|
|
|
- // try to move to voting stage
|
|
|
- let ann_ends = Election::stage_ends_at().unwrap();
|
|
|
- System::set_block_number(ann_ends);
|
|
|
- Election::on_announcing_ended();
|
|
|
-
|
|
|
- // A new round should have been started
|
|
|
- assert_eq!(Election::round(), round + 1);
|
|
|
-
|
|
|
- // A new announcing period started
|
|
|
- assert_announcing_period(ann_ends + Election::announcing_period());
|
|
|
-
|
|
|
- // applicants list should be unchanged..
|
|
|
- assert_eq!(Election::applicants(), applicants);
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn top_applicants_move_to_voting_stage() {
|
|
|
- initial_test_ext().execute_with(|| {
|
|
|
- <Applicants<Test>>::put(vec![10, 20, 30, 40]);
|
|
|
- let mut applicants = Election::applicants();
|
|
|
-
|
|
|
- for (i, applicant) in applicants.iter().enumerate() {
|
|
|
- <ApplicantStakes<Test>>::insert(
|
|
|
- applicant,
|
|
|
- Stake {
|
|
|
- new: (i * 10) as u64,
|
|
|
- transferred: 0,
|
|
|
- },
|
|
|
- );
|
|
|
- }
|
|
|
-
|
|
|
- let rejected = Election::find_least_staked_applicants(&mut applicants, 3);
|
|
|
- assert_eq!(rejected.to_vec(), vec![10]);
|
|
|
-
|
|
|
- <Applicants<Test>>::put(vec![40, 30, 20, 10]);
|
|
|
- let mut applicants = Election::applicants();
|
|
|
-
|
|
|
- for applicant in applicants.iter() {
|
|
|
- <ApplicantStakes<Test>>::insert(
|
|
|
- applicant,
|
|
|
- Stake {
|
|
|
- new: 20,
|
|
|
- transferred: 0,
|
|
|
- },
|
|
|
- );
|
|
|
- }
|
|
|
-
|
|
|
- // stable sort is preserving order when two elements are equivalent
|
|
|
- let rejected = Election::find_least_staked_applicants(&mut applicants, 3);
|
|
|
- assert_eq!(rejected.to_vec(), vec![40]);
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn refunding_applicant_stakes_should_work() {
|
|
|
- initial_test_ext().execute_with(|| {
|
|
|
- let _ = Balances::deposit_creating(&1, 1000);
|
|
|
- let _ = Balances::deposit_creating(&2, 7000);
|
|
|
- let _ = Balances::reserve(&2, 5000);
|
|
|
- let _ = Balances::deposit_creating(&3, 8000);
|
|
|
- let _ = Balances::reserve(&3, 5000);
|
|
|
-
|
|
|
- <Applicants<Test>>::put(vec![1, 2, 3]);
|
|
|
-
|
|
|
- save_transferable_stake(
|
|
|
- 1,
|
|
|
- TransferableStake {
|
|
|
- seat: 50,
|
|
|
- backing: 0,
|
|
|
- },
|
|
|
- );
|
|
|
- save_transferable_stake(
|
|
|
- 2,
|
|
|
- TransferableStake {
|
|
|
- seat: 0,
|
|
|
- backing: 0,
|
|
|
- },
|
|
|
- );
|
|
|
- save_transferable_stake(
|
|
|
- 3,
|
|
|
- TransferableStake {
|
|
|
- seat: 0,
|
|
|
- backing: 0,
|
|
|
- },
|
|
|
- );
|
|
|
-
|
|
|
- <ApplicantStakes<Test>>::insert(
|
|
|
- 1,
|
|
|
- Stake {
|
|
|
- new: 100,
|
|
|
- transferred: 200,
|
|
|
- },
|
|
|
- );
|
|
|
-
|
|
|
- <ApplicantStakes<Test>>::insert(
|
|
|
- 2,
|
|
|
- Stake {
|
|
|
- new: 300,
|
|
|
- transferred: 400,
|
|
|
- },
|
|
|
- );
|
|
|
-
|
|
|
- <ApplicantStakes<Test>>::insert(
|
|
|
- 3,
|
|
|
- Stake {
|
|
|
- new: 500,
|
|
|
- transferred: 600,
|
|
|
- },
|
|
|
- );
|
|
|
-
|
|
|
- Election::drop_applicants(&vec![2, 3][..]);
|
|
|
-
|
|
|
- assert_eq!(Election::applicants(), vec![1]);
|
|
|
-
|
|
|
- assert_eq!(Election::applicant_stakes(1).new, 100);
|
|
|
- assert_eq!(Election::applicant_stakes(1).transferred, 200);
|
|
|
- assert_eq!(Election::transferable_stakes(1).seat, 50);
|
|
|
- assert_eq!(Balances::free_balance(&1), 1000);
|
|
|
-
|
|
|
- //assert_eq!(Election::applicant_stakes(2), Default::default());
|
|
|
- assert!(!<ApplicantStakes<Test>>::contains_key(2));
|
|
|
- assert_eq!(Election::transferable_stakes(2).seat, 400);
|
|
|
- assert_eq!(Balances::free_balance(&2), 2300);
|
|
|
-
|
|
|
- //assert_eq!(Election::applicant_stakes(3), Default::default());
|
|
|
- assert!(!<ApplicantStakes<Test>>::contains_key(3));
|
|
|
- assert_eq!(Election::transferable_stakes(3).seat, 600);
|
|
|
- assert_eq!(Balances::free_balance(&3), 3500);
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn voting_should_work() {
|
|
|
- initial_test_ext().execute_with(|| {
|
|
|
- let _ = Balances::deposit_creating(&20, 1000);
|
|
|
- let payload = vec![10u8];
|
|
|
- let commitment = <Test as frame_system::Trait>::Hashing::hash(&payload[..]);
|
|
|
-
|
|
|
- assert!(Election::try_add_vote(20, 100, commitment).is_ok());
|
|
|
-
|
|
|
- assert_eq!(Election::commitments(), vec![commitment]);
|
|
|
- assert_eq!(Election::votes(commitment).voter, 20);
|
|
|
- assert_eq!(Election::votes(commitment).commitment, commitment);
|
|
|
- assert_eq!(
|
|
|
- Election::votes(commitment).stake,
|
|
|
- Stake {
|
|
|
- new: 100,
|
|
|
- transferred: 0,
|
|
|
- }
|
|
|
- );
|
|
|
- assert_eq!(Balances::free_balance(&20), 900);
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- fn save_transferable_stake(id: u64, stake: TransferableStake<u64>) {
|
|
|
- <TransferableStakes<Test>>::insert(id, stake);
|
|
|
- }
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn votes_can_be_covered_by_transferable_stake() {
|
|
|
- initial_test_ext().execute_with(|| {
|
|
|
- let _ = Balances::deposit_creating(&20, 1000);
|
|
|
-
|
|
|
- save_transferable_stake(
|
|
|
- 20,
|
|
|
- TransferableStake {
|
|
|
- seat: 0,
|
|
|
- backing: 500,
|
|
|
- },
|
|
|
- );
|
|
|
-
|
|
|
- let payload = vec![10u8];
|
|
|
- let commitment = <Test as frame_system::Trait>::Hashing::hash(&payload[..]);
|
|
|
-
|
|
|
- assert!(Election::try_add_vote(20, 100, commitment).is_ok());
|
|
|
-
|
|
|
- assert_eq!(Election::commitments(), vec![commitment]);
|
|
|
- assert_eq!(Election::votes(commitment).voter, 20);
|
|
|
- assert_eq!(Election::votes(commitment).commitment, commitment);
|
|
|
- assert_eq!(
|
|
|
- Election::votes(commitment).stake,
|
|
|
- Stake {
|
|
|
- new: 0,
|
|
|
- transferred: 100,
|
|
|
- }
|
|
|
- );
|
|
|
- assert_eq!(Balances::free_balance(&20), 1000);
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn voting_without_enough_balance_should_not_work() {
|
|
|
- initial_test_ext().execute_with(|| {
|
|
|
- let _ = Balances::deposit_creating(&20, 100);
|
|
|
-
|
|
|
- save_transferable_stake(
|
|
|
- 20,
|
|
|
- TransferableStake {
|
|
|
- seat: 0,
|
|
|
- backing: 500,
|
|
|
- },
|
|
|
- );
|
|
|
-
|
|
|
- let payload = vec![10u8];
|
|
|
- let commitment = <Test as frame_system::Trait>::Hashing::hash(&payload[..]);
|
|
|
-
|
|
|
- assert!(Election::try_add_vote(20, 1000, commitment).is_err());
|
|
|
- assert_eq!(Election::commitments(), vec![]);
|
|
|
- assert!(!<Votes<Test>>::contains_key(commitment));
|
|
|
- assert_eq!(Balances::free_balance(&20), 100);
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn voting_with_existing_commitment_should_not_work() {
|
|
|
- initial_test_ext().execute_with(|| {
|
|
|
- let _ = Balances::deposit_creating(&20, 1000);
|
|
|
-
|
|
|
- save_transferable_stake(
|
|
|
- 20,
|
|
|
- TransferableStake {
|
|
|
- seat: 0,
|
|
|
- backing: 500,
|
|
|
- },
|
|
|
- );
|
|
|
-
|
|
|
- let payload = vec![10u8];
|
|
|
- let commitment = <Test as frame_system::Trait>::Hashing::hash(&payload[..]);
|
|
|
-
|
|
|
- assert!(Election::try_add_vote(20, 100, commitment).is_ok());
|
|
|
-
|
|
|
- assert_eq!(Election::commitments(), vec![commitment]);
|
|
|
- assert_eq!(Election::votes(commitment).voter, 20);
|
|
|
- assert_eq!(Election::votes(commitment).commitment, commitment);
|
|
|
- assert_eq!(
|
|
|
- Election::votes(commitment).stake,
|
|
|
- Stake {
|
|
|
- new: 0,
|
|
|
- transferred: 100,
|
|
|
- }
|
|
|
- );
|
|
|
- assert_eq!(Balances::free_balance(&20), 1000);
|
|
|
-
|
|
|
- assert!(Election::try_add_vote(30, 100, commitment).is_err());
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- fn make_commitment_for_applicant(
|
|
|
- applicant: <Test as frame_system::Trait>::AccountId,
|
|
|
- salt: &mut Vec<u8>,
|
|
|
- ) -> <Test as frame_system::Trait>::Hash {
|
|
|
- let mut payload = applicant.encode();
|
|
|
- payload.append(salt);
|
|
|
- <Test as frame_system::Trait>::Hashing::hash(&payload[..])
|
|
|
- }
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn revealing_vote_works() {
|
|
|
- initial_test_ext().execute_with(|| {
|
|
|
- let applicant = 20 as u64;
|
|
|
- let salt = vec![128u8];
|
|
|
- let commitment = make_commitment_for_applicant(applicant, &mut salt.clone());
|
|
|
- let voter = 10 as u64;
|
|
|
-
|
|
|
- <ApplicantStakes<Test>>::insert(
|
|
|
- &applicant,
|
|
|
- Stake {
|
|
|
- new: 0,
|
|
|
- transferred: 0,
|
|
|
- },
|
|
|
- );
|
|
|
-
|
|
|
- <Votes<Test>>::insert(
|
|
|
- &commitment,
|
|
|
- SealedVote::new(
|
|
|
- voter,
|
|
|
- Stake {
|
|
|
- new: 100,
|
|
|
- transferred: 0,
|
|
|
- },
|
|
|
- commitment,
|
|
|
- ),
|
|
|
- );
|
|
|
-
|
|
|
- assert!(<Votes<Test>>::get(commitment).is_not_revealed());
|
|
|
- assert!(Election::try_reveal_vote(voter, commitment, applicant, salt).is_ok());
|
|
|
- assert_eq!(
|
|
|
- <Votes<Test>>::get(commitment).get_vote().unwrap(),
|
|
|
- applicant
|
|
|
- );
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn revealing_with_bad_salt_should_not_work() {
|
|
|
- initial_test_ext().execute_with(|| {
|
|
|
- let applicant = 20 as u64;
|
|
|
- let salt = vec![128u8];
|
|
|
- let commitment = make_commitment_for_applicant(applicant, &mut salt.clone());
|
|
|
- let voter = 10 as u64;
|
|
|
-
|
|
|
- <ApplicantStakes<Test>>::insert(
|
|
|
- &applicant,
|
|
|
- Stake {
|
|
|
- new: 0,
|
|
|
- transferred: 0,
|
|
|
- },
|
|
|
- );
|
|
|
-
|
|
|
- <Votes<Test>>::insert(
|
|
|
- &commitment,
|
|
|
- SealedVote::new(
|
|
|
- voter,
|
|
|
- Stake {
|
|
|
- new: 100,
|
|
|
- transferred: 0,
|
|
|
- },
|
|
|
- commitment,
|
|
|
- ),
|
|
|
- );
|
|
|
-
|
|
|
- assert!(<Votes<Test>>::get(commitment).is_not_revealed());
|
|
|
- assert!(Election::try_reveal_vote(voter, commitment, applicant, vec![]).is_err());
|
|
|
- assert!(<Votes<Test>>::get(commitment).is_not_revealed());
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn revealing_non_matching_commitment_should_not_work() {
|
|
|
- initial_test_ext().execute_with(|| {
|
|
|
- let applicant = 20 as u64;
|
|
|
- let salt = vec![128u8];
|
|
|
- let commitment = make_commitment_for_applicant(100, &mut salt.clone());
|
|
|
- let voter = 10 as u64;
|
|
|
-
|
|
|
- <ApplicantStakes<Test>>::insert(
|
|
|
- &applicant,
|
|
|
- Stake {
|
|
|
- new: 0,
|
|
|
- transferred: 0,
|
|
|
- },
|
|
|
- );
|
|
|
-
|
|
|
- assert!(Election::try_reveal_vote(voter, commitment, applicant, vec![]).is_err());
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn revealing_for_non_applicant_should_not_work() {
|
|
|
- initial_test_ext().execute_with(|| {
|
|
|
- let applicant = 20 as u64;
|
|
|
- let salt = vec![128u8];
|
|
|
- let commitment = make_commitment_for_applicant(applicant, &mut salt.clone());
|
|
|
- let voter = 10 as u64;
|
|
|
-
|
|
|
- <Votes<Test>>::insert(
|
|
|
- &commitment,
|
|
|
- SealedVote::new(
|
|
|
- voter,
|
|
|
- Stake {
|
|
|
- new: 100,
|
|
|
- transferred: 0,
|
|
|
- },
|
|
|
- commitment,
|
|
|
- ),
|
|
|
- );
|
|
|
-
|
|
|
- assert!(<Votes<Test>>::get(commitment).is_not_revealed());
|
|
|
- assert!(Election::try_reveal_vote(voter, commitment, applicant, vec![]).is_err());
|
|
|
- assert!(<Votes<Test>>::get(commitment).is_not_revealed());
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn revealing_by_non_committer_should_not_work() {
|
|
|
- initial_test_ext().execute_with(|| {
|
|
|
- let applicant = 20 as u64;
|
|
|
- let salt = vec![128u8];
|
|
|
- let commitment = make_commitment_for_applicant(applicant, &mut salt.clone());
|
|
|
- let voter = 10 as u64;
|
|
|
- let not_voter = 100 as u64;
|
|
|
-
|
|
|
- <ApplicantStakes<Test>>::insert(
|
|
|
- &applicant,
|
|
|
- Stake {
|
|
|
- new: 0,
|
|
|
- transferred: 0,
|
|
|
- },
|
|
|
- );
|
|
|
-
|
|
|
- <Votes<Test>>::insert(
|
|
|
- &commitment,
|
|
|
- SealedVote::new(
|
|
|
- voter,
|
|
|
- Stake {
|
|
|
- new: 100,
|
|
|
- transferred: 0,
|
|
|
- },
|
|
|
- commitment,
|
|
|
- ),
|
|
|
- );
|
|
|
-
|
|
|
- assert!(<Votes<Test>>::get(commitment).is_not_revealed());
|
|
|
- assert!(Election::try_reveal_vote(not_voter, commitment, applicant, salt).is_err());
|
|
|
- assert!(<Votes<Test>>::get(commitment).is_not_revealed());
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- pub fn mock_votes(
|
|
|
- mock: Vec<(u64, u64, u64, u64)>,
|
|
|
- ) -> Vec<SealedVote<u64, Stake<u64>, sp_core::H256, u64>> {
|
|
|
- let commitment = make_commitment_for_applicant(1, &mut vec![0u8]);
|
|
|
-
|
|
|
- mock.into_iter()
|
|
|
- .map(|(voter, stake_ref, stake_tran, applicant)| {
|
|
|
- SealedVote::new_unsealed(
|
|
|
- voter as u64,
|
|
|
- Stake {
|
|
|
- new: stake_ref,
|
|
|
- transferred: stake_tran,
|
|
|
- },
|
|
|
- commitment,
|
|
|
- applicant as u64,
|
|
|
- )
|
|
|
- })
|
|
|
- .collect()
|
|
|
- }
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn vote_tallying_should_work() {
|
|
|
- initial_test_ext().execute_with(|| {
|
|
|
- let votes = mock_votes(vec![
|
|
|
- // (voter, stake[new], stake[transferred], applicant)
|
|
|
- (10, 100, 0, 100),
|
|
|
- (10, 150, 0, 100),
|
|
|
- (10, 500, 0, 200),
|
|
|
- (20, 200, 0, 200),
|
|
|
- (30, 300, 0, 300),
|
|
|
- (30, 400, 0, 300),
|
|
|
- ]);
|
|
|
-
|
|
|
- let tally = Election::tally_votes(&votes);
|
|
|
-
|
|
|
- assert_eq!(tally.len(), 3);
|
|
|
-
|
|
|
- assert_eq!(tally.get(&100).unwrap().member, 100);
|
|
|
- assert_eq!(
|
|
|
- tally.get(&100).unwrap().backers,
|
|
|
- vec![
|
|
|
- Backer {
|
|
|
- member: 10 as u64,
|
|
|
- stake: 100 as u64,
|
|
|
- },
|
|
|
- Backer {
|
|
|
- member: 10 as u64,
|
|
|
- stake: 150 as u64,
|
|
|
- },
|
|
|
- ]
|
|
|
- );
|
|
|
-
|
|
|
- assert_eq!(tally.get(&200).unwrap().member, 200);
|
|
|
- assert_eq!(
|
|
|
- tally.get(&200).unwrap().backers,
|
|
|
- vec![
|
|
|
- Backer {
|
|
|
- member: 10 as u64,
|
|
|
- stake: 500 as u64,
|
|
|
- },
|
|
|
- Backer {
|
|
|
- member: 20 as u64,
|
|
|
- stake: 200 as u64,
|
|
|
- }
|
|
|
- ]
|
|
|
- );
|
|
|
-
|
|
|
- assert_eq!(tally.get(&300).unwrap().member, 300);
|
|
|
- assert_eq!(
|
|
|
- tally.get(&300).unwrap().backers,
|
|
|
- vec![
|
|
|
- Backer {
|
|
|
- member: 30 as u64,
|
|
|
- stake: 300 as u64,
|
|
|
- },
|
|
|
- Backer {
|
|
|
- member: 30 as u64,
|
|
|
- stake: 400 as u64,
|
|
|
- }
|
|
|
- ]
|
|
|
- );
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn filter_top_staked_applicants_should_work() {
|
|
|
- initial_test_ext().execute_with(|| {
|
|
|
- // filter_top_staked depends on order of applicants
|
|
|
- <Applicants<Test>>::put(vec![100, 200, 300]);
|
|
|
-
|
|
|
- {
|
|
|
- let votes = mock_votes(vec![
|
|
|
- // (voter, stake[new], stake[transferred], applicant)
|
|
|
- (10, 100, 0, 100),
|
|
|
- (10, 150, 0, 100),
|
|
|
- (10, 500, 0, 200),
|
|
|
- (20, 200, 0, 200),
|
|
|
- (30, 300, 0, 300),
|
|
|
- (30, 400, 0, 300),
|
|
|
- ]);
|
|
|
-
|
|
|
- let mut tally = Election::tally_votes(&votes);
|
|
|
- assert_eq!(tally.len(), 3);
|
|
|
- Election::filter_top_staked(&mut tally, 3);
|
|
|
- assert_eq!(tally.len(), 3);
|
|
|
- }
|
|
|
-
|
|
|
- {
|
|
|
- let votes = mock_votes(vec![
|
|
|
- // (voter, stake[new], stake[transferred], applicant)
|
|
|
- (10, 100, 0, 100),
|
|
|
- (10, 150, 0, 100),
|
|
|
- (10, 500, 0, 200),
|
|
|
- (20, 200, 0, 200),
|
|
|
- (30, 300, 0, 300),
|
|
|
- (30, 400, 0, 300),
|
|
|
- ]);
|
|
|
-
|
|
|
- let mut tally = Election::tally_votes(&votes);
|
|
|
- assert_eq!(tally.len(), 3);
|
|
|
- Election::filter_top_staked(&mut tally, 2);
|
|
|
- assert_eq!(tally.len(), 2);
|
|
|
- assert!(tally.get(&200).is_some());
|
|
|
- assert!(tally.get(&300).is_some());
|
|
|
- }
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn drop_unelected_applicants_should_work() {
|
|
|
- initial_test_ext().execute_with(|| {
|
|
|
- <Applicants<Test>>::put(vec![100, 200, 300]);
|
|
|
-
|
|
|
- let _ = Balances::deposit_creating(&100, 2000);
|
|
|
- let _ = Balances::reserve(&100, 1000);
|
|
|
-
|
|
|
- <ApplicantStakes<Test>>::insert(
|
|
|
- 100,
|
|
|
- Stake {
|
|
|
- new: 20 as u64,
|
|
|
- transferred: 50 as u64,
|
|
|
- },
|
|
|
- );
|
|
|
-
|
|
|
- save_transferable_stake(
|
|
|
- 100,
|
|
|
- TransferableStake {
|
|
|
- seat: 100,
|
|
|
- backing: 0,
|
|
|
- },
|
|
|
- );
|
|
|
-
|
|
|
- let mut new_council: BTreeMap<u64, Seat<u64, u64>> = BTreeMap::new();
|
|
|
- new_council.insert(
|
|
|
- 200 as u64,
|
|
|
- Seat {
|
|
|
- member: 200 as u64,
|
|
|
- stake: 0 as u64,
|
|
|
- backers: vec![],
|
|
|
- },
|
|
|
- );
|
|
|
- new_council.insert(
|
|
|
- 300 as u64,
|
|
|
- Seat {
|
|
|
- member: 300 as u64,
|
|
|
- stake: 0 as u64,
|
|
|
- backers: vec![],
|
|
|
- },
|
|
|
- );
|
|
|
-
|
|
|
- Election::drop_unelected_applicants(&new_council);
|
|
|
-
|
|
|
- // applicant dropped
|
|
|
- assert_eq!(Election::applicants(), vec![200, 300]);
|
|
|
- assert!(!<ApplicantStakes<Test>>::contains_key(100));
|
|
|
-
|
|
|
- // and refunded
|
|
|
- assert_eq!(Election::transferable_stakes(100).seat, 150);
|
|
|
- assert_eq!(Balances::free_balance(&100), 1020);
|
|
|
- assert_eq!(Balances::reserved_balance(&100), 980);
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn refunding_voting_stakes_should_work() {
|
|
|
- initial_test_ext().execute_with(|| {
|
|
|
- // voters' balances
|
|
|
- let _ = Balances::deposit_creating(&10, 6000);
|
|
|
- let _ = Balances::reserve(&10, 5000);
|
|
|
- let _ = Balances::deposit_creating(&20, 7000);
|
|
|
- let _ = Balances::reserve(&20, 5000);
|
|
|
- let _ = Balances::deposit_creating(&30, 8000);
|
|
|
- let _ = Balances::reserve(&30, 5000);
|
|
|
-
|
|
|
- save_transferable_stake(
|
|
|
- 10,
|
|
|
- TransferableStake {
|
|
|
- seat: 0,
|
|
|
- backing: 100,
|
|
|
- },
|
|
|
- );
|
|
|
- save_transferable_stake(
|
|
|
- 20,
|
|
|
- TransferableStake {
|
|
|
- seat: 0,
|
|
|
- backing: 200,
|
|
|
- },
|
|
|
- );
|
|
|
- save_transferable_stake(
|
|
|
- 30,
|
|
|
- TransferableStake {
|
|
|
- seat: 0,
|
|
|
- backing: 300,
|
|
|
- },
|
|
|
- );
|
|
|
-
|
|
|
- let votes = mock_votes(vec![
|
|
|
- // (voter, stake[new], stake[transferred], applicant)
|
|
|
- (10, 100, 20, 100),
|
|
|
- (20, 200, 40, 100),
|
|
|
- (30, 300, 60, 100),
|
|
|
- (10, 500, 70, 200),
|
|
|
- (20, 600, 80, 200),
|
|
|
- (30, 700, 90, 200),
|
|
|
- (10, 800, 100, 300),
|
|
|
- (20, 900, 120, 300),
|
|
|
- (30, 1000, 140, 300),
|
|
|
- ]);
|
|
|
-
|
|
|
- let mut new_council: BTreeMap<u64, Seat<u64, u64>> = BTreeMap::new();
|
|
|
- new_council.insert(
|
|
|
- 200 as u64,
|
|
|
- Seat {
|
|
|
- member: 200 as u64,
|
|
|
- stake: 0 as u64,
|
|
|
- backers: vec![],
|
|
|
- },
|
|
|
- );
|
|
|
- new_council.insert(
|
|
|
- 300 as u64,
|
|
|
- Seat {
|
|
|
- member: 300 as u64,
|
|
|
- stake: 0 as u64,
|
|
|
- backers: vec![],
|
|
|
- },
|
|
|
- );
|
|
|
-
|
|
|
- Election::refund_voting_stakes(&votes, &new_council);
|
|
|
-
|
|
|
- assert_eq!(Balances::free_balance(&10), 1100);
|
|
|
- assert_eq!(Balances::reserved_balance(&10), 4900);
|
|
|
- assert_eq!(Balances::free_balance(&20), 2200);
|
|
|
- assert_eq!(Balances::reserved_balance(&20), 4800);
|
|
|
- assert_eq!(Balances::free_balance(&30), 3300);
|
|
|
- assert_eq!(Balances::reserved_balance(&30), 4700);
|
|
|
-
|
|
|
- assert_eq!(Election::transferable_stakes(10).backing, 120);
|
|
|
- assert_eq!(Election::transferable_stakes(20).backing, 240);
|
|
|
- assert_eq!(Election::transferable_stakes(30).backing, 360);
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn unlock_transferable_stakes_should_work() {
|
|
|
- initial_test_ext().execute_with(|| {
|
|
|
- <ExistingStakeHolders<Test>>::put(vec![10, 20, 30]);
|
|
|
-
|
|
|
- let _ = Balances::deposit_creating(&10, 6000);
|
|
|
- let _ = Balances::reserve(&10, 5000);
|
|
|
- save_transferable_stake(
|
|
|
- 10,
|
|
|
- TransferableStake {
|
|
|
- seat: 50,
|
|
|
- backing: 100,
|
|
|
- },
|
|
|
- );
|
|
|
-
|
|
|
- let _ = Balances::deposit_creating(&20, 7000);
|
|
|
- let _ = Balances::reserve(&20, 5000);
|
|
|
- save_transferable_stake(
|
|
|
- 20,
|
|
|
- TransferableStake {
|
|
|
- seat: 60,
|
|
|
- backing: 200,
|
|
|
- },
|
|
|
- );
|
|
|
-
|
|
|
- let _ = Balances::deposit_creating(&30, 8000);
|
|
|
- let _ = Balances::reserve(&30, 5000);
|
|
|
- save_transferable_stake(
|
|
|
- 30,
|
|
|
- TransferableStake {
|
|
|
- seat: 70,
|
|
|
- backing: 300,
|
|
|
- },
|
|
|
- );
|
|
|
-
|
|
|
- Election::unlock_transferable_stakes();
|
|
|
-
|
|
|
- assert_eq!(Balances::free_balance(&10), 1150);
|
|
|
- assert_eq!(Balances::free_balance(&20), 2260);
|
|
|
- assert_eq!(Balances::free_balance(&30), 3370);
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn council_elected_hook_should_work() {
|
|
|
- initial_test_ext().execute_with(|| {
|
|
|
- let mut new_council: BTreeMap<u64, Seat<u64, u64>> = BTreeMap::new();
|
|
|
- new_council.insert(
|
|
|
- 200 as u64,
|
|
|
- Seat {
|
|
|
- member: 200 as u64,
|
|
|
- stake: 10 as u64,
|
|
|
- backers: vec![],
|
|
|
- },
|
|
|
- );
|
|
|
- new_council.insert(
|
|
|
- 300 as u64,
|
|
|
- Seat {
|
|
|
- member: 300 as u64,
|
|
|
- stake: 20 as u64,
|
|
|
- backers: vec![],
|
|
|
- },
|
|
|
- );
|
|
|
-
|
|
|
- assert_eq!(Council::active_council().len(), 0);
|
|
|
-
|
|
|
- let new_council = new_council
|
|
|
- .into_iter()
|
|
|
- .map(|(_, seat)| seat.clone())
|
|
|
- .collect();
|
|
|
- <Test as election::Trait>::CouncilElected::council_elected(new_council, 10);
|
|
|
-
|
|
|
- assert_eq!(Council::active_council().len(), 2);
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn simulation() {
|
|
|
- initial_test_ext().execute_with(|| {
|
|
|
- assert_eq!(Council::active_council().len(), 0);
|
|
|
- assert!(Election::stage().is_none());
|
|
|
-
|
|
|
- CouncilSize::put(10);
|
|
|
- <MinCouncilStake<Test>>::put(50);
|
|
|
- <AnnouncingPeriod<Test>>::put(10);
|
|
|
- <VotingPeriod<Test>>::put(10);
|
|
|
- <RevealingPeriod<Test>>::put(10);
|
|
|
- CandidacyLimit::put(20);
|
|
|
- <NewTermDuration<Test>>::put(100);
|
|
|
- <MinVotingStake<Test>>::put(10);
|
|
|
-
|
|
|
- for i in 1..30 {
|
|
|
- let _ = Balances::deposit_creating(&(i as u64), 50000);
|
|
|
- }
|
|
|
-
|
|
|
- System::set_block_number(1);
|
|
|
- assert_ok!(Election::start_election(vec![]));
|
|
|
-
|
|
|
- for i in 1..20 {
|
|
|
- if i < 21 {
|
|
|
- assert!(Election::apply(Origin::signed(i), 150).is_ok());
|
|
|
- } else {
|
|
|
- assert!(Election::apply(Origin::signed(i + 1000), 150).is_err()); // not enough free balance
|
|
|
- assert!(Election::apply(Origin::signed(i), 20).is_err()); // not enough minimum stake
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- let n = 1 + Election::announcing_period();
|
|
|
- System::set_block_number(n);
|
|
|
- let _ = Election::on_finalize(n);
|
|
|
-
|
|
|
- for i in 1..20 {
|
|
|
- assert!(Election::vote(
|
|
|
- Origin::signed(i),
|
|
|
- make_commitment_for_applicant(i, &mut vec![40u8]),
|
|
|
- 100
|
|
|
- )
|
|
|
- .is_ok());
|
|
|
-
|
|
|
- assert!(Election::vote(
|
|
|
- Origin::signed(i),
|
|
|
- make_commitment_for_applicant(i, &mut vec![41u8]),
|
|
|
- 100
|
|
|
- )
|
|
|
- .is_ok());
|
|
|
-
|
|
|
- assert!(Election::vote(
|
|
|
- Origin::signed(i),
|
|
|
- make_commitment_for_applicant(i + 1000, &mut vec![42u8]),
|
|
|
- 100
|
|
|
- )
|
|
|
- .is_ok());
|
|
|
- }
|
|
|
-
|
|
|
- let n = n + Election::voting_period();
|
|
|
- System::set_block_number(n);
|
|
|
- let _ = Election::on_finalize(n);
|
|
|
-
|
|
|
- for i in 1..20 {
|
|
|
- assert!(Election::reveal(
|
|
|
- Origin::signed(i),
|
|
|
- make_commitment_for_applicant(i, &mut vec![40u8]),
|
|
|
- i,
|
|
|
- vec![40u8]
|
|
|
- )
|
|
|
- .is_ok());
|
|
|
- //wrong salt
|
|
|
- assert!(Election::reveal(
|
|
|
- Origin::signed(i),
|
|
|
- make_commitment_for_applicant(i, &mut vec![41u8]),
|
|
|
- i,
|
|
|
- vec![]
|
|
|
- )
|
|
|
- .is_err());
|
|
|
- //vote not for valid applicant
|
|
|
- assert!(Election::reveal(
|
|
|
- Origin::signed(i),
|
|
|
- make_commitment_for_applicant(i + 1000, &mut vec![42u8]),
|
|
|
- i + 1000,
|
|
|
- vec![42u8]
|
|
|
- )
|
|
|
- .is_err());
|
|
|
- }
|
|
|
-
|
|
|
- let n = n + Election::revealing_period();
|
|
|
- System::set_block_number(n);
|
|
|
- let _ = Election::on_finalize(n);
|
|
|
-
|
|
|
- assert_eq!(
|
|
|
- Council::active_council().len(),
|
|
|
- Election::council_size_usize()
|
|
|
- );
|
|
|
- for (i, seat) in Council::active_council().iter().enumerate() {
|
|
|
- assert_eq!(seat.member, (i + 1) as u64);
|
|
|
- }
|
|
|
- assert!(Election::stage().is_none());
|
|
|
-
|
|
|
- // When council term ends.. start a new election.
|
|
|
- assert_ok!(Election::start_election(vec![]));
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn setting_election_parameters() {
|
|
|
- initial_test_ext().execute_with(|| {
|
|
|
- let default_parameters: ElectionParameters<u64, u64> = ElectionParameters::default();
|
|
|
- // default all zeros is invalid
|
|
|
- assert!(default_parameters.ensure_valid().is_err());
|
|
|
-
|
|
|
- let new_parameters = ElectionParameters {
|
|
|
- announcing_period: 1,
|
|
|
- voting_period: 2,
|
|
|
- revealing_period: 3,
|
|
|
- council_size: 4,
|
|
|
- candidacy_limit: 5,
|
|
|
- min_voting_stake: 6,
|
|
|
- min_council_stake: 7,
|
|
|
- new_term_duration: 8,
|
|
|
- };
|
|
|
-
|
|
|
- assert_ok!(Election::set_election_parameters(
|
|
|
- RawOrigin::Root.into(),
|
|
|
- new_parameters
|
|
|
- ));
|
|
|
-
|
|
|
- assert_eq!(
|
|
|
- <AnnouncingPeriod<Test>>::get(),
|
|
|
- new_parameters.announcing_period
|
|
|
- );
|
|
|
- assert_eq!(<VotingPeriod<Test>>::get(), new_parameters.voting_period);
|
|
|
- assert_eq!(
|
|
|
- <RevealingPeriod<Test>>::get(),
|
|
|
- new_parameters.revealing_period
|
|
|
- );
|
|
|
- assert_eq!(
|
|
|
- <MinCouncilStake<Test>>::get(),
|
|
|
- new_parameters.min_council_stake
|
|
|
- );
|
|
|
- assert_eq!(
|
|
|
- <NewTermDuration<Test>>::get(),
|
|
|
- new_parameters.new_term_duration
|
|
|
- );
|
|
|
- assert_eq!(CouncilSize::get(), new_parameters.council_size);
|
|
|
- assert_eq!(CandidacyLimit::get(), new_parameters.candidacy_limit);
|
|
|
- assert_eq!(
|
|
|
- <MinVotingStake<Test>>::get(),
|
|
|
- new_parameters.min_voting_stake
|
|
|
- );
|
|
|
- });
|
|
|
- }
|
|
|
-}
|