// 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 .
//! # Running
//!
//! Run with `cargo hfuzz run reduce`. `honggfuzz`.
//!
//! # Debugging a panic
//!
//! Once a panic is found, it can be debugged with
//! `cargo hfuzz run-debug reduce hfuzz_workspace/reduce/*.fuzz`.
use honggfuzz::fuzz;
use sp_phragmen::{StakedAssignment, ExtendedBalance, build_support_map, reduce};
use rand::{self, Rng};
type Balance = u128;
type AccountId = u64;
/// Or any other token type.
const KSM: Balance = 1_000_000_000_000;
fn main() {
loop {
fuzz!(|_data: _| {
let (assignments, winners) = generate_random_phragmen_assignment(
rr(100, 1000),
rr(100, 2000),
8,
8,
);
reduce_and_compare(&assignments, &winners);
});
}
}
fn generate_random_phragmen_assignment(
voter_count: usize,
target_count: usize,
avg_edge_per_voter: usize,
edge_per_voter_var: usize,
) -> (Vec>, Vec) {
// random in range of (a, b)
let rr_128 = |a: u128, b: u128| -> u128 { rand::thread_rng().gen_range(a, b) };
// prefix to distinguish the voter and target account ranges.
let target_prefix = 1_000_000;
// let target_prefix = 1000;
assert!(voter_count < target_prefix);
let mut assignments = Vec::with_capacity(voter_count as usize);
let mut winners: Vec = Vec::new();
let all_targets = (target_prefix..(target_prefix + target_count))
.map(|a| a as AccountId)
.collect::>();
(1..=voter_count).for_each(|acc| {
let mut targets_to_chose_from = all_targets.clone();
let targets_to_chose = if edge_per_voter_var > 0 { rr(
avg_edge_per_voter - edge_per_voter_var,
avg_edge_per_voter + edge_per_voter_var,
) } else { avg_edge_per_voter };
let distribution = (0..targets_to_chose).map(|_| {
let target = targets_to_chose_from.remove(rr(0, targets_to_chose_from.len()));
if winners.iter().find(|w| **w == target).is_none() {
winners.push(target.clone());
}
(target, rr_128(1 * KSM, 100 * KSM))
}).collect::>();
assignments.push(StakedAssignment {
who: (acc as AccountId),
distribution,
});
});
(assignments, winners)
}
fn assert_assignments_equal(
winners: &Vec,
ass1: &Vec>,
ass2: &Vec>,
) {
let (support_1, _) = build_support_map::(winners, ass1);
let (support_2, _) = build_support_map::(winners, ass2);
for (who, support) in support_1.iter() {
assert_eq!(support.total, support_2.get(who).unwrap().total);
}
}
fn reduce_and_compare(
assignment: &Vec>,
winners: &Vec,
) {
let mut altered_assignment = assignment.clone();
let n = assignment.len() as u32;
let m = winners.len() as u32;
let edges_before = assignment_len(&assignment);
let num_changed = reduce(&mut altered_assignment);
let edges_after = edges_before - num_changed;
assert!(
edges_after <= m + n,
"reduce bound not satisfied. n = {}, m = {}, edges after reduce = {} (removed {})",
n,
m,
edges_after,
num_changed,
);
assert_assignments_equal(
winners,
&assignment,
&altered_assignment,
);
}
fn assignment_len(assignments: &[StakedAssignment]) -> u32 {
let mut counter = 0;
assignments.iter().for_each(|x| x.distribution.iter().for_each(|_| counter += 1));
counter
}
fn rr(a: usize, b: usize) -> usize {
rand::thread_rng().gen_range(a, b)
}