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
};
24
use block_author::BlockAuthor as BlockAuthorMap;
25
use frame_support::{
26
	construct_runtime, parameter_types,
27
	traits::{fungible::InspectFreeze, Everything, Get, OnFinalize, OnInitialize, VariantCountOf},
28
	weights::{constants::RocksDbWeight, Weight},
29
};
30
use frame_system::pallet_prelude::BlockNumberFor;
31
use sp_consensus_slots::Slot;
32
use sp_core::H256;
33
use sp_io;
34
use sp_runtime::BuildStorage;
35
use sp_runtime::{
36
	traits::{BlakeTwo256, IdentityLookup},
37
	Perbill, Percent,
38
};
39

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

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

            
46
// Configure a mock runtime to test the pallet.
47
45634
construct_runtime!(
48
45634
	pub enum Test
49
45634
	{
50
45634
		System: frame_system,
51
45634
		Balances: pallet_balances,
52
45634
		ParachainStaking: pallet_parachain_staking::{Pallet, Call, Storage, Event<T>, FreezeReason},
53
45634
		BlockAuthor: block_author,
54
45634
	}
55
45634
);
56

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

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

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

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

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

            
154
impl Config for Test {
155
	type RuntimeEvent = RuntimeEvent;
156
	type Currency = Balances;
157
	type RuntimeFreezeReason = RuntimeFreezeReason;
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
251
	fn default() -> ExtBuilder {
199
251
		ExtBuilder {
200
251
			balances: vec![],
201
251
			delegations: vec![],
202
251
			collators: vec![],
203
251
			inflation: InflationInfo {
204
251
				expect: Range {
205
251
					min: 700,
206
251
					ideal: 700,
207
251
					max: 700,
208
251
				},
209
251
				// not used
210
251
				annual: Range {
211
251
					min: Perbill::from_percent(50),
212
251
					ideal: Perbill::from_percent(50),
213
251
					max: Perbill::from_percent(50),
214
251
				},
215
251
				// unrealistically high parameterization, only for testing
216
251
				round: Range {
217
251
					min: Perbill::from_percent(5),
218
251
					ideal: Perbill::from_percent(5),
219
251
					max: Perbill::from_percent(5),
220
251
				},
221
251
			},
222
251
		}
223
251
	}
224
}
225

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

            
232
177
	pub(crate) fn with_candidates(mut self, collators: Vec<(AccountId, Balance)>) -> Self {
233
177
		self.collators = collators;
234
177
		self
235
177
	}
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
251
	pub(crate) fn build(self) -> sp_io::TestExternalities {
263
251
		let mut t = frame_system::GenesisConfig::<Test>::default()
264
251
			.build_storage()
265
251
			.expect("Frame system builds valid default genesis config");
266
251

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
593
/// fn to query the freeze amount for a specific reason
594
27
pub(crate) fn query_freeze_amount(account_id: u64, reason: &RuntimeFreezeReason) -> Balance {
595
27
	Balances::balance_frozen(reason, &account_id)
596
27
}
597

            
598
#[test]
599
1
fn geneses() {
600
1
	ExtBuilder::default()
601
1
		.with_balances(vec![
602
1
			(1, 1000),
603
1
			(2, 300),
604
1
			(3, 100),
605
1
			(4, 100),
606
1
			(5, 100),
607
1
			(6, 100),
608
1
			(7, 100),
609
1
			(8, 9),
610
1
			(9, 4),
611
1
		])
612
1
		.with_candidates(vec![(1, 500), (2, 200)])
613
1
		.with_delegations(vec![(3, 1, 100), (4, 1, 100), (5, 2, 100), (6, 2, 100)])
614
1
		.build()
615
1
		.execute_with(|| {
616
1
			assert!(System::events().is_empty());
617
			// collators
618
1
			assert_eq!(
619
1
				ParachainStaking::get_collator_stakable_free_balance(&1),
620
1
				500
621
1
			);
622
1
			let collator_freeze_reason: RuntimeFreezeReason =
623
1
				pallet_parachain_staking::pallet::FreezeReason::StakingCollator.into();
624
1
			let delegator_freeze_reason: RuntimeFreezeReason =
625
1
				pallet_parachain_staking::pallet::FreezeReason::StakingDelegator.into();
626
1
			assert_eq!(query_freeze_amount(1, &collator_freeze_reason), 500);
627
1
			assert!(ParachainStaking::is_candidate(&1));
628
1
			assert_eq!(query_freeze_amount(2, &collator_freeze_reason), 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_freeze_amount(x, &delegator_freeze_reason), 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_freeze_amount(7, &delegator_freeze_reason), 0);
646
1
			assert_eq!(ParachainStaking::get_delegator_stakable_balance(&7), 100);
647
1
			assert_eq!(query_freeze_amount(8, &delegator_freeze_reason), 0);
648
1
			assert_eq!(ParachainStaking::get_delegator_stakable_balance(&8), 9);
649
1
			assert_eq!(query_freeze_amount(9, &delegator_freeze_reason), 0);
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
			// Define freeze reasons once
683
1
			let collator_freeze_reason: RuntimeFreezeReason =
684
1
				pallet_parachain_staking::pallet::FreezeReason::StakingCollator.into();
685
1
			let delegator_freeze_reason: RuntimeFreezeReason =
686
1
				pallet_parachain_staking::pallet::FreezeReason::StakingDelegator.into();
687
1
			assert!(System::events().is_empty());
688
			// collators
689
5
			for x in 1..5 {
690
4
				assert!(ParachainStaking::is_candidate(&x));
691
4
				assert_eq!(query_freeze_amount(x, &collator_freeze_reason), 20);
692
4
				assert_eq!(ParachainStaking::get_collator_stakable_free_balance(&x), 80);
693
			}
694
1
			assert!(ParachainStaking::is_candidate(&5));
695
1
			assert_eq!(query_freeze_amount(5, &collator_freeze_reason), 10);
696
1
			assert_eq!(ParachainStaking::get_collator_stakable_free_balance(&5), 90);
697
			// delegators
698
6
			for x in 6..11 {
699
5
				assert!(ParachainStaking::is_delegator(&x));
700
5
				assert_eq!(query_freeze_amount(x, &delegator_freeze_reason), 10);
701
5
				assert_eq!(ParachainStaking::get_delegator_stakable_balance(&x), 90);
702
			}
703
1
		});
704
1
}
705

            
706
#[frame_support::pallet]
707
pub mod block_author {
708
	use super::*;
709
	use frame_support::pallet_prelude::*;
710
	use frame_support::traits::Get;
711

            
712
	#[pallet::config]
713
	pub trait Config: frame_system::Config {}
714

            
715
3
	#[pallet::pallet]
716
	pub struct Pallet<T>(_);
717

            
718
2310
	#[pallet::storage]
719
	#[pallet::getter(fn block_author)]
720
	pub(super) type BlockAuthor<T> = StorageValue<_, AccountId, ValueQuery>;
721

            
722
	impl<T: Config> Get<AccountId> for Pallet<T> {
723
1149
		fn get() -> AccountId {
724
1149
			<BlockAuthor<T>>::get()
725
1149
		}
726
	}
727
}
728

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

            
735
1
		let num_blocks = roll_to_round_begin(1);
736
1
		assert_eq!(System::block_number(), 1); // no-op, we're already on this round
737
1
		assert_eq!(num_blocks, 0);
738

            
739
1
		let num_blocks = roll_to_round_begin(2);
740
1
		assert_eq!(System::block_number(), 5);
741
1
		assert_eq!(num_blocks, 4);
742

            
743
1
		let num_blocks = roll_to_round_begin(3);
744
1
		assert_eq!(System::block_number(), 10);
745
1
		assert_eq!(num_blocks, 5);
746
1
	});
747
1
}
748

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

            
755
1
		let num_blocks = roll_to_round_end(1);
756
1
		assert_eq!(System::block_number(), 4);
757
1
		assert_eq!(num_blocks, 3);
758

            
759
1
		let num_blocks = roll_to_round_end(2);
760
1
		assert_eq!(System::block_number(), 9);
761
1
		assert_eq!(num_blocks, 5);
762

            
763
1
		let num_blocks = roll_to_round_end(3);
764
1
		assert_eq!(System::block_number(), 14);
765
1
		assert_eq!(num_blocks, 5);
766
1
	});
767
1
}
768

            
769
#[test]
770
#[should_panic]
771
1
fn test_assert_events_eq_fails_if_event_missing() {
772
1
	ExtBuilder::default().build().execute_with(|| {
773
1
		inject_test_events();
774
1

            
775
1
		assert_events_eq!(
776
1
			ParachainStakingEvent::CollatorChosen {
777
1
				round: 2,
778
1
				collator_account: 1,
779
1
				total_exposed_amount: 10,
780
1
			},
781
1
			ParachainStakingEvent::NewRound {
782
1
				starting_block: 10,
783
1
				round: 2,
784
1
				selected_collators_number: 1,
785
1
				total_balance: 10,
786
1
			},
787
1
		);
788
1
	});
789
1
}
790

            
791
#[test]
792
#[should_panic]
793
1
fn test_assert_events_eq_fails_if_event_extra() {
794
1
	ExtBuilder::default().build().execute_with(|| {
795
1
		inject_test_events();
796
1

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

            
821
#[test]
822
#[should_panic]
823
1
fn test_assert_events_eq_fails_if_event_wrong_order() {
824
1
	ExtBuilder::default().build().execute_with(|| {
825
1
		inject_test_events();
826
1

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

            
847
#[test]
848
#[should_panic]
849
1
fn test_assert_events_eq_fails_if_event_wrong_value() {
850
1
	ExtBuilder::default().build().execute_with(|| {
851
1
		inject_test_events();
852
1

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

            
873
#[test]
874
1
fn test_assert_events_eq_passes_if_all_events_present_single() {
875
1
	ExtBuilder::default().build().execute_with(|| {
876
1
		System::deposit_event(ParachainStakingEvent::Rewarded {
877
1
			account: 1,
878
1
			rewards: 100,
879
1
		});
880
1

            
881
1
		assert_events_eq!(ParachainStakingEvent::Rewarded {
882
1
			account: 1,
883
1
			rewards: 100,
884
1
		});
885
1
	});
886
1
}
887

            
888
#[test]
889
1
fn test_assert_events_eq_passes_if_all_events_present_multiple() {
890
1
	ExtBuilder::default().build().execute_with(|| {
891
1
		inject_test_events();
892
1

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

            
913
#[test]
914
#[should_panic]
915
1
fn test_assert_events_emitted_fails_if_event_missing() {
916
1
	ExtBuilder::default().build().execute_with(|| {
917
1
		inject_test_events();
918
1

            
919
1
		assert_events_emitted!(ParachainStakingEvent::DelegatorExitScheduled {
920
1
			round: 2,
921
1
			delegator: 3,
922
1
			scheduled_exit: 4,
923
1
		});
924
1
	});
925
1
}
926

            
927
#[test]
928
#[should_panic]
929
1
fn test_assert_events_emitted_fails_if_event_wrong_value() {
930
1
	ExtBuilder::default().build().execute_with(|| {
931
1
		inject_test_events();
932
1

            
933
1
		assert_events_emitted!(ParachainStakingEvent::Rewarded {
934
1
			account: 1,
935
1
			rewards: 50,
936
1
		});
937
1
	});
938
1
}
939

            
940
#[test]
941
1
fn test_assert_events_emitted_passes_if_all_events_present_single() {
942
1
	ExtBuilder::default().build().execute_with(|| {
943
1
		System::deposit_event(ParachainStakingEvent::Rewarded {
944
1
			account: 1,
945
1
			rewards: 100,
946
1
		});
947
1

            
948
1
		assert_events_emitted!(ParachainStakingEvent::Rewarded {
949
1
			account: 1,
950
1
			rewards: 100,
951
1
		});
952
1
	});
953
1
}
954

            
955
#[test]
956
1
fn test_assert_events_emitted_passes_if_all_events_present_multiple() {
957
1
	ExtBuilder::default().build().execute_with(|| {
958
1
		inject_test_events();
959
1

            
960
1
		assert_events_emitted!(
961
1
			ParachainStakingEvent::CollatorChosen {
962
1
				round: 2,
963
1
				collator_account: 1,
964
1
				total_exposed_amount: 10,
965
1
			},
966
1
			ParachainStakingEvent::Rewarded {
967
1
				account: 1,
968
1
				rewards: 100,
969
1
			},
970
1
		);
971
1
	});
972
1
}
973

            
974
#[test]
975
#[should_panic]
976
1
fn test_assert_events_eq_match_fails_if_event_missing() {
977
1
	ExtBuilder::default().build().execute_with(|| {
978
1
		inject_test_events();
979
1

            
980
1
		assert_events_eq_match!(
981
1
			ParachainStakingEvent::CollatorChosen { .. },
982
1
			ParachainStakingEvent::NewRound { .. },
983
1
		);
984
1
	});
985
1
}
986

            
987
#[test]
988
#[should_panic]
989
1
fn test_assert_events_eq_match_fails_if_event_extra() {
990
1
	ExtBuilder::default().build().execute_with(|| {
991
1
		inject_test_events();
992
1

            
993
1
		assert_events_eq_match!(
994
1
			ParachainStakingEvent::CollatorChosen { .. },
995
1
			ParachainStakingEvent::NewRound { .. },
996
1
			ParachainStakingEvent::Rewarded { .. },
997
1
			ParachainStakingEvent::Rewarded { .. },
998
1
		);
999
1
	});
1
}
#[test]
#[should_panic]
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
}