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

            
121
        // Convert stake into joining shares quantity.
122
189
        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
189
        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
189
        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
189
        T::Currency::hold(&HoldReason::PooledStake.into(), &delegator, stake.0)?;
134
187
        pools::Joining::<T>::increase_hold(&candidate, &delegator, &stake)?;
135
187
        Candidates::<T>::add_total_stake(&candidate, &stake)?;
136

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

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

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

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

            
166
187
        Ok(().into())
167
191
    }
168

            
169
27
    pub fn request_undelegate(
170
27
        candidate: Candidate<T>,
171
27
        delegator: Delegator<T>,
172
27
        pool: TargetPool,
173
27
        amount: SharesOrStake<T::Balance>,
174
27
    ) -> DispatchResultWithPostInfo {
175
        // Converts amount to shares of the correct pool
176
27
        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
11
            (SharesOrStake::Stake(s), TargetPool::ManualRewards) => {
182
11
                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
27
        if let TargetPool::ManualRewards = pool {
188
12
            Self::claim_manual_rewards(&[(candidate.clone(), delegator.clone())])?;
189
15
        }
190

            
191
        // Destroy shares
192
27
        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
26
        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
26
        let (leaving_stake, dust) = Self::leave_stake(&candidate, &delegator, removed_stake)?;
200

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

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

            
211
26
        Ok(().into())
212
27
    }
213

            
214
128
    pub fn execute_pending_operations(
215
128
        operations: Vec<PendingOperationQueryOf<T>>,
216
128
    ) -> DispatchResultWithPostInfo {
217
142
        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
142
                delegator,
223
142
                operation,
224
142
            } = query;
225
142

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

            
228
142
            if value.is_zero() {
229
1
                continue;
230
141
            }
231
141

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

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

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

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

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

            
272
116
        Ok(().into())
273
128
    }
274

            
275
115
    fn execute_joining(
276
115
        candidate: Candidate<T>,
277
115
        delegator: Delegator<T>,
278
115
        pool: TargetPool,
279
115
        joining_shares: Shares<T::Balance>,
280
115
    ) -> DispatchResultWithPostInfo {
281
        // Convert joining shares into stake.
282
115
        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
115
        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
115
        if let TargetPool::ManualRewards = pool {
290
48
            Self::claim_manual_rewards(&[(candidate.clone(), delegator.clone())])?;
291
67
        }
292

            
293
        // Convert stake into shares quantity.
294
115
        let shares = match pool {
295
            TargetPool::AutoCompounding => {
296
67
                pools::AutoCompounding::<T>::stake_to_shares_or_init(&candidate, stake)?
297
            }
298
            TargetPool::ManualRewards => {
299
48
                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
115
        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
115
        }
315

            
316
        // We create the new shares. It returns the actual amount of stake those shares
317
        // represents (due to rounding).
318
115
        let actually_staked = match pool {
319
            TargetPool::AutoCompounding => {
320
67
                let stake =
321
67
                    pools::AutoCompounding::<T>::add_shares(&candidate, &delegator, shares)?;
322
67
                pools::AutoCompounding::<T>::increase_hold(&candidate, &delegator, &stake)?;
323
67
                stake
324
            }
325
            TargetPool::ManualRewards => {
326
48
                let stake = pools::ManualRewards::<T>::add_shares(&candidate, &delegator, shares)?;
327
48
                pools::ManualRewards::<T>::increase_hold(&candidate, &delegator, &stake)?;
328
48
                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
115
        let release = stake
335
115
            .0
336
115
            .err_sub(&actually_staked.0)
337
115
            .map_err(|_| Error::<T>::MathUnderflow)?;
338
115
        T::Currency::release(
339
115
            &HoldReason::PooledStake.into(),
340
115
            &delegator,
341
115
            release,
342
115
            Precision::Exact,
343
115
        )?;
344
115
        Candidates::<T>::sub_total_stake(&candidate, Stake(release))?;
345

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

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

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

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

            
376
14
    fn execute_leaving(
377
14
        candidate: Candidate<T>,
378
14
        delegator: Delegator<T>,
379
14
        leavinig_shares: Shares<T::Balance>,
380
14
    ) -> DispatchResultWithPostInfo {
381
        // Convert leaving shares into stake.
382
14
        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
14
        pools::Leaving::<T>::decrease_hold(&candidate, &delegator, &stake)?;
387

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

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

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

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

            
411
76
            if rewards.is_zero() {
412
68
                continue;
413
8
            }
414
8

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

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

            
429
76
        Ok(().into())
430
76
    }
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
16
    pub fn swap_pool(
442
16
        candidate: Candidate<T>,
443
16
        delegator: Delegator<T>,
444
16
        source_pool: TargetPool,
445
16
        amount: SharesOrStake<T::Balance>,
446
16
    ) -> DispatchResultWithPostInfo {
447
        // Converts amount to shares of the correct pool
448
16
        let old_shares = match (amount, source_pool) {
449
4
            (SharesOrStake::Shares(s), _) => s,
450
9
            (SharesOrStake::Stake(s), TargetPool::AutoCompounding) => {
451
9
                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
16
        Self::claim_manual_rewards(&[(candidate.clone(), delegator.clone())])?;
461

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

            
466
        // Convert removed amount to new pool shares.
467
14
        let new_shares = match source_pool {
468
            TargetPool::AutoCompounding => {
469
10
                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
14
        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
14
        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
10
                let stake =
489
10
                    pools::ManualRewards::<T>::add_shares(&candidate, &delegator, new_shares)?;
490
10
                pools::ManualRewards::<T>::increase_hold(&candidate, &delegator, &stake)?;
491
10
                stake
492
            }
493
        };
494

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

            
500
        // The left-over no longer contribute to the election of the candidate.
501
14
        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
14
        let (leaving_stake, dust) = if stake_decrease.is_zero() {
506
10
            (Stake(0u32.into()), Stake(0u32.into()))
507
        } else {
508
4
            Self::leave_stake(&candidate, &delegator, Stake(stake_decrease))?
509
        };
510

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

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

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

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

            
540
26
                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
22
                }
547

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

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

            
557
17
                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
14
                }
564

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

            
568
16
                pools::ManualRewards::<T>::decrease_hold(candidate, delegator, &stake)?;
569
16
                Ok(stake)
570
            }
571
        }
572
43
    }
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
30
    fn leave_stake(
580
30
        candidate: &Candidate<T>,
581
30
        delegator: &Delegator<T>,
582
30
        stake: Stake<T::Balance>,
583
30
    ) -> 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
30
        let leaving_shares = pools::Leaving::<T>::stake_to_shares_or_init(candidate, stake)?;
589
30
        let leaving_stake = pools::Leaving::<T>::add_shares(candidate, delegator, leaving_shares)?;
590
30
        pools::Leaving::<T>::increase_hold(candidate, delegator, &leaving_stake)?;
591

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

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

            
610
30
        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
14
        }
618

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