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, Pool},
21
        traits::Timer,
22
        AllTargetPool, Candidate, Config, Delegator, Error, Event, HoldReason, Pallet,
23
        PendingOperationKey, PendingOperationQuery, PendingOperationQueryOf, PendingOperations,
24
        Shares, SharesOrStake, Stake, TargetPool,
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
13
    pub fn rebalance_hold(
43
13
        candidate: Candidate<T>,
44
13
        delegator: Delegator<T>,
45
13
        pool: AllTargetPool,
46
13
    ) -> DispatchResultWithPostInfo {
47
13
        let (held, stake) = match pool {
48
            AllTargetPool::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
            AllTargetPool::AutoCompounding => {
56
7
                let held = pools::AutoCompounding::<T>::hold(&candidate, &delegator);
57
7
                let shares = pools::AutoCompounding::<T>::shares(&candidate, &delegator);
58
7
                let stake = pools::AutoCompounding::<T>::shares_to_stake(&candidate, shares)?;
59
7
                pools::AutoCompounding::<T>::set_hold(&candidate, &delegator, stake);
60
7
                (held, stake)
61
            }
62
            AllTargetPool::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
            AllTargetPool::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
13
        if stake == held {
79
5
            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
13
    }
112

            
113
161
    pub fn request_delegate(
114
161
        candidate: Candidate<T>,
115
161
        delegator: Delegator<T>,
116
161
        pool: TargetPool,
117
161
        stake: T::Balance,
118
161
    ) -> DispatchResultWithPostInfo {
119
161
        ensure!(!stake.is_zero(), Error::<T>::StakeMustBeNonZero);
120

            
121
        // Convert stake into joining shares quantity.
122
159
        let shares = pools::Joining::<T>::stake_to_shares_or_init(&candidate, Stake(stake))?;
123

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

            
128
        // We create the new joining shares. It returns the actual amount of stake those shares
129
        // represents (due to rounding).
130
159
        let stake = pools::Joining::<T>::add_shares(&candidate, &delegator, shares)?;
131

            
132
        // We hold the funds of the delegator and register its stake into the candidate stake.
133
159
        T::Currency::hold(&HoldReason::PooledStake.into(), &delegator, stake.0)?;
134
157
        pools::Joining::<T>::increase_hold(&candidate, &delegator, &stake)?;
135
157
        Candidates::<T>::add_total_stake(&candidate, &stake)?;
136

            
137
        // We create/mutate a request for joining.
138
157
        let now = T::JoiningRequestTimer::now();
139
157
        let operation_key = match pool {
140
92
            TargetPool::AutoCompounding => PendingOperationKey::JoiningAutoCompounding {
141
92
                candidate: candidate.clone(),
142
92
                at: now,
143
92
            },
144
65
            TargetPool::ManualRewards => PendingOperationKey::JoiningManualRewards {
145
65
                candidate: candidate.clone(),
146
65
                at: now,
147
65
            },
148
        };
149

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

            
157
157
        pools::check_candidate_consistency::<T>(&candidate)?;
158

            
159
157
        Pallet::<T>::deposit_event(Event::<T>::RequestedDelegate {
160
157
            candidate,
161
157
            delegator,
162
157
            pool,
163
157
            pending: stake.0,
164
157
        });
165
157

            
166
157
        Ok(().into())
167
161
    }
168

            
169
25
    pub fn request_undelegate(
170
25
        candidate: Candidate<T>,
171
25
        delegator: Delegator<T>,
172
25
        pool: TargetPool,
173
25
        amount: SharesOrStake<T::Balance>,
174
25
    ) -> DispatchResultWithPostInfo {
175
        // Converts amount to shares of the correct pool
176
25
        let shares = match (amount, pool) {
177
2
            (SharesOrStake::Shares(s), _) => s,
178
14
            (SharesOrStake::Stake(s), TargetPool::AutoCompounding) => {
179
14
                pools::AutoCompounding::<T>::stake_to_shares(&candidate, Stake(s))?.0
180
            }
181
9
            (SharesOrStake::Stake(s), TargetPool::ManualRewards) => {
182
9
                pools::ManualRewards::<T>::stake_to_shares(&candidate, Stake(s))?.0
183
            }
184
        };
185

            
186
        // Any change in the amount of Manual Rewards shares requires to claim manual rewards.
187
25
        if let TargetPool::ManualRewards = pool {
188
10
            Self::claim_manual_rewards(&[(candidate.clone(), delegator.clone())])?;
189
15
        }
190

            
191
        // Destroy shares
192
25
        let removed_stake = Self::destroy_shares(&candidate, &delegator, pool, Shares(shares))?;
193

            
194
        // All this stake no longer contribute to the election of the candidate.
195
24
        Candidates::<T>::sub_total_stake(&candidate, removed_stake)?;
196

            
197
        // We proceed with the leaving, which create Leaving shares and request,
198
        // and release the dust from the convertion to Leaving shares.
199
24
        let (leaving_stake, dust) = Self::leave_stake(&candidate, &delegator, removed_stake)?;
200

            
201
24
        pools::check_candidate_consistency::<T>(&candidate)?;
202

            
203
24
        Pallet::<T>::deposit_event(Event::<T>::RequestedUndelegate {
204
24
            candidate,
205
24
            delegator,
206
24
            from: pool,
207
24
            pending: leaving_stake.0,
208
24
            released: dust.0,
209
24
        });
210
24

            
211
24
        Ok(().into())
212
25
    }
213

            
214
114
    pub fn execute_pending_operations(
215
114
        operations: Vec<PendingOperationQueryOf<T>>,
216
114
    ) -> DispatchResultWithPostInfo {
217
124
        for (index, query) in operations.into_iter().enumerate() {
218
            // We deconstruct the query and find the balance associated with it.
219
            // If it is zero it may not exist or have been executed before, thus
220
            // we simply skip it instead of erroring.
221
            let PendingOperationQuery {
222
124
                delegator,
223
124
                operation,
224
124
            } = query;
225
124

            
226
124
            let value = PendingOperations::<T>::get(&delegator, &operation);
227
124

            
228
124
            if value.is_zero() {
229
1
                continue;
230
123
            }
231
123

            
232
123
            match &operation {
233
65
                PendingOperationKey::JoiningAutoCompounding { candidate, at } => {
234
65
                    ensure!(
235
65
                        T::JoiningRequestTimer::is_elapsed(at),
236
6
                        Error::<T>::RequestCannotBeExecuted(index as u16)
237
                    );
238

            
239
59
                    Self::execute_joining(
240
59
                        candidate.clone(),
241
59
                        delegator.clone(),
242
59
                        TargetPool::AutoCompounding,
243
59
                        Shares(value),
244
59
                    )?;
245
                }
246
43
                PendingOperationKey::JoiningManualRewards { candidate, at } => {
247
43
                    ensure!(
248
43
                        T::JoiningRequestTimer::is_elapsed(at),
249
1
                        Error::<T>::RequestCannotBeExecuted(index as u16)
250
                    );
251

            
252
42
                    Self::execute_joining(
253
42
                        candidate.clone(),
254
42
                        delegator.clone(),
255
42
                        TargetPool::ManualRewards,
256
42
                        Shares(value),
257
42
                    )?;
258
                }
259
15
                PendingOperationKey::Leaving { candidate, at } => {
260
15
                    ensure!(
261
15
                        T::LeavingRequestTimer::is_elapsed(at),
262
3
                        Error::<T>::RequestCannotBeExecuted(index as u16)
263
                    );
264

            
265
12
                    Self::execute_leaving(candidate.clone(), delegator.clone(), Shares(value))?;
266
                }
267
            }
268

            
269
113
            PendingOperations::<T>::remove(&delegator, &operation);
270
        }
271

            
272
104
        Ok(().into())
273
114
    }
274

            
275
101
    fn execute_joining(
276
101
        candidate: Candidate<T>,
277
101
        delegator: Delegator<T>,
278
101
        pool: TargetPool,
279
101
        joining_shares: Shares<T::Balance>,
280
101
    ) -> DispatchResultWithPostInfo {
281
        // Convert joining shares into stake.
282
101
        let stake = pools::Joining::<T>::sub_shares(&candidate, &delegator, joining_shares)?;
283

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

            
288
        // Any change in the amount of Manual Rewards shares requires to claim manual rewards.
289
101
        if let TargetPool::ManualRewards = pool {
290
42
            Self::claim_manual_rewards(&[(candidate.clone(), delegator.clone())])?;
291
59
        }
292

            
293
        // Convert stake into shares quantity.
294
101
        let shares = match pool {
295
            TargetPool::AutoCompounding => {
296
59
                pools::AutoCompounding::<T>::stake_to_shares_or_init(&candidate, stake)?
297
            }
298
            TargetPool::ManualRewards => {
299
42
                pools::ManualRewards::<T>::stake_to_shares_or_init(&candidate, stake)?
300
            }
301
        };
302

            
303
        // If stake doesn't allow to get at least one share we release all the funds.
304
101
        if shares.0.is_zero() {
305
            T::Currency::release(
306
                &HoldReason::PooledStake.into(),
307
                &delegator,
308
                stake.0,
309
                Precision::Exact,
310
            )?;
311
            Candidates::<T>::sub_total_stake(&candidate, Stake(stake.0))?;
312
            pools::check_candidate_consistency::<T>(&candidate)?;
313
            return Ok(().into());
314
101
        }
315

            
316
        // We create the new shares. It returns the actual amount of stake those shares
317
        // represents (due to rounding).
318
101
        let actually_staked = match pool {
319
            TargetPool::AutoCompounding => {
320
59
                let stake =
321
59
                    pools::AutoCompounding::<T>::add_shares(&candidate, &delegator, shares)?;
322
59
                pools::AutoCompounding::<T>::increase_hold(&candidate, &delegator, &stake)?;
323
59
                stake
324
            }
325
            TargetPool::ManualRewards => {
326
42
                let stake = pools::ManualRewards::<T>::add_shares(&candidate, &delegator, shares)?;
327
42
                pools::ManualRewards::<T>::increase_hold(&candidate, &delegator, &stake)?;
328
42
                stake
329
            }
330
        };
331

            
332
        // We release currency that couldn't be converted to shares due to rounding.
333
        // This thus can reduce slighly the total stake of the candidate.
334
101
        let release = stake
335
101
            .0
336
101
            .err_sub(&actually_staked.0)
337
101
            .map_err(|_| Error::<T>::MathUnderflow)?;
338
101
        T::Currency::release(
339
101
            &HoldReason::PooledStake.into(),
340
101
            &delegator,
341
101
            release,
342
101
            Precision::Exact,
343
101
        )?;
344
101
        Candidates::<T>::sub_total_stake(&candidate, Stake(release))?;
345

            
346
        // Events
347
101
        let event = match pool {
348
59
            TargetPool::AutoCompounding => Event::<T>::StakedAutoCompounding {
349
59
                candidate: candidate.clone(),
350
59
                delegator: delegator.clone(),
351
59
                shares: shares.0,
352
59
                stake: actually_staked.0,
353
59
            },
354
42
            TargetPool::ManualRewards => Event::<T>::StakedManualRewards {
355
42
                candidate: candidate.clone(),
356
42
                delegator: delegator.clone(),
357
42
                shares: shares.0,
358
42
                stake: actually_staked.0,
359
42
            },
360
        };
361

            
362
101
        pools::check_candidate_consistency::<T>(&candidate)?;
363

            
364
101
        Pallet::<T>::deposit_event(event);
365
101
        Pallet::<T>::deposit_event(Event::<T>::ExecutedDelegate {
366
101
            candidate,
367
101
            delegator,
368
101
            pool,
369
101
            staked: actually_staked.0,
370
101
            released: release,
371
101
        });
372
101

            
373
101
        Ok(().into())
374
101
    }
375

            
376
12
    fn execute_leaving(
377
12
        candidate: Candidate<T>,
378
12
        delegator: Delegator<T>,
379
12
        leavinig_shares: Shares<T::Balance>,
380
12
    ) -> DispatchResultWithPostInfo {
381
        // Convert leaving shares into stake.
382
12
        let stake = pools::Leaving::<T>::sub_shares(&candidate, &delegator, leavinig_shares)?;
383

            
384
        // No rewards are distributed to the Leaving pools, so there should always
385
        // be enough hold. Thus no need to rebalance.
386
12
        pools::Leaving::<T>::decrease_hold(&candidate, &delegator, &stake)?;
387

            
388
        // We release the funds and consider them unstaked.
389
12
        T::Currency::release(
390
12
            &HoldReason::PooledStake.into(),
391
12
            &delegator,
392
12
            stake.0,
393
12
            Precision::Exact,
394
12
        )?;
395

            
396
12
        Pallet::<T>::deposit_event(Event::<T>::ExecutedUndelegate {
397
12
            candidate,
398
12
            delegator,
399
12
            released: stake.0,
400
12
        });
401
12

            
402
12
        Ok(().into())
403
12
    }
404

            
405
66
    pub fn claim_manual_rewards(
406
66
        pairs: &[(Candidate<T>, Delegator<T>)],
407
66
    ) -> DispatchResultWithPostInfo {
408
132
        for (candidate, delegator) in pairs {
409
66
            let Stake(rewards) = pools::ManualRewards::<T>::claim_rewards(candidate, delegator)?;
410

            
411
66
            if rewards.is_zero() {
412
60
                continue;
413
6
            }
414
6

            
415
6
            T::Currency::transfer(
416
6
                &T::StakingAccount::get(),
417
6
                delegator,
418
6
                rewards,
419
6
                Preservation::Preserve,
420
6
            )?;
421

            
422
6
            Pallet::<T>::deposit_event(Event::<T>::ClaimedManualRewards {
423
6
                candidate: candidate.clone(),
424
6
                delegator: delegator.clone(),
425
6
                rewards,
426
6
            });
427
        }
428

            
429
66
        Ok(().into())
430
66
    }
431

            
432
2
    pub fn update_candidate_position(candidates: &[Candidate<T>]) -> DispatchResultWithPostInfo {
433
4
        for candidate in candidates {
434
2
            let stake = Candidates::<T>::total_stake(candidate);
435
2
            Candidates::<T>::update_total_stake(candidate, stake)?;
436
        }
437

            
438
2
        Ok(().into())
439
2
    }
440

            
441
14
    pub fn swap_pool(
442
14
        candidate: Candidate<T>,
443
14
        delegator: Delegator<T>,
444
14
        source_pool: TargetPool,
445
14
        amount: SharesOrStake<T::Balance>,
446
14
    ) -> DispatchResultWithPostInfo {
447
        // Converts amount to shares of the correct pool
448
14
        let old_shares = match (amount, source_pool) {
449
4
            (SharesOrStake::Shares(s), _) => s,
450
7
            (SharesOrStake::Stake(s), TargetPool::AutoCompounding) => {
451
7
                pools::AutoCompounding::<T>::stake_to_shares(&candidate, Stake(s))?.0
452
            }
453
3
            (SharesOrStake::Stake(s), TargetPool::ManualRewards) => {
454
3
                pools::ManualRewards::<T>::stake_to_shares(&candidate, Stake(s))?.0
455
            }
456
        };
457

            
458
        // As it will either move in or out of the ManualRewards pool, manual rewards
459
        // needs to be claimed.
460
14
        Self::claim_manual_rewards(&[(candidate.clone(), delegator.clone())])?;
461

            
462
        // Destroy shares from the old pool.
463
12
        let removed_stake =
464
14
            Self::destroy_shares(&candidate, &delegator, source_pool, Shares(old_shares))?;
465

            
466
        // Convert removed amount to new pool shares.
467
12
        let new_shares = match source_pool {
468
            TargetPool::AutoCompounding => {
469
8
                pools::ManualRewards::<T>::stake_to_shares_or_init(&candidate, removed_stake)?
470
            }
471
            TargetPool::ManualRewards => {
472
4
                pools::AutoCompounding::<T>::stake_to_shares_or_init(&candidate, removed_stake)?
473
            }
474
        };
475

            
476
12
        ensure!(!new_shares.0.is_zero(), Error::<T>::SwapResultsInZeroShares);
477

            
478
        // We create new shares in the new pool. It returns the actual amount of stake those shares
479
        // represents (due to rounding).
480
12
        let actually_staked = match source_pool {
481
            TargetPool::ManualRewards => {
482
4
                let stake =
483
4
                    pools::AutoCompounding::<T>::add_shares(&candidate, &delegator, new_shares)?;
484
4
                pools::AutoCompounding::<T>::increase_hold(&candidate, &delegator, &stake)?;
485
4
                stake
486
            }
487
            TargetPool::AutoCompounding => {
488
8
                let stake =
489
8
                    pools::ManualRewards::<T>::add_shares(&candidate, &delegator, new_shares)?;
490
8
                pools::ManualRewards::<T>::increase_hold(&candidate, &delegator, &stake)?;
491
8
                stake
492
            }
493
        };
494

            
495
12
        let stake_decrease = removed_stake
496
12
            .0
497
12
            .err_sub(&actually_staked.0)
498
12
            .map_err(Error::<T>::from)?;
499

            
500
        // The left-over no longer contribute to the election of the candidate.
501
12
        Candidates::<T>::sub_total_stake(&candidate, Stake(stake_decrease))?;
502

            
503
        // We proceed with the leaving, which create Leaving shares and request,
504
        // and release the dust from the convertion to Leaving shares.
505
12
        let (leaving_stake, dust) = if stake_decrease.is_zero() {
506
8
            (Stake(0u32.into()), Stake(0u32.into()))
507
        } else {
508
4
            Self::leave_stake(&candidate, &delegator, Stake(stake_decrease))?
509
        };
510

            
511
12
        pools::check_candidate_consistency::<T>(&candidate)?;
512

            
513
12
        Pallet::<T>::deposit_event(Event::<T>::SwappedPool {
514
12
            candidate: candidate.clone(),
515
12
            delegator: delegator.clone(),
516
12
            source_pool,
517
12
            source_shares: old_shares,
518
12
            source_stake: removed_stake.0,
519
12
            target_shares: new_shares.0,
520
12
            target_stake: actually_staked.0,
521
12
            pending_leaving: leaving_stake.0,
522
12
            released: dust.0,
523
12
        });
524
12

            
525
12
        Ok(().into())
526
14
    }
527

            
528
    /// Destory ManualReward or AutoCompounding shares while performing hold rebalancing if
529
    /// necessary.
530
39
    fn destroy_shares(
531
39
        candidate: &Candidate<T>,
532
39
        delegator: &Delegator<T>,
533
39
        pool: TargetPool,
534
39
        shares: Shares<T::Balance>,
535
39
    ) -> Result<Stake<T::Balance>, DispatchErrorWithPostInfo> {
536
39
        match pool {
537
            TargetPool::AutoCompounding => {
538
24
                let stake = pools::AutoCompounding::<T>::shares_to_stake(candidate, shares)?;
539

            
540
24
                if stake.0 > pools::AutoCompounding::<T>::hold(candidate, delegator).0 {
541
4
                    Self::rebalance_hold(
542
4
                        candidate.clone(),
543
4
                        delegator.clone(),
544
4
                        AllTargetPool::AutoCompounding,
545
4
                    )?;
546
20
                }
547

            
548
                // This should be the same `stake` as before.
549
24
                let stake = pools::AutoCompounding::<T>::sub_shares(candidate, delegator, shares)?;
550

            
551
22
                pools::AutoCompounding::<T>::decrease_hold(candidate, delegator, &stake)?;
552
22
                Ok(stake)
553
            }
554
            TargetPool::ManualRewards => {
555
15
                let stake = pools::ManualRewards::<T>::shares_to_stake(candidate, shares)?;
556

            
557
15
                if stake.0 > pools::ManualRewards::<T>::hold(candidate, delegator).0 {
558
3
                    Self::rebalance_hold(
559
3
                        candidate.clone(),
560
3
                        delegator.clone(),
561
3
                        AllTargetPool::ManualRewards,
562
3
                    )?;
563
12
                }
564

            
565
                // This should be the same `stake` as before.
566
15
                let stake = pools::ManualRewards::<T>::sub_shares(candidate, delegator, shares)?;
567

            
568
14
                pools::ManualRewards::<T>::decrease_hold(candidate, delegator, &stake)?;
569
14
                Ok(stake)
570
            }
571
        }
572
39
    }
573

            
574
    /// Perform the leaving proceduce with provided stake, which will create
575
    /// Leaving shares and request, and release the rounding dust. It DOES NOT
576
    /// destroy shares in other pools.
577
    /// Returns a tuple of the amount of stake in the leaving pool and the dust
578
    /// that was released.
579
28
    fn leave_stake(
580
28
        candidate: &Candidate<T>,
581
28
        delegator: &Delegator<T>,
582
28
        stake: Stake<T::Balance>,
583
28
    ) -> Result<(Stake<T::Balance>, Stake<T::Balance>), DispatchErrorWithPostInfo> {
584
        // Create leaving shares.
585
        // As with all pools there will be some rounding error, this amount
586
        // should be small enough so that it is safe to directly release it
587
        // in the delegator account.
588
28
        let leaving_shares = pools::Leaving::<T>::stake_to_shares_or_init(candidate, stake)?;
589
28
        let leaving_stake = pools::Leaving::<T>::add_shares(candidate, delegator, leaving_shares)?;
590
28
        pools::Leaving::<T>::increase_hold(candidate, delegator, &leaving_stake)?;
591

            
592
        // We create/mutate a request for leaving.
593
28
        let now = T::LeavingRequestTimer::now();
594
28
        let operation_key = PendingOperationKey::Leaving {
595
28
            candidate: candidate.clone(),
596
28
            at: now,
597
28
        };
598
28
        let operation = PendingOperations::<T>::get(delegator, &operation_key);
599
28
        let operation = operation
600
28
            .err_add(&leaving_shares.0)
601
28
            .map_err(|_| Error::<T>::MathOverflow)?;
602
28
        PendingOperations::<T>::set(delegator, &operation_key, operation);
603

            
604
        // We release the dust if non-zero.
605
28
        let dust = stake
606
28
            .0
607
28
            .err_sub(&leaving_stake.0)
608
28
            .map_err(Error::<T>::from)?;
609

            
610
28
        if !dust.is_zero() {
611
16
            T::Currency::release(
612
16
                &HoldReason::PooledStake.into(),
613
16
                delegator,
614
16
                dust,
615
16
                Precision::Exact,
616
16
            )?;
617
12
        }
618

            
619
28
        Ok((leaving_stake, Stake(dust)))
620
28
    }
621
}