1
// Copyright 2019-2022 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
10198
#[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::fail;
86
	use frame_support::pallet_prelude::*;
87
	use frame_support::traits::{
88
		tokens::WithdrawReasons, Currency, Get, Imbalance, LockIdentifier, LockableCurrency,
89
		ReservableCurrency,
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
32
	#[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 Currency<<T as frame_system::Config>::AccountId>>::Balance;
108

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

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

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

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

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

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

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

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

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

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

            
499
			// add on_finalize weight
500
			//   read:  Author, Points, AwardedPts
501
			//   write: Points, AwardedPts
502
30672
			weight = weight.saturating_add(T::DbWeight::get().reads_writes(3, 2));
503
30672
			weight
504
30672
		}
505
29483
		fn on_finalize(_n: BlockNumberFor<T>) {
506
29483
			Self::award_points_to_block_author();
507
29483
		}
508
	}
509

            
510
1184
	#[pallet::storage]
511
	#[pallet::getter(fn collator_commission)]
512
	/// Commission percent taken off of rewards for all collators
513
	type CollatorCommission<T: Config> = StorageValue<_, Perbill, ValueQuery>;
514

            
515
2634
	#[pallet::storage]
516
	#[pallet::getter(fn total_selected)]
517
	/// The total candidates selected every round
518
	pub(crate) type TotalSelected<T: Config> = StorageValue<_, u32, ValueQuery>;
519

            
520
1210
	#[pallet::storage]
521
	#[pallet::getter(fn parachain_bond_info)]
522
	/// Parachain bond config info { account, percent_of_inflation }
523
	pub(crate) type ParachainBondInfo<T: Config> =
524
		StorageValue<_, ParachainBondConfig<T::AccountId>, ValueQuery>;
525

            
526
122584
	#[pallet::storage]
527
	#[pallet::getter(fn round)]
528
	/// Current round index and next round scheduled transition
529
	pub type Round<T: Config> = StorageValue<_, RoundInfo<BlockNumberFor<T>>, ValueQuery>;
530

            
531
29506
	#[pallet::storage]
532
	#[pallet::getter(fn delegator_state)]
533
	/// Get delegator state associated with an account if account is delegating else None
534
	pub(crate) type DelegatorState<T: Config> = StorageMap<
535
		_,
536
		Twox64Concat,
537
		T::AccountId,
538
		Delegator<T::AccountId, BalanceOf<T>>,
539
		OptionQuery,
540
	>;
541

            
542
22703
	#[pallet::storage]
543
	#[pallet::getter(fn candidate_info)]
544
	/// Get collator candidate info associated with an account if account is candidate else None
545
	pub(crate) type CandidateInfo<T: Config> =
546
		StorageMap<_, Twox64Concat, T::AccountId, CandidateMetadata<BalanceOf<T>>, OptionQuery>;
547

            
548
	pub struct AddGet<T, R> {
549
		_phantom: PhantomData<(T, R)>,
550
	}
551
	impl<T, R> Get<u32> for AddGet<T, R>
552
	where
553
		T: Get<u32>,
554
		R: Get<u32>,
555
	{
556
377
		fn get() -> u32 {
557
377
			T::get() + R::get()
558
377
		}
559
	}
560

            
561
	/// Stores outstanding delegation requests per collator.
562
1361
	#[pallet::storage]
563
	#[pallet::getter(fn delegation_scheduled_requests)]
564
	pub(crate) type DelegationScheduledRequests<T: Config> = StorageMap<
565
		_,
566
		Blake2_128Concat,
567
		T::AccountId,
568
		BoundedVec<
569
			ScheduledRequest<T::AccountId, BalanceOf<T>>,
570
			AddGet<T::MaxTopDelegationsPerCandidate, T::MaxBottomDelegationsPerCandidate>,
571
		>,
572
		ValueQuery,
573
	>;
574

            
575
	/// Stores auto-compounding configuration per collator.
576
1121
	#[pallet::storage]
577
	#[pallet::getter(fn auto_compounding_delegations)]
578
	pub(crate) type AutoCompoundingDelegations<T: Config> = StorageMap<
579
		_,
580
		Blake2_128Concat,
581
		T::AccountId,
582
		BoundedVec<
583
			AutoCompoundConfig<T::AccountId>,
584
			AddGet<T::MaxTopDelegationsPerCandidate, T::MaxBottomDelegationsPerCandidate>,
585
		>,
586
		ValueQuery,
587
	>;
588

            
589
2626
	#[pallet::storage]
590
	#[pallet::getter(fn top_delegations)]
591
	/// Top delegations for collator candidate
592
	pub(crate) type TopDelegations<T: Config> = StorageMap<
593
		_,
594
		Twox64Concat,
595
		T::AccountId,
596
		Delegations<T::AccountId, BalanceOf<T>>,
597
		OptionQuery,
598
	>;
599

            
600
1089
	#[pallet::storage]
601
	#[pallet::getter(fn bottom_delegations)]
602
	/// Bottom delegations for collator candidate
603
	pub(crate) type BottomDelegations<T: Config> = StorageMap<
604
		_,
605
		Twox64Concat,
606
		T::AccountId,
607
		Delegations<T::AccountId, BalanceOf<T>>,
608
		OptionQuery,
609
	>;
610

            
611
1757
	#[pallet::storage]
612
	#[pallet::getter(fn selected_candidates)]
613
	/// The collator candidates selected for the current round
614
	type SelectedCandidates<T: Config> =
615
		StorageValue<_, BoundedVec<T::AccountId, T::MaxCandidates>, ValueQuery>;
616

            
617
5354
	#[pallet::storage]
618
	#[pallet::getter(fn total)]
619
	/// Total capital locked by this staking pallet
620
	pub(crate) type Total<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
621

            
622
6574
	#[pallet::storage]
623
	#[pallet::getter(fn candidate_pool)]
624
	/// The pool of collator candidates, each with their total backing stake
625
	pub(crate) type CandidatePool<T: Config> = StorageValue<
626
		_,
627
		BoundedOrderedSet<Bond<T::AccountId, BalanceOf<T>>, T::MaxCandidates>,
628
		ValueQuery,
629
	>;
630

            
631
1419
	#[pallet::storage]
632
	#[pallet::getter(fn at_stake)]
633
	/// Snapshot of collator delegation stake at the start of the round
634
	pub type AtStake<T: Config> = StorageDoubleMap<
635
		_,
636
		Twox64Concat,
637
		RoundIndex,
638
		Twox64Concat,
639
		T::AccountId,
640
		CollatorSnapshot<T::AccountId, BalanceOf<T>>,
641
		OptionQuery,
642
	>;
643

            
644
14022
	#[pallet::storage]
645
	#[pallet::getter(fn delayed_payouts)]
646
	/// Delayed payouts
647
	pub type DelayedPayouts<T: Config> =
648
		StorageMap<_, Twox64Concat, RoundIndex, DelayedPayout<BalanceOf<T>>, OptionQuery>;
649

            
650
1258
	#[pallet::storage]
651
	#[pallet::getter(fn inflation_config)]
652
	/// Inflation configuration
653
	pub type InflationConfig<T: Config> = StorageValue<_, InflationInfo<BalanceOf<T>>, ValueQuery>;
654

            
655
29969
	#[pallet::storage]
656
	#[pallet::getter(fn points)]
657
	/// Total points awarded to collators for block production in the round
658
	pub type Points<T: Config> = StorageMap<_, Twox64Concat, RoundIndex, RewardPoint, ValueQuery>;
659

            
660
59180
	#[pallet::storage]
661
	#[pallet::getter(fn awarded_pts)]
662
	/// Points for each collator per round
663
	pub type AwardedPts<T: Config> = StorageDoubleMap<
664
		_,
665
		Twox64Concat,
666
		RoundIndex,
667
		Twox64Concat,
668
		T::AccountId,
669
		RewardPoint,
670
		ValueQuery,
671
	>;
672

            
673
30
	#[pallet::storage]
674
	#[pallet::getter(fn marking_offline)]
675
	/// Killswitch to enable/disable marking offline feature.
676
	pub type EnableMarkingOffline<T: Config> = StorageValue<_, bool, ValueQuery>;
677

            
678
	#[pallet::genesis_config]
679
	pub struct GenesisConfig<T: Config> {
680
		/// Initialize balance and register all as collators: `(collator AccountId, balance Amount)`
681
		pub candidates: Vec<(T::AccountId, BalanceOf<T>)>,
682
		/// Initialize balance and make delegations:
683
		/// `(delegator AccountId, collator AccountId, delegation Amount, auto-compounding Percent)`
684
		pub delegations: Vec<(T::AccountId, T::AccountId, BalanceOf<T>, Percent)>,
685
		/// Inflation configuration
686
		pub inflation_config: InflationInfo<BalanceOf<T>>,
687
		/// Default fixed percent a collator takes off the top of due rewards
688
		pub collator_commission: Perbill,
689
		/// Default percent of inflation set aside for parachain bond every round
690
		pub parachain_bond_reserve_percent: Percent,
691
		/// Default number of blocks in a round
692
		pub blocks_per_round: u32,
693
		/// Number of selected candidates every round. Cannot be lower than MinSelectedCandidates
694
		pub num_selected_candidates: u32,
695
	}
696

            
697
	impl<T: Config> Default for GenesisConfig<T> {
698
5
		fn default() -> Self {
699
5
			Self {
700
5
				candidates: vec![],
701
5
				delegations: vec![],
702
5
				inflation_config: Default::default(),
703
5
				collator_commission: Default::default(),
704
5
				parachain_bond_reserve_percent: Default::default(),
705
5
				blocks_per_round: 1u32,
706
5
				num_selected_candidates: T::MinSelectedCandidates::get(),
707
5
			}
708
5
		}
709
	}
710

            
711
502
	#[pallet::genesis_build]
712
	impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
713
507
		fn build(&self) {
714
507
			assert!(self.blocks_per_round > 0, "Blocks per round must be > 0");
715
507
			<InflationConfig<T>>::put(self.inflation_config.clone());
716
507
			let mut candidate_count = 0u32;
717
			// Initialize the candidates
718
1143
			for &(ref candidate, balance) in &self.candidates {
719
636
				assert!(
720
636
					<Pallet<T>>::get_collator_stakable_free_balance(candidate) >= balance,
721
					"Account does not have enough balance to bond as a candidate."
722
				);
723
636
				if let Err(error) = <Pallet<T>>::join_candidates(
724
636
					T::RuntimeOrigin::from(Some(candidate.clone()).into()),
725
636
					balance,
726
636
					candidate_count,
727
636
				) {
728
13
					log::warn!("Join candidates failed in genesis with error {:?}", error);
729
623
				} else {
730
623
					candidate_count = candidate_count.saturating_add(1u32);
731
623
				}
732
			}
733

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

            
823
84
	#[pallet::call]
824
	impl<T: Config> Pallet<T> {
825
		/// Set the expectations for total staked. These expectations determine the issuance for
826
		/// the round according to logic in `fn compute_issuance`
827
		#[pallet::call_index(0)]
828
		#[pallet::weight(<T as Config>::WeightInfo::set_staking_expectations())]
829
		pub fn set_staking_expectations(
830
			origin: OriginFor<T>,
831
			expectations: Range<BalanceOf<T>>,
832
6
		) -> DispatchResultWithPostInfo {
833
6
			T::MonetaryGovernanceOrigin::ensure_origin(origin)?;
834
5
			ensure!(expectations.is_valid(), Error::<T>::InvalidSchedule);
835
4
			let mut config = <InflationConfig<T>>::get();
836
4
			ensure!(
837
4
				config.expect != expectations,
838
1
				Error::<T>::NoWritingSameValue
839
			);
840
3
			config.set_expectations(expectations);
841
3
			Self::deposit_event(Event::StakeExpectationsSet {
842
3
				expect_min: config.expect.min,
843
3
				expect_ideal: config.expect.ideal,
844
3
				expect_max: config.expect.max,
845
3
			});
846
3
			<InflationConfig<T>>::put(config);
847
3
			Ok(().into())
848
		}
849

            
850
		/// Set the annual inflation rate to derive per-round inflation
851
		#[pallet::call_index(1)]
852
		#[pallet::weight(<T as Config>::WeightInfo::set_inflation())]
853
		pub fn set_inflation(
854
			origin: OriginFor<T>,
855
			schedule: Range<Perbill>,
856
7
		) -> DispatchResultWithPostInfo {
857
7
			T::MonetaryGovernanceOrigin::ensure_origin(origin)?;
858
5
			ensure!(schedule.is_valid(), Error::<T>::InvalidSchedule);
859
4
			let mut config = <InflationConfig<T>>::get();
860
4
			ensure!(config.annual != schedule, Error::<T>::NoWritingSameValue);
861
3
			config.annual = schedule;
862
3
			config.set_round_from_annual::<T>(schedule);
863
3
			Self::deposit_event(Event::InflationSet {
864
3
				annual_min: config.annual.min,
865
3
				annual_ideal: config.annual.ideal,
866
3
				annual_max: config.annual.max,
867
3
				round_min: config.round.min,
868
3
				round_ideal: config.round.ideal,
869
3
				round_max: config.round.max,
870
3
			});
871
3
			<InflationConfig<T>>::put(config);
872
3
			Ok(().into())
873
		}
874

            
875
		/// Set the account that will hold funds set aside for parachain bond
876
		#[pallet::call_index(2)]
877
		#[pallet::weight(<T as Config>::WeightInfo::set_parachain_bond_account())]
878
		pub fn set_parachain_bond_account(
879
			origin: OriginFor<T>,
880
			new: T::AccountId,
881
6
		) -> DispatchResultWithPostInfo {
882
6
			T::MonetaryGovernanceOrigin::ensure_origin(origin)?;
883
			let ParachainBondConfig {
884
5
				account: old,
885
5
				percent,
886
5
			} = <ParachainBondInfo<T>>::get();
887
5
			ensure!(old != new, Error::<T>::NoWritingSameValue);
888
5
			<ParachainBondInfo<T>>::put(ParachainBondConfig {
889
5
				account: new.clone(),
890
5
				percent,
891
5
			});
892
5
			Self::deposit_event(Event::ParachainBondAccountSet { old, new });
893
5
			Ok(().into())
894
		}
895

            
896
		/// Set the percent of inflation set aside for parachain bond
897
		#[pallet::call_index(3)]
898
		#[pallet::weight(<T as Config>::WeightInfo::set_parachain_bond_reserve_percent())]
899
		pub fn set_parachain_bond_reserve_percent(
900
			origin: OriginFor<T>,
901
			new: Percent,
902
4
		) -> DispatchResultWithPostInfo {
903
4
			T::MonetaryGovernanceOrigin::ensure_origin(origin)?;
904
			let ParachainBondConfig {
905
3
				account,
906
3
				percent: old,
907
3
			} = <ParachainBondInfo<T>>::get();
908
3
			ensure!(old != new, Error::<T>::NoWritingSameValue);
909
2
			<ParachainBondInfo<T>>::put(ParachainBondConfig {
910
2
				account,
911
2
				percent: new,
912
2
			});
913
2
			Self::deposit_event(Event::ParachainBondReservePercentSet { old, new });
914
2
			Ok(().into())
915
		}
916

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

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

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

            
994
		/// Join the set of collator candidates
995
		#[pallet::call_index(7)]
996
		#[pallet::weight(<T as Config>::WeightInfo::join_candidates(*candidate_count))]
997
		pub fn join_candidates(
998
			origin: OriginFor<T>,
999
			bond: BalanceOf<T>,
			candidate_count: u32,
676
		) -> DispatchResultWithPostInfo {
676
			let acc = ensure_signed(origin)?;
676
			ensure!(
676
				bond >= T::MinCandidateStk::get(),
14
				Error::<T>::CandidateBondBelowMin
			);
662
			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())
		}
		/// DEPRECATED use delegateWithAutoCompound
		/// 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
		#[pallet::call_index(17)]
		#[pallet::weight(
			<T as Config>::WeightInfo::delegate_with_auto_compound_worst()
		)]
		pub fn delegate(
			origin: OriginFor<T>,
			candidate: T::AccountId,
			amount: BalanceOf<T>,
			candidate_delegation_count: u32,
			delegation_count: u32,
59
		) -> DispatchResultWithPostInfo {
59
			let delegator = ensure_signed(origin)?;
59
			<AutoCompoundDelegations<T>>::delegate_with_auto_compound(
59
				candidate,
59
				delegator,
59
				amount,
59
				Percent::zero(),
59
				candidate_delegation_count,
59
				0,
59
				delegation_count,
59
			)
		}
		/// 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,
9025
		) -> DispatchResultWithPostInfo {
9025
			let delegator = ensure_signed(origin)?;
9025
			<AutoCompoundDelegations<T>>::delegate_with_auto_compound(
9025
				candidate,
9025
				delegator,
9025
				amount,
9025
				auto_compound,
9025
				candidate_delegation_count,
9025
				candidate_auto_compounding_delegation_count,
9025
				delegation_count,
9025
			)
		}
		/// REMOVED, was schedule_leave_delegators
		#[pallet::call_index(19)]
		#[pallet::weight(<T as Config>::WeightInfo::set_staking_expectations())]
1
		pub fn removed_call_19(_origin: OriginFor<T>) -> DispatchResultWithPostInfo {
1
			fail!(Error::<T>::RemovedCall)
		}
		/// REMOVED, was execute_leave_delegators
		#[pallet::call_index(20)]
		#[pallet::weight(<T as Config>::WeightInfo::set_staking_expectations())]
1
		pub fn removed_call_20(_origin: OriginFor<T>) -> DispatchResultWithPostInfo {
1
			fail!(Error::<T>::RemovedCall)
		}
		/// REMOVED, was cancel_leave_delegators
		#[pallet::call_index(21)]
		#[pallet::weight(<T as Config>::WeightInfo::set_staking_expectations())]
1
		pub fn removed_call_21(_origin: OriginFor<T>) -> DispatchResultWithPostInfo {
1
			fail!(Error::<T>::RemovedCall)
		}
		/// 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,
5
		) -> DispatchResult {
5
			ensure!(
5
				<EnableMarkingOffline<T>>::get(),
				<Error<T>>::MarkingOfflineNotEnabled
			);
5
			ensure_signed(origin)?;
5
			let mut collators_len = 0usize;
5
			let max_collators = <TotalSelected<T>>::get();
5
			if let Some(len) = <SelectedCandidates<T>>::decode_len() {
5
				collators_len = len;
5
			};
			// 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.
5
			ensure!(
5
				collators_len * 3 > (max_collators * 2) as usize,
1
				<Error<T>>::TooLowCollatorCountToNotifyAsInactive
			);
4
			let round_info = <Round<T>>::get();
4
			let max_offline_rounds = T::MaxOfflineRounds::get();
4

            
4
			ensure!(
4
				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
3
			let first_round_to_check = round_info.current.saturating_sub(max_offline_rounds);
3
			let rounds_to_check = first_round_to_check..round_info.current;
3

            
3
			// If this counter is eq to max_offline_rounds,
3
			// the collator should be notified as inactive
3
			let mut inactive_counter: RoundIndex = 0u32;
			// Iter rounds to check
			//
			// - The collator has AtStake associated and their AwardedPts are zero
			//
			// If the previous condition is met in all rounds of rounds_to_check,
			// the collator is notified as inactive
6
			for r in rounds_to_check {
3
				let stake = <AtStake<T>>::get(r, &collator);
3
				let pts = <AwardedPts<T>>::get(r, &collator);
3

            
3
				if stake.is_some() && pts.is_zero() {
1
					inactive_counter = inactive_counter.saturating_add(1);
2
				}
			}
3
			if inactive_counter == max_offline_rounds {
1
				let _ = T::OnInactiveCollator::on_inactive_collator(
1
					collator.clone(),
1
					round_info.current.saturating_sub(1),
1
				);
1
			} else {
2
				return Err(<Error<T>>::CannotBeNotifiedAsInactive.into());
			}
1
			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)
		}
	}
	/// 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
		}
688
		pub fn is_delegator(acc: &T::AccountId) -> bool {
688
			<DelegatorState<T>>::get(acc).is_some()
688
		}
9680
		pub fn is_candidate(acc: &T::AccountId) -> bool {
9680
			<CandidateInfo<T>>::get(acc).is_some()
9680
		}
2
		pub fn is_selected_candidate(acc: &T::AccountId) -> bool {
2
			<SelectedCandidates<T>>::get().binary_search(acc).is_ok()
2
		}
663
		pub fn join_candidates_inner(
663
			acc: T::AccountId,
663
			bond: BalanceOf<T>,
663
			candidate_count: u32,
663
		) -> DispatchResultWithPostInfo {
663
			ensure!(!Self::is_candidate(&acc), Error::<T>::CandidateExists);
659
			ensure!(!Self::is_delegator(&acc), Error::<T>::DelegatorExists);
655
			let mut candidates = <CandidatePool<T>>::get();
655
			let old_count = candidates.0.len() as u32;
655
			ensure!(
655
				candidate_count >= old_count,
5
				Error::<T>::TooLowCandidateCountWeightHintJoinCandidates
			);
650
			let maybe_inserted_candidate = candidates
650
				.try_insert(Bond {
650
					owner: acc.clone(),
650
					amount: bond,
650
				})
650
				.map_err(|_| Error::<T>::CandidateLimitReached)?;
649
			ensure!(maybe_inserted_candidate, Error::<T>::CandidateExists);
649
			ensure!(
649
				Self::get_collator_stakable_free_balance(&acc) >= bond,
4
				Error::<T>::InsufficientBalance,
			);
645
			T::Currency::set_lock(COLLATOR_LOCK_ID, &acc, bond, WithdrawReasons::all());
645
			let candidate = CandidateMetadata::new(bond);
645
			<CandidateInfo<T>>::insert(&acc, candidate);
645
			let empty_delegations: Delegations<T::AccountId, BalanceOf<T>> = Default::default();
645
			// insert empty top delegations
645
			<TopDelegations<T>>::insert(&acc, empty_delegations.clone());
645
			// insert empty bottom delegations
645
			<BottomDelegations<T>>::insert(&acc, empty_delegations);
645
			<CandidatePool<T>>::put(candidates);
645
			let new_total = <Total<T>>::get().saturating_add(bond);
645
			<Total<T>>::put(new_total);
645
			Self::deposit_event(Event::JoinedCollatorCandidates {
645
				account: acc,
645
				amount_locked: bond,
645
				new_total_amt_locked: new_total,
645
			});
645
			Ok(().into())
663
		}
15
		pub fn go_offline_inner(collator: T::AccountId) -> DispatchResultWithPostInfo {
15
			let mut state = <CandidateInfo<T>>::get(&collator).ok_or(Error::<T>::CandidateDNE)?;
14
			let mut candidates = <CandidatePool<T>>::get();
14
			let actual_weight = <T as Config>::WeightInfo::go_offline(candidates.0.len() as u32);
14

            
14
			ensure!(
14
				state.is_active(),
1
				DispatchErrorWithPostInfo {
1
					post_info: Some(actual_weight).into(),
1
					error: <Error<T>>::AlreadyOffline.into(),
1
				}
			);
13
			state.go_offline();
13

            
13
			if candidates.remove(&Bond::from_owner(collator.clone())) {
13
				<CandidatePool<T>>::put(candidates);
13
			}
13
			<CandidateInfo<T>>::insert(&collator, state);
13
			Self::deposit_event(Event::CandidateWentOffline {
13
				candidate: collator,
13
			});
13
			Ok(Some(actual_weight).into())
15
		}
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 free balance which is not locked in delegation staking
18728
		pub fn get_delegator_stakable_free_balance(acc: &T::AccountId) -> BalanceOf<T> {
18728
			let mut balance = T::Currency::free_balance(acc);
18728
			if let Some(state) = <DelegatorState<T>>::get(acc) {
226
				balance = balance.saturating_sub(state.total());
18502
			}
18728
			balance
18728
		}
		/// Returns an account's free balance which is not locked in collator staking
1309
		pub fn get_collator_stakable_free_balance(acc: &T::AccountId) -> BalanceOf<T> {
1309
			let mut balance = T::Currency::free_balance(acc);
1309
			if let Some(info) = <CandidateInfo<T>>::get(acc) {
19
				balance = balance.saturating_sub(info.bond);
1290
			}
1309
			balance
1309
		}
		/// 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
77
		fn compute_issuance(round_duration: u64, round_length: u32) -> BalanceOf<T> {
77
			let ideal_duration: BalanceOf<T> = round_length
77
				.saturating_mul(T::BlockTime::get() as u32)
77
				.into();
77
			let config = <InflationConfig<T>>::get();
77
			let round_issuance = crate::inflation::round_issuance_range::<T>(config.round);
77

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

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

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

            
77
			// Compute total issuance based on round duration
77
			let total_issuance = Self::compute_issuance(round_duration, round_length);
77

            
77
			// reserve portion of issuance for parachain bond account
77
			let mut left_issuance = total_issuance;
77
			let bond_config = <ParachainBondInfo<T>>::get();
77
			let parachain_bond_reserve = bond_config.percent * total_issuance;
15
			if let Ok(imb) =
77
				T::Currency::deposit_into_existing(&bond_config.account, parachain_bond_reserve)
15
			{
15
				// update round issuance if transfer succeeds
15
				left_issuance = left_issuance.saturating_sub(imb.peek());
15
				Self::deposit_event(Event::ReservedForParachainBond {
15
					account: bond_config.account,
15
					value: imb.peek(),
15
				});
63
			}
77
			let payout = DelayedPayout {
77
				round_issuance: total_issuance,
77
				total_staking_reward: left_issuance,
77
				collator_commission: <CollatorCommission<T>>::get(),
77
			};
77

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

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

            
30414
			// don't underflow uint
30414
			if now < delay {
16491
				return Weight::from_parts(0u64, 0);
13923
			}
13923

            
13923
			let paid_for_round = now.saturating_sub(delay);
13923
			if let Some(payout_info) = <DelayedPayouts<T>>::get(paid_for_round) {
129
				let result = Self::pay_one_collator_reward(paid_for_round, payout_info);
				// clean up storage items that we no longer need
129
				if matches!(result.0, RewardPayment::Finished) {
16
					<DelayedPayouts<T>>::remove(paid_for_round);
16
					<Points<T>>::remove(paid_for_round);
120
				}
129
				result.1 // weight consumed by pay_one_collator_reward
			} else {
13794
				Weight::from_parts(0u64, 0)
			}
30414
		}
		/// 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.
129
		pub(crate) fn pay_one_collator_reward(
129
			paid_for_round: RoundIndex,
129
			payout_info: DelayedPayout<BalanceOf<T>>,
129
		) -> (RewardPayment, Weight) {
129
			// 'early_weight' tracks weight used for reads/writes done early in this fn before its
129
			// early-exit codepaths.
129
			let mut early_weight = Weight::zero();
129

            
129
			// TODO: it would probably be optimal to roll Points into the DelayedPayouts storage
129
			// item so that we do fewer reads each block
129
			let total_points = <Points<T>>::get(paid_for_round);
129
			early_weight = early_weight.saturating_add(T::DbWeight::get().reads_writes(1, 0));
129

            
129
			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);
129
			}
129

            
129
			let collator_fee = payout_info.collator_commission;
129
			let collator_issuance = collator_fee * payout_info.round_issuance;
113
			if let Some((collator, state)) =
129
				<AtStake<T>>::iter_prefix(paid_for_round).drain().next()
			{
				// read and kill AtStake
113
				early_weight = early_weight.saturating_add(T::DbWeight::get().reads_writes(1, 1));
113

            
113
				// Take the awarded points for the collator
113
				let pts = <AwardedPts<T>>::take(paid_for_round, &collator);
113
				// read and kill AwardedPts
113
				early_weight = early_weight.saturating_add(T::DbWeight::get().reads_writes(1, 1));
113
				if pts == 0 {
63
					return (RewardPayment::Skipped, early_weight);
50
				}
50

            
50
				// 'extra_weight' tracks weight returned from fns that we delegate to which can't be
50
				// known ahead of time.
50
				let mut extra_weight = Weight::zero();
50
				let pct_due = Perbill::from_rational(pts, total_points);
50
				let total_paid = pct_due * payout_info.total_staking_reward;
50
				let mut amt_due = total_paid;
50

            
50
				let num_delegators = state.delegations.len();
50
				let mut num_paid_delegations = 0u32;
50
				let mut num_auto_compounding = 0u32;
50
				let num_scheduled_requests =
50
					<DelegationScheduledRequests<T>>::decode_len(&collator).unwrap_or_default();
50
				if state.delegations.is_empty() {
15
					// solo collator with no delegators
15
					extra_weight = extra_weight
15
						.saturating_add(T::PayoutCollatorReward::payout_collator_reward(
15
							paid_for_round,
15
							collator.clone(),
15
							amt_due,
15
						))
15
						.saturating_add(T::OnCollatorPayout::on_collator_payout(
15
							paid_for_round,
15
							collator.clone(),
15
							amt_due,
15
						));
15
				} 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
						}
					}
				}
50
				extra_weight = extra_weight.saturating_add(
50
					<T as Config>::WeightInfo::pay_one_collator_reward_best(
50
						num_paid_delegations,
50
						num_auto_compounding,
50
						num_scheduled_requests as u32,
50
					),
50
				);
50

            
50
				(
50
					RewardPayment::Paid,
50
					<T as Config>::WeightInfo::pay_one_collator_reward(num_delegators as u32)
50
						.saturating_add(extra_weight),
50
				)
			} else {
				// Note that we don't clean up storage here; it is cleaned up in
				// handle_delayed_payouts()
16
				(RewardPayment::Finished, Weight::from_parts(0u64, 0))
			}
129
		}
		/// 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.
766
		pub fn compute_top_candidates() -> Vec<T::AccountId> {
766
			let top_n = <TotalSelected<T>>::get() as usize;
766
			if top_n == 0 {
				return vec![];
766
			}
766

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

            
766
			// If the number of candidates is greater than top_n, select the candidates with higher
766
			// amount. Otherwise, return all the candidates.
766
			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| {
745
						inner.select_nth_unstable_by(top_n - 1, |a, b| {
745
							// Order by amount, then owner. The owner is needed to ensure a stable order
745
							// when two accounts have the same amount.
745
							a.amount
745
								.cmp(&b.amount)
745
								.then_with(|| a.owner.cmp(&b.owner))
745
								.reverse()
745
						});
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
1017
				candidates.into_iter().map(|x| x.owner).collect::<Vec<_>>()
			}
766
		}
		/// Best as in most cumulatively supported in terms of stake
		/// Returns [collator_count, delegation_count, total staked]
765
		pub(crate) fn select_top_candidates(now: RoundIndex) -> (Weight, u32, u32, BalanceOf<T>) {
765
			let (mut collator_count, mut delegation_count, mut total) =
765
				(0u32, 0u32, BalanceOf::<T>::zero());
765
			// choose the top TotalSelected qualified candidates, ordered by stake
765
			let collators = Self::compute_top_candidates();
765
			if collators.is_empty() {
				// SELECTION FAILED TO SELECT >=1 COLLATOR => select collators from previous round
288
				let last_round = now.saturating_sub(1u32);
288
				let mut total_per_candidate: BTreeMap<T::AccountId, BalanceOf<T>> = BTreeMap::new();
				// set this round AtStake to last round AtStake
288
				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
331
				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
					})
				}
288
				let weight = <T as Config>::WeightInfo::select_top_candidates(0, 0);
288
				return (weight, collator_count, delegation_count, total);
477
			}
			// snapshot exposure for round for weighting reward distribution
913
			for account in collators.iter() {
913
				let state = <CandidateInfo<T>>::get(account)
913
					.expect("all members of CandidateQ must be candidates");
913

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

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

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

            
477
			let avg_delegator_count = delegation_count.checked_div(collator_count).unwrap_or(0);
477
			let weight = <T as Config>::WeightInfo::select_top_candidates(
477
				collator_count,
477
				avg_delegator_count,
477
			);
477
			(weight, collator_count, delegation_count, total)
765
		}
		/// 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.
913
		pub(crate) fn get_rewardable_delegators(collator: &T::AccountId) -> CountedDelegations<T> {
913
			let requests = <DelegationScheduledRequests<T>>::get(collator)
913
				.into_iter()
913
				.map(|x| (x.delegator, x.action))
913
				.collect::<BTreeMap<_, _>>();
913
			let mut uncounted_stake = BalanceOf::<T>::zero();
913
			let rewardable_delegations = <TopDelegations<T>>::get(collator)
913
				.expect("all members of CandidateQ must be candidates")
913
				.delegations
913
				.into_iter()
913
				.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
913
				})
913
				.collect();
913
			CountedDelegations {
913
				uncounted_stake,
913
				rewardable_delegations,
913
			}
913
		}
		/// 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.
88
		pub fn mint_collator_reward(
88
			_paid_for_round: RoundIndex,
88
			collator_id: T::AccountId,
88
			amt: BalanceOf<T>,
88
		) -> Weight {
88
			if let Ok(amount_transferred) = T::Currency::deposit_into_existing(&collator_id, amt) {
88
				Self::deposit_event(Event::Rewarded {
88
					account: collator_id.clone(),
88
					rewards: amount_transferred.peek(),
88
				});
88
			}
88
			<T as Config>::WeightInfo::mint_collator_reward()
88
		}
		/// Mint and compound delegation rewards. The function mints the amount towards the
		/// delegator and tries to compound a specified percent of it back towards the delegation.
		/// If a scheduled delegation revoke exists, then the amount is only minted, and nothing is
		/// compounded. Emits the [Compounded] event.
55
		pub fn mint_and_compound(
55
			amt: BalanceOf<T>,
55
			compound_percent: Percent,
55
			candidate: T::AccountId,
55
			delegator: T::AccountId,
55
		) {
55
			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
	impl<T: Config> Pallet<T> {
29483
		fn award_points_to_block_author() {
29483
			let author = T::BlockAuthor::get();
29483
			let now = <Round<T>>::get().current;
29483
			let score_plus_20 = <AwardedPts<T>>::get(now, &author).saturating_add(20);
29483
			<AwardedPts<T>>::insert(now, author, score_plus_20);
29483
			<Points<T>>::mutate(now, |x| *x = x.saturating_add(20));
29483
		}
	}
	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> {
66
		fn get() -> Vec<T::AccountId> {
66
			Self::selected_candidates().into_inner()
66
		}
	}
}