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, Exposure, Forcing, LedgerIntegrityState, MaxNominationsOf, Nominations,
28 NominationsQuota, PositiveImbalanceOf, PotAccountProvider, RewardDestination, RewardKind,
29 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) =
443 Self::calculate_validator_incentive_for_page(era, &stash, page_stake_part)
444 {
445 Self::transfer_validator_incentive(era, &stash, incentive);
446 }
447
448 let use_dap_payout =
450 DisableMintingGuard::<T>::get().is_some_and(|guard_era| era >= guard_era);
451
452 let nominator_payout_count: u32 = if use_dap_payout {
453 Self::payout_from_provider(
454 era,
455 &stash,
456 validator_staker_payout_for_page,
457 &exposure,
458 overview_own,
459 reward_split.nominator_payout,
460 )
461 } else {
462 Self::payout_legacy_mint(
463 era,
464 &stash,
465 validator_staker_payout_for_page,
466 &exposure,
467 overview_own,
468 reward_split.nominator_payout,
469 )
470 };
471
472 debug_assert!(nominator_payout_count <= T::MaxExposurePageSize::get());
473
474 Ok(Some(T::WeightInfo::payout_stakers_alive_staked(nominator_payout_count)).into())
475 }
476
477 fn payout_from_provider(
479 era: EraIndex,
480 stash: &T::AccountId,
481 validator_payout: BalanceOf<T>,
482 exposure: &crate::PagedExposure<T::AccountId, BalanceOf<T>>,
483 overview_own: BalanceOf<T>,
484 total_nominator_payout: BalanceOf<T>,
485 ) -> u32 {
486 let mut nominator_payout_count: u32 = 0;
487
488 if let Some((amount, dest)) = Self::make_payout_from_provider(era, stash, validator_payout)
489 {
490 Self::deposit_event(Event::<T>::Rewarded { stash: stash.clone(), dest, amount });
491 }
492
493 let total_nominator_stake = exposure.total().saturating_sub(overview_own);
494 for nominator in exposure.others().iter() {
495 let nominator_exposure_part =
496 Perbill::from_rational(nominator.value, total_nominator_stake);
497 let nominator_reward: BalanceOf<T> =
498 nominator_exposure_part.mul_floor(total_nominator_payout);
499
500 if let Some((amount, dest)) =
501 Self::make_payout_from_provider(era, &nominator.who, nominator_reward)
502 {
503 nominator_payout_count.saturating_inc();
504 Self::deposit_event(Event::<T>::Rewarded {
505 stash: nominator.who.clone(),
506 dest,
507 amount,
508 });
509 }
510 }
511
512 nominator_payout_count
513 }
514
515 fn payout_legacy_mint(
517 era: EraIndex,
518 stash: &T::AccountId,
519 validator_payout: BalanceOf<T>,
520 exposure: &crate::PagedExposure<T::AccountId, BalanceOf<T>>,
521 overview_own: BalanceOf<T>,
522 total_nominator_payout: BalanceOf<T>,
523 ) -> u32 {
524 let mut nominator_payout_count: u32 = 0;
525 let mut total_imbalance = PositiveImbalanceOf::<T>::zero();
526
527 if let Some((imbalance, dest)) = Self::make_payout_legacy(era, stash, validator_payout) {
528 Self::deposit_event(Event::<T>::Rewarded {
529 stash: stash.clone(),
530 dest,
531 amount: imbalance.peek(),
532 });
533 total_imbalance.subsume(imbalance);
534 }
535
536 let total_nominator_stake = exposure.total().saturating_sub(overview_own);
537 for nominator in exposure.others().iter() {
538 let nominator_exposure_part =
539 Perbill::from_rational(nominator.value, total_nominator_stake);
540 let nominator_reward: BalanceOf<T> =
541 nominator_exposure_part.mul_floor(total_nominator_payout);
542
543 if let Some((imbalance, dest)) =
544 Self::make_payout_legacy(era, &nominator.who, nominator_reward)
545 {
546 nominator_payout_count.saturating_inc();
547 Self::deposit_event(Event::<T>::Rewarded {
548 stash: nominator.who.clone(),
549 dest,
550 amount: imbalance.peek(),
551 });
552 total_imbalance.subsume(imbalance);
553 }
554 }
555
556 T::Reward::on_unbalanced(total_imbalance);
557 nominator_payout_count
558 }
559
560 fn payout_account_for_dest(
562 stash: &T::AccountId,
563 dest: &RewardDestination<T::AccountId>,
564 ) -> Option<T::AccountId> {
565 match dest {
566 RewardDestination::Stash | RewardDestination::Staked => Some(stash.clone()),
567 RewardDestination::Account(ref dest_account) => Some(dest_account.clone()),
568 RewardDestination::None => None,
569 #[allow(deprecated)]
570 RewardDestination::Controller => Self::bonded(stash),
571 }
572 }
573
574 fn make_payout_from_provider(
576 era: EraIndex,
577 stash: &T::AccountId,
578 amount: BalanceOf<T>,
579 ) -> Option<(BalanceOf<T>, RewardDestination<T::AccountId>)> {
580 if amount.is_zero() {
581 return None;
582 }
583
584 let dest = match Self::payee(Stash(stash.clone())) {
585 Some(d) => d,
586 None => {
587 Self::deposit_event(Event::<T>::Unexpected(UnexpectedKind::MissingPayee {
588 era,
589 stash: stash.clone(),
590 }));
591 return None;
592 },
593 };
594
595 let payout_account = Self::payout_account_for_dest(stash, &dest)?;
596
597 let staker_rewards_pot =
598 T::RewardPots::pot_account(RewardPot::Era(era, RewardKind::StakerRewards));
599 if let Err(e) = T::Currency::transfer(
600 &staker_rewards_pot,
601 &payout_account,
602 amount,
603 Preservation::Expendable,
604 ) {
605 log!(
606 error,
607 "Failed to transfer reward from pot for era {:?}, stash {:?}: {:?}",
608 era,
609 stash,
610 e
611 );
612 return None;
613 }
614
615 if matches!(dest, RewardDestination::Staked) {
617 if let Ok(mut ledger) = Self::ledger(Stash(stash.clone())) {
618 ledger.active += amount;
619 ledger.total += amount;
620 let _ = ledger
621 .update()
622 .defensive_proof("ledger fetched from storage, so it exists; qed.");
623 }
624 }
625
626 Some((amount, dest))
627 }
628
629 fn make_payout_legacy(
631 era: EraIndex,
632 stash: &T::AccountId,
633 amount: BalanceOf<T>,
634 ) -> Option<(PositiveImbalanceOf<T>, RewardDestination<T::AccountId>)> {
635 if amount.is_zero() {
636 return None;
637 }
638 let dest = match Self::payee(StakingAccount::Stash(stash.clone())) {
639 Some(d) => d,
640 None => {
641 Self::deposit_event(Event::<T>::Unexpected(UnexpectedKind::MissingPayee {
642 era,
643 stash: stash.clone(),
644 }));
645 return None;
646 },
647 };
648
649 let maybe_imbalance = match dest {
650 RewardDestination::Stash => asset::mint_into_existing::<T>(stash, amount),
651 RewardDestination::Staked => Self::ledger(Stash(stash.clone()))
652 .and_then(|mut ledger| {
653 ledger.active += amount;
654 ledger.total += amount;
655 let r = asset::mint_into_existing::<T>(stash, amount);
656 let _ = ledger
657 .update()
658 .defensive_proof("ledger fetched from storage, so it exists; qed.");
659 Ok(r)
660 })
661 .unwrap_or_default(),
662 RewardDestination::Account(ref dest_account) => {
663 Some(asset::mint_creating::<T>(dest_account, amount))
664 },
665 RewardDestination::None => None,
666 #[allow(deprecated)]
667 RewardDestination::Controller => Self::bonded(stash).map(|controller| {
668 defensive!("Paying out controller as reward destination which is deprecated.");
669 asset::mint_creating::<T>(&controller, amount)
670 }),
671 };
672 maybe_imbalance.map(|imbalance| (imbalance, dest))
673 }
674
675 fn calculate_validator_incentive_for_page(
682 era: EraIndex,
683 stash: &T::AccountId,
684 page_stake_part: Perbill,
685 ) -> Option<BalanceOf<T>> {
686 let era_incentive_budget = Eras::<T>::get_validator_incentive_budget(era);
687 if era_incentive_budget.is_zero() {
688 return None;
689 }
690
691 let (validator_weight, total_weight) = match (
692 ErasValidatorIncentiveWeight::<T>::get(era, stash),
693 ErasSumValidatorIncentiveWeight::<T>::get(era),
694 ) {
695 (Some(w), t) => (w, t),
696 _ => return None,
697 };
698
699 if total_weight.is_zero() {
700 log!(
701 warn,
702 "Total validator incentive weight is zero but budget exists for era {}",
703 era
704 );
705 Self::deposit_event(Event::<T>::Unexpected(
706 UnexpectedKind::ValidatorIncentiveWeightMismatch { era },
707 ));
708 return None;
709 }
710
711 if validator_weight.is_zero() {
712 return None;
713 }
714
715 let validator_weight_part = Perbill::from_rational(validator_weight, total_weight);
716 let validator_total_incentive = validator_weight_part.mul_floor(era_incentive_budget);
717 let validator_incentive_for_page = page_stake_part.mul_floor(validator_total_incentive);
718
719 if validator_incentive_for_page.is_zero() {
720 return None;
721 }
722
723 Some(validator_incentive_for_page)
724 }
725
726 fn transfer_validator_incentive(era: EraIndex, stash: &T::AccountId, amount: BalanceOf<T>) {
730 let Some(dest) = Self::payee(Stash(stash.clone())) else {
731 Self::deposit_event(Event::<T>::Unexpected(UnexpectedKind::MissingPayee {
732 era,
733 stash: stash.clone(),
734 }));
735 return;
736 };
737 let Some(payout_account) = Self::payout_account_for_dest(stash, &dest) else {
738 return;
740 };
741
742 let incentive_pot = T::RewardPots::pot_account(crate::RewardPot::Era(
743 era,
744 crate::RewardKind::ValidatorSelfStake,
745 ));
746
747 match T::Currency::transfer(
748 &incentive_pot,
749 &payout_account,
750 amount,
751 Preservation::Expendable,
752 ) {
753 Ok(_) => {
754 Self::deposit_event(Event::<T>::ValidatorIncentivePaid {
755 era,
756 validator_stash: stash.clone(),
757 dest,
758 amount,
759 });
760 },
761 Err(e) => {
762 log!(warn, "Failed to transfer liquid incentive: {:?}", e);
763 Self::deposit_event(Event::<T>::Unexpected(
764 UnexpectedKind::ValidatorIncentiveTransferFailed { era },
765 ));
766 defensive!("Validator incentive liquid transfer failed");
767 },
768 }
769 }
770
771 pub(crate) fn chill_stash(stash: &T::AccountId) {
773 let chilled_as_validator = Self::do_remove_validator(stash);
774 let chilled_as_nominator = Self::do_remove_nominator(stash);
775 if chilled_as_validator || chilled_as_nominator {
776 Self::deposit_event(Event::<T>::Chilled { stash: stash.clone() });
777 }
778 }
779
780 pub(crate) fn kill_stash(stash: &T::AccountId) -> DispatchResult {
789 StakingLedger::<T>::kill(&stash)?;
792
793 Self::do_remove_validator(&stash);
794 Self::do_remove_nominator(&stash);
795
796 LastValidatorEra::<T>::remove(&stash);
798
799 Ok(())
800 }
801
802 #[cfg(test)]
803 pub(crate) fn reward_by_ids(validators_points: impl IntoIterator<Item = (T::AccountId, u32)>) {
804 Eras::<T>::reward_active_era(validators_points)
805 }
806
807 pub(crate) fn set_force_era(mode: Forcing) {
809 log!(info, "Setting force era mode {:?}.", mode);
810 ForceEra::<T>::put(mode);
811 Self::deposit_event(Event::<T>::ForceEra { mode });
812 }
813
814 #[cfg(feature = "runtime-benchmarks")]
815 pub fn add_era_stakers(
816 current_era: EraIndex,
817 stash: T::AccountId,
818 exposure: Exposure<T::AccountId, BalanceOf<T>>,
819 ) {
820 Eras::<T>::upsert_exposure(current_era, &stash, exposure);
821 }
822
823 #[cfg(feature = "runtime-benchmarks")]
824 pub fn set_slash_reward_fraction(fraction: Perbill) {
825 SlashRewardFraction::<T>::put(fraction);
826 }
827
828 pub(crate) fn get_npos_voters(
838 bounds: DataProviderBounds,
839 status: &SnapshotStatus<T::AccountId>,
840 ) -> Vec<VoterOf<Self>> {
841 let mut voters_size_tracker: StaticTracker<Self> = StaticTracker::default();
842
843 let page_len_prediction = {
844 let all_voter_count = T::VoterList::count();
845 bounds.count.unwrap_or(all_voter_count.into()).min(all_voter_count.into()).0
846 };
847
848 let mut all_voters = Vec::<_>::with_capacity(page_len_prediction as usize);
849
850 let weight_of = Self::weight_of_fn();
852
853 let mut voters_seen = 0u32;
854 let mut validators_taken = 0u32;
855 let mut nominators_taken = 0u32;
856 let mut min_active_stake = u64::MAX;
857
858 let mut sorted_voters = match status {
859 SnapshotStatus::Waiting => T::VoterList::iter(),
861 SnapshotStatus::Ongoing(account_id) => T::VoterList::iter_from(&account_id)
863 .defensive_unwrap_or(Box::new(vec![].into_iter())),
864 SnapshotStatus::Consumed => Box::new(vec![].into_iter()),
866 };
867
868 while all_voters.len() < page_len_prediction as usize &&
869 voters_seen < (NPOS_MAX_ITERATIONS_COEFFICIENT * page_len_prediction as u32)
870 {
871 let voter = match sorted_voters.next() {
872 Some(voter) => {
873 voters_seen.saturating_inc();
874 voter
875 },
876 None => break,
877 };
878
879 let voter_weight = weight_of(&voter);
880 if voter_weight.is_zero() {
882 log!(debug, "voter's active balance is 0. skip this voter.");
883 continue;
884 }
885
886 if let Some(Nominations { targets, .. }) = <Nominators<T>>::get(&voter) {
887 if !targets.is_empty() {
888 let voter = (voter, voter_weight, targets);
893 if voters_size_tracker.try_register_voter(&voter, &bounds).is_err() {
894 Self::deposit_event(Event::<T>::SnapshotVotersSizeExceeded {
896 size: voters_size_tracker.size as u32,
897 });
898 break;
899 }
900
901 all_voters.push(voter);
902 nominators_taken.saturating_inc();
903 } else {
904 defensive!("non-nominator fetched from voter list: {:?}", voter);
905 }
907 min_active_stake =
908 if voter_weight < min_active_stake { voter_weight } else { min_active_stake };
909 } else if Validators::<T>::contains_key(&voter) {
910 let self_vote = (
912 voter.clone(),
913 voter_weight,
914 vec![voter.clone()]
915 .try_into()
916 .expect("`MaxVotesPerVoter` must be greater than or equal to 1"),
917 );
918
919 if voters_size_tracker.try_register_voter(&self_vote, &bounds).is_err() {
920 Self::deposit_event(Event::<T>::SnapshotVotersSizeExceeded {
922 size: voters_size_tracker.size as u32,
923 });
924 break;
925 }
926 all_voters.push(self_vote);
927 validators_taken.saturating_inc();
928 } else {
929 defensive!(
935 "invalid item in `VoterList`: {:?}, this nominator probably has too many nominations now",
936 voter,
937 );
938 }
939 }
940
941 debug_assert!(all_voters.capacity() == page_len_prediction as usize);
943
944 let min_active_stake: T::CurrencyBalance =
945 if all_voters.is_empty() { Zero::zero() } else { min_active_stake.into() };
946
947 MinimumActiveStake::<T>::put(min_active_stake);
948
949 all_voters
950 }
951
952 pub fn get_npos_targets(bounds: DataProviderBounds) -> Vec<T::AccountId> {
958 let mut targets_size_tracker: StaticTracker<Self> = StaticTracker::default();
959
960 let final_predicted_len = {
961 let all_target_count = T::TargetList::count();
962 bounds.count.unwrap_or(all_target_count.into()).min(all_target_count.into()).0
963 };
964
965 let mut all_targets = Vec::<T::AccountId>::with_capacity(final_predicted_len as usize);
966 let mut targets_seen = 0;
967
968 let mut targets_iter = T::TargetList::iter();
969 while all_targets.len() < final_predicted_len as usize &&
970 targets_seen < (NPOS_MAX_ITERATIONS_COEFFICIENT * final_predicted_len as u32)
971 {
972 let target = match targets_iter.next() {
973 Some(target) => {
974 targets_seen.saturating_inc();
975 target
976 },
977 None => break,
978 };
979
980 if targets_size_tracker.try_register_target(target.clone(), &bounds).is_err() {
981 log!(warn, "npos targets size exceeded, stopping iteration.");
983 Self::deposit_event(Event::<T>::SnapshotTargetsSizeExceeded {
984 size: targets_size_tracker.size as u32,
985 });
986 break;
987 }
988
989 if Validators::<T>::contains_key(&target) {
990 all_targets.push(target);
991 }
992 }
993
994 log!(debug, "[bounds {:?}] generated {} npos targets", bounds, all_targets.len());
995
996 all_targets
997 }
998
999 pub fn do_add_nominator(who: &T::AccountId, nominations: Nominations<T>) {
1008 if !Nominators::<T>::contains_key(who) {
1009 let _ = T::VoterList::on_insert(who.clone(), Self::weight_of(who))
1011 .defensive_unwrap_or_default();
1012 }
1013 Nominators::<T>::insert(who, nominations);
1014 }
1015
1016 pub fn do_remove_nominator(who: &T::AccountId) -> bool {
1025 let outcome = if Nominators::<T>::contains_key(who) {
1026 Nominators::<T>::remove(who);
1027 let _ = T::VoterList::on_remove(who);
1028 true
1029 } else {
1030 false
1031 };
1032
1033 outcome
1034 }
1035
1036 pub fn do_add_validator(who: &T::AccountId, prefs: ValidatorPrefs) {
1044 if !Validators::<T>::contains_key(who) {
1045 let _ = T::VoterList::on_insert(who.clone(), Self::weight_of(who));
1047 }
1048 Validators::<T>::insert(who, prefs);
1049 }
1050
1051 pub fn do_remove_validator(who: &T::AccountId) -> bool {
1059 let outcome = if Validators::<T>::contains_key(who) {
1060 Validators::<T>::remove(who);
1061 let _ = T::VoterList::on_remove(who);
1062 true
1063 } else {
1064 false
1065 };
1066
1067 outcome
1068 }
1069
1070 pub(crate) fn register_weight(weight: Weight) {
1074 <frame_system::Pallet<T>>::register_extra_weight_unchecked(
1075 weight,
1076 DispatchClass::Mandatory,
1077 );
1078 }
1079
1080 pub fn eras_stakers(
1087 era: EraIndex,
1088 account: &T::AccountId,
1089 ) -> Exposure<T::AccountId, BalanceOf<T>> {
1090 Eras::<T>::get_full_exposure(era, account)
1091 }
1092
1093 pub(super) fn do_migrate_currency(stash: &T::AccountId) -> DispatchResult {
1094 if Self::is_virtual_staker(stash) {
1095 return Self::do_migrate_virtual_staker(stash);
1096 }
1097
1098 let ledger = Self::ledger(Stash(stash.clone()))?;
1099 let staked: BalanceOf<T> = T::OldCurrency::balance_locked(STAKING_ID, stash).into();
1100 ensure!(!staked.is_zero(), Error::<T>::AlreadyMigrated);
1101 ensure!(ledger.total == staked, Error::<T>::BadState);
1102
1103 T::OldCurrency::remove_lock(STAKING_ID, &stash);
1105
1106 let max_hold = asset::free_to_stake::<T>(&stash);
1108 let force_withdraw = if max_hold >= staked {
1109 asset::update_stake::<T>(&stash, staked)?;
1111 Zero::zero()
1112 } else {
1113 let force_withdraw = staked.saturating_sub(max_hold);
1116
1117 StakingLedger {
1120 total: max_hold,
1121 active: ledger.active.saturating_sub(force_withdraw),
1122 ..ledger
1124 }
1125 .update()?;
1126 force_withdraw
1127 };
1128
1129 frame_system::Pallet::<T>::dec_consumers(&stash);
1131
1132 Self::deposit_event(Event::<T>::CurrencyMigrated { stash: stash.clone(), force_withdraw });
1133 Ok(())
1134 }
1135
1136 fn do_migrate_virtual_staker(stash: &T::AccountId) -> DispatchResult {
1137 frame_system::Pallet::<T>::dec_consumers(&stash);
1140
1141 let actual_providers = frame_system::Pallet::<T>::providers(stash);
1146
1147 let expected_providers =
1148 if asset::free_to_stake::<T>(&stash) >= asset::existential_deposit::<T>() {
1151 2
1152 } else {
1153 1
1154 };
1155
1156 ensure!(actual_providers <= expected_providers, Error::<T>::BadState);
1158
1159 ensure!(actual_providers == expected_providers, Error::<T>::AlreadyMigrated);
1161
1162 let _ = frame_system::Pallet::<T>::dec_providers(&stash)?;
1164
1165 return Ok(());
1166 }
1167}
1168
1169impl<T: Config> Pallet<T> {
1170 pub fn api_nominations_quota(balance: BalanceOf<T>) -> u32 {
1174 T::NominationsQuota::get_quota(balance)
1175 }
1176
1177 pub fn api_eras_stakers(
1178 era: EraIndex,
1179 account: T::AccountId,
1180 ) -> Exposure<T::AccountId, BalanceOf<T>> {
1181 Self::eras_stakers(era, &account)
1182 }
1183
1184 pub fn api_eras_stakers_page_count(era: EraIndex, account: T::AccountId) -> Page {
1185 Eras::<T>::exposure_page_count(era, &account)
1186 }
1187
1188 pub fn api_pending_rewards(era: EraIndex, account: T::AccountId) -> bool {
1189 Eras::<T>::pending_rewards(era, &account)
1190 }
1191}
1192
1193impl<T: Config> ElectionDataProvider for Pallet<T> {
1194 type AccountId = T::AccountId;
1195 type BlockNumber = BlockNumberFor<T>;
1196 type MaxVotesPerVoter = MaxNominationsOf<T>;
1197
1198 fn desired_targets() -> data_provider::Result<u32> {
1199 Self::register_weight(T::DbWeight::get().reads(1));
1200 Ok(ValidatorCount::<T>::get())
1201 }
1202
1203 fn electing_voters(
1204 bounds: DataProviderBounds,
1205 page: PageIndex,
1206 ) -> data_provider::Result<Vec<VoterOf<Self>>> {
1207 let mut status = VoterSnapshotStatus::<T>::get();
1208 let voters = Self::get_npos_voters(bounds, &status);
1209
1210 match (page, &status) {
1212 (0, _) => status = SnapshotStatus::Waiting,
1214
1215 (_, SnapshotStatus::Waiting) | (_, SnapshotStatus::Ongoing(_)) => {
1216 let maybe_last = voters.last().map(|(x, _, _)| x).cloned();
1217
1218 if let Some(ref last) = maybe_last {
1219 let has_next =
1220 T::VoterList::iter_from(last).ok().and_then(|mut i| i.next()).is_some();
1221 if has_next {
1222 status = SnapshotStatus::Ongoing(last.clone());
1223 } else {
1224 status = SnapshotStatus::Consumed;
1225 }
1226 }
1227 },
1228 (_, SnapshotStatus::Consumed) => (),
1230 }
1231
1232 log!(
1233 debug,
1234 "[page {}, (next) status {:?}, bounds {:?}] generated {} npos voters [first: {:?}, last: {:?}]",
1235 page,
1236 status,
1237 bounds,
1238 voters.len(),
1239 voters.first().map(|(x, y, _)| (x, y)),
1240 voters.last().map(|(x, y, _)| (x, y)),
1241 );
1242
1243 match status {
1244 SnapshotStatus::Ongoing(_) => T::VoterList::lock(),
1245 _ => T::VoterList::unlock(),
1246 }
1247
1248 VoterSnapshotStatus::<T>::put(status);
1249 debug_assert!(!bounds.slice_exhausted(&voters));
1250
1251 Ok(voters)
1252 }
1253
1254 fn electing_voters_stateless(
1255 bounds: DataProviderBounds,
1256 ) -> data_provider::Result<Vec<VoterOf<Self>>> {
1257 let voters = Self::get_npos_voters(bounds, &SnapshotStatus::Waiting);
1258 log!(debug, "[stateless, bounds {:?}] generated {} npos voters", bounds, voters.len(),);
1259 Ok(voters)
1260 }
1261
1262 fn electable_targets(
1263 bounds: DataProviderBounds,
1264 page: PageIndex,
1265 ) -> data_provider::Result<Vec<T::AccountId>> {
1266 if page > 0 {
1267 log!(warn, "multi-page target snapshot not supported, returning page 0.");
1268 }
1269
1270 let targets = Self::get_npos_targets(bounds);
1271 if bounds.exhausted(None, CountBound(targets.len() as u32).into()) {
1272 return Err("Target snapshot too big");
1273 }
1274
1275 debug_assert!(!bounds.slice_exhausted(&targets));
1276
1277 Ok(targets)
1278 }
1279
1280 fn next_election_prediction(_: BlockNumberFor<T>) -> BlockNumberFor<T> {
1281 debug_assert!(false, "this is deprecated and not used anymore");
1282 sp_runtime::traits::Bounded::max_value()
1283 }
1284
1285 #[cfg(feature = "runtime-benchmarks")]
1286 fn fetch_page(page: PageIndex) {
1287 session_rotation::EraElectionPlanner::<T>::do_elect_paged(page);
1288 }
1289
1290 #[cfg(feature = "runtime-benchmarks")]
1291 fn add_voter(
1292 voter: T::AccountId,
1293 weight: VoteWeight,
1294 targets: BoundedVec<T::AccountId, Self::MaxVotesPerVoter>,
1295 ) {
1296 let stake = <BalanceOf<T>>::try_from(weight).unwrap_or_else(|_| {
1297 panic!("cannot convert a VoteWeight into BalanceOf, benchmark needs reconfiguring.")
1298 });
1299 <Bonded<T>>::insert(voter.clone(), voter.clone());
1300 <Ledger<T>>::insert(voter.clone(), StakingLedger::<T>::new(voter.clone(), stake));
1301
1302 Self::do_add_nominator(&voter, Nominations { targets, submitted_in: 0, suppressed: false });
1303 }
1304
1305 #[cfg(feature = "runtime-benchmarks")]
1306 fn add_target(target: T::AccountId) {
1307 let stake = (Self::min_validator_bond() + 1u32.into()) * 100u32.into();
1308 <Bonded<T>>::insert(target.clone(), target.clone());
1309 <Ledger<T>>::insert(target.clone(), StakingLedger::<T>::new(target.clone(), stake));
1310 Self::do_add_validator(
1311 &target,
1312 ValidatorPrefs { commission: Perbill::zero(), blocked: false },
1313 );
1314 }
1315
1316 #[cfg(feature = "runtime-benchmarks")]
1317 fn clear() {
1318 #[allow(deprecated)]
1319 <Bonded<T>>::remove_all(None);
1320 #[allow(deprecated)]
1321 <Ledger<T>>::remove_all(None);
1322 #[allow(deprecated)]
1323 <Validators<T>>::remove_all();
1324 #[allow(deprecated)]
1325 <Nominators<T>>::remove_all();
1326
1327 T::VoterList::unsafe_clear();
1328 }
1329
1330 #[cfg(feature = "runtime-benchmarks")]
1331 fn put_snapshot(
1332 voters: Vec<VoterOf<Self>>,
1333 targets: Vec<T::AccountId>,
1334 target_stake: Option<VoteWeight>,
1335 ) {
1336 targets.into_iter().for_each(|v| {
1337 let stake: BalanceOf<T> = target_stake
1338 .and_then(|w| <BalanceOf<T>>::try_from(w).ok())
1339 .unwrap_or_else(|| Self::min_nominator_bond() * 100u32.into());
1340 <Bonded<T>>::insert(v.clone(), v.clone());
1341 <Ledger<T>>::insert(v.clone(), StakingLedger::<T>::new(v.clone(), stake));
1342 Self::do_add_validator(
1343 &v,
1344 ValidatorPrefs { commission: Perbill::zero(), blocked: false },
1345 );
1346 });
1347
1348 voters.into_iter().for_each(|(v, s, t)| {
1349 let stake = <BalanceOf<T>>::try_from(s).unwrap_or_else(|_| {
1350 panic!("cannot convert a VoteWeight into BalanceOf, benchmark needs reconfiguring.")
1351 });
1352 <Bonded<T>>::insert(v.clone(), v.clone());
1353 <Ledger<T>>::insert(v.clone(), StakingLedger::<T>::new(v.clone(), stake));
1354 Self::do_add_nominator(
1355 &v,
1356 Nominations { targets: t, submitted_in: 0, suppressed: false },
1357 );
1358 });
1359 }
1360
1361 #[cfg(feature = "runtime-benchmarks")]
1362 fn set_desired_targets(count: u32) {
1363 ValidatorCount::<T>::put(count);
1364 }
1365}
1366
1367impl<T: Config> rc_client::AHStakingInterface for Pallet<T> {
1368 type AccountId = T::AccountId;
1369 type MaxValidatorSet = T::MaxValidatorSet;
1370
1371 fn on_relay_session_report(report: rc_client::SessionReport<Self::AccountId>) -> Weight {
1380 log!(debug, "Received session report: {}", report,);
1381
1382 let rc_client::SessionReport {
1383 end_index,
1384 activation_timestamp,
1385 validator_points,
1386 leftover,
1387 } = report;
1388 debug_assert!(!leftover);
1389
1390 Eras::<T>::reward_active_era(validator_points.into_iter());
1392 session_rotation::Rotator::<T>::end_session(end_index, activation_timestamp)
1393 }
1394
1395 fn weigh_on_relay_session_report(
1396 _report: &rc_client::SessionReport<Self::AccountId>,
1397 ) -> Weight {
1398 T::WeightInfo::rc_on_session_report()
1400 }
1401
1402 fn on_new_offences(
1413 slash_session: SessionIndex,
1414 offences: Vec<rc_client::Offence<T::AccountId>>,
1415 ) -> Weight {
1416 log!(debug, "🦹 on_new_offences: {:?}", offences);
1417 let weight = T::WeightInfo::rc_on_offence(offences.len() as u32);
1418
1419 let Some(active_era) = ActiveEra::<T>::get() else {
1421 log!(warn, "🦹 on_new_offences: no active era; ignoring offence");
1422 return T::WeightInfo::rc_on_offence(0);
1423 };
1424
1425 let active_era_start_session = Rotator::<T>::active_era_start_session_index();
1426
1427 let offence_era = if slash_session >= active_era_start_session {
1430 active_era.index
1431 } else {
1432 match BondedEras::<T>::get()
1433 .iter()
1434 .rev()
1436 .find_map(|&(era, sesh)| if sesh <= slash_session { Some(era) } else { None })
1437 {
1438 Some(era) => era,
1439 None => {
1440 log!(warn, "🦹 on_offence: no era found for slash_session; ignoring offence");
1443 return T::WeightInfo::rc_on_offence(0);
1444 },
1445 }
1446 };
1447
1448 let oldest_reportable_offence_era = if T::SlashDeferDuration::get() == 0 {
1449 active_era.index.saturating_sub(T::BondingDuration::get().saturating_sub(2))
1455 } else {
1456 active_era.index.saturating_sub(T::SlashDeferDuration::get().saturating_sub(1))
1459 };
1460
1461 for o in offences {
1462 let slash_fraction = o.slash_fraction;
1463 let validator: <T as frame_system::Config>::AccountId = o.offender.into();
1464
1465 if offence_era < oldest_reportable_offence_era {
1467 log!(warn, "🦹 on_new_offences: offence era {:?} too old; Can only accept offences from era {:?} or newer", offence_era, oldest_reportable_offence_era);
1468 Self::deposit_event(Event::<T>::OffenceTooOld {
1469 validator: validator.clone(),
1470 fraction: slash_fraction,
1471 offence_era,
1472 });
1473 continue;
1475 }
1476 let Some(exposure_overview) = <ErasStakersOverview<T>>::get(&offence_era, &validator)
1477 else {
1478 log!(
1481 warn,
1482 "🦹 on_offence: no exposure found for {:?} in era {}; ignoring offence",
1483 validator,
1484 offence_era
1485 );
1486 continue;
1487 };
1488
1489 Self::deposit_event(Event::<T>::OffenceReported {
1490 validator: validator.clone(),
1491 fraction: slash_fraction,
1492 offence_era,
1493 });
1494
1495 let prior_slash_fraction = ValidatorSlashInEra::<T>::get(offence_era, &validator)
1496 .map_or(Zero::zero(), |(f, _)| f);
1497
1498 if let Some(existing) = OffenceQueue::<T>::get(offence_era, &validator) {
1499 if slash_fraction.deconstruct() > existing.slash_fraction.deconstruct() {
1500 OffenceQueue::<T>::insert(
1501 offence_era,
1502 &validator,
1503 OffenceRecord {
1504 reporter: o.reporters.first().cloned(),
1505 reported_era: active_era.index,
1506 slash_fraction,
1507 ..existing
1508 },
1509 );
1510
1511 ValidatorSlashInEra::<T>::insert(
1513 offence_era,
1514 &validator,
1515 (slash_fraction, exposure_overview.own),
1516 );
1517
1518 log!(
1519 debug,
1520 "🦹 updated slash for {:?}: {:?} (prior: {:?})",
1521 validator,
1522 slash_fraction,
1523 prior_slash_fraction,
1524 );
1525 } else {
1526 log!(
1527 debug,
1528 "🦹 ignored slash for {:?}: {:?} (existing prior is larger: {:?})",
1529 validator,
1530 slash_fraction,
1531 prior_slash_fraction,
1532 );
1533 }
1534 } else if slash_fraction.deconstruct() > prior_slash_fraction.deconstruct() {
1535 ValidatorSlashInEra::<T>::insert(
1536 offence_era,
1537 &validator,
1538 (slash_fraction, exposure_overview.own),
1539 );
1540
1541 OffenceQueue::<T>::insert(
1542 offence_era,
1543 &validator,
1544 OffenceRecord {
1545 reporter: o.reporters.first().cloned(),
1546 reported_era: active_era.index,
1547 exposure_page: exposure_overview.page_count.saturating_sub(1),
1550 slash_fraction,
1551 prior_slash_fraction,
1552 },
1553 );
1554
1555 OffenceQueueEras::<T>::mutate(|q| {
1556 if let Some(eras) = q {
1557 log!(debug, "🦹 inserting offence era {} into existing queue", offence_era);
1558 eras.binary_search(&offence_era).err().map(|idx| {
1559 eras.try_insert(idx, offence_era).defensive_proof(
1560 "Offence era must be present in the existing queue",
1561 )
1562 });
1563 } else {
1564 let mut eras = WeakBoundedVec::default();
1565 log!(debug, "🦹 inserting offence era {} into empty queue", offence_era);
1566 let _ = eras
1567 .try_push(offence_era)
1568 .defensive_proof("Failed to push offence era into empty queue");
1569 *q = Some(eras);
1570 }
1571 });
1572
1573 log!(
1574 debug,
1575 "🦹 queued slash for {:?}: {:?} (prior: {:?})",
1576 validator,
1577 slash_fraction,
1578 prior_slash_fraction,
1579 );
1580 } else {
1581 log!(
1582 debug,
1583 "🦹 ignored slash for {:?}: {:?} (already slashed in era with prior: {:?})",
1584 validator,
1585 slash_fraction,
1586 prior_slash_fraction,
1587 );
1588 }
1589 }
1590
1591 weight
1592 }
1593
1594 fn weigh_on_new_offences(offence_count: u32) -> Weight {
1595 T::WeightInfo::rc_on_offence(offence_count)
1596 }
1597
1598 fn active_era_start_session_index() -> SessionIndex {
1599 Rotator::<T>::active_era_start_session_index()
1600 }
1601
1602 fn is_validator(who: &Self::AccountId) -> bool {
1603 Validators::<T>::contains_key(who)
1604 }
1605}
1606
1607impl<T: Config> ScoreProvider<T::AccountId> for Pallet<T> {
1608 type Score = VoteWeight;
1609
1610 fn score(who: &T::AccountId) -> Option<Self::Score> {
1611 Self::ledger(Stash(who.clone()))
1612 .ok()
1613 .and_then(|l| {
1614 if Nominators::<T>::contains_key(&l.stash) ||
1615 Validators::<T>::contains_key(&l.stash)
1616 {
1617 Some(l.active)
1618 } else {
1619 None
1620 }
1621 })
1622 .map(|a| {
1623 let issuance = asset::total_issuance::<T>();
1624 T::CurrencyToVote::to_vote(a, issuance)
1625 })
1626 }
1627
1628 #[cfg(feature = "runtime-benchmarks")]
1629 fn set_score_of(who: &T::AccountId, weight: Self::Score) {
1630 let active: BalanceOf<T> = weight.try_into().map_err(|_| ()).unwrap();
1633 let mut ledger = match Self::ledger(StakingAccount::Stash(who.clone())) {
1634 Ok(l) => l,
1635 Err(_) => StakingLedger::default_from(who.clone()),
1636 };
1637 ledger.active = active;
1638
1639 <Ledger<T>>::insert(who, ledger);
1640 <Bonded<T>>::insert(who, who);
1641 <Validators<T>>::insert(who, ValidatorPrefs::default());
1644
1645 let imbalance = asset::burn::<T>(asset::total_issuance::<T>());
1649 core::mem::forget(imbalance);
1652 }
1653}
1654
1655pub struct UseValidatorsMap<T>(core::marker::PhantomData<T>);
1659impl<T: Config> SortedListProvider<T::AccountId> for UseValidatorsMap<T> {
1660 type Score = BalanceOf<T>;
1661 type Error = ();
1662
1663 fn iter() -> Box<dyn Iterator<Item = T::AccountId>> {
1665 Box::new(Validators::<T>::iter().map(|(v, _)| v))
1666 }
1667 fn iter_from(
1668 start: &T::AccountId,
1669 ) -> Result<Box<dyn Iterator<Item = T::AccountId>>, Self::Error> {
1670 if Validators::<T>::contains_key(start) {
1671 let start_key = Validators::<T>::hashed_key_for(start);
1672 Ok(Box::new(Validators::<T>::iter_from(start_key).map(|(n, _)| n)))
1673 } else {
1674 Err(())
1675 }
1676 }
1677 fn lock() {}
1678 fn unlock() {}
1679 fn count() -> u32 {
1680 Validators::<T>::count()
1681 }
1682 fn contains(id: &T::AccountId) -> bool {
1683 Validators::<T>::contains_key(id)
1684 }
1685 fn on_insert(_: T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> {
1686 Ok(())
1688 }
1689 fn get_score(id: &T::AccountId) -> Result<Self::Score, Self::Error> {
1690 Ok(Pallet::<T>::weight_of(id).into())
1691 }
1692 fn on_update(_: &T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> {
1693 Ok(())
1695 }
1696 fn on_remove(_: &T::AccountId) -> Result<(), Self::Error> {
1697 Ok(())
1699 }
1700 fn unsafe_regenerate(
1701 _: impl IntoIterator<Item = T::AccountId>,
1702 _: Box<dyn Fn(&T::AccountId) -> Option<Self::Score>>,
1703 ) -> u32 {
1704 0
1706 }
1707 #[cfg(feature = "try-runtime")]
1708 fn try_state() -> Result<(), TryRuntimeError> {
1709 Ok(())
1710 }
1711
1712 fn unsafe_clear() {
1713 #[allow(deprecated)]
1714 Validators::<T>::remove_all();
1715 }
1716
1717 #[cfg(feature = "runtime-benchmarks")]
1718 fn score_update_worst_case(_who: &T::AccountId, _is_increase: bool) -> Self::Score {
1719 unimplemented!()
1720 }
1721}
1722
1723pub struct UseNominatorsAndValidatorsMap<T>(core::marker::PhantomData<T>);
1727impl<T: Config> SortedListProvider<T::AccountId> for UseNominatorsAndValidatorsMap<T> {
1728 type Error = ();
1729 type Score = VoteWeight;
1730
1731 fn iter() -> Box<dyn Iterator<Item = T::AccountId>> {
1732 Box::new(
1733 Validators::<T>::iter()
1734 .map(|(v, _)| v)
1735 .chain(Nominators::<T>::iter().map(|(n, _)| n)),
1736 )
1737 }
1738 fn iter_from(
1739 start: &T::AccountId,
1740 ) -> Result<Box<dyn Iterator<Item = T::AccountId>>, Self::Error> {
1741 if Validators::<T>::contains_key(start) {
1742 let start_key = Validators::<T>::hashed_key_for(start);
1743 Ok(Box::new(
1744 Validators::<T>::iter_from(start_key)
1745 .map(|(n, _)| n)
1746 .chain(Nominators::<T>::iter().map(|(x, _)| x)),
1747 ))
1748 } else if Nominators::<T>::contains_key(start) {
1749 let start_key = Nominators::<T>::hashed_key_for(start);
1750 Ok(Box::new(Nominators::<T>::iter_from(start_key).map(|(n, _)| n)))
1751 } else {
1752 Err(())
1753 }
1754 }
1755 fn lock() {}
1756 fn unlock() {}
1757 fn count() -> u32 {
1758 Nominators::<T>::count().saturating_add(Validators::<T>::count())
1759 }
1760 fn contains(id: &T::AccountId) -> bool {
1761 Nominators::<T>::contains_key(id) || Validators::<T>::contains_key(id)
1762 }
1763 fn on_insert(_: T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> {
1764 Ok(())
1766 }
1767 fn get_score(id: &T::AccountId) -> Result<Self::Score, Self::Error> {
1768 Ok(Pallet::<T>::weight_of(id))
1769 }
1770 fn on_update(_: &T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> {
1771 Ok(())
1773 }
1774 fn on_remove(_: &T::AccountId) -> Result<(), Self::Error> {
1775 Ok(())
1777 }
1778 fn unsafe_regenerate(
1779 _: impl IntoIterator<Item = T::AccountId>,
1780 _: Box<dyn Fn(&T::AccountId) -> Option<Self::Score>>,
1781 ) -> u32 {
1782 0
1784 }
1785
1786 #[cfg(feature = "try-runtime")]
1787 fn try_state() -> Result<(), TryRuntimeError> {
1788 Ok(())
1789 }
1790
1791 fn unsafe_clear() {
1792 #[allow(deprecated)]
1795 Nominators::<T>::remove_all();
1796 #[allow(deprecated)]
1797 Validators::<T>::remove_all();
1798 }
1799
1800 #[cfg(feature = "runtime-benchmarks")]
1801 fn score_update_worst_case(_who: &T::AccountId, _is_increase: bool) -> Self::Score {
1802 unimplemented!()
1803 }
1804}
1805
1806impl<T: Config> StakingInterface for Pallet<T> {
1807 type AccountId = T::AccountId;
1808 type Balance = BalanceOf<T>;
1809 type CurrencyToVote = T::CurrencyToVote;
1810
1811 fn minimum_nominator_bond() -> Self::Balance {
1812 Self::min_nominator_bond()
1813 }
1814
1815 fn minimum_validator_bond() -> Self::Balance {
1816 Self::min_validator_bond()
1817 }
1818
1819 fn stash_by_ctrl(controller: &Self::AccountId) -> Result<Self::AccountId, DispatchError> {
1820 Self::ledger(Controller(controller.clone()))
1821 .map(|l| l.stash)
1822 .map_err(|e| e.into())
1823 }
1824
1825 fn bonding_duration() -> EraIndex {
1830 T::BondingDuration::get()
1831 }
1832
1833 fn nominator_bonding_duration() -> EraIndex {
1847 if AreNominatorsSlashable::<T>::get() {
1848 T::BondingDuration::get()
1849 } else {
1850 T::NominatorFastUnbondDuration::get()
1851 }
1852 }
1853
1854 fn current_era() -> EraIndex {
1855 Rotator::<T>::active_era()
1858 }
1859
1860 fn stake(who: &Self::AccountId) -> Result<Stake<BalanceOf<T>>, DispatchError> {
1861 Self::ledger(Stash(who.clone()))
1862 .map(|l| Stake { total: l.total, active: l.active })
1863 .map_err(|e| e.into())
1864 }
1865
1866 fn bond_extra(who: &Self::AccountId, extra: Self::Balance) -> DispatchResult {
1867 Self::bond_extra(RawOrigin::Signed(who.clone()).into(), extra)
1868 }
1869
1870 fn unbond(who: &Self::AccountId, value: Self::Balance) -> DispatchResult {
1871 let ctrl = Self::bonded(who).ok_or(Error::<T>::NotStash)?;
1872 Self::unbond(RawOrigin::Signed(ctrl).into(), value)
1873 .map_err(|with_post| with_post.error)
1874 .map(|_| ())
1875 }
1876
1877 fn set_payee(stash: &Self::AccountId, reward_acc: &Self::AccountId) -> DispatchResult {
1878 ensure!(
1882 !Self::is_virtual_staker(stash) || stash != reward_acc,
1883 Error::<T>::RewardDestinationRestricted
1884 );
1885
1886 let ledger = Self::ledger(Stash(stash.clone()))?;
1887 let _ = ledger
1888 .set_payee(RewardDestination::Account(reward_acc.clone()))
1889 .defensive_proof("ledger was retrieved from storage, thus its bonded; qed.")?;
1890
1891 Ok(())
1892 }
1893
1894 fn chill(who: &Self::AccountId) -> DispatchResult {
1895 let ctrl = Self::bonded(who).ok_or(Error::<T>::NotStash)?;
1898 Self::chill(RawOrigin::Signed(ctrl).into())
1899 }
1900
1901 fn withdraw_unbonded(
1902 who: Self::AccountId,
1903 _num_slashing_spans: u32,
1904 ) -> Result<bool, DispatchError> {
1905 let ctrl = Self::bonded(&who).ok_or(Error::<T>::NotStash)?;
1906 Self::withdraw_unbonded(RawOrigin::Signed(ctrl.clone()).into(), 0)
1907 .map(|_| !StakingLedger::<T>::is_bonded(StakingAccount::Controller(ctrl)))
1908 .map_err(|with_post| with_post.error)
1909 }
1910
1911 fn bond(
1912 who: &Self::AccountId,
1913 value: Self::Balance,
1914 payee: &Self::AccountId,
1915 ) -> DispatchResult {
1916 Self::bond(
1917 RawOrigin::Signed(who.clone()).into(),
1918 value,
1919 RewardDestination::Account(payee.clone()),
1920 )
1921 }
1922
1923 fn nominate(who: &Self::AccountId, targets: Vec<Self::AccountId>) -> DispatchResult {
1924 let ctrl = Self::bonded(who).ok_or(Error::<T>::NotStash)?;
1925 let targets = targets.into_iter().map(T::Lookup::unlookup).collect::<Vec<_>>();
1926 Self::nominate(RawOrigin::Signed(ctrl).into(), targets)
1927 }
1928
1929 fn desired_validator_count() -> u32 {
1930 ValidatorCount::<T>::get()
1931 }
1932
1933 fn election_ongoing() -> bool {
1934 <T::ElectionProvider as ElectionProvider>::status().is_ok()
1935 }
1936
1937 fn force_unstake(who: Self::AccountId) -> sp_runtime::DispatchResult {
1938 Self::force_unstake(RawOrigin::Root.into(), who.clone(), 0)
1939 }
1940
1941 fn is_exposed_in_era(who: &Self::AccountId, era: &EraIndex) -> bool {
1942 ErasStakersPaged::<T>::iter_prefix((era,)).any(|((validator, _), exposure_page)| {
1943 validator == *who || exposure_page.others.iter().any(|i| i.who == *who)
1944 })
1945 }
1946
1947 fn status(
1948 who: &Self::AccountId,
1949 ) -> Result<sp_staking::StakerStatus<Self::AccountId>, DispatchError> {
1950 if !StakingLedger::<T>::is_bonded(StakingAccount::Stash(who.clone())) {
1951 return Err(Error::<T>::NotStash.into());
1952 }
1953
1954 let is_validator = Validators::<T>::contains_key(&who);
1955 let is_nominator = Nominators::<T>::get(&who);
1956
1957 use sp_staking::StakerStatus;
1958 match (is_validator, is_nominator.is_some()) {
1959 (false, false) => Ok(StakerStatus::Idle),
1960 (true, false) => Ok(StakerStatus::Validator),
1961 (false, true) => Ok(StakerStatus::Nominator(
1962 is_nominator.expect("is checked above; qed").targets.into_inner(),
1963 )),
1964 (true, true) => {
1965 defensive!("cannot be both validators and nominator");
1966 Err(Error::<T>::BadState.into())
1967 },
1968 }
1969 }
1970
1971 fn is_virtual_staker(who: &T::AccountId) -> bool {
1976 frame_system::Pallet::<T>::account_nonce(who).is_zero() &&
1977 VirtualStakers::<T>::contains_key(who)
1978 }
1979
1980 fn slash_reward_fraction() -> Perbill {
1981 SlashRewardFraction::<T>::get()
1982 }
1983
1984 sp_staking::runtime_benchmarks_enabled! {
1985 fn nominations(who: &Self::AccountId) -> Option<Vec<T::AccountId>> {
1986 Nominators::<T>::get(who).map(|n| n.targets.into_inner())
1987 }
1988
1989 fn add_era_stakers(
1990 current_era: &EraIndex,
1991 stash: &T::AccountId,
1992 exposures: Vec<(Self::AccountId, Self::Balance)>,
1993 ) {
1994 let others = exposures
1995 .iter()
1996 .map(|(who, value)| crate::IndividualExposure { who: who.clone(), value: *value })
1997 .collect::<Vec<_>>();
1998 let exposure = Exposure { total: Default::default(), own: Default::default(), others };
1999 Eras::<T>::upsert_exposure(*current_era, stash, exposure);
2000 }
2001
2002 fn max_exposure_page_size() -> Page {
2003 T::MaxExposurePageSize::get()
2004 }
2005 }
2006
2007 sp_staking::std_or_benchmarks_enabled! {
2008 fn set_era(era: EraIndex) {
2009 ActiveEra::<T>::put(crate::ActiveEraInfo { index: era, start: None });
2010 CurrentEra::<T>::put(era.saturating_add(1));
2012 }
2013 }
2014}
2015
2016impl<T: Config> sp_staking::StakingUnchecked for Pallet<T> {
2017 fn migrate_to_virtual_staker(who: &Self::AccountId) -> DispatchResult {
2018 asset::kill_stake::<T>(who)?;
2019 VirtualStakers::<T>::insert(who, ());
2020 Ok(())
2021 }
2022
2023 fn virtual_bond(
2027 keyless_who: &Self::AccountId,
2028 value: Self::Balance,
2029 payee: &Self::AccountId,
2030 ) -> DispatchResult {
2031 if StakingLedger::<T>::is_bonded(StakingAccount::Stash(keyless_who.clone())) {
2032 return Err(Error::<T>::AlreadyBonded.into());
2033 }
2034
2035 ensure!(keyless_who != payee, Error::<T>::RewardDestinationRestricted);
2037
2038 VirtualStakers::<T>::insert(keyless_who, ());
2040
2041 Self::deposit_event(Event::<T>::Bonded { stash: keyless_who.clone(), amount: value });
2042 let ledger = StakingLedger::<T>::new(keyless_who.clone(), value);
2043
2044 ledger.bond(RewardDestination::Account(payee.clone()))?;
2045
2046 Ok(())
2047 }
2048
2049 #[cfg(feature = "runtime-benchmarks")]
2051 fn migrate_to_direct_staker(who: &Self::AccountId) {
2052 assert!(VirtualStakers::<T>::contains_key(who));
2053 let ledger = StakingLedger::<T>::get(Stash(who.clone())).unwrap();
2054 let _ = asset::update_stake::<T>(who, ledger.total)
2055 .expect("funds must be transferred to stash");
2056 VirtualStakers::<T>::remove(who);
2057 }
2058}
2059
2060#[cfg(any(test, feature = "try-runtime"))]
2061impl<T: Config> Pallet<T> {
2062 pub(crate) fn do_try_state(_now: BlockNumberFor<T>) -> Result<(), TryRuntimeError> {
2063 if ActiveEra::<T>::get().is_none() && CurrentEra::<T>::get().is_none() {
2066 return Ok(());
2067 }
2068
2069 session_rotation::Rotator::<T>::do_try_state()?;
2070 session_rotation::Eras::<T>::do_try_state()?;
2071
2072 use frame_support::traits::fungible::Inspect;
2073 if T::CurrencyToVote::will_downscale(T::Currency::total_issuance()).map_or(false, |x| x) {
2074 log!(warn, "total issuance will cause T::CurrencyToVote to downscale -- report to maintainers.")
2075 }
2076
2077 Self::check_ledgers()?;
2078 Self::check_bonded_consistency()?;
2079 Self::check_payees()?;
2080 Self::check_paged_exposures()?;
2081 Self::check_count()?;
2082 Self::check_slash_health()?;
2083 Self::check_reward_mode_consistency()?;
2084
2085 Ok(())
2086 }
2087
2088 fn check_reward_mode_consistency() -> Result<(), TryRuntimeError> {
2093 if DisableMintingGuard::<T>::get().is_some() {
2095 ensure!(
2096 T::DisableMinting::get(),
2097 "DisableMintingGuard is set but DisableMinting is false. \
2098 Switching from non-minting back to legacy mode is not supported."
2099 );
2100 }
2101
2102 if T::DisableMinting::get() {
2103 if let Some(guard_era) = DisableMintingGuard::<T>::get() {
2104 let active_era = crate::session_rotation::Rotator::<T>::active_era();
2105 let history_depth = T::HistoryDepth::get();
2106 let oldest = active_era.saturating_sub(history_depth);
2107 for era in oldest..active_era {
2108 if era >= guard_era {
2109 ensure!(
2110 crate::reward::EraRewardManager::<T>::has_staker_rewards_pot(era),
2111 "Era pot missing for guarded era. Rewards cannot be paid."
2112 );
2113 }
2114 }
2115 } else {
2116 log!(
2120 warn,
2121 "DisableMinting=true but DisableMintingGuard is not set. \
2122 Expected only before the first era boundary after migration."
2123 );
2124 }
2125 }
2126 Ok(())
2127 }
2128
2129 fn check_bonded_consistency() -> Result<(), TryRuntimeError> {
2139 use alloc::collections::btree_set::BTreeSet;
2140
2141 let mut count_controller_double = 0;
2142 let mut count_double = 0;
2143 let mut count_none = 0;
2144 let mut controllers = BTreeSet::new();
2147
2148 for (stash, controller) in <Bonded<T>>::iter() {
2149 if !controllers.insert(controller.clone()) {
2150 count_controller_double += 1;
2151 }
2152
2153 match (<Ledger<T>>::get(&stash), <Ledger<T>>::get(&controller)) {
2154 (Some(_), Some(_)) =>
2155 {
2159 if stash != controller {
2160 count_double += 1;
2161 }
2162 },
2163 (None, None) => {
2164 count_none += 1;
2165 },
2166 _ => {},
2167 };
2168 }
2169
2170 if count_controller_double != 0 {
2171 log!(
2172 warn,
2173 "a controller is associated with more than one ledger ({} occurrences)",
2174 count_controller_double
2175 );
2176 };
2177
2178 if count_double != 0 {
2179 log!(warn, "single tuple of (stash, controller) pair bonds more than one ledger ({} occurrences)", count_double);
2180 }
2181
2182 if count_none != 0 {
2183 log!(warn, "inconsistent bonded state: (stash, controller) pair missing associated ledger ({} occurrences)", count_none);
2184 }
2185
2186 Ok(())
2187 }
2188
2189 fn check_payees() -> Result<(), TryRuntimeError> {
2194 for (stash, _) in Bonded::<T>::iter() {
2195 ensure!(Payee::<T>::get(&stash).is_some(), "bonded ledger does not have payee set");
2196 }
2197
2198 ensure!(
2199 (Ledger::<T>::iter().count() == Payee::<T>::iter().count()) &&
2200 (Ledger::<T>::iter().count() == Bonded::<T>::iter().count()),
2201 "number of entries in payee storage items does not match the number of bonded ledgers",
2202 );
2203
2204 Ok(())
2205 }
2206
2207 fn check_count() -> Result<(), TryRuntimeError> {
2211 crate::log!(
2215 debug,
2216 "VoterList count: {}, Nominators count: {}, Validators count: {}",
2217 <T as Config>::VoterList::count(),
2218 Nominators::<T>::count(),
2219 Validators::<T>::count()
2220 );
2221
2222 ensure!(
2223 <T as Config>::TargetList::count() == Validators::<T>::count(),
2224 "wrong external count"
2225 );
2226 let max_validators_bound = crate::MaxWinnersOf::<T>::get();
2227 let max_winners_per_page_bound = crate::MaxWinnersPerPageOf::<T::ElectionProvider>::get();
2228 ensure!(
2229 max_validators_bound >= max_winners_per_page_bound,
2230 "max validators should be higher than per page bounds"
2231 );
2232 ensure!(ValidatorCount::<T>::get() <= max_validators_bound, Error::<T>::TooManyValidators);
2233 Ok(())
2234 }
2235
2236 fn check_ledgers() -> Result<(), TryRuntimeError> {
2244 let mut chilled_undermin: Vec<T::AccountId> = Vec::new();
2245 let mut chilled_total: u32 = 0;
2246 let mut nominator_undermin: Vec<T::AccountId> = Vec::new();
2247 let mut nominator_total: u32 = 0;
2248 let mut validator_undermin: Vec<T::AccountId> = Vec::new();
2249 let mut validator_total: u32 = 0;
2250 let mut total_ledgers: u32 = 0;
2251
2252 Bonded::<T>::iter()
2253 .map(|(stash, ctrl)| {
2254 total_ledgers += 1;
2255 if VirtualStakers::<T>::contains_key(stash.clone()) {
2257 ensure!(
2258 asset::staked::<T>(&stash) == Zero::zero(),
2259 "virtual stakers should not have any staked balance"
2260 );
2261 ensure!(
2262 <Bonded<T>>::get(stash.clone()).unwrap() == stash.clone(),
2263 "stash and controller should be same"
2264 );
2265 ensure!(
2266 Ledger::<T>::get(stash.clone()).unwrap().stash == stash,
2267 "ledger corrupted for virtual staker"
2268 );
2269 ensure!(
2270 frame_system::Pallet::<T>::account_nonce(&stash).is_zero(),
2271 "virtual stakers are keyless and should not have any nonce"
2272 );
2273 let reward_destination = <Payee<T>>::get(stash.clone()).unwrap();
2274 if let RewardDestination::Account(payee) = reward_destination {
2275 ensure!(
2276 payee != stash.clone(),
2277 "reward destination should not be same as stash for virtual staker"
2278 );
2279 } else {
2280 return Err(DispatchError::Other(
2281 "reward destination must be of account variant for virtual staker",
2282 ));
2283 }
2284 } else {
2285 let integrity = Self::inspect_bond_state(&stash);
2286 if integrity != Ok(LedgerIntegrityState::Ok) {
2287 log!(
2289 error,
2290 "defensive: bonded stash {:?} has inconsistent ledger state: {:?}",
2291 stash,
2292 integrity
2293 );
2294 }
2295 }
2296
2297 Self::ensure_ledger_consistent(&ctrl)?;
2298 Self::collect_min_bond_violations(
2299 &ctrl,
2300 &mut chilled_undermin,
2301 &mut chilled_total,
2302 &mut nominator_undermin,
2303 &mut nominator_total,
2304 &mut validator_undermin,
2305 &mut validator_total,
2306 )?;
2307 Ok(())
2308 })
2309 .collect::<Result<Vec<_>, _>>()?;
2310
2311 if chilled_total > 0 {
2312 log!(
2313 warn,
2314 "{} chilled stashes (out of {} total ledgers) have less stake than minimum role bond ({:?}). Examples: {:?}",
2315 chilled_total,
2316 total_ledgers,
2317 Self::min_chilled_bond(),
2318 chilled_undermin,
2319 );
2320 }
2321 if nominator_total > 0 {
2322 log!(
2323 warn,
2324 "{} nominators (out of {} total ledgers) have less stake than minimum role bond ({:?}). Examples: {:?}",
2325 nominator_total,
2326 total_ledgers,
2327 Self::min_nominator_bond(),
2328 nominator_undermin,
2329 );
2330 }
2331 if validator_total > 0 {
2332 log!(
2333 warn,
2334 "{} validators (out of {} total ledgers) have less stake than minimum role bond ({:?}). Examples: {:?}",
2335 validator_total,
2336 total_ledgers,
2337 Self::min_validator_bond(),
2338 validator_undermin,
2339 );
2340 }
2341
2342 Ok(())
2343 }
2344
2345 fn check_paged_exposures() -> Result<(), TryRuntimeError> {
2356 let Some(era) = ActiveEra::<T>::get().map(|a| a.index) else { return Ok(()) };
2357 let overview_and_pages = ErasStakersOverview::<T>::iter_prefix(era)
2358 .map(|(validator, metadata)| {
2359 let last_validator_era = LastValidatorEra::<T>::get(&validator).unwrap_or_default();
2360 if last_validator_era != era && last_validator_era != era + 1 {
2362 log!(
2363 error,
2364 "Validator {:?} has incorrect LastValidatorEra (expected {:?} or {:?}, got {:?})",
2365 validator,
2366 era,
2367 era + 1,
2368 last_validator_era
2369 );
2370 }
2371
2372 let pages = ErasStakersPaged::<T>::iter_prefix((era, validator))
2373 .map(|(_idx, page)| page)
2374 .collect::<Vec<_>>();
2375 (metadata, pages)
2376 })
2377 .collect::<Vec<_>>();
2378
2379 ensure!(
2380 overview_and_pages.iter().flat_map(|(_m, pages)| pages).all(|page| {
2381 let expected = page
2382 .others
2383 .iter()
2384 .map(|e| e.value)
2385 .fold(BalanceOf::<T>::zero(), |acc, x| acc + x);
2386 page.page_total == expected
2387 }),
2388 "found wrong page_total"
2389 );
2390
2391 ensure!(
2392 overview_and_pages.iter().all(|(metadata, pages)| {
2393 let page_count_good = metadata.page_count == pages.len() as u32;
2394 let nominator_count_good = metadata.nominator_count ==
2395 pages.iter().map(|p| p.others.len() as u32).fold(0u32, |acc, x| acc + x);
2396 let total_good = metadata.total ==
2397 metadata.own +
2398 pages
2399 .iter()
2400 .fold(BalanceOf::<T>::zero(), |acc, page| acc + page.page_total);
2401
2402 page_count_good && nominator_count_good && total_good
2403 }),
2404 "found bad metadata"
2405 );
2406
2407 ensure!(
2408 overview_and_pages
2409 .iter()
2410 .map(|(metadata, _pages)| metadata.total)
2411 .fold(BalanceOf::<T>::zero(), |acc, x| acc + x) ==
2412 ErasTotalStake::<T>::get(era),
2413 "found bad eras total stake"
2414 );
2415
2416 Ok(())
2417 }
2418
2419 fn check_slash_health() -> Result<(), TryRuntimeError> {
2421 let offence_queue_eras = OffenceQueueEras::<T>::get().unwrap_or_default().into_inner();
2423 let mut sorted_offence_queue_eras = offence_queue_eras.clone();
2424 sorted_offence_queue_eras.sort();
2425 ensure!(
2426 sorted_offence_queue_eras == offence_queue_eras,
2427 "Offence queue eras are not sorted"
2428 );
2429 drop(sorted_offence_queue_eras);
2430
2431 let active_era = Rotator::<T>::active_era();
2433 let oldest_unprocessed_offence_era =
2434 offence_queue_eras.first().cloned().unwrap_or(active_era);
2435
2436 let oldest_unprocessed_offence_age =
2440 active_era.saturating_sub(oldest_unprocessed_offence_era);
2441
2442 if oldest_unprocessed_offence_age > 2.min(T::BondingDuration::get()) {
2444 log!(
2445 warn,
2446 "Offence queue has unprocessed offences from older than 2 eras: oldest offence era in queue {:?} (active era: {:?})",
2447 oldest_unprocessed_offence_era,
2448 active_era
2449 );
2450 }
2451
2452 ensure!(
2454 oldest_unprocessed_offence_age < T::BondingDuration::get() - 1,
2455 "offences from era less than 3 eras old from active era not processed yet"
2456 );
2457
2458 for e in offence_queue_eras {
2460 let count = OffenceQueue::<T>::iter_prefix(e).count();
2461 ensure!(count > 0, "Offence queue is empty for era listed in offence queue eras");
2462 log!(info, "Offence queue for era {:?} has {:?} offences queued", e, count);
2463 }
2464
2465 for era in (active_era.saturating_sub(T::BondingDuration::get()))..(active_era) {
2469 Self::ensure_era_slashes_applied(era)?;
2473 }
2474
2475 for (era, _) in CancelledSlashes::<T>::iter() {
2477 ensure!(era >= active_era, "Found cancelled slashes for era before active era");
2478 }
2479
2480 Ok(())
2481 }
2482
2483 fn collect_min_bond_violations(
2486 ctrl: &T::AccountId,
2487 chilled_undermin: &mut Vec<T::AccountId>,
2488 chilled_total: &mut u32,
2489 nominator_undermin: &mut Vec<T::AccountId>,
2490 nominator_total: &mut u32,
2491 validator_undermin: &mut Vec<T::AccountId>,
2492 validator_total: &mut u32,
2493 ) -> Result<(), TryRuntimeError> {
2494 const MAX_EXAMPLES: usize = 10;
2495 let ledger = Self::ledger(StakingAccount::Controller(ctrl.clone()))?;
2496 let stash = ledger.stash;
2497
2498 let is_nominator = Nominators::<T>::contains_key(&stash);
2499 let is_validator = Validators::<T>::contains_key(&stash);
2500
2501 match (is_nominator, is_validator) {
2502 (false, false) => {
2503 if ledger.active < Self::min_chilled_bond() && !ledger.active.is_zero() {
2504 *chilled_total += 1;
2506 if chilled_undermin.len() < MAX_EXAMPLES {
2507 chilled_undermin.push(stash.clone());
2508 }
2509 log!(
2510 trace,
2511 "Chilled stash {:?} has less stake ({:?}) than minimum role bond ({:?})",
2512 stash,
2513 ledger.active,
2514 Self::min_chilled_bond()
2515 );
2516 }
2517 },
2519 (true, false) => {
2520 if ledger.active < Self::min_nominator_bond() {
2522 *nominator_total += 1;
2523 if nominator_undermin.len() < MAX_EXAMPLES {
2524 nominator_undermin.push(stash.clone());
2525 }
2526 log!(
2527 trace,
2528 "Nominator {:?} has less stake ({:?}) than minimum role bond ({:?})",
2529 stash,
2530 ledger.active,
2531 Self::min_nominator_bond()
2532 );
2533 }
2534 },
2535 (false, true) => {
2536 if ledger.active < Self::min_validator_bond() {
2538 *validator_total += 1;
2539 if validator_undermin.len() < MAX_EXAMPLES {
2540 validator_undermin.push(stash.clone());
2541 }
2542 log!(
2543 trace,
2544 "Validator {:?} has less stake ({:?}) than minimum role bond ({:?})",
2545 stash,
2546 ledger.active,
2547 Self::min_validator_bond()
2548 );
2549 }
2550 },
2551 (true, true) => {
2552 ensure!(false, "Stash cannot be both nominator and validator");
2553 },
2554 }
2555 Ok(())
2556 }
2557
2558 fn ensure_ledger_consistent(ctrl: &T::AccountId) -> Result<(), TryRuntimeError> {
2559 let ledger = Self::ledger(StakingAccount::Controller(ctrl.clone()))?;
2561
2562 let real_total: BalanceOf<T> =
2563 ledger.unlocking.iter().fold(ledger.active, |a, c| a + c.value);
2564 ensure!(real_total == ledger.total, "ledger.total corrupt");
2565
2566 Ok(())
2567 }
2568}