Newer
Older
map hasher(twox_64_concat) (T::AccountId, slashing::SpanIndex)
/// The earliest era for which we have a pending, unapplied slash.
EarliestUnappliedSlash: Option<EraIndex>;
/// Snapshot of validators at the beginning of the current election window. This should only
/// have a value when [`EraElectionStatus`] == `ElectionStatus::Open(_)`.
pub SnapshotValidators get(fn snapshot_validators): Option<Vec<T::AccountId>>;
/// Snapshot of nominators at the beginning of the current election window. This should only
/// have a value when [`EraElectionStatus`] == `ElectionStatus::Open(_)`.
pub SnapshotNominators get(fn snapshot_nominators): Option<Vec<T::AccountId>>;
/// The next validator set. At the end of an era, if this is available (potentially from the
/// result of an offchain worker), it is immediately used. Otherwise, the on-chain election
/// is executed.
pub QueuedElected get(fn queued_elected): Option<ElectionResult<T::AccountId, BalanceOf<T>>>;
/// The score of the current [`QueuedElected`].
pub QueuedScore get(fn queued_score): Option<PhragmenScore>;
/// Flag to control the execution of the offchain election. When `Open(_)`, we accept
/// solutions to be submitted.
pub EraElectionStatus get(fn era_election_status): ElectionStatus<T::BlockNumber>;
/// True if the current **planned** session is final. Note that this does not take era
/// forcing into account.
pub IsCurrentSessionFinal get(fn is_current_session_final): bool = false;
/// True if network has been upgraded to this version.
/// This is set to v3.0.0 for new networks.
StorageVersion build(|_: &GenesisConfig<T>| Releases::V3_0_0): Releases;
/// The era where we migrated from Lazy Payouts to Simple Payouts
MigrateEra: Option<EraIndex>;
config(stakers):
Vec<(T::AccountId, T::AccountId, BalanceOf<T>, StakerStatus<T::AccountId>)>;
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
build(|config: &GenesisConfig<T>| {
for &(ref stash, ref controller, balance, ref status) in &config.stakers {
assert!(
T::Currency::free_balance(&stash) >= balance,
"Stash does not have enough balance to bond."
);
let _ = <Module<T>>::bond(
T::Origin::from(Some(stash.clone()).into()),
T::Lookup::unlookup(controller.clone()),
balance,
RewardDestination::Staked,
);
let _ = match status {
StakerStatus::Validator => {
<Module<T>>::validate(
T::Origin::from(Some(controller.clone()).into()),
Default::default(),
)
},
StakerStatus::Nominator(votes) => {
<Module<T>>::nominate(
T::Origin::from(Some(controller.clone()).into()),
votes.iter().map(|l| T::Lookup::unlookup(l.clone())).collect(),
)
}, _ => Ok(())
};
}
pub enum Event<T> where Balance = BalanceOf<T>, <T as frame_system::Trait>::AccountId {
/// 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, Balance, Balance),
/// The staker has been rewarded by this amount. `AccountId` is the stash account.
/// One validator (and its nominators) has been slashed by the given amount.
Slash(AccountId, Balance),
/// An old slashing report from a prior era was discarded because it could
/// not be processed.
OldSlashingReportDiscarded(SessionIndex),
/// A new set of stakers was elected with the given computation method.
StakingElection(ElectionCompute),
/// An account has bonded this 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(AccountId, Balance),
/// An account has unbonded this amount.
Unbonded(AccountId, Balance),
/// An account has called `withdraw_unbonded` and removed unbonding chunks worth `Balance`
/// from the unlocking queue.
Withdrawn(AccountId, Balance),
/// Error for the staking module.
pub enum Error for Module<T: Trait> {
/// 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,
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
1159
1160
/// The submitted result is received out of the open window.
PhragmenEarlySubmission,
/// The submitted result is not as good as the one stored on chain.
PhragmenWeakSubmission,
/// The snapshot data of the current window is missing.
SnapshotUnavailable,
/// Incorrect number of winners were presented.
PhragmenBogusWinnerCount,
/// One of the submitted winners is not an active candidate on chain (index is out of range
/// in snapshot).
PhragmenBogusWinner,
/// Error while building the assignment type from the compact. This can happen if an index
/// is invalid, or if the weights _overflow_.
PhragmenBogusCompact,
/// One of the submitted nominators is not an active nominator on chain.
PhragmenBogusNominator,
/// One of the submitted nominators has an edge to which they have not voted on chain.
PhragmenBogusNomination,
/// One of the submitted nominators has an edge which is submitted before the last non-zero
/// slash of the target.
PhragmenSlashedNomination,
/// A self vote must only be originated from a validator to ONLY themselves.
PhragmenBogusSelfVote,
/// The submitted result has unknown edges that are not among the presented winners.
PhragmenBogusEdge,
/// The claimed score does not match with the one computed from the data.
PhragmenBogusScore,
/// The call is not allowed at the given time due to restrictions of election period.
CallNotAllowed,
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
/// Number of sessions per era.
const SessionsPerEra: SessionIndex = T::SessionsPerEra::get();
/// Number of eras that staked funds must remain bonded for.
const BondingDuration: EraIndex = T::BondingDuration::get();
fn deposit_event() = default;
/// sets `ElectionStatus` to `Open(now)` where `now` is the block number at which the
/// election window has opened, if we are at the last session and less blocks than
/// `T::ElectionLookahead` is remaining until the next new session schedule. The offchain
/// worker, if applicable, will execute at the end of the current block, and solutions may
/// be submitted.
fn on_initialize(now: T::BlockNumber) -> Weight {
if
// if we don't have any ongoing offchain compute.
Self::era_election_status().is_closed() &&
// either current session final based on the plan, or we're forcing.
(Self::is_current_session_final() || Self::will_era_be_forced())
{
if let Some(next_session_change) = T::NextNewSession::estimate_next_new_session(now){
if let Some(remaining) = next_session_change.checked_sub(&now) {
if remaining <= T::ElectionLookahead::get() && !remaining.is_zero() {
// create snapshot.
if Self::create_stakers_snapshot() {
// Set the flag to make sure we don't waste any compute here in the same era
// after we have triggered the offline compute.
<EraElectionStatus<T>>::put(
ElectionStatus::<T::BlockNumber>::Open(now)
);
log!(info, "💸 Election window is Open({:?}). Snapshot created", now);
log!(warn, "💸 Failed to create snapshot at {:?}.", now);
log!(warn, "💸 Estimating next session change failed.");
}
}
// weight
50_000
}
/// Check if the current block number is the one at which the election window has been set
/// to open. If so, it runs the offchain worker code.
fn offchain_worker(now: T::BlockNumber) {
use offchain_election::{set_check_offchain_execution_status, compute_offchain_election};
if Self::era_election_status().is_open_at(now) {
let offchain_status = set_check_offchain_execution_status::<T>(now);
if let Err(why) = offchain_status {
log!(debug, "skipping offchain worker in open election window due to [{}]", why);
} else {
if let Err(e) = compute_offchain_election::<T>() {
log!(warn, "💸 Error in phragmen offchain worker: {:?}", e);
log!(debug, "Executed offchain worker thread without errors.");
fn on_finalize() {
// 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);
ActiveEra::put(active_era);
fn on_runtime_upgrade() -> Weight {
// For Kusama the type hasn't actually changed as Moment was u64 and was the number of
// millisecond since unix epoch.
StorageVersion::mutate(|v| {
if matches!(v, Releases::V2_0_0) {
Self::migrate_last_reward_to_claimed_rewards();
}
*v = Releases::V3_0_0;
});
/// 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>
controller: <T::Lookup as StaticLookup>::Source,
#[compact] value: BalanceOf<T>,
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)?
}
// 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::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(RawEvent::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.
/// # </weight>
fn bond_extra(origin, #[compact] max_additional: BalanceOf<T>) {
ensure!(Self::era_election_status().is_closed(), Error::<T>::CallNotAllowed);
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;
Self::deposit_event(RawEvent::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.
/// </weight>
fn unbond(origin, #[compact] value: BalanceOf<T>) {
ensure!(Self::era_election_status().is_closed(), Error::<T>::CallNotAllowed);
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(RawEvent::Unbonded(ledger.stash.clone(), 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.
/// # </weight>
ensure!(Self::era_election_status().is_closed(), Error::<T>::CallNotAllowed);
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)
}
if ledger.unlocking.is_empty() && ledger.active.is_zero() {
// 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.
// remove the lock.
T::Currency::remove_lock(STAKING_ID, &stash);
} else {
// This was the consequence of a partial unbond. just update the ledger and move on.
Self::update_ledger(&controller, &ledger);
}
// `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(RawEvent::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.
/// # </weight>
pub fn validate(origin, prefs: ValidatorPrefs) {
ensure!(Self::era_election_status().is_closed(), Error::<T>::CallNotAllowed);
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`,
/// which is capped at CompactAssignments::LIMIT.
/// - Both the reads and writes follow a similar pattern.
/// # </weight>
pub fn nominate(origin, targets: Vec<<T::Lookup as StaticLookup>::Source>) {
ensure!(Self::era_election_status().is_closed(), Error::<T>::CallNotAllowed);
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);
let targets = targets.into_iter()
.take(<CompactAssignments as VotingLimit>::LIMIT)
.map(|t| T::Lookup::lookup(t))
.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.
/// # </weight>
ensure!(Self::era_election_status().is_closed(), Error::<T>::CallNotAllowed);
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.
/// # </weight>
fn set_payee(origin, payee: RewardDestination) {
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.
/// # </weight>
fn set_controller(origin, controller: <T::Lookup as StaticLookup>::Source) {
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);
}
/// The ideal number of validators.
fn set_validator_count(origin, #[compact] new: u32) {
ValidatorCount::put(new);
/// Force there to be no new eras indefinitely.
///
/// # <weight>
/// - No arguments.
/// # </weight>
fn force_no_eras(origin) {
ForceEra::put(Forcing::ForceNone);
}
/// 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.
///
/// # <weight>
/// # </weight>
fn force_new_era(origin) {
ForceEra::put(Forcing::ForceNew);
/// Set the validators who cannot be slashed (if any).
fn set_invulnerables(origin, validators: Vec<T::AccountId>) {
<Invulnerables<T>>::put(validators);
/// Force a current staker to become completely unstaked, immediately.
#[weight = 0]
fn force_unstake(origin, stash: T::AccountId) {
// remove all staking-related information.
Self::kill_stash(&stash)?;
// remove the lock.
T::Currency::remove_lock(STAKING_ID, &stash);
}
/// Force there to be a new era at the end of sessions indefinitely.
///
/// # <weight>
/// - One storage write
/// # </weight>
ForceEra::put(Forcing::ForceAlways);
}
/// Cancel enactment of a deferred slash. Can be called by either the root origin or
/// the `T::SlashCancelOrigin`.
/// passing the era and indices of the slashes for that era to kill.
///
/// # <weight>
/// - One storage write.
/// # </weight>
#[weight = 1_000_000_000]
fn cancel_deferred_slash(origin, era: EraIndex, slash_indices: Vec<u32>) {
T::SlashCancelOrigin::try_origin(origin)
.map(|_| ())
.or_else(ensure_root)?;
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);
}
/// **This extrinsic will be removed after `MigrationEra + HistoryDepth` has passed, giving
/// opportunity for users to claim all rewards before moving to Simple Payouts. After this
/// time, you should use `payout_stakers` instead.**
///
/// Make one nominator's payout for one era.
///
/// - `who` is the controller account of the nominator to pay out.
/// - `era` may not be lower than one following the most recently paid era. If it is higher,
/// then it indicates an instruction to skip the payout of all previous eras.
/// - `validators` is the list of all validators that `who` had exposure to during `era`,
/// alongside the index of `who` in the clipped exposure of the validator.
/// I.e. each element is a tuple of
/// `(validator, index of `who` in clipped exposure of validator)`.
/// If it is incomplete, then less than the full reward will be paid out.
/// It must not exceed `MAX_NOMINATIONS`.
///
/// WARNING: once an era is payed for a validator such validator can't claim the payout of
/// previous era.
///
/// WARNING: Incorrect arguments here can result in loss of payout. Be very careful.
///
/// # <weight>
/// - Number of storage read of `O(validators)`; `validators` is the argument of the call,
/// and is bounded by `MAX_NOMINATIONS`.
/// - Each storage read is `O(N)` size and decode complexity; `N` is the maximum
/// nominations that can be given to a single validator.
/// - Computation complexity: `O(MAX_NOMINATIONS * logN)`; `MAX_NOMINATIONS` is the
/// maximum number of validators that may be nominated by a single nominator, it is
/// bounded only economically (all nominators are required to place a minimum stake).
/// # </weight>
fn payout_nominator(origin, era: EraIndex, validators: Vec<(T::AccountId, u32)>)
-> DispatchResult
{
let ctrl = ensure_signed(origin)?;
Self::do_payout_nominator(ctrl, era, validators)
/// **This extrinsic will be removed after `MigrationEra + HistoryDepth` has passed, giving
/// opportunity for users to claim all rewards before moving to Simple Payouts. After this
/// time, you should use `payout_stakers` instead.**
///
/// Make one validator's payout for one era.
///
/// - `who` is the controller account of the validator to pay out.
/// - `era` may not be lower than one following the most recently paid era. If it is higher,
/// then it indicates an instruction to skip the payout of all previous eras.
///
/// WARNING: once an era is payed for a validator such validator can't claim the payout of
/// previous era.
///
/// WARNING: Incorrect arguments here can result in loss of payout. Be very careful.
///
/// # <weight>
/// - Time complexity: O(1).
/// - Contains a limited number of reads and writes.
/// # </weight>
fn payout_validator(origin, era: EraIndex) -> DispatchResult {
let ctrl = ensure_signed(origin)?;
Self::do_payout_validator(ctrl, era)
/// 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.
/// # </weight>
fn payout_stakers(origin, validator_stash: T::AccountId, era: EraIndex) -> DispatchResult {
ensure!(Self::era_election_status().is_closed(), Error::<T>::CallNotAllowed);
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`.
///
/// # <weight>
/// - Time complexity: O(1). Bounded by `MAX_UNLOCKING_CHUNKS`.
/// - Storage changes: Can't increase storage, only decrease it.
/// # </weight>
fn rebond(origin, #[compact] value: BalanceOf<T>) {
ensure!(Self::era_election_status().is_closed(), Error::<T>::CallNotAllowed);
let controller = ensure_signed(origin)?;
let ledger = Self::ledger(&controller).ok_or(Error::<T>::NotController)?;
ensure!(!ledger.unlocking.is_empty(), Error::<T>::NoUnlockChunk);
let ledger = ledger.rebond(value);
Self::update_ledger(&controller, &ledger);
}
/// Set history_depth value.
///
/// Origin must be root.
#[weight = (500_000_000, DispatchClass::Operational)]
fn set_history_depth(origin, #[compact] new_history_depth: EraIndex) {
ensure_root(origin)?;
if let Some(current_era) = Self::current_era() {
HistoryDepth::mutate(|history_depth| {
let last_kept = current_era.checked_sub(*history_depth).unwrap_or(0);
let new_last_kept = current_era.checked_sub(new_history_depth).unwrap_or(0);
for era_index in last_kept..new_last_kept {
Self::clear_era_information(era_index);
}
*history_depth = new_history_depth
})
}
}
/// Remove all data structure concerning a staker/stash once its balance is zero.
/// This is essentially equivalent to `withdraw_unbonded` except it can be called by anyone
/// and the target `stash` must have no funds left.
///
/// This can be called from any origin.
///
/// - `stash`: The stash account to reap. Its balance must be zero.
#[weight = 0]
fn reap_stash(_origin, stash: T::AccountId) {
ensure!(T::Currency::total_balance(&stash).is_zero(), Error::<T>::FundedTarget);
Self::kill_stash(&stash)?;
T::Currency::remove_lock(STAKING_ID, &stash);
}
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
/// Submit a phragmen result to the chain. If the solution:
///
/// 1. is valid.
/// 2. has a better score than a potentially existing solution on chain.
///
/// then, it will be _put_ on chain.
///
/// A solution consists of two pieces of data:
///
/// 1. `winners`: a flat vector of all the winners of the round.
/// 2. `assignments`: the compact version of an assignment vector that encodes the edge
/// weights.
///
/// Both of which may be computed using [`phragmen`], or any other algorithm.
///
/// Additionally, the submitter must provide:
///
/// - The `score` that they claim their solution has.
///
/// Both validators and nominators will be represented by indices in the solution. The
/// indices should respect the corresponding types ([`ValidatorIndex`] and
/// [`NominatorIndex`]). Moreover, they should be valid when used to index into
/// [`SnapshotValidators`] and [`SnapshotNominators`]. Any invalid index will cause the
/// solution to be rejected. These two storage items are set during the election window and
/// may be used to determine the indices.
///
/// A solution is valid if:
///
/// 0. It is submitted when [`EraElectionStatus`] is `Open`.
/// 1. Its claimed score is equal to the score computed on-chain.
/// 2. Presents the correct number of winners.
/// 3. All indexes must be value according to the snapshot vectors. All edge values must
/// also be correct and should not overflow the granularity of the ratio type (i.e. 256
/// or billion).
/// 4. For each edge, all targets are actually nominated by the voter.
/// 5. Has correct self-votes.
///
/// A solutions score is consisted of 3 parameters:
///
/// 1. `min { support.total }` for each support of a winner. This value should be maximized.
/// 2. `sum { support.total }` for each support of a winner. This value should be minimized.
/// 3. `sum { support.total^2 }` for each support of a winner. This value should be
/// minimized (to ensure less variance)
///
/// # <weight>
/// E: number of edges. m: size of winner committee. n: number of nominators. d: edge degree
/// (16 for now) v: number of on-chain validator candidates.
///
/// NOTE: given a solution which is reduced, we can enable a new check the ensure `|E| < n +
/// m`. We don't do this _yet_, but our offchain worker code executes it nonetheless.
///
/// major steps (all done in `check_and_replace_solution`):
///
/// - Storage: O(1) read `ElectionStatus`.
/// - Storage: O(1) read `PhragmenScore`.
/// - Storage: O(1) read `ValidatorCount`.
/// - Storage: O(1) length read from `SnapshotValidators`.
///
/// - Storage: O(v) reads of `AccountId` to fetch `snapshot_validators`.
/// - Memory: O(m) iterations to map winner index to validator id.
/// - Storage: O(n) reads `AccountId` to fetch `snapshot_nominators`.
/// - Memory: O(n + m) reads to map index to `AccountId` for un-compact.
///
/// - Storage: O(e) accountid reads from `Nomination` to read correct nominations.
/// - Storage: O(e) calls into `slashable_balance_of_vote_weight` to convert ratio to staked.
///
/// - Memory: build_support_map. O(e).
/// - Memory: evaluate_support: O(E).
///
/// - Storage: O(e) writes to `QueuedElected`.
/// - Storage: O(1) write to `QueuedScore`
///
/// The weight of this call is 1/10th of the blocks total weight.
/// # </weight>
#[weight = 100_000_000_000]
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
pub fn submit_election_solution(
origin,
winners: Vec<ValidatorIndex>,
compact_assignments: CompactAssignments,
score: PhragmenScore,
era: EraIndex,
) {
let _who = ensure_signed(origin)?;
Self::check_and_replace_solution(
winners,
compact_assignments,
ElectionCompute::Signed,
score,
era,
)?
}
/// Unsigned version of `submit_election_solution`.
///
/// Note that this must pass the [`ValidateUnsigned`] check which only allows transactions
/// from the local node to be included. In other words, only the block author can include a
/// transaction in the block.
#[weight = 100_000_000_000]
pub fn submit_election_solution_unsigned(
origin,
winners: Vec<ValidatorIndex>,
compact_assignments: CompactAssignments,
score: PhragmenScore,
era: EraIndex,
) {
ensure_none(origin)?;
Self::check_and_replace_solution(
winners,
compact_assignments,
ElectionCompute::Unsigned,
score,
era,
)?
// TODO: instead of returning an error, panic. This makes the entire produced block
// invalid.
// This ensures that block authors will not ever try and submit a solution which is not
// an improvement, since they will lose their authoring points/rewards.
}
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
/// Migrate `last_reward` to `claimed_rewards`
pub fn migrate_last_reward_to_claimed_rewards() {
use frame_support::migration::{StorageIterator, put_storage_value};
// Migrate from `last_reward` to `claimed_rewards`.
// We will construct a vector from `current_era - history_depth` to `last_reward`
// for each validator and nominator.
//
// Old Staking Ledger
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug)]
struct OldStakingLedger<AccountId, Balance: HasCompact> {
pub stash: AccountId,
#[codec(compact)]
pub total: Balance,
#[codec(compact)]
pub active: Balance,
pub unlocking: Vec<UnlockChunk<Balance>>,
pub last_reward: Option<EraIndex>,
}
// Current era and history depth
let current_era = Self::current_era().unwrap_or(0);
let history_depth = Self::history_depth();
let last_payout_era = current_era.saturating_sub(history_depth);
// Convert all ledgers to the new format.
for (hash, old_ledger) in StorageIterator::<OldStakingLedger<T::AccountId, BalanceOf<T>>>::new(b"Staking", b"Ledger").drain() {
let last_reward = old_ledger.last_reward.unwrap_or(0);
let new_ledger = StakingLedger {
stash: old_ledger.stash,
total: old_ledger.total,
active: old_ledger.active,
unlocking: old_ledger.unlocking,
claimed_rewards: (last_payout_era..=last_reward).collect(),
};
put_storage_value(b"Staking", b"Ledger", &hash, new_ledger);
}
MigrateEra::put(current_era);
}
/// The total balance that can be slashed from a stash account as of right now.
pub fn slashable_balance_of(stash: &T::AccountId) -> BalanceOf<T> {
Self::bonded(stash).and_then(Self::ledger).map(|l| l.active).unwrap_or_default()
/// internal impl of [`slashable_balance_of`] that returns [`VoteWeight`].
fn slashable_balance_of_vote_weight(stash: &T::AccountId) -> VoteWeight {
<T::CurrencyToVote as Convert<BalanceOf<T>, VoteWeight>>::convert(
}
/// Dump the list of validators and nominators into vectors and keep them on-chain.
///
/// This data is used to efficiently evaluate election results. returns `true` if the operation
/// is successful.
fn create_stakers_snapshot() -> bool {
let validators = <Validators<T>>::iter().map(|(v, _)| v).collect::<Vec<_>>();
let mut nominators = <Nominators<T>>::iter().map(|(n, _)| n).collect::<Vec<_>>();
let num_validators = validators.len();
let num_nominators = nominators.len();
if
num_validators > MAX_VALIDATORS ||
num_nominators.saturating_add(num_validators) > MAX_NOMINATORS
{
log!(
warn,
"💸 Snapshot size too big [{} <> {}][{} <> {}].",