Refactor: fixed point arithmetic for SRML. (#3456)

* Macro-ify perthings.

* Refactor fixed64

* Half-workign phragmen refactor.

* Finalize phragmen refactor.

* Fix creation of perquintill

* Fix build errors

* Line-width

* Fix more build errors.

* Line-width

* Fix offence test

* Resolve all TODOs.

* Apply suggestions from code review

Co-Authored-By: Gavin Wood <gavin@parity.io>
Co-Authored-By: thiolliere <gui.thiolliere@gmail.com>

* Fix most of the review comments.

* Updates to multiply by rational

* Fxi build

* Fix abs issue with Fixed64

* Fix tests and improvements.

* Fix build

* Remove more tests from staking.

* Review comments.

* Add fuzzing stuff.

* Better fuzzing

* Better doc.

* Bump.

* Master.into()

* A bit more hardening.

* Final nits.

* Update lock

* Fix indent.

* Revert lock file.

* Bump.
This commit is contained in:
Kian Paimani
2019-09-25 11:21:05 +02:00
committed by GitHub
parent 87688aadaa
commit 1c15ca6ad1
19 changed files with 1909 additions and 961 deletions
+35 -52
View File
@@ -270,7 +270,6 @@ use sr_primitives::{
SaturatedConversion,
}
};
use phragmen::{elect, equalize, Support, SupportMap, ExtendedBalance, ACCURACY};
use sr_staking_primitives::{
SessionIndex,
offence::{OnOffenceHandler, OffenceDetails, Offence, ReportOffence},
@@ -279,6 +278,8 @@ use sr_staking_primitives::{
use sr_primitives::{Serialize, Deserialize};
use system::{ensure_signed, ensure_root};
use phragmen::{elect, equalize, ExtendedBalance, Support, SupportMap, PhragmenStakedAssignment};
const DEFAULT_MINIMUM_VALIDATOR_COUNT: u32 = 4;
const MAX_NOMINATIONS: usize = 16;
const MAX_UNLOCKING_CHUNKS: usize = 32;
@@ -1190,7 +1191,7 @@ impl<T: Trait> Module<T> {
for (v, p) in validators.iter().zip(points.individual.into_iter()) {
if p != 0 {
let reward = multiply_by_rational(total_payout, p, points.total);
let reward = Perbill::from_rational_approximation(p, points.total) * total_payout;
total_imbalance.subsume(Self::reward_validator(v, reward));
}
}
@@ -1252,21 +1253,15 @@ impl<T: Trait> Module<T> {
);
if let Some(phragmen_result) = maybe_phragmen_result {
let elected_stashes = phragmen_result.winners.iter().map(|(s, _)| s.clone()).collect::<Vec<T::AccountId>>();
let mut assignments = phragmen_result.assignments;
let elected_stashes = phragmen_result.winners.iter()
.map(|(s, _)| s.clone())
.collect::<Vec<T::AccountId>>();
let assignments = phragmen_result.assignments;
// helper closure.
let to_balance = |b: ExtendedBalance|
<T::CurrencyToVote as Convert<ExtendedBalance, BalanceOf<T>>>::convert(b);
let to_votes = |b: BalanceOf<T>|
<T::CurrencyToVote as Convert<BalanceOf<T>, u64>>::convert(b) as ExtendedBalance;
// The return value of this is safe to be converted to u64.
// The original balance, `b` is within the scope of u64. It is just extended to u128
// to be properly multiplied by a ratio, which will lead to another value
// less than u64 for sure. The result can then be safely passed to `to_balance`.
// For now the backward convert is used. A simple `TryFrom<u64>` is also safe.
let ratio_of = |b, r: ExtendedBalance| r.saturating_mul(to_votes(b)) / ACCURACY;
let to_balance = |e: ExtendedBalance|
<T::CurrencyToVote as Convert<ExtendedBalance, BalanceOf<T>>>::convert(e);
// Initialize the support of each candidate.
let mut supports = <SupportMap<T::AccountId>>::new();
@@ -1278,29 +1273,42 @@ impl<T: Trait> Module<T> {
supports.insert(e.clone(), item);
});
// convert the ratio in-place (and replace) to the balance but still in the extended
// balance type.
for (n, assignment) in assignments.iter_mut() {
for (c, r) in assignment.iter_mut() {
let nominator_stake = Self::slashable_balance_of(n);
let other_stake = ratio_of(nominator_stake, *r);
// build support struct.
for (n, assignment) in assignments.iter() {
for (c, per_thing) in assignment.iter() {
let nominator_stake = to_votes(Self::slashable_balance_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;
if let Some(support) = supports.get_mut(c) {
// This for an astronomically rich validator with more astronomically rich
// For an astronomically rich validator with more astronomically rich
// set of nominators, this might saturate.
support.total = support.total.saturating_add(other_stake);
support.others.push((n.clone(), other_stake));
}
// convert the ratio to extended balance
*r = other_stake;
}
}
#[cfg(feature = "equalize")]
{
if cfg!(feature = "equalize") {
let mut staked_assignments
: Vec<(T::AccountId, Vec<PhragmenStakedAssignment<T::AccountId>>)>
= Vec::with_capacity(assignments.len());
for (n, assignment) in assignments.iter() {
let mut staked_assignment
: Vec<PhragmenStakedAssignment<T::AccountId>>
= Vec::with_capacity(assignment.len());
for (c, per_thing) in assignment.iter() {
let nominator_stake = to_votes(Self::slashable_balance_of(n));
let other_stake = *per_thing * nominator_stake;
staked_assignment.push((c.clone(), other_stake));
}
staked_assignments.push((n.clone(), staked_assignment));
}
let tolerance = 0_u128;
let iterations = 2_usize;
equalize::<_, _, _, T::CurrencyToVote>(
assignments,
equalize::<_, _, T::CurrencyToVote, _>(
staked_assignments,
&mut supports,
tolerance,
iterations,
@@ -1450,31 +1458,6 @@ impl<T: Trait + authorship::Trait> authorship::EventHandler<T::AccountId, T::Blo
}
}
// This is guarantee not to overflow on whatever values.
// `num` must be inferior to `den` otherwise it will be reduce to `den`.
fn multiply_by_rational<N>(value: N, num: u32, den: u32) -> N
where N: SimpleArithmetic + Clone
{
let num = num.min(den);
let result_divisor_part = value.clone() / den.into() * num.into();
let result_remainder_part = {
let rem = value % den.into();
// Fits into u32 because den is u32 and remainder < den
let rem_u32 = rem.saturated_into::<u32>();
// Multiplication fits into u64 as both term are u32
let rem_part = rem_u32 as u64 * num as u64 / den as u64;
// Result fits into u32 as num < total_points
(rem_part as u32).into()
};
result_divisor_part + result_remainder_part
}
/// A `Convert` implementation that finds the stash of the given controller account,
/// if any.
pub struct StashOf<T>(rstd::marker::PhantomData<T>);