1
// Copyright 2019-2025 PureStake Inc.
2
// This file is part of Moonbeam.
3

            
4
// Moonbeam is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8

            
9
// Moonbeam is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13

            
14
// You should have received a copy of the GNU General Public License
15
// along with Moonbeam.  If not, see <http://www.gnu.org/licenses/>.
16

            
17
//! # Parachain Staking
18
//! Minimal staking pallet that implements collator selection by total backed stake.
19
//! The main difference between this pallet and `frame/pallet-staking` is that this pallet
20
//! uses direct delegation. Delegators choose exactly who they delegate and with what stake.
21
//! This is different from `frame/pallet-staking` where delegators approval vote and run Phragmen.
22
//!
23
//! ### Rules
24
//! There is a new round every `<Round<T>>::get().length` blocks.
25
//!
26
//! At the start of every round,
27
//! * issuance is calculated for collators (and their delegators) for block authoring
28
//! `T::RewardPaymentDelay` rounds ago
29
//! * a new set of collators is chosen from the candidates
30
//!
31
//! Immediately following a round change, payments are made once-per-block until all payments have
32
//! been made. In each such block, one collator is chosen for a rewards payment and is paid along
33
//! with each of its top `T::MaxTopDelegationsPerCandidate` delegators.
34
//!
35
//! To join the set of candidates, call `join_candidates` with `bond >= MinCandidateStk`.
36
//! To leave the set of candidates, call `schedule_leave_candidates`. If the call succeeds,
37
//! the collator is removed from the pool of candidates so they cannot be selected for future
38
//! collator sets, but they are not unbonded until their exit request is executed. Any signed
39
//! account may trigger the exit `T::LeaveCandidatesDelay` rounds after the round in which the
40
//! original request was made.
41
//!
42
//! To join the set of delegators, call `delegate` and pass in an account that is
43
//! already a collator candidate and `bond >= MinDelegation`. Each delegator can delegate up to
44
//! `T::MaxDelegationsPerDelegator` collator candidates by calling `delegate`.
45
//!
46
//! To revoke a delegation, call `revoke_delegation` with the collator candidate's account.
47
//! To leave the set of delegators and revoke all delegations, call `leave_delegators`.
48

            
49
#![cfg_attr(not(feature = "std"), no_std)]
50

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

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

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

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

            
78
#[pallet]
79
pub mod pallet {
80
	use crate::delegation_requests::{
81
		CancelledScheduledRequest, DelegationAction, ScheduledRequest,
82
	};
83
	use crate::{set::BoundedOrderedSet, traits::*, types::*, InflationInfo, Range, WeightInfo};
84
	use crate::{AutoCompoundConfig, AutoCompoundDelegations};
85
	use frame_support::dispatch::DispatchResultWithPostInfo;
86
	use frame_support::pallet_prelude::*;
87
	use frame_support::traits::{
88
		fungible::{Balanced, Inspect, InspectHold, Mutate, MutateFreeze},
89
		Get,
90
	};
91
	use frame_system::pallet_prelude::*;
92
	use sp_consensus_slots::Slot;
93
	use sp_runtime::{
94
		traits::{Saturating, Zero},
95
		DispatchErrorWithPostInfo, Perbill, Percent,
96
	};
97
	use sp_std::{collections::btree_map::BTreeMap, prelude::*};
98

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

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

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

            
113
	/// Configuration trait of this pallet.
114
	#[pallet::config]
115
	pub trait Config: frame_system::Config<RuntimeEvent: From<Event<Self>>> {
116
		/// The fungible type for handling balances
117
		type Currency: Inspect<Self::AccountId>
118
			+ InspectHold<Self::AccountId>
119
			+ Mutate<Self::AccountId>
120
			+ MutateFreeze<Self::AccountId, Id = Self::RuntimeFreezeReason>
121
			+ Balanced<Self::AccountId>;
122
		/// The overarching freeze identifier type.
123
		type RuntimeFreezeReason: From<FreezeReason>;
124
		/// The origin for monetary governance
125
		type MonetaryGovernanceOrigin: EnsureOrigin<Self::RuntimeOrigin>;
126
		/// Minimum number of blocks per round
127
		#[pallet::constant]
128
		type MinBlocksPerRound: Get<u32>;
129
		/// If a collator doesn't produce any block on this number of rounds, it is notified as inactive.
130
		/// This value must be less than or equal to RewardPaymentDelay.
131
		#[pallet::constant]
132
		type MaxOfflineRounds: Get<u32>;
133
		/// Number of rounds that candidates remain bonded before exit request is executable
134
		#[pallet::constant]
135
		type LeaveCandidatesDelay: Get<RoundIndex>;
136
		/// Number of rounds candidate requests to decrease self-bond must wait to be executable
137
		#[pallet::constant]
138
		type CandidateBondLessDelay: Get<RoundIndex>;
139
		/// Number of rounds that delegators remain bonded before exit request is executable
140
		#[pallet::constant]
141
		type LeaveDelegatorsDelay: Get<RoundIndex>;
142
		/// Number of rounds that delegations remain bonded before revocation request is executable
143
		#[pallet::constant]
144
		type RevokeDelegationDelay: Get<RoundIndex>;
145
		/// Number of rounds that delegation less requests must wait before executable
146
		#[pallet::constant]
147
		type DelegationBondLessDelay: Get<RoundIndex>;
148
		/// Number of rounds after which block authors are rewarded
149
		#[pallet::constant]
150
		type RewardPaymentDelay: Get<RoundIndex>;
151
		/// Minimum number of selected candidates every round
152
		#[pallet::constant]
153
		type MinSelectedCandidates: Get<u32>;
154
		/// Maximum top delegations counted per candidate
155
		#[pallet::constant]
156
		type MaxTopDelegationsPerCandidate: Get<u32>;
157
		/// Maximum bottom delegations (not counted) per candidate
158
		#[pallet::constant]
159
		type MaxBottomDelegationsPerCandidate: Get<u32>;
160
		/// Maximum delegations per delegator
161
		#[pallet::constant]
162
		type MaxDelegationsPerDelegator: Get<u32>;
163
		/// 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 between 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
	/// The reason for freezing funds.
204
	#[pallet::composite_enum]
205
	pub enum FreezeReason {
206
		/// Funds frozen for staking as a collator
207
		StakingCollator,
208
		/// Funds frozen for staking as a delegator
209
		StakingDelegator,
210
	}
211

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
721
	impl<T: Config> Default for GenesisConfig<T> {
722
3
		fn default() -> Self {
723
3
			Self {
724
3
				candidates: vec![],
725
3
				delegations: vec![],
726
3
				inflation_config: Default::default(),
727
3
				collator_commission: Default::default(),
728
3
				parachain_bond_reserve_percent: Default::default(),
729
3
				blocks_per_round: 1u32,
730
3
				num_selected_candidates: T::MinSelectedCandidates::get(),
731
3
			}
732
3
		}
733
	}
734

            
735
	#[pallet::genesis_build]
736
	impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
737
498
		fn build(&self) {
738
498
			assert!(self.blocks_per_round > 0, "Blocks per round must be > 0");
739
498
			<InflationConfig<T>>::put(self.inflation_config.clone());
740
498
			let mut candidate_count = 0u32;
741
			// Initialize the candidates
742
1119
			for &(ref candidate, balance) in &self.candidates {
743
621
				assert!(
744
621
					<Pallet<T>>::get_collator_stakable_free_balance(candidate) >= balance,
745
					"Account does not have enough balance to bond as a candidate."
746
				);
747
621
				if let Err(error) = <Pallet<T>>::join_candidates(
748
621
					T::RuntimeOrigin::from(Some(candidate.clone()).into()),
749
621
					balance,
750
621
					candidate_count,
751
621
				) {
752
3
					log::warn!("Join candidates failed in genesis with error {:?}", error);
753
618
				} else {
754
618
					candidate_count = candidate_count.saturating_add(1u32);
755
618
				}
756
			}
757

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

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

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

            
907
		/// Set the total number of collator candidates selected per round
908
		/// - changes are not applied until the start of the next round
909
		#[pallet::call_index(4)]
910
		#[pallet::weight(<T as Config>::WeightInfo::set_total_selected())]
911
11
		pub fn set_total_selected(origin: OriginFor<T>, new: u32) -> DispatchResultWithPostInfo {
912
11
			frame_system::ensure_root(origin)?;
913
10
			ensure!(
914
10
				new >= T::MinSelectedCandidates::get(),
915
1
				Error::<T>::CannotSetBelowMin
916
			);
917
9
			ensure!(
918
9
				new <= T::MaxCandidates::get(),
919
1
				Error::<T>::CannotSetAboveMaxCandidates
920
			);
921
8
			let old = <TotalSelected<T>>::get();
922
8
			ensure!(old != new, Error::<T>::NoWritingSameValue);
923
7
			ensure!(
924
7
				new < <Round<T>>::get().length,
925
2
				Error::<T>::RoundLengthMustBeGreaterThanTotalSelectedCollators,
926
			);
927
5
			<TotalSelected<T>>::put(new);
928
5
			Self::deposit_event(Event::TotalSelectedSet { old, new });
929
5
			Ok(().into())
930
		}
931

            
932
		/// Set the commission for all collators
933
		#[pallet::call_index(5)]
934
		#[pallet::weight(<T as Config>::WeightInfo::set_collator_commission())]
935
		pub fn set_collator_commission(
936
			origin: OriginFor<T>,
937
			new: Perbill,
938
4
		) -> DispatchResultWithPostInfo {
939
4
			frame_system::ensure_root(origin)?;
940
3
			let old = <CollatorCommission<T>>::get();
941
3
			ensure!(old != new, Error::<T>::NoWritingSameValue);
942
2
			<CollatorCommission<T>>::put(new);
943
2
			Self::deposit_event(Event::CollatorCommissionSet { old, new });
944
2
			Ok(().into())
945
		}
946

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

            
984
		/// Join the set of collator candidates
985
		#[pallet::call_index(7)]
986
		#[pallet::weight(<T as Config>::WeightInfo::join_candidates(*candidate_count))]
987
		pub fn join_candidates(
988
			origin: OriginFor<T>,
989
			bond: BalanceOf<T>,
990
			candidate_count: u32,
991
661
		) -> DispatchResultWithPostInfo {
992
661
			let acc = ensure_signed(origin)?;
993
661
			ensure!(
994
661
				bond >= T::MinCandidateStk::get(),
995
4
				Error::<T>::CandidateBondBelowMin
996
			);
997
657
			Self::join_candidates_inner(acc, bond, candidate_count)
998
		}
999

            
		/// 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,
			)
		}
		/// 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(),
1
			)?;
18
			Pallet::<T>::deposit_event(Event::DelegationIncreased {
18
				delegator,
18
				candidate,
18
				amount: more,
18
				in_top,
18
			});
18
			Ok(Some(weight).into())
		}
		/// Request bond less for delegators wrt a specific collator candidate. The delegation's
		/// rewards for rounds while the request is pending use the reduced bonded amount.
		/// A bond less may not be performed if 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,
			)
		}
		/// Notify a collator is inactive during MaxOfflineRounds
		#[pallet::call_index(29)]
		#[pallet::weight(<T as Config>::WeightInfo::notify_inactive_collator())]
		pub fn notify_inactive_collator(
			origin: OriginFor<T>,
			collator: T::AccountId,
6
		) -> DispatchResult {
6
			ensure!(
6
				<EnableMarkingOffline<T>>::get(),
				<Error<T>>::MarkingOfflineNotEnabled
			);
6
			ensure_signed(origin)?;
6
			let mut collators_len = 0usize;
6
			let max_collators = <TotalSelected<T>>::get();
6
			if let Some(len) = <SelectedCandidates<T>>::decode_len() {
6
				collators_len = len;
6
			};
			// Check collators length is not below or eq to 66% of max_collators.
			// We use saturating logic here with (2/3)
			// as it is dangerous to use floating point numbers directly.
6
			ensure!(
6
				collators_len * 3 > (max_collators * 2) as usize,
1
				<Error<T>>::TooLowCollatorCountToNotifyAsInactive
			);
5
			let round_info = <Round<T>>::get();
5
			let max_offline_rounds = T::MaxOfflineRounds::get();
5
			ensure!(
5
				round_info.current > max_offline_rounds,
1
				<Error<T>>::CurrentRoundTooLow
			);
			// Have rounds_to_check = [8,9]
			// in case we are in round 10 for instance
			// with MaxOfflineRounds = 2
4
			let first_round_to_check = round_info.current.saturating_sub(max_offline_rounds);
4
			let rounds_to_check = first_round_to_check..round_info.current;
			// If this counter is eq to max_offline_rounds,
			// the collator should be notified as inactive
4
			let mut inactive_counter: RoundIndex = 0u32;
			// Iter rounds and check whether the collator has been inactive
12
			for r in rounds_to_check {
8
				if <WasInactive<T>>::get(r, &collator).is_some() {
4
					inactive_counter = inactive_counter.saturating_add(1);
4
				}
			}
4
			if inactive_counter == max_offline_rounds {
2
				let _ = T::OnInactiveCollator::on_inactive_collator(
2
					collator.clone(),
2
					round_info.current.saturating_sub(1),
2
				);
2
			} else {
2
				return Err(<Error<T>>::CannotBeNotifiedAsInactive.into());
			}
2
			Ok(().into())
		}
		/// Enable/Disable marking offline feature
		#[pallet::call_index(30)]
		#[pallet::weight(
			Weight::from_parts(3_000_000u64, 4_000u64)
				.saturating_add(T::DbWeight::get().writes(1u64))
		)]
3
		pub fn enable_marking_offline(origin: OriginFor<T>, value: bool) -> DispatchResult {
3
			ensure_root(origin)?;
2
			<EnableMarkingOffline<T>>::set(value);
2
			Ok(())
		}
		/// Force join the set of collator candidates.
		/// It will skip the minimum required bond check.
		#[pallet::call_index(31)]
		#[pallet::weight(<T as Config>::WeightInfo::join_candidates(*candidate_count))]
		pub fn force_join_candidates(
			origin: OriginFor<T>,
			account: T::AccountId,
			bond: BalanceOf<T>,
			candidate_count: u32,
1
		) -> DispatchResultWithPostInfo {
1
			T::MonetaryGovernanceOrigin::ensure_origin(origin.clone())?;
1
			Self::join_candidates_inner(account, bond, candidate_count)
		}
		/// Set the inflation distribution configuration.
		#[pallet::call_index(32)]
		#[pallet::weight(<T as Config>::WeightInfo::set_inflation_distribution_config())]
		pub fn set_inflation_distribution_config(
			origin: OriginFor<T>,
			new: InflationDistributionConfig<T::AccountId>,
30
		) -> DispatchResultWithPostInfo {
30
			T::MonetaryGovernanceOrigin::ensure_origin(origin)?;
29
			let old = <InflationDistributionInfo<T>>::get().0;
29
			let new = new.0;
29
			ensure!(old != new, Error::<T>::NoWritingSameValue);
56
			let total_percent = new.iter().fold(0, |acc, x| acc + x.percent.deconstruct());
28
			ensure!(
28
				total_percent <= 100,
8
				Error::<T>::TotalInflationDistributionPercentExceeds100,
			);
20
			<InflationDistributionInfo<T>>::put::<InflationDistributionConfig<T::AccountId>>(
20
				new.clone().into(),
			);
20
			Self::deposit_event(Event::InflationDistributionConfigUpdated {
20
				old: old.into(),
20
				new: new.into(),
20
			});
20
			Ok(().into())
		}
	}
	/// Represents a payout made via `pay_one_collator_reward`.
	pub(crate) enum RewardPayment {
		/// A collator was paid
		Paid,
		/// A collator was skipped for payment. This can happen if they haven't been awarded any
		/// points, that is, they did not produce any blocks.
		Skipped,
		/// All collator payments have been processed.
		Finished,
	}
	impl<T: Config> Pallet<T> {
		/// Set freeze
		///
		/// `is_collator` determines whether the account is a collator or delegator
1299
		pub(crate) fn freeze_extended(
1299
			account: &T::AccountId,
1299
			amount: BalanceOf<T>,
1299
			is_collator: bool,
1299
		) -> DispatchResult {
			use frame_support::traits::fungible::MutateFreeze;
			// Now set the freeze
1299
			let freeze_reason = if is_collator {
652
				FreezeReason::StakingCollator
			} else {
647
				FreezeReason::StakingDelegator
			};
1299
			T::Currency::set_freeze(&freeze_reason.into(), account, amount)
1299
		}
		/// `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
20021
		pub(crate) fn balance_frozen_extended(
20021
			account: &T::AccountId,
20021
			is_collator: bool,
20021
		) -> Option<BalanceOf<T>> {
			// Now return the frozen balance
20021
			if is_collator {
1289
				<CandidateInfo<T>>::get(account).map(|info| info.bond)
			} else {
18732
				<DelegatorState<T>>::get(account).map(|state| state.total)
			}
20021
		}
683
		pub fn is_delegator(acc: &T::AccountId) -> bool {
683
			<DelegatorState<T>>::get(acc).is_some()
683
		}
9675
		pub fn is_candidate(acc: &T::AccountId) -> bool {
9675
			<CandidateInfo<T>>::get(acc).is_some()
9675
		}
2
		pub fn is_selected_candidate(acc: &T::AccountId) -> bool {
2
			<SelectedCandidates<T>>::get().binary_search(acc).is_ok()
2
		}
658
		pub fn join_candidates_inner(
658
			acc: T::AccountId,
658
			bond: BalanceOf<T>,
658
			candidate_count: u32,
658
		) -> DispatchResultWithPostInfo {
658
			ensure!(!Self::is_candidate(&acc), Error::<T>::CandidateExists);
654
			ensure!(!Self::is_delegator(&acc), Error::<T>::DelegatorExists);
650
			let mut candidates = <CandidatePool<T>>::get();
650
			let old_count = candidates.0.len() as u32;
650
			ensure!(
650
				candidate_count >= old_count,
5
				Error::<T>::TooLowCandidateCountWeightHintJoinCandidates
			);
645
			let maybe_inserted_candidate = candidates
645
				.try_insert(Bond {
645
					owner: acc.clone(),
645
					amount: bond,
645
				})
645
				.map_err(|_| Error::<T>::CandidateLimitReached)?;
644
			ensure!(maybe_inserted_candidate, Error::<T>::CandidateExists);
644
			ensure!(
644
				Self::get_collator_stakable_free_balance(&acc) >= bond,
4
				Error::<T>::InsufficientBalance,
			);
640
			Self::freeze_extended(&acc, bond, true).map_err(|_| Error::<T>::InsufficientBalance)?;
640
			let candidate = CandidateMetadata::new(bond);
640
			<CandidateInfo<T>>::insert(&acc, candidate);
640
			let empty_delegations: Delegations<T::AccountId, BalanceOf<T>> = Default::default();
			// insert empty top delegations
640
			<TopDelegations<T>>::insert(&acc, empty_delegations.clone());
			// insert empty bottom delegations
640
			<BottomDelegations<T>>::insert(&acc, empty_delegations);
640
			<CandidatePool<T>>::put(candidates);
640
			let new_total = <Total<T>>::get().saturating_add(bond);
640
			<Total<T>>::put(new_total);
640
			Self::deposit_event(Event::JoinedCollatorCandidates {
640
				account: acc,
640
				amount_locked: bond,
640
				new_total_amt_locked: new_total,
640
			});
640
			Ok(().into())
658
		}
16
		pub fn go_offline_inner(collator: T::AccountId) -> DispatchResultWithPostInfo {
16
			let mut state = <CandidateInfo<T>>::get(&collator).ok_or(Error::<T>::CandidateDNE)?;
15
			let mut candidates = <CandidatePool<T>>::get();
15
			let actual_weight = <T as Config>::WeightInfo::go_offline(candidates.0.len() as u32);
15
			ensure!(
15
				state.is_active(),
1
				DispatchErrorWithPostInfo {
1
					post_info: Some(actual_weight).into(),
1
					error: <Error<T>>::AlreadyOffline.into(),
1
				}
			);
14
			state.go_offline();
14
			if candidates.remove(&Bond::from_owner(collator.clone())) {
14
				<CandidatePool<T>>::put(candidates);
14
			}
14
			<CandidateInfo<T>>::insert(&collator, state);
14
			Self::deposit_event(Event::CandidateWentOffline {
14
				candidate: collator,
14
			});
14
			Ok(Some(actual_weight).into())
16
		}
7
		pub fn go_online_inner(collator: T::AccountId) -> DispatchResultWithPostInfo {
7
			let mut state = <CandidateInfo<T>>::get(&collator).ok_or(Error::<T>::CandidateDNE)?;
6
			let mut candidates = <CandidatePool<T>>::get();
6
			let actual_weight = <T as Config>::WeightInfo::go_online(candidates.0.len() as u32);
6
			ensure!(
6
				!state.is_active(),
1
				DispatchErrorWithPostInfo {
1
					post_info: Some(actual_weight).into(),
1
					error: <Error<T>>::AlreadyActive.into(),
1
				}
			);
5
			ensure!(
5
				!state.is_leaving(),
1
				DispatchErrorWithPostInfo {
1
					post_info: Some(actual_weight).into(),
1
					error: <Error<T>>::CannotGoOnlineIfLeaving.into(),
1
				}
			);
4
			state.go_online();
4
			let maybe_inserted_candidate = candidates
4
				.try_insert(Bond {
4
					owner: collator.clone(),
4
					amount: state.total_counted,
4
				})
4
				.map_err(|_| Error::<T>::CandidateLimitReached)?;
4
			ensure!(
4
				maybe_inserted_candidate,
				DispatchErrorWithPostInfo {
					post_info: Some(actual_weight).into(),
					error: <Error<T>>::AlreadyActive.into(),
				},
			);
4
			<CandidatePool<T>>::put(candidates);
4
			<CandidateInfo<T>>::insert(&collator, state);
4
			Self::deposit_event(Event::CandidateBackOnline {
4
				candidate: collator,
4
			});
4
			Ok(Some(actual_weight).into())
7
		}
6
		pub fn candidate_bond_more_inner(
6
			collator: T::AccountId,
6
			more: BalanceOf<T>,
6
		) -> DispatchResultWithPostInfo {
6
			let mut state = <CandidateInfo<T>>::get(&collator).ok_or(Error::<T>::CandidateDNE)?;
6
			let actual_weight =
6
				<T as Config>::WeightInfo::candidate_bond_more(T::MaxCandidates::get());
6
			state
6
				.bond_more::<T>(collator.clone(), more)
6
				.map_err(|err| DispatchErrorWithPostInfo {
					post_info: Some(actual_weight).into(),
					error: err,
				})?;
6
			let (is_active, total_counted) = (state.is_active(), state.total_counted);
6
			<CandidateInfo<T>>::insert(&collator, state);
6
			if is_active {
6
				Self::update_active(collator, total_counted);
6
			}
6
			Ok(Some(actual_weight).into())
6
		}
6
		pub fn execute_candidate_bond_less_inner(
6
			candidate: T::AccountId,
6
		) -> DispatchResultWithPostInfo {
6
			let mut state = <CandidateInfo<T>>::get(&candidate).ok_or(Error::<T>::CandidateDNE)?;
6
			let actual_weight =
6
				<T as Config>::WeightInfo::execute_candidate_bond_less(T::MaxCandidates::get());
6
			state
6
				.execute_bond_less::<T>(candidate.clone())
6
				.map_err(|err| DispatchErrorWithPostInfo {
					post_info: Some(actual_weight).into(),
					error: err,
				})?;
6
			<CandidateInfo<T>>::insert(&candidate, state);
6
			Ok(Some(actual_weight).into())
6
		}
23
		pub fn execute_leave_candidates_inner(
23
			candidate: T::AccountId,
23
		) -> DispatchResultWithPostInfo {
23
			let state = <CandidateInfo<T>>::get(&candidate).ok_or(Error::<T>::CandidateDNE)?;
23
			let actual_auto_compound_delegation_count =
23
				<AutoCompoundingDelegations<T>>::decode_len(&candidate).unwrap_or_default() as u32;
			// TODO use these to return actual weight used via `execute_leave_candidates_ideal`
23
			let actual_delegation_count = state.delegation_count;
23
			let actual_weight = <T as Config>::WeightInfo::execute_leave_candidates_ideal(
23
				actual_delegation_count,
23
				actual_auto_compound_delegation_count,
			);
23
			state
23
				.can_leave::<T>()
23
				.map_err(|err| DispatchErrorWithPostInfo {
2
					post_info: Some(actual_weight).into(),
2
					error: err,
2
				})?;
21
			let return_stake = |bond: Bond<T::AccountId, BalanceOf<T>>| -> DispatchResult {
				// remove delegation from delegator state
15
				let mut delegator = DelegatorState::<T>::get(&bond.owner).expect(
15
					"Collator state and delegator state are consistent.
15
						Collator state has a record of this delegation. Therefore,
15
						Delegator state also has a record. qed.",
				);
15
				if let Some(remaining) = delegator.rm_delegation::<T>(&candidate) {
15
					Self::delegation_remove_request_with_state(
15
						&candidate,
15
						&bond.owner,
15
						&mut delegator,
					);
15
					<AutoCompoundDelegations<T>>::remove_auto_compound(&candidate, &bond.owner);
15
					if remaining.is_zero() {
						// we do not remove the scheduled delegation requests from other collators
						// since it is assumed that they were removed incrementally before only the
						// last delegation was left.
9
						<DelegatorState<T>>::remove(&bond.owner);
						// Thaw all frozen funds for delegator
9
						Self::thaw_extended(&bond.owner, false)?;
6
					} else {
6
						<DelegatorState<T>>::insert(&bond.owner, delegator);
6
					}
				} else {
					Self::thaw_extended(&bond.owner, false)?;
				}
15
				Ok(())
15
			};
			// total backing stake is at least the candidate self bond
21
			let mut total_backing = state.bond;
			// return all top delegations
21
			let top_delegations =
21
				<TopDelegations<T>>::take(&candidate).expect("CandidateInfo existence checked");
35
			for bond in top_delegations.delegations {
14
				return_stake(bond)?;
			}
21
			total_backing = total_backing.saturating_add(top_delegations.total);
			// return all bottom delegations
21
			let bottom_delegations =
21
				<BottomDelegations<T>>::take(&candidate).expect("CandidateInfo existence checked");
22
			for bond in bottom_delegations.delegations {
1
				return_stake(bond)?;
			}
21
			total_backing = total_backing.saturating_add(bottom_delegations.total);
			// Thaw all frozen funds for collator
21
			Self::thaw_extended(&candidate, true)?;
21
			<CandidateInfo<T>>::remove(&candidate);
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
18732
		pub fn get_delegator_stakable_balance(acc: &T::AccountId) -> BalanceOf<T> {
18732
			let total_balance =
18732
				T::Currency::balance(acc).saturating_add(T::Currency::total_balance_on_hold(acc));
18732
			if let Some(frozen_balance) = Self::balance_frozen_extended(acc, false) {
226
				return total_balance.saturating_sub(frozen_balance);
18506
			}
18506
			total_balance
18732
		}
		/// Returns an account's free balance which is not frozen in collator staking
1289
		pub fn get_collator_stakable_free_balance(acc: &T::AccountId) -> BalanceOf<T> {
1289
			let total_balance = T::Currency::balance(acc);
1289
			if let Some(frozen_balance) = Self::balance_frozen_extended(acc, true) {
19
				return total_balance.saturating_sub(frozen_balance);
1270
			}
1270
			total_balance
1289
		}
		/// 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
518
		pub(crate) fn update_active(candidate: T::AccountId, total: BalanceOf<T>) {
518
			let mut candidates = <CandidatePool<T>>::get();
518
			candidates.remove(&Bond::from_owner(candidate.clone()));
518
			candidates
518
				.try_insert(Bond {
518
					owner: candidate,
518
					amount: total,
518
				})
518
				.expect(
518
					"the candidate is removed in previous step so the length cannot increase; qed",
				);
518
			<CandidatePool<T>>::put(candidates);
518
		}
		/// 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);
			// Initial formula: (round_duration / ideal_duration) * ideal_issuance
			// 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 {
			let RoundInfo {
261
				current: now,
261
				length: round_length,
				..
261
			} = round_info;
			// This function is called right after the round index increment,
			// and the goal is to compute the payout informations for the round that just ended.
			// We don't need to saturate here because the genesis round is 1.
261
			let prepare_payout_for_round = now - 1;
			// 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
			}
			// Compute total issuance based on round duration
261
			let total_issuance = Self::compute_issuance(round_duration, round_length);
			// reserve portion of issuance for parachain bond account
261
			let mut left_issuance = total_issuance;
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
			<DelayedPayouts<T>>::insert(prepare_payout_for_round, payout);
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
30421
		fn handle_delayed_payouts(now: RoundIndex) -> Weight {
30421
			let delay = T::RewardPaymentDelay::get();
			// don't underflow uint
30421
			if now < delay {
16484
				return Weight::from_parts(0u64, 0);
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)
			}
30421
		}
		/// Payout a single collator from the given round.
		///
		/// Returns an optional tuple of (Collator's AccountId, total paid)
		/// or None if there were no more payouts to be made for the round.
352
		pub(crate) fn pay_one_collator_reward(
352
			paid_for_round: RoundIndex,
352
			payout_info: DelayedPayout<BalanceOf<T>>,
352
		) -> (RewardPayment, Weight) {
			// 'early_weight' tracks weight used for reads/writes done early in this fn before its
			// early-exit codepaths.
352
			let mut early_weight = Weight::zero();
			// TODO: it would probably be optimal to roll Points into the DelayedPayouts storage
			// item so that we do fewer reads each block
352
			let total_points = <Points<T>>::get(paid_for_round);
352
			early_weight = early_weight.saturating_add(T::DbWeight::get().reads_writes(1, 0));
352
			if total_points.is_zero() {
				// TODO: this case is obnoxious... it's a value query, so it could mean one of two
				// different logic errors:
				// 1. we removed it before we should have
				// 2. we called pay_one_collator_reward when we were actually done with deferred
				//    payouts
				log::warn!("pay_one_collator_reward called with no <Points<T>> for the round!");
				return (RewardPayment::Finished, early_weight);
352
			}
352
			let collator_fee = payout_info.collator_commission;
352
			let collator_issuance = collator_fee * payout_info.round_issuance;
289
			if let Some((collator, state)) =
352
				<AtStake<T>>::iter_prefix(paid_for_round).drain().next()
			{
				// read and kill AtStake
289
				early_weight = early_weight.saturating_add(T::DbWeight::get().reads_writes(1, 1));
				// Take the awarded points for the collator
289
				let pts = <AwardedPts<T>>::take(paid_for_round, &collator);
				// read and kill AwardedPts
289
				early_weight = early_weight.saturating_add(T::DbWeight::get().reads_writes(1, 1));
289
				if pts == 0 {
233
					return (RewardPayment::Skipped, early_weight);
56
				}
				// 'extra_weight' tracks weight returned from fns that we delegate to which can't be
				// known ahead of time.
56
				let mut extra_weight = Weight::zero();
56
				let pct_due = Perbill::from_rational(pts, total_points);
56
				let total_paid = pct_due * payout_info.total_staking_reward;
56
				let mut amt_due = total_paid;
56
				let num_delegators = state.delegations.len();
56
				let mut num_paid_delegations = 0u32;
56
				let mut num_auto_compounding = 0u32;
56
				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(),
							);
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
					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.
760
		pub fn compute_top_candidates() -> Vec<T::AccountId> {
760
			let top_n = <TotalSelected<T>>::get() as usize;
760
			if top_n == 0 {
				return vec![];
760
			}
760
			let candidates = <CandidatePool<T>>::get().0;
			// If the number of candidates is greater than top_n, select the candidates with higher
			// amount. Otherwise, return all the candidates.
760
			if candidates.len() > top_n {
				// Partially sort candidates such that element at index `top_n - 1` is sorted, and
				// all the elements in the range 0..top_n are the top n elements.
15
				let sorted_candidates = candidates
15
					.try_mutate(|inner| {
1083
						inner.select_nth_unstable_by(top_n - 1, |a, b| {
							// Order by amount, then owner. The owner is needed to ensure a stable order
							// when two accounts have the same amount.
1083
							a.amount
1083
								.cmp(&b.amount)
1083
								.then_with(|| a.owner.cmp(&b.owner))
1083
								.reverse()
1083
						});
15
					})
15
					.expect("sort cannot increase item count; qed");
15
				let mut collators = sorted_candidates
15
					.into_iter()
15
					.take(top_n)
15
					.map(|x| x.owner)
15
					.collect::<Vec<_>>();
				// Sort collators by AccountId
15
				collators.sort();
15
				collators
			} else {
				// Return all candidates
				// The candidates are already sorted by AccountId, so no need to sort again
745
				candidates.into_iter().map(|x| x.owner).collect::<Vec<_>>()
			}
760
		}
		/// Best as in most cumulatively supported in terms of stake
		/// Returns [collator_count, delegation_count, total staked]
759
		pub(crate) fn select_top_candidates(now: RoundIndex) -> (Weight, u32, u32, BalanceOf<T>) {
759
			let (mut collator_count, mut delegation_count, mut total) =
759
				(0u32, 0u32, BalanceOf::<T>::zero());
			// choose the top TotalSelected qualified candidates, ordered by stake
759
			let collators = Self::compute_top_candidates();
759
			if collators.is_empty() {
				// SELECTION FAILED TO SELECT >=1 COLLATOR => select collators from previous round
289
				let last_round = now.saturating_sub(1u32);
289
				let mut total_per_candidate: BTreeMap<T::AccountId, BalanceOf<T>> = BTreeMap::new();
				// set this round AtStake to last round AtStake
289
				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
289
				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
					})
				}
289
				let weight = <T as Config>::WeightInfo::select_top_candidates(0, 0);
289
				return (weight, collator_count, delegation_count, total);
470
			}
			// snapshot exposure for round for weighting reward distribution
914
			for account in collators.iter() {
914
				let state = <CandidateInfo<T>>::get(account)
914
					.expect("all members of CandidateQ must be candidates");
914
				collator_count = collator_count.saturating_add(1u32);
914
				delegation_count = delegation_count.saturating_add(state.delegation_count);
914
				total = total.saturating_add(state.total_counted);
				let CountedDelegations {
914
					uncounted_stake,
914
					rewardable_delegations,
914
				} = Self::get_rewardable_delegators(&account);
914
				let total_counted = state.total_counted.saturating_sub(uncounted_stake);
914
				let auto_compounding_delegations = <AutoCompoundingDelegations<T>>::get(&account)
914
					.into_iter()
914
					.map(|x| (x.delegator, x.value))
914
					.collect::<BTreeMap<_, _>>();
914
				let rewardable_delegations = rewardable_delegations
914
					.into_iter()
914
					.map(|d| BondWithAutoCompound {
767
						owner: d.owner.clone(),
767
						amount: d.amount,
767
						auto_compound: auto_compounding_delegations
767
							.get(&d.owner)
767
							.cloned()
767
							.unwrap_or_else(|| Percent::zero()),
767
					})
914
					.collect();
914
				let snapshot = CollatorSnapshot {
914
					bond: state.bond,
914
					delegations: rewardable_delegations,
914
					total: total_counted,
914
				};
914
				<AtStake<T>>::insert(now, account, snapshot);
914
				Self::deposit_event(Event::CollatorChosen {
914
					round: now,
914
					collator_account: account.clone(),
914
					total_exposed_amount: state.total_counted,
914
				});
			}
			// insert canonical collator set
470
			<SelectedCandidates<T>>::put(
470
				BoundedVec::try_from(collators)
470
					.expect("subset of collators is always less than or equal to max candidates"),
			);
470
			let avg_delegator_count = delegation_count.checked_div(collator_count).unwrap_or(0);
470
			let weight = <T as Config>::WeightInfo::select_top_candidates(
470
				collator_count,
470
				avg_delegator_count,
			);
470
			(weight, collator_count, delegation_count, total)
759
		}
		/// 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.
914
		pub(crate) fn get_rewardable_delegators(collator: &T::AccountId) -> CountedDelegations<T> {
914
			let requests = <DelegationScheduledRequests<T>>::get(collator)
914
				.into_iter()
914
				.map(|x| (x.delegator, x.action))
914
				.collect::<BTreeMap<_, _>>();
914
			let mut uncounted_stake = BalanceOf::<T>::zero();
914
			let rewardable_delegations = <TopDelegations<T>>::get(collator)
914
				.expect("all members of CandidateQ must be candidates")
914
				.delegations
914
				.into_iter()
914
				.map(|mut bond| {
767
					bond.amount = match requests.get(&bond.owner) {
656
						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)
						}
					};
767
					bond
767
				})
914
				.collect();
914
			CountedDelegations {
914
				uncounted_stake,
914
				rewardable_delegations,
914
			}
914
		}
		/// 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
			let in_top = state
21
				.increase_delegation::<T>(candidate.clone(), more)
21
				.map_err(|err| DispatchErrorWithPostInfo {
					post_info: Some(actual_weight).into(),
					error: err,
				})?;
21
			Ok((in_top, actual_weight))
23
		}
		/// Mint a specified reward amount to the beneficiary account. Emits the [Rewarded] event.
143
		pub fn mint(amt: BalanceOf<T>, to: &T::AccountId) -> Result<BalanceOf<T>, DispatchError> {
			// Mint rewards to the account
143
			let minted = T::Currency::mint_into(&to, amt)?;
143
			Self::deposit_event(Event::Rewarded {
143
				account: to.clone(),
143
				rewards: minted,
143
			});
143
			Ok(minted)
143
		}
		/// Mint a specified reward amount to the collator's account. Emits the [Rewarded] event.
88
		pub fn mint_collator_reward(
88
			_paid_for_round: RoundIndex,
88
			collator_id: T::AccountId,
88
			amt: BalanceOf<T>,
88
		) -> Weight {
			// Mint rewards to the collator
88
			if let Err(e) = Self::mint(amt, &collator_id) {
				log::warn!(
					"Failed to mint collator reward for {:?}: {:?}",
					collator_id,
					e
				);
88
			}
88
			<T as Config>::WeightInfo::mint_collator_reward()
88
		}
		/// Mint and compound delegation rewards. The function mints the amount towards the
		/// delegator and tries to compound a specified percent of it back towards the delegation.
		/// If a scheduled delegation revoke exists, then the amount is only minted, and nothing is
		/// compounded. Emits the [Compounded] event.
55
		pub fn mint_and_compound(
55
			amt: BalanceOf<T>,
55
			compound_percent: Percent,
55
			candidate: T::AccountId,
55
			delegator: T::AccountId,
55
		) {
			// Mint rewards to the delegator
55
			if frame_system::Pallet::<T>::account_exists(&delegator) {
55
				if let Ok(minted) = Self::mint(amt.clone(), &delegator) {
55
					let compound_amount = compound_percent.mul_ceil(minted);
55
					if compound_amount.is_zero() {
51
						return;
4
					}
4
					if let Err(err) = Self::delegation_bond_more_without_event(
4
						delegator.clone(),
4
						candidate.clone(),
4
						compound_amount.clone(),
4
					) {
1
						log::debug!(
							"skipped compounding staking reward towards candidate '{:?}' for delegator '{:?}': {:?}",
							candidate,
							delegator,
							err
						);
1
						return;
3
					};
3
					Pallet::<T>::deposit_event(Event::Compounded {
3
						delegator,
3
						candidate,
3
						amount: compound_amount.clone(),
3
					});
				};
			}
55
		}
		/// Add reward points to block authors:
		/// * 20 points to the block producer for producing a block in the chain
30623
		fn award_points_to_block_author() {
30623
			let author = T::BlockAuthor::get();
30623
			let now = <Round<T>>::get().current;
30623
			let score_plus_20 = <AwardedPts<T>>::get(now, &author).saturating_add(20);
30623
			<AwardedPts<T>>::insert(now, author, score_plus_20);
30623
			<Points<T>>::mutate(now, |x| *x = x.saturating_add(20));
30623
		}
		/// Marks collators as inactive for the previous round if they received zero awarded points.
261
		pub fn mark_collators_as_inactive(cur: RoundIndex) -> Weight {
			// This function is called after round index increment,
			// We don't need to saturate here because the genesis round is 1.
261
			let prev = cur - 1;
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.
30623
		fn cleanup_inactive_collator_info() {
30623
			let now = <Round<T>>::get().current;
30623
			let minimum_rounds_required = T::MaxOfflineRounds::get() + 1;
30623
			if now < minimum_rounds_required {
22898
				return;
7725
			}
7725
			let _ = <WasInactive<T>>::iter_prefix(now - minimum_rounds_required)
7725
				.drain()
7725
				.next();
30623
		}
	}
	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
		}
	}
}