pallet_parachain_staking/
lib.rs

1// Copyright 2019-2025 PureStake Inc.
2// This file is part of Moonbeam.
3
4// Moonbeam is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Moonbeam is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Moonbeam.  If not, see <http://www.gnu.org/licenses/>.
16
17//! # Parachain Staking
18//! Minimal staking pallet that implements collator selection by total backed stake.
19//! The main difference between this pallet and `frame/pallet-staking` is that this pallet
20//! uses direct delegation. Delegators choose exactly who they delegate and with what stake.
21//! This is different from `frame/pallet-staking` where delegators approval vote and run Phragmen.
22//!
23//! ### Rules
24//! There is a new round every `<Round<T>>::get().length` blocks.
25//!
26//! At the start of every round,
27//! * issuance is calculated for collators (and their delegators) for block authoring
28//! `T::RewardPaymentDelay` rounds ago
29//! * a new set of collators is chosen from the candidates
30//!
31//! Immediately following a round change, payments are made once-per-block until all payments have
32//! been made. In each such block, one collator is chosen for a rewards payment and is paid along
33//! with each of its top `T::MaxTopDelegationsPerCandidate` delegators.
34//!
35//! To join the set of candidates, call `join_candidates` with `bond >= MinCandidateStk`.
36//! To leave the set of candidates, call `schedule_leave_candidates`. If the call succeeds,
37//! the collator is removed from the pool of candidates so they cannot be selected for future
38//! collator sets, but they are not unbonded until their exit request is executed. Any signed
39//! account may trigger the exit `T::LeaveCandidatesDelay` rounds after the round in which the
40//! original request was made.
41//!
42//! To join the set of delegators, call `delegate` and pass in an account that is
43//! already a collator candidate and `bond >= MinDelegation`. Each delegator can delegate up to
44//! `T::MaxDelegationsPerDelegator` collator candidates by calling `delegate`.
45//!
46//! To revoke a delegation, call `revoke_delegation` with the collator candidate's account.
47//! To leave the set of delegators and revoke all delegations, call `leave_delegators`.
48
49#![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 for parachain staking
100	#[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	/// A hard limit for weight computation purposes for the max candidates that _could_
110	/// theoretically exist.
111	pub const MAX_CANDIDATES: u32 = 200;
112
113	/// Configuration trait of this pallet.
114	#[pallet::config]
115	pub trait Config: frame_system::Config<RuntimeEvent: From<Event<Self>>> {
116		/// The fungible type for handling balances
117		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		/// The overarching freeze identifier type.
123		type RuntimeFreezeReason: From<FreezeReason>;
124		/// The origin for monetary governance
125		type MonetaryGovernanceOrigin: EnsureOrigin<Self::RuntimeOrigin>;
126		/// Minimum number of blocks per round
127		#[pallet::constant]
128		type MinBlocksPerRound: Get<u32>;
129		/// If a collator doesn't produce any block on this number of rounds, it is notified as inactive.
130		/// This value must be less than or equal to RewardPaymentDelay.
131		#[pallet::constant]
132		type MaxOfflineRounds: Get<u32>;
133		/// Number of rounds that candidates remain bonded before exit request is executable
134		#[pallet::constant]
135		type LeaveCandidatesDelay: Get<RoundIndex>;
136		/// Number of rounds candidate requests to decrease self-bond must wait to be executable
137		#[pallet::constant]
138		type CandidateBondLessDelay: Get<RoundIndex>;
139		/// Number of rounds that delegators remain bonded before exit request is executable
140		#[pallet::constant]
141		type LeaveDelegatorsDelay: Get<RoundIndex>;
142		/// Number of rounds that delegations remain bonded before revocation request is executable
143		#[pallet::constant]
144		type RevokeDelegationDelay: Get<RoundIndex>;
145		/// Number of rounds that delegation less requests must wait before executable
146		#[pallet::constant]
147		type DelegationBondLessDelay: Get<RoundIndex>;
148		/// Number of rounds after which block authors are rewarded
149		#[pallet::constant]
150		type RewardPaymentDelay: Get<RoundIndex>;
151		/// Minimum number of selected candidates every round
152		#[pallet::constant]
153		type MinSelectedCandidates: Get<u32>;
154		/// Maximum top delegations counted per candidate
155		#[pallet::constant]
156		type MaxTopDelegationsPerCandidate: Get<u32>;
157		/// Maximum bottom delegations (not counted) per candidate
158		#[pallet::constant]
159		type MaxBottomDelegationsPerCandidate: Get<u32>;
160		/// Maximum delegations per delegator
161		#[pallet::constant]
162		type MaxDelegationsPerDelegator: Get<u32>;
163		/// Maximum number of scheduled delegation requests per (collator, delegator).
164		#[pallet::constant]
165		type MaxScheduledRequestsPerDelegator: Get<u32>;
166		/// Minimum stake required for any account to be a collator candidate
167		#[pallet::constant]
168		type MinCandidateStk: Get<BalanceOf<Self>>;
169		/// Minimum stake for any registered on-chain account to delegate
170		#[pallet::constant]
171		type MinDelegation: Get<BalanceOf<Self>>;
172		/// Get the current block author
173		type BlockAuthor: Get<Self::AccountId>;
174		/// Handler to notify the runtime when a collator is paid.
175		/// If you don't need it, you can specify the type `()`.
176		type OnCollatorPayout: OnCollatorPayout<Self::AccountId, BalanceOf<Self>>;
177		/// Handler to distribute a collator's reward.
178		/// To use the default implementation of minting rewards, specify the type `()`.
179		type PayoutCollatorReward: PayoutCollatorReward<Self>;
180		/// Handler to notify the runtime when a collator is inactive.
181		/// The default behavior is to mark the collator as offline.
182		/// If you need to use the default implementation, specify the type `()`.
183		type OnInactiveCollator: OnInactiveCollator<Self>;
184		/// Handler to notify the runtime when a new round begin.
185		/// If you don't need it, you can specify the type `()`.
186		type OnNewRound: OnNewRound;
187		/// Get the current slot number
188		type SlotProvider: Get<Slot>;
189		/// Get the slot duration in milliseconds
190		#[pallet::constant]
191		type SlotDuration: Get<u64>;
192		/// Get the average time between 2 blocks in milliseconds
193		#[pallet::constant]
194		type BlockTime: Get<u64>;
195		/// Maximum candidates
196		#[pallet::constant]
197		type MaxCandidates: Get<u32>;
198		/// Threshold after which inflation become linear
199		/// If you don't want to use it, set it to `()`
200		#[pallet::constant]
201		type LinearInflationThreshold: Get<Option<BalanceOf<Self>>>;
202		/// Weight information for extrinsics in this pallet.
203		type WeightInfo: WeightInfo;
204	}
205
206	/// The reason for freezing funds.
207	#[pallet::composite_enum]
208	pub enum FreezeReason {
209		/// Funds frozen for staking as a collator
210		StakingCollator,
211		/// Funds frozen for staking as a delegator
212		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		/// Started new round.
279		NewRound {
280			starting_block: BlockNumberFor<T>,
281			round: RoundIndex,
282			selected_collators_number: u32,
283			total_balance: BalanceOf<T>,
284		},
285		/// Account joined the set of collator candidates.
286		JoinedCollatorCandidates {
287			account: T::AccountId,
288			amount_locked: BalanceOf<T>,
289			new_total_amt_locked: BalanceOf<T>,
290		},
291		/// Candidate selected for collators. Total Exposed Amount includes all delegations.
292		CollatorChosen {
293			round: RoundIndex,
294			collator_account: T::AccountId,
295			total_exposed_amount: BalanceOf<T>,
296		},
297		/// Candidate requested to decrease a self bond.
298		CandidateBondLessRequested {
299			candidate: T::AccountId,
300			amount_to_decrease: BalanceOf<T>,
301			execute_round: RoundIndex,
302		},
303		/// Candidate has increased a self bond.
304		CandidateBondedMore {
305			candidate: T::AccountId,
306			amount: BalanceOf<T>,
307			new_total_bond: BalanceOf<T>,
308		},
309		/// Candidate has decreased a self bond.
310		CandidateBondedLess {
311			candidate: T::AccountId,
312			amount: BalanceOf<T>,
313			new_bond: BalanceOf<T>,
314		},
315		/// Candidate temporarily leave the set of collator candidates without unbonding.
316		CandidateWentOffline { candidate: T::AccountId },
317		/// Candidate rejoins the set of collator candidates.
318		CandidateBackOnline { candidate: T::AccountId },
319		/// Candidate has requested to leave the set of candidates.
320		CandidateScheduledExit {
321			exit_allowed_round: RoundIndex,
322			candidate: T::AccountId,
323			scheduled_exit: RoundIndex,
324		},
325		/// Cancelled request to leave the set of candidates.
326		CancelledCandidateExit { candidate: T::AccountId },
327		/// Cancelled request to decrease candidate's bond.
328		CancelledCandidateBondLess {
329			candidate: T::AccountId,
330			amount: BalanceOf<T>,
331			execute_round: RoundIndex,
332		},
333		/// Candidate has left the set of candidates.
334		CandidateLeft {
335			ex_candidate: T::AccountId,
336			unlocked_amount: BalanceOf<T>,
337			new_total_amt_locked: BalanceOf<T>,
338		},
339		/// Delegator requested to decrease a bond for the collator candidate.
340		DelegationDecreaseScheduled {
341			delegator: T::AccountId,
342			candidate: T::AccountId,
343			amount_to_decrease: BalanceOf<T>,
344			execute_round: RoundIndex,
345		},
346		// Delegation increased.
347		DelegationIncreased {
348			delegator: T::AccountId,
349			candidate: T::AccountId,
350			amount: BalanceOf<T>,
351			in_top: bool,
352		},
353		// Delegation decreased.
354		DelegationDecreased {
355			delegator: T::AccountId,
356			candidate: T::AccountId,
357			amount: BalanceOf<T>,
358			in_top: bool,
359		},
360		/// Delegator requested to leave the set of delegators.
361		DelegatorExitScheduled {
362			round: RoundIndex,
363			delegator: T::AccountId,
364			scheduled_exit: RoundIndex,
365		},
366		/// Delegator requested to revoke delegation.
367		DelegationRevocationScheduled {
368			round: RoundIndex,
369			delegator: T::AccountId,
370			candidate: T::AccountId,
371			scheduled_exit: RoundIndex,
372		},
373		/// Delegator has left the set of delegators.
374		DelegatorLeft {
375			delegator: T::AccountId,
376			unstaked_amount: BalanceOf<T>,
377		},
378		/// Delegation revoked.
379		DelegationRevoked {
380			delegator: T::AccountId,
381			candidate: T::AccountId,
382			unstaked_amount: BalanceOf<T>,
383		},
384		/// Delegation kicked.
385		DelegationKicked {
386			delegator: T::AccountId,
387			candidate: T::AccountId,
388			unstaked_amount: BalanceOf<T>,
389		},
390		/// Cancelled a pending request to exit the set of delegators.
391		DelegatorExitCancelled { delegator: T::AccountId },
392		/// Cancelled request to change an existing delegation.
393		CancelledDelegationRequest {
394			delegator: T::AccountId,
395			cancelled_request: CancelledScheduledRequest<BalanceOf<T>>,
396			collator: T::AccountId,
397		},
398		/// New delegation (increase of the existing one).
399		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		/// Delegation from candidate state has been removed.
407		DelegatorLeftCandidate {
408			delegator: T::AccountId,
409			candidate: T::AccountId,
410			unstaked_amount: BalanceOf<T>,
411			total_candidate_staked: BalanceOf<T>,
412		},
413		/// Paid the account (delegator or collator) the balance as liquid rewards.
414		Rewarded {
415			account: T::AccountId,
416			rewards: BalanceOf<T>,
417		},
418		/// Transferred to account which holds funds reserved for parachain bond.
419		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		/// Annual inflation input (first 3) was used to derive new per-round inflation (last 3)
429		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		/// Staking expectations set.
438		StakeExpectationsSet {
439			expect_min: BalanceOf<T>,
440			expect_ideal: BalanceOf<T>,
441			expect_max: BalanceOf<T>,
442		},
443		/// Set total selected candidates to this value.
444		TotalSelectedSet { old: u32, new: u32 },
445		/// Set collator commission to this value.
446		CollatorCommissionSet { old: Perbill, new: Perbill },
447		/// Set blocks per round
448		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		/// Auto-compounding reward percent was set for a delegation.
458		AutoCompoundSet {
459			candidate: T::AccountId,
460			delegator: T::AccountId,
461			value: Percent,
462		},
463		/// Compounded a portion of rewards towards the delegation.
464		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				// fetch current slot number
479				let current_slot: u64 = T::SlotProvider::get().into();
480
481				// account for SlotProvider read
482				weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 0));
483
484				// Compute round duration in slots
485				let round_duration = (current_slot.saturating_sub(round.first_slot))
486					.saturating_mul(T::SlotDuration::get());
487
488				// mutate round
489				round.update(n, current_slot);
490				// notify that new round begin
491				weight = weight.saturating_add(T::OnNewRound::on_new_round(round.current));
492				// pay all stakers for T::RewardPaymentDelay rounds ago
493				weight =
494					weight.saturating_add(Self::prepare_staking_payouts(round, round_duration));
495				// select top collator candidates for next round
496				let (extra_weight, collator_count, _delegation_count, total_staked) =
497					Self::select_top_candidates(round.current);
498				weight = weight.saturating_add(extra_weight);
499				// start next round
500				<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				// record inactive collators
508				weight = weight.saturating_add(Self::mark_collators_as_inactive(round.current));
509				// account for Round write
510				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			// add on_finalize weight
516			//   read:  Author, Points, AwardedPts, WasInactive
517			//   write: Points, AwardedPts, WasInactive
518			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	/// Commission percent taken off of rewards for all collators
530	type CollatorCommission<T: Config> = StorageValue<_, Perbill, ValueQuery>;
531
532	#[pallet::storage]
533	#[pallet::getter(fn total_selected)]
534	/// The total candidates selected every round
535	pub(crate) type TotalSelected<T: Config> = StorageValue<_, u32, ValueQuery>;
536
537	#[pallet::storage]
538	#[pallet::getter(fn inflation_distribution_info)]
539	/// Inflation distribution configuration, including accounts that should receive inflation
540	/// before it is distributed to collators and delegators.
541	///
542	/// The sum of the distribution percents must be less than or equal to 100.
543	pub(crate) type InflationDistributionInfo<T: Config> =
544		StorageValue<_, InflationDistributionConfig<T::AccountId>, ValueQuery>;
545
546	#[pallet::storage]
547	#[pallet::getter(fn round)]
548	/// Current round index and next round scheduled transition
549	pub type Round<T: Config> = StorageValue<_, RoundInfo<BlockNumberFor<T>>, ValueQuery>;
550
551	#[pallet::storage]
552	#[pallet::getter(fn delegator_state)]
553	/// Get delegator state associated with an account if account is delegating else None
554	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	/// Get collator candidate info associated with an account if account is candidate else None
565	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	/// Stores outstanding delegation requests per collator & delegator.
582	///
583	/// Each `(collator, delegator)` pair can have up to
584	/// `T::MaxScheduledRequestsPerDelegator` scheduled requests,
585	/// which are always interpreted and executed in FIFO order.
586	#[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	/// Tracks how many delegators have at least one pending delegation request for a given collator.
599	///
600	/// This is used to enforce that the number of delegators with pending requests per collator
601	/// does not exceed `MaxTopDelegationsPerCandidate + MaxBottomDelegationsPerCandidate` without
602	/// having to iterate over all scheduled requests.
603	#[pallet::storage]
604	pub(crate) type DelegationScheduledRequestsPerCollator<T: Config> =
605		StorageMap<_, Blake2_128Concat, T::AccountId, u32, ValueQuery>;
606
607	/// Stores auto-compounding configuration per collator.
608	#[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	/// Top delegations for collator candidate
624	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	/// Bottom delegations for collator candidate
635	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	/// The collator candidates selected for the current round
646	type SelectedCandidates<T: Config> =
647		StorageValue<_, BoundedVec<T::AccountId, T::MaxCandidates>, ValueQuery>;
648
649	#[pallet::storage]
650	#[pallet::getter(fn total)]
651	/// Total capital locked by this staking pallet
652	pub(crate) type Total<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
653
654	#[pallet::storage]
655	#[pallet::getter(fn candidate_pool)]
656	/// The pool of collator candidates, each with their total backing stake
657	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	/// Snapshot of collator delegation stake at the start of the round
666	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	/// Records collators' inactivity.
679	/// Data persists for MaxOfflineRounds + 1 rounds before being pruned.
680	pub type WasInactive<T: Config> =
681		StorageDoubleMap<_, Twox64Concat, RoundIndex, Twox64Concat, T::AccountId, (), OptionQuery>;
682
683	#[pallet::storage]
684	#[pallet::getter(fn delayed_payouts)]
685	/// Delayed payouts
686	pub type DelayedPayouts<T: Config> =
687		StorageMap<_, Twox64Concat, RoundIndex, DelayedPayout<BalanceOf<T>>, OptionQuery>;
688
689	#[pallet::storage]
690	#[pallet::getter(fn inflation_config)]
691	/// Inflation configuration
692	pub type InflationConfig<T: Config> = StorageValue<_, InflationInfo<BalanceOf<T>>, ValueQuery>;
693
694	#[pallet::storage]
695	#[pallet::getter(fn points)]
696	/// Total points awarded to collators for block production in the round
697	pub type Points<T: Config> = StorageMap<_, Twox64Concat, RoundIndex, RewardPoint, ValueQuery>;
698
699	#[pallet::storage]
700	#[pallet::getter(fn awarded_pts)]
701	/// Points for each collator per round
702	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	/// Killswitch to enable/disable marking offline feature.
715	pub type EnableMarkingOffline<T: Config> = StorageValue<_, bool, ValueQuery>;
716
717	#[pallet::genesis_config]
718	pub struct GenesisConfig<T: Config> {
719		/// Initialize balance and register all as collators: `(collator AccountId, balance Amount)`
720		pub candidates: Vec<(T::AccountId, BalanceOf<T>)>,
721		/// Initialize balance and make delegations:
722		/// `(delegator AccountId, collator AccountId, delegation Amount, auto-compounding Percent)`
723		pub delegations: Vec<(T::AccountId, T::AccountId, BalanceOf<T>, Percent)>,
724		/// Inflation configuration
725		pub inflation_config: InflationInfo<BalanceOf<T>>,
726		/// Default fixed percent a collator takes off the top of due rewards
727		pub collator_commission: Perbill,
728		/// Default percent of inflation set aside for parachain bond every round
729		pub parachain_bond_reserve_percent: Percent,
730		/// Default number of blocks in a round
731		pub blocks_per_round: u32,
732		/// Number of selected candidates every round. Cannot be lower than MinSelectedCandidates
733		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			// Initialize the candidates
757			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			// Initialize the delegations
778			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			// Set collator commission to default config
827			<CollatorCommission<T>>::put(self.collator_commission);
828			// Set parachain bond config to default config
829			let pbr = InflationDistributionAccount {
830				// must be set soon; if not => due inflation will be sent to collators/delegators
831				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			// Set total selected candidates to value from config
844			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			// Choose top TotalSelected collator candidates
856			let (_, v_count, _, total_staked) = <Pallet<T>>::select_top_candidates(1u32);
857			// Start Round 1 at Block 0
858			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		/// Set the expectations for total staked. These expectations determine the issuance for
873		/// the round according to logic in `fn compute_issuance`
874		#[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		/// Set the annual inflation rate to derive per-round inflation
898		#[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		/// Set the total number of collator candidates selected per round
923		/// - changes are not applied until the start of the next round
924		#[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		/// Set the commission for all collators
948		#[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		/// Set blocks per round
963		/// - if called with `new` less than length of current round, will transition immediately
964		/// in the next block
965		/// - also updates per-round inflation config
966		#[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			// update per-round inflation given new rounds per year
983			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		/// Join the set of collator candidates
1000		#[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		/// Request to leave the set of candidates. If successful, the account is immediately
1016		/// removed from the candidate pool to prevent selection as a collator.
1017		#[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		/// Execute leave candidates request
1044		#[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		/// Cancel open request to leave candidates
1063		/// - only callable by collator account
1064		/// - result upon successful call is the candidate is active in the candidate pool
1065		#[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		/// Temporarily leave the set of collator candidates without unbonding
1096		#[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		/// Rejoin the set of collator candidates if previously had called `go_offline`
1104		#[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		/// Increase collator candidate self bond by `more`
1112		#[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		/// Request by collator candidate to decrease self bond by `less`
1123		#[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		/// Execute pending request to adjust the collator candidate self bond
1142		#[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)?; // we may want to reward this if caller != candidate
1149			<Pallet<T>>::execute_candidate_bond_less_inner(candidate)
1150		}
1151
1152		/// Cancel pending request to adjust the collator candidate self bond
1153		#[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		/// If caller is not a delegator and not a collator, then join the set of delegators
1164		/// If caller is a delegator, then makes delegation to change their delegation state
1165		/// Sets the auto-compound config for the delegation
1166		#[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		/// Request to revoke an existing delegation. If successful, the delegation is scheduled
1196		/// to be allowed to be revoked via the `execute_delegation_request` extrinsic.
1197		/// The delegation receives no rewards for the rounds while a revoke is pending.
1198		/// A revoke may not be performed if any other scheduled request is pending.
1199		#[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		/// Bond more for delegators wrt a specific collator candidate.
1212		#[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		/// Request bond less for delegators wrt a specific collator candidate. The delegation's
1238		/// rewards for rounds while the request is pending use the reduced bonded amount.
1239		/// A bond less may not be performed if a revoke request is pending for the same delegation.
1240		#[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		/// Execute pending request to change an existing delegation
1254		#[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)?; // we may want to reward caller if caller != delegator
1262			Self::delegation_execute_scheduled_request(candidate, delegator)
1263		}
1264
1265		/// Cancel request to change an existing delegation.
1266		#[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		/// Sets the auto-compounding reward percentage for a delegation.
1277		#[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		/// Notify a collator is inactive during MaxOfflineRounds
1300		#[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			// Check collators length is not below or eq to 66% of max_collators.
1320			// We use saturating logic here with (2/3)
1321			// as it is dangerous to use floating point numbers directly.
1322			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			// Have rounds_to_check = [8,9]
1336			// in case we are in round 10 for instance
1337			// with MaxOfflineRounds = 2
1338			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			// If this counter is eq to max_offline_rounds,
1342			// the collator should be notified as inactive
1343			let mut inactive_counter: RoundIndex = 0u32;
1344
1345			// Iter rounds and check whether the collator has been inactive
1346			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		/// Enable/Disable marking offline feature
1365		#[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		/// Force join the set of collator candidates.
1377		/// It will skip the minimum required bond check.
1378		#[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		/// Set the inflation distribution configuration.
1391		#[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	/// Represents a payout made via `pay_one_collator_reward`.
1418	pub(crate) enum RewardPayment {
1419		/// A collator was paid
1420		Paid,
1421		/// A collator was skipped for payment. This can happen if they haven't been awarded any
1422		/// points, that is, they did not produce any blocks.
1423		Skipped,
1424		/// All collator payments have been processed.
1425		Finished,
1426	}
1427
1428	impl<T: Config> Pallet<T> {
1429		/// Set freeze
1430		///
1431		/// `is_collator` determines whether the account is a collator or delegator
1432		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			// Now set the freeze
1440			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		/// `is_collator` determines whether the account is a collator or delegator
1450		pub(crate) fn thaw_extended(account: &T::AccountId, is_collator: bool) -> DispatchResult {
1451			use frame_support::traits::fungible::MutateFreeze;
1452
1453			// Now thaw the freeze
1454			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		/// Get frozen balance
1464		///
1465		/// `is_collator` determines whether the account is a collator or delegator
1466		pub(crate) fn balance_frozen_extended(
1467			account: &T::AccountId,
1468			is_collator: bool,
1469		) -> Option<BalanceOf<T>> {
1470			// Now return the frozen balance
1471			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			// insert empty top delegations
1520			<TopDelegations<T>>::insert(&acc, empty_delegations.clone());
1521			// insert empty bottom delegations
1522			<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			// TODO use these to return actual weight used via `execute_leave_candidates_ideal`
1648			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				// remove delegation from delegator state
1662				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						// we do not remove the scheduled delegation requests from other collators
1678						// since it is assumed that they were removed incrementally before only the
1679						// last delegation was left.
1680						<DelegatorState<T>>::remove(&bond.owner);
1681						// Thaw all frozen funds for delegator
1682						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			// total backing stake is at least the candidate self bond
1692			let mut total_backing = state.bond;
1693			// return all top delegations
1694			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			// return all bottom delegations
1701			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			// Thaw all frozen funds for collator
1708			Self::thaw_extended(&candidate, true)?;
1709			<CandidateInfo<T>>::remove(&candidate);
1710			// Remove all scheduled delegation requests for this collator
1711			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		/// Returns an account's stakable balance (including the reserved) which is not frozen in delegation staking
1735		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		/// Returns an account's free balance which is not frozen in collator staking
1745		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		/// Returns a delegations auto-compound value.
1754		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		/// Caller must ensure candidate is active before calling
1762		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		/// Compute round issuance based on duration of the given round
1777		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			// Initial formula: (round_duration / ideal_duration) * ideal_issuance
1785			// We multiply before the division to reduce rounding effects
1786			BalanceOf::<T>::from(round_duration as u32).saturating_mul(round_issuance.ideal)
1787				/ (ideal_duration)
1788		}
1789
1790		/// Remove delegation from candidate state
1791		/// Amount input should be retrieved from delegator and it informs the storage lookups
1792		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			// This function is called right after the round index increment,
1823			// and the goal is to compute the payout informations for the round that just ended.
1824			// We don't need to saturate here because the genesis round is 1.
1825			let prepare_payout_for_round = now - 1;
1826
1827			// Return early if there is no blocks for this round
1828			if <Points<T>>::get(prepare_payout_for_round).is_zero() {
1829				return Weight::zero();
1830			}
1831
1832			// Compute total issuance based on round duration
1833			let total_issuance = Self::compute_issuance(round_duration, round_length);
1834			// reserve portion of issuance for parachain bond account
1835			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						// update round issuance if minting succeeds
1846						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		/// Wrapper around pay_one_collator_reward which handles the following logic:
1868		/// * whether or not a payout needs to be made
1869		/// * cleaning up when payouts are done
1870		/// * returns the weight consumed by pay_one_collator_reward if applicable
1871		fn handle_delayed_payouts(now: RoundIndex) -> Weight {
1872			let delay = T::RewardPaymentDelay::get();
1873
1874			// don't underflow uint
1875			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				// clean up storage items that we no longer need
1885				if matches!(result.0, RewardPayment::Finished) {
1886					<DelayedPayouts<T>>::remove(paid_for_round);
1887					<Points<T>>::remove(paid_for_round);
1888				}
1889				result.1 // weight consumed by pay_one_collator_reward
1890			} else {
1891				Weight::from_parts(0u64, 0)
1892			}
1893		}
1894
1895		/// Payout a single collator from the given round.
1896		///
1897		/// Returns an optional tuple of (Collator's AccountId, total paid)
1898		/// or None if there were no more payouts to be made for the round.
1899		pub(crate) fn pay_one_collator_reward(
1900			paid_for_round: RoundIndex,
1901			payout_info: DelayedPayout<BalanceOf<T>>,
1902		) -> (RewardPayment, Weight) {
1903			// 'early_weight' tracks weight used for reads/writes done early in this fn before its
1904			// early-exit codepaths.
1905			let mut early_weight = Weight::zero();
1906
1907			// TODO: it would probably be optimal to roll Points into the DelayedPayouts storage
1908			// item so that we do fewer reads each block
1909			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				// TODO: this case is obnoxious... it's a value query, so it could mean one of two
1914				// different logic errors:
1915				// 1. we removed it before we should have
1916				// 2. we called pay_one_collator_reward when we were actually done with deferred
1917				//    payouts
1918				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				// read and kill AtStake
1928				early_weight = early_weight.saturating_add(T::DbWeight::get().reads_writes(1, 1));
1929
1930				// Take the awarded points for the collator
1931				let pts = <AwardedPts<T>>::take(paid_for_round, &collator);
1932				// read and kill AwardedPts
1933				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				// 'extra_weight' tracks weight returned from fns that we delegate to which can't be
1939				// known ahead of time.
1940				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					// solo collator with no delegators
1950					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					// pay collator first; commission + due_portion
1963					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					// pay delegators due portion
1980					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				// Note that we don't clean up storage here; it is cleaned up in
2015				// handle_delayed_payouts()
2016				(RewardPayment::Finished, Weight::from_parts(0u64, 0))
2017			}
2018		}
2019
2020		/// Compute the top `TotalSelected` candidates in the CandidatePool and return
2021		/// a vec of their AccountIds (sorted by AccountId).
2022		///
2023		/// If the returned vec is empty, the previous candidates should be used.
2024		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 the number of candidates is greater than top_n, select the candidates with higher
2033			// amount. Otherwise, return all the candidates.
2034			if candidates.len() > top_n {
2035				// Partially sort candidates such that element at index `top_n - 1` is sorted, and
2036				// all the elements in the range 0..top_n are the top n elements.
2037				let sorted_candidates = candidates
2038					.try_mutate(|inner| {
2039						inner.select_nth_unstable_by(top_n - 1, |a, b| {
2040							// Order by amount, then owner. The owner is needed to ensure a stable order
2041							// when two accounts have the same amount.
2042							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				// Sort collators by AccountId
2057				collators.sort();
2058
2059				collators
2060			} else {
2061				// Return all candidates
2062				// The candidates are already sorted by AccountId, so no need to sort again
2063				candidates.into_iter().map(|x| x.owner).collect::<Vec<_>>()
2064			}
2065		}
2066		/// Best as in most cumulatively supported in terms of stake
2067		/// Returns [collator_count, delegation_count, total staked]
2068		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			// choose the top TotalSelected qualified candidates, ordered by stake
2072			let collators = Self::compute_top_candidates();
2073			if collators.is_empty() {
2074				// SELECTION FAILED TO SELECT >=1 COLLATOR => select collators from previous round
2075				let last_round = now.saturating_sub(1u32);
2076				let mut total_per_candidate: BTreeMap<T::AccountId, BalanceOf<T>> = BTreeMap::new();
2077				// set this round AtStake to last round AtStake
2078				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				// `SelectedCandidates` remains unchanged from last round
2087				// emit CollatorChosen event for tools that use this event
2088				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			// snapshot exposure for round for weighting reward distribution
2103			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			// insert canonical collator set
2145			<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		/// Apply the delegator intent for revoke and decrease in order to build the
2159		/// effective list of delegators with their intended bond amount.
2160		///
2161		/// This will:
2162		/// - if [DelegationChange::Revoke] is outstanding, set the bond amount to 0.
2163		/// - if [DelegationChange::Decrease] is outstanding, subtract the bond by specified amount.
2164		/// - else, do nothing
2165		///
2166		/// The intended bond amounts will be used while calculating rewards.
2167		pub(crate) fn get_rewardable_delegators(collator: &T::AccountId) -> CountedDelegations<T> {
2168			// Aggregate the net effect of all scheduled requests per delegator for this collator.
2169			// If a revoke exists, it dominates and is treated as a full revoke.
2170			// Otherwise, decreases are summed.
2171			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				// Compute in a single pass whether any revoke exists and, if not,
2181				// the total amount of all decreases.
2182				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							// Once a revoke is present, we ignore the accumulated decrease total.
2189							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					// Amount is irrelevant for revokes in this context, since we always
2199					// zero out the bond and account the full previous stake as uncounted.
2200					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		/// This function exists as a helper to delegator_bond_more & auto_compound functionality.
2233		/// Any changes to this function must align with both user-initiated bond increases and
2234		/// auto-compounding bond increases.
2235		/// Any feature-specific preconditions should be validated before this function is invoked.
2236		/// Any feature-specific events must be emitted after this function is invoked.
2237		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			// This helper does not depend on the number of scheduled requests; we pass 0
2252			// here and rely on the extrinsic declaration for an upper bound.
2253			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		/// Mint a specified reward amount to the beneficiary account. Emits the [Rewarded] event.
2265		pub fn mint(amt: BalanceOf<T>, to: &T::AccountId) -> Result<BalanceOf<T>, DispatchError> {
2266			// Mint rewards to the account
2267			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		/// Mint a specified reward amount to the collator's account. Emits the [Rewarded] event.
2276		pub fn mint_collator_reward(
2277			_paid_for_round: RoundIndex,
2278			collator_id: T::AccountId,
2279			amt: BalanceOf<T>,
2280		) -> Weight {
2281			// Mint rewards to the collator
2282			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		/// Mint and compound delegation rewards. The function mints the amount towards the
2294		/// delegator and tries to compound a specified percent of it back towards the delegation.
2295		/// If a scheduled delegation revoke exists, then the amount is only minted, and nothing is
2296		/// compounded. Emits the [Compounded] event.
2297		pub fn mint_and_compound(
2298			amt: BalanceOf<T>,
2299			compound_percent: Percent,
2300			candidate: T::AccountId,
2301			delegator: T::AccountId,
2302		) {
2303			// Mint rewards to the delegator
2304			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		/// Add reward points to block authors:
2335		/// * 20 points to the block producer for producing a block in the chain
2336		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		/// Marks collators as inactive for the previous round if they received zero awarded points.
2345		pub fn mark_collators_as_inactive(cur: RoundIndex) -> Weight {
2346			// This function is called after round index increment,
2347			// We don't need to saturate here because the genesis round is 1.
2348			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		/// Cleans up historical staking information that is older than MaxOfflineRounds
2362		/// by removing entries from the WasIactive storage map.
2363		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}