phoenix/program/processor/
reduce_order.rs

1use crate::{
2    program::{
3        assert_with_msg, dispatch_market::load_with_dispatch_mut, error::PhoenixError,
4        loaders::CancelOrWithdrawContext as Cancel, token_utils::try_withdraw, MarketHeader,
5        PhoenixMarketContext, PhoenixVaultContext,
6    },
7    quantities::{BaseLots, Ticks, WrapperU64},
8    state::{
9        markets::{FIFOOrderId, MarketEvent},
10        MatchingEngineResponse, Side,
11    },
12};
13use borsh::{BorshDeserialize, BorshSerialize};
14use solana_program::{
15    account_info::AccountInfo, entrypoint::ProgramResult, log::sol_log_compute_units,
16    pubkey::Pubkey,
17};
18use std::mem::size_of;
19
20#[derive(BorshDeserialize, BorshSerialize, Clone, Copy)]
21pub struct CancelOrderParams {
22    pub side: Side,
23    pub price_in_ticks: u64,
24    pub order_sequence_number: u64,
25}
26
27#[derive(BorshDeserialize, BorshSerialize, Clone, Copy)]
28pub struct ReduceOrderParams {
29    pub base_params: CancelOrderParams,
30    /// Size of the order to reduce in base lots
31    pub size: u64,
32}
33
34pub(crate) fn process_reduce_order<'a, 'info>(
35    _program_id: &Pubkey,
36    market_context: &PhoenixMarketContext<'a, 'info>,
37    accounts: &'a [AccountInfo<'info>],
38    data: &[u8],
39    withdraw_funds: bool,
40    record_event_fn: &mut dyn FnMut(MarketEvent<Pubkey>),
41) -> ProgramResult {
42    sol_log_compute_units();
43    let ReduceOrderParams { base_params, size } = ReduceOrderParams::try_from_slice(data)?;
44    let CancelOrderParams {
45        side,
46        price_in_ticks,
47        order_sequence_number,
48    } = base_params;
49    let order_id = FIFOOrderId::new(Ticks::new(price_in_ticks), order_sequence_number);
50
51    let vault_context_option = if withdraw_funds {
52        let Cancel { vault_context } = Cancel::load(market_context, accounts)?;
53        Some(vault_context)
54    } else {
55        None
56    };
57
58    let PhoenixMarketContext {
59        market_info,
60        signer: trader,
61    } = market_context;
62
63    let MatchingEngineResponse {
64        num_quote_lots_out,
65        num_base_lots_out,
66        ..
67    } = {
68        let market_bytes = &mut market_info.try_borrow_mut_data()?[size_of::<MarketHeader>()..];
69        let market = load_with_dispatch_mut(&market_info.size_params, market_bytes)?.inner;
70        sol_log_compute_units();
71        market
72            .reduce_order(
73                trader.key,
74                &order_id,
75                side,
76                Some(BaseLots::new(size)),
77                vault_context_option.is_some(),
78                record_event_fn,
79            )
80            .ok_or(PhoenixError::ReduceOrderError)?
81    };
82    sol_log_compute_units();
83
84    let header = market_info.get_header()?;
85
86    if let Some(PhoenixVaultContext {
87        base_account,
88        quote_account,
89        base_vault,
90        quote_vault,
91        token_program,
92    }) = vault_context_option
93    {
94        try_withdraw(
95            market_info.key,
96            &header.base_params,
97            &header.quote_params,
98            token_program.as_ref(),
99            quote_account.as_ref(),
100            quote_vault,
101            base_account.as_ref(),
102            base_vault,
103            num_quote_lots_out * header.get_quote_lot_size(),
104            num_base_lots_out * header.get_base_lot_size(),
105        )?;
106    } else {
107        // This case is only reached if the user is reducing orders with free funds
108        // In this case, there should be no funds to claim
109        assert_with_msg(
110            num_quote_lots_out == 0,
111            PhoenixError::ReduceOrderError,
112            "WARNING: num_quote_lots_out must be 0",
113        )?;
114        assert_with_msg(
115            num_base_lots_out == 0,
116            PhoenixError::ReduceOrderError,
117            "WARNING: num_base_lots_out must be 0",
118        )?;
119    }
120    Ok(())
121}