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, RewardsCollatorCommission};
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
            // wait for next session so that DAVE is elected
112
1
            run_to_session(4u32);
113
1
            run_block();
114

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

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

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

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

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

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

            
159
1
            let all_rewards = RewardsPortion::get() * summary.inflation;
160
            // rewards are shared between the 2 paras
161
1
            let rewards_per_chain = all_rewards / 2;
162
            // candidate gets transferred 20% of the reward immediately.
163
            // the rest goes into the staking pools
164
1
            let candidate_rewards = RewardsCollatorCommission::get() * rewards_per_chain;
165

            
166
1
            assert_eq!(
167
                candidate_rewards,
168
1
                balance_after - balance_before,
169
                "dave should get the correct reward portion"
170
            );
171
1
        });
172
1
}
173

            
174
#[test]
175
1
fn test_reward_to_invulnerable() {
176
1
    ExtBuilder::default()
177
1
        .with_balances(vec![
178
1
            // Alice gets 10k extra tokens for her mapping deposit
179
1
            (AccountId::from(ALICE), 210_000 * UNIT),
180
1
            (AccountId::from(BOB), 100_000 * UNIT),
181
1
            (AccountId::from(CHARLIE), 100_000 * UNIT),
182
1
            (AccountId::from(DAVE), 100_000 * UNIT),
183
1
        ])
184
1
        .with_collators(vec![
185
1
            (AccountId::from(ALICE), 210 * UNIT),
186
1
            (AccountId::from(BOB), 100 * UNIT),
187
1
            (AccountId::from(CHARLIE), 100 * UNIT),
188
1
            (AccountId::from(DAVE), 100 * UNIT),
189
1
        ])
190
1
        .with_empty_parachains(vec![1001, 1002])
191
1
        .build()
192
1
        .execute_with(|| {
193
            // Let's get the inflation of the block.
194
1
            let summary = run_block();
195

            
196
            // Calculate Bob's rewards.
197
1
            let all_rewards = RewardsPortion::get() * summary.inflation;
198
1
            let bob_rewards = all_rewards / 2;
199

            
200
1
            let mut sproof = ParaHeaderSproofBuilder::default();
201
1
            let slot: u64 = 5;
202
1
            let other_para: ParaId = 1001u32.into();
203

            
204
            // In dancelight there is no orchestrator chain, so instead of Charlie and Dave
205
            // we assign Alice and Bob.
206
1
            let assignment = TanssiCollatorAssignment::collator_container_chain();
207
1
            assert_eq!(
208
1
                assignment.container_chains[&1001u32.into()],
209
1
                vec![ALICE.into(), BOB.into()]
210
            );
211
            // All container chains have collators
212
2
            assert!(assignment.container_chains.values().all(|cs| cs.len() == 2));
213

            
214
            // Build the proof needed to call AuthorNoting's inherent.
215
1
            let s = ParaHeaderSproofBuilderItem {
216
1
                para_id: other_para,
217
1
                author_id: HeaderAs::NonEncoded(sp_runtime::generic::Header::<u32, BlakeTwo256> {
218
1
                    parent_hash: Default::default(),
219
1
                    number: 1,
220
1
                    state_root: Default::default(),
221
1
                    extrinsics_root: Default::default(),
222
1
                    digest: sp_runtime::generic::Digest {
223
1
                        logs: vec![DigestItem::PreRuntime(AURA_ENGINE_ID, slot.encode())],
224
1
                    },
225
1
                }),
226
1
            };
227
1
            sproof.items.push(s);
228

            
229
1
            let account: AccountId = BOB.into();
230
1
            let balance_before = System::account(account.clone()).data.free;
231

            
232
            // We need to set the AuthorNoting's inherent for it to also run
233
            // InflationRewards::on_container_authors_noted and reward the collator.
234
1
            set_author_noting_inherent_data(sproof);
235

            
236
1
            assert_eq!(
237
1
                AuthorNoting::latest_author(other_para),
238
1
                Some(ContainerChainBlockInfo {
239
1
                    block_number: 1,
240
1
                    author: AccountId::from(BOB),
241
1
                    latest_slot_number: 2.into(),
242
1
                })
243
            );
244

            
245
1
            let balance_after = System::account(account).data.free;
246

            
247
1
            assert_eq!(
248
                bob_rewards,
249
1
                balance_after - balance_before,
250
                "bob should get the correct reward portion"
251
            );
252
1
        });
253
1
}
254

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

            
276
1
            run_to_session(2u32);
277
1
            run_block();
278

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

            
282
1
            assert_ok!(Session::set_keys(
283
1
                origin_of(ALICE.into()),
284
1
                crate::SessionKeys {
285
1
                    babe: alice_new_key.babe.clone(),
286
1
                    grandpa: alice_new_key.grandpa.clone(),
287
1
                    para_validator: alice_new_key.para_validator.clone(),
288
1
                    para_assignment: alice_new_key.para_assignment.clone(),
289
1
                    authority_discovery: alice_new_key.authority_discovery.clone(),
290
1
                    beefy: alice_new_key.beefy.clone(),
291
1
                    nimbus: alice_new_key.nimbus.clone(),
292
1
                },
293
1
                vec![]
294
            ));
295

            
296
1
            run_to_session(4u32);
297
1
            run_block();
298

            
299
1
            let account: AccountId = ALICE.into();
300
1
            let balance_before = System::account(account.clone()).data.free;
301

            
302
1
            let summary = run_block();
303

            
304
            // Verify that all chains have collators
305
1
            let collator_assignment = TanssiCollatorAssignment::collator_container_chain();
306
            // 2 container chains total
307
1
            assert_eq!(collator_assignment.container_chains.len(), 2);
308
            // All container chains have collators
309
1
            assert!(collator_assignment
310
1
                .container_chains
311
1
                .values()
312
2
                .all(|cs| cs.len() == 2));
313

            
314
1
            let mut sproof = ParaHeaderSproofBuilder::default();
315
1
            let slot: u64 = 6;
316
1
            let other_para: ParaId = 1001u32.into();
317

            
318
            // Build the proof needed to call AuthorNoting's inherent.
319
1
            let s = ParaHeaderSproofBuilderItem {
320
1
                para_id: other_para,
321
1
                author_id: HeaderAs::NonEncoded(sp_runtime::generic::Header::<u32, BlakeTwo256> {
322
1
                    parent_hash: Default::default(),
323
1
                    number: 1,
324
1
                    state_root: Default::default(),
325
1
                    extrinsics_root: Default::default(),
326
1
                    digest: sp_runtime::generic::Digest {
327
1
                        logs: vec![DigestItem::PreRuntime(AURA_ENGINE_ID, slot.encode())],
328
1
                    },
329
1
                }),
330
1
            };
331
1
            sproof.items.push(s);
332

            
333
            // We need to set the AuthorNoting's inherent for it to also run
334
            // InflationRewards::on_container_authors_noted and reward the collator.
335
1
            set_author_noting_inherent_data(sproof);
336

            
337
            // Check that ALICE authored the container chain block. If this assert fails, change slot number above.
338
1
            let container_block_author = AuthorNoting::latest_author(ParaId::from(1001u32))
339
1
                .unwrap()
340
1
                .author;
341
1
            assert_eq!(container_block_author, AccountId::from(ALICE));
342

            
343
1
            let balance_after = System::account(account).data.free;
344

            
345
1
            let all_rewards = RewardsPortion::get() * summary.inflation;
346
            // rewards are shared between the 2 paras
347
1
            let rewards_per_chain = all_rewards / 2;
348
            // invulnerables get 100% of the reward immediately, unlike staking candidates
349
1
            assert_eq!(
350
                rewards_per_chain,
351
1
                balance_after - balance_before,
352
                "alice should get the correct reward portion"
353
            );
354
1
        });
355
1
}
356

            
357
#[test]
358
1
fn test_reward_chain_without_collators() {
359
1
    ExtBuilder::default()
360
1
        .with_balances(vec![
361
1
            // Alice gets 10k extra tokens for her mapping deposit
362
1
            (AccountId::from(ALICE), 210_000 * UNIT),
363
1
            (AccountId::from(BOB), 100_000 * UNIT),
364
1
            (AccountId::from(CHARLIE), 100_000 * UNIT),
365
1
            (AccountId::from(DAVE), 100_000 * UNIT),
366
1
        ])
367
1
        .with_collators(vec![
368
1
            (AccountId::from(ALICE), 210 * UNIT),
369
1
            (AccountId::from(BOB), 100 * UNIT),
370
1
        ])
371
1
        .with_empty_parachains(vec![1001, 1002])
372
1
        .build()
373
1
        .execute_with(|| {
374
            // Let's get the inflation of the block.
375
1
            let summary = run_block();
376

            
377
            // Calculate Bob's rewards.
378
1
            let all_rewards = RewardsPortion::get() * summary.inflation;
379
            // Even though there are 2 parachains registered, 1001 gets all the rewards because it
380
            // is the only chain with collators
381
1
            let bob_rewards = all_rewards;
382

            
383
1
            let mut sproof = ParaHeaderSproofBuilder::default();
384
1
            let slot: u64 = 5;
385
1
            let other_para: ParaId = 1001u32.into();
386

            
387
            // In dancelight there is no orchestrator chain, so instead of Charlie and Dave
388
            // we assign Alice and Bob.
389
1
            let assignment = TanssiCollatorAssignment::collator_container_chain();
390
1
            assert_eq!(
391
1
                assignment.container_chains[&1001u32.into()],
392
1
                vec![ALICE.into(), BOB.into()]
393
            );
394
            // The other chain has 0 collators
395
1
            assert_eq!(assignment.container_chains[&1002u32.into()], vec![]);
396

            
397
            // Build the proof needed to call AuthorNoting's inherent.
398
1
            let s = ParaHeaderSproofBuilderItem {
399
1
                para_id: other_para,
400
1
                author_id: HeaderAs::NonEncoded(sp_runtime::generic::Header::<u32, BlakeTwo256> {
401
1
                    parent_hash: Default::default(),
402
1
                    number: 1,
403
1
                    state_root: Default::default(),
404
1
                    extrinsics_root: Default::default(),
405
1
                    digest: sp_runtime::generic::Digest {
406
1
                        logs: vec![DigestItem::PreRuntime(AURA_ENGINE_ID, slot.encode())],
407
1
                    },
408
1
                }),
409
1
            };
410
1
            sproof.items.push(s);
411

            
412
1
            let account: AccountId = BOB.into();
413
1
            let balance_before = System::account(account.clone()).data.free;
414

            
415
            // We need to set the AuthorNoting's inherent for it to also run
416
            // InflationRewards::on_container_authors_noted and reward the collator.
417
1
            set_author_noting_inherent_data(sproof);
418

            
419
1
            assert_eq!(
420
1
                AuthorNoting::latest_author(other_para),
421
1
                Some(ContainerChainBlockInfo {
422
1
                    block_number: 1,
423
1
                    author: AccountId::from(BOB),
424
1
                    latest_slot_number: 2.into(),
425
1
                })
426
            );
427

            
428
1
            let balance_after = System::account(account).data.free;
429

            
430
1
            assert_eq!(
431
                bob_rewards,
432
1
                balance_after - balance_before,
433
                "bob should get the correct reward portion"
434
            );
435
1
        });
436
1
}
437

            
438
#[test]
439
1
fn test_reward_no_collators_no_inflation() {
440
1
    ExtBuilder::default()
441
1
        .with_balances(vec![
442
1
            // Alice gets 10k extra tokens for her mapping deposit
443
1
            (AccountId::from(ALICE), 210_000 * UNIT),
444
1
            (AccountId::from(BOB), 100_000 * UNIT),
445
1
            (AccountId::from(CHARLIE), 100_000 * UNIT),
446
1
            (AccountId::from(DAVE), 100_000 * UNIT),
447
1
        ])
448
1
        .with_collators(vec![(AccountId::from(ALICE), 210 * UNIT)])
449
1
        .with_empty_parachains(vec![1001, 1002])
450
1
        .build()
451
1
        .execute_with(|| {
452
            // Let's get the inflation of the block.
453
1
            let summary = run_block();
454

            
455
            // All container chains have 0 collators
456
1
            let assignment = TanssiCollatorAssignment::collator_container_chain();
457
            // There are 2 chains registered
458
1
            assert_eq!(assignment.container_chains.len(), 2);
459
            // But they don't have collators
460
2
            assert!(assignment.container_chains.values().all(|cs| cs.len() == 0));
461

            
462
            // So there is no minted inflation
463
1
            assert_eq!(summary.inflation, 0);
464
1
        });
465
1
}