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
//! # Staking Pallet Unit Tests
18
//! The unit tests are organized by the call they test. The order matches the order
19
//! of the calls in the `lib.rs`.
20
//! 1. Root
21
//! 2. Monetary Governance
22
//! 3. Public (Collator, Nominator)
23
//! 4. Miscellaneous Property-Based Tests
24

            
25
use crate::auto_compound::{AutoCompoundConfig, AutoCompoundDelegations};
26
use crate::delegation_requests::{CancelledScheduledRequest, DelegationAction, ScheduledRequest};
27
use crate::mock::{
28
	inflation_configs, roll_blocks, roll_to, roll_to_round_begin, roll_to_round_end, set_author,
29
	set_block_author, AccountId, Balances, BlockNumber, ExtBuilder, ParachainStaking, RuntimeEvent,
30
	RuntimeOrigin, Test, POINTS_PER_BLOCK, POINTS_PER_ROUND,
31
};
32
use crate::{
33
	assert_events_emitted, assert_events_emitted_match, assert_events_eq, assert_no_events,
34
	AtStake, Bond, CollatorStatus, DelegationScheduledRequests, DelegatorAdded,
35
	EnableMarkingOffline, Error, Event, InflationDistributionInfo, Range, WasInactive,
36
	DELEGATOR_LOCK_ID,
37
};
38
use frame_support::traits::{Currency, ExistenceRequirement, WithdrawReasons};
39
use frame_support::{assert_noop, assert_ok, BoundedVec};
40
use pallet_balances::{Event as BalancesEvent, PositiveImbalance};
41
use sp_runtime::{traits::Zero, DispatchError, ModuleError, Perbill, Percent};
42
// ~~ ROOT ~~
43

            
44
#[test]
45
1
fn invalid_root_origin_fails() {
46
1
	ExtBuilder::default().build().execute_with(|| {
47
1
		assert_noop!(
48
1
			ParachainStaking::set_total_selected(RuntimeOrigin::signed(45), 6u32),
49
1
			sp_runtime::DispatchError::BadOrigin
50
1
		);
51
1
		assert_noop!(
52
1
			ParachainStaking::set_collator_commission(
53
1
				RuntimeOrigin::signed(45),
54
1
				Perbill::from_percent(5)
55
1
			),
56
1
			sp_runtime::DispatchError::BadOrigin
57
1
		);
58
1
		assert_noop!(
59
1
			ParachainStaking::set_blocks_per_round(RuntimeOrigin::signed(45), 3u32),
60
1
			sp_runtime::DispatchError::BadOrigin
61
1
		);
62
1
	});
63
1
}
64

            
65
// SET TOTAL SELECTED
66

            
67
#[test]
68
1
fn set_total_selected_event_emits_correctly() {
69
1
	ExtBuilder::default().build().execute_with(|| {
70
1
		// before we can bump total_selected we must bump the blocks per round
71
1
		assert_ok!(ParachainStaking::set_blocks_per_round(
72
1
			RuntimeOrigin::root(),
73
1
			7u32
74
1
		));
75
1
		roll_blocks(1);
76
1
		assert_ok!(ParachainStaking::set_total_selected(
77
1
			RuntimeOrigin::root(),
78
1
			6u32
79
1
		));
80
1
		assert_events_eq!(Event::TotalSelectedSet {
81
1
			old: 5u32,
82
1
			new: 6u32
83
1
		});
84
1
	});
85
1
}
86

            
87
#[test]
88
1
fn set_total_selected_fails_if_above_blocks_per_round() {
89
1
	ExtBuilder::default().build().execute_with(|| {
90
1
		assert_eq!(ParachainStaking::round().length, 5); // test relies on this
91
1
		assert_noop!(
92
1
			ParachainStaking::set_total_selected(RuntimeOrigin::root(), 6u32),
93
1
			Error::<Test>::RoundLengthMustBeGreaterThanTotalSelectedCollators,
94
1
		);
95
1
	});
96
1
}
97

            
98
#[test]
99
1
fn set_total_selected_fails_if_above_max_candidates() {
100
1
	ExtBuilder::default().build().execute_with(|| {
101
1
		assert_eq!(<Test as crate::Config>::MaxCandidates::get(), 200); // test relies on this
102
1
		assert_noop!(
103
1
			ParachainStaking::set_total_selected(RuntimeOrigin::root(), 201u32),
104
1
			Error::<Test>::CannotSetAboveMaxCandidates,
105
1
		);
106
1
	});
107
1
}
108

            
109
#[test]
110
1
fn set_total_selected_fails_if_equal_to_blocks_per_round() {
111
1
	ExtBuilder::default().build().execute_with(|| {
112
1
		assert_ok!(ParachainStaking::set_blocks_per_round(
113
1
			RuntimeOrigin::root(),
114
1
			10u32
115
1
		));
116
1
		assert_noop!(
117
1
			ParachainStaking::set_total_selected(RuntimeOrigin::root(), 10u32),
118
1
			Error::<Test>::RoundLengthMustBeGreaterThanTotalSelectedCollators,
119
1
		);
120
1
	});
121
1
}
122

            
123
#[test]
124
1
fn set_total_selected_passes_if_below_blocks_per_round() {
125
1
	ExtBuilder::default().build().execute_with(|| {
126
1
		assert_ok!(ParachainStaking::set_blocks_per_round(
127
1
			RuntimeOrigin::root(),
128
1
			10u32
129
1
		));
130
1
		assert_ok!(ParachainStaking::set_total_selected(
131
1
			RuntimeOrigin::root(),
132
1
			9u32
133
1
		));
134
1
	});
135
1
}
136

            
137
#[test]
138
1
fn set_blocks_per_round_fails_if_below_total_selected() {
139
1
	ExtBuilder::default().build().execute_with(|| {
140
1
		assert_ok!(ParachainStaking::set_blocks_per_round(
141
1
			RuntimeOrigin::root(),
142
1
			20u32
143
1
		));
144
1
		assert_ok!(ParachainStaking::set_total_selected(
145
1
			RuntimeOrigin::root(),
146
1
			10u32
147
1
		));
148
1
		assert_noop!(
149
1
			ParachainStaking::set_blocks_per_round(RuntimeOrigin::root(), 9u32),
150
1
			Error::<Test>::RoundLengthMustBeGreaterThanTotalSelectedCollators,
151
1
		);
152
1
	});
153
1
}
154

            
155
#[test]
156
1
fn set_blocks_per_round_fails_if_equal_to_total_selected() {
157
1
	ExtBuilder::default().build().execute_with(|| {
158
1
		assert_ok!(ParachainStaking::set_blocks_per_round(
159
1
			RuntimeOrigin::root(),
160
1
			10u32
161
1
		));
162
1
		assert_ok!(ParachainStaking::set_total_selected(
163
1
			RuntimeOrigin::root(),
164
1
			9u32
165
1
		));
166
1
		assert_noop!(
167
1
			ParachainStaking::set_blocks_per_round(RuntimeOrigin::root(), 9u32),
168
1
			Error::<Test>::RoundLengthMustBeGreaterThanTotalSelectedCollators,
169
1
		);
170
1
	});
171
1
}
172

            
173
#[test]
174
1
fn set_blocks_per_round_passes_if_above_total_selected() {
175
1
	ExtBuilder::default().build().execute_with(|| {
176
1
		assert_eq!(ParachainStaking::round().length, 5); // test relies on this
177
1
		assert_ok!(ParachainStaking::set_blocks_per_round(
178
1
			RuntimeOrigin::root(),
179
1
			6u32
180
1
		));
181
1
	});
182
1
}
183

            
184
#[test]
185
1
fn set_total_selected_storage_updates_correctly() {
186
1
	ExtBuilder::default().build().execute_with(|| {
187
1
		// round length must be >= total_selected, so update that first
188
1
		assert_ok!(ParachainStaking::set_blocks_per_round(
189
1
			RuntimeOrigin::root(),
190
1
			10u32
191
1
		));
192

            
193
1
		assert_eq!(ParachainStaking::total_selected(), 5u32);
194
1
		assert_ok!(ParachainStaking::set_total_selected(
195
1
			RuntimeOrigin::root(),
196
1
			6u32
197
1
		));
198
1
		assert_eq!(ParachainStaking::total_selected(), 6u32);
199
1
	});
200
1
}
201

            
202
#[test]
203
1
fn cannot_set_total_selected_to_current_total_selected() {
204
1
	ExtBuilder::default().build().execute_with(|| {
205
1
		assert_noop!(
206
1
			ParachainStaking::set_total_selected(RuntimeOrigin::root(), 5u32),
207
1
			Error::<Test>::NoWritingSameValue
208
1
		);
209
1
	});
210
1
}
211

            
212
#[test]
213
1
fn cannot_set_total_selected_below_module_min() {
214
1
	ExtBuilder::default().build().execute_with(|| {
215
1
		assert_noop!(
216
1
			ParachainStaking::set_total_selected(RuntimeOrigin::root(), 4u32),
217
1
			Error::<Test>::CannotSetBelowMin
218
1
		);
219
1
	});
220
1
}
221

            
222
// SET COLLATOR COMMISSION
223

            
224
#[test]
225
1
fn set_collator_commission_event_emits_correctly() {
226
1
	ExtBuilder::default().build().execute_with(|| {
227
1
		assert_ok!(ParachainStaking::set_collator_commission(
228
1
			RuntimeOrigin::root(),
229
1
			Perbill::from_percent(5)
230
1
		));
231
1
		assert_events_eq!(Event::CollatorCommissionSet {
232
1
			old: Perbill::from_percent(20),
233
1
			new: Perbill::from_percent(5),
234
1
		});
235
1
	});
236
1
}
237

            
238
#[test]
239
1
fn set_collator_commission_storage_updates_correctly() {
240
1
	ExtBuilder::default().build().execute_with(|| {
241
1
		assert_eq!(
242
1
			ParachainStaking::collator_commission(),
243
1
			Perbill::from_percent(20)
244
1
		);
245
1
		assert_ok!(ParachainStaking::set_collator_commission(
246
1
			RuntimeOrigin::root(),
247
1
			Perbill::from_percent(5)
248
1
		));
249
1
		assert_eq!(
250
1
			ParachainStaking::collator_commission(),
251
1
			Perbill::from_percent(5)
252
1
		);
253
1
	});
254
1
}
255

            
256
#[test]
257
1
fn cannot_set_collator_commission_to_current_collator_commission() {
258
1
	ExtBuilder::default().build().execute_with(|| {
259
1
		assert_noop!(
260
1
			ParachainStaking::set_collator_commission(
261
1
				RuntimeOrigin::root(),
262
1
				Perbill::from_percent(20)
263
1
			),
264
1
			Error::<Test>::NoWritingSameValue
265
1
		);
266
1
	});
267
1
}
268

            
269
// SET BLOCKS PER ROUND
270

            
271
#[test]
272
1
fn set_blocks_per_round_event_emits_correctly() {
273
1
	ExtBuilder::default().build().execute_with(|| {
274
1
		assert_ok!(ParachainStaking::set_blocks_per_round(
275
1
			RuntimeOrigin::root(),
276
1
			6u32
277
1
		));
278
1
		assert_events_eq!(Event::BlocksPerRoundSet {
279
1
			current_round: 1,
280
1
			first_block: 0,
281
1
			old: 5,
282
1
			new: 6,
283
1
			new_per_round_inflation_min: Perbill::from_parts(463),
284
1
			new_per_round_inflation_ideal: Perbill::from_parts(463),
285
1
			new_per_round_inflation_max: Perbill::from_parts(463),
286
1
		});
287
1
	});
288
1
}
289

            
290
#[test]
291
1
fn set_blocks_per_round_storage_updates_correctly() {
292
1
	ExtBuilder::default().build().execute_with(|| {
293
1
		assert_eq!(ParachainStaking::round().length, 5);
294
1
		assert_ok!(ParachainStaking::set_blocks_per_round(
295
1
			RuntimeOrigin::root(),
296
1
			6u32
297
1
		));
298
1
		assert_eq!(ParachainStaking::round().length, 6);
299
1
	});
300
1
}
301

            
302
#[test]
303
1
fn cannot_set_blocks_per_round_below_module_min() {
304
1
	ExtBuilder::default().build().execute_with(|| {
305
1
		assert_noop!(
306
1
			ParachainStaking::set_blocks_per_round(RuntimeOrigin::root(), 2u32),
307
1
			Error::<Test>::CannotSetBelowMin
308
1
		);
309
1
	});
310
1
}
311

            
312
#[test]
313
1
fn cannot_set_blocks_per_round_to_current_blocks_per_round() {
314
1
	ExtBuilder::default().build().execute_with(|| {
315
1
		assert_noop!(
316
1
			ParachainStaking::set_blocks_per_round(RuntimeOrigin::root(), 5u32),
317
1
			Error::<Test>::NoWritingSameValue
318
1
		);
319
1
	});
320
1
}
321

            
322
#[test]
323
1
fn round_immediately_jumps_if_current_duration_exceeds_new_blocks_per_round() {
324
1
	ExtBuilder::default()
325
1
		.with_balances(vec![(1, 20)])
326
1
		.with_candidates(vec![(1, 20)])
327
1
		.build()
328
1
		.execute_with(|| {
329
1
			// we can't lower the blocks per round because it must be above the number of collators,
330
1
			// and we can't lower the number of collators because it must be above
331
1
			// MinSelectedCandidates. so we first raise blocks per round, then lower it.
332
1
			assert_ok!(ParachainStaking::set_blocks_per_round(
333
1
				RuntimeOrigin::root(),
334
1
				10u32
335
1
			));
336

            
337
1
			roll_to(10);
338
1
			assert_events_emitted!(Event::NewRound {
339
1
				starting_block: 10,
340
1
				round: 2,
341
1
				selected_collators_number: 1,
342
1
				total_balance: 20
343
1
			},);
344
1
			roll_to(17);
345
1
			assert_ok!(ParachainStaking::set_blocks_per_round(
346
1
				RuntimeOrigin::root(),
347
1
				6u32
348
1
			));
349
1
			roll_to(18);
350
1
			assert_events_emitted!(Event::NewRound {
351
1
				starting_block: 18,
352
1
				round: 3,
353
1
				selected_collators_number: 1,
354
1
				total_balance: 20
355
1
			});
356
1
		});
357
1
}
358

            
359
// ~~ MONETARY GOVERNANCE ~~
360

            
361
#[test]
362
1
fn invalid_monetary_origin_fails() {
363
1
	ExtBuilder::default().build().execute_with(|| {
364
1
		assert_noop!(
365
1
			ParachainStaking::set_staking_expectations(
366
1
				RuntimeOrigin::signed(45),
367
1
				Range {
368
1
					min: 3u32.into(),
369
1
					ideal: 4u32.into(),
370
1
					max: 5u32.into()
371
1
				}
372
1
			),
373
1
			sp_runtime::DispatchError::BadOrigin
374
1
		);
375
1
		assert_noop!(
376
1
			ParachainStaking::set_inflation(
377
1
				RuntimeOrigin::signed(45),
378
1
				Range {
379
1
					min: Perbill::from_percent(3),
380
1
					ideal: Perbill::from_percent(4),
381
1
					max: Perbill::from_percent(5)
382
1
				}
383
1
			),
384
1
			sp_runtime::DispatchError::BadOrigin
385
1
		);
386
1
		assert_noop!(
387
1
			ParachainStaking::set_inflation(
388
1
				RuntimeOrigin::signed(45),
389
1
				Range {
390
1
					min: Perbill::from_percent(3),
391
1
					ideal: Perbill::from_percent(4),
392
1
					max: Perbill::from_percent(5)
393
1
				}
394
1
			),
395
1
			sp_runtime::DispatchError::BadOrigin
396
1
		);
397
1
		assert_noop!(
398
1
			ParachainStaking::set_parachain_bond_account(RuntimeOrigin::signed(45), 11),
399
1
			sp_runtime::DispatchError::BadOrigin
400
1
		);
401
1
		assert_noop!(
402
1
			ParachainStaking::set_parachain_bond_reserve_percent(
403
1
				RuntimeOrigin::signed(45),
404
1
				Percent::from_percent(2)
405
1
			),
406
1
			sp_runtime::DispatchError::BadOrigin
407
1
		);
408
1
	});
409
1
}
410

            
411
// SET STAKING EXPECTATIONS
412

            
413
#[test]
414
1
fn set_staking_event_emits_event_correctly() {
415
1
	ExtBuilder::default().build().execute_with(|| {
416
1
		// valid call succeeds
417
1
		assert_ok!(ParachainStaking::set_staking_expectations(
418
1
			RuntimeOrigin::root(),
419
1
			Range {
420
1
				min: 3u128,
421
1
				ideal: 4u128,
422
1
				max: 5u128,
423
1
			}
424
1
		));
425
1
		assert_events_eq!(Event::StakeExpectationsSet {
426
1
			expect_min: 3u128,
427
1
			expect_ideal: 4u128,
428
1
			expect_max: 5u128,
429
1
		});
430
1
	});
431
1
}
432

            
433
#[test]
434
1
fn set_staking_updates_storage_correctly() {
435
1
	ExtBuilder::default().build().execute_with(|| {
436
1
		assert_eq!(
437
1
			ParachainStaking::inflation_config().expect,
438
1
			Range {
439
1
				min: 700,
440
1
				ideal: 700,
441
1
				max: 700
442
1
			}
443
1
		);
444
1
		assert_ok!(ParachainStaking::set_staking_expectations(
445
1
			RuntimeOrigin::root(),
446
1
			Range {
447
1
				min: 3u128,
448
1
				ideal: 4u128,
449
1
				max: 5u128,
450
1
			}
451
1
		));
452
1
		assert_eq!(
453
1
			ParachainStaking::inflation_config().expect,
454
1
			Range {
455
1
				min: 3u128,
456
1
				ideal: 4u128,
457
1
				max: 5u128
458
1
			}
459
1
		);
460
1
	});
461
1
}
462

            
463
#[test]
464
1
fn cannot_set_invalid_staking_expectations() {
465
1
	ExtBuilder::default().build().execute_with(|| {
466
1
		// invalid call fails
467
1
		assert_noop!(
468
1
			ParachainStaking::set_staking_expectations(
469
1
				RuntimeOrigin::root(),
470
1
				Range {
471
1
					min: 5u128,
472
1
					ideal: 4u128,
473
1
					max: 3u128
474
1
				}
475
1
			),
476
1
			Error::<Test>::InvalidSchedule
477
1
		);
478
1
	});
479
1
}
480

            
481
#[test]
482
1
fn cannot_set_same_staking_expectations() {
483
1
	ExtBuilder::default().build().execute_with(|| {
484
1
		assert_ok!(ParachainStaking::set_staking_expectations(
485
1
			RuntimeOrigin::root(),
486
1
			Range {
487
1
				min: 3u128,
488
1
				ideal: 4u128,
489
1
				max: 5u128
490
1
			}
491
1
		));
492
1
		assert_noop!(
493
1
			ParachainStaking::set_staking_expectations(
494
1
				RuntimeOrigin::root(),
495
1
				Range {
496
1
					min: 3u128,
497
1
					ideal: 4u128,
498
1
					max: 5u128
499
1
				}
500
1
			),
501
1
			Error::<Test>::NoWritingSameValue
502
1
		);
503
1
	});
504
1
}
505

            
506
// SET INFLATION
507

            
508
#[test]
509
1
fn set_inflation_event_emits_correctly() {
510
1
	ExtBuilder::default().build().execute_with(|| {
511
1
		let (min, ideal, max): (Perbill, Perbill, Perbill) = (
512
1
			Perbill::from_percent(3),
513
1
			Perbill::from_percent(4),
514
1
			Perbill::from_percent(5),
515
1
		);
516
1
		assert_ok!(ParachainStaking::set_inflation(
517
1
			RuntimeOrigin::root(),
518
1
			Range { min, ideal, max }
519
1
		));
520
1
		assert_events_eq!(Event::InflationSet {
521
1
			annual_min: min,
522
1
			annual_ideal: ideal,
523
1
			annual_max: max,
524
1
			round_min: Perbill::from_parts(29),
525
1
			round_ideal: Perbill::from_parts(38),
526
1
			round_max: Perbill::from_parts(47),
527
1
		});
528
1
	});
529
1
}
530

            
531
#[test]
532
1
fn set_inflation_storage_updates_correctly() {
533
1
	ExtBuilder::default().build().execute_with(|| {
534
1
		let (min, ideal, max): (Perbill, Perbill, Perbill) = (
535
1
			Perbill::from_percent(3),
536
1
			Perbill::from_percent(4),
537
1
			Perbill::from_percent(5),
538
1
		);
539
1
		assert_eq!(
540
1
			ParachainStaking::inflation_config().annual,
541
1
			Range {
542
1
				min: Perbill::from_percent(50),
543
1
				ideal: Perbill::from_percent(50),
544
1
				max: Perbill::from_percent(50)
545
1
			}
546
1
		);
547
1
		assert_eq!(
548
1
			ParachainStaking::inflation_config().round,
549
1
			Range {
550
1
				min: Perbill::from_percent(5),
551
1
				ideal: Perbill::from_percent(5),
552
1
				max: Perbill::from_percent(5)
553
1
			}
554
1
		);
555
1
		assert_ok!(ParachainStaking::set_inflation(
556
1
			RuntimeOrigin::root(),
557
1
			Range { min, ideal, max }
558
1
		),);
559
1
		assert_eq!(
560
1
			ParachainStaking::inflation_config().annual,
561
1
			Range { min, ideal, max }
562
1
		);
563
1
		assert_eq!(
564
1
			ParachainStaking::inflation_config().round,
565
1
			Range {
566
1
				min: Perbill::from_parts(29),
567
1
				ideal: Perbill::from_parts(38),
568
1
				max: Perbill::from_parts(47)
569
1
			}
570
1
		);
571
1
	});
572
1
}
573

            
574
#[test]
575
1
fn cannot_set_invalid_inflation() {
576
1
	ExtBuilder::default().build().execute_with(|| {
577
1
		assert_noop!(
578
1
			ParachainStaking::set_inflation(
579
1
				RuntimeOrigin::root(),
580
1
				Range {
581
1
					min: Perbill::from_percent(5),
582
1
					ideal: Perbill::from_percent(4),
583
1
					max: Perbill::from_percent(3)
584
1
				}
585
1
			),
586
1
			Error::<Test>::InvalidSchedule
587
1
		);
588
1
	});
589
1
}
590

            
591
#[test]
592
1
fn cannot_set_same_inflation() {
593
1
	ExtBuilder::default().build().execute_with(|| {
594
1
		let (min, ideal, max): (Perbill, Perbill, Perbill) = (
595
1
			Perbill::from_percent(3),
596
1
			Perbill::from_percent(4),
597
1
			Perbill::from_percent(5),
598
1
		);
599
1
		assert_ok!(ParachainStaking::set_inflation(
600
1
			RuntimeOrigin::root(),
601
1
			Range { min, ideal, max }
602
1
		),);
603
1
		assert_noop!(
604
1
			ParachainStaking::set_inflation(RuntimeOrigin::root(), Range { min, ideal, max }),
605
1
			Error::<Test>::NoWritingSameValue
606
1
		);
607
1
	});
608
1
}
609

            
610
// SET PARACHAIN BOND ACCOUNT
611

            
612
#[test]
613
1
fn set_parachain_bond_account_event_emits_correctly() {
614
1
	ExtBuilder::default().build().execute_with(|| {
615
1
		assert_ok!(ParachainStaking::set_parachain_bond_account(
616
1
			RuntimeOrigin::root(),
617
1
			11
618
1
		));
619
1
		assert_events_eq!(Event::InflationDistributionConfigUpdated {
620
1
			old: inflation_configs(0, 30, 0, 0),
621
1
			new: inflation_configs(11, 30, 0, 0),
622
1
		});
623
1
	});
624
1
}
625

            
626
#[test]
627
1
fn set_parachain_bond_account_storage_updates_correctly() {
628
1
	ExtBuilder::default().build().execute_with(|| {
629
1
		assert_eq!(
630
1
			ParachainStaking::inflation_distribution_info().0[0].account,
631
1
			0
632
1
		);
633
1
		assert_ok!(ParachainStaking::set_parachain_bond_account(
634
1
			RuntimeOrigin::root(),
635
1
			11
636
1
		));
637
1
		assert_eq!(
638
1
			ParachainStaking::inflation_distribution_info().0[0].account,
639
1
			11
640
1
		);
641
1
	});
642
1
}
643

            
644
// SET PARACHAIN BOND RESERVE PERCENT
645

            
646
#[test]
647
1
fn set_parachain_bond_reserve_percent_event_emits_correctly() {
648
1
	ExtBuilder::default().build().execute_with(|| {
649
1
		assert_ok!(ParachainStaking::set_parachain_bond_reserve_percent(
650
1
			RuntimeOrigin::root(),
651
1
			Percent::from_percent(50)
652
1
		));
653
1
		assert_events_eq!(Event::InflationDistributionConfigUpdated {
654
1
			old: inflation_configs(0, 30, 0, 0),
655
1
			new: inflation_configs(0, 50, 0, 0),
656
1
		});
657
1
	});
658
1
}
659

            
660
#[test]
661
1
fn set_parachain_bond_reserve_percent_storage_updates_correctly() {
662
1
	ExtBuilder::default().build().execute_with(|| {
663
1
		assert_eq!(
664
1
			ParachainStaking::inflation_distribution_info().0[0].percent,
665
1
			Percent::from_percent(30)
666
1
		);
667
1
		assert_ok!(ParachainStaking::set_parachain_bond_reserve_percent(
668
1
			RuntimeOrigin::root(),
669
1
			Percent::from_percent(50)
670
1
		));
671
1
		assert_eq!(
672
1
			ParachainStaking::inflation_distribution_info().0[0].percent,
673
1
			Percent::from_percent(50)
674
1
		);
675
1
	});
676
1
}
677

            
678
#[test]
679
1
fn cannot_set_same_parachain_bond_reserve_percent() {
680
1
	ExtBuilder::default().build().execute_with(|| {
681
1
		assert_noop!(
682
1
			ParachainStaking::set_parachain_bond_reserve_percent(
683
1
				RuntimeOrigin::root(),
684
1
				Percent::from_percent(30)
685
1
			),
686
1
			Error::<Test>::NoWritingSameValue
687
1
		);
688
1
	});
689
1
}
690

            
691
// Set Inflation Distribution Config
692

            
693
#[test]
694
1
fn set_inflation_distribution_config_fails_with_normal_origin() {
695
1
	ExtBuilder::default().build().execute_with(|| {
696
1
		assert_noop!(
697
1
			ParachainStaking::set_inflation_distribution_config(
698
1
				RuntimeOrigin::signed(45),
699
1
				inflation_configs(1, 30, 2, 20)
700
1
			),
701
1
			sp_runtime::DispatchError::BadOrigin,
702
1
		);
703
1
	});
704
1
}
705

            
706
#[test]
707
1
fn set_inflation_distribution_config_event_emits_correctly() {
708
1
	ExtBuilder::default().build().execute_with(|| {
709
1
		assert_ok!(ParachainStaking::set_inflation_distribution_config(
710
1
			RuntimeOrigin::root(),
711
1
			inflation_configs(1, 30, 2, 20),
712
1
		));
713
1
		assert_events_eq!(Event::InflationDistributionConfigUpdated {
714
1
			old: inflation_configs(0, 30, 0, 0),
715
1
			new: inflation_configs(1, 30, 2, 20),
716
1
		});
717
1
		roll_blocks(1);
718
1
		assert_ok!(ParachainStaking::set_inflation_distribution_config(
719
1
			RuntimeOrigin::root(),
720
1
			inflation_configs(5, 10, 6, 5),
721
1
		));
722
1
		assert_events_eq!(Event::InflationDistributionConfigUpdated {
723
1
			old: inflation_configs(1, 30, 2, 20),
724
1
			new: inflation_configs(5, 10, 6, 5),
725
1
		});
726
1
	});
727
1
}
728

            
729
#[test]
730
1
fn set_inflation_distribution_config_storage_updates_correctly() {
731
1
	ExtBuilder::default().build().execute_with(|| {
732
1
		assert_eq!(
733
1
			InflationDistributionInfo::<Test>::get(),
734
1
			inflation_configs(0, 30, 0, 0),
735
1
		);
736
1
		assert_ok!(ParachainStaking::set_inflation_distribution_config(
737
1
			RuntimeOrigin::root(),
738
1
			inflation_configs(5, 10, 6, 5),
739
1
		));
740
1
		assert_eq!(
741
1
			InflationDistributionInfo::<Test>::get(),
742
1
			inflation_configs(5, 10, 6, 5),
743
1
		);
744
1
		assert_ok!(ParachainStaking::set_inflation_distribution_config(
745
1
			RuntimeOrigin::root(),
746
1
			inflation_configs(1, 30, 2, 20),
747
1
		));
748
1
		assert_eq!(
749
1
			InflationDistributionInfo::<Test>::get(),
750
1
			inflation_configs(1, 30, 2, 20),
751
1
		);
752
1
	});
753
1
}
754

            
755
#[test]
756
1
fn cannot_set_same_inflation_distribution_config() {
757
1
	ExtBuilder::default().build().execute_with(|| {
758
1
		assert_ok!(ParachainStaking::set_inflation_distribution_config(
759
1
			RuntimeOrigin::root(),
760
1
			inflation_configs(1, 30, 2, 20),
761
1
		));
762
1
		assert_noop!(
763
1
			ParachainStaking::set_inflation_distribution_config(
764
1
				RuntimeOrigin::root(),
765
1
				inflation_configs(1, 30, 2, 20)
766
1
			),
767
1
			Error::<Test>::NoWritingSameValue,
768
1
		);
769
1
	});
770
1
}
771

            
772
#[test]
773
1
fn sum_of_inflation_distribution_config_percentages_must_lte_100() {
774
1
	ExtBuilder::default().build().execute_with(|| {
775
1
		let invalid_values: Vec<(u8, u8)> = vec![
776
1
			(20, 90),
777
1
			(90, 20),
778
1
			(50, 51),
779
1
			(100, 1),
780
1
			(1, 100),
781
1
			(55, 55),
782
1
			(2, 99),
783
1
			(100, 100),
784
1
		];
785

            
786
9
		for (pbr_percentage, treasury_percentage) in invalid_values {
787
8
			assert_noop!(
788
8
				ParachainStaking::set_inflation_distribution_config(
789
8
					RuntimeOrigin::root(),
790
8
					inflation_configs(1, pbr_percentage, 2, treasury_percentage),
791
8
				),
792
8
				Error::<Test>::TotalInflationDistributionPercentExceeds100,
793
8
			);
794
		}
795

            
796
1
		let valid_values: Vec<(u8, u8)> = vec![
797
1
			(0, 100),
798
1
			(100, 0),
799
1
			(0, 0),
800
1
			(100, 0),
801
1
			(0, 100),
802
1
			(50, 50),
803
1
			(1, 99),
804
1
			(99, 1),
805
1
			(1, 1),
806
1
			(10, 20),
807
1
			(34, 32),
808
1
			(15, 10),
809
1
		];
810

            
811
13
		for (pbr_percentage, treasury_percentage) in valid_values {
812
12
			assert_ok!(ParachainStaking::set_inflation_distribution_config(
813
12
				RuntimeOrigin::root(),
814
12
				inflation_configs(1, pbr_percentage, 2, treasury_percentage),
815
12
			));
816
		}
817
1
	});
818
1
}
819

            
820
// ~~ PUBLIC ~~
821

            
822
// JOIN CANDIDATES
823

            
824
#[test]
825
1
fn join_candidates_event_emits_correctly() {
826
1
	ExtBuilder::default()
827
1
		.with_balances(vec![(1, 10)])
828
1
		.build()
829
1
		.execute_with(|| {
830
1
			assert_ok!(ParachainStaking::join_candidates(
831
1
				RuntimeOrigin::signed(1),
832
1
				10u128,
833
1
				0u32
834
1
			));
835
1
			assert_events_eq!(Event::JoinedCollatorCandidates {
836
1
				account: 1,
837
1
				amount_locked: 10u128,
838
1
				new_total_amt_locked: 10u128,
839
1
			});
840
1
		});
841
1
}
842

            
843
#[test]
844
1
fn join_candidates_reserves_balance() {
845
1
	ExtBuilder::default()
846
1
		.with_balances(vec![(1, 10)])
847
1
		.build()
848
1
		.execute_with(|| {
849
1
			assert_eq!(ParachainStaking::get_collator_stakable_free_balance(&1), 10);
850
1
			assert_ok!(ParachainStaking::join_candidates(
851
1
				RuntimeOrigin::signed(1),
852
1
				10u128,
853
1
				0u32
854
1
			));
855
1
			assert_eq!(ParachainStaking::get_collator_stakable_free_balance(&1), 0);
856
1
		});
857
1
}
858

            
859
#[test]
860
1
fn join_candidates_increases_total_staked() {
861
1
	ExtBuilder::default()
862
1
		.with_balances(vec![(1, 10)])
863
1
		.build()
864
1
		.execute_with(|| {
865
1
			assert_eq!(ParachainStaking::total(), 0);
866
1
			assert_ok!(ParachainStaking::join_candidates(
867
1
				RuntimeOrigin::signed(1),
868
1
				10u128,
869
1
				0u32
870
1
			));
871
1
			assert_eq!(ParachainStaking::total(), 10);
872
1
		});
873
1
}
874

            
875
#[test]
876
1
fn join_candidates_creates_candidate_state() {
877
1
	ExtBuilder::default()
878
1
		.with_balances(vec![(1, 10)])
879
1
		.build()
880
1
		.execute_with(|| {
881
1
			assert!(ParachainStaking::candidate_info(1).is_none());
882
1
			assert_ok!(ParachainStaking::join_candidates(
883
1
				RuntimeOrigin::signed(1),
884
1
				10u128,
885
1
				0u32
886
1
			));
887
1
			let candidate_state =
888
1
				ParachainStaking::candidate_info(1).expect("just joined => exists");
889
1
			assert_eq!(candidate_state.bond, 10u128);
890
1
		});
891
1
}
892

            
893
#[test]
894
1
fn join_candidates_adds_to_candidate_pool() {
895
1
	ExtBuilder::default()
896
1
		.with_balances(vec![(1, 10)])
897
1
		.build()
898
1
		.execute_with(|| {
899
1
			assert!(ParachainStaking::candidate_pool().0.is_empty());
900
1
			assert_ok!(ParachainStaking::join_candidates(
901
1
				RuntimeOrigin::signed(1),
902
1
				10u128,
903
1
				0u32
904
1
			));
905
1
			let candidate_pool = ParachainStaking::candidate_pool();
906
1
			assert_eq!(candidate_pool.0[0].owner, 1);
907
1
			assert_eq!(candidate_pool.0[0].amount, 10);
908
1
		});
909
1
}
910

            
911
#[test]
912
1
fn cannot_join_candidates_if_candidate() {
913
1
	ExtBuilder::default()
914
1
		.with_balances(vec![(1, 1000)])
915
1
		.with_candidates(vec![(1, 500)])
916
1
		.build()
917
1
		.execute_with(|| {
918
1
			assert_noop!(
919
1
				ParachainStaking::join_candidates(RuntimeOrigin::signed(1), 11u128, 100u32),
920
1
				Error::<Test>::CandidateExists
921
1
			);
922
1
		});
923
1
}
924

            
925
#[test]
926
1
fn cannot_join_candidates_if_delegator() {
927
1
	ExtBuilder::default()
928
1
		.with_balances(vec![(1, 50), (2, 20)])
929
1
		.with_candidates(vec![(1, 50)])
930
1
		.with_delegations(vec![(2, 1, 10)])
931
1
		.build()
932
1
		.execute_with(|| {
933
1
			assert_noop!(
934
1
				ParachainStaking::join_candidates(RuntimeOrigin::signed(2), 10u128, 1u32),
935
1
				Error::<Test>::DelegatorExists
936
1
			);
937
1
		});
938
1
}
939

            
940
#[test]
941
1
fn cannot_join_candidates_without_min_bond() {
942
1
	ExtBuilder::default()
943
1
		.with_balances(vec![(1, 1000)])
944
1
		.build()
945
1
		.execute_with(|| {
946
1
			assert_noop!(
947
1
				ParachainStaking::join_candidates(RuntimeOrigin::signed(1), 9u128, 100u32),
948
1
				Error::<Test>::CandidateBondBelowMin
949
1
			);
950
1
		});
951
1
}
952

            
953
#[test]
954
1
fn can_force_join_candidates_without_min_bond() {
955
1
	ExtBuilder::default()
956
1
		.with_balances(vec![(1, 10)])
957
1
		.build()
958
1
		.execute_with(|| {
959
1
			assert_ok!(ParachainStaking::force_join_candidates(
960
1
				RuntimeOrigin::root(),
961
1
				1,
962
1
				9,
963
1
				100u32
964
1
			));
965
1
			assert_events_eq!(Event::JoinedCollatorCandidates {
966
1
				account: 1,
967
1
				amount_locked: 9u128,
968
1
				new_total_amt_locked: 9u128,
969
1
			});
970
1
		});
971
1
}
972

            
973
#[test]
974
1
fn cannot_join_candidates_with_more_than_available_balance() {
975
1
	ExtBuilder::default()
976
1
		.with_balances(vec![(1, 500)])
977
1
		.build()
978
1
		.execute_with(|| {
979
1
			assert_noop!(
980
1
				ParachainStaking::join_candidates(RuntimeOrigin::signed(1), 501u128, 100u32),
981
1
				DispatchError::Module(ModuleError {
982
1
					index: 2,
983
1
					error: [8, 0, 0, 0],
984
1
					message: Some("InsufficientBalance")
985
1
				})
986
1
			);
987
1
		});
988
1
}
989

            
990
#[test]
991
1
fn insufficient_join_candidates_weight_hint_fails() {
992
1
	ExtBuilder::default()
993
1
		.with_balances(vec![(1, 20), (2, 20), (3, 20), (4, 20), (5, 20), (6, 20)])
994
1
		.with_candidates(vec![(1, 20), (2, 20), (3, 20), (4, 20), (5, 20)])
995
1
		.build()
996
1
		.execute_with(|| {
997
6
			for i in 0..5 {
998
5
				assert_noop!(
999
5
					ParachainStaking::join_candidates(RuntimeOrigin::signed(6), 20, i),
5
					Error::<Test>::TooLowCandidateCountWeightHintJoinCandidates
5
				);
			}
1
		});
1
}
#[test]
1
fn sufficient_join_candidates_weight_hint_succeeds() {
1
	ExtBuilder::default()
1
		.with_balances(vec![
1
			(1, 20),
1
			(2, 20),
1
			(3, 20),
1
			(4, 20),
1
			(5, 20),
1
			(6, 20),
1
			(7, 20),
1
			(8, 20),
1
			(9, 20),
1
		])
1
		.with_candidates(vec![(1, 20), (2, 20), (3, 20), (4, 20), (5, 20)])
1
		.build()
1
		.execute_with(|| {
1
			let mut count = 5u32;
5
			for i in 6..10 {
4
				assert_ok!(ParachainStaking::join_candidates(
4
					RuntimeOrigin::signed(i),
4
					20,
4
					count
4
				));
4
				count += 1u32;
			}
1
		});
1
}
#[test]
1
fn join_candidates_fails_if_above_max_candidate_count() {
1
	let mut candidates = vec![];
200
	for i in 1..=crate::mock::MaxCandidates::get() {
200
		candidates.push((i as u64, 80));
200
	}
1
	let new_candidate = crate::mock::MaxCandidates::get() as u64 + 1;
1
	let mut balances = candidates.clone();
1
	balances.push((new_candidate, 100));
1

            
1
	ExtBuilder::default()
1
		.with_balances(balances)
1
		.with_candidates(candidates)
1
		.build()
1
		.execute_with(|| {
1
			assert_noop!(
1
				ParachainStaking::join_candidates(
1
					RuntimeOrigin::signed(new_candidate),
1
					80,
1
					crate::mock::MaxCandidates::get(),
1
				),
1
				Error::<Test>::CandidateLimitReached,
1
			);
1
		});
1
}
// SCHEDULE LEAVE CANDIDATES
#[test]
1
fn leave_candidates_event_emits_correctly() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 10)])
1
		.with_candidates(vec![(1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::schedule_leave_candidates(
1
				RuntimeOrigin::signed(1),
1
				1u32
1
			));
1
			assert_events_eq!(Event::CandidateScheduledExit {
1
				exit_allowed_round: 1,
1
				candidate: 1,
1
				scheduled_exit: 3
1
			});
1
		});
1
}
#[test]
1
fn leave_candidates_removes_candidate_from_candidate_pool() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 10)])
1
		.with_candidates(vec![(1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_eq!(ParachainStaking::candidate_pool().0.len(), 1);
1
			assert_ok!(ParachainStaking::schedule_leave_candidates(
1
				RuntimeOrigin::signed(1),
1
				1u32
1
			));
1
			assert!(ParachainStaking::candidate_pool().0.is_empty());
1
		});
1
}
#[test]
1
fn cannot_leave_candidates_if_not_candidate() {
1
	ExtBuilder::default().build().execute_with(|| {
1
		assert_noop!(
1
			ParachainStaking::schedule_leave_candidates(RuntimeOrigin::signed(1), 1u32),
1
			Error::<Test>::CandidateDNE
1
		);
1
	});
1
}
#[test]
1
fn cannot_leave_candidates_if_already_leaving_candidates() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 10)])
1
		.with_candidates(vec![(1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::schedule_leave_candidates(
1
				RuntimeOrigin::signed(1),
1
				1u32
1
			));
1
			assert_noop!(
1
				ParachainStaking::schedule_leave_candidates(RuntimeOrigin::signed(1), 1u32),
1
				Error::<Test>::CandidateAlreadyLeaving
1
			);
1
		});
1
}
#[test]
1
fn insufficient_leave_candidates_weight_hint_fails() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 20), (2, 20), (3, 20), (4, 20), (5, 20)])
1
		.with_candidates(vec![(1, 20), (2, 20), (3, 20), (4, 20), (5, 20)])
1
		.build()
1
		.execute_with(|| {
6
			for i in 1..6 {
5
				assert_noop!(
5
					ParachainStaking::schedule_leave_candidates(RuntimeOrigin::signed(i), 4u32),
5
					Error::<Test>::TooLowCandidateCountToLeaveCandidates
5
				);
			}
1
		});
1
}
#[test]
1
fn enable_marking_offline_works() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 20)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::enable_marking_offline(
1
				RuntimeOrigin::root(),
1
				true
1
			));
1
			assert!(ParachainStaking::marking_offline());
			// Set to false now
1
			assert_ok!(ParachainStaking::enable_marking_offline(
1
				RuntimeOrigin::root(),
1
				false
1
			));
1
			assert!(!ParachainStaking::marking_offline());
1
		});
1
}
#[test]
1
fn enable_marking_offline_fails_bad_origin() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 20)])
1
		.build()
1
		.execute_with(|| {
1
			assert_noop!(
1
				ParachainStaking::enable_marking_offline(RuntimeOrigin::signed(1), true),
1
				sp_runtime::DispatchError::BadOrigin
1
			);
1
		});
1
}
#[test]
1
fn was_inactive_is_cleaned_up_after_max_offline_rounds() {
	const ACTIVE_COLLATOR: AccountId = 1;
	const INACTIVE_COLLATOR: AccountId = 2;
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 20), (2, 20)])
1
		.with_candidates(vec![(1, 20), (2, 20)])
1
		.build()
1
		.execute_with(|| {
1
			assert_eq!(<Test as crate::Config>::MaxOfflineRounds::get(), 2);
1
			assert_eq!(<Test as crate::Config>::RewardPaymentDelay::get(), 2);
			// ACTIVE_COLLATOR authors all the blocks
1
			set_block_author(ACTIVE_COLLATOR);
1

            
1
			// Round 2
1
			roll_to_round_begin(2);
1

            
1
			assert!(<AtStake<Test>>::contains_key(1, ACTIVE_COLLATOR));
1
			assert!(!<WasInactive<Test>>::contains_key(1, ACTIVE_COLLATOR));
1
			assert!(<AtStake<Test>>::contains_key(1, INACTIVE_COLLATOR));
1
			assert!(<WasInactive<Test>>::contains_key(1, INACTIVE_COLLATOR));
			// Round 3
1
			roll_to_round_begin(3);
1

            
1
			assert!(<AtStake<Test>>::contains_key(2, ACTIVE_COLLATOR));
1
			assert!(!<WasInactive<Test>>::contains_key(2, ACTIVE_COLLATOR));
1
			assert!(<AtStake<Test>>::contains_key(2, INACTIVE_COLLATOR));
1
			assert!(<WasInactive<Test>>::contains_key(2, INACTIVE_COLLATOR));
			// End of round 3
1
			roll_to_round_end(3);
1

            
1
			assert!(
1
				!<AtStake<Test>>::contains_key(1, ACTIVE_COLLATOR),
				"Active collator should have no stake in round 1 due to the distribution of rewards"
			);
1
			assert!(
1
				!<AtStake<Test>>::contains_key(1, INACTIVE_COLLATOR),
				"Inactive collator should have no stake in round 1 due to the distribution of rewards"
			);
1
			assert!(
1
				!<WasInactive<Test>>::contains_key(1, ACTIVE_COLLATOR),
				"Active collator should not be in WasInactive for round 1"
			);
1
			assert!(
1
				<WasInactive<Test>>::contains_key(1, INACTIVE_COLLATOR),
				"Inactive collator should still be in WasInactive for round 1"
			);
			// Round 4
1
			roll_to_round_end(4);
1

            
1
			assert!(
1
				!<WasInactive<Test>>::contains_key(1, INACTIVE_COLLATOR),
				"Round 1 WasInactive should be cleaned up after MaxOfflineRounds"
			);
1
			assert!(<WasInactive<Test>>::contains_key(2, INACTIVE_COLLATOR));
1
			assert!(<WasInactive<Test>>::contains_key(3, INACTIVE_COLLATOR));
1
		});
1
}
#[test]
1
fn notify_inactive_collator_works() {
	const INACTIVE_COLLATOR: AccountId = 1;
	const ACTIVE_COLLATOR: AccountId = 2;
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 20), (2, 20), (3, 20), (4, 20), (5, 20)])
1
		.with_candidates(vec![(1, 20), (2, 20), (3, 20), (4, 20), (5, 20)])
1
		.build()
1
		.execute_with(|| {
1
			// Enable killswitch
1
			<EnableMarkingOffline<Test>>::set(true);
1

            
1
			assert_eq!(<Test as crate::Config>::MaxOfflineRounds::get(), 2);
1
			assert_eq!(<Test as crate::Config>::RewardPaymentDelay::get(), 2);
			// Round 2 - INACTIVE_COLLATOR authors blocks
1
			set_block_author(INACTIVE_COLLATOR);
1
			roll_to_round_begin(2);
1

            
1
			// Change block author
1
			set_block_author(ACTIVE_COLLATOR);
1

            
1
			// INACTIVE_COLLATOR does not produce blocks on round 2 and 3
1
			roll_to_round_begin(4);
1
			roll_blocks(1);
1

            
1
			// On round 4 notify inactive collator
1
			assert_ok!(ParachainStaking::notify_inactive_collator(
1
				RuntimeOrigin::signed(1),
1
				INACTIVE_COLLATOR
1
			));
			// Check the collator was marked as offline as it hasn't produced blocks
1
			assert_events_eq!(Event::CandidateWentOffline {
1
				candidate: INACTIVE_COLLATOR
1
			},);
1
		});
1
}
#[test]
1
fn notify_inactive_collator_succeeds_even_after_rewards_are_distributed() {
	const INACTIVE_COLLATOR: AccountId = 1;
	const ACTIVE_COLLATOR: AccountId = 2;
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 20), (2, 20), (3, 20), (4, 20), (5, 20)])
1
		.with_candidates(vec![(1, 20), (2, 20), (3, 20), (4, 20), (5, 20)])
1
		.build()
1
		.execute_with(|| {
1
			// Enable killswitch
1
			<EnableMarkingOffline<Test>>::set(true);
1

            
1
			// We need (strictly) more blocks per round than collators so rewards
1
			// can be distributed before the end of a round
1
			assert_ok!(ParachainStaking::set_blocks_per_round(
1
				RuntimeOrigin::root(),
1
				6u32
1
			));
			// ACTIVE_COLLATOR authors all the blocks while INACTIVE_COLLATOR stays inactive
1
			set_block_author(ACTIVE_COLLATOR);
1

            
1
			// Round 2
1
			roll_to_round_begin(2);
1
			roll_blocks(1);
1

            
1
			// INACTIVE_COLLATOR has a stake in round 1
1
			assert!(<AtStake<Test>>::contains_key(1, INACTIVE_COLLATOR));
			// Round 3
1
			roll_to_round_begin(3);
1
			roll_blocks(1);
1

            
1
			// INACTIVE_COLLATOR has a stake in round 2
1
			assert!(<AtStake<Test>>::contains_key(2, INACTIVE_COLLATOR));
			// End of round 3
1
			roll_to_round_end(3);
1

            
1
			// INACTIVE_COLLATOR has a no stake in round 1 anymore due to the distribution of rewards
1
			assert!(!<AtStake<Test>>::contains_key(1, INACTIVE_COLLATOR));
			// Call 'notify_inactive_collator' extrinsic on INACTIVE_COLLATOR
1
			assert_ok!(ParachainStaking::notify_inactive_collator(
1
				RuntimeOrigin::signed(1),
1
				INACTIVE_COLLATOR
1
			));
1
			assert_events_eq!(
1
				Event::Rewarded {
1
					account: 2,
1
					rewards: 0,
1
				},
1
				Event::CandidateWentOffline {
1
					candidate: INACTIVE_COLLATOR
1
				},
1
			);
1
		});
1
}
#[test]
1
fn notify_inactive_collator_fails_too_low_collator_count() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 20), (2, 20), (3, 20)])
1
		.with_candidates(vec![(1, 20), (2, 20), (3, 20)])
1
		.build()
1
		.execute_with(|| {
1
			// Enable killswitch
1
			<EnableMarkingOffline<Test>>::set(true);
1

            
1
			// Round 4
1
			roll_to_round_begin(4);
1
			roll_blocks(1);
1

            
1
			// Call 'notify_inactive_collator' extrinsic
1
			assert_noop!(
1
				ParachainStaking::notify_inactive_collator(RuntimeOrigin::signed(1), 1),
1
				Error::<Test>::TooLowCollatorCountToNotifyAsInactive
1
			);
1
		});
1
}
#[test]
1
fn notify_inactive_collator_fails_candidate_is_not_collator() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 80), (2, 80), (3, 80), (4, 80), (5, 80), (6, 20)])
1
		.with_candidates(vec![(1, 80), (2, 80), (3, 80), (4, 80), (5, 80)])
1
		.build()
1
		.execute_with(|| {
1
			// Enable killswitch
1
			<EnableMarkingOffline<Test>>::set(true);
1

            
1
			set_block_author(1);
1

            
1
			roll_to_round_begin(2);
1
			assert_events_eq!(
1
				Event::CollatorChosen {
1
					round: 2,
1
					collator_account: 1,
1
					total_exposed_amount: 80,
1
				},
1
				Event::CollatorChosen {
1
					round: 2,
1
					collator_account: 2,
1
					total_exposed_amount: 80,
1
				},
1
				Event::CollatorChosen {
1
					round: 2,
1
					collator_account: 3,
1
					total_exposed_amount: 80,
1
				},
1
				Event::CollatorChosen {
1
					round: 2,
1
					collator_account: 4,
1
					total_exposed_amount: 80,
1
				},
1
				Event::CollatorChosen {
1
					round: 2,
1
					collator_account: 5,
1
					total_exposed_amount: 80,
1
				},
1
				Event::NewRound {
1
					starting_block: 5,
1
					round: 2,
1
					selected_collators_number: 5,
1
					total_balance: 400,
1
				},
1
			);
1
			roll_blocks(1);
1

            
1
			assert_ok!(ParachainStaking::join_candidates(
1
				RuntimeOrigin::signed(6),
1
				10,
1
				100
1
			));
			// Round 6
1
			roll_to_round_begin(6);
1
			assert_events_eq!(
1
				Event::CollatorChosen {
1
					round: 6,
1
					collator_account: 1,
1
					total_exposed_amount: 80,
1
				},
1
				Event::CollatorChosen {
1
					round: 6,
1
					collator_account: 2,
1
					total_exposed_amount: 80,
1
				},
1
				Event::CollatorChosen {
1
					round: 6,
1
					collator_account: 3,
1
					total_exposed_amount: 80,
1
				},
1
				Event::CollatorChosen {
1
					round: 6,
1
					collator_account: 4,
1
					total_exposed_amount: 80,
1
				},
1
				Event::CollatorChosen {
1
					round: 6,
1
					collator_account: 5,
1
					total_exposed_amount: 80,
1
				},
1
				Event::NewRound {
1
					starting_block: 25,
1
					round: 6,
1
					selected_collators_number: 5,
1
					total_balance: 400,
1
				},
1
			);
1
			roll_blocks(1);
1

            
1
			// A candidate cannot be notified as inactive if it hasn't been selected
1
			// to produce blocks
1
			assert_noop!(
1
				ParachainStaking::notify_inactive_collator(RuntimeOrigin::signed(1), 6),
1
				Error::<Test>::CannotBeNotifiedAsInactive
1
			);
1
		});
1
}
#[test]
1
fn notify_inactive_collator_fails_cannot_be_notified_as_inactive() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 20), (2, 20), (3, 20), (4, 20), (5, 20)])
1
		.with_candidates(vec![(1, 20), (2, 20), (3, 20), (4, 20), (5, 20)])
1
		.build()
1
		.execute_with(|| {
1
			// Enable killswitch
1
			<EnableMarkingOffline<Test>>::set(true);
1

            
1
			// Round 2
1
			roll_to_round_begin(2);
1

            
1
			// Change block author
1
			set_block_author(1);
1

            
1
			// Round 3
1
			roll_to_round_begin(3);
1
			roll_blocks(1);
1

            
1
			// Round 4
1
			roll_to_round_begin(4);
1
			roll_blocks(1);
1

            
1
			// Call 'notify_inactive_collator' extrinsic
1
			assert_noop!(
1
				ParachainStaking::notify_inactive_collator(RuntimeOrigin::signed(1), 1),
1
				Error::<Test>::CannotBeNotifiedAsInactive
1
			);
1
		});
1
}
#[test]
1
fn notify_inactive_collator_fails_round_too_low() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 20), (2, 20), (3, 20), (4, 20), (5, 20)])
1
		.with_candidates(vec![(1, 20), (2, 20), (3, 20), (4, 20), (5, 20)])
1
		.build()
1
		.execute_with(|| {
1
			// Enable killswitch
1
			<EnableMarkingOffline<Test>>::set(true);
1

            
1
			// Round 1
1
			roll_to_round_begin(1);
1
			roll_blocks(1);
1

            
1
			// Call 'notify_inactive_collator' extrinsic
1
			assert_noop!(
1
				ParachainStaking::notify_inactive_collator(RuntimeOrigin::signed(1), 1),
1
				Error::<Test>::CurrentRoundTooLow
1
			);
1
		});
1
}
#[test]
1
fn sufficient_leave_candidates_weight_hint_succeeds() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 20), (2, 20), (3, 20), (4, 20), (5, 20)])
1
		.with_candidates(vec![(1, 20), (2, 20), (3, 20), (4, 20), (5, 20)])
1
		.build()
1
		.execute_with(|| {
1
			let mut count = 5u32;
6
			for i in 1..6 {
5
				assert_ok!(ParachainStaking::schedule_leave_candidates(
5
					RuntimeOrigin::signed(i),
5
					count
5
				));
5
				count -= 1u32;
			}
1
		});
1
}
// EXECUTE LEAVE CANDIDATES
#[test]
1
fn execute_leave_candidates_emits_event() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 10)])
1
		.with_candidates(vec![(1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::schedule_leave_candidates(
1
				RuntimeOrigin::signed(1),
1
				1u32
1
			));
1
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_leave_candidates(
1
				RuntimeOrigin::signed(1),
1
				1,
1
				0
1
			));
1
			assert_events_emitted!(Event::CandidateLeft {
1
				ex_candidate: 1,
1
				unlocked_amount: 10,
1
				new_total_amt_locked: 0
1
			});
1
		});
1
}
#[test]
1
fn execute_leave_candidates_callable_by_any_signed() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 10)])
1
		.with_candidates(vec![(1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::schedule_leave_candidates(
1
				RuntimeOrigin::signed(1),
1
				1u32
1
			));
1
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_leave_candidates(
1
				RuntimeOrigin::signed(2),
1
				1,
1
				0
1
			));
1
		});
1
}
#[test]
1
fn execute_leave_candidates_requires_correct_weight_hint() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 10), (2, 10), (3, 10), (4, 10)])
1
		.with_candidates(vec![(1, 10)])
1
		.with_delegations(vec![(2, 1, 10), (3, 1, 10), (4, 1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::schedule_leave_candidates(
1
				RuntimeOrigin::signed(1),
1
				1u32
1
			));
1
			roll_to(10);
4
			for i in 0..3 {
3
				assert_noop!(
3
					ParachainStaking::execute_leave_candidates(RuntimeOrigin::signed(1), 1, i),
3
					Error::<Test>::TooLowCandidateDelegationCountToLeaveCandidates
3
				);
			}
1
			assert_ok!(ParachainStaking::execute_leave_candidates(
1
				RuntimeOrigin::signed(2),
1
				1,
1
				3
1
			));
1
		});
1
}
#[test]
1
fn execute_leave_candidates_unreserves_balance() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 10)])
1
		.with_candidates(vec![(1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_eq!(ParachainStaking::get_collator_stakable_free_balance(&1), 0);
1
			assert_ok!(ParachainStaking::schedule_leave_candidates(
1
				RuntimeOrigin::signed(1),
1
				1u32
1
			));
1
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_leave_candidates(
1
				RuntimeOrigin::signed(1),
1
				1,
1
				0
1
			));
1
			assert_eq!(ParachainStaking::get_collator_stakable_free_balance(&1), 10);
1
		});
1
}
#[test]
1
fn execute_leave_candidates_decreases_total_staked() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 10)])
1
		.with_candidates(vec![(1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_eq!(ParachainStaking::total(), 10);
1
			assert_ok!(ParachainStaking::schedule_leave_candidates(
1
				RuntimeOrigin::signed(1),
1
				1u32
1
			));
1
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_leave_candidates(
1
				RuntimeOrigin::signed(1),
1
				1,
1
				0
1
			));
1
			assert_eq!(ParachainStaking::total(), 0);
1
		});
1
}
#[test]
1
fn execute_leave_candidates_removes_candidate_state() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 10)])
1
		.with_candidates(vec![(1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::schedule_leave_candidates(
1
				RuntimeOrigin::signed(1),
1
				1u32
1
			));
			// candidate state is not immediately removed
1
			let candidate_state =
1
				ParachainStaking::candidate_info(1).expect("just left => still exists");
1
			assert_eq!(candidate_state.bond, 10u128);
1
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_leave_candidates(
1
				RuntimeOrigin::signed(1),
1
				1,
1
				0
1
			));
1
			assert!(ParachainStaking::candidate_info(1).is_none());
1
		});
1
}
#[test]
1
fn execute_leave_candidates_removes_pending_delegation_requests() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 10), (2, 15)])
1
		.with_candidates(vec![(1, 10)])
1
		.with_delegations(vec![(2, 1, 15)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::schedule_delegator_bond_less(
1
				RuntimeOrigin::signed(2),
1
				1,
1
				5
1
			));
1
			let state = ParachainStaking::delegation_scheduled_requests(&1);
1
			assert_eq!(
1
				state,
1
				vec![ScheduledRequest {
1
					delegator: 2,
1
					when_executable: 3,
1
					action: DelegationAction::Decrease(5),
1
				}],
1
			);
1
			assert_ok!(ParachainStaking::schedule_leave_candidates(
1
				RuntimeOrigin::signed(1),
1
				1u32
1
			));
			// candidate state is not immediately removed
1
			let candidate_state =
1
				ParachainStaking::candidate_info(1).expect("just left => still exists");
1
			assert_eq!(candidate_state.bond, 10u128);
1
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_leave_candidates(
1
				RuntimeOrigin::signed(1),
1
				1,
1
				1
1
			));
1
			assert!(ParachainStaking::candidate_info(1).is_none());
1
			assert!(
1
				!ParachainStaking::delegation_scheduled_requests(&1)
1
					.iter()
1
					.any(|x| x.delegator == 2),
				"delegation request not removed"
			);
1
			assert!(
1
				!<DelegationScheduledRequests<Test>>::contains_key(&1),
				"the key was not removed from storage"
			);
1
		});
1
}
#[test]
1
fn cannot_execute_leave_candidates_before_delay() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 10)])
1
		.with_candidates(vec![(1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::schedule_leave_candidates(
1
				RuntimeOrigin::signed(1),
1
				1u32
1
			));
1
			assert_noop!(
1
				ParachainStaking::execute_leave_candidates(RuntimeOrigin::signed(3), 1, 0)
1
					.map_err(|err| err.error),
1
				Error::<Test>::CandidateCannotLeaveYet
1
			);
1
			roll_to(9);
1
			assert_noop!(
1
				ParachainStaking::execute_leave_candidates(RuntimeOrigin::signed(3), 1, 0)
1
					.map_err(|err| err.error),
1
				Error::<Test>::CandidateCannotLeaveYet
1
			);
1
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_leave_candidates(
1
				RuntimeOrigin::signed(3),
1
				1,
1
				0
1
			));
1
		});
1
}
// CANCEL LEAVE CANDIDATES
#[test]
1
fn cancel_leave_candidates_emits_event() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 10)])
1
		.with_candidates(vec![(1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::schedule_leave_candidates(
1
				RuntimeOrigin::signed(1),
1
				1u32
1
			));
1
			assert_ok!(ParachainStaking::cancel_leave_candidates(
1
				RuntimeOrigin::signed(1),
1
				1
1
			));
1
			assert_events_emitted!(Event::CancelledCandidateExit { candidate: 1 });
1
		});
1
}
#[test]
1
fn cancel_leave_candidates_updates_candidate_state() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 10)])
1
		.with_candidates(vec![(1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::schedule_leave_candidates(
1
				RuntimeOrigin::signed(1),
1
				1u32
1
			));
1
			assert_ok!(ParachainStaking::cancel_leave_candidates(
1
				RuntimeOrigin::signed(1),
1
				1
1
			));
1
			let candidate =
1
				ParachainStaking::candidate_info(&1).expect("just cancelled leave so exists");
1
			assert!(candidate.is_active());
1
		});
1
}
#[test]
1
fn cancel_leave_candidates_adds_to_candidate_pool() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 10)])
1
		.with_candidates(vec![(1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::schedule_leave_candidates(
1
				RuntimeOrigin::signed(1),
1
				1u32
1
			));
1
			assert_ok!(ParachainStaking::cancel_leave_candidates(
1
				RuntimeOrigin::signed(1),
1
				1
1
			));
1
			assert_eq!(ParachainStaking::candidate_pool().0[0].owner, 1);
1
			assert_eq!(ParachainStaking::candidate_pool().0[0].amount, 10);
1
		});
1
}
// GO OFFLINE
#[test]
1
fn go_offline_event_emits_correctly() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 20)])
1
		.with_candidates(vec![(1, 20)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::go_offline(RuntimeOrigin::signed(1)));
1
			assert_events_eq!(Event::CandidateWentOffline { candidate: 1 });
1
		});
1
}
#[test]
1
fn go_offline_removes_candidate_from_candidate_pool() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 20)])
1
		.with_candidates(vec![(1, 20)])
1
		.build()
1
		.execute_with(|| {
1
			assert_eq!(ParachainStaking::candidate_pool().0.len(), 1);
1
			assert_ok!(ParachainStaking::go_offline(RuntimeOrigin::signed(1)));
1
			assert!(ParachainStaking::candidate_pool().0.is_empty());
1
		});
1
}
#[test]
1
fn go_offline_updates_candidate_state_to_idle() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 20)])
1
		.with_candidates(vec![(1, 20)])
1
		.build()
1
		.execute_with(|| {
1
			let candidate_state = ParachainStaking::candidate_info(1).expect("is active candidate");
1
			assert_eq!(candidate_state.status, CollatorStatus::Active);
1
			assert_ok!(ParachainStaking::go_offline(RuntimeOrigin::signed(1)));
1
			let candidate_state =
1
				ParachainStaking::candidate_info(1).expect("is candidate, just offline");
1
			assert_eq!(candidate_state.status, CollatorStatus::Idle);
1
		});
1
}
#[test]
1
fn cannot_go_offline_if_not_candidate() {
1
	ExtBuilder::default().build().execute_with(|| {
1
		assert_noop!(
1
			ParachainStaking::go_offline(RuntimeOrigin::signed(3)).map_err(|err| err.error),
1
			Error::<Test>::CandidateDNE
1
		);
1
	});
1
}
#[test]
1
fn cannot_go_offline_if_already_offline() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 20)])
1
		.with_candidates(vec![(1, 20)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::go_offline(RuntimeOrigin::signed(1)));
1
			assert_noop!(
1
				ParachainStaking::go_offline(RuntimeOrigin::signed(1)).map_err(|err| err.error),
1
				Error::<Test>::AlreadyOffline
1
			);
1
		});
1
}
// GO ONLINE
#[test]
1
fn go_online_event_emits_correctly() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 20)])
1
		.with_candidates(vec![(1, 20)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::go_offline(RuntimeOrigin::signed(1)));
1
			roll_blocks(1);
1
			assert_ok!(ParachainStaking::go_online(RuntimeOrigin::signed(1)));
1
			assert_events_eq!(Event::CandidateBackOnline { candidate: 1 });
1
		});
1
}
#[test]
1
fn go_online_adds_to_candidate_pool() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 20)])
1
		.with_candidates(vec![(1, 20)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::go_offline(RuntimeOrigin::signed(1)));
1
			assert!(ParachainStaking::candidate_pool().0.is_empty());
1
			assert_ok!(ParachainStaking::go_online(RuntimeOrigin::signed(1)));
1
			assert_eq!(ParachainStaking::candidate_pool().0[0].owner, 1);
1
			assert_eq!(ParachainStaking::candidate_pool().0[0].amount, 20);
1
		});
1
}
#[test]
1
fn go_online_storage_updates_candidate_state() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 20)])
1
		.with_candidates(vec![(1, 20)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::go_offline(RuntimeOrigin::signed(1)));
1
			let candidate_state =
1
				ParachainStaking::candidate_info(1).expect("offline still exists");
1
			assert_eq!(candidate_state.status, CollatorStatus::Idle);
1
			assert_ok!(ParachainStaking::go_online(RuntimeOrigin::signed(1)));
1
			let candidate_state = ParachainStaking::candidate_info(1).expect("online so exists");
1
			assert_eq!(candidate_state.status, CollatorStatus::Active);
1
		});
1
}
#[test]
1
fn cannot_go_online_if_not_candidate() {
1
	ExtBuilder::default().build().execute_with(|| {
1
		assert_noop!(
1
			ParachainStaking::go_online(RuntimeOrigin::signed(3)),
1
			Error::<Test>::CandidateDNE
1
		);
1
	});
1
}
#[test]
1
fn cannot_go_online_if_already_online() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 20)])
1
		.with_candidates(vec![(1, 20)])
1
		.build()
1
		.execute_with(|| {
1
			assert_noop!(
1
				ParachainStaking::go_online(RuntimeOrigin::signed(1)).map_err(|err| err.error),
1
				Error::<Test>::AlreadyActive
1
			);
1
		});
1
}
#[test]
1
fn cannot_go_online_if_leaving() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 20)])
1
		.with_candidates(vec![(1, 20)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::schedule_leave_candidates(
1
				RuntimeOrigin::signed(1),
1
				1
1
			));
1
			assert_noop!(
1
				ParachainStaking::go_online(RuntimeOrigin::signed(1)).map_err(|err| err.error),
1
				Error::<Test>::CannotGoOnlineIfLeaving
1
			);
1
		});
1
}
// CANDIDATE BOND MORE
#[test]
1
fn candidate_bond_more_emits_correct_event() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 50)])
1
		.with_candidates(vec![(1, 20)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::candidate_bond_more(
1
				RuntimeOrigin::signed(1),
1
				30
1
			));
1
			assert_events_eq!(Event::CandidateBondedMore {
1
				candidate: 1,
1
				amount: 30,
1
				new_total_bond: 50
1
			});
1
		});
1
}
#[test]
1
fn candidate_bond_more_reserves_balance() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 50)])
1
		.with_candidates(vec![(1, 20)])
1
		.build()
1
		.execute_with(|| {
1
			assert_eq!(ParachainStaking::get_collator_stakable_free_balance(&1), 30);
1
			assert_ok!(ParachainStaking::candidate_bond_more(
1
				RuntimeOrigin::signed(1),
1
				30
1
			));
1
			assert_eq!(ParachainStaking::get_collator_stakable_free_balance(&1), 0);
1
		});
1
}
#[test]
1
fn candidate_bond_more_increases_total() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 50)])
1
		.with_candidates(vec![(1, 20)])
1
		.build()
1
		.execute_with(|| {
1
			let mut total = ParachainStaking::total();
1
			assert_ok!(ParachainStaking::candidate_bond_more(
1
				RuntimeOrigin::signed(1),
1
				30
1
			));
1
			total += 30;
1
			assert_eq!(ParachainStaking::total(), total);
1
		});
1
}
#[test]
1
fn candidate_bond_more_updates_candidate_state() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 50)])
1
		.with_candidates(vec![(1, 20)])
1
		.build()
1
		.execute_with(|| {
1
			let candidate_state = ParachainStaking::candidate_info(1).expect("updated => exists");
1
			assert_eq!(candidate_state.bond, 20);
1
			assert_ok!(ParachainStaking::candidate_bond_more(
1
				RuntimeOrigin::signed(1),
1
				30
1
			));
1
			let candidate_state = ParachainStaking::candidate_info(1).expect("updated => exists");
1
			assert_eq!(candidate_state.bond, 50);
1
		});
1
}
#[test]
1
fn candidate_bond_more_updates_candidate_pool() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 50)])
1
		.with_candidates(vec![(1, 20)])
1
		.build()
1
		.execute_with(|| {
1
			assert_eq!(ParachainStaking::candidate_pool().0[0].owner, 1);
1
			assert_eq!(ParachainStaking::candidate_pool().0[0].amount, 20);
1
			assert_ok!(ParachainStaking::candidate_bond_more(
1
				RuntimeOrigin::signed(1),
1
				30
1
			));
1
			assert_eq!(ParachainStaking::candidate_pool().0[0].owner, 1);
1
			assert_eq!(ParachainStaking::candidate_pool().0[0].amount, 50);
1
		});
1
}
// SCHEDULE CANDIDATE BOND LESS
#[test]
1
fn schedule_candidate_bond_less_event_emits_correctly() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30)])
1
		.with_candidates(vec![(1, 30)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::schedule_candidate_bond_less(
1
				RuntimeOrigin::signed(1),
1
				10
1
			));
1
			assert_events_eq!(Event::CandidateBondLessRequested {
1
				candidate: 1,
1
				amount_to_decrease: 10,
1
				execute_round: 3,
1
			});
1
		});
1
}
#[test]
1
fn cannot_schedule_candidate_bond_less_if_request_exists() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30)])
1
		.with_candidates(vec![(1, 30)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::schedule_candidate_bond_less(
1
				RuntimeOrigin::signed(1),
1
				5
1
			));
1
			assert_noop!(
1
				ParachainStaking::schedule_candidate_bond_less(RuntimeOrigin::signed(1), 5),
1
				Error::<Test>::PendingCandidateRequestAlreadyExists
1
			);
1
		});
1
}
#[test]
1
fn cannot_schedule_candidate_bond_less_if_not_candidate() {
1
	ExtBuilder::default().build().execute_with(|| {
1
		assert_noop!(
1
			ParachainStaking::schedule_candidate_bond_less(RuntimeOrigin::signed(6), 50),
1
			Error::<Test>::CandidateDNE
1
		);
1
	});
1
}
#[test]
1
fn cannot_schedule_candidate_bond_less_if_new_total_below_min_candidate_stk() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30)])
1
		.with_candidates(vec![(1, 30)])
1
		.build()
1
		.execute_with(|| {
1
			assert_noop!(
1
				ParachainStaking::schedule_candidate_bond_less(RuntimeOrigin::signed(1), 21),
1
				Error::<Test>::CandidateBondBelowMin
1
			);
1
		});
1
}
#[test]
1
fn can_schedule_candidate_bond_less_if_leaving_candidates() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30)])
1
		.with_candidates(vec![(1, 30)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::schedule_leave_candidates(
1
				RuntimeOrigin::signed(1),
1
				1
1
			));
1
			assert_ok!(ParachainStaking::schedule_candidate_bond_less(
1
				RuntimeOrigin::signed(1),
1
				10
1
			));
1
		});
1
}
#[test]
1
fn cannot_schedule_candidate_bond_less_if_exited_candidates() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30)])
1
		.with_candidates(vec![(1, 30)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::schedule_leave_candidates(
1
				RuntimeOrigin::signed(1),
1
				1
1
			));
1
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_leave_candidates(
1
				RuntimeOrigin::signed(1),
1
				1,
1
				0
1
			));
1
			assert_noop!(
1
				ParachainStaking::schedule_candidate_bond_less(RuntimeOrigin::signed(1), 10),
1
				Error::<Test>::CandidateDNE
1
			);
1
		});
1
}
// 2. EXECUTE BOND LESS REQUEST
#[test]
1
fn execute_candidate_bond_less_emits_correct_event() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 50)])
1
		.with_candidates(vec![(1, 50)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::schedule_candidate_bond_less(
1
				RuntimeOrigin::signed(1),
1
				30
1
			));
1
			roll_to(10);
1
			roll_blocks(1);
1
			assert_ok!(ParachainStaking::execute_candidate_bond_less(
1
				RuntimeOrigin::signed(1),
1
				1
1
			));
1
			assert_events_eq!(Event::CandidateBondedLess {
1
				candidate: 1,
1
				amount: 30,
1
				new_bond: 20
1
			});
1
		});
1
}
#[test]
1
fn execute_candidate_bond_less_unreserves_balance() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30)])
1
		.with_candidates(vec![(1, 30)])
1
		.build()
1
		.execute_with(|| {
1
			assert_eq!(ParachainStaking::get_collator_stakable_free_balance(&1), 0);
1
			assert_ok!(ParachainStaking::schedule_candidate_bond_less(
1
				RuntimeOrigin::signed(1),
1
				10
1
			));
1
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_candidate_bond_less(
1
				RuntimeOrigin::signed(1),
1
				1
1
			));
1
			assert_eq!(ParachainStaking::get_collator_stakable_free_balance(&1), 10);
1
		});
1
}
#[test]
1
fn execute_candidate_bond_less_decreases_total() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30)])
1
		.with_candidates(vec![(1, 30)])
1
		.build()
1
		.execute_with(|| {
1
			let mut total = ParachainStaking::total();
1
			assert_ok!(ParachainStaking::schedule_candidate_bond_less(
1
				RuntimeOrigin::signed(1),
1
				10
1
			));
1
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_candidate_bond_less(
1
				RuntimeOrigin::signed(1),
1
				1
1
			));
1
			total -= 10;
1
			assert_eq!(ParachainStaking::total(), total);
1
		});
1
}
#[test]
1
fn execute_candidate_bond_less_updates_candidate_state() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30)])
1
		.with_candidates(vec![(1, 30)])
1
		.build()
1
		.execute_with(|| {
1
			let candidate_state = ParachainStaking::candidate_info(1).expect("updated => exists");
1
			assert_eq!(candidate_state.bond, 30);
1
			assert_ok!(ParachainStaking::schedule_candidate_bond_less(
1
				RuntimeOrigin::signed(1),
1
				10
1
			));
1
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_candidate_bond_less(
1
				RuntimeOrigin::signed(1),
1
				1
1
			));
1
			let candidate_state = ParachainStaking::candidate_info(1).expect("updated => exists");
1
			assert_eq!(candidate_state.bond, 20);
1
		});
1
}
#[test]
1
fn execute_candidate_bond_less_updates_candidate_pool() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30)])
1
		.with_candidates(vec![(1, 30)])
1
		.build()
1
		.execute_with(|| {
1
			assert_eq!(ParachainStaking::candidate_pool().0[0].owner, 1);
1
			assert_eq!(ParachainStaking::candidate_pool().0[0].amount, 30);
1
			assert_ok!(ParachainStaking::schedule_candidate_bond_less(
1
				RuntimeOrigin::signed(1),
1
				10
1
			));
1
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_candidate_bond_less(
1
				RuntimeOrigin::signed(1),
1
				1
1
			));
1
			assert_eq!(ParachainStaking::candidate_pool().0[0].owner, 1);
1
			assert_eq!(ParachainStaking::candidate_pool().0[0].amount, 20);
1
		});
1
}
// CANCEL CANDIDATE BOND LESS REQUEST
#[test]
1
fn cancel_candidate_bond_less_emits_event() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30)])
1
		.with_candidates(vec![(1, 30)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::schedule_candidate_bond_less(
1
				RuntimeOrigin::signed(1),
1
				10
1
			));
1
			assert_ok!(ParachainStaking::cancel_candidate_bond_less(
1
				RuntimeOrigin::signed(1)
1
			));
1
			assert_events_emitted!(Event::CancelledCandidateBondLess {
1
				candidate: 1,
1
				amount: 10,
1
				execute_round: 3,
1
			});
1
		});
1
}
#[test]
1
fn cancel_candidate_bond_less_updates_candidate_state() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30)])
1
		.with_candidates(vec![(1, 30)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::schedule_candidate_bond_less(
1
				RuntimeOrigin::signed(1),
1
				10
1
			));
1
			assert_ok!(ParachainStaking::cancel_candidate_bond_less(
1
				RuntimeOrigin::signed(1)
1
			));
1
			assert!(ParachainStaking::candidate_info(&1)
1
				.unwrap()
1
				.request
1
				.is_none());
1
		});
1
}
#[test]
1
fn only_candidate_can_cancel_candidate_bond_less_request() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30)])
1
		.with_candidates(vec![(1, 30)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::schedule_candidate_bond_less(
1
				RuntimeOrigin::signed(1),
1
				10
1
			));
1
			assert_noop!(
1
				ParachainStaking::cancel_candidate_bond_less(RuntimeOrigin::signed(2)),
1
				Error::<Test>::CandidateDNE
1
			);
1
		});
1
}
// DELEGATE
#[test]
1
fn delegate_event_emits_correctly() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 10)])
1
		.with_candidates(vec![(1, 30)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::delegate_with_auto_compound(
1
				RuntimeOrigin::signed(2),
1
				1,
1
				10,
1
				Percent::zero(),
1
				0,
1
				0,
1
				0
1
			));
1
			assert_events_eq!(Event::Delegation {
1
				delegator: 2,
1
				locked_amount: 10,
1
				candidate: 1,
1
				delegator_position: DelegatorAdded::AddedToTop { new_total: 40 },
1
				auto_compound: Percent::zero(),
1
			});
1
		});
1
}
#[test]
1
fn delegate_reserves_balance() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 10)])
1
		.with_candidates(vec![(1, 30)])
1
		.build()
1
		.execute_with(|| {
1
			assert_eq!(ParachainStaking::get_delegator_stakable_balance(&2), 10);
1
			assert_ok!(ParachainStaking::delegate_with_auto_compound(
1
				RuntimeOrigin::signed(2),
1
				1,
1
				10,
1
				Percent::zero(),
1
				0,
1
				0,
1
				0
1
			));
1
			assert_eq!(ParachainStaking::get_delegator_stakable_balance(&2), 0);
1
		});
1
}
#[test]
1
fn delegate_updates_delegator_state() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 10)])
1
		.with_candidates(vec![(1, 30)])
1
		.build()
1
		.execute_with(|| {
1
			assert!(ParachainStaking::delegator_state(2).is_none());
1
			assert_ok!(ParachainStaking::delegate_with_auto_compound(
1
				RuntimeOrigin::signed(2),
1
				1,
1
				10,
1
				Percent::zero(),
1
				0,
1
				0,
1
				0
1
			));
1
			let delegator_state =
1
				ParachainStaking::delegator_state(2).expect("just delegated => exists");
1
			assert_eq!(delegator_state.total(), 10);
1
			assert_eq!(delegator_state.delegations.0[0].owner, 1);
1
			assert_eq!(delegator_state.delegations.0[0].amount, 10);
1
		});
1
}
#[test]
1
fn delegate_updates_collator_state() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 10)])
1
		.with_candidates(vec![(1, 30)])
1
		.build()
1
		.execute_with(|| {
1
			let candidate_state =
1
				ParachainStaking::candidate_info(1).expect("registered in genesis");
1
			assert_eq!(candidate_state.total_counted, 30);
1
			let top_delegations =
1
				ParachainStaking::top_delegations(1).expect("registered in genesis");
1
			assert!(top_delegations.delegations.is_empty());
1
			assert!(top_delegations.total.is_zero());
1
			assert_ok!(ParachainStaking::delegate_with_auto_compound(
1
				RuntimeOrigin::signed(2),
1
				1,
1
				10,
1
				Percent::zero(),
1
				0,
1
				0,
1
				0
1
			));
1
			let candidate_state =
1
				ParachainStaking::candidate_info(1).expect("just delegated => exists");
1
			assert_eq!(candidate_state.total_counted, 40);
1
			let top_delegations =
1
				ParachainStaking::top_delegations(1).expect("just delegated => exists");
1
			assert_eq!(top_delegations.delegations[0].owner, 2);
1
			assert_eq!(top_delegations.delegations[0].amount, 10);
1
			assert_eq!(top_delegations.total, 10);
1
		});
1
}
#[test]
1
fn can_delegate_immediately_after_other_join_candidates() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 20), (2, 20)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::join_candidates(
1
				RuntimeOrigin::signed(1),
1
				20,
1
				0
1
			));
1
			assert_ok!(ParachainStaking::delegate_with_auto_compound(
1
				RuntimeOrigin::signed(2),
1
				1,
1
				20,
1
				Percent::zero(),
1
				0,
1
				0,
1
				0
1
			));
1
		});
1
}
#[test]
1
fn can_delegate_if_revoking() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 20), (2, 30), (3, 20), (4, 20)])
1
		.with_candidates(vec![(1, 20), (3, 20), (4, 20)])
1
		.with_delegations(vec![(2, 1, 10), (2, 3, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
1
				1
1
			));
1
			assert_ok!(ParachainStaking::delegate_with_auto_compound(
1
				RuntimeOrigin::signed(2),
1
				4,
1
				10,
1
				Percent::zero(),
1
				0,
1
				0,
1
				2
1
			));
1
		});
1
}
#[test]
1
fn cannot_delegate_if_full_and_new_delegation_less_than_or_equal_lowest_bottom() {
1
	ExtBuilder::default()
1
		.with_balances(vec![
1
			(1, 20),
1
			(2, 10),
1
			(3, 10),
1
			(4, 10),
1
			(5, 10),
1
			(6, 10),
1
			(7, 10),
1
			(8, 10),
1
			(9, 10),
1
			(10, 10),
1
			(11, 10),
1
		])
1
		.with_candidates(vec![(1, 20)])
1
		.with_delegations(vec![
1
			(2, 1, 10),
1
			(3, 1, 10),
1
			(4, 1, 10),
1
			(5, 1, 10),
1
			(6, 1, 10),
1
			(8, 1, 10),
1
			(9, 1, 10),
1
			(10, 1, 10),
1
		])
1
		.build()
1
		.execute_with(|| {
1
			assert_noop!(
1
				ParachainStaking::delegate_with_auto_compound(
1
					RuntimeOrigin::signed(11),
1
					1,
1
					10,
1
					Percent::zero(),
1
					8,
1
					0,
1
					0
1
				),
1
				Error::<Test>::CannotDelegateLessThanOrEqualToLowestBottomWhenFull
1
			);
1
		});
1
}
#[test]
1
fn can_delegate_if_full_and_new_delegation_greater_than_lowest_bottom() {
1
	ExtBuilder::default()
1
		.with_balances(vec![
1
			(1, 20),
1
			(2, 10),
1
			(3, 10),
1
			(4, 10),
1
			(5, 10),
1
			(6, 10),
1
			(7, 10),
1
			(8, 10),
1
			(9, 10),
1
			(10, 10),
1
			(11, 11),
1
		])
1
		.with_candidates(vec![(1, 20)])
1
		.with_delegations(vec![
1
			(2, 1, 10),
1
			(3, 1, 10),
1
			(4, 1, 10),
1
			(5, 1, 10),
1
			(6, 1, 10),
1
			(8, 1, 10),
1
			(9, 1, 10),
1
			(10, 1, 10),
1
		])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::delegate_with_auto_compound(
1
				RuntimeOrigin::signed(11),
1
				1,
1
				11,
1
				Percent::zero(),
1
				8,
1
				0,
1
				0
1
			));
1
			assert_events_emitted!(Event::DelegationKicked {
1
				delegator: 10,
1
				candidate: 1,
1
				unstaked_amount: 10
1
			});
1
			assert_events_emitted!(Event::DelegatorLeft {
1
				delegator: 10,
1
				unstaked_amount: 10
1
			});
1
		});
1
}
#[test]
1
fn can_still_delegate_if_leaving() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 20), (2, 20), (3, 20)])
1
		.with_candidates(vec![(1, 20), (3, 20)])
1
		.with_delegations(vec![(2, 1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
1
				1,
1
			));
1
			assert_ok!(ParachainStaking::delegate_with_auto_compound(
1
				RuntimeOrigin::signed(2),
1
				3,
1
				10,
1
				Percent::zero(),
1
				0,
1
				0,
1
				1
1
			));
1
		});
1
}
#[test]
1
fn cannot_delegate_if_candidate() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 20), (2, 30)])
1
		.with_candidates(vec![(1, 20), (2, 20)])
1
		.build()
1
		.execute_with(|| {
1
			assert_noop!(
1
				ParachainStaking::delegate_with_auto_compound(
1
					RuntimeOrigin::signed(2),
1
					1,
1
					10,
1
					Percent::zero(),
1
					0,
1
					0,
1
					0
1
				),
1
				Error::<Test>::CandidateExists
1
			);
1
		});
1
}
#[test]
1
fn cannot_delegate_if_already_delegated() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 20), (2, 30)])
1
		.with_candidates(vec![(1, 20)])
1
		.with_delegations(vec![(2, 1, 20)])
1
		.build()
1
		.execute_with(|| {
1
			assert_noop!(
1
				ParachainStaking::delegate_with_auto_compound(
1
					RuntimeOrigin::signed(2),
1
					1,
1
					10,
1
					Percent::zero(),
1
					1,
1
					0,
1
					1
1
				),
1
				Error::<Test>::AlreadyDelegatedCandidate
1
			);
1
		});
1
}
#[test]
1
fn cannot_delegate_more_than_max_delegations() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 20), (2, 50), (3, 20), (4, 20), (5, 20), (6, 20)])
1
		.with_candidates(vec![(1, 20), (3, 20), (4, 20), (5, 20), (6, 20)])
1
		.with_delegations(vec![(2, 1, 10), (2, 3, 10), (2, 4, 10), (2, 5, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_noop!(
1
				ParachainStaking::delegate_with_auto_compound(
1
					RuntimeOrigin::signed(2),
1
					6,
1
					10,
1
					Percent::zero(),
1
					0,
1
					0,
1
					4,
1
				),
1
				Error::<Test>::ExceedMaxDelegationsPerDelegator,
1
			);
1
		});
1
}
#[test]
1
fn sufficient_delegate_weight_hint_succeeds() {
1
	ExtBuilder::default()
1
		.with_balances(vec![
1
			(1, 20),
1
			(2, 20),
1
			(3, 20),
1
			(4, 20),
1
			(5, 20),
1
			(6, 20),
1
			(7, 20),
1
			(8, 20),
1
			(9, 20),
1
			(10, 20),
1
		])
1
		.with_candidates(vec![(1, 20), (2, 20)])
1
		.with_delegations(vec![(3, 1, 10), (4, 1, 10), (5, 1, 10), (6, 1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			let mut count = 4u32;
5
			for i in 7..11 {
4
				assert_ok!(ParachainStaking::delegate_with_auto_compound(
4
					RuntimeOrigin::signed(i),
4
					1,
4
					10,
4
					Percent::zero(),
4
					count,
4
					0,
4
					0
4
				));
4
				count += 1u32;
			}
1
			let mut count = 0u32;
9
			for i in 3..11 {
8
				assert_ok!(ParachainStaking::delegate_with_auto_compound(
8
					RuntimeOrigin::signed(i),
8
					2,
8
					10,
8
					Percent::zero(),
8
					count,
8
					0,
8
					1u32
8
				));
8
				count += 1u32;
			}
1
		});
1
}
#[test]
1
fn insufficient_delegate_weight_hint_fails() {
1
	ExtBuilder::default()
1
		.with_balances(vec![
1
			(1, 20),
1
			(2, 20),
1
			(3, 20),
1
			(4, 20),
1
			(5, 20),
1
			(6, 20),
1
			(7, 20),
1
			(8, 20),
1
			(9, 20),
1
			(10, 20),
1
		])
1
		.with_candidates(vec![(1, 20), (2, 20)])
1
		.with_delegations(vec![(3, 1, 10), (4, 1, 10), (5, 1, 10), (6, 1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			let mut count = 3u32;
5
			for i in 7..11 {
4
				assert_noop!(
4
					ParachainStaking::delegate_with_auto_compound(
4
						RuntimeOrigin::signed(i),
4
						1,
4
						10,
4
						Percent::zero(),
4
						count,
4
						0,
4
						0
4
					),
4
					Error::<Test>::TooLowCandidateDelegationCountToDelegate
4
				);
			}
			// to set up for next error test
1
			count = 4u32;
5
			for i in 7..11 {
4
				assert_ok!(ParachainStaking::delegate_with_auto_compound(
4
					RuntimeOrigin::signed(i),
4
					1,
4
					10,
4
					Percent::zero(),
4
					count,
4
					0,
4
					0
4
				));
4
				count += 1u32;
			}
1
			count = 0u32;
9
			for i in 3..11 {
8
				assert_noop!(
8
					ParachainStaking::delegate_with_auto_compound(
8
						RuntimeOrigin::signed(i),
8
						2,
8
						10,
8
						Percent::zero(),
8
						count,
8
						0,
8
						0
8
					),
8
					Error::<Test>::TooLowDelegationCountToDelegate
8
				);
8
				count += 1u32;
			}
1
		});
1
}
// SCHEDULE REVOKE DELEGATION
#[test]
1
fn revoke_delegation_event_emits_correctly() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 20), (3, 30)])
1
		.with_candidates(vec![(1, 30), (3, 30)])
1
		.with_delegations(vec![(2, 1, 10), (2, 3, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
1
				1
1
			));
1
			assert_events_eq!(Event::DelegationRevocationScheduled {
1
				round: 1,
1
				delegator: 2,
1
				candidate: 1,
1
				scheduled_exit: 3,
1
			});
1
			roll_to_round_begin(3);
1
			roll_blocks(1);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
1
				2,
1
				1
1
			));
1
			assert_events_eq!(
1
				Event::DelegatorLeftCandidate {
1
					delegator: 2,
1
					candidate: 1,
1
					unstaked_amount: 10,
1
					total_candidate_staked: 30
1
				},
1
				Event::DelegationRevoked {
1
					delegator: 2,
1
					candidate: 1,
1
					unstaked_amount: 10,
1
				},
1
			);
1
		});
1
}
#[test]
1
fn can_revoke_delegation_if_revoking_another_delegation() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 20), (3, 20)])
1
		.with_candidates(vec![(1, 30), (3, 20)])
1
		.with_delegations(vec![(2, 1, 10), (2, 3, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
1
				1
1
			));
			// this is an exit implicitly because last delegation revoked
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
1
				3
1
			));
1
		});
1
}
#[test]
1
fn cannot_revoke_delegation_if_not_delegator() {
1
	ExtBuilder::default().build().execute_with(|| {
1
		assert_noop!(
1
			ParachainStaking::schedule_revoke_delegation(RuntimeOrigin::signed(2), 1),
1
			Error::<Test>::DelegatorDNE
1
		);
1
	});
1
}
#[test]
1
fn cannot_revoke_delegation_that_dne() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 10)])
1
		.with_candidates(vec![(1, 30)])
1
		.with_delegations(vec![(2, 1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_noop!(
1
				ParachainStaking::schedule_revoke_delegation(RuntimeOrigin::signed(2), 3),
1
				Error::<Test>::DelegationDNE
1
			);
1
		});
1
}
#[test]
1
fn can_schedule_revoke_delegation_below_min_delegator_stake() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 20), (2, 8), (3, 20)])
1
		.with_candidates(vec![(1, 20), (3, 20)])
1
		.with_delegations(vec![(2, 1, 5), (2, 3, 3)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
1
				1
1
			));
1
		});
1
}
// DELEGATOR BOND MORE
#[test]
1
fn delegator_bond_more_reserves_balance() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 15)])
1
		.with_candidates(vec![(1, 30)])
1
		.with_delegations(vec![(2, 1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_eq!(ParachainStaking::get_delegator_stakable_balance(&2), 5);
1
			assert_ok!(ParachainStaking::delegator_bond_more(
1
				RuntimeOrigin::signed(2),
1
				1,
1
				5
1
			));
1
			assert_eq!(ParachainStaking::get_delegator_stakable_balance(&2), 0);
1
		});
1
}
#[test]
1
fn delegator_bond_more_increases_total_staked() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 15)])
1
		.with_candidates(vec![(1, 30)])
1
		.with_delegations(vec![(2, 1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_eq!(ParachainStaking::total(), 40);
1
			assert_ok!(ParachainStaking::delegator_bond_more(
1
				RuntimeOrigin::signed(2),
1
				1,
1
				5
1
			));
1
			assert_eq!(ParachainStaking::total(), 45);
1
		});
1
}
#[test]
1
fn delegator_bond_more_updates_delegator_state() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 15)])
1
		.with_candidates(vec![(1, 30)])
1
		.with_delegations(vec![(2, 1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_eq!(
1
				ParachainStaking::delegator_state(2)
1
					.expect("exists")
1
					.total(),
1
				10
1
			);
1
			assert_ok!(ParachainStaking::delegator_bond_more(
1
				RuntimeOrigin::signed(2),
1
				1,
1
				5
1
			));
1
			assert_eq!(
1
				ParachainStaking::delegator_state(2)
1
					.expect("exists")
1
					.total(),
1
				15
1
			);
1
		});
1
}
#[test]
1
fn delegator_bond_more_updates_candidate_state_top_delegations() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 15)])
1
		.with_candidates(vec![(1, 30)])
1
		.with_delegations(vec![(2, 1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_eq!(
1
				ParachainStaking::top_delegations(1).unwrap().delegations[0].owner,
1
				2
1
			);
1
			assert_eq!(
1
				ParachainStaking::top_delegations(1).unwrap().delegations[0].amount,
1
				10
1
			);
1
			assert_eq!(ParachainStaking::top_delegations(1).unwrap().total, 10);
1
			assert_ok!(ParachainStaking::delegator_bond_more(
1
				RuntimeOrigin::signed(2),
1
				1,
1
				5
1
			));
1
			assert_eq!(
1
				ParachainStaking::top_delegations(1).unwrap().delegations[0].owner,
1
				2
1
			);
1
			assert_eq!(
1
				ParachainStaking::top_delegations(1).unwrap().delegations[0].amount,
1
				15
1
			);
1
			assert_eq!(ParachainStaking::top_delegations(1).unwrap().total, 15);
1
		});
1
}
#[test]
1
fn delegator_bond_more_updates_candidate_state_bottom_delegations() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 20), (3, 20), (4, 20), (5, 20), (6, 20)])
1
		.with_candidates(vec![(1, 30)])
1
		.with_delegations(vec![
1
			(2, 1, 10),
1
			(3, 1, 20),
1
			(4, 1, 20),
1
			(5, 1, 20),
1
			(6, 1, 20),
1
		])
1
		.build()
1
		.execute_with(|| {
1
			assert_eq!(
1
				ParachainStaking::bottom_delegations(1)
1
					.expect("exists")
1
					.delegations[0]
1
					.owner,
1
				2
1
			);
1
			assert_eq!(
1
				ParachainStaking::bottom_delegations(1)
1
					.expect("exists")
1
					.delegations[0]
1
					.amount,
1
				10
1
			);
1
			assert_eq!(ParachainStaking::bottom_delegations(1).unwrap().total, 10);
1
			assert_ok!(ParachainStaking::delegator_bond_more(
1
				RuntimeOrigin::signed(2),
1
				1,
1
				5
1
			));
1
			assert_events_eq!(Event::DelegationIncreased {
1
				delegator: 2,
1
				candidate: 1,
1
				amount: 5,
1
				in_top: false
1
			});
1
			assert_eq!(
1
				ParachainStaking::bottom_delegations(1)
1
					.expect("exists")
1
					.delegations[0]
1
					.owner,
1
				2
1
			);
1
			assert_eq!(
1
				ParachainStaking::bottom_delegations(1)
1
					.expect("exists")
1
					.delegations[0]
1
					.amount,
1
				15
1
			);
1
			assert_eq!(ParachainStaking::bottom_delegations(1).unwrap().total, 15);
1
		});
1
}
#[test]
1
fn delegator_bond_more_increases_total() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 15)])
1
		.with_candidates(vec![(1, 30)])
1
		.with_delegations(vec![(2, 1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_eq!(ParachainStaking::total(), 40);
1
			assert_ok!(ParachainStaking::delegator_bond_more(
1
				RuntimeOrigin::signed(2),
1
				1,
1
				5
1
			));
1
			assert_eq!(ParachainStaking::total(), 45);
1
		});
1
}
#[test]
1
fn can_delegator_bond_more_for_leaving_candidate() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 15)])
1
		.with_candidates(vec![(1, 30)])
1
		.with_delegations(vec![(2, 1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::schedule_leave_candidates(
1
				RuntimeOrigin::signed(1),
1
				1
1
			));
1
			assert_ok!(ParachainStaking::delegator_bond_more(
1
				RuntimeOrigin::signed(2),
1
				1,
1
				5
1
			));
1
		});
1
}
#[test]
1
fn delegator_bond_more_disallowed_when_revoke_scheduled() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 25)])
1
		.with_candidates(vec![(1, 30)])
1
		.with_delegations(vec![(2, 1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
1
				1
1
			));
1
			assert_noop!(
1
				ParachainStaking::delegator_bond_more(RuntimeOrigin::signed(2), 1, 5),
1
				<Error<Test>>::PendingDelegationRevoke
1
			);
1
		});
1
}
#[test]
1
fn delegator_bond_more_allowed_when_bond_decrease_scheduled() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 25)])
1
		.with_candidates(vec![(1, 30)])
1
		.with_delegations(vec![(2, 1, 15)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::schedule_delegator_bond_less(
1
				RuntimeOrigin::signed(2),
1
				1,
1
				5,
1
			));
1
			assert_ok!(ParachainStaking::delegator_bond_more(
1
				RuntimeOrigin::signed(2),
1
				1,
1
				5
1
			));
1
		});
1
}
// DELEGATOR BOND LESS
#[test]
1
fn delegator_bond_less_event_emits_correctly() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 10)])
1
		.with_candidates(vec![(1, 30)])
1
		.with_delegations(vec![(2, 1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::schedule_delegator_bond_less(
1
				RuntimeOrigin::signed(2),
1
				1,
1
				5
1
			));
1
			assert_events_eq!(Event::DelegationDecreaseScheduled {
1
				delegator: 2,
1
				candidate: 1,
1
				amount_to_decrease: 5,
1
				execute_round: 3,
1
			});
1
		});
1
}
#[test]
1
fn delegator_bond_less_updates_delegator_state() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 10)])
1
		.with_candidates(vec![(1, 30)])
1
		.with_delegations(vec![(2, 1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::schedule_delegator_bond_less(
1
				RuntimeOrigin::signed(2),
1
				1,
1
				5
1
			));
1
			let state = ParachainStaking::delegation_scheduled_requests(&1);
1
			assert_eq!(
1
				state,
1
				vec![ScheduledRequest {
1
					delegator: 2,
1
					when_executable: 3,
1
					action: DelegationAction::Decrease(5),
1
				}],
1
			);
1
		});
1
}
#[test]
1
fn cannot_delegator_bond_less_if_revoking() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 25), (3, 20)])
1
		.with_candidates(vec![(1, 30), (3, 20)])
1
		.with_delegations(vec![(2, 1, 10), (2, 3, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
1
				1
1
			));
1
			assert_noop!(
1
				ParachainStaking::schedule_delegator_bond_less(RuntimeOrigin::signed(2), 1, 1)
1
					.map_err(|err| err.error),
1
				Error::<Test>::PendingDelegationRequestAlreadyExists
1
			);
1
		});
1
}
#[test]
1
fn cannot_delegator_bond_less_if_not_delegator() {
1
	ExtBuilder::default().build().execute_with(|| {
1
		assert_noop!(
1
			ParachainStaking::schedule_delegator_bond_less(RuntimeOrigin::signed(2), 1, 5)
1
				.map_err(|err| err.error),
1
			Error::<Test>::DelegatorDNE
1
		);
1
	});
1
}
#[test]
1
fn cannot_delegator_bond_less_if_candidate_dne() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 10)])
1
		.with_candidates(vec![(1, 30)])
1
		.with_delegations(vec![(2, 1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_noop!(
1
				ParachainStaking::schedule_delegator_bond_less(RuntimeOrigin::signed(2), 3, 5)
1
					.map_err(|err| err.error),
1
				Error::<Test>::DelegationDNE
1
			);
1
		});
1
}
#[test]
1
fn cannot_delegator_bond_less_if_delegation_dne() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 10), (3, 30)])
1
		.with_candidates(vec![(1, 30), (3, 30)])
1
		.with_delegations(vec![(2, 1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_noop!(
1
				ParachainStaking::schedule_delegator_bond_less(RuntimeOrigin::signed(2), 3, 5)
1
					.map_err(|err| err.error),
1
				Error::<Test>::DelegationDNE
1
			);
1
		});
1
}
#[test]
1
fn cannot_delegator_bond_less_more_than_total_delegation() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 10)])
1
		.with_candidates(vec![(1, 30)])
1
		.with_delegations(vec![(2, 1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_noop!(
1
				ParachainStaking::schedule_delegator_bond_less(RuntimeOrigin::signed(2), 1, 11)
1
					.map_err(|err| err.error),
1
				Error::<Test>::DelegatorBondBelowMin
1
			);
1
		});
1
}
#[test]
1
fn cannot_delegator_bond_less_below_min_delegation() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 20), (3, 30)])
1
		.with_candidates(vec![(1, 30), (3, 30)])
1
		.with_delegations(vec![(2, 1, 10), (2, 3, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_noop!(
1
				ParachainStaking::schedule_delegator_bond_less(RuntimeOrigin::signed(2), 1, 8)
1
					.map_err(|err| err.error),
1
				Error::<Test>::DelegationBelowMin
1
			);
1
		});
1
}
// EXECUTE PENDING DELEGATION REQUEST
// 1. REVOKE DELEGATION
#[test]
1
fn execute_revoke_delegation_emits_exit_event_if_exit_happens() {
1
	// last delegation is revocation
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 10)])
1
		.with_candidates(vec![(1, 30)])
1
		.with_delegations(vec![(2, 1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
1
				1
1
			));
1
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
1
				2,
1
				1
1
			));
1
			assert_events_emitted!(Event::DelegatorLeftCandidate {
1
				delegator: 2,
1
				candidate: 1,
1
				unstaked_amount: 10,
1
				total_candidate_staked: 30
1
			});
1
			assert_events_emitted!(Event::DelegatorLeft {
1
				delegator: 2,
1
				unstaked_amount: 10
1
			});
1
		});
1
}
#[test]
1
fn revoke_delegation_executes_exit_if_last_delegation() {
1
	// last delegation is revocation
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 10)])
1
		.with_candidates(vec![(1, 30)])
1
		.with_delegations(vec![(2, 1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
1
				1
1
			));
1
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
1
				2,
1
				1
1
			));
1
			assert_events_emitted!(Event::DelegatorLeftCandidate {
1
				delegator: 2,
1
				candidate: 1,
1
				unstaked_amount: 10,
1
				total_candidate_staked: 30
1
			});
1
			assert_events_emitted!(Event::DelegatorLeft {
1
				delegator: 2,
1
				unstaked_amount: 10
1
			});
1
		});
1
}
#[test]
1
fn execute_revoke_delegation_emits_correct_event() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 20), (3, 30)])
1
		.with_candidates(vec![(1, 30), (3, 30)])
1
		.with_delegations(vec![(2, 1, 10), (2, 3, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
1
				1
1
			));
1
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
1
				2,
1
				1
1
			));
1
			assert_events_emitted!(Event::DelegatorLeftCandidate {
1
				delegator: 2,
1
				candidate: 1,
1
				unstaked_amount: 10,
1
				total_candidate_staked: 30
1
			});
1
		});
1
}
#[test]
1
fn execute_revoke_delegation_unreserves_balance() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 10)])
1
		.with_candidates(vec![(1, 30)])
1
		.with_delegations(vec![(2, 1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_eq!(ParachainStaking::get_delegator_stakable_balance(&2), 0);
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
1
				1
1
			));
1
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
1
				2,
1
				1
1
			));
1
			assert_eq!(ParachainStaking::get_delegator_stakable_balance(&2), 10);
1
		});
1
}
#[test]
1
fn execute_revoke_delegation_adds_revocation_to_delegator_state() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 20), (3, 20)])
1
		.with_candidates(vec![(1, 30), (3, 20)])
1
		.with_delegations(vec![(2, 1, 10), (2, 3, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert!(!ParachainStaking::delegation_scheduled_requests(&1)
1
				.iter()
1
				.any(|x| x.delegator == 2));
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
1
				1
1
			));
1
			assert!(ParachainStaking::delegation_scheduled_requests(&1)
1
				.iter()
1
				.any(|x| x.delegator == 2));
1
		});
1
}
#[test]
1
fn execute_revoke_delegation_removes_revocation_from_delegator_state_upon_execution() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 20), (3, 20)])
1
		.with_candidates(vec![(1, 30), (3, 20)])
1
		.with_delegations(vec![(2, 1, 10), (2, 3, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
1
				1
1
			));
1
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
1
				2,
1
				1
1
			));
1
			assert!(!ParachainStaking::delegation_scheduled_requests(&1)
1
				.iter()
1
				.any(|x| x.delegator == 2));
1
		});
1
}
#[test]
1
fn execute_revoke_delegation_removes_revocation_from_state_for_single_delegation_leave() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 20), (3, 20)])
1
		.with_candidates(vec![(1, 30), (3, 20)])
1
		.with_delegations(vec![(2, 1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
1
				1
1
			));
1
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
1
				2,
1
				1
1
			));
1
			assert!(
1
				!ParachainStaking::delegation_scheduled_requests(&1)
1
					.iter()
1
					.any(|x| x.delegator == 2),
				"delegation was not removed"
			);
1
		});
1
}
#[test]
1
fn execute_revoke_delegation_decreases_total_staked() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 10)])
1
		.with_candidates(vec![(1, 30)])
1
		.with_delegations(vec![(2, 1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_eq!(ParachainStaking::total(), 40);
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
1
				1
1
			));
1
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
1
				2,
1
				1
1
			));
1
			assert_eq!(ParachainStaking::total(), 30);
1
		});
1
}
#[test]
1
fn execute_revoke_delegation_for_last_delegation_removes_delegator_state() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 10)])
1
		.with_candidates(vec![(1, 30)])
1
		.with_delegations(vec![(2, 1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert!(ParachainStaking::delegator_state(2).is_some());
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
1
				1
1
			));
1
			roll_to(10);
1
			// this will be confusing for people
1
			// if status is leaving, then execute_delegation_request works if last delegation
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
1
				2,
1
				1
1
			));
1
			assert!(ParachainStaking::delegator_state(2).is_none());
1
		});
1
}
#[test]
1
fn execute_revoke_delegation_removes_delegation_from_candidate_state() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 10)])
1
		.with_candidates(vec![(1, 30)])
1
		.with_delegations(vec![(2, 1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_eq!(
1
				ParachainStaking::candidate_info(1)
1
					.expect("exists")
1
					.delegation_count,
1
				1u32
1
			);
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
1
				1
1
			));
1
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
1
				2,
1
				1
1
			));
1
			assert!(ParachainStaking::candidate_info(1)
1
				.expect("exists")
1
				.delegation_count
1
				.is_zero());
1
		});
1
}
#[test]
1
fn can_execute_revoke_delegation_for_leaving_candidate() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 10)])
1
		.with_candidates(vec![(1, 30)])
1
		.with_delegations(vec![(2, 1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::schedule_leave_candidates(
1
				RuntimeOrigin::signed(1),
1
				1
1
			));
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
1
				1
1
			));
1
			roll_to(10);
1
			// can execute delegation request for leaving candidate
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
1
				2,
1
				1
1
			));
1
		});
1
}
#[test]
1
fn can_execute_leave_candidates_if_revoking_candidate() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 10)])
1
		.with_candidates(vec![(1, 30)])
1
		.with_delegations(vec![(2, 1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::schedule_leave_candidates(
1
				RuntimeOrigin::signed(1),
1
				1
1
			));
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
1
				1
1
			));
1
			roll_to(10);
1
			// revocation executes during execute leave candidates (callable by anyone)
1
			assert_ok!(ParachainStaking::execute_leave_candidates(
1
				RuntimeOrigin::signed(1),
1
				1,
1
				1
1
			));
1
			assert!(!ParachainStaking::is_delegator(&2));
1
			assert_eq!(Balances::reserved_balance(&2), 0);
1
			assert_eq!(Balances::free_balance(&2), 10);
1
		});
1
}
#[test]
1
fn delegator_bond_more_after_revoke_delegation_does_not_effect_exit() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 30), (3, 30)])
1
		.with_candidates(vec![(1, 30), (3, 30)])
1
		.with_delegations(vec![(2, 1, 10), (2, 3, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
1
				1
1
			));
1
			assert_ok!(ParachainStaking::delegator_bond_more(
1
				RuntimeOrigin::signed(2),
1
				3,
1
				10
1
			));
1
			roll_to(100);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
1
				2,
1
				1
1
			));
1
			assert!(ParachainStaking::is_delegator(&2));
1
			assert_eq!(ParachainStaking::get_delegator_stakable_balance(&2), 10);
1
		});
1
}
#[test]
1
fn delegator_bond_less_after_revoke_delegation_does_not_effect_exit() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 30), (3, 30)])
1
		.with_candidates(vec![(1, 30), (3, 30)])
1
		.with_delegations(vec![(2, 1, 10), (2, 3, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
1
				1
1
			));
1
			assert_events_eq!(Event::DelegationRevocationScheduled {
1
				round: 1,
1
				delegator: 2,
1
				candidate: 1,
1
				scheduled_exit: 3,
1
			});
1
			assert_noop!(
1
				ParachainStaking::schedule_delegator_bond_less(RuntimeOrigin::signed(2), 1, 2)
1
					.map_err(|err| err.error),
1
				Error::<Test>::PendingDelegationRequestAlreadyExists
1
			);
1
			assert_ok!(ParachainStaking::schedule_delegator_bond_less(
1
				RuntimeOrigin::signed(2),
1
				3,
1
				2
1
			));
1
			roll_to(10);
1
			roll_blocks(1);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
1
				2,
1
				1
1
			));
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
1
				2,
1
				3
1
			));
1
			assert_events_eq!(
1
				Event::DelegatorLeftCandidate {
1
					delegator: 2,
1
					candidate: 1,
1
					unstaked_amount: 10,
1
					total_candidate_staked: 30,
1
				},
1
				Event::DelegationRevoked {
1
					delegator: 2,
1
					candidate: 1,
1
					unstaked_amount: 10,
1
				},
1
				Event::DelegationDecreased {
1
					delegator: 2,
1
					candidate: 3,
1
					amount: 2,
1
					in_top: true
1
				},
1
			);
1
			assert!(ParachainStaking::is_delegator(&2));
1
			assert_eq!(ParachainStaking::get_delegator_stakable_balance(&2), 22);
1
		});
1
}
// 2. EXECUTE BOND LESS
#[test]
1
fn execute_delegator_bond_less_unreserves_balance() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 10)])
1
		.with_candidates(vec![(1, 30)])
1
		.with_delegations(vec![(2, 1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_eq!(ParachainStaking::get_delegator_stakable_balance(&2), 0);
1
			assert_ok!(ParachainStaking::schedule_delegator_bond_less(
1
				RuntimeOrigin::signed(2),
1
				1,
1
				5
1
			));
1
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
1
				2,
1
				1
1
			));
1
			assert_eq!(ParachainStaking::get_delegator_stakable_balance(&2), 5);
1
		});
1
}
#[test]
1
fn execute_delegator_bond_less_decreases_total_staked() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 10)])
1
		.with_candidates(vec![(1, 30)])
1
		.with_delegations(vec![(2, 1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_eq!(ParachainStaking::total(), 40);
1
			assert_ok!(ParachainStaking::schedule_delegator_bond_less(
1
				RuntimeOrigin::signed(2),
1
				1,
1
				5
1
			));
1
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
1
				2,
1
				1
1
			));
1
			assert_eq!(ParachainStaking::total(), 35);
1
		});
1
}
#[test]
1
fn execute_delegator_bond_less_updates_delegator_state() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 15)])
1
		.with_candidates(vec![(1, 30)])
1
		.with_delegations(vec![(2, 1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_eq!(
1
				ParachainStaking::delegator_state(2)
1
					.expect("exists")
1
					.total(),
1
				10
1
			);
1
			assert_ok!(ParachainStaking::schedule_delegator_bond_less(
1
				RuntimeOrigin::signed(2),
1
				1,
1
				5
1
			));
1
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
1
				2,
1
				1
1
			));
1
			assert_eq!(
1
				ParachainStaking::delegator_state(2)
1
					.expect("exists")
1
					.total(),
1
				5
1
			);
1
		});
1
}
#[test]
1
fn execute_delegator_bond_less_updates_candidate_state() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 15)])
1
		.with_candidates(vec![(1, 30)])
1
		.with_delegations(vec![(2, 1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_eq!(
1
				ParachainStaking::top_delegations(1).unwrap().delegations[0].owner,
1
				2
1
			);
1
			assert_eq!(
1
				ParachainStaking::top_delegations(1).unwrap().delegations[0].amount,
1
				10
1
			);
1
			assert_ok!(ParachainStaking::schedule_delegator_bond_less(
1
				RuntimeOrigin::signed(2),
1
				1,
1
				5
1
			));
1
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
1
				2,
1
				1
1
			));
1
			assert_eq!(
1
				ParachainStaking::top_delegations(1).unwrap().delegations[0].owner,
1
				2
1
			);
1
			assert_eq!(
1
				ParachainStaking::top_delegations(1).unwrap().delegations[0].amount,
1
				5
1
			);
1
		});
1
}
#[test]
1
fn execute_delegator_bond_less_decreases_total() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 15)])
1
		.with_candidates(vec![(1, 30)])
1
		.with_delegations(vec![(2, 1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_eq!(ParachainStaking::total(), 40);
1
			assert_ok!(ParachainStaking::schedule_delegator_bond_less(
1
				RuntimeOrigin::signed(2),
1
				1,
1
				5
1
			));
1
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
1
				2,
1
				1
1
			));
1
			assert_eq!(ParachainStaking::total(), 35);
1
		});
1
}
#[test]
1
fn execute_delegator_bond_less_updates_just_bottom_delegations() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 20), (2, 10), (3, 11), (4, 12), (5, 14), (6, 15)])
1
		.with_candidates(vec![(1, 20)])
1
		.with_delegations(vec![
1
			(2, 1, 10),
1
			(3, 1, 11),
1
			(4, 1, 12),
1
			(5, 1, 14),
1
			(6, 1, 15),
1
		])
1
		.build()
1
		.execute_with(|| {
1
			let pre_call_candidate_info =
1
				ParachainStaking::candidate_info(&1).expect("delegated by all so exists");
1
			let pre_call_top_delegations =
1
				ParachainStaking::top_delegations(&1).expect("delegated by all so exists");
1
			let pre_call_bottom_delegations =
1
				ParachainStaking::bottom_delegations(&1).expect("delegated by all so exists");
1
			assert_ok!(ParachainStaking::schedule_delegator_bond_less(
1
				RuntimeOrigin::signed(2),
1
				1,
1
				2
1
			));
1
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
1
				2,
1
				1
1
			));
1
			let post_call_candidate_info =
1
				ParachainStaking::candidate_info(&1).expect("delegated by all so exists");
1
			let post_call_top_delegations =
1
				ParachainStaking::top_delegations(&1).expect("delegated by all so exists");
1
			let post_call_bottom_delegations =
1
				ParachainStaking::bottom_delegations(&1).expect("delegated by all so exists");
1
			let mut not_equal = false;
2
			for Bond { owner, amount } in pre_call_bottom_delegations.delegations {
				for Bond {
1
					owner: post_owner,
1
					amount: post_amount,
1
				} in &post_call_bottom_delegations.delegations
				{
1
					if &owner == post_owner {
1
						if &amount != post_amount {
1
							not_equal = true;
1
							break;
						}
					}
				}
			}
1
			assert!(not_equal);
1
			let mut equal = true;
5
			for Bond { owner, amount } in pre_call_top_delegations.delegations {
				for Bond {
16
					owner: post_owner,
16
					amount: post_amount,
20
				} in &post_call_top_delegations.delegations
				{
16
					if &owner == post_owner {
4
						if &amount != post_amount {
							equal = false;
							break;
4
						}
12
					}
				}
			}
1
			assert!(equal);
1
			assert_eq!(
1
				pre_call_candidate_info.total_counted,
1
				post_call_candidate_info.total_counted
1
			);
1
		});
1
}
#[test]
1
fn execute_delegator_bond_less_does_not_delete_bottom_delegations() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 20), (2, 10), (3, 11), (4, 12), (5, 14), (6, 15)])
1
		.with_candidates(vec![(1, 20)])
1
		.with_delegations(vec![
1
			(2, 1, 10),
1
			(3, 1, 11),
1
			(4, 1, 12),
1
			(5, 1, 14),
1
			(6, 1, 15),
1
		])
1
		.build()
1
		.execute_with(|| {
1
			let pre_call_candidate_info =
1
				ParachainStaking::candidate_info(&1).expect("delegated by all so exists");
1
			let pre_call_top_delegations =
1
				ParachainStaking::top_delegations(&1).expect("delegated by all so exists");
1
			let pre_call_bottom_delegations =
1
				ParachainStaking::bottom_delegations(&1).expect("delegated by all so exists");
1
			assert_ok!(ParachainStaking::schedule_delegator_bond_less(
1
				RuntimeOrigin::signed(6),
1
				1,
1
				4
1
			));
1
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(6),
1
				6,
1
				1
1
			));
1
			let post_call_candidate_info =
1
				ParachainStaking::candidate_info(&1).expect("delegated by all so exists");
1
			let post_call_top_delegations =
1
				ParachainStaking::top_delegations(&1).expect("delegated by all so exists");
1
			let post_call_bottom_delegations =
1
				ParachainStaking::bottom_delegations(&1).expect("delegated by all so exists");
1
			let mut equal = true;
2
			for Bond { owner, amount } in pre_call_bottom_delegations.delegations {
				for Bond {
1
					owner: post_owner,
1
					amount: post_amount,
2
				} in &post_call_bottom_delegations.delegations
				{
1
					if &owner == post_owner {
1
						if &amount != post_amount {
							equal = false;
							break;
1
						}
					}
				}
			}
1
			assert!(equal);
1
			let mut not_equal = false;
5
			for Bond { owner, amount } in pre_call_top_delegations.delegations {
				for Bond {
15
					owner: post_owner,
15
					amount: post_amount,
18
				} in &post_call_top_delegations.delegations
				{
15
					if &owner == post_owner {
4
						if &amount != post_amount {
1
							not_equal = true;
1
							break;
3
						}
11
					}
				}
			}
1
			assert!(not_equal);
1
			assert_eq!(
1
				pre_call_candidate_info.total_counted - 4,
1
				post_call_candidate_info.total_counted
1
			);
1
		});
1
}
#[test]
1
fn can_execute_delegator_bond_less_for_leaving_candidate() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 15)])
1
		.with_candidates(vec![(1, 30)])
1
		.with_delegations(vec![(2, 1, 15)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::schedule_leave_candidates(
1
				RuntimeOrigin::signed(1),
1
				1
1
			));
1
			assert_ok!(ParachainStaking::schedule_delegator_bond_less(
1
				RuntimeOrigin::signed(2),
1
				1,
1
				5
1
			));
1
			roll_to(10);
1
			// can execute bond more delegation request for leaving candidate
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
1
				2,
1
				1
1
			));
1
		});
1
}
// CANCEL PENDING DELEGATION REQUEST
// 1. CANCEL REVOKE DELEGATION
#[test]
1
fn cancel_revoke_delegation_emits_correct_event() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 10)])
1
		.with_candidates(vec![(1, 30)])
1
		.with_delegations(vec![(2, 1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
1
				1
1
			));
1
			assert_ok!(ParachainStaking::cancel_delegation_request(
1
				RuntimeOrigin::signed(2),
1
				1
1
			));
1
			assert_events_emitted!(Event::CancelledDelegationRequest {
1
				delegator: 2,
1
				collator: 1,
1
				cancelled_request: CancelledScheduledRequest {
1
					when_executable: 3,
1
					action: DelegationAction::Revoke(10),
1
				},
1
			});
1
		});
1
}
#[test]
1
fn cancel_revoke_delegation_updates_delegator_state() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 10)])
1
		.with_candidates(vec![(1, 30)])
1
		.with_delegations(vec![(2, 1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
1
				1
1
			));
1
			let state = ParachainStaking::delegation_scheduled_requests(&1);
1
			assert_eq!(
1
				state,
1
				vec![ScheduledRequest {
1
					delegator: 2,
1
					when_executable: 3,
1
					action: DelegationAction::Revoke(10),
1
				}],
1
			);
1
			assert_eq!(
1
				ParachainStaking::delegator_state(&2)
1
					.map(|x| x.less_total)
1
					.expect("delegator state must exist"),
1
				10
1
			);
1
			assert_ok!(ParachainStaking::cancel_delegation_request(
1
				RuntimeOrigin::signed(2),
1
				1
1
			));
1
			assert!(!ParachainStaking::delegation_scheduled_requests(&1)
1
				.iter()
1
				.any(|x| x.delegator == 2));
1
			assert_eq!(
1
				ParachainStaking::delegator_state(&2)
1
					.map(|x| x.less_total)
1
					.expect("delegator state must exist"),
1
				0
1
			);
1
		});
1
}
// 2. CANCEL DELEGATOR BOND LESS
#[test]
1
fn cancel_delegator_bond_less_correct_event() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 15)])
1
		.with_candidates(vec![(1, 30)])
1
		.with_delegations(vec![(2, 1, 15)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::schedule_delegator_bond_less(
1
				RuntimeOrigin::signed(2),
1
				1,
1
				5
1
			));
1
			assert_ok!(ParachainStaking::cancel_delegation_request(
1
				RuntimeOrigin::signed(2),
1
				1
1
			));
1
			assert_events_emitted!(Event::CancelledDelegationRequest {
1
				delegator: 2,
1
				collator: 1,
1
				cancelled_request: CancelledScheduledRequest {
1
					when_executable: 3,
1
					action: DelegationAction::Decrease(5),
1
				},
1
			});
1
		});
1
}
#[test]
1
fn cancel_delegator_bond_less_updates_delegator_state() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 15)])
1
		.with_candidates(vec![(1, 30)])
1
		.with_delegations(vec![(2, 1, 15)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::schedule_delegator_bond_less(
1
				RuntimeOrigin::signed(2),
1
				1,
1
				5
1
			));
1
			let state = ParachainStaking::delegation_scheduled_requests(&1);
1
			assert_eq!(
1
				state,
1
				vec![ScheduledRequest {
1
					delegator: 2,
1
					when_executable: 3,
1
					action: DelegationAction::Decrease(5),
1
				}],
1
			);
1
			assert_eq!(
1
				ParachainStaking::delegator_state(&2)
1
					.map(|x| x.less_total)
1
					.expect("delegator state must exist"),
1
				5
1
			);
1
			assert_ok!(ParachainStaking::cancel_delegation_request(
1
				RuntimeOrigin::signed(2),
1
				1
1
			));
1
			assert!(!ParachainStaking::delegation_scheduled_requests(&1)
1
				.iter()
1
				.any(|x| x.delegator == 2));
1
			assert_eq!(
1
				ParachainStaking::delegator_state(&2)
1
					.map(|x| x.less_total)
1
					.expect("delegator state must exist"),
1
				0
1
			);
1
		});
1
}
// ~~ PROPERTY-BASED TESTS ~~
#[test]
1
fn delegator_schedule_revocation_total() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 20), (2, 40), (3, 20), (4, 20), (5, 20)])
1
		.with_candidates(vec![(1, 20), (3, 20), (4, 20), (5, 20)])
1
		.with_delegations(vec![(2, 1, 10), (2, 3, 10), (2, 4, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
1
				1
1
			));
1
			assert_eq!(
1
				ParachainStaking::delegator_state(&2)
1
					.map(|x| x.less_total)
1
					.expect("delegator state must exist"),
1
				10
1
			);
1
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
1
				2,
1
				1
1
			));
1
			assert_eq!(
1
				ParachainStaking::delegator_state(&2)
1
					.map(|x| x.less_total)
1
					.expect("delegator state must exist"),
1
				0
1
			);
1
			assert_ok!(ParachainStaking::delegate_with_auto_compound(
1
				RuntimeOrigin::signed(2),
1
				5,
1
				10,
1
				Percent::zero(),
1
				0,
1
				0,
1
				2
1
			));
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
1
				3
1
			));
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
1
				4
1
			));
1
			assert_eq!(
1
				ParachainStaking::delegator_state(&2)
1
					.map(|x| x.less_total)
1
					.expect("delegator state must exist"),
1
				20,
1
			);
1
			roll_to(20);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
1
				2,
1
				3
1
			));
1
			assert_eq!(
1
				ParachainStaking::delegator_state(&2)
1
					.map(|x| x.less_total)
1
					.expect("delegator state must exist"),
1
				10,
1
			);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
1
				2,
1
				4
1
			));
1
			assert_eq!(
1
				ParachainStaking::delegator_state(&2)
1
					.map(|x| x.less_total)
1
					.expect("delegator state must exist"),
1
				0
1
			);
1
		});
1
}
#[ignore]
#[test]
fn parachain_bond_inflation_reserve_matches_config() {
	ExtBuilder::default()
		.with_balances(vec![
			(1, 100),
			(2, 100),
			(3, 100),
			(4, 100),
			(5, 100),
			(6, 100),
			(7, 100),
			(8, 100),
			(9, 100),
			(10, 100),
			(11, 1),
		])
		.with_candidates(vec![(1, 20), (2, 20), (3, 20), (4, 20), (5, 10)])
		.with_delegations(vec![
			(6, 1, 10),
			(7, 1, 10),
			(8, 2, 10),
			(9, 2, 10),
			(10, 1, 10),
		])
		.build()
		.execute_with(|| {
			assert_eq!(Balances::free_balance(&11), 1);
			// set parachain bond account so DefaultParachainBondReservePercent = 30% of inflation
			// is allocated to this account hereafter
			assert_ok!(ParachainStaking::set_parachain_bond_account(
				RuntimeOrigin::root(),
				11
			));
			assert_events_eq!(Event::InflationDistributionConfigUpdated {
				old: inflation_configs(0, 30, 0, 0),
				new: inflation_configs(11, 30, 0, 0),
			});
			roll_to_round_begin(2);
			// chooses top TotalSelectedCandidates (5), in order
			assert_events_eq!(
				Event::CollatorChosen {
					round: 2,
					collator_account: 1,
					total_exposed_amount: 50,
				},
				Event::CollatorChosen {
					round: 2,
					collator_account: 2,
					total_exposed_amount: 40,
				},
				Event::CollatorChosen {
					round: 2,
					collator_account: 3,
					total_exposed_amount: 20,
				},
				Event::CollatorChosen {
					round: 2,
					collator_account: 4,
					total_exposed_amount: 20,
				},
				Event::CollatorChosen {
					round: 2,
					collator_account: 5,
					total_exposed_amount: 10,
				},
				Event::NewRound {
					starting_block: 5,
					round: 2,
					selected_collators_number: 5,
					total_balance: 140,
				},
			);
			assert_eq!(Balances::free_balance(&11), 1);
			// ~ set block author as 1 for all blocks this round
			set_author(2, 1, POINTS_PER_ROUND);
			roll_to_round_begin(4);
			// distribute total issuance to collator 1 and its delegators 6, 7, 19
			assert_eq!(Balances::free_balance(&11), 16);
			// ~ set block author as 1 for all blocks in rounds 3, 4, and 5
			set_author(3, 1, POINTS_PER_ROUND);
			set_author(4, 1, POINTS_PER_ROUND);
			set_author(5, 1, POINTS_PER_ROUND);
			// 1. ensure delegators are paid for 2 rounds after they leave
			assert_noop!(
				ParachainStaking::schedule_revoke_delegation(RuntimeOrigin::signed(66), 1),
				Error::<Test>::DelegatorDNE
			);
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
				RuntimeOrigin::signed(6),
				1,
			));
			assert_events_eq!(
				Event::InflationDistributed {
					index: 0,
					account: 11, // PBR
					value: 15,
				},
				Event::InflationDistributed {
					index: 1,
					account: 0, // Treasury
					value: 0,
				},
				Event::CollatorChosen {
					round: 4,
					collator_account: 1,
					total_exposed_amount: 50,
				},
				Event::CollatorChosen {
					round: 4,
					collator_account: 2,
					total_exposed_amount: 40,
				},
				Event::CollatorChosen {
					round: 4,
					collator_account: 3,
					total_exposed_amount: 20,
				},
				Event::CollatorChosen {
					round: 4,
					collator_account: 4,
					total_exposed_amount: 20,
				},
				Event::CollatorChosen {
					round: 4,
					collator_account: 5,
					total_exposed_amount: 10,
				},
				Event::NewRound {
					starting_block: 15,
					round: 4,
					selected_collators_number: 5,
					total_balance: 140,
				},
				Event::DelegatorExitScheduled {
					round: 4,
					delegator: 6,
					scheduled_exit: 6,
				},
			);
			roll_blocks(3);
			assert_events_eq!(
				Event::Rewarded {
					account: 1,
					rewards: 20,
				},
				Event::Rewarded {
					account: 6,
					rewards: 5,
				},
				Event::Rewarded {
					account: 7,
					rewards: 5,
				},
				Event::Rewarded {
					account: 10,
					rewards: 5,
				},
			);
			// fast forward to block in which delegator 6 exit executes
			roll_to_round_begin(5);
			assert_events_eq!(
				Event::InflationDistributed {
					index: 0,
					account: 11, // PBR
					value: 16,
				},
				Event::InflationDistributed {
					index: 1,
					account: 0, // Treasury
					value: 0,
				},
				Event::CollatorChosen {
					round: 5,
					collator_account: 1,
					total_exposed_amount: 50,
				},
				Event::CollatorChosen {
					round: 5,
					collator_account: 2,
					total_exposed_amount: 40,
				},
				Event::CollatorChosen {
					round: 5,
					collator_account: 3,
					total_exposed_amount: 20,
				},
				Event::CollatorChosen {
					round: 5,
					collator_account: 4,
					total_exposed_amount: 20,
				},
				Event::CollatorChosen {
					round: 5,
					collator_account: 5,
					total_exposed_amount: 10,
				},
				Event::NewRound {
					starting_block: 20,
					round: 5,
					selected_collators_number: 5,
					total_balance: 140,
				},
			);
			roll_blocks(3);
			assert_events_eq!(
				Event::Rewarded {
					account: 1,
					rewards: 21,
				},
				Event::Rewarded {
					account: 6,
					rewards: 5,
				},
				Event::Rewarded {
					account: 7,
					rewards: 5,
				},
				Event::Rewarded {
					account: 10,
					rewards: 5,
				},
			);
			roll_to_round_begin(6);
			assert_ok!(ParachainStaking::execute_delegation_request(
				RuntimeOrigin::signed(6),
				6,
				10
			));
			assert_events_eq!(
				Event::InflationDistributed {
					index: 0,
					account: 11, // PBR
					value: 16,
				},
				Event::InflationDistributed {
					index: 1,
					account: 0, // Treasury
					value: 0,
				},
				Event::CollatorChosen {
					round: 6,
					collator_account: 1,
					total_exposed_amount: 50,
				},
				Event::CollatorChosen {
					round: 6,
					collator_account: 2,
					total_exposed_amount: 40,
				},
				Event::CollatorChosen {
					round: 6,
					collator_account: 3,
					total_exposed_amount: 20,
				},
				Event::CollatorChosen {
					round: 6,
					collator_account: 4,
					total_exposed_amount: 20,
				},
				Event::CollatorChosen {
					round: 6,
					collator_account: 5,
					total_exposed_amount: 10,
				},
				Event::NewRound {
					starting_block: 25,
					round: 6,
					selected_collators_number: 5,
					total_balance: 140,
				},
				Event::DelegatorLeftCandidate {
					delegator: 6,
					candidate: 1,
					unstaked_amount: 10,
					total_candidate_staked: 40,
				},
				Event::DelegatorLeft {
					delegator: 6,
					unstaked_amount: 10,
				},
			);
			roll_blocks(3);
			assert_events_eq!(
				Event::Rewarded {
					account: 1,
					rewards: 22,
				},
				Event::Rewarded {
					account: 6,
					rewards: 6,
				},
				Event::Rewarded {
					account: 7,
					rewards: 6,
				},
				Event::Rewarded {
					account: 10,
					rewards: 6,
				},
			);
			roll_to_round_begin(7);
			assert_events_eq!(
				Event::InflationDistributed {
					index: 0,
					account: 11, // PBR
					value: 17,
				},
				Event::InflationDistributed {
					index: 1,
					account: 0, // Treasury
					value: 0,
				},
				Event::CollatorChosen {
					round: 7,
					collator_account: 1,
					total_exposed_amount: 40,
				},
				Event::CollatorChosen {
					round: 7,
					collator_account: 2,
					total_exposed_amount: 40,
				},
				Event::CollatorChosen {
					round: 7,
					collator_account: 3,
					total_exposed_amount: 20,
				},
				Event::CollatorChosen {
					round: 7,
					collator_account: 4,
					total_exposed_amount: 20,
				},
				Event::CollatorChosen {
					round: 7,
					collator_account: 5,
					total_exposed_amount: 10,
				},
				Event::NewRound {
					starting_block: 30,
					round: 7,
					selected_collators_number: 5,
					total_balance: 130,
				},
			);
			roll_blocks(3);
			assert_events_eq!(
				Event::Rewarded {
					account: 1,
					rewards: 26,
				},
				Event::Rewarded {
					account: 7,
					rewards: 7,
				},
				Event::Rewarded {
					account: 10,
					rewards: 7,
				},
			);
			assert_eq!(Balances::free_balance(&11), 65);
			roll_blocks(1);
			assert_ok!(ParachainStaking::set_parachain_bond_reserve_percent(
				RuntimeOrigin::root(),
				Percent::from_percent(50)
			));
			assert_events_eq!(Event::InflationDistributionConfigUpdated {
				old: inflation_configs(11, 30, 0, 0),
				new: inflation_configs(11, 50, 0, 0),
			});
			// 6 won't be paid for this round because they left already
			set_author(6, 1, POINTS_PER_ROUND);
			roll_to_round_begin(8);
			// keep paying 6
			assert_events_eq!(
				Event::InflationDistributed {
					index: 0,
					account: 11, // PBR
					value: 30,
				},
				Event::InflationDistributed {
					index: 1,
					account: 0, // Treasury
					value: 0,
				},
				Event::CollatorChosen {
					round: 8,
					collator_account: 1,
					total_exposed_amount: 40,
				},
				Event::CollatorChosen {
					round: 8,
					collator_account: 2,
					total_exposed_amount: 40,
				},
				Event::CollatorChosen {
					round: 8,
					collator_account: 3,
					total_exposed_amount: 20,
				},
				Event::CollatorChosen {
					round: 8,
					collator_account: 4,
					total_exposed_amount: 20,
				},
				Event::CollatorChosen {
					round: 8,
					collator_account: 5,
					total_exposed_amount: 10,
				},
				Event::NewRound {
					starting_block: 35,
					round: 8,
					selected_collators_number: 5,
					total_balance: 130,
				},
			);
			roll_blocks(3);
			assert_events_eq!(
				Event::Rewarded {
					account: 1,
					rewards: 21,
				},
				Event::Rewarded {
					account: 7,
					rewards: 5,
				},
				Event::Rewarded {
					account: 10,
					rewards: 5,
				},
			);
			assert_eq!(Balances::free_balance(&11), 95);
			set_author(7, 1, POINTS_PER_ROUND);
			roll_to_round_begin(9);
			// no more paying 6
			assert_events_eq!(
				Event::InflationDistributed {
					index: 0,
					account: 11, // PBR
					value: 32,
				},
				Event::InflationDistributed {
					index: 1,
					account: 0, // Treasury
					value: 0,
				},
				Event::CollatorChosen {
					round: 9,
					collator_account: 1,
					total_exposed_amount: 40,
				},
				Event::CollatorChosen {
					round: 9,
					collator_account: 2,
					total_exposed_amount: 40,
				},
				Event::CollatorChosen {
					round: 9,
					collator_account: 3,
					total_exposed_amount: 20,
				},
				Event::CollatorChosen {
					round: 9,
					collator_account: 4,
					total_exposed_amount: 20,
				},
				Event::CollatorChosen {
					round: 9,
					collator_account: 5,
					total_exposed_amount: 10,
				},
				Event::NewRound {
					starting_block: 40,
					round: 9,
					selected_collators_number: 5,
					total_balance: 130,
				},
			);
			roll_blocks(3);
			assert_events_eq!(
				Event::Rewarded {
					account: 1,
					rewards: 22,
				},
				Event::Rewarded {
					account: 7,
					rewards: 5,
				},
				Event::Rewarded {
					account: 10,
					rewards: 5,
				},
			);
			assert_eq!(Balances::free_balance(&11), 127);
			set_author(8, 1, POINTS_PER_ROUND);
			roll_blocks(1);
			assert_ok!(ParachainStaking::delegate_with_auto_compound(
				RuntimeOrigin::signed(8),
				1,
				10,
				Percent::zero(),
				10,
				0,
				10
			));
			assert_events_eq!(Event::Delegation {
				delegator: 8,
				locked_amount: 10,
				candidate: 1,
				delegator_position: DelegatorAdded::AddedToTop { new_total: 50 },
				auto_compound: Percent::zero(),
			});
			roll_to_round_begin(10);
			// new delegation is not rewarded yet
			assert_events_eq!(
				Event::InflationDistributed {
					index: 0,
					account: 11, // PBR
					value: 33,
				},
				Event::InflationDistributed {
					index: 1,
					account: 0, // Treasury
					value: 0,
				},
				Event::CollatorChosen {
					round: 10,
					collator_account: 1,
					total_exposed_amount: 50,
				},
				Event::CollatorChosen {
					round: 10,
					collator_account: 2,
					total_exposed_amount: 40,
				},
				Event::CollatorChosen {
					round: 10,
					collator_account: 3,
					total_exposed_amount: 20,
				},
				Event::CollatorChosen {
					round: 10,
					collator_account: 4,
					total_exposed_amount: 20,
				},
				Event::CollatorChosen {
					round: 10,
					collator_account: 5,
					total_exposed_amount: 10,
				},
				Event::NewRound {
					starting_block: 45,
					round: 10,
					selected_collators_number: 5,
					total_balance: 140,
				},
			);
			roll_blocks(3);
			assert_events_eq!(
				Event::Rewarded {
					account: 1,
					rewards: 23,
				},
				Event::Rewarded {
					account: 7,
					rewards: 5,
				},
				Event::Rewarded {
					account: 10,
					rewards: 5,
				},
			);
			assert_eq!(Balances::free_balance(&11), 160);
			set_author(9, 1, POINTS_PER_ROUND);
			set_author(10, 1, POINTS_PER_ROUND);
			roll_to_round_begin(11);
			// new delegation is still not rewarded yet
			assert_events_eq!(
				Event::InflationDistributed {
					index: 0,
					account: 11, // PBR
					value: 35,
				},
				Event::InflationDistributed {
					index: 1,
					account: 0, // Treasury
					value: 0,
				},
				Event::CollatorChosen {
					round: 11,
					collator_account: 1,
					total_exposed_amount: 50,
				},
				Event::CollatorChosen {
					round: 11,
					collator_account: 2,
					total_exposed_amount: 40,
				},
				Event::CollatorChosen {
					round: 11,
					collator_account: 3,
					total_exposed_amount: 20,
				},
				Event::CollatorChosen {
					round: 11,
					collator_account: 4,
					total_exposed_amount: 20,
				},
				Event::CollatorChosen {
					round: 11,
					collator_account: 5,
					total_exposed_amount: 10,
				},
				Event::NewRound {
					starting_block: 50,
					round: 11,
					selected_collators_number: 5,
					total_balance: 140,
				},
			);
			roll_blocks(3);
			assert_events_eq!(
				Event::Rewarded {
					account: 1,
					rewards: 24,
				},
				Event::Rewarded {
					account: 7,
					rewards: 5,
				},
				Event::Rewarded {
					account: 10,
					rewards: 5,
				},
			);
			assert_eq!(Balances::free_balance(&11), 195);
			roll_to_round_begin(12);
			// new delegation is rewarded, 2 rounds after joining (`RewardPaymentDelay` is 2)
			assert_events_eq!(
				Event::InflationDistributed {
					index: 0,
					account: 11, // PBR
					value: 37,
				},
				Event::InflationDistributed {
					index: 1,
					account: 0, // Treasury
					value: 0,
				},
				Event::CollatorChosen {
					round: 12,
					collator_account: 1,
					total_exposed_amount: 50,
				},
				Event::CollatorChosen {
					round: 12,
					collator_account: 2,
					total_exposed_amount: 40,
				},
				Event::CollatorChosen {
					round: 12,
					collator_account: 3,
					total_exposed_amount: 20,
				},
				Event::CollatorChosen {
					round: 12,
					collator_account: 4,
					total_exposed_amount: 20,
				},
				Event::CollatorChosen {
					round: 12,
					collator_account: 5,
					total_exposed_amount: 10,
				},
				Event::NewRound {
					starting_block: 55,
					round: 12,
					selected_collators_number: 5,
					total_balance: 140,
				},
			);
			roll_blocks(3);
			assert_events_eq!(
				Event::Rewarded {
					account: 1,
					rewards: 24,
				},
				Event::Rewarded {
					account: 7,
					rewards: 4,
				},
				Event::Rewarded {
					account: 10,
					rewards: 4,
				},
				Event::Rewarded {
					account: 8,
					rewards: 4,
				},
			);
			assert_eq!(Balances::free_balance(&11), 232);
		});
}
#[test]
1
fn paid_collator_commission_matches_config() {
1
	ExtBuilder::default()
1
		.with_balances(vec![
1
			(1, 100),
1
			(2, 100),
1
			(3, 100),
1
			(4, 100),
1
			(5, 100),
1
			(6, 100),
1
		])
1
		.with_candidates(vec![(1, 20)])
1
		.with_delegations(vec![(2, 1, 10), (3, 1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			roll_to_round_begin(2);
1
			assert_ok!(ParachainStaking::join_candidates(
1
				RuntimeOrigin::signed(4),
1
				20u128,
1
				100u32
1
			));
1
			assert_events_eq!(
1
				Event::CollatorChosen {
1
					round: 2,
1
					collator_account: 1,
1
					total_exposed_amount: 40,
1
				},
1
				Event::NewRound {
1
					starting_block: 5,
1
					round: 2,
1
					selected_collators_number: 1,
1
					total_balance: 40,
1
				},
1
				Event::JoinedCollatorCandidates {
1
					account: 4,
1
					amount_locked: 20,
1
					new_total_amt_locked: 60,
1
				},
1
			);
1
			roll_blocks(1);
1
			assert_ok!(ParachainStaking::delegate_with_auto_compound(
1
				RuntimeOrigin::signed(5),
1
				4,
1
				10,
1
				Percent::zero(),
1
				10,
1
				0,
1
				10
1
			));
1
			assert_ok!(ParachainStaking::delegate_with_auto_compound(
1
				RuntimeOrigin::signed(6),
1
				4,
1
				10,
1
				Percent::zero(),
1
				10,
1
				0,
1
				10
1
			));
1
			assert_events_eq!(
1
				Event::Delegation {
1
					delegator: 5,
1
					locked_amount: 10,
1
					candidate: 4,
1
					delegator_position: DelegatorAdded::AddedToTop { new_total: 30 },
1
					auto_compound: Percent::zero(),
1
				},
1
				Event::Delegation {
1
					delegator: 6,
1
					locked_amount: 10,
1
					candidate: 4,
1
					delegator_position: DelegatorAdded::AddedToTop { new_total: 40 },
1
					auto_compound: Percent::zero(),
1
				},
1
			);
1
			roll_to_round_begin(3);
1
			assert_events_eq!(
1
				Event::CollatorChosen {
1
					round: 3,
1
					collator_account: 1,
1
					total_exposed_amount: 40,
1
				},
1
				Event::CollatorChosen {
1
					round: 3,
1
					collator_account: 4,
1
					total_exposed_amount: 40,
1
				},
1
				Event::NewRound {
1
					starting_block: 10,
1
					round: 3,
1
					selected_collators_number: 2,
1
					total_balance: 80,
1
				},
1
			);
			// only reward author with id 4
1
			set_author(3, 4, POINTS_PER_ROUND);
1
			roll_to_round_begin(5);
1
			// 20% of 10 is commission + due_portion (0) = 2 + 4 = 6
1
			// all delegator payouts are 10-2 = 8 * stake_pct
1
			assert_events_eq!(
1
				Event::CollatorChosen {
1
					round: 5,
1
					collator_account: 1,
1
					total_exposed_amount: 40,
1
				},
1
				Event::CollatorChosen {
1
					round: 5,
1
					collator_account: 4,
1
					total_exposed_amount: 40,
1
				},
1
				Event::NewRound {
1
					starting_block: 20,
1
					round: 5,
1
					selected_collators_number: 2,
1
					total_balance: 80,
1
				},
1
			);
1
			roll_blocks(1);
1
			assert_events_eq!(
1
				Event::Rewarded {
1
					account: 4,
1
					rewards: 9,
1
				},
1
				Event::Rewarded {
1
					account: 5,
1
					rewards: 3,
1
				},
1
				Event::Rewarded {
1
					account: 6,
1
					rewards: 3,
1
				},
1
			);
1
		});
1
}
#[test]
1
fn collator_exit_executes_after_delay() {
1
	ExtBuilder::default()
1
		.with_balances(vec![
1
			(1, 1000),
1
			(2, 300),
1
			(3, 100),
1
			(4, 100),
1
			(5, 100),
1
			(6, 100),
1
			(7, 100),
1
			(8, 9),
1
			(9, 4),
1
		])
1
		.with_candidates(vec![(1, 500), (2, 200)])
1
		.with_delegations(vec![(3, 1, 100), (4, 1, 100), (5, 2, 100), (6, 2, 100)])
1
		.build()
1
		.execute_with(|| {
1
			roll_to(11);
1
			assert_ok!(ParachainStaking::schedule_leave_candidates(
1
				RuntimeOrigin::signed(2),
1
				2
1
			));
1
			assert_events_eq!(Event::CandidateScheduledExit {
1
				exit_allowed_round: 3,
1
				candidate: 2,
1
				scheduled_exit: 5,
1
			});
1
			let info = ParachainStaking::candidate_info(&2).unwrap();
1
			assert_eq!(info.status, CollatorStatus::Leaving(5));
1
			roll_to(21);
1
			assert_ok!(ParachainStaking::execute_leave_candidates(
1
				RuntimeOrigin::signed(2),
1
				2,
1
				2
1
			));
			// we must exclude leaving collators from rewards while
			// holding them retroactively accountable for previous faults
			// (within the last T::SlashingWindow blocks)
1
			assert_events_eq!(Event::CandidateLeft {
1
				ex_candidate: 2,
1
				unlocked_amount: 400,
1
				new_total_amt_locked: 700,
1
			},);
1
		});
1
}
#[test]
1
fn collator_selection_chooses_top_candidates() {
1
	ExtBuilder::default()
1
		.with_balances(vec![
1
			(1, 1000),
1
			(2, 1000),
1
			(3, 1000),
1
			(4, 1000),
1
			(5, 1000),
1
			(6, 1000),
1
			(7, 33),
1
			(8, 33),
1
			(9, 33),
1
		])
1
		.with_candidates(vec![(1, 100), (2, 90), (3, 80), (4, 70), (5, 60), (6, 50)])
1
		.build()
1
		.execute_with(|| {
1
			roll_to_round_begin(2);
1
			assert_ok!(ParachainStaking::schedule_leave_candidates(
1
				RuntimeOrigin::signed(6),
1
				6
1
			));
			// should choose top TotalSelectedCandidates (5), in order
1
			assert_events_eq!(
1
				Event::CollatorChosen {
1
					round: 2,
1
					collator_account: 1,
1
					total_exposed_amount: 100,
1
				},
1
				Event::CollatorChosen {
1
					round: 2,
1
					collator_account: 2,
1
					total_exposed_amount: 90,
1
				},
1
				Event::CollatorChosen {
1
					round: 2,
1
					collator_account: 3,
1
					total_exposed_amount: 80,
1
				},
1
				Event::CollatorChosen {
1
					round: 2,
1
					collator_account: 4,
1
					total_exposed_amount: 70,
1
				},
1
				Event::CollatorChosen {
1
					round: 2,
1
					collator_account: 5,
1
					total_exposed_amount: 60,
1
				},
1
				Event::NewRound {
1
					starting_block: 5,
1
					round: 2,
1
					selected_collators_number: 5,
1
					total_balance: 400,
1
				},
1
				Event::CandidateScheduledExit {
1
					exit_allowed_round: 2,
1
					candidate: 6,
1
					scheduled_exit: 4
1
				},
1
			);
1
			roll_to_round_begin(4);
1
			roll_blocks(1);
1
			assert_ok!(ParachainStaking::execute_leave_candidates(
1
				RuntimeOrigin::signed(6),
1
				6,
1
				0
1
			));
1
			assert_ok!(ParachainStaking::join_candidates(
1
				RuntimeOrigin::signed(6),
1
				69u128,
1
				100u32
1
			));
1
			assert_events_eq!(
1
				Event::CandidateLeft {
1
					ex_candidate: 6,
1
					unlocked_amount: 50,
1
					new_total_amt_locked: 400,
1
				},
1
				Event::JoinedCollatorCandidates {
1
					account: 6,
1
					amount_locked: 69u128,
1
					new_total_amt_locked: 469u128,
1
				},
1
			);
1
			roll_to_round_begin(6);
1
			// should choose top TotalSelectedCandidates (5), in order
1
			assert_events_eq!(
1
				Event::CollatorChosen {
1
					round: 6,
1
					collator_account: 1,
1
					total_exposed_amount: 100,
1
				},
1
				Event::CollatorChosen {
1
					round: 6,
1
					collator_account: 2,
1
					total_exposed_amount: 90,
1
				},
1
				Event::CollatorChosen {
1
					round: 6,
1
					collator_account: 3,
1
					total_exposed_amount: 80,
1
				},
1
				Event::CollatorChosen {
1
					round: 6,
1
					collator_account: 4,
1
					total_exposed_amount: 70,
1
				},
1
				Event::CollatorChosen {
1
					round: 6,
1
					collator_account: 6,
1
					total_exposed_amount: 69,
1
				},
1
				Event::NewRound {
1
					starting_block: 25,
1
					round: 6,
1
					selected_collators_number: 5,
1
					total_balance: 409,
1
				},
1
			);
1
		});
1
}
#[test]
1
fn payout_distribution_to_solo_collators() {
1
	ExtBuilder::default()
1
		.with_balances(vec![
1
			(1, 1000),
1
			(2, 1000),
1
			(3, 1000),
1
			(4, 1000),
1
			(7, 33),
1
			(8, 33),
1
			(9, 33),
1
		])
1
		.with_candidates(vec![(1, 100), (2, 90), (3, 80), (4, 70)])
1
		.build()
1
		.execute_with(|| {
1
			roll_to_round_begin(2);
1
			// should choose top TotalCandidatesSelected (5), in order
1
			assert_events_eq!(
1
				Event::CollatorChosen {
1
					round: 2,
1
					collator_account: 1,
1
					total_exposed_amount: 100,
1
				},
1
				Event::CollatorChosen {
1
					round: 2,
1
					collator_account: 2,
1
					total_exposed_amount: 90,
1
				},
1
				Event::CollatorChosen {
1
					round: 2,
1
					collator_account: 3,
1
					total_exposed_amount: 80,
1
				},
1
				Event::CollatorChosen {
1
					round: 2,
1
					collator_account: 4,
1
					total_exposed_amount: 70,
1
				},
1
				Event::NewRound {
1
					starting_block: 5,
1
					round: 2,
1
					selected_collators_number: 4,
1
					total_balance: 340,
1
				},
1
			);
			// ~ set block author as 1 for all blocks this round
1
			set_author(2, 1, POINTS_PER_ROUND);
1
			roll_to_round_begin(4);
1
			assert_events_eq!(
1
				Event::CollatorChosen {
1
					round: 4,
1
					collator_account: 1,
1
					total_exposed_amount: 100,
1
				},
1
				Event::CollatorChosen {
1
					round: 4,
1
					collator_account: 2,
1
					total_exposed_amount: 90,
1
				},
1
				Event::CollatorChosen {
1
					round: 4,
1
					collator_account: 3,
1
					total_exposed_amount: 80,
1
				},
1
				Event::CollatorChosen {
1
					round: 4,
1
					collator_account: 4,
1
					total_exposed_amount: 70,
1
				},
1
				Event::NewRound {
1
					starting_block: 15,
1
					round: 4,
1
					selected_collators_number: 4,
1
					total_balance: 340,
1
				},
1
			);
			// pay total issuance to 1 at 2nd block
1
			roll_blocks(3);
1
			assert_events_eq!(Event::Rewarded {
1
				account: 1,
1
				rewards: 102,
1
			});
			// ~ set block author as 1 for 3 blocks this round
1
			set_author(4, 1, POINTS_PER_BLOCK * 3);
1
			// ~ set block author as 2 for 2 blocks this round
1
			set_author(4, 2, POINTS_PER_BLOCK * 2);
1
			roll_to_round_begin(6);
1
			// pay 60% total issuance to 1 and 40% total issuance to 2
1
			assert_events_eq!(
1
				Event::CollatorChosen {
1
					round: 6,
1
					collator_account: 1,
1
					total_exposed_amount: 100,
1
				},
1
				Event::CollatorChosen {
1
					round: 6,
1
					collator_account: 2,
1
					total_exposed_amount: 90,
1
				},
1
				Event::CollatorChosen {
1
					round: 6,
1
					collator_account: 3,
1
					total_exposed_amount: 80,
1
				},
1
				Event::CollatorChosen {
1
					round: 6,
1
					collator_account: 4,
1
					total_exposed_amount: 70,
1
				},
1
				Event::NewRound {
1
					starting_block: 25,
1
					round: 6,
1
					selected_collators_number: 4,
1
					total_balance: 340,
1
				},
1
			);
1
			roll_blocks(3);
1
			assert_events_eq!(Event::Rewarded {
1
				account: 1,
1
				rewards: 63,
1
			});
1
			roll_blocks(1);
1
			assert_events_eq!(Event::Rewarded {
1
				account: 2,
1
				rewards: 42,
1
			},);
			// ~ each collator produces at least 1 block this round
1
			set_author(6, 1, POINTS_PER_BLOCK * 2);
1
			set_author(6, 2, POINTS_PER_BLOCK);
1
			set_author(6, 3, POINTS_PER_BLOCK);
1
			set_author(6, 4, POINTS_PER_BLOCK);
1
			roll_to_round_begin(8);
1
			// pay 20% issuance for all collators
1
			assert_events_eq!(
1
				Event::CollatorChosen {
1
					round: 8,
1
					collator_account: 1,
1
					total_exposed_amount: 100,
1
				},
1
				Event::CollatorChosen {
1
					round: 8,
1
					collator_account: 2,
1
					total_exposed_amount: 90,
1
				},
1
				Event::CollatorChosen {
1
					round: 8,
1
					collator_account: 3,
1
					total_exposed_amount: 80,
1
				},
1
				Event::CollatorChosen {
1
					round: 8,
1
					collator_account: 4,
1
					total_exposed_amount: 70,
1
				},
1
				Event::NewRound {
1
					starting_block: 35,
1
					round: 8,
1
					selected_collators_number: 4,
1
					total_balance: 340,
1
				},
1
			);
1
			roll_blocks(1);
1
			assert_events_eq!(Event::Rewarded {
1
				account: 3,
1
				rewards: 21,
1
			});
1
			roll_blocks(1);
1
			assert_events_eq!(Event::Rewarded {
1
				account: 4,
1
				rewards: 21,
1
			});
1
			roll_blocks(1);
1
			assert_events_eq!(Event::Rewarded {
1
				account: 1,
1
				rewards: 43,
1
			});
1
			roll_blocks(1);
1
			assert_events_eq!(Event::Rewarded {
1
				account: 2,
1
				rewards: 21,
1
			});
			// check that distributing rewards clears awarded pts
1
			assert!(ParachainStaking::awarded_pts(1, 1).is_zero());
1
			assert!(ParachainStaking::awarded_pts(4, 1).is_zero());
1
			assert!(ParachainStaking::awarded_pts(4, 2).is_zero());
1
			assert!(ParachainStaking::awarded_pts(6, 1).is_zero());
1
			assert!(ParachainStaking::awarded_pts(6, 2).is_zero());
1
			assert!(ParachainStaking::awarded_pts(6, 3).is_zero());
1
			assert!(ParachainStaking::awarded_pts(6, 4).is_zero());
1
		});
1
}
#[test]
1
fn multiple_delegations() {
1
	ExtBuilder::default()
1
		.with_balances(vec![
1
			(1, 100),
1
			(2, 100),
1
			(3, 100),
1
			(4, 100),
1
			(5, 100),
1
			(6, 100),
1
			(7, 100),
1
			(8, 100),
1
			(9, 100),
1
			(10, 100),
1
		])
1
		.with_candidates(vec![(1, 20), (2, 20), (3, 20), (4, 20), (5, 10)])
1
		.with_delegations(vec![
1
			(6, 1, 10),
1
			(7, 1, 10),
1
			(8, 2, 10),
1
			(9, 2, 10),
1
			(10, 1, 10),
1
		])
1
		.build()
1
		.execute_with(|| {
1
			roll_to_round_begin(2);
1
			// chooses top TotalSelectedCandidates (5), in order
1
			assert_events_eq!(
1
				Event::CollatorChosen {
1
					round: 2,
1
					collator_account: 1,
1
					total_exposed_amount: 50,
1
				},
1
				Event::CollatorChosen {
1
					round: 2,
1
					collator_account: 2,
1
					total_exposed_amount: 40,
1
				},
1
				Event::CollatorChosen {
1
					round: 2,
1
					collator_account: 3,
1
					total_exposed_amount: 20,
1
				},
1
				Event::CollatorChosen {
1
					round: 2,
1
					collator_account: 4,
1
					total_exposed_amount: 20,
1
				},
1
				Event::CollatorChosen {
1
					round: 2,
1
					collator_account: 5,
1
					total_exposed_amount: 10,
1
				},
1
				Event::NewRound {
1
					starting_block: 5,
1
					round: 2,
1
					selected_collators_number: 5,
1
					total_balance: 140,
1
				},
1
			);
1
			roll_blocks(1);
1
			assert_ok!(ParachainStaking::delegate_with_auto_compound(
1
				RuntimeOrigin::signed(6),
1
				2,
1
				10,
1
				Percent::zero(),
1
				10,
1
				0,
1
				10
1
			));
1
			assert_ok!(ParachainStaking::delegate_with_auto_compound(
1
				RuntimeOrigin::signed(6),
1
				3,
1
				10,
1
				Percent::zero(),
1
				10,
1
				0,
1
				10
1
			));
1
			assert_ok!(ParachainStaking::delegate_with_auto_compound(
1
				RuntimeOrigin::signed(6),
1
				4,
1
				10,
1
				Percent::zero(),
1
				10,
1
				0,
1
				10
1
			));
1
			assert_events_eq!(
1
				Event::Delegation {
1
					delegator: 6,
1
					locked_amount: 10,
1
					candidate: 2,
1
					delegator_position: DelegatorAdded::AddedToTop { new_total: 50 },
1
					auto_compound: Percent::zero(),
1
				},
1
				Event::Delegation {
1
					delegator: 6,
1
					locked_amount: 10,
1
					candidate: 3,
1
					delegator_position: DelegatorAdded::AddedToTop { new_total: 30 },
1
					auto_compound: Percent::zero(),
1
				},
1
				Event::Delegation {
1
					delegator: 6,
1
					locked_amount: 10,
1
					candidate: 4,
1
					delegator_position: DelegatorAdded::AddedToTop { new_total: 30 },
1
					auto_compound: Percent::zero(),
1
				},
1
			);
1
			roll_to_round_begin(6);
1
			roll_blocks(1);
1
			assert_ok!(ParachainStaking::delegate_with_auto_compound(
1
				RuntimeOrigin::signed(7),
1
				2,
1
				80,
1
				Percent::zero(),
1
				10,
1
				0,
1
				10
1
			));
1
			assert_ok!(ParachainStaking::delegate_with_auto_compound(
1
				RuntimeOrigin::signed(10),
1
				2,
1
				10,
1
				Percent::zero(),
1
				10,
1
				0,
1
				10
1
			));
1
			assert_ok!(ParachainStaking::schedule_leave_candidates(
1
				RuntimeOrigin::signed(2),
1
				5
1
			));
1
			assert_events_eq!(
1
				Event::Delegation {
1
					delegator: 7,
1
					locked_amount: 80,
1
					candidate: 2,
1
					delegator_position: DelegatorAdded::AddedToTop { new_total: 130 },
1
					auto_compound: Percent::zero(),
1
				},
1
				Event::Delegation {
1
					delegator: 10,
1
					locked_amount: 10,
1
					candidate: 2,
1
					delegator_position: DelegatorAdded::AddedToBottom,
1
					auto_compound: Percent::zero(),
1
				},
1
				Event::CandidateScheduledExit {
1
					exit_allowed_round: 6,
1
					candidate: 2,
1
					scheduled_exit: 8
1
				},
1
			);
1
			roll_to_round_begin(7);
1
			assert_events_eq!(
1
				Event::CollatorChosen {
1
					round: 7,
1
					collator_account: 1,
1
					total_exposed_amount: 50,
1
				},
1
				Event::CollatorChosen {
1
					round: 7,
1
					collator_account: 3,
1
					total_exposed_amount: 30,
1
				},
1
				Event::CollatorChosen {
1
					round: 7,
1
					collator_account: 4,
1
					total_exposed_amount: 30,
1
				},
1
				Event::CollatorChosen {
1
					round: 7,
1
					collator_account: 5,
1
					total_exposed_amount: 10,
1
				},
1
				Event::NewRound {
1
					starting_block: 30,
1
					round: 7,
1
					selected_collators_number: 4,
1
					total_balance: 120,
1
				},
1
			);
			// verify that delegations are removed after collator leaves, not before
1
			assert_eq!(ParachainStaking::delegator_state(7).unwrap().total(), 90);
1
			assert_eq!(
1
				ParachainStaking::delegator_state(7)
1
					.unwrap()
1
					.delegations
1
					.0
1
					.len(),
1
				2usize
1
			);
1
			assert_eq!(ParachainStaking::delegator_state(6).unwrap().total(), 40);
1
			assert_eq!(
1
				ParachainStaking::delegator_state(6)
1
					.unwrap()
1
					.delegations
1
					.0
1
					.len(),
1
				4usize
1
			);
1
			assert_eq!(Balances::locks(&6)[0].amount, 40);
1
			assert_eq!(Balances::locks(&7)[0].amount, 90);
1
			assert_eq!(ParachainStaking::get_delegator_stakable_balance(&6), 60);
1
			assert_eq!(ParachainStaking::get_delegator_stakable_balance(&7), 10);
1
			roll_to_round_begin(8);
1
			roll_blocks(1);
1
			assert_ok!(ParachainStaking::execute_leave_candidates(
1
				RuntimeOrigin::signed(2),
1
				2,
1
				5
1
			));
1
			assert_events_eq!(Event::CandidateLeft {
1
				ex_candidate: 2,
1
				unlocked_amount: 140,
1
				new_total_amt_locked: 120,
1
			});
1
			assert_eq!(ParachainStaking::delegator_state(7).unwrap().total(), 10);
1
			assert_eq!(ParachainStaking::delegator_state(6).unwrap().total(), 30);
1
			assert_eq!(
1
				ParachainStaking::delegator_state(7)
1
					.unwrap()
1
					.delegations
1
					.0
1
					.len(),
1
				1usize
1
			);
1
			assert_eq!(
1
				ParachainStaking::delegator_state(6)
1
					.unwrap()
1
					.delegations
1
					.0
1
					.len(),
1
				3usize
1
			);
1
			assert_eq!(ParachainStaking::get_delegator_stakable_balance(&6), 70);
1
			assert_eq!(ParachainStaking::get_delegator_stakable_balance(&7), 90);
1
		});
1
}
#[test]
// The test verifies that the pending revoke request is removed by 2's exit so there is no dangling
// revoke request after 2 exits
1
fn execute_leave_candidate_removes_delegations() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 100), (2, 100), (3, 100), (4, 100)])
1
		.with_candidates(vec![(1, 20), (2, 20)])
1
		.with_delegations(vec![(3, 1, 10), (3, 2, 10), (4, 1, 10), (4, 2, 10)])
1
		.build()
1
		.execute_with(|| {
1
			// Verifies the revocation request is initially empty
1
			assert!(!ParachainStaking::delegation_scheduled_requests(&2)
1
				.iter()
1
				.any(|x| x.delegator == 3));
1
			assert_ok!(ParachainStaking::schedule_leave_candidates(
1
				RuntimeOrigin::signed(2),
1
				2
1
			));
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(3),
1
				2
1
			));
			// Verifies the revocation request is present
1
			assert!(ParachainStaking::delegation_scheduled_requests(&2)
1
				.iter()
1
				.any(|x| x.delegator == 3));
1
			roll_to(16);
1
			assert_ok!(ParachainStaking::execute_leave_candidates(
1
				RuntimeOrigin::signed(2),
1
				2,
1
				2
1
			));
			// Verifies the revocation request is again empty
1
			assert!(!ParachainStaking::delegation_scheduled_requests(&2)
1
				.iter()
1
				.any(|x| x.delegator == 3));
1
		});
1
}
#[test]
1
fn payouts_follow_delegation_changes() {
1
	ExtBuilder::default()
1
		.with_balances(vec![
1
			(1, 100),
1
			(2, 100),
1
			(3, 100),
1
			(4, 100),
1
			(6, 100),
1
			(7, 100),
1
			(8, 100),
1
			(9, 100),
1
			(10, 100),
1
		])
1
		.with_candidates(vec![(1, 20), (2, 20), (3, 20), (4, 20)])
1
		.with_delegations(vec![
1
			(6, 1, 10),
1
			(7, 1, 10),
1
			(8, 2, 10),
1
			(9, 2, 10),
1
			(10, 1, 10),
1
		])
1
		.build()
1
		.execute_with(|| {
1
			// ~ set block author as 1 for all blocks this round
1
			set_author(1, 1, POINTS_PER_ROUND);
1
			set_author(2, 1, POINTS_PER_ROUND);
1
			roll_to_round_begin(2);
1
			// chooses top TotalSelectedCandidates (5), in order
1
			assert_events_eq!(
1
				Event::CollatorChosen {
1
					round: 2,
1
					collator_account: 1,
1
					total_exposed_amount: 50,
1
				},
1
				Event::CollatorChosen {
1
					round: 2,
1
					collator_account: 2,
1
					total_exposed_amount: 40,
1
				},
1
				Event::CollatorChosen {
1
					round: 2,
1
					collator_account: 3,
1
					total_exposed_amount: 20,
1
				},
1
				Event::CollatorChosen {
1
					round: 2,
1
					collator_account: 4,
1
					total_exposed_amount: 20,
1
				},
1
				Event::NewRound {
1
					starting_block: 5,
1
					round: 2,
1
					selected_collators_number: 4,
1
					total_balance: 130,
1
				},
1
			);
1
			set_author(3, 1, POINTS_PER_ROUND);
1
			set_author(4, 1, POINTS_PER_ROUND);
1

            
1
			roll_to_round_begin(4);
1
			// distribute total issuance to collator 1 and its delegators 6, 7, 19
1
			assert_events_eq!(
1
				Event::CollatorChosen {
1
					round: 4,
1
					collator_account: 1,
1
					total_exposed_amount: 50,
1
				},
1
				Event::CollatorChosen {
1
					round: 4,
1
					collator_account: 2,
1
					total_exposed_amount: 40,
1
				},
1
				Event::CollatorChosen {
1
					round: 4,
1
					collator_account: 3,
1
					total_exposed_amount: 20,
1
				},
1
				Event::CollatorChosen {
1
					round: 4,
1
					collator_account: 4,
1
					total_exposed_amount: 20,
1
				},
1
				Event::NewRound {
1
					starting_block: 15,
1
					round: 4,
1
					selected_collators_number: 4,
1
					total_balance: 130,
1
				},
1
			);
1
			roll_blocks(3);
1
			assert_events_eq!(
1
				Event::Rewarded {
1
					account: 1,
1
					rewards: 11,
1
				},
1
				Event::Rewarded {
1
					account: 6,
1
					rewards: 4,
1
				},
1
				Event::Rewarded {
1
					account: 7,
1
					rewards: 4,
1
				},
1
				Event::Rewarded {
1
					account: 10,
1
					rewards: 4,
1
				},
1
			);
			// ~ set block author as 1 for all blocks this round
1
			set_author(5, 1, POINTS_PER_ROUND);
1

            
1
			roll_blocks(1);
1
			// 1. ensure delegators are paid for 2 rounds after they leave
1
			assert_noop!(
1
				ParachainStaking::schedule_revoke_delegation(RuntimeOrigin::signed(66), 1),
1
				Error::<Test>::DelegatorDNE
1
			);
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(6),
1
				1,
1
			));
1
			assert_events_eq!(Event::DelegationRevocationScheduled {
1
				round: 4,
1
				delegator: 6,
1
				candidate: 1,
1
				scheduled_exit: 6,
1
			});
			// fast forward to block in which delegator 6 exit executes
1
			roll_to_round_begin(5);
1
			assert_events_eq!(
1
				Event::CollatorChosen {
1
					round: 5,
1
					collator_account: 1,
1
					total_exposed_amount: 50,
1
				},
1
				Event::CollatorChosen {
1
					round: 5,
1
					collator_account: 2,
1
					total_exposed_amount: 40,
1
				},
1
				Event::CollatorChosen {
1
					round: 5,
1
					collator_account: 3,
1
					total_exposed_amount: 20,
1
				},
1
				Event::CollatorChosen {
1
					round: 5,
1
					collator_account: 4,
1
					total_exposed_amount: 20,
1
				},
1
				Event::NewRound {
1
					starting_block: 20,
1
					round: 5,
1
					selected_collators_number: 4,
1
					total_balance: 130,
1
				},
1
			);
1
			roll_blocks(3);
1
			assert_events_eq!(
1
				Event::Rewarded {
1
					account: 1,
1
					rewards: 12,
1
				},
1
				Event::Rewarded {
1
					account: 6,
1
					rewards: 4,
1
				},
1
				Event::Rewarded {
1
					account: 7,
1
					rewards: 4,
1
				},
1
				Event::Rewarded {
1
					account: 10,
1
					rewards: 4,
1
				},
1
			);
1
			set_author(6, 1, POINTS_PER_ROUND);
1
			// keep paying 6 (note: inflation is in terms of total issuance so that's why 1 is 21)
1
			roll_to_round_begin(6);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(6),
1
				6,
1
				1,
1
			));
1
			assert_events_eq!(
1
				Event::CollatorChosen {
1
					round: 6,
1
					collator_account: 1,
1
					total_exposed_amount: 50,
1
				},
1
				Event::CollatorChosen {
1
					round: 6,
1
					collator_account: 2,
1
					total_exposed_amount: 40,
1
				},
1
				Event::CollatorChosen {
1
					round: 6,
1
					collator_account: 3,
1
					total_exposed_amount: 20,
1
				},
1
				Event::CollatorChosen {
1
					round: 6,
1
					collator_account: 4,
1
					total_exposed_amount: 20,
1
				},
1
				Event::NewRound {
1
					starting_block: 25,
1
					round: 6,
1
					selected_collators_number: 4,
1
					total_balance: 130,
1
				},
1
				Event::DelegatorLeftCandidate {
1
					delegator: 6,
1
					candidate: 1,
1
					unstaked_amount: 10,
1
					total_candidate_staked: 40,
1
				},
1
				Event::DelegationRevoked {
1
					delegator: 6,
1
					candidate: 1,
1
					unstaked_amount: 10,
1
				},
1
				Event::DelegatorLeft {
1
					delegator: 6,
1
					unstaked_amount: 10,
1
				},
1
			);
1
			roll_blocks(3);
1
			assert_events_eq!(
1
				Event::Rewarded {
1
					account: 1,
1
					rewards: 12,
1
				},
1
				Event::Rewarded {
1
					account: 6,
1
					rewards: 4,
1
				},
1
				Event::Rewarded {
1
					account: 7,
1
					rewards: 4,
1
				},
1
				Event::Rewarded {
1
					account: 10,
1
					rewards: 4,
1
				},
1
			);
			// 6 won't be paid for this round because they left already
1
			set_author(7, 1, POINTS_PER_ROUND);
1
			roll_to_round_begin(7);
1
			// keep paying 6
1
			assert_events_eq!(
1
				Event::CollatorChosen {
1
					round: 7,
1
					collator_account: 1,
1
					total_exposed_amount: 40,
1
				},
1
				Event::CollatorChosen {
1
					round: 7,
1
					collator_account: 2,
1
					total_exposed_amount: 40,
1
				},
1
				Event::CollatorChosen {
1
					round: 7,
1
					collator_account: 3,
1
					total_exposed_amount: 20,
1
				},
1
				Event::CollatorChosen {
1
					round: 7,
1
					collator_account: 4,
1
					total_exposed_amount: 20,
1
				},
1
				Event::NewRound {
1
					starting_block: 30,
1
					round: 7,
1
					selected_collators_number: 4,
1
					total_balance: 120,
1
				},
1
			);
1
			roll_blocks(3);
1
			assert_events_eq!(
1
				Event::Rewarded {
1
					account: 1,
1
					rewards: 14,
1
				},
1
				Event::Rewarded {
1
					account: 7,
1
					rewards: 5,
1
				},
1
				Event::Rewarded {
1
					account: 10,
1
					rewards: 5,
1
				},
1
			);
1
			set_author(8, 1, POINTS_PER_ROUND);
1
			roll_to_round_begin(8);
1
			assert_events_eq!(
1
				Event::CollatorChosen {
1
					round: 8,
1
					collator_account: 1,
1
					total_exposed_amount: 40,
1
				},
1
				Event::CollatorChosen {
1
					round: 8,
1
					collator_account: 2,
1
					total_exposed_amount: 40,
1
				},
1
				Event::CollatorChosen {
1
					round: 8,
1
					collator_account: 3,
1
					total_exposed_amount: 20,
1
				},
1
				Event::CollatorChosen {
1
					round: 8,
1
					collator_account: 4,
1
					total_exposed_amount: 20,
1
				},
1
				Event::NewRound {
1
					starting_block: 35,
1
					round: 8,
1
					selected_collators_number: 4,
1
					total_balance: 120,
1
				},
1
			);
1
			roll_blocks(3);
1
			assert_events_eq!(
1
				Event::Rewarded {
1
					account: 1,
1
					rewards: 15,
1
				},
1
				Event::Rewarded {
1
					account: 7,
1
					rewards: 5,
1
				},
1
				Event::Rewarded {
1
					account: 10,
1
					rewards: 5,
1
				},
1
			);
1
			set_author(9, 1, POINTS_PER_ROUND);
1
			roll_to_round_begin(9);
1
			// no more paying 6
1
			assert_events_eq!(
1
				Event::CollatorChosen {
1
					round: 9,
1
					collator_account: 1,
1
					total_exposed_amount: 40,
1
				},
1
				Event::CollatorChosen {
1
					round: 9,
1
					collator_account: 2,
1
					total_exposed_amount: 40,
1
				},
1
				Event::CollatorChosen {
1
					round: 9,
1
					collator_account: 3,
1
					total_exposed_amount: 20,
1
				},
1
				Event::CollatorChosen {
1
					round: 9,
1
					collator_account: 4,
1
					total_exposed_amount: 20,
1
				},
1
				Event::NewRound {
1
					starting_block: 40,
1
					round: 9,
1
					selected_collators_number: 4,
1
					total_balance: 120,
1
				},
1
			);
1
			roll_blocks(3);
1
			assert_events_eq!(
1
				Event::Rewarded {
1
					account: 1,
1
					rewards: 15,
1
				},
1
				Event::Rewarded {
1
					account: 7,
1
					rewards: 5,
1
				},
1
				Event::Rewarded {
1
					account: 10,
1
					rewards: 5,
1
				},
1
			);
1
			roll_blocks(1);
1
			assert_ok!(ParachainStaking::delegate_with_auto_compound(
1
				RuntimeOrigin::signed(8),
1
				1,
1
				10,
1
				Percent::zero(),
1
				10,
1
				0,
1
				10
1
			));
1
			assert_events_eq!(Event::Delegation {
1
				delegator: 8,
1
				locked_amount: 10,
1
				candidate: 1,
1
				delegator_position: DelegatorAdded::AddedToTop { new_total: 50 },
1
				auto_compound: Percent::zero(),
1
			});
1
			set_author(10, 1, POINTS_PER_ROUND);
1
			roll_to_round_begin(10);
1
			// new delegation is not rewarded yet
1
			assert_events_eq!(
1
				Event::CollatorChosen {
1
					round: 10,
1
					collator_account: 1,
1
					total_exposed_amount: 50,
1
				},
1
				Event::CollatorChosen {
1
					round: 10,
1
					collator_account: 2,
1
					total_exposed_amount: 40,
1
				},
1
				Event::CollatorChosen {
1
					round: 10,
1
					collator_account: 3,
1
					total_exposed_amount: 20,
1
				},
1
				Event::CollatorChosen {
1
					round: 10,
1
					collator_account: 4,
1
					total_exposed_amount: 20,
1
				},
1
				Event::NewRound {
1
					starting_block: 45,
1
					round: 10,
1
					selected_collators_number: 4,
1
					total_balance: 130,
1
				},
1
			);
1
			roll_blocks(3);
1
			assert_events_eq!(
1
				Event::Rewarded {
1
					account: 1,
1
					rewards: 15,
1
				},
1
				Event::Rewarded {
1
					account: 7,
1
					rewards: 5,
1
				},
1
				Event::Rewarded {
1
					account: 10,
1
					rewards: 5,
1
				},
1
			);
1
			roll_to_round_begin(11);
1
			// new delegation not rewarded yet
1
			assert_events_eq!(
1
				Event::CollatorChosen {
1
					round: 11,
1
					collator_account: 1,
1
					total_exposed_amount: 50,
1
				},
1
				Event::CollatorChosen {
1
					round: 11,
1
					collator_account: 2,
1
					total_exposed_amount: 40,
1
				},
1
				Event::CollatorChosen {
1
					round: 11,
1
					collator_account: 3,
1
					total_exposed_amount: 20,
1
				},
1
				Event::CollatorChosen {
1
					round: 11,
1
					collator_account: 4,
1
					total_exposed_amount: 20,
1
				},
1
				Event::NewRound {
1
					starting_block: 50,
1
					round: 11,
1
					selected_collators_number: 4,
1
					total_balance: 130,
1
				},
1
			);
1
			roll_blocks(3);
1
			assert_events_eq!(
1
				Event::Rewarded {
1
					account: 1,
1
					rewards: 15,
1
				},
1
				Event::Rewarded {
1
					account: 7,
1
					rewards: 5,
1
				},
1
				Event::Rewarded {
1
					account: 10,
1
					rewards: 5,
1
				},
1
			);
1
			roll_to_round_begin(12);
1
			// new delegation is rewarded for first time
1
			// 2 rounds after joining (`RewardPaymentDelay` = 2)
1
			assert_events_eq!(
1
				Event::CollatorChosen {
1
					round: 12,
1
					collator_account: 1,
1
					total_exposed_amount: 50,
1
				},
1
				Event::CollatorChosen {
1
					round: 12,
1
					collator_account: 2,
1
					total_exposed_amount: 40,
1
				},
1
				Event::CollatorChosen {
1
					round: 12,
1
					collator_account: 3,
1
					total_exposed_amount: 20,
1
				},
1
				Event::CollatorChosen {
1
					round: 12,
1
					collator_account: 4,
1
					total_exposed_amount: 20,
1
				},
1
				Event::NewRound {
1
					starting_block: 55,
1
					round: 12,
1
					selected_collators_number: 4,
1
					total_balance: 130,
1
				},
1
			);
1
			roll_blocks(3);
1
			assert_events_eq!(
1
				Event::Rewarded {
1
					account: 1,
1
					rewards: 14,
1
				},
1
				Event::Rewarded {
1
					account: 7,
1
					rewards: 4,
1
				},
1
				Event::Rewarded {
1
					account: 10,
1
					rewards: 4,
1
				},
1
				Event::Rewarded {
1
					account: 8,
1
					rewards: 4,
1
				},
1
			);
1
		});
1
}
#[test]
1
fn bottom_delegations_are_empty_when_top_delegations_not_full() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 20), (2, 10), (3, 10), (4, 10), (5, 10)])
1
		.with_candidates(vec![(1, 20)])
1
		.build()
1
		.execute_with(|| {
1
			// no top delegators => no bottom delegators
1
			let top_delegations = ParachainStaking::top_delegations(1).unwrap();
1
			let bottom_delegations = ParachainStaking::bottom_delegations(1).unwrap();
1
			assert!(top_delegations.delegations.is_empty());
1
			assert!(bottom_delegations.delegations.is_empty());
			// 1 delegator => 1 top delegator, 0 bottom delegators
1
			assert_ok!(ParachainStaking::delegate_with_auto_compound(
1
				RuntimeOrigin::signed(2),
1
				1,
1
				10,
1
				Percent::zero(),
1
				10,
1
				0,
1
				10
1
			));
1
			let top_delegations = ParachainStaking::top_delegations(1).unwrap();
1
			let bottom_delegations = ParachainStaking::bottom_delegations(1).unwrap();
1
			assert_eq!(top_delegations.delegations.len(), 1usize);
1
			assert!(bottom_delegations.delegations.is_empty());
			// 2 delegators => 2 top delegators, 0 bottom delegators
1
			assert_ok!(ParachainStaking::delegate_with_auto_compound(
1
				RuntimeOrigin::signed(3),
1
				1,
1
				10,
1
				Percent::zero(),
1
				10,
1
				0,
1
				10
1
			));
1
			let top_delegations = ParachainStaking::top_delegations(1).unwrap();
1
			let bottom_delegations = ParachainStaking::bottom_delegations(1).unwrap();
1
			assert_eq!(top_delegations.delegations.len(), 2usize);
1
			assert!(bottom_delegations.delegations.is_empty());
			// 3 delegators => 3 top delegators, 0 bottom delegators
1
			assert_ok!(ParachainStaking::delegate_with_auto_compound(
1
				RuntimeOrigin::signed(4),
1
				1,
1
				10,
1
				Percent::zero(),
1
				10,
1
				0,
1
				10
1
			));
1
			let top_delegations = ParachainStaking::top_delegations(1).unwrap();
1
			let bottom_delegations = ParachainStaking::bottom_delegations(1).unwrap();
1
			assert_eq!(top_delegations.delegations.len(), 3usize);
1
			assert!(bottom_delegations.delegations.is_empty());
			// 4 delegators => 4 top delegators, 0 bottom delegators
1
			assert_ok!(ParachainStaking::delegate_with_auto_compound(
1
				RuntimeOrigin::signed(5),
1
				1,
1
				10,
1
				Percent::zero(),
1
				10,
1
				0,
1
				10
1
			));
1
			let top_delegations = ParachainStaking::top_delegations(1).unwrap();
1
			let bottom_delegations = ParachainStaking::bottom_delegations(1).unwrap();
1
			assert_eq!(top_delegations.delegations.len(), 4usize);
1
			assert!(bottom_delegations.delegations.is_empty());
1
		});
1
}
#[test]
1
fn candidate_pool_updates_when_total_counted_changes() {
1
	ExtBuilder::default()
1
		.with_balances(vec![
1
			(1, 20),
1
			(3, 19),
1
			(4, 20),
1
			(5, 21),
1
			(6, 22),
1
			(7, 15),
1
			(8, 16),
1
			(9, 17),
1
			(10, 18),
1
		])
1
		.with_candidates(vec![(1, 20)])
1
		.with_delegations(vec![
1
			(3, 1, 11),
1
			(4, 1, 12),
1
			(5, 1, 13),
1
			(6, 1, 14),
1
			(7, 1, 15),
1
			(8, 1, 16),
1
			(9, 1, 17),
1
			(10, 1, 18),
1
		])
1
		.build()
1
		.execute_with(|| {
5
			fn is_candidate_pool_bond(account: u64, bond: u128) {
5
				let pool = ParachainStaking::candidate_pool();
10
				for candidate in pool.0 {
5
					if candidate.owner == account {
5
						assert_eq!(
							candidate.amount, bond,
							"Candidate Bond {:?} is Not Equal to Expected: {:?}",
							candidate.amount, bond
						);
					}
				}
5
			}
			// 15 + 16 + 17 + 18 + 20 = 86 (top 4 + self bond)
1
			is_candidate_pool_bond(1, 86);
1
			assert_ok!(ParachainStaking::delegator_bond_more(
1
				RuntimeOrigin::signed(3),
1
				1,
1
				8
1
			));
			// 3: 11 -> 19 => 3 is in top, bumps out 7
			// 16 + 17 + 18 + 19 + 20 = 90 (top 4 + self bond)
1
			is_candidate_pool_bond(1, 90);
1
			assert_ok!(ParachainStaking::delegator_bond_more(
1
				RuntimeOrigin::signed(4),
1
				1,
1
				8
1
			));
			// 4: 12 -> 20 => 4 is in top, bumps out 8
			// 17 + 18 + 19 + 20 + 20 = 94 (top 4 + self bond)
1
			is_candidate_pool_bond(1, 94);
1
			assert_ok!(ParachainStaking::schedule_delegator_bond_less(
1
				RuntimeOrigin::signed(10),
1
				1,
1
				3
1
			));
1
			roll_to(30);
1
			// 10: 18 -> 15 => 10 bumped to bottom, 8 bumped to top (- 18 + 16 = -2 for count)
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(10),
1
				10,
1
				1
1
			));
			// 16 + 17 + 19 + 20 + 20 = 92 (top 4 + self bond)
1
			is_candidate_pool_bond(1, 92);
1
			assert_ok!(ParachainStaking::schedule_delegator_bond_less(
1
				RuntimeOrigin::signed(9),
1
				1,
1
				4
1
			));
1
			roll_to(40);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(9),
1
				9,
1
				1
1
			));
			// 15 + 16 + 19 + 20 + 20 = 90 (top 4 + self bond)
1
			is_candidate_pool_bond(1, 90);
1
		});
1
}
#[test]
1
fn only_top_collators_are_counted() {
1
	ExtBuilder::default()
1
		.with_balances(vec![
1
			(1, 20),
1
			(3, 19),
1
			(4, 20),
1
			(5, 21),
1
			(6, 22),
1
			(7, 15),
1
			(8, 16),
1
			(9, 17),
1
			(10, 18),
1
		])
1
		.with_candidates(vec![(1, 20)])
1
		.with_delegations(vec![
1
			(3, 1, 11),
1
			(4, 1, 12),
1
			(5, 1, 13),
1
			(6, 1, 14),
1
			(7, 1, 15),
1
			(8, 1, 16),
1
			(9, 1, 17),
1
			(10, 1, 18),
1
		])
1
		.build()
1
		.execute_with(|| {
			// sanity check that 3-10 are delegators immediately
9
			for i in 3..11 {
8
				assert!(ParachainStaking::is_delegator(&i));
			}
1
			let collator_state = ParachainStaking::candidate_info(1).unwrap();
1
			// 15 + 16 + 17 + 18 + 20 = 86 (top 4 + self bond)
1
			assert_eq!(collator_state.total_counted, 86);
			// bump bottom to the top
1
			assert_ok!(ParachainStaking::delegator_bond_more(
1
				RuntimeOrigin::signed(3),
1
				1,
1
				8
1
			));
1
			assert_events_emitted!(Event::DelegationIncreased {
1
				delegator: 3,
1
				candidate: 1,
1
				amount: 8,
1
				in_top: true,
1
			});
1
			let collator_state = ParachainStaking::candidate_info(1).unwrap();
1
			// 16 + 17 + 18 + 19 + 20 = 90 (top 4 + self bond)
1
			assert_eq!(collator_state.total_counted, 90);
			// bump bottom to the top
1
			assert_ok!(ParachainStaking::delegator_bond_more(
1
				RuntimeOrigin::signed(4),
1
				1,
1
				8
1
			));
1
			assert_events_emitted!(Event::DelegationIncreased {
1
				delegator: 4,
1
				candidate: 1,
1
				amount: 8,
1
				in_top: true,
1
			});
1
			let collator_state = ParachainStaking::candidate_info(1).unwrap();
1
			// 17 + 18 + 19 + 20 + 20 = 94 (top 4 + self bond)
1
			assert_eq!(collator_state.total_counted, 94);
			// bump bottom to the top
1
			assert_ok!(ParachainStaking::delegator_bond_more(
1
				RuntimeOrigin::signed(5),
1
				1,
1
				8
1
			));
1
			assert_events_emitted!(Event::DelegationIncreased {
1
				delegator: 5,
1
				candidate: 1,
1
				amount: 8,
1
				in_top: true,
1
			});
1
			let collator_state = ParachainStaking::candidate_info(1).unwrap();
1
			// 18 + 19 + 20 + 21 + 20 = 98 (top 4 + self bond)
1
			assert_eq!(collator_state.total_counted, 98);
			// bump bottom to the top
1
			assert_ok!(ParachainStaking::delegator_bond_more(
1
				RuntimeOrigin::signed(6),
1
				1,
1
				8
1
			));
1
			assert_events_emitted!(Event::DelegationIncreased {
1
				delegator: 6,
1
				candidate: 1,
1
				amount: 8,
1
				in_top: true,
1
			});
1
			let collator_state = ParachainStaking::candidate_info(1).unwrap();
1
			// 19 + 20 + 21 + 22 + 20 = 102 (top 4 + self bond)
1
			assert_eq!(collator_state.total_counted, 102);
1
		});
1
}
#[test]
1
fn delegation_events_convey_correct_position() {
1
	ExtBuilder::default()
1
		.with_balances(vec![
1
			(1, 100),
1
			(2, 100),
1
			(3, 100),
1
			(4, 100),
1
			(5, 100),
1
			(6, 100),
1
			(7, 100),
1
			(8, 100),
1
			(9, 100),
1
			(10, 100),
1
		])
1
		.with_candidates(vec![(1, 20), (2, 20)])
1
		.with_delegations(vec![(3, 1, 11), (4, 1, 12), (5, 1, 13), (6, 1, 14)])
1
		.build()
1
		.execute_with(|| {
1
			let collator1_state = ParachainStaking::candidate_info(1).unwrap();
1
			// 11 + 12 + 13 + 14 + 20 = 70 (top 4 + self bond)
1
			assert_eq!(collator1_state.total_counted, 70);
			// Top delegations are full, new highest delegation is made
1
			assert_ok!(ParachainStaking::delegate_with_auto_compound(
1
				RuntimeOrigin::signed(7),
1
				1,
1
				15,
1
				Percent::zero(),
1
				10,
1
				0,
1
				10
1
			));
1
			assert_events_emitted!(Event::Delegation {
1
				delegator: 7,
1
				locked_amount: 15,
1
				candidate: 1,
1
				delegator_position: DelegatorAdded::AddedToTop { new_total: 74 },
1
				auto_compound: Percent::zero(),
1
			});
1
			let collator1_state = ParachainStaking::candidate_info(1).unwrap();
1
			// 12 + 13 + 14 + 15 + 20 = 70 (top 4 + self bond)
1
			assert_eq!(collator1_state.total_counted, 74);
			// New delegation is added to the bottom
1
			assert_ok!(ParachainStaking::delegate_with_auto_compound(
1
				RuntimeOrigin::signed(8),
1
				1,
1
				10,
1
				Percent::zero(),
1
				10,
1
				0,
1
				10
1
			));
1
			assert_events_emitted!(Event::Delegation {
1
				delegator: 8,
1
				locked_amount: 10,
1
				candidate: 1,
1
				delegator_position: DelegatorAdded::AddedToBottom,
1
				auto_compound: Percent::zero(),
1
			});
1
			let collator1_state = ParachainStaking::candidate_info(1).unwrap();
1
			// 12 + 13 + 14 + 15 + 20 = 70 (top 4 + self bond)
1
			assert_eq!(collator1_state.total_counted, 74);
			// 8 increases delegation to the top
1
			assert_ok!(ParachainStaking::delegator_bond_more(
1
				RuntimeOrigin::signed(8),
1
				1,
1
				3
1
			));
1
			assert_events_emitted!(Event::DelegationIncreased {
1
				delegator: 8,
1
				candidate: 1,
1
				amount: 3,
1
				in_top: true,
1
			});
1
			let collator1_state = ParachainStaking::candidate_info(1).unwrap();
1
			// 13 + 13 + 14 + 15 + 20 = 75 (top 4 + self bond)
1
			assert_eq!(collator1_state.total_counted, 75);
			// 3 increases delegation but stays in bottom
1
			assert_ok!(ParachainStaking::delegator_bond_more(
1
				RuntimeOrigin::signed(3),
1
				1,
1
				1
1
			));
1
			assert_events_emitted!(Event::DelegationIncreased {
1
				delegator: 3,
1
				candidate: 1,
1
				amount: 1,
1
				in_top: false,
1
			});
1
			let collator1_state = ParachainStaking::candidate_info(1).unwrap();
1
			// 13 + 13 + 14 + 15 + 20 = 75 (top 4 + self bond)
1
			assert_eq!(collator1_state.total_counted, 75);
			// 6 decreases delegation but stays in top
1
			assert_ok!(ParachainStaking::schedule_delegator_bond_less(
1
				RuntimeOrigin::signed(6),
1
				1,
1
				2
1
			));
1
			assert_events_emitted!(Event::DelegationDecreaseScheduled {
1
				delegator: 6,
1
				candidate: 1,
1
				amount_to_decrease: 2,
1
				execute_round: 3,
1
			});
1
			roll_to(30);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(6),
1
				6,
1
				1
1
			));
1
			assert_events_emitted!(Event::DelegationDecreased {
1
				delegator: 6,
1
				candidate: 1,
1
				amount: 2,
1
				in_top: true,
1
			});
1
			let collator1_state = ParachainStaking::candidate_info(1).unwrap();
1
			// 12 + 13 + 13 + 15 + 20 = 73 (top 4 + self bond)Æ’
1
			assert_eq!(collator1_state.total_counted, 73);
			// 6 decreases delegation and is bumped to bottom
1
			assert_ok!(ParachainStaking::schedule_delegator_bond_less(
1
				RuntimeOrigin::signed(6),
1
				1,
1
				1
1
			));
1
			assert_events_emitted!(Event::DelegationDecreaseScheduled {
1
				delegator: 6,
1
				candidate: 1,
1
				amount_to_decrease: 1,
1
				execute_round: 9,
1
			});
1
			roll_to(40);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(6),
1
				6,
1
				1
1
			));
1
			assert_events_emitted!(Event::DelegationDecreased {
1
				delegator: 6,
1
				candidate: 1,
1
				amount: 1,
1
				in_top: false,
1
			});
1
			let collator1_state = ParachainStaking::candidate_info(1).unwrap();
1
			// 12 + 13 + 13 + 15 + 20 = 73 (top 4 + self bond)
1
			assert_eq!(collator1_state.total_counted, 73);
1
		});
1
}
#[test]
1
fn no_rewards_paid_until_after_reward_payment_delay() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 20), (2, 20), (3, 20)])
1
		.with_candidates(vec![(1, 20), (2, 20), (3, 20)])
1
		.build()
1
		.execute_with(|| {
1
			// payouts for round 1
1
			set_author(1, 1, POINTS_PER_BLOCK);
1
			set_author(1, 2, POINTS_PER_BLOCK * 2);
1
			set_author(1, 3, POINTS_PER_BLOCK * 2);
1

            
1
			roll_to_round_begin(2);
1
			assert_events_eq!(
1
				Event::CollatorChosen {
1
					round: 2,
1
					collator_account: 1,
1
					total_exposed_amount: 20,
1
				},
1
				Event::CollatorChosen {
1
					round: 2,
1
					collator_account: 2,
1
					total_exposed_amount: 20,
1
				},
1
				Event::CollatorChosen {
1
					round: 2,
1
					collator_account: 3,
1
					total_exposed_amount: 20,
1
				},
1
				Event::NewRound {
1
					starting_block: 5,
1
					round: 2,
1
					selected_collators_number: 3,
1
					total_balance: 60,
1
				},
1
			);
1
			roll_to_round_begin(3);
1
			assert_events_eq!(
1
				Event::CollatorChosen {
1
					round: 3,
1
					collator_account: 1,
1
					total_exposed_amount: 20,
1
				},
1
				Event::CollatorChosen {
1
					round: 3,
1
					collator_account: 2,
1
					total_exposed_amount: 20,
1
				},
1
				Event::CollatorChosen {
1
					round: 3,
1
					collator_account: 3,
1
					total_exposed_amount: 20,
1
				},
1
				Event::NewRound {
1
					starting_block: 10,
1
					round: 3,
1
					selected_collators_number: 3,
1
					total_balance: 60,
1
				},
1
			);
1
			roll_blocks(1);
1
			assert_events_eq!(Event::Rewarded {
1
				account: 3,
1
				rewards: 1,
1
			});
1
			roll_blocks(1);
1
			assert_events_eq!(Event::Rewarded {
1
				account: 1,
1
				rewards: 0,
1
			});
1
			roll_blocks(1);
1
			assert_events_eq!(Event::Rewarded {
1
				account: 2,
1
				rewards: 1,
1
			});
			// there should be no more payments in this round...
1
			let num_blocks_rolled = roll_to_round_end(3);
1
			assert_no_events!();
1
			assert_eq!(num_blocks_rolled, 1);
1
		});
1
}
#[test]
1
fn deferred_payment_storage_items_are_cleaned_up() {
	use crate::*;
	// this test sets up two collators, gives them points in round one, and focuses on the
	// storage over the next several blocks to show that it is properly cleaned up
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 20), (2, 20)])
1
		.with_candidates(vec![(1, 20), (2, 20)])
1
		.build()
1
		.execute_with(|| {
1
			set_author(1, 1, POINTS_PER_BLOCK * 3);
1
			set_author(1, 2, POINTS_PER_BLOCK * 2);
1

            
1
			// reflects genesis?
1
			assert!(<AtStake<Test>>::contains_key(1, 1));
1
			assert!(<AtStake<Test>>::contains_key(1, 2));
1
			roll_to_round_begin(2);
1
			assert_events_eq!(
1
				Event::CollatorChosen {
1
					round: 2,
1
					collator_account: 1,
1
					total_exposed_amount: 20,
1
				},
1
				Event::CollatorChosen {
1
					round: 2,
1
					collator_account: 2,
1
					total_exposed_amount: 20,
1
				},
1
				Event::NewRound {
1
					starting_block: 5,
1
					round: 2,
1
					selected_collators_number: 2,
1
					total_balance: 40,
1
				},
1
			);
			// we should have AtStake snapshots as soon as we start a round...
1
			assert!(<AtStake<Test>>::contains_key(2, 1));
1
			assert!(<AtStake<Test>>::contains_key(2, 2));
			// ...and it should persist until the round is fully paid out
1
			assert!(<AtStake<Test>>::contains_key(1, 1));
1
			assert!(<AtStake<Test>>::contains_key(1, 2));
1
			assert!(
1
				<Points<Test>>::contains_key(1),
				"Points should be populated during current round"
			);
1
			assert!(
1
				!<Points<Test>>::contains_key(2),
				"Points should not be populated until author noted"
			);
			// first payout occurs in round 3
1
			roll_to_round_begin(3);
1
			assert_events_eq!(
1
				Event::CollatorChosen {
1
					round: 3,
1
					collator_account: 1,
1
					total_exposed_amount: 20,
1
				},
1
				Event::CollatorChosen {
1
					round: 3,
1
					collator_account: 2,
1
					total_exposed_amount: 20,
1
				},
1
				Event::NewRound {
1
					starting_block: 10,
1
					round: 3,
1
					selected_collators_number: 2,
1
					total_balance: 40,
1
				},
1
			);
1
			roll_blocks(1);
1
			assert_events_eq!(Event::Rewarded {
1
				account: 1,
1
				rewards: 1,
1
			},);
			// payouts should exist for past rounds that haven't been paid out yet..
1
			assert!(<AtStake<Test>>::contains_key(3, 1));
1
			assert!(<AtStake<Test>>::contains_key(3, 2));
1
			assert!(<AtStake<Test>>::contains_key(2, 1));
1
			assert!(<AtStake<Test>>::contains_key(2, 2));
1
			assert!(
1
				<DelayedPayouts<Test>>::contains_key(1),
				"DelayedPayouts should be populated after RewardPaymentDelay"
			);
1
			assert!(<Points<Test>>::contains_key(1));
1
			assert!(<DelayedPayouts<Test>>::contains_key(2));
1
			assert!(
1
				<Points<Test>>::contains_key(2),
				"We awarded points for round 2"
			);
1
			assert!(!<DelayedPayouts<Test>>::contains_key(3));
1
			assert!(
1
				<Points<Test>>::contains_key(3),
				"We awarded points for round 3"
			);
			// collator 1 has been paid in this last block and associated storage cleaned up
1
			assert!(!<AtStake<Test>>::contains_key(1, 1));
1
			assert!(!<AwardedPts<Test>>::contains_key(1, 1));
			// but collator 2 hasn't been paid
1
			assert!(<AtStake<Test>>::contains_key(1, 2));
1
			assert!(<AwardedPts<Test>>::contains_key(1, 2));
			// second payout occurs in next block
1
			roll_blocks(1);
1
			assert_events_eq!(Event::Rewarded {
1
				account: 2,
1
				rewards: 0,
1
			},);
1
			roll_to_round_begin(4);
1
			assert_events_eq!(
1
				Event::CollatorChosen {
1
					round: 4,
1
					collator_account: 1,
1
					total_exposed_amount: 20,
1
				},
1
				Event::CollatorChosen {
1
					round: 4,
1
					collator_account: 2,
1
					total_exposed_amount: 20,
1
				},
1
				Event::NewRound {
1
					starting_block: 15,
1
					round: 4,
1
					selected_collators_number: 2,
1
					total_balance: 40,
1
				},
1
			);
			// collators have both been paid and storage fully cleaned up for round 1
1
			assert!(!<AtStake<Test>>::contains_key(1, 2));
1
			assert!(!<AwardedPts<Test>>::contains_key(1, 2));
1
			assert!(!<Points<Test>>::contains_key(1)); // points should be cleaned up
1
			assert!(!<DelayedPayouts<Test>>::contains_key(1));
1
			roll_to_round_end(4);
1

            
1
			// no more events expected
1
			assert_no_events!();
1
		});
1
}
#[test]
1
fn deferred_payment_and_at_stake_storage_items_cleaned_up_for_candidates_not_producing_blocks() {
	use crate::*;
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 20), (2, 20), (3, 20)])
1
		.with_candidates(vec![(1, 20), (2, 20), (3, 20)])
1
		.build()
1
		.execute_with(|| {
1
			// candidate 3 will not produce blocks
1
			set_author(1, 1, POINTS_PER_BLOCK * 3);
1
			set_author(1, 2, POINTS_PER_BLOCK * 2);
1

            
1
			// reflects genesis?
1
			assert!(<AtStake<Test>>::contains_key(1, 1));
1
			assert!(<AtStake<Test>>::contains_key(1, 2));
1
			roll_to_round_begin(2);
1
			assert!(<AtStake<Test>>::contains_key(1, 1));
1
			assert!(<AtStake<Test>>::contains_key(1, 2));
1
			assert!(<AtStake<Test>>::contains_key(1, 3));
1
			assert!(<AwardedPts<Test>>::contains_key(1, 1));
1
			assert!(<AwardedPts<Test>>::contains_key(1, 2));
1
			assert!(!<AwardedPts<Test>>::contains_key(1, 3));
1
			assert!(<Points<Test>>::contains_key(1));
1
			roll_to_round_begin(3);
1
			assert!(<DelayedPayouts<Test>>::contains_key(1));
			// all storage items must be cleaned up
1
			roll_to_round_begin(4);
1
			assert!(!<AtStake<Test>>::contains_key(1, 1));
1
			assert!(!<AtStake<Test>>::contains_key(1, 2));
1
			assert!(!<AtStake<Test>>::contains_key(1, 3));
1
			assert!(!<AwardedPts<Test>>::contains_key(1, 1));
1
			assert!(!<AwardedPts<Test>>::contains_key(1, 2));
1
			assert!(!<AwardedPts<Test>>::contains_key(1, 3));
1
			assert!(!<Points<Test>>::contains_key(1));
1
			assert!(!<DelayedPayouts<Test>>::contains_key(1));
1
		});
1
}
#[test]
1
fn deferred_payment_steady_state_event_flow() {
	// this test "flows" through a number of rounds, asserting that certain things do/don't happen
	// once the staking pallet is in a "steady state" (specifically, once we are past the first few
	// rounds to clear RewardPaymentDelay)
	use crate::mock::System;
1
	ExtBuilder::default()
1
		.with_balances(vec![
1
			// collators
1
			(1, 200),
1
			(2, 200),
1
			(3, 200),
1
			(4, 200),
1
			// delegators
1
			(11, 200),
1
			(22, 200),
1
			(33, 200),
1
			(44, 200),
1
			// burn account, see `reset_issuance()`
1
			(111, 1000),
1
		])
1
		.with_candidates(vec![(1, 200), (2, 200), (3, 200), (4, 200)])
1
		.with_delegations(vec![
1
			// delegator 11 delegates 100 to 1 and 2
1
			(11, 1, 100),
1
			(11, 2, 100),
1
			// delegator 22 delegates 100 to 2 and 3
1
			(22, 2, 100),
1
			(22, 3, 100),
1
			// delegator 33 delegates 100 to 3 and 4
1
			(33, 3, 100),
1
			(33, 4, 100),
1
			// delegator 44 delegates 100 to 4 and 1
1
			(44, 4, 100),
1
			(44, 1, 100),
1
		])
1
		.build()
1
		.execute_with(|| {
1
			// convenience to set the round points consistently
3
			let set_round_points = |round: BlockNumber| {
3
				set_author(round as BlockNumber, 1, 2 * POINTS_PER_ROUND);
3
				set_author(round as BlockNumber, 2, POINTS_PER_ROUND);
3
				set_author(round as BlockNumber, 3, POINTS_PER_ROUND);
3
				set_author(round as BlockNumber, 4, POINTS_PER_ROUND);
3
			};
			// grab initial issuance -- we will reset it before round issuance is calculated so that
			// it is consistent every round
1
			let account: AccountId = 111;
1
			let initial_issuance = Balances::total_issuance();
2
			let reset_issuance = || {
2
				let new_issuance = Balances::total_issuance();
2
				let amount_to_burn = new_issuance - initial_issuance;
2
				let _ = Balances::burn(Some(account).into(), amount_to_burn, false);
2
				System::assert_last_event(RuntimeEvent::Balances(BalancesEvent::Burned {
2
					who: account,
2
					amount: amount_to_burn,
2
				}));
2
				Balances::settle(
2
					&account,
2
					PositiveImbalance::new(amount_to_burn),
2
					WithdrawReasons::FEE,
2
					ExistenceRequirement::AllowDeath,
2
				)
2
				.expect("Account can absorb burn");
2
			};
			// fn to roll through the first RewardPaymentDelay rounds. returns new round index
1
			let roll_through_initial_rounds = |mut round: BlockNumber| -> BlockNumber {
3
				while round < crate::mock::RewardPaymentDelay::get() + 1 {
2
					set_round_points(round);
2

            
2
					roll_to_round_end(round);
2
					round += 1;
2
				}
1
				reset_issuance();
1

            
1
				round
1
			};
			// roll through a "steady state" round and make all of our assertions
			// returns new round index
1
			let roll_through_steady_state_round = |round: BlockNumber| -> BlockNumber {
1
				let num_rounds_rolled = roll_to_round_begin(round);
1
				assert!(
1
					num_rounds_rolled <= 1,
					"expected to be at round begin already"
				);
1
				assert_events_eq!(
1
					Event::CollatorChosen {
1
						round: round as u32,
1
						collator_account: 1,
1
						total_exposed_amount: 400,
1
					},
1
					Event::CollatorChosen {
1
						round: round as u32,
1
						collator_account: 2,
1
						total_exposed_amount: 400,
1
					},
1
					Event::CollatorChosen {
1
						round: round as u32,
1
						collator_account: 3,
1
						total_exposed_amount: 400,
1
					},
1
					Event::CollatorChosen {
1
						round: round as u32,
1
						collator_account: 4,
1
						total_exposed_amount: 400,
1
					},
1
					Event::NewRound {
1
						starting_block: (round as u32 - 1) * 5,
1
						round: round as u32,
1
						selected_collators_number: 4,
1
						total_balance: 1600,
1
					},
1
				);
1
				set_round_points(round);
1

            
1
				roll_blocks(1);
1
				assert_events_eq!(
1
					Event::Rewarded {
1
						account: 3,
1
						rewards: 13,
1
					},
1
					Event::Rewarded {
1
						account: 22,
1
						rewards: 4,
1
					},
1
					Event::Rewarded {
1
						account: 33,
1
						rewards: 4,
1
					},
1
				);
1
				roll_blocks(1);
1
				assert_events_eq!(
1
					Event::Rewarded {
1
						account: 4,
1
						rewards: 13,
1
					},
1
					Event::Rewarded {
1
						account: 33,
1
						rewards: 4,
1
					},
1
					Event::Rewarded {
1
						account: 44,
1
						rewards: 4,
1
					},
1
				);
1
				roll_blocks(1);
1
				assert_events_eq!(
1
					Event::Rewarded {
1
						account: 1,
1
						rewards: 27,
1
					},
1
					Event::Rewarded {
1
						account: 11,
1
						rewards: 9,
1
					},
1
					Event::Rewarded {
1
						account: 44,
1
						rewards: 9,
1
					},
1
				);
1
				roll_blocks(1);
1
				assert_events_eq!(
1
					Event::Rewarded {
1
						account: 2,
1
						rewards: 13,
1
					},
1
					Event::Rewarded {
1
						account: 11,
1
						rewards: 4,
1
					},
1
					Event::Rewarded {
1
						account: 22,
1
						rewards: 4,
1
					},
1
				);
1
				roll_blocks(1);
1
				// Since we defer first deferred staking payout, this test have the maximum amout of
1
				// supported collators. This eman that the next round is trigerred one block after
1
				// the last reward.
1
				//assert_no_events!();
1

            
1
				let num_rounds_rolled = roll_to_round_end(round);
1
				assert_eq!(num_rounds_rolled, 0, "expected to be at round end already");
1
				reset_issuance();
1

            
1
				round + 1
1
			};
1
			let mut round = 1;
1
			round = roll_through_initial_rounds(round); // we should be at RewardPaymentDelay
2
			for _ in 1..2 {
1
				round = roll_through_steady_state_round(round);
1
			}
1
		});
1
}
#[test]
1
fn delegation_kicked_from_bottom_removes_pending_request() {
1
	ExtBuilder::default()
1
		.with_balances(vec![
1
			(1, 30),
1
			(2, 29),
1
			(3, 20),
1
			(4, 20),
1
			(5, 20),
1
			(6, 20),
1
			(7, 20),
1
			(8, 20),
1
			(9, 20),
1
			(10, 20),
1
			(11, 30),
1
		])
1
		.with_candidates(vec![(1, 30), (11, 30)])
1
		.with_delegations(vec![
1
			(2, 1, 19),
1
			(2, 11, 10), // second delegation so not left after first is kicked
1
			(3, 1, 20),
1
			(4, 1, 20),
1
			(5, 1, 20),
1
			(6, 1, 20),
1
			(7, 1, 20),
1
			(8, 1, 20),
1
			(9, 1, 20),
1
		])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
1
				1
1
			));
			// 10 delegates to full 1 => kicks lowest delegation (2, 19)
1
			assert_ok!(ParachainStaking::delegate_with_auto_compound(
1
				RuntimeOrigin::signed(10),
1
				1,
1
				20,
1
				Percent::zero(),
1
				8,
1
				0,
1
				0,
1
			));
			// check the event
1
			assert_events_emitted!(Event::DelegationKicked {
1
				delegator: 2,
1
				candidate: 1,
1
				unstaked_amount: 19,
1
			});
1
			// ensure request DNE
1
			assert!(!ParachainStaking::delegation_scheduled_requests(&1)
1
				.iter()
1
				.any(|x| x.delegator == 2));
1
		});
1
}
#[test]
1
fn no_selected_candidates_defaults_to_last_round_collators() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 30), (3, 30), (4, 30), (5, 30)])
1
		.with_candidates(vec![(1, 30), (2, 30), (3, 30), (4, 30), (5, 30)])
1
		.build()
1
		.execute_with(|| {
1
			roll_to_round_begin(1);
			// schedule to leave
6
			for i in 1..6 {
5
				assert_ok!(ParachainStaking::schedule_leave_candidates(
5
					RuntimeOrigin::signed(i),
5
					5
5
				));
			}
1
			let old_round = ParachainStaking::round().current;
1
			let old_selected_candidates = ParachainStaking::selected_candidates();
1
			let mut old_at_stake_snapshots = Vec::new();
5
			for account in old_selected_candidates.clone() {
5
				old_at_stake_snapshots.push(<AtStake<Test>>::get(old_round, account));
5
			}
1
			roll_to_round_begin(3);
			// execute leave
6
			for i in 1..6 {
5
				assert_ok!(ParachainStaking::execute_leave_candidates(
5
					RuntimeOrigin::signed(i),
5
					i,
5
					0,
5
				));
			}
			// next round
1
			roll_to_round_begin(4);
1
			let new_round = ParachainStaking::round().current;
1
			// check AtStake matches previous
1
			let new_selected_candidates = ParachainStaking::selected_candidates();
1
			assert_eq!(old_selected_candidates, new_selected_candidates);
1
			let mut index = 0usize;
6
			for account in new_selected_candidates {
5
				assert_eq!(
5
					old_at_stake_snapshots[index],
5
					<AtStake<Test>>::get(new_round, account)
5
				);
5
				index += 1usize;
			}
1
		});
1
}
#[test]
1
fn test_delegator_scheduled_for_revoke_is_rewarded_for_previous_rounds_but_not_for_future() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 20), (2, 40), (3, 20), (4, 20)])
1
		.with_candidates(vec![(1, 20), (3, 20), (4, 20)])
1
		.with_delegations(vec![(2, 1, 10), (2, 3, 10)])
1
		.build()
1
		.execute_with(|| {
1
			// preset rewards for rounds 1, 2 and 3
3
			(1..=3).for_each(|round| set_author(round, 1, POINTS_PER_ROUND));
1

            
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
1
				1
1
			));
1
			assert_events_eq!(Event::DelegationRevocationScheduled {
1
				round: 1,
1
				delegator: 2,
1
				candidate: 1,
1
				scheduled_exit: 3,
1
			});
1
			let collator = ParachainStaking::candidate_info(1).expect("candidate must exist");
1
			assert_eq!(
				1, collator.delegation_count,
				"collator's delegator count was reduced unexpectedly"
			);
1
			assert_eq!(
				30, collator.total_counted,
				"collator's total was reduced unexpectedly"
			);
1
			roll_to_round_begin(3);
1
			assert_events_emitted_match!(Event::NewRound { round: 3, .. });
1
			roll_blocks(3);
1
			assert_events_eq!(
1
				Event::Rewarded {
1
					account: 1,
1
					rewards: 2,
1
				},
1
				Event::Rewarded {
1
					account: 2,
1
					rewards: 1,
1
				},
1
			);
1
			roll_to_round_begin(4);
1
			assert_events_emitted_match!(Event::NewRound { round: 4, .. });
1
			roll_blocks(3);
1
			assert_events_eq!(Event::Rewarded {
1
				account: 1,
1
				rewards: 2,
1
			},);
1
			let collator_snapshot =
1
				ParachainStaking::at_stake(ParachainStaking::round().current, 1)
1
					.unwrap_or_default();
1
			assert_eq!(
1
				1,
1
				collator_snapshot.delegations.len(),
				"collator snapshot's delegator count was reduced unexpectedly"
			);
1
			assert_eq!(
				20, collator_snapshot.total,
				"collator snapshot's total was reduced unexpectedly",
			);
1
		});
1
}
#[test]
1
fn test_delegator_scheduled_for_revoke_is_rewarded_when_request_cancelled() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 20), (2, 40), (3, 20), (4, 20)])
1
		.with_candidates(vec![(1, 20), (3, 20), (4, 20)])
1
		.with_delegations(vec![(2, 1, 10), (2, 3, 10)])
1
		.build()
1
		.execute_with(|| {
1
			// preset rewards for rounds 2, 3 and 4
3
			(2..=4).for_each(|round| set_author(round, 1, POINTS_PER_ROUND));
1

            
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
1
				1
1
			));
1
			assert_events_eq!(Event::DelegationRevocationScheduled {
1
				round: 1,
1
				delegator: 2,
1
				candidate: 1,
1
				scheduled_exit: 3,
1
			});
1
			let collator = ParachainStaking::candidate_info(1).expect("candidate must exist");
1
			assert_eq!(
				1, collator.delegation_count,
				"collator's delegator count was reduced unexpectedly"
			);
1
			assert_eq!(
				30, collator.total_counted,
				"collator's total was reduced unexpectedly"
			);
1
			roll_to_round_begin(2);
1
			assert_ok!(ParachainStaking::cancel_delegation_request(
1
				RuntimeOrigin::signed(2),
1
				1
1
			));
1
			roll_to_round_begin(4);
1
			assert_events_emitted_match!(Event::NewRound { round: 4, .. });
1
			roll_blocks(3);
1
			assert_events_eq!(Event::Rewarded {
1
				account: 1,
1
				rewards: 2,
1
			},);
1
			let collator_snapshot =
1
				ParachainStaking::at_stake(ParachainStaking::round().current, 1)
1
					.unwrap_or_default();
1
			assert_eq!(
1
				1,
1
				collator_snapshot.delegations.len(),
				"collator snapshot's delegator count was reduced unexpectedly"
			);
1
			assert_eq!(
				30, collator_snapshot.total,
				"collator snapshot's total was reduced unexpectedly",
			);
1
			roll_to_round_begin(5);
1
			assert_events_emitted_match!(Event::NewRound { round: 5, .. });
1
			roll_blocks(3);
1
			assert_events_eq!(
1
				Event::Rewarded {
1
					account: 1,
1
					rewards: 1,
1
				},
1
				Event::Rewarded {
1
					account: 2,
1
					rewards: 1,
1
				},
1
			);
1
		});
1
}
#[test]
1
fn test_delegator_scheduled_for_bond_decrease_is_rewarded_for_previous_rounds_but_less_for_future()
1
{
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 20), (2, 40), (3, 20), (4, 20)])
1
		.with_candidates(vec![(1, 20), (3, 20), (4, 20)])
1
		.with_delegations(vec![(2, 1, 20), (2, 3, 10)])
1
		.build()
1
		.execute_with(|| {
1
			// preset rewards for rounds 1, 2 and 3
3
			(1..=3).for_each(|round| set_author(round, 1, POINTS_PER_ROUND));
1

            
1
			assert_ok!(ParachainStaking::schedule_delegator_bond_less(
1
				RuntimeOrigin::signed(2),
1
				1,
1
				10,
1
			));
1
			assert_events_eq!(Event::DelegationDecreaseScheduled {
1
				execute_round: 3,
1
				delegator: 2,
1
				candidate: 1,
1
				amount_to_decrease: 10,
1
			});
1
			let collator = ParachainStaking::candidate_info(1).expect("candidate must exist");
1
			assert_eq!(
				1, collator.delegation_count,
				"collator's delegator count was reduced unexpectedly"
			);
1
			assert_eq!(
				40, collator.total_counted,
				"collator's total was reduced unexpectedly"
			);
1
			roll_to_round_begin(3);
1
			assert_events_emitted_match!(Event::NewRound { round: 3, .. });
1
			roll_blocks(3);
1
			assert_events_eq!(
1
				Event::Rewarded {
1
					account: 1,
1
					rewards: 2,
1
				},
1
				Event::Rewarded {
1
					account: 2,
1
					rewards: 1,
1
				},
1
			);
1
			roll_to_round_begin(4);
1
			assert_events_emitted_match!(Event::NewRound { round: 4, .. });
1
			roll_blocks(3);
1
			assert_events_eq!(
1
				Event::Rewarded {
1
					account: 1,
1
					rewards: 1,
1
				},
1
				Event::Rewarded {
1
					account: 2,
1
					rewards: 1,
1
				},
1
			);
1
			let collator_snapshot =
1
				ParachainStaking::at_stake(ParachainStaking::round().current, 1)
1
					.unwrap_or_default();
1
			assert_eq!(
1
				1,
1
				collator_snapshot.delegations.len(),
				"collator snapshot's delegator count was reduced unexpectedly"
			);
1
			assert_eq!(
				30, collator_snapshot.total,
				"collator snapshot's total was reduced unexpectedly",
			);
1
		});
1
}
#[test]
1
fn test_delegator_scheduled_for_bond_decrease_is_rewarded_when_request_cancelled() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 20), (2, 40), (3, 20), (4, 20)])
1
		.with_candidates(vec![(1, 20), (3, 20), (4, 20)])
1
		.with_delegations(vec![(2, 1, 20), (2, 3, 10)])
1
		.build()
1
		.execute_with(|| {
1
			// preset rewards for rounds 2, 3 and 4
3
			(2..=4).for_each(|round| set_author(round, 1, POINTS_PER_ROUND));
1

            
1
			assert_ok!(ParachainStaking::schedule_delegator_bond_less(
1
				RuntimeOrigin::signed(2),
1
				1,
1
				10,
1
			));
1
			assert_events_eq!(Event::DelegationDecreaseScheduled {
1
				execute_round: 3,
1
				delegator: 2,
1
				candidate: 1,
1
				amount_to_decrease: 10,
1
			});
1
			let collator = ParachainStaking::candidate_info(1).expect("candidate must exist");
1
			assert_eq!(
				1, collator.delegation_count,
				"collator's delegator count was reduced unexpectedly"
			);
1
			assert_eq!(
				40, collator.total_counted,
				"collator's total was reduced unexpectedly"
			);
1
			roll_to_round_begin(2);
1
			assert_ok!(ParachainStaking::cancel_delegation_request(
1
				RuntimeOrigin::signed(2),
1
				1
1
			));
1
			roll_to_round_begin(4);
1
			assert_events_emitted_match!(Event::NewRound { round: 4, .. });
1
			roll_blocks(3);
1
			assert_events_eq!(
1
				Event::Rewarded {
1
					account: 1,
1
					rewards: 1,
1
				},
1
				Event::Rewarded {
1
					account: 2,
1
					rewards: 1,
1
				},
1
			);
1
			let collator_snapshot =
1
				ParachainStaking::at_stake(ParachainStaking::round().current, 1)
1
					.unwrap_or_default();
1
			assert_eq!(
1
				1,
1
				collator_snapshot.delegations.len(),
				"collator snapshot's delegator count was reduced unexpectedly"
			);
1
			assert_eq!(
				40, collator_snapshot.total,
				"collator snapshot's total was reduced unexpectedly",
			);
1
			roll_to_round_begin(5);
1
			assert_events_emitted_match!(Event::NewRound { round: 5, .. });
1
			roll_blocks(3);
1
			assert_events_eq!(
1
				Event::Rewarded {
1
					account: 1,
1
					rewards: 1,
1
				},
1
				Event::Rewarded {
1
					account: 2,
1
					rewards: 1,
1
				},
1
			);
1
		});
1
}
#[test]
1
fn test_delegator_scheduled_for_leave_is_rewarded_for_previous_rounds_but_not_for_future() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 20), (2, 40), (3, 20), (4, 20)])
1
		.with_candidates(vec![(1, 20), (3, 20), (4, 20)])
1
		.with_delegations(vec![(2, 1, 10), (2, 3, 10)])
1
		.build()
1
		.execute_with(|| {
1
			// preset rewards for rounds 1, 2 and 3
3
			(1..=3).for_each(|round| set_author(round, 1, POINTS_PER_ROUND));
1

            
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
1
				1,
1
			));
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
1
				3,
1
			));
1
			assert_events_eq!(
1
				Event::DelegationRevocationScheduled {
1
					round: 1,
1
					delegator: 2,
1
					candidate: 1,
1
					scheduled_exit: 3,
1
				},
1
				Event::DelegationRevocationScheduled {
1
					round: 1,
1
					delegator: 2,
1
					candidate: 3,
1
					scheduled_exit: 3,
1
				},
1
			);
1
			let collator = ParachainStaking::candidate_info(1).expect("candidate must exist");
1
			assert_eq!(
				1, collator.delegation_count,
				"collator's delegator count was reduced unexpectedly"
			);
1
			assert_eq!(
				30, collator.total_counted,
				"collator's total was reduced unexpectedly"
			);
1
			roll_to_round_begin(3);
1
			assert_events_emitted_match!(Event::NewRound { round: 3, .. });
1
			roll_blocks(3);
1
			assert_events_eq!(
1
				Event::Rewarded {
1
					account: 1,
1
					rewards: 2,
1
				},
1
				Event::Rewarded {
1
					account: 2,
1
					rewards: 1,
1
				},
1
			);
1
			roll_to_round_begin(4);
1
			assert_events_emitted_match!(Event::NewRound { round: 4, .. });
1
			roll_blocks(3);
1
			assert_events_eq!(Event::Rewarded {
1
				account: 1,
1
				rewards: 2,
1
			},);
1
			let collator_snapshot =
1
				ParachainStaking::at_stake(ParachainStaking::round().current, 1)
1
					.unwrap_or_default();
1
			assert_eq!(
1
				1,
1
				collator_snapshot.delegations.len(),
				"collator snapshot's delegator count was reduced unexpectedly"
			);
1
			assert_eq!(
				20, collator_snapshot.total,
				"collator snapshot's total was reduced unexpectedly",
			);
1
		});
1
}
#[test]
1
fn test_delegator_scheduled_for_leave_is_rewarded_when_request_cancelled() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 20), (2, 40), (3, 20), (4, 20)])
1
		.with_candidates(vec![(1, 20), (3, 20), (4, 20)])
1
		.with_delegations(vec![(2, 1, 10), (2, 3, 10)])
1
		.build()
1
		.execute_with(|| {
1
			// preset rewards for rounds 2, 3 and 4
3
			(2..=4).for_each(|round| set_author(round, 1, POINTS_PER_ROUND));
1

            
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
1
				1,
1
			));
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
1
				3,
1
			));
1
			assert_events_eq!(
1
				Event::DelegationRevocationScheduled {
1
					round: 1,
1
					delegator: 2,
1
					candidate: 1,
1
					scheduled_exit: 3,
1
				},
1
				Event::DelegationRevocationScheduled {
1
					round: 1,
1
					delegator: 2,
1
					candidate: 3,
1
					scheduled_exit: 3,
1
				},
1
			);
1
			let collator = ParachainStaking::candidate_info(1).expect("candidate must exist");
1
			assert_eq!(
				1, collator.delegation_count,
				"collator's delegator count was reduced unexpectedly"
			);
1
			assert_eq!(
				30, collator.total_counted,
				"collator's total was reduced unexpectedly"
			);
1
			roll_to_round_begin(2);
1
			assert_ok!(ParachainStaking::cancel_delegation_request(
1
				RuntimeOrigin::signed(2),
1
				1,
1
			));
1
			assert_ok!(ParachainStaking::cancel_delegation_request(
1
				RuntimeOrigin::signed(2),
1
				3,
1
			));
1
			roll_to_round_begin(4);
1
			assert_events_emitted_match!(Event::NewRound { round: 4, .. });
1
			roll_blocks(3);
1
			assert_events_eq!(Event::Rewarded {
1
				account: 1,
1
				rewards: 2,
1
			},);
1
			let collator_snapshot =
1
				ParachainStaking::at_stake(ParachainStaking::round().current, 1)
1
					.unwrap_or_default();
1
			assert_eq!(
1
				1,
1
				collator_snapshot.delegations.len(),
				"collator snapshot's delegator count was reduced unexpectedly"
			);
1
			assert_eq!(
				30, collator_snapshot.total,
				"collator snapshot's total was reduced unexpectedly",
			);
1
			roll_to_round_begin(5);
1
			assert_events_emitted_match!(Event::NewRound { round: 5, .. });
1
			roll_blocks(3);
1
			assert_events_eq!(
1
				Event::Rewarded {
1
					account: 1,
1
					rewards: 1,
1
				},
1
				Event::Rewarded {
1
					account: 2,
1
					rewards: 1,
1
				},
1
			);
1
		});
1
}
#[test]
1
fn test_delegation_request_exists_returns_false_when_nothing_exists() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 25)])
1
		.with_candidates(vec![(1, 30)])
1
		.with_delegations(vec![(2, 1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert!(!ParachainStaking::delegation_request_exists(&1, &2));
1
		});
1
}
#[test]
1
fn test_delegation_request_exists_returns_true_when_decrease_exists() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 25)])
1
		.with_candidates(vec![(1, 30)])
1
		.with_delegations(vec![(2, 1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			<DelegationScheduledRequests<Test>>::insert(
1
				1,
1
				BoundedVec::try_from(vec![ScheduledRequest {
1
					delegator: 2,
1
					when_executable: 3,
1
					action: DelegationAction::Decrease(5),
1
				}])
1
				.expect("must succeed"),
1
			);
1
			assert!(ParachainStaking::delegation_request_exists(&1, &2));
1
		});
1
}
#[test]
1
fn test_delegation_request_exists_returns_true_when_revoke_exists() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 25)])
1
		.with_candidates(vec![(1, 30)])
1
		.with_delegations(vec![(2, 1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			<DelegationScheduledRequests<Test>>::insert(
1
				1,
1
				BoundedVec::try_from(vec![ScheduledRequest {
1
					delegator: 2,
1
					when_executable: 3,
1
					action: DelegationAction::Revoke(5),
1
				}])
1
				.expect("must succeed"),
1
			);
1
			assert!(ParachainStaking::delegation_request_exists(&1, &2));
1
		});
1
}
#[test]
1
fn test_delegation_request_revoke_exists_returns_false_when_nothing_exists() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 25)])
1
		.with_candidates(vec![(1, 30)])
1
		.with_delegations(vec![(2, 1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert!(!ParachainStaking::delegation_request_revoke_exists(&1, &2));
1
		});
1
}
#[test]
1
fn test_delegation_request_revoke_exists_returns_false_when_decrease_exists() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 25)])
1
		.with_candidates(vec![(1, 30)])
1
		.with_delegations(vec![(2, 1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			<DelegationScheduledRequests<Test>>::insert(
1
				1,
1
				BoundedVec::try_from(vec![ScheduledRequest {
1
					delegator: 2,
1
					when_executable: 3,
1
					action: DelegationAction::Decrease(5),
1
				}])
1
				.expect("must succeed"),
1
			);
1
			assert!(!ParachainStaking::delegation_request_revoke_exists(&1, &2));
1
		});
1
}
#[test]
1
fn test_delegation_request_revoke_exists_returns_true_when_revoke_exists() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 25)])
1
		.with_candidates(vec![(1, 30)])
1
		.with_delegations(vec![(2, 1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			<DelegationScheduledRequests<Test>>::insert(
1
				1,
1
				BoundedVec::try_from(vec![ScheduledRequest {
1
					delegator: 2,
1
					when_executable: 3,
1
					action: DelegationAction::Revoke(5),
1
				}])
1
				.expect("must succeed"),
1
			);
1
			assert!(ParachainStaking::delegation_request_revoke_exists(&1, &2));
1
		});
1
}
#[test]
1
fn test_hotfix_remove_delegation_requests_exited_candidates_cleans_up() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 20)])
1
		.with_candidates(vec![(1, 20)])
1
		.build()
1
		.execute_with(|| {
1
			// invalid state
1
			<DelegationScheduledRequests<Test>>::insert(2, BoundedVec::default());
1
			<DelegationScheduledRequests<Test>>::insert(3, BoundedVec::default());
1
			assert_ok!(
1
				ParachainStaking::hotfix_remove_delegation_requests_exited_candidates(
1
					RuntimeOrigin::signed(1),
1
					vec![2, 3, 4] // 4 does not exist, but is OK for idempotency
1
				)
1
			);
1
			assert!(!<DelegationScheduledRequests<Test>>::contains_key(2));
1
			assert!(!<DelegationScheduledRequests<Test>>::contains_key(3));
1
		});
1
}
#[test]
1
fn test_hotfix_remove_delegation_requests_exited_candidates_cleans_up_only_specified_keys() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 20)])
1
		.with_candidates(vec![(1, 20)])
1
		.build()
1
		.execute_with(|| {
1
			// invalid state
1
			<DelegationScheduledRequests<Test>>::insert(2, BoundedVec::default());
1
			<DelegationScheduledRequests<Test>>::insert(3, BoundedVec::default());
1
			assert_ok!(
1
				ParachainStaking::hotfix_remove_delegation_requests_exited_candidates(
1
					RuntimeOrigin::signed(1),
1
					vec![2]
1
				)
1
			);
1
			assert!(!<DelegationScheduledRequests<Test>>::contains_key(2));
1
			assert!(<DelegationScheduledRequests<Test>>::contains_key(3));
1
		});
1
}
#[test]
1
fn test_hotfix_remove_delegation_requests_exited_candidates_errors_when_requests_not_empty() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 20)])
1
		.with_candidates(vec![(1, 20)])
1
		.build()
1
		.execute_with(|| {
1
			// invalid state
1
			<DelegationScheduledRequests<Test>>::insert(2, BoundedVec::default());
1
			<DelegationScheduledRequests<Test>>::insert(
1
				3,
1
				BoundedVec::try_from(vec![ScheduledRequest {
1
					delegator: 10,
1
					when_executable: 1,
1
					action: DelegationAction::Revoke(10),
1
				}])
1
				.expect("must succeed"),
1
			);
1

            
1
			assert_noop!(
1
				ParachainStaking::hotfix_remove_delegation_requests_exited_candidates(
1
					RuntimeOrigin::signed(1),
1
					vec![2, 3]
1
				),
1
				<Error<Test>>::CandidateNotLeaving,
1
			);
1
		});
1
}
#[test]
1
fn test_hotfix_remove_delegation_requests_exited_candidates_errors_when_candidate_not_exited() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 20)])
1
		.with_candidates(vec![(1, 20)])
1
		.build()
1
		.execute_with(|| {
1
			// invalid state
1
			<DelegationScheduledRequests<Test>>::insert(1, BoundedVec::default());
1
			assert_noop!(
1
				ParachainStaking::hotfix_remove_delegation_requests_exited_candidates(
1
					RuntimeOrigin::signed(1),
1
					vec![1]
1
				),
1
				<Error<Test>>::CandidateNotLeaving,
1
			);
1
		});
1
}
#[test]
1
fn locking_zero_amount_removes_lock() {
	use frame_support::traits::{LockableCurrency, WithdrawReasons};
	// this test demonstrates the behavior of pallet Balance's `LockableCurrency` implementation of
	// `set_locks()` when an amount of 0 is provided: any previous lock is removed
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 100)])
1
		.build()
1
		.execute_with(|| {
1
			assert_eq!(crate::mock::query_lock_amount(1, DELEGATOR_LOCK_ID), None);
1
			Balances::set_lock(DELEGATOR_LOCK_ID, &1, 1, WithdrawReasons::all());
1
			assert_eq!(
1
				crate::mock::query_lock_amount(1, DELEGATOR_LOCK_ID),
1
				Some(1)
1
			);
1
			Balances::set_lock(DELEGATOR_LOCK_ID, &1, 0, WithdrawReasons::all());
1
			// Note that we tried to call `set_lock(0)` and the previous lock gets removed
1
			assert_eq!(crate::mock::query_lock_amount(1, DELEGATOR_LOCK_ID), None);
1
		});
1
}
#[test]
1
fn revoke_last_removes_lock() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 100), (2, 100), (3, 100)])
1
		.with_candidates(vec![(1, 25), (2, 25)])
1
		.with_delegations(vec![(3, 1, 30), (3, 2, 25)])
1
		.build()
1
		.execute_with(|| {
1
			assert_eq!(
1
				crate::mock::query_lock_amount(3, DELEGATOR_LOCK_ID),
1
				Some(55)
1
			);
			// schedule and remove one...
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(3),
1
				1
1
			));
1
			roll_to_round_begin(3);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(3),
1
				3,
1
				1
1
			));
1
			assert_eq!(
1
				crate::mock::query_lock_amount(3, DELEGATOR_LOCK_ID),
1
				Some(25)
1
			);
			// schedule and remove the other...
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(3),
1
				2
1
			));
1
			roll_to_round_begin(5);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(3),
1
				3,
1
				2
1
			));
1
			assert_eq!(crate::mock::query_lock_amount(3, DELEGATOR_LOCK_ID), None);
1
		});
1
}
#[test]
1
fn test_set_auto_compound_fails_if_invalid_delegation_hint() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 25)])
1
		.with_candidates(vec![(1, 30)])
1
		.with_delegations(vec![(2, 1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			let candidate_auto_compounding_delegation_count_hint = 0;
1
			let delegation_hint = 0; // is however, 1
1

            
1
			assert_noop!(
1
				ParachainStaking::set_auto_compound(
1
					RuntimeOrigin::signed(2),
1
					1,
1
					Percent::from_percent(50),
1
					candidate_auto_compounding_delegation_count_hint,
1
					delegation_hint,
1
				),
1
				<Error<Test>>::TooLowDelegationCountToAutoCompound,
1
			);
1
		});
1
}
#[test]
1
fn test_set_auto_compound_fails_if_invalid_candidate_auto_compounding_hint() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 25)])
1
		.with_candidates(vec![(1, 30)])
1
		.with_delegations(vec![(2, 1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			<AutoCompoundDelegations<Test>>::new(
1
				vec![AutoCompoundConfig {
1
					delegator: 2,
1
					value: Percent::from_percent(10),
1
				}]
1
				.try_into()
1
				.expect("must succeed"),
1
			)
1
			.set_storage(&1);
1
			let candidate_auto_compounding_delegation_count_hint = 0; // is however, 1
1
			let delegation_hint = 1;
1

            
1
			assert_noop!(
1
				ParachainStaking::set_auto_compound(
1
					RuntimeOrigin::signed(2),
1
					1,
1
					Percent::from_percent(50),
1
					candidate_auto_compounding_delegation_count_hint,
1
					delegation_hint,
1
				),
1
				<Error<Test>>::TooLowCandidateAutoCompoundingDelegationCountToAutoCompound,
1
			);
1
		});
1
}
#[test]
1
fn test_set_auto_compound_inserts_if_not_exists() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 25)])
1
		.with_candidates(vec![(1, 30)])
1
		.with_delegations(vec![(2, 1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::set_auto_compound(
1
				RuntimeOrigin::signed(2),
1
				1,
1
				Percent::from_percent(50),
1
				0,
1
				1,
1
			));
1
			assert_events_emitted!(Event::AutoCompoundSet {
1
				candidate: 1,
1
				delegator: 2,
1
				value: Percent::from_percent(50),
1
			});
1
			assert_eq!(
1
				vec![AutoCompoundConfig {
1
					delegator: 2,
1
					value: Percent::from_percent(50),
1
				}],
1
				ParachainStaking::auto_compounding_delegations(&1).into_inner(),
1
			);
1
		});
1
}
#[test]
1
fn test_set_auto_compound_updates_if_existing() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 25)])
1
		.with_candidates(vec![(1, 30)])
1
		.with_delegations(vec![(2, 1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			<AutoCompoundDelegations<Test>>::new(
1
				vec![AutoCompoundConfig {
1
					delegator: 2,
1
					value: Percent::from_percent(10),
1
				}]
1
				.try_into()
1
				.expect("must succeed"),
1
			)
1
			.set_storage(&1);
1

            
1
			assert_ok!(ParachainStaking::set_auto_compound(
1
				RuntimeOrigin::signed(2),
1
				1,
1
				Percent::from_percent(50),
1
				1,
1
				1,
1
			));
1
			assert_events_emitted!(Event::AutoCompoundSet {
1
				candidate: 1,
1
				delegator: 2,
1
				value: Percent::from_percent(50),
1
			});
1
			assert_eq!(
1
				vec![AutoCompoundConfig {
1
					delegator: 2,
1
					value: Percent::from_percent(50),
1
				}],
1
				ParachainStaking::auto_compounding_delegations(&1).into_inner(),
1
			);
1
		});
1
}
#[test]
1
fn test_set_auto_compound_removes_if_auto_compound_zero_percent() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 25)])
1
		.with_candidates(vec![(1, 30)])
1
		.with_delegations(vec![(2, 1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			<AutoCompoundDelegations<Test>>::new(
1
				vec![AutoCompoundConfig {
1
					delegator: 2,
1
					value: Percent::from_percent(10),
1
				}]
1
				.try_into()
1
				.expect("must succeed"),
1
			)
1
			.set_storage(&1);
1

            
1
			assert_ok!(ParachainStaking::set_auto_compound(
1
				RuntimeOrigin::signed(2),
1
				1,
1
				Percent::zero(),
1
				1,
1
				1,
1
			));
1
			assert_events_emitted!(Event::AutoCompoundSet {
1
				candidate: 1,
1
				delegator: 2,
1
				value: Percent::zero(),
1
			});
1
			assert_eq!(0, ParachainStaking::auto_compounding_delegations(&1).len(),);
1
		});
1
}
#[test]
1
fn test_execute_revoke_delegation_removes_auto_compounding_from_state_for_delegation_revoke() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 30), (3, 20)])
1
		.with_candidates(vec![(1, 30), (3, 20)])
1
		.with_delegations(vec![(2, 1, 10), (2, 3, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::set_auto_compound(
1
				RuntimeOrigin::signed(2),
1
				1,
1
				Percent::from_percent(50),
1
				0,
1
				2,
1
			));
1
			assert_ok!(ParachainStaking::set_auto_compound(
1
				RuntimeOrigin::signed(2),
1
				3,
1
				Percent::from_percent(50),
1
				0,
1
				2,
1
			));
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
1
				1
1
			));
1
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
1
				2,
1
				1
1
			));
1
			assert!(
1
				!ParachainStaking::auto_compounding_delegations(&1)
1
					.iter()
1
					.any(|x| x.delegator == 2),
				"delegation auto-compound config was not removed"
			);
1
			assert!(
1
				ParachainStaking::auto_compounding_delegations(&3)
1
					.iter()
1
					.any(|x| x.delegator == 2),
				"delegation auto-compound config was erroneously removed"
			);
1
		});
1
}
#[test]
1
fn test_execute_leave_delegators_removes_auto_compounding_state() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 20), (3, 20)])
1
		.with_candidates(vec![(1, 30), (3, 20)])
1
		.with_delegations(vec![(2, 1, 10), (2, 3, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::set_auto_compound(
1
				RuntimeOrigin::signed(2),
1
				1,
1
				Percent::from_percent(50),
1
				0,
1
				2,
1
			));
1
			assert_ok!(ParachainStaking::set_auto_compound(
1
				RuntimeOrigin::signed(2),
1
				3,
1
				Percent::from_percent(50),
1
				0,
1
				2,
1
			));
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
1
				1,
1
			));
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
1
				3,
1
			));
1
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
1
				2,
1
				1,
1
			));
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
1
				2,
1
				3,
1
			));
1
			assert!(
1
				!ParachainStaking::auto_compounding_delegations(&1)
1
					.iter()
1
					.any(|x| x.delegator == 2),
				"delegation auto-compound config was not removed"
			);
1
			assert!(
1
				!ParachainStaking::auto_compounding_delegations(&3)
1
					.iter()
1
					.any(|x| x.delegator == 2),
				"delegation auto-compound config was not removed"
			);
1
		});
1
}
#[test]
1
fn test_execute_leave_candidates_removes_auto_compounding_state() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 20), (3, 20)])
1
		.with_candidates(vec![(1, 30), (3, 20)])
1
		.with_delegations(vec![(2, 1, 10), (2, 3, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::set_auto_compound(
1
				RuntimeOrigin::signed(2),
1
				1,
1
				Percent::from_percent(50),
1
				0,
1
				2,
1
			));
1
			assert_ok!(ParachainStaking::set_auto_compound(
1
				RuntimeOrigin::signed(2),
1
				3,
1
				Percent::from_percent(50),
1
				0,
1
				2,
1
			));
1
			assert_ok!(ParachainStaking::schedule_leave_candidates(
1
				RuntimeOrigin::signed(1),
1
				2
1
			));
1
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_leave_candidates(
1
				RuntimeOrigin::signed(1),
1
				1,
1
				1,
1
			));
1
			assert!(
1
				!ParachainStaking::auto_compounding_delegations(&1)
1
					.iter()
1
					.any(|x| x.delegator == 2),
				"delegation auto-compound config was not removed"
			);
1
			assert!(
1
				ParachainStaking::auto_compounding_delegations(&3)
1
					.iter()
1
					.any(|x| x.delegator == 2),
				"delegation auto-compound config was erroneously removed"
			);
1
		});
1
}
#[test]
1
fn test_delegation_kicked_from_bottom_delegation_removes_auto_compounding_state() {
1
	ExtBuilder::default()
1
		.with_balances(vec![
1
			(1, 30),
1
			(2, 29),
1
			(3, 20),
1
			(4, 20),
1
			(5, 20),
1
			(6, 20),
1
			(7, 20),
1
			(8, 20),
1
			(9, 20),
1
			(10, 20),
1
			(11, 30),
1
		])
1
		.with_candidates(vec![(1, 30), (11, 30)])
1
		.with_delegations(vec![
1
			(2, 11, 10), // extra delegation to avoid leaving the delegator set
1
			(2, 1, 19),
1
			(3, 1, 20),
1
			(4, 1, 20),
1
			(5, 1, 20),
1
			(6, 1, 20),
1
			(7, 1, 20),
1
			(8, 1, 20),
1
			(9, 1, 20),
1
		])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::set_auto_compound(
1
				RuntimeOrigin::signed(2),
1
				1,
1
				Percent::from_percent(50),
1
				0,
1
				2,
1
			));
			// kicks lowest delegation (2, 19)
1
			assert_ok!(ParachainStaking::delegate_with_auto_compound(
1
				RuntimeOrigin::signed(10),
1
				1,
1
				20,
1
				Percent::zero(),
1
				8,
1
				0,
1
				0,
1
			));
1
			assert!(
1
				!ParachainStaking::auto_compounding_delegations(&1)
1
					.iter()
1
					.any(|x| x.delegator == 2),
				"delegation auto-compound config was not removed"
			);
1
		});
1
}
#[test]
1
fn test_rewards_do_not_auto_compound_on_payment_if_delegation_scheduled_revoke_exists() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 100), (2, 200), (3, 200)])
1
		.with_candidates(vec![(1, 100)])
1
		.with_delegations(vec![(2, 1, 200), (3, 1, 200)])
1
		.build()
1
		.execute_with(|| {
4
			(2..=5).for_each(|round| set_author(round, 1, POINTS_PER_ROUND));
1
			assert_ok!(ParachainStaking::set_auto_compound(
1
				RuntimeOrigin::signed(2),
1
				1,
1
				Percent::from_percent(50),
1
				0,
1
				1,
1
			));
1
			assert_ok!(ParachainStaking::set_auto_compound(
1
				RuntimeOrigin::signed(3),
1
				1,
1
				Percent::from_percent(50),
1
				1,
1
				1,
1
			));
1
			roll_to_round_begin(3);
1

            
1
			// schedule revoke for delegator 2; no rewards should be compounded
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
1
				1
1
			));
1
			roll_to_round_begin(4);
1

            
1
			assert_events_eq!(
1
				Event::CollatorChosen {
1
					round: 4,
1
					collator_account: 1,
1
					total_exposed_amount: 500,
1
				},
1
				Event::NewRound {
1
					starting_block: 15,
1
					round: 4,
1
					selected_collators_number: 1,
1
					total_balance: 500,
1
				},
1
			);
1
			roll_blocks(1);
1
			assert_events_eq!(
1
				Event::Rewarded {
1
					account: 1,
1
					rewards: 4,
1
				},
1
				// no compound since revoke request exists
1
				Event::Rewarded {
1
					account: 2,
1
					rewards: 4,
1
				},
1
				// 50%
1
				Event::Rewarded {
1
					account: 3,
1
					rewards: 4,
1
				},
1
				Event::Compounded {
1
					candidate: 1,
1
					delegator: 3,
1
					amount: 2,
1
				},
1
			);
1
		});
1
}
#[test]
1
fn test_rewards_auto_compound_on_payment_as_per_auto_compound_config() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 100), (2, 200), (3, 200), (4, 200), (5, 200)])
1
		.with_candidates(vec![(1, 100)])
1
		.with_delegations(vec![(2, 1, 200), (3, 1, 200), (4, 1, 200), (5, 1, 200)])
1
		.build()
1
		.execute_with(|| {
5
			(2..=6).for_each(|round| set_author(round, 1, POINTS_PER_ROUND));
1
			assert_ok!(ParachainStaking::set_auto_compound(
1
				RuntimeOrigin::signed(2),
1
				1,
1
				Percent::from_percent(0),
1
				0,
1
				1,
1
			));
1
			assert_ok!(ParachainStaking::set_auto_compound(
1
				RuntimeOrigin::signed(3),
1
				1,
1
				Percent::from_percent(50),
1
				1,
1
				1,
1
			));
1
			assert_ok!(ParachainStaking::set_auto_compound(
1
				RuntimeOrigin::signed(4),
1
				1,
1
				Percent::from_percent(100),
1
				2,
1
				1,
1
			));
1
			roll_to_round_begin(4);
1

            
1
			assert_events_eq!(
1
				Event::CollatorChosen {
1
					round: 4,
1
					collator_account: 1,
1
					total_exposed_amount: 900,
1
				},
1
				Event::NewRound {
1
					starting_block: 15,
1
					round: 4,
1
					selected_collators_number: 1,
1
					total_balance: 900,
1
				},
1
			);
1
			roll_blocks(1);
1
			assert_events_eq!(
1
				Event::Rewarded {
1
					account: 1,
1
					rewards: 6,
1
				},
1
				// 0%
1
				Event::Rewarded {
1
					account: 2,
1
					rewards: 4,
1
				},
1
				// 50%
1
				Event::Rewarded {
1
					account: 3,
1
					rewards: 4,
1
				},
1
				Event::Compounded {
1
					candidate: 1,
1
					delegator: 3,
1
					amount: 2,
1
				},
1
				// 100%
1
				Event::Rewarded {
1
					account: 4,
1
					rewards: 4,
1
				},
1
				Event::Compounded {
1
					candidate: 1,
1
					delegator: 4,
1
					amount: 4,
1
				},
1
				// no-config
1
				Event::Rewarded {
1
					account: 5,
1
					rewards: 4,
1
				},
1
			);
1
		});
1
}
#[test]
1
fn test_delegate_with_auto_compound_fails_if_invalid_delegation_hint() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 25), (3, 30)])
1
		.with_candidates(vec![(1, 30), (3, 30)])
1
		.with_delegations(vec![(2, 3, 10)])
1
		.build()
1
		.execute_with(|| {
1
			let candidate_delegation_count_hint = 0;
1
			let candidate_auto_compounding_delegation_count_hint = 0;
1
			let delegation_hint = 0; // is however, 1
1

            
1
			assert_noop!(
1
				ParachainStaking::delegate_with_auto_compound(
1
					RuntimeOrigin::signed(2),
1
					1,
1
					10,
1
					Percent::from_percent(50),
1
					candidate_delegation_count_hint,
1
					candidate_auto_compounding_delegation_count_hint,
1
					delegation_hint,
1
				),
1
				<Error<Test>>::TooLowDelegationCountToDelegate,
1
			);
1
		});
1
}
#[test]
1
fn test_delegate_with_auto_compound_fails_if_invalid_candidate_delegation_count_hint() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 25), (3, 30)])
1
		.with_candidates(vec![(1, 30)])
1
		.with_delegations(vec![(3, 1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			let candidate_delegation_count_hint = 0; // is however, 1
1
			let candidate_auto_compounding_delegation_count_hint = 0;
1
			let delegation_hint = 0;
1

            
1
			assert_noop!(
1
				ParachainStaking::delegate_with_auto_compound(
1
					RuntimeOrigin::signed(2),
1
					1,
1
					10,
1
					Percent::from_percent(50),
1
					candidate_delegation_count_hint,
1
					candidate_auto_compounding_delegation_count_hint,
1
					delegation_hint,
1
				),
1
				<Error<Test>>::TooLowCandidateDelegationCountToDelegate,
1
			);
1
		});
1
}
#[test]
1
fn test_delegate_with_auto_compound_fails_if_invalid_candidate_auto_compounding_delegations_hint() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 25), (3, 30)])
1
		.with_candidates(vec![(1, 30)])
1
		.with_auto_compounding_delegations(vec![(3, 1, 10, Percent::from_percent(10))])
1
		.build()
1
		.execute_with(|| {
1
			let candidate_delegation_count_hint = 1;
1
			let candidate_auto_compounding_delegation_count_hint = 0; // is however, 1
1
			let delegation_hint = 0;
1

            
1
			assert_noop!(
1
				ParachainStaking::delegate_with_auto_compound(
1
					RuntimeOrigin::signed(2),
1
					1,
1
					10,
1
					Percent::from_percent(50),
1
					candidate_delegation_count_hint,
1
					candidate_auto_compounding_delegation_count_hint,
1
					delegation_hint,
1
				),
1
				<Error<Test>>::TooLowCandidateAutoCompoundingDelegationCountToDelegate,
1
			);
1
		});
1
}
#[test]
1
fn test_delegate_with_auto_compound_sets_auto_compound_config() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 25)])
1
		.with_candidates(vec![(1, 30)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::delegate_with_auto_compound(
1
				RuntimeOrigin::signed(2),
1
				1,
1
				10,
1
				Percent::from_percent(50),
1
				0,
1
				0,
1
				0,
1
			));
1
			assert_events_emitted!(Event::Delegation {
1
				delegator: 2,
1
				locked_amount: 10,
1
				candidate: 1,
1
				delegator_position: DelegatorAdded::AddedToTop { new_total: 40 },
1
				auto_compound: Percent::from_percent(50),
1
			});
1
			assert_eq!(
1
				vec![AutoCompoundConfig {
1
					delegator: 2,
1
					value: Percent::from_percent(50),
1
				}],
1
				ParachainStaking::auto_compounding_delegations(&1).into_inner(),
1
			);
1
		});
1
}
#[test]
1
fn test_delegate_with_auto_compound_skips_storage_but_emits_event_for_zero_auto_compound() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 10)])
1
		.with_candidates(vec![(1, 30)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::delegate_with_auto_compound(
1
				RuntimeOrigin::signed(2),
1
				1,
1
				10,
1
				Percent::zero(),
1
				0,
1
				0,
1
				0,
1
			));
1
			assert_eq!(0, ParachainStaking::auto_compounding_delegations(&1).len(),);
1
			assert_events_eq!(Event::Delegation {
1
				delegator: 2,
1
				locked_amount: 10,
1
				candidate: 1,
1
				delegator_position: DelegatorAdded::AddedToTop { new_total: 40 },
1
				auto_compound: Percent::zero(),
1
			});
1
		});
1
}
#[test]
1
fn test_delegate_with_auto_compound_reserves_balance() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 10)])
1
		.with_candidates(vec![(1, 30)])
1
		.build()
1
		.execute_with(|| {
1
			assert_eq!(ParachainStaking::get_delegator_stakable_balance(&2), 10);
1
			assert_ok!(ParachainStaking::delegate_with_auto_compound(
1
				RuntimeOrigin::signed(2),
1
				1,
1
				10,
1
				Percent::from_percent(50),
1
				0,
1
				0,
1
				0,
1
			));
1
			assert_eq!(ParachainStaking::get_delegator_stakable_balance(&2), 0);
1
		});
1
}
#[test]
1
fn test_delegate_with_auto_compound_updates_delegator_state() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 10)])
1
		.with_candidates(vec![(1, 30)])
1
		.build()
1
		.execute_with(|| {
1
			assert!(ParachainStaking::delegator_state(2).is_none());
1
			assert_ok!(ParachainStaking::delegate_with_auto_compound(
1
				RuntimeOrigin::signed(2),
1
				1,
1
				10,
1
				Percent::from_percent(50),
1
				0,
1
				0,
1
				0
1
			));
1
			let delegator_state =
1
				ParachainStaking::delegator_state(2).expect("just delegated => exists");
1
			assert_eq!(delegator_state.total(), 10);
1
			assert_eq!(delegator_state.delegations.0[0].owner, 1);
1
			assert_eq!(delegator_state.delegations.0[0].amount, 10);
1
		});
1
}
#[test]
1
fn test_delegate_with_auto_compound_updates_collator_state() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 10)])
1
		.with_candidates(vec![(1, 30)])
1
		.build()
1
		.execute_with(|| {
1
			let candidate_state =
1
				ParachainStaking::candidate_info(1).expect("registered in genesis");
1
			assert_eq!(candidate_state.total_counted, 30);
1
			let top_delegations =
1
				ParachainStaking::top_delegations(1).expect("registered in genesis");
1
			assert!(top_delegations.delegations.is_empty());
1
			assert!(top_delegations.total.is_zero());
1
			assert_ok!(ParachainStaking::delegate_with_auto_compound(
1
				RuntimeOrigin::signed(2),
1
				1,
1
				10,
1
				Percent::from_percent(50),
1
				0,
1
				0,
1
				0
1
			));
1
			let candidate_state =
1
				ParachainStaking::candidate_info(1).expect("just delegated => exists");
1
			assert_eq!(candidate_state.total_counted, 40);
1
			let top_delegations =
1
				ParachainStaking::top_delegations(1).expect("just delegated => exists");
1
			assert_eq!(top_delegations.delegations[0].owner, 2);
1
			assert_eq!(top_delegations.delegations[0].amount, 10);
1
			assert_eq!(top_delegations.total, 10);
1
		});
1
}
#[test]
1
fn test_delegate_with_auto_compound_can_delegate_immediately_after_other_join_candidates() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 20), (2, 20)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::join_candidates(
1
				RuntimeOrigin::signed(1),
1
				20,
1
				0
1
			));
1
			assert_ok!(ParachainStaking::delegate_with_auto_compound(
1
				RuntimeOrigin::signed(2),
1
				1,
1
				20,
1
				Percent::from_percent(50),
1
				0,
1
				0,
1
				0
1
			));
1
		});
1
}
#[test]
1
fn test_delegate_with_auto_compound_can_delegate_to_other_if_revoking() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 20), (2, 30), (3, 20), (4, 20)])
1
		.with_candidates(vec![(1, 20), (3, 20), (4, 20)])
1
		.with_delegations(vec![(2, 1, 10), (2, 3, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
1
				1
1
			));
1
			assert_ok!(ParachainStaking::delegate_with_auto_compound(
1
				RuntimeOrigin::signed(2),
1
				4,
1
				10,
1
				Percent::from_percent(50),
1
				0,
1
				0,
1
				2
1
			));
1
		});
1
}
#[test]
1
fn test_delegate_with_auto_compound_cannot_delegate_if_less_than_or_equal_lowest_bottom() {
1
	ExtBuilder::default()
1
		.with_balances(vec![
1
			(1, 20),
1
			(2, 10),
1
			(3, 10),
1
			(4, 10),
1
			(5, 10),
1
			(6, 10),
1
			(7, 10),
1
			(8, 10),
1
			(9, 10),
1
			(10, 10),
1
			(11, 10),
1
		])
1
		.with_candidates(vec![(1, 20)])
1
		.with_delegations(vec![
1
			(2, 1, 10),
1
			(3, 1, 10),
1
			(4, 1, 10),
1
			(5, 1, 10),
1
			(6, 1, 10),
1
			(8, 1, 10),
1
			(9, 1, 10),
1
			(10, 1, 10),
1
		])
1
		.build()
1
		.execute_with(|| {
1
			assert_noop!(
1
				ParachainStaking::delegate_with_auto_compound(
1
					RuntimeOrigin::signed(11),
1
					1,
1
					10,
1
					Percent::from_percent(50),
1
					8,
1
					0,
1
					0
1
				),
1
				Error::<Test>::CannotDelegateLessThanOrEqualToLowestBottomWhenFull
1
			);
1
		});
1
}
#[test]
1
fn test_delegate_with_auto_compound_can_delegate_if_greater_than_lowest_bottom() {
1
	ExtBuilder::default()
1
		.with_balances(vec![
1
			(1, 20),
1
			(2, 10),
1
			(3, 10),
1
			(4, 10),
1
			(5, 10),
1
			(6, 10),
1
			(7, 10),
1
			(8, 10),
1
			(9, 10),
1
			(10, 10),
1
			(11, 11),
1
		])
1
		.with_candidates(vec![(1, 20)])
1
		.with_delegations(vec![
1
			(2, 1, 10),
1
			(3, 1, 10),
1
			(4, 1, 10),
1
			(5, 1, 10),
1
			(6, 1, 10),
1
			(8, 1, 10),
1
			(9, 1, 10),
1
			(10, 1, 10),
1
		])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::delegate_with_auto_compound(
1
				RuntimeOrigin::signed(11),
1
				1,
1
				11,
1
				Percent::from_percent(50),
1
				8,
1
				0,
1
				0
1
			));
1
			assert_events_emitted!(Event::DelegationKicked {
1
				delegator: 10,
1
				candidate: 1,
1
				unstaked_amount: 10
1
			});
1
			assert_events_emitted!(Event::DelegatorLeft {
1
				delegator: 10,
1
				unstaked_amount: 10
1
			});
1
		});
1
}
#[test]
1
fn test_delegate_with_auto_compound_can_still_delegate_to_other_if_leaving() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 20), (2, 20), (3, 20)])
1
		.with_candidates(vec![(1, 20), (3, 20)])
1
		.with_delegations(vec![(2, 1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
1
				1,
1
			));
1
			assert_ok!(ParachainStaking::delegate_with_auto_compound(
1
				RuntimeOrigin::signed(2),
1
				3,
1
				10,
1
				Percent::from_percent(50),
1
				0,
1
				0,
1
				1
1
			),);
1
		});
1
}
#[test]
1
fn test_delegate_with_auto_compound_cannot_delegate_if_candidate() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 20), (2, 30)])
1
		.with_candidates(vec![(1, 20), (2, 20)])
1
		.build()
1
		.execute_with(|| {
1
			assert_noop!(
1
				ParachainStaking::delegate_with_auto_compound(
1
					RuntimeOrigin::signed(2),
1
					1,
1
					10,
1
					Percent::from_percent(50),
1
					0,
1
					0,
1
					0
1
				),
1
				Error::<Test>::CandidateExists
1
			);
1
		});
1
}
#[test]
1
fn test_delegate_with_auto_compound_cannot_delegate_if_already_delegated() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 20), (2, 30)])
1
		.with_candidates(vec![(1, 20)])
1
		.with_delegations(vec![(2, 1, 20)])
1
		.build()
1
		.execute_with(|| {
1
			assert_noop!(
1
				ParachainStaking::delegate_with_auto_compound(
1
					RuntimeOrigin::signed(2),
1
					1,
1
					10,
1
					Percent::from_percent(50),
1
					0,
1
					1,
1
					1
1
				),
1
				Error::<Test>::AlreadyDelegatedCandidate
1
			);
1
		});
1
}
#[test]
1
fn test_delegate_with_auto_compound_cannot_delegate_more_than_max_delegations() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 20), (2, 50), (3, 20), (4, 20), (5, 20), (6, 20)])
1
		.with_candidates(vec![(1, 20), (3, 20), (4, 20), (5, 20), (6, 20)])
1
		.with_delegations(vec![(2, 1, 10), (2, 3, 10), (2, 4, 10), (2, 5, 10)])
1
		.build()
1
		.execute_with(|| {
1
			assert_noop!(
1
				ParachainStaking::delegate_with_auto_compound(
1
					RuntimeOrigin::signed(2),
1
					6,
1
					10,
1
					Percent::from_percent(50),
1
					0,
1
					0,
1
					4
1
				),
1
				Error::<Test>::ExceedMaxDelegationsPerDelegator,
1
			);
1
		});
1
}
#[test]
1
fn test_delegate_skips_auto_compound_storage_but_emits_event_for_zero_auto_compound() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 20), (3, 30)])
1
		.with_candidates(vec![(1, 30)])
1
		.with_auto_compounding_delegations(vec![(3, 1, 10, Percent::from_percent(50))])
1
		.build()
1
		.execute_with(|| {
1
			// We already have an auto-compounding delegation from 3 -> 1, so the hint validation
1
			// would cause a failure if the auto-compounding isn't skipped properly.
1
			assert_ok!(ParachainStaking::delegate_with_auto_compound(
1
				RuntimeOrigin::signed(2),
1
				1,
1
				10,
1
				Percent::zero(),
1
				1,
1
				0,
1
				0,
1
			));
1
			assert_eq!(1, ParachainStaking::auto_compounding_delegations(&1).len(),);
1
			assert_events_eq!(Event::Delegation {
1
				delegator: 2,
1
				locked_amount: 10,
1
				candidate: 1,
1
				delegator_position: DelegatorAdded::AddedToTop { new_total: 50 },
1
				auto_compound: Percent::zero(),
1
			});
1
		});
1
}
#[test]
1
fn test_on_initialize_weights() {
	use crate::mock::System;
	use crate::weights::{SubstrateWeight as PalletWeights, WeightInfo};
	use crate::*;
	use frame_support::{pallet_prelude::*, weights::constants::RocksDbWeight};
	// generate balance, candidate, and delegation vecs to "fill" out delegations
1
	let mut balances = Vec::new();
1
	let mut candidates = Vec::new();
1
	let mut delegations = Vec::new();
30
	for collator in 1..30 {
29
		balances.push((collator, 100));
29
		candidates.push((collator, 10));
29
		let starting_delegator = collator * 1000;
8700
		for delegator in starting_delegator..starting_delegator + 300 {
8700
			balances.push((delegator, 100));
8700
			delegations.push((delegator, collator, 10));
8700
		}
	}
1
	ExtBuilder::default()
1
		.with_balances(balances)
1
		.with_candidates(candidates)
1
		.with_delegations(delegations)
1
		.build()
1
		.execute_with(|| {
1
			let weight = ParachainStaking::on_initialize(1);
1

            
1
			// TODO: build this with proper db reads/writes
1
			assert_eq!(Weight::from_parts(401000000, 0), weight);
			// roll to the end of the round, then run on_init again, we should see round change...
1
			set_author(3, 1, POINTS_PER_ROUND); // must set some points for prepare_staking_payouts
1
			roll_to_round_end(3);
1
			let block = System::block_number() + 1;
1
			let weight = ParachainStaking::on_initialize(block);
1

            
1
			// the total on_init weight during our round change. this number is taken from running
1
			// the fn with a given weights.rs benchmark, so will need to be updated as benchmarks
1
			// change.
1
			//
1
			// following this assertion, we add individual weights together to show that we can
1
			// derive this number independently.
1
			let expected_on_init = 2999210735;
1
			assert_eq!(Weight::from_parts(expected_on_init, 51554), weight);
			// assemble weight manually to ensure it is well understood
1
			let mut expected_weight = 0u64;
1
			expected_weight += PalletWeights::<Test>::base_on_initialize().ref_time();
1
			expected_weight += PalletWeights::<Test>::prepare_staking_payouts().ref_time();
1
			expected_weight += PalletWeights::<Test>::mark_collators_as_inactive(5).ref_time();
1

            
1
			// TODO: this should be the same as <TotalSelected<Test>>. I believe this relates to
1
			// genesis building
1
			let num_avg_delegations = 8;
1
			expected_weight += PalletWeights::<Test>::select_top_candidates(
1
				<TotalSelected<Test>>::get(),
1
				num_avg_delegations,
1
			)
1
			.ref_time();
1
			// SlotProvider read
1
			expected_weight += RocksDbWeight::get().reads_writes(1, 0).ref_time();
1
			// Round write, done in on-round-change code block inside on_initialize()
1
			expected_weight += RocksDbWeight::get().reads_writes(0, 1).ref_time();
1
			// more reads/writes manually accounted for for on_finalize
1
			expected_weight += RocksDbWeight::get().reads_writes(4, 3).ref_time();
1

            
1
			assert_eq!(Weight::from_parts(expected_weight, 51554), weight);
1
			assert_eq!(expected_on_init, expected_weight); // magic number == independent accounting
1
		});
1
}
#[test]
1
fn test_compute_top_candidates_is_stable() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 30), (2, 30), (3, 30), (4, 30), (5, 30), (6, 30)])
1
		.with_candidates(vec![(1, 30), (2, 30), (3, 30), (4, 30), (5, 30), (6, 30)])
1
		.build()
1
		.execute_with(|| {
1
			// There are 6 candidates with equal amount, but only 5 can be selected
1
			assert_eq!(ParachainStaking::candidate_pool().0.len(), 6);
1
			assert_eq!(ParachainStaking::total_selected(), 5);
			// Returns the 5 candidates with greater AccountId, because they are iterated in reverse
1
			assert_eq!(
1
				ParachainStaking::compute_top_candidates(),
1
				vec![2, 3, 4, 5, 6]
1
			);
1
		});
1
}