1
// Copyright 2019-2025 PureStake Inc.
2
// This file is part of Moonbeam.
3

            
4
// Moonbeam 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
// Moonbeam 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 Moonbeam.  If not, see <http://www.gnu.org/licenses/>.
16

            
17
//! Test utilities
18
use crate::{
19
	self as pallet_parachain_staking, InflationDistributionAccount, InflationDistributionConfig,
20
};
21
use crate::{
22
	pallet, AwardedPts, Config, Event as ParachainStakingEvent, InflationInfo, Points, Range,
23
	COLLATOR_LOCK_ID, DELEGATOR_LOCK_ID,
24
};
25
use block_author::BlockAuthor as BlockAuthorMap;
26
use frame_support::{
27
	construct_runtime, parameter_types,
28
	traits::{Everything, Get, LockIdentifier, OnFinalize, OnInitialize},
29
	weights::{constants::RocksDbWeight, Weight},
30
};
31
use frame_system::pallet_prelude::BlockNumberFor;
32
use sp_consensus_slots::Slot;
33
use sp_core::H256;
34
use sp_io;
35
use sp_runtime::BuildStorage;
36
use sp_runtime::{
37
	traits::{BlakeTwo256, IdentityLookup},
38
	Perbill, Percent,
39
};
40

            
41
pub type AccountId = u64;
42
pub type Balance = u128;
43
pub type BlockNumber = BlockNumberFor<Test>;
44

            
45
type Block = frame_system::mocking::MockBlockU32<Test>;
46

            
47
// Configure a mock runtime to test the pallet.
48
224814
construct_runtime!(
49
44632
	pub enum Test
50
44632
	{
51
44632
		System: frame_system,
52
44632
		Balances: pallet_balances,
53
44632
		ParachainStaking: pallet_parachain_staking,
54
44632
		BlockAuthor: block_author,
55
44632
	}
56
234768
);
57

            
58
parameter_types! {
59
	pub const BlockHashCount: u32 = 250;
60
	pub const MaximumBlockWeight: Weight = Weight::from_parts(1024, 1);
61
	pub const MaximumBlockLength: u32 = 2 * 1024;
62
	pub const AvailableBlockRatio: Perbill = Perbill::one();
63
	pub const SS58Prefix: u8 = 42;
64
}
65
impl frame_system::Config for Test {
66
	type BaseCallFilter = Everything;
67
	type DbWeight = RocksDbWeight;
68
	type RuntimeOrigin = RuntimeOrigin;
69
	type RuntimeTask = RuntimeTask;
70
	type Nonce = u64;
71
	type Block = Block;
72
	type RuntimeCall = RuntimeCall;
73
	type Hash = H256;
74
	type Hashing = BlakeTwo256;
75
	type AccountId = AccountId;
76
	type Lookup = IdentityLookup<Self::AccountId>;
77
	type RuntimeEvent = RuntimeEvent;
78
	type BlockHashCount = BlockHashCount;
79
	type Version = ();
80
	type PalletInfo = PalletInfo;
81
	type AccountData = pallet_balances::AccountData<Balance>;
82
	type OnNewAccount = ();
83
	type OnKilledAccount = ();
84
	type SystemWeightInfo = ();
85
	type BlockWeights = ();
86
	type BlockLength = ();
87
	type SS58Prefix = SS58Prefix;
88
	type OnSetCode = ();
89
	type MaxConsumers = frame_support::traits::ConstU32<16>;
90
	type SingleBlockMigrations = ();
91
	type MultiBlockMigrator = ();
92
	type PreInherents = ();
93
	type PostInherents = ();
94
	type PostTransactions = ();
95
	type ExtensionsWeightInfo = ();
96
}
97
parameter_types! {
98
	pub const ExistentialDeposit: u128 = 0;
99
}
100
impl pallet_balances::Config for Test {
101
	type MaxReserves = ();
102
	type ReserveIdentifier = [u8; 4];
103
	type MaxLocks = ();
104
	type Balance = Balance;
105
	type RuntimeEvent = RuntimeEvent;
106
	type DustRemoval = ();
107
	type ExistentialDeposit = ExistentialDeposit;
108
	type AccountStore = System;
109
	type WeightInfo = ();
110
	type RuntimeHoldReason = ();
111
	type FreezeIdentifier = ();
112
	type MaxFreezes = ();
113
	type RuntimeFreezeReason = ();
114
	type DoneSlashHandler = ();
115
}
116
impl block_author::Config for Test {}
117
const GENESIS_BLOCKS_PER_ROUND: BlockNumber = 5;
118
const GENESIS_COLLATOR_COMMISSION: Perbill = Perbill::from_percent(20);
119
const GENESIS_PARACHAIN_BOND_RESERVE_PERCENT: Percent = Percent::from_percent(30);
120
const GENESIS_NUM_SELECTED_CANDIDATES: u32 = 5;
121

            
122
pub const POINTS_PER_BLOCK: u32 = 20;
123
pub const POINTS_PER_ROUND: u32 = GENESIS_BLOCKS_PER_ROUND * POINTS_PER_BLOCK;
124

            
125
parameter_types! {
126
	pub const MinBlocksPerRound: u32 = 3;
127
	pub const MaxOfflineRounds: u32 = 2;
128
	pub const LeaveCandidatesDelay: u32 = 2;
129
	pub const CandidateBondLessDelay: u32 = 2;
130
	pub const LeaveDelegatorsDelay: u32 = 2;
131
	pub const RevokeDelegationDelay: u32 = 2;
132
	pub const DelegationBondLessDelay: u32 = 2;
133
	pub const RewardPaymentDelay: u32 = 2;
134
	pub const MinSelectedCandidates: u32 = GENESIS_NUM_SELECTED_CANDIDATES;
135
	pub const MaxTopDelegationsPerCandidate: u32 = 4;
136
	pub const MaxBottomDelegationsPerCandidate: u32 = 4;
137
	pub const MaxDelegationsPerDelegator: u32 = 4;
138
	pub const MinCandidateStk: u128 = 10;
139
	pub const MinDelegation: u128 = 3;
140
	pub const MaxCandidates: u32 = 200;
141
}
142

            
143
pub struct StakingRoundSlotProvider;
144
impl Get<Slot> for StakingRoundSlotProvider {
145
230
	fn get() -> Slot {
146
230
		let block_number: u64 = System::block_number().into();
147
230
		Slot::from(block_number)
148
230
	}
149
}
150

            
151
parameter_types! {
152
	pub const LinearInflationThreshold: Option<Balance> = Some(1_200_000_000);
153
}
154

            
155
impl Config for Test {
156
	type RuntimeEvent = RuntimeEvent;
157
	type Currency = Balances;
158
	type MonetaryGovernanceOrigin = frame_system::EnsureRoot<AccountId>;
159
	type MinBlocksPerRound = MinBlocksPerRound;
160
	type MaxOfflineRounds = MaxOfflineRounds;
161
	type LeaveCandidatesDelay = LeaveCandidatesDelay;
162
	type CandidateBondLessDelay = CandidateBondLessDelay;
163
	type LeaveDelegatorsDelay = LeaveDelegatorsDelay;
164
	type RevokeDelegationDelay = RevokeDelegationDelay;
165
	type DelegationBondLessDelay = DelegationBondLessDelay;
166
	type RewardPaymentDelay = RewardPaymentDelay;
167
	type MinSelectedCandidates = MinSelectedCandidates;
168
	type MaxTopDelegationsPerCandidate = MaxTopDelegationsPerCandidate;
169
	type MaxBottomDelegationsPerCandidate = MaxBottomDelegationsPerCandidate;
170
	type MaxDelegationsPerDelegator = MaxDelegationsPerDelegator;
171
	type MinCandidateStk = MinCandidateStk;
172
	type MinDelegation = MinDelegation;
173
	type BlockAuthor = BlockAuthor;
174
	type OnCollatorPayout = ();
175
	type PayoutCollatorReward = ();
176
	type OnInactiveCollator = ();
177
	type OnNewRound = ();
178
	type SlotProvider = StakingRoundSlotProvider;
179
	type WeightInfo = ();
180
	type MaxCandidates = MaxCandidates;
181
	type SlotDuration = frame_support::traits::ConstU64<6_000>;
182
	type BlockTime = frame_support::traits::ConstU64<6_000>;
183
	type LinearInflationThreshold = LinearInflationThreshold;
184
}
185

            
186
pub(crate) struct ExtBuilder {
187
	// endowed accounts with balances
188
	balances: Vec<(AccountId, Balance)>,
189
	// [collator, amount]
190
	collators: Vec<(AccountId, Balance)>,
191
	// [delegator, collator, delegation_amount, auto_compound_percent]
192
	delegations: Vec<(AccountId, AccountId, Balance, Percent)>,
193
	// inflation config
194
	inflation: InflationInfo<Balance>,
195
}
196

            
197
impl Default for ExtBuilder {
198
261
	fn default() -> ExtBuilder {
199
261
		ExtBuilder {
200
261
			balances: vec![],
201
261
			delegations: vec![],
202
261
			collators: vec![],
203
261
			inflation: InflationInfo {
204
261
				expect: Range {
205
261
					min: 700,
206
261
					ideal: 700,
207
261
					max: 700,
208
261
				},
209
261
				// not used
210
261
				annual: Range {
211
261
					min: Perbill::from_percent(50),
212
261
					ideal: Perbill::from_percent(50),
213
261
					max: Perbill::from_percent(50),
214
261
				},
215
261
				// unrealistically high parameterization, only for testing
216
261
				round: Range {
217
261
					min: Perbill::from_percent(5),
218
261
					ideal: Perbill::from_percent(5),
219
261
					max: Perbill::from_percent(5),
220
261
				},
221
261
			},
222
261
		}
223
261
	}
224
}
225

            
226
impl ExtBuilder {
227
195
	pub(crate) fn with_balances(mut self, balances: Vec<(AccountId, Balance)>) -> Self {
228
195
		self.balances = balances;
229
195
		self
230
195
	}
231

            
232
181
	pub(crate) fn with_candidates(mut self, collators: Vec<(AccountId, Balance)>) -> Self {
233
181
		self.collators = collators;
234
181
		self
235
181
	}
236

            
237
103
	pub(crate) fn with_delegations(
238
103
		mut self,
239
103
		delegations: Vec<(AccountId, AccountId, Balance)>,
240
103
	) -> Self {
241
103
		self.delegations = delegations
242
103
			.into_iter()
243
8946
			.map(|d| (d.0, d.1, d.2, Percent::zero()))
244
103
			.collect();
245
103
		self
246
103
	}
247

            
248
2
	pub(crate) fn with_auto_compounding_delegations(
249
2
		mut self,
250
2
		delegations: Vec<(AccountId, AccountId, Balance, Percent)>,
251
2
	) -> Self {
252
2
		self.delegations = delegations;
253
2
		self
254
2
	}
255

            
256
	#[allow(dead_code)]
257
	pub(crate) fn with_inflation(mut self, inflation: InflationInfo<Balance>) -> Self {
258
		self.inflation = inflation;
259
		self
260
	}
261

            
262
261
	pub(crate) fn build(self) -> sp_io::TestExternalities {
263
261
		let mut t = frame_system::GenesisConfig::<Test>::default()
264
261
			.build_storage()
265
261
			.expect("Frame system builds valid default genesis config");
266
261

            
267
261
		pallet_balances::GenesisConfig::<Test> {
268
261
			balances: self.balances,
269
261
		}
270
261
		.assimilate_storage(&mut t)
271
261
		.expect("Pallet balances storage can be assimilated");
272
261
		pallet_parachain_staking::GenesisConfig::<Test> {
273
261
			candidates: self.collators,
274
261
			delegations: self.delegations,
275
261
			inflation_config: self.inflation,
276
261
			collator_commission: GENESIS_COLLATOR_COMMISSION,
277
261
			parachain_bond_reserve_percent: GENESIS_PARACHAIN_BOND_RESERVE_PERCENT,
278
261
			blocks_per_round: GENESIS_BLOCKS_PER_ROUND,
279
261
			num_selected_candidates: GENESIS_NUM_SELECTED_CANDIDATES,
280
261
		}
281
261
		.assimilate_storage(&mut t)
282
261
		.expect("Parachain Staking's storage can be assimilated");
283
261

            
284
261
		let mut ext = sp_io::TestExternalities::new(t);
285
261
		ext.execute_with(|| System::set_block_number(1));
286
261
		ext
287
261
	}
288
}
289

            
290
/// Rolls forward one block. Returns the new block number.
291
1149
fn roll_one_block() -> BlockNumber {
292
1149
	ParachainStaking::on_finalize(System::block_number());
293
1149
	Balances::on_finalize(System::block_number());
294
1149
	System::on_finalize(System::block_number());
295
1149
	System::set_block_number(System::block_number() + 1);
296
1149
	System::reset_events();
297
1149
	System::on_initialize(System::block_number());
298
1149
	Balances::on_initialize(System::block_number());
299
1149
	ParachainStaking::on_initialize(System::block_number());
300
1149
	System::block_number()
301
1149
}
302

            
303
/// Rolls to the desired block. Returns the number of blocks played.
304
132
pub(crate) fn roll_to(n: BlockNumber) -> BlockNumber {
305
132
	let mut num_blocks = 0;
306
132
	let mut block = System::block_number();
307
1172
	while block < n {
308
1040
		block = roll_one_block();
309
1040
		num_blocks += 1;
310
1040
	}
311
132
	num_blocks
312
132
}
313

            
314
/// Rolls desired number of blocks. Returns the final block.
315
63
pub(crate) fn roll_blocks(num_blocks: u32) -> BlockNumber {
316
63
	let mut block = System::block_number();
317
109
	for _ in 0..num_blocks {
318
109
		block = roll_one_block();
319
109
	}
320
63
	block
321
63
}
322

            
323
/// Rolls block-by-block to the beginning of the specified round.
324
/// This will complete the block in which the round change occurs.
325
/// Returns the number of blocks played.
326
73
pub(crate) fn roll_to_round_begin(round: BlockNumber) -> BlockNumber {
327
73
	let r = ParachainStaking::round();
328
73

            
329
73
	// Return 0 if target round has already passed
330
73
	if round < r.current + 1 {
331
3
		return 0;
332
70
	}
333
70

            
334
70
	// Calculate target block by adding round length for each round difference
335
70
	let target = r.first + (round - r.current) * r.length;
336
70
	roll_to(target)
337
73
}
338

            
339
/// Rolls block-by-block to the end of the specified round.
340
/// The block following will be the one in which the specified round change occurs.
341
12
pub(crate) fn roll_to_round_end(round: BlockNumber) -> BlockNumber {
342
12
	let r = ParachainStaking::round();
343
12

            
344
12
	// Return 0 if target round has already passed
345
12
	if round < r.current {
346
1
		return 0;
347
11
	}
348
11

            
349
11
	// Calculate target block by adding round length for each round difference
350
11
	let target = r.first + (round - r.current + 1) * r.length - 1;
351
11
	roll_to(target)
352
12
}
353

            
354
38
pub(crate) fn inflation_configs(
355
38
	pbr: AccountId,
356
38
	pbr_percent: u8,
357
38
	treasury: AccountId,
358
38
	treasury_percent: u8,
359
38
) -> InflationDistributionConfig<AccountId> {
360
38
	[
361
38
		InflationDistributionAccount {
362
38
			account: pbr,
363
38
			percent: Percent::from_percent(pbr_percent),
364
38
		},
365
38
		InflationDistributionAccount {
366
38
			account: treasury,
367
38
			percent: Percent::from_percent(treasury_percent),
368
38
		},
369
38
	]
370
38
	.into()
371
38
}
372

            
373
203
pub(crate) fn events() -> Vec<pallet::Event<Test>> {
374
203
	System::events()
375
203
		.into_iter()
376
799
		.map(|r| r.event)
377
799
		.filter_map(|e| {
378
799
			if let RuntimeEvent::ParachainStaking(inner) = e {
379
552
				Some(inner)
380
			} else {
381
247
				None
382
			}
383
799
		})
384
203
		.collect::<Vec<_>>()
385
203
}
386

            
387
/// Asserts that some events were never emitted.
388
///
389
/// # Example
390
///
391
/// ```
392
/// assert_no_events!();
393
/// ```
394
#[macro_export]
395
macro_rules! assert_no_events {
396
	() => {
397
		similar_asserts::assert_eq!(Vec::<Event<Test>>::new(), crate::mock::events())
398
	};
399
}
400

            
401
/// Asserts that emitted events match exactly the given input.
402
///
403
/// # Example
404
///
405
/// ```
406
/// assert_events_eq!(
407
///		Foo { x: 1, y: 2 },
408
///		Bar { value: "test" },
409
///		Baz { a: 10, b: 20 },
410
/// );
411
/// ```
412
#[macro_export]
413
macro_rules! assert_events_eq {
414
	($event:expr) => {
415
		similar_asserts::assert_eq!(vec![$event], crate::mock::events());
416
	};
417
	($($events:expr,)+) => {
418
		similar_asserts::assert_eq!(vec![$($events,)+], crate::mock::events());
419
	};
420
}
421

            
422
/// Asserts that some emitted events match the given input.
423
///
424
/// # Example
425
///
426
/// ```
427
/// assert_events_emitted!(
428
///		Foo { x: 1, y: 2 },
429
///		Baz { a: 10, b: 20 },
430
/// );
431
/// ```
432
#[macro_export]
433
macro_rules! assert_events_emitted {
434
	($event:expr) => {
435
35
		[$event].into_iter().for_each(|e| assert!(
436
92
			crate::mock::events().into_iter().find(|x| x == &e).is_some(),
437
2
			"Event {:?} was not found in events: \n{:#?}",
438
2
			e,
439
2
			crate::mock::events()
440
33
		));
441
	};
442
	($($events:expr,)+) => {
443
3
		[$($events,)+].into_iter().for_each(|e| assert!(
444
7
			crate::mock::events().into_iter().find(|x| x == &e).is_some(),
445
			"Event {:?} was not found in events: \n{:#?}",
446
			e,
447
			crate::mock::events()
448
3
		));
449
	};
450
}
451

            
452
/// Asserts that some events were never emitted.
453
///
454
/// # Example
455
///
456
/// ```
457
/// assert_events_not_emitted!(
458
///		Foo { x: 1, y: 2 },
459
///		Bar { value: "test" },
460
/// );
461
/// ```
462
#[macro_export]
463
macro_rules! assert_events_not_emitted {
464
	($event:expr) => {
465
		[$event].into_iter().for_each(|e| assert!(
466
			crate::mock::events().into_iter().find(|x| x != &e).is_some(),
467
			"Event {:?} was unexpectedly found in events: \n{:#?}",
468
			e,
469
			crate::mock::events()
470
		));
471
	};
472
	($($events:expr,)+) => {
473
		[$($events,)+].into_iter().for_each(|e| assert!(
474
			crate::mock::events().into_iter().find(|x| x != &e).is_some(),
475
			"Event {:?} was unexpectedly found in events: \n{:#?}",
476
			e,
477
			crate::mock::events()
478
		));
479
	};
480
}
481

            
482
/// Asserts that the emitted events are exactly equal to the input patterns.
483
///
484
/// # Example
485
///
486
/// ```
487
/// assert_events_eq_match!(
488
///		Foo { x: 1, .. },
489
///		Bar { .. },
490
///		Baz { a: 10, b: 20 },
491
/// );
492
/// ```
493
#[macro_export]
494
macro_rules! assert_events_eq_match {
495
	($index:expr;) => {
496
		assert_eq!(
497
			$index,
498
			crate::mock::events().len(),
499
			"Found {} extra event(s): \n{:#?}",
500
			crate::mock::events().len()-$index,
501
			crate::mock::events()
502
		);
503
	};
504
	($index:expr; $event:pat_param, $($events:pat_param,)*) => {
505
		assert!(
506
			matches!(
507
				crate::mock::events().get($index),
508
				Some($event),
509
			),
510
			"Event {:#?} was not found at index {}: \n{:#?}",
511
			stringify!($event),
512
			$index,
513
			crate::mock::events()
514
		);
515
		assert_events_eq_match!($index+1; $($events,)*);
516
	};
517
	($event:pat_param) => {
518
		assert_events_eq_match!(0; $event,);
519
	};
520
	($($events:pat_param,)+) => {
521
		assert_events_eq_match!(0; $($events,)+);
522
	};
523
}
524

            
525
/// Asserts that some emitted events match the input patterns.
526
///
527
/// # Example
528
///
529
/// ```
530
/// assert_events_emitted_match!(
531
///		Foo { x: 1, .. },
532
///		Baz { a: 10, b: 20 },
533
/// );
534
/// ```
535
#[macro_export]
536
macro_rules! assert_events_emitted_match {
537
	($event:pat_param) => {
538
		assert!(
539
59
			crate::mock::events().into_iter().any(|x| matches!(x, $event)),
540
			"Event {:?} was not found in events: \n{:#?}",
541
			stringify!($event),
542
			crate::mock::events()
543
		);
544
	};
545
	($event:pat_param, $($events:pat_param,)+) => {
546
		assert_events_emitted_match!($event);
547
		$(
548
			assert_events_emitted_match!($events);
549
		)+
550
	};
551
}
552

            
553
/// Asserts that the input patterns match none of the emitted events.
554
///
555
/// # Example
556
///
557
/// ```
558
/// assert_events_not_emitted_match!(
559
///		Foo { x: 1, .. },
560
///		Baz { a: 10, b: 20 },
561
/// );
562
/// ```
563
#[macro_export]
564
macro_rules! assert_events_not_emitted_match {
565
	($event:pat_param) => {
566
		assert!(
567
			crate::mock::events().into_iter().any(|x| !matches!(x, $event)),
568
			"Event {:?} was unexpectedly found in events: \n{:#?}",
569
			stringify!($event),
570
			crate::mock::events()
571
		);
572
	};
573
	($event:pat_param, $($events:pat_param,)+) => {
574
		assert_events_not_emitted_match!($event);
575
		$(
576
			assert_events_not_emitted_match!($events);
577
		)+
578
	};
579
}
580

            
581
// Same storage changes as ParachainStaking::on_finalize
582
65
pub(crate) fn set_author(round: BlockNumber, acc: u64, pts: u32) {
583
65
	<Points<Test>>::mutate(round, |p| *p += pts);
584
65
	<AwardedPts<Test>>::mutate(round, acc, |p| *p += pts);
585
65
}
586

            
587
// Allows to change the block author (default is always 0)
588
6
pub(crate) fn set_block_author(acc: u64) {
589
6
	<BlockAuthorMap<Test>>::set(acc);
590
6
}
591

            
592
/// fn to query the lock amount
593
25
pub(crate) fn query_lock_amount(account_id: u64, id: LockIdentifier) -> Option<Balance> {
594
25
	for lock in Balances::locks(&account_id) {
595
19
		if lock.id == id {
596
19
			return Some(lock.amount);
597
		}
598
	}
599
6
	None
600
25
}
601

            
602
#[test]
603
1
fn geneses() {
604
1
	ExtBuilder::default()
605
1
		.with_balances(vec![
606
1
			(1, 1000),
607
1
			(2, 300),
608
1
			(3, 100),
609
1
			(4, 100),
610
1
			(5, 100),
611
1
			(6, 100),
612
1
			(7, 100),
613
1
			(8, 9),
614
1
			(9, 4),
615
1
		])
616
1
		.with_candidates(vec![(1, 500), (2, 200)])
617
1
		.with_delegations(vec![(3, 1, 100), (4, 1, 100), (5, 2, 100), (6, 2, 100)])
618
1
		.build()
619
1
		.execute_with(|| {
620
1
			assert!(System::events().is_empty());
621
			// collators
622
1
			assert_eq!(
623
1
				ParachainStaking::get_collator_stakable_free_balance(&1),
624
1
				500
625
1
			);
626
1
			assert_eq!(query_lock_amount(1, COLLATOR_LOCK_ID), Some(500));
627
1
			assert!(ParachainStaking::is_candidate(&1));
628
1
			assert_eq!(query_lock_amount(2, COLLATOR_LOCK_ID), Some(200));
629
1
			assert_eq!(
630
1
				ParachainStaking::get_collator_stakable_free_balance(&2),
631
1
				100
632
1
			);
633
1
			assert!(ParachainStaking::is_candidate(&2));
634
			// delegators
635
5
			for x in 3..7 {
636
4
				assert!(ParachainStaking::is_delegator(&x));
637
4
				assert_eq!(ParachainStaking::get_delegator_stakable_balance(&x), 0);
638
4
				assert_eq!(query_lock_amount(x, DELEGATOR_LOCK_ID), Some(100));
639
			}
640
			// uninvolved
641
4
			for x in 7..10 {
642
3
				assert!(!ParachainStaking::is_delegator(&x));
643
			}
644
			// no delegator staking locks
645
1
			assert_eq!(query_lock_amount(7, DELEGATOR_LOCK_ID), None);
646
1
			assert_eq!(ParachainStaking::get_delegator_stakable_balance(&7), 100);
647
1
			assert_eq!(query_lock_amount(8, DELEGATOR_LOCK_ID), None);
648
1
			assert_eq!(ParachainStaking::get_delegator_stakable_balance(&8), 9);
649
1
			assert_eq!(query_lock_amount(9, DELEGATOR_LOCK_ID), None);
650
1
			assert_eq!(ParachainStaking::get_delegator_stakable_balance(&9), 4);
651
			// no collator staking locks
652
1
			assert_eq!(
653
1
				ParachainStaking::get_collator_stakable_free_balance(&7),
654
1
				100
655
1
			);
656
1
			assert_eq!(ParachainStaking::get_collator_stakable_free_balance(&8), 9);
657
1
			assert_eq!(ParachainStaking::get_collator_stakable_free_balance(&9), 4);
658
1
		});
659
1
	ExtBuilder::default()
660
1
		.with_balances(vec![
661
1
			(1, 100),
662
1
			(2, 100),
663
1
			(3, 100),
664
1
			(4, 100),
665
1
			(5, 100),
666
1
			(6, 100),
667
1
			(7, 100),
668
1
			(8, 100),
669
1
			(9, 100),
670
1
			(10, 100),
671
1
		])
672
1
		.with_candidates(vec![(1, 20), (2, 20), (3, 20), (4, 20), (5, 10)])
673
1
		.with_delegations(vec![
674
1
			(6, 1, 10),
675
1
			(7, 1, 10),
676
1
			(8, 2, 10),
677
1
			(9, 2, 10),
678
1
			(10, 1, 10),
679
1
		])
680
1
		.build()
681
1
		.execute_with(|| {
682
1
			assert!(System::events().is_empty());
683
			// collators
684
5
			for x in 1..5 {
685
4
				assert!(ParachainStaking::is_candidate(&x));
686
4
				assert_eq!(query_lock_amount(x, COLLATOR_LOCK_ID), Some(20));
687
4
				assert_eq!(ParachainStaking::get_collator_stakable_free_balance(&x), 80);
688
			}
689
1
			assert!(ParachainStaking::is_candidate(&5));
690
1
			assert_eq!(query_lock_amount(5, COLLATOR_LOCK_ID), Some(10));
691
1
			assert_eq!(ParachainStaking::get_collator_stakable_free_balance(&5), 90);
692
			// delegators
693
6
			for x in 6..11 {
694
5
				assert!(ParachainStaking::is_delegator(&x));
695
5
				assert_eq!(query_lock_amount(x, DELEGATOR_LOCK_ID), Some(10));
696
5
				assert_eq!(ParachainStaking::get_delegator_stakable_balance(&x), 90);
697
			}
698
1
		});
699
1
}
700

            
701
#[frame_support::pallet]
702
pub mod block_author {
703
	use super::*;
704
	use frame_support::pallet_prelude::*;
705
	use frame_support::traits::Get;
706

            
707
	#[pallet::config]
708
	pub trait Config: frame_system::Config {}
709

            
710
1
	#[pallet::pallet]
711
	pub struct Pallet<T>(_);
712

            
713
2310
	#[pallet::storage]
714
	#[pallet::getter(fn block_author)]
715
	pub(super) type BlockAuthor<T> = StorageValue<_, AccountId, ValueQuery>;
716

            
717
	impl<T: Config> Get<AccountId> for Pallet<T> {
718
1149
		fn get() -> AccountId {
719
1149
			<BlockAuthor<T>>::get()
720
1149
		}
721
	}
722
}
723

            
724
#[test]
725
1
fn roll_to_round_begin_works() {
726
1
	ExtBuilder::default().build().execute_with(|| {
727
1
		// these tests assume blocks-per-round of 5, as established by GENESIS_BLOCKS_PER_ROUND
728
1
		assert_eq!(System::block_number(), 1); // we start on block 1
729

            
730
1
		let num_blocks = roll_to_round_begin(1);
731
1
		assert_eq!(System::block_number(), 1); // no-op, we're already on this round
732
1
		assert_eq!(num_blocks, 0);
733

            
734
1
		let num_blocks = roll_to_round_begin(2);
735
1
		assert_eq!(System::block_number(), 5);
736
1
		assert_eq!(num_blocks, 4);
737

            
738
1
		let num_blocks = roll_to_round_begin(3);
739
1
		assert_eq!(System::block_number(), 10);
740
1
		assert_eq!(num_blocks, 5);
741
1
	});
742
1
}
743

            
744
#[test]
745
1
fn roll_to_round_end_works() {
746
1
	ExtBuilder::default().build().execute_with(|| {
747
1
		// these tests assume blocks-per-round of 5, as established by GENESIS_BLOCKS_PER_ROUND
748
1
		assert_eq!(System::block_number(), 1); // we start on block 1
749

            
750
1
		let num_blocks = roll_to_round_end(1);
751
1
		assert_eq!(System::block_number(), 4);
752
1
		assert_eq!(num_blocks, 3);
753

            
754
1
		let num_blocks = roll_to_round_end(2);
755
1
		assert_eq!(System::block_number(), 9);
756
1
		assert_eq!(num_blocks, 5);
757

            
758
1
		let num_blocks = roll_to_round_end(3);
759
1
		assert_eq!(System::block_number(), 14);
760
1
		assert_eq!(num_blocks, 5);
761
1
	});
762
1
}
763

            
764
#[test]
765
#[should_panic]
766
1
fn test_assert_events_eq_fails_if_event_missing() {
767
1
	ExtBuilder::default().build().execute_with(|| {
768
1
		inject_test_events();
769
1

            
770
1
		assert_events_eq!(
771
1
			ParachainStakingEvent::CollatorChosen {
772
1
				round: 2,
773
1
				collator_account: 1,
774
1
				total_exposed_amount: 10,
775
1
			},
776
1
			ParachainStakingEvent::NewRound {
777
1
				starting_block: 10,
778
1
				round: 2,
779
1
				selected_collators_number: 1,
780
1
				total_balance: 10,
781
1
			},
782
1
		);
783
1
	});
784
1
}
785

            
786
#[test]
787
#[should_panic]
788
1
fn test_assert_events_eq_fails_if_event_extra() {
789
1
	ExtBuilder::default().build().execute_with(|| {
790
1
		inject_test_events();
791
1

            
792
1
		assert_events_eq!(
793
1
			ParachainStakingEvent::CollatorChosen {
794
1
				round: 2,
795
1
				collator_account: 1,
796
1
				total_exposed_amount: 10,
797
1
			},
798
1
			ParachainStakingEvent::NewRound {
799
1
				starting_block: 10,
800
1
				round: 2,
801
1
				selected_collators_number: 1,
802
1
				total_balance: 10,
803
1
			},
804
1
			ParachainStakingEvent::Rewarded {
805
1
				account: 1,
806
1
				rewards: 100,
807
1
			},
808
1
			ParachainStakingEvent::Rewarded {
809
1
				account: 1,
810
1
				rewards: 200,
811
1
			},
812
1
		);
813
1
	});
814
1
}
815

            
816
#[test]
817
#[should_panic]
818
1
fn test_assert_events_eq_fails_if_event_wrong_order() {
819
1
	ExtBuilder::default().build().execute_with(|| {
820
1
		inject_test_events();
821
1

            
822
1
		assert_events_eq!(
823
1
			ParachainStakingEvent::Rewarded {
824
1
				account: 1,
825
1
				rewards: 100,
826
1
			},
827
1
			ParachainStakingEvent::CollatorChosen {
828
1
				round: 2,
829
1
				collator_account: 1,
830
1
				total_exposed_amount: 10,
831
1
			},
832
1
			ParachainStakingEvent::NewRound {
833
1
				starting_block: 10,
834
1
				round: 2,
835
1
				selected_collators_number: 1,
836
1
				total_balance: 10,
837
1
			},
838
1
		);
839
1
	});
840
1
}
841

            
842
#[test]
843
#[should_panic]
844
1
fn test_assert_events_eq_fails_if_event_wrong_value() {
845
1
	ExtBuilder::default().build().execute_with(|| {
846
1
		inject_test_events();
847
1

            
848
1
		assert_events_eq!(
849
1
			ParachainStakingEvent::CollatorChosen {
850
1
				round: 2,
851
1
				collator_account: 1,
852
1
				total_exposed_amount: 10,
853
1
			},
854
1
			ParachainStakingEvent::NewRound {
855
1
				starting_block: 10,
856
1
				round: 2,
857
1
				selected_collators_number: 1,
858
1
				total_balance: 10,
859
1
			},
860
1
			ParachainStakingEvent::Rewarded {
861
1
				account: 1,
862
1
				rewards: 50,
863
1
			},
864
1
		);
865
1
	});
866
1
}
867

            
868
#[test]
869
1
fn test_assert_events_eq_passes_if_all_events_present_single() {
870
1
	ExtBuilder::default().build().execute_with(|| {
871
1
		System::deposit_event(ParachainStakingEvent::Rewarded {
872
1
			account: 1,
873
1
			rewards: 100,
874
1
		});
875
1

            
876
1
		assert_events_eq!(ParachainStakingEvent::Rewarded {
877
1
			account: 1,
878
1
			rewards: 100,
879
1
		});
880
1
	});
881
1
}
882

            
883
#[test]
884
1
fn test_assert_events_eq_passes_if_all_events_present_multiple() {
885
1
	ExtBuilder::default().build().execute_with(|| {
886
1
		inject_test_events();
887
1

            
888
1
		assert_events_eq!(
889
1
			ParachainStakingEvent::CollatorChosen {
890
1
				round: 2,
891
1
				collator_account: 1,
892
1
				total_exposed_amount: 10,
893
1
			},
894
1
			ParachainStakingEvent::NewRound {
895
1
				starting_block: 10,
896
1
				round: 2,
897
1
				selected_collators_number: 1,
898
1
				total_balance: 10,
899
1
			},
900
1
			ParachainStakingEvent::Rewarded {
901
1
				account: 1,
902
1
				rewards: 100,
903
1
			},
904
1
		);
905
1
	});
906
1
}
907

            
908
#[test]
909
#[should_panic]
910
1
fn test_assert_events_emitted_fails_if_event_missing() {
911
1
	ExtBuilder::default().build().execute_with(|| {
912
1
		inject_test_events();
913
1

            
914
1
		assert_events_emitted!(ParachainStakingEvent::DelegatorExitScheduled {
915
1
			round: 2,
916
1
			delegator: 3,
917
1
			scheduled_exit: 4,
918
1
		});
919
1
	});
920
1
}
921

            
922
#[test]
923
#[should_panic]
924
1
fn test_assert_events_emitted_fails_if_event_wrong_value() {
925
1
	ExtBuilder::default().build().execute_with(|| {
926
1
		inject_test_events();
927
1

            
928
1
		assert_events_emitted!(ParachainStakingEvent::Rewarded {
929
1
			account: 1,
930
1
			rewards: 50,
931
1
		});
932
1
	});
933
1
}
934

            
935
#[test]
936
1
fn test_assert_events_emitted_passes_if_all_events_present_single() {
937
1
	ExtBuilder::default().build().execute_with(|| {
938
1
		System::deposit_event(ParachainStakingEvent::Rewarded {
939
1
			account: 1,
940
1
			rewards: 100,
941
1
		});
942
1

            
943
1
		assert_events_emitted!(ParachainStakingEvent::Rewarded {
944
1
			account: 1,
945
1
			rewards: 100,
946
1
		});
947
1
	});
948
1
}
949

            
950
#[test]
951
1
fn test_assert_events_emitted_passes_if_all_events_present_multiple() {
952
1
	ExtBuilder::default().build().execute_with(|| {
953
1
		inject_test_events();
954
1

            
955
1
		assert_events_emitted!(
956
1
			ParachainStakingEvent::CollatorChosen {
957
1
				round: 2,
958
1
				collator_account: 1,
959
1
				total_exposed_amount: 10,
960
1
			},
961
1
			ParachainStakingEvent::Rewarded {
962
1
				account: 1,
963
1
				rewards: 100,
964
1
			},
965
1
		);
966
1
	});
967
1
}
968

            
969
#[test]
970
#[should_panic]
971
1
fn test_assert_events_eq_match_fails_if_event_missing() {
972
1
	ExtBuilder::default().build().execute_with(|| {
973
1
		inject_test_events();
974
1

            
975
1
		assert_events_eq_match!(
976
1
			ParachainStakingEvent::CollatorChosen { .. },
977
1
			ParachainStakingEvent::NewRound { .. },
978
1
		);
979
1
	});
980
1
}
981

            
982
#[test]
983
#[should_panic]
984
1
fn test_assert_events_eq_match_fails_if_event_extra() {
985
1
	ExtBuilder::default().build().execute_with(|| {
986
1
		inject_test_events();
987
1

            
988
1
		assert_events_eq_match!(
989
1
			ParachainStakingEvent::CollatorChosen { .. },
990
1
			ParachainStakingEvent::NewRound { .. },
991
1
			ParachainStakingEvent::Rewarded { .. },
992
1
			ParachainStakingEvent::Rewarded { .. },
993
1
		);
994
1
	});
995
1
}
996

            
997
#[test]
998
#[should_panic]
999
1
fn test_assert_events_eq_match_fails_if_event_wrong_order() {
1
	ExtBuilder::default().build().execute_with(|| {
1
		inject_test_events();
1

            
1
		assert_events_eq_match!(
1
			ParachainStakingEvent::Rewarded { .. },
1
			ParachainStakingEvent::CollatorChosen { .. },
1
			ParachainStakingEvent::NewRound { .. },
1
		);
1
	});
1
}
#[test]
#[should_panic]
1
fn test_assert_events_eq_match_fails_if_event_wrong_value() {
1
	ExtBuilder::default().build().execute_with(|| {
1
		inject_test_events();
1

            
1
		assert_events_eq_match!(
1
			ParachainStakingEvent::CollatorChosen { .. },
1
			ParachainStakingEvent::NewRound { .. },
1
			ParachainStakingEvent::Rewarded { rewards: 50, .. },
1
		);
1
	});
1
}
#[test]
1
fn test_assert_events_eq_match_passes_if_all_events_present_single() {
1
	ExtBuilder::default().build().execute_with(|| {
1
		System::deposit_event(ParachainStakingEvent::Rewarded {
1
			account: 1,
1
			rewards: 100,
1
		});
1

            
1
		assert_events_eq_match!(ParachainStakingEvent::Rewarded { account: 1, .. });
1
	});
1
}
#[test]
1
fn test_assert_events_eq_match_passes_if_all_events_present_multiple() {
1
	ExtBuilder::default().build().execute_with(|| {
1
		inject_test_events();
1

            
1
		assert_events_eq_match!(
1
			ParachainStakingEvent::CollatorChosen {
1
				round: 2,
1
				collator_account: 1,
1
				..
1
			},
1
			ParachainStakingEvent::NewRound {
1
				starting_block: 10,
1
				..
1
			},
1
			ParachainStakingEvent::Rewarded {
1
				account: 1,
1
				rewards: 100,
1
			},
1
		);
1
	});
1
}
#[test]
#[should_panic]
1
fn test_assert_events_emitted_match_fails_if_event_missing() {
1
	ExtBuilder::default().build().execute_with(|| {
1
		inject_test_events();
1

            
1
		assert_events_emitted_match!(ParachainStakingEvent::DelegatorExitScheduled {
1
			round: 2,
1
			..
1
		});
1
	});
1
}
#[test]
#[should_panic]
1
fn test_assert_events_emitted_match_fails_if_event_wrong_value() {
1
	ExtBuilder::default().build().execute_with(|| {
1
		inject_test_events();
1

            
1
		assert_events_emitted_match!(ParachainStakingEvent::Rewarded { rewards: 50, .. });
1
	});
1
}
#[test]
1
fn test_assert_events_emitted_match_passes_if_all_events_present_single() {
1
	ExtBuilder::default().build().execute_with(|| {
1
		System::deposit_event(ParachainStakingEvent::Rewarded {
1
			account: 1,
1
			rewards: 100,
1
		});
1

            
1
		assert_events_emitted_match!(ParachainStakingEvent::Rewarded { rewards: 100, .. });
1
	});
1
}
#[test]
1
fn test_assert_events_emitted_match_passes_if_all_events_present_multiple() {
1
	ExtBuilder::default().build().execute_with(|| {
1
		inject_test_events();
1

            
1
		assert_events_emitted_match!(
1
			ParachainStakingEvent::CollatorChosen {
1
				total_exposed_amount: 10,
1
				..
1
			},
1
			ParachainStakingEvent::Rewarded {
1
				account: 1,
1
				rewards: 100,
1
			},
1
		);
1
	});
1
}
16
fn inject_test_events() {
16
	[
16
		ParachainStakingEvent::CollatorChosen {
16
			round: 2,
16
			collator_account: 1,
16
			total_exposed_amount: 10,
16
		},
16
		ParachainStakingEvent::NewRound {
16
			starting_block: 10,
16
			round: 2,
16
			selected_collators_number: 1,
16
			total_balance: 10,
16
		},
16
		ParachainStakingEvent::Rewarded {
16
			account: 1,
16
			rewards: 100,
16
		},
16
	]
16
	.into_iter()
16
	.for_each(System::deposit_event);
16
}