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
#![cfg(test)]
18

            
19
use {
20
    crate::{tests::common::*, MinimumSelfDelegation, PooledStaking},
21
    frame_support::{assert_noop, assert_ok, error::BadOrigin},
22
    pallet_pooled_staking::{
23
        traits::IsCandidateEligible, ActivePoolKind, EligibleCandidate, PendingOperationKey,
24
        PendingOperationQuery, PoolKind, PoolsKey, SharesOrStake,
25
    },
26
    sp_std::vec,
27
};
28

            
29
#[test]
30
1
fn test_staking_no_candidates_in_genesis() {
31
1
    ExtBuilder::default()
32
1
        .with_balances(vec![
33
1
            // Alice gets 10k extra tokens for her mapping deposit
34
1
            (AccountId::from(ALICE), 210_000 * UNIT),
35
1
            (AccountId::from(BOB), 100_000 * UNIT),
36
1
            (AccountId::from(CHARLIE), 100_000 * UNIT),
37
1
            (AccountId::from(DAVE), 100_000 * UNIT),
38
1
        ])
39
1
        .with_collators(vec![
40
1
            (AccountId::from(ALICE), 210 * UNIT),
41
1
            (AccountId::from(BOB), 100 * UNIT),
42
1
            (AccountId::from(CHARLIE), 100 * UNIT),
43
1
            (AccountId::from(DAVE), 100 * UNIT),
44
1
        ])
45
1
        .with_empty_parachains(vec![1001, 1002])
46
1
        .build()
47
1
        .execute_with(|| {
48
1
            run_to_block(2);
49
1

            
50
1
            let initial_candidates =
51
1
                pallet_pooled_staking::SortedEligibleCandidates::<Runtime>::get().to_vec();
52
1

            
53
1
            assert_eq!(initial_candidates, vec![]);
54
1
        });
55
1
}
56

            
57
#[test]
58
1
fn test_staking_join() {
59
1
    ExtBuilder::default()
60
1
        .with_balances(vec![
61
1
            // Alice gets 10k extra tokens for her mapping deposit
62
1
            (AccountId::from(ALICE), 210_000 * UNIT),
63
1
            (AccountId::from(BOB), 100_000 * UNIT),
64
1
            (AccountId::from(CHARLIE), 100_000 * UNIT),
65
1
            (AccountId::from(DAVE), 100_000 * UNIT),
66
1
        ])
67
1
        .with_collators(vec![
68
1
            (AccountId::from(ALICE), 210 * UNIT),
69
1
            (AccountId::from(BOB), 100 * UNIT),
70
1
            (AccountId::from(CHARLIE), 100 * UNIT),
71
1
            (AccountId::from(DAVE), 100 * UNIT),
72
1
        ])
73
1
        .with_empty_parachains(vec![1001, 1002])
74
1
        .build()
75
1
        .execute_with(|| {
76
1
            run_to_block(2);
77
1

            
78
1
            let balance_before = System::account(AccountId::from(ALICE)).data.free;
79
1
            assert_eq!(System::account(AccountId::from(ALICE)).data.reserved, 0);
80
1
            let stake = MinimumSelfDelegation::get() * 10;
81
1
            assert_ok!(PooledStaking::request_delegate(
82
1
                origin_of(ALICE.into()),
83
1
                ALICE.into(),
84
1
                ActivePoolKind::AutoCompounding,
85
1
                stake
86
1
            ));
87

            
88
            // Immediately after joining, Alice is the top candidate
89
1
            let eligible_candidates =
90
1
                pallet_pooled_staking::SortedEligibleCandidates::<Runtime>::get().to_vec();
91
1
            assert_eq!(
92
1
                eligible_candidates,
93
1
                vec![EligibleCandidate {
94
1
                    candidate: ALICE.into(),
95
1
                    stake
96
1
                }]
97
1
            );
98

            
99
            // And staked amount is immediately marked as "reserved"
100
1
            let balance_after = System::account(AccountId::from(ALICE)).data.free;
101
1
            assert_eq!(balance_before - balance_after, stake);
102
1
            assert_eq!(System::account(AccountId::from(ALICE)).data.reserved, stake);
103
1
        });
104
1
}
105

            
106
#[test]
107
1
fn test_staking_join_no_keys_registered() {
108
1
    ExtBuilder::default()
109
1
        .with_balances(vec![
110
1
            // Alice gets 10k extra tokens for her mapping deposit
111
1
            (AccountId::from(ALICE), 210_000 * UNIT),
112
1
            (AccountId::from(BOB), 100_000 * UNIT),
113
1
            (AccountId::from(CHARLIE), 100_000 * UNIT),
114
1
            (AccountId::from(DAVE), 100_000 * UNIT),
115
1
        ])
116
1
        .with_collators(vec![
117
1
            (AccountId::from(ALICE), 210 * UNIT),
118
1
            (AccountId::from(BOB), 100 * UNIT),
119
1
            (AccountId::from(CHARLIE), 100 * UNIT),
120
1
            (AccountId::from(DAVE), 100 * UNIT),
121
1
        ])
122
1
        .with_empty_parachains(vec![
123
1
            1001,
124
1
            1002,
125
1
        ])
126
1

            
127
1
        .build()
128
1
        .execute_with(|| {
129
1
            run_to_block(2);
130
1

            
131
1
            let stake = MinimumSelfDelegation::get() * 10;
132
1
            let new_account = AccountId::from([42u8; 32]);
133
1
            assert_ok!(Balances::transfer_allow_death(
134
1
                origin_of(ALICE.into()),
135
1
                new_account.clone().into(),
136
1
                stake * 2
137
1
            ));
138
1
            let balance_before = System::account(new_account.clone()).data.free;
139
1
            assert_eq!(System::account(new_account.clone()).data.reserved, 0);
140
1
            assert_ok!(PooledStaking::request_delegate(
141
1
                origin_of(new_account.clone()),
142
1
                new_account.clone(),
143
1
                ActivePoolKind::AutoCompounding,
144
1
                stake
145
1
            ));
146

            
147
            // The new account should be the top candidate but it has no keys registered in
148
            // pallet_session, so it is not eligible
149
1
            assert!(!<Runtime as pallet_pooled_staking::Config>::EligibleCandidatesFilter::is_candidate_eligible(&new_account));
150
1
            let eligible_candidates =
151
1
                pallet_pooled_staking::SortedEligibleCandidates::<Runtime>::get().to_vec();
152
1

            
153
1
            assert_eq!(eligible_candidates, vec![]);
154

            
155
            // And staked amount is immediately marked as "reserved"
156
1
            let balance_after = System::account(new_account.clone()).data.free;
157
1
            assert_eq!(balance_before - balance_after, stake);
158
1
            assert_eq!(System::account(new_account.clone()).data.reserved, stake);
159
1
        });
160
1
}
161

            
162
#[test]
163
1
fn test_staking_register_keys_after_joining() {
164
1
    ExtBuilder::default()
165
1
        .with_balances(vec![
166
1
            // Alice gets 10k extra tokens for her mapping deposit
167
1
            (AccountId::from(ALICE), 210_000 * UNIT),
168
1
            (AccountId::from(BOB), 100_000 * UNIT),
169
1
            (AccountId::from(CHARLIE), 100_000 * UNIT),
170
1
            (AccountId::from(DAVE), 100_000 * UNIT),
171
1
        ])
172
1
        .with_collators(vec![
173
1
            (AccountId::from(ALICE), 210 * UNIT),
174
1
            (AccountId::from(BOB), 100 * UNIT),
175
1
            (AccountId::from(CHARLIE), 100 * UNIT),
176
1
            (AccountId::from(DAVE), 100 * UNIT),
177
1
        ])
178
1
        .with_empty_parachains(vec![
179
1
            1001,
180
1
            1002,
181
1
        ])
182
1

            
183
1
        .build()
184
1
        .execute_with(|| {
185
1
            run_to_block(2);
186
1

            
187
1
            let stake = MinimumSelfDelegation::get() * 10;
188
1
            let new_account = AccountId::from([42u8; 32]);
189
1
            assert_ok!(Balances::transfer_allow_death(
190
1
                origin_of(ALICE.into()),
191
1
                new_account.clone().into(),
192
1
                stake * 2
193
1
            ));
194
1
            let balance_before = System::account(new_account.clone()).data.free;
195
1
            assert_eq!(System::account(new_account.clone()).data.reserved, 0);
196
1
            assert_ok!(PooledStaking::request_delegate(
197
1
                origin_of(new_account.clone()),
198
1
                new_account.clone(),
199
1
                ActivePoolKind::AutoCompounding,
200
1
                stake
201
1
            ));
202

            
203
            // The new account should be the top candidate but it has no keys registered in
204
            // pallet_session, so it is not eligible
205
1
            assert!(!<Runtime as pallet_pooled_staking::Config>::EligibleCandidatesFilter::is_candidate_eligible(&new_account));
206
1
            let eligible_candidates =
207
1
                pallet_pooled_staking::SortedEligibleCandidates::<Runtime>::get().to_vec();
208
1
            assert_eq!(eligible_candidates, vec![]);
209

            
210
            // And staked amount is immediately marked as "reserved"
211
1
            let balance_after = System::account(new_account.clone()).data.free;
212
1
            assert_eq!(balance_before - balance_after, stake);
213
1
            assert_eq!(System::account(new_account.clone()).data.reserved, stake);
214

            
215
            // Now register the keys
216
1
            let new_keys = get_authority_keys_from_seed(&new_account.to_string());
217
1
            assert_ok!(Session::set_keys(
218
1
                origin_of(new_account.clone()),
219
1
                crate::SessionKeys {
220
1
                    grandpa: new_keys.grandpa,babe: new_keys.babe,para_validator: new_keys.para_validator,para_assignment: new_keys.para_assignment,authority_discovery: new_keys.authority_discovery,beefy: new_keys.beefy,nimbus: new_keys.nimbus,
221
1
                },
222
1
                vec![]
223
1
            ));
224

            
225
            // Now eligible according to filter
226
1
            assert!(<Runtime as pallet_pooled_staking::Config>::EligibleCandidatesFilter::is_candidate_eligible(&new_account));
227
            // But not eligible according to pallet_pooled_staking, need to manually update candidate list
228
1
            let eligible_candidates =
229
1
                pallet_pooled_staking::SortedEligibleCandidates::<Runtime>::get().to_vec();
230
1
            assert_eq!(eligible_candidates, vec![]);
231

            
232
            // Update candidate list
233
1
            assert_ok!(PooledStaking::update_candidate_position(
234
1
                origin_of(BOB.into()),
235
1
                vec![new_account.clone()]
236
1
            ));
237

            
238
            // Now it is eligible
239
1
            let eligible_candidates =
240
1
                pallet_pooled_staking::SortedEligibleCandidates::<Runtime>::get().to_vec();
241
1
            assert_eq!(
242
1
                eligible_candidates,
243
1
                vec![EligibleCandidate {
244
1
                    candidate: new_account.clone(),
245
1
                    stake
246
1
                }]
247
1
            );
248
1
        });
249
1
}
250

            
251
#[test]
252
1
fn test_staking_join_bad_origin() {
253
1
    ExtBuilder::default()
254
1
        .with_balances(vec![
255
1
            // Alice gets 10k extra tokens for her mapping deposit
256
1
            (AccountId::from(ALICE), 210_000 * UNIT),
257
1
            (AccountId::from(BOB), 100_000 * UNIT),
258
1
            (AccountId::from(CHARLIE), 100_000 * UNIT),
259
1
            (AccountId::from(DAVE), 100_000 * UNIT),
260
1
        ])
261
1
        .with_collators(vec![
262
1
            (AccountId::from(ALICE), 210 * UNIT),
263
1
            (AccountId::from(BOB), 100 * UNIT),
264
1
            (AccountId::from(CHARLIE), 100 * UNIT),
265
1
            (AccountId::from(DAVE), 100 * UNIT),
266
1
        ])
267
1
        .with_empty_parachains(vec![1001, 1002])
268
1
        .build()
269
1
        .execute_with(|| {
270
1
            run_to_block(2);
271
1

            
272
1
            let stake = 10 * MinimumSelfDelegation::get();
273
1
            assert_noop!(
274
1
                PooledStaking::request_delegate(
275
1
                    root_origin(),
276
1
                    ALICE.into(),
277
1
                    ActivePoolKind::AutoCompounding,
278
1
                    stake
279
1
                ),
280
1
                BadOrigin,
281
1
            );
282
1
        });
283
1
}
284

            
285
#[test]
286
1
fn test_staking_join_below_self_delegation_min() {
287
1
    ExtBuilder::default()
288
1
        .with_balances(vec![
289
1
            // Alice gets 10k extra tokens for her mapping deposit
290
1
            (AccountId::from(ALICE), 210_000 * UNIT),
291
1
            (AccountId::from(BOB), 100_000 * UNIT),
292
1
            (AccountId::from(CHARLIE), 100_000 * UNIT),
293
1
            (AccountId::from(DAVE), 100_000 * UNIT),
294
1
        ])
295
1
        .with_collators(vec![
296
1
            (AccountId::from(ALICE), 210 * UNIT),
297
1
            (AccountId::from(BOB), 100 * UNIT),
298
1
            (AccountId::from(CHARLIE), 100 * UNIT),
299
1
            (AccountId::from(DAVE), 100 * UNIT),
300
1
        ])
301
1
        .with_empty_parachains(vec![1001, 1002])
302
1
        .build()
303
1
        .execute_with(|| {
304
1
            run_to_block(2);
305
1

            
306
1
            let stake1 = MinimumSelfDelegation::get() / 3;
307
1
            assert_ok!(PooledStaking::request_delegate(
308
1
                origin_of(ALICE.into()),
309
1
                ALICE.into(),
310
1
                ActivePoolKind::AutoCompounding,
311
1
                stake1
312
1
            ));
313

            
314
            // Since stake is below MinimumSelfDelegation, the join operation succeeds
315
            // but the candidate is not eligible
316
1
            let eligible_candidates =
317
1
                pallet_pooled_staking::SortedEligibleCandidates::<Runtime>::get().to_vec();
318
1
            assert_eq!(eligible_candidates, vec![],);
319

            
320
1
            let stake2 = MinimumSelfDelegation::get() - stake1 - 1;
321
1
            assert_ok!(PooledStaking::request_delegate(
322
1
                origin_of(ALICE.into()),
323
1
                ALICE.into(),
324
1
                ActivePoolKind::AutoCompounding,
325
1
                stake2,
326
1
            ));
327

            
328
            // Still below, missing 1 unit
329
1
            let eligible_candidates =
330
1
                pallet_pooled_staking::SortedEligibleCandidates::<Runtime>::get().to_vec();
331
1
            assert_eq!(eligible_candidates, vec![],);
332

            
333
1
            let stake3 = 1;
334
1
            assert_ok!(PooledStaking::request_delegate(
335
1
                origin_of(ALICE.into()),
336
1
                ALICE.into(),
337
1
                ActivePoolKind::AutoCompounding,
338
1
                stake3,
339
1
            ));
340

            
341
            // Increasing the stake to above MinimumSelfDelegation makes the candidate eligible
342
1
            let eligible_candidates =
343
1
                pallet_pooled_staking::SortedEligibleCandidates::<Runtime>::get().to_vec();
344
1
            assert_eq!(
345
1
                eligible_candidates,
346
1
                vec![EligibleCandidate {
347
1
                    candidate: ALICE.into(),
348
1
                    stake: stake1 + stake2 + stake3
349
1
                }],
350
1
            );
351
1
        });
352
1
}
353

            
354
#[test]
355
1
fn test_staking_join_no_self_delegation() {
356
1
    ExtBuilder::default()
357
1
        .with_balances(vec![
358
1
            // Alice gets 10k extra tokens for her mapping deposit
359
1
            (AccountId::from(ALICE), 210_000_000_000 * UNIT),
360
1
            (AccountId::from(BOB), 100_000_000_000 * UNIT),
361
1
            (AccountId::from(CHARLIE), 100_000_000_000 * UNIT),
362
1
            (AccountId::from(DAVE), 100_000_000_000 * UNIT),
363
1
        ])
364
1
        .with_collators(vec![
365
1
            (AccountId::from(ALICE), 210_000_000 * UNIT),
366
1
            (AccountId::from(BOB), 100_000_000 * UNIT),
367
1
            (AccountId::from(CHARLIE), 100_000_000 * UNIT),
368
1
            (AccountId::from(DAVE), 100_000_000 * UNIT),
369
1
        ])
370
1
        .with_empty_parachains(vec![1001, 1002])
371
1
        .build()
372
1
        .execute_with(|| {
373
1
            run_to_block(2);
374
1

            
375
1
            // Bob delegates to Alice, but Alice is not a valid candidate (not enough self-delegation)
376
1
            let stake = 10 * MinimumSelfDelegation::get();
377
1
            assert_ok!(PooledStaking::request_delegate(
378
1
                origin_of(BOB.into()),
379
1
                ALICE.into(),
380
1
                ActivePoolKind::AutoCompounding,
381
1
                stake,
382
1
            ));
383

            
384
1
            let eligible_candidates =
385
1
                pallet_pooled_staking::SortedEligibleCandidates::<Runtime>::get().to_vec();
386
1
            assert_eq!(eligible_candidates, vec![],);
387
1
        });
388
1
}
389

            
390
#[test]
391
1
fn test_staking_join_before_self_delegation() {
392
1
    ExtBuilder::default()
393
1
        .with_balances(vec![
394
1
            // Alice gets 10k extra tokens for her mapping deposit
395
1
            (AccountId::from(ALICE), 210_000_000_000 * UNIT),
396
1
            (AccountId::from(BOB), 100_000_000_000 * UNIT),
397
1
            (AccountId::from(CHARLIE), 100_000_000_000 * UNIT),
398
1
            (AccountId::from(DAVE), 100_000_000_000 * UNIT),
399
1
        ])
400
1
        .with_collators(vec![
401
1
            (AccountId::from(ALICE), 210_000_000 * UNIT),
402
1
            (AccountId::from(BOB), 100_000_000 * UNIT),
403
1
            (AccountId::from(CHARLIE), 100_000_000 * UNIT),
404
1
            (AccountId::from(DAVE), 100_000_000 * UNIT),
405
1
        ])
406
1
        .with_empty_parachains(vec![1001, 1002])
407
1
        .build()
408
1
        .execute_with(|| {
409
1
            run_to_block(2);
410
1

            
411
1
            // Bob delegates to Alice, but Alice is not a valid candidate (not enough self-delegation)
412
1
            let stake = 10 * MinimumSelfDelegation::get();
413
1
            assert_ok!(PooledStaking::request_delegate(
414
1
                origin_of(BOB.into()),
415
1
                ALICE.into(),
416
1
                ActivePoolKind::AutoCompounding,
417
1
                stake
418
1
            ));
419

            
420
1
            let eligible_candidates =
421
1
                pallet_pooled_staking::SortedEligibleCandidates::<Runtime>::get().to_vec();
422
1
            assert_eq!(eligible_candidates, vec![],);
423

            
424
1
            run_to_session(2);
425
1
            assert_ok!(PooledStaking::execute_pending_operations(
426
1
                origin_of(ALICE.into()),
427
1
                vec![PendingOperationQuery {
428
1
                    delegator: BOB.into(),
429
1
                    operation: PendingOperationKey::JoiningAutoCompounding {
430
1
                        candidate: ALICE.into(),
431
1
                        at: 0,
432
1
                    }
433
1
                }]
434
1
            ),);
435

            
436
            // Now Alice joins with enough self-delegation
437
1
            assert_ok!(PooledStaking::request_delegate(
438
1
                origin_of(ALICE.into()),
439
1
                ALICE.into(),
440
1
                ActivePoolKind::AutoCompounding,
441
1
                stake
442
1
            ));
443

            
444
            // Alice is a valid candidate, and Bob's stake is also counted
445
1
            let eligible_candidates =
446
1
                pallet_pooled_staking::SortedEligibleCandidates::<Runtime>::get().to_vec();
447
1
            assert_eq!(
448
1
                eligible_candidates,
449
1
                vec![EligibleCandidate {
450
1
                    candidate: ALICE.into(),
451
1
                    stake: stake * 2,
452
1
                }],
453
1
            );
454
1
        });
455
1
}
456

            
457
#[test]
458
1
fn test_staking_join_twice_in_same_block() {
459
1
    ExtBuilder::default()
460
1
        .with_balances(vec![
461
1
            // Alice gets 10k extra tokens for her mapping deposit
462
1
            (AccountId::from(ALICE), 210_000 * UNIT),
463
1
            (AccountId::from(BOB), 100_000 * UNIT),
464
1
            (AccountId::from(CHARLIE), 100_000 * UNIT),
465
1
            (AccountId::from(DAVE), 100_000 * UNIT),
466
1
        ])
467
1
        .with_collators(vec![
468
1
            (AccountId::from(ALICE), 210 * UNIT),
469
1
            (AccountId::from(BOB), 100 * UNIT),
470
1
            (AccountId::from(CHARLIE), 100 * UNIT),
471
1
            (AccountId::from(DAVE), 100 * UNIT),
472
1
        ])
473
1
        .with_empty_parachains(vec![1001, 1002])
474
1
        .build()
475
1
        .execute_with(|| {
476
1
            run_to_block(2);
477
1

            
478
1
            let stake1 = 10 * MinimumSelfDelegation::get();
479
1
            assert_ok!(PooledStaking::request_delegate(
480
1
                origin_of(ALICE.into()),
481
1
                ALICE.into(),
482
1
                ActivePoolKind::AutoCompounding,
483
1
                stake1
484
1
            ));
485

            
486
1
            let stake2 = 9 * MinimumSelfDelegation::get();
487
1
            assert_ok!(PooledStaking::request_delegate(
488
1
                origin_of(ALICE.into()),
489
1
                ALICE.into(),
490
1
                ActivePoolKind::AutoCompounding,
491
1
                stake2
492
1
            ));
493

            
494
            // Both operations succeed and the total stake is the sum of the individual stakes
495
1
            let eligible_candidates =
496
1
                pallet_pooled_staking::SortedEligibleCandidates::<Runtime>::get().to_vec();
497
1

            
498
1
            assert_eq!(
499
1
                eligible_candidates,
500
1
                vec![EligibleCandidate {
501
1
                    candidate: ALICE.into(),
502
1
                    stake: stake1 + stake2,
503
1
                }]
504
1
            );
505

            
506
1
            run_to_session(2);
507
1

            
508
1
            assert_ok!(PooledStaking::execute_pending_operations(
509
1
                origin_of(ALICE.into()),
510
1
                vec![PendingOperationQuery {
511
1
                    delegator: ALICE.into(),
512
1
                    operation: PendingOperationKey::JoiningAutoCompounding {
513
1
                        candidate: ALICE.into(),
514
1
                        at: 0,
515
1
                    }
516
1
                }]
517
1
            ),);
518

            
519
            // TODO: ensure the total stake has been moved to auto compounding pool
520
1
        });
521
1
}
522

            
523
#[test]
524
1
fn test_staking_join_execute_before_time() {
525
1
    ExtBuilder::default()
526
1
        .with_balances(vec![
527
1
            // Alice gets 10k extra tokens for her mapping deposit
528
1
            (AccountId::from(ALICE), 210_000 * UNIT),
529
1
            (AccountId::from(BOB), 100_000 * UNIT),
530
1
            (AccountId::from(CHARLIE), 100_000 * UNIT),
531
1
            (AccountId::from(DAVE), 100_000 * UNIT),
532
1
        ])
533
1
        .with_collators(vec![
534
1
            (AccountId::from(ALICE), 210 * UNIT),
535
1
            (AccountId::from(BOB), 100 * UNIT),
536
1
            (AccountId::from(CHARLIE), 100 * UNIT),
537
1
            (AccountId::from(DAVE), 100 * UNIT),
538
1
        ])
539
1
        .with_empty_parachains(vec![1001, 1002])
540
1
        .build()
541
1
        .execute_with(|| {
542
1
            run_to_block(2);
543
1

            
544
1
            let stake = 10 * MinimumSelfDelegation::get();
545
1
            assert_ok!(PooledStaking::request_delegate(
546
1
                origin_of(ALICE.into()),
547
1
                ALICE.into(),
548
1
                ActivePoolKind::AutoCompounding,
549
1
                stake
550
1
            ));
551

            
552
            // Immediately after joining, Alice is the top candidate
553
1
            let eligible_candidates =
554
1
                pallet_pooled_staking::SortedEligibleCandidates::<Runtime>::get().to_vec();
555
1
            assert_eq!(
556
1
                eligible_candidates,
557
1
                vec![EligibleCandidate {
558
1
                    candidate: ALICE.into(),
559
1
                    stake
560
1
                }]
561
1
            );
562

            
563
            // We called request_delegate in session 0, we will be able to execute it starting from session 2
564
1
            let start_of_session_2 = session_to_block(2);
565
1
            // Session 2 starts at block 600, but run_to_session runs to block 601, so subtract 2 here to go to 599
566
1
            run_to_block(start_of_session_2 - 2);
567
1
            assert_noop!(
568
1
                PooledStaking::execute_pending_operations(
569
1
                    origin_of(ALICE.into()),
570
1
                    vec![PendingOperationQuery {
571
1
                        delegator: ALICE.into(),
572
1
                        operation: PendingOperationKey::JoiningAutoCompounding {
573
1
                            candidate: ALICE.into(),
574
1
                            at: 0,
575
1
                        }
576
1
                    }]
577
1
                ),
578
1
                pallet_pooled_staking::Error::<Runtime>::RequestCannotBeExecuted(0),
579
1
            );
580

            
581
1
            run_to_block(start_of_session_2);
582
1
            assert_ok!(PooledStaking::execute_pending_operations(
583
1
                origin_of(ALICE.into()),
584
1
                vec![PendingOperationQuery {
585
1
                    delegator: ALICE.into(),
586
1
                    operation: PendingOperationKey::JoiningAutoCompounding {
587
1
                        candidate: ALICE.into(),
588
1
                        at: 0,
589
1
                    }
590
1
                }]
591
1
            ),);
592
1
        });
593
1
}
594

            
595
#[test]
596
1
fn test_staking_join_execute_any_origin() {
597
1
    ExtBuilder::default()
598
1
        .with_balances(vec![
599
1
            // Alice gets 10k extra tokens for her mapping deposit
600
1
            (AccountId::from(ALICE), 210_000 * UNIT),
601
1
            (AccountId::from(BOB), 100_000 * UNIT),
602
1
            (AccountId::from(CHARLIE), 100_000 * UNIT),
603
1
            (AccountId::from(DAVE), 100_000 * UNIT),
604
1
        ])
605
1
        .with_collators(vec![
606
1
            (AccountId::from(ALICE), 210 * UNIT),
607
1
            (AccountId::from(BOB), 100 * UNIT),
608
1
            (AccountId::from(CHARLIE), 100 * UNIT),
609
1
            (AccountId::from(DAVE), 100 * UNIT),
610
1
        ])
611
1
        .with_empty_parachains(vec![1001, 1002])
612
1
        .build()
613
1
        .execute_with(|| {
614
1
            run_to_block(2);
615
1

            
616
1
            let stake = 10 * MinimumSelfDelegation::get();
617
1
            assert_ok!(PooledStaking::request_delegate(
618
1
                origin_of(ALICE.into()),
619
1
                ALICE.into(),
620
1
                ActivePoolKind::AutoCompounding,
621
1
                stake
622
1
            ));
623

            
624
            // Immediately after joining, Alice is the top candidate
625
1
            let eligible_candidates =
626
1
                pallet_pooled_staking::SortedEligibleCandidates::<Runtime>::get().to_vec();
627
1
            assert_eq!(
628
1
                eligible_candidates,
629
1
                vec![EligibleCandidate {
630
1
                    candidate: ALICE.into(),
631
1
                    stake
632
1
                }]
633
1
            );
634

            
635
            // We called request_delegate in session 0, we will be able to execute it starting from session 2
636
1
            run_to_session(2);
637
1
            // Anyone can execute pending operations for anyone else
638
1
            assert_ok!(PooledStaking::execute_pending_operations(
639
1
                origin_of(BOB.into()),
640
1
                vec![PendingOperationQuery {
641
1
                    delegator: ALICE.into(),
642
1
                    operation: PendingOperationKey::JoiningAutoCompounding {
643
1
                        candidate: ALICE.into(),
644
1
                        at: 0,
645
1
                    }
646
1
                }]
647
1
            ),);
648
1
        });
649
1
}
650

            
651
#[test]
652
1
fn test_staking_join_execute_bad_origin() {
653
1
    ExtBuilder::default()
654
1
        .with_balances(vec![
655
1
            // Alice gets 10k extra tokens for her mapping deposit
656
1
            (AccountId::from(ALICE), 210_000 * UNIT),
657
1
            (AccountId::from(BOB), 100_000 * UNIT),
658
1
            (AccountId::from(CHARLIE), 100_000 * UNIT),
659
1
            (AccountId::from(DAVE), 100_000 * UNIT),
660
1
        ])
661
1
        .with_collators(vec![
662
1
            (AccountId::from(ALICE), 210 * UNIT),
663
1
            (AccountId::from(BOB), 100 * UNIT),
664
1
            (AccountId::from(CHARLIE), 100 * UNIT),
665
1
            (AccountId::from(DAVE), 100 * UNIT),
666
1
        ])
667
1
        .with_empty_parachains(vec![1001, 1002])
668
1
        .build()
669
1
        .execute_with(|| {
670
1
            run_to_block(2);
671
1

            
672
1
            let stake = 10 * MinimumSelfDelegation::get();
673
1
            assert_ok!(PooledStaking::request_delegate(
674
1
                origin_of(ALICE.into()),
675
1
                ALICE.into(),
676
1
                ActivePoolKind::AutoCompounding,
677
1
                stake
678
1
            ));
679

            
680
            // Immediately after joining, Alice is the top candidate
681
1
            let eligible_candidates =
682
1
                pallet_pooled_staking::SortedEligibleCandidates::<Runtime>::get().to_vec();
683
1
            assert_eq!(
684
1
                eligible_candidates,
685
1
                vec![EligibleCandidate {
686
1
                    candidate: ALICE.into(),
687
1
                    stake
688
1
                }]
689
1
            );
690

            
691
            // We called request_delegate in session 0, we will be able to execute it starting from session 2
692
1
            run_to_session(2);
693
1
            assert_noop!(
694
1
                PooledStaking::execute_pending_operations(
695
1
                    root_origin(),
696
1
                    vec![PendingOperationQuery {
697
1
                        delegator: ALICE.into(),
698
1
                        operation: PendingOperationKey::JoiningAutoCompounding {
699
1
                            candidate: ALICE.into(),
700
1
                            at: 0,
701
1
                        }
702
1
                    }]
703
1
                ),
704
1
                BadOrigin,
705
1
            );
706
1
        });
707
1
}
708

            
709
struct A {
710
    delegator: AccountId,
711
    candidate: AccountId,
712
    target_pool: ActivePoolKind,
713
    stake: u128,
714
}
715

            
716
// Setup test environment with provided delegations already being executed. Input function f gets executed at start session 2
717
9
fn setup_staking_join_and_execute<R>(ops: Vec<A>, f: impl FnOnce() -> R) {
718
9
    ExtBuilder::default()
719
9
        .with_balances(vec![
720
9
            // Alice gets 10k extra tokens for her mapping deposit
721
9
            (AccountId::from(ALICE), 210_000 * UNIT),
722
9
            (AccountId::from(BOB), 100_000 * UNIT),
723
9
            (AccountId::from(CHARLIE), 100_000 * UNIT),
724
9
            (AccountId::from(DAVE), 100_000 * UNIT),
725
9
        ])
726
9
        .with_collators(vec![
727
9
            (AccountId::from(ALICE), 210 * UNIT),
728
9
            (AccountId::from(BOB), 100 * UNIT),
729
9
            (AccountId::from(CHARLIE), 100 * UNIT),
730
9
            (AccountId::from(DAVE), 100 * UNIT),
731
9
        ])
732
9
        .with_empty_parachains(vec![1001, 1002])
733
9
        .build()
734
9
        .execute_with(|| {
735
9
            run_to_block(2);
736

            
737
9
            for op in ops.iter() {
738
9
                assert_ok!(PooledStaking::request_delegate(
739
9
                    origin_of(op.delegator.clone()),
740
9
                    op.candidate.clone(),
741
9
                    op.target_pool,
742
9
                    op.stake,
743
9
                ));
744
            }
745

            
746
            // We called request_delegate in session 0, we will be able to execute it starting from session 2
747
9
            run_to_session(2);
748

            
749
9
            for op in ops.iter() {
750
9
                let operation = match op.target_pool {
751
                    ActivePoolKind::AutoCompounding => {
752
9
                        PendingOperationKey::JoiningAutoCompounding {
753
9
                            candidate: op.candidate.clone(),
754
9
                            at: 0,
755
9
                        }
756
                    }
757
                    ActivePoolKind::ManualRewards => PendingOperationKey::JoiningManualRewards {
758
                        candidate: op.candidate.clone(),
759
                        at: 0,
760
                    },
761
                };
762

            
763
9
                assert_ok!(PooledStaking::execute_pending_operations(
764
9
                    origin_of(op.delegator.clone()),
765
9
                    vec![PendingOperationQuery {
766
9
                        delegator: op.delegator.clone(),
767
9
                        operation,
768
9
                    }]
769
9
                ));
770
            }
771

            
772
9
            f()
773
9
        });
774
9
}
775

            
776
#[test]
777
1
fn test_staking_leave_exact_amount() {
778
1
    setup_staking_join_and_execute(
779
1
        vec![A {
780
1
            delegator: ALICE.into(),
781
1
            candidate: ALICE.into(),
782
1
            target_pool: ActivePoolKind::AutoCompounding,
783
1
            stake: 10 * MinimumSelfDelegation::get(),
784
1
        }],
785
1
        || {
786
1
            let stake = 10 * MinimumSelfDelegation::get();
787
1
            assert_ok!(PooledStaking::request_undelegate(
788
1
                origin_of(ALICE.into()),
789
1
                ALICE.into(),
790
1
                ActivePoolKind::AutoCompounding,
791
1
                SharesOrStake::Stake(stake),
792
1
            ));
793

            
794
            // Immediately after calling request_undelegate, Alice is no longer a candidate
795
1
            let eligible_candidates =
796
1
                pallet_pooled_staking::SortedEligibleCandidates::<Runtime>::get().to_vec();
797
1
            assert_eq!(eligible_candidates, vec![]);
798
1
        },
799
1
    )
800
1
}
801

            
802
#[test]
803
1
fn test_staking_leave_bad_origin() {
804
1
    setup_staking_join_and_execute(
805
1
        vec![A {
806
1
            delegator: ALICE.into(),
807
1
            candidate: ALICE.into(),
808
1
            target_pool: ActivePoolKind::AutoCompounding,
809
1
            stake: 10 * MinimumSelfDelegation::get(),
810
1
        }],
811
1
        || {
812
1
            let stake = 10 * MinimumSelfDelegation::get();
813
1
            assert_noop!(
814
1
                PooledStaking::request_undelegate(
815
1
                    root_origin(),
816
1
                    ALICE.into(),
817
1
                    ActivePoolKind::AutoCompounding,
818
1
                    SharesOrStake::Stake(stake),
819
1
                ),
820
1
                BadOrigin
821
1
            );
822
1
        },
823
1
    )
824
1
}
825

            
826
#[test]
827
1
fn test_staking_leave_more_than_allowed() {
828
1
    setup_staking_join_and_execute(
829
1
        vec![A {
830
1
            delegator: ALICE.into(),
831
1
            candidate: ALICE.into(),
832
1
            target_pool: ActivePoolKind::AutoCompounding,
833
1
            stake: 10 * MinimumSelfDelegation::get(),
834
1
        }],
835
1
        || {
836
1
            let stake = 10 * MinimumSelfDelegation::get();
837
1
            assert_noop!(
838
1
                PooledStaking::request_undelegate(
839
1
                    origin_of(ALICE.into()),
840
1
                    ALICE.into(),
841
1
                    ActivePoolKind::AutoCompounding,
842
1
                    SharesOrStake::Stake(stake + 1 * MinimumSelfDelegation::get()),
843
1
                ),
844
1
                pallet_pooled_staking::Error::<Runtime>::MathUnderflow,
845
1
            );
846
1
        },
847
1
    );
848
1
}
849

            
850
#[test]
851
1
fn test_staking_leave_in_separate_transactions() {
852
1
    let stake = 10 * MinimumSelfDelegation::get();
853
1

            
854
1
    setup_staking_join_and_execute(
855
1
        vec![A {
856
1
            delegator: ALICE.into(),
857
1
            candidate: ALICE.into(),
858
1
            target_pool: ActivePoolKind::AutoCompounding,
859
1
            stake,
860
1
        }],
861
1
        || {
862
1
            let half_stake = stake / 2;
863
1
            assert_ok!(PooledStaking::request_undelegate(
864
1
                origin_of(ALICE.into()),
865
1
                ALICE.into(),
866
1
                ActivePoolKind::AutoCompounding,
867
1
                SharesOrStake::Stake(half_stake),
868
1
            ));
869

            
870
            // Alice is still a valid candidate, now with less stake
871
1
            let eligible_candidates =
872
1
                pallet_pooled_staking::SortedEligibleCandidates::<Runtime>::get().to_vec();
873
1
            let remaining_stake = stake - half_stake;
874
1
            assert_eq!(
875
1
                eligible_candidates,
876
1
                vec![EligibleCandidate {
877
1
                    candidate: ALICE.into(),
878
1
                    stake: remaining_stake,
879
1
                }],
880
1
            );
881

            
882
1
            assert_ok!(PooledStaking::request_undelegate(
883
1
                origin_of(ALICE.into()),
884
1
                ALICE.into(),
885
1
                ActivePoolKind::AutoCompounding,
886
1
                SharesOrStake::Stake(remaining_stake),
887
1
            ));
888

            
889
            // Unstaked remaining stake, so no longer a valid candidate
890
1
            let eligible_candidates =
891
1
                pallet_pooled_staking::SortedEligibleCandidates::<Runtime>::get().to_vec();
892
1
            assert_eq!(eligible_candidates, vec![],);
893
1
        },
894
1
    );
895
1
}
896

            
897
#[test]
898
1
fn test_staking_leave_all_except_some_dust() {
899
1
    let stake = 10 * MinimumSelfDelegation::get();
900
1

            
901
1
    setup_staking_join_and_execute(
902
1
        vec![A {
903
1
            delegator: ALICE.into(),
904
1
            candidate: ALICE.into(),
905
1
            target_pool: ActivePoolKind::AutoCompounding,
906
1
            stake,
907
1
        }],
908
1
        || {
909
1
            let dust = MinimumSelfDelegation::get() / 2;
910
1
            assert_ok!(PooledStaking::request_undelegate(
911
1
                origin_of(ALICE.into()),
912
1
                ALICE.into(),
913
1
                ActivePoolKind::AutoCompounding,
914
1
                SharesOrStake::Stake(stake - dust),
915
1
            ));
916

            
917
            // Alice still has some stake left, but not enough to reach MinimumSelfDelegation
918
1
            assert_eq!(
919
1
                pallet_pooled_staking::Pools::<Runtime>::get(
920
1
                    AccountId::from(ALICE),
921
1
                    PoolsKey::CandidateTotalStake
922
1
                ),
923
1
                dust,
924
1
            );
925

            
926
1
            let eligible_candidates =
927
1
                pallet_pooled_staking::SortedEligibleCandidates::<Runtime>::get().to_vec();
928
1
            assert_eq!(eligible_candidates, vec![],);
929

            
930
            // Leave with remaining stake
931
1
            assert_ok!(PooledStaking::request_undelegate(
932
1
                origin_of(ALICE.into()),
933
1
                ALICE.into(),
934
1
                ActivePoolKind::AutoCompounding,
935
1
                SharesOrStake::Stake(dust),
936
1
            ));
937

            
938
            // Alice has no more stake left
939
1
            assert_eq!(
940
1
                pallet_pooled_staking::Pools::<Runtime>::get(
941
1
                    AccountId::from(ALICE),
942
1
                    PoolsKey::CandidateTotalStake
943
1
                ),
944
1
                0,
945
1
            );
946
1
        },
947
1
    );
948
1
}
949

            
950
#[test]
951
1
fn test_staking_leave_execute_before_time() {
952
1
    let stake = 10 * MinimumSelfDelegation::get();
953
1

            
954
1
    setup_staking_join_and_execute(
955
1
        vec![A {
956
1
            delegator: ALICE.into(),
957
1
            candidate: ALICE.into(),
958
1
            target_pool: ActivePoolKind::AutoCompounding,
959
1
            stake,
960
1
        }],
961
1
        || {
962
1
            let balance_before = System::account(AccountId::from(ALICE)).data.free;
963
1
            let at = Session::current_index();
964
1
            assert_ok!(PooledStaking::request_undelegate(
965
1
                origin_of(ALICE.into()),
966
1
                ALICE.into(),
967
1
                ActivePoolKind::AutoCompounding,
968
1
                SharesOrStake::Stake(stake),
969
1
            ));
970

            
971
            // Request undelegate does not change account balance
972
1
            assert_eq!(
973
1
                balance_before,
974
1
                System::account(AccountId::from(ALICE)).data.free
975
1
            );
976

            
977
            // We called request_delegate in session 0, we will be able to execute it starting from session 2
978
1
            let start_of_session_4 = session_to_block(4);
979
1
            // Session 4 starts at block 1200, but run_to_session runs to block 1201, so subtract 2 here to go to 1999
980
1
            run_to_block(start_of_session_4 - 2);
981
1

            
982
1
            assert_noop!(
983
1
                PooledStaking::execute_pending_operations(
984
1
                    origin_of(ALICE.into()),
985
1
                    vec![PendingOperationQuery {
986
1
                        delegator: ALICE.into(),
987
1
                        operation: PendingOperationKey::Leaving {
988
1
                            candidate: ALICE.into(),
989
1
                            at,
990
1
                        }
991
1
                    }]
992
1
                ),
993
1
                pallet_pooled_staking::Error::<Runtime>::RequestCannotBeExecuted(0)
994
1
            );
995
1
        },
996
1
    );
997
1
}
998

            
999
#[test]
1
fn test_staking_leave_execute_any_origin() {
1
    let stake = 10 * MinimumSelfDelegation::get();
1

            
1
    setup_staking_join_and_execute(
1
        vec![A {
1
            delegator: ALICE.into(),
1
            candidate: ALICE.into(),
1
            target_pool: ActivePoolKind::AutoCompounding,
1
            stake,
1
        }],
1
        || {
1
            let balance_before = System::account(AccountId::from(ALICE)).data.free;
1
            let at = Session::current_index();
1
            assert_ok!(PooledStaking::request_undelegate(
1
                origin_of(ALICE.into()),
1
                ALICE.into(),
1
                ActivePoolKind::AutoCompounding,
1
                SharesOrStake::Stake(stake),
1
            ));
            // Request undelegate does not change account balance
1
            assert_eq!(
1
                balance_before,
1
                System::account(AccountId::from(ALICE)).data.free
1
            );
1
            run_to_session(4);
1

            
1
            let balance_before = System::account(AccountId::from(ALICE)).data.free;
1

            
1
            assert_ok!(PooledStaking::execute_pending_operations(
1
                // Any signed origin can execute this, the stake will go to Alice account
1
                origin_of(BOB.into()),
1
                vec![PendingOperationQuery {
1
                    delegator: ALICE.into(),
1
                    operation: PendingOperationKey::Leaving {
1
                        candidate: ALICE.into(),
1
                        at,
1
                    }
1
                }]
1
            ),);
1
            let balance_after = System::account(AccountId::from(ALICE)).data.free;
1
            assert_eq!(balance_after - balance_before, stake);
1
        },
1
    );
1
}
#[test]
1
fn test_staking_leave_execute_bad_origin() {
1
    let stake = 10 * MinimumSelfDelegation::get();
1

            
1
    setup_staking_join_and_execute(
1
        vec![A {
1
            delegator: ALICE.into(),
1
            candidate: ALICE.into(),
1
            target_pool: ActivePoolKind::AutoCompounding,
1
            stake,
1
        }],
1
        || {
1
            let at = Session::current_index();
1
            assert_ok!(PooledStaking::request_undelegate(
1
                origin_of(ALICE.into()),
1
                ALICE.into(),
1
                ActivePoolKind::AutoCompounding,
1
                SharesOrStake::Stake(stake),
1
            ));
1
            run_to_session(4);
1

            
1
            assert_noop!(
1
                PooledStaking::execute_pending_operations(
1
                    root_origin(),
1
                    vec![PendingOperationQuery {
1
                        delegator: ALICE.into(),
1
                        operation: PendingOperationKey::Leaving {
1
                            candidate: ALICE.into(),
1
                            at,
1
                        }
1
                    }]
1
                ),
1
                BadOrigin
1
            );
1
        },
1
    );
1
}
#[test]
1
fn test_staking_swap() {
1
    setup_staking_join_and_execute(
1
        vec![A {
1
            delegator: ALICE.into(),
1
            candidate: ALICE.into(),
1
            target_pool: ActivePoolKind::AutoCompounding,
1
            stake: 10 * MinimumSelfDelegation::get(),
1
        }],
1
        || {
1
            let stake = 10 * MinimumSelfDelegation::get();
1
            assert_ok!(PooledStaking::swap_pool(
1
                origin_of(ALICE.into()),
1
                ALICE.into(),
1
                ActivePoolKind::AutoCompounding,
1
                SharesOrStake::Stake(stake),
1
            ));
1
            assert_eq!(
1
                PooledStaking::computed_stake(
1
                    ALICE.into(),
1
                    ALICE.into(),
1
                    PoolKind::AutoCompounding
1
                ),
1
                Some(0u32.into())
1
            );
1
            assert_eq!(
1
                PooledStaking::computed_stake(ALICE.into(), ALICE.into(), PoolKind::ManualRewards),
1
                Some(stake)
1
            );
1
            assert_ok!(PooledStaking::swap_pool(
1
                origin_of(ALICE.into()),
1
                ALICE.into(),
1
                ActivePoolKind::ManualRewards,
1
                SharesOrStake::Stake(stake),
1
            ));
1
            assert_eq!(
1
                PooledStaking::computed_stake(
1
                    ALICE.into(),
1
                    ALICE.into(),
1
                    PoolKind::AutoCompounding
1
                ),
1
                Some(stake)
1
            );
1
            assert_eq!(
1
                PooledStaking::computed_stake(ALICE.into(), ALICE.into(), PoolKind::ManualRewards),
1
                Some(0u32.into())
1
            );
1
        },
1
    )
1
}