1use std::cell::RefMut;
2
3use crate::{
4 logs::{emit_stack, CancelOrderLog, PlaceOrderLog},
5 program::get_trader_index_with_hint,
6 quantities::{BaseAtoms, PriceConversionError, QuoteAtomsPerBaseAtom, WrapperU64},
7 require,
8 state::{
9 utils::get_now_slot, AddOrderToMarketArgs, AddOrderToMarketResult, MarketRefMut, OrderType,
10 RestingOrder, MARKET_BLOCK_SIZE,
11 },
12 validation::loaders::BatchUpdateContext,
13};
14use borsh::{BorshDeserialize, BorshSerialize};
15
16use hypertree::{get_helper, trace, DataIndex, PodBool, RBNode};
17use solana_program::{
18 account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError,
19 pubkey::Pubkey,
20};
21
22use super::{expand_market_if_needed, shared::get_mut_dynamic_account};
23
24use crate::validation::loaders::GlobalTradeAccounts;
25#[cfg(feature = "certora")]
26use {
27 crate::certora::mocks_batch_update::{mock_cancel_order, mock_place_order},
28 early_panic::early_panic,
29 vectors::no_resizable_vec::NoResizableVec,
30};
31
32#[derive(Debug, BorshDeserialize, BorshSerialize, Clone)]
33pub struct CancelOrderParams {
34 order_sequence_number: u64,
35 order_index_hint: Option<DataIndex>,
36}
37
38impl CancelOrderParams {
39 pub fn new(order_sequence_number: u64) -> Self {
40 CancelOrderParams {
41 order_sequence_number,
42 order_index_hint: None,
43 }
44 }
45 pub fn new_with_hint(order_sequence_number: u64, order_index_hint: Option<DataIndex>) -> Self {
46 CancelOrderParams {
47 order_sequence_number,
48 order_index_hint,
49 }
50 }
51 pub fn order_sequence_number(&self) -> u64 {
52 self.order_sequence_number
53 }
54 pub fn order_index_hint(&self) -> Option<DataIndex> {
55 self.order_index_hint
56 }
57}
58
59#[derive(Debug, BorshDeserialize, BorshSerialize, Clone)]
60pub struct PlaceOrderParams {
61 base_atoms: u64,
62 price_mantissa: u32,
63 price_exponent: i8,
64 is_bid: bool,
65 last_valid_slot: u32,
66 order_type: OrderType,
67}
68
69impl PlaceOrderParams {
70 pub fn new(
71 base_atoms: u64,
72 price_mantissa: u32,
73 price_exponent: i8,
74 is_bid: bool,
75 order_type: OrderType,
76 last_valid_slot: u32,
77 ) -> Self {
78 PlaceOrderParams {
79 base_atoms,
80 price_mantissa,
81 price_exponent,
82 is_bid,
83 order_type,
84 last_valid_slot,
85 }
86 }
87 pub fn base_atoms(&self) -> u64 {
88 self.base_atoms
89 }
90
91 pub fn try_price(&self) -> Result<QuoteAtomsPerBaseAtom, PriceConversionError> {
92 QuoteAtomsPerBaseAtom::try_from_mantissa_and_exponent(
93 self.price_mantissa,
94 self.price_exponent,
95 )
96 }
97 pub fn is_bid(&self) -> bool {
98 self.is_bid
99 }
100 pub fn last_valid_slot(&self) -> u32 {
101 self.last_valid_slot
102 }
103 pub fn order_type(&self) -> OrderType {
104 self.order_type
105 }
106}
107
108#[derive(BorshDeserialize, BorshSerialize)]
109pub struct BatchUpdateParams {
110 pub trader_index_hint: Option<DataIndex>,
112 #[cfg(not(feature = "certora"))]
113 pub cancels: Vec<CancelOrderParams>,
114 #[cfg(feature = "certora")]
115 pub cancels: NoResizableVec<CancelOrderParams>,
116 #[cfg(not(feature = "certora"))]
117 pub orders: Vec<PlaceOrderParams>,
118 #[cfg(feature = "certora")]
119 pub orders: NoResizableVec<PlaceOrderParams>,
120}
121
122impl BatchUpdateParams {
123 pub fn new(
124 trader_index_hint: Option<DataIndex>,
125 #[cfg(not(feature = "certora"))] cancels: Vec<CancelOrderParams>,
126 #[cfg(feature = "certora")] cancels: NoResizableVec<CancelOrderParams>,
127 #[cfg(not(feature = "certora"))] orders: Vec<PlaceOrderParams>,
128 #[cfg(feature = "certora")] orders: NoResizableVec<PlaceOrderParams>,
129 ) -> Self {
130 BatchUpdateParams {
131 trader_index_hint,
132 cancels,
133 orders,
134 }
135 }
136}
137
138#[derive(BorshDeserialize, BorshSerialize)]
139pub struct BatchUpdateReturn {
140 pub orders: Vec<(u64, DataIndex)>,
142}
143
144#[repr(u8)]
145#[derive(Debug, Copy, Clone, PartialEq, Default)]
146pub enum MarketDataTreeNodeType {
147 Empty = 0,
149 #[default]
150 ClaimedSeat = 1,
151 RestingOrder = 2,
152}
153
154pub(crate) fn process_batch_update(
155 program_id: &Pubkey,
156 accounts: &[AccountInfo],
157 data: &[u8],
158) -> ProgramResult {
159 let params: BatchUpdateParams = BatchUpdateParams::try_from_slice(data)?;
160 process_batch_update_core(program_id, accounts, params)
161}
162
163#[cfg(not(feature = "certora"))]
164fn batch_cancel_order(
165 dynamic_account: &mut MarketRefMut,
166 trader_index: DataIndex,
167 order_sequence_number: u64,
168 global_trade_accounts_opts: &[Option<GlobalTradeAccounts>; 2],
169) -> ProgramResult {
170 dynamic_account.cancel_order(
171 trader_index,
172 order_sequence_number,
173 &global_trade_accounts_opts,
174 )
175}
176
177#[cfg(feature = "certora")]
178fn batch_cancel_order(
179 dynamic_account: &mut MarketRefMut,
180 trader_index: DataIndex,
181 order_sequence_number: u64,
182 global_trade_accounts_opts: &[Option<GlobalTradeAccounts>; 2],
183) -> ProgramResult {
184 mock_cancel_order(
185 &dynamic_account,
186 trader_index,
187 order_sequence_number,
188 &global_trade_accounts_opts,
189 )
190}
191
192#[cfg(not(feature = "certora"))]
193fn batch_place_order(
194 dynamic_account: &mut MarketRefMut,
195 args: AddOrderToMarketArgs,
196) -> Result<AddOrderToMarketResult, ProgramError> {
197 dynamic_account.place_order(args)
198}
199
200#[cfg(feature = "certora")]
201fn batch_place_order(
202 dynamic_account: &mut MarketRefMut,
203 args: AddOrderToMarketArgs,
204) -> Result<AddOrderToMarketResult, ProgramError> {
205 mock_place_order(dynamic_account, args)
206}
207
208#[cfg_attr(all(feature = "certora", not(feature = "certora-test")), early_panic)]
209pub(crate) fn process_batch_update_core(
210 _program_id: &Pubkey,
211 accounts: &[AccountInfo],
212 params: BatchUpdateParams,
213) -> ProgramResult {
214 let batch_update_context: BatchUpdateContext = BatchUpdateContext::load(accounts)?;
215
216 let BatchUpdateContext {
217 market,
218 payer,
219 global_trade_accounts_opts,
220 ..
221 } = batch_update_context;
222
223 let BatchUpdateParams {
224 trader_index_hint,
225 cancels,
226 orders,
227 } = params;
228
229 let current_slot: Option<u32> = Some(get_now_slot());
230
231 trace!("batch_update trader_index_hint:{trader_index_hint:?} cancels:{cancels:?} orders:{orders:?}");
232
233 let trader_index: DataIndex = {
234 let market_data: &mut RefMut<&mut [u8]> = &mut market.try_borrow_mut_data()?;
235
236 let mut dynamic_account: MarketRefMut = get_mut_dynamic_account(market_data);
237 let trader_index: DataIndex =
238 get_trader_index_with_hint(trader_index_hint, &dynamic_account, &payer)?;
239
240 for cancel_order_params in cancels {
241 match cancel_order_params.order_index_hint() {
245 None => {
246 batch_cancel_order(
248 &mut dynamic_account,
249 trader_index,
250 cancel_order_params.order_sequence_number(),
251 &global_trade_accounts_opts,
252 )?;
253 }
254 Some(hinted_cancel_index) => {
255 require!(
259 hinted_cancel_index % (MARKET_BLOCK_SIZE as DataIndex) == 0,
260 crate::program::ManifestError::WrongIndexHintParams,
261 "Invalid cancel hint index {}",
262 hinted_cancel_index,
263 )?;
264 require!(
265 get_helper::<RBNode<RestingOrder>>(
266 &dynamic_account.dynamic,
267 hinted_cancel_index,
268 )
269 .get_payload_type()
270 == MarketDataTreeNodeType::RestingOrder as u8,
271 crate::program::ManifestError::WrongIndexHintParams,
272 "Invalid cancel hint index {}",
273 hinted_cancel_index,
274 )?;
275 let order: &RestingOrder =
276 dynamic_account.get_order_by_index(hinted_cancel_index);
277 require!(
278 trader_index == order.get_trader_index(),
279 crate::program::ManifestError::WrongIndexHintParams,
280 "Invalid cancel hint index {}",
281 hinted_cancel_index,
282 )?;
283 require!(
284 cancel_order_params.order_sequence_number() == order.get_sequence_number(),
285 crate::program::ManifestError::WrongIndexHintParams,
286 "Invalid cancel hint sequence number index {}",
287 hinted_cancel_index,
288 )?;
289 dynamic_account
290 .cancel_order_by_index(hinted_cancel_index, &global_trade_accounts_opts)?;
291 }
292 };
293
294 emit_stack(CancelOrderLog {
295 market: *market.key,
296 trader: *payer.key,
297 order_sequence_number: cancel_order_params.order_sequence_number(),
298 })?;
299 }
300 trader_index
301 };
302
303 #[cfg(not(feature = "certora"))]
305 let mut result: Vec<(u64, DataIndex)> = Vec::with_capacity(orders.len());
306 #[cfg(feature = "certora")]
307 let mut result = NoResizableVec::<(u64, DataIndex)>::new(10);
308 for place_order_params in orders {
309 {
310 let base_atoms: BaseAtoms = BaseAtoms::new(place_order_params.base_atoms());
311 let price: QuoteAtomsPerBaseAtom = place_order_params.try_price()?;
312 let order_type: OrderType = place_order_params.order_type();
313 let last_valid_slot: u32 = place_order_params.last_valid_slot();
314
315 let market_data: &mut RefMut<&mut [u8]> = &mut market.try_borrow_mut_data()?;
317 let mut dynamic_account: MarketRefMut = get_mut_dynamic_account(market_data);
318
319 let add_order_to_market_result: AddOrderToMarketResult = batch_place_order(
320 &mut dynamic_account,
321 AddOrderToMarketArgs {
322 market: *market.key,
323 trader_index,
324 num_base_atoms: base_atoms,
325 price,
326 is_bid: place_order_params.is_bid(),
327 last_valid_slot,
328 order_type,
329 global_trade_accounts_opts: &global_trade_accounts_opts,
330 current_slot,
331 },
332 )?;
333
334 let AddOrderToMarketResult {
335 order_index,
336 order_sequence_number,
337 ..
338 } = add_order_to_market_result;
339
340 emit_stack(PlaceOrderLog {
341 market: *market.key,
342 trader: *payer.key,
343 base_atoms,
344 price,
345 order_type,
346 is_bid: PodBool::from(place_order_params.is_bid()),
347 _padding: [0; 6],
348 order_sequence_number,
349 order_index,
350 last_valid_slot,
351 })?;
352 result.push((order_sequence_number, order_index));
353 }
354 expand_market_if_needed(&payer, &market)?;
355 }
356
357 #[cfg(not(feature = "certora"))]
359 {
360 let mut buffer: Vec<u8> = Vec::with_capacity(
361 std::mem::size_of::<BatchUpdateReturn>()
362 + result.len() * 2 * std::mem::size_of::<u64>(),
363 );
364 let return_data: BatchUpdateReturn = BatchUpdateReturn { orders: result };
365 return_data.serialize(&mut buffer).unwrap();
366 solana_program::program::set_return_data(&buffer[..]);
367 }
368
369 Ok(())
370}