// Copyright (C) 2021 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! Benchmarking setup for pallet-collator-selection use super::*; #[allow(unused)] use crate::Pallet as CollatorSelection; use frame_benchmarking::{account, benchmarks, impl_benchmark_test_suite, whitelisted_caller}; use frame_support::{ assert_ok, codec::Decode, traits::{Currency, EnsureOrigin, Get}, }; use frame_system::{EventRecord, RawOrigin}; use pallet_authorship::EventHandler; use pallet_session::{self as session, SessionManager}; use sp_std::prelude::*; pub type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; const SEED: u32 = 0; // TODO: remove if this is given in substrate commit. macro_rules! whitelist { ($acc:ident) => { frame_benchmarking::benchmarking::add_to_whitelist( frame_system::Account::::hashed_key_for(&$acc).into(), ); }; } fn assert_last_event(generic_event: ::Event) { let events = frame_system::Pallet::::events(); let system_event: ::Event = generic_event.into(); // compare to the last event record let EventRecord { event, .. } = &events[events.len() - 1]; assert_eq!(event, &system_event); } fn create_funded_user( string: &'static str, n: u32, balance_factor: u32, ) -> T::AccountId { let user = account(string, n, SEED); let balance = T::Currency::minimum_balance() * balance_factor.into(); let _ = T::Currency::make_free_balance_be(&user, balance); user } fn keys(c: u32) -> ::Keys { use rand::{RngCore, SeedableRng}; let keys = { let mut keys = [0u8; 128]; if c > 0 { let mut rng = rand::rngs::StdRng::seed_from_u64(c as u64); rng.fill_bytes(&mut keys); } keys }; Decode::decode(&mut &keys[..]).unwrap() } fn validator(c: u32) -> (T::AccountId, ::Keys) { (create_funded_user::("candidate", c, 1000), keys::(c)) } fn register_validators(count: u32) { let validators = (0..count).map(|c| validator::(c)).collect::>(); for (who, keys) in validators { >::set_keys(RawOrigin::Signed(who).into(), keys, Vec::new()).unwrap(); } } fn register_candidates(count: u32) { let candidates = (0..count).map(|c| account("candidate", c, SEED)).collect::>(); assert!(>::get() > 0u32.into(), "Bond cannot be zero!"); for who in candidates { T::Currency::make_free_balance_be(&who, >::get() * 2u32.into()); >::register_as_candidate(RawOrigin::Signed(who).into()).unwrap(); } } benchmarks! { where_clause { where T: pallet_authorship::Config + session::Config } set_invulnerables { let b in 1 .. T::MaxInvulnerables::get(); let new_invulnerables = (0..b).map(|c| account("candidate", c, SEED)).collect::>(); let origin = T::UpdateOrigin::successful_origin(); }: { assert_ok!( >::set_invulnerables(origin, new_invulnerables.clone()) ); } verify { assert_last_event::(Event::NewInvulnerables(new_invulnerables).into()); } set_desired_candidates { let max: u32 = 999; let origin = T::UpdateOrigin::successful_origin(); }: { assert_ok!( >::set_desired_candidates(origin, max.clone()) ); } verify { assert_last_event::(Event::NewDesiredCandidates(max).into()); } set_candidacy_bond { let bond: BalanceOf = T::Currency::minimum_balance() * 10u32.into(); let origin = T::UpdateOrigin::successful_origin(); }: { assert_ok!( >::set_candidacy_bond(origin, bond.clone()) ); } verify { assert_last_event::(Event::NewCandidacyBond(bond).into()); } // worse case is when we have all the max-candidate slots filled except one, and we fill that // one. register_as_candidate { let c in 1 .. T::MaxCandidates::get(); >::put(T::Currency::minimum_balance()); >::put(c + 1); register_validators::(c); register_candidates::(c); let caller: T::AccountId = whitelisted_caller(); let bond: BalanceOf = T::Currency::minimum_balance() * 2u32.into(); T::Currency::make_free_balance_be(&caller, bond.clone()); >::set_keys( RawOrigin::Signed(caller.clone()).into(), keys::(c + 1), Vec::new() ).unwrap(); }: _(RawOrigin::Signed(caller.clone())) verify { assert_last_event::(Event::CandidateAdded(caller, bond / 2u32.into()).into()); } // worse case is the last candidate leaving. leave_intent { let c in (T::MinCandidates::get() + 1) .. T::MaxCandidates::get(); >::put(T::Currency::minimum_balance()); >::put(c); register_validators::(c); register_candidates::(c); let leaving = >::get().last().unwrap().who.clone(); whitelist!(leaving); }: _(RawOrigin::Signed(leaving.clone())) verify { assert_last_event::(Event::CandidateRemoved(leaving).into()); } // worse case is paying a non-existing candidate account. note_author { >::put(T::Currency::minimum_balance()); T::Currency::make_free_balance_be( &>::account_id(), T::Currency::minimum_balance() * 4u32.into(), ); let author = account("author", 0, SEED); let new_block: T::BlockNumber = 10u32.into(); frame_system::Pallet::::set_block_number(new_block); assert!(T::Currency::free_balance(&author) == 0u32.into()); }: { as EventHandler<_, _>>::note_author(author.clone()) } verify { assert!(T::Currency::free_balance(&author) > 0u32.into()); assert_eq!(frame_system::Pallet::::block_number(), new_block); } // worst case for new session. new_session { let r in 1 .. T::MaxCandidates::get(); let c in 1 .. T::MaxCandidates::get(); >::put(T::Currency::minimum_balance()); >::put(c); frame_system::Pallet::::set_block_number(0u32.into()); register_validators::(c); register_candidates::(c); let new_block: T::BlockNumber = 1800u32.into(); let zero_block: T::BlockNumber = 0u32.into(); let candidates = >::get(); let non_removals = c.saturating_sub(r); for i in 0..c { >::insert(candidates[i as usize].who.clone(), zero_block); } if non_removals > 0 { for i in 0..non_removals { >::insert(candidates[i as usize].who.clone(), new_block); } } else { for i in 0..c { >::insert(candidates[i as usize].who.clone(), new_block); } } let pre_length = >::get().len(); frame_system::Pallet::::set_block_number(new_block); assert!(>::get().len() == c as usize); }: { as SessionManager<_>>::new_session(0) } verify { if c > r && non_removals >= T::MinCandidates::get() { assert!(>::get().len() < pre_length); } else if c > r && non_removals < T::MinCandidates::get() { assert!(>::get().len() == T::MinCandidates::get() as usize); } else { assert!(>::get().len() == pre_length); } } } impl_benchmark_test_suite!(CollatorSelection, crate::mock::new_test_ext(), crate::mock::Test,);