mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-27 15:07:59 +00:00
Improve Staking Limits (#9193)
* only allow `chill_other` near threshold. * improve test * skip limit check for existing validators / nominators * add `ChillThreshold` * rename to `set` for consistent api * more tests * fix some line width
This commit is contained in:
@@ -601,26 +601,33 @@ benchmarks! {
|
||||
assert_eq!(targets.len() as u32, v);
|
||||
}
|
||||
|
||||
update_staking_limits {
|
||||
set_staking_limits {
|
||||
// This function always does the same thing... just write to 4 storage items.
|
||||
}: _(
|
||||
RawOrigin::Root,
|
||||
BalanceOf::<T>::max_value(),
|
||||
BalanceOf::<T>::max_value(),
|
||||
Some(u32::MAX),
|
||||
Some(u32::MAX)
|
||||
Some(u32::MAX),
|
||||
Some(Percent::max_value())
|
||||
) verify {
|
||||
assert_eq!(MinNominatorBond::<T>::get(), BalanceOf::<T>::max_value());
|
||||
assert_eq!(MinValidatorBond::<T>::get(), BalanceOf::<T>::max_value());
|
||||
assert_eq!(MaxNominatorsCount::<T>::get(), Some(u32::MAX));
|
||||
assert_eq!(MaxValidatorsCount::<T>::get(), Some(u32::MAX));
|
||||
assert_eq!(ChillThreshold::<T>::get(), Some(Percent::from_percent(100)));
|
||||
}
|
||||
|
||||
chill_other {
|
||||
let (_, controller) = create_stash_controller::<T>(USER_SEED, 100, Default::default())?;
|
||||
Staking::<T>::validate(RawOrigin::Signed(controller.clone()).into(), ValidatorPrefs::default())?;
|
||||
Staking::<T>::update_staking_limits(
|
||||
RawOrigin::Root.into(), BalanceOf::<T>::max_value(), BalanceOf::<T>::max_value(), None, None,
|
||||
Staking::<T>::set_staking_limits(
|
||||
RawOrigin::Root.into(),
|
||||
BalanceOf::<T>::max_value(),
|
||||
BalanceOf::<T>::max_value(),
|
||||
Some(0),
|
||||
Some(0),
|
||||
Some(Percent::from_percent(0))
|
||||
)?;
|
||||
let caller = whitelisted_caller();
|
||||
}: _(RawOrigin::Signed(caller), controller.clone())
|
||||
|
||||
@@ -1216,6 +1216,12 @@ pub mod pallet {
|
||||
#[pallet::storage]
|
||||
pub(crate) type StorageVersion<T: Config> = StorageValue<_, Releases, ValueQuery>;
|
||||
|
||||
/// The threshold for when users can start calling `chill_other` for other validators / nominators.
|
||||
/// The threshold is compared to the actual number of validators / nominators (`CountFor*`) in
|
||||
/// the system compared to the configured max (`Max*Count`).
|
||||
#[pallet::storage]
|
||||
pub(crate) type ChillThreshold<T: Config> = StorageValue<_, Percent, OptionQuery>;
|
||||
|
||||
#[pallet::genesis_config]
|
||||
pub struct GenesisConfig<T: Config> {
|
||||
pub history_depth: u32,
|
||||
@@ -1714,16 +1720,19 @@ pub mod pallet {
|
||||
pub fn validate(origin: OriginFor<T>, prefs: ValidatorPrefs) -> DispatchResult {
|
||||
let controller = ensure_signed(origin)?;
|
||||
|
||||
// If this error is reached, we need to adjust the `MinValidatorBond` and start calling `chill_other`.
|
||||
// Until then, we explicitly block new validators to protect the runtime.
|
||||
if let Some(max_validators) = MaxValidatorsCount::<T>::get() {
|
||||
ensure!(CounterForValidators::<T>::get() < max_validators, Error::<T>::TooManyValidators);
|
||||
}
|
||||
|
||||
let ledger = Self::ledger(&controller).ok_or(Error::<T>::NotController)?;
|
||||
ensure!(ledger.active >= MinValidatorBond::<T>::get(), Error::<T>::InsufficientBond);
|
||||
|
||||
let stash = &ledger.stash;
|
||||
|
||||
// Only check limits if they are not already a validator.
|
||||
if !Validators::<T>::contains_key(stash) {
|
||||
// If this error is reached, we need to adjust the `MinValidatorBond` and start calling `chill_other`.
|
||||
// Until then, we explicitly block new validators to protect the runtime.
|
||||
if let Some(max_validators) = MaxValidatorsCount::<T>::get() {
|
||||
ensure!(CounterForValidators::<T>::get() < max_validators, Error::<T>::TooManyValidators);
|
||||
}
|
||||
}
|
||||
|
||||
Self::do_remove_nominator(stash);
|
||||
Self::do_add_validator(stash, prefs);
|
||||
Ok(())
|
||||
@@ -1755,16 +1764,19 @@ pub mod pallet {
|
||||
) -> DispatchResult {
|
||||
let controller = ensure_signed(origin)?;
|
||||
|
||||
// If this error is reached, we need to adjust the `MinNominatorBond` and start calling `chill_other`.
|
||||
// Until then, we explicitly block new nominators to protect the runtime.
|
||||
if let Some(max_nominators) = MaxNominatorsCount::<T>::get() {
|
||||
ensure!(CounterForNominators::<T>::get() < max_nominators, Error::<T>::TooManyNominators);
|
||||
}
|
||||
|
||||
let ledger = Self::ledger(&controller).ok_or(Error::<T>::NotController)?;
|
||||
ensure!(ledger.active >= MinNominatorBond::<T>::get(), Error::<T>::InsufficientBond);
|
||||
|
||||
let stash = &ledger.stash;
|
||||
|
||||
// Only check limits if they are not already a nominator.
|
||||
if !Nominators::<T>::contains_key(stash) {
|
||||
// If this error is reached, we need to adjust the `MinNominatorBond` and start calling `chill_other`.
|
||||
// Until then, we explicitly block new nominators to protect the runtime.
|
||||
if let Some(max_nominators) = MaxNominatorsCount::<T>::get() {
|
||||
ensure!(CounterForNominators::<T>::get() < max_nominators, Error::<T>::TooManyNominators);
|
||||
}
|
||||
}
|
||||
|
||||
ensure!(!targets.is_empty(), Error::<T>::EmptyTargets);
|
||||
ensure!(targets.len() <= T::MAX_NOMINATIONS as usize, Error::<T>::TooManyTargets);
|
||||
|
||||
@@ -2266,31 +2278,42 @@ pub mod pallet {
|
||||
///
|
||||
/// NOTE: Existing nominators and validators will not be affected by this update.
|
||||
/// to kick people under the new limits, `chill_other` should be called.
|
||||
#[pallet::weight(T::WeightInfo::update_staking_limits())]
|
||||
pub fn update_staking_limits(
|
||||
#[pallet::weight(T::WeightInfo::set_staking_limits())]
|
||||
pub fn set_staking_limits(
|
||||
origin: OriginFor<T>,
|
||||
min_nominator_bond: BalanceOf<T>,
|
||||
min_validator_bond: BalanceOf<T>,
|
||||
max_nominator_count: Option<u32>,
|
||||
max_validator_count: Option<u32>,
|
||||
threshold: Option<Percent>,
|
||||
) -> DispatchResult {
|
||||
ensure_root(origin)?;
|
||||
MinNominatorBond::<T>::set(min_nominator_bond);
|
||||
MinValidatorBond::<T>::set(min_validator_bond);
|
||||
MaxNominatorsCount::<T>::set(max_nominator_count);
|
||||
MaxValidatorsCount::<T>::set(max_validator_count);
|
||||
ChillThreshold::<T>::set(threshold);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Declare a `controller` as having no desire to either validator or nominate.
|
||||
/// Declare a `controller` to stop participating as either a validator or nominator.
|
||||
///
|
||||
/// Effects will be felt at the beginning of the next era.
|
||||
///
|
||||
/// The dispatch origin for this call must be _Signed_, but can be called by anyone.
|
||||
///
|
||||
/// If the caller is the same as the controller being targeted, then no further checks
|
||||
/// are enforced. However, this call can also be made by an third party user who witnesses
|
||||
/// that this controller does not satisfy the minimum bond requirements to be in their role.
|
||||
/// If the caller is the same as the controller being targeted, then no further checks are
|
||||
/// enforced, and this function behaves just like `chill`.
|
||||
///
|
||||
/// If the caller is different than the controller being targeted, the following conditions
|
||||
/// must be met:
|
||||
/// * A `ChillThreshold` must be set and checked which defines how close to the max
|
||||
/// nominators or validators we must reach before users can start chilling one-another.
|
||||
/// * A `MaxNominatorCount` and `MaxValidatorCount` must be set which is used to determine
|
||||
/// how close we are to the threshold.
|
||||
/// * A `MinNominatorBond` and `MinValidatorBond` must be set and checked, which determines
|
||||
/// if this is a person that should be chilled because they have not met the threshold
|
||||
/// bond required.
|
||||
///
|
||||
/// This can be helpful if bond requirements are updated, and we need to remove old users
|
||||
/// who do not satisfy these requirements.
|
||||
@@ -2307,14 +2330,27 @@ pub mod pallet {
|
||||
let ledger = Self::ledger(&controller).ok_or(Error::<T>::NotController)?;
|
||||
let stash = ledger.stash;
|
||||
|
||||
// If the caller is not the controller, we want to check that the minimum bond
|
||||
// requirements are not satisfied, and thus we have reason to chill this user.
|
||||
// In order for one user to chill another user, the following conditions must be met:
|
||||
// * A `ChillThreshold` is set which defines how close to the max nominators or
|
||||
// validators we must reach before users can start chilling one-another.
|
||||
// * A `MaxNominatorCount` and `MaxValidatorCount` which is used to determine how close
|
||||
// we are to the threshold.
|
||||
// * A `MinNominatorBond` and `MinValidatorBond` which is the final condition checked to
|
||||
// determine this is a person that should be chilled because they have not met the
|
||||
// threshold bond required.
|
||||
//
|
||||
// Otherwise, if caller is the same as the controller, this is just like `chill`.
|
||||
if caller != controller {
|
||||
let threshold = ChillThreshold::<T>::get().ok_or(Error::<T>::CannotChillOther)?;
|
||||
let min_active_bond = if Nominators::<T>::contains_key(&stash) {
|
||||
let max_nominator_count = MaxNominatorsCount::<T>::get().ok_or(Error::<T>::CannotChillOther)?;
|
||||
let current_nominator_count = CounterForNominators::<T>::get();
|
||||
ensure!(threshold * max_nominator_count < current_nominator_count, Error::<T>::CannotChillOther);
|
||||
MinNominatorBond::<T>::get()
|
||||
} else if Validators::<T>::contains_key(&stash) {
|
||||
let max_validator_count = MaxValidatorsCount::<T>::get().ok_or(Error::<T>::CannotChillOther)?;
|
||||
let current_validator_count = CounterForValidators::<T>::get();
|
||||
ensure!(threshold * max_validator_count < current_validator_count, Error::<T>::CannotChillOther);
|
||||
MinValidatorBond::<T>::get()
|
||||
} else {
|
||||
Zero::zero()
|
||||
|
||||
@@ -4050,12 +4050,18 @@ mod election_data_provider {
|
||||
// 500 is not enough for any role
|
||||
assert_ok!(Staking::bond(Origin::signed(3), 4, 500, RewardDestination::Controller));
|
||||
assert_noop!(Staking::nominate(Origin::signed(4), vec![1]), Error::<Test>::InsufficientBond);
|
||||
assert_noop!(Staking::validate(Origin::signed(4), ValidatorPrefs::default()), Error::<Test>::InsufficientBond);
|
||||
assert_noop!(
|
||||
Staking::validate(Origin::signed(4), ValidatorPrefs::default()),
|
||||
Error::<Test>::InsufficientBond,
|
||||
);
|
||||
|
||||
// 1000 is enough for nominator
|
||||
assert_ok!(Staking::bond_extra(Origin::signed(3), 500));
|
||||
assert_ok!(Staking::nominate(Origin::signed(4), vec![1]));
|
||||
assert_noop!(Staking::validate(Origin::signed(4), ValidatorPrefs::default()), Error::<Test>::InsufficientBond);
|
||||
assert_noop!(
|
||||
Staking::validate(Origin::signed(4), ValidatorPrefs::default()),
|
||||
Error::<Test>::InsufficientBond,
|
||||
);
|
||||
|
||||
// 1500 is enough for validator
|
||||
assert_ok!(Staking::bond_extra(Origin::signed(3), 500));
|
||||
@@ -4083,24 +4089,80 @@ mod election_data_provider {
|
||||
.min_nominator_bond(1_000)
|
||||
.min_validator_bond(1_500)
|
||||
.build_and_execute(|| {
|
||||
// Nominator
|
||||
assert_ok!(Staking::bond(Origin::signed(1), 2, 1000, RewardDestination::Controller));
|
||||
assert_ok!(Staking::nominate(Origin::signed(2), vec![1]));
|
||||
for i in 0 .. 15 {
|
||||
let a = 4 * i;
|
||||
let b = 4 * i + 1;
|
||||
let c = 4 * i + 2;
|
||||
let d = 4 * i + 3;
|
||||
Balances::make_free_balance_be(&a, 100_000);
|
||||
Balances::make_free_balance_be(&b, 100_000);
|
||||
Balances::make_free_balance_be(&c, 100_000);
|
||||
Balances::make_free_balance_be(&d, 100_000);
|
||||
|
||||
// Validator
|
||||
assert_ok!(Staking::bond(Origin::signed(3), 4, 1500, RewardDestination::Controller));
|
||||
assert_ok!(Staking::validate(Origin::signed(4), ValidatorPrefs::default()));
|
||||
// Nominator
|
||||
assert_ok!(Staking::bond(Origin::signed(a), b, 1000, RewardDestination::Controller));
|
||||
assert_ok!(Staking::nominate(Origin::signed(b), vec![1]));
|
||||
|
||||
// Validator
|
||||
assert_ok!(Staking::bond(Origin::signed(c), d, 1500, RewardDestination::Controller));
|
||||
assert_ok!(Staking::validate(Origin::signed(d), ValidatorPrefs::default()));
|
||||
}
|
||||
|
||||
// To chill other users, we need to:
|
||||
// * Set a minimum bond amount
|
||||
// * Set a limit
|
||||
// * Set a threshold
|
||||
//
|
||||
// If any of these are missing, we do not have enough information to allow the
|
||||
// `chill_other` to succeed from one user to another.
|
||||
|
||||
// Can't chill these users
|
||||
assert_noop!(Staking::chill_other(Origin::signed(1), 2), Error::<Test>::CannotChillOther);
|
||||
assert_noop!(Staking::chill_other(Origin::signed(1), 4), Error::<Test>::CannotChillOther);
|
||||
assert_noop!(Staking::chill_other(Origin::signed(1337), 1), Error::<Test>::CannotChillOther);
|
||||
assert_noop!(Staking::chill_other(Origin::signed(1337), 3), Error::<Test>::CannotChillOther);
|
||||
|
||||
// Change the minimum bond
|
||||
assert_ok!(Staking::update_staking_limits(Origin::root(), 1_500, 2_000, None, None));
|
||||
// Change the minimum bond... but no limits.
|
||||
assert_ok!(Staking::set_staking_limits(Origin::root(), 1_500, 2_000, None, None, None));
|
||||
|
||||
// Users can now be chilled
|
||||
assert_ok!(Staking::chill_other(Origin::signed(1), 2));
|
||||
assert_ok!(Staking::chill_other(Origin::signed(1), 4));
|
||||
// Still can't chill these users
|
||||
assert_noop!(Staking::chill_other(Origin::signed(1337), 1), Error::<Test>::CannotChillOther);
|
||||
assert_noop!(Staking::chill_other(Origin::signed(1337), 3), Error::<Test>::CannotChillOther);
|
||||
|
||||
// Add limits, but no threshold
|
||||
assert_ok!(Staking::set_staking_limits(Origin::root(), 1_500, 2_000, Some(10), Some(10), None));
|
||||
|
||||
// Still can't chill these users
|
||||
assert_noop!(Staking::chill_other(Origin::signed(1337), 1), Error::<Test>::CannotChillOther);
|
||||
assert_noop!(Staking::chill_other(Origin::signed(1337), 3), Error::<Test>::CannotChillOther);
|
||||
|
||||
// Add threshold, but no limits
|
||||
assert_ok!(Staking::set_staking_limits(
|
||||
Origin::root(), 1_500, 2_000, None, None, Some(Percent::from_percent(0))
|
||||
));
|
||||
|
||||
// Still can't chill these users
|
||||
assert_noop!(Staking::chill_other(Origin::signed(1337), 1), Error::<Test>::CannotChillOther);
|
||||
assert_noop!(Staking::chill_other(Origin::signed(1337), 3), Error::<Test>::CannotChillOther);
|
||||
|
||||
// Add threshold and limits
|
||||
assert_ok!(Staking::set_staking_limits(
|
||||
Origin::root(), 1_500, 2_000, Some(10), Some(10), Some(Percent::from_percent(75))
|
||||
));
|
||||
|
||||
// 16 people total because tests start with 1 active one
|
||||
assert_eq!(CounterForNominators::<Test>::get(), 16);
|
||||
assert_eq!(CounterForValidators::<Test>::get(), 16);
|
||||
|
||||
// Users can now be chilled down to 7 people, so we try to remove 9 of them (starting with 16)
|
||||
for i in 6 .. 15 {
|
||||
let b = 4 * i + 1;
|
||||
let d = 4 * i + 3;
|
||||
assert_ok!(Staking::chill_other(Origin::signed(1337), b));
|
||||
assert_ok!(Staking::chill_other(Origin::signed(1337), d));
|
||||
}
|
||||
|
||||
// Cant go lower.
|
||||
assert_noop!(Staking::chill_other(Origin::signed(1337), 1), Error::<Test>::CannotChillOther);
|
||||
assert_noop!(Staking::chill_other(Origin::signed(1337), 3), Error::<Test>::CannotChillOther);
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4114,36 +4176,53 @@ mod election_data_provider {
|
||||
|
||||
// Change the maximums
|
||||
let max = 10;
|
||||
assert_ok!(Staking::update_staking_limits(Origin::root(), 10, 10, Some(max), Some(max)));
|
||||
assert_ok!(Staking::set_staking_limits(
|
||||
Origin::root(), 10, 10, Some(max), Some(max), Some(Percent::from_percent(0))
|
||||
));
|
||||
|
||||
// can create `max - validator_count` validators
|
||||
assert_ok!(testing_utils::create_validators::<Test>(max - validator_count, 100));
|
||||
let mut some_existing_validator = AccountId::default();
|
||||
for i in 0 .. max - validator_count {
|
||||
let (_, controller) = testing_utils::create_stash_controller::<Test>(
|
||||
i + 10_000_000, 100, RewardDestination::Controller,
|
||||
).unwrap();
|
||||
assert_ok!(Staking::validate(Origin::signed(controller), ValidatorPrefs::default()));
|
||||
some_existing_validator = controller;
|
||||
}
|
||||
|
||||
// but no more
|
||||
let (_, last_validator) = testing_utils::create_stash_controller::<Test>(
|
||||
1337, 100, RewardDestination::Controller,
|
||||
).unwrap();
|
||||
|
||||
assert_noop!(
|
||||
Staking::validate(Origin::signed(last_validator), ValidatorPrefs::default()),
|
||||
Error::<Test>::TooManyValidators,
|
||||
);
|
||||
|
||||
// same with nominators
|
||||
let mut some_existing_nominator = AccountId::default();
|
||||
for i in 0 .. max - nominator_count {
|
||||
let (_, controller) = testing_utils::create_stash_controller::<Test>(
|
||||
i + 10_000_000, 100, RewardDestination::Controller,
|
||||
i + 20_000_000, 100, RewardDestination::Controller,
|
||||
).unwrap();
|
||||
assert_ok!(Staking::nominate(Origin::signed(controller), vec![1]));
|
||||
some_existing_nominator = controller;
|
||||
}
|
||||
|
||||
// one more is too many
|
||||
let (_, last_nominator) = testing_utils::create_stash_controller::<Test>(
|
||||
20_000_000, 100, RewardDestination::Controller,
|
||||
30_000_000, 100, RewardDestination::Controller,
|
||||
).unwrap();
|
||||
assert_noop!(Staking::nominate(Origin::signed(last_nominator), vec![1]), Error::<Test>::TooManyNominators);
|
||||
|
||||
// Re-nominate works fine
|
||||
assert_ok!(Staking::nominate(Origin::signed(some_existing_nominator), vec![1]));
|
||||
// Re-validate works fine
|
||||
assert_ok!(Staking::validate(Origin::signed(some_existing_validator), ValidatorPrefs::default()));
|
||||
|
||||
// No problem when we set to `None` again
|
||||
assert_ok!(Staking::update_staking_limits(Origin::root(), 10, 10, None, None));
|
||||
assert_ok!(Staking::set_staking_limits(Origin::root(), 10, 10, None, None, None));
|
||||
assert_ok!(Staking::nominate(Origin::signed(last_nominator), vec![1]));
|
||||
assert_ok!(Staking::validate(Origin::signed(last_validator), ValidatorPrefs::default()));
|
||||
})
|
||||
|
||||
@@ -70,7 +70,7 @@ pub trait WeightInfo {
|
||||
fn new_era(v: u32, n: u32, ) -> Weight;
|
||||
fn get_npos_voters(v: u32, n: u32, s: u32, ) -> Weight;
|
||||
fn get_npos_targets(v: u32, ) -> Weight;
|
||||
fn update_staking_limits() -> Weight;
|
||||
fn set_staking_limits() -> Weight;
|
||||
fn chill_other() -> Weight;
|
||||
}
|
||||
|
||||
@@ -252,7 +252,7 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
|
||||
.saturating_add(T::DbWeight::get().reads(1 as Weight))
|
||||
.saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(v as Weight)))
|
||||
}
|
||||
fn update_staking_limits() -> Weight {
|
||||
fn set_staking_limits() -> Weight {
|
||||
(5_028_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().writes(4 as Weight))
|
||||
}
|
||||
@@ -440,7 +440,7 @@ impl WeightInfo for () {
|
||||
.saturating_add(RocksDbWeight::get().reads(1 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(v as Weight)))
|
||||
}
|
||||
fn update_staking_limits() -> Weight {
|
||||
fn set_staking_limits() -> Weight {
|
||||
(5_028_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().writes(4 as Weight))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user