@@ -302,15 +302,15 @@ mod pallet;
302302use codec:: { Decode , Encode , HasCompact } ;
303303use frame_support:: {
304304 parameter_types,
305- traits:: { Currency , Get } ,
305+ traits:: { Currency , Defensive , Get } ,
306306 weights:: Weight ,
307307 BoundedVec , EqNoBound , PartialEqNoBound , RuntimeDebugNoBound ,
308308} ;
309309use scale_info:: TypeInfo ;
310310use sp_runtime:: {
311311 curve:: PiecewiseLinear ,
312312 traits:: { AtLeast32BitUnsigned , Convert , Saturating , Zero } ,
313- Perbill , RuntimeDebug ,
313+ Perbill , Perquintill , RuntimeDebug ,
314314} ;
315315use sp_staking:: {
316316 offence:: { Offence , OffenceError , ReportOffence } ,
@@ -338,8 +338,7 @@ macro_rules! log {
338338pub type RewardPoint = u32 ;
339339
340340/// The balance type of this pallet.
341- pub type BalanceOf < T > =
342- <<T as Config >:: Currency as Currency < <T as frame_system:: Config >:: AccountId > >:: Balance ;
341+ pub type BalanceOf < T > = <T as Config >:: CurrencyBalance ;
343342
344343type PositiveImbalanceOf < T > = <<T as Config >:: Currency as Currency <
345344 <T as frame_system:: Config >:: AccountId ,
@@ -440,31 +439,30 @@ pub struct UnlockChunk<Balance: HasCompact> {
440439
441440/// The ledger of a (bonded) stash.
442441#[ derive( PartialEq , Eq , Clone , Encode , Decode , RuntimeDebug , TypeInfo ) ]
443- pub struct StakingLedger < AccountId , Balance : HasCompact > {
442+ #[ scale_info( skip_type_params( T ) ) ]
443+ pub struct StakingLedger < T : Config > {
444444 /// The stash account whose balance is actually locked and at stake.
445- pub stash : AccountId ,
445+ pub stash : T :: AccountId ,
446446 /// The total amount of the stash's balance that we are currently accounting for.
447447 /// It's just `active` plus all the `unlocking` balances.
448448 #[ codec( compact) ]
449- pub total : Balance ,
449+ pub total : BalanceOf < T > ,
450450 /// The total amount of the stash's balance that will be at stake in any forthcoming
451451 /// rounds.
452452 #[ codec( compact) ]
453- pub active : Balance ,
453+ pub active : BalanceOf < T > ,
454454 /// Any balance that is becoming free, which may eventually be transferred out of the stash
455455 /// (assuming it doesn't get slashed first). It is assumed that this will be treated as a first
456456 /// in, first out queue where the new (higher value) eras get pushed on the back.
457- pub unlocking : BoundedVec < UnlockChunk < Balance > , MaxUnlockingChunks > ,
457+ pub unlocking : BoundedVec < UnlockChunk < BalanceOf < T > > , MaxUnlockingChunks > ,
458458 /// List of eras for which the stakers behind a validator have claimed rewards. Only updated
459459 /// for validators.
460460 pub claimed_rewards : Vec < EraIndex > ,
461461}
462462
463- impl < AccountId , Balance : HasCompact + Copy + Saturating + AtLeast32BitUnsigned + Zero >
464- StakingLedger < AccountId , Balance >
465- {
463+ impl < T : Config > StakingLedger < T > {
466464 /// Initializes the default object using the given `validator`.
467- pub fn default_from ( stash : AccountId ) -> Self {
465+ pub fn default_from ( stash : T :: AccountId ) -> Self {
468466 Self {
469467 stash,
470468 total : Zero :: zero ( ) ,
@@ -507,8 +505,8 @@ impl<AccountId, Balance: HasCompact + Copy + Saturating + AtLeast32BitUnsigned +
507505 /// Re-bond funds that were scheduled for unlocking.
508506 ///
509507 /// Returns the updated ledger, and the amount actually rebonded.
510- fn rebond ( mut self , value : Balance ) -> ( Self , Balance ) {
511- let mut unlocking_balance: Balance = Zero :: zero ( ) ;
508+ fn rebond ( mut self , value : BalanceOf < T > ) -> ( Self , BalanceOf < T > ) {
509+ let mut unlocking_balance = BalanceOf :: < T > :: zero ( ) ;
512510
513511 while let Some ( last) = self . unlocking . last_mut ( ) {
514512 if unlocking_balance + last. value <= value {
@@ -530,57 +528,96 @@ impl<AccountId, Balance: HasCompact + Copy + Saturating + AtLeast32BitUnsigned +
530528
531529 ( self , unlocking_balance)
532530 }
533- }
534531
535- impl < AccountId , Balance > StakingLedger < AccountId , Balance >
536- where
537- Balance : AtLeast32BitUnsigned + Saturating + Copy ,
538- {
539- /// Slash the validator for a given amount of balance. This can grow the value
540- /// of the slash in the case that the validator has less than `minimum_balance`
541- /// active funds. Returns the amount of funds actually slashed.
532+ /// Slash the staker for a given amount of balance. This can grow the value of the slash in the
533+ /// case that either the active bonded or some unlocking chunks become dust after slashing.
534+ /// Returns the amount of funds actually slashed.
542535 ///
543- /// Slashes from `active` funds first, and then `unlocking`, starting with the
544- /// chunks that are closest to unlocking.
545- fn slash ( & mut self , mut value : Balance , minimum_balance : Balance ) -> Balance {
546- let pre_total = self . total ;
547- let total = & mut self . total ;
548- let active = & mut self . active ;
549-
550- let slash_out_of =
551- |total_remaining : & mut Balance , target : & mut Balance , value : & mut Balance | {
552- let mut slash_from_target = ( * value) . min ( * target) ;
553-
554- if !slash_from_target. is_zero ( ) {
555- * target -= slash_from_target;
556-
557- // Don't leave a dust balance in the staking system.
558- if * target <= minimum_balance {
559- slash_from_target += * target;
560- * value += sp_std:: mem:: replace ( target, Zero :: zero ( ) ) ;
561- }
562-
563- * total_remaining = total_remaining. saturating_sub ( slash_from_target) ;
564- * value -= slash_from_target;
565- }
566- } ;
567-
568- slash_out_of ( total, active, & mut value) ;
569-
570- let i = self
571- . unlocking
572- . iter_mut ( )
573- . map ( |chunk| {
574- slash_out_of ( total, & mut chunk. value , & mut value) ;
575- chunk. value
576- } )
577- . take_while ( |value| value. is_zero ( ) ) // Take all fully-consumed chunks out.
578- . count ( ) ;
536+ /// # Note
537+ ///
538+ /// This calls `Config::OnStakerSlash::on_slash` with information as to how the slash
539+ /// was applied.
540+ fn slash (
541+ & mut self ,
542+ slash_amount : BalanceOf < T > ,
543+ minimum_balance : BalanceOf < T > ,
544+ slash_era : EraIndex ,
545+ ) -> BalanceOf < T > {
546+ use sp_staking:: OnStakerSlash as _;
547+
548+ if slash_amount. is_zero ( ) {
549+ return Zero :: zero ( )
550+ }
579551
580- // Kill all drained chunks.
581- let _ = self . unlocking . drain ( ..i) ;
552+ let mut remaining_slash = slash_amount;
553+ let pre_slash_total = self . total ;
554+
555+ let era_after_slash = slash_era + 1 ;
556+ let chunk_unlock_era_after_slash = era_after_slash + T :: BondingDuration :: get ( ) ;
557+
558+ // Calculate the total balance of active funds and unlocking funds in the affected range.
559+ let ( affected_balance, slash_chunks_priority) : ( _ , Box < dyn Iterator < Item = usize > > ) = {
560+ if let Some ( start_index) =
561+ self . unlocking . iter ( ) . position ( |c| c. era >= chunk_unlock_era_after_slash)
562+ {
563+ // The indices of the first chunk after the slash up through the most recent chunk.
564+ // (The most recent chunk is at greatest from this era)
565+ let affected_indices = start_index..self . unlocking . len ( ) ;
566+ let unbonding_affected_balance =
567+ affected_indices. clone ( ) . fold ( BalanceOf :: < T > :: zero ( ) , |sum, i| {
568+ if let Some ( chunk) = self . unlocking . get_mut ( i) . defensive ( ) {
569+ sum. saturating_add ( chunk. value )
570+ } else {
571+ sum
572+ }
573+ } ) ;
574+ (
575+ self . active . saturating_add ( unbonding_affected_balance) ,
576+ Box :: new ( affected_indices. chain ( ( 0 ..start_index) . rev ( ) ) ) ,
577+ )
578+ } else {
579+ ( self . active , Box :: new ( ( 0 ..self . unlocking . len ( ) ) . rev ( ) ) )
580+ }
581+ } ;
582+
583+ // Helper to update `target` and the ledgers total after accounting for slashing `target`.
584+ let ratio = Perquintill :: from_rational ( slash_amount, affected_balance) ;
585+ let mut slash_out_of = |target : & mut BalanceOf < T > , slash_remaining : & mut BalanceOf < T > | {
586+ let mut slash_from_target =
587+ if slash_amount < affected_balance { ratio * ( * target) } else { * slash_remaining }
588+ . min ( * target) ;
589+
590+ // slash out from *target exactly `slash_from_target`.
591+ * target = * target - slash_from_target;
592+ if * target < minimum_balance {
593+ // Slash the rest of the target if its dust
594+ slash_from_target =
595+ sp_std:: mem:: replace ( target, Zero :: zero ( ) ) . saturating_add ( slash_from_target)
596+ }
582597
583- pre_total. saturating_sub ( * total)
598+ self . total = self . total . saturating_sub ( slash_from_target) ;
599+ * slash_remaining = slash_remaining. saturating_sub ( slash_from_target) ;
600+ } ;
601+
602+ // If this is *not* a proportional slash, the active will always wiped to 0.
603+ slash_out_of ( & mut self . active , & mut remaining_slash) ;
604+
605+ let mut slashed_unlocking = BTreeMap :: < _ , _ > :: new ( ) ;
606+ for i in slash_chunks_priority {
607+ if let Some ( chunk) = self . unlocking . get_mut ( i) . defensive ( ) {
608+ slash_out_of ( & mut chunk. value , & mut remaining_slash) ;
609+ // write the new slashed value of this chunk to the map.
610+ slashed_unlocking. insert ( chunk. era , chunk. value ) ;
611+ if remaining_slash. is_zero ( ) {
612+ break
613+ }
614+ } else {
615+ break
616+ }
617+ }
618+ self . unlocking . retain ( |c| !c. value . is_zero ( ) ) ;
619+ T :: OnStakerSlash :: on_slash ( & self . stash , self . active , & slashed_unlocking) ;
620+ pre_slash_total. saturating_sub ( self . total )
584621 }
585622}
586623
0 commit comments