use cfg_traits::Seconds;
use cfg_types::{epoch::EpochState, pools::TrancheMetadata};
pub use changes::PoolChangeProposal;
use frame_support::{
dispatch::DispatchResult,
pallet_prelude::{DispatchError, RuntimeDebug},
traits::Get,
BoundedVec,
};
use orml_traits::Change;
use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
use scale_info::TypeInfo;
use sp_arithmetic::traits::{BaseArithmetic, Unsigned};
use sp_runtime::{
traits::{AtLeast32BitUnsigned, One, Zero},
FixedPointNumber, FixedPointOperand, TypeId,
};
use sp_std::{cmp::PartialEq, vec::Vec};
use crate::tranches::{
EpochExecutionTranches, TrancheEssence, TrancheInput, TrancheSolution, TrancheUpdate, Tranches,
};
impl<PoolId> TypeId for PoolLocator<PoolId> {
const TYPE_ID: [u8; 4] = *b"pool";
}
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
pub struct ReserveDetails<Balance> {
pub max: Balance,
pub total: Balance,
pub available: Balance,
}
impl<Balance> ReserveDetails<Balance>
where
Balance: AtLeast32BitUnsigned + Copy + From<u64>,
{
pub fn deposit_from_epoch<BalanceRatio, Weight, TrancheCurrency, MaxExecutionTranches>(
&mut self,
epoch_tranches: &EpochExecutionTranches<
Balance,
BalanceRatio,
Weight,
TrancheCurrency,
MaxExecutionTranches,
>,
solution: &[TrancheSolution],
) -> DispatchResult
where
Weight: Copy + From<u128>,
BalanceRatio: Copy,
MaxExecutionTranches: Get<u32>,
{
let executed_amounts = epoch_tranches.fulfillment_cash_flows(solution)?;
let mut acc_investments = Balance::zero();
let mut acc_redemptions = Balance::zero();
for &(invest, redeem) in executed_amounts.iter() {
acc_investments.ensure_add_assign(invest)?;
acc_redemptions.ensure_add_assign(redeem)?;
}
self.total = self
.total
.ensure_add(acc_investments)?
.ensure_sub(acc_redemptions)?;
Ok(())
}
}
#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
pub struct ScheduledUpdateDetails<Rate, StringLimit, MaxTranches>
where
StringLimit: Get<u32>,
MaxTranches: Get<u32>,
{
pub changes: PoolChanges<Rate, StringLimit, MaxTranches>,
pub submitted_at: Seconds,
}
#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo)]
pub struct PoolLocator<PoolId> {
pub pool_id: PoolId,
}
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
pub struct PoolDetails<
CurrencyId,
TrancheCurrency,
EpochId,
Balance,
Rate,
Weight,
TrancheId,
PoolId,
MaxTranches,
> where
Rate: FixedPointNumber<Inner = Balance>,
Balance: FixedPointOperand + sp_arithmetic::MultiplyRational,
MaxTranches: Get<u32>,
TrancheCurrency: Into<CurrencyId>,
{
pub currency: CurrencyId,
pub tranches: Tranches<Balance, Rate, Weight, TrancheCurrency, TrancheId, PoolId, MaxTranches>,
pub parameters: PoolParameters,
pub status: PoolStatus,
pub epoch: EpochState<EpochId>,
pub reserve: ReserveDetails<Balance>,
}
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
pub enum PoolStatus {
Open,
}
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
pub struct PoolParameters {
pub min_epoch_time: Seconds,
pub max_nav_age: Seconds,
}
#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
pub struct PoolChanges<Rate, StringLimit, MaxTranches>
where
StringLimit: Get<u32>,
MaxTranches: Get<u32>,
{
pub tranches: Change<BoundedVec<TrancheUpdate<Rate>, MaxTranches>>,
pub tranche_metadata: Change<BoundedVec<TrancheMetadata<StringLimit>, MaxTranches>>,
pub min_epoch_time: Change<Seconds>,
pub max_nav_age: Change<Seconds>,
}
#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug, Default, TypeInfo, MaxEncodedLen)]
pub struct PoolDepositInfo<AccountId, Balance> {
pub depositor: AccountId,
pub deposit: Balance,
}
#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo)]
pub struct PoolEssence<CurrencyId, Balance, TrancheCurrency, Rate, StringLimit>
where
CurrencyId: Copy,
StringLimit: Get<u32>,
TrancheCurrency: Into<CurrencyId>,
{
pub currency: CurrencyId,
pub max_reserve: Balance,
pub max_nav_age: Seconds,
pub min_epoch_time: Seconds,
pub tranches: Vec<TrancheEssence<TrancheCurrency, Rate, StringLimit>>,
}
impl<
CurrencyId,
TrancheCurrency,
EpochId,
Balance,
Rate,
Weight,
TrancheId,
PoolId,
MaxTranches,
>
PoolDetails<
CurrencyId,
TrancheCurrency,
EpochId,
Balance,
Rate,
Weight,
TrancheId,
PoolId,
MaxTranches,
>
where
Balance:
FixedPointOperand + BaseArithmetic + Unsigned + From<u64> + sp_arithmetic::MultiplyRational,
CurrencyId: Copy,
TrancheCurrency: Into<CurrencyId>,
EpochId: BaseArithmetic + Copy,
PoolId: Copy + Encode,
Rate: FixedPointNumber<Inner = Balance>,
TrancheCurrency: Copy + cfg_traits::investments::TrancheCurrency<PoolId, TrancheId>,
TrancheId: Clone + From<[u8; 16]> + PartialEq,
Weight: Copy + From<u128>,
MaxTranches: Get<u32>,
{
pub fn start_next_epoch(&mut self, now: Seconds) -> DispatchResult {
self.epoch.current.ensure_add_assign(One::one())?;
self.epoch.last_closed = now;
self.reserve.available = Zero::zero();
Ok(())
}
pub fn execute_previous_epoch(&mut self) -> DispatchResult {
self.reserve.available = self.reserve.total;
self.epoch
.last_executed
.ensure_add_assign(One::one())
.map_err(Into::into)
}
pub fn essence_from_registry<AssetRegistry, AssetId, StringLimit>(
&self,
) -> Result<PoolEssence<CurrencyId, Balance, TrancheCurrency, Rate, StringLimit>, DispatchError>
where
AssetRegistry:
orml_traits::asset_registry::Inspect<StringLimit = StringLimit, AssetId = CurrencyId>,
StringLimit: Get<u32>,
{
let mut ids: Vec<TrancheCurrency> = Vec::new();
let mut tranche_input: Vec<TrancheInput<Rate, StringLimit>> = Vec::new();
for tranche in self.tranches.residual_top_slice().iter() {
let metadata = AssetRegistry::metadata(
&<AssetRegistry as orml_traits::asset_registry::Inspect>::AssetId::from(
tranche.currency.into(),
),
)
.ok_or(DispatchError::CannotLookup)?;
ids.push(tranche.currency);
tranche_input.push(TrancheInput {
tranche_type: tranche.tranche_type,
seniority: Some(tranche.seniority),
metadata: TrancheMetadata {
token_name: metadata.name,
token_symbol: metadata.symbol,
},
});
}
Self::essence_from_tranche_input::<StringLimit>(self, ids, tranche_input)
}
pub fn essence_from_tranche_input<StringLimit>(
&self,
ids: Vec<TrancheCurrency>,
tranche_input: Vec<TrancheInput<Rate, StringLimit>>,
) -> Result<PoolEssence<CurrencyId, Balance, TrancheCurrency, Rate, StringLimit>, DispatchError>
where
StringLimit: Get<u32>,
TrancheCurrency: Into<CurrencyId>,
{
let mut tranche_essence: Vec<TrancheEssence<TrancheCurrency, Rate, StringLimit>> =
Vec::new();
for (id, input) in ids.into_iter().zip(tranche_input) {
tranche_essence.push(TrancheEssence {
currency: id,
ty: input.tranche_type,
metadata: TrancheMetadata {
token_name: input.metadata.token_name,
token_symbol: input.metadata.token_symbol,
},
});
}
Ok(PoolEssence {
currency: self.currency,
max_reserve: self.reserve.max,
max_nav_age: self.parameters.max_nav_age,
min_epoch_time: self.parameters.min_epoch_time,
tranches: tranche_essence,
})
}
}
pub mod changes {
use frame_support::storage::bounded_btree_set::BoundedBTreeSet;
use sp_std::collections::btree_set::BTreeSet;
use strum::EnumCount;
use super::*;
#[derive(
Encode,
Decode,
Clone,
PartialEq,
Eq,
PartialOrd,
Ord,
TypeInfo,
RuntimeDebug,
MaxEncodedLen,
EnumCount,
)]
pub enum Requirement {
DelayTime(u32),
NextEpoch,
BlockedByLockedRedemptions,
}
#[derive(Encode, Decode, Clone, Eq, PartialOrd, Ord, TypeInfo, RuntimeDebug, MaxEncodedLen)]
pub struct UniqueRequirement(pub Requirement);
impl PartialEq for UniqueRequirement {
fn eq(&self, other: &Self) -> bool {
match self.0 {
Requirement::DelayTime(_) => {
matches!(other.0, Requirement::DelayTime(_))
}
Requirement::NextEpoch => {
matches!(other.0, Requirement::NextEpoch)
}
Requirement::BlockedByLockedRedemptions => {
matches!(other.0, Requirement::BlockedByLockedRedemptions)
}
}
}
}
impl From<Requirement> for UniqueRequirement {
fn from(value: Requirement) -> Self {
UniqueRequirement(value)
}
}
pub struct MaxRequirements;
impl Get<u32> for MaxRequirements {
fn get() -> u32 {
Requirement::COUNT as u32
}
}
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
pub struct PoolChangeProposal {
pub requirements: BoundedBTreeSet<UniqueRequirement, MaxRequirements>,
}
impl PoolChangeProposal {
pub fn new(requirements: impl IntoIterator<Item = Requirement>) -> Self {
Self {
requirements: BTreeSet::from_iter(requirements.into_iter().map(UniqueRequirement))
.try_into()
.expect(
"Cannot exist more unique requirements in a set than `MaxRequirements`, qed",
),
}
}
pub fn requirements(&self) -> impl Iterator<Item = Requirement> + '_ {
self.requirements.iter().cloned().map(|req| req.0)
}
}
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
pub struct NotedPoolChange<ChangeProposal: Into<PoolChangeProposal>> {
pub submitted_time: Seconds,
pub change: ChangeProposal,
}
}