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

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

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

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

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

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

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

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

            
110
            // wait for next session so that DAVE is elected
111
1
            run_to_session(4u32);
112
1
            run_block();
113

            
114
1
            let account: AccountId = DAVE.into();
115
1
            let balance_before = System::account(account.clone()).data.free;
116
1
            let summary = run_block();
117

            
118
            // Verify that all chains have collators
119
1
            let collator_assignment = TanssiCollatorAssignment::collator_container_chain();
120
            // 2 container chains total
121
1
            assert_eq!(collator_assignment.container_chains.len(), 2);
122
            // All container chains have collators
123
1
            assert!(collator_assignment
124
1
                .container_chains
125
1
                .values()
126
2
                .all(|cs| cs.len() == 2));
127

            
128
1
            let mut sproof = ParaHeaderSproofBuilder::default();
129
1
            let slot: u64 = 5;
130
1
            let other_para: ParaId = 1002u32.into();
131

            
132
            // Build the proof needed to call AuthorNoting's inherent.
133
1
            let s = ParaHeaderSproofBuilderItem {
134
1
                para_id: other_para,
135
1
                author_id: HeaderAs::NonEncoded(sp_runtime::generic::Header::<u32, BlakeTwo256> {
136
1
                    parent_hash: Default::default(),
137
1
                    number: 1,
138
1
                    state_root: Default::default(),
139
1
                    extrinsics_root: Default::default(),
140
1
                    digest: sp_runtime::generic::Digest {
141
1
                        logs: vec![DigestItem::PreRuntime(AURA_ENGINE_ID, slot.encode())],
142
1
                    },
143
1
                }),
144
1
            };
145
1
            sproof.items.push(s);
146

            
147
            // We need to set the AuthorNoting's inherent for it to also run
148
            // InflationRewards::on_container_authors_noted and reward the collator.
149
1
            set_author_noting_inherent_data(sproof);
150

            
151
            // Check that DAVE authored the container chain block. If this assert fails, change slot number above.
152
1
            let container_block_author = AuthorNoting::latest_author(ParaId::from(1002u32))
153
1
                .unwrap()
154
1
                .author;
155
1
            assert_eq!(container_block_author, AccountId::from(DAVE));
156

            
157
1
            let all_rewards = RewardsPortion::get() * summary.inflation;
158
1
            let rewards_per_chain = all_rewards / 2; // rewards are shared between the 2 paras
159

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

            
163
            {
164
1
                let pending = pallet_pooled_staking::PendingRewards::<Runtime>::get().expect("pending rewards to exist");
165
1
                assert_eq!(pending.last_distribution, 4u32, "timer should be up to date");
166
1
                let dave_rewards = pending.rewards.get(&account).expect("DAVE should have pending rewards");
167

            
168
1
                assert_eq!(*dave_rewards, rewards_per_chain, "dave should have pending rewards for 1 chain");
169
            }
170

            
171
1
            let balance_after = System::account(&account).data.free;
172
1
            assert_eq!(
173
                balance_before,
174
                balance_after,
175
                "dave shouldn't have rewards in their account yet"
176
            );
177

            
178
            // let's go to next session
179
1
            run_to_session(5u32);
180

            
181
            {
182
1
                let pending = pallet_pooled_staking::PendingRewards::<Runtime>::get().expect("pending rewards to exist");
183
1
                assert_eq!(pending.last_distribution, 5u32, "timer should be up to date");
184
1
                assert!(pending.rewards.is_empty(), "rewards should be distributed including the latest ones, so the pending rewards should be empty");
185
            }
186

            
187
1
            let balance_after = System::account(&account).data.free;
188
1
            assert_ne!(
189
                balance_before,
190
                balance_after,
191
                "dave should have rewards in their account now"
192
            );
193
1
        });
194
1
}
195

            
196
#[test]
197
1
fn test_reward_to_invulnerable() {
198
1
    ExtBuilder::default()
199
1
        .with_balances(vec![
200
1
            // Alice gets 10k extra tokens for her mapping deposit
201
1
            (AccountId::from(ALICE), 210_000 * UNIT),
202
1
            (AccountId::from(BOB), 100_000 * UNIT),
203
1
            (AccountId::from(CHARLIE), 100_000 * UNIT),
204
1
            (AccountId::from(DAVE), 100_000 * UNIT),
205
1
        ])
206
1
        .with_collators(vec![
207
1
            (AccountId::from(ALICE), 210 * UNIT),
208
1
            (AccountId::from(BOB), 100 * UNIT),
209
1
            (AccountId::from(CHARLIE), 100 * UNIT),
210
1
            (AccountId::from(DAVE), 100 * UNIT),
211
1
        ])
212
1
        .with_empty_parachains(vec![1001, 1002])
213
1
        .build()
214
1
        .execute_with(|| {
215
            // Let's get the inflation of the block.
216
1
            let summary = run_block();
217

            
218
            // Calculate Bob's rewards.
219
1
            let all_rewards = RewardsPortion::get() * summary.inflation;
220
1
            let bob_rewards = all_rewards / 2;
221

            
222
1
            let mut sproof = ParaHeaderSproofBuilder::default();
223
1
            let slot: u64 = 5;
224
1
            let other_para: ParaId = 1001u32.into();
225

            
226
            // In starlight there is no orchestrator chain, so instead of Charlie and Dave
227
            // we assign Alice and Bob.
228
1
            let assignment = TanssiCollatorAssignment::collator_container_chain();
229
1
            assert_eq!(
230
1
                assignment.container_chains[&1001u32.into()],
231
1
                vec![ALICE.into(), BOB.into()]
232
            );
233
            // All container chains have collators
234
2
            assert!(assignment.container_chains.values().all(|cs| cs.len() == 2));
235

            
236
            // Build the proof needed to call AuthorNoting's inherent.
237
1
            let s = ParaHeaderSproofBuilderItem {
238
1
                para_id: other_para,
239
1
                author_id: HeaderAs::NonEncoded(sp_runtime::generic::Header::<u32, BlakeTwo256> {
240
1
                    parent_hash: Default::default(),
241
1
                    number: 1,
242
1
                    state_root: Default::default(),
243
1
                    extrinsics_root: Default::default(),
244
1
                    digest: sp_runtime::generic::Digest {
245
1
                        logs: vec![DigestItem::PreRuntime(AURA_ENGINE_ID, slot.encode())],
246
1
                    },
247
1
                }),
248
1
            };
249
1
            sproof.items.push(s);
250

            
251
1
            let account: AccountId = BOB.into();
252
1
            let balance_before = System::account(account.clone()).data.free;
253

            
254
            // We need to set the AuthorNoting's inherent for it to also run
255
            // InflationRewards::on_container_authors_noted and reward the collator.
256
1
            set_author_noting_inherent_data(sproof);
257

            
258
1
            assert_eq!(
259
1
                AuthorNoting::latest_author(other_para),
260
1
                Some(ContainerChainBlockInfo {
261
1
                    block_number: 1,
262
1
                    author: AccountId::from(BOB),
263
1
                    latest_slot_number: 2.into(),
264
1
                })
265
            );
266

            
267
1
            let balance_after = System::account(account).data.free;
268

            
269
1
            assert_eq!(
270
                bob_rewards,
271
1
                balance_after - balance_before,
272
                "bob should get the correct reward portion"
273
            );
274
1
        });
275
1
}
276

            
277
#[test]
278
1
fn test_reward_to_invulnerable_with_key_change() {
279
1
    ExtBuilder::default()
280
1
        .with_balances(vec![
281
1
            // Alice gets 10k extra tokens for her mapping deposit
282
1
            (AccountId::from(ALICE), 210_000 * UNIT),
283
1
            (AccountId::from(BOB), 100_000 * UNIT),
284
1
            (AccountId::from(CHARLIE), 100_000 * UNIT),
285
1
            (AccountId::from(DAVE), 100_000 * UNIT),
286
1
        ])
287
1
        .with_collators(vec![
288
1
            (AccountId::from(ALICE), 210 * UNIT),
289
1
            (AccountId::from(BOB), 100 * UNIT),
290
1
            (AccountId::from(CHARLIE), 100 * UNIT),
291
1
            (AccountId::from(DAVE), 100 * UNIT),
292
1
        ])
293
1
        .with_empty_parachains(vec![1001, 1002])
294
1
        .build()
295
1
        .execute_with(|| {
296
1
            run_to_block(2);
297

            
298
1
            run_to_session(2u32);
299
1
            run_block();
300

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

            
304
1
            assert_ok!(Session::set_keys(
305
1
                origin_of(ALICE.into()),
306
1
                crate::SessionKeys {
307
1
                    babe: alice_new_key.babe.clone(),
308
1
                    grandpa: alice_new_key.grandpa.clone(),
309
1
                    para_validator: alice_new_key.para_validator.clone(),
310
1
                    para_assignment: alice_new_key.para_assignment.clone(),
311
1
                    authority_discovery: alice_new_key.authority_discovery.clone(),
312
1
                    beefy: alice_new_key.beefy.clone(),
313
1
                    nimbus: alice_new_key.nimbus.clone(),
314
1
                },
315
1
                vec![]
316
            ));
317

            
318
1
            run_to_session(4u32);
319
1
            run_block();
320

            
321
1
            let account: AccountId = ALICE.into();
322
1
            let balance_before = System::account(account.clone()).data.free;
323

            
324
1
            let summary = run_block();
325

            
326
            // Verify that all chains have collators
327
1
            let collator_assignment = TanssiCollatorAssignment::collator_container_chain();
328
            // 2 container chains total
329
1
            assert_eq!(collator_assignment.container_chains.len(), 2);
330
            // All container chains have collators
331
1
            assert!(collator_assignment
332
1
                .container_chains
333
1
                .values()
334
2
                .all(|cs| cs.len() == 2));
335

            
336
1
            let mut sproof = ParaHeaderSproofBuilder::default();
337
1
            let slot: u64 = 6;
338
1
            let other_para: ParaId = 1001u32.into();
339

            
340
            // Build the proof needed to call AuthorNoting's inherent.
341
1
            let s = ParaHeaderSproofBuilderItem {
342
1
                para_id: other_para,
343
1
                author_id: HeaderAs::NonEncoded(sp_runtime::generic::Header::<u32, BlakeTwo256> {
344
1
                    parent_hash: Default::default(),
345
1
                    number: 1,
346
1
                    state_root: Default::default(),
347
1
                    extrinsics_root: Default::default(),
348
1
                    digest: sp_runtime::generic::Digest {
349
1
                        logs: vec![DigestItem::PreRuntime(AURA_ENGINE_ID, slot.encode())],
350
1
                    },
351
1
                }),
352
1
            };
353
1
            sproof.items.push(s);
354

            
355
            // We need to set the AuthorNoting's inherent for it to also run
356
            // InflationRewards::on_container_authors_noted and reward the collator.
357
1
            set_author_noting_inherent_data(sproof);
358

            
359
            // Check that ALICE authored the container chain block. If this assert fails, change slot number above.
360
1
            let container_block_author = AuthorNoting::latest_author(ParaId::from(1001u32))
361
1
                .unwrap()
362
1
                .author;
363
1
            assert_eq!(container_block_author, AccountId::from(ALICE));
364

            
365
1
            let balance_after = System::account(account).data.free;
366

            
367
1
            let all_rewards = RewardsPortion::get() * summary.inflation;
368
            // rewards are shared between the 2 paras
369
1
            let orchestrator_rewards = all_rewards / 2;
370
1
            assert_eq!(
371
                orchestrator_rewards,
372
1
                balance_after - balance_before,
373
                "alice should get the correct reward portion"
374
            );
375
1
        });
376
1
}
377

            
378
#[test]
379
1
fn test_reward_chain_without_collators() {
380
1
    ExtBuilder::default()
381
1
        .with_balances(vec![
382
1
            // Alice gets 10k extra tokens for her mapping deposit
383
1
            (AccountId::from(ALICE), 210_000 * UNIT),
384
1
            (AccountId::from(BOB), 100_000 * UNIT),
385
1
            (AccountId::from(CHARLIE), 100_000 * UNIT),
386
1
            (AccountId::from(DAVE), 100_000 * UNIT),
387
1
        ])
388
1
        .with_collators(vec![
389
1
            (AccountId::from(ALICE), 210 * UNIT),
390
1
            (AccountId::from(BOB), 100 * UNIT),
391
1
        ])
392
1
        .with_empty_parachains(vec![1001, 1002])
393
1
        .build()
394
1
        .execute_with(|| {
395
            // Let's get the inflation of the block.
396
1
            let summary = run_block();
397

            
398
            // Calculate Bob's rewards.
399
1
            let all_rewards = RewardsPortion::get() * summary.inflation;
400
            // Even though there are 2 parachains registered, 1001 gets all the rewards because it
401
            // is the only chain with collators
402
1
            let bob_rewards = all_rewards;
403

            
404
1
            let mut sproof = ParaHeaderSproofBuilder::default();
405
1
            let slot: u64 = 5;
406
1
            let other_para: ParaId = 1001u32.into();
407

            
408
            // In dancelight there is no orchestrator chain, so instead of Charlie and Dave
409
            // we assign Alice and Bob.
410
1
            let assignment = TanssiCollatorAssignment::collator_container_chain();
411
1
            assert_eq!(
412
1
                assignment.container_chains[&1001u32.into()],
413
1
                vec![ALICE.into(), BOB.into()]
414
            );
415
            // The other chain has 0 collators
416
1
            assert_eq!(assignment.container_chains[&1002u32.into()], vec![]);
417

            
418
            // Build the proof needed to call AuthorNoting's inherent.
419
1
            let s = ParaHeaderSproofBuilderItem {
420
1
                para_id: other_para,
421
1
                author_id: HeaderAs::NonEncoded(sp_runtime::generic::Header::<u32, BlakeTwo256> {
422
1
                    parent_hash: Default::default(),
423
1
                    number: 1,
424
1
                    state_root: Default::default(),
425
1
                    extrinsics_root: Default::default(),
426
1
                    digest: sp_runtime::generic::Digest {
427
1
                        logs: vec![DigestItem::PreRuntime(AURA_ENGINE_ID, slot.encode())],
428
1
                    },
429
1
                }),
430
1
            };
431
1
            sproof.items.push(s);
432

            
433
1
            let account: AccountId = BOB.into();
434
1
            let balance_before = System::account(account.clone()).data.free;
435

            
436
            // We need to set the AuthorNoting's inherent for it to also run
437
            // InflationRewards::on_container_authors_noted and reward the collator.
438
1
            set_author_noting_inherent_data(sproof);
439

            
440
1
            assert_eq!(
441
1
                AuthorNoting::latest_author(other_para),
442
1
                Some(ContainerChainBlockInfo {
443
1
                    block_number: 1,
444
1
                    author: AccountId::from(BOB),
445
1
                    latest_slot_number: 2.into(),
446
1
                })
447
            );
448

            
449
1
            let balance_after = System::account(account).data.free;
450

            
451
1
            assert_eq!(
452
                bob_rewards,
453
1
                balance_after - balance_before,
454
                "bob should get the correct reward portion"
455
            );
456
1
        });
457
1
}
458

            
459
#[test]
460
1
fn test_reward_no_collators_no_inflation() {
461
1
    ExtBuilder::default()
462
1
        .with_balances(vec![
463
1
            // Alice gets 10k extra tokens for her mapping deposit
464
1
            (AccountId::from(ALICE), 210_000 * UNIT),
465
1
            (AccountId::from(BOB), 100_000 * UNIT),
466
1
            (AccountId::from(CHARLIE), 100_000 * UNIT),
467
1
            (AccountId::from(DAVE), 100_000 * UNIT),
468
1
        ])
469
1
        .with_collators(vec![(AccountId::from(ALICE), 210 * UNIT)])
470
1
        .with_empty_parachains(vec![1001, 1002])
471
1
        .build()
472
1
        .execute_with(|| {
473
            // Let's get the inflation of the block.
474
1
            let summary = run_block();
475

            
476
            // All container chains have 0 collators
477
1
            let assignment = TanssiCollatorAssignment::collator_container_chain();
478
            // There are 2 chains registered
479
1
            assert_eq!(assignment.container_chains.len(), 2);
480
            // But they don't have collators
481
2
            assert!(assignment.container_chains.values().all(|cs| cs.len() == 0));
482

            
483
            // So there is no minted inflation
484
1
            assert_eq!(summary.inflation, 0);
485
1
        });
486
1
}