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
pub mod weights;
51
use frame_support::pallet;
52
pub use weights::WeightInfo;
53

            
54
pub use {candidate::EligibleCandidate, pallet::*};
55

            
56
#[pallet]
57
pub mod pallet {
58
    use {
59
        super::*,
60
        crate::{
61
            traits::{IsCandidateEligible, Timer},
62
            weights::WeightInfo,
63
        },
64
        calls::Calls,
65
        core::marker::PhantomData,
66
        frame_support::{
67
            pallet_prelude::*,
68
            storage::types::{StorageDoubleMap, StorageValue, ValueQuery},
69
            traits::{fungible, tokens::Balance, IsType},
70
            Blake2_128Concat,
71
        },
72
        frame_system::pallet_prelude::*,
73
        parity_scale_codec::{Decode, Encode, FullCodec},
74
        scale_info::TypeInfo,
75
        sp_core::Get,
76
        sp_runtime::{BoundedVec, Perbill},
77
        sp_std::vec::Vec,
78
        tp_maths::MulDiv,
79
    };
80

            
81
    /// A reason for this pallet placing a hold on funds.
82
    #[pallet::composite_enum]
83
    pub enum HoldReason {
84
509
        PooledStake,
85
    }
86

            
87
    #[cfg(feature = "std")]
88
    use serde::{Deserialize, Serialize};
89

            
90
    // Type aliases for better readability.
91
    pub type Candidate<T> = <T as frame_system::Config>::AccountId;
92
    pub type CreditOf<T> =
93
        fungible::Credit<<T as frame_system::Config>::AccountId, <T as Config>::Currency>;
94
    pub type Delegator<T> = <T as frame_system::Config>::AccountId;
95

            
96
    /// Key used by the `Pools` StorageDoubleMap, avoiding lots of maps.
97
    /// StorageDoubleMap first key is the account id of the candidate.
98
    #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
99
5104
    #[derive(RuntimeDebug, PartialEq, Eq, Encode, Decode, Clone, TypeInfo)]
100
    pub enum PoolsKey<A: FullCodec> {
101
        /// Total amount of currency backing this candidate across all pools.
102
        CandidateTotalStake,
103

            
104
        /// Amount of joining shares a delegator have for that candidate.
105
        JoiningShares { delegator: A },
106
        /// Total amount of joining shares existing for that candidate.
107
        JoiningSharesSupply,
108
        /// Amount of currency backing all the joining shares of that candidate.
109
        JoiningSharesTotalStaked,
110
        /// Amount of currency held in the delegator account.
111
        JoiningSharesHeldStake { delegator: A },
112

            
113
        /// Amount of auto compounding shares a delegator have for that candidate.
114
        AutoCompoundingShares { delegator: A },
115
        /// Total amount of auto compounding shares existing for that candidate.
116
        AutoCompoundingSharesSupply,
117
        /// Amount of currency backing all the auto compounding shares of that candidate.
118
        AutoCompoundingSharesTotalStaked,
119
        /// Amount of currency held in the delegator account.
120
        AutoCompoundingSharesHeldStake { delegator: A },
121

            
122
        /// Amount of manual rewards shares a delegator have for that candidate.
123
        ManualRewardsShares { delegator: A },
124
        /// Total amount of manual rewards shares existing for that candidate.
125
        ManualRewardsSharesSupply,
126
        /// Amount of currency backing all the manual rewards shares of that candidate.
127
        ManualRewardsSharesTotalStaked,
128
        /// Amount of currency held in the delegator account.
129
        ManualRewardsSharesHeldStake { delegator: A },
130
        /// Counter of the cumulated rewards per share generated by that candidate since genesis.
131
        /// Is safe to wrap around the maximum value of the balance type.
132
        ManualRewardsCounter,
133
        /// Value of the counter at the last time the delegator claimed its rewards or changed its amount of shares
134
        /// (changing the amount of shares automatically claims pending rewards).
135
        /// The difference between the checkpoint and the counter is the amount of claimable reward per share for
136
        /// that delegator.
137
        ManualRewardsCheckpoint { delegator: A },
138

            
139
        /// Amount of shares of that delegator in the leaving pool of that candidate.
140
        /// When leaving delegating funds are placed in the leaving pool until the leaving period is elapsed.
141
        /// While in the leaving pool the funds are still slashable.
142
        LeavingShares { delegator: A },
143
        /// Total amount of leaving shares existing for that candidate.
144
        LeavingSharesSupply,
145
        /// Amount of currency backing all the leaving shares of that candidate.
146
        LeavingSharesTotalStaked,
147
        /// Amount of currency held in the delegator account.
148
        LeavingSharesHeldStake { delegator: A },
149
    }
150

            
151
    /// Key used by the "PendingOperations" StorageDoubleMap.
152
    /// StorageDoubleMap first key is the account id of the delegator who made the request.
153
    /// Value is the amount of shares in the joining/leaving pool.
154
    #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
155
1760
    #[derive(RuntimeDebug, PartialEq, Eq, Encode, Decode, Clone, TypeInfo)]
156
    pub enum PendingOperationKey<A: FullCodec, J: FullCodec, L: FullCodec> {
157
20
        /// Candidate requested to join the auto compounding pool of a candidate.
158
        JoiningAutoCompounding { candidate: A, at: J },
159
12
        /// Candidate requested to join the manual rewards pool of a candidate.
160
        JoiningManualRewards { candidate: A, at: J },
161
4
        /// Candidate requested to to leave a pool of a candidate.
162
        Leaving { candidate: A, at: L },
163
    }
164

            
165
    pub type PendingOperationKeyOf<T> = PendingOperationKey<
166
        <T as frame_system::Config>::AccountId,
167
        <<T as Config>::JoiningRequestTimer as Timer>::Instant,
168
        <<T as Config>::LeavingRequestTimer as Timer>::Instant,
169
    >;
170

            
171
    #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
172
528
    #[derive(RuntimeDebug, PartialEq, Eq, Encode, Decode, Clone, TypeInfo)]
173
    pub struct PendingOperationQuery<A: FullCodec, J: FullCodec, L: FullCodec> {
174
        pub delegator: A,
175
        pub operation: PendingOperationKey<A, J, L>,
176
    }
177

            
178
    pub type PendingOperationQueryOf<T> = PendingOperationQuery<
179
        <T as frame_system::Config>::AccountId,
180
        <<T as Config>::JoiningRequestTimer as Timer>::Instant,
181
        <<T as Config>::LeavingRequestTimer as Timer>::Instant,
182
    >;
183

            
184
    #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
185
528
    #[derive(RuntimeDebug, PartialEq, Eq, Encode, Decode, Copy, Clone, TypeInfo)]
186
    pub enum TargetPool {
187
219
        AutoCompounding,
188
178
        ManualRewards,
189
    }
190

            
191
    #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
192
880
    #[derive(RuntimeDebug, PartialEq, Eq, Encode, Decode, Copy, Clone, TypeInfo)]
193
    pub enum AllTargetPool {
194
        Joining,
195
        AutoCompounding,
196
        ManualRewards,
197
        Leaving,
198
    }
199

            
200
    impl From<TargetPool> for AllTargetPool {
201
6
        fn from(value: TargetPool) -> Self {
202
6
            match value {
203
3
                TargetPool::AutoCompounding => AllTargetPool::AutoCompounding,
204
3
                TargetPool::ManualRewards => AllTargetPool::ManualRewards,
205
            }
206
6
        }
207
    }
208

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

            
220
    /// Wrapper type for an amount of shares.
221
    #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
222
    #[derive(RuntimeDebug, Default, PartialEq, Eq, Encode, Decode, Copy, Clone, TypeInfo)]
223
    pub struct Shares<T>(pub T);
224

            
225
    /// Wrapper type for an amount of staked currency.
226
    #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
227
    #[derive(RuntimeDebug, Default, PartialEq, Eq, Encode, Decode, Copy, Clone, TypeInfo)]
228
    pub struct Stake<T>(pub T);
229

            
230
    /// Pooled Staking pallet.
231
180
    #[pallet::pallet]
232
    #[pallet::without_storage_info]
233
    pub struct Pallet<T>(PhantomData<T>);
234

            
235
    #[pallet::config]
236
    pub trait Config: frame_system::Config {
237
        /// Overarching event type
238
        type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
239
        /// The currency type.
240
        /// Shares will use the same Balance type.
241
        type Currency: fungible::Inspect<Self::AccountId, Balance = Self::Balance>
242
            + fungible::Mutate<Self::AccountId>
243
            + fungible::Balanced<Self::AccountId>
244
            + fungible::MutateHold<Self::AccountId, Reason = Self::RuntimeHoldReason>;
245

            
246
        /// Same as Currency::Balance. Must impl `MulDiv` which perform
247
        /// multiplication followed by division using a bigger type to avoid
248
        /// overflows.
249
        type Balance: Balance + MulDiv;
250

            
251
        /// Account holding Currency of all delegators.
252
        #[pallet::constant]
253
        type StakingAccount: Get<Self::AccountId>;
254

            
255
        /// When creating the first Shares for a candidate the supply can be arbitrary.
256
        /// Picking a value too low will make an higher supply, which means each share will get
257
        /// less rewards, and rewards calculations will have more impactful rounding errors.
258
        /// Picking a value too high is a barrier of entry for staking.
259
        #[pallet::constant]
260
        type InitialManualClaimShareValue: Get<Self::Balance>;
261
        /// When creating the first Shares for a candidate the supply can arbitrary.
262
        /// Picking a value too high is a barrier of entry for staking, which will increase overtime
263
        /// as the value of each share will increase due to auto compounding.
264
        #[pallet::constant]
265
        type InitialAutoCompoundingShareValue: Get<Self::Balance>;
266

            
267
        /// Minimum amount of stake a Candidate must delegate (stake) towards itself. Not reaching
268
        /// this minimum prevents from being elected.
269
        #[pallet::constant]
270
        type MinimumSelfDelegation: Get<Self::Balance>;
271
        /// Part of the rewards that will be sent exclusively to the collator.
272
        #[pallet::constant]
273
        type RewardsCollatorCommission: Get<Perbill>;
274

            
275
        /// The overarching runtime hold reason.
276
        type RuntimeHoldReason: From<HoldReason>;
277

            
278
        /// Condition for when a joining request can be executed.
279
        type JoiningRequestTimer: Timer;
280
        /// Condition for when a leaving request can be executed.
281
        type LeavingRequestTimer: Timer;
282
        /// All eligible candidates are stored in a sorted list that is modified each time
283
        /// delegations changes. It is safer to bound this list, in which case eligible candidate
284
        /// could fall out of this list if they have less stake than the top `EligibleCandidatesBufferSize`
285
        /// eligible candidates. One of this top candidates leaving will then not bring the dropped candidate
286
        /// in the list. An extrinsic is available to manually bring back such dropped candidate.
287
        #[pallet::constant]
288
        type EligibleCandidatesBufferSize: Get<u32>;
289
        /// Additional filter for candidates to be eligible.
290
        type EligibleCandidatesFilter: IsCandidateEligible<Self::AccountId>;
291

            
292
        type WeightInfo: WeightInfo;
293
    }
294

            
295
    /// Keeps a list of all eligible candidates, sorted by the amount of stake backing them.
296
    /// This can be quickly updated using a binary search, and allow to easily take the top
297
    /// `MaxCollatorSetSize`.
298
4450
    #[pallet::storage]
299
    pub type SortedEligibleCandidates<T: Config> = StorageValue<
300
        _,
301
        BoundedVec<
302
            candidate::EligibleCandidate<Candidate<T>, T::Balance>,
303
            T::EligibleCandidatesBufferSize,
304
        >,
305
        ValueQuery,
306
    >;
307

            
308
    /// Pools balances.
309
9840
    #[pallet::storage]
310
    pub type Pools<T: Config> = StorageDoubleMap<
311
        _,
312
        Blake2_128Concat,
313
        Candidate<T>,
314
        Blake2_128Concat,
315
        PoolsKey<T::AccountId>,
316
        T::Balance,
317
        ValueQuery,
318
    >;
319

            
320
    /// Pending operations balances.
321
    /// Balances are expressed in joining/leaving shares amounts.
322
692
    #[pallet::storage]
323
    pub type PendingOperations<T: Config> = StorageDoubleMap<
324
        _,
325
        Blake2_128Concat,
326
        Delegator<T>,
327
        Blake2_128Concat,
328
        PendingOperationKeyOf<T>,
329
        T::Balance,
330
        ValueQuery,
331
    >;
332

            
333
    #[pallet::event]
334
1485
    #[pallet::generate_deposit(pub(super) fn deposit_event)]
335
    pub enum Event<T: Config> {
336
199
        /// Stake of the candidate has changed, which may have modified its
337
        /// position in the eligible candidates list.
338
        UpdatedCandidatePosition {
339
            candidate: Candidate<T>,
340
            stake: T::Balance,
341
            self_delegation: T::Balance,
342
            before: Option<u32>,
343
            after: Option<u32>,
344
        },
345

            
346
155
        /// User requested to delegate towards a candidate.
347
        RequestedDelegate {
348
            candidate: Candidate<T>,
349
            delegator: Delegator<T>,
350
            pool: TargetPool,
351
            pending: T::Balance,
352
        },
353
153
        /// Delegation request was executed. `staked` has been properly staked
354
        /// in `pool`, while the rounding when converting to shares has been
355
        /// `released`.
356
        ExecutedDelegate {
357
            candidate: Candidate<T>,
358
            delegator: Delegator<T>,
359
            pool: TargetPool,
360
            staked: T::Balance,
361
            released: T::Balance,
362
        },
363
9
        /// User requested to undelegate from a candidate.
364
        /// Stake was removed from a `pool` and is `pending` for the request
365
        /// to be executed. The rounding when converting to leaving shares has
366
        /// been `released` immediately.
367
        RequestedUndelegate {
368
            candidate: Candidate<T>,
369
            delegator: Delegator<T>,
370
            from: TargetPool,
371
            pending: T::Balance,
372
            released: T::Balance,
373
        },
374
9
        /// Undelegation request was executed.
375
        ExecutedUndelegate {
376
            candidate: Candidate<T>,
377
            delegator: Delegator<T>,
378
            released: T::Balance,
379
        },
380

            
381
176
        /// Stake of that Candidate increased.
382
        IncreasedStake {
383
            candidate: Candidate<T>,
384
            stake_diff: T::Balance,
385
        },
386
23
        /// Stake of that Candidate decreased.
387
        DecreasedStake {
388
            candidate: Candidate<T>,
389
            stake_diff: T::Balance,
390
        },
391
87
        /// Delegator staked towards a Candidate for AutoCompounding Shares.
392
        StakedAutoCompounding {
393
            candidate: Candidate<T>,
394
            delegator: Delegator<T>,
395
            shares: T::Balance,
396
            stake: T::Balance,
397
        },
398
        /// Delegator unstaked towards a candidate with AutoCompounding Shares.
399
        UnstakedAutoCompounding {
400
            candidate: Candidate<T>,
401
            delegator: Delegator<T>,
402
            shares: T::Balance,
403
            stake: T::Balance,
404
        },
405
66
        /// Delegator staked towards a candidate for ManualRewards Shares.
406
        StakedManualRewards {
407
            candidate: Candidate<T>,
408
            delegator: Delegator<T>,
409
            shares: T::Balance,
410
            stake: T::Balance,
411
        },
412
        /// Delegator unstaked towards a candidate with ManualRewards Shares.
413
        UnstakedManualRewards {
414
            candidate: Candidate<T>,
415
            delegator: Delegator<T>,
416
            shares: T::Balance,
417
            stake: T::Balance,
418
        },
419
27
        /// Collator has been rewarded.
420
        RewardedCollator {
421
            collator: Candidate<T>,
422
            auto_compounding_rewards: T::Balance,
423
            manual_claim_rewards: T::Balance,
424
        },
425
27
        /// Delegators have been rewarded.
426
        RewardedDelegators {
427
            collator: Candidate<T>,
428
            auto_compounding_rewards: T::Balance,
429
            manual_claim_rewards: T::Balance,
430
        },
431
        /// Rewards manually claimed.
432
        ClaimedManualRewards {
433
            candidate: Candidate<T>,
434
            delegator: Delegator<T>,
435
            rewards: T::Balance,
436
        },
437
12
        /// Swapped between AutoCompounding and ManualReward shares
438
        SwappedPool {
439
            candidate: Candidate<T>,
440
            delegator: Delegator<T>,
441
            source_pool: TargetPool,
442
            source_shares: T::Balance,
443
            source_stake: T::Balance,
444
            target_shares: T::Balance,
445
            target_stake: T::Balance,
446
            pending_leaving: T::Balance,
447
            released: T::Balance,
448
        },
449
    }
450

            
451
24
    #[pallet::error]
452
    pub enum Error<T> {
453
        InvalidPalletSetting,
454
        DisabledFeature,
455
        NoOneIsStaking,
456
        StakeMustBeNonZero,
457
        RewardsMustBeNonZero,
458
        MathUnderflow,
459
        MathOverflow,
460
        NotEnoughShares,
461
        TryingToLeaveTooSoon,
462
        InconsistentState,
463
        UnsufficientSharesForTransfer,
464
        CandidateTransferingOwnSharesForbidden,
465
        RequestCannotBeExecuted(u16),
466
        SwapResultsInZeroShares,
467
    }
468

            
469
    impl<T: Config> From<tp_maths::OverflowError> for Error<T> {
470
        fn from(_: tp_maths::OverflowError) -> Self {
471
            Error::MathOverflow
472
        }
473
    }
474

            
475
    impl<T: Config> From<tp_maths::UnderflowError> for Error<T> {
476
3
        fn from(_: tp_maths::UnderflowError) -> Self {
477
3
            Error::MathUnderflow
478
3
        }
479
    }
480

            
481
13614
    #[pallet::hooks]
482
    impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
483
        #[cfg(feature = "try-runtime")]
484
        fn try_state(_n: BlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
485
            use sp_std::collections::btree_set::BTreeSet;
486
            let mut all_candidates = BTreeSet::new();
487
            for (candidate, _k2) in Pools::<T>::iter_keys() {
488
                all_candidates.insert(candidate);
489
            }
490

            
491
            for candidate in all_candidates {
492
                pools::check_candidate_consistency::<T>(&candidate)?;
493
            }
494

            
495
            // Sorted storage items are sorted
496
            fn assert_is_sorted_and_unique<T: Ord>(x: &[T], name: &str) {
497
                assert!(
498
                    x.windows(2).all(|w| w[0] < w[1]),
499
                    "sorted list not sorted or not unique: {}",
500
                    name,
501
                );
502
            }
503
            assert_is_sorted_and_unique(
504
                &SortedEligibleCandidates::<T>::get(),
505
                "SortedEligibleCandidates",
506
            );
507

            
508
            Ok(())
509
        }
510
    }
511

            
512
288
    #[pallet::call]
513
    impl<T: Config> Pallet<T> {
514
        #[pallet::call_index(0)]
515
        #[pallet::weight(T::WeightInfo::rebalance_hold())]
516
        pub fn rebalance_hold(
517
            origin: OriginFor<T>,
518
            candidate: Candidate<T>,
519
            delegator: Delegator<T>,
520
            pool: AllTargetPool,
521
6
        ) -> DispatchResultWithPostInfo {
522
6
            // We don't care about the sender.
523
6
            let _ = ensure_signed(origin)?;
524

            
525
6
            Calls::<T>::rebalance_hold(candidate, delegator, pool)
526
        }
527

            
528
        #[pallet::call_index(1)]
529
        #[pallet::weight(T::WeightInfo::request_delegate())]
530
        pub fn request_delegate(
531
            origin: OriginFor<T>,
532
            candidate: Candidate<T>,
533
            pool: TargetPool,
534
            stake: T::Balance,
535
132
        ) -> DispatchResultWithPostInfo {
536
132
            let delegator = ensure_signed(origin)?;
537

            
538
131
            Calls::<T>::request_delegate(candidate, delegator, pool, stake)
539
        }
540

            
541
        /// Execute pending operations can incur in claim manual rewards per operation, we simply add the worst case
542
        #[pallet::call_index(2)]
543
        #[pallet::weight(T::WeightInfo::execute_pending_operations(operations.len() as u32).saturating_add(T::WeightInfo::claim_manual_rewards(operations.len() as u32)))]
544
        pub fn execute_pending_operations(
545
            origin: OriginFor<T>,
546
            operations: Vec<PendingOperationQueryOf<T>>,
547
102
        ) -> DispatchResultWithPostInfo {
548
102
            // We don't care about the sender.
549
102
            let _ = ensure_signed(origin)?;
550

            
551
100
            Calls::<T>::execute_pending_operations(operations)
552
        }
553

            
554
        /// Request undelegate can incur in either claim manual rewards or hold rebalances, we simply add the worst case
555
        #[pallet::call_index(3)]
556
        #[pallet::weight(T::WeightInfo::request_undelegate().saturating_add(T::WeightInfo::claim_manual_rewards(1).max(T::WeightInfo::rebalance_hold())))]
557
        pub fn request_undelegate(
558
            origin: OriginFor<T>,
559
            candidate: Candidate<T>,
560
            pool: TargetPool,
561
            amount: SharesOrStake<T::Balance>,
562
24
        ) -> DispatchResultWithPostInfo {
563
24
            let delegator = ensure_signed(origin)?;
564

            
565
23
            Calls::<T>::request_undelegate(candidate, delegator, pool, amount)
566
        }
567

            
568
        #[pallet::call_index(4)]
569
        #[pallet::weight(T::WeightInfo::claim_manual_rewards(pairs.len() as u32))]
570
        pub fn claim_manual_rewards(
571
            origin: OriginFor<T>,
572
            pairs: Vec<(Candidate<T>, Delegator<T>)>,
573
        ) -> DispatchResultWithPostInfo {
574
            // We don't care about the sender.
575
            let _ = ensure_signed(origin)?;
576

            
577
            Calls::<T>::claim_manual_rewards(&pairs)
578
        }
579

            
580
        #[pallet::call_index(5)]
581
        #[pallet::weight(T::WeightInfo::update_candidate_position(candidates.len() as u32))]
582
        pub fn update_candidate_position(
583
            origin: OriginFor<T>,
584
            candidates: Vec<Candidate<T>>,
585
2
        ) -> DispatchResultWithPostInfo {
586
2
            // We don't care about the sender.
587
2
            let _ = ensure_signed(origin)?;
588

            
589
2
            Calls::<T>::update_candidate_position(&candidates)
590
        }
591

            
592
        #[pallet::call_index(6)]
593
        #[pallet::weight(T::WeightInfo::swap_pool())]
594
        pub fn swap_pool(
595
            origin: OriginFor<T>,
596
            candidate: Candidate<T>,
597
            source_pool: TargetPool,
598
            amount: SharesOrStake<T::Balance>,
599
12
        ) -> DispatchResultWithPostInfo {
600
12
            let delegator = ensure_signed(origin)?;
601

            
602
12
            Calls::<T>::swap_pool(candidate, delegator, source_pool, amount)
603
        }
604
    }
605

            
606
    impl<T: Config> Pallet<T> {
607
4
        pub fn computed_stake(
608
4
            candidate: Candidate<T>,
609
4
            delegator: Delegator<T>,
610
4
            pool: AllTargetPool,
611
4
        ) -> Option<T::Balance> {
612
4
            use pools::Pool;
613
4
            match pool {
614
                AllTargetPool::Joining => {
615
                    pools::Joining::<T>::computed_stake(&candidate, &delegator)
616
                }
617
                AllTargetPool::AutoCompounding => {
618
2
                    pools::AutoCompounding::<T>::computed_stake(&candidate, &delegator)
619
                }
620
                AllTargetPool::ManualRewards => {
621
2
                    pools::ManualRewards::<T>::computed_stake(&candidate, &delegator)
622
                }
623
                AllTargetPool::Leaving => {
624
                    pools::Leaving::<T>::computed_stake(&candidate, &delegator)
625
                }
626
            }
627
4
            .ok()
628
4
            .map(|x| x.0)
629
4
        }
630
    }
631

            
632
    impl<T: Config> tp_traits::DistributeRewards<Candidate<T>, CreditOf<T>> for Pallet<T> {
633
381
        fn distribute_rewards(
634
381
            candidate: Candidate<T>,
635
381
            rewards: CreditOf<T>,
636
381
        ) -> DispatchResultWithPostInfo {
637
381
            pools::distribute_rewards::<T>(&candidate, rewards)
638
381
        }
639
    }
640
}