Skip to content

Commit

Permalink
add borrow check
Browse files Browse the repository at this point in the history
  • Loading branch information
lemunozm committed Apr 25, 2024
1 parent bd61fea commit abc3ef0
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 14 deletions.
28 changes: 15 additions & 13 deletions pallets/loans/src/entities/loans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -357,19 +357,19 @@ impl<T: Config> ActiveLoan<T> {
Error::<T>::from(BorrowLoanError::MaturityDatePassed)
);

// TODO
// If the loan has an interest or pay down schedule other than None,
// then we should only allow borrowing more if no interest or principal
// payments are overdue.
//
// This is required because after borrowing more, it is not possible
// to validate anymore whether previous cashflows matched the repayment
// schedule, as we don't store historic data of the principal.
//
// Therefore, in `borrow()` we set repayments_on_schedule_until to now.
//
// TODO: check total_repaid_interest >= total_expected_interest
// and total_repaid_principal >= total_expected_principal
if self.schedule.has_cashflow() {
let expected_payment = self.schedule.expected_payment(
self.origination_date,
self.principal()?,
self.pricing.interest().rate(),
now,
)?;

ensure!(
self.total_repaid.effective()? >= expected_payment,
DispatchError::Other("payment overdue")
)
}

Ok(())
}
Expand All @@ -388,6 +388,8 @@ impl<T: Config> ActiveLoan<T> {
}
}

self.repayments_on_schedule_until = T::Time::now();

Ok(())
}

Expand Down
44 changes: 43 additions & 1 deletion pallets/loans/src/types/cashflow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
use scale_info::TypeInfo;
use sp_runtime::{
traits::{
EnsureAddAssign, EnsureDiv, EnsureFixedPointNumber, EnsureInto, EnsureSub, EnsureSubAssign,
EnsureAdd, EnsureAddAssign, EnsureDiv, EnsureFixedPointNumber, EnsureInto, EnsureSub,
EnsureSubAssign,
},
ArithmeticError, DispatchError, FixedPointNumber, FixedPointOperand,
};
Expand Down Expand Up @@ -103,6 +104,8 @@ pub enum InterestPayments {
/// The associated value correspond to the paydown day in the month,
/// from 1-31.
/// The day will be adjusted to the month.
///
/// NOTE: Only day 1 is supported by now
Monthly(u8),
}

Expand Down Expand Up @@ -133,6 +136,20 @@ impl RepaymentSchedule {
self.maturity.is_valid(now)
}

pub fn has_cashflow(&self) -> bool {
let has_interest_payments = match self.interest_payments {
InterestPayments::None => false,
_ => true,
};

let has_pay_down_schedule = match self.pay_down_schedule {
PayDownSchedule::None => false,
_ => true,
};

has_interest_payments || has_pay_down_schedule
}

pub fn generate_cashflows<Balance, Rate>(
&self,
origination_date: Seconds,
Expand Down Expand Up @@ -169,6 +186,30 @@ impl RepaymentSchedule {
})
.collect()
}

pub fn expected_payment<Balance, Rate>(
&self,
origination_date: Seconds,
principal: Balance,
interest_rate: &InterestRate<Rate>,
until: Seconds,
) -> Result<Balance, DispatchError>
where
Balance: FixedPointOperand + EnsureAdd,
Rate: FixedPointNumber,
{
let cashflow = self.generate_cashflows(origination_date, principal, interest_rate)?;

let until_date = seconds_to_date(until)?;

let total_amount = cashflow
.iter()
.take_while(|(date, _)| *date < until)
.map(|(_, amount)| amount)
.try_fold(Balance::zero(), |a, b| a.ensure_add(*b))?;

Ok(total_amount)
}
}

fn monthly_dates(
Expand Down Expand Up @@ -268,6 +309,7 @@ mod tests {
from_ymd(year, month, day)
.and_hms_opt(hour, min, seconds)
.unwrap()
.and_utc()
.timestamp() as Seconds
}

Expand Down

0 comments on commit abc3ef0

Please sign in to comment.