use cfg_traits::{
self, data::DataRegistry, interest::InterestRate, IntoSeconds, Seconds, TimeAsSecs,
};
use cfg_types::adjustments::Adjustment;
use frame_support::{self, ensure, pallet_prelude::RuntimeDebug, RuntimeDebugNoBound};
use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
use scale_info::TypeInfo;
use sp_runtime::{
traits::{EnsureAdd, EnsureFixedPointNumber, EnsureSub, Zero},
ArithmeticError, DispatchError, DispatchResult, FixedPointNumber,
};
use sp_std::{cmp::min, collections::btree_map::BTreeMap};
use crate::{
entities::interest::ActiveInterestRate,
pallet::{Config, Error},
PriceOf,
};
#[derive(Encode, Decode, Clone, PartialEq, Eq, TypeInfo, RuntimeDebugNoBound, MaxEncodedLen)]
#[scale_info(skip_type_params(T))]
pub struct ExternalAmount<T: Config> {
pub quantity: T::Quantity,
pub settlement_price: T::Balance,
}
impl<T: Config> ExternalAmount<T> {
pub fn new(quantity: T::Quantity, price: T::Balance) -> Self {
Self {
quantity,
settlement_price: price,
}
}
pub fn empty() -> Self {
Self {
quantity: T::Quantity::zero(),
settlement_price: T::Balance::zero(),
}
}
pub fn balance(&self) -> Result<T::Balance, ArithmeticError> {
self.quantity.ensure_mul_int(self.settlement_price)
}
}
#[derive(Encode, Decode, Clone, PartialEq, Eq, TypeInfo, RuntimeDebug, MaxEncodedLen)]
pub enum MaxBorrowAmount<Quantity> {
NoLimit,
Quantity(Quantity),
}
#[derive(Encode, Decode, Clone, PartialEq, Eq, TypeInfo, RuntimeDebugNoBound, MaxEncodedLen)]
#[scale_info(skip_type_params(T))]
pub struct ExternalPricing<T: Config> {
pub price_id: T::PriceId,
pub max_borrow_amount: MaxBorrowAmount<T::Quantity>,
pub notional: T::Balance,
pub max_price_variation: T::Rate,
pub with_linear_pricing: bool,
}
impl<T: Config> ExternalPricing<T> {
pub fn validate(&self) -> DispatchResult {
Ok(())
}
}
#[derive(Encode, Decode, Clone, PartialEq, Eq, TypeInfo, RuntimeDebugNoBound, MaxEncodedLen)]
#[scale_info(skip_type_params(T))]
pub struct ExternalActivePricing<T: Config> {
info: ExternalPricing<T>,
outstanding_quantity: T::Quantity,
pub interest: ActiveInterestRate<T>,
latest_settlement_price: T::Balance,
settlement_price_updated: Seconds,
}
impl<T: Config> ExternalActivePricing<T> {
pub fn activate(
info: ExternalPricing<T>,
interest_rate: InterestRate<T::Rate>,
pool_id: T::PoolId,
amount: ExternalAmount<T>,
price_required: bool,
) -> Result<Self, DispatchError> {
let result = T::PriceRegistry::register_id(&info.price_id, &pool_id);
if price_required {
result?;
}
Ok(Self {
info,
outstanding_quantity: T::Quantity::zero(),
interest: ActiveInterestRate::activate(interest_rate)?,
latest_settlement_price: amount.settlement_price,
settlement_price_updated: T::Time::now(),
})
}
pub fn deactivate(
self,
pool_id: T::PoolId,
) -> Result<(ExternalPricing<T>, InterestRate<T::Rate>), DispatchError> {
T::PriceRegistry::unregister_id(&self.info.price_id, &pool_id)?;
Ok((self.info, self.interest.deactivate()?))
}
pub fn price_id(&self) -> T::PriceId {
self.info.price_id
}
pub fn has_registered_price(&self, pool_id: T::PoolId) -> bool {
T::PriceRegistry::get(&self.info.price_id, &pool_id).is_ok()
}
pub fn last_updated(&self, pool_id: T::PoolId) -> Seconds {
match T::PriceRegistry::get(&self.info.price_id, &pool_id) {
Ok((_, timestamp)) => timestamp.into_seconds(),
Err(_) => self.settlement_price_updated,
}
}
fn maybe_with_linear_accrual_price(
&self,
maturity: Option<Seconds>,
price: T::Balance,
price_last_updated: Seconds,
) -> Result<T::Balance, DispatchError> {
if let (Some(maturity), true) = (maturity, self.info.with_linear_pricing) {
if min(price_last_updated, maturity) == maturity {
return Ok(self.info.notional);
}
return Ok(cfg_utils::math::y_coord_in_rect(
(min(price_last_updated, maturity), price),
(maturity, self.info.notional),
min(T::Time::now(), maturity),
)?);
}
Ok(price)
}
pub fn current_price(
&self,
pool_id: T::PoolId,
maturity: Option<Seconds>,
) -> Result<T::Balance, DispatchError> {
self.current_price_inner(
maturity,
T::PriceRegistry::get(&self.info.price_id, &pool_id).ok(),
)
}
fn current_price_inner(
&self,
maturity: Option<Seconds>,
oracle: Option<PriceOf<T>>,
) -> Result<T::Balance, DispatchError> {
if let Some((oracle_price, oracle_provided_at)) = oracle {
self.maybe_with_linear_accrual_price(
maturity,
oracle_price,
oracle_provided_at.into_seconds(),
)
} else {
self.maybe_with_linear_accrual_price(
maturity,
self.latest_settlement_price,
self.settlement_price_updated,
)
}
}
pub fn outstanding_notional_principal(&self) -> Result<T::Balance, DispatchError> {
Ok(self
.outstanding_quantity
.ensure_mul_int(self.info.notional)?)
}
pub fn outstanding_priced_principal(
&self,
pool_id: T::PoolId,
maturity: Option<Seconds>,
) -> Result<T::Balance, DispatchError> {
let price = self.current_price(pool_id, maturity)?;
Ok(self.outstanding_quantity.ensure_mul_int(price)?)
}
pub fn outstanding_interest(&self) -> Result<T::Balance, DispatchError> {
let debt = self.interest.current_debt()?;
Ok(debt.ensure_sub(self.outstanding_notional_principal()?)?)
}
pub fn present_value(
&self,
pool_id: T::PoolId,
maturity: Option<Seconds>,
) -> Result<T::Balance, DispatchError> {
self.outstanding_priced_principal(pool_id, maturity)
}
pub fn present_value_cached(
&self,
cache: &BTreeMap<T::PriceId, PriceOf<T>>,
maturity: Option<Seconds>,
) -> Result<T::Balance, DispatchError> {
let price = self.current_price_inner(maturity, cache.get(&self.info.price_id).copied())?;
Ok(self.outstanding_quantity.ensure_mul_int(price)?)
}
fn validate_amount(
&self,
amount: &ExternalAmount<T>,
pool_id: T::PoolId,
) -> Result<(), DispatchError> {
match T::PriceRegistry::get(&self.info.price_id, &pool_id) {
Ok(data) => {
let price = data.0;
let delta = if amount.settlement_price > price {
amount.settlement_price.ensure_sub(price)?
} else {
price.ensure_sub(amount.settlement_price)?
};
let variation = T::Rate::checked_from_rational(delta, price)
.ok_or(ArithmeticError::Overflow)?;
ensure!(
variation <= self.info.max_price_variation || amount.quantity.is_zero(),
Error::<T>::SettlementPriceExceedsVariation
);
Ok(())
}
Err(_) => Ok(()),
}
}
pub fn max_borrow_amount(
&self,
amount: ExternalAmount<T>,
pool_id: T::PoolId,
) -> Result<T::Balance, DispatchError> {
self.validate_amount(&amount, pool_id)?;
match self.info.max_borrow_amount {
MaxBorrowAmount::Quantity(quantity) => {
let available = quantity.ensure_sub(self.outstanding_quantity)?;
Ok(available.ensure_mul_int(amount.settlement_price)?)
}
MaxBorrowAmount::NoLimit => Ok(amount.balance()?),
}
}
pub fn max_repay_principal(
&self,
amount: ExternalAmount<T>,
pool_id: T::PoolId,
) -> Result<T::Balance, DispatchError> {
self.validate_amount(&amount, pool_id)?;
Ok(self
.outstanding_quantity
.ensure_mul_int(amount.settlement_price)?)
}
pub fn adjust(
&mut self,
amount_adj: Adjustment<ExternalAmount<T>>,
interest: T::Balance,
) -> DispatchResult {
self.outstanding_quantity = amount_adj
.clone()
.map(|amount| amount.quantity)
.ensure_add(self.outstanding_quantity)?;
let interest_adj = amount_adj.clone().try_map(|amount| {
amount
.quantity
.ensure_mul_int(self.info.notional)?
.ensure_add(interest)
})?;
self.interest.adjust_debt(interest_adj)?;
self.latest_settlement_price = amount_adj.abs().settlement_price;
self.settlement_price_updated = T::Time::now();
Ok(())
}
}
pub mod v3 {
use cfg_traits::Seconds;
use parity_scale_codec::{Decode, Encode};
use crate::{
entities::{
interest::ActiveInterestRate,
pricing::{external::MaxBorrowAmount, internal, internal::InternalActivePricing},
},
Config,
};
#[derive(Encode, Decode)]
pub enum Pricing<T: Config> {
Internal(internal::InternalPricing<T>),
External(ExternalPricing<T>),
}
impl<T: Config> Pricing<T> {
pub fn migrate(self, with_linear_pricing: bool) -> crate::entities::pricing::Pricing<T> {
match self {
Pricing::Internal(i) => crate::entities::pricing::Pricing::Internal(i),
Pricing::External(e) => {
crate::entities::pricing::Pricing::External(e.migrate(with_linear_pricing))
}
}
}
}
#[derive(Encode, Decode)]
pub struct ExternalPricing<T: Config> {
pub price_id: T::PriceId,
pub max_borrow_amount: MaxBorrowAmount<T::Quantity>,
pub notional: T::Balance,
pub max_price_variation: T::Rate,
}
#[derive(Encode, Decode)]
pub enum ActivePricing<T: Config> {
Internal(InternalActivePricing<T>),
External(ExternalActivePricing<T>),
}
impl<T: Config> ActivePricing<T> {
pub fn migrate(
self,
with_linear_pricing: bool,
) -> crate::entities::pricing::ActivePricing<T> {
match self {
ActivePricing::Internal(i) => crate::entities::pricing::ActivePricing::Internal(i),
ActivePricing::External(e) => crate::entities::pricing::ActivePricing::External(
e.migrate(with_linear_pricing),
),
}
}
}
#[derive(Encode, Decode)]
pub struct ExternalActivePricing<T: Config> {
info: ExternalPricing<T>,
outstanding_quantity: T::Quantity,
pub interest: ActiveInterestRate<T>,
latest_settlement_price: T::Balance,
settlement_price_updated: Seconds,
}
impl<T: Config> ExternalActivePricing<T> {
pub fn migrate(self, with_linear_pricing: bool) -> super::ExternalActivePricing<T> {
super::ExternalActivePricing {
info: self.info.migrate(with_linear_pricing),
outstanding_quantity: self.outstanding_quantity,
interest: self.interest,
latest_settlement_price: self.latest_settlement_price,
settlement_price_updated: self.settlement_price_updated,
}
}
}
impl<T: Config> ExternalPricing<T> {
pub fn migrate(self, with_linear_pricing: bool) -> super::ExternalPricing<T> {
super::ExternalPricing {
price_id: self.price_id,
max_borrow_amount: self.max_borrow_amount,
notional: self.notional,
max_price_variation: self.max_price_variation,
with_linear_pricing,
}
}
}
}