Skip to content
Open
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
65 changes: 65 additions & 0 deletions programs/drift/src/controller/orders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1379,6 +1379,20 @@ pub fn fill_perp_order(
)?
}

if base_asset_amount_after == 0 && user.perp_positions[position_index].open_orders != 0 {
cancel_reduce_only_trigger_orders(
user,
&user_key,
Some(&filler_key),
perp_market_map,
spot_market_map,
oracle_map,
now,
slot,
market_index,
)?;
}

if base_asset_amount == 0 {
return Ok((base_asset_amount, quote_asset_amount));
}
Expand Down Expand Up @@ -2927,6 +2941,57 @@ fn get_taker_and_maker_for_order_record(
}
}

fn cancel_reduce_only_trigger_orders(
user: &mut User,
user_key: &Pubkey,
filler_key: Option<&Pubkey>,
perp_market_map: &PerpMarketMap,
spot_market_map: &SpotMarketMap,
oracle_map: &mut OracleMap,
now: i64,
slot: u64,
perp_market_index: u16,
) -> DriftResult {
for order_index in 0..user.orders.len() {
if user.orders[order_index].status != OrderStatus::Open {
continue;
}

if user.orders[order_index].market_type != MarketType::Perp {
continue;
}

if user.orders[order_index].market_index != perp_market_index {
continue;
}

if !user.orders[order_index].must_be_triggered() {
continue;
}

if !user.orders[order_index].reduce_only {
continue;
}

cancel_order(
order_index,
user,
user_key,
perp_market_map,
spot_market_map,
oracle_map,
now,
slot,
OrderActionExplanation::ReduceOnlyOrderIncreasedPosition,
filler_key,
0,
false,
)?;
}

Ok(())
}

pub fn trigger_order(
order_id: u32,
state: &State,
Expand Down
222 changes: 222 additions & 0 deletions programs/drift/src/controller/orders/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10315,6 +10315,228 @@ pub mod force_cancel_orders {
}
}

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

use anchor_lang::prelude::Clock;

use crate::controller::orders::cancel_reduce_only_trigger_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, LAMPORTS_PER_SOL_I64, PEG_PRECISION,
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};
use crate::test_utils::*;
use crate::test_utils::{
create_account_info, get_positions, get_pyth_price, get_spot_positions,
};

use super::*;

#[test]
fn test() {
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,
last_oracle_price_twap_5min: oracle_price.twap,
last_oracle_price: oracle_price.agg.price,
..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, PositionDirection::Long)
.unwrap();
let (new_bid_base_asset_reserve, new_bid_quote_asset_reserve) =
crate::math::amm_spread::calculate_spread_reserves(&market, 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,
..Order::default()
};
orders[1] = Order {
market_index: 1,
order_id: 2,
status: OrderStatus::Open,
order_type: OrderType::TriggerMarket,
market_type: MarketType::Perp,
reduce_only: true,
..Order::default()
};
orders[2] = Order {
market_index: 0,
order_id: 2,
status: OrderStatus::Open,
order_type: OrderType::TriggerMarket,
market_type: MarketType::Perp,
reduce_only: true,
..Order::default()
};
orders[3] = Order {
market_index: 0,
order_id: 2,
status: OrderStatus::Open,
order_type: OrderType::TriggerMarket,
market_type: MarketType::Spot,
reduce_only: true,
..Order::default()
};
orders[4] = Order {
market_index: 0,
order_id: 2,
status: OrderStatus::Open,
order_type: OrderType::TriggerLimit,
market_type: MarketType::Perp,
reduce_only: true,
..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()
};

cancel_reduce_only_trigger_orders(
&mut user,
&Pubkey::default(),
Some(&Pubkey::default()),
&market_map,
&spot_market_map,
&mut oracle_map,
0,
0,
0,
)
.unwrap();

assert_eq!(user.orders[0].status, OrderStatus::Open);
assert_eq!(user.orders[1].status, OrderStatus::Open);
assert_eq!(user.orders[2].status, OrderStatus::Canceled);
assert_eq!(user.orders[3].status, OrderStatus::Open);
assert_eq!(user.orders[4].status, OrderStatus::Canceled);
}
}

pub mod insert_maker_order_info {
use crate::controller::orders::insert_maker_order_info;
use crate::controller::position::PositionDirection;
Expand Down
Loading