// Copyright 2017-2019 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see .
//! Election module for stake-weighted membership selection of a collective.
//!
//! The composition of a set of account IDs works according to one or more approval votes
//! weighted by stake. There is a partial carry-over facility to give greater weight to those
//! whose voting is serially unsuccessful.
#![cfg_attr(not(feature = "std"), no_std)]
#![recursion_limit="128"]
use rstd::prelude::*;
use sr_primitives::{
RuntimeDebug,
print,
traits::{Zero, One, StaticLookup, Bounded, Saturating},
weights::SimpleDispatchInfo,
};
use support::{
dispatch::Result, decl_storage, decl_event, ensure, decl_module,
traits::{
Currency, ExistenceRequirement, Get, LockableCurrency, LockIdentifier,
OnUnbalanced, ReservableCurrency, WithdrawReason, WithdrawReasons, ChangeMembers
}
};
use codec::{Encode, Decode};
use system::{self, ensure_signed, ensure_root};
mod mock;
mod tests;
// no polynomial attacks:
//
// all unbonded public operations should be constant time.
// all other public operations must be linear time in terms of prior public operations and:
// - those "valid" ones that cost nothing be limited to a constant number per single protected
// operation
// - the rest costing the same order as the computational complexity
// all protected operations must complete in at most O(public operations)
//
// we assume "beneficial" transactions will have the same access as attack transactions.
//
// any storage requirements should be bonded by the same order as the volume.
// public operations:
// - express approvals (you pay in a "voter" bond the first time you do this; O(1); one extra DB
// entry, one DB change)
// - remove active voter (you get your "voter" bond back; O(1); one fewer DB entry, one DB change)
// - remove inactive voter (either you or the target is removed; if the target, you get their
// "voter" bond back; O(1); one fewer DB entry, one DB change)
// - submit candidacy (you pay a "candidate" bond; O(1); one extra DB entry, two DB changes)
// - present winner/runner-up (you may pay a "presentation" bond of O(voters) if the presentation
// is invalid; O(voters) compute; ) protected operations:
// - remove candidacy (remove all votes for a candidate) (one fewer DB entry, two DB changes)
// to avoid a potentially problematic case of not-enough approvals prior to voting causing a
// back-to-back votes that have no way of ending, then there's a forced grace period between votes.
// to keep the system as stateless as possible (making it a bit easier to reason about), we just
// restrict when votes can begin to blocks that lie on boundaries (`voting_period`).
// for an approval vote of C members:
// top K runners-up are maintained between votes. all others are discarded.
// - candidate removed & bond returned when elected.
// - candidate removed & bond burned when discarded.
// at the point that the vote ends (), all voters' balances are snapshotted.
// for B blocks following, there's a counting period whereby each of the candidates that believe
// they fall in the top K+C voted can present themselves. they get the total stake
// recorded (based on the snapshot); an ordered list is maintained (the leaderboard). Noone may
// present themselves that, if elected, would result in being included twice in the collective
// (important since existing members will have their approval votes as it may be that they
// don't get removed), nor if existing presenters would mean they're not in the top K+C.
// following B blocks, the top C candidates are elected and have their bond returned. the top C
// candidates and all other candidates beyond the top C+K are cleared.
// vote-clearing happens lazily; for an approval to count, the most recent vote at the time of the
// voter's most recent vote must be no later than the most recent vote at the time that the
// candidate in the approval position was registered there. as candidates are removed from the
// register and others join in their place, this prevents an approval meant for an earlier candidate
// being used to elect a new candidate.
// the candidate list increases as needed, but the contents (though not really the capacity) reduce
// after each vote as all but K entries are cleared. newly registering candidates must use cleared
// entries before they increase the capacity.
/// The activity status of a voter.
#[derive(PartialEq, Eq, Copy, Clone, Encode, Decode, Default, RuntimeDebug)]
pub struct VoterInfo {
/// Last VoteIndex in which this voter assigned (or initialized) approvals.
last_active: VoteIndex,
/// Last VoteIndex in which one of this voter's approvals won.
/// Note that `last_win = N` indicates a last win at index `N-1`, hence `last_win = 0` means no
/// win ever.
last_win: VoteIndex,
/// The amount of stored weight as a result of not winning but changing approvals.
pot: Balance,
/// Current staked amount. A lock equal to this value always exists.
stake: Balance,
}
/// Used to demonstrate the status of a particular index in the global voter list.
#[derive(PartialEq, Eq, RuntimeDebug)]
pub enum CellStatus {
/// Any out of bound index. Means a push a must happen to the chunk pointed by `NextVoterSet`.
/// Voting fee is applied in case a new chunk is created.
Head,
/// Already occupied by another voter. Voting fee is applied.
Occupied,
/// Empty hole which should be filled. No fee will be applied.
Hole,
}
const MODULE_ID: LockIdentifier = *b"py/elect";
/// Number of voters grouped in one chunk.
pub const VOTER_SET_SIZE: usize = 64;
/// NUmber of approvals grouped in one chunk.
pub const APPROVAL_SET_SIZE: usize = 8;
type BalanceOf = <::Currency as Currency<::AccountId>>::Balance;
type NegativeImbalanceOf =
<::Currency as Currency<::AccountId>>::NegativeImbalance;
/// Index used to access chunks.
type SetIndex = u32;
/// Index used to count voting rounds.
pub type VoteIndex = u32;
/// Underlying data type of the approvals.
type ApprovalFlag = u32;
/// Number of approval flags that can fit into [`ApprovalFlag`] type.
const APPROVAL_FLAG_LEN: usize = 32;
pub trait Trait: system::Trait {
type Event: From> + Into<::Event>;
/// The currency that people are electing with.
type Currency:
LockableCurrency
+ ReservableCurrency;
/// Handler for the unbalanced reduction when slashing a validator.
type BadPresentation: OnUnbalanced>;
/// Handler for the unbalanced reduction when slashing an invalid reaping attempt.
type BadReaper: OnUnbalanced>;
/// Handler for the unbalanced reduction when submitting a bad `voter_index`.
type BadVoterIndex: OnUnbalanced>;
/// Handler for the unbalanced reduction when a candidate has lost (and is not a runner up)
type LoserCandidate: OnUnbalanced>;
/// What to do when the members change.
type ChangeMembers: ChangeMembers;
/// How much should be locked up in order to submit one's candidacy. A reasonable
/// default value is 9.
type CandidacyBond: Get>;
/// How much should be locked up in order to be able to submit votes.
type VotingBond: Get>;
/// The amount of fee paid upon each vote submission, unless if they submit a
/// _hole_ index and replace it.
type VotingFee: Get>;
/// Minimum about that can be used as the locked value for voting.
type MinimumVotingLock: Get>;
/// The punishment, per voter, if you provide an invalid presentation. A
/// reasonable default value is 1.
type PresentSlashPerVoter: Get>;
/// How many runners-up should have their approvals persist until the next
/// vote. A reasonable default value is 2.
type CarryCount: Get;
/// How many vote indices need to go by after a target voter's last vote before
/// they can be reaped if their approvals are moot. A reasonable default value
/// is 1.
type InactiveGracePeriod: Get;
/// How often (in blocks) to check for new votes. A reasonable default value
/// is 1000.
type VotingPeriod: Get;
/// Decay factor of weight when being accumulated. It should typically be set to
/// __at least__ `membership_size -1` to keep the collective secure.
/// When set to `N`, it indicates `(1/N)^t` of staked is decayed at weight
/// increment step `t`. 0 will result in no weight being added at all (normal
/// approval voting). A reasonable default value is 24.
type DecayRatio: Get;
}
decl_storage! {
trait Store for Module as Council {
// ---- parameters
/// How long to give each top candidate to present themselves after the vote ends.
pub PresentationDuration get(fn presentation_duration) config(): T::BlockNumber;
/// How long each position is active for.
pub TermDuration get(fn term_duration) config(): T::BlockNumber;
/// Number of accounts that should constitute the collective.
pub DesiredSeats get(fn desired_seats) config(): u32;
// ---- permanent state (always relevant, changes only at the finalization of voting)
/// The current membership. When there's a vote going on, this should still be used for
/// executive matters. The block number (second element in the tuple) is the block that
/// their position is active until (calculated by the sum of the block number when the
/// member was elected and their term duration).
pub Members get(fn members) config(): Vec<(T::AccountId, T::BlockNumber)>;
/// The total number of vote rounds that have happened or are in progress.
pub VoteCount get(fn vote_index): VoteIndex;
// ---- persistent state (always relevant, changes constantly)
// A list of votes for each voter. The votes are stored as numeric values and parsed in a
// bit-wise manner. In order to get a human-readable representation (`Vec`), use
// [`all_approvals_of`]. Furthermore, each vector of scalars is chunked with the cap of
// `APPROVAL_SET_SIZE`.
pub ApprovalsOf get(fn approvals_of): map (T::AccountId, SetIndex) => Vec;
/// The vote index and list slot that the candidate `who` was registered or `None` if they
/// are not currently registered.
pub RegisterInfoOf get(fn candidate_reg_info): map T::AccountId => Option<(VoteIndex, u32)>;
/// Basic information about a voter.
pub VoterInfoOf get(fn voter_info): map T::AccountId => Option>>;
/// The present voter list (chunked and capped at [`VOTER_SET_SIZE`]).
pub Voters get(fn voters): map SetIndex => Vec