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::migrations::MigrateDelegationScheduledRequestsToDoubleMap;
28
use crate::mock::{
29
	inflation_configs, query_freeze_amount, roll_blocks, roll_to, roll_to_round_begin,
30
	roll_to_round_end, set_author, set_block_author, AccountId, Balances, BlockNumber, ExtBuilder,
31
	ParachainStaking, RuntimeEvent, RuntimeOrigin, Test, POINTS_PER_BLOCK, POINTS_PER_ROUND,
32
};
33
use crate::RoundIndex;
34
use crate::{
35
	assert_events_emitted, assert_events_emitted_match, assert_events_eq, assert_no_events,
36
	AtStake, Bond, CollatorStatus, DelegationScheduledRequests,
37
	DelegationScheduledRequestsPerCollator, DelegatorAdded, EnableMarkingOffline, Error, Event,
38
	FreezeReason, InflationDistributionInfo, Range, WasInactive,
39
};
40
use frame_support::migrations::SteppedMigration;
41
use frame_support::storage::storage_prefix;
42
use frame_support::traits::fungible::MutateFreeze;
43
use frame_support::traits::{Currency, ExistenceRequirement, WithdrawReasons};
44
use frame_support::weights::WeightMeter;
45
use frame_support::{assert_noop, assert_ok, BoundedVec};
46
use pallet_balances::{Event as BalancesEvent, PositiveImbalance};
47
use parity_scale_codec::{Decode, Encode};
48
use sp_io::hashing::blake2_128;
49
use sp_runtime::{traits::Zero, DispatchError, ModuleError, Perbill, Percent, RuntimeDebug};
50

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

            
72
// SET TOTAL SELECTED
73

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

            
94
#[test]
95
1
fn set_total_selected_fails_if_above_blocks_per_round() {
96
1
	ExtBuilder::default().build().execute_with(|| {
97
1
		assert_eq!(ParachainStaking::round().length, 5); // test relies on this
98
1
		assert_noop!(
99
1
			ParachainStaking::set_total_selected(RuntimeOrigin::root(), 6u32),
100
1
			Error::<Test>::RoundLengthMustBeGreaterThanTotalSelectedCollators,
101
		);
102
1
	});
103
1
}
104

            
105
#[test]
106
1
fn set_total_selected_fails_if_above_max_candidates() {
107
1
	ExtBuilder::default().build().execute_with(|| {
108
1
		assert_eq!(<Test as crate::Config>::MaxCandidates::get(), 200); // test relies on this
109
1
		assert_noop!(
110
1
			ParachainStaking::set_total_selected(RuntimeOrigin::root(), 201u32),
111
1
			Error::<Test>::CannotSetAboveMaxCandidates,
112
		);
113
1
	});
114
1
}
115

            
116
#[test]
117
1
fn set_total_selected_fails_if_equal_to_blocks_per_round() {
118
1
	ExtBuilder::default().build().execute_with(|| {
119
1
		assert_ok!(ParachainStaking::set_blocks_per_round(
120
1
			RuntimeOrigin::root(),
121
			10u32
122
		));
123
1
		assert_noop!(
124
1
			ParachainStaking::set_total_selected(RuntimeOrigin::root(), 10u32),
125
1
			Error::<Test>::RoundLengthMustBeGreaterThanTotalSelectedCollators,
126
		);
127
1
	});
128
1
}
129

            
130
#[test]
131
1
fn set_total_selected_passes_if_below_blocks_per_round() {
132
1
	ExtBuilder::default().build().execute_with(|| {
133
1
		assert_ok!(ParachainStaking::set_blocks_per_round(
134
1
			RuntimeOrigin::root(),
135
			10u32
136
		));
137
1
		assert_ok!(ParachainStaking::set_total_selected(
138
1
			RuntimeOrigin::root(),
139
			9u32
140
		));
141
1
	});
142
1
}
143

            
144
#[test]
145
1
fn set_blocks_per_round_fails_if_below_total_selected() {
146
1
	ExtBuilder::default().build().execute_with(|| {
147
1
		assert_ok!(ParachainStaking::set_blocks_per_round(
148
1
			RuntimeOrigin::root(),
149
			20u32
150
		));
151
1
		assert_ok!(ParachainStaking::set_total_selected(
152
1
			RuntimeOrigin::root(),
153
			10u32
154
		));
155
1
		assert_noop!(
156
1
			ParachainStaking::set_blocks_per_round(RuntimeOrigin::root(), 9u32),
157
1
			Error::<Test>::RoundLengthMustBeGreaterThanTotalSelectedCollators,
158
		);
159
1
	});
160
1
}
161

            
162
#[test]
163
1
fn set_blocks_per_round_fails_if_equal_to_total_selected() {
164
1
	ExtBuilder::default().build().execute_with(|| {
165
1
		assert_ok!(ParachainStaking::set_blocks_per_round(
166
1
			RuntimeOrigin::root(),
167
			10u32
168
		));
169
1
		assert_ok!(ParachainStaking::set_total_selected(
170
1
			RuntimeOrigin::root(),
171
			9u32
172
		));
173
1
		assert_noop!(
174
1
			ParachainStaking::set_blocks_per_round(RuntimeOrigin::root(), 9u32),
175
1
			Error::<Test>::RoundLengthMustBeGreaterThanTotalSelectedCollators,
176
		);
177
1
	});
178
1
}
179

            
180
#[test]
181
1
fn set_blocks_per_round_passes_if_above_total_selected() {
182
1
	ExtBuilder::default().build().execute_with(|| {
183
1
		assert_eq!(ParachainStaking::round().length, 5); // test relies on this
184
1
		assert_ok!(ParachainStaking::set_blocks_per_round(
185
1
			RuntimeOrigin::root(),
186
			6u32
187
		));
188
1
	});
189
1
}
190

            
191
#[test]
192
1
fn set_total_selected_storage_updates_correctly() {
193
1
	ExtBuilder::default().build().execute_with(|| {
194
		// round length must be >= total_selected, so update that first
195
1
		assert_ok!(ParachainStaking::set_blocks_per_round(
196
1
			RuntimeOrigin::root(),
197
			10u32
198
		));
199

            
200
1
		assert_eq!(ParachainStaking::total_selected(), 5u32);
201
1
		assert_ok!(ParachainStaking::set_total_selected(
202
1
			RuntimeOrigin::root(),
203
			6u32
204
		));
205
1
		assert_eq!(ParachainStaking::total_selected(), 6u32);
206
1
	});
207
1
}
208

            
209
#[test]
210
1
fn cannot_set_total_selected_to_current_total_selected() {
211
1
	ExtBuilder::default().build().execute_with(|| {
212
1
		assert_noop!(
213
1
			ParachainStaking::set_total_selected(RuntimeOrigin::root(), 5u32),
214
1
			Error::<Test>::NoWritingSameValue
215
		);
216
1
	});
217
1
}
218

            
219
#[test]
220
1
fn cannot_set_total_selected_below_module_min() {
221
1
	ExtBuilder::default().build().execute_with(|| {
222
1
		assert_noop!(
223
1
			ParachainStaking::set_total_selected(RuntimeOrigin::root(), 4u32),
224
1
			Error::<Test>::CannotSetBelowMin
225
		);
226
1
	});
227
1
}
228

            
229
// SET COLLATOR COMMISSION
230

            
231
#[test]
232
1
fn set_collator_commission_event_emits_correctly() {
233
1
	ExtBuilder::default().build().execute_with(|| {
234
1
		assert_ok!(ParachainStaking::set_collator_commission(
235
1
			RuntimeOrigin::root(),
236
1
			Perbill::from_percent(5)
237
		));
238
1
		assert_events_eq!(Event::CollatorCommissionSet {
239
1
			old: Perbill::from_percent(20),
240
1
			new: Perbill::from_percent(5),
241
1
		});
242
1
	});
243
1
}
244

            
245
#[test]
246
1
fn set_collator_commission_storage_updates_correctly() {
247
1
	ExtBuilder::default().build().execute_with(|| {
248
1
		assert_eq!(
249
1
			ParachainStaking::collator_commission(),
250
1
			Perbill::from_percent(20)
251
		);
252
1
		assert_ok!(ParachainStaking::set_collator_commission(
253
1
			RuntimeOrigin::root(),
254
1
			Perbill::from_percent(5)
255
		));
256
1
		assert_eq!(
257
1
			ParachainStaking::collator_commission(),
258
1
			Perbill::from_percent(5)
259
		);
260
1
	});
261
1
}
262

            
263
#[test]
264
1
fn cannot_set_collator_commission_to_current_collator_commission() {
265
1
	ExtBuilder::default().build().execute_with(|| {
266
1
		assert_noop!(
267
1
			ParachainStaking::set_collator_commission(
268
1
				RuntimeOrigin::root(),
269
1
				Perbill::from_percent(20)
270
			),
271
1
			Error::<Test>::NoWritingSameValue
272
		);
273
1
	});
274
1
}
275

            
276
// SET BLOCKS PER ROUND
277

            
278
#[test]
279
1
fn set_blocks_per_round_event_emits_correctly() {
280
1
	ExtBuilder::default().build().execute_with(|| {
281
1
		assert_ok!(ParachainStaking::set_blocks_per_round(
282
1
			RuntimeOrigin::root(),
283
			6u32
284
		));
285
1
		assert_events_eq!(Event::BlocksPerRoundSet {
286
1
			current_round: 1,
287
1
			first_block: 0,
288
1
			old: 5,
289
1
			new: 6,
290
1
			new_per_round_inflation_min: Perbill::from_parts(463),
291
1
			new_per_round_inflation_ideal: Perbill::from_parts(463),
292
1
			new_per_round_inflation_max: Perbill::from_parts(463),
293
1
		});
294
1
	});
295
1
}
296

            
297
#[test]
298
1
fn set_blocks_per_round_storage_updates_correctly() {
299
1
	ExtBuilder::default().build().execute_with(|| {
300
1
		assert_eq!(ParachainStaking::round().length, 5);
301
1
		assert_ok!(ParachainStaking::set_blocks_per_round(
302
1
			RuntimeOrigin::root(),
303
			6u32
304
		));
305
1
		assert_eq!(ParachainStaking::round().length, 6);
306
1
	});
307
1
}
308

            
309
#[test]
310
1
fn cannot_set_blocks_per_round_below_module_min() {
311
1
	ExtBuilder::default().build().execute_with(|| {
312
1
		assert_noop!(
313
1
			ParachainStaking::set_blocks_per_round(RuntimeOrigin::root(), 2u32),
314
1
			Error::<Test>::CannotSetBelowMin
315
		);
316
1
	});
317
1
}
318

            
319
#[test]
320
1
fn cannot_set_blocks_per_round_to_current_blocks_per_round() {
321
1
	ExtBuilder::default().build().execute_with(|| {
322
1
		assert_noop!(
323
1
			ParachainStaking::set_blocks_per_round(RuntimeOrigin::root(), 5u32),
324
1
			Error::<Test>::NoWritingSameValue
325
		);
326
1
	});
327
1
}
328

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

            
344
1
			roll_to(10);
345
1
			assert_events_emitted!(Event::NewRound {
346
1
				starting_block: 10,
347
1
				round: 2,
348
1
				selected_collators_number: 1,
349
1
				total_balance: 20
350
1
			},);
351
1
			roll_to(17);
352
1
			assert_ok!(ParachainStaking::set_blocks_per_round(
353
1
				RuntimeOrigin::root(),
354
				6u32
355
			));
356
1
			roll_to(18);
357
1
			assert_events_emitted!(Event::NewRound {
358
1
				starting_block: 18,
359
1
				round: 3,
360
1
				selected_collators_number: 1,
361
1
				total_balance: 20
362
1
			});
363
1
		});
364
1
}
365

            
366
// ~~ MONETARY GOVERNANCE ~~
367

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

            
414
// SET STAKING EXPECTATIONS
415

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

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

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

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

            
509
// SET INFLATION
510

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

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

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

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

            
613
#[test]
614
1
fn set_inflation_distribution_config_event_emits_correctly() {
615
1
	ExtBuilder::default().build().execute_with(|| {
616
1
		assert_ok!(ParachainStaking::set_inflation_distribution_config(
617
1
			RuntimeOrigin::root(),
618
1
			inflation_configs(1, 30, 2, 20),
619
		));
620
1
		assert_events_eq!(Event::InflationDistributionConfigUpdated {
621
1
			old: inflation_configs(0, 30, 0, 0),
622
1
			new: inflation_configs(1, 30, 2, 20),
623
1
		});
624
1
		roll_blocks(1);
625
1
		assert_ok!(ParachainStaking::set_inflation_distribution_config(
626
1
			RuntimeOrigin::root(),
627
1
			inflation_configs(5, 10, 6, 5),
628
		));
629
1
		assert_events_eq!(Event::InflationDistributionConfigUpdated {
630
1
			old: inflation_configs(1, 30, 2, 20),
631
1
			new: inflation_configs(5, 10, 6, 5),
632
1
		});
633
1
	});
634
1
}
635

            
636
#[test]
637
1
fn set_inflation_distribution_config_storage_updates_correctly() {
638
1
	ExtBuilder::default().build().execute_with(|| {
639
1
		assert_eq!(
640
1
			InflationDistributionInfo::<Test>::get(),
641
1
			inflation_configs(0, 30, 0, 0),
642
		);
643
1
		assert_ok!(ParachainStaking::set_inflation_distribution_config(
644
1
			RuntimeOrigin::root(),
645
1
			inflation_configs(5, 10, 6, 5),
646
		));
647
1
		assert_eq!(
648
1
			InflationDistributionInfo::<Test>::get(),
649
1
			inflation_configs(5, 10, 6, 5),
650
		);
651
1
		assert_ok!(ParachainStaking::set_inflation_distribution_config(
652
1
			RuntimeOrigin::root(),
653
1
			inflation_configs(1, 30, 2, 20),
654
		));
655
1
		assert_eq!(
656
1
			InflationDistributionInfo::<Test>::get(),
657
1
			inflation_configs(1, 30, 2, 20),
658
		);
659
1
	});
660
1
}
661

            
662
#[test]
663
1
fn cannot_set_same_inflation_distribution_config() {
664
1
	ExtBuilder::default().build().execute_with(|| {
665
1
		assert_ok!(ParachainStaking::set_inflation_distribution_config(
666
1
			RuntimeOrigin::root(),
667
1
			inflation_configs(1, 30, 2, 20),
668
		));
669
1
		assert_noop!(
670
1
			ParachainStaking::set_inflation_distribution_config(
671
1
				RuntimeOrigin::root(),
672
1
				inflation_configs(1, 30, 2, 20)
673
			),
674
1
			Error::<Test>::NoWritingSameValue,
675
		);
676
1
	});
677
1
}
678

            
679
#[test]
680
1
fn sum_of_inflation_distribution_config_percentages_must_lte_100() {
681
1
	ExtBuilder::default().build().execute_with(|| {
682
1
		let invalid_values: Vec<(u8, u8)> = vec![
683
1
			(20, 90),
684
1
			(90, 20),
685
1
			(50, 51),
686
1
			(100, 1),
687
1
			(1, 100),
688
1
			(55, 55),
689
1
			(2, 99),
690
1
			(100, 100),
691
		];
692

            
693
9
		for (pbr_percentage, treasury_percentage) in invalid_values {
694
8
			assert_noop!(
695
8
				ParachainStaking::set_inflation_distribution_config(
696
8
					RuntimeOrigin::root(),
697
8
					inflation_configs(1, pbr_percentage, 2, treasury_percentage),
698
				),
699
8
				Error::<Test>::TotalInflationDistributionPercentExceeds100,
700
			);
701
		}
702

            
703
1
		let valid_values: Vec<(u8, u8)> = vec![
704
1
			(0, 100),
705
1
			(100, 0),
706
1
			(0, 0),
707
1
			(100, 0),
708
1
			(0, 100),
709
1
			(50, 50),
710
1
			(1, 99),
711
1
			(99, 1),
712
1
			(1, 1),
713
1
			(10, 20),
714
1
			(34, 32),
715
1
			(15, 10),
716
		];
717

            
718
13
		for (pbr_percentage, treasury_percentage) in valid_values {
719
12
			assert_ok!(ParachainStaking::set_inflation_distribution_config(
720
12
				RuntimeOrigin::root(),
721
12
				inflation_configs(1, pbr_percentage, 2, treasury_percentage),
722
			));
723
		}
724
1
	});
725
1
}
726

            
727
// ~~ PUBLIC ~~
728

            
729
// JOIN CANDIDATES
730

            
731
#[test]
732
1
fn join_candidates_event_emits_correctly() {
733
1
	ExtBuilder::default()
734
1
		.with_balances(vec![(1, 10)])
735
1
		.build()
736
1
		.execute_with(|| {
737
1
			assert_ok!(ParachainStaking::join_candidates(
738
1
				RuntimeOrigin::signed(1),
739
				10u128,
740
				0u32
741
			));
742
1
			assert_events_eq!(Event::JoinedCollatorCandidates {
743
1
				account: 1,
744
1
				amount_locked: 10u128,
745
1
				new_total_amt_locked: 10u128,
746
1
			});
747
1
		});
748
1
}
749

            
750
#[test]
751
1
fn join_candidates_reserves_balance() {
752
1
	ExtBuilder::default()
753
1
		.with_balances(vec![(1, 10)])
754
1
		.build()
755
1
		.execute_with(|| {
756
1
			assert_eq!(ParachainStaking::get_collator_stakable_free_balance(&1), 10);
757
1
			assert_ok!(ParachainStaking::join_candidates(
758
1
				RuntimeOrigin::signed(1),
759
				10u128,
760
				0u32
761
			));
762
1
			assert_eq!(ParachainStaking::get_collator_stakable_free_balance(&1), 0);
763
1
		});
764
1
}
765

            
766
#[test]
767
1
fn join_candidates_increases_total_staked() {
768
1
	ExtBuilder::default()
769
1
		.with_balances(vec![(1, 10)])
770
1
		.build()
771
1
		.execute_with(|| {
772
1
			assert_eq!(ParachainStaking::total(), 0);
773
1
			assert_ok!(ParachainStaking::join_candidates(
774
1
				RuntimeOrigin::signed(1),
775
				10u128,
776
				0u32
777
			));
778
1
			assert_eq!(ParachainStaking::total(), 10);
779
1
		});
780
1
}
781

            
782
#[test]
783
1
fn join_candidates_creates_candidate_state() {
784
1
	ExtBuilder::default()
785
1
		.with_balances(vec![(1, 10)])
786
1
		.build()
787
1
		.execute_with(|| {
788
1
			assert!(ParachainStaking::candidate_info(1).is_none());
789
1
			assert_ok!(ParachainStaking::join_candidates(
790
1
				RuntimeOrigin::signed(1),
791
				10u128,
792
				0u32
793
			));
794
1
			let candidate_state =
795
1
				ParachainStaking::candidate_info(1).expect("just joined => exists");
796
1
			assert_eq!(candidate_state.bond, 10u128);
797
1
		});
798
1
}
799

            
800
#[test]
801
1
fn join_candidates_adds_to_candidate_pool() {
802
1
	ExtBuilder::default()
803
1
		.with_balances(vec![(1, 10)])
804
1
		.build()
805
1
		.execute_with(|| {
806
1
			assert!(ParachainStaking::candidate_pool().0.is_empty());
807
1
			assert_ok!(ParachainStaking::join_candidates(
808
1
				RuntimeOrigin::signed(1),
809
				10u128,
810
				0u32
811
			));
812
1
			let candidate_pool = ParachainStaking::candidate_pool();
813
1
			assert_eq!(candidate_pool.0[0].owner, 1);
814
1
			assert_eq!(candidate_pool.0[0].amount, 10);
815
1
		});
816
1
}
817

            
818
#[test]
819
1
fn cannot_join_candidates_if_candidate() {
820
1
	ExtBuilder::default()
821
1
		.with_balances(vec![(1, 1000)])
822
1
		.with_candidates(vec![(1, 500)])
823
1
		.build()
824
1
		.execute_with(|| {
825
1
			assert_noop!(
826
1
				ParachainStaking::join_candidates(RuntimeOrigin::signed(1), 11u128, 100u32),
827
1
				Error::<Test>::CandidateExists
828
			);
829
1
		});
830
1
}
831

            
832
#[test]
833
1
fn cannot_join_candidates_if_delegator() {
834
1
	ExtBuilder::default()
835
1
		.with_balances(vec![(1, 50), (2, 20)])
836
1
		.with_candidates(vec![(1, 50)])
837
1
		.with_delegations(vec![(2, 1, 10)])
838
1
		.build()
839
1
		.execute_with(|| {
840
1
			assert_noop!(
841
1
				ParachainStaking::join_candidates(RuntimeOrigin::signed(2), 10u128, 1u32),
842
1
				Error::<Test>::DelegatorExists
843
			);
844
1
		});
845
1
}
846

            
847
#[test]
848
1
fn cannot_join_candidates_without_min_bond() {
849
1
	ExtBuilder::default()
850
1
		.with_balances(vec![(1, 1000)])
851
1
		.build()
852
1
		.execute_with(|| {
853
1
			assert_noop!(
854
1
				ParachainStaking::join_candidates(RuntimeOrigin::signed(1), 9u128, 100u32),
855
1
				Error::<Test>::CandidateBondBelowMin
856
			);
857
1
		});
858
1
}
859

            
860
#[test]
861
1
fn can_force_join_candidates_without_min_bond() {
862
1
	ExtBuilder::default()
863
1
		.with_balances(vec![(1, 10)])
864
1
		.build()
865
1
		.execute_with(|| {
866
1
			assert_ok!(ParachainStaking::force_join_candidates(
867
1
				RuntimeOrigin::root(),
868
				1,
869
				9,
870
				100u32
871
			));
872
1
			assert_events_eq!(Event::JoinedCollatorCandidates {
873
1
				account: 1,
874
1
				amount_locked: 9u128,
875
1
				new_total_amt_locked: 9u128,
876
1
			});
877
1
		});
878
1
}
879

            
880
#[test]
881
1
fn cannot_join_candidates_with_more_than_available_balance() {
882
1
	ExtBuilder::default()
883
1
		.with_balances(vec![(1, 500)])
884
1
		.build()
885
1
		.execute_with(|| {
886
1
			assert_noop!(
887
1
				ParachainStaking::join_candidates(RuntimeOrigin::signed(1), 501u128, 100u32),
888
1
				DispatchError::Module(ModuleError {
889
1
					index: 2,
890
1
					error: [8, 0, 0, 0],
891
1
					message: Some("InsufficientBalance")
892
1
				})
893
			);
894
1
		});
895
1
}
896

            
897
#[test]
898
1
fn insufficient_join_candidates_weight_hint_fails() {
899
1
	ExtBuilder::default()
900
1
		.with_balances(vec![(1, 20), (2, 20), (3, 20), (4, 20), (5, 20), (6, 20)])
901
1
		.with_candidates(vec![(1, 20), (2, 20), (3, 20), (4, 20), (5, 20)])
902
1
		.build()
903
1
		.execute_with(|| {
904
6
			for i in 0..5 {
905
5
				assert_noop!(
906
5
					ParachainStaking::join_candidates(RuntimeOrigin::signed(6), 20, i),
907
5
					Error::<Test>::TooLowCandidateCountWeightHintJoinCandidates
908
				);
909
			}
910
1
		});
911
1
}
912

            
913
#[test]
914
1
fn sufficient_join_candidates_weight_hint_succeeds() {
915
1
	ExtBuilder::default()
916
1
		.with_balances(vec![
917
1
			(1, 20),
918
1
			(2, 20),
919
1
			(3, 20),
920
1
			(4, 20),
921
1
			(5, 20),
922
1
			(6, 20),
923
1
			(7, 20),
924
1
			(8, 20),
925
1
			(9, 20),
926
1
		])
927
1
		.with_candidates(vec![(1, 20), (2, 20), (3, 20), (4, 20), (5, 20)])
928
1
		.build()
929
1
		.execute_with(|| {
930
1
			let mut count = 5u32;
931
5
			for i in 6..10 {
932
4
				assert_ok!(ParachainStaking::join_candidates(
933
4
					RuntimeOrigin::signed(i),
934
					20,
935
4
					count
936
				));
937
4
				count += 1u32;
938
			}
939
1
		});
940
1
}
941

            
942
#[test]
943
1
fn join_candidates_fails_if_above_max_candidate_count() {
944
1
	let mut candidates = vec![];
945
200
	for i in 1..=crate::mock::MaxCandidates::get() {
946
200
		candidates.push((i as u64, 80));
947
200
	}
948

            
949
1
	let new_candidate = crate::mock::MaxCandidates::get() as u64 + 1;
950
1
	let mut balances = candidates.clone();
951
1
	balances.push((new_candidate, 100));
952

            
953
1
	ExtBuilder::default()
954
1
		.with_balances(balances)
955
1
		.with_candidates(candidates)
956
1
		.build()
957
1
		.execute_with(|| {
958
1
			assert_noop!(
959
1
				ParachainStaking::join_candidates(
960
1
					RuntimeOrigin::signed(new_candidate),
961
					80,
962
1
					crate::mock::MaxCandidates::get(),
963
				),
964
1
				Error::<Test>::CandidateLimitReached,
965
			);
966
1
		});
967
1
}
968

            
969
// SCHEDULE LEAVE CANDIDATES
970

            
971
#[test]
972
1
fn leave_candidates_event_emits_correctly() {
973
1
	ExtBuilder::default()
974
1
		.with_balances(vec![(1, 10)])
975
1
		.with_candidates(vec![(1, 10)])
976
1
		.build()
977
1
		.execute_with(|| {
978
1
			assert_ok!(ParachainStaking::schedule_leave_candidates(
979
1
				RuntimeOrigin::signed(1),
980
				1u32
981
			));
982
1
			assert_events_eq!(Event::CandidateScheduledExit {
983
1
				exit_allowed_round: 1,
984
1
				candidate: 1,
985
1
				scheduled_exit: 3
986
1
			});
987
1
		});
988
1
}
989

            
990
#[test]
991
1
fn leave_candidates_removes_candidate_from_candidate_pool() {
992
1
	ExtBuilder::default()
993
1
		.with_balances(vec![(1, 10)])
994
1
		.with_candidates(vec![(1, 10)])
995
1
		.build()
996
1
		.execute_with(|| {
997
1
			assert_eq!(ParachainStaking::candidate_pool().0.len(), 1);
998
1
			assert_ok!(ParachainStaking::schedule_leave_candidates(
999
1
				RuntimeOrigin::signed(1),
				1u32
			));
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
}
#[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),
				1u32
			));
1
			assert_noop!(
1
				ParachainStaking::schedule_leave_candidates(RuntimeOrigin::signed(1), 1u32),
1
				Error::<Test>::CandidateAlreadyLeaving
			);
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
				);
			}
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(),
				true
			));
1
			assert!(ParachainStaking::marking_offline());
			// Set to false now
1
			assert_ok!(ParachainStaking::enable_marking_offline(
1
				RuntimeOrigin::root(),
				false
			));
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
}
#[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);
			// Round 2
1
			roll_to_round_begin(2);
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
			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
			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
			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(|| {
			// Enable killswitch
1
			<EnableMarkingOffline<Test>>::set(true);
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);
			// Change block author
1
			set_block_author(ACTIVE_COLLATOR);
			// INACTIVE_COLLATOR does not produce blocks on round 2 and 3
1
			roll_to_round_begin(4);
1
			roll_blocks(1);
			// On round 4 notify inactive collator
1
			assert_ok!(ParachainStaking::notify_inactive_collator(
1
				RuntimeOrigin::signed(1),
				INACTIVE_COLLATOR
			));
			// 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(|| {
			// Enable killswitch
1
			<EnableMarkingOffline<Test>>::set(true);
			// We need (strictly) more blocks per round than collators so rewards
			// can be distributed before the end of a round
1
			assert_ok!(ParachainStaking::set_blocks_per_round(
1
				RuntimeOrigin::root(),
				6u32
			));
			// ACTIVE_COLLATOR authors all the blocks while INACTIVE_COLLATOR stays inactive
1
			set_block_author(ACTIVE_COLLATOR);
			// Round 2
1
			roll_to_round_begin(2);
1
			roll_blocks(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);
			// 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);
			// 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),
				INACTIVE_COLLATOR
			));
1
			assert_events_eq!(
1
				Event::Rewarded {
1
					account: 2,
1
					rewards: 0,
1
				},
1
				Event::CandidateWentOffline {
1
					candidate: INACTIVE_COLLATOR
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(|| {
			// Enable killswitch
1
			<EnableMarkingOffline<Test>>::set(true);
			// Round 4
1
			roll_to_round_begin(4);
1
			roll_blocks(1);
			// Call 'notify_inactive_collator' extrinsic
1
			assert_noop!(
1
				ParachainStaking::notify_inactive_collator(RuntimeOrigin::signed(1), 1),
1
				Error::<Test>::TooLowCollatorCountToNotifyAsInactive
			);
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(|| {
			// Enable killswitch
1
			<EnableMarkingOffline<Test>>::set(true);
1
			set_block_author(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
			roll_blocks(1);
1
			assert_ok!(ParachainStaking::join_candidates(
1
				RuntimeOrigin::signed(6),
				10,
				100
			));
			// 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
			roll_blocks(1);
			// A candidate cannot be notified as inactive if it hasn't been selected
			// to produce blocks
1
			assert_noop!(
1
				ParachainStaking::notify_inactive_collator(RuntimeOrigin::signed(1), 6),
1
				Error::<Test>::CannotBeNotifiedAsInactive
			);
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(|| {
			// Enable killswitch
1
			<EnableMarkingOffline<Test>>::set(true);
			// Round 2
1
			roll_to_round_begin(2);
			// Change block author
1
			set_block_author(1);
			// Round 3
1
			roll_to_round_begin(3);
1
			roll_blocks(1);
			// Round 4
1
			roll_to_round_begin(4);
1
			roll_blocks(1);
			// Call 'notify_inactive_collator' extrinsic
1
			assert_noop!(
1
				ParachainStaking::notify_inactive_collator(RuntimeOrigin::signed(1), 1),
1
				Error::<Test>::CannotBeNotifiedAsInactive
			);
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(|| {
			// Enable killswitch
1
			<EnableMarkingOffline<Test>>::set(true);
			// Round 1
1
			roll_to_round_begin(1);
1
			roll_blocks(1);
			// Call 'notify_inactive_collator' extrinsic
1
			assert_noop!(
1
				ParachainStaking::notify_inactive_collator(RuntimeOrigin::signed(1), 1),
1
				Error::<Test>::CurrentRoundTooLow
			);
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
				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),
				1u32
			));
1
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_leave_candidates(
1
				RuntimeOrigin::signed(1),
				1,
				0
			));
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),
				1u32
			));
1
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_leave_candidates(
1
				RuntimeOrigin::signed(2),
				1,
				0
			));
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),
				1u32
			));
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
				);
			}
1
			assert_ok!(ParachainStaking::execute_leave_candidates(
1
				RuntimeOrigin::signed(2),
				1,
				3
			));
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),
				1u32
			));
1
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_leave_candidates(
1
				RuntimeOrigin::signed(1),
				1,
				0
			));
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),
				1u32
			));
1
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_leave_candidates(
1
				RuntimeOrigin::signed(1),
				1,
				0
			));
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),
				1u32
			));
			// 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,
				0
			));
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,
				5
			));
1
			let state = ParachainStaking::delegation_scheduled_requests(&1, &2);
1
			assert_eq!(
				state,
1
				vec![ScheduledRequest {
1
					when_executable: 3,
1
					action: DelegationAction::Decrease(5),
1
				}],
			);
1
			assert_ok!(ParachainStaking::schedule_leave_candidates(
1
				RuntimeOrigin::signed(1),
				1u32
			));
			// 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
			assert!(ParachainStaking::candidate_info(1).is_none());
1
			assert!(
1
				<DelegationScheduledRequests<Test>>::iter_prefix(1)
1
					.next()
1
					.is_none(),
				"delegation requests were not removed for the candidate"
			);
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),
				1u32
			));
1
			assert_noop!(
1
				ParachainStaking::execute_leave_candidates(RuntimeOrigin::signed(3), 1, 0)
1
					.map_err(|err| err.error),
1
				Error::<Test>::CandidateCannotLeaveYet
			);
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
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_leave_candidates(
1
				RuntimeOrigin::signed(3),
				1,
				0
			));
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),
				1u32
			));
1
			assert_ok!(ParachainStaking::cancel_leave_candidates(
1
				RuntimeOrigin::signed(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),
				1u32
			));
1
			assert_ok!(ParachainStaking::cancel_leave_candidates(
1
				RuntimeOrigin::signed(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),
				1u32
			));
1
			assert_ok!(ParachainStaking::cancel_leave_candidates(
1
				RuntimeOrigin::signed(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
}
#[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
}
// 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
}
#[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
}
#[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
			assert_noop!(
1
				ParachainStaking::go_online(RuntimeOrigin::signed(1)).map_err(|err| err.error),
1
				Error::<Test>::CannotGoOnlineIfLeaving
			);
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),
				30
			));
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),
				30
			));
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),
				30
			));
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),
				30
			));
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),
				30
			));
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),
				10
			));
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),
				5
			));
1
			assert_noop!(
1
				ParachainStaking::schedule_candidate_bond_less(RuntimeOrigin::signed(1), 5),
1
				Error::<Test>::PendingCandidateRequestAlreadyExists
			);
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
}
#[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
}
#[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
			assert_ok!(ParachainStaking::schedule_candidate_bond_less(
1
				RuntimeOrigin::signed(1),
				10
			));
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
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_leave_candidates(
1
				RuntimeOrigin::signed(1),
				1,
				0
			));
1
			assert_noop!(
1
				ParachainStaking::schedule_candidate_bond_less(RuntimeOrigin::signed(1), 10),
1
				Error::<Test>::CandidateDNE
			);
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),
				30
			));
1
			roll_to(10);
1
			roll_blocks(1);
1
			assert_ok!(ParachainStaking::execute_candidate_bond_less(
1
				RuntimeOrigin::signed(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),
				10
			));
1
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_candidate_bond_less(
1
				RuntimeOrigin::signed(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),
				10
			));
1
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_candidate_bond_less(
1
				RuntimeOrigin::signed(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),
				10
			));
1
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_candidate_bond_less(
1
				RuntimeOrigin::signed(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),
				10
			));
1
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_candidate_bond_less(
1
				RuntimeOrigin::signed(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),
				10
			));
1
			assert_ok!(ParachainStaking::cancel_candidate_bond_less(
1
				RuntimeOrigin::signed(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),
				10
			));
1
			assert_ok!(ParachainStaking::cancel_candidate_bond_less(
1
				RuntimeOrigin::signed(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),
				10
			));
1
			assert_noop!(
1
				ParachainStaking::cancel_candidate_bond_less(RuntimeOrigin::signed(2)),
1
				Error::<Test>::CandidateDNE
			);
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,
				10,
1
				Percent::zero(),
				0,
				0,
				0
			));
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,
				10,
1
				Percent::zero(),
				0,
				0,
				0
			));
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,
				10,
1
				Percent::zero(),
				0,
				0,
				0
			));
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,
				10,
1
				Percent::zero(),
				0,
				0,
				0
			));
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),
				20,
				0
			));
1
			assert_ok!(ParachainStaking::delegate_with_auto_compound(
1
				RuntimeOrigin::signed(2),
				1,
				20,
1
				Percent::zero(),
				0,
				0,
				0
			));
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
			assert_ok!(ParachainStaking::delegate_with_auto_compound(
1
				RuntimeOrigin::signed(2),
				4,
				10,
1
				Percent::zero(),
				0,
				0,
				2
			));
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,
					10,
1
					Percent::zero(),
					8,
					0,
					0
				),
1
				Error::<Test>::CannotDelegateLessThanOrEqualToLowestBottomWhenFull
			);
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,
				11,
1
				Percent::zero(),
				8,
				0,
				0
			));
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
			assert_ok!(ParachainStaking::delegate_with_auto_compound(
1
				RuntimeOrigin::signed(2),
				3,
				10,
1
				Percent::zero(),
				0,
				0,
				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,
					10,
1
					Percent::zero(),
					0,
					0,
					0
				),
1
				Error::<Test>::CandidateExists
			);
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,
					10,
1
					Percent::zero(),
					1,
					0,
					1
				),
1
				Error::<Test>::AlreadyDelegatedCandidate
			);
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),
					6,
					10,
1
					Percent::zero(),
					0,
					0,
					4,
				),
1
				Error::<Test>::ExceedMaxDelegationsPerDelegator,
			);
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),
					1,
					10,
4
					Percent::zero(),
4
					count,
					0,
					0
				));
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),
					2,
					10,
8
					Percent::zero(),
8
					count,
					0,
					1u32
				));
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),
						1,
						10,
4
						Percent::zero(),
4
						count,
						0,
						0
					),
4
					Error::<Test>::TooLowCandidateDelegationCountToDelegate
				);
			}
			// 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),
					1,
					10,
4
					Percent::zero(),
4
					count,
					0,
					0
				));
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),
						2,
						10,
8
						Percent::zero(),
8
						count,
						0,
						0
					),
8
					Error::<Test>::TooLowDelegationCountToDelegate
				);
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
			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),
				2,
				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
}
#[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
			));
			// this is an exit implicitly because last delegation revoked
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
				3
			));
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
}
#[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
}
#[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
}
// 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,
				5
			));
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,
				5
			));
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(),
				10
			);
1
			assert_ok!(ParachainStaking::delegator_bond_more(
1
				RuntimeOrigin::signed(2),
				1,
				5
			));
1
			assert_eq!(
1
				ParachainStaking::delegator_state(2)
1
					.expect("exists")
1
					.total(),
				15
			);
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,
				2
			);
1
			assert_eq!(
1
				ParachainStaking::top_delegations(1).unwrap().delegations[0].amount,
				10
			);
1
			assert_eq!(ParachainStaking::top_delegations(1).unwrap().total, 10);
1
			assert_ok!(ParachainStaking::delegator_bond_more(
1
				RuntimeOrigin::signed(2),
				1,
				5
			));
1
			assert_eq!(
1
				ParachainStaking::top_delegations(1).unwrap().delegations[0].owner,
				2
			);
1
			assert_eq!(
1
				ParachainStaking::top_delegations(1).unwrap().delegations[0].amount,
				15
			);
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]
					.owner,
				2
			);
1
			assert_eq!(
1
				ParachainStaking::bottom_delegations(1)
1
					.expect("exists")
1
					.delegations[0]
					.amount,
				10
			);
1
			assert_eq!(ParachainStaking::bottom_delegations(1).unwrap().total, 10);
1
			assert_ok!(ParachainStaking::delegator_bond_more(
1
				RuntimeOrigin::signed(2),
				1,
				5
			));
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]
					.owner,
				2
			);
1
			assert_eq!(
1
				ParachainStaking::bottom_delegations(1)
1
					.expect("exists")
1
					.delegations[0]
					.amount,
				15
			);
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,
				5
			));
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
			assert_ok!(ParachainStaking::delegator_bond_more(
1
				RuntimeOrigin::signed(2),
				1,
				5
			));
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
			assert_noop!(
1
				ParachainStaking::delegator_bond_more(RuntimeOrigin::signed(2), 1, 5),
1
				<Error<Test>>::PendingDelegationRevoke
			);
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,
				5,
			));
1
			assert_ok!(ParachainStaking::delegator_bond_more(
1
				RuntimeOrigin::signed(2),
				1,
				5
			));
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,
				5
			));
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,
				5
			));
1
			let state = ParachainStaking::delegation_scheduled_requests(&1, &2);
1
			assert_eq!(
				state,
1
				vec![ScheduledRequest {
1
					when_executable: 3,
1
					action: DelegationAction::Decrease(5),
1
				}],
			);
1
		});
1
}
#[test]
1
fn can_schedule_multiple_bond_less_requests_for_same_delegation() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 100), (2, 100)])
1
		.with_candidates(vec![(1, 50)])
1
		.with_delegations(vec![(2, 1, 30)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ParachainStaking::schedule_delegator_bond_less(
1
				RuntimeOrigin::signed(2),
				1,
				5
			));
1
			assert_ok!(ParachainStaking::schedule_delegator_bond_less(
1
				RuntimeOrigin::signed(2),
				1,
				5
			));
1
			assert_ok!(ParachainStaking::schedule_delegator_bond_less(
1
				RuntimeOrigin::signed(2),
				1,
				5
			));
1
			let requests = ParachainStaking::delegation_scheduled_requests(&1, &2);
1
			assert_eq!(requests.len(), 3);
1
			assert_eq!(requests[0].action, DelegationAction::Decrease(5));
1
			assert_eq!(requests[1].action, DelegationAction::Decrease(5));
1
			assert_eq!(requests[2].action, DelegationAction::Decrease(5));
1
		});
1
}
#[test]
1
fn execute_multiple_bond_less_requests_in_fifo_order() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 50), (2, 50)])
1
		.with_candidates(vec![(1, 50)])
1
		.with_delegations(vec![(2, 1, 20)])
1
		.build()
1
		.execute_with(|| {
1
			assert_eq!(
1
				ParachainStaking::delegator_state(2)
1
					.expect("exists")
1
					.total(),
				20
			);
1
			assert_ok!(ParachainStaking::schedule_delegator_bond_less(
1
				RuntimeOrigin::signed(2),
				1,
				5
			));
1
			assert_ok!(ParachainStaking::schedule_delegator_bond_less(
1
				RuntimeOrigin::signed(2),
				1,
				7
			));
1
			let requests = ParachainStaking::delegation_scheduled_requests(&1, &2);
1
			assert_eq!(requests.len(), 2);
1
			assert_eq!(requests[0].action, DelegationAction::Decrease(5));
1
			assert_eq!(requests[1].action, DelegationAction::Decrease(7));
1
			roll_to(10);
			// Execute first pending request
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
				2,
				1
			));
1
			let state_after_first = ParachainStaking::delegator_state(2).expect("exists");
1
			assert_eq!(state_after_first.total(), 15);
1
			let remaining_requests = ParachainStaking::delegation_scheduled_requests(&1, &2);
1
			assert_eq!(remaining_requests.len(), 1);
1
			assert_eq!(remaining_requests[0].action, DelegationAction::Decrease(7));
			// Execute second pending request
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
				2,
				1
			));
1
			let state_after_second = ParachainStaking::delegator_state(2).expect("exists");
1
			assert_eq!(state_after_second.total(), 8);
1
			let remaining_requests = ParachainStaking::delegation_scheduled_requests(&1, &2);
1
			assert_eq!(remaining_requests.len(), 0);
1
		});
1
}
#[test]
1
fn cannot_revoke_delegation_if_other_request_pending() {
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_delegator_bond_less(
1
				RuntimeOrigin::signed(2),
				1,
				5
			));
1
			assert_noop!(
1
				ParachainStaking::schedule_revoke_delegation(RuntimeOrigin::signed(2), 1)
1
					.map_err(|err| err.error),
1
				Error::<Test>::PendingDelegationRequestAlreadyExists
			);
1
		});
1
}
#[test]
1
fn cannot_schedule_more_than_max_bond_less_requests_per_delegation() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 200), (2, 200)])
1
		.with_candidates(vec![(1, 100)])
1
		.with_delegations(vec![(2, 1, 80)])
1
		.build()
1
		.execute_with(|| {
51
			for _ in 0..50 {
50
				assert_ok!(ParachainStaking::schedule_delegator_bond_less(
50
					RuntimeOrigin::signed(2),
					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>::ExceedMaxDelegationsPerDelegator
			);
1
		});
1
}
#[test]
1
fn cannot_exceed_max_delegators_with_pending_requests_per_collator() {
	// Build a scenario with one collator and one real delegator,
	// then artificially saturate the scheduled-requests map for that collator
	// to the configured maximum number of delegators.
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 1_000), (2, 1_000)])
1
		.with_candidates(vec![(1, 100)])
1
		.with_delegations(vec![(2, 1, 10)])
1
		.build()
1
		.execute_with(|| {
1
			let max_delegators_per_collator =
1
				(<Test as crate::Config>::MaxTopDelegationsPerCandidate::get()
1
					+ <Test as crate::Config>::MaxBottomDelegationsPerCandidate::get()) as u64;
			// Artificially create `max_delegators_per_collator` delegators with pending
			// requests for collator 1 by directly populating storage.
8
			for i in 0..max_delegators_per_collator {
8
				let fake_delegator: AccountId = 1000 + i;
8
				let requests = BoundedVec::try_from(vec![ScheduledRequest {
8
					when_executable: 1,
8
					action: DelegationAction::Decrease(1),
8
				}])
8
				.expect("must succeed");
8
				<DelegationScheduledRequests<Test>>::insert(1, fake_delegator, requests);
8
			}
			// Maintain the per-collator counter consistently with the synthetic setup.
1
			<DelegationScheduledRequestsPerCollator<Test>>::insert(
				1,
1
				max_delegators_per_collator as u32,
			);
			// Now the collator already has the maximum number of delegators with pending
			// requests. Scheduling a new request for delegator 2 towards collator 1
			// should fail with `ExceedMaxDelegationsPerDelegator`.
1
			assert_noop!(
1
				ParachainStaking::schedule_delegator_bond_less(RuntimeOrigin::signed(2), 1, 1)
1
					.map_err(|err| err.error),
1
				Error::<Test>::ExceedMaxDelegationsPerDelegator
			);
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
			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
}
#[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
}
#[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
}
#[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
}
#[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
}
#[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
}
// EXECUTE PENDING DELEGATION REQUEST
// 1. REVOKE DELEGATION
#[test]
1
fn execute_revoke_delegation_emits_exit_event_if_exit_happens() {
	// 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
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
				2,
				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() {
	// 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
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
				2,
				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
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
				2,
				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
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
				2,
				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!(
1
				ParachainStaking::delegation_scheduled_requests(&1, &2).is_empty(),
				"no scheduled requests should exist before scheduling revoke"
			);
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
				1
			));
1
			assert!(
1
				!ParachainStaking::delegation_scheduled_requests(&1, &2).is_empty(),
				"a scheduled revoke request should exist after scheduling"
			);
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
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
				2,
				1
			));
1
			assert!(
1
				ParachainStaking::delegation_scheduled_requests(&1, &2).is_empty(),
				"scheduled revoke request should be removed after execution"
			);
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
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
				2,
				1
			));
1
			assert!(
1
				ParachainStaking::delegation_scheduled_requests(&1, &2).is_empty(),
				"delegation request 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
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
				2,
				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
			roll_to(10);
			// this will be confusing for people
			// if status is leaving, then execute_delegation_request works if last delegation
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
				2,
				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")
					.delegation_count,
				1u32
			);
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
				1
			));
1
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
				2,
				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
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
				1
			));
1
			roll_to(10);
			// can execute delegation request for leaving candidate
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
				2,
				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
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
				1
			));
1
			roll_to(10);
			// revocation executes during execute leave candidates (callable by anyone)
1
			assert_ok!(ParachainStaking::execute_leave_candidates(
1
				RuntimeOrigin::signed(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
			assert_ok!(ParachainStaking::delegator_bond_more(
1
				RuntimeOrigin::signed(2),
				3,
				10
			));
1
			roll_to(100);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
				2,
				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
			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
			assert_ok!(ParachainStaking::schedule_delegator_bond_less(
1
				RuntimeOrigin::signed(2),
				3,
				2
			));
1
			roll_to(10);
1
			roll_blocks(1);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
				2,
				1
			));
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
				2,
				3
			));
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
			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,
				5
			));
1
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
				2,
				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,
				5
			));
1
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
				2,
				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(),
				10
			);
1
			assert_ok!(ParachainStaking::schedule_delegator_bond_less(
1
				RuntimeOrigin::signed(2),
				1,
				5
			));
1
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
				2,
				1
			));
1
			assert_eq!(
1
				ParachainStaking::delegator_state(2)
1
					.expect("exists")
1
					.total(),
				5
			);
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,
				2
			);
1
			assert_eq!(
1
				ParachainStaking::top_delegations(1).unwrap().delegations[0].amount,
				10
			);
1
			assert_ok!(ParachainStaking::schedule_delegator_bond_less(
1
				RuntimeOrigin::signed(2),
				1,
				5
			));
1
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
				2,
				1
			));
1
			assert_eq!(
1
				ParachainStaking::top_delegations(1).unwrap().delegations[0].owner,
				2
			);
1
			assert_eq!(
1
				ParachainStaking::top_delegations(1).unwrap().delegations[0].amount,
				5
			);
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,
				5
			));
1
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
				2,
				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,
				2
			));
1
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
				2,
				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!(
				pre_call_candidate_info.total_counted,
				post_call_candidate_info.total_counted
			);
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,
				4
			));
1
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(6),
				6,
				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,
				post_call_candidate_info.total_counted
			);
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
			assert_ok!(ParachainStaking::schedule_delegator_bond_less(
1
				RuntimeOrigin::signed(2),
				1,
				5
			));
1
			roll_to(10);
			// can execute bond more delegation request for leaving candidate
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
				2,
				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
			assert_ok!(ParachainStaking::cancel_delegation_request(
1
				RuntimeOrigin::signed(2),
				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
			let state = ParachainStaking::delegation_scheduled_requests(&1, &2);
1
			assert_eq!(
				state,
1
				vec![ScheduledRequest {
1
					when_executable: 3,
1
					action: DelegationAction::Revoke(10),
1
				}],
			);
1
			assert_eq!(
1
				ParachainStaking::delegator_state(&2)
1
					.map(|x| x.less_total)
1
					.expect("delegator state must exist"),
				10
			);
1
			assert_ok!(ParachainStaking::cancel_delegation_request(
1
				RuntimeOrigin::signed(2),
				1
			));
1
			assert!(
1
				ParachainStaking::delegation_scheduled_requests(&1, &2).is_empty(),
				"scheduled revoke request should be removed after cancel"
			);
1
			assert_eq!(
1
				ParachainStaking::delegator_state(&2)
1
					.map(|x| x.less_total)
1
					.expect("delegator state must exist"),
				0
			);
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,
				5
			));
1
			assert_ok!(ParachainStaking::cancel_delegation_request(
1
				RuntimeOrigin::signed(2),
				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,
				5
			));
1
			let state = ParachainStaking::delegation_scheduled_requests(&1, &2);
1
			assert_eq!(
				state,
1
				vec![ScheduledRequest {
1
					when_executable: 3,
1
					action: DelegationAction::Decrease(5),
1
				}],
			);
1
			assert_eq!(
1
				ParachainStaking::delegator_state(&2)
1
					.map(|x| x.less_total)
1
					.expect("delegator state must exist"),
				5
			);
1
			assert_ok!(ParachainStaking::cancel_delegation_request(
1
				RuntimeOrigin::signed(2),
				1
			));
1
			assert!(
1
				ParachainStaking::delegation_scheduled_requests(&1, &2).is_empty(),
				"scheduled bond-less request should be removed after cancel"
			);
1
			assert_eq!(
1
				ParachainStaking::delegator_state(&2)
1
					.map(|x| x.less_total)
1
					.expect("delegator state must exist"),
				0
			);
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
			assert_eq!(
1
				ParachainStaking::delegator_state(&2)
1
					.map(|x| x.less_total)
1
					.expect("delegator state must exist"),
				10
			);
1
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
				2,
				1
			));
1
			assert_eq!(
1
				ParachainStaking::delegator_state(&2)
1
					.map(|x| x.less_total)
1
					.expect("delegator state must exist"),
				0
			);
1
			assert_ok!(ParachainStaking::delegate_with_auto_compound(
1
				RuntimeOrigin::signed(2),
				5,
				10,
1
				Percent::zero(),
				0,
				0,
				2
			));
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
				3
			));
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
				4
			));
1
			assert_eq!(
1
				ParachainStaking::delegator_state(&2)
1
					.map(|x| x.less_total)
1
					.expect("delegator state must exist"),
				20,
			);
1
			roll_to(20);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
				2,
				3
			));
1
			assert_eq!(
1
				ParachainStaking::delegator_state(&2)
1
					.map(|x| x.less_total)
1
					.expect("delegator state must exist"),
				10,
			);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
				2,
				4
			));
1
			assert_eq!(
1
				ParachainStaking::delegator_state(&2)
1
					.map(|x| x.less_total)
1
					.expect("delegator state must exist"),
				0
			);
1
		});
1
}
#[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),
				20u128,
				100u32
			));
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
			roll_blocks(1);
1
			assert_ok!(ParachainStaking::delegate_with_auto_compound(
1
				RuntimeOrigin::signed(5),
				4,
				10,
1
				Percent::zero(),
				10,
				0,
				10
			));
1
			assert_ok!(ParachainStaking::delegate_with_auto_compound(
1
				RuntimeOrigin::signed(6),
				4,
				10,
1
				Percent::zero(),
				10,
				0,
				10
			));
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
			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
				},
			);
			// only reward author with id 4
1
			set_author(3, 4, POINTS_PER_ROUND);
1
			roll_to_round_begin(5);
			// 20% of 10 is commission + due_portion (0) = 2 + 4 = 6
			// 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
			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
}
#[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),
				2
			));
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),
				2,
				2
			));
			// 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),
				6
			));
			// 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
			roll_to_round_begin(4);
1
			roll_blocks(1);
1
			assert_ok!(ParachainStaking::execute_leave_candidates(
1
				RuntimeOrigin::signed(6),
				6,
				0
			));
1
			assert_ok!(ParachainStaking::join_candidates(
1
				RuntimeOrigin::signed(6),
				69u128,
				100u32
			));
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
			roll_to_round_begin(6);
			// 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
}
#[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);
			// 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
				},
			);
			// ~ 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
				},
			);
			// 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);
			// ~ 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);
			// 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
			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);
			// 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
			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);
			// 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
			roll_blocks(1);
1
			assert_ok!(ParachainStaking::delegate_with_auto_compound(
1
				RuntimeOrigin::signed(6),
				2,
				10,
1
				Percent::zero(),
				10,
				0,
				10
			));
1
			assert_ok!(ParachainStaking::delegate_with_auto_compound(
1
				RuntimeOrigin::signed(6),
				3,
				10,
1
				Percent::zero(),
				10,
				0,
				10
			));
1
			assert_ok!(ParachainStaking::delegate_with_auto_compound(
1
				RuntimeOrigin::signed(6),
				4,
				10,
1
				Percent::zero(),
				10,
				0,
				10
			));
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
			roll_to_round_begin(6);
1
			roll_blocks(1);
1
			assert_ok!(ParachainStaking::delegate_with_auto_compound(
1
				RuntimeOrigin::signed(7),
				2,
				80,
1
				Percent::zero(),
				10,
				0,
				10
			));
1
			assert_ok!(ParachainStaking::delegate_with_auto_compound(
1
				RuntimeOrigin::signed(10),
				2,
				10,
1
				Percent::zero(),
				10,
				0,
				10
			));
1
			assert_ok!(ParachainStaking::schedule_leave_candidates(
1
				RuntimeOrigin::signed(2),
				5
			));
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
			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
				},
			);
			// 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(),
				2usize
			);
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(),
				4usize
			);
1
			assert_eq!(
1
				query_freeze_amount(6, &FreezeReason::StakingDelegator.into()),
				40
			);
1
			assert_eq!(
1
				query_freeze_amount(7, &FreezeReason::StakingDelegator.into()),
				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),
				2,
				5
			));
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(),
				1usize
			);
1
			assert_eq!(
1
				ParachainStaking::delegator_state(6)
1
					.unwrap()
1
					.delegations
1
					.0
1
					.len(),
				3usize
			);
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(|| {
			// Verifies the revocation request is initially empty
1
			assert!(
1
				ParachainStaking::delegation_scheduled_requests(&2, &3).is_empty(),
				"no scheduled revoke request should exist before scheduling"
			);
1
			assert_ok!(ParachainStaking::schedule_leave_candidates(
1
				RuntimeOrigin::signed(2),
				2
			));
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(3),
				2
			));
			// Verifies the revocation request is present
1
			assert!(
1
				!ParachainStaking::delegation_scheduled_requests(&2, &3).is_empty(),
				"a scheduled revoke request should exist after scheduling"
			);
1
			roll_to(16);
1
			assert_ok!(ParachainStaking::execute_leave_candidates(
1
				RuntimeOrigin::signed(2),
				2,
				2
			));
			// Verifies the revocation request is again empty
1
			assert!(
1
				ParachainStaking::delegation_scheduled_requests(&2, &3).is_empty(),
				"scheduled revoke request should be removed when candidate leaves"
			);
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(|| {
			// ~ 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);
			// 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
			set_author(3, 1, POINTS_PER_ROUND);
1
			set_author(4, 1, POINTS_PER_ROUND);
1
			roll_to_round_begin(4);
			// 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
			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
				},
			);
			// ~ set block author as 1 for all blocks this round
1
			set_author(5, 1, POINTS_PER_ROUND);
1
			roll_blocks(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
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(6),
				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
			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
			set_author(6, 1, POINTS_PER_ROUND);
			// 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),
				6,
				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
			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
				},
			);
			// 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);
			// 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
			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
			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
			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
			set_author(9, 1, POINTS_PER_ROUND);
1
			roll_to_round_begin(9);
			// 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
			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
			roll_blocks(1);
1
			assert_ok!(ParachainStaking::delegate_with_auto_compound(
1
				RuntimeOrigin::signed(8),
				1,
				10,
1
				Percent::zero(),
				10,
				0,
				10
			));
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);
			// 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
			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
			roll_to_round_begin(11);
			// 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
			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
			roll_to_round_begin(12);
			// new delegation is rewarded for first time
			// 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
			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
}
#[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(|| {
			// 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,
				10,
1
				Percent::zero(),
				10,
				0,
				10
			));
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,
				10,
1
				Percent::zero(),
				10,
				0,
				10
			));
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,
				10,
1
				Percent::zero(),
				10,
				0,
				10
			));
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,
				10,
1
				Percent::zero(),
				10,
				0,
				10
			));
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,
				8
			));
			// 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,
				8
			));
			// 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,
				3
			));
1
			roll_to(30);
			// 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),
				10,
				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,
				4
			));
1
			roll_to(40);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(9),
				9,
				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();
			// 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,
				8
			));
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();
			// 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,
				8
			));
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();
			// 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,
				8
			));
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();
			// 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,
				8
			));
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();
			// 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();
			// 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,
				15,
1
				Percent::zero(),
				10,
				0,
				10
			));
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();
			// 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,
				10,
1
				Percent::zero(),
				10,
				0,
				10
			));
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();
			// 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,
				3
			));
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();
			// 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
			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();
			// 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,
				2
			));
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),
				6,
				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();
			// 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
			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),
				6,
				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();
			// 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(|| {
			// 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
			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
			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
			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);
			// 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
				},
			);
			// 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
			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
				},
			);
			// 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);
			// 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(|| {
			// candidate 3 will not produce blocks
1
			set_author(1, 1, POINTS_PER_BLOCK * 3);
1
			set_author(1, 2, POINTS_PER_BLOCK * 2);
			// 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(|| {
			// 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),
					WithdrawReasons::FEE,
2
					ExistenceRequirement::AllowDeath,
				)
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
				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
				set_round_points(round);
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
				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
				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
				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
				roll_blocks(1);
				// Since we defer first deferred staking payout, this test have the maximum amout of
				// supported collators. This eman that the next round is trigerred one block after
				// the last reward.
				//assert_no_events!();
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
				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
			));
			// 10 delegates to full 1 => kicks lowest delegation (2, 19)
1
			assert_ok!(ParachainStaking::delegate_with_auto_compound(
1
				RuntimeOrigin::signed(10),
				1,
				20,
1
				Percent::zero(),
				8,
				0,
				0,
			));
			// check the event
1
			assert_events_emitted!(Event::DelegationKicked {
1
				delegator: 2,
1
				candidate: 1,
1
				unstaked_amount: 19,
1
			});
			// ensure request DNE
1
			assert!(
1
				ParachainStaking::delegation_scheduled_requests(&1, &2).is_empty(),
				"delegation request should be removed when delegation is kicked"
			);
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
				));
			}
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,
					0,
				));
			}
			// next round
1
			roll_to_round_begin(4);
1
			let new_round = ParachainStaking::round().current;
			// 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
				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(|| {
			// preset rewards for rounds 1, 2 and 3
3
			(1..=3).for_each(|round| set_author(round, 1, POINTS_PER_ROUND));
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
				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
			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
				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(|| {
			// preset rewards for rounds 2, 3 and 4
3
			(2..=4).for_each(|round| set_author(round, 1, POINTS_PER_ROUND));
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
				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
			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
				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
}
#[test]
1
fn test_delegator_scheduled_for_bond_decrease_is_rewarded_for_previous_rounds_but_less_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, 20), (2, 3, 10)])
1
		.build()
1
		.execute_with(|| {
			// preset rewards for rounds 1, 2 and 3
3
			(1..=3).for_each(|round| set_author(round, 1, POINTS_PER_ROUND));
1
			assert_ok!(ParachainStaking::schedule_delegator_bond_less(
1
				RuntimeOrigin::signed(2),
				1,
				10,
			));
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
			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
			let collator_snapshot =
1
				ParachainStaking::at_stake(ParachainStaking::round().current, 1)
1
					.unwrap_or_default();
1
			assert_eq!(
				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(|| {
			// preset rewards for rounds 2, 3 and 4
3
			(2..=4).for_each(|round| set_author(round, 1, POINTS_PER_ROUND));
1
			assert_ok!(ParachainStaking::schedule_delegator_bond_less(
1
				RuntimeOrigin::signed(2),
				1,
				10,
			));
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
			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
			let collator_snapshot =
1
				ParachainStaking::at_stake(ParachainStaking::round().current, 1)
1
					.unwrap_or_default();
1
			assert_eq!(
				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
}
#[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(|| {
			// preset rewards for rounds 1, 2 and 3
3
			(1..=3).for_each(|round| set_author(round, 1, POINTS_PER_ROUND));
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
				1,
			));
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
				3,
			));
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
			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
			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
				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(|| {
			// preset rewards for rounds 2, 3 and 4
3
			(2..=4).for_each(|round| set_author(round, 1, POINTS_PER_ROUND));
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
				1,
			));
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
				3,
			));
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
			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
			assert_ok!(ParachainStaking::cancel_delegation_request(
1
				RuntimeOrigin::signed(2),
				3,
			));
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
				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
}
#[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,
				2,
1
				BoundedVec::try_from(vec![ScheduledRequest {
1
					when_executable: 3,
1
					action: DelegationAction::Decrease(5),
1
				}])
1
				.expect("must succeed"),
			);
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,
				2,
1
				BoundedVec::try_from(vec![ScheduledRequest {
1
					when_executable: 3,
1
					action: DelegationAction::Revoke(5),
1
				}])
1
				.expect("must succeed"),
			);
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,
				2,
1
				BoundedVec::try_from(vec![ScheduledRequest {
1
					when_executable: 3,
1
					action: DelegationAction::Decrease(5),
1
				}])
1
				.expect("must succeed"),
			);
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,
				2,
1
				BoundedVec::try_from(vec![ScheduledRequest {
1
					when_executable: 3,
1
					action: DelegationAction::Revoke(5),
1
				}])
1
				.expect("must succeed"),
			);
1
			assert!(ParachainStaking::delegation_request_revoke_exists(&1, &2));
1
		});
1
}
#[test]
1
fn locking_zero_amount_removes_lock() {
	// this test demonstrates the behavior of fungible's freeze mechanism
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 100)])
1
		.build()
1
		.execute_with(|| {
1
			let reason = &crate::pallet::FreezeReason::StakingDelegator.into();
1
			assert_eq!(query_freeze_amount(1, reason), 0);
			// Freeze 1 unit
1
			assert_ok!(Balances::set_freeze(reason, &1, 1));
1
			assert_eq!(query_freeze_amount(1, reason), 1);
			// Thaw the freeze
1
			assert_ok!(Balances::thaw(reason, &1));
1
			assert_eq!(query_freeze_amount(1, reason), 0);
1
		});
1
}
#[test]
1
fn revoke_last_removes_freeze() {
	use crate::mock::query_freeze_amount;
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
			let reason = &crate::pallet::FreezeReason::StakingDelegator.into();
1
			assert_eq!(query_freeze_amount(3, reason), 55);
			// schedule and remove one...
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(3),
				1
			));
1
			roll_to_round_begin(3);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(3),
				3,
				1
			));
1
			assert_eq!(query_freeze_amount(3, reason), 25);
			// schedule and remove the other...
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(3),
				2
			));
1
			roll_to_round_begin(5);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(3),
				3,
				2
			));
1
			assert_eq!(query_freeze_amount(3, reason), 0);
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
			assert_noop!(
1
				ParachainStaking::set_auto_compound(
1
					RuntimeOrigin::signed(2),
					1,
1
					Percent::from_percent(50),
1
					candidate_auto_compounding_delegation_count_hint,
1
					delegation_hint,
				),
1
				<Error<Test>>::TooLowDelegationCountToAutoCompound,
			);
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
			.set_storage(&1);
1
			let candidate_auto_compounding_delegation_count_hint = 0; // is however, 1
1
			let delegation_hint = 1;
1
			assert_noop!(
1
				ParachainStaking::set_auto_compound(
1
					RuntimeOrigin::signed(2),
					1,
1
					Percent::from_percent(50),
1
					candidate_auto_compounding_delegation_count_hint,
1
					delegation_hint,
				),
1
				<Error<Test>>::TooLowCandidateAutoCompoundingDelegationCountToAutoCompound,
			);
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
				Percent::from_percent(50),
				0,
				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
}
#[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
			.set_storage(&1);
1
			assert_ok!(ParachainStaking::set_auto_compound(
1
				RuntimeOrigin::signed(2),
				1,
1
				Percent::from_percent(50),
				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
}
#[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
			.set_storage(&1);
1
			assert_ok!(ParachainStaking::set_auto_compound(
1
				RuntimeOrigin::signed(2),
				1,
1
				Percent::zero(),
				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
				Percent::from_percent(50),
				0,
				2,
			));
1
			assert_ok!(ParachainStaking::set_auto_compound(
1
				RuntimeOrigin::signed(2),
				3,
1
				Percent::from_percent(50),
				0,
				2,
			));
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
				1
			));
1
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
				2,
				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
				Percent::from_percent(50),
				0,
				2,
			));
1
			assert_ok!(ParachainStaking::set_auto_compound(
1
				RuntimeOrigin::signed(2),
				3,
1
				Percent::from_percent(50),
				0,
				2,
			));
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
				1,
			));
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
				3,
			));
1
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
				2,
				1,
			));
1
			assert_ok!(ParachainStaking::execute_delegation_request(
1
				RuntimeOrigin::signed(2),
				2,
				3,
			));
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
				Percent::from_percent(50),
				0,
				2,
			));
1
			assert_ok!(ParachainStaking::set_auto_compound(
1
				RuntimeOrigin::signed(2),
				3,
1
				Percent::from_percent(50),
				0,
				2,
			));
1
			assert_ok!(ParachainStaking::schedule_leave_candidates(
1
				RuntimeOrigin::signed(1),
				2
			));
1
			roll_to(10);
1
			assert_ok!(ParachainStaking::execute_leave_candidates(
1
				RuntimeOrigin::signed(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
				Percent::from_percent(50),
				0,
				2,
			));
			// kicks lowest delegation (2, 19)
1
			assert_ok!(ParachainStaking::delegate_with_auto_compound(
1
				RuntimeOrigin::signed(10),
				1,
				20,
1
				Percent::zero(),
				8,
				0,
				0,
			));
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
				Percent::from_percent(50),
				0,
				1,
			));
1
			assert_ok!(ParachainStaking::set_auto_compound(
1
				RuntimeOrigin::signed(3),
				1,
1
				Percent::from_percent(50),
				1,
				1,
			));
1
			roll_to_round_begin(3);
			// schedule revoke for delegator 2; no rewards should be compounded
1
			assert_ok!(ParachainStaking::schedule_revoke_delegation(
1
				RuntimeOrigin::signed(2),
				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: 500,
1
				},
1
				Event::NewRound {
1
					starting_block: 15,
1
					round: 4,
1
					selected_collators_number: 1,
1
					total_balance: 500,
1
				},
			);
1
			roll_blocks(1);
1
			assert_events_eq!(
1
				Event::Rewarded {
1
					account: 1,
1
					rewards: 4,
1
				},
				// no compound since revoke request exists
1
				Event::Rewarded {
1
					account: 2,
1
					rewards: 4,
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
}
#[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
				Percent::from_percent(0),
				0,
				1,
			));
1
			assert_ok!(ParachainStaking::set_auto_compound(
1
				RuntimeOrigin::signed(3),
				1,
1
				Percent::from_percent(50),
				1,
				1,
			));
1
			assert_ok!(ParachainStaking::set_auto_compound(
1
				RuntimeOrigin::signed(4),
				1,
1
				Percent::from_percent(100),
				2,
				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: 900,
1
				},
1
				Event::NewRound {
1
					starting_block: 15,
1
					round: 4,
1
					selected_collators_number: 1,
1
					total_balance: 900,
1
				},
			);
1
			roll_blocks(1);
1
			assert_events_eq!(
1
				Event::Rewarded {
1
					account: 1,
1
					rewards: 6,
1
				},
				// 0%
1
				Event::Rewarded {
1
					account: 2,
1
					rewards: 4,
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
				},
				// 100%
1
				Event::Rewarded {
1
					account: 4,
1
					rewards: 4,
1
				},
1
				Event::Compounded {
1
					candidate: 1,
1
					delegator: 4,
1
					amount: 4,
1
				},
				// no-config
1
				Event::Rewarded {
1
					account: 5,
1
					rewards: 4,
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
			assert_noop!(
1
				ParachainStaking::delegate_with_auto_compound(
1
					RuntimeOrigin::signed(2),
					1,
					10,
1
					Percent::from_percent(50),
1
					candidate_delegation_count_hint,
1
					candidate_auto_compounding_delegation_count_hint,
1
					delegation_hint,
				),
1
				<Error<Test>>::TooLowDelegationCountToDelegate,
			);
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
			assert_noop!(
1
				ParachainStaking::delegate_with_auto_compound(
1
					RuntimeOrigin::signed(2),
					1,
					10,
1
					Percent::from_percent(50),
1
					candidate_delegation_count_hint,
1
					candidate_auto_compounding_delegation_count_hint,
1
					delegation_hint,
				),
1
				<Error<Test>>::TooLowCandidateDelegationCountToDelegate,
			);
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
			assert_noop!(
1
				ParachainStaking::delegate_with_auto_compound(
1
					RuntimeOrigin::signed(2),
					1,
					10,
1
					Percent::from_percent(50),
1
					candidate_delegation_count_hint,
1
					candidate_auto_compounding_delegation_count_hint,
1
					delegation_hint,
				),
1
				<Error<Test>>::TooLowCandidateAutoCompoundingDelegationCountToDelegate,
			);
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,
				10,
1
				Percent::from_percent(50),
				0,
				0,
				0,
			));
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
}
#[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,
				10,
1
				Percent::zero(),
				0,
				0,
				0,
			));
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,
				10,
1
				Percent::from_percent(50),
				0,
				0,
				0,
			));
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,
				10,
1
				Percent::from_percent(50),
				0,
				0,
				0
			));
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,
				10,
1
				Percent::from_percent(50),
				0,
				0,
				0
			));
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),
				20,
				0
			));
1
			assert_ok!(ParachainStaking::delegate_with_auto_compound(
1
				RuntimeOrigin::signed(2),
				1,
				20,
1
				Percent::from_percent(50),
				0,
				0,
				0
			));
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
			assert_ok!(ParachainStaking::delegate_with_auto_compound(
1
				RuntimeOrigin::signed(2),
				4,
				10,
1
				Percent::from_percent(50),
				0,
				0,
				2
			));
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,
					10,
1
					Percent::from_percent(50),
					8,
					0,
					0
				),
1
				Error::<Test>::CannotDelegateLessThanOrEqualToLowestBottomWhenFull
			);
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,
				11,
1
				Percent::from_percent(50),
				8,
				0,
				0
			));
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
			assert_ok!(ParachainStaking::delegate_with_auto_compound(
1
				RuntimeOrigin::signed(2),
				3,
				10,
1
				Percent::from_percent(50),
				0,
				0,
				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,
					10,
1
					Percent::from_percent(50),
					0,
					0,
					0
				),
1
				Error::<Test>::CandidateExists
			);
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,
					10,
1
					Percent::from_percent(50),
					0,
					1,
					1
				),
1
				Error::<Test>::AlreadyDelegatedCandidate
			);
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),
					6,
					10,
1
					Percent::from_percent(50),
					0,
					0,
					4
				),
1
				Error::<Test>::ExceedMaxDelegationsPerDelegator,
			);
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(|| {
			// We already have an auto-compounding delegation from 3 -> 1, so the hint validation
			// 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,
				10,
1
				Percent::zero(),
				1,
				0,
				0,
			));
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);
			// 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);
			// the total on_init weight during our round change. this number is taken from running
			// the fn with a given weights.rs benchmark, so will need to be updated as benchmarks
			// change.
			//
			// following this assertion, we add individual weights together to show that we can
			// derive this number independently.
1
			let expected_on_init = 3018132161;
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();
			// TODO: this should be the same as <TotalSelected<Test>>. I believe this relates to
			// 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();
			// SlotProvider read
1
			expected_weight += RocksDbWeight::get().reads_writes(1, 0).ref_time();
			// Round write, done in on-round-change code block inside on_initialize()
1
			expected_weight += RocksDbWeight::get().reads_writes(0, 1).ref_time();
			// more reads/writes manually accounted for for on_finalize
1
			expected_weight += RocksDbWeight::get().reads_writes(4, 3).ref_time();
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(|| {
			// 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
}
#[test]
1
fn test_linear_inflation_threshold() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(1, 1_000_000_000)])
1
		.build()
1
		.execute_with(|| {
			// Set up initial state
1
			let initial_issuance = Balances::total_issuance();
1
			let threshold = <Test as crate::Config>::LinearInflationThreshold::get();
1
			assert_eq!(threshold, Some(1_200_000_000));
			// When total issuance is below threshold, should use total issuance
1
			let round_below = crate::inflation::round_issuance_range::<Test>(Range {
1
				min: Perbill::from_percent(5),
1
				ideal: Perbill::from_percent(5),
1
				max: Perbill::from_percent(5),
1
			});
1
			assert_eq!(round_below.ideal, initial_issuance / 20); // 5% of total issuance
			// Mint tokens to exceed threshold
1
			let _ = Balances::deposit_creating(&1, 500_000_000);
1
			assert!(Balances::total_issuance() > threshold.unwrap());
			// When total issuance exceeds threshold, should use threshold value
1
			let round_above = crate::inflation::round_issuance_range::<Test>(Range {
1
				min: Perbill::from_percent(5),
1
				ideal: Perbill::from_percent(5),
1
				max: Perbill::from_percent(5),
1
			});
1
			assert_eq!(round_above.ideal, threshold.unwrap() / 20); // 5% of threshold
1
		});
1
}
#[test]
1
fn delegation_scheduled_requests_stepped_migration_completes_and_preserves_state() {
	use crate::AddGet;
1
	ExtBuilder::default().build().execute_with(|| {
		type Balance = crate::BalanceOf<Test>;
		#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug)]
		struct LegacyScheduledRequest<AccountId, Balance> {
			delegator: AccountId,
			when_executable: RoundIndex,
			action: DelegationAction<Balance>,
		}
		type OldScheduledRequests = BoundedVec<
			LegacyScheduledRequest<AccountId, Balance>,
			AddGet<
				<Test as crate::Config>::MaxTopDelegationsPerCandidate,
				<Test as crate::Config>::MaxBottomDelegationsPerCandidate,
			>,
		>;
		// Helper to build the raw storage key for the legacy single-map layout:
		//   prefix ++ Blake2_128(Encode(collator)) ++ Encode(collator)
3
		fn legacy_key_for(prefix: &[u8], collator: &AccountId) -> Vec<u8> {
3
			let mut full = prefix.to_vec();
3
			let encoded_collator = collator.encode();
3
			let hash = blake2_128(&encoded_collator);
3
			full.extend_from_slice(&hash);
3
			full.extend_from_slice(&encoded_collator);
3
			full
3
		}
1
		let prefix = storage_prefix(b"ParachainStaking", b"DelegationScheduledRequests");
		// Build some synthetic legacy data for a few collators.
1
		let scenarios: &[(AccountId, &[(AccountId, u32)])] = &[
1
			(1, &[(10, 2), (11, 1)]),
1
			(2, &[(20, 3)]),
1
			(3, &[(30, 1), (31, 1), (32, 1)]),
1
		];
1
		let mut expected_total_requests: u32 = 0;
1
		let mut expected_per_collator: Vec<(AccountId, u32)> = Vec::new();
4
		for (collator, delegators) in scenarios {
3
			let mut legacy_requests: OldScheduledRequests = BoundedVec::default();
3
			let mut distinct_delegators: u32 = 0;
9
			for (delegator, count) in *delegators {
6
				distinct_delegators = distinct_delegators.saturating_add(1);
6
				for _ in 0..*count {
9
					let amount: Balance = 1u32.into();
9
					let request = LegacyScheduledRequest {
9
						delegator: *delegator,
9
						when_executable: 1,
9
						action: DelegationAction::Revoke(amount),
9
					};
9
					let push_result = legacy_requests.try_push(request);
9
					assert!(
9
						push_result.is_ok(),
						"legacy_requests should not exceed its bound"
					);
9
					expected_total_requests = expected_total_requests.saturating_add(1);
				}
			}
3
			let key = legacy_key_for(&prefix, collator);
3
			let value = legacy_requests.encode();
3
			sp_io::storage::set(&key, &value);
3
			expected_per_collator.push((*collator, distinct_delegators));
		}
		// Drive the stepped migration until completion, checking that each step
		// finishes without error and that the cursor eventually reaches `None`.
1
		let mut cursor: Option<
1
			<MigrateDelegationScheduledRequestsToDoubleMap<Test> as SteppedMigration>::Cursor,
1
		> = None;
1
		for _ in 0..32 {
1
			let mut meter = WeightMeter::new();
1
			let next_cursor = MigrateDelegationScheduledRequestsToDoubleMap::<Test>::step(
1
				cursor.clone(),
1
				&mut meter,
			)
1
			.expect("migration step must not error");
1
			cursor = next_cursor;
1
			if cursor.is_none() {
1
				break;
			}
		}
1
		assert!(
1
			cursor.is_none(),
			"migration should complete in a bounded number of steps"
		);
		// Verify that the total number of scheduled requests is preserved.
1
		let mut migrated_total_requests: u32 = 0;
6
		for (_, _, requests) in DelegationScheduledRequests::<Test>::iter() {
6
			migrated_total_requests = migrated_total_requests.saturating_add(requests.len() as u32);
6
		}
1
		assert_eq!(
			migrated_total_requests, expected_total_requests,
			"migration must preserve total number of scheduled delegation requests"
		);
		// Verify that the per-collator counters reflect the number of delegator queues.
4
		for (collator, expected_queues) in expected_per_collator {
3
			let counters = DelegationScheduledRequestsPerCollator::<Test>::get(collator);
3
			assert_eq!(
				counters, expected_queues,
				"per-collator queue counter should match number of delegators with requests"
			);
		}
1
	});
1
}