use super::{BalancingConfig, Edge, ExtendedBalance, IdentifierT, Voter};
use crate::arithmetic::traits::Zero;
use alloc::vec::Vec;
pub fn balance<AccountId: IdentifierT>(
voters: &mut Vec<Voter<AccountId>>,
config: &BalancingConfig,
) -> usize {
if config.iterations == 0 {
return 0;
}
let mut iter = 0;
loop {
let mut max_diff = 0;
for voter in voters.iter_mut() {
let diff = balance_voter(voter, config.tolerance);
if diff > max_diff {
max_diff = diff;
}
}
iter += 1;
if max_diff <= config.tolerance || iter >= config.iterations {
break iter;
}
}
}
pub(crate) fn balance_voter<AccountId: IdentifierT>(
voter: &mut Voter<AccountId>,
tolerance: ExtendedBalance,
) -> ExtendedBalance {
let mut elected_edges = voter
.edges
.iter_mut()
.filter(|e| e.candidate.borrow().elected)
.collect::<Vec<&mut Edge<AccountId>>>();
if elected_edges.len() <= 1 {
return Zero::zero();
}
let stake_used =
elected_edges.iter().fold(0, |a: ExtendedBalance, e| a.saturating_add(e.weight));
let backed_stakes = elected_edges
.iter()
.map(|e| e.candidate.borrow().backed_stake)
.collect::<Vec<_>>();
let backing_backed_stake = elected_edges
.iter()
.filter_map(|e| if e.weight > 0 { Some(e.candidate.borrow().backed_stake) } else { None })
.collect::<Vec<_>>();
let 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");
let mut difference = max_stake.saturating_sub(*min_stake);
difference = difference.saturating_add(voter.budget.saturating_sub(stake_used));
if difference < tolerance {
return difference;
}
difference
} else {
voter.budget
};
for edge in elected_edges.iter_mut() {
let mut candidate = edge.candidate.borrow_mut();
candidate.backed_stake = candidate.backed_stake.saturating_sub(edge.weight);
edge.weight = 0;
}
elected_edges.sort_by_key(|e| e.candidate.borrow().backed_stake);
let mut cumulative_backed_stake = Zero::zero();
let mut last_index = elected_edges.len() - 1;
for (index, edge) in elected_edges.iter().enumerate() {
let index = index as ExtendedBalance;
let backed_stake = edge.candidate.borrow().backed_stake;
let temp = backed_stake.saturating_mul(index);
if temp.saturating_sub(cumulative_backed_stake) > voter.budget {
last_index = index.saturating_sub(1) as usize;
break;
}
cumulative_backed_stake = cumulative_backed_stake.saturating_add(backed_stake);
}
let last_stake = elected_edges
.get(last_index)
.expect(
"length of elected_edges is greater than or equal 2; last_index index is at the \
minimum elected_edges.len() - 1; index is within range; qed",
)
.candidate
.borrow()
.backed_stake;
let ways_to_split = last_index + 1;
let excess = voter
.budget
.saturating_add(cumulative_backed_stake)
.saturating_sub(last_stake.saturating_mul(ways_to_split as ExtendedBalance));
for edge in elected_edges.into_iter().take(ways_to_split) {
let candidate_backed_stake = {
let candidate = edge.candidate.borrow();
candidate.backed_stake
};
let new_edge_weight = (excess / ways_to_split as ExtendedBalance)
.saturating_add(last_stake)
.saturating_sub(candidate_backed_stake);
edge.weight = new_edge_weight;
let mut candidate = edge.candidate.borrow_mut();
candidate.backed_stake = candidate.backed_stake.saturating_add(new_edge_weight);
}
let _ = voter.try_normalize_elected();
difference
}