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
11433
#[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::pallet_prelude::*;
86
	use frame_support::traits::{
87
		tokens::WithdrawReasons, Currency, Get, Imbalance, LockIdentifier, LockableCurrency,
88
		ReservableCurrency,
89
	};
90
	use frame_system::pallet_prelude::*;
91
	use sp_consensus_slots::Slot;
92
	use sp_runtime::{
93
		traits::{Saturating, Zero},
94
		DispatchErrorWithPostInfo, Perbill, Percent,
95
	};
96
	use sp_std::{collections::btree_map::BTreeMap, prelude::*};
97

            
98
	/// Pallet for parachain staking
99
50
	#[pallet::pallet]
100
	#[pallet::without_storage_info]
101
	pub struct Pallet<T>(PhantomData<T>);
102

            
103
	pub type RoundIndex = u32;
104
	type RewardPoint = u32;
105
	pub type BalanceOf<T> =
106
		<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
107

            
108
	pub const COLLATOR_LOCK_ID: LockIdentifier = *b"stkngcol";
109
	pub const DELEGATOR_LOCK_ID: LockIdentifier = *b"stkngdel";
110

            
111
	/// A hard limit for weight computation purposes for the max candidates that _could_
112
	/// theoretically exist.
113
	pub const MAX_CANDIDATES: u32 = 200;
114

            
115
	/// Configuration trait of this pallet.
116
	#[pallet::config]
117
	pub trait Config: frame_system::Config {
118
		/// Overarching event type
119
		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
120
		/// The currency type
121
		type Currency: Currency<Self::AccountId>
122
			+ ReservableCurrency<Self::AccountId>
123
			+ LockableCurrency<Self::AccountId>;
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
		/// Minimum stake required for any account to be a collator candidate
164
		#[pallet::constant]
165
		type MinCandidateStk: Get<BalanceOf<Self>>;
166
		/// Minimum stake for any registered on-chain account to delegate
167
		#[pallet::constant]
168
		type MinDelegation: Get<BalanceOf<Self>>;
169
		/// Get the current block author
170
		type BlockAuthor: Get<Self::AccountId>;
171
		/// Handler to notify the runtime when a collator is paid.
172
		/// If you don't need it, you can specify the type `()`.
173
		type OnCollatorPayout: OnCollatorPayout<Self::AccountId, BalanceOf<Self>>;
174
		/// Handler to distribute a collator's reward.
175
		/// To use the default implementation of minting rewards, specify the type `()`.
176
		type PayoutCollatorReward: PayoutCollatorReward<Self>;
177
		/// Handler to notify the runtime when a collator is inactive.
178
		/// The default behavior is to mark the collator as offline.
179
		/// If you need to use the default implementation, specify the type `()`.
180
		type OnInactiveCollator: OnInactiveCollator<Self>;
181
		/// Handler to notify the runtime when a new round begin.
182
		/// If you don't need it, you can specify the type `()`.
183
		type OnNewRound: OnNewRound;
184
		/// Get the current slot number
185
		type SlotProvider: Get<Slot>;
186
		/// Get the slot duration in milliseconds
187
		#[pallet::constant]
188
		type SlotDuration: Get<u64>;
189
		/// Get the average time beetween 2 blocks in milliseconds
190
		#[pallet::constant]
191
		type BlockTime: Get<u64>;
192
		/// Maximum candidates
193
		#[pallet::constant]
194
		type MaxCandidates: Get<u32>;
195
		/// Threshold after which inflation become linear
196
		/// If you don't want to use it, set it to `()`
197
		#[pallet::constant]
198
		type LinearInflationThreshold: Get<Option<BalanceOf<Self>>>;
199
		/// Weight information for extrinsics in this pallet.
200
		type WeightInfo: WeightInfo;
201
	}
202

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

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

            
458
61323
	#[pallet::hooks]
459
	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
460
30691
		fn on_initialize(n: BlockNumberFor<T>) -> Weight {
461
30691
			let mut weight = <T as Config>::WeightInfo::base_on_initialize();
462
30691

            
463
30691
			let mut round = <Round<T>>::get();
464
30691
			if round.should_update(n) {
465
261
				// fetch current slot number
466
261
				let current_slot: u64 = T::SlotProvider::get().into();
467
261

            
468
261
				// account for SlotProvider read
469
261
				weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 0));
470
261

            
471
261
				// Compute round duration in slots
472
261
				let round_duration = (current_slot.saturating_sub(round.first_slot))
473
261
					.saturating_mul(T::SlotDuration::get());
474
261

            
475
261
				// mutate round
476
261
				round.update(n, current_slot);
477
261
				// notify that new round begin
478
261
				weight = weight.saturating_add(T::OnNewRound::on_new_round(round.current));
479
261
				// pay all stakers for T::RewardPaymentDelay rounds ago
480
261
				weight =
481
261
					weight.saturating_add(Self::prepare_staking_payouts(round, round_duration));
482
261
				// select top collator candidates for next round
483
261
				let (extra_weight, collator_count, _delegation_count, total_staked) =
484
261
					Self::select_top_candidates(round.current);
485
261
				weight = weight.saturating_add(extra_weight);
486
261
				// start next round
487
261
				<Round<T>>::put(round);
488
261
				Self::deposit_event(Event::NewRound {
489
261
					starting_block: round.first,
490
261
					round: round.current,
491
261
					selected_collators_number: collator_count,
492
261
					total_balance: total_staked,
493
261
				});
494
261
				// record inactive collators
495
261
				weight = weight.saturating_add(Self::mark_collators_as_inactive(round.current));
496
261
				// account for Round write
497
261
				weight = weight.saturating_add(T::DbWeight::get().reads_writes(0, 1));
498
30430
			} else {
499
30430
				weight = weight.saturating_add(Self::handle_delayed_payouts(round.current));
500
30430
			}
501

            
502
			// add on_finalize weight
503
			//   read:  Author, Points, AwardedPts, WasInactive
504
			//   write: Points, AwardedPts, WasInactive
505
30691
			weight = weight.saturating_add(T::DbWeight::get().reads_writes(4, 3));
506
30691
			weight
507
30691
		}
508
30629
		fn on_finalize(_n: BlockNumberFor<T>) {
509
30629
			Self::award_points_to_block_author();
510
30629
			Self::cleanup_inactive_collator_info();
511
30629
		}
512
	}
513

            
514
1616
	#[pallet::storage]
515
	#[pallet::getter(fn collator_commission)]
516
	/// Commission percent taken off of rewards for all collators
517
	type CollatorCommission<T: Config> = StorageValue<_, Perbill, ValueQuery>;
518

            
519
2752
	#[pallet::storage]
520
	#[pallet::getter(fn total_selected)]
521
	/// The total candidates selected every round
522
	pub(crate) type TotalSelected<T: Config> = StorageValue<_, u32, ValueQuery>;
523

            
524
1750
	#[pallet::storage]
525
	#[pallet::getter(fn inflation_distribution_info)]
526
	/// Inflation distribution configuration, including accounts that should receive inflation
527
	/// before it is distributed to collators and delegators.
528
	///
529
	/// The sum of the distribution percents must be less than or equal to 100.
530
	pub(crate) type InflationDistributionInfo<T: Config> =
531
		StorageValue<_, InflationDistributionConfig<T::AccountId>, ValueQuery>;
532

            
533
186485
	#[pallet::storage]
534
	#[pallet::getter(fn round)]
535
	/// Current round index and next round scheduled transition
536
	pub type Round<T: Config> = StorageValue<_, RoundInfo<BlockNumberFor<T>>, ValueQuery>;
537

            
538
29533
	#[pallet::storage]
539
	#[pallet::getter(fn delegator_state)]
540
	/// Get delegator state associated with an account if account is delegating else None
541
	pub(crate) type DelegatorState<T: Config> = StorageMap<
542
		_,
543
		Twox64Concat,
544
		T::AccountId,
545
		Delegator<T::AccountId, BalanceOf<T>>,
546
		OptionQuery,
547
	>;
548

            
549
22766
	#[pallet::storage]
550
	#[pallet::getter(fn candidate_info)]
551
	/// Get collator candidate info associated with an account if account is candidate else None
552
	pub(crate) type CandidateInfo<T: Config> =
553
		StorageMap<_, Twox64Concat, T::AccountId, CandidateMetadata<BalanceOf<T>>, OptionQuery>;
554

            
555
	pub struct AddGet<T, R> {
556
		_phantom: PhantomData<(T, R)>,
557
	}
558
	impl<T, R> Get<u32> for AddGet<T, R>
559
	where
560
		T: Get<u32>,
561
		R: Get<u32>,
562
	{
563
377
		fn get() -> u32 {
564
377
			T::get() + R::get()
565
377
		}
566
	}
567

            
568
	/// Stores outstanding delegation requests per collator.
569
1400
	#[pallet::storage]
570
	#[pallet::getter(fn delegation_scheduled_requests)]
571
	pub(crate) type DelegationScheduledRequests<T: Config> = StorageMap<
572
		_,
573
		Blake2_128Concat,
574
		T::AccountId,
575
		BoundedVec<
576
			ScheduledRequest<T::AccountId, BalanceOf<T>>,
577
			AddGet<T::MaxTopDelegationsPerCandidate, T::MaxBottomDelegationsPerCandidate>,
578
		>,
579
		ValueQuery,
580
	>;
581

            
582
	/// Stores auto-compounding configuration per collator.
583
1154
	#[pallet::storage]
584
	#[pallet::getter(fn auto_compounding_delegations)]
585
	pub(crate) type AutoCompoundingDelegations<T: Config> = StorageMap<
586
		_,
587
		Blake2_128Concat,
588
		T::AccountId,
589
		BoundedVec<
590
			AutoCompoundConfig<T::AccountId>,
591
			AddGet<T::MaxTopDelegationsPerCandidate, T::MaxBottomDelegationsPerCandidate>,
592
		>,
593
		ValueQuery,
594
	>;
595

            
596
2666
	#[pallet::storage]
597
	#[pallet::getter(fn top_delegations)]
598
	/// Top delegations for collator candidate
599
	pub(crate) type TopDelegations<T: Config> = StorageMap<
600
		_,
601
		Twox64Concat,
602
		T::AccountId,
603
		Delegations<T::AccountId, BalanceOf<T>>,
604
		OptionQuery,
605
	>;
606

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

            
618
1811
	#[pallet::storage]
619
	#[pallet::getter(fn selected_candidates)]
620
	/// The collator candidates selected for the current round
621
	type SelectedCandidates<T: Config> =
622
		StorageValue<_, BoundedVec<T::AccountId, T::MaxCandidates>, ValueQuery>;
623

            
624
5402
	#[pallet::storage]
625
	#[pallet::getter(fn total)]
626
	/// Total capital locked by this staking pallet
627
	pub(crate) type Total<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
628

            
629
6676
	#[pallet::storage]
630
	#[pallet::getter(fn candidate_pool)]
631
	/// The pool of collator candidates, each with their total backing stake
632
	pub(crate) type CandidatePool<T: Config> = StorageValue<
633
		_,
634
		BoundedOrderedSet<Bond<T::AccountId, BalanceOf<T>>, T::MaxCandidates>,
635
		ValueQuery,
636
	>;
637

            
638
1962
	#[pallet::storage]
639
	#[pallet::getter(fn at_stake)]
640
	/// Snapshot of collator delegation stake at the start of the round
641
	pub type AtStake<T: Config> = StorageDoubleMap<
642
		_,
643
		Twox64Concat,
644
		RoundIndex,
645
		Twox64Concat,
646
		T::AccountId,
647
		CollatorSnapshot<T::AccountId, BalanceOf<T>>,
648
		OptionQuery,
649
	>;
650

            
651
8229
	#[pallet::storage]
652
	#[pallet::getter(fn was_inactive)]
653
	/// Records collators' inactivity.
654
	/// Data persists for MaxOfflineRounds + 1 rounds before being pruned.
655
	pub type WasInactive<T: Config> =
656
		StorageDoubleMap<_, Twox64Concat, RoundIndex, Twox64Concat, T::AccountId, (), OptionQuery>;
657

            
658
14287
	#[pallet::storage]
659
	#[pallet::getter(fn delayed_payouts)]
660
	/// Delayed payouts
661
	pub type DelayedPayouts<T: Config> =
662
		StorageMap<_, Twox64Concat, RoundIndex, DelayedPayout<BalanceOf<T>>, OptionQuery>;
663

            
664
1694
	#[pallet::storage]
665
	#[pallet::getter(fn inflation_config)]
666
	/// Inflation configuration
667
	pub type InflationConfig<T: Config> = StorageValue<_, InflationInfo<BalanceOf<T>>, ValueQuery>;
668

            
669
31406
	#[pallet::storage]
670
	#[pallet::getter(fn points)]
671
	/// Total points awarded to collators for block production in the round
672
	pub type Points<T: Config> = StorageMap<_, Twox64Concat, RoundIndex, RewardPoint, ValueQuery>;
673

            
674
62226
	#[pallet::storage]
675
	#[pallet::getter(fn awarded_pts)]
676
	/// Points for each collator per round
677
	pub type AwardedPts<T: Config> = StorageDoubleMap<
678
		_,
679
		Twox64Concat,
680
		RoundIndex,
681
		Twox64Concat,
682
		T::AccountId,
683
		RewardPoint,
684
		ValueQuery,
685
	>;
686

            
687
54
	#[pallet::storage]
688
	#[pallet::getter(fn marking_offline)]
689
	/// Killswitch to enable/disable marking offline feature.
690
	pub type EnableMarkingOffline<T: Config> = StorageValue<_, bool, ValueQuery>;
691

            
692
	#[pallet::genesis_config]
693
	pub struct GenesisConfig<T: Config> {
694
		/// Initialize balance and register all as collators: `(collator AccountId, balance Amount)`
695
		pub candidates: Vec<(T::AccountId, BalanceOf<T>)>,
696
		/// Initialize balance and make delegations:
697
		/// `(delegator AccountId, collator AccountId, delegation Amount, auto-compounding Percent)`
698
		pub delegations: Vec<(T::AccountId, T::AccountId, BalanceOf<T>, Percent)>,
699
		/// Inflation configuration
700
		pub inflation_config: InflationInfo<BalanceOf<T>>,
701
		/// Default fixed percent a collator takes off the top of due rewards
702
		pub collator_commission: Perbill,
703
		/// Default percent of inflation set aside for parachain bond every round
704
		pub parachain_bond_reserve_percent: Percent,
705
		/// Default number of blocks in a round
706
		pub blocks_per_round: u32,
707
		/// Number of selected candidates every round. Cannot be lower than MinSelectedCandidates
708
		pub num_selected_candidates: u32,
709
	}
710

            
711
	impl<T: Config> Default for GenesisConfig<T> {
712
5
		fn default() -> Self {
713
5
			Self {
714
5
				candidates: vec![],
715
5
				delegations: vec![],
716
5
				inflation_config: Default::default(),
717
5
				collator_commission: Default::default(),
718
5
				parachain_bond_reserve_percent: Default::default(),
719
5
				blocks_per_round: 1u32,
720
5
				num_selected_candidates: T::MinSelectedCandidates::get(),
721
5
			}
722
5
		}
723
	}
724

            
725
524
	#[pallet::genesis_build]
726
	impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
727
529
		fn build(&self) {
728
529
			assert!(self.blocks_per_round > 0, "Blocks per round must be > 0");
729
529
			<InflationConfig<T>>::put(self.inflation_config.clone());
730
529
			let mut candidate_count = 0u32;
731
			// Initialize the candidates
732
1172
			for &(ref candidate, balance) in &self.candidates {
733
643
				assert!(
734
643
					<Pallet<T>>::get_collator_stakable_free_balance(candidate) >= balance,
735
					"Account does not have enough balance to bond as a candidate."
736
				);
737
643
				if let Err(error) = <Pallet<T>>::join_candidates(
738
643
					T::RuntimeOrigin::from(Some(candidate.clone()).into()),
739
643
					balance,
740
643
					candidate_count,
741
643
				) {
742
13
					log::warn!("Join candidates failed in genesis with error {:?}", error);
743
630
				} else {
744
630
					candidate_count = candidate_count.saturating_add(1u32);
745
630
				}
746
			}
747

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

            
845
84
	#[pallet::call]
846
	impl<T: Config> Pallet<T> {
847
		/// Set the expectations for total staked. These expectations determine the issuance for
848
		/// the round according to logic in `fn compute_issuance`
849
		#[pallet::call_index(0)]
850
		#[pallet::weight(<T as Config>::WeightInfo::set_staking_expectations())]
851
		pub fn set_staking_expectations(
852
			origin: OriginFor<T>,
853
			expectations: Range<BalanceOf<T>>,
854
6
		) -> DispatchResultWithPostInfo {
855
6
			T::MonetaryGovernanceOrigin::ensure_origin(origin)?;
856
5
			ensure!(expectations.is_valid(), Error::<T>::InvalidSchedule);
857
4
			let mut config = <InflationConfig<T>>::get();
858
4
			ensure!(
859
4
				config.expect != expectations,
860
1
				Error::<T>::NoWritingSameValue
861
			);
862
3
			config.set_expectations(expectations);
863
3
			Self::deposit_event(Event::StakeExpectationsSet {
864
3
				expect_min: config.expect.min,
865
3
				expect_ideal: config.expect.ideal,
866
3
				expect_max: config.expect.max,
867
3
			});
868
3
			<InflationConfig<T>>::put(config);
869
3
			Ok(().into())
870
		}
871

            
872
		/// Set the annual inflation rate to derive per-round inflation
873
		#[pallet::call_index(1)]
874
		#[pallet::weight(<T as Config>::WeightInfo::set_inflation())]
875
		pub fn set_inflation(
876
			origin: OriginFor<T>,
877
			schedule: Range<Perbill>,
878
7
		) -> DispatchResultWithPostInfo {
879
7
			T::MonetaryGovernanceOrigin::ensure_origin(origin)?;
880
5
			ensure!(schedule.is_valid(), Error::<T>::InvalidSchedule);
881
4
			let mut config = <InflationConfig<T>>::get();
882
4
			ensure!(config.annual != schedule, Error::<T>::NoWritingSameValue);
883
3
			config.annual = schedule;
884
3
			config.set_round_from_annual::<T>(schedule);
885
3
			Self::deposit_event(Event::InflationSet {
886
3
				annual_min: config.annual.min,
887
3
				annual_ideal: config.annual.ideal,
888
3
				annual_max: config.annual.max,
889
3
				round_min: config.round.min,
890
3
				round_ideal: config.round.ideal,
891
3
				round_max: config.round.max,
892
3
			});
893
3
			<InflationConfig<T>>::put(config);
894
3
			Ok(().into())
895
		}
896

            
897
		/// Deprecated: please use `set_inflation_distribution_config` instead.
898
		///
899
		///  Set the account that will hold funds set aside for parachain bond
900
		#[pallet::call_index(2)]
901
		#[pallet::weight(<T as Config>::WeightInfo::set_parachain_bond_account())]
902
		pub fn set_parachain_bond_account(
903
			origin: OriginFor<T>,
904
			new: T::AccountId,
905
6
		) -> DispatchResultWithPostInfo {
906
6
			T::MonetaryGovernanceOrigin::ensure_origin(origin.clone())?;
907
5
			let old = <InflationDistributionInfo<T>>::get().0;
908
5
			let new = InflationDistributionAccount {
909
5
				account: new,
910
5
				percent: old[0].percent.clone(),
911
5
			};
912
5
			Pallet::<T>::set_inflation_distribution_config(origin, [new, old[1].clone()].into())
913
		}
914

            
915
		/// Deprecated: please use `set_inflation_distribution_config` instead.
916
		///
917
		/// Set the percent of inflation set aside for parachain bond
918
		#[pallet::call_index(3)]
919
		#[pallet::weight(<T as Config>::WeightInfo::set_parachain_bond_reserve_percent())]
920
		pub fn set_parachain_bond_reserve_percent(
921
			origin: OriginFor<T>,
922
			new: Percent,
923
4
		) -> DispatchResultWithPostInfo {
924
4
			T::MonetaryGovernanceOrigin::ensure_origin(origin.clone())?;
925
3
			let old = <InflationDistributionInfo<T>>::get().0;
926
3
			let new = InflationDistributionAccount {
927
3
				account: old[0].account.clone(),
928
3
				percent: new,
929
3
			};
930
3
			Pallet::<T>::set_inflation_distribution_config(origin, [new, old[1].clone()].into())
931
		}
932

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

            
958
		/// Set the commission for all collators
959
		#[pallet::call_index(5)]
960
		#[pallet::weight(<T as Config>::WeightInfo::set_collator_commission())]
961
		pub fn set_collator_commission(
962
			origin: OriginFor<T>,
963
			new: Perbill,
964
4
		) -> DispatchResultWithPostInfo {
965
4
			frame_system::ensure_root(origin)?;
966
3
			let old = <CollatorCommission<T>>::get();
967
3
			ensure!(old != new, Error::<T>::NoWritingSameValue);
968
2
			<CollatorCommission<T>>::put(new);
969
2
			Self::deposit_event(Event::CollatorCommissionSet { old, new });
970
2
			Ok(().into())
971
		}
972

            
973
		/// Set blocks per round
974
		/// - if called with `new` less than length of current round, will transition immediately
975
		/// in the next block
976
		/// - also updates per-round inflation config
977
		#[pallet::call_index(6)]
978
		#[pallet::weight(<T as Config>::WeightInfo::set_blocks_per_round())]
979
17
		pub fn set_blocks_per_round(origin: OriginFor<T>, new: u32) -> DispatchResultWithPostInfo {
980
17
			frame_system::ensure_root(origin)?;
981
16
			ensure!(
982
16
				new >= T::MinBlocksPerRound::get(),
983
1
				Error::<T>::CannotSetBelowMin
984
			);
985
15
			let mut round = <Round<T>>::get();
986
15
			let (now, first, old) = (round.current, round.first, round.length);
987
15
			ensure!(old != new, Error::<T>::NoWritingSameValue);
988
14
			ensure!(
989
14
				new > <TotalSelected<T>>::get(),
990
2
				Error::<T>::RoundLengthMustBeGreaterThanTotalSelectedCollators,
991
			);
992
12
			round.length = new;
993
12
			// update per-round inflation given new rounds per year
994
12
			let mut inflation_config = <InflationConfig<T>>::get();
995
12
			inflation_config.reset_round::<T>(new);
996
12
			<Round<T>>::put(round);
997
12
			Self::deposit_event(Event::BlocksPerRoundSet {
998
12
				current_round: now,
999
12
				first_block: first,
12
				old: old,
12
				new: new,
12
				new_per_round_inflation_min: inflation_config.round.min,
12
				new_per_round_inflation_ideal: inflation_config.round.ideal,
12
				new_per_round_inflation_max: inflation_config.round.max,
12
			});
12
			<InflationConfig<T>>::put(inflation_config);
12
			Ok(().into())
		}
		/// 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,
683
		) -> DispatchResultWithPostInfo {
683
			let acc = ensure_signed(origin)?;
683
			ensure!(
683
				bond >= T::MinCandidateStk::get(),
14
				Error::<T>::CandidateBondBelowMin
			);
669
			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
			)
		}
		/// Hotfix to remove existing empty entries for candidates that have left.
		#[pallet::call_index(28)]
		#[pallet::weight(
			T::DbWeight::get().reads_writes(2 * candidates.len() as u64, candidates.len() as u64)
		)]
		pub fn hotfix_remove_delegation_requests_exited_candidates(
			origin: OriginFor<T>,
			candidates: Vec<T::AccountId>,
4
		) -> DispatchResult {
4
			ensure_signed(origin)?;
4
			ensure!(candidates.len() < 100, <Error<T>>::InsufficientBalance);
9
			for candidate in &candidates {
7
				ensure!(
7
					<CandidateInfo<T>>::get(&candidate).is_none(),
1
					<Error<T>>::CandidateNotLeaving
				);
6
				ensure!(
6
					<DelegationScheduledRequests<T>>::get(&candidate).is_empty(),
1
					<Error<T>>::CandidateNotLeaving
				);
			}
6
			for candidate in candidates {
4
				<DelegationScheduledRequests<T>>::remove(candidate);
4
			}
2
			Ok(().into())
		}
		/// 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>,
35
		) -> DispatchResultWithPostInfo {
35
			T::MonetaryGovernanceOrigin::ensure_origin(origin)?;
34
			let old = <InflationDistributionInfo<T>>::get().0;
34
			let new = new.0;
34
			ensure!(old != new, Error::<T>::NoWritingSameValue);
64
			let total_percent = new.iter().fold(0, |acc, x| acc + x.percent.deconstruct());
32
			ensure!(
32
				total_percent <= 100,
8
				Error::<T>::TotalInflationDistributionPercentExceeds100,
			);
24
			<InflationDistributionInfo<T>>::put::<InflationDistributionConfig<T::AccountId>>(
24
				new.clone().into(),
24
			);
24
			Self::deposit_event(Event::InflationDistributionConfigUpdated {
24
				old: old.into(),
24
				new: new.into(),
24
			});
24
			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> {
		pub fn set_candidate_bond_to_zero(acc: &T::AccountId) -> Weight {
			let actual_weight =
				<T as Config>::WeightInfo::set_candidate_bond_to_zero(T::MaxCandidates::get());
			if let Some(mut state) = <CandidateInfo<T>>::get(&acc) {
				state.bond_less::<T>(acc.clone(), state.bond);
				<CandidateInfo<T>>::insert(&acc, state);
			}
			actual_weight
		}
695
		pub fn is_delegator(acc: &T::AccountId) -> bool {
695
			<DelegatorState<T>>::get(acc).is_some()
695
		}
9687
		pub fn is_candidate(acc: &T::AccountId) -> bool {
9687
			<CandidateInfo<T>>::get(acc).is_some()
9687
		}
2
		pub fn is_selected_candidate(acc: &T::AccountId) -> bool {
2
			<SelectedCandidates<T>>::get().binary_search(acc).is_ok()
2
		}
670
		pub fn join_candidates_inner(
670
			acc: T::AccountId,
670
			bond: BalanceOf<T>,
670
			candidate_count: u32,
670
		) -> DispatchResultWithPostInfo {
670
			ensure!(!Self::is_candidate(&acc), Error::<T>::CandidateExists);
666
			ensure!(!Self::is_delegator(&acc), Error::<T>::DelegatorExists);
662
			let mut candidates = <CandidatePool<T>>::get();
662
			let old_count = candidates.0.len() as u32;
662
			ensure!(
662
				candidate_count >= old_count,
5
				Error::<T>::TooLowCandidateCountWeightHintJoinCandidates
			);
657
			let maybe_inserted_candidate = candidates
657
				.try_insert(Bond {
657
					owner: acc.clone(),
657
					amount: bond,
657
				})
657
				.map_err(|_| Error::<T>::CandidateLimitReached)?;
656
			ensure!(maybe_inserted_candidate, Error::<T>::CandidateExists);
656
			ensure!(
656
				Self::get_collator_stakable_free_balance(&acc) >= bond,
4
				Error::<T>::InsufficientBalance,
			);
652
			T::Currency::set_lock(COLLATOR_LOCK_ID, &acc, bond, WithdrawReasons::all());
652
			let candidate = CandidateMetadata::new(bond);
652
			<CandidateInfo<T>>::insert(&acc, candidate);
652
			let empty_delegations: Delegations<T::AccountId, BalanceOf<T>> = Default::default();
652
			// insert empty top delegations
652
			<TopDelegations<T>>::insert(&acc, empty_delegations.clone());
652
			// insert empty bottom delegations
652
			<BottomDelegations<T>>::insert(&acc, empty_delegations);
652
			<CandidatePool<T>>::put(candidates);
652
			let new_total = <Total<T>>::get().saturating_add(bond);
652
			<Total<T>>::put(new_total);
652
			Self::deposit_event(Event::JoinedCollatorCandidates {
652
				account: acc,
652
				amount_locked: bond,
652
				new_total_amt_locked: new_total,
652
			});
652
			Ok(().into())
670
		}
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>>| {
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() {
9
						// we do not remove the scheduled delegation requests from other collators
9
						// since it is assumed that they were removed incrementally before only the
9
						// last delegation was left.
9
						<DelegatorState<T>>::remove(&bond.owner);
9
						T::Currency::remove_lock(DELEGATOR_LOCK_ID, &bond.owner);
9
					} else {
6
						<DelegatorState<T>>::insert(&bond.owner, delegator);
6
					}
				} else {
					// TODO: review. we assume here that this delegator has no remaining staked
					// balance, so we ensure the lock is cleared
					T::Currency::remove_lock(DELEGATOR_LOCK_ID, &bond.owner);
				}
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);
14
			}
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);
1
			}
21
			total_backing = total_backing.saturating_add(bottom_delegations.total);
21
			// return stake to collator
21
			T::Currency::remove_lock(COLLATOR_LOCK_ID, &candidate);
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 which is not locked in delegation staking
18728
		pub fn get_delegator_stakable_balance(acc: &T::AccountId) -> BalanceOf<T> {
18728
			let mut stakable_balance =
18728
				T::Currency::free_balance(acc).saturating_add(T::Currency::reserved_balance(acc));
18728
			if let Some(state) = <DelegatorState<T>>::get(acc) {
226
				stakable_balance = stakable_balance.saturating_sub(state.total());
18502
			}
18728
			stakable_balance
18728
		}
		/// Returns an account's free balance which is not locked in collator staking
1323
		pub fn get_collator_stakable_free_balance(acc: &T::AccountId) -> BalanceOf<T> {
1323
			let mut balance = T::Currency::free_balance(acc);
1323
			if let Some(info) = <CandidateInfo<T>>::get(acc) {
19
				balance = balance.saturating_sub(info.bond);
1304
			}
1323
			balance
1323
		}
		/// 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 let Ok(imb) = T::Currency::deposit_into_existing(&config.account, reserve) {
47
					// update round issuance if transfer succeeds
47
					left_issuance = left_issuance.saturating_sub(imb.peek());
47
					Self::deposit_event(Event::InflationDistributed {
47
						index: index as u32,
47
						account: config.account.clone(),
47
						value: imb.peek(),
47
					});
215
				}
			}
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
30430
		fn handle_delayed_payouts(now: RoundIndex) -> Weight {
30430
			let delay = T::RewardPaymentDelay::get();
30430

            
30430
			// don't underflow uint
30430
			if now < delay {
16493
				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)
			}
30430
		}
		/// 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.
791
		pub fn compute_top_candidates() -> Vec<T::AccountId> {
791
			let top_n = <TotalSelected<T>>::get() as usize;
791
			if top_n == 0 {
				return vec![];
791
			}
791

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

            
791
			// If the number of candidates is greater than top_n, select the candidates with higher
791
			// amount. Otherwise, return all the candidates.
791
			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
1045
				candidates.into_iter().map(|x| x.owner).collect::<Vec<_>>()
			}
791
		}
		/// Best as in most cumulatively supported in terms of stake
		/// Returns [collator_count, delegation_count, total staked]
790
		pub(crate) fn select_top_candidates(now: RoundIndex) -> (Weight, u32, u32, BalanceOf<T>) {
790
			let (mut collator_count, mut delegation_count, mut total) =
790
				(0u32, 0u32, BalanceOf::<T>::zero());
790
			// choose the top TotalSelected qualified candidates, ordered by stake
790
			let collators = Self::compute_top_candidates();
790
			if collators.is_empty() {
				// SELECTION FAILED TO SELECT >=1 COLLATOR => select collators from previous round
308
				let last_round = now.saturating_sub(1u32);
308
				let mut total_per_candidate: BTreeMap<T::AccountId, BalanceOf<T>> = BTreeMap::new();
				// set this round AtStake to last round AtStake
308
				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
351
				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
					})
				}
308
				let weight = <T as Config>::WeightInfo::select_top_candidates(0, 0);
308
				return (weight, collator_count, delegation_count, total);
482
			}
			// snapshot exposure for round for weighting reward distribution
926
			for account in collators.iter() {
926
				let state = <CandidateInfo<T>>::get(account)
926
					.expect("all members of CandidateQ must be candidates");
926

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

            
926
				let auto_compounding_delegations = <AutoCompoundingDelegations<T>>::get(&account)
926
					.into_iter()
926
					.map(|x| (x.delegator, x.value))
926
					.collect::<BTreeMap<_, _>>();
926
				let rewardable_delegations = rewardable_delegations
926
					.into_iter()
926
					.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()),
926
					})
926
					.collect();
926

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

            
482
			let avg_delegator_count = delegation_count.checked_div(collator_count).unwrap_or(0);
482
			let weight = <T as Config>::WeightInfo::select_top_candidates(
482
				collator_count,
482
				avg_delegator_count,
482
			);
482
			(weight, collator_count, delegation_count, total)
790
		}
		/// 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.
926
		pub(crate) fn get_rewardable_delegators(collator: &T::AccountId) -> CountedDelegations<T> {
926
			let requests = <DelegationScheduledRequests<T>>::get(collator)
926
				.into_iter()
926
				.map(|x| (x.delegator, x.action))
926
				.collect::<BTreeMap<_, _>>();
926
			let mut uncounted_stake = BalanceOf::<T>::zero();
926
			let rewardable_delegations = <TopDelegations<T>>::get(collator)
926
				.expect("all members of CandidateQ must be candidates")
926
				.delegations
926
				.into_iter()
926
				.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
926
				})
926
				.collect();
926
			CountedDelegations {
926
				uncounted_stake,
926
				rewardable_delegations,
926
			}
926
		}
		/// 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.
		pub fn mint(amt: BalanceOf<T>, to: T::AccountId) {
			if let Ok(amount_transferred) = T::Currency::deposit_into_existing(&to, amt) {
				Self::deposit_event(Event::Rewarded {
					account: to.clone(),
					rewards: amount_transferred.peek(),
				});
			}
		}
		/// Mint a specified reward amount to the collator's account. Emits the [Rewarded] event.
90
		pub fn mint_collator_reward(
90
			_paid_for_round: RoundIndex,
90
			collator_id: T::AccountId,
90
			amt: BalanceOf<T>,
90
		) -> Weight {
90
			if let Ok(amount_transferred) = T::Currency::deposit_into_existing(&collator_id, amt) {
90
				Self::deposit_event(Event::Rewarded {
90
					account: collator_id.clone(),
90
					rewards: amount_transferred.peek(),
90
				});
90
			}
90
			<T as Config>::WeightInfo::mint_collator_reward()
90
		}
		/// 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
			if let Ok(amount_transferred) =
55
				T::Currency::deposit_into_existing(&delegator, amt.clone())
			{
55
				Self::deposit_event(Event::Rewarded {
55
					account: delegator.clone(),
55
					rewards: amount_transferred.peek(),
55
				});
55

            
55
				let compound_amount = compound_percent.mul_ceil(amount_transferred.peek());
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> {
60
		fn get() -> Vec<T::AccountId> {
60
			Self::selected_candidates().into_inner()
60
		}
	}
}