Skip to content

Commit

Permalink
program: add spot borrow insurance limits (#1080)
Browse files Browse the repository at this point in the history
* program: add-spot-borrow-insurance-limits

* add sdk functions

* rename validate to validate_max_token_deposits_and_borrows

* use smaller amount of spot market state

* add tests

* update sdk and use padding1 on spot account

* formatting

* CHANGELOG

---------

Co-authored-by: Chris Heaney <[email protected]>
  • Loading branch information
0xbigz and crispheaney committed Jun 20, 2024
1 parent a72d637 commit 2cfed8f
Show file tree
Hide file tree
Showing 13 changed files with 317 additions and 21 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Features

- program: add spot borrow insurance limits ([#1080](https://github.com/drift-labs/protocol-v2/pull/1080))
- program: maker can be rewarded filler returns when amm gets fill ([#1093](https://github.com/drift-labs/protocol-v2/pull/1093))
- program: avoid overwriting 0 duration auction ([#1097](https://github.com/drift-labs/protocol-v2/pull/1097))
- program: add pyth pull oracles ([#1067](https://github.com/drift-labs/protocol-v2/pull/1067))
Expand Down
2 changes: 1 addition & 1 deletion programs/drift/src/controller/orders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3675,7 +3675,7 @@ pub fn fill_spot_order(

spot_market_map
.get_ref(&order_market_index)?
.validate_max_token_deposits()?;
.validate_max_token_deposits_and_borrows()?;

user.update_last_active_slot(slot);

Expand Down
138 changes: 138 additions & 0 deletions programs/drift/src/controller/spot_balance/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1671,3 +1671,141 @@ fn check_usdc_spot_market_twap() {
966501
);
}

#[test]
fn check_spot_market_max_borrow_fraction() {
let _now = 30_i64;
let _slot = 0_u64;

let _oracle_price = get_pyth_price(1, 6);
let mut spot_market = SpotMarket {
market_index: 0,
oracle_source: OracleSource::QuoteAsset,
cumulative_deposit_interest: SPOT_CUMULATIVE_INTEREST_PRECISION,
cumulative_borrow_interest: SPOT_CUMULATIVE_INTEREST_PRECISION,
decimals: 6,
initial_asset_weight: SPOT_WEIGHT_PRECISION,
maintenance_asset_weight: SPOT_WEIGHT_PRECISION,
deposit_balance: 100_000_000 * SPOT_BALANCE_PRECISION, //$100M usdc
borrow_balance: 0,
deposit_token_twap: QUOTE_PRECISION_U64 / 2,
historical_oracle_data: HistoricalOracleData::default_quote_oracle(),
status: MarketStatus::Active,
min_borrow_rate: 0,
max_token_borrows_fraction: 1,
..SpotMarket::default()
};

assert_eq!(
spot_market.get_deposits().unwrap(),
100_000_000 * QUOTE_PRECISION
);

assert!(spot_market
.validate_max_token_deposits_and_borrows()
.is_ok());

spot_market.borrow_balance = spot_market.deposit_balance;
assert!(spot_market
.validate_max_token_deposits_and_borrows()
.is_ok());

spot_market.max_token_deposits = (100_000_000 * QUOTE_PRECISION) as u64;

assert!(spot_market
.validate_max_token_deposits_and_borrows()
.is_err());
spot_market.borrow_balance = spot_market.deposit_balance / 100;
assert!(spot_market
.validate_max_token_deposits_and_borrows()
.is_err());

spot_market.borrow_balance = spot_market.deposit_balance / (10000 - 2); // just above 10000th
assert!(spot_market
.validate_max_token_deposits_and_borrows()
.is_err());

spot_market.borrow_balance = spot_market.deposit_balance / (10000); // exactly 10000th of deposit
assert!(spot_market
.validate_max_token_deposits_and_borrows()
.is_ok());

spot_market.borrow_balance = spot_market.deposit_balance / (10000 + 1); // < 10000th of deposit
assert!(spot_market
.validate_max_token_deposits_and_borrows()
.is_ok());

spot_market.borrow_balance = spot_market.deposit_balance / 100000; // 1/10th of 10000
assert!(spot_market
.validate_max_token_deposits_and_borrows()
.is_ok());
}

#[test]
fn check_spot_market_min_borrow_rate() {
let now = 30_i64;
let _slot = 0_u64;

let _oracle_price = get_pyth_price(1, 6);
let mut spot_market = SpotMarket {
market_index: 0,
oracle_source: OracleSource::QuoteAsset,
cumulative_deposit_interest: SPOT_CUMULATIVE_INTEREST_PRECISION,
cumulative_borrow_interest: SPOT_CUMULATIVE_INTEREST_PRECISION,
decimals: 6,
initial_asset_weight: SPOT_WEIGHT_PRECISION,
maintenance_asset_weight: SPOT_WEIGHT_PRECISION,
deposit_balance: 100_000_000 * SPOT_BALANCE_PRECISION, //$100M usdc
borrow_balance: 0,
deposit_token_twap: QUOTE_PRECISION_U64 / 2,
historical_oracle_data: HistoricalOracleData::default_quote_oracle(),
status: MarketStatus::Active,
min_borrow_rate: 0,
..SpotMarket::default()
};

assert_eq!(
spot_market.get_deposits().unwrap(),
100_000_000 * QUOTE_PRECISION
);

let accum_interest = calculate_accumulated_interest(&spot_market, now + 10000).unwrap();

assert_eq!(accum_interest.borrow_interest, 0);
assert_eq!(accum_interest.deposit_interest, 0);

spot_market.min_borrow_rate = 1; // .5%

let accum_interest = calculate_accumulated_interest(&spot_market, now + 10000).unwrap();

assert_eq!(accum_interest.borrow_interest, 0);
assert_eq!(accum_interest.deposit_interest, 0);

spot_market.min_borrow_rate = 1; // .5%
spot_market.borrow_balance = spot_market.deposit_balance / 100;
let accum_interest = calculate_accumulated_interest(&spot_market, now + 10000).unwrap();

assert_eq!(accum_interest.borrow_interest, 15903);
assert_eq!(accum_interest.deposit_interest, 159);

spot_market.min_borrow_rate = 10; // 5%
spot_market.borrow_balance = spot_market.deposit_balance / 100;
let accum_interest = calculate_accumulated_interest(&spot_market, now + 10000).unwrap();

assert_eq!(accum_interest.borrow_interest, 159025);
assert_eq!(accum_interest.deposit_interest, 1590);

spot_market.min_borrow_rate = 10; // 5%
spot_market.borrow_balance = spot_market.deposit_balance / 100;
let accum_interest = calculate_accumulated_interest(&spot_market, now + 1000000).unwrap();

assert_eq!(accum_interest.borrow_interest, 15855372);
assert_eq!(accum_interest.deposit_interest, 158553);

spot_market.min_borrow_rate = 200; // 100%
spot_market.borrow_balance = spot_market.deposit_balance / 100;
let accum_interest = calculate_accumulated_interest(&spot_market, now + 1000000).unwrap();

assert_eq!(accum_interest.borrow_interest, 317107433);
assert_eq!(accum_interest.deposit_interest, 3171074);
}
2 changes: 2 additions & 0 deletions programs/drift/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,8 @@ pub enum ErrorCode {
OracleStaleForAMM,
#[msg("Unable to parse pull oracle message")]
UnableToParsePullOracleMessage,
#[msg("Can not borow more than max borrows")]
MaxBorrows,
}

#[macro_export]
Expand Down
63 changes: 58 additions & 5 deletions programs/drift/src/instructions/admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use crate::math::constants::{
DEFAULT_LIQUIDATION_MARGIN_BUFFER_RATIO, FEE_POOL_TO_REVENUE_POOL_THRESHOLD,
IF_FACTOR_PRECISION, INSURANCE_A_MAX, INSURANCE_B_MAX, INSURANCE_C_MAX,
INSURANCE_SPECULATIVE_MAX, LIQUIDATION_FEE_PRECISION, MAX_CONCENTRATION_COEFFICIENT,
MAX_SQRT_K, MAX_UPDATE_K_PRICE_CHANGE, QUOTE_SPOT_MARKET_INDEX,
MAX_SQRT_K, MAX_UPDATE_K_PRICE_CHANGE, PERCENTAGE_PRECISION, QUOTE_SPOT_MARKET_INDEX,
SPOT_CUMULATIVE_INTEREST_PRECISION, SPOT_IMF_PRECISION, SPOT_WEIGHT_PRECISION, THIRTEEN_DAY,
TWENTY_FOUR_HOUR,
};
Expand Down Expand Up @@ -128,7 +128,7 @@ pub fn handle_initialize_spot_market(
return Err(ErrorCode::InvalidInsuranceFundAuthority.into());
}

validate_borrow_rate(optimal_utilization, optimal_borrow_rate, max_borrow_rate)?;
validate_borrow_rate(optimal_utilization, optimal_borrow_rate, max_borrow_rate, 0)?;

let spot_market_index = get_then_update_id!(state, number_of_spot_markets);

Expand Down Expand Up @@ -273,12 +273,13 @@ pub fn handle_initialize_spot_market(
paused_operations: 0,
if_paused_operations: 0,
fee_adjustment: 0,
padding1: [0; 2],
max_token_borrows_fraction: 0,
flash_loan_amount: 0,
flash_loan_initial_token_amount: 0,
total_swap_fee: 0,
scale_initial_asset_weight_start,
padding: [0; 48],
min_borrow_rate: 0,
padding: [0; 47],
insurance_fund: InsuranceFund {
vault: *ctx.accounts.insurance_fund_vault.to_account_info().key,
unstaking_period: THIRTEEN_DAY,
Expand Down Expand Up @@ -2296,9 +2297,18 @@ pub fn handle_update_spot_market_borrow_rate(
optimal_utilization: u32,
optimal_borrow_rate: u32,
max_borrow_rate: u32,
min_borrow_rate: Option<u8>,
) -> Result<()> {
let spot_market = &mut load_mut!(ctx.accounts.spot_market)?;
validate_borrow_rate(optimal_utilization, optimal_borrow_rate, max_borrow_rate)?;
validate_borrow_rate(
optimal_utilization,
optimal_borrow_rate,
max_borrow_rate,
min_borrow_rate
.unwrap_or(spot_market.min_borrow_rate)
.cast::<u32>()?
* ((PERCENTAGE_PRECISION / 200) as u32),
)?;

msg!(
"spot_market.optimal_utilization: {:?} -> {:?}",
Expand All @@ -2321,6 +2331,16 @@ pub fn handle_update_spot_market_borrow_rate(
spot_market.optimal_utilization = optimal_utilization;
spot_market.optimal_borrow_rate = optimal_borrow_rate;
spot_market.max_borrow_rate = max_borrow_rate;

if let Some(min_borrow_rate) = min_borrow_rate {
msg!(
"spot_market.min_borrow_rate: {:?} -> {:?}",
spot_market.min_borrow_rate,
min_borrow_rate
);
spot_market.min_borrow_rate = min_borrow_rate
}

Ok(())
}

Expand All @@ -2343,6 +2363,39 @@ pub fn handle_update_spot_market_max_token_deposits(
Ok(())
}

#[access_control(
spot_market_valid(&ctx.accounts.spot_market)
)]
pub fn handle_update_spot_market_max_token_borrows(
ctx: Context<AdminUpdateSpotMarket>,
max_token_borrows_fraction: u16,
) -> Result<()> {
let spot_market = &mut load_mut!(ctx.accounts.spot_market)?;

msg!(
"spot_market.max_token_borrows_fraction: {:?} -> {:?}",
spot_market.max_token_borrows_fraction,
max_token_borrows_fraction
);

let current_spot_tokens_borrows: u64 = spot_market.get_borrows()?.cast()?;
let new_max_token_borrows = spot_market
.max_token_deposits
.safe_mul(max_token_borrows_fraction.cast()?)?
.safe_div(10000)?;

validate!(
current_spot_tokens_borrows <= new_max_token_borrows,
ErrorCode::InvalidSpotMarketInitialization,
"spot borrows {} > max_token_borrows {}",
current_spot_tokens_borrows,
max_token_borrows_fraction
)?;

spot_market.max_token_borrows_fraction = max_token_borrows_fraction;
Ok(())
}

#[access_control(
spot_market_valid(&ctx.accounts.spot_market)
)]
Expand Down
6 changes: 3 additions & 3 deletions programs/drift/src/instructions/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,7 @@ pub fn handle_deposit<'c: 'info, 'info>(
};
emit!(deposit_record);

spot_market.validate_max_token_deposits()?;
spot_market.validate_max_token_deposits_and_borrows()?;

Ok(())
}
Expand Down Expand Up @@ -2003,7 +2003,7 @@ pub fn handle_deposit_into_spot_market_revenue_pool(
amount,
)?;

spot_market.validate_max_token_deposits()?;
spot_market.validate_max_token_deposits_and_borrows()?;
ctx.accounts.spot_market_vault.reload()?;
math::spot_withdraw::validate_spot_market_vault_amount(
&spot_market,
Expand Down Expand Up @@ -2873,7 +2873,7 @@ pub fn handle_end_swap<'c: 'info, 'info>(
out_spot_market.flash_loan_initial_token_amount = 0;
out_spot_market.flash_loan_amount = 0;

out_spot_market.validate_max_token_deposits()?;
out_spot_market.validate_max_token_deposits_and_borrows()?;

let in_strict_price = StrictOraclePrice::new(
in_oracle_price,
Expand Down
2 changes: 2 additions & 0 deletions programs/drift/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -919,12 +919,14 @@ pub mod drift {
optimal_utilization: u32,
optimal_borrow_rate: u32,
max_borrow_rate: u32,
min_borrow_rate: Option<u8>,
) -> Result<()> {
handle_update_spot_market_borrow_rate(
ctx,
optimal_utilization,
optimal_borrow_rate,
max_borrow_rate,
min_borrow_rate,
)
}

Expand Down
3 changes: 2 additions & 1 deletion programs/drift/src/math/spot_balance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,8 @@ pub fn calculate_accumulated_interest(
utilization
.safe_mul(borrow_rate_slope)?
.safe_div(SPOT_UTILIZATION_PRECISION)?
};
}
.max(spot_market.get_min_borrow_rate()?.cast()?);

let time_since_last_update = now
.cast::<u64>()
Expand Down
Loading

0 comments on commit 2cfed8f

Please sign in to comment.