#![cfg_attr(not(feature = "std"), no_std)]
use sp_std::prelude::*;
use sp_runtime::{
print, DispatchResult, DispatchError, Perbill, traits::{Zero, StaticLookup, Convert},
};
use frame_support::{
decl_storage, decl_event, ensure, decl_module, decl_error,
weights::{Weight, DispatchClass},
storage::{StorageMap, IterableStorageMap},
traits::{
Currency, Get, LockableCurrency, LockIdentifier, ReservableCurrency, WithdrawReasons,
ChangeMembers, OnUnbalanced, WithdrawReason, Contains, BalanceStatus, InitializeMembers,
ContainsLengthBound,
}
};
use sp_phragmen::{build_support_map, ExtendedBalance, VoteWeight, PhragmenResult};
use frame_system::{self as system, ensure_signed, ensure_root};
pub const MAXIMUM_VOTE: usize = 16;
type BalanceOf<T> = <<T as Trait>::Currency as Currency<<T as frame_system::Trait>::AccountId>>::Balance;
type NegativeImbalanceOf<T> =
<<T as Trait>::Currency as Currency<<T as frame_system::Trait>::AccountId>>::NegativeImbalance;
pub trait Trait: frame_system::Trait {
type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;
type ModuleId: Get<LockIdentifier>;
type Currency:
LockableCurrency<Self::AccountId, Moment=Self::BlockNumber> +
ReservableCurrency<Self::AccountId>;
type ChangeMembers: ChangeMembers<Self::AccountId>;
type InitializeMembers: InitializeMembers<Self::AccountId>;
type CurrencyToVote: Convert<BalanceOf<Self>, VoteWeight> + Convert<ExtendedBalance, BalanceOf<Self>>;
type CandidacyBond: Get<BalanceOf<Self>>;
type VotingBond: Get<BalanceOf<Self>>;
type LoserCandidate: OnUnbalanced<NegativeImbalanceOf<Self>>;
type BadReport: OnUnbalanced<NegativeImbalanceOf<Self>>;
type KickedMember: OnUnbalanced<NegativeImbalanceOf<Self>>;
type DesiredMembers: Get<u32>;
type DesiredRunnersUp: Get<u32>;
type TermDuration: Get<Self::BlockNumber>;
}
decl_storage! {
trait Store for Module<T: Trait> as PhragmenElection {
pub Members get(fn members): Vec<(T::AccountId, BalanceOf<T>)>;
pub RunnersUp get(fn runners_up): Vec<(T::AccountId, BalanceOf<T>)>;
pub ElectionRounds get(fn election_rounds): u32 = Zero::zero();
pub Voting get(fn voting): map hasher(twox_64_concat) T::AccountId => (BalanceOf<T>, Vec<T::AccountId>);
pub Candidates get(fn candidates): Vec<T::AccountId>;
} add_extra_genesis {
config(members): Vec<(T::AccountId, BalanceOf<T>)>;
build(|config: &GenesisConfig<T>| {
let members = config.members.iter().map(|(ref member, ref stake)| {
assert!(
T::Currency::free_balance(member) >= *stake,
"Genesis member does not have enough stake",
);
T::Currency::reserve(&member, T::CandidacyBond::get())
.expect("Genesis member does not have enough balance to be a candidate");
Members::<T>::mutate(|members| {
match members.binary_search_by(|(a, _b)| a.cmp(member)) {
Ok(_) => panic!("Duplicate member in elections phragmen genesis: {}", member),
Err(pos) => members.insert(pos, (member.clone(), *stake)),
}
});
<Module<T>>::vote(
T::Origin::from(Some(member.clone()).into()),
vec![member.clone()],
*stake,
).expect("Genesis member could not vote.");
member.clone()
}).collect::<Vec<T::AccountId>>();
T::InitializeMembers::initialize_members(&members);
})
}
}
decl_error! {
pub enum Error for Module<T: Trait> {
UnableToVote,
NoVotes,
TooManyVotes,
MaximumVotesExceeded,
LowBalance,
UnableToPayBond,
MustBeVoter,
ReportSelf,
DuplicatedCandidate,
MemberSubmit,
RunnerSubmit,
InsufficientCandidateFunds,
InvalidOrigin,
NotMember,
}
}
decl_module! {
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
type Error = Error<T>;
fn deposit_event() = default;
const CandidacyBond: BalanceOf<T> = T::CandidacyBond::get();
const VotingBond: BalanceOf<T> = T::VotingBond::get();
const DesiredMembers: u32 = T::DesiredMembers::get();
const DesiredRunnersUp: u32 = T::DesiredRunnersUp::get();
const TermDuration: T::BlockNumber = T::TermDuration::get();
const ModuleId: LockIdentifier = T::ModuleId::get();
#[weight = 100_000_000]
fn vote(origin, votes: Vec<T::AccountId>, #[compact] value: BalanceOf<T>) {
let who = ensure_signed(origin)?;
let candidates_count = <Candidates<T>>::decode_len().unwrap_or(0) as usize;
let members_count = <Members<T>>::decode_len().unwrap_or(0) as usize;
let runners_up_count = <RunnersUp<T>>::decode_len().unwrap_or(0) as usize;
let allowed_votes = candidates_count + members_count + runners_up_count;
ensure!(!allowed_votes.is_zero(), Error::<T>::UnableToVote);
ensure!(votes.len() <= allowed_votes, Error::<T>::TooManyVotes);
ensure!(votes.len() <= MAXIMUM_VOTE, Error::<T>::MaximumVotesExceeded);
ensure!(!votes.is_empty(), Error::<T>::NoVotes);
ensure!(
value > T::Currency::minimum_balance(),
Error::<T>::LowBalance,
);
if !Self::is_voter(&who) {
T::Currency::reserve(&who, T::VotingBond::get())
.map_err(|_| Error::<T>::UnableToPayBond)?;
}
let locked_balance = value.min(T::Currency::total_balance(&who));
T::Currency::set_lock(
T::ModuleId::get(),
&who,
locked_balance,
WithdrawReasons::except(WithdrawReason::TransactionPayment),
);
Voting::<T>::insert(&who, (locked_balance, votes));
}
#[weight = 0]
fn remove_voter(origin) {
let who = ensure_signed(origin)?;
ensure!(Self::is_voter(&who), Error::<T>::MustBeVoter);
Self::do_remove_voter(&who, true);
}
#[weight = 1_000_000_000]
fn report_defunct_voter(origin, target: <T::Lookup as StaticLookup>::Source) {
let reporter = ensure_signed(origin)?;
let target = T::Lookup::lookup(target)?;
ensure!(reporter != target, Error::<T>::ReportSelf);
ensure!(Self::is_voter(&reporter), Error::<T>::MustBeVoter);
let valid = Self::is_defunct_voter(&target);
if valid {
T::Currency::repatriate_reserved(&target, &reporter, T::VotingBond::get(), BalanceStatus::Free)?;
Self::do_remove_voter(&target, false);
} else {
let imbalance = T::Currency::slash_reserved(&reporter, T::VotingBond::get()).0;
T::BadReport::on_unbalanced(imbalance);
Self::do_remove_voter(&reporter, false);
}
Self::deposit_event(RawEvent::VoterReported(target, reporter, valid));
}
#[weight = 500_000_000]
fn submit_candidacy(origin) {
let who = ensure_signed(origin)?;
let is_candidate = Self::is_candidate(&who);
ensure!(is_candidate.is_err(), Error::<T>::DuplicatedCandidate);
let index = is_candidate.unwrap_err();
ensure!(!Self::is_member(&who), Error::<T>::MemberSubmit);
ensure!(!Self::is_runner(&who), Error::<T>::RunnerSubmit);
T::Currency::reserve(&who, T::CandidacyBond::get())
.map_err(|_| Error::<T>::InsufficientCandidateFunds)?;
<Candidates<T>>::mutate(|c| c.insert(index, who));
}
#[weight = (2_000_000_000, DispatchClass::Operational)]
fn renounce_candidacy(origin) {
let who = ensure_signed(origin)?;
if let Ok(_replacement) = Self::remove_and_replace_member(&who) {
T::Currency::unreserve(&who, T::CandidacyBond::get());
Self::deposit_event(RawEvent::MemberRenounced(who.clone()));
return Ok(());
}
let mut runners_up_with_stake = Self::runners_up();
if let Some(index) = runners_up_with_stake.iter()
.position(|(ref r, ref _s)| r == &who)
{
runners_up_with_stake.remove(index);
T::Currency::unreserve(&who, T::CandidacyBond::get());
<RunnersUp<T>>::put(runners_up_with_stake);
return Ok(());
}
let mut candidates = Self::candidates();
if let Ok(index) = candidates.binary_search(&who) {
candidates.remove(index);
T::Currency::unreserve(&who, T::CandidacyBond::get());
<Candidates<T>>::put(candidates);
return Ok(());
}
Err(Error::<T>::InvalidOrigin)?
}
#[weight = (2_000_000_000, DispatchClass::Operational)]
fn remove_member(origin, who: <T::Lookup as StaticLookup>::Source) -> DispatchResult {
ensure_root(origin)?;
let who = T::Lookup::lookup(who)?;
Self::remove_and_replace_member(&who).map(|had_replacement| {
let (imbalance, _) = T::Currency::slash_reserved(&who, T::CandidacyBond::get());
T::KickedMember::on_unbalanced(imbalance);
Self::deposit_event(RawEvent::MemberKicked(who.clone()));
if !had_replacement {
Self::do_phragmen();
}
})
}
fn on_initialize(n: T::BlockNumber) -> Weight {
if let Err(e) = Self::end_block(n) {
print("Guru meditation");
print(e);
}
0
}
}
}
decl_event!(
pub enum Event<T> where
Balance = BalanceOf<T>,
<T as frame_system::Trait>::AccountId,
{
NewTerm(Vec<(AccountId, Balance)>),
EmptyTerm,
MemberKicked(AccountId),
MemberRenounced(AccountId),
VoterReported(AccountId, AccountId, bool),
}
);
impl<T: Trait> Module<T> {
fn remove_and_replace_member(who: &T::AccountId) -> Result<bool, DispatchError> {
let mut members_with_stake = Self::members();
if let Ok(index) = members_with_stake.binary_search_by(|(ref m, ref _s)| m.cmp(who)) {
members_with_stake.remove(index);
let next_up = <RunnersUp<T>>::mutate(|runners_up| runners_up.pop());
let maybe_replacement = next_up.and_then(|(replacement, stake)|
members_with_stake.binary_search_by(|(ref m, ref _s)| m.cmp(&replacement))
.err()
.map(|index| {
members_with_stake.insert(index, (replacement.clone(), stake));
replacement
})
);
<Members<T>>::put(&members_with_stake);
let members = members_with_stake.into_iter().map(|m| m.0).collect::<Vec<_>>();
let result = Ok(maybe_replacement.is_some());
let old = [who.clone()];
match maybe_replacement {
Some(new) => T::ChangeMembers::change_members_sorted(&[new], &old, &members),
None => T::ChangeMembers::change_members_sorted(&[], &old, &members),
}
result
} else {
Err(Error::<T>::NotMember)?
}
}
fn is_candidate(who: &T::AccountId) -> Result<(), usize> {
Self::candidates().binary_search(who).map(|_| ())
}
fn is_voter(who: &T::AccountId) -> bool {
Voting::<T>::contains_key(who)
}
fn is_member(who: &T::AccountId) -> bool {
Self::members().binary_search_by(|(a, _b)| a.cmp(who)).is_ok()
}
fn is_runner(who: &T::AccountId) -> bool {
Self::runners_up().iter().position(|(a, _b)| a == who).is_some()
}
fn desired_members() -> u32 {
T::DesiredMembers::get()
}
fn desired_runners_up() -> u32 {
T::DesiredRunnersUp::get()
}
fn term_duration() -> T::BlockNumber {
T::TermDuration::get()
}
fn members_ids() -> Vec<T::AccountId> {
Self::members().into_iter().map(|(m, _)| m).collect::<Vec<T::AccountId>>()
}
fn runners_up_ids() -> Vec<T::AccountId> {
Self::runners_up().into_iter().map(|(r, _)| r).collect::<Vec<T::AccountId>>()
}
fn is_defunct_voter(who: &T::AccountId) -> bool {
if Self::is_voter(who) {
Self::votes_of(who)
.iter()
.all(|v| !Self::is_member(v) && !Self::is_runner(v) && !Self::is_candidate(v).is_ok())
} else {
false
}
}
fn do_remove_voter(who: &T::AccountId, unreserve: bool) {
Voting::<T>::remove(who);
T::Currency::remove_lock(T::ModuleId::get(), who);
if unreserve {
T::Currency::unreserve(who, T::VotingBond::get());
}
}
fn locked_stake_of(who: &T::AccountId) -> BalanceOf<T> {
Voting::<T>::get(who).0
}
fn votes_of(who: &T::AccountId) -> Vec<T::AccountId> {
Voting::<T>::get(who).1
}
fn end_block(block_number: T::BlockNumber) -> DispatchResult {
if !Self::term_duration().is_zero() {
if (block_number % Self::term_duration()).is_zero() {
Self::do_phragmen();
}
}
Ok(())
}
fn do_phragmen() {
let desired_seats = Self::desired_members() as usize;
let desired_runners_up = Self::desired_runners_up() as usize;
let num_to_elect = desired_runners_up + desired_seats;
let mut candidates = Self::candidates();
let exposed_candidates = candidates.clone();
candidates.append(&mut Self::members_ids());
candidates.append(&mut Self::runners_up_ids());
let to_votes = |b: BalanceOf<T>| -> VoteWeight {
<T::CurrencyToVote as Convert<BalanceOf<T>, VoteWeight>>::convert(b)
};
let to_balance = |e: ExtendedBalance| -> BalanceOf<T> {
<T::CurrencyToVote as Convert<ExtendedBalance, BalanceOf<T>>>::convert(e)
};
let stake_of = |who: &T::AccountId| -> VoteWeight {
to_votes(Self::locked_stake_of(who))
};
let voters_and_votes = Voting::<T>::iter()
.map(|(voter, (stake, targets))| { (voter, to_votes(stake), targets) })
.collect::<Vec<_>>();
let maybe_phragmen_result = sp_phragmen::elect::<T::AccountId, Perbill>(
num_to_elect,
0,
candidates,
voters_and_votes.clone(),
);
if let Some(PhragmenResult { winners, assignments }) = maybe_phragmen_result {
let old_members_ids = <Members<T>>::take().into_iter()
.map(|(m, _)| m)
.collect::<Vec<T::AccountId>>();
let old_runners_up_ids = <RunnersUp<T>>::take().into_iter()
.map(|(r, _)| r)
.collect::<Vec<T::AccountId>>();
let new_set_with_approval = winners;
let new_set = new_set_with_approval
.into_iter()
.filter_map(|(m, a)| if a.is_zero() { None } else { Some(m) } )
.collect::<Vec<T::AccountId>>();
let staked_assignments = sp_phragmen::assignment_ratio_to_staked(
assignments,
stake_of,
);
let (support_map, _) = build_support_map::<T::AccountId>(&new_set, &staked_assignments);
let new_set_with_stake = new_set
.into_iter()
.map(|ref m| {
let support = support_map.get(m)
.expect(
"entire new_set was given to build_support_map; en entry must be \
created for each item; qed"
);
(m.clone(), to_balance(support.total))
})
.collect::<Vec<(T::AccountId, BalanceOf<T>)>>();
let split_point = desired_seats.min(new_set_with_stake.len());
let mut new_members = (&new_set_with_stake[..split_point]).to_vec();
new_members.sort_by(|i, j| i.0.cmp(&j.0));
let mut prime_votes: Vec<_> = new_members.iter().map(|c| (&c.0, VoteWeight::zero())).collect();
for (_, stake, targets) in voters_and_votes.into_iter() {
for (votes, who) in targets.iter()
.enumerate()
.map(|(votes, who)| ((MAXIMUM_VOTE - votes) as u32, who))
{
if let Ok(i) = prime_votes.binary_search_by_key(&who, |k| k.0) {
prime_votes[i].1 += stake * votes as VoteWeight;
}
}
}
let prime = prime_votes.into_iter().max_by_key(|x| x.1).map(|x| x.0.clone());
let new_members_ids = new_members
.iter()
.map(|(m, _)| m.clone())
.collect::<Vec<T::AccountId>>();
let new_runners_up = &new_set_with_stake[split_point..]
.into_iter()
.cloned()
.rev()
.collect::<Vec<(T::AccountId, BalanceOf<T>)>>();
let new_runners_up_ids = new_runners_up
.iter()
.map(|(r, _)| r.clone())
.collect::<Vec<T::AccountId>>();
let (incoming, outgoing) = T::ChangeMembers::compute_members_diff(
&new_members_ids,
&old_members_ids,
);
T::ChangeMembers::change_members_sorted(
&incoming,
&outgoing.clone(),
&new_members_ids,
);
T::ChangeMembers::set_prime(prime);
let mut to_burn_bond = outgoing.to_vec();
{
let (_, outgoing) = T::ChangeMembers::compute_members_diff(
&new_runners_up_ids,
&old_runners_up_ids,
);
to_burn_bond.extend(outgoing);
}
exposed_candidates.into_iter().for_each(|c| {
if new_members.binary_search_by_key(&c, |(m, _)| m.clone()).is_err()
&& !new_runners_up_ids.contains(&c)
{
let (imbalance, _) = T::Currency::slash_reserved(&c, T::CandidacyBond::get());
T::LoserCandidate::on_unbalanced(imbalance);
}
});
to_burn_bond.into_iter().for_each(|x| {
let (imbalance, _) = T::Currency::slash_reserved(&x, T::CandidacyBond::get());
T::LoserCandidate::on_unbalanced(imbalance);
});
<Members<T>>::put(&new_members);
<RunnersUp<T>>::put(new_runners_up);
Self::deposit_event(RawEvent::NewTerm(new_members.clone().to_vec()));
} else {
Self::deposit_event(RawEvent::EmptyTerm);
}
<Candidates<T>>::kill();
ElectionRounds::mutate(|v| *v += 1);
}
}
impl<T: Trait> Contains<T::AccountId> for Module<T> {
fn contains(who: &T::AccountId) -> bool {
Self::is_member(who)
}
fn sorted_members() -> Vec<T::AccountId> { Self::members_ids() }
#[cfg(feature = "runtime-benchmarks")]
fn add(who: &T::AccountId) {
Members::<T>::mutate(|members| {
match members.binary_search_by(|(a, _b)| a.cmp(who)) {
Ok(_) => (),
Err(pos) => members.insert(pos, (who.clone(), BalanceOf::<T>::default())),
}
})
}
}
impl<T: Trait> ContainsLengthBound for Module<T> {
fn min_len() -> usize { 0 }
fn max_len() -> usize {
Self::desired_members() as usize
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::cell::RefCell;
use frame_support::{assert_ok, assert_noop, parameter_types, weights::Weight};
use substrate_test_utils::assert_eq_uvec;
use sp_core::H256;
use sp_runtime::{
Perbill, testing::Header, BuildStorage,
traits::{BlakeTwo256, IdentityLookup, Block as BlockT},
};
use crate as elections_phragmen;
use frame_system as system;
parameter_types! {
pub const BlockHashCount: u64 = 250;
pub const MaximumBlockWeight: Weight = 1024;
pub const MaximumBlockLength: u32 = 2 * 1024;
pub const AvailableBlockRatio: Perbill = Perbill::one();
}
impl frame_system::Trait for Test {
type Origin = Origin;
type Index = u64;
type BlockNumber = u64;
type Call = ();
type Hash = H256;
type Hashing = BlakeTwo256;
type AccountId = u64;
type Lookup = IdentityLookup<Self::AccountId>;
type Header = Header;
type Event = Event;
type BlockHashCount = BlockHashCount;
type MaximumBlockWeight = MaximumBlockWeight;
type DbWeight = ();
type BlockExecutionWeight = ();
type ExtrinsicBaseWeight = ();
type MaximumBlockLength = MaximumBlockLength;
type AvailableBlockRatio = AvailableBlockRatio;
type Version = ();
type ModuleToIndex = ();
type AccountData = pallet_balances::AccountData<u64>;
type OnNewAccount = ();
type OnKilledAccount = ();
}
parameter_types! {
pub const ExistentialDeposit: u64 = 1;
}
impl pallet_balances::Trait for Test {
type Balance = u64;
type Event = Event;
type DustRemoval = ();
type ExistentialDeposit = ExistentialDeposit;
type AccountStore = frame_system::Module<Test>;
}
parameter_types! {
pub const CandidacyBond: u64 = 3;
}
thread_local! {
static VOTING_BOND: RefCell<u64> = RefCell::new(2);
static DESIRED_MEMBERS: RefCell<u32> = RefCell::new(2);
static DESIRED_RUNNERS_UP: RefCell<u32> = RefCell::new(2);
static TERM_DURATION: RefCell<u64> = RefCell::new(5);
}
pub struct VotingBond;
impl Get<u64> for VotingBond {
fn get() -> u64 { VOTING_BOND.with(|v| *v.borrow()) }
}
pub struct DesiredMembers;
impl Get<u32> for DesiredMembers {
fn get() -> u32 { DESIRED_MEMBERS.with(|v| *v.borrow()) }
}
pub struct DesiredRunnersUp;
impl Get<u32> for DesiredRunnersUp {
fn get() -> u32 { DESIRED_RUNNERS_UP.with(|v| *v.borrow()) }
}
pub struct TermDuration;
impl Get<u64> for TermDuration {
fn get() -> u64 { TERM_DURATION.with(|v| *v.borrow()) }
}
thread_local! {
pub static MEMBERS: RefCell<Vec<u64>> = RefCell::new(vec![]);
pub static PRIME: RefCell<Option<u64>> = RefCell::new(None);
}
pub struct TestChangeMembers;
impl ChangeMembers<u64> for TestChangeMembers {
fn change_members_sorted(incoming: &[u64], outgoing: &[u64], new: &[u64]) {
let mut new_sorted = new.to_vec();
new_sorted.sort();
assert_eq!(new, &new_sorted[..]);
let mut incoming_sorted = incoming.to_vec();
incoming_sorted.sort();
assert_eq!(incoming, &incoming_sorted[..]);
let mut outgoing_sorted = outgoing.to_vec();
outgoing_sorted.sort();
assert_eq!(outgoing, &outgoing_sorted[..]);
for x in incoming.iter() {
assert!(outgoing.binary_search(x).is_err());
}
let mut old_plus_incoming = MEMBERS.with(|m| m.borrow().to_vec());
old_plus_incoming.extend_from_slice(incoming);
old_plus_incoming.sort();
let mut new_plus_outgoing = new.to_vec();
new_plus_outgoing.extend_from_slice(outgoing);
new_plus_outgoing.sort();
assert_eq!(old_plus_incoming, new_plus_outgoing);
MEMBERS.with(|m| *m.borrow_mut() = new.to_vec());
PRIME.with(|p| *p.borrow_mut() = None);
}
fn set_prime(who: Option<u64>) {
PRIME.with(|p| *p.borrow_mut() = who);
}
}
pub struct CurrencyToVoteHandler;
impl Convert<u64, u64> for CurrencyToVoteHandler {
fn convert(x: u64) -> u64 { x }
}
impl Convert<u128, u64> for CurrencyToVoteHandler {
fn convert(x: u128) -> u64 {
x as u64
}
}
parameter_types!{
pub const ElectionsPhragmenModuleId: LockIdentifier = *b"phrelect";
}
impl Trait for Test {
type ModuleId = ElectionsPhragmenModuleId;
type Event = Event;
type Currency = Balances;
type CurrencyToVote = CurrencyToVoteHandler;
type ChangeMembers = TestChangeMembers;
type InitializeMembers = ();
type CandidacyBond = CandidacyBond;
type VotingBond = VotingBond;
type TermDuration = TermDuration;
type DesiredMembers = DesiredMembers;
type DesiredRunnersUp = DesiredRunnersUp;
type LoserCandidate = ();
type KickedMember = ();
type BadReport = ();
}
pub type Block = sp_runtime::generic::Block<Header, UncheckedExtrinsic>;
pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic<u32, u64, Call, ()>;
frame_support::construct_runtime!(
pub enum Test where
Block = Block,
NodeBlock = Block,
UncheckedExtrinsic = UncheckedExtrinsic
{
System: system::{Module, Call, Event<T>},
Balances: pallet_balances::{Module, Call, Event<T>, Config<T>},
Elections: elections_phragmen::{Module, Call, Event<T>, Config<T>},
}
);
pub struct ExtBuilder {
genesis_members: Vec<(u64, u64)>,
balance_factor: u64,
voter_bond: u64,
term_duration: u64,
desired_runners_up: u32,
}
impl Default for ExtBuilder {
fn default() -> Self {
Self {
genesis_members: vec![],
balance_factor: 1,
voter_bond: 2,
desired_runners_up: 0,
term_duration: 5,
}
}
}
impl ExtBuilder {
pub fn voter_bond(mut self, fee: u64) -> Self {
self.voter_bond = fee;
self
}
pub fn desired_runners_up(mut self, count: u32) -> Self {
self.desired_runners_up = count;
self
}
pub fn term_duration(mut self, duration: u64) -> Self {
self.term_duration = duration;
self
}
pub fn genesis_members(mut self, members: Vec<(u64, u64)>) -> Self {
self.genesis_members = members;
self
}
pub fn balance_factor(mut self, factor: u64) -> Self {
self.balance_factor = factor;
self
}
pub fn build_and_execute(self, test: impl FnOnce() -> ()) {
VOTING_BOND.with(|v| *v.borrow_mut() = self.voter_bond);
TERM_DURATION.with(|v| *v.borrow_mut() = self.term_duration);
DESIRED_RUNNERS_UP.with(|v| *v.borrow_mut() = self.desired_runners_up);
MEMBERS.with(|m| *m.borrow_mut() = self.genesis_members.iter().map(|(m, _)| m.clone()).collect::<Vec<_>>());
let mut ext: sp_io::TestExternalities = GenesisConfig {
pallet_balances: Some(pallet_balances::GenesisConfig::<Test>{
balances: vec![
(1, 10 * self.balance_factor),
(2, 20 * self.balance_factor),
(3, 30 * self.balance_factor),
(4, 40 * self.balance_factor),
(5, 50 * self.balance_factor),
(6, 60 * self.balance_factor)
],
}),
elections_phragmen: Some(elections_phragmen::GenesisConfig::<Test> {
members: self.genesis_members
}),
}.build_storage().unwrap().into();
ext.execute_with(pre_conditions);
ext.execute_with(test);
ext.execute_with(post_conditions)
}
}
fn all_voters() -> Vec<u64> {
Voting::<Test>::iter().map(|(v, _)| v).collect::<Vec<u64>>()
}
fn balances(who: &u64) -> (u64, u64) {
(Balances::free_balance(who), Balances::reserved_balance(who))
}
fn has_lock(who: &u64) -> u64 {
let lock = Balances::locks(who)[0].clone();
assert_eq!(lock.id, ElectionsPhragmenModuleId::get());
lock.amount
}
fn intersects<T: PartialEq>(a: &[T], b: &[T]) -> bool {
a.iter().any(|e| b.contains(e))
}
fn ensure_members_sorted() {
let mut members = Elections::members().clone();
members.sort();
assert_eq!(Elections::members(), members);
}
fn ensure_candidates_sorted() {
let mut candidates = Elections::candidates().clone();
candidates.sort();
assert_eq!(Elections::candidates(), candidates);
}
fn ensure_members_has_approval_stake() {
assert!(
Elections::members().iter().chain(
Elections::runners_up().iter()
).all(|(_, s)| *s != Zero::zero())
);
}
fn ensure_member_candidates_runners_up_disjoint() {
assert!(!intersects(&Elections::members_ids(), &Elections::candidates()));
assert!(!intersects(&Elections::members_ids(), &Elections::runners_up_ids()));
assert!(!intersects(&Elections::candidates(), &Elections::runners_up_ids()));
}
fn pre_conditions() {
System::set_block_number(1);
ensure_members_sorted();
ensure_candidates_sorted();
}
fn post_conditions() {
ensure_members_sorted();
ensure_candidates_sorted();
ensure_member_candidates_runners_up_disjoint();
ensure_members_has_approval_stake();
}
#[test]
fn params_should_work() {
ExtBuilder::default().build_and_execute(|| {
assert_eq!(Elections::desired_members(), 2);
assert_eq!(Elections::term_duration(), 5);
assert_eq!(Elections::election_rounds(), 0);
assert_eq!(Elections::members(), vec![]);
assert_eq!(Elections::runners_up(), vec![]);
assert_eq!(Elections::candidates(), vec![]);
assert_eq!(<Candidates<Test>>::decode_len().unwrap(), 0);
assert!(Elections::is_candidate(&1).is_err());
assert_eq!(all_voters(), vec![]);
assert_eq!(Elections::votes_of(&1), vec![]);
});
}
#[test]
fn genesis_members_should_work() {
ExtBuilder::default().genesis_members(vec![(1, 10), (2, 20)]).build_and_execute(|| {
System::set_block_number(1);
assert_eq!(Elections::members(), vec![(1, 10), (2, 20)]);
assert_eq!(Elections::voting(1), (10, vec![1]));
assert_eq!(Elections::voting(2), (20, vec![2]));
System::set_block_number(5);
assert_ok!(Elections::end_block(System::block_number()));
assert_eq!(Elections::members_ids(), vec![1, 2]);
})
}
#[test]
fn genesis_members_unsorted_should_work() {
ExtBuilder::default().genesis_members(vec![(2, 20), (1, 10)]).build_and_execute(|| {
System::set_block_number(1);
assert_eq!(Elections::members(), vec![(1, 10), (2, 20)]);
assert_eq!(Elections::voting(1), (10, vec![1]));
assert_eq!(Elections::voting(2), (20, vec![2]));
System::set_block_number(5);
assert_ok!(Elections::end_block(System::block_number()));
assert_eq!(Elections::members_ids(), vec![1, 2]);
})
}
#[test]
#[should_panic = "Genesis member does not have enough stake"]
fn genesis_members_cannot_over_stake_0() {
ExtBuilder::default()
.genesis_members(vec![(1, 20), (2, 20)])
.build_and_execute(|| {});
}
#[test]
#[should_panic]
fn genesis_members_cannot_over_stake_1() {
ExtBuilder::default()
.voter_bond(20)
.genesis_members(vec![(1, 10), (2, 20)])
.build_and_execute(|| {});
}
#[test]
#[should_panic = "Duplicate member in elections phragmen genesis: 2"]
fn genesis_members_cannot_be_duplicate() {
ExtBuilder::default()
.genesis_members(vec![(1, 10), (2, 10), (2, 10)])
.build_and_execute(|| {});
}
#[test]
fn term_duration_zero_is_passive() {
ExtBuilder::default()
.term_duration(0)
.build_and_execute(||
{
assert_eq!(Elections::term_duration(), 0);
assert_eq!(Elections::desired_members(), 2);
assert_eq!(Elections::election_rounds(), 0);
assert_eq!(Elections::members_ids(), vec![]);
assert_eq!(Elections::runners_up(), vec![]);
assert_eq!(Elections::candidates(), vec![]);
System::set_block_number(5);
assert_ok!(Elections::end_block(System::block_number()));
assert_eq!(Elections::members_ids(), vec![]);
assert_eq!(Elections::runners_up(), vec![]);
assert_eq!(Elections::candidates(), vec![]);
});
}
#[test]
fn simple_candidate_submission_should_work() {
ExtBuilder::default().build_and_execute(|| {
assert_eq!(Elections::candidates(), Vec::<u64>::new());
assert!(Elections::is_candidate(&1).is_err());
assert!(Elections::is_candidate(&2).is_err());
assert_eq!(balances(&1), (10, 0));
assert_ok!(Elections::submit_candidacy(Origin::signed(1)));
assert_eq!(balances(&1), (7, 3));
assert_eq!(Elections::candidates(), vec![1]);
assert!(Elections::is_candidate(&1).is_ok());
assert!(Elections::is_candidate(&2).is_err());
assert_eq!(balances(&2), (20, 0));
assert_ok!(Elections::submit_candidacy(Origin::signed(2)));
assert_eq!(balances(&2), (17, 3));
assert_eq!(Elections::candidates(), vec![1, 2]);
assert!(Elections::is_candidate(&1).is_ok());
assert!(Elections::is_candidate(&2).is_ok());
});
}
#[test]
fn simple_candidate_submission_with_no_votes_should_work() {
ExtBuilder::default().build_and_execute(|| {
assert_eq!(Elections::candidates(), Vec::<u64>::new());
assert_ok!(Elections::submit_candidacy(Origin::signed(1)));
assert_ok!(Elections::submit_candidacy(Origin::signed(2)));
assert!(Elections::is_candidate(&1).is_ok());
assert!(Elections::is_candidate(&2).is_ok());
assert_eq!(Elections::candidates(), vec![1, 2]);
assert_eq!(Elections::members_ids(), vec![]);
assert_eq!(Elections::runners_up(), vec![]);
System::set_block_number(5);
assert_ok!(Elections::end_block(System::block_number()));
assert!(Elections::is_candidate(&1).is_err());
assert!(Elections::is_candidate(&2).is_err());
assert_eq!(Elections::candidates(), vec![]);
assert_eq!(Elections::members_ids(), vec![]);
assert_eq!(Elections::runners_up(), vec![]);
});
}
#[test]
fn dupe_candidate_submission_should_not_work() {
ExtBuilder::default().build_and_execute(|| {
assert_eq!(Elections::candidates(), Vec::<u64>::new());
assert_ok!(Elections::submit_candidacy(Origin::signed(1)));
assert_eq!(Elections::candidates(), vec![1]);
assert_noop!(
Elections::submit_candidacy(Origin::signed(1)),
Error::<Test>::DuplicatedCandidate,
);
});
}
#[test]
fn member_candidacy_submission_should_not_work() {
ExtBuilder::default().build_and_execute(|| {
assert_ok!(Elections::submit_candidacy(Origin::signed(5)));
assert_ok!(Elections::vote(Origin::signed(2), vec![5], 20));
System::set_block_number(5);
assert_ok!(Elections::end_block(System::block_number()));
assert_eq!(Elections::members_ids(), vec![5]);
assert_eq!(Elections::runners_up(), vec![]);
assert_eq!(Elections::candidates(), vec![]);
assert_noop!(
Elections::submit_candidacy(Origin::signed(5)),
Error::<Test>::MemberSubmit,
);
});
}
#[test]
fn runner_candidate_submission_should_not_work() {
ExtBuilder::default().desired_runners_up(2).build_and_execute(|| {
assert_ok!(Elections::submit_candidacy(Origin::signed(5)));
assert_ok!(Elections::submit_candidacy(Origin::signed(4)));
assert_ok!(Elections::submit_candidacy(Origin::signed(3)));
assert_ok!(Elections::vote(Origin::signed(2), vec![5, 4], 20));
assert_ok!(Elections::vote(Origin::signed(1), vec![3], 10));
System::set_block_number(5);
assert_ok!(Elections::end_block(System::block_number()));
assert_eq!(Elections::members_ids(), vec![4, 5]);
assert_eq!(Elections::runners_up_ids(), vec![3]);
assert_noop!(
Elections::submit_candidacy(Origin::signed(3)),
Error::<Test>::RunnerSubmit,
);
});
}
#[test]
fn poor_candidate_submission_should_not_work() {
ExtBuilder::default().build_and_execute(|| {
assert_eq!(Elections::candidates(), Vec::<u64>::new());
assert_noop!(
Elections::submit_candidacy(Origin::signed(7)),
Error::<Test>::InsufficientCandidateFunds,
);
});
}
#[test]
fn simple_voting_should_work() {
ExtBuilder::default().build_and_execute(|| {
assert_eq!(Elections::candidates(), Vec::<u64>::new());
assert_eq!(balances(&2), (20, 0));
assert_ok!(Elections::submit_candidacy(Origin::signed(5)));
assert_ok!(Elections::vote(Origin::signed(2), vec![5], 20));
assert_eq!(balances(&2), (18, 2));
assert_eq!(has_lock(&2), 20);
});
}
#[test]
fn can_vote_with_custom_stake() {
ExtBuilder::default().build_and_execute(|| {
assert_eq!(Elections::candidates(), Vec::<u64>::new());
assert_eq!(balances(&2), (20, 0));
assert_ok!(Elections::submit_candidacy(Origin::signed(5)));
assert_ok!(Elections::vote(Origin::signed(2), vec![5], 12));
assert_eq!(balances(&2), (18, 2));
assert_eq!(has_lock(&2), 12);
});
}
#[test]
fn can_update_votes_and_stake() {
ExtBuilder::default().build_and_execute(|| {
assert_eq!(balances(&2), (20, 0));
assert_ok!(Elections::submit_candidacy(Origin::signed(5)));
assert_ok!(Elections::submit_candidacy(Origin::signed(4)));
assert_ok!(Elections::vote(Origin::signed(2), vec![5], 20));
assert_eq!(balances(&2), (18, 2));
assert_eq!(has_lock(&2), 20);
assert_eq!(Elections::locked_stake_of(&2), 20);
assert_ok!(Elections::vote(Origin::signed(2), vec![5, 4], 15));
assert_eq!(balances(&2), (18, 2));
assert_eq!(has_lock(&2), 15);
assert_eq!(Elections::locked_stake_of(&2), 15);
});
}
#[test]
fn cannot_vote_for_no_candidate() {
ExtBuilder::default().build_and_execute(|| {
assert_noop!(
Elections::vote(Origin::signed(2), vec![], 20),
Error::<Test>::UnableToVote,
);
});
}
#[test]
fn can_vote_for_old_members_even_when_no_new_candidates() {
ExtBuilder::default().build_and_execute(|| {
assert_ok!(Elections::submit_candidacy(Origin::signed(5)));
assert_ok!(Elections::submit_candidacy(Origin::signed(4)));
assert_ok!(Elections::vote(Origin::signed(2), vec![4, 5], 20));
System::set_block_number(5);
assert_ok!(Elections::end_block(System::block_number()));
assert_eq!(Elections::members_ids(), vec![4, 5]);
assert_eq!(Elections::candidates(), vec![]);
assert_ok!(Elections::vote(Origin::signed(3), vec![4, 5], 10));
});
}
#[test]
fn prime_works() {
ExtBuilder::default().build_and_execute(|| {
assert_ok!(Elections::submit_candidacy(Origin::signed(3)));
assert_ok!(Elections::submit_candidacy(Origin::signed(4)));
assert_ok!(Elections::submit_candidacy(Origin::signed(5)));
assert_ok!(Elections::vote(Origin::signed(1), vec![4, 3], 10));
assert_ok!(Elections::vote(Origin::signed(2), vec![4], 20));
assert_ok!(Elections::vote(Origin::signed(3), vec![3], 30));
assert_ok!(Elections::vote(Origin::signed(4), vec![4], 40));
assert_ok!(Elections::vote(Origin::signed(5), vec![5], 50));
System::set_block_number(5);
assert_ok!(Elections::end_block(System::block_number()));
assert_eq!(Elections::members_ids(), vec![4, 5]);
assert_eq!(Elections::candidates(), vec![]);
assert_ok!(Elections::vote(Origin::signed(3), vec![4, 5], 10));
assert_eq!(PRIME.with(|p| *p.borrow()), Some(4));
});
}
#[test]
fn prime_votes_for_exiting_members_are_removed() {
ExtBuilder::default().build_and_execute(|| {
assert_ok!(Elections::submit_candidacy(Origin::signed(3)));
assert_ok!(Elections::submit_candidacy(Origin::signed(4)));
assert_ok!(Elections::submit_candidacy(Origin::signed(5)));
assert_ok!(Elections::vote(Origin::signed(1), vec![4, 3], 10));
assert_ok!(Elections::vote(Origin::signed(2), vec![4], 20));
assert_ok!(Elections::vote(Origin::signed(3), vec![3], 30));
assert_ok!(Elections::vote(Origin::signed(4), vec![4], 40));
assert_ok!(Elections::vote(Origin::signed(5), vec![5], 50));
assert_ok!(Elections::renounce_candidacy(Origin::signed(4)));
System::set_block_number(5);
assert_ok!(Elections::end_block(System::block_number()));
assert_eq!(Elections::members_ids(), vec![3, 5]);
assert_eq!(Elections::candidates(), vec![]);
assert_eq!(PRIME.with(|p| *p.borrow()), Some(5));
});
}
#[test]
fn cannot_vote_for_more_than_candidates_and_members_and_runners() {
ExtBuilder::default()
.desired_runners_up(1)
.balance_factor(10)
.build_and_execute(
|| {
assert_ok!(Elections::submit_candidacy(Origin::signed(5)));
assert_ok!(Elections::submit_candidacy(Origin::signed(4)));
assert_ok!(Elections::submit_candidacy(Origin::signed(3)));
assert_noop!(
Elections::vote(Origin::signed(1), vec![9, 99, 999, 9999], 5),
Error::<Test>::TooManyVotes,
);
assert_ok!(Elections::vote(Origin::signed(3), vec![3], 30));
assert_ok!(Elections::vote(Origin::signed(4), vec![4], 40));
assert_ok!(Elections::vote(Origin::signed(5), vec![5], 50));
System::set_block_number(5);
assert_ok!(Elections::end_block(System::block_number()));
assert_ok!(Elections::submit_candidacy(Origin::signed(2)));
assert_ok!(Elections::vote(Origin::signed(1), vec![9, 99, 999, 9999], 5));
assert_noop!(
Elections::vote(Origin::signed(1), vec![9, 99, 999, 9_999, 99_999], 5),
Error::<Test>::TooManyVotes,
);
});
}
#[test]
fn cannot_vote_for_less_than_ed() {
ExtBuilder::default().build_and_execute(|| {
assert_ok!(Elections::submit_candidacy(Origin::signed(5)));
assert_ok!(Elections::submit_candidacy(Origin::signed(4)));
assert_noop!(
Elections::vote(Origin::signed(2), vec![4], 1),
Error::<Test>::LowBalance,
);
})
}
#[test]
fn can_vote_for_more_than_total_balance_but_moot() {
ExtBuilder::default().build_and_execute(|| {
assert_ok!(Elections::submit_candidacy(Origin::signed(5)));
assert_ok!(Elections::submit_candidacy(Origin::signed(4)));
assert_ok!(Elections::vote(Origin::signed(2), vec![4, 5], 30));
assert_eq!(Elections::locked_stake_of(&2), 20);
assert_eq!(has_lock(&2), 20);
});
}
#[test]
fn remove_voter_should_work() {
ExtBuilder::default().voter_bond(8).build_and_execute(|| {
assert_ok!(Elections::submit_candidacy(Origin::signed(5)));
assert_ok!(Elections::vote(Origin::signed(2), vec![5], 20));
assert_ok!(Elections::vote(Origin::signed(3), vec![5], 30));
assert_eq_uvec!(all_voters(), vec![2, 3]);
assert_eq!(Elections::locked_stake_of(&2), 20);
assert_eq!(Elections::locked_stake_of(&3), 30);
assert_eq!(Elections::votes_of(&2), vec![5]);
assert_eq!(Elections::votes_of(&3), vec![5]);
assert_ok!(Elections::remove_voter(Origin::signed(2)));
assert_eq_uvec!(all_voters(), vec![3]);
assert_eq!(Elections::votes_of(&2), vec![]);
assert_eq!(Elections::locked_stake_of(&2), 0);
assert_eq!(balances(&2), (20, 0));
assert_eq!(Balances::locks(&2).len(), 0);
});
}
#[test]
fn non_voter_remove_should_not_work() {
ExtBuilder::default().build_and_execute(|| {
assert_noop!(Elections::remove_voter(Origin::signed(3)), Error::<Test>::MustBeVoter);
});
}
#[test]
fn dupe_remove_should_fail() {
ExtBuilder::default().build_and_execute(|| {
assert_ok!(Elections::submit_candidacy(Origin::signed(5)));
assert_ok!(Elections::vote(Origin::signed(2), vec![5], 20));
assert_ok!(Elections::remove_voter(Origin::signed(2)));
assert_eq!(all_voters(), vec![]);
assert_noop!(Elections::remove_voter(Origin::signed(2)), Error::<Test>::MustBeVoter);
});
}
#[test]
fn removed_voter_should_not_be_counted() {
ExtBuilder::default().build_and_execute(|| {
assert_ok!(Elections::submit_candidacy(Origin::signed(5)));
assert_ok!(Elections::submit_candidacy(Origin::signed(4)));
assert_ok!(Elections::submit_candidacy(Origin::signed(3)));
assert_ok!(Elections::vote(Origin::signed(5), vec![5], 50));
assert_ok!(Elections::vote(Origin::signed(4), vec![4], 40));
assert_ok!(Elections::vote(Origin::signed(3), vec![3], 30));
assert_ok!(Elections::remove_voter(Origin::signed(4)));
System::set_block_number(5);
assert_ok!(Elections::end_block(System::block_number()));
assert_eq!(Elections::members_ids(), vec![3, 5]);
});
}
#[test]
fn reporter_must_be_voter() {
ExtBuilder::default().build_and_execute(|| {
assert_noop!(
Elections::report_defunct_voter(Origin::signed(1), 2),
Error::<Test>::MustBeVoter,
);
});
}
#[test]
fn can_detect_defunct_voter() {
ExtBuilder::default().desired_runners_up(2).build_and_execute(|| {
assert_ok!(Elections::submit_candidacy(Origin::signed(4)));
assert_ok!(Elections::submit_candidacy(Origin::signed(5)));
assert_ok!(Elections::submit_candidacy(Origin::signed(6)));
assert_ok!(Elections::vote(Origin::signed(5), vec![5], 50));
assert_ok!(Elections::vote(Origin::signed(4), vec![4], 40));
assert_ok!(Elections::vote(Origin::signed(2), vec![4, 5], 20));
assert_ok!(Elections::vote(Origin::signed(6), vec![6], 30));
assert_ok!(Elections::vote(Origin::signed(3), vec![3], 30));
System::set_block_number(5);
assert_ok!(Elections::end_block(System::block_number()));
assert_eq!(Elections::members_ids(), vec![4, 5]);
assert_eq!(Elections::runners_up_ids(), vec![6]);
assert_eq!(Elections::candidates(), vec![]);
assert_eq!(Elections::is_defunct_voter(&5), false);
assert_eq!(Elections::is_defunct_voter(&4), false);
assert_eq!(Elections::is_defunct_voter(&2), false);
assert_eq!(Elections::is_defunct_voter(&6), false);
assert_eq!(Elections::is_defunct_voter(&3), true);
assert_ok!(Elections::submit_candidacy(Origin::signed(1)));
assert_ok!(Elections::vote(Origin::signed(1), vec![1], 10));
assert_eq!(Elections::is_defunct_voter(&1), false);
});
}
#[test]
fn report_voter_should_work_and_earn_reward() {
ExtBuilder::default().build_and_execute(|| {
assert_ok!(Elections::submit_candidacy(Origin::signed(5)));
assert_ok!(Elections::submit_candidacy(Origin::signed(4)));
assert_ok!(Elections::vote(Origin::signed(5), vec![5], 50));
assert_ok!(Elections::vote(Origin::signed(4), vec![4], 40));
assert_ok!(Elections::vote(Origin::signed(2), vec![4, 5], 20));
assert_ok!(Elections::vote(Origin::signed(3), vec![3], 30));
System::set_block_number(5);
assert_ok!(Elections::end_block(System::block_number()));
assert_eq!(Elections::members_ids(), vec![4, 5]);
assert_eq!(Elections::candidates(), vec![]);
assert_eq!(balances(&3), (28, 2));
assert_eq!(balances(&5), (45, 5));
assert_ok!(Elections::report_defunct_voter(Origin::signed(5), 3));
assert!(System::events().iter().any(|event| {
event.event == Event::elections_phragmen(RawEvent::VoterReported(3, 5, true))
}));
assert_eq!(balances(&3), (28, 0));
assert_eq!(balances(&5), (47, 5));
});
}
#[test]
fn report_voter_should_slash_when_bad_report() {
ExtBuilder::default().build_and_execute(|| {
assert_ok!(Elections::submit_candidacy(Origin::signed(5)));
assert_ok!(Elections::submit_candidacy(Origin::signed(4)));
assert_ok!(Elections::vote(Origin::signed(5), vec![5], 50));
assert_ok!(Elections::vote(Origin::signed(4), vec![4], 40));
System::set_block_number(5);
assert_ok!(Elections::end_block(System::block_number()));
assert_eq!(Elections::members_ids(), vec![4, 5]);
assert_eq!(Elections::candidates(), vec![]);
assert_eq!(balances(&4), (35, 5));
assert_eq!(balances(&5), (45, 5));
assert_ok!(Elections::report_defunct_voter(Origin::signed(5), 4));
assert!(System::events().iter().any(|event| {
event.event == Event::elections_phragmen(RawEvent::VoterReported(4, 5, false))
}));
assert_eq!(balances(&4), (35, 5));
assert_eq!(balances(&5), (45, 3));
});
}
#[test]
fn simple_voting_rounds_should_work() {
ExtBuilder::default().build_and_execute(|| {
assert_ok!(Elections::submit_candidacy(Origin::signed(5)));
assert_ok!(Elections::submit_candidacy(Origin::signed(4)));
assert_ok!(Elections::submit_candidacy(Origin::signed(3)));
assert_ok!(Elections::vote(Origin::signed(2), vec![5], 20));
assert_ok!(Elections::vote(Origin::signed(4), vec![4], 15));
assert_ok!(Elections::vote(Origin::signed(3), vec![3], 30));
assert_eq_uvec!(all_voters(), vec![2, 3, 4]);
assert_eq!(Elections::votes_of(&2), vec![5]);
assert_eq!(Elections::votes_of(&3), vec![3]);
assert_eq!(Elections::votes_of(&4), vec![4]);
assert_eq!(Elections::candidates(), vec![3, 4, 5]);
assert_eq!(<Candidates<Test>>::decode_len().unwrap(), 3);
assert_eq!(Elections::election_rounds(), 0);
System::set_block_number(5);
assert_ok!(Elections::end_block(System::block_number()));
assert_eq!(Elections::members(), vec![(3, 30), (5, 20)]);
assert_eq!(Elections::runners_up(), vec![]);
assert_eq_uvec!(all_voters(), vec![2, 3, 4]);
assert_eq!(Elections::candidates(), vec![]);
assert_eq!(<Candidates<Test>>::decode_len().unwrap(), 0);
assert_eq!(Elections::election_rounds(), 1);
});
}
#[test]
fn defunct_voter_will_be_counted() {
ExtBuilder::default().build_and_execute(|| {
assert_ok!(Elections::submit_candidacy(Origin::signed(5)));
assert_ok!(Elections::vote(Origin::signed(3), vec![4], 30));
assert_ok!(Elections::vote(Origin::signed(5), vec![5], 50));
System::set_block_number(5);
assert_ok!(Elections::end_block(System::block_number()));
assert_eq!(Elections::members(), vec![(5, 50)]);
assert_eq!(Elections::election_rounds(), 1);
assert_ok!(Elections::submit_candidacy(Origin::signed(4)));
System::set_block_number(10);
assert_ok!(Elections::end_block(System::block_number()));
assert_eq!(Elections::members(), vec![(4, 30), (5, 50)]);
assert_eq!(Elections::election_rounds(), 2);
assert_eq_uvec!(all_voters(), vec![3, 5]);
});
}
#[test]
fn only_desired_seats_are_chosen() {
ExtBuilder::default().build_and_execute(|| {
assert_ok!(Elections::submit_candidacy(Origin::signed(5)));
assert_ok!(Elections::submit_candidacy(Origin::signed(4)));
assert_ok!(Elections::submit_candidacy(Origin::signed(3)));
assert_ok!(Elections::submit_candidacy(Origin::signed(2)));
assert_ok!(Elections::vote(Origin::signed(2), vec![2], 20));
assert_ok!(Elections::vote(Origin::signed(3), vec![3], 30));
assert_ok!(Elections::vote(Origin::signed(4), vec![4], 40));
assert_ok!(Elections::vote(Origin::signed(5), vec![5], 50));
System::set_block_number(5);
assert_ok!(Elections::end_block(System::block_number()));
assert_eq!(Elections::election_rounds(), 1);
assert_eq!(Elections::members_ids(), vec![4, 5]);
});
}
#[test]
fn phragmen_should_not_self_vote() {
ExtBuilder::default().build_and_execute(|| {
assert_ok!(Elections::submit_candidacy(Origin::signed(5)));
assert_ok!(Elections::submit_candidacy(Origin::signed(4)));
System::set_block_number(5);
assert_ok!(Elections::end_block(System::block_number()));
assert_eq!(Elections::candidates(), vec![]);
assert_eq!(Elections::election_rounds(), 1);
assert_eq!(Elections::members_ids(), vec![]);
assert_eq!(
System::events().iter().last().unwrap().event,
Event::elections_phragmen(RawEvent::NewTerm(vec![])),
)
});
}
#[test]
fn runners_up_should_be_kept() {
ExtBuilder::default().desired_runners_up(2).build_and_execute(|| {
assert_ok!(Elections::submit_candidacy(Origin::signed(5)));
assert_ok!(Elections::submit_candidacy(Origin::signed(4)));
assert_ok!(Elections::submit_candidacy(Origin::signed(3)));
assert_ok!(Elections::submit_candidacy(Origin::signed(2)));
assert_ok!(Elections::vote(Origin::signed(2), vec![3], 20));
assert_ok!(Elections::vote(Origin::signed(3), vec![2], 30));
assert_ok!(Elections::vote(Origin::signed(4), vec![5], 40));
assert_ok!(Elections::vote(Origin::signed(5), vec![4], 50));
System::set_block_number(5);
assert_ok!(Elections::end_block(System::block_number()));
assert_eq!(Elections::members_ids(), vec![4, 5]);
assert_eq!(Elections::runners_up_ids(), vec![3, 2]);
assert_eq!(balances(&4), (35, 5));
assert_eq!(balances(&5), (45, 5));
assert_eq!(balances(&3), (25, 5));
});
}
#[test]
fn runners_up_should_be_next_candidates() {
ExtBuilder::default().desired_runners_up(2).build_and_execute(|| {
assert_ok!(Elections::submit_candidacy(Origin::signed(5)));
assert_ok!(Elections::submit_candidacy(Origin::signed(4)));
assert_ok!(Elections::submit_candidacy(Origin::signed(3)));
assert_ok!(Elections::submit_candidacy(Origin::signed(2)));
assert_ok!(Elections::vote(Origin::signed(2), vec![2], 20));
assert_ok!(Elections::vote(Origin::signed(3), vec![3], 30));
assert_ok!(Elections::vote(Origin::signed(4), vec![4], 40));
assert_ok!(Elections::vote(Origin::signed(5), vec![5], 50));
System::set_block_number(5);
assert_ok!(Elections::end_block(System::block_number()));
assert_eq!(Elections::members(), vec![(4, 40), (5, 50)]);
assert_eq!(Elections::runners_up(), vec![(2, 20), (3, 30)]);
assert_ok!(Elections::vote(Origin::signed(5), vec![5], 15));
System::set_block_number(10);
assert_ok!(Elections::end_block(System::block_number()));
assert_eq!(Elections::members(), vec![(3, 30), (4, 40)]);
assert_eq!(Elections::runners_up(), vec![(5, 15), (2, 20)]);
});
}
#[test]
fn runners_up_lose_bond_once_outgoing() {
ExtBuilder::default().desired_runners_up(1).build_and_execute(|| {
assert_ok!(Elections::submit_candidacy(Origin::signed(5)));
assert_ok!(Elections::submit_candidacy(Origin::signed(4)));
assert_ok!(Elections::submit_candidacy(Origin::signed(2)));
assert_ok!(Elections::vote(Origin::signed(2), vec![2], 20));
assert_ok!(Elections::vote(Origin::signed(4), vec![4], 40));
assert_ok!(Elections::vote(Origin::signed(5), vec![5], 50));
System::set_block_number(5);
assert_ok!(Elections::end_block(System::block_number()));
assert_eq!(Elections::members_ids(), vec![4, 5]);
assert_eq!(Elections::runners_up_ids(), vec![2]);
assert_eq!(balances(&2), (15, 5));
assert_ok!(Elections::submit_candidacy(Origin::signed(3)));
assert_ok!(Elections::vote(Origin::signed(3), vec![3], 30));
System::set_block_number(10);
assert_ok!(Elections::end_block(System::block_number()));
assert_eq!(Elections::runners_up_ids(), vec![3]);
assert_eq!(balances(&2), (15, 2));
});
}
#[test]
fn members_lose_bond_once_outgoing() {
ExtBuilder::default().build_and_execute(|| {
assert_eq!(balances(&5), (50, 0));
assert_ok!(Elections::submit_candidacy(Origin::signed(5)));
assert_eq!(balances(&5), (47, 3));
assert_ok!(Elections::vote(Origin::signed(5), vec![5], 50));
assert_eq!(balances(&5), (45, 5));
System::set_block_number(5);
assert_ok!(Elections::end_block(System::block_number()));
assert_eq!(Elections::members_ids(), vec![5]);
assert_ok!(Elections::remove_voter(Origin::signed(5)));
assert_eq!(balances(&5), (47, 3));
System::set_block_number(10);
assert_ok!(Elections::end_block(System::block_number()));
assert_eq!(Elections::members_ids(), vec![]);
assert_eq!(balances(&5), (47, 0));
});
}
#[test]
fn losers_will_lose_the_bond() {
ExtBuilder::default().build_and_execute(|| {
assert_ok!(Elections::submit_candidacy(Origin::signed(5)));
assert_ok!(Elections::submit_candidacy(Origin::signed(3)));
assert_ok!(Elections::vote(Origin::signed(4), vec![5], 40));
assert_eq!(balances(&5), (47, 3));
assert_eq!(balances(&3), (27, 3));
System::set_block_number(5);
assert_ok!(Elections::end_block(System::block_number()));
assert_eq!(Elections::members_ids(), vec![5]);
assert_eq!(balances(&5), (47, 3));
assert_eq!(balances(&3), (27, 0));
});
}
#[test]
fn current_members_are_always_next_candidate() {
ExtBuilder::default().build_and_execute(|| {
assert_ok!(Elections::submit_candidacy(Origin::signed(5)));
assert_ok!(Elections::submit_candidacy(Origin::signed(4)));
assert_ok!(Elections::vote(Origin::signed(4), vec![4], 40));
assert_ok!(Elections::vote(Origin::signed(5), vec![5], 50));
System::set_block_number(5);
assert_ok!(Elections::end_block(System::block_number()));
assert_eq!(Elections::members_ids(), vec![4, 5]);
assert_eq!(Elections::election_rounds(), 1);
assert_ok!(Elections::submit_candidacy(Origin::signed(2)));
assert_ok!(Elections::vote(Origin::signed(2), vec![2], 20));
assert_ok!(Elections::submit_candidacy(Origin::signed(3)));
assert_ok!(Elections::vote(Origin::signed(3), vec![3], 30));
assert_ok!(Elections::remove_voter(Origin::signed(4)));
assert_eq!(Elections::candidates(), vec![2, 3]);
System::set_block_number(10);
assert_ok!(Elections::end_block(System::block_number()));
assert_eq!(Elections::members_ids(), vec![3, 5]);
});
}
#[test]
fn election_state_is_uninterrupted() {
ExtBuilder::default().desired_runners_up(2).build_and_execute(|| {
assert_ok!(Elections::submit_candidacy(Origin::signed(5)));
assert_ok!(Elections::submit_candidacy(Origin::signed(4)));
assert_ok!(Elections::submit_candidacy(Origin::signed(3)));
assert_ok!(Elections::submit_candidacy(Origin::signed(2)));
assert_ok!(Elections::vote(Origin::signed(5), vec![5], 50));
assert_ok!(Elections::vote(Origin::signed(4), vec![4], 40));
assert_ok!(Elections::vote(Origin::signed(3), vec![3], 30));
assert_ok!(Elections::vote(Origin::signed(2), vec![2], 20));
let check_at_block = |b: u32| {
System::set_block_number(b.into());
assert_ok!(Elections::end_block(System::block_number()));
assert_eq!(Elections::members(), vec![(4, 40), (5, 50)]);
assert_eq!(Elections::runners_up(), vec![(2, 20), (3, 30)]);
assert_eq!(Elections::candidates(), vec![]);
assert_eq!(Elections::election_rounds(), b / 5);
assert_eq_uvec!(all_voters(), vec![2, 3, 4, 5]);
};
check_at_block(5);
check_at_block(10);
check_at_block(15);
check_at_block(20);
});
}
#[test]
fn remove_members_triggers_election() {
ExtBuilder::default().build_and_execute(|| {
assert_ok!(Elections::submit_candidacy(Origin::signed(5)));
assert_ok!(Elections::submit_candidacy(Origin::signed(4)));
assert_ok!(Elections::vote(Origin::signed(4), vec![4], 40));
assert_ok!(Elections::vote(Origin::signed(5), vec![5], 50));
System::set_block_number(5);
assert_ok!(Elections::end_block(System::block_number()));
assert_eq!(Elections::members_ids(), vec![4, 5]);
assert_eq!(Elections::election_rounds(), 1);
assert_ok!(Elections::submit_candidacy(Origin::signed(3)));
assert_ok!(Elections::vote(Origin::signed(3), vec![3], 30));
assert_ok!(Elections::remove_member(Origin::ROOT, 4));
assert_eq!(balances(&4), (35, 2));
assert_eq!(Elections::election_rounds(), 2);
assert_eq!(Elections::members_ids(), vec![3, 5]);
});
}
#[test]
fn seats_should_be_released_when_no_vote() {
ExtBuilder::default().build_and_execute(|| {
assert_ok!(Elections::submit_candidacy(Origin::signed(5)));
assert_ok!(Elections::submit_candidacy(Origin::signed(4)));
assert_ok!(Elections::submit_candidacy(Origin::signed(3)));
assert_ok!(Elections::vote(Origin::signed(2), vec![3], 20));
assert_ok!(Elections::vote(Origin::signed(3), vec![3], 30));
assert_ok!(Elections::vote(Origin::signed(4), vec![4], 40));
assert_ok!(Elections::vote(Origin::signed(5), vec![5], 50));
assert_eq!(<Candidates<Test>>::decode_len().unwrap(), 3);
assert_eq!(Elections::election_rounds(), 0);
System::set_block_number(5);
assert_ok!(Elections::end_block(System::block_number()));
assert_eq!(Elections::members_ids(), vec![3, 5]);
assert_eq!(Elections::election_rounds(), 1);
assert_ok!(Elections::remove_voter(Origin::signed(2)));
assert_ok!(Elections::remove_voter(Origin::signed(3)));
assert_ok!(Elections::remove_voter(Origin::signed(4)));
assert_ok!(Elections::remove_voter(Origin::signed(5)));
System::set_block_number(10);
assert_ok!(Elections::end_block(System::block_number()));
assert_eq!(Elections::members_ids(), vec![]);
assert_eq!(Elections::election_rounds(), 2);
});
}
#[test]
fn incoming_outgoing_are_reported() {
ExtBuilder::default().build_and_execute(|| {
assert_ok!(Elections::submit_candidacy(Origin::signed(4)));
assert_ok!(Elections::submit_candidacy(Origin::signed(5)));
assert_ok!(Elections::vote(Origin::signed(4), vec![4], 40));
assert_ok!(Elections::vote(Origin::signed(5), vec![5], 50));
System::set_block_number(5);
assert_ok!(Elections::end_block(System::block_number()));
assert_eq!(Elections::members_ids(), vec![4, 5]);
assert_ok!(Elections::submit_candidacy(Origin::signed(1)));
assert_ok!(Elections::submit_candidacy(Origin::signed(2)));
assert_ok!(Elections::submit_candidacy(Origin::signed(3)));
assert_ok!(Elections::vote(Origin::signed(5), vec![4], 8));
assert_ok!(Elections::vote(Origin::signed(4), vec![4], 40));
assert_ok!(Elections::vote(Origin::signed(3), vec![3], 30));
assert_ok!(Elections::vote(Origin::signed(2), vec![2], 20));
assert_ok!(Elections::vote(Origin::signed(1), vec![1], 10));
System::set_block_number(10);
assert_ok!(Elections::end_block(System::block_number()));
assert_eq!(Elections::members(), vec![(3, 30), (4, 48)]);
assert_eq!(balances(&3), (25, 5));
assert_eq!(balances(&4), (35, 5));
assert_eq!(balances(&1), (5, 2));
assert_eq!(balances(&5), (45, 2));
assert!(System::events().iter().any(|event| {
event.event == Event::elections_phragmen(RawEvent::NewTerm(vec![(4, 40), (5, 50)]))
}));
})
}
#[test]
fn invalid_votes_are_moot() {
ExtBuilder::default().build_and_execute(|| {
assert_ok!(Elections::submit_candidacy(Origin::signed(4)));
assert_ok!(Elections::submit_candidacy(Origin::signed(3)));
assert_ok!(Elections::vote(Origin::signed(3), vec![3], 30));
assert_ok!(Elections::vote(Origin::signed(4), vec![4], 40));
assert_ok!(Elections::vote(Origin::signed(5), vec![10], 50));
System::set_block_number(5);
assert_ok!(Elections::end_block(System::block_number()));
assert_eq_uvec!(Elections::members_ids(), vec![3, 4]);
assert_eq!(Elections::election_rounds(), 1);
});
}
#[test]
fn members_are_sorted_based_on_id_runners_on_merit() {
ExtBuilder::default().desired_runners_up(2).build_and_execute(|| {
assert_ok!(Elections::submit_candidacy(Origin::signed(5)));
assert_ok!(Elections::submit_candidacy(Origin::signed(4)));
assert_ok!(Elections::submit_candidacy(Origin::signed(3)));
assert_ok!(Elections::submit_candidacy(Origin::signed(2)));
assert_ok!(Elections::vote(Origin::signed(2), vec![3], 20));
assert_ok!(Elections::vote(Origin::signed(3), vec![2], 30));
assert_ok!(Elections::vote(Origin::signed(4), vec![5], 40));
assert_ok!(Elections::vote(Origin::signed(5), vec![4], 50));
System::set_block_number(5);
assert_ok!(Elections::end_block(System::block_number()));
assert_eq!(Elections::members(), vec![(4, 50), (5, 40)]);
assert_eq!(Elections::runners_up(), vec![(3, 20), (2, 30)]);
});
}
#[test]
fn candidates_are_sorted() {
ExtBuilder::default().build_and_execute(|| {
assert_ok!(Elections::submit_candidacy(Origin::signed(5)));
assert_ok!(Elections::submit_candidacy(Origin::signed(3)));
assert_eq!(Elections::candidates(), vec![3, 5]);
assert_ok!(Elections::submit_candidacy(Origin::signed(2)));
assert_ok!(Elections::submit_candidacy(Origin::signed(4)));
assert_ok!(Elections::renounce_candidacy(Origin::signed(3)));
assert_eq!(Elections::candidates(), vec![2, 4, 5]);
})
}
#[test]
fn runner_up_replacement_maintains_members_order() {
ExtBuilder::default().desired_runners_up(2).build_and_execute(|| {
assert_ok!(Elections::submit_candidacy(Origin::signed(5)));
assert_ok!(Elections::submit_candidacy(Origin::signed(4)));
assert_ok!(Elections::submit_candidacy(Origin::signed(2)));
assert_ok!(Elections::vote(Origin::signed(2), vec![5], 20));
assert_ok!(Elections::vote(Origin::signed(4), vec![4], 40));
assert_ok!(Elections::vote(Origin::signed(5), vec![2], 50));
System::set_block_number(5);
assert_ok!(Elections::end_block(System::block_number()));
assert_eq!(Elections::members_ids(), vec![2, 4]);
assert_ok!(Elections::remove_member(Origin::ROOT, 2));
assert_eq!(Elections::members_ids(), vec![4, 5]);
});
}
#[test]
fn runner_up_replacement_works_when_out_of_order() {
ExtBuilder::default().desired_runners_up(2).build_and_execute(|| {
assert_ok!(Elections::submit_candidacy(Origin::signed(5)));
assert_ok!(Elections::submit_candidacy(Origin::signed(4)));
assert_ok!(Elections::submit_candidacy(Origin::signed(3)));
assert_ok!(Elections::submit_candidacy(Origin::signed(2)));
assert_ok!(Elections::vote(Origin::signed(2), vec![5], 20));
assert_ok!(Elections::vote(Origin::signed(3), vec![3], 30));
assert_ok!(Elections::vote(Origin::signed(4), vec![4], 40));
assert_ok!(Elections::vote(Origin::signed(5), vec![2], 50));
System::set_block_number(5);
assert_ok!(Elections::end_block(System::block_number()));
assert_eq!(Elections::members_ids(), vec![2, 4]);
assert_ok!(Elections::renounce_candidacy(Origin::signed(3)));
});
}
#[test]
fn can_renounce_candidacy_member_with_runners_bond_is_refunded() {
ExtBuilder::default().desired_runners_up(2).build_and_execute(|| {
assert_ok!(Elections::submit_candidacy(Origin::signed(5)));
assert_ok!(Elections::submit_candidacy(Origin::signed(4)));
assert_ok!(Elections::submit_candidacy(Origin::signed(3)));
assert_ok!(Elections::submit_candidacy(Origin::signed(2)));
assert_ok!(Elections::vote(Origin::signed(5), vec![5], 50));
assert_ok!(Elections::vote(Origin::signed(4), vec![4], 40));
assert_ok!(Elections::vote(Origin::signed(3), vec![3], 30));
assert_ok!(Elections::vote(Origin::signed(2), vec![2], 20));
System::set_block_number(5);
assert_ok!(Elections::end_block(System::block_number()));
assert_eq!(Elections::members_ids(), vec![4, 5]);
assert_eq!(Elections::runners_up_ids(), vec![2, 3]);
assert_ok!(Elections::renounce_candidacy(Origin::signed(4)));
assert_eq!(balances(&4), (38, 2));
assert_eq!(Elections::members_ids(), vec![3, 5]);
assert_eq!(Elections::runners_up_ids(), vec![2]);
})
}
#[test]
fn can_renounce_candidacy_member_without_runners_bond_is_refunded() {
ExtBuilder::default().desired_runners_up(2).build_and_execute(|| {
assert_ok!(Elections::submit_candidacy(Origin::signed(5)));
assert_ok!(Elections::submit_candidacy(Origin::signed(4)));
assert_ok!(Elections::vote(Origin::signed(5), vec![5], 50));
assert_ok!(Elections::vote(Origin::signed(4), vec![4], 40));
System::set_block_number(5);
assert_ok!(Elections::end_block(System::block_number()));
assert_ok!(Elections::submit_candidacy(Origin::signed(3)));
assert_ok!(Elections::submit_candidacy(Origin::signed(2)));
assert_ok!(Elections::vote(Origin::signed(3), vec![3], 30));
assert_ok!(Elections::vote(Origin::signed(2), vec![2], 20));
assert_eq!(Elections::members_ids(), vec![4, 5]);
assert_eq!(Elections::runners_up_ids(), vec![]);
assert_eq!(Elections::candidates(), vec![2, 3]);
assert_ok!(Elections::renounce_candidacy(Origin::signed(4)));
assert_eq!(balances(&4), (38, 2));
assert_eq!(Elections::members_ids(), vec![5]);
assert_eq!(Elections::runners_up_ids(), vec![]);
assert_eq!(Elections::candidates(), vec![2, 3]);
})
}
#[test]
fn can_renounce_candidacy_runner() {
ExtBuilder::default().desired_runners_up(2).build_and_execute(|| {
assert_ok!(Elections::submit_candidacy(Origin::signed(5)));
assert_ok!(Elections::submit_candidacy(Origin::signed(4)));
assert_ok!(Elections::submit_candidacy(Origin::signed(3)));
assert_ok!(Elections::submit_candidacy(Origin::signed(2)));
assert_ok!(Elections::vote(Origin::signed(5), vec![4], 50));
assert_ok!(Elections::vote(Origin::signed(4), vec![5], 40));
assert_ok!(Elections::vote(Origin::signed(3), vec![3], 30));
assert_ok!(Elections::vote(Origin::signed(2), vec![2], 20));
System::set_block_number(5);
assert_ok!(Elections::end_block(System::block_number()));
assert_eq!(Elections::members_ids(), vec![4, 5]);
assert_eq!(Elections::runners_up_ids(), vec![2, 3]);
assert_ok!(Elections::renounce_candidacy(Origin::signed(3)));
assert_eq!(balances(&3), (28, 2));
assert_eq!(Elections::members_ids(), vec![4, 5]);
assert_eq!(Elections::runners_up_ids(), vec![2]);
})
}
#[test]
fn can_renounce_candidacy_candidate() {
ExtBuilder::default().build_and_execute(|| {
assert_ok!(Elections::submit_candidacy(Origin::signed(5)));
assert_eq!(balances(&5), (47, 3));
assert_eq!(Elections::candidates(), vec![5]);
assert_ok!(Elections::renounce_candidacy(Origin::signed(5)));
assert_eq!(balances(&5), (50, 0));
assert_eq!(Elections::candidates(), vec![]);
})
}
#[test]
fn wrong_renounce_candidacy_should_fail() {
ExtBuilder::default().build_and_execute(|| {
assert_noop!(
Elections::renounce_candidacy(Origin::signed(5)),
Error::<Test>::InvalidOrigin,
);
})
}
#[test]
fn behavior_with_dupe_candidate() {
ExtBuilder::default().desired_runners_up(2).build_and_execute(|| {
<Candidates<Test>>::put(vec![1, 1, 2, 3, 4]);
assert_ok!(Elections::vote(Origin::signed(5), vec![1], 50));
assert_ok!(Elections::vote(Origin::signed(4), vec![4], 40));
assert_ok!(Elections::vote(Origin::signed(3), vec![3], 30));
assert_ok!(Elections::vote(Origin::signed(2), vec![2], 20));
System::set_block_number(5);
assert_ok!(Elections::end_block(System::block_number()));
assert_eq!(Elections::members_ids(), vec![1, 4]);
assert_eq!(Elections::runners_up_ids(), vec![2, 3]);
assert_eq!(Elections::candidates(), vec![]);
})
}
}