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
    pub fn rebalance_hold(
43
        candidate: Candidate<T>,
44
        delegator: Delegator<T>,
45
        pool: AllTargetPool,
46
    ) -> DispatchResultWithPostInfo {
47
        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
                let held = pools::AutoCompounding::<T>::hold(&candidate, &delegator);
57
                let shares = pools::AutoCompounding::<T>::shares(&candidate, &delegator);
58
                let stake = pools::AutoCompounding::<T>::shares_to_stake(&candidate, shares)?;
59
                pools::AutoCompounding::<T>::set_hold(&candidate, &delegator, stake);
60
                (held, stake)
61
            }
62
            AllTargetPool::ManualRewards => {
63
                let held = pools::ManualRewards::<T>::hold(&candidate, &delegator);
64
                let shares = pools::ManualRewards::<T>::shares(&candidate, &delegator);
65
                let stake = pools::ManualRewards::<T>::shares_to_stake(&candidate, shares)?;
66
                pools::ManualRewards::<T>::set_hold(&candidate, &delegator, stake);
67
                (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
        if stake == held {
79
            return Ok(().into());
80
        }
81

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

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

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

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

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

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

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

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

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

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

            
169
    pub fn request_undelegate(
170
        candidate: Candidate<T>,
171
        delegator: Delegator<T>,
172
        pool: TargetPool,
173
        amount: SharesOrStake<T::Balance>,
174
    ) -> DispatchResultWithPostInfo {
175
        // Converts amount to shares of the correct pool
176
        let shares = match (amount, pool) {
177
            (SharesOrStake::Shares(s), _) => s,
178
            (SharesOrStake::Stake(s), TargetPool::AutoCompounding) => {
179
                pools::AutoCompounding::<T>::stake_to_shares(&candidate, Stake(s))?.0
180
            }
181
            (SharesOrStake::Stake(s), TargetPool::ManualRewards) => {
182
                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
        if let TargetPool::ManualRewards = pool {
188
            Self::claim_manual_rewards(&[(candidate.clone(), delegator.clone())])?;
189
        }
190

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
476
        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
        let actually_staked = match source_pool {
481
            TargetPool::ManualRewards => {
482
                let stake =
483
                    pools::AutoCompounding::<T>::add_shares(&candidate, &delegator, new_shares)?;
484
                pools::AutoCompounding::<T>::increase_hold(&candidate, &delegator, &stake)?;
485
                stake
486
            }
487
            TargetPool::AutoCompounding => {
488
                let stake =
489
                    pools::ManualRewards::<T>::add_shares(&candidate, &delegator, new_shares)?;
490
                pools::ManualRewards::<T>::increase_hold(&candidate, &delegator, &stake)?;
491
                stake
492
            }
493
        };
494

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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