Skip to content

Commit

Permalink
feat: interest rate model setter (#73)
Browse files Browse the repository at this point in the history
Upon setting a new interest rate model, pending interests are settled
immediately using the old rates.
  • Loading branch information
xJonathanLEI committed Mar 13, 2024
1 parent cd08ab9 commit 3477460
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 0 deletions.
4 changes: 4 additions & 0 deletions src/interfaces.cairo
Expand Up @@ -110,6 +110,10 @@ trait IMarket<TContractState> {

fn set_treasury(ref self: TContractState, new_treasury: ContractAddress);

fn set_interest_rate_model(
ref self: TContractState, token: ContractAddress, interest_rate_model: ContractAddress
);

fn set_collateral_factor(
ref self: TContractState, token: ContractAddress, collateral_factor: felt252
);
Expand Down
13 changes: 13 additions & 0 deletions src/market.cairo
Expand Up @@ -47,6 +47,7 @@ mod Market {
TreasuryUpdate: TreasuryUpdate,
AccumulatorsSync: AccumulatorsSync,
InterestRatesSync: InterestRatesSync,
InterestRateModelUpdate: InterestRateModelUpdate,
CollateralFactorUpdate: CollateralFactorUpdate,
BorrowFactorUpdate: BorrowFactorUpdate,
DebtLimitUpdate: DebtLimitUpdate,
Expand Down Expand Up @@ -94,6 +95,12 @@ mod Market {
borrowing_rate: felt252
}

#[derive(Drop, PartialEq, starknet::Event)]
struct InterestRateModelUpdate {
token: ContractAddress,
interest_rate_model: ContractAddress
}

#[derive(Drop, PartialEq, starknet::Event)]
struct CollateralFactorUpdate {
token: ContractAddress,
Expand Down Expand Up @@ -337,6 +344,12 @@ mod Market {
external::set_treasury(ref self, new_treasury)
}

fn set_interest_rate_model(
ref self: ContractState, token: ContractAddress, interest_rate_model: ContractAddress
) {
external::set_interest_rate_model(ref self, token, interest_rate_model)
}

fn set_collateral_factor(
ref self: ContractState, token: ContractAddress, collateral_factor: felt252
) {
Expand Down
34 changes: 34 additions & 0 deletions src/market/external.cairo
Expand Up @@ -253,6 +253,40 @@ fn set_treasury(ref self: ContractState, new_treasury: ContractAddress) {
self.emit(contract::Event::TreasuryUpdate(contract::TreasuryUpdate { new_treasury }));
}

fn set_interest_rate_model(
ref self: ContractState, token: ContractAddress, interest_rate_model: ContractAddress
) {
ownable::assert_only_owner(@self);

assert(interest_rate_model.is_non_zero(), errors::ZERO_ADDRESS);

internal::assert_reserve_exists(@self, token);

// Settles interest payments up until this point to prevent retrospective changes.
let UpdatedAccumulators{debt_accumulator: updated_debt_accumulator, .. } =
internal::update_accumulators(
ref self, token
);

self.reserves.write_interest_rate_model(token, interest_rate_model);
self
.emit(
contract::Event::InterestRateModelUpdate(
contract::InterestRateModelUpdate { token, interest_rate_model }
)
);

internal::update_rates_and_raw_total_debt(
ref self,
token, // token
updated_debt_accumulator, // updated_debt_accumulator
false, // is_delta_reserve_balance_negative
0, // abs_delta_reserve_balance
false, // is_delta_raw_total_debt_negative
0 // abs_delta_raw_total_debt
);
}

fn set_collateral_factor(
ref self: ContractState, token: ContractAddress, collateral_factor: felt252
) {
Expand Down
13 changes: 13 additions & 0 deletions src/market/storage.cairo
Expand Up @@ -102,6 +102,10 @@ trait ReservesStorageShortcuts<T> {

fn write_raw_total_debt(self: @T, token: ContractAddress, raw_total_debt: felt252);

fn write_interest_rate_model(
self: @T, token: ContractAddress, interest_rate_model: ContractAddress
);

fn write_collateral_factor(self: @T, token: ContractAddress, collateral_factor: felt252);

fn write_borrow_factor(self: @T, token: ContractAddress, borrow_factor: felt252);
Expand Down Expand Up @@ -277,6 +281,15 @@ impl ReservesStorageShortcutsImpl of ReservesStorageShortcuts<Reserves> {
Store::<felt252>::write_at_offset(D, base, 12, raw_total_debt).expect(E);
}

fn write_interest_rate_model(
self: @Reserves, token: ContractAddress, interest_rate_model: ContractAddress
) {
let base = self.address(token);

Store::<ContractAddress>::write_at_offset(D, base, 3, interest_rate_model).expect(E);
}


fn write_collateral_factor(
self: @Reserves, token: ContractAddress, collateral_factor: felt252
) {
Expand Down
63 changes: 63 additions & 0 deletions tests/market.cairo
Expand Up @@ -1392,6 +1392,69 @@ fn test_flashloan_fee_distribution() {
assert_eq(@reserve_data.current_borrowing_rate, @50556930693069306930693069, 'FAILED');
}

#[test]
#[available_gas(90000000)]
fn test_change_interest_rate_model() {
let setup = setup_with_loan();

// (Copied from `test_rates_changed_on_borrow`)
// Borrowing rate:
// Utilization rate = 22.5 / 10,000 = 0.00225
// Borrowing rate = 0.05 + 0.2 * 0.00225 / 0.8 = 0.0505625 => 505625 * 10 ** 20
// Lending rate:
// Lending rate = 0.0505625 * 0.00225 = 0.000113765625 => 113765625 * 10 ** 15
let reserve_data = setup.market.get_reserve_data(setup.token_b.contract_address);
assert_eq(@reserve_data.current_lending_rate, @113765625000000000000000, 'FAILED');
assert_eq(@reserve_data.current_borrowing_rate, @50562500000000000000000000, 'FAILED');

starknet::testing::set_block_timestamp(100);

// (Copied from `test_interest_accumulation`)
// Interest after 100 seconds:
// Interest = 0.000113765625 * 10000 * 100 * (1 - 20%) / (365 * 86400) = 0.000002885987442922374429223
// => 2885987442922
// Total balance = 10000 * 10 ** 18 + 2885987442922
assert_eq(
@setup.z_token_b.balanceOf(setup.bob.contract_address), @10000000002885987442922, 'FAILED'
);

// Change model to have higher interest rate
let irm_b = deploy::deploy_default_interest_rate_model(
400000000000000000000000000, // slope_0: 0.4
300000000000000000000000000, // slope_1: 0.3
500000000000000000000000000, // y_intercept: 50%
800000000000000000000000000, // optimal_rate: 80%
);
setup
.alice
.market_set_interest_rate_model(
setup.market.contract_address,
setup.token_b.contract_address, // token
irm_b.contract_address // interest_rate_model
);

// Debt interest during 100 seconds:
// Interest = 0.0505625 * 22.5 * 100 / (365 * 86400) = 0.000003607484303652

// New borrowing rate:
// Utilization rate = 22.500003607484303652 / 10000.000003607484303652 = 0.002250000359936746267031683
// Borrowing rate = 0.5 + 0.4 * 0.002250000359936746267031683 / 0.8 = 0.501125000179968373133515841
// New lending rate:
// Lending rate = 0.501125000179968373133515841 * 0.002250000359936746267031683 = 0.001127531430778230877393893
let reserve_data = setup.market.get_reserve_data(setup.token_b.contract_address);
assert_eq(@reserve_data.current_lending_rate, @1127531430778230877393893, 'FAILED');
assert_eq(@reserve_data.current_borrowing_rate, @501125000179968373133515841, 'FAILED');

starknet::testing::set_block_timestamp(200);

// Another 100 seconds (accumulating with the new rate):
// Interest = 0.001127531430778230877393893 * 10000.000002885987442922 * 100 * (1 - 20%) / (365 * 86400) = 0.000028603029708362
// Total balance = 10000.000002885987442922 + 0.000028603029708362 = 10000.000031489017151284
assert_eq(
@setup.z_token_b.balanceOf(setup.bob.contract_address), @10000000031489017151284, 'FAILED'
);
}

#[test]
#[available_gas(90000000)]
#[should_panic(expected: ('MKT_INSUFFICIENT_COLLATERAL', 'ENTRYPOINT_FAILED', 'ENTRYPOINT_FAILED'))]
Expand Down
7 changes: 7 additions & 0 deletions tests/mock.cairo
Expand Up @@ -67,6 +67,13 @@ trait IAccount<TContractState> {
ref self: TContractState, contract_address: ContractAddress, new_treasury: ContractAddress
);

fn market_set_interest_rate_model(
ref self: TContractState,
contract_address: ContractAddress,
token: ContractAddress,
interest_rate_model: ContractAddress
);

fn market_set_collateral_factor(
ref self: TContractState,
contract_address: ContractAddress,
Expand Down
11 changes: 11 additions & 0 deletions tests/mock/account.cairo
Expand Up @@ -56,6 +56,17 @@ mod Account {
IMarketDispatcher { contract_address }.set_treasury(new_treasury)
}

fn market_set_interest_rate_model(
ref self: ContractState,
contract_address: ContractAddress,
token: ContractAddress,
interest_rate_model: ContractAddress
) {
IMarketDispatcher {
contract_address
}.set_interest_rate_model(token, interest_rate_model)
}

fn market_set_collateral_factor(
ref self: ContractState,
contract_address: ContractAddress,
Expand Down

0 comments on commit 3477460

Please sign in to comment.