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 {super::*, crate::assert_eq_last_events};
18

            
19
pool_test!(
20
    fn empty_delegation<P>() {
21
2
        ExtBuilder::default().build().execute_with(|| {
22
2
            let before = State::extract(ACCOUNT_CANDIDATE_1, ACCOUNT_DELEGATOR_1);
23
2
            let pool_before =
24
2
                PoolState::extract::<Joining>(ACCOUNT_CANDIDATE_1, ACCOUNT_DELEGATOR_1);
25
2

            
26
2
            assert_noop!(
27
2
                Staking::request_delegate(
28
2
                    RuntimeOrigin::signed(ACCOUNT_DELEGATOR_1),
29
2
                    ACCOUNT_CANDIDATE_1,
30
2
                    P::target_pool(),
31
2
                    0
32
2
                ),
33
2
                Error::<Runtime>::StakeMustBeNonZero
34
2
            );
35

            
36
2
            let after = State::extract(ACCOUNT_CANDIDATE_1, ACCOUNT_DELEGATOR_1);
37
2
            let pool_after =
38
2
                PoolState::extract::<Joining>(ACCOUNT_CANDIDATE_1, ACCOUNT_DELEGATOR_1);
39
2

            
40
2
            assert_eq!(before, after);
41
2
            assert_eq!(pool_before, pool_after);
42

            
43
2
            assert_eq_events!(Vec::<Event<Runtime>>::new());
44
2
        })
45
    }
46
);
47

            
48
pool_test!(
49
    fn delegation_request<P>() {
50
2
        ExtBuilder::default().build().execute_with(|| {
51
2
            let amount = 3324;
52
2
            RequestDelegation {
53
2
                candidate: ACCOUNT_CANDIDATE_1,
54
2
                delegator: ACCOUNT_DELEGATOR_1,
55
2
                pool: P::target_pool(),
56
2
                amount: amount + 1, // to test joining rounding
57
2
                expected_joining: amount,
58
2
            }
59
2
            .test();
60
2

            
61
2
            assert_eq_events!(vec![
62
2
                Event::IncreasedStake {
63
2
                    candidate: ACCOUNT_CANDIDATE_1,
64
2
                    stake_diff: amount,
65
2
                },
66
2
                Event::UpdatedCandidatePosition {
67
2
                    candidate: ACCOUNT_CANDIDATE_1,
68
2
                    stake: amount,
69
2
                    self_delegation: 0,
70
2
                    before: None,
71
2
                    after: None,
72
2
                },
73
2
                Event::RequestedDelegate {
74
2
                    candidate: ACCOUNT_CANDIDATE_1,
75
2
                    delegator: ACCOUNT_DELEGATOR_1,
76
2
                    pool: P::target_pool(),
77
2
                    pending: amount
78
2
                },
79
2
            ]);
80
2
        })
81
    }
82
);
83

            
84
pool_test!(
85
    fn delegation_request_more_than_available<P>() {
86
2
        ExtBuilder::default().build().execute_with(|| {
87
2
            let amount = DEFAULT_BALANCE; // not enough to keep ED
88
2

            
89
2
            let before = State::extract(ACCOUNT_CANDIDATE_1, ACCOUNT_DELEGATOR_1);
90
2
            let pool_before =
91
2
                PoolState::extract::<Joining>(ACCOUNT_CANDIDATE_1, ACCOUNT_DELEGATOR_1);
92
2

            
93
2
            assert_noop!(
94
2
                Staking::request_delegate(
95
2
                    RuntimeOrigin::signed(ACCOUNT_DELEGATOR_1),
96
2
                    ACCOUNT_CANDIDATE_1,
97
2
                    P::target_pool(),
98
2
                    amount,
99
2
                ),
100
2
                TokenError::FundsUnavailable
101
2
            );
102

            
103
2
            let after = State::extract(ACCOUNT_CANDIDATE_1, ACCOUNT_DELEGATOR_1);
104
2
            let pool_after =
105
2
                PoolState::extract::<Joining>(ACCOUNT_CANDIDATE_1, ACCOUNT_DELEGATOR_1);
106
2

            
107
2
            assert_eq!(before, after);
108
2
            assert_eq!(pool_before, pool_after);
109

            
110
2
            assert_eq_events!(Vec::<Event<Runtime>>::new());
111
2
        })
112
    }
113
);
114

            
115
pool_test!(
116
    fn delegation_execution<P>() {
117
2
        ExtBuilder::default().build().execute_with(|| {
118
2
            let final_amount = 2 * SHARE_INIT;
119
2
            let requested_amount = final_amount + 10; // test share rounding
120
2

            
121
2
            FullDelegation {
122
2
                candidate: ACCOUNT_CANDIDATE_1,
123
2
                delegator: ACCOUNT_DELEGATOR_1,
124
2
                request_amount: requested_amount,
125
2
                expected_increase: final_amount,
126
2
                ..default()
127
2
            }
128
2
            .test::<P>();
129
2

            
130
2
            assert_eq_events!(vec![
131
2
                Event::IncreasedStake {
132
2
                    candidate: ACCOUNT_CANDIDATE_1,
133
2
                    stake_diff: requested_amount,
134
2
                },
135
2
                Event::UpdatedCandidatePosition {
136
2
                    candidate: ACCOUNT_CANDIDATE_1,
137
2
                    stake: requested_amount,
138
2
                    self_delegation: 0,
139
2
                    before: None,
140
2
                    after: None,
141
2
                },
142
2
                Event::RequestedDelegate {
143
2
                    candidate: ACCOUNT_CANDIDATE_1,
144
2
                    delegator: ACCOUNT_DELEGATOR_1,
145
2
                    pool: P::target_pool(),
146
2
                    pending: requested_amount,
147
2
                },
148
2
                Event::DecreasedStake {
149
2
                    candidate: ACCOUNT_CANDIDATE_1,
150
2
                    stake_diff: 10,
151
2
                },
152
2
                Event::UpdatedCandidatePosition {
153
2
                    candidate: ACCOUNT_CANDIDATE_1,
154
2
                    stake: final_amount,
155
2
                    self_delegation: 0,
156
2
                    before: None,
157
2
                    after: None,
158
2
                },
159
2
                P::event_staked(ACCOUNT_CANDIDATE_1, ACCOUNT_DELEGATOR_1, 2, final_amount),
160
2
                Event::ExecutedDelegate {
161
2
                    candidate: ACCOUNT_CANDIDATE_1,
162
2
                    delegator: ACCOUNT_DELEGATOR_1,
163
2
                    pool: P::target_pool(),
164
2
                    staked: final_amount,
165
2
                    released: 10,
166
2
                },
167
2
            ]);
168
2
        })
169
    }
170
);
171

            
172
pool_test!(
173
    fn delegation_execution_too_soon<P>() {
174
2
        ExtBuilder::default().build().execute_with(|| {
175
2
            let final_amount = 2 * SHARE_INIT;
176
2
            let block_number = block_number();
177
2

            
178
2
            RequestDelegation {
179
2
                candidate: ACCOUNT_CANDIDATE_1,
180
2
                delegator: ACCOUNT_DELEGATOR_1,
181
2
                pool: P::target_pool(),
182
2
                amount: final_amount,
183
2
                expected_joining: final_amount,
184
2
            }
185
2
            .test();
186
2
            roll_to(block_number + BLOCKS_TO_WAIT - 1); // too soon
187
2

            
188
2
            assert_noop!(
189
2
                Staking::execute_pending_operations(
190
2
                    RuntimeOrigin::signed(ACCOUNT_DELEGATOR_1),
191
2
                    vec![PendingOperationQuery {
192
2
                        delegator: ACCOUNT_DELEGATOR_1,
193
2
                        operation: P::joining_operation_key(ACCOUNT_CANDIDATE_1, block_number)
194
2
                    }]
195
2
                ),
196
2
                Error::<Runtime>::RequestCannotBeExecuted(0)
197
2
            );
198
2
        })
199
    }
200
);
201

            
202
pool_test!(
203
    fn undelegation_execution_too_soon<P>() {
204
2
        ExtBuilder::default().build().execute_with(|| {
205
2
            let final_amount = 2 * SHARE_INIT;
206
2
            let leaving_amount = round_down(final_amount, 3); // test leaving rounding
207
2

            
208
2
            FullDelegation {
209
2
                candidate: ACCOUNT_CANDIDATE_1,
210
2
                delegator: ACCOUNT_DELEGATOR_1,
211
2
                request_amount: final_amount,
212
2
                expected_increase: final_amount,
213
2
                ..default()
214
2
            }
215
2
            .test::<P>();
216
2

            
217
2
            let block_number = block_number();
218
2

            
219
2
            RequestUndelegation {
220
2
                candidate: ACCOUNT_CANDIDATE_1,
221
2
                delegator: ACCOUNT_DELEGATOR_1,
222
2
                request_amount: SharesOrStake::Stake(final_amount),
223
2
                expected_removed: final_amount,
224
2
                expected_leaving: leaving_amount,
225
2
                ..default()
226
2
            }
227
2
            .test::<P>();
228
2

            
229
2
            roll_to(block_number + BLOCKS_TO_WAIT - 1); // too soon
230
2
            assert_noop!(
231
2
                Staking::execute_pending_operations(
232
2
                    RuntimeOrigin::signed(ACCOUNT_DELEGATOR_1),
233
2
                    vec![PendingOperationQuery {
234
2
                        delegator: ACCOUNT_DELEGATOR_1,
235
2
                        operation: PendingOperationKey::Leaving {
236
2
                            candidate: ACCOUNT_CANDIDATE_1,
237
2
                            at: block_number,
238
2
                        }
239
2
                    }]
240
2
                ),
241
2
                Error::<Runtime>::RequestCannotBeExecuted(0)
242
2
            );
243
2
        })
244
    }
245
);
246

            
247
pool_test!(
248
    fn undelegation_execution<P>() {
249
2
        ExtBuilder::default().build().execute_with(|| {
250
2
            let final_amount = 2 * SHARE_INIT;
251
2
            let requested_amount = final_amount + 10; // test share rounding
252
2
            let leaving_amount = round_down(final_amount, 3); // test leaving rounding
253
2

            
254
2
            assert_eq!(leaving_amount, 1_999_998);
255

            
256
2
            FullDelegation {
257
2
                candidate: ACCOUNT_CANDIDATE_1,
258
2
                delegator: ACCOUNT_DELEGATOR_1,
259
2
                request_amount: requested_amount,
260
2
                expected_increase: final_amount,
261
2
                ..default()
262
2
            }
263
2
            .test::<P>();
264
2

            
265
2
            FullUndelegation {
266
2
                candidate: ACCOUNT_CANDIDATE_1,
267
2
                delegator: ACCOUNT_DELEGATOR_1,
268
2
                request_amount: SharesOrStake::Stake(final_amount),
269
2
                expected_removed: final_amount,
270
2
                expected_leaving: leaving_amount,
271
2
                ..default()
272
2
            }
273
2
            .test::<P>();
274
2

            
275
2
            assert_eq_events!(vec![
276
2
                // delegate request
277
2
                Event::IncreasedStake {
278
2
                    candidate: ACCOUNT_CANDIDATE_1,
279
2
                    stake_diff: requested_amount,
280
2
                },
281
2
                Event::UpdatedCandidatePosition {
282
2
                    candidate: ACCOUNT_CANDIDATE_1,
283
2
                    stake: requested_amount,
284
2
                    self_delegation: 0,
285
2
                    before: None,
286
2
                    after: None,
287
2
                },
288
2
                Event::RequestedDelegate {
289
2
                    candidate: ACCOUNT_CANDIDATE_1,
290
2
                    delegator: ACCOUNT_DELEGATOR_1,
291
2
                    pool: P::target_pool(),
292
2
                    pending: requested_amount
293
2
                },
294
2
                // delegate exec
295
2
                Event::DecreasedStake {
296
2
                    candidate: ACCOUNT_CANDIDATE_1,
297
2
                    stake_diff: 10,
298
2
                },
299
2
                Event::UpdatedCandidatePosition {
300
2
                    candidate: ACCOUNT_CANDIDATE_1,
301
2
                    stake: final_amount,
302
2
                    self_delegation: 0,
303
2
                    before: None,
304
2
                    after: None,
305
2
                },
306
2
                P::event_staked(ACCOUNT_CANDIDATE_1, ACCOUNT_DELEGATOR_1, 2, final_amount),
307
2
                Event::ExecutedDelegate {
308
2
                    candidate: ACCOUNT_CANDIDATE_1,
309
2
                    delegator: ACCOUNT_DELEGATOR_1,
310
2
                    pool: P::target_pool(),
311
2
                    staked: final_amount,
312
2
                    released: 10,
313
2
                },
314
2
                // undelegate request
315
2
                Event::DecreasedStake {
316
2
                    candidate: ACCOUNT_CANDIDATE_1,
317
2
                    stake_diff: final_amount,
318
2
                },
319
2
                Event::UpdatedCandidatePosition {
320
2
                    candidate: ACCOUNT_CANDIDATE_1,
321
2
                    stake: 0,
322
2
                    self_delegation: 0,
323
2
                    before: None,
324
2
                    after: None,
325
2
                },
326
2
                Event::RequestedUndelegate {
327
2
                    candidate: ACCOUNT_CANDIDATE_1,
328
2
                    delegator: ACCOUNT_DELEGATOR_1,
329
2
                    from: P::target_pool(),
330
2
                    pending: leaving_amount,
331
2
                    released: 2
332
2
                },
333
2
                // undelegate exec
334
2
                Event::ExecutedUndelegate {
335
2
                    candidate: ACCOUNT_CANDIDATE_1,
336
2
                    delegator: ACCOUNT_DELEGATOR_1,
337
2
                    released: leaving_amount,
338
2
                },
339
2
            ]);
340
2
        })
341
    }
342
);
343

            
344
pool_test!(
345
    fn undelegation_execution_amount_in_shares<P>() {
346
2
        ExtBuilder::default().build().execute_with(|| {
347
2
            let joining_amount = 2 * SHARE_INIT;
348
2
            let joining_requested_amount = joining_amount + 10; // test share rounding
349
2

            
350
2
            let leaving_requested_amount = SHARE_INIT;
351
2
            let leaving_amount = round_down(leaving_requested_amount, 3); // test leaving rounding
352
2

            
353
2
            assert_eq!(leaving_amount, 999_999);
354

            
355
2
            FullDelegation {
356
2
                candidate: ACCOUNT_CANDIDATE_1,
357
2
                delegator: ACCOUNT_DELEGATOR_1,
358
2
                request_amount: joining_requested_amount,
359
2
                expected_increase: joining_amount,
360
2
                ..default()
361
2
            }
362
2
            .test::<P>();
363
2

            
364
2
            FullUndelegation {
365
2
                candidate: ACCOUNT_CANDIDATE_1,
366
2
                delegator: ACCOUNT_DELEGATOR_1,
367
2
                request_amount: SharesOrStake::Shares(1),
368
2
                expected_removed: leaving_requested_amount,
369
2
                expected_leaving: leaving_amount,
370
2
                ..default()
371
2
            }
372
2
            .test::<P>();
373
2

            
374
2
            assert_eq_events!(vec![
375
2
                // delegate request
376
2
                Event::IncreasedStake {
377
2
                    candidate: ACCOUNT_CANDIDATE_1,
378
2
                    stake_diff: joining_requested_amount,
379
2
                },
380
2
                Event::UpdatedCandidatePosition {
381
2
                    candidate: ACCOUNT_CANDIDATE_1,
382
2
                    stake: joining_requested_amount,
383
2
                    self_delegation: 0,
384
2
                    before: None,
385
2
                    after: None,
386
2
                },
387
2
                Event::RequestedDelegate {
388
2
                    candidate: ACCOUNT_CANDIDATE_1,
389
2
                    delegator: ACCOUNT_DELEGATOR_1,
390
2
                    pool: P::target_pool(),
391
2
                    pending: joining_requested_amount
392
2
                },
393
2
                // delegate exec
394
2
                Event::DecreasedStake {
395
2
                    candidate: ACCOUNT_CANDIDATE_1,
396
2
                    stake_diff: 10,
397
2
                },
398
2
                Event::UpdatedCandidatePosition {
399
2
                    candidate: ACCOUNT_CANDIDATE_1,
400
2
                    stake: joining_amount,
401
2
                    self_delegation: 0,
402
2
                    before: None,
403
2
                    after: None,
404
2
                },
405
2
                P::event_staked(ACCOUNT_CANDIDATE_1, ACCOUNT_DELEGATOR_1, 2, joining_amount),
406
2
                Event::ExecutedDelegate {
407
2
                    candidate: ACCOUNT_CANDIDATE_1,
408
2
                    delegator: ACCOUNT_DELEGATOR_1,
409
2
                    pool: P::target_pool(),
410
2
                    staked: joining_amount,
411
2
                    released: 10,
412
2
                },
413
2
                // undelegate request
414
2
                Event::DecreasedStake {
415
2
                    candidate: ACCOUNT_CANDIDATE_1,
416
2
                    stake_diff: leaving_requested_amount,
417
2
                },
418
2
                Event::UpdatedCandidatePosition {
419
2
                    candidate: ACCOUNT_CANDIDATE_1,
420
2
                    stake: joining_amount - leaving_requested_amount,
421
2
                    self_delegation: 0,
422
2
                    before: None,
423
2
                    after: None,
424
2
                },
425
2
                Event::RequestedUndelegate {
426
2
                    candidate: ACCOUNT_CANDIDATE_1,
427
2
                    delegator: ACCOUNT_DELEGATOR_1,
428
2
                    from: P::target_pool(),
429
2
                    pending: leaving_amount,
430
2
                    released: 1
431
2
                },
432
2
                // undelegate exec
433
2
                Event::ExecutedUndelegate {
434
2
                    candidate: ACCOUNT_CANDIDATE_1,
435
2
                    delegator: ACCOUNT_DELEGATOR_1,
436
2
                    released: leaving_amount,
437
2
                },
438
2
            ]);
439
2
        })
440
    }
441
);
442

            
443
pool_test!(
444
    fn swap_works<P>() {
445
2
        ExtBuilder::default().build().execute_with(|| {
446
2
            FullDelegation {
447
2
                candidate: ACCOUNT_CANDIDATE_1,
448
2
                delegator: ACCOUNT_DELEGATOR_1,
449
2
                request_amount: 10 * SHARE_INIT,
450
2
                expected_increase: 10 * SHARE_INIT,
451
2
                ..default()
452
2
            }
453
2
            .test::<P>();
454
2

            
455
2
            Swap {
456
2
                candidate: ACCOUNT_CANDIDATE_1,
457
2
                delegator: ACCOUNT_DELEGATOR_1,
458
2
                requested_amount: SharesOrStake::Stake(5 * SHARE_INIT + 10),
459
2
                expected_removed: 5 * SHARE_INIT,
460
2
                expected_restaked: 5 * SHARE_INIT,
461
2
                ..default()
462
2
            }
463
2
            .test::<P>();
464
2

            
465
2
            assert_eq_last_events!(vec![Event::<Runtime>::SwappedPool {
466
2
                candidate: ACCOUNT_CANDIDATE_1,
467
2
                delegator: ACCOUNT_DELEGATOR_1,
468
2
                source_pool: P::target_pool(),
469
2
                source_shares: 5,
470
2
                source_stake: 5 * SHARE_INIT,
471
2
                target_shares: 5,
472
2
                target_stake: 5 * SHARE_INIT,
473
2
                pending_leaving: 0,
474
2
                released: 0,
475
2
            }]);
476
2
        })
477
    }
478
);
479

            
480
pool_test!(
481
    fn swap_too_much<P>() {
482
2
        ExtBuilder::default().build().execute_with(|| {
483
2
            FullDelegation {
484
2
                candidate: ACCOUNT_CANDIDATE_1,
485
2
                delegator: ACCOUNT_DELEGATOR_1,
486
2
                request_amount: 10 * SHARE_INIT,
487
2
                expected_increase: 10 * SHARE_INIT,
488
2
                ..default()
489
2
            }
490
2
            .test::<P>();
491
2

            
492
2
            assert_noop!(
493
2
                Staking::swap_pool(
494
2
                    RuntimeOrigin::signed(ACCOUNT_DELEGATOR_1),
495
2
                    ACCOUNT_CANDIDATE_1,
496
2
                    P::target_pool(),
497
2
                    SharesOrStake::Shares(11),
498
2
                ),
499
2
                Error::<Runtime>::MathUnderflow
500
2
            );
501
2
        })
502
    }
503
);
504

            
505
pool_test!(
506
    fn swap_with_rounding<P>() {
507
2
        ExtBuilder::default().build().execute_with(|| {
508
2
            FullDelegation {
509
2
                candidate: ACCOUNT_CANDIDATE_1,
510
2
                delegator: ACCOUNT_DELEGATOR_1,
511
2
                request_amount: 10 * SHARE_INIT,
512
2
                expected_increase: 10 * SHARE_INIT,
513
2
                ..default()
514
2
            }
515
2
            .test::<P>();
516
2

            
517
2
            FullDelegation {
518
2
                candidate: ACCOUNT_CANDIDATE_1,
519
2
                delegator: ACCOUNT_DELEGATOR_1,
520
2
                request_amount: 1 * SHARE_INIT,
521
2
                expected_increase: 1 * SHARE_INIT,
522
2
                ..default()
523
2
            }
524
2
            .test::<P::OppositePool>();
525
2

            
526
2
            // We then artificialy distribute rewards to the target by increasing the value of the pool
527
2
            // and minting currency to the staking account (this is not how manual rewards would
528
2
            // be distributed but whatever).
529
2
            let rewards = 5 * KILO;
530
2
            assert_ok!(Balances::mint_into(&ACCOUNT_STAKING, rewards));
531
2
            assert_ok!(P::OppositePool::share_stake_among_holders(
532
2
                &ACCOUNT_CANDIDATE_1,
533
2
                Stake(rewards)
534
2
            ));
535
2
            assert_ok!(Candidates::<Runtime>::add_total_stake(
536
2
                &ACCOUNT_CANDIDATE_1,
537
2
                &Stake(rewards)
538
2
            ));
539

            
540
2
            Swap {
541
2
                candidate: ACCOUNT_CANDIDATE_1,
542
2
                delegator: ACCOUNT_DELEGATOR_1,
543
2
                requested_amount: SharesOrStake::Stake(5 * SHARE_INIT + 10),
544
2
                expected_removed: 5 * SHARE_INIT,
545
2
                // due to 1 target share now being worth a bit more than SHARE_INIT,
546
2
                // only 4 target shares can be restaked
547
2
                expected_restaked: 4_020_000,
548
2
                // remaining amount is put in the leaving pool, rounded down
549
2
                // to the closest multiple of 3 (test leaving share init value)
550
2
                expected_leaving: 979_998,
551
2
                // thus the 2 stake that could not be put in the leaving pool
552
2
                // are directly released
553
2
                expected_released: 2,
554
2
                ..default()
555
2
            }
556
2
            .test::<P>();
557
2

            
558
2
            assert_eq_last_events!(vec![Event::<Runtime>::SwappedPool {
559
2
                candidate: ACCOUNT_CANDIDATE_1,
560
2
                delegator: ACCOUNT_DELEGATOR_1,
561
2
                source_pool: P::target_pool(),
562
2
                source_shares: 5,
563
2
                source_stake: 5 * SHARE_INIT,
564
2
                target_shares: 4,
565
2
                target_stake: 4_020_000,
566
2
                pending_leaving: 979_998,
567
2
                released: 2,
568
2
            }]);
569
2
        })
570
    }
571
);