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
            );
31

            
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

            
41
2
            assert_eq_events!(vec![
42
2
                Event::RequestedDelegate {
43
2
                    candidate: ACCOUNT_CANDIDATE_1,
44
2
                    delegator: ACCOUNT_CANDIDATE_1,
45
2
                    pool: P::target_pool(),
46
2
                    pending: round_down(requested_amount, 2),
47
2
                },
48
2
                P::event_staked(ACCOUNT_CANDIDATE_1, ACCOUNT_CANDIDATE_1, 9, final_amount),
49
2
                Event::ExecutedDelegate {
50
2
                    candidate: ACCOUNT_CANDIDATE_1,
51
2
                    delegator: ACCOUNT_CANDIDATE_1,
52
2
                    pool: P::target_pool(),
53
2
                    staked: final_amount,
54
2
                    released: round_down(requested_amount, 2) - final_amount,
55
2
                },
56
            ]);
57
2
        })
58
    }
59
);
60

            
61
pool_test!(
62
    fn self_delegation_above_minimum<P>() {
63
2
        ExtBuilder::default().build().execute_with(|| {
64
2
            let requested_amount = MinimumSelfDelegation::get();
65
2
            let final_amount = round_down(
66
2
                requested_amount,
67
2
                P::shares_to_stake_or_init(&ACCOUNT_CANDIDATE_1, Shares(1))
68
2
                    .unwrap()
69
2
                    .0,
70
            );
71

            
72
2
            FullDelegation {
73
2
                candidate: ACCOUNT_CANDIDATE_1,
74
2
                delegator: ACCOUNT_CANDIDATE_1,
75
2
                request_amount: requested_amount,
76
2
                expected_increase: final_amount,
77
2
                ..default()
78
2
            }
79
2
            .test::<P>();
80

            
81
2
            FullDelegation {
82
2
                candidate: ACCOUNT_CANDIDATE_1,
83
2
                delegator: ACCOUNT_CANDIDATE_1,
84
2
                request_amount: requested_amount,
85
2
                expected_increase: final_amount,
86
2
                ..default()
87
2
            }
88
2
            .test::<P>();
89

            
90
2
            FullUndelegation {
91
2
                candidate: ACCOUNT_CANDIDATE_1,
92
2
                delegator: ACCOUNT_CANDIDATE_1,
93
2
                request_amount: SharesOrStake::Stake(requested_amount * 2),
94
2
                expected_removed: requested_amount * 2,
95
2
                expected_leaving: round_down(requested_amount * 2, 3),
96
2
                ..default()
97
2
            }
98
2
            .test::<P>();
99

            
100
2
            assert_eq_events!(vec![
101
                // delegation 1
102
2
                Event::UpdatedCandidatePosition {
103
2
                    candidate: ACCOUNT_CANDIDATE_1,
104
2
                    stake: round_down(requested_amount, 2),
105
2
                    self_delegation: round_down(requested_amount, 2),
106
2
                    before: None,
107
2
                    after: Some(0),
108
2
                },
109
2
                Event::RequestedDelegate {
110
2
                    candidate: ACCOUNT_CANDIDATE_1,
111
2
                    delegator: ACCOUNT_CANDIDATE_1,
112
2
                    pool: P::target_pool(),
113
2
                    pending: round_down(requested_amount, 2),
114
2
                },
115
2
                P::event_staked(ACCOUNT_CANDIDATE_1, ACCOUNT_CANDIDATE_1, 10, final_amount),
116
2
                Event::ExecutedDelegate {
117
2
                    candidate: ACCOUNT_CANDIDATE_1,
118
2
                    delegator: ACCOUNT_CANDIDATE_1,
119
2
                    pool: P::target_pool(),
120
2
                    staked: final_amount,
121
2
                    released: round_down(requested_amount, 2) - final_amount,
122
2
                },
123
                // delegation 2
124
2
                Event::RequestedDelegate {
125
2
                    candidate: ACCOUNT_CANDIDATE_1,
126
2
                    delegator: ACCOUNT_CANDIDATE_1,
127
2
                    pool: P::target_pool(),
128
2
                    pending: round_down(requested_amount, 2),
129
2
                },
130
2
                P::event_staked(ACCOUNT_CANDIDATE_1, ACCOUNT_CANDIDATE_1, 10, final_amount),
131
2
                Event::ExecutedDelegate {
132
2
                    candidate: ACCOUNT_CANDIDATE_1,
133
2
                    delegator: ACCOUNT_CANDIDATE_1,
134
2
                    pool: P::target_pool(),
135
2
                    staked: final_amount,
136
2
                    released: round_down(requested_amount, 2) - final_amount,
137
2
                },
138
                // undelegation
139
2
                Event::UpdatedCandidatePosition {
140
2
                    candidate: ACCOUNT_CANDIDATE_1,
141
2
                    stake: 0,
142
2
                    self_delegation: 0,
143
2
                    before: Some(0),
144
2
                    after: None,
145
2
                },
146
2
                Event::RequestedUndelegate {
147
2
                    candidate: ACCOUNT_CANDIDATE_1,
148
2
                    delegator: ACCOUNT_CANDIDATE_1,
149
2
                    from: P::target_pool(),
150
2
                    pending: round_down(requested_amount * 2, 3),
151
2
                    released: 2,
152
2
                },
153
2
                Event::ExecutedUndelegate {
154
2
                    candidate: ACCOUNT_CANDIDATE_1,
155
2
                    delegator: ACCOUNT_CANDIDATE_1,
156
2
                    released: round_down(requested_amount * 2, 3)
157
2
                }
158
            ]);
159
2
        })
160
    }
161
);
162

            
163
#[test]
164
1
fn many_candidates_mixed_pools() {
165
1
    ExtBuilder::default().build().execute_with(|| {
166
1
        let share = InitialAutoCompoundingShareValue::get();
167
        // for simplicity we consider shares of both pools have the same value.
168
1
        assert_eq!(
169
1
            InitialAutoCompoundingShareValue::get(),
170
1
            InitialManualClaimShareValue::get()
171
        );
172

            
173
        struct Action {
174
            candidate: AccountId,
175
            delegator: AccountId,
176
            join: bool,
177
            auto: bool,
178
            amount: Balance,
179
            total_stake: Balance,
180
            total_self: Balance,
181

            
182
            rank_before: Option<u32>,
183
            rank_after: Option<u32>,
184
        }
185

            
186
2
        fn perform_actions(actions: &[Action]) {
187
2
            let share = InitialAutoCompoundingShareValue::get();
188
9
            for action in actions {
189
7
                match action {
190
                    Action {
191
                        join: true,
192
                        auto: true,
193
                        ..
194
                    } => {
195
4
                        FullDelegation {
196
4
                            candidate: action.candidate,
197
4
                            delegator: action.delegator,
198
4
                            request_amount: action.amount,
199
4
                            expected_increase: action.amount,
200
4
                            ..default()
201
4
                        }
202
4
                        .test::<pools::AutoCompounding<Runtime>>();
203

            
204
4
                        let mut expected_events = vec![];
205

            
206
4
                        if action.rank_before != action.rank_after {
207
3
                            expected_events.push(Event::<Runtime>::UpdatedCandidatePosition {
208
3
                                candidate: action.candidate,
209
3
                                stake: action.total_stake,
210
3
                                self_delegation: action.total_self,
211
3
                                before: action.rank_before,
212
3
                                after: action.rank_after,
213
3
                            });
214
3
                        }
215

            
216
4
                        expected_events.extend_from_slice(&vec![
217
4
                            Event::RequestedDelegate {
218
4
                                candidate: action.candidate,
219
4
                                delegator: action.delegator,
220
4
                                pool: ActivePoolKind::AutoCompounding,
221
4
                                pending: action.amount,
222
4
                            },
223
4
                            Event::StakedAutoCompounding {
224
4
                                candidate: action.candidate,
225
4
                                delegator: action.delegator,
226
4
                                shares: action.amount / share,
227
4
                                stake: action.amount,
228
4
                            },
229
4
                            Event::ExecutedDelegate {
230
4
                                candidate: action.candidate,
231
4
                                delegator: action.delegator,
232
4
                                pool: ActivePoolKind::AutoCompounding,
233
4
                                staked: action.amount,
234
4
                                released: 0,
235
4
                            },
236
4
                        ]);
237

            
238
4
                        assert_eq_last_events!(expected_events)
239
                    }
240
                    Action {
241
                        join: true,
242
                        auto: false,
243
                        ..
244
                    } => {
245
2
                        FullDelegation {
246
2
                            candidate: action.candidate,
247
2
                            delegator: action.delegator,
248
2
                            request_amount: action.amount,
249
2
                            expected_increase: action.amount,
250
2
                            ..default()
251
2
                        }
252
2
                        .test::<pools::ManualRewards<Runtime>>();
253

            
254
2
                        let mut expected_events = vec![];
255

            
256
2
                        if action.rank_before != action.rank_after {
257
1
                            expected_events.push(Event::<Runtime>::UpdatedCandidatePosition {
258
1
                                candidate: action.candidate,
259
1
                                stake: action.total_stake,
260
1
                                self_delegation: action.total_self,
261
1
                                before: action.rank_before,
262
1
                                after: action.rank_after,
263
1
                            });
264
1
                        }
265

            
266
2
                        expected_events.extend_from_slice(&vec![
267
2
                            Event::RequestedDelegate {
268
2
                                candidate: action.candidate,
269
2
                                delegator: action.delegator,
270
2
                                pool: ActivePoolKind::ManualRewards,
271
2
                                pending: action.amount,
272
2
                            },
273
2
                            Event::StakedManualRewards {
274
2
                                candidate: action.candidate,
275
2
                                delegator: action.delegator,
276
2
                                shares: action.amount / share,
277
2
                                stake: action.amount,
278
2
                            },
279
2
                            Event::ExecutedDelegate {
280
2
                                candidate: action.candidate,
281
2
                                delegator: action.delegator,
282
2
                                pool: ActivePoolKind::ManualRewards,
283
2
                                staked: action.amount,
284
2
                                released: 0,
285
2
                            },
286
2
                        ]);
287

            
288
2
                        assert_eq_last_events!(expected_events)
289
                    }
290
                    Action {
291
                        join: false,
292
                        auto: true,
293
                        ..
294
                    } => {
295
1
                        FullUndelegation {
296
1
                            candidate: action.candidate,
297
1
                            delegator: action.delegator,
298
1
                            request_amount: SharesOrStake::Stake(action.amount),
299
1
                            expected_removed: action.amount,
300
1
                            expected_leaving: round_down(action.amount, 3),
301
1
                            ..default()
302
1
                        }
303
1
                        .test::<pools::AutoCompounding<Runtime>>();
304

            
305
1
                        let mut expected_events = vec![];
306

            
307
1
                        if action.rank_before != action.rank_after {
308
1
                            expected_events.push(Event::<Runtime>::UpdatedCandidatePosition {
309
1
                                candidate: action.candidate,
310
1
                                stake: action.total_stake,
311
1
                                self_delegation: action.total_self,
312
1
                                before: action.rank_before,
313
1
                                after: action.rank_after,
314
1
                            });
315
1
                        }
316

            
317
1
                        expected_events.extend_from_slice(&vec![
318
1
                            Event::RequestedUndelegate {
319
1
                                candidate: action.candidate,
320
1
                                delegator: action.delegator,
321
1
                                from: ActivePoolKind::AutoCompounding,
322
1
                                pending: round_down(action.amount, 3),
323
1
                                released: action.amount - round_down(action.amount, 3),
324
1
                            },
325
1
                            Event::ExecutedUndelegate {
326
1
                                candidate: action.candidate,
327
1
                                delegator: action.delegator,
328
1
                                released: round_down(action.amount, 3),
329
1
                            },
330
1
                        ]);
331

            
332
1
                        assert_eq_last_events!(expected_events)
333
                    }
334
                    _ => todo!(),
335
                }
336
            }
337
2
        }
338

            
339
1
        perform_actions(&[
340
1
            Action {
341
1
                candidate: ACCOUNT_CANDIDATE_1,
342
1
                delegator: ACCOUNT_CANDIDATE_1,
343
1
                join: true,
344
1
                auto: true,
345
1
                amount: share * 11,
346
1
                total_stake: share * 11,
347
1
                total_self: share * 11,
348
1
                rank_before: None,
349
1
                rank_after: Some(0),
350
1
            },
351
1
            Action {
352
1
                candidate: ACCOUNT_CANDIDATE_2,
353
1
                delegator: ACCOUNT_CANDIDATE_2,
354
1
                join: true,
355
1
                auto: false,
356
1
                amount: share * 10,
357
1
                total_stake: share * 10,
358
1
                total_self: share * 10,
359
1
                rank_before: None,
360
1
                rank_after: Some(1),
361
1
            },
362
1
            Action {
363
1
                candidate: ACCOUNT_CANDIDATE_2,
364
1
                delegator: ACCOUNT_DELEGATOR_1,
365
1
                join: true,
366
1
                auto: true,
367
1
                amount: share * 3,
368
1
                total_stake: share * 13,
369
1
                total_self: share * 10,
370
1
                rank_before: Some(1),
371
1
                rank_after: Some(0),
372
1
            },
373
1
            Action {
374
1
                candidate: ACCOUNT_CANDIDATE_1,
375
1
                delegator: ACCOUNT_DELEGATOR_2,
376
1
                join: true,
377
1
                auto: false,
378
1
                amount: share,
379
1
                total_stake: share * 12,
380
1
                total_self: share * 11,
381
1
                rank_before: Some(1),
382
1
                rank_after: Some(1),
383
1
            },
384
1
            Action {
385
1
                candidate: ACCOUNT_DELEGATOR_1,
386
1
                delegator: ACCOUNT_DELEGATOR_1,
387
1
                join: true,
388
1
                auto: true,
389
1
                amount: share * 11,
390
1
                total_stake: share * 11,
391
1
                total_self: share * 11,
392
1
                rank_before: None,
393
1
                rank_after: Some(2),
394
1
            },
395
1
            Action {
396
1
                candidate: ACCOUNT_DELEGATOR_2,
397
1
                delegator: ACCOUNT_DELEGATOR_2,
398
1
                join: true,
399
1
                auto: true,
400
1
                amount: share * 10,
401
1
                total_stake: share * 10,
402
1
                total_self: share * 10,
403
1
                rank_before: None,
404
1
                rank_after: None, // list is full
405
1
            },
406
1
        ]);
407

            
408
1
        assert_eq!(
409
1
            SortedEligibleCandidates::<Runtime>::get().into_inner(),
410
1
            vec![
411
1
                EligibleCandidate {
412
1
                    candidate: ACCOUNT_CANDIDATE_2,
413
1
                    stake: share * 13,
414
1
                },
415
1
                EligibleCandidate {
416
1
                    candidate: ACCOUNT_CANDIDATE_1,
417
1
                    stake: share * 12,
418
1
                },
419
1
                EligibleCandidate {
420
1
                    candidate: ACCOUNT_DELEGATOR_1,
421
1
                    stake: share * 11,
422
1
                },
423
            ]
424
        );
425

            
426
        // We make candidate 1 leave, which doesn't make the out of list
427
        // candidate back in the list.
428
1
        perform_actions(&[Action {
429
1
            candidate: ACCOUNT_CANDIDATE_1,
430
1
            delegator: ACCOUNT_CANDIDATE_1,
431
1
            join: false,
432
1
            auto: true,
433
1
            amount: share * 11,
434
1
            total_stake: share * 1,
435
1
            total_self: 0,
436
1
            rank_before: Some(1),
437
1
            rank_after: None,
438
1
        }]);
439

            
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_DELEGATOR_1,
449
1
                    stake: share * 11,
450
1
                },
451
            ]
452
        );
453

            
454
        // It needs to be done manually.
455
1
        assert_ok!(Staking::update_candidate_position(
456
1
            RuntimeOrigin::signed(ACCOUNT_DELEGATOR_2),
457
1
            vec![ACCOUNT_DELEGATOR_2]
458
        ));
459

            
460
1
        assert_eq!(
461
1
            SortedEligibleCandidates::<Runtime>::get().into_inner(),
462
1
            vec![
463
1
                EligibleCandidate {
464
1
                    candidate: ACCOUNT_CANDIDATE_2,
465
1
                    stake: share * 13,
466
1
                },
467
1
                EligibleCandidate {
468
1
                    candidate: ACCOUNT_DELEGATOR_1,
469
1
                    stake: share * 11,
470
1
                },
471
1
                EligibleCandidate {
472
1
                    candidate: ACCOUNT_DELEGATOR_2,
473
1
                    stake: share * 10,
474
1
                },
475
            ]
476
        );
477
1
    })
478
1
}