1
// Copyright (C) Moondance Labs Ltd.
2
// This file is part of Tanssi.
3

            
4
// Tanssi 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
// Tanssi 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 Tanssi.  If not, see <http://www.gnu.org/licenses/>
16

            
17
//! A staking pallet based on pools of shares.
18
//!
19
//! This pallet works with pools inspired by AMM liquidity pools to easily distribute
20
//! rewards with support for both non-compounding and compounding rewards.
21
//!
22
//! Each candidate internally have 3 pools:
23
//! - a pool for all delegators willing to auto compound.
24
//! - a pool for all delegators not willing to auto compound.
25
//! - a pool for all delegators that are in the process of removing stake.
26
//!
27
//! When delegating the funds of the delegator are reserved, and shares allow to easily
28
//! distribute auto compounding rewards (by simply increasing the total shared amount)
29
//! and easily slash (each share loose part of its value). Rewards are distributed to an account
30
//! id dedicated to the staking pallet, and delegators can call an extrinsic to transfer their rewards
31
//! to their own account (but as reserved). Keeping funds reserved in user accounts allow them to
32
//! participate in other processes such as gouvernance.
33

            
34
#![cfg_attr(not(feature = "std"), no_std)]
35
extern crate alloc;
36

            
37
mod calls;
38
mod candidate;
39
mod pools;
40
pub mod traits;
41

            
42
#[cfg(test)]
43
mod mock;
44

            
45
#[cfg(test)]
46
mod tests;
47

            
48
#[cfg(feature = "runtime-benchmarks")]
49
mod benchmarking;
50

            
51
#[cfg(any(feature = "migrations", feature = "try-runtime"))]
52
pub mod migrations;
53

            
54
pub mod weights;
55
use frame_support::pallet;
56
pub use weights::WeightInfo;
57

            
58
pub use {
59
    candidate::EligibleCandidate,
60
    pallet::*,
61
    pools::{ActivePoolKind, CandidateSummary, DelegatorCandidateSummary, PoolKind},
62
};
63

            
64
#[cfg(feature = "runtime-benchmarks")]
65
use frame_support::traits::fungible::Mutate;
66
#[pallet]
67
pub mod pallet {
68
    use {
69
        super::*,
70
        crate::{
71
            traits::{IsCandidateEligible, Timer},
72
            weights::WeightInfo,
73
        },
74
        alloc::vec::Vec,
75
        calls::Calls,
76
        core::marker::PhantomData,
77
        frame_support::{
78
            pallet_prelude::*,
79
            storage::types::{StorageDoubleMap, StorageValue, ValueQuery},
80
            traits::{fungible, tokens::Balance},
81
            Blake2_128Concat,
82
        },
83
        frame_system::pallet_prelude::*,
84
        parity_scale_codec::{Decode, DecodeWithMemTracking, Encode, FullCodec},
85
        scale_info::TypeInfo,
86
        serde::{Deserialize, Serialize},
87
        sp_core::Get,
88
        sp_runtime::{BoundedVec, Perbill},
89
        tp_maths::MulDiv,
90
    };
91

            
92
    /// A reason for this pallet placing a hold on funds.
93
    #[pallet::composite_enum]
94
    pub enum HoldReason {
95
        PooledStake,
96
    }
97

            
98
    // Type aliases for better readability.
99
    pub type Candidate<T> = <T as frame_system::Config>::AccountId;
100
    pub type CreditOf<T> =
101
        fungible::Credit<<T as frame_system::Config>::AccountId, <T as Config>::Currency>;
102
    pub type Delegator<T> = <T as frame_system::Config>::AccountId;
103

            
104
    /// Key used by the `Pools` StorageDoubleMap, avoiding lots of maps.
105
    /// StorageDoubleMap first key is the account id of the candidate.
106
    #[derive(
107
        RuntimeDebug,
108
        PartialEq,
109
        Eq,
110
        Encode,
111
        Decode,
112
        Clone,
113
        TypeInfo,
114
        Serialize,
115
        Deserialize,
116
        MaxEncodedLen,
117
    )]
118
    pub enum PoolsKey<A: FullCodec> {
119
        /// Total amount of currency backing this candidate across all pools.
120
        CandidateTotalStake,
121

            
122
        /// Amount of joining shares a delegator have for that candidate.
123
        JoiningShares { delegator: A },
124
        /// Total amount of joining shares existing for that candidate.
125
        JoiningSharesSupply,
126
        /// Amount of currency backing all the joining shares of that candidate.
127
        JoiningSharesTotalStaked,
128
        /// Amount of currency held in the delegator account.
129
        JoiningSharesHeldStake { delegator: A },
130

            
131
        /// Amount of auto compounding shares a delegator have for that candidate.
132
        AutoCompoundingShares { delegator: A },
133
        /// Total amount of auto compounding shares existing for that candidate.
134
        AutoCompoundingSharesSupply,
135
        /// Amount of currency backing all the auto compounding shares of that candidate.
136
        AutoCompoundingSharesTotalStaked,
137
        /// Amount of currency held in the delegator account.
138
        AutoCompoundingSharesHeldStake { delegator: A },
139

            
140
        /// Amount of manual rewards shares a delegator have for that candidate.
141
        ManualRewardsShares { delegator: A },
142
        /// Total amount of manual rewards shares existing for that candidate.
143
        ManualRewardsSharesSupply,
144
        /// Amount of currency backing all the manual rewards shares of that candidate.
145
        ManualRewardsSharesTotalStaked,
146
        /// Amount of currency held in the delegator account.
147
        ManualRewardsSharesHeldStake { delegator: A },
148
        /// Counter of the cumulated rewards per share generated by that candidate since genesis.
149
        /// Is safe to wrap around the maximum value of the balance type.
150
        ManualRewardsCounter,
151
        /// Value of the counter at the last time the delegator claimed its rewards or changed its amount of shares
152
        /// (changing the amount of shares automatically claims pending rewards).
153
        /// The difference between the checkpoint and the counter is the amount of claimable reward per share for
154
        /// that delegator.
155
        ManualRewardsCheckpoint { delegator: A },
156

            
157
        /// Amount of shares of that delegator in the leaving pool of that candidate.
158
        /// When leaving delegating funds are placed in the leaving pool until the leaving period is elapsed.
159
        /// While in the leaving pool the funds are still slashable.
160
        LeavingShares { delegator: A },
161
        /// Total amount of leaving shares existing for that candidate.
162
        LeavingSharesSupply,
163
        /// Amount of currency backing all the leaving shares of that candidate.
164
        LeavingSharesTotalStaked,
165
        /// Amount of currency held in the delegator account.
166
        LeavingSharesHeldStake { delegator: A },
167
    }
168

            
169
    /// Key used by the "PendingOperations" StorageDoubleMap.
170
    /// StorageDoubleMap first key is the account id of the delegator who made the request.
171
    /// Value is the amount of shares in the joining/leaving pool.
172

            
173
    #[derive(
174
        RuntimeDebug,
175
        PartialEq,
176
        Eq,
177
        Encode,
178
        Decode,
179
        Clone,
180
        TypeInfo,
181
        Serialize,
182
        Deserialize,
183
        MaxEncodedLen,
184
        DecodeWithMemTracking,
185
    )]
186
    #[allow(clippy::multiple_bound_locations)]
187
    pub enum PendingOperationKey<A: FullCodec, J: FullCodec, L: FullCodec> {
188
        /// Candidate requested to join the auto compounding pool of a candidate.
189
        JoiningAutoCompounding { candidate: A, at: J },
190
        /// Candidate requested to join the manual rewards pool of a candidate.
191
        JoiningManualRewards { candidate: A, at: J },
192
        /// Candidate requested to to leave a pool of a candidate.
193
        Leaving { candidate: A, at: L },
194
    }
195

            
196
    pub type PendingOperationKeyOf<T> = PendingOperationKey<
197
        <T as frame_system::Config>::AccountId,
198
        <<T as Config>::JoiningRequestTimer as Timer>::Instant,
199
        <<T as Config>::LeavingRequestTimer as Timer>::Instant,
200
    >;
201

            
202
    #[derive(
203
        RuntimeDebug,
204
        PartialEq,
205
        Eq,
206
        Encode,
207
        Decode,
208
        Clone,
209
        TypeInfo,
210
        Serialize,
211
        Deserialize,
212
        DecodeWithMemTracking,
213
    )]
214
    #[allow(clippy::multiple_bound_locations)]
215
    pub struct PendingOperationQuery<A: FullCodec, J: FullCodec, L: FullCodec> {
216
        pub delegator: A,
217
        pub operation: PendingOperationKey<A, J, L>,
218
    }
219

            
220
    pub type PendingOperationQueryOf<T> = PendingOperationQuery<
221
        <T as frame_system::Config>::AccountId,
222
        <<T as Config>::JoiningRequestTimer as Timer>::Instant,
223
        <<T as Config>::LeavingRequestTimer as Timer>::Instant,
224
    >;
225

            
226
    /// Allow calls to be performed using either share amounts or stake.
227
    /// When providing stake, calls will convert them into share amounts that are
228
    /// worth up to the provided stake. The amount of stake thus will be at most the provided
229
    /// amount.
230
    #[derive(
231
        RuntimeDebug,
232
        PartialEq,
233
        Eq,
234
        Encode,
235
        Decode,
236
        Clone,
237
        TypeInfo,
238
        Serialize,
239
        Deserialize,
240
        DecodeWithMemTracking,
241
    )]
242
    pub enum SharesOrStake<T> {
243
        Shares(T),
244
        Stake(T),
245
    }
246

            
247
    /// Wrapper type for an amount of shares.
248
    #[derive(
249
        RuntimeDebug,
250
        Default,
251
        PartialEq,
252
        Eq,
253
        Encode,
254
        Decode,
255
        Copy,
256
        Clone,
257
        TypeInfo,
258
        Serialize,
259
        Deserialize,
260
    )]
261
    pub struct Shares<T>(pub T);
262

            
263
    /// Wrapper type for an amount of staked currency.
264
    #[derive(
265
        RuntimeDebug,
266
        Default,
267
        PartialEq,
268
        Eq,
269
        Encode,
270
        Decode,
271
        Copy,
272
        Clone,
273
        TypeInfo,
274
        Serialize,
275
        Deserialize,
276
    )]
277
    pub struct Stake<T>(pub T);
278

            
279
    const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
280

            
281
    /// Pooled Staking pallet.
282
    #[pallet::pallet]
283
    #[pallet::storage_version(STORAGE_VERSION)]
284
    pub struct Pallet<T>(PhantomData<T>);
285

            
286
    #[pallet::config]
287
    pub trait Config: frame_system::Config {
288
        /// The currency type.
289
        /// Shares will use the same Balance type.
290
        type Currency: fungible::Inspect<Self::AccountId, Balance = Self::Balance>
291
            + fungible::Mutate<Self::AccountId>
292
            + fungible::Balanced<Self::AccountId>
293
            + fungible::MutateHold<Self::AccountId, Reason = Self::RuntimeHoldReason>;
294

            
295
        /// Same as Currency::Balance. Must impl `MulDiv` which perform
296
        /// multiplication followed by division using a bigger type to avoid
297
        /// overflows.
298
        type Balance: Balance + MulDiv;
299

            
300
        /// Account holding Currency of all delegators.
301
        #[pallet::constant]
302
        type StakingAccount: Get<Self::AccountId>;
303

            
304
        /// When creating the first Shares for a candidate the supply can be arbitrary.
305
        /// Picking a value too low will make an higher supply, which means each share will get
306
        /// less rewards, and rewards calculations will have more impactful rounding errors.
307
        /// Picking a value too high is a barrier of entry for staking.
308
        #[pallet::constant]
309
        type InitialManualClaimShareValue: Get<Self::Balance>;
310
        /// When creating the first Shares for a candidate the supply can arbitrary.
311
        /// Picking a value too high is a barrier of entry for staking, which will increase overtime
312
        /// as the value of each share will increase due to auto compounding.
313
        #[pallet::constant]
314
        type InitialAutoCompoundingShareValue: Get<Self::Balance>;
315

            
316
        /// Minimum amount of stake a Candidate must delegate (stake) towards itself. Not reaching
317
        /// this minimum prevents from being elected.
318
        #[pallet::constant]
319
        type MinimumSelfDelegation: Get<Self::Balance>;
320
        /// Part of the rewards that will be sent exclusively to the collator.
321
        #[pallet::constant]
322
        type RewardsCollatorCommission: Get<Perbill>;
323

            
324
        /// The overarching runtime hold reason.
325
        type RuntimeHoldReason: From<HoldReason>;
326

            
327
        /// Condition for when a joining request can be executed.
328
        type JoiningRequestTimer: Timer;
329
        /// Condition for when a leaving request can be executed.
330
        type LeavingRequestTimer: Timer;
331
        /// Condition for when rewards should effectively be distributed.
332
        type RewardsDistributionTimer: Timer;
333

            
334
        /// All eligible candidates are stored in a sorted list that is modified each time
335
        /// delegations changes. It is safer to bound this list, in which case eligible candidate
336
        /// could fall out of this list if they have less stake than the top `EligibleCandidatesBufferSize`
337
        /// eligible candidates. One of this top candidates leaving will then not bring the dropped candidate
338
        /// in the list. An extrinsic is available to manually bring back such dropped candidate.
339
        #[pallet::constant]
340
        type EligibleCandidatesBufferSize: Get<u32>;
341
        /// Additional filter for candidates to be eligible.
342
        type EligibleCandidatesFilter: IsCandidateEligible<Self::AccountId>;
343
        type WeightInfo: WeightInfo;
344
    }
345

            
346
    /// Keeps a list of all eligible candidates, sorted by the amount of stake backing them.
347
    /// This can be quickly updated using a binary search, and allow to easily take the top
348
    /// `MaxCollatorSetSize`.
349
    #[pallet::storage]
350
    pub type SortedEligibleCandidates<T: Config> = StorageValue<
351
        _,
352
        BoundedVec<
353
            candidate::EligibleCandidate<Candidate<T>, T::Balance>,
354
            T::EligibleCandidatesBufferSize,
355
        >,
356
        ValueQuery,
357
    >;
358

            
359
    /// Pools balances.
360
    #[pallet::storage]
361
    pub type Pools<T: Config> = StorageDoubleMap<
362
        _,
363
        Blake2_128Concat,
364
        Candidate<T>,
365
        Blake2_128Concat,
366
        PoolsKey<T::AccountId>,
367
        T::Balance,
368
        ValueQuery,
369
    >;
370

            
371
    /// Pending operations balances.
372
    /// Balances are expressed in joining/leaving shares amounts.
373
    #[pallet::storage]
374
    pub type PendingOperations<T: Config> = StorageDoubleMap<
375
        _,
376
        Blake2_128Concat,
377
        Delegator<T>,
378
        Blake2_128Concat,
379
        PendingOperationKeyOf<T>,
380
        T::Balance,
381
        ValueQuery,
382
    >;
383

            
384
    /// Summary of a delegator's delegation.
385
    /// Used to quickly fetch all delegations of a delegator.
386
    #[pallet::storage]
387
    pub type DelegatorCandidateSummaries<T: Config> = StorageDoubleMap<
388
        _,
389
        Blake2_128Concat,
390
        Delegator<T>,
391
        Blake2_128Concat,
392
        Candidate<T>,
393
        DelegatorCandidateSummary,
394
        ValueQuery,
395
    >;
396

            
397
    /// Summary of a candidate state.
398
    #[pallet::storage]
399
    pub type CandidateSummaries<T: Config> =
400
        StorageMap<_, Blake2_128Concat, Candidate<T>, CandidateSummary, ValueQuery>;
401

            
402
    /// Pauses the ability to modify pools through extrinsics.
403
    ///
404
    /// Currently added only to run the multi-block migration to compute
405
    /// `DelegatorCandidateSummaries` and `CandidateSummaries`. It will NOT
406
    /// prevent to distribute rewards, which is fine as the reward distribution
407
    /// process doesn't alter the pools in a way that will mess with the migration.
408
    #[pallet::storage]
409
    pub type PausePoolsExtrinsics<T: Config> = StorageValue<_, bool, ValueQuery>;
410

            
411
    /// Aggregates rewards to be effectively distributed later.
412
    /// Size is unbounded to ensure new collators can be registered, but is
413
    /// indirectly bounded by how many collators can be rewarded before
414
    /// `RewardsDistributionTimer` is elapsed, which will empty the map.
415
    #[pallet::storage]
416
    #[pallet::unbounded]
417
    pub type PendingRewards<T: Config> = StorageValue<
418
        _,
419
        pools::PendingRewards<
420
            <T::RewardsDistributionTimer as Timer>::Instant,
421
            Candidate<T>,
422
            T::Balance,
423
        >,
424
        OptionQuery,
425
    >;
426

            
427
    #[pallet::event]
428
    #[pallet::generate_deposit(pub(super) fn deposit_event)]
429
    pub enum Event<T: Config> {
430
        /// Stake of the candidate has changed, which may have modified its
431
        /// position in the eligible candidates list.
432
        UpdatedCandidatePosition {
433
            candidate: Candidate<T>,
434
            stake: T::Balance,
435
            self_delegation: T::Balance,
436
            before: Option<u32>,
437
            after: Option<u32>,
438
        },
439

            
440
        /// User requested to delegate towards a candidate.
441
        RequestedDelegate {
442
            candidate: Candidate<T>,
443
            delegator: Delegator<T>,
444
            pool: ActivePoolKind,
445
            pending: T::Balance,
446
        },
447
        /// Delegation request was executed. `staked` has been properly staked
448
        /// in `pool`, while the rounding when converting to shares has been
449
        /// `released`.
450
        ExecutedDelegate {
451
            candidate: Candidate<T>,
452
            delegator: Delegator<T>,
453
            pool: ActivePoolKind,
454
            staked: T::Balance,
455
            released: T::Balance,
456
        },
457
        /// User requested to undelegate from a candidate.
458
        /// Stake was removed from a `pool` and is `pending` for the request
459
        /// to be executed. The rounding when converting to leaving shares has
460
        /// been `released` immediately.
461
        RequestedUndelegate {
462
            candidate: Candidate<T>,
463
            delegator: Delegator<T>,
464
            from: ActivePoolKind,
465
            pending: T::Balance,
466
            released: T::Balance,
467
        },
468
        /// Undelegation request was executed.
469
        ExecutedUndelegate {
470
            candidate: Candidate<T>,
471
            delegator: Delegator<T>,
472
            released: T::Balance,
473
        },
474

            
475
        /// Stake of that Candidate increased.
476
        #[deprecated]
477
        IncreasedStake {
478
            candidate: Candidate<T>,
479
            stake_diff: T::Balance,
480
        },
481
        /// Stake of that Candidate decreased.
482
        #[deprecated]
483
        DecreasedStake {
484
            candidate: Candidate<T>,
485
            stake_diff: T::Balance,
486
        },
487
        /// Delegator staked towards a Candidate for AutoCompounding Shares.
488
        StakedAutoCompounding {
489
            candidate: Candidate<T>,
490
            delegator: Delegator<T>,
491
            shares: T::Balance,
492
            stake: T::Balance,
493
        },
494
        /// Delegator unstaked towards a candidate with AutoCompounding Shares.
495
        UnstakedAutoCompounding {
496
            candidate: Candidate<T>,
497
            delegator: Delegator<T>,
498
            shares: T::Balance,
499
            stake: T::Balance,
500
        },
501
        /// Delegator staked towards a candidate for ManualRewards Shares.
502
        StakedManualRewards {
503
            candidate: Candidate<T>,
504
            delegator: Delegator<T>,
505
            shares: T::Balance,
506
            stake: T::Balance,
507
        },
508
        /// Delegator unstaked towards a candidate with ManualRewards Shares.
509
        UnstakedManualRewards {
510
            candidate: Candidate<T>,
511
            delegator: Delegator<T>,
512
            shares: T::Balance,
513
            stake: T::Balance,
514
        },
515
        /// Collator has been rewarded.
516
        #[deprecated]
517
        RewardedCollator {
518
            collator: Candidate<T>,
519
            auto_compounding_rewards: T::Balance,
520
            manual_claim_rewards: T::Balance,
521
        },
522
        /// Delegators have been rewarded.
523
        #[deprecated]
524
        RewardedDelegators {
525
            collator: Candidate<T>,
526
            auto_compounding_rewards: T::Balance,
527
            manual_claim_rewards: T::Balance,
528
        },
529
        /// Rewards manually claimed.
530
        ClaimedManualRewards {
531
            candidate: Candidate<T>,
532
            delegator: Delegator<T>,
533
            rewards: T::Balance,
534
        },
535
        /// Swapped between AutoCompounding and ManualReward shares
536
        SwappedPool {
537
            candidate: Candidate<T>,
538
            delegator: Delegator<T>,
539
            source_pool: ActivePoolKind,
540
            source_shares: T::Balance,
541
            source_stake: T::Balance,
542
            target_shares: T::Balance,
543
            target_stake: T::Balance,
544
            pending_leaving: T::Balance,
545
            released: T::Balance,
546
        },
547
        /// Rewards has been distributed to a collator and its delegators.
548
        RewardsDistributed {
549
            collator: Candidate<T>,
550
            collator_ac_rewards: T::Balance,
551
            collator_mc_rewards: T::Balance,
552
            delegators_ac_rewards: T::Balance,
553
            delegators_mc_rewards: T::Balance,
554
        },
555
    }
556

            
557
    #[pallet::error]
558
    pub enum Error<T> {
559
        InvalidPalletSetting,
560
        DisabledFeature,
561
        NoOneIsStaking,
562
        StakeMustBeNonZero,
563
        RewardsMustBeNonZero,
564
        MathUnderflow,
565
        MathOverflow,
566
        NotEnoughShares,
567
        TryingToLeaveTooSoon,
568
        InconsistentState,
569
        UnsufficientSharesForTransfer,
570
        CandidateTransferingOwnSharesForbidden,
571
        RequestCannotBeExecuted(u16),
572
        SwapResultsInZeroShares,
573
        PoolsExtrinsicsArePaused,
574
    }
575

            
576
    impl<T: Config> From<tp_maths::OverflowError> for Error<T> {
577
        fn from(_: tp_maths::OverflowError) -> Self {
578
            Error::MathOverflow
579
        }
580
    }
581

            
582
    impl<T: Config> From<tp_maths::UnderflowError> for Error<T> {
583
5
        fn from(_: tp_maths::UnderflowError) -> Self {
584
5
            Error::MathUnderflow
585
5
        }
586
    }
587

            
588
    #[pallet::hooks]
589
    impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
590
16029
        fn on_initialize(_: BlockNumberFor<T>) -> Weight {
591
16029
            match pools::distribute_accumulated_rewards_on_timer::<T>() {
592
16029
                Ok(post) => post.actual_weight.unwrap_or_default(),
593
                Err(err) => {
594
                    // error is logged inside distribute_accumulated_rewards
595
                    err.post_info.actual_weight.unwrap_or_default()
596
                }
597
            }
598
16029
        }
599

            
600
        #[cfg(feature = "try-runtime")]
601
        fn try_state(_n: BlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
602
            use alloc::collections::btree_set::BTreeSet;
603
            use frame_support::storage_alias;
604

            
605
            let mut all_candidates = BTreeSet::new();
606
            for (candidate, _k2) in Pools::<T>::iter_keys() {
607
                all_candidates.insert(candidate);
608
            }
609

            
610
            for candidate in all_candidates {
611
                pools::check_candidate_consistency::<T>(&candidate)?;
612
            }
613

            
614
            // Sorted storage items are sorted
615
            fn assert_is_sorted_and_unique<T: Ord>(x: &[T], name: &str) {
616
                assert!(
617
                    x.windows(2).all(|w| w[0] < w[1]),
618
                    "sorted list not sorted or not unique: {}",
619
                    name,
620
                );
621
            }
622
            assert_is_sorted_and_unique(
623
                &SortedEligibleCandidates::<T>::get(),
624
                "SortedEligibleCandidates",
625
            );
626

            
627
            if Pallet::<T>::on_chain_storage_version() < 1 {
628
                return Ok(());
629
            }
630

            
631
            // Summaries updated dynamically matches summaries generated entirely from shares.
632
            #[storage_alias(pallet_name)]
633
            pub type TryStateDelegatorSummaries<T: Config> = StorageDoubleMap<
634
                Pallet<T>,
635
                Blake2_128Concat,
636
                Delegator<T>,
637
                Blake2_128Concat,
638
                Candidate<T>,
639
                DelegatorCandidateSummary,
640
                ValueQuery,
641
            >;
642

            
643
            #[storage_alias(pallet_name)]
644
            pub type TryStateCandidateSummaries<T: Config> =
645
                StorageMap<Pallet<T>, Blake2_128Concat, Candidate<T>, CandidateSummary, ValueQuery>;
646

            
647
            assert!(
648
                migrations::stepped_generate_summaries::<
649
                    T,
650
                    TryStateDelegatorSummaries<T>,
651
                    TryStateCandidateSummaries<T>,
652
                >(
653
                    None,
654
                    &mut frame_support::weights::WeightMeter::new(), // call with no limit on weight
655
                )
656
                .expect("to generate summaries without errors")
657
                .is_none(),
658
                "failed to generate all summaries"
659
            );
660

            
661
            let mut candidate_summaries_count = 0;
662
            for (candidate, summary) in TryStateCandidateSummaries::<T>::iter() {
663
                candidate_summaries_count += 1;
664
                assert_eq!(
665
                    CandidateSummaries::<T>::get(&candidate),
666
                    summary,
667
                    "candidate summary for {candidate:?} didn't match"
668
                );
669
            }
670
            assert_eq!(
671
                candidate_summaries_count,
672
                CandidateSummaries::<T>::iter().count(),
673
                "count of candidate summaries didn't match"
674
            );
675

            
676
            let mut delegator_summaries_count = 0;
677
            for (delegator, candidate, summary) in TryStateDelegatorSummaries::<T>::iter() {
678
                delegator_summaries_count += 1;
679
                assert_eq!(
680
                    DelegatorCandidateSummaries::<T>::get(&delegator, &candidate),
681
                    summary,
682
                    "delegator summary for {delegator:?} to {candidate:?} didn't match"
683
                );
684
            }
685
            assert_eq!(
686
                delegator_summaries_count,
687
                DelegatorCandidateSummaries::<T>::iter().count(),
688
                "count of delegator summaries didn't match"
689
            );
690

            
691
            Ok(())
692
        }
693
    }
694

            
695
    #[pallet::call]
696
    impl<T: Config> Pallet<T> {
697
        #[pallet::call_index(0)]
698
        #[pallet::weight(T::WeightInfo::rebalance_hold())]
699
        #[allow(clippy::useless_conversion)]
700
        pub fn rebalance_hold(
701
            origin: OriginFor<T>,
702
            candidate: Candidate<T>,
703
            delegator: Delegator<T>,
704
            pool: PoolKind,
705
7
        ) -> DispatchResultWithPostInfo {
706
            // We don't care about the sender.
707
7
            let _ = ensure_signed(origin)?;
708

            
709
7
            Calls::<T>::rebalance_hold(candidate, delegator, pool)
710
        }
711

            
712
        #[pallet::call_index(1)]
713
        #[pallet::weight(T::WeightInfo::request_delegate())]
714
        #[allow(clippy::useless_conversion)]
715
        pub fn request_delegate(
716
            origin: OriginFor<T>,
717
            candidate: Candidate<T>,
718
            pool: ActivePoolKind,
719
            stake: T::Balance,
720
173
        ) -> DispatchResultWithPostInfo {
721
173
            let delegator = ensure_signed(origin)?;
722

            
723
170
            Calls::<T>::request_delegate(candidate, delegator, pool, stake)
724
        }
725

            
726
        /// Execute pending operations can incur in claim manual rewards per operation, we simply add the worst case
727
        #[pallet::call_index(2)]
728
        #[pallet::weight(T::WeightInfo::execute_pending_operations(operations.len() as u32).saturating_add(T::WeightInfo::claim_manual_rewards(operations.len() as u32)))]
729
        #[allow(clippy::useless_conversion)]
730
        pub fn execute_pending_operations(
731
            origin: OriginFor<T>,
732
            operations: Vec<PendingOperationQueryOf<T>>,
733
135
        ) -> DispatchResultWithPostInfo {
734
            // We don't care about the sender.
735
135
            let _ = ensure_signed(origin)?;
736

            
737
129
            Calls::<T>::execute_pending_operations(operations)
738
        }
739

            
740
        /// Request undelegate can incur in either claim manual rewards or hold rebalances, we simply add the worst case
741
        #[pallet::call_index(3)]
742
        #[pallet::weight(T::WeightInfo::request_undelegate().saturating_add(T::WeightInfo::claim_manual_rewards(1).max(T::WeightInfo::rebalance_hold())))]
743
        #[allow(clippy::useless_conversion)]
744
        pub fn request_undelegate(
745
            origin: OriginFor<T>,
746
            candidate: Candidate<T>,
747
            pool: ActivePoolKind,
748
            amount: SharesOrStake<T::Balance>,
749
43
        ) -> DispatchResultWithPostInfo {
750
43
            let delegator = ensure_signed(origin)?;
751

            
752
40
            Calls::<T>::request_undelegate(candidate, delegator, pool, amount)
753
        }
754

            
755
        #[pallet::call_index(4)]
756
        #[pallet::weight(T::WeightInfo::claim_manual_rewards(pairs.len() as u32))]
757
        #[allow(clippy::useless_conversion)]
758
        pub fn claim_manual_rewards(
759
            origin: OriginFor<T>,
760
            pairs: Vec<(Candidate<T>, Delegator<T>)>,
761
1
        ) -> DispatchResultWithPostInfo {
762
            // We don't care about the sender.
763
1
            let _ = ensure_signed(origin)?;
764

            
765
1
            Calls::<T>::claim_manual_rewards(&pairs)
766
        }
767

            
768
        #[pallet::call_index(5)]
769
        #[pallet::weight(T::WeightInfo::update_candidate_position(candidates.len() as u32))]
770
        #[allow(clippy::useless_conversion)]
771
        pub fn update_candidate_position(
772
            origin: OriginFor<T>,
773
            candidates: Vec<Candidate<T>>,
774
5
        ) -> DispatchResultWithPostInfo {
775
            // We don't care about the sender.
776
5
            let _ = ensure_signed(origin)?;
777

            
778
5
            Calls::<T>::update_candidate_position(&candidates)
779
        }
780

            
781
        #[pallet::call_index(6)]
782
        #[pallet::weight(T::WeightInfo::swap_pool())]
783
        #[allow(clippy::useless_conversion)]
784
        pub fn swap_pool(
785
            origin: OriginFor<T>,
786
            candidate: Candidate<T>,
787
            source_pool: ActivePoolKind,
788
            amount: SharesOrStake<T::Balance>,
789
17
        ) -> DispatchResultWithPostInfo {
790
17
            let delegator = ensure_signed(origin)?;
791

            
792
17
            Calls::<T>::swap_pool(candidate, delegator, source_pool, amount)
793
        }
794
    }
795

            
796
    impl<T: Config> Pallet<T> {
797
12
        pub fn computed_stake(
798
12
            candidate: Candidate<T>,
799
12
            delegator: Delegator<T>,
800
12
            pool: PoolKind,
801
12
        ) -> Option<T::Balance> {
802
            use pools::Pool;
803
12
            match pool {
804
                PoolKind::Joining => pools::Joining::<T>::computed_stake(&candidate, &delegator),
805
                PoolKind::AutoCompounding => {
806
6
                    pools::AutoCompounding::<T>::computed_stake(&candidate, &delegator)
807
                }
808
                PoolKind::ManualRewards => {
809
6
                    pools::ManualRewards::<T>::computed_stake(&candidate, &delegator)
810
                }
811
                PoolKind::Leaving => pools::Leaving::<T>::computed_stake(&candidate, &delegator),
812
            }
813
12
            .ok()
814
12
            .map(|x| x.0)
815
12
        }
816

            
817
        #[cfg(feature = "runtime-benchmarks")]
818
        pub fn execute_all_pending_operations() -> DispatchResultWithPostInfo {
819
            for (account, op_key) in PendingOperations::<T>::iter_keys() {
820
                Calls::<T>::execute_pending_operations(alloc::vec![PendingOperationQuery {
821
                    delegator: account,
822
                    operation: op_key
823
                }])?;
824
            }
825

            
826
            Ok(().into())
827
        }
828
    }
829

            
830
    impl<T: Config> tp_traits::DistributeRewards<Candidate<T>, CreditOf<T>> for Pallet<T> {
831
92
        fn distribute_rewards(
832
92
            candidate: Candidate<T>,
833
92
            rewards: CreditOf<T>,
834
92
        ) -> DispatchResultWithPostInfo {
835
            // We don't distribute them immediatly and instead accumulate them
836
            // to distribute them later.
837
92
            pools::accumulate_rewards::<T>(candidate, rewards)
838
92
        }
839

            
840
        #[cfg(feature = "runtime-benchmarks")]
841
        fn prepare_worst_case_for_bench(caller: &Candidate<T>) {
842
            // Worst case: account exists in both pools
843
            // Register and self-delegate
844
            use frame_support::traits::fungible::Balanced;
845
            use frame_system::RawOrigin;
846

            
847
            /// Minimum collator candidate stake
848
            fn min_candidate_stk<T: Config>() -> T::Balance {
849
                <<T as Config>::MinimumSelfDelegation as Get<T::Balance>>::get()
850
            }
851
            let source_stake = min_candidate_stk::<T>() * 10u32.into();
852

            
853
            T::Currency::resolve(caller, T::Currency::issue(source_stake * 3u32.into()))
854
                .expect("to mint tokens");
855

            
856
            Pallet::<T>::request_delegate(
857
                RawOrigin::Signed(caller.clone()).into(),
858
                caller.clone(),
859
                ActivePoolKind::AutoCompounding,
860
                source_stake,
861
            )
862
            .expect("failed to request_delegate AutoCompounding pool");
863
            Pallet::<T>::request_delegate(
864
                RawOrigin::Signed(caller.clone()).into(),
865
                caller.clone(),
866
                ActivePoolKind::ManualRewards,
867
                source_stake,
868
            )
869
            .expect("failed to request_delegate ManualRewards pool");
870
        }
871

            
872
        #[cfg(feature = "runtime-benchmarks")]
873
        fn bench_advance_block() {
874
            // This one is weird. It advances 2 sessions, but it only calls `rotate_session`,
875
            // it doesn't call on_initialize/on_finalize for any block.
876
            // This causes the following log line:
877
            // Current epoch index 0 is lower than session index 1.
878
            T::JoiningRequestTimer::skip_to_elapsed();
879
        }
880

            
881
        #[cfg(feature = "runtime-benchmarks")]
882
        fn bench_execute_pending() {
883
            Pallet::<T>::execute_all_pending_operations()
884
                .expect("failed to execute pending operations");
885
        }
886
    }
887

            
888
    impl<T: Config> tp_traits::StakingCandidateHelper<Candidate<T>> for Pallet<T> {
889
6
        fn is_candidate_selected(candidate: &Candidate<T>) -> bool {
890
6
            <SortedEligibleCandidates<T>>::get()
891
6
                .into_iter()
892
6
                .any(|c| &c.candidate == candidate)
893
6
        }
894
9
        fn on_online_status_change(
895
9
            candidate: &Candidate<T>,
896
9
            _is_online: bool,
897
9
        ) -> DispatchResultWithPostInfo {
898
9
            Calls::<T>::update_candidate_position(&[candidate.clone()])
899
9
        }
900

            
901
        #[cfg(feature = "runtime-benchmarks")]
902
        fn make_collator_eligible_candidate(collator: &Candidate<T>) {
903
            use alloc::vec;
904
            let minimum_stake = T::MinimumSelfDelegation::get();
905
            T::Currency::set_balance(collator, minimum_stake + minimum_stake);
906
            T::EligibleCandidatesFilter::make_candidate_eligible(collator, true);
907
            Calls::<T>::request_delegate(
908
                collator.clone(),
909
                collator.clone(),
910
                ActivePoolKind::AutoCompounding,
911
                minimum_stake,
912
            )
913
            .expect("request_delegate should not fail in benchmarks");
914
            let timer = T::JoiningRequestTimer::now();
915
            T::JoiningRequestTimer::skip_to_elapsed();
916
            Calls::<T>::execute_pending_operations(vec![PendingOperationQuery {
917
                delegator: collator.clone(),
918
                operation: PendingOperationKey::JoiningAutoCompounding {
919
                    candidate: collator.clone(),
920
                    at: timer.clone(),
921
                },
922
            }])
923
            .expect("execute_pending_operations should not fail in benchmarks");
924
        }
925
    }
926
}