From dd4607b50b8b16b27cc13dd992dff88ed00c5b6f Mon Sep 17 00:00:00 2001 From: kianenigma Date: Mon, 7 Sep 2020 16:40:33 +0200 Subject: [PATCH 01/69] Fix elections-phragmen and proxy issue --- bin/node/runtime/src/lib.rs | 16 +++++--- frame/elections-phragmen/src/lib.rs | 61 +++++++++++++++-------------- frame/elections/src/lib.rs | 9 ++++- frame/proxy/src/lib.rs | 3 +- 4 files changed, 53 insertions(+), 36 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 54dea704bd7f6..6ecfd3373ab70 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -234,13 +234,19 @@ impl InstanceFilter for ProxyType { fn filter(&self, c: &Call) -> bool { match self { ProxyType::Any => true, - ProxyType::NonTransfer => !matches!(c, - Call::Balances(..) | Call::Vesting(pallet_vesting::Call::vested_transfer(..)) - | Call::Indices(pallet_indices::Call::transfer(..)) + ProxyType::NonTransfer => !matches!( + c, + Call::Balances(..) | + Call::Vesting(pallet_vesting::Call::vested_transfer(..)) | + Call::Indices(pallet_indices::Call::transfer(..)) ), ProxyType::Governance => matches!(c, - Call::Democracy(..) | Call::Council(..) | Call::Society(..) - | Call::TechnicalCommittee(..) | Call::Elections(..) | Call::Treasury(..) + Call::Democracy(..) | + Call::Council(..) | + Call::Society(..) | + Call::TechnicalCommittee(..) | + Call::Elections(..) | + Call::Treasury(..) ), ProxyType::Staking => matches!(c, Call::Staking(..)), } diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index 9d1922576ad42..a30667dd693c7 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -96,7 +96,7 @@ use frame_support::{ dispatch::{DispatchResultWithPostInfo, WithPostDispatchInfo}, traits::{ Currency, Get, LockableCurrency, LockIdentifier, ReservableCurrency, WithdrawReasons, - ChangeMembers, OnUnbalanced, WithdrawReason, Contains, BalanceStatus, InitializeMembers, + ChangeMembers, OnUnbalanced, WithdrawReason, Contains, InitializeMembers, ContainsLengthBound, } }; @@ -194,7 +194,10 @@ pub trait Trait: frame_system::Trait { /// How much should be locked up in order to submit one's candidacy. type CandidacyBond: Get>; - /// How much should be locked up in order to be able to submit votes. + /// Base deposit associated with voting. + /// + /// This should be sensibly high to economically ensure the pallet cannot be attacked by + /// creating a gigantic number of votes. type VotingBond: Get>; /// Handler for the unbalanced reduction when a candidate has lost (and is not a runner-up) @@ -364,6 +367,7 @@ decl_module! { ) { let who = ensure_signed(origin)?; + // votes should not be empty and more than `MAXIMUM_VOTE` in any case. ensure!(votes.len() <= MAXIMUM_VOTE, Error::::MaximumVotesExceeded); ensure!(!votes.is_empty(), Error::::NoVotes); @@ -371,9 +375,10 @@ decl_module! { let members_count = >::decode_len().unwrap_or(0); let runners_up_count = >::decode_len().unwrap_or(0); + // can never submit a vote of there are no members, and cannot submit more votes than + // all potential vote targets. // addition is valid: candidates, members and runners-up will never overlap. let allowed_votes = candidates_count + members_count + runners_up_count; - ensure!(!allowed_votes.is_zero(), Error::::UnableToVote); ensure!(votes.len() <= allowed_votes, Error::::TooManyVotes); @@ -420,20 +425,21 @@ decl_module! { Self::do_remove_voter(&who, true); } - /// Report `target` for being an defunct voter. In case of a valid report, the reporter is - /// rewarded by the bond amount of `target`. Otherwise, the reporter itself is removed and - /// their bond is slashed. + /// Report `target` for being an defunct voter. In case of a valid report, the `target`'s + /// bond is returned and their voting is removed. Otherwise, nothing happens. + /// + /// Note that regardless of the outcome, neither slashing nor rewarding happens. /// - /// A defunct voter is defined to be: - /// - a voter whose current submitted votes are all invalid. i.e. all of them are no - /// longer a candidate nor an active member or a runner-up. + /// A defunct voter is defined to be: a voter who's current submitted votes are all invalid. + /// i.e. all of them are no longer a candidate nor an active member or a runner-up. /// + /// The dispatch origin of this call must be signed. /// /// The origin must provide the number of current candidates and votes of the reported target /// for the purpose of accurate weight calculation. /// /// # - /// No Base weight based on min square analysis. + /// No base weight based on min square analysis. /// Complexity of candidate_count: 1.755 µs /// Complexity of vote_count: 18.51 µs /// State reads: @@ -460,7 +466,6 @@ decl_module! { let target = T::Lookup::lookup(defunct.who)?; ensure!(reporter != target, Error::::ReportSelf); - ensure!(Self::is_voter(&reporter), Error::::MustBeVoter); let DefunctVoter { candidate_count, vote_count, .. } = defunct; @@ -483,17 +488,11 @@ decl_module! { let valid = Self::is_defunct_voter(&votes); if valid { - // reporter will get the voting bond of the target - T::Currency::repatriate_reserved(&target, &reporter, T::VotingBond::get(), BalanceStatus::Free)?; - // remove the target. They are defunct. - Self::do_remove_voter(&target, false); - } else { - // slash the bond of the reporter. - let imbalance = T::Currency::slash_reserved(&reporter, T::VotingBond::get()).0; - T::BadReport::on_unbalanced(imbalance); - // remove the reporter. - Self::do_remove_voter(&reporter, false); + // remove the defunct target while unreserving their bond. + Self::do_remove_voter(&target, true); + // TODO: probably refund or be more kind to the poor origin. } + Self::deposit_event(RawEvent::VoterReported(target, reporter, valid)); } @@ -832,7 +831,9 @@ impl Module { /// Reads Members, RunnersUp, Candidates and Voting(who) from database. fn is_defunct_voter(votes: &[T::AccountId]) -> bool { votes.iter().all(|v| - !Self::is_member(v) && !Self::is_runner_up(v) && !Self::is_candidate(v).is_ok() + !Self::is_member(v) && + !Self::is_runner_up(v) && + !Self::is_candidate(v).is_ok() ) } @@ -840,12 +841,11 @@ impl Module { /// /// This will clean always clean the storage associated with the voter, and remove the balance /// lock. Optionally, it would also return the reserved voting bond if indicated by `unreserve`. - /// If unreserve is true, has 3 storage reads and 1 reads. /// /// DB access: Voting and Lock are always written to, if unreserve, then 1 read and write added. fn do_remove_voter(who: &T::AccountId, unreserve: bool) { // remove storage and lock. - Voting::::remove(who); + >::remove(who); T::Currency::remove_lock(T::ModuleId::get(), who); if unreserve { @@ -2028,7 +2028,7 @@ mod tests { } #[test] - fn report_voter_should_work_and_earn_reward() { + fn report_voter_should_work() { ExtBuilder::default().build_and_execute(|| { assert_ok!(submit_candidacy(Origin::signed(5))); assert_ok!(submit_candidacy(Origin::signed(4))); @@ -2053,13 +2053,15 @@ mod tests { event.event == Event::elections_phragmen(RawEvent::VoterReported(3, 5, true)) })); - assert_eq!(balances(&3), (28, 0)); - assert_eq!(balances(&5), (47, 5)); + // target got bond back. + assert_eq!(balances(&3), (30, 0)); + // reporter nothing changed. + assert_eq!(balances(&5), (45, 5)); }); } #[test] - fn report_voter_should_slash_when_bad_report() { + fn report_voter_should_do_nothing_when_bad_report() { ExtBuilder::default().build_and_execute(|| { assert_ok!(submit_candidacy(Origin::signed(5))); assert_ok!(submit_candidacy(Origin::signed(4))); @@ -2081,8 +2083,9 @@ mod tests { event.event == Event::elections_phragmen(RawEvent::VoterReported(4, 5, false)) })); + // neither change. Only 5 is paying tx fee (not reflected here). assert_eq!(balances(&4), (35, 5)); - assert_eq!(balances(&5), (45, 3)); + assert_eq!(balances(&5), (45, 5)); }); } diff --git a/frame/elections/src/lib.rs b/frame/elections/src/lib.rs index 1453e2f0fd9fc..1d9e6825bf5a7 100644 --- a/frame/elections/src/lib.rs +++ b/frame/elections/src/lib.rs @@ -15,6 +15,13 @@ // See the License for the specific language governing permissions and // limitations under the License. +//! # WARNING: NOT ACTIVELY MAINTAINED +//! +//! This pallet is currently not maintained and should not be used in production until further +//! notice. +//! +//! --- +//! //! 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 @@ -706,7 +713,7 @@ decl_event!( BadReaperSlashed(AccountId), /// A tally (for approval votes of [seats]) has started. TallyStarted(u32), - /// A tally (for approval votes of seat(s)) has ended (with one or more new members). + /// A tally (for approval votes of seat(s)) has ended (with one or more new members). /// [incoming, outgoing] TallyFinalized(Vec, Vec), } diff --git a/frame/proxy/src/lib.rs b/frame/proxy/src/lib.rs index 5a852ea9f5314..519bb0e6ee7dc 100644 --- a/frame/proxy/src/lib.rs +++ b/frame/proxy/src/lib.rs @@ -245,7 +245,8 @@ decl_module! { /// Dispatch the given `call` from an account that the sender is authorised for through /// `add_proxy`. /// - /// Removes any corresponding announcement(s). + /// This can only be used for proxies that are set with no delay. Removes any corresponding + /// announcement(s). /// /// The dispatch origin for this call must be _Signed_. /// From 6bd6221c2718cf329b2fa78b8cef998ef8893d84 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Mon, 7 Sep 2020 16:46:41 +0200 Subject: [PATCH 02/69] remove TODO --- bin/node/runtime/src/lib.rs | 3 ++- frame/elections-phragmen/src/lib.rs | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 6ecfd3373ab70..42b473dffb3b8 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -240,7 +240,8 @@ impl InstanceFilter for ProxyType { Call::Vesting(pallet_vesting::Call::vested_transfer(..)) | Call::Indices(pallet_indices::Call::transfer(..)) ), - ProxyType::Governance => matches!(c, + ProxyType::Governance => matches!( + c, Call::Democracy(..) | Call::Council(..) | Call::Society(..) | diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index a30667dd693c7..5e1d48d678cd7 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -490,7 +490,6 @@ decl_module! { if valid { // remove the defunct target while unreserving their bond. Self::do_remove_voter(&target, true); - // TODO: probably refund or be more kind to the poor origin. } Self::deposit_event(RawEvent::VoterReported(target, reporter, valid)); From 57de14ea00fddbb4b8c454d558f4a23b59249742 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Tue, 8 Sep 2020 09:33:36 +0200 Subject: [PATCH 03/69] Update bond to be per-vote --- bin/node/runtime/src/lib.rs | 5 +- frame/elections-phragmen/src/lib.rs | 116 ++++++++++++++++++++++++---- 2 files changed, 107 insertions(+), 14 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 42b473dffb3b8..350644d2771d7 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -532,6 +532,8 @@ impl pallet_collective::Trait for Runtime { parameter_types! { pub const CandidacyBond: Balance = 10 * DOLLARS; + pub const VotingBondBase: Balance = 1 * DOLLARS; + pub const VotingBondFactor: Balance = 50 * CENTS; // per 32 bytes on-chain pub const VotingBond: Balance = 1 * DOLLARS; pub const TermDuration: BlockNumber = 7 * DAYS; pub const DesiredMembers: u32 = 13; @@ -552,7 +554,8 @@ impl pallet_elections_phragmen::Trait for Runtime { type InitializeMembers = Council; type CurrencyToVote = CurrencyToVoteHandler; type CandidacyBond = CandidacyBond; - type VotingBond = VotingBond; + type VotingBondBase = VotingBondBase; + type VotingBondFactor = VotingBondFactor; type LoserCandidate = (); type BadReport = (); type KickedMember = (); diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index 5e1d48d678cd7..7a80c9978799d 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -84,7 +84,7 @@ #![cfg_attr(not(feature = "std"), no_std)] use codec::{Encode, Decode}; -use sp_std::prelude::*; +use sp_std::{prelude::*, cmp::Ordering}; use sp_runtime::{ DispatchError, RuntimeDebug, Perbill, traits::{Zero, StaticLookup, Convert, Saturating}, @@ -198,7 +198,10 @@ pub trait Trait: frame_system::Trait { /// /// This should be sensibly high to economically ensure the pallet cannot be attacked by /// creating a gigantic number of votes. - type VotingBond: Get>; + type VotingBondBase: Get>; + + /// The amount of bond that need to be locked for each vote (32 bytes) + type VotingBondFactor: Get>; /// Handler for the unbalanced reduction when a candidate has lost (and is not a runner-up) type LoserCandidate: OnUnbalanced>; @@ -328,7 +331,8 @@ decl_module! { fn deposit_event() = default; const CandidacyBond: BalanceOf = T::CandidacyBond::get(); - const VotingBond: BalanceOf = T::VotingBond::get(); + const VotingBondBase: BalanceOf = T::VotingBondBase::get(); + const VotingBondFactor: BalanceOf = T::VotingBondFactor::get(); const DesiredMembers: u32 = T::DesiredMembers::get(); const DesiredRunnersUp: u32 = T::DesiredRunnersUp::get(); const TermDuration: T::BlockNumber = T::TermDuration::get(); @@ -384,10 +388,29 @@ decl_module! { ensure!(value > T::Currency::minimum_balance(), Error::::LowBalance); - // first time voter. Reserve bond. - if !Self::is_voter(&who) { - T::Currency::reserve(&who, T::VotingBond::get()) + // Reserve bond. + let (_, old_votes) = >::get(&who); + if old_votes.is_empty() { + // First time voter. Get full deposit. + T::Currency::reserve(&who, Self::deposit_of(votes.len())) .map_err(|_| Error::::UnableToPayBond)?; + } else { + // Old voter. + match votes.len().cmp(&old_votes.len()) { + Ordering::Greater => { + // Must reserve a bit more. + let to_reserve = Self::deposit_factor_of(votes.len() - old_votes.len()); + T::Currency::reserve(&who, to_reserve) + .map_err(|_| Error::::UnableToPayBond)?; + }, + Ordering::Equal => {}, + Ordering::Less => { + // Must unreserve a bit. + let to_unreserve = Self::deposit_factor_of(old_votes.len() - votes.len()); + let _remainder = T::Currency::unreserve(&who, to_unreserve); + debug_assert!(_remainder.is_zero()); + }, + } } // Amount to be locked up. @@ -727,6 +750,18 @@ decl_event!( ); impl Module { + /// The deposit value of `count` votes. + fn deposit_of(count: usize) -> BalanceOf { + T::VotingBondBase::get().saturating_add( + T::VotingBondFactor::get().saturating_mul((count as u32).into()) + ) + } + + /// Same as [`deposit_of`] but only returns the factor deposit. + fn deposit_factor_of(count: usize) -> BalanceOf { + T::VotingBondFactor::get().saturating_mul((count as u32).into()) + } + /// Attempts to remove a member `who`. If a runner-up exists, it is used as the replacement and /// Ok(true). is returned. /// @@ -843,12 +878,14 @@ impl Module { /// /// DB access: Voting and Lock are always written to, if unreserve, then 1 read and write added. fn do_remove_voter(who: &T::AccountId, unreserve: bool) { + let (_, votes) = >::get(who); // remove storage and lock. >::remove(who); T::Currency::remove_lock(T::ModuleId::get(), who); if unreserve { - T::Currency::unreserve(who, T::VotingBond::get()); + let _remainder = T::Currency::unreserve(who, Self::deposit_of(votes.len())); + debug_assert!(_remainder.is_zero()); } } @@ -1163,15 +1200,21 @@ mod tests { } thread_local! { - static VOTING_BOND: RefCell = RefCell::new(2); + static VOTING_BOND_BASE: RefCell = RefCell::new(2); + static VOTING_BOND_FACTOR: RefCell = RefCell::new(0); static DESIRED_MEMBERS: RefCell = RefCell::new(2); static DESIRED_RUNNERS_UP: RefCell = RefCell::new(2); static TERM_DURATION: RefCell = RefCell::new(5); } - pub struct VotingBond; - impl Get for VotingBond { - fn get() -> u64 { VOTING_BOND.with(|v| *v.borrow()) } + pub struct VotingBondBase; + impl Get for VotingBondBase { + fn get() -> u64 { VOTING_BOND_BASE.with(|v| *v.borrow()) } + } + + pub struct VotingBondFactor; + impl Get for VotingBondFactor { + fn get() -> u64 { VOTING_BOND_FACTOR.with(|v| *v.borrow()) } } pub struct DesiredMembers; @@ -1257,7 +1300,8 @@ mod tests { type ChangeMembers = TestChangeMembers; type InitializeMembers = (); type CandidacyBond = CandidacyBond; - type VotingBond = VotingBond; + type VotingBondBase = VotingBondBase; + type VotingBondFactor = VotingBondFactor; type TermDuration = TermDuration; type DesiredMembers = DesiredMembers; type DesiredRunnersUp = DesiredRunnersUp; @@ -1286,6 +1330,7 @@ mod tests { genesis_members: Vec<(u64, u64)>, balance_factor: u64, voter_bond: u64, + voter_bond_factor: u64, term_duration: u64, desired_runners_up: u32, desired_members: u32, @@ -1297,6 +1342,7 @@ mod tests { genesis_members: vec![], balance_factor: 1, voter_bond: 2, + voter_bond_factor: 0, term_duration: 5, desired_runners_up: 0, desired_members: 2, @@ -1309,6 +1355,10 @@ mod tests { self.voter_bond = fee; self } + pub fn voter_bond_factor(mut self, fee: u64) -> Self { + self.voter_bond_factor = fee; + self + } pub fn desired_runners_up(mut self, count: u32) -> Self { self.desired_runners_up = count; self @@ -1331,7 +1381,8 @@ mod tests { self } fn set_constants(&self) { - VOTING_BOND.with(|v| *v.borrow_mut() = self.voter_bond); + VOTING_BOND_BASE.with(|v| *v.borrow_mut() = self.voter_bond); + VOTING_BOND_FACTOR.with(|v| *v.borrow_mut() = self.voter_bond_factor); TERM_DURATION.with(|v| *v.borrow_mut() = self.term_duration); DESIRED_RUNNERS_UP.with(|v| *v.borrow_mut() = self.desired_runners_up); DESIRED_MEMBERS.with(|m| *m.borrow_mut() = self.desired_members); @@ -1718,6 +1769,45 @@ mod tests { }); } + #[test] + fn voting_reserves_per_bond_per_vote() { + ExtBuilder::default().voter_bond_factor(1).build_and_execute(|| { + assert_eq!(balances(&2), (20, 0)); + + assert_ok!(submit_candidacy(Origin::signed(5))); + assert_ok!(submit_candidacy(Origin::signed(4))); + + // initial vote. + assert_ok!(vote(Origin::signed(2), vec![5], 10)); + + // 2 + 1 + assert_eq!(balances(&2), (17, 3)); + assert_eq!(has_lock(&2), 10); + assert_eq!(Elections::locked_stake_of(&2), 10); + + // can update; different stake; different lock and reserve. + assert_ok!(vote(Origin::signed(2), vec![5, 4], 15)); + // 2 + 2 + assert_eq!(balances(&2), (16, 4)); + assert_eq!(has_lock(&2), 15); + assert_eq!(Elections::locked_stake_of(&2), 15); + + // stay at two votes with different stake. + assert_ok!(vote(Origin::signed(2), vec![5, 3], 18)); + // 2 + 2 + assert_eq!(balances(&2), (16, 4)); + assert_eq!(has_lock(&2), 18); + assert_eq!(Elections::locked_stake_of(&2), 18); + + // back to 1 vote. + assert_ok!(vote(Origin::signed(2), vec![4], 12)); + // 2 + 1 + assert_eq!(balances(&2), (17, 3)); + assert_eq!(has_lock(&2), 12); + assert_eq!(Elections::locked_stake_of(&2), 12); + }); + } + #[test] fn cannot_vote_for_no_candidate() { ExtBuilder::default().build_and_execute(|| { From 684802e1ee7fde3e3c55144d68791d68226ad7f5 Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Wed, 9 Sep 2020 13:28:29 +0200 Subject: [PATCH 04/69] Update frame/elections-phragmen/src/lib.rs --- frame/elections-phragmen/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index 7a80c9978799d..3a97e62d2dec6 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -200,7 +200,7 @@ pub trait Trait: frame_system::Trait { /// creating a gigantic number of votes. type VotingBondBase: Get>; - /// The amount of bond that need to be locked for each vote (32 bytes) + /// The amount of bond that need to be locked for each vote (32 bytes). type VotingBondFactor: Get>; /// Handler for the unbalanced reduction when a candidate has lost (and is not a runner-up) From 70852fac0b55ec1440a10cba6368d8b3cb3966f0 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 9 Sep 2020 13:29:45 +0200 Subject: [PATCH 05/69] Fix benchmakrs --- frame/elections-phragmen/src/benchmarking.rs | 151 +++++++++++-------- frame/elections-phragmen/src/lib.rs | 4 +- 2 files changed, 91 insertions(+), 64 deletions(-) diff --git a/frame/elections-phragmen/src/benchmarking.rs b/frame/elections-phragmen/src/benchmarking.rs index 6de9ad57e244f..d39c974366583 100644 --- a/frame/elections-phragmen/src/benchmarking.rs +++ b/frame/elections-phragmen/src/benchmarking.rs @@ -28,10 +28,18 @@ use crate::Module as Elections; const BALANCE_FACTOR: u32 = 250; const MAX_VOTERS: u32 = 500; -const MAX_CANDIDATES: u32 = 100; +const MAX_CANDIDATES: u32 = 200; type Lookup = <::Lookup as StaticLookup>::Source; +macro_rules! whitelist { + ($acc:ident) => { + frame_benchmarking::benchmarking::add_to_whitelist( + frame_system::Account::::hashed_key_for(&$acc).into() + ); + }; +} + /// grab new account with infinite balance. fn endowed_account(name: &'static str, index: u32) -> T::AccountId { let account: T::AccountId = account(name, index, 0); @@ -40,6 +48,7 @@ fn endowed_account(name: &'static str, index: u32) -> T::AccountId { // important to increase the total issuance since T::CurrencyToVote will need it to be sane for // phragmen to work. T::Currency::issue(amount); + account } @@ -131,7 +140,7 @@ fn distribute_voters(mut all_candidates: Vec, num_voters /// Fill the seats of members and runners-up up until `m`. Note that this might include either only /// members, or members and runners-up. fn fill_seats_up_to(m: u32) -> Result, &'static str> { - let candidates = submit_candidates_with_self_vote::(m, "fill_seats_up_to")?; + let _ = submit_candidates_with_self_vote::(m, "fill_seats_up_to")?; assert_eq!(>::candidates().len() as u32, m, "wrong number of candidates."); >::do_phragmen(); assert_eq!(>::candidates().len(), 0, "some candidates remaining."); @@ -140,7 +149,13 @@ fn fill_seats_up_to(m: u32) -> Result, &'static str> m as usize, "wrong number of members and runners-up", ); - Ok(candidates) + Ok( + >::members() + .into_iter() + .map(|(x, _)| x) + .chain(>::runners_up().into_iter().map(|(x, _)| x)) + .collect() + ) } /// removes all the storage items to reverse any genesis state. @@ -152,50 +167,46 @@ fn clean() { } benchmarks! { - _ { - // User account seed - let u in 0 .. 1000 => (); - } + _ {} // -- Signed ones vote { - let u in ...; - // we fix the number of voted candidates to max - let v = MAXIMUM_VOTE; + let v in 1 .. (MAXIMUM_VOTE as u32); clean::(); // create a bunch of candidates. - let all_candidates = submit_candidates::(MAXIMUM_VOTE as u32, "candidates")?; + let all_candidates = submit_candidates::(v, "candidates")?; - let caller = endowed_account::("caller", u); + let caller = endowed_account::("caller", 0); let stake = default_stake::(BALANCE_FACTOR); // vote for all of them. - let votes = all_candidates.into_iter().take(v).collect(); + let votes = all_candidates; + whitelist!(caller); }: _(RawOrigin::Signed(caller), votes, stake) vote_update { - let u in ...; - // we fix the number of voted candidates to max - let v = MAXIMUM_VOTE; + let v in 1 .. (MAXIMUM_VOTE as u32); clean::(); // create a bunch of candidates. - let all_candidates = submit_candidates::(MAXIMUM_VOTE as u32, "candidates")?; + let all_candidates = submit_candidates::(v, "candidates")?; - let caller = endowed_account::("caller", u); + let caller = endowed_account::("caller", 0); let stake = default_stake::(BALANCE_FACTOR); // original votes. - let mut votes = all_candidates.into_iter().take(v).collect::>(); + let mut votes = all_candidates; submit_voter::(caller.clone(), votes.clone(), stake)?; + // new votes. votes.rotate_left(1); + + whitelist!(caller); }: vote(RawOrigin::Signed(caller), votes, stake) remove_voter { - let u in ...; // we fix the number of voted candidates to max let v = MAXIMUM_VOTE as u32; clean::(); @@ -203,11 +214,12 @@ benchmarks! { // create a bunch of candidates. let all_candidates = submit_candidates::(v, "candidates")?; - let caller = endowed_account::("caller", u); + let caller = endowed_account::("caller", 0); let stake = default_stake::(BALANCE_FACTOR); submit_voter::(caller.clone(), all_candidates, stake)?; + whitelist!(caller); }: _(RawOrigin::Signed(caller)) report_defunct_voter_correct { @@ -217,11 +229,11 @@ benchmarks! { // number of candidates that the reported voter voted for. The worse case of search here is // basically `c * v`. let v in 1 .. (MAXIMUM_VOTE as u32); - // we fix the number of members to when members and runners-up to the desired. We'll be in + // we fix the number of members to the number of desired members and runners-up. We'll be in // this state almost always. let m = T::DesiredMembers::get() + T::DesiredRunnersUp::get(); - clean::(); + clean::(); let stake = default_stake::(BALANCE_FACTOR); // create m members and runners combined. @@ -231,14 +243,8 @@ benchmarks! { let bailing_candidates = submit_candidates::(v, "bailing_candidates")?; let all_candidates = submit_candidates::(c, "all_candidates")?; - // account 1 is the reporter and it doesn't matter how many it votes. But it has to be a - // voter. + // account 1 is the reporter and must be whitelisted. let account_1 = endowed_account::("caller", 0); - submit_voter::( - account_1.clone(), - all_candidates.iter().take(1).cloned().collect(), - stake, - )?; // account 2 votes for all of the mentioned candidates. let account_2 = endowed_account::("caller_2", 1); @@ -248,7 +254,9 @@ benchmarks! { stake, )?; - // all the bailers go away. + // all the bailers go away. TODO: we can simplify this. There's no need to cerate all these + // candidates and remove them. The defunct voter can just vote for random accounts as long + // as there are enough members (potential candidates). bailing_candidates.into_iter().for_each(|b| { let count = candidate_count::(); assert!(>::renounce_candidacy( @@ -256,10 +264,13 @@ benchmarks! { Renouncing::Candidate(count), ).is_ok()); }); - let defunct = defunct_for::(account_2.clone()); - }: report_defunct_voter(RawOrigin::Signed(account_1.clone()), defunct) + + let defunct_info = defunct_for::(account_2.clone()); + whitelist!(account_1); + + assert!(>::is_voter(&account_2)); + }: report_defunct_voter(RawOrigin::Signed(account_1.clone()), defunct_info) verify { - assert!(>::is_voter(&account_1)); assert!(!>::is_voter(&account_2)); #[cfg(test)] { @@ -276,7 +287,7 @@ benchmarks! { // number of candidates that the reported voter voted for. The worse case of search here is // basically `c * v`. let v in 1 .. (MAXIMUM_VOTE as u32); - // we fix the number of members to when members and runners-up to the desired. We'll be in + // we fix the number of members to the number of desired members and runners-up. We'll be in // this state almost always. let m = T::DesiredMembers::get() + T::DesiredRunnersUp::get(); @@ -289,18 +300,14 @@ benchmarks! { // create a bunch of candidates as well. let all_candidates = submit_candidates::(c, "candidates")?; - // account 1 is the reporter and it doesn't matter how many it votes. + // account 1 is the reporter and need to be whitelisted. let account_1 = endowed_account::("caller", 0); - submit_voter::( - account_1.clone(), - all_candidates.iter().take(1).cloned().collect(), - stake, - )?; // account 2 votes for a bunch of crap, and finally a correct candidate. let account_2 = endowed_account::("caller_2", 1); - let mut invalid: Vec = - (0..(v-1)).map(|seed| account::("invalid", 0, seed).clone()).collect(); + let mut invalid: Vec = (0..(v-1)) + .map(|seed| account::("invalid", 0, seed).clone()) + .collect(); invalid.push(all_candidates.last().unwrap().clone()); submit_voter::( account_2.clone(), @@ -308,11 +315,11 @@ benchmarks! { stake, )?; - let defunct = defunct_for::(account_2.clone()); - // no one bails out. account_1 is slashed and removed as voter now. - }: report_defunct_voter(RawOrigin::Signed(account_1.clone()), defunct) + let defunct_info = defunct_for::(account_2.clone()); + whitelist!(account_1); + }: report_defunct_voter(RawOrigin::Signed(account_1.clone()), defunct_info) verify { - assert!(!>::is_voter(&account_1)); + // account 2 is still a voter. assert!(>::is_voter(&account_2)); #[cfg(test)] { @@ -325,7 +332,7 @@ benchmarks! { submit_candidacy { // number of already existing candidates. let c in 1 .. MAX_CANDIDATES; - // we fix the number of members to when members and runners-up to the desired. We'll be in + // we fix the number of members to the number of desired members and runners-up. We'll be in // this state almost always. let m = T::DesiredMembers::get() + T::DesiredRunnersUp::get(); @@ -340,6 +347,7 @@ benchmarks! { // we assume worse case that: extrinsic is successful and candidate is not duplicate. let candidate_account = endowed_account::("caller", 0); + whitelist!(candidate_account); }: _(RawOrigin::Signed(candidate_account.clone()), candidate_count::()) verify { #[cfg(test)] @@ -355,7 +363,7 @@ benchmarks! { // limited by the runtime bound, nonetheless we fill them by `m`. // number of already existing candidates. let c in 1 .. MAX_CANDIDATES; - // we fix the number of members to when members and runners-up to the desired. We'll be in + // we fix the number of members to the number of desired members and runners-up. We'll be in // this state almost always. let m = T::DesiredMembers::get() + T::DesiredRunnersUp::get(); @@ -367,6 +375,7 @@ benchmarks! { let bailing = all_candidates[0].clone(); // Should be ("caller", 0) let count = candidate_count::(); + whitelist!(bailing); }: renounce_candidacy(RawOrigin::Signed(bailing), Renouncing::Candidate(count)) verify { #[cfg(test)] @@ -377,11 +386,10 @@ benchmarks! { } } - renounce_candidacy_member_runner_up { + renounce_candidacy_members { // removing members and runners will be cheaper than a candidate. // we fix the number of members to when members and runners-up to the desired. We'll be in // this state almost always. - let u in ...; let m = T::DesiredMembers::get() + T::DesiredRunnersUp::get(); clean::(); @@ -389,14 +397,34 @@ benchmarks! { let members_and_runners_up = fill_seats_up_to::(m)?; let bailing = members_and_runners_up[0].clone(); - let renouncing = if >::is_member(&bailing) { - Renouncing::Member - } else if >::is_runner_up(&bailing) { - Renouncing::RunnerUp - } else { - panic!("Bailing must be a member or runner-up for this bench to be sane."); - }; - }: renounce_candidacy(RawOrigin::Signed(bailing.clone()), renouncing) + assert!(>::is_member(&bailing)); + + whitelist!(bailing); + }: renounce_candidacy(RawOrigin::Signed(bailing.clone()), Renouncing::Member) + verify { + #[cfg(test)] + { + // reset members in between benchmark tests. + use crate::tests::MEMBERS; + MEMBERS.with(|m| *m.borrow_mut() = vec![]); + } + } + + renounce_candidacy_runners_up { + // removing members and runners will be cheaper than a candidate. + // we fix the number of members to when members and runners-up to the desired. We'll be in + // this state almost always. + let m = T::DesiredMembers::get() + T::DesiredRunnersUp::get(); + clean::(); + + // create m members and runners combined. + let members_and_runners_up = fill_seats_up_to::(m)?; + + let bailing = members_and_runners_up[T::DesiredMembers::get() as usize + 1].clone(); + assert!(>::is_runner_up(&bailing)); + + whitelist!(bailing); + }: renounce_candidacy(RawOrigin::Signed(bailing.clone()), Renouncing::RunnerUp) verify { #[cfg(test)] { @@ -407,6 +435,7 @@ benchmarks! { } // -- Root ones + #[extra] // this calls into phragmen and consumes a full block for now. remove_member_without_replacement { // worse case is when we remove a member and we have no runner as a replacement. This // triggers phragmen again. The only parameter is how many candidates will compete for the @@ -440,7 +469,6 @@ benchmarks! { remove_member_with_replacement { // easy case. We have a runner up. Nothing will have that much of an impact. m will be // number of members and runners. There is always at least one runner. - let u in ...; let m = T::DesiredMembers::get() + T::DesiredRunnersUp::get(); clean::(); @@ -461,7 +489,6 @@ benchmarks! { remove_member_wrong_refund { // The root call by mistake indicated that this will have no replacement, while it has! // this has now consumed a lot of weight and need to refund. - let u in ...; let m = T::DesiredMembers::get() + T::DesiredRunnersUp::get(); clean::(); @@ -484,6 +511,7 @@ benchmarks! { } } + #[extra] on_initialize { // if n % TermDuration is zero, then we run phragmen. The weight function must and should // check this as it is cheap to do so. TermDuration is not a storage item, it is a constant @@ -514,6 +542,7 @@ benchmarks! { } } + #[extra] phragmen { // This is just to focus on phragmen in the context of this module. We always select 20 // members, this is hard-coded in the runtime and cannot be trivially changed at this stage. diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index 7a80c9978799d..2afe2bb5c0611 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -357,6 +357,7 @@ decl_module! { /// State reads: /// - Candidates.len() + Members.len() + RunnersUp.len() /// - Voting (is_voter) + /// - Lock /// - [AccountBalance(who) (unreserve + total_balance)] /// State writes: /// - Voting @@ -530,7 +531,6 @@ decl_module! { /// Base weight = 33.33 µs /// Complexity of candidate_count: 0.375 µs /// State reads: - /// - Candidates.len() /// - Candidates /// - Members /// - RunnersUp @@ -603,8 +603,6 @@ decl_module! { /// State writes: /// - RunnersUp (remove_and_replace_member), /// - [AccountData(who) (unreserve)] - /// - /// Weight note: The call into changeMembers need to be accounted for. /// #[weight = match *renouncing { Renouncing::Candidate(count) => { From 77caa5cb4d39f4d8ef13b212e211e9b4f11fea9a Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 9 Sep 2020 15:32:35 +0200 Subject: [PATCH 06/69] Fix weight as well. --- frame/elections-phragmen/src/benchmarking.rs | 12 +- .../elections-phragmen/src/default_weights.rs | 70 ++++++++++++ frame/elections-phragmen/src/lib.rs | 104 +++++++----------- 3 files changed, 120 insertions(+), 66 deletions(-) create mode 100644 frame/elections-phragmen/src/default_weights.rs diff --git a/frame/elections-phragmen/src/benchmarking.rs b/frame/elections-phragmen/src/benchmarking.rs index d39c974366583..0f8b08327a544 100644 --- a/frame/elections-phragmen/src/benchmarking.rs +++ b/frame/elections-phragmen/src/benchmarking.rs @@ -103,7 +103,9 @@ fn submit_candidates_with_self_vote(c: u32, prefix: &'static str) let candidates = submit_candidates::(c, prefix)?; let stake = default_stake::(BALANCE_FACTOR); let _ = candidates.iter().map(|c| - submit_voter::(c.clone(), vec![c.clone()], stake) + // TODO: unlike DispatchInfo which is just a result, this PostDispatchInfoWithBlah cannot be + // collected, thus we just unwrap to make sure our setup is okay. + submit_voter::(c.clone(), vec![c.clone()], stake).map(|_| ()) ).collect::>()?; Ok(candidates) } @@ -111,7 +113,7 @@ fn submit_candidates_with_self_vote(c: u32, prefix: &'static str) /// Submit one voter. fn submit_voter(caller: T::AccountId, votes: Vec, stake: BalanceOf) - -> Result<(), &'static str> + -> Result { >::vote(RawOrigin::Signed(caller).into(), votes, stake) .map_err(|_| "failed to submit vote") @@ -607,7 +609,11 @@ mod tests { }); ExtBuilder::default().desired_members(13).desired_runners_up(7).build_and_execute(|| { - assert_ok!(test_benchmark_renounce_candidacy_member_runner_up::()); + assert_ok!(test_benchmark_renounce_candidacy_runners_up::()); + }); + + ExtBuilder::default().desired_members(13).desired_runners_up(7).build_and_execute(|| { + assert_ok!(test_benchmark_renounce_candidacy_members::()); }); ExtBuilder::default().desired_members(13).desired_runners_up(7).build_and_execute(|| { diff --git a/frame/elections-phragmen/src/default_weights.rs b/frame/elections-phragmen/src/default_weights.rs new file mode 100644 index 0000000000000..ff18dde5f9ec7 --- /dev/null +++ b/frame/elections-phragmen/src/default_weights.rs @@ -0,0 +1,70 @@ +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 2.0.0-rc6 + +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::weights::{Weight, constants::RocksDbWeight as DbWeight}; + +impl crate::WeightInfo for () { + fn vote(v: u32, ) -> Weight { + (131640000 as Weight) + .saturating_add((174000 as Weight).saturating_mul(v as Weight)) + .saturating_add(DbWeight::get().reads(5 as Weight)) + .saturating_add(DbWeight::get().writes(2 as Weight)) + } + fn vote_update(v: u32, ) -> Weight { + (82947000 as Weight) + .saturating_add((710000 as Weight).saturating_mul(v as Weight)) + .saturating_add(DbWeight::get().reads(5 as Weight)) + .saturating_add(DbWeight::get().writes(2 as Weight)) + } + fn remove_voter() -> Weight { + (117312000 as Weight) + .saturating_add(DbWeight::get().reads(2 as Weight)) + .saturating_add(DbWeight::get().writes(2 as Weight)) + } + fn report_defunct_voter_correct(c: u32, v: u32, ) -> Weight { + (0 as Weight) + .saturating_add((1649000 as Weight).saturating_mul(c as Weight)) + .saturating_add((35367000 as Weight).saturating_mul(v as Weight)) + .saturating_add(DbWeight::get().reads(6 as Weight)) + .saturating_add(DbWeight::get().writes(3 as Weight)) + } + fn report_defunct_voter_incorrect(c: u32, v: u32, ) -> Weight { + (0 as Weight) + .saturating_add((1683000 as Weight).saturating_mul(c as Weight)) + .saturating_add((35548000 as Weight).saturating_mul(v as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + } + fn submit_candidacy(c: u32, ) -> Weight { + (99786000 as Weight) + .saturating_add((289000 as Weight).saturating_mul(c as Weight)) + .saturating_add(DbWeight::get().reads(3 as Weight)) + .saturating_add(DbWeight::get().writes(1 as Weight)) + } + fn renounce_candidacy_candidate(c: u32, ) -> Weight { + (65481000 as Weight) + .saturating_add((173000 as Weight).saturating_mul(c as Weight)) + .saturating_add(DbWeight::get().reads(1 as Weight)) + .saturating_add(DbWeight::get().writes(1 as Weight)) + } + fn renounce_candidacy_members() -> Weight { + (107379000 as Weight) + .saturating_add(DbWeight::get().reads(3 as Weight)) + .saturating_add(DbWeight::get().writes(4 as Weight)) + } + fn renounce_candidacy_runners_up() -> Weight { + (65798000 as Weight) + .saturating_add(DbWeight::get().reads(1 as Weight)) + .saturating_add(DbWeight::get().writes(1 as Weight)) + } + fn remove_member_with_replacement() -> Weight { + (101751000 as Weight) + .saturating_add(DbWeight::get().reads(4 as Weight)) + .saturating_add(DbWeight::get().writes(5 as Weight)) + } + fn remove_member_wrong_refund() -> Weight { + (11328000 as Weight) + .saturating_add(DbWeight::get().reads(1 as Weight)) + } +} diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index 6f738957bce38..388e300548a61 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -91,7 +91,7 @@ use sp_runtime::{ }; use frame_support::{ decl_storage, decl_event, ensure, decl_module, decl_error, - weights::{Weight, constants::{WEIGHT_PER_MICROS, WEIGHT_PER_NANOS}}, + weights::Weight, storage::{StorageMap, IterableStorageMap}, dispatch::{DispatchResultWithPostInfo, WithPostDispatchInfo}, traits::{ @@ -104,6 +104,7 @@ use sp_npos_elections::{build_support_map, ExtendedBalance, VoteWeight, Election use frame_system::{ensure_signed, ensure_root}; mod benchmarking; +mod default_weights; /// The maximum votes allowed per voter. pub const MAXIMUM_VOTE: usize = 16; @@ -138,35 +139,17 @@ pub struct DefunctVoter { } pub trait WeightInfo { - fn vote(u: u32, ) -> Weight; - fn vote_update(u: u32, ) -> Weight; - fn remove_voter(u: u32, ) -> Weight; + fn vote(v: u32, ) -> Weight; + fn vote_update(v: u32, ) -> Weight; + fn remove_voter() -> Weight; fn report_defunct_voter_correct(c: u32, v: u32, ) -> Weight; fn report_defunct_voter_incorrect(c: u32, v: u32, ) -> Weight; fn submit_candidacy(c: u32, ) -> Weight; fn renounce_candidacy_candidate(c: u32, ) -> Weight; - fn renounce_candidacy_member_runner_up(u: u32, ) -> Weight; - fn remove_member_without_replacement(c: u32, ) -> Weight; - fn remove_member_with_replacement(u: u32, ) -> Weight; - fn remove_member_wrong_refund(u: u32, ) -> Weight; - fn on_initialize(c: u32, ) -> Weight; - fn phragmen(c: u32, v: u32, e: u32, ) -> Weight; -} - -impl WeightInfo for () { - fn vote(_u: u32, ) -> Weight { 1_000_000_000 } - fn vote_update(_u: u32, ) -> Weight { 1_000_000_000 } - fn remove_voter(_u: u32, ) -> Weight { 1_000_000_000 } - fn report_defunct_voter_correct(_c: u32, _v: u32, ) -> Weight { 1_000_000_000 } - fn report_defunct_voter_incorrect(_c: u32, _v: u32, ) -> Weight { 1_000_000_000 } - fn submit_candidacy(_c: u32, ) -> Weight { 1_000_000_000 } - fn renounce_candidacy_candidate(_c: u32, ) -> Weight { 1_000_000_000 } - fn renounce_candidacy_member_runner_up(_u: u32, ) -> Weight { 1_000_000_000 } - fn remove_member_without_replacement(_c: u32, ) -> Weight { 1_000_000_000 } - fn remove_member_with_replacement(_u: u32, ) -> Weight { 1_000_000_000 } - fn remove_member_wrong_refund(_u: u32, ) -> Weight { 1_000_000_000 } - fn on_initialize(_c: u32, ) -> Weight { 1_000_000_000 } - fn phragmen(_c: u32, _v: u32, _e: u32, ) -> Weight { 1_000_000_000 } + fn renounce_candidacy_members() -> Weight; + fn renounce_candidacy_runners_up() -> Weight; + fn remove_member_with_replacement() -> Weight; + fn remove_member_wrong_refund() -> Weight; } pub trait Trait: frame_system::Trait { @@ -364,12 +347,12 @@ decl_module! { /// - Lock /// - [AccountBalance(who) (unreserve -- only when creating a new voter)] /// # - #[weight = 50 * WEIGHT_PER_MICROS + T::DbWeight::get().reads_writes(4, 2)] + #[weight = T::WeightInfo::vote(votes.len() as u32)] fn vote( origin, votes: Vec, #[compact] value: BalanceOf, - ) { + ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; // votes should not be empty and more than `MAXIMUM_VOTE` in any case. @@ -391,10 +374,11 @@ decl_module! { // Reserve bond. let (_, old_votes) = >::get(&who); - if old_votes.is_empty() { + let maybe_refund = if old_votes.is_empty() { // First time voter. Get full deposit. T::Currency::reserve(&who, Self::deposit_of(votes.len())) .map_err(|_| Error::::UnableToPayBond)?; + None } else { // Old voter. match votes.len().cmp(&old_votes.len()) { @@ -412,7 +396,8 @@ decl_module! { debug_assert!(_remainder.is_zero()); }, } - } + Some(T::WeightInfo::vote_update(votes.len() as u32)) + }; // Amount to be locked up. let locked_balance = value.min(T::Currency::total_balance(&who)); @@ -426,6 +411,7 @@ decl_module! { ); Voting::::insert(&who, (locked_balance, votes)); + Ok(maybe_refund.into()) } /// Remove `origin` as a voter. This removes the lock and returns the bond. @@ -441,7 +427,7 @@ decl_module! { /// - Locks /// - [AccountData(who)] /// # - #[weight = 35 * WEIGHT_PER_MICROS + T::DbWeight::get().reads_writes(1, 2)] + #[weight = T::WeightInfo::remove_voter()] fn remove_voter(origin) { let who = ensure_signed(origin)?; ensure!(Self::is_voter(&who), Error::::MustBeVoter); @@ -475,17 +461,16 @@ decl_module! { /// - Lock(reporter || target) /// - [AccountBalance(reporter)] + AccountBalance(target) /// - Voting(reporter || target) - /// Note: the db access is worse with respect to db, which is when the report is correct. + /// Note: We charge the case if the report is correct, if incorrect, we refund. /// # - #[weight = - Weight::from(defunct.candidate_count).saturating_mul(2 * WEIGHT_PER_MICROS) - .saturating_add(Weight::from(defunct.vote_count).saturating_mul(19 * WEIGHT_PER_MICROS)) - .saturating_add(T::DbWeight::get().reads_writes(6, 3)) - ] + #[weight = T::WeightInfo::report_defunct_voter_correct( + defunct.candidate_count, + defunct.vote_count, + )] fn report_defunct_voter( origin, defunct: DefunctVoter<::Source>, - ) { + ) -> DispatchResultWithPostInfo { let reporter = ensure_signed(origin)?; let target = T::Lookup::lookup(defunct.who)?; @@ -511,12 +496,19 @@ decl_module! { ); let valid = Self::is_defunct_voter(&votes); - if valid { + let maybe_refund = if valid { // remove the defunct target while unreserving their bond. Self::do_remove_voter(&target, true); - } + None + } else { + Some(T::WeightInfo::report_defunct_voter_incorrect( + defunct.candidate_count, + defunct.vote_count, + )) + }; Self::deposit_event(RawEvent::VoterReported(target, reporter, valid)); + Ok(maybe_refund.into()) } /// Submit oneself for candidacy. @@ -539,11 +531,7 @@ decl_module! { /// - [AccountBalance(who)] /// - Candidates /// # - #[weight = - (35 * WEIGHT_PER_MICROS) - .saturating_add(Weight::from(*candidate_count).saturating_mul(375 * WEIGHT_PER_NANOS)) - .saturating_add(T::DbWeight::get().reads_writes(4, 1)) - ] + #[weight = T::WeightInfo::submit_candidacy(*candidate_count)] fn submit_candidacy(origin, #[compact] candidate_count: u32) { let who = ensure_signed(origin)?; @@ -605,19 +593,9 @@ decl_module! { /// - [AccountData(who) (unreserve)] /// #[weight = match *renouncing { - Renouncing::Candidate(count) => { - (18 * WEIGHT_PER_MICROS) - .saturating_add(Weight::from(count).saturating_mul(235 * WEIGHT_PER_NANOS)) - .saturating_add(T::DbWeight::get().reads_writes(1, 1)) - }, - Renouncing::Member => { - 46 * WEIGHT_PER_MICROS + - T::DbWeight::get().reads_writes(2, 2) - }, - Renouncing::RunnerUp => { - 46 * WEIGHT_PER_MICROS + - T::DbWeight::get().reads_writes(1, 1) - } + Renouncing::Candidate(count) => T::WeightInfo::renounce_candidacy_candidate(count), + Renouncing::Member => T::WeightInfo::renounce_candidacy_members(), + Renouncing::RunnerUp => T::WeightInfo::renounce_candidacy_runners_up(), }] fn renounce_candidacy(origin, renouncing: Renouncing) { let who = ensure_signed(origin)?; @@ -678,7 +656,7 @@ decl_module! { /// Else, since this is a root call and will go into phragmen, we assume full block for now. /// # #[weight = if *has_replacement { - 50 * WEIGHT_PER_MICROS + T::DbWeight::get().reads_writes(3, 2) + T::WeightInfo::remove_member_with_replacement() } else { T::MaximumBlockWeight::get() }] @@ -696,7 +674,7 @@ decl_module! { return Err(Error::::InvalidReplacement.with_weight( // refund. The weight value comes from a benchmark which is special to this. // 5.751 µs - 6 * WEIGHT_PER_MICROS + T::DbWeight::get().reads_writes(1, 0) + T::WeightInfo::remove_member_wrong_refund() )); } // else, prediction was correct. @@ -1473,7 +1451,7 @@ mod tests { Elections::submit_candidacy(origin, Elections::candidates().len() as u32) } - fn vote(origin: Origin, votes: Vec, stake: u64) -> DispatchResult { + fn vote(origin: Origin, votes: Vec, stake: u64) -> DispatchResultWithPostInfo { // historical note: helper function was created in a period of time in which the API of vote // call was changing. Currently it is a wrapper for the original call and does not do much. // Nonetheless, totally harmless. @@ -2523,7 +2501,7 @@ mod tests { assert_err_with_weight!( Elections::remove_member(Origin::root(), 4, true), Error::::InvalidReplacement, - Some(6000000), + Some(36328000), ); }); @@ -2545,7 +2523,7 @@ mod tests { assert_err_with_weight!( Elections::remove_member(Origin::root(), 4, false), Error::::InvalidReplacement, - Some(6000000) // only thing that matters for now is that it is NOT the full block. + Some(36328000) // only thing that matters for now is that it is NOT the full block. ); }); } From 06a0aa030a386078f61b6e83e0a254d85b0011b7 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 9 Sep 2020 15:34:19 +0200 Subject: [PATCH 07/69] Add license --- frame/elections-phragmen/src/default_weights.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/frame/elections-phragmen/src/default_weights.rs b/frame/elections-phragmen/src/default_weights.rs index ff18dde5f9ec7..3f072d44e2355 100644 --- a/frame/elections-phragmen/src/default_weights.rs +++ b/frame/elections-phragmen/src/default_weights.rs @@ -1,3 +1,20 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2020 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 2.0.0-rc6 #![allow(unused_parens)] From 99430423a9b495bc59176f4cd309a78a4dc30fbc Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 9 Sep 2020 17:11:10 +0200 Subject: [PATCH 08/69] =?UTF-8?q?Make=20weight=20interpreted=20wasm!=20?= =?UTF-8?q?=F0=9F=A4=A6=F0=9F=8F=BB=E2=80=8D=E2=99=82=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../elections-phragmen/src/default_weights.rs | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/frame/elections-phragmen/src/default_weights.rs b/frame/elections-phragmen/src/default_weights.rs index 3f072d44e2355..9a718b973cc50 100644 --- a/frame/elections-phragmen/src/default_weights.rs +++ b/frame/elections-phragmen/src/default_weights.rs @@ -24,64 +24,64 @@ use frame_support::weights::{Weight, constants::RocksDbWeight as DbWeight}; impl crate::WeightInfo for () { fn vote(v: u32, ) -> Weight { - (131640000 as Weight) - .saturating_add((174000 as Weight).saturating_mul(v as Weight)) + (35890000 as Weight) + .saturating_add((168000 as Weight).saturating_mul(v as Weight)) .saturating_add(DbWeight::get().reads(5 as Weight)) .saturating_add(DbWeight::get().writes(2 as Weight)) } fn vote_update(v: u32, ) -> Weight { - (82947000 as Weight) - .saturating_add((710000 as Weight).saturating_mul(v as Weight)) + (23406000 as Weight) + .saturating_add((312000 as Weight).saturating_mul(v as Weight)) .saturating_add(DbWeight::get().reads(5 as Weight)) .saturating_add(DbWeight::get().writes(2 as Weight)) } fn remove_voter() -> Weight { - (117312000 as Weight) + (35814000 as Weight) .saturating_add(DbWeight::get().reads(2 as Weight)) .saturating_add(DbWeight::get().writes(2 as Weight)) } fn report_defunct_voter_correct(c: u32, v: u32, ) -> Weight { (0 as Weight) - .saturating_add((1649000 as Weight).saturating_mul(c as Weight)) - .saturating_add((35367000 as Weight).saturating_mul(v as Weight)) + .saturating_add((1917000 as Weight).saturating_mul(c as Weight)) + .saturating_add((28128000 as Weight).saturating_mul(v as Weight)) .saturating_add(DbWeight::get().reads(6 as Weight)) .saturating_add(DbWeight::get().writes(3 as Weight)) } fn report_defunct_voter_incorrect(c: u32, v: u32, ) -> Weight { (0 as Weight) - .saturating_add((1683000 as Weight).saturating_mul(c as Weight)) - .saturating_add((35548000 as Weight).saturating_mul(v as Weight)) + .saturating_add((1946000 as Weight).saturating_mul(c as Weight)) + .saturating_add((28514000 as Weight).saturating_mul(v as Weight)) .saturating_add(DbWeight::get().reads(4 as Weight)) } fn submit_candidacy(c: u32, ) -> Weight { - (99786000 as Weight) - .saturating_add((289000 as Weight).saturating_mul(c as Weight)) + (41826000 as Weight) + .saturating_add((235000 as Weight).saturating_mul(c as Weight)) .saturating_add(DbWeight::get().reads(3 as Weight)) .saturating_add(DbWeight::get().writes(1 as Weight)) } fn renounce_candidacy_candidate(c: u32, ) -> Weight { - (65481000 as Weight) - .saturating_add((173000 as Weight).saturating_mul(c as Weight)) + (27830000 as Weight) + .saturating_add((181000 as Weight).saturating_mul(c as Weight)) .saturating_add(DbWeight::get().reads(1 as Weight)) .saturating_add(DbWeight::get().writes(1 as Weight)) } fn renounce_candidacy_members() -> Weight { - (107379000 as Weight) + (41689000 as Weight) .saturating_add(DbWeight::get().reads(3 as Weight)) .saturating_add(DbWeight::get().writes(4 as Weight)) } fn renounce_candidacy_runners_up() -> Weight { - (65798000 as Weight) + (27689000 as Weight) .saturating_add(DbWeight::get().reads(1 as Weight)) .saturating_add(DbWeight::get().writes(1 as Weight)) } fn remove_member_with_replacement() -> Weight { - (101751000 as Weight) + (41510000 as Weight) .saturating_add(DbWeight::get().reads(4 as Weight)) .saturating_add(DbWeight::get().writes(5 as Weight)) } fn remove_member_wrong_refund() -> Weight { - (11328000 as Weight) + (5159000 as Weight) .saturating_add(DbWeight::get().reads(1 as Weight)) } } From f9ea73a9c9ddf45c9ddecc43095a3fbd254589b3 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Mon, 14 Sep 2020 14:11:57 +0200 Subject: [PATCH 09/69] Remove a bunch of TODOs --- frame/elections-phragmen/src/benchmarking.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/frame/elections-phragmen/src/benchmarking.rs b/frame/elections-phragmen/src/benchmarking.rs index 0f8b08327a544..5a1ff22d24185 100644 --- a/frame/elections-phragmen/src/benchmarking.rs +++ b/frame/elections-phragmen/src/benchmarking.rs @@ -103,8 +103,6 @@ fn submit_candidates_with_self_vote(c: u32, prefix: &'static str) let candidates = submit_candidates::(c, prefix)?; let stake = default_stake::(BALANCE_FACTOR); let _ = candidates.iter().map(|c| - // TODO: unlike DispatchInfo which is just a result, this PostDispatchInfoWithBlah cannot be - // collected, thus we just unwrap to make sure our setup is okay. submit_voter::(c.clone(), vec![c.clone()], stake).map(|_| ()) ).collect::>()?; Ok(candidates) @@ -256,7 +254,7 @@ benchmarks! { stake, )?; - // all the bailers go away. TODO: we can simplify this. There's no need to cerate all these + // all the bailers go away. NOTE: we can simplify this. There's no need to cerate all these // candidates and remove them. The defunct voter can just vote for random accounts as long // as there are enough members (potential candidates). bailing_candidates.into_iter().for_each(|b| { From 24c416bb7f6eb75f429a4522d2d158d588358ec5 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Mon, 14 Sep 2020 14:55:15 +0200 Subject: [PATCH 10/69] Add migration --- bin/node/runtime/src/lib.rs | 2 + frame/elections-phragmen/src/lib.rs | 75 +++++++++++++++++++++++++++-- 2 files changed, 74 insertions(+), 3 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 350644d2771d7..b86d245d4275b 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -532,6 +532,7 @@ impl pallet_collective::Trait for Runtime { parameter_types! { pub const CandidacyBond: Balance = 10 * DOLLARS; + pub const LegacyVotingBond: Balance = 1 * DOLLARS; pub const VotingBondBase: Balance = 1 * DOLLARS; pub const VotingBondFactor: Balance = 50 * CENTS; // per 32 bytes on-chain pub const VotingBond: Balance = 1 * DOLLARS; @@ -554,6 +555,7 @@ impl pallet_elections_phragmen::Trait for Runtime { type InitializeMembers = Council; type CurrencyToVote = CurrencyToVoteHandler; type CandidacyBond = CandidacyBond; + type LegacyBondBase = LegacyVotingBond; type VotingBondBase = VotingBondBase; type VotingBondFactor = VotingBondFactor; type LoserCandidate = (); diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index febfb4736ea01..5a19c80e2b418 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -177,6 +177,13 @@ pub trait Trait: frame_system::Trait { /// How much should be locked up in order to submit one's candidacy. type CandidacyBond: Get>; + /// DEPRECATED. + /// + /// The old voting bond. Only used and needed for migrating to the new model. + /// // TODO: remove with all side effects after + /// https://github.com/paritytech/substrate/pull/7040 is merged and shipped. + type LegacyVotingBond: Get>; + /// Base deposit associated with voting. /// /// This should be sensibly high to economically ensure the pallet cannot be attacked by @@ -223,11 +230,17 @@ decl_storage! { /// Votes and locked stake of a particular voter. /// /// TWOX-NOTE: SAFE as `AccountId` is a crypto hash - pub Voting get(fn voting): map hasher(twox_64_concat) T::AccountId => (BalanceOf, Vec); + pub Voting get(fn voting): map hasher(twox_64_concat) T::AccountId + => (BalanceOf, Vec); /// The present candidate list. Sorted based on account-id. A current member or runner-up /// can never enter this vector and is always implicitly assumed to be a candidate. pub Candidates get(fn candidates): Vec; + + /// Should be used in conjunction with `on_runtime_upgrade` to ensure an upgrade is executed + /// once, even if the code is not removed in time. + pub NeedsMigration get(fn needs_migrations): bool = true; + } add_extra_genesis { config(members): Vec<(T::AccountId, BalanceOf)>; build(|config: &GenesisConfig| { @@ -314,6 +327,7 @@ decl_module! { fn deposit_event() = default; const CandidacyBond: BalanceOf = T::CandidacyBond::get(); + const LegacyVotingBond: BalanceOf = T::LegacyVotingBond::get(); const VotingBondBase: BalanceOf = T::VotingBondBase::get(); const VotingBondFactor: BalanceOf = T::VotingBondFactor::get(); const DesiredMembers: u32 = T::DesiredMembers::get(); @@ -321,6 +335,60 @@ decl_module! { const TermDuration: T::BlockNumber = T::TermDuration::get(); const ModuleId: LockIdentifier = T::ModuleId::get(); + fn on_runtime_upgrade() -> Weight { + if Self::needs_migrations() { + let mut consumed_weight = 0; + let mut add_weight = |reads, writes| { + consumed_weight += T::DbWeight::get().reads_writes(reads, writes); + }; + let mut success = 0u32; + let mut error = 0u32; + >::iter().for_each(|(who, (_, votes))| { + // first, reserve the new amount. + match T::Currency::reserve(&who, Self::deposit_of(votes.len())) { + Ok(_) => { + // Ok. Unreserve the old bond. + T::Currency::unreserve(&who, T::LegacyVotingBond::get()); + add_weight(0, 1); + success += 1; + } + Err(_) => { + // force remove this voter. + // we can't use `Self::do_remove_voter()` because that one un-reserves the + // new bond amount. Manually remove. + + // 1 write + >::remove(&who); + // 1 read 1 write + T::Currency::remove_lock(T::ModuleId::get(), &who); + // 1 read 1 write + T::Currency::unreserve(&who, T::LegacyVotingBond::get()); + + add_weight(2, 3); + error += 1; + } + } + }); + + // for iterating over `Voting` + consumed_weight += T::DbWeight::get().reads((success + error).into()); + + frame_support::debug::print!( + "Migration of pallet-elections-phragmen to new deposit scheme done. {} accounts moved successfully, {} failed.", + success, + error, + ); + + NeedsMigration::put(false); + consumed_weight + } else { + frame_support::debug::print!( + "Tried to run migration but NeedsMigration is false. This code probably needs to be removed now.", + ); + 0 + } + } + /// Vote for a set of candidates for the upcoming round of election. This can be called to /// set the initial votes, or update already existing votes. /// @@ -1276,6 +1344,7 @@ mod tests { type ChangeMembers = TestChangeMembers; type InitializeMembers = (); type CandidacyBond = CandidacyBond; + type LegacyVotingBond = VotingBondBase; type VotingBondBase = VotingBondBase; type VotingBondFactor = VotingBondFactor; type TermDuration = TermDuration; @@ -2501,7 +2570,7 @@ mod tests { assert_err_with_weight!( Elections::remove_member(Origin::root(), 4, true), Error::::InvalidReplacement, - Some(36328000), + Some(30159000), // only thing that matters for now is that it is NOT the full block. ); }); @@ -2523,7 +2592,7 @@ mod tests { assert_err_with_weight!( Elections::remove_member(Origin::root(), 4, false), Error::::InvalidReplacement, - Some(36328000) // only thing that matters for now is that it is NOT the full block. + Some(30159000) // only thing that matters for now is that it is NOT the full block. ); }); } From 10e368707fabf07d7428a4a404fc26226d971ef7 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Mon, 14 Sep 2020 15:44:28 +0200 Subject: [PATCH 11/69] Better storage version. --- bin/node/runtime/src/lib.rs | 2 +- frame/elections-phragmen/src/benchmarking.rs | 2 +- frame/elections-phragmen/src/lib.rs | 19 ++++++++++++++----- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index b86d245d4275b..ede59cd1514d4 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -555,7 +555,7 @@ impl pallet_elections_phragmen::Trait for Runtime { type InitializeMembers = Council; type CurrencyToVote = CurrencyToVoteHandler; type CandidacyBond = CandidacyBond; - type LegacyBondBase = LegacyVotingBond; + type LegacyVotingBond = LegacyVotingBond; type VotingBondBase = VotingBondBase; type VotingBondFactor = VotingBondFactor; type LoserCandidate = (); diff --git a/frame/elections-phragmen/src/benchmarking.rs b/frame/elections-phragmen/src/benchmarking.rs index 5a1ff22d24185..9103544557938 100644 --- a/frame/elections-phragmen/src/benchmarking.rs +++ b/frame/elections-phragmen/src/benchmarking.rs @@ -254,7 +254,7 @@ benchmarks! { stake, )?; - // all the bailers go away. NOTE: we can simplify this. There's no need to cerate all these + // all the bailers go away. NOTE: we can simplify this. There's no need to create all these // candidates and remove them. The defunct voter can just vote for random accounts as long // as there are enough members (potential candidates). bailing_candidates.into_iter().for_each(|b| { diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index 5a19c80e2b418..fce6e8619de65 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -152,6 +152,15 @@ pub trait WeightInfo { fn remove_member_wrong_refund() -> Weight; } +#[derive(Encode, Decode, Eq, PartialEq)] +/// Storage version. +pub enum StorageVersion { + /// Initial version. + V1, + /// After moving to per-vote deposit. + V2, +} + pub trait Trait: frame_system::Trait { /// The overarching event type.c type Event: From> + Into<::Event>; @@ -239,7 +248,7 @@ decl_storage! { /// Should be used in conjunction with `on_runtime_upgrade` to ensure an upgrade is executed /// once, even if the code is not removed in time. - pub NeedsMigration get(fn needs_migrations): bool = true; + pub PalletStorageVersion get(fn pallet_storage_version): StorageVersion = StorageVersion::V1; } add_extra_genesis { config(members): Vec<(T::AccountId, BalanceOf)>; @@ -336,7 +345,7 @@ decl_module! { const ModuleId: LockIdentifier = T::ModuleId::get(); fn on_runtime_upgrade() -> Weight { - if Self::needs_migrations() { + if Self::pallet_storage_version() == StorageVersion::V1 { let mut consumed_weight = 0; let mut add_weight = |reads, writes| { consumed_weight += T::DbWeight::get().reads_writes(reads, writes); @@ -374,16 +383,16 @@ decl_module! { consumed_weight += T::DbWeight::get().reads((success + error).into()); frame_support::debug::print!( - "Migration of pallet-elections-phragmen to new deposit scheme done. {} accounts moved successfully, {} failed.", + "🏛 pallet-elections-phragmen: Migration to new deposit scheme done. {} accounts moved successfully, {} failed.", success, error, ); - NeedsMigration::put(false); + PalletStorageVersion::put(StorageVersion::V2); consumed_weight } else { frame_support::debug::print!( - "Tried to run migration but NeedsMigration is false. This code probably needs to be removed now.", + "🏛 pallet-elections-phragmen: Tried to run migration but PalletStorageVersion is updated to V2. This code probably needs to be removed now.", ); 0 } From 4495ead2f5202e5859b18c38dc45cc29ec0e8d94 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Mon, 14 Sep 2020 15:50:27 +0200 Subject: [PATCH 12/69] Functionify. --- frame/elections-phragmen/src/lib.rs | 113 +++++++++++++++------------- 1 file changed, 62 insertions(+), 51 deletions(-) diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index fce6e8619de65..bbf20ca586627 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -114,6 +114,67 @@ type BalanceOf = type NegativeImbalanceOf = <::Currency as Currency<::AccountId>>::NegativeImbalance; + +/// Helper functions for migrations of this module. +pub mod migrations { + use super::*; + + /// Migrate from the old legacy voting bond (fixed) to the new one (per-vote dynamic). + pub fn migrate_deposits_to_per_vote() -> Weight { + if >::pallet_storage_version() == StorageVersion::V1 { + let mut consumed_weight = 0; + let mut add_weight = |reads, writes| { + consumed_weight += T::DbWeight::get().reads_writes(reads, writes); + }; + let mut success = 0u32; + let mut error = 0u32; + >::iter().for_each(|(who, (_, votes))| { + // first, reserve the new amount. + match T::Currency::reserve(&who, >::deposit_of(votes.len())) { + Ok(_) => { + // Ok. Unreserve the old bond. + T::Currency::unreserve(&who, T::LegacyVotingBond::get()); + add_weight(0, 1); + success += 1; + } + Err(_) => { + // force remove this voter. + // we can't use `Self::do_remove_voter()` because that one un-reserves the + // new bond amount. Manually remove. + + // 1 write + >::remove(&who); + // 1 read 1 write + T::Currency::remove_lock(T::ModuleId::get(), &who); + // 1 read 1 write + T::Currency::unreserve(&who, T::LegacyVotingBond::get()); + + add_weight(2, 3); + error += 1; + } + } + }); + + // for iterating over `Voting` + consumed_weight += T::DbWeight::get().reads((success + error).into()); + + frame_support::debug::print!( + "🏛 pallet-elections-phragmen: Migration to new deposit scheme done. {} accounts moved successfully, {} failed.", + success, + error, + ); + + PalletStorageVersion::put(StorageVersion::V2); + consumed_weight + } else { + frame_support::debug::print!( + "🏛 pallet-elections-phragmen: Tried to run migration but PalletStorageVersion is updated to V2. This code probably needs to be removed now.", + ); + 0 + } + } +} + /// An indication that the renouncing account currently has which of the below roles. #[derive(Encode, Decode, Clone, PartialEq, RuntimeDebug)] pub enum Renouncing { @@ -345,57 +406,7 @@ decl_module! { const ModuleId: LockIdentifier = T::ModuleId::get(); fn on_runtime_upgrade() -> Weight { - if Self::pallet_storage_version() == StorageVersion::V1 { - let mut consumed_weight = 0; - let mut add_weight = |reads, writes| { - consumed_weight += T::DbWeight::get().reads_writes(reads, writes); - }; - let mut success = 0u32; - let mut error = 0u32; - >::iter().for_each(|(who, (_, votes))| { - // first, reserve the new amount. - match T::Currency::reserve(&who, Self::deposit_of(votes.len())) { - Ok(_) => { - // Ok. Unreserve the old bond. - T::Currency::unreserve(&who, T::LegacyVotingBond::get()); - add_weight(0, 1); - success += 1; - } - Err(_) => { - // force remove this voter. - // we can't use `Self::do_remove_voter()` because that one un-reserves the - // new bond amount. Manually remove. - - // 1 write - >::remove(&who); - // 1 read 1 write - T::Currency::remove_lock(T::ModuleId::get(), &who); - // 1 read 1 write - T::Currency::unreserve(&who, T::LegacyVotingBond::get()); - - add_weight(2, 3); - error += 1; - } - } - }); - - // for iterating over `Voting` - consumed_weight += T::DbWeight::get().reads((success + error).into()); - - frame_support::debug::print!( - "🏛 pallet-elections-phragmen: Migration to new deposit scheme done. {} accounts moved successfully, {} failed.", - success, - error, - ); - - PalletStorageVersion::put(StorageVersion::V2); - consumed_weight - } else { - frame_support::debug::print!( - "🏛 pallet-elections-phragmen: Tried to run migration but PalletStorageVersion is updated to V2. This code probably needs to be removed now.", - ); - 0 - } + migrations::migrate_deposits_to_per_vote::() } /// Vote for a set of candidates for the upcoming round of election. This can be called to From 3c1a2ef3baea302d4d9a1ce9d42dff82d6bf4d11 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Thu, 17 Sep 2020 11:09:24 +0200 Subject: [PATCH 13/69] Fix deposit scheme. --- Cargo.lock | 12 +-- frame/elections-phragmen/src/lib.rs | 133 +++++++++++++--------------- 2 files changed, 68 insertions(+), 77 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9264534efa02b..6a26095f479ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1283,9 +1283,9 @@ dependencies = [ [[package]] name = "either" -version = "1.5.3" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" +checksum = "cd56b59865bce947ac5958779cfa508f6c3b9497cc762b7e24a12d11ccde2c4f" [[package]] name = "enumflags2" @@ -5149,9 +5149,9 @@ dependencies = [ [[package]] name = "parity-multiaddr" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc20af3143a62c16e7c9e92ea5c6ae49f7d271d97d4d8fe73afc28f0514a3d0f" +checksum = "2165a93382a93de55868dcbfa11e4a8f99676a9164eee6a2b4a9479ad319c257" dependencies = [ "arrayref", "bs58", @@ -10269,9 +10269,9 @@ dependencies = [ [[package]] name = "yamux" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd37e58a1256a0b328ce9c67d8b62ecdd02f4803ba443df478835cb1a41a637c" +checksum = "053585b18bca1a3d00e4b5ef93e72d4f49a10005374c455db7177e27149c899d" dependencies = [ "futures 0.3.5", "log", diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index bbf20ca586627..4e7c77f594e4c 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -118,57 +118,31 @@ type NegativeImbalanceOf = /// Helper functions for migrations of this module. pub mod migrations { use super::*; + use frame_support::migration::StorageKeyIterator; + use frame_support::Twox64Concat; /// Migrate from the old legacy voting bond (fixed) to the new one (per-vote dynamic). - pub fn migrate_deposits_to_per_vote() -> Weight { + pub fn migrate_deposits_to_per_vote(old_deposit: BalanceOf) -> Weight { if >::pallet_storage_version() == StorageVersion::V1 { - let mut consumed_weight = 0; - let mut add_weight = |reads, writes| { - consumed_weight += T::DbWeight::get().reads_writes(reads, writes); - }; - let mut success = 0u32; - let mut error = 0u32; - >::iter().for_each(|(who, (_, votes))| { - // first, reserve the new amount. - match T::Currency::reserve(&who, >::deposit_of(votes.len())) { - Ok(_) => { - // Ok. Unreserve the old bond. - T::Currency::unreserve(&who, T::LegacyVotingBond::get()); - add_weight(0, 1); - success += 1; - } - Err(_) => { - // force remove this voter. - // we can't use `Self::do_remove_voter()` because that one un-reserves the - // new bond amount. Manually remove. - - // 1 write - >::remove(&who); - // 1 read 1 write - T::Currency::remove_lock(T::ModuleId::get(), &who); - // 1 read 1 write - T::Currency::unreserve(&who, T::LegacyVotingBond::get()); - - add_weight(2, 3); - error += 1; - } - } - }); - // for iterating over `Voting` - consumed_weight += T::DbWeight::get().reads((success + error).into()); - - frame_support::debug::print!( - "🏛 pallet-elections-phragmen: Migration to new deposit scheme done. {} accounts moved successfully, {} failed.", - success, - error, - ); + , Vec), Twox64Concat>>::new( + b"PhragmenElection", + b"Voting", + ) + // remove previous elements as we move on + .drain() + .for_each(|(who, (stake, votes))| { + // Insert a new value into the same location. + let deposit = old_deposit; + let voter = Voter { votes, stake, deposit }; + >::insert(who, voter); + }); - PalletStorageVersion::put(StorageVersion::V2); - consumed_weight + Weight::max_value() } else { frame_support::debug::print!( - "🏛 pallet-elections-phragmen: Tried to run migration but PalletStorageVersion is updated to V2. This code probably needs to be removed now.", + "🏛 pallet-elections-phragmen: Tried to run migration but PalletStorageVersion is \ + updated to V2. This code probably needs to be removed now.", ); 0 } @@ -199,6 +173,19 @@ pub struct DefunctVoter { pub candidate_count: u32 } +/// An active voter. +#[derive(Encode, Decode, Clone, Default, RuntimeDebug, PartialEq)] +pub struct Voter { + /// The members being backed. + votes: Vec, + /// The amount of stake placed on this vote. + stake: Balance, + /// The amount of deposit reserved for this vote. + /// + /// To be unreserved upon removal. + deposit: Balance, +} + pub trait WeightInfo { fn vote(v: u32, ) -> Weight; fn vote_update(v: u32, ) -> Weight; @@ -300,8 +287,7 @@ decl_storage! { /// Votes and locked stake of a particular voter. /// /// TWOX-NOTE: SAFE as `AccountId` is a crypto hash - pub Voting get(fn voting): map hasher(twox_64_concat) T::AccountId - => (BalanceOf, Vec); + pub Voting get(fn voting): map hasher(twox_64_concat) T::AccountId => Voter>; /// The present candidate list. Sorted based on account-id. A current member or runner-up /// can never enter this vector and is always implicitly assumed to be a candidate. @@ -403,11 +389,7 @@ decl_module! { 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(); - - fn on_runtime_upgrade() -> Weight { - migrations::migrate_deposits_to_per_vote::() - } + const ModuleId: LockIdentifier = T::ModuleId::get(); /// Vote for a set of candidates for the upcoming round of election. This can be called to /// set the initial votes, or update already existing votes. @@ -461,10 +443,11 @@ decl_module! { ensure!(value > T::Currency::minimum_balance(), Error::::LowBalance); // Reserve bond. - let (_, old_votes) = >::get(&who); + let Voter { votes: old_votes, deposit: old_deposit, .. } = >::get(&who); let maybe_refund = if old_votes.is_empty() { // First time voter. Get full deposit. - T::Currency::reserve(&who, Self::deposit_of(votes.len())) + let deposit = Self::deposit_of(votes.len()); + T::Currency::reserve(&who, deposit) .map_err(|_| Error::::UnableToPayBond)?; None } else { @@ -473,6 +456,7 @@ decl_module! { Ordering::Greater => { // Must reserve a bit more. let to_reserve = Self::deposit_factor_of(votes.len() - old_votes.len()); + debug_assert_eq!(old_deposit + to_reserve, Self::deposit_of(votes.len())); T::Currency::reserve(&who, to_reserve) .map_err(|_| Error::::UnableToPayBond)?; }, @@ -480,25 +464,28 @@ decl_module! { Ordering::Less => { // Must unreserve a bit. let to_unreserve = Self::deposit_factor_of(old_votes.len() - votes.len()); + debug_assert_eq!(old_deposit - to_unreserve, Self::deposit_of(votes.len())); let _remainder = T::Currency::unreserve(&who, to_unreserve); debug_assert!(_remainder.is_zero()); }, - } + }; Some(T::WeightInfo::vote_update(votes.len() as u32)) }; // Amount to be locked up. - let locked_balance = value.min(T::Currency::total_balance(&who)); + let locked_stake = value.min(T::Currency::total_balance(&who)); // lock T::Currency::set_lock( T::ModuleId::get(), &who, - locked_balance, + locked_stake, WithdrawReasons::except(WithdrawReason::TransactionPayment), ); - Voting::::insert(&who, (locked_balance, votes)); + let deposit = Self::deposit_of(votes.len()); + + Voting::::insert(&who, Voter { votes, deposit, stake: locked_stake }); Ok(maybe_refund.into()) } @@ -571,7 +558,7 @@ decl_module! { Error::::InvalidCandidateCount, ); - let (_, votes) = >::get(&target); + let Voter { votes, .. } = >::get(&target); // indirect way to ensure target is a voter. We could call into `::contains()`, but it // would have the same effect with one extra db access. Note that votes cannot be // submitted with length 0. Hence, a non-zero length means that the target is a voter. @@ -942,20 +929,20 @@ impl Module { /// /// DB access: Voting and Lock are always written to, if unreserve, then 1 read and write added. fn do_remove_voter(who: &T::AccountId, unreserve: bool) { - let (_, votes) = >::get(who); + let Voter { deposit, .. } = >::get(who); // remove storage and lock. >::remove(who); T::Currency::remove_lock(T::ModuleId::get(), who); if unreserve { - let _remainder = T::Currency::unreserve(who, Self::deposit_of(votes.len())); + let _remainder = T::Currency::unreserve(who, deposit); debug_assert!(_remainder.is_zero()); } } /// The locked stake of a voter. fn locked_stake_of(who: &T::AccountId) -> BalanceOf { - Voting::::get(who).0 + Voting::::get(who).stake } /// Check there's nothing to do this block. @@ -1006,12 +993,12 @@ impl Module { // used for prime election. let voters_and_stakes = Voting::::iter() - .map(|(voter, (stake, targets))| { (voter, stake, targets) }) + .map(|(voter, Voter { stake, votes, .. })| { (voter, stake, votes) }) .collect::>(); // used for phragmen. let voters_and_votes = voters_and_stakes.iter() .cloned() - .map(|(voter, stake, targets)| { (voter, to_votes(stake), targets)} ) + .map(|(voter, stake, votes)| { (voter, to_votes(stake), votes)} ) .collect::>(); let maybe_phragmen_result = sp_npos_elections::seq_phragmen::( num_to_elect, @@ -1076,8 +1063,8 @@ impl Module { // of the votes. i.e. the first person a voter votes for gets a 16x multiplier, // the next person gets a 15x multiplier, an so on... (assuming `MAXIMUM_VOTE` = 16) let mut prime_votes: Vec<_> = new_members.iter().map(|c| (&c.0, BalanceOf::::zero())).collect(); - for (_, stake, targets) in voters_and_stakes.into_iter() { - for (vote_multiplier, who) in targets.iter() + for (_, stake, votes) in voters_and_stakes.into_iter() { + for (vote_multiplier, who) in votes.iter() .enumerate() .map(|(vote_position, who)| ((MAXIMUM_VOTE - vote_position) as u32, who)) { @@ -1549,7 +1536,7 @@ mod tests { } fn votes_of(who: &u64) -> Vec { - Voting::::get(who).1 + Voting::::get(who).votes } fn defunct_for(who: u64) -> DefunctVoter { @@ -1585,8 +1572,8 @@ mod tests { 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])); + assert_eq!(Elections::voting(1), Voter { stake: 10u64, votes: vec![1], deposit: 2 }); + assert_eq!(Elections::voting(2), Voter { stake: 20u64, votes: vec![2], deposit: 2 }); // they will persist since they have self vote. System::set_block_number(5); @@ -1602,8 +1589,8 @@ mod tests { 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])); + assert_eq!(Elections::voting(1), Voter { stake: 10u64, votes: vec![1], deposit: 2 }); + assert_eq!(Elections::voting(2), Voter { stake: 20u64, votes: vec![2], deposit: 2 }); // they will persist since they have self vote. System::set_block_number(5); @@ -1835,7 +1822,7 @@ mod tests { } #[test] - fn voting_reserves_per_bond_per_vote() { + fn voting_reserves_bond_per_vote() { ExtBuilder::default().voter_bond_factor(1).build_and_execute(|| { assert_eq!(balances(&2), (20, 0)); @@ -1847,6 +1834,7 @@ mod tests { // 2 + 1 assert_eq!(balances(&2), (17, 3)); + assert_eq!(Elections::voting(&2).deposit, 3); assert_eq!(has_lock(&2), 10); assert_eq!(Elections::locked_stake_of(&2), 10); @@ -1854,6 +1842,7 @@ mod tests { assert_ok!(vote(Origin::signed(2), vec![5, 4], 15)); // 2 + 2 assert_eq!(balances(&2), (16, 4)); + assert_eq!(Elections::voting(&2).deposit, 4); assert_eq!(has_lock(&2), 15); assert_eq!(Elections::locked_stake_of(&2), 15); @@ -1861,6 +1850,7 @@ mod tests { assert_ok!(vote(Origin::signed(2), vec![5, 3], 18)); // 2 + 2 assert_eq!(balances(&2), (16, 4)); + assert_eq!(Elections::voting(&2).deposit, 4); assert_eq!(has_lock(&2), 18); assert_eq!(Elections::locked_stake_of(&2), 18); @@ -1868,6 +1858,7 @@ mod tests { assert_ok!(vote(Origin::signed(2), vec![4], 12)); // 2 + 1 assert_eq!(balances(&2), (17, 3)); + assert_eq!(Elections::voting(&2).deposit, 3); assert_eq!(has_lock(&2), 12); assert_eq!(Elections::locked_stake_of(&2), 12); }); From 1b95224075313d9cf4a97cb16d688d20417abf0a Mon Sep 17 00:00:00 2001 From: kianenigma Date: Thu, 17 Sep 2020 11:12:58 +0200 Subject: [PATCH 14/69] remove legacy bond. --- bin/node/runtime/src/lib.rs | 2 -- frame/elections-phragmen/src/lib.rs | 16 ++++------------ 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index ede59cd1514d4..350644d2771d7 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -532,7 +532,6 @@ impl pallet_collective::Trait for Runtime { parameter_types! { pub const CandidacyBond: Balance = 10 * DOLLARS; - pub const LegacyVotingBond: Balance = 1 * DOLLARS; pub const VotingBondBase: Balance = 1 * DOLLARS; pub const VotingBondFactor: Balance = 50 * CENTS; // per 32 bytes on-chain pub const VotingBond: Balance = 1 * DOLLARS; @@ -555,7 +554,6 @@ impl pallet_elections_phragmen::Trait for Runtime { type InitializeMembers = Council; type CurrencyToVote = CurrencyToVoteHandler; type CandidacyBond = CandidacyBond; - type LegacyVotingBond = LegacyVotingBond; type VotingBondBase = VotingBondBase; type VotingBondFactor = VotingBondFactor; type LoserCandidate = (); diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index 4e7c77f594e4c..5459852dd3a0a 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -122,6 +122,8 @@ pub mod migrations { use frame_support::Twox64Concat; /// Migrate from the old legacy voting bond (fixed) to the new one (per-vote dynamic). + /// + /// Will only be triggered if storage version is V1. pub fn migrate_deposits_to_per_vote(old_deposit: BalanceOf) -> Weight { if >::pallet_storage_version() == StorageVersion::V1 { @@ -129,15 +131,14 @@ pub mod migrations { b"PhragmenElection", b"Voting", ) - // remove previous elements as we move on - .drain() .for_each(|(who, (stake, votes))| { - // Insert a new value into the same location. + // Insert a new value into the same location, thus no need to do `.drain()`. let deposit = old_deposit; let voter = Voter { votes, stake, deposit }; >::insert(who, voter); }); + PalletStorageVersion::put(StorageVersion::V2); Weight::max_value() } else { frame_support::debug::print!( @@ -234,13 +235,6 @@ pub trait Trait: frame_system::Trait { /// How much should be locked up in order to submit one's candidacy. type CandidacyBond: Get>; - /// DEPRECATED. - /// - /// The old voting bond. Only used and needed for migrating to the new model. - /// // TODO: remove with all side effects after - /// https://github.com/paritytech/substrate/pull/7040 is merged and shipped. - type LegacyVotingBond: Get>; - /// Base deposit associated with voting. /// /// This should be sensibly high to economically ensure the pallet cannot be attacked by @@ -383,7 +377,6 @@ decl_module! { fn deposit_event() = default; const CandidacyBond: BalanceOf = T::CandidacyBond::get(); - const LegacyVotingBond: BalanceOf = T::LegacyVotingBond::get(); const VotingBondBase: BalanceOf = T::VotingBondBase::get(); const VotingBondFactor: BalanceOf = T::VotingBondFactor::get(); const DesiredMembers: u32 = T::DesiredMembers::get(); @@ -1351,7 +1344,6 @@ mod tests { type ChangeMembers = TestChangeMembers; type InitializeMembers = (); type CandidacyBond = CandidacyBond; - type LegacyVotingBond = VotingBondBase; type VotingBondBase = VotingBondBase; type VotingBondFactor = VotingBondFactor; type TermDuration = TermDuration; From 2c677c2339043c2fd139febc43206c936ad8d5c8 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Thu, 17 Sep 2020 11:26:09 +0200 Subject: [PATCH 15/69] Master.into() --- frame/elections-phragmen/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index 5459852dd3a0a..1c9a708dc2d63 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -124,7 +124,7 @@ pub mod migrations { /// Migrate from the old legacy voting bond (fixed) to the new one (per-vote dynamic). /// /// Will only be triggered if storage version is V1. - pub fn migrate_deposits_to_per_vote(old_deposit: BalanceOf) -> Weight { + pub fn migrate_to_recorded_deposit(old_deposit: BalanceOf) -> Weight { if >::pallet_storage_version() == StorageVersion::V1 { , Vec), Twox64Concat>>::new( From a6288505f0ff31be8f42876551ba3653ba4ed885 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Thu, 17 Sep 2020 11:54:14 +0200 Subject: [PATCH 16/69] better logging. --- frame/elections-phragmen/src/lib.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index 8de006367b623..79f7dec217840 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -126,9 +126,9 @@ pub mod migrations { /// Will only be triggered if storage version is V1. pub fn migrate_to_recorded_deposit(old_deposit: BalanceOf) -> Weight { if >::pallet_storage_version() == StorageVersion::V1 { - + let module_storage_name = >::storage_prefix(); , Vec), Twox64Concat>>::new( - b"PhragmenElection", + module_storage_name, b"Voting", ) .for_each(|(who, (stake, votes))| { @@ -139,9 +139,12 @@ pub mod migrations { }); PalletStorageVersion::put(StorageVersion::V2); + frame_support::debug::info!( + "🏛 pallet-elections-phragmen: Storage migrated to V2." + ); Weight::max_value() } else { - frame_support::debug::print!( + frame_support::debug::warn!( "🏛 pallet-elections-phragmen: Tried to run migration but PalletStorageVersion is \ updated to V2. This code probably needs to be removed now.", ); From f6189d5eca90f23ec36b8042db57d23b9d7b7d8e Mon Sep 17 00:00:00 2001 From: kianenigma Date: Thu, 17 Sep 2020 14:16:22 +0200 Subject: [PATCH 17/69] Fix benchmarking test --- frame/elections-phragmen/src/benchmarking.rs | 2 +- frame/elections-phragmen/src/lib.rs | 14 ++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/frame/elections-phragmen/src/benchmarking.rs b/frame/elections-phragmen/src/benchmarking.rs index 9103544557938..aaf6d0ea83eb5 100644 --- a/frame/elections-phragmen/src/benchmarking.rs +++ b/frame/elections-phragmen/src/benchmarking.rs @@ -70,7 +70,7 @@ fn candidate_count() -> u32 { /// Get the number of votes of a voter. fn vote_count_of(who: &T::AccountId) -> u32 { - >::get(who).1.len() as u32 + >::get(who).votes.len() as u32 } /// A `DefunctVoter` struct with correct value diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index 79f7dec217840..9e44378e9a942 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -126,9 +126,9 @@ pub mod migrations { /// Will only be triggered if storage version is V1. pub fn migrate_to_recorded_deposit(old_deposit: BalanceOf) -> Weight { if >::pallet_storage_version() == StorageVersion::V1 { - let module_storage_name = >::storage_prefix(); + let mut count = 0; , Vec), Twox64Concat>>::new( - module_storage_name, + >::module_prefix(), b"Voting", ) .for_each(|(who, (stake, votes))| { @@ -136,11 +136,13 @@ pub mod migrations { let deposit = old_deposit; let voter = Voter { votes, stake, deposit }; >::insert(who, voter); + count += 1; }); PalletStorageVersion::put(StorageVersion::V2); frame_support::debug::info!( - "🏛 pallet-elections-phragmen: Storage migrated to V2." + "🏛 pallet-elections-phragmen: {} voters migrated to V2.", + count, ); Weight::max_value() } else { @@ -181,13 +183,13 @@ pub struct DefunctVoter { #[derive(Encode, Decode, Clone, Default, RuntimeDebug, PartialEq)] pub struct Voter { /// The members being backed. - votes: Vec, + pub votes: Vec, /// The amount of stake placed on this vote. - stake: Balance, + pub stake: Balance, /// The amount of deposit reserved for this vote. /// /// To be unreserved upon removal. - deposit: Balance, + pub deposit: Balance, } pub trait WeightInfo { From e812815a0eae1a9f1b2c1f365fee3fb5747cb14e Mon Sep 17 00:00:00 2001 From: kianenigma Date: Fri, 18 Sep 2020 19:13:50 +0200 Subject: [PATCH 18/69] Fix confused deposit collection. --- bin/node/runtime/src/lib.rs | 2 +- bin/node/runtime/src/weights/mod.rs | 1 + frame/elections-phragmen/src/lib.rs | 21 ++++++--------------- 3 files changed, 8 insertions(+), 16 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index dfae0c266106b..45549ed3d9f34 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -567,7 +567,7 @@ impl pallet_elections_phragmen::Trait for Runtime { type DesiredMembers = DesiredMembers; type DesiredRunnersUp = DesiredRunnersUp; type TermDuration = TermDuration; - type WeightInfo = (); + type WeightInfo = weights::pallet_elections_phragmen::WeightInfo; } parameter_types! { diff --git a/bin/node/runtime/src/weights/mod.rs b/bin/node/runtime/src/weights/mod.rs index 86cab773b18e3..6ec845e4e1530 100644 --- a/bin/node/runtime/src/weights/mod.rs +++ b/bin/node/runtime/src/weights/mod.rs @@ -23,3 +23,4 @@ pub mod pallet_proxy; pub mod pallet_timestamp; pub mod pallet_utility; pub mod pallet_vesting; +pub mod pallet_elections_phragmen; diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index 9e44378e9a942..09862f4d9d768 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -441,28 +441,26 @@ decl_module! { ensure!(value > T::Currency::minimum_balance(), Error::::LowBalance); // Reserve bond. + let new_deposit = Self::deposit_of(votes.len()); let Voter { votes: old_votes, deposit: old_deposit, .. } = >::get(&who); let maybe_refund = if old_votes.is_empty() { // First time voter. Get full deposit. - let deposit = Self::deposit_of(votes.len()); - T::Currency::reserve(&who, deposit) + T::Currency::reserve(&who, new_deposit) .map_err(|_| Error::::UnableToPayBond)?; None } else { // Old voter. - match votes.len().cmp(&old_votes.len()) { + match new_deposit.cmp(&old_deposit) { Ordering::Greater => { // Must reserve a bit more. - let to_reserve = Self::deposit_factor_of(votes.len() - old_votes.len()); - debug_assert_eq!(old_deposit + to_reserve, Self::deposit_of(votes.len())); + let to_reserve = new_deposit - old_deposit; T::Currency::reserve(&who, to_reserve) .map_err(|_| Error::::UnableToPayBond)?; }, Ordering::Equal => {}, Ordering::Less => { // Must unreserve a bit. - let to_unreserve = Self::deposit_factor_of(old_votes.len() - votes.len()); - debug_assert_eq!(old_deposit - to_unreserve, Self::deposit_of(votes.len())); + let to_unreserve = old_deposit - new_deposit; let _remainder = T::Currency::unreserve(&who, to_unreserve); debug_assert!(_remainder.is_zero()); }, @@ -481,9 +479,7 @@ decl_module! { WithdrawReasons::except(WithdrawReason::TransactionPayment), ); - let deposit = Self::deposit_of(votes.len()); - - Voting::::insert(&who, Voter { votes, deposit, stake: locked_stake }); + Voting::::insert(&who, Voter { votes, deposit: new_deposit, stake: locked_stake }); Ok(maybe_refund.into()) } @@ -806,11 +802,6 @@ impl Module { ) } - /// Same as [`deposit_of`] but only returns the factor deposit. - fn deposit_factor_of(count: usize) -> BalanceOf { - T::VotingBondFactor::get().saturating_mul((count as u32).into()) - } - /// Attempts to remove a member `who`. If a runner-up exists, it is used as the replacement and /// Ok(true). is returned. /// From 498376c1dc221e6ed8cc8a58eb807706d4b60da3 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Mon, 21 Sep 2020 10:36:28 +0200 Subject: [PATCH 19/69] Add fine --- .../src/weights/pallet_elections_phragmen.rs | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 bin/node/runtime/src/weights/pallet_elections_phragmen.rs diff --git a/bin/node/runtime/src/weights/pallet_elections_phragmen.rs b/bin/node/runtime/src/weights/pallet_elections_phragmen.rs new file mode 100644 index 0000000000000..2071cc28eca5a --- /dev/null +++ b/bin/node/runtime/src/weights/pallet_elections_phragmen.rs @@ -0,0 +1,88 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2020 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 2.0.0-rc6 + +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::weights::{Weight, constants::RocksDbWeight as DbWeight}; + +pub struct WeightInfo; +impl pallet_elections_phragmen::WeightInfo for WeightInfo { + fn vote(v: u32, ) -> Weight { + (35890000 as Weight) + .saturating_add((168000 as Weight).saturating_mul(v as Weight)) + .saturating_add(DbWeight::get().reads(5 as Weight)) + .saturating_add(DbWeight::get().writes(2 as Weight)) + } + fn vote_update(v: u32, ) -> Weight { + (23406000 as Weight) + .saturating_add((312000 as Weight).saturating_mul(v as Weight)) + .saturating_add(DbWeight::get().reads(5 as Weight)) + .saturating_add(DbWeight::get().writes(2 as Weight)) + } + fn remove_voter() -> Weight { + (35814000 as Weight) + .saturating_add(DbWeight::get().reads(2 as Weight)) + .saturating_add(DbWeight::get().writes(2 as Weight)) + } + fn report_defunct_voter_correct(c: u32, v: u32, ) -> Weight { + (0 as Weight) + .saturating_add((1917000 as Weight).saturating_mul(c as Weight)) + .saturating_add((28128000 as Weight).saturating_mul(v as Weight)) + .saturating_add(DbWeight::get().reads(6 as Weight)) + .saturating_add(DbWeight::get().writes(3 as Weight)) + } + fn report_defunct_voter_incorrect(c: u32, v: u32, ) -> Weight { + (0 as Weight) + .saturating_add((1946000 as Weight).saturating_mul(c as Weight)) + .saturating_add((28514000 as Weight).saturating_mul(v as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + } + fn submit_candidacy(c: u32, ) -> Weight { + (41826000 as Weight) + .saturating_add((235000 as Weight).saturating_mul(c as Weight)) + .saturating_add(DbWeight::get().reads(3 as Weight)) + .saturating_add(DbWeight::get().writes(1 as Weight)) + } + fn renounce_candidacy_candidate(c: u32, ) -> Weight { + (27830000 as Weight) + .saturating_add((181000 as Weight).saturating_mul(c as Weight)) + .saturating_add(DbWeight::get().reads(1 as Weight)) + .saturating_add(DbWeight::get().writes(1 as Weight)) + } + fn renounce_candidacy_members() -> Weight { + (41689000 as Weight) + .saturating_add(DbWeight::get().reads(3 as Weight)) + .saturating_add(DbWeight::get().writes(4 as Weight)) + } + fn renounce_candidacy_runners_up() -> Weight { + (27689000 as Weight) + .saturating_add(DbWeight::get().reads(1 as Weight)) + .saturating_add(DbWeight::get().writes(1 as Weight)) + } + fn remove_member_with_replacement() -> Weight { + (41510000 as Weight) + .saturating_add(DbWeight::get().reads(4 as Weight)) + .saturating_add(DbWeight::get().writes(5 as Weight)) + } + fn remove_member_wrong_refund() -> Weight { + (5159000 as Weight) + .saturating_add(DbWeight::get().reads(1 as Weight)) + } +} From c871c81c2fb64ea334d001484982b01be0c9b9c2 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Mon, 12 Oct 2020 16:19:51 +0200 Subject: [PATCH 20/69] Better name for storage item --- frame/elections-phragmen/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index dae51e4fbfa1e..a65367eebb183 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -140,9 +140,9 @@ pub mod migrations { count += 1; }); - PalletStorageVersion::put(StorageVersion::V2); + PalletStorageVersion::put(StorageVersion::V2_PER_VOTER_DEPOSIT); frame_support::debug::info!( - "🏛 pallet-elections-phragmen: {} voters migrated to V2.", + "🏛 pallet-elections-phragmen: {} voters migrated to V2_PER_VOTER_DEPOSIT.", count, ); Weight::max_value() @@ -213,7 +213,7 @@ pub enum StorageVersion { /// Initial version. V1, /// After moving to per-vote deposit. - V2, + V2_PER_VOTER_DEPOSIT, } pub trait Trait: frame_system::Trait { From 12c18d5197d7ac0e215c6d6fdbec564086507ae7 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Mon, 12 Oct 2020 16:21:18 +0200 Subject: [PATCH 21/69] Fix name again. --- frame/elections-phragmen/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index a65367eebb183..1154179bde6e4 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -140,9 +140,9 @@ pub mod migrations { count += 1; }); - PalletStorageVersion::put(StorageVersion::V2_PER_VOTER_DEPOSIT); + PalletStorageVersion::put(StorageVersion::V2PerVoterDeposit); frame_support::debug::info!( - "🏛 pallet-elections-phragmen: {} voters migrated to V2_PER_VOTER_DEPOSIT.", + "🏛 pallet-elections-phragmen: {} voters migrated to V2PerVoterDeposit.", count, ); Weight::max_value() @@ -213,7 +213,7 @@ pub enum StorageVersion { /// Initial version. V1, /// After moving to per-vote deposit. - V2_PER_VOTER_DEPOSIT, + V2PerVoterDeposit, } pub trait Trait: frame_system::Trait { From f900b68fe1725c1cba0e4d2b56c680482a9b0e79 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Mon, 12 Oct 2020 16:51:32 +0200 Subject: [PATCH 22/69] remove unused --- bin/node/runtime/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index d450162f2189b..e4939bbd99251 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -561,7 +561,6 @@ parameter_types! { pub const CandidacyBond: Balance = 10 * DOLLARS; pub const VotingBondBase: Balance = 1 * DOLLARS; pub const VotingBondFactor: Balance = 50 * CENTS; // per 32 bytes on-chain - pub const VotingBond: Balance = 1 * DOLLARS; pub const TermDuration: BlockNumber = 7 * DAYS; pub const DesiredMembers: u32 = 13; pub const DesiredRunnersUp: u32 = 7; From 6c942b4f648113b9e3f49c920d013cbf93e52b04 Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Tue, 13 Oct 2020 09:47:47 +0200 Subject: [PATCH 23/69] Update frame/elections-phragmen/src/lib.rs Co-authored-by: Guillaume Thiolliere --- frame/elections-phragmen/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index 1154179bde6e4..66d90538604cc 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -207,8 +207,8 @@ pub trait WeightInfo { fn remove_member_wrong_refund() -> Weight; } -#[derive(Encode, Decode, Eq, PartialEq)] /// Storage version. +#[derive(Encode, Decode, Eq, PartialEq)] pub enum StorageVersion { /// Initial version. V1, From 8e3c2eb1862e5437f3b8f47c229e2398ed103ecb Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Tue, 13 Oct 2020 09:54:29 +0200 Subject: [PATCH 24/69] Update frame/elections-phragmen/src/lib.rs Co-authored-by: Guillaume Thiolliere --- frame/elections-phragmen/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index 66d90538604cc..642f5592815a3 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -295,7 +295,7 @@ decl_storage! { /// Should be used in conjunction with `on_runtime_upgrade` to ensure an upgrade is executed /// once, even if the code is not removed in time. - pub PalletStorageVersion get(fn pallet_storage_version): StorageVersion = StorageVersion::V1; + pub PalletStorageVersion get(fn pallet_storage_version) build(|_| StorageVersion::V2PerVoterDeposit): StorageVersion = StorageVersion::V1; } add_extra_genesis { config(members): Vec<(T::AccountId, BalanceOf)>; From f5700cf1b1bf533d35ef024b5186590a5f3d967c Mon Sep 17 00:00:00 2001 From: Shawn Tabrizi Date: Tue, 13 Oct 2020 10:09:27 +0000 Subject: [PATCH 25/69] cargo run --release --features runtime-benchmarks --manifest-path bin/node/cli/Cargo.toml -- benchmark --chain dev --steps 50 --repeat 20 --extrinsic * --execution=wasm --wasm-execution=compiled --output ./bin/node/runtime/src/weights --header ./HEADER --pallet pallet_elections_phragmen --- .../src/weights/pallet_elections_phragmen.rs | 45 ++++++++++--------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/bin/node/runtime/src/weights/pallet_elections_phragmen.rs b/bin/node/runtime/src/weights/pallet_elections_phragmen.rs index 8da9838d5d7a1..66e3b3809ec1c 100644 --- a/bin/node/runtime/src/weights/pallet_elections_phragmen.rs +++ b/bin/node/runtime/src/weights/pallet_elections_phragmen.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2020 Parity Technologies (UK) Ltd. +// Copyright (C) 2020 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,7 +15,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 2.0.0-rc6 +//! Weights for pallet_elections_phragmen +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 2.0.0 +//! DATE: 2020-10-13, STEPS: [50], REPEAT: 20, LOW RANGE: [], HIGH RANGE: [] #![allow(unused_parens)] #![allow(unused_imports)] @@ -26,65 +28,64 @@ use sp_std::marker::PhantomData; pub struct WeightInfo(PhantomData); impl pallet_elections_phragmen::WeightInfo for WeightInfo { fn vote(v: u32, ) -> Weight { - (91_489_000 as Weight) - .saturating_add((199_000 as Weight).saturating_mul(v as Weight)) + (91_060_000 as Weight) + .saturating_add((305_000 as Weight).saturating_mul(v as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } fn vote_update(v: u32, ) -> Weight { - (56_511_000 as Weight) - .saturating_add((245_000 as Weight).saturating_mul(v as Weight)) + (57_469_000 as Weight) + .saturating_add((444_000 as Weight).saturating_mul(v as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } fn remove_voter() -> Weight { - (76_714_000 as Weight) + (81_517_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } fn report_defunct_voter_correct(c: u32, v: u32, ) -> Weight { (0 as Weight) - .saturating_add((1_743_000 as Weight).saturating_mul(c as Weight)) - .saturating_add((31_750_000 as Weight).saturating_mul(v as Weight)) - .saturating_add(T::DbWeight::get().reads(7 as Weight)) + .saturating_add((1_734_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((31_506_000 as Weight).saturating_mul(v as Weight)) + .saturating_add(T::DbWeight::get().reads(6 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } fn report_defunct_voter_incorrect(c: u32, v: u32, ) -> Weight { (0 as Weight) - .saturating_add((1_733_000 as Weight).saturating_mul(c as Weight)) - .saturating_add((31_861_000 as Weight).saturating_mul(v as Weight)) - .saturating_add(T::DbWeight::get().reads(6 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + .saturating_add((1_699_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((31_489_000 as Weight).saturating_mul(v as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) } fn submit_candidacy(c: u32, ) -> Weight { - (74_714_000 as Weight) - .saturating_add((315_000 as Weight).saturating_mul(c as Weight)) + (73_383_000 as Weight) + .saturating_add((326_000 as Weight).saturating_mul(c as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } fn renounce_candidacy_candidate(c: u32, ) -> Weight { - (50_408_000 as Weight) - .saturating_add((159_000 as Weight).saturating_mul(c as Weight)) + (50_694_000 as Weight) + .saturating_add((154_000 as Weight).saturating_mul(c as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } fn renounce_candidacy_members() -> Weight { - (79_626_000 as Weight) + (77_808_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } fn renounce_candidacy_runners_up() -> Weight { - (49_715_000 as Weight) + (48_452_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } fn remove_member_with_replacement() -> Weight { - (76_572_000 as Weight) + (75_784_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } fn remove_member_wrong_refund() -> Weight { - (8_777_000 as Weight) + (9_099_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) } } From 0564e59f67d5d551868909ac8d7b5a7309b8bce6 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Tue, 13 Oct 2020 12:15:45 +0200 Subject: [PATCH 26/69] new weight fns --- frame/elections-phragmen/src/benchmarking.rs | 83 +++++++++++++++---- .../elections-phragmen/src/default_weights.rs | 17 ++-- frame/elections-phragmen/src/lib.rs | 67 ++++++--------- 3 files changed, 100 insertions(+), 67 deletions(-) diff --git a/frame/elections-phragmen/src/benchmarking.rs b/frame/elections-phragmen/src/benchmarking.rs index 6c9a57c7c1dfb..ee04abb8f0cea 100644 --- a/frame/elections-phragmen/src/benchmarking.rs +++ b/frame/elections-phragmen/src/benchmarking.rs @@ -169,7 +169,7 @@ benchmarks! { _ {} // -- Signed ones - vote { + vote_equal { let v in 1 .. (MAXIMUM_VOTE as u32); clean::(); @@ -179,14 +179,39 @@ benchmarks! { let caller = endowed_account::("caller", 0); let stake = default_stake::(BALANCE_FACTOR); - // vote for all of them. - let votes = all_candidates; + // original votes. + let mut votes = all_candidates; + submit_voter::(caller.clone(), votes.clone(), stake)?; + + // new votes. + votes.rotate_left(1); whitelist!(caller); - }: _(RawOrigin::Signed(caller), votes, stake) + }: vote(RawOrigin::Signed(caller), votes, stake) - vote_update { - let v in 1 .. (MAXIMUM_VOTE as u32); + vote_more { + let v in 2 .. (MAXIMUM_VOTE as u32); + clean::(); + + // create a bunch of candidates. + let all_candidates = submit_candidates::(v, "candidates")?; + + let caller = endowed_account::("caller", 0); + let stake = default_stake::(BALANCE_FACTOR); + + // original votes. + let mut votes = all_candidates.iter().skip(1).cloned().collect::>(); + submit_voter::(caller.clone(), votes.clone(), stake)?; + + // new votes. + votes = all_candidates; + assert!(votes.len() > >::get(caller.clone()).votes.len()); + + whitelist!(caller); + }: vote(RawOrigin::Signed(caller), votes, stake) + + vote_less { + let v in 2 .. (MAXIMUM_VOTE as u32); clean::(); // create a bunch of candidates. @@ -200,7 +225,8 @@ benchmarks! { submit_voter::(caller.clone(), votes.clone(), stake)?; // new votes. - votes.rotate_left(1); + votes = votes.into_iter().skip(1).collect::>(); + assert!(votes.len() < >::get(caller.clone()).votes.len()); whitelist!(caller); }: vote(RawOrigin::Signed(caller), votes, stake) @@ -581,16 +607,39 @@ mod tests { #[test] fn test_benchmarks_elections_phragmen() { - ExtBuilder::default().desired_members(13).desired_runners_up(7).build_and_execute(|| { - assert_ok!(test_benchmark_vote::()); - }); - - ExtBuilder::default().desired_members(13).desired_runners_up(7).build_and_execute(|| { - assert_ok!(test_benchmark_remove_voter::()); - }); - - ExtBuilder::default().desired_members(13).desired_runners_up(7).build_and_execute(|| { - assert_ok!(test_benchmark_report_defunct_voter_correct::()); + ExtBuilder::default() + .desired_members(13) + .desired_runners_up(7) + .build_and_execute(|| { + assert_ok!(test_benchmark_vote_equal::()); + }); + + ExtBuilder::default() + .desired_members(13) + .desired_runners_up(7) + .build_and_execute(|| { + assert_ok!(test_benchmark_vote_more::()); + }); + + ExtBuilder::default() + .desired_members(13) + .desired_runners_up(7) + .build_and_execute(|| { + assert_ok!(test_benchmark_vote_less::()); + }); + + ExtBuilder::default() + .desired_members(13) + .desired_runners_up(7) + .build_and_execute(|| { + assert_ok!(test_benchmark_remove_voter::()); + }); + + ExtBuilder::default() + .desired_members(13) + .desired_runners_up(7) + .build_and_execute(|| { + assert_ok!(test_benchmark_report_defunct_voter_correct::()); }); ExtBuilder::default().desired_members(13).desired_runners_up(7).build_and_execute(|| { diff --git a/frame/elections-phragmen/src/default_weights.rs b/frame/elections-phragmen/src/default_weights.rs index 4025e61d15af4..fe78292fae2fd 100644 --- a/frame/elections-phragmen/src/default_weights.rs +++ b/frame/elections-phragmen/src/default_weights.rs @@ -23,17 +23,14 @@ use frame_support::weights::{Weight, constants::RocksDbWeight as DbWeight}; impl crate::WeightInfo for () { - fn vote(v: u32, ) -> Weight { - (91_489_000 as Weight) - .saturating_add((199_000 as Weight).saturating_mul(v as Weight)) - .saturating_add(DbWeight::get().reads(5 as Weight)) - .saturating_add(DbWeight::get().writes(2 as Weight)) + fn vote_equal(v: u32) -> Weight { + 0 } - fn vote_update(v: u32, ) -> Weight { - (56_511_000 as Weight) - .saturating_add((245_000 as Weight).saturating_mul(v as Weight)) - .saturating_add(DbWeight::get().reads(5 as Weight)) - .saturating_add(DbWeight::get().writes(2 as Weight)) + fn vote_more(v: u32) -> Weight { + 0 + } + fn vote_less(v: u32) -> Weight { + 0 } fn remove_voter() -> Weight { (76_714_000 as Weight) diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index 1154179bde6e4..3593ecd6c3238 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -194,13 +194,14 @@ pub struct Voter { } pub trait WeightInfo { - fn vote(v: u32, ) -> Weight; - fn vote_update(v: u32, ) -> Weight; + fn vote_equal(v: u32) -> Weight; + fn vote_more(v: u32) -> Weight; + fn vote_less(v: u32) -> Weight; fn remove_voter() -> Weight; - fn report_defunct_voter_correct(c: u32, v: u32, ) -> Weight; - fn report_defunct_voter_incorrect(c: u32, v: u32, ) -> Weight; - fn submit_candidacy(c: u32, ) -> Weight; - fn renounce_candidacy_candidate(c: u32, ) -> Weight; + fn report_defunct_voter_correct(c: u32, v: u32) -> Weight; + fn report_defunct_voter_incorrect(c: u32, v: u32) -> Weight; + fn submit_candidacy(c: u32) -> Weight; + fn renounce_candidacy_candidate(c: u32) -> Weight; fn renounce_candidacy_members() -> Weight; fn renounce_candidacy_runners_up() -> Weight; fn remove_member_with_replacement() -> Weight; @@ -405,18 +406,9 @@ decl_module! { /// and keep some for further transactions. /// /// # - /// Base weight: 47.93 µs - /// State reads: - /// - Candidates.len() + Members.len() + RunnersUp.len() - /// - Voting (is_voter) - /// - Lock - /// - [AccountBalance(who) (unreserve + total_balance)] - /// State writes: - /// - Voting - /// - Lock - /// - [AccountBalance(who) (unreserve -- only when creating a new voter)] + /// we consider the common case of placing more votes. In other two case, we refund. /// # - #[weight = T::WeightInfo::vote(votes.len() as u32)] + #[weight = T::WeightInfo::vote_more(votes.len() as u32)] fn vote( origin, votes: Vec, @@ -444,29 +436,24 @@ decl_module! { // Reserve bond. let new_deposit = Self::deposit_of(votes.len()); let Voter { votes: old_votes, deposit: old_deposit, .. } = >::get(&who); - let maybe_refund = if old_votes.is_empty() { - // First time voter. Get full deposit. - T::Currency::reserve(&who, new_deposit) - .map_err(|_| Error::::UnableToPayBond)?; - None - } else { - // Old voter. - match new_deposit.cmp(&old_deposit) { - Ordering::Greater => { - // Must reserve a bit more. - let to_reserve = new_deposit - old_deposit; - T::Currency::reserve(&who, to_reserve) - .map_err(|_| Error::::UnableToPayBond)?; - }, - Ordering::Equal => {}, - Ordering::Less => { - // Must unreserve a bit. - let to_unreserve = old_deposit - new_deposit; - let _remainder = T::Currency::unreserve(&who, to_unreserve); - debug_assert!(_remainder.is_zero()); - }, - }; - Some(T::WeightInfo::vote_update(votes.len() as u32)) + let maybe_refund = match new_deposit.cmp(&old_deposit) { + Ordering::Greater => { + // Must reserve a bit more. + let to_reserve = new_deposit - old_deposit; + T::Currency::reserve(&who, to_reserve) + .map_err(|_| Error::::UnableToPayBond)?; + None + }, + Ordering::Equal => { + Some(T::WeightInfo::vote_equal(votes.len() as u32)) + }, + Ordering::Less => { + // Must unreserve a bit. + let to_unreserve = old_deposit - new_deposit; + let _remainder = T::Currency::unreserve(&who, to_unreserve); + debug_assert!(_remainder.is_zero()); + Some(T::WeightInfo::vote_less(votes.len() as u32)) + }, }; // Amount to be locked up. From 1b48d0d00dfcd3f521473f65f7d4abf7b7e220e5 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Tue, 13 Oct 2020 12:36:39 +0200 Subject: [PATCH 27/69] Fix build --- .../src/weights/pallet_elections_phragmen.rs | 17 +++++++---------- frame/elections-phragmen/src/lib.rs | 2 +- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/bin/node/runtime/src/weights/pallet_elections_phragmen.rs b/bin/node/runtime/src/weights/pallet_elections_phragmen.rs index 66e3b3809ec1c..3b68322e53eb7 100644 --- a/bin/node/runtime/src/weights/pallet_elections_phragmen.rs +++ b/bin/node/runtime/src/weights/pallet_elections_phragmen.rs @@ -27,17 +27,14 @@ use sp_std::marker::PhantomData; pub struct WeightInfo(PhantomData); impl pallet_elections_phragmen::WeightInfo for WeightInfo { - fn vote(v: u32, ) -> Weight { - (91_060_000 as Weight) - .saturating_add((305_000 as Weight).saturating_mul(v as Weight)) - .saturating_add(T::DbWeight::get().reads(5 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + fn vote_more(v: u32, ) -> Weight { + 0 } - fn vote_update(v: u32, ) -> Weight { - (57_469_000 as Weight) - .saturating_add((444_000 as Weight).saturating_mul(v as Weight)) - .saturating_add(T::DbWeight::get().reads(5 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + fn vote_less(v: u32, ) -> Weight { + 0 + } + fn vote_equal(v: u32, ) -> Weight { + 0 } fn remove_voter() -> Weight { (81_517_000 as Weight) diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index 3b150d033fdbc..b25f0fd9f0e6c 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -435,7 +435,7 @@ decl_module! { // Reserve bond. let new_deposit = Self::deposit_of(votes.len()); - let Voter { votes: old_votes, deposit: old_deposit, .. } = >::get(&who); + let Voter { deposit: old_deposit, .. } = >::get(&who); let maybe_refund = match new_deposit.cmp(&old_deposit) { Ordering::Greater => { // Must reserve a bit more. From 960a4741dbeb8b62ebfec1f9508dd5656a46fdd0 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Tue, 13 Oct 2020 12:38:48 +0200 Subject: [PATCH 28/69] Fix line width --- frame/elections-phragmen/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index b25f0fd9f0e6c..7d9ba6d82600b 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -296,7 +296,8 @@ decl_storage! { /// Should be used in conjunction with `on_runtime_upgrade` to ensure an upgrade is executed /// once, even if the code is not removed in time. - pub PalletStorageVersion get(fn pallet_storage_version) build(|_| StorageVersion::V2PerVoterDeposit): StorageVersion = StorageVersion::V1; + pub PalletStorageVersion get(fn pallet_storage_version) + build(|_| StorageVersion::V2PerVoterDeposit): StorageVersion = StorageVersion::V1; } add_extra_genesis { config(members): Vec<(T::AccountId, BalanceOf)>; From 19e94368c97f32d4a18200ad29eb8a6601532677 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Tue, 13 Oct 2020 13:51:07 +0200 Subject: [PATCH 29/69] fix benchmakrs --- frame/elections-phragmen/src/benchmarking.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frame/elections-phragmen/src/benchmarking.rs b/frame/elections-phragmen/src/benchmarking.rs index ee04abb8f0cea..6545ca1d4f230 100644 --- a/frame/elections-phragmen/src/benchmarking.rs +++ b/frame/elections-phragmen/src/benchmarking.rs @@ -26,7 +26,7 @@ use frame_support::traits::OnInitialize; use crate::Module as Elections; -const BALANCE_FACTOR: u32 = 250; +const BALANCE_FACTOR: u32 = 25_000; const MAX_VOTERS: u32 = 500; const MAX_CANDIDATES: u32 = 200; @@ -201,14 +201,14 @@ benchmarks! { // original votes. let mut votes = all_candidates.iter().skip(1).cloned().collect::>(); - submit_voter::(caller.clone(), votes.clone(), stake)?; + submit_voter::(caller.clone(), votes.clone(), stake / >::from(10))?; // new votes. votes = all_candidates; assert!(votes.len() > >::get(caller.clone()).votes.len()); whitelist!(caller); - }: vote(RawOrigin::Signed(caller), votes, stake) + }: vote(RawOrigin::Signed(caller), votes, stake / >::from(10)) vote_less { let v in 2 .. (MAXIMUM_VOTE as u32); From 76cb36f845a8f556f7a3aaffd3eb9266311562ae Mon Sep 17 00:00:00 2001 From: kianenigma Date: Tue, 13 Oct 2020 13:52:13 +0200 Subject: [PATCH 30/69] fix warning --- bin/node/runtime/src/weights/pallet_elections_phragmen.rs | 6 +++--- frame/elections-phragmen/src/default_weights.rs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/bin/node/runtime/src/weights/pallet_elections_phragmen.rs b/bin/node/runtime/src/weights/pallet_elections_phragmen.rs index 3b68322e53eb7..dfe9e2a1a272a 100644 --- a/bin/node/runtime/src/weights/pallet_elections_phragmen.rs +++ b/bin/node/runtime/src/weights/pallet_elections_phragmen.rs @@ -27,13 +27,13 @@ use sp_std::marker::PhantomData; pub struct WeightInfo(PhantomData); impl pallet_elections_phragmen::WeightInfo for WeightInfo { - fn vote_more(v: u32, ) -> Weight { + fn vote_more(_v: u32, ) -> Weight { 0 } - fn vote_less(v: u32, ) -> Weight { + fn vote_less(_v: u32, ) -> Weight { 0 } - fn vote_equal(v: u32, ) -> Weight { + fn vote_equal(_v: u32, ) -> Weight { 0 } fn remove_voter() -> Weight { diff --git a/frame/elections-phragmen/src/default_weights.rs b/frame/elections-phragmen/src/default_weights.rs index fe78292fae2fd..30cf3e44b6091 100644 --- a/frame/elections-phragmen/src/default_weights.rs +++ b/frame/elections-phragmen/src/default_weights.rs @@ -23,13 +23,13 @@ use frame_support::weights::{Weight, constants::RocksDbWeight as DbWeight}; impl crate::WeightInfo for () { - fn vote_equal(v: u32) -> Weight { + fn vote_equal(_v: u32) -> Weight { 0 } - fn vote_more(v: u32) -> Weight { + fn vote_more(_v: u32) -> Weight { 0 } - fn vote_less(v: u32) -> Weight { + fn vote_less(_v: u32) -> Weight { 0 } fn remove_voter() -> Weight { From 820d76fabd7227e93f538f5b1caa86459b804955 Mon Sep 17 00:00:00 2001 From: Parity Benchmarking Bot Date: Tue, 13 Oct 2020 11:57:18 +0000 Subject: [PATCH 31/69] cargo run --release --features runtime-benchmarks --manifest-path bin/node/cli/Cargo.toml -- benchmark --chain dev --steps 50 --repeat 20 --extrinsic * --execution=wasm --wasm-execution=compiled --output ./bin/node/runtime/src/weights --header ./HEADER --pallet pallet_elections_phragmen --- .../src/weights/pallet_elections_phragmen.rs | 47 +++++++++++-------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/bin/node/runtime/src/weights/pallet_elections_phragmen.rs b/bin/node/runtime/src/weights/pallet_elections_phragmen.rs index dfe9e2a1a272a..0d8cde5cedbe5 100644 --- a/bin/node/runtime/src/weights/pallet_elections_phragmen.rs +++ b/bin/node/runtime/src/weights/pallet_elections_phragmen.rs @@ -27,62 +27,71 @@ use sp_std::marker::PhantomData; pub struct WeightInfo(PhantomData); impl pallet_elections_phragmen::WeightInfo for WeightInfo { - fn vote_more(_v: u32, ) -> Weight { - 0 + fn vote_equal(v: u32, ) -> Weight { + (57_810_000 as Weight) + .saturating_add((453_000 as Weight).saturating_mul(v as Weight)) + .saturating_add(T::DbWeight::get().reads(5 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) } - fn vote_less(_v: u32, ) -> Weight { - 0 + fn vote_more(v: u32, ) -> Weight { + (86_986_000 as Weight) + .saturating_add((492_000 as Weight).saturating_mul(v as Weight)) + .saturating_add(T::DbWeight::get().reads(5 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) } - fn vote_equal(_v: u32, ) -> Weight { - 0 + fn vote_less(v: u32, ) -> Weight { + (82_556_000 as Weight) + .saturating_add((500_000 as Weight).saturating_mul(v as Weight)) + .saturating_add(T::DbWeight::get().reads(5 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) } fn remove_voter() -> Weight { - (81_517_000 as Weight) + (81_184_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } fn report_defunct_voter_correct(c: u32, v: u32, ) -> Weight { (0 as Weight) - .saturating_add((1_734_000 as Weight).saturating_mul(c as Weight)) - .saturating_add((31_506_000 as Weight).saturating_mul(v as Weight)) + .saturating_add((1_761_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((31_934_000 as Weight).saturating_mul(v as Weight)) .saturating_add(T::DbWeight::get().reads(6 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } fn report_defunct_voter_incorrect(c: u32, v: u32, ) -> Weight { (0 as Weight) - .saturating_add((1_699_000 as Weight).saturating_mul(c as Weight)) - .saturating_add((31_489_000 as Weight).saturating_mul(v as Weight)) + .saturating_add((1_742_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((31_613_000 as Weight).saturating_mul(v as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) } fn submit_candidacy(c: u32, ) -> Weight { - (73_383_000 as Weight) - .saturating_add((326_000 as Weight).saturating_mul(c as Weight)) + (74_910_000 as Weight) + .saturating_add((312_000 as Weight).saturating_mul(c as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } fn renounce_candidacy_candidate(c: u32, ) -> Weight { - (50_694_000 as Weight) - .saturating_add((154_000 as Weight).saturating_mul(c as Weight)) + (50_086_000 as Weight) + .saturating_add((166_000 as Weight).saturating_mul(c as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } fn renounce_candidacy_members() -> Weight { - (77_808_000 as Weight) + (78_637_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } fn renounce_candidacy_runners_up() -> Weight { - (48_452_000 as Weight) + (48_856_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } fn remove_member_with_replacement() -> Weight { - (75_784_000 as Weight) + (75_570_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } fn remove_member_wrong_refund() -> Weight { - (9_099_000 as Weight) + (8_788_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) } } From 4caea9fae5077122853b2cf2dc3501532de34d43 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Tue, 13 Oct 2020 13:58:58 +0200 Subject: [PATCH 32/69] Tune the stake again --- frame/elections-phragmen/src/benchmarking.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/elections-phragmen/src/benchmarking.rs b/frame/elections-phragmen/src/benchmarking.rs index 6545ca1d4f230..1a5010907c6a4 100644 --- a/frame/elections-phragmen/src/benchmarking.rs +++ b/frame/elections-phragmen/src/benchmarking.rs @@ -26,7 +26,7 @@ use frame_support::traits::OnInitialize; use crate::Module as Elections; -const BALANCE_FACTOR: u32 = 25_000; +const BALANCE_FACTOR: u32 = 250; const MAX_VOTERS: u32 = 500; const MAX_CANDIDATES: u32 = 200; From df267fab778fcfe0d9a7db1cee005e45376c0839 Mon Sep 17 00:00:00 2001 From: Parity Benchmarking Bot Date: Tue, 13 Oct 2020 12:04:16 +0000 Subject: [PATCH 33/69] cargo run --release --features runtime-benchmarks --manifest-path bin/node/cli/Cargo.toml -- benchmark --chain dev --steps 50 --repeat 20 --extrinsic * --execution=wasm --wasm-execution=compiled --output ./bin/node/runtime/src/weights --header ./HEADER --pallet pallet_elections_phragmen --- .../src/weights/pallet_elections_phragmen.rs | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/bin/node/runtime/src/weights/pallet_elections_phragmen.rs b/bin/node/runtime/src/weights/pallet_elections_phragmen.rs index 0d8cde5cedbe5..dc0e61034e864 100644 --- a/bin/node/runtime/src/weights/pallet_elections_phragmen.rs +++ b/bin/node/runtime/src/weights/pallet_elections_phragmen.rs @@ -28,70 +28,70 @@ use sp_std::marker::PhantomData; pub struct WeightInfo(PhantomData); impl pallet_elections_phragmen::WeightInfo for WeightInfo { fn vote_equal(v: u32, ) -> Weight { - (57_810_000 as Weight) - .saturating_add((453_000 as Weight).saturating_mul(v as Weight)) + (57_231_000 as Weight) + .saturating_add((462_000 as Weight).saturating_mul(v as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } fn vote_more(v: u32, ) -> Weight { - (86_986_000 as Weight) - .saturating_add((492_000 as Weight).saturating_mul(v as Weight)) + (87_662_000 as Weight) + .saturating_add((442_000 as Weight).saturating_mul(v as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } fn vote_less(v: u32, ) -> Weight { - (82_556_000 as Weight) - .saturating_add((500_000 as Weight).saturating_mul(v as Weight)) + (82_888_000 as Weight) + .saturating_add((481_000 as Weight).saturating_mul(v as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } fn remove_voter() -> Weight { - (81_184_000 as Weight) + (81_946_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } fn report_defunct_voter_correct(c: u32, v: u32, ) -> Weight { (0 as Weight) - .saturating_add((1_761_000 as Weight).saturating_mul(c as Weight)) - .saturating_add((31_934_000 as Weight).saturating_mul(v as Weight)) + .saturating_add((1_744_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((31_999_000 as Weight).saturating_mul(v as Weight)) .saturating_add(T::DbWeight::get().reads(6 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } fn report_defunct_voter_incorrect(c: u32, v: u32, ) -> Weight { (0 as Weight) - .saturating_add((1_742_000 as Weight).saturating_mul(c as Weight)) - .saturating_add((31_613_000 as Weight).saturating_mul(v as Weight)) + .saturating_add((1_725_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((31_480_000 as Weight).saturating_mul(v as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) } fn submit_candidacy(c: u32, ) -> Weight { - (74_910_000 as Weight) - .saturating_add((312_000 as Weight).saturating_mul(c as Weight)) + (72_704_000 as Weight) + .saturating_add((348_000 as Weight).saturating_mul(c as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } fn renounce_candidacy_candidate(c: u32, ) -> Weight { - (50_086_000 as Weight) - .saturating_add((166_000 as Weight).saturating_mul(c as Weight)) + (49_516_000 as Weight) + .saturating_add((184_000 as Weight).saturating_mul(c as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } fn renounce_candidacy_members() -> Weight { - (78_637_000 as Weight) + (78_634_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } fn renounce_candidacy_runners_up() -> Weight { - (48_856_000 as Weight) + (49_294_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } fn remove_member_with_replacement() -> Weight { - (75_570_000 as Weight) + (75_374_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } fn remove_member_wrong_refund() -> Weight { - (8_788_000 as Weight) + (8_586_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) } } From 8bd074f5d13a37050049bcd3afdb51172a814c2e Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 18 Nov 2020 11:18:08 +0000 Subject: [PATCH 34/69] All tests work again. --- frame/elections-phragmen/src/lib.rs | 764 +++++++++++++++--------- frame/elections-phragmen/src/weights.rs | 20 +- 2 files changed, 486 insertions(+), 298 deletions(-) diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index 5e730eafa82c3..bc602a7ad15da 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -126,7 +126,7 @@ pub mod migrations { /// Migrate from the old legacy voting bond (fixed) to the new one (per-vote dynamic). /// /// Will only be triggered if storage version is V1. - pub fn migrate_to_recorded_deposit(old_deposit: BalanceOf) -> Weight { + pub fn migrate_voters_to_recorded_deposit(old_deposit: BalanceOf) -> Weight { if >::pallet_storage_version() == StorageVersion::V1 { let mut count = 0; , Vec), Twox64Concat>>::new( @@ -141,7 +141,7 @@ pub mod migrations { count += 1; }); - PalletStorageVersion::put(StorageVersion::V2PerVoterDeposit); + PalletStorageVersion::put(StorageVersion::V2RecordedDeposit); frame_support::debug::info!( "🏛 pallet-elections-phragmen: {} voters migrated to V2PerVoterDeposit.", count, @@ -194,13 +194,24 @@ pub struct Voter { pub deposit: Balance, } +/// A holder of a seat as either a member or a runner-up. +#[derive(Encode, Decode, Clone, Default, RuntimeDebug, PartialEq)] +pub struct SeatHolder { + /// The holder. + who: AccountId, + /// The total backing stake. + stake: Balance, + /// The amount of deposit held.s + deposit: Balance, +} + /// Storage version. #[derive(Encode, Decode, Eq, PartialEq)] pub enum StorageVersion { /// Initial version. V1, /// After moving to per-vote deposit. - V2PerVoterDeposit, + V2RecordedDeposit, } pub trait Trait: frame_system::Trait { @@ -265,9 +276,11 @@ decl_storage! { trait Store for Module as PhragmenElection { // ---- State /// The current elected membership. Sorted based on account id. - pub Members get(fn members): Vec<(T::AccountId, BalanceOf)>; + /// TODO: Migration + pub Members get(fn members): Vec>>; /// The current runners_up. Sorted based on low to high merit (worse to best). - pub RunnersUp get(fn runners_up): Vec<(T::AccountId, BalanceOf)>; + /// TODO: Migration + pub RunnersUp get(fn runners_up): Vec>>; /// The total number of vote rounds that have happened, excluding the upcoming one. pub ElectionRounds get(fn election_rounds): u32 = Zero::zero(); @@ -278,12 +291,15 @@ decl_storage! { /// The present candidate list. Sorted based on account-id. A current member or runner-up /// can never enter this vector and is always implicitly assumed to be a candidate. - pub Candidates get(fn candidates): Vec; + /// + /// Second element is the deposit. + /// TODO: migration + pub Candidates get(fn candidates): Vec<(T::AccountId, BalanceOf)>; /// Should be used in conjunction with `on_runtime_upgrade` to ensure an upgrade is executed /// once, even if the code is not removed in time. pub PalletStorageVersion get(fn pallet_storage_version) - build(|_| StorageVersion::V2PerVoterDeposit): StorageVersion = StorageVersion::V1; + build(|_| StorageVersion::V2RecordedDeposit): StorageVersion = StorageVersion::V1; } add_extra_genesis { config(members): Vec<(T::AccountId, BalanceOf)>; @@ -303,9 +319,12 @@ decl_storage! { // their own stake as total backing. Any sane election should behave as such. // Nonetheless, stakes will be updated for term 1 onwards according to the election. Members::::mutate(|members| { - match members.binary_search_by(|(a, _b)| a.cmp(member)) { + match members.binary_search_by(|m| m.who.cmp(member)) { Ok(_) => panic!("Duplicate member in elections phragmen genesis: {}", member), - Err(pos) => members.insert(pos, (member.clone(), *stake)), + Err(pos) => members.insert( + pos, + SeatHolder { who: member.clone(), stake: *stake, deposit: 0u32.into() }, + ), } }); @@ -367,7 +386,6 @@ decl_error! { decl_module! { pub struct Module for enum Call where origin: T::Origin { type Error = Error; - fn deposit_event() = default; const CandidacyBond: BalanceOf = T::CandidacyBond::get(); @@ -596,7 +614,7 @@ decl_module! { T::Currency::reserve(&who, T::CandidacyBond::get()) .map_err(|_| Error::::InsufficientCandidateFunds)?; - >::mutate(|c| c.insert(index, who)); + >::mutate(|c| c.insert(index, (who, T::CandidacyBond::get()))); } /// Renounce one's intention to be a candidate for the next election round. 3 potential @@ -645,21 +663,18 @@ decl_module! { match renouncing { Renouncing::Member => { // returns NoMember error in case of error. - let _ = Self::remove_and_replace_member(&who)?; - T::Currency::unreserve(&who, T::CandidacyBond::get()); + let _ = Self::remove_and_replace_member(&who, false)?; Self::deposit_event(RawEvent::MemberRenounced(who)); }, Renouncing::RunnerUp => { - let mut runners_up_with_stake = Self::runners_up(); - if let Some(index) = runners_up_with_stake + let mut runners_up = Self::runners_up(); + if let Some(index) = runners_up .iter() - .position(|(ref r, ref _s)| r == &who) + .position(|SeatHolder { who: r, .. }| r == &who) { - runners_up_with_stake.remove(index); - // unreserve the bond - T::Currency::unreserve(&who, T::CandidacyBond::get()); - // update storage. - >::put(runners_up_with_stake); + let SeatHolder { deposit, .. } = runners_up.remove(index); + T::Currency::unreserve(&who, deposit); + >::put(runners_up); } else { Err(Error::::InvalidRenouncing)?; } @@ -667,10 +682,10 @@ decl_module! { Renouncing::Candidate(count) => { let mut candidates = Self::candidates(); ensure!(count >= candidates.len() as u32, Error::::InvalidRenouncing); - if let Some(index) = candidates.iter().position(|x| *x == who) { - candidates.remove(index); + if let Some(index) = candidates.iter().position(|(x, _)| *x == who) { + let (_removed, deposit) = candidates.remove(index); // unreserve the bond - T::Currency::unreserve(&who, T::CandidacyBond::get()); + T::Currency::unreserve(&who, deposit); // update storage. >::put(candidates); } else { @@ -721,9 +736,8 @@ decl_module! { )); } // else, prediction was correct. - 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::remove_and_replace_member(&who, true).map(|had_replacement| { + // TODO: test for all the folks who call into `remove_and_replace_member`. Self::deposit_event(RawEvent::MemberKicked(who.clone())); if !had_replacement { @@ -779,45 +793,71 @@ impl Module { } /// Attempts to remove a member `who`. If a runner-up exists, it is used as the replacement and - /// Ok(true). is returned. + /// Ok(true) is returned. /// /// Otherwise, `Ok(false)` is returned to signal the caller. /// - /// If a replacement exists, `Members` and `RunnersUp` storage is updated, where the first - /// element of `RunnersUp` is used as the replacement and `Ok(true)` is returned. Else, - /// `Ok(false)` is returned with no storage updated. + /// If the `who` is not a member, `Err` is returned. /// - /// Note that this function _will_ call into `T::ChangeMembers` in case any change happens - /// (`Ok(true)`). - /// - /// If replacement exists, this will read and write from/into both `Members` and `RunnersUp`. - fn remove_and_replace_member(who: &T::AccountId) -> Result { - 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 = >::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)) + /// Both `Members` and `RunnersUp` storage is updated accordingly. `T::ChangeMember` is called + /// if needed. If `slash` is true, the deposit of the potentially removed member is slashed, + /// else it is unreserved. + fn remove_and_replace_member(who: &T::AccountId, slash: bool) -> Result { + // closure will return: + // Ok(Option(replacement)) if member was removed and replacement was replaced. + // Ok(None) if member was removed but no replacement was found + // Err(_) if who is not a member. + // TODO: what if we remove the prime. + let outcome = >::try_mutate(|members| { + // we remove the member anyhow, regardless of having a runner-up or not. + let remove_index = members + .binary_search_by(|m| m.who.cmp(who)) + .map_err(|_| Error::::NotMember)?; + let removed = members.remove(remove_index); + if slash { + let (imbalance, _remaining) = T::Currency::slash_reserved(who, removed.deposit); + debug_assert_eq!(_remaining, 0u32.into()); + T::LoserCandidate::on_unbalanced(imbalance); + // TODO: emit event. + } else { + T::Currency::unreserve(who, removed.deposit); + } + + Ok(>::mutate(|r| r.pop()).map(|next_best| { + // invariant: Members and runners-up are disjoint. This will always be err and give + // us an index. + members + .binary_search_by(|m| m.who.cmp(&next_best.who)) .err() .map(|index| { - members_with_stake.insert(index, (replacement.clone(), stake)); - replacement - }) - ); + members.insert(index, next_best.clone()); + }); + next_best + })) + }); - >::put(&members_with_stake); - let members = members_with_stake.into_iter().map(|m| m.0).collect::>(); - let result = Ok(maybe_replacement.is_some()); - let old = [who.clone()]; + outcome.map(|maybe_replacement| { + let member_ids = Self::members() + .into_iter() + .map(|x| x.who.clone()) + .collect::>(); + let outgoing = &[who.clone()]; match maybe_replacement { - Some(new) => T::ChangeMembers::change_members_sorted(&[new], &old, &members), - None => T::ChangeMembers::change_members_sorted(&[], &old, &members), + // member ids are already sorted, other two elements have one item. + Some(incoming) => { + T::ChangeMembers::change_members_sorted( + &[incoming.who], + outgoing, + &member_ids[..], + ); + true + } + None => { + T::ChangeMembers::change_members_sorted(&[], outgoing, &member_ids[..]); + false + } } - result - } else { - Err(Error::::NotMember)? - } + }) } /// Check if `who` is a candidate. It returns the insert index if the element does not exists as @@ -825,7 +865,10 @@ impl Module { /// /// O(LogN) given N candidates. fn is_candidate(who: &T::AccountId) -> Result<(), usize> { - Self::candidates().binary_search(who).map(|_| ()) + Self::candidates() + // TODO: review all use cases of binary-search + .binary_search_by(|c| c.0.cmp(who)) + .map(|_| ()) } /// Check if `who` is a voter. It may or may not be a _current_ one. @@ -839,14 +882,17 @@ impl Module { /// /// O(LogN) given N members. Since members are limited, O(1). fn is_member(who: &T::AccountId) -> bool { - Self::members().binary_search_by(|(a, _b)| a.cmp(who)).is_ok() + Self::members().binary_search_by(|m| m.who.cmp(who)).is_ok() } /// Check if `who` is currently an active runner-up. /// /// O(LogN) given N runners-up. Since runners-up are limited, O(1). fn is_runner_up(who: &T::AccountId) -> bool { - Self::runners_up().iter().position(|(a, _b)| a == who).is_some() + Self::runners_up() + .iter() + .position(|r| &r.who == who) + .is_some() } /// Returns number of desired members. @@ -866,12 +912,50 @@ impl Module { /// Get the members' account ids. fn members_ids() -> Vec { - Self::members().into_iter().map(|(m, _)| m).collect::>() + Self::members() + .into_iter() + .map(|m| m.who) + .collect::>() } /// The the runners' up account ids. fn runners_up_ids() -> Vec { - Self::runners_up().into_iter().map(|(r, _)| r).collect::>() + Self::runners_up() + .into_iter() + .map(|r| r.who) + .collect::>() + } + + /// Get the members and their recorded deposit. + /// + /// This can be concatenated with [`Self::candidates`]. + fn members_and_deposit() -> Vec<(T::AccountId, BalanceOf)> { + Self::members() + .into_iter() + .map(|m| (m.who, m.deposit)) + .collect::>() + } + + /// Get the runners-up and their recorded deposit. + /// + /// This can be concatenated with [`Self::candidates`]. + fn runners_up_and_deposit() -> Vec<(T::AccountId, BalanceOf)> { + Self::runners_up() + .into_iter() + .map(|r| (r.who, r.deposit)) + .collect::>() + } + + /// Get a concatenation of previous members and runners-up and their deposits. + /// + /// These accounts are essentially treated as candidates. + fn implicit_candidates() -> Vec<(T::AccountId, BalanceOf)> { + // TODO: this is not optimized. Allocation is not needed. + // TODO: invariant: these two are always without duplicates. + Self::members_and_deposit() + .into_iter() + .chain(Self::runners_up_and_deposit().into_iter()) + .collect::>() } /// Check if `votes` will correspond to a defunct voter. As no origin is part of the inputs, @@ -931,21 +1015,23 @@ impl Module { let desired_runners_up = Self::desired_runners_up() as usize; let num_to_elect = desired_runners_up + desired_seats; - let mut candidates = Self::candidates(); - // candidates who explicitly called `submit_candidacy`. Only these folks are at risk of - // losing their bond. - let exposed_candidates = candidates.clone(); - // current members are always a candidate for the next round as well. - // this is guaranteed to not create any duplicates. - candidates.append(&mut Self::members_ids()); - // previous runners_up are also always candidates for the next round. - candidates.append(&mut Self::runners_up_ids()); - - if candidates.len().is_zero() { + // TODO: what if we change deposit this on the fly? + let mut candidates_and_deposit = Self::candidates(); + // add all the previous members and runners-up as candidates as well. + candidates_and_deposit.append(&mut Self::implicit_candidates()); + + if candidates_and_deposit.len().is_zero() { Self::deposit_event(RawEvent::EmptyTerm); return; } + // All of the new winners that come out of phragmen will thus have a deposit recorded. + let candidate_ids = candidates_and_deposit + .iter() + .map(|(x, _)| x) + .cloned() + .collect::>(); + // helper closures to deal with balance/stake. let total_issuance = T::Currency::total_issuance(); let to_votes = |b: BalanceOf| T::CurrencyToVote::to_vote(b, total_issuance); @@ -963,146 +1049,164 @@ impl Module { let _ = sp_npos_elections::seq_phragmen::( num_to_elect, - candidates, + candidate_ids, voters_and_votes.clone(), None, - ).map(|ElectionResult { winners, assignments: _ }| { - // this is already sorted by id. - let old_members_ids_sorted = >::take().into_iter() - .map(|(m, _)| m) - .collect::>(); - // this one needs a sort by id. - let mut old_runners_up_ids_sorted = >::take().into_iter() - .map(|(r, _)| r) - .collect::>(); - old_runners_up_ids_sorted.sort(); - - // filter out those who end up with no backing stake. - let new_set_with_stake = winners - .into_iter() - .filter_map(|(m, b)| if b.is_zero() { None } else { Some((m, to_balance(b))) }) - .collect::)>>(); - - // OPTIMISATION NOTE: we could bail out here if `new_set.len() == 0`. There isn't much - // left to do. Yet, re-arranging the code would require duplicating the slashing of - // exposed candidates, cleaning any previous members, and so on. For now, in favour of - // readability and veracity, we keep it simple. - - // split new set into winners and runners up. - let split_point = desired_seats.min(new_set_with_stake.len()); - let mut new_members_sorted_by_id = (&new_set_with_stake[..split_point]).to_vec(); - - // save the runners up as-is. They are sorted based on desirability. - // save the members, sorted based on account id. - new_members_sorted_by_id.sort_by(|i, j| i.0.cmp(&j.0)); - - // Now we select a prime member using a [Borda count](https://en.wikipedia.org/wiki/Borda_count). - // We weigh everyone's vote for that new member by a multiplier based on the order - // of the votes. i.e. the first person a voter votes for gets a 16x multiplier, - // the next person gets a 15x multiplier, an so on... (assuming `MAXIMUM_VOTE` = 16) - let mut prime_votes: Vec<_> = new_members_sorted_by_id.iter().map(|c| (&c.0, BalanceOf::::zero())).collect(); - for (_, stake, votes) in voters_and_stakes.into_iter() { - for (vote_multiplier, who) in votes.iter() - .enumerate() - .map(|(vote_position, who)| ((MAXIMUM_VOTE - vote_position) as u32, who)) - { - if let Ok(i) = prime_votes.binary_search_by_key(&who, |k| k.0) { - prime_votes[i].1 = prime_votes[i].1.saturating_add( - stake.saturating_mul(vote_multiplier.into()) - ); + ) + .map( + |ElectionResult { + winners, + assignments: _, + }| { + // this is already sorted by id. + let old_members_ids_sorted = >::take() + .into_iter() + .map(|m| m.who) + .collect::>(); + // this one needs a sort by id. + let mut old_runners_up_ids_sorted = >::take() + .into_iter() + .map(|r| r.who) + .collect::>(); + old_runners_up_ids_sorted.sort(); + + // filter out those who end up with no backing stake. This is a logic specific to this + // module. + let new_set_with_stake = winners + .into_iter() + .filter_map(|(m, b)| { + if b.is_zero() { + None + } else { + Some((m, to_balance(b))) + } + }) + .collect::)>>(); + + // OPTIMISATION NOTE: we could bail out here if `new_set.len() == 0`. There isn't much + // left to do. Yet, re-arranging the code would require duplicating the slashing of + // exposed candidates, cleaning any previous members, and so on. For now, in favour of + // readability and veracity, we keep it simple. + + // split new set into winners and runners up. + let split_point = desired_seats.min(new_set_with_stake.len()); + + let mut new_members_sorted_by_id = (&new_set_with_stake[..split_point]).to_vec(); + new_members_sorted_by_id.sort_by(|i, j| i.0.cmp(&j.0)); + + let new_runners_up_sorted_by_rank = (&new_set_with_stake[split_point..]) + .to_vec() + .into_iter() + .rev() + .collect::)>>(); + let mut new_runners_up_ids_sorted = new_runners_up_sorted_by_rank + .iter() + .map(|(r, _)| r.clone()) + .collect::>(); + new_runners_up_ids_sorted.sort(); + + // Now we select a prime member using a [Borda + // count](https://en.wikipedia.org/wiki/Borda_count). We weigh everyone's vote for that + // new member by a multiplier based on the order of the votes. i.e. the first person a + // voter votes for gets a 16x multiplier, the next person gets a 15x multiplier, an so + // on... (assuming `MAXIMUM_VOTE` = 16) + let mut prime_votes = new_members_sorted_by_id + .iter() + .map(|c| (&c.0, BalanceOf::::zero())) + .collect::>(); + for (_, stake, votes) in voters_and_stakes.into_iter() { + for (vote_multiplier, who) in votes + .iter() + .enumerate() + .map(|(vote_position, who)| ((MAXIMUM_VOTE - vote_position) as u32, who)) + { + if let Ok(i) = prime_votes.binary_search_by_key(&who, |k| k.0) { + prime_votes[i].1 = prime_votes[i] + .1 + .saturating_add(stake.saturating_mul(vote_multiplier.into())); + } } } - } - // We then select the new member with the highest weighted stake. In the case of - // a tie, the last person in the list with the tied score is selected. This is - // the person with the "highest" account id based on the sort above. - let prime = prime_votes.into_iter().max_by_key(|x| x.1).map(|x| x.0.clone()); - - // new_members_sorted_by_id is sorted by account id. - let new_members_ids_sorted = new_members_sorted_by_id - .iter() - .map(|(m, _)| m.clone()) - .collect::>(); - - let new_runners_up_sorted_by_rank = &new_set_with_stake[split_point..] - .into_iter() - .cloned() - .rev() - .collect::)>>(); - // new_runners_up remains sorted by desirability. - let mut new_runners_up_ids_sorted = new_runners_up_sorted_by_rank - .iter() - .map(|(r, _)| r.clone()) - .collect::>(); - new_runners_up_ids_sorted.sort(); - - // report member changes. We compute diff because we need the outgoing list. - let (incoming, outgoing) = T::ChangeMembers::compute_members_diff( - &new_members_ids_sorted, - &old_members_ids_sorted, - ); - T::ChangeMembers::change_members_sorted( - &incoming, - &outgoing, - &new_members_ids_sorted, - ); - T::ChangeMembers::set_prime(prime); - - // outgoing members who are no longer a runner-up lose their bond. - let mut to_burn_bond = outgoing - .iter() - .filter(|o| new_runners_up_ids_sorted.binary_search(o).is_err()) - .cloned() - .collect::>(); - - // compute the outgoing of runners up as well and append them to the `to_burn_bond`, if - // they are not members. - { - let (_, outgoing) = T::ChangeMembers::compute_members_diff( - &new_runners_up_ids_sorted, - &old_runners_up_ids_sorted, + // We then select the new member with the highest weighted stake. In the case of + // a tie, the last person in the list with the tied score is selected. This is + // the person with the "highest" account id based on the sort above. + let prime = prime_votes + .into_iter() + .max_by_key(|x| x.1) + .map(|x| x.0.clone()); + + // new_members_sorted_by_id is sorted by account id. + let new_members_ids_sorted = new_members_sorted_by_id + .iter() + .map(|(m, _)| m.clone()) + .collect::>(); + + // report member changes. We compute diff because we need the outgoing list. + let (incoming, outgoing) = T::ChangeMembers::compute_members_diff( + &new_members_ids_sorted, + &old_members_ids_sorted, ); - // none of the ones computed to be outgoing must still be in the list. - debug_assert!(outgoing.iter().all(|o| !new_runners_up_ids_sorted.contains(o))); - to_burn_bond.extend( - outgoing - .iter() - .filter(|o| new_members_ids_sorted.binary_search(o).is_err()) - .cloned() - .collect::>() + T::ChangeMembers::change_members_sorted( + &incoming, + &outgoing, + &new_members_ids_sorted, ); - } - - // Burn loser bond. members list is sorted. O(NLogM) (N candidates, M members) - // runner up list is also sorted. O(NLogK) given K runner ups. Overall: O(NLogM + N*K) - // both the member and runner counts are bounded. - exposed_candidates.into_iter().for_each(|c| { - // any candidate who is not a member and not a runner up. - if - new_members_ids_sorted.binary_search(&c).is_err() && - new_runners_up_ids_sorted.binary_search(&c).is_err() - { - let (imbalance, _) = T::Currency::slash_reserved(&c, T::CandidacyBond::get()); - T::LoserCandidate::on_unbalanced(imbalance); - } - }); - - // Burn outgoing bonds - to_burn_bond.into_iter().for_each(|x| { - let (imbalance, _) = T::Currency::slash_reserved(&x, T::CandidacyBond::get()); - T::LoserCandidate::on_unbalanced(imbalance); - }); - - >::put(&new_members_sorted_by_id); - >::put(new_runners_up_sorted_by_rank); + T::ChangeMembers::set_prime(prime); + + // All candidates/members/runners-up who are no longer retaining a position as a seat + // holder will lose their bond. + candidates_and_deposit.iter().for_each(|(c, d)| { + // any candidate who is not a member and not a runner up. + if new_members_ids_sorted.binary_search(c).is_err() + && new_runners_up_ids_sorted.binary_search(c).is_err() + { + let (imbalance, _) = T::Currency::slash_reserved(c, *d); + T::LoserCandidate::on_unbalanced(imbalance); + // TODO: emit event. + } + }); - Self::deposit_event(RawEvent::NewTerm(new_members_sorted_by_id.clone().to_vec())); + // write final values to storage. + let deposit_of_candidate = |x: &T::AccountId| -> BalanceOf { + // defensive-only. This closure is used against the new members and new runners-up, + // both of which are phragmen winners and thus must have deposit. + candidates_and_deposit + .iter() + .find_map(|(c, d)| if c == x { Some(*d) } else { None }) + .unwrap_or_default() + }; + // fetch deposits from the one recorded one. This will make sure that a candidate who + // submitted candidacy before a change to candidacy deposit will have the correct amount + // recorded. + >::put( + new_members_sorted_by_id + .iter() + .map(|(who, stake)| SeatHolder { + deposit: deposit_of_candidate(&who), + who: who.clone(), + stake: stake.clone(), + }) + .collect::>(), + ); + >::put( + new_runners_up_sorted_by_rank + .into_iter() + .map(|(who, stake)| SeatHolder { + deposit: deposit_of_candidate(&who), + who, + stake, + }) + .collect::>(), + ); - // clean candidates. - >::kill(); + // clean candidates. + >::kill(); - ElectionRounds::mutate(|v| *v += 1); - }).map_err(|e| { + Self::deposit_event(RawEvent::NewTerm(new_members_sorted_by_id)); + ElectionRounds::mutate(|v| *v += 1); + }, + ) + .map_err(|e| { frame_support::debug::error!("elections-phragmen: failed to run election [{:?}].", e); Self::deposit_event(RawEvent::ElectionError); }); @@ -1405,6 +1509,27 @@ mod tests { } } + fn candidate_ids() -> Vec { + Elections::candidates() + .into_iter() + .map(|(c, _)| c) + .collect::>() + } + + fn members_and_stake() -> Vec<(u64, u64)> { + Elections::members() + .into_iter() + .map(|m| (m.who, m.stake)) + .collect::>() + } + + fn runners_up_and_stake() -> Vec<(u64, u64)> { + Elections::runners_up() + .into_iter() + .map(|r| (r.who, r.stake)) + .collect::>() + } + fn all_voters() -> Vec { Voting::::iter().map(|(v, _)| v).collect::>() } @@ -1425,7 +1550,7 @@ mod tests { fn ensure_members_sorted() { let mut members = Elections::members().clone(); - members.sort(); + members.sort_by_key(|m| m.who); assert_eq!(Elections::members(), members); } @@ -1442,18 +1567,20 @@ mod tests { fn ensure_members_has_approval_stake() { // we filter members that have no approval state. This means that even we have more seats // than candidates, we will never ever chose a member with no votes. - assert!( - Elections::members().iter().chain( - Elections::runners_up().iter() - ).all(|(_, s)| *s != u64::zero()) - ); + assert!(Elections::members() + .iter() + .chain(Elections::runners_up().iter()) + .all(|s| s.stake != u64::zero())); } fn ensure_member_candidates_runners_up_disjoint() { // members, candidates and runners-up must always be disjoint sets. - assert!(!intersects(&Elections::members_ids(), &Elections::candidates())); - assert!(!intersects(&Elections::members_ids(), &Elections::runners_up_ids())); - assert!(!intersects(&Elections::candidates(), &Elections::runners_up_ids())); + assert!(!intersects(&Elections::members_ids(), &candidate_ids())); + assert!(!intersects( + &Elections::members_ids(), + &Elections::runners_up_ids() + )); + assert!(!intersects(&candidate_ids(), &Elections::runners_up_ids())); } fn pre_conditions() { @@ -1503,7 +1630,7 @@ mod tests { assert!(Elections::members().is_empty()); assert!(Elections::runners_up().is_empty()); - assert!(Elections::candidates().is_empty()); + assert!(candidate_ids().is_empty()); assert_eq!(>::decode_len(), None); assert!(Elections::is_candidate(&1).is_err()); @@ -1514,36 +1641,97 @@ mod tests { #[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)]); + ExtBuilder::default() + .genesis_members(vec![(1, 10), (2, 20)]) + .build_and_execute(|| { + System::set_block_number(1); + // TODO: genesis members should also serve bond. + assert_eq!( + Elections::members(), + vec![ + SeatHolder { + who: 1, + stake: 10, + deposit: 0 + }, + SeatHolder { + who: 2, + stake: 20, + deposit: 0 + } + ] + ); - assert_eq!(Elections::voting(1), Voter { stake: 10u64, votes: vec![1], deposit: 2 }); - assert_eq!(Elections::voting(2), Voter { stake: 20u64, votes: vec![2], deposit: 2 }); + assert_eq!( + Elections::voting(1), + Voter { + stake: 10u64, + votes: vec![1], + deposit: 2 + } + ); + assert_eq!( + Elections::voting(2), + Voter { + stake: 20u64, + votes: vec![2], + deposit: 2 + } + ); - // they will persist since they have self vote. - System::set_block_number(5); - Elections::end_block(System::block_number()); + // they will persist since they have self vote. + System::set_block_number(5); + Elections::end_block(System::block_number()); - assert_eq!(Elections::members_ids(), vec![1, 2]); - }) + 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)]); + ExtBuilder::default() + .genesis_members(vec![(2, 20), (1, 10)]) + .build_and_execute(|| { + System::set_block_number(1); + assert_eq!( + Elections::members(), + vec![ + SeatHolder { + who: 1, + stake: 10, + deposit: 0 + }, + SeatHolder { + who: 2, + stake: 20, + deposit: 0 + } + ] + ); - assert_eq!(Elections::voting(1), Voter { stake: 10u64, votes: vec![1], deposit: 2 }); - assert_eq!(Elections::voting(2), Voter { stake: 20u64, votes: vec![2], deposit: 2 }); + assert_eq!( + Elections::voting(1), + Voter { + stake: 10u64, + votes: vec![1], + deposit: 2 + } + ); + assert_eq!( + Elections::voting(2), + Voter { + stake: 20u64, + votes: vec![2], + deposit: 2 + } + ); - // they will persist since they have self vote. - System::set_block_number(5); - Elections::end_block(System::block_number()); + // they will persist since they have self vote. + System::set_block_number(5); + Elections::end_block(System::block_number()); - assert_eq!(Elections::members_ids(), vec![1, 2]); - }) + assert_eq!(Elections::members_ids(), vec![1, 2]); + }) } #[test] @@ -1585,21 +1773,21 @@ mod tests { assert!(Elections::members_ids().is_empty()); assert!(Elections::runners_up().is_empty()); - assert!(Elections::candidates().is_empty()); + assert!(candidate_ids().is_empty()); System::set_block_number(5); Elections::end_block(System::block_number()); assert!(Elections::members_ids().is_empty()); assert!(Elections::runners_up().is_empty()); - assert!(Elections::candidates().is_empty()); + assert!(candidate_ids().is_empty()); }); } #[test] fn simple_candidate_submission_should_work() { ExtBuilder::default().build_and_execute(|| { - assert_eq!(Elections::candidates(), Vec::::new()); + assert_eq!(candidate_ids(), Vec::::new()); assert!(Elections::is_candidate(&1).is_err()); assert!(Elections::is_candidate(&2).is_err()); @@ -1607,7 +1795,7 @@ mod tests { assert_ok!(submit_candidacy(Origin::signed(1))); assert_eq!(balances(&1), (7, 3)); - assert_eq!(Elections::candidates(), vec![1]); + assert_eq!(candidate_ids(), vec![1]); assert!(Elections::is_candidate(&1).is_ok()); assert!(Elections::is_candidate(&2).is_err()); @@ -1616,24 +1804,26 @@ mod tests { assert_ok!(submit_candidacy(Origin::signed(2))); assert_eq!(balances(&2), (17, 3)); - assert_eq!(Elections::candidates(), vec![1, 2]); + assert_eq!(candidate_ids(), vec![1, 2]); assert!(Elections::is_candidate(&1).is_ok()); assert!(Elections::is_candidate(&2).is_ok()); + + // TODO: check deposit. }); } #[test] fn simple_candidate_submission_with_no_votes_should_work() { ExtBuilder::default().build_and_execute(|| { - assert_eq!(Elections::candidates(), Vec::::new()); + assert_eq!(candidate_ids(), Vec::::new()); assert_ok!(submit_candidacy(Origin::signed(1))); assert_ok!(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!(candidate_ids(), vec![1, 2]); assert!(Elections::members_ids().is_empty()); assert!(Elections::runners_up().is_empty()); @@ -1643,7 +1833,7 @@ mod tests { assert!(Elections::is_candidate(&1).is_err()); assert!(Elections::is_candidate(&2).is_err()); - assert!(Elections::candidates().is_empty()); + assert!(candidate_ids().is_empty()); assert!(Elections::members_ids().is_empty()); assert!(Elections::runners_up().is_empty()); @@ -1653,9 +1843,9 @@ mod tests { #[test] fn dupe_candidate_submission_should_not_work() { ExtBuilder::default().build_and_execute(|| { - assert_eq!(Elections::candidates(), Vec::::new()); + assert_eq!(candidate_ids(), Vec::::new()); assert_ok!(submit_candidacy(Origin::signed(1))); - assert_eq!(Elections::candidates(), vec![1]); + assert_eq!(candidate_ids(), vec![1]); assert_noop!( submit_candidacy(Origin::signed(1)), Error::::DuplicatedCandidate, @@ -1675,7 +1865,7 @@ mod tests { assert_eq!(Elections::members_ids(), vec![5]); assert!(Elections::runners_up().is_empty()); - assert!(Elections::candidates().is_empty()); + assert!(candidate_ids().is_empty()); assert_noop!( submit_candidacy(Origin::signed(5)), @@ -1710,7 +1900,7 @@ mod tests { #[test] fn poor_candidate_submission_should_not_work() { ExtBuilder::default().build_and_execute(|| { - assert_eq!(Elections::candidates(), Vec::::new()); + assert_eq!(candidate_ids(), Vec::::new()); assert_noop!( submit_candidacy(Origin::signed(7)), Error::::InsufficientCandidateFunds, @@ -1721,7 +1911,7 @@ mod tests { #[test] fn simple_voting_should_work() { ExtBuilder::default().build_and_execute(|| { - assert_eq!(Elections::candidates(), Vec::::new()); + assert_eq!(candidate_ids(), Vec::::new()); assert_eq!(balances(&2), (20, 0)); assert_ok!(submit_candidacy(Origin::signed(5))); @@ -1735,7 +1925,7 @@ mod tests { #[test] fn can_vote_with_custom_stake() { ExtBuilder::default().build_and_execute(|| { - assert_eq!(Elections::candidates(), Vec::::new()); + assert_eq!(candidate_ids(), Vec::::new()); assert_eq!(balances(&2), (20, 0)); assert_ok!(submit_candidacy(Origin::signed(5))); @@ -1832,7 +2022,7 @@ mod tests { Elections::end_block(System::block_number()); assert_eq!(Elections::members_ids(), vec![4, 5]); - assert!(Elections::candidates().is_empty()); + assert!(candidate_ids().is_empty()); assert_ok!(vote(Origin::signed(3), vec![4, 5], 10)); }); @@ -1855,7 +2045,7 @@ mod tests { Elections::end_block(System::block_number()); assert_eq!(Elections::members_ids(), vec![4, 5]); - assert!(Elections::candidates().is_empty()); + assert!(candidate_ids().is_empty()); assert_ok!(vote(Origin::signed(3), vec![4, 5], 10)); assert_eq!(PRIME.with(|p| *p.borrow()), Some(4)); @@ -1881,7 +2071,7 @@ mod tests { Elections::end_block(System::block_number()); assert_eq!(Elections::members_ids(), vec![3, 5]); - assert!(Elections::candidates().is_empty()); + assert!(candidate_ids().is_empty()); assert_eq!(PRIME.with(|p| *p.borrow()), Some(5)); }); @@ -2098,7 +2288,7 @@ mod tests { assert_eq!(Elections::members_ids(), vec![4, 5]); assert_eq!(Elections::runners_up_ids(), vec![6]); - assert!(Elections::candidates().is_empty()); + assert!(candidate_ids().is_empty()); // all of them have a member or runner-up that they voted for. assert_eq!(Elections::is_defunct_voter(&votes_of(&5)), false); @@ -2134,7 +2324,7 @@ mod tests { Elections::end_block(System::block_number()); assert_eq!(Elections::members_ids(), vec![4, 5]); - assert!(Elections::candidates().is_empty()); + assert!(candidate_ids().is_empty()); assert_eq!(balances(&3), (28, 2)); assert_eq!(balances(&5), (45, 5)); @@ -2164,7 +2354,7 @@ mod tests { Elections::end_block(System::block_number()); assert_eq!(Elections::members_ids(), vec![4, 5]); - assert!(Elections::candidates().is_empty()); + assert!(candidate_ids().is_empty()); assert_eq!(balances(&4), (35, 5)); assert_eq!(balances(&5), (45, 5)); @@ -2197,7 +2387,7 @@ mod tests { assert_eq!(votes_of(&3), vec![3]); assert_eq!(votes_of(&4), vec![4]); - assert_eq!(Elections::candidates(), vec![3, 4, 5]); + assert_eq!(candidate_ids(), vec![3, 4, 5]); assert_eq!(>::decode_len().unwrap(), 3); assert_eq!(Elections::election_rounds(), 0); @@ -2205,10 +2395,10 @@ mod tests { System::set_block_number(5); Elections::end_block(System::block_number()); - assert_eq!(Elections::members(), vec![(3, 30), (5, 20)]); + assert_eq!(members_and_stake(), vec![(3, 30), (5, 20)]); assert!(Elections::runners_up().is_empty()); assert_eq_uvec!(all_voters(), vec![2, 3, 4]); - assert!(Elections::candidates().is_empty()); + assert!(candidate_ids().is_empty()); assert_eq!(>::decode_len(), None); assert_eq!(Elections::election_rounds(), 1); @@ -2246,8 +2436,8 @@ mod tests { Event::elections_phragmen(RawEvent::NewTerm(vec![(4, 40), (5, 50)])), ); - assert_eq!(Elections::members(), vec![(4, 40), (5, 50)]); - assert_eq!(Elections::runners_up(), vec![]); + assert_eq!(members_and_stake(), vec![(4, 40), (5, 50)]); + assert_eq!(runners_up_and_stake(), vec![]); assert_ok!(Elections::remove_voter(Origin::signed(5))); assert_ok!(Elections::remove_voter(Origin::signed(4))); @@ -2278,7 +2468,7 @@ mod tests { System::set_block_number(5); Elections::end_block(System::block_number()); - assert_eq!(Elections::members(), vec![(5, 50)]); + assert_eq!(members_and_stake(), vec![(5, 50)]); assert_eq!(Elections::election_rounds(), 1); // but now it has a valid target. @@ -2288,7 +2478,7 @@ mod tests { Elections::end_block(System::block_number()); // candidate 4 is affected by an old vote. - assert_eq!(Elections::members(), vec![(4, 30), (5, 50)]); + assert_eq!(members_and_stake(), vec![(4, 30), (5, 50)]); assert_eq!(Elections::election_rounds(), 2); assert_eq_uvec!(all_voters(), vec![3, 5]); }); @@ -2324,7 +2514,7 @@ mod tests { System::set_block_number(5); Elections::end_block(System::block_number()); - assert!(Elections::candidates().is_empty()); + assert!(candidate_ids().is_empty()); assert_eq!(Elections::election_rounds(), 1); assert!(Elections::members_ids().is_empty()); @@ -2377,15 +2567,15 @@ mod tests { System::set_block_number(5); 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!(members_and_stake(), vec![(4, 40), (5, 50)]); + assert_eq!(runners_up_and_stake(), vec![(2, 20), (3, 30)]); assert_ok!(vote(Origin::signed(5), vec![5], 15)); System::set_block_number(10); 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)]); + assert_eq!(members_and_stake(), vec![(3, 30), (4, 40)]); + assert_eq!(runners_up_and_stake(), vec![(5, 15), (2, 20)]); }); } @@ -2490,7 +2680,7 @@ mod tests { assert_ok!(Elections::remove_voter(Origin::signed(4))); // 5 will persist as candidates despite not being in the list. - assert_eq!(Elections::candidates(), vec![2, 3]); + assert_eq!(candidate_ids(), vec![2, 3]); System::set_block_number(10); Elections::end_block(System::block_number()); @@ -2519,10 +2709,10 @@ mod tests { System::set_block_number(b.into()); Elections::end_block(System::block_number()); // we keep re-electing the same folks. - assert_eq!(Elections::members(), vec![(4, 40), (5, 50)]); - assert_eq!(Elections::runners_up(), vec![(2, 20), (3, 30)]); + assert_eq!(members_and_stake(), vec![(4, 40), (5, 50)]); + assert_eq!(runners_up_and_stake(), vec![(2, 20), (3, 30)]); // no new candidates but old members and runners-up are always added. - assert!(Elections::candidates().is_empty()); + assert!(candidate_ids().is_empty()); assert_eq!(Elections::election_rounds(), b / 5); assert_eq_uvec!(all_voters(), vec![2, 3, 4, 5]); }; @@ -2670,7 +2860,7 @@ mod tests { Elections::end_block(System::block_number()); // 3, 4 are new members, must still be bonded, nothing slashed. - assert_eq!(Elections::members(), vec![(3, 30), (4, 48)]); + assert_eq!(members_and_stake(), vec![(3, 30), (4, 48)]); assert_eq!(balances(&3), (25, 5)); assert_eq!(balances(&4), (35, 5)); @@ -2720,9 +2910,9 @@ mod tests { System::set_block_number(5); Elections::end_block(System::block_number()); // id: low -> high. - assert_eq!(Elections::members(), vec![(4, 50), (5, 40)]); + assert_eq!(members_and_stake(), vec![(4, 50), (5, 40)]); // merit: low -> high. - assert_eq!(Elections::runners_up(), vec![(3, 20), (2, 30)]); + assert_eq!(runners_up_and_stake(), vec![(3, 20), (2, 30)]); }); } @@ -2732,13 +2922,13 @@ mod tests { assert_ok!(submit_candidacy(Origin::signed(5))); assert_ok!(submit_candidacy(Origin::signed(3))); - assert_eq!(Elections::candidates(), vec![3, 5]); + assert_eq!(candidate_ids(), vec![3, 5]); assert_ok!(submit_candidacy(Origin::signed(2))); assert_ok!(submit_candidacy(Origin::signed(4))); assert_ok!(Elections::renounce_candidacy(Origin::signed(3), Renouncing::Candidate(4))); - assert_eq!(Elections::candidates(), vec![2, 4, 5]); + assert_eq!(candidate_ids(), vec![2, 4, 5]); }) } @@ -2869,11 +3059,11 @@ mod tests { ExtBuilder::default().build_and_execute(|| { assert_ok!(submit_candidacy(Origin::signed(5))); assert_eq!(balances(&5), (47, 3)); - assert_eq!(Elections::candidates(), vec![5]); + assert_eq!(candidate_ids(), vec![5]); assert_ok!(Elections::renounce_candidacy(Origin::signed(5), Renouncing::Candidate(1))); assert_eq!(balances(&5), (50, 0)); - assert!(Elections::candidates().is_empty()); + assert!(candidate_ids().is_empty()); }) } @@ -2972,21 +3162,23 @@ mod tests { #[test] fn behavior_with_dupe_candidate() { - ExtBuilder::default().desired_runners_up(2).build_and_execute(|| { - >::put(vec![1, 1, 2, 3, 4]); + ExtBuilder::default() + .desired_runners_up(2) + .build_and_execute(|| { + >::put(vec![(1, 0), (1, 0), (2, 0), (3, 0), (4, 0)]); - assert_ok!(vote(Origin::signed(5), vec![1], 50)); - assert_ok!(vote(Origin::signed(4), vec![4], 40)); - assert_ok!(vote(Origin::signed(3), vec![3], 30)); + assert_ok!(vote(Origin::signed(5), vec![1], 50)); + assert_ok!(vote(Origin::signed(4), vec![4], 40)); + assert_ok!(vote(Origin::signed(3), vec![3], 30)); assert_ok!(vote(Origin::signed(2), vec![2], 20)); System::set_block_number(5); Elections::end_block(System::block_number()); - assert_eq!(Elections::members_ids(), vec![1, 4]); - assert_eq!(Elections::runners_up_ids(), vec![2, 3]); - assert!(Elections::candidates().is_empty()); - }) + assert_eq!(Elections::members_ids(), vec![1, 4]); + assert_eq!(Elections::runners_up_ids(), vec![2, 3]); + assert!(candidate_ids().is_empty()); + }) } #[test] diff --git a/frame/elections-phragmen/src/weights.rs b/frame/elections-phragmen/src/weights.rs index fb370b14659b4..ce80b3da0b0bb 100644 --- a/frame/elections-phragmen/src/weights.rs +++ b/frame/elections-phragmen/src/weights.rs @@ -34,11 +34,13 @@ // --output=./frame/elections-phragmen/src/weights.rs // --template=./.maintain/frame-weight-template.hbs - #![allow(unused_parens)] #![allow(unused_imports)] -use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use frame_support::{ + traits::Get, + weights::{constants::RocksDbWeight, Weight}, +}; use sp_std::marker::PhantomData; /// Weight functions needed for pallet_elections_phragmen. @@ -55,7 +57,6 @@ pub trait WeightInfo { fn renounce_candidacy_runners_up() -> Weight; fn remove_member_with_replacement() -> Weight; fn remove_member_wrong_refund() -> Weight; - } /// Weights for pallet_elections_phragmen using the Substrate node and recommended hardware. @@ -138,17 +139,16 @@ impl WeightInfo for SubstrateWeight { // For backwards compatibility and tests impl WeightInfo for () { - fn vote_equal(v: u32, ) -> Weight { + fn vote_equal(v: u32) -> Weight { (89_627_000 as Weight) .saturating_add((197_000 as Weight).saturating_mul(v as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) - } - fn vote_more(v: u32, ) -> Weight { + fn vote_more(v: u32) -> Weight { 0 } - fn vote_less(v: u32, ) -> Weight { + fn vote_less(v: u32) -> Weight { 0 } fn remove_voter() -> Weight { @@ -203,12 +203,8 @@ impl WeightInfo for () { (75_421_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) - } fn remove_member_wrong_refund() -> Weight { - (8_489_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - + (8_489_000 as Weight).saturating_add(RocksDbWeight::get().reads(1 as Weight)) } - } From 29880e43d6efe116c3a625cb83a55181a783e49c Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 18 Nov 2020 18:12:27 +0000 Subject: [PATCH 35/69] A large number of fixes. --- frame/collective/src/lib.rs | 12 +- frame/elections-phragmen/src/lib.rs | 878 ++++++++++++---------------- frame/support/src/traits.rs | 11 +- 3 files changed, 402 insertions(+), 499 deletions(-) diff --git a/frame/collective/src/lib.rs b/frame/collective/src/lib.rs index b7d561672b82f..983946b72de83 100644 --- a/frame/collective/src/lib.rs +++ b/frame/collective/src/lib.rs @@ -40,12 +40,12 @@ //! If there are not, or if no prime is set, then the motion is dropped without being executed. #![cfg_attr(not(feature = "std"), no_std)] -#![recursion_limit="128"] +#![recursion_limit = "128"] -use sp_std::{prelude::*, result}; use sp_core::u32_trait::Value as U32; use sp_io::storage; -use sp_runtime::{RuntimeDebug, traits::Hash}; +use sp_runtime::{traits::Hash, RuntimeDebug}; +use sp_std::{prelude::*, result}; use frame_support::{ codec::{Decode, Encode}, @@ -58,7 +58,7 @@ use frame_support::{ traits::{ChangeMembers, EnsureOrigin, Get, InitializeMembers}, weights::{DispatchClass, GetDispatchInfo, Weight}, }; -use frame_system::{self as system, ensure_signed, ensure_root}; +use frame_system::{self as system, ensure_root, ensure_signed}; #[cfg(feature = "runtime-benchmarks")] mod benchmarking; @@ -817,6 +817,10 @@ impl, I: Instance> ChangeMembers for Module { fn set_prime(prime: Option) { Prime::::set(prime); } + + fn get_prime() -> Option { + Prime::::get() + } } impl, I: Instance> InitializeMembers for Module { diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index bc602a7ad15da..a08d91aac56b9 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -23,50 +23,58 @@ //! //! The election happens in _rounds_: every `N` blocks, all previous members are retired and a new //! set is elected (which may or may not have an intersection with the previous set). Each round -//! lasts for some number of blocks defined by `TermDuration` storage item. The words _term_ and -//! _round_ can be used interchangeably in this context. +//! lasts for some number of blocks defined by [`TermDuration`]. The words _term_ and _round_ can be +//! used interchangeably in this context. //! -//! `TermDuration` might change during a round. This can shorten or extend the length of the round. -//! The next election round's block number is never stored but rather always checked on the fly. -//! Based on the current block number and `TermDuration`, the condition `BlockNumber % TermDuration -//! == 0` being satisfied will always trigger a new election round. +//! [`TermDuration`] might change during a round. This can shorten or extend the length of the +//! round. The next election round's block number is never stored but rather always checked on the +//! fly. Based on the current block number and [`TermDuration`], the condition `BlockNumber % +//! TermDuration == 0` being satisfied will always trigger a new election round. +//! +//! ### Bonds and Deposits +//! +//! Both voting and being a candidate requires deposits to be taken, in exchange for the data that +//! needs to be kept on-chain, and the complexity forced to the election. The terms *bond* and +//! *deposit* can be used interchangeably in this context. +//! +//! Bonds will be unreserved only upon adhering to the protocol laws. Failing to do so will cause in +//! the bond to slashed. //! //! ### Voting //! -//! Voters can vote for any set of the candidates by providing a list of account ids. Invalid votes -//! (voting for non-candidates) are ignored during election. Yet, a voter _might_ vote for a future -//! candidate. Voters reserve a bond as they vote. Each vote defines a `value`. This amount is -//! locked from the account of the voter and indicates the weight of the vote. Voters can update -//! their votes at any time by calling `vote()` again. This keeps the bond untouched but can -//! optionally change the locked `value`. After a round, votes are kept and might still be valid for -//! further rounds. A voter is responsible for calling `remove_voter` once they are done to have -//! their bond back and remove the lock. +//! Voters can vote for any number of the candidates by providing a list of account ids. Invalid +//! votes (voting for non-candidates) and duplicate votes are ignored during election. Yet, a voter +//! _might_ vote for a future candidate. Voters reserve a bond as they vote. Each vote defines a +//! `value`. This amount is locked from the account of the voter and indicates the weight of the +//! vote. Voters can update their votes at any time by calling `vote()` again. This keeps the bond +//! untouched but can optionally change the locked `value`. After a round, votes are kept and might +//! still be valid for further rounds. A voter is responsible for calling `remove_voter` once they +//! are done to have their bond back and remove the lock. //! -//! Voters also report other voters as being defunct to earn their bond. A voter is defunct once all -//! of the candidates that they have voted for are neither a valid candidate anymore nor a member. -//! Upon reporting, if the target voter is actually defunct, the reporter will be rewarded by the -//! voting bond of the target. The target will lose their bond and get removed. If the target is not -//! defunct, the reporter is slashed and removed. To prevent being reported, voters should manually -//! submit a `remove_voter()` as soon as they are in the defunct state. +//! ### Defunct Voter +//! A voter is defunct once all of the candidates that they have voted for are not a valid candidate +//! (as seen further below, members and runners-up are also always candidates). Defunct voters can +//! be removed via a root call. Upon being removed, their bond is returned. This is an +//! administrative operation and can be called only by the root origin in the case of state blaot. //! //! ### Candidacy and Members //! -//! Candidates also reserve a bond as they submit candidacy. A candidate cannot take their candidacy -//! back. A candidate can end up in one of the below situations: -//! - **Winner**: A winner is kept as a _member_. They must still have a bond in reserve and they +//! Candidates also reserve a bond as they submit candidacy. A candidate can end up in one of the +//! below situations: +//! - **Members**: A winner is kept as a _member_. They must still have a bond in reserve and they //! are automatically counted as a candidate for the next election. //! - **Runner-up**: Runners-up are the best candidates immediately after the winners. The number //! of runners_up to keep is configurable. Runners-up are used, in order that they are elected, -//! as replacements when a candidate is kicked by `[remove_member]`, or when an active member +//! as replacements when a candidate is kicked by [`remove_member`], or when an active member //! renounces their candidacy. Runners are automatically counted as a candidate for the next //! election. //! - **Loser**: Any of the candidate who are not a winner are left as losers. A loser might be an //! _outgoing member or runner_, meaning that they are an active member who failed to keep their -//! spot. An outgoing will always lose their bond. +//! spot. **An outgoing will always lose their bond**. //! //! ##### Renouncing candidacy. //! -//! All candidates, elected or not, can renounce their candidacy. A call to [`Module::renounce_candidacy`] +//! All candidates, elected or not, can renounce their candidacy. A call to [`renounce_candidacy`] //! will always cause the candidacy bond to be refunded. //! //! Note that with the members being the default candidates for the next round and votes persisting @@ -116,12 +124,10 @@ type BalanceOf = type NegativeImbalanceOf = <::Currency as Currency<::AccountId>>::NegativeImbalance; - /// Helper functions for migrations of this module. pub mod migrations { use super::*; - use frame_support::migration::StorageKeyIterator; - use frame_support::Twox64Concat; + use frame_support::{migration::StorageKeyIterator, Twox64Concat}; /// Migrate from the old legacy voting bond (fixed) to the new one (per-vote dynamic). /// @@ -155,6 +161,92 @@ pub mod migrations { 0 } } + + /// Migrate all candidates to recorded deposit. + /// + /// Will only be triggered if storage version is V1. + pub fn migrate_candidates_to_recorded_deposit(old_deposit: BalanceOf) -> Weight { + if >::pallet_storage_version() == StorageVersion::V1 { + let old_candidates = frame_support::migration::take_storage_value::>( + >::module_prefix(), + b"Candidates", + &[], + ) + .unwrap_or_default(); + let new_candidates = old_candidates + .into_iter() + .map(|c| (c, old_deposit)) + .collect::>(); + >::put(new_candidates); + Weight::max_value() + } else { + frame_support::debug::warn!( + "🏛 pallet-elections-phragmen: Tried to run migration but PalletStorageVersion is \ + updated. This code probably needs to be removed now.", + ); + 0 + } + } + + pub fn migrate_members_to_recorded_deposit(deposit: BalanceOf) -> Weight { + if >::pallet_storage_version() == StorageVersion::V1 { + let _ = >::translate::)>, _>( + |maybe_old_members| { + maybe_old_members.map(|old_members| { + frame_support::debug::info!( + "migrated {} member accounts.", + old_members.len() + ); + old_members + .into_iter() + .map(|(who, stake)| SeatHolder { + who, + stake, + deposit, + }) + .collect::>() + }) + }, + ); + Weight::max_value() + } else { + frame_support::debug::warn!( + "🏛 pallet-elections-phragmen: Tried to run migration but PalletStorageVersion is \ + updated. This code probably needs to be removed now.", + ); + 0 + } + } + + pub fn migrate_runners_up_to_recorded_deposit(deposit: BalanceOf) -> Weight { + if >::pallet_storage_version() == StorageVersion::V1 { + let _ = >::translate::)>, _>( + |maybe_old_runners_up| { + maybe_old_runners_up.map(|old_runners_up| { + frame_support::debug::info!( + "migrated {} runner-up accounts.", + old_runners_up.len() + ); + old_runners_up + .into_iter() + .map(|(who, stake)| SeatHolder { + who, + stake, + deposit, + }) + .collect::>() + }) + }, + ); + Weight::max_value() + } else { + frame_support::debug::warn!( + "🏛 pallet-elections-phragmen: Tried to run migration but PalletStorageVersion is \ + updated. This code probably needs to be removed now.", + ); + 0 + } + } } /// An indication that the renouncing account currently has which of the below roles. @@ -168,19 +260,6 @@ pub enum Renouncing { Candidate(#[codec(compact)] u32), } -/// Information needed to prove the defunct-ness of a voter. -#[derive(Encode, Decode, Clone, PartialEq, RuntimeDebug)] -pub struct DefunctVoter { - /// the voter's who's being challenged for being defunct - pub who: AccountId, - /// The number of votes that `who` has placed. - #[codec(compact)] - pub vote_count: u32, - /// The number of current active candidates. - #[codec(compact)] - pub candidate_count: u32 -} - /// An active voter. #[derive(Encode, Decode, Clone, Default, RuntimeDebug, PartialEq)] pub struct Voter { @@ -251,9 +330,6 @@ pub trait Trait: frame_system::Trait { /// Handler for the unbalanced reduction when a candidate has lost (and is not a runner-up) type LoserCandidate: OnUnbalanced>; - /// Handler for the unbalanced reduction when a reporter has submitted a bad defunct report. - type BadReport: OnUnbalanced>; - /// Handler for the unbalanced reduction when a member has been kicked. type KickedMember: OnUnbalanced>; @@ -275,29 +351,36 @@ pub trait Trait: frame_system::Trait { decl_storage! { trait Store for Module as PhragmenElection { // ---- State - /// The current elected membership. Sorted based on account id. - /// TODO: Migration + /// The current elected members. + /// + /// Invariant: Always sorted based on account id. pub Members get(fn members): Vec>>; - /// The current runners_up. Sorted based on low to high merit (worse to best). - /// TODO: Migration + + /// The current reserved runners-up. + /// + /// Invariant: Always sorted based on rank (worse to best). Upon removal of a member, the + /// last (i.e. _best_) runner-up will be replaced. pub RunnersUp get(fn runners_up): Vec>>; + /// The total number of vote rounds that have happened, excluding the upcoming one. pub ElectionRounds get(fn election_rounds): u32 = Zero::zero(); /// Votes and locked stake of a particular voter. /// - /// TWOX-NOTE: SAFE as `AccountId` is a crypto hash + /// TWOX-NOTE: SAFE as `AccountId` is a crypto hash. pub Voting get(fn voting): map hasher(twox_64_concat) T::AccountId => Voter>; - /// The present candidate list. Sorted based on account-id. A current member or runner-up + /// The present candidate list. A current member or runner-up /// can never enter this vector and is always implicitly assumed to be a candidate. /// /// Second element is the deposit. - /// TODO: migration + /// + /// Invariant: Always sorted based on account id. pub Candidates get(fn candidates): Vec<(T::AccountId, BalanceOf)>; /// Should be used in conjunction with `on_runtime_upgrade` to ensure an upgrade is executed /// once, even if the code is not removed in time. + /// TODO: use basti's new version stuff? pub PalletStorageVersion get(fn pallet_storage_version) build(|_| StorageVersion::V2RecordedDeposit): StorageVersion = StorageVersion::V1; @@ -305,7 +388,7 @@ decl_storage! { config(members): Vec<(T::AccountId, BalanceOf)>; build(|config: &GenesisConfig| { let members = config.members.iter().map(|(ref member, ref stake)| { - // make sure they have enough stake + // make sure they have enough stake. assert!( T::Currency::free_balance(member) >= *stake, "Genesis member does not have enough stake", @@ -320,7 +403,7 @@ decl_storage! { // Nonetheless, stakes will be updated for term 1 onwards according to the election. Members::::mutate(|members| { match members.binary_search_by(|m| m.who.cmp(member)) { - Ok(_) => panic!("Duplicate member in elections phragmen genesis: {}", member), + Ok(_) => panic!("Duplicate member in elections-phragmen genesis: {}", member), Err(pos) => members.insert( pos, SeatHolder { who: member.clone(), stake: *stake, deposit: 0u32.into() }, @@ -383,6 +466,32 @@ decl_error! { } } +decl_event!( + pub enum Event where + Balance = BalanceOf, + ::AccountId, + { + /// A new term with \[new_members\]. This indicates that enough candidates existed to run the + /// election, not that enough have has been elected. The inner value must be examined for + /// this purpose. A `NewTerm(\[\])` indicates that some candidates got their bond slashed and + /// none were elected, whilst `EmptyTerm` means that no candidates existed to begin with. + NewTerm(Vec<(AccountId, Balance)>), + /// No (or not enough) candidates existed for this round. This is different from + /// `NewTerm(\[\])`. See the description of `NewTerm`. + EmptyTerm, + /// Internal error happened while trying to perform election. + ElectionError, + /// A \[member\] has been removed. This should always be followed by either `NewTerm` or + /// `EmptyTerm`. + MemberKicked(AccountId), + /// A \[member\] has renounced their candidacy. + MemberRenounced(AccountId), + /// A voter was reported with the the report being successful or not. + /// \[voter, reporter, success\] + VoterReported(AccountId, AccountId, bool), + } +); + decl_module! { pub struct Module for enum Call where origin: T::Origin { type Error = Error; @@ -399,16 +508,20 @@ decl_module! { /// Vote for a set of candidates for the upcoming round of election. This can be called to /// set the initial votes, or update already existing votes. /// - /// Upon initial voting, `value` units of `who`'s balance is locked and a bond amount is - /// reserved. + /// Upon initial voting, `value` units of `who`'s balance is locked and a deposit amount is + /// reserved. The deposit is based on the number of votes and can be updated over time. /// /// The `votes` should: /// - not be empty. /// - be less than the number of possible candidates. Note that all current members and /// runners-up are also automatically candidates for the next round. /// - /// It is the responsibility of the caller to not place all of their balance into the lock - /// and keep some for further transactions. + /// The dispatch origin of this call must be signed. + /// + /// ### Warning + /// + /// It is the responsibility of the caller to **NOT** place all of their balance into the + /// lock and keep some for further transactions. /// /// # /// we consider the common case of placing more votes. In other two case, we refund. @@ -476,121 +589,31 @@ decl_module! { Ok(maybe_refund.into()) } - /// Remove `origin` as a voter. This removes the lock and returns the bond. + /// Remove `origin` as a voter. This removes the lock and returns the deposit. /// - /// # - /// Base weight: 36.8 µs - /// All state access is from do_remove_voter. - /// State reads: - /// - Voting - /// - [AccountData(who)] - /// State writes: - /// - Voting - /// - Locks - /// - [AccountData(who)] - /// # + /// The dispatch origin of this call must be signed and be a voter. #[weight = T::WeightInfo::remove_voter()] fn remove_voter(origin) { let who = ensure_signed(origin)?; ensure!(Self::is_voter(&who), Error::::MustBeVoter); - Self::do_remove_voter(&who, true); + Self::do_remove_voter(&who); } - /// Report `target` for being an defunct voter. In case of a valid report, the `target`'s - /// bond is returned and their voting is removed. Otherwise, nothing happens. - /// - /// Note that regardless of the outcome, neither slashing nor rewarding happens. + /// Submit oneself for candidacy. A fixed amount of deposit is recorded. /// - /// A defunct voter is defined to be: a voter who's current submitted votes are all invalid. - /// i.e. all of them are no longer a candidate nor an active member or a runner-up. + /// All candidates are wiped at the end of the term. They either become a member/runner-up, + /// or leave the system while their deposit is slashed. /// /// The dispatch origin of this call must be signed. /// - /// The origin must provide the number of current candidates and votes of the reported target - /// for the purpose of accurate weight calculation. - /// - /// # - /// No base weight based on min square analysis. - /// Complexity of candidate_count: 1.755 µs - /// Complexity of vote_count: 18.51 µs - /// State reads: - /// - Voting(reporter) - /// - Candidate.len() - /// - Voting(Target) - /// - Candidates, Members, RunnersUp (is_defunct_voter) - /// State writes: - /// - Lock(reporter || target) - /// - [AccountBalance(reporter)] + AccountBalance(target) - /// - Voting(reporter || target) - /// Note: We charge the case if the report is correct, if incorrect, we refund. - /// # - #[weight = T::WeightInfo::report_defunct_voter_correct( - defunct.candidate_count, - defunct.vote_count, - )] - fn report_defunct_voter( - origin, - defunct: DefunctVoter<::Source>, - ) -> DispatchResultWithPostInfo { - let reporter = ensure_signed(origin)?; - let target = T::Lookup::lookup(defunct.who)?; - - ensure!(reporter != target, Error::::ReportSelf); - - let DefunctVoter { candidate_count, vote_count, .. } = defunct; - - ensure!( - >::decode_len().unwrap_or(0) as u32 <= candidate_count, - Error::::InvalidCandidateCount, - ); - - let Voter { votes, .. } = >::get(&target); - // indirect way to ensure target is a voter. We could call into `::contains()`, but it - // would have the same effect with one extra db access. Note that votes cannot be - // submitted with length 0. Hence, a non-zero length means that the target is a voter. - ensure!(votes.len() > 0, Error::::MustBeVoter); - - // ensure that the size of votes that need to be searched is correct. - ensure!( - votes.len() as u32 <= vote_count, - Error::::InvalidVoteCount, - ); - - let valid = Self::is_defunct_voter(&votes); - let maybe_refund = if valid { - // remove the defunct target while unreserving their bond. - Self::do_remove_voter(&target, true); - None - } else { - Some(T::WeightInfo::report_defunct_voter_incorrect( - defunct.candidate_count, - defunct.vote_count, - )) - }; - Self::deposit_event(RawEvent::VoterReported(target, reporter, valid)); - Ok(maybe_refund.into()) - } - - /// Submit oneself for candidacy. + /// ### Warning /// - /// A candidate will either: - /// - Lose at the end of the term and forfeit their deposit. - /// - Win and become a member. Members will eventually get their stash back. - /// - Become a runner-up. Runners-ups are reserved members in case one gets forcefully - /// removed. + /// Even if a candidate ends up being a member, they must call renounce_candidacy to get + /// their deposit back. Winning the spot in an election will also lead to a slash. /// /// # - /// Base weight = 33.33 µs - /// Complexity of candidate_count: 0.375 µs - /// State reads: - /// - Candidates - /// - Members - /// - RunnersUp - /// - [AccountBalance(who)] - /// State writes: - /// - [AccountBalance(who)] - /// - Candidates + /// The number of current candidates must be provided as witness data. /// # #[weight = T::WeightInfo::submit_candidacy(*candidate_count)] fn submit_candidacy(origin, #[compact] candidate_count: u32) { @@ -619,40 +642,21 @@ decl_module! { /// Renounce one's intention to be a candidate for the next election round. 3 potential /// outcomes exist: - /// - `origin` is a candidate and not elected in any set. In this case, the bond is + /// + /// - `origin` is a candidate and not elected in any set. In this case, the deposit is /// unreserved, returned and origin is removed as a candidate. - /// - `origin` is a current runner-up. In this case, the bond is unreserved, returned and + /// - `origin` is a current runner-up. In this case, the deposit is unreserved, returned and /// origin is removed as a runner-up. - /// - `origin` is a current member. In this case, the bond is unreserved and origin is + /// - `origin` is a current member. In this case, the deposit is unreserved and origin is /// removed as a member, consequently not being a candidate for the next round anymore. /// Similar to [`remove_voter`], if replacement runners exists, they are immediately used. - /// - /// If a candidate is renouncing: - /// Base weight: 17.28 µs - /// Complexity of candidate_count: 0.235 µs - /// State reads: - /// - Candidates - /// - [AccountBalance(who) (unreserve)] - /// State writes: - /// - Candidates - /// - [AccountBalance(who) (unreserve)] - /// If member is renouncing: - /// Base weight: 46.25 µs - /// State reads: - /// - Members, RunnersUp (remove_and_replace_member), - /// - [AccountData(who) (unreserve)] - /// State writes: - /// - Members, RunnersUp (remove_and_replace_member), - /// - [AccountData(who) (unreserve)] - /// If runner is renouncing: - /// Base weight: 46.25 µs - /// State reads: - /// - RunnersUp (remove_and_replace_member), - /// - [AccountData(who) (unreserve)] - /// State writes: - /// - RunnersUp (remove_and_replace_member), - /// - [AccountData(who) (unreserve)] - /// + /// If the prime is renouncing, then no prime will exist until the next round. + /// + /// The dispatch origin of this call must be signed, and have one of the above roles. + /// + /// # + /// The type of renouncing must be provided as witness data. + /// # #[weight = match *renouncing { Renouncing::Candidate(count) => T::WeightInfo::renounce_candidacy_candidate(count), Renouncing::Member => T::WeightInfo::renounce_candidacy_members(), @@ -662,7 +666,6 @@ decl_module! { let who = ensure_signed(origin)?; match renouncing { Renouncing::Member => { - // returns NoMember error in case of error. let _ = Self::remove_and_replace_member(&who, false)?; Self::deposit_event(RawEvent::MemberRenounced(who)); }, @@ -682,11 +685,10 @@ decl_module! { Renouncing::Candidate(count) => { let mut candidates = Self::candidates(); ensure!(count >= candidates.len() as u32, Error::::InvalidRenouncing); - if let Some(index) = candidates.iter().position(|(x, _)| *x == who) { + // Note: candidates list is always sorted. + if let Ok(index) = candidates.binary_search_by(|(c, _)| c.cmp(&who)) { let (_removed, deposit) = candidates.remove(index); - // unreserve the bond T::Currency::unreserve(&who, deposit); - // update storage. >::put(candidates); } else { Err(Error::::InvalidRenouncing)?; @@ -701,17 +703,13 @@ decl_module! { /// If a runner-up is available, then the best runner-up will be removed and replaces the /// outgoing member. Otherwise, a new phragmen election is started. /// + /// The dispatch origin of this call must be root. + /// /// Note that this does not affect the designated block number of the next election. /// /// # - /// If we have a replacement: - /// - Base weight: 50.93 µs - /// - State reads: - /// - RunnersUp.len() - /// - Members, RunnersUp (remove_and_replace_member) - /// - State writes: - /// - Members, RunnersUp (remove_and_replace_member) - /// Else, since this is a root call and will go into phragmen, we assume full block for now. + /// If we have a replacement, we use a small weight. Else, since this is a root call and + /// will go into phragmen, we assume full block for now. /// # #[weight = if *has_replacement { T::WeightInfo::remove_member_with_replacement() @@ -728,7 +726,7 @@ decl_module! { let will_have_replacement = >::decode_len().unwrap_or(0) > 0; if will_have_replacement != has_replacement { - // In both cases, we will change more weight than neede. Refund and abort. + // In both cases, we will change more weight than need. Refund and abort. return Err(Error::::InvalidReplacement.with_weight( // refund. The weight value comes from a benchmark which is special to this. // 5.751 µs @@ -737,7 +735,6 @@ decl_module! { } // else, prediction was correct. Self::remove_and_replace_member(&who, true).map(|had_replacement| { - // TODO: test for all the folks who call into `remove_and_replace_member`. Self::deposit_event(RawEvent::MemberKicked(who.clone())); if !had_replacement { @@ -750,7 +747,29 @@ decl_module! { }).map_err(|e| e.into()) } - /// What to do at the end of each block. Checks if an election needs to happen or not. + /// Clear all voters who are defunct (i.e. the do not serve any purpose at all). The deposit + /// of the removed voters are returned. + /// + /// This is an root function to be used only for cleaning the state. + /// + /// The dispatch origin of this call must be root. + /// + /// # + /// The total number of voters and those that are defunct must be provided as witness data. + /// # + #[weight = 0] + fn clean_defunct_voters(origin, num_voters: u32, num_defunct: u32) { + let _ = ensure_root(origin)?; + >::iter() + .filter(|(_, x)| Self::is_defunct_voter(&x.votes)) + .for_each(|(dv, _)| { + Self::do_remove_voter(&dv) + }) + } + + /// What to do at the end of each block. + /// + /// Checks if an election needs to happen or not. fn on_initialize(n: T::BlockNumber) -> Weight { // returns the correct weight. Self::end_block(n) @@ -758,32 +777,6 @@ decl_module! { } } -decl_event!( - pub enum Event where - Balance = BalanceOf, - ::AccountId, - { - /// A new term with \[new_members\]. This indicates that enough candidates existed to run the - /// election, not that enough have has been elected. The inner value must be examined for - /// this purpose. A `NewTerm(\[\])` indicates that some candidates got their bond slashed and - /// none were elected, whilst `EmptyTerm` means that no candidates existed to begin with. - NewTerm(Vec<(AccountId, Balance)>), - /// No (or not enough) candidates existed for this round. This is different from - /// `NewTerm(\[\])`. See the description of `NewTerm`. - EmptyTerm, - /// Internal error happened while trying to perform election. - ElectionError, - /// A \[member\] has been removed. This should always be followed by either `NewTerm` or - /// `EmptyTerm`. - MemberKicked(AccountId), - /// A \[member\] has renounced their candidacy. - MemberRenounced(AccountId), - /// A voter was reported with the the report being successful or not. - /// \[voter, reporter, success\] - VoterReported(AccountId, AccountId, bool), - } -); - impl Module { /// The deposit value of `count` votes. fn deposit_of(count: usize) -> BalanceOf { @@ -792,23 +785,28 @@ impl Module { ) } - /// Attempts to remove a member `who`. If a runner-up exists, it is used as the replacement and - /// Ok(true) is returned. + /// Attempts to remove a member `who`. If a runner-up exists, it is used as the replacement. /// - /// Otherwise, `Ok(false)` is returned to signal the caller. + /// Returns: /// - /// If the `who` is not a member, `Err` is returned. + /// - `Ok(true)` if the member was removed and a replacement was found. + /// - `Ok(false)` if the member was removed and but no replacement was found. + /// - `Err(_)` if the member was no found. /// /// Both `Members` and `RunnersUp` storage is updated accordingly. `T::ChangeMember` is called /// if needed. If `slash` is true, the deposit of the potentially removed member is slashed, /// else it is unreserved. + /// + /// ### Note + /// + /// This function attempts to preserve the prime. If the removed members is not the prime, it is + /// set again via [`Trait::ChangeMembers` ] to prevent it being wiped. fn remove_and_replace_member(who: &T::AccountId, slash: bool) -> Result { // closure will return: - // Ok(Option(replacement)) if member was removed and replacement was replaced. - // Ok(None) if member was removed but no replacement was found - // Err(_) if who is not a member. - // TODO: what if we remove the prime. - let outcome = >::try_mutate(|members| { + // - `Ok(Option(replacement))` if member was removed and replacement was replaced. + // - `Ok(None)` if member was removed but no replacement was found + // - `Err(_)` if who is not a member. + >::try_mutate(|members| { // we remove the member anyhow, regardless of having a runner-up or not. let remove_index = members .binary_search_by(|m| m.who.cmp(who)) @@ -825,7 +823,7 @@ impl Module { Ok(>::mutate(|r| r.pop()).map(|next_best| { // invariant: Members and runners-up are disjoint. This will always be err and give - // us an index. + // us an index to insert. members .binary_search_by(|m| m.who.cmp(&next_best.who)) .err() @@ -834,15 +832,15 @@ impl Module { }); next_best })) - }); - - outcome.map(|maybe_replacement| { + }) + .map(|maybe_replacement| { let member_ids = Self::members() .into_iter() .map(|x| x.who.clone()) .collect::>(); let outgoing = &[who.clone()]; - match maybe_replacement { + let current_prime = T::ChangeMembers::get_prime().unwrap_or_default(); + let return_value = match maybe_replacement { // member ids are already sorted, other two elements have one item. Some(incoming) => { T::ChangeMembers::change_members_sorted( @@ -856,7 +854,12 @@ impl Module { T::ChangeMembers::change_members_sorted(&[], outgoing, &member_ids[..]); false } + }; + if ¤t_prime != who { + // re-set the prime if the removed was not the old prime. + T::ChangeMembers::set_prime(Some(current_prime)) } + return_value }) } @@ -918,43 +921,15 @@ impl Module { .collect::>() } - /// The the runners' up account ids. - fn runners_up_ids() -> Vec { - Self::runners_up() - .into_iter() - .map(|r| r.who) - .collect::>() - } - - /// Get the members and their recorded deposit. - /// - /// This can be concatenated with [`Self::candidates`]. - fn members_and_deposit() -> Vec<(T::AccountId, BalanceOf)> { - Self::members() - .into_iter() - .map(|m| (m.who, m.deposit)) - .collect::>() - } - - /// Get the runners-up and their recorded deposit. - /// - /// This can be concatenated with [`Self::candidates`]. - fn runners_up_and_deposit() -> Vec<(T::AccountId, BalanceOf)> { - Self::runners_up() - .into_iter() - .map(|r| (r.who, r.deposit)) - .collect::>() - } - /// Get a concatenation of previous members and runners-up and their deposits. /// /// These accounts are essentially treated as candidates. fn implicit_candidates() -> Vec<(T::AccountId, BalanceOf)> { - // TODO: this is not optimized. Allocation is not needed. - // TODO: invariant: these two are always without duplicates. - Self::members_and_deposit() + // invariant: these two are always without duplicates. + Self::members() .into_iter() - .chain(Self::runners_up_and_deposit().into_iter()) + .map(|m| (m.who, m.deposit)) + .chain(Self::runners_up().into_iter().map(|r| (r.who, r.deposit))) .collect::>() } @@ -972,21 +947,16 @@ impl Module { } /// Remove a certain someone as a voter. - /// - /// This will clean always clean the storage associated with the voter, and remove the balance - /// lock. Optionally, it would also return the reserved voting bond if indicated by `unreserve`. - /// - /// DB access: Voting and Lock are always written to, if unreserve, then 1 read and write added. - fn do_remove_voter(who: &T::AccountId, unreserve: bool) { + fn do_remove_voter(who: &T::AccountId) { let Voter { deposit, .. } = >::get(who); - // remove storage and lock. + + // remove storage, lock and unreserve. >::remove(who); + T::Currency::remove_lock(T::ModuleId::get(), who); - if unreserve { - let _remainder = T::Currency::unreserve(who, deposit); - debug_assert!(_remainder.is_zero()); - } + let _remainder = T::Currency::unreserve(who, deposit); + debug_assert!(_remainder.is_zero()); } /// Check there's nothing to do this block. @@ -1385,9 +1355,13 @@ mod tests { fn set_prime(who: Option) { PRIME.with(|p| *p.borrow_mut() = who); } + + fn get_prime() -> Option { + PRIME.with(|p| *p.borrow()) + } } - parameter_types!{ + parameter_types! { pub const ElectionsPhragmenModuleId: LockIdentifier = *b"phrelect"; } @@ -1406,7 +1380,6 @@ mod tests { type DesiredRunnersUp = DesiredRunnersUp; type LoserCandidate = (); type KickedMember = (); - type BadReport = (); type WeightInfo = (); } @@ -1516,6 +1489,13 @@ mod tests { .collect::>() } + fn runners_up_ids() -> Vec { + Elections::runners_up() + .into_iter() + .map(|r| r.who) + .collect::>() + } + fn members_and_stake() -> Vec<(u64, u64)> { Elections::members() .into_iter() @@ -1556,7 +1536,7 @@ mod tests { fn ensure_candidates_sorted() { let mut candidates = Elections::candidates().clone(); - candidates.sort(); + candidates.sort_by_key(|(c, _)| *c); assert_eq!(Elections::candidates(), candidates); } @@ -1576,11 +1556,8 @@ mod tests { fn ensure_member_candidates_runners_up_disjoint() { // members, candidates and runners-up must always be disjoint sets. assert!(!intersects(&Elections::members_ids(), &candidate_ids())); - assert!(!intersects( - &Elections::members_ids(), - &Elections::runners_up_ids() - )); - assert!(!intersects(&candidate_ids(), &Elections::runners_up_ids())); + assert!(!intersects(&Elections::members_ids(), &runners_up_ids())); + assert!(!intersects(&candidate_ids(), &runners_up_ids())); } fn pre_conditions() { @@ -1612,14 +1589,6 @@ mod tests { Voting::::get(who).votes } - fn defunct_for(who: u64) -> DefunctVoter { - DefunctVoter { - who, - candidate_count: Elections::candidates().len() as u32, - vote_count: votes_of(&who).len() as u32 - } - } - #[test] fn params_should_work() { ExtBuilder::default().build_and_execute(|| { @@ -1754,7 +1723,7 @@ mod tests { } #[test] - #[should_panic = "Duplicate member in elections phragmen genesis: 2"] + #[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)]) @@ -1888,7 +1857,7 @@ mod tests { Elections::end_block(System::block_number()); assert_eq!(Elections::members_ids(), vec![4, 5]); - assert_eq!(Elections::runners_up_ids(), vec![3]); + assert_eq!(runners_up_ids(), vec![3]); assert_noop!( submit_candidacy(Origin::signed(3)), @@ -2077,6 +2046,51 @@ mod tests { }); } + #[test] + fn prime_is_kept_if_other_members_leave() { + ExtBuilder::default().build_and_execute(|| { + assert_ok!(submit_candidacy(Origin::signed(4))); + assert_ok!(submit_candidacy(Origin::signed(5))); + + assert_ok!(vote(Origin::signed(4), vec![4], 40)); + assert_ok!(vote(Origin::signed(5), vec![5], 50)); + + System::set_block_number(5); + Elections::end_block(System::block_number()); + + assert_eq!(Elections::members_ids(), vec![4, 5]); + assert_eq!(PRIME.with(|p| *p.borrow()), Some(5)); + assert_ok!(Elections::renounce_candidacy( + Origin::signed(4), + Renouncing::Member + )); + + assert_eq!(Elections::members_ids(), vec![5]); + assert_eq!(PRIME.with(|p| *p.borrow()), Some(5)); + }) + } + + #[test] + fn prime_is_gone_if_renouncing() { + ExtBuilder::default().build_and_execute(|| { + assert_ok!(submit_candidacy(Origin::signed(4))); + assert_ok!(submit_candidacy(Origin::signed(5))); + + assert_ok!(vote(Origin::signed(4), vec![4], 40)); + assert_ok!(vote(Origin::signed(5), vec![5], 50)); + + System::set_block_number(5); + Elections::end_block(System::block_number()); + + assert_eq!(Elections::members_ids(), vec![4, 5]); + assert_eq!(PRIME.with(|p| *p.borrow()), Some(5)); + assert_ok!(Elections::renounce_candidacy(Origin::signed(5), Renouncing::Member)); + + assert_eq!(Elections::members_ids(), vec![4]); + assert_eq!(PRIME.with(|p| *p.borrow()), None); + }) + } + #[test] fn cannot_vote_for_more_than_candidates_and_members_and_runners() { ExtBuilder::default() @@ -2204,172 +2218,6 @@ mod tests { }); } - #[test] - fn reporter_must_be_voter() { - ExtBuilder::default().build_and_execute(|| { - assert_noop!( - Elections::report_defunct_voter(Origin::signed(1), defunct_for(2)), - Error::::MustBeVoter, - ); - }); - } - - #[test] - fn reporter_must_provide_lengths() { - ExtBuilder::default().build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(submit_candidacy(Origin::signed(4))); - assert_ok!(submit_candidacy(Origin::signed(3))); - - // both are defunct. - assert_ok!(vote(Origin::signed(5), vec![99, 999, 9999], 50)); - assert_ok!(vote(Origin::signed(4), vec![999], 40)); - - // 3 candidates! incorrect candidate length. - assert_noop!( - Elections::report_defunct_voter(Origin::signed(4), DefunctVoter { - who: 5, - candidate_count: 2, - vote_count: 3, - }), - Error::::InvalidCandidateCount, - ); - - // 3 votes! incorrect vote length - assert_noop!( - Elections::report_defunct_voter(Origin::signed(4), DefunctVoter { - who: 5, - candidate_count: 3, - vote_count: 2, - }), - Error::::InvalidVoteCount, - ); - - // correct. - assert_ok!(Elections::report_defunct_voter(Origin::signed(4), DefunctVoter { - who: 5, - candidate_count: 3, - vote_count: 3, - })); - }); - } - - #[test] - fn reporter_can_overestimate_length() { - ExtBuilder::default().build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(submit_candidacy(Origin::signed(4))); - - // both are defunct. - assert_ok!(vote(Origin::signed(5), vec![99], 50)); - assert_ok!(vote(Origin::signed(4), vec![999], 40)); - - // 2 candidates! overestimation is okay. - assert_ok!(Elections::report_defunct_voter(Origin::signed(4), defunct_for(5))); - }); - } - - #[test] - fn can_detect_defunct_voter() { - ExtBuilder::default().desired_runners_up(2).build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(4))); - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(submit_candidacy(Origin::signed(6))); - - assert_ok!(vote(Origin::signed(5), vec![5], 50)); - assert_ok!(vote(Origin::signed(4), vec![4], 40)); - assert_ok!(vote(Origin::signed(2), vec![4, 5], 20)); - assert_ok!(vote(Origin::signed(6), vec![6], 30)); - // will be soon a defunct voter. - assert_ok!(vote(Origin::signed(3), vec![3], 30)); - - System::set_block_number(5); - Elections::end_block(System::block_number()); - - assert_eq!(Elections::members_ids(), vec![4, 5]); - assert_eq!(Elections::runners_up_ids(), vec![6]); - assert!(candidate_ids().is_empty()); - - // all of them have a member or runner-up that they voted for. - assert_eq!(Elections::is_defunct_voter(&votes_of(&5)), false); - assert_eq!(Elections::is_defunct_voter(&votes_of(&4)), false); - assert_eq!(Elections::is_defunct_voter(&votes_of(&2)), false); - assert_eq!(Elections::is_defunct_voter(&votes_of(&6)), false); - - // defunct - assert_eq!(Elections::is_defunct_voter(&votes_of(&3)), true); - - assert_ok!(submit_candidacy(Origin::signed(1))); - assert_ok!(vote(Origin::signed(1), vec![1], 10)); - - // has a candidate voted for. - assert_eq!(Elections::is_defunct_voter(&votes_of(&1)), false); - - }); - } - - #[test] - fn report_voter_should_work() { - ExtBuilder::default().build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(submit_candidacy(Origin::signed(4))); - - assert_ok!(vote(Origin::signed(5), vec![5], 50)); - assert_ok!(vote(Origin::signed(4), vec![4], 40)); - assert_ok!(vote(Origin::signed(2), vec![4, 5], 20)); - // will be soon a defunct voter. - assert_ok!(vote(Origin::signed(3), vec![3], 30)); - - System::set_block_number(5); - Elections::end_block(System::block_number()); - - assert_eq!(Elections::members_ids(), vec![4, 5]); - assert!(candidate_ids().is_empty()); - - assert_eq!(balances(&3), (28, 2)); - assert_eq!(balances(&5), (45, 5)); - - assert_ok!(Elections::report_defunct_voter(Origin::signed(5), defunct_for(3))); - assert!(System::events().iter().any(|event| { - event.event == Event::elections_phragmen(RawEvent::VoterReported(3, 5, true)) - })); - - // target got bond back. - assert_eq!(balances(&3), (30, 0)); - // reporter nothing changed. - assert_eq!(balances(&5), (45, 5)); - }); - } - - #[test] - fn report_voter_should_do_nothing_when_bad_report() { - ExtBuilder::default().build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(submit_candidacy(Origin::signed(4))); - - assert_ok!(vote(Origin::signed(5), vec![5], 50)); - assert_ok!(vote(Origin::signed(4), vec![4], 40)); - - System::set_block_number(5); - Elections::end_block(System::block_number()); - - assert_eq!(Elections::members_ids(), vec![4, 5]); - assert!(candidate_ids().is_empty()); - - assert_eq!(balances(&4), (35, 5)); - assert_eq!(balances(&5), (45, 5)); - - assert_ok!(Elections::report_defunct_voter(Origin::signed(5), defunct_for(4))); - assert!(System::events().iter().any(|event| { - event.event == Event::elections_phragmen(RawEvent::VoterReported(4, 5, false)) - })); - - // neither change. Only 5 is paying tx fee (not reflected here). - assert_eq!(balances(&4), (35, 5)); - assert_eq!(balances(&5), (45, 5)); - }); - } - #[test] fn simple_voting_rounds_should_work() { ExtBuilder::default().build_and_execute(|| { @@ -2543,7 +2391,7 @@ mod tests { // sorted based on account id. assert_eq!(Elections::members_ids(), vec![4, 5]); // sorted based on merit (least -> most) - assert_eq!(Elections::runners_up_ids(), vec![3, 2]); + assert_eq!(runners_up_ids(), vec![3, 2]); // runner ups are still locked. assert_eq!(balances(&4), (35, 5)); @@ -2593,7 +2441,7 @@ mod tests { System::set_block_number(5); Elections::end_block(System::block_number()); assert_eq!(Elections::members_ids(), vec![4, 5]); - assert_eq!(Elections::runners_up_ids(), vec![2]); + assert_eq!(runners_up_ids(), vec![2]); assert_eq!(balances(&2), (15, 5)); assert_ok!(submit_candidacy(Origin::signed(3))); @@ -2602,7 +2450,7 @@ mod tests { System::set_block_number(10); Elections::end_block(System::block_number()); - assert_eq!(Elections::runners_up_ids(), vec![3]); + assert_eq!(runners_up_ids(), vec![3]); assert_eq!(balances(&2), (15, 2)); }); } @@ -2784,7 +2632,7 @@ mod tests { System::set_block_number(5); Elections::end_block(System::block_number()); assert_eq!(Elections::members_ids(), vec![4, 5]); - assert_eq!(Elections::runners_up_ids(), vec![3]); + assert_eq!(runners_up_ids(), vec![3]); // there is a replacement! and this one needs a weight refund. assert_err_with_weight!( @@ -2969,13 +2817,13 @@ mod tests { Elections::end_block(System::block_number()); assert_eq!(Elections::members_ids(), vec![4, 5]); - assert_eq!(Elections::runners_up_ids(), vec![2, 3]); + assert_eq!(runners_up_ids(), vec![2, 3]); assert_ok!(Elections::renounce_candidacy(Origin::signed(4), Renouncing::Member)); assert_eq!(balances(&4), (38, 2)); // 2 is voting bond. assert_eq!(Elections::members_ids(), vec![3, 5]); - assert_eq!(Elections::runners_up_ids(), vec![2]); + assert_eq!(runners_up_ids(), vec![2]); }) } @@ -2992,14 +2840,14 @@ mod tests { Elections::end_block(System::block_number()); assert_eq!(Elections::members_ids(), vec![4, 5]); - assert!(Elections::runners_up_ids().is_empty()); + assert!(runners_up_ids().is_empty()); assert_ok!(Elections::renounce_candidacy(Origin::signed(4), Renouncing::Member)); assert_eq!(balances(&4), (38, 2)); // 2 is voting bond. // no replacement assert_eq!(Elections::members_ids(), vec![5]); - assert!(Elections::runners_up_ids().is_empty()); + assert!(runners_up_ids().is_empty()); }) } @@ -3020,13 +2868,13 @@ mod tests { Elections::end_block(System::block_number()); assert_eq!(Elections::members_ids(), vec![4, 5]); - assert_eq!(Elections::runners_up_ids(), vec![2, 3]); + assert_eq!(runners_up_ids(), vec![2, 3]); assert_ok!(Elections::renounce_candidacy(Origin::signed(3), Renouncing::RunnerUp)); assert_eq!(balances(&3), (28, 2)); // 2 is voting bond. assert_eq!(Elections::members_ids(), vec![4, 5]); - assert_eq!(Elections::runners_up_ids(), vec![2]); + assert_eq!(runners_up_ids(), vec![2]); }) } @@ -3047,10 +2895,10 @@ mod tests { Elections::end_block(System::block_number()); assert_eq!(Elections::members_ids(), vec![2, 4]); - assert_eq!(Elections::runners_up_ids(), vec![5, 3]); + assert_eq!(runners_up_ids(), vec![5, 3]); assert_ok!(Elections::renounce_candidacy(Origin::signed(3), Renouncing::RunnerUp)); assert_eq!(Elections::members_ids(), vec![2, 4]); - assert_eq!(Elections::runners_up_ids(), vec![5]); + assert_eq!(runners_up_ids(), vec![5]); }); } @@ -3100,7 +2948,7 @@ mod tests { Elections::end_block(System::block_number()); assert_eq!(Elections::members_ids(), vec![4, 5]); - assert_eq!(Elections::runners_up_ids(), vec![3]); + assert_eq!(runners_up_ids(), vec![3]); assert_noop!( Elections::renounce_candidacy(Origin::signed(3), Renouncing::Member), @@ -3124,7 +2972,7 @@ mod tests { Elections::end_block(System::block_number()); assert_eq!(Elections::members_ids(), vec![4, 5]); - assert_eq!(Elections::runners_up_ids(), vec![3]); + assert_eq!(runners_up_ids(), vec![3]); assert_noop!( Elections::renounce_candidacy(Origin::signed(4), Renouncing::RunnerUp), @@ -3176,7 +3024,7 @@ mod tests { 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!(runners_up_ids(), vec![2, 3]); assert!(candidate_ids().is_empty()); }) } @@ -3188,7 +3036,6 @@ mod tests { assert_ok!(submit_candidacy(Origin::signed(4))); assert_ok!(submit_candidacy(Origin::signed(3))); - assert_ok!(vote(Origin::signed(5), vec![5], 50)); assert_ok!(vote(Origin::signed(4), vec![4], 5)); assert_ok!(vote(Origin::signed(3), vec![3], 15)); @@ -3197,7 +3044,7 @@ mod tests { Elections::end_block(System::block_number()); assert_eq!(Elections::members_ids(), vec![5]); - assert_eq!(Elections::runners_up_ids(), vec![4, 3]); + assert_eq!(runners_up_ids(), vec![4, 3]); assert_ok!(submit_candidacy(Origin::signed(2))); assert_ok!(vote(Origin::signed(2), vec![2], 10)); @@ -3206,7 +3053,7 @@ mod tests { Elections::end_block(System::block_number()); assert_eq!(Elections::members_ids(), vec![5]); - assert_eq!(Elections::runners_up_ids(), vec![2, 3]); + assert_eq!(runners_up_ids(), vec![2, 3]); // 4 is outgoing runner-up. Slash candidacy bond. assert_eq!(balances(&4), (35, 2)); @@ -3231,7 +3078,7 @@ mod tests { Elections::end_block(System::block_number()); assert_eq!(Elections::members_ids(), vec![4]); - assert_eq!(Elections::runners_up_ids(), vec![2, 3]); + assert_eq!(runners_up_ids(), vec![2, 3]); assert_eq!(balances(&4), (35, 5)); assert_eq!(balances(&3), (25, 5)); @@ -3245,7 +3092,7 @@ mod tests { Elections::end_block(System::block_number()); assert_eq!(Elections::members_ids(), vec![5]); - assert_eq!(Elections::runners_up_ids(), vec![3, 4]); + assert_eq!(runners_up_ids(), vec![3, 4]); // 4 went from member to runner-up -- don't slash. assert_eq!(balances(&4), (35, 5)); @@ -3272,7 +3119,7 @@ mod tests { Elections::end_block(System::block_number()); assert_eq!(Elections::members_ids(), vec![4]); - assert_eq!(Elections::runners_up_ids(), vec![2, 3]); + assert_eq!(runners_up_ids(), vec![2, 3]); assert_eq!(balances(&4), (35, 5)); assert_eq!(balances(&3), (25, 5)); @@ -3286,7 +3133,7 @@ mod tests { Elections::end_block(System::block_number()); assert_eq!(Elections::members_ids(), vec![2]); - assert_eq!(Elections::runners_up_ids(), vec![4, 3]); + assert_eq!(runners_up_ids(), vec![4, 3]); // 2 went from runner to member, don't slash assert_eq!(balances(&2), (15, 5)); @@ -3296,4 +3143,51 @@ mod tests { assert_eq!(balances(&3), (25, 5)); }); } + + #[test] + fn dupe_voter_is_moot() {} + + #[test] + fn remove_and_replace_member_works() { + let setup = || { + assert_ok!(submit_candidacy(Origin::signed(5))); + assert_ok!(submit_candidacy(Origin::signed(4))); + assert_ok!(submit_candidacy(Origin::signed(3))); + + assert_ok!(vote(Origin::signed(5), vec![5], 50)); + assert_ok!(vote(Origin::signed(4), vec![4], 40)); + assert_ok!(vote(Origin::signed(3), vec![3], 30)); + + System::set_block_number(5); + Elections::end_block(System::block_number()); + + assert_eq!(Elections::members_ids(), vec![4, 5]); + assert_eq!(runners_up_ids(), vec![3]); + }; + + // member removed, replacement found. + ExtBuilder::default().desired_runners_up(1).build_and_execute(|| { + setup(); + assert_eq!(Elections::remove_and_replace_member(&4, false), Ok(true)); + + assert_eq!(Elections::members_ids(), vec![3, 5]); + assert_eq!(runners_up_ids().len(), 0); + }); + + // member removed, no replacement found. + ExtBuilder::default().desired_runners_up(1).build_and_execute(|| { + setup(); + assert_ok!(Elections::renounce_candidacy(Origin::signed(3), Renouncing::RunnerUp)); + assert_eq!(Elections::remove_and_replace_member(&4, false), Ok(false)); + + assert_eq!(Elections::members_ids(), vec![5]); + assert_eq!(runners_up_ids().len(), 0); + }); + + // wrong member to remove. + ExtBuilder::default().desired_runners_up(1).build_and_execute(|| { + setup(); + assert!(matches!(Elections::remove_and_replace_member(&2, false), Err(_))); + }); + } } diff --git a/frame/support/src/traits.rs b/frame/support/src/traits.rs index bc1700a43c3ee..b20e0f299ba52 100644 --- a/frame/support/src/traits.rs +++ b/frame/support/src/traits.rs @@ -1265,12 +1265,12 @@ pub trait ChangeMembers { Self::change_members_sorted(&incoming[..], &outgoing[..], &new_members); } - /// Compute diff between new and old members; they **must already be sorted**. + /// Compute diff between new and old members; they **must already be sorted**. /// /// Returns incoming and outgoing members. fn compute_members_diff( new_members: &[AccountId], - old_members: &[AccountId] + old_members: &[AccountId], ) -> (Vec, Vec) { let mut old_iter = old_members.iter(); let mut new_iter = new_members.iter(); @@ -1304,6 +1304,11 @@ pub trait ChangeMembers { /// Set the prime member. fn set_prime(_prime: Option) {} + + /// Get the current prime. + fn get_prime() -> Option { + None + } } impl ChangeMembers for () { @@ -1569,7 +1574,7 @@ pub mod schedule { /// Reschedule a task. For one-off tasks, this dispatch is guaranteed to succeed /// only if it is executed *before* the currently scheduled block. For periodic tasks, /// this dispatch is guaranteed to succeed only before the *initial* execution; for - /// others, use `reschedule_named`. + /// others, use `reschedule_named`. /// /// Will return an error if the `address` is invalid. fn reschedule( From 5d09983ebe2a7012591053ca17e418b5bf8b3794 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Thu, 19 Nov 2020 15:15:04 +0000 Subject: [PATCH 36/69] more fixes. --- frame/elections-phragmen/src/lib.rs | 183 +++++++++++++++++++--------- frame/support/src/traits.rs | 4 +- 2 files changed, 125 insertions(+), 62 deletions(-) diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index 4852dda6a1c30..5be950eec2ea3 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -391,7 +391,7 @@ decl_storage! { // make sure they have enough stake. assert!( T::Currency::free_balance(member) >= *stake, - "Genesis member does not have enough stake", + "Genesis member does not have enough stake.", ); // reserve candidacy bond and set as members. @@ -406,7 +406,7 @@ decl_storage! { Ok(_) => panic!("Duplicate member in elections-phragmen genesis: {}", member), Err(pos) => members.insert( pos, - SeatHolder { who: member.clone(), stake: *stake, deposit: 0u32.into() }, + SeatHolder { who: member.clone(), stake: *stake, deposit: T::CandidacyBond::get() }, ), } }); @@ -450,7 +450,7 @@ decl_error! { /// Member cannot re-submit candidacy. MemberSubmit, /// Runner cannot re-submit candidacy. - RunnerSubmit, + RunnerUpSubmit, /// Candidate does not have enough funds. InsufficientCandidateFunds, /// Not a member. @@ -636,7 +636,7 @@ decl_module! { let index = is_candidate.unwrap_err(); ensure!(!Self::is_member(&who), Error::::MemberSubmit); - ensure!(!Self::is_runner_up(&who), Error::::RunnerSubmit); + ensure!(!Self::is_runner_up(&who), Error::::RunnerUpSubmit); T::Currency::reserve(&who, T::CandidacyBond::get()) .map_err(|_| Error::::InsufficientCandidateFunds)?; @@ -812,7 +812,9 @@ impl Module { // - `Err(_)` if who is not a member. >::try_mutate(|members| { // we remove the member anyhow, regardless of having a runner-up or not. + // TODO: a way to make sure all tests operate in a way that accounts are not sorted. let remove_index = members + // note: members are always sorted. .binary_search_by(|m| m.who.cmp(who)) .map_err(|_| Error::::NotMember)?; let removed = members.remove(remove_index); @@ -873,7 +875,6 @@ impl Module { /// O(LogN) given N candidates. fn is_candidate(who: &T::AccountId) -> Result<(), usize> { Self::candidates() - // TODO: review all use cases of binary-search .binary_search_by(|c| c.0.cmp(who)) .map(|_| ()) } @@ -989,7 +990,6 @@ impl Module { let desired_runners_up = Self::desired_runners_up() as usize; let num_to_elect = desired_runners_up + desired_seats; - // TODO: what if we change deposit this on the fly? let mut candidates_and_deposit = Self::candidates(); // add all the previous members and runners-up as candidates as well. candidates_and_deposit.append(&mut Self::implicit_candidates()); @@ -1079,10 +1079,9 @@ impl Module { .collect::>(); new_runners_up_ids_sorted.sort(); - // Now we select a prime member using a [Borda - // count](https://en.wikipedia.org/wiki/Borda_count). We weigh everyone's vote for that - // new member by a multiplier based on the order of the votes. i.e. the first person a - // voter votes for gets a 16x multiplier, the next person gets a 15x multiplier, an so + // Now we select a prime member using a [Borda count](https://en.wikipedia.org/wiki/Borda_count). We + // weigh everyone's vote for that new member by a multiplier based on the order of the votes. i.e. the + // first person a voter votes for gets a 16x multiplier, the next person gets a 15x multiplier, an so // on... (assuming `MAXIMUM_VOTE` = 16) let mut prime_votes = new_members_sorted_by_id .iter() @@ -1116,7 +1115,7 @@ impl Module { .collect::>(); // report member changes. We compute diff because we need the outgoing list. - let (incoming, outgoing) = T::ChangeMembers::compute_members_diff( + let (incoming, outgoing) = T::ChangeMembers::compute_members_diff_sorted( &new_members_ids_sorted, &old_members_ids_sorted, ); @@ -1131,8 +1130,9 @@ impl Module { // holder will lose their bond. candidates_and_deposit.iter().for_each(|(c, d)| { // any candidate who is not a member and not a runner up. - if new_members_ids_sorted.binary_search(c).is_err() - && new_runners_up_ids_sorted.binary_search(c).is_err() + if + new_members_ids_sorted.binary_search(c).is_err() && + new_runners_up_ids_sorted.binary_search(c).is_err() { let (imbalance, _) = T::Currency::slash_reserved(c, *d); T::LoserCandidate::on_unbalanced(imbalance); @@ -1279,16 +1279,20 @@ mod tests { type WeightInfo = (); } - parameter_types! { - pub const CandidacyBond: u64 = 3; - } - thread_local! { static VOTING_BOND_BASE: RefCell = RefCell::new(2); static VOTING_BOND_FACTOR: RefCell = RefCell::new(0); static DESIRED_MEMBERS: RefCell = RefCell::new(2); - static DESIRED_RUNNERS_UP: RefCell = RefCell::new(2); + static DESIRED_RUNNERS_UP: RefCell = RefCell::new(0); static TERM_DURATION: RefCell = RefCell::new(5); + static CANDIDACY_BOND: RefCell = RefCell::new(3); + } + + pub struct CandidacyBond; + impl Get for CandidacyBond { + fn get() -> u64 { + CANDIDACY_BOND.with(|v| *v.borrow()) + } } pub struct VotingBondBase; @@ -1324,6 +1328,7 @@ mod tests { pub struct TestChangeMembers; impl ChangeMembers for TestChangeMembers { fn change_members_sorted(incoming: &[u64], outgoing: &[u64], new: &[u64]) { + dbg!(incoming, outgoing, new); // new, incoming, outgoing must be sorted. let mut new_sorted = new.to_vec(); new_sorted.sort(); @@ -1403,68 +1408,56 @@ mod tests { ); pub struct ExtBuilder { - genesis_members: Vec<(u64, u64)>, balance_factor: u64, - voter_bond: u64, - voter_bond_factor: u64, - term_duration: u64, - desired_runners_up: u32, - desired_members: u32, + genesis_members: Vec<(u64, u64)>, } impl Default for ExtBuilder { fn default() -> Self { Self { - genesis_members: vec![], balance_factor: 1, - voter_bond: 2, - voter_bond_factor: 0, - term_duration: 5, - desired_runners_up: 0, - desired_members: 2, + genesis_members: vec![], } } } impl ExtBuilder { - pub fn voter_bond(mut self, fee: u64) -> Self { - self.voter_bond = fee; + pub fn voter_bond(self, bond: u64) -> Self { + VOTING_BOND_BASE.with(|v| *v.borrow_mut() = bond); self } - pub fn voter_bond_factor(mut self, fee: u64) -> Self { - self.voter_bond_factor = fee; + pub fn voter_bond_factor(self, bond: u64) -> Self { + VOTING_BOND_FACTOR.with(|v| *v.borrow_mut() = bond); self } - pub fn desired_runners_up(mut self, count: u32) -> Self { - self.desired_runners_up = count; + pub fn desired_runners_up(self, count: u32) -> Self { + DESIRED_RUNNERS_UP.with(|v| *v.borrow_mut() = count); self } - pub fn term_duration(mut self, duration: u64) -> Self { - self.term_duration = duration; + pub fn term_duration(self, duration: u64) -> Self { + TERM_DURATION.with(|v| *v.borrow_mut() = duration); self } pub fn genesis_members(mut self, members: Vec<(u64, u64)>) -> Self { + MEMBERS.with(|m| { + *m.borrow_mut() = members + .iter() + .map(|(m, _)| m.clone()) + .collect::>() + }); self.genesis_members = members; self } - pub fn desired_members(mut self, count: u32) -> Self { - self.desired_members = count; + pub fn desired_members(self, count: u32) -> Self { + DESIRED_MEMBERS.with(|m| *m.borrow_mut() = count); self } pub fn balance_factor(mut self, factor: u64) -> Self { self.balance_factor = factor; self } - fn set_constants(&self) { - VOTING_BOND_BASE.with(|v| *v.borrow_mut() = self.voter_bond); - VOTING_BOND_FACTOR.with(|v| *v.borrow_mut() = self.voter_bond_factor); - TERM_DURATION.with(|v| *v.borrow_mut() = self.term_duration); - DESIRED_RUNNERS_UP.with(|v| *v.borrow_mut() = self.desired_runners_up); - DESIRED_MEMBERS.with(|m| *m.borrow_mut() = self.desired_members); - MEMBERS.with(|m| *m.borrow_mut() = self.genesis_members.iter().map(|(m, _)| m.clone()).collect::>()); - } pub fn build_and_execute(self, test: impl FnOnce() -> ()) { - self.set_constants(); + MEMBERS.with(|m| *m.borrow_mut() = self.genesis_members.iter().map(|(m, _)| m.clone()).collect::>()); let mut ext: sp_io::TestExternalities = GenesisConfig { pallet_balances: Some(pallet_balances::GenesisConfig::{ balances: vec![ @@ -1493,6 +1486,14 @@ mod tests { .collect::>() } + fn candidate_deposit(who: &u64) -> u64 { + Elections::candidates().into_iter().find_map(|(c, d)| if c == *who { Some(d) } else { None }).unwrap_or_default() + } + + fn voter_deposit(who: &u64) -> u64 { + Elections::voting(who).deposit + } + fn runners_up_ids() -> Vec { Elections::runners_up() .into_iter() @@ -1618,19 +1619,18 @@ mod tests { .genesis_members(vec![(1, 10), (2, 20)]) .build_and_execute(|| { System::set_block_number(1); - // TODO: genesis members should also serve bond. assert_eq!( Elections::members(), vec![ SeatHolder { who: 1, stake: 10, - deposit: 0 + deposit: 3, }, SeatHolder { who: 2, stake: 20, - deposit: 0 + deposit: 3, } ] ); @@ -1640,7 +1640,7 @@ mod tests { Voter { stake: 10u64, votes: vec![1], - deposit: 2 + deposit: 2, } ); assert_eq!( @@ -1648,7 +1648,7 @@ mod tests { Voter { stake: 20u64, votes: vec![2], - deposit: 2 + deposit: 2, } ); @@ -1672,12 +1672,12 @@ mod tests { SeatHolder { who: 1, stake: 10, - deposit: 0 + deposit: 3, }, SeatHolder { who: 2, stake: 20, - deposit: 0 + deposit: 3, } ] ); @@ -1719,7 +1719,7 @@ mod tests { #[test] #[should_panic] fn genesis_members_cannot_over_stake_1() { - // 10 cannot reserve 20 as voting bond and extra genesis will panic. + // 20 cannot reserve 20 as voting bond and extra genesis will panic. ExtBuilder::default() .voter_bond(20) .genesis_members(vec![(1, 10), (2, 20)]) @@ -1730,7 +1730,7 @@ mod tests { #[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)]) + .genesis_members(vec![(1, 10), (2, 10), (2, 12)]) .build_and_execute(|| {}); } @@ -1782,10 +1782,48 @@ mod tests { assert!(Elections::is_candidate(&1).is_ok()); assert!(Elections::is_candidate(&2).is_ok()); - // TODO: check deposit. + assert_eq!(candidate_deposit(&1), 3); + assert_eq!(candidate_deposit(&2), 3); + assert_eq!(candidate_deposit(&3), 0); }); } + #[test] + fn updating_candidacy_bond_works() { + ExtBuilder::default().build_and_execute(|| { + assert_ok!(submit_candidacy(Origin::signed(5))); + assert_ok!(vote(Origin::signed(5), vec![5], 50)); + assert_eq!(Elections::candidates(), vec![(5, 3)]); + + // a runtime upgrade changes the bond. + CANDIDACY_BOND.with(|v| *v.borrow_mut() = 4); + + assert_ok!(submit_candidacy(Origin::signed(4))); + assert_ok!(vote(Origin::signed(4), vec![4], 40)); + assert_eq!(Elections::candidates(), vec![(4, 4), (5, 3)]); + + // once elected, they each hold their candidacy bond, no more. + System::set_block_number(5); + Elections::end_block(System::block_number()); + + assert_eq!( + Elections::members(), + vec![ + SeatHolder { + who: 4, + stake: 40, + deposit: 4 + }, + SeatHolder { + who: 5, + stake: 50, + deposit: 3 + } + ] + ); + }) + } + #[test] fn simple_candidate_submission_with_no_votes_should_work() { ExtBuilder::default().build_and_execute(|| { @@ -1865,7 +1903,7 @@ mod tests { assert_noop!( submit_candidacy(Origin::signed(3)), - Error::::RunnerSubmit, + Error::::RunnerUpSubmit, ); }); } @@ -1930,6 +1968,31 @@ mod tests { }); } + #[test] + fn updated_voting_bond_works() { + ExtBuilder::default().build_and_execute(|| { + assert_ok!(submit_candidacy(Origin::signed(5))); + + assert_eq!(balances(&2), (20, 0)); + assert_ok!(vote(Origin::signed(2), vec![5], 5)); + assert_eq!(balances(&2), (18, 2)); + assert_eq!(voter_deposit(&2), 2); + + // a runtime upgrade lowers the voting bond to 1. This guy still un-reserves 2 when + // leaving. + VOTING_BOND_BASE.with(|v| *v.borrow_mut() = 1); + + // proof that bond changed. + assert_eq!(balances(&1), (10, 0)); + assert_ok!(vote(Origin::signed(1), vec![5], 5)); + assert_eq!(balances(&1), (9, 1)); + assert_eq!(voter_deposit(&1), 1); + + assert_ok!(Elections::remove_voter(Origin::signed(2))); + assert_eq!(balances(&2), (20, 0)); + }) + } + #[test] fn voting_reserves_bond_per_vote() { ExtBuilder::default().voter_bond_factor(1).build_and_execute(|| { diff --git a/frame/support/src/traits.rs b/frame/support/src/traits.rs index b20e0f299ba52..33ece411f0cbc 100644 --- a/frame/support/src/traits.rs +++ b/frame/support/src/traits.rs @@ -1261,14 +1261,14 @@ pub trait ChangeMembers { /// /// This resets any previous value of prime. fn set_members_sorted(new_members: &[AccountId], old_members: &[AccountId]) { - let (incoming, outgoing) = Self::compute_members_diff(new_members, old_members); + let (incoming, outgoing) = Self::compute_members_diff_sorted(new_members, old_members); Self::change_members_sorted(&incoming[..], &outgoing[..], &new_members); } /// Compute diff between new and old members; they **must already be sorted**. /// /// Returns incoming and outgoing members. - fn compute_members_diff( + fn compute_members_diff_sorted( new_members: &[AccountId], old_members: &[AccountId], ) -> (Vec, Vec) { From 6668ac17f2cde33891e9278ddf2cc45c7e56a693 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Thu, 19 Nov 2020 15:23:33 +0000 Subject: [PATCH 37/69] Fix node build --- bin/node/runtime/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 6d128343c0976..190525f8ccbc9 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -578,7 +578,6 @@ impl pallet_elections_phragmen::Trait for Runtime { type VotingBondBase = VotingBondBase; type VotingBondFactor = VotingBondFactor; type LoserCandidate = (); - type BadReport = (); type KickedMember = (); type DesiredMembers = DesiredMembers; type DesiredRunnersUp = DesiredRunnersUp; From 3dd01d8c58ed1d3256e724399ec4eba22c069fe8 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Thu, 19 Nov 2020 19:08:45 +0000 Subject: [PATCH 38/69] Some fixes to benchmarks --- frame/elections-phragmen/src/benchmarking.rs | 198 +++---------------- frame/elections-phragmen/src/lib.rs | 157 ++++++++------- frame/elections-phragmen/src/weights.rs | 50 ++--- 3 files changed, 121 insertions(+), 284 deletions(-) diff --git a/frame/elections-phragmen/src/benchmarking.rs b/frame/elections-phragmen/src/benchmarking.rs index 1a5010907c6a4..5acc9c60998a1 100644 --- a/frame/elections-phragmen/src/benchmarking.rs +++ b/frame/elections-phragmen/src/benchmarking.rs @@ -68,20 +68,6 @@ fn candidate_count() -> u32 { >::decode_len().unwrap_or(0usize) as u32 } -/// Get the number of votes of a voter. -fn vote_count_of(who: &T::AccountId) -> u32 { - >::get(who).votes.len() as u32 -} - -/// A `DefunctVoter` struct with correct value -fn defunct_for(who: T::AccountId) -> DefunctVoter> { - DefunctVoter { - who: as_lookup::(who.clone()), - candidate_count: candidate_count::(), - vote_count: vote_count_of::(&who), - } -} - /// Add `c` new candidates. fn submit_candidates(c: u32, prefix: &'static str) -> Result, &'static str> @@ -151,8 +137,8 @@ fn fill_seats_up_to(m: u32) -> Result, &'static str> Ok( >::members() .into_iter() - .map(|(x, _)| x) - .chain(>::runners_up().into_iter().map(|(x, _)| x)) + .map(|m| m.who) + .chain(>::runners_up().into_iter().map(|r| r.who)) .collect() ) } @@ -162,7 +148,7 @@ fn clean() { >::kill(); >::kill(); >::kill(); - let _ = >::drain(); + >::remove_all(); } benchmarks! { @@ -201,14 +187,14 @@ benchmarks! { // original votes. let mut votes = all_candidates.iter().skip(1).cloned().collect::>(); - submit_voter::(caller.clone(), votes.clone(), stake / >::from(10))?; + submit_voter::(caller.clone(), votes.clone(), stake / >::from(10u32))?; // new votes. votes = all_candidates; assert!(votes.len() > >::get(caller.clone()).votes.len()); whitelist!(caller); - }: vote(RawOrigin::Signed(caller), votes, stake / >::from(10)) + }: vote(RawOrigin::Signed(caller), votes, stake / >::from(10u32)) vote_less { let v in 2 .. (MAXIMUM_VOTE as u32); @@ -247,113 +233,6 @@ benchmarks! { whitelist!(caller); }: _(RawOrigin::Signed(caller)) - report_defunct_voter_correct { - // number of already existing candidates that may or may not be voted by the reported - // account. - let c in 1 .. MAX_CANDIDATES; - // number of candidates that the reported voter voted for. The worse case of search here is - // basically `c * v`. - let v in 1 .. (MAXIMUM_VOTE as u32); - // we fix the number of members to the number of desired members and runners-up. We'll be in - // this state almost always. - let m = T::DesiredMembers::get() + T::DesiredRunnersUp::get(); - - clean::(); - let stake = default_stake::(BALANCE_FACTOR); - - // create m members and runners combined. - let _ = fill_seats_up_to::(m)?; - - // create a bunch of candidates as well. - let bailing_candidates = submit_candidates::(v, "bailing_candidates")?; - let all_candidates = submit_candidates::(c, "all_candidates")?; - - // account 1 is the reporter and must be whitelisted. - let account_1 = endowed_account::("caller", 0); - - // account 2 votes for all of the mentioned candidates. - let account_2 = endowed_account::("caller_2", 1); - submit_voter::( - account_2.clone(), - bailing_candidates.clone(), - stake, - )?; - - // all the bailers go away. NOTE: we can simplify this. There's no need to create all these - // candidates and remove them. The defunct voter can just vote for random accounts as long - // as there are enough members (potential candidates). - bailing_candidates.into_iter().for_each(|b| { - let count = candidate_count::(); - assert!(>::renounce_candidacy( - RawOrigin::Signed(b).into(), - Renouncing::Candidate(count), - ).is_ok()); - }); - - let defunct_info = defunct_for::(account_2.clone()); - whitelist!(account_1); - - assert!(>::is_voter(&account_2)); - }: report_defunct_voter(RawOrigin::Signed(account_1.clone()), defunct_info) - verify { - assert!(!>::is_voter(&account_2)); - #[cfg(test)] - { - // reset members in between benchmark tests. - use crate::tests::MEMBERS; - MEMBERS.with(|m| *m.borrow_mut() = vec![]); - } - } - - report_defunct_voter_incorrect { - // number of already existing candidates that may or may not be voted by the reported - // account. - let c in 1 .. MAX_CANDIDATES; - // number of candidates that the reported voter voted for. The worse case of search here is - // basically `c * v`. - let v in 1 .. (MAXIMUM_VOTE as u32); - // we fix the number of members to the number of desired members and runners-up. We'll be in - // this state almost always. - let m = T::DesiredMembers::get() + T::DesiredRunnersUp::get(); - - clean::(); - let stake = default_stake::(BALANCE_FACTOR); - - // create m members and runners combined. - let _ = fill_seats_up_to::(m)?; - - // create a bunch of candidates as well. - let all_candidates = submit_candidates::(c, "candidates")?; - - // account 1 is the reporter and need to be whitelisted. - let account_1 = endowed_account::("caller", 0); - - // account 2 votes for a bunch of crap, and finally a correct candidate. - let account_2 = endowed_account::("caller_2", 1); - let mut invalid: Vec = (0..(v-1)) - .map(|seed| account::("invalid", 0, seed).clone()) - .collect(); - invalid.push(all_candidates.last().unwrap().clone()); - submit_voter::( - account_2.clone(), - invalid, - stake, - )?; - - let defunct_info = defunct_for::(account_2.clone()); - whitelist!(account_1); - }: report_defunct_voter(RawOrigin::Signed(account_1.clone()), defunct_info) - verify { - // account 2 is still a voter. - assert!(>::is_voter(&account_2)); - #[cfg(test)] - { - // reset members in between benchmark tests. - use crate::tests::MEMBERS; - MEMBERS.with(|m| *m.borrow_mut() = vec![]); - } - } - submit_candidacy { // number of already existing candidates. let c in 1 .. MAX_CANDIDATES; @@ -536,50 +415,42 @@ benchmarks! { } } - #[extra] - on_initialize { - // if n % TermDuration is zero, then we run phragmen. The weight function must and should - // check this as it is cheap to do so. TermDuration is not a storage item, it is a constant - // encoded in the runtime. - let c in 1 .. MAX_CANDIDATES; + clean_defunct_voters { + // total number of voters. + let v in (MAX_VOTERS / 2) .. MAX_VOTERS; + // those that are defunct and need removal. + let d in 1 .. (MAX_VOTERS / 2); + + // remove any previous stuff. clean::(); - // create c candidates. - let all_candidates = submit_candidates_with_self_vote::(c, "candidates")?; - // create 500 voters, each voting the maximum 16 - distribute_voters::(all_candidates, MAX_VOTERS, MAXIMUM_VOTE)?; - }: { - // elect - >::on_initialize(T::TermDuration::get()); - } - verify { - assert_eq!(>::members().len() as u32, T::DesiredMembers::get().min(c)); - assert_eq!( - >::runners_up().len() as u32, - T::DesiredRunnersUp::get().min(c.saturating_sub(T::DesiredMembers::get())), - ); + let all_candidates = submit_candidates::(v, "candidates")?; + distribute_voters::(all_candidates, v, MAXIMUM_VOTE)?; - #[cfg(test)] - { - // reset members in between benchmark tests. - use crate::tests::MEMBERS; - MEMBERS.with(|m| *m.borrow_mut() = vec![]); - } + // all candidates leave. + >::kill(); + + // now everyone is defunct + assert!(>::iter().all(|(_, v)| >::is_defunct_voter(&v.votes))); + assert_eq!(>::iter().count() as u32, v); + let root = RawOrigin::Root; + }: _(root, v, d) + verify { + assert_eq!(>::iter().count() as u32, 0); } - #[extra] - phragmen { + election_phragmen { // This is just to focus on phragmen in the context of this module. We always select 20 // members, this is hard-coded in the runtime and cannot be trivially changed at this stage. // Yet, change the number of voters, candidates and edge per voter to see the impact. Note // that we give all candidates a self vote to make sure they are all considered. let c in 1 .. MAX_CANDIDATES; let v in 1 .. MAX_VOTERS; - let e in 1 .. (MAXIMUM_VOTE as u32); + let e in MAX_VOTERS .. (MAX_VOTERS * MAXIMUM_VOTE as u32); clean::(); let all_candidates = submit_candidates_with_self_vote::(c, "candidates")?; - let _ = distribute_voters::(all_candidates, v, e as usize)?; + let _ = distribute_voters::(all_candidates, v, (e / MAX_VOTERS) as usize)?; }: { >::on_initialize(T::TermDuration::get()); } @@ -635,17 +506,6 @@ mod tests { assert_ok!(test_benchmark_remove_voter::()); }); - ExtBuilder::default() - .desired_members(13) - .desired_runners_up(7) - .build_and_execute(|| { - assert_ok!(test_benchmark_report_defunct_voter_correct::()); - }); - - ExtBuilder::default().desired_members(13).desired_runners_up(7).build_and_execute(|| { - assert_ok!(test_benchmark_report_defunct_voter_incorrect::()); - }); - ExtBuilder::default().desired_members(13).desired_runners_up(7).build_and_execute(|| { assert_ok!(test_benchmark_submit_candidacy::()); }); @@ -671,11 +531,11 @@ mod tests { }); ExtBuilder::default().desired_members(13).desired_runners_up(7).build_and_execute(|| { - assert_ok!(test_benchmark_on_initialize::()); + assert_ok!(test_benchmark_clean_defunct_voters::()); }); ExtBuilder::default().desired_members(13).desired_runners_up(7).build_and_execute(|| { - assert_ok!(test_benchmark_phragmen::()); + assert_ok!(test_benchmark_election_phragmen::()); }); } } diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index 5be950eec2ea3..d8e9067bfe70c 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -751,7 +751,7 @@ decl_module! { }).map_err(|e| e.into()) } - /// Clear all voters who are defunct (i.e. the do not serve any purpose at all). The deposit + /// Clean all voters who are defunct (i.e. the do not serve any purpose at all). The deposit /// of the removed voters are returned. /// /// This is an root function to be used only for cleaning the state. @@ -775,8 +775,11 @@ decl_module! { /// /// Checks if an election needs to happen or not. fn on_initialize(n: T::BlockNumber) -> Weight { - // returns the correct weight. - Self::end_block(n) + if !Self::term_duration().is_zero() && (n % Self::term_duration()).is_zero() { + Self::do_phragmen() + } else { + 0 + } } } } @@ -964,20 +967,6 @@ impl Module { debug_assert!(_remainder.is_zero()); } - /// Check there's nothing to do this block. - /// - /// Runs phragmen election and cleans all the previous candidate state. The voter state is NOT - /// cleaned and voters must themselves submit a transaction to retract. - fn end_block(block_number: T::BlockNumber) -> Weight { - if !Self::term_duration().is_zero() { - if (block_number % Self::term_duration()).is_zero() { - Self::do_phragmen(); - return T::MaximumBlockWeight::get() - } - } - 0 - } - /// Run the phragmen election with all required side processes and state updates, if election /// succeeds. Else, it will emit an `ElectionError` event. /// @@ -985,7 +974,7 @@ impl Module { /// /// Reads: O(C + V*E) where C = candidates, V voters and E votes per voter exits. /// Writes: O(M + R) with M desired members and R runners_up. - fn do_phragmen() { + fn do_phragmen() -> Weight { 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; @@ -996,7 +985,7 @@ impl Module { if candidates_and_deposit.len().is_zero() { Self::deposit_event(RawEvent::EmptyTerm); - return; + return T::DbWeight::get().reads(5); } // All of the new winners that come out of phragmen will thus have a deposit recorded. @@ -1011,6 +1000,7 @@ impl Module { let to_votes = |b: BalanceOf| T::CurrencyToVote::to_vote(b, total_issuance); let to_balance = |e: ExtendedBalance| T::CurrencyToVote::to_currency(e, total_issuance); + let mut num_edges: u32 = 0; // used for prime election. let voters_and_stakes = Voting::::iter() .map(|(voter, Voter { stake, votes, .. })| { (voter, stake, votes) }) @@ -1018,9 +1008,15 @@ impl Module { // used for phragmen. let voters_and_votes = voters_and_stakes.iter() .cloned() - .map(|(voter, stake, votes)| { (voter, to_votes(stake), votes)} ) + .map(|(voter, stake, votes)| { + num_edges = num_edges.saturating_add(votes.len() as u32); + (voter, to_votes(stake), votes) + }) .collect::>(); + let weight_candidates = candidates_and_deposit.len() as u32; + let weight_voters = voters_and_votes.len() as u32; + let weight_edges = num_edges; let _ = sp_npos_elections::seq_phragmen::( num_to_elect, candidate_ids, @@ -1184,6 +1180,8 @@ impl Module { frame_support::debug::error!("elections-phragmen: failed to run election [{:?}].", e); Self::deposit_event(RawEvent::ElectionError); }); + + T::WeightInfo::election_phragmen(weight_candidates, weight_voters, weight_edges) } } @@ -1198,9 +1196,9 @@ impl Contains for Module { #[cfg(feature = "runtime-benchmarks")] fn add(who: &T::AccountId) { Members::::mutate(|members| { - match members.binary_search_by(|(a, _b)| a.cmp(who)) { + match members.binary_search_by(|m| m.who.cmp(who)) { Ok(_) => (), - Err(pos) => members.insert(pos, (who.clone(), BalanceOf::::default())), + Err(pos) => members.insert(pos, SeatHolder { who: who.clone(), ..Default::default() }), } }) } @@ -1220,6 +1218,7 @@ mod tests { use super::*; use std::cell::RefCell; use frame_support::{assert_ok, assert_noop, assert_err_with_weight, parameter_types, + traits::OnInitialize, weights::Weight, }; use substrate_test_utils::assert_eq_uvec; @@ -1654,7 +1653,7 @@ mod tests { // they will persist since they have self vote. System::set_block_number(5); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); assert_eq!(Elections::members_ids(), vec![1, 2]); }) @@ -1701,7 +1700,7 @@ mod tests { // they will persist since they have self vote. System::set_block_number(5); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); assert_eq!(Elections::members_ids(), vec![1, 2]); }) @@ -1749,7 +1748,7 @@ mod tests { assert!(candidate_ids().is_empty()); System::set_block_number(5); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); assert!(Elections::members_ids().is_empty()); assert!(Elections::runners_up().is_empty()); @@ -1804,7 +1803,7 @@ mod tests { // once elected, they each hold their candidacy bond, no more. System::set_block_number(5); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); assert_eq!( Elections::members(), @@ -1840,7 +1839,7 @@ mod tests { assert!(Elections::runners_up().is_empty()); System::set_block_number(5); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); assert!(Elections::is_candidate(&1).is_err()); assert!(Elections::is_candidate(&2).is_err()); @@ -1872,7 +1871,7 @@ mod tests { assert_ok!(vote(Origin::signed(2), vec![5], 20)); System::set_block_number(5); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); assert_eq!(Elections::members_ids(), vec![5]); assert!(Elections::runners_up().is_empty()); @@ -1896,7 +1895,7 @@ mod tests { assert_ok!(vote(Origin::signed(1), vec![3], 10)); System::set_block_number(5); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); assert_eq!(Elections::members_ids(), vec![4, 5]); assert_eq!(runners_up_ids(), vec![3]); @@ -2055,7 +2054,7 @@ mod tests { assert_ok!(vote(Origin::signed(2), vec![4, 5], 20)); System::set_block_number(5); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); assert_eq!(Elections::members_ids(), vec![4, 5]); assert!(candidate_ids().is_empty()); @@ -2078,7 +2077,7 @@ mod tests { assert_ok!(vote(Origin::signed(5), vec![5], 50)); System::set_block_number(5); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); assert_eq!(Elections::members_ids(), vec![4, 5]); assert!(candidate_ids().is_empty()); @@ -2104,7 +2103,7 @@ mod tests { assert_ok!(Elections::renounce_candidacy(Origin::signed(4), Renouncing::Candidate(3))); System::set_block_number(5); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); assert_eq!(Elections::members_ids(), vec![3, 5]); assert!(candidate_ids().is_empty()); @@ -2123,7 +2122,7 @@ mod tests { assert_ok!(vote(Origin::signed(5), vec![5], 50)); System::set_block_number(5); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); assert_eq!(Elections::members_ids(), vec![4, 5]); assert_eq!(PRIME.with(|p| *p.borrow()), Some(5)); @@ -2147,7 +2146,7 @@ mod tests { assert_ok!(vote(Origin::signed(5), vec![5], 50)); System::set_block_number(5); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); assert_eq!(Elections::members_ids(), vec![4, 5]); assert_eq!(PRIME.with(|p| *p.borrow()), Some(5)); @@ -2181,7 +2180,7 @@ mod tests { assert_ok!(vote(Origin::signed(5), vec![5], 50)); System::set_block_number(5); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); // now we have 2 members, 1 runner-up, and 1 new candidate assert_ok!(submit_candidacy(Origin::signed(2))); @@ -2279,7 +2278,7 @@ mod tests { assert_ok!(Elections::remove_voter(Origin::signed(4))); System::set_block_number(5); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); assert_eq!(Elections::members_ids(), vec![3, 5]); }); @@ -2308,7 +2307,7 @@ mod tests { assert_eq!(Elections::election_rounds(), 0); System::set_block_number(5); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); assert_eq!(members_and_stake(), vec![(3, 30), (5, 20)]); assert!(Elections::runners_up().is_empty()); @@ -2325,7 +2324,7 @@ mod tests { ExtBuilder::default().build_and_execute(|| { // no candidates, no nothing. System::set_block_number(5); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); assert_eq!( System::events().iter().last().unwrap().event, @@ -2344,7 +2343,7 @@ mod tests { assert_ok!(vote(Origin::signed(4), vec![4], 40)); System::set_block_number(5); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); assert_eq!( System::events().iter().last().unwrap().event, @@ -2358,7 +2357,7 @@ mod tests { assert_ok!(Elections::remove_voter(Origin::signed(4))); System::set_block_number(10); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); assert_eq!( System::events().iter().last().unwrap().event, @@ -2381,7 +2380,7 @@ mod tests { assert_ok!(vote(Origin::signed(5), vec![5], 50)); System::set_block_number(5); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); assert_eq!(members_and_stake(), vec![(5, 50)]); assert_eq!(Elections::election_rounds(), 1); @@ -2390,7 +2389,7 @@ mod tests { assert_ok!(submit_candidacy(Origin::signed(4))); System::set_block_number(10); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); // candidate 4 is affected by an old vote. assert_eq!(members_and_stake(), vec![(4, 30), (5, 50)]); @@ -2413,7 +2412,7 @@ mod tests { assert_ok!(vote(Origin::signed(5), vec![5], 50)); System::set_block_number(5); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); assert_eq!(Elections::election_rounds(), 1); assert_eq!(Elections::members_ids(), vec![4, 5]); @@ -2427,7 +2426,7 @@ mod tests { assert_ok!(submit_candidacy(Origin::signed(4))); System::set_block_number(5); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); assert!(candidate_ids().is_empty()); assert_eq!(Elections::election_rounds(), 1); @@ -2454,7 +2453,7 @@ mod tests { assert_ok!(vote(Origin::signed(5), vec![4], 50)); System::set_block_number(5); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); // sorted based on account id. assert_eq!(Elections::members_ids(), vec![4, 5]); // sorted based on merit (least -> most) @@ -2481,14 +2480,14 @@ mod tests { assert_ok!(vote(Origin::signed(5), vec![5], 50)); System::set_block_number(5); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); assert_eq!(members_and_stake(), vec![(4, 40), (5, 50)]); assert_eq!(runners_up_and_stake(), vec![(2, 20), (3, 30)]); assert_ok!(vote(Origin::signed(5), vec![5], 15)); System::set_block_number(10); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); assert_eq!(members_and_stake(), vec![(3, 30), (4, 40)]); assert_eq!(runners_up_and_stake(), vec![(5, 15), (2, 20)]); }); @@ -2506,7 +2505,7 @@ mod tests { assert_ok!(vote(Origin::signed(5), vec![5], 50)); System::set_block_number(5); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); assert_eq!(Elections::members_ids(), vec![4, 5]); assert_eq!(runners_up_ids(), vec![2]); assert_eq!(balances(&2), (15, 5)); @@ -2515,7 +2514,7 @@ mod tests { assert_ok!(vote(Origin::signed(3), vec![3], 30)); System::set_block_number(10); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); assert_eq!(runners_up_ids(), vec![3]); assert_eq!(balances(&2), (15, 2)); @@ -2534,14 +2533,14 @@ mod tests { assert_eq!(balances(&5), (45, 5)); System::set_block_number(5); - Elections::end_block(System::block_number()); + Elections::on_initialize(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); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); assert!(Elections::members_ids().is_empty()); assert_eq!(balances(&5), (47, 0)); @@ -2560,7 +2559,7 @@ mod tests { assert_eq!(balances(&3), (27, 3)); System::set_block_number(5); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); assert_eq!(Elections::members_ids(), vec![5]); @@ -2581,7 +2580,7 @@ mod tests { assert_ok!(vote(Origin::signed(5), vec![5], 50)); System::set_block_number(5); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); assert_eq!(Elections::members_ids(), vec![4, 5]); assert_eq!(Elections::election_rounds(), 1); @@ -2598,7 +2597,7 @@ mod tests { assert_eq!(candidate_ids(), vec![2, 3]); System::set_block_number(10); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); // 4 removed; 5 and 3 are the new best. assert_eq!(Elections::members_ids(), vec![3, 5]); @@ -2622,7 +2621,7 @@ mod tests { let check_at_block = |b: u32| { System::set_block_number(b.into()); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); // we keep re-electing the same folks. assert_eq!(members_and_stake(), vec![(4, 40), (5, 50)]); assert_eq!(runners_up_and_stake(), vec![(2, 20), (3, 30)]); @@ -2650,7 +2649,7 @@ mod tests { assert_ok!(vote(Origin::signed(5), vec![5], 50)); System::set_block_number(5); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); assert_eq!(Elections::members_ids(), vec![4, 5]); assert_eq!(Elections::election_rounds(), 1); @@ -2676,7 +2675,7 @@ mod tests { assert_ok!(vote(Origin::signed(5), vec![5], 50)); System::set_block_number(5); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); assert_eq!(Elections::members_ids(), vec![4, 5]); // no replacement yet. @@ -2697,7 +2696,7 @@ mod tests { assert_ok!(vote(Origin::signed(5), vec![5], 50)); System::set_block_number(5); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); assert_eq!(Elections::members_ids(), vec![4, 5]); assert_eq!(runners_up_ids(), vec![3]); @@ -2727,7 +2726,7 @@ mod tests { assert_eq!(Elections::election_rounds(), 0); System::set_block_number(5); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); assert_eq!(Elections::members_ids(), vec![3, 5]); assert_eq!(Elections::election_rounds(), 1); @@ -2738,7 +2737,7 @@ mod tests { // meanwhile, no one cares to become a candidate again. System::set_block_number(10); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); assert!(Elections::members_ids().is_empty()); assert_eq!(Elections::election_rounds(), 2); }); @@ -2754,7 +2753,7 @@ mod tests { assert_ok!(vote(Origin::signed(5), vec![5], 50)); System::set_block_number(5); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); assert_eq!(Elections::members_ids(), vec![4, 5]); assert_ok!(submit_candidacy(Origin::signed(1))); @@ -2772,7 +2771,7 @@ mod tests { assert_ok!(vote(Origin::signed(1), vec![1], 10)); System::set_block_number(10); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); // 3, 4 are new members, must still be bonded, nothing slashed. assert_eq!(members_and_stake(), vec![(3, 30), (4, 48)]); @@ -2802,7 +2801,7 @@ mod tests { assert_ok!(vote(Origin::signed(5), vec![10], 50)); System::set_block_number(5); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); assert_eq_uvec!(Elections::members_ids(), vec![3, 4]); assert_eq!(Elections::election_rounds(), 1); @@ -2823,7 +2822,7 @@ mod tests { assert_ok!(vote(Origin::signed(5), vec![4], 50)); System::set_block_number(5); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); // id: low -> high. assert_eq!(members_and_stake(), vec![(4, 50), (5, 40)]); // merit: low -> high. @@ -2859,7 +2858,7 @@ mod tests { assert_ok!(vote(Origin::signed(5), vec![2], 50)); System::set_block_number(5); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); assert_eq!(Elections::members_ids(), vec![2, 4]); assert_ok!(Elections::remove_member(Origin::root(), 2, true)); @@ -2881,7 +2880,7 @@ mod tests { assert_ok!(vote(Origin::signed(2), vec![2], 20)); System::set_block_number(5); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); assert_eq!(Elections::members_ids(), vec![4, 5]); assert_eq!(runners_up_ids(), vec![2, 3]); @@ -2904,7 +2903,7 @@ mod tests { assert_ok!(vote(Origin::signed(4), vec![4], 40)); System::set_block_number(5); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); assert_eq!(Elections::members_ids(), vec![4, 5]); assert!(runners_up_ids().is_empty()); @@ -2932,7 +2931,7 @@ mod tests { assert_ok!(vote(Origin::signed(2), vec![2], 20)); System::set_block_number(5); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); assert_eq!(Elections::members_ids(), vec![4, 5]); assert_eq!(runners_up_ids(), vec![2, 3]); @@ -2959,7 +2958,7 @@ mod tests { assert_ok!(vote(Origin::signed(5), vec![2], 50)); System::set_block_number(5); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); assert_eq!(Elections::members_ids(), vec![2, 4]); assert_eq!(runners_up_ids(), vec![5, 3]); @@ -3012,7 +3011,7 @@ mod tests { assert_ok!(vote(Origin::signed(3), vec![3], 30)); System::set_block_number(5); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); assert_eq!(Elections::members_ids(), vec![4, 5]); assert_eq!(runners_up_ids(), vec![3]); @@ -3036,7 +3035,7 @@ mod tests { assert_ok!(vote(Origin::signed(3), vec![3], 30)); System::set_block_number(5); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); assert_eq!(Elections::members_ids(), vec![4, 5]); assert_eq!(runners_up_ids(), vec![3]); @@ -3088,7 +3087,7 @@ mod tests { assert_ok!(vote(Origin::signed(2), vec![2], 20)); System::set_block_number(5); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); assert_eq!(Elections::members_ids(), vec![1, 4]); assert_eq!(runners_up_ids(), vec![2, 3]); @@ -3108,7 +3107,7 @@ mod tests { assert_ok!(vote(Origin::signed(3), vec![3], 15)); System::set_block_number(5); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); assert_eq!(Elections::members_ids(), vec![5]); assert_eq!(runners_up_ids(), vec![4, 3]); @@ -3117,7 +3116,7 @@ mod tests { assert_ok!(vote(Origin::signed(2), vec![2], 10)); System::set_block_number(10); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); assert_eq!(Elections::members_ids(), vec![5]); assert_eq!(runners_up_ids(), vec![2, 3]); @@ -3142,7 +3141,7 @@ mod tests { assert_ok!(vote(Origin::signed(2), vec![2], 20)); System::set_block_number(5); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); assert_eq!(Elections::members_ids(), vec![4]); assert_eq!(runners_up_ids(), vec![2, 3]); @@ -3156,7 +3155,7 @@ mod tests { assert_ok!(vote(Origin::signed(5), vec![5], 50)); System::set_block_number(10); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); assert_eq!(Elections::members_ids(), vec![5]); assert_eq!(runners_up_ids(), vec![3, 4]); @@ -3183,7 +3182,7 @@ mod tests { assert_ok!(vote(Origin::signed(2), vec![2], 20)); System::set_block_number(5); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); assert_eq!(Elections::members_ids(), vec![4]); assert_eq!(runners_up_ids(), vec![2, 3]); @@ -3197,7 +3196,7 @@ mod tests { assert_ok!(vote(Origin::signed(2), vec![4], 20)); System::set_block_number(10); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); assert_eq!(Elections::members_ids(), vec![2]); assert_eq!(runners_up_ids(), vec![4, 3]); @@ -3226,7 +3225,7 @@ mod tests { assert_ok!(vote(Origin::signed(3), vec![3], 30)); System::set_block_number(5); - Elections::end_block(System::block_number()); + Elections::on_initialize(System::block_number()); assert_eq!(Elections::members_ids(), vec![4, 5]); assert_eq!(runners_up_ids(), vec![3]); diff --git a/frame/elections-phragmen/src/weights.rs b/frame/elections-phragmen/src/weights.rs index ce80b3da0b0bb..f9ec7e5c151bf 100644 --- a/frame/elections-phragmen/src/weights.rs +++ b/frame/elections-phragmen/src/weights.rs @@ -49,14 +49,14 @@ pub trait WeightInfo { fn vote_more(_v: u32, ) -> Weight; fn vote_less(_v: u32, ) -> Weight; fn remove_voter() -> Weight; - fn report_defunct_voter_correct(_c: u32, _v: u32, ) -> Weight; - fn report_defunct_voter_incorrect(_c: u32, _v: u32, ) -> Weight; fn submit_candidacy(_c: u32, ) -> Weight; fn renounce_candidacy_candidate(_c: u32, ) -> Weight; fn renounce_candidacy_members() -> Weight; fn renounce_candidacy_runners_up() -> Weight; fn remove_member_with_replacement() -> Weight; fn remove_member_wrong_refund() -> Weight; + fn clean_defunct_voters(v: u32, d: u32) -> Weight; + fn election_phragmen(c: u32, v: u32, e: u32) -> Weight; } /// Weights for pallet_elections_phragmen using the Substrate node and recommended hardware. @@ -80,22 +80,6 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) - } - fn report_defunct_voter_correct(c: u32, v: u32, ) -> Weight { - (0 as Weight) - .saturating_add((1_746_000 as Weight).saturating_mul(c as Weight)) - .saturating_add((31_383_000 as Weight).saturating_mul(v as Weight)) - .saturating_add(T::DbWeight::get().reads(7 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) - - } - fn report_defunct_voter_incorrect(c: u32, v: u32, ) -> Weight { - (0 as Weight) - .saturating_add((1_725_000 as Weight).saturating_mul(c as Weight)) - .saturating_add((31_293_000 as Weight).saturating_mul(v as Weight)) - .saturating_add(T::DbWeight::get().reads(6 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) - } fn submit_candidacy(c: u32, ) -> Weight { (73_403_000 as Weight) @@ -132,9 +116,13 @@ impl WeightInfo for SubstrateWeight { fn remove_member_wrong_refund() -> Weight { (8_489_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) - } - + fn election_phragmen(c: u32, v: u32, e: u32) -> Weight { + 0 + } + fn clean_defunct_voters(v: u32, d: u32) -> Weight { + 0 + } } // For backwards compatibility and tests @@ -156,22 +144,6 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) - } - fn report_defunct_voter_correct(c: u32, v: u32, ) -> Weight { - (0 as Weight) - .saturating_add((1_746_000 as Weight).saturating_mul(c as Weight)) - .saturating_add((31_383_000 as Weight).saturating_mul(v as Weight)) - .saturating_add(RocksDbWeight::get().reads(7 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) - - } - fn report_defunct_voter_incorrect(c: u32, v: u32, ) -> Weight { - (0 as Weight) - .saturating_add((1_725_000 as Weight).saturating_mul(c as Weight)) - .saturating_add((31_293_000 as Weight).saturating_mul(v as Weight)) - .saturating_add(RocksDbWeight::get().reads(6 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) - } fn submit_candidacy(c: u32, ) -> Weight { (73_403_000 as Weight) @@ -207,4 +179,10 @@ impl WeightInfo for () { fn remove_member_wrong_refund() -> Weight { (8_489_000 as Weight).saturating_add(RocksDbWeight::get().reads(1 as Weight)) } + fn election_phragmen(c: u32, v: u32, e: u32) -> Weight { + 0 + } + fn clean_defunct_voters(v: u32, d: u32) -> Weight { + 0 + } } From cf94f49c90c009e03fa37de8d1382342b58bd61e Mon Sep 17 00:00:00 2001 From: kianenigma Date: Thu, 19 Nov 2020 19:29:00 +0000 Subject: [PATCH 39/69] Fix some warnings. --- frame/elections-phragmen/src/weights.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/frame/elections-phragmen/src/weights.rs b/frame/elections-phragmen/src/weights.rs index f9ec7e5c151bf..6e5f74f586d77 100644 --- a/frame/elections-phragmen/src/weights.rs +++ b/frame/elections-phragmen/src/weights.rs @@ -69,10 +69,10 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().writes(2 as Weight)) } - fn vote_more(v: u32, ) -> Weight { + fn vote_more(_: u32, ) -> Weight { 0 } - fn vote_less(v: u32, ) -> Weight { + fn vote_less(_: u32, ) -> Weight { 0 } fn remove_voter() -> Weight { @@ -117,10 +117,10 @@ impl WeightInfo for SubstrateWeight { (8_489_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) } - fn election_phragmen(c: u32, v: u32, e: u32) -> Weight { + fn election_phragmen(_: u32, _: u32, _: u32) -> Weight { 0 } - fn clean_defunct_voters(v: u32, d: u32) -> Weight { + fn clean_defunct_voters(_: u32, _: u32) -> Weight { 0 } } @@ -133,10 +133,10 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } - fn vote_more(v: u32) -> Weight { + fn vote_more(_: u32) -> Weight { 0 } - fn vote_less(v: u32) -> Weight { + fn vote_less(_: u32) -> Weight { 0 } fn remove_voter() -> Weight { @@ -179,10 +179,10 @@ impl WeightInfo for () { fn remove_member_wrong_refund() -> Weight { (8_489_000 as Weight).saturating_add(RocksDbWeight::get().reads(1 as Weight)) } - fn election_phragmen(c: u32, v: u32, e: u32) -> Weight { + fn election_phragmen(_: u32, _: u32, _: u32) -> Weight { 0 } - fn clean_defunct_voters(v: u32, d: u32) -> Weight { + fn clean_defunct_voters(_: u32, _: u32) -> Weight { 0 } } From ea69a54ab0aad0bd672426f38207f3ceff260ffe Mon Sep 17 00:00:00 2001 From: Parity Benchmarking Bot Date: Fri, 20 Nov 2020 09:12:38 +0000 Subject: [PATCH 40/69] cargo run --release --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=pallet_elections_phragmen --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/elections-phragmen/src/weights.rs --template=./.maintain/frame-weight-template.hbs --- frame/elections-phragmen/src/weights.rs | 159 ++++++++++++++---------- 1 file changed, 90 insertions(+), 69 deletions(-) diff --git a/frame/elections-phragmen/src/weights.rs b/frame/elections-phragmen/src/weights.rs index 6e5f74f586d77..fd4bbad2b375e 100644 --- a/frame/elections-phragmen/src/weights.rs +++ b/frame/elections-phragmen/src/weights.rs @@ -17,7 +17,7 @@ //! Weights for pallet_elections_phragmen //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 2.0.0 -//! DATE: 2020-10-27, STEPS: [50, ], REPEAT: 20, LOW RANGE: [], HIGH RANGE: [] +//! DATE: 2020-11-20, STEPS: [50, ], REPEAT: 20, LOW RANGE: [], HIGH RANGE: [] //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 // Executed Command: @@ -34,155 +34,176 @@ // --output=./frame/elections-phragmen/src/weights.rs // --template=./.maintain/frame-weight-template.hbs + #![allow(unused_parens)] #![allow(unused_imports)] -use frame_support::{ - traits::Get, - weights::{constants::RocksDbWeight, Weight}, -}; +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; use sp_std::marker::PhantomData; /// Weight functions needed for pallet_elections_phragmen. pub trait WeightInfo { - fn vote_equal(_v: u32, ) -> Weight; - fn vote_more(_v: u32, ) -> Weight; - fn vote_less(_v: u32, ) -> Weight; + fn vote_equal(v: u32, ) -> Weight; + fn vote_more(v: u32, ) -> Weight; + fn vote_less(v: u32, ) -> Weight; fn remove_voter() -> Weight; - fn submit_candidacy(_c: u32, ) -> Weight; - fn renounce_candidacy_candidate(_c: u32, ) -> Weight; + fn submit_candidacy(c: u32, ) -> Weight; + fn renounce_candidacy_candidate(c: u32, ) -> Weight; fn renounce_candidacy_members() -> Weight; fn renounce_candidacy_runners_up() -> Weight; fn remove_member_with_replacement() -> Weight; fn remove_member_wrong_refund() -> Weight; - fn clean_defunct_voters(v: u32, d: u32) -> Weight; - fn election_phragmen(c: u32, v: u32, e: u32) -> Weight; + fn clean_defunct_voters(v: u32, d: u32, ) -> Weight; + fn election_phragmen(c: u32, v: u32, e: u32, ) -> Weight; } /// Weights for pallet_elections_phragmen using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { fn vote_equal(v: u32, ) -> Weight { - (89_627_000 as Weight) - .saturating_add((197_000 as Weight).saturating_mul(v as Weight)) + (58_246_000 as Weight) + .saturating_add((420_000 as Weight).saturating_mul(v as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) - } - fn vote_more(_: u32, ) -> Weight { - 0 + fn vote_more(v: u32, ) -> Weight { + (88_897_000 as Weight) + .saturating_add((430_000 as Weight).saturating_mul(v as Weight)) + .saturating_add(T::DbWeight::get().reads(5 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) } - fn vote_less(_: u32, ) -> Weight { - 0 + fn vote_less(v: u32, ) -> Weight { + (84_344_000 as Weight) + .saturating_add((423_000 as Weight).saturating_mul(v as Weight)) + .saturating_add(T::DbWeight::get().reads(5 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) } fn remove_voter() -> Weight { - (73_774_000 as Weight) + (80_901_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) - } fn submit_candidacy(c: u32, ) -> Weight { - (73_403_000 as Weight) - .saturating_add((314_000 as Weight).saturating_mul(c as Weight)) + (70_684_000 as Weight) + .saturating_add((425_000 as Weight).saturating_mul(c as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } fn renounce_candidacy_candidate(c: u32, ) -> Weight { - (48_834_000 as Weight) - .saturating_add((187_000 as Weight).saturating_mul(c as Weight)) + (49_223_000 as Weight) + .saturating_add((223_000 as Weight).saturating_mul(c as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } fn renounce_candidacy_members() -> Weight { - (78_402_000 as Weight) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) + (91_653_000 as Weight) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) - } fn renounce_candidacy_runners_up() -> Weight { - (49_054_000 as Weight) + (49_767_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } fn remove_member_with_replacement() -> Weight { - (75_421_000 as Weight) - .saturating_add(T::DbWeight::get().reads(4 as Weight)) + (100_030_000 as Weight) + .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) - } fn remove_member_wrong_refund() -> Weight { - (8_489_000 as Weight) + (9_062_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) } - fn election_phragmen(_: u32, _: u32, _: u32) -> Weight { - 0 - } - fn clean_defunct_voters(_: u32, _: u32) -> Weight { - 0 + fn clean_defunct_voters(v: u32, d: u32, ) -> Weight { + (0 as Weight) + .saturating_add((163_343_000 as Weight).saturating_mul(v as Weight)) + .saturating_add((357_000 as Weight).saturating_mul(d as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + .saturating_add(T::DbWeight::get().reads((3 as Weight).saturating_mul(v as Weight))) + .saturating_add(T::DbWeight::get().writes((3 as Weight).saturating_mul(v as Weight))) + } + fn election_phragmen(c: u32, v: u32, e: u32, ) -> Weight { + (0 as Weight) + .saturating_add((85_749_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((129_489_000 as Weight).saturating_mul(v as Weight)) + .saturating_add((9_132_000 as Weight).saturating_mul(e as Weight)) + .saturating_add(T::DbWeight::get().reads((2 as Weight).saturating_mul(c as Weight))) + .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(v as Weight))) + .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(c as Weight))) } } // For backwards compatibility and tests impl WeightInfo for () { - fn vote_equal(v: u32) -> Weight { - (89_627_000 as Weight) - .saturating_add((197_000 as Weight).saturating_mul(v as Weight)) + fn vote_equal(v: u32, ) -> Weight { + (58_246_000 as Weight) + .saturating_add((420_000 as Weight).saturating_mul(v as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } - fn vote_more(_: u32) -> Weight { - 0 + fn vote_more(v: u32, ) -> Weight { + (88_897_000 as Weight) + .saturating_add((430_000 as Weight).saturating_mul(v as Weight)) + .saturating_add(RocksDbWeight::get().reads(5 as Weight)) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } - fn vote_less(_: u32) -> Weight { - 0 + fn vote_less(v: u32, ) -> Weight { + (84_344_000 as Weight) + .saturating_add((423_000 as Weight).saturating_mul(v as Weight)) + .saturating_add(RocksDbWeight::get().reads(5 as Weight)) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } fn remove_voter() -> Weight { - (73_774_000 as Weight) + (80_901_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) - } fn submit_candidacy(c: u32, ) -> Weight { - (73_403_000 as Weight) - .saturating_add((314_000 as Weight).saturating_mul(c as Weight)) + (70_684_000 as Weight) + .saturating_add((425_000 as Weight).saturating_mul(c as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - } fn renounce_candidacy_candidate(c: u32, ) -> Weight { - (48_834_000 as Weight) - .saturating_add((187_000 as Weight).saturating_mul(c as Weight)) + (49_223_000 as Weight) + .saturating_add((223_000 as Weight).saturating_mul(c as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - } fn renounce_candidacy_members() -> Weight { - (78_402_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) + (91_653_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) - } fn renounce_candidacy_runners_up() -> Weight { - (49_054_000 as Weight) + (49_767_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - } fn remove_member_with_replacement() -> Weight { - (75_421_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(4 as Weight)) + (100_030_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } fn remove_member_wrong_refund() -> Weight { - (8_489_000 as Weight).saturating_add(RocksDbWeight::get().reads(1 as Weight)) - } - fn election_phragmen(_: u32, _: u32, _: u32) -> Weight { - 0 + (9_062_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(1 as Weight)) } - fn clean_defunct_voters(_: u32, _: u32) -> Weight { - 0 + fn clean_defunct_voters(v: u32, d: u32, ) -> Weight { + (0 as Weight) + .saturating_add((163_343_000 as Weight).saturating_mul(v as Weight)) + .saturating_add((357_000 as Weight).saturating_mul(d as Weight)) + .saturating_add(RocksDbWeight::get().reads(4 as Weight)) + .saturating_add(RocksDbWeight::get().reads((3 as Weight).saturating_mul(v as Weight))) + .saturating_add(RocksDbWeight::get().writes((3 as Weight).saturating_mul(v as Weight))) + } + fn election_phragmen(c: u32, v: u32, e: u32, ) -> Weight { + (0 as Weight) + .saturating_add((85_749_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((129_489_000 as Weight).saturating_mul(v as Weight)) + .saturating_add((9_132_000 as Weight).saturating_mul(e as Weight)) + .saturating_add(RocksDbWeight::get().reads((2 as Weight).saturating_mul(c as Weight))) + .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(v as Weight))) + .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(c as Weight))) } } From b2f36bdfcc4ca87e655639bc02dac7fd888b7061 Mon Sep 17 00:00:00 2001 From: Parity Benchmarking Bot Date: Fri, 20 Nov 2020 09:32:23 +0000 Subject: [PATCH 41/69] cargo run --release --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=pallet_elections_phragmen --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/elections-phragmen/src/weights.rs --template=./.maintain/frame-weight-template.hbs --- frame/elections-phragmen/src/weights.rs | 82 ++++++++++++------------- 1 file changed, 40 insertions(+), 42 deletions(-) diff --git a/frame/elections-phragmen/src/weights.rs b/frame/elections-phragmen/src/weights.rs index fd4bbad2b375e..6410dbcf8c223 100644 --- a/frame/elections-phragmen/src/weights.rs +++ b/frame/elections-phragmen/src/weights.rs @@ -61,72 +61,71 @@ pub trait WeightInfo { pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { fn vote_equal(v: u32, ) -> Weight { - (58_246_000 as Weight) - .saturating_add((420_000 as Weight).saturating_mul(v as Weight)) + (57_585_000 as Weight) + .saturating_add((409_000 as Weight).saturating_mul(v as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } fn vote_more(v: u32, ) -> Weight { - (88_897_000 as Weight) - .saturating_add((430_000 as Weight).saturating_mul(v as Weight)) + (87_970_000 as Weight) + .saturating_add((415_000 as Weight).saturating_mul(v as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } fn vote_less(v: u32, ) -> Weight { - (84_344_000 as Weight) - .saturating_add((423_000 as Weight).saturating_mul(v as Weight)) + (83_650_000 as Weight) + .saturating_add((405_000 as Weight).saturating_mul(v as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } fn remove_voter() -> Weight { - (80_901_000 as Weight) + (80_456_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } fn submit_candidacy(c: u32, ) -> Weight { - (70_684_000 as Weight) - .saturating_add((425_000 as Weight).saturating_mul(c as Weight)) + (70_738_000 as Weight) + .saturating_add((428_000 as Weight).saturating_mul(c as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } fn renounce_candidacy_candidate(c: u32, ) -> Weight { - (49_223_000 as Weight) - .saturating_add((223_000 as Weight).saturating_mul(c as Weight)) + (50_244_000 as Weight) + .saturating_add((214_000 as Weight).saturating_mul(c as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } fn renounce_candidacy_members() -> Weight { - (91_653_000 as Weight) + (90_817_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } fn renounce_candidacy_runners_up() -> Weight { - (49_767_000 as Weight) + (49_801_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } fn remove_member_with_replacement() -> Weight { - (100_030_000 as Weight) + (99_157_000 as Weight) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } fn remove_member_wrong_refund() -> Weight { - (9_062_000 as Weight) + (9_042_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) } - fn clean_defunct_voters(v: u32, d: u32, ) -> Weight { + fn clean_defunct_voters(v: u32, _d: u32, ) -> Weight { (0 as Weight) - .saturating_add((163_343_000 as Weight).saturating_mul(v as Weight)) - .saturating_add((357_000 as Weight).saturating_mul(d as Weight)) + .saturating_add((158_896_000 as Weight).saturating_mul(v as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().reads((3 as Weight).saturating_mul(v as Weight))) .saturating_add(T::DbWeight::get().writes((3 as Weight).saturating_mul(v as Weight))) } fn election_phragmen(c: u32, v: u32, e: u32, ) -> Weight { (0 as Weight) - .saturating_add((85_749_000 as Weight).saturating_mul(c as Weight)) - .saturating_add((129_489_000 as Weight).saturating_mul(v as Weight)) - .saturating_add((9_132_000 as Weight).saturating_mul(e as Weight)) + .saturating_add((85_751_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((130_226_000 as Weight).saturating_mul(v as Weight)) + .saturating_add((9_200_000 as Weight).saturating_mul(e as Weight)) .saturating_add(T::DbWeight::get().reads((2 as Weight).saturating_mul(c as Weight))) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(v as Weight))) .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(c as Weight))) @@ -136,72 +135,71 @@ impl WeightInfo for SubstrateWeight { // For backwards compatibility and tests impl WeightInfo for () { fn vote_equal(v: u32, ) -> Weight { - (58_246_000 as Weight) - .saturating_add((420_000 as Weight).saturating_mul(v as Weight)) + (57_585_000 as Weight) + .saturating_add((409_000 as Weight).saturating_mul(v as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } fn vote_more(v: u32, ) -> Weight { - (88_897_000 as Weight) - .saturating_add((430_000 as Weight).saturating_mul(v as Weight)) + (87_970_000 as Weight) + .saturating_add((415_000 as Weight).saturating_mul(v as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } fn vote_less(v: u32, ) -> Weight { - (84_344_000 as Weight) - .saturating_add((423_000 as Weight).saturating_mul(v as Weight)) + (83_650_000 as Weight) + .saturating_add((405_000 as Weight).saturating_mul(v as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } fn remove_voter() -> Weight { - (80_901_000 as Weight) + (80_456_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } fn submit_candidacy(c: u32, ) -> Weight { - (70_684_000 as Weight) - .saturating_add((425_000 as Weight).saturating_mul(c as Weight)) + (70_738_000 as Weight) + .saturating_add((428_000 as Weight).saturating_mul(c as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } fn renounce_candidacy_candidate(c: u32, ) -> Weight { - (49_223_000 as Weight) - .saturating_add((223_000 as Weight).saturating_mul(c as Weight)) + (50_244_000 as Weight) + .saturating_add((214_000 as Weight).saturating_mul(c as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } fn renounce_candidacy_members() -> Weight { - (91_653_000 as Weight) + (90_817_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } fn renounce_candidacy_runners_up() -> Weight { - (49_767_000 as Weight) + (49_801_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } fn remove_member_with_replacement() -> Weight { - (100_030_000 as Weight) + (99_157_000 as Weight) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } fn remove_member_wrong_refund() -> Weight { - (9_062_000 as Weight) + (9_042_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) } - fn clean_defunct_voters(v: u32, d: u32, ) -> Weight { + fn clean_defunct_voters(v: u32, _d: u32, ) -> Weight { (0 as Weight) - .saturating_add((163_343_000 as Weight).saturating_mul(v as Weight)) - .saturating_add((357_000 as Weight).saturating_mul(d as Weight)) + .saturating_add((158_896_000 as Weight).saturating_mul(v as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().reads((3 as Weight).saturating_mul(v as Weight))) .saturating_add(RocksDbWeight::get().writes((3 as Weight).saturating_mul(v as Weight))) } fn election_phragmen(c: u32, v: u32, e: u32, ) -> Weight { (0 as Weight) - .saturating_add((85_749_000 as Weight).saturating_mul(c as Weight)) - .saturating_add((129_489_000 as Weight).saturating_mul(v as Weight)) - .saturating_add((9_132_000 as Weight).saturating_mul(e as Weight)) + .saturating_add((85_751_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((130_226_000 as Weight).saturating_mul(v as Weight)) + .saturating_add((9_200_000 as Weight).saturating_mul(e as Weight)) .saturating_add(RocksDbWeight::get().reads((2 as Weight).saturating_mul(c as Weight))) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(v as Weight))) .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(c as Weight))) From 1fd2b081f0ccd416abcf91101767ee941383f7ec Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Wed, 25 Nov 2020 13:29:13 +0100 Subject: [PATCH 42/69] Update frame/elections-phragmen/src/lib.rs Co-authored-by: Guillaume Thiolliere --- frame/elections-phragmen/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index d8e9067bfe70c..50254c7e60cbf 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -898,7 +898,7 @@ impl Module { /// Check if `who` is currently an active runner-up. /// - /// O(LogN) given N runners-up. Since runners-up are limited, O(1). + /// O(N) given N runners-up. Since runners-up are limited, O(1). fn is_runner_up(who: &T::AccountId) -> bool { Self::runners_up() .iter() From 585e599a73b70d597726f19e42dd2c35636f7c3d Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 25 Nov 2020 12:31:14 +0000 Subject: [PATCH 43/69] a batch of review comments. --- frame/elections-phragmen/src/lib.rs | 36 +++++++++++++++-------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index d8e9067bfe70c..c18f045e4e3f6 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -614,7 +614,7 @@ decl_module! { /// ### Warning /// /// Even if a candidate ends up being a member, they must call renounce_candidacy to get - /// their deposit back. Winning the spot in an election will also lead to a slash. + /// their deposit back. Losing the spot in an election will also lead to a slash. /// /// # /// The number of current candidates must be provided as witness data. @@ -738,17 +738,16 @@ decl_module! { )); } // else, prediction was correct. - Self::remove_and_replace_member(&who, true).map(|had_replacement| { - Self::deposit_event(RawEvent::MemberKicked(who.clone())); + let had_replacement = Self::remove_and_replace_member(&who, true)?; + Self::deposit_event(RawEvent::MemberKicked(who.clone())); - if !had_replacement { - // if we end up here, we will charge a full block weight. - Self::do_phragmen(); - } + if !had_replacement { + // if we end up here, we will charge a full block weight. + Self::do_phragmen(); + } - // no refund needed. - None.into() - }).map_err(|e| e.into()) + // no refund needed. + Ok(None.into()) } /// Clean all voters who are defunct (i.e. the do not serve any purpose at all). The deposit @@ -848,7 +847,7 @@ impl Module { .map(|x| x.who.clone()) .collect::>(); let outgoing = &[who.clone()]; - let current_prime = T::ChangeMembers::get_prime().unwrap_or_default(); + let maybe_current_prime = T::ChangeMembers::get_prime(); let return_value = match maybe_replacement { // member ids are already sorted, other two elements have one item. Some(incoming) => { @@ -864,10 +863,15 @@ impl Module { false } }; - if ¤t_prime != who { - // re-set the prime if the removed was not the old prime. - T::ChangeMembers::set_prime(Some(current_prime)) + + // if there was a prime before and they are not the one being removed, then set them + // again. + if let Some(current_prime) = maybe_current_prime { + if ¤t_prime != who { + T::ChangeMembers::set_prime(Some(current_prime)); + } } + return_value }) } @@ -956,11 +960,9 @@ impl Module { /// Remove a certain someone as a voter. fn do_remove_voter(who: &T::AccountId) { - let Voter { deposit, .. } = >::get(who); + let Voter { deposit, .. } = >::take(who); // remove storage, lock and unreserve. - >::remove(who); - T::Currency::remove_lock(T::ModuleId::get(), who); let _remainder = T::Currency::unreserve(who, deposit); From 4b34e7294c17a9179e47ab803fd2bcc9d893f975 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 25 Nov 2020 14:44:40 +0000 Subject: [PATCH 44/69] Fix a test. --- frame/elections-phragmen/src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index eebb677098947..c5ef9ba8c1a36 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -1329,7 +1329,6 @@ mod tests { pub struct TestChangeMembers; impl ChangeMembers for TestChangeMembers { fn change_members_sorted(incoming: &[u64], outgoing: &[u64], new: &[u64]) { - dbg!(incoming, outgoing, new); // new, incoming, outgoing must be sorted. let mut new_sorted = new.to_vec(); new_sorted.sort(); @@ -2684,7 +2683,7 @@ mod tests { assert_err_with_weight!( Elections::remove_member(Origin::root(), 4, true), Error::::InvalidReplacement, - Some(33489000), // only thing that matters for now is that it is NOT the full block. + Some(34042000), // only thing that matters for now is that it is NOT the full block. ); }); From ecb0e4e846017fff300f70e09c9d064069ee8893 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 25 Nov 2020 15:04:38 +0000 Subject: [PATCH 45/69] Fix some more tests. --- frame/elections-phragmen/src/lib.rs | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index 74a24ae62dc31..415d5ca481c89 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -1098,6 +1098,7 @@ impl Module { } } } + // We then select the new member with the highest weighted stake. In the case of // a tie, the last person in the list with the tied score is selected. This is // the person with the "highest" account id based on the sort above. @@ -1279,14 +1280,12 @@ mod tests { type WeightInfo = (); } - parameter_types! { - pub const CandidacyBond: u64 = 3; - } - frame_support::parameter_types! { - pub static VotingBond: u64 = 2; + pub static VotingBondBase: u64 = 2; + pub static VotingBondFactor: u64 = 0; + pub static CandidacyBond: u64 = 3; pub static DesiredMembers: u32 = 2; - pub static DesiredRunnersUp: u32 = 2; + pub static DesiredRunnersUp: u32 = 0; pub static TermDuration: u64 = 5; pub static Members: Vec = vec![]; pub static Prime: Option = None; @@ -1295,7 +1294,6 @@ mod tests { pub struct TestChangeMembers; impl ChangeMembers for TestChangeMembers { fn change_members_sorted(incoming: &[u64], outgoing: &[u64], new: &[u64]) { - dbg!(incoming, outgoing, new); // new, incoming, outgoing must be sorted. let mut new_sorted = new.to_vec(); new_sorted.sort(); @@ -1565,6 +1563,10 @@ mod tests { fn params_should_work() { ExtBuilder::default().build_and_execute(|| { assert_eq!(Elections::desired_members(), 2); + assert_eq!(Elections::desired_runners_up(), 0); + assert_eq!(::VotingBondBase::get(), 2); + assert_eq!(::VotingBondFactor::get(), 0); + assert_eq!(::CandidacyBond::get(), 3); assert_eq!(Elections::term_duration(), 5); assert_eq!(Elections::election_rounds(), 0); @@ -2279,6 +2281,7 @@ mod tests { assert_eq!(members_and_stake(), vec![(3, 30), (5, 20)]); assert!(Elections::runners_up().is_empty()); + assert_eq_uvec!(all_voters(), vec![2, 3, 4]); assert!(candidate_ids().is_empty()); assert_eq!(>::decode_len(), None); @@ -2650,7 +2653,7 @@ mod tests { assert_err_with_weight!( Elections::remove_member(Origin::root(), 4, true), Error::::InvalidReplacement, - Some(33489000), // only thing that matters for now is that it is NOT the full block. + Some(34042000), // only thing that matters for now is that it is NOT the full block. ); }); @@ -2672,7 +2675,7 @@ mod tests { assert_err_with_weight!( Elections::remove_member(Origin::root(), 4, false), Error::::InvalidReplacement, - Some(33489000) // only thing that matters for now is that it is NOT the full block. + Some(34042000) // only thing that matters for now is that it is NOT the full block. ); }); } From e810cc72156b462585fc197174d6ca3d279643cd Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 25 Nov 2020 16:11:10 +0000 Subject: [PATCH 46/69] do migration with pallet version??? --- Cargo.lock | 2 +- bin/node/runtime/Cargo.toml | 2 +- frame/elections-phragmen/CHANGELOG.md | 24 ++++ frame/elections-phragmen/Cargo.toml | 2 +- frame/elections-phragmen/src/lib.rs | 151 ++++++++++++-------------- 5 files changed, 99 insertions(+), 82 deletions(-) create mode 100644 frame/elections-phragmen/CHANGELOG.md diff --git a/Cargo.lock b/Cargo.lock index 43af7d1a31674..2bcceabae9dee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4532,7 +4532,7 @@ dependencies = [ [[package]] name = "pallet-elections-phragmen" -version = "2.0.0" +version = "3.0.0" dependencies = [ "frame-benchmarking", "frame-support", diff --git a/bin/node/runtime/Cargo.toml b/bin/node/runtime/Cargo.toml index 2bad2db510be4..852264c1a13ce 100644 --- a/bin/node/runtime/Cargo.toml +++ b/bin/node/runtime/Cargo.toml @@ -52,7 +52,7 @@ pallet-contracts = { version = "2.0.0", default-features = false, path = "../../ pallet-contracts-primitives = { version = "2.0.0", default-features = false, path = "../../../frame/contracts/common/" } pallet-contracts-rpc-runtime-api = { version = "0.8.0", default-features = false, path = "../../../frame/contracts/rpc/runtime-api/" } pallet-democracy = { version = "2.0.0", default-features = false, path = "../../../frame/democracy" } -pallet-elections-phragmen = { version = "2.0.0", default-features = false, path = "../../../frame/elections-phragmen" } +pallet-elections-phragmen = { version = "3.0.0", default-features = false, path = "../../../frame/elections-phragmen" } pallet-grandpa = { version = "2.0.0", default-features = false, path = "../../../frame/grandpa" } pallet-im-online = { version = "2.0.0", default-features = false, path = "../../../frame/im-online" } pallet-indices = { version = "2.0.0", default-features = false, path = "../../../frame/indices" } diff --git a/frame/elections-phragmen/CHANGELOG.md b/frame/elections-phragmen/CHANGELOG.md new file mode 100644 index 0000000000000..5b8e9db639d4c --- /dev/null +++ b/frame/elections-phragmen/CHANGELOG.md @@ -0,0 +1,24 @@ +# Changelog +All notable changes to this crate will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added +[Add slashing events to elections-phragmen](https://github.com/paritytech/substrate/pull/7543) + +### Changed + +### Fixed +[Don't slash all outgoing members](https://github.com/paritytech/substrate/pull/7394) +[Fix wrong outgoing calculation in election](https://github.com/paritytech/substrate/pull/7384) + +### Security +\[**Needs Migration**\] [Fix elections-phragmen and proxy issue + Record deposits on-chain](https://github.com/paritytech/substrate/pull/7040) + +## [2.0.0] - 2020-09-2020 + +Initial version from which version tracking has begun. + diff --git a/frame/elections-phragmen/Cargo.toml b/frame/elections-phragmen/Cargo.toml index 8d59cde19255a..3bfba759279f6 100644 --- a/frame/elections-phragmen/Cargo.toml +++ b/frame/elections-phragmen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-elections-phragmen" -version = "2.0.0" +version = "3.0.0" authors = ["Parity Technologies "] edition = "2018" license = "Apache-2.0" diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index c5ef9ba8c1a36..365da0d516c4f 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -127,95 +127,88 @@ type NegativeImbalanceOf = /// Helper functions for migrations of this module. pub mod migrations { use super::*; - use frame_support::{migration::StorageKeyIterator, Twox64Concat}; - - /// Migrate from the old legacy voting bond (fixed) to the new one (per-vote dynamic). - /// - /// Will only be triggered if storage version is V1. - pub fn migrate_voters_to_recorded_deposit(old_deposit: BalanceOf) -> Weight { - if >::pallet_storage_version() == StorageVersion::V1 { - let mut count = 0; - , Vec), Twox64Concat>>::new( - >::module_prefix(), - b"Voting", - ) - .for_each(|(who, (stake, votes))| { - // Insert a new value into the same location, thus no need to do `.drain()`. - let deposit = old_deposit; - let voter = Voter { votes, stake, deposit }; - >::insert(who, voter); - count += 1; - }); + use frame_support::{ + traits::{PalletVersion, GetPalletVersion}, + Twox64Concat, + migration::StorageKeyIterator, + }; - PalletStorageVersion::put(StorageVersion::V2RecordedDeposit); - frame_support::debug::info!( - "🏛 pallet-elections-phragmen: {} voters migrated to V2PerVoterDeposit.", - count, - ); - Weight::max_value() + pub fn migrate_to_3_0_0(old_voter_bond: BalanceOf, old_candidacy_bond: BalanceOf) -> Weight { + let current_version = as GetPalletVersion>::current_version(); + let maybe_storage_version = as GetPalletVersion>::storage_version(); + + if current_version == PalletVersion::new(3, 0, 0) { + match maybe_storage_version { + Some(storage_version) if storage_version == PalletVersion::new(2, 0, 0) => { + migrate_voters_to_recorded_deposit::(old_voter_bond); + migrate_candidates_to_recorded_deposit::(old_candidacy_bond); + migrate_runners_up_to_recorded_deposit::(old_candidacy_bond); + migrate_members_to_recorded_deposit::(old_candidacy_bond); + Weight::max_value() + }, + _ => { + 0 + } + } } else { - frame_support::debug::warn!( - "🏛 pallet-elections-phragmen: Tried to run migration but PalletStorageVersion is \ - updated to V2. This code probably needs to be removed now.", - ); 0 } } + /// Migrate from the old legacy voting bond (fixed) to the new one (per-vote dynamic). + /// + /// Will only be triggered if storage version is V1. + pub fn migrate_voters_to_recorded_deposit(old_deposit: BalanceOf) { + let mut count = 0; + , Vec), Twox64Concat>>::new( + >::module_prefix(), + b"Voting", + ) + .for_each(|(who, (stake, votes))| { + // Insert a new value into the same location, thus no need to do `.drain()`. + let deposit = old_deposit; + let voter = Voter { votes, stake, deposit }; + >::insert(who, voter); + count += 1; + }); + } + /// Migrate all candidates to recorded deposit. /// /// Will only be triggered if storage version is V1. - pub fn migrate_candidates_to_recorded_deposit(old_deposit: BalanceOf) -> Weight { - if >::pallet_storage_version() == StorageVersion::V1 { - let old_candidates = frame_support::migration::take_storage_value::>( - >::module_prefix(), - b"Candidates", - &[], - ) - .unwrap_or_default(); - let new_candidates = old_candidates - .into_iter() - .map(|c| (c, old_deposit)) - .collect::>(); - >::put(new_candidates); - Weight::max_value() - } else { - frame_support::debug::warn!( - "🏛 pallet-elections-phragmen: Tried to run migration but PalletStorageVersion is \ - updated. This code probably needs to be removed now.", - ); - 0 - } + pub fn migrate_candidates_to_recorded_deposit(old_deposit: BalanceOf) { + let old_candidates = frame_support::migration::take_storage_value::>( + >::module_prefix(), + b"Candidates", + &[], + ) + .unwrap_or_default(); + let new_candidates = old_candidates + .into_iter() + .map(|c| (c, old_deposit)) + .collect::>(); + >::put(new_candidates); } - pub fn migrate_members_to_recorded_deposit(deposit: BalanceOf) -> Weight { - if >::pallet_storage_version() == StorageVersion::V1 { - let _ = >::translate::)>, _>( - |maybe_old_members| { - maybe_old_members.map(|old_members| { - frame_support::debug::info!( - "migrated {} member accounts.", - old_members.len() - ); - old_members - .into_iter() - .map(|(who, stake)| SeatHolder { - who, - stake, - deposit, - }) - .collect::>() - }) - }, - ); - Weight::max_value() - } else { - frame_support::debug::warn!( - "🏛 pallet-elections-phragmen: Tried to run migration but PalletStorageVersion is \ - updated. This code probably needs to be removed now.", - ); - 0 - } + pub fn migrate_members_to_recorded_deposit(deposit: BalanceOf) { + let _ = >::translate::)>, _>( + |maybe_old_members| { + maybe_old_members.map(|old_members| { + frame_support::debug::info!( + "migrated {} member accounts.", + old_members.len() + ); + old_members + .into_iter() + .map(|(who, stake)| SeatHolder { + who, + stake, + deposit, + }) + .collect::>() + }) + }, + ); } pub fn migrate_runners_up_to_recorded_deposit(deposit: BalanceOf) -> Weight { @@ -2705,7 +2698,7 @@ mod tests { assert_err_with_weight!( Elections::remove_member(Origin::root(), 4, false), Error::::InvalidReplacement, - Some(33489000) // only thing that matters for now is that it is NOT the full block. + Some(34042000) // only thing that matters for now is that it is NOT the full block. ); }); } From 87428652d418303a3febc086496c62c4b1706d52 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Thu, 26 Nov 2020 15:15:44 +0000 Subject: [PATCH 47/69] Final touches. --- frame/elections-phragmen/src/lib.rs | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index f3cd43fb2de0e..9e7ac626adc9d 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -171,23 +171,22 @@ pub mod migrations { >::insert(who, voter); count += 1; }); + frame_support::debug::info!("migrated {} voter accounts.", count); } /// Migrate all candidates to recorded deposit. /// /// Will only be triggered if storage version is V1. pub fn migrate_candidates_to_recorded_deposit(old_deposit: BalanceOf) { - let old_candidates = frame_support::migration::take_storage_value::>( - >::module_prefix(), - b"Candidates", - &[], - ) - .unwrap_or_default(); - let new_candidates = old_candidates - .into_iter() - .map(|c| (c, old_deposit)) - .collect::>(); - >::put(new_candidates); + let _ = >::translate::, _>(|maybe_old_candidates| { + maybe_old_candidates.map(|old_candidates| { + frame_support::debug::info!("migrated {} candidate accounts.", old_candidates.len()); + old_candidates + .into_iter() + .map(|c| (c, old_deposit)) + .collect::>() + }) + }); } pub fn migrate_members_to_recorded_deposit(deposit: BalanceOf) { @@ -270,11 +269,11 @@ pub struct Voter { #[derive(Encode, Decode, Clone, Default, RuntimeDebug, PartialEq)] pub struct SeatHolder { /// The holder. - who: AccountId, + pub who: AccountId, /// The total backing stake. - stake: Balance, + pub stake: Balance, /// The amount of deposit held.s - deposit: Balance, + pub deposit: Balance, } /// Storage version. From d6de6155124100dec11d7a1001dea6a4154bc8bd Mon Sep 17 00:00:00 2001 From: kianenigma Date: Fri, 27 Nov 2020 09:36:13 +0000 Subject: [PATCH 48/69] Remove unused storage. --- frame/elections-phragmen/src/lib.rs | 38 ++++++----------------------- 1 file changed, 7 insertions(+), 31 deletions(-) diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index 9e7ac626adc9d..2b9d19fa0252f 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -211,11 +211,10 @@ pub mod migrations { } pub fn migrate_runners_up_to_recorded_deposit(deposit: BalanceOf) -> Weight { - if >::pallet_storage_version() == StorageVersion::V1 { - let _ = >::translate::)>, _>( - |maybe_old_runners_up| { - maybe_old_runners_up.map(|old_runners_up| { - frame_support::debug::info!( + let _ = >::translate::)>, _>( + |maybe_old_runners_up| { + maybe_old_runners_up.map(|old_runners_up| { + frame_support::debug::info!( "migrated {} runner-up accounts.", old_runners_up.len() ); @@ -228,16 +227,9 @@ pub mod migrations { }) .collect::>() }) - }, - ); - Weight::max_value() - } else { - frame_support::debug::warn!( - "🏛 pallet-elections-phragmen: Tried to run migration but PalletStorageVersion is \ - updated. This code probably needs to be removed now.", - ); - 0 - } + }, + ); + Weight::max_value() } } @@ -276,15 +268,6 @@ pub struct SeatHolder { pub deposit: Balance, } -/// Storage version. -#[derive(Encode, Decode, Eq, PartialEq)] -pub enum StorageVersion { - /// Initial version. - V1, - /// After moving to per-vote deposit. - V2RecordedDeposit, -} - pub trait Trait: frame_system::Trait { /// The overarching event type.c type Event: From> + Into<::Event>; @@ -369,13 +352,6 @@ decl_storage! { /// /// Invariant: Always sorted based on account id. pub Candidates get(fn candidates): Vec<(T::AccountId, BalanceOf)>; - - /// Should be used in conjunction with `on_runtime_upgrade` to ensure an upgrade is executed - /// once, even if the code is not removed in time. - /// TODO: use basti's new version stuff? - pub PalletStorageVersion get(fn pallet_storage_version) - build(|_| StorageVersion::V2RecordedDeposit): StorageVersion = StorageVersion::V1; - } add_extra_genesis { config(members): Vec<(T::AccountId, BalanceOf)>; build(|config: &GenesisConfig| { From 6cae3da6cd51ac7608055758cc0019f758f64911 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Mon, 30 Nov 2020 10:02:02 +0000 Subject: [PATCH 49/69] another rounds of changes and fixes. --- bin/node/runtime/src/lib.rs | 6 +- frame/elections-phragmen/CHANGELOG.md | 4 +- frame/elections-phragmen/src/benchmarking.rs | 21 +- frame/elections-phragmen/src/lib.rs | 580 ++++++++++--------- 4 files changed, 344 insertions(+), 267 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index e2b5b3148a2c8..1f66f60951818 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -554,8 +554,10 @@ impl pallet_collective::Trait for Runtime { parameter_types! { pub const CandidacyBond: Balance = 10 * DOLLARS; - pub const VotingBondBase: Balance = 1 * DOLLARS; - pub const VotingBondFactor: Balance = 50 * CENTS; // per 32 bytes on-chain + // 1 storage item created, key size is 32 bytes, value size is 16+16. + pub const VotingBondBase: Balance = deposit(1, 64); + // additional data per vote is 32 bytes (account id). + pub const VotingBondFactor: Balance = deposit(0, 32); pub const TermDuration: BlockNumber = 7 * DAYS; pub const DesiredMembers: u32 = 13; pub const DesiredRunnersUp: u32 = 7; diff --git a/frame/elections-phragmen/CHANGELOG.md b/frame/elections-phragmen/CHANGELOG.md index 5b8e9db639d4c..3d48448fa55ec 100644 --- a/frame/elections-phragmen/CHANGELOG.md +++ b/frame/elections-phragmen/CHANGELOG.md @@ -2,9 +2,9 @@ All notable changes to this crate will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +and this crate adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] +## [3.0.0] - UNRELEASED ### Added [Add slashing events to elections-phragmen](https://github.com/paritytech/substrate/pull/7543) diff --git a/frame/elections-phragmen/src/benchmarking.rs b/frame/elections-phragmen/src/benchmarking.rs index 5acc9c60998a1..10f7a535720c1 100644 --- a/frame/elections-phragmen/src/benchmarking.rs +++ b/frame/elections-phragmen/src/benchmarking.rs @@ -446,11 +446,19 @@ benchmarks! { // that we give all candidates a self vote to make sure they are all considered. let c in 1 .. MAX_CANDIDATES; let v in 1 .. MAX_VOTERS; - let e in MAX_VOTERS .. (MAX_VOTERS * MAXIMUM_VOTE as u32); + let e in MAX_VOTERS .. MAX_VOTERS * MAXIMUM_VOTE as u32; clean::(); + // so we have a situation with v and e. we want e to basically always be in the range of `e + // -> e * MAXIMUM_VOTE`, but we cannot express that now with the benchmarks. So what we do + // is: when c is being iterated, v, and e are max and fine. when v is being iterated, e is + // being set to max and this is a problem. In these cases, we cap e to a lower value, namely + // v * MAXIMUM_VOTE. when e is being iterated, v is at max, and again fine. + // all in all, votes_per_voter can never be more than MAXIMUM_VOTE. + let votes_per_voter = (e / v).min(MAXIMUM_VOTE as u32); + let all_candidates = submit_candidates_with_self_vote::(c, "candidates")?; - let _ = distribute_voters::(all_candidates, v, (e / MAX_VOTERS) as usize)?; + let _ = distribute_voters::(all_candidates, v, votes_per_voter as usize)?; }: { >::on_initialize(T::TermDuration::get()); } @@ -535,7 +543,14 @@ mod tests { }); ExtBuilder::default().desired_members(13).desired_runners_up(7).build_and_execute(|| { - assert_ok!(test_benchmark_election_phragmen::()); + let result = test_benchmark_election_phragmen::(); + let is_ok = result.is_ok(); + let is_expected_error = std::matches!( + result, + Err("in the test setup, we only want to run for component `v` when it is max. This error message is expected.") + ); + assert!(is_ok || is_expected_error); + }); } } diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index 2b9d19fa0252f..7a345989507b4 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -34,28 +34,30 @@ //! ### Bonds and Deposits //! //! Both voting and being a candidate requires deposits to be taken, in exchange for the data that -//! needs to be kept on-chain, and the complexity forced to the election. The terms *bond* and -//! *deposit* can be used interchangeably in this context. +//! needs to be kept on-chain. The terms *bond* and *deposit* can be used interchangeably in this +//! context. //! //! Bonds will be unreserved only upon adhering to the protocol laws. Failing to do so will cause in //! the bond to slashed. //! //! ### Voting //! -//! Voters can vote for any number of the candidates by providing a list of account ids. Invalid -//! votes (voting for non-candidates) and duplicate votes are ignored during election. Yet, a voter -//! _might_ vote for a future candidate. Voters reserve a bond as they vote. Each vote defines a -//! `value`. This amount is locked from the account of the voter and indicates the weight of the -//! vote. Voters can update their votes at any time by calling `vote()` again. This keeps the bond -//! untouched but can optionally change the locked `value`. After a round, votes are kept and might -//! still be valid for further rounds. A voter is responsible for calling `remove_voter` once they -//! are done to have their bond back and remove the lock. +//! Voters can vote for a limited number of the candidates by providing a list of account ids, +//! bounded by [`MAXIMUM_VOTE`]. Invalid votes (voting for non-candidates) and duplicate votes are +//! ignored during election. Yet, a voter _might_ vote for a future candidate. Voters reserve a bond +//! as they vote. Each vote defines a `value`. This amount is locked from the account of the voter +//! and indicates the weight of the vote. Voters can update their votes at any time by calling +//! `vote()` again. This keeps the bond untouched but can optionally change the locked `value`. +//! After a round, votes are kept and might still be valid for further rounds. A voter is +//! responsible for calling `remove_voter` once they are done to have their bond back and remove the +//! lock. //! //! ### Defunct Voter +//! //! A voter is defunct once all of the candidates that they have voted for are not a valid candidate //! (as seen further below, members and runners-up are also always candidates). Defunct voters can //! be removed via a root call. Upon being removed, their bond is returned. This is an -//! administrative operation and can be called only by the root origin in the case of state blaot. +//! administrative operation and can be called only by the root origin in the case of state bloat. //! //! ### Candidacy and Members //! @@ -70,9 +72,9 @@ //! election. //! - **Loser**: Any of the candidate who are not a winner are left as losers. A loser might be an //! _outgoing member or runner_, meaning that they are an active member who failed to keep their -//! spot. **An outgoing will always lose their bond**. +//! spot. **An outgoing candidate/member/runner-up will always lose their bond**. //! -//! ##### Renouncing candidacy. +//! #### Renouncing candidacy. //! //! All candidates, elected or not, can renounce their candidacy. A call to [`renounce_candidacy`] //! will always cause the candidacy bond to be refunded. @@ -124,16 +126,19 @@ type BalanceOf = type NegativeImbalanceOf = <::Currency as Currency<::AccountId>>::NegativeImbalance; -/// Helper functions for migrations of this module. -pub mod migrations { +/// Migrations to version [`3.0.0`], as denoted by the changelog. +pub mod migrations_3_0_0 { use super::*; use frame_support::{ - traits::{PalletVersion, GetPalletVersion}, - Twox64Concat, migration::StorageKeyIterator, + traits::{GetPalletVersion, PalletVersion}, + Twox64Concat, }; - pub fn migrate_to_3_0_0(old_voter_bond: BalanceOf, old_candidacy_bond: BalanceOf) -> Weight { + pub fn apply( + old_voter_bond: BalanceOf, + old_candidacy_bond: BalanceOf, + ) -> Weight { let current_version = as GetPalletVersion>::current_version(); let maybe_storage_version = as GetPalletVersion>::storage_version(); @@ -264,7 +269,7 @@ pub struct SeatHolder { pub who: AccountId, /// The total backing stake. pub stake: Balance, - /// The amount of deposit held.s + /// The amount of deposit held on-chain. pub deposit: Balance, } @@ -325,7 +330,6 @@ pub trait Trait: frame_system::Trait { decl_storage! { trait Store for Module as PhragmenElection { - // ---- State /// The current elected members. /// /// Invariant: Always sorted based on account id. @@ -337,14 +341,6 @@ decl_storage! { /// last (i.e. _best_) runner-up will be replaced. pub RunnersUp get(fn runners_up): Vec>>; - /// The total number of vote rounds that have happened, excluding the upcoming one. - pub ElectionRounds get(fn election_rounds): u32 = Zero::zero(); - - /// Votes and locked stake of a particular voter. - /// - /// TWOX-NOTE: SAFE as `AccountId` is a crypto hash. - pub Voting get(fn voting): map hasher(twox_64_concat) T::AccountId => Voter>; - /// The present candidate list. A current member or runner-up /// can never enter this vector and is always implicitly assumed to be a candidate. /// @@ -352,6 +348,14 @@ decl_storage! { /// /// Invariant: Always sorted based on account id. pub Candidates get(fn candidates): Vec<(T::AccountId, BalanceOf)>; + + /// The total number of vote rounds that have happened, excluding the upcoming one. + pub ElectionRounds get(fn election_rounds): u32 = Zero::zero(); + + /// Votes and locked stake of a particular voter. + /// + /// TWOX-NOTE: SAFE as `AccountId` is a crypto hash. + pub Voting get(fn voting): map hasher(twox_64_concat) T::AccountId => Voter>; } add_extra_genesis { config(members): Vec<(T::AccountId, BalanceOf)>; build(|config: &GenesisConfig| { @@ -452,8 +456,8 @@ decl_event!( /// A \[member\] has been removed. This should always be followed by either `NewTerm` or /// `EmptyTerm`. MemberKicked(AccountId), - /// A \[member\] has renounced their candidacy. - MemberRenounced(AccountId), + /// Someone has renounced their candidacy. + Renounced(AccountId), /// A \[candidate\] was slashed by \[amount\] due to failing to obtain a seat as member or /// runner-up. /// @@ -561,7 +565,9 @@ decl_module! { Ok(maybe_refund.into()) } - /// Remove `origin` as a voter. This removes the lock and returns the deposit. + /// Remove `origin` as a voter. + /// + /// This removes the lock and returns the deposit. /// /// The dispatch origin of this call must be signed and be a voter. #[weight = T::WeightInfo::remove_voter()] @@ -581,7 +587,7 @@ decl_module! { /// /// ### Warning /// - /// Even if a candidate ends up being a member, they must call renounce_candidacy to get + /// Even if a candidate ends up being a member, they must call [`renounce_candidacy`] to get /// their deposit back. Losing the spot in an election will also lead to a slash. /// /// # @@ -599,8 +605,6 @@ decl_module! { let is_candidate = Self::is_candidate(&who); ensure!(is_candidate.is_err(), Error::::DuplicatedCandidate); - - // assured to be an error, error always contains the index. let index = is_candidate.unwrap_err(); ensure!(!Self::is_member(&who), Error::::MemberSubmit); @@ -638,33 +642,29 @@ decl_module! { let who = ensure_signed(origin)?; match renouncing { Renouncing::Member => { - let _ = Self::remove_and_replace_member(&who, false)?; - Self::deposit_event(RawEvent::MemberRenounced(who)); + let _ = Self::remove_and_replace_member(&who, false).map_err(|_| Error::::InvalidRenouncing)?; + Self::deposit_event(RawEvent::Renounced(who)); }, Renouncing::RunnerUp => { - let mut runners_up = Self::runners_up(); - if let Some(index) = runners_up - .iter() - .position(|SeatHolder { who: r, .. }| r == &who) - { + >::try_mutate::<_, Error, _>(|runners_up| { + let index = runners_up.iter().position(|SeatHolder { who: r, .. }| r == &who).ok_or(Error::::InvalidRenouncing)?; let SeatHolder { deposit, .. } = runners_up.remove(index); T::Currency::unreserve(&who, deposit); >::put(runners_up); - } else { - Err(Error::::InvalidRenouncing)?; - } + Self::deposit_event(RawEvent::Renounced(who)); + Ok(()) + })?; } Renouncing::Candidate(count) => { - let mut candidates = Self::candidates(); - ensure!(count >= candidates.len() as u32, Error::::InvalidRenouncing); - // Note: candidates list is always sorted. - if let Ok(index) = candidates.binary_search_by(|(c, _)| c.cmp(&who)) { + >::try_mutate::<_, Error, _>(|candidates| { + ensure!(count >= candidates.len() as u32, Error::::InvalidRenouncing); + let index = candidates.binary_search_by(|(c, _)| c.cmp(&who)).map_err(|_| Error::::InvalidRenouncing)?; let (_removed, deposit) = candidates.remove(index); T::Currency::unreserve(&who, deposit); + Self::deposit_event(RawEvent::Renounced(who)); >::put(candidates); - } else { - Err(Error::::InvalidRenouncing)?; - } + Ok(()) + })?; } }; } @@ -728,8 +728,8 @@ decl_module! { /// # /// The total number of voters and those that are defunct must be provided as witness data. /// # - #[weight = 0] - fn clean_defunct_voters(origin, num_voters: u32, num_defunct: u32) { + #[weight = T::WeightInfo::clean_defunct_voters(*_num_voters, *_num_defunct)] + fn clean_defunct_voters(origin, _num_voters: u32, _num_defunct: u32) { let _ = ensure_root(origin)?; >::iter() .filter(|(_, x)| Self::is_defunct_voter(&x.votes)) @@ -780,14 +780,15 @@ impl Module { // - `Ok(Option(replacement))` if member was removed and replacement was replaced. // - `Ok(None)` if member was removed but no replacement was found // - `Err(_)` if who is not a member. - >::try_mutate(|members| { + let maybe_replacement = >::try_mutate::<_, Error, _>(|members| { // we remove the member anyhow, regardless of having a runner-up or not. - // TODO: a way to make sure all tests operate in a way that accounts are not sorted. let remove_index = members // note: members are always sorted. .binary_search_by(|m| m.who.cmp(who)) .map_err(|_| Error::::NotMember)?; let removed = members.remove(remove_index); + + // slash or unreserve if slash { let (imbalance, _remaining) = T::Currency::slash_reserved(who, removed.deposit); debug_assert_eq!(_remaining, 0u32.into()); @@ -797,7 +798,7 @@ impl Module { T::Currency::unreserve(who, removed.deposit); } - Ok(>::mutate(|r| r.pop()).map(|next_best| { + let maybe_next_best = >::mutate(|r| r.pop()).map(|next_best| { // invariant: Members and runners-up are disjoint. This will always be err and give // us an index to insert. members @@ -807,41 +808,37 @@ impl Module { members.insert(index, next_best.clone()); }); next_best - })) - }) - .map(|maybe_replacement| { - let member_ids = Self::members() - .into_iter() - .map(|x| x.who.clone()) - .collect::>(); - let outgoing = &[who.clone()]; - let maybe_current_prime = T::ChangeMembers::get_prime(); - let return_value = match maybe_replacement { - // member ids are already sorted, other two elements have one item. - Some(incoming) => { - T::ChangeMembers::change_members_sorted( - &[incoming.who], - outgoing, - &member_ids[..], - ); - true - } - None => { - T::ChangeMembers::change_members_sorted(&[], outgoing, &member_ids[..]); - false - } - }; + }); + Ok(maybe_next_best) + })?; - // if there was a prime before and they are not the one being removed, then set them - // again. - if let Some(current_prime) = maybe_current_prime { - if ¤t_prime != who { - T::ChangeMembers::set_prime(Some(current_prime)); - } + let member_ids = Self::members() + .into_iter() + .map(|x| x.who.clone()) + .collect::>(); + let outgoing = &[who.clone()]; + let maybe_current_prime = T::ChangeMembers::get_prime(); + let return_value = match maybe_replacement { + // member ids are already sorted, other two elements have one item. + Some(incoming) => { + T::ChangeMembers::change_members_sorted(&[incoming.who], outgoing, &member_ids[..]); + true + } + None => { + T::ChangeMembers::change_members_sorted(&[], outgoing, &member_ids[..]); + false + } + }; + + // if there was a prime before and they are not the one being removed, then set them + // again. + if let Some(current_prime) = maybe_current_prime { + if ¤t_prime != who { + T::ChangeMembers::set_prime(Some(current_prime)); } + } - return_value - }) + Ok(return_value) } /// Check if `who` is a candidate. It returns the insert index if the element does not exists as @@ -904,7 +901,7 @@ impl Module { /// Get a concatenation of previous members and runners-up and their deposits. /// /// These accounts are essentially treated as candidates. - fn implicit_candidates() -> Vec<(T::AccountId, BalanceOf)> { + fn implicit_candidates_with_deposit() -> Vec<(T::AccountId, BalanceOf)> { // invariant: these two are always without duplicates. Self::members() .into_iter() @@ -941,9 +938,6 @@ impl Module { /// succeeds. Else, it will emit an `ElectionError` event. /// /// Calls the appropriate [`ChangeMembers`] function variant internally. - /// - /// Reads: O(C + V*E) where C = candidates, V voters and E votes per voter exits. - /// Writes: O(M + R) with M desired members and R runners_up. fn do_phragmen() -> Weight { let desired_seats = Self::desired_members() as usize; let desired_runners_up = Self::desired_runners_up() as usize; @@ -951,7 +945,7 @@ impl Module { let mut candidates_and_deposit = Self::candidates(); // add all the previous members and runners-up as candidates as well. - candidates_and_deposit.append(&mut Self::implicit_candidates()); + candidates_and_deposit.append(&mut Self::implicit_candidates_with_deposit()); if candidates_and_deposit.len().is_zero() { Self::deposit_event(RawEvent::EmptyTerm); @@ -994,10 +988,7 @@ impl Module { None, ) .map( - |ElectionResult { - winners, - assignments: _, - }| { + |ElectionResult { winners, assignments: _, }| { // this is already sorted by id. let old_members_ids_sorted = >::take() .into_iter() @@ -1010,8 +1001,8 @@ impl Module { .collect::>(); old_runners_up_ids_sorted.sort(); - // filter out those who end up with no backing stake. This is a logic specific to this - // module. + // filter out those who end up with no backing stake. This is a logic specific to + // this pallet. let new_set_with_stake = winners .into_iter() .filter_map(|(m, b)| { @@ -1023,10 +1014,10 @@ impl Module { }) .collect::)>>(); - // OPTIMISATION NOTE: we could bail out here if `new_set.len() == 0`. There isn't much - // left to do. Yet, re-arranging the code would require duplicating the slashing of - // exposed candidates, cleaning any previous members, and so on. For now, in favour of - // readability and veracity, we keep it simple. + // OPTIMISATION NOTE: we could bail out here if `new_set.len() == 0`. There isn't + // much left to do. Yet, re-arranging the code would require duplicating the + // slashing of exposed candidates, cleaning any previous members, and so on. For + // now, in favour of readability and veracity, we keep it simple. // split new set into winners and runners up. let split_point = desired_seats.min(new_set_with_stake.len()); @@ -1045,10 +1036,11 @@ impl Module { .collect::>(); new_runners_up_ids_sorted.sort(); - // Now we select a prime member using a [Borda count](https://en.wikipedia.org/wiki/Borda_count). We - // weigh everyone's vote for that new member by a multiplier based on the order of the votes. i.e. the - // first person a voter votes for gets a 16x multiplier, the next person gets a 15x multiplier, an so - // on... (assuming `MAXIMUM_VOTE` = 16) + // Now we select a prime member using a [Borda + // count](https://en.wikipedia.org/wiki/Borda_count). We weigh everyone's vote for + // that new member by a multiplier based on the order of the votes. i.e. the first + // person a voter votes for gets a 16x multiplier, the next person gets a 15x + // multiplier, an so on... (assuming `MAXIMUM_VOTE` = 16) let mut prime_votes = new_members_sorted_by_id .iter() .map(|c| (&c.0, BalanceOf::::zero())) @@ -1093,10 +1085,9 @@ impl Module { ); T::ChangeMembers::set_prime(prime); - // All candidates/members/runners-up who are no longer retaining a position as a seat - // holder will lose their bond. + // All candidates/members/runners-up who are no longer retaining a position as a + // seat holder will lose their bond. candidates_and_deposit.iter().for_each(|(c, d)| { - // any candidate who is not a member and not a runner up. if new_members_ids_sorted.binary_search(c).is_err() && new_runners_up_ids_sorted.binary_search(c).is_err() @@ -1109,16 +1100,15 @@ impl Module { // write final values to storage. let deposit_of_candidate = |x: &T::AccountId| -> BalanceOf { - // defensive-only. This closure is used against the new members and new runners-up, - // both of which are phragmen winners and thus must have deposit. + // defensive-only. This closure is used against the new members and new runners-up, both of which + // are phragmen winners and thus must have deposit. candidates_and_deposit .iter() .find_map(|(c, d)| if c == x { Some(*d) } else { None }) .unwrap_or_default() }; - // fetch deposits from the one recorded one. This will make sure that a candidate who - // submitted candidacy before a change to candidacy deposit will have the correct amount - // recorded. + // fetch deposits from the one recorded one. This will make sure that a candidate who submitted + // candidacy before a change to candidacy deposit will have the correct amount recorded. >::put( new_members_sorted_by_id .iter() @@ -1434,6 +1424,10 @@ mod tests { .collect::>() } + fn members_ids() -> Vec { + Elections::members_ids() + } + fn members_and_stake() -> Vec<(u64, u64)> { Elections::members() .into_iter() @@ -1493,8 +1487,8 @@ mod tests { fn ensure_member_candidates_runners_up_disjoint() { // members, candidates and runners-up must always be disjoint sets. - assert!(!intersects(&Elections::members_ids(), &candidate_ids())); - assert!(!intersects(&Elections::members_ids(), &runners_up_ids())); + assert!(!intersects(&members_ids(), &candidate_ids())); + assert!(!intersects(&members_ids(), &runners_up_ids())); assert!(!intersects(&candidate_ids(), &runners_up_ids())); } @@ -1502,6 +1496,7 @@ mod tests { System::set_block_number(1); ensure_members_sorted(); ensure_candidates_sorted(); + ensure_member_candidates_runners_up_disjoint(); } fn post_conditions() { @@ -1593,7 +1588,7 @@ mod tests { System::set_block_number(5); Elections::on_initialize(System::block_number()); - assert_eq!(Elections::members_ids(), vec![1, 2]); + assert_eq!(members_ids(), vec![1, 2]); }) } @@ -1640,7 +1635,7 @@ mod tests { System::set_block_number(5); Elections::on_initialize(System::block_number()); - assert_eq!(Elections::members_ids(), vec![1, 2]); + assert_eq!(members_ids(), vec![1, 2]); }) } @@ -1681,14 +1676,14 @@ mod tests { assert_eq!(Elections::desired_members(), 2); assert_eq!(Elections::election_rounds(), 0); - assert!(Elections::members_ids().is_empty()); + assert!(members_ids().is_empty()); assert!(Elections::runners_up().is_empty()); assert!(candidate_ids().is_empty()); System::set_block_number(5); Elections::on_initialize(System::block_number()); - assert!(Elections::members_ids().is_empty()); + assert!(members_ids().is_empty()); assert!(Elections::runners_up().is_empty()); assert!(candidate_ids().is_empty()); }); @@ -1762,29 +1757,18 @@ mod tests { } #[test] - fn simple_candidate_submission_with_no_votes_should_work() { + fn candidates_are_always_sorted() { ExtBuilder::default().build_and_execute(|| { assert_eq!(candidate_ids(), Vec::::new()); + assert_ok!(submit_candidacy(Origin::signed(3))); + assert_eq!(candidate_ids(), vec![3]); assert_ok!(submit_candidacy(Origin::signed(1))); + assert_eq!(candidate_ids(), vec![1, 3]); assert_ok!(submit_candidacy(Origin::signed(2))); - - assert!(Elections::is_candidate(&1).is_ok()); - assert!(Elections::is_candidate(&2).is_ok()); - assert_eq!(candidate_ids(), vec![1, 2]); - - assert!(Elections::members_ids().is_empty()); - assert!(Elections::runners_up().is_empty()); - - System::set_block_number(5); - Elections::on_initialize(System::block_number()); - - assert!(Elections::is_candidate(&1).is_err()); - assert!(Elections::is_candidate(&2).is_err()); - assert!(candidate_ids().is_empty()); - - assert!(Elections::members_ids().is_empty()); - assert!(Elections::runners_up().is_empty()); + assert_eq!(candidate_ids(), vec![1, 2, 3]); + assert_ok!(submit_candidacy(Origin::signed(4))); + assert_eq!(candidate_ids(), vec![1, 2, 3, 4]); }); } @@ -1811,7 +1795,7 @@ mod tests { System::set_block_number(5); Elections::on_initialize(System::block_number()); - assert_eq!(Elections::members_ids(), vec![5]); + assert_eq!(members_ids(), vec![5]); assert!(Elections::runners_up().is_empty()); assert!(candidate_ids().is_empty()); @@ -1835,7 +1819,7 @@ mod tests { System::set_block_number(5); Elections::on_initialize(System::block_number()); - assert_eq!(Elections::members_ids(), vec![4, 5]); + assert_eq!(members_ids(), vec![4, 5]); assert_eq!(runners_up_ids(), vec![3]); assert_noop!( @@ -1994,7 +1978,7 @@ mod tests { System::set_block_number(5); Elections::on_initialize(System::block_number()); - assert_eq!(Elections::members_ids(), vec![4, 5]); + assert_eq!(members_ids(), vec![4, 5]); assert!(candidate_ids().is_empty()); assert_ok!(vote(Origin::signed(3), vec![4, 5], 10)); @@ -2017,7 +2001,7 @@ mod tests { System::set_block_number(5); Elections::on_initialize(System::block_number()); - assert_eq!(Elections::members_ids(), vec![4, 5]); + assert_eq!(members_ids(), vec![4, 5]); assert!(candidate_ids().is_empty()); assert_ok!(vote(Origin::signed(3), vec![4, 5], 10)); @@ -2043,7 +2027,7 @@ mod tests { System::set_block_number(5); Elections::on_initialize(System::block_number()); - assert_eq!(Elections::members_ids(), vec![3, 5]); + assert_eq!(members_ids(), vec![3, 5]); assert!(candidate_ids().is_empty()); assert_eq!(PRIME.with(|p| *p.borrow()), Some(5)); @@ -2062,14 +2046,14 @@ mod tests { System::set_block_number(5); Elections::on_initialize(System::block_number()); - assert_eq!(Elections::members_ids(), vec![4, 5]); + assert_eq!(members_ids(), vec![4, 5]); assert_eq!(PRIME.with(|p| *p.borrow()), Some(5)); assert_ok!(Elections::renounce_candidacy( Origin::signed(4), Renouncing::Member )); - assert_eq!(Elections::members_ids(), vec![5]); + assert_eq!(members_ids(), vec![5]); assert_eq!(PRIME.with(|p| *p.borrow()), Some(5)); }) } @@ -2086,11 +2070,11 @@ mod tests { System::set_block_number(5); Elections::on_initialize(System::block_number()); - assert_eq!(Elections::members_ids(), vec![4, 5]); + assert_eq!(members_ids(), vec![4, 5]); assert_eq!(PRIME.with(|p| *p.borrow()), Some(5)); assert_ok!(Elections::renounce_candidacy(Origin::signed(5), Renouncing::Member)); - assert_eq!(Elections::members_ids(), vec![4]); + assert_eq!(members_ids(), vec![4]); assert_eq!(PRIME.with(|p| *p.borrow()), None); }) } @@ -2218,7 +2202,7 @@ mod tests { System::set_block_number(5); Elections::on_initialize(System::block_number()); - assert_eq!(Elections::members_ids(), vec![3, 5]); + assert_eq!(members_ids(), vec![3, 5]); }); } @@ -2354,7 +2338,7 @@ mod tests { Elections::on_initialize(System::block_number()); assert_eq!(Elections::election_rounds(), 1); - assert_eq!(Elections::members_ids(), vec![4, 5]); + assert_eq!(members_ids(), vec![4, 5]); }); } @@ -2369,7 +2353,7 @@ mod tests { assert!(candidate_ids().is_empty()); assert_eq!(Elections::election_rounds(), 1); - assert!(Elections::members_ids().is_empty()); + assert!(members_ids().is_empty()); assert_eq!( System::events().iter().last().unwrap().event, @@ -2394,7 +2378,7 @@ mod tests { System::set_block_number(5); Elections::on_initialize(System::block_number()); // sorted based on account id. - assert_eq!(Elections::members_ids(), vec![4, 5]); + assert_eq!(members_ids(), vec![4, 5]); // sorted based on merit (least -> most) assert_eq!(runners_up_ids(), vec![3, 2]); @@ -2427,6 +2411,7 @@ mod tests { System::set_block_number(10); Elections::on_initialize(System::block_number()); + assert_eq!(members_and_stake(), vec![(3, 30), (4, 40)]); assert_eq!(runners_up_and_stake(), vec![(5, 15), (2, 20)]); }); @@ -2445,7 +2430,7 @@ mod tests { System::set_block_number(5); Elections::on_initialize(System::block_number()); - assert_eq!(Elections::members_ids(), vec![4, 5]); + assert_eq!(members_ids(), vec![4, 5]); assert_eq!(runners_up_ids(), vec![2]); assert_eq!(balances(&2), (15, 5)); @@ -2473,21 +2458,21 @@ mod tests { System::set_block_number(5); Elections::on_initialize(System::block_number()); - assert_eq!(Elections::members_ids(), vec![5]); + assert_eq!(members_ids(), vec![5]); assert_ok!(Elections::remove_voter(Origin::signed(5))); assert_eq!(balances(&5), (47, 3)); System::set_block_number(10); Elections::on_initialize(System::block_number()); - assert!(Elections::members_ids().is_empty()); + assert!(members_ids().is_empty()); assert_eq!(balances(&5), (47, 0)); }); } #[test] - fn losers_will_lose_the_bond() { + fn candidates_lose_the_bond_when_outgoing() { ExtBuilder::default().build_and_execute(|| { assert_ok!(submit_candidacy(Origin::signed(5))); assert_ok!(submit_candidacy(Origin::signed(3))); @@ -2500,7 +2485,7 @@ mod tests { System::set_block_number(5); Elections::on_initialize(System::block_number()); - assert_eq!(Elections::members_ids(), vec![5]); + assert_eq!(members_ids(), vec![5]); // winner assert_eq!(balances(&5), (47, 3)); @@ -2521,7 +2506,7 @@ mod tests { System::set_block_number(5); Elections::on_initialize(System::block_number()); - assert_eq!(Elections::members_ids(), vec![4, 5]); + assert_eq!(members_ids(), vec![4, 5]); assert_eq!(Elections::election_rounds(), 1); assert_ok!(submit_candidacy(Origin::signed(2))); @@ -2539,7 +2524,7 @@ mod tests { Elections::on_initialize(System::block_number()); // 4 removed; 5 and 3 are the new best. - assert_eq!(Elections::members_ids(), vec![3, 5]); + assert_eq!(members_ids(), vec![3, 5]); }); } @@ -2589,7 +2574,7 @@ mod tests { System::set_block_number(5); Elections::on_initialize(System::block_number()); - assert_eq!(Elections::members_ids(), vec![4, 5]); + assert_eq!(members_ids(), vec![4, 5]); assert_eq!(Elections::election_rounds(), 1); // a new candidate @@ -2600,7 +2585,7 @@ mod tests { assert_eq!(balances(&4), (35, 2)); // slashed assert_eq!(Elections::election_rounds(), 2); // new election round - assert_eq!(Elections::members_ids(), vec![3, 5]); // new members + assert_eq!(members_ids(), vec![3, 5]); // new members }); } @@ -2615,7 +2600,7 @@ mod tests { System::set_block_number(5); Elections::on_initialize(System::block_number()); - assert_eq!(Elections::members_ids(), vec![4, 5]); + assert_eq!(members_ids(), vec![4, 5]); // no replacement yet. assert_err_with_weight!( @@ -2636,7 +2621,7 @@ mod tests { System::set_block_number(5); Elections::on_initialize(System::block_number()); - assert_eq!(Elections::members_ids(), vec![4, 5]); + assert_eq!(members_ids(), vec![4, 5]); assert_eq!(runners_up_ids(), vec![3]); // there is a replacement! and this one needs a weight refund. @@ -2666,7 +2651,7 @@ mod tests { System::set_block_number(5); Elections::on_initialize(System::block_number()); - assert_eq!(Elections::members_ids(), vec![3, 5]); + assert_eq!(members_ids(), vec![3, 5]); assert_eq!(Elections::election_rounds(), 1); assert_ok!(Elections::remove_voter(Origin::signed(2))); @@ -2677,7 +2662,7 @@ mod tests { // meanwhile, no one cares to become a candidate again. System::set_block_number(10); Elections::on_initialize(System::block_number()); - assert!(Elections::members_ids().is_empty()); + assert!(members_ids().is_empty()); assert_eq!(Elections::election_rounds(), 2); }); } @@ -2693,7 +2678,7 @@ mod tests { System::set_block_number(5); Elections::on_initialize(System::block_number()); - assert_eq!(Elections::members_ids(), vec![4, 5]); + assert_eq!(members_ids(), vec![4, 5]); assert_ok!(submit_candidacy(Origin::signed(1))); assert_ok!(submit_candidacy(Origin::signed(2))); @@ -2742,7 +2727,7 @@ mod tests { System::set_block_number(5); Elections::on_initialize(System::block_number()); - assert_eq_uvec!(Elections::members_ids(), vec![3, 4]); + assert_eq_uvec!(members_ids(), vec![3, 4]); assert_eq!(Elections::election_rounds(), 1); }); } @@ -2769,40 +2754,26 @@ mod tests { }); } - #[test] - fn candidates_are_sorted() { - ExtBuilder::default().build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(submit_candidacy(Origin::signed(3))); - - assert_eq!(candidate_ids(), vec![3, 5]); - - assert_ok!(submit_candidacy(Origin::signed(2))); - assert_ok!(submit_candidacy(Origin::signed(4))); - assert_ok!(Elections::renounce_candidacy(Origin::signed(3), Renouncing::Candidate(4))); - - assert_eq!(candidate_ids(), vec![2, 4, 5]); - }) - } - #[test] fn runner_up_replacement_maintains_members_order() { - ExtBuilder::default().desired_runners_up(2).build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(submit_candidacy(Origin::signed(4))); + ExtBuilder::default() + .desired_runners_up(2) + .build_and_execute(|| { + assert_ok!(submit_candidacy(Origin::signed(5))); + assert_ok!(submit_candidacy(Origin::signed(4))); assert_ok!(submit_candidacy(Origin::signed(2))); assert_ok!(vote(Origin::signed(2), vec![5], 20)); assert_ok!(vote(Origin::signed(4), vec![4], 40)); assert_ok!(vote(Origin::signed(5), vec![2], 50)); - System::set_block_number(5); - Elections::on_initialize(System::block_number()); + System::set_block_number(5); + Elections::on_initialize(System::block_number()); - assert_eq!(Elections::members_ids(), vec![2, 4]); - assert_ok!(Elections::remove_member(Origin::root(), 2, true)); - assert_eq!(Elections::members_ids(), vec![4, 5]); - }); + assert_eq!(members_ids(), vec![2, 4]); + assert_ok!(Elections::remove_member(Origin::root(), 2, true)); + assert_eq!(members_ids(), vec![4, 5]); + }); } #[test] @@ -2821,13 +2792,13 @@ mod tests { System::set_block_number(5); Elections::on_initialize(System::block_number()); - assert_eq!(Elections::members_ids(), vec![4, 5]); + assert_eq!(members_ids(), vec![4, 5]); assert_eq!(runners_up_ids(), vec![2, 3]); assert_ok!(Elections::renounce_candidacy(Origin::signed(4), Renouncing::Member)); assert_eq!(balances(&4), (38, 2)); // 2 is voting bond. - assert_eq!(Elections::members_ids(), vec![3, 5]); + assert_eq!(members_ids(), vec![3, 5]); assert_eq!(runners_up_ids(), vec![2]); }) } @@ -2844,24 +2815,26 @@ mod tests { System::set_block_number(5); Elections::on_initialize(System::block_number()); - assert_eq!(Elections::members_ids(), vec![4, 5]); + assert_eq!(members_ids(), vec![4, 5]); assert!(runners_up_ids().is_empty()); assert_ok!(Elections::renounce_candidacy(Origin::signed(4), Renouncing::Member)); assert_eq!(balances(&4), (38, 2)); // 2 is voting bond. // no replacement - assert_eq!(Elections::members_ids(), vec![5]); + assert_eq!(members_ids(), vec![5]); assert!(runners_up_ids().is_empty()); }) } #[test] - fn can_renounce_candidacy_runner() { - ExtBuilder::default().desired_runners_up(2).build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(submit_candidacy(Origin::signed(4))); - assert_ok!(submit_candidacy(Origin::signed(3))); + fn can_renounce_candidacy_runner_up() { + ExtBuilder::default() + .desired_runners_up(2) + .build_and_execute(|| { + assert_ok!(submit_candidacy(Origin::signed(5))); + assert_ok!(submit_candidacy(Origin::signed(4))); + assert_ok!(submit_candidacy(Origin::signed(3))); assert_ok!(submit_candidacy(Origin::signed(2))); assert_ok!(vote(Origin::signed(5), vec![4], 50)); @@ -2869,18 +2842,21 @@ mod tests { assert_ok!(vote(Origin::signed(3), vec![3], 30)); assert_ok!(vote(Origin::signed(2), vec![2], 20)); - System::set_block_number(5); - Elections::on_initialize(System::block_number()); + System::set_block_number(5); + Elections::on_initialize(System::block_number()); - assert_eq!(Elections::members_ids(), vec![4, 5]); - assert_eq!(runners_up_ids(), vec![2, 3]); + assert_eq!(members_ids(), vec![4, 5]); + assert_eq!(runners_up_ids(), vec![2, 3]); - assert_ok!(Elections::renounce_candidacy(Origin::signed(3), Renouncing::RunnerUp)); - assert_eq!(balances(&3), (28, 2)); // 2 is voting bond. + assert_ok!(Elections::renounce_candidacy( + Origin::signed(3), + Renouncing::RunnerUp + )); + assert_eq!(balances(&3), (28, 2)); // 2 is voting bond. - assert_eq!(Elections::members_ids(), vec![4, 5]); - assert_eq!(runners_up_ids(), vec![2]); - }) + assert_eq!(members_ids(), vec![4, 5]); + assert_eq!(runners_up_ids(), vec![2]); + }) } #[test] @@ -2899,10 +2875,10 @@ mod tests { System::set_block_number(5); Elections::on_initialize(System::block_number()); - assert_eq!(Elections::members_ids(), vec![2, 4]); + assert_eq!(members_ids(), vec![2, 4]); assert_eq!(runners_up_ids(), vec![5, 3]); assert_ok!(Elections::renounce_candidacy(Origin::signed(3), Renouncing::RunnerUp)); - assert_eq!(Elections::members_ids(), vec![2, 4]); + assert_eq!(members_ids(), vec![2, 4]); assert_eq!(runners_up_ids(), vec![5]); }); } @@ -2929,7 +2905,7 @@ mod tests { ); assert_noop!( Elections::renounce_candidacy(Origin::signed(5), Renouncing::Member), - Error::::NotMember, + Error::::InvalidRenouncing, ); assert_noop!( Elections::renounce_candidacy(Origin::signed(5), Renouncing::RunnerUp), @@ -2952,12 +2928,12 @@ mod tests { System::set_block_number(5); Elections::on_initialize(System::block_number()); - assert_eq!(Elections::members_ids(), vec![4, 5]); + assert_eq!(members_ids(), vec![4, 5]); assert_eq!(runners_up_ids(), vec![3]); assert_noop!( Elections::renounce_candidacy(Origin::signed(3), Renouncing::Member), - Error::::NotMember, + Error::::InvalidRenouncing, ); }) } @@ -2976,7 +2952,7 @@ mod tests { System::set_block_number(5); Elections::on_initialize(System::block_number()); - assert_eq!(Elections::members_ids(), vec![4, 5]); + assert_eq!(members_ids(), vec![4, 5]); assert_eq!(runners_up_ids(), vec![3]); assert_noop!( @@ -3014,54 +2990,36 @@ mod tests { } #[test] - fn behavior_with_dupe_candidate() { + fn unsorted_runners_up_are_detected() { ExtBuilder::default() .desired_runners_up(2) + .desired_members(1) .build_and_execute(|| { - >::put(vec![(1, 0), (1, 0), (2, 0), (3, 0), (4, 0)]); - - assert_ok!(vote(Origin::signed(5), vec![1], 50)); - assert_ok!(vote(Origin::signed(4), vec![4], 40)); - assert_ok!(vote(Origin::signed(3), vec![3], 30)); - assert_ok!(vote(Origin::signed(2), vec![2], 20)); - - System::set_block_number(5); - Elections::on_initialize(System::block_number()); - - assert_eq!(Elections::members_ids(), vec![1, 4]); - assert_eq!(runners_up_ids(), vec![2, 3]); - assert!(candidate_ids().is_empty()); - }) - } - - #[test] - fn unsorted_runners_up_are_detected() { - ExtBuilder::default().desired_runners_up(2).desired_members(1).build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(submit_candidacy(Origin::signed(4))); + assert_ok!(submit_candidacy(Origin::signed(5))); + assert_ok!(submit_candidacy(Origin::signed(4))); assert_ok!(submit_candidacy(Origin::signed(3))); assert_ok!(vote(Origin::signed(5), vec![5], 50)); assert_ok!(vote(Origin::signed(4), vec![4], 5)); assert_ok!(vote(Origin::signed(3), vec![3], 15)); - System::set_block_number(5); - Elections::on_initialize(System::block_number()); + System::set_block_number(5); + Elections::on_initialize(System::block_number()); - assert_eq!(Elections::members_ids(), vec![5]); - assert_eq!(runners_up_ids(), vec![4, 3]); + assert_eq!(members_ids(), vec![5]); + assert_eq!(runners_up_ids(), vec![4, 3]); - assert_ok!(submit_candidacy(Origin::signed(2))); - assert_ok!(vote(Origin::signed(2), vec![2], 10)); + assert_ok!(submit_candidacy(Origin::signed(2))); + assert_ok!(vote(Origin::signed(2), vec![2], 10)); - System::set_block_number(10); - Elections::on_initialize(System::block_number()); + System::set_block_number(10); + Elections::on_initialize(System::block_number()); - assert_eq!(Elections::members_ids(), vec![5]); - assert_eq!(runners_up_ids(), vec![2, 3]); + assert_eq!(members_ids(), vec![5]); + assert_eq!(runners_up_ids(), vec![2, 3]); - // 4 is outgoing runner-up. Slash candidacy bond. - assert_eq!(balances(&4), (35, 2)); + // 4 is outgoing runner-up. Slash candidacy bond. + assert_eq!(balances(&4), (35, 2)); // 3 stays. assert_eq!(balances(&3), (25, 5)); }) @@ -3082,7 +3040,7 @@ mod tests { System::set_block_number(5); Elections::on_initialize(System::block_number()); - assert_eq!(Elections::members_ids(), vec![4]); + assert_eq!(members_ids(), vec![4]); assert_eq!(runners_up_ids(), vec![2, 3]); assert_eq!(balances(&4), (35, 5)); @@ -3096,7 +3054,7 @@ mod tests { System::set_block_number(10); Elections::on_initialize(System::block_number()); - assert_eq!(Elections::members_ids(), vec![5]); + assert_eq!(members_ids(), vec![5]); assert_eq!(runners_up_ids(), vec![3, 4]); // 4 went from member to runner-up -- don't slash. @@ -3123,7 +3081,7 @@ mod tests { System::set_block_number(5); Elections::on_initialize(System::block_number()); - assert_eq!(Elections::members_ids(), vec![4]); + assert_eq!(members_ids(), vec![4]); assert_eq!(runners_up_ids(), vec![2, 3]); assert_eq!(balances(&4), (35, 5)); @@ -3137,7 +3095,7 @@ mod tests { System::set_block_number(10); Elections::on_initialize(System::block_number()); - assert_eq!(Elections::members_ids(), vec![2]); + assert_eq!(members_ids(), vec![2]); assert_eq!(runners_up_ids(), vec![4, 3]); // 2 went from runner to member, don't slash @@ -3149,9 +3107,6 @@ mod tests { }); } - #[test] - fn dupe_voter_is_moot() {} - #[test] fn remove_and_replace_member_works() { let setup = || { @@ -3166,7 +3121,7 @@ mod tests { System::set_block_number(5); Elections::on_initialize(System::block_number()); - assert_eq!(Elections::members_ids(), vec![4, 5]); + assert_eq!(members_ids(), vec![4, 5]); assert_eq!(runners_up_ids(), vec![3]); }; @@ -3175,7 +3130,7 @@ mod tests { setup(); assert_eq!(Elections::remove_and_replace_member(&4, false), Ok(true)); - assert_eq!(Elections::members_ids(), vec![3, 5]); + assert_eq!(members_ids(), vec![3, 5]); assert_eq!(runners_up_ids().len(), 0); }); @@ -3185,7 +3140,7 @@ mod tests { assert_ok!(Elections::renounce_candidacy(Origin::signed(3), Renouncing::RunnerUp)); assert_eq!(Elections::remove_and_replace_member(&4, false), Ok(false)); - assert_eq!(Elections::members_ids(), vec![5]); + assert_eq!(members_ids(), vec![5]); assert_eq!(runners_up_ids().len(), 0); }); @@ -3195,4 +3150,109 @@ mod tests { assert!(matches!(Elections::remove_and_replace_member(&2, false), Err(_))); }); } + + #[test] + fn no_desired_members() { + // not interested in anything + ExtBuilder::default() + .desired_members(0) + .desired_runners_up(0) + .build_and_execute(|| { + assert_eq!(Elections::candidates().len(), 0); + + assert_ok!(submit_candidacy(Origin::signed(4))); + assert_ok!(submit_candidacy(Origin::signed(3))); + assert_ok!(submit_candidacy(Origin::signed(2))); + + assert_eq!(Elections::candidates().len(), 3); + + assert_ok!(vote(Origin::signed(4), vec![4], 40)); + assert_ok!(vote(Origin::signed(3), vec![3], 30)); + assert_ok!(vote(Origin::signed(2), vec![2], 20)); + + System::set_block_number(5); + Elections::on_initialize(System::block_number()); + + assert_eq!(members_ids().len(), 0); + assert_eq!(runners_up_ids().len(), 0); + assert_eq!(all_voters().len(), 3); + assert_eq!(Elections::candidates().len(), 0); + }); + + // not interested in members + ExtBuilder::default() + .desired_members(0) + .desired_runners_up(2) + .build_and_execute(|| { + assert_eq!(Elections::candidates().len(), 0); + + assert_ok!(submit_candidacy(Origin::signed(4))); + assert_ok!(submit_candidacy(Origin::signed(3))); + assert_ok!(submit_candidacy(Origin::signed(2))); + + assert_eq!(Elections::candidates().len(), 3); + + assert_ok!(vote(Origin::signed(4), vec![4], 40)); + assert_ok!(vote(Origin::signed(3), vec![3], 30)); + assert_ok!(vote(Origin::signed(2), vec![2], 20)); + + System::set_block_number(5); + Elections::on_initialize(System::block_number()); + + assert_eq!(members_ids().len(), 0); + assert_eq!(runners_up_ids(), vec![3, 4]); + assert_eq!(all_voters().len(), 3); + assert_eq!(Elections::candidates().len(), 0); + }); + + // not interested in runners-up + ExtBuilder::default() + .desired_members(2) + .desired_runners_up(0) + .build_and_execute(|| { + assert_eq!(Elections::candidates().len(), 0); + + assert_ok!(submit_candidacy(Origin::signed(4))); + assert_ok!(submit_candidacy(Origin::signed(3))); + assert_ok!(submit_candidacy(Origin::signed(2))); + + assert_eq!(Elections::candidates().len(), 3); + + assert_ok!(vote(Origin::signed(4), vec![4], 40)); + assert_ok!(vote(Origin::signed(3), vec![3], 30)); + assert_ok!(vote(Origin::signed(2), vec![2], 20)); + + System::set_block_number(5); + Elections::on_initialize(System::block_number()); + + assert_eq!(members_ids(), vec![3, 4]); + assert_eq!(runners_up_ids().len(), 0); + assert_eq!(all_voters().len(), 3); + assert_eq!(Elections::candidates().len(), 0); + }); + } + + #[test] + fn dupe_vote_is_moot() { + ExtBuilder::default() + .desired_members(1) + .build_and_execute(|| { + assert_ok!(submit_candidacy(Origin::signed(5))); + assert_ok!(submit_candidacy(Origin::signed(4))); + assert_ok!(submit_candidacy(Origin::signed(3))); + assert_ok!(submit_candidacy(Origin::signed(2))); + assert_ok!(submit_candidacy(Origin::signed(1))); + + // all these duplicate votes will not cause 2 to win. + assert_ok!(vote(Origin::signed(1), vec![2, 2, 2, 2], 5)); + assert_ok!(vote(Origin::signed(2), vec![2, 2, 2, 2], 20)); + + assert_ok!(vote(Origin::signed(3), vec![3], 30)); + + System::set_block_number(5); + Elections::on_initialize(System::block_number()); + + assert_eq!(members_ids(), vec![3]); + }) + } } From 3c0d34131ec9e489a6ba42b3b4fb861da1cd84ad Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Mon, 30 Nov 2020 11:03:40 +0100 Subject: [PATCH 50/69] Update frame/elections-phragmen/src/lib.rs Co-authored-by: Shawn Tabrizi --- frame/elections-phragmen/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index 7a345989507b4..f55246f5e83a0 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -718,7 +718,7 @@ decl_module! { Ok(None.into()) } - /// Clean all voters who are defunct (i.e. the do not serve any purpose at all). The deposit + /// Clean all voters who are defunct (i.e. they do not serve any purpose at all). The deposit /// of the removed voters are returned. /// /// This is an root function to be used only for cleaning the state. From 3ca15904eb91b2957851df8e2feee15394e656a1 Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Mon, 30 Nov 2020 11:03:50 +0100 Subject: [PATCH 51/69] Update frame/elections-phragmen/src/lib.rs Co-authored-by: Shawn Tabrizi --- frame/elections-phragmen/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index f55246f5e83a0..0154c1da627e9 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -701,7 +701,6 @@ decl_module! { // In both cases, we will change more weight than need. Refund and abort. return Err(Error::::InvalidReplacement.with_weight( // refund. The weight value comes from a benchmark which is special to this. - // 5.751 µs T::WeightInfo::remove_member_wrong_refund() )); } // else, prediction was correct. From 9c9819b9e12b6ca70def71c6d8f9a5b0c2b9cfc7 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Mon, 30 Nov 2020 10:23:29 +0000 Subject: [PATCH 52/69] Review grumbles. --- frame/elections-phragmen/src/lib.rs | 48 ++++++++++------------------- 1 file changed, 17 insertions(+), 31 deletions(-) diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index 7a345989507b4..ba53b49cb54c7 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -366,10 +366,6 @@ decl_storage! { "Genesis member does not have enough stake.", ); - // reserve candidacy bond and set as members. - T::Currency::reserve(&member, T::CandidacyBond::get()) - .expect("Genesis member does not have enough balance to be a candidate"); - // Note: all members will only vote for themselves, hence they must be given exactly // their own stake as total backing. Any sane election should behave as such. // Nonetheless, stakes will be updated for term 1 onwards according to the election. @@ -378,17 +374,17 @@ decl_storage! { Ok(_) => panic!("Duplicate member in elections-phragmen genesis: {}", member), Err(pos) => members.insert( pos, - SeatHolder { who: member.clone(), stake: *stake, deposit: T::CandidacyBond::get() }, + SeatHolder { who: member.clone(), stake: *stake, deposit: Zero::zero() }, ), } }); - // set self-votes to make persistent. - >::vote( - T::Origin::from(Some(member.clone()).into()), - vec![member.clone()], - *stake, - ).expect("Genesis member could not vote."); + // set self-votes to make persistent. Genesis voters don't have any bond, nor do + // they have any lock. + >::insert( + &member, + Voter { votes: vec![member.clone()], stake: *stake, deposit: Zero::zero() }, + ); member.clone() }).collect::>(); @@ -790,8 +786,8 @@ impl Module { // slash or unreserve if slash { - let (imbalance, _remaining) = T::Currency::slash_reserved(who, removed.deposit); - debug_assert_eq!(_remaining, 0u32.into()); + let (imbalance, _remainder) = T::Currency::slash_reserved(who, removed.deposit); + debug_assert!(_remainder.is_zero()); T::LoserCandidate::on_unbalanced(imbalance); Self::deposit_event(RawEvent::SeatHolderSlashed(who.clone(), removed.deposit)); } else { @@ -1557,12 +1553,12 @@ mod tests { SeatHolder { who: 1, stake: 10, - deposit: 3, + deposit: 0, }, SeatHolder { who: 2, stake: 20, - deposit: 3, + deposit: 0, } ] ); @@ -1572,7 +1568,7 @@ mod tests { Voter { stake: 10u64, votes: vec![1], - deposit: 2, + deposit: 0, } ); assert_eq!( @@ -1580,7 +1576,7 @@ mod tests { Voter { stake: 20u64, votes: vec![2], - deposit: 2, + deposit: 0, } ); @@ -1604,12 +1600,12 @@ mod tests { SeatHolder { who: 1, stake: 10, - deposit: 3, + deposit: 0, }, SeatHolder { who: 2, stake: 20, - deposit: 3, + deposit: 0, } ] ); @@ -1619,7 +1615,7 @@ mod tests { Voter { stake: 10u64, votes: vec![1], - deposit: 2 + deposit: 0, } ); assert_eq!( @@ -1627,7 +1623,7 @@ mod tests { Voter { stake: 20u64, votes: vec![2], - deposit: 2 + deposit: 0, } ); @@ -1648,16 +1644,6 @@ mod tests { .build_and_execute(|| {}); } - #[test] - #[should_panic] - fn genesis_members_cannot_over_stake_1() { - // 20 cannot reserve 20 as voting bond and extra genesis will panic. - 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() { From 73a353751a96286b7fd6da55eebeb17e64dff208 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 2 Dec 2020 10:16:36 +0000 Subject: [PATCH 53/69] Fix a bit more. --- frame/elections-phragmen/src/lib.rs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index ed0590799c674..18f327aced3de 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -498,12 +498,16 @@ decl_module! { /// # /// we consider the common case of placing more votes. In other two case, we refund. /// # - #[weight = T::WeightInfo::vote_more(votes.len() as u32)] + #[weight = + T::WeightInfo::vote_more(votes.len() as u32) + .max(T::WeightInfo::vote_less(votes.len() as u32)) + .max(T::WeightInfo::vote_equal(votes.len() as u32)) + ] fn vote( origin, votes: Vec, #[compact] value: BalanceOf, - ) -> DispatchResultWithPostInfo { + ) { let who = ensure_signed(origin)?; // votes should not be empty and more than `MAXIMUM_VOTE` in any case. @@ -526,23 +530,19 @@ decl_module! { // Reserve bond. let new_deposit = Self::deposit_of(votes.len()); let Voter { deposit: old_deposit, .. } = >::get(&who); - let maybe_refund = match new_deposit.cmp(&old_deposit) { + match new_deposit.cmp(&old_deposit) { Ordering::Greater => { // Must reserve a bit more. let to_reserve = new_deposit - old_deposit; T::Currency::reserve(&who, to_reserve) .map_err(|_| Error::::UnableToPayBond)?; - None - }, - Ordering::Equal => { - Some(T::WeightInfo::vote_equal(votes.len() as u32)) }, + Ordering::Equal => {}, Ordering::Less => { // Must unreserve a bit. let to_unreserve = old_deposit - new_deposit; let _remainder = T::Currency::unreserve(&who, to_unreserve); debug_assert!(_remainder.is_zero()); - Some(T::WeightInfo::vote_less(votes.len() as u32)) }, }; @@ -558,7 +558,6 @@ decl_module! { ); Voting::::insert(&who, Voter { votes, deposit: new_deposit, stake: locked_stake }); - Ok(maybe_refund.into()) } /// Remove `origin` as a voter. @@ -1505,7 +1504,7 @@ mod tests { Elections::submit_candidacy(origin, Elections::candidates().len() as u32) } - fn vote(origin: Origin, votes: Vec, stake: u64) -> DispatchResultWithPostInfo { + fn vote(origin: Origin, votes: Vec, stake: u64) -> DispatchResult { // historical note: helper function was created in a period of time in which the API of vote // call was changing. Currently it is a wrapper for the original call and does not do much. // Nonetheless, totally harmless. From 98f349e8266b95a237fdb93fc4d1fe813d606bcc Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 2 Dec 2020 11:01:12 +0000 Subject: [PATCH 54/69] Fix build --- frame/elections-phragmen/src/benchmarking.rs | 84 +++++++++++++++++--- 1 file changed, 74 insertions(+), 10 deletions(-) diff --git a/frame/elections-phragmen/src/benchmarking.rs b/frame/elections-phragmen/src/benchmarking.rs index 65fe090d64b29..e78a55c764712 100644 --- a/frame/elections-phragmen/src/benchmarking.rs +++ b/frame/elections-phragmen/src/benchmarking.rs @@ -97,7 +97,7 @@ fn submit_candidates_with_self_vote(c: u32, prefix: &'static str) /// Submit one voter. fn submit_voter(caller: T::AccountId, votes: Vec, stake: BalanceOf) - -> frame_support::dispatch::DispatchResultWithPostInfo + -> frame_support::dispatch::DispatchResult { >::vote(RawOrigin::Signed(caller).into(), votes, stake) } @@ -453,8 +453,9 @@ benchmarks! { // -> e * MAXIMUM_VOTE`, but we cannot express that now with the benchmarks. So what we do // is: when c is being iterated, v, and e are max and fine. when v is being iterated, e is // being set to max and this is a problem. In these cases, we cap e to a lower value, namely - // v * MAXIMUM_VOTE. when e is being iterated, v is at max, and again fine. - // all in all, votes_per_voter can never be more than MAXIMUM_VOTE. + // v * MAXIMUM_VOTE. when e is being iterated, v is at max, and again fine. all in all, + // votes_per_voter can never be more than MAXIMUM_VOTE. Note that this might cause `v` to be + // an overestimate. let votes_per_voter = (e / v).min(MAXIMUM_VOTE as u32); let all_candidates = submit_candidates_with_self_vote::(c, "candidates")?; @@ -476,6 +477,64 @@ benchmarks! { MEMBERS.with(|m| *m.borrow_mut() = vec![]); } } + + #[extra] + election_phragmen_c_e { + let c in 1 .. MAX_CANDIDATES; + let e in MAX_VOTERS .. MAX_VOTERS * MAXIMUM_VOTE as u32; + let fixed_v = MAX_VOTERS; + clean::(); + + let votes_per_voter = e / fixed_v; + + let all_candidates = submit_candidates_with_self_vote::(c, "candidates")?; + let _ = distribute_voters::(all_candidates, fixed_v, votes_per_voter as usize)?; + }: { + >::on_initialize(T::TermDuration::get()); + } + verify { + assert_eq!(>::members().len() as u32, T::DesiredMembers::get().min(c)); + assert_eq!( + >::runners_up().len() as u32, + T::DesiredRunnersUp::get().min(c.saturating_sub(T::DesiredMembers::get())), + ); + + #[cfg(test)] + { + // reset members in between benchmark tests. + use crate::tests::MEMBERS; + MEMBERS.with(|m| *m.borrow_mut() = vec![]); + } + } + + #[extra] + election_phragmen_v { + let v in 4 .. 16; + let fixed_c = MAX_CANDIDATES; + let fixed_e = 64; + clean::(); + + let votes_per_voter = fixed_e / v; + + let all_candidates = submit_candidates_with_self_vote::(fixed_c, "candidates")?; + let _ = distribute_voters::(all_candidates, v, votes_per_voter as usize)?; + }: { + >::on_initialize(T::TermDuration::get()); + } + verify { + assert_eq!(>::members().len() as u32, T::DesiredMembers::get().min(fixed_c)); + assert_eq!( + >::runners_up().len() as u32, + T::DesiredRunnersUp::get().min(fixed_c.saturating_sub(T::DesiredMembers::get())), + ); + + #[cfg(test)] + { + // reset members in between benchmark tests. + use crate::tests::MEMBERS; + MEMBERS.with(|m| *m.borrow_mut() = vec![]); + } + } } #[cfg(test)] @@ -543,14 +602,19 @@ mod tests { }); ExtBuilder::default().desired_members(13).desired_runners_up(7).build_and_execute(|| { - let result = test_benchmark_election_phragmen::(); - let is_ok = result.is_ok(); - let is_expected_error = std::matches!( - result, - Err("in the test setup, we only want to run for component `v` when it is max. This error message is expected.") - ); - assert!(is_ok || is_expected_error); + assert_ok!(test_benchmark_election_phragmen::()); + }); + ExtBuilder::default().desired_members(13).desired_runners_up(7).build_and_execute(|| { + assert_ok!(test_benchmark_election_phragmen::()); + }); + + ExtBuilder::default().desired_members(13).desired_runners_up(7).build_and_execute(|| { + assert_ok!(test_benchmark_election_phragmen_c_e::()); + }); + + ExtBuilder::default().desired_members(13).desired_runners_up(7).build_and_execute(|| { + assert_ok!(test_benchmark_election_phragmen_v::()); }); } } From 9454a68e4b620b785dfa43e1bf6e0c092a0d170f Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 2 Dec 2020 16:50:13 +0000 Subject: [PATCH 55/69] Experimental: independent migration. --- frame/elections-phragmen/src/lib.rs | 188 +++++++++++++++++----------- 1 file changed, 117 insertions(+), 71 deletions(-) diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index 18f327aced3de..6a60289797196 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -129,79 +129,118 @@ type NegativeImbalanceOf = /// Migrations to version [`3.0.0`], as denoted by the changelog. pub mod migrations_3_0_0 { use super::*; + use codec::FullCodec; use frame_support::{ - migration::StorageKeyIterator, - traits::{GetPalletVersion, PalletVersion}, + storage::types::{StorageMap, StorageValue}, + traits::{GetPalletVersion, PalletVersion, StorageInstance}, Twox64Concat, }; - pub fn apply( - old_voter_bond: BalanceOf, - old_candidacy_bond: BalanceOf, + type NewCandidates = StorageValue>; + type NewMembers = StorageValue>>; + type NewRunnersUp = StorageValue>>; + // NOTE: maybe be generic on the damn hasher as well, neh? + type NewVoting = StorageMap>; + + /// Apply all of the migrations from 2_0_0 to 3_0_0. + /// + /// ### Warning + /// + /// This code will **ONLY** check that the storage version is less than 2_0_0. Further check + /// might be needed at the user runtime. + /// + /// Be aware that this migration is intended to be used only for the mentioned versions. Use + /// with care and run at your own risk. + pub fn apply< + Instance: StorageInstance, + Module: GetPalletVersion, + AccountId: 'static + FullCodec, + Balance: 'static + FullCodec + Copy + std::fmt::Debug, + >( + old_voter_bond: Balance, + old_candidacy_bond: Balance, ) -> Weight { - let current_version = as GetPalletVersion>::current_version(); - let maybe_storage_version = as GetPalletVersion>::storage_version(); - - if current_version == PalletVersion::new(3, 0, 0) { - match maybe_storage_version { - Some(storage_version) if storage_version == PalletVersion::new(2, 0, 0) => { - migrate_voters_to_recorded_deposit::(old_voter_bond); - migrate_candidates_to_recorded_deposit::(old_candidacy_bond); - migrate_runners_up_to_recorded_deposit::(old_candidacy_bond); - migrate_members_to_recorded_deposit::(old_candidacy_bond); - Weight::max_value() - }, - _ => { - 0 - } + let maybe_storage_version = ::storage_version(); + match maybe_storage_version { + Some(storage_version) if storage_version <= PalletVersion::new(2, 0, 0) => { + migrate_voters_to_recorded_deposit::(old_voter_bond); + migrate_candidates_to_recorded_deposit::( + old_candidacy_bond, + ); + migrate_runners_up_to_recorded_deposit::( + old_candidacy_bond, + ); + migrate_members_to_recorded_deposit::( + old_candidacy_bond, + ); + Weight::max_value() } - } else { - 0 + _ => 0, } } /// Migrate from the old legacy voting bond (fixed) to the new one (per-vote dynamic). - /// - /// Will only be triggered if storage version is V1. - pub fn migrate_voters_to_recorded_deposit(old_deposit: BalanceOf) { - let mut count = 0; - , Vec), Twox64Concat>>::new( - >::module_prefix(), - b"Voting", - ) - .for_each(|(who, (stake, votes))| { - // Insert a new value into the same location, thus no need to do `.drain()`. - let deposit = old_deposit; - let voter = Voter { votes, stake, deposit }; - >::insert(who, voter); - count += 1; - }); - frame_support::debug::info!("migrated {} voter accounts.", count); + pub fn migrate_voters_to_recorded_deposit< + I: StorageInstance, + AccountId: 'static + FullCodec, + Balance: 'static + FullCodec + Copy + std::fmt::Debug, + >( + old_deposit: Balance, + ) { + >::translate::<(Balance, Vec), _>( + |_who, (stake, votes)| { + let deposit = old_deposit; + dbg!(deposit); + Some(Voter { + votes, + stake, + deposit, + }) + }, + ); + + frame_support::debug::info!( + "migrated {} voter accounts.", + >::iter().count(), + ); } /// Migrate all candidates to recorded deposit. - /// - /// Will only be triggered if storage version is V1. - pub fn migrate_candidates_to_recorded_deposit(old_deposit: BalanceOf) { - let _ = >::translate::, _>(|maybe_old_candidates| { - maybe_old_candidates.map(|old_candidates| { - frame_support::debug::info!("migrated {} candidate accounts.", old_candidates.len()); - old_candidates - .into_iter() - .map(|c| (c, old_deposit)) - .collect::>() - }) - }); + pub fn migrate_candidates_to_recorded_deposit< + I: StorageInstance, + AccountId: 'static + FullCodec, + Balance: 'static + FullCodec + Copy, + >( + old_deposit: Balance, + ) { + let _ = >::translate::, _>( + |maybe_old_candidates| { + maybe_old_candidates.map(|old_candidates| { + frame_support::debug::info!( + "migrated {} candidate accounts.", + old_candidates.len() + ); + old_candidates + .into_iter() + .map(|c| (c, old_deposit)) + .collect::>() + }) + }, + ); } - pub fn migrate_members_to_recorded_deposit(deposit: BalanceOf) { - let _ = >::translate::)>, _>( + /// Migrate all members to recorded deposit. + pub fn migrate_members_to_recorded_deposit< + I: StorageInstance, + AccountId: 'static + FullCodec, + Balance: 'static + FullCodec + Copy, + >( + deposit: Balance, + ) { + let _ = >::translate::, _>( |maybe_old_members| { maybe_old_members.map(|old_members| { - frame_support::debug::info!( - "migrated {} member accounts.", - old_members.len() - ); + frame_support::debug::info!("migrated {} member accounts.", old_members.len()); old_members .into_iter() .map(|(who, stake)| SeatHolder { @@ -215,26 +254,32 @@ pub mod migrations_3_0_0 { ); } - pub fn migrate_runners_up_to_recorded_deposit(deposit: BalanceOf) -> Weight { - let _ = >::translate::)>, _>( + /// Migrate all runners-up to recorded deposit. + pub fn migrate_runners_up_to_recorded_deposit< + I: StorageInstance, + AccountId: 'static + FullCodec, + Balance: 'static + FullCodec + Copy, + >( + deposit: Balance, + ) { + let _ = >::translate::, _>( |maybe_old_runners_up| { maybe_old_runners_up.map(|old_runners_up| { frame_support::debug::info!( - "migrated {} runner-up accounts.", - old_runners_up.len() - ); - old_runners_up - .into_iter() - .map(|(who, stake)| SeatHolder { - who, - stake, - deposit, - }) - .collect::>() - }) + "migrated {} runner-up accounts.", + old_runners_up.len() + ); + old_runners_up + .into_iter() + .map(|(who, stake)| SeatHolder { + who, + stake, + deposit, + }) + .collect::>() + }) }, ); - Weight::max_value() } } @@ -558,6 +603,7 @@ decl_module! { ); Voting::::insert(&who, Voter { votes, deposit: new_deposit, stake: locked_stake }); + } /// Remove `origin` as a voter. From c750f74ea010e4ce4a9c5378000a0e3e3b1422c5 Mon Sep 17 00:00:00 2001 From: thiolliere Date: Wed, 2 Dec 2020 18:27:17 +0100 Subject: [PATCH 56/69] WIP: isolated migration logics --- frame/elections-phragmen/src/lib.rs | 137 +++++++++++++++++----------- 1 file changed, 82 insertions(+), 55 deletions(-) diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index 6a60289797196..97966e9e4ab83 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -128,19 +128,75 @@ type NegativeImbalanceOf = /// Migrations to version [`3.0.0`], as denoted by the changelog. pub mod migrations_3_0_0 { - use super::*; - use codec::FullCodec; + use codec::{Encode, Decode, FullCodec}; use frame_support::{ + RuntimeDebug, weights::Weight, Twox64Concat, storage::types::{StorageMap, StorageValue}, - traits::{GetPalletVersion, PalletVersion, StorageInstance}, - Twox64Concat, + traits::{GetPalletVersion, PalletVersion}, }; - type NewCandidates = StorageValue>; - type NewMembers = StorageValue>>; - type NewRunnersUp = StorageValue>>; - // NOTE: maybe be generic on the damn hasher as well, neh? - type NewVoting = StorageMap>; + #[derive(Encode, Decode, Clone, Default, RuntimeDebug, PartialEq)] + pub struct SeatHolder { + pub who: AccountId, + pub stake: Balance, + pub deposit: Balance, + } + + #[derive(Encode, Decode, Clone, Default, RuntimeDebug, PartialEq)] + pub struct Voter { + pub votes: Vec, + pub stake: Balance, + pub deposit: Balance, + } + + pub trait V2ToV3 { + type Module: GetPalletVersion; + type AccountId: 'static + FullCodec; + type Balance: 'static + FullCodec + Copy + std::fmt::Debug; + } + + struct __PhragmenElection; + impl frame_support::traits::PalletInfo for __PhragmenElection { + fn index() -> Option { unreachable!() } + fn name() -> Option<&'static str> { Some("PhragmenElection") } + } + + struct __Candidates; + impl frame_support::traits::StorageInstance for __Candidates { + type Pallet = (); + type PalletInfo = __PhragmenElection; + const STORAGE_PREFIX: &'static str = "Candidates"; + } + + #[allow(type_alias_bounds)] + type Candidates = StorageValue<__Candidates, Vec<(T::AccountId, T::Balance)>>; + + struct __Members; + impl frame_support::traits::StorageInstance for __Members { + type Pallet = (); + type PalletInfo = __PhragmenElection; + const STORAGE_PREFIX: &'static str = "Members"; + } + #[allow(type_alias_bounds)] + type Members = StorageValue<__Members, Vec>>; + + struct __RunnersUp; + impl frame_support::traits::StorageInstance for __RunnersUp { + type Pallet = (); + type PalletInfo = __PhragmenElection; + const STORAGE_PREFIX: &'static str = "RunnersUp"; + } + #[allow(type_alias_bounds)] + type RunnersUp = StorageValue<__RunnersUp, Vec>>; + + struct __Voting; + impl frame_support::traits::StorageInstance for __Voting { + type Pallet = (); + type PalletInfo = __PhragmenElection; + const STORAGE_PREFIX: &'static str = "Voting"; + } + #[allow(type_alias_bounds)] + type Voting = StorageMap<__Voting, Twox64Concat, T::AccountId, Voter>; /// Apply all of the migrations from 2_0_0 to 3_0_0. /// @@ -151,26 +207,21 @@ pub mod migrations_3_0_0 { /// /// Be aware that this migration is intended to be used only for the mentioned versions. Use /// with care and run at your own risk. - pub fn apply< - Instance: StorageInstance, - Module: GetPalletVersion, - AccountId: 'static + FullCodec, - Balance: 'static + FullCodec + Copy + std::fmt::Debug, - >( - old_voter_bond: Balance, - old_candidacy_bond: Balance, + pub fn apply( + old_voter_bond: T::Balance, + old_candidacy_bond: T::Balance, ) -> Weight { - let maybe_storage_version = ::storage_version(); + let maybe_storage_version = ::storage_version(); match maybe_storage_version { Some(storage_version) if storage_version <= PalletVersion::new(2, 0, 0) => { - migrate_voters_to_recorded_deposit::(old_voter_bond); - migrate_candidates_to_recorded_deposit::( + migrate_voters_to_recorded_deposit::(old_voter_bond); + migrate_candidates_to_recorded_deposit::( old_candidacy_bond, ); - migrate_runners_up_to_recorded_deposit::( + migrate_runners_up_to_recorded_deposit::( old_candidacy_bond, ); - migrate_members_to_recorded_deposit::( + migrate_members_to_recorded_deposit::( old_candidacy_bond, ); Weight::max_value() @@ -180,14 +231,8 @@ pub mod migrations_3_0_0 { } /// Migrate from the old legacy voting bond (fixed) to the new one (per-vote dynamic). - pub fn migrate_voters_to_recorded_deposit< - I: StorageInstance, - AccountId: 'static + FullCodec, - Balance: 'static + FullCodec + Copy + std::fmt::Debug, - >( - old_deposit: Balance, - ) { - >::translate::<(Balance, Vec), _>( + pub fn migrate_voters_to_recorded_deposit(old_deposit: T::Balance) { + >::translate::<(T::Balance, Vec), _>( |_who, (stake, votes)| { let deposit = old_deposit; dbg!(deposit); @@ -201,19 +246,13 @@ pub mod migrations_3_0_0 { frame_support::debug::info!( "migrated {} voter accounts.", - >::iter().count(), + >::iter().count(), ); } /// Migrate all candidates to recorded deposit. - pub fn migrate_candidates_to_recorded_deposit< - I: StorageInstance, - AccountId: 'static + FullCodec, - Balance: 'static + FullCodec + Copy, - >( - old_deposit: Balance, - ) { - let _ = >::translate::, _>( + pub fn migrate_candidates_to_recorded_deposit(old_deposit: T::Balance) { + let _ = >::translate::, _>( |maybe_old_candidates| { maybe_old_candidates.map(|old_candidates| { frame_support::debug::info!( @@ -230,14 +269,8 @@ pub mod migrations_3_0_0 { } /// Migrate all members to recorded deposit. - pub fn migrate_members_to_recorded_deposit< - I: StorageInstance, - AccountId: 'static + FullCodec, - Balance: 'static + FullCodec + Copy, - >( - deposit: Balance, - ) { - let _ = >::translate::, _>( + pub fn migrate_members_to_recorded_deposit(deposit: T::Balance) { + let _ = >::translate::, _>( |maybe_old_members| { maybe_old_members.map(|old_members| { frame_support::debug::info!("migrated {} member accounts.", old_members.len()); @@ -255,14 +288,8 @@ pub mod migrations_3_0_0 { } /// Migrate all runners-up to recorded deposit. - pub fn migrate_runners_up_to_recorded_deposit< - I: StorageInstance, - AccountId: 'static + FullCodec, - Balance: 'static + FullCodec + Copy, - >( - deposit: Balance, - ) { - let _ = >::translate::, _>( + pub fn migrate_runners_up_to_recorded_deposit(deposit: T::Balance) { + let _ = >::translate::, _>( |maybe_old_runners_up| { maybe_old_runners_up.map(|old_runners_up| { frame_support::debug::info!( From e11a2161a59f8a88126e3c09895e855dba54932e Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 2 Dec 2020 17:43:49 +0000 Subject: [PATCH 57/69] clean up. --- frame/elections-phragmen/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index 97966e9e4ab83..cde7074b2fe66 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -129,6 +129,7 @@ type NegativeImbalanceOf = /// Migrations to version [`3.0.0`], as denoted by the changelog. pub mod migrations_3_0_0 { use codec::{Encode, Decode, FullCodec}; + use sp_std::prelude::*; use frame_support::{ RuntimeDebug, weights::Weight, Twox64Concat, storage::types::{StorageMap, StorageValue}, @@ -152,7 +153,7 @@ pub mod migrations_3_0_0 { pub trait V2ToV3 { type Module: GetPalletVersion; type AccountId: 'static + FullCodec; - type Balance: 'static + FullCodec + Copy + std::fmt::Debug; + type Balance: 'static + FullCodec + Copy; } struct __PhragmenElection; @@ -235,7 +236,6 @@ pub mod migrations_3_0_0 { >::translate::<(T::Balance, Vec), _>( |_who, (stake, votes)| { let deposit = old_deposit; - dbg!(deposit); Some(Voter { votes, stake, From 1e2c8e97eff51906b54406a7dbe22d9e9e6fa826 Mon Sep 17 00:00:00 2001 From: thiolliere Date: Thu, 3 Dec 2020 11:45:35 +0100 Subject: [PATCH 58/69] make migration struct private and move migration to own file --- frame/elections-phragmen/src/lib.rs | 186 +--------------- .../src/migrations_3_0_0.rs | 199 ++++++++++++++++++ 2 files changed, 201 insertions(+), 184 deletions(-) create mode 100644 frame/elections-phragmen/src/migrations_3_0_0.rs diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index cde7074b2fe66..24ad5a0b1d49e 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -118,6 +118,8 @@ mod benchmarking; pub mod weights; pub use weights::WeightInfo; +pub mod migrations_3_0_0; + /// The maximum votes allowed per voter. pub const MAXIMUM_VOTE: usize = 16; @@ -126,190 +128,6 @@ type BalanceOf = type NegativeImbalanceOf = <::Currency as Currency<::AccountId>>::NegativeImbalance; -/// Migrations to version [`3.0.0`], as denoted by the changelog. -pub mod migrations_3_0_0 { - use codec::{Encode, Decode, FullCodec}; - use sp_std::prelude::*; - use frame_support::{ - RuntimeDebug, weights::Weight, Twox64Concat, - storage::types::{StorageMap, StorageValue}, - traits::{GetPalletVersion, PalletVersion}, - }; - - #[derive(Encode, Decode, Clone, Default, RuntimeDebug, PartialEq)] - pub struct SeatHolder { - pub who: AccountId, - pub stake: Balance, - pub deposit: Balance, - } - - #[derive(Encode, Decode, Clone, Default, RuntimeDebug, PartialEq)] - pub struct Voter { - pub votes: Vec, - pub stake: Balance, - pub deposit: Balance, - } - - pub trait V2ToV3 { - type Module: GetPalletVersion; - type AccountId: 'static + FullCodec; - type Balance: 'static + FullCodec + Copy; - } - - struct __PhragmenElection; - impl frame_support::traits::PalletInfo for __PhragmenElection { - fn index() -> Option { unreachable!() } - fn name() -> Option<&'static str> { Some("PhragmenElection") } - } - - struct __Candidates; - impl frame_support::traits::StorageInstance for __Candidates { - type Pallet = (); - type PalletInfo = __PhragmenElection; - const STORAGE_PREFIX: &'static str = "Candidates"; - } - - #[allow(type_alias_bounds)] - type Candidates = StorageValue<__Candidates, Vec<(T::AccountId, T::Balance)>>; - - struct __Members; - impl frame_support::traits::StorageInstance for __Members { - type Pallet = (); - type PalletInfo = __PhragmenElection; - const STORAGE_PREFIX: &'static str = "Members"; - } - #[allow(type_alias_bounds)] - type Members = StorageValue<__Members, Vec>>; - - struct __RunnersUp; - impl frame_support::traits::StorageInstance for __RunnersUp { - type Pallet = (); - type PalletInfo = __PhragmenElection; - const STORAGE_PREFIX: &'static str = "RunnersUp"; - } - #[allow(type_alias_bounds)] - type RunnersUp = StorageValue<__RunnersUp, Vec>>; - - struct __Voting; - impl frame_support::traits::StorageInstance for __Voting { - type Pallet = (); - type PalletInfo = __PhragmenElection; - const STORAGE_PREFIX: &'static str = "Voting"; - } - #[allow(type_alias_bounds)] - type Voting = StorageMap<__Voting, Twox64Concat, T::AccountId, Voter>; - - /// Apply all of the migrations from 2_0_0 to 3_0_0. - /// - /// ### Warning - /// - /// This code will **ONLY** check that the storage version is less than 2_0_0. Further check - /// might be needed at the user runtime. - /// - /// Be aware that this migration is intended to be used only for the mentioned versions. Use - /// with care and run at your own risk. - pub fn apply( - old_voter_bond: T::Balance, - old_candidacy_bond: T::Balance, - ) -> Weight { - let maybe_storage_version = ::storage_version(); - match maybe_storage_version { - Some(storage_version) if storage_version <= PalletVersion::new(2, 0, 0) => { - migrate_voters_to_recorded_deposit::(old_voter_bond); - migrate_candidates_to_recorded_deposit::( - old_candidacy_bond, - ); - migrate_runners_up_to_recorded_deposit::( - old_candidacy_bond, - ); - migrate_members_to_recorded_deposit::( - old_candidacy_bond, - ); - Weight::max_value() - } - _ => 0, - } - } - - /// Migrate from the old legacy voting bond (fixed) to the new one (per-vote dynamic). - pub fn migrate_voters_to_recorded_deposit(old_deposit: T::Balance) { - >::translate::<(T::Balance, Vec), _>( - |_who, (stake, votes)| { - let deposit = old_deposit; - Some(Voter { - votes, - stake, - deposit, - }) - }, - ); - - frame_support::debug::info!( - "migrated {} voter accounts.", - >::iter().count(), - ); - } - - /// Migrate all candidates to recorded deposit. - pub fn migrate_candidates_to_recorded_deposit(old_deposit: T::Balance) { - let _ = >::translate::, _>( - |maybe_old_candidates| { - maybe_old_candidates.map(|old_candidates| { - frame_support::debug::info!( - "migrated {} candidate accounts.", - old_candidates.len() - ); - old_candidates - .into_iter() - .map(|c| (c, old_deposit)) - .collect::>() - }) - }, - ); - } - - /// Migrate all members to recorded deposit. - pub fn migrate_members_to_recorded_deposit(deposit: T::Balance) { - let _ = >::translate::, _>( - |maybe_old_members| { - maybe_old_members.map(|old_members| { - frame_support::debug::info!("migrated {} member accounts.", old_members.len()); - old_members - .into_iter() - .map(|(who, stake)| SeatHolder { - who, - stake, - deposit, - }) - .collect::>() - }) - }, - ); - } - - /// Migrate all runners-up to recorded deposit. - pub fn migrate_runners_up_to_recorded_deposit(deposit: T::Balance) { - let _ = >::translate::, _>( - |maybe_old_runners_up| { - maybe_old_runners_up.map(|old_runners_up| { - frame_support::debug::info!( - "migrated {} runner-up accounts.", - old_runners_up.len() - ); - old_runners_up - .into_iter() - .map(|(who, stake)| SeatHolder { - who, - stake, - deposit, - }) - .collect::>() - }) - }, - ); - } -} - /// An indication that the renouncing account currently has which of the below roles. #[derive(Encode, Decode, Clone, PartialEq, RuntimeDebug)] pub enum Renouncing { diff --git a/frame/elections-phragmen/src/migrations_3_0_0.rs b/frame/elections-phragmen/src/migrations_3_0_0.rs new file mode 100644 index 0000000000000..8568857bc6281 --- /dev/null +++ b/frame/elections-phragmen/src/migrations_3_0_0.rs @@ -0,0 +1,199 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2020 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Migrations to version [`3.0.0`], as denoted by the changelog. + +use codec::{Encode, Decode, FullCodec}; +use sp_std::prelude::*; +use frame_support::{ + RuntimeDebug, weights::Weight, Twox64Concat, + storage::types::{StorageMap, StorageValue}, + traits::{GetPalletVersion, PalletVersion}, +}; + +#[derive(Encode, Decode, Clone, Default, RuntimeDebug, PartialEq)] +struct SeatHolder { + who: AccountId, + stake: Balance, + deposit: Balance, +} + +#[derive(Encode, Decode, Clone, Default, RuntimeDebug, PartialEq)] +struct Voter { + votes: Vec, + stake: Balance, + deposit: Balance, +} + +pub trait V2ToV3 { + type Module: GetPalletVersion; + type AccountId: 'static + FullCodec; + type Balance: 'static + FullCodec + Copy; +} + +struct __PhragmenElection; +impl frame_support::traits::PalletInfo for __PhragmenElection { + fn index() -> Option { unreachable!() } + fn name() -> Option<&'static str> { Some("PhragmenElection") } +} + +struct __Candidates; +impl frame_support::traits::StorageInstance for __Candidates { + type Pallet = (); + type PalletInfo = __PhragmenElection; + const STORAGE_PREFIX: &'static str = "Candidates"; +} + +#[allow(type_alias_bounds)] +type Candidates = StorageValue<__Candidates, Vec<(T::AccountId, T::Balance)>>; + +struct __Members; +impl frame_support::traits::StorageInstance for __Members { + type Pallet = (); + type PalletInfo = __PhragmenElection; + const STORAGE_PREFIX: &'static str = "Members"; +} +#[allow(type_alias_bounds)] +type Members = StorageValue<__Members, Vec>>; + +struct __RunnersUp; +impl frame_support::traits::StorageInstance for __RunnersUp { + type Pallet = (); + type PalletInfo = __PhragmenElection; + const STORAGE_PREFIX: &'static str = "RunnersUp"; +} +#[allow(type_alias_bounds)] +type RunnersUp = StorageValue<__RunnersUp, Vec>>; + +struct __Voting; +impl frame_support::traits::StorageInstance for __Voting { + type Pallet = (); + type PalletInfo = __PhragmenElection; + const STORAGE_PREFIX: &'static str = "Voting"; +} +#[allow(type_alias_bounds)] +type Voting = StorageMap<__Voting, Twox64Concat, T::AccountId, Voter>; + +/// Apply all of the migrations from 2_0_0 to 3_0_0. +/// +/// ### Warning +/// +/// This code will **ONLY** check that the storage version is less than 2_0_0. Further check +/// might be needed at the user runtime. +/// +/// Be aware that this migration is intended to be used only for the mentioned versions. Use +/// with care and run at your own risk. +pub fn apply( + old_voter_bond: T::Balance, + old_candidacy_bond: T::Balance, +) -> Weight { + let maybe_storage_version = ::storage_version(); + match maybe_storage_version { + Some(storage_version) if storage_version <= PalletVersion::new(2, 0, 0) => { + migrate_voters_to_recorded_deposit::(old_voter_bond); + migrate_candidates_to_recorded_deposit::( + old_candidacy_bond, + ); + migrate_runners_up_to_recorded_deposit::( + old_candidacy_bond, + ); + migrate_members_to_recorded_deposit::( + old_candidacy_bond, + ); + Weight::max_value() + } + _ => 0, + } +} + +/// Migrate from the old legacy voting bond (fixed) to the new one (per-vote dynamic). +pub fn migrate_voters_to_recorded_deposit(old_deposit: T::Balance) { + >::translate::<(T::Balance, Vec), _>( + |_who, (stake, votes)| { + let deposit = old_deposit; + Some(Voter { + votes, + stake, + deposit, + }) + }, + ); + + frame_support::debug::info!( + "migrated {} voter accounts.", + >::iter().count(), + ); +} + +/// Migrate all candidates to recorded deposit. +pub fn migrate_candidates_to_recorded_deposit(old_deposit: T::Balance) { + let _ = >::translate::, _>( + |maybe_old_candidates| { + maybe_old_candidates.map(|old_candidates| { + frame_support::debug::info!( + "migrated {} candidate accounts.", + old_candidates.len() + ); + old_candidates + .into_iter() + .map(|c| (c, old_deposit)) + .collect::>() + }) + }, + ); +} + +/// Migrate all members to recorded deposit. +pub fn migrate_members_to_recorded_deposit(deposit: T::Balance) { + let _ = >::translate::, _>( + |maybe_old_members| { + maybe_old_members.map(|old_members| { + frame_support::debug::info!("migrated {} member accounts.", old_members.len()); + old_members + .into_iter() + .map(|(who, stake)| SeatHolder { + who, + stake, + deposit, + }) + .collect::>() + }) + }, + ); +} + +/// Migrate all runners-up to recorded deposit. +pub fn migrate_runners_up_to_recorded_deposit(deposit: T::Balance) { + let _ = >::translate::, _>( + |maybe_old_runners_up| { + maybe_old_runners_up.map(|old_runners_up| { + frame_support::debug::info!( + "migrated {} runner-up accounts.", + old_runners_up.len() + ); + old_runners_up + .into_iter() + .map(|(who, stake)| SeatHolder { + who, + stake, + deposit, + }) + .collect::>() + }) + }, + ); +} From 583aa82e5ed21af7a01f4fa65ddda458557b1ab2 Mon Sep 17 00:00:00 2001 From: thiolliere Date: Thu, 3 Dec 2020 11:51:49 +0100 Subject: [PATCH 59/69] add doc --- frame/elections-phragmen/src/migrations_3_0_0.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/frame/elections-phragmen/src/migrations_3_0_0.rs b/frame/elections-phragmen/src/migrations_3_0_0.rs index 8568857bc6281..b95c393619d7e 100644 --- a/frame/elections-phragmen/src/migrations_3_0_0.rs +++ b/frame/elections-phragmen/src/migrations_3_0_0.rs @@ -39,9 +39,15 @@ struct Voter { deposit: Balance, } +/// Trait to implement to give information about types used for migration pub trait V2ToV3 { + /// elections-phragmen module, used to check storage version. type Module: GetPalletVersion; + + /// System config account id type AccountId: 'static + FullCodec; + + /// Elections-phragmen currency balance. type Balance: 'static + FullCodec + Copy; } From 586c61ca6c54af1c9e9ed1967690911ef35e504a Mon Sep 17 00:00:00 2001 From: thiolliere Date: Thu, 3 Dec 2020 11:56:04 +0100 Subject: [PATCH 60/69] fix StorageInstance new syntax --- .../elections-phragmen/src/migrations_3_0_0.rs | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/frame/elections-phragmen/src/migrations_3_0_0.rs b/frame/elections-phragmen/src/migrations_3_0_0.rs index b95c393619d7e..7aac084337a2d 100644 --- a/frame/elections-phragmen/src/migrations_3_0_0.rs +++ b/frame/elections-phragmen/src/migrations_3_0_0.rs @@ -51,16 +51,9 @@ pub trait V2ToV3 { type Balance: 'static + FullCodec + Copy; } -struct __PhragmenElection; -impl frame_support::traits::PalletInfo for __PhragmenElection { - fn index() -> Option { unreachable!() } - fn name() -> Option<&'static str> { Some("PhragmenElection") } -} - struct __Candidates; impl frame_support::traits::StorageInstance for __Candidates { - type Pallet = (); - type PalletInfo = __PhragmenElection; + fn pallet_prefix() -> &'static str { "PhragmenElection" } const STORAGE_PREFIX: &'static str = "Candidates"; } @@ -69,8 +62,7 @@ type Candidates = StorageValue<__Candidates, Vec<(T::AccountId, T::Ba struct __Members; impl frame_support::traits::StorageInstance for __Members { - type Pallet = (); - type PalletInfo = __PhragmenElection; + fn pallet_prefix() -> &'static str { "PhragmenElection" } const STORAGE_PREFIX: &'static str = "Members"; } #[allow(type_alias_bounds)] @@ -78,8 +70,7 @@ type Members = StorageValue<__Members, Vec &'static str { "PhragmenElection" } const STORAGE_PREFIX: &'static str = "RunnersUp"; } #[allow(type_alias_bounds)] @@ -87,8 +78,7 @@ type RunnersUp = StorageValue<__RunnersUp, Vec &'static str { "PhragmenElection" } const STORAGE_PREFIX: &'static str = "Voting"; } #[allow(type_alias_bounds)] From 0a0ef71fb86c1ddcfabdc293154aa05a5f88521a Mon Sep 17 00:00:00 2001 From: Guillaume Thiolliere Date: Fri, 4 Dec 2020 12:52:17 +0100 Subject: [PATCH 61/69] Update frame/elections-phragmen/src/migrations_3_0_0.rs Co-authored-by: Shawn Tabrizi --- frame/elections-phragmen/src/migrations_3_0_0.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/elections-phragmen/src/migrations_3_0_0.rs b/frame/elections-phragmen/src/migrations_3_0_0.rs index 7aac084337a2d..e896b5037e064 100644 --- a/frame/elections-phragmen/src/migrations_3_0_0.rs +++ b/frame/elections-phragmen/src/migrations_3_0_0.rs @@ -88,8 +88,8 @@ type Voting = StorageMap<__Voting, Twox64Concat, T::AccountId, Voter< /// /// ### Warning /// -/// This code will **ONLY** check that the storage version is less than 2_0_0. Further check -/// might be needed at the user runtime. +/// This code will **ONLY** check that the storage version is less than or equal to 2_0_0. +/// Further check might be needed at the user runtime. /// /// Be aware that this migration is intended to be used only for the mentioned versions. Use /// with care and run at your own risk. From 63a4c565bb65dca88a6b97882092d04ed3f6b07d Mon Sep 17 00:00:00 2001 From: kianenigma Date: Thu, 17 Dec 2020 10:38:32 +0000 Subject: [PATCH 62/69] another round of self-review. --- frame/elections-phragmen/src/lib.rs | 145 +++++++++++++++++----------- 1 file changed, 86 insertions(+), 59 deletions(-) diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index 3b70bc6f27408..9df3d97920e7b 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -23,13 +23,13 @@ //! //! The election happens in _rounds_: every `N` blocks, all previous members are retired and a new //! set is elected (which may or may not have an intersection with the previous set). Each round -//! lasts for some number of blocks defined by [`TermDuration`]. The words _term_ and _round_ can be -//! used interchangeably in this context. +//! lasts for some number of blocks defined by [`Config::TermDuration`]. The words _term_ and +//! _round_ can be used interchangeably in this context. //! -//! [`TermDuration`] might change during a round. This can shorten or extend the length of the -//! round. The next election round's block number is never stored but rather always checked on the -//! fly. Based on the current block number and [`TermDuration`], the condition `BlockNumber % -//! TermDuration == 0` being satisfied will always trigger a new election round. +//! [`Config::TermDuration`] might change during a round. This can shorten or extend the length of +//! the round. The next election round's block number is never stored but rather always checked on +//! the fly. Based on the current block number and [`Config::TermDuration`], the condition +//! `BlockNumber % TermDuration == 0` being satisfied will always trigger a new election round. //! //! ### Bonds and Deposits //! @@ -47,37 +47,42 @@ //! ignored during election. Yet, a voter _might_ vote for a future candidate. Voters reserve a bond //! as they vote. Each vote defines a `value`. This amount is locked from the account of the voter //! and indicates the weight of the vote. Voters can update their votes at any time by calling -//! `vote()` again. This keeps the bond untouched but can optionally change the locked `value`. -//! After a round, votes are kept and might still be valid for further rounds. A voter is -//! responsible for calling `remove_voter` once they are done to have their bond back and remove the -//! lock. +//! `vote()` again. This can update the vote targets (which might update the deposit) or update the +//! vote's stake ([`Voter::stake`]). After a round, votes are kept and might still be valid for +//! further rounds. A voter is responsible for calling `remove_voter` once they are done to have +//! their bond back and remove the lock. +//! +//! See [`Call::vote`]. //! //! ### Defunct Voter //! //! A voter is defunct once all of the candidates that they have voted for are not a valid candidate //! (as seen further below, members and runners-up are also always candidates). Defunct voters can -//! be removed via a root call. Upon being removed, their bond is returned. This is an -//! administrative operation and can be called only by the root origin in the case of state bloat. +//! be removed via a root call ([`Call::clean_defunct_voters`]). Upon being removed, their bond is +//! returned. This is an administrative operation and can be called only by the root origin in the +//! case of state bloat. //! //! ### Candidacy and Members //! //! Candidates also reserve a bond as they submit candidacy. A candidate can end up in one of the //! below situations: //! - **Members**: A winner is kept as a _member_. They must still have a bond in reserve and they -//! are automatically counted as a candidate for the next election. +//! are automatically counted as a candidate for the next election. The number of desired +//! members is set by [`Config::DesiredMembers`]. //! - **Runner-up**: Runners-up are the best candidates immediately after the winners. The number -//! of runners_up to keep is configurable. Runners-up are used, in order that they are elected, -//! as replacements when a candidate is kicked by [`remove_member`], or when an active member -//! renounces their candidacy. Runners are automatically counted as a candidate for the next -//! election. -//! - **Loser**: Any of the candidate who are not a winner are left as losers. A loser might be an -//! _outgoing member or runner_, meaning that they are an active member who failed to keep their -//! spot. **An outgoing candidate/member/runner-up will always lose their bond**. +//! of runners up to keep is set by [`Config::DesiredRunnersUp`]. Runners-up are used, in order +//! that they are elected, as replacements when a candidate is kicked by +//! [`Call::remove_member`], or when an active member renounces their candidacy. Runners are +//! automatically counted as a candidate for the next election. +//! - **Loser**: Any of the candidate who are not member/runner-up are left as losers. A loser +//! might be an _outgoing member or runner-up_, meaning that they are an active member who +//! failed to keep their spot. **An outgoing candidate/member/runner-up will always lose their +//! bond**. //! //! #### Renouncing candidacy. //! -//! All candidates, elected or not, can renounce their candidacy. A call to [`renounce_candidacy`] -//! will always cause the candidacy bond to be refunded. +//! All candidates, elected or not, can renounce their candidacy. A call to +//! [`Call::renounce_candidacy`] will always cause the candidacy bond to be refunded. //! //! Note that with the members being the default candidates for the next round and votes persisting //! in storage, the election system is entirely stable given no further input. This means that if @@ -160,6 +165,8 @@ pub struct SeatHolder { /// The total backing stake. pub stake: Balance, /// The amount of deposit held on-chain. + /// + /// To be unreserved upon renouncing, or slashed upon being a loser. pub deposit: Balance, } @@ -231,8 +238,8 @@ decl_storage! { /// last (i.e. _best_) runner-up will be replaced. pub RunnersUp get(fn runners_up): Vec>>; - /// The present candidate list. A current member or runner-up - /// can never enter this vector and is always implicitly assumed to be a candidate. + /// The present candidate list. A current member or runner-up can never enter this vector + /// and is always implicitly assumed to be a candidate. /// /// Second element is the deposit. /// @@ -245,7 +252,10 @@ decl_storage! { /// Votes and locked stake of a particular voter. /// /// TWOX-NOTE: SAFE as `AccountId` is a crypto hash. - pub Voting get(fn voting): map hasher(twox_64_concat) T::AccountId => Voter>; + pub Voting get(fn voting): map hasher(twox_64_concat) + T::AccountId + => + Voter>; } add_extra_genesis { config(members): Vec<(T::AccountId, BalanceOf)>; build(|config: &GenesisConfig| { @@ -270,7 +280,9 @@ decl_storage! { }); // set self-votes to make persistent. Genesis voters don't have any bond, nor do - // they have any lock. + // they have any lock. NOTE: this means that we will still try to remove a lock once + // this genesis voter is removed, and for now it is okay because remove_lock is noop + // if lock is not there. >::insert( &member, Voter { votes: vec![member.clone()], stake: *stake, deposit: Zero::zero() }, @@ -314,7 +326,7 @@ decl_error! { /// Not a member. NotMember, /// The provided count of number of candidates is incorrect. - InvalidCandidateCount, + InvalidWitnessData, /// The provided count of number of votes is incorrect. InvalidVoteCount, /// The renouncing origin presented a wrong `Renouncing` parameter. @@ -378,15 +390,17 @@ decl_module! { /// - be less than the number of possible candidates. Note that all current members and /// runners-up are also automatically candidates for the next round. /// + /// If `value` is more than `who`'s total balance, then the maximum of the two is used. + /// /// The dispatch origin of this call must be signed. /// /// ### Warning /// /// It is the responsibility of the caller to **NOT** place all of their balance into the - /// lock and keep some for further transactions. + /// lock and keep some for further operations. /// /// # - /// we consider the common case of placing more votes. In other two case, we refund. + /// We assume the maximum weight among all 3 cases: vote_equal, vote_more and vote_less. /// # #[weight = T::WeightInfo::vote_more(votes.len() as u32) @@ -411,7 +425,7 @@ decl_module! { // can never submit a vote of there are no members, and cannot submit more votes than // all potential vote targets. // addition is valid: candidates, members and runners-up will never overlap. - let allowed_votes = candidates_count + members_count + runners_up_count; + let allowed_votes = candidates_count.saturating_add(members_count).saturating_add(runners_up_count); ensure!(!allowed_votes.is_zero(), Error::::UnableToVote); ensure!(votes.len() <= allowed_votes, Error::::TooManyVotes); @@ -460,7 +474,6 @@ decl_module! { fn remove_voter(origin) { let who = ensure_signed(origin)?; ensure!(Self::is_voter(&who), Error::::MustBeVoter); - Self::do_remove_voter(&who); } @@ -473,8 +486,8 @@ decl_module! { /// /// ### Warning /// - /// Even if a candidate ends up being a member, they must call [`renounce_candidacy`] to get - /// their deposit back. Losing the spot in an election will also lead to a slash. + /// Even if a candidate ends up being a member, they must call [`Call::renounce_candidacy`] + /// to get their deposit back. Losing the spot in an election will always lead to a slash. /// /// # /// The number of current candidates must be provided as witness data. @@ -486,12 +499,10 @@ decl_module! { let actual_count = >::decode_len().unwrap_or(0); ensure!( actual_count as u32 <= candidate_count, - Error::::InvalidCandidateCount, + Error::::InvalidWitnessData, ); - let is_candidate = Self::is_candidate(&who); - ensure!(is_candidate.is_err(), Error::::DuplicatedCandidate); - let index = is_candidate.unwrap_err(); + let index = Self::is_candidate(&who).err().ok_or(Error::::DuplicatedCandidate)?; ensure!(!Self::is_member(&who), Error::::MemberSubmit); ensure!(!Self::is_runner_up(&who), Error::::RunnerUpSubmit); @@ -528,27 +539,31 @@ decl_module! { let who = ensure_signed(origin)?; match renouncing { Renouncing::Member => { - let _ = Self::remove_and_replace_member(&who, false).map_err(|_| Error::::InvalidRenouncing)?; + let _ = Self::remove_and_replace_member(&who, false) + .map_err(|_| Error::::InvalidRenouncing)?; Self::deposit_event(RawEvent::Renounced(who)); }, Renouncing::RunnerUp => { >::try_mutate::<_, Error, _>(|runners_up| { - let index = runners_up.iter().position(|SeatHolder { who: r, .. }| r == &who).ok_or(Error::::InvalidRenouncing)?; + let index = runners_up + .iter() + .position(|SeatHolder { who: r, .. }| r == &who) + .ok_or(Error::::InvalidRenouncing)?; let SeatHolder { deposit, .. } = runners_up.remove(index); T::Currency::unreserve(&who, deposit); - >::put(runners_up); Self::deposit_event(RawEvent::Renounced(who)); Ok(()) })?; } Renouncing::Candidate(count) => { >::try_mutate::<_, Error, _>(|candidates| { - ensure!(count >= candidates.len() as u32, Error::::InvalidRenouncing); - let index = candidates.binary_search_by(|(c, _)| c.cmp(&who)).map_err(|_| Error::::InvalidRenouncing)?; + ensure!(count >= candidates.len() as u32, Error::::InvalidWitnessData); + let index = candidates + .binary_search_by(|(c, _)| c.cmp(&who)) + .map_err(|_| Error::::InvalidRenouncing)?; let (_removed, deposit) = candidates.remove(index); T::Currency::unreserve(&who, deposit); Self::deposit_event(RawEvent::Renounced(who)); - >::put(candidates); Ok(()) })?; } @@ -589,9 +604,10 @@ decl_module! { // refund. The weight value comes from a benchmark which is special to this. T::WeightInfo::remove_member_wrong_refund() )); - } // else, prediction was correct. + } let had_replacement = Self::remove_and_replace_member(&who, true)?; + debug_assert_eq!(has_replacement, had_replacement); Self::deposit_event(RawEvent::MemberKicked(who.clone())); if !had_replacement { @@ -654,12 +670,12 @@ impl Module { /// /// Both `Members` and `RunnersUp` storage is updated accordingly. `T::ChangeMember` is called /// if needed. If `slash` is true, the deposit of the potentially removed member is slashed, - /// else it is unreserved. + /// else, it is unreserved. /// - /// ### Note + /// ### Note: Prime preservation /// /// This function attempts to preserve the prime. If the removed members is not the prime, it is - /// set again via [`Trait::ChangeMembers` ] to prevent it being wiped. + /// set again via [`Config::ChangeMembers` ] to prevent it being wiped. fn remove_and_replace_member(who: &T::AccountId, slash: bool) -> Result { // closure will return: // - `Ok(Option(replacement))` if member was removed and replacement was replaced. @@ -684,20 +700,23 @@ impl Module { } let maybe_next_best = >::mutate(|r| r.pop()).map(|next_best| { - // invariant: Members and runners-up are disjoint. This will always be err and give - // us an index to insert. - members - .binary_search_by(|m| m.who.cmp(&next_best.who)) - .err() - .map(|index| { - members.insert(index, next_best.clone()); - }); + // defensive-only: Members and runners-up are disjoint. This will always be err and + // give us an index to insert. + if let Err(index) = members.binary_search_by(|m| m.who.cmp(&next_best.who)) { + members.insert(index, next_best.clone()); + } else { + // overlap. This can never happen. If so, it seems like our intended replacement + // is already a member, so not much more to do. + frame_support::debug::error!( + "pallet-elections-phragmen: a member seems to also be a runner-up." + ); + } next_best }); Ok(maybe_next_best) })?; - let member_ids = Self::members() + let remaining_member_ids_sorted = Self::members() .into_iter() .map(|x| x.who.clone()) .collect::>(); @@ -706,11 +725,19 @@ impl Module { let return_value = match maybe_replacement { // member ids are already sorted, other two elements have one item. Some(incoming) => { - T::ChangeMembers::change_members_sorted(&[incoming.who], outgoing, &member_ids[..]); + T::ChangeMembers::change_members_sorted( + &[incoming.who], + outgoing, + &remaining_member_ids_sorted[..] + ); true } None => { - T::ChangeMembers::change_members_sorted(&[], outgoing, &member_ids[..]); + T::ChangeMembers::change_members_sorted( + &[], + outgoing, + &remaining_member_ids_sorted[..] + ); false } }; @@ -2840,7 +2867,7 @@ mod tests { assert_noop!( Elections::renounce_candidacy(Origin::signed(4), Renouncing::Candidate(2)), - Error::::InvalidRenouncing, + Error::::InvalidWitnessData, ); assert_ok!(Elections::renounce_candidacy(Origin::signed(4), Renouncing::Candidate(3))); From ec067c92525a1af735d296e11007d502160b07fe Mon Sep 17 00:00:00 2001 From: kianenigma Date: Thu, 17 Dec 2020 10:41:49 +0000 Subject: [PATCH 63/69] bit better formatting --- frame/elections-phragmen/src/lib.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index 9df3d97920e7b..d48a07c073c7c 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -619,8 +619,8 @@ decl_module! { Ok(None.into()) } - /// Clean all voters who are defunct (i.e. they do not serve any purpose at all). The deposit - /// of the removed voters are returned. + /// Clean all voters who are defunct (i.e. they do not serve any purpose at all). The + /// deposit of the removed voters are returned. /// /// This is an root function to be used only for cleaning the state. /// @@ -1062,7 +1062,10 @@ impl Contains for Module { fn contains(who: &T::AccountId) -> bool { Self::is_member(who) } - fn sorted_members() -> Vec { Self::members_ids() } + + fn sorted_members() -> Vec { + Self::members_ids() + } // A special function to populate members in this pallet for passing Origin // checks in runtime benchmarking. From 32cb6c1a5e42a7f1ba04be6c4a04f12e1e7595f7 Mon Sep 17 00:00:00 2001 From: Parity Benchmarking Bot Date: Thu, 17 Dec 2020 20:14:06 +0000 Subject: [PATCH 64/69] cargo run --release --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=pallet_elections_phragmen --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/elections-phragmen/src/weights.rs --template=./.maintain/frame-weight-template.hbs --- frame/elections-phragmen/src/weights.rs | 99 +++++++++++++++---------- 1 file changed, 59 insertions(+), 40 deletions(-) diff --git a/frame/elections-phragmen/src/weights.rs b/frame/elections-phragmen/src/weights.rs index fe9c8f706a036..86cef72bb5317 100644 --- a/frame/elections-phragmen/src/weights.rs +++ b/frame/elections-phragmen/src/weights.rs @@ -15,9 +15,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Weights for pallet_elections_phragmen +//! Autogenerated weights for pallet_elections_phragmen +//! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 2.0.0 -//! DATE: 2020-11-20, STEPS: [50, ], REPEAT: 20, LOW RANGE: [], HIGH RANGE: [] +//! DATE: 2020-12-17, STEPS: [50, ], REPEAT: 20, LOW RANGE: [], HIGH RANGE: [] //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 // Executed Command: @@ -61,71 +62,80 @@ pub trait WeightInfo { pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { fn vote_equal(v: u32, ) -> Weight { - (57_585_000 as Weight) - .saturating_add((409_000 as Weight).saturating_mul(v as Weight)) + (61_124_000 as Weight) + // Standard Error: 7_000 + .saturating_add((408_000 as Weight).saturating_mul(v as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } fn vote_more(v: u32, ) -> Weight { - (87_970_000 as Weight) - .saturating_add((415_000 as Weight).saturating_mul(v as Weight)) + (93_154_000 as Weight) + // Standard Error: 9_000 + .saturating_add((451_000 as Weight).saturating_mul(v as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } fn vote_less(v: u32, ) -> Weight { - (83_650_000 as Weight) - .saturating_add((405_000 as Weight).saturating_mul(v as Weight)) + (88_123_000 as Weight) + // Standard Error: 11_000 + .saturating_add((453_000 as Weight).saturating_mul(v as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } fn remove_voter() -> Weight { - (80_456_000 as Weight) + (82_228_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } fn submit_candidacy(c: u32, ) -> Weight { - (70_738_000 as Weight) - .saturating_add((428_000 as Weight).saturating_mul(c as Weight)) + (74_924_000 as Weight) + // Standard Error: 1_000 + .saturating_add((441_000 as Weight).saturating_mul(c as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } fn renounce_candidacy_candidate(c: u32, ) -> Weight { - (50_244_000 as Weight) - .saturating_add((214_000 as Weight).saturating_mul(c as Weight)) + (64_985_000 as Weight) + // Standard Error: 1_000 + .saturating_add((219_000 as Weight).saturating_mul(c as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } fn renounce_candidacy_members() -> Weight { - (90_817_000 as Weight) + (96_491_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } fn renounce_candidacy_runners_up() -> Weight { - (49_801_000 as Weight) + (64_584_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } fn remove_member_with_replacement() -> Weight { - (99_157_000 as Weight) + (105_844_000 as Weight) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } fn remove_member_wrong_refund() -> Weight { - (9_042_000 as Weight) + (9_459_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) } fn clean_defunct_voters(v: u32, _d: u32, ) -> Weight { (0 as Weight) - .saturating_add((158_896_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 54_000 + .saturating_add((171_348_000 as Weight).saturating_mul(v as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().reads((3 as Weight).saturating_mul(v as Weight))) .saturating_add(T::DbWeight::get().writes((3 as Weight).saturating_mul(v as Weight))) } fn election_phragmen(c: u32, v: u32, e: u32, ) -> Weight { (0 as Weight) - .saturating_add((85_751_000 as Weight).saturating_mul(c as Weight)) - .saturating_add((130_226_000 as Weight).saturating_mul(v as Weight)) - .saturating_add((9_200_000 as Weight).saturating_mul(e as Weight)) + // Standard Error: 1_954_000 + .saturating_add((47_765_000 as Weight).saturating_mul(c as Weight)) + // Standard Error: 812_000 + .saturating_add((71_025_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 55_000 + .saturating_add((4_359_000 as Weight).saturating_mul(e as Weight)) .saturating_add(T::DbWeight::get().reads((2 as Weight).saturating_mul(c as Weight))) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(v as Weight))) .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(c as Weight))) @@ -135,71 +145,80 @@ impl WeightInfo for SubstrateWeight { // For backwards compatibility and tests impl WeightInfo for () { fn vote_equal(v: u32, ) -> Weight { - (57_585_000 as Weight) - .saturating_add((409_000 as Weight).saturating_mul(v as Weight)) + (61_124_000 as Weight) + // Standard Error: 7_000 + .saturating_add((408_000 as Weight).saturating_mul(v as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } fn vote_more(v: u32, ) -> Weight { - (87_970_000 as Weight) - .saturating_add((415_000 as Weight).saturating_mul(v as Weight)) + (93_154_000 as Weight) + // Standard Error: 9_000 + .saturating_add((451_000 as Weight).saturating_mul(v as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } fn vote_less(v: u32, ) -> Weight { - (83_650_000 as Weight) - .saturating_add((405_000 as Weight).saturating_mul(v as Weight)) + (88_123_000 as Weight) + // Standard Error: 11_000 + .saturating_add((453_000 as Weight).saturating_mul(v as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } fn remove_voter() -> Weight { - (80_456_000 as Weight) + (82_228_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } fn submit_candidacy(c: u32, ) -> Weight { - (70_738_000 as Weight) - .saturating_add((428_000 as Weight).saturating_mul(c as Weight)) + (74_924_000 as Weight) + // Standard Error: 1_000 + .saturating_add((441_000 as Weight).saturating_mul(c as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } fn renounce_candidacy_candidate(c: u32, ) -> Weight { - (50_244_000 as Weight) - .saturating_add((214_000 as Weight).saturating_mul(c as Weight)) + (64_985_000 as Weight) + // Standard Error: 1_000 + .saturating_add((219_000 as Weight).saturating_mul(c as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } fn renounce_candidacy_members() -> Weight { - (90_817_000 as Weight) + (96_491_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } fn renounce_candidacy_runners_up() -> Weight { - (49_801_000 as Weight) + (64_584_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } fn remove_member_with_replacement() -> Weight { - (99_157_000 as Weight) + (105_844_000 as Weight) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } fn remove_member_wrong_refund() -> Weight { - (9_042_000 as Weight) + (9_459_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) } fn clean_defunct_voters(v: u32, _d: u32, ) -> Weight { (0 as Weight) - .saturating_add((158_896_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 54_000 + .saturating_add((171_348_000 as Weight).saturating_mul(v as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().reads((3 as Weight).saturating_mul(v as Weight))) .saturating_add(RocksDbWeight::get().writes((3 as Weight).saturating_mul(v as Weight))) } fn election_phragmen(c: u32, v: u32, e: u32, ) -> Weight { (0 as Weight) - .saturating_add((85_751_000 as Weight).saturating_mul(c as Weight)) - .saturating_add((130_226_000 as Weight).saturating_mul(v as Weight)) - .saturating_add((9_200_000 as Weight).saturating_mul(e as Weight)) + // Standard Error: 1_954_000 + .saturating_add((47_765_000 as Weight).saturating_mul(c as Weight)) + // Standard Error: 812_000 + .saturating_add((71_025_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 55_000 + .saturating_add((4_359_000 as Weight).saturating_mul(e as Weight)) .saturating_add(RocksDbWeight::get().reads((2 as Weight).saturating_mul(c as Weight))) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(v as Weight))) .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(c as Weight))) From 38881e0b23eaaf6b9c015280a8e0c5df882ae246 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Mon, 21 Dec 2020 09:19:51 +0000 Subject: [PATCH 65/69] Fix tests. --- frame/elections-phragmen/src/lib.rs | 34 ++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index d48a07c073c7c..b631a037c05fb 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -1092,7 +1092,7 @@ impl ContainsLengthBound for Module { #[cfg(test)] mod tests { use super::*; - use frame_support::{assert_ok, assert_noop, assert_err_with_weight, parameter_types, + use frame_support::{assert_ok, assert_noop, parameter_types, traits::OnInitialize, }; use substrate_test_utils::assert_eq_uvec; @@ -1111,7 +1111,7 @@ mod tests { impl frame_system::Config for Test { type BaseCallFilter = (); - type BlockWeights = (); + type BlockWeights = BlockWeights; type BlockLength = (); type DbWeight = (); type Origin = Origin; @@ -2502,10 +2502,17 @@ mod tests { assert_eq!(members_ids(), vec![4, 5]); // no replacement yet. - assert_err_with_weight!( - Elections::remove_member(Origin::root(), 4, true), - Error::::InvalidReplacement, - Some(34042000), // only thing that matters for now is that it is NOT the full block. + let unwrapped_error = Elections::remove_member(Origin::root(), 4, true).unwrap_err(); + matches!( + unwrapped_error.error, + DispatchError::Module { + message: Some("InvalidReplacement"), + .. + } + ); + matches!( + unwrapped_error.post_info.actual_weight, + Some(x) if x < ::BlockWeights::get().max_block ); }); @@ -2524,10 +2531,17 @@ mod tests { assert_eq!(runners_up_ids(), vec![3]); // there is a replacement! and this one needs a weight refund. - assert_err_with_weight!( - Elections::remove_member(Origin::root(), 4, false), - Error::::InvalidReplacement, - Some(34042000) // only thing that matters for now is that it is NOT the full block. + let unwrapped_error = Elections::remove_member(Origin::root(), 4, false).unwrap_err(); + matches!( + unwrapped_error.error, + DispatchError::Module { + message: Some("InvalidReplacement"), + .. + } + ); + matches!( + unwrapped_error.post_info.actual_weight, + Some(x) if x < ::BlockWeights::get().max_block ); }); } From 7d95b1582055f9828201c356f243d9cb1eebbdb7 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 20 Jan 2021 08:13:32 +0000 Subject: [PATCH 66/69] Round of self-review --- Cargo.lock | 4 - frame/collective/src/lib.rs | 6 +- frame/elections-phragmen/src/lib.rs | 730 ++++++++++++---------------- frame/proxy/src/lib.rs | 3 +- 4 files changed, 326 insertions(+), 417 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 79124bb3f6eb1..b6b1e8aa07927 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4563,11 +4563,7 @@ dependencies = [ [[package]] name = "pallet-elections-phragmen" -<<<<<<< HEAD version = "3.0.0" -======= -version = "2.0.1" ->>>>>>> e813f62d94431fb220bc9f984ca12fe01c1650b2 dependencies = [ "frame-benchmarking", "frame-support", diff --git a/frame/collective/src/lib.rs b/frame/collective/src/lib.rs index 1b2705575f3fa..b2993fd45eb3a 100644 --- a/frame/collective/src/lib.rs +++ b/frame/collective/src/lib.rs @@ -42,10 +42,10 @@ #![cfg_attr(not(feature = "std"), no_std)] #![recursion_limit = "128"] +use sp_std::{prelude::*, result}; use sp_core::u32_trait::Value as U32; use sp_io::storage; -use sp_runtime::{traits::Hash, RuntimeDebug}; -use sp_std::{prelude::*, result}; +use sp_runtime::{RuntimeDebug, traits::Hash}; use frame_support::{ codec::{Decode, Encode}, @@ -58,7 +58,7 @@ use frame_support::{ traits::{ChangeMembers, EnsureOrigin, Get, InitializeMembers}, weights::{DispatchClass, GetDispatchInfo, Weight, Pays}, }; -use frame_system::{self as system, ensure_root, ensure_signed}; +use frame_system::{self as system, ensure_signed, ensure_root}; #[cfg(feature = "runtime-benchmarks")] mod benchmarking; diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index 916128fdaf82a..1bef73831e65d 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -52,7 +52,7 @@ //! further rounds. A voter is responsible for calling `remove_voter` once they are done to have //! their bond back and remove the lock. //! -//! See [`Call::vote`]. +//! See [`Call::vote`], [`Call::remove_voter`]. //! //! ### Defunct Voter //! @@ -70,8 +70,8 @@ //! are automatically counted as a candidate for the next election. The number of desired //! members is set by [`Config::DesiredMembers`]. //! - **Runner-up**: Runners-up are the best candidates immediately after the winners. The number -//! of runners up to keep is set by [`Config::DesiredRunnersUp`]. Runners-up are used, in order -//! that they are elected, as replacements when a candidate is kicked by +//! of runners up to keep is set by [`Config::DesiredRunnersUp`]. Runners-up are used, in the +//! same order as they are elected, as replacements when a candidate is kicked by //! [`Call::remove_member`], or when an active member renounces their candidacy. Runners are //! automatically counted as a candidate for the next election. //! - **Loser**: Any of the candidate who are not member/runner-up are left as losers. A loser @@ -252,10 +252,7 @@ decl_storage! { /// Votes and locked stake of a particular voter. /// /// TWOX-NOTE: SAFE as `AccountId` is a crypto hash. - pub Voting get(fn voting): map hasher(twox_64_concat) - T::AccountId - => - Voter>; + pub Voting get(fn voting): map hasher(twox_64_concat) T::AccountId => Voter>; } add_extra_genesis { config(members): Vec<(T::AccountId, BalanceOf)>; build(|config: &GenesisConfig| { @@ -341,10 +338,7 @@ decl_error! { } decl_event!( - pub enum Event where - Balance = BalanceOf, - ::AccountId, - { + pub enum Event where Balance = BalanceOf, ::AccountId { /// A new term with \[new_members\]. This indicates that enough candidates existed to run the /// election, not that enough have has been elected. The inner value must be examined for /// this purpose. A `NewTerm(\[\])` indicates that some candidates got their bond slashed and @@ -429,7 +423,9 @@ decl_module! { // can never submit a vote of there are no members, and cannot submit more votes than // all potential vote targets. // addition is valid: candidates, members and runners-up will never overlap. - let allowed_votes = candidates_count.saturating_add(members_count).saturating_add(runners_up_count); + let allowed_votes = candidates_count + .saturating_add(members_count) + .saturating_add(runners_up_count); ensure!(!allowed_votes.is_zero(), Error::::UnableToVote); ensure!(votes.len() <= allowed_votes, Error::::TooManyVotes); @@ -442,8 +438,7 @@ decl_module! { Ordering::Greater => { // Must reserve a bit more. let to_reserve = new_deposit - old_deposit; - T::Currency::reserve(&who, to_reserve) - .map_err(|_| Error::::UnableToPayBond)?; + T::Currency::reserve(&who, to_reserve).map_err(|_| Error::::UnableToPayBond)?; }, Ordering::Equal => {}, Ordering::Less => { @@ -456,17 +451,14 @@ decl_module! { // Amount to be locked up. let locked_stake = value.min(T::Currency::total_balance(&who)); - - // lock T::Currency::set_lock( T::ModuleId::get(), &who, locked_stake, - WithdrawReasons::except(WithdrawReasons::TRANSACTION_PAYMENT), + WithdrawReasons::all(), ); Voting::::insert(&who, Voter { votes, deposit: new_deposit, stake: locked_stake }); - } /// Remove `origin` as a voter. @@ -526,7 +518,7 @@ decl_module! { /// origin is removed as a runner-up. /// - `origin` is a current member. In this case, the deposit is unreserved and origin is /// removed as a member, consequently not being a candidate for the next round anymore. - /// Similar to [`remove_voter`], if replacement runners exists, they are immediately used. + /// Similar to [`remove_members`], if replacement runners exists, they are immediately used. /// If the prime is renouncing, then no prime will exist until the next round. /// /// The dispatch origin of this call must be signed, and have one of the above roles. @@ -553,8 +545,10 @@ decl_module! { .iter() .position(|SeatHolder { who: r, .. }| r == &who) .ok_or(Error::::InvalidRenouncing)?; + // can't fail anymore. let SeatHolder { deposit, .. } = runners_up.remove(index); - T::Currency::unreserve(&who, deposit); + let _remainder = T::Currency::unreserve(&who, deposit); + debug_assert!(_remainder.is_zero()); Self::deposit_event(RawEvent::Renounced(who)); Ok(()) })?; @@ -566,7 +560,8 @@ decl_module! { .binary_search_by(|(c, _)| c.cmp(&who)) .map_err(|_| Error::::InvalidRenouncing)?; let (_removed, deposit) = candidates.remove(index); - T::Currency::unreserve(&who, deposit); + let _remainder = T::Currency::unreserve(&who, deposit); + debug_assert!(_remainder.is_zero()); Self::deposit_event(RawEvent::Renounced(who)); Ok(()) })?; @@ -601,7 +596,7 @@ decl_module! { ensure_root(origin)?; let who = T::Lookup::lookup(who)?; - let will_have_replacement = >::decode_len().unwrap_or(0) > 0; + let will_have_replacement = >::decode_len().map_or(false, |l| l > 0); if will_have_replacement != has_replacement { // In both cases, we will change more weight than need. Refund and abort. return Err(Error::::InvalidReplacement.with_weight( @@ -615,7 +610,6 @@ decl_module! { Self::deposit_event(RawEvent::MemberKicked(who.clone())); if !had_replacement { - // if we end up here, we will charge a full block weight. Self::do_phragmen(); } @@ -647,7 +641,8 @@ decl_module! { /// /// Checks if an election needs to happen or not. fn on_initialize(n: T::BlockNumber) -> Weight { - if !Self::term_duration().is_zero() && (n % Self::term_duration()).is_zero() { + let term_duration = T::TermDuration::get(); + if !term_duration.is_zero() && (n % term_duration).is_zero() { Self::do_phragmen() } else { 0 @@ -679,18 +674,16 @@ impl Module { /// ### Note: Prime preservation /// /// This function attempts to preserve the prime. If the removed members is not the prime, it is - /// set again via [`Config::ChangeMembers` ] to prevent it being wiped. + /// set again via [`Config::ChangeMembers`]. fn remove_and_replace_member(who: &T::AccountId, slash: bool) -> Result { // closure will return: // - `Ok(Option(replacement))` if member was removed and replacement was replaced. // - `Ok(None)` if member was removed but no replacement was found // - `Err(_)` if who is not a member. let maybe_replacement = >::try_mutate::<_, Error, _>(|members| { + let remove_index = + members.binary_search_by(|m| m.who.cmp(who)).map_err(|_| Error::::NotMember)?; // we remove the member anyhow, regardless of having a runner-up or not. - let remove_index = members - // note: members are always sorted. - .binary_search_by(|m| m.who.cmp(who)) - .map_err(|_| Error::::NotMember)?; let removed = members.remove(remove_index); // slash or unreserve @@ -759,59 +752,28 @@ impl Module { /// Check if `who` is a candidate. It returns the insert index if the element does not exists as /// an error. - /// - /// O(LogN) given N candidates. fn is_candidate(who: &T::AccountId) -> Result<(), usize> { - Self::candidates() - .binary_search_by(|c| c.0.cmp(who)) - .map(|_| ()) + Self::candidates().binary_search_by(|c| c.0.cmp(who)).map(|_| ()) } /// Check if `who` is a voter. It may or may not be a _current_ one. - /// - /// State: O(1). fn is_voter(who: &T::AccountId) -> bool { Voting::::contains_key(who) } /// Check if `who` is currently an active member. - /// - /// O(LogN) given N members. Since members are limited, O(1). fn is_member(who: &T::AccountId) -> bool { Self::members().binary_search_by(|m| m.who.cmp(who)).is_ok() } /// Check if `who` is currently an active runner-up. - /// - /// O(N) given N runners-up. Since runners-up are limited, O(1). fn is_runner_up(who: &T::AccountId) -> bool { - Self::runners_up() - .iter() - .position(|r| &r.who == who) - .is_some() - } - - /// Returns number of desired members. - fn desired_members() -> u32 { - T::DesiredMembers::get() - } - - /// Returns number of desired runners up. - fn desired_runners_up() -> u32 { - T::DesiredRunnersUp::get() - } - - /// Returns the term duration - fn term_duration() -> T::BlockNumber { - T::TermDuration::get() + Self::runners_up().iter().position(|r| &r.who == who).is_some() } /// Get the members' account ids. fn members_ids() -> Vec { - Self::members() - .into_iter() - .map(|m| m.who) - .collect::>() + Self::members().into_iter().map(|m| m.who).collect::>() } /// Get a concatenation of previous members and runners-up and their deposits. @@ -846,6 +808,8 @@ impl Module { // remove storage, lock and unreserve. T::Currency::remove_lock(T::ModuleId::get(), who); + // NOTE: we could check the deposit amount before removing and skip if zero, but it will be + // a noop anyhow. let _remainder = T::Currency::unreserve(who, deposit); debug_assert!(_remainder.is_zero()); } @@ -855,8 +819,8 @@ impl Module { /// /// Calls the appropriate [`ChangeMembers`] function variant internally. fn do_phragmen() -> Weight { - let desired_seats = Self::desired_members() as usize; - let desired_runners_up = Self::desired_runners_up() as usize; + let desired_seats = T::DesiredMembers::get() as usize; + let desired_runners_up = T::DesiredRunnersUp::get() as usize; let num_to_elect = desired_runners_up + desired_seats; let mut candidates_and_deposit = Self::candidates(); @@ -902,158 +866,138 @@ impl Module { candidate_ids, voters_and_votes.clone(), None, - ) - .map( - |ElectionResult { winners, assignments: _, }| { - // this is already sorted by id. - let old_members_ids_sorted = >::take() - .into_iter() - .map(|m| m.who) - .collect::>(); - // this one needs a sort by id. - let mut old_runners_up_ids_sorted = >::take() - .into_iter() - .map(|r| r.who) - .collect::>(); - old_runners_up_ids_sorted.sort(); - - // filter out those who end up with no backing stake. This is a logic specific to - // this pallet. - let new_set_with_stake = winners - .into_iter() - .filter_map(|(m, b)| { - if b.is_zero() { - None - } else { - Some((m, to_balance(b))) - } - }) - .collect::)>>(); - - // OPTIMISATION NOTE: we could bail out here if `new_set.len() == 0`. There isn't - // much left to do. Yet, re-arranging the code would require duplicating the - // slashing of exposed candidates, cleaning any previous members, and so on. For - // now, in favour of readability and veracity, we keep it simple. - - // split new set into winners and runners up. - let split_point = desired_seats.min(new_set_with_stake.len()); - - let mut new_members_sorted_by_id = (&new_set_with_stake[..split_point]).to_vec(); - new_members_sorted_by_id.sort_by(|i, j| i.0.cmp(&j.0)); - - let new_runners_up_sorted_by_rank = (&new_set_with_stake[split_point..]) - .to_vec() - .into_iter() - .rev() - .collect::)>>(); - let mut new_runners_up_ids_sorted = new_runners_up_sorted_by_rank - .iter() - .map(|(r, _)| r.clone()) - .collect::>(); - new_runners_up_ids_sorted.sort(); - - // Now we select a prime member using a [Borda - // count](https://en.wikipedia.org/wiki/Borda_count). We weigh everyone's vote for - // that new member by a multiplier based on the order of the votes. i.e. the first - // person a voter votes for gets a 16x multiplier, the next person gets a 15x - // multiplier, an so on... (assuming `MAXIMUM_VOTE` = 16) - let mut prime_votes = new_members_sorted_by_id - .iter() - .map(|c| (&c.0, BalanceOf::::zero())) - .collect::>(); - for (_, stake, votes) in voters_and_stakes.into_iter() { - for (vote_multiplier, who) in votes - .iter() - .enumerate() - .map(|(vote_position, who)| ((MAXIMUM_VOTE - vote_position) as u32, who)) - { - if let Ok(i) = prime_votes.binary_search_by_key(&who, |k| k.0) { - prime_votes[i].1 = prime_votes[i] - .1 - .saturating_add(stake.saturating_mul(vote_multiplier.into())); - } + ).map(|ElectionResult { winners, assignments: _, }| { + // this is already sorted by id. + let old_members_ids_sorted = >::take().into_iter() + .map(|m| m.who) + .collect::>(); + // this one needs a sort by id. + let mut old_runners_up_ids_sorted = >::take().into_iter() + .map(|r| r.who) + .collect::>(); + old_runners_up_ids_sorted.sort(); + + // filter out those who end up with no backing stake. + let mut new_set_with_stake = winners + .into_iter() + .filter_map(|(m, b)| if b.is_zero() { None } else { Some((m, to_balance(b))) }) + .collect::)>>(); + + // OPTIMIZATION NOTE: we could bail out here if `new_set.len() == 0`. There isn't + // much left to do. Yet, re-arranging the code would require duplicating the + // slashing of exposed candidates, cleaning any previous members, and so on. For + // now, in favor of readability and veracity, we keep it simple. + + // split new set into winners and runners up. + let split_point = desired_seats.min(new_set_with_stake.len()); + let mut new_members_sorted_by_id = new_set_with_stake.drain(..split_point).collect::>(); + new_members_sorted_by_id.sort_by(|i, j| i.0.cmp(&j.0)); + + // all the rest will be runners-up + new_set_with_stake.reverse(); + let new_runners_up_sorted_by_rank = new_set_with_stake; + let mut new_runners_up_ids_sorted = new_runners_up_sorted_by_rank + .iter() + .map(|(r, _)| r.clone()) + .collect::>(); + new_runners_up_ids_sorted.sort(); + + // Now we select a prime member using a [Borda + // count](https://en.wikipedia.org/wiki/Borda_count). We weigh everyone's vote for + // that new member by a multiplier based on the order of the votes. i.e. the first + // person a voter votes for gets a 16x multiplier, the next person gets a 15x + // multiplier, an so on... (assuming `MAXIMUM_VOTE` = 16) + let mut prime_votes = new_members_sorted_by_id + .iter() + .map(|c| (&c.0, BalanceOf::::zero())) + .collect::>(); + for (_, stake, votes) in voters_and_stakes.into_iter() { + for (vote_multiplier, who) in votes.iter() + .enumerate() + .map(|(vote_position, who)| ((MAXIMUM_VOTE - vote_position) as u32, who)) + { + if let Ok(i) = prime_votes.binary_search_by_key(&who, |k| k.0) { + prime_votes[i].1 = prime_votes[i].1.saturating_add( + stake.saturating_mul(vote_multiplier.into()) + ); } } + } + // We then select the new member with the highest weighted stake. In the case of a tie, + // the last person in the list with the tied score is selected. This is the person with + // the "highest" account id based on the sort above. + let prime = prime_votes.into_iter().max_by_key(|x| x.1).map(|x| x.0.clone()); + + // new_members_sorted_by_id is sorted by account id. + let new_members_ids_sorted = new_members_sorted_by_id + .iter() + .map(|(m, _)| m.clone()) + .collect::>(); + + // report member changes. We compute diff because we need the outgoing list. + let (incoming, outgoing) = T::ChangeMembers::compute_members_diff_sorted( + &new_members_ids_sorted, + &old_members_ids_sorted, + ); + T::ChangeMembers::change_members_sorted( + &incoming, + &outgoing, + &new_members_ids_sorted, + ); + T::ChangeMembers::set_prime(prime); + + // All candidates/members/runners-up who are no longer retaining a position as a + // seat holder will lose their bond. + candidates_and_deposit.iter().for_each(|(c, d)| { + if + new_members_ids_sorted.binary_search(c).is_err() && + new_runners_up_ids_sorted.binary_search(c).is_err() + { + let (imbalance, _) = T::Currency::slash_reserved(c, *d); + T::LoserCandidate::on_unbalanced(imbalance); + Self::deposit_event(RawEvent::CandidateSlashed(c.clone(), *d)); + } + }); - // We then select the new member with the highest weighted stake. In the case of - // a tie, the last person in the list with the tied score is selected. This is - // the person with the "highest" account id based on the sort above. - let prime = prime_votes - .into_iter() - .max_by_key(|x| x.1) - .map(|x| x.0.clone()); - - // new_members_sorted_by_id is sorted by account id. - let new_members_ids_sorted = new_members_sorted_by_id + // write final values to storage. + let deposit_of_candidate = |x: &T::AccountId| -> BalanceOf { + // defensive-only. This closure is used against the new members and new runners-up, + // both of which are phragmen winners and thus must have deposit. + candidates_and_deposit .iter() - .map(|(m, _)| m.clone()) - .collect::>(); - - // report member changes. We compute diff because we need the outgoing list. - let (incoming, outgoing) = T::ChangeMembers::compute_members_diff_sorted( - &new_members_ids_sorted, - &old_members_ids_sorted, - ); - T::ChangeMembers::change_members_sorted( - &incoming, - &outgoing, - &new_members_ids_sorted, - ); - T::ChangeMembers::set_prime(prime); - - // All candidates/members/runners-up who are no longer retaining a position as a - // seat holder will lose their bond. - candidates_and_deposit.iter().for_each(|(c, d)| { - if - new_members_ids_sorted.binary_search(c).is_err() && - new_runners_up_ids_sorted.binary_search(c).is_err() - { - let (imbalance, _) = T::Currency::slash_reserved(c, *d); - T::LoserCandidate::on_unbalanced(imbalance); - Self::deposit_event(RawEvent::CandidateSlashed(c.clone(), *d)); - } - }); - - // write final values to storage. - let deposit_of_candidate = |x: &T::AccountId| -> BalanceOf { - // defensive-only. This closure is used against the new members and new runners-up, both of which - // are phragmen winners and thus must have deposit. - candidates_and_deposit - .iter() - .find_map(|(c, d)| if c == x { Some(*d) } else { None }) - .unwrap_or_default() - }; - // fetch deposits from the one recorded one. This will make sure that a candidate who submitted - // candidacy before a change to candidacy deposit will have the correct amount recorded. - >::put( - new_members_sorted_by_id - .iter() - .map(|(who, stake)| SeatHolder { - deposit: deposit_of_candidate(&who), - who: who.clone(), - stake: stake.clone(), - }) - .collect::>(), - ); - >::put( - new_runners_up_sorted_by_rank - .into_iter() - .map(|(who, stake)| SeatHolder { - deposit: deposit_of_candidate(&who), - who, - stake, - }) - .collect::>(), - ); + .find_map(|(c, d)| if c == x { Some(*d) } else { None }) + .unwrap_or_default() + }; + // fetch deposits from the one recorded one. This will make sure that a candidate who + // submitted candidacy before a change to candidacy deposit will have the correct amount + // recorded. + >::put( + new_members_sorted_by_id + .iter() + .map(|(who, stake)| SeatHolder { + deposit: deposit_of_candidate(&who), + who: who.clone(), + stake: stake.clone(), + }) + .collect::>(), + ); + >::put( + new_runners_up_sorted_by_rank + .into_iter() + .map(|(who, stake)| SeatHolder { + deposit: deposit_of_candidate(&who), + who, + stake, + }) + .collect::>(), + ); - // clean candidates. - >::kill(); + // clean candidates. + >::kill(); - Self::deposit_event(RawEvent::NewTerm(new_members_sorted_by_id)); - ElectionRounds::mutate(|v| *v += 1); - }, - ) - .map_err(|e| { + Self::deposit_event(RawEvent::NewTerm(new_members_sorted_by_id)); + ElectionRounds::mutate(|v| *v += 1); + }).map_err(|e| { frame_support::debug::error!("elections-phragmen: failed to run election [{:?}].", e); Self::deposit_event(RawEvent::ElectionError); }); @@ -1089,7 +1033,7 @@ impl ContainsLengthBound for Module { /// Implementation uses a parameter type so calling is cost-free. fn max_len() -> usize { - Self::desired_members() as usize + T::DesiredMembers::get() as usize } } @@ -1324,7 +1268,10 @@ mod tests { } fn candidate_deposit(who: &u64) -> u64 { - Elections::candidates().into_iter().find_map(|(c, d)| if c == *who { Some(d) } else { None }).unwrap_or_default() + Elections::candidates() + .into_iter() + .find_map(|(c, d)| if c == *who { Some(d) } else { None }) + .unwrap_or_default() } fn voter_deposit(who: &u64) -> u64 { @@ -1332,10 +1279,7 @@ mod tests { } fn runners_up_ids() -> Vec { - Elections::runners_up() - .into_iter() - .map(|r| r.who) - .collect::>() + Elections::runners_up().into_iter().map(|r| r.who).collect::>() } fn members_ids() -> Vec { @@ -1343,17 +1287,11 @@ mod tests { } fn members_and_stake() -> Vec<(u64, u64)> { - Elections::members() - .into_iter() - .map(|m| (m.who, m.stake)) - .collect::>() + Elections::members().into_iter().map(|m| (m.who, m.stake)).collect::>() } fn runners_up_and_stake() -> Vec<(u64, u64)> { - Elections::runners_up() - .into_iter() - .map(|r| (r.who, r.stake)) - .collect::>() + Elections::runners_up().into_iter().map(|r| (r.who, r.stake)).collect::>() } fn all_voters() -> Vec { @@ -1365,9 +1303,15 @@ mod tests { } fn has_lock(who: &u64) -> u64 { - let lock = Balances::locks(who)[0].clone(); - assert_eq!(lock.id, ElectionsPhragmenModuleId::get()); - lock.amount + dbg!(Balances::locks(who)); + Balances::locks(who) + .get(0) + .cloned() + .map(|lock| { + assert_eq!(lock.id, ElectionsPhragmenModuleId::get()); + lock.amount + }) + .unwrap_or_default() } fn intersects(a: &[T], b: &[T]) -> bool { @@ -1439,12 +1383,12 @@ mod tests { #[test] fn params_should_work() { ExtBuilder::default().build_and_execute(|| { - assert_eq!(Elections::desired_members(), 2); - assert_eq!(Elections::desired_runners_up(), 0); + assert_eq!(::DesiredMembers::get(), 2); + assert_eq!(::DesiredRunnersUp::get(), 0); assert_eq!(::VotingBondBase::get(), 2); assert_eq!(::VotingBondFactor::get(), 0); assert_eq!(::CandidacyBond::get(), 3); - assert_eq!(Elections::term_duration(), 5); + assert_eq!(::TermDuration::get(), 5); assert_eq!(Elections::election_rounds(), 0); assert!(Elections::members().is_empty()); @@ -1461,96 +1405,64 @@ mod tests { #[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![ - SeatHolder { - who: 1, - stake: 10, - deposit: 0, - }, - SeatHolder { - who: 2, - stake: 20, - deposit: 0, - } - ] - ); + ExtBuilder::default().genesis_members(vec![(1, 10), (2, 20)]).build_and_execute(|| { + System::set_block_number(1); + assert_eq!( + Elections::members(), + vec![ + SeatHolder { who: 1, stake: 10, deposit: 0 }, + SeatHolder { who: 2, stake: 20, deposit: 0 } + ] + ); - assert_eq!( - Elections::voting(1), - Voter { - stake: 10u64, - votes: vec![1], - deposit: 0, - } - ); - assert_eq!( - Elections::voting(2), - Voter { - stake: 20u64, - votes: vec![2], - deposit: 0, - } - ); + assert_eq!(Elections::voting(1), Voter { stake: 10u64, votes: vec![1], deposit: 0 }); + assert_eq!(Elections::voting(2), Voter { stake: 20u64, votes: vec![2], deposit: 0 }); - // they will persist since they have self vote. - System::set_block_number(5); - Elections::on_initialize(System::block_number()); + // they will persist since they have self vote. + System::set_block_number(5); + Elections::on_initialize(System::block_number()); - assert_eq!(members_ids(), vec![1, 2]); - }) + assert_eq!(members_ids(), vec![1, 2]); + }) + } + + #[test] + fn genesis_voters_can_remove_lock() { + ExtBuilder::default().genesis_members(vec![(1, 10), (2, 20)]).build_and_execute(|| { + System::set_block_number(1); + + assert_eq!(Elections::voting(1), Voter { stake: 10u64, votes: vec![1], deposit: 0 }); + assert_eq!(Elections::voting(2), Voter { stake: 20u64, votes: vec![2], deposit: 0 }); + + assert_ok!(Elections::remove_voter(Origin::signed(1))); + assert_ok!(Elections::remove_voter(Origin::signed(2))); + + assert_eq!(Elections::voting(1), Default::default()); + assert_eq!(Elections::voting(2), Default::default()); + }) } #[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![ - SeatHolder { - who: 1, - stake: 10, - deposit: 0, - }, - SeatHolder { - who: 2, - stake: 20, - deposit: 0, - } - ] - ); + ExtBuilder::default().genesis_members(vec![(2, 20), (1, 10)]).build_and_execute(|| { + System::set_block_number(1); + assert_eq!( + Elections::members(), + vec![ + SeatHolder { who: 1, stake: 10, deposit: 0 }, + SeatHolder { who: 2, stake: 20, deposit: 0 }, + ] + ); - assert_eq!( - Elections::voting(1), - Voter { - stake: 10u64, - votes: vec![1], - deposit: 0, - } - ); - assert_eq!( - Elections::voting(2), - Voter { - stake: 20u64, - votes: vec![2], - deposit: 0, - } - ); + assert_eq!(Elections::voting(1), Voter { stake: 10u64, votes: vec![1], deposit: 0 }); + assert_eq!(Elections::voting(2), Voter { stake: 20u64, votes: vec![2], deposit: 0 }); - // they will persist since they have self vote. - System::set_block_number(5); - Elections::on_initialize(System::block_number()); + // they will persist since they have self vote. + System::set_block_number(5); + Elections::on_initialize(System::block_number()); - assert_eq!(members_ids(), vec![1, 2]); - }) + assert_eq!(members_ids(), vec![1, 2]); + }) } #[test] @@ -1586,8 +1498,8 @@ mod tests { .term_duration(0) .build_and_execute(|| { - assert_eq!(Elections::term_duration(), 0); - assert_eq!(Elections::desired_members(), 2); + assert_eq!(::TermDuration::get(), 0); + assert_eq!(::DesiredMembers::get(), 2); assert_eq!(Elections::election_rounds(), 0); assert!(members_ids().is_empty()); @@ -1655,16 +1567,8 @@ mod tests { assert_eq!( Elections::members(), vec![ - SeatHolder { - who: 4, - stake: 40, - deposit: 4 - }, - SeatHolder { - who: 5, - stake: 50, - deposit: 3 - } + SeatHolder { who: 4, stake: 40, deposit: 4 }, + SeatHolder { who: 5, stake: 50, deposit: 3 }, ] ); }) @@ -2919,35 +2823,32 @@ mod tests { #[test] fn unsorted_runners_up_are_detected() { - ExtBuilder::default() - .desired_runners_up(2) - .desired_members(1) - .build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(submit_candidacy(Origin::signed(4))); + ExtBuilder::default().desired_runners_up(2).desired_members(1).build_and_execute(|| { + assert_ok!(submit_candidacy(Origin::signed(5))); + assert_ok!(submit_candidacy(Origin::signed(4))); assert_ok!(submit_candidacy(Origin::signed(3))); assert_ok!(vote(Origin::signed(5), vec![5], 50)); assert_ok!(vote(Origin::signed(4), vec![4], 5)); assert_ok!(vote(Origin::signed(3), vec![3], 15)); - System::set_block_number(5); - Elections::on_initialize(System::block_number()); + System::set_block_number(5); + Elections::on_initialize(System::block_number()); - assert_eq!(members_ids(), vec![5]); - assert_eq!(runners_up_ids(), vec![4, 3]); + assert_eq!(members_ids(), vec![5]); + assert_eq!(runners_up_ids(), vec![4, 3]); - assert_ok!(submit_candidacy(Origin::signed(2))); - assert_ok!(vote(Origin::signed(2), vec![2], 10)); + assert_ok!(submit_candidacy(Origin::signed(2))); + assert_ok!(vote(Origin::signed(2), vec![2], 10)); - System::set_block_number(10); - Elections::on_initialize(System::block_number()); + System::set_block_number(10); + Elections::on_initialize(System::block_number()); - assert_eq!(members_ids(), vec![5]); - assert_eq!(runners_up_ids(), vec![2, 3]); + assert_eq!(members_ids(), vec![5]); + assert_eq!(runners_up_ids(), vec![2, 3]); - // 4 is outgoing runner-up. Slash candidacy bond. - assert_eq!(balances(&4), (35, 2)); + // 4 is outgoing runner-up. Slash candidacy bond. + assert_eq!(balances(&4), (35, 2)); // 3 stays. assert_eq!(balances(&3), (25, 5)); }) @@ -3082,17 +2983,14 @@ mod tests { #[test] fn no_desired_members() { // not interested in anything - ExtBuilder::default() - .desired_members(0) - .desired_runners_up(0) - .build_and_execute(|| { - assert_eq!(Elections::candidates().len(), 0); + ExtBuilder::default().desired_members(0).desired_runners_up(0).build_and_execute(|| { + assert_eq!(Elections::candidates().len(), 0); - assert_ok!(submit_candidacy(Origin::signed(4))); - assert_ok!(submit_candidacy(Origin::signed(3))); - assert_ok!(submit_candidacy(Origin::signed(2))); + assert_ok!(submit_candidacy(Origin::signed(4))); + assert_ok!(submit_candidacy(Origin::signed(3))); + assert_ok!(submit_candidacy(Origin::signed(2))); - assert_eq!(Elections::candidates().len(), 3); + assert_eq!(Elections::candidates().len(), 3); assert_ok!(vote(Origin::signed(4), vec![4], 40)); assert_ok!(vote(Origin::signed(3), vec![3], 30)); @@ -3102,85 +3000,101 @@ mod tests { Elections::on_initialize(System::block_number()); assert_eq!(members_ids().len(), 0); - assert_eq!(runners_up_ids().len(), 0); - assert_eq!(all_voters().len(), 3); - assert_eq!(Elections::candidates().len(), 0); - }); + assert_eq!(runners_up_ids().len(), 0); + assert_eq!(all_voters().len(), 3); + assert_eq!(Elections::candidates().len(), 0); + }); // not interested in members - ExtBuilder::default() - .desired_members(0) - .desired_runners_up(2) - .build_and_execute(|| { - assert_eq!(Elections::candidates().len(), 0); + ExtBuilder::default().desired_members(0).desired_runners_up(2).build_and_execute(|| { + assert_eq!(Elections::candidates().len(), 0); - assert_ok!(submit_candidacy(Origin::signed(4))); - assert_ok!(submit_candidacy(Origin::signed(3))); - assert_ok!(submit_candidacy(Origin::signed(2))); + assert_ok!(submit_candidacy(Origin::signed(4))); + assert_ok!(submit_candidacy(Origin::signed(3))); + assert_ok!(submit_candidacy(Origin::signed(2))); - assert_eq!(Elections::candidates().len(), 3); + assert_eq!(Elections::candidates().len(), 3); - assert_ok!(vote(Origin::signed(4), vec![4], 40)); - assert_ok!(vote(Origin::signed(3), vec![3], 30)); - assert_ok!(vote(Origin::signed(2), vec![2], 20)); + assert_ok!(vote(Origin::signed(4), vec![4], 40)); + assert_ok!(vote(Origin::signed(3), vec![3], 30)); + assert_ok!(vote(Origin::signed(2), vec![2], 20)); - System::set_block_number(5); - Elections::on_initialize(System::block_number()); + System::set_block_number(5); + Elections::on_initialize(System::block_number()); - assert_eq!(members_ids().len(), 0); - assert_eq!(runners_up_ids(), vec![3, 4]); - assert_eq!(all_voters().len(), 3); - assert_eq!(Elections::candidates().len(), 0); - }); + assert_eq!(members_ids().len(), 0); + assert_eq!(runners_up_ids(), vec![3, 4]); + assert_eq!(all_voters().len(), 3); + assert_eq!(Elections::candidates().len(), 0); + }); // not interested in runners-up - ExtBuilder::default() - .desired_members(2) - .desired_runners_up(0) - .build_and_execute(|| { - assert_eq!(Elections::candidates().len(), 0); + ExtBuilder::default().desired_members(2).desired_runners_up(0).build_and_execute(|| { + assert_eq!(Elections::candidates().len(), 0); - assert_ok!(submit_candidacy(Origin::signed(4))); - assert_ok!(submit_candidacy(Origin::signed(3))); - assert_ok!(submit_candidacy(Origin::signed(2))); + assert_ok!(submit_candidacy(Origin::signed(4))); + assert_ok!(submit_candidacy(Origin::signed(3))); + assert_ok!(submit_candidacy(Origin::signed(2))); - assert_eq!(Elections::candidates().len(), 3); + assert_eq!(Elections::candidates().len(), 3); - assert_ok!(vote(Origin::signed(4), vec![4], 40)); - assert_ok!(vote(Origin::signed(3), vec![3], 30)); - assert_ok!(vote(Origin::signed(2), vec![2], 20)); + assert_ok!(vote(Origin::signed(4), vec![4], 40)); + assert_ok!(vote(Origin::signed(3), vec![3], 30)); + assert_ok!(vote(Origin::signed(2), vec![2], 20)); - System::set_block_number(5); - Elections::on_initialize(System::block_number()); + System::set_block_number(5); + Elections::on_initialize(System::block_number()); - assert_eq!(members_ids(), vec![3, 4]); - assert_eq!(runners_up_ids().len(), 0); - assert_eq!(all_voters().len(), 3); - assert_eq!(Elections::candidates().len(), 0); - }); + assert_eq!(members_ids(), vec![3, 4]); + assert_eq!(runners_up_ids().len(), 0); + assert_eq!(all_voters().len(), 3); + assert_eq!(Elections::candidates().len(), 0); + }); } #[test] fn dupe_vote_is_moot() { - ExtBuilder::default() - .desired_members(1) - .build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(submit_candidacy(Origin::signed(4))); - assert_ok!(submit_candidacy(Origin::signed(3))); - assert_ok!(submit_candidacy(Origin::signed(2))); - assert_ok!(submit_candidacy(Origin::signed(1))); + ExtBuilder::default().desired_members(1).build_and_execute(|| { + assert_ok!(submit_candidacy(Origin::signed(5))); + assert_ok!(submit_candidacy(Origin::signed(4))); + assert_ok!(submit_candidacy(Origin::signed(3))); + assert_ok!(submit_candidacy(Origin::signed(2))); + assert_ok!(submit_candidacy(Origin::signed(1))); - // all these duplicate votes will not cause 2 to win. - assert_ok!(vote(Origin::signed(1), vec![2, 2, 2, 2], 5)); - assert_ok!(vote(Origin::signed(2), vec![2, 2, 2, 2], 20)); + // all these duplicate votes will not cause 2 to win. + assert_ok!(vote(Origin::signed(1), vec![2, 2, 2, 2], 5)); + assert_ok!(vote(Origin::signed(2), vec![2, 2, 2, 2], 20)); - assert_ok!(vote(Origin::signed(3), vec![3], 30)); + assert_ok!(vote(Origin::signed(3), vec![3], 30)); - System::set_block_number(5); - Elections::on_initialize(System::block_number()); + System::set_block_number(5); + Elections::on_initialize(System::block_number()); - assert_eq!(members_ids(), vec![3]); - }) + assert_eq!(members_ids(), vec![3]); + }) + } + + #[test] + fn remove_defunct_voter_works() { + ExtBuilder::default().build_and_execute(|| { + assert_ok!(submit_candidacy(Origin::signed(5))); + assert_ok!(submit_candidacy(Origin::signed(4))); + assert_ok!(submit_candidacy(Origin::signed(3))); + + // defunct + assert_ok!(vote(Origin::signed(5), vec![5, 4], 5)); + // defunct + assert_ok!(vote(Origin::signed(4), vec![4], 5)); + // ok + assert_ok!(vote(Origin::signed(3), vec![3], 5)); + // ok + assert_ok!(vote(Origin::signed(2), vec![3, 4], 5)); + + assert_ok!(Elections::renounce_candidacy(Origin::signed(5), Renouncing::Candidate(3))); + assert_ok!(Elections::renounce_candidacy(Origin::signed(4), Renouncing::Candidate(2))); + assert_ok!(Elections::renounce_candidacy(Origin::signed(3), Renouncing::Candidate(1))); + + assert_ok!(Elections::clean_defunct_voters(Origin::root(), 4, 2)); + }) } } diff --git a/frame/proxy/src/lib.rs b/frame/proxy/src/lib.rs index 62424e6645594..1e5aaadcc62d3 100644 --- a/frame/proxy/src/lib.rs +++ b/frame/proxy/src/lib.rs @@ -218,8 +218,7 @@ decl_module! { /// Dispatch the given `call` from an account that the sender is authorised for through /// `add_proxy`. /// - /// This can only be used for proxies that are set with no delay. Removes any corresponding - /// announcement(s). + /// Removes any corresponding announcement(s). /// /// The dispatch origin for this call must be _Signed_. /// From ba12aecac83e2b87336119f68b2cc6ca83f94246 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 20 Jan 2021 11:32:05 +0000 Subject: [PATCH 67/69] Clean migrations --- .../src/migrations_3_0_0.rs | 40 +++++++++---------- .../procedural/src/construct_runtime/mod.rs | 4 +- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/frame/elections-phragmen/src/migrations_3_0_0.rs b/frame/elections-phragmen/src/migrations_3_0_0.rs index e896b5037e064..0737a12207c11 100644 --- a/frame/elections-phragmen/src/migrations_3_0_0.rs +++ b/frame/elections-phragmen/src/migrations_3_0_0.rs @@ -93,26 +93,27 @@ type Voting = StorageMap<__Voting, Twox64Concat, T::AccountId, Voter< /// /// Be aware that this migration is intended to be used only for the mentioned versions. Use /// with care and run at your own risk. -pub fn apply( - old_voter_bond: T::Balance, - old_candidacy_bond: T::Balance, -) -> Weight { +pub fn apply(old_voter_bond: T::Balance, old_candidacy_bond: T::Balance) -> Weight { let maybe_storage_version = ::storage_version(); + frame_support::debug::info!( + "Running migration for elections-phragmen with storage version {:?}", + maybe_storage_version + ); match maybe_storage_version { Some(storage_version) if storage_version <= PalletVersion::new(2, 0, 0) => { migrate_voters_to_recorded_deposit::(old_voter_bond); - migrate_candidates_to_recorded_deposit::( - old_candidacy_bond, - ); - migrate_runners_up_to_recorded_deposit::( - old_candidacy_bond, - ); - migrate_members_to_recorded_deposit::( - old_candidacy_bond, - ); + migrate_candidates_to_recorded_deposit::(old_candidacy_bond); + migrate_runners_up_to_recorded_deposit::(old_candidacy_bond); + migrate_members_to_recorded_deposit::(old_candidacy_bond); Weight::max_value() } - _ => 0, + _ => { + frame_support::debug::warn!( + "Attempted to apply migration to V3 but failed because storage version is {:?}", + maybe_storage_version + ); + 0 + }, } } @@ -120,11 +121,10 @@ pub fn apply( pub fn migrate_voters_to_recorded_deposit(old_deposit: T::Balance) { >::translate::<(T::Balance, Vec), _>( |_who, (stake, votes)| { - let deposit = old_deposit; Some(Voter { votes, stake, - deposit, + deposit: old_deposit, }) }, ); @@ -154,7 +154,7 @@ pub fn migrate_candidates_to_recorded_deposit(old_deposit: T::Balance } /// Migrate all members to recorded deposit. -pub fn migrate_members_to_recorded_deposit(deposit: T::Balance) { +pub fn migrate_members_to_recorded_deposit(old_deposit: T::Balance) { let _ = >::translate::, _>( |maybe_old_members| { maybe_old_members.map(|old_members| { @@ -164,7 +164,7 @@ pub fn migrate_members_to_recorded_deposit(deposit: T::Balance) { .map(|(who, stake)| SeatHolder { who, stake, - deposit, + deposit: old_deposit, }) .collect::>() }) @@ -173,7 +173,7 @@ pub fn migrate_members_to_recorded_deposit(deposit: T::Balance) { } /// Migrate all runners-up to recorded deposit. -pub fn migrate_runners_up_to_recorded_deposit(deposit: T::Balance) { +pub fn migrate_runners_up_to_recorded_deposit(old_deposit: T::Balance) { let _ = >::translate::, _>( |maybe_old_runners_up| { maybe_old_runners_up.map(|old_runners_up| { @@ -186,7 +186,7 @@ pub fn migrate_runners_up_to_recorded_deposit(deposit: T::Balance) { .map(|(who, stake)| SeatHolder { who, stake, - deposit, + deposit: old_deposit, }) .collect::>() }) diff --git a/frame/support/procedural/src/construct_runtime/mod.rs b/frame/support/procedural/src/construct_runtime/mod.rs index fc799c923b0ba..8a1b21dd74180 100644 --- a/frame/support/procedural/src/construct_runtime/mod.rs +++ b/frame/support/procedural/src/construct_runtime/mod.rs @@ -461,8 +461,8 @@ fn decl_all_modules<'a>( quote!( #types - type AllModules = ( #all_modules ); - type AllModulesWithSystem = ( #all_modules_with_system ); + pub type AllModules = ( #all_modules ); + pub type AllModulesWithSystem = ( #all_modules_with_system ); ) } From 99f5fd21aefa2f75262c68f134f44a9cdcd7df60 Mon Sep 17 00:00:00 2001 From: Parity Benchmarking Bot Date: Wed, 20 Jan 2021 11:52:28 +0000 Subject: [PATCH 68/69] cargo run --release --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=pallet_elections_phragmen --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/elections-phragmen/src/weights.rs --template=./.maintain/frame-weight-template.hbs --- frame/elections-phragmen/src/weights.rs | 122 ++++++++++++------------ 1 file changed, 63 insertions(+), 59 deletions(-) diff --git a/frame/elections-phragmen/src/weights.rs b/frame/elections-phragmen/src/weights.rs index 3430312e5a171..25c2091408361 100644 --- a/frame/elections-phragmen/src/weights.rs +++ b/frame/elections-phragmen/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,8 +17,8 @@ //! Autogenerated weights for pallet_elections_phragmen //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 2.0.0 -//! DATE: 2020-12-17, STEPS: [50, ], REPEAT: 20, LOW RANGE: [], HIGH RANGE: [] +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 2.0.1 +//! DATE: 2021-01-20, STEPS: [50, ], REPEAT: 20, LOW RANGE: [], HIGH RANGE: [] //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 // Executed Command: @@ -62,80 +62,82 @@ pub trait WeightInfo { pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { fn vote_equal(v: u32, ) -> Weight { - (61_124_000 as Weight) - // Standard Error: 7_000 - .saturating_add((408_000 as Weight).saturating_mul(v as Weight)) + (45_157_000 as Weight) + // Standard Error: 6_000 + .saturating_add((399_000 as Weight).saturating_mul(v as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } fn vote_more(v: u32, ) -> Weight { - (93_154_000 as Weight) - // Standard Error: 9_000 - .saturating_add((451_000 as Weight).saturating_mul(v as Weight)) + (69_738_000 as Weight) + // Standard Error: 14_000 + .saturating_add((450_000 as Weight).saturating_mul(v as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } fn vote_less(v: u32, ) -> Weight { - (88_123_000 as Weight) - // Standard Error: 11_000 - .saturating_add((453_000 as Weight).saturating_mul(v as Weight)) + (73_955_000 as Weight) + // Standard Error: 38_000 + .saturating_add((227_000 as Weight).saturating_mul(v as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } fn remove_voter() -> Weight { - (82_228_000 as Weight) + (68_398_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } fn submit_candidacy(c: u32, ) -> Weight { - (74_924_000 as Weight) - // Standard Error: 1_000 - .saturating_add((441_000 as Weight).saturating_mul(c as Weight)) + (59_291_000 as Weight) + // Standard Error: 2_000 + .saturating_add((412_000 as Weight).saturating_mul(c as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } fn renounce_candidacy_candidate(c: u32, ) -> Weight { - (64_985_000 as Weight) - // Standard Error: 1_000 - .saturating_add((219_000 as Weight).saturating_mul(c as Weight)) + (55_026_000 as Weight) + // Standard Error: 2_000 + .saturating_add((207_000 as Weight).saturating_mul(c as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } fn renounce_candidacy_members() -> Weight { - (96_491_000 as Weight) + (77_840_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } fn renounce_candidacy_runners_up() -> Weight { - (64_584_000 as Weight) + (54_559_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } fn remove_member_with_replacement() -> Weight { - (105_844_000 as Weight) + (84_311_000 as Weight) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } fn remove_member_wrong_refund() -> Weight { - (9_459_000 as Weight) + (7_677_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) } - fn clean_defunct_voters(v: u32, _d: u32, ) -> Weight { + fn clean_defunct_voters(v: u32, d: u32, ) -> Weight { (0 as Weight) - // Standard Error: 54_000 - .saturating_add((171_348_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 55_000 + .saturating_add((114_815_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 53_000 + .saturating_add((49_000 as Weight).saturating_mul(d as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().reads((3 as Weight).saturating_mul(v as Weight))) .saturating_add(T::DbWeight::get().writes((3 as Weight).saturating_mul(v as Weight))) } fn election_phragmen(c: u32, v: u32, e: u32, ) -> Weight { (0 as Weight) - // Standard Error: 1_954_000 - .saturating_add((47_765_000 as Weight).saturating_mul(c as Weight)) - // Standard Error: 812_000 - .saturating_add((71_025_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 1_940_000 + .saturating_add((43_557_000 as Weight).saturating_mul(c as Weight)) + // Standard Error: 807_000 + .saturating_add((65_849_000 as Weight).saturating_mul(v as Weight)) // Standard Error: 55_000 - .saturating_add((4_359_000 as Weight).saturating_mul(e as Weight)) + .saturating_add((4_206_000 as Weight).saturating_mul(e as Weight)) .saturating_add(T::DbWeight::get().reads((2 as Weight).saturating_mul(c as Weight))) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(v as Weight))) .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(c as Weight))) @@ -145,80 +147,82 @@ impl WeightInfo for SubstrateWeight { // For backwards compatibility and tests impl WeightInfo for () { fn vote_equal(v: u32, ) -> Weight { - (61_124_000 as Weight) - // Standard Error: 7_000 - .saturating_add((408_000 as Weight).saturating_mul(v as Weight)) + (45_157_000 as Weight) + // Standard Error: 6_000 + .saturating_add((399_000 as Weight).saturating_mul(v as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } fn vote_more(v: u32, ) -> Weight { - (93_154_000 as Weight) - // Standard Error: 9_000 - .saturating_add((451_000 as Weight).saturating_mul(v as Weight)) + (69_738_000 as Weight) + // Standard Error: 14_000 + .saturating_add((450_000 as Weight).saturating_mul(v as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } fn vote_less(v: u32, ) -> Weight { - (88_123_000 as Weight) - // Standard Error: 11_000 - .saturating_add((453_000 as Weight).saturating_mul(v as Weight)) + (73_955_000 as Weight) + // Standard Error: 38_000 + .saturating_add((227_000 as Weight).saturating_mul(v as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } fn remove_voter() -> Weight { - (82_228_000 as Weight) + (68_398_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } fn submit_candidacy(c: u32, ) -> Weight { - (74_924_000 as Weight) - // Standard Error: 1_000 - .saturating_add((441_000 as Weight).saturating_mul(c as Weight)) + (59_291_000 as Weight) + // Standard Error: 2_000 + .saturating_add((412_000 as Weight).saturating_mul(c as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } fn renounce_candidacy_candidate(c: u32, ) -> Weight { - (64_985_000 as Weight) - // Standard Error: 1_000 - .saturating_add((219_000 as Weight).saturating_mul(c as Weight)) + (55_026_000 as Weight) + // Standard Error: 2_000 + .saturating_add((207_000 as Weight).saturating_mul(c as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } fn renounce_candidacy_members() -> Weight { - (96_491_000 as Weight) + (77_840_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } fn renounce_candidacy_runners_up() -> Weight { - (64_584_000 as Weight) + (54_559_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } fn remove_member_with_replacement() -> Weight { - (105_844_000 as Weight) + (84_311_000 as Weight) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } fn remove_member_wrong_refund() -> Weight { - (9_459_000 as Weight) + (7_677_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) } - fn clean_defunct_voters(v: u32, _d: u32, ) -> Weight { + fn clean_defunct_voters(v: u32, d: u32, ) -> Weight { (0 as Weight) - // Standard Error: 54_000 - .saturating_add((171_348_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 55_000 + .saturating_add((114_815_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 53_000 + .saturating_add((49_000 as Weight).saturating_mul(d as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().reads((3 as Weight).saturating_mul(v as Weight))) .saturating_add(RocksDbWeight::get().writes((3 as Weight).saturating_mul(v as Weight))) } fn election_phragmen(c: u32, v: u32, e: u32, ) -> Weight { (0 as Weight) - // Standard Error: 1_954_000 - .saturating_add((47_765_000 as Weight).saturating_mul(c as Weight)) - // Standard Error: 812_000 - .saturating_add((71_025_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 1_940_000 + .saturating_add((43_557_000 as Weight).saturating_mul(c as Weight)) + // Standard Error: 807_000 + .saturating_add((65_849_000 as Weight).saturating_mul(v as Weight)) // Standard Error: 55_000 - .saturating_add((4_359_000 as Weight).saturating_mul(e as Weight)) + .saturating_add((4_206_000 as Weight).saturating_mul(e as Weight)) .saturating_add(RocksDbWeight::get().reads((2 as Weight).saturating_mul(c as Weight))) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(v as Weight))) .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(c as Weight))) From 92dbabef40605b0a758dd45f98c24a30bd63648c Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 20 Jan 2021 13:38:31 +0000 Subject: [PATCH 69/69] Revert unwanted change to construct-runtime --- frame/support/procedural/src/construct_runtime/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/support/procedural/src/construct_runtime/mod.rs b/frame/support/procedural/src/construct_runtime/mod.rs index 8a1b21dd74180..fc799c923b0ba 100644 --- a/frame/support/procedural/src/construct_runtime/mod.rs +++ b/frame/support/procedural/src/construct_runtime/mod.rs @@ -461,8 +461,8 @@ fn decl_all_modules<'a>( quote!( #types - pub type AllModules = ( #all_modules ); - pub type AllModulesWithSystem = ( #all_modules_with_system ); + type AllModules = ( #all_modules ); + type AllModulesWithSystem = ( #all_modules_with_system ); ) }