Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 27 additions & 8 deletions common/src/borrow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@ use crate::{
MS_IN_A_YEAR,
};

/// This struct can only be constructed after accumulating interest on a
/// borrow position. This serves as proof that the interest has accrued, so it
/// is safe to perform certain other operations.
pub struct InterestAccumulationProof(());

#[cfg(test)]
impl InterestAccumulationProof {
pub fn test() -> Self {
Self(())
}
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
#[near(serializers = [borsh, json])]
pub enum BorrowStatus {
Expand Down Expand Up @@ -105,8 +117,10 @@ impl BorrowPosition {
self.collateral_asset_deposit.split(amount)
}

/// Interest accumulation MUST be applied before calling this function.
pub(crate) fn increase_borrow_asset_principal(
&mut self,
_proof: InterestAccumulationProof,
amount: BorrowAssetAmount,
block_timestamp_ms: u64,
) -> Option<()> {
Expand All @@ -118,8 +132,10 @@ impl BorrowPosition {
self.borrow_asset_principal.join(amount)
}

/// Interest accumulation MUST be applied before calling this function.
pub(crate) fn reduce_borrow_asset_liability(
&mut self,
_proof: InterestAccumulationProof,
mut amount: BorrowAssetAmount,
) -> Result<LiabilityReduction, error::LiquidationLockError> {
if self.liquidation_lock {
Expand Down Expand Up @@ -405,14 +421,13 @@ impl<M: BorrowMut<Market>> LinkedBorrowPositionMut<M> {

pub fn record_borrow_asset_withdrawal(
&mut self,
proof: InterestAccumulationProof,
amount: BorrowAssetAmount,
fees: BorrowAssetAmount,
) {
self.accumulate_interest();

self.position.borrow_asset_fees.add_once(fees);
self.position
.increase_borrow_asset_principal(amount, env::block_timestamp_ms())
.increase_borrow_asset_principal(proof, amount, env::block_timestamp_ms())
.unwrap_or_else(|| env::panic_str("Increase borrow asset principal overflow"));

self.market
Expand All @@ -429,12 +444,14 @@ impl<M: BorrowMut<Market>> LinkedBorrowPositionMut<M> {
.emit();
}

pub fn record_repay(&mut self, amount: BorrowAssetAmount) -> BorrowAssetAmount {
self.accumulate_interest();

pub fn record_repay(
&mut self,
proof: InterestAccumulationProof,
amount: BorrowAssetAmount,
) -> BorrowAssetAmount {
let liability_reduction = self
.position
.reduce_borrow_asset_liability(amount)
.reduce_borrow_asset_liability(proof, amount)
.unwrap_or_else(|e| env::panic_str(&e.to_string()));

self.market
Expand Down Expand Up @@ -462,7 +479,7 @@ impl<M: BorrowMut<Market>> LinkedBorrowPositionMut<M> {
liability_reduction.amount_remaining
}

pub fn accumulate_interest(&mut self) {
pub fn accumulate_interest(&mut self) -> InterestAccumulationProof {
self.market.borrow_mut().snapshot();

let accumulation_record = self.calculate_interest(u32::MAX);
Expand All @@ -478,6 +495,8 @@ impl<M: BorrowMut<Market>> LinkedBorrowPositionMut<M> {
self.position
.borrow_asset_fees
.accumulate(accumulation_record);

InterestAccumulationProof(())
}

pub fn liquidation_lock(&mut self) {
Expand Down
4 changes: 2 additions & 2 deletions common/src/market/configuration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,15 +192,15 @@ fn is_within_mcr(mcr: &Decimal, borrow_position: &BorrowPosition, price_pair: &P

#[cfg(test)]
mod tests {
use crate::{dec, oracle::pyth};
use crate::{borrow::InterestAccumulationProof, dec, oracle::pyth};

use super::*;

#[test]
fn test_is_within_mcr() {
let mut b = BorrowPosition::new(0);
b.increase_collateral_asset_deposit(121u128.into());
b.increase_borrow_asset_principal(100u128.into(), 0);
b.increase_borrow_asset_principal(InterestAccumulationProof::test(), 100u128.into(), 0);
assert!(is_within_mcr(
&dec!("1.2"),
&b,
Expand Down
4 changes: 3 additions & 1 deletion common/src/market/impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,9 @@ impl Market {
return Ok(None);
};

let resolution = supply_position.record_withdrawal(amount, env::block_timestamp_ms());
let proof = supply_position.accumulate_yield();
let resolution =
supply_position.record_withdrawal(proof, amount, env::block_timestamp_ms());

Ok(Some(resolution))
}
Expand Down
36 changes: 21 additions & 15 deletions common/src/supply.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ use crate::{
number::Decimal,
};

/// This struct can only be constructed after accumulating yield on a
/// supply position. This serves as proof that the yield has accrued, so it
/// is safe to perform certain other operations.
pub struct YieldAccumulationProof(());

#[derive(Debug, Clone, PartialEq, Eq)]
#[near(serializers = [json, borsh])]
pub struct SupplyPosition {
Expand Down Expand Up @@ -45,9 +50,10 @@ impl SupplyPosition {
!self.borrow_asset_deposit.is_zero() || !self.borrow_asset_yield.get_total().is_zero()
}

/// MUST always be paired with a yield recalculation!
/// Yield accumulation MUST be applied before calling this function.
pub(crate) fn increase_borrow_asset_deposit(
&mut self,
_proof: YieldAccumulationProof,
amount: BorrowAssetAmount,
block_timestamp_ms: u64,
) -> Option<()> {
Expand All @@ -57,9 +63,10 @@ impl SupplyPosition {
self.borrow_asset_deposit.join(amount)
}

/// MUST always be paired with a yield recalculation!
/// Yield accumulation MUST be applied before calling this function.
pub(crate) fn decrease_borrow_asset_deposit(
&mut self,
_proof: YieldAccumulationProof,
amount: BorrowAssetAmount,
) -> Option<BorrowAssetAmount> {
// No need to reset the timer; it is a permanent indication of the
Expand Down Expand Up @@ -201,12 +208,7 @@ impl<M: BorrowMut<Market>> LinkedSupplyPositionMut<M> {
Self(LinkedSupplyPosition::new(market, account_id, position))
}

/// In order for yield calculations to be accurate, this function MUST
/// BE CALLED every time a supply position's deposit changes. This
/// requirement is largely met by virtue of the fact that
/// `SupplyPosition->borrow_asset_deposit` is a private field and can only
/// be modified via methods on this type.
pub fn accumulate_yield(&mut self) {
pub fn accumulate_yield(&mut self) -> YieldAccumulationProof {
self.market.borrow_mut().snapshot();

let accumulation_record = self.calculate_yield();
Expand All @@ -222,17 +224,18 @@ impl<M: BorrowMut<Market>> LinkedSupplyPositionMut<M> {
self.position
.borrow_asset_yield
.accumulate(accumulation_record);

YieldAccumulationProof(())
}

pub fn record_withdrawal(
&mut self,
proof: YieldAccumulationProof,
mut amount: BorrowAssetAmount,
block_timestamp_ms: u64,
) -> WithdrawalResolution {
self.accumulate_yield();

self.position
.decrease_borrow_asset_deposit(amount)
.decrease_borrow_asset_deposit(proof, amount)
.unwrap_or_else(|| env::panic_str("Supply position borrow asset underflow"));

self.market
Expand Down Expand Up @@ -271,11 +274,14 @@ impl<M: BorrowMut<Market>> LinkedSupplyPositionMut<M> {
}
}

pub fn record_deposit(&mut self, amount: BorrowAssetAmount, block_timestamp_ms: u64) {
self.accumulate_yield();

pub fn record_deposit(
&mut self,
proof: YieldAccumulationProof,
amount: BorrowAssetAmount,
block_timestamp_ms: u64,
) {
self.position
.increase_borrow_asset_deposit(amount, block_timestamp_ms)
.increase_borrow_asset_deposit(proof, amount, block_timestamp_ms)
.unwrap_or_else(|| env::panic_str("Supply position borrow asset overflow"));

self.market
Expand Down
14 changes: 8 additions & 6 deletions contract/market/src/impl_helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ impl Contract {
pub fn execute_supply(&mut self, account_id: AccountId, amount: BorrowAssetAmount) {
let supply_maximum_amount = self.configuration.supply_maximum_amount;
let mut supply_position = self.get_or_create_linked_supply_position_mut(account_id);
supply_position.record_deposit(amount, env::block_timestamp_ms());
let proof = supply_position.accumulate_yield();
supply_position.record_deposit(proof, amount, env::block_timestamp_ms());
if let Some(ref supply_maximum_amount) = supply_maximum_amount {
require!(
supply_position.inner().get_borrow_asset_deposit() <= *supply_maximum_amount,
Expand Down Expand Up @@ -51,7 +52,8 @@ impl Contract {
// borrowed + fees/interest.
// -- https://github.com/Templar-Protocol/contract-mvp/pull/6#discussion_r1923876327

borrow_position.record_repay(amount)
let proof = borrow_position.accumulate_interest();
borrow_position.record_repay(proof, amount)
} else {
// No borrow exists: just return the whole amount.
amount
Expand Down Expand Up @@ -206,7 +208,8 @@ impl Contract {
//
// Borrow position has already been created: finalize
// withdrawal record.
borrow_position.record_borrow_asset_withdrawal(amount, fees);
let proof = borrow_position.accumulate_interest();
borrow_position.record_borrow_asset_withdrawal(proof, amount, fees);
}
PromiseResult::Failed => {
// Likely reasons for failure:
Expand Down Expand Up @@ -269,14 +272,13 @@ impl Contract {

env::log_str("The withdrawal request cannot be fulfilled at this time. Please try again later.");
self.withdrawal_queue.unlock();

if let Some(mut supply_position) =
self.get_linked_supply_position_mut(withdrawal_resolution.account_id.clone())
{
let timestamp = env::block_timestamp_ms();
let proof = supply_position.accumulate_yield();
let mut amount = withdrawal_resolution.amount_to_account;
amount.join(withdrawal_resolution.amount_to_fees);
supply_position.record_deposit(amount, timestamp);
supply_position.record_deposit(proof, amount, env::block_timestamp_ms());
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions contract/market/src/impl_market_external.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,12 +226,12 @@ impl MarketExternalInterface for Contract {
fn harvest_yield(&mut self, compounding: Option<bool>) {
let predecessor = env::predecessor_account_id();
if let Some(mut supply_position) = self.get_linked_supply_position_mut(predecessor) {
supply_position.accumulate_yield();
let proof = supply_position.accumulate_yield();
if compounding.unwrap_or(false) {
// Compound yield by withdrawing it and recording it as an immediate deposit.
let total_yield = supply_position.inner().borrow_asset_yield.get_total();
supply_position.record_yield_withdrawal(total_yield);
supply_position.record_deposit(total_yield, env::block_timestamp_ms());
supply_position.record_deposit(proof, total_yield, env::block_timestamp_ms());
}
}
}
Expand Down