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 {
18
    super::*,
19
    crate::{
20
        assert_eq_last_events,
21
        pools::{ActivePoolKind, AutoCompounding, ManualRewards},
22
        Pallet,
23
    },
24
    frame_support::assert_err,
25
    sp_runtime::DispatchError,
26
    tp_traits::DistributeRewards,
27
};
28

            
29
struct Delegation {
30
    candidate: AccountId,
31
    delegator: AccountId,
32
    pool: ActivePoolKind,
33
    stake: Balance,
34
}
35

            
36
struct RewardRequest {
37
    collator: AccountId,
38
    rewards: Balance,
39
}
40

            
41
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42
struct DelegatorState {
43
    candidate: AccountId,
44
    delegator: AccountId,
45
    auto_stake: Balance,
46
    auto_shares: Balance,
47
    manual_stake: Balance,
48
    manual_shares: Balance,
49
    pending_rewards: Balance,
50
}
51

            
52
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53
struct Distribution {
54
    collator_auto: Balance,
55
    collator_manual: Balance,
56
    delegators_auto: Balance,
57
    delegators_manual: Balance,
58
}
59

            
60
10
fn setup_delegations(delegations: &[Delegation]) {
61
    use crate::traits::Timer;
62
10
    let block_number = <Runtime as crate::Config>::JoiningRequestTimer::now();
63

            
64
    // Request all delegations
65
30
    for d in delegations {
66
20
        assert_ok!(Staking::request_delegate(
67
20
            RuntimeOrigin::signed(d.delegator),
68
20
            d.candidate,
69
20
            d.pool,
70
20
            d.stake,
71
        ));
72
    }
73

            
74
    // Wait for delegation to be executable
75
30
    for _ in 0..BLOCKS_TO_WAIT {
76
20
        run_block();
77
20
    }
78

            
79
    // Execute delegations
80
30
    for d in delegations {
81
20
        assert_ok!(Staking::execute_pending_operations(
82
20
            RuntimeOrigin::signed(d.delegator),
83
20
            vec![PendingOperationQuery {
84
20
                delegator: d.delegator,
85
20
                operation: match d.pool {
86
                    ActivePoolKind::AutoCompounding =>
87
10
                        PendingOperationKey::JoiningAutoCompounding {
88
10
                            candidate: d.candidate,
89
10
                            at: block_number
90
10
                        },
91
10
                    ActivePoolKind::ManualRewards => PendingOperationKey::JoiningManualRewards {
92
10
                        candidate: d.candidate,
93
10
                        at: block_number
94
10
                    },
95
                }
96
            }]
97
        ));
98
    }
99
10
}
100

            
101
9
fn test_distribution(
102
9
    delegations: &[Delegation],
103
9
    reward: RewardRequest,
104
9
    stakes: &[DelegatorState],
105
9
    distribution: Distribution,
106
9
) {
107
    use frame_support::traits::Imbalance;
108

            
109
    // Create new supply for rewards
110
9
    let new_supply = currency_issue(reward.rewards);
111
9
    let new_supply_amount = new_supply.peek();
112

            
113
    // Setup delegations before rewarding.
114
9
    setup_delegations(delegations);
115

            
116
    // Distribute rewards
117
9
    let candidate_balance_before = total_balance(&ACCOUNT_CANDIDATE_1);
118

            
119
9
    assert_ok!(crate::pools::accumulate_rewards::<Runtime>(
120
9
        reward.collator,
121
9
        new_supply
122
    ));
123
9
    let _ = crate::pools::distribute_accumulated_rewards_immediately::<Runtime>();
124
9
    let candidate_balance_after = total_balance(&ACCOUNT_CANDIDATE_1);
125

            
126
    // Check events matches the expected distribution.
127
9
    assert_eq_last_events!(vec![Event::<Runtime>::RewardsDistributed {
128
9
        collator: reward.collator,
129
9
        collator_ac_rewards: distribution.collator_auto,
130
9
        collator_mc_rewards: distribution.collator_manual,
131
9
        delegators_ac_rewards: distribution.delegators_auto,
132
9
        delegators_mc_rewards: distribution.delegators_manual,
133
9
    },]);
134

            
135
    // Check the state of each delegate match the expected values.
136
19
    for expected in stakes {
137
10
        let actual = DelegatorState {
138
10
            candidate: expected.candidate,
139
10
            delegator: expected.delegator,
140
10
            auto_stake: AutoCompounding::<Runtime>::computed_stake(
141
10
                &expected.candidate,
142
10
                &expected.delegator,
143
10
            )
144
10
            .expect("to have stake")
145
10
            .0,
146
10
            auto_shares: AutoCompounding::<Runtime>::shares(
147
10
                &expected.candidate,
148
10
                &expected.delegator,
149
10
            )
150
10
            .0,
151
10
            manual_stake: ManualRewards::<Runtime>::computed_stake(
152
10
                &expected.candidate,
153
10
                &expected.delegator,
154
10
            )
155
10
            .expect("to have stake")
156
10
            .0,
157
10
            manual_shares: ManualRewards::<Runtime>::shares(
158
10
                &expected.candidate,
159
10
                &expected.delegator,
160
10
            )
161
10
            .0,
162
10
            pending_rewards: ManualRewards::<Runtime>::pending_rewards(
163
10
                &expected.candidate,
164
10
                &expected.delegator,
165
10
            )
166
10
            .expect("no overflow")
167
10
            .0,
168
10
        };
169

            
170
10
        similar_asserts::assert_eq!(&actual, expected);
171
    }
172

            
173
    // Additional checks.
174
9
    assert_eq!(
175
9
        distribution.collator_auto
176
9
            + distribution.collator_manual
177
9
            + distribution.delegators_auto
178
9
            + distribution.delegators_manual,
179
        new_supply_amount,
180
        "Distribution total doesn't match requested reward"
181
    );
182

            
183
9
    assert_eq!(
184
9
        candidate_balance_before + distribution.collator_manual,
185
        candidate_balance_after,
186
        "candidate balance should be increased by collator_manual"
187
    );
188

            
189
9
    let sum_manual: Balance = stakes.iter().map(|s| s.pending_rewards).sum();
190
9
    assert_eq!(
191
        sum_manual, distribution.delegators_manual,
192
        "sum of pending rewards should match distributed delegators manual rewards"
193
    );
194

            
195
9
    let sum_auto_stake_before: Balance = delegations
196
9
        .iter()
197
14
        .filter_map(|d| match d.pool {
198
7
            ActivePoolKind::AutoCompounding => Some(d.stake),
199
7
            _ => None,
200
14
        })
201
9
        .sum();
202

            
203
9
    let sum_auto_stake_after = AutoCompounding::<Runtime>::total_staked(&reward.collator).0;
204
9
    assert_eq!(
205
9
        sum_auto_stake_after - sum_auto_stake_before,
206
9
        distribution.collator_auto + distribution.delegators_auto,
207
        "diff between sum of auto stake before and after distribution should match distributed auto rewards"
208
    );
209
9
}
210

            
211
#[test]
212
1
fn candidate_only_manual_only() {
213
1
    ExtBuilder::default().build().execute_with(|| {
214
1
        test_distribution(
215
1
            &[Delegation {
216
1
                candidate: ACCOUNT_CANDIDATE_1,
217
1
                delegator: ACCOUNT_CANDIDATE_1,
218
1
                pool: ActivePoolKind::ManualRewards,
219
1
                stake: 1_000_000_000,
220
1
            }],
221
1
            RewardRequest {
222
1
                collator: ACCOUNT_CANDIDATE_1,
223
1
                rewards: 1_000_000,
224
1
            },
225
1
            &[DelegatorState {
226
1
                candidate: ACCOUNT_CANDIDATE_1,
227
1
                delegator: ACCOUNT_CANDIDATE_1,
228
1
                auto_shares: 0,
229
1
                auto_stake: 0,
230
1
                manual_shares: 1_000,
231
1
                manual_stake: 1_000_000_000,
232
1
                pending_rewards: 800_000,
233
1
            }],
234
1
            Distribution {
235
1
                collator_auto: 0,
236
1
                collator_manual: 200_000, // 20% of rewards
237
1
                delegators_auto: 0,
238
1
                delegators_manual: 800_000, // 80% of rewards
239
1
            },
240
        )
241
1
    });
242
1
}
243

            
244
#[test]
245
1
fn candidate_only_auto_only() {
246
1
    ExtBuilder::default().build().execute_with(|| {
247
1
        test_distribution(
248
1
            &[Delegation {
249
1
                candidate: ACCOUNT_CANDIDATE_1,
250
1
                delegator: ACCOUNT_CANDIDATE_1,
251
1
                pool: ActivePoolKind::AutoCompounding,
252
1
                stake: 1_000_000_000,
253
1
            }],
254
1
            RewardRequest {
255
1
                collator: ACCOUNT_CANDIDATE_1,
256
1
                rewards: 10_000_000,
257
1
            },
258
1
            &[DelegatorState {
259
1
                candidate: ACCOUNT_CANDIDATE_1,
260
1
                delegator: ACCOUNT_CANDIDATE_1,
261
1
                auto_shares: 1_001,
262
1
                // initial auto stake is 1_000_000_000 for
263
1
                // 8_000_000 is shared between all delegators, so 1 share
264
1
                // is now worth 1_008_000_000 / 1000 = 1_008_000 now
265
1
                // collator is should be rewarded 2_000_000 in auto shares,
266
1
                // which allows to get 1 more share, so the collator now
267
1
                // have 1_001 shares worth
268
1
                // 1_008_000_000 + 1_008_000 = 1_009_008_000
269
1
                auto_stake: 1_009_008_000,
270
1
                manual_shares: 0,
271
1
                manual_stake: 0,
272
1
                pending_rewards: 0,
273
1
            }],
274
1
            Distribution {
275
1
                // 20% of rewards, rounded down to closest amount of Auto shares
276
1
                // AFTER delegators rewards has been rewarded
277
1
                collator_auto: 1_008_000,
278
1
                // dust from collator_auto
279
1
                collator_manual: 992_000,
280
1
                delegators_auto: 8_000_000, // 80% of rewards
281
1
                delegators_manual: 0,
282
1
            },
283
        )
284
1
    });
285
1
}
286

            
287
#[test]
288
1
fn candidate_only_mixed() {
289
1
    ExtBuilder::default().build().execute_with(|| {
290
1
        test_distribution(
291
1
            &[
292
1
                Delegation {
293
1
                    candidate: ACCOUNT_CANDIDATE_1,
294
1
                    delegator: ACCOUNT_CANDIDATE_1,
295
1
                    pool: ActivePoolKind::AutoCompounding,
296
1
                    stake: 1_000_000_000,
297
1
                },
298
1
                Delegation {
299
1
                    candidate: ACCOUNT_CANDIDATE_1,
300
1
                    delegator: ACCOUNT_CANDIDATE_1,
301
1
                    pool: ActivePoolKind::ManualRewards,
302
1
                    stake: 250_000_000,
303
1
                },
304
1
            ],
305
1
            RewardRequest {
306
1
                collator: ACCOUNT_CANDIDATE_1,
307
1
                rewards: 10_000_000,
308
1
            },
309
1
            &[DelegatorState {
310
1
                candidate: ACCOUNT_CANDIDATE_1,
311
1
                delegator: ACCOUNT_CANDIDATE_1,
312
1
                auto_shares: 1_001,
313
1
                auto_stake: 1_007_406_400,
314
1
                manual_shares: 250,
315
1
                manual_stake: 250_000_000,
316
1
                pending_rewards: 1_600_000,
317
1
            }],
318
1
            Distribution {
319
1
                collator_auto: 1_006_400,
320
1
                collator_manual: 993_600,
321
1
                delegators_auto: 6_400_000,
322
1
                delegators_manual: 1_600_000,
323
1
            },
324
        )
325
1
    });
326
1
}
327

            
328
#[test]
329
1
fn delegators_manual_only() {
330
1
    ExtBuilder::default().build().execute_with(|| {
331
1
        test_distribution(
332
1
            &[
333
1
                Delegation {
334
1
                    candidate: ACCOUNT_CANDIDATE_1,
335
1
                    delegator: ACCOUNT_CANDIDATE_1,
336
1
                    pool: ActivePoolKind::ManualRewards,
337
1
                    stake: 1_000_000_000,
338
1
                },
339
1
                Delegation {
340
1
                    candidate: ACCOUNT_CANDIDATE_1,
341
1
                    delegator: ACCOUNT_DELEGATOR_1,
342
1
                    pool: ActivePoolKind::ManualRewards,
343
1
                    stake: 250_000_000,
344
1
                },
345
1
            ],
346
1
            RewardRequest {
347
1
                collator: ACCOUNT_CANDIDATE_1,
348
1
                rewards: 10_000_000,
349
1
            },
350
1
            &[
351
1
                DelegatorState {
352
1
                    candidate: ACCOUNT_CANDIDATE_1,
353
1
                    delegator: ACCOUNT_CANDIDATE_1,
354
1
                    auto_shares: 0,
355
1
                    auto_stake: 0,
356
1
                    manual_shares: 1_000,
357
1
                    manual_stake: 1_000_000_000,
358
1
                    pending_rewards: 6_400_000,
359
1
                },
360
1
                DelegatorState {
361
1
                    candidate: ACCOUNT_CANDIDATE_1,
362
1
                    delegator: ACCOUNT_DELEGATOR_1,
363
1
                    auto_shares: 0,
364
1
                    auto_stake: 0,
365
1
                    manual_shares: 250,
366
1
                    manual_stake: 250_000_000,
367
1
                    pending_rewards: 1_600_000,
368
1
                },
369
1
            ],
370
1
            Distribution {
371
1
                collator_auto: 0,
372
1
                collator_manual: 2_000_000,
373
1
                delegators_auto: 0,
374
1
                delegators_manual: 8_000_000,
375
1
            },
376
        )
377
1
    });
378
1
}
379

            
380
#[test]
381
1
fn delegators_auto_only() {
382
1
    ExtBuilder::default().build().execute_with(|| {
383
1
        test_distribution(
384
1
            &[
385
1
                Delegation {
386
1
                    candidate: ACCOUNT_CANDIDATE_1,
387
1
                    delegator: ACCOUNT_CANDIDATE_1,
388
1
                    pool: ActivePoolKind::AutoCompounding,
389
1
                    stake: 1_000_000_000,
390
1
                },
391
1
                Delegation {
392
1
                    candidate: ACCOUNT_CANDIDATE_1,
393
1
                    delegator: ACCOUNT_DELEGATOR_1,
394
1
                    pool: ActivePoolKind::AutoCompounding,
395
1
                    stake: 250_000_000,
396
1
                },
397
1
            ],
398
1
            RewardRequest {
399
1
                collator: ACCOUNT_CANDIDATE_1,
400
1
                rewards: 10_000_000,
401
1
            },
402
1
            &[
403
1
                DelegatorState {
404
1
                    candidate: ACCOUNT_CANDIDATE_1,
405
1
                    delegator: ACCOUNT_CANDIDATE_1,
406
1
                    auto_shares: 1_001,
407
1
                    auto_stake: 1_007_406_400,
408
1
                    manual_shares: 0,
409
1
                    manual_stake: 0,
410
1
                    pending_rewards: 0,
411
1
                },
412
1
                DelegatorState {
413
1
                    candidate: ACCOUNT_CANDIDATE_1,
414
1
                    delegator: ACCOUNT_DELEGATOR_1,
415
1
                    auto_shares: 250,
416
1
                    auto_stake: 251_600_000,
417
1
                    manual_shares: 0,
418
1
                    manual_stake: 0,
419
1
                    pending_rewards: 0,
420
1
                },
421
1
            ],
422
1
            Distribution {
423
1
                collator_auto: 1_006_400,
424
1
                collator_manual: 993_600,
425
1
                delegators_auto: 8_000_000,
426
1
                delegators_manual: 0,
427
1
            },
428
        )
429
1
    });
430
1
}
431

            
432
#[test]
433
1
fn delegators_mixed() {
434
1
    ExtBuilder::default().build().execute_with(|| {
435
1
        test_distribution(
436
1
            &[
437
1
                Delegation {
438
1
                    candidate: ACCOUNT_CANDIDATE_1,
439
1
                    delegator: ACCOUNT_CANDIDATE_1,
440
1
                    pool: ActivePoolKind::AutoCompounding,
441
1
                    stake: 1_000_000_000,
442
1
                },
443
1
                Delegation {
444
1
                    candidate: ACCOUNT_CANDIDATE_1,
445
1
                    delegator: ACCOUNT_CANDIDATE_1,
446
1
                    pool: ActivePoolKind::ManualRewards,
447
1
                    stake: 500_000_000,
448
1
                },
449
1
                Delegation {
450
1
                    candidate: ACCOUNT_CANDIDATE_1,
451
1
                    delegator: ACCOUNT_DELEGATOR_1,
452
1
                    pool: ActivePoolKind::ManualRewards,
453
1
                    stake: 250_000_000,
454
1
                },
455
1
                Delegation {
456
1
                    candidate: ACCOUNT_CANDIDATE_1,
457
1
                    delegator: ACCOUNT_DELEGATOR_1,
458
1
                    pool: ActivePoolKind::AutoCompounding,
459
1
                    stake: 500_000_000,
460
1
                },
461
1
            ],
462
1
            RewardRequest {
463
1
                collator: ACCOUNT_CANDIDATE_1,
464
1
                rewards: 10_000_000,
465
1
            },
466
1
            &[
467
1
                DelegatorState {
468
1
                    candidate: ACCOUNT_CANDIDATE_1,
469
1
                    delegator: ACCOUNT_CANDIDATE_1,
470
1
                    auto_shares: 1_001,
471
1
                    auto_stake: 1_004_559_388,
472
1
                    manual_shares: 500,
473
1
                    manual_stake: 500_000_000,
474
1
                    pending_rewards: 1_777_500,
475
1
                },
476
1
                DelegatorState {
477
1
                    candidate: ACCOUNT_CANDIDATE_1,
478
1
                    delegator: ACCOUNT_DELEGATOR_1,
479
1
                    auto_shares: 500,
480
1
                    auto_stake: 501_777_916,
481
1
                    manual_shares: 250,
482
1
                    manual_stake: 250_000_000,
483
1
                    pending_rewards: 888_750,
484
1
                },
485
1
            ],
486
1
            Distribution {
487
1
                collator_auto: 1_003_555,
488
1
                collator_manual: 996_445,
489
1
                // Total stake: 2_250_000_000
490
1
                // Auto stake: 1_500_000_000
491
1
                // Manual stake: 750_000_000
492
1
                // Manual shares: 750
493
1
                // Rewards towards delegators: 80% of 10_000_000 = 8_000_000
494
1
                // Rewards towards manual deleg
495
1
                //   = 8_000_000 * 750_000_000 / 2_250_000_000
496
1
                //   = 2_666_666
497
1
                //   => 2_666_250 (rounding down to closest multiple of 750)
498
1
                //   gives dust of 2_666_666 - 2_666_250 = 416
499
1
                delegators_manual: 2_666_250,
500
1
                // Rewards towards auto deleg
501
1
                // = Rewards deleg - Rewards manual deleg
502
1
                // = 8_000_000 - 2_666_250
503
1
                // = 5_333_750
504
1
                delegators_auto: 5_333_750,
505
1
            },
506
        );
507
1
    });
508
1
}
509

            
510
#[test]
511
1
fn candidate_only_no_stake() {
512
    // Rewarding a candidate that does not have any stake works
513
1
    ExtBuilder::default().build().execute_with(|| {
514
1
        test_distribution(
515
1
            &[],
516
1
            RewardRequest {
517
1
                collator: ACCOUNT_CANDIDATE_1,
518
1
                rewards: 1_000_000,
519
1
            },
520
1
            &[],
521
1
            Distribution {
522
1
                collator_auto: 0,
523
1
                collator_manual: 1_000_000, // 100% of rewards
524
1
                delegators_auto: 0,
525
1
                delegators_manual: 0, // 0% of rewards
526
1
            },
527
        )
528
1
    });
529
1
}
530

            
531
#[test]
532
1
fn delegator_only_candidate_zero() {
533
    // Rewarding a candidate that does not have any stake works
534
1
    ExtBuilder::default().build().execute_with(|| {
535
1
        test_distribution(
536
1
            &[Delegation {
537
1
                candidate: ACCOUNT_CANDIDATE_1,
538
1
                delegator: ACCOUNT_DELEGATOR_1,
539
1
                pool: ActivePoolKind::ManualRewards,
540
1
                stake: 250_000_000,
541
1
            }],
542
1
            RewardRequest {
543
1
                collator: ACCOUNT_CANDIDATE_1,
544
1
                rewards: 1_000_000,
545
1
            },
546
1
            &[DelegatorState {
547
1
                candidate: ACCOUNT_CANDIDATE_1,
548
1
                delegator: ACCOUNT_DELEGATOR_1,
549
1
                auto_shares: 0,
550
1
                auto_stake: 0,
551
1
                manual_shares: 250,
552
1
                manual_stake: 250_000_000,
553
1
                pending_rewards: 800_000,
554
1
            }],
555
1
            Distribution {
556
1
                collator_auto: 0,
557
1
                collator_manual: 200_000, // 20% of rewards
558
1
                delegators_auto: 0,
559
1
                delegators_manual: 800_000, // 80% of rewards
560
1
            },
561
        )
562
1
    });
563
1
}
564

            
565
#[test]
566
1
fn delegator_only_candidate_no_stake_auto_compounding() {
567
    // Rewarding a candidate that does not have any stake, while some delegator
568
    // has stake for that candidate
569
1
    ExtBuilder::default().build().execute_with(|| {
570
1
        test_distribution(
571
1
            &[Delegation {
572
1
                candidate: ACCOUNT_CANDIDATE_1,
573
1
                delegator: ACCOUNT_DELEGATOR_1,
574
1
                pool: ActivePoolKind::AutoCompounding,
575
1
                stake: 250_000_000,
576
1
            }],
577
1
            RewardRequest {
578
1
                collator: ACCOUNT_CANDIDATE_1,
579
1
                rewards: 1_000_000,
580
1
            },
581
1
            &[],
582
1
            Distribution {
583
1
                collator_auto: 0,
584
1
                collator_manual: 200_000, // 20% of rewards
585
1
                delegators_auto: 800_000, // 80% of rewards
586
1
                delegators_manual: 0,
587
1
            },
588
        )
589
1
    });
590
1
}
591

            
592
#[test]
593
1
fn reward_distribution_is_transactional() {
594
1
    ExtBuilder::default().build().execute_with(|| {
595
        use crate::traits::Timer;
596
1
        let request_time = <Runtime as crate::Config>::JoiningRequestTimer::now();
597

            
598
1
        assert_ok!(Staking::request_delegate(
599
1
            RuntimeOrigin::signed(ACCOUNT_CANDIDATE_1),
600
            ACCOUNT_CANDIDATE_1,
601
1
            ActivePoolKind::AutoCompounding,
602
            1_000_000_000,
603
        ));
604

            
605
        // Wait for delegation to be executable
606
3
        for _ in 0..BLOCKS_TO_WAIT {
607
2
            run_block();
608
2
        }
609

            
610
1
        assert_ok!(Staking::execute_pending_operations(
611
1
            RuntimeOrigin::signed(ACCOUNT_CANDIDATE_1),
612
1
            vec![PendingOperationQuery {
613
1
                delegator: ACCOUNT_CANDIDATE_1,
614
1
                operation: PendingOperationKey::JoiningAutoCompounding {
615
1
                    candidate: ACCOUNT_CANDIDATE_1,
616
1
                    at: request_time
617
1
                },
618
1
            }]
619
        ));
620

            
621
1
        let total_staked_before =
622
1
            pools::AutoCompounding::<Runtime>::total_staked(&ACCOUNT_CANDIDATE_1);
623

            
624
        // Increase ED to make reward destribution fail when resolving
625
        // credit to Staking account.
626
1
        MockExistentialDeposit::set(u128::MAX);
627

            
628
1
        let rewards = Balances::issue(1_000_000_000);
629
1
        assert_err!(
630
1
            Staking::distribute_rewards(ACCOUNT_CANDIDATE_1, rewards),
631
1
            DispatchError::NoProviders
632
        );
633

            
634
1
        let total_staked_after =
635
1
            pools::AutoCompounding::<Runtime>::total_staked(&ACCOUNT_CANDIDATE_1);
636
1
        assert_eq!(
637
            total_staked_before, total_staked_after,
638
            "distribution should be reverted"
639
        );
640
1
    })
641
1
}
642

            
643
#[test]
644
1
fn rewards_are_aggregated_then_distributed() {
645
    use crate::traits::Timer;
646

            
647
1
    ExtBuilder::default().build().execute_with(|| {
648
        // Pending rewards storage is empty at genesis
649
1
        assert!(!crate::PendingRewards::<Runtime>::exists());
650

            
651
1
        setup_delegations(&[
652
1
            Delegation {
653
1
                candidate: ACCOUNT_CANDIDATE_1,
654
1
                delegator: ACCOUNT_CANDIDATE_1,
655
1
                pool: ActivePoolKind::AutoCompounding,
656
1
                stake: 1_000_000_000,
657
1
            },
658
1
            Delegation {
659
1
                candidate: ACCOUNT_CANDIDATE_1,
660
1
                delegator: ACCOUNT_CANDIDATE_1,
661
1
                pool: ActivePoolKind::ManualRewards,
662
1
                stake: 500_000_000,
663
1
            },
664
1
            Delegation {
665
1
                candidate: ACCOUNT_CANDIDATE_1,
666
1
                delegator: ACCOUNT_DELEGATOR_1,
667
1
                pool: ActivePoolKind::ManualRewards,
668
1
                stake: 250_000_000,
669
1
            },
670
1
            Delegation {
671
1
                candidate: ACCOUNT_CANDIDATE_1,
672
1
                delegator: ACCOUNT_DELEGATOR_1,
673
1
                pool: ActivePoolKind::AutoCompounding,
674
1
                stake: 500_000_000,
675
1
            },
676
1
            Delegation {
677
1
                candidate: ACCOUNT_CANDIDATE_2,
678
1
                delegator: ACCOUNT_CANDIDATE_2,
679
1
                pool: ActivePoolKind::AutoCompounding,
680
1
                stake: 1_000_000_000,
681
1
            },
682
1
            Delegation {
683
1
                candidate: ACCOUNT_CANDIDATE_2,
684
1
                delegator: ACCOUNT_DELEGATOR_1,
685
1
                pool: ActivePoolKind::ManualRewards,
686
1
                stake: 500_000_000,
687
1
            },
688
1
        ]);
689

            
690
        // First time calling it will never reward immediatly since it write the current instant for
691
        // the first time. We'll reward multiple times and ensure no distribution occurs.
692
1
        let block_number = <Runtime as crate::Config>::RewardsDistributionTimer::now();
693
1
        let candidates_balance_before = [
694
1
            total_balance(&ACCOUNT_CANDIDATE_1),
695
1
            total_balance(&ACCOUNT_CANDIDATE_2),
696
1
        ];
697

            
698
        // - 1st reward
699
1
        let rewards = currency_issue(1_000_000);
700
1
        assert_ok!(Pallet::<Runtime>::distribute_rewards(
701
            ACCOUNT_CANDIDATE_1,
702
1
            rewards
703
        ));
704

            
705
1
        assert_eq!(
706
1
            crate::PendingRewards::<Runtime>::get(),
707
1
            Some(crate::pools::PendingRewards {
708
1
                last_distribution: block_number,
709
1
                rewards: [(ACCOUNT_CANDIDATE_1, 1_000_000)].into(),
710
1
            })
711
        );
712

            
713
        // - 2nd reward
714
1
        let rewards = currency_issue(1_100_000);
715
1
        assert_ok!(Pallet::<Runtime>::distribute_rewards(
716
            ACCOUNT_CANDIDATE_2,
717
1
            rewards
718
        ));
719
1
        assert_eq!(
720
1
            crate::PendingRewards::<Runtime>::get(),
721
1
            Some(crate::pools::PendingRewards {
722
1
                last_distribution: block_number,
723
1
                rewards: [
724
1
                    (ACCOUNT_CANDIDATE_1, 1_000_000),
725
1
                    (ACCOUNT_CANDIDATE_2, 1_100_000)
726
1
                ]
727
1
                .into(),
728
1
            })
729
        );
730

            
731
        // - 3rd reward
732
1
        let rewards = currency_issue(1_200_000);
733
1
        assert_ok!(Pallet::<Runtime>::distribute_rewards(
734
            ACCOUNT_CANDIDATE_1,
735
1
            rewards
736
        ));
737

            
738
1
        assert_eq!(
739
1
            crate::PendingRewards::<Runtime>::get(),
740
1
            Some(crate::pools::PendingRewards {
741
1
                last_distribution: block_number,
742
1
                rewards: [
743
1
                    (ACCOUNT_CANDIDATE_1, 2_200_000),
744
1
                    (ACCOUNT_CANDIDATE_2, 1_100_000)
745
1
                ]
746
1
                .into(),
747
1
            })
748
        );
749

            
750
        // Rewards have only be aggregated, not actually distributed.
751
1
        let candidates_balance_after = [
752
1
            total_balance(&ACCOUNT_CANDIDATE_1),
753
1
            total_balance(&ACCOUNT_CANDIDATE_2),
754
1
        ];
755
1
        assert_eq!(candidates_balance_before, candidates_balance_after);
756

            
757
        // Let's move to next block, rewards should be distributed in on_initialize.
758
1
        run_block();
759
1
        assert_eq!(
760
1
            <Runtime as crate::Config>::RewardsDistributionTimer::now(),
761
1
            block_number + 1
762
        );
763

            
764
1
        assert_eq!(
765
1
            crate::PendingRewards::<Runtime>::get(),
766
1
            Some(crate::pools::PendingRewards {
767
1
                last_distribution: block_number + 1,
768
1
                rewards: Default::default(), // distribution occured, no rewards pending
769
1
            })
770
        );
771

            
772
1
        let candidates_balance_after = [
773
1
            total_balance(&ACCOUNT_CANDIDATE_1),
774
1
            total_balance(&ACCOUNT_CANDIDATE_2),
775
1
        ];
776
1
        assert_ne!(candidates_balance_before, candidates_balance_after);
777

            
778
1
        assert_eq_last_events!(vec![
779
1
            Event::<Runtime>::RewardsDistributed {
780
1
                collator: ACCOUNT_CANDIDATE_1,
781
1
                collator_ac_rewards: 0,
782
1
                collator_mc_rewards: 440_000,
783
1
                delegators_ac_rewards: 1_173_500,
784
1
                delegators_mc_rewards: 586_500,
785
1
            },
786
1
            Event::RewardsDistributed {
787
1
                collator: ACCOUNT_CANDIDATE_2,
788
1
                collator_ac_rewards: 0,
789
1
                collator_mc_rewards: 220_000,
790
1
                delegators_ac_rewards: 587_000,
791
1
                delegators_mc_rewards: 293_000
792
1
            },
793
        ]);
794
1
    });
795
1
}