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
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
Loading