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

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

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

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

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

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

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

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

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

            
89
    /// A reason for this pallet placing a hold on funds.
90
    #[pallet::composite_enum]
91
    pub enum HoldReason {
92
751
        PooledStake,
93
    }
94

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

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

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

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

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

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

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

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

            
193
    pub type PendingOperationKeyOf<T> = PendingOperationKey<
194
        <T as frame_system::Config>::AccountId,
195
        <<T as Config>::JoiningRequestTimer as Timer>::Instant,
196
        <<T as Config>::LeavingRequestTimer as Timer>::Instant,
197
    >;
198

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

            
217
    pub type PendingOperationQueryOf<T> = PendingOperationQuery<
218
        <T as frame_system::Config>::AccountId,
219
        <<T as Config>::JoiningRequestTimer as Timer>::Instant,
220
        <<T as Config>::LeavingRequestTimer as Timer>::Instant,
221
    >;
222

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

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

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

            
276
    const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
277

            
278
    /// Pooled Staking pallet.
279
2806
    #[pallet::pallet]
280
    #[pallet::storage_version(STORAGE_VERSION)]
281
    pub struct Pallet<T>(PhantomData<T>);
282

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

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

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

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

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

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

            
326
        /// Condition for when a joining request can be executed.
327
        type JoiningRequestTimer: Timer;
328
        /// Condition for when a leaving request can be executed.
329
        type LeavingRequestTimer: Timer;
330
        /// All eligible candidates are stored in a sorted list that is modified each time
331
        /// delegations changes. It is safer to bound this list, in which case eligible candidate
332
        /// could fall out of this list if they have less stake than the top `EligibleCandidatesBufferSize`
333
        /// eligible candidates. One of this top candidates leaving will then not bring the dropped candidate
334
        /// in the list. An extrinsic is available to manually bring back such dropped candidate.
335
        #[pallet::constant]
336
        type EligibleCandidatesBufferSize: Get<u32>;
337
        /// Additional filter for candidates to be eligible.
338
        type EligibleCandidatesFilter: IsCandidateEligible<Self::AccountId>;
339

            
340
        type WeightInfo: WeightInfo;
341
    }
342

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

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

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

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

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

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

            
408
618
    #[pallet::event]
409
3425
    #[pallet::generate_deposit(pub(super) fn deposit_event)]
410
    pub enum Event<T: Config> {
411
        /// Stake of the candidate has changed, which may have modified its
412
        /// position in the eligible candidates list.
413
        UpdatedCandidatePosition {
414
            candidate: Candidate<T>,
415
            stake: T::Balance,
416
            self_delegation: T::Balance,
417
            before: Option<u32>,
418
            after: Option<u32>,
419
        },
420

            
421
        /// User requested to delegate towards a candidate.
422
        RequestedDelegate {
423
            candidate: Candidate<T>,
424
            delegator: Delegator<T>,
425
            pool: ActivePoolKind,
426
            pending: T::Balance,
427
        },
428
        /// Delegation request was executed. `staked` has been properly staked
429
        /// in `pool`, while the rounding when converting to shares has been
430
        /// `released`.
431
        ExecutedDelegate {
432
            candidate: Candidate<T>,
433
            delegator: Delegator<T>,
434
            pool: ActivePoolKind,
435
            staked: T::Balance,
436
            released: T::Balance,
437
        },
438
        /// User requested to undelegate from a candidate.
439
        /// Stake was removed from a `pool` and is `pending` for the request
440
        /// to be executed. The rounding when converting to leaving shares has
441
        /// been `released` immediately.
442
        RequestedUndelegate {
443
            candidate: Candidate<T>,
444
            delegator: Delegator<T>,
445
            from: ActivePoolKind,
446
            pending: T::Balance,
447
            released: T::Balance,
448
        },
449
        /// Undelegation request was executed.
450
        ExecutedUndelegate {
451
            candidate: Candidate<T>,
452
            delegator: Delegator<T>,
453
            released: T::Balance,
454
        },
455

            
456
        /// Stake of that Candidate increased.
457
        IncreasedStake {
458
            candidate: Candidate<T>,
459
            stake_diff: T::Balance,
460
        },
461
        /// Stake of that Candidate decreased.
462
        DecreasedStake {
463
            candidate: Candidate<T>,
464
            stake_diff: T::Balance,
465
        },
466
        /// Delegator staked towards a Candidate for AutoCompounding Shares.
467
        StakedAutoCompounding {
468
            candidate: Candidate<T>,
469
            delegator: Delegator<T>,
470
            shares: T::Balance,
471
            stake: T::Balance,
472
        },
473
        /// Delegator unstaked towards a candidate with AutoCompounding Shares.
474
        UnstakedAutoCompounding {
475
            candidate: Candidate<T>,
476
            delegator: Delegator<T>,
477
            shares: T::Balance,
478
            stake: T::Balance,
479
        },
480
        /// Delegator staked towards a candidate for ManualRewards Shares.
481
        StakedManualRewards {
482
            candidate: Candidate<T>,
483
            delegator: Delegator<T>,
484
            shares: T::Balance,
485
            stake: T::Balance,
486
        },
487
        /// Delegator unstaked towards a candidate with ManualRewards Shares.
488
        UnstakedManualRewards {
489
            candidate: Candidate<T>,
490
            delegator: Delegator<T>,
491
            shares: T::Balance,
492
            stake: T::Balance,
493
        },
494
        /// Collator has been rewarded.
495
        RewardedCollator {
496
            collator: Candidate<T>,
497
            auto_compounding_rewards: T::Balance,
498
            manual_claim_rewards: T::Balance,
499
        },
500
        /// Delegators have been rewarded.
501
        RewardedDelegators {
502
            collator: Candidate<T>,
503
            auto_compounding_rewards: T::Balance,
504
            manual_claim_rewards: T::Balance,
505
        },
506
        /// Rewards manually claimed.
507
        ClaimedManualRewards {
508
            candidate: Candidate<T>,
509
            delegator: Delegator<T>,
510
            rewards: T::Balance,
511
        },
512
        /// Swapped between AutoCompounding and ManualReward shares
513
        SwappedPool {
514
            candidate: Candidate<T>,
515
            delegator: Delegator<T>,
516
            source_pool: ActivePoolKind,
517
            source_shares: T::Balance,
518
            source_stake: T::Balance,
519
            target_shares: T::Balance,
520
            target_stake: T::Balance,
521
            pending_leaving: T::Balance,
522
            released: T::Balance,
523
        },
524
    }
525

            
526
618
    #[pallet::error]
527
    pub enum Error<T> {
528
        InvalidPalletSetting,
529
        DisabledFeature,
530
        NoOneIsStaking,
531
        StakeMustBeNonZero,
532
        RewardsMustBeNonZero,
533
        MathUnderflow,
534
        MathOverflow,
535
        NotEnoughShares,
536
        TryingToLeaveTooSoon,
537
        InconsistentState,
538
        UnsufficientSharesForTransfer,
539
        CandidateTransferingOwnSharesForbidden,
540
        RequestCannotBeExecuted(u16),
541
        SwapResultsInZeroShares,
542
        PoolsExtrinsicsArePaused,
543
    }
544

            
545
    impl<T: Config> From<tp_maths::OverflowError> for Error<T> {
546
        fn from(_: tp_maths::OverflowError) -> Self {
547
            Error::MathOverflow
548
        }
549
    }
550

            
551
    impl<T: Config> From<tp_maths::UnderflowError> for Error<T> {
552
5
        fn from(_: tp_maths::UnderflowError) -> Self {
553
5
            Error::MathUnderflow
554
5
        }
555
    }
556

            
557
65950
    #[pallet::hooks]
558
    impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
559
        #[cfg(feature = "try-runtime")]
560
        fn try_state(_n: BlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
561
            use frame_support::storage_alias;
562
            use sp_std::collections::btree_set::BTreeSet;
563

            
564
            let mut all_candidates = BTreeSet::new();
565
            for (candidate, _k2) in Pools::<T>::iter_keys() {
566
                all_candidates.insert(candidate);
567
            }
568

            
569
            for candidate in all_candidates {
570
                pools::check_candidate_consistency::<T>(&candidate)?;
571
            }
572

            
573
            // Sorted storage items are sorted
574
            fn assert_is_sorted_and_unique<T: Ord>(x: &[T], name: &str) {
575
                assert!(
576
                    x.windows(2).all(|w| w[0] < w[1]),
577
                    "sorted list not sorted or not unique: {}",
578
                    name,
579
                );
580
            }
581
            assert_is_sorted_and_unique(
582
                &SortedEligibleCandidates::<T>::get(),
583
                "SortedEligibleCandidates",
584
            );
585

            
586
            if Pallet::<T>::on_chain_storage_version() < 1 {
587
                return Ok(());
588
            }
589

            
590
            // Summaries updated dynamically matches summaries generated entirely from shares.
591
            #[storage_alias(pallet_name)]
592
            pub type TryStateDelegatorSummaries<T: Config> = StorageDoubleMap<
593
                Pallet<T>,
594
                Blake2_128Concat,
595
                Delegator<T>,
596
                Blake2_128Concat,
597
                Candidate<T>,
598
                DelegatorCandidateSummary,
599
                ValueQuery,
600
            >;
601

            
602
            #[storage_alias(pallet_name)]
603
            pub type TryStateCandidateSummaries<T: Config> =
604
                StorageMap<Pallet<T>, Blake2_128Concat, Candidate<T>, CandidateSummary, ValueQuery>;
605

            
606
            assert!(
607
                migrations::stepped_generate_summaries::<
608
                    T,
609
                    TryStateDelegatorSummaries<T>,
610
                    TryStateCandidateSummaries<T>,
611
                >(
612
                    None,
613
                    &mut frame_support::weights::WeightMeter::new(), // call with no limit on weight
614
                )
615
                .expect("to generate summaries without errors")
616
                .is_none(),
617
                "failed to generate all summaries"
618
            );
619

            
620
            let mut candidate_summaries_count = 0;
621
            for (candidate, summary) in TryStateCandidateSummaries::<T>::iter() {
622
                candidate_summaries_count += 1;
623
                assert_eq!(
624
                    CandidateSummaries::<T>::get(&candidate),
625
                    summary,
626
                    "candidate summary for {candidate:?} didn't match"
627
                );
628
            }
629
            assert_eq!(
630
                candidate_summaries_count,
631
                CandidateSummaries::<T>::iter().count(),
632
                "count of candidate summaries didn't match"
633
            );
634

            
635
            let mut delegator_summaries_count = 0;
636
            for (delegator, candidate, summary) in TryStateDelegatorSummaries::<T>::iter() {
637
                delegator_summaries_count += 1;
638
                assert_eq!(
639
                    DelegatorCandidateSummaries::<T>::get(&delegator, &candidate),
640
                    summary,
641
                    "delegator summary for {delegator:?} to {candidate:?} didn't match"
642
                );
643
            }
644
            assert_eq!(
645
                delegator_summaries_count,
646
                DelegatorCandidateSummaries::<T>::iter().count(),
647
                "count of delegator summaries didn't match"
648
            );
649

            
650
            Ok(())
651
        }
652
    }
653

            
654
618
    #[pallet::call]
655
    impl<T: Config> Pallet<T> {
656
        #[pallet::call_index(0)]
657
        #[pallet::weight(T::WeightInfo::rebalance_hold())]
658
        #[allow(clippy::useless_conversion)]
659
        pub fn rebalance_hold(
660
            origin: OriginFor<T>,
661
            candidate: Candidate<T>,
662
            delegator: Delegator<T>,
663
            pool: PoolKind,
664
7
        ) -> DispatchResultWithPostInfo {
665
7
            // We don't care about the sender.
666
7
            let _ = ensure_signed(origin)?;
667

            
668
7
            Calls::<T>::rebalance_hold(candidate, delegator, pool)
669
        }
670

            
671
        #[pallet::call_index(1)]
672
        #[pallet::weight(T::WeightInfo::request_delegate())]
673
        #[allow(clippy::useless_conversion)]
674
        pub fn request_delegate(
675
            origin: OriginFor<T>,
676
            candidate: Candidate<T>,
677
            pool: ActivePoolKind,
678
            stake: T::Balance,
679
261
        ) -> DispatchResultWithPostInfo {
680
261
            let delegator = ensure_signed(origin)?;
681

            
682
258
            Calls::<T>::request_delegate(candidate, delegator, pool, stake)
683
        }
684

            
685
        /// Execute pending operations can incur in claim manual rewards per operation, we simply add the worst case
686
        #[pallet::call_index(2)]
687
        #[pallet::weight(T::WeightInfo::execute_pending_operations(operations.len() as u32).saturating_add(T::WeightInfo::claim_manual_rewards(operations.len() as u32)))]
688
        #[allow(clippy::useless_conversion)]
689
        pub fn execute_pending_operations(
690
            origin: OriginFor<T>,
691
            operations: Vec<PendingOperationQueryOf<T>>,
692
175
        ) -> DispatchResultWithPostInfo {
693
175
            // We don't care about the sender.
694
175
            let _ = ensure_signed(origin)?;
695

            
696
169
            Calls::<T>::execute_pending_operations(operations)
697
        }
698

            
699
        /// Request undelegate can incur in either claim manual rewards or hold rebalances, we simply add the worst case
700
        #[pallet::call_index(3)]
701
        #[pallet::weight(T::WeightInfo::request_undelegate().saturating_add(T::WeightInfo::claim_manual_rewards(1).max(T::WeightInfo::rebalance_hold())))]
702
        #[allow(clippy::useless_conversion)]
703
        pub fn request_undelegate(
704
            origin: OriginFor<T>,
705
            candidate: Candidate<T>,
706
            pool: ActivePoolKind,
707
            amount: SharesOrStake<T::Balance>,
708
49
        ) -> DispatchResultWithPostInfo {
709
49
            let delegator = ensure_signed(origin)?;
710

            
711
46
            Calls::<T>::request_undelegate(candidate, delegator, pool, amount)
712
        }
713

            
714
        #[pallet::call_index(4)]
715
        #[pallet::weight(T::WeightInfo::claim_manual_rewards(pairs.len() as u32))]
716
        #[allow(clippy::useless_conversion)]
717
        pub fn claim_manual_rewards(
718
            origin: OriginFor<T>,
719
            pairs: Vec<(Candidate<T>, Delegator<T>)>,
720
1
        ) -> DispatchResultWithPostInfo {
721
1
            // We don't care about the sender.
722
1
            let _ = ensure_signed(origin)?;
723

            
724
1
            Calls::<T>::claim_manual_rewards(&pairs)
725
        }
726

            
727
        #[pallet::call_index(5)]
728
        #[pallet::weight(T::WeightInfo::update_candidate_position(candidates.len() as u32))]
729
        #[allow(clippy::useless_conversion)]
730
        pub fn update_candidate_position(
731
            origin: OriginFor<T>,
732
            candidates: Vec<Candidate<T>>,
733
5
        ) -> DispatchResultWithPostInfo {
734
5
            // We don't care about the sender.
735
5
            let _ = ensure_signed(origin)?;
736

            
737
5
            Calls::<T>::update_candidate_position(&candidates)
738
        }
739

            
740
        #[pallet::call_index(6)]
741
        #[pallet::weight(T::WeightInfo::swap_pool())]
742
        #[allow(clippy::useless_conversion)]
743
        pub fn swap_pool(
744
            origin: OriginFor<T>,
745
            candidate: Candidate<T>,
746
            source_pool: ActivePoolKind,
747
            amount: SharesOrStake<T::Balance>,
748
23
        ) -> DispatchResultWithPostInfo {
749
23
            let delegator = ensure_signed(origin)?;
750

            
751
23
            Calls::<T>::swap_pool(candidate, delegator, source_pool, amount)
752
        }
753
    }
754

            
755
    impl<T: Config> Pallet<T> {
756
12
        pub fn computed_stake(
757
12
            candidate: Candidate<T>,
758
12
            delegator: Delegator<T>,
759
12
            pool: PoolKind,
760
12
        ) -> Option<T::Balance> {
761
            use pools::Pool;
762
12
            match pool {
763
                PoolKind::Joining => pools::Joining::<T>::computed_stake(&candidate, &delegator),
764
                PoolKind::AutoCompounding => {
765
6
                    pools::AutoCompounding::<T>::computed_stake(&candidate, &delegator)
766
                }
767
                PoolKind::ManualRewards => {
768
6
                    pools::ManualRewards::<T>::computed_stake(&candidate, &delegator)
769
                }
770
                PoolKind::Leaving => pools::Leaving::<T>::computed_stake(&candidate, &delegator),
771
            }
772
12
            .ok()
773
12
            .map(|x| x.0)
774
12
        }
775
    }
776

            
777
    impl<T: Config> tp_traits::DistributeRewards<Candidate<T>, CreditOf<T>> for Pallet<T> {
778
1045
        fn distribute_rewards(
779
1045
            candidate: Candidate<T>,
780
1045
            rewards: CreditOf<T>,
781
1045
        ) -> DispatchResultWithPostInfo {
782
1045
            pools::distribute_rewards::<T>(&candidate, rewards)
783
1045
        }
784
    }
785
}