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> {
90 MinValidatorBond::<T>::get().max(asset::existential_deposit::<T>())
91 }
92
93 pub(crate) fn min_nominator_bond() -> BalanceOf<T> {
95 MinNominatorBond::<T>::get().max(asset::existential_deposit::<T>())
96 }
97
98 pub fn ledger(account: StakingAccount<T::AccountId>) -> Result<StakingLedger<T>, Error<T>> {
100 StakingLedger::<T>::get(account)
101 }
102
103 pub fn payee(account: StakingAccount<T::AccountId>) -> Option<RewardDestination<T::AccountId>> {
104 StakingLedger::<T>::reward_destination(account)
105 }
106
107 pub fn bonded(stash: &T::AccountId) -> Option<T::AccountId> {
109 StakingLedger::<T>::paired_account(Stash(stash.clone()))
110 }
111
112 pub(crate) fn inspect_bond_state(
119 stash: &T::AccountId,
120 ) -> Result<LedgerIntegrityState, Error<T>> {
121 let hold_or_lock = asset::staked::<T>(&stash)
123 .max(T::OldCurrency::balance_locked(STAKING_ID, &stash).into());
124
125 let controller = <Bonded<T>>::get(stash).ok_or_else(|| {
126 if hold_or_lock == Zero::zero() {
127 Error::<T>::NotStash
128 } else {
129 Error::<T>::BadState
130 }
131 })?;
132
133 match Ledger::<T>::get(controller) {
134 Some(ledger) => {
135 if ledger.stash != *stash {
136 Ok(LedgerIntegrityState::Corrupted)
137 } else {
138 if hold_or_lock != ledger.total {
139 Ok(LedgerIntegrityState::LockCorrupted)
140 } else {
141 Ok(LedgerIntegrityState::Ok)
142 }
143 }
144 },
145 None => Ok(LedgerIntegrityState::CorruptedKilled),
146 }
147 }
148
149 pub fn slashable_balance_of(stash: &T::AccountId) -> BalanceOf<T> {
151 Self::ledger(Stash(stash.clone())).map(|l| l.active).unwrap_or_default()
153 }
154
155 pub fn slashable_balance_of_vote_weight(
157 stash: &T::AccountId,
158 issuance: BalanceOf<T>,
159 ) -> VoteWeight {
160 T::CurrencyToVote::to_vote(Self::slashable_balance_of(stash), issuance)
161 }
162
163 pub fn weight_of_fn() -> Box<dyn Fn(&T::AccountId) -> VoteWeight> {
168 let issuance = asset::total_issuance::<T>();
171 Box::new(move |who: &T::AccountId| -> VoteWeight {
172 Self::slashable_balance_of_vote_weight(who, issuance)
173 })
174 }
175
176 pub fn weight_of(who: &T::AccountId) -> VoteWeight {
178 let issuance = asset::total_issuance::<T>();
179 Self::slashable_balance_of_vote_weight(who, issuance)
180 }
181
182 pub(crate) fn check_slash_cancelled(
184 era: EraIndex,
185 validator: &T::AccountId,
186 slash_fraction: Perbill,
187 ) -> bool {
188 let cancelled_slashes = CancelledSlashes::<T>::get(&era);
189 cancelled_slashes.iter().any(|(cancelled_validator, cancel_fraction)| {
190 *cancelled_validator == *validator && *cancel_fraction >= slash_fraction
191 })
192 }
193
194 pub(super) fn do_bond_extra(stash: &T::AccountId, additional: BalanceOf<T>) -> DispatchResult {
195 let mut ledger = Self::ledger(StakingAccount::Stash(stash.clone()))?;
196
197 let extra = if Self::is_virtual_staker(stash) {
200 additional
201 } else {
202 additional.min(asset::free_to_stake::<T>(stash))
204 };
205
206 ledger.total = ledger.total.checked_add(&extra).ok_or(ArithmeticError::Overflow)?;
207 ledger.active = ledger.active.checked_add(&extra).ok_or(ArithmeticError::Overflow)?;
208 ensure!(ledger.active >= Self::min_chilled_bond(), Error::<T>::InsufficientBond);
210
211 ledger.update()?;
213 if T::VoterList::contains(stash) {
215 let _ = T::VoterList::on_update(&stash, Self::weight_of(stash));
217 }
218
219 Self::deposit_event(Event::<T>::Bonded { stash: stash.clone(), amount: extra });
220
221 Ok(())
222 }
223
224 fn calculate_earliest_withdrawal_era(active_era: EraIndex) -> EraIndex {
228 let earliest_unlock_era_by_offence_queue = OffenceQueueEras::<T>::get()
230 .as_ref()
231 .and_then(|eras| eras.first())
232 .copied()
233 .unwrap_or(active_era)
235 .saturating_sub(1)
238 .saturating_add(T::BondingDuration::get());
245
246 active_era.min(earliest_unlock_era_by_offence_queue)
252 }
253
254 pub(super) fn do_withdraw_unbonded(controller: &T::AccountId) -> Result<Weight, DispatchError> {
255 let mut ledger = Self::ledger(Controller(controller.clone()))?;
256 let (stash, old_total) = (ledger.stash.clone(), ledger.total);
257 let active_era = Rotator::<T>::active_era();
258
259 if active_era > 1 {
261 Self::ensure_era_slashes_applied(active_era.saturating_sub(1))?;
262 }
263
264 let earliest_era_to_withdraw = Self::calculate_earliest_withdrawal_era(active_era);
265
266 log!(
267 debug,
268 "Withdrawing unbonded stake. Active_era is: {:?} | \
269 Earliest era we can allow withdrawing: {:?}",
270 active_era,
271 earliest_era_to_withdraw
272 );
273
274 ledger = ledger.consolidate_unlocked(earliest_era_to_withdraw);
276
277 let new_total = ledger.total;
278 debug_assert!(
279 new_total <= old_total,
280 "consolidate_unlocked should never increase the total balance of the ledger"
281 );
282
283 let used_weight = if ledger.unlocking.is_empty() &&
284 (ledger.active < Self::min_chilled_bond() || ledger.active.is_zero())
285 {
286 Self::kill_stash(&ledger.stash)?;
290
291 T::WeightInfo::withdraw_unbonded_kill()
292 } else {
293 ledger.update()?;
295
296 T::WeightInfo::withdraw_unbonded_update()
298 };
299
300 if new_total < old_total {
303 let value = old_total.defensive_saturating_sub(new_total);
305 Self::deposit_event(Event::<T>::Withdrawn { stash, amount: value });
306
307 T::EventListeners::on_withdraw(controller, value);
309 }
310
311 Ok(used_weight)
312 }
313
314 fn ensure_era_slashes_applied(era: EraIndex) -> Result<(), DispatchError> {
315 ensure!(
316 !UnappliedSlashes::<T>::contains_prefix(era),
317 Error::<T>::UnappliedSlashesInPreviousEra
318 );
319 Ok(())
320 }
321
322 pub(super) fn do_payout_stakers(
323 validator_stash: T::AccountId,
324 era: EraIndex,
325 ) -> DispatchResultWithPostInfo {
326 let page = Eras::<T>::get_next_claimable_page(era, &validator_stash).ok_or_else(|| {
327 Error::<T>::AlreadyClaimed.with_weight(T::WeightInfo::payout_stakers_alive_staked(0))
328 })?;
329
330 Self::do_payout_stakers_by_page(validator_stash, era, page)
331 }
332
333 pub(super) fn do_payout_stakers_by_page(
334 validator_stash: T::AccountId,
335 era: EraIndex,
336 page: Page,
337 ) -> DispatchResultWithPostInfo {
338 let current_era = CurrentEra::<T>::get().ok_or_else(|| {
340 Error::<T>::InvalidEraToReward
341 .with_weight(T::WeightInfo::payout_stakers_alive_staked(0))
342 })?;
343
344 let history_depth = T::HistoryDepth::get();
345
346 ensure!(
347 era <= current_era && era >= current_era.saturating_sub(history_depth),
348 Error::<T>::InvalidEraToReward
349 .with_weight(T::WeightInfo::payout_stakers_alive_staked(0))
350 );
351
352 ensure!(
353 page < Eras::<T>::exposure_page_count(era, &validator_stash),
354 Error::<T>::InvalidPage.with_weight(T::WeightInfo::payout_stakers_alive_staked(0))
355 );
356
357 let era_payout = Eras::<T>::get_validators_reward(era).ok_or_else(|| {
359 Error::<T>::InvalidEraToReward
360 .with_weight(T::WeightInfo::payout_stakers_alive_staked(0))
361 })?;
362
363 let account = StakingAccount::Stash(validator_stash.clone());
364 let ledger = Self::ledger(account.clone()).or_else(|_| {
365 if StakingLedger::<T>::is_bonded(account) {
366 Err(Error::<T>::NotController.into())
367 } else {
368 Err(Error::<T>::NotStash.with_weight(T::WeightInfo::payout_stakers_alive_staked(0)))
369 }
370 })?;
371
372 ledger.clone().update()?;
373
374 let stash = ledger.stash.clone();
375
376 if Eras::<T>::is_rewards_claimed(era, &stash, page) {
377 return Err(Error::<T>::AlreadyClaimed
378 .with_weight(T::WeightInfo::payout_stakers_alive_staked(0)));
379 }
380
381 Eras::<T>::set_rewards_as_claimed(era, &stash, page);
382
383 let exposure = Eras::<T>::get_paged_exposure(era, &stash, page).ok_or_else(|| {
384 Error::<T>::InvalidEraToReward
385 .with_weight(T::WeightInfo::payout_stakers_alive_staked(0))
386 })?;
387
388 let era_reward_points = Eras::<T>::get_reward_points(era);
398 let total_reward_points = era_reward_points.total;
399 let validator_reward_points =
400 era_reward_points.individual.get(&stash).copied().unwrap_or_else(Zero::zero);
401
402 if validator_reward_points.is_zero() {
404 return Ok(Some(T::WeightInfo::payout_stakers_alive_staked(0)).into());
405 }
406
407 let validator_total_reward_part =
410 Perbill::from_rational(validator_reward_points, total_reward_points);
411
412 let validator_total_payout = validator_total_reward_part * era_payout;
414
415 let validator_commission = Eras::<T>::get_validator_commission(era, &ledger.stash);
416 let validator_total_commission_payout = validator_commission * validator_total_payout;
418
419 let validator_leftover_payout =
420 validator_total_payout.defensive_saturating_sub(validator_total_commission_payout);
421 let validator_exposure_part = Perbill::from_rational(exposure.own(), exposure.total());
423 let validator_staking_payout = validator_exposure_part * validator_leftover_payout;
424 let page_stake_part = Perbill::from_rational(exposure.page_total(), exposure.total());
425 let validator_commission_payout = page_stake_part * validator_total_commission_payout;
427
428 Self::deposit_event(Event::<T>::PayoutStarted {
429 era_index: era,
430 validator_stash: stash.clone(),
431 page,
432 next: Eras::<T>::get_next_claimable_page(era, &stash),
433 });
434
435 let mut total_imbalance = PositiveImbalanceOf::<T>::zero();
436 if let Some((imbalance, dest)) =
438 Self::make_payout(&stash, validator_staking_payout + validator_commission_payout)
439 {
440 Self::deposit_event(Event::<T>::Rewarded { stash, dest, amount: imbalance.peek() });
441 total_imbalance.subsume(imbalance);
442 }
443
444 let mut nominator_payout_count: u32 = 0;
448
449 for nominator in exposure.others().iter() {
452 let nominator_exposure_part = Perbill::from_rational(nominator.value, exposure.total());
453
454 let nominator_reward: BalanceOf<T> =
455 nominator_exposure_part * validator_leftover_payout;
456 if let Some((imbalance, dest)) = Self::make_payout(&nominator.who, nominator_reward) {
458 nominator_payout_count += 1;
460 let e = Event::<T>::Rewarded {
461 stash: nominator.who.clone(),
462 dest,
463 amount: imbalance.peek(),
464 };
465 Self::deposit_event(e);
466 total_imbalance.subsume(imbalance);
467 }
468 }
469
470 T::Reward::on_unbalanced(total_imbalance);
471 debug_assert!(nominator_payout_count <= T::MaxExposurePageSize::get());
472
473 Ok(Some(T::WeightInfo::payout_stakers_alive_staked(nominator_payout_count)).into())
474 }
475
476 pub(crate) fn chill_stash(stash: &T::AccountId) {
478 let chilled_as_validator = Self::do_remove_validator(stash);
479 let chilled_as_nominator = Self::do_remove_nominator(stash);
480 if chilled_as_validator || chilled_as_nominator {
481 Self::deposit_event(Event::<T>::Chilled { stash: stash.clone() });
482 }
483 }
484
485 fn make_payout(
488 stash: &T::AccountId,
489 amount: BalanceOf<T>,
490 ) -> Option<(PositiveImbalanceOf<T>, RewardDestination<T::AccountId>)> {
491 if amount.is_zero() {
493 return None;
494 }
495 let dest = Self::payee(StakingAccount::Stash(stash.clone()))?;
496
497 let maybe_imbalance = match dest {
498 RewardDestination::Stash => asset::mint_into_existing::<T>(stash, amount),
499 RewardDestination::Staked => Self::ledger(Stash(stash.clone()))
500 .and_then(|mut ledger| {
501 ledger.active += amount;
502 ledger.total += amount;
503 let r = asset::mint_into_existing::<T>(stash, amount);
504
505 let _ = ledger
506 .update()
507 .defensive_proof("ledger fetched from storage, so it exists; qed.");
508
509 Ok(r)
510 })
511 .unwrap_or_default(),
512 RewardDestination::Account(ref dest_account) =>
513 Some(asset::mint_creating::<T>(&dest_account, amount)),
514 RewardDestination::None => None,
515 #[allow(deprecated)]
516 RewardDestination::Controller => Self::bonded(stash)
517 .map(|controller| {
518 defensive!("Paying out controller as reward destination which is deprecated and should be migrated.");
519 asset::mint_creating::<T>(&controller, amount)
522 }),
523 };
524 maybe_imbalance.map(|imbalance| (imbalance, dest))
525 }
526
527 pub(crate) fn kill_stash(stash: &T::AccountId) -> DispatchResult {
535 StakingLedger::<T>::kill(&stash)?;
538
539 Self::do_remove_validator(&stash);
540 Self::do_remove_nominator(&stash);
541
542 LastValidatorEra::<T>::remove(&stash);
544
545 Ok(())
546 }
547
548 #[cfg(test)]
549 pub(crate) fn reward_by_ids(validators_points: impl IntoIterator<Item = (T::AccountId, u32)>) {
550 Eras::<T>::reward_active_era(validators_points)
551 }
552
553 pub(crate) fn set_force_era(mode: Forcing) {
555 log!(info, "Setting force era mode {:?}.", mode);
556 ForceEra::<T>::put(mode);
557 Self::deposit_event(Event::<T>::ForceEra { mode });
558 }
559
560 #[cfg(feature = "runtime-benchmarks")]
561 pub fn add_era_stakers(
562 current_era: EraIndex,
563 stash: T::AccountId,
564 exposure: Exposure<T::AccountId, BalanceOf<T>>,
565 ) {
566 Eras::<T>::upsert_exposure(current_era, &stash, exposure);
567 }
568
569 #[cfg(feature = "runtime-benchmarks")]
570 pub fn set_slash_reward_fraction(fraction: Perbill) {
571 SlashRewardFraction::<T>::put(fraction);
572 }
573
574 pub(crate) fn get_npos_voters(
584 bounds: DataProviderBounds,
585 status: &SnapshotStatus<T::AccountId>,
586 ) -> Vec<VoterOf<Self>> {
587 let mut voters_size_tracker: StaticTracker<Self> = StaticTracker::default();
588
589 let page_len_prediction = {
590 let all_voter_count = T::VoterList::count();
591 bounds.count.unwrap_or(all_voter_count.into()).min(all_voter_count.into()).0
592 };
593
594 let mut all_voters = Vec::<_>::with_capacity(page_len_prediction as usize);
595
596 let weight_of = Self::weight_of_fn();
598
599 let mut voters_seen = 0u32;
600 let mut validators_taken = 0u32;
601 let mut nominators_taken = 0u32;
602 let mut min_active_stake = u64::MAX;
603
604 let mut sorted_voters = match status {
605 SnapshotStatus::Waiting => T::VoterList::iter(),
607 SnapshotStatus::Ongoing(account_id) => T::VoterList::iter_from(&account_id)
609 .defensive_unwrap_or(Box::new(vec![].into_iter())),
610 SnapshotStatus::Consumed => Box::new(vec![].into_iter()),
612 };
613
614 while all_voters.len() < page_len_prediction as usize &&
615 voters_seen < (NPOS_MAX_ITERATIONS_COEFFICIENT * page_len_prediction as u32)
616 {
617 let voter = match sorted_voters.next() {
618 Some(voter) => {
619 voters_seen.saturating_inc();
620 voter
621 },
622 None => break,
623 };
624
625 let voter_weight = weight_of(&voter);
626 if voter_weight.is_zero() {
628 log!(debug, "voter's active balance is 0. skip this voter.");
629 continue;
630 }
631
632 if let Some(Nominations { targets, .. }) = <Nominators<T>>::get(&voter) {
633 if !targets.is_empty() {
634 let voter = (voter, voter_weight, targets);
639 if voters_size_tracker.try_register_voter(&voter, &bounds).is_err() {
640 Self::deposit_event(Event::<T>::SnapshotVotersSizeExceeded {
642 size: voters_size_tracker.size as u32,
643 });
644 break;
645 }
646
647 all_voters.push(voter);
648 nominators_taken.saturating_inc();
649 } else {
650 defensive!("non-nominator fetched from voter list: {:?}", voter);
651 }
653 min_active_stake =
654 if voter_weight < min_active_stake { voter_weight } else { min_active_stake };
655 } else if Validators::<T>::contains_key(&voter) {
656 let self_vote = (
658 voter.clone(),
659 voter_weight,
660 vec![voter.clone()]
661 .try_into()
662 .expect("`MaxVotesPerVoter` must be greater than or equal to 1"),
663 );
664
665 if voters_size_tracker.try_register_voter(&self_vote, &bounds).is_err() {
666 Self::deposit_event(Event::<T>::SnapshotVotersSizeExceeded {
668 size: voters_size_tracker.size as u32,
669 });
670 break;
671 }
672 all_voters.push(self_vote);
673 validators_taken.saturating_inc();
674 } else {
675 defensive!(
681 "invalid item in `VoterList`: {:?}, this nominator probably has too many nominations now",
682 voter,
683 );
684 }
685 }
686
687 debug_assert!(all_voters.capacity() == page_len_prediction as usize);
689
690 let min_active_stake: T::CurrencyBalance =
691 if all_voters.is_empty() { Zero::zero() } else { min_active_stake.into() };
692
693 MinimumActiveStake::<T>::put(min_active_stake);
694
695 all_voters
696 }
697
698 pub fn get_npos_targets(bounds: DataProviderBounds) -> Vec<T::AccountId> {
704 let mut targets_size_tracker: StaticTracker<Self> = StaticTracker::default();
705
706 let final_predicted_len = {
707 let all_target_count = T::TargetList::count();
708 bounds.count.unwrap_or(all_target_count.into()).min(all_target_count.into()).0
709 };
710
711 let mut all_targets = Vec::<T::AccountId>::with_capacity(final_predicted_len as usize);
712 let mut targets_seen = 0;
713
714 let mut targets_iter = T::TargetList::iter();
715 while all_targets.len() < final_predicted_len as usize &&
716 targets_seen < (NPOS_MAX_ITERATIONS_COEFFICIENT * final_predicted_len as u32)
717 {
718 let target = match targets_iter.next() {
719 Some(target) => {
720 targets_seen.saturating_inc();
721 target
722 },
723 None => break,
724 };
725
726 if targets_size_tracker.try_register_target(target.clone(), &bounds).is_err() {
727 log!(warn, "npos targets size exceeded, stopping iteration.");
729 Self::deposit_event(Event::<T>::SnapshotTargetsSizeExceeded {
730 size: targets_size_tracker.size as u32,
731 });
732 break;
733 }
734
735 if Validators::<T>::contains_key(&target) {
736 all_targets.push(target);
737 }
738 }
739
740 log!(debug, "[bounds {:?}] generated {} npos targets", bounds, all_targets.len());
741
742 all_targets
743 }
744
745 pub fn do_add_nominator(who: &T::AccountId, nominations: Nominations<T>) {
754 if !Nominators::<T>::contains_key(who) {
755 let _ = T::VoterList::on_insert(who.clone(), Self::weight_of(who))
757 .defensive_unwrap_or_default();
758 }
759 Nominators::<T>::insert(who, nominations);
760 }
761
762 pub fn do_remove_nominator(who: &T::AccountId) -> bool {
771 let outcome = if Nominators::<T>::contains_key(who) {
772 Nominators::<T>::remove(who);
773 let _ = T::VoterList::on_remove(who);
774 true
775 } else {
776 false
777 };
778
779 outcome
780 }
781
782 pub fn do_add_validator(who: &T::AccountId, prefs: ValidatorPrefs) {
790 if !Validators::<T>::contains_key(who) {
791 let _ = T::VoterList::on_insert(who.clone(), Self::weight_of(who));
793 }
794 Validators::<T>::insert(who, prefs);
795 }
796
797 pub fn do_remove_validator(who: &T::AccountId) -> bool {
805 let outcome = if Validators::<T>::contains_key(who) {
806 Validators::<T>::remove(who);
807 let _ = T::VoterList::on_remove(who);
808 true
809 } else {
810 false
811 };
812
813 outcome
814 }
815
816 pub(crate) fn register_weight(weight: Weight) {
820 <frame_system::Pallet<T>>::register_extra_weight_unchecked(
821 weight,
822 DispatchClass::Mandatory,
823 );
824 }
825
826 pub fn eras_stakers(
833 era: EraIndex,
834 account: &T::AccountId,
835 ) -> Exposure<T::AccountId, BalanceOf<T>> {
836 Eras::<T>::get_full_exposure(era, account)
837 }
838
839 pub(super) fn do_migrate_currency(stash: &T::AccountId) -> DispatchResult {
840 if Self::is_virtual_staker(stash) {
841 return Self::do_migrate_virtual_staker(stash);
842 }
843
844 let ledger = Self::ledger(Stash(stash.clone()))?;
845 let staked: BalanceOf<T> = T::OldCurrency::balance_locked(STAKING_ID, stash).into();
846 ensure!(!staked.is_zero(), Error::<T>::AlreadyMigrated);
847 ensure!(ledger.total == staked, Error::<T>::BadState);
848
849 T::OldCurrency::remove_lock(STAKING_ID, &stash);
851
852 let max_hold = asset::free_to_stake::<T>(&stash);
854 let force_withdraw = if max_hold >= staked {
855 asset::update_stake::<T>(&stash, staked)?;
857 Zero::zero()
858 } else {
859 let force_withdraw = staked.saturating_sub(max_hold);
862
863 StakingLedger {
866 total: max_hold,
867 active: ledger.active.saturating_sub(force_withdraw),
868 ..ledger
870 }
871 .update()?;
872 force_withdraw
873 };
874
875 frame_system::Pallet::<T>::dec_consumers(&stash);
877
878 Self::deposit_event(Event::<T>::CurrencyMigrated { stash: stash.clone(), force_withdraw });
879 Ok(())
880 }
881
882 fn do_migrate_virtual_staker(stash: &T::AccountId) -> DispatchResult {
883 frame_system::Pallet::<T>::dec_consumers(&stash);
886
887 let actual_providers = frame_system::Pallet::<T>::providers(stash);
892
893 let expected_providers =
894 if asset::free_to_stake::<T>(&stash) >= asset::existential_deposit::<T>() {
897 2
898 } else {
899 1
900 };
901
902 ensure!(actual_providers <= expected_providers, Error::<T>::BadState);
904
905 ensure!(actual_providers == expected_providers, Error::<T>::AlreadyMigrated);
907
908 let _ = frame_system::Pallet::<T>::dec_providers(&stash)?;
910
911 return Ok(());
912 }
913}
914
915impl<T: Config> Pallet<T> {
916 pub fn api_nominations_quota(balance: BalanceOf<T>) -> u32 {
920 T::NominationsQuota::get_quota(balance)
921 }
922
923 pub fn api_eras_stakers(
924 era: EraIndex,
925 account: T::AccountId,
926 ) -> Exposure<T::AccountId, BalanceOf<T>> {
927 Self::eras_stakers(era, &account)
928 }
929
930 pub fn api_eras_stakers_page_count(era: EraIndex, account: T::AccountId) -> Page {
931 Eras::<T>::exposure_page_count(era, &account)
932 }
933
934 pub fn api_pending_rewards(era: EraIndex, account: T::AccountId) -> bool {
935 Eras::<T>::pending_rewards(era, &account)
936 }
937}
938
939impl<T: Config> ElectionDataProvider for Pallet<T> {
940 type AccountId = T::AccountId;
941 type BlockNumber = BlockNumberFor<T>;
942 type MaxVotesPerVoter = MaxNominationsOf<T>;
943
944 fn desired_targets() -> data_provider::Result<u32> {
945 Self::register_weight(T::DbWeight::get().reads(1));
946 Ok(ValidatorCount::<T>::get())
947 }
948
949 fn electing_voters(
950 bounds: DataProviderBounds,
951 page: PageIndex,
952 ) -> data_provider::Result<Vec<VoterOf<Self>>> {
953 let mut status = VoterSnapshotStatus::<T>::get();
954 let voters = Self::get_npos_voters(bounds, &status);
955
956 match (page, &status) {
958 (0, _) => status = SnapshotStatus::Waiting,
960
961 (_, SnapshotStatus::Waiting) | (_, SnapshotStatus::Ongoing(_)) => {
962 let maybe_last = voters.last().map(|(x, _, _)| x).cloned();
963
964 if let Some(ref last) = maybe_last {
965 let has_next =
966 T::VoterList::iter_from(last).ok().and_then(|mut i| i.next()).is_some();
967 if has_next {
968 status = SnapshotStatus::Ongoing(last.clone());
969 } else {
970 status = SnapshotStatus::Consumed;
971 }
972 }
973 },
974 (_, SnapshotStatus::Consumed) => (),
976 }
977
978 log!(
979 debug,
980 "[page {}, (next) status {:?}, bounds {:?}] generated {} npos voters [first: {:?}, last: {:?}]",
981 page,
982 status,
983 bounds,
984 voters.len(),
985 voters.first().map(|(x, y, _)| (x, y)),
986 voters.last().map(|(x, y, _)| (x, y)),
987 );
988
989 match status {
990 SnapshotStatus::Ongoing(_) => T::VoterList::lock(),
991 _ => T::VoterList::unlock(),
992 }
993
994 VoterSnapshotStatus::<T>::put(status);
995 debug_assert!(!bounds.slice_exhausted(&voters));
996
997 Ok(voters)
998 }
999
1000 fn electing_voters_stateless(
1001 bounds: DataProviderBounds,
1002 ) -> data_provider::Result<Vec<VoterOf<Self>>> {
1003 let voters = Self::get_npos_voters(bounds, &SnapshotStatus::Waiting);
1004 log!(debug, "[stateless, bounds {:?}] generated {} npos voters", bounds, voters.len(),);
1005 Ok(voters)
1006 }
1007
1008 fn electable_targets(
1009 bounds: DataProviderBounds,
1010 page: PageIndex,
1011 ) -> data_provider::Result<Vec<T::AccountId>> {
1012 if page > 0 {
1013 log!(warn, "multi-page target snapshot not supported, returning page 0.");
1014 }
1015
1016 let targets = Self::get_npos_targets(bounds);
1017 if bounds.exhausted(None, CountBound(targets.len() as u32).into()) {
1018 return Err("Target snapshot too big");
1019 }
1020
1021 debug_assert!(!bounds.slice_exhausted(&targets));
1022
1023 Ok(targets)
1024 }
1025
1026 fn next_election_prediction(_: BlockNumberFor<T>) -> BlockNumberFor<T> {
1027 debug_assert!(false, "this is deprecated and not used anymore");
1028 sp_runtime::traits::Bounded::max_value()
1029 }
1030
1031 #[cfg(feature = "runtime-benchmarks")]
1032 fn fetch_page(page: PageIndex) {
1033 session_rotation::EraElectionPlanner::<T>::do_elect_paged(page);
1034 }
1035
1036 #[cfg(feature = "runtime-benchmarks")]
1037 fn add_voter(
1038 voter: T::AccountId,
1039 weight: VoteWeight,
1040 targets: BoundedVec<T::AccountId, Self::MaxVotesPerVoter>,
1041 ) {
1042 let stake = <BalanceOf<T>>::try_from(weight).unwrap_or_else(|_| {
1043 panic!("cannot convert a VoteWeight into BalanceOf, benchmark needs reconfiguring.")
1044 });
1045 <Bonded<T>>::insert(voter.clone(), voter.clone());
1046 <Ledger<T>>::insert(voter.clone(), StakingLedger::<T>::new(voter.clone(), stake));
1047
1048 Self::do_add_nominator(&voter, Nominations { targets, submitted_in: 0, suppressed: false });
1049 }
1050
1051 #[cfg(feature = "runtime-benchmarks")]
1052 fn add_target(target: T::AccountId) {
1053 let stake = (Self::min_validator_bond() + 1u32.into()) * 100u32.into();
1054 <Bonded<T>>::insert(target.clone(), target.clone());
1055 <Ledger<T>>::insert(target.clone(), StakingLedger::<T>::new(target.clone(), stake));
1056 Self::do_add_validator(
1057 &target,
1058 ValidatorPrefs { commission: Perbill::zero(), blocked: false },
1059 );
1060 }
1061
1062 #[cfg(feature = "runtime-benchmarks")]
1063 fn clear() {
1064 #[allow(deprecated)]
1065 <Bonded<T>>::remove_all(None);
1066 #[allow(deprecated)]
1067 <Ledger<T>>::remove_all(None);
1068 #[allow(deprecated)]
1069 <Validators<T>>::remove_all();
1070 #[allow(deprecated)]
1071 <Nominators<T>>::remove_all();
1072
1073 T::VoterList::unsafe_clear();
1074 }
1075
1076 #[cfg(feature = "runtime-benchmarks")]
1077 fn put_snapshot(
1078 voters: Vec<VoterOf<Self>>,
1079 targets: Vec<T::AccountId>,
1080 target_stake: Option<VoteWeight>,
1081 ) {
1082 targets.into_iter().for_each(|v| {
1083 let stake: BalanceOf<T> = target_stake
1084 .and_then(|w| <BalanceOf<T>>::try_from(w).ok())
1085 .unwrap_or_else(|| Self::min_nominator_bond() * 100u32.into());
1086 <Bonded<T>>::insert(v.clone(), v.clone());
1087 <Ledger<T>>::insert(v.clone(), StakingLedger::<T>::new(v.clone(), stake));
1088 Self::do_add_validator(
1089 &v,
1090 ValidatorPrefs { commission: Perbill::zero(), blocked: false },
1091 );
1092 });
1093
1094 voters.into_iter().for_each(|(v, s, t)| {
1095 let stake = <BalanceOf<T>>::try_from(s).unwrap_or_else(|_| {
1096 panic!("cannot convert a VoteWeight into BalanceOf, benchmark needs reconfiguring.")
1097 });
1098 <Bonded<T>>::insert(v.clone(), v.clone());
1099 <Ledger<T>>::insert(v.clone(), StakingLedger::<T>::new(v.clone(), stake));
1100 Self::do_add_nominator(
1101 &v,
1102 Nominations { targets: t, submitted_in: 0, suppressed: false },
1103 );
1104 });
1105 }
1106
1107 #[cfg(feature = "runtime-benchmarks")]
1108 fn set_desired_targets(count: u32) {
1109 ValidatorCount::<T>::put(count);
1110 }
1111}
1112
1113impl<T: Config> rc_client::AHStakingInterface for Pallet<T> {
1114 type AccountId = T::AccountId;
1115 type MaxValidatorSet = T::MaxValidatorSet;
1116
1117 fn on_relay_session_report(report: rc_client::SessionReport<Self::AccountId>) -> Weight {
1126 log!(debug, "Received session report: {}", report,);
1127
1128 let rc_client::SessionReport {
1129 end_index,
1130 activation_timestamp,
1131 validator_points,
1132 leftover,
1133 } = report;
1134 debug_assert!(!leftover);
1135
1136 Eras::<T>::reward_active_era(validator_points.into_iter());
1138 session_rotation::Rotator::<T>::end_session(end_index, activation_timestamp)
1139 }
1140
1141 fn weigh_on_relay_session_report(
1142 _report: &rc_client::SessionReport<Self::AccountId>,
1143 ) -> Weight {
1144 T::WeightInfo::rc_on_session_report()
1146 }
1147
1148 fn on_new_offences(
1159 slash_session: SessionIndex,
1160 offences: Vec<rc_client::Offence<T::AccountId>>,
1161 ) -> Weight {
1162 log!(debug, "🦹 on_new_offences: {:?}", offences);
1163 let weight = T::WeightInfo::rc_on_offence(offences.len() as u32);
1164
1165 let Some(active_era) = ActiveEra::<T>::get() else {
1167 log!(warn, "🦹 on_new_offences: no active era; ignoring offence");
1168 return T::WeightInfo::rc_on_offence(0);
1169 };
1170
1171 let active_era_start_session = Rotator::<T>::active_era_start_session_index();
1172
1173 let offence_era = if slash_session >= active_era_start_session {
1176 active_era.index
1177 } else {
1178 match BondedEras::<T>::get()
1179 .iter()
1180 .rev()
1182 .find_map(|&(era, sesh)| if sesh <= slash_session { Some(era) } else { None })
1183 {
1184 Some(era) => era,
1185 None => {
1186 log!(warn, "🦹 on_offence: no era found for slash_session; ignoring offence");
1189 return T::WeightInfo::rc_on_offence(0);
1190 },
1191 }
1192 };
1193
1194 let oldest_reportable_offence_era = if T::SlashDeferDuration::get() == 0 {
1195 active_era.index.saturating_sub(T::BondingDuration::get().saturating_sub(2))
1201 } else {
1202 active_era.index.saturating_sub(T::SlashDeferDuration::get().saturating_sub(1))
1205 };
1206
1207 for o in offences {
1208 let slash_fraction = o.slash_fraction;
1209 let validator: <T as frame_system::Config>::AccountId = o.offender.into();
1210
1211 if offence_era < oldest_reportable_offence_era {
1213 log!(warn, "🦹 on_new_offences: offence era {:?} too old; Can only accept offences from era {:?} or newer", offence_era, oldest_reportable_offence_era);
1214 Self::deposit_event(Event::<T>::OffenceTooOld {
1215 validator: validator.clone(),
1216 fraction: slash_fraction,
1217 offence_era,
1218 });
1219 continue;
1221 }
1222 let Some(exposure_overview) = <ErasStakersOverview<T>>::get(&offence_era, &validator)
1223 else {
1224 log!(
1227 warn,
1228 "🦹 on_offence: no exposure found for {:?} in era {}; ignoring offence",
1229 validator,
1230 offence_era
1231 );
1232 continue;
1233 };
1234
1235 Self::deposit_event(Event::<T>::OffenceReported {
1236 validator: validator.clone(),
1237 fraction: slash_fraction,
1238 offence_era,
1239 });
1240
1241 let prior_slash_fraction = ValidatorSlashInEra::<T>::get(offence_era, &validator)
1242 .map_or(Zero::zero(), |(f, _)| f);
1243
1244 if let Some(existing) = OffenceQueue::<T>::get(offence_era, &validator) {
1245 if slash_fraction.deconstruct() > existing.slash_fraction.deconstruct() {
1246 OffenceQueue::<T>::insert(
1247 offence_era,
1248 &validator,
1249 OffenceRecord {
1250 reporter: o.reporters.first().cloned(),
1251 reported_era: active_era.index,
1252 slash_fraction,
1253 ..existing
1254 },
1255 );
1256
1257 ValidatorSlashInEra::<T>::insert(
1259 offence_era,
1260 &validator,
1261 (slash_fraction, exposure_overview.own),
1262 );
1263
1264 log!(
1265 debug,
1266 "🦹 updated slash for {:?}: {:?} (prior: {:?})",
1267 validator,
1268 slash_fraction,
1269 prior_slash_fraction,
1270 );
1271 } else {
1272 log!(
1273 debug,
1274 "🦹 ignored slash for {:?}: {:?} (existing prior is larger: {:?})",
1275 validator,
1276 slash_fraction,
1277 prior_slash_fraction,
1278 );
1279 }
1280 } else if slash_fraction.deconstruct() > prior_slash_fraction.deconstruct() {
1281 ValidatorSlashInEra::<T>::insert(
1282 offence_era,
1283 &validator,
1284 (slash_fraction, exposure_overview.own),
1285 );
1286
1287 OffenceQueue::<T>::insert(
1288 offence_era,
1289 &validator,
1290 OffenceRecord {
1291 reporter: o.reporters.first().cloned(),
1292 reported_era: active_era.index,
1293 exposure_page: exposure_overview.page_count.saturating_sub(1),
1296 slash_fraction,
1297 prior_slash_fraction,
1298 },
1299 );
1300
1301 OffenceQueueEras::<T>::mutate(|q| {
1302 if let Some(eras) = q {
1303 log!(debug, "🦹 inserting offence era {} into existing queue", offence_era);
1304 eras.binary_search(&offence_era).err().map(|idx| {
1305 eras.try_insert(idx, offence_era).defensive_proof(
1306 "Offence era must be present in the existing queue",
1307 )
1308 });
1309 } else {
1310 let mut eras = WeakBoundedVec::default();
1311 log!(debug, "🦹 inserting offence era {} into empty queue", offence_era);
1312 let _ = eras
1313 .try_push(offence_era)
1314 .defensive_proof("Failed to push offence era into empty queue");
1315 *q = Some(eras);
1316 }
1317 });
1318
1319 log!(
1320 debug,
1321 "🦹 queued slash for {:?}: {:?} (prior: {:?})",
1322 validator,
1323 slash_fraction,
1324 prior_slash_fraction,
1325 );
1326 } else {
1327 log!(
1328 debug,
1329 "🦹 ignored slash for {:?}: {:?} (already slashed in era with prior: {:?})",
1330 validator,
1331 slash_fraction,
1332 prior_slash_fraction,
1333 );
1334 }
1335 }
1336
1337 weight
1338 }
1339
1340 fn weigh_on_new_offences(offence_count: u32) -> Weight {
1341 T::WeightInfo::rc_on_offence(offence_count)
1342 }
1343
1344 fn active_era_start_session_index() -> SessionIndex {
1345 Rotator::<T>::active_era_start_session_index()
1346 }
1347
1348 fn is_validator(who: &Self::AccountId) -> bool {
1349 Validators::<T>::contains_key(who)
1350 }
1351}
1352
1353impl<T: Config> ScoreProvider<T::AccountId> for Pallet<T> {
1354 type Score = VoteWeight;
1355
1356 fn score(who: &T::AccountId) -> Option<Self::Score> {
1357 Self::ledger(Stash(who.clone()))
1358 .ok()
1359 .and_then(|l| {
1360 if Nominators::<T>::contains_key(&l.stash) ||
1361 Validators::<T>::contains_key(&l.stash)
1362 {
1363 Some(l.active)
1364 } else {
1365 None
1366 }
1367 })
1368 .map(|a| {
1369 let issuance = asset::total_issuance::<T>();
1370 T::CurrencyToVote::to_vote(a, issuance)
1371 })
1372 }
1373
1374 #[cfg(feature = "runtime-benchmarks")]
1375 fn set_score_of(who: &T::AccountId, weight: Self::Score) {
1376 let active: BalanceOf<T> = weight.try_into().map_err(|_| ()).unwrap();
1379 let mut ledger = match Self::ledger(StakingAccount::Stash(who.clone())) {
1380 Ok(l) => l,
1381 Err(_) => StakingLedger::default_from(who.clone()),
1382 };
1383 ledger.active = active;
1384
1385 <Ledger<T>>::insert(who, ledger);
1386 <Bonded<T>>::insert(who, who);
1387 <Validators<T>>::insert(who, ValidatorPrefs::default());
1390
1391 let imbalance = asset::burn::<T>(asset::total_issuance::<T>());
1395 core::mem::forget(imbalance);
1398 }
1399}
1400
1401pub struct UseValidatorsMap<T>(core::marker::PhantomData<T>);
1405impl<T: Config> SortedListProvider<T::AccountId> for UseValidatorsMap<T> {
1406 type Score = BalanceOf<T>;
1407 type Error = ();
1408
1409 fn iter() -> Box<dyn Iterator<Item = T::AccountId>> {
1411 Box::new(Validators::<T>::iter().map(|(v, _)| v))
1412 }
1413 fn iter_from(
1414 start: &T::AccountId,
1415 ) -> Result<Box<dyn Iterator<Item = T::AccountId>>, Self::Error> {
1416 if Validators::<T>::contains_key(start) {
1417 let start_key = Validators::<T>::hashed_key_for(start);
1418 Ok(Box::new(Validators::<T>::iter_from(start_key).map(|(n, _)| n)))
1419 } else {
1420 Err(())
1421 }
1422 }
1423 fn lock() {}
1424 fn unlock() {}
1425 fn count() -> u32 {
1426 Validators::<T>::count()
1427 }
1428 fn contains(id: &T::AccountId) -> bool {
1429 Validators::<T>::contains_key(id)
1430 }
1431 fn on_insert(_: T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> {
1432 Ok(())
1434 }
1435 fn get_score(id: &T::AccountId) -> Result<Self::Score, Self::Error> {
1436 Ok(Pallet::<T>::weight_of(id).into())
1437 }
1438 fn on_update(_: &T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> {
1439 Ok(())
1441 }
1442 fn on_remove(_: &T::AccountId) -> Result<(), Self::Error> {
1443 Ok(())
1445 }
1446 fn unsafe_regenerate(
1447 _: impl IntoIterator<Item = T::AccountId>,
1448 _: Box<dyn Fn(&T::AccountId) -> Option<Self::Score>>,
1449 ) -> u32 {
1450 0
1452 }
1453 #[cfg(feature = "try-runtime")]
1454 fn try_state() -> Result<(), TryRuntimeError> {
1455 Ok(())
1456 }
1457
1458 fn unsafe_clear() {
1459 #[allow(deprecated)]
1460 Validators::<T>::remove_all();
1461 }
1462
1463 #[cfg(feature = "runtime-benchmarks")]
1464 fn score_update_worst_case(_who: &T::AccountId, _is_increase: bool) -> Self::Score {
1465 unimplemented!()
1466 }
1467}
1468
1469pub struct UseNominatorsAndValidatorsMap<T>(core::marker::PhantomData<T>);
1473impl<T: Config> SortedListProvider<T::AccountId> for UseNominatorsAndValidatorsMap<T> {
1474 type Error = ();
1475 type Score = VoteWeight;
1476
1477 fn iter() -> Box<dyn Iterator<Item = T::AccountId>> {
1478 Box::new(
1479 Validators::<T>::iter()
1480 .map(|(v, _)| v)
1481 .chain(Nominators::<T>::iter().map(|(n, _)| n)),
1482 )
1483 }
1484 fn iter_from(
1485 start: &T::AccountId,
1486 ) -> Result<Box<dyn Iterator<Item = T::AccountId>>, Self::Error> {
1487 if Validators::<T>::contains_key(start) {
1488 let start_key = Validators::<T>::hashed_key_for(start);
1489 Ok(Box::new(
1490 Validators::<T>::iter_from(start_key)
1491 .map(|(n, _)| n)
1492 .chain(Nominators::<T>::iter().map(|(x, _)| x)),
1493 ))
1494 } else if Nominators::<T>::contains_key(start) {
1495 let start_key = Nominators::<T>::hashed_key_for(start);
1496 Ok(Box::new(Nominators::<T>::iter_from(start_key).map(|(n, _)| n)))
1497 } else {
1498 Err(())
1499 }
1500 }
1501 fn lock() {}
1502 fn unlock() {}
1503 fn count() -> u32 {
1504 Nominators::<T>::count().saturating_add(Validators::<T>::count())
1505 }
1506 fn contains(id: &T::AccountId) -> bool {
1507 Nominators::<T>::contains_key(id) || Validators::<T>::contains_key(id)
1508 }
1509 fn on_insert(_: T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> {
1510 Ok(())
1512 }
1513 fn get_score(id: &T::AccountId) -> Result<Self::Score, Self::Error> {
1514 Ok(Pallet::<T>::weight_of(id))
1515 }
1516 fn on_update(_: &T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> {
1517 Ok(())
1519 }
1520 fn on_remove(_: &T::AccountId) -> Result<(), Self::Error> {
1521 Ok(())
1523 }
1524 fn unsafe_regenerate(
1525 _: impl IntoIterator<Item = T::AccountId>,
1526 _: Box<dyn Fn(&T::AccountId) -> Option<Self::Score>>,
1527 ) -> u32 {
1528 0
1530 }
1531
1532 #[cfg(feature = "try-runtime")]
1533 fn try_state() -> Result<(), TryRuntimeError> {
1534 Ok(())
1535 }
1536
1537 fn unsafe_clear() {
1538 #[allow(deprecated)]
1541 Nominators::<T>::remove_all();
1542 #[allow(deprecated)]
1543 Validators::<T>::remove_all();
1544 }
1545
1546 #[cfg(feature = "runtime-benchmarks")]
1547 fn score_update_worst_case(_who: &T::AccountId, _is_increase: bool) -> Self::Score {
1548 unimplemented!()
1549 }
1550}
1551
1552impl<T: Config> StakingInterface for Pallet<T> {
1553 type AccountId = T::AccountId;
1554 type Balance = BalanceOf<T>;
1555 type CurrencyToVote = T::CurrencyToVote;
1556
1557 fn minimum_nominator_bond() -> Self::Balance {
1558 Self::min_nominator_bond()
1559 }
1560
1561 fn minimum_validator_bond() -> Self::Balance {
1562 Self::min_validator_bond()
1563 }
1564
1565 fn stash_by_ctrl(controller: &Self::AccountId) -> Result<Self::AccountId, DispatchError> {
1566 Self::ledger(Controller(controller.clone()))
1567 .map(|l| l.stash)
1568 .map_err(|e| e.into())
1569 }
1570
1571 fn bonding_duration() -> EraIndex {
1576 T::BondingDuration::get()
1577 }
1578
1579 fn nominator_bonding_duration() -> EraIndex {
1593 if AreNominatorsSlashable::<T>::get() {
1594 T::BondingDuration::get()
1595 } else {
1596 T::NominatorFastUnbondDuration::get()
1597 }
1598 }
1599
1600 fn current_era() -> EraIndex {
1601 Rotator::<T>::active_era()
1604 }
1605
1606 fn stake(who: &Self::AccountId) -> Result<Stake<BalanceOf<T>>, DispatchError> {
1607 Self::ledger(Stash(who.clone()))
1608 .map(|l| Stake { total: l.total, active: l.active })
1609 .map_err(|e| e.into())
1610 }
1611
1612 fn bond_extra(who: &Self::AccountId, extra: Self::Balance) -> DispatchResult {
1613 Self::bond_extra(RawOrigin::Signed(who.clone()).into(), extra)
1614 }
1615
1616 fn unbond(who: &Self::AccountId, value: Self::Balance) -> DispatchResult {
1617 let ctrl = Self::bonded(who).ok_or(Error::<T>::NotStash)?;
1618 Self::unbond(RawOrigin::Signed(ctrl).into(), value)
1619 .map_err(|with_post| with_post.error)
1620 .map(|_| ())
1621 }
1622
1623 fn set_payee(stash: &Self::AccountId, reward_acc: &Self::AccountId) -> DispatchResult {
1624 ensure!(
1628 !Self::is_virtual_staker(stash) || stash != reward_acc,
1629 Error::<T>::RewardDestinationRestricted
1630 );
1631
1632 let ledger = Self::ledger(Stash(stash.clone()))?;
1633 let _ = ledger
1634 .set_payee(RewardDestination::Account(reward_acc.clone()))
1635 .defensive_proof("ledger was retrieved from storage, thus its bonded; qed.")?;
1636
1637 Ok(())
1638 }
1639
1640 fn chill(who: &Self::AccountId) -> DispatchResult {
1641 let ctrl = Self::bonded(who).ok_or(Error::<T>::NotStash)?;
1644 Self::chill(RawOrigin::Signed(ctrl).into())
1645 }
1646
1647 fn withdraw_unbonded(
1648 who: Self::AccountId,
1649 _num_slashing_spans: u32,
1650 ) -> Result<bool, DispatchError> {
1651 let ctrl = Self::bonded(&who).ok_or(Error::<T>::NotStash)?;
1652 Self::withdraw_unbonded(RawOrigin::Signed(ctrl.clone()).into(), 0)
1653 .map(|_| !StakingLedger::<T>::is_bonded(StakingAccount::Controller(ctrl)))
1654 .map_err(|with_post| with_post.error)
1655 }
1656
1657 fn bond(
1658 who: &Self::AccountId,
1659 value: Self::Balance,
1660 payee: &Self::AccountId,
1661 ) -> DispatchResult {
1662 Self::bond(
1663 RawOrigin::Signed(who.clone()).into(),
1664 value,
1665 RewardDestination::Account(payee.clone()),
1666 )
1667 }
1668
1669 fn nominate(who: &Self::AccountId, targets: Vec<Self::AccountId>) -> DispatchResult {
1670 let ctrl = Self::bonded(who).ok_or(Error::<T>::NotStash)?;
1671 let targets = targets.into_iter().map(T::Lookup::unlookup).collect::<Vec<_>>();
1672 Self::nominate(RawOrigin::Signed(ctrl).into(), targets)
1673 }
1674
1675 fn desired_validator_count() -> u32 {
1676 ValidatorCount::<T>::get()
1677 }
1678
1679 fn election_ongoing() -> bool {
1680 <T::ElectionProvider as ElectionProvider>::status().is_ok()
1681 }
1682
1683 fn force_unstake(who: Self::AccountId) -> sp_runtime::DispatchResult {
1684 Self::force_unstake(RawOrigin::Root.into(), who.clone(), 0)
1685 }
1686
1687 fn is_exposed_in_era(who: &Self::AccountId, era: &EraIndex) -> bool {
1688 ErasStakersPaged::<T>::iter_prefix((era,)).any(|((validator, _), exposure_page)| {
1689 validator == *who || exposure_page.others.iter().any(|i| i.who == *who)
1690 })
1691 }
1692
1693 fn status(
1694 who: &Self::AccountId,
1695 ) -> Result<sp_staking::StakerStatus<Self::AccountId>, DispatchError> {
1696 if !StakingLedger::<T>::is_bonded(StakingAccount::Stash(who.clone())) {
1697 return Err(Error::<T>::NotStash.into());
1698 }
1699
1700 let is_validator = Validators::<T>::contains_key(&who);
1701 let is_nominator = Nominators::<T>::get(&who);
1702
1703 use sp_staking::StakerStatus;
1704 match (is_validator, is_nominator.is_some()) {
1705 (false, false) => Ok(StakerStatus::Idle),
1706 (true, false) => Ok(StakerStatus::Validator),
1707 (false, true) => Ok(StakerStatus::Nominator(
1708 is_nominator.expect("is checked above; qed").targets.into_inner(),
1709 )),
1710 (true, true) => {
1711 defensive!("cannot be both validators and nominator");
1712 Err(Error::<T>::BadState.into())
1713 },
1714 }
1715 }
1716
1717 fn is_virtual_staker(who: &T::AccountId) -> bool {
1722 frame_system::Pallet::<T>::account_nonce(who).is_zero() &&
1723 VirtualStakers::<T>::contains_key(who)
1724 }
1725
1726 fn slash_reward_fraction() -> Perbill {
1727 SlashRewardFraction::<T>::get()
1728 }
1729
1730 sp_staking::runtime_benchmarks_enabled! {
1731 fn nominations(who: &Self::AccountId) -> Option<Vec<T::AccountId>> {
1732 Nominators::<T>::get(who).map(|n| n.targets.into_inner())
1733 }
1734
1735 fn add_era_stakers(
1736 current_era: &EraIndex,
1737 stash: &T::AccountId,
1738 exposures: Vec<(Self::AccountId, Self::Balance)>,
1739 ) {
1740 let others = exposures
1741 .iter()
1742 .map(|(who, value)| crate::IndividualExposure { who: who.clone(), value: *value })
1743 .collect::<Vec<_>>();
1744 let exposure = Exposure { total: Default::default(), own: Default::default(), others };
1745 Eras::<T>::upsert_exposure(*current_era, stash, exposure);
1746 }
1747
1748 fn max_exposure_page_size() -> Page {
1749 T::MaxExposurePageSize::get()
1750 }
1751 }
1752
1753 sp_staking::std_or_benchmarks_enabled! {
1754 fn set_era(era: EraIndex) {
1755 ActiveEra::<T>::put(crate::ActiveEraInfo { index: era, start: None });
1756 CurrentEra::<T>::put(era.saturating_add(1));
1758 }
1759 }
1760}
1761
1762impl<T: Config> sp_staking::StakingUnchecked for Pallet<T> {
1763 fn migrate_to_virtual_staker(who: &Self::AccountId) -> DispatchResult {
1764 asset::kill_stake::<T>(who)?;
1765 VirtualStakers::<T>::insert(who, ());
1766 Ok(())
1767 }
1768
1769 fn virtual_bond(
1773 keyless_who: &Self::AccountId,
1774 value: Self::Balance,
1775 payee: &Self::AccountId,
1776 ) -> DispatchResult {
1777 if StakingLedger::<T>::is_bonded(StakingAccount::Stash(keyless_who.clone())) {
1778 return Err(Error::<T>::AlreadyBonded.into());
1779 }
1780
1781 ensure!(keyless_who != payee, Error::<T>::RewardDestinationRestricted);
1783
1784 VirtualStakers::<T>::insert(keyless_who, ());
1786
1787 Self::deposit_event(Event::<T>::Bonded { stash: keyless_who.clone(), amount: value });
1788 let ledger = StakingLedger::<T>::new(keyless_who.clone(), value);
1789
1790 ledger.bond(RewardDestination::Account(payee.clone()))?;
1791
1792 Ok(())
1793 }
1794
1795 #[cfg(feature = "runtime-benchmarks")]
1797 fn migrate_to_direct_staker(who: &Self::AccountId) {
1798 assert!(VirtualStakers::<T>::contains_key(who));
1799 let ledger = StakingLedger::<T>::get(Stash(who.clone())).unwrap();
1800 let _ = asset::update_stake::<T>(who, ledger.total)
1801 .expect("funds must be transferred to stash");
1802 VirtualStakers::<T>::remove(who);
1803 }
1804}
1805
1806#[cfg(any(test, feature = "try-runtime"))]
1807impl<T: Config> Pallet<T> {
1808 pub(crate) fn do_try_state(_now: BlockNumberFor<T>) -> Result<(), TryRuntimeError> {
1809 if ActiveEra::<T>::get().is_none() && CurrentEra::<T>::get().is_none() {
1812 return Ok(());
1813 }
1814
1815 session_rotation::Rotator::<T>::do_try_state()?;
1816 session_rotation::Eras::<T>::do_try_state()?;
1817
1818 use frame_support::traits::fungible::Inspect;
1819 if T::CurrencyToVote::will_downscale(T::Currency::total_issuance()).map_or(false, |x| x) {
1820 log!(warn, "total issuance will cause T::CurrencyToVote to downscale -- report to maintainers.")
1821 }
1822
1823 Self::check_ledgers()?;
1824 Self::check_bonded_consistency()?;
1825 Self::check_payees()?;
1826 Self::check_paged_exposures()?;
1827 Self::check_count()?;
1828 Self::check_slash_health()?;
1829
1830 Ok(())
1831 }
1832
1833 fn check_bonded_consistency() -> Result<(), TryRuntimeError> {
1843 use alloc::collections::btree_set::BTreeSet;
1844
1845 let mut count_controller_double = 0;
1846 let mut count_double = 0;
1847 let mut count_none = 0;
1848 let mut controllers = BTreeSet::new();
1851
1852 for (stash, controller) in <Bonded<T>>::iter() {
1853 if !controllers.insert(controller.clone()) {
1854 count_controller_double += 1;
1855 }
1856
1857 match (<Ledger<T>>::get(&stash), <Ledger<T>>::get(&controller)) {
1858 (Some(_), Some(_)) =>
1859 {
1863 if stash != controller {
1864 count_double += 1;
1865 }
1866 },
1867 (None, None) => {
1868 count_none += 1;
1869 },
1870 _ => {},
1871 };
1872 }
1873
1874 if count_controller_double != 0 {
1875 log!(
1876 warn,
1877 "a controller is associated with more than one ledger ({} occurrences)",
1878 count_controller_double
1879 );
1880 };
1881
1882 if count_double != 0 {
1883 log!(warn, "single tuple of (stash, controller) pair bonds more than one ledger ({} occurrences)", count_double);
1884 }
1885
1886 if count_none != 0 {
1887 log!(warn, "inconsistent bonded state: (stash, controller) pair missing associated ledger ({} occurrences)", count_none);
1888 }
1889
1890 Ok(())
1891 }
1892
1893 fn check_payees() -> Result<(), TryRuntimeError> {
1898 for (stash, _) in Bonded::<T>::iter() {
1899 ensure!(Payee::<T>::get(&stash).is_some(), "bonded ledger does not have payee set");
1900 }
1901
1902 ensure!(
1903 (Ledger::<T>::iter().count() == Payee::<T>::iter().count()) &&
1904 (Ledger::<T>::iter().count() == Bonded::<T>::iter().count()),
1905 "number of entries in payee storage items does not match the number of bonded ledgers",
1906 );
1907
1908 Ok(())
1909 }
1910
1911 fn check_count() -> Result<(), TryRuntimeError> {
1915 crate::log!(
1919 debug,
1920 "VoterList count: {}, Nominators count: {}, Validators count: {}",
1921 <T as Config>::VoterList::count(),
1922 Nominators::<T>::count(),
1923 Validators::<T>::count()
1924 );
1925
1926 ensure!(
1927 <T as Config>::TargetList::count() == Validators::<T>::count(),
1928 "wrong external count"
1929 );
1930 let max_validators_bound = crate::MaxWinnersOf::<T>::get();
1931 let max_winners_per_page_bound = crate::MaxWinnersPerPageOf::<T::ElectionProvider>::get();
1932 ensure!(
1933 max_validators_bound >= max_winners_per_page_bound,
1934 "max validators should be higher than per page bounds"
1935 );
1936 ensure!(ValidatorCount::<T>::get() <= max_validators_bound, Error::<T>::TooManyValidators);
1937 Ok(())
1938 }
1939
1940 fn check_ledgers() -> Result<(), TryRuntimeError> {
1948 Bonded::<T>::iter()
1949 .map(|(stash, ctrl)| {
1950 if VirtualStakers::<T>::contains_key(stash.clone()) {
1952 ensure!(
1953 asset::staked::<T>(&stash) == Zero::zero(),
1954 "virtual stakers should not have any staked balance"
1955 );
1956 ensure!(
1957 <Bonded<T>>::get(stash.clone()).unwrap() == stash.clone(),
1958 "stash and controller should be same"
1959 );
1960 ensure!(
1961 Ledger::<T>::get(stash.clone()).unwrap().stash == stash,
1962 "ledger corrupted for virtual staker"
1963 );
1964 ensure!(
1965 frame_system::Pallet::<T>::account_nonce(&stash).is_zero(),
1966 "virtual stakers are keyless and should not have any nonce"
1967 );
1968 let reward_destination = <Payee<T>>::get(stash.clone()).unwrap();
1969 if let RewardDestination::Account(payee) = reward_destination {
1970 ensure!(
1971 payee != stash.clone(),
1972 "reward destination should not be same as stash for virtual staker"
1973 );
1974 } else {
1975 return Err(DispatchError::Other(
1976 "reward destination must be of account variant for virtual staker",
1977 ));
1978 }
1979 } else {
1980 let integrity = Self::inspect_bond_state(&stash);
1981 if integrity != Ok(LedgerIntegrityState::Ok) {
1982 log!(
1984 error,
1985 "defensive: bonded stash {:?} has inconsistent ledger state: {:?}",
1986 stash,
1987 integrity
1988 );
1989 }
1990 }
1991
1992 Self::ensure_ledger_consistent(&ctrl)?;
1993 Self::ensure_ledger_role_and_min_bond(&ctrl)?;
1994 Ok(())
1995 })
1996 .collect::<Result<Vec<_>, _>>()?;
1997 Ok(())
1998 }
1999
2000 fn check_paged_exposures() -> Result<(), TryRuntimeError> {
2011 let Some(era) = ActiveEra::<T>::get().map(|a| a.index) else { return Ok(()) };
2012 let overview_and_pages = ErasStakersOverview::<T>::iter_prefix(era)
2013 .map(|(validator, metadata)| {
2014 if LastValidatorEra::<T>::get(&validator) != Some(era) {
2016 log!(
2017 warn,
2018 "Validator {:?} has incorrect LastValidatorEra (expected {:?}, got {:?})",
2019 validator,
2020 era,
2021 LastValidatorEra::<T>::get(&validator)
2022 );
2023 }
2024
2025 let pages = ErasStakersPaged::<T>::iter_prefix((era, validator))
2026 .map(|(_idx, page)| page)
2027 .collect::<Vec<_>>();
2028 (metadata, pages)
2029 })
2030 .collect::<Vec<_>>();
2031
2032 ensure!(
2033 overview_and_pages.iter().flat_map(|(_m, pages)| pages).all(|page| {
2034 let expected = page
2035 .others
2036 .iter()
2037 .map(|e| e.value)
2038 .fold(BalanceOf::<T>::zero(), |acc, x| acc + x);
2039 page.page_total == expected
2040 }),
2041 "found wrong page_total"
2042 );
2043
2044 ensure!(
2045 overview_and_pages.iter().all(|(metadata, pages)| {
2046 let page_count_good = metadata.page_count == pages.len() as u32;
2047 let nominator_count_good = metadata.nominator_count ==
2048 pages.iter().map(|p| p.others.len() as u32).fold(0u32, |acc, x| acc + x);
2049 let total_good = metadata.total ==
2050 metadata.own +
2051 pages
2052 .iter()
2053 .fold(BalanceOf::<T>::zero(), |acc, page| acc + page.page_total);
2054
2055 page_count_good && nominator_count_good && total_good
2056 }),
2057 "found bad metadata"
2058 );
2059
2060 ensure!(
2061 overview_and_pages
2062 .iter()
2063 .map(|(metadata, _pages)| metadata.total)
2064 .fold(BalanceOf::<T>::zero(), |acc, x| acc + x) ==
2065 ErasTotalStake::<T>::get(era),
2066 "found bad eras total stake"
2067 );
2068
2069 Ok(())
2070 }
2071
2072 fn check_slash_health() -> Result<(), TryRuntimeError> {
2074 let offence_queue_eras = OffenceQueueEras::<T>::get().unwrap_or_default().into_inner();
2076 let mut sorted_offence_queue_eras = offence_queue_eras.clone();
2077 sorted_offence_queue_eras.sort();
2078 ensure!(
2079 sorted_offence_queue_eras == offence_queue_eras,
2080 "Offence queue eras are not sorted"
2081 );
2082 drop(sorted_offence_queue_eras);
2083
2084 let active_era = Rotator::<T>::active_era();
2086 let oldest_unprocessed_offence_era =
2087 offence_queue_eras.first().cloned().unwrap_or(active_era);
2088
2089 let oldest_unprocessed_offence_age =
2093 active_era.saturating_sub(oldest_unprocessed_offence_era);
2094
2095 if oldest_unprocessed_offence_age > 2.min(T::BondingDuration::get()) {
2097 log!(
2098 warn,
2099 "Offence queue has unprocessed offences from older than 2 eras: oldest offence era in queue {:?} (active era: {:?})",
2100 oldest_unprocessed_offence_era,
2101 active_era
2102 );
2103 }
2104
2105 ensure!(
2107 oldest_unprocessed_offence_age < T::BondingDuration::get() - 1,
2108 "offences from era less than 3 eras old from active era not processed yet"
2109 );
2110
2111 for e in offence_queue_eras {
2113 let count = OffenceQueue::<T>::iter_prefix(e).count();
2114 ensure!(count > 0, "Offence queue is empty for era listed in offence queue eras");
2115 log!(info, "Offence queue for era {:?} has {:?} offences queued", e, count);
2116 }
2117
2118 for era in (active_era.saturating_sub(T::BondingDuration::get()))..(active_era) {
2122 Self::ensure_era_slashes_applied(era)?;
2126 }
2127
2128 for (era, _) in CancelledSlashes::<T>::iter() {
2130 ensure!(era >= active_era, "Found cancelled slashes for era before active era");
2131 }
2132
2133 Ok(())
2134 }
2135
2136 fn ensure_ledger_role_and_min_bond(ctrl: &T::AccountId) -> Result<(), TryRuntimeError> {
2137 let ledger = Self::ledger(StakingAccount::Controller(ctrl.clone()))?;
2138 let stash = ledger.stash;
2139
2140 let is_nominator = Nominators::<T>::contains_key(&stash);
2141 let is_validator = Validators::<T>::contains_key(&stash);
2142
2143 match (is_nominator, is_validator) {
2144 (false, false) => {
2145 if ledger.active < Self::min_chilled_bond() && !ledger.active.is_zero() {
2146 log!(
2148 warn,
2149 "Chilled stash {:?} has less stake ({:?}) than minimum role bond ({:?})",
2150 stash,
2151 ledger.active,
2152 Self::min_chilled_bond()
2153 );
2154 }
2155 },
2157 (true, false) => {
2158 if ledger.active < Self::min_nominator_bond() {
2160 log!(
2161 warn,
2162 "Nominator {:?} has less stake ({:?}) than minimum role bond ({:?})",
2163 stash,
2164 ledger.active,
2165 Self::min_nominator_bond()
2166 );
2167 }
2168 },
2169 (false, true) => {
2170 if ledger.active < Self::min_validator_bond() {
2172 log!(
2173 warn,
2174 "Validator {:?} has less stake ({:?}) than minimum role bond ({:?})",
2175 stash,
2176 ledger.active,
2177 Self::min_validator_bond()
2178 );
2179 }
2180 },
2181 (true, true) => {
2182 ensure!(false, "Stash cannot be both nominator and validator");
2183 },
2184 }
2185 Ok(())
2186 }
2187
2188 fn ensure_ledger_consistent(ctrl: &T::AccountId) -> Result<(), TryRuntimeError> {
2189 let ledger = Self::ledger(StakingAccount::Controller(ctrl.clone()))?;
2191
2192 let real_total: BalanceOf<T> =
2193 ledger.unlocking.iter().fold(ledger.active, |a, c| a + c.value);
2194 ensure!(real_total == ledger.total, "ledger.total corrupt");
2195
2196 Ok(())
2197 }
2198}