1#[cfg(not(feature = "certora"))]
2mod free_addr_helpers {
3 use crate::state::market::{MarketFixed, MarketUnusedFreeListPadding};
4 use hypertree::{DataIndex, FreeList};
5
6 pub fn get_free_address_on_market_fixed(
7 fixed: &mut MarketFixed,
8 dynamic: &mut [u8],
9 ) -> DataIndex {
10 let mut free_list: FreeList<MarketUnusedFreeListPadding> =
11 FreeList::new(dynamic, fixed.free_list_head_index);
12 let free_address: DataIndex = free_list.remove();
13 fixed.free_list_head_index = free_list.get_head();
14 free_address
15 }
16
17 pub fn get_free_address_on_market_fixed_for_seat(
18 fixed: &mut MarketFixed,
19 dynamic: &mut [u8],
20 ) -> DataIndex {
21 get_free_address_on_market_fixed(fixed, dynamic)
22 }
23
24 pub fn get_free_address_on_market_fixed_for_bid_order(
25 fixed: &mut MarketFixed,
26 dynamic: &mut [u8],
27 ) -> DataIndex {
28 get_free_address_on_market_fixed(fixed, dynamic)
29 }
30
31 pub fn get_free_address_on_market_fixed_for_ask_order(
32 fixed: &mut MarketFixed,
33 dynamic: &mut [u8],
34 ) -> DataIndex {
35 get_free_address_on_market_fixed(fixed, dynamic)
36 }
37
38 pub fn release_address_on_market_fixed(
39 fixed: &mut MarketFixed,
40 dynamic: &mut [u8],
41 index: DataIndex,
42 ) {
43 let mut free_list: FreeList<MarketUnusedFreeListPadding> =
44 FreeList::new(dynamic, fixed.free_list_head_index);
45 free_list.add(index);
46 fixed.free_list_head_index = index;
47 }
48
49 pub fn release_address_on_market_fixed_for_seat(
50 fixed: &mut MarketFixed,
51 dynamic: &mut [u8],
52 index: DataIndex,
53 ) {
54 release_address_on_market_fixed(fixed, dynamic, index);
55 }
56
57 pub fn release_address_on_market_fixed_for_bid_order(
58 fixed: &mut MarketFixed,
59 dynamic: &mut [u8],
60 index: DataIndex,
61 ) {
62 release_address_on_market_fixed(fixed, dynamic, index);
63 }
64
65 pub fn release_address_on_market_fixed_for_ask_order(
66 fixed: &mut MarketFixed,
67 dynamic: &mut [u8],
68 index: DataIndex,
69 ) {
70 release_address_on_market_fixed(fixed, dynamic, index);
71 }
72}
73
74#[cfg(feature = "certora")]
75mod free_addr_helpers {
76 use crate::state::market::MarketFixed;
77
78 use super::{is_main_seat_free, is_second_seat_free, main_trader_index, second_trader_index};
79 use hypertree::DataIndex;
80
81 pub fn get_free_address_on_market_fixed_for_seat(
82 _fixed: &mut MarketFixed,
83 _dynamic: &mut [u8],
84 ) -> DataIndex {
85 if is_main_seat_free() {
87 main_trader_index()
88 } else if is_second_seat_free() {
89 second_trader_index()
90 } else {
91 cvt::cvt_assert!(false);
92 crate::state::market::NIL
93 }
94 }
95
96 pub fn get_free_address_on_market_fixed_for_bid_order(
97 _fixed: &mut MarketFixed,
98 _dynamic: &mut [u8],
99 ) -> DataIndex {
100 if super::is_bid_order_free() {
101 super::main_bid_order_index()
102 } else {
103 cvt::cvt_assert!(false);
104 super::NIL
105 }
106 }
107
108 pub fn get_free_address_on_market_fixed_for_ask_order(
109 _fixed: &mut MarketFixed,
110 _dynamic: &mut [u8],
111 ) -> DataIndex {
112 if super::is_ask_order_free() {
113 super::main_ask_order_index()
114 } else {
115 cvt::cvt_assert!(false);
116 super::NIL
117 }
118 }
119
120 pub fn release_address_on_market_fixed_for_seat(
121 _fixed: &mut MarketFixed,
122 _dynamic: &mut [u8],
123 _index: DataIndex,
124 ) {
125 }
126
127 pub fn release_address_on_market_fixed_for_bid_order(
128 _fixed: &mut MarketFixed,
129 _dynamic: &mut [u8],
130 _index: DataIndex,
131 ) {
132 }
133
134 pub fn release_address_on_market_fixed_for_ask_order(
135 _fixed: &mut MarketFixed,
136 _dynamic: &mut [u8],
137 _index: DataIndex,
138 ) {
139 }
140}
141
142pub use free_addr_helpers::*;
143
144use super::*;
147
148#[derive(Default, PartialEq)]
149pub enum AddOrderStatus {
150 #[default]
151 Canceled,
152 Filled,
153 PartialFill,
154 Unmatched,
155 GlobalSkip,
156}
157
158#[derive(Default)]
159pub struct AddOrderToMarketInnerResult {
160 pub next_order_index: DataIndex,
161 pub status: AddOrderStatus,
162}
163
164pub struct AddSingleOrderCtx<'a, 'b, 'info> {
165 pub args: AddOrderToMarketArgs<'b, 'info>,
166 fixed: &'a mut MarketFixed,
167 dynamic: &'a mut [u8],
168 pub now_slot: u32,
169 pub remaining_base_atoms: BaseAtoms,
170 pub total_base_atoms_traded: BaseAtoms,
171 pub total_quote_atoms_traded: QuoteAtoms,
172}
173
174impl<'a, 'b, 'info> AddSingleOrderCtx<'a, 'b, 'info> {
175 pub fn new(
176 args: AddOrderToMarketArgs<'b, 'info>,
177 fixed: &'a mut MarketFixed,
178 dynamic: &'a mut [u8],
179 remaining_base_atoms: BaseAtoms,
180 now_slot: u32,
181 ) -> Self {
182 Self {
183 args,
184 fixed,
185 dynamic,
186 now_slot,
187 remaining_base_atoms,
188 total_base_atoms_traded: BaseAtoms::ZERO,
189 total_quote_atoms_traded: QuoteAtoms::ZERO,
190 }
191 }
192 pub fn place_single_order(
194 &mut self,
195 current_order_index: DataIndex,
196 ) -> Result<AddOrderToMarketInnerResult, ProgramError> {
197 let fixed: &mut _ = self.fixed;
198 let dynamic: &mut _ = self.dynamic;
199 let now_slot = self.now_slot;
200 let remaining_base_atoms = self.remaining_base_atoms;
201
202 let AddOrderToMarketArgs {
203 market,
204 trader_index,
205 num_base_atoms: _,
206 price,
207 is_bid,
208 last_valid_slot: _,
209 order_type,
210 global_trade_accounts_opts,
211 current_slot: _,
212 } = self.args;
213
214 let next_order_index: DataIndex =
215 get_next_candidate_match_index(fixed, dynamic, current_order_index, is_bid);
216
217 let other_order: &RestingOrder = get_helper_order(dynamic, current_order_index).get_value();
218
219 if other_order.is_expired(now_slot) {
221 remove_and_update_balances(
222 fixed,
223 dynamic,
224 current_order_index,
225 global_trade_accounts_opts,
226 )?;
227 return Ok(AddOrderToMarketInnerResult {
228 next_order_index,
229 status: AddOrderStatus::Canceled,
230 ..Default::default()
231 });
232 }
233
234 if (is_bid && other_order.get_price() > price)
236 || (!is_bid && other_order.get_price() < price)
237 {
238 return Ok(AddOrderToMarketInnerResult {
239 next_order_index: NIL,
240 status: AddOrderStatus::Unmatched,
241 ..Default::default()
242 });
243 }
244
245 trace!(
249 "match {} {order_type:?} {price:?} with {other_order:?}",
250 if is_bid { "bid" } else { "ask" }
251 );
252 assert_can_take(order_type)?;
253
254 let maker_sequence_number = other_order.get_sequence_number();
255 let other_trader_index: DataIndex = other_order.get_trader_index();
256 let did_fully_match_resting_order: bool =
257 remaining_base_atoms >= other_order.get_num_base_atoms();
258 let base_atoms_traded: BaseAtoms = if did_fully_match_resting_order {
259 other_order.get_num_base_atoms()
260 } else {
261 remaining_base_atoms
262 };
263
264 let matched_price: QuoteAtomsPerBaseAtom = other_order.get_price();
265
266 let quote_atoms_traded: QuoteAtoms = matched_price
269 .checked_quote_for_base(base_atoms_traded, is_bid != did_fully_match_resting_order)?;
270
271 let maker: Pubkey = get_helper_seat(dynamic, other_order.get_trader_index())
274 .get_value()
275 .trader;
276 let taker: Pubkey = get_helper_seat(dynamic, trader_index).get_value().trader;
277
278 if other_order.is_global() {
279 let global_trade_accounts_opt: &Option<GlobalTradeAccounts> = if is_bid {
280 &global_trade_accounts_opts[0]
281 } else {
282 &global_trade_accounts_opts[1]
283 };
284 let has_enough_tokens: bool = try_to_move_global_tokens(
285 global_trade_accounts_opt,
286 &maker,
287 GlobalAtoms::new(if is_bid {
288 quote_atoms_traded.as_u64()
289 } else {
290 base_atoms_traded.as_u64()
291 }),
292 )?;
293 if !has_enough_tokens {
294 remove_and_update_balances(
295 fixed,
296 dynamic,
297 current_order_index,
298 global_trade_accounts_opts,
299 )?;
300 return Ok(AddOrderToMarketInnerResult {
301 next_order_index,
302 status: AddOrderStatus::GlobalSkip,
303 ..Default::default()
304 });
305 }
306 }
307
308 self.total_base_atoms_traded = self
309 .total_base_atoms_traded
310 .checked_add(base_atoms_traded)?;
311 self.total_quote_atoms_traded = self
312 .total_quote_atoms_traded
313 .checked_add(quote_atoms_traded)?;
314
315 if !is_bid {
335 let other_order: &RestingOrder =
337 get_helper_order(dynamic, current_order_index).get_value();
338 let previous_maker_quote_atoms_allocated: QuoteAtoms =
339 matched_price.checked_quote_for_base(other_order.get_num_base_atoms(), true)?;
340 let new_maker_quote_atoms_allocated: QuoteAtoms = matched_price
341 .checked_quote_for_base(
342 other_order
343 .get_num_base_atoms()
344 .checked_sub(base_atoms_traded)?,
345 true,
346 )?;
347 update_balance(
348 fixed,
349 dynamic,
350 other_trader_index,
351 is_bid,
352 true,
353 (previous_maker_quote_atoms_allocated
354 .checked_sub(new_maker_quote_atoms_allocated)?
355 .checked_sub(quote_atoms_traded)?)
356 .as_u64(),
357 )?;
358 }
359
360 update_balance(
366 fixed,
367 dynamic,
368 trader_index,
369 !is_bid,
370 false,
371 if is_bid {
372 quote_atoms_traded.into()
373 } else {
374 base_atoms_traded.into()
375 },
376 )?;
377 update_balance(
379 fixed,
380 dynamic,
381 other_trader_index,
382 !is_bid,
383 true,
384 if is_bid {
385 quote_atoms_traded.into()
386 } else {
387 base_atoms_traded.into()
388 },
389 )?;
390 update_balance(
392 fixed,
393 dynamic,
394 trader_index,
395 is_bid,
396 true,
397 if is_bid {
398 base_atoms_traded.into()
399 } else {
400 quote_atoms_traded.into()
401 },
402 )?;
403
404 record_volume_by_trader_index(dynamic, other_trader_index, quote_atoms_traded);
406 record_volume_by_trader_index(dynamic, trader_index, quote_atoms_traded);
407
408 emit_stack(FillLog {
409 market,
410 maker,
411 taker,
412 base_atoms: base_atoms_traded,
413 quote_atoms: quote_atoms_traded,
414 price: matched_price,
415 maker_sequence_number,
416 taker_sequence_number: fixed.order_sequence_number,
417 taker_is_buy: PodBool::from(is_bid),
418 base_mint: *fixed.get_base_mint(),
419 quote_mint: *fixed.get_quote_mint(),
420 is_maker_global: PodBool::from(false),
422 _padding: [0; 14],
423 })?;
424
425 if did_fully_match_resting_order {
426 if get_helper_order(dynamic, current_order_index)
428 .get_value()
429 .get_order_type()
430 == OrderType::Global
431 {
432 if is_bid {
433 remove_from_global(&global_trade_accounts_opts[0])?;
434 } else {
435 remove_from_global(&global_trade_accounts_opts[1])?;
436 }
437 }
438
439 remove_order_from_tree_and_free(fixed, dynamic, current_order_index, !is_bid)?;
440 self.remaining_base_atoms = self.remaining_base_atoms.checked_sub(base_atoms_traded)?;
441 return Ok(AddOrderToMarketInnerResult {
442 next_order_index,
443 status: AddOrderStatus::Filled,
444 ..Default::default()
445 });
446 } else {
447 #[cfg(feature = "certora")]
448 remove_from_orderbook_balance(fixed, dynamic, current_order_index);
449 let other_order: &mut RestingOrder =
450 get_mut_helper_order(dynamic, current_order_index).get_mut_value();
451 other_order.reduce(base_atoms_traded)?;
452 #[cfg(feature = "certora")]
453 add_to_orderbook_balance(fixed, dynamic, current_order_index);
454 self.remaining_base_atoms = BaseAtoms::ZERO;
455 return Ok(AddOrderToMarketInnerResult {
456 next_order_index: NIL,
457 status: AddOrderStatus::PartialFill,
458 ..Default::default()
459 });
460 }
461 }
462}
463
464pub fn place_order_helper<
465 Fixed: DerefOrBorrowMut<MarketFixed> + DerefOrBorrow<MarketFixed>,
466 Dynamic: DerefOrBorrowMut<[u8]> + DerefOrBorrow<[u8]>,
467>(
468 self_: &mut DynamicAccount<Fixed, Dynamic>,
469 args: AddOrderToMarketArgs,
470) -> Result<AddOrderToMarketResult, ProgramError> {
471 let AddOrderToMarketArgs {
472 market: _,
473 trader_index,
474 num_base_atoms,
475 price: _,
476 is_bid,
477 last_valid_slot,
478 order_type,
479 global_trade_accounts_opts: _,
480 current_slot,
481 } = args;
482 assert_already_has_seat(trader_index)?;
483 let now_slot: u32 = current_slot.unwrap_or_else(|| get_now_slot());
484
485 assert_not_already_expired(last_valid_slot, now_slot)?;
486
487 let DynamicAccount { fixed, dynamic } = self_.borrow_mut();
488
489 let mut current_order_index: DataIndex = if is_bid {
490 fixed.asks_best_index
491 } else {
492 fixed.bids_best_index
493 };
494
495 let mut total_base_atoms_traded: BaseAtoms = BaseAtoms::ZERO;
496 let mut total_quote_atoms_traded: QuoteAtoms = QuoteAtoms::ZERO;
497
498 let mut remaining_base_atoms: BaseAtoms = num_base_atoms;
499
500 let mut ctx: AddSingleOrderCtx =
501 AddSingleOrderCtx::new(args, fixed, dynamic, remaining_base_atoms, now_slot);
502
503 while remaining_base_atoms > BaseAtoms::ZERO && is_not_nil!(current_order_index) {
504 let AddOrderToMarketInnerResult {
506 next_order_index,
507 status,
508 } = ctx.place_single_order(current_order_index)?;
509
510 current_order_index = next_order_index;
514 remaining_base_atoms = ctx.remaining_base_atoms;
515 total_base_atoms_traded = ctx.total_base_atoms_traded;
516 total_quote_atoms_traded = ctx.total_quote_atoms_traded;
517
518 if status == AddOrderStatus::Unmatched {
519 break;
520 } else if status == AddOrderStatus::PartialFill {
521 break;
522 }
523 }
524 let args: AddOrderToMarketArgs = ctx.args;
526 fixed.quote_volume = fixed.quote_volume.wrapping_add(total_quote_atoms_traded);
530
531 let order_sequence_number: u64 = fixed.order_sequence_number;
534 fixed.order_sequence_number = order_sequence_number.wrapping_add(1);
535
536 if !order_type_can_rest(order_type) || remaining_base_atoms == BaseAtoms::ZERO {
538 return Ok(AddOrderToMarketResult {
539 order_sequence_number,
540 order_index: NIL,
541 base_atoms_traded: total_base_atoms_traded,
542 quote_atoms_traded: total_quote_atoms_traded,
543 });
544 }
545
546 self_.rest_remaining(
547 args,
548 remaining_base_atoms,
549 order_sequence_number,
550 total_base_atoms_traded,
551 total_quote_atoms_traded,
552 )
553}