// 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) }