use cfg_traits::{
interest::{InterestRate, RateCollection},
Seconds, TimeAsSecs,
};
use cfg_types::adjustments::Adjustment;
use frame_support::{
ensure,
pallet_prelude::{DispatchResult, RuntimeDebug},
RuntimeDebugNoBound,
};
use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
use scale_info::TypeInfo;
use sp_arithmetic::traits::Saturating;
use sp_runtime::{
traits::{EnsureFixedPointNumber, EnsureSub},
DispatchError,
};
use crate::{
entities::{changes::InternalMutation, interest::ActiveInterestRate},
pallet::{Config, Error},
types::{
valuation::{DiscountedCashFlow, ValuationMethod},
CreateLoanError, MutationError,
},
};
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
pub enum MaxBorrowAmount<Rate> {
UpToTotalBorrowed { advance_rate: Rate },
UpToOutstandingDebt { advance_rate: Rate },
}
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebugNoBound, TypeInfo, MaxEncodedLen)]
#[scale_info(skip_type_params(T))]
pub struct InternalPricing<T: Config> {
pub collateral_value: T::Balance,
pub valuation_method: ValuationMethod<T::Rate>,
pub max_borrow_amount: MaxBorrowAmount<T::Rate>,
}
impl<T: Config> InternalPricing<T> {
pub fn validate(&self) -> DispatchResult {
ensure!(
self.valuation_method.is_valid(),
Error::<T>::from(CreateLoanError::InvalidValuationMethod)
);
Ok(())
}
}
#[derive(Encode, Decode, Clone, PartialEq, Eq, TypeInfo, RuntimeDebugNoBound, MaxEncodedLen)]
#[scale_info(skip_type_params(T))]
pub struct InternalActivePricing<T: Config> {
info: InternalPricing<T>,
pub interest: ActiveInterestRate<T>,
}
impl<T: Config> InternalActivePricing<T> {
pub fn activate(
info: InternalPricing<T>,
interest_rate: InterestRate<T::Rate>,
) -> Result<Self, DispatchError> {
Ok(Self {
info,
interest: ActiveInterestRate::activate(interest_rate)?,
})
}
pub fn deactivate(self) -> Result<(InternalPricing<T>, InterestRate<T::Rate>), DispatchError> {
Ok((self.info, self.interest.deactivate()?))
}
fn compute_present_value(
&self,
debt: T::Balance,
origination_date: Seconds,
maturity_date: Option<Seconds>,
) -> Result<T::Balance, DispatchError> {
match &self.info.valuation_method {
ValuationMethod::DiscountedCashFlow(dcf) => {
let maturity_date =
maturity_date.ok_or(Error::<T>::MaturityDateNeededForValuationMethod)?;
let now = T::Time::now();
Ok(dcf.compute_present_value(
debt,
now,
self.interest.rate(),
maturity_date,
origination_date,
)?)
}
ValuationMethod::OutstandingDebt | ValuationMethod::Cash => Ok(debt),
}
}
pub fn present_value(
&self,
origination_date: Seconds,
maturity_date: Option<Seconds>,
) -> Result<T::Balance, DispatchError> {
let debt = self.interest.current_debt()?;
self.compute_present_value(debt, origination_date, maturity_date)
}
pub fn present_value_cached<Rates>(
&self,
cache: &Rates,
origination_date: Seconds,
maturity_date: Option<Seconds>,
) -> Result<T::Balance, DispatchError>
where
Rates: RateCollection<T::Rate, T::Balance, T::Balance>,
{
let debt = self.interest.current_debt_cached(cache)?;
self.compute_present_value(debt, origination_date, maturity_date)
}
pub fn outstanding_interest(
&self,
outstanding_principal: T::Balance,
) -> Result<T::Balance, DispatchError> {
let debt = self.interest.current_debt()?;
Ok(debt.ensure_sub(outstanding_principal)?)
}
pub fn max_borrow_amount(
&self,
total_borrowed: T::Balance,
) -> Result<T::Balance, DispatchError> {
Ok(match self.info.max_borrow_amount {
MaxBorrowAmount::UpToTotalBorrowed { advance_rate } => advance_rate
.ensure_mul_int(self.info.collateral_value)?
.saturating_sub(total_borrowed),
MaxBorrowAmount::UpToOutstandingDebt { advance_rate } => advance_rate
.ensure_mul_int(self.info.collateral_value)?
.saturating_sub(self.interest.current_debt()?),
})
}
pub fn adjust(&mut self, adjustment: Adjustment<T::Balance>) -> DispatchResult {
self.interest.adjust_debt(adjustment)
}
fn mut_dcf(&mut self) -> Result<&mut DiscountedCashFlow<T::Rate>, DispatchError> {
match &mut self.info.valuation_method {
ValuationMethod::DiscountedCashFlow(dcf) => Ok(dcf),
_ => Err(Error::<T>::from(MutationError::DiscountedCashFlowExpected).into()),
}
}
pub fn mutate_with(&mut self, mutation: InternalMutation<T::Rate>) -> DispatchResult {
match mutation {
InternalMutation::ValuationMethod(method) => self.info.valuation_method = method,
InternalMutation::ProbabilityOfDefault(rate) => {
self.mut_dcf()?.probability_of_default = rate;
}
InternalMutation::LossGivenDefault(rate) => self.mut_dcf()?.loss_given_default = rate,
InternalMutation::DiscountRate(rate) => self.mut_dcf()?.discount_rate = rate,
}
self.info.validate()
}
}