// 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.

//! # Staking Pallet
//!
//! The Staking pallet is used to manage funds at stake by network maintainers.
//!
//! - [`Config`]
//! - [`Call`]
//! - [`Pallet`]
//!
//! ## Overview
//!
//! The Staking pallet is the means by which a set of network maintainers (known as _authorities_ in
//! some contexts and _validators_ in others) are chosen based upon those who voluntarily place
//! funds under deposit. Under deposit, those funds are rewarded under normal operation but are held
//! at pain of _slash_ (expropriation) should the staked maintainer be found not to be discharging
//! its duties properly.
//!
//! ### Terminology
//! <!-- Original author of paragraph: @gavofyork -->
//!
//! - Staking: The process of locking up funds for some time, placing them at risk of slashing
//!   (loss) in order to become a rewarded maintainer of the network.
//! - Validating: The process of running a node to actively maintain the network, either by
//!   producing blocks or guaranteeing finality of the chain.
//! - Nominating: The process of placing staked funds behind one or more validators in order to
//!   share in any reward, and punishment, they take.
//! - Stash account: The account holding an owner's funds used for staking.
//! - Controller account: The account that controls an owner's funds for staking.
//! - Era: A (whole) number of sessions, which is the period that the validator set (and each
//!   validator's active nominator set) is recalculated and where rewards are paid out.
//! - Slash: The punishment of a staker by reducing its funds.
//!
//! ### Goals
//! <!-- Original author of paragraph: @gavofyork -->
//!
//! The staking system in Substrate NPoS is designed to make the following possible:
//!
//! - Stake funds that are controlled by a cold wallet.
//! - Withdraw some, or deposit more, funds without interrupting the role of an entity.
//! - Switch between roles (nominator, validator, idle) with minimal overhead.
//!
//! ### Scenarios
//!
//! #### Staking
//!
//! Almost any interaction with the Staking pallet requires a process of _**bonding**_ (also known
//! as being a _staker_). To become *bonded*, a fund-holding register known as the _stash account_,
//! which holds some or all of the funds that become frozen in place as part of the staking process,
//! is paired with an active **controller** account, which issues instructions on how they shall be
//! used.
//!
//! An account pair can become bonded using the [`bond`](Call::bond) call.
//!
//! Stash accounts can change their associated controller using the
//! [`set_controller`](Call::set_controller) call.
//!
//! There are three possible roles that any staked account pair can be in: `Validator`, `Nominator`
//! and `Idle` (defined in [`StakerStatus`]). There are three
//! corresponding instructions to change between roles, namely:
//! [`validate`](Call::validate),
//! [`nominate`](Call::nominate), and [`chill`](Call::chill).
//!
//! #### Validating
//!
//! A **validator** takes the role of either validating blocks or ensuring their finality,
//! maintaining the veracity of the network. A validator should avoid both any sort of malicious
//! misbehavior and going offline. Bonded accounts that state interest in being a validator do NOT
//! get immediately chosen as a validator. Instead, they are declared as a _candidate_ and they
//! _might_ get elected at the _next era_ as a validator. The result of the election is determined
//! by nominators and their votes.
//!
//! An account can become a validator candidate via the
//! [`validate`](Call::validate) call.
//!
//! #### Nomination
//!
//! A **nominator** does not take any _direct_ role in maintaining the network, instead, it votes on
//! a set of validators  to be elected. Once interest in nomination is stated by an account, it
//! takes effect at the next election round. The funds in the nominator's stash account indicate the
//! _weight_ of its vote. Both the rewards and any punishment that a validator earns are shared
//! between the validator and its nominators. This rule incentivizes the nominators to NOT vote for
//! the misbehaving/offline validators as much as possible, simply because the nominators will also
//! lose funds if they vote poorly.
//!
//! An account can become a nominator via the [`nominate`](Call::nominate) call.
//!
//! #### Voting
//!
//! Staking is closely related to elections; actual validators are chosen from among all potential
//! validators via election by the potential validators and nominators. To reduce use of the phrase
//! "potential validators and nominators", we often use the term **voters**, who are simply
//! the union of potential validators and nominators.
//!
//! #### Rewards and Slash
//!
//! The **reward and slashing** procedure is the core of the Staking pallet, attempting to _embrace
//! valid behavior_ while _punishing any misbehavior or lack of availability_.
//!
//! Rewards must be claimed for each era before it gets too old by `$HISTORY_DEPTH` using the
//! `payout_stakers` call. Any account can call `payout_stakers`, which pays the reward to the
//! validator as well as its nominators. Only the [`Config::MaxNominatorRewardedPerValidator`]
//! biggest stakers can claim their reward. This is to limit the i/o cost to mutate storage for each
//! nominator's account.
//!
//! Slashing can occur at any point in time, once misbehavior is reported. Once slashing is
//! determined, a value is deducted from the balance of the validator and all the nominators who
//! voted for this validator (values are deducted from the _stash_ account of the slashed entity).
//!
//! Slashing logic is further described in the documentation of the `slashing` pallet.
//!
//! Similar to slashing, rewards are also shared among a validator and its associated nominators.
//! Yet, the reward funds are not always transferred to the stash account and can be configured. See
//! [Reward Calculation](#reward-calculation) for more details.
//!
//! #### Chilling
//!
//! Finally, any of the roles above can choose to step back temporarily and just chill for a while.
//! This means that if they are a nominator, they will not be considered as voters anymore and if
//! they are validators, they will no longer be a candidate for the next election.
//!
//! An account can step back via the [`chill`](Call::chill) call.
//!
//! ### Session managing
//!
//! The pallet implement the trait `SessionManager`. Which is the only API to query new validator
//! set and allowing these validator set to be rewarded once their era is ended.
//!
//! ## Interface
//!
//! ### Dispatchable Functions
//!
//! The dispatchable functions of the Staking pallet enable the steps needed for entities to accept
//! and change their role, alongside some helper functions to get/set the metadata of the pallet.
//!
//! ### Public Functions
//!
//! The Staking pallet contains many public storage items and (im)mutable functions.
//!
//! ## Usage
//!
//! ### Example: Rewarding a validator by id.
//!
//! ```
//! use pallet_staking::{self as staking};
//!
//! #[frame_support::pallet]
//! pub mod pallet {
//! 	use super::*;
//! 	use frame_support::pallet_prelude::*;
//! 	use frame_system::pallet_prelude::*;
//!
//! 	#[pallet::pallet]
//! 	pub struct Pallet<T>(_);
//!
//! 	#[pallet::config]
//! 	pub trait Config: frame_system::Config + staking::Config {}
//!
//! 	#[pallet::call]
//! 	impl<T: Config> Pallet<T> {
//!         /// Reward a validator.
//!         #[pallet::weight(0)]
//!         pub fn reward_myself(origin: OriginFor<T>) -> DispatchResult {
//!             let reported = ensure_signed(origin)?;
//!             <staking::Pallet<T>>::reward_by_ids(vec![(reported, 10)]);
//!             Ok(())
//!         }
//!     }
//! }
//! # fn main() { }
//! ```
//!
//! ## Implementation Details
//!
//! ### Era payout
//!
//! The era payout is computed using yearly inflation curve defined at
//! [`Config::EraPayout`] as such:
//!
//! ```nocompile
//! staker_payout = yearly_inflation(npos_token_staked / total_tokens) * total_tokens / era_per_year
//! ```
//! This payout is used to reward stakers as defined in next section
//!
//! ```nocompile
//! remaining_payout = max_yearly_inflation * total_tokens / era_per_year - staker_payout
//! ```
//! The remaining reward is send to the configurable end-point
//! [`Config::RewardRemainder`].
//!
//! ### Reward Calculation
//!
//! Validators and nominators are rewarded at the end of each era. The total reward of an era is
//! calculated using the era duration and the staking rate (the total amount of tokens staked by
//! nominators and validators, divided by the total token supply). It aims to incentivize toward a
//! defined staking rate. The full specification can be found
//! [here](https://research.web3.foundation/en/latest/polkadot/Token%20Economics.html#inflation-model).
//!
//! Total reward is split among validators and their nominators depending on the number of points
//! they received during the era. Points are added to a validator using
//! [`reward_by_ids`](Pallet::reward_by_ids).
//!
//! [`Pallet`] implements
//! [`pallet_authorship::EventHandler`] to add reward
//! points to block producer and block producer of referenced uncles.
//!
//! The validator and its nominator split their reward as following:
//!
//! The validator can declare an amount, named [`commission`](ValidatorPrefs::commission), that does
//! not get shared with the nominators at each reward payout through its [`ValidatorPrefs`]. This
//! value gets deducted from the total reward that is paid to the validator and its nominators. The
//! remaining portion is split pro rata among the validator and the top
//! [`Config::MaxNominatorRewardedPerValidator`] nominators that nominated the validator,
//! proportional to the value staked behind the validator (_i.e._ dividing the
//! [`own`](Exposure::own) or [`others`](Exposure::others) by [`total`](Exposure::total) in
//! [`Exposure`]). Note that the pro rata division of rewards uses the total exposure behind the
//! validator, *not* just the exposure of the validator and the top
//! [`Config::MaxNominatorRewardedPerValidator`] nominators.
//!
//! All entities who receive a reward have the option to choose their reward destination through the
//! [`Payee`] storage item (see
//! [`set_payee`](Call::set_payee)), to be one of the following:
//!
//! - Controller account, (obviously) not increasing the staked value.
//! - Stash account, not increasing the staked value.
//! - Stash account, also increasing the staked value.
//!
//! ### Additional Fund Management Operations
//!
//! Any funds already placed into stash can be the target of the following operations:
//!
//! The controller account can free a portion (or all) of the funds using the
//! [`unbond`](Call::unbond) call. Note that the funds are not immediately
//! accessible. Instead, a duration denoted by
//! [`Config::BondingDuration`] (in number of eras) must
//! pass until the funds can actually be removed. Once the `BondingDuration` is over, the
//! [`withdraw_unbonded`](Call::withdraw_unbonded) call can be used to actually
//! withdraw the funds.
//!
//! Note that there is a limitation to the number of fund-chunks that can be scheduled to be
//! unlocked in the future via [`unbond`](Call::unbond). In case this maximum
//! (`MAX_UNLOCKING_CHUNKS`) is reached, the bonded account _must_ first wait until a successful
//! call to `withdraw_unbonded` to remove some of the chunks.
//!
//! ### Election Algorithm
//!
//! The current election algorithm is implemented based on PhragmÃ©n. The reference implementation
//! can be found [here](https://github.com/w3f/consensus/tree/master/NPoS).
//!
//! The election algorithm, aside from electing the validators with the most stake value and votes,
//! tries to divide the nominator votes among candidates in an equal manner. To further assure this,
//! an optional post-processing can be applied that iteratively normalizes the nominator staked
//! values until the total difference among votes of a particular nominator are less than a
//! threshold.
//!
//! ## GenesisConfig
//!
//! The Staking pallet depends on the [`GenesisConfig`]. The
//! `GenesisConfig` is optional and allow to set some initial stakers.
//!
//! ## Related Modules
//!
//! - [Balances](../pallet_balances/index.html): Used to manage values at stake.
//! - [Session](../pallet_session/index.html): Used to manage sessions. Also, a list of new
//!   validators is stored in the Session pallet's `Validators` at the end of each era.

#![cfg_attr(not(feature = "std"), no_std)]
#![recursion_limit = "256"]

#[cfg(feature = "runtime-benchmarks")]
pub mod benchmarking;
#[cfg(any(feature = "runtime-benchmarks"))]
pub mod testing_utils;

pub mod inflation;
pub mod migrations;
pub mod slashing;
pub mod types;
pub mod weights;

pub mod pallet;

use codec::{Decode, Encode, HasCompact, MaxEncodedLen};
use frame_support::{
    traits::{Currency, Defensive, Get},
    BoundedVec, CloneNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound,
};
use scale_info::TypeInfo;
use sp_runtime::{
    curve::PiecewiseLinear,
    traits::{AtLeast32BitUnsigned, Convert, Saturating, StaticLookup, Zero},
    Perbill, Perquintill, Rounding, RuntimeDebug,
};
use sp_staking::{
    offence::{Offence, OffenceError, ReportOffence},
    EraIndex, SessionIndex,
};
use sp_std::{collections::btree_map::BTreeMap, prelude::*};
pub use weights::WeightInfo;

pub use pallet::{pallet::*, *};

pub(crate) const LOG_TARGET: &str = "runtime::staking";

// syntactic sugar for logging.
#[macro_export]
macro_rules! log {
	($level:tt, $patter:expr $(, $values:expr)* $(,)?) => {
		log::$level!(
			target: crate::LOG_TARGET,
			concat!("[{:?}] ðŸ’¸ ", $patter), <frame_system::Pallet<T>>::block_number() $(, $values)*
		)
	};
}

/// Maximum number of winners (aka. active validators), as defined in the election provider of this
/// pallet.
pub type MaxWinnersOf<T> = <<T as Config>::ElectionProvider as frame_election_provider_support::ElectionProviderBase>::MaxWinners;

/// Counter for the number of "reward" points earned by a given validator.
pub type RewardPoint = u32;

/// The balance type of this pallet.
pub type BalanceOf<T> = <T as Config>::CurrencyBalance;

pub type PositiveImbalanceOf<T> = <<T as Config>::Currency as Currency<
    <T as frame_system::Config>::AccountId,
>>::PositiveImbalance;
pub type NegativeImbalanceOf<T> = <<T as Config>::Currency as Currency<
    <T as frame_system::Config>::AccountId,
>>::NegativeImbalance;

type AccountIdLookupOf<T> = <<T as frame_system::Config>::Lookup as StaticLookup>::Source;

/// Information regarding the active era (era in used in session).
#[derive(Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)]
pub struct ActiveEraInfo {
    /// Index of era.
    pub index: EraIndex,
    /// Moment of start expressed as millisecond from `$UNIX_EPOCH`.
    ///
    /// Start can be none if start hasn't been set for the era yet,
    /// Start is set on the first on_finalize of the era to guarantee usage of `Time`.
    start: Option<u64>,
}

/// Reward points of an era. Used to split era total payout between validators.
///
/// This points will be used to reward validators and their respective nominators.
#[derive(PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)]
pub struct EraRewardPoints<AccountId: Ord> {
    /// Total number of points. Equals the sum of reward points for each validator.
    pub total: RewardPoint,
    /// The reward points earned by a given validator.
    pub individual: BTreeMap<AccountId, RewardPoint>,
}

impl<AccountId: Ord> Default for EraRewardPoints<AccountId> {
    fn default() -> Self {
        EraRewardPoints {
            total: Default::default(),
            individual: BTreeMap::new(),
        }
    }
}

/// Indicates the initial status of the staker.
#[derive(RuntimeDebug, TypeInfo)]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize, Clone))]
pub enum StakerStatus<AccountId> {
    /// Chilling.
    Idle,
    /// Declared desire in validating or already participating in it.
    Validator,
    /// Nominating for a group of other stakers.
    Nominator(Vec<AccountId>),
}

/// A destination account for payment.
#[derive(
    PartialEq,
    Eq,
    Copy,
    Clone,
    Encode,
    Decode,
    RuntimeDebug,
    TypeInfo,
    MaxEncodedLen
)]
pub enum RewardDestination<AccountId> {
    /// Pay into the stash account, increasing the amount at stake accordingly.
    Staked,
    /// Pay into the stash account, not increasing the amount at stake.
    Stash,
    /// Pay into the controller account.
    Controller,
    /// Pay into a specified account.
    Account(AccountId),
    /// Receive no reward.
    None,
}

impl<AccountId> Default for RewardDestination<AccountId> {
    fn default() -> Self {
        RewardDestination::Staked
    }
}

/// Preference of what happens regarding validation.
#[derive(
    PartialEq,
    Eq,
    Clone,
    Encode,
    Decode,
    RuntimeDebug,
    TypeInfo,
    Default,
    MaxEncodedLen
)]
pub struct ValidatorPrefs {
    /// Reward that validator takes up-front; only the rest is split between themselves and
    /// nominators.
    #[codec(compact)]
    pub commission: Perbill,
    /// Whether or not this validator is accepting more nominations. If `true`, then no nominator
    /// who is not already nominating this validator may nominate them. By default, validators
    /// are accepting nominations.
    pub blocked: bool,
}

/// Just a Balance/BlockNumber tuple to encode when a chunk of funds will be unlocked.
#[derive(
    PartialEq,
    Eq,
    Clone,
    Encode,
    Decode,
    RuntimeDebug,
    TypeInfo,
    MaxEncodedLen
)]
pub struct UnlockChunk<Balance: HasCompact + MaxEncodedLen> {
    /// Amount of funds to be unlocked.
    #[codec(compact)]
    pub value: Balance,
    /// Era number at which point it'll be unlocked.
    #[codec(compact)]
    pub era: EraIndex,
}

/// The ledger of a (bonded) stash.
#[derive(
    PartialEqNoBound,
    EqNoBound,
    CloneNoBound,
    Encode,
    Decode,
    RuntimeDebugNoBound,
    TypeInfo,
    MaxEncodedLen
)]
#[scale_info(skip_type_params(T))]
pub struct StakingLedger<T: Config> {
    /// The stash account whose balance is actually locked and at stake.
    pub stash: T::AccountId,
    /// The total amount of the stash's balance that we are currently accounting for.
    /// It's just `active` plus all the `unlocking` balances.
    #[codec(compact)]
    pub total: BalanceOf<T>,
    /// The total amount of the stash's balance that will be at stake in any forthcoming
    /// rounds.
    #[codec(compact)]
    pub active: BalanceOf<T>,
    /// Any balance that is becoming free, which may eventually be transferred out of the stash
    /// (assuming it doesn't get slashed first). It is assumed that this will be treated as a first
    /// in, first out queue where the new (higher value) eras get pushed on the back.
    pub unlocking: BoundedVec<UnlockChunk<BalanceOf<T>>, T::MaxUnlockingChunks>,
    /// List of eras for which the stakers behind a validator have claimed rewards. Only updated
    /// for validators.
    pub claimed_rewards: BoundedVec<EraIndex, T::HistoryDepth>,
}

impl<T: Config> StakingLedger<T> {
    /// Initializes the default object using the given `validator`.
    pub fn default_from(stash: T::AccountId) -> Self {
        Self {
            stash,
            total: Zero::zero(),
            active: Zero::zero(),
            unlocking: Default::default(),
            claimed_rewards: Default::default(),
        }
    }

    /// Remove entries from `unlocking` that are sufficiently old and reduce the
    /// total by the sum of their balances.
    fn consolidate_unlocked(self, current_era: EraIndex) -> Self {
        let mut total = self.total;
        let unlocking: BoundedVec<_, _> = self
            .unlocking
            .into_iter()
            .filter(|chunk| {
                if chunk.era > current_era {
                    true
                } else {
                    total = total.saturating_sub(chunk.value);
                    false
                }
            })
            .collect::<Vec<_>>()
            .try_into()
            .expect(
                "filtering items from a bounded vec always leaves length less than bounds. qed",
            );

        Self {
            stash: self.stash,
            total,
            active: self.active,
            unlocking,
            claimed_rewards: self.claimed_rewards,
        }
    }

    /// Re-bond funds that were scheduled for unlocking.
    ///
    /// Returns the updated ledger, and the amount actually rebonded.
    fn rebond(mut self, value: BalanceOf<T>) -> (Self, BalanceOf<T>) {
        let mut unlocking_balance = BalanceOf::<T>::zero();

        while let Some(last) = self.unlocking.last_mut() {
            if unlocking_balance + last.value <= value {
                unlocking_balance += last.value;
                self.active += last.value;
                self.unlocking.pop();
            } else {
                let diff = value - unlocking_balance;

                unlocking_balance += diff;
                self.active += diff;
                last.value -= diff;
            }

            if unlocking_balance >= value {
                break;
            }
        }

        (self, unlocking_balance)
    }

    /// Slash the staker for a given amount of balance.
    ///
    /// This implements a proportional slashing system, whereby we set our preference to slash as
    /// such:
    ///
    /// - If any unlocking chunks exist that are scheduled to be unlocked at `slash_era +
    ///   bonding_duration` and onwards, the slash is divided equally between the active ledger and
    ///   the unlocking chunks.
    /// - If no such chunks exist, then only the active balance is slashed.
    ///
    /// Note that the above is only a *preference*. If for any reason the active ledger, with or
    /// without some portion of the unlocking chunks that are more justified to be slashed are not
    /// enough, then the slashing will continue and will consume as much of the active and unlocking
    /// chunks as needed.
    ///
    /// This will never slash more than the given amount. If any of the chunks become dusted, the
    /// last chunk is slashed slightly less to compensate. Returns the amount of funds actually
    /// slashed.
    ///
    /// `slash_era` is the era in which the slash (which is being enacted now) actually happened.
    ///
    /// This calls `Config::OnStakerSlash::on_slash` with information as to how the slash was
    /// applied.
    pub fn slash(
        &mut self,
        slash_amount: BalanceOf<T>,
        minimum_balance: BalanceOf<T>,
        slash_era: EraIndex,
    ) -> BalanceOf<T> {
        if slash_amount.is_zero() {
            return Zero::zero();
        }

        use sp_runtime::PerThing as _;
        use sp_staking::OnStakerSlash as _;
        let mut remaining_slash = slash_amount;
        let pre_slash_total = self.total;

        // for a `slash_era = x`, any chunk that is scheduled to be unlocked at era `x + 28`
        // (assuming 28 is the bonding duration) onwards should be slashed.
        let slashable_chunks_start = slash_era + T::BondingDuration::get();

        // `Some(ratio)` if this is proportional, with `ratio`, `None` otherwise. In both cases, we
        // slash first the active chunk, and then `slash_chunks_priority`.
        let (maybe_proportional, slash_chunks_priority) = {
            if let Some(first_slashable_index) = self
                .unlocking
                .iter()
                .position(|c| c.era >= slashable_chunks_start)
            {
                // If there exists a chunk who's after the first_slashable_start, then this is a
                // proportional slash, because we want to slash active and these chunks
                // proportionally.

                // The indices of the first chunk after the slash up through the most recent chunk.
                // (The most recent chunk is at greatest from this era)
                let affected_indices = first_slashable_index..self.unlocking.len();
                let unbonding_affected_balance =
                    affected_indices
                        .clone()
                        .fold(BalanceOf::<T>::zero(), |sum, i| {
                            if let Some(chunk) = self.unlocking.get(i).defensive() {
                                sum.saturating_add(chunk.value)
                            } else {
                                sum
                            }
                        });
                let affected_balance = self.active.saturating_add(unbonding_affected_balance);
                let ratio = Perquintill::from_rational_with_rounding(
                    slash_amount,
                    affected_balance,
                    Rounding::Up,
                )
                .unwrap_or_else(|_| Perquintill::one());
                (
                    Some(ratio),
                    affected_indices
                        .chain((0..first_slashable_index).rev())
                        .collect::<Vec<_>>(),
                )
            } else {
                // We just slash from the last chunk to the most recent one, if need be.
                (None, (0..self.unlocking.len()).rev().collect::<Vec<_>>())
            }
        };

        // Helper to update `target` and the ledgers total after accounting for slashing `target`.
        log!(
            debug,
            "slashing {:?} for era {:?} out of {:?}, priority: {:?}, proportional = {:?}",
            slash_amount,
            slash_era,
            self,
            slash_chunks_priority,
            maybe_proportional,
        );

        let mut slash_out_of = |target: &mut BalanceOf<T>, slash_remaining: &mut BalanceOf<T>| {
            let mut slash_from_target = if let Some(ratio) = maybe_proportional {
                ratio.mul_ceil(*target)
            } else {
                *slash_remaining
            }
            // this is the total that that the slash target has. We can't slash more than
            // this anyhow!
            .min(*target)
            // this is the total amount that we would have wanted to slash
            // non-proportionally, a proportional slash should never exceed this either!
            .min(*slash_remaining);

            // slash out from *target exactly `slash_from_target`.
            *target = *target - slash_from_target;
            if *target < minimum_balance {
                // Slash the rest of the target if it's dust. This might cause the last chunk to be
                // slightly under-slashed, by at most `MaxUnlockingChunks * ED`, which is not a big
                // deal.
                slash_from_target =
                    sp_std::mem::replace(target, Zero::zero()).saturating_add(slash_from_target)
            }

            self.total = self.total.saturating_sub(slash_from_target);
            *slash_remaining = slash_remaining.saturating_sub(slash_from_target);
        };

        // If this is *not* a proportional slash, the active will always wiped to 0.
        slash_out_of(&mut self.active, &mut remaining_slash);

        let mut slashed_unlocking = BTreeMap::<_, _>::new();
        for i in slash_chunks_priority {
            if remaining_slash.is_zero() {
                break;
            }

            if let Some(chunk) = self.unlocking.get_mut(i).defensive() {
                slash_out_of(&mut chunk.value, &mut remaining_slash);
                // write the new slashed value of this chunk to the map.
                slashed_unlocking.insert(chunk.era, chunk.value);
            } else {
                break;
            }
        }

        // clean unlocking chunks that are set to zero.
        self.unlocking.retain(|c| !c.value.is_zero());

        T::OnStakerSlash::on_slash(&self.stash, self.active, &slashed_unlocking);
        pre_slash_total.saturating_sub(self.total)
    }
}

/// A record of the nominations made by a specific account.
#[derive(
    PartialEqNoBound,
    EqNoBound,
    Clone,
    Encode,
    Decode,
    RuntimeDebugNoBound,
    TypeInfo,
    MaxEncodedLen
)]
#[codec(mel_bound())]
#[scale_info(skip_type_params(T))]
pub struct Nominations<T: Config> {
    /// The targets of nomination.
    pub targets: BoundedVec<T::AccountId, T::MaxNominations>,
    /// The era the nominations were submitted.
    ///
    /// Except for initial nominations which are considered submitted at era 0.
    pub submitted_in: EraIndex,
    /// Whether the nominations have been suppressed. This can happen due to slashing of the
    /// validators, or other events that might invalidate the nomination.
    ///
    /// NOTE: this for future proofing and is thus far not used.
    pub suppressed: bool,
}

/// The amount of exposure (to slashing) than an individual nominator has.
#[derive(
    PartialEq,
    Eq,
    PartialOrd,
    Ord,
    Clone,
    Encode,
    Decode,
    RuntimeDebug,
    TypeInfo
)]
pub struct IndividualExposure<AccountId, Balance: HasCompact> {
    /// The stash account of the nominator in question.
    pub who: AccountId,
    /// Amount of funds exposed.
    #[codec(compact)]
    pub value: Balance,
}

/// A snapshot of the stake backing a single validator in the system.
#[derive(
    PartialEq,
    Eq,
    PartialOrd,
    Ord,
    Clone,
    Encode,
    Decode,
    RuntimeDebug,
    TypeInfo
)]
pub struct Exposure<AccountId, Balance: HasCompact> {
    /// The total balance backing this validator.
    #[codec(compact)]
    pub total: Balance,
    /// The validator's own stash that is exposed.
    #[codec(compact)]
    pub own: Balance,
    /// The portions of nominators stashes that are exposed.
    pub others: Vec<IndividualExposure<AccountId, Balance>>,
}

impl<AccountId, Balance: Default + HasCompact> Default for Exposure<AccountId, Balance> {
    fn default() -> Self {
        Self {
            total: Default::default(),
            own: Default::default(),
            others: vec![],
        }
    }
}

/// A pending slash record. The value of the slash has been computed but not applied yet,
/// rather deferred for several eras.
#[derive(Encode, Decode, RuntimeDebug, TypeInfo)]
pub struct UnappliedSlash<AccountId, Balance: HasCompact> {
    /// The stash ID of the offending validator.
    pub validator: AccountId,
    /// The validator's own slash.
    pub own: Balance,
    /// All other slashed stakers and amounts.
    pub others: Vec<(AccountId, Balance)>,
    /// Reporters of the offence; bounty payout recipients.
    pub reporters: Vec<AccountId>,
    /// The amount of payout.
    pub payout: Balance,
}

impl<AccountId, Balance: HasCompact + Zero> UnappliedSlash<AccountId, Balance> {
    /// Initializes the default object using the given `validator`.
    pub fn default_from(validator: AccountId) -> Self {
        Self {
            validator,
            own: Zero::zero(),
            others: vec![],
            reporters: vec![],
            payout: Zero::zero(),
        }
    }
}

/// Means for interacting with a specialized version of the `session` trait.
///
/// This is needed because `Staking` sets the `ValidatorIdOf` of the `pallet_session::Config`
pub trait SessionInterface<AccountId> {
    /// Disable the validator at the given index, returns `false` if the validator was already
    /// disabled or the index is out of bounds.
    fn disable_validator(validator_index: u32) -> bool;
    /// Get the validators from session.
    fn validators() -> Vec<AccountId>;
    /// Prune historical session tries up to but not including the given index.
    fn prune_historical_up_to(up_to: SessionIndex);
}

impl<T: Config> SessionInterface<<T as frame_system::Config>::AccountId> for T
where
    T: pallet_session::Config<ValidatorId = <T as frame_system::Config>::AccountId>,
    T: pallet_session::historical::Config<
        FullIdentification = Exposure<<T as frame_system::Config>::AccountId, BalanceOf<T>>,
        FullIdentificationOf = ExposureOf<T>,
    >,
    T::SessionHandler: pallet_session::SessionHandler<<T as frame_system::Config>::AccountId>,
    T::SessionManager: pallet_session::SessionManager<<T as frame_system::Config>::AccountId>,
    T::ValidatorIdOf: Convert<
        <T as frame_system::Config>::AccountId,
        Option<<T as frame_system::Config>::AccountId>,
    >,
{
    fn disable_validator(validator_index: u32) -> bool {
        <pallet_session::Pallet<T>>::disable_index(validator_index)
    }

    fn validators() -> Vec<<T as frame_system::Config>::AccountId> {
        <pallet_session::Pallet<T>>::validators()
    }

    fn prune_historical_up_to(up_to: SessionIndex) {
        <pallet_session::historical::Pallet<T>>::prune_up_to(up_to);
    }
}

impl<AccountId> SessionInterface<AccountId> for () {
    fn disable_validator(_: u32) -> bool {
        true
    }
    fn validators() -> Vec<AccountId> {
        Vec::new()
    }
    fn prune_historical_up_to(_: SessionIndex) {
        ()
    }
}

/// Handler for determining how much of a balance should be paid out on the current era.
pub trait EraPayout<Balance> {
    /// Determine the payout for this era.
    ///
    /// Returns the amount to be paid to stakers in this era, as well as whatever else should be
    /// paid out ("the rest").
    fn era_payout(
        total_staked: Balance,
        total_issuance: Balance,
        era_duration_millis: u64,
        max_inflated_issuance: Balance,
        non_inflated_yearly_reward: Balance,
    ) -> (Balance, Balance);
}

impl<Balance: Default> EraPayout<Balance> for () {
    fn era_payout(
        _total_staked: Balance,
        _total_issuance: Balance,
        _era_duration_millis: u64,
        _max_inflated_issuance: Balance,
        _non_inflated_yearly_reward: Balance,
    ) -> (Balance, Balance) {
        (Default::default(), Default::default())
    }
}

/// Adaptor to turn a `PiecewiseLinear` curve definition into an `EraPayout` impl, used for
/// backwards compatibility.
pub struct ConvertCurve<T>(sp_std::marker::PhantomData<T>);

impl<Balance: AtLeast32BitUnsigned + Clone, T: Get<&'static PiecewiseLinear<'static>>>
    EraPayout<Balance> for ConvertCurve<T>
{
    fn era_payout(
        total_staked: Balance,
        total_issuance: Balance,
        era_duration_millis: u64,
        max_inflated_issuance: Balance,
        non_inflated_yearly_reward: Balance,
    ) -> (Balance, Balance) {
        let (validator_payout, max_payout) = inflation::compute_total_payout(
            T::get(),
            total_staked,
            total_issuance,
            // Duration of era; more than u64::MAX is rewarded as u64::MAX.
            era_duration_millis,
            max_inflated_issuance,
            non_inflated_yearly_reward,
        );
        let rest = max_payout.saturating_sub(validator_payout.clone());
        (validator_payout, rest)
    }
}

/// Mode of era-forcing.
#[derive(
    Copy,
    Clone,
    PartialEq,
    Eq,
    Encode,
    Decode,
    RuntimeDebug,
    TypeInfo,
    MaxEncodedLen
)]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
pub enum Forcing {
    /// Not forcing anything - just let whatever happen.
    NotForcing,
    /// Force a new era, then reset to `NotForcing` as soon as it is done.
    /// Note that this will force to trigger an election until a new era is triggered, if the
    /// election failed, the next session end will trigger a new election again, until success.
    ForceNew,
    /// Avoid a new era indefinitely.
    ForceNone,
    /// Force a new era at the end of all sessions indefinitely.
    ForceAlways,
}

impl Default for Forcing {
    fn default() -> Self {
        Forcing::NotForcing
    }
}

/// A `Convert` implementation that finds the stash of the given controller account,
/// if any.
pub struct StashOf<T>(sp_std::marker::PhantomData<T>);

impl<T: Config> Convert<T::AccountId, Option<T::AccountId>> for StashOf<T> {
    fn convert(controller: T::AccountId) -> Option<T::AccountId> {
        <Pallet<T>>::ledger(&controller).map(|l| l.stash)
    }
}

/// A typed conversion from stash account ID to the active exposure of nominators
/// on that account.
///
/// Active exposure is the exposure of the validator set currently validating, i.e. in
/// `active_era`. It can differ from the latest planned exposure in `current_era`.
pub struct ExposureOf<T>(sp_std::marker::PhantomData<T>);

impl<T: Config> Convert<T::AccountId, Option<Exposure<T::AccountId, BalanceOf<T>>>>
    for ExposureOf<T>
{
    fn convert(validator: T::AccountId) -> Option<Exposure<T::AccountId, BalanceOf<T>>> {
        <Pallet<T>>::active_era()
            .map(|active_era| <Pallet<T>>::eras_stakers(active_era.index, &validator))
    }
}

/// Filter historical offences out and only allow those from the bonding period.
pub struct FilterHistoricalOffences<T, R> {
    _inner: sp_std::marker::PhantomData<(T, R)>,
}

impl<T, Reporter, Offender, R, O> ReportOffence<Reporter, Offender, O>
    for FilterHistoricalOffences<Pallet<T>, R>
where
    T: Config,
    R: ReportOffence<Reporter, Offender, O>,
    O: Offence<Offender>,
{
    fn report_offence(reporters: Vec<Reporter>, offence: O) -> Result<(), OffenceError> {
        // Disallow any slashing from before the current bonding period.
        let offence_session = offence.session_index();
        let bonded_eras = BondedEras::<T>::get();

        if bonded_eras
            .first()
            .filter(|(_, start)| offence_session >= *start)
            .is_some()
        {
            R::report_offence(reporters, offence)
        } else {
            <Pallet<T>>::deposit_event(Event::<T>::OldSlashingReportDiscarded {
                session_index: offence_session,
            });
            Ok(())
        }
    }

    fn is_known_offence(offenders: &[Offender], time_slot: &O::TimeSlot) -> bool {
        R::is_known_offence(offenders, time_slot)
    }
}

pub struct OnStakerSlashMock<T: Config>(core::marker::PhantomData<T>);

impl<T: Config> sp_staking::OnStakerSlash<T::AccountId, T::CurrencyBalance>
    for OnStakerSlashMock<T>
{
    fn on_slash(
        _pool_account: &T::AccountId,
        _slashed_bonded: T::CurrencyBalance,
        _slashed_chunks: &BTreeMap<EraIndex, T::CurrencyBalance>,
    ) {
    }
}

/// Configurations of the benchmarking of the pallet.
pub trait BenchmarkingConfig {
    /// The maximum number of validators to use.
    type MaxValidators: Get<u32>;
    /// The maximum number of nominators to use.
    type MaxNominators: Get<u32>;
}

/// A mock benchmarking config for pallet-staking.
///
/// Should only be used for testing.
pub struct SampleBenchmarkingConfig;

impl BenchmarkingConfig for SampleBenchmarkingConfig {
    type MaxValidators = frame_support::traits::ConstU32<100>;
    type MaxNominators = frame_support::traits::ConstU32<100>;
}
