Newer
Older
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
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
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
>;
/// 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>;
/// 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 [`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 v6.0.0 for new networks.
#[pallet::storage]
pub(crate) type StorageVersion<T: Config> = StorageValue<_, Releases, ValueQuery>;
#[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>)>,
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
#[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(),
}
}
}
#[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::V6_0_0);
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),
#[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 balance.
InsufficientValue,
/// 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,
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_runtime_upgrade() -> Weight {
if StorageVersion::<T>::get() == Releases::V5_0_0 {
migrations::v6::migrate::<T>()
T::DbWeight::get().reads(1)
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.
/// DB Weight:
/// - Read: Bonded, Ledger, [Origin Account], Current Era, History Depth, Locks
/// - Write: Bonded, Payee, [Origin Account], Locks, Ledger
/// # </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)?
// reject a bond which is considered to be _dust_.
if value < T::Currency::minimum_balance() {
Err(Error::<T>::InsufficientValue)?
}
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
/// Use this if there are additional funds in your stash account that you wish to bond.
/// Unlike [`bond`] or [`unbond`] this function does not impose any limitation on the amount
/// that can be added.
/// The dispatch origin for this call must be _Signed_ by the stash, not the controller and
/// it can be only called when [`EraElectionStatus`] is `Closed`.
///
/// Emits `Bonded`.
///
/// # <weight>
/// - Independent of the arguments. Insignificant complexity.
/// - O(1).
/// - One DB entry.
/// ------------
/// DB Weight:
/// - Read: Era Election Status, Bonded, Ledger, [Origin Account], Locks
/// - Write: [Origin Account], Locks, Ledger
/// # </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;
// last check: the new active amount of ledger must be more than ED.
ensure!(ledger.active >= T::Currency::minimum_balance(), Error::<T>::InsufficientValue);
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.
/// 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).
///
/// The dispatch origin for this call must be _Signed_ by the controller, not the stash.
/// And, it can be only called when [`EraElectionStatus`] is `Closed`.
/// Emits `Unbonded`.
///
///
/// # <weight>
/// - Independent of the arguments. Limited but potentially exploitable complexity.
/// - Contains a limited number of reads.
/// - Each call (requires the remainder of the bonded balance to be above `minimum_balance`)
/// will cause a new entry to be inserted into a vector (`Ledger.unlocking`) kept in storage.
/// The only way to clean the aforementioned storage item is also user-controlled via
/// `withdraw_unbonded`.
/// - One DB entry.
/// - Read: EraElectionStatus, Ledger, CurrentEra, Locks, BalanceOf Stash,
/// - Write: Locks, Ledger, BalanceOf Stash,
/// </weight>
#[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,
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();
}
// 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, not the stash.
/// And, it can be only called when [`EraElectionStatus`] is `Closed`.
/// Emits `Withdrawn`.
///
///
/// # <weight>
/// - Could be dependent on the `origin` argument and how much `unlocking` chunks exist.
/// It implies `consolidate_unlocked` which loops over `Ledger.unlocking`, which is
/// indirectly user-controlled. See [`unbond`] for more detail.
/// - Contains a limited number of reads, yet the size of which could be large based on `ledger`.
/// - Writes are limited to the `origin` account key.
/// ---------------
/// Complexity O(S) where S is the number of slashing spans to remove
/// - Reads: EraElectionStatus, Ledger, Current Era, Locks, [Origin Account]
/// - Writes: [Origin Account], Locks, Ledger
/// Kill:
/// - Reads: EraElectionStatus, Ledger, Current Era, Bonded, Slashing Spans, [Origin
/// Account], Locks, BalanceOf stash
/// - Writes: Bonded, Slashing Spans (if S > 0), Ledger, Payee, Validators, Nominators,
/// [Origin Account], Locks, BalanceOf stash.
/// - Writes Each: SpanSlash * S
/// 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.
/// And, it can be only called when [`EraElectionStatus`] is `Closed`.
///
/// # <weight>
/// - Independent of the arguments. Insignificant complexity.
/// - Contains a limited number of reads.
/// - Writes are limited to the `origin` account key.
/// DB Weight:
/// - Read: Era Election Status, Ledger
/// - Write: Nominators, Validators
/// # </weight>
#[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)?;
let stash = &ledger.stash;
<Nominators<T>>::remove(stash);
<Validators<T>>::insert(stash, prefs);
/// Declare the desire to nominate `targets` for the origin controller.
///
/// Effects will be felt at the beginning of the next era. This can only be called when
/// [`EraElectionStatus`] is `Closed`.
/// The dispatch origin for this call must be _Signed_ by the controller, not the stash.
/// And, it can be only called when [`EraElectionStatus`] is `Closed`.
///
/// # <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.
/// where N is the number of targets
/// DB Weight:
/// - Reads: Era Election Status, Ledger, Current Era
/// - Writes: Validators, Nominators
/// # </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)?;
let stash = &ledger.stash;
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,
// initial nominations are considered submitted at era 0. See `Nominations` doc
submitted_in: Self::current_era().unwrap_or(0),
suppressed: false,
};
<Validators<T>>::remove(stash);
<Nominators<T>>::insert(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.
/// And, it can be only called when [`EraElectionStatus`] is `Closed`.
///
/// # <weight>
/// - Independent of the arguments. Insignificant complexity.
/// - Contains one read.
/// - Writes are limited to the `origin` account key.
/// DB Weight:
/// - Read: EraElectionStatus, Ledger
/// - Write: Validators, Nominators
/// # </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 [`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 [`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.
///
/// # <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.
///
/// # <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 {
Self::kill_stash(&stash, num_slashing_spans)?;
// 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.
///
#[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.
///
/// Can be called by the `T::SlashCancelOrigin`.
///
/// Parameters: era and indices of the slashes for that era to kill.
/// Complexity: O(U + S)
/// with U unapplied slashes weighted with U=1000
/// and S is the number of slash indices to be canceled.
/// - Read: Unapplied Slashes
/// - Write: Unapplied Slashes
#[pallet::weight(T::WeightInfo::cancel_deferred_slash(slash_indices.len() as u32))]
pub fn cancel_deferred_slash(
origin: OriginFor<T>,
era: EraIndex,
slash_indices: Vec<u32>,
) -> DispatchResult {
T::SlashCancelOrigin::ensure_origin(origin)?;
ensure!(!slash_indices.is_empty(), Error::<T>::EmptyTargets);
ensure!(is_sorted_and_unique(&slash_indices), Error::<T>::NotSortedAndUnique);
let mut unapplied = <Self as Store>::UnappliedSlashes::get(&era);
let last_item = slash_indices[slash_indices.len() - 1];
ensure!((last_item as usize) < unapplied.len(), Error::<T>::InvalidSlashIndex);
for (removed, index) in slash_indices.into_iter().enumerate() {
let index = (index as usize) - removed;
unapplied.remove(index);
}
<Self as Store>::UnappliedSlashes::insert(&era, &unapplied);
/// Pay out all the stakers behind a single validator for a single era.
///
/// - `validator_stash` is the stash account of the validator. Their nominators, up to
/// `T::MaxNominatorRewardedPerValidator`, will also receive their rewards.
/// - `era` may be any era between `[current_era - history_depth; current_era]`.
///
/// The origin of this call must be _Signed_. Any account can call this function, even if
/// it is not one of the stakers.
///
/// This can only be called when [`EraElectionStatus`] is `Closed`.
///
/// # <weight>
/// - Time complexity: at most O(MaxNominatorRewardedPerValidator).
/// - Contains a limited number of reads and writes.
/// -----------
/// N is the Number of payouts for the validator (including the validator)
/// Weight:
/// - Reward Destination Staked: O(N)
/// - Reward Destination Controller (Creating): O(N)
/// - Read: EraElectionStatus, CurrentEra, HistoryDepth, ErasValidatorReward,
/// ErasStakersClipped, ErasRewardPoints, ErasValidatorPrefs (8 items)
/// - Read Each: Bonded, Ledger, Payee, Locks, System Account (5 items)
/// - Write Each: System Account, Locks, Ledger (3 items)
///
/// NOTE: weights are assuming that payouts are made to alive stash account (Staked).
/// Paying even a dead controller is cheaper weight-wise. We don't do any refunds here.
#[pallet::weight(T::WeightInfo::payout_stakers_alive_staked(T::MaxNominatorRewardedPerValidator::get()))]
origin: OriginFor<T>,
validator_stash: T::AccountId,
era: EraIndex,
) -> DispatchResultWithPostInfo {
ensure_signed(origin)?;
Self::do_payout_stakers(validator_stash, era)
}
/// Rebond a portion of the stash scheduled to be unlocked.
///
/// The dispatch origin must be signed by the controller, and it can be only called when
/// [`EraElectionStatus`] is `Closed`.
///
/// - Time complexity: O(L), where L is unlocking chunks
/// - Bounded by `MAX_UNLOCKING_CHUNKS`.
/// - Storage changes: Can't increase storage, only decrease it.
/// ---------------
/// - DB Weight:
/// - Reads: EraElectionStatus, Ledger, Locks, [Origin Account]
/// - Writes: [Origin Account], Locks, Ledger
#[pallet::weight(T::WeightInfo::rebond(MAX_UNLOCKING_CHUNKS as u32))]
pub fn rebond(
origin: OriginFor<T>,
#[pallet::compact] value: BalanceOf<T>,
) -> DispatchResultWithPostInfo {
let controller = ensure_signed(origin)?;
let ledger = Self::ledger(&controller).ok_or(Error::<T>::NotController)?;
ensure!(!ledger.unlocking.is_empty(), Error::<T>::NoUnlockChunk);
// last check: the new active amount of ledger must be more than ED.
ensure!(ledger.active >= T::Currency::minimum_balance(), Error::<T>::InsufficientValue);
Self::deposit_event(Event::<T>::Bonded(ledger.stash.clone(), value));