1use crate::{
21 asset,
22 election_size_tracker::StaticTracker,
23 log,
24 session_rotation::{self, Eras, Rotator},
25 slashing::OffenceRecord,
26 weights::WeightInfo,
27 BalanceOf, Exposure, Forcing, LedgerIntegrityState, MaxNominationsOf, Nominations,
28 NominationsQuota, PositiveImbalanceOf, RewardDestination, SnapshotStatus, StakingLedger,
29 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 Defensive, DefensiveSaturating, Get, Imbalance, InspectLockableCurrency, LockableCurrency,
42 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,
56 StakingAccount::{self, Controller, Stash},
57 StakingInterface,
58};
59
60use super::pallet::*;
61
62#[cfg(feature = "try-runtime")]
63use frame_support::ensure;
64#[cfg(any(test, feature = "try-runtime"))]
65use sp_runtime::TryRuntimeError;
66
67const NPOS_MAX_ITERATIONS_COEFFICIENT: u32 = 2;
74
75impl<T: Config> Pallet<T> {
76 pub(crate) fn min_chilled_bond() -> BalanceOf<T> {
83 MinValidatorBond::<T>::get()
84 .min(MinNominatorBond::<T>::get())
85 .max(asset::existential_deposit::<T>())
86 }
87
88 pub(crate) fn min_validator_bond() -> BalanceOf<T> {
91 MinValidatorBond::<T>::get().max(Self::min_nominator_bond())
92 }
93
94 pub(crate) fn min_nominator_bond() -> BalanceOf<T> {
97 MinNominatorBond::<T>::get().max(asset::existential_deposit::<T>())
98 }
99
100 pub fn ledger(account: StakingAccount<T::AccountId>) -> Result<StakingLedger<T>, Error<T>> {
102 StakingLedger::<T>::get(account)
103 }
104
105 pub fn payee(account: StakingAccount<T::AccountId>) -> Option<RewardDestination<T::AccountId>> {
106 StakingLedger::<T>::reward_destination(account)
107 }
108
109 pub fn bonded(stash: &T::AccountId) -> Option<T::AccountId> {
111 StakingLedger::<T>::paired_account(Stash(stash.clone()))
112 }
113
114 pub(crate) fn inspect_bond_state(
121 stash: &T::AccountId,
122 ) -> Result<LedgerIntegrityState, Error<T>> {
123 let hold_or_lock = asset::staked::<T>(&stash)
125 .max(T::OldCurrency::balance_locked(STAKING_ID, &stash).into());
126
127 let controller = <Bonded<T>>::get(stash).ok_or_else(|| {
128 if hold_or_lock == Zero::zero() {
129 Error::<T>::NotStash
130 } else {
131 Error::<T>::BadState
132 }
133 })?;
134
135 match Ledger::<T>::get(controller) {
136 Some(ledger) =>
137 if ledger.stash != *stash {
138 Ok(LedgerIntegrityState::Corrupted)
139 } else {
140 if hold_or_lock != ledger.total {
141 Ok(LedgerIntegrityState::LockCorrupted)
142 } else {
143 Ok(LedgerIntegrityState::Ok)
144 }
145 },
146 None => Ok(LedgerIntegrityState::CorruptedKilled),
147 }
148 }
149
150 pub fn slashable_balance_of(stash: &T::AccountId) -> BalanceOf<T> {
152 Self::ledger(Stash(stash.clone())).map(|l| l.active).unwrap_or_default()
154 }
155
156 pub fn slashable_balance_of_vote_weight(
158 stash: &T::AccountId,
159 issuance: BalanceOf<T>,
160 ) -> VoteWeight {
161 T::CurrencyToVote::to_vote(Self::slashable_balance_of(stash), issuance)
162 }
163
164 pub fn weight_of_fn() -> Box<dyn Fn(&T::AccountId) -> VoteWeight> {
169 let issuance = asset::total_issuance::<T>();
172 Box::new(move |who: &T::AccountId| -> VoteWeight {
173 Self::slashable_balance_of_vote_weight(who, issuance)
174 })
175 }
176
177 pub fn weight_of(who: &T::AccountId) -> VoteWeight {
179 let issuance = asset::total_issuance::<T>();
180 Self::slashable_balance_of_vote_weight(who, issuance)
181 }
182
183 pub(crate) fn check_slash_cancelled(
185 era: EraIndex,
186 validator: &T::AccountId,
187 slash_fraction: Perbill,
188 ) -> bool {
189 let cancelled_slashes = CancelledSlashes::<T>::get(&era);
190 cancelled_slashes.iter().any(|(cancelled_validator, cancel_fraction)| {
191 *cancelled_validator == *validator && *cancel_fraction >= slash_fraction
192 })
193 }
194
195 pub(super) fn do_bond_extra(stash: &T::AccountId, additional: BalanceOf<T>) -> DispatchResult {
196 let mut ledger = Self::ledger(StakingAccount::Stash(stash.clone()))?;
197
198 let extra = if Self::is_virtual_staker(stash) {
201 additional
202 } else {
203 additional.min(asset::free_to_stake::<T>(stash))
205 };
206
207 ledger.total = ledger.total.checked_add(&extra).ok_or(ArithmeticError::Overflow)?;
208 ledger.active = ledger.active.checked_add(&extra).ok_or(ArithmeticError::Overflow)?;
209 ensure!(ledger.active >= Self::min_chilled_bond(), Error::<T>::InsufficientBond);
211
212 ledger.update()?;
214 if T::VoterList::contains(stash) {
216 let _ = T::VoterList::on_update(&stash, Self::weight_of(stash));
218 }
219
220 Self::deposit_event(Event::<T>::Bonded { stash: stash.clone(), amount: extra });
221
222 Ok(())
223 }
224
225 fn calculate_earliest_withdrawal_era(active_era: EraIndex) -> EraIndex {
229 let earliest_unlock_era_by_offence_queue = OffenceQueueEras::<T>::get()
231 .as_ref()
232 .and_then(|eras| eras.first())
233 .copied()
234 .unwrap_or(active_era)
236 .saturating_sub(1)
239 .saturating_add(T::BondingDuration::get());
242
243 active_era.min(earliest_unlock_era_by_offence_queue)
249 }
250
251 pub(super) fn do_withdraw_unbonded(controller: &T::AccountId) -> Result<Weight, DispatchError> {
252 let mut ledger = Self::ledger(Controller(controller.clone()))?;
253 let (stash, old_total) = (ledger.stash.clone(), ledger.total);
254 let active_era = Rotator::<T>::active_era();
255
256 if active_era > 1 {
258 Self::ensure_era_slashes_applied(active_era.saturating_sub(1))?;
259 }
260
261 let earliest_era_to_withdraw = Self::calculate_earliest_withdrawal_era(active_era);
262
263 log!(
264 debug,
265 "Withdrawing unbonded stake. Active_era is: {:?} | \
266 Earliest era we can allow withdrawing: {:?}",
267 active_era,
268 earliest_era_to_withdraw
269 );
270
271 ledger = ledger.consolidate_unlocked(earliest_era_to_withdraw);
273
274 let new_total = ledger.total;
275 debug_assert!(
276 new_total <= old_total,
277 "consolidate_unlocked should never increase the total balance of the ledger"
278 );
279
280 let used_weight = if ledger.unlocking.is_empty() &&
281 (ledger.active < Self::min_chilled_bond() || ledger.active.is_zero())
282 {
283 Self::kill_stash(&ledger.stash)?;
287
288 T::WeightInfo::withdraw_unbonded_kill()
289 } else {
290 ledger.update()?;
292
293 T::WeightInfo::withdraw_unbonded_update()
295 };
296
297 if new_total < old_total {
300 let value = old_total.defensive_saturating_sub(new_total);
302 Self::deposit_event(Event::<T>::Withdrawn { stash, amount: value });
303
304 T::EventListeners::on_withdraw(controller, value);
306 }
307
308 Ok(used_weight)
309 }
310
311 fn ensure_era_slashes_applied(era: EraIndex) -> Result<(), DispatchError> {
312 ensure!(
313 !UnappliedSlashes::<T>::contains_prefix(era),
314 Error::<T>::UnappliedSlashesInPreviousEra
315 );
316 Ok(())
317 }
318
319 pub(super) fn do_payout_stakers(
320 validator_stash: T::AccountId,
321 era: EraIndex,
322 ) -> DispatchResultWithPostInfo {
323 let page = Eras::<T>::get_next_claimable_page(era, &validator_stash).ok_or_else(|| {
324 Error::<T>::AlreadyClaimed.with_weight(T::WeightInfo::payout_stakers_alive_staked(0))
325 })?;
326
327 Self::do_payout_stakers_by_page(validator_stash, era, page)
328 }
329
330 pub(super) fn do_payout_stakers_by_page(
331 validator_stash: T::AccountId,
332 era: EraIndex,
333 page: Page,
334 ) -> DispatchResultWithPostInfo {
335 let current_era = CurrentEra::<T>::get().ok_or_else(|| {
337 Error::<T>::InvalidEraToReward
338 .with_weight(T::WeightInfo::payout_stakers_alive_staked(0))
339 })?;
340
341 let history_depth = T::HistoryDepth::get();
342
343 ensure!(
344 era <= current_era && era >= current_era.saturating_sub(history_depth),
345 Error::<T>::InvalidEraToReward
346 .with_weight(T::WeightInfo::payout_stakers_alive_staked(0))
347 );
348
349 ensure!(
350 page < Eras::<T>::exposure_page_count(era, &validator_stash),
351 Error::<T>::InvalidPage.with_weight(T::WeightInfo::payout_stakers_alive_staked(0))
352 );
353
354 let era_payout = Eras::<T>::get_validators_reward(era).ok_or_else(|| {
356 Error::<T>::InvalidEraToReward
357 .with_weight(T::WeightInfo::payout_stakers_alive_staked(0))
358 })?;
359
360 let account = StakingAccount::Stash(validator_stash.clone());
361 let ledger = Self::ledger(account.clone()).or_else(|_| {
362 if StakingLedger::<T>::is_bonded(account) {
363 Err(Error::<T>::NotController.into())
364 } else {
365 Err(Error::<T>::NotStash.with_weight(T::WeightInfo::payout_stakers_alive_staked(0)))
366 }
367 })?;
368
369 ledger.clone().update()?;
370
371 let stash = ledger.stash.clone();
372
373 if Eras::<T>::is_rewards_claimed(era, &stash, page) {
374 return Err(Error::<T>::AlreadyClaimed
375 .with_weight(T::WeightInfo::payout_stakers_alive_staked(0)))
376 }
377
378 Eras::<T>::set_rewards_as_claimed(era, &stash, page);
379
380 let exposure = Eras::<T>::get_paged_exposure(era, &stash, page).ok_or_else(|| {
381 Error::<T>::InvalidEraToReward
382 .with_weight(T::WeightInfo::payout_stakers_alive_staked(0))
383 })?;
384
385 let era_reward_points = Eras::<T>::get_reward_points(era);
395 let total_reward_points = era_reward_points.total;
396 let validator_reward_points =
397 era_reward_points.individual.get(&stash).copied().unwrap_or_else(Zero::zero);
398
399 if validator_reward_points.is_zero() {
401 return Ok(Some(T::WeightInfo::payout_stakers_alive_staked(0)).into())
402 }
403
404 let validator_total_reward_part =
407 Perbill::from_rational(validator_reward_points, total_reward_points);
408
409 let validator_total_payout = validator_total_reward_part * era_payout;
411
412 let validator_commission = Eras::<T>::get_validator_commission(era, &ledger.stash);
413 let validator_total_commission_payout = validator_commission * validator_total_payout;
415
416 let validator_leftover_payout =
417 validator_total_payout.defensive_saturating_sub(validator_total_commission_payout);
418 let validator_exposure_part = Perbill::from_rational(exposure.own(), exposure.total());
420 let validator_staking_payout = validator_exposure_part * validator_leftover_payout;
421 let page_stake_part = Perbill::from_rational(exposure.page_total(), exposure.total());
422 let validator_commission_payout = page_stake_part * validator_total_commission_payout;
424
425 Self::deposit_event(Event::<T>::PayoutStarted {
426 era_index: era,
427 validator_stash: stash.clone(),
428 page,
429 next: Eras::<T>::get_next_claimable_page(era, &stash),
430 });
431
432 let mut total_imbalance = PositiveImbalanceOf::<T>::zero();
433 if let Some((imbalance, dest)) =
435 Self::make_payout(&stash, validator_staking_payout + validator_commission_payout)
436 {
437 Self::deposit_event(Event::<T>::Rewarded { stash, dest, amount: imbalance.peek() });
438 total_imbalance.subsume(imbalance);
439 }
440
441 let mut nominator_payout_count: u32 = 0;
445
446 for nominator in exposure.others().iter() {
449 let nominator_exposure_part = Perbill::from_rational(nominator.value, exposure.total());
450
451 let nominator_reward: BalanceOf<T> =
452 nominator_exposure_part * validator_leftover_payout;
453 if let Some((imbalance, dest)) = Self::make_payout(&nominator.who, nominator_reward) {
455 nominator_payout_count += 1;
457 let e = Event::<T>::Rewarded {
458 stash: nominator.who.clone(),
459 dest,
460 amount: imbalance.peek(),
461 };
462 Self::deposit_event(e);
463 total_imbalance.subsume(imbalance);
464 }
465 }
466
467 T::Reward::on_unbalanced(total_imbalance);
468 debug_assert!(nominator_payout_count <= T::MaxExposurePageSize::get());
469
470 Ok(Some(T::WeightInfo::payout_stakers_alive_staked(nominator_payout_count)).into())
471 }
472
473 pub(crate) fn chill_stash(stash: &T::AccountId) {
475 let chilled_as_validator = Self::do_remove_validator(stash);
476 let chilled_as_nominator = Self::do_remove_nominator(stash);
477 if chilled_as_validator || chilled_as_nominator {
478 Self::deposit_event(Event::<T>::Chilled { stash: stash.clone() });
479 }
480 }
481
482 fn make_payout(
485 stash: &T::AccountId,
486 amount: BalanceOf<T>,
487 ) -> Option<(PositiveImbalanceOf<T>, RewardDestination<T::AccountId>)> {
488 if amount.is_zero() {
490 return None
491 }
492 let dest = Self::payee(StakingAccount::Stash(stash.clone()))?;
493
494 let maybe_imbalance = match dest {
495 RewardDestination::Stash => asset::mint_into_existing::<T>(stash, amount),
496 RewardDestination::Staked => Self::ledger(Stash(stash.clone()))
497 .and_then(|mut ledger| {
498 ledger.active += amount;
499 ledger.total += amount;
500 let r = asset::mint_into_existing::<T>(stash, amount);
501
502 let _ = ledger
503 .update()
504 .defensive_proof("ledger fetched from storage, so it exists; qed.");
505
506 Ok(r)
507 })
508 .unwrap_or_default(),
509 RewardDestination::Account(ref dest_account) =>
510 Some(asset::mint_creating::<T>(&dest_account, amount)),
511 RewardDestination::None => None,
512 #[allow(deprecated)]
513 RewardDestination::Controller => Self::bonded(stash)
514 .map(|controller| {
515 defensive!("Paying out controller as reward destination which is deprecated and should be migrated.");
516 asset::mint_creating::<T>(&controller, amount)
519 }),
520 };
521 maybe_imbalance.map(|imbalance| (imbalance, dest))
522 }
523
524 pub(crate) fn kill_stash(stash: &T::AccountId) -> DispatchResult {
532 StakingLedger::<T>::kill(&stash)?;
535
536 Self::do_remove_validator(&stash);
537 Self::do_remove_nominator(&stash);
538
539 Ok(())
540 }
541
542 #[cfg(test)]
543 pub(crate) fn reward_by_ids(validators_points: impl IntoIterator<Item = (T::AccountId, u32)>) {
544 Eras::<T>::reward_active_era(validators_points)
545 }
546
547 pub(crate) fn set_force_era(mode: Forcing) {
549 log!(info, "Setting force era mode {:?}.", mode);
550 ForceEra::<T>::put(mode);
551 Self::deposit_event(Event::<T>::ForceEra { mode });
552 }
553
554 #[cfg(feature = "runtime-benchmarks")]
555 pub fn add_era_stakers(
556 current_era: EraIndex,
557 stash: T::AccountId,
558 exposure: Exposure<T::AccountId, BalanceOf<T>>,
559 ) {
560 Eras::<T>::upsert_exposure(current_era, &stash, exposure);
561 }
562
563 #[cfg(feature = "runtime-benchmarks")]
564 pub fn set_slash_reward_fraction(fraction: Perbill) {
565 SlashRewardFraction::<T>::put(fraction);
566 }
567
568 pub(crate) fn get_npos_voters(
578 bounds: DataProviderBounds,
579 status: &SnapshotStatus<T::AccountId>,
580 ) -> Vec<VoterOf<Self>> {
581 let mut voters_size_tracker: StaticTracker<Self> = StaticTracker::default();
582
583 let page_len_prediction = {
584 let all_voter_count = T::VoterList::count();
585 bounds.count.unwrap_or(all_voter_count.into()).min(all_voter_count.into()).0
586 };
587
588 let mut all_voters = Vec::<_>::with_capacity(page_len_prediction as usize);
589
590 let weight_of = Self::weight_of_fn();
592
593 let mut voters_seen = 0u32;
594 let mut validators_taken = 0u32;
595 let mut nominators_taken = 0u32;
596 let mut min_active_stake = u64::MAX;
597
598 let mut sorted_voters = match status {
599 SnapshotStatus::Waiting => T::VoterList::iter(),
601 SnapshotStatus::Ongoing(account_id) => T::VoterList::iter_from(&account_id)
603 .defensive_unwrap_or(Box::new(vec![].into_iter())),
604 SnapshotStatus::Consumed => Box::new(vec![].into_iter()),
606 };
607
608 while all_voters.len() < page_len_prediction as usize &&
609 voters_seen < (NPOS_MAX_ITERATIONS_COEFFICIENT * page_len_prediction as u32)
610 {
611 let voter = match sorted_voters.next() {
612 Some(voter) => {
613 voters_seen.saturating_inc();
614 voter
615 },
616 None => break,
617 };
618
619 let voter_weight = weight_of(&voter);
620 if voter_weight.is_zero() {
622 log!(debug, "voter's active balance is 0. skip this voter.");
623 continue
624 }
625
626 if let Some(Nominations { targets, .. }) = <Nominators<T>>::get(&voter) {
627 if !targets.is_empty() {
628 let voter = (voter, voter_weight, targets);
633 if voters_size_tracker.try_register_voter(&voter, &bounds).is_err() {
634 Self::deposit_event(Event::<T>::SnapshotVotersSizeExceeded {
636 size: voters_size_tracker.size as u32,
637 });
638 break
639 }
640
641 all_voters.push(voter);
642 nominators_taken.saturating_inc();
643 } else {
644 defensive!("non-nominator fetched from voter list: {:?}", voter);
645 }
647 min_active_stake =
648 if voter_weight < min_active_stake { voter_weight } else { min_active_stake };
649 } else if Validators::<T>::contains_key(&voter) {
650 let self_vote = (
652 voter.clone(),
653 voter_weight,
654 vec![voter.clone()]
655 .try_into()
656 .expect("`MaxVotesPerVoter` must be greater than or equal to 1"),
657 );
658
659 if voters_size_tracker.try_register_voter(&self_vote, &bounds).is_err() {
660 Self::deposit_event(Event::<T>::SnapshotVotersSizeExceeded {
662 size: voters_size_tracker.size as u32,
663 });
664 break
665 }
666 all_voters.push(self_vote);
667 validators_taken.saturating_inc();
668 } else {
669 defensive!(
675 "invalid item in `VoterList`: {:?}, this nominator probably has too many nominations now",
676 voter,
677 );
678 }
679 }
680
681 debug_assert!(all_voters.capacity() == page_len_prediction as usize);
683
684 let min_active_stake: T::CurrencyBalance =
685 if all_voters.is_empty() { Zero::zero() } else { min_active_stake.into() };
686
687 MinimumActiveStake::<T>::put(min_active_stake);
688
689 all_voters
690 }
691
692 pub fn get_npos_targets(bounds: DataProviderBounds) -> Vec<T::AccountId> {
698 let mut targets_size_tracker: StaticTracker<Self> = StaticTracker::default();
699
700 let final_predicted_len = {
701 let all_target_count = T::TargetList::count();
702 bounds.count.unwrap_or(all_target_count.into()).min(all_target_count.into()).0
703 };
704
705 let mut all_targets = Vec::<T::AccountId>::with_capacity(final_predicted_len as usize);
706 let mut targets_seen = 0;
707
708 let mut targets_iter = T::TargetList::iter();
709 while all_targets.len() < final_predicted_len as usize &&
710 targets_seen < (NPOS_MAX_ITERATIONS_COEFFICIENT * final_predicted_len as u32)
711 {
712 let target = match targets_iter.next() {
713 Some(target) => {
714 targets_seen.saturating_inc();
715 target
716 },
717 None => break,
718 };
719
720 if targets_size_tracker.try_register_target(target.clone(), &bounds).is_err() {
721 log!(warn, "npos targets size exceeded, stopping iteration.");
723 Self::deposit_event(Event::<T>::SnapshotTargetsSizeExceeded {
724 size: targets_size_tracker.size as u32,
725 });
726 break
727 }
728
729 if Validators::<T>::contains_key(&target) {
730 all_targets.push(target);
731 }
732 }
733
734 log!(debug, "[bounds {:?}] generated {} npos targets", bounds, all_targets.len());
735
736 all_targets
737 }
738
739 pub fn do_add_nominator(who: &T::AccountId, nominations: Nominations<T>) {
748 if !Nominators::<T>::contains_key(who) {
749 let _ = T::VoterList::on_insert(who.clone(), Self::weight_of(who))
751 .defensive_unwrap_or_default();
752 }
753 Nominators::<T>::insert(who, nominations);
754
755 debug_assert_eq!(
756 Nominators::<T>::count() + Validators::<T>::count(),
757 T::VoterList::count()
758 );
759 }
760
761 pub fn do_remove_nominator(who: &T::AccountId) -> bool {
770 let outcome = if Nominators::<T>::contains_key(who) {
771 Nominators::<T>::remove(who);
772 let _ = T::VoterList::on_remove(who);
773 true
774 } else {
775 false
776 };
777
778 debug_assert_eq!(
779 Nominators::<T>::count() + Validators::<T>::count(),
780 T::VoterList::count()
781 );
782
783 outcome
784 }
785
786 pub fn do_add_validator(who: &T::AccountId, prefs: ValidatorPrefs) {
794 if !Validators::<T>::contains_key(who) {
795 let _ = T::VoterList::on_insert(who.clone(), Self::weight_of(who));
797 }
798 Validators::<T>::insert(who, prefs);
799
800 debug_assert_eq!(
801 Nominators::<T>::count() + Validators::<T>::count(),
802 T::VoterList::count()
803 );
804 }
805
806 pub fn do_remove_validator(who: &T::AccountId) -> bool {
814 let outcome = if Validators::<T>::contains_key(who) {
815 Validators::<T>::remove(who);
816 let _ = T::VoterList::on_remove(who);
817 true
818 } else {
819 false
820 };
821
822 debug_assert_eq!(
823 Nominators::<T>::count() + Validators::<T>::count(),
824 T::VoterList::count()
825 );
826
827 outcome
828 }
829
830 pub(crate) fn register_weight(weight: Weight) {
834 <frame_system::Pallet<T>>::register_extra_weight_unchecked(
835 weight,
836 DispatchClass::Mandatory,
837 );
838 }
839
840 pub fn eras_stakers(
847 era: EraIndex,
848 account: &T::AccountId,
849 ) -> Exposure<T::AccountId, BalanceOf<T>> {
850 Eras::<T>::get_full_exposure(era, account)
851 }
852
853 pub(super) fn do_migrate_currency(stash: &T::AccountId) -> DispatchResult {
854 if Self::is_virtual_staker(stash) {
855 return Self::do_migrate_virtual_staker(stash);
856 }
857
858 let ledger = Self::ledger(Stash(stash.clone()))?;
859 let staked: BalanceOf<T> = T::OldCurrency::balance_locked(STAKING_ID, stash).into();
860 ensure!(!staked.is_zero(), Error::<T>::AlreadyMigrated);
861 ensure!(ledger.total == staked, Error::<T>::BadState);
862
863 T::OldCurrency::remove_lock(STAKING_ID, &stash);
865
866 let max_hold = asset::free_to_stake::<T>(&stash);
868 let force_withdraw = if max_hold >= staked {
869 asset::update_stake::<T>(&stash, staked)?;
871 Zero::zero()
872 } else {
873 let force_withdraw = staked.saturating_sub(max_hold);
876
877 StakingLedger {
880 total: max_hold,
881 active: ledger.active.saturating_sub(force_withdraw),
882 ..ledger
884 }
885 .update()?;
886 force_withdraw
887 };
888
889 frame_system::Pallet::<T>::dec_consumers(&stash);
891
892 Self::deposit_event(Event::<T>::CurrencyMigrated { stash: stash.clone(), force_withdraw });
893 Ok(())
894 }
895
896 fn do_migrate_virtual_staker(stash: &T::AccountId) -> DispatchResult {
897 frame_system::Pallet::<T>::dec_consumers(&stash);
900
901 let actual_providers = frame_system::Pallet::<T>::providers(stash);
906
907 let expected_providers =
908 if asset::free_to_stake::<T>(&stash) >= asset::existential_deposit::<T>() {
911 2
912 } else {
913 1
914 };
915
916 ensure!(actual_providers <= expected_providers, Error::<T>::BadState);
918
919 ensure!(actual_providers == expected_providers, Error::<T>::AlreadyMigrated);
921
922 let _ = frame_system::Pallet::<T>::dec_providers(&stash)?;
924
925 return Ok(())
926 }
927}
928
929impl<T: Config> Pallet<T> {
930 pub fn api_nominations_quota(balance: BalanceOf<T>) -> u32 {
934 T::NominationsQuota::get_quota(balance)
935 }
936
937 pub fn api_eras_stakers(
938 era: EraIndex,
939 account: T::AccountId,
940 ) -> Exposure<T::AccountId, BalanceOf<T>> {
941 Self::eras_stakers(era, &account)
942 }
943
944 pub fn api_eras_stakers_page_count(era: EraIndex, account: T::AccountId) -> Page {
945 Eras::<T>::exposure_page_count(era, &account)
946 }
947
948 pub fn api_pending_rewards(era: EraIndex, account: T::AccountId) -> bool {
949 Eras::<T>::pending_rewards(era, &account)
950 }
951}
952
953impl<T: Config> ElectionDataProvider for Pallet<T> {
954 type AccountId = T::AccountId;
955 type BlockNumber = BlockNumberFor<T>;
956 type MaxVotesPerVoter = MaxNominationsOf<T>;
957
958 fn desired_targets() -> data_provider::Result<u32> {
959 Self::register_weight(T::DbWeight::get().reads(1));
960 Ok(ValidatorCount::<T>::get())
961 }
962
963 fn electing_voters(
964 bounds: DataProviderBounds,
965 page: PageIndex,
966 ) -> data_provider::Result<Vec<VoterOf<Self>>> {
967 let mut status = VoterSnapshotStatus::<T>::get();
968 let voters = Self::get_npos_voters(bounds, &status);
969
970 match (page, &status) {
972 (0, _) => status = SnapshotStatus::Waiting,
974
975 (_, SnapshotStatus::Waiting) | (_, SnapshotStatus::Ongoing(_)) => {
976 let maybe_last = voters.last().map(|(x, _, _)| x).cloned();
977
978 if let Some(ref last) = maybe_last {
979 let has_next =
980 T::VoterList::iter_from(last).ok().and_then(|mut i| i.next()).is_some();
981 if has_next {
982 status = SnapshotStatus::Ongoing(last.clone());
983 } else {
984 status = SnapshotStatus::Consumed;
985 }
986 }
987 },
988 (_, SnapshotStatus::Consumed) => (),
990 }
991
992 log!(
993 debug,
994 "[page {}, (next) status {:?}, bounds {:?}] generated {} npos voters",
995 page,
996 status,
997 bounds,
998 voters.len(),
999 );
1000
1001 match status {
1002 SnapshotStatus::Ongoing(_) => T::VoterList::lock(),
1003 _ => T::VoterList::unlock(),
1004 }
1005
1006 VoterSnapshotStatus::<T>::put(status);
1007 debug_assert!(!bounds.slice_exhausted(&voters));
1008
1009 Ok(voters)
1010 }
1011
1012 fn electing_voters_stateless(
1013 bounds: DataProviderBounds,
1014 ) -> data_provider::Result<Vec<VoterOf<Self>>> {
1015 let voters = Self::get_npos_voters(bounds, &SnapshotStatus::Waiting);
1016 log!(debug, "[stateless, bounds {:?}] generated {} npos voters", bounds, voters.len(),);
1017 Ok(voters)
1018 }
1019
1020 fn electable_targets(
1021 bounds: DataProviderBounds,
1022 page: PageIndex,
1023 ) -> data_provider::Result<Vec<T::AccountId>> {
1024 if page > 0 {
1025 log!(warn, "multi-page target snapshot not supported, returning page 0.");
1026 }
1027
1028 let targets = Self::get_npos_targets(bounds);
1029 if bounds.exhausted(None, CountBound(targets.len() as u32).into()) {
1032 return Err("Target snapshot too big")
1033 }
1034
1035 debug_assert!(!bounds.slice_exhausted(&targets));
1036
1037 Ok(targets)
1038 }
1039
1040 fn next_election_prediction(_: BlockNumberFor<T>) -> BlockNumberFor<T> {
1041 debug_assert!(false, "this is deprecated and not used anymore");
1042 sp_runtime::traits::Bounded::max_value()
1043 }
1044
1045 #[cfg(feature = "runtime-benchmarks")]
1046 fn fetch_page(page: PageIndex) {
1047 session_rotation::EraElectionPlanner::<T>::do_elect_paged(page);
1048 }
1049
1050 #[cfg(feature = "runtime-benchmarks")]
1051 fn add_voter(
1052 voter: T::AccountId,
1053 weight: VoteWeight,
1054 targets: BoundedVec<T::AccountId, Self::MaxVotesPerVoter>,
1055 ) {
1056 let stake = <BalanceOf<T>>::try_from(weight).unwrap_or_else(|_| {
1057 panic!("cannot convert a VoteWeight into BalanceOf, benchmark needs reconfiguring.")
1058 });
1059 <Bonded<T>>::insert(voter.clone(), voter.clone());
1060 <Ledger<T>>::insert(voter.clone(), StakingLedger::<T>::new(voter.clone(), stake));
1061
1062 Self::do_add_nominator(&voter, Nominations { targets, submitted_in: 0, suppressed: false });
1063 }
1064
1065 #[cfg(feature = "runtime-benchmarks")]
1066 fn add_target(target: T::AccountId) {
1067 let stake = (Self::min_validator_bond() + 1u32.into()) * 100u32.into();
1068 <Bonded<T>>::insert(target.clone(), target.clone());
1069 <Ledger<T>>::insert(target.clone(), StakingLedger::<T>::new(target.clone(), stake));
1070 Self::do_add_validator(
1071 &target,
1072 ValidatorPrefs { commission: Perbill::zero(), blocked: false },
1073 );
1074 }
1075
1076 #[cfg(feature = "runtime-benchmarks")]
1077 fn clear() {
1078 #[allow(deprecated)]
1079 <Bonded<T>>::remove_all(None);
1080 #[allow(deprecated)]
1081 <Ledger<T>>::remove_all(None);
1082 #[allow(deprecated)]
1083 <Validators<T>>::remove_all();
1084 #[allow(deprecated)]
1085 <Nominators<T>>::remove_all();
1086
1087 T::VoterList::unsafe_clear();
1088 }
1089
1090 #[cfg(feature = "runtime-benchmarks")]
1091 fn put_snapshot(
1092 voters: Vec<VoterOf<Self>>,
1093 targets: Vec<T::AccountId>,
1094 target_stake: Option<VoteWeight>,
1095 ) {
1096 targets.into_iter().for_each(|v| {
1097 let stake: BalanceOf<T> = target_stake
1098 .and_then(|w| <BalanceOf<T>>::try_from(w).ok())
1099 .unwrap_or_else(|| Self::min_nominator_bond() * 100u32.into());
1100 <Bonded<T>>::insert(v.clone(), v.clone());
1101 <Ledger<T>>::insert(v.clone(), StakingLedger::<T>::new(v.clone(), stake));
1102 Self::do_add_validator(
1103 &v,
1104 ValidatorPrefs { commission: Perbill::zero(), blocked: false },
1105 );
1106 });
1107
1108 voters.into_iter().for_each(|(v, s, t)| {
1109 let stake = <BalanceOf<T>>::try_from(s).unwrap_or_else(|_| {
1110 panic!("cannot convert a VoteWeight into BalanceOf, benchmark needs reconfiguring.")
1111 });
1112 <Bonded<T>>::insert(v.clone(), v.clone());
1113 <Ledger<T>>::insert(v.clone(), StakingLedger::<T>::new(v.clone(), stake));
1114 Self::do_add_nominator(
1115 &v,
1116 Nominations { targets: t, submitted_in: 0, suppressed: false },
1117 );
1118 });
1119 }
1120
1121 #[cfg(feature = "runtime-benchmarks")]
1122 fn set_desired_targets(count: u32) {
1123 ValidatorCount::<T>::put(count);
1124 }
1125}
1126
1127impl<T: Config> rc_client::AHStakingInterface for Pallet<T> {
1128 type AccountId = T::AccountId;
1129 type MaxValidatorSet = T::MaxValidatorSet;
1130
1131 fn on_relay_session_report(report: rc_client::SessionReport<Self::AccountId>) -> Weight {
1140 log!(debug, "Received session report: {}", report,);
1141
1142 let rc_client::SessionReport {
1143 end_index,
1144 activation_timestamp,
1145 validator_points,
1146 leftover,
1147 } = report;
1148 debug_assert!(!leftover);
1149
1150 Eras::<T>::reward_active_era(validator_points.into_iter());
1152 session_rotation::Rotator::<T>::end_session(end_index, activation_timestamp)
1153 }
1154
1155 fn weigh_on_relay_session_report(
1156 _report: &rc_client::SessionReport<Self::AccountId>,
1157 ) -> Weight {
1158 T::WeightInfo::rc_on_session_report()
1160 }
1161
1162 fn on_new_offences(
1173 slash_session: SessionIndex,
1174 offences: Vec<rc_client::Offence<T::AccountId>>,
1175 ) -> Weight {
1176 log!(debug, "🦹 on_new_offences: {:?}", offences);
1177 let weight = T::WeightInfo::rc_on_offence(offences.len() as u32);
1178
1179 let Some(active_era) = ActiveEra::<T>::get() else {
1181 log!(warn, "🦹 on_new_offences: no active era; ignoring offence");
1182 return T::WeightInfo::rc_on_offence(0);
1183 };
1184
1185 let active_era_start_session = Rotator::<T>::active_era_start_session_index();
1186
1187 let offence_era = if slash_session >= active_era_start_session {
1190 active_era.index
1191 } else {
1192 match BondedEras::<T>::get()
1193 .iter()
1194 .rev()
1196 .find_map(|&(era, sesh)| if sesh <= slash_session { Some(era) } else { None })
1197 {
1198 Some(era) => era,
1199 None => {
1200 log!(warn, "🦹 on_offence: no era found for slash_session; ignoring offence");
1203 return T::WeightInfo::rc_on_offence(0);
1204 },
1205 }
1206 };
1207
1208 let oldest_reportable_offence_era = if T::SlashDeferDuration::get() == 0 {
1209 active_era.index.saturating_sub(T::BondingDuration::get())
1212 } else {
1213 active_era.index.saturating_sub(T::SlashDeferDuration::get().saturating_sub(1))
1216 };
1217
1218 let invulnerables = Invulnerables::<T>::get();
1219
1220 for o in offences {
1221 let slash_fraction = o.slash_fraction;
1222 let validator: <T as frame_system::Config>::AccountId = o.offender.into();
1223 if invulnerables.contains(&validator) {
1225 log!(debug, "🦹 on_offence: {:?} is invulnerable; ignoring offence", validator);
1226 continue
1227 }
1228
1229 if offence_era < oldest_reportable_offence_era {
1231 log!(warn, "🦹 on_new_offences: offence era {:?} too old; Can only accept offences from era {:?} or newer", offence_era, oldest_reportable_offence_era);
1232 Self::deposit_event(Event::<T>::OffenceTooOld {
1233 validator: validator.clone(),
1234 fraction: slash_fraction,
1235 offence_era,
1236 });
1237 continue;
1239 }
1240 let Some(exposure_overview) = <ErasStakersOverview<T>>::get(&offence_era, &validator)
1241 else {
1242 log!(
1245 warn,
1246 "🦹 on_offence: no exposure found for {:?} in era {}; ignoring offence",
1247 validator,
1248 offence_era
1249 );
1250 continue;
1251 };
1252
1253 Self::deposit_event(Event::<T>::OffenceReported {
1254 validator: validator.clone(),
1255 fraction: slash_fraction,
1256 offence_era,
1257 });
1258
1259 let prior_slash_fraction = ValidatorSlashInEra::<T>::get(offence_era, &validator)
1260 .map_or(Zero::zero(), |(f, _)| f);
1261
1262 if let Some(existing) = OffenceQueue::<T>::get(offence_era, &validator) {
1263 if slash_fraction.deconstruct() > existing.slash_fraction.deconstruct() {
1264 OffenceQueue::<T>::insert(
1265 offence_era,
1266 &validator,
1267 OffenceRecord {
1268 reporter: o.reporters.first().cloned(),
1269 reported_era: active_era.index,
1270 slash_fraction,
1271 ..existing
1272 },
1273 );
1274
1275 ValidatorSlashInEra::<T>::insert(
1277 offence_era,
1278 &validator,
1279 (slash_fraction, exposure_overview.own),
1280 );
1281
1282 log!(
1283 debug,
1284 "🦹 updated slash for {:?}: {:?} (prior: {:?})",
1285 validator,
1286 slash_fraction,
1287 prior_slash_fraction,
1288 );
1289 } else {
1290 log!(
1291 debug,
1292 "🦹 ignored slash for {:?}: {:?} (existing prior is larger: {:?})",
1293 validator,
1294 slash_fraction,
1295 prior_slash_fraction,
1296 );
1297 }
1298 } else if slash_fraction.deconstruct() > prior_slash_fraction.deconstruct() {
1299 ValidatorSlashInEra::<T>::insert(
1300 offence_era,
1301 &validator,
1302 (slash_fraction, exposure_overview.own),
1303 );
1304
1305 OffenceQueue::<T>::insert(
1306 offence_era,
1307 &validator,
1308 OffenceRecord {
1309 reporter: o.reporters.first().cloned(),
1310 reported_era: active_era.index,
1311 exposure_page: exposure_overview.page_count.saturating_sub(1),
1314 slash_fraction,
1315 prior_slash_fraction,
1316 },
1317 );
1318
1319 OffenceQueueEras::<T>::mutate(|q| {
1320 if let Some(eras) = q {
1321 log!(debug, "🦹 inserting offence era {} into existing queue", offence_era);
1322 eras.binary_search(&offence_era).err().map(|idx| {
1323 eras.try_insert(idx, offence_era).defensive_proof(
1324 "Offence era must be present in the existing queue",
1325 )
1326 });
1327 } else {
1328 let mut eras = WeakBoundedVec::default();
1329 log!(debug, "🦹 inserting offence era {} into empty queue", offence_era);
1330 let _ = eras
1331 .try_push(offence_era)
1332 .defensive_proof("Failed to push offence era into empty queue");
1333 *q = Some(eras);
1334 }
1335 });
1336
1337 log!(
1338 debug,
1339 "🦹 queued slash for {:?}: {:?} (prior: {:?})",
1340 validator,
1341 slash_fraction,
1342 prior_slash_fraction,
1343 );
1344 } else {
1345 log!(
1346 debug,
1347 "🦹 ignored slash for {:?}: {:?} (already slashed in era with prior: {:?})",
1348 validator,
1349 slash_fraction,
1350 prior_slash_fraction,
1351 );
1352 }
1353 }
1354
1355 weight
1356 }
1357
1358 fn weigh_on_new_offences(
1359 _slash_session: SessionIndex,
1360 offences: &[pallet_staking_async_rc_client::Offence<Self::AccountId>],
1361 ) -> Weight {
1362 T::WeightInfo::rc_on_offence(offences.len() as u32)
1363 }
1364}
1365
1366impl<T: Config> ScoreProvider<T::AccountId> for Pallet<T> {
1367 type Score = VoteWeight;
1368
1369 fn score(who: &T::AccountId) -> Option<Self::Score> {
1370 Self::ledger(Stash(who.clone()))
1371 .map(|l| l.active)
1372 .map(|a| {
1373 let issuance = asset::total_issuance::<T>();
1374 T::CurrencyToVote::to_vote(a, issuance)
1375 })
1376 .ok()
1377 }
1378
1379 #[cfg(feature = "runtime-benchmarks")]
1380 fn set_score_of(who: &T::AccountId, weight: Self::Score) {
1381 let active: BalanceOf<T> = weight.try_into().map_err(|_| ()).unwrap();
1384 let mut ledger = match Self::ledger(StakingAccount::Stash(who.clone())) {
1385 Ok(l) => l,
1386 Err(_) => StakingLedger::default_from(who.clone()),
1387 };
1388 ledger.active = active;
1389
1390 <Ledger<T>>::insert(who, ledger);
1391 <Bonded<T>>::insert(who, who);
1392
1393 let imbalance = asset::burn::<T>(asset::total_issuance::<T>());
1397 core::mem::forget(imbalance);
1400 }
1401}
1402
1403pub struct UseValidatorsMap<T>(core::marker::PhantomData<T>);
1407impl<T: Config> SortedListProvider<T::AccountId> for UseValidatorsMap<T> {
1408 type Score = BalanceOf<T>;
1409 type Error = ();
1410
1411 fn iter() -> Box<dyn Iterator<Item = T::AccountId>> {
1413 Box::new(Validators::<T>::iter().map(|(v, _)| v))
1414 }
1415 fn iter_from(
1416 start: &T::AccountId,
1417 ) -> Result<Box<dyn Iterator<Item = T::AccountId>>, Self::Error> {
1418 if Validators::<T>::contains_key(start) {
1419 let start_key = Validators::<T>::hashed_key_for(start);
1420 Ok(Box::new(Validators::<T>::iter_from(start_key).map(|(n, _)| n)))
1421 } else {
1422 Err(())
1423 }
1424 }
1425 fn lock() {}
1426 fn unlock() {}
1427 fn count() -> u32 {
1428 Validators::<T>::count()
1429 }
1430 fn contains(id: &T::AccountId) -> bool {
1431 Validators::<T>::contains_key(id)
1432 }
1433 fn on_insert(_: T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> {
1434 Ok(())
1436 }
1437 fn get_score(id: &T::AccountId) -> Result<Self::Score, Self::Error> {
1438 Ok(Pallet::<T>::weight_of(id).into())
1439 }
1440 fn on_update(_: &T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> {
1441 Ok(())
1443 }
1444 fn on_remove(_: &T::AccountId) -> Result<(), Self::Error> {
1445 Ok(())
1447 }
1448 fn unsafe_regenerate(
1449 _: impl IntoIterator<Item = T::AccountId>,
1450 _: Box<dyn Fn(&T::AccountId) -> Option<Self::Score>>,
1451 ) -> u32 {
1452 0
1454 }
1455 #[cfg(feature = "try-runtime")]
1456 fn try_state() -> Result<(), TryRuntimeError> {
1457 Ok(())
1458 }
1459
1460 fn unsafe_clear() {
1461 #[allow(deprecated)]
1462 Validators::<T>::remove_all();
1463 }
1464
1465 #[cfg(feature = "runtime-benchmarks")]
1466 fn score_update_worst_case(_who: &T::AccountId, _is_increase: bool) -> Self::Score {
1467 unimplemented!()
1468 }
1469}
1470
1471pub struct UseNominatorsAndValidatorsMap<T>(core::marker::PhantomData<T>);
1475impl<T: Config> SortedListProvider<T::AccountId> for UseNominatorsAndValidatorsMap<T> {
1476 type Error = ();
1477 type Score = VoteWeight;
1478
1479 fn iter() -> Box<dyn Iterator<Item = T::AccountId>> {
1480 Box::new(
1481 Validators::<T>::iter()
1482 .map(|(v, _)| v)
1483 .chain(Nominators::<T>::iter().map(|(n, _)| n)),
1484 )
1485 }
1486 fn iter_from(
1487 start: &T::AccountId,
1488 ) -> Result<Box<dyn Iterator<Item = T::AccountId>>, Self::Error> {
1489 if Validators::<T>::contains_key(start) {
1490 let start_key = Validators::<T>::hashed_key_for(start);
1491 Ok(Box::new(
1492 Validators::<T>::iter_from(start_key)
1493 .map(|(n, _)| n)
1494 .chain(Nominators::<T>::iter().map(|(x, _)| x)),
1495 ))
1496 } else if Nominators::<T>::contains_key(start) {
1497 let start_key = Nominators::<T>::hashed_key_for(start);
1498 Ok(Box::new(Nominators::<T>::iter_from(start_key).map(|(n, _)| n)))
1499 } else {
1500 Err(())
1501 }
1502 }
1503 fn lock() {}
1504 fn unlock() {}
1505 fn count() -> u32 {
1506 Nominators::<T>::count().saturating_add(Validators::<T>::count())
1507 }
1508 fn contains(id: &T::AccountId) -> bool {
1509 Nominators::<T>::contains_key(id) || Validators::<T>::contains_key(id)
1510 }
1511 fn on_insert(_: T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> {
1512 Ok(())
1514 }
1515 fn get_score(id: &T::AccountId) -> Result<Self::Score, Self::Error> {
1516 Ok(Pallet::<T>::weight_of(id))
1517 }
1518 fn on_update(_: &T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> {
1519 Ok(())
1521 }
1522 fn on_remove(_: &T::AccountId) -> Result<(), Self::Error> {
1523 Ok(())
1525 }
1526 fn unsafe_regenerate(
1527 _: impl IntoIterator<Item = T::AccountId>,
1528 _: Box<dyn Fn(&T::AccountId) -> Option<Self::Score>>,
1529 ) -> u32 {
1530 0
1532 }
1533
1534 #[cfg(feature = "try-runtime")]
1535 fn try_state() -> Result<(), TryRuntimeError> {
1536 Ok(())
1537 }
1538
1539 fn unsafe_clear() {
1540 #[allow(deprecated)]
1543 Nominators::<T>::remove_all();
1544 #[allow(deprecated)]
1545 Validators::<T>::remove_all();
1546 }
1547
1548 #[cfg(feature = "runtime-benchmarks")]
1549 fn score_update_worst_case(_who: &T::AccountId, _is_increase: bool) -> Self::Score {
1550 unimplemented!()
1551 }
1552}
1553
1554impl<T: Config> StakingInterface for Pallet<T> {
1555 type AccountId = T::AccountId;
1556 type Balance = BalanceOf<T>;
1557 type CurrencyToVote = T::CurrencyToVote;
1558
1559 fn minimum_nominator_bond() -> Self::Balance {
1560 Self::min_nominator_bond()
1561 }
1562
1563 fn minimum_validator_bond() -> Self::Balance {
1564 Self::min_validator_bond()
1565 }
1566
1567 fn stash_by_ctrl(controller: &Self::AccountId) -> Result<Self::AccountId, DispatchError> {
1568 Self::ledger(Controller(controller.clone()))
1569 .map(|l| l.stash)
1570 .map_err(|e| e.into())
1571 }
1572
1573 fn bonding_duration() -> EraIndex {
1574 T::BondingDuration::get()
1575 }
1576
1577 fn current_era() -> EraIndex {
1578 CurrentEra::<T>::get().unwrap_or(Zero::zero())
1579 }
1580
1581 fn stake(who: &Self::AccountId) -> Result<Stake<BalanceOf<T>>, DispatchError> {
1582 Self::ledger(Stash(who.clone()))
1583 .map(|l| Stake { total: l.total, active: l.active })
1584 .map_err(|e| e.into())
1585 }
1586
1587 fn bond_extra(who: &Self::AccountId, extra: Self::Balance) -> DispatchResult {
1588 Self::bond_extra(RawOrigin::Signed(who.clone()).into(), extra)
1589 }
1590
1591 fn unbond(who: &Self::AccountId, value: Self::Balance) -> DispatchResult {
1592 let ctrl = Self::bonded(who).ok_or(Error::<T>::NotStash)?;
1593 Self::unbond(RawOrigin::Signed(ctrl).into(), value)
1594 .map_err(|with_post| with_post.error)
1595 .map(|_| ())
1596 }
1597
1598 fn set_payee(stash: &Self::AccountId, reward_acc: &Self::AccountId) -> DispatchResult {
1599 ensure!(
1603 !Self::is_virtual_staker(stash) || stash != reward_acc,
1604 Error::<T>::RewardDestinationRestricted
1605 );
1606
1607 let ledger = Self::ledger(Stash(stash.clone()))?;
1608 let _ = ledger
1609 .set_payee(RewardDestination::Account(reward_acc.clone()))
1610 .defensive_proof("ledger was retrieved from storage, thus its bonded; qed.")?;
1611
1612 Ok(())
1613 }
1614
1615 fn chill(who: &Self::AccountId) -> DispatchResult {
1616 let ctrl = Self::bonded(who).ok_or(Error::<T>::NotStash)?;
1619 Self::chill(RawOrigin::Signed(ctrl).into())
1620 }
1621
1622 fn withdraw_unbonded(
1623 who: Self::AccountId,
1624 _num_slashing_spans: u32,
1625 ) -> Result<bool, DispatchError> {
1626 let ctrl = Self::bonded(&who).ok_or(Error::<T>::NotStash)?;
1627 Self::withdraw_unbonded(RawOrigin::Signed(ctrl.clone()).into(), 0)
1628 .map(|_| !StakingLedger::<T>::is_bonded(StakingAccount::Controller(ctrl)))
1629 .map_err(|with_post| with_post.error)
1630 }
1631
1632 fn bond(
1633 who: &Self::AccountId,
1634 value: Self::Balance,
1635 payee: &Self::AccountId,
1636 ) -> DispatchResult {
1637 Self::bond(
1638 RawOrigin::Signed(who.clone()).into(),
1639 value,
1640 RewardDestination::Account(payee.clone()),
1641 )
1642 }
1643
1644 fn nominate(who: &Self::AccountId, targets: Vec<Self::AccountId>) -> DispatchResult {
1645 let ctrl = Self::bonded(who).ok_or(Error::<T>::NotStash)?;
1646 let targets = targets.into_iter().map(T::Lookup::unlookup).collect::<Vec<_>>();
1647 Self::nominate(RawOrigin::Signed(ctrl).into(), targets)
1648 }
1649
1650 fn desired_validator_count() -> u32 {
1651 ValidatorCount::<T>::get()
1652 }
1653
1654 fn election_ongoing() -> bool {
1655 <T::ElectionProvider as ElectionProvider>::status().is_ok()
1656 }
1657
1658 fn force_unstake(who: Self::AccountId) -> sp_runtime::DispatchResult {
1659 Self::force_unstake(RawOrigin::Root.into(), who.clone(), 0)
1660 }
1661
1662 fn is_exposed_in_era(who: &Self::AccountId, era: &EraIndex) -> bool {
1663 ErasStakersPaged::<T>::iter_prefix((era,)).any(|((validator, _), exposure_page)| {
1664 validator == *who || exposure_page.others.iter().any(|i| i.who == *who)
1665 })
1666 }
1667
1668 fn status(
1669 who: &Self::AccountId,
1670 ) -> Result<sp_staking::StakerStatus<Self::AccountId>, DispatchError> {
1671 if !StakingLedger::<T>::is_bonded(StakingAccount::Stash(who.clone())) {
1672 return Err(Error::<T>::NotStash.into())
1673 }
1674
1675 let is_validator = Validators::<T>::contains_key(&who);
1676 let is_nominator = Nominators::<T>::get(&who);
1677
1678 use sp_staking::StakerStatus;
1679 match (is_validator, is_nominator.is_some()) {
1680 (false, false) => Ok(StakerStatus::Idle),
1681 (true, false) => Ok(StakerStatus::Validator),
1682 (false, true) => Ok(StakerStatus::Nominator(
1683 is_nominator.expect("is checked above; qed").targets.into_inner(),
1684 )),
1685 (true, true) => {
1686 defensive!("cannot be both validators and nominator");
1687 Err(Error::<T>::BadState.into())
1688 },
1689 }
1690 }
1691
1692 fn is_virtual_staker(who: &T::AccountId) -> bool {
1697 frame_system::Pallet::<T>::account_nonce(who).is_zero() &&
1698 VirtualStakers::<T>::contains_key(who)
1699 }
1700
1701 fn slash_reward_fraction() -> Perbill {
1702 SlashRewardFraction::<T>::get()
1703 }
1704
1705 sp_staking::runtime_benchmarks_enabled! {
1706 fn nominations(who: &Self::AccountId) -> Option<Vec<T::AccountId>> {
1707 Nominators::<T>::get(who).map(|n| n.targets.into_inner())
1708 }
1709
1710 fn add_era_stakers(
1711 current_era: &EraIndex,
1712 stash: &T::AccountId,
1713 exposures: Vec<(Self::AccountId, Self::Balance)>,
1714 ) {
1715 let others = exposures
1716 .iter()
1717 .map(|(who, value)| crate::IndividualExposure { who: who.clone(), value: *value })
1718 .collect::<Vec<_>>();
1719 let exposure = Exposure { total: Default::default(), own: Default::default(), others };
1720 Eras::<T>::upsert_exposure(*current_era, stash, exposure);
1721 }
1722
1723 fn set_current_era(era: EraIndex) {
1724 CurrentEra::<T>::put(era);
1725 }
1726
1727 fn max_exposure_page_size() -> Page {
1728 T::MaxExposurePageSize::get()
1729 }
1730 }
1731}
1732
1733impl<T: Config> sp_staking::StakingUnchecked for Pallet<T> {
1734 fn migrate_to_virtual_staker(who: &Self::AccountId) -> DispatchResult {
1735 asset::kill_stake::<T>(who)?;
1736 VirtualStakers::<T>::insert(who, ());
1737 Ok(())
1738 }
1739
1740 fn virtual_bond(
1744 keyless_who: &Self::AccountId,
1745 value: Self::Balance,
1746 payee: &Self::AccountId,
1747 ) -> DispatchResult {
1748 if StakingLedger::<T>::is_bonded(StakingAccount::Stash(keyless_who.clone())) {
1749 return Err(Error::<T>::AlreadyBonded.into())
1750 }
1751
1752 ensure!(keyless_who != payee, Error::<T>::RewardDestinationRestricted);
1754
1755 VirtualStakers::<T>::insert(keyless_who, ());
1757
1758 Self::deposit_event(Event::<T>::Bonded { stash: keyless_who.clone(), amount: value });
1759 let ledger = StakingLedger::<T>::new(keyless_who.clone(), value);
1760
1761 ledger.bond(RewardDestination::Account(payee.clone()))?;
1762
1763 Ok(())
1764 }
1765
1766 #[cfg(feature = "runtime-benchmarks")]
1768 fn migrate_to_direct_staker(who: &Self::AccountId) {
1769 assert!(VirtualStakers::<T>::contains_key(who));
1770 let ledger = StakingLedger::<T>::get(Stash(who.clone())).unwrap();
1771 let _ = asset::update_stake::<T>(who, ledger.total)
1772 .expect("funds must be transferred to stash");
1773 VirtualStakers::<T>::remove(who);
1774 }
1775}
1776
1777#[cfg(any(test, feature = "try-runtime"))]
1778impl<T: Config> Pallet<T> {
1779 pub(crate) fn do_try_state(_now: BlockNumberFor<T>) -> Result<(), TryRuntimeError> {
1780 session_rotation::Rotator::<T>::do_try_state()?;
1781 session_rotation::Eras::<T>::do_try_state()?;
1782
1783 use frame_support::traits::fungible::Inspect;
1784 if T::CurrencyToVote::will_downscale(T::Currency::total_issuance()).map_or(false, |x| x) {
1785 log!(warn, "total issuance will cause T::CurrencyToVote to downscale -- report to maintainers.")
1786 }
1787
1788 Self::check_ledgers()?;
1789 Self::check_bonded_consistency()?;
1790 Self::check_payees()?;
1791 Self::check_paged_exposures()?;
1792 Self::check_count()?;
1793 Self::check_slash_health()?;
1794
1795 Ok(())
1796 }
1797
1798 fn check_bonded_consistency() -> Result<(), TryRuntimeError> {
1808 use alloc::collections::btree_set::BTreeSet;
1809
1810 let mut count_controller_double = 0;
1811 let mut count_double = 0;
1812 let mut count_none = 0;
1813 let mut controllers = BTreeSet::new();
1816
1817 for (stash, controller) in <Bonded<T>>::iter() {
1818 if !controllers.insert(controller.clone()) {
1819 count_controller_double += 1;
1820 }
1821
1822 match (<Ledger<T>>::get(&stash), <Ledger<T>>::get(&controller)) {
1823 (Some(_), Some(_)) =>
1824 if stash != controller {
1828 count_double += 1;
1829 },
1830 (None, None) => {
1831 count_none += 1;
1832 },
1833 _ => {},
1834 };
1835 }
1836
1837 if count_controller_double != 0 {
1838 log!(
1839 warn,
1840 "a controller is associated with more than one ledger ({} occurrences)",
1841 count_controller_double
1842 );
1843 };
1844
1845 if count_double != 0 {
1846 log!(warn, "single tuple of (stash, controller) pair bonds more than one ledger ({} occurrences)", count_double);
1847 }
1848
1849 if count_none != 0 {
1850 log!(warn, "inconsistent bonded state: (stash, controller) pair missing associated ledger ({} occurrences)", count_none);
1851 }
1852
1853 Ok(())
1854 }
1855
1856 fn check_payees() -> Result<(), TryRuntimeError> {
1861 for (stash, _) in Bonded::<T>::iter() {
1862 ensure!(Payee::<T>::get(&stash).is_some(), "bonded ledger does not have payee set");
1863 }
1864
1865 ensure!(
1866 (Ledger::<T>::iter().count() == Payee::<T>::iter().count()) &&
1867 (Ledger::<T>::iter().count() == Bonded::<T>::iter().count()),
1868 "number of entries in payee storage items does not match the number of bonded ledgers",
1869 );
1870
1871 Ok(())
1872 }
1873
1874 fn check_count() -> Result<(), TryRuntimeError> {
1880 ensure!(
1881 <T as Config>::VoterList::count() ==
1882 Nominators::<T>::count() + Validators::<T>::count(),
1883 "wrong external count"
1884 );
1885 ensure!(
1886 <T as Config>::TargetList::count() == Validators::<T>::count(),
1887 "wrong external count"
1888 );
1889 let max_validators_bound = crate::MaxWinnersOf::<T>::get();
1890 let max_winners_per_page_bound = crate::MaxWinnersPerPageOf::<T::ElectionProvider>::get();
1891 ensure!(
1892 max_validators_bound >= max_winners_per_page_bound,
1893 "max validators should be higher than per page bounds"
1894 );
1895 ensure!(ValidatorCount::<T>::get() <= max_validators_bound, Error::<T>::TooManyValidators);
1896 Ok(())
1897 }
1898
1899 fn check_ledgers() -> Result<(), TryRuntimeError> {
1907 Bonded::<T>::iter()
1908 .map(|(stash, ctrl)| {
1909 if VirtualStakers::<T>::contains_key(stash.clone()) {
1911 ensure!(
1912 asset::staked::<T>(&stash) == Zero::zero(),
1913 "virtual stakers should not have any staked balance"
1914 );
1915 ensure!(
1916 <Bonded<T>>::get(stash.clone()).unwrap() == stash.clone(),
1917 "stash and controller should be same"
1918 );
1919 ensure!(
1920 Ledger::<T>::get(stash.clone()).unwrap().stash == stash,
1921 "ledger corrupted for virtual staker"
1922 );
1923 ensure!(
1924 frame_system::Pallet::<T>::account_nonce(&stash).is_zero(),
1925 "virtual stakers are keyless and should not have any nonce"
1926 );
1927 let reward_destination = <Payee<T>>::get(stash.clone()).unwrap();
1928 if let RewardDestination::Account(payee) = reward_destination {
1929 ensure!(
1930 payee != stash.clone(),
1931 "reward destination should not be same as stash for virtual staker"
1932 );
1933 } else {
1934 return Err(DispatchError::Other(
1935 "reward destination must be of account variant for virtual staker",
1936 ));
1937 }
1938 } else {
1939 let integrity = Self::inspect_bond_state(&stash);
1940 if integrity != Ok(LedgerIntegrityState::Ok) {
1941 log!(
1943 error,
1944 "defensive: bonded stash {:?} has inconsistent ledger state: {:?}",
1945 stash,
1946 integrity
1947 );
1948 }
1949 }
1950
1951 Self::ensure_ledger_consistent(&ctrl)?;
1952 Self::ensure_ledger_role_and_min_bond(&ctrl)?;
1953 Ok(())
1954 })
1955 .collect::<Result<Vec<_>, _>>()?;
1956 Ok(())
1957 }
1958
1959 fn check_paged_exposures() -> Result<(), TryRuntimeError> {
1964 use alloc::collections::btree_map::BTreeMap;
1965 use sp_staking::PagedExposureMetadata;
1966
1967 let mut exposures: BTreeMap<T::AccountId, PagedExposureMetadata<BalanceOf<T>>> =
1969 BTreeMap::new();
1970 let era = ActiveEra::<T>::get().unwrap().index;
1971 let accumulator_default = PagedExposureMetadata {
1972 total: Zero::zero(),
1973 own: Zero::zero(),
1974 nominator_count: 0,
1975 page_count: 0,
1976 };
1977
1978 ErasStakersPaged::<T>::iter_prefix((era,))
1979 .map(|((validator, _page), expo)| {
1980 ensure!(
1981 expo.page_total ==
1982 expo.others.iter().map(|e| e.value).fold(Zero::zero(), |acc, x| acc + x),
1983 "wrong total exposure for the page.",
1984 );
1985
1986 let metadata = exposures.get(&validator).unwrap_or(&accumulator_default);
1987 exposures.insert(
1988 validator,
1989 PagedExposureMetadata {
1990 total: metadata.total + expo.page_total,
1991 own: metadata.own,
1992 nominator_count: metadata.nominator_count + expo.others.len() as u32,
1993 page_count: metadata.page_count + 1,
1994 },
1995 );
1996
1997 Ok(())
1998 })
1999 .collect::<Result<(), TryRuntimeError>>()?;
2000
2001 exposures
2002 .iter()
2003 .map(|(validator, metadata)| {
2004 let actual_overview = ErasStakersOverview::<T>::get(era, validator);
2005
2006 ensure!(actual_overview.is_some(), "No overview found for a paged exposure");
2007 let actual_overview = actual_overview.unwrap();
2008
2009 ensure!(
2010 actual_overview.total == metadata.total + actual_overview.own,
2011 "Exposure metadata does not have correct total exposed stake."
2012 );
2013 ensure!(
2014 actual_overview.nominator_count == metadata.nominator_count,
2015 "Exposure metadata does not have correct count of nominators."
2016 );
2017 ensure!(
2018 actual_overview.page_count == metadata.page_count,
2019 "Exposure metadata does not have correct count of pages."
2020 );
2021
2022 Ok(())
2023 })
2024 .collect::<Result<(), TryRuntimeError>>()
2025 }
2026
2027 fn check_slash_health() -> Result<(), TryRuntimeError> {
2029 let offence_queue_eras = OffenceQueueEras::<T>::get().unwrap_or_default().into_inner();
2031 let mut sorted_offence_queue_eras = offence_queue_eras.clone();
2032 sorted_offence_queue_eras.sort();
2033 ensure!(
2034 sorted_offence_queue_eras == offence_queue_eras,
2035 "Offence queue eras are not sorted"
2036 );
2037 drop(sorted_offence_queue_eras);
2038
2039 let active_era = Rotator::<T>::active_era();
2041 let oldest_unprocessed_offence_era =
2042 offence_queue_eras.first().cloned().unwrap_or(active_era);
2043
2044 let oldest_unprocessed_offence_age =
2048 active_era.saturating_sub(oldest_unprocessed_offence_era);
2049
2050 if oldest_unprocessed_offence_age > 2.min(T::BondingDuration::get()) {
2052 log!(
2053 warn,
2054 "Offence queue has unprocessed offences from older than 2 eras: oldest offence era in queue {:?} (active era: {:?})",
2055 oldest_unprocessed_offence_era,
2056 active_era
2057 );
2058 }
2059
2060 ensure!(
2062 oldest_unprocessed_offence_age < T::BondingDuration::get() - 1,
2063 "offences from era less than 3 eras old from active era not processed yet"
2064 );
2065
2066 for e in offence_queue_eras {
2068 let count = OffenceQueue::<T>::iter_prefix(e).count();
2069 ensure!(count > 0, "Offence queue is empty for era listed in offence queue eras");
2070 log!(info, "Offence queue for era {:?} has {:?} offences queued", e, count);
2071 }
2072
2073 for era in (active_era.saturating_sub(T::BondingDuration::get()))..(active_era) {
2077 Self::ensure_era_slashes_applied(era)?;
2081 }
2082
2083 for (era, _) in CancelledSlashes::<T>::iter() {
2085 ensure!(era >= active_era, "Found cancelled slashes for era before active era");
2086 }
2087
2088 Ok(())
2089 }
2090
2091 fn ensure_ledger_role_and_min_bond(ctrl: &T::AccountId) -> Result<(), TryRuntimeError> {
2092 let ledger = Self::ledger(StakingAccount::Controller(ctrl.clone()))?;
2093 let stash = ledger.stash;
2094
2095 let is_nominator = Nominators::<T>::contains_key(&stash);
2096 let is_validator = Validators::<T>::contains_key(&stash);
2097
2098 match (is_nominator, is_validator) {
2099 (false, false) => {
2100 if ledger.active < Self::min_chilled_bond() {
2101 log!(warn, "Chilled stash {:?} has less than minimum bond", stash);
2102 }
2103 },
2105 (true, false) => {
2106 if ledger.active < Self::min_nominator_bond() {
2108 log!(warn, "Nominator {:?} has less than minimum bond", stash);
2109 }
2110 },
2111 (false, true) => {
2112 if ledger.active < Self::min_validator_bond() {
2114 log!(warn, "Validator {:?} has less than minimum bond", stash);
2115 }
2116 },
2117 (true, true) => {
2118 ensure!(false, "Stash cannot be both nominator and validator");
2119 },
2120 }
2121 Ok(())
2122 }
2123
2124 fn ensure_ledger_consistent(ctrl: &T::AccountId) -> Result<(), TryRuntimeError> {
2125 let ledger = Self::ledger(StakingAccount::Controller(ctrl.clone()))?;
2127
2128 let real_total: BalanceOf<T> =
2129 ledger.unlocking.iter().fold(ledger.active, |a, c| a + c.value);
2130 ensure!(real_total == ledger.total, "ledger.total corrupt");
2131
2132 Ok(())
2133 }
2134}