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 orchestrator_rewards = all_rewards / 2;
162
1
            let candidate_rewards = RewardsCollatorCommission::get() * orchestrator_rewards;
163

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

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

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

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

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

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

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

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

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

            
243
1
            let balance_after = System::account(account).data.free;
244

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

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

            
274
1
            run_to_session(2u32);
275
1
            run_block();
276

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

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

            
294
1
            run_to_session(4u32);
295
1
            run_block();
296

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

            
300
1
            let summary = run_block();
301

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

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

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

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

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

            
341
1
            let balance_after = System::account(account).data.free;
342

            
343
1
            let all_rewards = RewardsPortion::get() * summary.inflation;
344
            // rewards are shared between the 2 paras
345
1
            let orchestrator_rewards = all_rewards / 2;
346
1
            assert_eq!(
347
                orchestrator_rewards,
348
1
                balance_after - balance_before,
349
                "alice should get the correct reward portion"
350
            );
351
1
        });
352
1
}
353

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

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

            
380
1
            let mut sproof = ParaHeaderSproofBuilder::default();
381
1
            let slot: u64 = 5;
382
1
            let other_para: ParaId = 1001u32.into();
383

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

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

            
409
1
            let account: AccountId = BOB.into();
410
1
            let balance_before = System::account(account.clone()).data.free;
411

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

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

            
425
1
            let balance_after = System::account(account).data.free;
426

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

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

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

            
459
            // So there is no minted inflation
460
1
            assert_eq!(summary.inflation, 0);
461
1
        });
462
1
}