use super::*;
use common_primitives::capacity::RewardEra;
use frame_support::{
pallet_prelude::PhantomData, BoundedVec, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound,
};
use parity_scale_codec::{Decode, Encode, EncodeLike, MaxEncodedLen};
use scale_info::TypeInfo;
use sp_runtime::{
traits::{AtLeast32BitUnsigned, CheckedAdd, CheckedSub, Get, Saturating, Zero},
BoundedBTreeMap, RuntimeDebug,
};
#[cfg(any(feature = "runtime-benchmarks", test))]
use sp_std::vec::Vec;
pub const STAKED_PERCENTAGE_TO_BOOST: u32 = 50;
#[derive(
Clone, Copy, Debug, Decode, Encode, TypeInfo, Eq, MaxEncodedLen, PartialEq, PartialOrd,
)]
pub enum StakingType {
MaximumCapacity,
ProviderBoost,
}
#[derive(
TypeInfo, RuntimeDebugNoBound, PartialEqNoBound, EqNoBound, Clone, Decode, Encode, MaxEncodedLen,
)]
#[scale_info(skip_type_params(T))]
pub struct StakingDetails<T: Config> {
pub active: BalanceOf<T>,
pub staking_type: StakingType,
}
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)]
pub struct UnlockChunk<Balance, EpochNumber> {
pub value: Balance,
pub thaw_at: EpochNumber,
}
impl<T: Config> StakingDetails<T> {
pub fn deposit(&mut self, amount: BalanceOf<T>) -> Option<()> {
self.active = amount.checked_add(&self.active)?;
Some(())
}
pub fn withdraw(&mut self, amount: BalanceOf<T>) -> Result<BalanceOf<T>, DispatchError> {
let current_active = self.active;
let mut new_active = self.active.saturating_sub(amount);
let mut actual_unstaked: BalanceOf<T> = amount;
if new_active.le(&T::MinimumStakingAmount::get()) {
actual_unstaked = current_active;
new_active = Zero::zero();
}
self.active = new_active;
Ok(actual_unstaked)
}
}
impl<T: Config> Default for StakingDetails<T> {
fn default() -> Self {
Self { active: Zero::zero(), staking_type: StakingType::MaximumCapacity }
}
}
#[derive(Default, PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)]
pub struct StakingTargetDetails<Balance>
where
Balance: Default + Saturating + Copy + CheckedAdd + CheckedSub,
{
pub amount: Balance,
pub capacity: Balance,
}
impl<Balance: Default + Saturating + Copy + CheckedAdd + CheckedSub + PartialOrd>
StakingTargetDetails<Balance>
{
pub fn deposit(&mut self, amount: Balance, capacity: Balance) -> Option<()> {
self.amount = amount.checked_add(&self.amount)?;
self.capacity = capacity.checked_add(&self.capacity)?;
Some(())
}
pub fn withdraw(
&mut self,
amount: Balance,
capacity: Balance,
minimum: Balance,
) -> (Balance, Balance) {
let entire_amount = self.amount;
let entire_capacity = self.capacity;
self.amount = self.amount.saturating_sub(amount);
if self.amount.lt(&minimum) {
*self = Self::default();
return (entire_amount, entire_capacity);
} else {
self.capacity = self.capacity.saturating_sub(capacity);
}
(amount, capacity)
}
}
#[derive(PartialEq, Eq, Clone, Default, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)]
pub struct CapacityDetails<Balance, EpochNumber> {
pub remaining_capacity: Balance,
pub total_tokens_staked: Balance,
pub total_capacity_issued: Balance,
pub last_replenished_epoch: EpochNumber,
}
impl<Balance, EpochNumber> CapacityDetails<Balance, EpochNumber>
where
Balance: Saturating + Copy + CheckedAdd + CheckedSub,
EpochNumber: Clone + PartialOrd + PartialEq,
{
pub fn deposit(&mut self, amount: &Balance, capacity: &Balance) -> Option<()> {
self.total_tokens_staked = amount.checked_add(&self.total_tokens_staked)?;
self.remaining_capacity = capacity.checked_add(&self.remaining_capacity)?;
self.total_capacity_issued = capacity.checked_add(&self.total_capacity_issued)?;
Some(())
}
pub fn can_replenish(&self, current_epoch: EpochNumber) -> bool {
self.last_replenished_epoch.lt(¤t_epoch)
}
pub fn replenish_all(&mut self, current_epoch: &EpochNumber) {
self.remaining_capacity = self.total_capacity_issued;
self.last_replenished_epoch = current_epoch.clone();
}
pub fn replenish_by_amount(&mut self, amount: Balance, current_epoch: &EpochNumber) {
self.remaining_capacity = amount.saturating_add(self.remaining_capacity);
self.last_replenished_epoch = current_epoch.clone();
}
pub fn deduct_capacity_by_amount(&mut self, amount: Balance) -> Result<(), ArithmeticError> {
let new_remaining =
self.remaining_capacity.checked_sub(&amount).ok_or(ArithmeticError::Underflow)?;
self.remaining_capacity = new_remaining;
Ok(())
}
pub fn withdraw(&mut self, capacity_deduction: Balance, tokens_staked_deduction: Balance) {
self.total_tokens_staked = self.total_tokens_staked.saturating_sub(tokens_staked_deduction);
self.total_capacity_issued = self.total_capacity_issued.saturating_sub(capacity_deduction);
self.remaining_capacity = self.remaining_capacity.saturating_sub(capacity_deduction);
}
}
#[derive(
PartialEq, Eq, Clone, Default, PartialOrd, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen,
)]
pub struct EpochInfo<BlockNumber> {
pub epoch_start: BlockNumber,
}
pub type UnlockChunkList<T> = BoundedVec<
UnlockChunk<BalanceOf<T>, <T as Config>::EpochNumber>,
<T as Config>::MaxUnlockingChunks,
>;
pub fn unlock_chunks_total<T: Config>(unlock_chunks: &UnlockChunkList<T>) -> BalanceOf<T> {
unlock_chunks
.iter()
.fold(Zero::zero(), |acc: BalanceOf<T>, chunk| acc.saturating_add(chunk.value))
}
pub fn unlock_chunks_reap_thawed<T: Config>(
unlock_chunks: &mut UnlockChunkList<T>,
current_epoch: <T>::EpochNumber,
) -> BalanceOf<T> {
let mut total_reaped: BalanceOf<T> = 0u32.into();
unlock_chunks.retain(|chunk| {
if current_epoch.ge(&chunk.thaw_at) {
total_reaped = total_reaped.saturating_add(chunk.value);
false
} else {
true
}
});
total_reaped
}
#[cfg(any(feature = "runtime-benchmarks", test))]
#[allow(clippy::unwrap_used)]
pub fn unlock_chunks_from_vec<T: Config>(chunks: &Vec<(u32, u32)>) -> UnlockChunkList<T> {
let result: Vec<UnlockChunk<BalanceOf<T>, <T>::EpochNumber>> = chunks
.into_iter()
.map(|chunk| UnlockChunk { value: chunk.0.into(), thaw_at: chunk.1.into() })
.collect();
BoundedVec::try_from(result).unwrap()
}
#[derive(
PartialEq,
Eq,
Clone,
Copy,
Default,
PartialOrd,
Encode,
Decode,
RuntimeDebug,
TypeInfo,
MaxEncodedLen,
)]
pub struct RewardEraInfo<RewardEra, BlockNumber>
where
RewardEra: AtLeast32BitUnsigned + EncodeLike,
BlockNumber: AtLeast32BitUnsigned + EncodeLike,
{
pub era_index: RewardEra,
pub started_at: BlockNumber,
}
#[derive(PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)]
#[scale_info(skip_type_params(T))]
pub struct RewardPoolHistoryChunk<T: Config>(
BoundedBTreeMap<RewardEra, BalanceOf<T>, T::RewardPoolChunkLength>,
);
impl<T: Config> Default for RewardPoolHistoryChunk<T> {
fn default() -> Self {
Self::new()
}
}
impl<T: Config> Clone for RewardPoolHistoryChunk<T> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<T: Config> RewardPoolHistoryChunk<T> {
pub fn new() -> Self {
RewardPoolHistoryChunk(BoundedBTreeMap::new())
}
pub fn total_for_era(&self, reward_era: &RewardEra) -> Option<&BalanceOf<T>> {
self.0.get(reward_era)
}
#[cfg(test)]
pub fn era_range(&self) -> (RewardEra, RewardEra) {
let zero_reward_era: RewardEra = Zero::zero();
let zero_balance: BalanceOf<T> = Zero::zero();
let (first, _vf) = self.0.first_key_value().unwrap_or((&zero_reward_era, &zero_balance));
let (last, _vl) = self.0.last_key_value().unwrap_or((&zero_reward_era, &zero_balance));
(*first, *last)
}
pub fn try_insert(
&mut self,
reward_era: RewardEra,
total: BalanceOf<T>,
) -> Result<Option<BalanceOf<T>>, (RewardEra, BalanceOf<T>)> {
self.0.try_insert(reward_era, total)
}
#[cfg(test)]
pub fn earliest_era(&self) -> Option<&RewardEra> {
if let Some((first_era, _first_total)) = self.0.first_key_value() {
return Some(first_era);
}
None
}
pub fn is_full(&self) -> bool {
self.0.len().eq(&(T::RewardPoolChunkLength::get() as usize))
}
}
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)]
#[scale_info(skip_type_params(T))]
pub struct ProviderBoostHistory<T: Config>(
BoundedBTreeMap<RewardEra, BalanceOf<T>, T::ProviderBoostHistoryLimit>,
);
impl<T: Config> Default for ProviderBoostHistory<T> {
fn default() -> Self {
Self::new()
}
}
impl<T: Config> ProviderBoostHistory<T> {
pub fn new() -> Self {
ProviderBoostHistory(BoundedBTreeMap::new())
}
pub fn add_era_balance(
&mut self,
reward_era: &RewardEra,
add_amount: &BalanceOf<T>,
) -> Option<usize> {
if let Some(entry) = self.0.get_mut(&reward_era) {
*entry = entry.saturating_add(*add_amount);
} else {
self.remove_oldest_entry_if_full(); let current_staking_amount = self.get_last_staking_amount();
if self
.0
.try_insert(*reward_era, current_staking_amount.saturating_add(*add_amount))
.is_err()
{
return None;
};
}
Some(self.count())
}
pub fn subtract_era_balance(
&mut self,
reward_era: &RewardEra,
subtract_amount: &BalanceOf<T>,
) -> Option<usize> {
if self.count().is_zero() {
return None;
};
let current_staking_amount = self.get_last_staking_amount();
if current_staking_amount.eq(subtract_amount) && self.count().eq(&1usize) {
return Some(0usize);
}
if let Some(entry) = self.0.get_mut(reward_era) {
*entry = entry.saturating_sub(*subtract_amount);
} else {
self.remove_oldest_entry_if_full();
if self
.0
.try_insert(*reward_era, current_staking_amount.saturating_sub(*subtract_amount))
.is_err()
{
return None;
}
}
Some(self.count())
}
pub(crate) fn get_entry_for_era(&self, reward_era: &RewardEra) -> Option<&BalanceOf<T>> {
self.0.get(reward_era)
}
pub(crate) fn get_amount_staked_for_era(&self, reward_era: &RewardEra) -> BalanceOf<T> {
let mut bmap_iter = self.0.iter();
let mut eligible_amount: BalanceOf<T> = Zero::zero();
while let Some((era, balance)) = bmap_iter.next() {
if era.eq(reward_era) {
return *balance;
}
else if era.gt(reward_era) {
return eligible_amount;
} eligible_amount = *balance;
}
eligible_amount
}
pub fn count(&self) -> usize {
self.0.len()
}
fn remove_oldest_entry_if_full(&mut self) {
if self.is_full() {
if let Some((earliest_key, _earliest_val)) = self.0.first_key_value() {
self.0.remove(&earliest_key.clone());
}
}
}
fn get_last_staking_amount(&self) -> BalanceOf<T> {
if let Some((_last_key, last_value)) = self.0.last_key_value() {
return *last_value;
};
Zero::zero()
}
fn is_full(&self) -> bool {
self.count().eq(&(T::ProviderBoostHistoryLimit::get() as usize))
}
}
#[derive(Debug, TypeInfo, PartialEqNoBound, EqNoBound, Clone, Decode, Encode, MaxEncodedLen)]
#[scale_info(skip_type_params(T))]
pub struct RetargetInfo<T: Config> {
pub retarget_count: u32,
pub last_retarget_at: RewardEra,
_marker: PhantomData<T>,
}
impl<T: Config> Default for RetargetInfo<T> {
fn default() -> Self {
Self { retarget_count: 0u32, last_retarget_at: Zero::zero(), _marker: Default::default() }
}
}
impl<T: Config> RetargetInfo<T> {
pub fn new(retarget_count: u32, last_retarget_at: RewardEra) -> Self {
Self { retarget_count, last_retarget_at, _marker: Default::default() }
}
pub fn update(&mut self, current_era: RewardEra) -> Option<()> {
let max_retargets = T::MaxRetargetsPerRewardEra::get();
if self.retarget_count.ge(&max_retargets) && self.last_retarget_at.eq(¤t_era) {
return None;
}
if self.last_retarget_at.lt(¤t_era) {
self.last_retarget_at = current_era;
self.retarget_count = 1;
} else {
self.retarget_count = self.retarget_count.saturating_add(1u32);
}
Some(())
}
}
pub trait ProviderBoostRewardsProvider<T: Config> {
type Balance;
fn reward_pool_size(total_staked: BalanceOf<T>) -> BalanceOf<T>;
fn era_staking_reward(
era_amount_staked: BalanceOf<T>, era_total_staked: BalanceOf<T>, era_reward_pool_size: BalanceOf<T>, ) -> BalanceOf<T>;
fn capacity_boost(amount: BalanceOf<T>) -> BalanceOf<T>;
}