Skip to content

Commit

Permalink
feat(lpp): stub merges all payments into one
Browse files Browse the repository at this point in the history
  • Loading branch information
Gancho Manev committed May 26, 2023
1 parent b72bc09 commit e04619c
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 25 deletions.
3 changes: 1 addition & 2 deletions contracts/lease/src/lease/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,9 +212,8 @@ mod tests {
self.loan.interest_due(by)
}

fn repay(&mut self, by: Timestamp, repayment: Coin<Lpn>) -> lpp::error::Result<()> {
fn repay(&mut self, by: Timestamp, repayment: Coin<Lpn>) {
self.loan.repay(by, repayment);
Ok(())
}

fn annual_interest_rate(&self) -> Percent {
Expand Down
5 changes: 2 additions & 3 deletions contracts/lease/src/loan/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ where
if !loan_payment.is_zero() {
// In theory, zero loan payment may occur if two consecutive repayments are executed within the same time.
// In practice, that means two repayment transactions of the same lease enter the same block.
self.lpp_loan.repay(by, loan_payment)?;
self.lpp_loan.repay(by, loan_payment);
}
Ok(receipt)
}
Expand Down Expand Up @@ -994,9 +994,8 @@ mod tests {
self.loan.interest_due(by)
}

fn repay(&mut self, by: Timestamp, repayment: Coin<Lpn>) -> LppResult<()> {
fn repay(&mut self, by: Timestamp, repayment: Coin<Lpn>) {
self.loan.repay(by, repayment);
Ok(())
}

fn annual_interest_rate(&self) -> Percent {
Expand Down
98 changes: 96 additions & 2 deletions contracts/lpp/src/loan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ where
pub interest_paid: Timestamp,
}

#[cfg_attr(test, derive(Debug, Eq, PartialEq))]
pub struct RepayShares<LPN>
where
LPN: Currency,
Expand Down Expand Up @@ -116,11 +117,12 @@ where
#[cfg(test)]
mod test {
use finance::{
coin::Coin, duration::Duration, percent::Percent, test::currency::Usdc, zero::Zero,
coin::Coin, duration::Duration, fraction::Fraction, percent::Percent, test::currency::Usdc,
zero::Zero,
};
use sdk::cosmwasm_std::Timestamp;

use crate::loan::Loan;
use crate::loan::{Loan, RepayShares};

#[test]
fn interest() {
Expand All @@ -139,6 +141,98 @@ mod test {
assert_eq!(Coin::ZERO, l.interest_due(l.interest_paid.minus_nanos(1)));
}

#[test]
fn repay_no_interest() {
let principal_at_start = Coin::<Usdc>::from(500);
let interest = Percent::from_percent(50);
let start_at = Timestamp::from_nanos(200);
let mut l = Loan {
principal_due: principal_at_start,
annual_interest_rate: interest,
interest_paid: start_at,
};

let payment1 = 10.into();
assert_eq!(
RepayShares {
interest: Coin::ZERO,
principal: payment1,
excess: Coin::ZERO
},
l.repay(l.interest_paid, payment1)
);
assert_eq!(
Loan {
principal_due: principal_at_start - payment1,
annual_interest_rate: interest,
interest_paid: l.interest_paid
},
l
);
}

#[test]
fn repay_interest_only() {
let principal_start = Coin::<Usdc>::from(500);
let interest = Percent::from_percent(50);
let mut l = Loan {
principal_due: principal_start,
annual_interest_rate: interest,
interest_paid: Timestamp::from_nanos(200),
};

let interest_a_year = interest.of(principal_start);
let at_first_year_end = l.interest_paid + Duration::YEAR;
assert_eq!(
RepayShares {
interest: interest_a_year,
principal: Coin::ZERO,
excess: Coin::ZERO
},
l.repay(at_first_year_end, interest_a_year)
);
assert_eq!(
Loan {
principal_due: principal_start,
annual_interest_rate: interest,
interest_paid: at_first_year_end
},
l
);
}

#[test]
fn repay_all() {
let principal_start = Coin::<Usdc>::from(50000000000);
let interest = Percent::from_percent(50);
let mut l = Loan {
principal_due: principal_start,
annual_interest_rate: interest,
interest_paid: Timestamp::from_nanos(200),
};

let interest_a_year = interest.of(principal_start);
let at_first_hour_end = l.interest_paid + Duration::HOUR;
let exp_interest = interest_a_year.checked_div(365 * 24).unwrap();
let excess = 12441.into();
assert_eq!(
RepayShares {
interest: exp_interest,
principal: principal_start,
excess,
},
l.repay(at_first_hour_end, exp_interest + principal_start + excess)
);
assert_eq!(
Loan {
principal_due: Coin::ZERO,
annual_interest_rate: interest,
interest_paid: at_first_hour_end
},
l
);
}

mod persistence {
use finance::{
coin::Coin, duration::Duration, percent::Percent, test::currency::Usdc, zero::Zero,
Expand Down
93 changes: 76 additions & 17 deletions contracts/lpp/src/stub/loan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,7 @@ use finance::{coin::Coin, currency::Currency, percent::Percent};
use platform::batch::Batch;
use serde::{de::DeserializeOwned, Serialize};

use crate::{
error::{ContractError, Result},
loan::Loan,
msg::ExecuteMsg,
};
use crate::{error::ContractError, loan::Loan, msg::ExecuteMsg};

use super::{LppBatch, LppRef};

Expand All @@ -20,7 +16,7 @@ where
{
fn principal_due(&self) -> Coin<Lpn>;
fn interest_due(&self, by: Timestamp) -> Coin<Lpn>;
fn repay(&mut self, by: Timestamp, repayment: Coin<Lpn>) -> Result<()>;
fn repay(&mut self, by: Timestamp, repayment: Coin<Lpn>);
fn annual_interest_rate(&self) -> Percent;
}

Expand All @@ -41,7 +37,7 @@ where
lpp_ref: LppRef,
currency: PhantomData<Lpn>,
loan: Loan<Lpn>,
batch: Batch,
repayment: Coin<Lpn>,
}

impl<Lpn> LppLoanImpl<Lpn>
Expand All @@ -53,7 +49,7 @@ where
lpp_ref,
currency: PhantomData,
loan,
batch: Batch::default(),
repayment: Default::default(),
}
}
}
Expand All @@ -69,15 +65,9 @@ where
self.loan.interest_due(by)
}

fn repay(&mut self, by: Timestamp, repayment: Coin<Lpn>) -> Result<()> {
fn repay(&mut self, by: Timestamp, repayment: Coin<Lpn>) {
self.loan.repay(by, repayment);
self.batch
.schedule_execute_wasm_no_reply(
&self.lpp_ref.addr,
ExecuteMsg::RepayLoan(),
Some(repayment),
)
.map_err(ContractError::from)
self.repayment += repayment;
}

fn annual_interest_rate(&self) -> Percent {
Expand All @@ -92,9 +82,78 @@ where
type Error = ContractError;

fn try_from(stub: LppLoanImpl<Lpn>) -> StdResult<Self, Self::Error> {
let mut batch = Batch::default();
if !stub.repayment.is_zero() {
batch.schedule_execute_wasm_no_reply(
&stub.lpp_ref.addr,
ExecuteMsg::RepayLoan(),
Some(stub.repayment),
)?;
}
Ok(Self {
lpp_ref: stub.lpp_ref,
batch: stub.batch,
batch,
})
}
}

#[cfg(test)]
mod test {
use cosmwasm_std::Timestamp;
use finance::{coin::Coin, duration::Duration, percent::Percent, test::currency::Usdc};
use platform::batch::Batch;

use crate::{
loan::Loan,
msg::ExecuteMsg,
stub::{loan::LppLoan, LppBatch, LppRef},
};

use super::LppLoanImpl;

#[test]
fn try_from_no_payments() {
let lpp_ref = LppRef::unchecked::<_, Usdc>("lpp_address");
let loan = LppLoanImpl::new(
lpp_ref.clone(),
Loan {
principal_due: Coin::<Usdc>::new(100),
annual_interest_rate: Percent::from_percent(12),
interest_paid: Timestamp::from_seconds(10),
},
);
let batch: LppBatch<LppRef> = loan.try_into().unwrap();
assert_eq!(lpp_ref, batch.lpp_ref);
assert_eq!(Batch::default(), batch.batch);
}

#[test]
fn try_from_a_few_payments() {
let lpp_ref = LppRef::unchecked::<_, Usdc>("lpp_address");
let start = Timestamp::from_seconds(0);
let mut loan = LppLoanImpl::new(
lpp_ref.clone(),
Loan {
principal_due: Coin::<Usdc>::new(100),
annual_interest_rate: Percent::from_percent(12),
interest_paid: start,
},
);
let payment1 = 8.into();
let payment2 = 4.into();
loan.repay(start + Duration::YEAR, payment1);
loan.repay(start + Duration::YEAR, payment2);
let batch: LppBatch<LppRef> = loan.try_into().unwrap();
assert_eq!(lpp_ref, batch.lpp_ref);
{
let mut exp = Batch::default();
exp.schedule_execute_wasm_no_reply(
lpp_ref.addr(),
ExecuteMsg::RepayLoan(),
Some(payment1 + payment2),
)
.unwrap();
assert_eq!(exp, batch.batch);
}
}
}
3 changes: 2 additions & 1 deletion contracts/lpp/src/stub/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ pub trait WithLpp {
}

#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub struct LppRef {
addr: Addr,
currency: SymbolOwned,
Expand Down Expand Up @@ -227,7 +228,7 @@ impl LppRef {
}
}

#[cfg(feature = "testing")]
#[cfg(any(test, feature = "testing"))]
impl LppRef {
pub fn unchecked<A, Lpn>(addr: A) -> Self
where
Expand Down

0 comments on commit e04619c

Please sign in to comment.