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};
46use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin};
47use pallet_staking_async_rc_client::{self as rc_client};
48use sp_runtime::{
49 traits::{CheckedAdd, Saturating, StaticLookup, Zero},
50 ArithmeticError, DispatchResult, Perbill,
51};
52use sp_staking::{
53 currency_to_vote::CurrencyToVote,
54 EraIndex, OnStakingUpdate, Page, SessionIndex, Stake,
55 StakingAccount::{self, Controller, Stash},
56 StakingInterface,
57};
58
59use super::pallet::*;
60
61#[cfg(feature = "try-runtime")]
62use frame_support::ensure;
63#[cfg(any(test, feature = "try-runtime"))]
64use sp_runtime::TryRuntimeError;
65
66const NPOS_MAX_ITERATIONS_COEFFICIENT: u32 = 2;
73
74impl<T: Config> Pallet<T> {
75 pub(crate) fn min_chilled_bond() -> BalanceOf<T> {
82 MinValidatorBond::<T>::get()
83 .min(MinNominatorBond::<T>::get())
84 .max(asset::existential_deposit::<T>())
85 }
86
87 pub(crate) fn min_validator_bond() -> BalanceOf<T> {
90 MinValidatorBond::<T>::get().max(Self::min_nominator_bond())
91 }
92
93 pub(crate) fn min_nominator_bond() -> BalanceOf<T> {
96 MinNominatorBond::<T>::get().max(asset::existential_deposit::<T>())
97 }
98
99 pub fn ledger(account: StakingAccount<T::AccountId>) -> Result<StakingLedger<T>, Error<T>> {
101 StakingLedger::<T>::get(account)
102 }
103
104 pub fn payee(account: StakingAccount<T::AccountId>) -> Option<RewardDestination<T::AccountId>> {
105 StakingLedger::<T>::reward_destination(account)
106 }
107
108 pub fn bonded(stash: &T::AccountId) -> Option<T::AccountId> {
110 StakingLedger::<T>::paired_account(Stash(stash.clone()))
111 }
112
113 pub(crate) fn inspect_bond_state(
120 stash: &T::AccountId,
121 ) -> Result<LedgerIntegrityState, Error<T>> {
122 let hold_or_lock = asset::staked::<T>(&stash)
124 .max(T::OldCurrency::balance_locked(STAKING_ID, &stash).into());
125
126 let controller = <Bonded<T>>::get(stash).ok_or_else(|| {
127 if hold_or_lock == Zero::zero() {
128 Error::<T>::NotStash
129 } else {
130 Error::<T>::BadState
131 }
132 })?;
133
134 match Ledger::<T>::get(controller) {
135 Some(ledger) =>
136 if ledger.stash != *stash {
137 Ok(LedgerIntegrityState::Corrupted)
138 } else {
139 if hold_or_lock != ledger.total {
140 Ok(LedgerIntegrityState::LockCorrupted)
141 } else {
142 Ok(LedgerIntegrityState::Ok)
143 }
144 },
145 None => Ok(LedgerIntegrityState::CorruptedKilled),
146 }
147 }
148
149 pub fn slashable_balance_of(stash: &T::AccountId) -> BalanceOf<T> {
151 Self::ledger(Stash(stash.clone())).map(|l| l.active).unwrap_or_default()
153 }
154
155 pub fn slashable_balance_of_vote_weight(
157 stash: &T::AccountId,
158 issuance: BalanceOf<T>,
159 ) -> VoteWeight {
160 T::CurrencyToVote::to_vote(Self::slashable_balance_of(stash), issuance)
161 }
162
163 pub fn weight_of_fn() -> Box<dyn Fn(&T::AccountId) -> VoteWeight> {
168 let issuance = asset::total_issuance::<T>();
171 Box::new(move |who: &T::AccountId| -> VoteWeight {
172 Self::slashable_balance_of_vote_weight(who, issuance)
173 })
174 }
175
176 pub fn weight_of(who: &T::AccountId) -> VoteWeight {
178 let issuance = asset::total_issuance::<T>();
179 Self::slashable_balance_of_vote_weight(who, issuance)
180 }
181
182 pub(super) fn do_bond_extra(stash: &T::AccountId, additional: BalanceOf<T>) -> DispatchResult {
183 let mut ledger = Self::ledger(StakingAccount::Stash(stash.clone()))?;
184
185 let extra = if Self::is_virtual_staker(stash) {
188 additional
189 } else {
190 additional.min(asset::free_to_stake::<T>(stash))
192 };
193
194 ledger.total = ledger.total.checked_add(&extra).ok_or(ArithmeticError::Overflow)?;
195 ledger.active = ledger.active.checked_add(&extra).ok_or(ArithmeticError::Overflow)?;
196 ensure!(ledger.active >= Self::min_chilled_bond(), Error::<T>::InsufficientBond);
198
199 ledger.update()?;
201 if T::VoterList::contains(stash) {
203 let _ = T::VoterList::on_update(&stash, Self::weight_of(stash));
205 }
206
207 Self::deposit_event(Event::<T>::Bonded { stash: stash.clone(), amount: extra });
208
209 Ok(())
210 }
211
212 pub(super) fn do_withdraw_unbonded(controller: &T::AccountId) -> Result<Weight, DispatchError> {
213 let mut ledger = Self::ledger(Controller(controller.clone()))?;
214 let (stash, old_total) = (ledger.stash.clone(), ledger.total);
215 if let Some(current_era) = CurrentEra::<T>::get() {
216 ledger = ledger.consolidate_unlocked(current_era)
217 }
218 let new_total = ledger.total;
219
220 let used_weight = if ledger.unlocking.is_empty() &&
221 (ledger.active < Self::min_chilled_bond() || ledger.active.is_zero())
222 {
223 Self::kill_stash(&ledger.stash)?;
227
228 T::WeightInfo::withdraw_unbonded_kill()
229 } else {
230 ledger.update()?;
232
233 T::WeightInfo::withdraw_unbonded_update()
235 };
236
237 if new_total < old_total {
240 let value = old_total.defensive_saturating_sub(new_total);
242 Self::deposit_event(Event::<T>::Withdrawn { stash, amount: value });
243
244 T::EventListeners::on_withdraw(controller, value);
246 }
247
248 Ok(used_weight)
249 }
250
251 pub(super) fn do_payout_stakers(
252 validator_stash: T::AccountId,
253 era: EraIndex,
254 ) -> DispatchResultWithPostInfo {
255 let page = Eras::<T>::get_next_claimable_page(era, &validator_stash).ok_or_else(|| {
256 Error::<T>::AlreadyClaimed.with_weight(T::WeightInfo::payout_stakers_alive_staked(0))
257 })?;
258
259 Self::do_payout_stakers_by_page(validator_stash, era, page)
260 }
261
262 pub(super) fn do_payout_stakers_by_page(
263 validator_stash: T::AccountId,
264 era: EraIndex,
265 page: Page,
266 ) -> DispatchResultWithPostInfo {
267 let current_era = CurrentEra::<T>::get().ok_or_else(|| {
269 Error::<T>::InvalidEraToReward
270 .with_weight(T::WeightInfo::payout_stakers_alive_staked(0))
271 })?;
272
273 let history_depth = T::HistoryDepth::get();
274
275 ensure!(
276 era <= current_era && era >= current_era.saturating_sub(history_depth),
277 Error::<T>::InvalidEraToReward
278 .with_weight(T::WeightInfo::payout_stakers_alive_staked(0))
279 );
280
281 ensure!(
282 page < Eras::<T>::exposure_page_count(era, &validator_stash),
283 Error::<T>::InvalidPage.with_weight(T::WeightInfo::payout_stakers_alive_staked(0))
284 );
285
286 let era_payout = Eras::<T>::get_validators_reward(era).ok_or_else(|| {
288 Error::<T>::InvalidEraToReward
289 .with_weight(T::WeightInfo::payout_stakers_alive_staked(0))
290 })?;
291
292 let account = StakingAccount::Stash(validator_stash.clone());
293 let ledger = Self::ledger(account.clone()).or_else(|_| {
294 if StakingLedger::<T>::is_bonded(account) {
295 Err(Error::<T>::NotController.into())
296 } else {
297 Err(Error::<T>::NotStash.with_weight(T::WeightInfo::payout_stakers_alive_staked(0)))
298 }
299 })?;
300
301 ledger.clone().update()?;
302
303 let stash = ledger.stash.clone();
304
305 if Eras::<T>::is_rewards_claimed(era, &stash, page) {
306 return Err(Error::<T>::AlreadyClaimed
307 .with_weight(T::WeightInfo::payout_stakers_alive_staked(0)))
308 }
309
310 Eras::<T>::set_rewards_as_claimed(era, &stash, page);
311
312 let exposure = Eras::<T>::get_paged_exposure(era, &stash, page).ok_or_else(|| {
313 Error::<T>::InvalidEraToReward
314 .with_weight(T::WeightInfo::payout_stakers_alive_staked(0))
315 })?;
316
317 let era_reward_points = Eras::<T>::get_reward_points(era);
327 let total_reward_points = era_reward_points.total;
328 let validator_reward_points =
329 era_reward_points.individual.get(&stash).copied().unwrap_or_else(Zero::zero);
330
331 if validator_reward_points.is_zero() {
333 return Ok(Some(T::WeightInfo::payout_stakers_alive_staked(0)).into())
334 }
335
336 let validator_total_reward_part =
339 Perbill::from_rational(validator_reward_points, total_reward_points);
340
341 let validator_total_payout = validator_total_reward_part * era_payout;
343
344 let validator_commission = Eras::<T>::get_validator_commission(era, &ledger.stash);
345 let validator_total_commission_payout = validator_commission * validator_total_payout;
347
348 let validator_leftover_payout =
349 validator_total_payout.defensive_saturating_sub(validator_total_commission_payout);
350 let validator_exposure_part = Perbill::from_rational(exposure.own(), exposure.total());
352 let validator_staking_payout = validator_exposure_part * validator_leftover_payout;
353 let page_stake_part = Perbill::from_rational(exposure.page_total(), exposure.total());
354 let validator_commission_payout = page_stake_part * validator_total_commission_payout;
356
357 Self::deposit_event(Event::<T>::PayoutStarted {
358 era_index: era,
359 validator_stash: stash.clone(),
360 page,
361 next: Eras::<T>::get_next_claimable_page(era, &stash),
362 });
363
364 let mut total_imbalance = PositiveImbalanceOf::<T>::zero();
365 if let Some((imbalance, dest)) =
367 Self::make_payout(&stash, validator_staking_payout + validator_commission_payout)
368 {
369 Self::deposit_event(Event::<T>::Rewarded { stash, dest, amount: imbalance.peek() });
370 total_imbalance.subsume(imbalance);
371 }
372
373 let mut nominator_payout_count: u32 = 0;
377
378 for nominator in exposure.others().iter() {
381 let nominator_exposure_part = Perbill::from_rational(nominator.value, exposure.total());
382
383 let nominator_reward: BalanceOf<T> =
384 nominator_exposure_part * validator_leftover_payout;
385 if let Some((imbalance, dest)) = Self::make_payout(&nominator.who, nominator_reward) {
387 nominator_payout_count += 1;
389 let e = Event::<T>::Rewarded {
390 stash: nominator.who.clone(),
391 dest,
392 amount: imbalance.peek(),
393 };
394 Self::deposit_event(e);
395 total_imbalance.subsume(imbalance);
396 }
397 }
398
399 T::Reward::on_unbalanced(total_imbalance);
400 debug_assert!(nominator_payout_count <= T::MaxExposurePageSize::get());
401
402 Ok(Some(T::WeightInfo::payout_stakers_alive_staked(nominator_payout_count)).into())
403 }
404
405 pub(crate) fn chill_stash(stash: &T::AccountId) {
407 let chilled_as_validator = Self::do_remove_validator(stash);
408 let chilled_as_nominator = Self::do_remove_nominator(stash);
409 if chilled_as_validator || chilled_as_nominator {
410 Self::deposit_event(Event::<T>::Chilled { stash: stash.clone() });
411 }
412 }
413
414 fn make_payout(
417 stash: &T::AccountId,
418 amount: BalanceOf<T>,
419 ) -> Option<(PositiveImbalanceOf<T>, RewardDestination<T::AccountId>)> {
420 if amount.is_zero() {
422 return None
423 }
424 let dest = Self::payee(StakingAccount::Stash(stash.clone()))?;
425
426 let maybe_imbalance = match dest {
427 RewardDestination::Stash => asset::mint_into_existing::<T>(stash, amount),
428 RewardDestination::Staked => Self::ledger(Stash(stash.clone()))
429 .and_then(|mut ledger| {
430 ledger.active += amount;
431 ledger.total += amount;
432 let r = asset::mint_into_existing::<T>(stash, amount);
433
434 let _ = ledger
435 .update()
436 .defensive_proof("ledger fetched from storage, so it exists; qed.");
437
438 Ok(r)
439 })
440 .unwrap_or_default(),
441 RewardDestination::Account(ref dest_account) =>
442 Some(asset::mint_creating::<T>(&dest_account, amount)),
443 RewardDestination::None => None,
444 #[allow(deprecated)]
445 RewardDestination::Controller => Self::bonded(stash)
446 .map(|controller| {
447 defensive!("Paying out controller as reward destination which is deprecated and should be migrated.");
448 asset::mint_creating::<T>(&controller, amount)
451 }),
452 };
453 maybe_imbalance.map(|imbalance| (imbalance, dest))
454 }
455
456 pub(crate) fn kill_stash(stash: &T::AccountId) -> DispatchResult {
464 StakingLedger::<T>::kill(&stash)?;
467
468 Self::do_remove_validator(&stash);
469 Self::do_remove_nominator(&stash);
470
471 Ok(())
472 }
473
474 #[cfg(test)]
475 pub(crate) fn reward_by_ids(validators_points: impl IntoIterator<Item = (T::AccountId, u32)>) {
476 Eras::<T>::reward_active_era(validators_points)
477 }
478
479 pub(crate) fn set_force_era(mode: Forcing) {
481 log!(info, "Setting force era mode {:?}.", mode);
482 ForceEra::<T>::put(mode);
483 Self::deposit_event(Event::<T>::ForceEra { mode });
484 }
485
486 #[cfg(feature = "runtime-benchmarks")]
487 pub fn add_era_stakers(
488 current_era: EraIndex,
489 stash: T::AccountId,
490 exposure: Exposure<T::AccountId, BalanceOf<T>>,
491 ) {
492 Eras::<T>::upsert_exposure(current_era, &stash, exposure);
493 }
494
495 #[cfg(feature = "runtime-benchmarks")]
496 pub fn set_slash_reward_fraction(fraction: Perbill) {
497 SlashRewardFraction::<T>::put(fraction);
498 }
499
500 pub(crate) fn get_npos_voters(
510 bounds: DataProviderBounds,
511 status: &SnapshotStatus<T::AccountId>,
512 ) -> Vec<VoterOf<Self>> {
513 let mut voters_size_tracker: StaticTracker<Self> = StaticTracker::default();
514
515 let page_len_prediction = {
516 let all_voter_count = T::VoterList::count();
517 bounds.count.unwrap_or(all_voter_count.into()).min(all_voter_count.into()).0
518 };
519
520 let mut all_voters = Vec::<_>::with_capacity(page_len_prediction as usize);
521
522 let weight_of = Self::weight_of_fn();
524
525 let mut voters_seen = 0u32;
526 let mut validators_taken = 0u32;
527 let mut nominators_taken = 0u32;
528 let mut min_active_stake = u64::MAX;
529
530 let mut sorted_voters = match status {
531 SnapshotStatus::Waiting => T::VoterList::iter(),
533 SnapshotStatus::Ongoing(account_id) => T::VoterList::iter_from(&account_id)
535 .defensive_unwrap_or(Box::new(vec![].into_iter())),
536 SnapshotStatus::Consumed => Box::new(vec![].into_iter()),
538 };
539
540 while all_voters.len() < page_len_prediction as usize &&
541 voters_seen < (NPOS_MAX_ITERATIONS_COEFFICIENT * page_len_prediction as u32)
542 {
543 let voter = match sorted_voters.next() {
544 Some(voter) => {
545 voters_seen.saturating_inc();
546 voter
547 },
548 None => break,
549 };
550
551 let voter_weight = weight_of(&voter);
552 if voter_weight.is_zero() {
554 log!(debug, "voter's active balance is 0. skip this voter.");
555 continue
556 }
557
558 if let Some(Nominations { targets, .. }) = <Nominators<T>>::get(&voter) {
559 if !targets.is_empty() {
560 let voter = (voter, voter_weight, targets);
565 if voters_size_tracker.try_register_voter(&voter, &bounds).is_err() {
566 Self::deposit_event(Event::<T>::SnapshotVotersSizeExceeded {
568 size: voters_size_tracker.size as u32,
569 });
570 break
571 }
572
573 all_voters.push(voter);
574 nominators_taken.saturating_inc();
575 } else {
576 defensive!("non-nominator fetched from voter list: {:?}", voter);
577 }
579 min_active_stake =
580 if voter_weight < min_active_stake { voter_weight } else { min_active_stake };
581 } else if Validators::<T>::contains_key(&voter) {
582 let self_vote = (
584 voter.clone(),
585 voter_weight,
586 vec![voter.clone()]
587 .try_into()
588 .expect("`MaxVotesPerVoter` must be greater than or equal to 1"),
589 );
590
591 if voters_size_tracker.try_register_voter(&self_vote, &bounds).is_err() {
592 Self::deposit_event(Event::<T>::SnapshotVotersSizeExceeded {
594 size: voters_size_tracker.size as u32,
595 });
596 break
597 }
598 all_voters.push(self_vote);
599 validators_taken.saturating_inc();
600 } else {
601 defensive!(
607 "invalid item in `VoterList`: {:?}, this nominator probably has too many nominations now",
608 voter,
609 );
610 }
611 }
612
613 debug_assert!(all_voters.capacity() == page_len_prediction as usize);
615
616 let min_active_stake: T::CurrencyBalance =
617 if all_voters.is_empty() { Zero::zero() } else { min_active_stake.into() };
618
619 MinimumActiveStake::<T>::put(min_active_stake);
620
621 all_voters
622 }
623
624 pub fn get_npos_targets(bounds: DataProviderBounds) -> Vec<T::AccountId> {
630 let mut targets_size_tracker: StaticTracker<Self> = StaticTracker::default();
631
632 let final_predicted_len = {
633 let all_target_count = T::TargetList::count();
634 bounds.count.unwrap_or(all_target_count.into()).min(all_target_count.into()).0
635 };
636
637 let mut all_targets = Vec::<T::AccountId>::with_capacity(final_predicted_len as usize);
638 let mut targets_seen = 0;
639
640 let mut targets_iter = T::TargetList::iter();
641 while all_targets.len() < final_predicted_len as usize &&
642 targets_seen < (NPOS_MAX_ITERATIONS_COEFFICIENT * final_predicted_len as u32)
643 {
644 let target = match targets_iter.next() {
645 Some(target) => {
646 targets_seen.saturating_inc();
647 target
648 },
649 None => break,
650 };
651
652 if targets_size_tracker.try_register_target(target.clone(), &bounds).is_err() {
653 log!(warn, "npos targets size exceeded, stopping iteration.");
655 Self::deposit_event(Event::<T>::SnapshotTargetsSizeExceeded {
656 size: targets_size_tracker.size as u32,
657 });
658 break
659 }
660
661 if Validators::<T>::contains_key(&target) {
662 all_targets.push(target);
663 }
664 }
665
666 log!(debug, "[bounds {:?}] generated {} npos targets", bounds, all_targets.len());
667
668 all_targets
669 }
670
671 pub fn do_add_nominator(who: &T::AccountId, nominations: Nominations<T>) {
680 if !Nominators::<T>::contains_key(who) {
681 let _ = T::VoterList::on_insert(who.clone(), Self::weight_of(who))
683 .defensive_unwrap_or_default();
684 }
685 Nominators::<T>::insert(who, nominations);
686
687 debug_assert_eq!(
688 Nominators::<T>::count() + Validators::<T>::count(),
689 T::VoterList::count()
690 );
691 }
692
693 pub fn do_remove_nominator(who: &T::AccountId) -> bool {
702 let outcome = if Nominators::<T>::contains_key(who) {
703 Nominators::<T>::remove(who);
704 let _ = T::VoterList::on_remove(who);
705 true
706 } else {
707 false
708 };
709
710 debug_assert_eq!(
711 Nominators::<T>::count() + Validators::<T>::count(),
712 T::VoterList::count()
713 );
714
715 outcome
716 }
717
718 pub fn do_add_validator(who: &T::AccountId, prefs: ValidatorPrefs) {
726 if !Validators::<T>::contains_key(who) {
727 let _ = T::VoterList::on_insert(who.clone(), Self::weight_of(who));
729 }
730 Validators::<T>::insert(who, prefs);
731
732 debug_assert_eq!(
733 Nominators::<T>::count() + Validators::<T>::count(),
734 T::VoterList::count()
735 );
736 }
737
738 pub fn do_remove_validator(who: &T::AccountId) -> bool {
746 let outcome = if Validators::<T>::contains_key(who) {
747 Validators::<T>::remove(who);
748 let _ = T::VoterList::on_remove(who);
749 true
750 } else {
751 false
752 };
753
754 debug_assert_eq!(
755 Nominators::<T>::count() + Validators::<T>::count(),
756 T::VoterList::count()
757 );
758
759 outcome
760 }
761
762 pub(crate) fn register_weight(weight: Weight) {
766 <frame_system::Pallet<T>>::register_extra_weight_unchecked(
767 weight,
768 DispatchClass::Mandatory,
769 );
770 }
771
772 pub fn eras_stakers(
779 era: EraIndex,
780 account: &T::AccountId,
781 ) -> Exposure<T::AccountId, BalanceOf<T>> {
782 Eras::<T>::get_full_exposure(era, account)
783 }
784
785 pub(super) fn do_migrate_currency(stash: &T::AccountId) -> DispatchResult {
786 if Self::is_virtual_staker(stash) {
787 return Self::do_migrate_virtual_staker(stash);
788 }
789
790 let ledger = Self::ledger(Stash(stash.clone()))?;
791 let staked: BalanceOf<T> = T::OldCurrency::balance_locked(STAKING_ID, stash).into();
792 ensure!(!staked.is_zero(), Error::<T>::AlreadyMigrated);
793 ensure!(ledger.total == staked, Error::<T>::BadState);
794
795 T::OldCurrency::remove_lock(STAKING_ID, &stash);
797
798 let max_hold = asset::free_to_stake::<T>(&stash);
800 let force_withdraw = if max_hold >= staked {
801 asset::update_stake::<T>(&stash, staked)?;
803 Zero::zero()
804 } else {
805 let force_withdraw = staked.saturating_sub(max_hold);
808
809 StakingLedger {
812 total: max_hold,
813 active: ledger.active.saturating_sub(force_withdraw),
814 ..ledger
816 }
817 .update()?;
818 force_withdraw
819 };
820
821 frame_system::Pallet::<T>::dec_consumers(&stash);
823
824 Self::deposit_event(Event::<T>::CurrencyMigrated { stash: stash.clone(), force_withdraw });
825 Ok(())
826 }
827
828 fn do_migrate_virtual_staker(stash: &T::AccountId) -> DispatchResult {
829 frame_system::Pallet::<T>::dec_consumers(&stash);
832
833 let actual_providers = frame_system::Pallet::<T>::providers(stash);
838
839 let expected_providers =
840 if asset::free_to_stake::<T>(&stash) >= asset::existential_deposit::<T>() {
843 2
844 } else {
845 1
846 };
847
848 ensure!(actual_providers <= expected_providers, Error::<T>::BadState);
850
851 ensure!(actual_providers == expected_providers, Error::<T>::AlreadyMigrated);
853
854 let _ = frame_system::Pallet::<T>::dec_providers(&stash)?;
856
857 return Ok(())
858 }
859}
860
861impl<T: Config> Pallet<T> {
862 pub fn api_nominations_quota(balance: BalanceOf<T>) -> u32 {
866 T::NominationsQuota::get_quota(balance)
867 }
868
869 pub fn api_eras_stakers(
870 era: EraIndex,
871 account: T::AccountId,
872 ) -> Exposure<T::AccountId, BalanceOf<T>> {
873 Self::eras_stakers(era, &account)
874 }
875
876 pub fn api_eras_stakers_page_count(era: EraIndex, account: T::AccountId) -> Page {
877 Eras::<T>::exposure_page_count(era, &account)
878 }
879
880 pub fn api_pending_rewards(era: EraIndex, account: T::AccountId) -> bool {
881 Eras::<T>::pending_rewards(era, &account)
882 }
883}
884
885impl<T: Config> ElectionDataProvider for Pallet<T> {
886 type AccountId = T::AccountId;
887 type BlockNumber = BlockNumberFor<T>;
888 type MaxVotesPerVoter = MaxNominationsOf<T>;
889
890 fn desired_targets() -> data_provider::Result<u32> {
891 Self::register_weight(T::DbWeight::get().reads(1));
892 Ok(ValidatorCount::<T>::get())
893 }
894
895 fn electing_voters(
896 bounds: DataProviderBounds,
897 page: PageIndex,
898 ) -> data_provider::Result<Vec<VoterOf<Self>>> {
899 let mut status = VoterSnapshotStatus::<T>::get();
900 let voters = Self::get_npos_voters(bounds, &status);
901
902 match (page, &status) {
904 (0, _) => status = SnapshotStatus::Waiting,
906
907 (_, SnapshotStatus::Waiting) | (_, SnapshotStatus::Ongoing(_)) => {
908 let maybe_last = voters.last().map(|(x, _, _)| x).cloned();
909
910 if let Some(ref last) = maybe_last {
911 let has_next =
912 T::VoterList::iter_from(last).ok().and_then(|mut i| i.next()).is_some();
913 if has_next {
914 status = SnapshotStatus::Ongoing(last.clone());
915 } else {
916 status = SnapshotStatus::Consumed;
917 }
918 }
919 },
920 (_, SnapshotStatus::Consumed) => (),
922 }
923
924 log!(
925 debug,
926 "[page {}, (next) status {:?}, bounds {:?}] generated {} npos voters",
927 page,
928 status,
929 bounds,
930 voters.len(),
931 );
932
933 match status {
934 SnapshotStatus::Ongoing(_) => T::VoterList::lock(),
935 _ => T::VoterList::unlock(),
936 }
937
938 VoterSnapshotStatus::<T>::put(status);
939 debug_assert!(!bounds.slice_exhausted(&voters));
940
941 Ok(voters)
942 }
943
944 fn electing_voters_stateless(
945 bounds: DataProviderBounds,
946 ) -> data_provider::Result<Vec<VoterOf<Self>>> {
947 let voters = Self::get_npos_voters(bounds, &SnapshotStatus::Waiting);
948 log!(debug, "[stateless, bounds {:?}] generated {} npos voters", bounds, voters.len(),);
949 Ok(voters)
950 }
951
952 fn electable_targets(
953 bounds: DataProviderBounds,
954 page: PageIndex,
955 ) -> data_provider::Result<Vec<T::AccountId>> {
956 if page > 0 {
957 log!(warn, "multi-page target snapshot not supported, returning page 0.");
958 }
959
960 let targets = Self::get_npos_targets(bounds);
961 if bounds.exhausted(None, CountBound(targets.len() as u32).into()) {
964 return Err("Target snapshot too big")
965 }
966
967 debug_assert!(!bounds.slice_exhausted(&targets));
968
969 Ok(targets)
970 }
971
972 fn next_election_prediction(_: BlockNumberFor<T>) -> BlockNumberFor<T> {
973 debug_assert!(false, "this is deprecated and not used anymore");
974 sp_runtime::traits::Bounded::max_value()
975 }
976
977 #[cfg(feature = "runtime-benchmarks")]
978 fn fetch_page(page: PageIndex) {
979 session_rotation::EraElectionPlanner::<T>::do_elect_paged(page);
980 }
981
982 #[cfg(feature = "runtime-benchmarks")]
983 fn add_voter(
984 voter: T::AccountId,
985 weight: VoteWeight,
986 targets: BoundedVec<T::AccountId, Self::MaxVotesPerVoter>,
987 ) {
988 let stake = <BalanceOf<T>>::try_from(weight).unwrap_or_else(|_| {
989 panic!("cannot convert a VoteWeight into BalanceOf, benchmark needs reconfiguring.")
990 });
991 <Bonded<T>>::insert(voter.clone(), voter.clone());
992 <Ledger<T>>::insert(voter.clone(), StakingLedger::<T>::new(voter.clone(), stake));
993
994 Self::do_add_nominator(&voter, Nominations { targets, submitted_in: 0, suppressed: false });
995 }
996
997 #[cfg(feature = "runtime-benchmarks")]
998 fn add_target(target: T::AccountId) {
999 let stake = (Self::min_validator_bond() + 1u32.into()) * 100u32.into();
1000 <Bonded<T>>::insert(target.clone(), target.clone());
1001 <Ledger<T>>::insert(target.clone(), StakingLedger::<T>::new(target.clone(), stake));
1002 Self::do_add_validator(
1003 &target,
1004 ValidatorPrefs { commission: Perbill::zero(), blocked: false },
1005 );
1006 }
1007
1008 #[cfg(feature = "runtime-benchmarks")]
1009 fn clear() {
1010 #[allow(deprecated)]
1011 <Bonded<T>>::remove_all(None);
1012 #[allow(deprecated)]
1013 <Ledger<T>>::remove_all(None);
1014 #[allow(deprecated)]
1015 <Validators<T>>::remove_all();
1016 #[allow(deprecated)]
1017 <Nominators<T>>::remove_all();
1018
1019 T::VoterList::unsafe_clear();
1020 }
1021
1022 #[cfg(feature = "runtime-benchmarks")]
1023 fn put_snapshot(
1024 voters: Vec<VoterOf<Self>>,
1025 targets: Vec<T::AccountId>,
1026 target_stake: Option<VoteWeight>,
1027 ) {
1028 targets.into_iter().for_each(|v| {
1029 let stake: BalanceOf<T> = target_stake
1030 .and_then(|w| <BalanceOf<T>>::try_from(w).ok())
1031 .unwrap_or_else(|| Self::min_nominator_bond() * 100u32.into());
1032 <Bonded<T>>::insert(v.clone(), v.clone());
1033 <Ledger<T>>::insert(v.clone(), StakingLedger::<T>::new(v.clone(), stake));
1034 Self::do_add_validator(
1035 &v,
1036 ValidatorPrefs { commission: Perbill::zero(), blocked: false },
1037 );
1038 });
1039
1040 voters.into_iter().for_each(|(v, s, t)| {
1041 let stake = <BalanceOf<T>>::try_from(s).unwrap_or_else(|_| {
1042 panic!("cannot convert a VoteWeight into BalanceOf, benchmark needs reconfiguring.")
1043 });
1044 <Bonded<T>>::insert(v.clone(), v.clone());
1045 <Ledger<T>>::insert(v.clone(), StakingLedger::<T>::new(v.clone(), stake));
1046 Self::do_add_nominator(
1047 &v,
1048 Nominations { targets: t, submitted_in: 0, suppressed: false },
1049 );
1050 });
1051 }
1052
1053 #[cfg(feature = "runtime-benchmarks")]
1054 fn set_desired_targets(count: u32) {
1055 ValidatorCount::<T>::put(count);
1056 }
1057}
1058
1059impl<T: Config> rc_client::AHStakingInterface for Pallet<T> {
1060 type AccountId = T::AccountId;
1061 type MaxValidatorSet = T::MaxValidatorSet;
1062
1063 fn on_relay_session_report(report: rc_client::SessionReport<Self::AccountId>) {
1072 log!(debug, "Received session report: {}", report,);
1073 let consumed_weight = T::WeightInfo::rc_on_session_report();
1074
1075 let rc_client::SessionReport {
1076 end_index,
1077 activation_timestamp,
1078 validator_points,
1079 leftover,
1080 } = report;
1081 debug_assert!(!leftover);
1082
1083 Eras::<T>::reward_active_era(validator_points.into_iter());
1084 session_rotation::Rotator::<T>::end_session(end_index, activation_timestamp);
1085 Self::register_weight(consumed_weight);
1090 }
1091
1092 fn on_new_offences(
1093 slash_session: SessionIndex,
1094 offences: Vec<rc_client::Offence<T::AccountId>>,
1095 ) {
1096 log!(debug, "๐ฆน on_new_offences: {:?}", offences);
1097 let consumed_weight = T::WeightInfo::rc_on_offence(offences.len() as u32);
1098
1099 let Some(active_era) = ActiveEra::<T>::get() else {
1101 log!(warn, "๐ฆน on_new_offences: no active era; ignoring offence");
1102 return
1103 };
1104
1105 let active_era_start_session = Rotator::<T>::active_era_start_session_index();
1106
1107 let offence_era = if slash_session >= active_era_start_session {
1110 active_era.index
1111 } else {
1112 match BondedEras::<T>::get()
1113 .iter()
1114 .rev()
1116 .find_map(|&(era, sesh)| if sesh <= slash_session { Some(era) } else { None })
1117 {
1118 Some(era) => era,
1119 None => {
1120 log!(warn, "๐ฆน on_offence: no era found for slash_session; ignoring offence");
1123 return
1124 },
1125 }
1126 };
1127
1128 let invulnerables = Invulnerables::<T>::get();
1129
1130 for o in offences {
1131 let slash_fraction = o.slash_fraction;
1132 let validator: <T as frame_system::Config>::AccountId = o.offender.into();
1133 if invulnerables.contains(&validator) {
1135 log!(debug, "๐ฆน on_offence: {:?} is invulnerable; ignoring offence", validator);
1136 continue
1137 }
1138
1139 let Some(exposure_overview) = <ErasStakersOverview<T>>::get(&offence_era, &validator)
1140 else {
1141 log!(
1144 warn,
1145 "๐ฆน on_offence: no exposure found for {:?} in era {}; ignoring offence",
1146 validator,
1147 offence_era
1148 );
1149 continue;
1150 };
1151
1152 Self::deposit_event(Event::<T>::OffenceReported {
1153 validator: validator.clone(),
1154 fraction: slash_fraction,
1155 offence_era,
1156 });
1157
1158 let prior_slash_fraction = ValidatorSlashInEra::<T>::get(offence_era, &validator)
1159 .map_or(Zero::zero(), |(f, _)| f);
1160
1161 if let Some(existing) = OffenceQueue::<T>::get(offence_era, &validator) {
1162 if slash_fraction.deconstruct() > existing.slash_fraction.deconstruct() {
1163 OffenceQueue::<T>::insert(
1164 offence_era,
1165 &validator,
1166 OffenceRecord {
1167 reporter: o.reporters.first().cloned(),
1168 reported_era: active_era.index,
1169 slash_fraction,
1170 ..existing
1171 },
1172 );
1173
1174 ValidatorSlashInEra::<T>::insert(
1176 offence_era,
1177 &validator,
1178 (slash_fraction, exposure_overview.own),
1179 );
1180
1181 log!(
1182 debug,
1183 "๐ฆน updated slash for {:?}: {:?} (prior: {:?})",
1184 validator,
1185 slash_fraction,
1186 prior_slash_fraction,
1187 );
1188 } else {
1189 log!(
1190 debug,
1191 "๐ฆน ignored slash for {:?}: {:?} (existing prior is larger: {:?})",
1192 validator,
1193 slash_fraction,
1194 prior_slash_fraction,
1195 );
1196 }
1197 } else if slash_fraction.deconstruct() > prior_slash_fraction.deconstruct() {
1198 ValidatorSlashInEra::<T>::insert(
1199 offence_era,
1200 &validator,
1201 (slash_fraction, exposure_overview.own),
1202 );
1203
1204 OffenceQueue::<T>::insert(
1205 offence_era,
1206 &validator,
1207 OffenceRecord {
1208 reporter: o.reporters.first().cloned(),
1209 reported_era: active_era.index,
1210 exposure_page: exposure_overview.page_count.saturating_sub(1),
1213 slash_fraction,
1214 prior_slash_fraction,
1215 },
1216 );
1217
1218 OffenceQueueEras::<T>::mutate(|q| {
1219 if let Some(eras) = q {
1220 log!(debug, "๐ฆน inserting offence era {} into existing queue", offence_era);
1221 eras.binary_search(&offence_era).err().map(|idx| {
1222 eras.try_insert(idx, offence_era).defensive_proof(
1223 "Offence era must be present in the existing queue",
1224 )
1225 });
1226 } else {
1227 let mut eras = BoundedVec::default();
1228 log!(debug, "๐ฆน inserting offence era {} into empty queue", offence_era);
1229 let _ = eras
1230 .try_push(offence_era)
1231 .defensive_proof("Failed to push offence era into empty queue");
1232 *q = Some(eras);
1233 }
1234 });
1235
1236 log!(
1237 debug,
1238 "๐ฆน queued slash for {:?}: {:?} (prior: {:?})",
1239 validator,
1240 slash_fraction,
1241 prior_slash_fraction,
1242 );
1243 } else {
1244 log!(
1245 debug,
1246 "๐ฆน ignored slash for {:?}: {:?} (already slashed in era with prior: {:?})",
1247 validator,
1248 slash_fraction,
1249 prior_slash_fraction,
1250 );
1251 }
1252 }
1253
1254 Self::register_weight(consumed_weight);
1255 }
1256}
1257
1258impl<T: Config> ScoreProvider<T::AccountId> for Pallet<T> {
1259 type Score = VoteWeight;
1260
1261 fn score(who: &T::AccountId) -> Option<Self::Score> {
1262 Self::ledger(Stash(who.clone()))
1263 .map(|l| l.active)
1264 .map(|a| {
1265 let issuance = asset::total_issuance::<T>();
1266 T::CurrencyToVote::to_vote(a, issuance)
1267 })
1268 .ok()
1269 }
1270
1271 #[cfg(feature = "runtime-benchmarks")]
1272 fn set_score_of(who: &T::AccountId, weight: Self::Score) {
1273 let active: BalanceOf<T> = weight.try_into().map_err(|_| ()).unwrap();
1276 let mut ledger = match Self::ledger(StakingAccount::Stash(who.clone())) {
1277 Ok(l) => l,
1278 Err(_) => StakingLedger::default_from(who.clone()),
1279 };
1280 ledger.active = active;
1281
1282 <Ledger<T>>::insert(who, ledger);
1283 <Bonded<T>>::insert(who, who);
1284
1285 let imbalance = asset::burn::<T>(asset::total_issuance::<T>());
1289 core::mem::forget(imbalance);
1292 }
1293}
1294
1295pub struct UseValidatorsMap<T>(core::marker::PhantomData<T>);
1299impl<T: Config> SortedListProvider<T::AccountId> for UseValidatorsMap<T> {
1300 type Score = BalanceOf<T>;
1301 type Error = ();
1302
1303 fn iter() -> Box<dyn Iterator<Item = T::AccountId>> {
1305 Box::new(Validators::<T>::iter().map(|(v, _)| v))
1306 }
1307 fn iter_from(
1308 start: &T::AccountId,
1309 ) -> Result<Box<dyn Iterator<Item = T::AccountId>>, Self::Error> {
1310 if Validators::<T>::contains_key(start) {
1311 let start_key = Validators::<T>::hashed_key_for(start);
1312 Ok(Box::new(Validators::<T>::iter_from(start_key).map(|(n, _)| n)))
1313 } else {
1314 Err(())
1315 }
1316 }
1317 fn lock() {}
1318 fn unlock() {}
1319 fn count() -> u32 {
1320 Validators::<T>::count()
1321 }
1322 fn contains(id: &T::AccountId) -> bool {
1323 Validators::<T>::contains_key(id)
1324 }
1325 fn on_insert(_: T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> {
1326 Ok(())
1328 }
1329 fn get_score(id: &T::AccountId) -> Result<Self::Score, Self::Error> {
1330 Ok(Pallet::<T>::weight_of(id).into())
1331 }
1332 fn on_update(_: &T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> {
1333 Ok(())
1335 }
1336 fn on_remove(_: &T::AccountId) -> Result<(), Self::Error> {
1337 Ok(())
1339 }
1340 fn unsafe_regenerate(
1341 _: impl IntoIterator<Item = T::AccountId>,
1342 _: Box<dyn Fn(&T::AccountId) -> Option<Self::Score>>,
1343 ) -> u32 {
1344 0
1346 }
1347 #[cfg(feature = "try-runtime")]
1348 fn try_state() -> Result<(), TryRuntimeError> {
1349 Ok(())
1350 }
1351
1352 fn unsafe_clear() {
1353 #[allow(deprecated)]
1354 Validators::<T>::remove_all();
1355 }
1356
1357 #[cfg(feature = "runtime-benchmarks")]
1358 fn score_update_worst_case(_who: &T::AccountId, _is_increase: bool) -> Self::Score {
1359 unimplemented!()
1360 }
1361}
1362
1363pub struct UseNominatorsAndValidatorsMap<T>(core::marker::PhantomData<T>);
1367impl<T: Config> SortedListProvider<T::AccountId> for UseNominatorsAndValidatorsMap<T> {
1368 type Error = ();
1369 type Score = VoteWeight;
1370
1371 fn iter() -> Box<dyn Iterator<Item = T::AccountId>> {
1372 Box::new(
1373 Validators::<T>::iter()
1374 .map(|(v, _)| v)
1375 .chain(Nominators::<T>::iter().map(|(n, _)| n)),
1376 )
1377 }
1378 fn iter_from(
1379 start: &T::AccountId,
1380 ) -> Result<Box<dyn Iterator<Item = T::AccountId>>, Self::Error> {
1381 if Validators::<T>::contains_key(start) {
1382 let start_key = Validators::<T>::hashed_key_for(start);
1383 Ok(Box::new(
1384 Validators::<T>::iter_from(start_key)
1385 .map(|(n, _)| n)
1386 .chain(Nominators::<T>::iter().map(|(x, _)| x)),
1387 ))
1388 } else if Nominators::<T>::contains_key(start) {
1389 let start_key = Nominators::<T>::hashed_key_for(start);
1390 Ok(Box::new(Nominators::<T>::iter_from(start_key).map(|(n, _)| n)))
1391 } else {
1392 Err(())
1393 }
1394 }
1395 fn lock() {}
1396 fn unlock() {}
1397 fn count() -> u32 {
1398 Nominators::<T>::count().saturating_add(Validators::<T>::count())
1399 }
1400 fn contains(id: &T::AccountId) -> bool {
1401 Nominators::<T>::contains_key(id) || Validators::<T>::contains_key(id)
1402 }
1403 fn on_insert(_: T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> {
1404 Ok(())
1406 }
1407 fn get_score(id: &T::AccountId) -> Result<Self::Score, Self::Error> {
1408 Ok(Pallet::<T>::weight_of(id))
1409 }
1410 fn on_update(_: &T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> {
1411 Ok(())
1413 }
1414 fn on_remove(_: &T::AccountId) -> Result<(), Self::Error> {
1415 Ok(())
1417 }
1418 fn unsafe_regenerate(
1419 _: impl IntoIterator<Item = T::AccountId>,
1420 _: Box<dyn Fn(&T::AccountId) -> Option<Self::Score>>,
1421 ) -> u32 {
1422 0
1424 }
1425
1426 #[cfg(feature = "try-runtime")]
1427 fn try_state() -> Result<(), TryRuntimeError> {
1428 Ok(())
1429 }
1430
1431 fn unsafe_clear() {
1432 #[allow(deprecated)]
1435 Nominators::<T>::remove_all();
1436 #[allow(deprecated)]
1437 Validators::<T>::remove_all();
1438 }
1439
1440 #[cfg(feature = "runtime-benchmarks")]
1441 fn score_update_worst_case(_who: &T::AccountId, _is_increase: bool) -> Self::Score {
1442 unimplemented!()
1443 }
1444}
1445
1446impl<T: Config> StakingInterface for Pallet<T> {
1447 type AccountId = T::AccountId;
1448 type Balance = BalanceOf<T>;
1449 type CurrencyToVote = T::CurrencyToVote;
1450
1451 fn minimum_nominator_bond() -> Self::Balance {
1452 Self::min_nominator_bond()
1453 }
1454
1455 fn minimum_validator_bond() -> Self::Balance {
1456 Self::min_validator_bond()
1457 }
1458
1459 fn stash_by_ctrl(controller: &Self::AccountId) -> Result<Self::AccountId, DispatchError> {
1460 Self::ledger(Controller(controller.clone()))
1461 .map(|l| l.stash)
1462 .map_err(|e| e.into())
1463 }
1464
1465 fn bonding_duration() -> EraIndex {
1466 T::BondingDuration::get()
1467 }
1468
1469 fn current_era() -> EraIndex {
1470 CurrentEra::<T>::get().unwrap_or(Zero::zero())
1471 }
1472
1473 fn stake(who: &Self::AccountId) -> Result<Stake<BalanceOf<T>>, DispatchError> {
1474 Self::ledger(Stash(who.clone()))
1475 .map(|l| Stake { total: l.total, active: l.active })
1476 .map_err(|e| e.into())
1477 }
1478
1479 fn bond_extra(who: &Self::AccountId, extra: Self::Balance) -> DispatchResult {
1480 Self::bond_extra(RawOrigin::Signed(who.clone()).into(), extra)
1481 }
1482
1483 fn unbond(who: &Self::AccountId, value: Self::Balance) -> DispatchResult {
1484 let ctrl = Self::bonded(who).ok_or(Error::<T>::NotStash)?;
1485 Self::unbond(RawOrigin::Signed(ctrl).into(), value)
1486 .map_err(|with_post| with_post.error)
1487 .map(|_| ())
1488 }
1489
1490 fn set_payee(stash: &Self::AccountId, reward_acc: &Self::AccountId) -> DispatchResult {
1491 ensure!(
1495 !Self::is_virtual_staker(stash) || stash != reward_acc,
1496 Error::<T>::RewardDestinationRestricted
1497 );
1498
1499 let ledger = Self::ledger(Stash(stash.clone()))?;
1500 let _ = ledger
1501 .set_payee(RewardDestination::Account(reward_acc.clone()))
1502 .defensive_proof("ledger was retrieved from storage, thus its bonded; qed.")?;
1503
1504 Ok(())
1505 }
1506
1507 fn chill(who: &Self::AccountId) -> DispatchResult {
1508 let ctrl = Self::bonded(who).ok_or(Error::<T>::NotStash)?;
1511 Self::chill(RawOrigin::Signed(ctrl).into())
1512 }
1513
1514 fn withdraw_unbonded(
1515 who: Self::AccountId,
1516 _num_slashing_spans: u32,
1517 ) -> Result<bool, DispatchError> {
1518 let ctrl = Self::bonded(&who).ok_or(Error::<T>::NotStash)?;
1519 Self::withdraw_unbonded(RawOrigin::Signed(ctrl.clone()).into(), 0)
1520 .map(|_| !StakingLedger::<T>::is_bonded(StakingAccount::Controller(ctrl)))
1521 .map_err(|with_post| with_post.error)
1522 }
1523
1524 fn bond(
1525 who: &Self::AccountId,
1526 value: Self::Balance,
1527 payee: &Self::AccountId,
1528 ) -> DispatchResult {
1529 Self::bond(
1530 RawOrigin::Signed(who.clone()).into(),
1531 value,
1532 RewardDestination::Account(payee.clone()),
1533 )
1534 }
1535
1536 fn nominate(who: &Self::AccountId, targets: Vec<Self::AccountId>) -> DispatchResult {
1537 let ctrl = Self::bonded(who).ok_or(Error::<T>::NotStash)?;
1538 let targets = targets.into_iter().map(T::Lookup::unlookup).collect::<Vec<_>>();
1539 Self::nominate(RawOrigin::Signed(ctrl).into(), targets)
1540 }
1541
1542 fn desired_validator_count() -> u32 {
1543 ValidatorCount::<T>::get()
1544 }
1545
1546 fn election_ongoing() -> bool {
1547 <T::ElectionProvider as ElectionProvider>::status().is_ok()
1548 }
1549
1550 fn force_unstake(who: Self::AccountId) -> sp_runtime::DispatchResult {
1551 Self::force_unstake(RawOrigin::Root.into(), who.clone(), 0)
1552 }
1553
1554 fn is_exposed_in_era(who: &Self::AccountId, era: &EraIndex) -> bool {
1555 ErasStakersPaged::<T>::iter_prefix((era,)).any(|((validator, _), exposure_page)| {
1556 validator == *who || exposure_page.others.iter().any(|i| i.who == *who)
1557 })
1558 }
1559
1560 fn status(
1561 who: &Self::AccountId,
1562 ) -> Result<sp_staking::StakerStatus<Self::AccountId>, DispatchError> {
1563 if !StakingLedger::<T>::is_bonded(StakingAccount::Stash(who.clone())) {
1564 return Err(Error::<T>::NotStash.into())
1565 }
1566
1567 let is_validator = Validators::<T>::contains_key(&who);
1568 let is_nominator = Nominators::<T>::get(&who);
1569
1570 use sp_staking::StakerStatus;
1571 match (is_validator, is_nominator.is_some()) {
1572 (false, false) => Ok(StakerStatus::Idle),
1573 (true, false) => Ok(StakerStatus::Validator),
1574 (false, true) => Ok(StakerStatus::Nominator(
1575 is_nominator.expect("is checked above; qed").targets.into_inner(),
1576 )),
1577 (true, true) => {
1578 defensive!("cannot be both validators and nominator");
1579 Err(Error::<T>::BadState.into())
1580 },
1581 }
1582 }
1583
1584 fn is_virtual_staker(who: &T::AccountId) -> bool {
1589 frame_system::Pallet::<T>::account_nonce(who).is_zero() &&
1590 VirtualStakers::<T>::contains_key(who)
1591 }
1592
1593 fn slash_reward_fraction() -> Perbill {
1594 SlashRewardFraction::<T>::get()
1595 }
1596
1597 sp_staking::runtime_benchmarks_enabled! {
1598 fn nominations(who: &Self::AccountId) -> Option<Vec<T::AccountId>> {
1599 Nominators::<T>::get(who).map(|n| n.targets.into_inner())
1600 }
1601
1602 fn add_era_stakers(
1603 current_era: &EraIndex,
1604 stash: &T::AccountId,
1605 exposures: Vec<(Self::AccountId, Self::Balance)>,
1606 ) {
1607 let others = exposures
1608 .iter()
1609 .map(|(who, value)| crate::IndividualExposure { who: who.clone(), value: *value })
1610 .collect::<Vec<_>>();
1611 let exposure = Exposure { total: Default::default(), own: Default::default(), others };
1612 Eras::<T>::upsert_exposure(*current_era, stash, exposure);
1613 }
1614
1615 fn set_current_era(era: EraIndex) {
1616 CurrentEra::<T>::put(era);
1617 }
1618
1619 fn max_exposure_page_size() -> Page {
1620 T::MaxExposurePageSize::get()
1621 }
1622 }
1623}
1624
1625impl<T: Config> sp_staking::StakingUnchecked for Pallet<T> {
1626 fn migrate_to_virtual_staker(who: &Self::AccountId) -> DispatchResult {
1627 asset::kill_stake::<T>(who)?;
1628 VirtualStakers::<T>::insert(who, ());
1629 Ok(())
1630 }
1631
1632 fn virtual_bond(
1636 keyless_who: &Self::AccountId,
1637 value: Self::Balance,
1638 payee: &Self::AccountId,
1639 ) -> DispatchResult {
1640 if StakingLedger::<T>::is_bonded(StakingAccount::Stash(keyless_who.clone())) {
1641 return Err(Error::<T>::AlreadyBonded.into())
1642 }
1643
1644 ensure!(keyless_who != payee, Error::<T>::RewardDestinationRestricted);
1646
1647 VirtualStakers::<T>::insert(keyless_who, ());
1649
1650 Self::deposit_event(Event::<T>::Bonded { stash: keyless_who.clone(), amount: value });
1651 let ledger = StakingLedger::<T>::new(keyless_who.clone(), value);
1652
1653 ledger.bond(RewardDestination::Account(payee.clone()))?;
1654
1655 Ok(())
1656 }
1657
1658 #[cfg(feature = "runtime-benchmarks")]
1660 fn migrate_to_direct_staker(who: &Self::AccountId) {
1661 assert!(VirtualStakers::<T>::contains_key(who));
1662 let ledger = StakingLedger::<T>::get(Stash(who.clone())).unwrap();
1663 let _ = asset::update_stake::<T>(who, ledger.total)
1664 .expect("funds must be transferred to stash");
1665 VirtualStakers::<T>::remove(who);
1666 }
1667}
1668
1669#[cfg(any(test, feature = "try-runtime"))]
1670impl<T: Config> Pallet<T> {
1671 pub(crate) fn do_try_state(_now: BlockNumberFor<T>) -> Result<(), TryRuntimeError> {
1672 session_rotation::Rotator::<T>::do_try_state()?;
1673 session_rotation::Eras::<T>::do_try_state()?;
1674
1675 use frame_support::traits::fungible::Inspect;
1676 if T::CurrencyToVote::will_downscale(T::Currency::total_issuance()).map_or(false, |x| x) {
1677 log!(warn, "total issuance will cause T::CurrencyToVote to downscale -- report to maintainers.")
1678 }
1679
1680 Self::check_ledgers()?;
1681 Self::check_bonded_consistency()?;
1682 Self::check_payees()?;
1683 Self::check_paged_exposures()?;
1684 Self::check_count()?;
1685
1686 Ok(())
1687 }
1688
1689 fn check_bonded_consistency() -> Result<(), TryRuntimeError> {
1699 use alloc::collections::btree_set::BTreeSet;
1700
1701 let mut count_controller_double = 0;
1702 let mut count_double = 0;
1703 let mut count_none = 0;
1704 let mut controllers = BTreeSet::new();
1707
1708 for (stash, controller) in <Bonded<T>>::iter() {
1709 if !controllers.insert(controller.clone()) {
1710 count_controller_double += 1;
1711 }
1712
1713 match (<Ledger<T>>::get(&stash), <Ledger<T>>::get(&controller)) {
1714 (Some(_), Some(_)) =>
1715 if stash != controller {
1719 count_double += 1;
1720 },
1721 (None, None) => {
1722 count_none += 1;
1723 },
1724 _ => {},
1725 };
1726 }
1727
1728 if count_controller_double != 0 {
1729 log!(
1730 warn,
1731 "a controller is associated with more than one ledger ({} occurrences)",
1732 count_controller_double
1733 );
1734 };
1735
1736 if count_double != 0 {
1737 log!(warn, "single tuple of (stash, controller) pair bonds more than one ledger ({} occurrences)", count_double);
1738 }
1739
1740 if count_none != 0 {
1741 log!(warn, "inconsistent bonded state: (stash, controller) pair missing associated ledger ({} occurrences)", count_none);
1742 }
1743
1744 Ok(())
1745 }
1746
1747 fn check_payees() -> Result<(), TryRuntimeError> {
1752 for (stash, _) in Bonded::<T>::iter() {
1753 ensure!(Payee::<T>::get(&stash).is_some(), "bonded ledger does not have payee set");
1754 }
1755
1756 ensure!(
1757 (Ledger::<T>::iter().count() == Payee::<T>::iter().count()) &&
1758 (Ledger::<T>::iter().count() == Bonded::<T>::iter().count()),
1759 "number of entries in payee storage items does not match the number of bonded ledgers",
1760 );
1761
1762 Ok(())
1763 }
1764
1765 fn check_count() -> Result<(), TryRuntimeError> {
1771 ensure!(
1772 <T as Config>::VoterList::count() ==
1773 Nominators::<T>::count() + Validators::<T>::count(),
1774 "wrong external count"
1775 );
1776 ensure!(
1777 <T as Config>::TargetList::count() == Validators::<T>::count(),
1778 "wrong external count"
1779 );
1780 let max_validators_bound = crate::MaxWinnersOf::<T>::get();
1781 let max_winners_per_page_bound = crate::MaxWinnersPerPageOf::<T::ElectionProvider>::get();
1782 ensure!(
1783 max_validators_bound >= max_winners_per_page_bound,
1784 "max validators should be higher than per page bounds"
1785 );
1786 ensure!(ValidatorCount::<T>::get() <= max_validators_bound, Error::<T>::TooManyValidators);
1787 Ok(())
1788 }
1789
1790 fn check_ledgers() -> Result<(), TryRuntimeError> {
1798 Bonded::<T>::iter()
1799 .map(|(stash, ctrl)| {
1800 if VirtualStakers::<T>::contains_key(stash.clone()) {
1802 ensure!(
1803 asset::staked::<T>(&stash) == Zero::zero(),
1804 "virtual stakers should not have any staked balance"
1805 );
1806 ensure!(
1807 <Bonded<T>>::get(stash.clone()).unwrap() == stash.clone(),
1808 "stash and controller should be same"
1809 );
1810 ensure!(
1811 Ledger::<T>::get(stash.clone()).unwrap().stash == stash,
1812 "ledger corrupted for virtual staker"
1813 );
1814 ensure!(
1815 frame_system::Pallet::<T>::account_nonce(&stash).is_zero(),
1816 "virtual stakers are keyless and should not have any nonce"
1817 );
1818 let reward_destination = <Payee<T>>::get(stash.clone()).unwrap();
1819 if let RewardDestination::Account(payee) = reward_destination {
1820 ensure!(
1821 payee != stash.clone(),
1822 "reward destination should not be same as stash for virtual staker"
1823 );
1824 } else {
1825 return Err(DispatchError::Other(
1826 "reward destination must be of account variant for virtual staker",
1827 ));
1828 }
1829 } else {
1830 let integrity = Self::inspect_bond_state(&stash);
1831 if integrity != Ok(LedgerIntegrityState::Ok) {
1832 log!(
1834 error,
1835 "defensive: bonded stash {:?} has inconsistent ledger state: {:?}",
1836 stash,
1837 integrity
1838 );
1839 }
1840 }
1841
1842 Self::ensure_ledger_consistent(&ctrl)?;
1843 Self::ensure_ledger_role_and_min_bond(&ctrl)?;
1844 Ok(())
1845 })
1846 .collect::<Result<Vec<_>, _>>()?;
1847 Ok(())
1848 }
1849
1850 fn check_paged_exposures() -> Result<(), TryRuntimeError> {
1855 use alloc::collections::btree_map::BTreeMap;
1856 use sp_staking::PagedExposureMetadata;
1857
1858 let mut exposures: BTreeMap<T::AccountId, PagedExposureMetadata<BalanceOf<T>>> =
1860 BTreeMap::new();
1861 let era = ActiveEra::<T>::get().unwrap().index;
1862 let accumulator_default = PagedExposureMetadata {
1863 total: Zero::zero(),
1864 own: Zero::zero(),
1865 nominator_count: 0,
1866 page_count: 0,
1867 };
1868
1869 ErasStakersPaged::<T>::iter_prefix((era,))
1870 .map(|((validator, _page), expo)| {
1871 ensure!(
1872 expo.page_total ==
1873 expo.others.iter().map(|e| e.value).fold(Zero::zero(), |acc, x| acc + x),
1874 "wrong total exposure for the page.",
1875 );
1876
1877 let metadata = exposures.get(&validator).unwrap_or(&accumulator_default);
1878 exposures.insert(
1879 validator,
1880 PagedExposureMetadata {
1881 total: metadata.total + expo.page_total,
1882 own: metadata.own,
1883 nominator_count: metadata.nominator_count + expo.others.len() as u32,
1884 page_count: metadata.page_count + 1,
1885 },
1886 );
1887
1888 Ok(())
1889 })
1890 .collect::<Result<(), TryRuntimeError>>()?;
1891
1892 exposures
1893 .iter()
1894 .map(|(validator, metadata)| {
1895 let actual_overview = ErasStakersOverview::<T>::get(era, validator);
1896
1897 ensure!(actual_overview.is_some(), "No overview found for a paged exposure");
1898 let actual_overview = actual_overview.unwrap();
1899
1900 ensure!(
1901 actual_overview.total == metadata.total + actual_overview.own,
1902 "Exposure metadata does not have correct total exposed stake."
1903 );
1904 ensure!(
1905 actual_overview.nominator_count == metadata.nominator_count,
1906 "Exposure metadata does not have correct count of nominators."
1907 );
1908 ensure!(
1909 actual_overview.page_count == metadata.page_count,
1910 "Exposure metadata does not have correct count of pages."
1911 );
1912
1913 Ok(())
1914 })
1915 .collect::<Result<(), TryRuntimeError>>()
1916 }
1917
1918 fn ensure_ledger_role_and_min_bond(ctrl: &T::AccountId) -> Result<(), TryRuntimeError> {
1919 let ledger = Self::ledger(StakingAccount::Controller(ctrl.clone()))?;
1920 let stash = ledger.stash;
1921
1922 let is_nominator = Nominators::<T>::contains_key(&stash);
1923 let is_validator = Validators::<T>::contains_key(&stash);
1924
1925 match (is_nominator, is_validator) {
1926 (false, false) => {
1927 if ledger.active < Self::min_chilled_bond() {
1928 log!(warn, "Chilled stash {:?} has less than minimum bond", stash);
1929 }
1930 },
1932 (true, false) => {
1933 if ledger.active < Self::min_nominator_bond() {
1935 log!(warn, "Nominator {:?} has less than minimum bond", stash);
1936 }
1937 },
1938 (false, true) => {
1939 if ledger.active < Self::min_validator_bond() {
1941 log!(warn, "Validator {:?} has less than minimum bond", stash);
1942 }
1943 },
1944 (true, true) => {
1945 ensure!(false, "Stash cannot be both nominator and validator");
1946 },
1947 }
1948 Ok(())
1949 }
1950
1951 fn ensure_ledger_consistent(ctrl: &T::AccountId) -> Result<(), TryRuntimeError> {
1952 let ledger = Self::ledger(StakingAccount::Controller(ctrl.clone()))?;
1954
1955 let real_total: BalanceOf<T> =
1956 ledger.unlocking.iter().fold(ledger.active, |a, c| a + c.value);
1957 ensure!(real_total == ledger.total, "ledger.total corrupt");
1958
1959 Ok(())
1960 }
1961}