Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Loans: Add Runtime API #1457

Merged
merged 12 commits into from
Jul 18, 2023
Merged
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions libs/traits/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive
frame-support = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.38" }
impl-trait-for-tuples = "0.2.1"
scale-info = { version = "2.3.0", default-features = false, features = ["derive"] }
serde = { version = "1.0", optional = true }
sp-arithmetic = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.38" }
sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.38" }
sp-std = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.38" }
Expand Down Expand Up @@ -43,6 +44,7 @@ std = [
"sp-std/std",
"cfg-primitives/std",
"scale-info/std",
"serde/std",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since it's optional, shouldn't we apply ?

Suggested change
"serde/std",
"serde?/std",

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering why adding this broke tests?

Copy link
Contributor Author

@lemunozm lemunozm Jul 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think:

  • serde?/std in case serde is enable it adds serde/std
  • serde/srd enables serde and it adds serde/std (what we want here)

]
try-runtime = [
"frame-support/try-runtime",
Expand Down
2 changes: 2 additions & 0 deletions libs/traits/src/interest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ use sp_runtime::{
};

#[derive(Encode, Decode, Clone, PartialEq, Eq, TypeInfo, RuntimeDebug, MaxEncodedLen)]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
pub enum CompoundingSchedule {
/// Interest compounds every second
Secondly,
}

/// Interest rate method with compounding schedule information
#[derive(Encode, Decode, Clone, PartialEq, Eq, TypeInfo, RuntimeDebug, MaxEncodedLen)]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
pub enum InterestRate<Rate> {
/// Interest accrues at a fixed rate
Fixed {
Expand Down
2 changes: 2 additions & 0 deletions pallets/loans/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { package = "parity-scale-codec", default-features = false, version = "3.0.0", features = ["derive"] }
scale-info = { version = "2.3.0", default-features = false, features = ["derive"] }
serde = { version = "1.0", optional = true }

frame-support = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.38" }
frame-system = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.38" }
Expand Down Expand Up @@ -63,6 +64,7 @@ std = [
"sp-io/std",
"strum/std",
"orml-traits/std",
"serde/std",
lemunozm marked this conversation as resolved.
Show resolved Hide resolved
]
runtime-benchmarks = [
"frame-benchmarking",
Expand Down
49 changes: 19 additions & 30 deletions pallets/loans/docs/types.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,14 @@ set namespaceSeparator ::
hide methods

enum Maturity {
Fixed: Moment
Fixed::date: Moment
Fixed::extension: Moment
}

enum CalendarEvent {
End
}

enum ReferenceDate{
CalendarDate: CalendarEvent,
OriginationDate
}

ReferenceDate *--> CalendarEvent

enum InterestPayments {
None
Monthly: ReferenceDate
SemiAnnually: ReferenceDate
}

InterestPayments *-----> ReferenceDate

enum PayDownSchedule {
None
}
Expand All @@ -38,8 +24,8 @@ class RepaymentSchedule {
}

RepaymentSchedule *--> Maturity
RepaymentSchedule *--> PayDownSchedule
RepaymentSchedule *--> InterestPayments
RepaymentSchedule *---> PayDownSchedule
RepaymentSchedule *----> InterestPayments

enum BorrowRestrictions {
NoWrittenOff
Expand All @@ -59,24 +45,27 @@ class LoanRestrictions {
LoanRestrictions *--> BorrowRestrictions
LoanRestrictions *--> RepayRestrictions

enum CompoundingCadence {
Secondly: ReferenceDate
}

CompoundingCadence *-r-> ReferenceDate

enum InterestRate {
Fixed: Rate, CompoundingCadence
}

InterestRate *--> CompoundingCadence

class RepaidAmount {
principal: Balance
interest: Balance
unscheduled: Balance
}

node traits {
package interest {
enum CompoundingSchedule {
Secondly
}

enum InterestRate {
Fixed::rate_per_year: Rate
Fixed::compounding: CompoundingSchedule
}

InterestRate *--> CompoundingSchedule
}
}

package portfolio {
class PortfolioValuation {
value: Balance
Expand Down
1 change: 1 addition & 0 deletions pallets/loans/src/entities/interest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::pallet::Config;

#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebugNoBound, TypeInfo, MaxEncodedLen)]
#[scale_info(skip_type_params(T))]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
pub struct ActiveInterestRate<T: Config> {
/// The current interest rate value (per year).
/// It the rate it has been penalized,
Expand Down
46 changes: 46 additions & 0 deletions pallets/loans/src/entities/loans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ impl<T: Config> ClosedLoan<T> {
/// Data containing an active loan.
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebugNoBound, TypeInfo, MaxEncodedLen)]
#[scale_info(skip_type_params(T))]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "std", serde(bound = ""))]
pub struct ActiveLoan<T: Config> {
/// Specify the repayments schedule of the loan
schedule: RepaymentSchedule,
Expand Down Expand Up @@ -489,3 +491,47 @@ impl<T: Config> ActiveLoan<T> {
self.schedule.maturity = crate::types::Maturity::fixed(duration);
}
}

/// Data containing an active loan with extra computed.
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebugNoBound, TypeInfo, MaxEncodedLen)]
#[scale_info(skip_type_params(T))]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "std", serde(bound = ""))]
pub struct ActiveLoanInfo<T: Config> {
Copy link
Contributor Author

@lemunozm lemunozm Jul 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit tricky and probably useful for futures runtime APIs.

The line with bound = "" is mandatory for cases where you have both, the scale_info and derive(serde) lines. serde defaults to adding Serialize and Deserialize bounds to T, but T, in this case, does not need (and we don't want) to be serializable, it's only used to fetch information from associated types. With bounds = "", we remove those implicit bounds from serde.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for clarifying this. So this is the serde equivalent of scale_info(skip_type_params(T) more or less?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah! More or less. Also, if there were other bounds, and you do not want to "reset them" you should add bound = "K: MyOtherBound". That way you've reset the Serialize bound to T and K but not the MyOtherBound to K.

/// Related active loan
active_loan: ActiveLoan<T>,

/// Interest accrued for this loan
interest_accrued: T::Balance,

/// Present value of the loan
present_value: T::Balance,
}

impl<T: Config> TryFrom<ActiveLoan<T>> for ActiveLoanInfo<T> {
type Error = DispatchError;

fn try_from(active_loan: ActiveLoan<T>) -> Result<Self, Self::Error> {
let (interest_accrued, present_value) = match &active_loan.pricing {
ActivePricing::Internal(inner) => {
let principal = active_loan
.total_borrowed
.ensure_sub(active_loan.total_repaid.principal)?;

let maturity_date = active_loan.schedule.maturity.date();

(
inner.current_interest(principal)?,
inner.present_value(active_loan.origination_date, maturity_date)?,
)
}
ActivePricing::External(inner) => (inner.current_interest()?, inner.present_value()?),
};

Ok(Self {
active_loan,
interest_accrued,
present_value,
})
}
}
4 changes: 4 additions & 0 deletions pallets/loans/src/entities/pricing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ pub mod internal;
/// Loan pricing method
#[derive(Encode, Decode, Clone, PartialEq, Eq, TypeInfo, RuntimeDebugNoBound, MaxEncodedLen)]
#[scale_info(skip_type_params(T))]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "std", serde(bound = ""))]
pub enum Pricing<T: Config> {
/// Calculated internally
Internal(internal::InternalPricing<T>),
Expand All @@ -25,6 +27,8 @@ pub enum Pricing<T: Config> {
/// Pricing attributes for active loans
#[derive(Encode, Decode, Clone, PartialEq, Eq, TypeInfo, RuntimeDebugNoBound, MaxEncodedLen)]
#[scale_info(skip_type_params(T))]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "std", serde(bound = ""))]
pub enum ActivePricing<T: Config> {
/// External attributes
Internal(internal::InternalActivePricing<T>),
Expand Down
5 changes: 5 additions & 0 deletions pallets/loans/src/entities/pricing/external.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ impl<T: Config> ExternalAmount<T> {

/// Define the max borrow amount of a loan
#[derive(Encode, Decode, Clone, PartialEq, Eq, TypeInfo, RuntimeDebug, MaxEncodedLen)]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
pub enum MaxBorrowAmount<Quantity> {
/// You can borrow until the pool reserve
NoLimit,
Expand All @@ -58,6 +59,8 @@ pub enum MaxBorrowAmount<Quantity> {
/// External pricing method
#[derive(Encode, Decode, Clone, PartialEq, Eq, TypeInfo, RuntimeDebugNoBound, MaxEncodedLen)]
#[scale_info(skip_type_params(T))]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "std", serde(bound = ""))]
pub struct ExternalPricing<T: Config> {
/// Id of an external price
pub price_id: T::PriceId,
Expand Down Expand Up @@ -85,6 +88,8 @@ impl<T: Config> ExternalPricing<T> {
/// External pricing method with extra attributes for active loans
#[derive(Encode, Decode, Clone, PartialEq, Eq, TypeInfo, RuntimeDebugNoBound, MaxEncodedLen)]
#[scale_info(skip_type_params(T))]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "std", serde(bound = ""))]
pub struct ExternalActivePricing<T: Config> {
/// Basic external pricing info
info: ExternalPricing<T>,
Expand Down
5 changes: 5 additions & 0 deletions pallets/loans/src/entities/pricing/internal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use crate::{

/// Diferents methods of how to compute the amount can be borrowed
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
pub enum MaxBorrowAmount<Rate> {
/// Max borrow amount computation using the total borrowed
UpToTotalBorrowed { advance_rate: Rate },
Expand All @@ -34,6 +35,8 @@ pub enum MaxBorrowAmount<Rate> {
/// Internal pricing method
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebugNoBound, TypeInfo, MaxEncodedLen)]
#[scale_info(skip_type_params(T))]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "std", serde(bound = ""))]
pub struct InternalPricing<T: Config> {
/// Value of the collateral used for this loan
pub collateral_value: T::Balance,
Expand All @@ -59,6 +62,8 @@ impl<T: Config> InternalPricing<T> {
/// Internal pricing method with extra attributes for active loans
#[derive(Encode, Decode, Clone, PartialEq, Eq, TypeInfo, RuntimeDebugNoBound, MaxEncodedLen)]
#[scale_info(skip_type_params(T))]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "std", serde(bound = ""))]
pub struct InternalActivePricing<T: Config> {
/// Basic internal pricing info
info: InternalPricing<T>,
Expand Down
26 changes: 23 additions & 3 deletions pallets/loans/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ pub mod pallet {
};
use codec::HasCompact;
use entities::{
loans::{self, ActiveLoan, LoanInfo},
loans::{self, ActiveLoan, ActiveLoanInfo, LoanInfo},
pricing::{PricingAmount, RepaidPricingAmount},
};
use frame_support::{
Expand Down Expand Up @@ -120,7 +120,7 @@ pub mod pallet {
<T as Config>::PriceId,
<T as Config>::PoolId,
>>::Collection;

pub type PortfolioInfoOf<T> = Vec<(<T as Config>::LoanId, ActiveLoanInfo<T>)>;
pub type AssetOf<T> = (<T as Config>::CollectionId, <T as Config>::ItemId);
pub type PriceOf<T> = (<T as Config>::Balance, Moment);
pub type PriceResultOf<T> = Result<PriceOf<T>, DispatchError>;
Expand Down Expand Up @@ -190,7 +190,7 @@ pub mod pallet {
+ MaxEncodedLen;

/// Defines the balance type used for math computations
type Balance: tokens::Balance + FixedPointOperand;
type Balance: tokens::Balance + FixedPointOperand + MaybeSerializeDeserialize;

/// Fetching method for the time of the current block
type Time: UnixTime;
Expand Down Expand Up @@ -992,6 +992,26 @@ pub mod pallet {
Ok((loan, count))
}

pub fn get_active_loans_info(
pool_id: T::PoolId,
) -> Result<PortfolioInfoOf<T>, DispatchError> {
ActiveLoans::<T>::get(pool_id)
.into_iter()
.map(|(loan_id, loan)| Ok((loan_id, loan.try_into()?)))
.collect()
}

pub fn get_active_loan_info(
pool_id: T::PoolId,
loan_id: T::LoanId,
) -> Result<Option<ActiveLoanInfo<T>>, DispatchError> {
ActiveLoans::<T>::get(pool_id)
.into_iter()
.find(|(id, _)| *id == loan_id)
.map(|(_, loan)| loan.try_into())
.transpose()
}

/// Set the maturity date of the loan to this instant.
#[cfg(feature = "runtime-benchmarks")]
pub fn expire(pool_id: T::PoolId, loan_id: T::LoanId) -> DispatchResult {
Expand Down
8 changes: 8 additions & 0 deletions pallets/loans/src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ pub enum MutationError {

/// Specify the expected repayments date
#[derive(Encode, Decode, Clone, PartialEq, Eq, TypeInfo, RuntimeDebug, MaxEncodedLen)]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
pub enum Maturity {
/// Fixed point in time, in secs
Fixed {
Expand Down Expand Up @@ -131,13 +132,15 @@ impl Maturity {

/// Interest payment periods
#[derive(Encode, Decode, Clone, PartialEq, Eq, TypeInfo, RuntimeDebug, MaxEncodedLen)]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
pub enum InterestPayments {
/// All interest is expected to be paid at the maturity date
None,
}

/// Specify the paydown schedules of the loan
#[derive(Encode, Decode, Clone, PartialEq, Eq, TypeInfo, RuntimeDebug, MaxEncodedLen)]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
pub enum PayDownSchedule {
/// The entire borrowed amount is expected to be paid back at the maturity
/// date
Expand All @@ -146,6 +149,7 @@ pub enum PayDownSchedule {

/// Specify the repayment schedule of the loan
#[derive(Encode, Decode, Clone, PartialEq, Eq, TypeInfo, RuntimeDebug, MaxEncodedLen)]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
pub struct RepaymentSchedule {
/// Expected repayments date for remaining debt
pub maturity: Maturity,
Expand All @@ -166,6 +170,7 @@ impl RepaymentSchedule {

/// Specify how offer a loan can be borrowed
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
pub enum BorrowRestrictions {
/// The loan can not be borrowed if it has been written off.
NotWrittenOff,
Expand All @@ -176,6 +181,7 @@ pub enum BorrowRestrictions {

/// Specify how offer a loan can be repaid
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
pub enum RepayRestrictions {
/// No restrictions
None,
Expand All @@ -186,6 +192,7 @@ pub enum RepayRestrictions {

/// Define the loan restrictions
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
pub struct LoanRestrictions {
/// How offen can be borrowed
pub borrows: BorrowRestrictions,
Expand Down Expand Up @@ -222,6 +229,7 @@ pub enum Change<LoanId, Rate, MaxRules: Get<u32>> {
}

#[derive(Default, Encode, Decode, Clone, PartialEq, Eq, TypeInfo, RuntimeDebug, MaxEncodedLen)]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
pub struct RepaidAmount<Balance> {
pub principal: Balance,
pub interest: Balance,
Expand Down