1use crate::{
21 asset,
22 election_size_tracker::StaticTracker,
23 log,
24 session_rotation::{self, Eras, Rotator},
25 slashing::OffenceRecord,
26 weights::WeightInfo,
27 BalanceOf, EraRewardPoints, Exposure, Forcing, LedgerIntegrityState, MaxNominationsOf,
28 Nominations, NominationsQuota, PositiveImbalanceOf, PotAccountProvider, RewardDestination,
29 RewardKind, RewardPoint, RewardPot, SnapshotStatus, StakingLedger, ValidatorPrefs, STAKING_ID,
30};
31use alloc::{boxed::Box, vec, vec::Vec};
32use frame_election_provider_support::{
33 bounds::CountBound, data_provider, DataProviderBounds, ElectionDataProvider, ElectionProvider,
34 PageIndex, ScoreProvider, SortedListProvider, VoteWeight, VoterOf,
35};
36use frame_support::{
37 defensive,
38 dispatch::WithPostDispatchInfo,
39 pallet_prelude::*,
40 traits::{
41 fungible::Mutate as FunMutate, tokens::Preservation, Defensive, DefensiveSaturating, Get,
42 Imbalance, InspectLockableCurrency, LockableCurrency, OnUnbalanced,
43 },
44 weights::Weight,
45 StorageDoubleMap,
46};
47use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin};
48use pallet_staking_async_rc_client::{self as rc_client};
49use sp_runtime::{
50 traits::{CheckedAdd, Saturating, StaticLookup, Zero},
51 ArithmeticError, DispatchResult, Perbill,
52};
53use sp_staking::{
54 currency_to_vote::CurrencyToVote,
55 EraIndex, OnStakingUpdate, Page, SessionIndex, Stake, StakerRewardCalculator,
56 StakingAccount::{self, Controller, Stash},
57 StakingInterface,
58};
59
60use super::pallet::*;
61
62#[cfg(feature = "try-runtime")]
63use frame_support::ensure;
64#[cfg(any(test, feature = "try-runtime"))]
65use sp_runtime::TryRuntimeError;
66
67const NPOS_MAX_ITERATIONS_COEFFICIENT: u32 = 2;
74
75impl<T: Config> Pallet<T> {
76 pub(crate) fn min_chilled_bond() -> BalanceOf<T> {
83 MinValidatorBond::<T>::get()
84 .min(MinNominatorBond::<T>::get())
85 .max(asset::existential_deposit::<T>())
86 }
87
88 pub(crate) fn min_validator_bond() -> BalanceOf<T> {
90 MinValidatorBond::<T>::get().max(asset::existential_deposit::<T>())
91 }
92
93 pub(crate) fn min_nominator_bond() -> BalanceOf<T> {
95 MinNominatorBond::<T>::get().max(asset::existential_deposit::<T>())
96 }
97
98 pub fn ledger(account: StakingAccount<T::AccountId>) -> Result<StakingLedger<T>, Error<T>> {
100 StakingLedger::<T>::get(account)
101 }
102
103 pub fn payee(account: StakingAccount<T::AccountId>) -> Option<RewardDestination<T::AccountId>> {
104 StakingLedger::<T>::reward_destination(account)
105 }
106
107 pub fn bonded(stash: &T::AccountId) -> Option<T::AccountId> {
109 StakingLedger::<T>::paired_account(Stash(stash.clone()))
110 }
111
112 pub(crate) fn inspect_bond_state(
119 stash: &T::AccountId,
120 ) -> Result<LedgerIntegrityState, Error<T>> {
121 let hold_or_lock = asset::staked::<T>(&stash)
123 .max(T::OldCurrency::balance_locked(STAKING_ID, &stash).into());
124
125 let controller = <Bonded<T>>::get(stash).ok_or_else(|| {
126 if hold_or_lock == Zero::zero() {
127 Error::<T>::NotStash
128 } else {
129 Error::<T>::BadState
130 }
131 })?;
132
133 match Ledger::<T>::get(controller) {
134 Some(ledger) => {
135 if ledger.stash != *stash {
136 Ok(LedgerIntegrityState::Corrupted)
137 } else {
138 if hold_or_lock != ledger.total {
139 Ok(LedgerIntegrityState::LockCorrupted)
140 } else {
141 Ok(LedgerIntegrityState::Ok)
142 }
143 }
144 },
145 None => Ok(LedgerIntegrityState::CorruptedKilled),
146 }
147 }
148
149 pub fn slashable_balance_of(stash: &T::AccountId) -> BalanceOf<T> {
151 Self::ledger(Stash(stash.clone())).map(|l| l.active).unwrap_or_default()
153 }
154
155 pub fn slashable_balance_of_vote_weight(
157 stash: &T::AccountId,
158 issuance: BalanceOf<T>,
159 ) -> VoteWeight {
160 T::CurrencyToVote::to_vote(Self::slashable_balance_of(stash), issuance)
161 }
162
163 pub fn weight_of_fn() -> Box<dyn Fn(&T::AccountId) -> VoteWeight> {
168 let issuance = asset::total_issuance::<T>();
171 Box::new(move |who: &T::AccountId| -> VoteWeight {
172 Self::slashable_balance_of_vote_weight(who, issuance)
173 })
174 }
175
176 pub fn weight_of(who: &T::AccountId) -> VoteWeight {
178 let issuance = asset::total_issuance::<T>();
179 Self::slashable_balance_of_vote_weight(who, issuance)
180 }
181
182 pub(crate) fn offence_era_of(application_era: EraIndex) -> EraIndex {
184 application_era.saturating_sub(T::SlashDeferDuration::get())
185 }
186
187 pub(crate) fn check_slash_cancelled(
189 era: EraIndex,
190 validator: &T::AccountId,
191 slash_fraction: Perbill,
192 ) -> bool {
193 let cancelled_slashes = CancelledSlashes::<T>::get(&era);
194 cancelled_slashes.iter().any(|(cancelled_validator, cancel_fraction)| {
195 *cancelled_validator == *validator && *cancel_fraction >= slash_fraction
196 })
197 }
198
199 pub(super) fn do_bond_extra(stash: &T::AccountId, additional: BalanceOf<T>) -> DispatchResult {
200 let mut ledger = Self::ledger(StakingAccount::Stash(stash.clone()))?;
201
202 let extra = if Self::is_virtual_staker(stash) {
205 additional
206 } else {
207 additional.min(asset::free_to_stake::<T>(stash))
209 };
210
211 ledger.total = ledger.total.checked_add(&extra).ok_or(ArithmeticError::Overflow)?;
212 ledger.active = ledger.active.checked_add(&extra).ok_or(ArithmeticError::Overflow)?;
213 ensure!(ledger.active >= Self::min_chilled_bond(), Error::<T>::InsufficientBond);
215
216 ledger.update()?;
218 if T::VoterList::contains(stash) {
220 let _ = T::VoterList::on_update(&stash, Self::weight_of(stash));
222 }
223
224 Self::deposit_event(Event::<T>::Bonded { stash: stash.clone(), amount: extra });
225
226 Ok(())
227 }
228
229 fn calculate_earliest_withdrawal_era(active_era: EraIndex) -> EraIndex {
233 let earliest_unlock_era_by_offence_queue = OffenceQueueEras::<T>::get()
235 .as_ref()
236 .and_then(|eras| eras.first())
237 .copied()
238 .unwrap_or(active_era)
240 .saturating_sub(1)
243 .saturating_add(T::BondingDuration::get());
250
251 active_era.min(earliest_unlock_era_by_offence_queue)
257 }
258
259 pub(super) fn do_withdraw_unbonded(controller: &T::AccountId) -> Result<Weight, DispatchError> {
260 let mut ledger = Self::ledger(Controller(controller.clone()))?;
261 let (stash, old_total) = (ledger.stash.clone(), ledger.total);
262 let active_era = Rotator::<T>::active_era();
263
264 if active_era > 1 {
266 Self::ensure_era_slashes_applied(active_era.saturating_sub(1))?;
267 }
268
269 let earliest_era_to_withdraw = Self::calculate_earliest_withdrawal_era(active_era);
270
271 log!(
272 debug,
273 "Withdrawing unbonded stake. Active_era is: {:?} | \
274 Earliest era we can allow withdrawing: {:?}",
275 active_era,
276 earliest_era_to_withdraw
277 );
278
279 ledger = ledger.consolidate_unlocked(earliest_era_to_withdraw);
281
282 let new_total = ledger.total;
283 debug_assert!(
284 new_total <= old_total,
285 "consolidate_unlocked should never increase the total balance of the ledger"
286 );
287
288 let ed = asset::existential_deposit::<T>();
289 let used_weight =
290 if ledger.unlocking.is_empty() && (ledger.active < ed || ledger.active.is_zero()) {
291 Self::kill_stash(&ledger.stash)?;
295
296 T::WeightInfo::withdraw_unbonded_kill()
297 } else {
298 ledger.update()?;
300
301 T::WeightInfo::withdraw_unbonded_update()
303 };
304
305 if new_total < old_total {
308 let value = old_total.defensive_saturating_sub(new_total);
310 Self::deposit_event(Event::<T>::Withdrawn { stash, amount: value });
311
312 T::EventListeners::on_withdraw(controller, value);
314 }
315
316 Ok(used_weight)
317 }
318
319 fn ensure_era_slashes_applied(era: EraIndex) -> Result<(), DispatchError> {
320 ensure!(
321 !UnappliedSlashes::<T>::contains_prefix(era),
322 Error::<T>::UnappliedSlashesInPreviousEra
323 );
324 Ok(())
325 }
326
327 pub(super) fn do_payout_stakers(
328 validator_stash: T::AccountId,
329 era: EraIndex,
330 ) -> DispatchResultWithPostInfo {
331 let page = Eras::<T>::get_next_claimable_page(era, &validator_stash).ok_or_else(|| {
332 Error::<T>::AlreadyClaimed.with_weight(T::WeightInfo::payout_stakers_alive_staked(0))
333 })?;
334
335 Self::do_payout_stakers_by_page(validator_stash, era, page)
336 }
337
338 pub(super) fn do_payout_stakers_by_page(
339 validator_stash: T::AccountId,
340 era: EraIndex,
341 page: Page,
342 ) -> DispatchResultWithPostInfo {
343 let current_era = CurrentEra::<T>::get().ok_or_else(|| {
345 Error::<T>::InvalidEraToReward
346 .with_weight(T::WeightInfo::payout_stakers_alive_staked(0))
347 })?;
348
349 let history_depth = T::HistoryDepth::get();
350
351 ensure!(
352 era <= current_era && era >= current_era.saturating_sub(history_depth),
353 Error::<T>::InvalidEraToReward
354 .with_weight(T::WeightInfo::payout_stakers_alive_staked(0))
355 );
356
357 ensure!(
358 page < Eras::<T>::exposure_page_count(era, &validator_stash),
359 Error::<T>::InvalidPage.with_weight(T::WeightInfo::payout_stakers_alive_staked(0))
360 );
361
362 let era_payout = Eras::<T>::get_stakers_reward(era).ok_or_else(|| {
364 Error::<T>::InvalidEraToReward
365 .with_weight(T::WeightInfo::payout_stakers_alive_staked(0))
366 })?;
367
368 let account = StakingAccount::Stash(validator_stash.clone());
369 let ledger = Self::ledger(account.clone()).or_else(|_| {
370 if StakingLedger::<T>::is_bonded(account) {
371 Err(Error::<T>::NotController.into())
372 } else {
373 Err(Error::<T>::NotStash.with_weight(T::WeightInfo::payout_stakers_alive_staked(0)))
374 }
375 })?;
376
377 ledger.clone().update()?;
378
379 let stash = ledger.stash.clone();
380
381 if Eras::<T>::is_rewards_claimed(era, &stash, page) {
382 return Err(Error::<T>::AlreadyClaimed
383 .with_weight(T::WeightInfo::payout_stakers_alive_staked(0)));
384 }
385
386 Eras::<T>::set_rewards_as_claimed(era, &stash, page);
387
388 let exposure = Eras::<T>::get_paged_exposure(era, &stash, page).ok_or_else(|| {
389 Error::<T>::InvalidEraToReward
390 .with_weight(T::WeightInfo::payout_stakers_alive_staked(0))
391 })?;
392
393 let era_reward_points = Eras::<T>::get_reward_points(era);
396 let total_reward_points = era_reward_points.total;
397 let validator_reward_points =
398 era_reward_points.individual.get(&stash).copied().unwrap_or_else(Zero::zero);
399
400 if validator_reward_points.is_zero() {
402 return Ok(Some(T::WeightInfo::payout_stakers_alive_staked(0)).into());
403 }
404
405 let validator_total_reward_part =
408 Perbill::from_rational(validator_reward_points, total_reward_points);
409
410 let validator_total_payout = validator_total_reward_part.mul_floor(era_payout);
412
413 let validator_commission = Eras::<T>::get_validator_commission(era, &ledger.stash);
414
415 let overview_own =
418 ErasStakersOverview::<T>::get(era, &stash).map(|o| o.own).unwrap_or_default();
419
420 let reward_split = T::StakerRewardCalculator::calculate_staker_reward(
421 validator_total_payout,
422 validator_commission,
423 overview_own,
424 exposure.total(),
425 );
426
427 let page_stake_part = Perbill::from_rational(exposure.page_total(), exposure.total());
430 let validator_staker_payout_for_page =
431 page_stake_part.mul_floor(reward_split.validator_payout);
432
433 Self::deposit_event(Event::<T>::PayoutStarted {
434 era_index: era,
435 validator_stash: stash.clone(),
436 page,
437 next: Eras::<T>::get_next_claimable_page(era, &stash),
438 });
439
440 if let Some(incentive) = Self::calculate_validator_incentive_for_page(
443 era,
444 &stash,
445 page_stake_part,
446 &era_reward_points,
447 ) {
448 Self::transfer_validator_incentive(era, &stash, incentive);
449 }
450
451 let use_dap_payout =
453 DisableMintingGuard::<T>::get().is_some_and(|guard_era| era >= guard_era);
454
455 let nominator_payout_count: u32 = if use_dap_payout {
456 Self::payout_from_provider(
457 era,
458 &stash,
459 validator_staker_payout_for_page,
460 &exposure,
461 overview_own,
462 reward_split.nominator_payout,
463 )
464 } else {
465 Self::payout_legacy_mint(
466 era,
467 &stash,
468 validator_staker_payout_for_page,
469 &exposure,
470 overview_own,
471 reward_split.nominator_payout,
472 )
473 };
474
475 debug_assert!(nominator_payout_count <= T::MaxExposurePageSize::get());
476
477 Ok(Some(T::WeightInfo::payout_stakers_alive_staked(nominator_payout_count)).into())
478 }
479
480 fn payout_from_provider(
482 era: EraIndex,
483 stash: &T::AccountId,
484 validator_payout: BalanceOf<T>,
485 exposure: &crate::PagedExposure<T::AccountId, BalanceOf<T>>,
486 overview_own: BalanceOf<T>,
487 total_nominator_payout: BalanceOf<T>,
488 ) -> u32 {
489 let mut nominator_payout_count: u32 = 0;
490
491 if let Some((amount, dest)) = Self::make_payout_from_provider(era, stash, validator_payout)
492 {
493 Self::deposit_event(Event::<T>::Rewarded { stash: stash.clone(), dest, amount });
494 }
495
496 let total_nominator_stake = exposure.total().saturating_sub(overview_own);
497 for nominator in exposure.others().iter() {
498 let nominator_exposure_part =
499 Perbill::from_rational(nominator.value, total_nominator_stake);
500 let nominator_reward: BalanceOf<T> =
501 nominator_exposure_part.mul_floor(total_nominator_payout);
502
503 if let Some((amount, dest)) =
504 Self::make_payout_from_provider(era, &nominator.who, nominator_reward)
505 {
506 nominator_payout_count.saturating_inc();
507 Self::deposit_event(Event::<T>::Rewarded {
508 stash: nominator.who.clone(),
509 dest,
510 amount,
511 });
512 }
513 }
514
515 nominator_payout_count
516 }
517
518 fn payout_legacy_mint(
520 era: EraIndex,
521 stash: &T::AccountId,
522 validator_payout: BalanceOf<T>,
523 exposure: &crate::PagedExposure<T::AccountId, BalanceOf<T>>,
524 overview_own: BalanceOf<T>,
525 total_nominator_payout: BalanceOf<T>,
526 ) -> u32 {
527 let mut nominator_payout_count: u32 = 0;
528 let mut total_imbalance = PositiveImbalanceOf::<T>::zero();
529
530 if let Some((imbalance, dest)) = Self::make_payout_legacy(era, stash, validator_payout) {
531 Self::deposit_event(Event::<T>::Rewarded {
532 stash: stash.clone(),
533 dest,
534 amount: imbalance.peek(),
535 });
536 total_imbalance.subsume(imbalance);
537 }
538
539 let total_nominator_stake = exposure.total().saturating_sub(overview_own);
540 for nominator in exposure.others().iter() {
541 let nominator_exposure_part =
542 Perbill::from_rational(nominator.value, total_nominator_stake);
543 let nominator_reward: BalanceOf<T> =
544 nominator_exposure_part.mul_floor(total_nominator_payout);
545
546 if let Some((imbalance, dest)) =
547 Self::make_payout_legacy(era, &nominator.who, nominator_reward)
548 {
549 nominator_payout_count.saturating_inc();
550 Self::deposit_event(Event::<T>::Rewarded {
551 stash: nominator.who.clone(),
552 dest,
553 amount: imbalance.peek(),
554 });
555 total_imbalance.subsume(imbalance);
556 }
557 }
558
559 T::Reward::on_unbalanced(total_imbalance);
560 nominator_payout_count
561 }
562
563 fn payout_account_for_dest(
565 stash: &T::AccountId,
566 dest: &RewardDestination<T::AccountId>,
567 ) -> Option<T::AccountId> {
568 match dest {
569 RewardDestination::Stash | RewardDestination::Staked => Some(stash.clone()),
570 RewardDestination::Account(ref dest_account) => Some(dest_account.clone()),
571 RewardDestination::None => None,
572 #[allow(deprecated)]
573 RewardDestination::Controller => Self::bonded(stash),
574 }
575 }
576
577 fn make_payout_from_provider(
579 era: EraIndex,
580 stash: &T::AccountId,
581 amount: BalanceOf<T>,
582 ) -> Option<(BalanceOf<T>, RewardDestination<T::AccountId>)> {
583 if amount.is_zero() {
584 return None;
585 }
586
587 let dest = match Self::payee(Stash(stash.clone())) {
588 Some(d) => d,
589 None => {
590 Self::deposit_event(Event::<T>::Unexpected(UnexpectedKind::MissingPayee {
591 era,
592 stash: stash.clone(),
593 }));
594 return None;
595 },
596 };
597
598 let payout_account = Self::payout_account_for_dest(stash, &dest)?;
599
600 let staker_rewards_pot =
601 T::RewardPots::pot_account(RewardPot::Era(era, RewardKind::StakerRewards));
602 if let Err(e) = T::Currency::transfer(
603 &staker_rewards_pot,
604 &payout_account,
605 amount,
606 Preservation::Expendable,
607 ) {
608 log!(
609 error,
610 "Failed to transfer reward from pot for era {:?}, stash {:?}: {:?}",
611 era,
612 stash,
613 e
614 );
615 return None;
616 }
617
618 if matches!(dest, RewardDestination::Staked) {
620 if let Ok(mut ledger) = Self::ledger(Stash(stash.clone())) {
621 ledger.active += amount;
622 ledger.total += amount;
623 let _ = ledger
624 .update()
625 .defensive_proof("ledger fetched from storage, so it exists; qed.");
626 }
627 }
628
629 Some((amount, dest))
630 }
631
632 fn make_payout_legacy(
634 era: EraIndex,
635 stash: &T::AccountId,
636 amount: BalanceOf<T>,
637 ) -> Option<(PositiveImbalanceOf<T>, RewardDestination<T::AccountId>)> {
638 if amount.is_zero() {
639 return None;
640 }
641 let dest = match Self::payee(StakingAccount::Stash(stash.clone())) {
642 Some(d) => d,
643 None => {
644 Self::deposit_event(Event::<T>::Unexpected(UnexpectedKind::MissingPayee {
645 era,
646 stash: stash.clone(),
647 }));
648 return None;
649 },
650 };
651
652 let maybe_imbalance = match dest {
653 RewardDestination::Stash => asset::mint_into_existing::<T>(stash, amount),
654 RewardDestination::Staked => Self::ledger(Stash(stash.clone()))
655 .and_then(|mut ledger| {
656 ledger.active += amount;
657 ledger.total += amount;
658 let r = asset::mint_into_existing::<T>(stash, amount);
659 let _ = ledger
660 .update()
661 .defensive_proof("ledger fetched from storage, so it exists; qed.");
662 Ok(r)
663 })
664 .unwrap_or_default(),
665 RewardDestination::Account(ref dest_account) => {
666 Some(asset::mint_creating::<T>(dest_account, amount))
667 },
668 RewardDestination::None => None,
669 #[allow(deprecated)]
670 RewardDestination::Controller => Self::bonded(stash).map(|controller| {
671 defensive!("Paying out controller as reward destination which is deprecated.");
672 asset::mint_creating::<T>(&controller, amount)
673 }),
674 };
675 maybe_imbalance.map(|imbalance| (imbalance, dest))
676 }
677
678 fn calculate_validator_incentive_for_page(
692 era: EraIndex,
693 stash: &T::AccountId,
694 page_stake_part: Perbill,
695 era_reward_points: &EraRewardPoints<T>,
696 ) -> Option<BalanceOf<T>> {
697 let era_incentive_budget = Eras::<T>::get_validator_incentive_budget(era);
698 if era_incentive_budget.is_zero() {
699 return None;
700 }
701
702 let validator_weight = match ErasValidatorIncentiveWeight::<T>::get(era, stash) {
703 Some(w) if !w.is_zero() => w,
705 _ => return None,
706 };
707
708 let share_part = if Eras::<T>::uses_weighted_points(era) {
711 let sum_weighted_points = ErasSumWeightedPoints::<T>::get(era);
716 if sum_weighted_points.is_zero() {
717 log!(warn, "Sum of weighted points is zero but budget exists for era {}", era);
718 Self::deposit_event(Event::<T>::Unexpected(
719 UnexpectedKind::ValidatorIncentiveWeightMismatch { era },
720 ));
721 return None;
722 }
723 let validator_points: RewardPoint =
724 era_reward_points.individual.get(stash).copied().unwrap_or(0);
725 let numerator = validator_weight.saturating_mul(BalanceOf::<T>::from(validator_points));
726 Perbill::from_rational(numerator, sum_weighted_points)
727 } else {
728 let total_weight = ErasSumValidatorIncentiveWeight::<T>::get(era);
732 if total_weight.is_zero() {
733 log!(
734 warn,
735 "Total validator incentive weight is zero but budget exists for era {}",
736 era
737 );
738 Self::deposit_event(Event::<T>::Unexpected(
739 UnexpectedKind::ValidatorIncentiveWeightMismatch { era },
740 ));
741 return None;
742 }
743 Perbill::from_rational(validator_weight, total_weight)
744 };
745
746 if share_part.is_zero() {
747 return None;
748 }
749
750 let validator_total_incentive = share_part.mul_floor(era_incentive_budget);
751 let validator_incentive_for_page = page_stake_part.mul_floor(validator_total_incentive);
752
753 if validator_incentive_for_page.is_zero() {
754 return None;
755 }
756
757 Some(validator_incentive_for_page)
758 }
759
760 fn transfer_validator_incentive(era: EraIndex, stash: &T::AccountId, amount: BalanceOf<T>) {
764 let Some(dest) = Self::payee(Stash(stash.clone())) else {
765 Self::deposit_event(Event::<T>::Unexpected(UnexpectedKind::MissingPayee {
766 era,
767 stash: stash.clone(),
768 }));
769 return;
770 };
771 let Some(payout_account) = Self::payout_account_for_dest(stash, &dest) else {
772 return;
774 };
775
776 let incentive_pot = T::RewardPots::pot_account(crate::RewardPot::Era(
777 era,
778 crate::RewardKind::ValidatorSelfStake,
779 ));
780
781 match T::Currency::transfer(
782 &incentive_pot,
783 &payout_account,
784 amount,
785 Preservation::Expendable,
786 ) {
787 Ok(_) => {
788 Self::deposit_event(Event::<T>::ValidatorIncentivePaid {
789 era,
790 validator_stash: stash.clone(),
791 dest,
792 amount,
793 });
794 },
795 Err(e) => {
796 log!(warn, "Failed to transfer liquid incentive: {:?}", e);
797 Self::deposit_event(Event::<T>::Unexpected(
798 UnexpectedKind::ValidatorIncentiveTransferFailed { era },
799 ));
800 defensive!("Validator incentive liquid transfer failed");
801 },
802 }
803 }
804
805 pub(crate) fn chill_stash(stash: &T::AccountId) {
807 let chilled_as_validator = Self::do_remove_validator(stash);
808 let chilled_as_nominator = Self::do_remove_nominator(stash);
809 if chilled_as_validator || chilled_as_nominator {
810 Self::deposit_event(Event::<T>::Chilled { stash: stash.clone() });
811 }
812 }
813
814 pub(crate) fn kill_stash(stash: &T::AccountId) -> DispatchResult {
823 StakingLedger::<T>::kill(&stash)?;
826
827 Self::do_remove_validator(&stash);
828 Self::do_remove_nominator(&stash);
829
830 LastValidatorEra::<T>::remove(&stash);
832
833 Ok(())
834 }
835
836 #[cfg(test)]
837 pub(crate) fn reward_by_ids(validators_points: impl IntoIterator<Item = (T::AccountId, u32)>) {
838 Eras::<T>::reward_active_era(validators_points)
839 }
840
841 pub(crate) fn set_force_era(mode: Forcing) {
843 log!(info, "Setting force era mode {:?}.", mode);
844 ForceEra::<T>::put(mode);
845 Self::deposit_event(Event::<T>::ForceEra { mode });
846 }
847
848 #[cfg(feature = "runtime-benchmarks")]
849 pub fn add_era_stakers(
850 current_era: EraIndex,
851 stash: T::AccountId,
852 exposure: Exposure<T::AccountId, BalanceOf<T>>,
853 ) {
854 Eras::<T>::upsert_exposure(current_era, &stash, exposure);
855 }
856
857 #[cfg(feature = "runtime-benchmarks")]
858 pub fn set_slash_reward_fraction(fraction: Perbill) {
859 SlashRewardFraction::<T>::put(fraction);
860 }
861
862 pub(crate) fn get_npos_voters(
872 bounds: DataProviderBounds,
873 status: &SnapshotStatus<T::AccountId>,
874 ) -> Vec<VoterOf<Self>> {
875 let mut voters_size_tracker: StaticTracker<Self> = StaticTracker::default();
876
877 let page_len_prediction = {
878 let all_voter_count = T::VoterList::count();
879 bounds.count.unwrap_or(all_voter_count.into()).min(all_voter_count.into()).0
880 };
881
882 let mut all_voters = Vec::<_>::with_capacity(page_len_prediction as usize);
883
884 let weight_of = Self::weight_of_fn();
886
887 let mut voters_seen = 0u32;
888 let mut validators_taken = 0u32;
889 let mut nominators_taken = 0u32;
890 let mut min_active_stake = u64::MAX;
891
892 let mut sorted_voters = match status {
893 SnapshotStatus::Waiting => T::VoterList::iter(),
895 SnapshotStatus::Ongoing(account_id) => T::VoterList::iter_from(&account_id)
897 .defensive_unwrap_or(Box::new(vec![].into_iter())),
898 SnapshotStatus::Consumed => Box::new(vec![].into_iter()),
900 };
901
902 while all_voters.len() < page_len_prediction as usize &&
903 voters_seen < (NPOS_MAX_ITERATIONS_COEFFICIENT * page_len_prediction as u32)
904 {
905 let voter = match sorted_voters.next() {
906 Some(voter) => {
907 voters_seen.saturating_inc();
908 voter
909 },
910 None => break,
911 };
912
913 let voter_weight = weight_of(&voter);
914 if voter_weight.is_zero() {
916 log!(debug, "voter's active balance is 0. skip this voter.");
917 continue;
918 }
919
920 if let Some(Nominations { targets, .. }) = <Nominators<T>>::get(&voter) {
921 if !targets.is_empty() {
922 let voter = (voter, voter_weight, targets);
927 if voters_size_tracker.try_register_voter(&voter, &bounds).is_err() {
928 Self::deposit_event(Event::<T>::SnapshotVotersSizeExceeded {
930 size: voters_size_tracker.size as u32,
931 });
932 break;
933 }
934
935 all_voters.push(voter);
936 nominators_taken.saturating_inc();
937 } else {
938 defensive!("non-nominator fetched from voter list: {:?}", voter);
939 }
941 min_active_stake =
942 if voter_weight < min_active_stake { voter_weight } else { min_active_stake };
943 } else if Validators::<T>::contains_key(&voter) {
944 let self_vote = (
946 voter.clone(),
947 voter_weight,
948 vec![voter.clone()]
949 .try_into()
950 .expect("`MaxVotesPerVoter` must be greater than or equal to 1"),
951 );
952
953 if voters_size_tracker.try_register_voter(&self_vote, &bounds).is_err() {
954 Self::deposit_event(Event::<T>::SnapshotVotersSizeExceeded {
956 size: voters_size_tracker.size as u32,
957 });
958 break;
959 }
960 all_voters.push(self_vote);
961 validators_taken.saturating_inc();
962 } else {
963 defensive!(
969 "invalid item in `VoterList`: {:?}, this nominator probably has too many nominations now",
970 voter,
971 );
972 }
973 }
974
975 debug_assert!(all_voters.capacity() == page_len_prediction as usize);
977
978 let min_active_stake: T::CurrencyBalance =
979 if all_voters.is_empty() { Zero::zero() } else { min_active_stake.into() };
980
981 MinimumActiveStake::<T>::put(min_active_stake);
982
983 all_voters
984 }
985
986 pub fn get_npos_targets(bounds: DataProviderBounds) -> Vec<T::AccountId> {
992 let mut targets_size_tracker: StaticTracker<Self> = StaticTracker::default();
993
994 let final_predicted_len = {
995 let all_target_count = T::TargetList::count();
996 bounds.count.unwrap_or(all_target_count.into()).min(all_target_count.into()).0
997 };
998
999 let mut all_targets = Vec::<T::AccountId>::with_capacity(final_predicted_len as usize);
1000 let mut targets_seen = 0;
1001
1002 let mut targets_iter = T::TargetList::iter();
1003 while all_targets.len() < final_predicted_len as usize &&
1004 targets_seen < (NPOS_MAX_ITERATIONS_COEFFICIENT * final_predicted_len as u32)
1005 {
1006 let target = match targets_iter.next() {
1007 Some(target) => {
1008 targets_seen.saturating_inc();
1009 target
1010 },
1011 None => break,
1012 };
1013
1014 if targets_size_tracker.try_register_target(target.clone(), &bounds).is_err() {
1015 log!(warn, "npos targets size exceeded, stopping iteration.");
1017 Self::deposit_event(Event::<T>::SnapshotTargetsSizeExceeded {
1018 size: targets_size_tracker.size as u32,
1019 });
1020 break;
1021 }
1022
1023 if Validators::<T>::contains_key(&target) {
1024 all_targets.push(target);
1025 }
1026 }
1027
1028 log!(debug, "[bounds {:?}] generated {} npos targets", bounds, all_targets.len());
1029
1030 all_targets
1031 }
1032
1033 pub fn do_add_nominator(who: &T::AccountId, nominations: Nominations<T>) {
1042 if !Nominators::<T>::contains_key(who) {
1043 let _ = T::VoterList::on_insert(who.clone(), Self::weight_of(who))
1045 .defensive_unwrap_or_default();
1046 }
1047 Nominators::<T>::insert(who, nominations);
1048 }
1049
1050 pub fn do_remove_nominator(who: &T::AccountId) -> bool {
1059 let outcome = if Nominators::<T>::contains_key(who) {
1060 Nominators::<T>::remove(who);
1061 let _ = T::VoterList::on_remove(who);
1062 true
1063 } else {
1064 false
1065 };
1066
1067 outcome
1068 }
1069
1070 pub fn do_add_validator(who: &T::AccountId, prefs: ValidatorPrefs) {
1078 if !Validators::<T>::contains_key(who) {
1079 let _ = T::VoterList::on_insert(who.clone(), Self::weight_of(who));
1081 }
1082 Validators::<T>::insert(who, prefs);
1083 }
1084
1085 pub fn do_remove_validator(who: &T::AccountId) -> bool {
1093 let outcome = if Validators::<T>::contains_key(who) {
1094 Validators::<T>::remove(who);
1095 let _ = T::VoterList::on_remove(who);
1096 true
1097 } else {
1098 false
1099 };
1100
1101 outcome
1102 }
1103
1104 pub(crate) fn register_weight(weight: Weight) {
1108 <frame_system::Pallet<T>>::register_extra_weight_unchecked(
1109 weight,
1110 DispatchClass::Mandatory,
1111 );
1112 }
1113
1114 pub fn eras_stakers(
1121 era: EraIndex,
1122 account: &T::AccountId,
1123 ) -> Exposure<T::AccountId, BalanceOf<T>> {
1124 Eras::<T>::get_full_exposure(era, account)
1125 }
1126
1127 pub(super) fn do_migrate_currency(stash: &T::AccountId) -> DispatchResult {
1128 if Self::is_virtual_staker(stash) {
1129 return Self::do_migrate_virtual_staker(stash);
1130 }
1131
1132 let ledger = Self::ledger(Stash(stash.clone()))?;
1133 let staked: BalanceOf<T> = T::OldCurrency::balance_locked(STAKING_ID, stash).into();
1134 ensure!(!staked.is_zero(), Error::<T>::AlreadyMigrated);
1135 ensure!(ledger.total == staked, Error::<T>::BadState);
1136
1137 T::OldCurrency::remove_lock(STAKING_ID, &stash);
1139
1140 let max_hold = asset::free_to_stake::<T>(&stash);
1142 let force_withdraw = if max_hold >= staked {
1143 asset::update_stake::<T>(&stash, staked)?;
1145 Zero::zero()
1146 } else {
1147 let force_withdraw = staked.saturating_sub(max_hold);
1150
1151 StakingLedger {
1154 total: max_hold,
1155 active: ledger.active.saturating_sub(force_withdraw),
1156 ..ledger
1158 }
1159 .update()?;
1160 force_withdraw
1161 };
1162
1163 frame_system::Pallet::<T>::dec_consumers(&stash);
1165
1166 Self::deposit_event(Event::<T>::CurrencyMigrated { stash: stash.clone(), force_withdraw });
1167 Ok(())
1168 }
1169
1170 fn do_migrate_virtual_staker(stash: &T::AccountId) -> DispatchResult {
1171 frame_system::Pallet::<T>::dec_consumers(&stash);
1174
1175 let actual_providers = frame_system::Pallet::<T>::providers(stash);
1180
1181 let expected_providers =
1182 if asset::free_to_stake::<T>(&stash) >= asset::existential_deposit::<T>() {
1185 2
1186 } else {
1187 1
1188 };
1189
1190 ensure!(actual_providers <= expected_providers, Error::<T>::BadState);
1192
1193 ensure!(actual_providers == expected_providers, Error::<T>::AlreadyMigrated);
1195
1196 let _ = frame_system::Pallet::<T>::dec_providers(&stash)?;
1198
1199 return Ok(());
1200 }
1201}
1202
1203impl<T: Config> Pallet<T> {
1204 pub fn api_nominations_quota(balance: BalanceOf<T>) -> u32 {
1208 T::NominationsQuota::get_quota(balance)
1209 }
1210
1211 pub fn api_eras_stakers(
1212 era: EraIndex,
1213 account: T::AccountId,
1214 ) -> Exposure<T::AccountId, BalanceOf<T>> {
1215 Self::eras_stakers(era, &account)
1216 }
1217
1218 pub fn api_eras_stakers_page_count(era: EraIndex, account: T::AccountId) -> Page {
1219 Eras::<T>::exposure_page_count(era, &account)
1220 }
1221
1222 pub fn api_pending_rewards(era: EraIndex, account: T::AccountId) -> bool {
1223 Eras::<T>::pending_rewards(era, &account)
1224 }
1225}
1226
1227impl<T: Config> ElectionDataProvider for Pallet<T> {
1228 type AccountId = T::AccountId;
1229 type BlockNumber = BlockNumberFor<T>;
1230 type MaxVotesPerVoter = MaxNominationsOf<T>;
1231
1232 fn desired_targets() -> data_provider::Result<u32> {
1233 Self::register_weight(T::DbWeight::get().reads(1));
1234 Ok(ValidatorCount::<T>::get())
1235 }
1236
1237 fn electing_voters(
1238 bounds: DataProviderBounds,
1239 page: PageIndex,
1240 ) -> data_provider::Result<Vec<VoterOf<Self>>> {
1241 let mut status = VoterSnapshotStatus::<T>::get();
1242 let voters = Self::get_npos_voters(bounds, &status);
1243
1244 match (page, &status) {
1246 (0, _) => status = SnapshotStatus::Waiting,
1248
1249 (_, SnapshotStatus::Waiting) | (_, SnapshotStatus::Ongoing(_)) => {
1250 let maybe_last = voters.last().map(|(x, _, _)| x).cloned();
1251
1252 if let Some(ref last) = maybe_last {
1253 let has_next =
1254 T::VoterList::iter_from(last).ok().and_then(|mut i| i.next()).is_some();
1255 if has_next {
1256 status = SnapshotStatus::Ongoing(last.clone());
1257 } else {
1258 status = SnapshotStatus::Consumed;
1259 }
1260 }
1261 },
1262 (_, SnapshotStatus::Consumed) => (),
1264 }
1265
1266 log!(
1267 debug,
1268 "[page {}, (next) status {:?}, bounds {:?}] generated {} npos voters [first: {:?}, last: {:?}]",
1269 page,
1270 status,
1271 bounds,
1272 voters.len(),
1273 voters.first().map(|(x, y, _)| (x, y)),
1274 voters.last().map(|(x, y, _)| (x, y)),
1275 );
1276
1277 match status {
1278 SnapshotStatus::Ongoing(_) => T::VoterList::lock(),
1279 _ => T::VoterList::unlock(),
1280 }
1281
1282 VoterSnapshotStatus::<T>::put(status);
1283 debug_assert!(!bounds.slice_exhausted(&voters));
1284
1285 Ok(voters)
1286 }
1287
1288 fn electing_voters_stateless(
1289 bounds: DataProviderBounds,
1290 ) -> data_provider::Result<Vec<VoterOf<Self>>> {
1291 let voters = Self::get_npos_voters(bounds, &SnapshotStatus::Waiting);
1292 log!(debug, "[stateless, bounds {:?}] generated {} npos voters", bounds, voters.len(),);
1293 Ok(voters)
1294 }
1295
1296 fn electable_targets(
1297 bounds: DataProviderBounds,
1298 page: PageIndex,
1299 ) -> data_provider::Result<Vec<T::AccountId>> {
1300 if page > 0 {
1301 log!(warn, "multi-page target snapshot not supported, returning page 0.");
1302 }
1303
1304 let targets = Self::get_npos_targets(bounds);
1305 if bounds.exhausted(None, CountBound(targets.len() as u32).into()) {
1306 return Err("Target snapshot too big");
1307 }
1308
1309 debug_assert!(!bounds.slice_exhausted(&targets));
1310
1311 Ok(targets)
1312 }
1313
1314 fn next_election_prediction(_: BlockNumberFor<T>) -> BlockNumberFor<T> {
1315 debug_assert!(false, "this is deprecated and not used anymore");
1316 sp_runtime::traits::Bounded::max_value()
1317 }
1318
1319 #[cfg(feature = "runtime-benchmarks")]
1320 fn fetch_page(page: PageIndex) {
1321 session_rotation::EraElectionPlanner::<T>::do_elect_paged(page);
1322 }
1323
1324 #[cfg(feature = "runtime-benchmarks")]
1325 fn add_voter(
1326 voter: T::AccountId,
1327 weight: VoteWeight,
1328 targets: BoundedVec<T::AccountId, Self::MaxVotesPerVoter>,
1329 ) {
1330 let stake = <BalanceOf<T>>::try_from(weight).unwrap_or_else(|_| {
1331 panic!("cannot convert a VoteWeight into BalanceOf, benchmark needs reconfiguring.")
1332 });
1333 <Bonded<T>>::insert(voter.clone(), voter.clone());
1334 <Ledger<T>>::insert(voter.clone(), StakingLedger::<T>::new(voter.clone(), stake));
1335
1336 Self::do_add_nominator(&voter, Nominations { targets, submitted_in: 0, suppressed: false });
1337 }
1338
1339 #[cfg(feature = "runtime-benchmarks")]
1340 fn add_target(target: T::AccountId) {
1341 let stake = (Self::min_validator_bond() + 1u32.into()) * 100u32.into();
1342 <Bonded<T>>::insert(target.clone(), target.clone());
1343 <Ledger<T>>::insert(target.clone(), StakingLedger::<T>::new(target.clone(), stake));
1344 Self::do_add_validator(
1345 &target,
1346 ValidatorPrefs { commission: Perbill::zero(), blocked: false },
1347 );
1348 }
1349
1350 #[cfg(feature = "runtime-benchmarks")]
1351 fn clear() {
1352 #[allow(deprecated)]
1353 <Bonded<T>>::remove_all(None);
1354 #[allow(deprecated)]
1355 <Ledger<T>>::remove_all(None);
1356 #[allow(deprecated)]
1357 <Validators<T>>::remove_all();
1358 #[allow(deprecated)]
1359 <Nominators<T>>::remove_all();
1360
1361 T::VoterList::unsafe_clear();
1362 }
1363
1364 #[cfg(feature = "runtime-benchmarks")]
1365 fn put_snapshot(
1366 voters: Vec<VoterOf<Self>>,
1367 targets: Vec<T::AccountId>,
1368 target_stake: Option<VoteWeight>,
1369 ) {
1370 targets.into_iter().for_each(|v| {
1371 let stake: BalanceOf<T> = target_stake
1372 .and_then(|w| <BalanceOf<T>>::try_from(w).ok())
1373 .unwrap_or_else(|| Self::min_nominator_bond() * 100u32.into());
1374 <Bonded<T>>::insert(v.clone(), v.clone());
1375 <Ledger<T>>::insert(v.clone(), StakingLedger::<T>::new(v.clone(), stake));
1376 Self::do_add_validator(
1377 &v,
1378 ValidatorPrefs { commission: Perbill::zero(), blocked: false },
1379 );
1380 });
1381
1382 voters.into_iter().for_each(|(v, s, t)| {
1383 let stake = <BalanceOf<T>>::try_from(s).unwrap_or_else(|_| {
1384 panic!("cannot convert a VoteWeight into BalanceOf, benchmark needs reconfiguring.")
1385 });
1386 <Bonded<T>>::insert(v.clone(), v.clone());
1387 <Ledger<T>>::insert(v.clone(), StakingLedger::<T>::new(v.clone(), stake));
1388 Self::do_add_nominator(
1389 &v,
1390 Nominations { targets: t, submitted_in: 0, suppressed: false },
1391 );
1392 });
1393 }
1394
1395 #[cfg(feature = "runtime-benchmarks")]
1396 fn set_desired_targets(count: u32) {
1397 ValidatorCount::<T>::put(count);
1398 }
1399}
1400
1401impl<T: Config> rc_client::AHStakingInterface for Pallet<T> {
1402 type AccountId = T::AccountId;
1403 type MaxValidatorSet = T::MaxValidatorSet;
1404
1405 fn on_relay_session_report(report: rc_client::SessionReport<Self::AccountId>) -> Weight {
1414 log!(debug, "Received session report: {}", report,);
1415
1416 let rc_client::SessionReport {
1417 end_index,
1418 activation_timestamp,
1419 validator_points,
1420 leftover,
1421 } = report;
1422 debug_assert!(!leftover);
1423
1424 let validator_count = validator_points.len() as u32;
1425 Eras::<T>::reward_active_era(validator_points.into_iter());
1427 session_rotation::Rotator::<T>::end_session(
1428 end_index,
1429 activation_timestamp,
1430 validator_count,
1431 )
1432 }
1433
1434 fn weigh_on_relay_session_report(report: &rc_client::SessionReport<Self::AccountId>) -> Weight {
1435 let era_start = if report.activation_timestamp.is_some() && !report.leftover {
1436 session_rotation::Rotator::<T>::notify_era_start_max_weight()
1437 } else {
1438 Weight::zero()
1439 };
1440 T::WeightInfo::rc_on_session_report(report.validator_points.len() as u32)
1441 .saturating_add(era_start)
1442 }
1443
1444 fn on_new_offences(
1455 slash_session: SessionIndex,
1456 offences: Vec<rc_client::Offence<T::AccountId>>,
1457 ) -> Weight {
1458 log!(debug, "🦹 on_new_offences: {:?}", offences);
1459 let weight = T::WeightInfo::rc_on_offence(offences.len() as u32);
1460
1461 let Some(active_era) = ActiveEra::<T>::get() else {
1463 log!(warn, "🦹 on_new_offences: no active era; ignoring offence");
1464 return T::WeightInfo::rc_on_offence(0);
1465 };
1466
1467 let active_era_start_session = Rotator::<T>::active_era_start_session_index();
1468
1469 let offence_era = if slash_session >= active_era_start_session {
1472 active_era.index
1473 } else {
1474 match BondedEras::<T>::get()
1475 .iter()
1476 .rev()
1478 .find_map(|&(era, sesh)| if sesh <= slash_session { Some(era) } else { None })
1479 {
1480 Some(era) => era,
1481 None => {
1482 log!(warn, "🦹 on_offence: no era found for slash_session; ignoring offence");
1485 return T::WeightInfo::rc_on_offence(0);
1486 },
1487 }
1488 };
1489
1490 let oldest_reportable_offence_era = if T::SlashDeferDuration::get() == 0 {
1491 active_era.index.saturating_sub(T::BondingDuration::get().saturating_sub(2))
1497 } else {
1498 active_era.index.saturating_sub(T::SlashDeferDuration::get().saturating_sub(1))
1501 };
1502
1503 for o in offences {
1504 let slash_fraction = o.slash_fraction;
1505 let validator: <T as frame_system::Config>::AccountId = o.offender.into();
1506
1507 if offence_era < oldest_reportable_offence_era {
1509 log!(warn, "🦹 on_new_offences: offence era {:?} too old; Can only accept offences from era {:?} or newer", offence_era, oldest_reportable_offence_era);
1510 Self::deposit_event(Event::<T>::OffenceTooOld {
1511 validator: validator.clone(),
1512 fraction: slash_fraction,
1513 offence_era,
1514 });
1515 continue;
1517 }
1518 let Some(exposure_overview) = <ErasStakersOverview<T>>::get(&offence_era, &validator)
1519 else {
1520 log!(
1523 warn,
1524 "🦹 on_offence: no exposure found for {:?} in era {}; ignoring offence",
1525 validator,
1526 offence_era
1527 );
1528 continue;
1529 };
1530
1531 Self::deposit_event(Event::<T>::OffenceReported {
1532 validator: validator.clone(),
1533 fraction: slash_fraction,
1534 offence_era,
1535 });
1536
1537 let prior_slash_fraction = ValidatorSlashInEra::<T>::get(offence_era, &validator)
1538 .map_or(Zero::zero(), |(f, _)| f);
1539
1540 if let Some(existing) = OffenceQueue::<T>::get(offence_era, &validator) {
1541 if slash_fraction.deconstruct() > existing.slash_fraction.deconstruct() {
1542 OffenceQueue::<T>::insert(
1543 offence_era,
1544 &validator,
1545 OffenceRecord {
1546 reporter: o.reporters.first().cloned(),
1547 reported_era: active_era.index,
1548 slash_fraction,
1549 ..existing
1550 },
1551 );
1552
1553 ValidatorSlashInEra::<T>::insert(
1555 offence_era,
1556 &validator,
1557 (slash_fraction, exposure_overview.own),
1558 );
1559
1560 log!(
1561 debug,
1562 "🦹 updated slash for {:?}: {:?} (prior: {:?})",
1563 validator,
1564 slash_fraction,
1565 prior_slash_fraction,
1566 );
1567 } else {
1568 log!(
1569 debug,
1570 "🦹 ignored slash for {:?}: {:?} (existing prior is larger: {:?})",
1571 validator,
1572 slash_fraction,
1573 prior_slash_fraction,
1574 );
1575 }
1576 } else if slash_fraction.deconstruct() > prior_slash_fraction.deconstruct() {
1577 ValidatorSlashInEra::<T>::insert(
1578 offence_era,
1579 &validator,
1580 (slash_fraction, exposure_overview.own),
1581 );
1582
1583 OffenceQueue::<T>::insert(
1584 offence_era,
1585 &validator,
1586 OffenceRecord {
1587 reporter: o.reporters.first().cloned(),
1588 reported_era: active_era.index,
1589 exposure_page: exposure_overview.page_count.saturating_sub(1),
1592 slash_fraction,
1593 prior_slash_fraction,
1594 },
1595 );
1596
1597 OffenceQueueEras::<T>::mutate(|q| {
1598 if let Some(eras) = q {
1599 log!(debug, "🦹 inserting offence era {} into existing queue", offence_era);
1600 eras.binary_search(&offence_era).err().map(|idx| {
1601 eras.try_insert(idx, offence_era).defensive_proof(
1602 "Offence era must be present in the existing queue",
1603 )
1604 });
1605 } else {
1606 let mut eras = WeakBoundedVec::default();
1607 log!(debug, "🦹 inserting offence era {} into empty queue", offence_era);
1608 let _ = eras
1609 .try_push(offence_era)
1610 .defensive_proof("Failed to push offence era into empty queue");
1611 *q = Some(eras);
1612 }
1613 });
1614
1615 log!(
1616 debug,
1617 "🦹 queued slash for {:?}: {:?} (prior: {:?})",
1618 validator,
1619 slash_fraction,
1620 prior_slash_fraction,
1621 );
1622 } else {
1623 log!(
1624 debug,
1625 "🦹 ignored slash for {:?}: {:?} (already slashed in era with prior: {:?})",
1626 validator,
1627 slash_fraction,
1628 prior_slash_fraction,
1629 );
1630 }
1631 }
1632
1633 weight
1634 }
1635
1636 fn weigh_on_new_offences(offence_count: u32) -> Weight {
1637 T::WeightInfo::rc_on_offence(offence_count)
1638 }
1639
1640 fn active_era_start_session_index() -> SessionIndex {
1641 Rotator::<T>::active_era_start_session_index()
1642 }
1643
1644 fn is_validator(who: &Self::AccountId) -> bool {
1645 Validators::<T>::contains_key(who)
1646 }
1647}
1648
1649impl<T: Config> ScoreProvider<T::AccountId> for Pallet<T> {
1650 type Score = VoteWeight;
1651
1652 fn score(who: &T::AccountId) -> Option<Self::Score> {
1653 Self::ledger(Stash(who.clone()))
1654 .ok()
1655 .and_then(|l| {
1656 if Nominators::<T>::contains_key(&l.stash) ||
1657 Validators::<T>::contains_key(&l.stash)
1658 {
1659 Some(l.active)
1660 } else {
1661 None
1662 }
1663 })
1664 .map(|a| {
1665 let issuance = asset::total_issuance::<T>();
1666 T::CurrencyToVote::to_vote(a, issuance)
1667 })
1668 }
1669
1670 #[cfg(feature = "runtime-benchmarks")]
1671 fn set_score_of(who: &T::AccountId, weight: Self::Score) {
1672 let active: BalanceOf<T> = weight.try_into().map_err(|_| ()).unwrap();
1675 let mut ledger = match Self::ledger(StakingAccount::Stash(who.clone())) {
1676 Ok(l) => l,
1677 Err(_) => StakingLedger::default_from(who.clone()),
1678 };
1679 ledger.active = active;
1680
1681 <Ledger<T>>::insert(who, ledger);
1682 <Bonded<T>>::insert(who, who);
1683 <Validators<T>>::insert(who, ValidatorPrefs::default());
1686
1687 let imbalance = asset::burn::<T>(asset::total_issuance::<T>());
1691 core::mem::forget(imbalance);
1694 }
1695}
1696
1697pub struct UseValidatorsMap<T>(core::marker::PhantomData<T>);
1701impl<T: Config> SortedListProvider<T::AccountId> for UseValidatorsMap<T> {
1702 type Score = BalanceOf<T>;
1703 type Error = ();
1704
1705 fn iter() -> Box<dyn Iterator<Item = T::AccountId>> {
1707 Box::new(Validators::<T>::iter().map(|(v, _)| v))
1708 }
1709 fn iter_from(
1710 start: &T::AccountId,
1711 ) -> Result<Box<dyn Iterator<Item = T::AccountId>>, Self::Error> {
1712 if Validators::<T>::contains_key(start) {
1713 let start_key = Validators::<T>::hashed_key_for(start);
1714 Ok(Box::new(Validators::<T>::iter_from(start_key).map(|(n, _)| n)))
1715 } else {
1716 Err(())
1717 }
1718 }
1719 fn lock() {}
1720 fn unlock() {}
1721 fn count() -> u32 {
1722 Validators::<T>::count()
1723 }
1724 fn contains(id: &T::AccountId) -> bool {
1725 Validators::<T>::contains_key(id)
1726 }
1727 fn on_insert(_: T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> {
1728 Ok(())
1730 }
1731 fn get_score(id: &T::AccountId) -> Result<Self::Score, Self::Error> {
1732 Ok(Pallet::<T>::weight_of(id).into())
1733 }
1734 fn on_update(_: &T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> {
1735 Ok(())
1737 }
1738 fn on_remove(_: &T::AccountId) -> Result<(), Self::Error> {
1739 Ok(())
1741 }
1742 fn unsafe_regenerate(
1743 _: impl IntoIterator<Item = T::AccountId>,
1744 _: Box<dyn Fn(&T::AccountId) -> Option<Self::Score>>,
1745 ) -> u32 {
1746 0
1748 }
1749 #[cfg(feature = "try-runtime")]
1750 fn try_state() -> Result<(), TryRuntimeError> {
1751 Ok(())
1752 }
1753
1754 fn unsafe_clear() {
1755 #[allow(deprecated)]
1756 Validators::<T>::remove_all();
1757 }
1758
1759 #[cfg(feature = "runtime-benchmarks")]
1760 fn score_update_worst_case(_who: &T::AccountId, _is_increase: bool) -> Self::Score {
1761 unimplemented!()
1762 }
1763}
1764
1765pub struct UseNominatorsAndValidatorsMap<T>(core::marker::PhantomData<T>);
1769impl<T: Config> SortedListProvider<T::AccountId> for UseNominatorsAndValidatorsMap<T> {
1770 type Error = ();
1771 type Score = VoteWeight;
1772
1773 fn iter() -> Box<dyn Iterator<Item = T::AccountId>> {
1774 Box::new(
1775 Validators::<T>::iter()
1776 .map(|(v, _)| v)
1777 .chain(Nominators::<T>::iter().map(|(n, _)| n)),
1778 )
1779 }
1780 fn iter_from(
1781 start: &T::AccountId,
1782 ) -> Result<Box<dyn Iterator<Item = T::AccountId>>, Self::Error> {
1783 if Validators::<T>::contains_key(start) {
1784 let start_key = Validators::<T>::hashed_key_for(start);
1785 Ok(Box::new(
1786 Validators::<T>::iter_from(start_key)
1787 .map(|(n, _)| n)
1788 .chain(Nominators::<T>::iter().map(|(x, _)| x)),
1789 ))
1790 } else if Nominators::<T>::contains_key(start) {
1791 let start_key = Nominators::<T>::hashed_key_for(start);
1792 Ok(Box::new(Nominators::<T>::iter_from(start_key).map(|(n, _)| n)))
1793 } else {
1794 Err(())
1795 }
1796 }
1797 fn lock() {}
1798 fn unlock() {}
1799 fn count() -> u32 {
1800 Nominators::<T>::count().saturating_add(Validators::<T>::count())
1801 }
1802 fn contains(id: &T::AccountId) -> bool {
1803 Nominators::<T>::contains_key(id) || Validators::<T>::contains_key(id)
1804 }
1805 fn on_insert(_: T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> {
1806 Ok(())
1808 }
1809 fn get_score(id: &T::AccountId) -> Result<Self::Score, Self::Error> {
1810 Ok(Pallet::<T>::weight_of(id))
1811 }
1812 fn on_update(_: &T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> {
1813 Ok(())
1815 }
1816 fn on_remove(_: &T::AccountId) -> Result<(), Self::Error> {
1817 Ok(())
1819 }
1820 fn unsafe_regenerate(
1821 _: impl IntoIterator<Item = T::AccountId>,
1822 _: Box<dyn Fn(&T::AccountId) -> Option<Self::Score>>,
1823 ) -> u32 {
1824 0
1826 }
1827
1828 #[cfg(feature = "try-runtime")]
1829 fn try_state() -> Result<(), TryRuntimeError> {
1830 Ok(())
1831 }
1832
1833 fn unsafe_clear() {
1834 #[allow(deprecated)]
1837 Nominators::<T>::remove_all();
1838 #[allow(deprecated)]
1839 Validators::<T>::remove_all();
1840 }
1841
1842 #[cfg(feature = "runtime-benchmarks")]
1843 fn score_update_worst_case(_who: &T::AccountId, _is_increase: bool) -> Self::Score {
1844 unimplemented!()
1845 }
1846}
1847
1848impl<T: Config> StakingInterface for Pallet<T> {
1849 type AccountId = T::AccountId;
1850 type Balance = BalanceOf<T>;
1851 type CurrencyToVote = T::CurrencyToVote;
1852
1853 fn minimum_nominator_bond() -> Self::Balance {
1854 Self::min_nominator_bond()
1855 }
1856
1857 fn minimum_validator_bond() -> Self::Balance {
1858 Self::min_validator_bond()
1859 }
1860
1861 fn stash_by_ctrl(controller: &Self::AccountId) -> Result<Self::AccountId, DispatchError> {
1862 Self::ledger(Controller(controller.clone()))
1863 .map(|l| l.stash)
1864 .map_err(|e| e.into())
1865 }
1866
1867 fn bonding_duration() -> EraIndex {
1872 T::BondingDuration::get()
1873 }
1874
1875 fn nominator_bonding_duration() -> EraIndex {
1889 if AreNominatorsSlashable::<T>::get() {
1890 T::BondingDuration::get()
1891 } else {
1892 T::NominatorFastUnbondDuration::get()
1893 }
1894 }
1895
1896 fn current_era() -> EraIndex {
1897 Rotator::<T>::active_era()
1900 }
1901
1902 fn stake(who: &Self::AccountId) -> Result<Stake<BalanceOf<T>>, DispatchError> {
1903 Self::ledger(Stash(who.clone()))
1904 .map(|l| Stake { total: l.total, active: l.active })
1905 .map_err(|e| e.into())
1906 }
1907
1908 fn bond_extra(who: &Self::AccountId, extra: Self::Balance) -> DispatchResult {
1909 Self::bond_extra(RawOrigin::Signed(who.clone()).into(), extra)
1910 }
1911
1912 fn unbond(who: &Self::AccountId, value: Self::Balance) -> DispatchResult {
1913 let ctrl = Self::bonded(who).ok_or(Error::<T>::NotStash)?;
1914 Self::unbond(RawOrigin::Signed(ctrl).into(), value)
1915 .map_err(|with_post| with_post.error)
1916 .map(|_| ())
1917 }
1918
1919 fn set_payee(stash: &Self::AccountId, reward_acc: &Self::AccountId) -> DispatchResult {
1920 ensure!(
1924 !Self::is_virtual_staker(stash) || stash != reward_acc,
1925 Error::<T>::RewardDestinationRestricted
1926 );
1927
1928 let ledger = Self::ledger(Stash(stash.clone()))?;
1929 let _ = ledger
1930 .set_payee(RewardDestination::Account(reward_acc.clone()))
1931 .defensive_proof("ledger was retrieved from storage, thus its bonded; qed.")?;
1932
1933 Ok(())
1934 }
1935
1936 fn chill(who: &Self::AccountId) -> DispatchResult {
1937 let ctrl = Self::bonded(who).ok_or(Error::<T>::NotStash)?;
1940 Self::chill(RawOrigin::Signed(ctrl).into())
1941 }
1942
1943 fn withdraw_unbonded(
1944 who: Self::AccountId,
1945 _num_slashing_spans: u32,
1946 ) -> Result<bool, DispatchError> {
1947 let ctrl = Self::bonded(&who).ok_or(Error::<T>::NotStash)?;
1948 Self::withdraw_unbonded(RawOrigin::Signed(ctrl.clone()).into(), 0)
1949 .map(|_| !StakingLedger::<T>::is_bonded(StakingAccount::Controller(ctrl)))
1950 .map_err(|with_post| with_post.error)
1951 }
1952
1953 fn bond(
1954 who: &Self::AccountId,
1955 value: Self::Balance,
1956 payee: &Self::AccountId,
1957 ) -> DispatchResult {
1958 Self::bond(
1959 RawOrigin::Signed(who.clone()).into(),
1960 value,
1961 RewardDestination::Account(payee.clone()),
1962 )
1963 }
1964
1965 fn nominate(who: &Self::AccountId, targets: Vec<Self::AccountId>) -> DispatchResult {
1966 let ctrl = Self::bonded(who).ok_or(Error::<T>::NotStash)?;
1967 let targets = targets.into_iter().map(T::Lookup::unlookup).collect::<Vec<_>>();
1968 Self::nominate(RawOrigin::Signed(ctrl).into(), targets)
1969 }
1970
1971 fn desired_validator_count() -> u32 {
1972 ValidatorCount::<T>::get()
1973 }
1974
1975 fn election_ongoing() -> bool {
1976 <T::ElectionProvider as ElectionProvider>::status().is_ok()
1977 }
1978
1979 fn force_unstake(who: Self::AccountId) -> sp_runtime::DispatchResult {
1980 Self::force_unstake(RawOrigin::Root.into(), who.clone(), 0)
1981 }
1982
1983 fn is_exposed_in_era(who: &Self::AccountId, era: &EraIndex) -> bool {
1984 ErasStakersPaged::<T>::iter_prefix((era,)).any(|((validator, _), exposure_page)| {
1985 validator == *who || exposure_page.others.iter().any(|i| i.who == *who)
1986 })
1987 }
1988
1989 fn status(
1990 who: &Self::AccountId,
1991 ) -> Result<sp_staking::StakerStatus<Self::AccountId>, DispatchError> {
1992 if !StakingLedger::<T>::is_bonded(StakingAccount::Stash(who.clone())) {
1993 return Err(Error::<T>::NotStash.into());
1994 }
1995
1996 let is_validator = Validators::<T>::contains_key(&who);
1997 let is_nominator = Nominators::<T>::get(&who);
1998
1999 use sp_staking::StakerStatus;
2000 match (is_validator, is_nominator.is_some()) {
2001 (false, false) => Ok(StakerStatus::Idle),
2002 (true, false) => Ok(StakerStatus::Validator),
2003 (false, true) => Ok(StakerStatus::Nominator(
2004 is_nominator.expect("is checked above; qed").targets.into_inner(),
2005 )),
2006 (true, true) => {
2007 defensive!("cannot be both validators and nominator");
2008 Err(Error::<T>::BadState.into())
2009 },
2010 }
2011 }
2012
2013 fn is_virtual_staker(who: &T::AccountId) -> bool {
2018 frame_system::Pallet::<T>::account_nonce(who).is_zero() &&
2019 VirtualStakers::<T>::contains_key(who)
2020 }
2021
2022 fn slash_reward_fraction() -> Perbill {
2023 SlashRewardFraction::<T>::get()
2024 }
2025
2026 sp_staking::runtime_benchmarks_enabled! {
2027 fn nominations(who: &Self::AccountId) -> Option<Vec<T::AccountId>> {
2028 Nominators::<T>::get(who).map(|n| n.targets.into_inner())
2029 }
2030
2031 fn add_era_stakers(
2032 current_era: &EraIndex,
2033 stash: &T::AccountId,
2034 exposures: Vec<(Self::AccountId, Self::Balance)>,
2035 ) {
2036 let others = exposures
2037 .iter()
2038 .map(|(who, value)| crate::IndividualExposure { who: who.clone(), value: *value })
2039 .collect::<Vec<_>>();
2040 let exposure = Exposure { total: Default::default(), own: Default::default(), others };
2041 Eras::<T>::upsert_exposure(*current_era, stash, exposure);
2042 }
2043
2044 fn max_exposure_page_size() -> Page {
2045 T::MaxExposurePageSize::get()
2046 }
2047 }
2048
2049 sp_staking::std_or_benchmarks_enabled! {
2050 fn set_era(era: EraIndex) {
2051 ActiveEra::<T>::put(crate::ActiveEraInfo { index: era, start: None });
2052 CurrentEra::<T>::put(era.saturating_add(1));
2054 }
2055 }
2056}
2057
2058impl<T: Config> sp_staking::StakingUnchecked for Pallet<T> {
2059 fn migrate_to_virtual_staker(who: &Self::AccountId) -> DispatchResult {
2060 asset::kill_stake::<T>(who)?;
2061 VirtualStakers::<T>::insert(who, ());
2062 Ok(())
2063 }
2064
2065 fn virtual_bond(
2069 keyless_who: &Self::AccountId,
2070 value: Self::Balance,
2071 payee: &Self::AccountId,
2072 ) -> DispatchResult {
2073 if StakingLedger::<T>::is_bonded(StakingAccount::Stash(keyless_who.clone())) {
2074 return Err(Error::<T>::AlreadyBonded.into());
2075 }
2076
2077 ensure!(keyless_who != payee, Error::<T>::RewardDestinationRestricted);
2079
2080 VirtualStakers::<T>::insert(keyless_who, ());
2082
2083 Self::deposit_event(Event::<T>::Bonded { stash: keyless_who.clone(), amount: value });
2084 let ledger = StakingLedger::<T>::new(keyless_who.clone(), value);
2085
2086 ledger.bond(RewardDestination::Account(payee.clone()))?;
2087
2088 Ok(())
2089 }
2090
2091 #[cfg(feature = "runtime-benchmarks")]
2093 fn migrate_to_direct_staker(who: &Self::AccountId) {
2094 assert!(VirtualStakers::<T>::contains_key(who));
2095 let ledger = StakingLedger::<T>::get(Stash(who.clone())).unwrap();
2096 let _ = asset::update_stake::<T>(who, ledger.total)
2097 .expect("funds must be transferred to stash");
2098 VirtualStakers::<T>::remove(who);
2099 }
2100}
2101
2102#[cfg(any(test, feature = "try-runtime"))]
2103impl<T: Config> Pallet<T> {
2104 pub(crate) fn do_try_state(_now: BlockNumberFor<T>) -> Result<(), TryRuntimeError> {
2105 if ActiveEra::<T>::get().is_none() && CurrentEra::<T>::get().is_none() {
2108 return Ok(());
2109 }
2110
2111 session_rotation::Rotator::<T>::do_try_state()?;
2112 session_rotation::Eras::<T>::do_try_state()?;
2113
2114 use frame_support::traits::fungible::Inspect;
2115 if T::CurrencyToVote::will_downscale(T::Currency::total_issuance()).map_or(false, |x| x) {
2116 log!(warn, "total issuance will cause T::CurrencyToVote to downscale -- report to maintainers.")
2117 }
2118
2119 Self::check_ledgers()?;
2120 Self::check_bonded_consistency()?;
2121 Self::check_payees()?;
2122 Self::check_paged_exposures()?;
2123 Self::check_count()?;
2124 Self::check_slash_health()?;
2125 Self::check_reward_mode_consistency()?;
2126
2127 Ok(())
2128 }
2129
2130 fn check_reward_mode_consistency() -> Result<(), TryRuntimeError> {
2135 if DisableMintingGuard::<T>::get().is_some() {
2137 ensure!(
2138 T::DisableMinting::get(),
2139 "DisableMintingGuard is set but DisableMinting is false. \
2140 Switching from non-minting back to legacy mode is not supported."
2141 );
2142 }
2143
2144 if T::DisableMinting::get() {
2145 if let Some(guard_era) = DisableMintingGuard::<T>::get() {
2146 let active_era = crate::session_rotation::Rotator::<T>::active_era();
2147 let history_depth = T::HistoryDepth::get();
2148 let oldest = active_era.saturating_sub(history_depth);
2149 for era in oldest..active_era {
2150 if era >= guard_era {
2151 ensure!(
2152 crate::reward::EraRewardManager::<T>::has_staker_rewards_pot(era),
2153 "Era pot missing for guarded era. Rewards cannot be paid."
2154 );
2155 }
2156 }
2157 } else {
2158 log!(
2162 warn,
2163 "DisableMinting=true but DisableMintingGuard is not set. \
2164 Expected only before the first era boundary after migration."
2165 );
2166 }
2167 }
2168 Ok(())
2169 }
2170
2171 fn check_bonded_consistency() -> Result<(), TryRuntimeError> {
2181 use alloc::collections::btree_set::BTreeSet;
2182
2183 let mut count_controller_double = 0;
2184 let mut count_double = 0;
2185 let mut count_none = 0;
2186 let mut controllers = BTreeSet::new();
2189
2190 for (stash, controller) in <Bonded<T>>::iter() {
2191 if !controllers.insert(controller.clone()) {
2192 count_controller_double += 1;
2193 }
2194
2195 match (<Ledger<T>>::get(&stash), <Ledger<T>>::get(&controller)) {
2196 (Some(_), Some(_)) =>
2197 {
2201 if stash != controller {
2202 count_double += 1;
2203 }
2204 },
2205 (None, None) => {
2206 count_none += 1;
2207 },
2208 _ => {},
2209 };
2210 }
2211
2212 if count_controller_double != 0 {
2213 log!(
2214 warn,
2215 "a controller is associated with more than one ledger ({} occurrences)",
2216 count_controller_double
2217 );
2218 };
2219
2220 if count_double != 0 {
2221 log!(warn, "single tuple of (stash, controller) pair bonds more than one ledger ({} occurrences)", count_double);
2222 }
2223
2224 if count_none != 0 {
2225 log!(warn, "inconsistent bonded state: (stash, controller) pair missing associated ledger ({} occurrences)", count_none);
2226 }
2227
2228 Ok(())
2229 }
2230
2231 fn check_payees() -> Result<(), TryRuntimeError> {
2236 for (stash, _) in Bonded::<T>::iter() {
2237 ensure!(Payee::<T>::get(&stash).is_some(), "bonded ledger does not have payee set");
2238 }
2239
2240 ensure!(
2241 (Ledger::<T>::iter().count() == Payee::<T>::iter().count()) &&
2242 (Ledger::<T>::iter().count() == Bonded::<T>::iter().count()),
2243 "number of entries in payee storage items does not match the number of bonded ledgers",
2244 );
2245
2246 Ok(())
2247 }
2248
2249 fn check_count() -> Result<(), TryRuntimeError> {
2253 crate::log!(
2257 debug,
2258 "VoterList count: {}, Nominators count: {}, Validators count: {}",
2259 <T as Config>::VoterList::count(),
2260 Nominators::<T>::count(),
2261 Validators::<T>::count()
2262 );
2263
2264 ensure!(
2265 <T as Config>::TargetList::count() == Validators::<T>::count(),
2266 "wrong external count"
2267 );
2268 let max_validators_bound = crate::MaxWinnersOf::<T>::get();
2269 let max_winners_per_page_bound = crate::MaxWinnersPerPageOf::<T::ElectionProvider>::get();
2270 ensure!(
2271 max_validators_bound >= max_winners_per_page_bound,
2272 "max validators should be higher than per page bounds"
2273 );
2274 ensure!(ValidatorCount::<T>::get() <= max_validators_bound, Error::<T>::TooManyValidators);
2275 Ok(())
2276 }
2277
2278 fn check_ledgers() -> Result<(), TryRuntimeError> {
2286 let mut chilled_undermin: Vec<T::AccountId> = Vec::new();
2287 let mut chilled_total: u32 = 0;
2288 let mut nominator_undermin: Vec<T::AccountId> = Vec::new();
2289 let mut nominator_total: u32 = 0;
2290 let mut validator_undermin: Vec<T::AccountId> = Vec::new();
2291 let mut validator_total: u32 = 0;
2292 let mut total_ledgers: u32 = 0;
2293
2294 Bonded::<T>::iter()
2295 .map(|(stash, ctrl)| {
2296 total_ledgers += 1;
2297 if VirtualStakers::<T>::contains_key(stash.clone()) {
2299 ensure!(
2300 asset::staked::<T>(&stash) == Zero::zero(),
2301 "virtual stakers should not have any staked balance"
2302 );
2303 ensure!(
2304 <Bonded<T>>::get(stash.clone()).unwrap() == stash.clone(),
2305 "stash and controller should be same"
2306 );
2307 ensure!(
2308 Ledger::<T>::get(stash.clone()).unwrap().stash == stash,
2309 "ledger corrupted for virtual staker"
2310 );
2311 ensure!(
2312 frame_system::Pallet::<T>::account_nonce(&stash).is_zero(),
2313 "virtual stakers are keyless and should not have any nonce"
2314 );
2315 let reward_destination = <Payee<T>>::get(stash.clone()).unwrap();
2316 if let RewardDestination::Account(payee) = reward_destination {
2317 ensure!(
2318 payee != stash.clone(),
2319 "reward destination should not be same as stash for virtual staker"
2320 );
2321 } else {
2322 return Err(DispatchError::Other(
2323 "reward destination must be of account variant for virtual staker",
2324 ));
2325 }
2326 } else {
2327 let integrity = Self::inspect_bond_state(&stash);
2328 if integrity != Ok(LedgerIntegrityState::Ok) {
2329 log!(
2331 error,
2332 "defensive: bonded stash {:?} has inconsistent ledger state: {:?}",
2333 stash,
2334 integrity
2335 );
2336 }
2337 }
2338
2339 Self::ensure_ledger_consistent(&ctrl)?;
2340 Self::collect_min_bond_violations(
2341 &ctrl,
2342 &mut chilled_undermin,
2343 &mut chilled_total,
2344 &mut nominator_undermin,
2345 &mut nominator_total,
2346 &mut validator_undermin,
2347 &mut validator_total,
2348 )?;
2349 Ok(())
2350 })
2351 .collect::<Result<Vec<_>, _>>()?;
2352
2353 if chilled_total > 0 {
2354 log!(
2355 warn,
2356 "{} chilled stashes (out of {} total ledgers) have less stake than minimum role bond ({:?}). Examples: {:?}",
2357 chilled_total,
2358 total_ledgers,
2359 Self::min_chilled_bond(),
2360 chilled_undermin,
2361 );
2362 }
2363 if nominator_total > 0 {
2364 log!(
2365 warn,
2366 "{} nominators (out of {} total ledgers) have less stake than minimum role bond ({:?}). Examples: {:?}",
2367 nominator_total,
2368 total_ledgers,
2369 Self::min_nominator_bond(),
2370 nominator_undermin,
2371 );
2372 }
2373 if validator_total > 0 {
2374 log!(
2375 warn,
2376 "{} validators (out of {} total ledgers) have less stake than minimum role bond ({:?}). Examples: {:?}",
2377 validator_total,
2378 total_ledgers,
2379 Self::min_validator_bond(),
2380 validator_undermin,
2381 );
2382 }
2383
2384 Ok(())
2385 }
2386
2387 fn check_paged_exposures() -> Result<(), TryRuntimeError> {
2398 let Some(era) = ActiveEra::<T>::get().map(|a| a.index) else { return Ok(()) };
2399 let overview_and_pages = ErasStakersOverview::<T>::iter_prefix(era)
2400 .map(|(validator, metadata)| {
2401 let last_validator_era = LastValidatorEra::<T>::get(&validator).unwrap_or_default();
2402 if last_validator_era != era && last_validator_era != era + 1 {
2404 log!(
2405 error,
2406 "Validator {:?} has incorrect LastValidatorEra (expected {:?} or {:?}, got {:?})",
2407 validator,
2408 era,
2409 era + 1,
2410 last_validator_era
2411 );
2412 }
2413
2414 let pages = ErasStakersPaged::<T>::iter_prefix((era, validator))
2415 .map(|(_idx, page)| page)
2416 .collect::<Vec<_>>();
2417 (metadata, pages)
2418 })
2419 .collect::<Vec<_>>();
2420
2421 ensure!(
2422 overview_and_pages.iter().flat_map(|(_m, pages)| pages).all(|page| {
2423 let expected = page
2424 .others
2425 .iter()
2426 .map(|e| e.value)
2427 .fold(BalanceOf::<T>::zero(), |acc, x| acc + x);
2428 page.page_total == expected
2429 }),
2430 "found wrong page_total"
2431 );
2432
2433 ensure!(
2434 overview_and_pages.iter().all(|(metadata, pages)| {
2435 let page_count_good = metadata.page_count == pages.len() as u32;
2436 let nominator_count_good = metadata.nominator_count ==
2437 pages.iter().map(|p| p.others.len() as u32).fold(0u32, |acc, x| acc + x);
2438 let total_good = metadata.total ==
2439 metadata.own +
2440 pages
2441 .iter()
2442 .fold(BalanceOf::<T>::zero(), |acc, page| acc + page.page_total);
2443
2444 page_count_good && nominator_count_good && total_good
2445 }),
2446 "found bad metadata"
2447 );
2448
2449 ensure!(
2450 overview_and_pages
2451 .iter()
2452 .map(|(metadata, _pages)| metadata.total)
2453 .fold(BalanceOf::<T>::zero(), |acc, x| acc + x) ==
2454 ErasTotalStake::<T>::get(era),
2455 "found bad eras total stake"
2456 );
2457
2458 Ok(())
2459 }
2460
2461 fn check_slash_health() -> Result<(), TryRuntimeError> {
2463 let offence_queue_eras = OffenceQueueEras::<T>::get().unwrap_or_default().into_inner();
2465 let mut sorted_offence_queue_eras = offence_queue_eras.clone();
2466 sorted_offence_queue_eras.sort();
2467 ensure!(
2468 sorted_offence_queue_eras == offence_queue_eras,
2469 "Offence queue eras are not sorted"
2470 );
2471 drop(sorted_offence_queue_eras);
2472
2473 let active_era = Rotator::<T>::active_era();
2475 let oldest_unprocessed_offence_era =
2476 offence_queue_eras.first().cloned().unwrap_or(active_era);
2477
2478 let oldest_unprocessed_offence_age =
2482 active_era.saturating_sub(oldest_unprocessed_offence_era);
2483
2484 if oldest_unprocessed_offence_age > 2.min(T::BondingDuration::get()) {
2486 log!(
2487 warn,
2488 "Offence queue has unprocessed offences from older than 2 eras: oldest offence era in queue {:?} (active era: {:?})",
2489 oldest_unprocessed_offence_era,
2490 active_era
2491 );
2492 }
2493
2494 ensure!(
2496 oldest_unprocessed_offence_age < T::BondingDuration::get() - 1,
2497 "offences from era less than 3 eras old from active era not processed yet"
2498 );
2499
2500 for e in offence_queue_eras {
2502 let count = OffenceQueue::<T>::iter_prefix(e).count();
2503 ensure!(count > 0, "Offence queue is empty for era listed in offence queue eras");
2504 log!(info, "Offence queue for era {:?} has {:?} offences queued", e, count);
2505 }
2506
2507 for era in (active_era.saturating_sub(T::BondingDuration::get()))..(active_era) {
2511 Self::ensure_era_slashes_applied(era)?;
2515 }
2516
2517 for (era, _) in CancelledSlashes::<T>::iter() {
2519 ensure!(era >= active_era, "Found cancelled slashes for era before active era");
2520 }
2521
2522 Ok(())
2523 }
2524
2525 fn collect_min_bond_violations(
2528 ctrl: &T::AccountId,
2529 chilled_undermin: &mut Vec<T::AccountId>,
2530 chilled_total: &mut u32,
2531 nominator_undermin: &mut Vec<T::AccountId>,
2532 nominator_total: &mut u32,
2533 validator_undermin: &mut Vec<T::AccountId>,
2534 validator_total: &mut u32,
2535 ) -> Result<(), TryRuntimeError> {
2536 const MAX_EXAMPLES: usize = 10;
2537 let ledger = Self::ledger(StakingAccount::Controller(ctrl.clone()))?;
2538 let stash = ledger.stash;
2539
2540 let is_nominator = Nominators::<T>::contains_key(&stash);
2541 let is_validator = Validators::<T>::contains_key(&stash);
2542
2543 match (is_nominator, is_validator) {
2544 (false, false) => {
2545 if ledger.active < Self::min_chilled_bond() && !ledger.active.is_zero() {
2546 *chilled_total += 1;
2548 if chilled_undermin.len() < MAX_EXAMPLES {
2549 chilled_undermin.push(stash.clone());
2550 }
2551 log!(
2552 trace,
2553 "Chilled stash {:?} has less stake ({:?}) than minimum role bond ({:?})",
2554 stash,
2555 ledger.active,
2556 Self::min_chilled_bond()
2557 );
2558 }
2559 },
2561 (true, false) => {
2562 if ledger.active < Self::min_nominator_bond() {
2564 *nominator_total += 1;
2565 if nominator_undermin.len() < MAX_EXAMPLES {
2566 nominator_undermin.push(stash.clone());
2567 }
2568 log!(
2569 trace,
2570 "Nominator {:?} has less stake ({:?}) than minimum role bond ({:?})",
2571 stash,
2572 ledger.active,
2573 Self::min_nominator_bond()
2574 );
2575 }
2576 },
2577 (false, true) => {
2578 if ledger.active < Self::min_validator_bond() {
2580 *validator_total += 1;
2581 if validator_undermin.len() < MAX_EXAMPLES {
2582 validator_undermin.push(stash.clone());
2583 }
2584 log!(
2585 trace,
2586 "Validator {:?} has less stake ({:?}) than minimum role bond ({:?})",
2587 stash,
2588 ledger.active,
2589 Self::min_validator_bond()
2590 );
2591 }
2592 },
2593 (true, true) => {
2594 ensure!(false, "Stash cannot be both nominator and validator");
2595 },
2596 }
2597 Ok(())
2598 }
2599
2600 fn ensure_ledger_consistent(ctrl: &T::AccountId) -> Result<(), TryRuntimeError> {
2601 let ledger = Self::ledger(StakingAccount::Controller(ctrl.clone()))?;
2603
2604 let real_total: BalanceOf<T> =
2605 ledger.unlocking.iter().fold(ledger.active, |a, c| a + c.value);
2606 ensure!(real_total == ledger.total, "ledger.total corrupt");
2607
2608 Ok(())
2609 }
2610}