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
mod candidates;
18
mod delegator_flow;
19
mod manual_rewards;
20
mod rebalance;
21
mod rewards;
22

            
23
use {
24
    crate::{
25
        assert_eq_events, assert_fields_eq,
26
        candidate::Candidates,
27
        mock::*,
28
        pool_test,
29
        pools::{self, Pool},
30
        AllTargetPool, Error, Event, PendingOperationKey, PendingOperationQuery, PendingOperations,
31
        Shares, SharesOrStake, Stake, TargetPool,
32
    },
33
    frame_support::{
34
        assert_noop, assert_ok,
35
        traits::tokens::fungible::{Balanced, Mutate},
36
    },
37
    sp_runtime::TokenError,
38
};
39

            
40
pub type Joining = pools::Joining<Runtime>;
41
pub type Leaving = pools::Leaving<Runtime>;
42

            
43
64
pub fn default<T: Default>() -> T {
44
64
    Default::default()
45
64
}
46

            
47
92
pub(crate) fn operation_stake(
48
92
    candidate: AccountId,
49
92
    delegator: AccountId,
50
92
    pool: TargetPool,
51
92
    at: u64,
52
92
) -> Balance {
53
92
    let operation_key = match pool {
54
        TargetPool::AutoCompounding => {
55
44
            PendingOperationKey::JoiningAutoCompounding { candidate, at }
56
        }
57
48
        TargetPool::ManualRewards => PendingOperationKey::JoiningManualRewards { candidate, at },
58
    };
59

            
60
92
    let shares = PendingOperations::<Runtime>::get(delegator, operation_key);
61
92
    if shares == 0 {
62
46
        return 0;
63
46
    }
64
46

            
65
46
    Joining::shares_to_stake(&candidate, Shares(shares))
66
46
        .unwrap()
67
46
        .0
68
92
}
69

            
70
#[must_use]
71
pub(crate) struct RequestDelegation {
72
    candidate: AccountId,
73
    delegator: AccountId,
74
    pool: TargetPool,
75
    amount: Balance,
76
    expected_joining: Balance,
77
}
78

            
79
impl RequestDelegation {
80
46
    pub fn test(self) {
81
46
        let Self {
82
46
            candidate,
83
46
            delegator,
84
46
            pool,
85
46
            amount,
86
46
            expected_joining,
87
46
        } = self;
88
46

            
89
46
        let now = block_number();
90
46

            
91
46
        let before = State::extract(candidate, delegator);
92
46
        let pool_before = PoolState::extract::<Joining>(candidate, delegator);
93
46
        let operation_before = operation_stake(candidate, delegator, pool, now);
94
46

            
95
46
        assert_ok!(Staking::request_delegate(
96
46
            RuntimeOrigin::signed(delegator),
97
46
            candidate,
98
46
            pool,
99
46
            amount,
100
46
        ));
101

            
102
46
        let after = State::extract(candidate, delegator);
103
46
        let pool_after = PoolState::extract::<Joining>(candidate, delegator);
104
46
        let operation_after = operation_stake(candidate, delegator, pool, now);
105
46

            
106
46
        // Actual balances don't change
107
46
        assert_fields_eq!(before, after, [delegator_balance, staking_balance]);
108
46
        assert_eq!(
109
46
            before.delegator_hold + expected_joining,
110
46
            after.delegator_hold
111
46
        );
112
46
        assert_eq!(pool_before.hold + expected_joining, pool_after.hold);
113
46
        assert_eq!(pool_before.stake + expected_joining, pool_after.stake);
114
46
        assert_eq!(
115
46
            before.candidate_total_stake + expected_joining,
116
46
            after.candidate_total_stake
117
46
        );
118
46
        assert_eq!(operation_before + expected_joining, operation_after);
119
46
    }
120
}
121

            
122
#[must_use]
123
#[derive(Default)]
124
pub(crate) struct ExecuteDelegation {
125
    candidate: AccountId,
126
    delegator: AccountId,
127
    block_number: u64,
128
    expected_increase: Balance,
129
    expected_manual_rewards: Balance,
130
}
131

            
132
impl ExecuteDelegation {
133
42
    pub fn test<P: PoolExt<Runtime>>(self) {
134
42
        let Self {
135
42
            candidate,
136
42
            delegator,
137
42
            block_number,
138
42
            expected_increase,
139
42
            expected_manual_rewards,
140
42
        } = self;
141
42

            
142
42
        let before = State::extract(candidate, delegator);
143
42
        let joining_before = PoolState::extract::<Joining>(candidate, delegator);
144
42
        let pool_before = PoolState::extract::<P>(candidate, delegator);
145
42
        let request_before = crate::PendingOperations::<Runtime>::get(
146
42
            delegator,
147
42
            P::joining_operation_key(candidate, block_number),
148
42
        );
149
42
        let request_before =
150
42
            pools::Joining::<Runtime>::shares_to_stake(&candidate, Shares(request_before))
151
42
                .unwrap()
152
42
                .0;
153
42

            
154
42
        let refund = request_before
155
42
            .checked_sub(expected_increase)
156
42
            .expect("expected increase should be <= requested amount");
157
42

            
158
42
        assert_ok!(Staking::execute_pending_operations(
159
42
            RuntimeOrigin::signed(delegator),
160
42
            vec![PendingOperationQuery {
161
42
                delegator,
162
42
                operation: P::joining_operation_key(candidate, block_number)
163
42
            }]
164
42
        ));
165

            
166
42
        let after = State::extract(candidate, delegator);
167
42
        let joining_after = PoolState::extract::<Joining>(candidate, delegator);
168
42
        let pool_after = PoolState::extract::<P>(candidate, delegator);
169
42
        let request_after = crate::PendingOperations::<Runtime>::get(
170
42
            delegator,
171
42
            P::joining_operation_key(candidate, block_number),
172
42
        );
173
42

            
174
42
        // Actual balances changes only due to manuyal rewards.
175
42
        assert_eq!(
176
42
            before.delegator_balance + expected_manual_rewards,
177
42
            after.delegator_balance
178
42
        );
179
42
        assert_eq!(
180
42
            before.staking_balance - expected_manual_rewards,
181
42
            after.staking_balance
182
42
        );
183
        // However funds are held (with share rounding released)
184
42
        assert_eq!(request_after, 0);
185

            
186
42
        assert_eq!(before.delegator_hold - refund, after.delegator_hold);
187
42
        assert_eq!(
188
42
            before.candidate_total_stake - refund,
189
42
            after.candidate_total_stake
190
42
        );
191

            
192
42
        assert_eq!(joining_before.hold - request_before, joining_after.hold);
193
42
        assert_eq!(joining_before.stake - request_before, joining_after.stake);
194

            
195
42
        assert_eq!(pool_before.hold + expected_increase, pool_after.hold);
196
42
        assert_eq!(pool_before.stake + expected_increase, pool_after.stake);
197
42
    }
198
}
199

            
200
#[must_use]
201
#[derive(Default)]
202
pub(crate) struct FullDelegation {
203
    candidate: AccountId,
204
    delegator: AccountId,
205
    request_amount: Balance,
206
    expected_increase: Balance,
207
    expected_manual_rewards: Balance,
208
}
209

            
210
impl FullDelegation {
211
42
    pub fn test<P: PoolExt<Runtime>>(self) {
212
42
        let Self {
213
42
            candidate,
214
42
            delegator,
215
42
            request_amount,
216
42
            expected_increase,
217
42
            expected_manual_rewards,
218
42
        } = self;
219
42

            
220
42
        let block_number = block_number();
221
42

            
222
42
        RequestDelegation {
223
42
            candidate,
224
42
            delegator,
225
42
            pool: P::target_pool(),
226
42
            amount: request_amount,
227
42
            expected_joining: round_down(request_amount, 2),
228
42
        }
229
42
        .test();
230
42

            
231
42
        roll_to(block_number + BLOCKS_TO_WAIT);
232
42

            
233
42
        ExecuteDelegation {
234
42
            candidate,
235
42
            delegator,
236
42
            block_number,
237
42
            expected_increase,
238
42
            expected_manual_rewards,
239
42
        }
240
42
        .test::<P>();
241
42
    }
242
}
243

            
244
#[must_use]
245
pub(crate) struct RequestUndelegation {
246
    candidate: AccountId,
247
    delegator: AccountId,
248
    request_amount: SharesOrStake<Balance>,
249
    expected_removed: Balance,
250
    expected_leaving: Balance,
251
    expected_manual_rewards: Balance,
252
    expected_hold_rebalance: Balance,
253
}
254

            
255
impl Default for RequestUndelegation {
256
12
    fn default() -> Self {
257
12
        Self {
258
12
            candidate: 0,
259
12
            delegator: 0,
260
12
            request_amount: SharesOrStake::Stake(0),
261
12
            expected_removed: 0,
262
12
            expected_leaving: 0,
263
12
            expected_manual_rewards: 0,
264
12
            expected_hold_rebalance: 0,
265
12
        }
266
12
    }
267
}
268

            
269
impl RequestUndelegation {
270
12
    pub fn test<P: PoolExt<Runtime>>(self) {
271
12
        let Self {
272
12
            candidate,
273
12
            delegator,
274
12
            request_amount,
275
12
            expected_removed,
276
12
            expected_leaving,
277
12
            expected_manual_rewards,
278
12
            expected_hold_rebalance,
279
12
        } = self;
280
12

            
281
12
        let dust = expected_removed
282
12
            .checked_sub(expected_leaving)
283
12
            .expect("should removed >= leaving");
284
12

            
285
12
        let before = State::extract(candidate, delegator);
286
12
        let pool_before = PoolState::extract::<P>(candidate, delegator);
287
12
        let leaving_before = PoolState::extract::<Leaving>(candidate, delegator);
288
12

            
289
12
        assert_ok!(Staking::request_undelegate(
290
12
            RuntimeOrigin::signed(delegator),
291
12
            candidate,
292
12
            P::target_pool(),
293
12
            request_amount,
294
12
        ));
295

            
296
12
        let after = State::extract(candidate, delegator);
297
12
        let pool_after = PoolState::extract::<P>(candidate, delegator);
298
12
        let leaving_after = PoolState::extract::<Leaving>(candidate, delegator);
299
12

            
300
12
        // Actual balances changes due to manual rewards and hold rebalance.
301
12
        assert_eq!(
302
12
            before.delegator_balance + expected_manual_rewards + expected_hold_rebalance,
303
12
            after.delegator_balance
304
12
        );
305
12
        assert_eq!(
306
12
            before.staking_balance - expected_manual_rewards - expected_hold_rebalance,
307
12
            after.staking_balance
308
12
        );
309
        // Dust is released immediately.
310
12
        assert_eq!(
311
12
            before.delegator_hold - dust + expected_hold_rebalance,
312
12
            after.delegator_hold
313
12
        );
314
        // Pool decrease.
315
12
        assert_eq!(pool_before.stake - expected_removed, pool_after.stake);
316
12
        assert_eq!(
317
12
            pool_before.hold + expected_hold_rebalance - expected_removed,
318
12
            pool_after.stake
319
12
        );
320
        // Leaving increase.
321
12
        assert_eq!(leaving_before.stake + expected_leaving, leaving_after.stake);
322
12
        assert_eq!(leaving_before.hold + expected_leaving, leaving_after.stake);
323
        // Stake no longer contribute to election
324
12
        assert_eq!(
325
12
            before.candidate_total_stake - expected_removed,
326
12
            after.candidate_total_stake
327
12
        );
328
12
    }
329
}
330

            
331
#[must_use]
332
#[derive(Default)]
333
pub(crate) struct ExecuteUndelegation {
334
    candidate: AccountId,
335
    delegator: AccountId,
336
    block_number: u64,
337
    expected_decrease: Balance,
338
}
339

            
340
impl ExecuteUndelegation {
341
7
    pub fn test(self) {
342
7
        let Self {
343
7
            candidate,
344
7
            delegator,
345
7
            block_number,
346
7
            expected_decrease,
347
7
        } = self;
348
7

            
349
7
        let before = State::extract(candidate, delegator);
350
7
        let leaving_before = PoolState::extract::<Leaving>(candidate, delegator);
351
7

            
352
7
        assert_ok!(Staking::execute_pending_operations(
353
7
            RuntimeOrigin::signed(delegator),
354
7
            vec![PendingOperationQuery {
355
7
                delegator,
356
7
                operation: PendingOperationKey::Leaving {
357
7
                    candidate,
358
7
                    at: block_number
359
7
                }
360
7
            }]
361
7
        ));
362

            
363
7
        let after = State::extract(candidate, delegator);
364
7
        let leaving_after = PoolState::extract::<Joining>(candidate, delegator);
365
7
        let request_after = crate::PendingOperations::<Runtime>::get(
366
7
            delegator,
367
7
            PendingOperationKey::Leaving {
368
7
                candidate,
369
7
                at: block_number,
370
7
            },
371
7
        );
372
7

            
373
7
        // Actual balances don't change
374
7
        assert_fields_eq!(before, after, [delegator_balance, staking_balance]);
375
7
        assert_eq!(request_after, 0);
376
7
        assert_eq!(
377
7
            before.delegator_hold - expected_decrease,
378
7
            after.delegator_hold
379
7
        );
380
7
        assert_fields_eq!(before, after, candidate_total_stake);
381
7
        assert_eq!(leaving_before.hold - expected_decrease, leaving_after.hold);
382
7
        assert_eq!(
383
7
            leaving_before.stake - expected_decrease,
384
7
            leaving_after.stake
385
7
        );
386
7
    }
387
}
388

            
389
#[must_use]
390
pub(crate) struct FullUndelegation {
391
    candidate: AccountId,
392
    delegator: AccountId,
393
    request_amount: SharesOrStake<Balance>,
394
    expected_removed: Balance,
395
    expected_leaving: Balance,
396
    expected_hold_rebalance: Balance,
397
}
398

            
399
impl Default for FullUndelegation {
400
7
    fn default() -> Self {
401
7
        Self {
402
7
            candidate: 0,
403
7
            delegator: 0,
404
7
            request_amount: SharesOrStake::Stake(0),
405
7
            expected_removed: 0,
406
7
            expected_leaving: 0,
407
7
            expected_hold_rebalance: 0,
408
7
        }
409
7
    }
410
}
411

            
412
impl FullUndelegation {
413
7
    pub fn test<P: PoolExt<Runtime>>(self) {
414
7
        let Self {
415
7
            candidate,
416
7
            delegator,
417
7
            request_amount,
418
7
            expected_removed,
419
7
            expected_leaving,
420
7
            expected_hold_rebalance,
421
7
        } = self;
422
7

            
423
7
        let block_number = block_number();
424
7
        RequestUndelegation {
425
7
            candidate,
426
7
            delegator,
427
7
            request_amount,
428
7
            expected_removed,
429
7
            expected_leaving,
430
7
            expected_hold_rebalance,
431
7
            ..default()
432
7
        }
433
7
        .test::<P>();
434
7

            
435
7
        roll_to(block_number + BLOCKS_TO_WAIT);
436
7

            
437
7
        ExecuteUndelegation {
438
7
            candidate,
439
7
            delegator,
440
7
            block_number,
441
7
            expected_decrease: expected_leaving,
442
7
        }
443
7
        .test();
444
7
    }
445
}
446

            
447
6
pub(crate) fn do_rebalance_hold<P: Pool<Runtime>>(
448
6
    candidate: AccountId,
449
6
    delegator: AccountId,
450
6
    target_pool: AllTargetPool,
451
6
    expected_rebalance: SignedBalance,
452
6
) {
453
6
    let before = State::extract(candidate, delegator);
454
6
    let pool_before = PoolState::extract::<P>(candidate, delegator);
455
6

            
456
6
    assert_ok!(Staking::rebalance_hold(
457
6
        RuntimeOrigin::signed(ACCOUNT_DELEGATOR_1),
458
6
        ACCOUNT_CANDIDATE_1,
459
6
        ACCOUNT_DELEGATOR_1,
460
6
        target_pool
461
6
    ));
462

            
463
6
    let after = State::extract(candidate, delegator);
464
6
    let pool_after = PoolState::extract::<P>(candidate, delegator);
465
6

            
466
6
    // Balances should update
467
6
    match expected_rebalance {
468
4
        SignedBalance::Positive(balance) => {
469
4
            assert_eq!(pool_before.hold + balance, pool_after.hold);
470
4
            assert_eq!(before.delegator_balance + balance, after.delegator_balance);
471
4
            assert_eq!(before.staking_balance - balance, after.staking_balance);
472
        }
473
2
        SignedBalance::Negative(balance) => {
474
2
            assert_eq!(pool_before.hold - balance, pool_after.hold);
475
2
            assert_eq!(before.delegator_balance - balance, after.delegator_balance);
476
2
            assert_eq!(before.staking_balance + balance, after.staking_balance);
477
        }
478
    }
479

            
480
    // Stake stay the same.
481
6
    assert_fields_eq!(pool_before, pool_after, stake);
482
6
}
483

            
484
9
pub(crate) fn currency_issue(amount: Balance) -> crate::CreditOf<Runtime> {
485
9
    <<Runtime as crate::Config>::Currency as Balanced<AccountId>>::issue(amount)
486
9
}
487

            
488
#[must_use]
489
pub(crate) struct Swap {
490
    candidate: AccountId,
491
    delegator: AccountId,
492
    requested_amount: SharesOrStake<Balance>,
493

            
494
    expected_removed: Balance,
495
    expected_restaked: Balance,
496
    expected_leaving: Balance,
497
    expected_released: Balance,
498
    expected_hold_rebalance: Balance,
499
}
500

            
501
impl Default for Swap {
502
4
    fn default() -> Self {
503
4
        Self {
504
4
            candidate: 0,
505
4
            delegator: 0,
506
4
            requested_amount: SharesOrStake::Stake(0),
507
4
            expected_removed: 0,
508
4
            expected_restaked: 0,
509
4
            expected_leaving: 0,
510
4
            expected_released: 0,
511
4
            expected_hold_rebalance: 0,
512
4
        }
513
4
    }
514
}
515

            
516
impl Swap {
517
6
    pub fn test<P: PoolExt<Runtime>>(self) {
518
6
        let Self {
519
6
            candidate,
520
6
            delegator,
521
6
            requested_amount,
522
6
            expected_removed,
523
6
            expected_restaked,
524
6
            expected_leaving,
525
6
            expected_released,
526
6
            expected_hold_rebalance,
527
6
        } = self;
528
6

            
529
6
        let before = State::extract(candidate, delegator);
530
6
        let source_pool_before = PoolState::extract::<P>(candidate, delegator);
531
6
        let target_pool_before = PoolState::extract::<P::OppositePool>(candidate, delegator);
532
6
        let leaving_before = PoolState::extract::<Leaving>(candidate, delegator);
533
6

            
534
6
        assert_ok!(Staking::swap_pool(
535
6
            RuntimeOrigin::signed(delegator),
536
6
            candidate,
537
6
            P::target_pool(),
538
6
            requested_amount
539
6
        ));
540

            
541
6
        let after = State::extract(candidate, delegator);
542
6
        let source_pool_after = PoolState::extract::<P>(candidate, delegator);
543
6
        let target_pool_after = PoolState::extract::<P::OppositePool>(candidate, delegator);
544
6
        let leaving_after = PoolState::extract::<Leaving>(candidate, delegator);
545
6

            
546
6
        // Actual balances changes due to hold rebalance.
547
6
        assert_eq!(
548
6
            before.delegator_balance + expected_hold_rebalance,
549
6
            after.delegator_balance
550
6
        );
551
6
        assert_eq!(
552
6
            before.staking_balance - expected_hold_rebalance,
553
6
            after.staking_balance
554
6
        );
555

            
556
        // Pool change.
557
6
        assert_eq!(
558
6
            source_pool_before.stake - expected_removed,
559
6
            source_pool_after.stake
560
6
        );
561
6
        assert_eq!(
562
6
            source_pool_before.hold + expected_hold_rebalance - expected_removed,
563
6
            source_pool_after.stake
564
6
        );
565

            
566
6
        assert_eq!(
567
6
            target_pool_before.stake + expected_restaked,
568
6
            target_pool_after.stake
569
6
        );
570
6
        assert_eq!(
571
6
            target_pool_before.hold + expected_restaked,
572
6
            target_pool_after.hold
573
6
        );
574

            
575
6
        assert_eq!(leaving_before.stake + expected_leaving, leaving_after.stake);
576
6
        assert_eq!(leaving_before.hold + expected_leaving, leaving_after.stake);
577

            
578
6
        assert_eq!(
579
6
            before.candidate_total_stake - expected_leaving - expected_released,
580
6
            after.candidate_total_stake
581
6
        );
582
        // Dust is released immediately.
583
6
        assert_eq!(
584
6
            before.delegator_hold - expected_released + expected_hold_rebalance,
585
6
            after.delegator_hold
586
6
        );
587
6
    }
588
}