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 force_cancel_orders #298

Merged
merged 6 commits into from
Dec 22, 2022
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Features

- program: add force_cancel_orders to cancel risk-increasing orders for users with excessive leverage ([#298](https://github.com/drift-labs/protocol-v2/pull/298))

### Fixes

- program: fix calculate_availability_borrow_liquidity ([#301](https://github.com/drift-labs/protocol-v2/pull/301))
Expand Down
109 changes: 109 additions & 0 deletions programs/drift/src/controller/orders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2323,6 +2323,101 @@ pub fn trigger_order(
Ok(())
}

pub fn force_cancel_orders(
state: &State,
user: &AccountLoader<User>,
spot_market_map: &SpotMarketMap,
perp_market_map: &PerpMarketMap,
oracle_map: &mut OracleMap,
filler: &AccountLoader<User>,
clock: &Clock,
) -> DriftResult {
let now = clock.unix_timestamp;
let slot = clock.slot;

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

validate!(
!user.is_being_liquidated(),
ErrorCode::UserIsBeingLiquidated
)?;

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

let meets_initial_margin_requirement =
meets_initial_margin_requirement(user, perp_market_map, spot_market_map, oracle_map)?;

validate!(
!meets_initial_margin_requirement,
ErrorCode::SufficientCollateral
)?;

let mut total_fee = 0_u64;

for order_index in 0..user.orders.len() {
if user.orders[order_index].status != OrderStatus::Open {
continue;
}

let market_index = user.orders[order_index].market_index;
let market_type = user.orders[order_index].market_type;

let fee = match market_type {
MarketType::Spot => {
let spot_market = spot_market_map.get_ref(&market_index)?;
let is_risk_decreasing = determine_if_user_spot_order_is_risk_decreasing(
user,
&spot_market,
order_index,
)?;
if is_risk_decreasing {
continue;
}

state.spot_fee_structure.flat_filler_fee
}
MarketType::Perp => {
let is_risk_decreasing =
determine_if_user_order_is_risk_decreasing(user, market_index, order_index)?;
if is_risk_decreasing {
continue;
}

state.perp_fee_structure.flat_filler_fee
}
};

total_fee = total_fee.safe_add(fee)?;

cancel_order(
order_index,
user,
&user_key,
perp_market_map,
spot_market_map,
oracle_map,
now,
slot,
OrderActionExplanation::InsufficientFreeCollateral,
Some(&filler_key),
fee,
false,
)?;
}

pay_keeper_flat_reward_for_spot(
user,
Some(filler),
spot_market_map.get_quote_spot_market_mut()?.deref_mut(),
total_fee,
)?;

Ok(())
}

pub fn can_reward_user_with_perp_pnl(user: &mut Option<&mut User>, market_index: u16) -> bool {
user.is_some()
&& user
Expand Down Expand Up @@ -3204,6 +3299,20 @@ fn fulfill_spot_order(
Ok((base_asset_amount, base_asset_amount != 0))
}

fn determine_if_user_spot_order_is_risk_decreasing(
user: &User,
spot_market: &SpotMarket,
order_index: usize,
) -> DriftResult<bool> {
let position_index = user.get_spot_position_index(spot_market.market_index)?;
let token_amount = user.spot_positions[position_index].get_token_amount(spot_market)?;
is_spot_order_risk_decreasing(
&user.orders[order_index],
&user.spot_positions[position_index].balance_type,
token_amount,
)
}

pub fn fulfill_spot_order_with_match(
base_market: &mut SpotMarket,
quote_market: &mut SpotMarket,
Expand Down
255 changes: 255 additions & 0 deletions programs/drift/src/controller/orders/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7675,3 +7675,258 @@ pub mod fill_spot_order {
assert_eq!(taker_after.orders[0], Order::default()); // order expired
}
}

pub mod force_cancel_orders {
use std::str::FromStr;

use anchor_lang::prelude::{AccountLoader, Clock};

use crate::controller::orders::force_cancel_orders;
use crate::controller::position::PositionDirection;
use crate::create_account_info;
use crate::create_anchor_account_info;
use crate::math::constants::{
AMM_RESERVE_PRECISION, BASE_PRECISION_I64, BASE_PRECISION_U64, LAMPORTS_PER_SOL_I64,
LAMPORTS_PER_SOL_U64, PEG_PRECISION, PRICE_PRECISION_U64, SPOT_BALANCE_PRECISION,
SPOT_BALANCE_PRECISION_U64, SPOT_CUMULATIVE_INTEREST_PRECISION, SPOT_WEIGHT_PRECISION,
};
use crate::state::oracle::HistoricalOracleData;
use crate::state::oracle::OracleSource;
use crate::state::perp_market::{PerpMarket, AMM};
use crate::state::perp_market_map::PerpMarketMap;
use crate::state::spot_market::{SpotBalanceType, SpotMarket};
use crate::state::spot_market_map::SpotMarketMap;
use crate::state::state::State;
use crate::state::user::{MarketType, OrderStatus, OrderType, SpotPosition, User, UserStats};
use crate::test_utils::*;
use crate::test_utils::{
create_account_info, get_positions, get_pyth_price, get_spot_positions,
};

use super::*;

#[test]
fn cancel_order_after_fulfill() {
let clock = Clock {
slot: 6,
epoch_start_timestamp: 0,
epoch: 0,
leader_schedule_epoch: 0,
unix_timestamp: 0,
};

let mut oracle_price = get_pyth_price(100, 6);
let oracle_price_key =
Pubkey::from_str("J83w4HKfqxwcq3BEMMkPFSppX3gqekLyLJBexebFVkix").unwrap();
let pyth_program = crate::ids::pyth_program::id();
create_account_info!(
oracle_price,
&oracle_price_key,
&pyth_program,
oracle_account_info
);
let mut oracle_map = OracleMap::load_one(&oracle_account_info, clock.slot, None).unwrap();

let mut market = PerpMarket {
amm: AMM {
base_asset_reserve: 100 * AMM_RESERVE_PRECISION,
quote_asset_reserve: 100 * AMM_RESERVE_PRECISION,
terminal_quote_asset_reserve: 100 * AMM_RESERVE_PRECISION,
// bid_base_asset_reserve: 101 * AMM_RESERVE_PRECISION,
// bid_quote_asset_reserve: 99 * AMM_RESERVE_PRECISION,
// ask_base_asset_reserve: 99 * AMM_RESERVE_PRECISION,
// ask_quote_asset_reserve: 101 * AMM_RESERVE_PRECISION,
sqrt_k: 100 * AMM_RESERVE_PRECISION,
peg_multiplier: 100 * PEG_PRECISION,
max_slippage_ratio: 100,
max_fill_reserve_fraction: 100,
order_step_size: 1000,
order_tick_size: 1,
oracle: oracle_price_key,
max_spread: 1000,
base_spread: 0,
long_spread: 0,
short_spread: 0,
historical_oracle_data: HistoricalOracleData {
last_oracle_price_twap: oracle_price.twap as i64,
last_oracle_price_twap_5min: oracle_price.twap as i64,
last_oracle_price: oracle_price.agg.price as i64,
..HistoricalOracleData::default()
},
..AMM::default()
},
margin_ratio_initial: 1000,
margin_ratio_maintenance: 500,
status: MarketStatus::Initialized,
..PerpMarket::default()
};
market.status = MarketStatus::Active;
market.amm.max_base_asset_reserve = u128::MAX;
market.amm.min_base_asset_reserve = 0;
let (new_ask_base_asset_reserve, new_ask_quote_asset_reserve) =
crate::math::amm_spread::calculate_spread_reserves(
&market.amm,
PositionDirection::Long,
)
.unwrap();
let (new_bid_base_asset_reserve, new_bid_quote_asset_reserve) =
crate::math::amm_spread::calculate_spread_reserves(
&market.amm,
PositionDirection::Short,
)
.unwrap();
market.amm.ask_base_asset_reserve = new_ask_base_asset_reserve;
market.amm.bid_base_asset_reserve = new_bid_base_asset_reserve;
market.amm.ask_quote_asset_reserve = new_ask_quote_asset_reserve;
market.amm.bid_quote_asset_reserve = new_bid_quote_asset_reserve;
create_anchor_account_info!(market, PerpMarket, market_account_info);
let market_map = PerpMarketMap::load_one(&market_account_info, true).unwrap();

let mut usdc_spot_market = SpotMarket {
market_index: 0,
oracle_source: OracleSource::QuoteAsset,
deposit_balance: SPOT_BALANCE_PRECISION,
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,
..SpotMarket::default()
};
create_anchor_account_info!(usdc_spot_market, SpotMarket, usdc_spot_market_account_info);

let mut sol_spot_market = SpotMarket {
market_index: 1,
deposit_balance: SPOT_BALANCE_PRECISION,
cumulative_deposit_interest: SPOT_CUMULATIVE_INTEREST_PRECISION,
cumulative_borrow_interest: SPOT_CUMULATIVE_INTEREST_PRECISION,
oracle: oracle_price_key,
..SpotMarket::default_base_market()
};
create_anchor_account_info!(sol_spot_market, SpotMarket, sol_spot_market_account_info);

let spot_market_map = SpotMarketMap::load_multiple(
vec![
&usdc_spot_market_account_info,
&sol_spot_market_account_info,
],
true,
)
.unwrap();

let mut orders = [Order::default(); 32];
orders[0] = Order {
market_index: 0,
order_id: 1,
status: OrderStatus::Open,
order_type: OrderType::Limit,
market_type: MarketType::Perp,
direction: PositionDirection::Long,
base_asset_amount: 100 * BASE_PRECISION_U64,
slot: 0,
price: 102 * PRICE_PRECISION_U64,
..Order::default()
};
orders[1] = Order {
market_index: 0,
order_id: 2,
status: OrderStatus::Open,
order_type: OrderType::Limit,
market_type: MarketType::Perp,
direction: PositionDirection::Short,
base_asset_amount: BASE_PRECISION_U64,
slot: 0,
price: 102 * PRICE_PRECISION_U64,
..Order::default()
};
orders[2] = Order {
market_index: 1,
order_id: 1,
status: OrderStatus::Open,
order_type: OrderType::Limit,
market_type: MarketType::Spot,
direction: PositionDirection::Long,
base_asset_amount: 100 * LAMPORTS_PER_SOL_U64,
slot: 0,
price: 102 * PRICE_PRECISION_U64,
..Order::default()
};
orders[3] = Order {
market_index: 1,
order_id: 1,
status: OrderStatus::Open,
order_type: OrderType::Limit,
market_type: MarketType::Spot,
direction: PositionDirection::Short,
base_asset_amount: LAMPORTS_PER_SOL_U64,
slot: 0,
price: 102 * PRICE_PRECISION_U64,
..Order::default()
};

let mut user = User {
authority: Pubkey::from_str("My11111111111111111111111111111111111111111").unwrap(), // different authority than filler
orders,
perp_positions: get_positions(PerpPosition {
market_index: 0,
base_asset_amount: BASE_PRECISION_I64,
open_orders: 2,
open_bids: 100 * BASE_PRECISION_I64,
open_asks: -BASE_PRECISION_I64,
..PerpPosition::default()
}),
spot_positions: get_spot_positions(SpotPosition {
market_index: 1,
balance_type: SpotBalanceType::Deposit,
scaled_balance: SPOT_BALANCE_PRECISION_U64,
open_orders: 2,
open_bids: 100 * LAMPORTS_PER_SOL_I64,
open_asks: -LAMPORTS_PER_SOL_I64,
..SpotPosition::default()
}),
..User::default()
};
create_anchor_account_info!(user, User, user_account_info);
let user_account_loader: AccountLoader<User> =
AccountLoader::try_from(&user_account_info).unwrap();

create_anchor_account_info!(UserStats::default(), UserStats, user_stats_account_info);
let _user_stats_account_loader: AccountLoader<UserStats> =
AccountLoader::try_from(&user_stats_account_info).unwrap();

let filler_key = Pubkey::from_str("My11111111111111111111111111111111111111111").unwrap();
create_anchor_account_info!(User::default(), &filler_key, User, user_account_info);
let filler_account_loader: AccountLoader<User> =
AccountLoader::try_from(&user_account_info).unwrap();

create_anchor_account_info!(UserStats::default(), UserStats, filler_stats_account_info);
let _filler_stats_account_loader: AccountLoader<UserStats> =
AccountLoader::try_from(&filler_stats_account_info).unwrap();

let state = State {
min_perp_auction_duration: 1,
default_market_order_time_in_force: 10,
..State::default()
};

force_cancel_orders(
&state,
&user_account_loader,
&spot_market_map,
&market_map,
&mut oracle_map,
&filler_account_loader,
&clock,
)
.unwrap();

let user = user_account_loader.load().unwrap();
assert_eq!(user.orders[0], Order::default());
assert_ne!(user.orders[1], Order::default());
assert_eq!(user.orders[2], Order::default());
assert_ne!(user.orders[3], Order::default());

assert_eq!(user.spot_positions[0].scaled_balance, 20000001);
assert_eq!(user.spot_positions[0].balance_type, SpotBalanceType::Borrow,);
}
}
Loading