mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-07-22 21:55:45 +00:00
Generic Normalize impl for arithmetic and npos-elections (#6374)
* add normalize * better api for normalize * Some grumbles * Update primitives/arithmetic/src/lib.rs Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com> * More great review grumbles * Way better doc for everything. * Some improvement * Update primitives/arithmetic/src/lib.rs Co-authored-by: Bernhard Schuster <bernhard@ahoi.io> Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com> Co-authored-by: Bernhard Schuster <bernhard@ahoi.io>
This commit is contained in:
@@ -17,37 +17,72 @@
|
||||
|
||||
//! Helper methods for npos-elections.
|
||||
|
||||
use crate::{Assignment, ExtendedBalance, VoteWeight, IdentifierT, StakedAssignment, WithApprovalOf};
|
||||
use sp_arithmetic::PerThing;
|
||||
use crate::{Assignment, ExtendedBalance, VoteWeight, IdentifierT, StakedAssignment, WithApprovalOf, Error};
|
||||
use sp_arithmetic::{PerThing, InnerOf};
|
||||
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>>,
|
||||
///
|
||||
/// Note that this will NOT attempt at normalizing the result.
|
||||
pub fn assignment_ratio_to_staked<A: IdentifierT, P: PerThing, FS>(
|
||||
ratio: Vec<Assignment<A, P>>,
|
||||
stake_of: FS,
|
||||
) -> Vec<StakedAssignment<A>>
|
||||
where
|
||||
for<'r> FS: Fn(&'r A) -> VoteWeight,
|
||||
T: sp_std::ops::Mul<ExtendedBalance, Output = ExtendedBalance>,
|
||||
ExtendedBalance: From<<T as PerThing>::Inner>,
|
||||
P: sp_std::ops::Mul<ExtendedBalance, Output = ExtendedBalance>,
|
||||
ExtendedBalance: From<InnerOf<P>>,
|
||||
{
|
||||
ratio
|
||||
.into_iter()
|
||||
.map(|a| {
|
||||
let stake = stake_of(&a.who);
|
||||
a.into_staked(stake.into(), true)
|
||||
a.into_staked(stake.into())
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Converts a vector of staked assignments into ones with ratio values.
|
||||
pub fn assignment_staked_to_ratio<A: IdentifierT, T: PerThing>(
|
||||
staked: Vec<StakedAssignment<A>>,
|
||||
) -> Vec<Assignment<A, T>>
|
||||
/// Same as [`assignment_ratio_to_staked`] and try and do normalization.
|
||||
pub fn assignment_ratio_to_staked_normalized<A: IdentifierT, P: PerThing, FS>(
|
||||
ratio: Vec<Assignment<A, P>>,
|
||||
stake_of: FS,
|
||||
) -> Result<Vec<StakedAssignment<A>>, Error>
|
||||
where
|
||||
ExtendedBalance: From<<T as PerThing>::Inner>,
|
||||
for<'r> FS: Fn(&'r A) -> VoteWeight,
|
||||
P: sp_std::ops::Mul<ExtendedBalance, Output = ExtendedBalance>,
|
||||
ExtendedBalance: From<InnerOf<P>>,
|
||||
{
|
||||
staked.into_iter().map(|a| a.into_assignment(true)).collect()
|
||||
let mut staked = assignment_ratio_to_staked(ratio, &stake_of);
|
||||
staked.iter_mut().map(|a|
|
||||
a.try_normalize(stake_of(&a.who).into()).map_err(|err| Error::ArithmeticError(err))
|
||||
).collect::<Result<_, _>>()?;
|
||||
Ok(staked)
|
||||
}
|
||||
|
||||
/// Converts a vector of staked assignments into ones with ratio values.
|
||||
///
|
||||
/// Note that this will NOT attempt at normalizing the result.
|
||||
pub fn assignment_staked_to_ratio<A: IdentifierT, P: PerThing>(
|
||||
staked: Vec<StakedAssignment<A>>,
|
||||
) -> Vec<Assignment<A, P>>
|
||||
where
|
||||
ExtendedBalance: From<InnerOf<P>>,
|
||||
{
|
||||
staked.into_iter().map(|a| a.into_assignment()).collect()
|
||||
}
|
||||
|
||||
/// Same as [`assignment_staked_to_ratio`] and try and do normalization.
|
||||
pub fn assignment_staked_to_ratio_normalized<A: IdentifierT, P: PerThing>(
|
||||
staked: Vec<StakedAssignment<A>>,
|
||||
) -> Result<Vec<Assignment<A, P>>, Error>
|
||||
where
|
||||
ExtendedBalance: From<InnerOf<P>>,
|
||||
{
|
||||
let mut ratio = staked.into_iter().map(|a| a.into_assignment()).collect::<Vec<_>>();
|
||||
ratio.iter_mut().map(|a|
|
||||
a.try_normalize().map_err(|err| Error::ArithmeticError(err))
|
||||
).collect::<Result<_, _>>()?;
|
||||
Ok(ratio)
|
||||
}
|
||||
|
||||
/// consumes a vector of winners with backing stake to just winners.
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
use sp_std::{prelude::*, collections::btree_map::BTreeMap, fmt::Debug, cmp::Ordering, convert::TryFrom};
|
||||
use sp_arithmetic::{
|
||||
PerThing, Rational128, ThresholdOrd,
|
||||
PerThing, Rational128, ThresholdOrd, InnerOf, Normalizable,
|
||||
helpers_128bit::multiply_by_rational,
|
||||
traits::{Zero, Saturating, Bounded, SaturatedConversion},
|
||||
};
|
||||
@@ -84,6 +84,8 @@ pub enum Error {
|
||||
CompactTargetOverflow,
|
||||
/// One of the index functions returned none.
|
||||
CompactInvalidIndex,
|
||||
/// An error occurred in some arithmetic operation.
|
||||
ArithmeticError(&'static str),
|
||||
}
|
||||
|
||||
/// A type which is used in the API of this crate as a numeric weight of a vote, most often the
|
||||
@@ -155,16 +157,16 @@ pub struct ElectionResult<AccountId, T: PerThing> {
|
||||
/// A voter's stake assignment among a set of targets, represented as ratios.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[cfg_attr(feature = "std", derive(PartialEq, Eq, Encode, Decode))]
|
||||
pub struct Assignment<AccountId, T: PerThing> {
|
||||
pub struct Assignment<AccountId, P: PerThing> {
|
||||
/// Voter's identifier.
|
||||
pub who: AccountId,
|
||||
/// The distribution of the voter's stake.
|
||||
pub distribution: Vec<(AccountId, T)>,
|
||||
pub distribution: Vec<(AccountId, P)>,
|
||||
}
|
||||
|
||||
impl<AccountId, T: PerThing> Assignment<AccountId, T>
|
||||
impl<AccountId: IdentifierT, P: PerThing> Assignment<AccountId, P>
|
||||
where
|
||||
ExtendedBalance: From<<T as PerThing>::Inner>,
|
||||
ExtendedBalance: From<InnerOf<P>>,
|
||||
{
|
||||
/// Convert from a ratio assignment into one with absolute values aka. [`StakedAssignment`].
|
||||
///
|
||||
@@ -173,50 +175,49 @@ where
|
||||
/// 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
|
||||
/// If an edge ratio is [`Bounded::min_value()`], it is dropped. This edge can never mean
|
||||
/// anything useful.
|
||||
pub fn into_staked(self, stake: ExtendedBalance, fill: bool) -> StakedAssignment<AccountId>
|
||||
pub fn into_staked(self, stake: ExtendedBalance) -> StakedAssignment<AccountId>
|
||||
where
|
||||
T: sp_std::ops::Mul<ExtendedBalance, Output = ExtendedBalance>,
|
||||
P: sp_std::ops::Mul<ExtendedBalance, Output = ExtendedBalance>,
|
||||
{
|
||||
let mut sum: ExtendedBalance = Bounded::min_value();
|
||||
let mut distribution = self
|
||||
.distribution
|
||||
let distribution = self.distribution
|
||||
.into_iter()
|
||||
.filter_map(|(target, p)| {
|
||||
// if this ratio is zero, then skip it.
|
||||
if p == Bounded::min_value() {
|
||||
if p.is_zero() {
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
/// Try and normalize this assignment.
|
||||
///
|
||||
/// If `Ok(())` is returned, then the assignment MUST have been successfully normalized to 100%.
|
||||
pub fn try_normalize(&mut self) -> Result<(), &'static str> {
|
||||
self.distribution
|
||||
.iter()
|
||||
.map(|(_, p)| *p)
|
||||
.collect::<Vec<_>>()
|
||||
.normalize(P::one())
|
||||
.map(|normalized_ratios|
|
||||
self.distribution
|
||||
.iter_mut()
|
||||
.zip(normalized_ratios)
|
||||
.for_each(|((_, old), corrected)| { *old = corrected; })
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// A voter's stake assignment among a set of targets, represented as absolute values in the scale
|
||||
@@ -243,42 +244,23 @@ impl<AccountId> StakedAssignment<AccountId> {
|
||||
///
|
||||
/// 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>
|
||||
pub fn into_assignment<P: PerThing>(self) -> Assignment<AccountId, P>
|
||||
where
|
||||
ExtendedBalance: From<<T as PerThing>::Inner>,
|
||||
ExtendedBalance: From<InnerOf<P>>,
|
||||
AccountId: IdentifierT,
|
||||
{
|
||||
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
|
||||
let stake = self.total();
|
||||
let distribution = self.distribution
|
||||
.into_iter()
|
||||
.filter_map(|(target, w)| {
|
||||
let per_thing = T::from_rational_approximation(w, stake);
|
||||
let per_thing = P::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())
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
.collect::<Vec<(AccountId, P)>>();
|
||||
|
||||
Assignment {
|
||||
who: self.who,
|
||||
@@ -286,6 +268,30 @@ impl<AccountId> StakedAssignment<AccountId> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Try and normalize this assignment.
|
||||
///
|
||||
/// If `Ok(())` is returned, then the assignment MUST have been successfully normalized to
|
||||
/// `stake`.
|
||||
///
|
||||
/// NOTE: current implementation of `.normalize` is almost safe to `expect()` upon. The only
|
||||
/// error case is when the input cannot fit in `T`, or the sum of input cannot fit in `T`.
|
||||
/// Sadly, both of these are dependent upon the implementation of `VoteLimit`, i.e. the limit
|
||||
/// of edges per voter which is enforced from upstream. Hence, at this crate, we prefer
|
||||
/// returning a result and a use the name prefix `try_`.
|
||||
pub fn try_normalize(&mut self, stake: ExtendedBalance) -> Result<(), &'static str> {
|
||||
self.distribution
|
||||
.iter()
|
||||
.map(|(_, ref weight)| *weight)
|
||||
.collect::<Vec<_>>()
|
||||
.normalize(stake)
|
||||
.map(|normalized_weights|
|
||||
self.distribution
|
||||
.iter_mut()
|
||||
.zip(normalized_weights.into_iter())
|
||||
.for_each(|((_, weight), corrected)| { *weight = corrected; })
|
||||
)
|
||||
}
|
||||
|
||||
/// Get the total stake of this assignment (aka voter budget).
|
||||
pub fn total(&self) -> ExtendedBalance {
|
||||
self.distribution.iter().fold(Zero::zero(), |a, b| a.saturating_add(b.1))
|
||||
|
||||
@@ -588,186 +588,278 @@ fn self_votes_should_be_kept() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assignment_convert_works() {
|
||||
let staked = StakedAssignment {
|
||||
who: 1 as AccountId,
|
||||
distribution: vec![
|
||||
(20, 100 as ExtendedBalance),
|
||||
(30, 25),
|
||||
],
|
||||
};
|
||||
mod assignment_convert_normalize {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn assignment_convert_works() {
|
||||
let staked = StakedAssignment {
|
||||
who: 1 as AccountId,
|
||||
distribution: vec![
|
||||
(20, 100 as ExtendedBalance),
|
||||
(30, 25),
|
||||
],
|
||||
};
|
||||
|
||||
let assignment = staked.clone().into_assignment(true);
|
||||
assert_eq!(
|
||||
assignment,
|
||||
Assignment {
|
||||
let assignment = staked.clone().into_assignment();
|
||||
assert_eq!(
|
||||
assignment,
|
||||
Assignment {
|
||||
who: 1,
|
||||
distribution: vec![
|
||||
(20, Perbill::from_percent(80)),
|
||||
(30, Perbill::from_percent(20)),
|
||||
]
|
||||
}
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
assignment.into_staked(125),
|
||||
staked,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assignment_convert_will_not_normalize() {
|
||||
assert_eq!(
|
||||
Assignment {
|
||||
who: 1,
|
||||
distribution: vec![
|
||||
(2, Perbill::from_percent(33)),
|
||||
(3, Perbill::from_percent(66)),
|
||||
]
|
||||
}.into_staked(100),
|
||||
StakedAssignment {
|
||||
who: 1,
|
||||
distribution: vec![
|
||||
(2, 33),
|
||||
(3, 66),
|
||||
// sum is not 100!
|
||||
],
|
||||
},
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
StakedAssignment {
|
||||
who: 1,
|
||||
distribution: vec![
|
||||
(2, 333_333_333_333_333),
|
||||
(3, 333_333_333_333_333),
|
||||
(4, 666_666_666_666_333),
|
||||
],
|
||||
}.into_assignment(),
|
||||
Assignment {
|
||||
who: 1,
|
||||
distribution: vec![
|
||||
(2, Perbill::from_parts(250000000)),
|
||||
(3, Perbill::from_parts(250000000)),
|
||||
(4, Perbill::from_parts(499999999)),
|
||||
// sum is not 100%!
|
||||
]
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assignment_can_normalize() {
|
||||
let mut a = Assignment {
|
||||
who: 1,
|
||||
distribution: vec![
|
||||
(20, Perbill::from_percent(80)),
|
||||
(30, Perbill::from_percent(20)),
|
||||
(2, Perbill::from_parts(330000000)),
|
||||
(3, Perbill::from_parts(660000000)),
|
||||
// sum is not 100%!
|
||||
]
|
||||
};
|
||||
a.try_normalize().unwrap();
|
||||
assert_eq!(
|
||||
a,
|
||||
Assignment {
|
||||
who: 1,
|
||||
distribution: vec![
|
||||
(2, Perbill::from_parts(340000000)),
|
||||
(3, Perbill::from_parts(660000000)),
|
||||
]
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn staked_assignment_can_normalize() {
|
||||
let mut a = StakedAssignment {
|
||||
who: 1,
|
||||
distribution: vec![
|
||||
(2, 33),
|
||||
(3, 66),
|
||||
]
|
||||
};
|
||||
a.try_normalize(100).unwrap();
|
||||
assert_eq!(
|
||||
a,
|
||||
StakedAssignment {
|
||||
who: 1,
|
||||
distribution: vec![
|
||||
(2, 34),
|
||||
(3, 66),
|
||||
]
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mod score {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn score_comparison_is_lexicographical_no_epsilon() {
|
||||
let epsilon = Perbill::zero();
|
||||
// only better in the fist parameter, worse in the other two ✅
|
||||
assert_eq!(
|
||||
is_score_better([12, 10, 35], [10, 20, 30], epsilon),
|
||||
true,
|
||||
);
|
||||
|
||||
// worse in the first, better in the other two ❌
|
||||
assert_eq!(
|
||||
is_score_better([9, 30, 10], [10, 20, 30], epsilon),
|
||||
false,
|
||||
);
|
||||
|
||||
// equal in the first, the second one dictates.
|
||||
assert_eq!(
|
||||
is_score_better([10, 25, 40], [10, 20, 30], epsilon),
|
||||
true,
|
||||
);
|
||||
|
||||
// equal in the first two, the last one dictates.
|
||||
assert_eq!(
|
||||
is_score_better([10, 20, 40], [10, 20, 30], epsilon),
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn score_comparison_with_epsilon() {
|
||||
let epsilon = Perbill::from_percent(1);
|
||||
|
||||
{
|
||||
// no more than 1 percent (10) better in the first param.
|
||||
assert_eq!(
|
||||
is_score_better([1009, 5000, 100000], [1000, 5000, 100000], epsilon),
|
||||
false,
|
||||
);
|
||||
|
||||
// now equal, still not better.
|
||||
assert_eq!(
|
||||
is_score_better([1010, 5000, 100000], [1000, 5000, 100000], epsilon),
|
||||
false,
|
||||
);
|
||||
|
||||
// now it is.
|
||||
assert_eq!(
|
||||
is_score_better([1011, 5000, 100000], [1000, 5000, 100000], epsilon),
|
||||
true,
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
assignment.into_staked(125, true),
|
||||
staked,
|
||||
);
|
||||
}
|
||||
{
|
||||
// First score score is epsilon better, but first score is no longer `ge`. Then this is
|
||||
// still not a good solution.
|
||||
assert_eq!(
|
||||
is_score_better([999, 6000, 100000], [1000, 5000, 100000], epsilon),
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn score_comparison_is_lexicographical_no_epsilon() {
|
||||
let epsilon = Perbill::zero();
|
||||
// only better in the fist parameter, worse in the other two ✅
|
||||
assert_eq!(
|
||||
is_score_better([12, 10, 35], [10, 20, 30], epsilon),
|
||||
true,
|
||||
);
|
||||
{
|
||||
// first score is equal or better, but not epsilon. Then second one is the determinant.
|
||||
assert_eq!(
|
||||
is_score_better([1005, 5000, 100000], [1000, 5000, 100000], epsilon),
|
||||
false,
|
||||
);
|
||||
|
||||
// worse in the first, better in the other two ❌
|
||||
assert_eq!(
|
||||
is_score_better([9, 30, 10], [10, 20, 30], epsilon),
|
||||
false,
|
||||
);
|
||||
assert_eq!(
|
||||
is_score_better([1005, 5050, 100000], [1000, 5000, 100000], epsilon),
|
||||
false,
|
||||
);
|
||||
|
||||
// equal in the first, the second one dictates.
|
||||
assert_eq!(
|
||||
is_score_better([10, 25, 40], [10, 20, 30], epsilon),
|
||||
true,
|
||||
);
|
||||
assert_eq!(
|
||||
is_score_better([1005, 5051, 100000], [1000, 5000, 100000], epsilon),
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
// equal in the first two, the last one dictates.
|
||||
assert_eq!(
|
||||
is_score_better([10, 20, 40], [10, 20, 30], epsilon),
|
||||
false,
|
||||
);
|
||||
}
|
||||
{
|
||||
// first score and second are equal or less than epsilon more, third is determinant.
|
||||
assert_eq!(
|
||||
is_score_better([1005, 5025, 100000], [1000, 5000, 100000], epsilon),
|
||||
false,
|
||||
);
|
||||
|
||||
#[test]
|
||||
fn score_comparison_with_epsilon() {
|
||||
let epsilon = Perbill::from_percent(1);
|
||||
assert_eq!(
|
||||
is_score_better([1005, 5025, 99_000], [1000, 5000, 100000], epsilon),
|
||||
false,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
is_score_better([1005, 5025, 98_999], [1000, 5000, 100000], epsilon),
|
||||
true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn score_comparison_large_value() {
|
||||
// some random value taken from eras in kusama.
|
||||
let initial = [12488167277027543u128, 5559266368032409496, 118749283262079244270992278287436446];
|
||||
// this claim is 0.04090% better in the third component. It should be accepted as better if
|
||||
// epsilon is smaller than 5/10_0000
|
||||
let claim = [12488167277027543u128, 5559266368032409496, 118700736389524721358337889258988054];
|
||||
|
||||
{
|
||||
// no more than 1 percent (10) better in the first param.
|
||||
assert_eq!(
|
||||
is_score_better([1009, 5000, 100000], [1000, 5000, 100000], epsilon),
|
||||
false,
|
||||
);
|
||||
|
||||
// now equal, still not better.
|
||||
assert_eq!(
|
||||
is_score_better([1010, 5000, 100000], [1000, 5000, 100000], epsilon),
|
||||
false,
|
||||
);
|
||||
|
||||
// now it is.
|
||||
assert_eq!(
|
||||
is_score_better([1011, 5000, 100000], [1000, 5000, 100000], epsilon),
|
||||
is_score_better(
|
||||
claim.clone(),
|
||||
initial.clone(),
|
||||
Perbill::from_rational_approximation(1u32, 10_000),
|
||||
),
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
// First score score is epsilon better, but first score is no longer `ge`. Then this is
|
||||
// still not a good solution.
|
||||
assert_eq!(
|
||||
is_score_better([999, 6000, 100000], [1000, 5000, 100000], epsilon),
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
// first score is equal or better, but not epsilon. Then second one is the determinant.
|
||||
assert_eq!(
|
||||
is_score_better([1005, 5000, 100000], [1000, 5000, 100000], epsilon),
|
||||
false,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
is_score_better([1005, 5050, 100000], [1000, 5000, 100000], epsilon),
|
||||
false,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
is_score_better([1005, 5051, 100000], [1000, 5000, 100000], epsilon),
|
||||
is_score_better(
|
||||
claim.clone(),
|
||||
initial.clone(),
|
||||
Perbill::from_rational_approximation(2u32, 10_000),
|
||||
),
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
// first score and second are equal or less than epsilon more, third is determinant.
|
||||
assert_eq!(
|
||||
is_score_better([1005, 5025, 100000], [1000, 5000, 100000], epsilon),
|
||||
false,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
is_score_better([1005, 5025, 99_000], [1000, 5000, 100000], epsilon),
|
||||
false,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
is_score_better([1005, 5025, 98_999], [1000, 5000, 100000], epsilon),
|
||||
is_score_better(
|
||||
claim.clone(),
|
||||
initial.clone(),
|
||||
Perbill::from_rational_approximation(3u32, 10_000),
|
||||
),
|
||||
true,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
is_score_better(
|
||||
claim.clone(),
|
||||
initial.clone(),
|
||||
Perbill::from_rational_approximation(4u32, 10_000),
|
||||
),
|
||||
true,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
is_score_better(
|
||||
claim.clone(),
|
||||
initial.clone(),
|
||||
Perbill::from_rational_approximation(5u32, 10_000),
|
||||
),
|
||||
false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn score_comparison_large_value() {
|
||||
// some random value taken from eras in kusama.
|
||||
let initial = [12488167277027543u128, 5559266368032409496, 118749283262079244270992278287436446];
|
||||
// this claim is 0.04090% better in the third component. It should be accepted as better if
|
||||
// epsilon is smaller than 5/10_0000
|
||||
let claim = [12488167277027543u128, 5559266368032409496, 118700736389524721358337889258988054];
|
||||
|
||||
assert_eq!(
|
||||
is_score_better(
|
||||
claim.clone(),
|
||||
initial.clone(),
|
||||
Perbill::from_rational_approximation(1u32, 10_000),
|
||||
),
|
||||
true,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
is_score_better(
|
||||
claim.clone(),
|
||||
initial.clone(),
|
||||
Perbill::from_rational_approximation(2u32, 10_000),
|
||||
),
|
||||
true,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
is_score_better(
|
||||
claim.clone(),
|
||||
initial.clone(),
|
||||
Perbill::from_rational_approximation(3u32, 10_000),
|
||||
),
|
||||
true,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
is_score_better(
|
||||
claim.clone(),
|
||||
initial.clone(),
|
||||
Perbill::from_rational_approximation(4u32, 10_000),
|
||||
),
|
||||
true,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
is_score_better(
|
||||
claim.clone(),
|
||||
initial.clone(),
|
||||
Perbill::from_rational_approximation(5u32, 10_000),
|
||||
),
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
mod compact {
|
||||
use codec::{Decode, Encode};
|
||||
use super::AccountId;
|
||||
|
||||
Reference in New Issue
Block a user