// 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 . //! Testing utils for staking. Needs the `testing-utils` feature to be enabled. //! //! Note that these helpers should NOT be used with the actual crate tests, but are rather designed //! for when the module is being externally tested (i.e. fuzzing, benchmarking, e2e tests). Enabling //! this feature in the current crate's Cargo.toml will leak all of this into a normal release //! build. Just don't do it. use crate::*; use codec::{Decode, Encode}; use frame_support::assert_ok; use frame_system::RawOrigin; use pallet_indices::address::Address; use rand::Rng; use sp_core::hashing::blake2_256; use sp_phragmen::{ build_support_map, evaluate_support, reduce, Assignment, PhragmenScore, StakedAssignment, }; const CTRL_PREFIX: u32 = 1000; const NOMINATOR_PREFIX: u32 = 1_000_000; /// A dummy suer. pub const USER: u32 = 999_999_999; /// Address type of the `T` pub type AddressOf = Address<::AccountId, u32>; /// Random number in the range `[a, b]`. pub fn random(a: u32, b: u32) -> u32 { rand::thread_rng().gen_range(a, b) } /// Set the desired validator count, with related storage items. pub fn set_validator_count(to_elect: u32) { ValidatorCount::put(to_elect); MinimumValidatorCount::put(to_elect / 2); >::put(ElectionStatus::Open(T::BlockNumber::from(1u32))); } /// Build an account with the given index. pub fn account(index: u32) -> T::AccountId { let entropy = (b"benchmark/staking", index).using_encoded(blake2_256); T::AccountId::decode(&mut &entropy[..]).unwrap_or_default() } /// Build an address given Index pub fn address(index: u32) -> AddressOf { pallet_indices::address::Address::Id(account::(index)) } /// Generate signed origin from `who`. pub fn signed(who: T::AccountId) -> T::Origin { RawOrigin::Signed(who).into() } /// Generate signed origin from `index`. pub fn signed_account(index: u32) -> T::Origin { signed::(account::(index)) } /// Bond a validator. pub fn bond_validator(stash: T::AccountId, ctrl: u32, val: BalanceOf) where T::Lookup: StaticLookup>, { let _ = T::Currency::make_free_balance_be(&stash, val); assert_ok!(>::bond( signed::(stash), address::(ctrl), val, RewardDestination::Controller )); assert_ok!(>::validate( signed_account::(ctrl), ValidatorPrefs::default() )); } pub fn bond_nominator( stash: T::AccountId, ctrl: u32, val: BalanceOf, target: Vec>, ) where T::Lookup: StaticLookup>, { let _ = T::Currency::make_free_balance_be(&stash, val); assert_ok!(>::bond( signed::(stash), address::(ctrl), val, RewardDestination::Controller )); assert_ok!(>::nominate(signed_account::(ctrl), target)); } /// Bond `nun_validators` validators and `num_nominator` nominators with `edge_per_voter` random /// votes per nominator. pub fn setup_chain_stakers(num_validators: u32, num_voters: u32, edge_per_voter: u32) where T::Lookup: StaticLookup>, { (0..num_validators).for_each(|i| { bond_validator::( account::(i), i + CTRL_PREFIX, >::from(random(1, 1000)) * T::Currency::minimum_balance(), ); }); (0..num_voters).for_each(|i| { let mut targets: Vec> = Vec::with_capacity(edge_per_voter as usize); let mut all_targets = (0..num_validators) .map(|t| address::(t)) .collect::>(); assert!(num_validators >= edge_per_voter); (0..edge_per_voter).for_each(|_| { let target = all_targets.remove(random(0, all_targets.len() as u32 - 1) as usize); targets.push(target); }); bond_nominator::( account::(i + NOMINATOR_PREFIX), i + NOMINATOR_PREFIX + CTRL_PREFIX, >::from(random(1, 1000)) * T::Currency::minimum_balance(), targets, ); }); >::create_stakers_snapshot(); } /// Build a _really bad_ but acceptable solution for election. This should always yield a solution /// which has a less score than the seq-phragmen. pub fn get_weak_solution( do_reduce: bool, ) -> (Vec, CompactAssignments, PhragmenScore) { let mut backing_stake_of: BTreeMap> = BTreeMap::new(); // self stake >::enumerate().for_each(|(who, _p)| { *backing_stake_of.entry(who.clone()).or_insert(Zero::zero()) += >::slashable_balance_of(&who) }); // add nominator stuff >::enumerate().for_each(|(who, nomination)| { nomination.targets.into_iter().for_each(|v| { *backing_stake_of.entry(v).or_insert(Zero::zero()) += >::slashable_balance_of(&who) }) }); // elect winners let mut sorted: Vec = backing_stake_of.keys().cloned().collect(); sorted.sort_by_key(|x| backing_stake_of.get(x).unwrap()); let winners: Vec = sorted .iter() .cloned() .take(>::validator_count() as usize) .collect(); let mut staked_assignments: Vec> = Vec::new(); >::enumerate().for_each(|(who, nomination)| { let mut dist: Vec<(T::AccountId, ExtendedBalance)> = Vec::new(); nomination.targets.into_iter().for_each(|v| { if winners.iter().find(|&w| *w == v).is_some() { dist.push((v, ExtendedBalance::zero())); } }); if dist.len() == 0 { return; } // assign real stakes. just split the stake. let stake = , u64>>::convert( >::slashable_balance_of(&who), ) as ExtendedBalance; let mut sum: ExtendedBalance = Zero::zero(); let dist_len = dist.len() as ExtendedBalance; // assign main portion // only take the first half into account. This should highly imbalance stuff, which is good. dist.iter_mut() .take(if dist_len > 1 { (dist_len as usize) / 2 } else { 1 }) .for_each(|(_, w)| { let partial = stake / dist_len; *w = partial; sum += partial; }); // assign the leftover to last. let leftover = stake - sum; let last = dist.last_mut().unwrap(); last.1 += leftover; staked_assignments.push(StakedAssignment { who, distribution: dist, }); }); // add self support to winners. winners.iter().for_each(|w| { staked_assignments.push(StakedAssignment { who: w.clone(), distribution: vec![( w.clone(), , u64>>::convert( >::slashable_balance_of(&w), ) as ExtendedBalance, )], }) }); if do_reduce { reduce(&mut staked_assignments); } // helpers for building the compact let snapshot_validators = >::snapshot_validators().unwrap(); let snapshot_nominators = >::snapshot_nominators().unwrap(); let nominator_index = |a: &T::AccountId| -> Option { snapshot_nominators .iter() .position(|x| x == a) .and_then(|i| >::try_into(i).ok()) }; let validator_index = |a: &T::AccountId| -> Option { snapshot_validators .iter() .position(|x| x == a) .and_then(|i| >::try_into(i).ok()) }; let stake_of = |who: &T::AccountId| -> ExtendedBalance { , u64>>::convert( >::slashable_balance_of(who), ) as ExtendedBalance }; // convert back to ratio assignment. This takes less space. let low_accuracy_assignment: Vec> = staked_assignments .into_iter() .map(|sa| sa.into_assignment(true)) .collect(); // re-calculate score based on what the chain will decode. let score = { let staked: Vec> = low_accuracy_assignment .iter() .map(|a| { let stake = stake_of(&a.who); a.clone().into_staked(stake, true) }) .collect(); let (support_map, _) = build_support_map::(winners.as_slice(), staked.as_slice()); evaluate_support::(&support_map) }; // compact encode the assignment. let compact = CompactAssignments::from_assignment( low_accuracy_assignment, nominator_index, validator_index, ) .unwrap(); // winners to index. let winners = winners .into_iter() .map(|w| { snapshot_validators .iter() .position(|v| *v == w) .unwrap() .try_into() .unwrap() }) .collect::>(); (winners, compact, score) } /// Create a solution for seq-phragmen. This uses the same internal function as used by the offchain /// worker code. pub fn get_seq_phragmen_solution( do_reduce: bool, ) -> (Vec, CompactAssignments, PhragmenScore) { let sp_phragmen::PhragmenResult { winners, assignments, } = >::do_phragmen::().unwrap(); offchain_election::prepare_submission::(assignments, winners, do_reduce).unwrap() } /// Remove all validator, nominators, votes and exposures. pub fn clean(era: EraIndex) where ::AccountId: codec::EncodeLike, u32: codec::EncodeLike, { >::enumerate().for_each(|(k, _)| { let ctrl = >::bonded(&k).unwrap(); >::remove(&k); >::remove(&k); >::remove(&ctrl); >::remove(k, era); }); >::enumerate().for_each(|(k, _)| >::remove(k)); >::remove_all(); >::remove_all(); >::kill(); QueuedScore::kill(); }