Newer
Older
assert_ok!(Staking::validate(
RuntimeOrigin::signed(last_validator),
ValidatorPrefs::default()
));
#[test]
fn min_commission_works() {
ExtBuilder::default().build_and_execute(|| {
// account 11 controls the stash of itself.
assert_ok!(Staking::validate(
RuntimeOrigin::signed(11),
ValidatorPrefs { commission: Perbill::from_percent(5), blocked: false }
));
// event emitted should be correct
assert_eq!(
*staking_events().last().unwrap(),
Event::ValidatorPrefsSet {
stash: 11,
prefs: ValidatorPrefs { commission: Perbill::from_percent(5), blocked: false }
}
assert_ok!(Staking::set_staking_configs(
ConfigOp::Remove,
ConfigOp::Remove,
ConfigOp::Remove,
ConfigOp::Remove,
ConfigOp::Remove,
ConfigOp::Set(Perbill::from_percent(10)),
));
// can't make it less than 10 now
assert_noop!(
Staking::validate(
RuntimeOrigin::signed(11),
ValidatorPrefs { commission: Perbill::from_percent(5), blocked: false }
),
Error::<Test>::CommissionTooLow
);
// can only change to higher.
assert_ok!(Staking::validate(
RuntimeOrigin::signed(11),
ValidatorPrefs { commission: Perbill::from_percent(10), blocked: false }
));
assert_ok!(Staking::validate(
RuntimeOrigin::signed(11),
ValidatorPrefs { commission: Perbill::from_percent(15), blocked: false }
));
})
}
#[test]
fn change_of_max_nominations() {
use frame_election_provider_support::ElectionDataProvider;
ExtBuilder::default()
.add_staker(61, 61, 10, StakerStatus::Nominator(vec![1]))
.add_staker(71, 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![(101, 2), (71, 3), (61, 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![(101, 2), (71, 3), (61, 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![(101, 2), (71, 3), (61, 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), (61, 1)]
);
// 70 is still in storage..
assert!(Nominators::<Test>::contains_key(71));
// but its value cannot be decoded and default is returned.
assert!(Nominators::<Test>::get(71).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![(61, 1)]
assert!(Nominators::<Test>::contains_key(71));
assert!(Nominators::<Test>::contains_key(61));
assert!(Nominators::<Test>::get(71).is_none());
assert!(Nominators::<Test>::get(61).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![(71, 1), (61, 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(71), 101));
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(101), 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(11), 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(31), prefs(10)));
assert_ok!(Staking::validate(RuntimeOrigin::signed(21), 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![],
5267
5268
5269
5270
5271
5272
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
};
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)]));
5323
5324
5325
5326
5327
5328
5329
5330
5331
5332
5333
5334
5335
5336
5337
5338
5339
5340
5341
5342
5343
5344
5345
5346
5347
5348
5349
5350
5351
5352
5353
5354
5355
5356
5357
// 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
5389
5390
5391
5392
5393
5394
5395
5396
5397
5398
5399
5400
5401
5402
5403
5404
5405
5406
5407
5408
5409
5410
* 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
assert_eq!(ledger.slash(slash, 0, 0), slash);
// 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), c(7, 32)]);
assert_eq!(ledger.total, value_slashed + 32);
assert_eq!(LedgerSlashPerEra::get().0, 0);
assert_eq!(
LedgerSlashPerEra::get().1,
BTreeMap::from([(4, 0), (5, value_slashed), (6, 0), (7, 32)])
#[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), 1500, RewardDestination::Controller));
let claimed_rewards: BoundedVec<_, _> =
(start_reward_era..=last_reward_era).collect::<Vec<_>>().try_into().unwrap();
assert_eq!(
Staking::ledger(&3).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(3), 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(3), 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(3), 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() {
// 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,3),(5,5).
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 3.
assert_ok!(Staking::bond(RuntimeOrigin::signed(3), 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(&3).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(3), 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(3), 3, current_era - 1),
Error::<Test>::NotController
);
// new stakers can still bond
assert_ok!(Staking::bond(RuntimeOrigin::signed(5), 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(&5).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
#[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), 300, RewardDestination::Staked));
assert!(matches!(Staking::ledger(3), Some(_)));
Ankan
committed
// when staker unbonds
assert_ok!(Staking::unbond(RuntimeOrigin::signed(3), 20));
Ankan
committed
// 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(3),
Ankan
committed
Some(StakingLedger {
unlocking,
..
}) if unlocking==expected_unlocking));
// when staker unbonds at next era
start_active_era(11);
assert_ok!(Staking::unbond(RuntimeOrigin::signed(3), 50));
Ankan
committed
// 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(3),
Ankan
committed
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(3), 20), Error::<Test>::NoMoreChunks);
Ankan
committed
// 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(3), 20), Error::<Test>::NotController);
assert_noop!(Staking::rebond(RuntimeOrigin::signed(3), 100), Error::<Test>::NotController);
Ankan
committed
// reset the ledger corruption
MaxUnlockingChunks::set(2);
})
}
5677
5678
5679
5680
5681
5682
5683
5684
5685
5686
5687
5688
5689
5690
5691
5692
5693
5694
5695
5696
5697
5698
5699
5700
5701
5702
5703
5704
5705
5706
5707
5708
5709
5710
5711
5712
5713
5714
5715
5716
5717
5718
5719
5720
5721
5722
5723
5724
5725
5726
5727
5728
5729
5730
#[test]
fn cannot_set_unsupported_validator_count() {
ExtBuilder::default().build_and_execute(|| {
MaxWinners::set(50);
// set validator count works
assert_ok!(Staking::set_validator_count(RuntimeOrigin::root(), 30));
assert_ok!(Staking::set_validator_count(RuntimeOrigin::root(), 50));
// setting validator count above 100 does not work
assert_noop!(
Staking::set_validator_count(RuntimeOrigin::root(), 51),
Error::<Test>::TooManyValidators,
);
})
}
#[test]
fn increase_validator_count_errors() {
ExtBuilder::default().build_and_execute(|| {
MaxWinners::set(50);
assert_ok!(Staking::set_validator_count(RuntimeOrigin::root(), 40));
// increase works
assert_ok!(Staking::increase_validator_count(RuntimeOrigin::root(), 6));
assert_eq!(ValidatorCount::<Test>::get(), 46);
// errors
assert_noop!(
Staking::increase_validator_count(RuntimeOrigin::root(), 5),
Error::<Test>::TooManyValidators,
);
})
}
#[test]
fn scale_validator_count_errors() {
ExtBuilder::default().build_and_execute(|| {
MaxWinners::set(50);
assert_ok!(Staking::set_validator_count(RuntimeOrigin::root(), 20));
// scale value works
assert_ok!(Staking::scale_validator_count(
RuntimeOrigin::root(),
Percent::from_percent(200)
));
assert_eq!(ValidatorCount::<Test>::get(), 40);
// errors
assert_noop!(
Staking::scale_validator_count(RuntimeOrigin::root(), Percent::from_percent(126)),
Error::<Test>::TooManyValidators,
);
})
}
5732
5733
5734
5735
5736
5737
5738
5739
5740
5741
5742
5743
5744
5745
5746
5747
5748
5749
5750
5751
5752
5753
5754
5755
5756
5757
#[test]
fn set_min_commission_works_with_admin_origin() {
ExtBuilder::default().build_and_execute(|| {
// no minimum commission set initially
assert_eq!(MinCommission::<Test>::get(), Zero::zero());
// root can set min commission
assert_ok!(Staking::set_min_commission(RuntimeOrigin::root(), Perbill::from_percent(10)));
assert_eq!(MinCommission::<Test>::get(), Perbill::from_percent(10));
// Non privileged origin can not set min_commission
assert_noop!(
Staking::set_min_commission(RuntimeOrigin::signed(2), Perbill::from_percent(15)),
BadOrigin
);
// Admin Origin can set min commission
assert_ok!(Staking::set_min_commission(
RuntimeOrigin::signed(1),
Perbill::from_percent(15),
));
// setting commission below min_commission fails
assert_noop!(
Staking::validate(
RuntimeOrigin::signed(11),
ValidatorPrefs { commission: Perbill::from_percent(14), blocked: false }
),
Error::<Test>::CommissionTooLow
);
// setting commission >= min_commission works
assert_ok!(Staking::validate(
RuntimeOrigin::signed(11),
ValidatorPrefs { commission: Perbill::from_percent(15), blocked: false }
));
})
}
mod staking_interface {
use frame_support::storage::with_storage_layer;
use sp_staking::StakingInterface;
use super::*;
#[test]
fn force_unstake_with_slash_works() {
ExtBuilder::default().build_and_execute(|| {
// without slash
let _ = with_storage_layer::<(), _, _>(|| {
// bond an account, can unstake
assert_eq!(Staking::bonded(&11), Some(11));
assert_ok!(<Staking as StakingInterface>::force_unstake(11));
Err(DispatchError::from("revert"))
});
// bond again and add a slash, still can unstake.
assert_eq!(Staking::bonded(&11), Some(11));
add_slash(&11);
assert_ok!(<Staking as StakingInterface>::force_unstake(11));
});
}
#[test]
fn do_withdraw_unbonded_with_wrong_slash_spans_works_as_expected() {
ExtBuilder::default().build_and_execute(|| {
on_offence_now(
&[OffenceDetails {
offender: (11, Staking::eras_stakers(active_era(), 11)),
reporters: vec![],
}],
&[Perbill::from_percent(100)],
);
assert_eq!(Staking::bonded(&11), Some(11));
Staking::withdraw_unbonded(RuntimeOrigin::signed(11), 0),
Error::<Test>::IncorrectSlashingSpans
);
let num_slashing_spans = Staking::slashing_spans(&11).map_or(0, |s| s.iter().count());
assert_ok!(Staking::withdraw_unbonded(
RuntimeOrigin::signed(11),
num_slashing_spans as u32
));
});
}
5821
5822
5823
5824
5825
5826
5827
5828
5829
5830
5831
5832
5833
5834
5835
5836
5837
5838
5839
5840
5841
5842
5843
#[test]
fn status() {
ExtBuilder::default().build_and_execute(|| {
// stash of a validator is identified as a validator
assert_eq!(Staking::status(&11).unwrap(), StakerStatus::Validator);
// .. but not the controller.
assert!(Staking::status(&10).is_err());
// stash of nominator is identified as a nominator
assert_eq!(Staking::status(&101).unwrap(), StakerStatus::Nominator(vec![11, 21]));
// .. but not the controller.
assert!(Staking::status(&100).is_err());
// stash of chilled is identified as a chilled
assert_eq!(Staking::status(&41).unwrap(), StakerStatus::Idle);
// .. but not the controller.
assert!(Staking::status(&40).is_err());
// random other account.
assert!(Staking::status(&42).is_err());
})
}