mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-29 00:57:57 +00:00
60e5011c72
* Adding first rough ouline of the repository structure * Remove old CI stuff * add title * formatting fixes * move node-exits job's script to scripts dir * Move docs into subdir * move to bin * move maintainence scripts, configs and helpers into its own dir * add .local to ignore * move core->client * start up 'test' area * move test client * move test runtime * make test move compile * Add dependencies rule enforcement. * Fix indexing. * Update docs to reflect latest changes * Moving /srml->/paint * update docs * move client/sr-* -> primitives/ * clean old readme * remove old broken code in rhd * update lock * Step 1. * starting to untangle client * Fix after merge. * start splitting out client interfaces * move children and blockchain interfaces * Move trie and state-machine to primitives. * Fix WASM builds. * fixing broken imports * more interface moves * move backend and light to interfaces * move CallExecutor * move cli off client * moving around more interfaces * re-add consensus crates into the mix * fix subkey path * relieve client from executor * starting to pull out client from grandpa * move is_decendent_of out of client * grandpa still depends on client directly * lemme tests pass * rename srml->paint * Make it compile. * rename interfaces->client-api * Move keyring to primitives. * fixup libp2p dep * fix broken use * allow dependency enforcement to fail * move fork-tree * Moving wasm-builder * make env * move build-script-utils * fixup broken crate depdencies and names * fix imports for authority discovery * fix typo * update cargo.lock * fixing imports * Fix paths and add missing crates * re-add missing crates
536 lines
19 KiB
Rust
536 lines
19 KiB
Rust
// Copyright 2019 Parity Technologies (UK) Ltd.
|
|
// This file is part of Substrate.
|
|
|
|
// Substrate is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
|
|
// Substrate is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
//! Rust implementation of the Phragmén election algorithm. This is used in several SRML modules to
|
|
//! optimally distribute the weight of a set of voters among an elected set of candidates. In the
|
|
//! context of staking this is mapped to validators and nominators.
|
|
//!
|
|
//! The algorithm has two phases:
|
|
//! - Sequential phragmen: performed in [`elect`] function which is first pass of the distribution
|
|
//! The results are not optimal but the execution time is less.
|
|
//! - Equalize post-processing: tries to further distribute the weight fairly among candidates.
|
|
//! Incurs more execution time.
|
|
//!
|
|
//! The main objective of the assignments done by phragmen is to maximize the minimum backed
|
|
//! candidate in the elected set.
|
|
//!
|
|
//! Reference implementation: https://github.com/w3f/consensus
|
|
//! Further details:
|
|
//! https://research.web3.foundation/en/latest/polkadot/NPoS/4.%20Sequential%20Phragm%C3%A9n%E2%80%99s%20method/
|
|
|
|
#![cfg_attr(not(feature = "std"), no_std)]
|
|
|
|
use rstd::{prelude::*, collections::btree_map::BTreeMap};
|
|
use sr_primitives::RuntimeDebug;
|
|
use sr_primitives::{helpers_128bit::multiply_by_rational, Perbill, Rational128};
|
|
use sr_primitives::traits::{Zero, Convert, Member, SimpleArithmetic, Saturating, Bounded};
|
|
|
|
mod mock;
|
|
mod tests;
|
|
|
|
/// A type in which performing operations on balances and stakes of candidates and voters are safe.
|
|
///
|
|
/// This module's functions expect a `Convert` type to convert all balances to u64. Hence, u128 is
|
|
/// a safe type for arithmetic operations over them.
|
|
///
|
|
/// Balance types converted to `ExtendedBalance` are referred to as `Votes`.
|
|
pub type ExtendedBalance = u128;
|
|
|
|
/// 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;
|
|
const DEN: u128 = u128::max_value();
|
|
|
|
/// A candidate entity for phragmen election.
|
|
#[derive(Clone, Default, RuntimeDebug)]
|
|
pub struct Candidate<AccountId> {
|
|
/// Identifier.
|
|
pub who: AccountId,
|
|
/// Intermediary value used to sort candidates.
|
|
pub score: Rational128,
|
|
/// Sum of the stake of this candidate based on received votes.
|
|
approval_stake: ExtendedBalance,
|
|
/// Flag for being elected.
|
|
elected: bool,
|
|
}
|
|
|
|
/// A voter entity.
|
|
#[derive(Clone, Default, RuntimeDebug)]
|
|
pub struct Voter<AccountId> {
|
|
/// Identifier.
|
|
who: AccountId,
|
|
/// List of candidates proposed by this voter.
|
|
edges: Vec<Edge<AccountId>>,
|
|
/// The stake of this voter.
|
|
budget: ExtendedBalance,
|
|
/// Incremented each time a candidate that this voter voted for has been elected.
|
|
load: Rational128,
|
|
}
|
|
|
|
/// A candidate being backed by a voter.
|
|
#[derive(Clone, Default, RuntimeDebug)]
|
|
pub struct Edge<AccountId> {
|
|
/// Identifier.
|
|
who: AccountId,
|
|
/// Load of this vote.
|
|
load: Rational128,
|
|
/// Index of the candidate stored in the 'candidates' vector.
|
|
candidate_index: usize,
|
|
}
|
|
|
|
/// Means a particular `AccountId` was backed by `Perbill`th of a nominator's stake.
|
|
pub type PhragmenAssignment<AccountId> = (AccountId, Perbill);
|
|
|
|
/// Means a 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> {
|
|
/// Just winners zipped with their approval stake. Note that the approval stake is merely the
|
|
/// sub of their received stake and could be used for very basic sorting and approval voting.
|
|
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>>)>
|
|
}
|
|
|
|
/// A structure to demonstrate the phragmen result from the perspective of the candidate, i.e. how
|
|
/// much support each candidate is receiving.
|
|
///
|
|
/// This complements the [`PhragmenResult`] and is needed to run the equalize post-processing.
|
|
///
|
|
/// This, at the current version, resembles the `Exposure` defined in the staking SRML module, yet
|
|
/// they do not necessarily have to be the same.
|
|
#[derive(Default, RuntimeDebug)]
|
|
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
|
|
pub struct Support<AccountId> {
|
|
/// The amount of support as the effect of self-vote.
|
|
pub own: ExtendedBalance,
|
|
/// Total support.
|
|
pub total: ExtendedBalance,
|
|
/// Support from voters.
|
|
pub others: Vec<PhragmenStakedAssignment<AccountId>>,
|
|
}
|
|
|
|
/// A linkage from a candidate and its [`Support`].
|
|
pub type SupportMap<A> = BTreeMap<A, Support<A>>;
|
|
|
|
/// Perform election based on Phragmén algorithm.
|
|
///
|
|
/// Returns an `Option` the set of winners and their detailed support ratio from each voter if
|
|
/// enough candidates are provided. Returns `None` otherwise.
|
|
///
|
|
/// * `candidate_count`: number of candidates to elect.
|
|
/// * `minimum_candidate_count`: minimum number of candidates to elect. If less candidates exist,
|
|
/// `None` is returned.
|
|
/// * `initial_candidates`: candidates list to be elected from.
|
|
/// * `initial_voters`: voters list.
|
|
/// * `stake_of`: something that can return the stake stake of a particular candidate or voter.
|
|
///
|
|
/// This function does not strip out candidates who do not have any backing stake. It is the
|
|
/// responsibility of the caller to make sure only those candidates who have a sensible economic
|
|
/// value are passed in. From the perspective of this function, a candidate can easily be among the
|
|
/// winner with no backing stake.
|
|
pub fn elect<AccountId, Balance, FS, C>(
|
|
candidate_count: usize,
|
|
minimum_candidate_count: usize,
|
|
initial_candidates: Vec<AccountId>,
|
|
initial_voters: Vec<(AccountId, Vec<AccountId>)>,
|
|
stake_of: FS,
|
|
) -> Option<PhragmenResult<AccountId>> where
|
|
AccountId: Default + Ord + Member,
|
|
Balance: Default + Copy + SimpleArithmetic,
|
|
for<'r> FS: Fn(&'r AccountId) -> Balance,
|
|
C: Convert<Balance, u64> + Convert<u128, Balance>,
|
|
{
|
|
let to_votes = |b: Balance| <C as Convert<Balance, u64>>::convert(b) as ExtendedBalance;
|
|
|
|
// return structures
|
|
let mut elected_candidates: Vec<(AccountId, ExtendedBalance)>;
|
|
let mut assigned: Vec<(AccountId, Vec<PhragmenAssignment<AccountId>>)>;
|
|
|
|
// used to cache and access candidates index.
|
|
let mut c_idx_cache = BTreeMap::<AccountId, usize>::new();
|
|
|
|
// voters list.
|
|
let num_voters = initial_candidates.len() + initial_voters.len();
|
|
let mut voters: Vec<Voter<AccountId>> = Vec::with_capacity(num_voters);
|
|
|
|
// Iterate once to create a cache of candidates indexes. This could be optimized by being
|
|
// provided by the call site.
|
|
let mut candidates = initial_candidates
|
|
.into_iter()
|
|
.enumerate()
|
|
.map(|(idx, who)| {
|
|
c_idx_cache.insert(who.clone(), idx);
|
|
Candidate { who, ..Default::default() }
|
|
})
|
|
.collect::<Vec<Candidate<AccountId>>>();
|
|
|
|
// early return if we don't have enough candidates
|
|
if candidates.len() < minimum_candidate_count { return None; }
|
|
|
|
// collect voters. use `c_idx_cache` for fast access and aggregate `approval_stake` of
|
|
// candidates.
|
|
voters.extend(initial_voters.into_iter().map(|(who, votes)| {
|
|
let voter_stake = stake_of(&who);
|
|
let mut edges: Vec<Edge<AccountId>> = Vec::with_capacity(votes.len());
|
|
for v in votes {
|
|
if let Some(idx) = c_idx_cache.get(&v) {
|
|
// This candidate is valid + already cached.
|
|
candidates[*idx].approval_stake = candidates[*idx].approval_stake
|
|
.saturating_add(to_votes(voter_stake));
|
|
edges.push(Edge { who: v.clone(), candidate_index: *idx, ..Default::default() });
|
|
} // else {} would be wrong votes. We don't really care about it.
|
|
}
|
|
Voter {
|
|
who,
|
|
edges: edges,
|
|
budget: to_votes(voter_stake),
|
|
load: Rational128::zero(),
|
|
}
|
|
}));
|
|
|
|
|
|
// we have already checked that we have more candidates than minimum_candidate_count.
|
|
// run phragmen.
|
|
let to_elect = candidate_count.min(candidates.len());
|
|
elected_candidates = Vec::with_capacity(candidate_count);
|
|
assigned = Vec::with_capacity(candidate_count);
|
|
|
|
// main election loop
|
|
for _round in 0..to_elect {
|
|
// loop 1: initialize score
|
|
for c in &mut candidates {
|
|
if !c.elected {
|
|
// 1 / approval_stake == (DEN / approval_stake) / DEN. If approval_stake is zero,
|
|
// then the ratio should be as large as possible, essentially `infinity`.
|
|
if c.approval_stake.is_zero() {
|
|
c.score = Rational128::from_unchecked(DEN, 0);
|
|
} else {
|
|
c.score = Rational128::from(DEN / c.approval_stake, DEN);
|
|
}
|
|
}
|
|
}
|
|
|
|
// loop 2: increment score
|
|
for n in &voters {
|
|
for e in &n.edges {
|
|
let c = &mut candidates[e.candidate_index];
|
|
if !c.elected && !c.approval_stake.is_zero() {
|
|
let temp_n = multiply_by_rational(
|
|
n.load.n(),
|
|
n.budget,
|
|
c.approval_stake,
|
|
).unwrap_or(Bounded::max_value());
|
|
let temp_d = n.load.d();
|
|
let temp = Rational128::from(temp_n, temp_d);
|
|
c.score = c.score.lazy_saturating_add(temp);
|
|
}
|
|
}
|
|
}
|
|
|
|
// loop 3: find the best
|
|
if let Some(winner) = candidates
|
|
.iter_mut()
|
|
.filter(|c| !c.elected)
|
|
.min_by_key(|c| c.score)
|
|
{
|
|
// loop 3: update voter and edge load
|
|
winner.elected = true;
|
|
for n in &mut voters {
|
|
for e in &mut n.edges {
|
|
if e.who == winner.who {
|
|
e.load = winner.score.lazy_saturating_sub(n.load);
|
|
n.load = winner.score;
|
|
}
|
|
}
|
|
}
|
|
|
|
elected_candidates.push((winner.who.clone(), winner.approval_stake));
|
|
} else {
|
|
break
|
|
}
|
|
} // end of all rounds
|
|
|
|
// update backing stake of candidates and voters
|
|
for n in &mut voters {
|
|
let mut assignment = (n.who.clone(), vec![]);
|
|
for e in &mut n.edges {
|
|
if elected_candidates.iter().position(|(ref c, _)| *c == e.who).is_some() {
|
|
let per_bill_parts =
|
|
{
|
|
if n.load == e.load {
|
|
// Full support. No need to calculate.
|
|
Perbill::accuracy().into()
|
|
} else {
|
|
if e.load.d() == n.load.d() {
|
|
// return e.load / n.load.
|
|
let desired_scale: u128 = Perbill::accuracy().into();
|
|
multiply_by_rational(
|
|
desired_scale,
|
|
e.load.n(),
|
|
n.load.n(),
|
|
).unwrap_or(Bounded::max_value())
|
|
} else {
|
|
// defensive only. Both edge and nominator loads are built from
|
|
// scores, hence MUST have the same denominator.
|
|
Zero::zero()
|
|
}
|
|
}
|
|
};
|
|
// safer to .min() inside as well to argue as u32 is safe.
|
|
let per_thing = Perbill::from_parts(
|
|
per_bill_parts.min(Perbill::accuracy().into()) as u32
|
|
);
|
|
assignment.1.push((e.who.clone(), per_thing));
|
|
}
|
|
}
|
|
|
|
if assignment.1.len() > 0 {
|
|
// To ensure an assertion indicating: no stake from the nominator going to waste,
|
|
// we add a minimal post-processing to equally assign all of the leftover stake ratios.
|
|
let vote_count = assignment.1.len() as u32;
|
|
let len = assignment.1.len();
|
|
let sum = assignment.1.iter()
|
|
.map(|a| a.1.deconstruct())
|
|
.sum::<u32>();
|
|
let accuracy = Perbill::accuracy();
|
|
let diff = accuracy.checked_sub(sum).unwrap_or(0);
|
|
let diff_per_vote = (diff / vote_count).min(accuracy);
|
|
|
|
if diff_per_vote > 0 {
|
|
for i in 0..len {
|
|
let current_ratio = assignment.1[i % len].1;
|
|
let next_ratio = current_ratio
|
|
.saturating_add(Perbill::from_parts(diff_per_vote));
|
|
assignment.1[i % len].1 = next_ratio;
|
|
}
|
|
}
|
|
|
|
// `remainder` is set to be less than maximum votes of a nominator (currently 16).
|
|
// safe to cast it to usize.
|
|
let remainder = diff - diff_per_vote * vote_count;
|
|
for i in 0..remainder as usize {
|
|
let current_ratio = assignment.1[i % len].1;
|
|
let next_ratio = current_ratio.saturating_add(Perbill::from_parts(1));
|
|
assignment.1[i % len].1 = next_ratio;
|
|
}
|
|
assigned.push(assignment);
|
|
}
|
|
}
|
|
|
|
Some(PhragmenResult {
|
|
winners: elected_candidates,
|
|
assignments: assigned,
|
|
})
|
|
}
|
|
|
|
/// Build the support map from the given phragmen result.
|
|
pub fn build_support_map<Balance, AccountId, FS, C>(
|
|
elected_stashes: &Vec<AccountId>,
|
|
assignments: &Vec<(AccountId, Vec<PhragmenAssignment<AccountId>>)>,
|
|
stake_of: FS,
|
|
) -> SupportMap<AccountId> where
|
|
AccountId: Default + Ord + Member,
|
|
Balance: Default + Copy + SimpleArithmetic,
|
|
C: Convert<Balance, u64> + Convert<u128, Balance>,
|
|
for<'r> FS: Fn(&'r AccountId) -> Balance,
|
|
{
|
|
let to_votes = |b: Balance| <C as Convert<Balance, u64>>::convert(b) as ExtendedBalance;
|
|
// Initialize the support of each candidate.
|
|
let mut supports = <SupportMap<AccountId>>::new();
|
|
elected_stashes
|
|
.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;
|
|
if let Some(support) = supports.get_mut(c) {
|
|
if c == n {
|
|
// This is a nomination from `n` to themselves. This will increase both the
|
|
// `own` and `total` field.
|
|
debug_assert!(*per_thing == Perbill::one()); // TODO: deal with this: do we want it?
|
|
support.own = support.own.saturating_add(other_stake);
|
|
support.total = support.total.saturating_add(other_stake);
|
|
} else {
|
|
// This is a nomination from `n` to someone else. Increase `total` and add an entry
|
|
// inside `others`.
|
|
// 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));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
supports
|
|
}
|
|
|
|
/// Performs equalize post-processing to the output of the election algorithm. This happens in
|
|
/// rounds. The number of rounds and the maximum diff-per-round tolerance can be tuned through input
|
|
/// parameters.
|
|
///
|
|
/// 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.
|
|
pub fn equalize<Balance, AccountId, C, FS>(
|
|
mut assignments: Vec<(AccountId, Vec<PhragmenStakedAssignment<AccountId>>)>,
|
|
supports: &mut SupportMap<AccountId>,
|
|
tolerance: ExtendedBalance,
|
|
iterations: usize,
|
|
stake_of: FS,
|
|
) where
|
|
C: Convert<Balance, u64> + Convert<u128, Balance>,
|
|
for<'r> FS: Fn(&'r AccountId) -> Balance,
|
|
AccountId: Ord + Clone,
|
|
{
|
|
// prepare the data for equalise
|
|
for _i in 0..iterations {
|
|
let mut max_diff = 0;
|
|
|
|
for (voter, assignment) in assignments.iter_mut() {
|
|
let voter_budget = stake_of(&voter);
|
|
|
|
let diff = do_equalize::<_, _, C>(
|
|
voter,
|
|
voter_budget,
|
|
assignment,
|
|
supports,
|
|
tolerance,
|
|
);
|
|
if diff > max_diff { max_diff = diff; }
|
|
}
|
|
|
|
if max_diff < tolerance {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// actually perform equalize. same interface is `equalize`. Just called in loops with a check for
|
|
/// maximum difference.
|
|
fn do_equalize<Balance, AccountId, C>(
|
|
voter: &AccountId,
|
|
budget_balance: Balance,
|
|
elected_edges: &mut Vec<PhragmenStakedAssignment<AccountId>>,
|
|
support_map: &mut SupportMap<AccountId>,
|
|
tolerance: ExtendedBalance
|
|
) -> ExtendedBalance where
|
|
C: Convert<Balance, u64> + Convert<u128, Balance>,
|
|
AccountId: Ord + Clone,
|
|
{
|
|
let to_votes = |b: Balance|
|
|
<C as Convert<Balance, u64>>::convert(b) as ExtendedBalance;
|
|
let budget = to_votes(budget_balance);
|
|
|
|
// Nothing to do. This voter had nothing useful.
|
|
// Defensive only. Assignment list should always be populated.
|
|
if elected_edges.is_empty() { return 0; }
|
|
|
|
let stake_used = elected_edges
|
|
.iter()
|
|
.fold(0 as ExtendedBalance, |s, e| s.saturating_add(e.1));
|
|
|
|
let backed_stakes_iter = elected_edges
|
|
.iter()
|
|
.filter_map(|e| support_map.get(&e.0))
|
|
.map(|e| e.total);
|
|
|
|
let backing_backed_stake = elected_edges
|
|
.iter()
|
|
.filter(|e| e.1 > 0)
|
|
.filter_map(|e| support_map.get(&e.0))
|
|
.map(|e| e.total)
|
|
.collect::<Vec<ExtendedBalance>>();
|
|
|
|
let mut difference;
|
|
if backing_backed_stake.len() > 0 {
|
|
let max_stake = backing_backed_stake
|
|
.iter()
|
|
.max()
|
|
.expect("vector with positive length will have a max; qed");
|
|
let min_stake = backed_stakes_iter
|
|
.min()
|
|
.expect("iterator with positive length will have a min; qed");
|
|
|
|
difference = max_stake.saturating_sub(min_stake);
|
|
difference = difference.saturating_add(budget.saturating_sub(stake_used));
|
|
if difference < tolerance {
|
|
return difference;
|
|
}
|
|
} else {
|
|
difference = budget;
|
|
}
|
|
|
|
// Undo updates to support
|
|
elected_edges.iter_mut().for_each(|e| {
|
|
if let Some(support) = support_map.get_mut(&e.0) {
|
|
support.total = support.total.saturating_sub(e.1);
|
|
support.others.retain(|i_support| i_support.0 != *voter);
|
|
}
|
|
e.1 = 0;
|
|
});
|
|
|
|
elected_edges.sort_unstable_by_key(|e|
|
|
if let Some(e) = support_map.get(&e.0) { e.total } else { Zero::zero() }
|
|
);
|
|
|
|
let mut cumulative_stake: ExtendedBalance = 0;
|
|
let mut last_index = elected_edges.len() - 1;
|
|
let mut idx = 0usize;
|
|
for e in &mut elected_edges[..] {
|
|
if let Some(support) = support_map.get_mut(&e.0) {
|
|
let stake = support.total;
|
|
let stake_mul = stake.saturating_mul(idx as ExtendedBalance);
|
|
let stake_sub = stake_mul.saturating_sub(cumulative_stake);
|
|
if stake_sub > budget {
|
|
last_index = idx.checked_sub(1).unwrap_or(0);
|
|
break;
|
|
}
|
|
cumulative_stake = cumulative_stake.saturating_add(stake);
|
|
}
|
|
idx += 1;
|
|
}
|
|
|
|
let last_stake = elected_edges[last_index].1;
|
|
let split_ways = last_index + 1;
|
|
let excess = budget
|
|
.saturating_add(cumulative_stake)
|
|
.saturating_sub(last_stake.saturating_mul(split_ways as ExtendedBalance));
|
|
elected_edges.iter_mut().take(split_ways).for_each(|e| {
|
|
if let Some(support) = support_map.get_mut(&e.0) {
|
|
e.1 = (excess / split_ways as ExtendedBalance)
|
|
.saturating_add(last_stake)
|
|
.saturating_sub(support.total);
|
|
support.total = support.total.saturating_add(e.1);
|
|
support.others.push((voter.clone(), e.1));
|
|
}
|
|
});
|
|
|
|
difference
|
|
}
|