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
224771
construct_runtime!(
49
44628
	pub enum Test
50
44628
	{
51
44628
		System: frame_system,
52
44628
		Balances: pallet_balances,
53
44628
		ParachainStaking: pallet_parachain_staking,
54
44628
		BlockAuthor: block_author,
55
44628
	}
56
234725
);
57

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
702
	#[pallet::config]
703
	pub trait Config: frame_system::Config {}
704

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
998
1
		assert_events_eq_match!(
999
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
}