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
construct_runtime!(
48
	pub enum Test
49
	{
50
		System: frame_system,
51
		Balances: pallet_balances,
52
		ParachainStaking: pallet_parachain_staking::{Pallet, Call, Storage, Event<T>, FreezeReason},
53
		BlockAuthor: block_author,
54
	}
55
);
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
232
	fn get() -> Slot {
145
232
		let block_number: u64 = System::block_number().into();
146
232
		Slot::from(block_number)
147
232
	}
148
}
149

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

            
155
impl Config for Test {
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 MaxScheduledRequestsPerDelegator = MaxScheduledRequestsPerDelegator;
172
	type MinCandidateStk = MinCandidateStk;
173
	type MinDelegation = MinDelegation;
174
	type BlockAuthor = BlockAuthor;
175
	type OnCollatorPayout = ();
176
	type PayoutCollatorReward = ();
177
	type OnInactiveCollator = ();
178
	type OnNewRound = ();
179
	type SlotProvider = StakingRoundSlotProvider;
180
	type WeightInfo = ();
181
	type MaxCandidates = MaxCandidates;
182
	type SlotDuration = frame_support::traits::ConstU64<6_000>;
183
	type BlockTime = frame_support::traits::ConstU64<6_000>;
184
	type LinearInflationThreshold = LinearInflationThreshold;
185
}
186

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
994
1
		assert_events_eq_match!(
995
			ParachainStakingEvent::CollatorChosen { .. },
996
			ParachainStakingEvent::NewRound { .. },
997
			ParachainStakingEvent::Rewarded { .. },
998
			ParachainStakingEvent::Rewarded { .. },
999
		);
	});
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
		assert_events_eq_match!(
			ParachainStakingEvent::Rewarded { .. },
			ParachainStakingEvent::CollatorChosen { .. },
			ParachainStakingEvent::NewRound { .. },
		);
	});
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
		assert_events_eq_match!(
			ParachainStakingEvent::CollatorChosen { .. },
			ParachainStakingEvent::NewRound { .. },
			ParachainStakingEvent::Rewarded { rewards: 50, .. },
		);
	});
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
		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
		assert_events_eq_match!(
			ParachainStakingEvent::CollatorChosen {
				round: 2,
				collator_account: 1,
				..
			},
			ParachainStakingEvent::NewRound {
				starting_block: 10,
				..
			},
			ParachainStakingEvent::Rewarded {
				account: 1,
				rewards: 100,
			},
		);
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
		assert_events_emitted_match!(ParachainStakingEvent::DelegatorExitScheduled {
			round: 2,
			..
		});
	});
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
		assert_events_emitted_match!(ParachainStakingEvent::Rewarded { rewards: 50, .. });
	});
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
		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
		assert_events_emitted_match!(
			ParachainStakingEvent::CollatorChosen {
				total_exposed_amount: 10,
				..
			},
			ParachainStakingEvent::Rewarded {
				account: 1,
				rewards: 100,
			},
		);
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
}