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
17827
#[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, 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
1
        /// 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
1
        /// 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
2
        /// 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
    )]
182
    pub enum PendingOperationKey<A: FullCodec, J: FullCodec, L: FullCodec> {
183
144
        /// Candidate requested to join the auto compounding pool of a candidate.
184
        JoiningAutoCompounding { candidate: A, at: J },
185
96
        /// Candidate requested to join the manual rewards pool of a candidate.
186
        JoiningManualRewards { candidate: A, at: J },
187
24
        /// Candidate requested to to leave a pool of a candidate.
188
        Leaving { candidate: A, at: L },
189
    }
190

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

            
197
    #[derive(
198
1236
        RuntimeDebug, PartialEq, Eq, Encode, Decode, Clone, TypeInfo, Serialize, Deserialize,
199
    )]
200
    pub struct PendingOperationQuery<A: FullCodec, J: FullCodec, L: FullCodec> {
201
        pub delegator: A,
202
        pub operation: PendingOperationKey<A, J, L>,
203
    }
204

            
205
    pub type PendingOperationQueryOf<T> = PendingOperationQuery<
206
        <T as frame_system::Config>::AccountId,
207
        <<T as Config>::JoiningRequestTimer as Timer>::Instant,
208
        <<T as Config>::LeavingRequestTimer as Timer>::Instant,
209
    >;
210

            
211
    /// Allow calls to be performed using either share amounts or stake.
212
    /// When providing stake, calls will convert them into share amounts that are
213
    /// worth up to the provided stake. The amount of stake thus will be at most the provided
214
    /// amount.
215
    #[derive(
216
1854
        RuntimeDebug, PartialEq, Eq, Encode, Decode, Clone, TypeInfo, Serialize, Deserialize,
217
    )]
218
    pub enum SharesOrStake<T> {
219
        Shares(T),
220
48
        Stake(T),
221
    }
222

            
223
    /// Wrapper type for an amount of shares.
224
    #[derive(
225
        RuntimeDebug,
226
        Default,
227
        PartialEq,
228
        Eq,
229
        Encode,
230
        Decode,
231
        Copy,
232
        Clone,
233
        TypeInfo,
234
        Serialize,
235
        Deserialize,
236
    )]
237
    pub struct Shares<T>(pub T);
238

            
239
    /// Wrapper type for an amount of staked currency.
240
    #[derive(
241
        RuntimeDebug,
242
        Default,
243
        PartialEq,
244
        Eq,
245
        Encode,
246
        Decode,
247
        Copy,
248
        Clone,
249
        TypeInfo,
250
        Serialize,
251
        Deserialize,
252
    )]
253
    pub struct Stake<T>(pub T);
254

            
255
    const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
256

            
257
    /// Pooled Staking pallet.
258
2806
    #[pallet::pallet]
259
    #[pallet::storage_version(STORAGE_VERSION)]
260
    pub struct Pallet<T>(PhantomData<T>);
261

            
262
    #[pallet::config]
263
    pub trait Config: frame_system::Config {
264
        /// Overarching event type
265
        type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
266
        /// The currency type.
267
        /// Shares will use the same Balance type.
268
        type Currency: fungible::Inspect<Self::AccountId, Balance = Self::Balance>
269
            + fungible::Mutate<Self::AccountId>
270
            + fungible::Balanced<Self::AccountId>
271
            + fungible::MutateHold<Self::AccountId, Reason = Self::RuntimeHoldReason>;
272

            
273
        /// Same as Currency::Balance. Must impl `MulDiv` which perform
274
        /// multiplication followed by division using a bigger type to avoid
275
        /// overflows.
276
        type Balance: Balance + MulDiv;
277

            
278
        /// Account holding Currency of all delegators.
279
        #[pallet::constant]
280
        type StakingAccount: Get<Self::AccountId>;
281

            
282
        /// When creating the first Shares for a candidate the supply can be arbitrary.
283
        /// Picking a value too low will make an higher supply, which means each share will get
284
        /// less rewards, and rewards calculations will have more impactful rounding errors.
285
        /// Picking a value too high is a barrier of entry for staking.
286
        #[pallet::constant]
287
        type InitialManualClaimShareValue: Get<Self::Balance>;
288
        /// When creating the first Shares for a candidate the supply can arbitrary.
289
        /// Picking a value too high is a barrier of entry for staking, which will increase overtime
290
        /// as the value of each share will increase due to auto compounding.
291
        #[pallet::constant]
292
        type InitialAutoCompoundingShareValue: Get<Self::Balance>;
293

            
294
        /// Minimum amount of stake a Candidate must delegate (stake) towards itself. Not reaching
295
        /// this minimum prevents from being elected.
296
        #[pallet::constant]
297
        type MinimumSelfDelegation: Get<Self::Balance>;
298
        /// Part of the rewards that will be sent exclusively to the collator.
299
        #[pallet::constant]
300
        type RewardsCollatorCommission: Get<Perbill>;
301

            
302
        /// The overarching runtime hold reason.
303
        type RuntimeHoldReason: From<HoldReason>;
304

            
305
        /// Condition for when a joining request can be executed.
306
        type JoiningRequestTimer: Timer;
307
        /// Condition for when a leaving request can be executed.
308
        type LeavingRequestTimer: Timer;
309
        /// All eligible candidates are stored in a sorted list that is modified each time
310
        /// delegations changes. It is safer to bound this list, in which case eligible candidate
311
        /// could fall out of this list if they have less stake than the top `EligibleCandidatesBufferSize`
312
        /// eligible candidates. One of this top candidates leaving will then not bring the dropped candidate
313
        /// in the list. An extrinsic is available to manually bring back such dropped candidate.
314
        #[pallet::constant]
315
        type EligibleCandidatesBufferSize: Get<u32>;
316
        /// Additional filter for candidates to be eligible.
317
        type EligibleCandidatesFilter: IsCandidateEligible<Self::AccountId>;
318

            
319
        type WeightInfo: WeightInfo;
320
    }
321

            
322
    /// Keeps a list of all eligible candidates, sorted by the amount of stake backing them.
323
    /// This can be quickly updated using a binary search, and allow to easily take the top
324
    /// `MaxCollatorSetSize`.
325
12656
    #[pallet::storage]
326
    pub type SortedEligibleCandidates<T: Config> = StorageValue<
327
        _,
328
        BoundedVec<
329
            candidate::EligibleCandidate<Candidate<T>, T::Balance>,
330
            T::EligibleCandidatesBufferSize,
331
        >,
332
        ValueQuery,
333
    >;
334

            
335
    /// Pools balances.
336
18061
    #[pallet::storage]
337
    pub type Pools<T: Config> = StorageDoubleMap<
338
        _,
339
        Blake2_128Concat,
340
        Candidate<T>,
341
        Blake2_128Concat,
342
        PoolsKey<T::AccountId>,
343
        T::Balance,
344
        ValueQuery,
345
    >;
346

            
347
    /// Pending operations balances.
348
    /// Balances are expressed in joining/leaving shares amounts.
349
1766
    #[pallet::storage]
350
    pub type PendingOperations<T: Config> = StorageDoubleMap<
351
        _,
352
        Blake2_128Concat,
353
        Delegator<T>,
354
        Blake2_128Concat,
355
        PendingOperationKeyOf<T>,
356
        T::Balance,
357
        ValueQuery,
358
    >;
359

            
360
    /// Summary of a delegator's delegation.
361
    /// Used to quickly fetch all delegations of a delegator.
362
1388
    #[pallet::storage]
363
    pub type DelegatorCandidateSummaries<T: Config> = StorageDoubleMap<
364
        _,
365
        Blake2_128Concat,
366
        Delegator<T>,
367
        Blake2_128Concat,
368
        Candidate<T>,
369
        DelegatorCandidateSummary,
370
        ValueQuery,
371
    >;
372

            
373
    /// Summary of a candidate state.
374
1371
    #[pallet::storage]
375
    pub type CandidateSummaries<T: Config> =
376
        StorageMap<_, Blake2_128Concat, Candidate<T>, CandidateSummary, ValueQuery>;
377

            
378
    /// Pauses the ability to modify pools through extrinsics.
379
    ///
380
    /// Currently added only to run the multi-block migration to compute
381
    /// `DelegatorCandidateSummaries` and `CandidateSummaries`. It will NOT
382
    /// prevent to distribute rewards, which is fine as the reward distribution
383
    /// process doesn't alter the pools in a way that will mess with the migration.
384
1958
    #[pallet::storage]
385
    pub type PausePoolsExtrinsics<T: Config> = StorageValue<_, bool, ValueQuery>;
386

            
387
618
    #[pallet::event]
388
3425
    #[pallet::generate_deposit(pub(super) fn deposit_event)]
389
    pub enum Event<T: Config> {
390
205
        /// Stake of the candidate has changed, which may have modified its
391
        /// position in the eligible candidates list.
392
        UpdatedCandidatePosition {
393
            candidate: Candidate<T>,
394
            stake: T::Balance,
395
            self_delegation: T::Balance,
396
            before: Option<u32>,
397
            after: Option<u32>,
398
        },
399

            
400
161
        /// User requested to delegate towards a candidate.
401
        RequestedDelegate {
402
            candidate: Candidate<T>,
403
            delegator: Delegator<T>,
404
            pool: ActivePoolKind,
405
            pending: T::Balance,
406
        },
407
159
        /// Delegation request was executed. `staked` has been properly staked
408
        /// in `pool`, while the rounding when converting to shares has been
409
        /// `released`.
410
        ExecutedDelegate {
411
            candidate: Candidate<T>,
412
            delegator: Delegator<T>,
413
            pool: ActivePoolKind,
414
            staked: T::Balance,
415
            released: T::Balance,
416
        },
417
9
        /// User requested to undelegate from a candidate.
418
        /// Stake was removed from a `pool` and is `pending` for the request
419
        /// to be executed. The rounding when converting to leaving shares has
420
        /// been `released` immediately.
421
        RequestedUndelegate {
422
            candidate: Candidate<T>,
423
            delegator: Delegator<T>,
424
            from: ActivePoolKind,
425
            pending: T::Balance,
426
            released: T::Balance,
427
        },
428
9
        /// Undelegation request was executed.
429
        ExecutedUndelegate {
430
            candidate: Candidate<T>,
431
            delegator: Delegator<T>,
432
            released: T::Balance,
433
        },
434

            
435
182
        /// Stake of that Candidate increased.
436
        IncreasedStake {
437
            candidate: Candidate<T>,
438
            stake_diff: T::Balance,
439
        },
440
23
        /// Stake of that Candidate decreased.
441
        DecreasedStake {
442
            candidate: Candidate<T>,
443
            stake_diff: T::Balance,
444
        },
445
90
        /// Delegator staked towards a Candidate for AutoCompounding Shares.
446
        StakedAutoCompounding {
447
            candidate: Candidate<T>,
448
            delegator: Delegator<T>,
449
            shares: T::Balance,
450
            stake: T::Balance,
451
        },
452
        /// Delegator unstaked towards a candidate with AutoCompounding Shares.
453
        UnstakedAutoCompounding {
454
            candidate: Candidate<T>,
455
            delegator: Delegator<T>,
456
            shares: T::Balance,
457
            stake: T::Balance,
458
        },
459
69
        /// Delegator staked towards a candidate for ManualRewards Shares.
460
        StakedManualRewards {
461
            candidate: Candidate<T>,
462
            delegator: Delegator<T>,
463
            shares: T::Balance,
464
            stake: T::Balance,
465
        },
466
        /// Delegator unstaked towards a candidate with ManualRewards Shares.
467
        UnstakedManualRewards {
468
            candidate: Candidate<T>,
469
            delegator: Delegator<T>,
470
            shares: T::Balance,
471
            stake: T::Balance,
472
        },
473
27
        /// Collator has been rewarded.
474
        RewardedCollator {
475
            collator: Candidate<T>,
476
            auto_compounding_rewards: T::Balance,
477
            manual_claim_rewards: T::Balance,
478
        },
479
27
        /// Delegators have been rewarded.
480
        RewardedDelegators {
481
            collator: Candidate<T>,
482
            auto_compounding_rewards: T::Balance,
483
            manual_claim_rewards: T::Balance,
484
        },
485
        /// Rewards manually claimed.
486
        ClaimedManualRewards {
487
            candidate: Candidate<T>,
488
            delegator: Delegator<T>,
489
            rewards: T::Balance,
490
        },
491
18
        /// Swapped between AutoCompounding and ManualReward shares
492
        SwappedPool {
493
            candidate: Candidate<T>,
494
            delegator: Delegator<T>,
495
            source_pool: ActivePoolKind,
496
            source_shares: T::Balance,
497
            source_stake: T::Balance,
498
            target_shares: T::Balance,
499
            target_stake: T::Balance,
500
            pending_leaving: T::Balance,
501
            released: T::Balance,
502
        },
503
    }
504

            
505
100
    #[pallet::error]
506
    pub enum Error<T> {
507
        InvalidPalletSetting,
508
        DisabledFeature,
509
        NoOneIsStaking,
510
        StakeMustBeNonZero,
511
        RewardsMustBeNonZero,
512
        MathUnderflow,
513
        MathOverflow,
514
        NotEnoughShares,
515
        TryingToLeaveTooSoon,
516
        InconsistentState,
517
        UnsufficientSharesForTransfer,
518
        CandidateTransferingOwnSharesForbidden,
519
        RequestCannotBeExecuted(u16),
520
        SwapResultsInZeroShares,
521
        PoolsExtrinsicsArePaused,
522
    }
523

            
524
    impl<T: Config> From<tp_maths::OverflowError> for Error<T> {
525
        fn from(_: tp_maths::OverflowError) -> Self {
526
            Error::MathOverflow
527
        }
528
    }
529

            
530
    impl<T: Config> From<tp_maths::UnderflowError> for Error<T> {
531
5
        fn from(_: tp_maths::UnderflowError) -> Self {
532
5
            Error::MathUnderflow
533
5
        }
534
    }
535

            
536
46876
    #[pallet::hooks]
537
    impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
538
        #[cfg(feature = "try-runtime")]
539
        fn try_state(_n: BlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
540
            use frame_support::storage_alias;
541
            use sp_std::collections::btree_set::BTreeSet;
542

            
543
            let mut all_candidates = BTreeSet::new();
544
            for (candidate, _k2) in Pools::<T>::iter_keys() {
545
                all_candidates.insert(candidate);
546
            }
547

            
548
            for candidate in all_candidates {
549
                pools::check_candidate_consistency::<T>(&candidate)?;
550
            }
551

            
552
            // Sorted storage items are sorted
553
            fn assert_is_sorted_and_unique<T: Ord>(x: &[T], name: &str) {
554
                assert!(
555
                    x.windows(2).all(|w| w[0] < w[1]),
556
                    "sorted list not sorted or not unique: {}",
557
                    name,
558
                );
559
            }
560
            assert_is_sorted_and_unique(
561
                &SortedEligibleCandidates::<T>::get(),
562
                "SortedEligibleCandidates",
563
            );
564

            
565
            if Pallet::<T>::on_chain_storage_version() < 1 {
566
                return Ok(());
567
            }
568

            
569
            // Summaries updated dynamically matches summaries generated entirely from shares.
570
            #[storage_alias(pallet_name)]
571
            pub type TryStateDelegatorSummaries<T: Config> = StorageDoubleMap<
572
                Pallet<T>,
573
                Blake2_128Concat,
574
                Delegator<T>,
575
                Blake2_128Concat,
576
                Candidate<T>,
577
                DelegatorCandidateSummary,
578
                ValueQuery,
579
            >;
580

            
581
            #[storage_alias(pallet_name)]
582
            pub type TryStateCandidateSummaries<T: Config> =
583
                StorageMap<Pallet<T>, Blake2_128Concat, Candidate<T>, CandidateSummary, ValueQuery>;
584

            
585
            assert!(
586
                migrations::stepped_generate_summaries::<
587
                    T,
588
                    TryStateDelegatorSummaries<T>,
589
                    TryStateCandidateSummaries<T>,
590
                >(
591
                    None,
592
                    &mut frame_support::weights::WeightMeter::new(), // call with no limit on weight
593
                )
594
                .expect("to generate summaries without errors")
595
                .is_none(),
596
                "failed to generate all summaries"
597
            );
598

            
599
            let mut candidate_summaries_count = 0;
600
            for (candidate, summary) in TryStateCandidateSummaries::<T>::iter() {
601
                candidate_summaries_count += 1;
602
                assert_eq!(
603
                    CandidateSummaries::<T>::get(&candidate),
604
                    summary,
605
                    "candidate summary for {candidate:?} didn't match"
606
                );
607
            }
608
            assert_eq!(
609
                candidate_summaries_count,
610
                CandidateSummaries::<T>::iter().count(),
611
                "count of candidate summaries didn't match"
612
            );
613

            
614
            let mut delegator_summaries_count = 0;
615
            for (delegator, candidate, summary) in TryStateDelegatorSummaries::<T>::iter() {
616
                delegator_summaries_count += 1;
617
                assert_eq!(
618
                    DelegatorCandidateSummaries::<T>::get(&delegator, &candidate),
619
                    summary,
620
                    "delegator summary for {delegator:?} to {candidate:?} didn't match"
621
                );
622
            }
623
            assert_eq!(
624
                delegator_summaries_count,
625
                DelegatorCandidateSummaries::<T>::iter().count(),
626
                "count of delegator summaries didn't match"
627
            );
628

            
629
            Ok(())
630
        }
631
    }
632

            
633
1512
    #[pallet::call]
634
    impl<T: Config> Pallet<T> {
635
        #[pallet::call_index(0)]
636
        #[pallet::weight(T::WeightInfo::rebalance_hold())]
637
        pub fn rebalance_hold(
638
            origin: OriginFor<T>,
639
            candidate: Candidate<T>,
640
            delegator: Delegator<T>,
641
            pool: PoolKind,
642
7
        ) -> DispatchResultWithPostInfo {
643
7
            // We don't care about the sender.
644
7
            let _ = ensure_signed(origin)?;
645

            
646
7
            Calls::<T>::rebalance_hold(candidate, delegator, pool)
647
        }
648

            
649
        #[pallet::call_index(1)]
650
        #[pallet::weight(T::WeightInfo::request_delegate())]
651
        pub fn request_delegate(
652
            origin: OriginFor<T>,
653
            candidate: Candidate<T>,
654
            pool: ActivePoolKind,
655
            stake: T::Balance,
656
261
        ) -> DispatchResultWithPostInfo {
657
261
            let delegator = ensure_signed(origin)?;
658

            
659
258
            Calls::<T>::request_delegate(candidate, delegator, pool, stake)
660
        }
661

            
662
        /// Execute pending operations can incur in claim manual rewards per operation, we simply add the worst case
663
        #[pallet::call_index(2)]
664
        #[pallet::weight(T::WeightInfo::execute_pending_operations(operations.len() as u32).saturating_add(T::WeightInfo::claim_manual_rewards(operations.len() as u32)))]
665
        pub fn execute_pending_operations(
666
            origin: OriginFor<T>,
667
            operations: Vec<PendingOperationQueryOf<T>>,
668
175
        ) -> DispatchResultWithPostInfo {
669
175
            // We don't care about the sender.
670
175
            let _ = ensure_signed(origin)?;
671

            
672
169
            Calls::<T>::execute_pending_operations(operations)
673
        }
674

            
675
        /// Request undelegate can incur in either claim manual rewards or hold rebalances, we simply add the worst case
676
        #[pallet::call_index(3)]
677
        #[pallet::weight(T::WeightInfo::request_undelegate().saturating_add(T::WeightInfo::claim_manual_rewards(1).max(T::WeightInfo::rebalance_hold())))]
678
        pub fn request_undelegate(
679
            origin: OriginFor<T>,
680
            candidate: Candidate<T>,
681
            pool: ActivePoolKind,
682
            amount: SharesOrStake<T::Balance>,
683
49
        ) -> DispatchResultWithPostInfo {
684
49
            let delegator = ensure_signed(origin)?;
685

            
686
46
            Calls::<T>::request_undelegate(candidate, delegator, pool, amount)
687
        }
688

            
689
        #[pallet::call_index(4)]
690
        #[pallet::weight(T::WeightInfo::claim_manual_rewards(pairs.len() as u32))]
691
        pub fn claim_manual_rewards(
692
            origin: OriginFor<T>,
693
            pairs: Vec<(Candidate<T>, Delegator<T>)>,
694
1
        ) -> DispatchResultWithPostInfo {
695
1
            // We don't care about the sender.
696
1
            let _ = ensure_signed(origin)?;
697

            
698
1
            Calls::<T>::claim_manual_rewards(&pairs)
699
        }
700

            
701
        #[pallet::call_index(5)]
702
        #[pallet::weight(T::WeightInfo::update_candidate_position(candidates.len() as u32))]
703
        pub fn update_candidate_position(
704
            origin: OriginFor<T>,
705
            candidates: Vec<Candidate<T>>,
706
5
        ) -> DispatchResultWithPostInfo {
707
5
            // We don't care about the sender.
708
5
            let _ = ensure_signed(origin)?;
709

            
710
5
            Calls::<T>::update_candidate_position(&candidates)
711
        }
712

            
713
        #[pallet::call_index(6)]
714
        #[pallet::weight(T::WeightInfo::swap_pool())]
715
        pub fn swap_pool(
716
            origin: OriginFor<T>,
717
            candidate: Candidate<T>,
718
            source_pool: ActivePoolKind,
719
            amount: SharesOrStake<T::Balance>,
720
23
        ) -> DispatchResultWithPostInfo {
721
23
            let delegator = ensure_signed(origin)?;
722

            
723
23
            Calls::<T>::swap_pool(candidate, delegator, source_pool, amount)
724
        }
725
    }
726

            
727
    impl<T: Config> Pallet<T> {
728
12
        pub fn computed_stake(
729
12
            candidate: Candidate<T>,
730
12
            delegator: Delegator<T>,
731
12
            pool: PoolKind,
732
12
        ) -> Option<T::Balance> {
733
            use pools::Pool;
734
12
            match pool {
735
                PoolKind::Joining => pools::Joining::<T>::computed_stake(&candidate, &delegator),
736
                PoolKind::AutoCompounding => {
737
6
                    pools::AutoCompounding::<T>::computed_stake(&candidate, &delegator)
738
                }
739
                PoolKind::ManualRewards => {
740
6
                    pools::ManualRewards::<T>::computed_stake(&candidate, &delegator)
741
                }
742
                PoolKind::Leaving => pools::Leaving::<T>::computed_stake(&candidate, &delegator),
743
            }
744
12
            .ok()
745
12
            .map(|x| x.0)
746
12
        }
747
    }
748

            
749
    impl<T: Config> tp_traits::DistributeRewards<Candidate<T>, CreditOf<T>> for Pallet<T> {
750
1045
        fn distribute_rewards(
751
1045
            candidate: Candidate<T>,
752
1045
            rewards: CreditOf<T>,
753
1045
        ) -> DispatchResultWithPostInfo {
754
1045
            pools::distribute_rewards::<T>(&candidate, rewards)
755
1045
        }
756
    }
757
}