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
use {
18
    crate::{
19
        candidate::Candidates,
20
        pools::{self, ActivePoolKind, Pool, PoolKind},
21
        traits::Timer,
22
        Candidate, Config, Delegator, Error, Event, HoldReason, Pallet, PausePoolsExtrinsics,
23
        PendingOperationKey, PendingOperationQuery, PendingOperationQueryOf, PendingOperations,
24
        Shares, SharesOrStake, Stake,
25
    },
26
    alloc::vec::Vec,
27
    frame_support::{
28
        dispatch::DispatchErrorWithPostInfo,
29
        pallet_prelude::*,
30
        traits::{
31
            fungible::{Mutate, MutateHold},
32
            tokens::{Precision, Preservation},
33
        },
34
    },
35
    sp_runtime::traits::{CheckedSub, Zero},
36
    tp_maths::{ErrAdd, ErrSub},
37
};
38

            
39
pub struct Calls<T>(PhantomData<T>);
40

            
41
impl<T: Config> Calls<T> {
42
16
    pub fn rebalance_hold(
43
16
        candidate: Candidate<T>,
44
16
        delegator: Delegator<T>,
45
16
        pool: PoolKind,
46
16
    ) -> DispatchResultWithPostInfo {
47
16
        let (held, stake) = match pool {
48
            PoolKind::Joining => {
49
                let held = pools::Joining::<T>::hold(&candidate, &delegator);
50
                let shares = pools::Joining::<T>::shares(&candidate, &delegator);
51
                let stake = pools::Joining::<T>::shares_to_stake(&candidate, shares)?;
52
                pools::Joining::<T>::set_hold(&candidate, &delegator, stake);
53
                (held, stake)
54
            }
55
            PoolKind::AutoCompounding => {
56
10
                let held = pools::AutoCompounding::<T>::hold(&candidate, &delegator);
57
10
                let shares = pools::AutoCompounding::<T>::shares(&candidate, &delegator);
58
10
                let stake = pools::AutoCompounding::<T>::shares_to_stake(&candidate, shares)?;
59
9
                pools::AutoCompounding::<T>::set_hold(&candidate, &delegator, stake);
60
9
                (held, stake)
61
            }
62
            PoolKind::ManualRewards => {
63
6
                let held = pools::ManualRewards::<T>::hold(&candidate, &delegator);
64
6
                let shares = pools::ManualRewards::<T>::shares(&candidate, &delegator);
65
6
                let stake = pools::ManualRewards::<T>::shares_to_stake(&candidate, shares)?;
66
6
                pools::ManualRewards::<T>::set_hold(&candidate, &delegator, stake);
67
6
                (held, stake)
68
            }
69
            PoolKind::Leaving => {
70
                let held = pools::Leaving::<T>::hold(&candidate, &delegator);
71
                let shares = pools::Leaving::<T>::shares(&candidate, &delegator);
72
                let stake = pools::Leaving::<T>::shares_to_stake(&candidate, shares)?;
73
                pools::Leaving::<T>::set_hold(&candidate, &delegator, stake);
74
                (held, stake)
75
            }
76
        };
77

            
78
15
        if stake == held {
79
7
            return Ok(().into());
80
8
        }
81

            
82
8
        if let Some(diff) = stake.0.checked_sub(&held.0) {
83
6
            T::Currency::transfer(
84
6
                &T::StakingAccount::get(),
85
6
                &delegator,
86
6
                diff,
87
6
                Preservation::Preserve,
88
            )?;
89
6
            T::Currency::hold(&HoldReason::PooledStake.into(), &delegator, diff)?;
90
6
            return Ok(().into());
91
2
        }
92

            
93
2
        if let Some(diff) = held.0.checked_sub(&stake.0) {
94
2
            T::Currency::release(
95
2
                &HoldReason::PooledStake.into(),
96
2
                &delegator,
97
2
                diff,
98
2
                Precision::Exact,
99
            )?;
100
2
            T::Currency::transfer(
101
2
                &delegator,
102
2
                &T::StakingAccount::get(),
103
2
                diff,
104
2
                Preservation::Preserve,
105
            )?;
106
2
            return Ok(().into());
107
        }
108

            
109
        // should be unreachable as diff must either be positive or negative
110
        Ok(().into())
111
16
    }
112

            
113
160
    pub fn request_delegate(
114
160
        candidate: Candidate<T>,
115
160
        delegator: Delegator<T>,
116
160
        pool: ActivePoolKind,
117
160
        stake: T::Balance,
118
160
    ) -> DispatchResultWithPostInfo {
119
160
        ensure!(
120
160
            !PausePoolsExtrinsics::<T>::get(),
121
1
            Error::<T>::PoolsExtrinsicsArePaused
122
        );
123

            
124
159
        ensure!(!stake.is_zero(), Error::<T>::StakeMustBeNonZero);
125

            
126
        // Convert stake into joining shares quantity.
127
157
        let shares = pools::Joining::<T>::stake_to_shares_or_init(&candidate, Stake(stake))?;
128

            
129
        // If the amount was stake and is less than the value of 1 share it will round down to
130
        // 0 share. We avoid doing any work for 0 shares.
131
157
        ensure!(!shares.0.is_zero(), Error::<T>::StakeMustBeNonZero);
132

            
133
        // We create the new joining shares. It returns the actual amount of stake those shares
134
        // represents (due to rounding).
135
157
        let stake = pools::Joining::<T>::add_shares(&candidate, &delegator, shares)?;
136

            
137
        // We hold the funds of the delegator and register its stake into the candidate stake.
138
157
        T::Currency::hold(&HoldReason::PooledStake.into(), &delegator, stake.0)?;
139
155
        pools::Joining::<T>::increase_hold(&candidate, &delegator, &stake)?;
140
155
        Candidates::<T>::add_total_stake(&candidate, &stake)?;
141

            
142
        // We create/mutate a request for joining.
143
155
        let now = T::JoiningRequestTimer::now();
144
155
        let operation_key = match pool {
145
121
            ActivePoolKind::AutoCompounding => PendingOperationKey::JoiningAutoCompounding {
146
121
                candidate: candidate.clone(),
147
121
                at: now,
148
121
            },
149
34
            ActivePoolKind::ManualRewards => PendingOperationKey::JoiningManualRewards {
150
34
                candidate: candidate.clone(),
151
34
                at: now,
152
34
            },
153
        };
154

            
155
        // We store/mutate the operation in storage.
156
155
        let operation = PendingOperations::<T>::get(&delegator, &operation_key);
157
155
        let operation = operation
158
155
            .err_add(&shares.0)
159
155
            .map_err(|_| Error::<T>::MathOverflow)?;
160
155
        PendingOperations::<T>::set(&delegator, &operation_key, operation);
161

            
162
155
        pools::check_candidate_consistency::<T>(&candidate)?;
163

            
164
155
        Pallet::<T>::deposit_event(Event::<T>::RequestedDelegate {
165
155
            candidate,
166
155
            delegator,
167
155
            pool,
168
155
            pending: stake.0,
169
155
        });
170

            
171
155
        Ok(().into())
172
160
    }
173

            
174
40
    pub fn request_undelegate(
175
40
        candidate: Candidate<T>,
176
40
        delegator: Delegator<T>,
177
40
        pool: ActivePoolKind,
178
40
        amount: SharesOrStake<T::Balance>,
179
40
    ) -> DispatchResultWithPostInfo {
180
40
        ensure!(
181
40
            !PausePoolsExtrinsics::<T>::get(),
182
1
            Error::<T>::PoolsExtrinsicsArePaused
183
        );
184

            
185
        // Converts amount to shares of the correct pool
186
39
        let shares = match (amount, pool) {
187
2
            (SharesOrStake::Shares(s), _) => s,
188
32
            (SharesOrStake::Stake(s), ActivePoolKind::AutoCompounding) => {
189
32
                pools::AutoCompounding::<T>::stake_to_shares(&candidate, Stake(s))?.0
190
            }
191
5
            (SharesOrStake::Stake(s), ActivePoolKind::ManualRewards) => {
192
5
                pools::ManualRewards::<T>::stake_to_shares(&candidate, Stake(s))?.0
193
            }
194
        };
195

            
196
        // Any change in the amount of Manual Rewards shares requires to claim manual rewards.
197
39
        if let ActivePoolKind::ManualRewards = pool {
198
6
            Self::claim_manual_rewards(&[(candidate.clone(), delegator.clone())])?;
199
33
        }
200

            
201
        // Destroy shares
202
39
        let removed_stake = Self::destroy_shares(&candidate, &delegator, pool, Shares(shares))?;
203

            
204
        // All this stake no longer contribute to the election of the candidate.
205
36
        Candidates::<T>::sub_total_stake(&candidate, removed_stake)?;
206

            
207
        // We proceed with the leaving, which create Leaving shares and request,
208
        // and release the dust from the convertion to Leaving shares.
209
36
        let (leaving_stake, dust) = Self::leave_stake(&candidate, &delegator, removed_stake)?;
210

            
211
36
        pools::check_candidate_consistency::<T>(&candidate)?;
212

            
213
36
        Pallet::<T>::deposit_event(Event::<T>::RequestedUndelegate {
214
36
            candidate,
215
36
            delegator,
216
36
            from: pool,
217
36
            pending: leaving_stake.0,
218
36
            released: dust.0,
219
36
        });
220

            
221
36
        Ok(().into())
222
40
    }
223

            
224
121
    pub fn execute_pending_operations(
225
121
        operations: Vec<PendingOperationQueryOf<T>>,
226
121
    ) -> DispatchResultWithPostInfo {
227
121
        ensure!(
228
121
            !PausePoolsExtrinsics::<T>::get(),
229
1
            Error::<T>::PoolsExtrinsicsArePaused
230
        );
231

            
232
122
        for (index, query) in operations.into_iter().enumerate() {
233
            // We deconstruct the query and find the balance associated with it.
234
            // If it is zero it may not exist or have been executed before, thus
235
            // we simply skip it instead of erroring.
236
            let PendingOperationQuery {
237
122
                delegator,
238
122
                operation,
239
122
            } = query;
240

            
241
122
            let value = PendingOperations::<T>::get(&delegator, &operation);
242

            
243
122
            if value.is_zero() {
244
1
                continue;
245
121
            }
246

            
247
121
            match &operation {
248
74
                PendingOperationKey::JoiningAutoCompounding { candidate, at } => {
249
74
                    ensure!(
250
74
                        T::JoiningRequestTimer::is_elapsed(at),
251
4
                        Error::<T>::RequestCannotBeExecuted(index as u16)
252
                    );
253

            
254
70
                    Self::execute_joining(
255
70
                        candidate.clone(),
256
70
                        delegator.clone(),
257
70
                        ActivePoolKind::AutoCompounding,
258
70
                        Shares(value),
259
                    )?;
260
                }
261
32
                PendingOperationKey::JoiningManualRewards { candidate, at } => {
262
32
                    ensure!(
263
32
                        T::JoiningRequestTimer::is_elapsed(at),
264
1
                        Error::<T>::RequestCannotBeExecuted(index as u16)
265
                    );
266

            
267
31
                    Self::execute_joining(
268
31
                        candidate.clone(),
269
31
                        delegator.clone(),
270
31
                        ActivePoolKind::ManualRewards,
271
31
                        Shares(value),
272
                    )?;
273
                }
274
15
                PendingOperationKey::Leaving { candidate, at } => {
275
15
                    ensure!(
276
15
                        T::LeavingRequestTimer::is_elapsed(at),
277
5
                        Error::<T>::RequestCannotBeExecuted(index as u16)
278
                    );
279

            
280
10
                    Self::execute_leaving(candidate.clone(), delegator.clone(), Shares(value))?;
281
                }
282
            }
283

            
284
111
            PendingOperations::<T>::remove(&delegator, &operation);
285
        }
286

            
287
110
        Ok(().into())
288
121
    }
289

            
290
101
    fn execute_joining(
291
101
        candidate: Candidate<T>,
292
101
        delegator: Delegator<T>,
293
101
        pool: ActivePoolKind,
294
101
        joining_shares: Shares<T::Balance>,
295
101
    ) -> DispatchResultWithPostInfo {
296
101
        ensure!(
297
101
            !PausePoolsExtrinsics::<T>::get(),
298
            Error::<T>::PoolsExtrinsicsArePaused
299
        );
300

            
301
        // Convert joining shares into stake.
302
101
        let stake = pools::Joining::<T>::sub_shares(&candidate, &delegator, joining_shares)?;
303

            
304
        // No rewards are distributed to the Joining pools, so there should always
305
        // be enough hold. Thus no need to rebalance.
306
101
        pools::Joining::<T>::decrease_hold(&candidate, &delegator, &stake)?;
307

            
308
        // Any change in the amount of Manual Rewards shares requires to claim manual rewards.
309
101
        if let ActivePoolKind::ManualRewards = pool {
310
31
            Self::claim_manual_rewards(&[(candidate.clone(), delegator.clone())])?;
311
70
        }
312

            
313
        // Convert stake into shares quantity.
314
101
        let shares = match pool {
315
            ActivePoolKind::AutoCompounding => {
316
70
                pools::AutoCompounding::<T>::stake_to_shares_or_init(&candidate, stake)?
317
            }
318
            ActivePoolKind::ManualRewards => {
319
31
                pools::ManualRewards::<T>::stake_to_shares_or_init(&candidate, stake)?
320
            }
321
        };
322

            
323
        // If stake doesn't allow to get at least one share we release all the funds.
324
101
        if shares.0.is_zero() {
325
            T::Currency::release(
326
                &HoldReason::PooledStake.into(),
327
                &delegator,
328
                stake.0,
329
                Precision::Exact,
330
            )?;
331
            Candidates::<T>::sub_total_stake(&candidate, Stake(stake.0))?;
332
            pools::check_candidate_consistency::<T>(&candidate)?;
333
            return Ok(().into());
334
101
        }
335

            
336
        // We create the new shares. It returns the actual amount of stake those shares
337
        // represents (due to rounding).
338
101
        let actually_staked = match pool {
339
            ActivePoolKind::AutoCompounding => {
340
70
                let stake =
341
70
                    pools::AutoCompounding::<T>::add_shares(&candidate, &delegator, shares)?;
342
70
                pools::AutoCompounding::<T>::increase_hold(&candidate, &delegator, &stake)?;
343
70
                stake
344
            }
345
            ActivePoolKind::ManualRewards => {
346
31
                let stake = pools::ManualRewards::<T>::add_shares(&candidate, &delegator, shares)?;
347
31
                pools::ManualRewards::<T>::increase_hold(&candidate, &delegator, &stake)?;
348
31
                stake
349
            }
350
        };
351

            
352
        // We release currency that couldn't be converted to shares due to rounding.
353
        // This thus can reduce slighly the total stake of the candidate.
354
101
        let release = stake
355
101
            .0
356
101
            .err_sub(&actually_staked.0)
357
101
            .map_err(|_| Error::<T>::MathUnderflow)?;
358
101
        T::Currency::release(
359
101
            &HoldReason::PooledStake.into(),
360
101
            &delegator,
361
101
            release,
362
101
            Precision::Exact,
363
        )?;
364
101
        Candidates::<T>::sub_total_stake(&candidate, Stake(release))?;
365

            
366
        // Events
367
101
        let event = match pool {
368
70
            ActivePoolKind::AutoCompounding => Event::<T>::StakedAutoCompounding {
369
70
                candidate: candidate.clone(),
370
70
                delegator: delegator.clone(),
371
70
                shares: shares.0,
372
70
                stake: actually_staked.0,
373
70
            },
374
31
            ActivePoolKind::ManualRewards => Event::<T>::StakedManualRewards {
375
31
                candidate: candidate.clone(),
376
31
                delegator: delegator.clone(),
377
31
                shares: shares.0,
378
31
                stake: actually_staked.0,
379
31
            },
380
        };
381

            
382
101
        pools::check_candidate_consistency::<T>(&candidate)?;
383

            
384
101
        Pallet::<T>::deposit_event(event);
385
101
        Pallet::<T>::deposit_event(Event::<T>::ExecutedDelegate {
386
101
            candidate,
387
101
            delegator,
388
101
            pool,
389
101
            staked: actually_staked.0,
390
101
            released: release,
391
101
        });
392

            
393
101
        Ok(().into())
394
101
    }
395

            
396
10
    fn execute_leaving(
397
10
        candidate: Candidate<T>,
398
10
        delegator: Delegator<T>,
399
10
        leavinig_shares: Shares<T::Balance>,
400
10
    ) -> DispatchResultWithPostInfo {
401
10
        ensure!(
402
10
            !PausePoolsExtrinsics::<T>::get(),
403
            Error::<T>::PoolsExtrinsicsArePaused
404
        );
405

            
406
        // Convert leaving shares into stake.
407
10
        let stake = pools::Leaving::<T>::sub_shares(&candidate, &delegator, leavinig_shares)?;
408

            
409
        // No rewards are distributed to the Leaving pools, so there should always
410
        // be enough hold. Thus no need to rebalance.
411
10
        pools::Leaving::<T>::decrease_hold(&candidate, &delegator, &stake)?;
412

            
413
        // We release the funds and consider them unstaked.
414
10
        T::Currency::release(
415
10
            &HoldReason::PooledStake.into(),
416
10
            &delegator,
417
10
            stake.0,
418
10
            Precision::Exact,
419
        )?;
420

            
421
10
        Pallet::<T>::deposit_event(Event::<T>::ExecutedUndelegate {
422
10
            candidate,
423
10
            delegator,
424
10
            released: stake.0,
425
10
        });
426

            
427
10
        Ok(().into())
428
10
    }
429

            
430
54
    pub fn claim_manual_rewards(
431
54
        pairs: &[(Candidate<T>, Delegator<T>)],
432
54
    ) -> DispatchResultWithPostInfo {
433
107
        for (candidate, delegator) in pairs {
434
53
            let Stake(rewards) = pools::ManualRewards::<T>::claim_rewards(candidate, delegator)?;
435

            
436
53
            if rewards.is_zero() {
437
51
                continue;
438
2
            }
439

            
440
2
            T::Currency::transfer(
441
2
                &T::StakingAccount::get(),
442
2
                delegator,
443
2
                rewards,
444
2
                Preservation::Preserve,
445
            )?;
446

            
447
2
            Pallet::<T>::deposit_event(Event::<T>::ClaimedManualRewards {
448
2
                candidate: candidate.clone(),
449
2
                delegator: delegator.clone(),
450
2
                rewards,
451
2
            });
452
        }
453

            
454
54
        Ok(().into())
455
54
    }
456

            
457
14
    pub fn update_candidate_position(candidates: &[Candidate<T>]) -> DispatchResultWithPostInfo {
458
27
        for candidate in candidates {
459
13
            let stake = Candidates::<T>::total_stake(candidate);
460
13
            Candidates::<T>::update_total_stake(candidate, stake)?;
461
        }
462

            
463
14
        Ok(().into())
464
14
    }
465

            
466
17
    pub fn swap_pool(
467
17
        candidate: Candidate<T>,
468
17
        delegator: Delegator<T>,
469
17
        source_pool: ActivePoolKind,
470
17
        amount: SharesOrStake<T::Balance>,
471
17
    ) -> DispatchResultWithPostInfo {
472
17
        ensure!(
473
17
            !PausePoolsExtrinsics::<T>::get(),
474
1
            Error::<T>::PoolsExtrinsicsArePaused
475
        );
476

            
477
        // Converts amount to shares of the correct pool
478
16
        let old_shares = match (amount, source_pool) {
479
4
            (SharesOrStake::Shares(s), _) => s,
480
6
            (SharesOrStake::Stake(s), ActivePoolKind::AutoCompounding) => {
481
6
                pools::AutoCompounding::<T>::stake_to_shares(&candidate, Stake(s))?.0
482
            }
483
6
            (SharesOrStake::Stake(s), ActivePoolKind::ManualRewards) => {
484
6
                pools::ManualRewards::<T>::stake_to_shares(&candidate, Stake(s))?.0
485
            }
486
        };
487

            
488
        // As it will either move in or out of the ManualRewards pool, manual rewards
489
        // needs to be claimed.
490
16
        Self::claim_manual_rewards(&[(candidate.clone(), delegator.clone())])?;
491

            
492
        // Destroy shares from the old pool.
493
14
        let removed_stake =
494
16
            Self::destroy_shares(&candidate, &delegator, source_pool, Shares(old_shares))?;
495

            
496
        // Convert removed amount to new pool shares.
497
14
        let new_shares = match source_pool {
498
            ActivePoolKind::AutoCompounding => {
499
7
                pools::ManualRewards::<T>::stake_to_shares_or_init(&candidate, removed_stake)?
500
            }
501
            ActivePoolKind::ManualRewards => {
502
7
                pools::AutoCompounding::<T>::stake_to_shares_or_init(&candidate, removed_stake)?
503
            }
504
        };
505

            
506
14
        ensure!(!new_shares.0.is_zero(), Error::<T>::SwapResultsInZeroShares);
507

            
508
        // We create new shares in the new pool. It returns the actual amount of stake those shares
509
        // represents (due to rounding).
510
14
        let actually_staked = match source_pool {
511
            ActivePoolKind::ManualRewards => {
512
7
                let stake =
513
7
                    pools::AutoCompounding::<T>::add_shares(&candidate, &delegator, new_shares)?;
514
7
                pools::AutoCompounding::<T>::increase_hold(&candidate, &delegator, &stake)?;
515
7
                stake
516
            }
517
            ActivePoolKind::AutoCompounding => {
518
7
                let stake =
519
7
                    pools::ManualRewards::<T>::add_shares(&candidate, &delegator, new_shares)?;
520
7
                pools::ManualRewards::<T>::increase_hold(&candidate, &delegator, &stake)?;
521
7
                stake
522
            }
523
        };
524

            
525
14
        let stake_decrease = removed_stake
526
14
            .0
527
14
            .err_sub(&actually_staked.0)
528
14
            .map_err(Error::<T>::from)?;
529

            
530
        // The left-over no longer contribute to the election of the candidate.
531
14
        Candidates::<T>::sub_total_stake(&candidate, Stake(stake_decrease))?;
532

            
533
        // We proceed with the leaving, which create Leaving shares and request,
534
        // and release the dust from the convertion to Leaving shares.
535
14
        let (leaving_stake, dust) = if stake_decrease.is_zero() {
536
10
            (Stake(0u32.into()), Stake(0u32.into()))
537
        } else {
538
4
            Self::leave_stake(&candidate, &delegator, Stake(stake_decrease))?
539
        };
540

            
541
14
        pools::check_candidate_consistency::<T>(&candidate)?;
542

            
543
14
        Pallet::<T>::deposit_event(Event::<T>::SwappedPool {
544
14
            candidate: candidate.clone(),
545
14
            delegator: delegator.clone(),
546
14
            source_pool,
547
14
            source_shares: old_shares,
548
14
            source_stake: removed_stake.0,
549
14
            target_shares: new_shares.0,
550
14
            target_stake: actually_staked.0,
551
14
            pending_leaving: leaving_stake.0,
552
14
            released: dust.0,
553
14
        });
554

            
555
14
        Ok(().into())
556
17
    }
557

            
558
    /// Destory ManualReward or AutoCompounding shares while performing hold rebalancing if
559
    /// necessary.
560
55
    fn destroy_shares(
561
55
        candidate: &Candidate<T>,
562
55
        delegator: &Delegator<T>,
563
55
        pool: ActivePoolKind,
564
55
        shares: Shares<T::Balance>,
565
55
    ) -> Result<Stake<T::Balance>, DispatchErrorWithPostInfo> {
566
55
        match pool {
567
            ActivePoolKind::AutoCompounding => {
568
41
                let stake = pools::AutoCompounding::<T>::shares_to_stake(candidate, shares)?;
569

            
570
41
                if stake.0 > pools::AutoCompounding::<T>::hold(candidate, delegator).0 {
571
6
                    Self::rebalance_hold(
572
6
                        candidate.clone(),
573
6
                        delegator.clone(),
574
6
                        PoolKind::AutoCompounding,
575
                    )?;
576
35
                }
577

            
578
                // This should be the same `stake` as before.
579
41
                let stake = pools::AutoCompounding::<T>::sub_shares(candidate, delegator, shares)?;
580

            
581
37
                pools::AutoCompounding::<T>::decrease_hold(candidate, delegator, &stake)?;
582
37
                Ok(stake)
583
            }
584
            ActivePoolKind::ManualRewards => {
585
14
                let stake = pools::ManualRewards::<T>::shares_to_stake(candidate, shares)?;
586

            
587
14
                if stake.0 > pools::ManualRewards::<T>::hold(candidate, delegator).0 {
588
3
                    Self::rebalance_hold(
589
3
                        candidate.clone(),
590
3
                        delegator.clone(),
591
3
                        PoolKind::ManualRewards,
592
                    )?;
593
11
                }
594

            
595
                // This should be the same `stake` as before.
596
14
                let stake = pools::ManualRewards::<T>::sub_shares(candidate, delegator, shares)?;
597

            
598
13
                pools::ManualRewards::<T>::decrease_hold(candidate, delegator, &stake)?;
599
13
                Ok(stake)
600
            }
601
        }
602
55
    }
603

            
604
    /// Perform the leaving proceduce with provided stake, which will create
605
    /// Leaving shares and request, and release the rounding dust. It DOES NOT
606
    /// destroy shares in other pools.
607
    /// Returns a tuple of the amount of stake in the leaving pool and the dust
608
    /// that was released.
609
40
    fn leave_stake(
610
40
        candidate: &Candidate<T>,
611
40
        delegator: &Delegator<T>,
612
40
        stake: Stake<T::Balance>,
613
40
    ) -> Result<(Stake<T::Balance>, Stake<T::Balance>), DispatchErrorWithPostInfo> {
614
        // Create leaving shares.
615
        // As with all pools there will be some rounding error, this amount
616
        // should be small enough so that it is safe to directly release it
617
        // in the delegator account.
618
40
        let leaving_shares = pools::Leaving::<T>::stake_to_shares_or_init(candidate, stake)?;
619
40
        let leaving_stake = pools::Leaving::<T>::add_shares(candidate, delegator, leaving_shares)?;
620
40
        pools::Leaving::<T>::increase_hold(candidate, delegator, &leaving_stake)?;
621

            
622
        // We create/mutate a request for leaving.
623
40
        let now = T::LeavingRequestTimer::now();
624
40
        let operation_key = PendingOperationKey::Leaving {
625
40
            candidate: candidate.clone(),
626
40
            at: now,
627
40
        };
628
40
        let operation = PendingOperations::<T>::get(delegator, &operation_key);
629
40
        let operation = operation
630
40
            .err_add(&leaving_shares.0)
631
40
            .map_err(|_| Error::<T>::MathOverflow)?;
632
40
        PendingOperations::<T>::set(delegator, &operation_key, operation);
633

            
634
        // We release the dust if non-zero.
635
40
        let dust = stake
636
40
            .0
637
40
            .err_sub(&leaving_stake.0)
638
40
            .map_err(Error::<T>::from)?;
639

            
640
40
        if !dust.is_zero() {
641
16
            T::Currency::release(
642
16
                &HoldReason::PooledStake.into(),
643
16
                delegator,
644
16
                dust,
645
16
                Precision::Exact,
646
            )?;
647
24
        }
648

            
649
40
        Ok((leaving_stake, Stake(dust)))
650
40
    }
651
}