Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

program: add-spot-borrow-insurance-limits #1080

Merged
merged 11 commits into from
Jun 20, 2024
2 changes: 1 addition & 1 deletion programs/drift/src/controller/orders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3619,7 +3619,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
125 changes: 125 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,128 @@ 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
59 changes: 55 additions & 4 deletions programs/drift/src/instructions/admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use crate::math::constants::{
INSURANCE_SPECULATIVE_MAX, LIQUIDATION_FEE_PRECISION, MAX_CONCENTRATION_COEFFICIENT,
MAX_SQRT_K, MAX_UPDATE_K_PRICE_CHANGE, QUOTE_SPOT_MARKET_INDEX,
SPOT_CUMULATIVE_INTEREST_PRECISION, SPOT_IMF_PRECISION, SPOT_WEIGHT_PRECISION, THIRTEEN_DAY,
TWENTY_FOUR_HOUR,
TWENTY_FOUR_HOUR, PERCENTAGE_PRECISION
};
use crate::math::cp_curve::get_update_k_result;
use crate::math::orders::is_multiple_of_step_size;
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 @@ -278,7 +278,9 @@ pub fn handle_initialize_spot_market(
flash_loan_initial_token_amount: 0,
total_swap_fee: 0,
scale_initial_asset_weight_start,
padding: [0; 48],
max_token_borrows_fraction: 0,
min_borrow_rate: 0,
padding: [0; 45],
insurance_fund: InsuranceFund {
vault: *ctx.accounts.insurance_fund_vault.to_account_info().key,
unstaking_period: THIRTEEN_DAY,
Expand Down Expand Up @@ -2296,9 +2298,15 @@ 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 +2329,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 +2361,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
41 changes: 37 additions & 4 deletions programs/drift/src/state/spot_market.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use crate::state::oracle::{HistoricalIndexData, HistoricalOracleData, OracleSour
use crate::state::paused_operations::{InsuranceFundOperation, SpotOperation};
use crate::state::perp_market::{MarketStatus, PoolBalance};
use crate::state::traits::{MarketIndexOffset, Size};
use crate::validate;
use crate::{validate, PERCENTAGE_PRECISION};

#[account(zero_copy(unsafe))]
#[derive(PartialEq, Eq, Debug)]
Expand Down Expand Up @@ -178,7 +178,15 @@ pub struct SpotMarket {
/// disabled when 0
/// precision: QUOTE_PRECISION
pub scale_initial_asset_weight_start: u64,
pub padding: [u8; 48],
/// What fraction of max_token_deposits
/// disabled when 0, 1 => 1/10000 => .01% of max_token_deposits
/// precision: X/10000
pub max_token_borrows_fraction: u16,
0xbigz marked this conversation as resolved.
Show resolved Hide resolved
/// The min borrow rate for this market when the market regardless of utilization
/// 1 => 1/200 => .5%
/// precision: X/200
pub min_borrow_rate: u8,
pub padding: [u8; 45],
}

impl Default for SpotMarket {
Expand Down Expand Up @@ -239,7 +247,9 @@ impl Default for SpotMarket {
flash_loan_initial_token_amount: 0,
total_swap_fee: 0,
scale_initial_asset_weight_start: 0,
padding: [0; 48],
max_token_borrows_fraction: 0,
min_borrow_rate: 0,
padding: [0; 45],
}
}
}
Expand Down Expand Up @@ -414,7 +424,7 @@ impl SpotMarket {
get_token_amount(self.borrow_balance, self, &SpotBalanceType::Borrow)
}

pub fn validate_max_token_deposits(&self) -> DriftResult {
pub fn validate_max_token_deposits_and_borrows(&self) -> DriftResult {
let deposits = self.get_deposits()?;
let max_token_deposits = self.max_token_deposits.cast::<u128>()?;

Expand All @@ -426,6 +436,23 @@ impl SpotMarket {
deposits,
)?;

if self.max_token_borrows_fraction > 0 && self.max_token_deposits > 0 {
let borrows = self.get_borrows()?;
let max_token_borrows = self
.max_token_deposits
.safe_mul(self.max_token_borrows_fraction.cast()?)?
.safe_div(10000)?
.cast::<u128>()?;

validate!(
max_token_borrows == 0 || borrows <= max_token_borrows,
ErrorCode::MaxBorrows,
"max token amount ({}) < borrows ({})",
max_token_borrows,
borrows,
)?;
}

Ok(())
}

Expand Down Expand Up @@ -458,6 +485,12 @@ impl SpotMarket {
Ok(self.utilization_twap <= unhealthy_utilization && utilization <= unhealthy_utilization)
}

pub fn get_min_borrow_rate(self) -> DriftResult<u32> {
self.min_borrow_rate
.cast::<u32>()?
.safe_mul((PERCENTAGE_PRECISION / 200).cast()?)
}

pub fn update_historical_index_price(
&mut self,
best_bid: Option<u64>,
Expand Down
Loading
Loading