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, ActivePoolKind, Pool, PoolKind},
30
        Error, Event, PendingOperationKey, PendingOperationQuery, PendingOperations, Shares,
31
        SharesOrStake, Stake,
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
86
pub fn default<T: Default>() -> T {
44
86
    Default::default()
45
86
}
46

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

            
62
96
    let shares = PendingOperations::<Runtime>::get(delegator, operation_key);
63
96
    if shares == 0 {
64
48
        return 0;
65
48
    }
66
48

            
67
48
    Joining::shares_to_stake(&candidate, Shares(shares))
68
48
        .unwrap()
69
48
        .0
70
96
}
71

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

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

            
91
48
        let now = block_number();
92
48

            
93
48
        let before = State::extract(candidate, delegator);
94
48
        let pool_before = PoolState::extract::<Joining>(candidate, delegator);
95
48
        let operation_before = operation_stake(candidate, delegator, pool, now);
96
48

            
97
48
        assert_ok!(Staking::request_delegate(
98
48
            RuntimeOrigin::signed(delegator),
99
48
            candidate,
100
48
            pool,
101
48
            amount,
102
48
        ));
103

            
104
48
        let after = State::extract(candidate, delegator);
105
48
        let pool_after = PoolState::extract::<Joining>(candidate, delegator);
106
48
        let operation_after = operation_stake(candidate, delegator, pool, now);
107
48

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

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

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

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

            
156
44
        let refund = request_before
157
44
            .checked_sub(expected_increase)
158
44
            .expect("expected increase should be <= requested amount");
159
44

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

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

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

            
188
44
        assert_eq!(before.delegator_hold - refund, after.delegator_hold);
189
44
        assert_eq!(
190
44
            before.candidate_total_stake - refund,
191
44
            after.candidate_total_stake
192
44
        );
193

            
194
44
        assert_eq!(joining_before.hold - request_before, joining_after.hold);
195
44
        assert_eq!(joining_before.stake - request_before, joining_after.stake);
196

            
197
44
        assert_eq!(pool_before.hold + expected_increase, pool_after.hold);
198
44
        assert_eq!(pool_before.stake + expected_increase, pool_after.stake);
199
44
    }
200
}
201

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

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

            
222
44
        let block_number = block_number();
223
44

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

            
233
44
        roll_to(block_number + BLOCKS_TO_WAIT);
234
44

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
437
7
        roll_to(block_number + BLOCKS_TO_WAIT);
438
7

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

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

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

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

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

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

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

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

            
496
    expected_removed: Balance,
497
    expected_restaked: Balance,
498
    expected_leaving: Balance,
499
    expected_released: Balance,
500
    expected_hold_rebalance: Balance,
501
}
502

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

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

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

            
536
8
        assert_ok!(Staking::swap_pool(
537
8
            RuntimeOrigin::signed(delegator),
538
8
            candidate,
539
8
            P::target_pool(),
540
8
            requested_amount
541
8
        ));
542

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

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

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

            
568
8
        assert_eq!(
569
8
            target_pool_before.stake + expected_restaked,
570
8
            target_pool_after.stake
571
8
        );
572
8
        assert_eq!(
573
8
            target_pool_before.hold + expected_restaked,
574
8
            target_pool_after.hold
575
8
        );
576

            
577
8
        assert_eq!(leaving_before.stake + expected_leaving, leaving_after.stake);
578
8
        assert_eq!(leaving_before.hold + expected_leaving, leaving_after.stake);
579

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