serum_dex/
matching.rs

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    // first byte of a key is 0xaa or 0xbb, disambiguating bids and asks
54    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    // Removes all orders belonging to the given open orders account.
146    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                // Stop matching and release funds if we're out of cycles
258                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                // boot out the least aggressive offer
559                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                // boot out the least aggressive bid
909                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}