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

            
51
mod auto_compound;
52
mod delegation_requests;
53
pub mod inflation;
54
pub mod migrations;
55
pub mod traits;
56
pub mod types;
57
pub mod weights;
58

            
59
#[cfg(any(test, feature = "runtime-benchmarks"))]
60
mod benchmarks;
61
#[cfg(test)]
62
mod mock;
63
mod set;
64
#[cfg(test)]
65
mod tests;
66

            
67
use frame_support::pallet;
68
pub use inflation::{InflationInfo, Range};
69
pub use weights::WeightInfo;
70

            
71
pub use auto_compound::{AutoCompoundConfig, AutoCompoundDelegations};
72
pub use delegation_requests::{CancelledScheduledRequest, DelegationAction, ScheduledRequest};
73
pub use pallet::*;
74
pub use traits::*;
75
pub use types::*;
76
pub use RoundIndex;
77

            
78
#[pallet]
79
pub 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
30691
		fn on_initialize(n: BlockNumberFor<T>) -> Weight {
474
30691
			let mut weight = <T as Config>::WeightInfo::base_on_initialize();
475

            
476
30691
			let mut round = <Round<T>>::get();
477
30691
			if round.should_update(n) {
478
263
				// fetch current slot number
479
263
				let current_slot: u64 = T::SlotProvider::get().into();
480
263

            
481
263
				// account for SlotProvider read
482
263
				weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 0));
483
263

            
484
263
				// Compute round duration in slots
485
263
				let round_duration = (current_slot.saturating_sub(round.first_slot))
486
263
					.saturating_mul(T::SlotDuration::get());
487
263

            
488
263
				// mutate round
489
263
				round.update(n, current_slot);
490
263
				// notify that new round begin
491
263
				weight = weight.saturating_add(T::OnNewRound::on_new_round(round.current));
492
263
				// pay all stakers for T::RewardPaymentDelay rounds ago
493
263
				weight =
494
263
					weight.saturating_add(Self::prepare_staking_payouts(round, round_duration));
495
263
				// select top collator candidates for next round
496
263
				let (extra_weight, collator_count, _delegation_count, total_staked) =
497
263
					Self::select_top_candidates(round.current);
498
263
				weight = weight.saturating_add(extra_weight);
499
263
				// start next round
500
263
				<Round<T>>::put(round);
501
263
				Self::deposit_event(Event::NewRound {
502
263
					starting_block: round.first,
503
263
					round: round.current,
504
263
					selected_collators_number: collator_count,
505
263
					total_balance: total_staked,
506
263
				});
507
263
				// record inactive collators
508
263
				weight = weight.saturating_add(Self::mark_collators_as_inactive(round.current));
509
263
				// account for Round write
510
263
				weight = weight.saturating_add(T::DbWeight::get().reads_writes(0, 1));
511
30428
			} else {
512
30428
				weight = weight.saturating_add(Self::handle_delayed_payouts(round.current));
513
30428
			}
514

            
515
			// add on_finalize weight
516
			//   read:  Author, Points, AwardedPts, WasInactive
517
			//   write: Points, AwardedPts, WasInactive
518
30691
			weight = weight.saturating_add(T::DbWeight::get().reads_writes(4, 3));
519
30691
			weight
520
30691
		}
521
30632
		fn on_finalize(_n: BlockNumberFor<T>) {
522
30632
			Self::award_points_to_block_author();
523
30632
			Self::cleanup_inactive_collator_info();
524
30632
		}
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
198
		fn get() -> u32 {
577
198
			T::get() + R::get()
578
198
		}
579
	}
580

            
581
	/// Stores outstanding delegation requests per collator & delegator.
582
	///
583
	/// Each `(collator, delegator)` pair can have up to
584
	/// `T::MaxScheduledRequestsPerDelegator` scheduled requests,
585
	/// which are always interpreted and executed in FIFO order.
586
	#[pallet::storage]
587
	#[pallet::getter(fn delegation_scheduled_requests)]
588
	pub(crate) type DelegationScheduledRequests<T: Config> = StorageDoubleMap<
589
		_,
590
		Blake2_128Concat,
591
		T::AccountId,
592
		Blake2_128Concat,
593
		T::AccountId,
594
		BoundedVec<ScheduledRequest<BalanceOf<T>>, T::MaxScheduledRequestsPerDelegator>,
595
		ValueQuery,
596
	>;
597

            
598
	/// Tracks how many delegators have at least one pending delegation request for a given collator.
599
	///
600
	/// This is used to enforce that the number of delegators with pending requests per collator
601
	/// does not exceed `MaxTopDelegationsPerCandidate + MaxBottomDelegationsPerCandidate` without
602
	/// having to iterate over all scheduled requests.
603
	#[pallet::storage]
604
	pub(crate) type DelegationScheduledRequestsPerCollator<T: Config> =
605
		StorageMap<_, Blake2_128Concat, T::AccountId, u32, ValueQuery>;
606

            
607
	/// Stores auto-compounding configuration per collator.
608
	#[pallet::storage]
609
	#[pallet::getter(fn auto_compounding_delegations)]
610
	pub(crate) type AutoCompoundingDelegations<T: Config> = StorageMap<
611
		_,
612
		Blake2_128Concat,
613
		T::AccountId,
614
		BoundedVec<
615
			AutoCompoundConfig<T::AccountId>,
616
			AddGet<T::MaxTopDelegationsPerCandidate, T::MaxBottomDelegationsPerCandidate>,
617
		>,
618
		ValueQuery,
619
	>;
620

            
621
	#[pallet::storage]
622
	#[pallet::getter(fn top_delegations)]
623
	/// Top delegations for collator candidate
624
	pub(crate) type TopDelegations<T: Config> = StorageMap<
625
		_,
626
		Twox64Concat,
627
		T::AccountId,
628
		Delegations<T::AccountId, BalanceOf<T>>,
629
		OptionQuery,
630
	>;
631

            
632
	#[pallet::storage]
633
	#[pallet::getter(fn bottom_delegations)]
634
	/// Bottom delegations for collator candidate
635
	pub(crate) type BottomDelegations<T: Config> = StorageMap<
636
		_,
637
		Twox64Concat,
638
		T::AccountId,
639
		Delegations<T::AccountId, BalanceOf<T>>,
640
		OptionQuery,
641
	>;
642

            
643
	#[pallet::storage]
644
	#[pallet::getter(fn selected_candidates)]
645
	/// The collator candidates selected for the current round
646
	type SelectedCandidates<T: Config> =
647
		StorageValue<_, BoundedVec<T::AccountId, T::MaxCandidates>, ValueQuery>;
648

            
649
	#[pallet::storage]
650
	#[pallet::getter(fn total)]
651
	/// Total capital locked by this staking pallet
652
	pub(crate) type Total<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
653

            
654
	#[pallet::storage]
655
	#[pallet::getter(fn candidate_pool)]
656
	/// The pool of collator candidates, each with their total backing stake
657
	pub(crate) type CandidatePool<T: Config> = StorageValue<
658
		_,
659
		BoundedOrderedSet<Bond<T::AccountId, BalanceOf<T>>, T::MaxCandidates>,
660
		ValueQuery,
661
	>;
662

            
663
	#[pallet::storage]
664
	#[pallet::getter(fn at_stake)]
665
	/// Snapshot of collator delegation stake at the start of the round
666
	pub type AtStake<T: Config> = StorageDoubleMap<
667
		_,
668
		Twox64Concat,
669
		RoundIndex,
670
		Twox64Concat,
671
		T::AccountId,
672
		CollatorSnapshot<T::AccountId, BalanceOf<T>>,
673
		OptionQuery,
674
	>;
675

            
676
	#[pallet::storage]
677
	#[pallet::getter(fn was_inactive)]
678
	/// Records collators' inactivity.
679
	/// Data persists for MaxOfflineRounds + 1 rounds before being pruned.
680
	pub type WasInactive<T: Config> =
681
		StorageDoubleMap<_, Twox64Concat, RoundIndex, Twox64Concat, T::AccountId, (), OptionQuery>;
682

            
683
	#[pallet::storage]
684
	#[pallet::getter(fn delayed_payouts)]
685
	/// Delayed payouts
686
	pub type DelayedPayouts<T: Config> =
687
		StorageMap<_, Twox64Concat, RoundIndex, DelayedPayout<BalanceOf<T>>, OptionQuery>;
688

            
689
	#[pallet::storage]
690
	#[pallet::getter(fn inflation_config)]
691
	/// Inflation configuration
692
	pub type InflationConfig<T: Config> = StorageValue<_, InflationInfo<BalanceOf<T>>, ValueQuery>;
693

            
694
	#[pallet::storage]
695
	#[pallet::getter(fn points)]
696
	/// Total points awarded to collators for block production in the round
697
	pub type Points<T: Config> = StorageMap<_, Twox64Concat, RoundIndex, RewardPoint, ValueQuery>;
698

            
699
	#[pallet::storage]
700
	#[pallet::getter(fn awarded_pts)]
701
	/// Points for each collator per round
702
	pub type AwardedPts<T: Config> = StorageDoubleMap<
703
		_,
704
		Twox64Concat,
705
		RoundIndex,
706
		Twox64Concat,
707
		T::AccountId,
708
		RewardPoint,
709
		ValueQuery,
710
	>;
711

            
712
	#[pallet::storage]
713
	#[pallet::getter(fn marking_offline)]
714
	/// Killswitch to enable/disable marking offline feature.
715
	pub type EnableMarkingOffline<T: Config> = StorageValue<_, bool, ValueQuery>;
716

            
717
	#[pallet::genesis_config]
718
	pub struct GenesisConfig<T: Config> {
719
		/// Initialize balance and register all as collators: `(collator AccountId, balance Amount)`
720
		pub candidates: Vec<(T::AccountId, BalanceOf<T>)>,
721
		/// Initialize balance and make delegations:
722
		/// `(delegator AccountId, collator AccountId, delegation Amount, auto-compounding Percent)`
723
		pub delegations: Vec<(T::AccountId, T::AccountId, BalanceOf<T>, Percent)>,
724
		/// Inflation configuration
725
		pub inflation_config: InflationInfo<BalanceOf<T>>,
726
		/// Default fixed percent a collator takes off the top of due rewards
727
		pub collator_commission: Perbill,
728
		/// Default percent of inflation set aside for parachain bond every round
729
		pub parachain_bond_reserve_percent: Percent,
730
		/// Default number of blocks in a round
731
		pub blocks_per_round: u32,
732
		/// Number of selected candidates every round. Cannot be lower than MinSelectedCandidates
733
		pub num_selected_candidates: u32,
734
	}
735

            
736
	impl<T: Config> Default for GenesisConfig<T> {
737
3
		fn default() -> Self {
738
3
			Self {
739
3
				candidates: vec![],
740
3
				delegations: vec![],
741
3
				inflation_config: Default::default(),
742
3
				collator_commission: Default::default(),
743
3
				parachain_bond_reserve_percent: Default::default(),
744
3
				blocks_per_round: 1u32,
745
3
				num_selected_candidates: T::MinSelectedCandidates::get(),
746
3
			}
747
3
		}
748
	}
749

            
750
	#[pallet::genesis_build]
751
	impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
752
504
		fn build(&self) {
753
504
			assert!(self.blocks_per_round > 0, "Blocks per round must be > 0");
754
504
			<InflationConfig<T>>::put(self.inflation_config.clone());
755
504
			let mut candidate_count = 0u32;
756
			// Initialize the candidates
757
1131
			for &(ref candidate, balance) in &self.candidates {
758
627
				assert!(
759
627
					<Pallet<T>>::get_collator_stakable_free_balance(candidate) >= balance,
760
					"Account does not have enough balance to bond as a candidate."
761
				);
762
627
				if let Err(error) = <Pallet<T>>::join_candidates(
763
627
					T::RuntimeOrigin::from(Some(candidate.clone()).into()),
764
627
					balance,
765
627
					candidate_count,
766
627
				) {
767
3
					log::warn!("Join candidates failed in genesis with error {:?}", error);
768
624
				} else {
769
624
					candidate_count = candidate_count.saturating_add(1u32);
770
624
				}
771
			}
772

            
773
504
			let mut col_delegator_count: BTreeMap<T::AccountId, u32> = BTreeMap::new();
774
504
			let mut col_auto_compound_delegator_count: BTreeMap<T::AccountId, u32> =
775
504
				BTreeMap::new();
776
504
			let mut del_delegation_count: BTreeMap<T::AccountId, u32> = BTreeMap::new();
777
			// Initialize the delegations
778
9516
			for &(ref delegator, ref target, balance, auto_compound) in &self.delegations {
779
9012
				assert!(
780
9012
					<Pallet<T>>::get_delegator_stakable_balance(delegator) >= balance,
781
					"Account does not have enough balance to place delegation."
782
				);
783
9012
				let cd_count = if let Some(x) = col_delegator_count.get(target) {
784
8780
					*x
785
				} else {
786
232
					0u32
787
				};
788
9012
				let dd_count = if let Some(x) = del_delegation_count.get(delegator) {
789
45
					*x
790
				} else {
791
8967
					0u32
792
				};
793
9012
				let cd_auto_compound_count = col_auto_compound_delegator_count
794
9012
					.get(target)
795
9012
					.cloned()
796
9012
					.unwrap_or_default();
797
9012
				if let Err(error) = <Pallet<T>>::delegate_with_auto_compound(
798
9012
					T::RuntimeOrigin::from(Some(delegator.clone()).into()),
799
9012
					target.clone(),
800
9012
					balance,
801
9012
					auto_compound,
802
9012
					cd_count,
803
9012
					cd_auto_compound_count,
804
9012
					dd_count,
805
9012
				) {
806
8468
					log::warn!("Delegate failed in genesis with error {:?}", error);
807
				} else {
808
544
					if let Some(x) = col_delegator_count.get_mut(target) {
809
312
						*x = x.saturating_add(1u32);
810
358
					} else {
811
232
						col_delegator_count.insert(target.clone(), 1u32);
812
232
					};
813
544
					if let Some(x) = del_delegation_count.get_mut(delegator) {
814
45
						*x = x.saturating_add(1u32);
815
499
					} else {
816
499
						del_delegation_count.insert(delegator.clone(), 1u32);
817
499
					};
818
544
					if !auto_compound.is_zero() {
819
4
						col_auto_compound_delegator_count
820
4
							.entry(target.clone())
821
4
							.and_modify(|x| *x = x.saturating_add(1))
822
4
							.or_insert(1);
823
540
					}
824
				}
825
			}
826
			// Set collator commission to default config
827
504
			<CollatorCommission<T>>::put(self.collator_commission);
828
			// Set parachain bond config to default config
829
504
			let pbr = InflationDistributionAccount {
830
504
				// must be set soon; if not => due inflation will be sent to collators/delegators
831
504
				account: T::AccountId::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes())
832
504
					.expect("infinite length input; no invalid inputs for type; qed"),
833
504
				percent: self.parachain_bond_reserve_percent,
834
504
			};
835
504
			let zeroed_account = InflationDistributionAccount {
836
504
				account: T::AccountId::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes())
837
504
					.expect("infinite length input; no invalid inputs for type; qed"),
838
504
				percent: Percent::zero(),
839
504
			};
840
504
			<InflationDistributionInfo<T>>::put::<InflationDistributionConfig<T::AccountId>>(
841
504
				[pbr, zeroed_account].into(),
842
			);
843
			// Set total selected candidates to value from config
844
504
			assert!(
845
504
				self.num_selected_candidates >= T::MinSelectedCandidates::get(),
846
				"{:?}",
847
				Error::<T>::CannotSetBelowMin
848
			);
849
504
			assert!(
850
504
				self.num_selected_candidates <= T::MaxCandidates::get(),
851
				"{:?}",
852
				Error::<T>::CannotSetAboveMaxCandidates
853
			);
854
504
			<TotalSelected<T>>::put(self.num_selected_candidates);
855
			// Choose top TotalSelected collator candidates
856
504
			let (_, v_count, _, total_staked) = <Pallet<T>>::select_top_candidates(1u32);
857
			// Start Round 1 at Block 0
858
504
			let round: RoundInfo<BlockNumberFor<T>> =
859
504
				RoundInfo::new(1u32, Zero::zero(), self.blocks_per_round, 0);
860
504
			<Round<T>>::put(round);
861
504
			<Pallet<T>>::deposit_event(Event::NewRound {
862
504
				starting_block: Zero::zero(),
863
504
				round: 1u32,
864
504
				selected_collators_number: v_count,
865
504
				total_balance: total_staked,
866
504
			});
867
504
		}
868
	}
869

            
870
	#[pallet::call]
871
	impl<T: Config> Pallet<T> {
872
		/// Set the expectations for total staked. These expectations determine the issuance for
873
		/// the round according to logic in `fn compute_issuance`
874
		#[pallet::call_index(0)]
875
		#[pallet::weight(<T as Config>::WeightInfo::set_staking_expectations())]
876
		pub fn set_staking_expectations(
877
			origin: OriginFor<T>,
878
			expectations: Range<BalanceOf<T>>,
879
6
		) -> DispatchResultWithPostInfo {
880
6
			T::MonetaryGovernanceOrigin::ensure_origin(origin)?;
881
5
			ensure!(expectations.is_valid(), Error::<T>::InvalidSchedule);
882
4
			let mut config = <InflationConfig<T>>::get();
883
4
			ensure!(
884
4
				config.expect != expectations,
885
1
				Error::<T>::NoWritingSameValue
886
			);
887
3
			config.set_expectations(expectations);
888
3
			Self::deposit_event(Event::StakeExpectationsSet {
889
3
				expect_min: config.expect.min,
890
3
				expect_ideal: config.expect.ideal,
891
3
				expect_max: config.expect.max,
892
3
			});
893
3
			<InflationConfig<T>>::put(config);
894
3
			Ok(().into())
895
		}
896

            
897
		/// Set the annual inflation rate to derive per-round inflation
898
		#[pallet::call_index(1)]
899
		#[pallet::weight(<T as Config>::WeightInfo::set_inflation())]
900
		pub fn set_inflation(
901
			origin: OriginFor<T>,
902
			schedule: Range<Perbill>,
903
7
		) -> DispatchResultWithPostInfo {
904
7
			T::MonetaryGovernanceOrigin::ensure_origin(origin)?;
905
5
			ensure!(schedule.is_valid(), Error::<T>::InvalidSchedule);
906
4
			let mut config = <InflationConfig<T>>::get();
907
4
			ensure!(config.annual != schedule, Error::<T>::NoWritingSameValue);
908
3
			config.annual = schedule;
909
3
			config.set_round_from_annual::<T>(schedule);
910
3
			Self::deposit_event(Event::InflationSet {
911
3
				annual_min: config.annual.min,
912
3
				annual_ideal: config.annual.ideal,
913
3
				annual_max: config.annual.max,
914
3
				round_min: config.round.min,
915
3
				round_ideal: config.round.ideal,
916
3
				round_max: config.round.max,
917
3
			});
918
3
			<InflationConfig<T>>::put(config);
919
3
			Ok(().into())
920
		}
921

            
922
		/// Set the total number of collator candidates selected per round
923
		/// - changes are not applied until the start of the next round
924
		#[pallet::call_index(4)]
925
		#[pallet::weight(<T as Config>::WeightInfo::set_total_selected())]
926
11
		pub fn set_total_selected(origin: OriginFor<T>, new: u32) -> DispatchResultWithPostInfo {
927
11
			frame_system::ensure_root(origin)?;
928
10
			ensure!(
929
10
				new >= T::MinSelectedCandidates::get(),
930
1
				Error::<T>::CannotSetBelowMin
931
			);
932
9
			ensure!(
933
9
				new <= T::MaxCandidates::get(),
934
1
				Error::<T>::CannotSetAboveMaxCandidates
935
			);
936
8
			let old = <TotalSelected<T>>::get();
937
8
			ensure!(old != new, Error::<T>::NoWritingSameValue);
938
7
			ensure!(
939
7
				new < <Round<T>>::get().length,
940
2
				Error::<T>::RoundLengthMustBeGreaterThanTotalSelectedCollators,
941
			);
942
5
			<TotalSelected<T>>::put(new);
943
5
			Self::deposit_event(Event::TotalSelectedSet { old, new });
944
5
			Ok(().into())
945
		}
946

            
947
		/// Set the commission for all collators
948
		#[pallet::call_index(5)]
949
		#[pallet::weight(<T as Config>::WeightInfo::set_collator_commission())]
950
		pub fn set_collator_commission(
951
			origin: OriginFor<T>,
952
			new: Perbill,
953
4
		) -> DispatchResultWithPostInfo {
954
4
			frame_system::ensure_root(origin)?;
955
3
			let old = <CollatorCommission<T>>::get();
956
3
			ensure!(old != new, Error::<T>::NoWritingSameValue);
957
2
			<CollatorCommission<T>>::put(new);
958
2
			Self::deposit_event(Event::CollatorCommissionSet { old, new });
959
2
			Ok(().into())
960
		}
961

            
962
		/// Set blocks per round
963
		/// - if called with `new` less than length of current round, will transition immediately
964
		/// in the next block
965
		/// - also updates per-round inflation config
966
		#[pallet::call_index(6)]
967
		#[pallet::weight(<T as Config>::WeightInfo::set_blocks_per_round())]
968
17
		pub fn set_blocks_per_round(origin: OriginFor<T>, new: u32) -> DispatchResultWithPostInfo {
969
17
			frame_system::ensure_root(origin)?;
970
16
			ensure!(
971
16
				new >= T::MinBlocksPerRound::get(),
972
1
				Error::<T>::CannotSetBelowMin
973
			);
974
15
			let mut round = <Round<T>>::get();
975
15
			let (now, first, old) = (round.current, round.first, round.length);
976
15
			ensure!(old != new, Error::<T>::NoWritingSameValue);
977
14
			ensure!(
978
14
				new > <TotalSelected<T>>::get(),
979
2
				Error::<T>::RoundLengthMustBeGreaterThanTotalSelectedCollators,
980
			);
981
12
			round.length = new;
982
			// update per-round inflation given new rounds per year
983
12
			let mut inflation_config = <InflationConfig<T>>::get();
984
12
			inflation_config.reset_round::<T>(new);
985
12
			<Round<T>>::put(round);
986
12
			Self::deposit_event(Event::BlocksPerRoundSet {
987
12
				current_round: now,
988
12
				first_block: first,
989
12
				old: old,
990
12
				new: new,
991
12
				new_per_round_inflation_min: inflation_config.round.min,
992
12
				new_per_round_inflation_ideal: inflation_config.round.ideal,
993
12
				new_per_round_inflation_max: inflation_config.round.max,
994
12
			});
995
12
			<InflationConfig<T>>::put(inflation_config);
996
12
			Ok(().into())
997
		}
998

            
999
		/// Join the set of collator candidates
		#[pallet::call_index(7)]
		#[pallet::weight(<T as Config>::WeightInfo::join_candidates(*candidate_count))]
		pub fn join_candidates(
			origin: OriginFor<T>,
			bond: BalanceOf<T>,
			candidate_count: u32,
667
		) -> DispatchResultWithPostInfo {
667
			let acc = ensure_signed(origin)?;
667
			ensure!(
667
				bond >= T::MinCandidateStk::get(),
4
				Error::<T>::CandidateBondBelowMin
			);
663
			Self::join_candidates_inner(acc, bond, candidate_count)
		}
		/// Request to leave the set of candidates. If successful, the account is immediately
		/// removed from the candidate pool to prevent selection as a collator.
		#[pallet::call_index(8)]
		#[pallet::weight(<T as Config>::WeightInfo::schedule_leave_candidates(*candidate_count))]
		pub fn schedule_leave_candidates(
			origin: OriginFor<T>,
			candidate_count: u32,
47
		) -> DispatchResultWithPostInfo {
47
			let collator = ensure_signed(origin)?;
47
			let mut state = <CandidateInfo<T>>::get(&collator).ok_or(Error::<T>::CandidateDNE)?;
46
			let (now, when) = state.schedule_leave::<T>()?;
45
			let mut candidates = <CandidatePool<T>>::get();
45
			ensure!(
45
				candidate_count >= candidates.0.len() as u32,
5
				Error::<T>::TooLowCandidateCountToLeaveCandidates
			);
40
			if candidates.remove(&Bond::from_owner(collator.clone())) {
40
				<CandidatePool<T>>::put(candidates);
40
			}
40
			<CandidateInfo<T>>::insert(&collator, state);
40
			Self::deposit_event(Event::CandidateScheduledExit {
40
				exit_allowed_round: now,
40
				candidate: collator,
40
				scheduled_exit: when,
40
			});
40
			Ok(().into())
		}
		/// Execute leave candidates request
		#[pallet::call_index(9)]
		#[pallet::weight(
			<T as Config>::WeightInfo::execute_leave_candidates_worst_case(*candidate_delegation_count)
		)]
		pub fn execute_leave_candidates(
			origin: OriginFor<T>,
			candidate: T::AccountId,
			candidate_delegation_count: u32,
26
		) -> DispatchResultWithPostInfo {
26
			ensure_signed(origin)?;
26
			let state = <CandidateInfo<T>>::get(&candidate).ok_or(Error::<T>::CandidateDNE)?;
26
			ensure!(
26
				state.delegation_count <= candidate_delegation_count,
3
				Error::<T>::TooLowCandidateDelegationCountToLeaveCandidates
			);
23
			<Pallet<T>>::execute_leave_candidates_inner(candidate)
		}
		/// Cancel open request to leave candidates
		/// - only callable by collator account
		/// - result upon successful call is the candidate is active in the candidate pool
		#[pallet::call_index(10)]
		#[pallet::weight(<T as Config>::WeightInfo::cancel_leave_candidates(*candidate_count))]
		pub fn cancel_leave_candidates(
			origin: OriginFor<T>,
			candidate_count: u32,
4
		) -> DispatchResultWithPostInfo {
4
			let collator = ensure_signed(origin)?;
4
			let mut state = <CandidateInfo<T>>::get(&collator).ok_or(Error::<T>::CandidateDNE)?;
4
			ensure!(state.is_leaving(), Error::<T>::CandidateNotLeaving);
4
			state.go_online();
4
			let mut candidates = <CandidatePool<T>>::get();
4
			ensure!(
4
				candidates.0.len() as u32 <= candidate_count,
				Error::<T>::TooLowCandidateCountWeightHintCancelLeaveCandidates
			);
4
			let maybe_inserted_candidate = candidates
4
				.try_insert(Bond {
4
					owner: collator.clone(),
4
					amount: state.total_counted,
4
				})
4
				.map_err(|_| Error::<T>::CandidateLimitReached)?;
4
			ensure!(maybe_inserted_candidate, Error::<T>::AlreadyActive);
4
			<CandidatePool<T>>::put(candidates);
4
			<CandidateInfo<T>>::insert(&collator, state);
4
			Self::deposit_event(Event::CancelledCandidateExit {
4
				candidate: collator,
4
			});
4
			Ok(().into())
		}
		/// Temporarily leave the set of collator candidates without unbonding
		#[pallet::call_index(11)]
		#[pallet::weight(<T as Config>::WeightInfo::go_offline(MAX_CANDIDATES))]
14
		pub fn go_offline(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
14
			let collator = ensure_signed(origin)?;
14
			<Pallet<T>>::go_offline_inner(collator)
		}
		/// Rejoin the set of collator candidates if previously had called `go_offline`
		#[pallet::call_index(12)]
		#[pallet::weight(<T as Config>::WeightInfo::go_online(MAX_CANDIDATES))]
7
		pub fn go_online(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
7
			let collator = ensure_signed(origin)?;
7
			<Pallet<T>>::go_online_inner(collator)
		}
		/// Increase collator candidate self bond by `more`
		#[pallet::call_index(13)]
		#[pallet::weight(<T as Config>::WeightInfo::candidate_bond_more(MAX_CANDIDATES))]
		pub fn candidate_bond_more(
			origin: OriginFor<T>,
			more: BalanceOf<T>,
6
		) -> DispatchResultWithPostInfo {
6
			let candidate = ensure_signed(origin)?;
6
			<Pallet<T>>::candidate_bond_more_inner(candidate, more)
		}
		/// Request by collator candidate to decrease self bond by `less`
		#[pallet::call_index(14)]
		#[pallet::weight(<T as Config>::WeightInfo::schedule_candidate_bond_less())]
		pub fn schedule_candidate_bond_less(
			origin: OriginFor<T>,
			less: BalanceOf<T>,
19
		) -> DispatchResultWithPostInfo {
19
			let collator = ensure_signed(origin)?;
19
			let mut state = <CandidateInfo<T>>::get(&collator).ok_or(Error::<T>::CandidateDNE)?;
17
			let when = state.schedule_bond_less::<T>(less)?;
15
			<CandidateInfo<T>>::insert(&collator, state);
15
			Self::deposit_event(Event::CandidateBondLessRequested {
15
				candidate: collator,
15
				amount_to_decrease: less,
15
				execute_round: when,
15
			});
15
			Ok(().into())
		}
		/// Execute pending request to adjust the collator candidate self bond
		#[pallet::call_index(15)]
		#[pallet::weight(<T as Config>::WeightInfo::execute_candidate_bond_less(MAX_CANDIDATES))]
		pub fn execute_candidate_bond_less(
			origin: OriginFor<T>,
			candidate: T::AccountId,
6
		) -> DispatchResultWithPostInfo {
6
			ensure_signed(origin)?; // we may want to reward this if caller != candidate
6
			<Pallet<T>>::execute_candidate_bond_less_inner(candidate)
		}
		/// Cancel pending request to adjust the collator candidate self bond
		#[pallet::call_index(16)]
		#[pallet::weight(<T as Config>::WeightInfo::cancel_candidate_bond_less())]
4
		pub fn cancel_candidate_bond_less(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
4
			let collator = ensure_signed(origin)?;
4
			let mut state = <CandidateInfo<T>>::get(&collator).ok_or(Error::<T>::CandidateDNE)?;
3
			state.cancel_bond_less::<T>(collator.clone())?;
3
			<CandidateInfo<T>>::insert(&collator, state);
3
			Ok(().into())
		}
		/// If caller is not a delegator and not a collator, then join the set of delegators
		/// If caller is a delegator, then makes delegation to change their delegation state
		/// Sets the auto-compound config for the delegation
		#[pallet::call_index(18)]
		#[pallet::weight(
			<T as Config>::WeightInfo::delegate_with_auto_compound(
				*candidate_delegation_count,
				*candidate_auto_compounding_delegation_count,
				*delegation_count,
			)
		)]
		pub fn delegate_with_auto_compound(
			origin: OriginFor<T>,
			candidate: T::AccountId,
			amount: BalanceOf<T>,
			auto_compound: Percent,
			candidate_delegation_count: u32,
			candidate_auto_compounding_delegation_count: u32,
			delegation_count: u32,
9090
		) -> DispatchResultWithPostInfo {
9090
			let delegator = ensure_signed(origin)?;
9090
			<AutoCompoundDelegations<T>>::delegate_with_auto_compound(
9090
				candidate,
9090
				delegator,
9090
				amount,
9090
				auto_compound,
9090
				candidate_delegation_count,
9090
				candidate_auto_compounding_delegation_count,
9090
				delegation_count,
			)
		}
		/// Request to revoke an existing delegation. If successful, the delegation is scheduled
		/// to be allowed to be revoked via the `execute_delegation_request` extrinsic.
		/// The delegation receives no rewards for the rounds while a revoke is pending.
		/// A revoke may not be performed if any other scheduled request is pending.
		#[pallet::call_index(22)]
		#[pallet::weight(<T as Config>::WeightInfo::schedule_revoke_delegation(
			T::MaxTopDelegationsPerCandidate::get() + T::MaxBottomDelegationsPerCandidate::get()
		))]
		pub fn schedule_revoke_delegation(
			origin: OriginFor<T>,
			collator: T::AccountId,
52
		) -> DispatchResultWithPostInfo {
52
			let delegator = ensure_signed(origin)?;
52
			Self::delegation_schedule_revoke(collator, delegator)
		}
		/// Bond more for delegators wrt a specific collator candidate.
		#[pallet::call_index(23)]
		#[pallet::weight(<T as Config>::WeightInfo::delegator_bond_more(
			T::MaxTopDelegationsPerCandidate::get() + T::MaxBottomDelegationsPerCandidate::get()
		))]
		pub fn delegator_bond_more(
			origin: OriginFor<T>,
			candidate: T::AccountId,
			more: BalanceOf<T>,
19
		) -> DispatchResultWithPostInfo {
19
			let delegator = ensure_signed(origin)?;
19
			let (in_top, weight) = Self::delegation_bond_more_without_event(
19
				delegator.clone(),
19
				candidate.clone(),
19
				more.clone(),
1
			)?;
18
			Pallet::<T>::deposit_event(Event::DelegationIncreased {
18
				delegator,
18
				candidate,
18
				amount: more,
18
				in_top,
18
			});
18
			Ok(Some(weight).into())
		}
		/// Request bond less for delegators wrt a specific collator candidate. The delegation's
		/// rewards for rounds while the request is pending use the reduced bonded amount.
		/// A bond less may not be performed if a revoke request is pending for the same delegation.
		#[pallet::call_index(24)]
		#[pallet::weight(<T as Config>::WeightInfo::schedule_delegator_bond_less(
			T::MaxTopDelegationsPerCandidate::get() + T::MaxBottomDelegationsPerCandidate::get()
		))]
		pub fn schedule_delegator_bond_less(
			origin: OriginFor<T>,
			candidate: T::AccountId,
			less: BalanceOf<T>,
89
		) -> DispatchResultWithPostInfo {
89
			let delegator = ensure_signed(origin)?;
89
			Self::delegation_schedule_bond_decrease(candidate, delegator, less)
		}
		/// Execute pending request to change an existing delegation
		#[pallet::call_index(25)]
		#[pallet::weight(<T as Config>::WeightInfo::execute_delegator_revoke_delegation_worst())]
		pub fn execute_delegation_request(
			origin: OriginFor<T>,
			delegator: T::AccountId,
			candidate: T::AccountId,
39
		) -> DispatchResultWithPostInfo {
39
			ensure_signed(origin)?; // we may want to reward caller if caller != delegator
39
			Self::delegation_execute_scheduled_request(candidate, delegator)
		}
		/// Cancel request to change an existing delegation.
		#[pallet::call_index(26)]
		#[pallet::weight(<T as Config>::WeightInfo::cancel_delegation_request(350))]
		pub fn cancel_delegation_request(
			origin: OriginFor<T>,
			candidate: T::AccountId,
10
		) -> DispatchResultWithPostInfo {
10
			let delegator = ensure_signed(origin)?;
10
			Self::delegation_cancel_request(candidate, delegator)
		}
		/// Sets the auto-compounding reward percentage for a delegation.
		#[pallet::call_index(27)]
		#[pallet::weight(<T as Config>::WeightInfo::set_auto_compound(
			*candidate_auto_compounding_delegation_count_hint,
			*delegation_count_hint,
		))]
		pub fn set_auto_compound(
			origin: OriginFor<T>,
			candidate: T::AccountId,
			value: Percent,
			candidate_auto_compounding_delegation_count_hint: u32,
			delegation_count_hint: u32,
21
		) -> DispatchResultWithPostInfo {
21
			let delegator = ensure_signed(origin)?;
21
			<AutoCompoundDelegations<T>>::set_auto_compound(
21
				candidate,
21
				delegator,
21
				value,
21
				candidate_auto_compounding_delegation_count_hint,
21
				delegation_count_hint,
			)
		}
		/// Notify a collator is inactive during MaxOfflineRounds
		#[pallet::call_index(29)]
		#[pallet::weight(<T as Config>::WeightInfo::notify_inactive_collator())]
		pub fn notify_inactive_collator(
			origin: OriginFor<T>,
			collator: T::AccountId,
6
		) -> DispatchResult {
6
			ensure!(
6
				<EnableMarkingOffline<T>>::get(),
				<Error<T>>::MarkingOfflineNotEnabled
			);
6
			ensure_signed(origin)?;
6
			let mut collators_len = 0usize;
6
			let max_collators = <TotalSelected<T>>::get();
6
			if let Some(len) = <SelectedCandidates<T>>::decode_len() {
6
				collators_len = len;
6
			};
			// Check collators length is not below or eq to 66% of max_collators.
			// We use saturating logic here with (2/3)
			// as it is dangerous to use floating point numbers directly.
6
			ensure!(
6
				collators_len * 3 > (max_collators * 2) as usize,
1
				<Error<T>>::TooLowCollatorCountToNotifyAsInactive
			);
5
			let round_info = <Round<T>>::get();
5
			let max_offline_rounds = T::MaxOfflineRounds::get();
5
			ensure!(
5
				round_info.current > max_offline_rounds,
1
				<Error<T>>::CurrentRoundTooLow
			);
			// Have rounds_to_check = [8,9]
			// in case we are in round 10 for instance
			// with MaxOfflineRounds = 2
4
			let first_round_to_check = round_info.current.saturating_sub(max_offline_rounds);
4
			let rounds_to_check = first_round_to_check..round_info.current;
			// If this counter is eq to max_offline_rounds,
			// the collator should be notified as inactive
4
			let mut inactive_counter: RoundIndex = 0u32;
			// Iter rounds and check whether the collator has been inactive
12
			for r in rounds_to_check {
8
				if <WasInactive<T>>::get(r, &collator).is_some() {
4
					inactive_counter = inactive_counter.saturating_add(1);
4
				}
			}
4
			if inactive_counter == max_offline_rounds {
2
				let _ = T::OnInactiveCollator::on_inactive_collator(
2
					collator.clone(),
2
					round_info.current.saturating_sub(1),
2
				);
2
			} else {
2
				return Err(<Error<T>>::CannotBeNotifiedAsInactive.into());
			}
2
			Ok(().into())
		}
		/// Enable/Disable marking offline feature
		#[pallet::call_index(30)]
		#[pallet::weight(
			Weight::from_parts(3_000_000u64, 4_000u64)
				.saturating_add(T::DbWeight::get().writes(1u64))
		)]
3
		pub fn enable_marking_offline(origin: OriginFor<T>, value: bool) -> DispatchResult {
3
			ensure_root(origin)?;
2
			<EnableMarkingOffline<T>>::set(value);
2
			Ok(())
		}
		/// Force join the set of collator candidates.
		/// It will skip the minimum required bond check.
		#[pallet::call_index(31)]
		#[pallet::weight(<T as Config>::WeightInfo::join_candidates(*candidate_count))]
		pub fn force_join_candidates(
			origin: OriginFor<T>,
			account: T::AccountId,
			bond: BalanceOf<T>,
			candidate_count: u32,
1
		) -> DispatchResultWithPostInfo {
1
			T::MonetaryGovernanceOrigin::ensure_origin(origin.clone())?;
1
			Self::join_candidates_inner(account, bond, candidate_count)
		}
		/// Set the inflation distribution configuration.
		#[pallet::call_index(32)]
		#[pallet::weight(<T as Config>::WeightInfo::set_inflation_distribution_config())]
		pub fn set_inflation_distribution_config(
			origin: OriginFor<T>,
			new: InflationDistributionConfig<T::AccountId>,
30
		) -> DispatchResultWithPostInfo {
30
			T::MonetaryGovernanceOrigin::ensure_origin(origin)?;
29
			let old = <InflationDistributionInfo<T>>::get().0;
29
			let new = new.0;
29
			ensure!(old != new, Error::<T>::NoWritingSameValue);
56
			let total_percent = new.iter().fold(0, |acc, x| acc + x.percent.deconstruct());
28
			ensure!(
28
				total_percent <= 100,
8
				Error::<T>::TotalInflationDistributionPercentExceeds100,
			);
20
			<InflationDistributionInfo<T>>::put::<InflationDistributionConfig<T::AccountId>>(
20
				new.clone().into(),
			);
20
			Self::deposit_event(Event::InflationDistributionConfigUpdated {
20
				old: old.into(),
20
				new: new.into(),
20
			});
20
			Ok(().into())
		}
	}
	/// Represents a payout made via `pay_one_collator_reward`.
	pub(crate) enum RewardPayment {
		/// A collator was paid
		Paid,
		/// A collator was skipped for payment. This can happen if they haven't been awarded any
		/// points, that is, they did not produce any blocks.
		Skipped,
		/// All collator payments have been processed.
		Finished,
	}
	impl<T: Config> Pallet<T> {
		/// Set freeze
		///
		/// `is_collator` determines whether the account is a collator or delegator
1313
		pub(crate) fn freeze_extended(
1313
			account: &T::AccountId,
1313
			amount: BalanceOf<T>,
1313
			is_collator: bool,
1313
		) -> DispatchResult {
			use frame_support::traits::fungible::MutateFreeze;
			// Now set the freeze
1313
			let freeze_reason = if is_collator {
658
				FreezeReason::StakingCollator
			} else {
655
				FreezeReason::StakingDelegator
			};
1313
			T::Currency::set_freeze(&freeze_reason.into(), account, amount)
1313
		}
		/// `is_collator` determines whether the account is a collator or delegator
53
		pub(crate) fn thaw_extended(account: &T::AccountId, is_collator: bool) -> DispatchResult {
			use frame_support::traits::fungible::MutateFreeze;
			// Now thaw the freeze
53
			let freeze_reason = if is_collator {
21
				FreezeReason::StakingCollator
			} else {
32
				FreezeReason::StakingDelegator
			};
53
			T::Currency::thaw(&freeze_reason.into(), account)
53
		}
		/// Get frozen balance
		///
		/// `is_collator` determines whether the account is a collator or delegator
20051
		pub(crate) fn balance_frozen_extended(
20051
			account: &T::AccountId,
20051
			is_collator: bool,
20051
		) -> Option<BalanceOf<T>> {
			// Now return the frozen balance
20051
			if is_collator {
1301
				<CandidateInfo<T>>::get(account).map(|info| info.bond)
			} else {
18750
				<DelegatorState<T>>::get(account).map(|state| state.total)
			}
20051
		}
689
		pub fn is_delegator(acc: &T::AccountId) -> bool {
689
			<DelegatorState<T>>::get(acc).is_some()
689
		}
9686
		pub fn is_candidate(acc: &T::AccountId) -> bool {
9686
			<CandidateInfo<T>>::get(acc).is_some()
9686
		}
2
		pub fn is_selected_candidate(acc: &T::AccountId) -> bool {
2
			<SelectedCandidates<T>>::get().binary_search(acc).is_ok()
2
		}
664
		pub fn join_candidates_inner(
664
			acc: T::AccountId,
664
			bond: BalanceOf<T>,
664
			candidate_count: u32,
664
		) -> DispatchResultWithPostInfo {
664
			ensure!(!Self::is_candidate(&acc), Error::<T>::CandidateExists);
660
			ensure!(!Self::is_delegator(&acc), Error::<T>::DelegatorExists);
656
			let mut candidates = <CandidatePool<T>>::get();
656
			let old_count = candidates.0.len() as u32;
656
			ensure!(
656
				candidate_count >= old_count,
5
				Error::<T>::TooLowCandidateCountWeightHintJoinCandidates
			);
651
			let maybe_inserted_candidate = candidates
651
				.try_insert(Bond {
651
					owner: acc.clone(),
651
					amount: bond,
651
				})
651
				.map_err(|_| Error::<T>::CandidateLimitReached)?;
650
			ensure!(maybe_inserted_candidate, Error::<T>::CandidateExists);
650
			ensure!(
650
				Self::get_collator_stakable_free_balance(&acc) >= bond,
4
				Error::<T>::InsufficientBalance,
			);
646
			Self::freeze_extended(&acc, bond, true).map_err(|_| Error::<T>::InsufficientBalance)?;
646
			let candidate = CandidateMetadata::new(bond);
646
			<CandidateInfo<T>>::insert(&acc, candidate);
646
			let empty_delegations: Delegations<T::AccountId, BalanceOf<T>> = Default::default();
			// insert empty top delegations
646
			<TopDelegations<T>>::insert(&acc, empty_delegations.clone());
			// insert empty bottom delegations
646
			<BottomDelegations<T>>::insert(&acc, empty_delegations);
646
			<CandidatePool<T>>::put(candidates);
646
			let new_total = <Total<T>>::get().saturating_add(bond);
646
			<Total<T>>::put(new_total);
646
			Self::deposit_event(Event::JoinedCollatorCandidates {
646
				account: acc,
646
				amount_locked: bond,
646
				new_total_amt_locked: new_total,
646
			});
646
			Ok(().into())
664
		}
16
		pub fn go_offline_inner(collator: T::AccountId) -> DispatchResultWithPostInfo {
16
			let mut state = <CandidateInfo<T>>::get(&collator).ok_or(Error::<T>::CandidateDNE)?;
15
			let mut candidates = <CandidatePool<T>>::get();
15
			let actual_weight = <T as Config>::WeightInfo::go_offline(candidates.0.len() as u32);
15
			ensure!(
15
				state.is_active(),
1
				DispatchErrorWithPostInfo {
1
					post_info: Some(actual_weight).into(),
1
					error: <Error<T>>::AlreadyOffline.into(),
1
				}
			);
14
			state.go_offline();
14
			if candidates.remove(&Bond::from_owner(collator.clone())) {
14
				<CandidatePool<T>>::put(candidates);
14
			}
14
			<CandidateInfo<T>>::insert(&collator, state);
14
			Self::deposit_event(Event::CandidateWentOffline {
14
				candidate: collator,
14
			});
14
			Ok(Some(actual_weight).into())
16
		}
7
		pub fn go_online_inner(collator: T::AccountId) -> DispatchResultWithPostInfo {
7
			let mut state = <CandidateInfo<T>>::get(&collator).ok_or(Error::<T>::CandidateDNE)?;
6
			let mut candidates = <CandidatePool<T>>::get();
6
			let actual_weight = <T as Config>::WeightInfo::go_online(candidates.0.len() as u32);
6
			ensure!(
6
				!state.is_active(),
1
				DispatchErrorWithPostInfo {
1
					post_info: Some(actual_weight).into(),
1
					error: <Error<T>>::AlreadyActive.into(),
1
				}
			);
5
			ensure!(
5
				!state.is_leaving(),
1
				DispatchErrorWithPostInfo {
1
					post_info: Some(actual_weight).into(),
1
					error: <Error<T>>::CannotGoOnlineIfLeaving.into(),
1
				}
			);
4
			state.go_online();
4
			let maybe_inserted_candidate = candidates
4
				.try_insert(Bond {
4
					owner: collator.clone(),
4
					amount: state.total_counted,
4
				})
4
				.map_err(|_| Error::<T>::CandidateLimitReached)?;
4
			ensure!(
4
				maybe_inserted_candidate,
				DispatchErrorWithPostInfo {
					post_info: Some(actual_weight).into(),
					error: <Error<T>>::AlreadyActive.into(),
				},
			);
4
			<CandidatePool<T>>::put(candidates);
4
			<CandidateInfo<T>>::insert(&collator, state);
4
			Self::deposit_event(Event::CandidateBackOnline {
4
				candidate: collator,
4
			});
4
			Ok(Some(actual_weight).into())
7
		}
6
		pub fn candidate_bond_more_inner(
6
			collator: T::AccountId,
6
			more: BalanceOf<T>,
6
		) -> DispatchResultWithPostInfo {
6
			let mut state = <CandidateInfo<T>>::get(&collator).ok_or(Error::<T>::CandidateDNE)?;
6
			let actual_weight =
6
				<T as Config>::WeightInfo::candidate_bond_more(T::MaxCandidates::get());
6
			state
6
				.bond_more::<T>(collator.clone(), more)
6
				.map_err(|err| DispatchErrorWithPostInfo {
					post_info: Some(actual_weight).into(),
					error: err,
				})?;
6
			let (is_active, total_counted) = (state.is_active(), state.total_counted);
6
			<CandidateInfo<T>>::insert(&collator, state);
6
			if is_active {
6
				Self::update_active(collator, total_counted);
6
			}
6
			Ok(Some(actual_weight).into())
6
		}
6
		pub fn execute_candidate_bond_less_inner(
6
			candidate: T::AccountId,
6
		) -> DispatchResultWithPostInfo {
6
			let mut state = <CandidateInfo<T>>::get(&candidate).ok_or(Error::<T>::CandidateDNE)?;
6
			let actual_weight =
6
				<T as Config>::WeightInfo::execute_candidate_bond_less(T::MaxCandidates::get());
6
			state
6
				.execute_bond_less::<T>(candidate.clone())
6
				.map_err(|err| DispatchErrorWithPostInfo {
					post_info: Some(actual_weight).into(),
					error: err,
				})?;
6
			<CandidateInfo<T>>::insert(&candidate, state);
6
			Ok(Some(actual_weight).into())
6
		}
23
		pub fn execute_leave_candidates_inner(
23
			candidate: T::AccountId,
23
		) -> DispatchResultWithPostInfo {
23
			let state = <CandidateInfo<T>>::get(&candidate).ok_or(Error::<T>::CandidateDNE)?;
23
			let actual_auto_compound_delegation_count =
23
				<AutoCompoundingDelegations<T>>::decode_len(&candidate).unwrap_or_default() as u32;
			// TODO use these to return actual weight used via `execute_leave_candidates_ideal`
23
			let actual_delegation_count = state.delegation_count;
23
			let actual_weight = <T as Config>::WeightInfo::execute_leave_candidates_ideal(
23
				actual_delegation_count,
23
				actual_auto_compound_delegation_count,
			);
23
			state
23
				.can_leave::<T>()
23
				.map_err(|err| DispatchErrorWithPostInfo {
2
					post_info: Some(actual_weight).into(),
2
					error: err,
2
				})?;
21
			let return_stake = |bond: Bond<T::AccountId, BalanceOf<T>>| -> DispatchResult {
				// remove delegation from delegator state
15
				let mut delegator = DelegatorState::<T>::get(&bond.owner).expect(
15
					"Collator state and delegator state are consistent.
15
						Collator state has a record of this delegation. Therefore,
15
						Delegator state also has a record. qed.",
				);
15
				if let Some(remaining) = delegator.rm_delegation::<T>(&candidate) {
15
					Self::delegation_remove_request_with_state(
15
						&candidate,
15
						&bond.owner,
15
						&mut delegator,
					);
15
					<AutoCompoundDelegations<T>>::remove_auto_compound(&candidate, &bond.owner);
15
					if remaining.is_zero() {
						// we do not remove the scheduled delegation requests from other collators
						// since it is assumed that they were removed incrementally before only the
						// last delegation was left.
9
						<DelegatorState<T>>::remove(&bond.owner);
						// Thaw all frozen funds for delegator
9
						Self::thaw_extended(&bond.owner, false)?;
6
					} else {
6
						<DelegatorState<T>>::insert(&bond.owner, delegator);
6
					}
				} else {
					Self::thaw_extended(&bond.owner, false)?;
				}
15
				Ok(())
15
			};
			// total backing stake is at least the candidate self bond
21
			let mut total_backing = state.bond;
			// return all top delegations
21
			let top_delegations =
21
				<TopDelegations<T>>::take(&candidate).expect("CandidateInfo existence checked");
35
			for bond in top_delegations.delegations {
14
				return_stake(bond)?;
			}
21
			total_backing = total_backing.saturating_add(top_delegations.total);
			// return all bottom delegations
21
			let bottom_delegations =
21
				<BottomDelegations<T>>::take(&candidate).expect("CandidateInfo existence checked");
22
			for bond in bottom_delegations.delegations {
1
				return_stake(bond)?;
			}
21
			total_backing = total_backing.saturating_add(bottom_delegations.total);
			// Thaw all frozen funds for collator
21
			Self::thaw_extended(&candidate, true)?;
21
			<CandidateInfo<T>>::remove(&candidate);
			// Remove all scheduled delegation requests for this collator
21
			let _ = <DelegationScheduledRequests<T>>::clear_prefix(
21
				&candidate,
21
				Self::max_delegators_per_candidate(),
21
				None,
21
			);
21
			<DelegationScheduledRequestsPerCollator<T>>::remove(&candidate);
21
			<AutoCompoundingDelegations<T>>::remove(&candidate);
21
			<TopDelegations<T>>::remove(&candidate);
21
			<BottomDelegations<T>>::remove(&candidate);
21
			let new_total_staked = <Total<T>>::get().saturating_sub(total_backing);
21
			<Total<T>>::put(new_total_staked);
21
			Self::deposit_event(Event::CandidateLeft {
21
				ex_candidate: candidate,
21
				unlocked_amount: total_backing,
21
				new_total_amt_locked: new_total_staked,
21
			});
21
			Ok(Some(actual_weight).into())
23
		}
103
		pub fn max_delegators_per_candidate() -> u32 {
103
			AddGet::<T::MaxTopDelegationsPerCandidate, T::MaxBottomDelegationsPerCandidate>::get()
103
		}
		/// Returns an account's stakable balance (including the reserved) which is not frozen in delegation staking
18750
		pub fn get_delegator_stakable_balance(acc: &T::AccountId) -> BalanceOf<T> {
18750
			let total_balance =
18750
				T::Currency::balance(acc).saturating_add(T::Currency::total_balance_on_hold(acc));
18750
			if let Some(frozen_balance) = Self::balance_frozen_extended(acc, false) {
229
				return total_balance.saturating_sub(frozen_balance);
18521
			}
18521
			total_balance
18750
		}
		/// Returns an account's free balance which is not frozen in collator staking
1301
		pub fn get_collator_stakable_free_balance(acc: &T::AccountId) -> BalanceOf<T> {
1301
			let total_balance = T::Currency::balance(acc);
1301
			if let Some(frozen_balance) = Self::balance_frozen_extended(acc, true) {
19
				return total_balance.saturating_sub(frozen_balance);
1282
			}
1282
			total_balance
1301
		}
		/// Returns a delegations auto-compound value.
5
		pub fn delegation_auto_compound(
5
			candidate: &T::AccountId,
5
			delegator: &T::AccountId,
5
		) -> Percent {
5
			<AutoCompoundDelegations<T>>::auto_compound(candidate, delegator)
5
		}
		/// Caller must ensure candidate is active before calling
526
		pub(crate) fn update_active(candidate: T::AccountId, total: BalanceOf<T>) {
526
			let mut candidates = <CandidatePool<T>>::get();
526
			candidates.remove(&Bond::from_owner(candidate.clone()));
526
			candidates
526
				.try_insert(Bond {
526
					owner: candidate,
526
					amount: total,
526
				})
526
				.expect(
526
					"the candidate is removed in previous step so the length cannot increase; qed",
				);
526
			<CandidatePool<T>>::put(candidates);
526
		}
		/// Compute round issuance based on duration of the given round
263
		fn compute_issuance(round_duration: u64, round_length: u32) -> BalanceOf<T> {
263
			let ideal_duration: BalanceOf<T> = round_length
263
				.saturating_mul(T::BlockTime::get() as u32)
263
				.into();
263
			let config = <InflationConfig<T>>::get();
263
			let round_issuance = crate::inflation::round_issuance_range::<T>(config.round);
			// Initial formula: (round_duration / ideal_duration) * ideal_issuance
			// We multiply before the division to reduce rounding effects
263
			BalanceOf::<T>::from(round_duration as u32).saturating_mul(round_issuance.ideal)
263
				/ (ideal_duration)
263
		}
		/// Remove delegation from candidate state
		/// Amount input should be retrieved from delegator and it informs the storage lookups
23
		pub(crate) fn delegator_leaves_candidate(
23
			candidate: T::AccountId,
23
			delegator: T::AccountId,
23
			amount: BalanceOf<T>,
23
		) -> DispatchResult {
23
			let mut state = <CandidateInfo<T>>::get(&candidate).ok_or(Error::<T>::CandidateDNE)?;
23
			state.rm_delegation_if_exists::<T>(&candidate, delegator.clone(), amount)?;
23
			let new_total_locked = <Total<T>>::get().saturating_sub(amount);
23
			<Total<T>>::put(new_total_locked);
23
			let new_total = state.total_counted;
23
			<CandidateInfo<T>>::insert(&candidate, state);
23
			Self::deposit_event(Event::DelegatorLeftCandidate {
23
				delegator: delegator,
23
				candidate: candidate,
23
				unstaked_amount: amount,
23
				total_candidate_staked: new_total,
23
			});
23
			Ok(())
23
		}
263
		pub(crate) fn prepare_staking_payouts(
263
			round_info: RoundInfo<BlockNumberFor<T>>,
263
			round_duration: u64,
263
		) -> Weight {
			let RoundInfo {
263
				current: now,
263
				length: round_length,
				..
263
			} = round_info;
			// This function is called right after the round index increment,
			// and the goal is to compute the payout informations for the round that just ended.
			// We don't need to saturate here because the genesis round is 1.
263
			let prepare_payout_for_round = now - 1;
			// Return early if there is no blocks for this round
263
			if <Points<T>>::get(prepare_payout_for_round).is_zero() {
				return Weight::zero();
263
			}
			// Compute total issuance based on round duration
263
			let total_issuance = Self::compute_issuance(round_duration, round_length);
			// reserve portion of issuance for parachain bond account
263
			let mut left_issuance = total_issuance;
263
			let configs = <InflationDistributionInfo<T>>::get().0;
526
			for (index, config) in configs.iter().enumerate() {
526
				if config.percent.is_zero() {
263
					continue;
263
				}
263
				let reserve = config.percent * total_issuance;
263
				if frame_system::Pallet::<T>::account_exists(&config.account) {
6
					if let Ok(minted) = T::Currency::mint_into(&config.account, reserve) {
6
						// update round issuance if minting succeeds
6
						left_issuance = left_issuance.saturating_sub(minted);
6
						Self::deposit_event(Event::InflationDistributed {
6
							index: index as u32,
6
							account: config.account.clone(),
6
							value: minted,
6
						});
6
					}
257
				}
			}
263
			let payout = DelayedPayout {
263
				round_issuance: total_issuance,
263
				total_staking_reward: left_issuance,
263
				collator_commission: <CollatorCommission<T>>::get(),
263
			};
263
			<DelayedPayouts<T>>::insert(prepare_payout_for_round, payout);
263
			<T as Config>::WeightInfo::prepare_staking_payouts()
263
		}
		/// Wrapper around pay_one_collator_reward which handles the following logic:
		/// * whether or not a payout needs to be made
		/// * cleaning up when payouts are done
		/// * returns the weight consumed by pay_one_collator_reward if applicable
30428
		fn handle_delayed_payouts(now: RoundIndex) -> Weight {
30428
			let delay = T::RewardPaymentDelay::get();
			// don't underflow uint
30428
			if now < delay {
16487
				return Weight::from_parts(0u64, 0);
13941
			}
13941
			let paid_for_round = now.saturating_sub(delay);
13941
			if let Some(payout_info) = <DelayedPayouts<T>>::get(paid_for_round) {
352
				let result = Self::pay_one_collator_reward(paid_for_round, payout_info);
				// clean up storage items that we no longer need
352
				if matches!(result.0, RewardPayment::Finished) {
63
					<DelayedPayouts<T>>::remove(paid_for_round);
63
					<Points<T>>::remove(paid_for_round);
296
				}
352
				result.1 // weight consumed by pay_one_collator_reward
			} else {
13589
				Weight::from_parts(0u64, 0)
			}
30428
		}
		/// Payout a single collator from the given round.
		///
		/// Returns an optional tuple of (Collator's AccountId, total paid)
		/// or None if there were no more payouts to be made for the round.
352
		pub(crate) fn pay_one_collator_reward(
352
			paid_for_round: RoundIndex,
352
			payout_info: DelayedPayout<BalanceOf<T>>,
352
		) -> (RewardPayment, Weight) {
			// 'early_weight' tracks weight used for reads/writes done early in this fn before its
			// early-exit codepaths.
352
			let mut early_weight = Weight::zero();
			// TODO: it would probably be optimal to roll Points into the DelayedPayouts storage
			// item so that we do fewer reads each block
352
			let total_points = <Points<T>>::get(paid_for_round);
352
			early_weight = early_weight.saturating_add(T::DbWeight::get().reads_writes(1, 0));
352
			if total_points.is_zero() {
				// TODO: this case is obnoxious... it's a value query, so it could mean one of two
				// different logic errors:
				// 1. we removed it before we should have
				// 2. we called pay_one_collator_reward when we were actually done with deferred
				//    payouts
				log::warn!("pay_one_collator_reward called with no <Points<T>> for the round!");
				return (RewardPayment::Finished, early_weight);
352
			}
352
			let collator_fee = payout_info.collator_commission;
352
			let collator_issuance = collator_fee * payout_info.round_issuance;
289
			if let Some((collator, state)) =
352
				<AtStake<T>>::iter_prefix(paid_for_round).drain().next()
			{
				// read and kill AtStake
289
				early_weight = early_weight.saturating_add(T::DbWeight::get().reads_writes(1, 1));
				// Take the awarded points for the collator
289
				let pts = <AwardedPts<T>>::take(paid_for_round, &collator);
				// read and kill AwardedPts
289
				early_weight = early_weight.saturating_add(T::DbWeight::get().reads_writes(1, 1));
289
				if pts == 0 {
233
					return (RewardPayment::Skipped, early_weight);
56
				}
				// 'extra_weight' tracks weight returned from fns that we delegate to which can't be
				// known ahead of time.
56
				let mut extra_weight = Weight::zero();
56
				let pct_due = Perbill::from_rational(pts, total_points);
56
				let total_paid = pct_due * payout_info.total_staking_reward;
56
				let mut amt_due = total_paid;
56
				let num_delegators = state.delegations.len();
56
				let mut num_paid_delegations = 0u32;
56
				let mut num_auto_compounding = 0u32;
56
				if state.delegations.is_empty() {
21
					// solo collator with no delegators
21
					extra_weight = extra_weight
21
						.saturating_add(T::PayoutCollatorReward::payout_collator_reward(
21
							paid_for_round,
21
							collator.clone(),
21
							amt_due,
21
						))
21
						.saturating_add(T::OnCollatorPayout::on_collator_payout(
21
							paid_for_round,
21
							collator.clone(),
21
							amt_due,
21
						));
21
				} else {
					// pay collator first; commission + due_portion
35
					let collator_pct = Perbill::from_rational(state.bond, state.total);
35
					let commission = pct_due * collator_issuance;
35
					amt_due = amt_due.saturating_sub(commission);
35
					let collator_reward = (collator_pct * amt_due).saturating_add(commission);
35
					extra_weight = extra_weight
35
						.saturating_add(T::PayoutCollatorReward::payout_collator_reward(
35
							paid_for_round,
35
							collator.clone(),
35
							collator_reward,
35
						))
35
						.saturating_add(T::OnCollatorPayout::on_collator_payout(
35
							paid_for_round,
35
							collator.clone(),
35
							collator_reward,
35
						));
					// pay delegators due portion
					for BondWithAutoCompound {
61
						owner,
61
						amount,
61
						auto_compound,
96
					} in state.delegations
					{
61
						let percent = Perbill::from_rational(amount, state.total);
61
						let due = percent * amt_due;
61
						if !due.is_zero() {
55
							num_auto_compounding += if auto_compound.is_zero() { 0 } else { 1 };
55
							num_paid_delegations += 1u32;
55
							Self::mint_and_compound(
55
								due,
55
								auto_compound.clone(),
55
								collator.clone(),
55
								owner.clone(),
							);
6
						}
					}
				}
56
				extra_weight = extra_weight.saturating_add(
56
					<T as Config>::WeightInfo::pay_one_collator_reward_best(
56
						num_paid_delegations,
56
						num_auto_compounding,
56
					),
56
				);
56
				(
56
					RewardPayment::Paid,
56
					<T as Config>::WeightInfo::pay_one_collator_reward(num_delegators as u32)
56
						.saturating_add(extra_weight),
56
				)
			} else {
				// Note that we don't clean up storage here; it is cleaned up in
				// handle_delayed_payouts()
63
				(RewardPayment::Finished, Weight::from_parts(0u64, 0))
			}
352
		}
		/// Compute the top `TotalSelected` candidates in the CandidatePool and return
		/// a vec of their AccountIds (sorted by AccountId).
		///
		/// If the returned vec is empty, the previous candidates should be used.
768
		pub fn compute_top_candidates() -> Vec<T::AccountId> {
768
			let top_n = <TotalSelected<T>>::get() as usize;
768
			if top_n == 0 {
				return vec![];
768
			}
768
			let candidates = <CandidatePool<T>>::get().0;
			// If the number of candidates is greater than top_n, select the candidates with higher
			// amount. Otherwise, return all the candidates.
768
			if candidates.len() > top_n {
				// Partially sort candidates such that element at index `top_n - 1` is sorted, and
				// all the elements in the range 0..top_n are the top n elements.
15
				let sorted_candidates = candidates
15
					.try_mutate(|inner| {
1083
						inner.select_nth_unstable_by(top_n - 1, |a, b| {
							// Order by amount, then owner. The owner is needed to ensure a stable order
							// when two accounts have the same amount.
1083
							a.amount
1083
								.cmp(&b.amount)
1083
								.then_with(|| a.owner.cmp(&b.owner))
1083
								.reverse()
1083
						});
15
					})
15
					.expect("sort cannot increase item count; qed");
15
				let mut collators = sorted_candidates
15
					.into_iter()
15
					.take(top_n)
15
					.map(|x| x.owner)
15
					.collect::<Vec<_>>();
				// Sort collators by AccountId
15
				collators.sort();
15
				collators
			} else {
				// Return all candidates
				// The candidates are already sorted by AccountId, so no need to sort again
753
				candidates.into_iter().map(|x| x.owner).collect::<Vec<_>>()
			}
768
		}
		/// Best as in most cumulatively supported in terms of stake
		/// Returns [collator_count, delegation_count, total staked]
767
		pub(crate) fn select_top_candidates(now: RoundIndex) -> (Weight, u32, u32, BalanceOf<T>) {
767
			let (mut collator_count, mut delegation_count, mut total) =
767
				(0u32, 0u32, BalanceOf::<T>::zero());
			// choose the top TotalSelected qualified candidates, ordered by stake
767
			let collators = Self::compute_top_candidates();
767
			if collators.is_empty() {
				// SELECTION FAILED TO SELECT >=1 COLLATOR => select collators from previous round
290
				let last_round = now.saturating_sub(1u32);
290
				let mut total_per_candidate: BTreeMap<T::AccountId, BalanceOf<T>> = BTreeMap::new();
				// set this round AtStake to last round AtStake
290
				for (account, snapshot) in <AtStake<T>>::iter_prefix(last_round) {
43
					collator_count = collator_count.saturating_add(1u32);
43
					delegation_count =
43
						delegation_count.saturating_add(snapshot.delegations.len() as u32);
43
					total = total.saturating_add(snapshot.total);
43
					total_per_candidate.insert(account.clone(), snapshot.total);
43
					<AtStake<T>>::insert(now, account, snapshot);
43
				}
				// `SelectedCandidates` remains unchanged from last round
				// emit CollatorChosen event for tools that use this event
290
				for candidate in <SelectedCandidates<T>>::get() {
43
					let snapshot_total = total_per_candidate
43
						.get(&candidate)
43
						.expect("all selected candidates have snapshots");
43
					Self::deposit_event(Event::CollatorChosen {
43
						round: now,
43
						collator_account: candidate,
43
						total_exposed_amount: *snapshot_total,
43
					})
				}
290
				let weight = <T as Config>::WeightInfo::select_top_candidates(0, 0);
290
				return (weight, collator_count, delegation_count, total);
477
			}
			// snapshot exposure for round for weighting reward distribution
922
			for account in collators.iter() {
922
				let state = <CandidateInfo<T>>::get(account)
922
					.expect("all members of CandidateQ must be candidates");
922
				collator_count = collator_count.saturating_add(1u32);
922
				delegation_count = delegation_count.saturating_add(state.delegation_count);
922
				total = total.saturating_add(state.total_counted);
				let CountedDelegations {
922
					uncounted_stake,
922
					rewardable_delegations,
922
				} = Self::get_rewardable_delegators(&account);
922
				let total_counted = state.total_counted.saturating_sub(uncounted_stake);
922
				let auto_compounding_delegations = <AutoCompoundingDelegations<T>>::get(&account)
922
					.into_iter()
922
					.map(|x| (x.delegator, x.value))
922
					.collect::<BTreeMap<_, _>>();
922
				let rewardable_delegations = rewardable_delegations
922
					.into_iter()
922
					.map(|d| BondWithAutoCompound {
775
						owner: d.owner.clone(),
775
						amount: d.amount,
775
						auto_compound: auto_compounding_delegations
775
							.get(&d.owner)
775
							.cloned()
775
							.unwrap_or_else(|| Percent::zero()),
775
					})
922
					.collect();
922
				let snapshot = CollatorSnapshot {
922
					bond: state.bond,
922
					delegations: rewardable_delegations,
922
					total: total_counted,
922
				};
922
				<AtStake<T>>::insert(now, account, snapshot);
922
				Self::deposit_event(Event::CollatorChosen {
922
					round: now,
922
					collator_account: account.clone(),
922
					total_exposed_amount: state.total_counted,
922
				});
			}
			// insert canonical collator set
477
			<SelectedCandidates<T>>::put(
477
				BoundedVec::try_from(collators)
477
					.expect("subset of collators is always less than or equal to max candidates"),
			);
477
			let avg_delegator_count = delegation_count.checked_div(collator_count).unwrap_or(0);
477
			let weight = <T as Config>::WeightInfo::select_top_candidates(
477
				collator_count,
477
				avg_delegator_count,
			);
477
			(weight, collator_count, delegation_count, total)
767
		}
		/// Apply the delegator intent for revoke and decrease in order to build the
		/// effective list of delegators with their intended bond amount.
		///
		/// This will:
		/// - if [DelegationChange::Revoke] is outstanding, set the bond amount to 0.
		/// - if [DelegationChange::Decrease] is outstanding, subtract the bond by specified amount.
		/// - else, do nothing
		///
		/// The intended bond amounts will be used while calculating rewards.
922
		pub(crate) fn get_rewardable_delegators(collator: &T::AccountId) -> CountedDelegations<T> {
			// Aggregate the net effect of all scheduled requests per delegator for this collator.
			// If a revoke exists, it dominates and is treated as a full revoke.
			// Otherwise, decreases are summed.
922
			let mut requests: BTreeMap<T::AccountId, DelegationAction<BalanceOf<T>>> =
922
				BTreeMap::new();
115
			for (delegator, scheduled_requests) in
922
				<DelegationScheduledRequests<T>>::iter_prefix(collator)
			{
115
				if scheduled_requests.is_empty() {
					continue;
115
				}
				// Compute in a single pass whether any revoke exists and, if not,
				// the total amount of all decreases.
115
				let (has_revoke, total) = scheduled_requests.iter().fold(
115
					(false, BalanceOf::<T>::zero()),
117
					|(has_revoke, total), req| {
117
						let has_revoke =
117
							has_revoke || matches!(req.action, DelegationAction::Revoke(_));
117
						let total = if has_revoke {
							// Once a revoke is present, we ignore the accumulated decrease total.
75
							BalanceOf::<T>::zero()
						} else {
42
							total.saturating_add(req.action.amount())
						};
117
						(has_revoke, total)
117
					},
				);
115
				if has_revoke {
75
					// Amount is irrelevant for revokes in this context, since we always
75
					// zero out the bond and account the full previous stake as uncounted.
75
					requests.insert(delegator, DelegationAction::Revoke(BalanceOf::<T>::zero()));
75
				} else if !total.is_zero() {
40
					requests.insert(delegator, DelegationAction::Decrease(total));
40
				}
			}
922
			let mut uncounted_stake = BalanceOf::<T>::zero();
922
			let rewardable_delegations = <TopDelegations<T>>::get(collator)
922
				.expect("all members of CandidateQ must be candidates")
922
				.delegations
922
				.into_iter()
922
				.map(|mut bond| {
775
					bond.amount = match requests.get(&bond.owner) {
662
						None => bond.amount,
						Some(DelegationAction::Revoke(_)) => {
75
							uncounted_stake = uncounted_stake.saturating_add(bond.amount);
75
							BalanceOf::<T>::zero()
						}
38
						Some(DelegationAction::Decrease(amount)) => {
38
							uncounted_stake = uncounted_stake.saturating_add(*amount);
38
							bond.amount.saturating_sub(*amount)
						}
					};
775
					bond
775
				})
922
				.collect();
922
			CountedDelegations {
922
				uncounted_stake,
922
				rewardable_delegations,
922
			}
922
		}
		/// This function exists as a helper to delegator_bond_more & auto_compound functionality.
		/// Any changes to this function must align with both user-initiated bond increases and
		/// auto-compounding bond increases.
		/// Any feature-specific preconditions should be validated before this function is invoked.
		/// Any feature-specific events must be emitted after this function is invoked.
23
		pub fn delegation_bond_more_without_event(
23
			delegator: T::AccountId,
23
			candidate: T::AccountId,
23
			more: BalanceOf<T>,
23
		) -> Result<
23
			(bool, Weight),
23
			DispatchErrorWithPostInfo<frame_support::dispatch::PostDispatchInfo>,
23
		> {
23
			let mut state = <DelegatorState<T>>::get(&delegator).ok_or(Error::<T>::DelegatorDNE)?;
23
			ensure!(
23
				!Self::delegation_request_revoke_exists(&candidate, &delegator),
2
				Error::<T>::PendingDelegationRevoke
			);
			// This helper does not depend on the number of scheduled requests; we pass 0
			// here and rely on the extrinsic declaration for an upper bound.
21
			let actual_weight = <T as Config>::WeightInfo::delegator_bond_more(0);
21
			let in_top = state
21
				.increase_delegation::<T>(candidate.clone(), more)
21
				.map_err(|err| DispatchErrorWithPostInfo {
					post_info: Some(actual_weight).into(),
					error: err,
				})?;
21
			Ok((in_top, actual_weight))
23
		}
		/// Mint a specified reward amount to the beneficiary account. Emits the [Rewarded] event.
143
		pub fn mint(amt: BalanceOf<T>, to: &T::AccountId) -> Result<BalanceOf<T>, DispatchError> {
			// Mint rewards to the account
143
			let minted = T::Currency::mint_into(&to, amt)?;
143
			Self::deposit_event(Event::Rewarded {
143
				account: to.clone(),
143
				rewards: minted,
143
			});
143
			Ok(minted)
143
		}
		/// Mint a specified reward amount to the collator's account. Emits the [Rewarded] event.
88
		pub fn mint_collator_reward(
88
			_paid_for_round: RoundIndex,
88
			collator_id: T::AccountId,
88
			amt: BalanceOf<T>,
88
		) -> Weight {
			// Mint rewards to the collator
88
			if let Err(e) = Self::mint(amt, &collator_id) {
				log::warn!(
					"Failed to mint collator reward for {:?}: {:?}",
					collator_id,
					e
				);
88
			}
88
			<T as Config>::WeightInfo::mint_collator_reward()
88
		}
		/// Mint and compound delegation rewards. The function mints the amount towards the
		/// delegator and tries to compound a specified percent of it back towards the delegation.
		/// If a scheduled delegation revoke exists, then the amount is only minted, and nothing is
		/// compounded. Emits the [Compounded] event.
55
		pub fn mint_and_compound(
55
			amt: BalanceOf<T>,
55
			compound_percent: Percent,
55
			candidate: T::AccountId,
55
			delegator: T::AccountId,
55
		) {
			// Mint rewards to the delegator
55
			if frame_system::Pallet::<T>::account_exists(&delegator) {
55
				if let Ok(minted) = Self::mint(amt.clone(), &delegator) {
55
					let compound_amount = compound_percent.mul_ceil(minted);
55
					if compound_amount.is_zero() {
51
						return;
4
					}
4
					if let Err(err) = Self::delegation_bond_more_without_event(
4
						delegator.clone(),
4
						candidate.clone(),
4
						compound_amount.clone(),
4
					) {
1
						log::debug!(
							"skipped compounding staking reward towards candidate '{:?}' for delegator '{:?}': {:?}",
							candidate,
							delegator,
							err
						);
1
						return;
3
					};
3
					Pallet::<T>::deposit_event(Event::Compounded {
3
						delegator,
3
						candidate,
3
						amount: compound_amount.clone(),
3
					});
				};
			}
55
		}
		/// Add reward points to block authors:
		/// * 20 points to the block producer for producing a block in the chain
30632
		fn award_points_to_block_author() {
30632
			let author = T::BlockAuthor::get();
30632
			let now = <Round<T>>::get().current;
30632
			let score_plus_20 = <AwardedPts<T>>::get(now, &author).saturating_add(20);
30632
			<AwardedPts<T>>::insert(now, author, score_plus_20);
30632
			<Points<T>>::mutate(now, |x| *x = x.saturating_add(20));
30632
		}
		/// Marks collators as inactive for the previous round if they received zero awarded points.
263
		pub fn mark_collators_as_inactive(cur: RoundIndex) -> Weight {
			// This function is called after round index increment,
			// We don't need to saturate here because the genesis round is 1.
263
			let prev = cur - 1;
263
			let mut collators_at_stake_count = 0u32;
574
			for (account, _) in <AtStake<T>>::iter_prefix(prev) {
565
				collators_at_stake_count = collators_at_stake_count.saturating_add(1u32);
565
				if <AwardedPts<T>>::get(prev, &account).is_zero() {
469
					<WasInactive<T>>::insert(prev, account, ());
491
				}
			}
263
			<T as Config>::WeightInfo::mark_collators_as_inactive(collators_at_stake_count)
263
		}
		/// Cleans up historical staking information that is older than MaxOfflineRounds
		/// by removing entries from the WasIactive storage map.
30632
		fn cleanup_inactive_collator_info() {
30632
			let now = <Round<T>>::get().current;
30632
			let minimum_rounds_required = T::MaxOfflineRounds::get() + 1;
30632
			if now < minimum_rounds_required {
22907
				return;
7725
			}
7725
			let _ = <WasInactive<T>>::iter_prefix(now - minimum_rounds_required)
7725
				.drain()
7725
				.next();
30632
		}
	}
	impl<T: Config> nimbus_primitives::CanAuthor<T::AccountId> for Pallet<T> {
		fn can_author(account: &T::AccountId, _slot: &u32) -> bool {
			Self::is_selected_candidate(account)
		}
	}
	impl<T: Config> Get<Vec<T::AccountId>> for Pallet<T> {
57
		fn get() -> Vec<T::AccountId> {
57
			Self::selected_candidates().into_inner()
57
		}
	}
}