1#![cfg_attr(not(feature = "std"), no_std)]
50
51mod auto_compound;
52mod delegation_requests;
53pub mod inflation;
54pub mod migrations;
55pub mod traits;
56pub mod types;
57pub mod weights;
58
59#[cfg(any(test, feature = "runtime-benchmarks"))]
60mod benchmarks;
61#[cfg(test)]
62mod mock;
63mod set;
64#[cfg(test)]
65mod tests;
66
67use frame_support::pallet;
68pub use inflation::{InflationInfo, Range};
69pub use weights::WeightInfo;
70
71pub use auto_compound::{AutoCompoundConfig, AutoCompoundDelegations};
72pub use delegation_requests::{CancelledScheduledRequest, DelegationAction, ScheduledRequest};
73pub use pallet::*;
74pub use traits::*;
75pub use types::*;
76pub use RoundIndex;
77
78#[pallet]
79pub mod pallet {
80 use crate::delegation_requests::{
81 CancelledScheduledRequest, DelegationAction, ScheduledRequest,
82 };
83 use crate::{set::BoundedOrderedSet, traits::*, types::*, InflationInfo, Range, WeightInfo};
84 use crate::{AutoCompoundConfig, AutoCompoundDelegations};
85 use frame_support::dispatch::DispatchResultWithPostInfo;
86 use frame_support::pallet_prelude::*;
87 use frame_support::traits::{
88 fungible::{Balanced, Inspect, InspectHold, Mutate, MutateFreeze},
89 Get,
90 };
91 use frame_system::pallet_prelude::*;
92 use sp_consensus_slots::Slot;
93 use sp_runtime::{
94 traits::{Saturating, Zero},
95 DispatchErrorWithPostInfo, Perbill, Percent,
96 };
97 use sp_std::{collections::btree_map::BTreeMap, prelude::*};
98
99 #[pallet::pallet]
101 #[pallet::without_storage_info]
102 pub struct Pallet<T>(PhantomData<T>);
103
104 pub type RoundIndex = u32;
105 type RewardPoint = u32;
106 pub type BalanceOf<T> =
107 <<T as Config>::Currency as Inspect<<T as frame_system::Config>::AccountId>>::Balance;
108
109 pub const MAX_CANDIDATES: u32 = 200;
112
113 #[pallet::config]
115 pub trait Config: frame_system::Config<RuntimeEvent: From<Event<Self>>> {
116 type Currency: Inspect<Self::AccountId>
118 + InspectHold<Self::AccountId>
119 + Mutate<Self::AccountId>
120 + MutateFreeze<Self::AccountId, Id = Self::RuntimeFreezeReason>
121 + Balanced<Self::AccountId>;
122 type RuntimeFreezeReason: From<FreezeReason>;
124 type MonetaryGovernanceOrigin: EnsureOrigin<Self::RuntimeOrigin>;
126 #[pallet::constant]
128 type MinBlocksPerRound: Get<u32>;
129 #[pallet::constant]
132 type MaxOfflineRounds: Get<u32>;
133 #[pallet::constant]
135 type LeaveCandidatesDelay: Get<RoundIndex>;
136 #[pallet::constant]
138 type CandidateBondLessDelay: Get<RoundIndex>;
139 #[pallet::constant]
141 type LeaveDelegatorsDelay: Get<RoundIndex>;
142 #[pallet::constant]
144 type RevokeDelegationDelay: Get<RoundIndex>;
145 #[pallet::constant]
147 type DelegationBondLessDelay: Get<RoundIndex>;
148 #[pallet::constant]
150 type RewardPaymentDelay: Get<RoundIndex>;
151 #[pallet::constant]
153 type MinSelectedCandidates: Get<u32>;
154 #[pallet::constant]
156 type MaxTopDelegationsPerCandidate: Get<u32>;
157 #[pallet::constant]
159 type MaxBottomDelegationsPerCandidate: Get<u32>;
160 #[pallet::constant]
162 type MaxDelegationsPerDelegator: Get<u32>;
163 #[pallet::constant]
165 type MaxScheduledRequestsPerDelegator: Get<u32>;
166 #[pallet::constant]
168 type MinCandidateStk: Get<BalanceOf<Self>>;
169 #[pallet::constant]
171 type MinDelegation: Get<BalanceOf<Self>>;
172 type BlockAuthor: Get<Self::AccountId>;
174 type OnCollatorPayout: OnCollatorPayout<Self::AccountId, BalanceOf<Self>>;
177 type PayoutCollatorReward: PayoutCollatorReward<Self>;
180 type OnInactiveCollator: OnInactiveCollator<Self>;
184 type OnNewRound: OnNewRound;
187 type SlotProvider: Get<Slot>;
189 #[pallet::constant]
191 type SlotDuration: Get<u64>;
192 #[pallet::constant]
194 type BlockTime: Get<u64>;
195 #[pallet::constant]
197 type MaxCandidates: Get<u32>;
198 #[pallet::constant]
201 type LinearInflationThreshold: Get<Option<BalanceOf<Self>>>;
202 type WeightInfo: WeightInfo;
204 }
205
206 #[pallet::composite_enum]
208 pub enum FreezeReason {
209 StakingCollator,
211 StakingDelegator,
213 }
214
215 #[pallet::error]
216 pub enum Error<T> {
217 DelegatorDNE,
218 DelegatorDNEinTopNorBottom,
219 DelegatorDNEInDelegatorSet,
220 CandidateDNE,
221 DelegationDNE,
222 DelegatorExists,
223 CandidateExists,
224 CandidateBondBelowMin,
225 InsufficientBalance,
226 DelegatorBondBelowMin,
227 DelegationBelowMin,
228 AlreadyOffline,
229 AlreadyActive,
230 DelegatorAlreadyLeaving,
231 DelegatorNotLeaving,
232 DelegatorCannotLeaveYet,
233 CannotDelegateIfLeaving,
234 CandidateAlreadyLeaving,
235 CandidateNotLeaving,
236 CandidateCannotLeaveYet,
237 CannotGoOnlineIfLeaving,
238 ExceedMaxDelegationsPerDelegator,
239 AlreadyDelegatedCandidate,
240 InvalidSchedule,
241 CannotSetBelowMin,
242 RoundLengthMustBeGreaterThanTotalSelectedCollators,
243 NoWritingSameValue,
244 TotalInflationDistributionPercentExceeds100,
245 TooLowCandidateCountWeightHintJoinCandidates,
246 TooLowCandidateCountWeightHintCancelLeaveCandidates,
247 TooLowCandidateCountToLeaveCandidates,
248 TooLowDelegationCountToDelegate,
249 TooLowCandidateDelegationCountToDelegate,
250 TooLowCandidateDelegationCountToLeaveCandidates,
251 TooLowDelegationCountToLeaveDelegators,
252 PendingCandidateRequestsDNE,
253 PendingCandidateRequestAlreadyExists,
254 PendingCandidateRequestNotDueYet,
255 PendingDelegationRequestDNE,
256 PendingDelegationRequestAlreadyExists,
257 PendingDelegationRequestNotDueYet,
258 CannotDelegateLessThanOrEqualToLowestBottomWhenFull,
259 PendingDelegationRevoke,
260 TooLowDelegationCountToAutoCompound,
261 TooLowCandidateAutoCompoundingDelegationCountToAutoCompound,
262 TooLowCandidateAutoCompoundingDelegationCountToDelegate,
263 TooLowCollatorCountToNotifyAsInactive,
264 CannotBeNotifiedAsInactive,
265 TooLowCandidateAutoCompoundingDelegationCountToLeaveCandidates,
266 TooLowCandidateCountWeightHint,
267 TooLowCandidateCountWeightHintGoOffline,
268 CandidateLimitReached,
269 CannotSetAboveMaxCandidates,
270 MarkingOfflineNotEnabled,
271 CurrentRoundTooLow,
272 EmptyMigrationBatch,
273 }
274
275 #[pallet::event]
276 #[pallet::generate_deposit(pub(crate) fn deposit_event)]
277 pub enum Event<T: Config> {
278 NewRound {
280 starting_block: BlockNumberFor<T>,
281 round: RoundIndex,
282 selected_collators_number: u32,
283 total_balance: BalanceOf<T>,
284 },
285 JoinedCollatorCandidates {
287 account: T::AccountId,
288 amount_locked: BalanceOf<T>,
289 new_total_amt_locked: BalanceOf<T>,
290 },
291 CollatorChosen {
293 round: RoundIndex,
294 collator_account: T::AccountId,
295 total_exposed_amount: BalanceOf<T>,
296 },
297 CandidateBondLessRequested {
299 candidate: T::AccountId,
300 amount_to_decrease: BalanceOf<T>,
301 execute_round: RoundIndex,
302 },
303 CandidateBondedMore {
305 candidate: T::AccountId,
306 amount: BalanceOf<T>,
307 new_total_bond: BalanceOf<T>,
308 },
309 CandidateBondedLess {
311 candidate: T::AccountId,
312 amount: BalanceOf<T>,
313 new_bond: BalanceOf<T>,
314 },
315 CandidateWentOffline { candidate: T::AccountId },
317 CandidateBackOnline { candidate: T::AccountId },
319 CandidateScheduledExit {
321 exit_allowed_round: RoundIndex,
322 candidate: T::AccountId,
323 scheduled_exit: RoundIndex,
324 },
325 CancelledCandidateExit { candidate: T::AccountId },
327 CancelledCandidateBondLess {
329 candidate: T::AccountId,
330 amount: BalanceOf<T>,
331 execute_round: RoundIndex,
332 },
333 CandidateLeft {
335 ex_candidate: T::AccountId,
336 unlocked_amount: BalanceOf<T>,
337 new_total_amt_locked: BalanceOf<T>,
338 },
339 DelegationDecreaseScheduled {
341 delegator: T::AccountId,
342 candidate: T::AccountId,
343 amount_to_decrease: BalanceOf<T>,
344 execute_round: RoundIndex,
345 },
346 DelegationIncreased {
348 delegator: T::AccountId,
349 candidate: T::AccountId,
350 amount: BalanceOf<T>,
351 in_top: bool,
352 },
353 DelegationDecreased {
355 delegator: T::AccountId,
356 candidate: T::AccountId,
357 amount: BalanceOf<T>,
358 in_top: bool,
359 },
360 DelegatorExitScheduled {
362 round: RoundIndex,
363 delegator: T::AccountId,
364 scheduled_exit: RoundIndex,
365 },
366 DelegationRevocationScheduled {
368 round: RoundIndex,
369 delegator: T::AccountId,
370 candidate: T::AccountId,
371 scheduled_exit: RoundIndex,
372 },
373 DelegatorLeft {
375 delegator: T::AccountId,
376 unstaked_amount: BalanceOf<T>,
377 },
378 DelegationRevoked {
380 delegator: T::AccountId,
381 candidate: T::AccountId,
382 unstaked_amount: BalanceOf<T>,
383 },
384 DelegationKicked {
386 delegator: T::AccountId,
387 candidate: T::AccountId,
388 unstaked_amount: BalanceOf<T>,
389 },
390 DelegatorExitCancelled { delegator: T::AccountId },
392 CancelledDelegationRequest {
394 delegator: T::AccountId,
395 cancelled_request: CancelledScheduledRequest<BalanceOf<T>>,
396 collator: T::AccountId,
397 },
398 Delegation {
400 delegator: T::AccountId,
401 locked_amount: BalanceOf<T>,
402 candidate: T::AccountId,
403 delegator_position: DelegatorAdded<BalanceOf<T>>,
404 auto_compound: Percent,
405 },
406 DelegatorLeftCandidate {
408 delegator: T::AccountId,
409 candidate: T::AccountId,
410 unstaked_amount: BalanceOf<T>,
411 total_candidate_staked: BalanceOf<T>,
412 },
413 Rewarded {
415 account: T::AccountId,
416 rewards: BalanceOf<T>,
417 },
418 InflationDistributed {
420 index: u32,
421 account: T::AccountId,
422 value: BalanceOf<T>,
423 },
424 InflationDistributionConfigUpdated {
425 old: InflationDistributionConfig<T::AccountId>,
426 new: InflationDistributionConfig<T::AccountId>,
427 },
428 InflationSet {
430 annual_min: Perbill,
431 annual_ideal: Perbill,
432 annual_max: Perbill,
433 round_min: Perbill,
434 round_ideal: Perbill,
435 round_max: Perbill,
436 },
437 StakeExpectationsSet {
439 expect_min: BalanceOf<T>,
440 expect_ideal: BalanceOf<T>,
441 expect_max: BalanceOf<T>,
442 },
443 TotalSelectedSet { old: u32, new: u32 },
445 CollatorCommissionSet { old: Perbill, new: Perbill },
447 BlocksPerRoundSet {
449 current_round: RoundIndex,
450 first_block: BlockNumberFor<T>,
451 old: u32,
452 new: u32,
453 new_per_round_inflation_min: Perbill,
454 new_per_round_inflation_ideal: Perbill,
455 new_per_round_inflation_max: Perbill,
456 },
457 AutoCompoundSet {
459 candidate: T::AccountId,
460 delegator: T::AccountId,
461 value: Percent,
462 },
463 Compounded {
465 candidate: T::AccountId,
466 delegator: T::AccountId,
467 amount: BalanceOf<T>,
468 },
469 }
470
471 #[pallet::hooks]
472 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
473 fn on_initialize(n: BlockNumberFor<T>) -> Weight {
474 let mut weight = <T as Config>::WeightInfo::base_on_initialize();
475
476 let mut round = <Round<T>>::get();
477 if round.should_update(n) {
478 let current_slot: u64 = T::SlotProvider::get().into();
480
481 weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 0));
483
484 let round_duration = (current_slot.saturating_sub(round.first_slot))
486 .saturating_mul(T::SlotDuration::get());
487
488 round.update(n, current_slot);
490 weight = weight.saturating_add(T::OnNewRound::on_new_round(round.current));
492 weight =
494 weight.saturating_add(Self::prepare_staking_payouts(round, round_duration));
495 let (extra_weight, collator_count, _delegation_count, total_staked) =
497 Self::select_top_candidates(round.current);
498 weight = weight.saturating_add(extra_weight);
499 <Round<T>>::put(round);
501 Self::deposit_event(Event::NewRound {
502 starting_block: round.first,
503 round: round.current,
504 selected_collators_number: collator_count,
505 total_balance: total_staked,
506 });
507 weight = weight.saturating_add(Self::mark_collators_as_inactive(round.current));
509 weight = weight.saturating_add(T::DbWeight::get().reads_writes(0, 1));
511 } else {
512 weight = weight.saturating_add(Self::handle_delayed_payouts(round.current));
513 }
514
515 weight = weight.saturating_add(T::DbWeight::get().reads_writes(4, 3));
519 weight
520 }
521 fn on_finalize(_n: BlockNumberFor<T>) {
522 Self::award_points_to_block_author();
523 Self::cleanup_inactive_collator_info();
524 }
525 }
526
527 #[pallet::storage]
528 #[pallet::getter(fn collator_commission)]
529 type CollatorCommission<T: Config> = StorageValue<_, Perbill, ValueQuery>;
531
532 #[pallet::storage]
533 #[pallet::getter(fn total_selected)]
534 pub(crate) type TotalSelected<T: Config> = StorageValue<_, u32, ValueQuery>;
536
537 #[pallet::storage]
538 #[pallet::getter(fn inflation_distribution_info)]
539 pub(crate) type InflationDistributionInfo<T: Config> =
544 StorageValue<_, InflationDistributionConfig<T::AccountId>, ValueQuery>;
545
546 #[pallet::storage]
547 #[pallet::getter(fn round)]
548 pub type Round<T: Config> = StorageValue<_, RoundInfo<BlockNumberFor<T>>, ValueQuery>;
550
551 #[pallet::storage]
552 #[pallet::getter(fn delegator_state)]
553 pub(crate) type DelegatorState<T: Config> = StorageMap<
555 _,
556 Twox64Concat,
557 T::AccountId,
558 Delegator<T::AccountId, BalanceOf<T>>,
559 OptionQuery,
560 >;
561
562 #[pallet::storage]
563 #[pallet::getter(fn candidate_info)]
564 pub(crate) type CandidateInfo<T: Config> =
566 StorageMap<_, Twox64Concat, T::AccountId, CandidateMetadata<BalanceOf<T>>, OptionQuery>;
567
568 pub struct AddGet<T, R> {
569 _phantom: PhantomData<(T, R)>,
570 }
571 impl<T, R> Get<u32> for AddGet<T, R>
572 where
573 T: Get<u32>,
574 R: Get<u32>,
575 {
576 fn get() -> u32 {
577 T::get() + R::get()
578 }
579 }
580
581 #[pallet::storage]
587 #[pallet::getter(fn delegation_scheduled_requests)]
588 pub(crate) type DelegationScheduledRequests<T: Config> = StorageDoubleMap<
589 _,
590 Blake2_128Concat,
591 T::AccountId,
592 Blake2_128Concat,
593 T::AccountId,
594 BoundedVec<ScheduledRequest<BalanceOf<T>>, T::MaxScheduledRequestsPerDelegator>,
595 ValueQuery,
596 >;
597
598 #[pallet::storage]
604 pub(crate) type DelegationScheduledRequestsPerCollator<T: Config> =
605 StorageMap<_, Blake2_128Concat, T::AccountId, u32, ValueQuery>;
606
607 #[pallet::storage]
614 pub(crate) type DelegationScheduledRequestsSummaryMap<T: Config> = StorageDoubleMap<
615 _,
616 Blake2_128Concat,
617 T::AccountId, Blake2_128Concat,
619 T::AccountId, DelegationAction<BalanceOf<T>>,
621 OptionQuery,
622 >;
623
624 #[pallet::storage]
626 #[pallet::getter(fn auto_compounding_delegations)]
627 pub(crate) type AutoCompoundingDelegations<T: Config> = StorageMap<
628 _,
629 Blake2_128Concat,
630 T::AccountId,
631 BoundedVec<
632 AutoCompoundConfig<T::AccountId>,
633 AddGet<T::MaxTopDelegationsPerCandidate, T::MaxBottomDelegationsPerCandidate>,
634 >,
635 ValueQuery,
636 >;
637
638 #[pallet::storage]
639 #[pallet::getter(fn top_delegations)]
640 pub(crate) type TopDelegations<T: Config> = StorageMap<
642 _,
643 Twox64Concat,
644 T::AccountId,
645 Delegations<T::AccountId, BalanceOf<T>>,
646 OptionQuery,
647 >;
648
649 #[pallet::storage]
650 #[pallet::getter(fn bottom_delegations)]
651 pub(crate) type BottomDelegations<T: Config> = StorageMap<
653 _,
654 Twox64Concat,
655 T::AccountId,
656 Delegations<T::AccountId, BalanceOf<T>>,
657 OptionQuery,
658 >;
659
660 #[pallet::storage]
661 #[pallet::getter(fn selected_candidates)]
662 type SelectedCandidates<T: Config> =
664 StorageValue<_, BoundedVec<T::AccountId, T::MaxCandidates>, ValueQuery>;
665
666 #[pallet::storage]
667 #[pallet::getter(fn total)]
668 pub(crate) type Total<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
670
671 #[pallet::storage]
672 #[pallet::getter(fn candidate_pool)]
673 pub(crate) type CandidatePool<T: Config> = StorageValue<
675 _,
676 BoundedOrderedSet<Bond<T::AccountId, BalanceOf<T>>, T::MaxCandidates>,
677 ValueQuery,
678 >;
679
680 #[pallet::storage]
681 #[pallet::getter(fn at_stake)]
682 pub type AtStake<T: Config> = StorageDoubleMap<
684 _,
685 Twox64Concat,
686 RoundIndex,
687 Twox64Concat,
688 T::AccountId,
689 CollatorSnapshot<T::AccountId, BalanceOf<T>>,
690 OptionQuery,
691 >;
692
693 #[pallet::storage]
694 #[pallet::getter(fn was_inactive)]
695 pub type WasInactive<T: Config> =
698 StorageDoubleMap<_, Twox64Concat, RoundIndex, Twox64Concat, T::AccountId, (), OptionQuery>;
699
700 #[pallet::storage]
701 #[pallet::getter(fn delayed_payouts)]
702 pub type DelayedPayouts<T: Config> =
704 StorageMap<_, Twox64Concat, RoundIndex, DelayedPayout<BalanceOf<T>>, OptionQuery>;
705
706 #[pallet::storage]
707 #[pallet::getter(fn inflation_config)]
708 pub type InflationConfig<T: Config> = StorageValue<_, InflationInfo<BalanceOf<T>>, ValueQuery>;
710
711 #[pallet::storage]
712 #[pallet::getter(fn points)]
713 pub type Points<T: Config> = StorageMap<_, Twox64Concat, RoundIndex, RewardPoint, ValueQuery>;
715
716 #[pallet::storage]
717 #[pallet::getter(fn awarded_pts)]
718 pub type AwardedPts<T: Config> = StorageDoubleMap<
720 _,
721 Twox64Concat,
722 RoundIndex,
723 Twox64Concat,
724 T::AccountId,
725 RewardPoint,
726 ValueQuery,
727 >;
728
729 #[pallet::storage]
730 #[pallet::getter(fn marking_offline)]
731 pub type EnableMarkingOffline<T: Config> = StorageValue<_, bool, ValueQuery>;
733
734 #[pallet::genesis_config]
735 pub struct GenesisConfig<T: Config> {
736 pub candidates: Vec<(T::AccountId, BalanceOf<T>)>,
738 pub delegations: Vec<(T::AccountId, T::AccountId, BalanceOf<T>, Percent)>,
741 pub inflation_config: InflationInfo<BalanceOf<T>>,
743 pub collator_commission: Perbill,
745 pub parachain_bond_reserve_percent: Percent,
747 pub blocks_per_round: u32,
749 pub num_selected_candidates: u32,
751 }
752
753 impl<T: Config> Default for GenesisConfig<T> {
754 fn default() -> Self {
755 Self {
756 candidates: vec![],
757 delegations: vec![],
758 inflation_config: Default::default(),
759 collator_commission: Default::default(),
760 parachain_bond_reserve_percent: Default::default(),
761 blocks_per_round: 1u32,
762 num_selected_candidates: T::MinSelectedCandidates::get(),
763 }
764 }
765 }
766
767 #[pallet::genesis_build]
768 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
769 fn build(&self) {
770 assert!(self.blocks_per_round > 0, "Blocks per round must be > 0");
771 <InflationConfig<T>>::put(self.inflation_config.clone());
772 let mut candidate_count = 0u32;
773 for &(ref candidate, balance) in &self.candidates {
775 assert!(
776 <Pallet<T>>::get_collator_stakable_free_balance(candidate) >= balance,
777 "Account does not have enough balance to bond as a candidate."
778 );
779 if let Err(error) = <Pallet<T>>::join_candidates(
780 T::RuntimeOrigin::from(Some(candidate.clone()).into()),
781 balance,
782 candidate_count,
783 ) {
784 log::warn!("Join candidates failed in genesis with error {:?}", error);
785 } else {
786 candidate_count = candidate_count.saturating_add(1u32);
787 }
788 }
789
790 let mut col_delegator_count: BTreeMap<T::AccountId, u32> = BTreeMap::new();
791 let mut col_auto_compound_delegator_count: BTreeMap<T::AccountId, u32> =
792 BTreeMap::new();
793 let mut del_delegation_count: BTreeMap<T::AccountId, u32> = BTreeMap::new();
794 for &(ref delegator, ref target, balance, auto_compound) in &self.delegations {
796 assert!(
797 <Pallet<T>>::get_delegator_stakable_balance(delegator) >= balance,
798 "Account does not have enough balance to place delegation."
799 );
800 let cd_count = if let Some(x) = col_delegator_count.get(target) {
801 *x
802 } else {
803 0u32
804 };
805 let dd_count = if let Some(x) = del_delegation_count.get(delegator) {
806 *x
807 } else {
808 0u32
809 };
810 let cd_auto_compound_count = col_auto_compound_delegator_count
811 .get(target)
812 .cloned()
813 .unwrap_or_default();
814 if let Err(error) = <Pallet<T>>::delegate_with_auto_compound(
815 T::RuntimeOrigin::from(Some(delegator.clone()).into()),
816 target.clone(),
817 balance,
818 auto_compound,
819 cd_count,
820 cd_auto_compound_count,
821 dd_count,
822 ) {
823 log::warn!("Delegate failed in genesis with error {:?}", error);
824 } else {
825 if let Some(x) = col_delegator_count.get_mut(target) {
826 *x = x.saturating_add(1u32);
827 } else {
828 col_delegator_count.insert(target.clone(), 1u32);
829 };
830 if let Some(x) = del_delegation_count.get_mut(delegator) {
831 *x = x.saturating_add(1u32);
832 } else {
833 del_delegation_count.insert(delegator.clone(), 1u32);
834 };
835 if !auto_compound.is_zero() {
836 col_auto_compound_delegator_count
837 .entry(target.clone())
838 .and_modify(|x| *x = x.saturating_add(1))
839 .or_insert(1);
840 }
841 }
842 }
843 <CollatorCommission<T>>::put(self.collator_commission);
845 let pbr = InflationDistributionAccount {
847 account: T::AccountId::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes())
849 .expect("infinite length input; no invalid inputs for type; qed"),
850 percent: self.parachain_bond_reserve_percent,
851 };
852 let zeroed_account = InflationDistributionAccount {
853 account: T::AccountId::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes())
854 .expect("infinite length input; no invalid inputs for type; qed"),
855 percent: Percent::zero(),
856 };
857 <InflationDistributionInfo<T>>::put::<InflationDistributionConfig<T::AccountId>>(
858 [pbr, zeroed_account].into(),
859 );
860 assert!(
862 self.num_selected_candidates >= T::MinSelectedCandidates::get(),
863 "{:?}",
864 Error::<T>::CannotSetBelowMin
865 );
866 assert!(
867 self.num_selected_candidates <= T::MaxCandidates::get(),
868 "{:?}",
869 Error::<T>::CannotSetAboveMaxCandidates
870 );
871 <TotalSelected<T>>::put(self.num_selected_candidates);
872 let (_, v_count, _, total_staked) = <Pallet<T>>::select_top_candidates(1u32);
874 let round: RoundInfo<BlockNumberFor<T>> =
876 RoundInfo::new(1u32, Zero::zero(), self.blocks_per_round, 0);
877 <Round<T>>::put(round);
878 <Pallet<T>>::deposit_event(Event::NewRound {
879 starting_block: Zero::zero(),
880 round: 1u32,
881 selected_collators_number: v_count,
882 total_balance: total_staked,
883 });
884 }
885 }
886
887 #[pallet::call]
888 impl<T: Config> Pallet<T> {
889 #[pallet::call_index(0)]
892 #[pallet::weight(<T as Config>::WeightInfo::set_staking_expectations())]
893 pub fn set_staking_expectations(
894 origin: OriginFor<T>,
895 expectations: Range<BalanceOf<T>>,
896 ) -> DispatchResultWithPostInfo {
897 T::MonetaryGovernanceOrigin::ensure_origin(origin)?;
898 ensure!(expectations.is_valid(), Error::<T>::InvalidSchedule);
899 let mut config = <InflationConfig<T>>::get();
900 ensure!(
901 config.expect != expectations,
902 Error::<T>::NoWritingSameValue
903 );
904 config.set_expectations(expectations);
905 Self::deposit_event(Event::StakeExpectationsSet {
906 expect_min: config.expect.min,
907 expect_ideal: config.expect.ideal,
908 expect_max: config.expect.max,
909 });
910 <InflationConfig<T>>::put(config);
911 Ok(().into())
912 }
913
914 #[pallet::call_index(1)]
916 #[pallet::weight(<T as Config>::WeightInfo::set_inflation())]
917 pub fn set_inflation(
918 origin: OriginFor<T>,
919 schedule: Range<Perbill>,
920 ) -> DispatchResultWithPostInfo {
921 T::MonetaryGovernanceOrigin::ensure_origin(origin)?;
922 ensure!(schedule.is_valid(), Error::<T>::InvalidSchedule);
923 let mut config = <InflationConfig<T>>::get();
924 ensure!(config.annual != schedule, Error::<T>::NoWritingSameValue);
925 config.annual = schedule;
926 config.set_round_from_annual::<T>(schedule);
927 Self::deposit_event(Event::InflationSet {
928 annual_min: config.annual.min,
929 annual_ideal: config.annual.ideal,
930 annual_max: config.annual.max,
931 round_min: config.round.min,
932 round_ideal: config.round.ideal,
933 round_max: config.round.max,
934 });
935 <InflationConfig<T>>::put(config);
936 Ok(().into())
937 }
938
939 #[pallet::call_index(4)]
942 #[pallet::weight(<T as Config>::WeightInfo::set_total_selected())]
943 pub fn set_total_selected(origin: OriginFor<T>, new: u32) -> DispatchResultWithPostInfo {
944 frame_system::ensure_root(origin)?;
945 ensure!(
946 new >= T::MinSelectedCandidates::get(),
947 Error::<T>::CannotSetBelowMin
948 );
949 ensure!(
950 new <= T::MaxCandidates::get(),
951 Error::<T>::CannotSetAboveMaxCandidates
952 );
953 let old = <TotalSelected<T>>::get();
954 ensure!(old != new, Error::<T>::NoWritingSameValue);
955 ensure!(
956 new < <Round<T>>::get().length,
957 Error::<T>::RoundLengthMustBeGreaterThanTotalSelectedCollators,
958 );
959 <TotalSelected<T>>::put(new);
960 Self::deposit_event(Event::TotalSelectedSet { old, new });
961 Ok(().into())
962 }
963
964 #[pallet::call_index(5)]
966 #[pallet::weight(<T as Config>::WeightInfo::set_collator_commission())]
967 pub fn set_collator_commission(
968 origin: OriginFor<T>,
969 new: Perbill,
970 ) -> DispatchResultWithPostInfo {
971 frame_system::ensure_root(origin)?;
972 let old = <CollatorCommission<T>>::get();
973 ensure!(old != new, Error::<T>::NoWritingSameValue);
974 <CollatorCommission<T>>::put(new);
975 Self::deposit_event(Event::CollatorCommissionSet { old, new });
976 Ok(().into())
977 }
978
979 #[pallet::call_index(6)]
984 #[pallet::weight(<T as Config>::WeightInfo::set_blocks_per_round())]
985 pub fn set_blocks_per_round(origin: OriginFor<T>, new: u32) -> DispatchResultWithPostInfo {
986 frame_system::ensure_root(origin)?;
987 ensure!(
988 new >= T::MinBlocksPerRound::get(),
989 Error::<T>::CannotSetBelowMin
990 );
991 let mut round = <Round<T>>::get();
992 let (now, first, old) = (round.current, round.first, round.length);
993 ensure!(old != new, Error::<T>::NoWritingSameValue);
994 ensure!(
995 new > <TotalSelected<T>>::get(),
996 Error::<T>::RoundLengthMustBeGreaterThanTotalSelectedCollators,
997 );
998 round.length = new;
999 let mut inflation_config = <InflationConfig<T>>::get();
1001 inflation_config.reset_round::<T>(new);
1002 <Round<T>>::put(round);
1003 Self::deposit_event(Event::BlocksPerRoundSet {
1004 current_round: now,
1005 first_block: first,
1006 old: old,
1007 new: new,
1008 new_per_round_inflation_min: inflation_config.round.min,
1009 new_per_round_inflation_ideal: inflation_config.round.ideal,
1010 new_per_round_inflation_max: inflation_config.round.max,
1011 });
1012 <InflationConfig<T>>::put(inflation_config);
1013 Ok(().into())
1014 }
1015
1016 #[pallet::call_index(7)]
1018 #[pallet::weight(<T as Config>::WeightInfo::join_candidates(*candidate_count))]
1019 pub fn join_candidates(
1020 origin: OriginFor<T>,
1021 bond: BalanceOf<T>,
1022 candidate_count: u32,
1023 ) -> DispatchResultWithPostInfo {
1024 let acc = ensure_signed(origin)?;
1025 ensure!(
1026 bond >= T::MinCandidateStk::get(),
1027 Error::<T>::CandidateBondBelowMin
1028 );
1029 Self::join_candidates_inner(acc, bond, candidate_count)
1030 }
1031
1032 #[pallet::call_index(8)]
1035 #[pallet::weight(<T as Config>::WeightInfo::schedule_leave_candidates(*candidate_count))]
1036 pub fn schedule_leave_candidates(
1037 origin: OriginFor<T>,
1038 candidate_count: u32,
1039 ) -> DispatchResultWithPostInfo {
1040 let collator = ensure_signed(origin)?;
1041 let mut state = <CandidateInfo<T>>::get(&collator).ok_or(Error::<T>::CandidateDNE)?;
1042 let (now, when) = state.schedule_leave::<T>()?;
1043 let mut candidates = <CandidatePool<T>>::get();
1044 ensure!(
1045 candidate_count >= candidates.0.len() as u32,
1046 Error::<T>::TooLowCandidateCountToLeaveCandidates
1047 );
1048 if candidates.remove(&Bond::from_owner(collator.clone())) {
1049 <CandidatePool<T>>::put(candidates);
1050 }
1051 <CandidateInfo<T>>::insert(&collator, state);
1052 Self::deposit_event(Event::CandidateScheduledExit {
1053 exit_allowed_round: now,
1054 candidate: collator,
1055 scheduled_exit: when,
1056 });
1057 Ok(().into())
1058 }
1059
1060 #[pallet::call_index(9)]
1062 #[pallet::weight(
1063 <T as Config>::WeightInfo::execute_leave_candidates_worst_case(*candidate_delegation_count)
1064 )]
1065 pub fn execute_leave_candidates(
1066 origin: OriginFor<T>,
1067 candidate: T::AccountId,
1068 candidate_delegation_count: u32,
1069 ) -> DispatchResultWithPostInfo {
1070 ensure_signed(origin)?;
1071 let state = <CandidateInfo<T>>::get(&candidate).ok_or(Error::<T>::CandidateDNE)?;
1072 ensure!(
1073 state.delegation_count <= candidate_delegation_count,
1074 Error::<T>::TooLowCandidateDelegationCountToLeaveCandidates
1075 );
1076 <Pallet<T>>::execute_leave_candidates_inner(candidate)
1077 }
1078
1079 #[pallet::call_index(10)]
1083 #[pallet::weight(<T as Config>::WeightInfo::cancel_leave_candidates(*candidate_count))]
1084 pub fn cancel_leave_candidates(
1085 origin: OriginFor<T>,
1086 candidate_count: u32,
1087 ) -> DispatchResultWithPostInfo {
1088 let collator = ensure_signed(origin)?;
1089 let mut state = <CandidateInfo<T>>::get(&collator).ok_or(Error::<T>::CandidateDNE)?;
1090 ensure!(state.is_leaving(), Error::<T>::CandidateNotLeaving);
1091 state.go_online();
1092 let mut candidates = <CandidatePool<T>>::get();
1093 ensure!(
1094 candidates.0.len() as u32 <= candidate_count,
1095 Error::<T>::TooLowCandidateCountWeightHintCancelLeaveCandidates
1096 );
1097 let maybe_inserted_candidate = candidates
1098 .try_insert(Bond {
1099 owner: collator.clone(),
1100 amount: state.total_counted,
1101 })
1102 .map_err(|_| Error::<T>::CandidateLimitReached)?;
1103 ensure!(maybe_inserted_candidate, Error::<T>::AlreadyActive);
1104 <CandidatePool<T>>::put(candidates);
1105 <CandidateInfo<T>>::insert(&collator, state);
1106 Self::deposit_event(Event::CancelledCandidateExit {
1107 candidate: collator,
1108 });
1109 Ok(().into())
1110 }
1111
1112 #[pallet::call_index(11)]
1114 #[pallet::weight(<T as Config>::WeightInfo::go_offline(MAX_CANDIDATES))]
1115 pub fn go_offline(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
1116 let collator = ensure_signed(origin)?;
1117 <Pallet<T>>::go_offline_inner(collator)
1118 }
1119
1120 #[pallet::call_index(12)]
1122 #[pallet::weight(<T as Config>::WeightInfo::go_online(MAX_CANDIDATES))]
1123 pub fn go_online(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
1124 let collator = ensure_signed(origin)?;
1125 <Pallet<T>>::go_online_inner(collator)
1126 }
1127
1128 #[pallet::call_index(13)]
1130 #[pallet::weight(<T as Config>::WeightInfo::candidate_bond_more(MAX_CANDIDATES))]
1131 pub fn candidate_bond_more(
1132 origin: OriginFor<T>,
1133 more: BalanceOf<T>,
1134 ) -> DispatchResultWithPostInfo {
1135 let candidate = ensure_signed(origin)?;
1136 <Pallet<T>>::candidate_bond_more_inner(candidate, more)
1137 }
1138
1139 #[pallet::call_index(14)]
1141 #[pallet::weight(<T as Config>::WeightInfo::schedule_candidate_bond_less())]
1142 pub fn schedule_candidate_bond_less(
1143 origin: OriginFor<T>,
1144 less: BalanceOf<T>,
1145 ) -> DispatchResultWithPostInfo {
1146 let collator = ensure_signed(origin)?;
1147 let mut state = <CandidateInfo<T>>::get(&collator).ok_or(Error::<T>::CandidateDNE)?;
1148 let when = state.schedule_bond_less::<T>(less)?;
1149 <CandidateInfo<T>>::insert(&collator, state);
1150 Self::deposit_event(Event::CandidateBondLessRequested {
1151 candidate: collator,
1152 amount_to_decrease: less,
1153 execute_round: when,
1154 });
1155 Ok(().into())
1156 }
1157
1158 #[pallet::call_index(15)]
1160 #[pallet::weight(<T as Config>::WeightInfo::execute_candidate_bond_less(MAX_CANDIDATES))]
1161 pub fn execute_candidate_bond_less(
1162 origin: OriginFor<T>,
1163 candidate: T::AccountId,
1164 ) -> DispatchResultWithPostInfo {
1165 ensure_signed(origin)?; <Pallet<T>>::execute_candidate_bond_less_inner(candidate)
1167 }
1168
1169 #[pallet::call_index(16)]
1171 #[pallet::weight(<T as Config>::WeightInfo::cancel_candidate_bond_less())]
1172 pub fn cancel_candidate_bond_less(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
1173 let collator = ensure_signed(origin)?;
1174 let mut state = <CandidateInfo<T>>::get(&collator).ok_or(Error::<T>::CandidateDNE)?;
1175 state.cancel_bond_less::<T>(collator.clone())?;
1176 <CandidateInfo<T>>::insert(&collator, state);
1177 Ok(().into())
1178 }
1179
1180 #[pallet::call_index(18)]
1184 #[pallet::weight(
1185 <T as Config>::WeightInfo::delegate_with_auto_compound(
1186 *candidate_delegation_count,
1187 *candidate_auto_compounding_delegation_count,
1188 *delegation_count,
1189 )
1190 )]
1191 pub fn delegate_with_auto_compound(
1192 origin: OriginFor<T>,
1193 candidate: T::AccountId,
1194 amount: BalanceOf<T>,
1195 auto_compound: Percent,
1196 candidate_delegation_count: u32,
1197 candidate_auto_compounding_delegation_count: u32,
1198 delegation_count: u32,
1199 ) -> DispatchResultWithPostInfo {
1200 let delegator = ensure_signed(origin)?;
1201 <AutoCompoundDelegations<T>>::delegate_with_auto_compound(
1202 candidate,
1203 delegator,
1204 amount,
1205 auto_compound,
1206 candidate_delegation_count,
1207 candidate_auto_compounding_delegation_count,
1208 delegation_count,
1209 )
1210 }
1211
1212 #[pallet::call_index(22)]
1217 #[pallet::weight(<T as Config>::WeightInfo::schedule_revoke_delegation(
1218 T::MaxTopDelegationsPerCandidate::get() + T::MaxBottomDelegationsPerCandidate::get()
1219 ))]
1220 pub fn schedule_revoke_delegation(
1221 origin: OriginFor<T>,
1222 collator: T::AccountId,
1223 ) -> DispatchResultWithPostInfo {
1224 let delegator = ensure_signed(origin)?;
1225 Self::delegation_schedule_revoke(collator, delegator)
1226 }
1227
1228 #[pallet::call_index(23)]
1230 #[pallet::weight(<T as Config>::WeightInfo::delegator_bond_more(
1231 T::MaxTopDelegationsPerCandidate::get() + T::MaxBottomDelegationsPerCandidate::get()
1232 ))]
1233 pub fn delegator_bond_more(
1234 origin: OriginFor<T>,
1235 candidate: T::AccountId,
1236 more: BalanceOf<T>,
1237 ) -> DispatchResultWithPostInfo {
1238 let delegator = ensure_signed(origin)?;
1239 let (in_top, weight) = Self::delegation_bond_more_without_event(
1240 delegator.clone(),
1241 candidate.clone(),
1242 more.clone(),
1243 )?;
1244 Pallet::<T>::deposit_event(Event::DelegationIncreased {
1245 delegator,
1246 candidate,
1247 amount: more,
1248 in_top,
1249 });
1250
1251 Ok(Some(weight).into())
1252 }
1253
1254 #[pallet::call_index(24)]
1258 #[pallet::weight(<T as Config>::WeightInfo::schedule_delegator_bond_less(
1259 T::MaxTopDelegationsPerCandidate::get() + T::MaxBottomDelegationsPerCandidate::get()
1260 ))]
1261 pub fn schedule_delegator_bond_less(
1262 origin: OriginFor<T>,
1263 candidate: T::AccountId,
1264 less: BalanceOf<T>,
1265 ) -> DispatchResultWithPostInfo {
1266 let delegator = ensure_signed(origin)?;
1267 Self::delegation_schedule_bond_decrease(candidate, delegator, less)
1268 }
1269
1270 #[pallet::call_index(25)]
1272 #[pallet::weight(<T as Config>::WeightInfo::execute_delegator_revoke_delegation_worst())]
1273 pub fn execute_delegation_request(
1274 origin: OriginFor<T>,
1275 delegator: T::AccountId,
1276 candidate: T::AccountId,
1277 ) -> DispatchResultWithPostInfo {
1278 ensure_signed(origin)?; Self::delegation_execute_scheduled_request(candidate, delegator)
1280 }
1281
1282 #[pallet::call_index(26)]
1284 #[pallet::weight(<T as Config>::WeightInfo::cancel_delegation_request(350))]
1285 pub fn cancel_delegation_request(
1286 origin: OriginFor<T>,
1287 candidate: T::AccountId,
1288 ) -> DispatchResultWithPostInfo {
1289 let delegator = ensure_signed(origin)?;
1290 Self::delegation_cancel_request(candidate, delegator)
1291 }
1292
1293 #[pallet::call_index(27)]
1295 #[pallet::weight(<T as Config>::WeightInfo::set_auto_compound(
1296 *candidate_auto_compounding_delegation_count_hint,
1297 *delegation_count_hint,
1298 ))]
1299 pub fn set_auto_compound(
1300 origin: OriginFor<T>,
1301 candidate: T::AccountId,
1302 value: Percent,
1303 candidate_auto_compounding_delegation_count_hint: u32,
1304 delegation_count_hint: u32,
1305 ) -> DispatchResultWithPostInfo {
1306 let delegator = ensure_signed(origin)?;
1307 <AutoCompoundDelegations<T>>::set_auto_compound(
1308 candidate,
1309 delegator,
1310 value,
1311 candidate_auto_compounding_delegation_count_hint,
1312 delegation_count_hint,
1313 )
1314 }
1315
1316 #[pallet::call_index(29)]
1318 #[pallet::weight(<T as Config>::WeightInfo::notify_inactive_collator())]
1319 pub fn notify_inactive_collator(
1320 origin: OriginFor<T>,
1321 collator: T::AccountId,
1322 ) -> DispatchResult {
1323 ensure!(
1324 <EnableMarkingOffline<T>>::get(),
1325 <Error<T>>::MarkingOfflineNotEnabled
1326 );
1327 ensure_signed(origin)?;
1328
1329 let mut collators_len = 0usize;
1330 let max_collators = <TotalSelected<T>>::get();
1331
1332 if let Some(len) = <SelectedCandidates<T>>::decode_len() {
1333 collators_len = len;
1334 };
1335
1336 ensure!(
1340 collators_len * 3 > (max_collators * 2) as usize,
1341 <Error<T>>::TooLowCollatorCountToNotifyAsInactive
1342 );
1343
1344 let round_info = <Round<T>>::get();
1345 let max_offline_rounds = T::MaxOfflineRounds::get();
1346
1347 ensure!(
1348 round_info.current > max_offline_rounds,
1349 <Error<T>>::CurrentRoundTooLow
1350 );
1351
1352 let first_round_to_check = round_info.current.saturating_sub(max_offline_rounds);
1356 let rounds_to_check = first_round_to_check..round_info.current;
1357
1358 let mut inactive_counter: RoundIndex = 0u32;
1361
1362 for r in rounds_to_check {
1364 if <WasInactive<T>>::get(r, &collator).is_some() {
1365 inactive_counter = inactive_counter.saturating_add(1);
1366 }
1367 }
1368
1369 if inactive_counter == max_offline_rounds {
1370 let _ = T::OnInactiveCollator::on_inactive_collator(
1371 collator.clone(),
1372 round_info.current.saturating_sub(1),
1373 );
1374 } else {
1375 return Err(<Error<T>>::CannotBeNotifiedAsInactive.into());
1376 }
1377
1378 Ok(().into())
1379 }
1380
1381 #[pallet::call_index(30)]
1383 #[pallet::weight(
1384 Weight::from_parts(3_000_000u64, 4_000u64)
1385 .saturating_add(T::DbWeight::get().writes(1u64))
1386 )]
1387 pub fn enable_marking_offline(origin: OriginFor<T>, value: bool) -> DispatchResult {
1388 ensure_root(origin)?;
1389 <EnableMarkingOffline<T>>::set(value);
1390 Ok(())
1391 }
1392
1393 #[pallet::call_index(31)]
1396 #[pallet::weight(<T as Config>::WeightInfo::join_candidates(*candidate_count))]
1397 pub fn force_join_candidates(
1398 origin: OriginFor<T>,
1399 account: T::AccountId,
1400 bond: BalanceOf<T>,
1401 candidate_count: u32,
1402 ) -> DispatchResultWithPostInfo {
1403 T::MonetaryGovernanceOrigin::ensure_origin(origin.clone())?;
1404 Self::join_candidates_inner(account, bond, candidate_count)
1405 }
1406
1407 #[pallet::call_index(32)]
1409 #[pallet::weight(<T as Config>::WeightInfo::set_inflation_distribution_config())]
1410 pub fn set_inflation_distribution_config(
1411 origin: OriginFor<T>,
1412 new: InflationDistributionConfig<T::AccountId>,
1413 ) -> DispatchResultWithPostInfo {
1414 T::MonetaryGovernanceOrigin::ensure_origin(origin)?;
1415 let old = <InflationDistributionInfo<T>>::get().0;
1416 let new = new.0;
1417 ensure!(old != new, Error::<T>::NoWritingSameValue);
1418 let total_percent = new.iter().fold(0, |acc, x| acc + x.percent.deconstruct());
1419 ensure!(
1420 total_percent <= 100,
1421 Error::<T>::TotalInflationDistributionPercentExceeds100,
1422 );
1423 <InflationDistributionInfo<T>>::put::<InflationDistributionConfig<T::AccountId>>(
1424 new.clone().into(),
1425 );
1426 Self::deposit_event(Event::InflationDistributionConfigUpdated {
1427 old: old.into(),
1428 new: new.into(),
1429 });
1430 Ok(().into())
1431 }
1432 }
1433
1434 pub(crate) enum RewardPayment {
1436 Paid,
1438 Skipped,
1441 Finished,
1443 }
1444
1445 impl<T: Config> Pallet<T> {
1446 pub(crate) fn freeze_extended(
1450 account: &T::AccountId,
1451 amount: BalanceOf<T>,
1452 is_collator: bool,
1453 ) -> DispatchResult {
1454 use frame_support::traits::fungible::MutateFreeze;
1455
1456 let freeze_reason = if is_collator {
1458 FreezeReason::StakingCollator
1459 } else {
1460 FreezeReason::StakingDelegator
1461 };
1462
1463 T::Currency::set_freeze(&freeze_reason.into(), account, amount)
1464 }
1465
1466 pub(crate) fn thaw_extended(account: &T::AccountId, is_collator: bool) -> DispatchResult {
1468 use frame_support::traits::fungible::MutateFreeze;
1469
1470 let freeze_reason = if is_collator {
1472 FreezeReason::StakingCollator
1473 } else {
1474 FreezeReason::StakingDelegator
1475 };
1476
1477 T::Currency::thaw(&freeze_reason.into(), account)
1478 }
1479
1480 pub(crate) fn balance_frozen_extended(
1484 account: &T::AccountId,
1485 is_collator: bool,
1486 ) -> Option<BalanceOf<T>> {
1487 if is_collator {
1489 <CandidateInfo<T>>::get(account).map(|info| info.bond)
1490 } else {
1491 <DelegatorState<T>>::get(account).map(|state| state.total)
1492 }
1493 }
1494
1495 pub fn is_delegator(acc: &T::AccountId) -> bool {
1496 <DelegatorState<T>>::get(acc).is_some()
1497 }
1498
1499 pub fn is_candidate(acc: &T::AccountId) -> bool {
1500 <CandidateInfo<T>>::get(acc).is_some()
1501 }
1502
1503 pub fn is_selected_candidate(acc: &T::AccountId) -> bool {
1504 <SelectedCandidates<T>>::get().binary_search(acc).is_ok()
1505 }
1506
1507 pub fn join_candidates_inner(
1508 acc: T::AccountId,
1509 bond: BalanceOf<T>,
1510 candidate_count: u32,
1511 ) -> DispatchResultWithPostInfo {
1512 ensure!(!Self::is_candidate(&acc), Error::<T>::CandidateExists);
1513 ensure!(!Self::is_delegator(&acc), Error::<T>::DelegatorExists);
1514 let mut candidates = <CandidatePool<T>>::get();
1515 let old_count = candidates.0.len() as u32;
1516 ensure!(
1517 candidate_count >= old_count,
1518 Error::<T>::TooLowCandidateCountWeightHintJoinCandidates
1519 );
1520 let maybe_inserted_candidate = candidates
1521 .try_insert(Bond {
1522 owner: acc.clone(),
1523 amount: bond,
1524 })
1525 .map_err(|_| Error::<T>::CandidateLimitReached)?;
1526 ensure!(maybe_inserted_candidate, Error::<T>::CandidateExists);
1527
1528 ensure!(
1529 Self::get_collator_stakable_free_balance(&acc) >= bond,
1530 Error::<T>::InsufficientBalance,
1531 );
1532 Self::freeze_extended(&acc, bond, true).map_err(|_| Error::<T>::InsufficientBalance)?;
1533 let candidate = CandidateMetadata::new(bond);
1534 <CandidateInfo<T>>::insert(&acc, candidate);
1535 let empty_delegations: Delegations<T::AccountId, BalanceOf<T>> = Default::default();
1536 <TopDelegations<T>>::insert(&acc, empty_delegations.clone());
1538 <BottomDelegations<T>>::insert(&acc, empty_delegations);
1540 <CandidatePool<T>>::put(candidates);
1541 let new_total = <Total<T>>::get().saturating_add(bond);
1542 <Total<T>>::put(new_total);
1543 Self::deposit_event(Event::JoinedCollatorCandidates {
1544 account: acc,
1545 amount_locked: bond,
1546 new_total_amt_locked: new_total,
1547 });
1548 Ok(().into())
1549 }
1550
1551 pub fn go_offline_inner(collator: T::AccountId) -> DispatchResultWithPostInfo {
1552 let mut state = <CandidateInfo<T>>::get(&collator).ok_or(Error::<T>::CandidateDNE)?;
1553 let mut candidates = <CandidatePool<T>>::get();
1554 let actual_weight = <T as Config>::WeightInfo::go_offline(candidates.0.len() as u32);
1555
1556 ensure!(
1557 state.is_active(),
1558 DispatchErrorWithPostInfo {
1559 post_info: Some(actual_weight).into(),
1560 error: <Error<T>>::AlreadyOffline.into(),
1561 }
1562 );
1563 state.go_offline();
1564
1565 if candidates.remove(&Bond::from_owner(collator.clone())) {
1566 <CandidatePool<T>>::put(candidates);
1567 }
1568 <CandidateInfo<T>>::insert(&collator, state);
1569 Self::deposit_event(Event::CandidateWentOffline {
1570 candidate: collator,
1571 });
1572 Ok(Some(actual_weight).into())
1573 }
1574
1575 pub fn go_online_inner(collator: T::AccountId) -> DispatchResultWithPostInfo {
1576 let mut state = <CandidateInfo<T>>::get(&collator).ok_or(Error::<T>::CandidateDNE)?;
1577 let mut candidates = <CandidatePool<T>>::get();
1578 let actual_weight = <T as Config>::WeightInfo::go_online(candidates.0.len() as u32);
1579
1580 ensure!(
1581 !state.is_active(),
1582 DispatchErrorWithPostInfo {
1583 post_info: Some(actual_weight).into(),
1584 error: <Error<T>>::AlreadyActive.into(),
1585 }
1586 );
1587 ensure!(
1588 !state.is_leaving(),
1589 DispatchErrorWithPostInfo {
1590 post_info: Some(actual_weight).into(),
1591 error: <Error<T>>::CannotGoOnlineIfLeaving.into(),
1592 }
1593 );
1594 state.go_online();
1595
1596 let maybe_inserted_candidate = candidates
1597 .try_insert(Bond {
1598 owner: collator.clone(),
1599 amount: state.total_counted,
1600 })
1601 .map_err(|_| Error::<T>::CandidateLimitReached)?;
1602 ensure!(
1603 maybe_inserted_candidate,
1604 DispatchErrorWithPostInfo {
1605 post_info: Some(actual_weight).into(),
1606 error: <Error<T>>::AlreadyActive.into(),
1607 },
1608 );
1609
1610 <CandidatePool<T>>::put(candidates);
1611 <CandidateInfo<T>>::insert(&collator, state);
1612 Self::deposit_event(Event::CandidateBackOnline {
1613 candidate: collator,
1614 });
1615 Ok(Some(actual_weight).into())
1616 }
1617
1618 pub fn candidate_bond_more_inner(
1619 collator: T::AccountId,
1620 more: BalanceOf<T>,
1621 ) -> DispatchResultWithPostInfo {
1622 let mut state = <CandidateInfo<T>>::get(&collator).ok_or(Error::<T>::CandidateDNE)?;
1623 let actual_weight =
1624 <T as Config>::WeightInfo::candidate_bond_more(T::MaxCandidates::get());
1625
1626 state
1627 .bond_more::<T>(collator.clone(), more)
1628 .map_err(|err| DispatchErrorWithPostInfo {
1629 post_info: Some(actual_weight).into(),
1630 error: err,
1631 })?;
1632 let (is_active, total_counted) = (state.is_active(), state.total_counted);
1633 <CandidateInfo<T>>::insert(&collator, state);
1634 if is_active {
1635 Self::update_active(collator, total_counted);
1636 }
1637 Ok(Some(actual_weight).into())
1638 }
1639
1640 pub fn execute_candidate_bond_less_inner(
1641 candidate: T::AccountId,
1642 ) -> DispatchResultWithPostInfo {
1643 let mut state = <CandidateInfo<T>>::get(&candidate).ok_or(Error::<T>::CandidateDNE)?;
1644 let actual_weight =
1645 <T as Config>::WeightInfo::execute_candidate_bond_less(T::MaxCandidates::get());
1646
1647 state
1648 .execute_bond_less::<T>(candidate.clone())
1649 .map_err(|err| DispatchErrorWithPostInfo {
1650 post_info: Some(actual_weight).into(),
1651 error: err,
1652 })?;
1653 <CandidateInfo<T>>::insert(&candidate, state);
1654 Ok(Some(actual_weight).into())
1655 }
1656
1657 pub fn execute_leave_candidates_inner(
1658 candidate: T::AccountId,
1659 ) -> DispatchResultWithPostInfo {
1660 let state = <CandidateInfo<T>>::get(&candidate).ok_or(Error::<T>::CandidateDNE)?;
1661 let actual_auto_compound_delegation_count =
1662 <AutoCompoundingDelegations<T>>::decode_len(&candidate).unwrap_or_default() as u32;
1663
1664 let actual_delegation_count = state.delegation_count;
1666 let actual_weight = <T as Config>::WeightInfo::execute_leave_candidates_ideal(
1667 actual_delegation_count,
1668 actual_auto_compound_delegation_count,
1669 );
1670
1671 state
1672 .can_leave::<T>()
1673 .map_err(|err| DispatchErrorWithPostInfo {
1674 post_info: Some(actual_weight).into(),
1675 error: err,
1676 })?;
1677 let return_stake = |bond: Bond<T::AccountId, BalanceOf<T>>| -> DispatchResult {
1678 let mut delegator = DelegatorState::<T>::get(&bond.owner).expect(
1680 "Collator state and delegator state are consistent.
1681 Collator state has a record of this delegation. Therefore,
1682 Delegator state also has a record. qed.",
1683 );
1684
1685 if let Some(remaining) = delegator.rm_delegation::<T>(&candidate) {
1686 Self::delegation_remove_request_with_state(
1687 &candidate,
1688 &bond.owner,
1689 &mut delegator,
1690 );
1691 <AutoCompoundDelegations<T>>::remove_auto_compound(&candidate, &bond.owner);
1692
1693 if remaining.is_zero() {
1694 <DelegatorState<T>>::remove(&bond.owner);
1698 Self::thaw_extended(&bond.owner, false)?;
1700 } else {
1701 <DelegatorState<T>>::insert(&bond.owner, delegator);
1702 }
1703 } else {
1704 Self::thaw_extended(&bond.owner, false)?;
1705 }
1706 Ok(())
1707 };
1708 let mut total_backing = state.bond;
1710 let top_delegations =
1712 <TopDelegations<T>>::take(&candidate).expect("CandidateInfo existence checked");
1713 for bond in top_delegations.delegations {
1714 return_stake(bond)?;
1715 }
1716 total_backing = total_backing.saturating_add(top_delegations.total);
1717 let bottom_delegations =
1719 <BottomDelegations<T>>::take(&candidate).expect("CandidateInfo existence checked");
1720 for bond in bottom_delegations.delegations {
1721 return_stake(bond)?;
1722 }
1723 total_backing = total_backing.saturating_add(bottom_delegations.total);
1724 Self::thaw_extended(&candidate, true)?;
1726 <CandidateInfo<T>>::remove(&candidate);
1727 let _ = <DelegationScheduledRequests<T>>::clear_prefix(
1729 &candidate,
1730 Self::max_delegators_per_candidate(),
1731 None,
1732 );
1733 let _ = <DelegationScheduledRequestsSummaryMap<T>>::clear_prefix(
1734 &candidate,
1735 Self::max_delegators_per_candidate(),
1736 None,
1737 );
1738 <DelegationScheduledRequestsPerCollator<T>>::remove(&candidate);
1739 <AutoCompoundingDelegations<T>>::remove(&candidate);
1740 <TopDelegations<T>>::remove(&candidate);
1741 <BottomDelegations<T>>::remove(&candidate);
1742 let new_total_staked = <Total<T>>::get().saturating_sub(total_backing);
1743 <Total<T>>::put(new_total_staked);
1744 Self::deposit_event(Event::CandidateLeft {
1745 ex_candidate: candidate,
1746 unlocked_amount: total_backing,
1747 new_total_amt_locked: new_total_staked,
1748 });
1749 Ok(Some(actual_weight).into())
1750 }
1751
1752 pub fn max_delegators_per_candidate() -> u32 {
1753 AddGet::<T::MaxTopDelegationsPerCandidate, T::MaxBottomDelegationsPerCandidate>::get()
1754 }
1755
1756 pub fn get_delegator_stakable_balance(acc: &T::AccountId) -> BalanceOf<T> {
1758 let total_balance =
1759 T::Currency::balance(acc).saturating_add(T::Currency::total_balance_on_hold(acc));
1760 if let Some(frozen_balance) = Self::balance_frozen_extended(acc, false) {
1761 return total_balance.saturating_sub(frozen_balance);
1762 }
1763 total_balance
1764 }
1765
1766 pub fn get_collator_stakable_free_balance(acc: &T::AccountId) -> BalanceOf<T> {
1768 let total_balance = T::Currency::balance(acc);
1769 if let Some(frozen_balance) = Self::balance_frozen_extended(acc, true) {
1770 return total_balance.saturating_sub(frozen_balance);
1771 }
1772 total_balance
1773 }
1774
1775 pub fn delegation_auto_compound(
1777 candidate: &T::AccountId,
1778 delegator: &T::AccountId,
1779 ) -> Percent {
1780 <AutoCompoundDelegations<T>>::auto_compound(candidate, delegator)
1781 }
1782
1783 pub(crate) fn update_active(candidate: T::AccountId, total: BalanceOf<T>) {
1785 let mut candidates = <CandidatePool<T>>::get();
1786 candidates.remove(&Bond::from_owner(candidate.clone()));
1787 candidates
1788 .try_insert(Bond {
1789 owner: candidate,
1790 amount: total,
1791 })
1792 .expect(
1793 "the candidate is removed in previous step so the length cannot increase; qed",
1794 );
1795 <CandidatePool<T>>::put(candidates);
1796 }
1797
1798 fn compute_issuance(round_duration: u64, round_length: u32) -> BalanceOf<T> {
1800 let ideal_duration: BalanceOf<T> = round_length
1801 .saturating_mul(T::BlockTime::get() as u32)
1802 .into();
1803 let config = <InflationConfig<T>>::get();
1804 let round_issuance = crate::inflation::round_issuance_range::<T>(config.round);
1805
1806 BalanceOf::<T>::from(round_duration as u32).saturating_mul(round_issuance.ideal)
1809 / (ideal_duration)
1810 }
1811
1812 pub(crate) fn delegator_leaves_candidate(
1815 candidate: T::AccountId,
1816 delegator: T::AccountId,
1817 amount: BalanceOf<T>,
1818 ) -> DispatchResult {
1819 let mut state = <CandidateInfo<T>>::get(&candidate).ok_or(Error::<T>::CandidateDNE)?;
1820 state.rm_delegation_if_exists::<T>(&candidate, delegator.clone(), amount)?;
1821 let new_total_locked = <Total<T>>::get().saturating_sub(amount);
1822 <Total<T>>::put(new_total_locked);
1823 let new_total = state.total_counted;
1824 <CandidateInfo<T>>::insert(&candidate, state);
1825 Self::deposit_event(Event::DelegatorLeftCandidate {
1826 delegator: delegator,
1827 candidate: candidate,
1828 unstaked_amount: amount,
1829 total_candidate_staked: new_total,
1830 });
1831 Ok(())
1832 }
1833
1834 pub(crate) fn prepare_staking_payouts(
1835 round_info: RoundInfo<BlockNumberFor<T>>,
1836 round_duration: u64,
1837 ) -> Weight {
1838 let RoundInfo {
1839 current: now,
1840 length: round_length,
1841 ..
1842 } = round_info;
1843
1844 let prepare_payout_for_round = now - 1;
1848
1849 if <Points<T>>::get(prepare_payout_for_round).is_zero() {
1851 return Weight::zero();
1852 }
1853
1854 let total_issuance = Self::compute_issuance(round_duration, round_length);
1856 let mut left_issuance = total_issuance;
1858
1859 let configs = <InflationDistributionInfo<T>>::get().0;
1860 for (index, config) in configs.iter().enumerate() {
1861 if config.percent.is_zero() {
1862 continue;
1863 }
1864 let reserve = config.percent * total_issuance;
1865 if frame_system::Pallet::<T>::account_exists(&config.account) {
1866 if let Ok(minted) = T::Currency::mint_into(&config.account, reserve) {
1867 left_issuance = left_issuance.saturating_sub(minted);
1869 Self::deposit_event(Event::InflationDistributed {
1870 index: index as u32,
1871 account: config.account.clone(),
1872 value: minted,
1873 });
1874 }
1875 }
1876 }
1877
1878 let payout = DelayedPayout {
1879 round_issuance: total_issuance,
1880 total_staking_reward: left_issuance,
1881 collator_commission: <CollatorCommission<T>>::get(),
1882 };
1883
1884 <DelayedPayouts<T>>::insert(prepare_payout_for_round, payout);
1885
1886 <T as Config>::WeightInfo::prepare_staking_payouts()
1887 }
1888
1889 fn handle_delayed_payouts(now: RoundIndex) -> Weight {
1894 let delay = T::RewardPaymentDelay::get();
1895
1896 if now < delay {
1898 return Weight::from_parts(0u64, 0);
1899 }
1900
1901 let paid_for_round = now.saturating_sub(delay);
1902
1903 if let Some(payout_info) = <DelayedPayouts<T>>::get(paid_for_round) {
1904 let result = Self::pay_one_collator_reward(paid_for_round, payout_info);
1905
1906 if matches!(result.0, RewardPayment::Finished) {
1908 <DelayedPayouts<T>>::remove(paid_for_round);
1909 <Points<T>>::remove(paid_for_round);
1910 }
1911 result.1 } else {
1913 Weight::from_parts(0u64, 0)
1914 }
1915 }
1916
1917 pub(crate) fn pay_one_collator_reward(
1922 paid_for_round: RoundIndex,
1923 payout_info: DelayedPayout<BalanceOf<T>>,
1924 ) -> (RewardPayment, Weight) {
1925 let mut early_weight = Weight::zero();
1928
1929 let total_points = <Points<T>>::get(paid_for_round);
1932 early_weight = early_weight.saturating_add(T::DbWeight::get().reads_writes(1, 0));
1933
1934 if total_points.is_zero() {
1935 log::warn!("pay_one_collator_reward called with no <Points<T>> for the round!");
1941 return (RewardPayment::Finished, early_weight);
1942 }
1943
1944 let collator_fee = payout_info.collator_commission;
1945 let collator_issuance = collator_fee * payout_info.round_issuance;
1946 if let Some((collator, state)) =
1947 <AtStake<T>>::iter_prefix(paid_for_round).drain().next()
1948 {
1949 early_weight = early_weight.saturating_add(T::DbWeight::get().reads_writes(1, 1));
1951
1952 let pts = <AwardedPts<T>>::take(paid_for_round, &collator);
1954 early_weight = early_weight.saturating_add(T::DbWeight::get().reads_writes(1, 1));
1956 if pts == 0 {
1957 return (RewardPayment::Skipped, early_weight);
1958 }
1959
1960 let mut extra_weight = Weight::zero();
1963 let pct_due = Perbill::from_rational(pts, total_points);
1964 let total_paid = pct_due * payout_info.total_staking_reward;
1965 let mut amt_due = total_paid;
1966
1967 let num_delegators = state.delegations.len();
1968 let mut num_paid_delegations = 0u32;
1969 let mut num_auto_compounding = 0u32;
1970 if state.delegations.is_empty() {
1971 extra_weight = extra_weight
1973 .saturating_add(T::PayoutCollatorReward::payout_collator_reward(
1974 paid_for_round,
1975 collator.clone(),
1976 amt_due,
1977 ))
1978 .saturating_add(T::OnCollatorPayout::on_collator_payout(
1979 paid_for_round,
1980 collator.clone(),
1981 amt_due,
1982 ));
1983 } else {
1984 let collator_pct = Perbill::from_rational(state.bond, state.total);
1986 let commission = pct_due * collator_issuance;
1987 amt_due = amt_due.saturating_sub(commission);
1988 let collator_reward = (collator_pct * amt_due).saturating_add(commission);
1989 extra_weight = extra_weight
1990 .saturating_add(T::PayoutCollatorReward::payout_collator_reward(
1991 paid_for_round,
1992 collator.clone(),
1993 collator_reward,
1994 ))
1995 .saturating_add(T::OnCollatorPayout::on_collator_payout(
1996 paid_for_round,
1997 collator.clone(),
1998 collator_reward,
1999 ));
2000
2001 for BondWithAutoCompound {
2003 owner,
2004 amount,
2005 auto_compound,
2006 } in state.delegations
2007 {
2008 let percent = Perbill::from_rational(amount, state.total);
2009 let due = percent * amt_due;
2010 if !due.is_zero() {
2011 num_auto_compounding += if auto_compound.is_zero() { 0 } else { 1 };
2012 num_paid_delegations += 1u32;
2013 Self::mint_and_compound(
2014 due,
2015 auto_compound.clone(),
2016 collator.clone(),
2017 owner.clone(),
2018 );
2019 }
2020 }
2021 }
2022
2023 extra_weight = extra_weight.saturating_add(
2024 <T as Config>::WeightInfo::pay_one_collator_reward_best(
2025 num_paid_delegations,
2026 num_auto_compounding,
2027 ),
2028 );
2029
2030 (
2031 RewardPayment::Paid,
2032 <T as Config>::WeightInfo::pay_one_collator_reward(num_delegators as u32)
2033 .saturating_add(extra_weight),
2034 )
2035 } else {
2036 (RewardPayment::Finished, Weight::from_parts(0u64, 0))
2039 }
2040 }
2041
2042 pub fn compute_top_candidates() -> Vec<T::AccountId> {
2047 let top_n = <TotalSelected<T>>::get() as usize;
2048 if top_n == 0 {
2049 return vec![];
2050 }
2051
2052 let candidates = <CandidatePool<T>>::get().0;
2053
2054 if candidates.len() > top_n {
2057 let sorted_candidates = candidates
2060 .try_mutate(|inner| {
2061 inner.select_nth_unstable_by(top_n - 1, |a, b| {
2062 a.amount
2065 .cmp(&b.amount)
2066 .then_with(|| a.owner.cmp(&b.owner))
2067 .reverse()
2068 });
2069 })
2070 .expect("sort cannot increase item count; qed");
2071
2072 let mut collators = sorted_candidates
2073 .into_iter()
2074 .take(top_n)
2075 .map(|x| x.owner)
2076 .collect::<Vec<_>>();
2077
2078 collators.sort();
2080
2081 collators
2082 } else {
2083 candidates.into_iter().map(|x| x.owner).collect::<Vec<_>>()
2086 }
2087 }
2088 pub(crate) fn select_top_candidates(now: RoundIndex) -> (Weight, u32, u32, BalanceOf<T>) {
2091 let (mut collator_count, mut delegation_count, mut total) =
2092 (0u32, 0u32, BalanceOf::<T>::zero());
2093 let collators = Self::compute_top_candidates();
2095 if collators.is_empty() {
2096 let last_round = now.saturating_sub(1u32);
2098 let mut total_per_candidate: BTreeMap<T::AccountId, BalanceOf<T>> = BTreeMap::new();
2099 for (account, snapshot) in <AtStake<T>>::iter_prefix(last_round) {
2101 collator_count = collator_count.saturating_add(1u32);
2102 delegation_count =
2103 delegation_count.saturating_add(snapshot.delegations.len() as u32);
2104 total = total.saturating_add(snapshot.total);
2105 total_per_candidate.insert(account.clone(), snapshot.total);
2106 <AtStake<T>>::insert(now, account, snapshot);
2107 }
2108 for candidate in <SelectedCandidates<T>>::get() {
2111 let snapshot_total = total_per_candidate
2112 .get(&candidate)
2113 .expect("all selected candidates have snapshots");
2114 Self::deposit_event(Event::CollatorChosen {
2115 round: now,
2116 collator_account: candidate,
2117 total_exposed_amount: *snapshot_total,
2118 })
2119 }
2120 let weight = <T as Config>::WeightInfo::select_top_candidates(0, 0);
2121 return (weight, collator_count, delegation_count, total);
2122 }
2123
2124 for account in collators.iter() {
2126 let state = <CandidateInfo<T>>::get(account)
2127 .expect("all members of CandidateQ must be candidates");
2128
2129 collator_count = collator_count.saturating_add(1u32);
2130 delegation_count = delegation_count.saturating_add(state.delegation_count);
2131 total = total.saturating_add(state.total_counted);
2132 let CountedDelegations {
2133 uncounted_stake,
2134 rewardable_delegations,
2135 } = Self::get_rewardable_delegators(&account);
2136 let total_counted = state.total_counted.saturating_sub(uncounted_stake);
2137
2138 let auto_compounding_delegations = <AutoCompoundingDelegations<T>>::get(&account)
2139 .into_iter()
2140 .map(|x| (x.delegator, x.value))
2141 .collect::<BTreeMap<_, _>>();
2142 let rewardable_delegations = rewardable_delegations
2143 .into_iter()
2144 .map(|d| BondWithAutoCompound {
2145 owner: d.owner.clone(),
2146 amount: d.amount,
2147 auto_compound: auto_compounding_delegations
2148 .get(&d.owner)
2149 .cloned()
2150 .unwrap_or_else(|| Percent::zero()),
2151 })
2152 .collect();
2153
2154 let snapshot = CollatorSnapshot {
2155 bond: state.bond,
2156 delegations: rewardable_delegations,
2157 total: total_counted,
2158 };
2159 <AtStake<T>>::insert(now, account, snapshot);
2160 Self::deposit_event(Event::CollatorChosen {
2161 round: now,
2162 collator_account: account.clone(),
2163 total_exposed_amount: state.total_counted,
2164 });
2165 }
2166 <SelectedCandidates<T>>::put(
2168 BoundedVec::try_from(collators)
2169 .expect("subset of collators is always less than or equal to max candidates"),
2170 );
2171
2172 let avg_delegator_count = delegation_count.checked_div(collator_count).unwrap_or(0);
2173 let weight = <T as Config>::WeightInfo::select_top_candidates(
2174 collator_count,
2175 avg_delegator_count,
2176 );
2177 (weight, collator_count, delegation_count, total)
2178 }
2179
2180 pub(crate) fn get_rewardable_delegators(collator: &T::AccountId) -> CountedDelegations<T> {
2188 let mut uncounted_stake = BalanceOf::<T>::zero();
2189 let rewardable_delegations = <TopDelegations<T>>::get(collator)
2190 .expect("all members of CandidateQ must be candidates")
2191 .delegations
2192 .into_iter()
2193 .map(|mut bond| {
2194 match <DelegationScheduledRequestsSummaryMap<T>>::get(collator, &bond.owner) {
2195 Some(DelegationAction::Revoke(_)) => {
2196 uncounted_stake = uncounted_stake.saturating_add(bond.amount);
2197 bond.amount = BalanceOf::<T>::zero();
2198 }
2199 Some(DelegationAction::Decrease(total)) => {
2200 let decrease = total.min(bond.amount);
2201 uncounted_stake = uncounted_stake.saturating_add(decrease);
2202 bond.amount = bond.amount.saturating_sub(decrease);
2203 }
2204 None => {}
2205 }
2206 bond
2207 })
2208 .collect();
2209 CountedDelegations {
2210 uncounted_stake,
2211 rewardable_delegations,
2212 }
2213 }
2214
2215 pub fn delegation_bond_more_without_event(
2221 delegator: T::AccountId,
2222 candidate: T::AccountId,
2223 more: BalanceOf<T>,
2224 ) -> Result<
2225 (bool, Weight),
2226 DispatchErrorWithPostInfo<frame_support::dispatch::PostDispatchInfo>,
2227 > {
2228 let mut state = <DelegatorState<T>>::get(&delegator).ok_or(Error::<T>::DelegatorDNE)?;
2229 ensure!(
2230 !Self::delegation_request_revoke_exists(&candidate, &delegator),
2231 Error::<T>::PendingDelegationRevoke
2232 );
2233
2234 let actual_weight = <T as Config>::WeightInfo::delegator_bond_more(0);
2237 let in_top = state
2238 .increase_delegation::<T>(candidate.clone(), more)
2239 .map_err(|err| DispatchErrorWithPostInfo {
2240 post_info: Some(actual_weight).into(),
2241 error: err,
2242 })?;
2243
2244 Ok((in_top, actual_weight))
2245 }
2246
2247 pub fn mint(amt: BalanceOf<T>, to: &T::AccountId) -> Result<BalanceOf<T>, DispatchError> {
2249 let minted = T::Currency::mint_into(&to, amt)?;
2251 Self::deposit_event(Event::Rewarded {
2252 account: to.clone(),
2253 rewards: minted,
2254 });
2255 Ok(minted)
2256 }
2257
2258 pub fn mint_collator_reward(
2260 _paid_for_round: RoundIndex,
2261 collator_id: T::AccountId,
2262 amt: BalanceOf<T>,
2263 ) -> Weight {
2264 if let Err(e) = Self::mint(amt, &collator_id) {
2266 log::warn!(
2267 "Failed to mint collator reward for {:?}: {:?}",
2268 collator_id,
2269 e
2270 );
2271 }
2272
2273 <T as Config>::WeightInfo::mint_collator_reward()
2274 }
2275
2276 pub fn mint_and_compound(
2281 amt: BalanceOf<T>,
2282 compound_percent: Percent,
2283 candidate: T::AccountId,
2284 delegator: T::AccountId,
2285 ) {
2286 if frame_system::Pallet::<T>::account_exists(&delegator) {
2288 if let Ok(minted) = Self::mint(amt.clone(), &delegator) {
2289 let compound_amount = compound_percent.mul_ceil(minted);
2290 if compound_amount.is_zero() {
2291 return;
2292 }
2293
2294 if let Err(err) = Self::delegation_bond_more_without_event(
2295 delegator.clone(),
2296 candidate.clone(),
2297 compound_amount.clone(),
2298 ) {
2299 log::debug!(
2300 "skipped compounding staking reward towards candidate '{:?}' for delegator '{:?}': {:?}",
2301 candidate,
2302 delegator,
2303 err
2304 );
2305 return;
2306 };
2307
2308 Pallet::<T>::deposit_event(Event::Compounded {
2309 delegator,
2310 candidate,
2311 amount: compound_amount.clone(),
2312 });
2313 };
2314 }
2315 }
2316
2317 fn award_points_to_block_author() {
2320 let author = T::BlockAuthor::get();
2321 let now = <Round<T>>::get().current;
2322 let score_plus_20 = <AwardedPts<T>>::get(now, &author).saturating_add(20);
2323 <AwardedPts<T>>::insert(now, author, score_plus_20);
2324 <Points<T>>::mutate(now, |x| *x = x.saturating_add(20));
2325 }
2326
2327 pub fn mark_collators_as_inactive(cur: RoundIndex) -> Weight {
2329 let prev = cur - 1;
2332
2333 let mut collators_at_stake_count = 0u32;
2334 for (account, _) in <AtStake<T>>::iter_prefix(prev) {
2335 collators_at_stake_count = collators_at_stake_count.saturating_add(1u32);
2336 if <AwardedPts<T>>::get(prev, &account).is_zero() {
2337 <WasInactive<T>>::insert(prev, account, ());
2338 }
2339 }
2340
2341 <T as Config>::WeightInfo::mark_collators_as_inactive(collators_at_stake_count)
2342 }
2343
2344 fn cleanup_inactive_collator_info() {
2347 let now = <Round<T>>::get().current;
2348 let minimum_rounds_required = T::MaxOfflineRounds::get() + 1;
2349
2350 if now < minimum_rounds_required {
2351 return;
2352 }
2353
2354 let _ = <WasInactive<T>>::iter_prefix(now - minimum_rounds_required)
2355 .drain()
2356 .next();
2357 }
2358 }
2359
2360 impl<T: Config> nimbus_primitives::CanAuthor<T::AccountId> for Pallet<T> {
2361 fn can_author(account: &T::AccountId, _slot: &u32) -> bool {
2362 Self::is_selected_candidate(account)
2363 }
2364 }
2365
2366 impl<T: Config> Get<Vec<T::AccountId>> for Pallet<T> {
2367 fn get() -> Vec<T::AccountId> {
2368 Self::selected_candidates().into_inner()
2369 }
2370 }
2371}