Skip to content

Commit

Permalink
Merge pull request #51 from laina-protocol/feat/variable-interest-rates
Browse files Browse the repository at this point in the history
feat: variable interest rate for contracts
  • Loading branch information
Teolhyn authored Oct 17, 2024
2 parents 826396e + 85f0fa3 commit 0714e12
Show file tree
Hide file tree
Showing 2 changed files with 188 additions and 5 deletions.
168 changes: 168 additions & 0 deletions contracts/loan_manager/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,10 @@ impl LoanManager {

(borrowed_amount, new_borrowed_amount)
}

pub fn get_interest_rate(e: Env, pool: Address) -> i128 {
get_interest(e, pool)
}
}

#[cfg(test)]
Expand Down Expand Up @@ -622,6 +626,170 @@ mod tests {
assert_eq!(user_loan.collateral_amount, 100_000);
}

#[test]
fn interest_at_max_usage() {
// 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 = 1_000_000;
li.min_temp_entry_ttl = 1_000_000;
li.max_entry_ttl = 1_000_001;
});

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);
loan_asset.mint(&admin, &1_000_000);
let loan_currency = loan_pool::Currency {
token_address: loan_token_contract_id.clone(),
ticker: Symbol::new(&e, "XLM"),
};

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);
let collateral_currency = loan_pool::Currency {
token_address: collateral_token_contract_id.clone(),
ticker: Symbol::new(&e, "USDC"),
};

// 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, &10_000_000);

assert_eq!(collateral_token.balance(&user), 10_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, LoanManager);
let contract_client = LoanManagerClient::new(&e, &contract_id);

// 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.deposit(&admin, &1_000_000);

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

// Create a loan.
contract_client.create_loan(
&user,
&999_000,
&loan_pool_id,
&10_000_000,
&collateral_pool_id,
);

contract_client.add_interest();

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

// A new instance of reflector mock needs to be created, they only live for one ledger.
let reflector_addr = Address::from_string(&String::from_str(&e, REFLECTOR_ADDRESS));
e.register_contract_wasm(&reflector_addr, oracle::WASM);

assert_eq!(contract_client.get_interest_rate(&loan_pool_id), 2_980_000);
}

#[test]
fn interest_at_half_usage() {
// 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 = 1_000_000;
li.min_temp_entry_ttl = 1_000_000;
li.max_entry_ttl = 1_000_001;
});

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);
loan_asset.mint(&admin, &1_000_000);
let loan_currency = loan_pool::Currency {
token_address: loan_token_contract_id.clone(),
ticker: Symbol::new(&e, "XLM"),
};

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);
let collateral_currency = loan_pool::Currency {
token_address: collateral_token_contract_id.clone(),
ticker: Symbol::new(&e, "USDC"),
};

// 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, &10_000_000);

assert_eq!(collateral_token.balance(&user), 10_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, LoanManager);
let contract_client = LoanManagerClient::new(&e, &contract_id);

// 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.deposit(&admin, &1_000_000);

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

// Create a loan.
contract_client.create_loan(
&user,
&500_000,
&loan_pool_id,
&10_000_000,
&collateral_pool_id,
);

contract_client.add_interest();

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

// A new instance of reflector mock needs to be created, they only live for one ledger.
let reflector_addr = Address::from_string(&String::from_str(&e, REFLECTOR_ADDRESS));
e.register_contract_wasm(&reflector_addr, oracle::WASM);

assert_eq!(contract_client.get_interest_rate(&loan_pool_id), 644_440);
}

#[test]
fn repay() {
// ARRANGE
Expand Down
25 changes: 20 additions & 5 deletions contracts/loan_manager/src/interest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,30 @@ mod loan_pool {
);
}
#[allow(dead_code)]
pub const BASE_INTEREST_RATE: i128 = 200_000; // TODO: This should be based on what pool it is. Current 2 %.
// These are mockup numbers that should be set to each pool based on the token.
pub const BASE_INTEREST_RATE: i128 = 200_000; // 2%
pub const INTEREST_RATE_AT_PANIC: i128 = 1_000_000; // 10%
pub const MAX_INTEREST_RATE: i128 = 3_000_000; // 30%
pub const PANIC_BASE_RATE: i128 = -17_000_000;

#[allow(dead_code, unused_variables)]

pub fn get_interest(e: Env, pool: Address) -> i128 {
const PANIC_RATES_THRESHOLD: i128 = 90_000_000;
let pool_client = loan_pool::Client::new(&e, &pool);
//let available = pool_client.get_available_balance
//let total = pool_client.get_total_balance
let available = pool_client.get_available_balance();
let total = pool_client.get_contract_balance();

//let borrowed_rate = (available - total) * decimal / total
BASE_INTEREST_RATE
let slope_before_panic =
(INTEREST_RATE_AT_PANIC - BASE_INTEREST_RATE) * 10_000_000 / PANIC_RATES_THRESHOLD;
let slope_after_panic = (MAX_INTEREST_RATE - INTEREST_RATE_AT_PANIC) * 10_000_000
/ (100_000_000 - PANIC_RATES_THRESHOLD);

let ratio_of_balances = ((total - available) * 100_000_000) / total; // correct

if ratio_of_balances < PANIC_RATES_THRESHOLD {
(slope_before_panic * ratio_of_balances) / 10_000_000 + BASE_INTEREST_RATE
} else {
(slope_after_panic * ratio_of_balances) / 10_000_000 + PANIC_BASE_RATE
}
}

0 comments on commit 0714e12

Please sign in to comment.