Newer
Older
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Implementations for the Staking FRAME Pallet.
use frame_election_provider_support::{
bounds::{CountBound, SizeBound},
data_provider, BoundedSupportsOf, DataProviderBounds, ElectionDataProvider, ElectionProvider,
ScoreProvider, SortedListProvider, VoteWeight, VoterOf,
};
use frame_support::{
dispatch::WithPostDispatchInfo,
pallet_prelude::*,
traits::{
Currency, Defensive, EstimateNextNewSession, Get, Imbalance, Len, OnUnbalanced, TryCollect,
UnixTime,
weights::Weight,
use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin};
use pallet_session::historical;
use sp_runtime::{
traits::{Bounded, Convert, One, SaturatedConversion, Saturating, StaticLookup, Zero},
Perbill,
};
use sp_staking::{
currency_to_vote::CurrencyToVote,
offence::{DisableStrategy, OffenceDetails, OnOffenceHandler},
EraIndex, Page, SessionIndex, Stake,
StakingAccount::{self, Controller, Stash},
StakingInterface,
use sp_std::prelude::*;
use crate::{
election_size_tracker::StaticTracker, log, slashing, weights::WeightInfo, ActiveEraInfo,
BalanceOf, EraInfo, EraPayout, Exposure, ExposureOf, Forcing, IndividualExposure,
MaxNominationsOf, MaxWinnersOf, Nominations, NominationsQuota, PositiveImbalanceOf,
RewardDestination, SessionInterface, StakingLedger, ValidatorPrefs,
#[cfg(feature = "try-runtime")]
use frame_support::ensure;
#[cfg(any(test, feature = "try-runtime"))]
use sp_runtime::TryRuntimeError;
Kian Paimani
committed
/// The maximum number of iterations that we do whilst iterating over `T::VoterList` in
/// `get_npos_voters`.
///
/// In most cases, if we want n items, we iterate exactly n times. In rare cases, if a voter is
/// invalid (for any reason) the iteration continues. With this constant, we iterate at most 2 * n
/// times and then give up.
const NPOS_MAX_ITERATIONS_COEFFICIENT: u32 = 2;
impl<T: Config> Pallet<T> {
/// Fetches the ledger associated with a controller or stash account, if any.
pub fn ledger(account: StakingAccount<T::AccountId>) -> Result<StakingLedger<T>, Error<T>> {
StakingLedger::<T>::get(account)
}
pub fn payee(account: StakingAccount<T::AccountId>) -> RewardDestination<T::AccountId> {
StakingLedger::<T>::reward_destination(account)
}
/// Fetches the controller bonded to a stash account, if any.
pub fn bonded(stash: &T::AccountId) -> Option<T::AccountId> {
StakingLedger::<T>::paired_account(Stash(stash.clone()))
}
/// 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> {
// Weight note: consider making the stake accessible through stash.
Self::ledger(Stash(stash.clone())).map(|l| l.active).unwrap_or_default()
}
/// Internal impl of [`Self::slashable_balance_of`] that returns [`VoteWeight`].
pub fn slashable_balance_of_vote_weight(
stash: &T::AccountId,
issuance: BalanceOf<T>,
) -> VoteWeight {
T::CurrencyToVote::to_vote(Self::slashable_balance_of(stash), issuance)
}
/// Returns a closure around `slashable_balance_of_vote_weight` that can be passed around.
///
/// This prevents call sites from repeatedly requesting `total_issuance` from backend. But it is
/// important to be only used while the total issuance is not changing.
pub fn weight_of_fn() -> Box<dyn Fn(&T::AccountId) -> VoteWeight> {
// NOTE: changing this to unboxed `impl Fn(..)` return type and the pallet will still
// compile, while some types in mock fail to resolve.
let issuance = T::Currency::total_issuance();
Box::new(move |who: &T::AccountId| -> VoteWeight {
Self::slashable_balance_of_vote_weight(who, issuance)
})
}
/// Same as `weight_of_fn`, but made for one time use.
pub fn weight_of(who: &T::AccountId) -> VoteWeight {
let issuance = T::Currency::total_issuance();
Self::slashable_balance_of_vote_weight(who, issuance)
}
pub(super) fn do_withdraw_unbonded(
controller: &T::AccountId,
num_slashing_spans: u32,
) -> Result<Weight, DispatchError> {
let mut ledger = Self::ledger(Controller(controller.clone()))?;
let (stash, old_total) = (ledger.stash.clone(), ledger.total);
if let Some(current_era) = Self::current_era() {
ledger = ledger.consolidate_unlocked(current_era)
}
let used_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(&ledger.stash, num_slashing_spans)?;
T::WeightInfo::withdraw_unbonded_kill(num_slashing_spans)
} else {
// This was the consequence of a partial unbond. just update the ledger and move on.
// This is only an update, so we use less overall weight.
T::WeightInfo::withdraw_unbonded_update(num_slashing_spans)
};
// `old_total` should never be less than the new total because
// `consolidate_unlocked` strictly subtracts balance.
// Already checked that this won't overflow by entry condition.
Self::deposit_event(Event::<T>::Withdrawn { stash, amount: value });
}
Ok(used_weight)
}
pub(super) fn do_payout_stakers(
validator_stash: T::AccountId,
era: EraIndex,
) -> DispatchResultWithPostInfo {
let controller = Self::bonded(&validator_stash).ok_or_else(|| {
Error::<T>::NotStash.with_weight(T::WeightInfo::payout_stakers_alive_staked(0))
})?;
let ledger = <Ledger<T>>::get(&controller).ok_or(Error::<T>::NotController)?;
let page = EraInfo::<T>::get_next_claimable_page(era, &validator_stash, &ledger)
.ok_or_else(|| {
Error::<T>::AlreadyClaimed
.with_weight(T::WeightInfo::payout_stakers_alive_staked(0))
})?;
Self::do_payout_stakers_by_page(validator_stash, era, page)
}
pub(super) fn do_payout_stakers_by_page(
validator_stash: T::AccountId,
era: EraIndex,
page: Page,
) -> DispatchResultWithPostInfo {
// Validate input data
let current_era = CurrentEra::<T>::get().ok_or_else(|| {
Error::<T>::InvalidEraToReward
.with_weight(T::WeightInfo::payout_stakers_alive_staked(0))
})?;
let history_depth = T::HistoryDepth::get();
ensure!(
era <= current_era && era >= current_era.saturating_sub(history_depth),
Error::<T>::InvalidEraToReward
.with_weight(T::WeightInfo::payout_stakers_alive_staked(0))
);
ensure!(
page < EraInfo::<T>::get_page_count(era, &validator_stash),
Error::<T>::InvalidPage.with_weight(T::WeightInfo::payout_stakers_alive_staked(0))
);
// Note: if era has no reward to be claimed, era may be future. better not to update
// `ledger.legacy_claimed_rewards` in this case.
let era_payout = <ErasValidatorReward<T>>::get(&era).ok_or_else(|| {
Loading full blame...