use frame_support::{pallet_prelude::*, traits::tokens};
use num_traits::Signed;
pub use pallet::*;
use sp_runtime::{
traits::{
EnsureAdd, EnsureAddAssign, EnsureFixedPointNumber, EnsureInto, EnsureSub, EnsureSubAssign,
One, Saturating, Zero,
},
ArithmeticError, FixedPointNumber, FixedPointOperand,
};
use super::{base, MoveCurrencyError, RewardMechanism};
#[derive(Encode, Decode, TypeInfo, MaxEncodedLen, RuntimeDebug)]
#[cfg_attr(test, derive(PartialEq, Clone))]
pub struct Group<T: Config> {
base: base::Group<T::Balance, T::Rate>,
last_rate: T::Rate,
lost_reward: T::Balance,
distribution_id: T::DistributionId,
}
impl<T: Config> Default for Group<T> {
fn default() -> Self {
Self {
base: base::Group::default(),
last_rate: T::Rate::zero(),
lost_reward: T::Balance::zero(),
distribution_id: T::DistributionId::default(),
}
}
}
impl<T: Config> Group<T> {
fn get_last_rate(&self, currency: &Currency<T>) -> T::Rate {
if self.distribution_id == currency.next_distribution_id {
currency.prev_last_rate
} else {
self.last_rate
}
}
}
#[derive(Encode, Decode, TypeInfo, MaxEncodedLen, RuntimeDebug)]
#[cfg_attr(test, derive(PartialEq, Clone))]
pub struct Account<T: Config> {
base: base::Account<T::Balance, T::IBalance>,
rewarded_stake: T::Balance,
distribution_id: T::DistributionId,
}
impl<T: Config> Default for Account<T> {
fn default() -> Self {
Self {
base: base::Account::default(),
rewarded_stake: T::Balance::zero(),
distribution_id: T::DistributionId::default(),
}
}
}
impl<T: Config> Account<T> {
fn was_distribution(&self, group: &Group<T>, currency: &Currency<T>) -> bool {
if self.base.last_currency_movement as usize == currency.base.rpt_changes.len() {
self.distribution_id != group.distribution_id
} else {
self.distribution_id != currency.prev_distribution_id
|| group.distribution_id != currency.next_distribution_id
}
}
fn get_rewarded_stake(&self, group: &Group<T>, currency: &Currency<T>) -> T::Balance {
if self.was_distribution(group, currency) {
self.base.stake
} else {
self.rewarded_stake
}
}
fn update_rewarded_stake(&mut self, group: &Group<T>, currency: &Currency<T>) {
self.rewarded_stake = self.get_rewarded_stake(group, currency);
self.distribution_id = group.distribution_id;
}
fn last_rewarded_stake(
&self,
group: &Group<T>,
currency: &Currency<T>,
) -> Result<T::Balance, ArithmeticError> {
group
.get_last_rate(currency)
.ensure_mul_int(self.get_rewarded_stake(group, currency))
}
}
#[derive(Encode, Decode, TypeInfo, MaxEncodedLen, RuntimeDebug)]
#[cfg_attr(test, derive(PartialEq, Clone))]
pub struct Currency<T: Config> {
base: base::Currency<T::Balance, T::Rate, T::MaxCurrencyMovements>,
prev_distribution_id: T::DistributionId,
next_distribution_id: T::DistributionId,
prev_last_rate: T::Rate,
}
impl<T: Config> Default for Currency<T> {
fn default() -> Self {
Self {
base: base::Currency::default(),
prev_distribution_id: T::DistributionId::default(),
next_distribution_id: T::DistributionId::default(),
prev_last_rate: T::Rate::default(),
}
}
}
pub struct Mechanism<Balance, IBalance, Rate, MaxCurrencyMovements>(
sp_std::marker::PhantomData<(Balance, IBalance, Rate, MaxCurrencyMovements)>,
);
#[frame_support::pallet]
pub mod pallet {
use frame_support::pallet_prelude::*;
use super::*;
#[pallet::config]
pub trait Config: frame_system::Config {
type DistributionId: PartialEq
+ Copy
+ parity_scale_codec::FullCodec
+ MaxEncodedLen
+ Default
+ TypeInfo
+ One
+ EnsureAdd
+ sp_std::fmt::Debug;
type Balance: tokens::Balance
+ FixedPointOperand
+ TryFrom<Self::IBalance>
+ parity_scale_codec::FullCodec
+ TypeInfo
+ MaxEncodedLen;
type IBalance: FixedPointOperand
+ TryFrom<Self::Balance>
+ parity_scale_codec::FullCodec
+ TypeInfo
+ MaxEncodedLen
+ EnsureAdd
+ EnsureSub
+ Copy
+ Signed
+ sp_std::fmt::Debug
+ Default;
type Rate: FixedPointNumber + parity_scale_codec::FullCodec + TypeInfo + MaxEncodedLen;
type MaxCurrencyMovements: Get<u32> + sp_std::fmt::Debug;
}
#[pallet::pallet]
pub struct Pallet<T>(_);
#[pallet::storage]
pub(super) type LastDistributionId<T: Config> = StorageValue<_, T::DistributionId, ValueQuery>;
impl<T: Config> RewardMechanism for Pallet<T>
where
<T::Rate as FixedPointNumber>::Inner: Signed,
{
type Account = Account<T>;
type Balance = T::Balance;
type Currency = Currency<T>;
type Group = Group<T>;
type MaxCurrencyMovements = T::MaxCurrencyMovements;
fn is_ready(group: &Self::Group) -> bool {
group.base.total_stake > Self::Balance::zero()
}
fn reward_group(
group: &mut Self::Group,
amount: Self::Balance,
) -> Result<Self::Balance, DispatchError> {
let mut reward_used = Self::Balance::zero();
if group.base.total_stake > Self::Balance::zero() {
let reward = amount.ensure_add(group.lost_reward)?;
base::Mechanism::<T::Balance, T::IBalance, T::Rate, T::MaxCurrencyMovements>::reward_group(
&mut group.base,
reward,
)?;
group.lost_reward = T::Balance::zero();
group.last_rate = T::Rate::ensure_from_rational(reward, group.base.total_stake)?;
reward_used = reward
}
group.distribution_id = LastDistributionId::<T>::try_mutate(
|distribution_id| -> Result<T::DistributionId, DispatchError> {
distribution_id.ensure_add_assign(One::one())?;
Ok(*distribution_id)
},
)?;
Ok(reward_used)
}
fn deposit_stake(
account: &mut Self::Account,
currency: &mut Self::Currency,
group: &mut Self::Group,
amount: Self::Balance,
) -> DispatchResult {
account.update_rewarded_stake(group, currency);
base::Mechanism::deposit_stake(
&mut account.base,
&mut currency.base,
&mut group.base,
amount,
)
}
fn withdraw_stake(
account: &mut Self::Account,
currency: &mut Self::Currency,
group: &mut Self::Group,
amount: Self::Balance,
) -> DispatchResult {
account.update_rewarded_stake(group, currency);
let rewarded_amount = {
let unrewarded_stake = account.base.stake.saturating_sub(account.rewarded_stake);
let unrewarded_amount = amount.min(unrewarded_stake);
amount.ensure_sub(unrewarded_amount)
}?;
base::Mechanism::withdraw_stake(
&mut account.base,
&mut currency.base,
&mut group.base,
amount,
)?;
let lost_reward = group
.get_last_rate(currency)
.ensure_mul_int(rewarded_amount)?;
account.rewarded_stake.ensure_sub_assign(rewarded_amount)?;
account
.base
.reward_tally
.ensure_add_assign(lost_reward.ensure_into()?)?;
group.lost_reward.ensure_add_assign(lost_reward)?;
Ok(())
}
fn compute_reward(
account: &Self::Account,
currency: &Self::Currency,
group: &Self::Group,
) -> Result<Self::Balance, DispatchError> {
base::Mechanism::compute_reward(&account.base, ¤cy.base, &group.base)?
.ensure_sub(account.last_rewarded_stake(group, currency)?)
.map_err(|e| e.into())
}
fn claim_reward(
account: &mut Self::Account,
currency: &Self::Currency,
group: &Self::Group,
) -> Result<Self::Balance, DispatchError> {
let last_rewarded_stake = account.last_rewarded_stake(group, currency)?;
let reward =
base::Mechanism::claim_reward(&mut account.base, ¤cy.base, &group.base)?
.ensure_sub(last_rewarded_stake)?;
account
.base
.reward_tally
.ensure_sub_assign(last_rewarded_stake.ensure_into()?)?;
Ok(reward)
}
fn move_currency(
currency: &mut Self::Currency,
from_group: &mut Self::Group,
to_group: &mut Self::Group,
) -> Result<(), MoveCurrencyError> {
base::Mechanism::<_, T::IBalance, _, _>::move_currency(
&mut currency.base,
&mut from_group.base,
&mut to_group.base,
)?;
if currency.next_distribution_id != from_group.distribution_id {
currency.prev_distribution_id = from_group.distribution_id;
currency.prev_last_rate = from_group.last_rate;
}
currency.next_distribution_id = to_group.distribution_id;
Ok(())
}
fn account_stake(account: &Self::Account) -> Self::Balance {
account.base.stake
}
fn group_stake(group: &Self::Group) -> Self::Balance {
group.base.total_stake
}
}
}