// 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. //! Offences pallet benchmarking. #![cfg(feature = "runtime-benchmarks")] #![cfg_attr(not(feature = "std"), no_std)] mod mock; use sp_std::{prelude::*, vec}; use frame_benchmarking::v1::{account, benchmarks}; use frame_support::traits::{Currency, Get}; use frame_system::{Config as SystemConfig, Pallet as System, RawOrigin}; use sp_runtime::{ traits::{Convert, Saturating, StaticLookup}, Perbill, }; use sp_staking::offence::ReportOffence; use pallet_babe::EquivocationOffence as BabeEquivocationOffence; use pallet_balances::Config as BalancesConfig; use pallet_grandpa::{ EquivocationOffence as GrandpaEquivocationOffence, TimeSlot as GrandpaTimeSlot, }; use pallet_offences::{Config as OffencesConfig, Pallet as Offences}; use pallet_session::{ historical::{Config as HistoricalConfig, IdentificationTuple}, Config as SessionConfig, Pallet as Session, SessionManager, }; use pallet_staking::{ Config as StakingConfig, Exposure, IndividualExposure, MaxNominationsOf, Pallet as Staking, RewardDestination, ValidatorPrefs, }; const SEED: u32 = 0; const MAX_NOMINATORS: u32 = 100; pub struct Pallet(Offences); pub trait Config: SessionConfig + StakingConfig + OffencesConfig + HistoricalConfig + BalancesConfig + IdTupleConvert { } /// A helper trait to make sure we can convert `IdentificationTuple` coming from historical /// and the one required by offences. pub trait IdTupleConvert { /// Convert identification tuple from `historical` trait to the one expected by `offences`. fn convert(id: IdentificationTuple) -> ::IdentificationTuple; } impl IdTupleConvert for T where ::IdentificationTuple: From>, { fn convert(id: IdentificationTuple) -> ::IdentificationTuple { id.into() } } type LookupSourceOf = <::Lookup as StaticLookup>::Source; type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; struct Offender { pub controller: T::AccountId, #[allow(dead_code)] pub stash: T::AccountId, #[allow(dead_code)] pub nominator_stashes: Vec, } fn bond_amount() -> BalanceOf { T::Currency::minimum_balance().saturating_mul(10_000u32.into()) } fn create_offender(n: u32, nominators: u32) -> Result, &'static str> { let stash: T::AccountId = account("stash", n, SEED); let stash_lookup: LookupSourceOf = T::Lookup::unlookup(stash.clone()); let reward_destination = RewardDestination::Staked; let amount = bond_amount::(); // add twice as much balance to prevent the account from being killed. let free_amount = amount.saturating_mul(2u32.into()); T::Currency::make_free_balance_be(&stash, free_amount); Staking::::bond( RawOrigin::Signed(stash.clone()).into(), amount, reward_destination.clone(), )?; let validator_prefs = ValidatorPrefs { commission: Perbill::from_percent(50), ..Default::default() }; Staking::::validate(RawOrigin::Signed(stash.clone()).into(), validator_prefs)?; let mut individual_exposures = vec![]; let mut nominator_stashes = vec![]; // Create n nominators for i in 0..nominators { let nominator_stash: T::AccountId = account("nominator stash", n * MAX_NOMINATORS + i, SEED); T::Currency::make_free_balance_be(&nominator_stash, free_amount); Staking::::bond( RawOrigin::Signed(nominator_stash.clone()).into(), amount, reward_destination.clone(), )?; let selected_validators: Vec> = vec![stash_lookup.clone()]; Staking::::nominate( RawOrigin::Signed(nominator_stash.clone()).into(), selected_validators, )?; individual_exposures .push(IndividualExposure { who: nominator_stash.clone(), value: amount }); nominator_stashes.push(nominator_stash.clone()); } let exposure = Exposure { total: amount * n.into(), own: amount, others: individual_exposures }; let current_era = 0u32; Staking::::add_era_stakers(current_era, stash.clone(), exposure); Ok(Offender { controller: stash.clone(), stash, nominator_stashes }) } fn make_offenders( num_offenders: u32, num_nominators: u32, ) -> Result<(Vec>, Vec>), &'static str> { Staking::::new_session(0); let mut offenders = vec![]; for i in 0..num_offenders { let offender = create_offender::(i + 1, num_nominators)?; offenders.push(offender); } Staking::::start_session(0); let id_tuples = offenders .iter() .map(|offender| { ::ValidatorIdOf::convert(offender.controller.clone()) .expect("failed to get validator id from account id") }) .map(|validator_id| { ::FullIdentificationOf::convert(validator_id.clone()) .map(|full_id| (validator_id, full_id)) .expect("failed to convert validator id to full identification") }) .collect::>>(); Ok((id_tuples, offenders)) } benchmarks! { report_offence_grandpa { let n in 0 .. MAX_NOMINATORS.min(MaxNominationsOf::::get()); // for grandpa equivocation reports the number of reporters // and offenders is always 1 let reporters = vec![account("reporter", 1, SEED)]; // make sure reporters actually get rewarded Staking::::set_slash_reward_fraction(Perbill::one()); let (mut offenders, raw_offenders) = make_offenders::(1, n)?; let validator_set_count = Session::::validators().len() as u32; let offence = GrandpaEquivocationOffence { time_slot: GrandpaTimeSlot { set_id: 0, round: 0 }, session_index: 0, validator_set_count, offender: T::convert(offenders.pop().unwrap()), }; assert_eq!(System::::event_count(), 0); }: { let _ = Offences::::report_offence(reporters, offence); } verify { // make sure that all slashes have been applied #[cfg(test)] assert_eq!( System::::event_count(), 0 + 1 // offence + 3 // reporter (reward + endowment) + 1 // offenders reported + 3 // offenders slashed + 1 // offenders chilled + 3 * n // nominators slashed ); } report_offence_babe { let n in 0 .. MAX_NOMINATORS.min(MaxNominationsOf::::get()); // for babe equivocation reports the number of reporters // and offenders is always 1 let reporters = vec![account("reporter", 1, SEED)]; // make sure reporters actually get rewarded Staking::::set_slash_reward_fraction(Perbill::one()); let (mut offenders, raw_offenders) = make_offenders::(1, n)?; let validator_set_count = Session::::validators().len() as u32; let offence = BabeEquivocationOffence { slot: 0u64.into(), session_index: 0, validator_set_count, offender: T::convert(offenders.pop().unwrap()), }; assert_eq!(System::::event_count(), 0); }: { let _ = Offences::::report_offence(reporters, offence); } verify { // make sure that all slashes have been applied #[cfg(test)] assert_eq!( System::::event_count(), 0 + 1 // offence + 3 // reporter (reward + endowment) + 1 // offenders reported + 3 // offenders slashed + 1 // offenders chilled + 3 * n // nominators slashed ); } impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test); }