// This file is part of Substrate. // Copyright (C) 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 for pallet-fast-unstake. #![cfg(feature = "runtime-benchmarks")] use crate::{types::*, Pallet as FastUnstake, *}; use frame_benchmarking::v1::{benchmarks, whitelist_account, BenchmarkError}; use frame_support::{ assert_ok, traits::{Currency, EnsureOrigin, Get, Hooks}, }; use frame_system::RawOrigin; use sp_runtime::traits::Zero; use sp_staking::{EraIndex, StakingInterface}; use sp_std::prelude::*; const USER_SEED: u32 = 0; type CurrencyOf = ::Currency; fn create_unexposed_batch(batch_size: u32) -> Vec { (0..batch_size) .map(|i| { let account = frame_benchmarking::account::("unexposed_nominator", i, USER_SEED); fund_and_bond_account::(&account); account }) .collect() } fn fund_and_bond_account(account: &T::AccountId) { let stake = CurrencyOf::::minimum_balance() * 100u32.into(); CurrencyOf::::make_free_balance_be(&account, stake * 10u32.into()); // bond and nominate ourselves, this will guarantee that we are not backing anyone. assert_ok!(T::Staking::bond(account, stake, account)); assert_ok!(T::Staking::nominate(account, vec![account.clone()])); } pub(crate) fn fast_unstake_events() -> Vec> { frame_system::Pallet::::events() .into_iter() .map(|r| r.event) .filter_map(|e| ::RuntimeEvent::from(e).try_into().ok()) .collect::>() } fn setup_staking(v: u32, until: EraIndex) { let ed = CurrencyOf::::minimum_balance(); log!(debug, "registering {} validators and {} eras.", v, until); // our validators don't actually need to registered in staking -- just generate `v` random // accounts. let validators = (0..v) .map(|x| frame_benchmarking::account::("validator", x, USER_SEED)) .collect::>(); for era in 0..=until { let others = (0..T::Staking::max_exposure_page_size()) .map(|s| { let who = frame_benchmarking::account::("nominator", era, s.into()); let value = ed; (who, value) }) .collect::>(); validators.iter().for_each(|v| { T::Staking::add_era_stakers(&era, &v, others.clone()); }); } } fn on_idle_full_block() { let remaining_weight = ::BlockWeights::get().max_block; FastUnstake::::on_idle(Zero::zero(), remaining_weight); } benchmarks! { // on_idle, we don't check anyone, but fully unbond them. on_idle_unstake { let b in 1 .. T::BatchSize::get(); ErasToCheckPerBlock::::put(1); for who in create_unexposed_batch::(b).into_iter() { assert_ok!(FastUnstake::::register_fast_unstake( RawOrigin::Signed(who.clone()).into(), )); } // run on_idle once. This will check era 0. assert_eq!(Head::::get(), None); on_idle_full_block::(); assert!(matches!( Head::::get(), Some(UnstakeRequest { checked, stashes, .. }) if checked.len() == 1 && stashes.len() as u32 == b )); } : { on_idle_full_block::(); } verify { assert!(matches!( fast_unstake_events::().last(), Some(Event::BatchFinished { size: b }) )); } // on_idle, when we check some number of eras and the queue is already set. on_idle_check { let v in 1 .. 256; let b in 1 .. T::BatchSize::get(); let u = T::MaxErasToCheckPerBlock::get().min(T::Staking::bonding_duration()); ErasToCheckPerBlock::::put(u); T::Staking::set_current_era(u); // setup staking with v validators and u eras of data (0..=u+1) setup_staking::(v, u); let stashes = create_unexposed_batch::(b).into_iter().map(|s| { assert_ok!(FastUnstake::::register_fast_unstake( RawOrigin::Signed(s.clone()).into(), )); (s, T::Deposit::get()) }).collect::>(); // no one is queued thus far. assert_eq!(Head::::get(), None); Head::::put(UnstakeRequest { stashes: stashes.clone().try_into().unwrap(), checked: Default::default() }); } : { on_idle_full_block::(); } verify { let checked = (1..=u).rev().collect::>(); let request = Head::::get().unwrap(); assert_eq!(checked, request.checked.into_inner()); assert!(matches!( fast_unstake_events::().last(), Some(Event::BatchChecked { .. }) )); assert!(stashes.iter().all(|(s, _)| request.stashes.iter().find(|(ss, _)| ss == s).is_some())); } register_fast_unstake { ErasToCheckPerBlock::::put(1); let who = create_unexposed_batch::(1).get(0).cloned().unwrap(); whitelist_account!(who); assert_eq!(Queue::::count(), 0); } :_(RawOrigin::Signed(who.clone())) verify { assert_eq!(Queue::::count(), 1); } deregister { ErasToCheckPerBlock::::put(1); let who = create_unexposed_batch::(1).get(0).cloned().unwrap(); assert_ok!(FastUnstake::::register_fast_unstake( RawOrigin::Signed(who.clone()).into(), )); assert_eq!(Queue::::count(), 1); whitelist_account!(who); } :_(RawOrigin::Signed(who.clone())) verify { assert_eq!(Queue::::count(), 0); } control { let origin = ::ControlOrigin::try_successful_origin() .map_err(|_| BenchmarkError::Weightless)?; } : _(origin, T::MaxErasToCheckPerBlock::get()) verify {} impl_benchmark_test_suite!(Pallet, crate::mock::ExtBuilder::default().build(), crate::mock::Runtime) }