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
4034
#[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
54
	#[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 {
116
		/// Overarching event type
117
		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
118
		/// The fungible type for handling balances
119
		type Currency: Inspect<Self::AccountId>
120
			+ InspectHold<Self::AccountId>
121
			+ Mutate<Self::AccountId>
122
			+ MutateFreeze<Self::AccountId, Id = Self::RuntimeFreezeReason>
123
			+ Balanced<Self::AccountId>;
124
		/// The overarching freeze identifier type.
125
		type RuntimeFreezeReason: From<FreezeReason>;
126
		/// The origin for monetary governance
127
		type MonetaryGovernanceOrigin: EnsureOrigin<Self::RuntimeOrigin>;
128
		/// Minimum number of blocks per round
129
		#[pallet::constant]
130
		type MinBlocksPerRound: Get<u32>;
131
		/// If a collator doesn't produce any block on this number of rounds, it is notified as inactive.
132
		/// This value must be less than or equal to RewardPaymentDelay.
133
		#[pallet::constant]
134
		type MaxOfflineRounds: Get<u32>;
135
		/// Number of rounds that candidates remain bonded before exit request is executable
136
		#[pallet::constant]
137
		type LeaveCandidatesDelay: Get<RoundIndex>;
138
		/// Number of rounds candidate requests to decrease self-bond must wait to be executable
139
		#[pallet::constant]
140
		type CandidateBondLessDelay: Get<RoundIndex>;
141
		/// Number of rounds that delegators remain bonded before exit request is executable
142
		#[pallet::constant]
143
		type LeaveDelegatorsDelay: Get<RoundIndex>;
144
		/// Number of rounds that delegations remain bonded before revocation request is executable
145
		#[pallet::constant]
146
		type RevokeDelegationDelay: Get<RoundIndex>;
147
		/// Number of rounds that delegation less requests must wait before executable
148
		#[pallet::constant]
149
		type DelegationBondLessDelay: Get<RoundIndex>;
150
		/// Number of rounds after which block authors are rewarded
151
		#[pallet::constant]
152
		type RewardPaymentDelay: Get<RoundIndex>;
153
		/// Minimum number of selected candidates every round
154
		#[pallet::constant]
155
		type MinSelectedCandidates: Get<u32>;
156
		/// Maximum top delegations counted per candidate
157
		#[pallet::constant]
158
		type MaxTopDelegationsPerCandidate: Get<u32>;
159
		/// Maximum bottom delegations (not counted) per candidate
160
		#[pallet::constant]
161
		type MaxBottomDelegationsPerCandidate: Get<u32>;
162
		/// Maximum delegations per delegator
163
		#[pallet::constant]
164
		type MaxDelegationsPerDelegator: Get<u32>;
165
		/// Minimum stake required for any account to be a collator candidate
166
		#[pallet::constant]
167
		type MinCandidateStk: Get<BalanceOf<Self>>;
168
		/// Minimum stake for any registered on-chain account to delegate
169
		#[pallet::constant]
170
		type MinDelegation: Get<BalanceOf<Self>>;
171
		/// Get the current block author
172
		type BlockAuthor: Get<Self::AccountId>;
173
		/// Handler to notify the runtime when a collator is paid.
174
		/// If you don't need it, you can specify the type `()`.
175
		type OnCollatorPayout: OnCollatorPayout<Self::AccountId, BalanceOf<Self>>;
176
		/// Handler to distribute a collator's reward.
177
		/// To use the default implementation of minting rewards, specify the type `()`.
178
		type PayoutCollatorReward: PayoutCollatorReward<Self>;
179
		/// Handler to notify the runtime when a collator is inactive.
180
		/// The default behavior is to mark the collator as offline.
181
		/// If you need to use the default implementation, specify the type `()`.
182
		type OnInactiveCollator: OnInactiveCollator<Self>;
183
		/// Handler to notify the runtime when a new round begin.
184
		/// If you don't need it, you can specify the type `()`.
185
		type OnNewRound: OnNewRound;
186
		/// Get the current slot number
187
		type SlotProvider: Get<Slot>;
188
		/// Get the slot duration in milliseconds
189
		#[pallet::constant]
190
		type SlotDuration: Get<u64>;
191
		/// Get the average time between 2 blocks in milliseconds
192
		#[pallet::constant]
193
		type BlockTime: Get<u64>;
194
		/// Maximum candidates
195
		#[pallet::constant]
196
		type MaxCandidates: Get<u32>;
197
		/// Threshold after which inflation become linear
198
		/// If you don't want to use it, set it to `()`
199
		#[pallet::constant]
200
		type LinearInflationThreshold: Get<Option<BalanceOf<Self>>>;
201
		/// Weight information for extrinsics in this pallet.
202
		type WeightInfo: WeightInfo;
203
	}
204

            
205
	/// The reason for freezing funds.
206
	#[pallet::composite_enum]
207
	pub enum FreezeReason {
208
40
		/// Funds frozen for staking as a collator
209
		StakingCollator,
210
155
		/// Funds frozen for staking as a delegator
211
		StakingDelegator,
212
	}
213

            
214
19
	#[pallet::error]
215
	pub enum Error<T> {
216
		DelegatorDNE,
217
		DelegatorDNEinTopNorBottom,
218
		DelegatorDNEInDelegatorSet,
219
		CandidateDNE,
220
		DelegationDNE,
221
		DelegatorExists,
222
		CandidateExists,
223
		CandidateBondBelowMin,
224
		InsufficientBalance,
225
		DelegatorBondBelowMin,
226
		DelegationBelowMin,
227
		AlreadyOffline,
228
		AlreadyActive,
229
		DelegatorAlreadyLeaving,
230
		DelegatorNotLeaving,
231
		DelegatorCannotLeaveYet,
232
		CannotDelegateIfLeaving,
233
		CandidateAlreadyLeaving,
234
		CandidateNotLeaving,
235
		CandidateCannotLeaveYet,
236
		CannotGoOnlineIfLeaving,
237
		ExceedMaxDelegationsPerDelegator,
238
		AlreadyDelegatedCandidate,
239
		InvalidSchedule,
240
		CannotSetBelowMin,
241
		RoundLengthMustBeGreaterThanTotalSelectedCollators,
242
		NoWritingSameValue,
243
		TotalInflationDistributionPercentExceeds100,
244
		TooLowCandidateCountWeightHintJoinCandidates,
245
		TooLowCandidateCountWeightHintCancelLeaveCandidates,
246
		TooLowCandidateCountToLeaveCandidates,
247
		TooLowDelegationCountToDelegate,
248
		TooLowCandidateDelegationCountToDelegate,
249
		TooLowCandidateDelegationCountToLeaveCandidates,
250
		TooLowDelegationCountToLeaveDelegators,
251
		PendingCandidateRequestsDNE,
252
		PendingCandidateRequestAlreadyExists,
253
		PendingCandidateRequestNotDueYet,
254
		PendingDelegationRequestDNE,
255
		PendingDelegationRequestAlreadyExists,
256
		PendingDelegationRequestNotDueYet,
257
		CannotDelegateLessThanOrEqualToLowestBottomWhenFull,
258
		PendingDelegationRevoke,
259
		TooLowDelegationCountToAutoCompound,
260
		TooLowCandidateAutoCompoundingDelegationCountToAutoCompound,
261
		TooLowCandidateAutoCompoundingDelegationCountToDelegate,
262
		TooLowCollatorCountToNotifyAsInactive,
263
		CannotBeNotifiedAsInactive,
264
		TooLowCandidateAutoCompoundingDelegationCountToLeaveCandidates,
265
		TooLowCandidateCountWeightHint,
266
		TooLowCandidateCountWeightHintGoOffline,
267
		CandidateLimitReached,
268
		CannotSetAboveMaxCandidates,
269
		MarkingOfflineNotEnabled,
270
		CurrentRoundTooLow,
271
		EmptyMigrationBatch,
272
	}
273

            
274
19
	#[pallet::event]
275
3447
	#[pallet::generate_deposit(pub(crate) fn deposit_event)]
276
	pub enum Event<T: Config> {
277
		/// Started new round.
278
		NewRound {
279
			starting_block: BlockNumberFor<T>,
280
			round: RoundIndex,
281
			selected_collators_number: u32,
282
			total_balance: BalanceOf<T>,
283
		},
284
		/// Account joined the set of collator candidates.
285
		JoinedCollatorCandidates {
286
			account: T::AccountId,
287
			amount_locked: BalanceOf<T>,
288
			new_total_amt_locked: BalanceOf<T>,
289
		},
290
		/// Candidate selected for collators. Total Exposed Amount includes all delegations.
291
		CollatorChosen {
292
			round: RoundIndex,
293
			collator_account: T::AccountId,
294
			total_exposed_amount: BalanceOf<T>,
295
		},
296
		/// Candidate requested to decrease a self bond.
297
		CandidateBondLessRequested {
298
			candidate: T::AccountId,
299
			amount_to_decrease: BalanceOf<T>,
300
			execute_round: RoundIndex,
301
		},
302
		/// Candidate has increased a self bond.
303
		CandidateBondedMore {
304
			candidate: T::AccountId,
305
			amount: BalanceOf<T>,
306
			new_total_bond: BalanceOf<T>,
307
		},
308
		/// Candidate has decreased a self bond.
309
		CandidateBondedLess {
310
			candidate: T::AccountId,
311
			amount: BalanceOf<T>,
312
			new_bond: BalanceOf<T>,
313
		},
314
		/// Candidate temporarily leave the set of collator candidates without unbonding.
315
		CandidateWentOffline { candidate: T::AccountId },
316
		/// Candidate rejoins the set of collator candidates.
317
		CandidateBackOnline { candidate: T::AccountId },
318
		/// Candidate has requested to leave the set of candidates.
319
		CandidateScheduledExit {
320
			exit_allowed_round: RoundIndex,
321
			candidate: T::AccountId,
322
			scheduled_exit: RoundIndex,
323
		},
324
		/// Cancelled request to leave the set of candidates.
325
		CancelledCandidateExit { candidate: T::AccountId },
326
		/// Cancelled request to decrease candidate's bond.
327
		CancelledCandidateBondLess {
328
			candidate: T::AccountId,
329
			amount: BalanceOf<T>,
330
			execute_round: RoundIndex,
331
		},
332
		/// Candidate has left the set of candidates.
333
		CandidateLeft {
334
			ex_candidate: T::AccountId,
335
			unlocked_amount: BalanceOf<T>,
336
			new_total_amt_locked: BalanceOf<T>,
337
		},
338
		/// Delegator requested to decrease a bond for the collator candidate.
339
		DelegationDecreaseScheduled {
340
			delegator: T::AccountId,
341
			candidate: T::AccountId,
342
			amount_to_decrease: BalanceOf<T>,
343
			execute_round: RoundIndex,
344
		},
345
		// Delegation increased.
346
		DelegationIncreased {
347
			delegator: T::AccountId,
348
			candidate: T::AccountId,
349
			amount: BalanceOf<T>,
350
			in_top: bool,
351
		},
352
		// Delegation decreased.
353
		DelegationDecreased {
354
			delegator: T::AccountId,
355
			candidate: T::AccountId,
356
			amount: BalanceOf<T>,
357
			in_top: bool,
358
		},
359
		/// Delegator requested to leave the set of delegators.
360
		DelegatorExitScheduled {
361
			round: RoundIndex,
362
			delegator: T::AccountId,
363
			scheduled_exit: RoundIndex,
364
		},
365
		/// Delegator requested to revoke delegation.
366
		DelegationRevocationScheduled {
367
			round: RoundIndex,
368
			delegator: T::AccountId,
369
			candidate: T::AccountId,
370
			scheduled_exit: RoundIndex,
371
		},
372
		/// Delegator has left the set of delegators.
373
		DelegatorLeft {
374
			delegator: T::AccountId,
375
			unstaked_amount: BalanceOf<T>,
376
		},
377
		/// Delegation revoked.
378
		DelegationRevoked {
379
			delegator: T::AccountId,
380
			candidate: T::AccountId,
381
			unstaked_amount: BalanceOf<T>,
382
		},
383
		/// Delegation kicked.
384
		DelegationKicked {
385
			delegator: T::AccountId,
386
			candidate: T::AccountId,
387
			unstaked_amount: BalanceOf<T>,
388
		},
389
		/// Cancelled a pending request to exit the set of delegators.
390
		DelegatorExitCancelled { delegator: T::AccountId },
391
		/// Cancelled request to change an existing delegation.
392
		CancelledDelegationRequest {
393
			delegator: T::AccountId,
394
			cancelled_request: CancelledScheduledRequest<BalanceOf<T>>,
395
			collator: T::AccountId,
396
		},
397
		/// New delegation (increase of the existing one).
398
		Delegation {
399
			delegator: T::AccountId,
400
			locked_amount: BalanceOf<T>,
401
			candidate: T::AccountId,
402
			delegator_position: DelegatorAdded<BalanceOf<T>>,
403
			auto_compound: Percent,
404
		},
405
		/// Delegation from candidate state has been removed.
406
		DelegatorLeftCandidate {
407
			delegator: T::AccountId,
408
			candidate: T::AccountId,
409
			unstaked_amount: BalanceOf<T>,
410
			total_candidate_staked: BalanceOf<T>,
411
		},
412
		/// Paid the account (delegator or collator) the balance as liquid rewards.
413
		Rewarded {
414
			account: T::AccountId,
415
			rewards: BalanceOf<T>,
416
		},
417
		/// Transferred to account which holds funds reserved for parachain bond.
418
		InflationDistributed {
419
			index: u32,
420
			account: T::AccountId,
421
			value: BalanceOf<T>,
422
		},
423
		InflationDistributionConfigUpdated {
424
			old: InflationDistributionConfig<T::AccountId>,
425
			new: InflationDistributionConfig<T::AccountId>,
426
		},
427
		/// Annual inflation input (first 3) was used to derive new per-round inflation (last 3)
428
		InflationSet {
429
			annual_min: Perbill,
430
			annual_ideal: Perbill,
431
			annual_max: Perbill,
432
			round_min: Perbill,
433
			round_ideal: Perbill,
434
			round_max: Perbill,
435
		},
436
		/// Staking expectations set.
437
		StakeExpectationsSet {
438
			expect_min: BalanceOf<T>,
439
			expect_ideal: BalanceOf<T>,
440
			expect_max: BalanceOf<T>,
441
		},
442
		/// Set total selected candidates to this value.
443
		TotalSelectedSet { old: u32, new: u32 },
444
		/// Set collator commission to this value.
445
		CollatorCommissionSet { old: Perbill, new: Perbill },
446
		/// Set blocks per round
447
		BlocksPerRoundSet {
448
			current_round: RoundIndex,
449
			first_block: BlockNumberFor<T>,
450
			old: u32,
451
			new: u32,
452
			new_per_round_inflation_min: Perbill,
453
			new_per_round_inflation_ideal: Perbill,
454
			new_per_round_inflation_max: Perbill,
455
		},
456
		/// Auto-compounding reward percent was set for a delegation.
457
		AutoCompoundSet {
458
			candidate: T::AccountId,
459
			delegator: T::AccountId,
460
			value: Percent,
461
		},
462
		/// Compounded a portion of rewards towards the delegation.
463
		Compounded {
464
			candidate: T::AccountId,
465
			delegator: T::AccountId,
466
			amount: BalanceOf<T>,
467
		},
468
	}
469

            
470
71
	#[pallet::hooks]
471
	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
472
30688
		fn on_initialize(n: BlockNumberFor<T>) -> Weight {
473
30688
			let mut weight = <T as Config>::WeightInfo::base_on_initialize();
474
30688

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

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

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

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

            
514
			// add on_finalize weight
515
			//   read:  Author, Points, AwardedPts, WasInactive
516
			//   write: Points, AwardedPts, WasInactive
517
30688
			weight = weight.saturating_add(T::DbWeight::get().reads_writes(4, 3));
518
30688
			weight
519
30688
		}
520
30629
		fn on_finalize(_n: BlockNumberFor<T>) {
521
30629
			Self::award_points_to_block_author();
522
30629
			Self::cleanup_inactive_collator_info();
523
30629
		}
524
	}
525

            
526
1561
	#[pallet::storage]
527
	#[pallet::getter(fn collator_commission)]
528
	/// Commission percent taken off of rewards for all collators
529
	type CollatorCommission<T: Config> = StorageValue<_, Perbill, ValueQuery>;
530

            
531
2643
	#[pallet::storage]
532
	#[pallet::getter(fn total_selected)]
533
	/// The total candidates selected every round
534
	pub(crate) type TotalSelected<T: Config> = StorageValue<_, u32, ValueQuery>;
535

            
536
1649
	#[pallet::storage]
537
	#[pallet::getter(fn inflation_distribution_info)]
538
	/// Inflation distribution configuration, including accounts that should receive inflation
539
	/// before it is distributed to collators and delegators.
540
	///
541
	/// The sum of the distribution percents must be less than or equal to 100.
542
	pub(crate) type InflationDistributionInfo<T: Config> =
543
		StorageValue<_, InflationDistributionConfig<T::AccountId>, ValueQuery>;
544

            
545
186415
	#[pallet::storage]
546
	#[pallet::getter(fn round)]
547
	/// Current round index and next round scheduled transition
548
	pub type Round<T: Config> = StorageValue<_, RoundInfo<BlockNumberFor<T>>, ValueQuery>;
549

            
550
29516
	#[pallet::storage]
551
	#[pallet::getter(fn delegator_state)]
552
	/// Get delegator state associated with an account if account is delegating else None
553
	pub(crate) type DelegatorState<T: Config> = StorageMap<
554
		_,
555
		Twox64Concat,
556
		T::AccountId,
557
		Delegator<T::AccountId, BalanceOf<T>>,
558
		OptionQuery,
559
	>;
560

            
561
22672
	#[pallet::storage]
562
	#[pallet::getter(fn candidate_info)]
563
	/// Get collator candidate info associated with an account if account is candidate else None
564
	pub(crate) type CandidateInfo<T: Config> =
565
		StorageMap<_, Twox64Concat, T::AccountId, CandidateMetadata<BalanceOf<T>>, OptionQuery>;
566

            
567
	pub struct AddGet<T, R> {
568
		_phantom: PhantomData<(T, R)>,
569
	}
570
	impl<T, R> Get<u32> for AddGet<T, R>
571
	where
572
		T: Get<u32>,
573
		R: Get<u32>,
574
	{
575
371
		fn get() -> u32 {
576
371
			T::get() + R::get()
577
371
		}
578
	}
579

            
580
	/// Stores outstanding delegation requests per collator.
581
1362
	#[pallet::storage]
582
	#[pallet::getter(fn delegation_scheduled_requests)]
583
	pub(crate) type DelegationScheduledRequests<T: Config> = StorageMap<
584
		_,
585
		Blake2_128Concat,
586
		T::AccountId,
587
		BoundedVec<
588
			ScheduledRequest<T::AccountId, BalanceOf<T>>,
589
			AddGet<T::MaxTopDelegationsPerCandidate, T::MaxBottomDelegationsPerCandidate>,
590
		>,
591
		ValueQuery,
592
	>;
593

            
594
	/// Stores auto-compounding configuration per collator.
595
1137
	#[pallet::storage]
596
	#[pallet::getter(fn auto_compounding_delegations)]
597
	pub(crate) type AutoCompoundingDelegations<T: Config> = StorageMap<
598
		_,
599
		Blake2_128Concat,
600
		T::AccountId,
601
		BoundedVec<
602
			AutoCompoundConfig<T::AccountId>,
603
			AddGet<T::MaxTopDelegationsPerCandidate, T::MaxBottomDelegationsPerCandidate>,
604
		>,
605
		ValueQuery,
606
	>;
607

            
608
2633
	#[pallet::storage]
609
	#[pallet::getter(fn top_delegations)]
610
	/// Top delegations for collator candidate
611
	pub(crate) type TopDelegations<T: Config> = StorageMap<
612
		_,
613
		Twox64Concat,
614
		T::AccountId,
615
		Delegations<T::AccountId, BalanceOf<T>>,
616
		OptionQuery,
617
	>;
618

            
619
1099
	#[pallet::storage]
620
	#[pallet::getter(fn bottom_delegations)]
621
	/// Bottom delegations for collator candidate
622
	pub(crate) type BottomDelegations<T: Config> = StorageMap<
623
		_,
624
		Twox64Concat,
625
		T::AccountId,
626
		Delegations<T::AccountId, BalanceOf<T>>,
627
		OptionQuery,
628
	>;
629

            
630
1747
	#[pallet::storage]
631
	#[pallet::getter(fn selected_candidates)]
632
	/// The collator candidates selected for the current round
633
	type SelectedCandidates<T: Config> =
634
		StorageValue<_, BoundedVec<T::AccountId, T::MaxCandidates>, ValueQuery>;
635

            
636
5337
	#[pallet::storage]
637
	#[pallet::getter(fn total)]
638
	/// Total capital locked by this staking pallet
639
	pub(crate) type Total<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
640

            
641
6557
	#[pallet::storage]
642
	#[pallet::getter(fn candidate_pool)]
643
	/// The pool of collator candidates, each with their total backing stake
644
	pub(crate) type CandidatePool<T: Config> = StorageValue<
645
		_,
646
		BoundedOrderedSet<Bond<T::AccountId, BalanceOf<T>>, T::MaxCandidates>,
647
		ValueQuery,
648
	>;
649

            
650
1934
	#[pallet::storage]
651
	#[pallet::getter(fn at_stake)]
652
	/// Snapshot of collator delegation stake at the start of the round
653
	pub type AtStake<T: Config> = StorageDoubleMap<
654
		_,
655
		Twox64Concat,
656
		RoundIndex,
657
		Twox64Concat,
658
		T::AccountId,
659
		CollatorSnapshot<T::AccountId, BalanceOf<T>>,
660
		OptionQuery,
661
	>;
662

            
663
8228
	#[pallet::storage]
664
	#[pallet::getter(fn was_inactive)]
665
	/// Records collators' inactivity.
666
	/// Data persists for MaxOfflineRounds + 1 rounds before being pruned.
667
	pub type WasInactive<T: Config> =
668
		StorageDoubleMap<_, Twox64Concat, RoundIndex, Twox64Concat, T::AccountId, (), OptionQuery>;
669

            
670
14286
	#[pallet::storage]
671
	#[pallet::getter(fn delayed_payouts)]
672
	/// Delayed payouts
673
	pub type DelayedPayouts<T: Config> =
674
		StorageMap<_, Twox64Concat, RoundIndex, DelayedPayout<BalanceOf<T>>, OptionQuery>;
675

            
676
1639
	#[pallet::storage]
677
	#[pallet::getter(fn inflation_config)]
678
	/// Inflation configuration
679
	pub type InflationConfig<T: Config> = StorageValue<_, InflationInfo<BalanceOf<T>>, ValueQuery>;
680

            
681
31405
	#[pallet::storage]
682
	#[pallet::getter(fn points)]
683
	/// Total points awarded to collators for block production in the round
684
	pub type Points<T: Config> = StorageMap<_, Twox64Concat, RoundIndex, RewardPoint, ValueQuery>;
685

            
686
62225
	#[pallet::storage]
687
	#[pallet::getter(fn awarded_pts)]
688
	/// Points for each collator per round
689
	pub type AwardedPts<T: Config> = StorageDoubleMap<
690
		_,
691
		Twox64Concat,
692
		RoundIndex,
693
		Twox64Concat,
694
		T::AccountId,
695
		RewardPoint,
696
		ValueQuery,
697
	>;
698

            
699
53
	#[pallet::storage]
700
	#[pallet::getter(fn marking_offline)]
701
	/// Killswitch to enable/disable marking offline feature.
702
	pub type EnableMarkingOffline<T: Config> = StorageValue<_, bool, ValueQuery>;
703

            
704
19
	#[pallet::storage]
705
	/// Temporary storage to track candidates that have been migrated from locks to freezes.
706
	/// This storage should be removed after all accounts have been migrated.
707
	pub type MigratedCandidates<T: Config> =
708
		StorageMap<_, Twox64Concat, T::AccountId, (), OptionQuery>;
709

            
710
19
	#[pallet::storage]
711
	/// Temporary storage to track delegators that have been migrated from locks to freezes.
712
	/// This storage should be removed after all accounts have been migrated.
713
	pub type MigratedDelegators<T: Config> =
714
		StorageMap<_, Twox64Concat, T::AccountId, (), OptionQuery>;
715

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

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

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

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

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

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

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

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

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

            
998
		/// Join the set of collator candidates
999
		#[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,
661
		) -> DispatchResultWithPostInfo {
661
			let acc = ensure_signed(origin)?;
661
			ensure!(
661
				bond >= T::MinCandidateStk::get(),
8
				Error::<T>::CandidateBondBelowMin
			);
653
			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,
9084
		) -> DispatchResultWithPostInfo {
9084
			let delegator = ensure_signed(origin)?;
9084
			<AutoCompoundDelegations<T>>::delegate_with_auto_compound(
9084
				candidate,
9084
				delegator,
9084
				amount,
9084
				auto_compound,
9084
				candidate_delegation_count,
9084
				candidate_auto_compounding_delegation_count,
9084
				delegation_count,
9084
			)
		}
		/// 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,
51
		) -> DispatchResultWithPostInfo {
51
			let delegator = ensure_signed(origin)?;
51
			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(),
19
			)?;
18
			Pallet::<T>::deposit_event(Event::DelegationIncreased {
18
				delegator,
18
				candidate,
18
				amount: more,
18
				in_top,
18
			});
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 any other scheduled request is pending.
		#[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>,
31
		) -> DispatchResultWithPostInfo {
31
			let delegator = ensure_signed(origin)?;
31
			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,
37
		) -> DispatchResultWithPostInfo {
37
			ensure_signed(origin)?; // we may want to reward caller if caller != delegator
37
			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,
21
			)
		}
		/// 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

            
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;
4

            
4
			// If this counter is eq to max_offline_rounds,
4
			// 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
			);
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
1291
		pub(crate) fn freeze_extended(
1291
			account: &T::AccountId,
1291
			amount: BalanceOf<T>,
1291
			is_collator: bool,
1291
		) -> DispatchResult {
			use frame_support::traits::fungible::MutateFreeze;
			// Now set the freeze
1291
			let freeze_reason = if is_collator {
648
				FreezeReason::StakingCollator
			} else {
643
				FreezeReason::StakingDelegator
			};
1291
			T::Currency::set_freeze(&freeze_reason.into(), account, amount)
1291
		}
		/// `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
20013
		pub(crate) fn balance_frozen_extended(
20013
			account: &T::AccountId,
20013
			is_collator: bool,
20013
		) -> Option<BalanceOf<T>> {
20013
			// Now return the frozen balance
20013
			if is_collator {
1285
				<CandidateInfo<T>>::get(account).map(|info| info.bond)
			} else {
18728
				<DelegatorState<T>>::get(account).map(|state| state.total)
			}
20013
		}
679
		pub fn is_delegator(acc: &T::AccountId) -> bool {
679
			<DelegatorState<T>>::get(acc).is_some()
679
		}
9671
		pub fn is_candidate(acc: &T::AccountId) -> bool {
9671
			<CandidateInfo<T>>::get(acc).is_some()
9671
		}
2
		pub fn is_selected_candidate(acc: &T::AccountId) -> bool {
2
			<SelectedCandidates<T>>::get().binary_search(acc).is_ok()
2
		}
654
		pub fn join_candidates_inner(
654
			acc: T::AccountId,
654
			bond: BalanceOf<T>,
654
			candidate_count: u32,
654
		) -> DispatchResultWithPostInfo {
654
			ensure!(!Self::is_candidate(&acc), Error::<T>::CandidateExists);
650
			ensure!(!Self::is_delegator(&acc), Error::<T>::DelegatorExists);
646
			let mut candidates = <CandidatePool<T>>::get();
646
			let old_count = candidates.0.len() as u32;
646
			ensure!(
646
				candidate_count >= old_count,
5
				Error::<T>::TooLowCandidateCountWeightHintJoinCandidates
			);
641
			let maybe_inserted_candidate = candidates
641
				.try_insert(Bond {
641
					owner: acc.clone(),
641
					amount: bond,
641
				})
641
				.map_err(|_| Error::<T>::CandidateLimitReached)?;
640
			ensure!(maybe_inserted_candidate, Error::<T>::CandidateExists);
640
			ensure!(
640
				Self::get_collator_stakable_free_balance(&acc) >= bond,
4
				Error::<T>::InsufficientBalance,
			);
636
			Self::freeze_extended(&acc, bond, true).map_err(|_| Error::<T>::InsufficientBalance)?;
636
			let candidate = CandidateMetadata::new(bond);
636
			<CandidateInfo<T>>::insert(&acc, candidate);
636
			let empty_delegations: Delegations<T::AccountId, BalanceOf<T>> = Default::default();
636
			// insert empty top delegations
636
			<TopDelegations<T>>::insert(&acc, empty_delegations.clone());
636
			// insert empty bottom delegations
636
			<BottomDelegations<T>>::insert(&acc, empty_delegations);
636
			<CandidatePool<T>>::put(candidates);
636
			let new_total = <Total<T>>::get().saturating_add(bond);
636
			<Total<T>>::put(new_total);
636
			Self::deposit_event(Event::JoinedCollatorCandidates {
636
				account: acc,
636
				amount_locked: bond,
636
				new_total_amt_locked: new_total,
636
			});
636
			Ok(().into())
654
		}
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

            
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

            
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

            
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

            
6
			state
6
				.bond_more::<T>(collator.clone(), more)
6
				.map_err(|err| DispatchErrorWithPostInfo {
					post_info: Some(actual_weight).into(),
					error: err,
6
				})?;
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

            
6
			state
6
				.execute_bond_less::<T>(candidate.clone())
6
				.map_err(|err| DispatchErrorWithPostInfo {
					post_info: Some(actual_weight).into(),
					error: err,
6
				})?;
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;
23

            
23
			// 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
			);
23

            
23
			state
23
				.can_leave::<T>()
23
				.map_err(|err| DispatchErrorWithPostInfo {
2
					post_info: Some(actual_weight).into(),
2
					error: err,
23
				})?;
21
			let return_stake = |bond: Bond<T::AccountId, BalanceOf<T>>| -> DispatchResult {
15
				// 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
				);
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
					);
15
					<AutoCompoundDelegations<T>>::remove_auto_compound(&candidate, &bond.owner);
15

            
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);
9
						// 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;
21
			// 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);
21
			// 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);
21
			// Thaw all frozen funds for collator
21
			Self::thaw_extended(&candidate, true)?;
21
			<CandidateInfo<T>>::remove(&candidate);
21
			<DelegationScheduledRequests<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
		}
		/// Returns an account's stakable balance (including the reserved) which is not frozen in delegation staking
18728
		pub fn get_delegator_stakable_balance(acc: &T::AccountId) -> BalanceOf<T> {
18728
			let total_balance =
18728
				T::Currency::balance(acc).saturating_add(T::Currency::total_balance_on_hold(acc));
18728
			if let Some(frozen_balance) = Self::balance_frozen_extended(acc, false) {
226
				return total_balance.saturating_sub(frozen_balance);
18502
			}
18502
			total_balance
18728
		}
		/// Returns an account's free balance which is not frozen in collator staking
1285
		pub fn get_collator_stakable_free_balance(acc: &T::AccountId) -> BalanceOf<T> {
1285
			let total_balance = T::Currency::balance(acc);
1285
			if let Some(frozen_balance) = Self::balance_frozen_extended(acc, true) {
19
				return total_balance.saturating_sub(frozen_balance);
1266
			}
1266
			total_balance
1285
		}
		/// 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
514
		pub(crate) fn update_active(candidate: T::AccountId, total: BalanceOf<T>) {
514
			let mut candidates = <CandidatePool<T>>::get();
514
			candidates.remove(&Bond::from_owner(candidate.clone()));
514
			candidates
514
				.try_insert(Bond {
514
					owner: candidate,
514
					amount: total,
514
				})
514
				.expect(
514
					"the candidate is removed in previous step so the length cannot increase; qed",
514
				);
514
			<CandidatePool<T>>::put(candidates);
514
		}
		/// Compute round issuance based on duration of the given round
261
		fn compute_issuance(round_duration: u64, round_length: u32) -> BalanceOf<T> {
261
			let ideal_duration: BalanceOf<T> = round_length
261
				.saturating_mul(T::BlockTime::get() as u32)
261
				.into();
261
			let config = <InflationConfig<T>>::get();
261
			let round_issuance = crate::inflation::round_issuance_range::<T>(config.round);
261

            
261
			// Initial formula: (round_duration / ideal_duration) * ideal_issuance
261
			// We multiply before the division to reduce rounding effects
261
			BalanceOf::<T>::from(round_duration as u32).saturating_mul(round_issuance.ideal)
261
				/ (ideal_duration)
261
		}
		/// 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
		}
261
		pub(crate) fn prepare_staking_payouts(
261
			round_info: RoundInfo<BlockNumberFor<T>>,
261
			round_duration: u64,
261
		) -> Weight {
261
			let RoundInfo {
261
				current: now,
261
				length: round_length,
261
				..
261
			} = round_info;
261

            
261
			// This function is called right after the round index increment,
261
			// and the goal is to compute the payout informations for the round that just ended.
261
			// We don't need to saturate here because the genesis round is 1.
261
			let prepare_payout_for_round = now - 1;
261

            
261
			// Return early if there is no blocks for this round
261
			if <Points<T>>::get(prepare_payout_for_round).is_zero() {
				return Weight::zero();
261
			}
261

            
261
			// Compute total issuance based on round duration
261
			let total_issuance = Self::compute_issuance(round_duration, round_length);
261
			// reserve portion of issuance for parachain bond account
261
			let mut left_issuance = total_issuance;
261

            
261
			let configs = <InflationDistributionInfo<T>>::get().0;
522
			for (index, config) in configs.iter().enumerate() {
522
				if config.percent.is_zero() {
261
					continue;
261
				}
261
				let reserve = config.percent * total_issuance;
261
				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
					}
255
				}
			}
261
			let payout = DelayedPayout {
261
				round_issuance: total_issuance,
261
				total_staking_reward: left_issuance,
261
				collator_commission: <CollatorCommission<T>>::get(),
261
			};
261

            
261
			<DelayedPayouts<T>>::insert(prepare_payout_for_round, payout);
261

            
261
			<T as Config>::WeightInfo::prepare_staking_payouts()
261
		}
		/// 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
30427
		fn handle_delayed_payouts(now: RoundIndex) -> Weight {
30427
			let delay = T::RewardPaymentDelay::get();
30427

            
30427
			// don't underflow uint
30427
			if now < delay {
16490
				return Weight::from_parts(0u64, 0);
13937
			}
13937

            
13937
			let paid_for_round = now.saturating_sub(delay);
13937
			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 {
13585
				Weight::from_parts(0u64, 0)
			}
30427
		}
		/// 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) {
352
			// 'early_weight' tracks weight used for reads/writes done early in this fn before its
352
			// early-exit codepaths.
352
			let mut early_weight = Weight::zero();
352

            
352
			// TODO: it would probably be optimal to roll Points into the DelayedPayouts storage
352
			// 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

            
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

            
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));
289

            
289
				// Take the awarded points for the collator
289
				let pts = <AwardedPts<T>>::take(paid_for_round, &collator);
289
				// 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
				}
56

            
56
				// 'extra_weight' tracks weight returned from fns that we delegate to which can't be
56
				// 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

            
56
				let num_delegators = state.delegations.len();
56
				let mut num_paid_delegations = 0u32;
56
				let mut num_auto_compounding = 0u32;
56
				let num_scheduled_requests =
56
					<DelegationScheduledRequests<T>>::decode_len(&collator).unwrap_or_default();
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(),
55
							);
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
						num_scheduled_requests as u32,
56
					),
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.
764
		pub fn compute_top_candidates() -> Vec<T::AccountId> {
764
			let top_n = <TotalSelected<T>>::get() as usize;
764
			if top_n == 0 {
				return vec![];
764
			}
764

            
764
			let candidates = <CandidatePool<T>>::get().0;
764

            
764
			// If the number of candidates is greater than top_n, select the candidates with higher
764
			// amount. Otherwise, return all the candidates.
764
			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| {
1083
							// Order by amount, then owner. The owner is needed to ensure a stable order
1083
							// 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

            
15
				let mut collators = sorted_candidates
15
					.into_iter()
15
					.take(top_n)
75
					.map(|x| x.owner)
15
					.collect::<Vec<_>>();
15

            
15
				// Sort collators by AccountId
15
				collators.sort();
15

            
15
				collators
			} else {
				// Return all candidates
				// The candidates are already sorted by AccountId, so no need to sort again
1025
				candidates.into_iter().map(|x| x.owner).collect::<Vec<_>>()
			}
764
		}
		/// Best as in most cumulatively supported in terms of stake
		/// Returns [collator_count, delegation_count, total staked]
763
		pub(crate) fn select_top_candidates(now: RoundIndex) -> (Weight, u32, u32, BalanceOf<T>) {
763
			let (mut collator_count, mut delegation_count, mut total) =
763
				(0u32, 0u32, BalanceOf::<T>::zero());
763
			// choose the top TotalSelected qualified candidates, ordered by stake
763
			let collators = Self::compute_top_candidates();
763
			if collators.is_empty() {
				// SELECTION FAILED TO SELECT >=1 COLLATOR => select collators from previous round
297
				let last_round = now.saturating_sub(1u32);
297
				let mut total_per_candidate: BTreeMap<T::AccountId, BalanceOf<T>> = BTreeMap::new();
				// set this round AtStake to last round AtStake
297
				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
340
				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
					})
				}
297
				let weight = <T as Config>::WeightInfo::select_top_candidates(0, 0);
297
				return (weight, collator_count, delegation_count, total);
466
			}
			// snapshot exposure for round for weighting reward distribution
910
			for account in collators.iter() {
910
				let state = <CandidateInfo<T>>::get(account)
910
					.expect("all members of CandidateQ must be candidates");
910

            
910
				collator_count = collator_count.saturating_add(1u32);
910
				delegation_count = delegation_count.saturating_add(state.delegation_count);
910
				total = total.saturating_add(state.total_counted);
910
				let CountedDelegations {
910
					uncounted_stake,
910
					rewardable_delegations,
910
				} = Self::get_rewardable_delegators(&account);
910
				let total_counted = state.total_counted.saturating_sub(uncounted_stake);
910

            
910
				let auto_compounding_delegations = <AutoCompoundingDelegations<T>>::get(&account)
910
					.into_iter()
910
					.map(|x| (x.delegator, x.value))
910
					.collect::<BTreeMap<_, _>>();
910
				let rewardable_delegations = rewardable_delegations
910
					.into_iter()
910
					.map(|d| BondWithAutoCompound {
763
						owner: d.owner.clone(),
763
						amount: d.amount,
763
						auto_compound: auto_compounding_delegations
763
							.get(&d.owner)
763
							.cloned()
763
							.unwrap_or_else(|| Percent::zero()),
910
					})
910
					.collect();
910

            
910
				let snapshot = CollatorSnapshot {
910
					bond: state.bond,
910
					delegations: rewardable_delegations,
910
					total: total_counted,
910
				};
910
				<AtStake<T>>::insert(now, account, snapshot);
910
				Self::deposit_event(Event::CollatorChosen {
910
					round: now,
910
					collator_account: account.clone(),
910
					total_exposed_amount: state.total_counted,
910
				});
910
			}
			// insert canonical collator set
466
			<SelectedCandidates<T>>::put(
466
				BoundedVec::try_from(collators)
466
					.expect("subset of collators is always less than or equal to max candidates"),
466
			);
466

            
466
			let avg_delegator_count = delegation_count.checked_div(collator_count).unwrap_or(0);
466
			let weight = <T as Config>::WeightInfo::select_top_candidates(
466
				collator_count,
466
				avg_delegator_count,
466
			);
466
			(weight, collator_count, delegation_count, total)
763
		}
		/// 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.
910
		pub(crate) fn get_rewardable_delegators(collator: &T::AccountId) -> CountedDelegations<T> {
910
			let requests = <DelegationScheduledRequests<T>>::get(collator)
910
				.into_iter()
910
				.map(|x| (x.delegator, x.action))
910
				.collect::<BTreeMap<_, _>>();
910
			let mut uncounted_stake = BalanceOf::<T>::zero();
910
			let rewardable_delegations = <TopDelegations<T>>::get(collator)
910
				.expect("all members of CandidateQ must be candidates")
910
				.delegations
910
				.into_iter()
910
				.map(|mut bond| {
763
					bond.amount = match requests.get(&bond.owner) {
652
						None => bond.amount,
						Some(DelegationAction::Revoke(_)) => {
75
							uncounted_stake = uncounted_stake.saturating_add(bond.amount);
75
							BalanceOf::<T>::zero()
						}
36
						Some(DelegationAction::Decrease(amount)) => {
36
							uncounted_stake = uncounted_stake.saturating_add(*amount);
36
							bond.amount.saturating_sub(*amount)
						}
					};
763
					bond
910
				})
910
				.collect();
910
			CountedDelegations {
910
				uncounted_stake,
910
				rewardable_delegations,
910
			}
910
		}
		/// 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
			);
21
			let actual_weight = <T as Config>::WeightInfo::delegator_bond_more(
21
				<DelegationScheduledRequests<T>>::decode_len(&candidate).unwrap_or_default() as u32,
21
			);
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
				})?;
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
		) {
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

            
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
30629
		fn award_points_to_block_author() {
30629
			let author = T::BlockAuthor::get();
30629
			let now = <Round<T>>::get().current;
30629
			let score_plus_20 = <AwardedPts<T>>::get(now, &author).saturating_add(20);
30629
			<AwardedPts<T>>::insert(now, author, score_plus_20);
30629
			<Points<T>>::mutate(now, |x| *x = x.saturating_add(20));
30629
		}
		/// Marks collators as inactive for the previous round if they received zero awarded points.
261
		pub fn mark_collators_as_inactive(cur: RoundIndex) -> Weight {
261
			// This function is called after round index increment,
261
			// We don't need to saturate here because the genesis round is 1.
261
			let prev = cur - 1;
261

            
261
			let mut collators_at_stake_count = 0u32;
572
			for (account, _) in <AtStake<T>>::iter_prefix(prev) {
563
				collators_at_stake_count = collators_at_stake_count.saturating_add(1u32);
563
				if <AwardedPts<T>>::get(prev, &account).is_zero() {
467
					<WasInactive<T>>::insert(prev, account, ());
489
				}
			}
261
			<T as Config>::WeightInfo::mark_collators_as_inactive(collators_at_stake_count)
261
		}
		/// Cleans up historical staking information that is older than MaxOfflineRounds
		/// by removing entries from the WasIactive storage map.
30629
		fn cleanup_inactive_collator_info() {
30629
			let now = <Round<T>>::get().current;
30629
			let minimum_rounds_required = T::MaxOfflineRounds::get() + 1;
30629

            
30629
			if now < minimum_rounds_required {
22904
				return;
7725
			}
7725

            
7725
			let _ = <WasInactive<T>>::iter_prefix(now - minimum_rounds_required)
7725
				.drain()
7725
				.next();
30629
		}
	}
	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
		}
	}
}