Skip to content

Commit

Permalink
program: allow settle pnl and spot fills via match when utilization i…
Browse files Browse the repository at this point in the history
…s 100% (#525)

* bigz/update-spot-tvl-invariant-check

* loosen check in update_spot_balances

* add tests/spotWithdrawUtil100.ts

* in fill spot order, account for tvl leaving system

* test for fulfill_spot_order_with_match with max utilization

* more debugging for test

* sdk: fix bug in transfer account creation (#532)

* sdk: release v2.34.1-beta.5

* sdk: event subscriber avoids web3.js to get performance boost (#542)

* sdk: create rpc method to get transactions to avoid some web3.js overhead

* sdk: custom parse logs logic

* sdk: release v2.34.1-beta.6

* sdk: remove extra console.log in fetchLogs

* sdk: release v2.34.1-beta.7

* program: add post only slide for perps (#541)

* program: add post only slide for perps

* tests

* add to ts types

* CHANGELOG

* sdk: release v2.34.1-beta.8

* program: add cancel_orders_by_ids (#540)

* program: add cancelOrdersByIds

* CHANGELOG

* sdk: release v2.34.1-beta.9

* v2.35.0

* sdk: release v2.35.1-beta.0

* sdk: fixes for lp (liq price and lp share burn) (#522)

* sdk: fix liq price issue

* incorp getPerpPositionWithLPSettle across sdk/margin calc properly

* rename burnLpShares, in getPerpPositionWithLPSettle

* CHANGELOG

---------

Co-authored-by: 0xbigz <[email protected]>
Co-authored-by: Chris Heaney <[email protected]>

* sdk: release v2.35.1-beta.1

* intermediate testing

* more test progress

* CHANGELOG fix

* fix use of is_leaving_drift

* more consistent naming

* more consistent naming

* revert test change

* CHANGELOG

---------

Co-authored-by: Chris Heaney <[email protected]>
Co-authored-by: Evan Pipta <[email protected]>
Co-authored-by: GitHub Actions <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: lowkeynicc <[email protected]>
  • Loading branch information
5 people committed Jul 31, 2023
1 parent f2d5332 commit 20f8f99
Show file tree
Hide file tree
Showing 17 changed files with 1,019 additions and 62 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: allow settle pnl and spot fills via match when utilization is 100% ([#525](https://github.com/drift-labs/protocol-v2/pull/525))
- program: new update_perp_bid_ask_twap ix ([#548](https://github.com/drift-labs/protocol-v2/pull/548))
- program: dont check price bands for place order ([#556](https://github.com/drift-labs/protocol-v2/pull/556))

Expand Down
3 changes: 2 additions & 1 deletion programs/drift/src/controller/amm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -635,8 +635,9 @@ pub fn update_pool_balances(

// dont settle negative pnl to spot borrows when utilization is high (> 80%)
let max_withdraw_amount =
-get_max_withdraw_for_market_with_token_amount(spot_market, token_amount, true)?
-get_max_withdraw_for_market_with_token_amount(spot_market, token_amount, false)?
.cast::<i128>()?;

max_withdraw_amount.max(user_unsettled_pnl)
};

Expand Down
6 changes: 3 additions & 3 deletions programs/drift/src/controller/insurance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -555,17 +555,17 @@ pub fn settle_revenue_to_insurance_fund(
)?;

let depositors_claim =
validate_spot_market_vault_amount(spot_market, spot_market_vault_amount)?.cast::<u128>()?;
validate_spot_market_vault_amount(spot_market, spot_market_vault_amount)?;

let mut token_amount = get_token_amount(
spot_market.revenue_pool.scaled_balance,
spot_market,
&SpotBalanceType::Deposit,
)?;

if depositors_claim < token_amount {
if depositors_claim < token_amount.cast()? {
// only allow half of withdraw available when utilization is high
token_amount = depositors_claim.safe_div(2)?;
token_amount = depositors_claim.max(0).cast::<u128>()?.safe_div(2)?;
}

if spot_market.insurance_fund.user_shares > 0 {
Expand Down
20 changes: 12 additions & 8 deletions programs/drift/src/controller/orders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3286,7 +3286,7 @@ pub fn fill_spot_order(
let base_market = spot_market_map.get_ref(&market_index)?;
let quote_market = spot_market_map.get_quote_spot_market()?;
let (max_base_asset_amount, max_quote_asset_amount) =
get_max_fill_amounts(user, order_index, &base_market, &quote_market)?;
get_max_fill_amounts(user, order_index, &base_market, &quote_market, false)?;
max_base_asset_amount == Some(0) || max_quote_asset_amount == Some(0)
} else {
false
Expand Down Expand Up @@ -3752,7 +3752,7 @@ pub fn fulfill_spot_order_with_match(
}

let (taker_max_base_asset_amount, taker_max_quote_asset_amount) =
get_max_fill_amounts(taker, taker_order_index, base_market, quote_market)?;
get_max_fill_amounts(taker, taker_order_index, base_market, quote_market, false)?;

let taker_base_asset_amount =
if let Some(taker_max_quote_asset_amount) = taker_max_quote_asset_amount {
Expand All @@ -3772,7 +3772,7 @@ pub fn fulfill_spot_order_with_match(
};

let (maker_max_base_asset_amount, maker_max_quote_asset_amount) =
get_max_fill_amounts(maker, maker_order_index, base_market, quote_market)?;
get_max_fill_amounts(maker, maker_order_index, base_market, quote_market, false)?;

let maker_base_asset_amount =
if let Some(maker_max_quote_asset_amount) = maker_max_quote_asset_amount {
Expand Down Expand Up @@ -4058,7 +4058,7 @@ pub fn fulfill_spot_order_with_external_market(
let taker_order_slot = taker.orders[taker_order_index].slot;

let (max_base_asset_amount, max_quote_asset_amount) =
get_max_fill_amounts(taker, taker_order_index, base_market, quote_market)?;
get_max_fill_amounts(taker, taker_order_index, base_market, quote_market, true)?;

let taker_base_asset_amount =
taker_base_asset_amount.min(max_base_asset_amount.unwrap_or(u64::MAX));
Expand Down Expand Up @@ -4206,12 +4206,14 @@ pub fn fulfill_spot_order_with_external_market(
"Fill on external spot market lead to unexpected to update direction"
)?;

let base_update_direction =
taker.orders[taker_order_index].get_spot_position_update_direction(AssetType::Base);
update_spot_balances_and_cumulative_deposits(
base_asset_amount_filled.cast()?,
&taker.orders[taker_order_index].get_spot_position_update_direction(AssetType::Base),
&base_update_direction,
base_market,
taker.force_get_spot_position_mut(base_market.market_index)?,
false,
base_update_direction == SpotBalanceType::Borrow,
None,
)?;

Expand All @@ -4222,12 +4224,14 @@ pub fn fulfill_spot_order_with_external_market(
"Fill on external market lead to unexpected to update direction"
)?;

let quote_update_direction =
taker.orders[taker_order_index].get_spot_position_update_direction(AssetType::Quote);
update_spot_balances_and_cumulative_deposits(
quote_spot_position_delta.cast()?,
&taker.orders[taker_order_index].get_spot_position_update_direction(AssetType::Quote),
&quote_update_direction,
quote_market,
taker.get_quote_spot_position_mut(),
false,
quote_update_direction == SpotBalanceType::Borrow,
Some(quote_asset_amount_filled.cast()?),
)?;

Expand Down
144 changes: 144 additions & 0 deletions programs/drift/src/controller/orders/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4680,9 +4680,11 @@ pub mod fulfill_spot_order_with_match {
LAMPORTS_PER_SOL_I64, LAMPORTS_PER_SOL_U64, PRICE_PRECISION_I64, PRICE_PRECISION_U64,
QUOTE_PRECISION_U64, SPOT_BALANCE_PRECISION, SPOT_BALANCE_PRECISION_U64,
};
use crate::math::spot_balance::calculate_utilization;
use crate::state::spot_market::{SpotBalanceType, SpotMarket};
use crate::state::user::{MarketType, Order, OrderType, SpotPosition, User, UserStats};
use crate::test_utils::get_orders;
use crate::SPOT_UTILIZATION_PRECISION;

use super::*;

Expand Down Expand Up @@ -7113,6 +7115,148 @@ pub mod fulfill_spot_order_with_match {

assert_eq!(base_filled, 166666666);
}

#[test]
fn max_utilization() {
let mut taker_spot_positions = [SpotPosition::default(); 8];
taker_spot_positions[0] = SpotPosition {
market_index: 0,
scaled_balance: 101 * SPOT_BALANCE_PRECISION_U64,
balance_type: SpotBalanceType::Deposit,
..SpotPosition::default()
};
taker_spot_positions[1] = SpotPosition {
market_index: 1,
open_orders: 1,
open_bids: LAMPORTS_PER_SOL_I64,
..SpotPosition::default()
};
let mut taker = User {
orders: get_orders(Order {
market_index: 1,
market_type: MarketType::Spot,
order_type: OrderType::Limit,
direction: PositionDirection::Long,
base_asset_amount: LAMPORTS_PER_SOL_U64,
slot: 0,
price: 100 * PRICE_PRECISION_U64,
..Order::default()
}),
spot_positions: taker_spot_positions,
..User::default()
};

let mut maker_spot_positions = [SpotPosition::default(); 8];
maker_spot_positions[1] = SpotPosition {
market_index: 1,
balance_type: SpotBalanceType::Deposit,
scaled_balance: SPOT_BALANCE_PRECISION_U64,
open_orders: 1,
open_asks: -LAMPORTS_PER_SOL_I64,
..SpotPosition::default()
};
let mut maker = User {
orders: get_orders(Order {
market_index: 1,
post_only: true,
market_type: MarketType::Spot,
order_type: OrderType::Limit,
direction: PositionDirection::Short,
base_asset_amount: LAMPORTS_PER_SOL_U64,
price: 100 * PRICE_PRECISION_U64,
..Order::default()
}),
spot_positions: maker_spot_positions,
..User::default()
};

let mut base_market = SpotMarket {
deposit_balance: SPOT_BALANCE_PRECISION,
borrow_balance: SPOT_BALANCE_PRECISION,
utilization_twap: SPOT_UTILIZATION_PRECISION as u64,
..SpotMarket::default_base_market()
};
let mut quote_market = SpotMarket {
deposit_balance: 101 * SPOT_BALANCE_PRECISION,
borrow_balance: 101 * SPOT_BALANCE_PRECISION,
utilization_twap: SPOT_UTILIZATION_PRECISION as u64,
..SpotMarket::default_quote_market()
};

let base_utilization = calculate_utilization(
base_market.get_deposits().unwrap(),
base_market.get_borrows().unwrap(),
)
.unwrap();

assert_eq!(base_utilization, SPOT_UTILIZATION_PRECISION);

let quote_utilization = calculate_utilization(
base_market.get_deposits().unwrap(),
base_market.get_borrows().unwrap(),
)
.unwrap();

assert_eq!(quote_utilization, SPOT_UTILIZATION_PRECISION);

let now = 1_i64;
let slot = 1_u64;

let fee_structure = get_fee_structure();

let (taker_key, maker_key, filler_key) = get_user_keys();

let mut taker_stats = UserStats::default();
let mut maker_stats = UserStats::default();

fulfill_spot_order_with_match(
&mut base_market,
&mut quote_market,
&mut taker,
&mut taker_stats,
0,
&taker_key,
&mut maker,
&mut Some(&mut maker_stats),
0,
&maker_key,
None,
None,
&filler_key,
now,
slot,
&mut get_oracle_map(),
&fee_structure,
)
.unwrap();

let taker_quote_position = taker.spot_positions[0];
assert_eq!(taker_quote_position.scaled_balance, 950000000);

let taker_base_position = taker.spot_positions[1];
assert_eq!(
taker_base_position.scaled_balance,
SPOT_BALANCE_PRECISION_U64
);
assert_eq!(taker_base_position.open_bids, 0);
assert_eq!(taker_base_position.open_orders, 0);

let base_utilization = calculate_utilization(
base_market.get_deposits().unwrap(),
base_market.get_borrows().unwrap(),
)
.unwrap();

assert_eq!(base_utilization, SPOT_UTILIZATION_PRECISION);

let quote_utilization = calculate_utilization(
base_market.get_deposits().unwrap(),
base_market.get_borrows().unwrap(),
)
.unwrap();

assert_eq!(quote_utilization, SPOT_UTILIZATION_PRECISION);
}
}

pub mod fulfill_spot_order {
Expand Down
2 changes: 0 additions & 2 deletions programs/drift/src/controller/pnl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ pub fn settle_pnl(
crate::controller::lp::settle_funding_payment_then_lp(user, user_key, &mut market, now)?;

let oracle_price = oracle_map.get_price_data(&market.amm.oracle)?.price;

drop(market);

let position_index = get_position_index(&user.perp_positions, market_index)?;
Expand Down Expand Up @@ -129,7 +128,6 @@ pub fn settle_pnl(
user_unsettled_pnl,
now,
)?;

if user_unsettled_pnl == 0 {
msg!("User has no unsettled pnl for market {}", market_index);
return Ok(());
Expand Down
22 changes: 12 additions & 10 deletions programs/drift/src/controller/spot_balance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ pub fn update_spot_balances(
update_direction: &SpotBalanceType,
spot_market: &mut SpotMarket,
spot_balance: &mut dyn SpotBalance,
force_round_up: bool,
is_leaving_drift: bool,
) -> DriftResult {
let increase_user_existing_balance = update_direction == spot_balance.balance_type();
if increase_user_existing_balance {
Expand All @@ -225,7 +225,7 @@ pub fn update_spot_balances(
// determine how much to reduce balance based on size of current token amount
let (token_delta, balance_delta) = if current_token_amount > token_amount {
let round_up =
force_round_up || spot_balance.balance_type() == &SpotBalanceType::Borrow;
is_leaving_drift || spot_balance.balance_type() == &SpotBalanceType::Borrow;
let balance_delta = get_spot_balance(
token_amount,
spot_market,
Expand All @@ -252,7 +252,7 @@ pub fn update_spot_balances(
}
}

if let SpotBalanceType::Borrow = update_direction {
if is_leaving_drift && update_direction == &SpotBalanceType::Borrow {
let deposit_token_amount = get_token_amount(
spot_market.deposit_balance,
spot_market,
Expand Down Expand Up @@ -293,13 +293,15 @@ pub fn transfer_spot_balances(
return Ok(());
}

validate!(
spot_market.deposit_balance >= from_spot_balance.balance(),
ErrorCode::InvalidSpotMarketState,
"spot_market.deposit_balance={} lower than individual spot balance={}",
spot_market.deposit_balance,
from_spot_balance.balance()
)?;
if from_spot_balance.balance_type() == &SpotBalanceType::Deposit {
validate!(
spot_market.deposit_balance >= from_spot_balance.balance(),
ErrorCode::InvalidSpotMarketState,
"spot_market.deposit_balance={} lower than individual spot balance={}",
spot_market.deposit_balance,
from_spot_balance.balance()
)?;
}

update_spot_balances(
token_amount.unsigned_abs(),
Expand Down
4 changes: 2 additions & 2 deletions programs/drift/src/controller/spot_position.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,15 @@ pub fn update_spot_balances_and_cumulative_deposits(
update_direction: &SpotBalanceType,
spot_market: &mut SpotMarket,
spot_position: &mut SpotPosition,
force_round_up: bool,
is_leaving_drift: bool,
cumulative_deposit_delta: Option<u128>,
) -> DriftResult {
update_spot_balances(
token_amount,
update_direction,
spot_market,
spot_position,
force_round_up,
is_leaving_drift,
)?;

let cumulative_deposit_delta = cumulative_deposit_delta.unwrap_or(token_amount);
Expand Down
15 changes: 11 additions & 4 deletions programs/drift/src/math/orders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -746,27 +746,34 @@ pub fn get_max_fill_amounts(
user_order_index: usize,
base_market: &SpotMarket,
quote_market: &SpotMarket,
is_leaving_drift: bool,
) -> DriftResult<(Option<u64>, Option<u64>)> {
let direction: PositionDirection = user.orders[user_order_index].direction;
match direction {
PositionDirection::Long => {
let max_quote = get_max_fill_amounts_for_market(user, quote_market)?.cast::<u64>()?;
let max_quote = get_max_fill_amounts_for_market(user, quote_market, is_leaving_drift)?
.cast::<u64>()?;
Ok((None, Some(max_quote)))
}
PositionDirection::Short => {
let max_base = standardize_base_asset_amount(
get_max_fill_amounts_for_market(user, base_market)?.cast::<u64>()?,
get_max_fill_amounts_for_market(user, base_market, is_leaving_drift)?
.cast::<u64>()?,
base_market.order_step_size,
)?;
Ok((Some(max_base), None))
}
}
}

fn get_max_fill_amounts_for_market(user: &User, market: &SpotMarket) -> DriftResult<u128> {
fn get_max_fill_amounts_for_market(
user: &User,
market: &SpotMarket,
is_leaving_drift: bool,
) -> DriftResult<u128> {
let position_index = user.get_spot_position_index(market.market_index)?;
let token_amount = user.spot_positions[position_index].get_signed_token_amount(market)?;
get_max_withdraw_for_market_with_token_amount(market, token_amount, false)
get_max_withdraw_for_market_with_token_amount(market, token_amount, is_leaving_drift)
}

pub fn find_fallback_maker_order(
Expand Down
Loading

0 comments on commit 20f8f99

Please sign in to comment.