Skip to content

Commit

Permalink
Added inital logic for interest calculation and updating of interest …
Browse files Browse the repository at this point in the history
…and health-factor
  • Loading branch information
Teolhyn committed Sep 4, 2024
1 parent 8d7ac50 commit be3affc
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 17 deletions.
115 changes: 100 additions & 15 deletions contracts/loan_manager/src/contract.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
use crate::interest::get_interest;
use crate::oracle::{self, Asset};
use crate::positions;
use crate::storage_types::{LoansDataKey, POSITIONS_BUMP_AMOUNT, POSITIONS_LIFETIME_THRESHOLD};
use crate::storage_types::{
Loan, LoansDataKey, POSITIONS_BUMP_AMOUNT, POSITIONS_LIFETIME_THRESHOLD, DAY_IN_LEDGERS,
};

use soroban_sdk::{
contract, contractimpl, vec, Address, Env, IntoVal, Map, String, Symbol, TryFromVal, Val, Vec,
contract, contractimpl, vec, Address, Env, IntoVal, String, Symbol, TryFromVal, Val, Vec
};

mod loan_pool {
Expand Down Expand Up @@ -105,6 +108,7 @@ impl LoansTrait for LoansContract {
}

fn add_interest(e: Env) {
const DECIMAL: i128 = 1000000;
/*
We calculate interest for ledgers_between from a given APY approximation simply by dividing the rate r with ledgers in a year
and multiplying it with ledgers_between. This would result in slightly different total yearly interest, e.g. 12% -> 12.7% total.
Expand All @@ -123,7 +127,8 @@ impl LoansTrait for LoansContract {
let previous_ledger: u32 =
u32::try_from_val(&e, &previous_ledger_val).expect("Failed to convert Val to u32");

let _ledgers_since_update: u32 = current_ledger - previous_ledger; // Currently unused but is a placeholder for interest calculations. Now time is handled.
let ledgers_since_update: u32 = current_ledger - previous_ledger; // Currently unused but is a placeholder for interest calculations. Now time is handled.
let ledger_ratio: i128 = (i128::from(ledgers_since_update) * DECIMAL) / (i128::from(DAY_IN_LEDGERS * 365));

// Update current ledger as the new 'last time'

Expand All @@ -138,27 +143,30 @@ impl LoansTrait for LoansContract {
for user in addresses.iter() {
let key = (Symbol::new(&e, "Loan"), user.clone());

let loan: Val = e
let loan_val: Val = e
.storage()
.persistent()
.get::<(Symbol, Address), Val>(&key)
.unwrap();

let mut loan_map: Map<Symbol, Val> =
Map::try_from_val(&e, &loan).expect("Failed to convert Val to Map");
let mut loan: Loan =
Loan::try_from_val(&e, &loan_val).expect("Failed to convert Val to Loan");

let borrowed: Val = loan_map.get(Symbol::new(&e, "borrowed")).unwrap();
let borrowed_as_int: i128 =
i128::try_from_val(&e, &borrowed).expect("Failed to convert Val to i128");
let borrowed: i128 = loan.borrowed_amount;

// FIXME: the calculation doesn't work, perhaps because of the change in types. OR it could be that the value is not retrieved properly
let interest_rate: i128 = 12000;
let interest_amount: i128 = borrowed_as_int * interest_rate;
let new_borrowed: i128 = borrowed_as_int + interest_amount;
let interest_rate: i128 = get_interest(e.clone(), loan.borrowed_from.clone());
let interest_amount_in_year: i128 = (borrowed * interest_rate) / DECIMAL;
let interest_since_update: i128 = (interest_amount_in_year * ledger_ratio) / DECIMAL;
let new_borrowed: i128 = borrowed + interest_since_update;
// Insert the new value to the loan_map
loan_map.set(Symbol::new(&e, "borrowed"), new_borrowed.into_val(&e));
loan.borrowed_amount = new_borrowed;
// Get updated health_factor
let token_ticker: Symbol = Symbol::new(&e, "USDC"); // temporary
let collateral_ticker: Symbol = Symbol::new(&e, "XLM"); // temporary
loan.health_factor = Self::calculate_health_factor(&e, token_ticker, new_borrowed, collateral_ticker, loan.collateral_amount); // It now calls reflector for each address. This is safe but might end up being costly
// Transform the Map back to Val
let new_loan: Val = loan_map.into_val(&e);
let new_loan: Val = loan.into_val(&e);
// Set it to storage
e.storage().persistent().set(&key, &new_loan);
e.storage().persistent().extend_ttl(
Expand All @@ -170,6 +178,7 @@ impl LoansTrait for LoansContract {
}

e.storage().persistent().set(&key, &current_ledger);
e.storage().persistent().extend_ttl(&key, POSITIONS_LIFETIME_THRESHOLD, POSITIONS_BUMP_AMOUNT);
}

fn calculate_health_factor(
Expand Down Expand Up @@ -203,7 +212,7 @@ impl LoansTrait for LoansContract {
mod tests {
use super::*;
use soroban_sdk::{
testutils::Address as _,
testutils::{Address as _, Ledger},
token::{Client as TokenClient, StellarAssetClient},
Env, TryIntoVal,
};
Expand Down Expand Up @@ -264,9 +273,85 @@ mod tests {

// Create a loan.
contract_client.initialize(&user, &10, &loan_pool_id, &100, &collateral_pool_id);


// ASSERT
assert_eq!(loan_token.balance(&user), 10);
assert_eq!(collateral_token.balance(&user), 900);
}

#[test]
fn add_interest() {
// ARRANGE
let e = Env::default();
e.mock_all_auths_allowing_non_root_auth();
e.ledger().with_mut(|li| {
li.sequence_number = 100_000;
li.min_persistent_entry_ttl = 10000;
li.min_temp_entry_ttl = 10000;
li.max_entry_ttl = 1_000_000;
});


let admin = Address::generate(&e);
let loan_token_contract_id = e.register_stellar_asset_contract(admin.clone());
let loan_asset = StellarAssetClient::new(&e, &loan_token_contract_id);
let loan_token = TokenClient::new(&e, &loan_token_contract_id);
loan_asset.mint(&admin, &1_000_000);

let admin2 = Address::generate(&e);
let collateral_token_contract_id = e.register_stellar_asset_contract(admin2.clone());
let collateral_asset = StellarAssetClient::new(&e, &collateral_token_contract_id);
let collateral_token = TokenClient::new(&e, &collateral_token_contract_id);

// Register mock Reflector contract.
let reflector_addr = Address::from_string(&String::from_str(&e, REFLECTOR_ADDRESS));
e.register_contract_wasm(&reflector_addr, oracle::WASM);

// Mint the user some coins
let user = Address::generate(&e);
collateral_asset.mint(&user, &1_000_000);

assert_eq!(collateral_token.balance(&user), 1_000_000);

// Set up a loan pool with funds for borrowing.
let loan_pool_id = e.register_contract_wasm(None, loan_pool::WASM);
let loan_pool_client = loan_pool::Client::new(&e, &loan_pool_id);

// Set up a loan_pool for the collaterals.
let collateral_pool_id = e.register_contract_wasm(None, loan_pool::WASM);
let collateral_pool_client = loan_pool::Client::new(&e, &collateral_pool_id);

// Register loan manager contract.
let contract_id = e.register_contract(None, LoansContract);
let contract_client = LoansContractClient::new(&e, &contract_id);

// ACT
// Initialize the loan pool and deposit some of the admin's funds.
loan_pool_client.initialize(&loan_token_contract_id);
loan_pool_client.deposit(&admin, &1_000_000);

collateral_pool_client.initialize(&collateral_token_contract_id);

// Create a loan.
contract_client.initialize(&user, &10_000, &loan_pool_id, &100_000, &collateral_pool_id);

assert_eq!(loan_token.balance(&user), 10_000);
assert_eq!(collateral_token.balance(&user), 900_000);

contract_client.add_interest();

assert_eq!(loan_token.balance(&user), 10_000);
assert_eq!(collateral_token.balance(&user), 900_000);

e.ledger().with_mut(|li| {
li.sequence_number = 100_000 + 5;
});

contract_client.add_interest();

assert_eq!(loan_token.balance(&user), 10_000);
assert_eq!(collateral_token.balance(&user), 900_000);

}
}
18 changes: 18 additions & 0 deletions contracts/loan_manager/src/interest.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use soroban_sdk::{Address, Env};

mod loan_pool {
soroban_sdk::contractimport!(
file = "../../target/wasm32-unknown-unknown/release/loan_pool.wasm"
);
}
#[allow(dead_code)]
pub const BASE_INTEREST_RATE: i128 = 200_000; // TODO: This should be based on what pool it is. Current 2 %.

#[allow(dead_code, unused_variables)]
pub fn get_interest(e: Env, pool: Address) -> i128 {
let pool_client = loan_pool::Client::new(&e, &pool);
//let available = pool_client.get_available_balance
//let total = pool_client.get_total_balance
//let borrowed_rate = (available - total) * decimal / total
BASE_INTEREST_RATE
}
1 change: 1 addition & 0 deletions contracts/loan_manager/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#![no_std]

mod contract;
mod interest;
mod oracle;
mod positions;
mod storage_types;
2 changes: 1 addition & 1 deletion contracts/loan_pool/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ contractmeta!(
);

// TODO: get this dynamically when creating the contract.
const LOAN_MANAGER_ADDRESS: &str = "CC62OM25XWZY6QECTFQR4NP7PINU6JN46FSDUOXC7Y5PUFJX3ZPVHZDF";
const LOAN_MANAGER_ADDRESS: &str = "CDIK5MXMF5F3TRW7XHPMUXGHDKBH2SESVDSG2RFGOP2GHXOPCDHTC7VO";

#[allow(dead_code)]
pub trait LoanPoolTrait {
Expand Down
2 changes: 1 addition & 1 deletion src/pages/_borrow/BorrowableAssetCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { Currency } from 'src/currencies';
import { useWallet } from 'src/stellar-wallet';

// Temporary hack to use XLM pool for all loans and collaterals.
const XLM_LOAN_POOL_ID = 'CBGVA2KV423WKDNYILW3BPZSNPDCQI3LXA7XYR7H36XUDOPO3UCNQ3RC';
const XLM_LOAN_POOL_ID = 'CCME5C2PJFCWGW5O5MCO4O6X3AUJLD6XHVH3ZJHC7SD7XK5OTPJRPHF7';

interface BorrowableAssetCardProps {
currency: Currency;
Expand Down

0 comments on commit be3affc

Please sign in to comment.