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