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 crate::{MinimumSelfDelegation, PooledStaking};
20
use frame_support::assert_ok;
21
use pallet_pooled_staking::{ActivePoolKind, PendingOperationKey, PendingOperationQuery};
22
use {
23
    crate::{tests::common::*, AuthorNoting, RewardsPortion},
24
    alloc::vec,
25
    cumulus_primitives_core::ParaId,
26
    parity_scale_codec::Encode,
27
    sp_consensus_aura::AURA_ENGINE_ID,
28
    sp_runtime::{generic::DigestItem, traits::BlakeTwo256},
29
    test_relay_sproof_builder::{HeaderAs, ParaHeaderSproofBuilder, ParaHeaderSproofBuilderItem},
30
    tp_traits::ContainerChainBlockInfo,
31
};
32

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

            
54
            // Set DAVE session keys
55
1
            let dave_keys = get_authority_keys_from_seed(&AccountId::from(DAVE).to_string());
56

            
57
1
            assert_ok!(Session::set_keys(
58
1
                origin_of(DAVE.into()),
59
1
                crate::SessionKeys {
60
1
                    babe: dave_keys.babe.clone(),
61
1
                    grandpa: dave_keys.grandpa.clone(),
62
1
                    para_validator: dave_keys.para_validator.clone(),
63
1
                    para_assignment: dave_keys.para_assignment.clone(),
64
1
                    authority_discovery: dave_keys.authority_discovery.clone(),
65
1
                    beefy: dave_keys.beefy.clone(),
66
1
                    nimbus: dave_keys.nimbus.clone(),
67
1
                },
68
1
                vec![]
69
            ));
70

            
71
            // We make delegations to DAVE so that she is an elligible candidate.
72

            
73
1
            let stake = 10 * MinimumSelfDelegation::get();
74

            
75
1
            assert_ok!(PooledStaking::request_delegate(
76
1
                origin_of(DAVE.into()),
77
1
                DAVE.into(),
78
1
                ActivePoolKind::ManualRewards,
79
1
                stake,
80
            ));
81
1
            assert_ok!(PooledStaking::request_delegate(
82
1
                origin_of(BOB.into()),
83
1
                DAVE.into(),
84
1
                ActivePoolKind::AutoCompounding,
85
1
                stake,
86
            ));
87

            
88
            // wait few sessions for the request to be executable
89
1
            run_to_session(3u32);
90
1
            run_block();
91
1
            assert_ok!(PooledStaking::execute_pending_operations(
92
1
                origin_of(ALICE.into()),
93
1
                vec![
94
1
                    PendingOperationQuery {
95
1
                        delegator: DAVE.into(),
96
1
                        operation: PendingOperationKey::JoiningManualRewards {
97
1
                            candidate: DAVE.into(),
98
1
                            at: 0
99
1
                        }
100
1
                    },
101
1
                    PendingOperationQuery {
102
1
                        delegator: BOB.into(),
103
1
                        operation: PendingOperationKey::JoiningAutoCompounding {
104
1
                            candidate: DAVE.into(),
105
1
                            at: 0
106
1
                        }
107
1
                    }
108
                ]
109
            ));
110

            
111
            {
112
1
                let pending = pallet_pooled_staking::PendingRewards::<Runtime>::get().expect("pending rewards to exist");
113
1
                assert_eq!(pending.last_distribution, 3u32, "timer should be up to date");
114
            }
115

            
116
            // wait for next session so that DAVE is elected
117
1
            run_to_session(4u32);
118

            
119
            {
120
1
                let pending = pallet_pooled_staking::PendingRewards::<Runtime>::get().expect("pending rewards to exist");
121
1
                assert_eq!(pending.last_distribution, 4u32, "timer should be up to date");
122
            }
123

            
124
1
            run_block();
125

            
126
1
            let account: AccountId = DAVE.into();
127
1
            let balance_before = System::account(account.clone()).data.free;
128
1
            let summary = run_block();
129

            
130
            // Verify that all chains have collators
131
1
            let collator_assignment = TanssiCollatorAssignment::collator_container_chain();
132
            // 2 container chains total
133
1
            assert_eq!(collator_assignment.container_chains.len(), 2);
134
            // All container chains have collators
135
1
            assert!(collator_assignment
136
1
                .container_chains
137
1
                .values()
138
2
                .all(|cs| cs.len() == 2));
139

            
140
1
            let mut sproof = ParaHeaderSproofBuilder::default();
141
1
            let slot: u64 = 5;
142
1
            let other_para: ParaId = 1002u32.into();
143

            
144
            // Build the proof needed to call AuthorNoting's inherent.
145
1
            let s = ParaHeaderSproofBuilderItem {
146
1
                para_id: other_para,
147
1
                author_id: HeaderAs::NonEncoded(sp_runtime::generic::Header::<u32, BlakeTwo256> {
148
1
                    parent_hash: Default::default(),
149
1
                    number: 1,
150
1
                    state_root: Default::default(),
151
1
                    extrinsics_root: Default::default(),
152
1
                    digest: sp_runtime::generic::Digest {
153
1
                        logs: vec![DigestItem::PreRuntime(AURA_ENGINE_ID, slot.encode())],
154
1
                    },
155
1
                }),
156
1
            };
157
1
            sproof.items.push(s);
158

            
159
            // We need to set the AuthorNoting's inherent for it to also run
160
            // InflationRewards::on_container_authors_noted and reward the collator.
161
1
            set_author_noting_inherent_data(sproof);
162

            
163
            // Check that DAVE authored the container chain block. If this assert fails, change slot number above.
164
1
            let container_block_author = AuthorNoting::latest_author(ParaId::from(1002u32))
165
1
                .unwrap()
166
1
                .author;
167
1
            assert_eq!(container_block_author, AccountId::from(DAVE));
168

            
169
1
            let all_rewards = RewardsPortion::get() * summary.inflation;
170
1
            let rewards_per_chain = all_rewards / 2; // rewards are shared between the 2 paras
171

            
172
            // Rewards are now aggregated in storage and then distributed at the start of the next session.
173
            // We'll thus check that the pending rewards contains DAVE rewards.
174

            
175
            {
176
1
                let pending = pallet_pooled_staking::PendingRewards::<Runtime>::get().expect("pending rewards to exist");
177
1
                assert_eq!(pending.last_distribution, 4u32, "timer should be up to date");
178
1
                let dave_rewards = pending.rewards.get(&account).expect("DAVE should have pending rewards");
179

            
180
1
                assert_eq!(*dave_rewards, rewards_per_chain, "dave should have pending rewards for 1 chain");
181
            }
182

            
183
1
            let balance_after = System::account(&account).data.free;
184
1
            assert_eq!(
185
                balance_before,
186
                balance_after,
187
                "dave shouldn't have rewards in their account yet"
188
            );
189

            
190
            // let's go to next session
191
1
            run_to_session(5u32);
192

            
193
            {
194
1
                let pending = pallet_pooled_staking::PendingRewards::<Runtime>::get().expect("pending rewards to exist");
195
1
                assert_eq!(pending.last_distribution, 5u32, "timer should be up to date");
196
1
                assert!(pending.rewards.is_empty(), "rewards should be distributed including the latest ones, so the pending rewards should be empty");
197
            }
198

            
199
1
            let balance_after = System::account(&account).data.free;
200
1
            assert_ne!(
201
                balance_before,
202
                balance_after,
203
                "dave should have rewards in their account now"
204
            );
205
1
        });
206
1
}
207

            
208
#[test]
209
1
fn test_reward_to_invulnerable() {
210
1
    ExtBuilder::default()
211
1
        .with_balances(vec![
212
1
            // Alice gets 10k extra tokens for her mapping deposit
213
1
            (AccountId::from(ALICE), 210_000 * UNIT),
214
1
            (AccountId::from(BOB), 100_000 * UNIT),
215
1
            (AccountId::from(CHARLIE), 100_000 * UNIT),
216
1
            (AccountId::from(DAVE), 100_000 * UNIT),
217
1
        ])
218
1
        .with_collators(vec![
219
1
            (AccountId::from(ALICE), 210 * UNIT),
220
1
            (AccountId::from(BOB), 100 * UNIT),
221
1
            (AccountId::from(CHARLIE), 100 * UNIT),
222
1
            (AccountId::from(DAVE), 100 * UNIT),
223
1
        ])
224
1
        .with_empty_parachains(vec![1001, 1002])
225
1
        .build()
226
1
        .execute_with(|| {
227
            // Let's get the inflation of the block.
228
1
            let summary = run_block();
229

            
230
            // Calculate Bob's rewards.
231
1
            let all_rewards = RewardsPortion::get() * summary.inflation;
232
1
            let bob_rewards = all_rewards / 2;
233

            
234
1
            let mut sproof = ParaHeaderSproofBuilder::default();
235
1
            let slot: u64 = 5;
236
1
            let other_para: ParaId = 1001u32.into();
237

            
238
            // In dancelight there is no orchestrator chain, so instead of Charlie and Dave
239
            // we assign Alice and Bob.
240
1
            let assignment = TanssiCollatorAssignment::collator_container_chain();
241
1
            assert_eq!(
242
1
                assignment.container_chains[&1001u32.into()],
243
1
                vec![ALICE.into(), BOB.into()]
244
            );
245
            // All container chains have collators
246
2
            assert!(assignment.container_chains.values().all(|cs| cs.len() == 2));
247

            
248
            // Build the proof needed to call AuthorNoting's inherent.
249
1
            let s = ParaHeaderSproofBuilderItem {
250
1
                para_id: other_para,
251
1
                author_id: HeaderAs::NonEncoded(sp_runtime::generic::Header::<u32, BlakeTwo256> {
252
1
                    parent_hash: Default::default(),
253
1
                    number: 1,
254
1
                    state_root: Default::default(),
255
1
                    extrinsics_root: Default::default(),
256
1
                    digest: sp_runtime::generic::Digest {
257
1
                        logs: vec![DigestItem::PreRuntime(AURA_ENGINE_ID, slot.encode())],
258
1
                    },
259
1
                }),
260
1
            };
261
1
            sproof.items.push(s);
262

            
263
1
            let account: AccountId = BOB.into();
264
1
            let balance_before = System::account(account.clone()).data.free;
265

            
266
            // We need to set the AuthorNoting's inherent for it to also run
267
            // InflationRewards::on_container_authors_noted and reward the collator.
268
1
            set_author_noting_inherent_data(sproof);
269

            
270
1
            assert_eq!(
271
1
                AuthorNoting::latest_author(other_para),
272
1
                Some(ContainerChainBlockInfo {
273
1
                    block_number: 1,
274
1
                    author: AccountId::from(BOB),
275
1
                    latest_slot_number: 2.into(),
276
1
                })
277
            );
278

            
279
1
            let balance_after = System::account(account).data.free;
280

            
281
1
            assert_eq!(
282
                bob_rewards,
283
1
                balance_after - balance_before,
284
                "bob should get the correct reward portion"
285
            );
286
1
        });
287
1
}
288

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

            
310
1
            run_to_session(2u32);
311
1
            run_block();
312

            
313
            // change key, this should be reflected 2 sessions afterward
314
1
            let alice_new_key = get_authority_keys_from_seed(&AccountId::from(EVE).to_string());
315

            
316
1
            assert_ok!(Session::set_keys(
317
1
                origin_of(ALICE.into()),
318
1
                crate::SessionKeys {
319
1
                    babe: alice_new_key.babe.clone(),
320
1
                    grandpa: alice_new_key.grandpa.clone(),
321
1
                    para_validator: alice_new_key.para_validator.clone(),
322
1
                    para_assignment: alice_new_key.para_assignment.clone(),
323
1
                    authority_discovery: alice_new_key.authority_discovery.clone(),
324
1
                    beefy: alice_new_key.beefy.clone(),
325
1
                    nimbus: alice_new_key.nimbus.clone(),
326
1
                },
327
1
                vec![]
328
            ));
329

            
330
1
            run_to_session(4u32);
331
1
            run_block();
332

            
333
1
            let account: AccountId = ALICE.into();
334
1
            let balance_before = System::account(account.clone()).data.free;
335

            
336
1
            let summary = run_block();
337

            
338
            // Verify that all chains have collators
339
1
            let collator_assignment = TanssiCollatorAssignment::collator_container_chain();
340
            // 2 container chains total
341
1
            assert_eq!(collator_assignment.container_chains.len(), 2);
342
            // All container chains have collators
343
1
            assert!(collator_assignment
344
1
                .container_chains
345
1
                .values()
346
2
                .all(|cs| cs.len() == 2));
347

            
348
1
            let mut sproof = ParaHeaderSproofBuilder::default();
349
1
            let slot: u64 = 6;
350
1
            let other_para: ParaId = 1001u32.into();
351

            
352
            // Build the proof needed to call AuthorNoting's inherent.
353
1
            let s = ParaHeaderSproofBuilderItem {
354
1
                para_id: other_para,
355
1
                author_id: HeaderAs::NonEncoded(sp_runtime::generic::Header::<u32, BlakeTwo256> {
356
1
                    parent_hash: Default::default(),
357
1
                    number: 1,
358
1
                    state_root: Default::default(),
359
1
                    extrinsics_root: Default::default(),
360
1
                    digest: sp_runtime::generic::Digest {
361
1
                        logs: vec![DigestItem::PreRuntime(AURA_ENGINE_ID, slot.encode())],
362
1
                    },
363
1
                }),
364
1
            };
365
1
            sproof.items.push(s);
366

            
367
            // We need to set the AuthorNoting's inherent for it to also run
368
            // InflationRewards::on_container_authors_noted and reward the collator.
369
1
            set_author_noting_inherent_data(sproof);
370

            
371
            // Check that ALICE authored the container chain block. If this assert fails, change slot number above.
372
1
            let container_block_author = AuthorNoting::latest_author(ParaId::from(1001u32))
373
1
                .unwrap()
374
1
                .author;
375
1
            assert_eq!(container_block_author, AccountId::from(ALICE));
376

            
377
1
            let balance_after = System::account(account).data.free;
378

            
379
1
            let all_rewards = RewardsPortion::get() * summary.inflation;
380
            // rewards are shared between the 2 paras
381
1
            let rewards_per_chain = all_rewards / 2;
382
            // invulnerables get 100% of the reward immediately, unlike staking candidates
383
1
            assert_eq!(
384
                rewards_per_chain,
385
1
                balance_after - balance_before,
386
                "alice should get the correct reward portion"
387
            );
388
1
        });
389
1
}
390

            
391
#[test]
392
1
fn test_reward_chain_without_collators() {
393
1
    ExtBuilder::default()
394
1
        .with_balances(vec![
395
1
            // Alice gets 10k extra tokens for her mapping deposit
396
1
            (AccountId::from(ALICE), 210_000 * UNIT),
397
1
            (AccountId::from(BOB), 100_000 * UNIT),
398
1
            (AccountId::from(CHARLIE), 100_000 * UNIT),
399
1
            (AccountId::from(DAVE), 100_000 * UNIT),
400
1
        ])
401
1
        .with_collators(vec![
402
1
            (AccountId::from(ALICE), 210 * UNIT),
403
1
            (AccountId::from(BOB), 100 * UNIT),
404
1
        ])
405
1
        .with_empty_parachains(vec![1001, 1002])
406
1
        .build()
407
1
        .execute_with(|| {
408
            // Let's get the inflation of the block.
409
1
            let summary = run_block();
410

            
411
            // Calculate Bob's rewards.
412
1
            let all_rewards = RewardsPortion::get() * summary.inflation;
413
            // Even though there are 2 parachains registered, 1001 gets all the rewards because it
414
            // is the only chain with collators
415
1
            let bob_rewards = all_rewards;
416

            
417
1
            let mut sproof = ParaHeaderSproofBuilder::default();
418
1
            let slot: u64 = 5;
419
1
            let other_para: ParaId = 1001u32.into();
420

            
421
            // In dancelight there is no orchestrator chain, so instead of Charlie and Dave
422
            // we assign Alice and Bob.
423
1
            let assignment = TanssiCollatorAssignment::collator_container_chain();
424
1
            assert_eq!(
425
1
                assignment.container_chains[&1001u32.into()],
426
1
                vec![ALICE.into(), BOB.into()]
427
            );
428
            // The other chain has 0 collators
429
1
            assert_eq!(assignment.container_chains[&1002u32.into()], vec![]);
430

            
431
            // Build the proof needed to call AuthorNoting's inherent.
432
1
            let s = ParaHeaderSproofBuilderItem {
433
1
                para_id: other_para,
434
1
                author_id: HeaderAs::NonEncoded(sp_runtime::generic::Header::<u32, BlakeTwo256> {
435
1
                    parent_hash: Default::default(),
436
1
                    number: 1,
437
1
                    state_root: Default::default(),
438
1
                    extrinsics_root: Default::default(),
439
1
                    digest: sp_runtime::generic::Digest {
440
1
                        logs: vec![DigestItem::PreRuntime(AURA_ENGINE_ID, slot.encode())],
441
1
                    },
442
1
                }),
443
1
            };
444
1
            sproof.items.push(s);
445

            
446
1
            let account: AccountId = BOB.into();
447
1
            let balance_before = System::account(account.clone()).data.free;
448

            
449
            // We need to set the AuthorNoting's inherent for it to also run
450
            // InflationRewards::on_container_authors_noted and reward the collator.
451
1
            set_author_noting_inherent_data(sproof);
452

            
453
1
            assert_eq!(
454
1
                AuthorNoting::latest_author(other_para),
455
1
                Some(ContainerChainBlockInfo {
456
1
                    block_number: 1,
457
1
                    author: AccountId::from(BOB),
458
1
                    latest_slot_number: 2.into(),
459
1
                })
460
            );
461

            
462
1
            let balance_after = System::account(account).data.free;
463

            
464
1
            assert_eq!(
465
                bob_rewards,
466
1
                balance_after - balance_before,
467
                "bob should get the correct reward portion"
468
            );
469
1
        });
470
1
}
471

            
472
#[test]
473
1
fn test_reward_no_collators_no_inflation() {
474
1
    ExtBuilder::default()
475
1
        .with_balances(vec![
476
1
            // Alice gets 10k extra tokens for her mapping deposit
477
1
            (AccountId::from(ALICE), 210_000 * UNIT),
478
1
            (AccountId::from(BOB), 100_000 * UNIT),
479
1
            (AccountId::from(CHARLIE), 100_000 * UNIT),
480
1
            (AccountId::from(DAVE), 100_000 * UNIT),
481
1
        ])
482
1
        .with_collators(vec![(AccountId::from(ALICE), 210 * UNIT)])
483
1
        .with_empty_parachains(vec![1001, 1002])
484
1
        .build()
485
1
        .execute_with(|| {
486
            // Let's get the inflation of the block.
487
1
            let summary = run_block();
488

            
489
            // All container chains have 0 collators
490
1
            let assignment = TanssiCollatorAssignment::collator_container_chain();
491
            // There are 2 chains registered
492
1
            assert_eq!(assignment.container_chains.len(), 2);
493
            // But they don't have collators
494
2
            assert!(assignment.container_chains.values().all(|cs| cs.len() == 0));
495

            
496
            // So there is no minted inflation
497
1
            assert_eq!(summary.inflation, 0);
498
1
        });
499
1
}