use cfg_traits::{
fee::{FeeAmountProration, PoolFeeBucket},
Seconds,
};
use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
use scale_info::TypeInfo;
use sp_arithmetic::FixedPointOperand;
use sp_runtime::{traits::Get, BoundedVec, RuntimeDebug};
use sp_std::vec::Vec;
use crate::fixed_point::FixedPointNumberExtension;
#[derive(Debug, Encode, PartialEq, Eq, Decode, Clone, TypeInfo, MaxEncodedLen)]
pub struct TrancheMetadata<StringLimit: Get<u32>> {
pub token_name: BoundedVec<u8, StringLimit>,
pub token_symbol: BoundedVec<u8, StringLimit>,
}
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
pub struct PoolMetadata<MetaSize>
where
MetaSize: Get<u32>,
{
pub metadata: BoundedVec<u8, MetaSize>,
}
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
pub enum PoolRegistrationStatus {
Registered,
Unregistered,
}
#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
pub struct PoolNav<Balance> {
pub nav_aum: Balance,
pub nav_fees: Balance,
pub reserve: Balance,
pub total: Balance,
}
#[derive(Debug, Encode, Decode, TypeInfo, MaxEncodedLen, PartialEq, Eq, Clone)]
pub struct PoolFee<AccountId, FeeId, FeeAmounts> {
pub destination: AccountId,
pub editor: PoolFeeEditor<AccountId>,
pub amounts: FeeAmounts,
pub id: FeeId,
}
#[derive(Debug, Encode, Decode, TypeInfo, MaxEncodedLen, PartialEq, Eq, Clone)]
pub struct PoolFeeInfo<AccountId, Balance, Rate> {
pub destination: AccountId,
pub editor: PoolFeeEditor<AccountId>,
pub fee_type: PoolFeeType<Balance, Rate>,
}
impl<AccountId, Balance, FeeId, Rate> PoolFee<AccountId, FeeId, PoolFeeAmounts<Balance, Rate>>
where
Balance: Default,
{
pub fn from_info(fee: PoolFeeInfo<AccountId, Balance, Rate>, fee_id: FeeId) -> Self {
let payable = match fee.fee_type {
PoolFeeType::ChargedUpTo { .. } => PayableFeeAmount::UpTo(Balance::default()),
PoolFeeType::Fixed { .. } => PayableFeeAmount::AllPending,
};
let amount = PoolFeeAmounts {
payable,
fee_type: fee.fee_type,
pending: Balance::default(),
disbursement: Balance::default(),
};
Self {
amounts: amount,
destination: fee.destination,
editor: fee.editor,
id: fee_id,
}
}
}
#[derive(Debug, Encode, Decode, TypeInfo, MaxEncodedLen, PartialEq, Eq, Clone)]
pub enum PoolFeeEditor<AccountId> {
Root,
Account(AccountId),
}
impl<AccountId> From<PoolFeeEditor<AccountId>> for Option<AccountId> {
fn from(editor: PoolFeeEditor<AccountId>) -> Option<AccountId> {
match editor {
PoolFeeEditor::Account(acc) => Some(acc),
_ => None,
}
}
}
#[derive(Debug, Encode, Decode, TypeInfo, MaxEncodedLen, PartialEq, Eq, Clone)]
pub enum PoolFeeType<Balance, Rate> {
Fixed { limit: PoolFeeAmount<Balance, Rate> },
ChargedUpTo { limit: PoolFeeAmount<Balance, Rate> },
}
#[derive(Debug, Encode, Decode, TypeInfo, MaxEncodedLen, PartialEq, Eq, Clone)]
pub struct PoolFeeAmounts<Balance, Rate> {
pub fee_type: PoolFeeType<Balance, Rate>,
pub pending: Balance,
pub disbursement: Balance,
pub payable: PayableFeeAmount<Balance>,
}
#[derive(Debug, Encode, Decode, TypeInfo, MaxEncodedLen, PartialEq, Eq, Clone)]
pub enum PayableFeeAmount<Balance> {
AllPending,
UpTo(Balance),
}
impl<Balance, Rate> PoolFeeAmounts<Balance, Rate> {
pub fn limit(&self) -> &PoolFeeAmount<Balance, Rate> {
match &self.fee_type {
PoolFeeType::Fixed { limit } | PoolFeeType::ChargedUpTo { limit } => limit,
}
}
}
#[derive(Debug, Encode, Decode, TypeInfo, MaxEncodedLen, PartialEq, Eq, Clone)]
pub enum PoolFeeAmount<Balance, Rate> {
ShareOfPortfolioValuation(Rate),
AmountPerSecond(Balance),
}
impl<Balance, Rate> FeeAmountProration<Balance, Rate, Seconds> for PoolFeeAmount<Balance, Rate>
where
Rate: FixedPointNumberExtension,
Balance: From<Seconds> + FixedPointOperand + sp_std::ops::Div<Output = Balance>,
{
fn saturated_prorated_amount(&self, portfolio_valuation: Balance, period: Seconds) -> Balance {
match self {
PoolFeeAmount::ShareOfPortfolioValuation(_) => {
let proration: Rate =
<Self as FeeAmountProration<Balance, Rate, Seconds>>::saturated_prorated_rate(
self,
portfolio_valuation,
period,
);
proration.saturating_mul_int(portfolio_valuation)
}
PoolFeeAmount::AmountPerSecond(amount) => amount.saturating_mul(period.into()),
}
}
fn saturated_prorated_rate(&self, portfolio_valuation: Balance, period: Seconds) -> Rate {
match self {
PoolFeeAmount::ShareOfPortfolioValuation(rate) => {
saturated_rate_proration(*rate, period)
}
PoolFeeAmount::AmountPerSecond(_) => {
let prorated_amount: Balance = <Self as FeeAmountProration<
Balance,
Rate,
Seconds,
>>::saturated_prorated_amount(
self, portfolio_valuation, period
);
Rate::saturating_from_rational(prorated_amount, portfolio_valuation)
}
}
}
}
pub fn saturated_balance_proration<
Balance: From<Seconds> + FixedPointOperand + sp_std::ops::Div<Output = Balance>,
>(
annual_amount: Balance,
period: Seconds,
) -> Balance {
let amount = annual_amount.saturating_mul(period.into());
amount.div(cfg_primitives::SECONDS_PER_YEAR.into())
}
pub fn saturated_rate_proration<Rate: FixedPointNumberExtension>(
annual_rate: Rate,
period: Seconds,
) -> Rate {
let rate = annual_rate.saturating_mul(Rate::saturating_from_integer::<u64>(period));
rate.saturating_div_ceil(&Rate::saturating_from_integer::<u64>(
cfg_primitives::SECONDS_PER_YEAR,
))
}
#[derive(Decode, Encode, TypeInfo)]
pub struct PoolFeesOfBucket<FeeId, AccountId, Balance, Rate> {
pub bucket: PoolFeeBucket,
pub fees: Vec<PoolFee<AccountId, FeeId, PoolFeeAmounts<Balance, Rate>>>,
}
pub type PoolFeesList<FeeId, AccountId, Balance, Rate> =
Vec<PoolFeesOfBucket<FeeId, AccountId, Balance, Rate>>;
#[cfg(test)]
mod tests {
use super::*;
mod saturated_proration {
use cfg_primitives::{CFG, DAYS, SECONDS_PER_YEAR};
use sp_arithmetic::{
traits::{One, Zero},
FixedPointNumber,
};
use super::*;
use crate::fixed_point::{Quantity, Rate};
type Balance = u128;
#[test]
fn balance_zero() {
assert_eq!(
saturated_balance_proration::<Balance>(SECONDS_PER_YEAR.into(), 0),
0
);
assert_eq!(
saturated_balance_proration::<Balance>(0u128, SECONDS_PER_YEAR),
0
);
assert_eq!(
saturated_balance_proration::<Balance>((SECONDS_PER_YEAR - 1).into(), 1),
0
);
assert_eq!(
saturated_balance_proration::<Balance>(1u128, SECONDS_PER_YEAR - 1),
0
);
}
#[test]
fn balance_one() {
assert_eq!(
saturated_balance_proration::<Balance>(SECONDS_PER_YEAR.into(), 1),
1u128
);
assert_eq!(
saturated_balance_proration::<Balance>(1u128, SECONDS_PER_YEAR),
1u128
);
}
#[test]
fn balance_overflow() {
assert_eq!(
saturated_balance_proration::<Balance>(u128::MAX, u64::MAX),
u128::MAX / u128::from(SECONDS_PER_YEAR)
);
}
#[test]
fn rate_zero() {
assert_eq!(
saturated_rate_proration::<Rate>(Rate::from_integer(SECONDS_PER_YEAR.into()), 0),
Rate::zero()
);
assert_eq!(
saturated_rate_proration::<Rate>(Rate::zero(), SECONDS_PER_YEAR),
Rate::zero()
);
assert!(
saturated_rate_proration::<Rate>(
Rate::from_integer((SECONDS_PER_YEAR - 1).into()),
1
) > Rate::zero()
);
assert!(
saturated_rate_proration::<Rate>(Rate::one(), SECONDS_PER_YEAR - 1) > Rate::zero()
);
}
#[test]
fn rate_one() {
assert_eq!(
saturated_rate_proration::<Rate>(Rate::from_integer(SECONDS_PER_YEAR.into()), 1),
Rate::one()
);
assert_eq!(
saturated_rate_proration::<Rate>(Rate::one(), SECONDS_PER_YEAR),
Rate::one()
);
}
#[test]
fn rate_overflow() {
let left_bound = Rate::from_integer(10790);
let right_bound = Rate::from_integer(10791);
let rate = saturated_rate_proration::<Rate>(
Rate::from_integer(u128::from(u128::MAX / 10u128.pow(27))),
1,
);
assert!(left_bound < rate);
assert!(rate < right_bound);
assert!(saturated_rate_proration::<Rate>(Rate::one(), u64::MAX) > left_bound);
assert!(saturated_rate_proration::<Rate>(Rate::one(), u64::MAX) < right_bound);
assert!(saturated_rate_proration::<Rate>(Rate::from_integer(2), u64::MAX) > left_bound);
assert!(
saturated_rate_proration::<Rate>(Rate::from_integer(2), u64::MAX) < right_bound
);
}
#[test]
fn precision_quantity_vs_rate() {
let period = (DAYS / 4) as Seconds;
let nav_multiplier = 1_000_000;
let nav = nav_multiplier * CFG;
let q_proration = saturated_rate_proration::<Quantity>(
Quantity::checked_from_rational(1, 100).unwrap(),
period,
);
let r_proration = saturated_rate_proration::<Rate>(
Rate::checked_from_rational(1, 100).unwrap(),
period,
);
let q_amount = q_proration.saturating_mul_int(nav);
let r_amount = r_proration.saturating_mul_int(nav);
let r_amount_rounded_up = (r_amount / (nav_multiplier) + 1) * (nav_multiplier);
assert_eq!(q_amount, r_amount_rounded_up);
}
}
}