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
    frame_support::{
27
        dispatch::DispatchErrorWithPostInfo,
28
        pallet_prelude::*,
29
        traits::{
30
            fungible::{Mutate, MutateHold},
31
            tokens::{Precision, Preservation},
32
        },
33
    },
34
    sp_runtime::traits::{CheckedSub, Zero},
35
    sp_std::vec::Vec,
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
6
            )?;
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
2
            )?;
100
2
            T::Currency::transfer(
101
2
                &delegator,
102
2
                &T::StakingAccount::get(),
103
2
                diff,
104
2
                Preservation::Preserve,
105
2
            )?;
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
258
    pub fn request_delegate(
114
258
        candidate: Candidate<T>,
115
258
        delegator: Delegator<T>,
116
258
        pool: ActivePoolKind,
117
258
        stake: T::Balance,
118
258
    ) -> DispatchResultWithPostInfo {
119
258
        ensure!(
120
258
            !PausePoolsExtrinsics::<T>::get(),
121
1
            Error::<T>::PoolsExtrinsicsArePaused
122
        );
123

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

            
126
        // Convert stake into joining shares quantity.
127
255
        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
255
        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
255
        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
255
        T::Currency::hold(&HoldReason::PooledStake.into(), &delegator, stake.0)?;
139
253
        pools::Joining::<T>::increase_hold(&candidate, &delegator, &stake)?;
140
253
        Candidates::<T>::add_total_stake(&candidate, &stake)?;
141

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

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

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

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

            
171
253
        Ok(().into())
172
258
    }
173

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

            
185
        // Converts amount to shares of the correct pool
186
45
        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
11
            (SharesOrStake::Stake(s), ActivePoolKind::ManualRewards) => {
192
11
                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
45
        if let ActivePoolKind::ManualRewards = pool {
198
12
            Self::claim_manual_rewards(&[(candidate.clone(), delegator.clone())])?;
199
33
        }
200

            
201
        // Destroy shares
202
45
        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
42
        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
42
        let (leaving_stake, dust) = Self::leave_stake(&candidate, &delegator, removed_stake)?;
210

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

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

            
221
42
        Ok(().into())
222
46
    }
223

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

            
232
188
        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
188
                delegator,
238
188
                operation,
239
188
            } = query;
240
188

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

            
243
188
            if value.is_zero() {
244
1
                continue;
245
187
            }
246
187

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

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

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

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

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

            
287
152
        Ok(().into())
288
169
    }
289

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

            
301
        // Convert joining shares into stake.
302
155
        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
155
        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
155
        if let ActivePoolKind::ManualRewards = pool {
310
55
            Self::claim_manual_rewards(&[(candidate.clone(), delegator.clone())])?;
311
100
        }
312

            
313
        // Convert stake into shares quantity.
314
155
        let shares = match pool {
315
            ActivePoolKind::AutoCompounding => {
316
100
                pools::AutoCompounding::<T>::stake_to_shares_or_init(&candidate, stake)?
317
            }
318
            ActivePoolKind::ManualRewards => {
319
55
                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
155
        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
155
        }
335

            
336
        // We create the new shares. It returns the actual amount of stake those shares
337
        // represents (due to rounding).
338
155
        let actually_staked = match pool {
339
            ActivePoolKind::AutoCompounding => {
340
100
                let stake =
341
100
                    pools::AutoCompounding::<T>::add_shares(&candidate, &delegator, shares)?;
342
100
                pools::AutoCompounding::<T>::increase_hold(&candidate, &delegator, &stake)?;
343
100
                stake
344
            }
345
            ActivePoolKind::ManualRewards => {
346
55
                let stake = pools::ManualRewards::<T>::add_shares(&candidate, &delegator, shares)?;
347
55
                pools::ManualRewards::<T>::increase_hold(&candidate, &delegator, &stake)?;
348
55
                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
155
        let release = stake
355
155
            .0
356
155
            .err_sub(&actually_staked.0)
357
155
            .map_err(|_| Error::<T>::MathUnderflow)?;
358
155
        T::Currency::release(
359
155
            &HoldReason::PooledStake.into(),
360
155
            &delegator,
361
155
            release,
362
155
            Precision::Exact,
363
155
        )?;
364
155
        Candidates::<T>::sub_total_stake(&candidate, Stake(release))?;
365

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

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

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

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

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

            
406
        // Convert leaving shares into stake.
407
16
        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
16
        pools::Leaving::<T>::decrease_hold(&candidate, &delegator, &stake)?;
412

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

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

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

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

            
436
89
            if rewards.is_zero() {
437
81
                continue;
438
8
            }
439
8

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

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

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

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

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

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

            
477
        // Converts amount to shares of the correct pool
478
22
        let old_shares = match (amount, source_pool) {
479
4
            (SharesOrStake::Shares(s), _) => s,
480
12
            (SharesOrStake::Stake(s), ActivePoolKind::AutoCompounding) => {
481
12
                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
22
        Self::claim_manual_rewards(&[(candidate.clone(), delegator.clone())])?;
491

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

            
496
        // Convert removed amount to new pool shares.
497
20
        let new_shares = match source_pool {
498
            ActivePoolKind::AutoCompounding => {
499
13
                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
20
        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
20
        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
13
                let stake =
519
13
                    pools::ManualRewards::<T>::add_shares(&candidate, &delegator, new_shares)?;
520
13
                pools::ManualRewards::<T>::increase_hold(&candidate, &delegator, &stake)?;
521
13
                stake
522
            }
523
        };
524

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

            
530
        // The left-over no longer contribute to the election of the candidate.
531
20
        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
20
        let (leaving_stake, dust) = if stake_decrease.is_zero() {
536
16
            (Stake(0u32.into()), Stake(0u32.into()))
537
        } else {
538
4
            Self::leave_stake(&candidate, &delegator, Stake(stake_decrease))?
539
        };
540

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

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

            
555
20
        Ok(().into())
556
23
    }
557

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

            
570
47
                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
6
                    )?;
576
41
                }
577

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

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

            
587
20
                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
3
                    )?;
593
17
                }
594

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

            
598
19
                pools::ManualRewards::<T>::decrease_hold(candidate, delegator, &stake)?;
599
19
                Ok(stake)
600
            }
601
        }
602
67
    }
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
46
    fn leave_stake(
610
46
        candidate: &Candidate<T>,
611
46
        delegator: &Delegator<T>,
612
46
        stake: Stake<T::Balance>,
613
46
    ) -> 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
46
        let leaving_shares = pools::Leaving::<T>::stake_to_shares_or_init(candidate, stake)?;
619
46
        let leaving_stake = pools::Leaving::<T>::add_shares(candidate, delegator, leaving_shares)?;
620
46
        pools::Leaving::<T>::increase_hold(candidate, delegator, &leaving_stake)?;
621

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

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

            
640
46
        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
16
            )?;
647
30
        }
648

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