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
223965
construct_runtime!(
49
	pub enum Test
50
	{
51
		System: frame_system,
52
		Balances: pallet_balances,
53
		ParachainStaking: pallet_parachain_staking,
54
		BlockAuthor: block_author,
55
	}
56
413775
);
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
}
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 = ();
111
	type MaxFreezes = ();
112
	type RuntimeFreezeReason = ();
113
}
114
impl block_author::Config for Test {}
115
const GENESIS_BLOCKS_PER_ROUND: BlockNumber = 5;
116
const GENESIS_COLLATOR_COMMISSION: Perbill = Perbill::from_percent(20);
117
const GENESIS_PARACHAIN_BOND_RESERVE_PERCENT: Percent = Percent::from_percent(30);
118
const GENESIS_NUM_SELECTED_CANDIDATES: u32 = 5;
119

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

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

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

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

            
179
pub(crate) struct ExtBuilder {
180
	// endowed accounts with balances
181
	balances: Vec<(AccountId, Balance)>,
182
	// [collator, amount]
183
	collators: Vec<(AccountId, Balance)>,
184
	// [delegator, collator, delegation_amount, auto_compound_percent]
185
	delegations: Vec<(AccountId, AccountId, Balance, Percent)>,
186
	// inflation config
187
	inflation: InflationInfo<Balance>,
188
}
189

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

            
219
impl ExtBuilder {
220
194
	pub(crate) fn with_balances(mut self, balances: Vec<(AccountId, Balance)>) -> Self {
221
194
		self.balances = balances;
222
194
		self
223
194
	}
224

            
225
181
	pub(crate) fn with_candidates(mut self, collators: Vec<(AccountId, Balance)>) -> Self {
226
181
		self.collators = collators;
227
181
		self
228
181
	}
229

            
230
103
	pub(crate) fn with_delegations(
231
103
		mut self,
232
103
		delegations: Vec<(AccountId, AccountId, Balance)>,
233
103
	) -> Self {
234
103
		self.delegations = delegations
235
103
			.into_iter()
236
8946
			.map(|d| (d.0, d.1, d.2, Percent::zero()))
237
103
			.collect();
238
103
		self
239
103
	}
240

            
241
2
	pub(crate) fn with_auto_compounding_delegations(
242
2
		mut self,
243
2
		delegations: Vec<(AccountId, AccountId, Balance, Percent)>,
244
2
	) -> Self {
245
2
		self.delegations = delegations;
246
2
		self
247
2
	}
248

            
249
	#[allow(dead_code)]
250
	pub(crate) fn with_inflation(mut self, inflation: InflationInfo<Balance>) -> Self {
251
		self.inflation = inflation;
252
		self
253
	}
254

            
255
260
	pub(crate) fn build(self) -> sp_io::TestExternalities {
256
260
		let mut t = frame_system::GenesisConfig::<Test>::default()
257
260
			.build_storage()
258
260
			.expect("Frame system builds valid default genesis config");
259
260

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

            
277
260
		let mut ext = sp_io::TestExternalities::new(t);
278
260
		ext.execute_with(|| System::set_block_number(1));
279
260
		ext
280
260
	}
281
}
282

            
283
/// Rolls forward one block. Returns the new block number.
284
1149
fn roll_one_block() -> BlockNumber {
285
1149
	ParachainStaking::on_finalize(System::block_number());
286
1149
	Balances::on_finalize(System::block_number());
287
1149
	System::on_finalize(System::block_number());
288
1149
	System::set_block_number(System::block_number() + 1);
289
1149
	System::reset_events();
290
1149
	System::on_initialize(System::block_number());
291
1149
	Balances::on_initialize(System::block_number());
292
1149
	ParachainStaking::on_initialize(System::block_number());
293
1149
	System::block_number()
294
1149
}
295

            
296
/// Rolls to the desired block. Returns the number of blocks played.
297
132
pub(crate) fn roll_to(n: BlockNumber) -> BlockNumber {
298
132
	let mut num_blocks = 0;
299
132
	let mut block = System::block_number();
300
1172
	while block < n {
301
1040
		block = roll_one_block();
302
1040
		num_blocks += 1;
303
1040
	}
304
132
	num_blocks
305
132
}
306

            
307
/// Rolls desired number of blocks. Returns the final block.
308
63
pub(crate) fn roll_blocks(num_blocks: u32) -> BlockNumber {
309
63
	let mut block = System::block_number();
310
109
	for _ in 0..num_blocks {
311
109
		block = roll_one_block();
312
109
	}
313
63
	block
314
63
}
315

            
316
/// Rolls block-by-block to the beginning of the specified round.
317
/// This will complete the block in which the round change occurs.
318
/// Returns the number of blocks played.
319
73
pub(crate) fn roll_to_round_begin(round: BlockNumber) -> BlockNumber {
320
73
	let r = ParachainStaking::round();
321
73

            
322
73
	// Return 0 if target round has already passed
323
73
	if round < r.current + 1 {
324
3
		return 0;
325
70
	}
326
70

            
327
70
	// Calculate target block by adding round length for each round difference
328
70
	let target = r.first + (round - r.current) * r.length;
329
70
	roll_to(target)
330
73
}
331

            
332
/// Rolls block-by-block to the end of the specified round.
333
/// The block following will be the one in which the specified round change occurs.
334
12
pub(crate) fn roll_to_round_end(round: BlockNumber) -> BlockNumber {
335
12
	let r = ParachainStaking::round();
336
12

            
337
12
	// Return 0 if target round has already passed
338
12
	if round < r.current {
339
1
		return 0;
340
11
	}
341
11

            
342
11
	// Calculate target block by adding round length for each round difference
343
11
	let target = r.first + (round - r.current + 1) * r.length - 1;
344
11
	roll_to(target)
345
12
}
346

            
347
38
pub(crate) fn inflation_configs(
348
38
	pbr: AccountId,
349
38
	pbr_percent: u8,
350
38
	treasury: AccountId,
351
38
	treasury_percent: u8,
352
38
) -> InflationDistributionConfig<AccountId> {
353
38
	[
354
38
		InflationDistributionAccount {
355
38
			account: pbr,
356
38
			percent: Percent::from_percent(pbr_percent),
357
38
		},
358
38
		InflationDistributionAccount {
359
38
			account: treasury,
360
38
			percent: Percent::from_percent(treasury_percent),
361
38
		},
362
38
	]
363
38
	.into()
364
38
}
365

            
366
203
pub(crate) fn events() -> Vec<pallet::Event<Test>> {
367
203
	System::events()
368
203
		.into_iter()
369
799
		.map(|r| r.event)
370
799
		.filter_map(|e| {
371
799
			if let RuntimeEvent::ParachainStaking(inner) = e {
372
552
				Some(inner)
373
			} else {
374
247
				None
375
			}
376
799
		})
377
203
		.collect::<Vec<_>>()
378
203
}
379

            
380
/// Asserts that some events were never emitted.
381
///
382
/// # Example
383
///
384
/// ```
385
/// assert_no_events!();
386
/// ```
387
#[macro_export]
388
macro_rules! assert_no_events {
389
	() => {
390
		similar_asserts::assert_eq!(Vec::<Event<Test>>::new(), crate::mock::events())
391
	};
392
}
393

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

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

            
445
/// Asserts that some events were never emitted.
446
///
447
/// # Example
448
///
449
/// ```
450
/// assert_events_not_emitted!(
451
///		Foo { x: 1, y: 2 },
452
///		Bar { value: "test" },
453
/// );
454
/// ```
455
#[macro_export]
456
macro_rules! assert_events_not_emitted {
457
	($event:expr) => {
458
		[$event].into_iter().for_each(|e| assert!(
459
			crate::mock::events().into_iter().find(|x| x != &e).is_some(),
460
			"Event {:?} was unexpectedly found in events: \n{:#?}",
461
			e,
462
			crate::mock::events()
463
		));
464
	};
465
	($($events:expr,)+) => {
466
		[$($events,)+].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
}
474

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

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

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

            
574
// Same storage changes as ParachainStaking::on_finalize
575
65
pub(crate) fn set_author(round: BlockNumber, acc: u64, pts: u32) {
576
65
	<Points<Test>>::mutate(round, |p| *p += pts);
577
65
	<AwardedPts<Test>>::mutate(round, acc, |p| *p += pts);
578
65
}
579

            
580
// Allows to change the block author (default is always 0)
581
6
pub(crate) fn set_block_author(acc: u64) {
582
6
	<BlockAuthorMap<Test>>::set(acc);
583
6
}
584

            
585
/// fn to query the lock amount
586
25
pub(crate) fn query_lock_amount(account_id: u64, id: LockIdentifier) -> Option<Balance> {
587
25
	for lock in Balances::locks(&account_id) {
588
19
		if lock.id == id {
589
19
			return Some(lock.amount);
590
		}
591
	}
592
6
	None
593
25
}
594

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

            
694
#[frame_support::pallet]
695
pub mod block_author {
696
	use super::*;
697
	use frame_support::pallet_prelude::*;
698
	use frame_support::traits::Get;
699

            
700
	#[pallet::config]
701
	pub trait Config: frame_system::Config {}
702

            
703
1
	#[pallet::pallet]
704
	pub struct Pallet<T>(_);
705

            
706
2310
	#[pallet::storage]
707
	#[pallet::getter(fn block_author)]
708
	pub(super) type BlockAuthor<T> = StorageValue<_, AccountId, ValueQuery>;
709

            
710
	impl<T: Config> Get<AccountId> for Pallet<T> {
711
1149
		fn get() -> AccountId {
712
1149
			<BlockAuthor<T>>::get()
713
1149
		}
714
	}
715
}
716

            
717
#[test]
718
1
fn roll_to_round_begin_works() {
719
1
	ExtBuilder::default().build().execute_with(|| {
720
1
		// these tests assume blocks-per-round of 5, as established by GENESIS_BLOCKS_PER_ROUND
721
1
		assert_eq!(System::block_number(), 1); // we start on block 1
722

            
723
1
		let num_blocks = roll_to_round_begin(1);
724
1
		assert_eq!(System::block_number(), 1); // no-op, we're already on this round
725
1
		assert_eq!(num_blocks, 0);
726

            
727
1
		let num_blocks = roll_to_round_begin(2);
728
1
		assert_eq!(System::block_number(), 5);
729
1
		assert_eq!(num_blocks, 4);
730

            
731
1
		let num_blocks = roll_to_round_begin(3);
732
1
		assert_eq!(System::block_number(), 10);
733
1
		assert_eq!(num_blocks, 5);
734
1
	});
735
1
}
736

            
737
#[test]
738
1
fn roll_to_round_end_works() {
739
1
	ExtBuilder::default().build().execute_with(|| {
740
1
		// these tests assume blocks-per-round of 5, as established by GENESIS_BLOCKS_PER_ROUND
741
1
		assert_eq!(System::block_number(), 1); // we start on block 1
742

            
743
1
		let num_blocks = roll_to_round_end(1);
744
1
		assert_eq!(System::block_number(), 4);
745
1
		assert_eq!(num_blocks, 3);
746

            
747
1
		let num_blocks = roll_to_round_end(2);
748
1
		assert_eq!(System::block_number(), 9);
749
1
		assert_eq!(num_blocks, 5);
750

            
751
1
		let num_blocks = roll_to_round_end(3);
752
1
		assert_eq!(System::block_number(), 14);
753
1
		assert_eq!(num_blocks, 5);
754
1
	});
755
1
}
756

            
757
#[test]
758
#[should_panic]
759
1
fn test_assert_events_eq_fails_if_event_missing() {
760
1
	ExtBuilder::default().build().execute_with(|| {
761
1
		inject_test_events();
762
1

            
763
1
		assert_events_eq!(
764
1
			ParachainStakingEvent::CollatorChosen {
765
1
				round: 2,
766
1
				collator_account: 1,
767
1
				total_exposed_amount: 10,
768
1
			},
769
1
			ParachainStakingEvent::NewRound {
770
1
				starting_block: 10,
771
1
				round: 2,
772
1
				selected_collators_number: 1,
773
1
				total_balance: 10,
774
1
			},
775
1
		);
776
1
	});
777
1
}
778

            
779
#[test]
780
#[should_panic]
781
1
fn test_assert_events_eq_fails_if_event_extra() {
782
1
	ExtBuilder::default().build().execute_with(|| {
783
1
		inject_test_events();
784
1

            
785
1
		assert_events_eq!(
786
1
			ParachainStakingEvent::CollatorChosen {
787
1
				round: 2,
788
1
				collator_account: 1,
789
1
				total_exposed_amount: 10,
790
1
			},
791
1
			ParachainStakingEvent::NewRound {
792
1
				starting_block: 10,
793
1
				round: 2,
794
1
				selected_collators_number: 1,
795
1
				total_balance: 10,
796
1
			},
797
1
			ParachainStakingEvent::Rewarded {
798
1
				account: 1,
799
1
				rewards: 100,
800
1
			},
801
1
			ParachainStakingEvent::Rewarded {
802
1
				account: 1,
803
1
				rewards: 200,
804
1
			},
805
1
		);
806
1
	});
807
1
}
808

            
809
#[test]
810
#[should_panic]
811
1
fn test_assert_events_eq_fails_if_event_wrong_order() {
812
1
	ExtBuilder::default().build().execute_with(|| {
813
1
		inject_test_events();
814
1

            
815
1
		assert_events_eq!(
816
1
			ParachainStakingEvent::Rewarded {
817
1
				account: 1,
818
1
				rewards: 100,
819
1
			},
820
1
			ParachainStakingEvent::CollatorChosen {
821
1
				round: 2,
822
1
				collator_account: 1,
823
1
				total_exposed_amount: 10,
824
1
			},
825
1
			ParachainStakingEvent::NewRound {
826
1
				starting_block: 10,
827
1
				round: 2,
828
1
				selected_collators_number: 1,
829
1
				total_balance: 10,
830
1
			},
831
1
		);
832
1
	});
833
1
}
834

            
835
#[test]
836
#[should_panic]
837
1
fn test_assert_events_eq_fails_if_event_wrong_value() {
838
1
	ExtBuilder::default().build().execute_with(|| {
839
1
		inject_test_events();
840
1

            
841
1
		assert_events_eq!(
842
1
			ParachainStakingEvent::CollatorChosen {
843
1
				round: 2,
844
1
				collator_account: 1,
845
1
				total_exposed_amount: 10,
846
1
			},
847
1
			ParachainStakingEvent::NewRound {
848
1
				starting_block: 10,
849
1
				round: 2,
850
1
				selected_collators_number: 1,
851
1
				total_balance: 10,
852
1
			},
853
1
			ParachainStakingEvent::Rewarded {
854
1
				account: 1,
855
1
				rewards: 50,
856
1
			},
857
1
		);
858
1
	});
859
1
}
860

            
861
#[test]
862
1
fn test_assert_events_eq_passes_if_all_events_present_single() {
863
1
	ExtBuilder::default().build().execute_with(|| {
864
1
		System::deposit_event(ParachainStakingEvent::Rewarded {
865
1
			account: 1,
866
1
			rewards: 100,
867
1
		});
868
1

            
869
1
		assert_events_eq!(ParachainStakingEvent::Rewarded {
870
1
			account: 1,
871
1
			rewards: 100,
872
1
		});
873
1
	});
874
1
}
875

            
876
#[test]
877
1
fn test_assert_events_eq_passes_if_all_events_present_multiple() {
878
1
	ExtBuilder::default().build().execute_with(|| {
879
1
		inject_test_events();
880
1

            
881
1
		assert_events_eq!(
882
1
			ParachainStakingEvent::CollatorChosen {
883
1
				round: 2,
884
1
				collator_account: 1,
885
1
				total_exposed_amount: 10,
886
1
			},
887
1
			ParachainStakingEvent::NewRound {
888
1
				starting_block: 10,
889
1
				round: 2,
890
1
				selected_collators_number: 1,
891
1
				total_balance: 10,
892
1
			},
893
1
			ParachainStakingEvent::Rewarded {
894
1
				account: 1,
895
1
				rewards: 100,
896
1
			},
897
1
		);
898
1
	});
899
1
}
900

            
901
#[test]
902
#[should_panic]
903
1
fn test_assert_events_emitted_fails_if_event_missing() {
904
1
	ExtBuilder::default().build().execute_with(|| {
905
1
		inject_test_events();
906
1

            
907
3
		assert_events_emitted!(ParachainStakingEvent::DelegatorExitScheduled {
908
3
			round: 2,
909
3
			delegator: 3,
910
3
			scheduled_exit: 4,
911
3
		});
912
1
	});
913
1
}
914

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

            
921
3
		assert_events_emitted!(ParachainStakingEvent::Rewarded {
922
3
			account: 1,
923
3
			rewards: 50,
924
3
		});
925
1
	});
926
1
}
927

            
928
#[test]
929
1
fn test_assert_events_emitted_passes_if_all_events_present_single() {
930
1
	ExtBuilder::default().build().execute_with(|| {
931
1
		System::deposit_event(ParachainStakingEvent::Rewarded {
932
1
			account: 1,
933
1
			rewards: 100,
934
1
		});
935
1

            
936
1
		assert_events_emitted!(ParachainStakingEvent::Rewarded {
937
1
			account: 1,
938
1
			rewards: 100,
939
1
		});
940
1
	});
941
1
}
942

            
943
#[test]
944
1
fn test_assert_events_emitted_passes_if_all_events_present_multiple() {
945
1
	ExtBuilder::default().build().execute_with(|| {
946
1
		inject_test_events();
947
1

            
948
4
		assert_events_emitted!(
949
4
			ParachainStakingEvent::CollatorChosen {
950
4
				round: 2,
951
4
				collator_account: 1,
952
4
				total_exposed_amount: 10,
953
4
			},
954
4
			ParachainStakingEvent::Rewarded {
955
4
				account: 1,
956
4
				rewards: 100,
957
4
			},
958
4
		);
959
1
	});
960
1
}
961

            
962
#[test]
963
#[should_panic]
964
1
fn test_assert_events_eq_match_fails_if_event_missing() {
965
1
	ExtBuilder::default().build().execute_with(|| {
966
1
		inject_test_events();
967
1

            
968
1
		assert_events_eq_match!(
969
1
			ParachainStakingEvent::CollatorChosen { .. },
970
1
			ParachainStakingEvent::NewRound { .. },
971
1
		);
972
1
	});
973
1
}
974

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

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

            
990
#[test]
991
#[should_panic]
992
1
fn test_assert_events_eq_match_fails_if_event_wrong_order() {
993
1
	ExtBuilder::default().build().execute_with(|| {
994
1
		inject_test_events();
995
1

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