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

bigz/add-lp-order-risk-mitigations #766

Merged
merged 21 commits into from
Jan 11, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 26 additions & 15 deletions programs/drift/src/controller/lp/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ use crate::state::state::{OracleGuardRails, State, ValidityGuardRails};
use crate::state::user::{SpotPosition, User};
use crate::test_utils::*;
use crate::test_utils::{get_positions, get_pyth_price, get_spot_positions};
use anchor_lang::prelude::Clock;

#[test]
fn test_lp_wont_collect_improper_funding() {
let mut position = PerpPosition {
Expand Down Expand Up @@ -433,8 +435,14 @@ pub fn test_lp_settle_pnl() {
&pyth_program,
oracle_account_info
);
let slot = 0;
let mut oracle_map = OracleMap::load_one(&oracle_account_info, slot, None).unwrap();
let clock = Clock {
slot: 0,
epoch_start_timestamp: 0,
epoch: 0,
leader_schedule_epoch: 0,
unix_timestamp: 0,
};
let mut oracle_map = OracleMap::load_one(&oracle_account_info, clock.slot, None).unwrap();

let mut market = PerpMarket {
amm: AMM {
Expand Down Expand Up @@ -516,8 +524,6 @@ pub fn test_lp_settle_pnl() {
..User::default()
};

let now = 1000000;

let state = State {
oracle_guard_rails: OracleGuardRails {
validity: ValidityGuardRails {
Expand Down Expand Up @@ -555,7 +561,7 @@ pub fn test_lp_settle_pnl() {
&market_map,
&spot_market_map,
&mut oracle_map,
now,
&clock,
&state,
);

Expand Down Expand Up @@ -728,16 +734,21 @@ fn test_lp_margin_calc() {

let strict_quote_price = StrictOraclePrice::test(1000000);
// ensure margin calc doesnt incorrectly count funding rate (funding pnl MUST come before settling lp)
let (margin_requirement, weighted_unrealized_pnl, worse_case_base_asset_value) =
calculate_perp_position_value_and_pnl(
&user.perp_positions[0],
&market,
&oracle_price_data,
&strict_quote_price,
crate::math::margin::MarginRequirementType::Initial,
0,
)
.unwrap();
let (
margin_requirement,
weighted_unrealized_pnl,
worse_case_base_asset_value,
_open_order_fraction,
) = calculate_perp_position_value_and_pnl(
&user.perp_positions[0],
&market,
&oracle_price_data,
&strict_quote_price,
crate::math::margin::MarginRequirementType::Initial,
0,
false,
)
.unwrap();

assert_eq!(margin_requirement, 1012000000); // $1010 + $2 mr for lp_shares
assert_eq!(weighted_unrealized_pnl, -9916900000); // $-9900000000 upnl (+ -16900000 from old funding)
Expand Down
177 changes: 167 additions & 10 deletions programs/drift/src/controller/orders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

use crate::controller;
use crate::controller::funding::settle_funding_payment;
use crate::controller::lp::burn_lp_shares;
use crate::controller::position;
use crate::controller::position::{
add_new_position, decrease_open_bids_and_asks, get_position_index, increase_open_bids_and_asks,
Expand Down Expand Up @@ -57,11 +58,13 @@
use crate::math::safe_unwrap::SafeUnwrap;
use crate::math::spot_swap::select_margin_type_for_swap;
use crate::print_error;
use crate::state::events::{emit_stack, get_order_action_record, OrderActionRecord, OrderRecord};
use crate::state::events::{
emit_stack, get_order_action_record, LPAction, LPRecord, OrderActionRecord, OrderRecord,
};
use crate::state::events::{OrderAction, OrderActionExplanation};
use crate::state::fill_mode::FillMode;
use crate::state::fulfillment::{PerpFulfillmentMethod, SpotFulfillmentMethod};
use crate::state::margin_calculation::MarginContext;
use crate::state::margin_calculation::{MarginCalculation, MarginContext};
use crate::state::oracle::{OraclePriceData, StrictOraclePrice};
use crate::state::oracle_map::OracleMap;
use crate::state::perp_market::{AMMLiquiditySplit, MarketStatus, PerpMarket};
Expand Down Expand Up @@ -99,12 +102,36 @@
oracle_map: &mut OracleMap,
clock: &Clock,
mut params: OrderParams,
options: PlaceOrderOptions,
) -> DriftResult {
let user_key = user.key();
let user = &mut load_mut!(user)?;

Check warning on line 108 in programs/drift/src/controller/orders.rs

View check run for this annotation

Codecov / codecov/patch

programs/drift/src/controller/orders.rs#L107-L108

Added lines #L107 - L108 were not covered by tests
_place_perp_order(
state,
user,
user_key,

Check warning on line 112 in programs/drift/src/controller/orders.rs

View check run for this annotation

Codecov / codecov/patch

programs/drift/src/controller/orders.rs#L111-L112

Added lines #L111 - L112 were not covered by tests
perp_market_map,
spot_market_map,
oracle_map,
clock,
&mut params,
options,

Check warning on line 118 in programs/drift/src/controller/orders.rs

View check run for this annotation

Codecov / codecov/patch

programs/drift/src/controller/orders.rs#L118

Added line #L118 was not covered by tests
)
}

pub fn _place_perp_order(

Check warning on line 122 in programs/drift/src/controller/orders.rs

View check run for this annotation

Codecov / codecov/patch

programs/drift/src/controller/orders.rs#L122

Added line #L122 was not covered by tests
crispheaney marked this conversation as resolved.
Show resolved Hide resolved
state: &State,
user: &mut User,
user_key: Pubkey,
perp_market_map: &PerpMarketMap,
spot_market_map: &SpotMarketMap,
oracle_map: &mut OracleMap,
clock: &Clock,
params: &mut OrderParams,
mut options: PlaceOrderOptions,
) -> DriftResult {
let now = clock.unix_timestamp;
let slot = clock.slot;
let user_key = user.key();
let user = &mut load_mut!(user)?;

validate_user_not_being_liquidated(
user,
Expand Down Expand Up @@ -225,7 +252,7 @@
params.update_perp_auction_params(market, oracle_price_data.price)?;

let (auction_start_price, auction_end_price, auction_duration) = get_auction_params(
&params,
params,

Check warning on line 255 in programs/drift/src/controller/orders.rs

View check run for this annotation

Codecov / codecov/patch

programs/drift/src/controller/orders.rs#L255

Added line #L255 was not covered by tests
oracle_price_data,
market.amm.order_tick_size,
state.min_perp_auction_duration,
Expand Down Expand Up @@ -2708,7 +2735,7 @@

pub fn force_cancel_orders(
state: &State,
user: &AccountLoader<User>,
user_account_loader: &AccountLoader<User>,
spot_market_map: &SpotMarketMap,
perp_market_map: &PerpMarketMap,
oracle_map: &mut OracleMap,
Expand All @@ -2719,8 +2746,8 @@
let slot = clock.slot;

let filler_key = filler.key();
let user_key = user.key();
let user = &mut load_mut!(user)?;
let user_key = user_account_loader.key();
let user = &mut load_mut!(user_account_loader)?;
let filler = &mut load_mut!(filler)?;

validate!(
Expand All @@ -2730,8 +2757,15 @@

validate!(!user.is_bankrupt(), ErrorCode::UserBankrupt)?;

let meets_initial_margin_requirement =
meets_initial_margin_requirement(user, perp_market_map, spot_market_map, oracle_map)?;
let margin_calc = calculate_margin_requirement_and_total_collateral_and_liability_info(
user,
perp_market_map,
spot_market_map,
oracle_map,
MarginContext::standard(MarginRequirementType::Initial),
)?;

let meets_initial_margin_requirement = margin_calc.meets_margin_requirement();

validate!(
!meets_initial_margin_requirement,
Expand Down Expand Up @@ -2800,6 +2834,17 @@
)?;
}

attempt_burn_user_lp_shares_for_risk_reduction(
crispheaney marked this conversation as resolved.
Show resolved Hide resolved
state,
user,
margin_calc,
&user_key,
perp_market_map,
spot_market_map,
oracle_map,
clock,
)?;

pay_keeper_flat_reward_for_spot(
user,
Some(filler),
Expand All @@ -2820,6 +2865,118 @@
}
}

pub fn attempt_burn_user_lp_shares_for_risk_reduction(
crispheaney marked this conversation as resolved.
Show resolved Hide resolved
state: &State,
user: &mut User,
margin_calc: MarginCalculation,
user_key: &Pubkey,
perp_market_map: &PerpMarketMap,
spot_market_map: &SpotMarketMap,
oracle_map: &mut OracleMap,
clock: &Clock,
) -> DriftResult {
let now = clock.unix_timestamp;
// attempt to burn lp shares if user has a custom margin ratio set and its breached with orders
if !margin_calc.positions_meets_margin_requirement()? {
let time_since_last_liquidity_change: i64 =
now.safe_sub(user.last_add_perp_lp_shares_ts)?;
// avoid spamming update if orders have already been set
if time_since_last_liquidity_change >= state.lp_cooldown_time.cast()? {
let set_reduce_only_orders = true; // todo
crispheaney marked this conversation as resolved.
Show resolved Hide resolved
for position_index in 0..user.perp_positions.len() {
let market_index = user.perp_positions[position_index].market_index;
_burn_user_lp_shares_for_risk_reduction(
state,
user,
user_key,
market_index,
perp_market_map,
spot_market_map,
oracle_map,
clock,
set_reduce_only_orders,
)?;
}
user.last_add_perp_lp_shares_ts = now;
}
}

Ok(())
}

pub fn _burn_user_lp_shares_for_risk_reduction(
state: &State,
user: &mut User,
user_key: &Pubkey,
market_index: u16,
perp_market_map: &PerpMarketMap,
spot_market_map: &SpotMarketMap,
oracle_map: &mut OracleMap,
clock: &Clock,
set_reduce_only_orders: bool,
) -> DriftResult {
let perp_position = user.get_perp_position_mut(market_index)?;
if perp_position.is_lp() {
let lp_shares = perp_position.lp_shares;
let market_index = perp_position.market_index;

Check warning on line 2921 in programs/drift/src/controller/orders.rs

View check run for this annotation

Codecov / codecov/patch

programs/drift/src/controller/orders.rs#L2920-L2921

Added lines #L2920 - L2921 were not covered by tests
{
let market = perp_market_map.get_ref(&market_index)?;
let oracle_price_data = oracle_map.get_price_data(&market.amm.oracle)?;

Check warning on line 2924 in programs/drift/src/controller/orders.rs

View check run for this annotation

Codecov / codecov/patch

programs/drift/src/controller/orders.rs#L2923-L2924

Added lines #L2923 - L2924 were not covered by tests

let oracle_price = if market.status == MarketStatus::Settlement {
market.expiry_price

Check warning on line 2927 in programs/drift/src/controller/orders.rs

View check run for this annotation

Codecov / codecov/patch

programs/drift/src/controller/orders.rs#L2926-L2927

Added lines #L2926 - L2927 were not covered by tests
} else {
oracle_price_data.price

Check warning on line 2929 in programs/drift/src/controller/orders.rs

View check run for this annotation

Codecov / codecov/patch

programs/drift/src/controller/orders.rs#L2929

Added line #L2929 was not covered by tests
};

let (position_delta, pnl) = burn_lp_shares(
perp_position,
perp_market_map.get_ref_mut(&market_index)?.deref_mut(),
crispheaney marked this conversation as resolved.
Show resolved Hide resolved
lp_shares.safe_div(3)?.max(market.amm.order_step_size),
oracle_price,

Check warning on line 2936 in programs/drift/src/controller/orders.rs

View check run for this annotation

Codecov / codecov/patch

programs/drift/src/controller/orders.rs#L2934-L2936

Added lines #L2934 - L2936 were not covered by tests
)?;

// emit LP record for shares removed
emit_stack::<_, { LPRecord::SIZE }>(LPRecord {
ts: clock.unix_timestamp,

Check warning on line 2941 in programs/drift/src/controller/orders.rs

View check run for this annotation

Codecov / codecov/patch

programs/drift/src/controller/orders.rs#L2940-L2941

Added lines #L2940 - L2941 were not covered by tests
action: LPAction::RemoveLiquidity,
user: *user_key,

Check warning on line 2943 in programs/drift/src/controller/orders.rs

View check run for this annotation

Codecov / codecov/patch

programs/drift/src/controller/orders.rs#L2943

Added line #L2943 was not covered by tests
n_shares: lp_shares,
market_index: perp_position.market_index,

Check warning on line 2945 in programs/drift/src/controller/orders.rs

View check run for this annotation

Codecov / codecov/patch

programs/drift/src/controller/orders.rs#L2945

Added line #L2945 was not covered by tests
delta_base_asset_amount: position_delta.base_asset_amount,
delta_quote_asset_amount: position_delta.quote_asset_amount,
pnl,
})?;
}

if set_reduce_only_orders {
let market = perp_market_map.get_ref(&market_index)?;

Check warning on line 2953 in programs/drift/src/controller/orders.rs

View check run for this annotation

Codecov / codecov/patch

programs/drift/src/controller/orders.rs#L2952-L2953

Added lines #L2952 - L2953 were not covered by tests

let direction_to_close = perp_position.get_direction_to_close();

Check warning on line 2955 in programs/drift/src/controller/orders.rs

View check run for this annotation

Codecov / codecov/patch

programs/drift/src/controller/orders.rs#L2955

Added line #L2955 was not covered by tests

let mut params = OrderParams::get_aggressive_close_params(
&market,

Check warning on line 2958 in programs/drift/src/controller/orders.rs

View check run for this annotation

Codecov / codecov/patch

programs/drift/src/controller/orders.rs#L2958

Added line #L2958 was not covered by tests
direction_to_close,
perp_position.base_asset_amount.unsigned_abs(),

Check warning on line 2960 in programs/drift/src/controller/orders.rs

View check run for this annotation

Codecov / codecov/patch

programs/drift/src/controller/orders.rs#L2960

Added line #L2960 was not covered by tests
)?;

controller::orders::_place_perp_order(
state,
user,
*user_key,

Check warning on line 2966 in programs/drift/src/controller/orders.rs

View check run for this annotation

Codecov / codecov/patch

programs/drift/src/controller/orders.rs#L2966

Added line #L2966 was not covered by tests
perp_market_map,
spot_market_map,
oracle_map,
clock,
&mut params,
PlaceOrderOptions::default(),

Check warning on line 2972 in programs/drift/src/controller/orders.rs

View check run for this annotation

Codecov / codecov/patch

programs/drift/src/controller/orders.rs#L2972

Added line #L2972 was not covered by tests
)?;
}
}

Ok(())
}

pub fn pay_keeper_flat_reward_for_perps(
user: &mut User,
filler: Option<&mut User>,
Expand Down
Loading
Loading