1use crate::{
53 asset, BalanceOf, Config, DisabledValidators, DisablingStrategy, Error, Exposure,
54 NegativeImbalanceOf, NominatorSlashInEra, Pallet, Perbill, SessionInterface, SpanSlash,
55 UnappliedSlash, ValidatorSlashInEra,
56};
57use alloc::vec::Vec;
58use codec::{Decode, Encode, MaxEncodedLen};
59use frame_support::{
60 ensure,
61 traits::{Defensive, DefensiveSaturating, Imbalance, OnUnbalanced},
62};
63use scale_info::TypeInfo;
64use sp_runtime::{
65 traits::{Saturating, Zero},
66 DispatchResult, RuntimeDebug,
67};
68use sp_staking::{EraIndex, StakingInterface};
69
70const REWARD_F1: Perbill = Perbill::from_percent(50);
73
74pub type SpanIndex = u32;
76
77#[derive(Encode, Decode, TypeInfo)]
79#[cfg_attr(test, derive(Debug, PartialEq))]
80pub(crate) struct SlashingSpan {
81 pub(crate) index: SpanIndex,
82 pub(crate) start: EraIndex,
83 pub(crate) length: Option<EraIndex>, }
85
86impl SlashingSpan {
87 fn contains_era(&self, era: EraIndex) -> bool {
88 self.start <= era && self.length.map_or(true, |l| self.start.saturating_add(l) > era)
89 }
90}
91
92#[derive(Encode, Decode, RuntimeDebug, TypeInfo)]
94pub struct SlashingSpans {
95 span_index: SpanIndex,
98 last_start: EraIndex,
100 last_nonzero_slash: EraIndex,
102 prior: Vec<EraIndex>,
105}
106
107impl SlashingSpans {
108 pub(crate) fn new(window_start: EraIndex) -> Self {
111 SlashingSpans {
112 span_index: 0,
113 last_start: window_start,
114 last_nonzero_slash: 0,
118 prior: Vec::new(),
119 }
120 }
121
122 pub(crate) fn end_span(&mut self, now: EraIndex) -> bool {
126 let next_start = now.defensive_saturating_add(1);
127 if next_start <= self.last_start {
128 return false
129 }
130
131 let last_length = next_start.defensive_saturating_sub(self.last_start);
132 self.prior.insert(0, last_length);
133 self.last_start = next_start;
134 self.span_index.defensive_saturating_accrue(1);
135 true
136 }
137
138 pub(crate) fn iter(&'_ self) -> impl Iterator<Item = SlashingSpan> + '_ {
140 let mut last_start = self.last_start;
141 let mut index = self.span_index;
142 let last = SlashingSpan { index, start: last_start, length: None };
143 let prior = self.prior.iter().cloned().map(move |length| {
144 let start = last_start.defensive_saturating_sub(length);
145 last_start = start;
146 index.defensive_saturating_reduce(1);
147
148 SlashingSpan { index, start, length: Some(length) }
149 });
150
151 core::iter::once(last).chain(prior)
152 }
153
154 pub fn last_nonzero_slash(&self) -> EraIndex {
156 self.last_nonzero_slash
157 }
158
159 fn prune(&mut self, window_start: EraIndex) -> Option<(SpanIndex, SpanIndex)> {
164 let old_idx = self
165 .iter()
166 .skip(1) .position(|span| {
168 span.length
169 .map_or(false, |len| span.start.defensive_saturating_add(len) <= window_start)
170 });
171
172 let earliest_span_index =
173 self.span_index.defensive_saturating_sub(self.prior.len() as SpanIndex);
174 let pruned = match old_idx {
175 Some(o) => {
176 self.prior.truncate(o);
177 let new_earliest =
178 self.span_index.defensive_saturating_sub(self.prior.len() as SpanIndex);
179 Some((earliest_span_index, new_earliest))
180 },
181 None => None,
182 };
183
184 self.last_start = core::cmp::max(self.last_start, window_start);
186 pruned
187 }
188}
189
190#[derive(Encode, Decode, Default, TypeInfo, MaxEncodedLen)]
192pub(crate) struct SpanRecord<Balance> {
193 slashed: Balance,
194 paid_out: Balance,
195}
196
197impl<Balance> SpanRecord<Balance> {
198 #[cfg(test)]
200 pub(crate) fn amount(&self) -> &Balance {
201 &self.slashed
202 }
203}
204
205#[derive(Clone)]
207pub(crate) struct SlashParams<'a, T: 'a + Config> {
208 pub(crate) stash: &'a T::AccountId,
210 pub(crate) slash: Perbill,
212 pub(crate) exposure: &'a Exposure<T::AccountId, BalanceOf<T>>,
214 pub(crate) slash_era: EraIndex,
216 pub(crate) window_start: EraIndex,
218 pub(crate) now: EraIndex,
220 pub(crate) reward_proportion: Perbill,
223}
224
225pub(crate) fn compute_slash<T: Config>(
232 params: SlashParams<T>,
233) -> Option<UnappliedSlash<T::AccountId, BalanceOf<T>>> {
234 let mut reward_payout = Zero::zero();
235 let mut val_slashed = Zero::zero();
236
237 let own_slash = params.slash * params.exposure.own;
239 if params.slash * params.exposure.total == Zero::zero() {
240 kick_out_if_recent::<T>(params);
243 return None
244 }
245
246 let prior_slash_p = ValidatorSlashInEra::<T>::get(¶ms.slash_era, params.stash)
247 .map_or(Zero::zero(), |(prior_slash_proportion, _)| prior_slash_proportion);
248
249 if params.slash.deconstruct() > prior_slash_p.deconstruct() {
252 ValidatorSlashInEra::<T>::insert(
253 ¶ms.slash_era,
254 params.stash,
255 &(params.slash, own_slash),
256 );
257 } else {
258 return None
266 }
267
268 {
270 let mut spans = fetch_spans::<T>(
271 params.stash,
272 params.window_start,
273 &mut reward_payout,
274 &mut val_slashed,
275 params.reward_proportion,
276 );
277
278 let target_span = spans.compare_and_update_span_slash(params.slash_era, own_slash);
279
280 if target_span == Some(spans.span_index()) {
281 spans.end_span(params.now);
284 }
285 }
286
287 add_offending_validator::<T>(¶ms);
288
289 let mut nominators_slashed = Vec::new();
290 reward_payout += slash_nominators::<T>(params.clone(), prior_slash_p, &mut nominators_slashed);
291
292 Some(UnappliedSlash {
293 validator: params.stash.clone(),
294 own: val_slashed,
295 others: nominators_slashed,
296 reporters: Vec::new(),
297 payout: reward_payout,
298 })
299}
300
301fn kick_out_if_recent<T: Config>(params: SlashParams<T>) {
304 let mut reward_payout = Zero::zero();
306 let mut val_slashed = Zero::zero();
307 let mut spans = fetch_spans::<T>(
308 params.stash,
309 params.window_start,
310 &mut reward_payout,
311 &mut val_slashed,
312 params.reward_proportion,
313 );
314
315 if spans.era_span(params.slash_era).map(|s| s.index) == Some(spans.span_index()) {
316 spans.end_span(params.now);
318 }
319
320 add_offending_validator::<T>(¶ms);
321}
322
323fn add_offending_validator<T: Config>(params: &SlashParams<T>) {
326 DisabledValidators::<T>::mutate(|disabled| {
327 if let Some(offender) =
328 T::DisablingStrategy::decision(params.stash, params.slash_era, &disabled)
329 {
330 if let Err(index) = disabled.binary_search_by_key(&offender, |index| *index) {
333 disabled.insert(index, offender);
334 T::SessionInterface::disable_validator(offender);
335 }
336 }
337 });
338
339 debug_assert!(DisabledValidators::<T>::get().windows(2).all(|pair| pair[0] < pair[1]));
341}
342
343fn slash_nominators<T: Config>(
347 params: SlashParams<T>,
348 prior_slash_p: Perbill,
349 nominators_slashed: &mut Vec<(T::AccountId, BalanceOf<T>)>,
350) -> BalanceOf<T> {
351 let mut reward_payout = Zero::zero();
352
353 nominators_slashed.reserve(params.exposure.others.len());
354 for nominator in ¶ms.exposure.others {
355 let stash = &nominator.who;
356 let mut nom_slashed = Zero::zero();
357
358 let era_slash = {
361 let own_slash_prior = prior_slash_p * nominator.value;
362 let own_slash_by_validator = params.slash * nominator.value;
363 let own_slash_difference = own_slash_by_validator.saturating_sub(own_slash_prior);
364
365 let mut era_slash =
366 NominatorSlashInEra::<T>::get(¶ms.slash_era, stash).unwrap_or_else(Zero::zero);
367 era_slash += own_slash_difference;
368 NominatorSlashInEra::<T>::insert(¶ms.slash_era, stash, &era_slash);
369
370 era_slash
371 };
372
373 {
375 let mut spans = fetch_spans::<T>(
376 stash,
377 params.window_start,
378 &mut reward_payout,
379 &mut nom_slashed,
380 params.reward_proportion,
381 );
382
383 let target_span = spans.compare_and_update_span_slash(params.slash_era, era_slash);
384
385 if target_span == Some(spans.span_index()) {
386 spans.end_span(params.now);
388 }
389 }
390 nominators_slashed.push((stash.clone(), nom_slashed));
391 }
392
393 reward_payout
394}
395
396struct InspectingSpans<'a, T: Config + 'a> {
404 dirty: bool,
405 window_start: EraIndex,
406 stash: &'a T::AccountId,
407 spans: SlashingSpans,
408 paid_out: &'a mut BalanceOf<T>,
409 slash_of: &'a mut BalanceOf<T>,
410 reward_proportion: Perbill,
411 _marker: core::marker::PhantomData<T>,
412}
413
414fn fetch_spans<'a, T: Config + 'a>(
416 stash: &'a T::AccountId,
417 window_start: EraIndex,
418 paid_out: &'a mut BalanceOf<T>,
419 slash_of: &'a mut BalanceOf<T>,
420 reward_proportion: Perbill,
421) -> InspectingSpans<'a, T> {
422 let spans = crate::SlashingSpans::<T>::get(stash).unwrap_or_else(|| {
423 let spans = SlashingSpans::new(window_start);
424 crate::SlashingSpans::<T>::insert(stash, &spans);
425 spans
426 });
427
428 InspectingSpans {
429 dirty: false,
430 window_start,
431 stash,
432 spans,
433 slash_of,
434 paid_out,
435 reward_proportion,
436 _marker: core::marker::PhantomData,
437 }
438}
439
440impl<'a, T: 'a + Config> InspectingSpans<'a, T> {
441 fn span_index(&self) -> SpanIndex {
442 self.spans.span_index
443 }
444
445 fn end_span(&mut self, now: EraIndex) {
446 self.dirty = self.spans.end_span(now) || self.dirty;
447 }
448
449 fn add_slash(&mut self, amount: BalanceOf<T>, slash_era: EraIndex) {
453 *self.slash_of += amount;
454 self.spans.last_nonzero_slash = core::cmp::max(self.spans.last_nonzero_slash, slash_era);
455 }
456
457 fn era_span(&self, era: EraIndex) -> Option<SlashingSpan> {
459 self.spans.iter().find(|span| span.contains_era(era))
460 }
461
462 fn compare_and_update_span_slash(
467 &mut self,
468 slash_era: EraIndex,
469 slash: BalanceOf<T>,
470 ) -> Option<SpanIndex> {
471 let target_span = self.era_span(slash_era)?;
472 let span_slash_key = (self.stash.clone(), target_span.index);
473 let mut span_record = SpanSlash::<T>::get(&span_slash_key);
474 let mut changed = false;
475
476 let reward = if span_record.slashed < slash {
477 let difference = slash.defensive_saturating_sub(span_record.slashed);
479 span_record.slashed = slash;
480
481 let reward =
483 REWARD_F1 * (self.reward_proportion * slash).saturating_sub(span_record.paid_out);
484
485 self.add_slash(difference, slash_era);
486 changed = true;
487
488 reward
489 } else if span_record.slashed == slash {
490 REWARD_F1 * (self.reward_proportion * slash).saturating_sub(span_record.paid_out)
492 } else {
493 Zero::zero()
494 };
495
496 if !reward.is_zero() {
497 changed = true;
498 span_record.paid_out += reward;
499 *self.paid_out += reward;
500 }
501
502 if changed {
503 self.dirty = true;
504 SpanSlash::<T>::insert(&span_slash_key, &span_record);
505 }
506
507 Some(target_span.index)
508 }
509}
510
511impl<'a, T: 'a + Config> Drop for InspectingSpans<'a, T> {
512 fn drop(&mut self) {
513 if !self.dirty {
515 return
516 }
517
518 if let Some((start, end)) = self.spans.prune(self.window_start) {
519 for span_index in start..end {
520 SpanSlash::<T>::remove(&(self.stash.clone(), span_index));
521 }
522 }
523
524 crate::SlashingSpans::<T>::insert(self.stash, &self.spans);
525 }
526}
527
528pub(crate) fn clear_era_metadata<T: Config>(obsolete_era: EraIndex) {
530 #[allow(deprecated)]
531 ValidatorSlashInEra::<T>::remove_prefix(&obsolete_era, None);
532 #[allow(deprecated)]
533 NominatorSlashInEra::<T>::remove_prefix(&obsolete_era, None);
534}
535
536pub(crate) fn clear_stash_metadata<T: Config>(
538 stash: &T::AccountId,
539 num_slashing_spans: u32,
540) -> DispatchResult {
541 let spans = match crate::SlashingSpans::<T>::get(stash) {
542 None => return Ok(()),
543 Some(s) => s,
544 };
545
546 ensure!(
547 num_slashing_spans as usize >= spans.iter().count(),
548 Error::<T>::IncorrectSlashingSpans
549 );
550
551 crate::SlashingSpans::<T>::remove(stash);
552
553 for span in spans.iter() {
559 SpanSlash::<T>::remove(&(stash.clone(), span.index));
560 }
561
562 Ok(())
563}
564
565pub fn do_slash<T: Config>(
569 stash: &T::AccountId,
570 value: BalanceOf<T>,
571 reward_payout: &mut BalanceOf<T>,
572 slashed_imbalance: &mut NegativeImbalanceOf<T>,
573 slash_era: EraIndex,
574) {
575 let mut ledger =
576 match Pallet::<T>::ledger(sp_staking::StakingAccount::Stash(stash.clone())).defensive() {
577 Ok(ledger) => ledger,
578 Err(_) => return, };
580
581 let value = ledger.slash(value, asset::existential_deposit::<T>(), slash_era);
582 if value.is_zero() {
583 return
585 }
586
587 if !Pallet::<T>::is_virtual_staker(stash) {
589 let (imbalance, missing) = asset::slash::<T>(stash, value);
590 slashed_imbalance.subsume(imbalance);
591
592 if !missing.is_zero() {
593 *reward_payout = reward_payout.saturating_sub(missing);
595 }
596 }
597
598 let _ = ledger
599 .update()
600 .defensive_proof("ledger fetched from storage so it exists in storage; qed.");
601
602 <Pallet<T>>::deposit_event(super::Event::<T>::Slashed { staker: stash.clone(), amount: value });
604}
605
606pub(crate) fn apply_slash<T: Config>(
608 unapplied_slash: UnappliedSlash<T::AccountId, BalanceOf<T>>,
609 slash_era: EraIndex,
610) {
611 let mut slashed_imbalance = NegativeImbalanceOf::<T>::zero();
612 let mut reward_payout = unapplied_slash.payout;
613
614 do_slash::<T>(
615 &unapplied_slash.validator,
616 unapplied_slash.own,
617 &mut reward_payout,
618 &mut slashed_imbalance,
619 slash_era,
620 );
621
622 for &(ref nominator, nominator_slash) in &unapplied_slash.others {
623 do_slash::<T>(
624 nominator,
625 nominator_slash,
626 &mut reward_payout,
627 &mut slashed_imbalance,
628 slash_era,
629 );
630 }
631
632 pay_reporters::<T>(reward_payout, slashed_imbalance, &unapplied_slash.reporters);
633}
634
635fn pay_reporters<T: Config>(
637 reward_payout: BalanceOf<T>,
638 slashed_imbalance: NegativeImbalanceOf<T>,
639 reporters: &[T::AccountId],
640) {
641 if reward_payout.is_zero() || reporters.is_empty() {
642 T::Slash::on_unbalanced(slashed_imbalance);
645 return
646 }
647
648 let reward_payout = reward_payout.min(slashed_imbalance.peek());
650 let (mut reward_payout, mut value_slashed) = slashed_imbalance.split(reward_payout);
651
652 let per_reporter = reward_payout.peek() / (reporters.len() as u32).into();
653 for reporter in reporters {
654 let (reporter_reward, rest) = reward_payout.split(per_reporter);
655 reward_payout = rest;
656
657 asset::deposit_slashed::<T>(reporter, reporter_reward);
660 }
661
662 value_slashed.subsume(reward_payout); T::Slash::on_unbalanced(value_slashed);
665}
666
667#[cfg(test)]
668mod tests {
669 use super::*;
670
671 #[test]
672 fn span_contains_era() {
673 let span = SlashingSpan { index: 0, start: 1000, length: None };
675 assert!(!span.contains_era(0));
676 assert!(!span.contains_era(999));
677
678 assert!(span.contains_era(1000));
679 assert!(span.contains_era(1001));
680 assert!(span.contains_era(10000));
681
682 let span = SlashingSpan { index: 0, start: 1000, length: Some(10) };
684 assert!(!span.contains_era(0));
685 assert!(!span.contains_era(999));
686
687 assert!(span.contains_era(1000));
688 assert!(span.contains_era(1001));
689 assert!(span.contains_era(1009));
690 assert!(!span.contains_era(1010));
691 assert!(!span.contains_era(1011));
692 }
693
694 #[test]
695 fn single_slashing_span() {
696 let spans = SlashingSpans {
697 span_index: 0,
698 last_start: 1000,
699 last_nonzero_slash: 0,
700 prior: Vec::new(),
701 };
702
703 assert_eq!(
704 spans.iter().collect::<Vec<_>>(),
705 vec![SlashingSpan { index: 0, start: 1000, length: None }],
706 );
707 }
708
709 #[test]
710 fn many_prior_spans() {
711 let spans = SlashingSpans {
712 span_index: 10,
713 last_start: 1000,
714 last_nonzero_slash: 0,
715 prior: vec![10, 9, 8, 10],
716 };
717
718 assert_eq!(
719 spans.iter().collect::<Vec<_>>(),
720 vec![
721 SlashingSpan { index: 10, start: 1000, length: None },
722 SlashingSpan { index: 9, start: 990, length: Some(10) },
723 SlashingSpan { index: 8, start: 981, length: Some(9) },
724 SlashingSpan { index: 7, start: 973, length: Some(8) },
725 SlashingSpan { index: 6, start: 963, length: Some(10) },
726 ],
727 )
728 }
729
730 #[test]
731 fn pruning_spans() {
732 let mut spans = SlashingSpans {
733 span_index: 10,
734 last_start: 1000,
735 last_nonzero_slash: 0,
736 prior: vec![10, 9, 8, 10],
737 };
738
739 assert_eq!(spans.prune(981), Some((6, 8)));
740 assert_eq!(
741 spans.iter().collect::<Vec<_>>(),
742 vec![
743 SlashingSpan { index: 10, start: 1000, length: None },
744 SlashingSpan { index: 9, start: 990, length: Some(10) },
745 SlashingSpan { index: 8, start: 981, length: Some(9) },
746 ],
747 );
748
749 assert_eq!(spans.prune(982), None);
750 assert_eq!(
751 spans.iter().collect::<Vec<_>>(),
752 vec![
753 SlashingSpan { index: 10, start: 1000, length: None },
754 SlashingSpan { index: 9, start: 990, length: Some(10) },
755 SlashingSpan { index: 8, start: 981, length: Some(9) },
756 ],
757 );
758
759 assert_eq!(spans.prune(989), None);
760 assert_eq!(
761 spans.iter().collect::<Vec<_>>(),
762 vec![
763 SlashingSpan { index: 10, start: 1000, length: None },
764 SlashingSpan { index: 9, start: 990, length: Some(10) },
765 SlashingSpan { index: 8, start: 981, length: Some(9) },
766 ],
767 );
768
769 assert_eq!(spans.prune(1000), Some((8, 10)));
770 assert_eq!(
771 spans.iter().collect::<Vec<_>>(),
772 vec![SlashingSpan { index: 10, start: 1000, length: None },],
773 );
774
775 assert_eq!(spans.prune(2000), None);
776 assert_eq!(
777 spans.iter().collect::<Vec<_>>(),
778 vec![SlashingSpan { index: 10, start: 2000, length: None },],
779 );
780
781 let mut spans = SlashingSpans {
783 span_index: 10,
784 last_start: 1000,
785 last_nonzero_slash: 0,
786 prior: vec![10, 9, 8, 10],
787 };
788 assert_eq!(spans.prune(2000), Some((6, 10)));
789 assert_eq!(
790 spans.iter().collect::<Vec<_>>(),
791 vec![SlashingSpan { index: 10, start: 2000, length: None },],
792 );
793 }
794
795 #[test]
796 fn ending_span() {
797 let mut spans = SlashingSpans {
798 span_index: 1,
799 last_start: 10,
800 last_nonzero_slash: 0,
801 prior: Vec::new(),
802 };
803
804 assert!(spans.end_span(10));
805
806 assert_eq!(
807 spans.iter().collect::<Vec<_>>(),
808 vec![
809 SlashingSpan { index: 2, start: 11, length: None },
810 SlashingSpan { index: 1, start: 10, length: Some(1) },
811 ],
812 );
813
814 assert!(spans.end_span(15));
815 assert_eq!(
816 spans.iter().collect::<Vec<_>>(),
817 vec![
818 SlashingSpan { index: 3, start: 16, length: None },
819 SlashingSpan { index: 2, start: 11, length: Some(5) },
820 SlashingSpan { index: 1, start: 10, length: Some(1) },
821 ],
822 );
823
824 assert!(!spans.end_span(15));
826 assert_eq!(
827 spans.iter().collect::<Vec<_>>(),
828 vec![
829 SlashingSpan { index: 3, start: 16, length: None },
830 SlashingSpan { index: 2, start: 11, length: Some(5) },
831 SlashingSpan { index: 1, start: 10, length: Some(1) },
832 ],
833 );
834 }
835}