1use crate::{
53 asset, BalanceOf, Config, Error, Exposure, NegativeImbalanceOf, NominatorSlashInEra, Pallet,
54 Perbill, SpanSlash, UnappliedSlash, ValidatorSlashInEra,
55};
56use alloc::vec::Vec;
57use codec::{Decode, Encode, MaxEncodedLen};
58use frame_support::{
59 ensure,
60 pallet_prelude::DecodeWithMemTracking,
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, Clone, TypeInfo, RuntimeDebug, PartialEq, Eq)]
79pub struct SlashingSpan {
80 pub index: SpanIndex,
81 pub start: EraIndex,
82 pub length: Option<EraIndex>, }
84
85impl SlashingSpan {
86 fn contains_era(&self, era: EraIndex) -> bool {
87 self.start <= era && self.length.map_or(true, |l| self.start.saturating_add(l) > era)
88 }
89}
90
91#[derive(Encode, Decode, Clone, TypeInfo, RuntimeDebug, PartialEq, Eq)]
93pub struct SlashingSpans {
94 pub span_index: SpanIndex,
97 pub last_start: EraIndex,
99 pub last_nonzero_slash: EraIndex,
101 pub prior: Vec<EraIndex>,
104}
105
106impl SlashingSpans {
107 pub(crate) fn new(window_start: EraIndex) -> Self {
110 SlashingSpans {
111 span_index: 0,
112 last_start: window_start,
113 last_nonzero_slash: 0,
117 prior: Vec::new(),
118 }
119 }
120
121 pub(crate) fn end_span(&mut self, now: EraIndex) -> bool {
125 let next_start = now.defensive_saturating_add(1);
126 if next_start <= self.last_start {
127 return false
128 }
129
130 let last_length = next_start.defensive_saturating_sub(self.last_start);
131 self.prior.insert(0, last_length);
132 self.last_start = next_start;
133 self.span_index.defensive_saturating_accrue(1);
134 true
135 }
136
137 pub(crate) fn iter(&'_ self) -> impl Iterator<Item = SlashingSpan> + '_ {
139 let mut last_start = self.last_start;
140 let mut index = self.span_index;
141 let last = SlashingSpan { index, start: last_start, length: None };
142 let prior = self.prior.iter().cloned().map(move |length| {
143 let start = last_start.defensive_saturating_sub(length);
144 last_start = start;
145 index.defensive_saturating_reduce(1);
146
147 SlashingSpan { index, start, length: Some(length) }
148 });
149
150 core::iter::once(last).chain(prior)
151 }
152
153 pub fn last_nonzero_slash(&self) -> EraIndex {
155 self.last_nonzero_slash
156 }
157
158 fn prune(&mut self, window_start: EraIndex) -> Option<(SpanIndex, SpanIndex)> {
163 let old_idx = self
164 .iter()
165 .skip(1) .position(|span| {
167 span.length
168 .map_or(false, |len| span.start.defensive_saturating_add(len) <= window_start)
169 });
170
171 let earliest_span_index =
172 self.span_index.defensive_saturating_sub(self.prior.len() as SpanIndex);
173 let pruned = match old_idx {
174 Some(o) => {
175 self.prior.truncate(o);
176 let new_earliest =
177 self.span_index.defensive_saturating_sub(self.prior.len() as SpanIndex);
178 Some((earliest_span_index, new_earliest))
179 },
180 None => None,
181 };
182
183 self.last_start = core::cmp::max(self.last_start, window_start);
185 pruned
186 }
187}
188
189#[derive(
191 Encode,
192 Decode,
193 DecodeWithMemTracking,
194 Clone,
195 Default,
196 TypeInfo,
197 MaxEncodedLen,
198 PartialEq,
199 Eq,
200 RuntimeDebug,
201)]
202pub struct SpanRecord<Balance> {
203 pub slashed: Balance,
204 pub paid_out: Balance,
205}
206
207impl<Balance> SpanRecord<Balance> {
208 #[cfg(test)]
210 pub(crate) fn amount(&self) -> &Balance {
211 &self.slashed
212 }
213}
214
215#[derive(Clone)]
217pub(crate) struct SlashParams<'a, T: 'a + Config> {
218 pub(crate) stash: &'a T::AccountId,
220 pub(crate) slash: Perbill,
222 pub(crate) exposure: &'a Exposure<T::AccountId, BalanceOf<T>>,
224 pub(crate) slash_era: EraIndex,
226 pub(crate) window_start: EraIndex,
228 pub(crate) now: EraIndex,
230 pub(crate) reward_proportion: Perbill,
233}
234
235pub(crate) fn compute_slash<T: Config>(
242 params: SlashParams<T>,
243) -> Option<UnappliedSlash<T::AccountId, BalanceOf<T>>> {
244 let mut reward_payout = Zero::zero();
245 let mut val_slashed = Zero::zero();
246
247 let own_slash = params.slash * params.exposure.own;
249 if params.slash * params.exposure.total == Zero::zero() {
250 kick_out_if_recent::<T>(params);
253 return None
254 }
255
256 let prior_slash_p = ValidatorSlashInEra::<T>::get(¶ms.slash_era, params.stash)
257 .map_or(Zero::zero(), |(prior_slash_proportion, _)| prior_slash_proportion);
258
259 if params.slash.deconstruct() > prior_slash_p.deconstruct() {
262 ValidatorSlashInEra::<T>::insert(
263 ¶ms.slash_era,
264 params.stash,
265 &(params.slash, own_slash),
266 );
267 } else {
268 return None
276 }
277
278 {
280 let mut spans = fetch_spans::<T>(
281 params.stash,
282 params.window_start,
283 &mut reward_payout,
284 &mut val_slashed,
285 params.reward_proportion,
286 );
287
288 let target_span = spans.compare_and_update_span_slash(params.slash_era, own_slash);
289
290 if target_span == Some(spans.span_index()) {
291 spans.end_span(params.now);
294 }
295 }
296
297 let mut nominators_slashed = Vec::new();
298 reward_payout += slash_nominators::<T>(params.clone(), prior_slash_p, &mut nominators_slashed);
299
300 Some(UnappliedSlash {
301 validator: params.stash.clone(),
302 own: val_slashed,
303 others: nominators_slashed,
304 reporters: Vec::new(),
305 payout: reward_payout,
306 })
307}
308
309fn kick_out_if_recent<T: Config>(params: SlashParams<T>) {
312 let mut reward_payout = Zero::zero();
314 let mut val_slashed = Zero::zero();
315 let mut spans = fetch_spans::<T>(
316 params.stash,
317 params.window_start,
318 &mut reward_payout,
319 &mut val_slashed,
320 params.reward_proportion,
321 );
322
323 if spans.era_span(params.slash_era).map(|s| s.index) == Some(spans.span_index()) {
324 spans.end_span(params.now);
326 }
327}
328
329fn slash_nominators<T: Config>(
333 params: SlashParams<T>,
334 prior_slash_p: Perbill,
335 nominators_slashed: &mut Vec<(T::AccountId, BalanceOf<T>)>,
336) -> BalanceOf<T> {
337 let mut reward_payout = Zero::zero();
338
339 nominators_slashed.reserve(params.exposure.others.len());
340 for nominator in ¶ms.exposure.others {
341 let stash = &nominator.who;
342 let mut nom_slashed = Zero::zero();
343
344 let era_slash = {
347 let own_slash_prior = prior_slash_p * nominator.value;
348 let own_slash_by_validator = params.slash * nominator.value;
349 let own_slash_difference = own_slash_by_validator.saturating_sub(own_slash_prior);
350
351 let mut era_slash =
352 NominatorSlashInEra::<T>::get(¶ms.slash_era, stash).unwrap_or_else(Zero::zero);
353 era_slash += own_slash_difference;
354 NominatorSlashInEra::<T>::insert(¶ms.slash_era, stash, &era_slash);
355
356 era_slash
357 };
358
359 {
361 let mut spans = fetch_spans::<T>(
362 stash,
363 params.window_start,
364 &mut reward_payout,
365 &mut nom_slashed,
366 params.reward_proportion,
367 );
368
369 let target_span = spans.compare_and_update_span_slash(params.slash_era, era_slash);
370
371 if target_span == Some(spans.span_index()) {
372 spans.end_span(params.now);
374 }
375 }
376 nominators_slashed.push((stash.clone(), nom_slashed));
377 }
378
379 reward_payout
380}
381
382struct InspectingSpans<'a, T: Config + 'a> {
390 dirty: bool,
391 window_start: EraIndex,
392 stash: &'a T::AccountId,
393 spans: SlashingSpans,
394 paid_out: &'a mut BalanceOf<T>,
395 slash_of: &'a mut BalanceOf<T>,
396 reward_proportion: Perbill,
397 _marker: core::marker::PhantomData<T>,
398}
399
400fn fetch_spans<'a, T: Config + 'a>(
402 stash: &'a T::AccountId,
403 window_start: EraIndex,
404 paid_out: &'a mut BalanceOf<T>,
405 slash_of: &'a mut BalanceOf<T>,
406 reward_proportion: Perbill,
407) -> InspectingSpans<'a, T> {
408 let spans = crate::SlashingSpans::<T>::get(stash).unwrap_or_else(|| {
409 let spans = SlashingSpans::new(window_start);
410 crate::SlashingSpans::<T>::insert(stash, &spans);
411 spans
412 });
413
414 InspectingSpans {
415 dirty: false,
416 window_start,
417 stash,
418 spans,
419 slash_of,
420 paid_out,
421 reward_proportion,
422 _marker: core::marker::PhantomData,
423 }
424}
425
426impl<'a, T: 'a + Config> InspectingSpans<'a, T> {
427 fn span_index(&self) -> SpanIndex {
428 self.spans.span_index
429 }
430
431 fn end_span(&mut self, now: EraIndex) {
432 self.dirty = self.spans.end_span(now) || self.dirty;
433 }
434
435 fn add_slash(&mut self, amount: BalanceOf<T>, slash_era: EraIndex) {
439 *self.slash_of += amount;
440 self.spans.last_nonzero_slash = core::cmp::max(self.spans.last_nonzero_slash, slash_era);
441 }
442
443 fn era_span(&self, era: EraIndex) -> Option<SlashingSpan> {
445 self.spans.iter().find(|span| span.contains_era(era))
446 }
447
448 fn compare_and_update_span_slash(
453 &mut self,
454 slash_era: EraIndex,
455 slash: BalanceOf<T>,
456 ) -> Option<SpanIndex> {
457 let target_span = self.era_span(slash_era)?;
458 let span_slash_key = (self.stash.clone(), target_span.index);
459 let mut span_record = SpanSlash::<T>::get(&span_slash_key);
460 let mut changed = false;
461
462 let reward = if span_record.slashed < slash {
463 let difference = slash.defensive_saturating_sub(span_record.slashed);
465 span_record.slashed = slash;
466
467 let reward =
469 REWARD_F1 * (self.reward_proportion * slash).saturating_sub(span_record.paid_out);
470
471 self.add_slash(difference, slash_era);
472 changed = true;
473
474 reward
475 } else if span_record.slashed == slash {
476 REWARD_F1 * (self.reward_proportion * slash).saturating_sub(span_record.paid_out)
478 } else {
479 Zero::zero()
480 };
481
482 if !reward.is_zero() {
483 changed = true;
484 span_record.paid_out += reward;
485 *self.paid_out += reward;
486 }
487
488 if changed {
489 self.dirty = true;
490 SpanSlash::<T>::insert(&span_slash_key, &span_record);
491 }
492
493 Some(target_span.index)
494 }
495}
496
497impl<'a, T: 'a + Config> Drop for InspectingSpans<'a, T> {
498 fn drop(&mut self) {
499 if !self.dirty {
501 return
502 }
503
504 if let Some((start, end)) = self.spans.prune(self.window_start) {
505 for span_index in start..end {
506 SpanSlash::<T>::remove(&(self.stash.clone(), span_index));
507 }
508 }
509
510 crate::SlashingSpans::<T>::insert(self.stash, &self.spans);
511 }
512}
513
514pub(crate) fn clear_era_metadata<T: Config>(obsolete_era: EraIndex) {
516 #[allow(deprecated)]
517 ValidatorSlashInEra::<T>::remove_prefix(&obsolete_era, None);
518 #[allow(deprecated)]
519 NominatorSlashInEra::<T>::remove_prefix(&obsolete_era, None);
520}
521
522pub(crate) fn clear_stash_metadata<T: Config>(
524 stash: &T::AccountId,
525 num_slashing_spans: u32,
526) -> DispatchResult {
527 let spans = match crate::SlashingSpans::<T>::get(stash) {
528 None => return Ok(()),
529 Some(s) => s,
530 };
531
532 ensure!(
533 num_slashing_spans as usize >= spans.iter().count(),
534 Error::<T>::IncorrectSlashingSpans
535 );
536
537 crate::SlashingSpans::<T>::remove(stash);
538
539 for span in spans.iter() {
545 SpanSlash::<T>::remove(&(stash.clone(), span.index));
546 }
547
548 Ok(())
549}
550
551pub fn do_slash<T: Config>(
555 stash: &T::AccountId,
556 value: BalanceOf<T>,
557 reward_payout: &mut BalanceOf<T>,
558 slashed_imbalance: &mut NegativeImbalanceOf<T>,
559 slash_era: EraIndex,
560) {
561 let mut ledger =
562 match Pallet::<T>::ledger(sp_staking::StakingAccount::Stash(stash.clone())).defensive() {
563 Ok(ledger) => ledger,
564 Err(_) => return, };
566
567 let value = ledger.slash(value, asset::existential_deposit::<T>(), slash_era);
568 if value.is_zero() {
569 return
571 }
572
573 if !Pallet::<T>::is_virtual_staker(stash) {
575 let (imbalance, missing) = asset::slash::<T>(stash, value);
576 slashed_imbalance.subsume(imbalance);
577
578 if !missing.is_zero() {
579 *reward_payout = reward_payout.saturating_sub(missing);
581 }
582 }
583
584 let _ = ledger
585 .update()
586 .defensive_proof("ledger fetched from storage so it exists in storage; qed.");
587
588 <Pallet<T>>::deposit_event(super::Event::<T>::Slashed { staker: stash.clone(), amount: value });
590}
591
592pub(crate) fn apply_slash<T: Config>(
594 unapplied_slash: UnappliedSlash<T::AccountId, BalanceOf<T>>,
595 slash_era: EraIndex,
596) {
597 let mut slashed_imbalance = NegativeImbalanceOf::<T>::zero();
598 let mut reward_payout = unapplied_slash.payout;
599
600 do_slash::<T>(
601 &unapplied_slash.validator,
602 unapplied_slash.own,
603 &mut reward_payout,
604 &mut slashed_imbalance,
605 slash_era,
606 );
607
608 for &(ref nominator, nominator_slash) in &unapplied_slash.others {
609 do_slash::<T>(
610 nominator,
611 nominator_slash,
612 &mut reward_payout,
613 &mut slashed_imbalance,
614 slash_era,
615 );
616 }
617
618 pay_reporters::<T>(reward_payout, slashed_imbalance, &unapplied_slash.reporters);
619}
620
621fn pay_reporters<T: Config>(
623 reward_payout: BalanceOf<T>,
624 slashed_imbalance: NegativeImbalanceOf<T>,
625 reporters: &[T::AccountId],
626) {
627 if reward_payout.is_zero() || reporters.is_empty() {
628 T::Slash::on_unbalanced(slashed_imbalance);
631 return
632 }
633
634 let reward_payout = reward_payout.min(slashed_imbalance.peek());
636 let (mut reward_payout, mut value_slashed) = slashed_imbalance.split(reward_payout);
637
638 let per_reporter = reward_payout.peek() / (reporters.len() as u32).into();
639 for reporter in reporters {
640 let (reporter_reward, rest) = reward_payout.split(per_reporter);
641 reward_payout = rest;
642
643 asset::deposit_slashed::<T>(reporter, reporter_reward);
646 }
647
648 value_slashed.subsume(reward_payout); T::Slash::on_unbalanced(value_slashed);
651}
652
653#[cfg(test)]
654mod tests {
655 use super::*;
656
657 #[test]
658 fn span_contains_era() {
659 let span = SlashingSpan { index: 0, start: 1000, length: None };
661 assert!(!span.contains_era(0));
662 assert!(!span.contains_era(999));
663
664 assert!(span.contains_era(1000));
665 assert!(span.contains_era(1001));
666 assert!(span.contains_era(10000));
667
668 let span = SlashingSpan { index: 0, start: 1000, length: Some(10) };
670 assert!(!span.contains_era(0));
671 assert!(!span.contains_era(999));
672
673 assert!(span.contains_era(1000));
674 assert!(span.contains_era(1001));
675 assert!(span.contains_era(1009));
676 assert!(!span.contains_era(1010));
677 assert!(!span.contains_era(1011));
678 }
679
680 #[test]
681 fn single_slashing_span() {
682 let spans = SlashingSpans {
683 span_index: 0,
684 last_start: 1000,
685 last_nonzero_slash: 0,
686 prior: Vec::new(),
687 };
688
689 assert_eq!(
690 spans.iter().collect::<Vec<_>>(),
691 vec![SlashingSpan { index: 0, start: 1000, length: None }],
692 );
693 }
694
695 #[test]
696 fn many_prior_spans() {
697 let spans = SlashingSpans {
698 span_index: 10,
699 last_start: 1000,
700 last_nonzero_slash: 0,
701 prior: vec![10, 9, 8, 10],
702 };
703
704 assert_eq!(
705 spans.iter().collect::<Vec<_>>(),
706 vec![
707 SlashingSpan { index: 10, start: 1000, length: None },
708 SlashingSpan { index: 9, start: 990, length: Some(10) },
709 SlashingSpan { index: 8, start: 981, length: Some(9) },
710 SlashingSpan { index: 7, start: 973, length: Some(8) },
711 SlashingSpan { index: 6, start: 963, length: Some(10) },
712 ],
713 )
714 }
715
716 #[test]
717 fn pruning_spans() {
718 let mut spans = SlashingSpans {
719 span_index: 10,
720 last_start: 1000,
721 last_nonzero_slash: 0,
722 prior: vec![10, 9, 8, 10],
723 };
724
725 assert_eq!(spans.prune(981), Some((6, 8)));
726 assert_eq!(
727 spans.iter().collect::<Vec<_>>(),
728 vec![
729 SlashingSpan { index: 10, start: 1000, length: None },
730 SlashingSpan { index: 9, start: 990, length: Some(10) },
731 SlashingSpan { index: 8, start: 981, length: Some(9) },
732 ],
733 );
734
735 assert_eq!(spans.prune(982), None);
736 assert_eq!(
737 spans.iter().collect::<Vec<_>>(),
738 vec![
739 SlashingSpan { index: 10, start: 1000, length: None },
740 SlashingSpan { index: 9, start: 990, length: Some(10) },
741 SlashingSpan { index: 8, start: 981, length: Some(9) },
742 ],
743 );
744
745 assert_eq!(spans.prune(989), None);
746 assert_eq!(
747 spans.iter().collect::<Vec<_>>(),
748 vec![
749 SlashingSpan { index: 10, start: 1000, length: None },
750 SlashingSpan { index: 9, start: 990, length: Some(10) },
751 SlashingSpan { index: 8, start: 981, length: Some(9) },
752 ],
753 );
754
755 assert_eq!(spans.prune(1000), Some((8, 10)));
756 assert_eq!(
757 spans.iter().collect::<Vec<_>>(),
758 vec![SlashingSpan { index: 10, start: 1000, length: None },],
759 );
760
761 assert_eq!(spans.prune(2000), None);
762 assert_eq!(
763 spans.iter().collect::<Vec<_>>(),
764 vec![SlashingSpan { index: 10, start: 2000, length: None },],
765 );
766
767 let mut spans = SlashingSpans {
769 span_index: 10,
770 last_start: 1000,
771 last_nonzero_slash: 0,
772 prior: vec![10, 9, 8, 10],
773 };
774 assert_eq!(spans.prune(2000), Some((6, 10)));
775 assert_eq!(
776 spans.iter().collect::<Vec<_>>(),
777 vec![SlashingSpan { index: 10, start: 2000, length: None },],
778 );
779 }
780
781 #[test]
782 fn ending_span() {
783 let mut spans = SlashingSpans {
784 span_index: 1,
785 last_start: 10,
786 last_nonzero_slash: 0,
787 prior: Vec::new(),
788 };
789
790 assert!(spans.end_span(10));
791
792 assert_eq!(
793 spans.iter().collect::<Vec<_>>(),
794 vec![
795 SlashingSpan { index: 2, start: 11, length: None },
796 SlashingSpan { index: 1, start: 10, length: Some(1) },
797 ],
798 );
799
800 assert!(spans.end_span(15));
801 assert_eq!(
802 spans.iter().collect::<Vec<_>>(),
803 vec![
804 SlashingSpan { index: 3, start: 16, length: None },
805 SlashingSpan { index: 2, start: 11, length: Some(5) },
806 SlashingSpan { index: 1, start: 10, length: Some(1) },
807 ],
808 );
809
810 assert!(!spans.end_span(15));
812 assert_eq!(
813 spans.iter().collect::<Vec<_>>(),
814 vec![
815 SlashingSpan { index: 3, start: 16, length: None },
816 SlashingSpan { index: 2, start: 11, length: Some(5) },
817 SlashingSpan { index: 1, start: 10, length: Some(1) },
818 ],
819 );
820 }
821}