1use crate::{
2 program::{
3 assert_with_msg, dispatch_market::load_with_dispatch_mut,
4 loaders::CancelOrWithdrawContext as Cancel, token_utils::try_withdraw,
5 validation::checkers::phoenix_checkers::MarketAccountInfo, MarketHeader, PhoenixError,
6 PhoenixMarketContext, PhoenixVaultContext,
7 },
8 quantities::{Ticks, WrapperU64},
9 state::{
10 markets::{FIFOOrderId, MarketEvent},
11 MatchingEngineResponse, Side,
12 },
13};
14use borsh::{BorshDeserialize, BorshSerialize};
15use solana_program::{
16 account_info::AccountInfo, entrypoint::ProgramResult, log::sol_log_compute_units,
17 pubkey::Pubkey,
18};
19use std::mem::size_of;
20
21use super::CancelOrderParams;
22
23#[derive(BorshDeserialize, BorshSerialize, Clone, Copy)]
24pub struct CancelUpToParams {
25 pub side: Side,
26 pub tick_limit: Option<u64>,
27 pub num_orders_to_search: Option<u32>,
28 pub num_orders_to_cancel: Option<u32>,
29}
30
31#[derive(BorshDeserialize, BorshSerialize, Clone)]
32pub struct CancelMultipleOrdersByIdParams {
33 pub orders: Vec<CancelOrderParams>,
34}
35
36pub(crate) fn process_cancel_all_orders<'a, 'info>(
37 _program_id: &Pubkey,
38 market_context: &PhoenixMarketContext<'a, 'info>,
39 accounts: &'a [AccountInfo<'info>],
40 _data: &[u8],
41 withdraw_funds: bool,
42 record_event_fn: &mut dyn FnMut(MarketEvent<Pubkey>),
43) -> ProgramResult {
44 let vault_context_option = if withdraw_funds {
45 let Cancel { vault_context } = Cancel::load(market_context, accounts)?;
46 Some(vault_context)
47 } else {
48 None
49 };
50
51 let PhoenixMarketContext {
52 market_info,
53 signer: trader,
54 } = market_context;
55
56 let claim_funds = vault_context_option.is_some();
57 let MatchingEngineResponse {
58 num_base_lots_out,
59 num_quote_lots_out,
60 ..
61 } = {
62 let market_bytes = &mut market_info.try_borrow_mut_data()?[size_of::<MarketHeader>()..];
63 let market = load_with_dispatch_mut(&market_info.size_params, market_bytes)?.inner;
64 sol_log_compute_units();
65 market
66 .cancel_all_orders(trader.key, claim_funds, record_event_fn)
67 .unwrap_or_default()
68 };
69 sol_log_compute_units();
70
71 let header = market_info.get_header()?;
72
73 if let Some(PhoenixVaultContext {
74 base_account,
75 quote_account,
76 base_vault,
77 quote_vault,
78 token_program,
79 }) = vault_context_option
80 {
81 try_withdraw(
82 market_info.key,
83 &header.base_params,
84 &header.quote_params,
85 &token_program,
86 quote_account.as_ref(),
87 quote_vault,
88 base_account.as_ref(),
89 base_vault,
90 num_quote_lots_out * header.get_quote_lot_size(),
91 num_base_lots_out * header.get_base_lot_size(),
92 )?;
93 } else {
94 assert_with_msg(
97 num_quote_lots_out == 0,
98 PhoenixError::CancelMultipleOrdersError,
99 "WARNING: num_quote_lots_out must be 0",
100 )?;
101 assert_with_msg(
102 num_base_lots_out == 0,
103 PhoenixError::CancelMultipleOrdersError,
104 "WARNING: num_base_lots_out must be 0",
105 )?;
106 }
107
108 drop(header);
109 Ok(())
110}
111
112pub(crate) fn process_cancel_up_to<'a, 'info>(
113 _program_id: &Pubkey,
114 market_context: &PhoenixMarketContext<'a, 'info>,
115 accounts: &'a [AccountInfo<'info>],
116 data: &[u8],
117 withdraw_funds: bool,
118 record_event_fn: &mut dyn FnMut(MarketEvent<Pubkey>),
119) -> ProgramResult {
120 let vault_context_option = if withdraw_funds {
121 let Cancel { vault_context } = Cancel::load(market_context, accounts)?;
122 Some(vault_context)
123 } else {
124 None
125 };
126
127 let PhoenixMarketContext {
128 market_info,
129 signer: trader,
130 } = market_context;
131
132 let params = CancelUpToParams::try_from_slice(data)?;
133 process_cancel_orders(
134 market_info,
135 trader.key,
136 vault_context_option,
137 params,
138 record_event_fn,
139 )
140}
141
142pub(crate) fn process_cancel_multiple_orders_by_id<'a, 'info>(
143 _program_id: &Pubkey,
144 market_context: &PhoenixMarketContext<'a, 'info>,
145 accounts: &'a [AccountInfo<'info>],
146 data: &[u8],
147 withdraw_funds: bool,
148 record_event_fn: &mut dyn FnMut(MarketEvent<Pubkey>),
149) -> ProgramResult {
150 let vault_context_option = if withdraw_funds {
151 let Cancel { vault_context } = Cancel::load(market_context, accounts)?;
152 Some(vault_context)
153 } else {
154 None
155 };
156
157 let PhoenixMarketContext {
158 market_info,
159 signer: trader,
160 } = market_context;
161
162 let cancel_params = CancelMultipleOrdersByIdParams::try_from_slice(data)?;
163 if cancel_params.orders.is_empty() {
164 phoenix_log!("No orders to cancel");
165 return Ok(());
166 }
167
168 let MatchingEngineResponse {
169 num_quote_lots_out,
170 num_base_lots_out,
171 ..
172 } = {
173 sol_log_compute_units();
174 let market_bytes = &mut market_info.try_borrow_mut_data()?[size_of::<MarketHeader>()..];
175 let market = load_with_dispatch_mut(&market_info.size_params, market_bytes)?.inner;
176 let orders_to_cancel = cancel_params
177 .orders
178 .iter()
179 .filter_map(
180 |CancelOrderParams {
181 side,
182 price_in_ticks,
183 order_sequence_number,
184 }| {
185 if *side == Side::from_order_sequence_number(*order_sequence_number) {
186 Some(FIFOOrderId::new(
187 Ticks::new(*price_in_ticks),
188 *order_sequence_number,
189 ))
190 } else {
191 None
192 }
193 },
194 )
195 .collect::<Vec<_>>();
196
197 market
198 .cancel_multiple_orders_by_id(
199 trader.key,
200 &orders_to_cancel,
201 vault_context_option.is_some(),
202 record_event_fn,
203 )
204 .unwrap_or_default()
205 };
206 sol_log_compute_units();
207
208 let header = market_info.get_header()?;
209
210 if let Some(PhoenixVaultContext {
211 base_account,
212 quote_account,
213 base_vault,
214 quote_vault,
215 token_program,
216 }) = vault_context_option
217 {
218 try_withdraw(
219 market_info.key,
220 &header.base_params,
221 &header.quote_params,
222 &token_program,
223 quote_account.as_ref(),
224 quote_vault,
225 base_account.as_ref(),
226 base_vault,
227 num_quote_lots_out * header.get_quote_lot_size(),
228 num_base_lots_out * header.get_base_lot_size(),
229 )?;
230 } else {
231 assert_with_msg(
234 num_quote_lots_out == 0,
235 PhoenixError::CancelMultipleOrdersError,
236 "WARNING: num_quote_lots_out must be 0",
237 )?;
238 assert_with_msg(
239 num_base_lots_out == 0,
240 PhoenixError::CancelMultipleOrdersError,
241 "WARNING: num_base_lots_out must be 0",
242 )?;
243 }
244
245 Ok(())
246}
247
248#[allow(clippy::too_many_arguments)]
249pub(crate) fn process_cancel_orders<'a, 'info>(
250 market_info: &MarketAccountInfo<'a, 'info>,
251 trader_key: &Pubkey,
252 vault_context_option: Option<PhoenixVaultContext<'a, 'info>>,
253 cancel_params: CancelUpToParams,
254 record_event_fn: &mut dyn FnMut(MarketEvent<Pubkey>),
255) -> ProgramResult {
256 let CancelUpToParams {
257 side,
258 tick_limit,
259 num_orders_to_search,
260 num_orders_to_cancel,
261 } = cancel_params;
262
263 let claim_funds = vault_context_option.is_some();
264 let released = {
265 let market_bytes = &mut market_info.try_borrow_mut_data()?[size_of::<MarketHeader>()..];
266 let market = load_with_dispatch_mut(&market_info.size_params, market_bytes)?.inner;
267 sol_log_compute_units();
268 market
269 .cancel_up_to(
270 trader_key,
271 side,
272 num_orders_to_search.map(|x| x as usize),
273 num_orders_to_cancel.map(|x| x as usize),
274 tick_limit.map(Ticks::new),
275 claim_funds,
276 record_event_fn,
277 )
278 .unwrap_or_default()
279 };
280 sol_log_compute_units();
281
282 let header = market_info.get_header()?;
283
284 let MatchingEngineResponse {
285 num_quote_lots_out,
286 num_base_lots_out,
287 ..
288 } = released;
289 if let Some(PhoenixVaultContext {
290 base_account,
291 quote_account,
292 base_vault,
293 quote_vault,
294 token_program,
295 }) = vault_context_option
296 {
297 try_withdraw(
298 market_info.key,
299 &header.base_params,
300 &header.quote_params,
301 &token_program,
302 quote_account.as_ref(),
303 quote_vault,
304 base_account.as_ref(),
305 base_vault,
306 num_quote_lots_out * header.get_quote_lot_size(),
307 num_base_lots_out * header.get_base_lot_size(),
308 )?;
309 } else {
310 assert_with_msg(
313 num_quote_lots_out == 0,
314 PhoenixError::CancelMultipleOrdersError,
315 "WARNING: num_quote_lots_out must be 0",
316 )?;
317 assert_with_msg(
318 num_base_lots_out == 0,
319 PhoenixError::CancelMultipleOrdersError,
320 "WARNING: num_base_lots_out must be 0",
321 )?;
322 }
323
324 Ok(())
325}