Skip to content

Commit

Permalink
Merge pull request #97 from laina-defi/feat/change-health-factor-thre…
Browse files Browse the repository at this point in the history
…shold-and-add-interest-multiplier

feat: interest_rate_multiplier and new hf treshold
  • Loading branch information
Teolhyn authored Feb 11, 2025
2 parents 5c16840 + 3a8cfac commit 58e90d1
Show file tree
Hide file tree
Showing 8 changed files with 98 additions and 34 deletions.
69 changes: 44 additions & 25 deletions contracts/loan_manager/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,11 +160,12 @@ impl LoanManager {
borrowed,
collateral_currency.ticker,
collateral,
collateral_from.clone(),
)?;

// Health factor has to be over 1.2 for the loan to be initialized.
// Health factor is defined as so: 1.0 = 10000000_i128
const HEALTH_FACTOR_THRESHOLD: i128 = 12000000;
const HEALTH_FACTOR_THRESHOLD: i128 = 10000000;
assert!(
health_factor > HEALTH_FACTOR_THRESHOLD,
"Health factor must be over {HEALTH_FACTOR_THRESHOLD} to create a new loan!"
Expand Down Expand Up @@ -240,6 +241,7 @@ impl LoanManager {
new_borrowed_amount,
token_collateral_ticker,
collateral_amount,
collateral_from.clone(),
)?;

let borrow_change = new_borrowed_amount
Expand Down Expand Up @@ -280,19 +282,28 @@ impl LoanManager {
token_amount: i128,
token_collateral_ticker: Symbol,
token_collateral_amount: i128,
token_collateral_address: Address,
) -> Result<i128, Error> {
const DECIMAL_TO_INT_MULTIPLIER: i128 = 10000000;
let reflector_address = Address::from_string(&String::from_str(e, REFLECTOR_ADDRESS));
let reflector_contract = oracle::Client::new(e, &reflector_address);

// get the price and calculate the value of the collateral
let collateral_asset = Asset::Other(token_collateral_ticker);

let collateral_pool_client = loan_pool::Client::new(e, &token_collateral_address);
let collateral_factor = collateral_pool_client.get_collateral_factor();

let collateral_asset_price = reflector_contract
.lastprice(&collateral_asset)
.ok_or(Error::NoLastPrice)?;
let collateral_value = collateral_asset_price
.price
.checked_mul(token_collateral_amount)
.ok_or(Error::OverOrUnderFlow)?
.checked_mul(collateral_factor)
.ok_or(Error::OverOrUnderFlow)?
.checked_div(DECIMAL_TO_INT_MULTIPLIER)
.ok_or(Error::OverOrUnderFlow)?;

// get the price and calculate the value of the borrowed asset
Expand All @@ -305,7 +316,6 @@ impl LoanManager {
.checked_mul(token_amount)
.ok_or(Error::OverOrUnderFlow)?;

const DECIMAL_TO_INT_MULTIPLIER: i128 = 10000000;
let health_factor = collateral_value
.checked_mul(DECIMAL_TO_INT_MULTIPLIER)
.ok_or(Error::OverOrUnderFlow)?
Expand Down Expand Up @@ -378,6 +388,7 @@ impl LoanManager {
new_borrowed_amount,
collateral_pool_client.get_currency().ticker,
collateral_amount,
collateral_from.clone(),
)?;

let loan = Loan {
Expand Down Expand Up @@ -475,7 +486,8 @@ impl LoanManager {
borrowed_amount,
collateral_ticker.clone(),
collateral_amount,
)? < 12000000
collateral_from.clone(),
)? < 10000000
); // Temp high value for testing
assert!(
amount
Expand Down Expand Up @@ -521,6 +533,7 @@ impl LoanManager {
new_borrowed_amount,
collateral_ticker,
new_collateral_amount,
collateral_from.clone(),
)?;

let new_loan = Loan {
Expand Down Expand Up @@ -598,7 +611,7 @@ mod tests {
// ACT
// Deploy contract using loan_manager as factory
let loan_pool_addr =
deployer_client.deploy_pool(&wasm_hash, &salt, &token.address(), &ticker, &800_000);
deployer_client.deploy_pool(&wasm_hash, &salt, &token.address(), &ticker, &8_000_000);

// ASSERT
// No authorizations needed - the contract acts as a factory.
Expand Down Expand Up @@ -630,7 +643,13 @@ mod tests {
let salt = BytesN::from_array(&e, &[0; 32]);

// ACT
deployer_client.deploy_pool(&pool_wasm_hash, &salt, &token.address(), &ticker, &800_000);
deployer_client.deploy_pool(
&pool_wasm_hash,
&salt,
&token.address(),
&ticker,
&8_000_000,
);
deployer_client.upgrade(&manager_wasm_hash, &pool_wasm_hash);
}

Expand Down Expand Up @@ -683,10 +702,10 @@ mod tests {

// ACT
// Initialize the loan pool and deposit some of the admin's funds.
loan_pool_client.initialize(&contract_id, &loan_currency, &800_000);
loan_pool_client.initialize(&contract_id, &loan_currency, &8_000_000);
loan_pool_client.deposit(&admin, &1000);

collateral_pool_client.initialize(&contract_id, &collateral_currency, &800_000);
collateral_pool_client.initialize(&contract_id, &collateral_currency, &8_000_000);

contract_client.create_loan(&user, &10, &loan_pool_id, &100, &collateral_pool_id);

Expand Down Expand Up @@ -750,10 +769,10 @@ mod tests {

// ACT
// Initialize the loan pool and deposit some of the admin's funds.
loan_pool_client.initialize(&contract_id, &loan_currency, &800_000);
loan_pool_client.initialize(&contract_id, &loan_currency, &8_000_000);
loan_pool_client.deposit(&admin, &10_001);

collateral_pool_client.initialize(&contract_id, &collateral_currency, &800_000);
collateral_pool_client.initialize(&contract_id, &collateral_currency, &8_000_000);

// Create a loan.
contract_client.create_loan(&user, &10_000, &loan_pool_id, &100_000, &collateral_pool_id);
Expand All @@ -765,7 +784,7 @@ mod tests {

// Here borrowed amount should be the same as time has not moved. add_interest() is only called to store the LastUpdate sequence number.
assert_eq!(user_loan.borrowed_amount, 10_000);
assert_eq!(user_loan.health_factor, 100_000_000);
assert_eq!(user_loan.health_factor, 80_000_000);
assert_eq!(collateral_token_client.balance(&user), 900_000);

// Move time
Expand All @@ -783,7 +802,7 @@ mod tests {
let user_loan = contract_client.get_loan(&user);

assert_eq!(user_loan.borrowed_amount, 12_998);
assert_eq!(user_loan.health_factor, 76_934_913);
assert_eq!(user_loan.health_factor, 61_547_930);
assert_eq!(user_loan.collateral_amount, 100_000);
}

Expand Down Expand Up @@ -843,10 +862,10 @@ mod tests {

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

collateral_pool_client.initialize(&contract_id, &collateral_currency, &800_000);
collateral_pool_client.initialize(&contract_id, &collateral_currency, &8_000_000);

// Create a loan.
contract_client.create_loan(&user, &1_000, &loan_pool_id, &100_000, &collateral_pool_id);
Expand Down Expand Up @@ -936,10 +955,10 @@ mod tests {

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

collateral_pool_client.initialize(&contract_id, &collateral_currency, &800_000);
collateral_pool_client.initialize(&contract_id, &collateral_currency, &8_000_000);

// Create a loan.
contract_client.create_loan(&user, &1_000, &loan_pool_id, &100_000, &collateral_pool_id);
Expand Down Expand Up @@ -1022,10 +1041,10 @@ mod tests {

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

collateral_pool_client.initialize(&contract_id, &collateral_currency, &800_000);
collateral_pool_client.initialize(&contract_id, &collateral_currency, &8_000_000);

// Create a loan.
contract_client.create_loan(&user, &1_000, &loan_pool_id, &100_000, &collateral_pool_id);
Expand Down Expand Up @@ -1087,13 +1106,13 @@ mod tests {

// ACT
// Initialize the loan pool and deposit some of the admin's funds.
loan_pool_client.initialize(&contract_id, &loan_currency, &800_000);
loan_pool_client.initialize(&contract_id, &loan_currency, &8_000_000);
loan_pool_client.deposit(&admin, &10_001);

collateral_pool_client.initialize(&contract_id, &collateral_currency, &800_000);
collateral_pool_client.initialize(&contract_id, &collateral_currency, &8_000_000);

// Create a loan.
contract_client.create_loan(&user, &10_000, &loan_pool_id, &12_001, &collateral_pool_id);
contract_client.create_loan(&user, &10_000, &loan_pool_id, &12_505, &collateral_pool_id);

let user_loan = contract_client.get_loan(&user);

Expand All @@ -1103,7 +1122,7 @@ mod tests {

// Here borrowed amount should be the same as time has not moved. add_interest() is only called to store the LastUpdate sequence number.
assert_eq!(user_loan.borrowed_amount, 10_000);
assert_eq!(user_loan.health_factor, 12_001_000);
assert_eq!(user_loan.health_factor, 10_004_000);

// Move time
e.ledger().with_mut(|li| {
Expand All @@ -1120,8 +1139,8 @@ mod tests {
let user_loan = contract_client.get_loan(&user);

assert_eq!(user_loan.borrowed_amount, 12_998);
assert_eq!(user_loan.health_factor, 9_232_958);
assert_eq!(user_loan.collateral_amount, 12_001);
assert_eq!(user_loan.health_factor, 7_696_568);
assert_eq!(user_loan.collateral_amount, 12_505);

e.ledger().with_mut(|li| {
li.sequence_number = 100_000 + 1_000;
Expand All @@ -1135,7 +1154,7 @@ mod tests {
let user_loan = contract_client.get_loan(&user);

assert_eq!(user_loan.borrowed_amount, 7_998);
assert_eq!(user_loan.health_factor, 8_440_860);
assert_eq!(user_loan.collateral_amount, 6_751);
assert_eq!(user_loan.health_factor, 7_256_814);
assert_eq!(user_loan.collateral_amount, 7_255);
}
}
2 changes: 2 additions & 0 deletions contracts/loan_manager/src/positions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ fn write_positions(e: &Env, addr: Address, loan: Loan) {
e.storage()
.persistent()
.extend_ttl(&key, POSITIONS_LIFETIME_THRESHOLD, POSITIONS_BUMP_AMOUNT);

e.events().publish(("Loan", "created"), key);
}

pub fn read_positions(e: &Env, addr: Address) -> Option<Loan> {
Expand Down
19 changes: 16 additions & 3 deletions contracts/loan_pool/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ impl LoanPoolContract {
pool::write_available_balance(&e, 0);
pool::write_accrual(&e, 10_000_000); // Default initial accrual value.
pool::write_accrual_last_updated(&e, e.ledger().timestamp());
pool::change_interest_rate_multiplier(&e, 1); // Temporary parameter
}

pub fn upgrade(e: Env, new_wasm_hash: BytesN<32>) -> Result<(), Error> {
Expand All @@ -46,6 +47,14 @@ impl LoanPoolContract {
Ok(())
}

pub fn change_interest_rate_multiplier(e: Env, multiplier: i128) -> Result<(), Error> {
let loan_manager_addr = pool::read_loan_manager_addr(&e)?;
loan_manager_addr.require_auth();

pool::change_interest_rate_multiplier(&e, multiplier);
Ok(())
}

/// Deposits token. Also, mints pool shares for the "user" Identifier.
pub fn deposit(e: Env, user: Address, amount: i128) -> Result<i128, Error> {
user.require_auth();
Expand Down Expand Up @@ -258,8 +267,12 @@ impl LoanPoolContract {
Ok(())
}

pub fn get_accrual(e: Env) -> Result<i128, Error> {
pool::read_accrual(&e)
pub fn get_accrual(e: &Env) -> Result<i128, Error> {
pool::read_accrual(e)
}

pub fn get_collateral_factor(e: &Env) -> Result<i128, Error> {
pool::read_collateral_factor(e)
}

/// Get user's positions in the pool
Expand Down Expand Up @@ -435,7 +448,7 @@ mod test {
Env, Symbol,
};

const TEST_LIQUIDATION_THRESHOLD: i128 = 800_000;
const TEST_LIQUIDATION_THRESHOLD: i128 = 8_000_000;

#[test]
fn initialize() {
Expand Down
9 changes: 8 additions & 1 deletion contracts/loan_pool/src/interest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub const PANIC_BASE_RATE: i128 = -17_000_000;
#[allow(dead_code, unused_variables)]

pub fn get_interest(e: Env) -> Result<i128, Error> {
let interest_rate_multiplier = pool::read_interest_rate_multiplier(&e)?;
const PANIC_RATES_THRESHOLD: i128 = 90_000_000;
let available = pool::read_available_balance(&e)?;
let total = pool::read_total_balance(&e)?;
Expand Down Expand Up @@ -50,6 +51,8 @@ pub fn get_interest(e: Env) -> Result<i128, Error> {
.checked_div(10_000_000)
.ok_or(Error::OverOrUnderFlow)?
.checked_add(BASE_INTEREST_RATE)
.ok_or(Error::OverOrUnderFlow)?
.checked_mul(interest_rate_multiplier)
.ok_or(Error::OverOrUnderFlow)?)
} else {
Ok((slope_after_panic
Expand All @@ -58,9 +61,13 @@ pub fn get_interest(e: Env) -> Result<i128, Error> {
.checked_div(10_000_000)
.ok_or(Error::OverOrUnderFlow)?
.checked_add(PANIC_BASE_RATE)
.ok_or(Error::OverOrUnderFlow)?
.checked_mul(interest_rate_multiplier)
.ok_or(Error::OverOrUnderFlow)?)
}
} else {
Ok(BASE_INTEREST_RATE)
Ok(BASE_INTEREST_RATE
.checked_mul(interest_rate_multiplier)
.ok_or(Error::OverOrUnderFlow)?)
}
}
21 changes: 21 additions & 0 deletions contracts/loan_pool/src/pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ pub enum Error {
NegativeDeposit = 10,
WithdrawOverBalance = 11,
WithdrawIsNegative = 12,
InterestRateMultiplier = 13,
}

pub fn write_loan_manager_addr(e: &Env, loan_manager_addr: Address) {
Expand Down Expand Up @@ -182,3 +183,23 @@ pub fn read_accrual_last_updated(e: &Env) -> Result<u64, Error> {
Err(Error::AccrualLastUpdated)
}
}

pub fn change_interest_rate_multiplier(e: &Env, multiplier: i128) {
e.storage()
.persistent()
.set(&PoolDataKey::InterestRateMultiplier, &multiplier);
}

pub fn read_interest_rate_multiplier(e: &Env) -> Result<i128, Error> {
e.storage()
.persistent()
.get(&PoolDataKey::InterestRateMultiplier)
.ok_or(Error::InterestRateMultiplier)
}

pub fn read_collateral_factor(e: &Env) -> Result<i128, Error> {
e.storage()
.persistent()
.get(&PoolDataKey::LiquidationThreshold)
.ok_or(Error::LiquidationThreshold)
}
2 changes: 2 additions & 0 deletions contracts/loan_pool/src/storage_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ pub enum PoolDataKey {
Accrual,
// Last update ledger of accrual
AccrualLastUpdate,
// Interest rate multiplier
InterestRateMultiplier,
}

/* Persistent ttl bumper */
Expand Down
2 changes: 1 addition & 1 deletion scripts/initialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ const deployLoanPools = () => {
--salt ${salt} \
--token_address ${tokenContractAddress} \
--ticker ${ticker} \
--liquidation_threshold 800000 \
--liquidation_threshold 8000000 \
| tr -d '"' > ./.stellar/contract-ids/${loanPoolName}.txt`,
);
});
Expand Down
8 changes: 4 additions & 4 deletions src/components/HealthFactor.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export const HEALTH_FACTOR_AUTO_THRESHOLD = 1.65;
export const HEALTH_FACTOR_MIN_THRESHOLD = 1.2;
export const HEALTH_FACTOR_GOOD_THRESHOLD = 1.6;
export const HEALTH_FACTOR_EXCELLENT_THRESHOLD = 2.0;
export const HEALTH_FACTOR_AUTO_THRESHOLD = 1.4;
export const HEALTH_FACTOR_MIN_THRESHOLD = 1.25;
export const HEALTH_FACTOR_GOOD_THRESHOLD = 1.35;
export const HEALTH_FACTOR_EXCELLENT_THRESHOLD = 1.45;

export const HealthFactor = ({ value }: { value: number }) => {
if (value < HEALTH_FACTOR_MIN_THRESHOLD) {
Expand Down

0 comments on commit 58e90d1

Please sign in to comment.