Newer
Older
assert_ok!(Staking::validate(
ValidatorPrefs { commission: Perbill::from_percent(15), blocked: false }
));
})
}
#[test]
fn change_of_max_nominations() {
use frame_election_provider_support::ElectionDataProvider;
ExtBuilder::default()
.add_staker(60, 61, 10, StakerStatus::Nominator(vec![1]))
.add_staker(70, 71, 10, StakerStatus::Nominator(vec![1, 2, 3]))
.balance_factor(10)
.build_and_execute(|| {
// pre-condition
assert_eq!(MaxNominations::get(), 16);
assert_eq!(
Nominators::<Test>::iter()
.map(|(k, n)| (k, n.targets.len()))
.collect::<Vec<_>>(),
vec![(70, 3), (101, 2), (60, 1)]
);
// 3 validators and 3 nominators
assert_eq!(Staking::electing_voters(None).unwrap().len(), 3 + 3);
// abrupt change from 16 to 4, everyone should be fine.
MaxNominations::set(4);
assert_eq!(
Nominators::<Test>::iter()
.map(|(k, n)| (k, n.targets.len()))
.collect::<Vec<_>>(),
vec![(70, 3), (101, 2), (60, 1)]
);
assert_eq!(Staking::electing_voters(None).unwrap().len(), 3 + 3);
// abrupt change from 4 to 3, everyone should be fine.
MaxNominations::set(3);
assert_eq!(
Nominators::<Test>::iter()
.map(|(k, n)| (k, n.targets.len()))
.collect::<Vec<_>>(),
vec![(70, 3), (101, 2), (60, 1)]
);
assert_eq!(Staking::electing_voters(None).unwrap().len(), 3 + 3);
// abrupt change from 3 to 2, this should cause some nominators to be non-decodable, and
// thus non-existent unless if they update.
MaxNominations::set(2);
assert_eq!(
Nominators::<Test>::iter()
.map(|(k, n)| (k, n.targets.len()))
.collect::<Vec<_>>(),
vec![(101, 2), (60, 1)]
);
// 70 is still in storage..
assert!(Nominators::<Test>::contains_key(70));
// but its value cannot be decoded and default is returned.
assert!(Nominators::<Test>::get(70).is_none());
assert_eq!(Staking::electing_voters(None).unwrap().len(), 3 + 2);
assert!(Nominators::<Test>::contains_key(101));
// abrupt change from 2 to 1, this should cause some nominators to be non-decodable, and
// thus non-existent unless if they update.
MaxNominations::set(1);
assert_eq!(
Nominators::<Test>::iter()
.map(|(k, n)| (k, n.targets.len()))
.collect::<Vec<_>>(),
vec![(60, 1)]
);
assert!(Nominators::<Test>::contains_key(70));
assert!(Nominators::<Test>::contains_key(60));
assert!(Nominators::<Test>::get(70).is_none());
assert!(Nominators::<Test>::get(60).is_some());
assert_eq!(Staking::electing_voters(None).unwrap().len(), 3 + 1);
// now one of them can revive themselves by re-nominating to a proper value.
assert_ok!(Staking::nominate(RuntimeOrigin::signed(71), vec![1]));
assert_eq!(
Nominators::<Test>::iter()
.map(|(k, n)| (k, n.targets.len()))
.collect::<Vec<_>>(),
vec![(70, 1), (60, 1)]
);
// or they can be chilled by any account.
assert!(Nominators::<Test>::contains_key(101));
assert!(Nominators::<Test>::get(101).is_none());
assert_ok!(Staking::chill_other(RuntimeOrigin::signed(70), 100));
assert!(!Nominators::<Test>::contains_key(101));
assert!(Nominators::<Test>::get(101).is_none());
})
}
mod sorted_list_provider {
use super::*;
use frame_election_provider_support::SortedListProvider;
#[test]
fn re_nominate_does_not_change_counters_or_list() {
ExtBuilder::default().nominate(true).build_and_execute(|| {
// given
Kian Paimani
committed
let pre_insert_voter_count =
(Nominators::<Test>::count() + Validators::<Test>::count()) as u32;
assert_eq!(<Test as Config>::VoterList::count(), pre_insert_voter_count);
assert_eq!(
<Test as Config>::VoterList::iter().collect::<Vec<_>>(),
vec![11, 21, 31, 101]
);
// when account 101 renominates
assert_ok!(Staking::nominate(RuntimeOrigin::signed(100), vec![41]));
// then counts don't change
Kian Paimani
committed
assert_eq!(<Test as Config>::VoterList::count(), pre_insert_voter_count);
// and the list is the same
assert_eq!(
<Test as Config>::VoterList::iter().collect::<Vec<_>>(),
vec![11, 21, 31, 101]
);
});
}
#[test]
fn re_validate_does_not_change_counters_or_list() {
ExtBuilder::default().nominate(false).build_and_execute(|| {
// given
let pre_insert_voter_count =
(Nominators::<Test>::count() + Validators::<Test>::count()) as u32;
assert_eq!(<Test as Config>::VoterList::count(), pre_insert_voter_count);
assert_eq!(<Test as Config>::VoterList::iter().collect::<Vec<_>>(), vec![11, 21, 31]);
// when account 11 re-validates
assert_ok!(Staking::validate(RuntimeOrigin::signed(10), Default::default()));
Kian Paimani
committed
// then counts don't change
assert_eq!(<Test as Config>::VoterList::count(), pre_insert_voter_count);
// and the list is the same
Kian Paimani
committed
assert_eq!(<Test as Config>::VoterList::iter().collect::<Vec<_>>(), vec![11, 21, 31]);
});
}
}
#[test]
fn force_apply_min_commission_works() {
let prefs = |c| ValidatorPrefs { commission: Perbill::from_percent(c), blocked: false };
let validators = || Validators::<Test>::iter().collect::<Vec<_>>();
ExtBuilder::default().build_and_execute(|| {
assert_ok!(Staking::validate(RuntimeOrigin::signed(30), prefs(10)));
assert_ok!(Staking::validate(RuntimeOrigin::signed(20), prefs(5)));
// Given
assert_eq!(validators(), vec![(31, prefs(10)), (21, prefs(5)), (11, prefs(0))]);
MinCommission::<Test>::set(Perbill::from_percent(5));
// When applying to a commission greater than min
assert_ok!(Staking::force_apply_min_commission(RuntimeOrigin::signed(1), 31));
// Then the commission is not changed
assert_eq!(validators(), vec![(31, prefs(10)), (21, prefs(5)), (11, prefs(0))]);
// When applying to a commission that is equal to min
assert_ok!(Staking::force_apply_min_commission(RuntimeOrigin::signed(1), 21));
// Then the commission is not changed
assert_eq!(validators(), vec![(31, prefs(10)), (21, prefs(5)), (11, prefs(0))]);
// When applying to a commission that is less than the min
assert_ok!(Staking::force_apply_min_commission(RuntimeOrigin::signed(1), 11));
// Then the commission is bumped to the min
assert_eq!(validators(), vec![(31, prefs(10)), (21, prefs(5)), (11, prefs(5))]);
// When applying commission to a validator that doesn't exist then storage is not altered
assert_noop!(
Staking::force_apply_min_commission(RuntimeOrigin::signed(1), 420),
Error::<Test>::NotStash
);
});
}
#[test]
fn proportional_slash_stop_slashing_if_remaining_zero() {
let c = |era, value| UnlockChunk::<Balance> { era, value };
// Given
let mut ledger = StakingLedger::<Test> {
stash: 123,
total: 40,
active: 20,
// we have some chunks, but they are not affected.
unlocking: bounded_vec![c(1, 10), c(2, 10)],
};
assert_eq!(BondingDuration::get(), 3);
// should not slash more than the amount requested, by accidentally slashing the first chunk.
assert_eq!(ledger.slash(18, 1, 0), 18);
}
fn proportional_ledger_slash_works() {
let c = |era, value| UnlockChunk::<Balance> { era, value };
// Given
let mut ledger = StakingLedger::<Test> {
stash: 123,
total: 10,
active: 10,
unlocking: bounded_vec![],
5217
5218
5219
5220
5221
5222
5223
5224
5225
5226
5227
5228
5229
5230
5231
5232
5233
5234
5235
5236
5237
5238
5239
5240
5241
5242
5243
5244
5245
5246
5247
5248
5249
5250
5251
5252
5253
};
assert_eq!(BondingDuration::get(), 3);
// When we slash a ledger with no unlocking chunks
assert_eq!(ledger.slash(5, 1, 0), 5);
// Then
assert_eq!(ledger.total, 5);
assert_eq!(ledger.active, 5);
assert_eq!(LedgerSlashPerEra::get().0, 5);
assert_eq!(LedgerSlashPerEra::get().1, Default::default());
// When we slash a ledger with no unlocking chunks and the slash amount is greater then the
// total
assert_eq!(ledger.slash(11, 1, 0), 5);
// Then
assert_eq!(ledger.total, 0);
assert_eq!(ledger.active, 0);
assert_eq!(LedgerSlashPerEra::get().0, 0);
assert_eq!(LedgerSlashPerEra::get().1, Default::default());
// Given
ledger.unlocking = bounded_vec![c(4, 10), c(5, 10)];
ledger.total = 2 * 10;
ledger.active = 0;
// When all the chunks overlap with the slash eras
assert_eq!(ledger.slash(20, 0, 0), 20);
// Then
assert_eq!(ledger.unlocking, vec![]);
assert_eq!(ledger.total, 0);
assert_eq!(LedgerSlashPerEra::get().0, 0);
assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(4, 0), (5, 0)]));
// Given
ledger.unlocking = bounded_vec![c(4, 100), c(5, 100), c(6, 100), c(7, 100)];
ledger.total = 4 * 100;
ledger.active = 0;
// When the first 2 chunks don't overlap with the affected range of unlock eras.
assert_eq!(ledger.slash(140, 0, 3), 140);
// Then
assert_eq!(ledger.unlocking, vec![c(4, 100), c(5, 100), c(6, 30), c(7, 30)]);
assert_eq!(ledger.total, 4 * 100 - 140);
assert_eq!(LedgerSlashPerEra::get().0, 0);
assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(6, 30), (7, 30)]));
NingLin-P
committed
// Given
ledger.unlocking = bounded_vec![c(4, 100), c(5, 100), c(6, 100), c(7, 100)];
ledger.total = 4 * 100;
ledger.active = 0;
// When the first 2 chunks don't overlap with the affected range of unlock eras.
assert_eq!(ledger.slash(15, 0, 3), 15);
// Then
assert_eq!(ledger.unlocking, vec![c(4, 100), c(5, 100), c(6, 100 - 8), c(7, 100 - 7)]);
assert_eq!(ledger.total, 4 * 100 - 15);
assert_eq!(LedgerSlashPerEra::get().0, 0);
assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(6, 92), (7, 93)]));
5273
5274
5275
5276
5277
5278
5279
5280
5281
5282
5283
5284
5285
5286
5287
5288
5289
5290
5291
5292
5293
5294
5295
5296
5297
5298
5299
5300
5301
5302
5303
5304
5305
5306
5307
// Given
ledger.unlocking = bounded_vec![c(4, 40), c(5, 100), c(6, 10), c(7, 250)];
ledger.active = 500;
// 900
ledger.total = 40 + 10 + 100 + 250 + 500;
// When we have a partial slash that touches all chunks
assert_eq!(ledger.slash(900 / 2, 0, 0), 450);
// Then
assert_eq!(ledger.active, 500 / 2);
assert_eq!(ledger.unlocking, vec![c(4, 40 / 2), c(5, 100 / 2), c(6, 10 / 2), c(7, 250 / 2)]);
assert_eq!(ledger.total, 900 / 2);
assert_eq!(LedgerSlashPerEra::get().0, 500 / 2);
assert_eq!(
LedgerSlashPerEra::get().1,
BTreeMap::from([(4, 40 / 2), (5, 100 / 2), (6, 10 / 2), (7, 250 / 2)])
);
// slash 1/4th with not chunk.
ledger.unlocking = bounded_vec![];
ledger.active = 500;
ledger.total = 500;
// When we have a partial slash that touches all chunks
assert_eq!(ledger.slash(500 / 4, 0, 0), 500 / 4);
// Then
assert_eq!(ledger.active, 3 * 500 / 4);
assert_eq!(ledger.unlocking, vec![]);
assert_eq!(ledger.total, ledger.active);
assert_eq!(LedgerSlashPerEra::get().0, 3 * 500 / 4);
assert_eq!(LedgerSlashPerEra::get().1, Default::default());
// Given we have the same as above,
ledger.unlocking = bounded_vec![c(4, 40), c(5, 100), c(6, 10), c(7, 250)];
ledger.active = 500;
ledger.total = 40 + 10 + 100 + 250 + 500; // 900
assert_eq!(ledger.total, 900);
// When we have a higher min balance
assert_eq!(
ledger.slash(
900 / 2,
25, /* min balance - chunks with era 0 & 2 will be slashed to <=25, causing it to
* get swept */
0
),
);
assert_eq!(ledger.active, 500 / 2);
// the last chunk was not slashed 50% like all the rest, because some other earlier chunks got
// dusted.
assert_eq!(ledger.unlocking, vec![c(5, 100 / 2), c(7, 150)]);
assert_eq!(ledger.total, 900 / 2);
assert_eq!(LedgerSlashPerEra::get().0, 500 / 2);
assert_eq!(
LedgerSlashPerEra::get().1,
BTreeMap::from([(4, 0), (5, 100 / 2), (6, 0), (7, 150)])
);
// Given
// slash order --------------------NA--------2----------0----------1----
ledger.unlocking = bounded_vec![c(4, 40), c(5, 100), c(6, 10), c(7, 250)];
ledger.active = 500;
ledger.total = 40 + 10 + 100 + 250 + 500; // 900
assert_eq!(
ledger.slash(
500 + 10 + 250 + 100 / 2, // active + era 6 + era 7 + era 5 / 2
0,
3 /* slash era 6 first, so the affected parts are era 6, era 7 and
5339
5340
5341
5342
5343
5344
5345
5346
5347
5348
5349
5350
5351
5352
5353
5354
5355
5356
5357
5358
5359
5360
* ledge.active. This will cause the affected to go to zero, and then we will
* start slashing older chunks */
),
500 + 250 + 10 + 100 / 2
);
// Then
assert_eq!(ledger.active, 0);
assert_eq!(ledger.unlocking, vec![c(4, 40), c(5, 100 / 2)]);
assert_eq!(ledger.total, 90);
assert_eq!(LedgerSlashPerEra::get().0, 0);
assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(5, 100 / 2), (6, 0), (7, 0)]));
// Given
// iteration order------------------NA---------2----------0----------1----
ledger.unlocking = bounded_vec![c(4, 100), c(5, 100), c(6, 100), c(7, 100)];
ledger.active = 100;
ledger.total = 5 * 100;
// When
assert_eq!(
ledger.slash(
351, // active + era 6 + era 7 + era 5 / 2 + 1
50, // min balance - everything slashed below 50 will get dusted
3 /* slash era 3+3 first, so the affected parts are era 6, era 7 and
* ledge.active. This will cause the affected to go to zero, and then we will
* start slashing older chunks */
),
400
);
// Then
assert_eq!(ledger.active, 0);
assert_eq!(ledger.unlocking, vec![c(4, 100)]);
assert_eq!(ledger.total, 100);
assert_eq!(LedgerSlashPerEra::get().0, 0);
assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(5, 0), (6, 0), (7, 0)]));
// Tests for saturating arithmetic
// Given
let slash = u64::MAX as Balance * 2;
// The value of the other parts of ledger that will get slashed
let value = slash - (10 * 4);
ledger.active = 10;
ledger.unlocking = bounded_vec![c(4, 10), c(5, 10), c(6, 10), c(7, value)];
ledger.total = value + 40;
// When
let slash_amount = ledger.slash(slash, 0, 0);
assert_eq_error_rate!(slash_amount, slash, 5);
// Then
assert_eq!(ledger.active, 0); // slash of 9
assert_eq!(ledger.unlocking, vec![]);
assert_eq!(ledger.total, 0);
assert_eq!(LedgerSlashPerEra::get().0, 0);
assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(4, 0), (5, 0), (6, 0), (7, 0)]));
// Given
NingLin-P
committed
use sp_runtime::PerThing as _;
let slash = u64::MAX as Balance * 2;
let value = u64::MAX as Balance * 2;
let unit = 100;
// slash * value that will saturate
assert!(slash.checked_mul(value).is_none());
// but slash * unit won't.
assert!(slash.checked_mul(unit).is_some());
ledger.unlocking = bounded_vec![c(4, unit), c(5, value), c(6, unit), c(7, unit)];
//--------------------------------------note value^^^
ledger.active = unit;
ledger.total = unit * 4 + value;
// When
NingLin-P
committed
assert_eq!(ledger.slash(slash, 0, 0), slash - 5);
// Then
// The amount slashed out of `unit`
let affected_balance = value + unit * 4;
NingLin-P
committed
let ratio =
Perquintill::from_rational_with_rounding(slash, affected_balance, Rounding::Up).unwrap();
// `unit` after the slash is applied
let unit_slashed = {
NingLin-P
committed
let unit_slash = ratio.mul_ceil(unit);
unit - unit_slash
};
let value_slashed = {
NingLin-P
committed
let value_slash = ratio.mul_ceil(value);
value - value_slash
};
assert_eq!(ledger.active, unit_slashed);
assert_eq!(ledger.unlocking, vec![c(5, value_slashed)]);
assert_eq!(ledger.total, value_slashed);
assert_eq!(LedgerSlashPerEra::get().0, 0);
assert_eq!(
LedgerSlashPerEra::get().1,
BTreeMap::from([(4, 0), (5, value_slashed), (6, 0), (7, 0)])
);
}
5432
5433
5434
5435
5436
5437
5438
5439
5440
5441
5442
5443
5444
5445
5446
5447
5448
5449
5450
5451
5452
5453
5454
5455
5456
5457
5458
5459
5460
5461
5462
5463
5464
5465
5466
5467
5468
5469
5470
5471
5472
5473
5474
5475
5476
5477
5478
5479
5480
5481
5482
5483
5484
5485
5486
5487
5488
5489
5490
5491
5492
5493
5494
5495
5496
5497
#[test]
fn pre_bonding_era_cannot_be_claimed() {
// Verifies initial conditions of mock
ExtBuilder::default().nominate(false).build_and_execute(|| {
let history_depth = HistoryDepth::get();
// jump to some era above history_depth
let mut current_era = history_depth + 10;
let last_reward_era = current_era - 1;
let start_reward_era = current_era - history_depth;
// put some money in stash=3 and controller=4.
for i in 3..5 {
let _ = Balances::make_free_balance_be(&i, 2000);
}
mock::start_active_era(current_era);
// add a new candidate for being a validator. account 3 controlled by 4.
assert_ok!(Staking::bond(RuntimeOrigin::signed(3), 4, 1500, RewardDestination::Controller));
let claimed_rewards: BoundedVec<_, _> =
(start_reward_era..=last_reward_era).collect::<Vec<_>>().try_into().unwrap();
assert_eq!(
Staking::ledger(&4).unwrap(),
StakingLedger {
stash: 3,
total: 1500,
active: 1500,
unlocking: Default::default(),
claimed_rewards,
}
);
// start next era
current_era = current_era + 1;
mock::start_active_era(current_era);
// claiming reward for last era in which validator was active works
assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(4), 3, current_era - 1));
// consumed weight for all payout_stakers dispatches that fail
let err_weight = <Test as Config>::WeightInfo::payout_stakers_alive_staked(0);
// cannot claim rewards for an era before bonding occured as it is
// already marked as claimed.
assert_noop!(
Staking::payout_stakers(RuntimeOrigin::signed(4), 3, current_era - 2),
Error::<Test>::AlreadyClaimed.with_weight(err_weight)
);
// decoding will fail now since Staking Ledger is in corrupt state
HistoryDepth::set(history_depth - 1);
assert_eq!(Staking::ledger(&4), None);
// make sure stakers still cannot claim rewards that they are not meant to
assert_noop!(
Staking::payout_stakers(RuntimeOrigin::signed(4), 3, current_era - 2),
Error::<Test>::NotController
);
// fix the corrupted state for post conditions check
HistoryDepth::set(history_depth);
});
}
#[test]
Ankan
committed
fn reducing_history_depth_abrupt() {
5499
5500
5501
5502
5503
5504
5505
5506
5507
5508
5509
5510
5511
5512
5513
5514
5515
5516
5517
5518
5519
5520
5521
5522
5523
5524
5525
5526
5527
5528
5529
5530
5531
5532
5533
5534
5535
5536
5537
5538
5539
5540
5541
5542
5543
5544
5545
5546
5547
5548
5549
5550
5551
5552
5553
5554
5555
5556
5557
5558
5559
5560
5561
5562
5563
5564
5565
5566
5567
5568
5569
5570
5571
5572
5573
5574
// Verifies initial conditions of mock
ExtBuilder::default().nominate(false).build_and_execute(|| {
let original_history_depth = HistoryDepth::get();
let mut current_era = original_history_depth + 10;
let last_reward_era = current_era - 1;
let start_reward_era = current_era - original_history_depth;
// put some money in (stash, controller)=(3,4),(5,6).
for i in 3..7 {
let _ = Balances::make_free_balance_be(&i, 2000);
}
// start current era
mock::start_active_era(current_era);
// add a new candidate for being a staker. account 3 controlled by 4.
assert_ok!(Staking::bond(RuntimeOrigin::signed(3), 4, 1500, RewardDestination::Controller));
// all previous era before the bonding action should be marked as
// claimed.
let claimed_rewards: BoundedVec<_, _> =
(start_reward_era..=last_reward_era).collect::<Vec<_>>().try_into().unwrap();
assert_eq!(
Staking::ledger(&4).unwrap(),
StakingLedger {
stash: 3,
total: 1500,
active: 1500,
unlocking: Default::default(),
claimed_rewards,
}
);
// next era
current_era = current_era + 1;
mock::start_active_era(current_era);
// claiming reward for last era in which validator was active works
assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(4), 3, current_era - 1));
// next era
current_era = current_era + 1;
mock::start_active_era(current_era);
// history_depth reduced without migration
let history_depth = original_history_depth - 1;
HistoryDepth::set(history_depth);
// claiming reward does not work anymore
assert_noop!(
Staking::payout_stakers(RuntimeOrigin::signed(4), 3, current_era - 1),
Error::<Test>::NotController
);
// new stakers can still bond
assert_ok!(Staking::bond(RuntimeOrigin::signed(5), 6, 1200, RewardDestination::Controller));
// new staking ledgers created will be bounded by the current history depth
let last_reward_era = current_era - 1;
let start_reward_era = current_era - history_depth;
let claimed_rewards: BoundedVec<_, _> =
(start_reward_era..=last_reward_era).collect::<Vec<_>>().try_into().unwrap();
assert_eq!(
Staking::ledger(&6).unwrap(),
StakingLedger {
stash: 5,
total: 1200,
active: 1200,
unlocking: Default::default(),
claimed_rewards,
}
);
// fix the corrupted state for post conditions check
HistoryDepth::set(original_history_depth);
});
}
Ankan
committed
5575
5576
5577
5578
5579
5580
5581
5582
5583
5584
5585
5586
5587
5588
5589
5590
5591
5592
5593
5594
5595
5596
5597
5598
5599
5600
5601
5602
5603
5604
5605
5606
5607
5608
5609
5610
5611
5612
5613
5614
5615
5616
5617
5618
5619
5620
5621
5622
5623
5624
5625
5626
#[test]
fn reducing_max_unlocking_chunks_abrupt() {
// Concern is on validators only
// By Default 11, 10 are stash and ctrl and 21,20
ExtBuilder::default().build_and_execute(|| {
// given a staker at era=10 and MaxUnlockChunks set to 2
MaxUnlockingChunks::set(2);
start_active_era(10);
assert_ok!(Staking::bond(RuntimeOrigin::signed(3), 4, 300, RewardDestination::Staked));
assert!(matches!(Staking::ledger(4), Some(_)));
// when staker unbonds
assert_ok!(Staking::unbond(RuntimeOrigin::signed(4), 20));
// then an unlocking chunk is added at `current_era + bonding_duration`
// => 10 + 3 = 13
let expected_unlocking: BoundedVec<UnlockChunk<Balance>, MaxUnlockingChunks> =
bounded_vec![UnlockChunk { value: 20 as Balance, era: 13 as EraIndex }];
assert!(matches!(Staking::ledger(4),
Some(StakingLedger {
unlocking,
..
}) if unlocking==expected_unlocking));
// when staker unbonds at next era
start_active_era(11);
assert_ok!(Staking::unbond(RuntimeOrigin::signed(4), 50));
// then another unlock chunk is added
let expected_unlocking: BoundedVec<UnlockChunk<Balance>, MaxUnlockingChunks> =
bounded_vec![UnlockChunk { value: 20, era: 13 }, UnlockChunk { value: 50, era: 14 }];
assert!(matches!(Staking::ledger(4),
Some(StakingLedger {
unlocking,
..
}) if unlocking==expected_unlocking));
// when staker unbonds further
start_active_era(12);
// then further unbonding not possible
assert_noop!(Staking::unbond(RuntimeOrigin::signed(4), 20), Error::<Test>::NoMoreChunks);
// when max unlocking chunks is reduced abruptly to a low value
MaxUnlockingChunks::set(1);
// then unbond, rebond ops are blocked with ledger in corrupt state
assert_noop!(Staking::unbond(RuntimeOrigin::signed(4), 20), Error::<Test>::NotController);
assert_noop!(Staking::rebond(RuntimeOrigin::signed(4), 100), Error::<Test>::NotController);
// reset the ledger corruption
MaxUnlockingChunks::set(2);
})
}