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