1use std::num::NonZeroU64;
2
3use crate::instruction::SelfTradeBehavior;
4use num_enum::{IntoPrimitive, TryFromPrimitive};
5#[cfg(test)]
6use proptest_derive::Arbitrary;
7use serde::{Deserialize, Serialize};
8#[cfg(feature = "program")]
9use solana_program::msg;
10
11use crate::critbit::SlabTreeError;
12use crate::error::{DexErrorCode, DexResult, SourceFileId};
13use crate::{
14 critbit::{LeafNode, NodeHandle, Slab, SlabView},
15 fees::{self, FeeTier},
16 state::{Event, EventQueue, EventView, MarketState, OpenOrders, RequestView},
17};
18
19#[cfg(not(feature = "program"))]
20macro_rules! msg {
21 ($($i:expr),*) => { { ($($i),*) } };
22}
23declare_check_assert_macros!(SourceFileId::Matching);
24
25#[derive(
26 Eq, PartialEq, Copy, Clone, TryFromPrimitive, IntoPrimitive, Debug, Serialize, Deserialize,
27)]
28#[cfg_attr(test, derive(Arbitrary))]
29#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
30#[repr(u8)]
31pub enum Side {
32 Bid = 0,
33 Ask = 1,
34}
35
36#[derive(
37 Eq, PartialEq, Copy, Clone, TryFromPrimitive, IntoPrimitive, Debug, Serialize, Deserialize,
38)]
39#[cfg_attr(test, derive(Arbitrary))]
40#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
41#[repr(u8)]
42pub enum OrderType {
43 Limit = 0,
44 ImmediateOrCancel = 1,
45 PostOnly = 2,
46}
47
48fn extract_price_from_order_id(order_id: u128) -> u64 {
49 (order_id >> 64) as u64
50}
51
52pub struct OrderBookState<'a> {
53 pub bids: &'a mut Slab,
55 pub asks: &'a mut Slab,
56 pub market_state: &'a mut MarketState,
57}
58
59impl<'ob> OrderBookState<'ob> {
60 fn orders_mut(&mut self, side: Side) -> &mut Slab {
61 match side {
62 Side::Bid => self.bids,
63 Side::Ask => self.asks,
64 }
65 }
66
67 fn find_bbo(&self, side: Side) -> Option<NodeHandle> {
68 match side {
69 Side::Bid => self.bids.find_max(),
70 Side::Ask => self.asks.find_min(),
71 }
72 }
73
74 pub(crate) fn process_orderbook_request(
75 &mut self,
76 request: &RequestView,
77 event_q: &mut EventQueue,
78 proceeds: &mut RequestProceeds,
79 limit: &mut u16,
80 ) -> DexResult<Option<RequestView>> {
81 Ok(match *request {
82 RequestView::NewOrder {
83 side,
84 order_type,
85 order_id,
86 owner_slot,
87 fee_tier,
88 owner,
89 max_coin_qty,
90 native_pc_qty_locked,
91 client_order_id,
92 self_trade_behavior,
93 } => self
94 .new_order(
95 NewOrderParams {
96 side,
97 order_type,
98 order_id,
99 owner,
100 owner_slot,
101 fee_tier,
102 max_coin_qty,
103 native_pc_qty_locked,
104 client_order_id: client_order_id.map_or(0, NonZeroU64::get),
105 self_trade_behavior,
106 },
107 event_q,
108 proceeds,
109 limit,
110 )?
111 .map(|remaining| RequestView::NewOrder {
112 side,
113 order_type,
114 order_id,
115 owner_slot,
116 fee_tier,
117 owner,
118 max_coin_qty: remaining.coin_qty_remaining,
119 native_pc_qty_locked: remaining.native_pc_qty_remaining,
120 client_order_id,
121 self_trade_behavior,
122 }),
123 RequestView::CancelOrder {
124 side,
125 order_id,
126 expected_owner_slot,
127 expected_owner,
128 client_order_id,
129 cancel_id: _,
130 } => {
131 *limit -= 1;
132 self.cancel_order(
133 side,
134 order_id,
135 expected_owner,
136 expected_owner_slot,
137 client_order_id,
138 event_q,
139 )?;
140 None
141 }
142 })
143 }
144
145 pub fn remove_all(
147 &mut self,
148 open_orders: [u64; 4],
149 mut limit: u16,
150 ) -> DexResult<(Vec<LeafNode>, Vec<LeafNode>)> {
151 let asks_matching_open_orders = self
152 .asks
153 .find_by(&mut limit, |order| order.owner().eq(&open_orders));
154 let bids_matching_open_orders = self
155 .bids
156 .find_by(&mut limit, |order| order.owner().eq(&open_orders));
157 let bids_removed: Vec<LeafNode> = bids_matching_open_orders
158 .iter()
159 .filter_map(|order_to_remove| self.bids.remove_by_key(*order_to_remove))
160 .collect();
161 let asks_removed: Vec<LeafNode> = asks_matching_open_orders
162 .iter()
163 .filter_map(|order_to_remove| self.asks.remove_by_key(*order_to_remove))
164 .collect();
165 Ok((bids_removed, asks_removed))
166 }
167}
168
169pub(crate) struct RequestProceeds {
170 pub coin_unlocked: u64,
171 pub native_pc_unlocked: u64,
172
173 pub coin_credit: u64,
174 pub native_pc_credit: u64,
175
176 pub coin_debit: u64,
177 pub native_pc_debit: u64,
178}
179
180macro_rules! impl_incr_method {
181 ($method:ident, $var:ident) => {
182 #[allow(unused)]
183 pub(crate) fn $method(&mut self, $var: u64) {
184 self.$var = self.$var.checked_add($var).unwrap();
185 }
186 };
187}
188
189impl RequestProceeds {
190 pub(crate) fn zero() -> Self {
191 Self {
192 coin_unlocked: 0,
193 native_pc_unlocked: 0,
194 coin_credit: 0,
195 native_pc_credit: 0,
196 coin_debit: 0,
197 native_pc_debit: 0,
198 }
199 }
200 impl_incr_method!(unlock_coin, coin_unlocked);
201 impl_incr_method!(unlock_native_pc, native_pc_unlocked);
202 impl_incr_method!(credit_coin, coin_credit);
203 impl_incr_method!(credit_native_pc, native_pc_credit);
204 impl_incr_method!(debit_coin, coin_debit);
205 impl_incr_method!(debit_native_pc, native_pc_debit);
206}
207
208pub(crate) struct NewOrderParams {
209 side: Side,
210 order_type: OrderType,
211 order_id: u128,
212 owner: [u64; 4],
213 owner_slot: u8,
214 fee_tier: FeeTier,
215 max_coin_qty: NonZeroU64,
216 native_pc_qty_locked: Option<NonZeroU64>,
217 client_order_id: u64,
218 self_trade_behavior: SelfTradeBehavior,
219}
220
221struct OrderRemaining {
222 coin_qty_remaining: NonZeroU64,
223 native_pc_qty_remaining: Option<NonZeroU64>,
224}
225
226impl<'ob> OrderBookState<'ob> {
227 fn new_order(
228 &mut self,
229
230 params: NewOrderParams,
231 event_q: &mut EventQueue,
232
233 proceeds: &mut RequestProceeds,
234
235 limit: &mut u16,
236 ) -> DexResult<Option<OrderRemaining>> {
237 let NewOrderParams {
238 side,
239 order_type,
240 order_id,
241 owner,
242 owner_slot,
243 fee_tier,
244 mut max_coin_qty,
245 mut native_pc_qty_locked,
246 client_order_id,
247 self_trade_behavior,
248 } = params;
249 let (mut post_only, mut post_allowed) = match order_type {
250 OrderType::Limit => (false, true),
251 OrderType::ImmediateOrCancel => (false, false),
252 OrderType::PostOnly => (true, true),
253 };
254 let limit_price = extract_price_from_order_id(order_id);
255 loop {
256 if *limit == 0 {
257 post_only = true;
259 post_allowed = true;
260 }
261
262 let remaining_order = match side {
263 Side::Bid => self.new_bid(
264 NewBidParams {
265 max_coin_qty,
266 native_pc_qty_locked: native_pc_qty_locked.unwrap(),
267 limit_price: NonZeroU64::new(limit_price),
268 order_id,
269 owner,
270 owner_slot,
271 fee_tier,
272 post_only,
273 post_allowed,
274 client_order_id,
275 self_trade_behavior,
276 },
277 event_q,
278 proceeds,
279 ),
280 Side::Ask => {
281 native_pc_qty_locked.ok_or(()).unwrap_err();
282 self.new_ask(
283 NewAskParams {
284 max_qty: max_coin_qty,
285 limit_price: NonZeroU64::new(limit_price).unwrap(),
286 order_id,
287 owner,
288 owner_slot,
289 fee_tier,
290 post_only,
291 post_allowed,
292 client_order_id,
293 self_trade_behavior,
294 },
295 event_q,
296 proceeds,
297 )
298 }
299 }?;
300 if *limit == 0 {
301 return Ok(remaining_order);
302 }
303 *limit -= 1;
304 match remaining_order {
305 Some(remaining_order) => {
306 max_coin_qty = remaining_order.coin_qty_remaining;
307 native_pc_qty_locked = remaining_order.native_pc_qty_remaining;
308 }
309 None => return Ok(None),
310 };
311 }
312 }
313}
314
315struct NewAskParams {
316 max_qty: NonZeroU64,
317 limit_price: NonZeroU64,
318 order_id: u128,
319 owner: [u64; 4],
320 owner_slot: u8,
321 fee_tier: FeeTier,
322 post_only: bool,
323 post_allowed: bool,
324 client_order_id: u64,
325 self_trade_behavior: SelfTradeBehavior,
326}
327
328impl<'ob> OrderBookState<'ob> {
329 fn new_ask(
330 &mut self,
331 params: NewAskParams,
332 event_q: &mut EventQueue,
333 to_release: &mut RequestProceeds,
334 ) -> DexResult<Option<OrderRemaining>> {
335 let NewAskParams {
336 max_qty,
337 limit_price,
338 order_id,
339 owner,
340 owner_slot,
341 fee_tier,
342 post_only,
343 post_allowed,
344 client_order_id,
345 self_trade_behavior,
346 } = params;
347 let mut unfilled_qty = max_qty.get();
348 let mut accum_fill_price = 0;
349
350 let pc_lot_size = self.market_state.pc_lot_size;
351 let coin_lot_size = self.market_state.coin_lot_size;
352
353 let mut accum_maker_rebates = 0;
354 let crossed;
355 let done = loop {
356 let best_bid_h = match self.find_bbo(Side::Bid) {
357 None => {
358 crossed = false;
359 break true;
360 }
361 Some(h) => h,
362 };
363
364 let best_bid_ref = self
365 .orders_mut(Side::Bid)
366 .get_mut(best_bid_h)
367 .unwrap()
368 .as_leaf_mut()
369 .unwrap();
370
371 let trade_price = best_bid_ref.price();
372 crossed = limit_price <= trade_price;
373
374 if !crossed || post_only {
375 break true;
376 }
377
378 let bid_size = best_bid_ref.quantity();
379 let trade_qty = bid_size.min(unfilled_qty);
380
381 if trade_qty == 0 {
382 break true;
383 }
384
385 let order_would_self_trade = owner == best_bid_ref.owner();
386 if order_would_self_trade {
387 let best_bid_id = best_bid_ref.order_id();
388 let cancelled_provide_qty;
389 let cancelled_take_qty;
390
391 match self_trade_behavior {
392 SelfTradeBehavior::DecrementTake => {
393 cancelled_provide_qty = trade_qty;
394 cancelled_take_qty = trade_qty;
395 }
396 SelfTradeBehavior::CancelProvide => {
397 cancelled_provide_qty = best_bid_ref.quantity();
398 cancelled_take_qty = 0;
399 }
400 SelfTradeBehavior::AbortTransaction => {
401 return Err(DexErrorCode::WouldSelfTrade.into())
402 }
403 };
404
405 let remaining_provide_size = bid_size - cancelled_provide_qty;
406 let provide_out = Event::new(EventView::Out {
407 side: Side::Bid,
408 release_funds: true,
409 native_qty_unlocked: cancelled_provide_qty * trade_price.get() * pc_lot_size,
410 native_qty_still_locked: remaining_provide_size
411 * trade_price.get()
412 * pc_lot_size,
413 order_id: best_bid_id,
414 owner: best_bid_ref.owner(),
415 owner_slot: best_bid_ref.owner_slot(),
416 client_order_id: NonZeroU64::new(best_bid_ref.client_order_id()),
417 });
418 event_q
419 .push_back(provide_out)
420 .map_err(|_| DexErrorCode::EventQueueFull)?;
421 if remaining_provide_size == 0 {
422 self.orders_mut(Side::Bid)
423 .remove_by_key(best_bid_id)
424 .unwrap();
425 } else {
426 best_bid_ref.set_quantity(remaining_provide_size);
427 }
428
429 unfilled_qty -= cancelled_take_qty;
430 let take_out = Event::new(EventView::Out {
431 side: Side::Ask,
432 release_funds: false,
433 native_qty_unlocked: cancelled_take_qty * coin_lot_size,
434 native_qty_still_locked: unfilled_qty,
435 order_id,
436 owner,
437 owner_slot,
438 client_order_id: NonZeroU64::new(client_order_id),
439 });
440 event_q
441 .push_back(take_out)
442 .map_err(|_| DexErrorCode::EventQueueFull)?;
443 to_release.unlock_coin(cancelled_take_qty);
444
445 let order_remaining =
446 NonZeroU64::new(unfilled_qty).map(|coin_qty_remaining| OrderRemaining {
447 coin_qty_remaining,
448 native_pc_qty_remaining: None,
449 });
450 return Ok(order_remaining);
451 }
452
453 let maker_fee_tier = best_bid_ref.fee_tier();
454 let native_maker_pc_qty = trade_qty * trade_price.get() * pc_lot_size;
455 let native_maker_rebate = maker_fee_tier.maker_rebate(native_maker_pc_qty);
456 accum_maker_rebates += native_maker_rebate;
457
458 let maker_fill = Event::new(EventView::Fill {
459 side: Side::Bid,
460 maker: true,
461 native_qty_paid: native_maker_pc_qty - native_maker_rebate,
462 native_qty_received: trade_qty * coin_lot_size,
463 native_fee_or_rebate: native_maker_rebate,
464 order_id: best_bid_ref.order_id(),
465 owner: best_bid_ref.owner(),
466 owner_slot: best_bid_ref.owner_slot(),
467 fee_tier: maker_fee_tier,
468 client_order_id: NonZeroU64::new(best_bid_ref.client_order_id()),
469 });
470 event_q
471 .push_back(maker_fill)
472 .map_err(|_| DexErrorCode::EventQueueFull)?;
473
474 best_bid_ref.set_quantity(best_bid_ref.quantity() - trade_qty);
475 unfilled_qty -= trade_qty;
476 accum_fill_price += trade_qty * trade_price.get();
477
478 if best_bid_ref.quantity() == 0 {
479 let best_bid_id = best_bid_ref.order_id();
480 event_q
481 .push_back(Event::new(EventView::Out {
482 side: Side::Bid,
483 release_funds: true,
484 native_qty_unlocked: 0,
485 native_qty_still_locked: 0,
486 order_id: best_bid_id,
487 owner: best_bid_ref.owner(),
488 owner_slot: best_bid_ref.owner_slot(),
489 client_order_id: NonZeroU64::new(best_bid_ref.client_order_id()),
490 }))
491 .map_err(|_| DexErrorCode::EventQueueFull)?;
492 self.orders_mut(Side::Bid)
493 .remove_by_key(best_bid_id)
494 .unwrap();
495 }
496
497 break false;
498 };
499
500 let native_taker_pc_qty = accum_fill_price * pc_lot_size;
501 let native_taker_fee = fee_tier.taker_fee(native_taker_pc_qty);
502
503 {
504 let net_taker_pc_qty = native_taker_pc_qty - native_taker_fee;
505 let coin_lots_traded = max_qty.get() - unfilled_qty;
506
507 to_release.credit_native_pc(net_taker_pc_qty);
508 to_release.debit_coin(coin_lots_traded);
509
510 if native_taker_pc_qty > 0 {
511 let taker_fill = Event::new(EventView::Fill {
512 side: Side::Ask,
513 maker: false,
514 native_qty_paid: coin_lots_traded * coin_lot_size,
515 native_qty_received: net_taker_pc_qty,
516 native_fee_or_rebate: native_taker_fee,
517 order_id,
518 owner,
519 owner_slot,
520 fee_tier,
521 client_order_id: NonZeroU64::new(client_order_id),
522 });
523 event_q
524 .push_back(taker_fill)
525 .map_err(|_| DexErrorCode::EventQueueFull)?;
526 }
527 }
528
529 let net_fees_before_referrer_rebate = native_taker_fee - accum_maker_rebates;
530 let referrer_rebate = fees::referrer_rebate(native_taker_fee);
531 let net_fees = net_fees_before_referrer_rebate - referrer_rebate;
532
533 self.market_state.referrer_rebates_accrued += referrer_rebate;
534 self.market_state.pc_fees_accrued += net_fees;
535 self.market_state.pc_deposits_total -= net_fees_before_referrer_rebate;
536
537 if !done {
538 if let Some(coin_qty_remaining) = NonZeroU64::new(unfilled_qty) {
539 return Ok(Some(OrderRemaining {
540 coin_qty_remaining,
541 native_pc_qty_remaining: None,
542 }));
543 }
544 }
545
546 if post_allowed && !crossed && unfilled_qty > 0 {
547 let offers = self.orders_mut(Side::Ask);
548 let new_order = LeafNode::new(
549 owner_slot,
550 order_id,
551 owner,
552 unfilled_qty,
553 fee_tier,
554 client_order_id,
555 );
556 let insert_result = offers.insert_leaf(&new_order);
557 if let Err(SlabTreeError::OutOfSpace) = insert_result {
558 msg!("offers full! booting...");
560 let order = offers.remove_max().unwrap();
561 let out = Event::new(EventView::Out {
562 side: Side::Ask,
563 release_funds: true,
564 native_qty_unlocked: order.quantity() * coin_lot_size,
565 native_qty_still_locked: 0,
566 order_id: order.order_id(),
567 owner: order.owner(),
568 owner_slot: order.owner_slot(),
569 client_order_id: NonZeroU64::new(order.client_order_id()),
570 });
571 event_q
572 .push_back(out)
573 .map_err(|_| DexErrorCode::EventQueueFull)?;
574 offers.insert_leaf(&new_order).unwrap();
575 } else {
576 insert_result.unwrap();
577 }
578 } else {
579 to_release.unlock_coin(unfilled_qty);
580 let out = Event::new(EventView::Out {
581 side: Side::Ask,
582 release_funds: false,
583 native_qty_unlocked: unfilled_qty * coin_lot_size,
584 native_qty_still_locked: 0,
585 order_id,
586 owner,
587 owner_slot,
588 client_order_id: NonZeroU64::new(client_order_id),
589 });
590 event_q
591 .push_back(out)
592 .map_err(|_| DexErrorCode::EventQueueFull)?;
593 }
594
595 Ok(None)
596 }
597}
598
599struct NewBidParams {
600 max_coin_qty: NonZeroU64,
601 native_pc_qty_locked: NonZeroU64,
602 limit_price: Option<NonZeroU64>,
603 order_id: u128,
604 owner: [u64; 4],
605 owner_slot: u8,
606 fee_tier: FeeTier,
607 post_only: bool,
608 post_allowed: bool,
609 client_order_id: u64,
610 self_trade_behavior: SelfTradeBehavior,
611}
612
613impl<'ob> OrderBookState<'ob> {
614 fn new_bid(
615 &mut self,
616 params: NewBidParams,
617 event_q: &mut EventQueue,
618 to_release: &mut RequestProceeds,
619 ) -> DexResult<Option<OrderRemaining>> {
620 let NewBidParams {
621 max_coin_qty,
622 native_pc_qty_locked,
623 limit_price,
624 order_id,
625 owner,
626 owner_slot,
627 fee_tier,
628 post_only,
629 post_allowed,
630 client_order_id,
631 self_trade_behavior,
632 } = params;
633 if post_allowed {
634 check_assert!(limit_price.is_some())?;
635 }
636
637 let pc_lot_size = self.market_state.pc_lot_size;
638 let coin_lot_size = self.market_state.coin_lot_size;
639
640 let max_pc_qty = fee_tier.remove_taker_fee(native_pc_qty_locked.get()) / pc_lot_size;
641
642 let mut coin_qty_remaining = max_coin_qty.get();
643 let mut pc_qty_remaining = max_pc_qty;
644 let mut accum_maker_rebates = 0;
645
646 let crossed;
647 let done = loop {
648 let best_offer_h = match self.find_bbo(Side::Ask) {
649 None => {
650 crossed = false;
651 break true;
652 }
653 Some(h) => h,
654 };
655
656 let best_offer_ref = self
657 .orders_mut(Side::Ask)
658 .get_mut(best_offer_h)
659 .unwrap()
660 .as_leaf_mut()
661 .unwrap();
662
663 let trade_price = best_offer_ref.price();
664 crossed = limit_price
665 .map(|limit_price| limit_price >= trade_price)
666 .unwrap_or(true);
667 if !crossed || post_only {
668 break true;
669 }
670
671 let offer_size = best_offer_ref.quantity();
672 let trade_qty = offer_size
673 .min(coin_qty_remaining)
674 .min(pc_qty_remaining / best_offer_ref.price().get());
675
676 if trade_qty == 0 {
677 break true;
678 }
679
680 let order_would_self_trade = owner == best_offer_ref.owner();
681 if order_would_self_trade {
682 let best_offer_id = best_offer_ref.order_id();
683
684 let cancelled_take_qty;
685 let cancelled_provide_qty;
686
687 match self_trade_behavior {
688 SelfTradeBehavior::CancelProvide => {
689 cancelled_take_qty = 0;
690 cancelled_provide_qty = best_offer_ref.quantity();
691 }
692 SelfTradeBehavior::DecrementTake => {
693 cancelled_take_qty = trade_qty;
694 cancelled_provide_qty = trade_qty;
695 }
696 SelfTradeBehavior::AbortTransaction => {
697 return Err(DexErrorCode::WouldSelfTrade.into())
698 }
699 };
700
701 let remaining_provide_qty = best_offer_ref.quantity() - cancelled_provide_qty;
702 let provide_out = Event::new(EventView::Out {
703 side: Side::Ask,
704 release_funds: true,
705 native_qty_unlocked: cancelled_provide_qty * coin_lot_size,
706 native_qty_still_locked: remaining_provide_qty * coin_lot_size,
707 order_id: best_offer_id,
708 owner: best_offer_ref.owner(),
709 owner_slot: best_offer_ref.owner_slot(),
710 client_order_id: NonZeroU64::new(best_offer_ref.client_order_id()),
711 });
712 event_q
713 .push_back(provide_out)
714 .map_err(|_| DexErrorCode::EventQueueFull)?;
715 if remaining_provide_qty == 0 {
716 self.orders_mut(Side::Ask)
717 .remove_by_key(best_offer_id)
718 .unwrap();
719 } else {
720 best_offer_ref.set_quantity(remaining_provide_qty);
721 }
722
723 let native_taker_pc_unlocked = cancelled_take_qty * trade_price.get() * pc_lot_size;
724 let native_taker_pc_still_locked =
725 native_pc_qty_locked.get() - native_taker_pc_unlocked;
726
727 let order_remaining = (|| {
728 Some(OrderRemaining {
729 coin_qty_remaining: NonZeroU64::new(
730 coin_qty_remaining - cancelled_take_qty,
731 )?,
732 native_pc_qty_remaining: Some(NonZeroU64::new(
733 native_taker_pc_still_locked,
734 )?),
735 })
736 })();
737
738 {
739 let native_qty_unlocked;
740 let native_qty_still_locked;
741 match order_remaining {
742 Some(_) => {
743 native_qty_unlocked = native_taker_pc_unlocked;
744 native_qty_still_locked = native_taker_pc_still_locked;
745 }
746 None => {
747 native_qty_unlocked = native_pc_qty_locked.get();
748 native_qty_still_locked = 0;
749 }
750 };
751 to_release.unlock_native_pc(native_qty_unlocked);
752 let take_out = Event::new(EventView::Out {
753 side: Side::Bid,
754 release_funds: false,
755 native_qty_unlocked,
756 native_qty_still_locked,
757 order_id,
758 owner,
759 owner_slot,
760 client_order_id: NonZeroU64::new(client_order_id),
761 });
762 event_q
763 .push_back(take_out)
764 .map_err(|_| DexErrorCode::EventQueueFull)?;
765 };
766
767 return Ok(order_remaining);
768 }
769 let maker_fee_tier = best_offer_ref.fee_tier();
770 let native_maker_pc_qty = trade_qty * trade_price.get() * pc_lot_size;
771 let native_maker_rebate = maker_fee_tier.maker_rebate(native_maker_pc_qty);
772 accum_maker_rebates += native_maker_rebate;
773
774 let maker_fill = Event::new(EventView::Fill {
775 side: Side::Ask,
776 maker: true,
777 native_qty_paid: trade_qty * coin_lot_size,
778 native_qty_received: native_maker_pc_qty + native_maker_rebate,
779 native_fee_or_rebate: native_maker_rebate,
780 order_id: best_offer_ref.order_id(),
781 owner: best_offer_ref.owner(),
782 owner_slot: best_offer_ref.owner_slot(),
783 fee_tier: maker_fee_tier,
784 client_order_id: NonZeroU64::new(best_offer_ref.client_order_id()),
785 });
786 event_q
787 .push_back(maker_fill)
788 .map_err(|_| DexErrorCode::EventQueueFull)?;
789
790 best_offer_ref.set_quantity(best_offer_ref.quantity() - trade_qty);
791 coin_qty_remaining -= trade_qty;
792 pc_qty_remaining -= trade_qty * trade_price.get();
793
794 if best_offer_ref.quantity() == 0 {
795 let best_offer_id = best_offer_ref.order_id();
796 event_q
797 .push_back(Event::new(EventView::Out {
798 side: Side::Ask,
799 release_funds: true,
800 native_qty_unlocked: 0,
801 native_qty_still_locked: 0,
802 order_id: best_offer_id,
803 owner: best_offer_ref.owner(),
804 owner_slot: best_offer_ref.owner_slot(),
805 client_order_id: NonZeroU64::new(best_offer_ref.client_order_id()),
806 }))
807 .map_err(|_| DexErrorCode::EventQueueFull)?;
808 self.orders_mut(Side::Ask)
809 .remove_by_key(best_offer_id)
810 .unwrap();
811 }
812
813 break false;
814 };
815
816 let native_accum_fill_price = (max_pc_qty - pc_qty_remaining) * pc_lot_size;
817 let native_taker_fee = fee_tier.taker_fee(native_accum_fill_price);
818 let native_pc_qty_remaining =
819 native_pc_qty_locked.get() - native_accum_fill_price - native_taker_fee;
820
821 {
822 let coin_lots_received = max_coin_qty.get() - coin_qty_remaining;
823 let native_pc_paid = native_accum_fill_price + native_taker_fee;
824
825 to_release.credit_coin(coin_lots_received);
826 to_release.debit_native_pc(native_pc_paid);
827
828 if native_accum_fill_price > 0 {
829 let taker_fill = Event::new(EventView::Fill {
830 side: Side::Bid,
831 maker: false,
832 native_qty_paid: native_pc_paid,
833 native_qty_received: coin_lots_received * coin_lot_size,
834 native_fee_or_rebate: native_taker_fee,
835 order_id,
836 owner,
837 owner_slot,
838 fee_tier,
839 client_order_id: NonZeroU64::new(client_order_id),
840 });
841 event_q
842 .push_back(taker_fill)
843 .map_err(|_| DexErrorCode::EventQueueFull)?;
844 }
845 }
846
847 let net_fees_before_referrer_rebate = native_taker_fee - accum_maker_rebates;
848 let referrer_rebate = fees::referrer_rebate(native_taker_fee);
849 let net_fees = net_fees_before_referrer_rebate - referrer_rebate;
850
851 self.market_state.referrer_rebates_accrued += referrer_rebate;
852 self.market_state.pc_fees_accrued += net_fees;
853 self.market_state.pc_deposits_total -= net_fees_before_referrer_rebate;
854
855 if !done {
856 if let Some(coin_qty_remaining) = NonZeroU64::new(coin_qty_remaining) {
857 if let Some(native_pc_qty_remaining) = NonZeroU64::new(native_pc_qty_remaining) {
858 return Ok(Some(OrderRemaining {
859 coin_qty_remaining,
860 native_pc_qty_remaining: Some(native_pc_qty_remaining),
861 }));
862 }
863 }
864 }
865
866 let (coin_qty_to_post, pc_qty_to_keep_locked) = match limit_price {
867 Some(price) if post_allowed && !crossed => {
868 let coin_qty_to_post =
869 coin_qty_remaining.min(native_pc_qty_remaining / pc_lot_size / price.get());
870 (coin_qty_to_post, coin_qty_to_post * price.get())
871 }
872 _ => (0, 0),
873 };
874
875 let out = {
876 let native_qty_still_locked = pc_qty_to_keep_locked * pc_lot_size;
877 let native_qty_unlocked = native_pc_qty_remaining - native_qty_still_locked;
878
879 to_release.unlock_native_pc(native_qty_unlocked);
880
881 Event::new(EventView::Out {
882 side: Side::Bid,
883 release_funds: false,
884 native_qty_unlocked,
885 native_qty_still_locked,
886 order_id,
887 owner,
888 owner_slot,
889 client_order_id: NonZeroU64::new(client_order_id),
890 })
891 };
892 event_q
893 .push_back(out)
894 .map_err(|_| DexErrorCode::EventQueueFull)?;
895
896 if pc_qty_to_keep_locked > 0 {
897 let bids = self.orders_mut(Side::Bid);
898 let new_leaf = LeafNode::new(
899 owner_slot,
900 order_id,
901 owner,
902 coin_qty_to_post,
903 fee_tier,
904 client_order_id,
905 );
906 let insert_result = bids.insert_leaf(&new_leaf);
907 if let Err(SlabTreeError::OutOfSpace) = insert_result {
908 msg!("bids full! booting...");
910 let order = bids.remove_min().unwrap();
911 let out = Event::new(EventView::Out {
912 side: Side::Bid,
913 release_funds: true,
914 native_qty_unlocked: order.quantity() * order.price().get() * pc_lot_size,
915 native_qty_still_locked: 0,
916 order_id: order.order_id(),
917 owner: order.owner(),
918 owner_slot: order.owner_slot(),
919 client_order_id: NonZeroU64::new(order.client_order_id()),
920 });
921 event_q
922 .push_back(out)
923 .map_err(|_| DexErrorCode::EventQueueFull)?;
924 bids.insert_leaf(&new_leaf).unwrap();
925 } else {
926 insert_result.unwrap();
927 }
928 }
929
930 Ok(None)
931 }
932
933 pub(crate) fn cancel_order_v2(
934 &mut self,
935 side: Side,
936 open_orders_address: [u64; 4],
937 open_orders: &mut OpenOrders,
938 order_id: u128,
939 event_q: &mut EventQueue,
940 ) -> DexResult {
941 let leaf_node = self
942 .orders_mut(side)
943 .remove_by_key(order_id)
944 .ok_or(DexErrorCode::OrderNotFound)?;
945 self.cancel_leaf_node(
946 leaf_node,
947 side,
948 open_orders,
949 open_orders_address,
950 order_id,
951 event_q,
952 )
953 }
954
955 pub(crate) fn cancel_leaf_node(
956 &mut self,
957 leaf_node: LeafNode,
958 side: Side,
959 open_orders: &mut OpenOrders,
960 open_orders_address: [u64; 4],
961 order_id: u128,
962 event_q: &mut EventQueue,
963 ) -> DexResult {
964 check_assert_eq!(leaf_node.owner(), open_orders_address)
965 .or(Err(DexErrorCode::OrderNotYours))?;
966
967 let open_orders_slot = leaf_node.owner_slot();
968 check_assert_eq!(order_id, open_orders.orders[open_orders_slot as usize])?;
969 check_assert_eq!(Some(side), open_orders.slot_side(open_orders_slot))?;
970
971 let native_qty_unlocked;
972 match side {
973 Side::Bid => {
974 native_qty_unlocked =
975 leaf_node.quantity() * leaf_node.price().get() * self.market_state.pc_lot_size;
976 open_orders.unlock_pc(native_qty_unlocked);
977 }
978 Side::Ask => {
979 native_qty_unlocked = leaf_node.quantity() * self.market_state.coin_lot_size;
980 open_orders.unlock_coin(native_qty_unlocked);
981 }
982 }
983 event_q
984 .push_back(Event::new(EventView::Out {
985 side,
986 release_funds: false,
987 native_qty_unlocked,
988 native_qty_still_locked: 0,
989 order_id,
990 owner: open_orders_address,
991 owner_slot: open_orders_slot,
992 client_order_id: NonZeroU64::new(leaf_node.client_order_id()),
993 }))
994 .map_err(|_| DexErrorCode::EventQueueFull)?;
995 Ok(())
996 }
997
998 fn cancel_order(
999 &mut self,
1000 side: Side,
1001 order_id: u128,
1002 expected_owner: [u64; 4],
1003 expected_owner_slot: u8,
1004 client_order_id: Option<NonZeroU64>,
1005 event_q: &mut EventQueue,
1006 ) -> DexResult<()> {
1007 if let Some(leaf_node) = self.orders_mut(side).remove_by_key(order_id) {
1008 if leaf_node.owner() == expected_owner && leaf_node.owner_slot() == expected_owner_slot
1009 {
1010 if let Some(client_id) = client_order_id {
1011 debug_assert_eq!(client_id.get(), leaf_node.client_order_id());
1012 }
1013 let native_qty_unlocked = match side {
1014 Side::Bid => {
1015 leaf_node.quantity()
1016 * leaf_node.price().get()
1017 * self.market_state.pc_lot_size
1018 }
1019 Side::Ask => leaf_node.quantity() * self.market_state.coin_lot_size,
1020 };
1021 event_q
1022 .push_back(Event::new(EventView::Out {
1023 side,
1024 release_funds: true,
1025 native_qty_unlocked,
1026 native_qty_still_locked: 0,
1027 order_id,
1028 owner: expected_owner,
1029 owner_slot: expected_owner_slot,
1030 client_order_id: NonZeroU64::new(leaf_node.client_order_id()),
1031 }))
1032 .map_err(|_| DexErrorCode::EventQueueFull)?;
1033 } else {
1034 self.orders_mut(side).insert_leaf(&leaf_node).unwrap();
1035 }
1036 }
1037 Ok(())
1038 }
1039}