serum_dex/
instruction.rs

1#![cfg_attr(not(feature = "program"), allow(unused))]
2use crate::error::DexError;
3use crate::matching::{OrderType, Side};
4use bytemuck::cast;
5use serde::{Deserialize, Serialize};
6use solana_program::{
7    instruction::{AccountMeta, Instruction},
8    pubkey::Pubkey,
9    sysvar::rent,
10};
11use std::convert::TryInto;
12
13use arrayref::{array_ref, array_refs};
14use num_enum::{IntoPrimitive, TryFromPrimitive};
15use std::num::NonZeroU64;
16
17#[cfg(test)]
18use proptest::prelude::*;
19#[cfg(test)]
20use proptest_derive::Arbitrary;
21
22pub mod srm_token {
23    use solana_program::declare_id;
24    declare_id!("SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt");
25}
26
27pub mod msrm_token {
28    use solana_program::declare_id;
29    declare_id!("MSRMcoVyrFxnSgo5uXwone5SKcGhT1KEJMFEkMEWf9L");
30}
31
32pub mod disable_authority {
33    use solana_program::declare_id;
34    declare_id!("5ZVJgwWxMsqXxRMYHXqMwH2hd4myX5Ef4Au2iUsuNQ7V");
35}
36
37pub mod fee_sweeper {
38    use solana_program::declare_id;
39    declare_id!("DeqYsmBd9BnrbgUwQjVH4sQWK71dEgE6eoZFw3Rp4ftE");
40}
41
42#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)]
43#[cfg_attr(test, derive(Arbitrary))]
44#[cfg_attr(test, proptest(no_params))]
45#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
46pub struct InitializeMarketInstruction {
47    // In the matching engine, all prices and balances are integers.
48    // This only works if the smallest representable quantity of the coin
49    // is at least a few orders of magnitude larger than the smallest representable
50    // quantity of the price currency. The internal representation also relies on
51    // on the assumption that every order will have a (quantity x price) value that
52    // fits into a u64.
53    //
54    // If these assumptions are problematic, rejigger the lot sizes.
55    pub coin_lot_size: u64,
56    pub pc_lot_size: u64,
57    pub fee_rate_bps: u16,
58    pub vault_signer_nonce: u64,
59    pub pc_dust_threshold: u64,
60}
61
62#[derive(
63    PartialEq, Eq, Copy, Clone, Debug, TryFromPrimitive, IntoPrimitive, Serialize, Deserialize,
64)]
65#[cfg_attr(test, derive(Arbitrary))]
66#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
67#[repr(u8)]
68pub enum SelfTradeBehavior {
69    DecrementTake = 0,
70    CancelProvide = 1,
71    AbortTransaction = 2,
72}
73
74#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)]
75#[cfg_attr(test, derive(Arbitrary))]
76pub struct SendTakeInstruction {
77    pub side: Side,
78
79    #[cfg_attr(
80        test,
81        proptest(strategy = "(1u64..=std::u64::MAX).prop_map(|x| NonZeroU64::new(x).unwrap())")
82    )]
83    pub limit_price: NonZeroU64,
84
85    #[cfg_attr(
86        test,
87        proptest(strategy = "(1u64..=std::u64::MAX).prop_map(|x| NonZeroU64::new(x).unwrap())")
88    )]
89    pub max_coin_qty: NonZeroU64,
90    #[cfg_attr(
91        test,
92        proptest(strategy = "(1u64..=std::u64::MAX).prop_map(|x| NonZeroU64::new(x).unwrap())")
93    )]
94    pub max_native_pc_qty_including_fees: NonZeroU64,
95
96    pub min_coin_qty: u64,
97    pub min_native_pc_qty: u64,
98
99    pub limit: u16,
100}
101
102#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)]
103#[cfg_attr(test, derive(Arbitrary))]
104pub struct NewOrderInstructionV3 {
105    pub side: Side,
106
107    #[cfg_attr(
108        test,
109        proptest(strategy = "(1u64..=std::u64::MAX).prop_map(|x| NonZeroU64::new(x).unwrap())")
110    )]
111    pub limit_price: NonZeroU64,
112
113    #[cfg_attr(
114        test,
115        proptest(strategy = "(1u64..=std::u64::MAX).prop_map(|x| NonZeroU64::new(x).unwrap())")
116    )]
117    pub max_coin_qty: NonZeroU64,
118    #[cfg_attr(
119        test,
120        proptest(strategy = "(1u64..=std::u64::MAX).prop_map(|x| NonZeroU64::new(x).unwrap())")
121    )]
122    pub max_native_pc_qty_including_fees: NonZeroU64,
123
124    pub self_trade_behavior: SelfTradeBehavior,
125
126    pub order_type: OrderType,
127    pub client_order_id: u64,
128    pub limit: u16,
129}
130
131#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)]
132#[cfg_attr(test, derive(Arbitrary))]
133pub struct NewOrderInstructionV2 {
134    pub side: Side,
135    #[cfg_attr(
136        test,
137        proptest(strategy = "(1u64..=std::u64::MAX).prop_map(|x| NonZeroU64::new(x).unwrap())")
138    )]
139    pub limit_price: NonZeroU64,
140    #[cfg_attr(
141        test,
142        proptest(strategy = "(1u64..=std::u64::MAX).prop_map(|x| NonZeroU64::new(x).unwrap())")
143    )]
144    pub max_qty: NonZeroU64,
145    pub order_type: OrderType,
146    pub client_id: u64,
147    pub self_trade_behavior: SelfTradeBehavior,
148}
149
150#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)]
151#[cfg_attr(test, derive(Arbitrary))]
152pub struct NewOrderInstructionV1 {
153    pub side: Side,
154    #[cfg_attr(
155        test,
156        proptest(strategy = "(1u64..=std::u64::MAX).prop_map(|x| NonZeroU64::new(x).unwrap())")
157    )]
158    pub limit_price: NonZeroU64,
159    #[cfg_attr(
160        test,
161        proptest(strategy = "(1u64..=std::u64::MAX).prop_map(|x| NonZeroU64::new(x).unwrap())")
162    )]
163    pub max_qty: NonZeroU64,
164    pub order_type: OrderType,
165    pub client_id: u64,
166}
167
168impl NewOrderInstructionV1 {
169    pub fn add_self_trade_behavior(
170        self,
171        self_trade_behavior: SelfTradeBehavior,
172    ) -> NewOrderInstructionV2 {
173        let NewOrderInstructionV1 {
174            side,
175            limit_price,
176            max_qty,
177            order_type,
178            client_id,
179        } = self;
180        NewOrderInstructionV2 {
181            side,
182            limit_price,
183            max_qty,
184            order_type,
185            client_id,
186            self_trade_behavior,
187        }
188    }
189}
190
191impl SendTakeInstruction {
192    fn unpack(data: &[u8; 46]) -> Option<Self> {
193        let (
194            &side_arr,
195            &price_arr,
196            &max_coin_qty_arr,
197            &max_native_pc_qty_arr,
198            &min_coin_qty_arr,
199            &min_native_pc_qty_arr,
200            &limit_arr,
201        ) = array_refs![data, 4, 8, 8, 8, 8, 8, 2];
202
203        let side = Side::try_from_primitive(u32::from_le_bytes(side_arr).try_into().ok()?).ok()?;
204        let limit_price = NonZeroU64::new(u64::from_le_bytes(price_arr))?;
205        let max_coin_qty = NonZeroU64::new(u64::from_le_bytes(max_coin_qty_arr))?;
206        let max_native_pc_qty_including_fees =
207            NonZeroU64::new(u64::from_le_bytes(max_native_pc_qty_arr))?;
208        let min_coin_qty = u64::from_le_bytes(min_coin_qty_arr);
209        let min_native_pc_qty = u64::from_le_bytes(min_native_pc_qty_arr);
210        let limit = u16::from_le_bytes(limit_arr);
211
212        Some(SendTakeInstruction {
213            side,
214            limit_price,
215            max_coin_qty,
216            max_native_pc_qty_including_fees,
217            min_coin_qty,
218            min_native_pc_qty,
219            limit,
220        })
221    }
222}
223
224impl NewOrderInstructionV3 {
225    fn unpack(data: &[u8; 46]) -> Option<Self> {
226        let (
227            &side_arr,
228            &price_arr,
229            &max_coin_qty_arr,
230            &max_native_pc_qty_arr,
231            &self_trade_behavior_arr,
232            &otype_arr,
233            &client_order_id_bytes,
234            &limit_arr,
235        ) = array_refs![data, 4, 8, 8, 8, 4, 4, 8, 2];
236
237        let side = Side::try_from_primitive(u32::from_le_bytes(side_arr).try_into().ok()?).ok()?;
238        let limit_price = NonZeroU64::new(u64::from_le_bytes(price_arr))?;
239        let max_coin_qty = NonZeroU64::new(u64::from_le_bytes(max_coin_qty_arr))?;
240        let max_native_pc_qty_including_fees =
241            NonZeroU64::new(u64::from_le_bytes(max_native_pc_qty_arr))?;
242        let self_trade_behavior = SelfTradeBehavior::try_from_primitive(
243            u32::from_le_bytes(self_trade_behavior_arr)
244                .try_into()
245                .ok()?,
246        )
247        .ok()?;
248        let order_type =
249            OrderType::try_from_primitive(u32::from_le_bytes(otype_arr).try_into().ok()?).ok()?;
250        let client_order_id = u64::from_le_bytes(client_order_id_bytes);
251        let limit = u16::from_le_bytes(limit_arr);
252
253        Some(NewOrderInstructionV3 {
254            side,
255            limit_price,
256            max_coin_qty,
257            max_native_pc_qty_including_fees,
258            self_trade_behavior,
259            order_type,
260            client_order_id,
261            limit,
262        })
263    }
264}
265
266impl NewOrderInstructionV1 {
267    fn unpack(data: &[u8; 32]) -> Option<Self> {
268        let (&side_arr, &price_arr, &max_qty_arr, &otype_arr, &client_id_bytes) =
269            array_refs![data, 4, 8, 8, 4, 8];
270        let client_id = u64::from_le_bytes(client_id_bytes);
271        let side = match u32::from_le_bytes(side_arr) {
272            0 => Side::Bid,
273            1 => Side::Ask,
274            _ => return None,
275        };
276        let limit_price = NonZeroU64::new(u64::from_le_bytes(price_arr))?;
277        let max_qty = NonZeroU64::new(u64::from_le_bytes(max_qty_arr))?;
278        let order_type = match u32::from_le_bytes(otype_arr) {
279            0 => OrderType::Limit,
280            1 => OrderType::ImmediateOrCancel,
281            2 => OrderType::PostOnly,
282            _ => return None,
283        };
284        Some(NewOrderInstructionV1 {
285            side,
286            limit_price,
287            max_qty,
288            order_type,
289            client_id,
290        })
291    }
292}
293#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)]
294#[cfg_attr(test, derive(Arbitrary))]
295#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
296pub struct CancelOrderInstructionV2 {
297    pub side: Side,
298    pub order_id: u128,
299}
300
301#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)]
302#[cfg_attr(test, derive(Arbitrary))]
303#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
304pub struct CancelOrderInstruction {
305    pub side: Side,
306    pub order_id: u128,
307    pub owner: [u64; 4], // Unused
308    pub owner_slot: u8,
309}
310
311impl CancelOrderInstructionV2 {
312    fn unpack(data: &[u8; 20]) -> Option<Self> {
313        let (&side_arr, &oid_arr) = array_refs![data, 4, 16];
314        let side = Side::try_from_primitive(u32::from_le_bytes(side_arr).try_into().ok()?).ok()?;
315        let order_id = u128::from_le_bytes(oid_arr);
316        Some(CancelOrderInstructionV2 { side, order_id })
317    }
318}
319
320#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)]
321#[cfg_attr(test, derive(Arbitrary))]
322#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
323pub enum MarketInstruction {
324    /// 0. `[writable]` the market to initialize
325    /// 1. `[writable]` zeroed out request queue
326    /// 2. `[writable]` zeroed out event queue
327    /// 3. `[writable]` zeroed out bids
328    /// 4. `[writable]` zeroed out asks
329    /// 5. `[writable]` spl-token account for the coin currency
330    /// 6. `[writable]` spl-token account for the price currency
331    /// 7. `[]` coin currency Mint
332    /// 8. `[]` price currency Mint
333    /// 9. `[]` the rent sysvar
334    /// 10. `[]` open orders market authority (optional)
335    /// 11. `[]` prune authority (optional, requires open orders market authority)
336    /// 12. `[]` crank authority (optional, requires prune authority)
337    InitializeMarket(InitializeMarketInstruction),
338    /// 0. `[writable]` the market
339    /// 1. `[writable]` the OpenOrders account to use
340    /// 2. `[writable]` the request queue
341    /// 3. `[writable]` the (coin or price currency) account paying for the order
342    /// 4. `[signer]` owner of the OpenOrders account
343    /// 5. `[writable]` coin vault
344    /// 6. `[writable]` pc vault
345    /// 7. `[]` spl token program
346    /// 8. `[]` the rent sysvar
347    /// 9. `[]` (optional) the (M)SRM account used for fee discounts
348    NewOrder(NewOrderInstructionV1),
349    /// 0. `[writable]` market
350    /// 1. `[writable]` req_q
351    /// 2. `[writable]` event_q
352    /// 3. `[writable]` bids
353    /// 4. `[writable]` asks
354    MatchOrders(u16),
355    /// ... `[writable]` OpenOrders
356    /// accounts.len() - 4 `[writable]` market
357    /// accounts.len() - 3 `[writable]` event queue
358    /// accounts.len() - 2 `[]`
359    /// accounts.len() - 1 `[]`
360    ConsumeEvents(u16),
361    /// 0. `[]` market
362    /// 1. `[writable]` OpenOrders
363    /// 2. `[writable]` the request queue
364    /// 3. `[signer]` the OpenOrders owner
365    CancelOrder(CancelOrderInstruction),
366    /// 0. `[writable]` market
367    /// 1. `[writable]` OpenOrders
368    /// 2. `[signer]` the OpenOrders owner
369    /// 3. `[writable]` coin vault
370    /// 4. `[writable]` pc vault
371    /// 5. `[writable]` coin wallet
372    /// 6. `[writable]` pc wallet
373    /// 7. `[]` vault signer
374    /// 8. `[]` spl token program
375    /// 9. `[writable]` (optional) referrer pc wallet
376    SettleFunds,
377    /// 0. `[]` market
378    /// 1. `[writable]` OpenOrders
379    /// 2. `[writable]` the request queue
380    /// 3. `[signer]` the OpenOrders owner
381    CancelOrderByClientId(u64),
382    /// 0. `[writable]` market
383    /// 1. `[signer]` disable authority
384    DisableMarket,
385    /// 0. `[writable]` market
386    /// 1. `[writable]` pc vault
387    /// 2. `[signer]` fee sweeping authority
388    /// 3. `[writable]` fee receivable account
389    /// 4. `[]` vault signer
390    /// 5. `[]` spl token program
391    SweepFees,
392    /// 0. `[writable]` the market
393    /// 1. `[writable]` the OpenOrders account to use
394    /// 2. `[writable]` the request queue
395    /// 3. `[writable]` the (coin or price currency) account paying for the order
396    /// 4. `[signer]` owner of the OpenOrders account
397    /// 5. `[writable]` coin vault
398    /// 6. `[writable]` pc vault
399    /// 7. `[]` spl token program
400    /// 8. `[]` the rent sysvar
401    /// 9. `[]` (optional) the (M)SRM account used for fee discounts
402    NewOrderV2(NewOrderInstructionV2),
403    /// 0. `[writable]` the market
404    /// 1. `[writable]` the OpenOrders account to use
405    /// 2. `[writable]` the request queue
406    /// 3. `[writable]` the event queue
407    /// 4. `[writable]` bids
408    /// 5. `[writable]` asks
409    /// 6. `[writable]` the (coin or price currency) account paying for the order
410    /// 7. `[signer]` owner of the OpenOrders account
411    /// 8. `[writable]` coin vault
412    /// 9. `[writable]` pc vault
413    /// 10. `[]` spl token program
414    /// 11. `[]` the rent sysvar
415    /// 12. `[]` (optional) the (M)SRM account used for fee discounts
416    NewOrderV3(NewOrderInstructionV3),
417    /// 0. `[writable]` market
418    /// 1. `[writable]` bids
419    /// 2. `[writable]` asks
420    /// 3. `[writable]` OpenOrders
421    /// 4. `[signer]` the OpenOrders owner
422    /// 5. `[writable]` event_q
423    CancelOrderV2(CancelOrderInstructionV2),
424    /// 0. `[writable]` market
425    /// 1. `[writable]` bids
426    /// 2. `[writable]` asks
427    /// 3. `[writable]` OpenOrders
428    /// 4. `[signer]` the OpenOrders owner
429    /// 5. `[writable]` event_q
430    CancelOrderByClientIdV2(u64),
431    /// 0. `[writable]` market
432    /// 1. `[writable]` the request queue
433    /// 2. `[writable]` the event queue
434    /// 3. `[writable]` bids
435    /// 4. `[writable]` asks
436    /// 5. `[writable]` the coin currency wallet account
437    /// 6. `[writable]` the price currency wallet account
438    /// 7. `[]` signer
439    /// 8. `[writable]` coin vault
440    /// 9. `[writable]` pc vault
441    /// 10. `[]` spl token program
442    /// 11. `[]` (optional) the (M)SRM account used for fee discounts
443    SendTake(SendTakeInstruction),
444    /// 0. `[writable]` OpenOrders
445    /// 1. `[signer]` the OpenOrders owner
446    /// 2. `[writable]` the destination account to send rent exemption SOL to
447    /// 3. `[]` market
448    CloseOpenOrders,
449    /// 0. `[writable]` OpenOrders
450    /// 1. `[signer]` the OpenOrders owner
451    /// 2. `[]` market
452    /// 3. `[]`
453    /// 4. `[signer]` open orders market authority (optional).
454    InitOpenOrders,
455    /// Removes all orders for a given open orders account from the orderbook.
456    ///
457    /// 0. `[writable]` market
458    /// 1. `[writable]` bids
459    /// 2. `[writable]` asks
460    /// 3. `[signer]` prune authority
461    /// 4. `[]` open orders.
462    /// 5. `[]` open orders owner.
463    /// 6. `[writable]` event queue.
464    Prune(u16),
465    /// ... `[writable]` OpenOrders
466    /// accounts.len() - 3 `[writable]` market
467    /// accounts.len() - 2 `[writable]` event queue
468    /// accounts.len() - 1 `[signer]` crank authority
469    ConsumeEventsPermissioned(u16),
470}
471
472impl MarketInstruction {
473    pub fn pack(&self) -> Vec<u8> {
474        bincode::serialize(&(0u8, self)).unwrap()
475    }
476
477    pub fn unpack(versioned_bytes: &[u8]) -> Option<Self> {
478        if versioned_bytes.len() < 5 || versioned_bytes.len() > 58 {
479            return None;
480        }
481        let (&[version], &discrim, data) = array_refs![versioned_bytes, 1, 4; ..;];
482        if version != 0 {
483            return None;
484        }
485        let discrim = u32::from_le_bytes(discrim);
486        Some(match (discrim, data.len()) {
487            (0, 34) => MarketInstruction::InitializeMarket({
488                let data_array = array_ref![data, 0, 34];
489                let fields = array_refs![data_array, 8, 8, 2, 8, 8];
490                InitializeMarketInstruction {
491                    coin_lot_size: u64::from_le_bytes(*fields.0),
492                    pc_lot_size: u64::from_le_bytes(*fields.1),
493                    fee_rate_bps: u16::from_le_bytes(*fields.2),
494                    vault_signer_nonce: u64::from_le_bytes(*fields.3),
495                    pc_dust_threshold: u64::from_le_bytes(*fields.4),
496                }
497            }),
498            (1, 32) => MarketInstruction::NewOrder({
499                let data_arr = array_ref![data, 0, 32];
500                NewOrderInstructionV1::unpack(data_arr)?
501            }),
502            (2, 2) => {
503                let limit = array_ref![data, 0, 2];
504                MarketInstruction::MatchOrders(u16::from_le_bytes(*limit))
505            }
506            (3, 2) => {
507                let limit = array_ref![data, 0, 2];
508                MarketInstruction::ConsumeEvents(u16::from_le_bytes(*limit))
509            }
510            (4, 53) => MarketInstruction::CancelOrder({
511                let data_array = array_ref![data, 0, 53];
512                let fields = array_refs![data_array, 4, 16, 32, 1];
513                let side = match u32::from_le_bytes(*fields.0) {
514                    0 => Side::Bid,
515                    1 => Side::Ask,
516                    _ => return None,
517                };
518                let order_id = u128::from_le_bytes(*fields.1);
519                let owner = cast(*fields.2);
520                let &[owner_slot] = fields.3;
521                CancelOrderInstruction {
522                    side,
523                    order_id,
524                    owner,
525                    owner_slot,
526                }
527            }),
528            (5, 0) => MarketInstruction::SettleFunds,
529            (6, 8) => {
530                let client_id = array_ref![data, 0, 8];
531                MarketInstruction::CancelOrderByClientId(u64::from_le_bytes(*client_id))
532            }
533            (7, 0) => MarketInstruction::DisableMarket,
534            (8, 0) => MarketInstruction::SweepFees,
535            (9, 36) => MarketInstruction::NewOrderV2({
536                let data_arr = array_ref![data, 0, 36];
537                let (v1_data_arr, v2_data_arr) = array_refs![data_arr, 32, 4];
538                let v1_instr = NewOrderInstructionV1::unpack(v1_data_arr)?;
539                let self_trade_behavior = SelfTradeBehavior::try_from_primitive(
540                    u32::from_le_bytes(*v2_data_arr).try_into().ok()?,
541                )
542                .ok()?;
543                v1_instr.add_self_trade_behavior(self_trade_behavior)
544            }),
545            (10, 46) => MarketInstruction::NewOrderV3({
546                let data_arr = array_ref![data, 0, 46];
547                NewOrderInstructionV3::unpack(data_arr)?
548            }),
549            (11, 20) => MarketInstruction::CancelOrderV2({
550                let data_arr = array_ref![data, 0, 20];
551                CancelOrderInstructionV2::unpack(data_arr)?
552            }),
553            (12, 8) => {
554                let client_id = array_ref![data, 0, 8];
555                MarketInstruction::CancelOrderByClientIdV2(u64::from_le_bytes(*client_id))
556            }
557            (13, 46) => MarketInstruction::SendTake({
558                let data_arr = array_ref![data, 0, 46];
559                SendTakeInstruction::unpack(data_arr)?
560            }),
561            (14, 0) => MarketInstruction::CloseOpenOrders,
562            (15, 0) => MarketInstruction::InitOpenOrders,
563            (16, 2) => {
564                let limit = array_ref![data, 0, 2];
565                MarketInstruction::Prune(u16::from_le_bytes(*limit))
566            }
567            (17, 2) => {
568                let limit = array_ref![data, 0, 2];
569                MarketInstruction::ConsumeEventsPermissioned(u16::from_le_bytes(*limit))
570            }
571            _ => return None,
572        })
573    }
574
575    #[cfg(test)]
576    #[inline]
577    pub fn unpack_serde(data: &[u8]) -> Result<Self, ()> {
578        match data.split_first() {
579            None => Err(()),
580            Some((&0u8, rest)) => bincode::deserialize(rest).map_err(|_| ()),
581            Some((_, _rest)) => Err(()),
582        }
583    }
584}
585
586pub fn initialize_market(
587    market: &Pubkey,
588    program_id: &Pubkey,
589    coin_mint_pk: &Pubkey,
590    pc_mint_pk: &Pubkey,
591    coin_vault_pk: &Pubkey,
592    pc_vault_pk: &Pubkey,
593    authority_pk: Option<&Pubkey>,
594    prune_authority_pk: Option<&Pubkey>,
595    consume_events_authority_pk: Option<&Pubkey>,
596    // srm_vault_pk: &Pubkey,
597    bids_pk: &Pubkey,
598    asks_pk: &Pubkey,
599    req_q_pk: &Pubkey,
600    event_q_pk: &Pubkey,
601    coin_lot_size: u64,
602    pc_lot_size: u64,
603    vault_signer_nonce: u64,
604    pc_dust_threshold: u64,
605) -> Result<solana_program::instruction::Instruction, DexError> {
606    let data = MarketInstruction::InitializeMarket(InitializeMarketInstruction {
607        coin_lot_size,
608        pc_lot_size,
609        fee_rate_bps: 0,
610        vault_signer_nonce,
611        pc_dust_threshold,
612    })
613    .pack();
614
615    let market_account = AccountMeta::new(*market, false);
616
617    let bids = AccountMeta::new(*bids_pk, false);
618    let asks = AccountMeta::new(*asks_pk, false);
619    let req_q = AccountMeta::new(*req_q_pk, false);
620    let event_q = AccountMeta::new(*event_q_pk, false);
621
622    let coin_vault = AccountMeta::new(*coin_vault_pk, false);
623    let pc_vault = AccountMeta::new(*pc_vault_pk, false);
624
625    let coin_mint = AccountMeta::new_readonly(*coin_mint_pk, false);
626    let pc_mint = AccountMeta::new_readonly(*pc_mint_pk, false);
627
628    let rent_sysvar = AccountMeta::new_readonly(solana_program::sysvar::rent::ID, false);
629
630    let mut accounts = vec![
631        market_account,
632        req_q,
633        event_q,
634        bids,
635        asks,
636        coin_vault,
637        pc_vault,
638        //srm_vault,
639        coin_mint,
640        pc_mint,
641        //srm_mint,
642        rent_sysvar,
643    ];
644    if let Some(auth) = authority_pk {
645        let authority = AccountMeta::new_readonly(*auth, false);
646        accounts.push(authority);
647        if let Some(prune_auth) = prune_authority_pk {
648            let authority = AccountMeta::new_readonly(*prune_auth, false);
649            accounts.push(authority);
650            if let Some(consume_events_auth) = consume_events_authority_pk {
651                let authority = AccountMeta::new_readonly(*consume_events_auth, false);
652                accounts.push(authority);
653            }
654        }
655    }
656
657    Ok(Instruction {
658        program_id: *program_id,
659        data,
660        accounts,
661    })
662}
663
664pub fn new_order(
665    market: &Pubkey,
666    open_orders_account: &Pubkey,
667    request_queue: &Pubkey,
668    event_queue: &Pubkey,
669    market_bids: &Pubkey,
670    market_asks: &Pubkey,
671    order_payer: &Pubkey,
672    open_orders_account_owner: &Pubkey,
673    coin_vault: &Pubkey,
674    pc_vault: &Pubkey,
675    spl_token_program_id: &Pubkey,
676    rent_sysvar_id: &Pubkey,
677    srm_account_referral: Option<&Pubkey>,
678    program_id: &Pubkey,
679    side: Side,
680    limit_price: NonZeroU64,
681    max_coin_qty: NonZeroU64,
682    order_type: OrderType,
683    client_order_id: u64,
684    self_trade_behavior: SelfTradeBehavior,
685    limit: u16,
686    max_native_pc_qty_including_fees: NonZeroU64,
687) -> Result<Instruction, DexError> {
688    let data = MarketInstruction::NewOrderV3(NewOrderInstructionV3 {
689        side,
690        limit_price,
691        max_coin_qty,
692        order_type,
693        client_order_id,
694        self_trade_behavior,
695        limit,
696        max_native_pc_qty_including_fees,
697    })
698    .pack();
699    let mut accounts = vec![
700        AccountMeta::new(*market, false),
701        AccountMeta::new(*open_orders_account, false),
702        AccountMeta::new(*request_queue, false),
703        AccountMeta::new(*event_queue, false),
704        AccountMeta::new(*market_bids, false),
705        AccountMeta::new(*market_asks, false),
706        AccountMeta::new(*order_payer, false),
707        AccountMeta::new_readonly(*open_orders_account_owner, true),
708        AccountMeta::new(*coin_vault, false),
709        AccountMeta::new(*pc_vault, false),
710        AccountMeta::new_readonly(*spl_token_program_id, false),
711        AccountMeta::new_readonly(*rent_sysvar_id, false),
712    ];
713    if let Some(key) = srm_account_referral {
714        accounts.push(AccountMeta::new_readonly(*key, false))
715    }
716    Ok(Instruction {
717        program_id: *program_id,
718        data,
719        accounts,
720    })
721}
722
723pub fn match_orders(
724    program_id: &Pubkey,
725    market: &Pubkey,
726    request_queue: &Pubkey,
727    bids: &Pubkey,
728    asks: &Pubkey,
729    event_queue: &Pubkey,
730    coin_fee_receivable_account: &Pubkey,
731    pc_fee_receivable_account: &Pubkey,
732    limit: u16,
733) -> Result<Instruction, DexError> {
734    let data = MarketInstruction::MatchOrders(limit).pack();
735    let accounts: Vec<AccountMeta> = vec![
736        AccountMeta::new(*market, false),
737        AccountMeta::new(*request_queue, false),
738        AccountMeta::new(*event_queue, false),
739        AccountMeta::new(*bids, false),
740        AccountMeta::new(*asks, false),
741        AccountMeta::new(*coin_fee_receivable_account, false),
742        AccountMeta::new(*pc_fee_receivable_account, false),
743    ];
744    Ok(Instruction {
745        program_id: *program_id,
746        data,
747        accounts,
748    })
749}
750
751pub fn consume_events(
752    program_id: &Pubkey,
753    open_orders_accounts: Vec<&Pubkey>,
754    market: &Pubkey,
755    event_queue: &Pubkey,
756    coin_fee_receivable_account: &Pubkey,
757    pc_fee_receivable_account: &Pubkey,
758    limit: u16,
759) -> Result<Instruction, DexError> {
760    let data = MarketInstruction::ConsumeEvents(limit).pack();
761    let mut accounts: Vec<AccountMeta> = open_orders_accounts
762        .iter()
763        .map(|key| AccountMeta::new(**key, false))
764        .collect();
765    accounts.append(&mut vec![
766        AccountMeta::new(*market, false),
767        AccountMeta::new(*event_queue, false),
768        AccountMeta::new(*coin_fee_receivable_account, false),
769        AccountMeta::new(*pc_fee_receivable_account, false),
770    ]);
771    Ok(Instruction {
772        program_id: *program_id,
773        data,
774        accounts,
775    })
776}
777
778pub fn consume_events_permissioned(
779    program_id: &Pubkey,
780    open_orders_accounts: Vec<&Pubkey>,
781    market: &Pubkey,
782    event_queue: &Pubkey,
783    consume_events_authority: &Pubkey,
784    limit: u16,
785) -> Result<Instruction, DexError> {
786    let data = MarketInstruction::ConsumeEventsPermissioned(limit).pack();
787    let mut accounts: Vec<AccountMeta> = open_orders_accounts
788        .iter()
789        .map(|key| AccountMeta::new(**key, false))
790        .collect();
791    accounts.append(&mut vec![
792        AccountMeta::new(*market, false),
793        AccountMeta::new(*event_queue, false),
794        AccountMeta::new_readonly(*consume_events_authority, true),
795    ]);
796    Ok(Instruction {
797        program_id: *program_id,
798        data,
799        accounts,
800    })
801}
802
803pub fn cancel_order(
804    program_id: &Pubkey,
805    market: &Pubkey,
806    market_bids: &Pubkey,
807    market_asks: &Pubkey,
808    open_orders_account: &Pubkey,
809    open_orders_account_owner: &Pubkey,
810    event_queue: &Pubkey,
811    side: Side,
812    order_id: u128,
813) -> Result<Instruction, DexError> {
814    let data = MarketInstruction::CancelOrderV2(CancelOrderInstructionV2 { side, order_id }).pack();
815    let accounts: Vec<AccountMeta> = vec![
816        AccountMeta::new(*market, false),
817        AccountMeta::new(*market_bids, false),
818        AccountMeta::new(*market_asks, false),
819        AccountMeta::new(*open_orders_account, false),
820        AccountMeta::new_readonly(*open_orders_account_owner, true),
821        AccountMeta::new(*event_queue, false),
822    ];
823    Ok(Instruction {
824        program_id: *program_id,
825        data,
826        accounts,
827    })
828}
829
830pub fn settle_funds(
831    program_id: &Pubkey,
832    market: &Pubkey,
833    spl_token_program_id: &Pubkey,
834    open_orders_account: &Pubkey,
835    open_orders_account_owner: &Pubkey,
836    coin_vault: &Pubkey,
837    coin_wallet: &Pubkey,
838    pc_vault: &Pubkey,
839    pc_wallet: &Pubkey,
840    referrer_pc_wallet: Option<&Pubkey>,
841    vault_signer: &Pubkey,
842) -> Result<Instruction, DexError> {
843    let data = MarketInstruction::SettleFunds.pack();
844    let mut accounts: Vec<AccountMeta> = vec![
845        AccountMeta::new(*market, false),
846        AccountMeta::new(*open_orders_account, false),
847        AccountMeta::new_readonly(*open_orders_account_owner, true),
848        AccountMeta::new(*coin_vault, false),
849        AccountMeta::new(*pc_vault, false),
850        AccountMeta::new(*coin_wallet, false),
851        AccountMeta::new(*pc_wallet, false),
852        AccountMeta::new_readonly(*vault_signer, false),
853        AccountMeta::new_readonly(*spl_token_program_id, false),
854    ];
855    if let Some(key) = referrer_pc_wallet {
856        accounts.push(AccountMeta::new(*key, false))
857    }
858    Ok(Instruction {
859        program_id: *program_id,
860        data,
861        accounts,
862    })
863}
864
865pub fn cancel_order_by_client_order_id(
866    program_id: &Pubkey,
867    market: &Pubkey,
868    market_bids: &Pubkey,
869    market_asks: &Pubkey,
870    open_orders_account: &Pubkey,
871    open_orders_account_owner: &Pubkey,
872    event_queue: &Pubkey,
873    client_order_id: u64,
874) -> Result<Instruction, DexError> {
875    let data = MarketInstruction::CancelOrderByClientIdV2(client_order_id).pack();
876    let accounts: Vec<AccountMeta> = vec![
877        AccountMeta::new(*market, false),
878        AccountMeta::new(*market_bids, false),
879        AccountMeta::new(*market_asks, false),
880        AccountMeta::new(*open_orders_account, false),
881        AccountMeta::new_readonly(*open_orders_account_owner, true),
882        AccountMeta::new(*event_queue, false),
883    ];
884    Ok(Instruction {
885        program_id: *program_id,
886        data,
887        accounts,
888    })
889}
890
891pub fn disable_market(
892    program_id: &Pubkey,
893    market: &Pubkey,
894    disable_authority_key: &Pubkey,
895) -> Result<Instruction, DexError> {
896    let data = MarketInstruction::DisableMarket.pack();
897    let accounts: Vec<AccountMeta> = vec![
898        AccountMeta::new(*market, false),
899        AccountMeta::new_readonly(*disable_authority_key, true),
900    ];
901    Ok(Instruction {
902        program_id: *program_id,
903        data,
904        accounts,
905    })
906}
907
908pub fn sweep_fees(
909    program_id: &Pubkey,
910    market: &Pubkey,
911    pc_vault: &Pubkey,
912    fee_sweeping_authority: &Pubkey,
913    fee_receivable_account: &Pubkey,
914    vault_signer: &Pubkey,
915    spl_token_program_id: &Pubkey,
916) -> Result<Instruction, DexError> {
917    let data = MarketInstruction::SweepFees.pack();
918    let accounts: Vec<AccountMeta> = vec![
919        AccountMeta::new(*market, false),
920        AccountMeta::new(*pc_vault, false),
921        AccountMeta::new_readonly(*fee_sweeping_authority, true),
922        AccountMeta::new(*fee_receivable_account, false),
923        AccountMeta::new_readonly(*vault_signer, false),
924        AccountMeta::new_readonly(*spl_token_program_id, false),
925    ];
926    Ok(Instruction {
927        program_id: *program_id,
928        data,
929        accounts,
930    })
931}
932
933pub fn close_open_orders(
934    program_id: &Pubkey,
935    open_orders: &Pubkey,
936    owner: &Pubkey,
937    destination: &Pubkey,
938    market: &Pubkey,
939) -> Result<Instruction, DexError> {
940    let data = MarketInstruction::CloseOpenOrders.pack();
941    let accounts: Vec<AccountMeta> = vec![
942        AccountMeta::new(*open_orders, false),
943        AccountMeta::new_readonly(*owner, true),
944        AccountMeta::new(*destination, false),
945        AccountMeta::new_readonly(*market, false),
946    ];
947    Ok(Instruction {
948        program_id: *program_id,
949        data,
950        accounts,
951    })
952}
953
954pub fn init_open_orders(
955    program_id: &Pubkey,
956    open_orders: &Pubkey,
957    owner: &Pubkey,
958    market: &Pubkey,
959    market_authority: Option<&Pubkey>,
960) -> Result<Instruction, DexError> {
961    let data = MarketInstruction::InitOpenOrders.pack();
962    let mut accounts: Vec<AccountMeta> = vec![
963        AccountMeta::new(*open_orders, false),
964        AccountMeta::new_readonly(*owner, true),
965        AccountMeta::new_readonly(*market, false),
966        AccountMeta::new_readonly(rent::ID, false),
967    ];
968    if let Some(market_authority) = market_authority {
969        accounts.push(AccountMeta::new_readonly(*market_authority, true));
970    }
971    Ok(Instruction {
972        program_id: *program_id,
973        data,
974        accounts,
975    })
976}
977
978pub fn prune(
979    program_id: &Pubkey,
980    market: &Pubkey,
981    bids: &Pubkey,
982    asks: &Pubkey,
983    prune_authority: &Pubkey,
984    open_orders: &Pubkey,
985    open_orders_owner: &Pubkey,
986    event_q: &Pubkey,
987    limit: u16,
988) -> Result<Instruction, DexError> {
989    let data = MarketInstruction::Prune(limit).pack();
990    let accounts: Vec<AccountMeta> = vec![
991        AccountMeta::new(*market, false),
992        AccountMeta::new(*bids, false),
993        AccountMeta::new(*asks, false),
994        AccountMeta::new_readonly(*prune_authority, true),
995        AccountMeta::new(*open_orders, false),
996        AccountMeta::new_readonly(*open_orders_owner, false),
997        AccountMeta::new(*event_q, false),
998    ];
999    Ok(Instruction {
1000        program_id: *program_id,
1001        data,
1002        accounts,
1003    })
1004}
1005
1006#[cfg(test)]
1007mod tests {
1008    use super::*;
1009
1010    proptest! {
1011        #[test]
1012        fn test_pack_unpack_roundtrip(inst: MarketInstruction) {
1013            let serialized = inst.pack();
1014            let unpack_serde_result = MarketInstruction::unpack_serde(&serialized).ok();
1015            let unpack_result = MarketInstruction::unpack(&serialized);
1016            assert_eq!(unpack_result, Some(inst));
1017            assert!(unpack_serde_result == unpack_result,
1018                "Serialized:\n{:?}\nLeft:\n{:#?}\nRight:\n{:#?}",
1019                serialized, unpack_serde_result, unpack_result
1020            );
1021        }
1022    }
1023}
1024
1025#[cfg(feature = "fuzz")]
1026mod fuzzing {
1027    use super::*;
1028    use crate::matching::{OrderType, Side};
1029    use arbitrary::Unstructured;
1030    use std::convert::{TryFrom, TryInto};
1031
1032    #[derive(arbitrary::Arbitrary)]
1033    struct NewOrderInstructionU64 {
1034        pub side: Side,
1035        pub limit_price: u64,
1036        pub max_qty: u64,
1037        pub order_type: OrderType,
1038        pub client_id: u64,
1039        pub self_trade_behavior: SelfTradeBehavior,
1040    }
1041
1042    #[derive(arbitrary::Arbitrary)]
1043    struct NewOrderInstructionV3U64 {
1044        pub side: Side,
1045
1046        pub limit_price: u64,
1047        pub max_coin_qty: u64,
1048        pub max_native_pc_qty_including_fees: u64,
1049        pub self_trade_behavior: SelfTradeBehavior,
1050        pub order_type: OrderType,
1051        pub client_order_id: u64,
1052        pub limit: u16,
1053    }
1054
1055    #[derive(arbitrary::Arbitrary)]
1056    struct SendTakeInstructionU64 {
1057        pub side: Side,
1058        pub limit_price: u64,
1059        pub max_coin_qty: u64,
1060        pub max_native_pc_qty_including_fees: u64,
1061        pub min_coin_qty: u64,
1062        pub min_native_pc_qty: u64,
1063        pub limit: u16,
1064    }
1065
1066    impl TryFrom<SendTakeInstructionU64> for SendTakeInstruction {
1067        type Error = std::num::TryFromIntError;
1068
1069        fn try_from(value: SendTakeInstructionU64) -> Result<Self, Self::Error> {
1070            Ok(Self {
1071                side: value.side,
1072                limit_price: value.limit_price.try_into()?,
1073                max_coin_qty: value.max_coin_qty.try_into()?,
1074                max_native_pc_qty_including_fees: value
1075                    .max_native_pc_qty_including_fees
1076                    .try_into()?,
1077                min_coin_qty: value.min_coin_qty,
1078                min_native_pc_qty: value.min_native_pc_qty,
1079                limit: value.limit,
1080            })
1081        }
1082    }
1083
1084    impl TryFrom<NewOrderInstructionV3U64> for NewOrderInstructionV3 {
1085        type Error = std::num::TryFromIntError;
1086
1087        fn try_from(value: NewOrderInstructionV3U64) -> Result<Self, Self::Error> {
1088            Ok(Self {
1089                side: value.side,
1090                limit_price: value.limit_price.try_into()?,
1091                max_coin_qty: value.max_coin_qty.try_into()?,
1092                max_native_pc_qty_including_fees: value
1093                    .max_native_pc_qty_including_fees
1094                    .try_into()?,
1095                order_type: value.order_type,
1096                client_order_id: value.client_order_id,
1097                self_trade_behavior: value.self_trade_behavior,
1098                limit: value.limit,
1099            })
1100        }
1101    }
1102
1103    impl TryFrom<NewOrderInstructionU64> for NewOrderInstructionV2 {
1104        type Error = std::num::TryFromIntError;
1105
1106        fn try_from(value: NewOrderInstructionU64) -> Result<Self, Self::Error> {
1107            Ok(Self {
1108                side: value.side,
1109                limit_price: value.limit_price.try_into()?,
1110                max_qty: value.max_qty.try_into()?,
1111                order_type: value.order_type,
1112                client_id: value.client_id,
1113                self_trade_behavior: value.self_trade_behavior,
1114            })
1115        }
1116    }
1117
1118    impl TryFrom<NewOrderInstructionU64> for NewOrderInstructionV1 {
1119        type Error = std::num::TryFromIntError;
1120
1121        fn try_from(value: NewOrderInstructionU64) -> Result<Self, Self::Error> {
1122            Ok(Self {
1123                side: value.side,
1124                limit_price: value.limit_price.try_into()?,
1125                max_qty: value.max_qty.try_into()?,
1126                order_type: value.order_type,
1127                client_id: value.client_id,
1128            })
1129        }
1130    }
1131
1132    impl From<&SendTakeInstruction> for SendTakeInstructionU64 {
1133        fn from(value: &SendTakeInstruction) -> Self {
1134            Self {
1135                side: value.side,
1136                limit_price: value.limit_price.into(),
1137                max_coin_qty: value.max_coin_qty.into(),
1138                max_native_pc_qty_including_fees: value.max_native_pc_qty_including_fees.into(),
1139                min_coin_qty: value.min_coin_qty,
1140                min_native_pc_qty: value.min_native_pc_qty,
1141                limit: value.limit,
1142            }
1143        }
1144    }
1145
1146    impl From<&NewOrderInstructionV3> for NewOrderInstructionV3U64 {
1147        fn from(value: &NewOrderInstructionV3) -> Self {
1148            Self {
1149                side: value.side,
1150                limit_price: value.limit_price.get(),
1151                max_coin_qty: value.max_coin_qty.get(),
1152                max_native_pc_qty_including_fees: value.max_native_pc_qty_including_fees.get(),
1153                self_trade_behavior: value.self_trade_behavior,
1154                order_type: value.order_type,
1155                client_order_id: value.client_order_id,
1156                limit: value.limit,
1157            }
1158        }
1159    }
1160
1161    impl From<&NewOrderInstructionV2> for NewOrderInstructionU64 {
1162        fn from(value: &NewOrderInstructionV2) -> Self {
1163            Self {
1164                side: value.side,
1165                limit_price: value.limit_price.get(),
1166                max_qty: value.max_qty.get(),
1167                order_type: value.order_type,
1168                client_id: value.client_id,
1169                self_trade_behavior: value.self_trade_behavior,
1170            }
1171        }
1172    }
1173
1174    impl From<&NewOrderInstructionV1> for NewOrderInstructionU64 {
1175        fn from(value: &NewOrderInstructionV1) -> Self {
1176            Self {
1177                side: value.side,
1178                limit_price: value.limit_price.get(),
1179                max_qty: value.max_qty.get(),
1180                order_type: value.order_type,
1181                client_id: value.client_id,
1182                self_trade_behavior: SelfTradeBehavior::DecrementTake,
1183            }
1184        }
1185    }
1186
1187    macro_rules! arbitrary_impl {
1188        ($T:ident, $TU64:ident) => {
1189            impl arbitrary::Arbitrary for $T {
1190                fn arbitrary(u: &mut Unstructured<'_>) -> Result<Self, arbitrary::Error> {
1191                    <$TU64 as arbitrary::Arbitrary>::arbitrary(u)?
1192                        .try_into()
1193                        .map_err(|_| arbitrary::Error::IncorrectFormat)
1194                }
1195
1196                fn size_hint(depth: usize) -> (usize, Option<usize>) {
1197                    <$TU64 as arbitrary::Arbitrary>::size_hint(depth)
1198                }
1199
1200                fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
1201                    let x: $TU64 = self.into();
1202                    Box::new(x.shrink().map($TU64::try_into).filter_map(Result::ok))
1203                }
1204            }
1205        };
1206    }
1207
1208    arbitrary_impl!(SendTakeInstruction, SendTakeInstructionU64);
1209    arbitrary_impl!(NewOrderInstructionV3, NewOrderInstructionV3U64);
1210    arbitrary_impl!(NewOrderInstructionV2, NewOrderInstructionU64);
1211    arbitrary_impl!(NewOrderInstructionV1, NewOrderInstructionU64);
1212}