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	/// Summary of pending delegation actions for a (collator, delegator) pair.
608	///
609	/// Stores `DelegationAction::Revoke(bond)` when a revocation is pending, or
610	/// `DelegationAction::Decrease(total)` with the aggregated sum of all pending
611	/// decrease amounts. Used during round transitions to adjust reward
612	/// calculations without reading the full `DelegationScheduledRequests`.
613	#[pallet::storage]
614	pub(crate) type DelegationScheduledRequestsSummaryMap<T: Config> = StorageDoubleMap<
615		_,
616		Blake2_128Concat,
617		T::AccountId, // collator
618		Blake2_128Concat,
619		T::AccountId, // delegator
620		DelegationAction<BalanceOf<T>>,
621		OptionQuery,
622	>;
623
624	/// Stores auto-compounding configuration per collator.
625	#[pallet::storage]
626	#[pallet::getter(fn auto_compounding_delegations)]
627	pub(crate) type AutoCompoundingDelegations<T: Config> = StorageMap<
628		_,
629		Blake2_128Concat,
630		T::AccountId,
631		BoundedVec<
632			AutoCompoundConfig<T::AccountId>,
633			AddGet<T::MaxTopDelegationsPerCandidate, T::MaxBottomDelegationsPerCandidate>,
634		>,
635		ValueQuery,
636	>;
637
638	#[pallet::storage]
639	#[pallet::getter(fn top_delegations)]
640	/// Top delegations for collator candidate
641	pub(crate) type TopDelegations<T: Config> = StorageMap<
642		_,
643		Twox64Concat,
644		T::AccountId,
645		Delegations<T::AccountId, BalanceOf<T>>,
646		OptionQuery,
647	>;
648
649	#[pallet::storage]
650	#[pallet::getter(fn bottom_delegations)]
651	/// Bottom delegations for collator candidate
652	pub(crate) type BottomDelegations<T: Config> = StorageMap<
653		_,
654		Twox64Concat,
655		T::AccountId,
656		Delegations<T::AccountId, BalanceOf<T>>,
657		OptionQuery,
658	>;
659
660	#[pallet::storage]
661	#[pallet::getter(fn selected_candidates)]
662	/// The collator candidates selected for the current round
663	type SelectedCandidates<T: Config> =
664		StorageValue<_, BoundedVec<T::AccountId, T::MaxCandidates>, ValueQuery>;
665
666	#[pallet::storage]
667	#[pallet::getter(fn total)]
668	/// Total capital locked by this staking pallet
669	pub(crate) type Total<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
670
671	#[pallet::storage]
672	#[pallet::getter(fn candidate_pool)]
673	/// The pool of collator candidates, each with their total backing stake
674	pub(crate) type CandidatePool<T: Config> = StorageValue<
675		_,
676		BoundedOrderedSet<Bond<T::AccountId, BalanceOf<T>>, T::MaxCandidates>,
677		ValueQuery,
678	>;
679
680	#[pallet::storage]
681	#[pallet::getter(fn at_stake)]
682	/// Snapshot of collator delegation stake at the start of the round
683	pub type AtStake<T: Config> = StorageDoubleMap<
684		_,
685		Twox64Concat,
686		RoundIndex,
687		Twox64Concat,
688		T::AccountId,
689		CollatorSnapshot<T::AccountId, BalanceOf<T>>,
690		OptionQuery,
691	>;
692
693	#[pallet::storage]
694	#[pallet::getter(fn was_inactive)]
695	/// Records collators' inactivity.
696	/// Data persists for MaxOfflineRounds + 1 rounds before being pruned.
697	pub type WasInactive<T: Config> =
698		StorageDoubleMap<_, Twox64Concat, RoundIndex, Twox64Concat, T::AccountId, (), OptionQuery>;
699
700	#[pallet::storage]
701	#[pallet::getter(fn delayed_payouts)]
702	/// Delayed payouts
703	pub type DelayedPayouts<T: Config> =
704		StorageMap<_, Twox64Concat, RoundIndex, DelayedPayout<BalanceOf<T>>, OptionQuery>;
705
706	#[pallet::storage]
707	#[pallet::getter(fn inflation_config)]
708	/// Inflation configuration
709	pub type InflationConfig<T: Config> = StorageValue<_, InflationInfo<BalanceOf<T>>, ValueQuery>;
710
711	#[pallet::storage]
712	#[pallet::getter(fn points)]
713	/// Total points awarded to collators for block production in the round
714	pub type Points<T: Config> = StorageMap<_, Twox64Concat, RoundIndex, RewardPoint, ValueQuery>;
715
716	#[pallet::storage]
717	#[pallet::getter(fn awarded_pts)]
718	/// Points for each collator per round
719	pub type AwardedPts<T: Config> = StorageDoubleMap<
720		_,
721		Twox64Concat,
722		RoundIndex,
723		Twox64Concat,
724		T::AccountId,
725		RewardPoint,
726		ValueQuery,
727	>;
728
729	#[pallet::storage]
730	#[pallet::getter(fn marking_offline)]
731	/// Killswitch to enable/disable marking offline feature.
732	pub type EnableMarkingOffline<T: Config> = StorageValue<_, bool, ValueQuery>;
733
734	#[pallet::genesis_config]
735	pub struct GenesisConfig<T: Config> {
736		/// Initialize balance and register all as collators: `(collator AccountId, balance Amount)`
737		pub candidates: Vec<(T::AccountId, BalanceOf<T>)>,
738		/// Initialize balance and make delegations:
739		/// `(delegator AccountId, collator AccountId, delegation Amount, auto-compounding Percent)`
740		pub delegations: Vec<(T::AccountId, T::AccountId, BalanceOf<T>, Percent)>,
741		/// Inflation configuration
742		pub inflation_config: InflationInfo<BalanceOf<T>>,
743		/// Default fixed percent a collator takes off the top of due rewards
744		pub collator_commission: Perbill,
745		/// Default percent of inflation set aside for parachain bond every round
746		pub parachain_bond_reserve_percent: Percent,
747		/// Default number of blocks in a round
748		pub blocks_per_round: u32,
749		/// Number of selected candidates every round. Cannot be lower than MinSelectedCandidates
750		pub num_selected_candidates: u32,
751	}
752
753	impl<T: Config> Default for GenesisConfig<T> {
754		fn default() -> Self {
755			Self {
756				candidates: vec![],
757				delegations: vec![],
758				inflation_config: Default::default(),
759				collator_commission: Default::default(),
760				parachain_bond_reserve_percent: Default::default(),
761				blocks_per_round: 1u32,
762				num_selected_candidates: T::MinSelectedCandidates::get(),
763			}
764		}
765	}
766
767	#[pallet::genesis_build]
768	impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
769		fn build(&self) {
770			assert!(self.blocks_per_round > 0, "Blocks per round must be > 0");
771			<InflationConfig<T>>::put(self.inflation_config.clone());
772			let mut candidate_count = 0u32;
773			// Initialize the candidates
774			for &(ref candidate, balance) in &self.candidates {
775				assert!(
776					<Pallet<T>>::get_collator_stakable_free_balance(candidate) >= balance,
777					"Account does not have enough balance to bond as a candidate."
778				);
779				if let Err(error) = <Pallet<T>>::join_candidates(
780					T::RuntimeOrigin::from(Some(candidate.clone()).into()),
781					balance,
782					candidate_count,
783				) {
784					log::warn!("Join candidates failed in genesis with error {:?}", error);
785				} else {
786					candidate_count = candidate_count.saturating_add(1u32);
787				}
788			}
789
790			let mut col_delegator_count: BTreeMap<T::AccountId, u32> = BTreeMap::new();
791			let mut col_auto_compound_delegator_count: BTreeMap<T::AccountId, u32> =
792				BTreeMap::new();
793			let mut del_delegation_count: BTreeMap<T::AccountId, u32> = BTreeMap::new();
794			// Initialize the delegations
795			for &(ref delegator, ref target, balance, auto_compound) in &self.delegations {
796				assert!(
797					<Pallet<T>>::get_delegator_stakable_balance(delegator) >= balance,
798					"Account does not have enough balance to place delegation."
799				);
800				let cd_count = if let Some(x) = col_delegator_count.get(target) {
801					*x
802				} else {
803					0u32
804				};
805				let dd_count = if let Some(x) = del_delegation_count.get(delegator) {
806					*x
807				} else {
808					0u32
809				};
810				let cd_auto_compound_count = col_auto_compound_delegator_count
811					.get(target)
812					.cloned()
813					.unwrap_or_default();
814				if let Err(error) = <Pallet<T>>::delegate_with_auto_compound(
815					T::RuntimeOrigin::from(Some(delegator.clone()).into()),
816					target.clone(),
817					balance,
818					auto_compound,
819					cd_count,
820					cd_auto_compound_count,
821					dd_count,
822				) {
823					log::warn!("Delegate failed in genesis with error {:?}", error);
824				} else {
825					if let Some(x) = col_delegator_count.get_mut(target) {
826						*x = x.saturating_add(1u32);
827					} else {
828						col_delegator_count.insert(target.clone(), 1u32);
829					};
830					if let Some(x) = del_delegation_count.get_mut(delegator) {
831						*x = x.saturating_add(1u32);
832					} else {
833						del_delegation_count.insert(delegator.clone(), 1u32);
834					};
835					if !auto_compound.is_zero() {
836						col_auto_compound_delegator_count
837							.entry(target.clone())
838							.and_modify(|x| *x = x.saturating_add(1))
839							.or_insert(1);
840					}
841				}
842			}
843			// Set collator commission to default config
844			<CollatorCommission<T>>::put(self.collator_commission);
845			// Set parachain bond config to default config
846			let pbr = InflationDistributionAccount {
847				// must be set soon; if not => due inflation will be sent to collators/delegators
848				account: T::AccountId::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes())
849					.expect("infinite length input; no invalid inputs for type; qed"),
850				percent: self.parachain_bond_reserve_percent,
851			};
852			let zeroed_account = InflationDistributionAccount {
853				account: T::AccountId::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes())
854					.expect("infinite length input; no invalid inputs for type; qed"),
855				percent: Percent::zero(),
856			};
857			<InflationDistributionInfo<T>>::put::<InflationDistributionConfig<T::AccountId>>(
858				[pbr, zeroed_account].into(),
859			);
860			// Set total selected candidates to value from config
861			assert!(
862				self.num_selected_candidates >= T::MinSelectedCandidates::get(),
863				"{:?}",
864				Error::<T>::CannotSetBelowMin
865			);
866			assert!(
867				self.num_selected_candidates <= T::MaxCandidates::get(),
868				"{:?}",
869				Error::<T>::CannotSetAboveMaxCandidates
870			);
871			<TotalSelected<T>>::put(self.num_selected_candidates);
872			// Choose top TotalSelected collator candidates
873			let (_, v_count, _, total_staked) = <Pallet<T>>::select_top_candidates(1u32);
874			// Start Round 1 at Block 0
875			let round: RoundInfo<BlockNumberFor<T>> =
876				RoundInfo::new(1u32, Zero::zero(), self.blocks_per_round, 0);
877			<Round<T>>::put(round);
878			<Pallet<T>>::deposit_event(Event::NewRound {
879				starting_block: Zero::zero(),
880				round: 1u32,
881				selected_collators_number: v_count,
882				total_balance: total_staked,
883			});
884		}
885	}
886
887	#[pallet::call]
888	impl<T: Config> Pallet<T> {
889		/// Set the expectations for total staked. These expectations determine the issuance for
890		/// the round according to logic in `fn compute_issuance`
891		#[pallet::call_index(0)]
892		#[pallet::weight(<T as Config>::WeightInfo::set_staking_expectations())]
893		pub fn set_staking_expectations(
894			origin: OriginFor<T>,
895			expectations: Range<BalanceOf<T>>,
896		) -> DispatchResultWithPostInfo {
897			T::MonetaryGovernanceOrigin::ensure_origin(origin)?;
898			ensure!(expectations.is_valid(), Error::<T>::InvalidSchedule);
899			let mut config = <InflationConfig<T>>::get();
900			ensure!(
901				config.expect != expectations,
902				Error::<T>::NoWritingSameValue
903			);
904			config.set_expectations(expectations);
905			Self::deposit_event(Event::StakeExpectationsSet {
906				expect_min: config.expect.min,
907				expect_ideal: config.expect.ideal,
908				expect_max: config.expect.max,
909			});
910			<InflationConfig<T>>::put(config);
911			Ok(().into())
912		}
913
914		/// Set the annual inflation rate to derive per-round inflation
915		#[pallet::call_index(1)]
916		#[pallet::weight(<T as Config>::WeightInfo::set_inflation())]
917		pub fn set_inflation(
918			origin: OriginFor<T>,
919			schedule: Range<Perbill>,
920		) -> DispatchResultWithPostInfo {
921			T::MonetaryGovernanceOrigin::ensure_origin(origin)?;
922			ensure!(schedule.is_valid(), Error::<T>::InvalidSchedule);
923			let mut config = <InflationConfig<T>>::get();
924			ensure!(config.annual != schedule, Error::<T>::NoWritingSameValue);
925			config.annual = schedule;
926			config.set_round_from_annual::<T>(schedule);
927			Self::deposit_event(Event::InflationSet {
928				annual_min: config.annual.min,
929				annual_ideal: config.annual.ideal,
930				annual_max: config.annual.max,
931				round_min: config.round.min,
932				round_ideal: config.round.ideal,
933				round_max: config.round.max,
934			});
935			<InflationConfig<T>>::put(config);
936			Ok(().into())
937		}
938
939		/// Set the total number of collator candidates selected per round
940		/// - changes are not applied until the start of the next round
941		#[pallet::call_index(4)]
942		#[pallet::weight(<T as Config>::WeightInfo::set_total_selected())]
943		pub fn set_total_selected(origin: OriginFor<T>, new: u32) -> DispatchResultWithPostInfo {
944			frame_system::ensure_root(origin)?;
945			ensure!(
946				new >= T::MinSelectedCandidates::get(),
947				Error::<T>::CannotSetBelowMin
948			);
949			ensure!(
950				new <= T::MaxCandidates::get(),
951				Error::<T>::CannotSetAboveMaxCandidates
952			);
953			let old = <TotalSelected<T>>::get();
954			ensure!(old != new, Error::<T>::NoWritingSameValue);
955			ensure!(
956				new < <Round<T>>::get().length,
957				Error::<T>::RoundLengthMustBeGreaterThanTotalSelectedCollators,
958			);
959			<TotalSelected<T>>::put(new);
960			Self::deposit_event(Event::TotalSelectedSet { old, new });
961			Ok(().into())
962		}
963
964		/// Set the commission for all collators
965		#[pallet::call_index(5)]
966		#[pallet::weight(<T as Config>::WeightInfo::set_collator_commission())]
967		pub fn set_collator_commission(
968			origin: OriginFor<T>,
969			new: Perbill,
970		) -> DispatchResultWithPostInfo {
971			frame_system::ensure_root(origin)?;
972			let old = <CollatorCommission<T>>::get();
973			ensure!(old != new, Error::<T>::NoWritingSameValue);
974			<CollatorCommission<T>>::put(new);
975			Self::deposit_event(Event::CollatorCommissionSet { old, new });
976			Ok(().into())
977		}
978
979		/// Set blocks per round
980		/// - if called with `new` less than length of current round, will transition immediately
981		/// in the next block
982		/// - also updates per-round inflation config
983		#[pallet::call_index(6)]
984		#[pallet::weight(<T as Config>::WeightInfo::set_blocks_per_round())]
985		pub fn set_blocks_per_round(origin: OriginFor<T>, new: u32) -> DispatchResultWithPostInfo {
986			frame_system::ensure_root(origin)?;
987			ensure!(
988				new >= T::MinBlocksPerRound::get(),
989				Error::<T>::CannotSetBelowMin
990			);
991			let mut round = <Round<T>>::get();
992			let (now, first, old) = (round.current, round.first, round.length);
993			ensure!(old != new, Error::<T>::NoWritingSameValue);
994			ensure!(
995				new > <TotalSelected<T>>::get(),
996				Error::<T>::RoundLengthMustBeGreaterThanTotalSelectedCollators,
997			);
998			round.length = new;
999			// update per-round inflation given new rounds per year
1000			let mut inflation_config = <InflationConfig<T>>::get();
1001			inflation_config.reset_round::<T>(new);
1002			<Round<T>>::put(round);
1003			Self::deposit_event(Event::BlocksPerRoundSet {
1004				current_round: now,
1005				first_block: first,
1006				old: old,
1007				new: new,
1008				new_per_round_inflation_min: inflation_config.round.min,
1009				new_per_round_inflation_ideal: inflation_config.round.ideal,
1010				new_per_round_inflation_max: inflation_config.round.max,
1011			});
1012			<InflationConfig<T>>::put(inflation_config);
1013			Ok(().into())
1014		}
1015
1016		/// Join the set of collator candidates
1017		#[pallet::call_index(7)]
1018		#[pallet::weight(<T as Config>::WeightInfo::join_candidates(*candidate_count))]
1019		pub fn join_candidates(
1020			origin: OriginFor<T>,
1021			bond: BalanceOf<T>,
1022			candidate_count: u32,
1023		) -> DispatchResultWithPostInfo {
1024			let acc = ensure_signed(origin)?;
1025			ensure!(
1026				bond >= T::MinCandidateStk::get(),
1027				Error::<T>::CandidateBondBelowMin
1028			);
1029			Self::join_candidates_inner(acc, bond, candidate_count)
1030		}
1031
1032		/// Request to leave the set of candidates. If successful, the account is immediately
1033		/// removed from the candidate pool to prevent selection as a collator.
1034		#[pallet::call_index(8)]
1035		#[pallet::weight(<T as Config>::WeightInfo::schedule_leave_candidates(*candidate_count))]
1036		pub fn schedule_leave_candidates(
1037			origin: OriginFor<T>,
1038			candidate_count: u32,
1039		) -> DispatchResultWithPostInfo {
1040			let collator = ensure_signed(origin)?;
1041			let mut state = <CandidateInfo<T>>::get(&collator).ok_or(Error::<T>::CandidateDNE)?;
1042			let (now, when) = state.schedule_leave::<T>()?;
1043			let mut candidates = <CandidatePool<T>>::get();
1044			ensure!(
1045				candidate_count >= candidates.0.len() as u32,
1046				Error::<T>::TooLowCandidateCountToLeaveCandidates
1047			);
1048			if candidates.remove(&Bond::from_owner(collator.clone())) {
1049				<CandidatePool<T>>::put(candidates);
1050			}
1051			<CandidateInfo<T>>::insert(&collator, state);
1052			Self::deposit_event(Event::CandidateScheduledExit {
1053				exit_allowed_round: now,
1054				candidate: collator,
1055				scheduled_exit: when,
1056			});
1057			Ok(().into())
1058		}
1059
1060		/// Execute leave candidates request
1061		#[pallet::call_index(9)]
1062		#[pallet::weight(
1063			<T as Config>::WeightInfo::execute_leave_candidates_worst_case(*candidate_delegation_count)
1064		)]
1065		pub fn execute_leave_candidates(
1066			origin: OriginFor<T>,
1067			candidate: T::AccountId,
1068			candidate_delegation_count: u32,
1069		) -> DispatchResultWithPostInfo {
1070			ensure_signed(origin)?;
1071			let state = <CandidateInfo<T>>::get(&candidate).ok_or(Error::<T>::CandidateDNE)?;
1072			ensure!(
1073				state.delegation_count <= candidate_delegation_count,
1074				Error::<T>::TooLowCandidateDelegationCountToLeaveCandidates
1075			);
1076			<Pallet<T>>::execute_leave_candidates_inner(candidate)
1077		}
1078
1079		/// Cancel open request to leave candidates
1080		/// - only callable by collator account
1081		/// - result upon successful call is the candidate is active in the candidate pool
1082		#[pallet::call_index(10)]
1083		#[pallet::weight(<T as Config>::WeightInfo::cancel_leave_candidates(*candidate_count))]
1084		pub fn cancel_leave_candidates(
1085			origin: OriginFor<T>,
1086			candidate_count: u32,
1087		) -> DispatchResultWithPostInfo {
1088			let collator = ensure_signed(origin)?;
1089			let mut state = <CandidateInfo<T>>::get(&collator).ok_or(Error::<T>::CandidateDNE)?;
1090			ensure!(state.is_leaving(), Error::<T>::CandidateNotLeaving);
1091			state.go_online();
1092			let mut candidates = <CandidatePool<T>>::get();
1093			ensure!(
1094				candidates.0.len() as u32 <= candidate_count,
1095				Error::<T>::TooLowCandidateCountWeightHintCancelLeaveCandidates
1096			);
1097			let maybe_inserted_candidate = candidates
1098				.try_insert(Bond {
1099					owner: collator.clone(),
1100					amount: state.total_counted,
1101				})
1102				.map_err(|_| Error::<T>::CandidateLimitReached)?;
1103			ensure!(maybe_inserted_candidate, Error::<T>::AlreadyActive);
1104			<CandidatePool<T>>::put(candidates);
1105			<CandidateInfo<T>>::insert(&collator, state);
1106			Self::deposit_event(Event::CancelledCandidateExit {
1107				candidate: collator,
1108			});
1109			Ok(().into())
1110		}
1111
1112		/// Temporarily leave the set of collator candidates without unbonding
1113		#[pallet::call_index(11)]
1114		#[pallet::weight(<T as Config>::WeightInfo::go_offline(MAX_CANDIDATES))]
1115		pub fn go_offline(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
1116			let collator = ensure_signed(origin)?;
1117			<Pallet<T>>::go_offline_inner(collator)
1118		}
1119
1120		/// Rejoin the set of collator candidates if previously had called `go_offline`
1121		#[pallet::call_index(12)]
1122		#[pallet::weight(<T as Config>::WeightInfo::go_online(MAX_CANDIDATES))]
1123		pub fn go_online(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
1124			let collator = ensure_signed(origin)?;
1125			<Pallet<T>>::go_online_inner(collator)
1126		}
1127
1128		/// Increase collator candidate self bond by `more`
1129		#[pallet::call_index(13)]
1130		#[pallet::weight(<T as Config>::WeightInfo::candidate_bond_more(MAX_CANDIDATES))]
1131		pub fn candidate_bond_more(
1132			origin: OriginFor<T>,
1133			more: BalanceOf<T>,
1134		) -> DispatchResultWithPostInfo {
1135			let candidate = ensure_signed(origin)?;
1136			<Pallet<T>>::candidate_bond_more_inner(candidate, more)
1137		}
1138
1139		/// Request by collator candidate to decrease self bond by `less`
1140		#[pallet::call_index(14)]
1141		#[pallet::weight(<T as Config>::WeightInfo::schedule_candidate_bond_less())]
1142		pub fn schedule_candidate_bond_less(
1143			origin: OriginFor<T>,
1144			less: BalanceOf<T>,
1145		) -> DispatchResultWithPostInfo {
1146			let collator = ensure_signed(origin)?;
1147			let mut state = <CandidateInfo<T>>::get(&collator).ok_or(Error::<T>::CandidateDNE)?;
1148			let when = state.schedule_bond_less::<T>(less)?;
1149			<CandidateInfo<T>>::insert(&collator, state);
1150			Self::deposit_event(Event::CandidateBondLessRequested {
1151				candidate: collator,
1152				amount_to_decrease: less,
1153				execute_round: when,
1154			});
1155			Ok(().into())
1156		}
1157
1158		/// Execute pending request to adjust the collator candidate self bond
1159		#[pallet::call_index(15)]
1160		#[pallet::weight(<T as Config>::WeightInfo::execute_candidate_bond_less(MAX_CANDIDATES))]
1161		pub fn execute_candidate_bond_less(
1162			origin: OriginFor<T>,
1163			candidate: T::AccountId,
1164		) -> DispatchResultWithPostInfo {
1165			ensure_signed(origin)?; // we may want to reward this if caller != candidate
1166			<Pallet<T>>::execute_candidate_bond_less_inner(candidate)
1167		}
1168
1169		/// Cancel pending request to adjust the collator candidate self bond
1170		#[pallet::call_index(16)]
1171		#[pallet::weight(<T as Config>::WeightInfo::cancel_candidate_bond_less())]
1172		pub fn cancel_candidate_bond_less(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
1173			let collator = ensure_signed(origin)?;
1174			let mut state = <CandidateInfo<T>>::get(&collator).ok_or(Error::<T>::CandidateDNE)?;
1175			state.cancel_bond_less::<T>(collator.clone())?;
1176			<CandidateInfo<T>>::insert(&collator, state);
1177			Ok(().into())
1178		}
1179
1180		/// If caller is not a delegator and not a collator, then join the set of delegators
1181		/// If caller is a delegator, then makes delegation to change their delegation state
1182		/// Sets the auto-compound config for the delegation
1183		#[pallet::call_index(18)]
1184		#[pallet::weight(
1185			<T as Config>::WeightInfo::delegate_with_auto_compound(
1186				*candidate_delegation_count,
1187				*candidate_auto_compounding_delegation_count,
1188				*delegation_count,
1189			)
1190		)]
1191		pub fn delegate_with_auto_compound(
1192			origin: OriginFor<T>,
1193			candidate: T::AccountId,
1194			amount: BalanceOf<T>,
1195			auto_compound: Percent,
1196			candidate_delegation_count: u32,
1197			candidate_auto_compounding_delegation_count: u32,
1198			delegation_count: u32,
1199		) -> DispatchResultWithPostInfo {
1200			let delegator = ensure_signed(origin)?;
1201			<AutoCompoundDelegations<T>>::delegate_with_auto_compound(
1202				candidate,
1203				delegator,
1204				amount,
1205				auto_compound,
1206				candidate_delegation_count,
1207				candidate_auto_compounding_delegation_count,
1208				delegation_count,
1209			)
1210		}
1211
1212		/// Request to revoke an existing delegation. If successful, the delegation is scheduled
1213		/// to be allowed to be revoked via the `execute_delegation_request` extrinsic.
1214		/// The delegation receives no rewards for the rounds while a revoke is pending.
1215		/// A revoke may not be performed if any other scheduled request is pending.
1216		#[pallet::call_index(22)]
1217		#[pallet::weight(<T as Config>::WeightInfo::schedule_revoke_delegation(
1218			T::MaxTopDelegationsPerCandidate::get() + T::MaxBottomDelegationsPerCandidate::get()
1219		))]
1220		pub fn schedule_revoke_delegation(
1221			origin: OriginFor<T>,
1222			collator: T::AccountId,
1223		) -> DispatchResultWithPostInfo {
1224			let delegator = ensure_signed(origin)?;
1225			Self::delegation_schedule_revoke(collator, delegator)
1226		}
1227
1228		/// Bond more for delegators wrt a specific collator candidate.
1229		#[pallet::call_index(23)]
1230		#[pallet::weight(<T as Config>::WeightInfo::delegator_bond_more(
1231			T::MaxTopDelegationsPerCandidate::get() + T::MaxBottomDelegationsPerCandidate::get()
1232		))]
1233		pub fn delegator_bond_more(
1234			origin: OriginFor<T>,
1235			candidate: T::AccountId,
1236			more: BalanceOf<T>,
1237		) -> DispatchResultWithPostInfo {
1238			let delegator = ensure_signed(origin)?;
1239			let (in_top, weight) = Self::delegation_bond_more_without_event(
1240				delegator.clone(),
1241				candidate.clone(),
1242				more.clone(),
1243			)?;
1244			Pallet::<T>::deposit_event(Event::DelegationIncreased {
1245				delegator,
1246				candidate,
1247				amount: more,
1248				in_top,
1249			});
1250
1251			Ok(Some(weight).into())
1252		}
1253
1254		/// Request bond less for delegators wrt a specific collator candidate. The delegation's
1255		/// rewards for rounds while the request is pending use the reduced bonded amount.
1256		/// A bond less may not be performed if a revoke request is pending for the same delegation.
1257		#[pallet::call_index(24)]
1258		#[pallet::weight(<T as Config>::WeightInfo::schedule_delegator_bond_less(
1259			T::MaxTopDelegationsPerCandidate::get() + T::MaxBottomDelegationsPerCandidate::get()
1260		))]
1261		pub fn schedule_delegator_bond_less(
1262			origin: OriginFor<T>,
1263			candidate: T::AccountId,
1264			less: BalanceOf<T>,
1265		) -> DispatchResultWithPostInfo {
1266			let delegator = ensure_signed(origin)?;
1267			Self::delegation_schedule_bond_decrease(candidate, delegator, less)
1268		}
1269
1270		/// Execute pending request to change an existing delegation
1271		#[pallet::call_index(25)]
1272		#[pallet::weight(<T as Config>::WeightInfo::execute_delegator_revoke_delegation_worst())]
1273		pub fn execute_delegation_request(
1274			origin: OriginFor<T>,
1275			delegator: T::AccountId,
1276			candidate: T::AccountId,
1277		) -> DispatchResultWithPostInfo {
1278			ensure_signed(origin)?; // we may want to reward caller if caller != delegator
1279			Self::delegation_execute_scheduled_request(candidate, delegator)
1280		}
1281
1282		/// Cancel request to change an existing delegation.
1283		#[pallet::call_index(26)]
1284		#[pallet::weight(<T as Config>::WeightInfo::cancel_delegation_request(350))]
1285		pub fn cancel_delegation_request(
1286			origin: OriginFor<T>,
1287			candidate: T::AccountId,
1288		) -> DispatchResultWithPostInfo {
1289			let delegator = ensure_signed(origin)?;
1290			Self::delegation_cancel_request(candidate, delegator)
1291		}
1292
1293		/// Sets the auto-compounding reward percentage for a delegation.
1294		#[pallet::call_index(27)]
1295		#[pallet::weight(<T as Config>::WeightInfo::set_auto_compound(
1296			*candidate_auto_compounding_delegation_count_hint,
1297			*delegation_count_hint,
1298		))]
1299		pub fn set_auto_compound(
1300			origin: OriginFor<T>,
1301			candidate: T::AccountId,
1302			value: Percent,
1303			candidate_auto_compounding_delegation_count_hint: u32,
1304			delegation_count_hint: u32,
1305		) -> DispatchResultWithPostInfo {
1306			let delegator = ensure_signed(origin)?;
1307			<AutoCompoundDelegations<T>>::set_auto_compound(
1308				candidate,
1309				delegator,
1310				value,
1311				candidate_auto_compounding_delegation_count_hint,
1312				delegation_count_hint,
1313			)
1314		}
1315
1316		/// Notify a collator is inactive during MaxOfflineRounds
1317		#[pallet::call_index(29)]
1318		#[pallet::weight(<T as Config>::WeightInfo::notify_inactive_collator())]
1319		pub fn notify_inactive_collator(
1320			origin: OriginFor<T>,
1321			collator: T::AccountId,
1322		) -> DispatchResult {
1323			ensure!(
1324				<EnableMarkingOffline<T>>::get(),
1325				<Error<T>>::MarkingOfflineNotEnabled
1326			);
1327			ensure_signed(origin)?;
1328
1329			let mut collators_len = 0usize;
1330			let max_collators = <TotalSelected<T>>::get();
1331
1332			if let Some(len) = <SelectedCandidates<T>>::decode_len() {
1333				collators_len = len;
1334			};
1335
1336			// Check collators length is not below or eq to 66% of max_collators.
1337			// We use saturating logic here with (2/3)
1338			// as it is dangerous to use floating point numbers directly.
1339			ensure!(
1340				collators_len * 3 > (max_collators * 2) as usize,
1341				<Error<T>>::TooLowCollatorCountToNotifyAsInactive
1342			);
1343
1344			let round_info = <Round<T>>::get();
1345			let max_offline_rounds = T::MaxOfflineRounds::get();
1346
1347			ensure!(
1348				round_info.current > max_offline_rounds,
1349				<Error<T>>::CurrentRoundTooLow
1350			);
1351
1352			// Have rounds_to_check = [8,9]
1353			// in case we are in round 10 for instance
1354			// with MaxOfflineRounds = 2
1355			let first_round_to_check = round_info.current.saturating_sub(max_offline_rounds);
1356			let rounds_to_check = first_round_to_check..round_info.current;
1357
1358			// If this counter is eq to max_offline_rounds,
1359			// the collator should be notified as inactive
1360			let mut inactive_counter: RoundIndex = 0u32;
1361
1362			// Iter rounds and check whether the collator has been inactive
1363			for r in rounds_to_check {
1364				if <WasInactive<T>>::get(r, &collator).is_some() {
1365					inactive_counter = inactive_counter.saturating_add(1);
1366				}
1367			}
1368
1369			if inactive_counter == max_offline_rounds {
1370				let _ = T::OnInactiveCollator::on_inactive_collator(
1371					collator.clone(),
1372					round_info.current.saturating_sub(1),
1373				);
1374			} else {
1375				return Err(<Error<T>>::CannotBeNotifiedAsInactive.into());
1376			}
1377
1378			Ok(().into())
1379		}
1380
1381		/// Enable/Disable marking offline feature
1382		#[pallet::call_index(30)]
1383		#[pallet::weight(
1384			Weight::from_parts(3_000_000u64, 4_000u64)
1385				.saturating_add(T::DbWeight::get().writes(1u64))
1386		)]
1387		pub fn enable_marking_offline(origin: OriginFor<T>, value: bool) -> DispatchResult {
1388			ensure_root(origin)?;
1389			<EnableMarkingOffline<T>>::set(value);
1390			Ok(())
1391		}
1392
1393		/// Force join the set of collator candidates.
1394		/// It will skip the minimum required bond check.
1395		#[pallet::call_index(31)]
1396		#[pallet::weight(<T as Config>::WeightInfo::join_candidates(*candidate_count))]
1397		pub fn force_join_candidates(
1398			origin: OriginFor<T>,
1399			account: T::AccountId,
1400			bond: BalanceOf<T>,
1401			candidate_count: u32,
1402		) -> DispatchResultWithPostInfo {
1403			T::MonetaryGovernanceOrigin::ensure_origin(origin.clone())?;
1404			Self::join_candidates_inner(account, bond, candidate_count)
1405		}
1406
1407		/// Set the inflation distribution configuration.
1408		#[pallet::call_index(32)]
1409		#[pallet::weight(<T as Config>::WeightInfo::set_inflation_distribution_config())]
1410		pub fn set_inflation_distribution_config(
1411			origin: OriginFor<T>,
1412			new: InflationDistributionConfig<T::AccountId>,
1413		) -> DispatchResultWithPostInfo {
1414			T::MonetaryGovernanceOrigin::ensure_origin(origin)?;
1415			let old = <InflationDistributionInfo<T>>::get().0;
1416			let new = new.0;
1417			ensure!(old != new, Error::<T>::NoWritingSameValue);
1418			let total_percent = new.iter().fold(0, |acc, x| acc + x.percent.deconstruct());
1419			ensure!(
1420				total_percent <= 100,
1421				Error::<T>::TotalInflationDistributionPercentExceeds100,
1422			);
1423			<InflationDistributionInfo<T>>::put::<InflationDistributionConfig<T::AccountId>>(
1424				new.clone().into(),
1425			);
1426			Self::deposit_event(Event::InflationDistributionConfigUpdated {
1427				old: old.into(),
1428				new: new.into(),
1429			});
1430			Ok(().into())
1431		}
1432	}
1433
1434	/// Represents a payout made via `pay_one_collator_reward`.
1435	pub(crate) enum RewardPayment {
1436		/// A collator was paid
1437		Paid,
1438		/// A collator was skipped for payment. This can happen if they haven't been awarded any
1439		/// points, that is, they did not produce any blocks.
1440		Skipped,
1441		/// All collator payments have been processed.
1442		Finished,
1443	}
1444
1445	impl<T: Config> Pallet<T> {
1446		/// Set freeze
1447		///
1448		/// `is_collator` determines whether the account is a collator or delegator
1449		pub(crate) fn freeze_extended(
1450			account: &T::AccountId,
1451			amount: BalanceOf<T>,
1452			is_collator: bool,
1453		) -> DispatchResult {
1454			use frame_support::traits::fungible::MutateFreeze;
1455
1456			// Now set the freeze
1457			let freeze_reason = if is_collator {
1458				FreezeReason::StakingCollator
1459			} else {
1460				FreezeReason::StakingDelegator
1461			};
1462
1463			T::Currency::set_freeze(&freeze_reason.into(), account, amount)
1464		}
1465
1466		/// `is_collator` determines whether the account is a collator or delegator
1467		pub(crate) fn thaw_extended(account: &T::AccountId, is_collator: bool) -> DispatchResult {
1468			use frame_support::traits::fungible::MutateFreeze;
1469
1470			// Now thaw the freeze
1471			let freeze_reason = if is_collator {
1472				FreezeReason::StakingCollator
1473			} else {
1474				FreezeReason::StakingDelegator
1475			};
1476
1477			T::Currency::thaw(&freeze_reason.into(), account)
1478		}
1479
1480		/// Get frozen balance
1481		///
1482		/// `is_collator` determines whether the account is a collator or delegator
1483		pub(crate) fn balance_frozen_extended(
1484			account: &T::AccountId,
1485			is_collator: bool,
1486		) -> Option<BalanceOf<T>> {
1487			// Now return the frozen balance
1488			if is_collator {
1489				<CandidateInfo<T>>::get(account).map(|info| info.bond)
1490			} else {
1491				<DelegatorState<T>>::get(account).map(|state| state.total)
1492			}
1493		}
1494
1495		pub fn is_delegator(acc: &T::AccountId) -> bool {
1496			<DelegatorState<T>>::get(acc).is_some()
1497		}
1498
1499		pub fn is_candidate(acc: &T::AccountId) -> bool {
1500			<CandidateInfo<T>>::get(acc).is_some()
1501		}
1502
1503		pub fn is_selected_candidate(acc: &T::AccountId) -> bool {
1504			<SelectedCandidates<T>>::get().binary_search(acc).is_ok()
1505		}
1506
1507		pub fn join_candidates_inner(
1508			acc: T::AccountId,
1509			bond: BalanceOf<T>,
1510			candidate_count: u32,
1511		) -> DispatchResultWithPostInfo {
1512			ensure!(!Self::is_candidate(&acc), Error::<T>::CandidateExists);
1513			ensure!(!Self::is_delegator(&acc), Error::<T>::DelegatorExists);
1514			let mut candidates = <CandidatePool<T>>::get();
1515			let old_count = candidates.0.len() as u32;
1516			ensure!(
1517				candidate_count >= old_count,
1518				Error::<T>::TooLowCandidateCountWeightHintJoinCandidates
1519			);
1520			let maybe_inserted_candidate = candidates
1521				.try_insert(Bond {
1522					owner: acc.clone(),
1523					amount: bond,
1524				})
1525				.map_err(|_| Error::<T>::CandidateLimitReached)?;
1526			ensure!(maybe_inserted_candidate, Error::<T>::CandidateExists);
1527
1528			ensure!(
1529				Self::get_collator_stakable_free_balance(&acc) >= bond,
1530				Error::<T>::InsufficientBalance,
1531			);
1532			Self::freeze_extended(&acc, bond, true).map_err(|_| Error::<T>::InsufficientBalance)?;
1533			let candidate = CandidateMetadata::new(bond);
1534			<CandidateInfo<T>>::insert(&acc, candidate);
1535			let empty_delegations: Delegations<T::AccountId, BalanceOf<T>> = Default::default();
1536			// insert empty top delegations
1537			<TopDelegations<T>>::insert(&acc, empty_delegations.clone());
1538			// insert empty bottom delegations
1539			<BottomDelegations<T>>::insert(&acc, empty_delegations);
1540			<CandidatePool<T>>::put(candidates);
1541			let new_total = <Total<T>>::get().saturating_add(bond);
1542			<Total<T>>::put(new_total);
1543			Self::deposit_event(Event::JoinedCollatorCandidates {
1544				account: acc,
1545				amount_locked: bond,
1546				new_total_amt_locked: new_total,
1547			});
1548			Ok(().into())
1549		}
1550
1551		pub fn go_offline_inner(collator: T::AccountId) -> DispatchResultWithPostInfo {
1552			let mut state = <CandidateInfo<T>>::get(&collator).ok_or(Error::<T>::CandidateDNE)?;
1553			let mut candidates = <CandidatePool<T>>::get();
1554			let actual_weight = <T as Config>::WeightInfo::go_offline(candidates.0.len() as u32);
1555
1556			ensure!(
1557				state.is_active(),
1558				DispatchErrorWithPostInfo {
1559					post_info: Some(actual_weight).into(),
1560					error: <Error<T>>::AlreadyOffline.into(),
1561				}
1562			);
1563			state.go_offline();
1564
1565			if candidates.remove(&Bond::from_owner(collator.clone())) {
1566				<CandidatePool<T>>::put(candidates);
1567			}
1568			<CandidateInfo<T>>::insert(&collator, state);
1569			Self::deposit_event(Event::CandidateWentOffline {
1570				candidate: collator,
1571			});
1572			Ok(Some(actual_weight).into())
1573		}
1574
1575		pub fn go_online_inner(collator: T::AccountId) -> DispatchResultWithPostInfo {
1576			let mut state = <CandidateInfo<T>>::get(&collator).ok_or(Error::<T>::CandidateDNE)?;
1577			let mut candidates = <CandidatePool<T>>::get();
1578			let actual_weight = <T as Config>::WeightInfo::go_online(candidates.0.len() as u32);
1579
1580			ensure!(
1581				!state.is_active(),
1582				DispatchErrorWithPostInfo {
1583					post_info: Some(actual_weight).into(),
1584					error: <Error<T>>::AlreadyActive.into(),
1585				}
1586			);
1587			ensure!(
1588				!state.is_leaving(),
1589				DispatchErrorWithPostInfo {
1590					post_info: Some(actual_weight).into(),
1591					error: <Error<T>>::CannotGoOnlineIfLeaving.into(),
1592				}
1593			);
1594			state.go_online();
1595
1596			let maybe_inserted_candidate = candidates
1597				.try_insert(Bond {
1598					owner: collator.clone(),
1599					amount: state.total_counted,
1600				})
1601				.map_err(|_| Error::<T>::CandidateLimitReached)?;
1602			ensure!(
1603				maybe_inserted_candidate,
1604				DispatchErrorWithPostInfo {
1605					post_info: Some(actual_weight).into(),
1606					error: <Error<T>>::AlreadyActive.into(),
1607				},
1608			);
1609
1610			<CandidatePool<T>>::put(candidates);
1611			<CandidateInfo<T>>::insert(&collator, state);
1612			Self::deposit_event(Event::CandidateBackOnline {
1613				candidate: collator,
1614			});
1615			Ok(Some(actual_weight).into())
1616		}
1617
1618		pub fn candidate_bond_more_inner(
1619			collator: T::AccountId,
1620			more: BalanceOf<T>,
1621		) -> DispatchResultWithPostInfo {
1622			let mut state = <CandidateInfo<T>>::get(&collator).ok_or(Error::<T>::CandidateDNE)?;
1623			let actual_weight =
1624				<T as Config>::WeightInfo::candidate_bond_more(T::MaxCandidates::get());
1625
1626			state
1627				.bond_more::<T>(collator.clone(), more)
1628				.map_err(|err| DispatchErrorWithPostInfo {
1629					post_info: Some(actual_weight).into(),
1630					error: err,
1631				})?;
1632			let (is_active, total_counted) = (state.is_active(), state.total_counted);
1633			<CandidateInfo<T>>::insert(&collator, state);
1634			if is_active {
1635				Self::update_active(collator, total_counted);
1636			}
1637			Ok(Some(actual_weight).into())
1638		}
1639
1640		pub fn execute_candidate_bond_less_inner(
1641			candidate: T::AccountId,
1642		) -> DispatchResultWithPostInfo {
1643			let mut state = <CandidateInfo<T>>::get(&candidate).ok_or(Error::<T>::CandidateDNE)?;
1644			let actual_weight =
1645				<T as Config>::WeightInfo::execute_candidate_bond_less(T::MaxCandidates::get());
1646
1647			state
1648				.execute_bond_less::<T>(candidate.clone())
1649				.map_err(|err| DispatchErrorWithPostInfo {
1650					post_info: Some(actual_weight).into(),
1651					error: err,
1652				})?;
1653			<CandidateInfo<T>>::insert(&candidate, state);
1654			Ok(Some(actual_weight).into())
1655		}
1656
1657		pub fn execute_leave_candidates_inner(
1658			candidate: T::AccountId,
1659		) -> DispatchResultWithPostInfo {
1660			let state = <CandidateInfo<T>>::get(&candidate).ok_or(Error::<T>::CandidateDNE)?;
1661			let actual_auto_compound_delegation_count =
1662				<AutoCompoundingDelegations<T>>::decode_len(&candidate).unwrap_or_default() as u32;
1663
1664			// TODO use these to return actual weight used via `execute_leave_candidates_ideal`
1665			let actual_delegation_count = state.delegation_count;
1666			let actual_weight = <T as Config>::WeightInfo::execute_leave_candidates_ideal(
1667				actual_delegation_count,
1668				actual_auto_compound_delegation_count,
1669			);
1670
1671			state
1672				.can_leave::<T>()
1673				.map_err(|err| DispatchErrorWithPostInfo {
1674					post_info: Some(actual_weight).into(),
1675					error: err,
1676				})?;
1677			let return_stake = |bond: Bond<T::AccountId, BalanceOf<T>>| -> DispatchResult {
1678				// remove delegation from delegator state
1679				let mut delegator = DelegatorState::<T>::get(&bond.owner).expect(
1680					"Collator state and delegator state are consistent.
1681						Collator state has a record of this delegation. Therefore,
1682						Delegator state also has a record. qed.",
1683				);
1684
1685				if let Some(remaining) = delegator.rm_delegation::<T>(&candidate) {
1686					Self::delegation_remove_request_with_state(
1687						&candidate,
1688						&bond.owner,
1689						&mut delegator,
1690					);
1691					<AutoCompoundDelegations<T>>::remove_auto_compound(&candidate, &bond.owner);
1692
1693					if remaining.is_zero() {
1694						// we do not remove the scheduled delegation requests from other collators
1695						// since it is assumed that they were removed incrementally before only the
1696						// last delegation was left.
1697						<DelegatorState<T>>::remove(&bond.owner);
1698						// Thaw all frozen funds for delegator
1699						Self::thaw_extended(&bond.owner, false)?;
1700					} else {
1701						<DelegatorState<T>>::insert(&bond.owner, delegator);
1702					}
1703				} else {
1704					Self::thaw_extended(&bond.owner, false)?;
1705				}
1706				Ok(())
1707			};
1708			// total backing stake is at least the candidate self bond
1709			let mut total_backing = state.bond;
1710			// return all top delegations
1711			let top_delegations =
1712				<TopDelegations<T>>::take(&candidate).expect("CandidateInfo existence checked");
1713			for bond in top_delegations.delegations {
1714				return_stake(bond)?;
1715			}
1716			total_backing = total_backing.saturating_add(top_delegations.total);
1717			// return all bottom delegations
1718			let bottom_delegations =
1719				<BottomDelegations<T>>::take(&candidate).expect("CandidateInfo existence checked");
1720			for bond in bottom_delegations.delegations {
1721				return_stake(bond)?;
1722			}
1723			total_backing = total_backing.saturating_add(bottom_delegations.total);
1724			// Thaw all frozen funds for collator
1725			Self::thaw_extended(&candidate, true)?;
1726			<CandidateInfo<T>>::remove(&candidate);
1727			// Remove all scheduled delegation requests for this collator
1728			let _ = <DelegationScheduledRequests<T>>::clear_prefix(
1729				&candidate,
1730				Self::max_delegators_per_candidate(),
1731				None,
1732			);
1733			let _ = <DelegationScheduledRequestsSummaryMap<T>>::clear_prefix(
1734				&candidate,
1735				Self::max_delegators_per_candidate(),
1736				None,
1737			);
1738			<DelegationScheduledRequestsPerCollator<T>>::remove(&candidate);
1739			<AutoCompoundingDelegations<T>>::remove(&candidate);
1740			<TopDelegations<T>>::remove(&candidate);
1741			<BottomDelegations<T>>::remove(&candidate);
1742			let new_total_staked = <Total<T>>::get().saturating_sub(total_backing);
1743			<Total<T>>::put(new_total_staked);
1744			Self::deposit_event(Event::CandidateLeft {
1745				ex_candidate: candidate,
1746				unlocked_amount: total_backing,
1747				new_total_amt_locked: new_total_staked,
1748			});
1749			Ok(Some(actual_weight).into())
1750		}
1751
1752		pub fn max_delegators_per_candidate() -> u32 {
1753			AddGet::<T::MaxTopDelegationsPerCandidate, T::MaxBottomDelegationsPerCandidate>::get()
1754		}
1755
1756		/// Returns an account's stakable balance (including the reserved) which is not frozen in delegation staking
1757		pub fn get_delegator_stakable_balance(acc: &T::AccountId) -> BalanceOf<T> {
1758			let total_balance =
1759				T::Currency::balance(acc).saturating_add(T::Currency::total_balance_on_hold(acc));
1760			if let Some(frozen_balance) = Self::balance_frozen_extended(acc, false) {
1761				return total_balance.saturating_sub(frozen_balance);
1762			}
1763			total_balance
1764		}
1765
1766		/// Returns an account's free balance which is not frozen in collator staking
1767		pub fn get_collator_stakable_free_balance(acc: &T::AccountId) -> BalanceOf<T> {
1768			let total_balance = T::Currency::balance(acc);
1769			if let Some(frozen_balance) = Self::balance_frozen_extended(acc, true) {
1770				return total_balance.saturating_sub(frozen_balance);
1771			}
1772			total_balance
1773		}
1774
1775		/// Returns a delegations auto-compound value.
1776		pub fn delegation_auto_compound(
1777			candidate: &T::AccountId,
1778			delegator: &T::AccountId,
1779		) -> Percent {
1780			<AutoCompoundDelegations<T>>::auto_compound(candidate, delegator)
1781		}
1782
1783		/// Caller must ensure candidate is active before calling
1784		pub(crate) fn update_active(candidate: T::AccountId, total: BalanceOf<T>) {
1785			let mut candidates = <CandidatePool<T>>::get();
1786			candidates.remove(&Bond::from_owner(candidate.clone()));
1787			candidates
1788				.try_insert(Bond {
1789					owner: candidate,
1790					amount: total,
1791				})
1792				.expect(
1793					"the candidate is removed in previous step so the length cannot increase; qed",
1794				);
1795			<CandidatePool<T>>::put(candidates);
1796		}
1797
1798		/// Compute round issuance based on duration of the given round
1799		fn compute_issuance(round_duration: u64, round_length: u32) -> BalanceOf<T> {
1800			let ideal_duration: BalanceOf<T> = round_length
1801				.saturating_mul(T::BlockTime::get() as u32)
1802				.into();
1803			let config = <InflationConfig<T>>::get();
1804			let round_issuance = crate::inflation::round_issuance_range::<T>(config.round);
1805
1806			// Initial formula: (round_duration / ideal_duration) * ideal_issuance
1807			// We multiply before the division to reduce rounding effects
1808			BalanceOf::<T>::from(round_duration as u32).saturating_mul(round_issuance.ideal)
1809				/ (ideal_duration)
1810		}
1811
1812		/// Remove delegation from candidate state
1813		/// Amount input should be retrieved from delegator and it informs the storage lookups
1814		pub(crate) fn delegator_leaves_candidate(
1815			candidate: T::AccountId,
1816			delegator: T::AccountId,
1817			amount: BalanceOf<T>,
1818		) -> DispatchResult {
1819			let mut state = <CandidateInfo<T>>::get(&candidate).ok_or(Error::<T>::CandidateDNE)?;
1820			state.rm_delegation_if_exists::<T>(&candidate, delegator.clone(), amount)?;
1821			let new_total_locked = <Total<T>>::get().saturating_sub(amount);
1822			<Total<T>>::put(new_total_locked);
1823			let new_total = state.total_counted;
1824			<CandidateInfo<T>>::insert(&candidate, state);
1825			Self::deposit_event(Event::DelegatorLeftCandidate {
1826				delegator: delegator,
1827				candidate: candidate,
1828				unstaked_amount: amount,
1829				total_candidate_staked: new_total,
1830			});
1831			Ok(())
1832		}
1833
1834		pub(crate) fn prepare_staking_payouts(
1835			round_info: RoundInfo<BlockNumberFor<T>>,
1836			round_duration: u64,
1837		) -> Weight {
1838			let RoundInfo {
1839				current: now,
1840				length: round_length,
1841				..
1842			} = round_info;
1843
1844			// This function is called right after the round index increment,
1845			// and the goal is to compute the payout informations for the round that just ended.
1846			// We don't need to saturate here because the genesis round is 1.
1847			let prepare_payout_for_round = now - 1;
1848
1849			// Return early if there is no blocks for this round
1850			if <Points<T>>::get(prepare_payout_for_round).is_zero() {
1851				return Weight::zero();
1852			}
1853
1854			// Compute total issuance based on round duration
1855			let total_issuance = Self::compute_issuance(round_duration, round_length);
1856			// reserve portion of issuance for parachain bond account
1857			let mut left_issuance = total_issuance;
1858
1859			let configs = <InflationDistributionInfo<T>>::get().0;
1860			for (index, config) in configs.iter().enumerate() {
1861				if config.percent.is_zero() {
1862					continue;
1863				}
1864				let reserve = config.percent * total_issuance;
1865				if frame_system::Pallet::<T>::account_exists(&config.account) {
1866					if let Ok(minted) = T::Currency::mint_into(&config.account, reserve) {
1867						// update round issuance if minting succeeds
1868						left_issuance = left_issuance.saturating_sub(minted);
1869						Self::deposit_event(Event::InflationDistributed {
1870							index: index as u32,
1871							account: config.account.clone(),
1872							value: minted,
1873						});
1874					}
1875				}
1876			}
1877
1878			let payout = DelayedPayout {
1879				round_issuance: total_issuance,
1880				total_staking_reward: left_issuance,
1881				collator_commission: <CollatorCommission<T>>::get(),
1882			};
1883
1884			<DelayedPayouts<T>>::insert(prepare_payout_for_round, payout);
1885
1886			<T as Config>::WeightInfo::prepare_staking_payouts()
1887		}
1888
1889		/// Wrapper around pay_one_collator_reward which handles the following logic:
1890		/// * whether or not a payout needs to be made
1891		/// * cleaning up when payouts are done
1892		/// * returns the weight consumed by pay_one_collator_reward if applicable
1893		fn handle_delayed_payouts(now: RoundIndex) -> Weight {
1894			let delay = T::RewardPaymentDelay::get();
1895
1896			// don't underflow uint
1897			if now < delay {
1898				return Weight::from_parts(0u64, 0);
1899			}
1900
1901			let paid_for_round = now.saturating_sub(delay);
1902
1903			if let Some(payout_info) = <DelayedPayouts<T>>::get(paid_for_round) {
1904				let result = Self::pay_one_collator_reward(paid_for_round, payout_info);
1905
1906				// clean up storage items that we no longer need
1907				if matches!(result.0, RewardPayment::Finished) {
1908					<DelayedPayouts<T>>::remove(paid_for_round);
1909					<Points<T>>::remove(paid_for_round);
1910				}
1911				result.1 // weight consumed by pay_one_collator_reward
1912			} else {
1913				Weight::from_parts(0u64, 0)
1914			}
1915		}
1916
1917		/// Payout a single collator from the given round.
1918		///
1919		/// Returns an optional tuple of (Collator's AccountId, total paid)
1920		/// or None if there were no more payouts to be made for the round.
1921		pub(crate) fn pay_one_collator_reward(
1922			paid_for_round: RoundIndex,
1923			payout_info: DelayedPayout<BalanceOf<T>>,
1924		) -> (RewardPayment, Weight) {
1925			// 'early_weight' tracks weight used for reads/writes done early in this fn before its
1926			// early-exit codepaths.
1927			let mut early_weight = Weight::zero();
1928
1929			// TODO: it would probably be optimal to roll Points into the DelayedPayouts storage
1930			// item so that we do fewer reads each block
1931			let total_points = <Points<T>>::get(paid_for_round);
1932			early_weight = early_weight.saturating_add(T::DbWeight::get().reads_writes(1, 0));
1933
1934			if total_points.is_zero() {
1935				// TODO: this case is obnoxious... it's a value query, so it could mean one of two
1936				// different logic errors:
1937				// 1. we removed it before we should have
1938				// 2. we called pay_one_collator_reward when we were actually done with deferred
1939				//    payouts
1940				log::warn!("pay_one_collator_reward called with no <Points<T>> for the round!");
1941				return (RewardPayment::Finished, early_weight);
1942			}
1943
1944			let collator_fee = payout_info.collator_commission;
1945			let collator_issuance = collator_fee * payout_info.round_issuance;
1946			if let Some((collator, state)) =
1947				<AtStake<T>>::iter_prefix(paid_for_round).drain().next()
1948			{
1949				// read and kill AtStake
1950				early_weight = early_weight.saturating_add(T::DbWeight::get().reads_writes(1, 1));
1951
1952				// Take the awarded points for the collator
1953				let pts = <AwardedPts<T>>::take(paid_for_round, &collator);
1954				// read and kill AwardedPts
1955				early_weight = early_weight.saturating_add(T::DbWeight::get().reads_writes(1, 1));
1956				if pts == 0 {
1957					return (RewardPayment::Skipped, early_weight);
1958				}
1959
1960				// 'extra_weight' tracks weight returned from fns that we delegate to which can't be
1961				// known ahead of time.
1962				let mut extra_weight = Weight::zero();
1963				let pct_due = Perbill::from_rational(pts, total_points);
1964				let total_paid = pct_due * payout_info.total_staking_reward;
1965				let mut amt_due = total_paid;
1966
1967				let num_delegators = state.delegations.len();
1968				let mut num_paid_delegations = 0u32;
1969				let mut num_auto_compounding = 0u32;
1970				if state.delegations.is_empty() {
1971					// solo collator with no delegators
1972					extra_weight = extra_weight
1973						.saturating_add(T::PayoutCollatorReward::payout_collator_reward(
1974							paid_for_round,
1975							collator.clone(),
1976							amt_due,
1977						))
1978						.saturating_add(T::OnCollatorPayout::on_collator_payout(
1979							paid_for_round,
1980							collator.clone(),
1981							amt_due,
1982						));
1983				} else {
1984					// pay collator first; commission + due_portion
1985					let collator_pct = Perbill::from_rational(state.bond, state.total);
1986					let commission = pct_due * collator_issuance;
1987					amt_due = amt_due.saturating_sub(commission);
1988					let collator_reward = (collator_pct * amt_due).saturating_add(commission);
1989					extra_weight = extra_weight
1990						.saturating_add(T::PayoutCollatorReward::payout_collator_reward(
1991							paid_for_round,
1992							collator.clone(),
1993							collator_reward,
1994						))
1995						.saturating_add(T::OnCollatorPayout::on_collator_payout(
1996							paid_for_round,
1997							collator.clone(),
1998							collator_reward,
1999						));
2000
2001					// pay delegators due portion
2002					for BondWithAutoCompound {
2003						owner,
2004						amount,
2005						auto_compound,
2006					} in state.delegations
2007					{
2008						let percent = Perbill::from_rational(amount, state.total);
2009						let due = percent * amt_due;
2010						if !due.is_zero() {
2011							num_auto_compounding += if auto_compound.is_zero() { 0 } else { 1 };
2012							num_paid_delegations += 1u32;
2013							Self::mint_and_compound(
2014								due,
2015								auto_compound.clone(),
2016								collator.clone(),
2017								owner.clone(),
2018							);
2019						}
2020					}
2021				}
2022
2023				extra_weight = extra_weight.saturating_add(
2024					<T as Config>::WeightInfo::pay_one_collator_reward_best(
2025						num_paid_delegations,
2026						num_auto_compounding,
2027					),
2028				);
2029
2030				(
2031					RewardPayment::Paid,
2032					<T as Config>::WeightInfo::pay_one_collator_reward(num_delegators as u32)
2033						.saturating_add(extra_weight),
2034				)
2035			} else {
2036				// Note that we don't clean up storage here; it is cleaned up in
2037				// handle_delayed_payouts()
2038				(RewardPayment::Finished, Weight::from_parts(0u64, 0))
2039			}
2040		}
2041
2042		/// Compute the top `TotalSelected` candidates in the CandidatePool and return
2043		/// a vec of their AccountIds (sorted by AccountId).
2044		///
2045		/// If the returned vec is empty, the previous candidates should be used.
2046		pub fn compute_top_candidates() -> Vec<T::AccountId> {
2047			let top_n = <TotalSelected<T>>::get() as usize;
2048			if top_n == 0 {
2049				return vec![];
2050			}
2051
2052			let candidates = <CandidatePool<T>>::get().0;
2053
2054			// If the number of candidates is greater than top_n, select the candidates with higher
2055			// amount. Otherwise, return all the candidates.
2056			if candidates.len() > top_n {
2057				// Partially sort candidates such that element at index `top_n - 1` is sorted, and
2058				// all the elements in the range 0..top_n are the top n elements.
2059				let sorted_candidates = candidates
2060					.try_mutate(|inner| {
2061						inner.select_nth_unstable_by(top_n - 1, |a, b| {
2062							// Order by amount, then owner. The owner is needed to ensure a stable order
2063							// when two accounts have the same amount.
2064							a.amount
2065								.cmp(&b.amount)
2066								.then_with(|| a.owner.cmp(&b.owner))
2067								.reverse()
2068						});
2069					})
2070					.expect("sort cannot increase item count; qed");
2071
2072				let mut collators = sorted_candidates
2073					.into_iter()
2074					.take(top_n)
2075					.map(|x| x.owner)
2076					.collect::<Vec<_>>();
2077
2078				// Sort collators by AccountId
2079				collators.sort();
2080
2081				collators
2082			} else {
2083				// Return all candidates
2084				// The candidates are already sorted by AccountId, so no need to sort again
2085				candidates.into_iter().map(|x| x.owner).collect::<Vec<_>>()
2086			}
2087		}
2088		/// Best as in most cumulatively supported in terms of stake
2089		/// Returns [collator_count, delegation_count, total staked]
2090		pub(crate) fn select_top_candidates(now: RoundIndex) -> (Weight, u32, u32, BalanceOf<T>) {
2091			let (mut collator_count, mut delegation_count, mut total) =
2092				(0u32, 0u32, BalanceOf::<T>::zero());
2093			// choose the top TotalSelected qualified candidates, ordered by stake
2094			let collators = Self::compute_top_candidates();
2095			if collators.is_empty() {
2096				// SELECTION FAILED TO SELECT >=1 COLLATOR => select collators from previous round
2097				let last_round = now.saturating_sub(1u32);
2098				let mut total_per_candidate: BTreeMap<T::AccountId, BalanceOf<T>> = BTreeMap::new();
2099				// set this round AtStake to last round AtStake
2100				for (account, snapshot) in <AtStake<T>>::iter_prefix(last_round) {
2101					collator_count = collator_count.saturating_add(1u32);
2102					delegation_count =
2103						delegation_count.saturating_add(snapshot.delegations.len() as u32);
2104					total = total.saturating_add(snapshot.total);
2105					total_per_candidate.insert(account.clone(), snapshot.total);
2106					<AtStake<T>>::insert(now, account, snapshot);
2107				}
2108				// `SelectedCandidates` remains unchanged from last round
2109				// emit CollatorChosen event for tools that use this event
2110				for candidate in <SelectedCandidates<T>>::get() {
2111					let snapshot_total = total_per_candidate
2112						.get(&candidate)
2113						.expect("all selected candidates have snapshots");
2114					Self::deposit_event(Event::CollatorChosen {
2115						round: now,
2116						collator_account: candidate,
2117						total_exposed_amount: *snapshot_total,
2118					})
2119				}
2120				let weight = <T as Config>::WeightInfo::select_top_candidates(0, 0);
2121				return (weight, collator_count, delegation_count, total);
2122			}
2123
2124			// snapshot exposure for round for weighting reward distribution
2125			for account in collators.iter() {
2126				let state = <CandidateInfo<T>>::get(account)
2127					.expect("all members of CandidateQ must be candidates");
2128
2129				collator_count = collator_count.saturating_add(1u32);
2130				delegation_count = delegation_count.saturating_add(state.delegation_count);
2131				total = total.saturating_add(state.total_counted);
2132				let CountedDelegations {
2133					uncounted_stake,
2134					rewardable_delegations,
2135				} = Self::get_rewardable_delegators(&account);
2136				let total_counted = state.total_counted.saturating_sub(uncounted_stake);
2137
2138				let auto_compounding_delegations = <AutoCompoundingDelegations<T>>::get(&account)
2139					.into_iter()
2140					.map(|x| (x.delegator, x.value))
2141					.collect::<BTreeMap<_, _>>();
2142				let rewardable_delegations = rewardable_delegations
2143					.into_iter()
2144					.map(|d| BondWithAutoCompound {
2145						owner: d.owner.clone(),
2146						amount: d.amount,
2147						auto_compound: auto_compounding_delegations
2148							.get(&d.owner)
2149							.cloned()
2150							.unwrap_or_else(|| Percent::zero()),
2151					})
2152					.collect();
2153
2154				let snapshot = CollatorSnapshot {
2155					bond: state.bond,
2156					delegations: rewardable_delegations,
2157					total: total_counted,
2158				};
2159				<AtStake<T>>::insert(now, account, snapshot);
2160				Self::deposit_event(Event::CollatorChosen {
2161					round: now,
2162					collator_account: account.clone(),
2163					total_exposed_amount: state.total_counted,
2164				});
2165			}
2166			// insert canonical collator set
2167			<SelectedCandidates<T>>::put(
2168				BoundedVec::try_from(collators)
2169					.expect("subset of collators is always less than or equal to max candidates"),
2170			);
2171
2172			let avg_delegator_count = delegation_count.checked_div(collator_count).unwrap_or(0);
2173			let weight = <T as Config>::WeightInfo::select_top_candidates(
2174				collator_count,
2175				avg_delegator_count,
2176			);
2177			(weight, collator_count, delegation_count, total)
2178		}
2179
2180		/// Build the effective list of delegators with their intended bond amount
2181		/// for reward calculation.
2182		///
2183		/// Reads [`DelegationScheduledRequestsSummaryMap`] to adjust bonds:
2184		/// - `Revoke(_)`: bond zeroed out, full amount counted as uncounted stake.
2185		/// - `Decrease(total)`: pending decrease total subtracted from bond (capped
2186		///   at bond amount), difference counted as uncounted stake.
2187		pub(crate) fn get_rewardable_delegators(collator: &T::AccountId) -> CountedDelegations<T> {
2188			let mut uncounted_stake = BalanceOf::<T>::zero();
2189			let rewardable_delegations = <TopDelegations<T>>::get(collator)
2190				.expect("all members of CandidateQ must be candidates")
2191				.delegations
2192				.into_iter()
2193				.map(|mut bond| {
2194					match <DelegationScheduledRequestsSummaryMap<T>>::get(collator, &bond.owner) {
2195						Some(DelegationAction::Revoke(_)) => {
2196							uncounted_stake = uncounted_stake.saturating_add(bond.amount);
2197							bond.amount = BalanceOf::<T>::zero();
2198						}
2199						Some(DelegationAction::Decrease(total)) => {
2200							let decrease = total.min(bond.amount);
2201							uncounted_stake = uncounted_stake.saturating_add(decrease);
2202							bond.amount = bond.amount.saturating_sub(decrease);
2203						}
2204						None => {}
2205					}
2206					bond
2207				})
2208				.collect();
2209			CountedDelegations {
2210				uncounted_stake,
2211				rewardable_delegations,
2212			}
2213		}
2214
2215		/// This function exists as a helper to delegator_bond_more & auto_compound functionality.
2216		/// Any changes to this function must align with both user-initiated bond increases and
2217		/// auto-compounding bond increases.
2218		/// Any feature-specific preconditions should be validated before this function is invoked.
2219		/// Any feature-specific events must be emitted after this function is invoked.
2220		pub fn delegation_bond_more_without_event(
2221			delegator: T::AccountId,
2222			candidate: T::AccountId,
2223			more: BalanceOf<T>,
2224		) -> Result<
2225			(bool, Weight),
2226			DispatchErrorWithPostInfo<frame_support::dispatch::PostDispatchInfo>,
2227		> {
2228			let mut state = <DelegatorState<T>>::get(&delegator).ok_or(Error::<T>::DelegatorDNE)?;
2229			ensure!(
2230				!Self::delegation_request_revoke_exists(&candidate, &delegator),
2231				Error::<T>::PendingDelegationRevoke
2232			);
2233
2234			// This helper does not depend on the number of scheduled requests; we pass 0
2235			// here and rely on the extrinsic declaration for an upper bound.
2236			let actual_weight = <T as Config>::WeightInfo::delegator_bond_more(0);
2237			let in_top = state
2238				.increase_delegation::<T>(candidate.clone(), more)
2239				.map_err(|err| DispatchErrorWithPostInfo {
2240					post_info: Some(actual_weight).into(),
2241					error: err,
2242				})?;
2243
2244			Ok((in_top, actual_weight))
2245		}
2246
2247		/// Mint a specified reward amount to the beneficiary account. Emits the [Rewarded] event.
2248		pub fn mint(amt: BalanceOf<T>, to: &T::AccountId) -> Result<BalanceOf<T>, DispatchError> {
2249			// Mint rewards to the account
2250			let minted = T::Currency::mint_into(&to, amt)?;
2251			Self::deposit_event(Event::Rewarded {
2252				account: to.clone(),
2253				rewards: minted,
2254			});
2255			Ok(minted)
2256		}
2257
2258		/// Mint a specified reward amount to the collator's account. Emits the [Rewarded] event.
2259		pub fn mint_collator_reward(
2260			_paid_for_round: RoundIndex,
2261			collator_id: T::AccountId,
2262			amt: BalanceOf<T>,
2263		) -> Weight {
2264			// Mint rewards to the collator
2265			if let Err(e) = Self::mint(amt, &collator_id) {
2266				log::warn!(
2267					"Failed to mint collator reward for {:?}: {:?}",
2268					collator_id,
2269					e
2270				);
2271			}
2272
2273			<T as Config>::WeightInfo::mint_collator_reward()
2274		}
2275
2276		/// Mint and compound delegation rewards. The function mints the amount towards the
2277		/// delegator and tries to compound a specified percent of it back towards the delegation.
2278		/// If a scheduled delegation revoke exists, then the amount is only minted, and nothing is
2279		/// compounded. Emits the [Compounded] event.
2280		pub fn mint_and_compound(
2281			amt: BalanceOf<T>,
2282			compound_percent: Percent,
2283			candidate: T::AccountId,
2284			delegator: T::AccountId,
2285		) {
2286			// Mint rewards to the delegator
2287			if frame_system::Pallet::<T>::account_exists(&delegator) {
2288				if let Ok(minted) = Self::mint(amt.clone(), &delegator) {
2289					let compound_amount = compound_percent.mul_ceil(minted);
2290					if compound_amount.is_zero() {
2291						return;
2292					}
2293
2294					if let Err(err) = Self::delegation_bond_more_without_event(
2295						delegator.clone(),
2296						candidate.clone(),
2297						compound_amount.clone(),
2298					) {
2299						log::debug!(
2300							"skipped compounding staking reward towards candidate '{:?}' for delegator '{:?}': {:?}",
2301							candidate,
2302							delegator,
2303							err
2304						);
2305						return;
2306					};
2307
2308					Pallet::<T>::deposit_event(Event::Compounded {
2309						delegator,
2310						candidate,
2311						amount: compound_amount.clone(),
2312					});
2313				};
2314			}
2315		}
2316
2317		/// Add reward points to block authors:
2318		/// * 20 points to the block producer for producing a block in the chain
2319		fn award_points_to_block_author() {
2320			let author = T::BlockAuthor::get();
2321			let now = <Round<T>>::get().current;
2322			let score_plus_20 = <AwardedPts<T>>::get(now, &author).saturating_add(20);
2323			<AwardedPts<T>>::insert(now, author, score_plus_20);
2324			<Points<T>>::mutate(now, |x| *x = x.saturating_add(20));
2325		}
2326
2327		/// Marks collators as inactive for the previous round if they received zero awarded points.
2328		pub fn mark_collators_as_inactive(cur: RoundIndex) -> Weight {
2329			// This function is called after round index increment,
2330			// We don't need to saturate here because the genesis round is 1.
2331			let prev = cur - 1;
2332
2333			let mut collators_at_stake_count = 0u32;
2334			for (account, _) in <AtStake<T>>::iter_prefix(prev) {
2335				collators_at_stake_count = collators_at_stake_count.saturating_add(1u32);
2336				if <AwardedPts<T>>::get(prev, &account).is_zero() {
2337					<WasInactive<T>>::insert(prev, account, ());
2338				}
2339			}
2340
2341			<T as Config>::WeightInfo::mark_collators_as_inactive(collators_at_stake_count)
2342		}
2343
2344		/// Cleans up historical staking information that is older than MaxOfflineRounds
2345		/// by removing entries from the WasIactive storage map.
2346		fn cleanup_inactive_collator_info() {
2347			let now = <Round<T>>::get().current;
2348			let minimum_rounds_required = T::MaxOfflineRounds::get() + 1;
2349
2350			if now < minimum_rounds_required {
2351				return;
2352			}
2353
2354			let _ = <WasInactive<T>>::iter_prefix(now - minimum_rounds_required)
2355				.drain()
2356				.next();
2357		}
2358	}
2359
2360	impl<T: Config> nimbus_primitives::CanAuthor<T::AccountId> for Pallet<T> {
2361		fn can_author(account: &T::AccountId, _slot: &u32) -> bool {
2362			Self::is_selected_candidate(account)
2363		}
2364	}
2365
2366	impl<T: Config> Get<Vec<T::AccountId>> for Pallet<T> {
2367		fn get() -> Vec<T::AccountId> {
2368			Self::selected_candidates().into_inner()
2369		}
2370	}
2371}