Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into feat/loans/no-maturit…
Browse files Browse the repository at this point in the history
…y-date
  • Loading branch information
mustermeiszer committed May 24, 2024
2 parents 88939a5 + 1570c34 commit b579add
Show file tree
Hide file tree
Showing 25 changed files with 822 additions and 93 deletions.
3 changes: 2 additions & 1 deletion pallets/loans/src/entities/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::{
entities::pricing::external::ExternalAmount,
pallet::{Config, Error},
types::RepaidAmount,
PriceOf,
};

#[derive(Encode, Decode, Clone, PartialEq, Eq, TypeInfo, RuntimeDebugNoBound, MaxEncodedLen)]
Expand Down Expand Up @@ -61,6 +62,6 @@ impl<T: Config> RepaidInput<T> {
#[scale_info(skip_type_params(T))]
pub enum PriceCollectionInput<T: Config> {
Empty,
Custom(BoundedBTreeMap<T::PriceId, T::Balance, T::MaxActiveLoansPerPool>),
Custom(BoundedBTreeMap<T::PriceId, PriceOf<T>, T::MaxActiveLoansPerPool>),
FromRegistry,
}
101 changes: 100 additions & 1 deletion pallets/loans/src/entities/loans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ use crate::{
BorrowLoanError, BorrowRestrictions, CloseLoanError, CreateLoanError, LoanRestrictions,
MutationError, RepaidAmount, RepayLoanError, RepayRestrictions, RepaymentSchedule,
},
PriceOf,
};

/// Loan information.
Expand Down Expand Up @@ -292,7 +293,7 @@ impl<T: Config> ActiveLoan<T> {
pub fn present_value_by<Rates>(
&self,
rates: &Rates,
prices: &BTreeMap<T::PriceId, T::Balance>,
prices: &BTreeMap<T::PriceId, PriceOf<T>>,
) -> Result<T::Balance, DispatchError>
where
Rates: RateCollection<T::Rate, T::Balance, T::Balance>,
Expand Down Expand Up @@ -583,3 +584,101 @@ impl<T: Config> TryFrom<(T::PoolId, ActiveLoan<T>)> for ActiveLoanInfo<T> {
})
}
}

/// Adds `with_linear_pricing` to ExternalPricing struct for migration to v4
pub mod v3 {
use cfg_traits::{interest::InterestRate, Seconds};
use parity_scale_codec::{Decode, Encode};

use crate::{
entities::{
loans::BlockNumberFor,
pricing::external::v3::{ActivePricing, Pricing},
},
types::{LoanRestrictions, RepaidAmount, RepaymentSchedule},
AssetOf, Config,
};

#[derive(Encode, Decode)]
pub struct ActiveLoan<T: Config> {
schedule: RepaymentSchedule,
collateral: AssetOf<T>,
restrictions: LoanRestrictions,
borrower: T::AccountId,
write_off_percentage: T::Rate,
origination_date: Seconds,
pricing: ActivePricing<T>,
total_borrowed: T::Balance,
total_repaid: RepaidAmount<T::Balance>,
repayments_on_schedule_until: Seconds,
}

impl<T: Config> ActiveLoan<T> {
pub fn migrate(self, with_linear_pricing: bool) -> super::ActiveLoan<T> {
super::ActiveLoan {
schedule: self.schedule,
collateral: self.collateral,
restrictions: self.restrictions,
borrower: self.borrower,
write_off_percentage: self.write_off_percentage,
origination_date: self.origination_date,
pricing: self.pricing.migrate(with_linear_pricing),
total_borrowed: self.total_borrowed,
total_repaid: self.total_repaid,
repayments_on_schedule_until: self.repayments_on_schedule_until,
}
}
}

#[derive(Encode, Decode)]
pub struct CreatedLoan<T: Config> {
info: LoanInfo<T>,
borrower: T::AccountId,
}

impl<T: Config> CreatedLoan<T> {
pub fn migrate(self, with_linear_pricing: bool) -> super::CreatedLoan<T> {
super::CreatedLoan::<T>::new(self.info.migrate(with_linear_pricing), self.borrower)
}
}

#[derive(Encode, Decode)]
pub struct ClosedLoan<T: Config> {
closed_at: BlockNumberFor<T>,
info: LoanInfo<T>,
total_borrowed: T::Balance,
total_repaid: RepaidAmount<T::Balance>,
}

impl<T: Config> ClosedLoan<T> {
pub fn migrate(self, with_linear_pricing: bool) -> super::ClosedLoan<T> {
super::ClosedLoan::<T> {
closed_at: self.closed_at,
info: self.info.migrate(with_linear_pricing),
total_borrowed: self.total_borrowed,
total_repaid: self.total_repaid,
}
}
}

#[derive(Encode, Decode)]
pub struct LoanInfo<T: Config> {
pub schedule: RepaymentSchedule,
pub collateral: AssetOf<T>,
pub interest_rate: InterestRate<T::Rate>,
pub pricing: Pricing<T>,
pub restrictions: LoanRestrictions,
}

impl<T: Config> LoanInfo<T> {
pub fn migrate(self, with_linear_pricing: bool) -> super::LoanInfo<T> {
super::LoanInfo::<T> {
pricing: self.pricing.migrate(with_linear_pricing),
schedule: self.schedule,
collateral: self.collateral,
interest_rate: self.interest_rate,
restrictions: self.restrictions,
}
}
}
}
164 changes: 144 additions & 20 deletions pallets/loans/src/entities/pricing/external.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ use sp_runtime::{
traits::{EnsureAdd, EnsureFixedPointNumber, EnsureSub, Zero},
ArithmeticError, DispatchError, DispatchResult, FixedPointNumber,
};
use sp_std::collections::btree_map::BTreeMap;
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)]
Expand Down Expand Up @@ -76,6 +77,9 @@ pub struct ExternalPricing<T: Config> {
/// borrow/repay and the current oracle price.
/// See [`ExternalAmount::settlement_price`].
pub max_price_variation: T::Rate,

/// If the pricing is estimated with a linear pricing model.
pub with_linear_pricing: bool,
}

impl<T: Config> ExternalPricing<T> {
Expand Down Expand Up @@ -150,25 +154,59 @@ impl<T: Config> ExternalActivePricing<T> {
}
}

fn linear_accrual_price(&self, maturity: Seconds) -> Result<T::Balance, DispatchError> {
Ok(cfg_utils::math::y_coord_in_rect(
(self.settlement_price_updated, self.latest_settlement_price),
(maturity, self.info.notional),
T::Time::now(),
)?)
fn maybe_with_linear_accrual_price(
&self,
maturity: Seconds,
price: T::Balance,
price_last_updated: Seconds,
) -> Result<T::Balance, DispatchError> {
if self.info.with_linear_pricing {
if min(price_last_updated, maturity) == maturity {
// We can not have 2 'xs' with different 'y' in a rect.
// That only happens at maturity
return Ok(self.info.notional);
}

Ok(cfg_utils::math::y_coord_in_rect(
(min(price_last_updated, maturity), price),
(maturity, self.info.notional),
min(T::Time::now(), maturity),
)?)
} else {
Ok(price)
}
}

pub fn current_price(
&self,
pool_id: T::PoolId,
maturity: Option<Seconds>,
) -> Result<T::Balance, DispatchError> {
let maturity = maturity.unwrap_or(T::Time::now());
self.current_price_inner(
maturity,
T::PriceRegistry::get(&self.info.price_id, &pool_id).ok(),
)
}

Ok(match T::PriceRegistry::get(&self.info.price_id, &pool_id) {
Ok(data) => data.0,
Err(_) => self.linear_accrual_price(maturity)?,
})
fn current_price_inner(
&self,
maturity: Option<Seconds>,
oracle: Option<PriceOf<T>>,
) -> Result<T::Balance, DispatchError> {
let maturity = maturity.unwrap_or(T::Time::now());
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_principal(
Expand Down Expand Up @@ -199,16 +237,10 @@ impl<T: Config> ExternalActivePricing<T> {

pub fn present_value_cached(
&self,
cache: &BTreeMap<T::PriceId, T::Balance>,
cache: &BTreeMap<T::PriceId, PriceOf<T>>,
maturity: Option<Seconds>,
) -> Result<T::Balance, DispatchError> {
let price = match cache.get(&self.info.price_id) {
Some(data) => *data,
None => {
let maturity = maturity.unwrap_or(T::Time::now());
self.linear_accrual_price(maturity)?
}
};
let price = self.current_price_inner(maturity, cache.get(&self.info.price_id).copied())?;
Ok(self.outstanding_quantity.ensure_mul_int(price)?)
}

Expand Down Expand Up @@ -293,3 +325,95 @@ impl<T: Config> ExternalActivePricing<T> {
Ok(())
}
}

/// Adds `with_linear_pricing` to ExternalPricing struct for migration to v4
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,
}
}
}
}
12 changes: 6 additions & 6 deletions pallets/loans/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ pub mod pallet {
pub type AssetOf<T> = (<T as Config>::CollectionId, <T as Config>::ItemId);
pub type PriceOf<T> = (<T as Config>::Balance, <T as Config>::Moment);

const STORAGE_VERSION: StorageVersion = StorageVersion::new(3);
const STORAGE_VERSION: StorageVersion = StorageVersion::new(4);

#[pallet::pallet]
#[pallet::storage_version(STORAGE_VERSION)]
Expand Down Expand Up @@ -169,7 +169,7 @@ pub mod pallet {
type Time: TimeAsSecs;

/// Generic time type
type Moment: Parameter + Member + IntoSeconds;
type Moment: Parameter + Member + Copy + IntoSeconds;

/// Used to mint, transfer, and inspect assets.
type NonFungible: Transfer<Self::AccountId>
Expand Down Expand Up @@ -232,7 +232,7 @@ pub mod pallet {

/// Storage for loans that has been created but are not still active.
#[pallet::storage]
pub(crate) type CreatedLoan<T: Config> = StorageDoubleMap<
pub type CreatedLoan<T: Config> = StorageDoubleMap<
_,
Blake2_128Concat,
T::PoolId,
Expand All @@ -259,7 +259,7 @@ pub mod pallet {
/// No mutations are expected in this storage.
/// Loans are stored here for historical purposes.
#[pallet::storage]
pub(crate) type ClosedLoan<T: Config> = StorageDoubleMap<
pub type ClosedLoan<T: Config> = StorageDoubleMap<
_,
Blake2_128Concat,
T::PoolId,
Expand Down Expand Up @@ -1054,15 +1054,15 @@ pub mod pallet {

pub fn registered_prices(
pool_id: T::PoolId,
) -> Result<BTreeMap<T::PriceId, T::Balance>, DispatchError> {
) -> Result<BTreeMap<T::PriceId, PriceOf<T>>, DispatchError> {
let collection = T::PriceRegistry::collection(&pool_id)?;
Ok(ActiveLoans::<T>::get(pool_id)
.iter()
.filter_map(|(_, loan)| loan.price_id())
.filter_map(|price_id| {
collection
.get(&price_id)
.map(|price| (price_id, price.0))
.map(|price| (price_id, (price.0, price.1)))
.ok()
})
.collect::<BTreeMap<_, _>>())
Expand Down
Loading

0 comments on commit b579add

Please sign in to comment.