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 used_weight = if ledger.unlocking.is_empty() &&
289 (ledger.active < Self::min_chilled_bond() || ledger.active.is_zero())
290 {
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 {
788 StakingLedger::<T>::kill(&stash)?;
791
792 Self::do_remove_validator(&stash);
793 Self::do_remove_nominator(&stash);
794
795 LastValidatorEra::<T>::remove(&stash);
797
798 Ok(())
799 }
800
801 #[cfg(test)]
802 pub(crate) fn reward_by_ids(validators_points: impl IntoIterator<Item = (T::AccountId, u32)>) {
803 Eras::<T>::reward_active_era(validators_points)
804 }
805
806 pub(crate) fn set_force_era(mode: Forcing) {
808 log!(info, "Setting force era mode {:?}.", mode);
809 ForceEra::<T>::put(mode);
810 Self::deposit_event(Event::<T>::ForceEra { mode });
811 }
812
813 #[cfg(feature = "runtime-benchmarks")]
814 pub fn add_era_stakers(
815 current_era: EraIndex,
816 stash: T::AccountId,
817 exposure: Exposure<T::AccountId, BalanceOf<T>>,
818 ) {
819 Eras::<T>::upsert_exposure(current_era, &stash, exposure);
820 }
821
822 #[cfg(feature = "runtime-benchmarks")]
823 pub fn set_slash_reward_fraction(fraction: Perbill) {
824 SlashRewardFraction::<T>::put(fraction);
825 }
826
827 pub(crate) fn get_npos_voters(
837 bounds: DataProviderBounds,
838 status: &SnapshotStatus<T::AccountId>,
839 ) -> Vec<VoterOf<Self>> {
840 let mut voters_size_tracker: StaticTracker<Self> = StaticTracker::default();
841
842 let page_len_prediction = {
843 let all_voter_count = T::VoterList::count();
844 bounds.count.unwrap_or(all_voter_count.into()).min(all_voter_count.into()).0
845 };
846
847 let mut all_voters = Vec::<_>::with_capacity(page_len_prediction as usize);
848
849 let weight_of = Self::weight_of_fn();
851
852 let mut voters_seen = 0u32;
853 let mut validators_taken = 0u32;
854 let mut nominators_taken = 0u32;
855 let mut min_active_stake = u64::MAX;
856
857 let mut sorted_voters = match status {
858 SnapshotStatus::Waiting => T::VoterList::iter(),
860 SnapshotStatus::Ongoing(account_id) => T::VoterList::iter_from(&account_id)
862 .defensive_unwrap_or(Box::new(vec![].into_iter())),
863 SnapshotStatus::Consumed => Box::new(vec![].into_iter()),
865 };
866
867 while all_voters.len() < page_len_prediction as usize &&
868 voters_seen < (NPOS_MAX_ITERATIONS_COEFFICIENT * page_len_prediction as u32)
869 {
870 let voter = match sorted_voters.next() {
871 Some(voter) => {
872 voters_seen.saturating_inc();
873 voter
874 },
875 None => break,
876 };
877
878 let voter_weight = weight_of(&voter);
879 if voter_weight.is_zero() {
881 log!(debug, "voter's active balance is 0. skip this voter.");
882 continue;
883 }
884
885 if let Some(Nominations { targets, .. }) = <Nominators<T>>::get(&voter) {
886 if !targets.is_empty() {
887 let voter = (voter, voter_weight, targets);
892 if voters_size_tracker.try_register_voter(&voter, &bounds).is_err() {
893 Self::deposit_event(Event::<T>::SnapshotVotersSizeExceeded {
895 size: voters_size_tracker.size as u32,
896 });
897 break;
898 }
899
900 all_voters.push(voter);
901 nominators_taken.saturating_inc();
902 } else {
903 defensive!("non-nominator fetched from voter list: {:?}", voter);
904 }
906 min_active_stake =
907 if voter_weight < min_active_stake { voter_weight } else { min_active_stake };
908 } else if Validators::<T>::contains_key(&voter) {
909 let self_vote = (
911 voter.clone(),
912 voter_weight,
913 vec![voter.clone()]
914 .try_into()
915 .expect("`MaxVotesPerVoter` must be greater than or equal to 1"),
916 );
917
918 if voters_size_tracker.try_register_voter(&self_vote, &bounds).is_err() {
919 Self::deposit_event(Event::<T>::SnapshotVotersSizeExceeded {
921 size: voters_size_tracker.size as u32,
922 });
923 break;
924 }
925 all_voters.push(self_vote);
926 validators_taken.saturating_inc();
927 } else {
928 defensive!(
934 "invalid item in `VoterList`: {:?}, this nominator probably has too many nominations now",
935 voter,
936 );
937 }
938 }
939
940 debug_assert!(all_voters.capacity() == page_len_prediction as usize);
942
943 let min_active_stake: T::CurrencyBalance =
944 if all_voters.is_empty() { Zero::zero() } else { min_active_stake.into() };
945
946 MinimumActiveStake::<T>::put(min_active_stake);
947
948 all_voters
949 }
950
951 pub fn get_npos_targets(bounds: DataProviderBounds) -> Vec<T::AccountId> {
957 let mut targets_size_tracker: StaticTracker<Self> = StaticTracker::default();
958
959 let final_predicted_len = {
960 let all_target_count = T::TargetList::count();
961 bounds.count.unwrap_or(all_target_count.into()).min(all_target_count.into()).0
962 };
963
964 let mut all_targets = Vec::<T::AccountId>::with_capacity(final_predicted_len as usize);
965 let mut targets_seen = 0;
966
967 let mut targets_iter = T::TargetList::iter();
968 while all_targets.len() < final_predicted_len as usize &&
969 targets_seen < (NPOS_MAX_ITERATIONS_COEFFICIENT * final_predicted_len as u32)
970 {
971 let target = match targets_iter.next() {
972 Some(target) => {
973 targets_seen.saturating_inc();
974 target
975 },
976 None => break,
977 };
978
979 if targets_size_tracker.try_register_target(target.clone(), &bounds).is_err() {
980 log!(warn, "npos targets size exceeded, stopping iteration.");
982 Self::deposit_event(Event::<T>::SnapshotTargetsSizeExceeded {
983 size: targets_size_tracker.size as u32,
984 });
985 break;
986 }
987
988 if Validators::<T>::contains_key(&target) {
989 all_targets.push(target);
990 }
991 }
992
993 log!(debug, "[bounds {:?}] generated {} npos targets", bounds, all_targets.len());
994
995 all_targets
996 }
997
998 pub fn do_add_nominator(who: &T::AccountId, nominations: Nominations<T>) {
1007 if !Nominators::<T>::contains_key(who) {
1008 let _ = T::VoterList::on_insert(who.clone(), Self::weight_of(who))
1010 .defensive_unwrap_or_default();
1011 }
1012 Nominators::<T>::insert(who, nominations);
1013 }
1014
1015 pub fn do_remove_nominator(who: &T::AccountId) -> bool {
1024 let outcome = if Nominators::<T>::contains_key(who) {
1025 Nominators::<T>::remove(who);
1026 let _ = T::VoterList::on_remove(who);
1027 true
1028 } else {
1029 false
1030 };
1031
1032 outcome
1033 }
1034
1035 pub fn do_add_validator(who: &T::AccountId, prefs: ValidatorPrefs) {
1043 if !Validators::<T>::contains_key(who) {
1044 let _ = T::VoterList::on_insert(who.clone(), Self::weight_of(who));
1046 }
1047 Validators::<T>::insert(who, prefs);
1048 }
1049
1050 pub fn do_remove_validator(who: &T::AccountId) -> bool {
1058 let outcome = if Validators::<T>::contains_key(who) {
1059 Validators::<T>::remove(who);
1060 let _ = T::VoterList::on_remove(who);
1061 true
1062 } else {
1063 false
1064 };
1065
1066 outcome
1067 }
1068
1069 pub(crate) fn register_weight(weight: Weight) {
1073 <frame_system::Pallet<T>>::register_extra_weight_unchecked(
1074 weight,
1075 DispatchClass::Mandatory,
1076 );
1077 }
1078
1079 pub fn eras_stakers(
1086 era: EraIndex,
1087 account: &T::AccountId,
1088 ) -> Exposure<T::AccountId, BalanceOf<T>> {
1089 Eras::<T>::get_full_exposure(era, account)
1090 }
1091
1092 pub(super) fn do_migrate_currency(stash: &T::AccountId) -> DispatchResult {
1093 if Self::is_virtual_staker(stash) {
1094 return Self::do_migrate_virtual_staker(stash);
1095 }
1096
1097 let ledger = Self::ledger(Stash(stash.clone()))?;
1098 let staked: BalanceOf<T> = T::OldCurrency::balance_locked(STAKING_ID, stash).into();
1099 ensure!(!staked.is_zero(), Error::<T>::AlreadyMigrated);
1100 ensure!(ledger.total == staked, Error::<T>::BadState);
1101
1102 T::OldCurrency::remove_lock(STAKING_ID, &stash);
1104
1105 let max_hold = asset::free_to_stake::<T>(&stash);
1107 let force_withdraw = if max_hold >= staked {
1108 asset::update_stake::<T>(&stash, staked)?;
1110 Zero::zero()
1111 } else {
1112 let force_withdraw = staked.saturating_sub(max_hold);
1115
1116 StakingLedger {
1119 total: max_hold,
1120 active: ledger.active.saturating_sub(force_withdraw),
1121 ..ledger
1123 }
1124 .update()?;
1125 force_withdraw
1126 };
1127
1128 frame_system::Pallet::<T>::dec_consumers(&stash);
1130
1131 Self::deposit_event(Event::<T>::CurrencyMigrated { stash: stash.clone(), force_withdraw });
1132 Ok(())
1133 }
1134
1135 fn do_migrate_virtual_staker(stash: &T::AccountId) -> DispatchResult {
1136 frame_system::Pallet::<T>::dec_consumers(&stash);
1139
1140 let actual_providers = frame_system::Pallet::<T>::providers(stash);
1145
1146 let expected_providers =
1147 if asset::free_to_stake::<T>(&stash) >= asset::existential_deposit::<T>() {
1150 2
1151 } else {
1152 1
1153 };
1154
1155 ensure!(actual_providers <= expected_providers, Error::<T>::BadState);
1157
1158 ensure!(actual_providers == expected_providers, Error::<T>::AlreadyMigrated);
1160
1161 let _ = frame_system::Pallet::<T>::dec_providers(&stash)?;
1163
1164 return Ok(());
1165 }
1166}
1167
1168impl<T: Config> Pallet<T> {
1169 pub fn api_nominations_quota(balance: BalanceOf<T>) -> u32 {
1173 T::NominationsQuota::get_quota(balance)
1174 }
1175
1176 pub fn api_eras_stakers(
1177 era: EraIndex,
1178 account: T::AccountId,
1179 ) -> Exposure<T::AccountId, BalanceOf<T>> {
1180 Self::eras_stakers(era, &account)
1181 }
1182
1183 pub fn api_eras_stakers_page_count(era: EraIndex, account: T::AccountId) -> Page {
1184 Eras::<T>::exposure_page_count(era, &account)
1185 }
1186
1187 pub fn api_pending_rewards(era: EraIndex, account: T::AccountId) -> bool {
1188 Eras::<T>::pending_rewards(era, &account)
1189 }
1190}
1191
1192impl<T: Config> ElectionDataProvider for Pallet<T> {
1193 type AccountId = T::AccountId;
1194 type BlockNumber = BlockNumberFor<T>;
1195 type MaxVotesPerVoter = MaxNominationsOf<T>;
1196
1197 fn desired_targets() -> data_provider::Result<u32> {
1198 Self::register_weight(T::DbWeight::get().reads(1));
1199 Ok(ValidatorCount::<T>::get())
1200 }
1201
1202 fn electing_voters(
1203 bounds: DataProviderBounds,
1204 page: PageIndex,
1205 ) -> data_provider::Result<Vec<VoterOf<Self>>> {
1206 let mut status = VoterSnapshotStatus::<T>::get();
1207 let voters = Self::get_npos_voters(bounds, &status);
1208
1209 match (page, &status) {
1211 (0, _) => status = SnapshotStatus::Waiting,
1213
1214 (_, SnapshotStatus::Waiting) | (_, SnapshotStatus::Ongoing(_)) => {
1215 let maybe_last = voters.last().map(|(x, _, _)| x).cloned();
1216
1217 if let Some(ref last) = maybe_last {
1218 let has_next =
1219 T::VoterList::iter_from(last).ok().and_then(|mut i| i.next()).is_some();
1220 if has_next {
1221 status = SnapshotStatus::Ongoing(last.clone());
1222 } else {
1223 status = SnapshotStatus::Consumed;
1224 }
1225 }
1226 },
1227 (_, SnapshotStatus::Consumed) => (),
1229 }
1230
1231 log!(
1232 debug,
1233 "[page {}, (next) status {:?}, bounds {:?}] generated {} npos voters [first: {:?}, last: {:?}]",
1234 page,
1235 status,
1236 bounds,
1237 voters.len(),
1238 voters.first().map(|(x, y, _)| (x, y)),
1239 voters.last().map(|(x, y, _)| (x, y)),
1240 );
1241
1242 match status {
1243 SnapshotStatus::Ongoing(_) => T::VoterList::lock(),
1244 _ => T::VoterList::unlock(),
1245 }
1246
1247 VoterSnapshotStatus::<T>::put(status);
1248 debug_assert!(!bounds.slice_exhausted(&voters));
1249
1250 Ok(voters)
1251 }
1252
1253 fn electing_voters_stateless(
1254 bounds: DataProviderBounds,
1255 ) -> data_provider::Result<Vec<VoterOf<Self>>> {
1256 let voters = Self::get_npos_voters(bounds, &SnapshotStatus::Waiting);
1257 log!(debug, "[stateless, bounds {:?}] generated {} npos voters", bounds, voters.len(),);
1258 Ok(voters)
1259 }
1260
1261 fn electable_targets(
1262 bounds: DataProviderBounds,
1263 page: PageIndex,
1264 ) -> data_provider::Result<Vec<T::AccountId>> {
1265 if page > 0 {
1266 log!(warn, "multi-page target snapshot not supported, returning page 0.");
1267 }
1268
1269 let targets = Self::get_npos_targets(bounds);
1270 if bounds.exhausted(None, CountBound(targets.len() as u32).into()) {
1271 return Err("Target snapshot too big");
1272 }
1273
1274 debug_assert!(!bounds.slice_exhausted(&targets));
1275
1276 Ok(targets)
1277 }
1278
1279 fn next_election_prediction(_: BlockNumberFor<T>) -> BlockNumberFor<T> {
1280 debug_assert!(false, "this is deprecated and not used anymore");
1281 sp_runtime::traits::Bounded::max_value()
1282 }
1283
1284 #[cfg(feature = "runtime-benchmarks")]
1285 fn fetch_page(page: PageIndex) {
1286 session_rotation::EraElectionPlanner::<T>::do_elect_paged(page);
1287 }
1288
1289 #[cfg(feature = "runtime-benchmarks")]
1290 fn add_voter(
1291 voter: T::AccountId,
1292 weight: VoteWeight,
1293 targets: BoundedVec<T::AccountId, Self::MaxVotesPerVoter>,
1294 ) {
1295 let stake = <BalanceOf<T>>::try_from(weight).unwrap_or_else(|_| {
1296 panic!("cannot convert a VoteWeight into BalanceOf, benchmark needs reconfiguring.")
1297 });
1298 <Bonded<T>>::insert(voter.clone(), voter.clone());
1299 <Ledger<T>>::insert(voter.clone(), StakingLedger::<T>::new(voter.clone(), stake));
1300
1301 Self::do_add_nominator(&voter, Nominations { targets, submitted_in: 0, suppressed: false });
1302 }
1303
1304 #[cfg(feature = "runtime-benchmarks")]
1305 fn add_target(target: T::AccountId) {
1306 let stake = (Self::min_validator_bond() + 1u32.into()) * 100u32.into();
1307 <Bonded<T>>::insert(target.clone(), target.clone());
1308 <Ledger<T>>::insert(target.clone(), StakingLedger::<T>::new(target.clone(), stake));
1309 Self::do_add_validator(
1310 &target,
1311 ValidatorPrefs { commission: Perbill::zero(), blocked: false },
1312 );
1313 }
1314
1315 #[cfg(feature = "runtime-benchmarks")]
1316 fn clear() {
1317 #[allow(deprecated)]
1318 <Bonded<T>>::remove_all(None);
1319 #[allow(deprecated)]
1320 <Ledger<T>>::remove_all(None);
1321 #[allow(deprecated)]
1322 <Validators<T>>::remove_all();
1323 #[allow(deprecated)]
1324 <Nominators<T>>::remove_all();
1325
1326 T::VoterList::unsafe_clear();
1327 }
1328
1329 #[cfg(feature = "runtime-benchmarks")]
1330 fn put_snapshot(
1331 voters: Vec<VoterOf<Self>>,
1332 targets: Vec<T::AccountId>,
1333 target_stake: Option<VoteWeight>,
1334 ) {
1335 targets.into_iter().for_each(|v| {
1336 let stake: BalanceOf<T> = target_stake
1337 .and_then(|w| <BalanceOf<T>>::try_from(w).ok())
1338 .unwrap_or_else(|| Self::min_nominator_bond() * 100u32.into());
1339 <Bonded<T>>::insert(v.clone(), v.clone());
1340 <Ledger<T>>::insert(v.clone(), StakingLedger::<T>::new(v.clone(), stake));
1341 Self::do_add_validator(
1342 &v,
1343 ValidatorPrefs { commission: Perbill::zero(), blocked: false },
1344 );
1345 });
1346
1347 voters.into_iter().for_each(|(v, s, t)| {
1348 let stake = <BalanceOf<T>>::try_from(s).unwrap_or_else(|_| {
1349 panic!("cannot convert a VoteWeight into BalanceOf, benchmark needs reconfiguring.")
1350 });
1351 <Bonded<T>>::insert(v.clone(), v.clone());
1352 <Ledger<T>>::insert(v.clone(), StakingLedger::<T>::new(v.clone(), stake));
1353 Self::do_add_nominator(
1354 &v,
1355 Nominations { targets: t, submitted_in: 0, suppressed: false },
1356 );
1357 });
1358 }
1359
1360 #[cfg(feature = "runtime-benchmarks")]
1361 fn set_desired_targets(count: u32) {
1362 ValidatorCount::<T>::put(count);
1363 }
1364}
1365
1366impl<T: Config> rc_client::AHStakingInterface for Pallet<T> {
1367 type AccountId = T::AccountId;
1368 type MaxValidatorSet = T::MaxValidatorSet;
1369
1370 fn on_relay_session_report(report: rc_client::SessionReport<Self::AccountId>) -> Weight {
1379 log!(debug, "Received session report: {}", report,);
1380
1381 let rc_client::SessionReport {
1382 end_index,
1383 activation_timestamp,
1384 validator_points,
1385 leftover,
1386 } = report;
1387 debug_assert!(!leftover);
1388
1389 Eras::<T>::reward_active_era(validator_points.into_iter());
1391 session_rotation::Rotator::<T>::end_session(end_index, activation_timestamp)
1392 }
1393
1394 fn weigh_on_relay_session_report(
1395 _report: &rc_client::SessionReport<Self::AccountId>,
1396 ) -> Weight {
1397 T::WeightInfo::rc_on_session_report()
1399 }
1400
1401 fn on_new_offences(
1412 slash_session: SessionIndex,
1413 offences: Vec<rc_client::Offence<T::AccountId>>,
1414 ) -> Weight {
1415 log!(debug, "🦹 on_new_offences: {:?}", offences);
1416 let weight = T::WeightInfo::rc_on_offence(offences.len() as u32);
1417
1418 let Some(active_era) = ActiveEra::<T>::get() else {
1420 log!(warn, "🦹 on_new_offences: no active era; ignoring offence");
1421 return T::WeightInfo::rc_on_offence(0);
1422 };
1423
1424 let active_era_start_session = Rotator::<T>::active_era_start_session_index();
1425
1426 let offence_era = if slash_session >= active_era_start_session {
1429 active_era.index
1430 } else {
1431 match BondedEras::<T>::get()
1432 .iter()
1433 .rev()
1435 .find_map(|&(era, sesh)| if sesh <= slash_session { Some(era) } else { None })
1436 {
1437 Some(era) => era,
1438 None => {
1439 log!(warn, "🦹 on_offence: no era found for slash_session; ignoring offence");
1442 return T::WeightInfo::rc_on_offence(0);
1443 },
1444 }
1445 };
1446
1447 let oldest_reportable_offence_era = if T::SlashDeferDuration::get() == 0 {
1448 active_era.index.saturating_sub(T::BondingDuration::get().saturating_sub(2))
1454 } else {
1455 active_era.index.saturating_sub(T::SlashDeferDuration::get().saturating_sub(1))
1458 };
1459
1460 for o in offences {
1461 let slash_fraction = o.slash_fraction;
1462 let validator: <T as frame_system::Config>::AccountId = o.offender.into();
1463
1464 if offence_era < oldest_reportable_offence_era {
1466 log!(warn, "🦹 on_new_offences: offence era {:?} too old; Can only accept offences from era {:?} or newer", offence_era, oldest_reportable_offence_era);
1467 Self::deposit_event(Event::<T>::OffenceTooOld {
1468 validator: validator.clone(),
1469 fraction: slash_fraction,
1470 offence_era,
1471 });
1472 continue;
1474 }
1475 let Some(exposure_overview) = <ErasStakersOverview<T>>::get(&offence_era, &validator)
1476 else {
1477 log!(
1480 warn,
1481 "🦹 on_offence: no exposure found for {:?} in era {}; ignoring offence",
1482 validator,
1483 offence_era
1484 );
1485 continue;
1486 };
1487
1488 Self::deposit_event(Event::<T>::OffenceReported {
1489 validator: validator.clone(),
1490 fraction: slash_fraction,
1491 offence_era,
1492 });
1493
1494 let prior_slash_fraction = ValidatorSlashInEra::<T>::get(offence_era, &validator)
1495 .map_or(Zero::zero(), |(f, _)| f);
1496
1497 if let Some(existing) = OffenceQueue::<T>::get(offence_era, &validator) {
1498 if slash_fraction.deconstruct() > existing.slash_fraction.deconstruct() {
1499 OffenceQueue::<T>::insert(
1500 offence_era,
1501 &validator,
1502 OffenceRecord {
1503 reporter: o.reporters.first().cloned(),
1504 reported_era: active_era.index,
1505 slash_fraction,
1506 ..existing
1507 },
1508 );
1509
1510 ValidatorSlashInEra::<T>::insert(
1512 offence_era,
1513 &validator,
1514 (slash_fraction, exposure_overview.own),
1515 );
1516
1517 log!(
1518 debug,
1519 "🦹 updated slash for {:?}: {:?} (prior: {:?})",
1520 validator,
1521 slash_fraction,
1522 prior_slash_fraction,
1523 );
1524 } else {
1525 log!(
1526 debug,
1527 "🦹 ignored slash for {:?}: {:?} (existing prior is larger: {:?})",
1528 validator,
1529 slash_fraction,
1530 prior_slash_fraction,
1531 );
1532 }
1533 } else if slash_fraction.deconstruct() > prior_slash_fraction.deconstruct() {
1534 ValidatorSlashInEra::<T>::insert(
1535 offence_era,
1536 &validator,
1537 (slash_fraction, exposure_overview.own),
1538 );
1539
1540 OffenceQueue::<T>::insert(
1541 offence_era,
1542 &validator,
1543 OffenceRecord {
1544 reporter: o.reporters.first().cloned(),
1545 reported_era: active_era.index,
1546 exposure_page: exposure_overview.page_count.saturating_sub(1),
1549 slash_fraction,
1550 prior_slash_fraction,
1551 },
1552 );
1553
1554 OffenceQueueEras::<T>::mutate(|q| {
1555 if let Some(eras) = q {
1556 log!(debug, "🦹 inserting offence era {} into existing queue", offence_era);
1557 eras.binary_search(&offence_era).err().map(|idx| {
1558 eras.try_insert(idx, offence_era).defensive_proof(
1559 "Offence era must be present in the existing queue",
1560 )
1561 });
1562 } else {
1563 let mut eras = WeakBoundedVec::default();
1564 log!(debug, "🦹 inserting offence era {} into empty queue", offence_era);
1565 let _ = eras
1566 .try_push(offence_era)
1567 .defensive_proof("Failed to push offence era into empty queue");
1568 *q = Some(eras);
1569 }
1570 });
1571
1572 log!(
1573 debug,
1574 "🦹 queued slash for {:?}: {:?} (prior: {:?})",
1575 validator,
1576 slash_fraction,
1577 prior_slash_fraction,
1578 );
1579 } else {
1580 log!(
1581 debug,
1582 "🦹 ignored slash for {:?}: {:?} (already slashed in era with prior: {:?})",
1583 validator,
1584 slash_fraction,
1585 prior_slash_fraction,
1586 );
1587 }
1588 }
1589
1590 weight
1591 }
1592
1593 fn weigh_on_new_offences(offence_count: u32) -> Weight {
1594 T::WeightInfo::rc_on_offence(offence_count)
1595 }
1596
1597 fn active_era_start_session_index() -> SessionIndex {
1598 Rotator::<T>::active_era_start_session_index()
1599 }
1600
1601 fn is_validator(who: &Self::AccountId) -> bool {
1602 Validators::<T>::contains_key(who)
1603 }
1604}
1605
1606impl<T: Config> ScoreProvider<T::AccountId> for Pallet<T> {
1607 type Score = VoteWeight;
1608
1609 fn score(who: &T::AccountId) -> Option<Self::Score> {
1610 Self::ledger(Stash(who.clone()))
1611 .ok()
1612 .and_then(|l| {
1613 if Nominators::<T>::contains_key(&l.stash) ||
1614 Validators::<T>::contains_key(&l.stash)
1615 {
1616 Some(l.active)
1617 } else {
1618 None
1619 }
1620 })
1621 .map(|a| {
1622 let issuance = asset::total_issuance::<T>();
1623 T::CurrencyToVote::to_vote(a, issuance)
1624 })
1625 }
1626
1627 #[cfg(feature = "runtime-benchmarks")]
1628 fn set_score_of(who: &T::AccountId, weight: Self::Score) {
1629 let active: BalanceOf<T> = weight.try_into().map_err(|_| ()).unwrap();
1632 let mut ledger = match Self::ledger(StakingAccount::Stash(who.clone())) {
1633 Ok(l) => l,
1634 Err(_) => StakingLedger::default_from(who.clone()),
1635 };
1636 ledger.active = active;
1637
1638 <Ledger<T>>::insert(who, ledger);
1639 <Bonded<T>>::insert(who, who);
1640 <Validators<T>>::insert(who, ValidatorPrefs::default());
1643
1644 let imbalance = asset::burn::<T>(asset::total_issuance::<T>());
1648 core::mem::forget(imbalance);
1651 }
1652}
1653
1654pub struct UseValidatorsMap<T>(core::marker::PhantomData<T>);
1658impl<T: Config> SortedListProvider<T::AccountId> for UseValidatorsMap<T> {
1659 type Score = BalanceOf<T>;
1660 type Error = ();
1661
1662 fn iter() -> Box<dyn Iterator<Item = T::AccountId>> {
1664 Box::new(Validators::<T>::iter().map(|(v, _)| v))
1665 }
1666 fn iter_from(
1667 start: &T::AccountId,
1668 ) -> Result<Box<dyn Iterator<Item = T::AccountId>>, Self::Error> {
1669 if Validators::<T>::contains_key(start) {
1670 let start_key = Validators::<T>::hashed_key_for(start);
1671 Ok(Box::new(Validators::<T>::iter_from(start_key).map(|(n, _)| n)))
1672 } else {
1673 Err(())
1674 }
1675 }
1676 fn lock() {}
1677 fn unlock() {}
1678 fn count() -> u32 {
1679 Validators::<T>::count()
1680 }
1681 fn contains(id: &T::AccountId) -> bool {
1682 Validators::<T>::contains_key(id)
1683 }
1684 fn on_insert(_: T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> {
1685 Ok(())
1687 }
1688 fn get_score(id: &T::AccountId) -> Result<Self::Score, Self::Error> {
1689 Ok(Pallet::<T>::weight_of(id).into())
1690 }
1691 fn on_update(_: &T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> {
1692 Ok(())
1694 }
1695 fn on_remove(_: &T::AccountId) -> Result<(), Self::Error> {
1696 Ok(())
1698 }
1699 fn unsafe_regenerate(
1700 _: impl IntoIterator<Item = T::AccountId>,
1701 _: Box<dyn Fn(&T::AccountId) -> Option<Self::Score>>,
1702 ) -> u32 {
1703 0
1705 }
1706 #[cfg(feature = "try-runtime")]
1707 fn try_state() -> Result<(), TryRuntimeError> {
1708 Ok(())
1709 }
1710
1711 fn unsafe_clear() {
1712 #[allow(deprecated)]
1713 Validators::<T>::remove_all();
1714 }
1715
1716 #[cfg(feature = "runtime-benchmarks")]
1717 fn score_update_worst_case(_who: &T::AccountId, _is_increase: bool) -> Self::Score {
1718 unimplemented!()
1719 }
1720}
1721
1722pub struct UseNominatorsAndValidatorsMap<T>(core::marker::PhantomData<T>);
1726impl<T: Config> SortedListProvider<T::AccountId> for UseNominatorsAndValidatorsMap<T> {
1727 type Error = ();
1728 type Score = VoteWeight;
1729
1730 fn iter() -> Box<dyn Iterator<Item = T::AccountId>> {
1731 Box::new(
1732 Validators::<T>::iter()
1733 .map(|(v, _)| v)
1734 .chain(Nominators::<T>::iter().map(|(n, _)| n)),
1735 )
1736 }
1737 fn iter_from(
1738 start: &T::AccountId,
1739 ) -> Result<Box<dyn Iterator<Item = T::AccountId>>, Self::Error> {
1740 if Validators::<T>::contains_key(start) {
1741 let start_key = Validators::<T>::hashed_key_for(start);
1742 Ok(Box::new(
1743 Validators::<T>::iter_from(start_key)
1744 .map(|(n, _)| n)
1745 .chain(Nominators::<T>::iter().map(|(x, _)| x)),
1746 ))
1747 } else if Nominators::<T>::contains_key(start) {
1748 let start_key = Nominators::<T>::hashed_key_for(start);
1749 Ok(Box::new(Nominators::<T>::iter_from(start_key).map(|(n, _)| n)))
1750 } else {
1751 Err(())
1752 }
1753 }
1754 fn lock() {}
1755 fn unlock() {}
1756 fn count() -> u32 {
1757 Nominators::<T>::count().saturating_add(Validators::<T>::count())
1758 }
1759 fn contains(id: &T::AccountId) -> bool {
1760 Nominators::<T>::contains_key(id) || Validators::<T>::contains_key(id)
1761 }
1762 fn on_insert(_: T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> {
1763 Ok(())
1765 }
1766 fn get_score(id: &T::AccountId) -> Result<Self::Score, Self::Error> {
1767 Ok(Pallet::<T>::weight_of(id))
1768 }
1769 fn on_update(_: &T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> {
1770 Ok(())
1772 }
1773 fn on_remove(_: &T::AccountId) -> Result<(), Self::Error> {
1774 Ok(())
1776 }
1777 fn unsafe_regenerate(
1778 _: impl IntoIterator<Item = T::AccountId>,
1779 _: Box<dyn Fn(&T::AccountId) -> Option<Self::Score>>,
1780 ) -> u32 {
1781 0
1783 }
1784
1785 #[cfg(feature = "try-runtime")]
1786 fn try_state() -> Result<(), TryRuntimeError> {
1787 Ok(())
1788 }
1789
1790 fn unsafe_clear() {
1791 #[allow(deprecated)]
1794 Nominators::<T>::remove_all();
1795 #[allow(deprecated)]
1796 Validators::<T>::remove_all();
1797 }
1798
1799 #[cfg(feature = "runtime-benchmarks")]
1800 fn score_update_worst_case(_who: &T::AccountId, _is_increase: bool) -> Self::Score {
1801 unimplemented!()
1802 }
1803}
1804
1805impl<T: Config> StakingInterface for Pallet<T> {
1806 type AccountId = T::AccountId;
1807 type Balance = BalanceOf<T>;
1808 type CurrencyToVote = T::CurrencyToVote;
1809
1810 fn minimum_nominator_bond() -> Self::Balance {
1811 Self::min_nominator_bond()
1812 }
1813
1814 fn minimum_validator_bond() -> Self::Balance {
1815 Self::min_validator_bond()
1816 }
1817
1818 fn stash_by_ctrl(controller: &Self::AccountId) -> Result<Self::AccountId, DispatchError> {
1819 Self::ledger(Controller(controller.clone()))
1820 .map(|l| l.stash)
1821 .map_err(|e| e.into())
1822 }
1823
1824 fn bonding_duration() -> EraIndex {
1829 T::BondingDuration::get()
1830 }
1831
1832 fn nominator_bonding_duration() -> EraIndex {
1846 if AreNominatorsSlashable::<T>::get() {
1847 T::BondingDuration::get()
1848 } else {
1849 T::NominatorFastUnbondDuration::get()
1850 }
1851 }
1852
1853 fn current_era() -> EraIndex {
1854 Rotator::<T>::active_era()
1857 }
1858
1859 fn stake(who: &Self::AccountId) -> Result<Stake<BalanceOf<T>>, DispatchError> {
1860 Self::ledger(Stash(who.clone()))
1861 .map(|l| Stake { total: l.total, active: l.active })
1862 .map_err(|e| e.into())
1863 }
1864
1865 fn bond_extra(who: &Self::AccountId, extra: Self::Balance) -> DispatchResult {
1866 Self::bond_extra(RawOrigin::Signed(who.clone()).into(), extra)
1867 }
1868
1869 fn unbond(who: &Self::AccountId, value: Self::Balance) -> DispatchResult {
1870 let ctrl = Self::bonded(who).ok_or(Error::<T>::NotStash)?;
1871 Self::unbond(RawOrigin::Signed(ctrl).into(), value)
1872 .map_err(|with_post| with_post.error)
1873 .map(|_| ())
1874 }
1875
1876 fn set_payee(stash: &Self::AccountId, reward_acc: &Self::AccountId) -> DispatchResult {
1877 ensure!(
1881 !Self::is_virtual_staker(stash) || stash != reward_acc,
1882 Error::<T>::RewardDestinationRestricted
1883 );
1884
1885 let ledger = Self::ledger(Stash(stash.clone()))?;
1886 let _ = ledger
1887 .set_payee(RewardDestination::Account(reward_acc.clone()))
1888 .defensive_proof("ledger was retrieved from storage, thus its bonded; qed.")?;
1889
1890 Ok(())
1891 }
1892
1893 fn chill(who: &Self::AccountId) -> DispatchResult {
1894 let ctrl = Self::bonded(who).ok_or(Error::<T>::NotStash)?;
1897 Self::chill(RawOrigin::Signed(ctrl).into())
1898 }
1899
1900 fn withdraw_unbonded(
1901 who: Self::AccountId,
1902 _num_slashing_spans: u32,
1903 ) -> Result<bool, DispatchError> {
1904 let ctrl = Self::bonded(&who).ok_or(Error::<T>::NotStash)?;
1905 Self::withdraw_unbonded(RawOrigin::Signed(ctrl.clone()).into(), 0)
1906 .map(|_| !StakingLedger::<T>::is_bonded(StakingAccount::Controller(ctrl)))
1907 .map_err(|with_post| with_post.error)
1908 }
1909
1910 fn bond(
1911 who: &Self::AccountId,
1912 value: Self::Balance,
1913 payee: &Self::AccountId,
1914 ) -> DispatchResult {
1915 Self::bond(
1916 RawOrigin::Signed(who.clone()).into(),
1917 value,
1918 RewardDestination::Account(payee.clone()),
1919 )
1920 }
1921
1922 fn nominate(who: &Self::AccountId, targets: Vec<Self::AccountId>) -> DispatchResult {
1923 let ctrl = Self::bonded(who).ok_or(Error::<T>::NotStash)?;
1924 let targets = targets.into_iter().map(T::Lookup::unlookup).collect::<Vec<_>>();
1925 Self::nominate(RawOrigin::Signed(ctrl).into(), targets)
1926 }
1927
1928 fn desired_validator_count() -> u32 {
1929 ValidatorCount::<T>::get()
1930 }
1931
1932 fn election_ongoing() -> bool {
1933 <T::ElectionProvider as ElectionProvider>::status().is_ok()
1934 }
1935
1936 fn force_unstake(who: Self::AccountId) -> sp_runtime::DispatchResult {
1937 Self::force_unstake(RawOrigin::Root.into(), who.clone(), 0)
1938 }
1939
1940 fn is_exposed_in_era(who: &Self::AccountId, era: &EraIndex) -> bool {
1941 ErasStakersPaged::<T>::iter_prefix((era,)).any(|((validator, _), exposure_page)| {
1942 validator == *who || exposure_page.others.iter().any(|i| i.who == *who)
1943 })
1944 }
1945
1946 fn status(
1947 who: &Self::AccountId,
1948 ) -> Result<sp_staking::StakerStatus<Self::AccountId>, DispatchError> {
1949 if !StakingLedger::<T>::is_bonded(StakingAccount::Stash(who.clone())) {
1950 return Err(Error::<T>::NotStash.into());
1951 }
1952
1953 let is_validator = Validators::<T>::contains_key(&who);
1954 let is_nominator = Nominators::<T>::get(&who);
1955
1956 use sp_staking::StakerStatus;
1957 match (is_validator, is_nominator.is_some()) {
1958 (false, false) => Ok(StakerStatus::Idle),
1959 (true, false) => Ok(StakerStatus::Validator),
1960 (false, true) => Ok(StakerStatus::Nominator(
1961 is_nominator.expect("is checked above; qed").targets.into_inner(),
1962 )),
1963 (true, true) => {
1964 defensive!("cannot be both validators and nominator");
1965 Err(Error::<T>::BadState.into())
1966 },
1967 }
1968 }
1969
1970 fn is_virtual_staker(who: &T::AccountId) -> bool {
1975 frame_system::Pallet::<T>::account_nonce(who).is_zero() &&
1976 VirtualStakers::<T>::contains_key(who)
1977 }
1978
1979 fn slash_reward_fraction() -> Perbill {
1980 SlashRewardFraction::<T>::get()
1981 }
1982
1983 sp_staking::runtime_benchmarks_enabled! {
1984 fn nominations(who: &Self::AccountId) -> Option<Vec<T::AccountId>> {
1985 Nominators::<T>::get(who).map(|n| n.targets.into_inner())
1986 }
1987
1988 fn add_era_stakers(
1989 current_era: &EraIndex,
1990 stash: &T::AccountId,
1991 exposures: Vec<(Self::AccountId, Self::Balance)>,
1992 ) {
1993 let others = exposures
1994 .iter()
1995 .map(|(who, value)| crate::IndividualExposure { who: who.clone(), value: *value })
1996 .collect::<Vec<_>>();
1997 let exposure = Exposure { total: Default::default(), own: Default::default(), others };
1998 Eras::<T>::upsert_exposure(*current_era, stash, exposure);
1999 }
2000
2001 fn max_exposure_page_size() -> Page {
2002 T::MaxExposurePageSize::get()
2003 }
2004 }
2005
2006 sp_staking::std_or_benchmarks_enabled! {
2007 fn set_era(era: EraIndex) {
2008 ActiveEra::<T>::put(crate::ActiveEraInfo { index: era, start: None });
2009 CurrentEra::<T>::put(era.saturating_add(1));
2011 }
2012 }
2013}
2014
2015impl<T: Config> sp_staking::StakingUnchecked for Pallet<T> {
2016 fn migrate_to_virtual_staker(who: &Self::AccountId) -> DispatchResult {
2017 asset::kill_stake::<T>(who)?;
2018 VirtualStakers::<T>::insert(who, ());
2019 Ok(())
2020 }
2021
2022 fn virtual_bond(
2026 keyless_who: &Self::AccountId,
2027 value: Self::Balance,
2028 payee: &Self::AccountId,
2029 ) -> DispatchResult {
2030 if StakingLedger::<T>::is_bonded(StakingAccount::Stash(keyless_who.clone())) {
2031 return Err(Error::<T>::AlreadyBonded.into());
2032 }
2033
2034 ensure!(keyless_who != payee, Error::<T>::RewardDestinationRestricted);
2036
2037 VirtualStakers::<T>::insert(keyless_who, ());
2039
2040 Self::deposit_event(Event::<T>::Bonded { stash: keyless_who.clone(), amount: value });
2041 let ledger = StakingLedger::<T>::new(keyless_who.clone(), value);
2042
2043 ledger.bond(RewardDestination::Account(payee.clone()))?;
2044
2045 Ok(())
2046 }
2047
2048 #[cfg(feature = "runtime-benchmarks")]
2050 fn migrate_to_direct_staker(who: &Self::AccountId) {
2051 assert!(VirtualStakers::<T>::contains_key(who));
2052 let ledger = StakingLedger::<T>::get(Stash(who.clone())).unwrap();
2053 let _ = asset::update_stake::<T>(who, ledger.total)
2054 .expect("funds must be transferred to stash");
2055 VirtualStakers::<T>::remove(who);
2056 }
2057}
2058
2059#[cfg(any(test, feature = "try-runtime"))]
2060impl<T: Config> Pallet<T> {
2061 pub(crate) fn do_try_state(_now: BlockNumberFor<T>) -> Result<(), TryRuntimeError> {
2062 if ActiveEra::<T>::get().is_none() && CurrentEra::<T>::get().is_none() {
2065 return Ok(());
2066 }
2067
2068 session_rotation::Rotator::<T>::do_try_state()?;
2069 session_rotation::Eras::<T>::do_try_state()?;
2070
2071 use frame_support::traits::fungible::Inspect;
2072 if T::CurrencyToVote::will_downscale(T::Currency::total_issuance()).map_or(false, |x| x) {
2073 log!(warn, "total issuance will cause T::CurrencyToVote to downscale -- report to maintainers.")
2074 }
2075
2076 Self::check_ledgers()?;
2077 Self::check_bonded_consistency()?;
2078 Self::check_payees()?;
2079 Self::check_paged_exposures()?;
2080 Self::check_count()?;
2081 Self::check_slash_health()?;
2082 Self::check_reward_mode_consistency()?;
2083
2084 Ok(())
2085 }
2086
2087 fn check_reward_mode_consistency() -> Result<(), TryRuntimeError> {
2092 if DisableMintingGuard::<T>::get().is_some() {
2094 ensure!(
2095 T::DisableMinting::get(),
2096 "DisableMintingGuard is set but DisableMinting is false. \
2097 Switching from non-minting back to legacy mode is not supported."
2098 );
2099 }
2100
2101 if T::DisableMinting::get() {
2102 if let Some(guard_era) = DisableMintingGuard::<T>::get() {
2103 let active_era = crate::session_rotation::Rotator::<T>::active_era();
2104 let history_depth = T::HistoryDepth::get();
2105 let oldest = active_era.saturating_sub(history_depth);
2106 for era in oldest..active_era {
2107 if era >= guard_era {
2108 ensure!(
2109 crate::reward::EraRewardManager::<T>::has_staker_rewards_pot(era),
2110 "Era pot missing for guarded era. Rewards cannot be paid."
2111 );
2112 }
2113 }
2114 } else {
2115 log!(
2119 warn,
2120 "DisableMinting=true but DisableMintingGuard is not set. \
2121 Expected only before the first era boundary after migration."
2122 );
2123 }
2124 }
2125 Ok(())
2126 }
2127
2128 fn check_bonded_consistency() -> Result<(), TryRuntimeError> {
2138 use alloc::collections::btree_set::BTreeSet;
2139
2140 let mut count_controller_double = 0;
2141 let mut count_double = 0;
2142 let mut count_none = 0;
2143 let mut controllers = BTreeSet::new();
2146
2147 for (stash, controller) in <Bonded<T>>::iter() {
2148 if !controllers.insert(controller.clone()) {
2149 count_controller_double += 1;
2150 }
2151
2152 match (<Ledger<T>>::get(&stash), <Ledger<T>>::get(&controller)) {
2153 (Some(_), Some(_)) =>
2154 {
2158 if stash != controller {
2159 count_double += 1;
2160 }
2161 },
2162 (None, None) => {
2163 count_none += 1;
2164 },
2165 _ => {},
2166 };
2167 }
2168
2169 if count_controller_double != 0 {
2170 log!(
2171 warn,
2172 "a controller is associated with more than one ledger ({} occurrences)",
2173 count_controller_double
2174 );
2175 };
2176
2177 if count_double != 0 {
2178 log!(warn, "single tuple of (stash, controller) pair bonds more than one ledger ({} occurrences)", count_double);
2179 }
2180
2181 if count_none != 0 {
2182 log!(warn, "inconsistent bonded state: (stash, controller) pair missing associated ledger ({} occurrences)", count_none);
2183 }
2184
2185 Ok(())
2186 }
2187
2188 fn check_payees() -> Result<(), TryRuntimeError> {
2193 for (stash, _) in Bonded::<T>::iter() {
2194 ensure!(Payee::<T>::get(&stash).is_some(), "bonded ledger does not have payee set");
2195 }
2196
2197 ensure!(
2198 (Ledger::<T>::iter().count() == Payee::<T>::iter().count()) &&
2199 (Ledger::<T>::iter().count() == Bonded::<T>::iter().count()),
2200 "number of entries in payee storage items does not match the number of bonded ledgers",
2201 );
2202
2203 Ok(())
2204 }
2205
2206 fn check_count() -> Result<(), TryRuntimeError> {
2210 crate::log!(
2214 debug,
2215 "VoterList count: {}, Nominators count: {}, Validators count: {}",
2216 <T as Config>::VoterList::count(),
2217 Nominators::<T>::count(),
2218 Validators::<T>::count()
2219 );
2220
2221 ensure!(
2222 <T as Config>::TargetList::count() == Validators::<T>::count(),
2223 "wrong external count"
2224 );
2225 let max_validators_bound = crate::MaxWinnersOf::<T>::get();
2226 let max_winners_per_page_bound = crate::MaxWinnersPerPageOf::<T::ElectionProvider>::get();
2227 ensure!(
2228 max_validators_bound >= max_winners_per_page_bound,
2229 "max validators should be higher than per page bounds"
2230 );
2231 ensure!(ValidatorCount::<T>::get() <= max_validators_bound, Error::<T>::TooManyValidators);
2232 Ok(())
2233 }
2234
2235 fn check_ledgers() -> Result<(), TryRuntimeError> {
2243 let mut chilled_undermin: Vec<T::AccountId> = Vec::new();
2244 let mut chilled_total: u32 = 0;
2245 let mut nominator_undermin: Vec<T::AccountId> = Vec::new();
2246 let mut nominator_total: u32 = 0;
2247 let mut validator_undermin: Vec<T::AccountId> = Vec::new();
2248 let mut validator_total: u32 = 0;
2249 let mut total_ledgers: u32 = 0;
2250
2251 Bonded::<T>::iter()
2252 .map(|(stash, ctrl)| {
2253 total_ledgers += 1;
2254 if VirtualStakers::<T>::contains_key(stash.clone()) {
2256 ensure!(
2257 asset::staked::<T>(&stash) == Zero::zero(),
2258 "virtual stakers should not have any staked balance"
2259 );
2260 ensure!(
2261 <Bonded<T>>::get(stash.clone()).unwrap() == stash.clone(),
2262 "stash and controller should be same"
2263 );
2264 ensure!(
2265 Ledger::<T>::get(stash.clone()).unwrap().stash == stash,
2266 "ledger corrupted for virtual staker"
2267 );
2268 ensure!(
2269 frame_system::Pallet::<T>::account_nonce(&stash).is_zero(),
2270 "virtual stakers are keyless and should not have any nonce"
2271 );
2272 let reward_destination = <Payee<T>>::get(stash.clone()).unwrap();
2273 if let RewardDestination::Account(payee) = reward_destination {
2274 ensure!(
2275 payee != stash.clone(),
2276 "reward destination should not be same as stash for virtual staker"
2277 );
2278 } else {
2279 return Err(DispatchError::Other(
2280 "reward destination must be of account variant for virtual staker",
2281 ));
2282 }
2283 } else {
2284 let integrity = Self::inspect_bond_state(&stash);
2285 if integrity != Ok(LedgerIntegrityState::Ok) {
2286 log!(
2288 error,
2289 "defensive: bonded stash {:?} has inconsistent ledger state: {:?}",
2290 stash,
2291 integrity
2292 );
2293 }
2294 }
2295
2296 Self::ensure_ledger_consistent(&ctrl)?;
2297 Self::collect_min_bond_violations(
2298 &ctrl,
2299 &mut chilled_undermin,
2300 &mut chilled_total,
2301 &mut nominator_undermin,
2302 &mut nominator_total,
2303 &mut validator_undermin,
2304 &mut validator_total,
2305 )?;
2306 Ok(())
2307 })
2308 .collect::<Result<Vec<_>, _>>()?;
2309
2310 if chilled_total > 0 {
2311 log!(
2312 warn,
2313 "{} chilled stashes (out of {} total ledgers) have less stake than minimum role bond ({:?}). Examples: {:?}",
2314 chilled_total,
2315 total_ledgers,
2316 Self::min_chilled_bond(),
2317 chilled_undermin,
2318 );
2319 }
2320 if nominator_total > 0 {
2321 log!(
2322 warn,
2323 "{} nominators (out of {} total ledgers) have less stake than minimum role bond ({:?}). Examples: {:?}",
2324 nominator_total,
2325 total_ledgers,
2326 Self::min_nominator_bond(),
2327 nominator_undermin,
2328 );
2329 }
2330 if validator_total > 0 {
2331 log!(
2332 warn,
2333 "{} validators (out of {} total ledgers) have less stake than minimum role bond ({:?}). Examples: {:?}",
2334 validator_total,
2335 total_ledgers,
2336 Self::min_validator_bond(),
2337 validator_undermin,
2338 );
2339 }
2340
2341 Ok(())
2342 }
2343
2344 fn check_paged_exposures() -> Result<(), TryRuntimeError> {
2355 let Some(era) = ActiveEra::<T>::get().map(|a| a.index) else { return Ok(()) };
2356 let overview_and_pages = ErasStakersOverview::<T>::iter_prefix(era)
2357 .map(|(validator, metadata)| {
2358 let last_validator_era = LastValidatorEra::<T>::get(&validator).unwrap_or_default();
2359 if last_validator_era != era && last_validator_era != era + 1 {
2361 log!(
2362 error,
2363 "Validator {:?} has incorrect LastValidatorEra (expected {:?} or {:?}, got {:?})",
2364 validator,
2365 era,
2366 era + 1,
2367 last_validator_era
2368 );
2369 }
2370
2371 let pages = ErasStakersPaged::<T>::iter_prefix((era, validator))
2372 .map(|(_idx, page)| page)
2373 .collect::<Vec<_>>();
2374 (metadata, pages)
2375 })
2376 .collect::<Vec<_>>();
2377
2378 ensure!(
2379 overview_and_pages.iter().flat_map(|(_m, pages)| pages).all(|page| {
2380 let expected = page
2381 .others
2382 .iter()
2383 .map(|e| e.value)
2384 .fold(BalanceOf::<T>::zero(), |acc, x| acc + x);
2385 page.page_total == expected
2386 }),
2387 "found wrong page_total"
2388 );
2389
2390 ensure!(
2391 overview_and_pages.iter().all(|(metadata, pages)| {
2392 let page_count_good = metadata.page_count == pages.len() as u32;
2393 let nominator_count_good = metadata.nominator_count ==
2394 pages.iter().map(|p| p.others.len() as u32).fold(0u32, |acc, x| acc + x);
2395 let total_good = metadata.total ==
2396 metadata.own +
2397 pages
2398 .iter()
2399 .fold(BalanceOf::<T>::zero(), |acc, page| acc + page.page_total);
2400
2401 page_count_good && nominator_count_good && total_good
2402 }),
2403 "found bad metadata"
2404 );
2405
2406 ensure!(
2407 overview_and_pages
2408 .iter()
2409 .map(|(metadata, _pages)| metadata.total)
2410 .fold(BalanceOf::<T>::zero(), |acc, x| acc + x) ==
2411 ErasTotalStake::<T>::get(era),
2412 "found bad eras total stake"
2413 );
2414
2415 Ok(())
2416 }
2417
2418 fn check_slash_health() -> Result<(), TryRuntimeError> {
2420 let offence_queue_eras = OffenceQueueEras::<T>::get().unwrap_or_default().into_inner();
2422 let mut sorted_offence_queue_eras = offence_queue_eras.clone();
2423 sorted_offence_queue_eras.sort();
2424 ensure!(
2425 sorted_offence_queue_eras == offence_queue_eras,
2426 "Offence queue eras are not sorted"
2427 );
2428 drop(sorted_offence_queue_eras);
2429
2430 let active_era = Rotator::<T>::active_era();
2432 let oldest_unprocessed_offence_era =
2433 offence_queue_eras.first().cloned().unwrap_or(active_era);
2434
2435 let oldest_unprocessed_offence_age =
2439 active_era.saturating_sub(oldest_unprocessed_offence_era);
2440
2441 if oldest_unprocessed_offence_age > 2.min(T::BondingDuration::get()) {
2443 log!(
2444 warn,
2445 "Offence queue has unprocessed offences from older than 2 eras: oldest offence era in queue {:?} (active era: {:?})",
2446 oldest_unprocessed_offence_era,
2447 active_era
2448 );
2449 }
2450
2451 ensure!(
2453 oldest_unprocessed_offence_age < T::BondingDuration::get() - 1,
2454 "offences from era less than 3 eras old from active era not processed yet"
2455 );
2456
2457 for e in offence_queue_eras {
2459 let count = OffenceQueue::<T>::iter_prefix(e).count();
2460 ensure!(count > 0, "Offence queue is empty for era listed in offence queue eras");
2461 log!(info, "Offence queue for era {:?} has {:?} offences queued", e, count);
2462 }
2463
2464 for era in (active_era.saturating_sub(T::BondingDuration::get()))..(active_era) {
2468 Self::ensure_era_slashes_applied(era)?;
2472 }
2473
2474 for (era, _) in CancelledSlashes::<T>::iter() {
2476 ensure!(era >= active_era, "Found cancelled slashes for era before active era");
2477 }
2478
2479 Ok(())
2480 }
2481
2482 fn collect_min_bond_violations(
2485 ctrl: &T::AccountId,
2486 chilled_undermin: &mut Vec<T::AccountId>,
2487 chilled_total: &mut u32,
2488 nominator_undermin: &mut Vec<T::AccountId>,
2489 nominator_total: &mut u32,
2490 validator_undermin: &mut Vec<T::AccountId>,
2491 validator_total: &mut u32,
2492 ) -> Result<(), TryRuntimeError> {
2493 const MAX_EXAMPLES: usize = 10;
2494 let ledger = Self::ledger(StakingAccount::Controller(ctrl.clone()))?;
2495 let stash = ledger.stash;
2496
2497 let is_nominator = Nominators::<T>::contains_key(&stash);
2498 let is_validator = Validators::<T>::contains_key(&stash);
2499
2500 match (is_nominator, is_validator) {
2501 (false, false) => {
2502 if ledger.active < Self::min_chilled_bond() && !ledger.active.is_zero() {
2503 *chilled_total += 1;
2505 if chilled_undermin.len() < MAX_EXAMPLES {
2506 chilled_undermin.push(stash.clone());
2507 }
2508 log!(
2509 trace,
2510 "Chilled stash {:?} has less stake ({:?}) than minimum role bond ({:?})",
2511 stash,
2512 ledger.active,
2513 Self::min_chilled_bond()
2514 );
2515 }
2516 },
2518 (true, false) => {
2519 if ledger.active < Self::min_nominator_bond() {
2521 *nominator_total += 1;
2522 if nominator_undermin.len() < MAX_EXAMPLES {
2523 nominator_undermin.push(stash.clone());
2524 }
2525 log!(
2526 trace,
2527 "Nominator {:?} has less stake ({:?}) than minimum role bond ({:?})",
2528 stash,
2529 ledger.active,
2530 Self::min_nominator_bond()
2531 );
2532 }
2533 },
2534 (false, true) => {
2535 if ledger.active < Self::min_validator_bond() {
2537 *validator_total += 1;
2538 if validator_undermin.len() < MAX_EXAMPLES {
2539 validator_undermin.push(stash.clone());
2540 }
2541 log!(
2542 trace,
2543 "Validator {:?} has less stake ({:?}) than minimum role bond ({:?})",
2544 stash,
2545 ledger.active,
2546 Self::min_validator_bond()
2547 );
2548 }
2549 },
2550 (true, true) => {
2551 ensure!(false, "Stash cannot be both nominator and validator");
2552 },
2553 }
2554 Ok(())
2555 }
2556
2557 fn ensure_ledger_consistent(ctrl: &T::AccountId) -> Result<(), TryRuntimeError> {
2558 let ledger = Self::ledger(StakingAccount::Controller(ctrl.clone()))?;
2560
2561 let real_total: BalanceOf<T> =
2562 ledger.unlocking.iter().fold(ledger.active, |a, c| a + c.value);
2563 ensure!(real_total == ledger.total, "ledger.total corrupt");
2564
2565 Ok(())
2566 }
2567}