phoenix/state/order_schema/
order_packet.rs

1// By aliasing the BorshDeserialize and BorshSerialize traits, we prevent Shank from
2// writing structs with these annotations to the IDL.
3use borsh::{BorshDeserialize as Deserialize, BorshSerialize as Serialize};
4
5use crate::{
6    quantities::{BaseLots, QuoteLots, Ticks, WrapperU64},
7    state::{SelfTradeBehavior, Side},
8};
9
10pub trait OrderPacketMetadata {
11    fn is_take_only(&self) -> bool {
12        self.is_ioc() || self.is_fok()
13    }
14    fn is_ioc(&self) -> bool;
15    fn is_fok(&self) -> bool;
16    fn is_post_only(&self) -> bool;
17    fn no_deposit_or_withdrawal(&self) -> bool;
18}
19
20#[derive(Deserialize, Serialize, Copy, Clone, PartialEq, Eq, Debug)]
21pub enum OrderPacket {
22    /// This order type is used to place a limit order on the book.
23    /// It will never be matched against other existing limit orders
24    PostOnly {
25        side: Side,
26
27        /// The price of the order, in ticks
28        price_in_ticks: Ticks,
29
30        /// Number of base lots to place on the book
31        num_base_lots: BaseLots,
32
33        /// Client order id used to identify the order in the response to the client
34        client_order_id: u128,
35
36        /// Flag for whether or not to reject the order if it would immediately match or amend it to the best non-crossing price
37        /// Default value is true
38        reject_post_only: bool,
39
40        /// Flag for whether or not the order should only use funds that are already in the account
41        /// Using only deposited funds will allow the trader to pass in less accounts per instruction and
42        /// save transaction space as well as compute. This is only for traders who have a seat
43        use_only_deposited_funds: bool,
44
45        /// If this is set, the order will be invalid after the specified slot
46        last_valid_slot: Option<u64>,
47
48        /// If this is set, the order will be invalid after the specified unix timestamp
49        last_valid_unix_timestamp_in_seconds: Option<u64>,
50
51        /// If this is set, the order will fail silently if there are insufficient funds
52        fail_silently_on_insufficient_funds: bool,
53    },
54
55    /// This order type is used to place a limit order on the book
56    /// It can be matched against other existing limit orders, but will posted at the
57    /// specified level if it is not matched
58    Limit {
59        side: Side,
60
61        /// The price of the order, in ticks
62        price_in_ticks: Ticks,
63
64        /// Total number of base lots to place on the book or fill at a better price
65        num_base_lots: BaseLots,
66
67        /// How the matching engine should handle a self trade
68        self_trade_behavior: SelfTradeBehavior,
69
70        /// Number of orders to match against. If this is `None` there is no limit
71        match_limit: Option<u64>,
72
73        /// Client order id used to identify the order in the response to the client
74        client_order_id: u128,
75
76        /// Flag for whether or not the order should only use funds that are already in the account.
77        /// Using only deposited funds will allow the trader to pass in less accounts per instruction and
78        /// save transaction space as well as compute. This is only for traders who have a seat
79        use_only_deposited_funds: bool,
80
81        /// If this is set, the order will be invalid after the specified slot
82        last_valid_slot: Option<u64>,
83
84        /// If this is set, the order will be invalid after the specified unix timestamp
85        last_valid_unix_timestamp_in_seconds: Option<u64>,
86
87        /// If this is set, the order will fail silently if there are insufficient funds
88        fail_silently_on_insufficient_funds: bool,
89    },
90
91    /// This order type is used to place an order that will be matched against existing resting orders
92    /// If the order matches fewer than `min_lots` lots, it will be cancelled.
93    ///
94    /// Fill or Kill (FOK) orders are a subset of Immediate or Cancel (IOC) orders where either
95    /// the `num_base_lots` is equal to the `min_base_lots_to_fill` of the order, or the `num_quote_lots` is
96    /// equal to the `min_quote_lots_to_fill` of the order.
97    ImmediateOrCancel {
98        side: Side,
99
100        /// The most aggressive price an order can be matched at. For example, if there is an IOC buy order
101        /// to purchase 10 lots with the tick_per_lot parameter set to 10, then the order will never
102        /// be matched at a price higher than 10 quote ticks per base unit. If this value is None, then the order
103        /// is treated as a market order.
104        price_in_ticks: Option<Ticks>,
105
106        /// The number of base lots to fill against the order book. Either this parameter or the `num_quote_lots`
107        /// parameter must be set to a nonzero value.
108        num_base_lots: BaseLots,
109
110        /// The number of quote lots to fill against the order book. Either this parameter or the `num_base_lots`
111        /// parameter must be set to a nonzero value.
112        num_quote_lots: QuoteLots,
113
114        /// The minimum number of base lots to fill against the order book. If the order does not fill
115        /// this many base lots, it will be voided.
116        min_base_lots_to_fill: BaseLots,
117
118        /// The minimum number of quote lots to fill against the order book. If the order does not fill
119        /// this many quote lots, it will be voided.
120        min_quote_lots_to_fill: QuoteLots,
121
122        /// How the matching engine should handle a self trade.
123        self_trade_behavior: SelfTradeBehavior,
124
125        /// Number of orders to match against. If set to `None`, there is no limit.
126        match_limit: Option<u64>,
127
128        /// Client order id used to identify the order in the program's inner instruction data.
129        client_order_id: u128,
130
131        /// Flag for whether or not the order should only use funds that are already in the account.
132        /// Using only deposited funds will allow the trader to pass in less accounts per instruction and
133        /// save transaction space as well as compute. This is only for traders who have a seat
134        use_only_deposited_funds: bool,
135
136        /// If this is set, the order will be invalid after the specified slot
137        last_valid_slot: Option<u64>,
138
139        /// If this is set, the order will be invalid after the specified unix timestamp
140        last_valid_unix_timestamp_in_seconds: Option<u64>,
141    },
142}
143
144impl OrderPacketMetadata for OrderPacket {
145    fn is_ioc(&self) -> bool {
146        matches!(self, OrderPacket::ImmediateOrCancel { .. })
147    }
148
149    fn is_fok(&self) -> bool {
150        match self {
151            &Self::ImmediateOrCancel {
152                num_base_lots,
153                num_quote_lots,
154                min_base_lots_to_fill,
155                min_quote_lots_to_fill,
156                ..
157            } => {
158                num_base_lots > BaseLots::ZERO && num_base_lots == min_base_lots_to_fill
159                    || num_quote_lots > QuoteLots::ZERO && num_quote_lots == min_quote_lots_to_fill
160            }
161            _ => false,
162        }
163    }
164
165    fn is_post_only(&self) -> bool {
166        matches!(self, OrderPacket::PostOnly { .. })
167    }
168
169    fn no_deposit_or_withdrawal(&self) -> bool {
170        match *self {
171            Self::PostOnly {
172                use_only_deposited_funds,
173                ..
174            } => use_only_deposited_funds,
175            Self::Limit {
176                use_only_deposited_funds,
177                ..
178            } => use_only_deposited_funds,
179            Self::ImmediateOrCancel {
180                use_only_deposited_funds,
181                ..
182            } => use_only_deposited_funds,
183        }
184    }
185}
186
187impl OrderPacket {
188    pub fn new_post_only_default(side: Side, price_in_ticks: u64, num_base_lots: u64) -> Self {
189        Self::PostOnly {
190            side,
191            price_in_ticks: Ticks::new(price_in_ticks),
192            num_base_lots: BaseLots::new(num_base_lots),
193            client_order_id: 0,
194            reject_post_only: true,
195            use_only_deposited_funds: false,
196            last_valid_slot: None,
197            last_valid_unix_timestamp_in_seconds: None,
198            fail_silently_on_insufficient_funds: false,
199        }
200    }
201
202    pub fn new_post_only_default_with_client_order_id(
203        side: Side,
204        price_in_ticks: u64,
205        num_base_lots: u64,
206        client_order_id: u128,
207    ) -> Self {
208        Self::PostOnly {
209            side,
210            price_in_ticks: Ticks::new(price_in_ticks),
211            num_base_lots: BaseLots::new(num_base_lots),
212            client_order_id,
213            reject_post_only: true,
214            use_only_deposited_funds: false,
215            last_valid_slot: None,
216            last_valid_unix_timestamp_in_seconds: None,
217            fail_silently_on_insufficient_funds: false,
218        }
219    }
220
221    pub fn new_adjustable_post_only_default_with_client_order_id(
222        side: Side,
223        price_in_ticks: u64,
224        num_base_lots: u64,
225        client_order_id: u128,
226    ) -> Self {
227        Self::PostOnly {
228            side,
229            price_in_ticks: Ticks::new(price_in_ticks),
230            num_base_lots: BaseLots::new(num_base_lots),
231            client_order_id,
232            reject_post_only: false,
233            use_only_deposited_funds: false,
234            last_valid_slot: None,
235            last_valid_unix_timestamp_in_seconds: None,
236            fail_silently_on_insufficient_funds: false,
237        }
238    }
239
240    pub fn new_post_only(
241        side: Side,
242        price_in_ticks: u64,
243        num_base_lots: u64,
244        client_order_id: u128,
245        reject_post_only: bool,
246        use_only_deposited_funds: bool,
247    ) -> Self {
248        Self::PostOnly {
249            side,
250            price_in_ticks: Ticks::new(price_in_ticks),
251            num_base_lots: BaseLots::new(num_base_lots),
252            client_order_id,
253            reject_post_only,
254            use_only_deposited_funds,
255            last_valid_slot: None,
256            last_valid_unix_timestamp_in_seconds: None,
257            fail_silently_on_insufficient_funds: false,
258        }
259    }
260
261    pub fn new_limit_order_default(side: Side, price_in_ticks: u64, num_base_lots: u64) -> Self {
262        Self::new_limit_order(
263            side,
264            price_in_ticks,
265            num_base_lots,
266            SelfTradeBehavior::CancelProvide,
267            None,
268            0,
269            false,
270        )
271    }
272
273    pub fn new_limit_order_default_with_client_order_id(
274        side: Side,
275        price_in_ticks: u64,
276        num_base_lots: u64,
277        client_order_id: u128,
278    ) -> Self {
279        Self::new_limit_order(
280            side,
281            price_in_ticks,
282            num_base_lots,
283            SelfTradeBehavior::CancelProvide,
284            None,
285            client_order_id,
286            false,
287        )
288    }
289
290    pub fn new_limit_order(
291        side: Side,
292        price_in_ticks: u64,
293        num_base_lots: u64,
294        self_trade_behavior: SelfTradeBehavior,
295        match_limit: Option<u64>,
296        client_order_id: u128,
297        use_only_deposited_funds: bool,
298    ) -> Self {
299        Self::Limit {
300            side,
301            price_in_ticks: Ticks::new(price_in_ticks),
302            num_base_lots: BaseLots::new(num_base_lots),
303            self_trade_behavior,
304            match_limit,
305            client_order_id,
306            use_only_deposited_funds,
307            last_valid_slot: None,
308            last_valid_unix_timestamp_in_seconds: None,
309            fail_silently_on_insufficient_funds: false,
310        }
311    }
312
313    pub fn new_fok_sell_with_limit_price(
314        target_price_in_ticks: u64,
315        base_lot_budget: u64,
316        self_trade_behavior: SelfTradeBehavior,
317        match_limit: Option<u64>,
318        client_order_id: u128,
319        use_only_deposited_funds: bool,
320    ) -> Self {
321        Self::new_ioc(
322            Side::Ask,
323            Some(target_price_in_ticks),
324            base_lot_budget,
325            0,
326            base_lot_budget,
327            0,
328            self_trade_behavior,
329            match_limit,
330            client_order_id,
331            use_only_deposited_funds,
332            None,
333            None,
334        )
335    }
336
337    pub fn new_fok_buy_with_limit_price(
338        target_price_in_ticks: u64,
339        base_lot_budget: u64,
340        self_trade_behavior: SelfTradeBehavior,
341        match_limit: Option<u64>,
342        client_order_id: u128,
343        use_only_deposited_funds: bool,
344    ) -> Self {
345        Self::new_ioc(
346            Side::Bid,
347            Some(target_price_in_ticks),
348            base_lot_budget,
349            0,
350            base_lot_budget,
351            0,
352            self_trade_behavior,
353            match_limit,
354            client_order_id,
355            use_only_deposited_funds,
356            None,
357            None,
358        )
359    }
360
361    pub fn new_ioc_sell_with_limit_price(
362        price_in_ticks: u64,
363        num_base_lots: u64,
364        self_trade_behavior: SelfTradeBehavior,
365        match_limit: Option<u64>,
366        client_order_id: u128,
367        use_only_deposited_funds: bool,
368    ) -> Self {
369        Self::new_ioc(
370            Side::Ask,
371            Some(price_in_ticks),
372            num_base_lots,
373            0,
374            0,
375            0,
376            self_trade_behavior,
377            match_limit,
378            client_order_id,
379            use_only_deposited_funds,
380            None,
381            None,
382        )
383    }
384
385    pub fn new_ioc_buy_with_limit_price(
386        price_in_ticks: u64,
387        num_quote_lots: u64,
388        self_trade_behavior: SelfTradeBehavior,
389        match_limit: Option<u64>,
390        client_order_id: u128,
391        use_only_deposited_funds: bool,
392    ) -> Self {
393        Self::new_ioc(
394            Side::Bid,
395            Some(price_in_ticks),
396            0,
397            num_quote_lots,
398            0,
399            0,
400            self_trade_behavior,
401            match_limit,
402            client_order_id,
403            use_only_deposited_funds,
404            None,
405            None,
406        )
407    }
408
409    pub fn new_ioc_by_lots(
410        side: Side,
411        price_in_ticks: u64,
412        base_lot_budget: u64,
413        self_trade_behavior: SelfTradeBehavior,
414        match_limit: Option<u64>,
415        client_order_id: u128,
416        use_only_deposited_funds: bool,
417    ) -> Self {
418        Self::new_ioc(
419            side,
420            Some(price_in_ticks),
421            base_lot_budget,
422            0,
423            0,
424            0,
425            self_trade_behavior,
426            match_limit,
427            client_order_id,
428            use_only_deposited_funds,
429            None,
430            None,
431        )
432    }
433
434    pub fn new_ioc_buy_with_slippage(quote_lots_in: u64, min_base_lots_out: u64) -> Self {
435        Self::new_ioc(
436            Side::Bid,
437            None,
438            0,
439            quote_lots_in,
440            min_base_lots_out,
441            0,
442            SelfTradeBehavior::CancelProvide,
443            None,
444            0,
445            false,
446            None,
447            None,
448        )
449    }
450
451    pub fn new_ioc_sell_with_slippage(base_lots_in: u64, min_quote_lots_out: u64) -> Self {
452        Self::new_ioc(
453            Side::Ask,
454            None,
455            base_lots_in,
456            0,
457            0,
458            min_quote_lots_out,
459            SelfTradeBehavior::CancelProvide,
460            None,
461            0,
462            false,
463            None,
464            None,
465        )
466    }
467
468    #[allow(clippy::too_many_arguments)]
469    pub fn new_ioc(
470        side: Side,
471        price_in_ticks: Option<u64>,
472        num_base_lots: u64,
473        num_quote_lots: u64,
474        min_base_lots_to_fill: u64,
475        min_quote_lots_to_fill: u64,
476        self_trade_behavior: SelfTradeBehavior,
477        match_limit: Option<u64>,
478        client_order_id: u128,
479        use_only_deposited_funds: bool,
480        last_valid_slot: Option<u64>,
481        last_valid_unix_timestamp_in_seconds: Option<u64>,
482    ) -> Self {
483        Self::ImmediateOrCancel {
484            side,
485            price_in_ticks: price_in_ticks.map(Ticks::new),
486            num_base_lots: BaseLots::new(num_base_lots),
487            num_quote_lots: QuoteLots::new(num_quote_lots),
488            min_base_lots_to_fill: BaseLots::new(min_base_lots_to_fill),
489            min_quote_lots_to_fill: QuoteLots::new(min_quote_lots_to_fill),
490            self_trade_behavior,
491            match_limit,
492            client_order_id,
493            use_only_deposited_funds,
494            last_valid_slot,
495            last_valid_unix_timestamp_in_seconds,
496        }
497    }
498}
499
500impl OrderPacket {
501    pub fn side(&self) -> Side {
502        match self {
503            Self::PostOnly { side, .. } => *side,
504            Self::Limit { side, .. } => *side,
505            Self::ImmediateOrCancel { side, .. } => *side,
506        }
507    }
508
509    pub fn fail_silently_on_insufficient_funds(&self) -> bool {
510        match self {
511            Self::PostOnly {
512                fail_silently_on_insufficient_funds,
513                ..
514            } => *fail_silently_on_insufficient_funds,
515            Self::Limit {
516                fail_silently_on_insufficient_funds,
517                ..
518            } => *fail_silently_on_insufficient_funds,
519            Self::ImmediateOrCancel { .. } => false,
520        }
521    }
522
523    pub fn client_order_id(&self) -> u128 {
524        match self {
525            Self::PostOnly {
526                client_order_id, ..
527            } => *client_order_id,
528            Self::Limit {
529                client_order_id, ..
530            } => *client_order_id,
531            Self::ImmediateOrCancel {
532                client_order_id, ..
533            } => *client_order_id,
534        }
535    }
536
537    pub fn num_base_lots(&self) -> BaseLots {
538        match self {
539            Self::PostOnly { num_base_lots, .. } => *num_base_lots,
540            Self::Limit { num_base_lots, .. } => *num_base_lots,
541            Self::ImmediateOrCancel { num_base_lots, .. } => *num_base_lots,
542        }
543    }
544
545    pub fn num_quote_lots(&self) -> QuoteLots {
546        match self {
547            Self::PostOnly { .. } => QuoteLots::ZERO,
548            Self::Limit { .. } => QuoteLots::ZERO,
549            Self::ImmediateOrCancel { num_quote_lots, .. } => *num_quote_lots,
550        }
551    }
552
553    pub fn base_lot_budget(&self) -> BaseLots {
554        let base_lots = self.num_base_lots();
555        if base_lots == BaseLots::ZERO {
556            BaseLots::MAX
557        } else {
558            base_lots
559        }
560    }
561
562    pub fn quote_lot_budget(&self) -> Option<QuoteLots> {
563        let quote_lots = self.num_quote_lots();
564        if quote_lots == QuoteLots::ZERO {
565            None
566        } else {
567            Some(quote_lots)
568        }
569    }
570
571    pub fn match_limit(&self) -> u64 {
572        match self {
573            Self::PostOnly { .. } => u64::MAX,
574            Self::Limit { match_limit, .. } => match_limit.unwrap_or(u64::MAX),
575            Self::ImmediateOrCancel { match_limit, .. } => match_limit.unwrap_or(u64::MAX),
576        }
577    }
578
579    pub fn self_trade_behavior(&self) -> SelfTradeBehavior {
580        match self {
581            Self::PostOnly { .. } => panic!("PostOnly orders do not have a self trade behavior"),
582            Self::Limit {
583                self_trade_behavior,
584                ..
585            } => *self_trade_behavior,
586            Self::ImmediateOrCancel {
587                self_trade_behavior,
588                ..
589            } => *self_trade_behavior,
590        }
591    }
592
593    pub fn get_price_in_ticks(&self) -> Ticks {
594        match self {
595            Self::PostOnly { price_in_ticks, .. } => *price_in_ticks,
596            Self::Limit { price_in_ticks, .. } => *price_in_ticks,
597            Self::ImmediateOrCancel { price_in_ticks, .. } => {
598                price_in_ticks.unwrap_or(match self.side() {
599                    Side::Bid => Ticks::MAX,
600                    Side::Ask => Ticks::MIN,
601                })
602            }
603        }
604    }
605
606    pub fn set_price_in_ticks(&mut self, price_in_ticks: Ticks) {
607        match self {
608            Self::PostOnly {
609                price_in_ticks: old_price_in_ticks,
610                ..
611            } => *old_price_in_ticks = price_in_ticks,
612            Self::Limit {
613                price_in_ticks: old_price_in_ticks,
614                ..
615            } => *old_price_in_ticks = price_in_ticks,
616            Self::ImmediateOrCancel {
617                price_in_ticks: old_price_in_ticks,
618                ..
619            } => *old_price_in_ticks = Some(price_in_ticks),
620        }
621    }
622
623    pub fn get_last_valid_slot(&self) -> Option<u64> {
624        match self {
625            Self::PostOnly {
626                last_valid_slot, ..
627            } => *last_valid_slot,
628            Self::Limit {
629                last_valid_slot, ..
630            } => *last_valid_slot,
631            Self::ImmediateOrCancel {
632                last_valid_slot, ..
633            } => *last_valid_slot,
634        }
635    }
636
637    pub fn get_last_valid_unix_timestamp_in_seconds(&self) -> Option<u64> {
638        match self {
639            Self::PostOnly {
640                last_valid_unix_timestamp_in_seconds,
641                ..
642            } => *last_valid_unix_timestamp_in_seconds,
643            Self::Limit {
644                last_valid_unix_timestamp_in_seconds,
645                ..
646            } => *last_valid_unix_timestamp_in_seconds,
647            Self::ImmediateOrCancel {
648                last_valid_unix_timestamp_in_seconds,
649                ..
650            } => *last_valid_unix_timestamp_in_seconds,
651        }
652    }
653
654    pub fn is_expired(&self, current_slot: u64, current_unix_timestamp_in_seconds: u64) -> bool {
655        if let Some(last_valid_slot) = self.get_last_valid_slot() {
656            if current_slot > last_valid_slot {
657                return true;
658            }
659        }
660        if let Some(last_valid_unix_timestamp_in_seconds) =
661            self.get_last_valid_unix_timestamp_in_seconds()
662        {
663            if current_unix_timestamp_in_seconds > last_valid_unix_timestamp_in_seconds {
664                return true;
665            }
666        }
667        false
668    }
669}
670
671pub fn decode_order_packet(bytes: &[u8]) -> Option<OrderPacket> {
672    // First, attempt to decode the order packet with the raw input data.
673    match OrderPacket::try_from_slice(bytes) {
674        Ok(order_packet) => Some(order_packet),
675        // If the initial deserialization fails, the strategy is to decode the order packet with the
676        // starting assumption that none of the optional fields are present.
677        //
678        // The original input data is padded with all of the optional fields at the end.
679        // Each field is then removed one at a time in order until the order packet successfully decodes.
680        // The requirement here is that the included optional fields must be contiguous in memory
681        // (i.e. it is undefined behavior to include non-adjacent optional fields while excluding the ones in between)
682        Err(_) => {
683            // The optional fields at the end of the order packet are not required in the raw input data.
684            let additional_fields = &[
685                0_u8, /* last_valid_slot */
686                0_u8, /* last_valid_unix_timestamp_in_seconds */
687                0_u8, /* fail_silently_on_insufficient_funds */
688            ];
689            let mut padded_bytes = [bytes, additional_fields].concat();
690            for _ in 0..additional_fields.len() {
691                if let Ok(order_packet) = OrderPacket::try_from_slice(&padded_bytes) {
692                    return Some(order_packet);
693                }
694                padded_bytes.pop();
695            }
696            None
697        }
698    }
699}
700
701#[test]
702fn test_decode_order_packet() {
703    use rand::Rng;
704    use rand::{rngs::StdRng, SeedableRng};
705    let mut rng = StdRng::seed_from_u64(42);
706
707    let num_iters = 100;
708
709    #[derive(Deserialize, Serialize, Copy, Clone, PartialEq, Eq, Debug)]
710    pub enum DeprecatedOrderPacket {
711        PostOnly {
712            side: Side,
713            price_in_ticks: Ticks,
714            num_base_lots: BaseLots,
715            client_order_id: u128,
716            reject_post_only: bool,
717            use_only_deposited_funds: bool,
718        },
719        Limit {
720            side: Side,
721            price_in_ticks: Ticks,
722            num_base_lots: BaseLots,
723            self_trade_behavior: SelfTradeBehavior,
724            match_limit: Option<u64>,
725            client_order_id: u128,
726            use_only_deposited_funds: bool,
727        },
728
729        ImmediateOrCancel {
730            side: Side,
731            price_in_ticks: Option<Ticks>,
732            num_base_lots: BaseLots,
733            num_quote_lots: QuoteLots,
734            min_base_lots_to_fill: BaseLots,
735            min_quote_lots_to_fill: QuoteLots,
736            self_trade_behavior: SelfTradeBehavior,
737            match_limit: Option<u64>,
738            client_order_id: u128,
739            use_only_deposited_funds: bool,
740        },
741    }
742    for _ in 0..num_iters {
743        let side = if rng.gen::<f64>() > 0.5 {
744            Side::Bid
745        } else {
746            Side::Ask
747        };
748
749        let price_in_ticks = Ticks::new(rng.gen::<u64>());
750        let num_base_lots = BaseLots::new(rng.gen::<u64>());
751        let client_order_id = rng.gen::<u128>();
752        let reject_post_only = rng.gen::<bool>();
753        let use_only_deposited_funds = rng.gen::<bool>();
754        let packet = OrderPacket::PostOnly {
755            side,
756            price_in_ticks,
757            num_base_lots,
758            client_order_id,
759            reject_post_only,
760            use_only_deposited_funds,
761            last_valid_slot: None,
762            last_valid_unix_timestamp_in_seconds: None,
763            fail_silently_on_insufficient_funds: false,
764        };
765        let deprecated_packet = DeprecatedOrderPacket::PostOnly {
766            side,
767            price_in_ticks,
768            num_base_lots,
769            client_order_id,
770            reject_post_only,
771            use_only_deposited_funds,
772        };
773        let bytes = packet.try_to_vec().unwrap();
774        let decoded_normal = decode_order_packet(&bytes).unwrap();
775        let decoded_inferred_1 = decode_order_packet(&bytes[..bytes.len() - 1]).unwrap();
776        let decoded_inferred_2 = decode_order_packet(&bytes[..bytes.len() - 3]).unwrap();
777        let deprecated_bytes = deprecated_packet.try_to_vec().unwrap();
778        let decoded_deprecated = decode_order_packet(&deprecated_bytes).unwrap();
779        assert_eq!(packet, decoded_normal);
780        assert_eq!(decoded_normal, decoded_inferred_1);
781        assert_eq!(decoded_inferred_1, decoded_deprecated);
782        assert_eq!(decoded_inferred_1, decoded_inferred_2);
783    }
784
785    for _ in 0..num_iters {
786        let side = if rng.gen::<f64>() > 0.5 {
787            Side::Bid
788        } else {
789            Side::Ask
790        };
791
792        let price_in_ticks = Ticks::new(rng.gen::<u64>());
793        let num_base_lots = BaseLots::new(rng.gen::<u64>());
794        let client_order_id = rng.gen::<u128>();
795        let self_trade_behavior = match rng.gen_range(0, 3) {
796            0 => SelfTradeBehavior::DecrementTake,
797            1 => SelfTradeBehavior::CancelProvide,
798            2 => SelfTradeBehavior::Abort,
799            _ => unreachable!(),
800        };
801        let match_limit = if rng.gen::<f64>() > 0.5 {
802            Some(rng.gen::<u64>())
803        } else {
804            None
805        };
806        let use_only_deposited_funds = rng.gen::<bool>();
807        let packet = OrderPacket::Limit {
808            side,
809            price_in_ticks,
810            num_base_lots,
811            client_order_id,
812            self_trade_behavior,
813            match_limit,
814            use_only_deposited_funds,
815            last_valid_slot: None,
816            last_valid_unix_timestamp_in_seconds: None,
817            fail_silently_on_insufficient_funds: false,
818        };
819        let deprecated_packet = DeprecatedOrderPacket::Limit {
820            side,
821            price_in_ticks,
822            num_base_lots,
823            client_order_id,
824            self_trade_behavior,
825            match_limit,
826            use_only_deposited_funds,
827        };
828        let bytes = packet.try_to_vec().unwrap();
829        let decoded_normal = decode_order_packet(&bytes).unwrap();
830        let decoded_inferred_1 = decode_order_packet(&bytes[..bytes.len() - 1]).unwrap();
831        let decoded_inferred_2 = decode_order_packet(&bytes[..bytes.len() - 3]).unwrap();
832        let deprecated_bytes = deprecated_packet.try_to_vec().unwrap();
833        let decoded_deprecated = decode_order_packet(&deprecated_bytes).unwrap();
834        assert_eq!(packet, decoded_normal);
835        assert_eq!(decoded_normal, decoded_inferred_1);
836        assert_eq!(decoded_inferred_1, decoded_deprecated);
837        assert_eq!(decoded_inferred_1, decoded_inferred_2);
838    }
839
840    for _ in 0..num_iters {
841        let side = if rng.gen::<f64>() > 0.5 {
842            Side::Bid
843        } else {
844            Side::Ask
845        };
846
847        let price_in_ticks = if rng.gen::<f64>() > 0.5 {
848            Some(Ticks::new(rng.gen::<u64>()))
849        } else {
850            None
851        };
852        let num_base_lots = BaseLots::new(rng.gen::<u64>());
853        let min_base_lots_to_fill = BaseLots::new(rng.gen::<u64>());
854        let num_quote_lots = QuoteLots::new(rng.gen::<u64>());
855        let min_quote_lots_to_fill = QuoteLots::new(rng.gen::<u64>());
856        let client_order_id = rng.gen::<u128>();
857        let self_trade_behavior = match rng.gen_range(0, 3) {
858            0 => SelfTradeBehavior::DecrementTake,
859            1 => SelfTradeBehavior::CancelProvide,
860            2 => SelfTradeBehavior::Abort,
861            _ => unreachable!(),
862        };
863        let match_limit = if rng.gen::<f64>() > 0.5 {
864            Some(rng.gen::<u64>())
865        } else {
866            None
867        };
868        let use_only_deposited_funds = rng.gen::<bool>();
869        let packet = OrderPacket::ImmediateOrCancel {
870            side,
871            price_in_ticks,
872            num_base_lots,
873            num_quote_lots,
874            min_base_lots_to_fill,
875            min_quote_lots_to_fill,
876            client_order_id,
877            self_trade_behavior,
878            match_limit,
879            use_only_deposited_funds,
880            last_valid_slot: None,
881            last_valid_unix_timestamp_in_seconds: None,
882        };
883        let deprecated_packet = DeprecatedOrderPacket::ImmediateOrCancel {
884            side,
885            price_in_ticks,
886            num_base_lots,
887            num_quote_lots,
888            min_base_lots_to_fill,
889            min_quote_lots_to_fill,
890            client_order_id,
891            self_trade_behavior,
892            match_limit,
893            use_only_deposited_funds,
894        };
895        let bytes = packet.try_to_vec().unwrap();
896        let decoded_normal = decode_order_packet(&bytes).unwrap();
897        let decoded_inferred_1 = decode_order_packet(&bytes[..bytes.len() - 2]).unwrap();
898        let decoded_inferred_2 = decode_order_packet(&bytes[..bytes.len() - 1]).unwrap();
899        let deprecated_bytes = deprecated_packet.try_to_vec().unwrap();
900        let decoded_deprecated = decode_order_packet(&deprecated_bytes).unwrap();
901        assert_eq!(packet, decoded_normal);
902        assert_eq!(decoded_normal, decoded_inferred_1);
903        assert_eq!(decoded_inferred_1, decoded_deprecated);
904        assert_eq!(decoded_inferred_1, decoded_inferred_2);
905    }
906}