Skip to content

Commit

Permalink
base impl done as loan change
Browse files Browse the repository at this point in the history
  • Loading branch information
lemunozm committed Aug 16, 2023
1 parent 0191a72 commit 2a1c7e9
Show file tree
Hide file tree
Showing 6 changed files with 156 additions and 72 deletions.
48 changes: 48 additions & 0 deletions pallets/loans/src/entities/changes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
use cfg_primitives::Moment;
use cfg_traits::interest::InterestRate;
use codec::{Decode, Encode, MaxEncodedLen};
use frame_support::{storage::bounded_vec::BoundedVec, RuntimeDebug};
use scale_info::TypeInfo;

use crate::{
entities::pricing::{PricingAmount, RepaidPricingAmount},
pallet::Config,
types::{
policy::WriteOffRule, valuation::ValuationMethod, InterestPayments, Maturity,
PayDownSchedule,
},
};

/// Active loan mutation for internal pricing
#[derive(Encode, Decode, Clone, PartialEq, Eq, TypeInfo, RuntimeDebug, MaxEncodedLen)]
pub enum InternalMutation<Rate> {
ValuationMethod(ValuationMethod<Rate>),
ProbabilityOfDefault(Rate),
LossGivenDefault(Rate),
DiscountRate(InterestRate<Rate>),
}

/// Active loan mutation
#[derive(Encode, Decode, Clone, PartialEq, Eq, TypeInfo, RuntimeDebug, MaxEncodedLen)]
pub enum LoanMutation<Rate> {
Maturity(Maturity),
MaturityExtension(Moment),
InterestRate(InterestRate<Rate>),
InterestPayments(InterestPayments),
PayDownSchedule(PayDownSchedule),
Internal(InternalMutation<Rate>),
}

/// Change description
#[derive(Encode, Decode, Clone, PartialEq, Eq, TypeInfo, RuntimeDebug, MaxEncodedLen)]
#[scale_info(skip_type_params(T))]
pub enum Change<T: Config> {
Loan(T::LoanId, LoanMutation<T::Rate>),
Policy(BoundedVec<WriteOffRule<T::Rate>, T::MaxWriteOffPolicySize>),
TransferDebt(
T::LoanId,
T::LoanId,
RepaidPricingAmount<T>,
PricingAmount<T>,
),
}
16 changes: 9 additions & 7 deletions pallets/loans/src/entities/loans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,19 @@ use sp_runtime::{
DispatchError,
};

use super::pricing::{
external::ExternalActivePricing, internal::InternalActivePricing, ActivePricing, Pricing,
PricingAmount, RepaidPricingAmount,
};
use crate::{
entities::{
changes::LoanMutation,
pricing::{
external::ExternalActivePricing, internal::InternalActivePricing, ActivePricing,
Pricing, PricingAmount, RepaidPricingAmount,
},
},
pallet::{AssetOf, Config, Error, PriceOf},
types::{
policy::{WriteOffStatus, WriteOffTrigger},
BorrowLoanError, BorrowRestrictions, CloseLoanError, CreateLoanError, LoanMutation,
LoanRestrictions, MutationError, RepaidAmount, RepayLoanError, RepayRestrictions,
RepaymentSchedule,
BorrowLoanError, BorrowRestrictions, CloseLoanError, CreateLoanError, LoanRestrictions,
MutationError, RepaidAmount, RepayLoanError, RepayRestrictions, RepaymentSchedule,
},
};

Expand Down
4 changes: 2 additions & 2 deletions pallets/loans/src/entities/pricing/internal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ use sp_runtime::{
};

use crate::{
entities::interest::ActiveInterestRate,
entities::{changes::InternalMutation, interest::ActiveInterestRate},
pallet::{Config, Error},
types::{
valuation::{DiscountedCashFlow, ValuationMethod},
CreateLoanError, InternalMutation, MutationError,
CreateLoanError, MutationError,
},
};

Expand Down
118 changes: 90 additions & 28 deletions pallets/loans/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ pub mod migrations {

/// High level types that uses `pallet::Config`
pub mod entities {
pub mod changes;
pub mod interest;
pub mod loans;
pub mod pricing;
Expand Down Expand Up @@ -84,6 +85,7 @@ pub mod pallet {
};
use codec::HasCompact;
use entities::{
changes::{Change, LoanMutation},
loans::{self, ActiveLoan, ActiveLoanInfo, LoanInfo},
pricing::{PricingAmount, RepaidPricingAmount},
};
Expand All @@ -110,8 +112,8 @@ pub mod pallet {
self,
policy::{self, WriteOffRule, WriteOffStatus},
portfolio::{self, InitialPortfolioValuation, PortfolioValuationUpdateType},
BorrowLoanError, Change, CloseLoanError, CreateLoanError, LoanMutation, MutationError,
RepayLoanError, WrittenOffError,
BorrowLoanError, CloseLoanError, CreateLoanError, MutationError, RepayLoanError,
WrittenOffError,
};

use super::*;
Expand All @@ -124,8 +126,6 @@ pub mod pallet {
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>;
pub type ChangeOf<T> =
Change<<T as Config>::LoanId, <T as Config>::Rate, <T as Config>::MaxWriteOffPolicySize>;

const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);

Expand All @@ -139,7 +139,7 @@ pub mod pallet {
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;

/// Represent a runtime change
type RuntimeChange: From<ChangeOf<Self>> + TryInto<ChangeOf<Self>>;
type RuntimeChange: From<Change<Self>> + TryInto<Change<Self>>;

/// Identify a curreny.
type CurrencyId: Parameter + Copy + MaxEncodedLen;
Expand Down Expand Up @@ -782,7 +782,7 @@ pub mod pallet {
/// The repaid and borrow amount must match.
#[pallet::weight(T::WeightInfo::transfer_debt(T::MaxActiveLoansPerPool::get()))]
#[pallet::call_index(11)]
pub fn transfer_debt(
pub fn propose_transfer_debt(
origin: OriginFor<T>,
pool_id: T::PoolId,
from_loan_id: T::LoanId,
Expand All @@ -792,35 +792,62 @@ pub mod pallet {
) -> DispatchResult {
let who = ensure_signed(origin)?;

ensure!(
from_loan_id != to_loan_id,
Error::<T>::TransferDebtToSameLoan
);
transactional::with_transaction(|| {
let result = Self::transfer_debt_action(
&who,
pool_id,
from_loan_id,
to_loan_id,
repaid_amount.clone(),
borrow_amount.clone(),
);

let repaid_amount = Self::repay_action(&who, pool_id, from_loan_id, &repaid_amount)?.0;
// We do not want to apply the mutation,
// only check if there is no error in applying it
TransactionOutcome::Rollback(result)
})?;

match &borrow_amount {
PricingAmount::Internal(value) => {
ensure!(
*value == repaid_amount.repaid_amount()?.total()?,
Error::<T>::TransferDebtAmountMismatched
)
}
PricingAmount::External(_external) => {
// TODO (1): handle it once slippage is added.
// TODO (2): if quantity is measured as a rate,
// then instead of using slippage we can use quantity with
// decimals.
}
}
T::ChangeGuard::note(
pool_id,
Change::TransferDebt(from_loan_id, to_loan_id, repaid_amount, borrow_amount).into(),
)?;

Ok(())
}

/// Transfer debt from one loan to another loan,
/// repaying from the first loan and borrowing the same amount from the
/// second loan. `from_loan_id` is the loan used to repay.
/// `to_loan_id` is the loan used to borrow.
/// The repaid and borrow amount must match.
#[pallet::weight(T::WeightInfo::transfer_debt(T::MaxActiveLoansPerPool::get()))]
#[pallet::call_index(12)]
pub fn apply_transfer_debt(
origin: OriginFor<T>,
pool_id: T::PoolId,
change_id: T::Hash,
) -> DispatchResult {
let who = ensure_signed(origin)?;

let _count = Self::borrow_action(&who, pool_id, to_loan_id, &borrow_amount)?;
let Change::TransferDebt(from_loan_id, to_loan_id, repaid_amount, borrow_amount) =
Self::get_released_change(pool_id, change_id)? else {
Err(Error::<T>::UnrelatedChangeId)?
};

let (amount, _count) = Self::transfer_debt_action(
&who,
pool_id,
from_loan_id,
to_loan_id,
repaid_amount,
borrow_amount,
)?;

Self::deposit_event(Event::<T>::TransferDebt {
pool_id,
from_loan_id,
to_loan_id,
amount: repaid_amount.repaid_amount()?.total()?,
amount,
});

Ok(())
Expand Down Expand Up @@ -895,7 +922,7 @@ pub mod pallet {
fn get_released_change(
pool_id: T::PoolId,
change_id: T::Hash,
) -> Result<ChangeOf<T>, DispatchError> {
) -> Result<Change<T>, DispatchError> {
T::ChangeGuard::released(pool_id, change_id)?
.try_into()
.map_err(|_| Error::<T>::NoLoanChangeId.into())
Expand Down Expand Up @@ -1046,6 +1073,41 @@ pub mod pallet {
})
}

fn transfer_debt_action(
who: &T::AccountId,
pool_id: T::PoolId,
from_loan_id: T::LoanId,
to_loan_id: T::LoanId,
repaid_amount: RepaidPricingAmount<T>,
borrow_amount: PricingAmount<T>,
) -> Result<(T::Balance, u32), DispatchError> {
ensure!(
from_loan_id != to_loan_id,
Error::<T>::TransferDebtToSameLoan
);

let repaid_amount = Self::repay_action(&who, pool_id, from_loan_id, &repaid_amount)?.0;

match &borrow_amount {
PricingAmount::Internal(value) => {
ensure!(
*value == repaid_amount.repaid_amount()?.total()?,
Error::<T>::TransferDebtAmountMismatched
)
}
PricingAmount::External(_external) => {
// TODO (1): handle it once slippage is added.
// TODO (2): if quantity is measured as a rate,
// then instead of using slippage we can use quantity with
// decimals.
}
}

let count = Self::borrow_action(&who, pool_id, to_loan_id, &borrow_amount)?;

Ok((repaid_amount.repaid_amount()?.total()?, count))
}

/// Set the maturity date of the loan to this instant.
#[cfg(feature = "runtime-benchmarks")]
pub fn expire_action(pool_id: T::PoolId, loan_id: T::LoanId) -> DispatchResult {
Expand Down
35 changes: 2 additions & 33 deletions pallets/loans/src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,18 @@
//! Contains base types without Config references
use cfg_primitives::Moment;
use cfg_traits::interest::InterestRate;
use codec::{Decode, Encode, MaxEncodedLen};
use frame_support::{storage::bounded_vec::BoundedVec, PalletError, RuntimeDebug};
use frame_support::{PalletError, RuntimeDebug};
use scale_info::TypeInfo;
use sp_runtime::{
traits::{EnsureAdd, EnsureAddAssign, EnsureSubAssign, Get},
traits::{EnsureAdd, EnsureAddAssign, EnsureSubAssign},
ArithmeticError,
};

pub mod policy;
pub mod portfolio;
pub mod valuation;

use policy::WriteOffRule;
use valuation::ValuationMethod;

/// Error related to loan creation
#[derive(Encode, Decode, TypeInfo, PalletError)]
pub enum CreateLoanError {
Expand Down Expand Up @@ -194,33 +190,6 @@ pub struct LoanRestrictions {
pub repayments: RepayRestrictions,
}

/// Active loan mutation for internal pricing
#[derive(Encode, Decode, Clone, PartialEq, Eq, TypeInfo, RuntimeDebug, MaxEncodedLen)]
pub enum InternalMutation<Rate> {
ValuationMethod(ValuationMethod<Rate>),
ProbabilityOfDefault(Rate),
LossGivenDefault(Rate),
DiscountRate(InterestRate<Rate>),
}

/// Active loan mutation
#[derive(Encode, Decode, Clone, PartialEq, Eq, TypeInfo, RuntimeDebug, MaxEncodedLen)]
pub enum LoanMutation<Rate> {
Maturity(Maturity),
MaturityExtension(Moment),
InterestRate(InterestRate<Rate>),
InterestPayments(InterestPayments),
PayDownSchedule(PayDownSchedule),
Internal(InternalMutation<Rate>),
}

/// Change description
#[derive(Encode, Decode, Clone, PartialEq, Eq, TypeInfo, RuntimeDebug, MaxEncodedLen)]
pub enum Change<LoanId, Rate, MaxRules: Get<u32>> {
Loan(LoanId, LoanMutation<Rate>),
Policy(BoundedVec<WriteOffRule<Rate>, MaxRules>),
}

#[derive(Default, Encode, Decode, Clone, PartialEq, Eq, TypeInfo, RuntimeDebug, MaxEncodedLen)]
pub struct RepaidAmount<Balance> {
pub principal: Balance,
Expand Down
7 changes: 5 additions & 2 deletions pallets/loans/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ use orml_traits::{DataFeeder, DataProvider};
use sp_runtime::{DispatchError, DispatchResult};
use sp_std::marker::PhantomData;

use crate::pallet::{ChangeOf, Config, PriceResultOf};
use crate::{
entities::changes::Change,
pallet::{Config, PriceResultOf},
};

const DEFAULT_PRICE_ERR: DispatchError =
DispatchError::Other("No configured price registry for pallet-loans");
Expand Down Expand Up @@ -79,7 +82,7 @@ const DEFAULT_CHANGE_ERR: DispatchError =
pub struct NoLoanChanges<T>(PhantomData<T>);

impl<T: Config> ChangeGuard for NoLoanChanges<T> {
type Change = ChangeOf<T>;
type Change = Change<T>;
type ChangeId = T::Hash;
type PoolId = T::PoolId;

Expand Down

0 comments on commit 2a1c7e9

Please sign in to comment.