mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-12 04:11:07 +00:00
Offchain Phragmén BREAKING. (#4517)
* Initial skeleton for offchain phragmen * Basic compact encoding decoding for results * add compact files * Bring back Self::ensure_storage_upgraded(); * Make staking use compact stuff. * First seemingly working version of reduce, full of todos * Everything phragmen related works again. * Signing made easier, still issues. * Signing from offchain compile fine 😎 * make compact work with staked asssignment * Evaluation basics are in place. * Move reduce into crate. Document stuff * move reduce into no_std * Add files * Remove other std deps. Runtime compiles * Seemingly it is al stable; cycle implemented but not integrated. * Add fuzzing code. * Cleanup reduce a bit more. * a metric ton of tests for staking; wip 🔨 * Implement a lot more of the tests. * wip getting the unsigned stuff to work * A bit gleanup for unsigned debug * Clean and finalize compact code. * Document reduce. * Still problems with signing * We officaly duct taped the transaction submission stuff. 🤓 * Deadlock with keys again * Runtime builds * Unsigned test works 🙌 * Some cleanups * Make all the tests compile and stuff * Minor cleanup * fix more merge stuff * Most tests work again. * a very nasty bug in reduce * Fix all integrations * Fix more todos * Revamp everything and everything * Remove bogus test * Some review grumbles. * Some fixes * Fix doc test * loop for submission * Fix cli, keyring etc. * some cleanup * Fix staking tests again * fix per-things; bring patches from benchmarking * better score prediction * Add fuzzer, more patches. * Some fixes * More docs * Remove unused generics * Remove max-nominator footgun * Better fuzzer * Disable it ❌ * Bump. * Another round of self-review * Refactor a lot * More major fixes in perThing * Add new fuzz file * Update lock * fix fuzzing code. * Fix nominator retain test * Add slashing check * Update frame/staking/src/tests.rs Co-Authored-By: Joshy Orndorff <JoshOrndorff@users.noreply.github.com> * Some formatting nits * Review comments. * Fix cargo file * Almost all tests work again * Update frame/staking/src/tests.rs Co-Authored-By: thiolliere <gui.thiolliere@gmail.com> * Fix review comments * More review stuff * Some nits * Fix new staking / session / babe relation * Update primitives/phragmen/src/lib.rs Co-Authored-By: thiolliere <gui.thiolliere@gmail.com> * Update primitives/phragmen/src/lib.rs Co-Authored-By: thiolliere <gui.thiolliere@gmail.com> * Update primitives/phragmen/compact/src/lib.rs Co-Authored-By: thiolliere <gui.thiolliere@gmail.com> * Some doc updates to slashing * Fix derive * Remove imports * Remove unimplemented tests * nits * Remove dbg * Better fuzzing params * Remove unused pref map * Deferred Slashing/Offence for offchain Phragmen (#5151) * Some boilerplate * Add test * One more test * Review comments * Fix build * review comments * fix more * fix build * Some cleanups and self-reviews * More minor self reviews * Final nits * Some merge fixes. * opt comment * Fix build * Fix build again. * Update frame/staking/fuzz/fuzz_targets/submit_solution.rs Co-Authored-By: Gavin Wood <gavin@parity.io> * Update frame/staking/src/slashing.rs Co-Authored-By: Gavin Wood <gavin@parity.io> * Update frame/staking/src/offchain_election.rs Co-Authored-By: Gavin Wood <gavin@parity.io> * Fix review comments * fix test * === 🔑 Revamp without staking key. * final round of changes. * Fix cargo-deny * Update frame/staking/src/lib.rs Co-Authored-By: Gavin Wood <gavin@parity.io> Co-authored-by: Joshy Orndorff <JoshOrndorff@users.noreply.github.com> Co-authored-by: thiolliere <gui.thiolliere@gmail.com> Co-authored-by: Gavin Wood <gavin@parity.io>
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
// Copyright 2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Helper methods for phragmen.
|
||||
|
||||
use crate::{Assignment, ExtendedBalance, IdentifierT, StakedAssignment};
|
||||
use sp_runtime::PerThing;
|
||||
use sp_std::prelude::*;
|
||||
|
||||
/// Converts a vector of ratio assignments into ones with absolute budget value.
|
||||
pub fn assignment_ratio_to_staked<A: IdentifierT, T: PerThing, FS>(
|
||||
ratio: Vec<Assignment<A, T>>,
|
||||
stake_of: FS,
|
||||
) -> Vec<StakedAssignment<A>>
|
||||
where
|
||||
for<'r> FS: Fn(&'r A) -> ExtendedBalance,
|
||||
T: sp_std::ops::Mul<ExtendedBalance, Output = ExtendedBalance>,
|
||||
ExtendedBalance: From<<T as PerThing>::Inner>,
|
||||
{
|
||||
ratio
|
||||
.into_iter()
|
||||
.map(|a| {
|
||||
let stake = stake_of(&a.who);
|
||||
a.into_staked(stake, true)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Converts a vector of staked assignments into ones with ratio values.
|
||||
pub fn assignment_staked_to_ratio<A: IdentifierT, T: PerThing>(
|
||||
ratio: Vec<StakedAssignment<A>>,
|
||||
) -> Vec<Assignment<A, T>>
|
||||
where
|
||||
ExtendedBalance: From<<T as PerThing>::Inner>,
|
||||
{
|
||||
ratio.into_iter().map(|a| a.into_assignment(true)).collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::ExtendedBalance;
|
||||
use sp_runtime::Perbill;
|
||||
|
||||
#[test]
|
||||
fn into_staked_works() {
|
||||
let ratio = vec![
|
||||
Assignment {
|
||||
who: 1u32,
|
||||
distribution: vec![
|
||||
(10u32, Perbill::from_fraction(0.5)),
|
||||
(20, Perbill::from_fraction(0.5)),
|
||||
],
|
||||
},
|
||||
Assignment {
|
||||
who: 2u32,
|
||||
distribution: vec![
|
||||
(10, Perbill::from_fraction(0.33)),
|
||||
(20, Perbill::from_fraction(0.67)),
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
let stake_of = |_: &u32| -> ExtendedBalance { 100u128 };
|
||||
let staked = assignment_ratio_to_staked(ratio, stake_of);
|
||||
|
||||
assert_eq!(
|
||||
staked,
|
||||
vec![
|
||||
StakedAssignment {
|
||||
who: 1u32,
|
||||
distribution: vec![(10u32, 50), (20, 50),]
|
||||
},
|
||||
StakedAssignment {
|
||||
who: 2u32,
|
||||
distribution: vec![(10u32, 33), (20, 67),]
|
||||
}
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -33,19 +33,60 @@
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use sp_std::{prelude::*, collections::btree_map::BTreeMap, convert::TryFrom};
|
||||
use sp_runtime::{
|
||||
PerThing, Rational128, RuntimeDebug,
|
||||
helpers_128bit::multiply_by_rational,
|
||||
};
|
||||
use sp_runtime::traits::{
|
||||
Zero, Convert, Member, AtLeast32Bit, SaturatedConversion, Bounded, Saturating,
|
||||
};
|
||||
use sp_std::{prelude::*, collections::btree_map::BTreeMap, fmt::Debug, cmp::Ordering, convert::TryFrom};
|
||||
use sp_runtime::{helpers_128bit::multiply_by_rational, PerThing, Rational128, RuntimeDebug, SaturatedConversion};
|
||||
use sp_runtime::traits::{Zero, Convert, Member, AtLeast32Bit, Saturating, Bounded};
|
||||
|
||||
#[cfg(test)]
|
||||
mod mock;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
#[cfg(feature = "std")]
|
||||
use serde::{Serialize, Deserialize};
|
||||
#[cfg(feature = "std")]
|
||||
use codec::{Encode, Decode};
|
||||
|
||||
mod node;
|
||||
mod reduce;
|
||||
mod helpers;
|
||||
|
||||
// re-export reduce stuff.
|
||||
pub use reduce::reduce;
|
||||
|
||||
// re-export the helpers.
|
||||
pub use helpers::*;
|
||||
|
||||
// re-export the compact macro, with the dependencies of the macro.
|
||||
#[doc(hidden)]
|
||||
pub use codec;
|
||||
#[doc(hidden)]
|
||||
pub use sp_runtime;
|
||||
|
||||
// re-export the compact solution type.
|
||||
pub use sp_phragmen_compact::generate_compact_solution_type;
|
||||
|
||||
/// A trait to limit the number of votes per voter. The generated compact type will implement this.
|
||||
pub trait VotingLimit {
|
||||
const LIMIT: usize;
|
||||
}
|
||||
|
||||
/// an aggregator trait for a generic type of a voter/target identifier. This usually maps to
|
||||
/// substrate's account id.
|
||||
pub trait IdentifierT: Clone + Eq + Default + Ord + Debug + codec::Codec {}
|
||||
|
||||
impl<T: Clone + Eq + Default + Ord + Debug + codec::Codec> IdentifierT for T {}
|
||||
|
||||
/// The errors that might occur in the this crate and compact.
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub enum Error {
|
||||
/// While going from compact to staked, the stake of all the edges has gone above the
|
||||
/// total and the last stake cannot be assigned.
|
||||
CompactStakeOverflow,
|
||||
/// The compact type has a voter who's number of targets is out of bound.
|
||||
CompactTargetOverflow,
|
||||
/// One of the index functions returned none.
|
||||
CompactInvalidIndex,
|
||||
}
|
||||
|
||||
/// A type in which performing operations on balances and stakes of candidates and voters are safe.
|
||||
///
|
||||
@@ -55,6 +96,9 @@ mod tests;
|
||||
/// Balance types converted to `ExtendedBalance` are referred to as `Votes`.
|
||||
pub type ExtendedBalance = u128;
|
||||
|
||||
/// The score of an assignment. This can be computed from the support map via [`evaluate_support`].
|
||||
pub type PhragmenScore = [ExtendedBalance; 3];
|
||||
|
||||
/// The denominator used for loads. Since votes are collected as u64, the smallest ratio that we
|
||||
/// might collect is `1/approval_stake` where approval stake is the sum of votes. Hence, some number
|
||||
/// bigger than u64::max_value() is needed. For maximum accuracy we simply use u128;
|
||||
@@ -62,11 +106,11 @@ const DEN: u128 = u128::max_value();
|
||||
|
||||
/// A candidate entity for phragmen election.
|
||||
#[derive(Clone, Default, RuntimeDebug)]
|
||||
pub struct Candidate<AccountId> {
|
||||
struct Candidate<AccountId> {
|
||||
/// Identifier.
|
||||
pub who: AccountId,
|
||||
who: AccountId,
|
||||
/// Intermediary value used to sort candidates.
|
||||
pub score: Rational128,
|
||||
score: Rational128,
|
||||
/// Sum of the stake of this candidate based on received votes.
|
||||
approval_stake: ExtendedBalance,
|
||||
/// Flag for being elected.
|
||||
@@ -75,7 +119,7 @@ pub struct Candidate<AccountId> {
|
||||
|
||||
/// A voter entity.
|
||||
#[derive(Clone, Default, RuntimeDebug)]
|
||||
pub struct Voter<AccountId> {
|
||||
struct Voter<AccountId> {
|
||||
/// Identifier.
|
||||
who: AccountId,
|
||||
/// List of candidates proposed by this voter.
|
||||
@@ -88,7 +132,7 @@ pub struct Voter<AccountId> {
|
||||
|
||||
/// A candidate being backed by a voter.
|
||||
#[derive(Clone, Default, RuntimeDebug)]
|
||||
pub struct Edge<AccountId> {
|
||||
struct Edge<AccountId> {
|
||||
/// Identifier.
|
||||
who: AccountId,
|
||||
/// Load of this vote.
|
||||
@@ -97,12 +141,6 @@ pub struct Edge<AccountId> {
|
||||
candidate_index: usize,
|
||||
}
|
||||
|
||||
/// Particular `AccountId` was backed by `T`-ratio of a nominator's stake.
|
||||
pub type PhragmenAssignment<AccountId, T> = (AccountId, T);
|
||||
|
||||
/// Particular `AccountId` was backed by `ExtendedBalance` of a nominator's stake.
|
||||
pub type PhragmenStakedAssignment<AccountId> = (AccountId, ExtendedBalance);
|
||||
|
||||
/// Final result of the phragmen election.
|
||||
#[derive(RuntimeDebug)]
|
||||
pub struct PhragmenResult<AccountId, T: PerThing> {
|
||||
@@ -111,7 +149,142 @@ pub struct PhragmenResult<AccountId, T: PerThing> {
|
||||
pub winners: Vec<(AccountId, ExtendedBalance)>,
|
||||
/// Individual assignments. for each tuple, the first elements is a voter and the second
|
||||
/// is the list of candidates that it supports.
|
||||
pub assignments: Vec<(AccountId, Vec<PhragmenAssignment<AccountId, T>>)>
|
||||
pub assignments: Vec<Assignment<AccountId, T>>,
|
||||
}
|
||||
|
||||
/// A voter's stake assignment among a set of targets, represented as ratios.
|
||||
#[derive(RuntimeDebug, Clone, Default)]
|
||||
#[cfg_attr(feature = "std", derive(PartialEq, Eq, Encode, Decode))]
|
||||
pub struct Assignment<AccountId, T: PerThing> {
|
||||
/// Voter's identifier
|
||||
pub who: AccountId,
|
||||
/// The distribution of the voter's stake.
|
||||
pub distribution: Vec<(AccountId, T)>,
|
||||
}
|
||||
|
||||
impl<AccountId, T: PerThing> Assignment<AccountId, T>
|
||||
where
|
||||
ExtendedBalance: From<<T as PerThing>::Inner>,
|
||||
{
|
||||
/// Convert from a ratio assignment into one with absolute values aka. [`StakedAssignment`].
|
||||
///
|
||||
/// It needs `stake` which is the total budget of the voter. If `fill` is set to true,
|
||||
/// it _tries_ to ensure that all the potential rounding errors are compensated and the
|
||||
/// distribution's sum is exactly equal to the total budget, by adding or subtracting the
|
||||
/// remainder from the last distribution.
|
||||
///
|
||||
/// If an edge ratio is [`Bounded::max_value()`], it is dropped. This edge can never mean
|
||||
/// anything useful.
|
||||
pub fn into_staked(self, stake: ExtendedBalance, fill: bool) -> StakedAssignment<AccountId>
|
||||
where
|
||||
T: sp_std::ops::Mul<ExtendedBalance, Output = ExtendedBalance>,
|
||||
{
|
||||
let mut sum: ExtendedBalance = Bounded::min_value();
|
||||
let mut distribution = self
|
||||
.distribution
|
||||
.into_iter()
|
||||
.filter_map(|(target, p)| {
|
||||
// if this ratio is zero, then skip it.
|
||||
if p == Bounded::min_value() {
|
||||
None
|
||||
} else {
|
||||
// NOTE: this mul impl will always round to the nearest number, so we might both
|
||||
// overflow and underflow.
|
||||
let distribution_stake = p * stake;
|
||||
// defensive only. We assume that balance cannot exceed extended balance.
|
||||
sum = sum.saturating_add(distribution_stake);
|
||||
Some((target, distribution_stake))
|
||||
}
|
||||
})
|
||||
.collect::<Vec<(AccountId, ExtendedBalance)>>();
|
||||
|
||||
if fill {
|
||||
// NOTE: we can do this better.
|
||||
// https://revs.runtime-revolution.com/getting-100-with-rounded-percentages-273ffa70252b
|
||||
if let Some(leftover) = stake.checked_sub(sum) {
|
||||
if let Some(last) = distribution.last_mut() {
|
||||
last.1 = last.1.saturating_add(leftover);
|
||||
}
|
||||
} else if let Some(excess) = sum.checked_sub(stake) {
|
||||
if let Some(last) = distribution.last_mut() {
|
||||
last.1 = last.1.saturating_sub(excess);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StakedAssignment {
|
||||
who: self.who,
|
||||
distribution,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A voter's stake assignment among a set of targets, represented as absolute values in the scale
|
||||
/// of [`ExtendedBalance`].
|
||||
#[derive(RuntimeDebug, Clone, Default)]
|
||||
#[cfg_attr(feature = "std", derive(PartialEq, Eq, Encode, Decode))]
|
||||
pub struct StakedAssignment<AccountId> {
|
||||
/// Voter's identifier
|
||||
pub who: AccountId,
|
||||
/// The distribution of the voter's stake.
|
||||
pub distribution: Vec<(AccountId, ExtendedBalance)>,
|
||||
}
|
||||
|
||||
impl<AccountId> StakedAssignment<AccountId> {
|
||||
/// Converts self into the normal [`Assignment`] type.
|
||||
///
|
||||
/// If `fill` is set to true, it _tries_ to ensure that all the potential rounding errors are
|
||||
/// compensated and the distribution's sum is exactly equal to 100%, by adding or subtracting
|
||||
/// the remainder from the last distribution.
|
||||
///
|
||||
/// NOTE: it is quite critical that this attempt always works. The data type returned here will
|
||||
/// potentially get used to create a compact type; a compact type requires sum of ratios to be
|
||||
/// less than 100% upon un-compacting.
|
||||
///
|
||||
/// If an edge stake is so small that it cannot be represented in `T`, it is ignored. This edge
|
||||
/// can never be re-created and does not mean anything useful anymore.
|
||||
pub fn into_assignment<T: PerThing>(self, fill: bool) -> Assignment<AccountId, T>
|
||||
where
|
||||
ExtendedBalance: From<<T as PerThing>::Inner>,
|
||||
{
|
||||
let accuracy: u128 = T::ACCURACY.saturated_into();
|
||||
let mut sum: u128 = Zero::zero();
|
||||
let stake = self.distribution.iter().map(|x| x.1).sum();
|
||||
let mut distribution = self
|
||||
.distribution
|
||||
.into_iter()
|
||||
.filter_map(|(target, w)| {
|
||||
let per_thing = T::from_rational_approximation(w, stake);
|
||||
if per_thing == Bounded::min_value() {
|
||||
None
|
||||
} else {
|
||||
sum += per_thing.clone().deconstruct().saturated_into();
|
||||
Some((target, per_thing))
|
||||
}
|
||||
})
|
||||
.collect::<Vec<(AccountId, T)>>();
|
||||
|
||||
if fill {
|
||||
if let Some(leftover) = accuracy.checked_sub(sum) {
|
||||
if let Some(last) = distribution.last_mut() {
|
||||
last.1 = last.1.saturating_add(
|
||||
T::from_parts(leftover.saturated_into())
|
||||
);
|
||||
}
|
||||
} else if let Some(excess) = sum.checked_sub(accuracy) {
|
||||
if let Some(last) = distribution.last_mut() {
|
||||
last.1 = last.1.saturating_sub(
|
||||
T::from_parts(excess.saturated_into())
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Assignment {
|
||||
who: self.who,
|
||||
distribution,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A structure to demonstrate the phragmen result from the perspective of the candidate, i.e. how
|
||||
@@ -122,12 +295,12 @@ pub struct PhragmenResult<AccountId, T: PerThing> {
|
||||
/// This, at the current version, resembles the `Exposure` defined in the Staking pallet, yet
|
||||
/// they do not necessarily have to be the same.
|
||||
#[derive(Default, RuntimeDebug)]
|
||||
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize, Eq, PartialEq))]
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Eq, PartialEq))]
|
||||
pub struct Support<AccountId> {
|
||||
/// Total support.
|
||||
pub total: ExtendedBalance,
|
||||
/// Support from voters.
|
||||
pub voters: Vec<PhragmenStakedAssignment<AccountId>>,
|
||||
pub voters: Vec<(AccountId, ExtendedBalance)>,
|
||||
}
|
||||
|
||||
/// A linkage from a candidate and its [`Support`].
|
||||
@@ -164,7 +337,7 @@ pub fn elect<AccountId, Balance, C, R>(
|
||||
|
||||
// return structures
|
||||
let mut elected_candidates: Vec<(AccountId, ExtendedBalance)>;
|
||||
let mut assigned: Vec<(AccountId, Vec<PhragmenAssignment<AccountId, R>>)>;
|
||||
let mut assigned: Vec<Assignment<AccountId, R>>;
|
||||
|
||||
// used to cache and access candidates index.
|
||||
let mut c_idx_cache = BTreeMap::<AccountId, usize>::new();
|
||||
@@ -271,7 +444,10 @@ pub fn elect<AccountId, Balance, C, R>(
|
||||
|
||||
// update backing stake of candidates and voters
|
||||
for n in &mut voters {
|
||||
let mut assignment = (n.who.clone(), vec![]);
|
||||
let mut assignment = Assignment {
|
||||
who: n.who.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
for e in &mut n.edges {
|
||||
if elected_candidates.iter().position(|(ref c, _)| *c == e.who).is_some() {
|
||||
let per_bill_parts: R::Inner =
|
||||
@@ -298,44 +474,45 @@ pub fn elect<AccountId, Balance, C, R>(
|
||||
// R::Inner.
|
||||
.unwrap_or(Bounded::max_value())
|
||||
} else {
|
||||
// defensive only. Both edge and nominator loads are built from
|
||||
// defensive only. Both edge and voter loads are built from
|
||||
// scores, hence MUST have the same denominator.
|
||||
Zero::zero()
|
||||
}
|
||||
}
|
||||
};
|
||||
let per_thing = R::from_parts(per_bill_parts);
|
||||
assignment.1.push((e.who.clone(), per_thing));
|
||||
assignment.distribution.push((e.who.clone(), per_thing));
|
||||
}
|
||||
}
|
||||
|
||||
if assignment.1.len() > 0 {
|
||||
// To ensure an assertion indicating: no stake from the nominator going to waste,
|
||||
let len = assignment.distribution.len();
|
||||
if len > 0 {
|
||||
// To ensure an assertion indicating: no stake from the voter going to waste,
|
||||
// we add a minimal post-processing to equally assign all of the leftover stake ratios.
|
||||
let vote_count: R::Inner = assignment.1.len().saturated_into();
|
||||
let len = assignment.1.len();
|
||||
let mut sum: R::Inner = Zero::zero();
|
||||
assignment.1.iter().for_each(|a| sum = sum.saturating_add(a.1.deconstruct()));
|
||||
let vote_count: R::Inner = len.saturated_into();
|
||||
let accuracy = R::ACCURACY;
|
||||
let mut sum: R::Inner = Zero::zero();
|
||||
assignment.distribution.iter().for_each(|a| sum = sum.saturating_add(a.1.deconstruct()));
|
||||
|
||||
let diff = accuracy.saturating_sub(sum);
|
||||
let diff_per_vote = (diff / vote_count).min(accuracy);
|
||||
|
||||
if !diff_per_vote.is_zero() {
|
||||
for i in 0..len {
|
||||
let current_ratio = assignment.1[i % len].1;
|
||||
let current_ratio = assignment.distribution[i % len].1;
|
||||
let next_ratio = current_ratio
|
||||
.saturating_add(R::from_parts(diff_per_vote));
|
||||
assignment.1[i % len].1 = next_ratio;
|
||||
assignment.distribution[i % len].1 = next_ratio;
|
||||
}
|
||||
}
|
||||
|
||||
// `remainder` is set to be less than maximum votes of a nominator (currently 16).
|
||||
// `remainder` is set to be less than maximum votes of a voter (currently 16).
|
||||
// safe to cast it to usize.
|
||||
let remainder = diff - diff_per_vote * vote_count;
|
||||
for i in 0..remainder.saturated_into::<usize>() {
|
||||
let current_ratio = assignment.1[i % len].1;
|
||||
let current_ratio = assignment.distribution[i % len].1;
|
||||
let next_ratio = current_ratio.saturating_add(R::from_parts(1u8.into()));
|
||||
assignment.1[i % len].1 = next_ratio;
|
||||
assignment.distribution[i % len].1 = next_ratio;
|
||||
}
|
||||
assigned.push(assignment);
|
||||
}
|
||||
@@ -347,39 +524,109 @@ pub fn elect<AccountId, Balance, C, R>(
|
||||
})
|
||||
}
|
||||
|
||||
/// Build the support map from the given phragmen result.
|
||||
pub fn build_support_map<Balance, AccountId, FS, C, R>(
|
||||
elected_stashes: &Vec<AccountId>,
|
||||
assignments: &Vec<(AccountId, Vec<PhragmenAssignment<AccountId, R>>)>,
|
||||
stake_of: FS,
|
||||
) -> SupportMap<AccountId> where
|
||||
/// Build the support map from the given phragmen result. It maps a flat structure like
|
||||
///
|
||||
/// ```nocompile
|
||||
/// assignments: vec![
|
||||
/// voter1, vec![(candidate1, w11), (candidate2, w12)],
|
||||
/// voter2, vec![(candidate1, w21), (candidate2, w22)]
|
||||
/// ]
|
||||
/// ```
|
||||
///
|
||||
/// into a mapping of candidates and their respective support:
|
||||
///
|
||||
/// ```nocompile
|
||||
/// SupportMap {
|
||||
/// candidate1: Support {
|
||||
/// own:0,
|
||||
/// total: w11 + w21,
|
||||
/// others: vec![(candidate1, w11), (candidate2, w21)]
|
||||
/// },
|
||||
/// candidate2: Support {
|
||||
/// own:0,
|
||||
/// total: w12 + w22,
|
||||
/// others: vec![(candidate1, w12), (candidate2, w22)]
|
||||
/// },
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// The second returned flag indicates the number of edges who didn't corresponded to an actual
|
||||
/// winner from the given winner set. A value in this place larger than 0 indicates a potentially
|
||||
/// faulty assignment.
|
||||
///
|
||||
/// `O(E)` where `E` is the total number of edges.
|
||||
pub fn build_support_map<AccountId>(
|
||||
winners: &[AccountId],
|
||||
assignments: &[StakedAssignment<AccountId>],
|
||||
) -> (SupportMap<AccountId>, u32) where
|
||||
AccountId: Default + Ord + Member,
|
||||
Balance: Default + Copy + AtLeast32Bit,
|
||||
C: Convert<Balance, u64> + Convert<u128, Balance>,
|
||||
for<'r> FS: Fn(&'r AccountId) -> Balance,
|
||||
R: PerThing + sp_std::ops::Mul<ExtendedBalance, Output=ExtendedBalance>,
|
||||
{
|
||||
let to_votes = |b: Balance| <C as Convert<Balance, u64>>::convert(b) as ExtendedBalance;
|
||||
let mut errors = 0;
|
||||
// Initialize the support of each candidate.
|
||||
let mut supports = <SupportMap<AccountId>>::new();
|
||||
elected_stashes
|
||||
winners
|
||||
.iter()
|
||||
.for_each(|e| { supports.insert(e.clone(), Default::default()); });
|
||||
|
||||
// build support struct.
|
||||
for (n, assignment) in assignments.iter() {
|
||||
for (c, per_thing) in assignment.iter() {
|
||||
let nominator_stake = to_votes(stake_of(n));
|
||||
// AUDIT: it is crucially important for the `Mul` implementation of all
|
||||
// per-things to be sound.
|
||||
let other_stake = *per_thing * nominator_stake;
|
||||
for StakedAssignment { who, distribution } in assignments.iter() {
|
||||
for (c, weight_extended) in distribution.iter() {
|
||||
if let Some(support) = supports.get_mut(c) {
|
||||
support.voters.push((n.clone(), other_stake));
|
||||
support.total = support.total.saturating_add(other_stake);
|
||||
support.total = support.total.saturating_add(*weight_extended);
|
||||
support.voters.push((who.clone(), *weight_extended));
|
||||
} else {
|
||||
errors = errors.saturating_add(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
supports
|
||||
(supports, errors)
|
||||
}
|
||||
|
||||
/// Evaluate a phragmen result, given the support map. The returned tuple contains:
|
||||
///
|
||||
/// - Minimum support. This value must be **maximized**.
|
||||
/// - Sum of all supports. This value must be **maximized**.
|
||||
/// - Sum of all supports squared. This value must be **minimized**.
|
||||
///
|
||||
/// `O(E)` where `E` is the total number of edges.
|
||||
pub fn evaluate_support<AccountId>(
|
||||
support: &SupportMap<AccountId>,
|
||||
) -> PhragmenScore {
|
||||
let mut min_support = ExtendedBalance::max_value();
|
||||
let mut sum: ExtendedBalance = Zero::zero();
|
||||
// NOTE: this will probably saturate but using big num makes it even slower. We'll have to see.
|
||||
// This must run on chain..
|
||||
let mut sum_squared: ExtendedBalance = Zero::zero();
|
||||
for (_, support) in support.iter() {
|
||||
sum += support.total;
|
||||
let squared = support.total.saturating_mul(support.total);
|
||||
sum_squared = sum_squared.saturating_add(squared);
|
||||
if support.total < min_support {
|
||||
min_support = support.total;
|
||||
}
|
||||
}
|
||||
[min_support, sum, sum_squared]
|
||||
}
|
||||
|
||||
/// Compares two sets of phragmen scores based on desirability and returns true if `that` is
|
||||
/// better than `this`.
|
||||
///
|
||||
/// Evaluation is done in a lexicographic manner.
|
||||
///
|
||||
/// Note that the third component should be minimized.
|
||||
pub fn is_score_better(this: PhragmenScore, that: PhragmenScore) -> bool {
|
||||
match that
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, e)| e.cmp(&this[i]))
|
||||
.collect::<Vec<Ordering>>()
|
||||
.as_slice()
|
||||
{
|
||||
[Ordering::Greater, _, _] => true,
|
||||
[Ordering::Equal, Ordering::Greater, _] => true,
|
||||
[Ordering::Equal, Ordering::Equal, Ordering::Less] => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs equalize post-processing to the output of the election algorithm. This happens in
|
||||
@@ -388,13 +635,13 @@ pub fn build_support_map<Balance, AccountId, FS, C, R>(
|
||||
///
|
||||
/// No value is returned from the function and the `supports` parameter is updated.
|
||||
///
|
||||
/// * `assignments`: exactly the same is the output of phragmen.
|
||||
/// * `supports`: mutable reference to s `SupportMap`. This parameter is updated.
|
||||
/// * `tolerance`: maximum difference that can occur before an early quite happens.
|
||||
/// * `iterations`: maximum number of iterations that will be processed.
|
||||
/// * `stake_of`: something that can return the stake stake of a particular candidate or voter.
|
||||
/// - `assignments`: exactly the same is the output of phragmen.
|
||||
/// - `supports`: mutable reference to s `SupportMap`. This parameter is updated.
|
||||
/// - `tolerance`: maximum difference that can occur before an early quite happens.
|
||||
/// - `iterations`: maximum number of iterations that will be processed.
|
||||
/// - `stake_of`: something that can return the stake stake of a particular candidate or voter.
|
||||
pub fn equalize<Balance, AccountId, C, FS>(
|
||||
mut assignments: Vec<(AccountId, Vec<PhragmenStakedAssignment<AccountId>>)>,
|
||||
mut assignments: Vec<StakedAssignment<AccountId>>,
|
||||
supports: &mut SupportMap<AccountId>,
|
||||
tolerance: ExtendedBalance,
|
||||
iterations: usize,
|
||||
@@ -408,13 +655,13 @@ pub fn equalize<Balance, AccountId, C, FS>(
|
||||
for _i in 0..iterations {
|
||||
let mut max_diff = 0;
|
||||
|
||||
for (voter, assignment) in assignments.iter_mut() {
|
||||
let voter_budget = stake_of(&voter);
|
||||
for StakedAssignment { who, distribution } in assignments.iter_mut() {
|
||||
let voter_budget = stake_of(&who);
|
||||
|
||||
let diff = do_equalize::<_, _, C>(
|
||||
voter,
|
||||
who,
|
||||
voter_budget,
|
||||
assignment,
|
||||
distribution,
|
||||
supports,
|
||||
tolerance,
|
||||
);
|
||||
@@ -432,7 +679,7 @@ pub fn equalize<Balance, AccountId, C, FS>(
|
||||
fn do_equalize<Balance, AccountId, C>(
|
||||
voter: &AccountId,
|
||||
budget_balance: Balance,
|
||||
elected_edges: &mut Vec<PhragmenStakedAssignment<AccountId>>,
|
||||
elected_edges: &mut Vec<(AccountId, ExtendedBalance)>,
|
||||
support_map: &mut SupportMap<AccountId>,
|
||||
tolerance: ExtendedBalance
|
||||
) -> ExtendedBalance where
|
||||
|
||||
@@ -18,10 +18,10 @@
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use crate::{elect, PhragmenResult, PhragmenAssignment};
|
||||
use crate::{elect, PhragmenResult, Assignment};
|
||||
use sp_runtime::{
|
||||
assert_eq_error_rate, Perbill, PerThing,
|
||||
traits::{Convert, Member, SaturatedConversion}
|
||||
assert_eq_error_rate, PerThing,
|
||||
traits::{Convert, Member, SaturatedConversion, Zero, One}
|
||||
};
|
||||
use sp_std::collections::btree_map::BTreeMap;
|
||||
|
||||
@@ -320,22 +320,23 @@ pub(crate) fn create_stake_of(stakes: &[(AccountId, Balance)])
|
||||
}
|
||||
|
||||
|
||||
pub fn check_assignments(assignments: Vec<(AccountId, Vec<PhragmenAssignment<AccountId, Perbill>>)>) {
|
||||
for (_, a) in assignments {
|
||||
let sum: u32 = a.iter().map(|(_, p)| p.deconstruct()).sum();
|
||||
assert_eq_error_rate!(sum, Perbill::ACCURACY, 5);
|
||||
pub fn check_assignments_sum<T: PerThing>(assignments: Vec<Assignment<AccountId, T>>) {
|
||||
for Assignment { distribution, .. } in assignments {
|
||||
let mut sum: u128 = Zero::zero();
|
||||
distribution.iter().for_each(|(_, p)| sum += p.deconstruct().saturated_into());
|
||||
assert_eq_error_rate!(sum, T::ACCURACY.saturated_into(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn run_and_compare(
|
||||
pub(crate) fn run_and_compare<Output: PerThing>(
|
||||
candidates: Vec<AccountId>,
|
||||
voters: Vec<(AccountId, Vec<AccountId>)>,
|
||||
stake_of: Box<dyn Fn(&AccountId) -> Balance>,
|
||||
stake_of: &Box<dyn Fn(&AccountId) -> Balance>,
|
||||
to_elect: usize,
|
||||
min_to_elect: usize,
|
||||
) {
|
||||
// run fixed point code.
|
||||
let PhragmenResult { winners, assignments } = elect::<_, _, TestCurrencyToVote, Perbill>(
|
||||
let PhragmenResult { winners, assignments } = elect::<_, _, TestCurrencyToVote, Output>(
|
||||
to_elect,
|
||||
min_to_elect,
|
||||
candidates.clone(),
|
||||
@@ -353,14 +354,14 @@ pub(crate) fn run_and_compare(
|
||||
|
||||
assert_eq!(winners, truth_value.winners);
|
||||
|
||||
for (nominator, assigned) in assignments.clone() {
|
||||
if let Some(float_assignments) = truth_value.assignments.iter().find(|x| x.0 == nominator) {
|
||||
for (candidate, per_thingy) in assigned {
|
||||
for Assignment { who, distribution } in assignments.clone() {
|
||||
if let Some(float_assignments) = truth_value.assignments.iter().find(|x| x.0 == who) {
|
||||
for (candidate, per_thingy) in distribution {
|
||||
if let Some(float_assignment) = float_assignments.1.iter().find(|x| x.0 == candidate ) {
|
||||
assert_eq_error_rate!(
|
||||
Perbill::from_fraction(float_assignment.1).deconstruct(),
|
||||
Output::from_fraction(float_assignment.1).deconstruct(),
|
||||
per_thingy.deconstruct(),
|
||||
1,
|
||||
Output::Inner::one(),
|
||||
);
|
||||
} else {
|
||||
panic!("candidate mismatch. This should never happen.")
|
||||
@@ -371,7 +372,7 @@ pub(crate) fn run_and_compare(
|
||||
}
|
||||
}
|
||||
|
||||
check_assignments(assignments);
|
||||
check_assignments_sum(assignments);
|
||||
}
|
||||
|
||||
pub(crate) fn build_support_map_float<FS>(
|
||||
|
||||
@@ -0,0 +1,287 @@
|
||||
// Copyright 2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! (very) Basic implementation of a graph node used in the reduce algorithm.
|
||||
|
||||
use sp_runtime::RuntimeDebug;
|
||||
use sp_std::{cell::RefCell, fmt, prelude::*, rc::Rc};
|
||||
|
||||
/// The role that a node can accept.
|
||||
#[derive(PartialEq, Eq, Ord, PartialOrd, Clone, RuntimeDebug)]
|
||||
pub(crate) enum NodeRole {
|
||||
/// A voter. This is synonym to a nominator in a staking context.
|
||||
Voter,
|
||||
/// A target. This is synonym to a candidate/validator in a staking context.
|
||||
Target,
|
||||
}
|
||||
|
||||
pub(crate) type RefCellOf<T> = Rc<RefCell<T>>;
|
||||
pub(crate) type NodeRef<A> = RefCellOf<Node<A>>;
|
||||
|
||||
/// Identifier of a node. This is particularly handy to have a proper `PartialEq` implementation.
|
||||
/// Otherwise, self votes wouldn't have been indistinguishable.
|
||||
#[derive(PartialOrd, Ord, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct NodeId<A> {
|
||||
/// An account-like identifier representing the node.
|
||||
pub who: A,
|
||||
/// The role of the node.
|
||||
pub role: NodeRole,
|
||||
}
|
||||
|
||||
impl<A> NodeId<A> {
|
||||
/// Create a new [`NodeId`].
|
||||
pub fn from(who: A, role: NodeRole) -> Self {
|
||||
Self { who, role }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<A: fmt::Debug> sp_std::fmt::Debug for NodeId<A> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> sp_std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"Node({:?}, {:?})",
|
||||
self.who,
|
||||
if self.role == NodeRole::Voter {
|
||||
"V"
|
||||
} else {
|
||||
"T"
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// A one-way graph note. This can only store a pointer to its parent.
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct Node<A> {
|
||||
/// The identifier of the note.
|
||||
pub(crate) id: NodeId<A>,
|
||||
/// The parent pointer.
|
||||
pub(crate) parent: Option<NodeRef<A>>,
|
||||
}
|
||||
|
||||
impl<A: PartialEq> PartialEq for Node<A> {
|
||||
fn eq(&self, other: &Node<A>) -> bool {
|
||||
self.id == other.id
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: PartialEq> Eq for Node<A> {}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<A: fmt::Debug + Clone> fmt::Debug for Node<A> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"({:?} --> {:?})",
|
||||
self.id,
|
||||
self.parent.as_ref().map(|p| p.borrow().id.clone())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: PartialEq + Eq + Clone + fmt::Debug> Node<A> {
|
||||
/// Create a new [`Node`]
|
||||
pub fn new(id: NodeId<A>) -> Node<A> {
|
||||
Self { id, parent: None }
|
||||
}
|
||||
|
||||
/// Returns true if `other` is the parent of `who`.
|
||||
pub fn is_parent_of(who: &NodeRef<A>, other: &NodeRef<A>) -> bool {
|
||||
if who.borrow().parent.is_none() {
|
||||
return false;
|
||||
}
|
||||
who.borrow().parent.as_ref() == Some(other)
|
||||
}
|
||||
|
||||
/// Removes the parent of `who`.
|
||||
pub fn remove_parent(who: &NodeRef<A>) {
|
||||
who.borrow_mut().parent = None;
|
||||
}
|
||||
|
||||
/// Sets `who`'s parent to be `parent`.
|
||||
pub fn set_parent_of(who: &NodeRef<A>, parent: &NodeRef<A>) {
|
||||
who.borrow_mut().parent = Some(parent.clone());
|
||||
}
|
||||
|
||||
/// Finds the root of `start`. It return a tuple of `(root, root_vec)` where `root_vec` is the
|
||||
/// vector of Nodes leading to the root. Hence the first element is the start itself and the
|
||||
/// last one is the root. As convenient, the root itself is also returned as the first element
|
||||
/// of the tuple.
|
||||
///
|
||||
/// This function detects cycles and breaks as soon a duplicate node is visited, returning the
|
||||
/// cycle up to but not including the duplicate node.
|
||||
///
|
||||
/// If you are certain that no cycles exist, you can use [`root_unchecked`].
|
||||
pub fn root(start: &NodeRef<A>) -> (NodeRef<A>, Vec<NodeRef<A>>) {
|
||||
let mut parent_path: Vec<NodeRef<A>> = Vec::new();
|
||||
let mut visited: Vec<NodeRef<A>> = Vec::new();
|
||||
|
||||
parent_path.push(start.clone());
|
||||
visited.push(start.clone());
|
||||
let mut current = start.clone();
|
||||
|
||||
while let Some(ref next_parent) = current.clone().borrow().parent {
|
||||
if visited.contains(next_parent) {
|
||||
break;
|
||||
}
|
||||
parent_path.push(next_parent.clone());
|
||||
current = next_parent.clone();
|
||||
visited.push(current.clone());
|
||||
}
|
||||
|
||||
(current, parent_path)
|
||||
}
|
||||
|
||||
/// Consumes self and wraps it in a `Rc<RefCell<T>>`. This type can be used as the pointer type
|
||||
/// to a parent node.
|
||||
pub fn into_ref(self) -> NodeRef<A> {
|
||||
Rc::from(RefCell::from(self))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn id(i: u32) -> NodeId<u32> {
|
||||
NodeId::from(i, NodeRole::Target)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_create_works() {
|
||||
let node = Node::new(id(10));
|
||||
assert_eq!(
|
||||
node,
|
||||
Node {
|
||||
id: NodeId {
|
||||
who: 10,
|
||||
role: NodeRole::Target
|
||||
},
|
||||
parent: None
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_parent_works() {
|
||||
let a = Node::new(id(10)).into_ref();
|
||||
let b = Node::new(id(20)).into_ref();
|
||||
|
||||
assert_eq!(a.borrow().parent, None);
|
||||
Node::set_parent_of(&a, &b);
|
||||
assert_eq!(*a.borrow().parent.as_ref().unwrap(), b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_root_singular() {
|
||||
let a = Node::new(id(1)).into_ref();
|
||||
assert_eq!(Node::root(&a), (a.clone(), vec![a.clone()]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_root_works() {
|
||||
// D <-- A <-- B <-- C
|
||||
// \
|
||||
// <-- E
|
||||
let a = Node::new(id(1)).into_ref();
|
||||
let b = Node::new(id(2)).into_ref();
|
||||
let c = Node::new(id(3)).into_ref();
|
||||
let d = Node::new(id(4)).into_ref();
|
||||
let e = Node::new(id(5)).into_ref();
|
||||
let f = Node::new(id(6)).into_ref();
|
||||
|
||||
Node::set_parent_of(&c, &b);
|
||||
Node::set_parent_of(&b, &a);
|
||||
Node::set_parent_of(&e, &a);
|
||||
Node::set_parent_of(&a, &d);
|
||||
|
||||
assert_eq!(
|
||||
Node::root(&e),
|
||||
(d.clone(), vec![e.clone(), a.clone(), d.clone()]),
|
||||
);
|
||||
|
||||
assert_eq!(Node::root(&a), (d.clone(), vec![a.clone(), d.clone()]),);
|
||||
|
||||
assert_eq!(
|
||||
Node::root(&c),
|
||||
(d.clone(), vec![c.clone(), b.clone(), a.clone(), d.clone()]),
|
||||
);
|
||||
|
||||
// D A <-- B <-- C
|
||||
// F <-- / \
|
||||
// <-- E
|
||||
Node::set_parent_of(&a, &f);
|
||||
|
||||
assert_eq!(Node::root(&a), (f.clone(), vec![a.clone(), f.clone()]),);
|
||||
|
||||
assert_eq!(
|
||||
Node::root(&c),
|
||||
(f.clone(), vec![c.clone(), b.clone(), a.clone(), f.clone()]),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_root_on_cycle() {
|
||||
// A ---> B
|
||||
// | |
|
||||
// <---- C
|
||||
let a = Node::new(id(1)).into_ref();
|
||||
let b = Node::new(id(2)).into_ref();
|
||||
let c = Node::new(id(3)).into_ref();
|
||||
|
||||
Node::set_parent_of(&a, &b);
|
||||
Node::set_parent_of(&b, &c);
|
||||
Node::set_parent_of(&c, &a);
|
||||
|
||||
let (root, path) = Node::root(&a);
|
||||
assert_eq!(root, c);
|
||||
assert_eq!(path.clone(), vec![a.clone(), b.clone(), c.clone()]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_root_on_cycle_2() {
|
||||
// A ---> B
|
||||
// | | |
|
||||
// - C
|
||||
let a = Node::new(id(1)).into_ref();
|
||||
let b = Node::new(id(2)).into_ref();
|
||||
let c = Node::new(id(3)).into_ref();
|
||||
|
||||
Node::set_parent_of(&a, &b);
|
||||
Node::set_parent_of(&b, &c);
|
||||
Node::set_parent_of(&c, &b);
|
||||
|
||||
let (root, path) = Node::root(&a);
|
||||
assert_eq!(root, c);
|
||||
assert_eq!(path.clone(), vec![a.clone(), b.clone(), c.clone()]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn node_cmp_stack_overflows_on_non_unique_elements() {
|
||||
// To make sure we don't stack overflow on duplicate who. This needs manual impl of
|
||||
// PartialEq.
|
||||
let a = Node::new(id(1)).into_ref();
|
||||
let b = Node::new(id(2)).into_ref();
|
||||
let c = Node::new(id(3)).into_ref();
|
||||
|
||||
Node::set_parent_of(&a, &b);
|
||||
Node::set_parent_of(&b, &c);
|
||||
Node::set_parent_of(&c, &a);
|
||||
|
||||
Node::root(&a);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -19,11 +19,12 @@
|
||||
#![cfg(test)]
|
||||
|
||||
use crate::mock::*;
|
||||
use crate::{elect, PhragmenResult, PhragmenStakedAssignment, build_support_map, Support, equalize};
|
||||
use crate::{
|
||||
elect, equalize, build_support_map, is_score_better,
|
||||
Support, StakedAssignment, Assignment, PhragmenResult, ExtendedBalance,
|
||||
};
|
||||
use substrate_test_utils::assert_eq_uvec;
|
||||
use sp_runtime::Perbill;
|
||||
|
||||
type Output = Perbill;
|
||||
use sp_runtime::{Perbill, Permill, Percent, PerU16, traits::Convert};
|
||||
|
||||
#[test]
|
||||
fn float_phragmen_poc_works() {
|
||||
@@ -81,7 +82,7 @@ fn phragmen_poc_works() {
|
||||
];
|
||||
|
||||
let stake_of = create_stake_of(&[(10, 10), (20, 20), (30, 30)]);
|
||||
let PhragmenResult { winners, assignments } = elect::<_, _, TestCurrencyToVote, Output>(
|
||||
let PhragmenResult { winners, assignments } = elect::<_, _, TestCurrencyToVote, Perbill>(
|
||||
2,
|
||||
2,
|
||||
candidates,
|
||||
@@ -92,9 +93,21 @@ fn phragmen_poc_works() {
|
||||
assert_eq_uvec!(
|
||||
assignments,
|
||||
vec![
|
||||
(10, vec![(2, Perbill::from_percent(100))]),
|
||||
(20, vec![(3, Perbill::from_percent(100))]),
|
||||
(30, vec![(2, Perbill::from_percent(100/2)), (3, Perbill::from_percent(100/2))]),
|
||||
Assignment {
|
||||
who: 10u64,
|
||||
distribution: vec![(2, Perbill::from_percent(100))],
|
||||
},
|
||||
Assignment {
|
||||
who: 20,
|
||||
distribution: vec![(3, Perbill::from_percent(100))],
|
||||
},
|
||||
Assignment {
|
||||
who: 30,
|
||||
distribution: vec![
|
||||
(2, Perbill::from_percent(100/2)),
|
||||
(3, Perbill::from_percent(100/2)),
|
||||
],
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -115,7 +128,10 @@ fn phragmen_poc_2_works() {
|
||||
(4, 500),
|
||||
]);
|
||||
|
||||
run_and_compare(candidates, voters, stake_of, 2, 2);
|
||||
run_and_compare::<Perbill>(candidates.clone(), voters.clone(), &stake_of, 2, 2);
|
||||
run_and_compare::<Permill>(candidates.clone(), voters.clone(), &stake_of, 2, 2);
|
||||
run_and_compare::<Percent>(candidates.clone(), voters.clone(), &stake_of, 2, 2);
|
||||
run_and_compare::<PerU16>(candidates, voters, &stake_of, 2, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -133,7 +149,10 @@ fn phragmen_poc_3_works() {
|
||||
(4, 1000),
|
||||
]);
|
||||
|
||||
run_and_compare(candidates, voters, stake_of, 2, 2);
|
||||
run_and_compare::<Perbill>(candidates.clone(), voters.clone(), &stake_of, 2, 2);
|
||||
run_and_compare::<Permill>(candidates.clone(), voters.clone(), &stake_of, 2, 2);
|
||||
run_and_compare::<Percent>(candidates.clone(), voters.clone(), &stake_of, 2, 2);
|
||||
run_and_compare::<PerU16>(candidates, voters, &stake_of, 2, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -149,7 +168,7 @@ fn phragmen_accuracy_on_large_scale_only_validators() {
|
||||
(5, (u64::max_value() - 2).into()),
|
||||
]);
|
||||
|
||||
let PhragmenResult { winners, assignments } = elect::<_, _, TestCurrencyToVote, Output>(
|
||||
let PhragmenResult { winners, assignments } = elect::<_, _, TestCurrencyToVote, Perbill>(
|
||||
2,
|
||||
2,
|
||||
candidates.clone(),
|
||||
@@ -158,7 +177,7 @@ fn phragmen_accuracy_on_large_scale_only_validators() {
|
||||
|
||||
assert_eq_uvec!(winners, vec![(1, 18446744073709551614u128), (5, 18446744073709551613u128)]);
|
||||
assert_eq!(assignments.len(), 2);
|
||||
check_assignments(assignments);
|
||||
check_assignments_sum(assignments);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -179,7 +198,7 @@ fn phragmen_accuracy_on_large_scale_validators_and_nominators() {
|
||||
(14, u64::max_value().into()),
|
||||
]);
|
||||
|
||||
let PhragmenResult { winners, assignments } = elect::<_, _, TestCurrencyToVote, Output>(
|
||||
let PhragmenResult { winners, assignments } = elect::<_, _, TestCurrencyToVote, Perbill>(
|
||||
2,
|
||||
2,
|
||||
candidates,
|
||||
@@ -190,13 +209,25 @@ fn phragmen_accuracy_on_large_scale_validators_and_nominators() {
|
||||
assert_eq!(
|
||||
assignments,
|
||||
vec![
|
||||
(13, vec![(1, Perbill::one())]),
|
||||
(14, vec![(2, Perbill::one())]),
|
||||
(1, vec![(1, Perbill::one())]),
|
||||
(2, vec![(2, Perbill::one())]),
|
||||
Assignment {
|
||||
who: 13u64,
|
||||
distribution: vec![(1, Perbill::one())],
|
||||
},
|
||||
Assignment {
|
||||
who: 14,
|
||||
distribution: vec![(2, Perbill::one())],
|
||||
},
|
||||
Assignment {
|
||||
who: 1,
|
||||
distribution: vec![(1, Perbill::one())],
|
||||
},
|
||||
Assignment {
|
||||
who: 2,
|
||||
distribution: vec![(2, Perbill::one())],
|
||||
},
|
||||
]
|
||||
);
|
||||
check_assignments(assignments);
|
||||
check_assignments_sum(assignments);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -210,7 +241,7 @@ fn phragmen_accuracy_on_small_scale_self_vote() {
|
||||
(30, 1),
|
||||
]);
|
||||
|
||||
let PhragmenResult { winners, assignments: _ } = elect::<_, _, TestCurrencyToVote, Output>(
|
||||
let PhragmenResult { winners, assignments: _ } = elect::<_, _, TestCurrencyToVote, Perbill>(
|
||||
3,
|
||||
3,
|
||||
candidates,
|
||||
@@ -240,7 +271,7 @@ fn phragmen_accuracy_on_small_scale_no_self_vote() {
|
||||
(3, 1),
|
||||
]);
|
||||
|
||||
let PhragmenResult { winners, assignments: _ } = elect::<_, _, TestCurrencyToVote, Output>(
|
||||
let PhragmenResult { winners, assignments: _ } = elect::<_, _, TestCurrencyToVote, Perbill>(
|
||||
3,
|
||||
3,
|
||||
candidates,
|
||||
@@ -273,7 +304,7 @@ fn phragmen_large_scale_test() {
|
||||
(50, 990000000000000000),
|
||||
]);
|
||||
|
||||
let PhragmenResult { winners, assignments } = elect::<_, _, TestCurrencyToVote, Output>(
|
||||
let PhragmenResult { winners, assignments } = elect::<_, _, TestCurrencyToVote, Perbill>(
|
||||
2,
|
||||
2,
|
||||
candidates,
|
||||
@@ -281,7 +312,7 @@ fn phragmen_large_scale_test() {
|
||||
).unwrap();
|
||||
|
||||
assert_eq_uvec!(winners, vec![(24, 1490000000000200000u128), (22, 1490000000000100000u128)]);
|
||||
check_assignments(assignments);
|
||||
check_assignments_sum(assignments);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -299,7 +330,7 @@ fn phragmen_large_scale_test_2() {
|
||||
(50, nom_budget.into()),
|
||||
]);
|
||||
|
||||
let PhragmenResult { winners, assignments } = elect::<_, _, TestCurrencyToVote, Output>(
|
||||
let PhragmenResult { winners, assignments } = elect::<_, _, TestCurrencyToVote, Perbill>(
|
||||
2,
|
||||
2,
|
||||
candidates,
|
||||
@@ -310,12 +341,24 @@ fn phragmen_large_scale_test_2() {
|
||||
assert_eq!(
|
||||
assignments,
|
||||
vec![
|
||||
(50, vec![(2, Perbill::from_parts(500000001)), (4, Perbill::from_parts(499999999))]),
|
||||
(2, vec![(2, Perbill::one())]),
|
||||
(4, vec![(4, Perbill::one())]),
|
||||
Assignment {
|
||||
who: 50u64,
|
||||
distribution: vec![
|
||||
(2, Perbill::from_parts(500000001)),
|
||||
(4, Perbill::from_parts(499999999))
|
||||
],
|
||||
},
|
||||
Assignment {
|
||||
who: 2,
|
||||
distribution: vec![(2, Perbill::one())],
|
||||
},
|
||||
Assignment {
|
||||
who: 4,
|
||||
distribution: vec![(4, Perbill::one())],
|
||||
},
|
||||
],
|
||||
);
|
||||
check_assignments(assignments);
|
||||
check_assignments_sum(assignments);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -348,7 +391,7 @@ fn phragmen_linear_equalize() {
|
||||
(130, 1000),
|
||||
]);
|
||||
|
||||
run_and_compare(candidates, voters, stake_of, 2, 2);
|
||||
run_and_compare::<Perbill>(candidates, voters, &stake_of, 2, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -363,7 +406,7 @@ fn elect_has_no_entry_barrier() {
|
||||
(2, 10),
|
||||
]);
|
||||
|
||||
let PhragmenResult { winners, assignments: _ } = elect::<_, _, TestCurrencyToVote, Output>(
|
||||
let PhragmenResult { winners, assignments: _ } = elect::<_, _, TestCurrencyToVote, Perbill>(
|
||||
3,
|
||||
3,
|
||||
candidates,
|
||||
@@ -390,7 +433,7 @@ fn minimum_to_elect_is_respected() {
|
||||
(2, 10),
|
||||
]);
|
||||
|
||||
let maybe_result = elect::<_, _, TestCurrencyToVote, Output>(
|
||||
let maybe_result = elect::<_, _, TestCurrencyToVote, Perbill>(
|
||||
10,
|
||||
10,
|
||||
candidates,
|
||||
@@ -416,7 +459,7 @@ fn self_votes_should_be_kept() {
|
||||
(1, 8),
|
||||
]);
|
||||
|
||||
let result = elect::<_, _, TestCurrencyToVote, Output>(
|
||||
let result = elect::<_, _, TestCurrencyToVote, Perbill>(
|
||||
2,
|
||||
2,
|
||||
candidates,
|
||||
@@ -427,27 +470,28 @@ fn self_votes_should_be_kept() {
|
||||
assert_eq!(
|
||||
result.assignments,
|
||||
vec![
|
||||
(10, vec![(10, Perbill::from_percent(100))]),
|
||||
(20, vec![(20, Perbill::from_percent(100))]),
|
||||
(1, vec![
|
||||
Assignment { who: 10, distribution: vec![(10, Perbill::from_percent(100))] },
|
||||
Assignment { who: 20, distribution: vec![(20, Perbill::from_percent(100))] },
|
||||
Assignment { who: 1, distribution: vec![
|
||||
(10, Perbill::from_percent(50)),
|
||||
(20, Perbill::from_percent(50))
|
||||
]
|
||||
)
|
||||
},
|
||||
],
|
||||
);
|
||||
|
||||
let mut supports = build_support_map::<
|
||||
Balance,
|
||||
AccountId,
|
||||
_,
|
||||
TestCurrencyToVote,
|
||||
Output,
|
||||
>(
|
||||
&result.winners.into_iter().map(|(who, _)| who).collect(),
|
||||
&result.assignments,
|
||||
&stake_of
|
||||
);
|
||||
let staked_assignments: Vec<StakedAssignment<AccountId>> = result.assignments
|
||||
.into_iter()
|
||||
.map(|a| {
|
||||
let stake = <TestCurrencyToVote as Convert<Balance, u64>>::convert(stake_of(&a.who)) as ExtendedBalance;
|
||||
a.into_staked(stake, true)
|
||||
}).collect();
|
||||
|
||||
let winners = result.winners.into_iter().map(|(who, _)| who).collect::<Vec<AccountId>>();
|
||||
let (mut supports, _) = build_support_map::<AccountId>(
|
||||
winners.as_slice(),
|
||||
&staked_assignments,
|
||||
);
|
||||
|
||||
assert_eq!(supports.get(&5u64), None);
|
||||
assert_eq!(
|
||||
@@ -459,23 +503,13 @@ fn self_votes_should_be_kept() {
|
||||
&Support { total: 24u128, voters: vec![(20u64, 20u128), (1u64, 4u128)] },
|
||||
);
|
||||
|
||||
let assignments = result.assignments;
|
||||
let mut staked_assignments
|
||||
: Vec<(AccountId, Vec<PhragmenStakedAssignment<AccountId>>)>
|
||||
= Vec::with_capacity(assignments.len());
|
||||
for (n, assignment) in assignments.iter() {
|
||||
let mut staked_assignment
|
||||
: Vec<PhragmenStakedAssignment<AccountId>>
|
||||
= Vec::with_capacity(assignment.len());
|
||||
let stake = stake_of(&n);
|
||||
for (c, per_thing) in assignment.iter() {
|
||||
let vote_stake = *per_thing * stake;
|
||||
staked_assignment.push((c.clone(), vote_stake));
|
||||
}
|
||||
staked_assignments.push((n.clone(), staked_assignment));
|
||||
}
|
||||
|
||||
equalize::<Balance, AccountId, TestCurrencyToVote, _>(staked_assignments, &mut supports, 0, 2usize, &stake_of);
|
||||
equalize::<Balance, AccountId, TestCurrencyToVote, _>(
|
||||
staked_assignments,
|
||||
&mut supports,
|
||||
0,
|
||||
2usize,
|
||||
&stake_of,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
supports.get(&10u64).unwrap(),
|
||||
@@ -486,3 +520,462 @@ fn self_votes_should_be_kept() {
|
||||
&Support { total: 20u128, voters: vec![(20u64, 20u128)] },
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assignment_convert_works() {
|
||||
let staked = StakedAssignment {
|
||||
who: 1 as AccountId,
|
||||
distribution: vec![
|
||||
(20, 100 as Balance),
|
||||
(30, 25),
|
||||
],
|
||||
};
|
||||
|
||||
let assignment = staked.clone().into_assignment(true);
|
||||
assert_eq!(
|
||||
assignment,
|
||||
Assignment {
|
||||
who: 1,
|
||||
distribution: vec![
|
||||
(20, Perbill::from_percent(80)),
|
||||
(30, Perbill::from_percent(20)),
|
||||
]
|
||||
}
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
assignment.into_staked(125, true),
|
||||
staked,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn score_comparison_is_lexicographical() {
|
||||
// only better in the fist parameter, worse in the other two ✅
|
||||
assert_eq!(
|
||||
is_score_better([10, 20, 30], [12, 10, 35]),
|
||||
true,
|
||||
);
|
||||
|
||||
// worse in the first, better in the other two ❌
|
||||
assert_eq!(
|
||||
is_score_better([10, 20, 30], [9, 30, 10]),
|
||||
false,
|
||||
);
|
||||
|
||||
// equal in the first, the second one dictates.
|
||||
assert_eq!(
|
||||
is_score_better([10, 20, 30], [10, 25, 40]),
|
||||
true,
|
||||
);
|
||||
|
||||
// equal in the first two, the last one dictates.
|
||||
assert_eq!(
|
||||
is_score_better([10, 20, 30], [10, 20, 40]),
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
mod compact {
|
||||
use codec::{Decode, Encode};
|
||||
use crate::generate_compact_solution_type;
|
||||
use super::{AccountId, Balance};
|
||||
// these need to come from the same dev-dependency `sp-phragmen`, not from the crate.
|
||||
use sp_phragmen::{Assignment, StakedAssignment, Error as PhragmenError};
|
||||
use sp_std::{convert::{TryInto, TryFrom}, fmt::Debug};
|
||||
use sp_runtime::Percent;
|
||||
|
||||
type Accuracy = Percent;
|
||||
|
||||
generate_compact_solution_type!(TestCompact, 16);
|
||||
|
||||
#[test]
|
||||
fn compact_struct_is_codec() {
|
||||
let compact = TestCompact::<_, _, _> {
|
||||
votes1: vec![(2u64, 20), (4, 40)],
|
||||
votes2: vec![
|
||||
(1, (10, Accuracy::from_percent(80)), 11),
|
||||
(5, (50, Accuracy::from_percent(85)), 51),
|
||||
],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let encoded = compact.encode();
|
||||
|
||||
assert_eq!(
|
||||
compact,
|
||||
Decode::decode(&mut &encoded[..]).unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
fn basic_ratio_test_with<V, T>() where
|
||||
V: codec::Codec + Copy + Default + PartialEq + Eq + TryInto<usize> + TryFrom<usize> + From<u8> + Debug,
|
||||
T: codec::Codec + Copy + Default + PartialEq + Eq + TryInto<usize> + TryFrom<usize> + From<u8> + Debug,
|
||||
<V as TryFrom<usize>>::Error: std::fmt::Debug,
|
||||
<T as TryFrom<usize>>::Error: std::fmt::Debug,
|
||||
<V as TryInto<usize>>::Error: std::fmt::Debug,
|
||||
<T as TryInto<usize>>::Error: std::fmt::Debug,
|
||||
{
|
||||
let voters = vec![
|
||||
2 as AccountId,
|
||||
4,
|
||||
1,
|
||||
5,
|
||||
3,
|
||||
];
|
||||
let targets = vec![
|
||||
10 as AccountId,
|
||||
11,
|
||||
20, // 2
|
||||
30,
|
||||
31, // 4
|
||||
32,
|
||||
40, // 6
|
||||
50,
|
||||
51, // 8
|
||||
];
|
||||
|
||||
let assignments = vec![
|
||||
Assignment {
|
||||
who: 2 as AccountId,
|
||||
distribution: vec![(20u64, Accuracy::from_percent(100))]
|
||||
},
|
||||
Assignment {
|
||||
who: 4,
|
||||
distribution: vec![(40, Accuracy::from_percent(100))],
|
||||
},
|
||||
Assignment {
|
||||
who: 1,
|
||||
distribution: vec![
|
||||
(10, Accuracy::from_percent(80)),
|
||||
(11, Accuracy::from_percent(20))
|
||||
],
|
||||
},
|
||||
Assignment {
|
||||
who: 5,
|
||||
distribution: vec![
|
||||
(50, Accuracy::from_percent(85)),
|
||||
(51, Accuracy::from_percent(15)),
|
||||
]
|
||||
},
|
||||
Assignment {
|
||||
who: 3,
|
||||
distribution: vec![
|
||||
(30, Accuracy::from_percent(50)),
|
||||
(31, Accuracy::from_percent(25)),
|
||||
(32, Accuracy::from_percent(25)),
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
let voter_index = |a: &AccountId| -> Option<V> {
|
||||
voters.iter().position(|x| x == a).map(TryInto::try_into).unwrap().ok()
|
||||
};
|
||||
let target_index = |a: &AccountId| -> Option<T> {
|
||||
targets.iter().position(|x| x == a).map(TryInto::try_into).unwrap().ok()
|
||||
};
|
||||
|
||||
let compacted = <TestCompact<V, T, Percent>>::from_assignment(
|
||||
assignments.clone(),
|
||||
voter_index,
|
||||
target_index,
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
compacted,
|
||||
TestCompact {
|
||||
votes1: vec![(V::from(0u8), T::from(2u8)), (V::from(1u8), T::from(6u8))],
|
||||
votes2: vec![
|
||||
(V::from(2u8), (T::from(0u8), Accuracy::from_percent(80)), T::from(1u8)),
|
||||
(V::from(3u8), (T::from(7u8), Accuracy::from_percent(85)), T::from(8u8)),
|
||||
],
|
||||
votes3: vec![
|
||||
(
|
||||
V::from(4),
|
||||
[(T::from(3u8), Accuracy::from_percent(50)), (T::from(4u8), Accuracy::from_percent(25))],
|
||||
T::from(5u8),
|
||||
),
|
||||
],
|
||||
..Default::default()
|
||||
}
|
||||
);
|
||||
|
||||
let voter_at = |a: V| -> Option<AccountId> { voters.get(<V as TryInto<usize>>::try_into(a).unwrap()).cloned() };
|
||||
let target_at = |a: T| -> Option<AccountId> { targets.get(<T as TryInto<usize>>::try_into(a).unwrap()).cloned() };
|
||||
|
||||
assert_eq!(
|
||||
compacted.into_assignment(voter_at, target_at).unwrap(),
|
||||
assignments,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_from_and_into_compact_works_assignments() {
|
||||
basic_ratio_test_with::<u16, u16>();
|
||||
basic_ratio_test_with::<u16, u32>();
|
||||
basic_ratio_test_with::<u8, u32>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_from_and_into_compact_works_staked_assignments() {
|
||||
let voters = vec![
|
||||
2 as AccountId,
|
||||
4,
|
||||
1,
|
||||
5,
|
||||
3,
|
||||
];
|
||||
let targets = vec![
|
||||
10 as AccountId, 11,
|
||||
20,
|
||||
30, 31, 32,
|
||||
40,
|
||||
50, 51,
|
||||
];
|
||||
|
||||
let assignments = vec![
|
||||
StakedAssignment {
|
||||
who: 2 as AccountId,
|
||||
distribution: vec![(20, 100 as Balance)]
|
||||
},
|
||||
StakedAssignment {
|
||||
who: 4,
|
||||
distribution: vec![(40, 100)],
|
||||
},
|
||||
StakedAssignment {
|
||||
who: 1,
|
||||
distribution: vec![
|
||||
(10, 80),
|
||||
(11, 20)
|
||||
],
|
||||
},
|
||||
StakedAssignment {
|
||||
who: 5, distribution:
|
||||
vec![
|
||||
(50, 85),
|
||||
(51, 15),
|
||||
]
|
||||
},
|
||||
StakedAssignment {
|
||||
who: 3,
|
||||
distribution: vec![
|
||||
(30, 50),
|
||||
(31, 25),
|
||||
(32, 25),
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
let voter_index = |a: &AccountId| -> Option<u16> {
|
||||
voters.iter().position(|x| x == a).map(TryInto::try_into).unwrap().ok()
|
||||
};
|
||||
let target_index = |a: &AccountId| -> Option<u16> {
|
||||
targets.iter().position(|x| x == a).map(TryInto::try_into).unwrap().ok()
|
||||
};
|
||||
|
||||
let compacted = <TestCompact<u16, u16, Balance>>::from_staked(
|
||||
assignments.clone(),
|
||||
voter_index,
|
||||
target_index,
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
compacted,
|
||||
TestCompact {
|
||||
votes1: vec![(0, 2), (1, 6)],
|
||||
votes2: vec![
|
||||
(2, (0, 80), 1),
|
||||
(3, (7, 85), 8),
|
||||
],
|
||||
votes3: vec![
|
||||
(4, [(3, 50), (4, 25)], 5),
|
||||
],
|
||||
..Default::default()
|
||||
}
|
||||
);
|
||||
|
||||
let max_of_fn = |_: &AccountId| -> Balance { 100u128 };
|
||||
let voter_at = |a: u16| -> Option<AccountId> { voters.get(a as usize).cloned() };
|
||||
let target_at = |a: u16| -> Option<AccountId> { targets.get(a as usize).cloned() };
|
||||
|
||||
assert_eq!(
|
||||
compacted.into_staked(
|
||||
max_of_fn,
|
||||
voter_at,
|
||||
target_at,
|
||||
).unwrap(),
|
||||
assignments,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compact_into_stake_must_report_overflow() {
|
||||
// The last edge which is computed from the rest should ALWAYS be positive.
|
||||
// in votes2
|
||||
let compact = TestCompact::<u16, u16, Balance> {
|
||||
votes1: Default::default(),
|
||||
votes2: vec![(0, (1, 10), 2)],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let entity_at = |a: u16| -> Option<AccountId> { Some(a as AccountId) };
|
||||
let max_of = |_: &AccountId| -> Balance { 5 };
|
||||
|
||||
assert_eq!(
|
||||
compact.into_staked(&max_of, &entity_at, &entity_at).unwrap_err(),
|
||||
PhragmenError::CompactStakeOverflow,
|
||||
);
|
||||
|
||||
// in votes3 onwards
|
||||
let compact = TestCompact::<u16, u16, Balance> {
|
||||
votes1: Default::default(),
|
||||
votes2: Default::default(),
|
||||
votes3: vec![(0, [(1, 7), (2, 8)], 3)],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
compact.into_staked(&max_of, &entity_at, &entity_at).unwrap_err(),
|
||||
PhragmenError::CompactStakeOverflow,
|
||||
);
|
||||
|
||||
// Also if equal
|
||||
let compact = TestCompact::<u16, u16, Balance> {
|
||||
votes1: Default::default(),
|
||||
votes2: Default::default(),
|
||||
// 5 is total, we cannot leave none for 30 here.
|
||||
votes3: vec![(0, [(1, 3), (2, 2)], 3)],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
compact.into_staked(&max_of, &entity_at, &entity_at).unwrap_err(),
|
||||
PhragmenError::CompactStakeOverflow,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compact_into_assignment_must_report_overflow() {
|
||||
// in votes2
|
||||
let compact = TestCompact::<u16, u16, Accuracy> {
|
||||
votes1: Default::default(),
|
||||
votes2: vec![(0, (1, Accuracy::from_percent(100)), 2)],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let entity_at = |a: u16| -> Option<AccountId> { Some(a as AccountId) };
|
||||
|
||||
assert_eq!(
|
||||
compact.into_assignment(&entity_at, &entity_at).unwrap_err(),
|
||||
PhragmenError::CompactStakeOverflow,
|
||||
);
|
||||
|
||||
// in votes3 onwards
|
||||
let compact = TestCompact::<u16, u16, Accuracy> {
|
||||
votes1: Default::default(),
|
||||
votes2: Default::default(),
|
||||
votes3: vec![(0, [(1, Accuracy::from_percent(70)), (2, Accuracy::from_percent(80))], 3)],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
compact.into_assignment(&entity_at, &entity_at).unwrap_err(),
|
||||
PhragmenError::CompactStakeOverflow,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn target_count_overflow_is_detected() {
|
||||
let assignments = vec![
|
||||
StakedAssignment {
|
||||
who: 1 as AccountId,
|
||||
distribution: (10..26).map(|i| (i as AccountId, i as Balance)).collect::<Vec<_>>(),
|
||||
},
|
||||
];
|
||||
|
||||
let entity_index = |a: &AccountId| -> Option<u16> { Some(*a as u16) };
|
||||
|
||||
let compacted = <TestCompact<u16, u16, Balance>>::from_staked(
|
||||
assignments.clone(),
|
||||
entity_index,
|
||||
entity_index,
|
||||
);
|
||||
|
||||
assert!(compacted.is_ok());
|
||||
|
||||
let assignments = vec![
|
||||
StakedAssignment {
|
||||
who: 1 as AccountId,
|
||||
distribution: (10..27).map(|i| (i as AccountId, i as Balance)).collect::<Vec<_>>(),
|
||||
},
|
||||
];
|
||||
|
||||
let compacted = <TestCompact<u16, u16, Balance>>::from_staked(
|
||||
assignments.clone(),
|
||||
entity_index,
|
||||
entity_index,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
compacted.unwrap_err(),
|
||||
PhragmenError::CompactTargetOverflow,
|
||||
);
|
||||
|
||||
let assignments = vec![
|
||||
Assignment {
|
||||
who: 1 as AccountId,
|
||||
distribution: (10..27).map(|i| (i as AccountId, Percent::from_parts(i as u8))).collect::<Vec<_>>(),
|
||||
},
|
||||
];
|
||||
|
||||
let compacted = <TestCompact<u16, u16, Percent>>::from_assignment(
|
||||
assignments.clone(),
|
||||
entity_index,
|
||||
entity_index,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
compacted.unwrap_err(),
|
||||
PhragmenError::CompactTargetOverflow,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero_target_count_is_ignored() {
|
||||
let voters = vec![1 as AccountId, 2];
|
||||
let targets = vec![10 as AccountId, 11];
|
||||
|
||||
let assignments = vec![
|
||||
StakedAssignment {
|
||||
who: 1 as AccountId,
|
||||
distribution: vec![(10, 100 as Balance), (11, 100)]
|
||||
},
|
||||
StakedAssignment {
|
||||
who: 2,
|
||||
distribution: vec![],
|
||||
},
|
||||
];
|
||||
|
||||
let voter_index = |a: &AccountId| -> Option<u16> {
|
||||
voters.iter().position(|x| x == a).map(TryInto::try_into).unwrap().ok()
|
||||
};
|
||||
let target_index = |a: &AccountId| -> Option<u16> {
|
||||
targets.iter().position(|x| x == a).map(TryInto::try_into).unwrap().ok()
|
||||
};
|
||||
|
||||
let compacted = <TestCompact<u16, u16, Balance>>::from_staked(
|
||||
assignments.clone(),
|
||||
voter_index,
|
||||
target_index,
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
compacted,
|
||||
TestCompact {
|
||||
votes1: Default::default(),
|
||||
votes2: vec![(0, (0, 100), 1)],
|
||||
..Default::default()
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user