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 fn is_validator(who: &Self::AccountId) -> bool {
1352 Validators::<T>::contains_key(who)
1353 }
1354}
1355
1356impl<T: Config> ScoreProvider<T::AccountId> for Pallet<T> {
1357 type Score = VoteWeight;
1358
1359 fn score(who: &T::AccountId) -> Option<Self::Score> {
1360 Self::ledger(Stash(who.clone()))
1361 .ok()
1362 .and_then(|l| {
1363 if Nominators::<T>::contains_key(&l.stash) ||
1364 Validators::<T>::contains_key(&l.stash)
1365 {
1366 Some(l.active)
1367 } else {
1368 None
1369 }
1370 })
1371 .map(|a| {
1372 let issuance = asset::total_issuance::<T>();
1373 T::CurrencyToVote::to_vote(a, issuance)
1374 })
1375 }
1376
1377 #[cfg(feature = "runtime-benchmarks")]
1378 fn set_score_of(who: &T::AccountId, weight: Self::Score) {
1379 let active: BalanceOf<T> = weight.try_into().map_err(|_| ()).unwrap();
1382 let mut ledger = match Self::ledger(StakingAccount::Stash(who.clone())) {
1383 Ok(l) => l,
1384 Err(_) => StakingLedger::default_from(who.clone()),
1385 };
1386 ledger.active = active;
1387
1388 <Ledger<T>>::insert(who, ledger);
1389 <Bonded<T>>::insert(who, who);
1390 <Validators<T>>::insert(who, ValidatorPrefs::default());
1393
1394 let imbalance = asset::burn::<T>(asset::total_issuance::<T>());
1398 core::mem::forget(imbalance);
1401 }
1402}
1403
1404pub struct UseValidatorsMap<T>(core::marker::PhantomData<T>);
1408impl<T: Config> SortedListProvider<T::AccountId> for UseValidatorsMap<T> {
1409 type Score = BalanceOf<T>;
1410 type Error = ();
1411
1412 fn iter() -> Box<dyn Iterator<Item = T::AccountId>> {
1414 Box::new(Validators::<T>::iter().map(|(v, _)| v))
1415 }
1416 fn iter_from(
1417 start: &T::AccountId,
1418 ) -> Result<Box<dyn Iterator<Item = T::AccountId>>, Self::Error> {
1419 if Validators::<T>::contains_key(start) {
1420 let start_key = Validators::<T>::hashed_key_for(start);
1421 Ok(Box::new(Validators::<T>::iter_from(start_key).map(|(n, _)| n)))
1422 } else {
1423 Err(())
1424 }
1425 }
1426 fn lock() {}
1427 fn unlock() {}
1428 fn count() -> u32 {
1429 Validators::<T>::count()
1430 }
1431 fn contains(id: &T::AccountId) -> bool {
1432 Validators::<T>::contains_key(id)
1433 }
1434 fn on_insert(_: T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> {
1435 Ok(())
1437 }
1438 fn get_score(id: &T::AccountId) -> Result<Self::Score, Self::Error> {
1439 Ok(Pallet::<T>::weight_of(id).into())
1440 }
1441 fn on_update(_: &T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> {
1442 Ok(())
1444 }
1445 fn on_remove(_: &T::AccountId) -> Result<(), Self::Error> {
1446 Ok(())
1448 }
1449 fn unsafe_regenerate(
1450 _: impl IntoIterator<Item = T::AccountId>,
1451 _: Box<dyn Fn(&T::AccountId) -> Option<Self::Score>>,
1452 ) -> u32 {
1453 0
1455 }
1456 #[cfg(feature = "try-runtime")]
1457 fn try_state() -> Result<(), TryRuntimeError> {
1458 Ok(())
1459 }
1460
1461 fn unsafe_clear() {
1462 #[allow(deprecated)]
1463 Validators::<T>::remove_all();
1464 }
1465
1466 #[cfg(feature = "runtime-benchmarks")]
1467 fn score_update_worst_case(_who: &T::AccountId, _is_increase: bool) -> Self::Score {
1468 unimplemented!()
1469 }
1470}
1471
1472pub struct UseNominatorsAndValidatorsMap<T>(core::marker::PhantomData<T>);
1476impl<T: Config> SortedListProvider<T::AccountId> for UseNominatorsAndValidatorsMap<T> {
1477 type Error = ();
1478 type Score = VoteWeight;
1479
1480 fn iter() -> Box<dyn Iterator<Item = T::AccountId>> {
1481 Box::new(
1482 Validators::<T>::iter()
1483 .map(|(v, _)| v)
1484 .chain(Nominators::<T>::iter().map(|(n, _)| n)),
1485 )
1486 }
1487 fn iter_from(
1488 start: &T::AccountId,
1489 ) -> Result<Box<dyn Iterator<Item = T::AccountId>>, Self::Error> {
1490 if Validators::<T>::contains_key(start) {
1491 let start_key = Validators::<T>::hashed_key_for(start);
1492 Ok(Box::new(
1493 Validators::<T>::iter_from(start_key)
1494 .map(|(n, _)| n)
1495 .chain(Nominators::<T>::iter().map(|(x, _)| x)),
1496 ))
1497 } else if Nominators::<T>::contains_key(start) {
1498 let start_key = Nominators::<T>::hashed_key_for(start);
1499 Ok(Box::new(Nominators::<T>::iter_from(start_key).map(|(n, _)| n)))
1500 } else {
1501 Err(())
1502 }
1503 }
1504 fn lock() {}
1505 fn unlock() {}
1506 fn count() -> u32 {
1507 Nominators::<T>::count().saturating_add(Validators::<T>::count())
1508 }
1509 fn contains(id: &T::AccountId) -> bool {
1510 Nominators::<T>::contains_key(id) || Validators::<T>::contains_key(id)
1511 }
1512 fn on_insert(_: T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> {
1513 Ok(())
1515 }
1516 fn get_score(id: &T::AccountId) -> Result<Self::Score, Self::Error> {
1517 Ok(Pallet::<T>::weight_of(id))
1518 }
1519 fn on_update(_: &T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> {
1520 Ok(())
1522 }
1523 fn on_remove(_: &T::AccountId) -> Result<(), Self::Error> {
1524 Ok(())
1526 }
1527 fn unsafe_regenerate(
1528 _: impl IntoIterator<Item = T::AccountId>,
1529 _: Box<dyn Fn(&T::AccountId) -> Option<Self::Score>>,
1530 ) -> u32 {
1531 0
1533 }
1534
1535 #[cfg(feature = "try-runtime")]
1536 fn try_state() -> Result<(), TryRuntimeError> {
1537 Ok(())
1538 }
1539
1540 fn unsafe_clear() {
1541 #[allow(deprecated)]
1544 Nominators::<T>::remove_all();
1545 #[allow(deprecated)]
1546 Validators::<T>::remove_all();
1547 }
1548
1549 #[cfg(feature = "runtime-benchmarks")]
1550 fn score_update_worst_case(_who: &T::AccountId, _is_increase: bool) -> Self::Score {
1551 unimplemented!()
1552 }
1553}
1554
1555impl<T: Config> StakingInterface for Pallet<T> {
1556 type AccountId = T::AccountId;
1557 type Balance = BalanceOf<T>;
1558 type CurrencyToVote = T::CurrencyToVote;
1559
1560 fn minimum_nominator_bond() -> Self::Balance {
1561 Self::min_nominator_bond()
1562 }
1563
1564 fn minimum_validator_bond() -> Self::Balance {
1565 Self::min_validator_bond()
1566 }
1567
1568 fn stash_by_ctrl(controller: &Self::AccountId) -> Result<Self::AccountId, DispatchError> {
1569 Self::ledger(Controller(controller.clone()))
1570 .map(|l| l.stash)
1571 .map_err(|e| e.into())
1572 }
1573
1574 fn bonding_duration() -> EraIndex {
1579 T::BondingDuration::get()
1580 }
1581
1582 fn nominator_bonding_duration() -> EraIndex {
1596 if AreNominatorsSlashable::<T>::get() {
1597 T::BondingDuration::get()
1598 } else {
1599 T::NominatorFastUnbondDuration::get()
1600 }
1601 }
1602
1603 fn current_era() -> EraIndex {
1604 Rotator::<T>::active_era()
1607 }
1608
1609 fn stake(who: &Self::AccountId) -> Result<Stake<BalanceOf<T>>, DispatchError> {
1610 Self::ledger(Stash(who.clone()))
1611 .map(|l| Stake { total: l.total, active: l.active })
1612 .map_err(|e| e.into())
1613 }
1614
1615 fn bond_extra(who: &Self::AccountId, extra: Self::Balance) -> DispatchResult {
1616 Self::bond_extra(RawOrigin::Signed(who.clone()).into(), extra)
1617 }
1618
1619 fn unbond(who: &Self::AccountId, value: Self::Balance) -> DispatchResult {
1620 let ctrl = Self::bonded(who).ok_or(Error::<T>::NotStash)?;
1621 Self::unbond(RawOrigin::Signed(ctrl).into(), value)
1622 .map_err(|with_post| with_post.error)
1623 .map(|_| ())
1624 }
1625
1626 fn set_payee(stash: &Self::AccountId, reward_acc: &Self::AccountId) -> DispatchResult {
1627 ensure!(
1631 !Self::is_virtual_staker(stash) || stash != reward_acc,
1632 Error::<T>::RewardDestinationRestricted
1633 );
1634
1635 let ledger = Self::ledger(Stash(stash.clone()))?;
1636 let _ = ledger
1637 .set_payee(RewardDestination::Account(reward_acc.clone()))
1638 .defensive_proof("ledger was retrieved from storage, thus its bonded; qed.")?;
1639
1640 Ok(())
1641 }
1642
1643 fn chill(who: &Self::AccountId) -> DispatchResult {
1644 let ctrl = Self::bonded(who).ok_or(Error::<T>::NotStash)?;
1647 Self::chill(RawOrigin::Signed(ctrl).into())
1648 }
1649
1650 fn withdraw_unbonded(
1651 who: Self::AccountId,
1652 _num_slashing_spans: u32,
1653 ) -> Result<bool, DispatchError> {
1654 let ctrl = Self::bonded(&who).ok_or(Error::<T>::NotStash)?;
1655 Self::withdraw_unbonded(RawOrigin::Signed(ctrl.clone()).into(), 0)
1656 .map(|_| !StakingLedger::<T>::is_bonded(StakingAccount::Controller(ctrl)))
1657 .map_err(|with_post| with_post.error)
1658 }
1659
1660 fn bond(
1661 who: &Self::AccountId,
1662 value: Self::Balance,
1663 payee: &Self::AccountId,
1664 ) -> DispatchResult {
1665 Self::bond(
1666 RawOrigin::Signed(who.clone()).into(),
1667 value,
1668 RewardDestination::Account(payee.clone()),
1669 )
1670 }
1671
1672 fn nominate(who: &Self::AccountId, targets: Vec<Self::AccountId>) -> DispatchResult {
1673 let ctrl = Self::bonded(who).ok_or(Error::<T>::NotStash)?;
1674 let targets = targets.into_iter().map(T::Lookup::unlookup).collect::<Vec<_>>();
1675 Self::nominate(RawOrigin::Signed(ctrl).into(), targets)
1676 }
1677
1678 fn desired_validator_count() -> u32 {
1679 ValidatorCount::<T>::get()
1680 }
1681
1682 fn election_ongoing() -> bool {
1683 <T::ElectionProvider as ElectionProvider>::status().is_ok()
1684 }
1685
1686 fn force_unstake(who: Self::AccountId) -> sp_runtime::DispatchResult {
1687 Self::force_unstake(RawOrigin::Root.into(), who.clone(), 0)
1688 }
1689
1690 fn is_exposed_in_era(who: &Self::AccountId, era: &EraIndex) -> bool {
1691 ErasStakersPaged::<T>::iter_prefix((era,)).any(|((validator, _), exposure_page)| {
1692 validator == *who || exposure_page.others.iter().any(|i| i.who == *who)
1693 })
1694 }
1695
1696 fn status(
1697 who: &Self::AccountId,
1698 ) -> Result<sp_staking::StakerStatus<Self::AccountId>, DispatchError> {
1699 if !StakingLedger::<T>::is_bonded(StakingAccount::Stash(who.clone())) {
1700 return Err(Error::<T>::NotStash.into())
1701 }
1702
1703 let is_validator = Validators::<T>::contains_key(&who);
1704 let is_nominator = Nominators::<T>::get(&who);
1705
1706 use sp_staking::StakerStatus;
1707 match (is_validator, is_nominator.is_some()) {
1708 (false, false) => Ok(StakerStatus::Idle),
1709 (true, false) => Ok(StakerStatus::Validator),
1710 (false, true) => Ok(StakerStatus::Nominator(
1711 is_nominator.expect("is checked above; qed").targets.into_inner(),
1712 )),
1713 (true, true) => {
1714 defensive!("cannot be both validators and nominator");
1715 Err(Error::<T>::BadState.into())
1716 },
1717 }
1718 }
1719
1720 fn is_virtual_staker(who: &T::AccountId) -> bool {
1725 frame_system::Pallet::<T>::account_nonce(who).is_zero() &&
1726 VirtualStakers::<T>::contains_key(who)
1727 }
1728
1729 fn slash_reward_fraction() -> Perbill {
1730 SlashRewardFraction::<T>::get()
1731 }
1732
1733 sp_staking::runtime_benchmarks_enabled! {
1734 fn nominations(who: &Self::AccountId) -> Option<Vec<T::AccountId>> {
1735 Nominators::<T>::get(who).map(|n| n.targets.into_inner())
1736 }
1737
1738 fn add_era_stakers(
1739 current_era: &EraIndex,
1740 stash: &T::AccountId,
1741 exposures: Vec<(Self::AccountId, Self::Balance)>,
1742 ) {
1743 let others = exposures
1744 .iter()
1745 .map(|(who, value)| crate::IndividualExposure { who: who.clone(), value: *value })
1746 .collect::<Vec<_>>();
1747 let exposure = Exposure { total: Default::default(), own: Default::default(), others };
1748 Eras::<T>::upsert_exposure(*current_era, stash, exposure);
1749 }
1750
1751 fn set_current_era(era: EraIndex) {
1752 CurrentEra::<T>::put(era);
1753 }
1754
1755 fn max_exposure_page_size() -> Page {
1756 T::MaxExposurePageSize::get()
1757 }
1758 }
1759}
1760
1761impl<T: Config> sp_staking::StakingUnchecked for Pallet<T> {
1762 fn migrate_to_virtual_staker(who: &Self::AccountId) -> DispatchResult {
1763 asset::kill_stake::<T>(who)?;
1764 VirtualStakers::<T>::insert(who, ());
1765 Ok(())
1766 }
1767
1768 fn virtual_bond(
1772 keyless_who: &Self::AccountId,
1773 value: Self::Balance,
1774 payee: &Self::AccountId,
1775 ) -> DispatchResult {
1776 if StakingLedger::<T>::is_bonded(StakingAccount::Stash(keyless_who.clone())) {
1777 return Err(Error::<T>::AlreadyBonded.into())
1778 }
1779
1780 ensure!(keyless_who != payee, Error::<T>::RewardDestinationRestricted);
1782
1783 VirtualStakers::<T>::insert(keyless_who, ());
1785
1786 Self::deposit_event(Event::<T>::Bonded { stash: keyless_who.clone(), amount: value });
1787 let ledger = StakingLedger::<T>::new(keyless_who.clone(), value);
1788
1789 ledger.bond(RewardDestination::Account(payee.clone()))?;
1790
1791 Ok(())
1792 }
1793
1794 #[cfg(feature = "runtime-benchmarks")]
1796 fn migrate_to_direct_staker(who: &Self::AccountId) {
1797 assert!(VirtualStakers::<T>::contains_key(who));
1798 let ledger = StakingLedger::<T>::get(Stash(who.clone())).unwrap();
1799 let _ = asset::update_stake::<T>(who, ledger.total)
1800 .expect("funds must be transferred to stash");
1801 VirtualStakers::<T>::remove(who);
1802 }
1803}
1804
1805#[cfg(any(test, feature = "try-runtime"))]
1806impl<T: Config> Pallet<T> {
1807 pub(crate) fn do_try_state(_now: BlockNumberFor<T>) -> Result<(), TryRuntimeError> {
1808 if ActiveEra::<T>::get().is_none() && CurrentEra::<T>::get().is_none() {
1811 return Ok(());
1812 }
1813
1814 session_rotation::Rotator::<T>::do_try_state()?;
1815 session_rotation::Eras::<T>::do_try_state()?;
1816
1817 use frame_support::traits::fungible::Inspect;
1818 if T::CurrencyToVote::will_downscale(T::Currency::total_issuance()).map_or(false, |x| x) {
1819 log!(warn, "total issuance will cause T::CurrencyToVote to downscale -- report to maintainers.")
1820 }
1821
1822 Self::check_ledgers()?;
1823 Self::check_bonded_consistency()?;
1824 Self::check_payees()?;
1825 Self::check_paged_exposures()?;
1826 Self::check_count()?;
1827 Self::check_slash_health()?;
1828
1829 Ok(())
1830 }
1831
1832 fn check_bonded_consistency() -> Result<(), TryRuntimeError> {
1842 use alloc::collections::btree_set::BTreeSet;
1843
1844 let mut count_controller_double = 0;
1845 let mut count_double = 0;
1846 let mut count_none = 0;
1847 let mut controllers = BTreeSet::new();
1850
1851 for (stash, controller) in <Bonded<T>>::iter() {
1852 if !controllers.insert(controller.clone()) {
1853 count_controller_double += 1;
1854 }
1855
1856 match (<Ledger<T>>::get(&stash), <Ledger<T>>::get(&controller)) {
1857 (Some(_), Some(_)) =>
1858 if stash != controller {
1862 count_double += 1;
1863 },
1864 (None, None) => {
1865 count_none += 1;
1866 },
1867 _ => {},
1868 };
1869 }
1870
1871 if count_controller_double != 0 {
1872 log!(
1873 warn,
1874 "a controller is associated with more than one ledger ({} occurrences)",
1875 count_controller_double
1876 );
1877 };
1878
1879 if count_double != 0 {
1880 log!(warn, "single tuple of (stash, controller) pair bonds more than one ledger ({} occurrences)", count_double);
1881 }
1882
1883 if count_none != 0 {
1884 log!(warn, "inconsistent bonded state: (stash, controller) pair missing associated ledger ({} occurrences)", count_none);
1885 }
1886
1887 Ok(())
1888 }
1889
1890 fn check_payees() -> Result<(), TryRuntimeError> {
1895 for (stash, _) in Bonded::<T>::iter() {
1896 ensure!(Payee::<T>::get(&stash).is_some(), "bonded ledger does not have payee set");
1897 }
1898
1899 ensure!(
1900 (Ledger::<T>::iter().count() == Payee::<T>::iter().count()) &&
1901 (Ledger::<T>::iter().count() == Bonded::<T>::iter().count()),
1902 "number of entries in payee storage items does not match the number of bonded ledgers",
1903 );
1904
1905 Ok(())
1906 }
1907
1908 fn check_count() -> Result<(), TryRuntimeError> {
1912 crate::log!(
1916 debug,
1917 "VoterList count: {}, Nominators count: {}, Validators count: {}",
1918 <T as Config>::VoterList::count(),
1919 Nominators::<T>::count(),
1920 Validators::<T>::count()
1921 );
1922
1923 ensure!(
1924 <T as Config>::TargetList::count() == Validators::<T>::count(),
1925 "wrong external count"
1926 );
1927 let max_validators_bound = crate::MaxWinnersOf::<T>::get();
1928 let max_winners_per_page_bound = crate::MaxWinnersPerPageOf::<T::ElectionProvider>::get();
1929 ensure!(
1930 max_validators_bound >= max_winners_per_page_bound,
1931 "max validators should be higher than per page bounds"
1932 );
1933 ensure!(ValidatorCount::<T>::get() <= max_validators_bound, Error::<T>::TooManyValidators);
1934 Ok(())
1935 }
1936
1937 fn check_ledgers() -> Result<(), TryRuntimeError> {
1945 Bonded::<T>::iter()
1946 .map(|(stash, ctrl)| {
1947 if VirtualStakers::<T>::contains_key(stash.clone()) {
1949 ensure!(
1950 asset::staked::<T>(&stash) == Zero::zero(),
1951 "virtual stakers should not have any staked balance"
1952 );
1953 ensure!(
1954 <Bonded<T>>::get(stash.clone()).unwrap() == stash.clone(),
1955 "stash and controller should be same"
1956 );
1957 ensure!(
1958 Ledger::<T>::get(stash.clone()).unwrap().stash == stash,
1959 "ledger corrupted for virtual staker"
1960 );
1961 ensure!(
1962 frame_system::Pallet::<T>::account_nonce(&stash).is_zero(),
1963 "virtual stakers are keyless and should not have any nonce"
1964 );
1965 let reward_destination = <Payee<T>>::get(stash.clone()).unwrap();
1966 if let RewardDestination::Account(payee) = reward_destination {
1967 ensure!(
1968 payee != stash.clone(),
1969 "reward destination should not be same as stash for virtual staker"
1970 );
1971 } else {
1972 return Err(DispatchError::Other(
1973 "reward destination must be of account variant for virtual staker",
1974 ));
1975 }
1976 } else {
1977 let integrity = Self::inspect_bond_state(&stash);
1978 if integrity != Ok(LedgerIntegrityState::Ok) {
1979 log!(
1981 error,
1982 "defensive: bonded stash {:?} has inconsistent ledger state: {:?}",
1983 stash,
1984 integrity
1985 );
1986 }
1987 }
1988
1989 Self::ensure_ledger_consistent(&ctrl)?;
1990 Self::ensure_ledger_role_and_min_bond(&ctrl)?;
1991 Ok(())
1992 })
1993 .collect::<Result<Vec<_>, _>>()?;
1994 Ok(())
1995 }
1996
1997 fn check_paged_exposures() -> Result<(), TryRuntimeError> {
2008 let Some(era) = ActiveEra::<T>::get().map(|a| a.index) else { return Ok(()) };
2009 let overview_and_pages = ErasStakersOverview::<T>::iter_prefix(era)
2010 .map(|(validator, metadata)| {
2011 if LastValidatorEra::<T>::get(&validator) != Some(era) {
2013 log!(
2014 warn,
2015 "Validator {:?} has incorrect LastValidatorEra (expected {:?}, got {:?})",
2016 validator,
2017 era,
2018 LastValidatorEra::<T>::get(&validator)
2019 );
2020 }
2021
2022 let pages = ErasStakersPaged::<T>::iter_prefix((era, validator))
2023 .map(|(_idx, page)| page)
2024 .collect::<Vec<_>>();
2025 (metadata, pages)
2026 })
2027 .collect::<Vec<_>>();
2028
2029 ensure!(
2030 overview_and_pages.iter().flat_map(|(_m, pages)| pages).all(|page| {
2031 let expected = page
2032 .others
2033 .iter()
2034 .map(|e| e.value)
2035 .fold(BalanceOf::<T>::zero(), |acc, x| acc + x);
2036 page.page_total == expected
2037 }),
2038 "found wrong page_total"
2039 );
2040
2041 ensure!(
2042 overview_and_pages.iter().all(|(metadata, pages)| {
2043 let page_count_good = metadata.page_count == pages.len() as u32;
2044 let nominator_count_good = metadata.nominator_count ==
2045 pages.iter().map(|p| p.others.len() as u32).fold(0u32, |acc, x| acc + x);
2046 let total_good = metadata.total ==
2047 metadata.own +
2048 pages
2049 .iter()
2050 .fold(BalanceOf::<T>::zero(), |acc, page| acc + page.page_total);
2051
2052 page_count_good && nominator_count_good && total_good
2053 }),
2054 "found bad metadata"
2055 );
2056
2057 ensure!(
2058 overview_and_pages
2059 .iter()
2060 .map(|(metadata, _pages)| metadata.total)
2061 .fold(BalanceOf::<T>::zero(), |acc, x| acc + x) ==
2062 ErasTotalStake::<T>::get(era),
2063 "found bad eras total stake"
2064 );
2065
2066 Ok(())
2067 }
2068
2069 fn check_slash_health() -> Result<(), TryRuntimeError> {
2071 let offence_queue_eras = OffenceQueueEras::<T>::get().unwrap_or_default().into_inner();
2073 let mut sorted_offence_queue_eras = offence_queue_eras.clone();
2074 sorted_offence_queue_eras.sort();
2075 ensure!(
2076 sorted_offence_queue_eras == offence_queue_eras,
2077 "Offence queue eras are not sorted"
2078 );
2079 drop(sorted_offence_queue_eras);
2080
2081 let active_era = Rotator::<T>::active_era();
2083 let oldest_unprocessed_offence_era =
2084 offence_queue_eras.first().cloned().unwrap_or(active_era);
2085
2086 let oldest_unprocessed_offence_age =
2090 active_era.saturating_sub(oldest_unprocessed_offence_era);
2091
2092 if oldest_unprocessed_offence_age > 2.min(T::BondingDuration::get()) {
2094 log!(
2095 warn,
2096 "Offence queue has unprocessed offences from older than 2 eras: oldest offence era in queue {:?} (active era: {:?})",
2097 oldest_unprocessed_offence_era,
2098 active_era
2099 );
2100 }
2101
2102 ensure!(
2104 oldest_unprocessed_offence_age < T::BondingDuration::get() - 1,
2105 "offences from era less than 3 eras old from active era not processed yet"
2106 );
2107
2108 for e in offence_queue_eras {
2110 let count = OffenceQueue::<T>::iter_prefix(e).count();
2111 ensure!(count > 0, "Offence queue is empty for era listed in offence queue eras");
2112 log!(info, "Offence queue for era {:?} has {:?} offences queued", e, count);
2113 }
2114
2115 for era in (active_era.saturating_sub(T::BondingDuration::get()))..(active_era) {
2119 Self::ensure_era_slashes_applied(era)?;
2123 }
2124
2125 for (era, _) in CancelledSlashes::<T>::iter() {
2127 ensure!(era >= active_era, "Found cancelled slashes for era before active era");
2128 }
2129
2130 Ok(())
2131 }
2132
2133 fn ensure_ledger_role_and_min_bond(ctrl: &T::AccountId) -> Result<(), TryRuntimeError> {
2134 let ledger = Self::ledger(StakingAccount::Controller(ctrl.clone()))?;
2135 let stash = ledger.stash;
2136
2137 let is_nominator = Nominators::<T>::contains_key(&stash);
2138 let is_validator = Validators::<T>::contains_key(&stash);
2139
2140 match (is_nominator, is_validator) {
2141 (false, false) => {
2142 if ledger.active < Self::min_chilled_bond() && !ledger.active.is_zero() {
2143 log!(
2145 warn,
2146 "Chilled stash {:?} has less stake ({:?}) than minimum role bond ({:?})",
2147 stash,
2148 ledger.active,
2149 Self::min_chilled_bond()
2150 );
2151 }
2152 },
2154 (true, false) => {
2155 if ledger.active < Self::min_nominator_bond() {
2157 log!(
2158 warn,
2159 "Nominator {:?} has less stake ({:?}) than minimum role bond ({:?})",
2160 stash,
2161 ledger.active,
2162 Self::min_nominator_bond()
2163 );
2164 }
2165 },
2166 (false, true) => {
2167 if ledger.active < Self::min_validator_bond() {
2169 log!(
2170 warn,
2171 "Validator {:?} has less stake ({:?}) than minimum role bond ({:?})",
2172 stash,
2173 ledger.active,
2174 Self::min_validator_bond()
2175 );
2176 }
2177 },
2178 (true, true) => {
2179 ensure!(false, "Stash cannot be both nominator and validator");
2180 },
2181 }
2182 Ok(())
2183 }
2184
2185 fn ensure_ledger_consistent(ctrl: &T::AccountId) -> Result<(), TryRuntimeError> {
2186 let ledger = Self::ledger(StakingAccount::Controller(ctrl.clone()))?;
2188
2189 let real_total: BalanceOf<T> =
2190 ledger.unlocking.iter().fold(ledger.active, |a, c| a + c.value);
2191 ensure!(real_total == ledger.total, "ledger.total corrupt");
2192
2193 Ok(())
2194 }
2195}