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
16088
#[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
        serde::{Deserialize, Serialize},
76
        sp_core::Get,
77
        sp_runtime::{BoundedVec, Perbill},
78
        sp_std::vec::Vec,
79
        tp_maths::MulDiv,
80
    };
81

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

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

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

            
112
        /// Amount of joining shares a delegator have for that candidate.
113
        JoiningShares { delegator: A },
114
        /// Total amount of joining shares existing for that candidate.
115
        JoiningSharesSupply,
116
        /// Amount of currency backing all the joining shares of that candidate.
117
        JoiningSharesTotalStaked,
118
        /// Amount of currency held in the delegator account.
119
        JoiningSharesHeldStake { delegator: A },
120

            
121
        /// Amount of auto compounding shares a delegator have for that candidate.
122
        AutoCompoundingShares { delegator: A },
123
        /// Total amount of auto compounding shares existing for that candidate.
124
        AutoCompoundingSharesSupply,
125
        /// Amount of currency backing all the auto compounding shares of that candidate.
126
        AutoCompoundingSharesTotalStaked,
127
        /// Amount of currency held in the delegator account.
128
        AutoCompoundingSharesHeldStake { delegator: A },
129

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

            
147
        /// Amount of shares of that delegator in the leaving pool of that candidate.
148
        /// When leaving delegating funds are placed in the leaving pool until the leaving period is elapsed.
149
        /// While in the leaving pool the funds are still slashable.
150
        LeavingShares { delegator: A },
151
        /// Total amount of leaving shares existing for that candidate.
152
        LeavingSharesSupply,
153
        /// Amount of currency backing all the leaving shares of that candidate.
154
        LeavingSharesTotalStaked,
155
        /// Amount of currency held in the delegator account.
156
        LeavingSharesHeldStake { delegator: A },
157
    }
158

            
159
    /// Key used by the "PendingOperations" StorageDoubleMap.
160
    /// StorageDoubleMap first key is the account id of the delegator who made the request.
161
    /// Value is the amount of shares in the joining/leaving pool.
162

            
163
    #[derive(
164
        RuntimeDebug,
165
        PartialEq,
166
        Eq,
167
        Encode,
168
        Decode,
169
        Clone,
170
3492
        TypeInfo,
171
        Serialize,
172
        Deserialize,
173
        MaxEncodedLen,
174
    )]
175
    pub enum PendingOperationKey<A: FullCodec, J: FullCodec, L: FullCodec> {
176
60
        /// Candidate requested to join the auto compounding pool of a candidate.
177
        JoiningAutoCompounding { candidate: A, at: J },
178
36
        /// Candidate requested to join the manual rewards pool of a candidate.
179
        JoiningManualRewards { candidate: A, at: J },
180
12
        /// Candidate requested to to leave a pool of a candidate.
181
        Leaving { candidate: A, at: L },
182
    }
183

            
184
    pub type PendingOperationKeyOf<T> = PendingOperationKey<
185
        <T as frame_system::Config>::AccountId,
186
        <<T as Config>::JoiningRequestTimer as Timer>::Instant,
187
        <<T as Config>::LeavingRequestTimer as Timer>::Instant,
188
    >;
189

            
190
    #[derive(
191
1164
        RuntimeDebug, PartialEq, Eq, Encode, Decode, Clone, TypeInfo, Serialize, Deserialize,
192
    )]
193
    pub struct PendingOperationQuery<A: FullCodec, J: FullCodec, L: FullCodec> {
194
        pub delegator: A,
195
        pub operation: PendingOperationKey<A, J, L>,
196
    }
197

            
198
    pub type PendingOperationQueryOf<T> = PendingOperationQuery<
199
        <T as frame_system::Config>::AccountId,
200
        <<T as Config>::JoiningRequestTimer as Timer>::Instant,
201
        <<T as Config>::LeavingRequestTimer as Timer>::Instant,
202
    >;
203

            
204
    #[derive(
205
1164
        RuntimeDebug, PartialEq, Eq, Encode, Decode, Copy, Clone, TypeInfo, Serialize, Deserialize,
206
    )]
207
    pub enum TargetPool {
208
283
        AutoCompounding,
209
250
        ManualRewards,
210
    }
211

            
212
    #[derive(
213
2328
        RuntimeDebug, PartialEq, Eq, Encode, Decode, Copy, Clone, TypeInfo, Serialize, Deserialize,
214
    )]
215
    pub enum AllTargetPool {
216
        Joining,
217
        AutoCompounding,
218
        ManualRewards,
219
        Leaving,
220
    }
221

            
222
    impl From<TargetPool> for AllTargetPool {
223
6
        fn from(value: TargetPool) -> Self {
224
6
            match value {
225
3
                TargetPool::AutoCompounding => AllTargetPool::AutoCompounding,
226
3
                TargetPool::ManualRewards => AllTargetPool::ManualRewards,
227
            }
228
6
        }
229
    }
230

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

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

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

            
275
    /// Pooled Staking pallet.
276
586
    #[pallet::pallet]
277
    pub struct Pallet<T>(PhantomData<T>);
278

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

            
290
        /// Same as Currency::Balance. Must impl `MulDiv` which perform
291
        /// multiplication followed by division using a bigger type to avoid
292
        /// overflows.
293
        type Balance: Balance + MulDiv;
294

            
295
        /// Account holding Currency of all delegators.
296
        #[pallet::constant]
297
        type StakingAccount: Get<Self::AccountId>;
298

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

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

            
319
        /// The overarching runtime hold reason.
320
        type RuntimeHoldReason: From<HoldReason>;
321

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

            
336
        type WeightInfo: WeightInfo;
337
    }
338

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

            
352
    /// Pools balances.
353
13924
    #[pallet::storage]
354
    pub type Pools<T: Config> = StorageDoubleMap<
355
        _,
356
        Blake2_128Concat,
357
        Candidate<T>,
358
        Blake2_128Concat,
359
        PoolsKey<T::AccountId>,
360
        T::Balance,
361
        ValueQuery,
362
    >;
363

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

            
377
    #[pallet::event]
378
3089
    #[pallet::generate_deposit(pub(super) fn deposit_event)]
379
    pub enum Event<T: Config> {
380
199
        /// Stake of the candidate has changed, which may have modified its
381
        /// position in the eligible candidates list.
382
        UpdatedCandidatePosition {
383
            candidate: Candidate<T>,
384
            stake: T::Balance,
385
            self_delegation: T::Balance,
386
            before: Option<u32>,
387
            after: Option<u32>,
388
        },
389

            
390
155
        /// User requested to delegate towards a candidate.
391
        RequestedDelegate {
392
            candidate: Candidate<T>,
393
            delegator: Delegator<T>,
394
            pool: TargetPool,
395
            pending: T::Balance,
396
        },
397
153
        /// Delegation request was executed. `staked` has been properly staked
398
        /// in `pool`, while the rounding when converting to shares has been
399
        /// `released`.
400
        ExecutedDelegate {
401
            candidate: Candidate<T>,
402
            delegator: Delegator<T>,
403
            pool: TargetPool,
404
            staked: T::Balance,
405
            released: T::Balance,
406
        },
407
9
        /// User requested to undelegate from a candidate.
408
        /// Stake was removed from a `pool` and is `pending` for the request
409
        /// to be executed. The rounding when converting to leaving shares has
410
        /// been `released` immediately.
411
        RequestedUndelegate {
412
            candidate: Candidate<T>,
413
            delegator: Delegator<T>,
414
            from: TargetPool,
415
            pending: T::Balance,
416
            released: T::Balance,
417
        },
418
9
        /// Undelegation request was executed.
419
        ExecutedUndelegate {
420
            candidate: Candidate<T>,
421
            delegator: Delegator<T>,
422
            released: T::Balance,
423
        },
424

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

            
495
56
    #[pallet::error]
496
    pub enum Error<T> {
497
        InvalidPalletSetting,
498
        DisabledFeature,
499
        NoOneIsStaking,
500
        StakeMustBeNonZero,
501
        RewardsMustBeNonZero,
502
        MathUnderflow,
503
        MathOverflow,
504
        NotEnoughShares,
505
        TryingToLeaveTooSoon,
506
        InconsistentState,
507
        UnsufficientSharesForTransfer,
508
        CandidateTransferingOwnSharesForbidden,
509
        RequestCannotBeExecuted(u16),
510
        SwapResultsInZeroShares,
511
    }
512

            
513
    impl<T: Config> From<tp_maths::OverflowError> for Error<T> {
514
        fn from(_: tp_maths::OverflowError) -> Self {
515
            Error::MathOverflow
516
        }
517
    }
518

            
519
    impl<T: Config> From<tp_maths::UnderflowError> for Error<T> {
520
3
        fn from(_: tp_maths::UnderflowError) -> Self {
521
3
            Error::MathUnderflow
522
3
        }
523
    }
524

            
525
41954
    #[pallet::hooks]
526
    impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
527
        #[cfg(feature = "try-runtime")]
528
        fn try_state(_n: BlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
529
            use sp_std::collections::btree_set::BTreeSet;
530
            let mut all_candidates = BTreeSet::new();
531
            for (candidate, _k2) in Pools::<T>::iter_keys() {
532
                all_candidates.insert(candidate);
533
            }
534

            
535
            for candidate in all_candidates {
536
                pools::check_candidate_consistency::<T>(&candidate)?;
537
            }
538

            
539
            // Sorted storage items are sorted
540
            fn assert_is_sorted_and_unique<T: Ord>(x: &[T], name: &str) {
541
                assert!(
542
                    x.windows(2).all(|w| w[0] < w[1]),
543
                    "sorted list not sorted or not unique: {}",
544
                    name,
545
                );
546
            }
547
            assert_is_sorted_and_unique(
548
                &SortedEligibleCandidates::<T>::get(),
549
                "SortedEligibleCandidates",
550
            );
551

            
552
            Ok(())
553
        }
554
    }
555

            
556
1008
    #[pallet::call]
557
    impl<T: Config> Pallet<T> {
558
        #[pallet::call_index(0)]
559
        #[pallet::weight(T::WeightInfo::rebalance_hold())]
560
        pub fn rebalance_hold(
561
            origin: OriginFor<T>,
562
            candidate: Candidate<T>,
563
            delegator: Delegator<T>,
564
            pool: AllTargetPool,
565
6
        ) -> DispatchResultWithPostInfo {
566
6
            // We don't care about the sender.
567
6
            let _ = ensure_signed(origin)?;
568

            
569
6
            Calls::<T>::rebalance_hold(candidate, delegator, pool)
570
        }
571

            
572
        #[pallet::call_index(1)]
573
        #[pallet::weight(T::WeightInfo::request_delegate())]
574
        pub fn request_delegate(
575
            origin: OriginFor<T>,
576
            candidate: Candidate<T>,
577
            pool: TargetPool,
578
            stake: T::Balance,
579
192
        ) -> DispatchResultWithPostInfo {
580
192
            let delegator = ensure_signed(origin)?;
581

            
582
191
            Calls::<T>::request_delegate(candidate, delegator, pool, stake)
583
        }
584

            
585
        /// Execute pending operations can incur in claim manual rewards per operation, we simply add the worst case
586
        #[pallet::call_index(2)]
587
        #[pallet::weight(T::WeightInfo::execute_pending_operations(operations.len() as u32).saturating_add(T::WeightInfo::claim_manual_rewards(operations.len() as u32)))]
588
        pub fn execute_pending_operations(
589
            origin: OriginFor<T>,
590
            operations: Vec<PendingOperationQueryOf<T>>,
591
130
        ) -> DispatchResultWithPostInfo {
592
130
            // We don't care about the sender.
593
130
            let _ = ensure_signed(origin)?;
594

            
595
128
            Calls::<T>::execute_pending_operations(operations)
596
        }
597

            
598
        /// Request undelegate can incur in either claim manual rewards or hold rebalances, we simply add the worst case
599
        #[pallet::call_index(3)]
600
        #[pallet::weight(T::WeightInfo::request_undelegate().saturating_add(T::WeightInfo::claim_manual_rewards(1).max(T::WeightInfo::rebalance_hold())))]
601
        pub fn request_undelegate(
602
            origin: OriginFor<T>,
603
            candidate: Candidate<T>,
604
            pool: TargetPool,
605
            amount: SharesOrStake<T::Balance>,
606
28
        ) -> DispatchResultWithPostInfo {
607
28
            let delegator = ensure_signed(origin)?;
608

            
609
27
            Calls::<T>::request_undelegate(candidate, delegator, pool, amount)
610
        }
611

            
612
        #[pallet::call_index(4)]
613
        #[pallet::weight(T::WeightInfo::claim_manual_rewards(pairs.len() as u32))]
614
        pub fn claim_manual_rewards(
615
            origin: OriginFor<T>,
616
            pairs: Vec<(Candidate<T>, Delegator<T>)>,
617
        ) -> DispatchResultWithPostInfo {
618
            // We don't care about the sender.
619
            let _ = ensure_signed(origin)?;
620

            
621
            Calls::<T>::claim_manual_rewards(&pairs)
622
        }
623

            
624
        #[pallet::call_index(5)]
625
        #[pallet::weight(T::WeightInfo::update_candidate_position(candidates.len() as u32))]
626
        pub fn update_candidate_position(
627
            origin: OriginFor<T>,
628
            candidates: Vec<Candidate<T>>,
629
2
        ) -> DispatchResultWithPostInfo {
630
2
            // We don't care about the sender.
631
2
            let _ = ensure_signed(origin)?;
632

            
633
2
            Calls::<T>::update_candidate_position(&candidates)
634
        }
635

            
636
        #[pallet::call_index(6)]
637
        #[pallet::weight(T::WeightInfo::swap_pool())]
638
        pub fn swap_pool(
639
            origin: OriginFor<T>,
640
            candidate: Candidate<T>,
641
            source_pool: TargetPool,
642
            amount: SharesOrStake<T::Balance>,
643
16
        ) -> DispatchResultWithPostInfo {
644
16
            let delegator = ensure_signed(origin)?;
645

            
646
16
            Calls::<T>::swap_pool(candidate, delegator, source_pool, amount)
647
        }
648
    }
649

            
650
    impl<T: Config> Pallet<T> {
651
4
        pub fn computed_stake(
652
4
            candidate: Candidate<T>,
653
4
            delegator: Delegator<T>,
654
4
            pool: AllTargetPool,
655
4
        ) -> Option<T::Balance> {
656
            use pools::Pool;
657
4
            match pool {
658
                AllTargetPool::Joining => {
659
                    pools::Joining::<T>::computed_stake(&candidate, &delegator)
660
                }
661
                AllTargetPool::AutoCompounding => {
662
2
                    pools::AutoCompounding::<T>::computed_stake(&candidate, &delegator)
663
                }
664
                AllTargetPool::ManualRewards => {
665
2
                    pools::ManualRewards::<T>::computed_stake(&candidate, &delegator)
666
                }
667
                AllTargetPool::Leaving => {
668
                    pools::Leaving::<T>::computed_stake(&candidate, &delegator)
669
                }
670
            }
671
4
            .ok()
672
4
            .map(|x| x.0)
673
4
        }
674
    }
675

            
676
    impl<T: Config> tp_traits::DistributeRewards<Candidate<T>, CreditOf<T>> for Pallet<T> {
677
1045
        fn distribute_rewards(
678
1045
            candidate: Candidate<T>,
679
1045
            rewards: CreditOf<T>,
680
1045
        ) -> DispatchResultWithPostInfo {
681
1045
            pools::distribute_rewards::<T>(&candidate, rewards)
682
1045
        }
683
    }
684
}