From f8c90b2a01ec77579bccd21ae17bd6ff2eeffd6a Mon Sep 17 00:00:00 2001 From: ordian <write@reusable.software> Date: Thu, 13 Mar 2025 04:39:41 +0100 Subject: [PATCH] staking: add `manual_slash` extrinsic (#7805) This PR adds a convenience extrinsic `manual_slash` for the governance to slash a validator manually. ## Changes * The `on_offence` implementation for the Staking pallet accepts a slice of `OffenceDetails` including the full validator exposure, however, it simply [ignores](https://github.com/paritytech/polkadot-sdk/blob/c8d33396345237c1864dfc0a9b2172b7dfe7ac8f/substrate/frame/staking/src/pallet/impls.rs#L1864) that part. I've extracted the functionality into an inherent `on_offence` method that takes `OffenceDetails` without the full exposure and this is called directly in `manual_slash` * `manual_slash` creates an offence for a validator with a given slash percentange ## Questions - [x] should `manual_slash` accept session instead of an era when the validator was in the active set? staking thinks in terms of eras and we can check out of bounds this way, which is why it was chosen for this implementation, but if there are arguments against, happy to change to session index - [X] should the accepted origin be something more than just root? Changed to `T::AdminOrigin` to align with `cancel_deferred_slash` - [X] should I adapt this PR also against https://github.com/paritytech/polkadot-sdk/pull/6996? looking at the changes, it should apply mostly without conflicts --------- Co-authored-by: Tsvetomir Dimitrov <tsvetomir@parity.io> Co-authored-by: Ankan <10196091+Ank4n@users.noreply.github.com> --- .../westend/src/weights/pallet_staking.rs | 29 +++ prdoc/pr_7805.prdoc | 11 ++ .../test-staking-e2e/src/mock.rs | 2 +- substrate/frame/staking/src/benchmarking.rs | 27 +++ substrate/frame/staking/src/mock.rs | 8 +- substrate/frame/staking/src/pallet/impls.rs | 22 ++- substrate/frame/staking/src/pallet/mod.rs | 58 ++++++ substrate/frame/staking/src/tests.rs | 186 +++++++++++++++++- substrate/frame/staking/src/weights.rs | 59 ++++++ 9 files changed, 394 insertions(+), 8 deletions(-) create mode 100644 prdoc/pr_7805.prdoc diff --git a/polkadot/runtime/westend/src/weights/pallet_staking.rs b/polkadot/runtime/westend/src/weights/pallet_staking.rs index 5a176c76b6e..496bc01e5e3 100644 --- a/polkadot/runtime/westend/src/weights/pallet_staking.rs +++ b/polkadot/runtime/westend/src/weights/pallet_staking.rs @@ -875,4 +875,33 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> { .saturating_add(T::DbWeight::get().reads(457)) .saturating_add(T::DbWeight::get().writes(261)) } + /// Storage: `Staking::CurrentEra` (r:1 w:0) + /// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::ErasStartSessionIndex` (r:1 w:0) + /// Proof: `Staking::ErasStartSessionIndex` (`max_values`: None, `max_size`: Some(16), added: 2491, mode: `MaxEncodedLen`) + /// Storage: `Staking::ActiveEra` (r:1 w:0) + /// Proof: `Staking::ActiveEra` (`max_values`: Some(1), `max_size`: Some(13), added: 508, mode: `MaxEncodedLen`) + /// Storage: `Staking::Invulnerables` (r:1 w:0) + /// Proof: `Staking::Invulnerables` (`max_values`: Some(1), `max_size`: Some(641), added: 1136, mode: `MaxEncodedLen`) + /// Storage: `Staking::ErasStakersOverview` (r:1 w:0) + /// Proof: `Staking::ErasStakersOverview` (`max_values`: None, `max_size`: Some(92), added: 2567, mode: `MaxEncodedLen`) + /// Storage: `Session::DisabledValidators` (r:1 w:1) + /// Proof: `Session::DisabledValidators` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `Session::Validators` (r:1 w:0) + /// Proof: `Session::Validators` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `Staking::ValidatorSlashInEra` (r:1 w:1) + /// Proof: `Staking::ValidatorSlashInEra` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) + /// Storage: `Staking::OffenceQueue` (r:1 w:1) + /// Proof: `Staking::OffenceQueue` (`max_values`: None, `max_size`: Some(101), added: 2576, mode: `MaxEncodedLen`) + /// Storage: `Staking::OffenceQueueEras` (r:1 w:1) + /// Proof: `Staking::OffenceQueueEras` (`max_values`: Some(1), `max_size`: Some(2690), added: 3185, mode: `MaxEncodedLen`) + fn manual_slash() -> Weight { + // Proof Size summary in bytes: + // Measured: `514` + // Estimated: `4175` + // Minimum execution time: 30_000_000 picoseconds. + Weight::from_parts(33_000_000, 4175) + .saturating_add(T::DbWeight::get().reads(10_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } } diff --git a/prdoc/pr_7805.prdoc b/prdoc/pr_7805.prdoc new file mode 100644 index 00000000000..1de4fcd0859 --- /dev/null +++ b/prdoc/pr_7805.prdoc @@ -0,0 +1,11 @@ +title: New `staking::manual_slash` extrinsic + +doc: + - audience: Runtime Dev + description: A new `manual_slash` extrinsic that allows slashing a validator's stake manually by governance. + +crates: + - name: pallet-staking + bump: major + - name: westend-runtime + bump: major diff --git a/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs b/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs index aa9b314d006..e4b77975707 100644 --- a/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs +++ b/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs @@ -899,7 +899,7 @@ pub(crate) fn on_offence_now( slash_fraction: &[Perbill], ) { let now = ActiveEra::<Runtime>::get().unwrap().index; - let _ = Staking::on_offence( + let _ = <Staking as OnOffenceHandler<_, _, _>>::on_offence( offenders, slash_fraction, ErasStartSessionIndex::<Runtime>::get(now).unwrap(), diff --git a/substrate/frame/staking/src/benchmarking.rs b/substrate/frame/staking/src/benchmarking.rs index 1978449bb4b..c4299449196 100644 --- a/substrate/frame/staking/src/benchmarking.rs +++ b/substrate/frame/staking/src/benchmarking.rs @@ -1189,6 +1189,33 @@ mod benchmarks { Ok(()) } + #[benchmark] + fn manual_slash() -> Result<(), BenchmarkError> { + let era = EraIndex::zero(); + CurrentEra::<T>::put(era); + ErasStartSessionIndex::<T>::insert(era, 0); + ActiveEra::<T>::put(ActiveEraInfo { index: era, start: None }); + + // Create a validator with nominators + let (validator_stash, _nominators) = create_validator_with_nominators::<T>( + T::MaxExposurePageSize::get() as u32, + T::MaxExposurePageSize::get() as u32, + false, + true, + RewardDestination::Staked, + era, + )?; + + let slash_fraction = Perbill::from_percent(10); + + #[extrinsic_call] + _(RawOrigin::Root, validator_stash.clone(), era, slash_fraction); + + assert!(ValidatorSlashInEra::<T>::get(era, &validator_stash).is_some()); + + Ok(()) + } + impl_benchmark_test_suite!( Staking, crate::mock::ExtBuilder::default().has_stakers(true), diff --git a/substrate/frame/staking/src/mock.rs b/substrate/frame/staking/src/mock.rs index 4546dbf7459..cf1b2c7912a 100644 --- a/substrate/frame/staking/src/mock.rs +++ b/substrate/frame/staking/src/mock.rs @@ -846,7 +846,11 @@ pub(crate) fn on_offence_in_era( let bonded_eras = crate::BondedEras::<Test>::get(); for &(bonded_era, start_session) in bonded_eras.iter() { if bonded_era == era { - let _ = Staking::on_offence(offenders, slash_fraction, start_session); + let _ = <Staking as OnOffenceHandler<_, _, _>>::on_offence( + offenders, + slash_fraction, + start_session, + ); if advance_processing_blocks { advance_blocks(process_blocks as u64); } @@ -857,7 +861,7 @@ pub(crate) fn on_offence_in_era( } if pallet_staking::ActiveEra::<Test>::get().unwrap().index == era { - let _ = Staking::on_offence( + let _ = <Staking as OnOffenceHandler<_, _, _>>::on_offence( offenders, slash_fraction, pallet_staking::ErasStartSessionIndex::<Test>::get(era).unwrap(), diff --git a/substrate/frame/staking/src/pallet/impls.rs b/substrate/frame/staking/src/pallet/impls.rs index 0a06236238c..0d4ec8c16e2 100644 --- a/substrate/frame/staking/src/pallet/impls.rs +++ b/substrate/frame/staking/src/pallet/impls.rs @@ -1833,6 +1833,24 @@ where slash_session, ); + // the exposure is not actually being used in this implementation + let offenders = offenders.iter().map(|details| { + let (ref offender, _) = details.offender; + OffenceDetails { offender: offender.clone(), reporters: details.reporters.clone() } + }); + Self::on_offence(offenders, slash_fractions, slash_session) + } +} + +impl<T: Config> Pallet<T> { + /// When an offence is reported, it is split into pages and put in the offence queue. + /// As offence queue is processed, computed slashes are queued to be applied after the + /// `SlashDeferDuration`. + pub fn on_offence( + offenders: impl Iterator<Item = OffenceDetails<T::AccountId, T::AccountId>>, + slash_fractions: &[Perbill], + slash_session: SessionIndex, + ) -> Weight { // todo(ank4n): Needs to be properly benched. let mut consumed_weight = Weight::zero(); let mut add_db_reads_writes = |reads, writes| { @@ -1876,8 +1894,8 @@ where add_db_reads_writes(1, 0); let invulnerables = Invulnerables::<T>::get(); - for (details, slash_fraction) in offenders.iter().zip(slash_fractions) { - let (validator, _) = &details.offender; + for (details, slash_fraction) in offenders.zip(slash_fractions) { + let validator = &details.offender; // Skip if the validator is invulnerable. if invulnerables.contains(&validator) { log!(debug, "🦹 on_offence: {:?} is invulnerable; ignoring offence", validator); diff --git a/substrate/frame/staking/src/pallet/mod.rs b/substrate/frame/staking/src/pallet/mod.rs index 6a6ba36d969..bd035bbc0f0 100644 --- a/substrate/frame/staking/src/pallet/mod.rs +++ b/substrate/frame/staking/src/pallet/mod.rs @@ -2607,5 +2607,63 @@ pub mod pallet { Ok(Pays::No.into()) } + + /// This function allows governance to manually slash a validator and is a + /// **fallback mechanism**. + /// + /// The dispatch origin must be `T::AdminOrigin`. + /// + /// ## Parameters + /// - `validator_stash` - The stash account of the validator to slash. + /// - `era` - The era in which the validator was in the active set. + /// - `slash_fraction` - The percentage of the stake to slash, expressed as a Perbill. + /// + /// ## Behavior + /// + /// The slash will be applied using the standard slashing mechanics, respecting the + /// configured `SlashDeferDuration`. + /// + /// This means: + /// - If the validator was already slashed by a higher percentage for the same era, this + /// slash will have no additional effect. + /// - If the validator was previously slashed by a lower percentage, only the difference + /// will be applied. + /// - The slash will be deferred by `SlashDeferDuration` eras before being enacted. + #[pallet::call_index(33)] + #[pallet::weight(T::WeightInfo::manual_slash())] + pub fn manual_slash( + origin: OriginFor<T>, + validator_stash: T::AccountId, + era: EraIndex, + slash_fraction: Perbill, + ) -> DispatchResult { + T::AdminOrigin::ensure_origin(origin)?; + + // Check era is valid + let current_era = CurrentEra::<T>::get().ok_or(Error::<T>::InvalidEraToReward)?; + let history_depth = T::HistoryDepth::get(); + ensure!( + era <= current_era && era >= current_era.saturating_sub(history_depth), + Error::<T>::InvalidEraToReward + ); + + let offence_details = sp_staking::offence::OffenceDetails { + offender: validator_stash.clone(), + reporters: Vec::new(), + }; + + // Get the session index for the era + let session_index = + ErasStartSessionIndex::<T>::get(era).ok_or(Error::<T>::InvalidEraToReward)?; + + // Create the offence and report it through on_offence system + let _ = Self::on_offence( + core::iter::once(offence_details), + &[slash_fraction], + session_index, + ); + + Ok(()) + } } } diff --git a/substrate/frame/staking/src/tests.rs b/substrate/frame/staking/src/tests.rs index a698d6d9b1e..554c705bfbb 100644 --- a/substrate/frame/staking/src/tests.rs +++ b/substrate/frame/staking/src/tests.rs @@ -4727,7 +4727,7 @@ fn offences_weight_calculated_correctly() { let zero_offence_weight = <Test as frame_system::Config>::DbWeight::get().reads_writes(4, 1); assert_eq!( - Staking::on_offence(&[], &[Perbill::from_percent(50)], 0), + <Staking as OnOffenceHandler<_, _, _>>::on_offence(&[], &[Perbill::from_percent(50)], 0), zero_offence_weight ); @@ -4748,7 +4748,7 @@ fn offences_weight_calculated_correctly() { }) .collect(); assert_eq!( - Staking::on_offence( + <Staking as OnOffenceHandler<_, _, _>>::on_offence( &offenders, &[Perbill::from_percent(50)], 0, @@ -4774,7 +4774,7 @@ fn offences_weight_calculated_correctly() { ; assert_eq!( - Staking::on_offence( + <Staking as OnOffenceHandler<_, _, _>>::on_offence( &one_offender, &[Perbill::from_percent(50)], 0, @@ -9556,4 +9556,184 @@ mod paged_slashing { ); }); } + + // Tests for manual_slash extrinsic + // Covers the following scenarios: + // 1. Basic slashing functionality - verifies root origin slashing works correctly + // 2. Slashing with a lower percentage - should have no effect + // 3. Slashing with a higher percentage - should increase the slash amount + // 4. Slashing in non-existent eras - should fail with an error + // 5. Slashing in previous eras - should work within history depth + #[test] + fn manual_slashing_works() { + ExtBuilder::default().validator_count(2).build_and_execute(|| { + // setup: Start with era 0 + start_active_era(0); + + let validator_stash = 11; + let initial_balance = Staking::slashable_balance_of(&validator_stash); + assert!(initial_balance > 0, "Validator must have stake to be slashed"); + + // scenario 1: basic slashing works + // this verifies that the manual_slash extrinsic properly slashes a validator when + // called with root origin + let current_era = CurrentEra::<Test>::get().unwrap(); + let slash_fraction_1 = Perbill::from_percent(25); + + // only root can call this function + assert_noop!( + Staking::manual_slash( + RuntimeOrigin::signed(10), + validator_stash, + current_era, + slash_fraction_1 + ), + BadOrigin + ); + + // root can slash + assert_ok!(Staking::manual_slash( + RuntimeOrigin::root(), + validator_stash, + current_era, + slash_fraction_1 + )); + + // process offence + advance_blocks(1); + + // check if balance was slashed correctly (25%) + let balance_after_first_slash = Staking::slashable_balance_of(&validator_stash); + let expected_balance_1 = initial_balance - (initial_balance / 4); // 25% slash + + assert!( + balance_after_first_slash <= expected_balance_1 && + balance_after_first_slash >= expected_balance_1 - 5, + "First slash was not applied correctly. Expected around {}, got {}", + expected_balance_1, + balance_after_first_slash + ); + + // clear events from first slash + System::reset_events(); + + // scenario 2: slashing with a smaller fraction has no effect + // when a validator has already been slashed by a higher percentage, + // attempting to slash with a lower percentage should have no effect + let slash_fraction_2 = Perbill::from_percent(10); // Smaller than 25% + assert_ok!(Staking::manual_slash( + RuntimeOrigin::root(), + validator_stash, + current_era, + slash_fraction_2 + )); + + // balance should not change because we already slashed with a higher percentage + let balance_after_second_slash = Staking::slashable_balance_of(&validator_stash); + assert_eq!( + balance_after_first_slash, balance_after_second_slash, + "Balance changed after slashing with smaller fraction" + ); + + // with the new implementation, we should see an OffenceReported event + // but no Slashed event yet as the slash will be queued + let has_offence_reported = System::events().iter().any(|record| { + matches!( + record.event, + RuntimeEvent::Staking(Event::<Test>::OffenceReported { + validator, + fraction, + .. + }) if validator == validator_stash && fraction == slash_fraction_2 + ) + }); + assert!(has_offence_reported, "No OffenceReported event was emitted"); + + // verify no Slashed event was emitted yet (since it's queued for later processing) + let no_slashed_events = !System::events().iter().any(|record| { + matches!(record.event, RuntimeEvent::Staking(Event::<Test>::Slashed { .. })) + }); + assert!(no_slashed_events, "A Slashed event was incorrectly emitted immediately"); + + // clear events again + System::reset_events(); + + // scenario 3: slashing with a larger fraction works + // when a validator is slashed with a higher percentage than previous slashes, + // their stake should be further reduced to match the new larger slash percentage + let slash_fraction_3 = Perbill::from_percent(50); // Larger than 25% + assert_ok!(Staking::manual_slash( + RuntimeOrigin::root(), + validator_stash, + current_era, + slash_fraction_3 + )); + + // process offence + advance_blocks(1); + + // check if balance was further slashed (from 75% to 50% of original) + let balance_after_third_slash = Staking::slashable_balance_of(&validator_stash); + let expected_balance_3 = initial_balance / 2; // 50% of original + + assert!( + balance_after_third_slash <= expected_balance_3 && + balance_after_third_slash >= expected_balance_3 - 5, + "Third slash was not applied correctly. Expected around {}, got {}", + expected_balance_3, + balance_after_third_slash + ); + + // verify a Slashed event was emitted + assert!( + System::events().iter().any(|record| { + matches!( + record.event, + RuntimeEvent::Staking(Event::<Test>::Slashed { staker, .. }) + if staker == validator_stash + ) + }), + "No Slashed event was emitted after effective slash" + ); + + // scenario 4: slashing in a non-existent era fails + // the manual_slash extrinsic should validate that the era exists within history depth + assert_noop!( + Staking::manual_slash( + RuntimeOrigin::root(), + validator_stash, + 999, + slash_fraction_1 + ), + Error::<Test>::InvalidEraToReward + ); + + // move to next era + start_active_era(1); + + // scenario 5: slashing in previous era still works + // as long as the era is within history depth, validators can be slashed for past eras + assert_ok!(Staking::manual_slash( + RuntimeOrigin::root(), + validator_stash, + 0, + Perbill::from_percent(75) + )); + + // process offence + advance_blocks(1); + + // check balance was further reduced + let balance_after_fifth_slash = Staking::slashable_balance_of(&validator_stash); + let expected_balance_5 = initial_balance / 4; // 25% of original (75% slashed) + + assert!( + balance_after_fifth_slash <= expected_balance_5 && + balance_after_fifth_slash >= expected_balance_5 - 5, + "Fifth slash was not applied correctly. Expected around {}, got {}", + expected_balance_5, + balance_after_fifth_slash + ); + }) + } } diff --git a/substrate/frame/staking/src/weights.rs b/substrate/frame/staking/src/weights.rs index 92300d39dbf..1ccb534e4c5 100644 --- a/substrate/frame/staking/src/weights.rs +++ b/substrate/frame/staking/src/weights.rs @@ -107,6 +107,7 @@ pub trait WeightInfo { fn restore_ledger() -> Weight; fn migrate_currency() -> Weight; fn apply_slash() -> Weight; + fn manual_slash() -> Weight; } /// Weights for `pallet_staking` using the Substrate node and recommended hardware. @@ -897,6 +898,35 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { .saturating_add(T::DbWeight::get().reads(233_u64)) .saturating_add(T::DbWeight::get().writes(133_u64)) } + /// Storage: `Staking::CurrentEra` (r:1 w:0) + /// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::ErasStartSessionIndex` (r:1 w:0) + /// Proof: `Staking::ErasStartSessionIndex` (`max_values`: None, `max_size`: Some(16), added: 2491, mode: `MaxEncodedLen`) + /// Storage: `Staking::ActiveEra` (r:1 w:0) + /// Proof: `Staking::ActiveEra` (`max_values`: Some(1), `max_size`: Some(13), added: 508, mode: `MaxEncodedLen`) + /// Storage: `Staking::Invulnerables` (r:1 w:0) + /// Proof: `Staking::Invulnerables` (`max_values`: Some(1), `max_size`: Some(641), added: 1136, mode: `MaxEncodedLen`) + /// Storage: `Staking::ErasStakersOverview` (r:1 w:0) + /// Proof: `Staking::ErasStakersOverview` (`max_values`: None, `max_size`: Some(92), added: 2567, mode: `MaxEncodedLen`) + /// Storage: `Session::DisabledValidators` (r:1 w:1) + /// Proof: `Session::DisabledValidators` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `Session::Validators` (r:1 w:0) + /// Proof: `Session::Validators` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `Staking::ValidatorSlashInEra` (r:1 w:1) + /// Proof: `Staking::ValidatorSlashInEra` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) + /// Storage: `Staking::OffenceQueue` (r:1 w:1) + /// Proof: `Staking::OffenceQueue` (`max_values`: None, `max_size`: Some(101), added: 2576, mode: `MaxEncodedLen`) + /// Storage: `Staking::OffenceQueueEras` (r:1 w:1) + /// Proof: `Staking::OffenceQueueEras` (`max_values`: Some(1), `max_size`: Some(2690), added: 3185, mode: `MaxEncodedLen`) + fn manual_slash() -> Weight { + // Proof Size summary in bytes: + // Measured: `514` + // Estimated: `4175` + // Minimum execution time: 30_000_000 picoseconds. + Weight::from_parts(33_000_000, 4175) + .saturating_add(RocksDbWeight::get().reads(10_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } } // For backwards compatibility and tests. @@ -1686,4 +1716,33 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(233_u64)) .saturating_add(RocksDbWeight::get().writes(133_u64)) } + /// Storage: `Staking::CurrentEra` (r:1 w:0) + /// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::ErasStartSessionIndex` (r:1 w:0) + /// Proof: `Staking::ErasStartSessionIndex` (`max_values`: None, `max_size`: Some(16), added: 2491, mode: `MaxEncodedLen`) + /// Storage: `Staking::ActiveEra` (r:1 w:0) + /// Proof: `Staking::ActiveEra` (`max_values`: Some(1), `max_size`: Some(13), added: 508, mode: `MaxEncodedLen`) + /// Storage: `Staking::Invulnerables` (r:1 w:0) + /// Proof: `Staking::Invulnerables` (`max_values`: Some(1), `max_size`: Some(641), added: 1136, mode: `MaxEncodedLen`) + /// Storage: `Staking::ErasStakersOverview` (r:1 w:0) + /// Proof: `Staking::ErasStakersOverview` (`max_values`: None, `max_size`: Some(92), added: 2567, mode: `MaxEncodedLen`) + /// Storage: `Session::DisabledValidators` (r:1 w:1) + /// Proof: `Session::DisabledValidators` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `Session::Validators` (r:1 w:0) + /// Proof: `Session::Validators` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `Staking::ValidatorSlashInEra` (r:1 w:1) + /// Proof: `Staking::ValidatorSlashInEra` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) + /// Storage: `Staking::OffenceQueue` (r:1 w:1) + /// Proof: `Staking::OffenceQueue` (`max_values`: None, `max_size`: Some(101), added: 2576, mode: `MaxEncodedLen`) + /// Storage: `Staking::OffenceQueueEras` (r:1 w:1) + /// Proof: `Staking::OffenceQueueEras` (`max_values`: Some(1), `max_size`: Some(2690), added: 3185, mode: `MaxEncodedLen`) + fn manual_slash() -> Weight { + // Proof Size summary in bytes: + // Measured: `514` + // Estimated: `4175` + // Minimum execution time: 30_000_000 picoseconds. + Weight::from_parts(33_000_000, 4175) + .saturating_add(RocksDbWeight::get().reads(10_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } } -- GitLab