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 crate::{assert_eq_last_events, candidate::EligibleCandidate, SortedEligibleCandidates};
18

            
19
use super::*;
20

            
21
pool_test!(
22
    fn self_delegation_below_minimum<P>() {
23
2
        ExtBuilder::default().build().execute_with(|| {
24
2
            let requested_amount = MinimumSelfDelegation::get() - 1;
25
2
            let final_amount = round_down(
26
2
                requested_amount,
27
2
                P::shares_to_stake_or_init(&ACCOUNT_CANDIDATE_1, Shares(1))
28
2
                    .unwrap()
29
2
                    .0,
30
2
            );
31
2

            
32
2
            FullDelegation {
33
2
                candidate: ACCOUNT_CANDIDATE_1,
34
2
                delegator: ACCOUNT_CANDIDATE_1,
35
2
                request_amount: requested_amount,
36
2
                expected_increase: final_amount,
37
2
                ..default()
38
2
            }
39
2
            .test::<P>();
40
2

            
41
2
            assert_eq_events!(vec![
42
2
                Event::IncreasedStake {
43
2
                    candidate: ACCOUNT_CANDIDATE_1,
44
2
                    stake_diff: round_down(requested_amount, 2),
45
2
                },
46
2
                Event::UpdatedCandidatePosition {
47
2
                    candidate: ACCOUNT_CANDIDATE_1,
48
2
                    stake: round_down(requested_amount, 2),
49
2
                    self_delegation: round_down(requested_amount, 2),
50
2
                    before: None,
51
2
                    after: None,
52
2
                },
53
2
                Event::RequestedDelegate {
54
2
                    candidate: ACCOUNT_CANDIDATE_1,
55
2
                    delegator: ACCOUNT_CANDIDATE_1,
56
2
                    pool: P::target_pool(),
57
2
                    pending: round_down(requested_amount, 2),
58
2
                },
59
2
                Event::DecreasedStake {
60
2
                    candidate: ACCOUNT_CANDIDATE_1,
61
2
                    stake_diff: round_down(requested_amount, 2) - final_amount,
62
2
                },
63
2
                Event::UpdatedCandidatePosition {
64
2
                    candidate: ACCOUNT_CANDIDATE_1,
65
2
                    stake: final_amount,
66
2
                    self_delegation: final_amount,
67
2
                    before: None,
68
2
                    after: None,
69
2
                },
70
2
                P::event_staked(ACCOUNT_CANDIDATE_1, ACCOUNT_CANDIDATE_1, 9, final_amount),
71
2
                Event::ExecutedDelegate {
72
2
                    candidate: ACCOUNT_CANDIDATE_1,
73
2
                    delegator: ACCOUNT_CANDIDATE_1,
74
2
                    pool: P::target_pool(),
75
2
                    staked: final_amount,
76
2
                    released: round_down(requested_amount, 2) - final_amount,
77
2
                },
78
2
            ]);
79
2
        })
80
    }
81
);
82

            
83
pool_test!(
84
    fn self_delegation_above_minimum<P>() {
85
2
        ExtBuilder::default().build().execute_with(|| {
86
2
            let requested_amount = MinimumSelfDelegation::get();
87
2
            let final_amount = round_down(
88
2
                requested_amount,
89
2
                P::shares_to_stake_or_init(&ACCOUNT_CANDIDATE_1, Shares(1))
90
2
                    .unwrap()
91
2
                    .0,
92
2
            );
93
2

            
94
2
            FullDelegation {
95
2
                candidate: ACCOUNT_CANDIDATE_1,
96
2
                delegator: ACCOUNT_CANDIDATE_1,
97
2
                request_amount: requested_amount,
98
2
                expected_increase: final_amount,
99
2
                ..default()
100
2
            }
101
2
            .test::<P>();
102
2

            
103
2
            FullDelegation {
104
2
                candidate: ACCOUNT_CANDIDATE_1,
105
2
                delegator: ACCOUNT_CANDIDATE_1,
106
2
                request_amount: requested_amount,
107
2
                expected_increase: final_amount,
108
2
                ..default()
109
2
            }
110
2
            .test::<P>();
111
2

            
112
2
            FullUndelegation {
113
2
                candidate: ACCOUNT_CANDIDATE_1,
114
2
                delegator: ACCOUNT_CANDIDATE_1,
115
2
                request_amount: SharesOrStake::Stake(requested_amount * 2),
116
2
                expected_removed: requested_amount * 2,
117
2
                expected_leaving: round_down(requested_amount * 2, 3),
118
2
                ..default()
119
2
            }
120
2
            .test::<P>();
121
2

            
122
2
            assert_eq_events!(vec![
123
2
                // delegation 1
124
2
                Event::IncreasedStake {
125
2
                    candidate: ACCOUNT_CANDIDATE_1,
126
2
                    stake_diff: round_down(requested_amount, 2),
127
2
                },
128
2
                Event::UpdatedCandidatePosition {
129
2
                    candidate: ACCOUNT_CANDIDATE_1,
130
2
                    stake: round_down(requested_amount, 2),
131
2
                    self_delegation: round_down(requested_amount, 2),
132
2
                    before: None,
133
2
                    after: Some(0),
134
2
                },
135
2
                Event::RequestedDelegate {
136
2
                    candidate: ACCOUNT_CANDIDATE_1,
137
2
                    delegator: ACCOUNT_CANDIDATE_1,
138
2
                    pool: P::target_pool(),
139
2
                    pending: round_down(requested_amount, 2),
140
2
                },
141
2
                P::event_staked(ACCOUNT_CANDIDATE_1, ACCOUNT_CANDIDATE_1, 10, final_amount),
142
2
                Event::ExecutedDelegate {
143
2
                    candidate: ACCOUNT_CANDIDATE_1,
144
2
                    delegator: ACCOUNT_CANDIDATE_1,
145
2
                    pool: P::target_pool(),
146
2
                    staked: final_amount,
147
2
                    released: round_down(requested_amount, 2) - final_amount,
148
2
                },
149
2
                // delegation 2
150
2
                Event::IncreasedStake {
151
2
                    candidate: ACCOUNT_CANDIDATE_1,
152
2
                    stake_diff: round_down(requested_amount, 2),
153
2
                },
154
2
                Event::UpdatedCandidatePosition {
155
2
                    candidate: ACCOUNT_CANDIDATE_1,
156
2
                    stake: round_down(requested_amount * 2, 2),
157
2
                    self_delegation: round_down(requested_amount * 2, 2),
158
2
                    before: Some(0),
159
2
                    after: Some(0),
160
2
                },
161
2
                Event::RequestedDelegate {
162
2
                    candidate: ACCOUNT_CANDIDATE_1,
163
2
                    delegator: ACCOUNT_CANDIDATE_1,
164
2
                    pool: P::target_pool(),
165
2
                    pending: round_down(requested_amount, 2),
166
2
                },
167
2
                P::event_staked(ACCOUNT_CANDIDATE_1, ACCOUNT_CANDIDATE_1, 10, final_amount),
168
2
                Event::ExecutedDelegate {
169
2
                    candidate: ACCOUNT_CANDIDATE_1,
170
2
                    delegator: ACCOUNT_CANDIDATE_1,
171
2
                    pool: P::target_pool(),
172
2
                    staked: final_amount,
173
2
                    released: round_down(requested_amount, 2) - final_amount,
174
2
                },
175
2
                // undelegation
176
2
                Event::DecreasedStake {
177
2
                    candidate: ACCOUNT_CANDIDATE_1,
178
2
                    stake_diff: requested_amount * 2,
179
2
                },
180
2
                Event::UpdatedCandidatePosition {
181
2
                    candidate: ACCOUNT_CANDIDATE_1,
182
2
                    stake: 0,
183
2
                    self_delegation: 0,
184
2
                    before: Some(0),
185
2
                    after: None,
186
2
                },
187
2
                Event::RequestedUndelegate {
188
2
                    candidate: ACCOUNT_CANDIDATE_1,
189
2
                    delegator: ACCOUNT_CANDIDATE_1,
190
2
                    from: P::target_pool(),
191
2
                    pending: round_down(requested_amount * 2, 3),
192
2
                    released: 2,
193
2
                },
194
2
                Event::ExecutedUndelegate {
195
2
                    candidate: ACCOUNT_CANDIDATE_1,
196
2
                    delegator: ACCOUNT_CANDIDATE_1,
197
2
                    released: round_down(requested_amount * 2, 3)
198
2
                }
199
2
            ]);
200
2
        })
201
    }
202
);
203

            
204
#[test]
205
1
fn many_candidates_mixed_pools() {
206
1
    ExtBuilder::default().build().execute_with(|| {
207
1
        let share = InitialAutoCompoundingShareValue::get();
208
1
        // for simplicity we consider shares of both pools have the same value.
209
1
        assert_eq!(
210
1
            InitialAutoCompoundingShareValue::get(),
211
1
            InitialManualClaimShareValue::get()
212
1
        );
213

            
214
        struct Action {
215
            candidate: AccountId,
216
            delegator: AccountId,
217
            join: bool,
218
            auto: bool,
219
            amount: Balance,
220
            total_stake: Balance,
221
            total_self: Balance,
222

            
223
            rank_before: Option<u32>,
224
            rank_after: Option<u32>,
225
        }
226

            
227
2
        fn perform_actions(actions: &[Action]) {
228
2
            let share = InitialAutoCompoundingShareValue::get();
229
9
            for action in actions {
230
7
                match action {
231
                    Action {
232
                        join: true,
233
                        auto: true,
234
                        ..
235
                    } => {
236
4
                        FullDelegation {
237
4
                            candidate: action.candidate,
238
4
                            delegator: action.delegator,
239
4
                            request_amount: action.amount,
240
4
                            expected_increase: action.amount,
241
4
                            ..default()
242
4
                        }
243
4
                        .test::<pools::AutoCompounding<Runtime>>();
244
4

            
245
4
                        assert_eq_last_events!(vec![
246
4
                            Event::<Runtime>::IncreasedStake {
247
4
                                candidate: action.candidate,
248
4
                                stake_diff: action.amount,
249
4
                            },
250
4
                            Event::UpdatedCandidatePosition {
251
4
                                candidate: action.candidate,
252
4
                                stake: action.total_stake,
253
4
                                self_delegation: action.total_self,
254
4
                                before: action.rank_before,
255
4
                                after: action.rank_after,
256
4
                            },
257
4
                            Event::RequestedDelegate {
258
4
                                candidate: action.candidate,
259
4
                                delegator: action.delegator,
260
4
                                pool: ActivePoolKind::AutoCompounding,
261
4
                                pending: action.amount,
262
4
                            },
263
4
                            Event::StakedAutoCompounding {
264
4
                                candidate: action.candidate,
265
4
                                delegator: action.delegator,
266
4
                                shares: action.amount / share,
267
4
                                stake: action.amount,
268
4
                            },
269
4
                            Event::ExecutedDelegate {
270
4
                                candidate: action.candidate,
271
4
                                delegator: action.delegator,
272
4
                                pool: ActivePoolKind::AutoCompounding,
273
4
                                staked: action.amount,
274
4
                                released: 0,
275
4
                            },
276
                        ])
277
                    }
278
                    Action {
279
                        join: true,
280
                        auto: false,
281
                        ..
282
                    } => {
283
2
                        FullDelegation {
284
2
                            candidate: action.candidate,
285
2
                            delegator: action.delegator,
286
2
                            request_amount: action.amount,
287
2
                            expected_increase: action.amount,
288
2
                            ..default()
289
2
                        }
290
2
                        .test::<pools::ManualRewards<Runtime>>();
291
2

            
292
2
                        assert_eq_last_events!(vec![
293
2
                            Event::<Runtime>::IncreasedStake {
294
2
                                candidate: action.candidate,
295
2
                                stake_diff: action.amount,
296
2
                            },
297
2
                            Event::UpdatedCandidatePosition {
298
2
                                candidate: action.candidate,
299
2
                                stake: action.total_stake,
300
2
                                self_delegation: action.total_self,
301
2
                                before: action.rank_before,
302
2
                                after: action.rank_after,
303
2
                            },
304
2
                            Event::RequestedDelegate {
305
2
                                candidate: action.candidate,
306
2
                                delegator: action.delegator,
307
2
                                pool: ActivePoolKind::ManualRewards,
308
2
                                pending: action.amount,
309
2
                            },
310
2
                            Event::StakedManualRewards {
311
2
                                candidate: action.candidate,
312
2
                                delegator: action.delegator,
313
2
                                shares: action.amount / share,
314
2
                                stake: action.amount,
315
2
                            },
316
2
                            Event::ExecutedDelegate {
317
2
                                candidate: action.candidate,
318
2
                                delegator: action.delegator,
319
2
                                pool: ActivePoolKind::ManualRewards,
320
2
                                staked: action.amount,
321
2
                                released: 0,
322
2
                            },
323
                        ])
324
                    }
325
                    Action {
326
                        join: false,
327
                        auto: true,
328
                        ..
329
                    } => {
330
1
                        FullUndelegation {
331
1
                            candidate: action.candidate,
332
1
                            delegator: action.delegator,
333
1
                            request_amount: SharesOrStake::Stake(action.amount),
334
1
                            expected_removed: action.amount,
335
1
                            expected_leaving: round_down(action.amount, 3),
336
1
                            ..default()
337
1
                        }
338
1
                        .test::<pools::AutoCompounding<Runtime>>();
339
1

            
340
1
                        assert_eq_last_events!(vec![
341
1
                            Event::<Runtime>::DecreasedStake {
342
1
                                candidate: action.candidate,
343
1
                                stake_diff: action.amount,
344
1
                            },
345
1
                            Event::UpdatedCandidatePosition {
346
1
                                candidate: action.candidate,
347
1
                                stake: action.total_stake,
348
1
                                self_delegation: action.total_self,
349
1
                                before: action.rank_before,
350
1
                                after: action.rank_after,
351
1
                            },
352
1
                            Event::RequestedUndelegate {
353
1
                                candidate: action.candidate,
354
1
                                delegator: action.delegator,
355
1
                                from: ActivePoolKind::AutoCompounding,
356
1
                                pending: round_down(action.amount, 3),
357
1
                                released: action.amount - round_down(action.amount, 3),
358
1
                            },
359
1
                            Event::ExecutedUndelegate {
360
1
                                candidate: action.candidate,
361
1
                                delegator: action.delegator,
362
1
                                released: round_down(action.amount, 3),
363
1
                            },
364
                        ])
365
                    }
366
                    _ => todo!(),
367
                }
368
            }
369
2
        }
370

            
371
1
        perform_actions(&[
372
1
            Action {
373
1
                candidate: ACCOUNT_CANDIDATE_1,
374
1
                delegator: ACCOUNT_CANDIDATE_1,
375
1
                join: true,
376
1
                auto: true,
377
1
                amount: share * 11,
378
1
                total_stake: share * 11,
379
1
                total_self: share * 11,
380
1
                rank_before: None,
381
1
                rank_after: Some(0),
382
1
            },
383
1
            Action {
384
1
                candidate: ACCOUNT_CANDIDATE_2,
385
1
                delegator: ACCOUNT_CANDIDATE_2,
386
1
                join: true,
387
1
                auto: false,
388
1
                amount: share * 10,
389
1
                total_stake: share * 10,
390
1
                total_self: share * 10,
391
1
                rank_before: None,
392
1
                rank_after: Some(1),
393
1
            },
394
1
            Action {
395
1
                candidate: ACCOUNT_CANDIDATE_2,
396
1
                delegator: ACCOUNT_DELEGATOR_1,
397
1
                join: true,
398
1
                auto: true,
399
1
                amount: share * 3,
400
1
                total_stake: share * 13,
401
1
                total_self: share * 10,
402
1
                rank_before: Some(1),
403
1
                rank_after: Some(0),
404
1
            },
405
1
            Action {
406
1
                candidate: ACCOUNT_CANDIDATE_1,
407
1
                delegator: ACCOUNT_DELEGATOR_2,
408
1
                join: true,
409
1
                auto: false,
410
1
                amount: share,
411
1
                total_stake: share * 12,
412
1
                total_self: share * 11,
413
1
                rank_before: Some(1),
414
1
                rank_after: Some(1),
415
1
            },
416
1
            Action {
417
1
                candidate: ACCOUNT_DELEGATOR_1,
418
1
                delegator: ACCOUNT_DELEGATOR_1,
419
1
                join: true,
420
1
                auto: true,
421
1
                amount: share * 11,
422
1
                total_stake: share * 11,
423
1
                total_self: share * 11,
424
1
                rank_before: None,
425
1
                rank_after: Some(2),
426
1
            },
427
1
            Action {
428
1
                candidate: ACCOUNT_DELEGATOR_2,
429
1
                delegator: ACCOUNT_DELEGATOR_2,
430
1
                join: true,
431
1
                auto: true,
432
1
                amount: share * 10,
433
1
                total_stake: share * 10,
434
1
                total_self: share * 10,
435
1
                rank_before: None,
436
1
                rank_after: None, // list is full
437
1
            },
438
1
        ]);
439
1

            
440
1
        assert_eq!(
441
1
            SortedEligibleCandidates::<Runtime>::get().into_inner(),
442
1
            vec![
443
1
                EligibleCandidate {
444
1
                    candidate: ACCOUNT_CANDIDATE_2,
445
1
                    stake: share * 13,
446
1
                },
447
1
                EligibleCandidate {
448
1
                    candidate: ACCOUNT_CANDIDATE_1,
449
1
                    stake: share * 12,
450
1
                },
451
1
                EligibleCandidate {
452
1
                    candidate: ACCOUNT_DELEGATOR_1,
453
1
                    stake: share * 11,
454
1
                },
455
1
            ]
456
1
        );
457

            
458
        // We make candidate 1 leave, which doesn't make the out of list
459
        // candidate back in the list.
460
1
        perform_actions(&[Action {
461
1
            candidate: ACCOUNT_CANDIDATE_1,
462
1
            delegator: ACCOUNT_CANDIDATE_1,
463
1
            join: false,
464
1
            auto: true,
465
1
            amount: share * 11,
466
1
            total_stake: share * 1,
467
1
            total_self: 0,
468
1
            rank_before: Some(1),
469
1
            rank_after: None,
470
1
        }]);
471
1

            
472
1
        assert_eq!(
473
1
            SortedEligibleCandidates::<Runtime>::get().into_inner(),
474
1
            vec![
475
1
                EligibleCandidate {
476
1
                    candidate: ACCOUNT_CANDIDATE_2,
477
1
                    stake: share * 13,
478
1
                },
479
1
                EligibleCandidate {
480
1
                    candidate: ACCOUNT_DELEGATOR_1,
481
1
                    stake: share * 11,
482
1
                },
483
1
            ]
484
1
        );
485

            
486
        // It needs to be done manually.
487
1
        assert_ok!(Staking::update_candidate_position(
488
1
            RuntimeOrigin::signed(ACCOUNT_DELEGATOR_2),
489
1
            vec![ACCOUNT_DELEGATOR_2]
490
1
        ));
491

            
492
1
        assert_eq!(
493
1
            SortedEligibleCandidates::<Runtime>::get().into_inner(),
494
1
            vec![
495
1
                EligibleCandidate {
496
1
                    candidate: ACCOUNT_CANDIDATE_2,
497
1
                    stake: share * 13,
498
1
                },
499
1
                EligibleCandidate {
500
1
                    candidate: ACCOUNT_DELEGATOR_1,
501
1
                    stake: share * 11,
502
1
                },
503
1
                EligibleCandidate {
504
1
                    candidate: ACCOUNT_DELEGATOR_2,
505
1
                    stake: share * 10,
506
1
                },
507
1
            ]
508
1
        );
509
1
    })
510
1
}