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]
609 #[pallet::getter(fn auto_compounding_delegations)]
610 pub(crate) type AutoCompoundingDelegations<T: Config> = StorageMap<
611 _,
612 Blake2_128Concat,
613 T::AccountId,
614 BoundedVec<
615 AutoCompoundConfig<T::AccountId>,
616 AddGet<T::MaxTopDelegationsPerCandidate, T::MaxBottomDelegationsPerCandidate>,
617 >,
618 ValueQuery,
619 >;
620
621 #[pallet::storage]
622 #[pallet::getter(fn top_delegations)]
623 pub(crate) type TopDelegations<T: Config> = StorageMap<
625 _,
626 Twox64Concat,
627 T::AccountId,
628 Delegations<T::AccountId, BalanceOf<T>>,
629 OptionQuery,
630 >;
631
632 #[pallet::storage]
633 #[pallet::getter(fn bottom_delegations)]
634 pub(crate) type BottomDelegations<T: Config> = StorageMap<
636 _,
637 Twox64Concat,
638 T::AccountId,
639 Delegations<T::AccountId, BalanceOf<T>>,
640 OptionQuery,
641 >;
642
643 #[pallet::storage]
644 #[pallet::getter(fn selected_candidates)]
645 type SelectedCandidates<T: Config> =
647 StorageValue<_, BoundedVec<T::AccountId, T::MaxCandidates>, ValueQuery>;
648
649 #[pallet::storage]
650 #[pallet::getter(fn total)]
651 pub(crate) type Total<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
653
654 #[pallet::storage]
655 #[pallet::getter(fn candidate_pool)]
656 pub(crate) type CandidatePool<T: Config> = StorageValue<
658 _,
659 BoundedOrderedSet<Bond<T::AccountId, BalanceOf<T>>, T::MaxCandidates>,
660 ValueQuery,
661 >;
662
663 #[pallet::storage]
664 #[pallet::getter(fn at_stake)]
665 pub type AtStake<T: Config> = StorageDoubleMap<
667 _,
668 Twox64Concat,
669 RoundIndex,
670 Twox64Concat,
671 T::AccountId,
672 CollatorSnapshot<T::AccountId, BalanceOf<T>>,
673 OptionQuery,
674 >;
675
676 #[pallet::storage]
677 #[pallet::getter(fn was_inactive)]
678 pub type WasInactive<T: Config> =
681 StorageDoubleMap<_, Twox64Concat, RoundIndex, Twox64Concat, T::AccountId, (), OptionQuery>;
682
683 #[pallet::storage]
684 #[pallet::getter(fn delayed_payouts)]
685 pub type DelayedPayouts<T: Config> =
687 StorageMap<_, Twox64Concat, RoundIndex, DelayedPayout<BalanceOf<T>>, OptionQuery>;
688
689 #[pallet::storage]
690 #[pallet::getter(fn inflation_config)]
691 pub type InflationConfig<T: Config> = StorageValue<_, InflationInfo<BalanceOf<T>>, ValueQuery>;
693
694 #[pallet::storage]
695 #[pallet::getter(fn points)]
696 pub type Points<T: Config> = StorageMap<_, Twox64Concat, RoundIndex, RewardPoint, ValueQuery>;
698
699 #[pallet::storage]
700 #[pallet::getter(fn awarded_pts)]
701 pub type AwardedPts<T: Config> = StorageDoubleMap<
703 _,
704 Twox64Concat,
705 RoundIndex,
706 Twox64Concat,
707 T::AccountId,
708 RewardPoint,
709 ValueQuery,
710 >;
711
712 #[pallet::storage]
713 #[pallet::getter(fn marking_offline)]
714 pub type EnableMarkingOffline<T: Config> = StorageValue<_, bool, ValueQuery>;
716
717 #[pallet::genesis_config]
718 pub struct GenesisConfig<T: Config> {
719 pub candidates: Vec<(T::AccountId, BalanceOf<T>)>,
721 pub delegations: Vec<(T::AccountId, T::AccountId, BalanceOf<T>, Percent)>,
724 pub inflation_config: InflationInfo<BalanceOf<T>>,
726 pub collator_commission: Perbill,
728 pub parachain_bond_reserve_percent: Percent,
730 pub blocks_per_round: u32,
732 pub num_selected_candidates: u32,
734 }
735
736 impl<T: Config> Default for GenesisConfig<T> {
737 fn default() -> Self {
738 Self {
739 candidates: vec![],
740 delegations: vec![],
741 inflation_config: Default::default(),
742 collator_commission: Default::default(),
743 parachain_bond_reserve_percent: Default::default(),
744 blocks_per_round: 1u32,
745 num_selected_candidates: T::MinSelectedCandidates::get(),
746 }
747 }
748 }
749
750 #[pallet::genesis_build]
751 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
752 fn build(&self) {
753 assert!(self.blocks_per_round > 0, "Blocks per round must be > 0");
754 <InflationConfig<T>>::put(self.inflation_config.clone());
755 let mut candidate_count = 0u32;
756 for &(ref candidate, balance) in &self.candidates {
758 assert!(
759 <Pallet<T>>::get_collator_stakable_free_balance(candidate) >= balance,
760 "Account does not have enough balance to bond as a candidate."
761 );
762 if let Err(error) = <Pallet<T>>::join_candidates(
763 T::RuntimeOrigin::from(Some(candidate.clone()).into()),
764 balance,
765 candidate_count,
766 ) {
767 log::warn!("Join candidates failed in genesis with error {:?}", error);
768 } else {
769 candidate_count = candidate_count.saturating_add(1u32);
770 }
771 }
772
773 let mut col_delegator_count: BTreeMap<T::AccountId, u32> = BTreeMap::new();
774 let mut col_auto_compound_delegator_count: BTreeMap<T::AccountId, u32> =
775 BTreeMap::new();
776 let mut del_delegation_count: BTreeMap<T::AccountId, u32> = BTreeMap::new();
777 for &(ref delegator, ref target, balance, auto_compound) in &self.delegations {
779 assert!(
780 <Pallet<T>>::get_delegator_stakable_balance(delegator) >= balance,
781 "Account does not have enough balance to place delegation."
782 );
783 let cd_count = if let Some(x) = col_delegator_count.get(target) {
784 *x
785 } else {
786 0u32
787 };
788 let dd_count = if let Some(x) = del_delegation_count.get(delegator) {
789 *x
790 } else {
791 0u32
792 };
793 let cd_auto_compound_count = col_auto_compound_delegator_count
794 .get(target)
795 .cloned()
796 .unwrap_or_default();
797 if let Err(error) = <Pallet<T>>::delegate_with_auto_compound(
798 T::RuntimeOrigin::from(Some(delegator.clone()).into()),
799 target.clone(),
800 balance,
801 auto_compound,
802 cd_count,
803 cd_auto_compound_count,
804 dd_count,
805 ) {
806 log::warn!("Delegate failed in genesis with error {:?}", error);
807 } else {
808 if let Some(x) = col_delegator_count.get_mut(target) {
809 *x = x.saturating_add(1u32);
810 } else {
811 col_delegator_count.insert(target.clone(), 1u32);
812 };
813 if let Some(x) = del_delegation_count.get_mut(delegator) {
814 *x = x.saturating_add(1u32);
815 } else {
816 del_delegation_count.insert(delegator.clone(), 1u32);
817 };
818 if !auto_compound.is_zero() {
819 col_auto_compound_delegator_count
820 .entry(target.clone())
821 .and_modify(|x| *x = x.saturating_add(1))
822 .or_insert(1);
823 }
824 }
825 }
826 <CollatorCommission<T>>::put(self.collator_commission);
828 let pbr = InflationDistributionAccount {
830 account: T::AccountId::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes())
832 .expect("infinite length input; no invalid inputs for type; qed"),
833 percent: self.parachain_bond_reserve_percent,
834 };
835 let zeroed_account = InflationDistributionAccount {
836 account: T::AccountId::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes())
837 .expect("infinite length input; no invalid inputs for type; qed"),
838 percent: Percent::zero(),
839 };
840 <InflationDistributionInfo<T>>::put::<InflationDistributionConfig<T::AccountId>>(
841 [pbr, zeroed_account].into(),
842 );
843 assert!(
845 self.num_selected_candidates >= T::MinSelectedCandidates::get(),
846 "{:?}",
847 Error::<T>::CannotSetBelowMin
848 );
849 assert!(
850 self.num_selected_candidates <= T::MaxCandidates::get(),
851 "{:?}",
852 Error::<T>::CannotSetAboveMaxCandidates
853 );
854 <TotalSelected<T>>::put(self.num_selected_candidates);
855 let (_, v_count, _, total_staked) = <Pallet<T>>::select_top_candidates(1u32);
857 let round: RoundInfo<BlockNumberFor<T>> =
859 RoundInfo::new(1u32, Zero::zero(), self.blocks_per_round, 0);
860 <Round<T>>::put(round);
861 <Pallet<T>>::deposit_event(Event::NewRound {
862 starting_block: Zero::zero(),
863 round: 1u32,
864 selected_collators_number: v_count,
865 total_balance: total_staked,
866 });
867 }
868 }
869
870 #[pallet::call]
871 impl<T: Config> Pallet<T> {
872 #[pallet::call_index(0)]
875 #[pallet::weight(<T as Config>::WeightInfo::set_staking_expectations())]
876 pub fn set_staking_expectations(
877 origin: OriginFor<T>,
878 expectations: Range<BalanceOf<T>>,
879 ) -> DispatchResultWithPostInfo {
880 T::MonetaryGovernanceOrigin::ensure_origin(origin)?;
881 ensure!(expectations.is_valid(), Error::<T>::InvalidSchedule);
882 let mut config = <InflationConfig<T>>::get();
883 ensure!(
884 config.expect != expectations,
885 Error::<T>::NoWritingSameValue
886 );
887 config.set_expectations(expectations);
888 Self::deposit_event(Event::StakeExpectationsSet {
889 expect_min: config.expect.min,
890 expect_ideal: config.expect.ideal,
891 expect_max: config.expect.max,
892 });
893 <InflationConfig<T>>::put(config);
894 Ok(().into())
895 }
896
897 #[pallet::call_index(1)]
899 #[pallet::weight(<T as Config>::WeightInfo::set_inflation())]
900 pub fn set_inflation(
901 origin: OriginFor<T>,
902 schedule: Range<Perbill>,
903 ) -> DispatchResultWithPostInfo {
904 T::MonetaryGovernanceOrigin::ensure_origin(origin)?;
905 ensure!(schedule.is_valid(), Error::<T>::InvalidSchedule);
906 let mut config = <InflationConfig<T>>::get();
907 ensure!(config.annual != schedule, Error::<T>::NoWritingSameValue);
908 config.annual = schedule;
909 config.set_round_from_annual::<T>(schedule);
910 Self::deposit_event(Event::InflationSet {
911 annual_min: config.annual.min,
912 annual_ideal: config.annual.ideal,
913 annual_max: config.annual.max,
914 round_min: config.round.min,
915 round_ideal: config.round.ideal,
916 round_max: config.round.max,
917 });
918 <InflationConfig<T>>::put(config);
919 Ok(().into())
920 }
921
922 #[pallet::call_index(4)]
925 #[pallet::weight(<T as Config>::WeightInfo::set_total_selected())]
926 pub fn set_total_selected(origin: OriginFor<T>, new: u32) -> DispatchResultWithPostInfo {
927 frame_system::ensure_root(origin)?;
928 ensure!(
929 new >= T::MinSelectedCandidates::get(),
930 Error::<T>::CannotSetBelowMin
931 );
932 ensure!(
933 new <= T::MaxCandidates::get(),
934 Error::<T>::CannotSetAboveMaxCandidates
935 );
936 let old = <TotalSelected<T>>::get();
937 ensure!(old != new, Error::<T>::NoWritingSameValue);
938 ensure!(
939 new < <Round<T>>::get().length,
940 Error::<T>::RoundLengthMustBeGreaterThanTotalSelectedCollators,
941 );
942 <TotalSelected<T>>::put(new);
943 Self::deposit_event(Event::TotalSelectedSet { old, new });
944 Ok(().into())
945 }
946
947 #[pallet::call_index(5)]
949 #[pallet::weight(<T as Config>::WeightInfo::set_collator_commission())]
950 pub fn set_collator_commission(
951 origin: OriginFor<T>,
952 new: Perbill,
953 ) -> DispatchResultWithPostInfo {
954 frame_system::ensure_root(origin)?;
955 let old = <CollatorCommission<T>>::get();
956 ensure!(old != new, Error::<T>::NoWritingSameValue);
957 <CollatorCommission<T>>::put(new);
958 Self::deposit_event(Event::CollatorCommissionSet { old, new });
959 Ok(().into())
960 }
961
962 #[pallet::call_index(6)]
967 #[pallet::weight(<T as Config>::WeightInfo::set_blocks_per_round())]
968 pub fn set_blocks_per_round(origin: OriginFor<T>, new: u32) -> DispatchResultWithPostInfo {
969 frame_system::ensure_root(origin)?;
970 ensure!(
971 new >= T::MinBlocksPerRound::get(),
972 Error::<T>::CannotSetBelowMin
973 );
974 let mut round = <Round<T>>::get();
975 let (now, first, old) = (round.current, round.first, round.length);
976 ensure!(old != new, Error::<T>::NoWritingSameValue);
977 ensure!(
978 new > <TotalSelected<T>>::get(),
979 Error::<T>::RoundLengthMustBeGreaterThanTotalSelectedCollators,
980 );
981 round.length = new;
982 let mut inflation_config = <InflationConfig<T>>::get();
984 inflation_config.reset_round::<T>(new);
985 <Round<T>>::put(round);
986 Self::deposit_event(Event::BlocksPerRoundSet {
987 current_round: now,
988 first_block: first,
989 old: old,
990 new: new,
991 new_per_round_inflation_min: inflation_config.round.min,
992 new_per_round_inflation_ideal: inflation_config.round.ideal,
993 new_per_round_inflation_max: inflation_config.round.max,
994 });
995 <InflationConfig<T>>::put(inflation_config);
996 Ok(().into())
997 }
998
999 #[pallet::call_index(7)]
1001 #[pallet::weight(<T as Config>::WeightInfo::join_candidates(*candidate_count))]
1002 pub fn join_candidates(
1003 origin: OriginFor<T>,
1004 bond: BalanceOf<T>,
1005 candidate_count: u32,
1006 ) -> DispatchResultWithPostInfo {
1007 let acc = ensure_signed(origin)?;
1008 ensure!(
1009 bond >= T::MinCandidateStk::get(),
1010 Error::<T>::CandidateBondBelowMin
1011 );
1012 Self::join_candidates_inner(acc, bond, candidate_count)
1013 }
1014
1015 #[pallet::call_index(8)]
1018 #[pallet::weight(<T as Config>::WeightInfo::schedule_leave_candidates(*candidate_count))]
1019 pub fn schedule_leave_candidates(
1020 origin: OriginFor<T>,
1021 candidate_count: u32,
1022 ) -> DispatchResultWithPostInfo {
1023 let collator = ensure_signed(origin)?;
1024 let mut state = <CandidateInfo<T>>::get(&collator).ok_or(Error::<T>::CandidateDNE)?;
1025 let (now, when) = state.schedule_leave::<T>()?;
1026 let mut candidates = <CandidatePool<T>>::get();
1027 ensure!(
1028 candidate_count >= candidates.0.len() as u32,
1029 Error::<T>::TooLowCandidateCountToLeaveCandidates
1030 );
1031 if candidates.remove(&Bond::from_owner(collator.clone())) {
1032 <CandidatePool<T>>::put(candidates);
1033 }
1034 <CandidateInfo<T>>::insert(&collator, state);
1035 Self::deposit_event(Event::CandidateScheduledExit {
1036 exit_allowed_round: now,
1037 candidate: collator,
1038 scheduled_exit: when,
1039 });
1040 Ok(().into())
1041 }
1042
1043 #[pallet::call_index(9)]
1045 #[pallet::weight(
1046 <T as Config>::WeightInfo::execute_leave_candidates_worst_case(*candidate_delegation_count)
1047 )]
1048 pub fn execute_leave_candidates(
1049 origin: OriginFor<T>,
1050 candidate: T::AccountId,
1051 candidate_delegation_count: u32,
1052 ) -> DispatchResultWithPostInfo {
1053 ensure_signed(origin)?;
1054 let state = <CandidateInfo<T>>::get(&candidate).ok_or(Error::<T>::CandidateDNE)?;
1055 ensure!(
1056 state.delegation_count <= candidate_delegation_count,
1057 Error::<T>::TooLowCandidateDelegationCountToLeaveCandidates
1058 );
1059 <Pallet<T>>::execute_leave_candidates_inner(candidate)
1060 }
1061
1062 #[pallet::call_index(10)]
1066 #[pallet::weight(<T as Config>::WeightInfo::cancel_leave_candidates(*candidate_count))]
1067 pub fn cancel_leave_candidates(
1068 origin: OriginFor<T>,
1069 candidate_count: u32,
1070 ) -> DispatchResultWithPostInfo {
1071 let collator = ensure_signed(origin)?;
1072 let mut state = <CandidateInfo<T>>::get(&collator).ok_or(Error::<T>::CandidateDNE)?;
1073 ensure!(state.is_leaving(), Error::<T>::CandidateNotLeaving);
1074 state.go_online();
1075 let mut candidates = <CandidatePool<T>>::get();
1076 ensure!(
1077 candidates.0.len() as u32 <= candidate_count,
1078 Error::<T>::TooLowCandidateCountWeightHintCancelLeaveCandidates
1079 );
1080 let maybe_inserted_candidate = candidates
1081 .try_insert(Bond {
1082 owner: collator.clone(),
1083 amount: state.total_counted,
1084 })
1085 .map_err(|_| Error::<T>::CandidateLimitReached)?;
1086 ensure!(maybe_inserted_candidate, Error::<T>::AlreadyActive);
1087 <CandidatePool<T>>::put(candidates);
1088 <CandidateInfo<T>>::insert(&collator, state);
1089 Self::deposit_event(Event::CancelledCandidateExit {
1090 candidate: collator,
1091 });
1092 Ok(().into())
1093 }
1094
1095 #[pallet::call_index(11)]
1097 #[pallet::weight(<T as Config>::WeightInfo::go_offline(MAX_CANDIDATES))]
1098 pub fn go_offline(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
1099 let collator = ensure_signed(origin)?;
1100 <Pallet<T>>::go_offline_inner(collator)
1101 }
1102
1103 #[pallet::call_index(12)]
1105 #[pallet::weight(<T as Config>::WeightInfo::go_online(MAX_CANDIDATES))]
1106 pub fn go_online(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
1107 let collator = ensure_signed(origin)?;
1108 <Pallet<T>>::go_online_inner(collator)
1109 }
1110
1111 #[pallet::call_index(13)]
1113 #[pallet::weight(<T as Config>::WeightInfo::candidate_bond_more(MAX_CANDIDATES))]
1114 pub fn candidate_bond_more(
1115 origin: OriginFor<T>,
1116 more: BalanceOf<T>,
1117 ) -> DispatchResultWithPostInfo {
1118 let candidate = ensure_signed(origin)?;
1119 <Pallet<T>>::candidate_bond_more_inner(candidate, more)
1120 }
1121
1122 #[pallet::call_index(14)]
1124 #[pallet::weight(<T as Config>::WeightInfo::schedule_candidate_bond_less())]
1125 pub fn schedule_candidate_bond_less(
1126 origin: OriginFor<T>,
1127 less: BalanceOf<T>,
1128 ) -> DispatchResultWithPostInfo {
1129 let collator = ensure_signed(origin)?;
1130 let mut state = <CandidateInfo<T>>::get(&collator).ok_or(Error::<T>::CandidateDNE)?;
1131 let when = state.schedule_bond_less::<T>(less)?;
1132 <CandidateInfo<T>>::insert(&collator, state);
1133 Self::deposit_event(Event::CandidateBondLessRequested {
1134 candidate: collator,
1135 amount_to_decrease: less,
1136 execute_round: when,
1137 });
1138 Ok(().into())
1139 }
1140
1141 #[pallet::call_index(15)]
1143 #[pallet::weight(<T as Config>::WeightInfo::execute_candidate_bond_less(MAX_CANDIDATES))]
1144 pub fn execute_candidate_bond_less(
1145 origin: OriginFor<T>,
1146 candidate: T::AccountId,
1147 ) -> DispatchResultWithPostInfo {
1148 ensure_signed(origin)?; <Pallet<T>>::execute_candidate_bond_less_inner(candidate)
1150 }
1151
1152 #[pallet::call_index(16)]
1154 #[pallet::weight(<T as Config>::WeightInfo::cancel_candidate_bond_less())]
1155 pub fn cancel_candidate_bond_less(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
1156 let collator = ensure_signed(origin)?;
1157 let mut state = <CandidateInfo<T>>::get(&collator).ok_or(Error::<T>::CandidateDNE)?;
1158 state.cancel_bond_less::<T>(collator.clone())?;
1159 <CandidateInfo<T>>::insert(&collator, state);
1160 Ok(().into())
1161 }
1162
1163 #[pallet::call_index(18)]
1167 #[pallet::weight(
1168 <T as Config>::WeightInfo::delegate_with_auto_compound(
1169 *candidate_delegation_count,
1170 *candidate_auto_compounding_delegation_count,
1171 *delegation_count,
1172 )
1173 )]
1174 pub fn delegate_with_auto_compound(
1175 origin: OriginFor<T>,
1176 candidate: T::AccountId,
1177 amount: BalanceOf<T>,
1178 auto_compound: Percent,
1179 candidate_delegation_count: u32,
1180 candidate_auto_compounding_delegation_count: u32,
1181 delegation_count: u32,
1182 ) -> DispatchResultWithPostInfo {
1183 let delegator = ensure_signed(origin)?;
1184 <AutoCompoundDelegations<T>>::delegate_with_auto_compound(
1185 candidate,
1186 delegator,
1187 amount,
1188 auto_compound,
1189 candidate_delegation_count,
1190 candidate_auto_compounding_delegation_count,
1191 delegation_count,
1192 )
1193 }
1194
1195 #[pallet::call_index(22)]
1200 #[pallet::weight(<T as Config>::WeightInfo::schedule_revoke_delegation(
1201 T::MaxTopDelegationsPerCandidate::get() + T::MaxBottomDelegationsPerCandidate::get()
1202 ))]
1203 pub fn schedule_revoke_delegation(
1204 origin: OriginFor<T>,
1205 collator: T::AccountId,
1206 ) -> DispatchResultWithPostInfo {
1207 let delegator = ensure_signed(origin)?;
1208 Self::delegation_schedule_revoke(collator, delegator)
1209 }
1210
1211 #[pallet::call_index(23)]
1213 #[pallet::weight(<T as Config>::WeightInfo::delegator_bond_more(
1214 T::MaxTopDelegationsPerCandidate::get() + T::MaxBottomDelegationsPerCandidate::get()
1215 ))]
1216 pub fn delegator_bond_more(
1217 origin: OriginFor<T>,
1218 candidate: T::AccountId,
1219 more: BalanceOf<T>,
1220 ) -> DispatchResultWithPostInfo {
1221 let delegator = ensure_signed(origin)?;
1222 let (in_top, weight) = Self::delegation_bond_more_without_event(
1223 delegator.clone(),
1224 candidate.clone(),
1225 more.clone(),
1226 )?;
1227 Pallet::<T>::deposit_event(Event::DelegationIncreased {
1228 delegator,
1229 candidate,
1230 amount: more,
1231 in_top,
1232 });
1233
1234 Ok(Some(weight).into())
1235 }
1236
1237 #[pallet::call_index(24)]
1241 #[pallet::weight(<T as Config>::WeightInfo::schedule_delegator_bond_less(
1242 T::MaxTopDelegationsPerCandidate::get() + T::MaxBottomDelegationsPerCandidate::get()
1243 ))]
1244 pub fn schedule_delegator_bond_less(
1245 origin: OriginFor<T>,
1246 candidate: T::AccountId,
1247 less: BalanceOf<T>,
1248 ) -> DispatchResultWithPostInfo {
1249 let delegator = ensure_signed(origin)?;
1250 Self::delegation_schedule_bond_decrease(candidate, delegator, less)
1251 }
1252
1253 #[pallet::call_index(25)]
1255 #[pallet::weight(<T as Config>::WeightInfo::execute_delegator_revoke_delegation_worst())]
1256 pub fn execute_delegation_request(
1257 origin: OriginFor<T>,
1258 delegator: T::AccountId,
1259 candidate: T::AccountId,
1260 ) -> DispatchResultWithPostInfo {
1261 ensure_signed(origin)?; Self::delegation_execute_scheduled_request(candidate, delegator)
1263 }
1264
1265 #[pallet::call_index(26)]
1267 #[pallet::weight(<T as Config>::WeightInfo::cancel_delegation_request(350))]
1268 pub fn cancel_delegation_request(
1269 origin: OriginFor<T>,
1270 candidate: T::AccountId,
1271 ) -> DispatchResultWithPostInfo {
1272 let delegator = ensure_signed(origin)?;
1273 Self::delegation_cancel_request(candidate, delegator)
1274 }
1275
1276 #[pallet::call_index(27)]
1278 #[pallet::weight(<T as Config>::WeightInfo::set_auto_compound(
1279 *candidate_auto_compounding_delegation_count_hint,
1280 *delegation_count_hint,
1281 ))]
1282 pub fn set_auto_compound(
1283 origin: OriginFor<T>,
1284 candidate: T::AccountId,
1285 value: Percent,
1286 candidate_auto_compounding_delegation_count_hint: u32,
1287 delegation_count_hint: u32,
1288 ) -> DispatchResultWithPostInfo {
1289 let delegator = ensure_signed(origin)?;
1290 <AutoCompoundDelegations<T>>::set_auto_compound(
1291 candidate,
1292 delegator,
1293 value,
1294 candidate_auto_compounding_delegation_count_hint,
1295 delegation_count_hint,
1296 )
1297 }
1298
1299 #[pallet::call_index(29)]
1301 #[pallet::weight(<T as Config>::WeightInfo::notify_inactive_collator())]
1302 pub fn notify_inactive_collator(
1303 origin: OriginFor<T>,
1304 collator: T::AccountId,
1305 ) -> DispatchResult {
1306 ensure!(
1307 <EnableMarkingOffline<T>>::get(),
1308 <Error<T>>::MarkingOfflineNotEnabled
1309 );
1310 ensure_signed(origin)?;
1311
1312 let mut collators_len = 0usize;
1313 let max_collators = <TotalSelected<T>>::get();
1314
1315 if let Some(len) = <SelectedCandidates<T>>::decode_len() {
1316 collators_len = len;
1317 };
1318
1319 ensure!(
1323 collators_len * 3 > (max_collators * 2) as usize,
1324 <Error<T>>::TooLowCollatorCountToNotifyAsInactive
1325 );
1326
1327 let round_info = <Round<T>>::get();
1328 let max_offline_rounds = T::MaxOfflineRounds::get();
1329
1330 ensure!(
1331 round_info.current > max_offline_rounds,
1332 <Error<T>>::CurrentRoundTooLow
1333 );
1334
1335 let first_round_to_check = round_info.current.saturating_sub(max_offline_rounds);
1339 let rounds_to_check = first_round_to_check..round_info.current;
1340
1341 let mut inactive_counter: RoundIndex = 0u32;
1344
1345 for r in rounds_to_check {
1347 if <WasInactive<T>>::get(r, &collator).is_some() {
1348 inactive_counter = inactive_counter.saturating_add(1);
1349 }
1350 }
1351
1352 if inactive_counter == max_offline_rounds {
1353 let _ = T::OnInactiveCollator::on_inactive_collator(
1354 collator.clone(),
1355 round_info.current.saturating_sub(1),
1356 );
1357 } else {
1358 return Err(<Error<T>>::CannotBeNotifiedAsInactive.into());
1359 }
1360
1361 Ok(().into())
1362 }
1363
1364 #[pallet::call_index(30)]
1366 #[pallet::weight(
1367 Weight::from_parts(3_000_000u64, 4_000u64)
1368 .saturating_add(T::DbWeight::get().writes(1u64))
1369 )]
1370 pub fn enable_marking_offline(origin: OriginFor<T>, value: bool) -> DispatchResult {
1371 ensure_root(origin)?;
1372 <EnableMarkingOffline<T>>::set(value);
1373 Ok(())
1374 }
1375
1376 #[pallet::call_index(31)]
1379 #[pallet::weight(<T as Config>::WeightInfo::join_candidates(*candidate_count))]
1380 pub fn force_join_candidates(
1381 origin: OriginFor<T>,
1382 account: T::AccountId,
1383 bond: BalanceOf<T>,
1384 candidate_count: u32,
1385 ) -> DispatchResultWithPostInfo {
1386 T::MonetaryGovernanceOrigin::ensure_origin(origin.clone())?;
1387 Self::join_candidates_inner(account, bond, candidate_count)
1388 }
1389
1390 #[pallet::call_index(32)]
1392 #[pallet::weight(<T as Config>::WeightInfo::set_inflation_distribution_config())]
1393 pub fn set_inflation_distribution_config(
1394 origin: OriginFor<T>,
1395 new: InflationDistributionConfig<T::AccountId>,
1396 ) -> DispatchResultWithPostInfo {
1397 T::MonetaryGovernanceOrigin::ensure_origin(origin)?;
1398 let old = <InflationDistributionInfo<T>>::get().0;
1399 let new = new.0;
1400 ensure!(old != new, Error::<T>::NoWritingSameValue);
1401 let total_percent = new.iter().fold(0, |acc, x| acc + x.percent.deconstruct());
1402 ensure!(
1403 total_percent <= 100,
1404 Error::<T>::TotalInflationDistributionPercentExceeds100,
1405 );
1406 <InflationDistributionInfo<T>>::put::<InflationDistributionConfig<T::AccountId>>(
1407 new.clone().into(),
1408 );
1409 Self::deposit_event(Event::InflationDistributionConfigUpdated {
1410 old: old.into(),
1411 new: new.into(),
1412 });
1413 Ok(().into())
1414 }
1415 }
1416
1417 pub(crate) enum RewardPayment {
1419 Paid,
1421 Skipped,
1424 Finished,
1426 }
1427
1428 impl<T: Config> Pallet<T> {
1429 pub(crate) fn freeze_extended(
1433 account: &T::AccountId,
1434 amount: BalanceOf<T>,
1435 is_collator: bool,
1436 ) -> DispatchResult {
1437 use frame_support::traits::fungible::MutateFreeze;
1438
1439 let freeze_reason = if is_collator {
1441 FreezeReason::StakingCollator
1442 } else {
1443 FreezeReason::StakingDelegator
1444 };
1445
1446 T::Currency::set_freeze(&freeze_reason.into(), account, amount)
1447 }
1448
1449 pub(crate) fn thaw_extended(account: &T::AccountId, is_collator: bool) -> DispatchResult {
1451 use frame_support::traits::fungible::MutateFreeze;
1452
1453 let freeze_reason = if is_collator {
1455 FreezeReason::StakingCollator
1456 } else {
1457 FreezeReason::StakingDelegator
1458 };
1459
1460 T::Currency::thaw(&freeze_reason.into(), account)
1461 }
1462
1463 pub(crate) fn balance_frozen_extended(
1467 account: &T::AccountId,
1468 is_collator: bool,
1469 ) -> Option<BalanceOf<T>> {
1470 if is_collator {
1472 <CandidateInfo<T>>::get(account).map(|info| info.bond)
1473 } else {
1474 <DelegatorState<T>>::get(account).map(|state| state.total)
1475 }
1476 }
1477
1478 pub fn is_delegator(acc: &T::AccountId) -> bool {
1479 <DelegatorState<T>>::get(acc).is_some()
1480 }
1481
1482 pub fn is_candidate(acc: &T::AccountId) -> bool {
1483 <CandidateInfo<T>>::get(acc).is_some()
1484 }
1485
1486 pub fn is_selected_candidate(acc: &T::AccountId) -> bool {
1487 <SelectedCandidates<T>>::get().binary_search(acc).is_ok()
1488 }
1489
1490 pub fn join_candidates_inner(
1491 acc: T::AccountId,
1492 bond: BalanceOf<T>,
1493 candidate_count: u32,
1494 ) -> DispatchResultWithPostInfo {
1495 ensure!(!Self::is_candidate(&acc), Error::<T>::CandidateExists);
1496 ensure!(!Self::is_delegator(&acc), Error::<T>::DelegatorExists);
1497 let mut candidates = <CandidatePool<T>>::get();
1498 let old_count = candidates.0.len() as u32;
1499 ensure!(
1500 candidate_count >= old_count,
1501 Error::<T>::TooLowCandidateCountWeightHintJoinCandidates
1502 );
1503 let maybe_inserted_candidate = candidates
1504 .try_insert(Bond {
1505 owner: acc.clone(),
1506 amount: bond,
1507 })
1508 .map_err(|_| Error::<T>::CandidateLimitReached)?;
1509 ensure!(maybe_inserted_candidate, Error::<T>::CandidateExists);
1510
1511 ensure!(
1512 Self::get_collator_stakable_free_balance(&acc) >= bond,
1513 Error::<T>::InsufficientBalance,
1514 );
1515 Self::freeze_extended(&acc, bond, true).map_err(|_| Error::<T>::InsufficientBalance)?;
1516 let candidate = CandidateMetadata::new(bond);
1517 <CandidateInfo<T>>::insert(&acc, candidate);
1518 let empty_delegations: Delegations<T::AccountId, BalanceOf<T>> = Default::default();
1519 <TopDelegations<T>>::insert(&acc, empty_delegations.clone());
1521 <BottomDelegations<T>>::insert(&acc, empty_delegations);
1523 <CandidatePool<T>>::put(candidates);
1524 let new_total = <Total<T>>::get().saturating_add(bond);
1525 <Total<T>>::put(new_total);
1526 Self::deposit_event(Event::JoinedCollatorCandidates {
1527 account: acc,
1528 amount_locked: bond,
1529 new_total_amt_locked: new_total,
1530 });
1531 Ok(().into())
1532 }
1533
1534 pub fn go_offline_inner(collator: T::AccountId) -> DispatchResultWithPostInfo {
1535 let mut state = <CandidateInfo<T>>::get(&collator).ok_or(Error::<T>::CandidateDNE)?;
1536 let mut candidates = <CandidatePool<T>>::get();
1537 let actual_weight = <T as Config>::WeightInfo::go_offline(candidates.0.len() as u32);
1538
1539 ensure!(
1540 state.is_active(),
1541 DispatchErrorWithPostInfo {
1542 post_info: Some(actual_weight).into(),
1543 error: <Error<T>>::AlreadyOffline.into(),
1544 }
1545 );
1546 state.go_offline();
1547
1548 if candidates.remove(&Bond::from_owner(collator.clone())) {
1549 <CandidatePool<T>>::put(candidates);
1550 }
1551 <CandidateInfo<T>>::insert(&collator, state);
1552 Self::deposit_event(Event::CandidateWentOffline {
1553 candidate: collator,
1554 });
1555 Ok(Some(actual_weight).into())
1556 }
1557
1558 pub fn go_online_inner(collator: T::AccountId) -> DispatchResultWithPostInfo {
1559 let mut state = <CandidateInfo<T>>::get(&collator).ok_or(Error::<T>::CandidateDNE)?;
1560 let mut candidates = <CandidatePool<T>>::get();
1561 let actual_weight = <T as Config>::WeightInfo::go_online(candidates.0.len() as u32);
1562
1563 ensure!(
1564 !state.is_active(),
1565 DispatchErrorWithPostInfo {
1566 post_info: Some(actual_weight).into(),
1567 error: <Error<T>>::AlreadyActive.into(),
1568 }
1569 );
1570 ensure!(
1571 !state.is_leaving(),
1572 DispatchErrorWithPostInfo {
1573 post_info: Some(actual_weight).into(),
1574 error: <Error<T>>::CannotGoOnlineIfLeaving.into(),
1575 }
1576 );
1577 state.go_online();
1578
1579 let maybe_inserted_candidate = candidates
1580 .try_insert(Bond {
1581 owner: collator.clone(),
1582 amount: state.total_counted,
1583 })
1584 .map_err(|_| Error::<T>::CandidateLimitReached)?;
1585 ensure!(
1586 maybe_inserted_candidate,
1587 DispatchErrorWithPostInfo {
1588 post_info: Some(actual_weight).into(),
1589 error: <Error<T>>::AlreadyActive.into(),
1590 },
1591 );
1592
1593 <CandidatePool<T>>::put(candidates);
1594 <CandidateInfo<T>>::insert(&collator, state);
1595 Self::deposit_event(Event::CandidateBackOnline {
1596 candidate: collator,
1597 });
1598 Ok(Some(actual_weight).into())
1599 }
1600
1601 pub fn candidate_bond_more_inner(
1602 collator: T::AccountId,
1603 more: BalanceOf<T>,
1604 ) -> DispatchResultWithPostInfo {
1605 let mut state = <CandidateInfo<T>>::get(&collator).ok_or(Error::<T>::CandidateDNE)?;
1606 let actual_weight =
1607 <T as Config>::WeightInfo::candidate_bond_more(T::MaxCandidates::get());
1608
1609 state
1610 .bond_more::<T>(collator.clone(), more)
1611 .map_err(|err| DispatchErrorWithPostInfo {
1612 post_info: Some(actual_weight).into(),
1613 error: err,
1614 })?;
1615 let (is_active, total_counted) = (state.is_active(), state.total_counted);
1616 <CandidateInfo<T>>::insert(&collator, state);
1617 if is_active {
1618 Self::update_active(collator, total_counted);
1619 }
1620 Ok(Some(actual_weight).into())
1621 }
1622
1623 pub fn execute_candidate_bond_less_inner(
1624 candidate: T::AccountId,
1625 ) -> DispatchResultWithPostInfo {
1626 let mut state = <CandidateInfo<T>>::get(&candidate).ok_or(Error::<T>::CandidateDNE)?;
1627 let actual_weight =
1628 <T as Config>::WeightInfo::execute_candidate_bond_less(T::MaxCandidates::get());
1629
1630 state
1631 .execute_bond_less::<T>(candidate.clone())
1632 .map_err(|err| DispatchErrorWithPostInfo {
1633 post_info: Some(actual_weight).into(),
1634 error: err,
1635 })?;
1636 <CandidateInfo<T>>::insert(&candidate, state);
1637 Ok(Some(actual_weight).into())
1638 }
1639
1640 pub fn execute_leave_candidates_inner(
1641 candidate: T::AccountId,
1642 ) -> DispatchResultWithPostInfo {
1643 let state = <CandidateInfo<T>>::get(&candidate).ok_or(Error::<T>::CandidateDNE)?;
1644 let actual_auto_compound_delegation_count =
1645 <AutoCompoundingDelegations<T>>::decode_len(&candidate).unwrap_or_default() as u32;
1646
1647 let actual_delegation_count = state.delegation_count;
1649 let actual_weight = <T as Config>::WeightInfo::execute_leave_candidates_ideal(
1650 actual_delegation_count,
1651 actual_auto_compound_delegation_count,
1652 );
1653
1654 state
1655 .can_leave::<T>()
1656 .map_err(|err| DispatchErrorWithPostInfo {
1657 post_info: Some(actual_weight).into(),
1658 error: err,
1659 })?;
1660 let return_stake = |bond: Bond<T::AccountId, BalanceOf<T>>| -> DispatchResult {
1661 let mut delegator = DelegatorState::<T>::get(&bond.owner).expect(
1663 "Collator state and delegator state are consistent.
1664 Collator state has a record of this delegation. Therefore,
1665 Delegator state also has a record. qed.",
1666 );
1667
1668 if let Some(remaining) = delegator.rm_delegation::<T>(&candidate) {
1669 Self::delegation_remove_request_with_state(
1670 &candidate,
1671 &bond.owner,
1672 &mut delegator,
1673 );
1674 <AutoCompoundDelegations<T>>::remove_auto_compound(&candidate, &bond.owner);
1675
1676 if remaining.is_zero() {
1677 <DelegatorState<T>>::remove(&bond.owner);
1681 Self::thaw_extended(&bond.owner, false)?;
1683 } else {
1684 <DelegatorState<T>>::insert(&bond.owner, delegator);
1685 }
1686 } else {
1687 Self::thaw_extended(&bond.owner, false)?;
1688 }
1689 Ok(())
1690 };
1691 let mut total_backing = state.bond;
1693 let top_delegations =
1695 <TopDelegations<T>>::take(&candidate).expect("CandidateInfo existence checked");
1696 for bond in top_delegations.delegations {
1697 return_stake(bond)?;
1698 }
1699 total_backing = total_backing.saturating_add(top_delegations.total);
1700 let bottom_delegations =
1702 <BottomDelegations<T>>::take(&candidate).expect("CandidateInfo existence checked");
1703 for bond in bottom_delegations.delegations {
1704 return_stake(bond)?;
1705 }
1706 total_backing = total_backing.saturating_add(bottom_delegations.total);
1707 Self::thaw_extended(&candidate, true)?;
1709 <CandidateInfo<T>>::remove(&candidate);
1710 let _ = <DelegationScheduledRequests<T>>::clear_prefix(
1712 &candidate,
1713 Self::max_delegators_per_candidate(),
1714 None,
1715 );
1716 <DelegationScheduledRequestsPerCollator<T>>::remove(&candidate);
1717 <AutoCompoundingDelegations<T>>::remove(&candidate);
1718 <TopDelegations<T>>::remove(&candidate);
1719 <BottomDelegations<T>>::remove(&candidate);
1720 let new_total_staked = <Total<T>>::get().saturating_sub(total_backing);
1721 <Total<T>>::put(new_total_staked);
1722 Self::deposit_event(Event::CandidateLeft {
1723 ex_candidate: candidate,
1724 unlocked_amount: total_backing,
1725 new_total_amt_locked: new_total_staked,
1726 });
1727 Ok(Some(actual_weight).into())
1728 }
1729
1730 pub fn max_delegators_per_candidate() -> u32 {
1731 AddGet::<T::MaxTopDelegationsPerCandidate, T::MaxBottomDelegationsPerCandidate>::get()
1732 }
1733
1734 pub fn get_delegator_stakable_balance(acc: &T::AccountId) -> BalanceOf<T> {
1736 let total_balance =
1737 T::Currency::balance(acc).saturating_add(T::Currency::total_balance_on_hold(acc));
1738 if let Some(frozen_balance) = Self::balance_frozen_extended(acc, false) {
1739 return total_balance.saturating_sub(frozen_balance);
1740 }
1741 total_balance
1742 }
1743
1744 pub fn get_collator_stakable_free_balance(acc: &T::AccountId) -> BalanceOf<T> {
1746 let total_balance = T::Currency::balance(acc);
1747 if let Some(frozen_balance) = Self::balance_frozen_extended(acc, true) {
1748 return total_balance.saturating_sub(frozen_balance);
1749 }
1750 total_balance
1751 }
1752
1753 pub fn delegation_auto_compound(
1755 candidate: &T::AccountId,
1756 delegator: &T::AccountId,
1757 ) -> Percent {
1758 <AutoCompoundDelegations<T>>::auto_compound(candidate, delegator)
1759 }
1760
1761 pub(crate) fn update_active(candidate: T::AccountId, total: BalanceOf<T>) {
1763 let mut candidates = <CandidatePool<T>>::get();
1764 candidates.remove(&Bond::from_owner(candidate.clone()));
1765 candidates
1766 .try_insert(Bond {
1767 owner: candidate,
1768 amount: total,
1769 })
1770 .expect(
1771 "the candidate is removed in previous step so the length cannot increase; qed",
1772 );
1773 <CandidatePool<T>>::put(candidates);
1774 }
1775
1776 fn compute_issuance(round_duration: u64, round_length: u32) -> BalanceOf<T> {
1778 let ideal_duration: BalanceOf<T> = round_length
1779 .saturating_mul(T::BlockTime::get() as u32)
1780 .into();
1781 let config = <InflationConfig<T>>::get();
1782 let round_issuance = crate::inflation::round_issuance_range::<T>(config.round);
1783
1784 BalanceOf::<T>::from(round_duration as u32).saturating_mul(round_issuance.ideal)
1787 / (ideal_duration)
1788 }
1789
1790 pub(crate) fn delegator_leaves_candidate(
1793 candidate: T::AccountId,
1794 delegator: T::AccountId,
1795 amount: BalanceOf<T>,
1796 ) -> DispatchResult {
1797 let mut state = <CandidateInfo<T>>::get(&candidate).ok_or(Error::<T>::CandidateDNE)?;
1798 state.rm_delegation_if_exists::<T>(&candidate, delegator.clone(), amount)?;
1799 let new_total_locked = <Total<T>>::get().saturating_sub(amount);
1800 <Total<T>>::put(new_total_locked);
1801 let new_total = state.total_counted;
1802 <CandidateInfo<T>>::insert(&candidate, state);
1803 Self::deposit_event(Event::DelegatorLeftCandidate {
1804 delegator: delegator,
1805 candidate: candidate,
1806 unstaked_amount: amount,
1807 total_candidate_staked: new_total,
1808 });
1809 Ok(())
1810 }
1811
1812 pub(crate) fn prepare_staking_payouts(
1813 round_info: RoundInfo<BlockNumberFor<T>>,
1814 round_duration: u64,
1815 ) -> Weight {
1816 let RoundInfo {
1817 current: now,
1818 length: round_length,
1819 ..
1820 } = round_info;
1821
1822 let prepare_payout_for_round = now - 1;
1826
1827 if <Points<T>>::get(prepare_payout_for_round).is_zero() {
1829 return Weight::zero();
1830 }
1831
1832 let total_issuance = Self::compute_issuance(round_duration, round_length);
1834 let mut left_issuance = total_issuance;
1836
1837 let configs = <InflationDistributionInfo<T>>::get().0;
1838 for (index, config) in configs.iter().enumerate() {
1839 if config.percent.is_zero() {
1840 continue;
1841 }
1842 let reserve = config.percent * total_issuance;
1843 if frame_system::Pallet::<T>::account_exists(&config.account) {
1844 if let Ok(minted) = T::Currency::mint_into(&config.account, reserve) {
1845 left_issuance = left_issuance.saturating_sub(minted);
1847 Self::deposit_event(Event::InflationDistributed {
1848 index: index as u32,
1849 account: config.account.clone(),
1850 value: minted,
1851 });
1852 }
1853 }
1854 }
1855
1856 let payout = DelayedPayout {
1857 round_issuance: total_issuance,
1858 total_staking_reward: left_issuance,
1859 collator_commission: <CollatorCommission<T>>::get(),
1860 };
1861
1862 <DelayedPayouts<T>>::insert(prepare_payout_for_round, payout);
1863
1864 <T as Config>::WeightInfo::prepare_staking_payouts()
1865 }
1866
1867 fn handle_delayed_payouts(now: RoundIndex) -> Weight {
1872 let delay = T::RewardPaymentDelay::get();
1873
1874 if now < delay {
1876 return Weight::from_parts(0u64, 0);
1877 }
1878
1879 let paid_for_round = now.saturating_sub(delay);
1880
1881 if let Some(payout_info) = <DelayedPayouts<T>>::get(paid_for_round) {
1882 let result = Self::pay_one_collator_reward(paid_for_round, payout_info);
1883
1884 if matches!(result.0, RewardPayment::Finished) {
1886 <DelayedPayouts<T>>::remove(paid_for_round);
1887 <Points<T>>::remove(paid_for_round);
1888 }
1889 result.1 } else {
1891 Weight::from_parts(0u64, 0)
1892 }
1893 }
1894
1895 pub(crate) fn pay_one_collator_reward(
1900 paid_for_round: RoundIndex,
1901 payout_info: DelayedPayout<BalanceOf<T>>,
1902 ) -> (RewardPayment, Weight) {
1903 let mut early_weight = Weight::zero();
1906
1907 let total_points = <Points<T>>::get(paid_for_round);
1910 early_weight = early_weight.saturating_add(T::DbWeight::get().reads_writes(1, 0));
1911
1912 if total_points.is_zero() {
1913 log::warn!("pay_one_collator_reward called with no <Points<T>> for the round!");
1919 return (RewardPayment::Finished, early_weight);
1920 }
1921
1922 let collator_fee = payout_info.collator_commission;
1923 let collator_issuance = collator_fee * payout_info.round_issuance;
1924 if let Some((collator, state)) =
1925 <AtStake<T>>::iter_prefix(paid_for_round).drain().next()
1926 {
1927 early_weight = early_weight.saturating_add(T::DbWeight::get().reads_writes(1, 1));
1929
1930 let pts = <AwardedPts<T>>::take(paid_for_round, &collator);
1932 early_weight = early_weight.saturating_add(T::DbWeight::get().reads_writes(1, 1));
1934 if pts == 0 {
1935 return (RewardPayment::Skipped, early_weight);
1936 }
1937
1938 let mut extra_weight = Weight::zero();
1941 let pct_due = Perbill::from_rational(pts, total_points);
1942 let total_paid = pct_due * payout_info.total_staking_reward;
1943 let mut amt_due = total_paid;
1944
1945 let num_delegators = state.delegations.len();
1946 let mut num_paid_delegations = 0u32;
1947 let mut num_auto_compounding = 0u32;
1948 if state.delegations.is_empty() {
1949 extra_weight = extra_weight
1951 .saturating_add(T::PayoutCollatorReward::payout_collator_reward(
1952 paid_for_round,
1953 collator.clone(),
1954 amt_due,
1955 ))
1956 .saturating_add(T::OnCollatorPayout::on_collator_payout(
1957 paid_for_round,
1958 collator.clone(),
1959 amt_due,
1960 ));
1961 } else {
1962 let collator_pct = Perbill::from_rational(state.bond, state.total);
1964 let commission = pct_due * collator_issuance;
1965 amt_due = amt_due.saturating_sub(commission);
1966 let collator_reward = (collator_pct * amt_due).saturating_add(commission);
1967 extra_weight = extra_weight
1968 .saturating_add(T::PayoutCollatorReward::payout_collator_reward(
1969 paid_for_round,
1970 collator.clone(),
1971 collator_reward,
1972 ))
1973 .saturating_add(T::OnCollatorPayout::on_collator_payout(
1974 paid_for_round,
1975 collator.clone(),
1976 collator_reward,
1977 ));
1978
1979 for BondWithAutoCompound {
1981 owner,
1982 amount,
1983 auto_compound,
1984 } in state.delegations
1985 {
1986 let percent = Perbill::from_rational(amount, state.total);
1987 let due = percent * amt_due;
1988 if !due.is_zero() {
1989 num_auto_compounding += if auto_compound.is_zero() { 0 } else { 1 };
1990 num_paid_delegations += 1u32;
1991 Self::mint_and_compound(
1992 due,
1993 auto_compound.clone(),
1994 collator.clone(),
1995 owner.clone(),
1996 );
1997 }
1998 }
1999 }
2000
2001 extra_weight = extra_weight.saturating_add(
2002 <T as Config>::WeightInfo::pay_one_collator_reward_best(
2003 num_paid_delegations,
2004 num_auto_compounding,
2005 ),
2006 );
2007
2008 (
2009 RewardPayment::Paid,
2010 <T as Config>::WeightInfo::pay_one_collator_reward(num_delegators as u32)
2011 .saturating_add(extra_weight),
2012 )
2013 } else {
2014 (RewardPayment::Finished, Weight::from_parts(0u64, 0))
2017 }
2018 }
2019
2020 pub fn compute_top_candidates() -> Vec<T::AccountId> {
2025 let top_n = <TotalSelected<T>>::get() as usize;
2026 if top_n == 0 {
2027 return vec![];
2028 }
2029
2030 let candidates = <CandidatePool<T>>::get().0;
2031
2032 if candidates.len() > top_n {
2035 let sorted_candidates = candidates
2038 .try_mutate(|inner| {
2039 inner.select_nth_unstable_by(top_n - 1, |a, b| {
2040 a.amount
2043 .cmp(&b.amount)
2044 .then_with(|| a.owner.cmp(&b.owner))
2045 .reverse()
2046 });
2047 })
2048 .expect("sort cannot increase item count; qed");
2049
2050 let mut collators = sorted_candidates
2051 .into_iter()
2052 .take(top_n)
2053 .map(|x| x.owner)
2054 .collect::<Vec<_>>();
2055
2056 collators.sort();
2058
2059 collators
2060 } else {
2061 candidates.into_iter().map(|x| x.owner).collect::<Vec<_>>()
2064 }
2065 }
2066 pub(crate) fn select_top_candidates(now: RoundIndex) -> (Weight, u32, u32, BalanceOf<T>) {
2069 let (mut collator_count, mut delegation_count, mut total) =
2070 (0u32, 0u32, BalanceOf::<T>::zero());
2071 let collators = Self::compute_top_candidates();
2073 if collators.is_empty() {
2074 let last_round = now.saturating_sub(1u32);
2076 let mut total_per_candidate: BTreeMap<T::AccountId, BalanceOf<T>> = BTreeMap::new();
2077 for (account, snapshot) in <AtStake<T>>::iter_prefix(last_round) {
2079 collator_count = collator_count.saturating_add(1u32);
2080 delegation_count =
2081 delegation_count.saturating_add(snapshot.delegations.len() as u32);
2082 total = total.saturating_add(snapshot.total);
2083 total_per_candidate.insert(account.clone(), snapshot.total);
2084 <AtStake<T>>::insert(now, account, snapshot);
2085 }
2086 for candidate in <SelectedCandidates<T>>::get() {
2089 let snapshot_total = total_per_candidate
2090 .get(&candidate)
2091 .expect("all selected candidates have snapshots");
2092 Self::deposit_event(Event::CollatorChosen {
2093 round: now,
2094 collator_account: candidate,
2095 total_exposed_amount: *snapshot_total,
2096 })
2097 }
2098 let weight = <T as Config>::WeightInfo::select_top_candidates(0, 0);
2099 return (weight, collator_count, delegation_count, total);
2100 }
2101
2102 for account in collators.iter() {
2104 let state = <CandidateInfo<T>>::get(account)
2105 .expect("all members of CandidateQ must be candidates");
2106
2107 collator_count = collator_count.saturating_add(1u32);
2108 delegation_count = delegation_count.saturating_add(state.delegation_count);
2109 total = total.saturating_add(state.total_counted);
2110 let CountedDelegations {
2111 uncounted_stake,
2112 rewardable_delegations,
2113 } = Self::get_rewardable_delegators(&account);
2114 let total_counted = state.total_counted.saturating_sub(uncounted_stake);
2115
2116 let auto_compounding_delegations = <AutoCompoundingDelegations<T>>::get(&account)
2117 .into_iter()
2118 .map(|x| (x.delegator, x.value))
2119 .collect::<BTreeMap<_, _>>();
2120 let rewardable_delegations = rewardable_delegations
2121 .into_iter()
2122 .map(|d| BondWithAutoCompound {
2123 owner: d.owner.clone(),
2124 amount: d.amount,
2125 auto_compound: auto_compounding_delegations
2126 .get(&d.owner)
2127 .cloned()
2128 .unwrap_or_else(|| Percent::zero()),
2129 })
2130 .collect();
2131
2132 let snapshot = CollatorSnapshot {
2133 bond: state.bond,
2134 delegations: rewardable_delegations,
2135 total: total_counted,
2136 };
2137 <AtStake<T>>::insert(now, account, snapshot);
2138 Self::deposit_event(Event::CollatorChosen {
2139 round: now,
2140 collator_account: account.clone(),
2141 total_exposed_amount: state.total_counted,
2142 });
2143 }
2144 <SelectedCandidates<T>>::put(
2146 BoundedVec::try_from(collators)
2147 .expect("subset of collators is always less than or equal to max candidates"),
2148 );
2149
2150 let avg_delegator_count = delegation_count.checked_div(collator_count).unwrap_or(0);
2151 let weight = <T as Config>::WeightInfo::select_top_candidates(
2152 collator_count,
2153 avg_delegator_count,
2154 );
2155 (weight, collator_count, delegation_count, total)
2156 }
2157
2158 pub(crate) fn get_rewardable_delegators(collator: &T::AccountId) -> CountedDelegations<T> {
2168 let mut requests: BTreeMap<T::AccountId, DelegationAction<BalanceOf<T>>> =
2172 BTreeMap::new();
2173 for (delegator, scheduled_requests) in
2174 <DelegationScheduledRequests<T>>::iter_prefix(collator)
2175 {
2176 if scheduled_requests.is_empty() {
2177 continue;
2178 }
2179
2180 let (has_revoke, total) = scheduled_requests.iter().fold(
2183 (false, BalanceOf::<T>::zero()),
2184 |(has_revoke, total), req| {
2185 let has_revoke =
2186 has_revoke || matches!(req.action, DelegationAction::Revoke(_));
2187 let total = if has_revoke {
2188 BalanceOf::<T>::zero()
2190 } else {
2191 total.saturating_add(req.action.amount())
2192 };
2193 (has_revoke, total)
2194 },
2195 );
2196
2197 if has_revoke {
2198 requests.insert(delegator, DelegationAction::Revoke(BalanceOf::<T>::zero()));
2201 } else if !total.is_zero() {
2202 requests.insert(delegator, DelegationAction::Decrease(total));
2203 }
2204 }
2205 let mut uncounted_stake = BalanceOf::<T>::zero();
2206 let rewardable_delegations = <TopDelegations<T>>::get(collator)
2207 .expect("all members of CandidateQ must be candidates")
2208 .delegations
2209 .into_iter()
2210 .map(|mut bond| {
2211 bond.amount = match requests.get(&bond.owner) {
2212 None => bond.amount,
2213 Some(DelegationAction::Revoke(_)) => {
2214 uncounted_stake = uncounted_stake.saturating_add(bond.amount);
2215 BalanceOf::<T>::zero()
2216 }
2217 Some(DelegationAction::Decrease(amount)) => {
2218 uncounted_stake = uncounted_stake.saturating_add(*amount);
2219 bond.amount.saturating_sub(*amount)
2220 }
2221 };
2222
2223 bond
2224 })
2225 .collect();
2226 CountedDelegations {
2227 uncounted_stake,
2228 rewardable_delegations,
2229 }
2230 }
2231
2232 pub fn delegation_bond_more_without_event(
2238 delegator: T::AccountId,
2239 candidate: T::AccountId,
2240 more: BalanceOf<T>,
2241 ) -> Result<
2242 (bool, Weight),
2243 DispatchErrorWithPostInfo<frame_support::dispatch::PostDispatchInfo>,
2244 > {
2245 let mut state = <DelegatorState<T>>::get(&delegator).ok_or(Error::<T>::DelegatorDNE)?;
2246 ensure!(
2247 !Self::delegation_request_revoke_exists(&candidate, &delegator),
2248 Error::<T>::PendingDelegationRevoke
2249 );
2250
2251 let actual_weight = <T as Config>::WeightInfo::delegator_bond_more(0);
2254 let in_top = state
2255 .increase_delegation::<T>(candidate.clone(), more)
2256 .map_err(|err| DispatchErrorWithPostInfo {
2257 post_info: Some(actual_weight).into(),
2258 error: err,
2259 })?;
2260
2261 Ok((in_top, actual_weight))
2262 }
2263
2264 pub fn mint(amt: BalanceOf<T>, to: &T::AccountId) -> Result<BalanceOf<T>, DispatchError> {
2266 let minted = T::Currency::mint_into(&to, amt)?;
2268 Self::deposit_event(Event::Rewarded {
2269 account: to.clone(),
2270 rewards: minted,
2271 });
2272 Ok(minted)
2273 }
2274
2275 pub fn mint_collator_reward(
2277 _paid_for_round: RoundIndex,
2278 collator_id: T::AccountId,
2279 amt: BalanceOf<T>,
2280 ) -> Weight {
2281 if let Err(e) = Self::mint(amt, &collator_id) {
2283 log::warn!(
2284 "Failed to mint collator reward for {:?}: {:?}",
2285 collator_id,
2286 e
2287 );
2288 }
2289
2290 <T as Config>::WeightInfo::mint_collator_reward()
2291 }
2292
2293 pub fn mint_and_compound(
2298 amt: BalanceOf<T>,
2299 compound_percent: Percent,
2300 candidate: T::AccountId,
2301 delegator: T::AccountId,
2302 ) {
2303 if frame_system::Pallet::<T>::account_exists(&delegator) {
2305 if let Ok(minted) = Self::mint(amt.clone(), &delegator) {
2306 let compound_amount = compound_percent.mul_ceil(minted);
2307 if compound_amount.is_zero() {
2308 return;
2309 }
2310
2311 if let Err(err) = Self::delegation_bond_more_without_event(
2312 delegator.clone(),
2313 candidate.clone(),
2314 compound_amount.clone(),
2315 ) {
2316 log::debug!(
2317 "skipped compounding staking reward towards candidate '{:?}' for delegator '{:?}': {:?}",
2318 candidate,
2319 delegator,
2320 err
2321 );
2322 return;
2323 };
2324
2325 Pallet::<T>::deposit_event(Event::Compounded {
2326 delegator,
2327 candidate,
2328 amount: compound_amount.clone(),
2329 });
2330 };
2331 }
2332 }
2333
2334 fn award_points_to_block_author() {
2337 let author = T::BlockAuthor::get();
2338 let now = <Round<T>>::get().current;
2339 let score_plus_20 = <AwardedPts<T>>::get(now, &author).saturating_add(20);
2340 <AwardedPts<T>>::insert(now, author, score_plus_20);
2341 <Points<T>>::mutate(now, |x| *x = x.saturating_add(20));
2342 }
2343
2344 pub fn mark_collators_as_inactive(cur: RoundIndex) -> Weight {
2346 let prev = cur - 1;
2349
2350 let mut collators_at_stake_count = 0u32;
2351 for (account, _) in <AtStake<T>>::iter_prefix(prev) {
2352 collators_at_stake_count = collators_at_stake_count.saturating_add(1u32);
2353 if <AwardedPts<T>>::get(prev, &account).is_zero() {
2354 <WasInactive<T>>::insert(prev, account, ());
2355 }
2356 }
2357
2358 <T as Config>::WeightInfo::mark_collators_as_inactive(collators_at_stake_count)
2359 }
2360
2361 fn cleanup_inactive_collator_info() {
2364 let now = <Round<T>>::get().current;
2365 let minimum_rounds_required = T::MaxOfflineRounds::get() + 1;
2366
2367 if now < minimum_rounds_required {
2368 return;
2369 }
2370
2371 let _ = <WasInactive<T>>::iter_prefix(now - minimum_rounds_required)
2372 .drain()
2373 .next();
2374 }
2375 }
2376
2377 impl<T: Config> nimbus_primitives::CanAuthor<T::AccountId> for Pallet<T> {
2378 fn can_author(account: &T::AccountId, _slot: &u32) -> bool {
2379 Self::is_selected_candidate(account)
2380 }
2381 }
2382
2383 impl<T: Config> Get<Vec<T::AccountId>> for Pallet<T> {
2384 fn get() -> Vec<T::AccountId> {
2385 Self::selected_candidates().into_inner()
2386 }
2387 }
2388}