#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
use crate::currency_to_vote::CurrencyToVote;
use alloc::{collections::btree_map::BTreeMap, vec, vec::Vec};
use codec::{Decode, DecodeWithMemTracking, Encode, FullCodec, HasCompact, MaxEncodedLen};
use core::ops::{Add, AddAssign, Sub, SubAssign};
use scale_info::TypeInfo;
use sp_runtime::{
traits::{AtLeast32BitUnsigned, Zero},
Debug, DispatchError, DispatchResult, Perbill, Saturating,
};
pub mod offence;
pub mod currency_to_vote;
pub type SessionIndex = u32;
pub type EraIndex = u32;
pub type Page = u32;
#[derive(Clone, Debug)]
pub enum StakingAccount<AccountId> {
Stash(AccountId),
Controller(AccountId),
}
#[cfg(feature = "std")]
impl<AccountId> From<AccountId> for StakingAccount<AccountId> {
fn from(account: AccountId) -> Self {
StakingAccount::Stash(account)
}
}
#[derive(Debug, TypeInfo)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize, PartialEq, Eq, Clone))]
pub enum StakerStatus<AccountId> {
Idle,
Validator,
Nominator(Vec<AccountId>),
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
pub struct Stake<Balance> {
pub total: Balance,
pub active: Balance,
}
#[impl_trait_for_tuples::impl_for_tuples(10)]
pub trait OnStakingUpdate<AccountId, Balance> {
fn on_stake_update(_who: &AccountId, _prev_stake: Option<Stake<Balance>>) {}
fn on_nominator_add(_who: &AccountId) {}
fn on_nominator_update(_who: &AccountId, _prev_nominations: Vec<AccountId>) {}
fn on_nominator_remove(_who: &AccountId, _nominations: Vec<AccountId>) {}
fn on_validator_add(_who: &AccountId) {}
fn on_validator_update(_who: &AccountId) {}
fn on_validator_remove(_who: &AccountId) {}
fn on_unstake(_who: &AccountId) {}
fn on_slash(
_stash: &AccountId,
_slashed_active: Balance,
_slashed_unlocking: &BTreeMap<EraIndex, Balance>,
_slashed_total: Balance,
) {
}
fn on_withdraw(_stash: &AccountId, _amount: Balance) {}
}
pub trait StakingInterface {
type Balance: Sub<Output = Self::Balance>
+ Ord
+ PartialEq
+ Default
+ Copy
+ MaxEncodedLen
+ FullCodec
+ TypeInfo
+ Saturating;
type AccountId: Clone + core::fmt::Debug;
type CurrencyToVote: CurrencyToVote<Self::Balance>;
fn minimum_nominator_bond() -> Self::Balance;
fn minimum_validator_bond() -> Self::Balance;
fn stash_by_ctrl(controller: &Self::AccountId) -> Result<Self::AccountId, DispatchError>;
fn bonding_duration() -> EraIndex;
fn nominator_bonding_duration() -> EraIndex {
Self::bonding_duration()
}
fn current_era() -> EraIndex;
fn stake(who: &Self::AccountId) -> Result<Stake<Self::Balance>, DispatchError>;
fn total_stake(who: &Self::AccountId) -> Result<Self::Balance, DispatchError> {
Self::stake(who).map(|s| s.total)
}
fn active_stake(who: &Self::AccountId) -> Result<Self::Balance, DispatchError> {
Self::stake(who).map(|s| s.active)
}
fn is_unbonding(who: &Self::AccountId) -> Result<bool, DispatchError> {
Self::stake(who).map(|s| s.active != s.total)
}
fn fully_unbond(who: &Self::AccountId) -> DispatchResult {
Self::unbond(who, Self::stake(who)?.active)
}
fn bond(who: &Self::AccountId, value: Self::Balance, payee: &Self::AccountId)
-> DispatchResult;
fn nominate(who: &Self::AccountId, validators: Vec<Self::AccountId>) -> DispatchResult;
fn chill(who: &Self::AccountId) -> DispatchResult;
fn bond_extra(who: &Self::AccountId, extra: Self::Balance) -> DispatchResult;
fn unbond(stash: &Self::AccountId, value: Self::Balance) -> DispatchResult;
fn set_payee(stash: &Self::AccountId, reward_acc: &Self::AccountId) -> DispatchResult;
fn withdraw_unbonded(
stash: Self::AccountId,
num_slashing_spans: u32,
) -> Result<bool, DispatchError>;
fn desired_validator_count() -> u32;
fn election_ongoing() -> bool;
fn force_unstake(who: Self::AccountId) -> DispatchResult;
fn is_exposed_in_era(who: &Self::AccountId, era: &EraIndex) -> bool;
fn status(who: &Self::AccountId) -> Result<StakerStatus<Self::AccountId>, DispatchError>;
fn is_validator(who: &Self::AccountId) -> bool {
Self::status(who).map(|s| matches!(s, StakerStatus::Validator)).unwrap_or(false)
}
fn is_virtual_staker(who: &Self::AccountId) -> bool;
fn nominations(who: &Self::AccountId) -> Option<Vec<Self::AccountId>> {
match Self::status(who) {
Ok(StakerStatus::Nominator(t)) => Some(t),
_ => None,
}
}
fn slash_reward_fraction() -> Perbill;
#[cfg(feature = "runtime-benchmarks")]
fn max_exposure_page_size() -> Page;
#[cfg(feature = "runtime-benchmarks")]
fn add_era_stakers(
current_era: &EraIndex,
stash: &Self::AccountId,
exposures: Vec<(Self::AccountId, Self::Balance)>,
);
#[cfg(any(feature = "std", feature = "runtime-benchmarks"))]
fn set_era(era: EraIndex);
}
pub trait StakingUnchecked: StakingInterface {
fn migrate_to_virtual_staker(who: &Self::AccountId) -> DispatchResult;
fn virtual_bond(
keyless_who: &Self::AccountId,
value: Self::Balance,
payee: &Self::AccountId,
) -> DispatchResult;
#[cfg(feature = "runtime-benchmarks")]
fn migrate_to_direct_staker(who: &Self::AccountId);
}
#[derive(
PartialEq,
Eq,
PartialOrd,
Ord,
Clone,
Encode,
Decode,
DecodeWithMemTracking,
Debug,
TypeInfo,
Copy,
)]
pub struct IndividualExposure<AccountId, Balance: HasCompact> {
pub who: AccountId,
#[codec(compact)]
pub value: Balance,
}
#[derive(
PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, DecodeWithMemTracking, Debug, TypeInfo,
)]
pub struct Exposure<AccountId, Balance: HasCompact> {
#[codec(compact)]
pub total: Balance,
#[codec(compact)]
pub own: Balance,
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![] }
}
}
impl<
AccountId: Clone,
Balance: HasCompact + AtLeast32BitUnsigned + Copy + codec::MaxEncodedLen,
> Exposure<AccountId, Balance>
{
pub fn split_others(&mut self, n_others: u32) -> Self {
let head_others: Vec<_> =
self.others.drain(..(n_others as usize).min(self.others.len())).collect();
let total_others_head: Balance = head_others
.iter()
.fold(Zero::zero(), |acc: Balance, o| acc.saturating_add(o.value));
self.total = self.total.saturating_sub(total_others_head);
Self {
total: total_others_head.saturating_add(self.own),
own: self.own,
others: head_others,
}
}
pub fn into_pages(
self,
page_size: Page,
) -> (PagedExposureMetadata<Balance>, Vec<ExposurePage<AccountId, Balance>>) {
let individual_chunks = self.others.chunks(page_size as usize);
let mut exposure_pages: Vec<ExposurePage<AccountId, Balance>> =
Vec::with_capacity(individual_chunks.len());
for chunk in individual_chunks {
let mut page_total: Balance = Zero::zero();
let mut others: Vec<IndividualExposure<AccountId, Balance>> =
Vec::with_capacity(chunk.len());
for individual in chunk.iter() {
page_total.saturating_accrue(individual.value);
others.push(IndividualExposure {
who: individual.who.clone(),
value: individual.value,
})
}
exposure_pages.push(ExposurePage { page_total, others });
}
(
PagedExposureMetadata {
total: self.total,
own: self.own,
nominator_count: self.others.len() as u32,
page_count: exposure_pages.len() as Page,
},
exposure_pages,
)
}
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, Debug, TypeInfo)]
pub struct ExposurePage<AccountId, Balance: HasCompact> {
#[codec(compact)]
pub page_total: Balance,
pub others: Vec<IndividualExposure<AccountId, Balance>>,
}
impl<A, B: Default + HasCompact> Default for ExposurePage<A, B> {
fn default() -> Self {
ExposurePage { page_total: Default::default(), others: vec![] }
}
}
impl<A, B: HasCompact + Default + AddAssign + SubAssign + Clone> From<Vec<IndividualExposure<A, B>>>
for ExposurePage<A, B>
{
fn from(exposures: Vec<IndividualExposure<A, B>>) -> Self {
exposures.into_iter().fold(ExposurePage::default(), |mut page, e| {
page.page_total += e.value.clone();
page.others.push(e);
page
})
}
}
#[derive(
PartialEq,
Eq,
PartialOrd,
Ord,
Clone,
Encode,
Decode,
Debug,
TypeInfo,
Default,
MaxEncodedLen,
Copy,
)]
pub struct PagedExposureMetadata<Balance: HasCompact + codec::MaxEncodedLen> {
#[codec(compact)]
pub total: Balance,
#[codec(compact)]
pub own: Balance,
pub nominator_count: u32,
pub page_count: Page,
}
impl<Balance> PagedExposureMetadata<Balance>
where
Balance: HasCompact
+ codec::MaxEncodedLen
+ Add<Output = Balance>
+ Sub<Output = Balance>
+ sp_runtime::Saturating
+ PartialEq
+ Copy
+ sp_runtime::traits::Debug,
{
pub fn update_with<Max: sp_core::Get<u32>>(
self,
others_balance: Balance,
others_num: u32,
) -> Self {
let page_limit = Max::get().max(1);
let new_nominator_count = self.nominator_count.saturating_add(others_num);
let new_page_count = new_nominator_count
.saturating_add(page_limit)
.saturating_sub(1)
.saturating_div(page_limit);
Self {
total: self.total.saturating_add(others_balance),
own: self.own,
nominator_count: new_nominator_count,
page_count: new_page_count,
}
}
}
#[derive(Clone, Debug)]
pub struct Agent<T>(T);
impl<T> From<T> for Agent<T> {
fn from(acc: T) -> Self {
Agent(acc)
}
}
impl<T> Agent<T> {
pub fn get(self) -> T {
self.0
}
}
#[derive(Clone, Debug)]
pub struct Delegator<T>(T);
impl<T> From<T> for Delegator<T> {
fn from(acc: T) -> Self {
Delegator(acc)
}
}
impl<T> Delegator<T> {
pub fn get(self) -> T {
self.0
}
}
pub trait DelegationInterface {
type Balance: Sub<Output = Self::Balance>
+ Ord
+ PartialEq
+ Default
+ Copy
+ MaxEncodedLen
+ FullCodec
+ TypeInfo
+ Saturating;
type AccountId: Clone + core::fmt::Debug;
fn agent_balance(agent: Agent<Self::AccountId>) -> Option<Self::Balance>;
fn agent_transferable_balance(agent: Agent<Self::AccountId>) -> Option<Self::Balance>;
fn delegator_balance(delegator: Delegator<Self::AccountId>) -> Option<Self::Balance>;
fn register_agent(
agent: Agent<Self::AccountId>,
reward_account: &Self::AccountId,
) -> DispatchResult;
fn remove_agent(agent: Agent<Self::AccountId>) -> DispatchResult;
fn delegate(
delegator: Delegator<Self::AccountId>,
agent: Agent<Self::AccountId>,
amount: Self::Balance,
) -> DispatchResult;
fn withdraw_delegation(
delegator: Delegator<Self::AccountId>,
agent: Agent<Self::AccountId>,
amount: Self::Balance,
num_slashing_spans: u32,
) -> DispatchResult;
fn pending_slash(agent: Agent<Self::AccountId>) -> Option<Self::Balance>;
fn delegator_slash(
agent: Agent<Self::AccountId>,
delegator: Delegator<Self::AccountId>,
value: Self::Balance,
maybe_reporter: Option<Self::AccountId>,
) -> DispatchResult;
}
pub trait DelegationMigrator {
type Balance: Sub<Output = Self::Balance>
+ Ord
+ PartialEq
+ Default
+ Copy
+ MaxEncodedLen
+ FullCodec
+ TypeInfo
+ Saturating;
type AccountId: Clone + core::fmt::Debug;
fn migrate_nominator_to_agent(
agent: Agent<Self::AccountId>,
reward_account: &Self::AccountId,
) -> DispatchResult;
fn migrate_delegation(
agent: Agent<Self::AccountId>,
delegator: Delegator<Self::AccountId>,
value: Self::Balance,
) -> DispatchResult;
#[cfg(feature = "runtime-benchmarks")]
fn force_kill_agent(agent: Agent<Self::AccountId>);
}
sp_core::generate_feature_enabled_macro!(runtime_benchmarks_enabled, feature = "runtime-benchmarks", $);
sp_core::generate_feature_enabled_macro!(std_or_benchmarks_enabled, any(feature = "std", feature = "runtime-benchmarks"), $);
#[cfg(test)]
mod tests {
use sp_core::ConstU32;
use super::*;
#[test]
fn update_with_works() {
let metadata = PagedExposureMetadata::<u32> {
total: 1000,
own: 0, nominator_count: 10,
page_count: 1,
};
assert_eq!(
metadata.update_with::<ConstU32<10>>(1, 1),
PagedExposureMetadata { total: 1001, own: 0, nominator_count: 11, page_count: 2 },
);
assert_eq!(
metadata.update_with::<ConstU32<5>>(1, 1),
PagedExposureMetadata { total: 1001, own: 0, nominator_count: 11, page_count: 3 },
);
assert_eq!(
metadata.update_with::<ConstU32<4>>(1, 1),
PagedExposureMetadata { total: 1001, own: 0, nominator_count: 11, page_count: 3 },
);
assert_eq!(
metadata.update_with::<ConstU32<1>>(1, 1),
PagedExposureMetadata { total: 1001, own: 0, nominator_count: 11, page_count: 11 },
);
}
#[test]
fn individual_exposures_to_exposure_works() {
let exposure_1 = IndividualExposure { who: 1, value: 10u32 };
let exposure_2 = IndividualExposure { who: 2, value: 20 };
let exposure_3 = IndividualExposure { who: 3, value: 30 };
let exposure_page: ExposurePage<u32, u32> = vec![exposure_1, exposure_2, exposure_3].into();
assert_eq!(
exposure_page,
ExposurePage { page_total: 60, others: vec![exposure_1, exposure_2, exposure_3] },
);
}
#[test]
fn empty_individual_exposures_to_exposure_works() {
let empty_exposures: Vec<IndividualExposure<u32, u32>> = vec![];
let exposure_page: ExposurePage<u32, u32> = empty_exposures.into();
assert_eq!(exposure_page, ExposurePage { page_total: 0, others: vec![] });
}
#[test]
fn exposure_split_others_works() {
let exposure = Exposure {
total: 100,
own: 20,
others: vec![
IndividualExposure { who: 1, value: 20u32 },
IndividualExposure { who: 2, value: 20 },
IndividualExposure { who: 3, value: 20 },
IndividualExposure { who: 4, value: 20 },
],
};
let mut exposure_0 = exposure.clone();
let split_exposure = exposure_0.split_others(0);
assert_eq!(exposure_0, exposure);
assert_eq!(split_exposure, Exposure { total: 20, own: 20, others: vec![] });
let mut exposure_1 = exposure.clone();
let split_exposure = exposure_1.split_others(1);
assert_eq!(exposure_1.own, 20);
assert_eq!(exposure_1.total, 20 + 3 * 20);
assert_eq!(exposure_1.others.len(), 3);
assert_eq!(split_exposure.own, 20);
assert_eq!(split_exposure.total, 20 + 1 * 20);
assert_eq!(split_exposure.others.len(), 1);
let mut exposure_3 = exposure.clone();
let split_exposure = exposure_3.split_others(3);
assert_eq!(exposure_3.own, 20);
assert_eq!(exposure_3.total, 20 + 1 * 20);
assert_eq!(exposure_3.others.len(), 1);
assert_eq!(split_exposure.own, 20);
assert_eq!(split_exposure.total, 20 + 3 * 20);
assert_eq!(split_exposure.others.len(), 3);
let mut exposure_max = exposure.clone();
let split_exposure = exposure_max.split_others(u32::MAX);
assert_eq!(split_exposure, exposure);
assert_eq!(exposure_max, Exposure { total: 20, own: 20, others: vec![] });
}
}