referrerpolicy=no-referrer-when-downgrade

pallet_staking_async/pallet/
impls.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! `pallet-staking-async`'s main `impl` blocks.
19
20use crate::{
21	asset,
22	election_size_tracker::StaticTracker,
23	log,
24	session_rotation::{self, Eras, Rotator},
25	slashing::OffenceRecord,
26	weights::WeightInfo,
27	BalanceOf, EraRewardPoints, Exposure, Forcing, LedgerIntegrityState, MaxNominationsOf,
28	Nominations, NominationsQuota, PositiveImbalanceOf, PotAccountProvider, RewardDestination,
29	RewardKind, RewardPoint, RewardPot, SnapshotStatus, StakingLedger, ValidatorPrefs, STAKING_ID,
30};
31use alloc::{boxed::Box, vec, vec::Vec};
32use frame_election_provider_support::{
33	bounds::CountBound, data_provider, DataProviderBounds, ElectionDataProvider, ElectionProvider,
34	PageIndex, ScoreProvider, SortedListProvider, VoteWeight, VoterOf,
35};
36use frame_support::{
37	defensive,
38	dispatch::WithPostDispatchInfo,
39	pallet_prelude::*,
40	traits::{
41		fungible::Mutate as FunMutate, tokens::Preservation, Defensive, DefensiveSaturating, Get,
42		Imbalance, InspectLockableCurrency, LockableCurrency, OnUnbalanced,
43	},
44	weights::Weight,
45	StorageDoubleMap,
46};
47use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin};
48use pallet_staking_async_rc_client::{self as rc_client};
49use sp_runtime::{
50	traits::{CheckedAdd, Saturating, StaticLookup, Zero},
51	ArithmeticError, DispatchResult, Perbill,
52};
53use sp_staking::{
54	currency_to_vote::CurrencyToVote,
55	EraIndex, OnStakingUpdate, Page, SessionIndex, Stake, StakerRewardCalculator,
56	StakingAccount::{self, Controller, Stash},
57	StakingInterface,
58};
59
60use super::pallet::*;
61
62#[cfg(feature = "try-runtime")]
63use frame_support::ensure;
64#[cfg(any(test, feature = "try-runtime"))]
65use sp_runtime::TryRuntimeError;
66
67/// The maximum number of iterations that we do whilst iterating over `T::VoterList` in
68/// `get_npos_voters`.
69///
70/// In most cases, if we want n items, we iterate exactly n times. In rare cases, if a voter is
71/// invalid (for any reason) the iteration continues. With this constant, we iterate at most 2 * n
72/// times and then give up.
73const NPOS_MAX_ITERATIONS_COEFFICIENT: u32 = 2;
74
75impl<T: Config> Pallet<T> {
76	/// Returns the minimum required bond for participation, considering nominators,
77	/// and the chain’s existential deposit.
78	///
79	/// This function computes the smallest allowed bond among `MinValidatorBond` and
80	/// `MinNominatorBond`, but ensures it is not below the existential deposit required to keep an
81	/// account alive.
82	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	/// Returns the minimum required bond for participation in staking as a validator account.
89	pub(crate) fn min_validator_bond() -> BalanceOf<T> {
90		MinValidatorBond::<T>::get().max(asset::existential_deposit::<T>())
91	}
92
93	/// Returns the minimum required bond for participation in staking as a nominator account.
94	pub(crate) fn min_nominator_bond() -> BalanceOf<T> {
95		MinNominatorBond::<T>::get().max(asset::existential_deposit::<T>())
96	}
97
98	/// Fetches the ledger associated with a controller or stash account, if any.
99	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	/// Fetches the controller bonded to a stash account, if any.
108	pub fn bonded(stash: &T::AccountId) -> Option<T::AccountId> {
109		StakingLedger::<T>::paired_account(Stash(stash.clone()))
110	}
111
112	/// Inspects and returns the corruption state of a ledger and direct bond, if any.
113	///
114	/// Note: all operations in this method access directly the `Bonded` and `Ledger` storage maps
115	/// instead of using the [`StakingLedger`] API since the bond and/or ledger may be corrupted.
116	/// It is also meant to check state for direct bonds and may not work as expected for virtual
117	/// bonds.
118	pub(crate) fn inspect_bond_state(
119		stash: &T::AccountId,
120	) -> Result<LedgerIntegrityState, Error<T>> {
121		// look at any old unmigrated lock as well.
122		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	/// The total balance that can be slashed from a stash account as of right now.
150	pub fn slashable_balance_of(stash: &T::AccountId) -> BalanceOf<T> {
151		// Weight note: consider making the stake accessible through stash.
152		Self::ledger(Stash(stash.clone())).map(|l| l.active).unwrap_or_default()
153	}
154
155	/// Internal impl of [`Self::slashable_balance_of`] that returns [`VoteWeight`].
156	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	/// Returns a closure around `slashable_balance_of_vote_weight` that can be passed around.
164	///
165	/// This prevents call sites from repeatedly requesting `total_issuance` from backend. But it is
166	/// important to be only used while the total issuance is not changing.
167	pub fn weight_of_fn() -> Box<dyn Fn(&T::AccountId) -> VoteWeight> {
168		// NOTE: changing this to unboxed `impl Fn(..)` return type and the pallet will still
169		// compile, while some types in mock fail to resolve.
170		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	/// Same as `weight_of_fn`, but made for one time use.
177	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	/// Calculates the offence era from the slash application era.
183	pub(crate) fn offence_era_of(application_era: EraIndex) -> EraIndex {
184		application_era.saturating_sub(T::SlashDeferDuration::get())
185	}
186
187	/// Checks if a slash has been cancelled for the given era and slash parameters.
188	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		// for virtual stakers, we don't need to check the balance. Since they are only accessed
203		// via low level apis, we can assume that the caller has done the due diligence.
204		let extra = if Self::is_virtual_staker(stash) {
205			additional
206		} else {
207			// additional amount or actual balance of stash whichever is lower.
208			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		// last check: the new active amount of ledger must be more than min bond.
214		ensure!(ledger.active >= Self::min_chilled_bond(), Error::<T>::InsufficientBond);
215
216		// NOTE: ledger must be updated prior to calling `Self::weight_of`.
217		ledger.update()?;
218		// update this staker in the sorted list, if they exist in it.
219		if T::VoterList::contains(stash) {
220			// This might fail if the voter list is locked.
221			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	/// Calculate the earliest era that withdrawals are allowed for, considering:
230	/// - The current active era
231	/// - Any unprocessed offences in the queue
232	fn calculate_earliest_withdrawal_era(active_era: EraIndex) -> EraIndex {
233		// get lowest era for which all offences are processed and withdrawals can be allowed.
234		let earliest_unlock_era_by_offence_queue = OffenceQueueEras::<T>::get()
235			.as_ref()
236			.and_then(|eras| eras.first())
237			.copied()
238			// if nothing in queue, use the active era.
239			.unwrap_or(active_era)
240			// above returns earliest era for which offences are NOT processed yet, so we subtract
241			// one from it which gives us the oldest era for which all offences are processed.
242			.saturating_sub(1)
243			// Unlock chunks are keyed by the era they were initiated plus their unbond duration.
244			// We use full BondingDuration (validator duration) here because:
245			// - For validators: this is their actual unbond duration
246			// - For nominators: when slashable, they use full duration; when not slashable, their
247			//   chunks already have shorter unlock eras (set during unbond), so this calculation
248			//   still correctly allows their withdrawals.
249			.saturating_add(T::BondingDuration::get());
250
251		// If there are unprocessed offences older than the active era, withdrawals are only
252		// allowed up to the last era for which offences have been processed.
253		// Note: This situation is extremely unlikely, since offences have `SlashDeferDuration` eras
254		// to be processed. If it ever occurs, it likely indicates offence spam and that we're
255		// struggling to keep up with processing.
256		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		// Ensure last era slashes are applied. Else we block the withdrawals.
265		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		// withdraw unbonded balance from the ledger until earliest_era_to_withdraw.
280		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				// This account must have called `unbond()` with some value that caused the active
292				// portion to fall below the existential deposit + will have no more unlocking
293				// chunks left. We can now safely remove all staking-related information.
294				Self::kill_stash(&ledger.stash)?;
295
296				T::WeightInfo::withdraw_unbonded_kill()
297			} else {
298				// This was the consequence of a partial unbond. just update the ledger and move on.
299				ledger.update()?;
300
301				// This is only an update, so we use less overall weight.
302				T::WeightInfo::withdraw_unbonded_update()
303			};
304
305		// `old_total` should never be less than the new total because
306		// `consolidate_unlocked` strictly subtracts balance.
307		if new_total < old_total {
308			// Already checked that this won't overflow by entry condition.
309			let value = old_total.defensive_saturating_sub(new_total);
310			Self::deposit_event(Event::<T>::Withdrawn { stash, amount: value });
311
312			// notify listeners.
313			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		// Validate input data
344		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		// Note: if era has no reward to be claimed, era may be future.
363		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		// Input data seems good, no errors allowed after this point
394
395		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		// Nothing to do if they have no reward points.
401		if validator_reward_points.is_zero() {
402			return Ok(Some(T::WeightInfo::payout_stakers_alive_staked(0)).into());
403		}
404
405		// This is the fraction of the total reward that the validator and the
406		// nominators will get.
407		let validator_total_reward_part =
408			Perbill::from_rational(validator_reward_points, total_reward_points);
409
410		// This is how much validator + nominators are entitled to.
411		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		// Use the overview's own-stake (not the page's, which is zeroed on pages > 0)
416		// so the calculator sees the full validator self-stake for reward computation.
417		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		// Prorate the validator's reward (commission + own-stake share) across pages
428		// proportional to each page's stake relative to total.
429		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		// Pay validator incentive bonus from the separate incentive pot.
441		// Emits `ValidatorIncentivePaid` event inside `transfer_validator_incentive`.
442		if let Some(incentive) = Self::calculate_validator_incentive_for_page(
443			era,
444			&stash,
445			page_stake_part,
446			&era_reward_points,
447		) {
448			Self::transfer_validator_incentive(era, &stash, incentive);
449		}
450
451		// Determine whether to use dap payout or legacy path.
452		let use_dap_payout =
453			DisableMintingGuard::<T>::get().is_some_and(|guard_era| era >= guard_era);
454
455		let nominator_payout_count: u32 = if use_dap_payout {
456			Self::payout_from_provider(
457				era,
458				&stash,
459				validator_staker_payout_for_page,
460				&exposure,
461				overview_own,
462				reward_split.nominator_payout,
463			)
464		} else {
465			Self::payout_legacy_mint(
466				era,
467				&stash,
468				validator_staker_payout_for_page,
469				&exposure,
470				overview_own,
471				reward_split.nominator_payout,
472			)
473		};
474
475		debug_assert!(nominator_payout_count <= T::MaxExposurePageSize::get());
476
477		Ok(Some(T::WeightInfo::payout_stakers_alive_staked(nominator_payout_count)).into())
478	}
479
480	/// Payout stakers from an era reward pot (transfer-based, no minting).
481	fn payout_from_provider(
482		era: EraIndex,
483		stash: &T::AccountId,
484		validator_payout: BalanceOf<T>,
485		exposure: &crate::PagedExposure<T::AccountId, BalanceOf<T>>,
486		overview_own: BalanceOf<T>,
487		total_nominator_payout: BalanceOf<T>,
488	) -> u32 {
489		let mut nominator_payout_count: u32 = 0;
490
491		if let Some((amount, dest)) = Self::make_payout_from_provider(era, stash, validator_payout)
492		{
493			Self::deposit_event(Event::<T>::Rewarded { stash: stash.clone(), dest, amount });
494		}
495
496		let total_nominator_stake = exposure.total().saturating_sub(overview_own);
497		for nominator in exposure.others().iter() {
498			let nominator_exposure_part =
499				Perbill::from_rational(nominator.value, total_nominator_stake);
500			let nominator_reward: BalanceOf<T> =
501				nominator_exposure_part.mul_floor(total_nominator_payout);
502
503			if let Some((amount, dest)) =
504				Self::make_payout_from_provider(era, &nominator.who, nominator_reward)
505			{
506				nominator_payout_count.saturating_inc();
507				Self::deposit_event(Event::<T>::Rewarded {
508					stash: nominator.who.clone(),
509					dest,
510					amount,
511				});
512			}
513		}
514
515		nominator_payout_count
516	}
517
518	/// Legacy mint-based payout for pre-upgrade eras.
519	fn payout_legacy_mint(
520		era: EraIndex,
521		stash: &T::AccountId,
522		validator_payout: BalanceOf<T>,
523		exposure: &crate::PagedExposure<T::AccountId, BalanceOf<T>>,
524		overview_own: BalanceOf<T>,
525		total_nominator_payout: BalanceOf<T>,
526	) -> u32 {
527		let mut nominator_payout_count: u32 = 0;
528		let mut total_imbalance = PositiveImbalanceOf::<T>::zero();
529
530		if let Some((imbalance, dest)) = Self::make_payout_legacy(era, stash, validator_payout) {
531			Self::deposit_event(Event::<T>::Rewarded {
532				stash: stash.clone(),
533				dest,
534				amount: imbalance.peek(),
535			});
536			total_imbalance.subsume(imbalance);
537		}
538
539		let total_nominator_stake = exposure.total().saturating_sub(overview_own);
540		for nominator in exposure.others().iter() {
541			let nominator_exposure_part =
542				Perbill::from_rational(nominator.value, total_nominator_stake);
543			let nominator_reward: BalanceOf<T> =
544				nominator_exposure_part.mul_floor(total_nominator_payout);
545
546			if let Some((imbalance, dest)) =
547				Self::make_payout_legacy(era, &nominator.who, nominator_reward)
548			{
549				nominator_payout_count.saturating_inc();
550				Self::deposit_event(Event::<T>::Rewarded {
551					stash: nominator.who.clone(),
552					dest,
553					amount: imbalance.peek(),
554				});
555				total_imbalance.subsume(imbalance);
556			}
557		}
558
559		T::Reward::on_unbalanced(total_imbalance);
560		nominator_payout_count
561	}
562
563	/// Determine the payout account from a reward destination.
564	fn payout_account_for_dest(
565		stash: &T::AccountId,
566		dest: &RewardDestination<T::AccountId>,
567	) -> Option<T::AccountId> {
568		match dest {
569			RewardDestination::Stash | RewardDestination::Staked => Some(stash.clone()),
570			RewardDestination::Account(ref dest_account) => Some(dest_account.clone()),
571			RewardDestination::None => None,
572			#[allow(deprecated)]
573			RewardDestination::Controller => Self::bonded(stash),
574		}
575	}
576
577	/// Make a payment to a staker from an era reward pot (transfer, not mint).
578	fn make_payout_from_provider(
579		era: EraIndex,
580		stash: &T::AccountId,
581		amount: BalanceOf<T>,
582	) -> Option<(BalanceOf<T>, RewardDestination<T::AccountId>)> {
583		if amount.is_zero() {
584			return None;
585		}
586
587		let dest = match Self::payee(Stash(stash.clone())) {
588			Some(d) => d,
589			None => {
590				Self::deposit_event(Event::<T>::Unexpected(UnexpectedKind::MissingPayee {
591					era,
592					stash: stash.clone(),
593				}));
594				return None;
595			},
596		};
597
598		let payout_account = Self::payout_account_for_dest(stash, &dest)?;
599
600		let staker_rewards_pot =
601			T::RewardPots::pot_account(RewardPot::Era(era, RewardKind::StakerRewards));
602		if let Err(e) = T::Currency::transfer(
603			&staker_rewards_pot,
604			&payout_account,
605			amount,
606			Preservation::Expendable,
607		) {
608			log!(
609				error,
610				"Failed to transfer reward from pot for era {:?}, stash {:?}: {:?}",
611				era,
612				stash,
613				e
614			);
615			return None;
616		}
617
618		// For Staked destination, update ledger.
619		if matches!(dest, RewardDestination::Staked) {
620			if let Ok(mut ledger) = Self::ledger(Stash(stash.clone())) {
621				ledger.active += amount;
622				ledger.total += amount;
623				let _ = ledger
624					.update()
625					.defensive_proof("ledger fetched from storage, so it exists; qed.");
626			}
627		}
628
629		Some((amount, dest))
630	}
631
632	/// Legacy: make a payment to a staker by minting new tokens.
633	fn make_payout_legacy(
634		era: EraIndex,
635		stash: &T::AccountId,
636		amount: BalanceOf<T>,
637	) -> Option<(PositiveImbalanceOf<T>, RewardDestination<T::AccountId>)> {
638		if amount.is_zero() {
639			return None;
640		}
641		let dest = match Self::payee(StakingAccount::Stash(stash.clone())) {
642			Some(d) => d,
643			None => {
644				Self::deposit_event(Event::<T>::Unexpected(UnexpectedKind::MissingPayee {
645					era,
646					stash: stash.clone(),
647				}));
648				return None;
649			},
650		};
651
652		let maybe_imbalance = match dest {
653			RewardDestination::Stash => asset::mint_into_existing::<T>(stash, amount),
654			RewardDestination::Staked => Self::ledger(Stash(stash.clone()))
655				.and_then(|mut ledger| {
656					ledger.active += amount;
657					ledger.total += amount;
658					let r = asset::mint_into_existing::<T>(stash, amount);
659					let _ = ledger
660						.update()
661						.defensive_proof("ledger fetched from storage, so it exists; qed.");
662					Ok(r)
663				})
664				.unwrap_or_default(),
665			RewardDestination::Account(ref dest_account) => {
666				Some(asset::mint_creating::<T>(dest_account, amount))
667			},
668			RewardDestination::None => None,
669			#[allow(deprecated)]
670			RewardDestination::Controller => Self::bonded(stash).map(|controller| {
671				defensive!("Paying out controller as reward destination which is deprecated.");
672				asset::mint_creating::<T>(&controller, amount)
673			}),
674		};
675		maybe_imbalance.map(|imbalance| (imbalance, dest))
676	}
677
678	/// Calculate the validator incentive amount for a single page.
679	///
680	/// Computes the validator's slice of the era incentive budget for one payout page:
681	/// `share × budget × page_stake_part`.
682	///
683	/// The share formula is the weighted-points share
684	/// `(weight_stash · ep_stash) / Σ_v(weight_v · ep_v)`, with the denominator read from
685	/// [`ErasSumWeightedPoints`]. Pre-cutoff eras instead use the legacy stake-only share
686	/// `weight_stash / Σ weight`; see [`Eras::uses_weighted_points`] for which eras fall on
687	/// which side and why.
688	///
689	/// Returns `None` if the validator has no incentive weight, no era points, or the
690	/// computed amount rounds to zero.
691	fn calculate_validator_incentive_for_page(
692		era: EraIndex,
693		stash: &T::AccountId,
694		page_stake_part: Perbill,
695		era_reward_points: &EraRewardPoints<T>,
696	) -> Option<BalanceOf<T>> {
697		let era_incentive_budget = Eras::<T>::get_validator_incentive_budget(era);
698		if era_incentive_budget.is_zero() {
699			return None;
700		}
701
702		let validator_weight = match ErasValidatorIncentiveWeight::<T>::get(era, stash) {
703			// No incentive weight (e.g. own-stake was zero at election) means no share.
704			Some(w) if !w.is_zero() => w,
705			_ => return None,
706		};
707
708		// Branch on the cutoff: legacy formula for eras whose denominator was never
709		// maintained, new weighted-points formula otherwise.
710		let share_part = if Eras::<T>::uses_weighted_points(era) {
711			// This validator has non-zero weight (checked above) and reached this point only
712			// with non-zero reward points (gated by the caller), so it must have contributed
713			// to the denominator. A zero denominator with a live budget is therefore a storage
714			// inconsistency and is surfaced rather than silently paying nothing.
715			let sum_weighted_points = ErasSumWeightedPoints::<T>::get(era);
716			if sum_weighted_points.is_zero() {
717				log!(warn, "Sum of weighted points is zero but budget exists for era {}", era);
718				Self::deposit_event(Event::<T>::Unexpected(
719					UnexpectedKind::ValidatorIncentiveWeightMismatch { era },
720				));
721				return None;
722			}
723			let validator_points: RewardPoint =
724				era_reward_points.individual.get(stash).copied().unwrap_or(0);
725			let numerator = validator_weight.saturating_mul(BalanceOf::<T>::from(validator_points));
726			Perbill::from_rational(numerator, sum_weighted_points)
727		} else {
728			// Legacy stake-only share, denominated by the total incentive weight across all
729			// elected validators. A zero denominator with a non-zero budget is a storage
730			// inconsistency, so it is surfaced rather than silently paying nothing.
731			let total_weight = ErasSumValidatorIncentiveWeight::<T>::get(era);
732			if total_weight.is_zero() {
733				log!(
734					warn,
735					"Total validator incentive weight is zero but budget exists for era {}",
736					era
737				);
738				Self::deposit_event(Event::<T>::Unexpected(
739					UnexpectedKind::ValidatorIncentiveWeightMismatch { era },
740				));
741				return None;
742			}
743			Perbill::from_rational(validator_weight, total_weight)
744		};
745
746		if share_part.is_zero() {
747			return None;
748		}
749
750		let validator_total_incentive = share_part.mul_floor(era_incentive_budget);
751		let validator_incentive_for_page = page_stake_part.mul_floor(validator_total_incentive);
752
753		if validator_incentive_for_page.is_zero() {
754			return None;
755		}
756
757		Some(validator_incentive_for_page)
758	}
759
760	/// Transfer validator incentive from era pot to the validator's payout account.
761	///
762	/// This is a direct liquid transfer. Future PRs may introduce vesting via a trait.
763	fn transfer_validator_incentive(era: EraIndex, stash: &T::AccountId, amount: BalanceOf<T>) {
764		let Some(dest) = Self::payee(Stash(stash.clone())) else {
765			Self::deposit_event(Event::<T>::Unexpected(UnexpectedKind::MissingPayee {
766				era,
767				stash: stash.clone(),
768			}));
769			return;
770		};
771		let Some(payout_account) = Self::payout_account_for_dest(stash, &dest) else {
772			// Destination is `None`; intentional opt-out.
773			return;
774		};
775
776		let incentive_pot = T::RewardPots::pot_account(crate::RewardPot::Era(
777			era,
778			crate::RewardKind::ValidatorSelfStake,
779		));
780
781		match T::Currency::transfer(
782			&incentive_pot,
783			&payout_account,
784			amount,
785			Preservation::Expendable,
786		) {
787			Ok(_) => {
788				Self::deposit_event(Event::<T>::ValidatorIncentivePaid {
789					era,
790					validator_stash: stash.clone(),
791					dest,
792					amount,
793				});
794			},
795			Err(e) => {
796				log!(warn, "Failed to transfer liquid incentive: {:?}", e);
797				Self::deposit_event(Event::<T>::Unexpected(
798					UnexpectedKind::ValidatorIncentiveTransferFailed { era },
799				));
800				defensive!("Validator incentive liquid transfer failed");
801			},
802		}
803	}
804
805	/// Chill a stash account.
806	pub(crate) fn chill_stash(stash: &T::AccountId) {
807		let chilled_as_validator = Self::do_remove_validator(stash);
808		let chilled_as_nominator = Self::do_remove_nominator(stash);
809		if chilled_as_validator || chilled_as_nominator {
810			Self::deposit_event(Event::<T>::Chilled { stash: stash.clone() });
811		}
812	}
813
814	/// Remove all associated data of a stash account from the staking system.
815	///
816	/// Assumes storage is upgraded before calling.
817	///
818	/// This is called:
819	/// - after a `withdraw_unbonded()` call that frees all of a stash's bonded balance.
820	/// - through `reap_stash()` if the balance has fallen below the existential deposit (through
821	///   slashing or full unbond).
822	pub(crate) fn kill_stash(stash: &T::AccountId) -> DispatchResult {
823		// removes controller from `Bonded` and staking ledger from `Ledger`, as well as reward
824		// setting of the stash in `Payee`.
825		StakingLedger::<T>::kill(&stash)?;
826
827		Self::do_remove_validator(&stash);
828		Self::do_remove_nominator(&stash);
829
830		// Clean up validator history tracking.
831		LastValidatorEra::<T>::remove(&stash);
832
833		Ok(())
834	}
835
836	#[cfg(test)]
837	pub(crate) fn reward_by_ids(validators_points: impl IntoIterator<Item = (T::AccountId, u32)>) {
838		Eras::<T>::reward_active_era(validators_points)
839	}
840
841	/// Helper to set a new `ForceEra` mode.
842	pub(crate) fn set_force_era(mode: Forcing) {
843		log!(info, "Setting force era mode {:?}.", mode);
844		ForceEra::<T>::put(mode);
845		Self::deposit_event(Event::<T>::ForceEra { mode });
846	}
847
848	#[cfg(feature = "runtime-benchmarks")]
849	pub fn add_era_stakers(
850		current_era: EraIndex,
851		stash: T::AccountId,
852		exposure: Exposure<T::AccountId, BalanceOf<T>>,
853	) {
854		Eras::<T>::upsert_exposure(current_era, &stash, exposure);
855	}
856
857	#[cfg(feature = "runtime-benchmarks")]
858	pub fn set_slash_reward_fraction(fraction: Perbill) {
859		SlashRewardFraction::<T>::put(fraction);
860	}
861
862	/// Get all the voters associated with `page` that are eligible for the npos election.
863	///
864	/// `bounds` can impose a cap on the number of voters returned per page.
865	///
866	/// Sets `MinimumActiveStake` to the minimum active nominator stake in the returned set of
867	/// nominators.
868	///
869	/// Note: in the context of the multi-page snapshot, we expect the *order* of `VoterList` and
870	/// `TargetList` not to change while the pages are being processed.
871	pub(crate) fn get_npos_voters(
872		bounds: DataProviderBounds,
873		status: &SnapshotStatus<T::AccountId>,
874	) -> Vec<VoterOf<Self>> {
875		let mut voters_size_tracker: StaticTracker<Self> = StaticTracker::default();
876
877		let page_len_prediction = {
878			let all_voter_count = T::VoterList::count();
879			bounds.count.unwrap_or(all_voter_count.into()).min(all_voter_count.into()).0
880		};
881
882		let mut all_voters = Vec::<_>::with_capacity(page_len_prediction as usize);
883
884		// cache a few things.
885		let weight_of = Self::weight_of_fn();
886
887		let mut voters_seen = 0u32;
888		let mut validators_taken = 0u32;
889		let mut nominators_taken = 0u32;
890		let mut min_active_stake = u64::MAX;
891
892		let mut sorted_voters = match status {
893			// start the snapshot processing from the beginning.
894			SnapshotStatus::Waiting => T::VoterList::iter(),
895			// snapshot continues, start from the last iterated voter in the list.
896			SnapshotStatus::Ongoing(account_id) => T::VoterList::iter_from(&account_id)
897				.defensive_unwrap_or(Box::new(vec![].into_iter())),
898			// all voters have been consumed already, return an empty iterator.
899			SnapshotStatus::Consumed => Box::new(vec![].into_iter()),
900		};
901
902		while all_voters.len() < page_len_prediction as usize &&
903			voters_seen < (NPOS_MAX_ITERATIONS_COEFFICIENT * page_len_prediction as u32)
904		{
905			let voter = match sorted_voters.next() {
906				Some(voter) => {
907					voters_seen.saturating_inc();
908					voter
909				},
910				None => break,
911			};
912
913			let voter_weight = weight_of(&voter);
914			// if voter weight is zero, do not consider this voter for the snapshot.
915			if voter_weight.is_zero() {
916				log!(debug, "voter's active balance is 0. skip this voter.");
917				continue;
918			}
919
920			if let Some(Nominations { targets, .. }) = <Nominators<T>>::get(&voter) {
921				if !targets.is_empty() {
922					// Note on lazy nomination quota: we do not check the nomination quota of the
923					// voter at this point and accept all the current nominations. The nomination
924					// quota is only enforced at `nominate` time.
925
926					let voter = (voter, voter_weight, targets);
927					if voters_size_tracker.try_register_voter(&voter, &bounds).is_err() {
928						// no more space left for the election result, stop iterating.
929						Self::deposit_event(Event::<T>::SnapshotVotersSizeExceeded {
930							size: voters_size_tracker.size as u32,
931						});
932						break;
933					}
934
935					all_voters.push(voter);
936					nominators_taken.saturating_inc();
937				} else {
938					defensive!("non-nominator fetched from voter list: {:?}", voter);
939					// technically should never happen, but not much we can do about it.
940				}
941				min_active_stake =
942					if voter_weight < min_active_stake { voter_weight } else { min_active_stake };
943			} else if Validators::<T>::contains_key(&voter) {
944				// if this voter is a validator:
945				let self_vote = (
946					voter.clone(),
947					voter_weight,
948					vec![voter.clone()]
949						.try_into()
950						.expect("`MaxVotesPerVoter` must be greater than or equal to 1"),
951				);
952
953				if voters_size_tracker.try_register_voter(&self_vote, &bounds).is_err() {
954					// no more space left for the election snapshot, stop iterating.
955					Self::deposit_event(Event::<T>::SnapshotVotersSizeExceeded {
956						size: voters_size_tracker.size as u32,
957					});
958					break;
959				}
960				all_voters.push(self_vote);
961				validators_taken.saturating_inc();
962			} else {
963				// this can only happen if: 1. there a bug in the bags-list (or whatever is the
964				// sorted list) logic and the state of the two pallets is no longer compatible, or
965				// because the nominators is not decodable since they have more nomination than
966				// `T::NominationsQuota::get_quota`. The latter can rarely happen, and is not
967				// really an emergency or bug if it does.
968				defensive!(
969				    "invalid item in `VoterList`: {:?}, this nominator probably has too many nominations now",
970                    voter,
971                );
972			}
973		}
974
975		// all_voters should have not re-allocated.
976		debug_assert!(all_voters.capacity() == page_len_prediction as usize);
977
978		let min_active_stake: T::CurrencyBalance =
979			if all_voters.is_empty() { Zero::zero() } else { min_active_stake.into() };
980
981		MinimumActiveStake::<T>::put(min_active_stake);
982
983		all_voters
984	}
985
986	/// Get all the targets associated are eligible for the npos election.
987	///
988	/// The target snapshot is *always* single paged.
989	///
990	/// This function is self-weighing as [`DispatchClass::Mandatory`].
991	pub fn get_npos_targets(bounds: DataProviderBounds) -> Vec<T::AccountId> {
992		let mut targets_size_tracker: StaticTracker<Self> = StaticTracker::default();
993
994		let final_predicted_len = {
995			let all_target_count = T::TargetList::count();
996			bounds.count.unwrap_or(all_target_count.into()).min(all_target_count.into()).0
997		};
998
999		let mut all_targets = Vec::<T::AccountId>::with_capacity(final_predicted_len as usize);
1000		let mut targets_seen = 0;
1001
1002		let mut targets_iter = T::TargetList::iter();
1003		while all_targets.len() < final_predicted_len as usize &&
1004			targets_seen < (NPOS_MAX_ITERATIONS_COEFFICIENT * final_predicted_len as u32)
1005		{
1006			let target = match targets_iter.next() {
1007				Some(target) => {
1008					targets_seen.saturating_inc();
1009					target
1010				},
1011				None => break,
1012			};
1013
1014			if targets_size_tracker.try_register_target(target.clone(), &bounds).is_err() {
1015				// no more space left for the election snapshot, stop iterating.
1016				log!(warn, "npos targets size exceeded, stopping iteration.");
1017				Self::deposit_event(Event::<T>::SnapshotTargetsSizeExceeded {
1018					size: targets_size_tracker.size as u32,
1019				});
1020				break;
1021			}
1022
1023			if Validators::<T>::contains_key(&target) {
1024				all_targets.push(target);
1025			}
1026		}
1027
1028		log!(debug, "[bounds {:?}] generated {} npos targets", bounds, all_targets.len());
1029
1030		all_targets
1031	}
1032
1033	/// This function will add a nominator to the `Nominators` storage map,
1034	/// and `VoterList`.
1035	///
1036	/// If the nominator already exists, their nominations will be updated.
1037	///
1038	/// NOTE: you must ALWAYS use this function to add nominator or update their targets. Any access
1039	/// to `Nominators` or `VoterList` outside of this function is almost certainly
1040	/// wrong.
1041	pub fn do_add_nominator(who: &T::AccountId, nominations: Nominations<T>) {
1042		if !Nominators::<T>::contains_key(who) {
1043			// maybe update sorted list.
1044			let _ = T::VoterList::on_insert(who.clone(), Self::weight_of(who))
1045				.defensive_unwrap_or_default();
1046		}
1047		Nominators::<T>::insert(who, nominations);
1048	}
1049
1050	/// This function will remove a nominator from the `Nominators` storage map,
1051	/// and `VoterList`.
1052	///
1053	/// Returns true if `who` was removed from `Nominators`, otherwise false.
1054	///
1055	/// NOTE: you must ALWAYS use this function to remove a nominator from the system. Any access to
1056	/// `Nominators` or `VoterList` outside of this function is almost certainly
1057	/// wrong.
1058	pub fn do_remove_nominator(who: &T::AccountId) -> bool {
1059		let outcome = if Nominators::<T>::contains_key(who) {
1060			Nominators::<T>::remove(who);
1061			let _ = T::VoterList::on_remove(who);
1062			true
1063		} else {
1064			false
1065		};
1066
1067		outcome
1068	}
1069
1070	/// This function will add a validator to the `Validators` storage map.
1071	///
1072	/// If the validator already exists, their preferences will be updated.
1073	///
1074	/// NOTE: you must ALWAYS use this function to add a validator to the system. Any access to
1075	/// `Validators` or `VoterList` outside of this function is almost certainly
1076	/// wrong.
1077	pub fn do_add_validator(who: &T::AccountId, prefs: ValidatorPrefs) {
1078		if !Validators::<T>::contains_key(who) {
1079			// maybe update sorted list.
1080			let _ = T::VoterList::on_insert(who.clone(), Self::weight_of(who));
1081		}
1082		Validators::<T>::insert(who, prefs);
1083	}
1084
1085	/// This function will remove a validator from the `Validators` storage map.
1086	///
1087	/// Returns true if `who` was removed from `Validators`, otherwise false.
1088	///
1089	/// NOTE: you must ALWAYS use this function to remove a validator from the system. Any access to
1090	/// `Validators` or `VoterList` outside of this function is almost certainly
1091	/// wrong.
1092	pub fn do_remove_validator(who: &T::AccountId) -> bool {
1093		let outcome = if Validators::<T>::contains_key(who) {
1094			Validators::<T>::remove(who);
1095			let _ = T::VoterList::on_remove(who);
1096			true
1097		} else {
1098			false
1099		};
1100
1101		outcome
1102	}
1103
1104	/// Register some amount of weight directly with the system pallet.
1105	///
1106	/// This is always mandatory weight.
1107	pub(crate) fn register_weight(weight: Weight) {
1108		<frame_system::Pallet<T>>::register_extra_weight_unchecked(
1109			weight,
1110			DispatchClass::Mandatory,
1111		);
1112	}
1113
1114	/// Returns full exposure of a validator for a given era.
1115	///
1116	/// History note: This used to be a getter for old storage item `ErasStakers` deprecated in v14
1117	/// and deleted in v17. Since this function is used in the codebase at various places, we kept
1118	/// it as a custom getter that takes care of getting the full exposure of the validator in a
1119	/// backward compatible way.
1120	pub fn eras_stakers(
1121		era: EraIndex,
1122		account: &T::AccountId,
1123	) -> Exposure<T::AccountId, BalanceOf<T>> {
1124		Eras::<T>::get_full_exposure(era, account)
1125	}
1126
1127	pub(super) fn do_migrate_currency(stash: &T::AccountId) -> DispatchResult {
1128		if Self::is_virtual_staker(stash) {
1129			return Self::do_migrate_virtual_staker(stash);
1130		}
1131
1132		let ledger = Self::ledger(Stash(stash.clone()))?;
1133		let staked: BalanceOf<T> = T::OldCurrency::balance_locked(STAKING_ID, stash).into();
1134		ensure!(!staked.is_zero(), Error::<T>::AlreadyMigrated);
1135		ensure!(ledger.total == staked, Error::<T>::BadState);
1136
1137		// remove old staking lock
1138		T::OldCurrency::remove_lock(STAKING_ID, &stash);
1139
1140		// check if we can hold all stake.
1141		let max_hold = asset::free_to_stake::<T>(&stash);
1142		let force_withdraw = if max_hold >= staked {
1143			// this means we can hold all stake. yay!
1144			asset::update_stake::<T>(&stash, staked)?;
1145			Zero::zero()
1146		} else {
1147			// if we are here, it means we cannot hold all user stake. We will do a force withdraw
1148			// from ledger, but that's okay since anyways user do not have funds for it.
1149			let force_withdraw = staked.saturating_sub(max_hold);
1150
1151			// we ignore if active is 0. It implies the locked amount is not actively staked. The
1152			// account can still get away from potential slash but we can't do much better here.
1153			StakingLedger {
1154				total: max_hold,
1155				active: ledger.active.saturating_sub(force_withdraw),
1156				// we are not changing the stash, so we can keep the stash.
1157				..ledger
1158			}
1159			.update()?;
1160			force_withdraw
1161		};
1162
1163		// Get rid of the extra consumer we used to have with OldCurrency.
1164		frame_system::Pallet::<T>::dec_consumers(&stash);
1165
1166		Self::deposit_event(Event::<T>::CurrencyMigrated { stash: stash.clone(), force_withdraw });
1167		Ok(())
1168	}
1169
1170	fn do_migrate_virtual_staker(stash: &T::AccountId) -> DispatchResult {
1171		// Funds for virtual stakers not managed/held by this pallet. We only need to clear
1172		// the extra consumer we used to have with OldCurrency.
1173		frame_system::Pallet::<T>::dec_consumers(&stash);
1174
1175		// The delegation system that manages the virtual staker needed to increment provider
1176		// previously because of the consumer needed by this pallet. In reality, this stash
1177		// is just a key for managing the ledger and the account does not need to hold any
1178		// balance or exist. We decrement this provider.
1179		let actual_providers = frame_system::Pallet::<T>::providers(stash);
1180
1181		let expected_providers =
1182			// provider is expected to be 1 but someone can always transfer some free funds to
1183			// these accounts, increasing the provider.
1184			if asset::free_to_stake::<T>(&stash) >= asset::existential_deposit::<T>() {
1185				2
1186			} else {
1187				1
1188			};
1189
1190		// We should never have more than expected providers.
1191		ensure!(actual_providers <= expected_providers, Error::<T>::BadState);
1192
1193		// if actual provider is less than expected, it is already migrated.
1194		ensure!(actual_providers == expected_providers, Error::<T>::AlreadyMigrated);
1195
1196		// dec provider
1197		let _ = frame_system::Pallet::<T>::dec_providers(&stash)?;
1198
1199		return Ok(());
1200	}
1201}
1202
1203impl<T: Config> Pallet<T> {
1204	/// Returns the current nominations quota for nominators.
1205	///
1206	/// Used by the runtime API.
1207	pub fn api_nominations_quota(balance: BalanceOf<T>) -> u32 {
1208		T::NominationsQuota::get_quota(balance)
1209	}
1210
1211	pub fn api_eras_stakers(
1212		era: EraIndex,
1213		account: T::AccountId,
1214	) -> Exposure<T::AccountId, BalanceOf<T>> {
1215		Self::eras_stakers(era, &account)
1216	}
1217
1218	pub fn api_eras_stakers_page_count(era: EraIndex, account: T::AccountId) -> Page {
1219		Eras::<T>::exposure_page_count(era, &account)
1220	}
1221
1222	pub fn api_pending_rewards(era: EraIndex, account: T::AccountId) -> bool {
1223		Eras::<T>::pending_rewards(era, &account)
1224	}
1225}
1226
1227impl<T: Config> ElectionDataProvider for Pallet<T> {
1228	type AccountId = T::AccountId;
1229	type BlockNumber = BlockNumberFor<T>;
1230	type MaxVotesPerVoter = MaxNominationsOf<T>;
1231
1232	fn desired_targets() -> data_provider::Result<u32> {
1233		Self::register_weight(T::DbWeight::get().reads(1));
1234		Ok(ValidatorCount::<T>::get())
1235	}
1236
1237	fn electing_voters(
1238		bounds: DataProviderBounds,
1239		page: PageIndex,
1240	) -> data_provider::Result<Vec<VoterOf<Self>>> {
1241		let mut status = VoterSnapshotStatus::<T>::get();
1242		let voters = Self::get_npos_voters(bounds, &status);
1243
1244		// update the voter snapshot status.
1245		match (page, &status) {
1246			// last page, reset status for next round.
1247			(0, _) => status = SnapshotStatus::Waiting,
1248
1249			(_, SnapshotStatus::Waiting) | (_, SnapshotStatus::Ongoing(_)) => {
1250				let maybe_last = voters.last().map(|(x, _, _)| x).cloned();
1251
1252				if let Some(ref last) = maybe_last {
1253					let has_next =
1254						T::VoterList::iter_from(last).ok().and_then(|mut i| i.next()).is_some();
1255					if has_next {
1256						status = SnapshotStatus::Ongoing(last.clone());
1257					} else {
1258						status = SnapshotStatus::Consumed;
1259					}
1260				}
1261			},
1262			// do nothing.
1263			(_, SnapshotStatus::Consumed) => (),
1264		}
1265
1266		log!(
1267			debug,
1268			"[page {}, (next) status {:?}, bounds {:?}] generated {} npos voters [first: {:?}, last: {:?}]",
1269			page,
1270			status,
1271			bounds,
1272			voters.len(),
1273			voters.first().map(|(x, y, _)| (x, y)),
1274			voters.last().map(|(x, y, _)| (x, y)),
1275		);
1276
1277		match status {
1278			SnapshotStatus::Ongoing(_) => T::VoterList::lock(),
1279			_ => T::VoterList::unlock(),
1280		}
1281
1282		VoterSnapshotStatus::<T>::put(status);
1283		debug_assert!(!bounds.slice_exhausted(&voters));
1284
1285		Ok(voters)
1286	}
1287
1288	fn electing_voters_stateless(
1289		bounds: DataProviderBounds,
1290	) -> data_provider::Result<Vec<VoterOf<Self>>> {
1291		let voters = Self::get_npos_voters(bounds, &SnapshotStatus::Waiting);
1292		log!(debug, "[stateless, bounds {:?}] generated {} npos voters", bounds, voters.len(),);
1293		Ok(voters)
1294	}
1295
1296	fn electable_targets(
1297		bounds: DataProviderBounds,
1298		page: PageIndex,
1299	) -> data_provider::Result<Vec<T::AccountId>> {
1300		if page > 0 {
1301			log!(warn, "multi-page target snapshot not supported, returning page 0.");
1302		}
1303
1304		let targets = Self::get_npos_targets(bounds);
1305		if bounds.exhausted(None, CountBound(targets.len() as u32).into()) {
1306			return Err("Target snapshot too big");
1307		}
1308
1309		debug_assert!(!bounds.slice_exhausted(&targets));
1310
1311		Ok(targets)
1312	}
1313
1314	fn next_election_prediction(_: BlockNumberFor<T>) -> BlockNumberFor<T> {
1315		debug_assert!(false, "this is deprecated and not used anymore");
1316		sp_runtime::traits::Bounded::max_value()
1317	}
1318
1319	#[cfg(feature = "runtime-benchmarks")]
1320	fn fetch_page(page: PageIndex) {
1321		session_rotation::EraElectionPlanner::<T>::do_elect_paged(page);
1322	}
1323
1324	#[cfg(feature = "runtime-benchmarks")]
1325	fn add_voter(
1326		voter: T::AccountId,
1327		weight: VoteWeight,
1328		targets: BoundedVec<T::AccountId, Self::MaxVotesPerVoter>,
1329	) {
1330		let stake = <BalanceOf<T>>::try_from(weight).unwrap_or_else(|_| {
1331			panic!("cannot convert a VoteWeight into BalanceOf, benchmark needs reconfiguring.")
1332		});
1333		<Bonded<T>>::insert(voter.clone(), voter.clone());
1334		<Ledger<T>>::insert(voter.clone(), StakingLedger::<T>::new(voter.clone(), stake));
1335
1336		Self::do_add_nominator(&voter, Nominations { targets, submitted_in: 0, suppressed: false });
1337	}
1338
1339	#[cfg(feature = "runtime-benchmarks")]
1340	fn add_target(target: T::AccountId) {
1341		let stake = (Self::min_validator_bond() + 1u32.into()) * 100u32.into();
1342		<Bonded<T>>::insert(target.clone(), target.clone());
1343		<Ledger<T>>::insert(target.clone(), StakingLedger::<T>::new(target.clone(), stake));
1344		Self::do_add_validator(
1345			&target,
1346			ValidatorPrefs { commission: Perbill::zero(), blocked: false },
1347		);
1348	}
1349
1350	#[cfg(feature = "runtime-benchmarks")]
1351	fn clear() {
1352		#[allow(deprecated)]
1353		<Bonded<T>>::remove_all(None);
1354		#[allow(deprecated)]
1355		<Ledger<T>>::remove_all(None);
1356		#[allow(deprecated)]
1357		<Validators<T>>::remove_all();
1358		#[allow(deprecated)]
1359		<Nominators<T>>::remove_all();
1360
1361		T::VoterList::unsafe_clear();
1362	}
1363
1364	#[cfg(feature = "runtime-benchmarks")]
1365	fn put_snapshot(
1366		voters: Vec<VoterOf<Self>>,
1367		targets: Vec<T::AccountId>,
1368		target_stake: Option<VoteWeight>,
1369	) {
1370		targets.into_iter().for_each(|v| {
1371			let stake: BalanceOf<T> = target_stake
1372				.and_then(|w| <BalanceOf<T>>::try_from(w).ok())
1373				.unwrap_or_else(|| Self::min_nominator_bond() * 100u32.into());
1374			<Bonded<T>>::insert(v.clone(), v.clone());
1375			<Ledger<T>>::insert(v.clone(), StakingLedger::<T>::new(v.clone(), stake));
1376			Self::do_add_validator(
1377				&v,
1378				ValidatorPrefs { commission: Perbill::zero(), blocked: false },
1379			);
1380		});
1381
1382		voters.into_iter().for_each(|(v, s, t)| {
1383			let stake = <BalanceOf<T>>::try_from(s).unwrap_or_else(|_| {
1384				panic!("cannot convert a VoteWeight into BalanceOf, benchmark needs reconfiguring.")
1385			});
1386			<Bonded<T>>::insert(v.clone(), v.clone());
1387			<Ledger<T>>::insert(v.clone(), StakingLedger::<T>::new(v.clone(), stake));
1388			Self::do_add_nominator(
1389				&v,
1390				Nominations { targets: t, submitted_in: 0, suppressed: false },
1391			);
1392		});
1393	}
1394
1395	#[cfg(feature = "runtime-benchmarks")]
1396	fn set_desired_targets(count: u32) {
1397		ValidatorCount::<T>::put(count);
1398	}
1399}
1400
1401impl<T: Config> rc_client::AHStakingInterface for Pallet<T> {
1402	type AccountId = T::AccountId;
1403	type MaxValidatorSet = T::MaxValidatorSet;
1404
1405	/// When we receive a session report from the relay chain, it kicks off the next session.
1406	///
1407	/// There are three special types of things we can do in a session:
1408	/// 1. Plan a new era: We do this one session before the expected era rotation.
1409	/// 2. Kick off election: We do this based on the [`Config::PlanningEraOffset`] configuration.
1410	/// 3. Activate Next Era: When we receive an activation timestamp in the session report, it
1411	/// implies a new validator set has been applied, and we must increment the active era to keep
1412	/// the systems in sync.
1413	fn on_relay_session_report(report: rc_client::SessionReport<Self::AccountId>) -> Weight {
1414		log!(debug, "Received session report: {}", report,);
1415
1416		let rc_client::SessionReport {
1417			end_index,
1418			activation_timestamp,
1419			validator_points,
1420			leftover,
1421		} = report;
1422		debug_assert!(!leftover);
1423
1424		let validator_count = validator_points.len() as u32;
1425		// note: weight for `reward_active_era` is taken care of inside `end_session`
1426		Eras::<T>::reward_active_era(validator_points.into_iter());
1427		session_rotation::Rotator::<T>::end_session(
1428			end_index,
1429			activation_timestamp,
1430			validator_count,
1431		)
1432	}
1433
1434	fn weigh_on_relay_session_report(report: &rc_client::SessionReport<Self::AccountId>) -> Weight {
1435		T::WeightInfo::rc_on_session_report(report.validator_points.len() as u32)
1436	}
1437
1438	/// Accepts offences only if they are from era `active_era - (SlashDeferDuration - 1)` or newer.
1439	///
1440	/// Slashes for offences are applied `SlashDeferDuration` eras after the offence occurred.
1441	/// Accepting offences older than this range would not leave enough time for slashes to be
1442	/// applied.
1443	///
1444	/// Note: The validator set report that we send to the relay chain contains the pruning
1445	/// information for a relay chain, but we conservatively keep some extra sessions, so it is
1446	/// possible that an offence report is created for a session between SlashDeferDuration and
1447	/// BondingDuration eras before the active era. But they will be dropped here.
1448	fn on_new_offences(
1449		slash_session: SessionIndex,
1450		offences: Vec<rc_client::Offence<T::AccountId>>,
1451	) -> Weight {
1452		log!(debug, "🦹 on_new_offences: {:?}", offences);
1453		let weight = T::WeightInfo::rc_on_offence(offences.len() as u32);
1454
1455		// Find the era to which offence belongs.
1456		let Some(active_era) = ActiveEra::<T>::get() else {
1457			log!(warn, "🦹 on_new_offences: no active era; ignoring offence");
1458			return T::WeightInfo::rc_on_offence(0);
1459		};
1460
1461		let active_era_start_session = Rotator::<T>::active_era_start_session_index();
1462
1463		// Fast path for active-era report - most likely.
1464		// `slash_session` cannot be in a future active era. It must be in `active_era` or before.
1465		let offence_era = if slash_session >= active_era_start_session {
1466			active_era.index
1467		} else {
1468			match BondedEras::<T>::get()
1469				.iter()
1470				// Reverse because it's more likely to find reports from recent eras.
1471				.rev()
1472				.find_map(|&(era, sesh)| if sesh <= slash_session { Some(era) } else { None })
1473			{
1474				Some(era) => era,
1475				None => {
1476					// defensive: this implies offence is for a discarded era, and should already be
1477					// filtered out.
1478					log!(warn, "🦹 on_offence: no era found for slash_session; ignoring offence");
1479					return T::WeightInfo::rc_on_offence(0);
1480				},
1481			}
1482		};
1483
1484		let oldest_reportable_offence_era = if T::SlashDeferDuration::get() == 0 {
1485			// this implies that slashes are applied immediately, so we can accept any offence up to
1486			// bonding duration old.
1487			// Align with the SlashDeferDuration > 0 branch: accept offences from at most
1488			// BondingDuration - 1 distinct eras, ensuring the count fits within the
1489			// OffenceQueueEras bound.
1490			active_era.index.saturating_sub(T::BondingDuration::get().saturating_sub(2))
1491		} else {
1492			// slashes are deffered, so we only accept offences that are not older than the
1493			// defferal duration.
1494			active_era.index.saturating_sub(T::SlashDeferDuration::get().saturating_sub(1))
1495		};
1496
1497		for o in offences {
1498			let slash_fraction = o.slash_fraction;
1499			let validator: <T as frame_system::Config>::AccountId = o.offender.into();
1500
1501			// ignore offence if too old to report.
1502			if offence_era < oldest_reportable_offence_era {
1503				log!(warn, "🦹 on_new_offences: offence era {:?} too old; Can only accept offences from era {:?} or newer", offence_era, oldest_reportable_offence_era);
1504				Self::deposit_event(Event::<T>::OffenceTooOld {
1505					validator: validator.clone(),
1506					fraction: slash_fraction,
1507					offence_era,
1508				});
1509				// will emit an event for each validator in the report.
1510				continue;
1511			}
1512			let Some(exposure_overview) = <ErasStakersOverview<T>>::get(&offence_era, &validator)
1513			else {
1514				// defensive: this implies offence is for a discarded era, and should already be
1515				// filtered out.
1516				log!(
1517					warn,
1518					"🦹 on_offence: no exposure found for {:?} in era {}; ignoring offence",
1519					validator,
1520					offence_era
1521				);
1522				continue;
1523			};
1524
1525			Self::deposit_event(Event::<T>::OffenceReported {
1526				validator: validator.clone(),
1527				fraction: slash_fraction,
1528				offence_era,
1529			});
1530
1531			let prior_slash_fraction = ValidatorSlashInEra::<T>::get(offence_era, &validator)
1532				.map_or(Zero::zero(), |(f, _)| f);
1533
1534			if let Some(existing) = OffenceQueue::<T>::get(offence_era, &validator) {
1535				if slash_fraction.deconstruct() > existing.slash_fraction.deconstruct() {
1536					OffenceQueue::<T>::insert(
1537						offence_era,
1538						&validator,
1539						OffenceRecord {
1540							reporter: o.reporters.first().cloned(),
1541							reported_era: active_era.index,
1542							slash_fraction,
1543							..existing
1544						},
1545					);
1546
1547					// update the slash fraction in the `ValidatorSlashInEra` storage.
1548					ValidatorSlashInEra::<T>::insert(
1549						offence_era,
1550						&validator,
1551						(slash_fraction, exposure_overview.own),
1552					);
1553
1554					log!(
1555						debug,
1556						"🦹 updated slash for {:?}: {:?} (prior: {:?})",
1557						validator,
1558						slash_fraction,
1559						prior_slash_fraction,
1560					);
1561				} else {
1562					log!(
1563						debug,
1564						"🦹 ignored slash for {:?}: {:?} (existing prior is larger: {:?})",
1565						validator,
1566						slash_fraction,
1567						prior_slash_fraction,
1568					);
1569				}
1570			} else if slash_fraction.deconstruct() > prior_slash_fraction.deconstruct() {
1571				ValidatorSlashInEra::<T>::insert(
1572					offence_era,
1573					&validator,
1574					(slash_fraction, exposure_overview.own),
1575				);
1576
1577				OffenceQueue::<T>::insert(
1578					offence_era,
1579					&validator,
1580					OffenceRecord {
1581						reporter: o.reporters.first().cloned(),
1582						reported_era: active_era.index,
1583						// there are cases of validator with no exposure, hence 0 page, so we
1584						// saturate to avoid underflow.
1585						exposure_page: exposure_overview.page_count.saturating_sub(1),
1586						slash_fraction,
1587						prior_slash_fraction,
1588					},
1589				);
1590
1591				OffenceQueueEras::<T>::mutate(|q| {
1592					if let Some(eras) = q {
1593						log!(debug, "🦹 inserting offence era {} into existing queue", offence_era);
1594						eras.binary_search(&offence_era).err().map(|idx| {
1595							eras.try_insert(idx, offence_era).defensive_proof(
1596								"Offence era must be present in the existing queue",
1597							)
1598						});
1599					} else {
1600						let mut eras = WeakBoundedVec::default();
1601						log!(debug, "🦹 inserting offence era {} into empty queue", offence_era);
1602						let _ = eras
1603							.try_push(offence_era)
1604							.defensive_proof("Failed to push offence era into empty queue");
1605						*q = Some(eras);
1606					}
1607				});
1608
1609				log!(
1610					debug,
1611					"🦹 queued slash for {:?}: {:?} (prior: {:?})",
1612					validator,
1613					slash_fraction,
1614					prior_slash_fraction,
1615				);
1616			} else {
1617				log!(
1618					debug,
1619					"🦹 ignored slash for {:?}: {:?} (already slashed in era with prior: {:?})",
1620					validator,
1621					slash_fraction,
1622					prior_slash_fraction,
1623				);
1624			}
1625		}
1626
1627		weight
1628	}
1629
1630	fn weigh_on_new_offences(offence_count: u32) -> Weight {
1631		T::WeightInfo::rc_on_offence(offence_count)
1632	}
1633
1634	fn active_era_start_session_index() -> SessionIndex {
1635		Rotator::<T>::active_era_start_session_index()
1636	}
1637
1638	fn is_validator(who: &Self::AccountId) -> bool {
1639		Validators::<T>::contains_key(who)
1640	}
1641}
1642
1643impl<T: Config> ScoreProvider<T::AccountId> for Pallet<T> {
1644	type Score = VoteWeight;
1645
1646	fn score(who: &T::AccountId) -> Option<Self::Score> {
1647		Self::ledger(Stash(who.clone()))
1648			.ok()
1649			.and_then(|l| {
1650				if Nominators::<T>::contains_key(&l.stash) ||
1651					Validators::<T>::contains_key(&l.stash)
1652				{
1653					Some(l.active)
1654				} else {
1655					None
1656				}
1657			})
1658			.map(|a| {
1659				let issuance = asset::total_issuance::<T>();
1660				T::CurrencyToVote::to_vote(a, issuance)
1661			})
1662	}
1663
1664	#[cfg(feature = "runtime-benchmarks")]
1665	fn set_score_of(who: &T::AccountId, weight: Self::Score) {
1666		// this will clearly results in an inconsistent state, but it should not matter for a
1667		// benchmark.
1668		let active: BalanceOf<T> = weight.try_into().map_err(|_| ()).unwrap();
1669		let mut ledger = match Self::ledger(StakingAccount::Stash(who.clone())) {
1670			Ok(l) => l,
1671			Err(_) => StakingLedger::default_from(who.clone()),
1672		};
1673		ledger.active = active;
1674
1675		<Ledger<T>>::insert(who, ledger);
1676		<Bonded<T>>::insert(who, who);
1677		// we also need to appoint this staker to be validator or nominator, such that their score
1678		// is actually there. Note that `fn score` above checks the role.
1679		<Validators<T>>::insert(who, ValidatorPrefs::default());
1680
1681		// also, we play a trick to make sure that a issuance based-`CurrencyToVote` behaves well:
1682		// This will make sure that total issuance is zero, thus the currency to vote will be a 1-1
1683		// conversion.
1684		let imbalance = asset::burn::<T>(asset::total_issuance::<T>());
1685		// kinda ugly, but gets the job done. The fact that this works here is a HUGE exception.
1686		// Don't try this pattern in other places.
1687		core::mem::forget(imbalance);
1688	}
1689}
1690
1691/// A simple sorted list implementation that does not require any additional pallets. Note, this
1692/// does not provide validators in sorted order. If you desire nominators in a sorted order take
1693/// a look at `pallet-bags-list`.
1694pub struct UseValidatorsMap<T>(core::marker::PhantomData<T>);
1695impl<T: Config> SortedListProvider<T::AccountId> for UseValidatorsMap<T> {
1696	type Score = BalanceOf<T>;
1697	type Error = ();
1698
1699	/// Returns iterator over voter list, which can have `take` called on it.
1700	fn iter() -> Box<dyn Iterator<Item = T::AccountId>> {
1701		Box::new(Validators::<T>::iter().map(|(v, _)| v))
1702	}
1703	fn iter_from(
1704		start: &T::AccountId,
1705	) -> Result<Box<dyn Iterator<Item = T::AccountId>>, Self::Error> {
1706		if Validators::<T>::contains_key(start) {
1707			let start_key = Validators::<T>::hashed_key_for(start);
1708			Ok(Box::new(Validators::<T>::iter_from(start_key).map(|(n, _)| n)))
1709		} else {
1710			Err(())
1711		}
1712	}
1713	fn lock() {}
1714	fn unlock() {}
1715	fn count() -> u32 {
1716		Validators::<T>::count()
1717	}
1718	fn contains(id: &T::AccountId) -> bool {
1719		Validators::<T>::contains_key(id)
1720	}
1721	fn on_insert(_: T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> {
1722		// nothing to do on insert.
1723		Ok(())
1724	}
1725	fn get_score(id: &T::AccountId) -> Result<Self::Score, Self::Error> {
1726		Ok(Pallet::<T>::weight_of(id).into())
1727	}
1728	fn on_update(_: &T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> {
1729		// nothing to do on update.
1730		Ok(())
1731	}
1732	fn on_remove(_: &T::AccountId) -> Result<(), Self::Error> {
1733		// nothing to do on remove.
1734		Ok(())
1735	}
1736	fn unsafe_regenerate(
1737		_: impl IntoIterator<Item = T::AccountId>,
1738		_: Box<dyn Fn(&T::AccountId) -> Option<Self::Score>>,
1739	) -> u32 {
1740		// nothing to do upon regenerate.
1741		0
1742	}
1743	#[cfg(feature = "try-runtime")]
1744	fn try_state() -> Result<(), TryRuntimeError> {
1745		Ok(())
1746	}
1747
1748	fn unsafe_clear() {
1749		#[allow(deprecated)]
1750		Validators::<T>::remove_all();
1751	}
1752
1753	#[cfg(feature = "runtime-benchmarks")]
1754	fn score_update_worst_case(_who: &T::AccountId, _is_increase: bool) -> Self::Score {
1755		unimplemented!()
1756	}
1757}
1758
1759/// A simple voter list implementation that does not require any additional pallets. Note, this
1760/// does not provided nominators in sorted ordered. If you desire nominators in a sorted order take
1761/// a look at `pallet-bags-list`.
1762pub struct UseNominatorsAndValidatorsMap<T>(core::marker::PhantomData<T>);
1763impl<T: Config> SortedListProvider<T::AccountId> for UseNominatorsAndValidatorsMap<T> {
1764	type Error = ();
1765	type Score = VoteWeight;
1766
1767	fn iter() -> Box<dyn Iterator<Item = T::AccountId>> {
1768		Box::new(
1769			Validators::<T>::iter()
1770				.map(|(v, _)| v)
1771				.chain(Nominators::<T>::iter().map(|(n, _)| n)),
1772		)
1773	}
1774	fn iter_from(
1775		start: &T::AccountId,
1776	) -> Result<Box<dyn Iterator<Item = T::AccountId>>, Self::Error> {
1777		if Validators::<T>::contains_key(start) {
1778			let start_key = Validators::<T>::hashed_key_for(start);
1779			Ok(Box::new(
1780				Validators::<T>::iter_from(start_key)
1781					.map(|(n, _)| n)
1782					.chain(Nominators::<T>::iter().map(|(x, _)| x)),
1783			))
1784		} else if Nominators::<T>::contains_key(start) {
1785			let start_key = Nominators::<T>::hashed_key_for(start);
1786			Ok(Box::new(Nominators::<T>::iter_from(start_key).map(|(n, _)| n)))
1787		} else {
1788			Err(())
1789		}
1790	}
1791	fn lock() {}
1792	fn unlock() {}
1793	fn count() -> u32 {
1794		Nominators::<T>::count().saturating_add(Validators::<T>::count())
1795	}
1796	fn contains(id: &T::AccountId) -> bool {
1797		Nominators::<T>::contains_key(id) || Validators::<T>::contains_key(id)
1798	}
1799	fn on_insert(_: T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> {
1800		// nothing to do on insert.
1801		Ok(())
1802	}
1803	fn get_score(id: &T::AccountId) -> Result<Self::Score, Self::Error> {
1804		Ok(Pallet::<T>::weight_of(id))
1805	}
1806	fn on_update(_: &T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> {
1807		// nothing to do on update.
1808		Ok(())
1809	}
1810	fn on_remove(_: &T::AccountId) -> Result<(), Self::Error> {
1811		// nothing to do on remove.
1812		Ok(())
1813	}
1814	fn unsafe_regenerate(
1815		_: impl IntoIterator<Item = T::AccountId>,
1816		_: Box<dyn Fn(&T::AccountId) -> Option<Self::Score>>,
1817	) -> u32 {
1818		// nothing to do upon regenerate.
1819		0
1820	}
1821
1822	#[cfg(feature = "try-runtime")]
1823	fn try_state() -> Result<(), TryRuntimeError> {
1824		Ok(())
1825	}
1826
1827	fn unsafe_clear() {
1828		// NOTE: Caller must ensure this doesn't lead to too many storage accesses. This is a
1829		// condition of SortedListProvider::unsafe_clear.
1830		#[allow(deprecated)]
1831		Nominators::<T>::remove_all();
1832		#[allow(deprecated)]
1833		Validators::<T>::remove_all();
1834	}
1835
1836	#[cfg(feature = "runtime-benchmarks")]
1837	fn score_update_worst_case(_who: &T::AccountId, _is_increase: bool) -> Self::Score {
1838		unimplemented!()
1839	}
1840}
1841
1842impl<T: Config> StakingInterface for Pallet<T> {
1843	type AccountId = T::AccountId;
1844	type Balance = BalanceOf<T>;
1845	type CurrencyToVote = T::CurrencyToVote;
1846
1847	fn minimum_nominator_bond() -> Self::Balance {
1848		Self::min_nominator_bond()
1849	}
1850
1851	fn minimum_validator_bond() -> Self::Balance {
1852		Self::min_validator_bond()
1853	}
1854
1855	fn stash_by_ctrl(controller: &Self::AccountId) -> Result<Self::AccountId, DispatchError> {
1856		Self::ledger(Controller(controller.clone()))
1857			.map(|l| l.stash)
1858			.map_err(|e| e.into())
1859	}
1860
1861	/// Returns the bonding duration for validators.
1862	///
1863	/// Validators always need to wait the full [`Config::BondingDuration`] before withdrawing
1864	/// unbonded funds, as they may be subject to slashing for offences reported during this period.
1865	fn bonding_duration() -> EraIndex {
1866		T::BondingDuration::get()
1867	}
1868
1869	/// Returns the bonding duration for pure nominators.
1870	///
1871	/// This returns the *potential* fast unbonding duration that pure nominators can use:
1872	/// - When [`AreNominatorsSlashable`] is `true`, returns full [`Config::BondingDuration`]
1873	/// - When [`AreNominatorsSlashable`] is `false`, returns
1874	///   [`Config::NominatorFastUnbondDuration`]
1875	///
1876	/// **Important**: The actual unbonding duration for a specific account is determined in
1877	/// `unbond()` based on validator history (see [`LastValidatorEra`]):
1878	/// - Validators always use full [`Config::BondingDuration`]
1879	/// - Nominators who were validators in recent eras (within [`Config::BondingDuration`]) use
1880	///   full [`Config::BondingDuration`] to ensure they can be slashed for past offences
1881	/// - Pure nominators use the value returned by this function
1882	fn nominator_bonding_duration() -> EraIndex {
1883		if AreNominatorsSlashable::<T>::get() {
1884			T::BondingDuration::get()
1885		} else {
1886			T::NominatorFastUnbondDuration::get()
1887		}
1888	}
1889
1890	fn current_era() -> EraIndex {
1891		// Named current_era for legacy interface compatibility, but returns active_era.
1892		// Active era should be used for all non-election staking logic.
1893		Rotator::<T>::active_era()
1894	}
1895
1896	fn stake(who: &Self::AccountId) -> Result<Stake<BalanceOf<T>>, DispatchError> {
1897		Self::ledger(Stash(who.clone()))
1898			.map(|l| Stake { total: l.total, active: l.active })
1899			.map_err(|e| e.into())
1900	}
1901
1902	fn bond_extra(who: &Self::AccountId, extra: Self::Balance) -> DispatchResult {
1903		Self::bond_extra(RawOrigin::Signed(who.clone()).into(), extra)
1904	}
1905
1906	fn unbond(who: &Self::AccountId, value: Self::Balance) -> DispatchResult {
1907		let ctrl = Self::bonded(who).ok_or(Error::<T>::NotStash)?;
1908		Self::unbond(RawOrigin::Signed(ctrl).into(), value)
1909			.map_err(|with_post| with_post.error)
1910			.map(|_| ())
1911	}
1912
1913	fn set_payee(stash: &Self::AccountId, reward_acc: &Self::AccountId) -> DispatchResult {
1914		// Since virtual stakers are not allowed to compound their rewards as this pallet does not
1915		// manage their locks, we do not allow reward account to be set same as stash. For
1916		// external pallets that manage the virtual bond, they can claim rewards and re-bond them.
1917		ensure!(
1918			!Self::is_virtual_staker(stash) || stash != reward_acc,
1919			Error::<T>::RewardDestinationRestricted
1920		);
1921
1922		let ledger = Self::ledger(Stash(stash.clone()))?;
1923		let _ = ledger
1924			.set_payee(RewardDestination::Account(reward_acc.clone()))
1925			.defensive_proof("ledger was retrieved from storage, thus its bonded; qed.")?;
1926
1927		Ok(())
1928	}
1929
1930	fn chill(who: &Self::AccountId) -> DispatchResult {
1931		// defensive-only: any account bonded via this interface has the stash set as the
1932		// controller, but we have to be sure. Same comment anywhere else that we read this.
1933		let ctrl = Self::bonded(who).ok_or(Error::<T>::NotStash)?;
1934		Self::chill(RawOrigin::Signed(ctrl).into())
1935	}
1936
1937	fn withdraw_unbonded(
1938		who: Self::AccountId,
1939		_num_slashing_spans: u32,
1940	) -> Result<bool, DispatchError> {
1941		let ctrl = Self::bonded(&who).ok_or(Error::<T>::NotStash)?;
1942		Self::withdraw_unbonded(RawOrigin::Signed(ctrl.clone()).into(), 0)
1943			.map(|_| !StakingLedger::<T>::is_bonded(StakingAccount::Controller(ctrl)))
1944			.map_err(|with_post| with_post.error)
1945	}
1946
1947	fn bond(
1948		who: &Self::AccountId,
1949		value: Self::Balance,
1950		payee: &Self::AccountId,
1951	) -> DispatchResult {
1952		Self::bond(
1953			RawOrigin::Signed(who.clone()).into(),
1954			value,
1955			RewardDestination::Account(payee.clone()),
1956		)
1957	}
1958
1959	fn nominate(who: &Self::AccountId, targets: Vec<Self::AccountId>) -> DispatchResult {
1960		let ctrl = Self::bonded(who).ok_or(Error::<T>::NotStash)?;
1961		let targets = targets.into_iter().map(T::Lookup::unlookup).collect::<Vec<_>>();
1962		Self::nominate(RawOrigin::Signed(ctrl).into(), targets)
1963	}
1964
1965	fn desired_validator_count() -> u32 {
1966		ValidatorCount::<T>::get()
1967	}
1968
1969	fn election_ongoing() -> bool {
1970		<T::ElectionProvider as ElectionProvider>::status().is_ok()
1971	}
1972
1973	fn force_unstake(who: Self::AccountId) -> sp_runtime::DispatchResult {
1974		Self::force_unstake(RawOrigin::Root.into(), who.clone(), 0)
1975	}
1976
1977	fn is_exposed_in_era(who: &Self::AccountId, era: &EraIndex) -> bool {
1978		ErasStakersPaged::<T>::iter_prefix((era,)).any(|((validator, _), exposure_page)| {
1979			validator == *who || exposure_page.others.iter().any(|i| i.who == *who)
1980		})
1981	}
1982
1983	fn status(
1984		who: &Self::AccountId,
1985	) -> Result<sp_staking::StakerStatus<Self::AccountId>, DispatchError> {
1986		if !StakingLedger::<T>::is_bonded(StakingAccount::Stash(who.clone())) {
1987			return Err(Error::<T>::NotStash.into());
1988		}
1989
1990		let is_validator = Validators::<T>::contains_key(&who);
1991		let is_nominator = Nominators::<T>::get(&who);
1992
1993		use sp_staking::StakerStatus;
1994		match (is_validator, is_nominator.is_some()) {
1995			(false, false) => Ok(StakerStatus::Idle),
1996			(true, false) => Ok(StakerStatus::Validator),
1997			(false, true) => Ok(StakerStatus::Nominator(
1998				is_nominator.expect("is checked above; qed").targets.into_inner(),
1999			)),
2000			(true, true) => {
2001				defensive!("cannot be both validators and nominator");
2002				Err(Error::<T>::BadState.into())
2003			},
2004		}
2005	}
2006
2007	/// Whether `who` is a virtual staker whose funds are managed by another pallet.
2008	///
2009	/// There is an assumption that, this account is keyless and managed by another pallet in the
2010	/// runtime. Hence, it can never sign its own transactions.
2011	fn is_virtual_staker(who: &T::AccountId) -> bool {
2012		frame_system::Pallet::<T>::account_nonce(who).is_zero() &&
2013			VirtualStakers::<T>::contains_key(who)
2014	}
2015
2016	fn slash_reward_fraction() -> Perbill {
2017		SlashRewardFraction::<T>::get()
2018	}
2019
2020	sp_staking::runtime_benchmarks_enabled! {
2021		fn nominations(who: &Self::AccountId) -> Option<Vec<T::AccountId>> {
2022			Nominators::<T>::get(who).map(|n| n.targets.into_inner())
2023		}
2024
2025		fn add_era_stakers(
2026			current_era: &EraIndex,
2027			stash: &T::AccountId,
2028			exposures: Vec<(Self::AccountId, Self::Balance)>,
2029		) {
2030			let others = exposures
2031				.iter()
2032				.map(|(who, value)| crate::IndividualExposure { who: who.clone(), value: *value })
2033				.collect::<Vec<_>>();
2034			let exposure = Exposure { total: Default::default(), own: Default::default(), others };
2035			Eras::<T>::upsert_exposure(*current_era, stash, exposure);
2036		}
2037
2038		fn max_exposure_page_size() -> Page {
2039			T::MaxExposurePageSize::get()
2040		}
2041	}
2042
2043	sp_staking::std_or_benchmarks_enabled! {
2044		fn set_era(era: EraIndex) {
2045			ActiveEra::<T>::put(crate::ActiveEraInfo { index: era, start: None });
2046			// Simulate prod behaviour where current era is always ahead of active era by 1.
2047			CurrentEra::<T>::put(era.saturating_add(1));
2048		}
2049	}
2050}
2051
2052impl<T: Config> sp_staking::StakingUnchecked for Pallet<T> {
2053	fn migrate_to_virtual_staker(who: &Self::AccountId) -> DispatchResult {
2054		asset::kill_stake::<T>(who)?;
2055		VirtualStakers::<T>::insert(who, ());
2056		Ok(())
2057	}
2058
2059	/// Virtually bonds `keyless_who` to `payee` with `value`.
2060	///
2061	/// The payee must not be the same as the `keyless_who`.
2062	fn virtual_bond(
2063		keyless_who: &Self::AccountId,
2064		value: Self::Balance,
2065		payee: &Self::AccountId,
2066	) -> DispatchResult {
2067		if StakingLedger::<T>::is_bonded(StakingAccount::Stash(keyless_who.clone())) {
2068			return Err(Error::<T>::AlreadyBonded.into());
2069		}
2070
2071		// check if payee not same as who.
2072		ensure!(keyless_who != payee, Error::<T>::RewardDestinationRestricted);
2073
2074		// mark who as a virtual staker.
2075		VirtualStakers::<T>::insert(keyless_who, ());
2076
2077		Self::deposit_event(Event::<T>::Bonded { stash: keyless_who.clone(), amount: value });
2078		let ledger = StakingLedger::<T>::new(keyless_who.clone(), value);
2079
2080		ledger.bond(RewardDestination::Account(payee.clone()))?;
2081
2082		Ok(())
2083	}
2084
2085	/// Only meant to be used in tests.
2086	#[cfg(feature = "runtime-benchmarks")]
2087	fn migrate_to_direct_staker(who: &Self::AccountId) {
2088		assert!(VirtualStakers::<T>::contains_key(who));
2089		let ledger = StakingLedger::<T>::get(Stash(who.clone())).unwrap();
2090		let _ = asset::update_stake::<T>(who, ledger.total)
2091			.expect("funds must be transferred to stash");
2092		VirtualStakers::<T>::remove(who);
2093	}
2094}
2095
2096#[cfg(any(test, feature = "try-runtime"))]
2097impl<T: Config> Pallet<T> {
2098	pub(crate) fn do_try_state(_now: BlockNumberFor<T>) -> Result<(), TryRuntimeError> {
2099		// If the pallet is not initialized (both ActiveEra and CurrentEra are None),
2100		// there's nothing to check, so return early.
2101		if ActiveEra::<T>::get().is_none() && CurrentEra::<T>::get().is_none() {
2102			return Ok(());
2103		}
2104
2105		session_rotation::Rotator::<T>::do_try_state()?;
2106		session_rotation::Eras::<T>::do_try_state()?;
2107
2108		use frame_support::traits::fungible::Inspect;
2109		if T::CurrencyToVote::will_downscale(T::Currency::total_issuance()).map_or(false, |x| x) {
2110			log!(warn, "total issuance will cause T::CurrencyToVote to downscale -- report to maintainers.")
2111		}
2112
2113		Self::check_ledgers()?;
2114		Self::check_bonded_consistency()?;
2115		Self::check_payees()?;
2116		Self::check_paged_exposures()?;
2117		Self::check_count()?;
2118		Self::check_slash_health()?;
2119		Self::check_reward_mode_consistency()?;
2120
2121		Ok(())
2122	}
2123
2124	/// Checks consistency of the reward mode configuration and storage.
2125	///
2126	/// - If `DisableMintingGuard` is set, `DisableMinting` must be `true` (irreversible).
2127	/// - In non-minting mode, all eras >= guard within `HistoryDepth` should have pots.
2128	fn check_reward_mode_consistency() -> Result<(), TryRuntimeError> {
2129		// If the guard is set, DisableMinting must be true. Catching "switched back" misconfig.
2130		if DisableMintingGuard::<T>::get().is_some() {
2131			ensure!(
2132				T::DisableMinting::get(),
2133				"DisableMintingGuard is set but DisableMinting is false. \
2134				 Switching from non-minting back to legacy mode is not supported."
2135			);
2136		}
2137
2138		if T::DisableMinting::get() {
2139			if let Some(guard_era) = DisableMintingGuard::<T>::get() {
2140				let active_era = crate::session_rotation::Rotator::<T>::active_era();
2141				let history_depth = T::HistoryDepth::get();
2142				let oldest = active_era.saturating_sub(history_depth);
2143				for era in oldest..active_era {
2144					if era >= guard_era {
2145						ensure!(
2146							crate::reward::EraRewardManager::<T>::has_staker_rewards_pot(era),
2147							"Era pot missing for guarded era. Rewards cannot be paid."
2148						);
2149					}
2150				}
2151			} else {
2152				// TODO: convert to `ensure!` once deployed and the first era has been
2153				// snapshotted. Before deployment this is expected (guard is set on first
2154				// successful DAP snapshot in end_era_dap).
2155				log!(
2156					warn,
2157					"DisableMinting=true but DisableMintingGuard is not set. \
2158					 Expected only before the first era boundary after migration."
2159				);
2160			}
2161		}
2162		Ok(())
2163	}
2164
2165	/// Invariants:
2166	/// * A controller should not be associated with more than one ledger.
2167	/// * A bonded (stash, controller) pair should have only one associated ledger. I.e. if the
2168	///   ledger is bonded by stash, the controller account must not bond a different ledger.
2169	/// * A bonded (stash, controller) pair must have an associated ledger.
2170	///
2171	/// NOTE: these checks result in warnings only. Once
2172	/// <https://github.com/paritytech/polkadot-sdk/issues/3245> is resolved, turn warns into check
2173	/// failures.
2174	fn check_bonded_consistency() -> Result<(), TryRuntimeError> {
2175		use alloc::collections::btree_set::BTreeSet;
2176
2177		let mut count_controller_double = 0;
2178		let mut count_double = 0;
2179		let mut count_none = 0;
2180		// sanity check to ensure that each controller in Bonded storage is associated with only one
2181		// ledger.
2182		let mut controllers = BTreeSet::new();
2183
2184		for (stash, controller) in <Bonded<T>>::iter() {
2185			if !controllers.insert(controller.clone()) {
2186				count_controller_double += 1;
2187			}
2188
2189			match (<Ledger<T>>::get(&stash), <Ledger<T>>::get(&controller)) {
2190				(Some(_), Some(_)) =>
2191				// if stash == controller, it means that the ledger has migrated to
2192				// post-controller. If no migration happened, we expect that the (stash,
2193				// controller) pair has only one associated ledger.
2194				{
2195					if stash != controller {
2196						count_double += 1;
2197					}
2198				},
2199				(None, None) => {
2200					count_none += 1;
2201				},
2202				_ => {},
2203			};
2204		}
2205
2206		if count_controller_double != 0 {
2207			log!(
2208				warn,
2209				"a controller is associated with more than one ledger ({} occurrences)",
2210				count_controller_double
2211			);
2212		};
2213
2214		if count_double != 0 {
2215			log!(warn, "single tuple of (stash, controller) pair bonds more than one ledger ({} occurrences)", count_double);
2216		}
2217
2218		if count_none != 0 {
2219			log!(warn, "inconsistent bonded state: (stash, controller) pair missing associated ledger ({} occurrences)", count_none);
2220		}
2221
2222		Ok(())
2223	}
2224
2225	/// Invariants:
2226	/// * A bonded ledger should always have an assigned `Payee`.
2227	/// * The number of entries in `Payee` and of bonded staking ledgers *must* match.
2228	/// * The stash account in the ledger must match that of the bonded account.
2229	fn check_payees() -> Result<(), TryRuntimeError> {
2230		for (stash, _) in Bonded::<T>::iter() {
2231			ensure!(Payee::<T>::get(&stash).is_some(), "bonded ledger does not have payee set");
2232		}
2233
2234		ensure!(
2235			(Ledger::<T>::iter().count() == Payee::<T>::iter().count()) &&
2236				(Ledger::<T>::iter().count() == Bonded::<T>::iter().count()),
2237			"number of entries in payee storage items does not match the number of bonded ledgers",
2238		);
2239
2240		Ok(())
2241	}
2242
2243	/// Invariants:
2244	/// * Number of targets in `TargetList` matches the number of validators in the system.
2245	/// * Current validator count is bounded by the election provider's max winners.
2246	fn check_count() -> Result<(), TryRuntimeError> {
2247		// When the bags list is locked, nominators and validators may be temporarily
2248		// missing from the voter set. If `PendingRebag` is enabled, it will later
2249		// reconcile the mismatch.
2250		crate::log!(
2251			debug,
2252			"VoterList count: {}, Nominators count: {}, Validators count: {}",
2253			<T as Config>::VoterList::count(),
2254			Nominators::<T>::count(),
2255			Validators::<T>::count()
2256		);
2257
2258		ensure!(
2259			<T as Config>::TargetList::count() == Validators::<T>::count(),
2260			"wrong external count"
2261		);
2262		let max_validators_bound = crate::MaxWinnersOf::<T>::get();
2263		let max_winners_per_page_bound = crate::MaxWinnersPerPageOf::<T::ElectionProvider>::get();
2264		ensure!(
2265			max_validators_bound >= max_winners_per_page_bound,
2266			"max validators should be higher than per page bounds"
2267		);
2268		ensure!(ValidatorCount::<T>::get() <= max_validators_bound, Error::<T>::TooManyValidators);
2269		Ok(())
2270	}
2271
2272	/// Invariants:
2273	/// * Stake consistency: ledger.total == ledger.active + sum(ledger.unlocking).
2274	/// * The ledger's controller and stash matches the associated `Bonded` tuple.
2275	/// * Staking locked funds for every bonded stash (non virtual stakers) should be the same as
2276	/// its ledger's total.
2277	/// * For virtual stakers, locked funds should be zero and payee should be non-stash account.
2278	/// * Staking ledger and bond are not corrupted.
2279	fn check_ledgers() -> Result<(), TryRuntimeError> {
2280		let mut chilled_undermin: Vec<T::AccountId> = Vec::new();
2281		let mut chilled_total: u32 = 0;
2282		let mut nominator_undermin: Vec<T::AccountId> = Vec::new();
2283		let mut nominator_total: u32 = 0;
2284		let mut validator_undermin: Vec<T::AccountId> = Vec::new();
2285		let mut validator_total: u32 = 0;
2286		let mut total_ledgers: u32 = 0;
2287
2288		Bonded::<T>::iter()
2289			.map(|(stash, ctrl)| {
2290				total_ledgers += 1;
2291				// ensure locks consistency.
2292				if VirtualStakers::<T>::contains_key(stash.clone()) {
2293					ensure!(
2294						asset::staked::<T>(&stash) == Zero::zero(),
2295						"virtual stakers should not have any staked balance"
2296					);
2297					ensure!(
2298						<Bonded<T>>::get(stash.clone()).unwrap() == stash.clone(),
2299						"stash and controller should be same"
2300					);
2301					ensure!(
2302						Ledger::<T>::get(stash.clone()).unwrap().stash == stash,
2303						"ledger corrupted for virtual staker"
2304					);
2305					ensure!(
2306						frame_system::Pallet::<T>::account_nonce(&stash).is_zero(),
2307						"virtual stakers are keyless and should not have any nonce"
2308					);
2309					let reward_destination = <Payee<T>>::get(stash.clone()).unwrap();
2310					if let RewardDestination::Account(payee) = reward_destination {
2311						ensure!(
2312							payee != stash.clone(),
2313							"reward destination should not be same as stash for virtual staker"
2314						);
2315					} else {
2316						return Err(DispatchError::Other(
2317							"reward destination must be of account variant for virtual staker",
2318						));
2319					}
2320				} else {
2321					let integrity = Self::inspect_bond_state(&stash);
2322					if integrity != Ok(LedgerIntegrityState::Ok) {
2323						// NOTE: not using defensive! since we test these cases and it panics them
2324						log!(
2325							error,
2326							"defensive: bonded stash {:?} has inconsistent ledger state: {:?}",
2327							stash,
2328							integrity
2329						);
2330					}
2331				}
2332
2333				Self::ensure_ledger_consistent(&ctrl)?;
2334				Self::collect_min_bond_violations(
2335					&ctrl,
2336					&mut chilled_undermin,
2337					&mut chilled_total,
2338					&mut nominator_undermin,
2339					&mut nominator_total,
2340					&mut validator_undermin,
2341					&mut validator_total,
2342				)?;
2343				Ok(())
2344			})
2345			.collect::<Result<Vec<_>, _>>()?;
2346
2347		if chilled_total > 0 {
2348			log!(
2349				warn,
2350				"{} chilled stashes (out of {} total ledgers) have less stake than minimum role bond ({:?}). Examples: {:?}",
2351				chilled_total,
2352				total_ledgers,
2353				Self::min_chilled_bond(),
2354				chilled_undermin,
2355			);
2356		}
2357		if nominator_total > 0 {
2358			log!(
2359				warn,
2360				"{} nominators (out of {} total ledgers) have less stake than minimum role bond ({:?}). Examples: {:?}",
2361				nominator_total,
2362				total_ledgers,
2363				Self::min_nominator_bond(),
2364				nominator_undermin,
2365			);
2366		}
2367		if validator_total > 0 {
2368			log!(
2369				warn,
2370				"{} validators (out of {} total ledgers) have less stake than minimum role bond ({:?}). Examples: {:?}",
2371				validator_total,
2372				total_ledgers,
2373				Self::min_validator_bond(),
2374				validator_undermin,
2375			);
2376		}
2377
2378		Ok(())
2379	}
2380
2381	/// Invariants:
2382	/// Nothing to do if ActiveEra is not set.
2383	/// For each page in `ErasStakersPaged`, `page_total` must be set.
2384	/// For each metadata:
2385	/// 	* page_count is correct
2386	/// 	* nominator_count is correct
2387	/// 	* total is own + sum of pages
2388	/// `ErasTotalStake`` must be correct
2389	/// For each validator in `ErasStakersOverview`, `LastValidatorEra` should be set to the active
2390	/// era.
2391	fn check_paged_exposures() -> Result<(), TryRuntimeError> {
2392		let Some(era) = ActiveEra::<T>::get().map(|a| a.index) else { return Ok(()) };
2393		let overview_and_pages = ErasStakersOverview::<T>::iter_prefix(era)
2394			.map(|(validator, metadata)| {
2395				let last_validator_era = LastValidatorEra::<T>::get(&validator).unwrap_or_default();
2396				// If election for next era is finished, last_validator_era is set to next era.
2397				if last_validator_era != era && last_validator_era != era + 1 {
2398					log!(
2399						error,
2400						"Validator {:?} has incorrect LastValidatorEra (expected {:?} or {:?}, got {:?})",
2401						validator,
2402						era,
2403						era + 1,
2404						last_validator_era
2405					);
2406				}
2407
2408				let pages = ErasStakersPaged::<T>::iter_prefix((era, validator))
2409					.map(|(_idx, page)| page)
2410					.collect::<Vec<_>>();
2411				(metadata, pages)
2412			})
2413			.collect::<Vec<_>>();
2414
2415		ensure!(
2416			overview_and_pages.iter().flat_map(|(_m, pages)| pages).all(|page| {
2417				let expected = page
2418					.others
2419					.iter()
2420					.map(|e| e.value)
2421					.fold(BalanceOf::<T>::zero(), |acc, x| acc + x);
2422				page.page_total == expected
2423			}),
2424			"found wrong page_total"
2425		);
2426
2427		ensure!(
2428			overview_and_pages.iter().all(|(metadata, pages)| {
2429				let page_count_good = metadata.page_count == pages.len() as u32;
2430				let nominator_count_good = metadata.nominator_count ==
2431					pages.iter().map(|p| p.others.len() as u32).fold(0u32, |acc, x| acc + x);
2432				let total_good = metadata.total ==
2433					metadata.own +
2434						pages
2435							.iter()
2436							.fold(BalanceOf::<T>::zero(), |acc, page| acc + page.page_total);
2437
2438				page_count_good && nominator_count_good && total_good
2439			}),
2440			"found bad metadata"
2441		);
2442
2443		ensure!(
2444			overview_and_pages
2445				.iter()
2446				.map(|(metadata, _pages)| metadata.total)
2447				.fold(BalanceOf::<T>::zero(), |acc, x| acc + x) ==
2448				ErasTotalStake::<T>::get(era),
2449			"found bad eras total stake"
2450		);
2451
2452		Ok(())
2453	}
2454
2455	/// Ensures offence pipeline and slashing is in a healthy state.
2456	fn check_slash_health() -> Result<(), TryRuntimeError> {
2457		// (1) Ensure offence queue is sorted
2458		let offence_queue_eras = OffenceQueueEras::<T>::get().unwrap_or_default().into_inner();
2459		let mut sorted_offence_queue_eras = offence_queue_eras.clone();
2460		sorted_offence_queue_eras.sort();
2461		ensure!(
2462			sorted_offence_queue_eras == offence_queue_eras,
2463			"Offence queue eras are not sorted"
2464		);
2465		drop(sorted_offence_queue_eras);
2466
2467		// (2) Ensure oldest offence queue era is old enough.
2468		let active_era = Rotator::<T>::active_era();
2469		let oldest_unprocessed_offence_era =
2470			offence_queue_eras.first().cloned().unwrap_or(active_era);
2471
2472		// how old is the oldest unprocessed offence era?
2473		// given bonding duration = 28, the ideal value is between 0 and 2 eras.
2474		// anything close to bonding duration is terrible.
2475		let oldest_unprocessed_offence_age =
2476			active_era.saturating_sub(oldest_unprocessed_offence_era);
2477
2478		// warn if less than 26 eras old.
2479		if oldest_unprocessed_offence_age > 2.min(T::BondingDuration::get()) {
2480			log!(
2481				warn,
2482				"Offence queue has unprocessed offences from older than 2 eras: oldest offence era in queue {:?} (active era: {:?})",
2483				oldest_unprocessed_offence_era,
2484				active_era
2485			);
2486		}
2487
2488		// error if the oldest unprocessed offence era closer to bonding duration.
2489		ensure!(
2490			oldest_unprocessed_offence_age < T::BondingDuration::get() - 1,
2491			"offences from era less than 3 eras old from active era not processed yet"
2492		);
2493
2494		// (3) Report count of offences in the queue.
2495		for e in offence_queue_eras {
2496			let count = OffenceQueue::<T>::iter_prefix(e).count();
2497			ensure!(count > 0, "Offence queue is empty for era listed in offence queue eras");
2498			log!(info, "Offence queue for era {:?} has {:?} offences queued", e, count);
2499		}
2500
2501		// (4) Ensure all slashes older than (active era - 1) are applied.
2502		// We will look at all eras before the active era as it can take 1 era for slashes
2503		// to be applied.
2504		for era in (active_era.saturating_sub(T::BondingDuration::get()))..(active_era) {
2505			// all unapplied slashes are expected to be applied until the active era. If this is not
2506			// the case, then we need to use a permissionless call to apply all of them.
2507			// See `Call::apply_slash` for more details.
2508			Self::ensure_era_slashes_applied(era)?;
2509		}
2510
2511		// (5) Ensure no canceled slashes exist in the past eras.
2512		for (era, _) in CancelledSlashes::<T>::iter() {
2513			ensure!(era >= active_era, "Found cancelled slashes for era before active era");
2514		}
2515
2516		Ok(())
2517	}
2518
2519	/// Checks if a ledger's stash has less stake than the minimum for its role and collects
2520	/// violations into the provided accumulators (up to `MAX_EXAMPLES` examples per role).
2521	fn collect_min_bond_violations(
2522		ctrl: &T::AccountId,
2523		chilled_undermin: &mut Vec<T::AccountId>,
2524		chilled_total: &mut u32,
2525		nominator_undermin: &mut Vec<T::AccountId>,
2526		nominator_total: &mut u32,
2527		validator_undermin: &mut Vec<T::AccountId>,
2528		validator_total: &mut u32,
2529	) -> Result<(), TryRuntimeError> {
2530		const MAX_EXAMPLES: usize = 10;
2531		let ledger = Self::ledger(StakingAccount::Controller(ctrl.clone()))?;
2532		let stash = ledger.stash;
2533
2534		let is_nominator = Nominators::<T>::contains_key(&stash);
2535		let is_validator = Validators::<T>::contains_key(&stash);
2536
2537		match (is_nominator, is_validator) {
2538			(false, false) => {
2539				if ledger.active < Self::min_chilled_bond() && !ledger.active.is_zero() {
2540					// chilled accounts allow to go to zero and fully unbond ^^^^^^^^^
2541					*chilled_total += 1;
2542					if chilled_undermin.len() < MAX_EXAMPLES {
2543						chilled_undermin.push(stash.clone());
2544					}
2545					log!(
2546						trace,
2547						"Chilled stash {:?} has less stake ({:?}) than minimum role bond ({:?})",
2548						stash,
2549						ledger.active,
2550						Self::min_chilled_bond()
2551					);
2552				}
2553				// is chilled
2554			},
2555			(true, false) => {
2556				// Nominators must have a minimum bond.
2557				if ledger.active < Self::min_nominator_bond() {
2558					*nominator_total += 1;
2559					if nominator_undermin.len() < MAX_EXAMPLES {
2560						nominator_undermin.push(stash.clone());
2561					}
2562					log!(
2563						trace,
2564						"Nominator {:?} has less stake ({:?}) than minimum role bond ({:?})",
2565						stash,
2566						ledger.active,
2567						Self::min_nominator_bond()
2568					);
2569				}
2570			},
2571			(false, true) => {
2572				// Validators must have a minimum bond.
2573				if ledger.active < Self::min_validator_bond() {
2574					*validator_total += 1;
2575					if validator_undermin.len() < MAX_EXAMPLES {
2576						validator_undermin.push(stash.clone());
2577					}
2578					log!(
2579						trace,
2580						"Validator {:?} has less stake ({:?}) than minimum role bond ({:?})",
2581						stash,
2582						ledger.active,
2583						Self::min_validator_bond()
2584					);
2585				}
2586			},
2587			(true, true) => {
2588				ensure!(false, "Stash cannot be both nominator and validator");
2589			},
2590		}
2591		Ok(())
2592	}
2593
2594	fn ensure_ledger_consistent(ctrl: &T::AccountId) -> Result<(), TryRuntimeError> {
2595		// ensures ledger.total == ledger.active + sum(ledger.unlocking).
2596		let ledger = Self::ledger(StakingAccount::Controller(ctrl.clone()))?;
2597
2598		let real_total: BalanceOf<T> =
2599			ledger.unlocking.iter().fold(ledger.active, |a, c| a + c.value);
2600		ensure!(real_total == ledger.total, "ledger.total corrupt");
2601
2602		Ok(())
2603	}
2604}