Newer
Older
pub type CounterForValidators<T> = StorageValue<_, u32, ValueQuery>;
/// The maximum validator count before we stop allowing new validators to join.
///
/// When this value is not set, no limits are enforced.
#[pallet::storage]
pub type MaxValidatorsCount<T> = StorageValue<_, u32, OptionQuery>;
/// The map from nominator stash key to the set of stash keys of all validators to nominate.
/// When updating this storage item, you must also update the `CounterForNominators`.
#[pallet::storage]
#[pallet::getter(fn nominators)]
pub type Nominators<T: Config> =
StorageMap<_, Twox64Concat, T::AccountId, Nominations<T::AccountId>>;
/// A tracker to keep count of the number of items in the `Nominators` map.
#[pallet::storage]
pub type CounterForNominators<T> = StorageValue<_, u32, ValueQuery>;
/// The maximum nominator count before we stop allowing new validators to join.
///
/// When this value is not set, no limits are enforced.
#[pallet::storage]
pub type MaxNominatorsCount<T> = StorageValue<_, u32, OptionQuery>;
/// The current era index.
///
/// This is the latest planned era, depending on how the Session pallet queues the validator
/// set, it might be active or not.
#[pallet::storage]
#[pallet::getter(fn current_era)]
pub type CurrentEra<T> = StorageValue<_, EraIndex>;
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
/// The active era information, it holds index and start.
///
/// The active era is the era being currently rewarded. Validator set of this era must be
/// equal to [`SessionInterface::validators`].
#[pallet::storage]
#[pallet::getter(fn active_era)]
pub type ActiveEra<T> = StorageValue<_, ActiveEraInfo>;
/// The session index at which the era start for the last `HISTORY_DEPTH` eras.
///
/// Note: This tracks the starting session (i.e. session index when era start being active)
/// for the eras in `[CurrentEra - HISTORY_DEPTH, CurrentEra]`.
#[pallet::storage]
#[pallet::getter(fn eras_start_session_index)]
pub type ErasStartSessionIndex<T> = StorageMap<_, Twox64Concat, EraIndex, SessionIndex>;
/// Exposure of validator at era.
///
/// This is keyed first by the era index to allow bulk deletion and then the stash account.
///
/// Is it removed after `HISTORY_DEPTH` eras.
/// If stakers hasn't been set or has been removed then empty exposure is returned.
#[pallet::storage]
#[pallet::getter(fn eras_stakers)]
pub type ErasStakers<T: Config> = StorageDoubleMap<
_,
Twox64Concat,
EraIndex,
Twox64Concat,
T::AccountId,
Exposure<T::AccountId, BalanceOf<T>>,
ValueQuery,
>;
/// Clipped Exposure of validator at era.
///
/// This is similar to [`ErasStakers`] but number of nominators exposed is reduced to the
/// `T::MaxNominatorRewardedPerValidator` biggest stakers.
/// (Note: the field `total` and `own` of the exposure remains unchanged).
/// This is used to limit the i/o cost for the nominator payout.
///
/// This is keyed fist by the era index to allow bulk deletion and then the stash account.
///
/// Is it removed after `HISTORY_DEPTH` eras.
/// If stakers hasn't been set or has been removed then empty exposure is returned.
#[pallet::storage]
#[pallet::getter(fn eras_stakers_clipped)]
pub type ErasStakersClipped<T: Config> = StorageDoubleMap<
_,
Twox64Concat,
EraIndex,
Twox64Concat,
T::AccountId,
Exposure<T::AccountId, BalanceOf<T>>,
ValueQuery,
>;
/// Similar to `ErasStakers`, this holds the preferences of validators.
///
/// This is keyed first by the era index to allow bulk deletion and then the stash account.
///
/// Is it removed after `HISTORY_DEPTH` eras.
// If prefs hasn't been set or has been removed then 0 commission is returned.
#[pallet::storage]
#[pallet::getter(fn eras_validator_prefs)]
pub type ErasValidatorPrefs<T: Config> = StorageDoubleMap<
_,
Twox64Concat,
EraIndex,
Twox64Concat,
T::AccountId,
ValidatorPrefs,
ValueQuery,
>;
/// The total validator era payout for the last `HISTORY_DEPTH` eras.
///
/// Eras that haven't finished yet or has been removed doesn't have reward.
#[pallet::storage]
#[pallet::getter(fn eras_validator_reward)]
pub type ErasValidatorReward<T: Config> = StorageMap<_, Twox64Concat, EraIndex, BalanceOf<T>>;
/// Rewards for the last `HISTORY_DEPTH` eras.
/// If reward hasn't been set or has been removed then 0 reward is returned.
#[pallet::storage]
#[pallet::getter(fn eras_reward_points)]
pub type ErasRewardPoints<T: Config> =
StorageMap<_, Twox64Concat, EraIndex, EraRewardPoints<T::AccountId>, ValueQuery>;
/// The total amount staked for the last `HISTORY_DEPTH` eras.
/// If total hasn't been set or has been removed then 0 stake is returned.
#[pallet::storage]
#[pallet::getter(fn eras_total_stake)]
pub type ErasTotalStake<T: Config> =
StorageMap<_, Twox64Concat, EraIndex, BalanceOf<T>, ValueQuery>;
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
/// Mode of era forcing.
#[pallet::storage]
#[pallet::getter(fn force_era)]
pub type ForceEra<T> = StorageValue<_, Forcing, ValueQuery>;
/// The percentage of the slash that is distributed to reporters.
///
/// The rest of the slashed value is handled by the `Slash`.
#[pallet::storage]
#[pallet::getter(fn slash_reward_fraction)]
pub type SlashRewardFraction<T> = StorageValue<_, Perbill, ValueQuery>;
/// The amount of currency given to reporters of a slash event which was
/// canceled by extraordinary circumstances (e.g. governance).
#[pallet::storage]
#[pallet::getter(fn canceled_payout)]
pub type CanceledSlashPayout<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
/// All unapplied slashes that are queued for later.
#[pallet::storage]
pub type UnappliedSlashes<T: Config> = StorageMap<
_,
Twox64Concat,
EraIndex,
Vec<UnappliedSlash<T::AccountId, BalanceOf<T>>>,
ValueQuery,
>;
/// A mapping from still-bonded eras to the first session index of that era.
///
/// Must contains information for eras for the range:
/// `[active_era - bounding_duration; active_era]`
#[pallet::storage]
pub(crate) type BondedEras<T: Config> =
StorageValue<_, Vec<(EraIndex, SessionIndex)>, ValueQuery>;
/// All slashing events on validators, mapped by era to the highest slash proportion
/// and slash value of the era.
#[pallet::storage]
pub(crate) type ValidatorSlashInEra<T: Config> = StorageDoubleMap<
_,
Twox64Concat,
EraIndex,
Twox64Concat,
T::AccountId,
(Perbill, BalanceOf<T>),
>;
/// All slashing events on nominators, mapped by era to the highest slash value of the era.
#[pallet::storage]
pub(crate) type NominatorSlashInEra<T: Config> =
StorageDoubleMap<_, Twox64Concat, EraIndex, Twox64Concat, T::AccountId, BalanceOf<T>>;
/// Slashing spans for stash accounts.
#[pallet::storage]
pub(crate) type SlashingSpans<T: Config> =
StorageMap<_, Twox64Concat, T::AccountId, slashing::SlashingSpans>;
/// Records information about the maximum slash of a stash within a slashing span,
/// as well as how much reward has been paid out.
#[pallet::storage]
pub(crate) type SpanSlash<T: Config> = StorageMap<
_,
Twox64Concat,
(T::AccountId, slashing::SpanIndex),
slashing::SpanRecord<BalanceOf<T>>,
ValueQuery,
>;
/// The earliest era for which we have a pending, unapplied slash.
#[pallet::storage]
pub(crate) type EarliestUnappliedSlash<T> = StorageValue<_, EraIndex>;
/// The last planned session scheduled by the session pallet.
///
/// This is basically in sync with the call to [`pallet_session::SessionManager::new_session`].
#[pallet::storage]
#[pallet::getter(fn current_planned_session)]
pub type CurrentPlannedSession<T> = StorageValue<_, SessionIndex, ValueQuery>;
/// True if network has been upgraded to this version.
/// Storage version of the pallet.
///
/// This is set to v7.0.0 for new networks.
#[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,
pub validator_count: u32,
pub minimum_validator_count: u32,
pub invulnerables: Vec<T::AccountId>,
pub force_era: Forcing,
pub slash_reward_fraction: Perbill,
pub canceled_payout: BalanceOf<T>,
pub stakers: Vec<(T::AccountId, T::AccountId, BalanceOf<T>, StakerStatus<T::AccountId>)>,
pub min_nominator_bond: BalanceOf<T>,
pub min_validator_bond: BalanceOf<T>,
#[cfg(feature = "std")]
impl<T: Config> Default for GenesisConfig<T> {
fn default() -> Self {
GenesisConfig {
history_depth: 84u32,
validator_count: Default::default(),
minimum_validator_count: Default::default(),
invulnerables: Default::default(),
force_era: Default::default(),
slash_reward_fraction: Default::default(),
canceled_payout: Default::default(),
stakers: Default::default(),
min_nominator_bond: Default::default(),
min_validator_bond: Default::default(),
}
}
}
#[pallet::genesis_build]
impl<T: Config> GenesisBuild<T> for GenesisConfig<T> {
fn build(&self) {
HistoryDepth::<T>::put(self.history_depth);
ValidatorCount::<T>::put(self.validator_count);
MinimumValidatorCount::<T>::put(self.minimum_validator_count);
Invulnerables::<T>::put(&self.invulnerables);
ForceEra::<T>::put(self.force_era);
CanceledSlashPayout::<T>::put(self.canceled_payout);
SlashRewardFraction::<T>::put(self.slash_reward_fraction);
StorageVersion::<T>::put(Releases::V7_0_0);
MinNominatorBond::<T>::put(self.min_nominator_bond);
MinValidatorBond::<T>::put(self.min_validator_bond);
for &(ref stash, ref controller, balance, ref status) in &self.stakers {
assert!(
T::Currency::free_balance(&stash) >= balance,
"Stash does not have enough balance to bond."
);
let _ = <Pallet<T>>::bond(
T::Origin::from(Some(stash.clone()).into()),
T::Lookup::unlookup(controller.clone()),
balance,
RewardDestination::Staked,
);
let _ = match status {
StakerStatus::Validator => <Pallet<T>>::validate(
T::Origin::from(Some(controller.clone()).into()),
Default::default(),
),
StakerStatus::Nominator(votes) => <Pallet<T>>::nominate(
T::Origin::from(Some(controller.clone()).into()),
votes.iter().map(|l| T::Lookup::unlookup(l.clone())).collect(),
),
_ => Ok(()),
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
#[pallet::metadata(T::AccountId = "AccountId", BalanceOf<T> = "Balance")]
pub enum Event<T: Config> {
/// The era payout has been set; the first balance is the validator-payout; the second is
/// the remainder from the maximum amount of reward.
EraPayout(EraIndex, BalanceOf<T>, BalanceOf<T>),
/// The staker has been rewarded by this amount. \[stash, amount\]
Reward(T::AccountId, BalanceOf<T>),
/// One validator (and its nominators) has been slashed by the given amount.
Slash(T::AccountId, BalanceOf<T>),
/// An old slashing report from a prior era was discarded because it could
OldSlashingReportDiscarded(SessionIndex),
/// A new set of stakers was elected.
StakingElection,
/// An account has bonded this amount. \[stash, amount\]
///
/// NOTE: This event is only emitted when funds are bonded via a dispatchable. Notably,
/// it will not be emitted for staking rewards when they are added to stake.
Bonded(T::AccountId, BalanceOf<T>),
/// An account has unbonded this amount. \[stash, amount\]
Unbonded(T::AccountId, BalanceOf<T>),
/// An account has called `withdraw_unbonded` and removed unbonding chunks worth `Balance`
/// from the unlocking queue. \[stash, amount\]
Withdrawn(T::AccountId, BalanceOf<T>),
/// A nominator has been kicked from a validator. \[nominator, stash\]
Kicked(T::AccountId, T::AccountId),
thiolliere
committed
/// The election failed. No new era is planned.
StakingElectionFailed,
/// An account has stopped participating as either a validator or nominator.
/// \[stash\]
Chilled(T::AccountId),
#[pallet::error]
pub enum Error<T> {
/// Not a controller account.
NotController,
/// Not a stash account.
NotStash,
/// Stash is already bonded.
AlreadyBonded,
/// Controller is already paired.
AlreadyPaired,
/// Targets cannot be empty.
EmptyTargets,
/// Duplicate index.
DuplicateIndex,
/// Slash record index out of bounds.
InvalidSlashIndex,
/// Can not bond with value less than minimum required.
InsufficientBond,
/// Can not schedule more unlock chunks.
NoMoreChunks,
/// Can not rebond without unlocking chunks.
NoUnlockChunk,
/// Attempting to target a stash that still has funds.
FundedTarget,
/// Invalid era to reward.
InvalidEraToReward,
/// Invalid number of nominations.
InvalidNumberOfNominations,
/// Items are not sorted and unique.
NotSortedAndUnique,
/// Rewards for this era have already been claimed for this validator.
AlreadyClaimed,
/// Incorrect previous history depth input provided.
IncorrectHistoryDepth,
/// Incorrect number of slashing spans provided.
IncorrectSlashingSpans,
/// Internal state has become somehow corrupted and the operation cannot continue.
BadState,
/// Too many nomination targets supplied.
TooManyTargets,
/// A nomination target was supplied that was blocked or otherwise not a validator.
BadTarget,
/// The user has enough bond and thus cannot be chilled forcefully by an external person.
CannotChillOther,
/// There are too many nominators in the system. Governance needs to adjust the staking settings
/// to keep things safe for the runtime.
TooManyNominators,
/// There are too many validators in the system. Governance needs to adjust the staking settings
/// to keep things safe for the runtime.
TooManyValidators,
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_runtime_upgrade() -> Weight {
if StorageVersion::<T>::get() == Releases::V6_0_0 {
migrations::v7::migrate::<T>()
T::DbWeight::get().reads(1)
#[cfg(feature = "try-runtime")]
fn pre_upgrade() -> Result<(), &'static str> {
if StorageVersion::<T>::get() == Releases::V6_0_0 {
migrations::v7::pre_migrate::<T>()
} else {
Ok(())
}
}
fn on_initialize(_now: BlockNumberFor<T>) -> Weight {
// just return the weight of the on_finalize.
T::DbWeight::get().reads(1)
}
fn on_finalize(_n: BlockNumberFor<T>) {
// Set the start of the first era.
if let Some(mut active_era) = Self::active_era() {
if active_era.start.is_none() {
let now_as_millis_u64 = T::UnixTime::now().as_millis().saturated_into::<u64>();
active_era.start = Some(now_as_millis_u64);
// This write only ever happens once, we don't include it in the weight in general
ActiveEra::<T>::put(active_era);
// `on_finalize` weight is tracked in `on_initialize`
fn integrity_test() {
sp_std::if_std! {
sp_io::TestExternalities::new_empty().execute_with(||
assert!(
T::SlashDeferDuration::get() < T::BondingDuration::get() || T::BondingDuration::get() == 0,
"As per documentation, slash defer duration ({}) should be less than bonding duration ({}).",
T::SlashDeferDuration::get(),
T::BondingDuration::get(),
)
);
}
#[pallet::call]
impl<T: Config> Pallet<T> {
/// Take the origin account as a stash and lock up `value` of its balance. `controller` will
/// `value` must be more than the `minimum_balance` specified by `T::Currency`.
/// The dispatch origin for this call must be _Signed_ by the stash account.
///
/// Emits `Bonded`.
/// # <weight>
/// - Independent of the arguments. Moderate complexity.
/// - O(1).
/// - Three extra DB entries.
///
/// NOTE: Two of the storage writes (`Self::bonded`, `Self::payee`) are _never_ cleaned
/// unless the `origin` falls below _existential deposit_ and gets removed as dust.
/// # </weight>
#[pallet::weight(T::WeightInfo::bond())]
pub fn bond(
origin: OriginFor<T>,
controller: <T::Lookup as StaticLookup>::Source,
#[pallet::compact] value: BalanceOf<T>,
payee: RewardDestination<T::AccountId>,
) -> DispatchResult {
let stash = ensure_signed(origin)?;
if <Bonded<T>>::contains_key(&stash) {
Err(Error::<T>::AlreadyBonded)?
let controller = T::Lookup::lookup(controller)?;
if <Ledger<T>>::contains_key(&controller) {
Err(Error::<T>::AlreadyPaired)?
thiolliere
committed
// Reject a bond which is considered to be _dust_.
if value < T::Currency::minimum_balance() {
Err(Error::<T>::InsufficientBond)?
}
frame_system::Pallet::<T>::inc_consumers(&stash).map_err(|_| Error::<T>::BadState)?;
// You're auto-bonded forever, here. We might improve this by only bonding when
// you actually validate/nominate and remove once you unbond __everything__.
<Bonded<T>>::insert(&stash, &controller);
<Payee<T>>::insert(&stash, payee);
let current_era = CurrentEra::<T>::get().unwrap_or(0);
let history_depth = Self::history_depth();
let last_reward_era = current_era.saturating_sub(history_depth);
let stash_balance = T::Currency::free_balance(&stash);
let value = value.min(stash_balance);
Self::deposit_event(Event::<T>::Bonded(stash.clone(), value));
let item = StakingLedger {
stash,
total: value,
active: value,
unlocking: vec![],
claimed_rewards: (last_reward_era..current_era).collect(),
Self::update_ledger(&controller, &item);
/// Add some extra amount that have appeared in the stash `free_balance` into the balance up
/// The dispatch origin for this call must be _Signed_ by the stash, not the controller.
///
/// Use this if there are additional funds in your stash account that you wish to bond.
/// Unlike [`bond`](Self::bond) or [`unbond`](Self::unbond) this function does not impose any limitation
/// on the amount that can be added.
/// Emits `Bonded`.
///
/// # <weight>
/// - Independent of the arguments. Insignificant complexity.
/// - O(1).
/// # </weight>
#[pallet::weight(T::WeightInfo::bond_extra())]
pub fn bond_extra(
origin: OriginFor<T>,
#[pallet::compact] max_additional: BalanceOf<T>,
) -> DispatchResult {
let stash = ensure_signed(origin)?;
let controller = Self::bonded(&stash).ok_or(Error::<T>::NotStash)?;
let mut ledger = Self::ledger(&controller).ok_or(Error::<T>::NotController)?;
let stash_balance = T::Currency::free_balance(&stash);
if let Some(extra) = stash_balance.checked_sub(&ledger.total) {
let extra = extra.min(max_additional);
ledger.total += extra;
ledger.active += extra;
thiolliere
committed
// Last check: the new active amount of ledger must be more than ED.
ensure!(
ledger.active >= T::Currency::minimum_balance(),
Error::<T>::InsufficientBond
);
Self::deposit_event(Event::<T>::Bonded(stash, extra));
Self::update_ledger(&controller, &ledger);
/// Schedule a portion of the stash to be unlocked ready for transfer out after the bond
/// period ends. If this leaves an amount actively bonded less than
/// T::Currency::minimum_balance(), then it is increased to the full amount.
/// The dispatch origin for this call must be _Signed_ by the controller, not the stash.
///
/// Once the unlock period is done, you can call `withdraw_unbonded` to actually move
/// the funds out of management ready for transfer.
///
/// No more than a limited number of unlocking chunks (see `MAX_UNLOCKING_CHUNKS`)
/// can co-exists at the same time. In that case, [`Call::withdraw_unbonded`] need
/// to be called first to remove some of the chunks (if possible).
///
/// If a user encounters the `InsufficientBond` error when calling this extrinsic,
/// they should call `chill` first in order to free up their bonded funds.
///
/// Emits `Unbonded`.
///
#[pallet::weight(T::WeightInfo::unbond())]
pub fn unbond(
origin: OriginFor<T>,
#[pallet::compact] value: BalanceOf<T>,
) -> DispatchResult {
let controller = ensure_signed(origin)?;
let mut ledger = Self::ledger(&controller).ok_or(Error::<T>::NotController)?;
ensure!(ledger.unlocking.len() < MAX_UNLOCKING_CHUNKS, Error::<T>::NoMoreChunks,);
let mut value = value.min(ledger.active);
if !value.is_zero() {
ledger.active -= value;
// Avoid there being a dust balance left in the staking system.
if ledger.active < T::Currency::minimum_balance() {
value += ledger.active;
ledger.active = Zero::zero();
}
let min_active_bond = if Nominators::<T>::contains_key(&ledger.stash) {
MinNominatorBond::<T>::get()
} else if Validators::<T>::contains_key(&ledger.stash) {
MinValidatorBond::<T>::get()
} else {
Zero::zero()
};
// Make sure that the user maintains enough active bond for their role.
// If a user runs into this error, they should chill first.
ensure!(ledger.active >= min_active_bond, Error::<T>::InsufficientBond);
// Note: in case there is no current era it is fine to bond one era more.
let era = Self::current_era().unwrap_or(0) + T::BondingDuration::get();
ledger.unlocking.push(UnlockChunk { value, era });
Self::update_ledger(&controller, &ledger);
Self::deposit_event(Event::<T>::Unbonded(ledger.stash, value));
/// Remove any unlocked chunks from the `unlocking` queue from our management.
/// This essentially frees up that balance to be used by the stash account to do
/// whatever it wants.
/// The dispatch origin for this call must be _Signed_ by the controller.
/// Emits `Withdrawn`.
///
///
/// # <weight>
/// Complexity O(S) where S is the number of slashing spans to remove
/// NOTE: Weight annotation is the kill scenario, we refund otherwise.
/// # </weight>
#[pallet::weight(T::WeightInfo::withdraw_unbonded_kill(*num_slashing_spans))]
pub fn withdraw_unbonded(
origin: OriginFor<T>,
num_slashing_spans: u32,
) -> DispatchResultWithPostInfo {
let controller = ensure_signed(origin)?;
let mut ledger = Self::ledger(&controller).ok_or(Error::<T>::NotController)?;
let (stash, old_total) = (ledger.stash.clone(), ledger.total);
if let Some(current_era) = Self::current_era() {
ledger = ledger.consolidate_unlocked(current_era)
}
let post_info_weight =
if ledger.unlocking.is_empty() && ledger.active < T::Currency::minimum_balance() {
// This account must have called `unbond()` with some value that caused the active
// portion to fall below existential deposit + will have no more unlocking chunks
// left. We can now safely remove all staking-related information.
Self::kill_stash(&stash, num_slashing_spans)?;
// Remove the lock.
T::Currency::remove_lock(STAKING_ID, &stash);
// This is worst case scenario, so we use the full weight and return None
None
} else {
// This was the consequence of a partial unbond. just update the ledger and move on.
Self::update_ledger(&controller, &ledger);
// This is only an update, so we use less overall weight.
Some(T::WeightInfo::withdraw_unbonded_update(num_slashing_spans))
};
// `old_total` should never be less than the new total because
// `consolidate_unlocked` strictly subtracts balance.
if ledger.total < old_total {
// Already checked that this won't overflow by entry condition.
let value = old_total - ledger.total;
Self::deposit_event(Event::<T>::Withdrawn(stash, value));
/// Declare the desire to validate for the origin controller.
///
/// Effects will be felt at the beginning of the next era.
/// The dispatch origin for this call must be _Signed_ by the controller, not the stash.
#[pallet::weight(T::WeightInfo::validate())]
pub fn validate(origin: OriginFor<T>, prefs: ValidatorPrefs) -> DispatchResult {
let controller = ensure_signed(origin)?;
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);
/// Declare the desire to nominate `targets` for the origin controller.
///
/// Effects will be felt at the beginning of the next era.
/// The dispatch origin for this call must be _Signed_ by the controller, not the stash.
///
/// # <weight>
/// - The transaction's complexity is proportional to the size of `targets` (N)
/// which is capped at CompactAssignments::LIMIT (MAX_NOMINATIONS).
/// - Both the reads and writes follow a similar pattern.
/// # </weight>
#[pallet::weight(T::WeightInfo::nominate(targets.len() as u32))]
pub fn nominate(
origin: OriginFor<T>,
targets: Vec<<T::Lookup as StaticLookup>::Source>,
) -> DispatchResult {
let controller = ensure_signed(origin)?;
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);
let old = Nominators::<T>::get(stash).map_or_else(Vec::new, |x| x.targets);
let targets = targets
.into_iter()
.map(|t| T::Lookup::lookup(t).map_err(DispatchError::from))
.map(|n| {
n.and_then(|n| {
if old.contains(&n) || !Validators::<T>::get(&n).blocked {
Ok(n)
} else {
Err(Error::<T>::BadTarget.into())
}
})
})
.collect::<result::Result<Vec<T::AccountId>, _>>()?;
let nominations = Nominations {
targets,
thiolliere
committed
// Initial nominations are considered submitted at era 0. See `Nominations` doc
suppressed: false,
};
Self::do_remove_validator(stash);
Self::do_add_nominator(stash, nominations);
/// Declare no desire to either validate or nominate.
/// Effects will be felt at the beginning of the next era.
/// The dispatch origin for this call must be _Signed_ by the controller, not the stash.
///
/// # <weight>
/// - Independent of the arguments. Insignificant complexity.
/// - Contains one read.
/// - Writes are limited to the `origin` account key.
/// # </weight>
#[pallet::weight(T::WeightInfo::chill())]
pub fn chill(origin: OriginFor<T>) -> DispatchResult {
let controller = ensure_signed(origin)?;
let ledger = Self::ledger(&controller).ok_or(Error::<T>::NotController)?;
Self::chill_stash(&ledger.stash);
/// (Re-)set the payment target for a controller.
///
/// Effects will be felt at the beginning of the next era.
/// The dispatch origin for this call must be _Signed_ by the controller, not the stash.
///
/// # <weight>
/// - Independent of the arguments. Insignificant complexity.
/// - Contains a limited number of reads.
/// - Writes are limited to the `origin` account key.
/// - DB Weight:
/// - Read: Ledger
/// - Write: Payee
/// # </weight>
#[pallet::weight(T::WeightInfo::set_payee())]
pub fn set_payee(
origin: OriginFor<T>,
payee: RewardDestination<T::AccountId>,
) -> DispatchResult {
let controller = ensure_signed(origin)?;
let ledger = Self::ledger(&controller).ok_or(Error::<T>::NotController)?;
let stash = &ledger.stash;
<Payee<T>>::insert(stash, payee);
/// (Re-)set the controller of a stash.
///
/// Effects will be felt at the beginning of the next era.
///
/// The dispatch origin for this call must be _Signed_ by the stash, not the controller.
///
/// # <weight>
/// - Independent of the arguments. Insignificant complexity.
/// - Contains a limited number of reads.
/// - Writes are limited to the `origin` account key.
/// DB Weight:
/// - Read: Bonded, Ledger New Controller, Ledger Old Controller
/// - Write: Bonded, Ledger New Controller, Ledger Old Controller
/// # </weight>
#[pallet::weight(T::WeightInfo::set_controller())]
pub fn set_controller(
origin: OriginFor<T>,
controller: <T::Lookup as StaticLookup>::Source,
) -> DispatchResult {
let stash = ensure_signed(origin)?;
let old_controller = Self::bonded(&stash).ok_or(Error::<T>::NotStash)?;
let controller = T::Lookup::lookup(controller)?;
if <Ledger<T>>::contains_key(&controller) {
Err(Error::<T>::AlreadyPaired)?
}
if controller != old_controller {
<Bonded<T>>::insert(&stash, &controller);
if let Some(l) = <Ledger<T>>::take(&old_controller) {
<Ledger<T>>::insert(&controller, l);
}
/// Sets the ideal number of validators.
///
/// The dispatch origin must be Root.
///
/// # <weight>
/// Write: Validator Count
/// # </weight>
#[pallet::weight(T::WeightInfo::set_validator_count())]
pub fn set_validator_count(
origin: OriginFor<T>,
#[pallet::compact] new: u32,
) -> DispatchResult {
ValidatorCount::<T>::put(new);
Ok(())
/// Increments the ideal number of validators.
///
/// The dispatch origin must be Root.
///
/// # <weight>
/// Same as [`Self::set_validator_count`].
#[pallet::weight(T::WeightInfo::set_validator_count())]
pub fn increase_validator_count(
origin: OriginFor<T>,
#[pallet::compact] additional: u32,
) -> DispatchResult {
ValidatorCount::<T>::mutate(|n| *n += additional);
Ok(())
}
/// Scale up the ideal number of validators by a factor.
///
/// The dispatch origin must be Root.
///
/// # <weight>
/// Same as [`Self::set_validator_count`].
#[pallet::weight(T::WeightInfo::set_validator_count())]
pub fn scale_validator_count(origin: OriginFor<T>, factor: Percent) -> DispatchResult {
ValidatorCount::<T>::mutate(|n| *n += factor * *n);
Ok(())
/// Force there to be no new eras indefinitely.
///
/// The dispatch origin must be Root.
///
thiolliere
committed
/// # Warning
///
/// The election process starts multiple blocks before the end of the era.
/// Thus the election process may be ongoing when this is called. In this case the
/// election will continue until the next era is triggered.
///
/// # <weight>
/// - No arguments.
#[pallet::weight(T::WeightInfo::force_no_eras())]
pub fn force_no_eras(origin: OriginFor<T>) -> DispatchResult {
ForceEra::<T>::put(Forcing::ForceNone);
Ok(())
}
/// Force there to be a new era at the end of the next session. After this, it will be
/// reset to normal (non-forced) behaviour.
///
/// The dispatch origin must be Root.
///
thiolliere
committed
/// # Warning
///
/// The election process starts multiple blocks before the end of the era.
/// If this is called just before a new era is triggered, the election process may not
/// have enough blocks to get a result.
///
/// # <weight>
/// # </weight>
#[pallet::weight(T::WeightInfo::force_new_era())]
pub fn force_new_era(origin: OriginFor<T>) -> DispatchResult {
ForceEra::<T>::put(Forcing::ForceNew);
Ok(())
/// Set the validators who cannot be slashed (if any).
///
/// The dispatch origin must be Root.
///
/// # <weight>
/// - O(V)
/// - Write: Invulnerables
/// # </weight>
#[pallet::weight(T::WeightInfo::set_invulnerables(invulnerables.len() as u32))]
pub fn set_invulnerables(
origin: OriginFor<T>,
invulnerables: Vec<T::AccountId>,
) -> DispatchResult {
<Invulnerables<T>>::put(invulnerables);
/// Force a current staker to become completely unstaked, immediately.
///
/// The dispatch origin must be Root.
///
/// # <weight>
/// O(S) where S is the number of slashing spans to be removed
/// Reads: Bonded, Slashing Spans, Account, Locks
/// Writes: Bonded, Slashing Spans (if S > 0), Ledger, Payee, Validators, Nominators, Account, Locks
/// Writes Each: SpanSlash * S
/// # </weight>
#[pallet::weight(T::WeightInfo::force_unstake(*num_slashing_spans))]
pub fn force_unstake(
origin: OriginFor<T>,
stash: T::AccountId,
num_slashing_spans: u32,
) -> DispatchResult {
thiolliere
committed
// Remove all staking-related information.
Self::kill_stash(&stash, num_slashing_spans)?;
thiolliere
committed
// Remove the lock.
T::Currency::remove_lock(STAKING_ID, &stash);
/// Force there to be a new era at the end of sessions indefinitely.
///
/// The dispatch origin must be Root.
///
thiolliere
committed
/// # Warning
///
/// The election process starts multiple blocks before the end of the era.
/// If this is called just before a new era is triggered, the election process may not
/// have enough blocks to get a result.
///
#[pallet::weight(T::WeightInfo::force_new_era_always())]
pub fn force_new_era_always(origin: OriginFor<T>) -> DispatchResult {
ForceEra::<T>::put(Forcing::ForceAlways);
Ok(())
/// Cancel enactment of a deferred slash.
///