gmsol_store/states/
order.rs

1use anchor_lang::prelude::*;
2use borsh::{BorshDeserialize, BorshSerialize};
3use gmsol_model::{
4    action::decrease_position::{DecreasePositionReport, DecreasePositionSwapType},
5    price::Price,
6};
7use gmsol_utils::InitSpace as _;
8
9use crate::{
10    events::{EventEmitter, GtUpdated, OrderRemoved},
11    utils::pubkey::optional_address,
12    CoreError,
13};
14
15use super::{
16    common::{
17        action::{Action, ActionHeader, ActionSigner, Closable},
18        swap::SwapActionParams,
19        token::TokenAndAccount,
20    },
21    user::UserHeader,
22    Oracle, Seed, Store,
23};
24
25pub use gmsol_utils::order::{OrderKind, OrderSide};
26
27/// Update Order Params.
28#[derive(AnchorSerialize, AnchorDeserialize, Clone, InitSpace, Copy)]
29#[cfg_attr(feature = "debug", derive(Debug))]
30pub struct UpdateOrderParams {
31    /// Size delta in USD.
32    pub size_delta_value: Option<u128>,
33    /// Acceptable price.
34    pub acceptable_price: Option<u128>,
35    /// Trigger price.
36    pub trigger_price: Option<u128>,
37    /// Min output amount.
38    pub min_output: Option<u128>,
39    /// Valid from this timestamp.
40    pub valid_from_ts: Option<i64>,
41}
42
43impl UpdateOrderParams {
44    /// Is empty.
45    pub fn is_empty(&self) -> bool {
46        self.size_delta_value.is_none()
47            && self.acceptable_price.is_none()
48            && self.trigger_price.is_none()
49            && self.min_output.is_none()
50            && self.valid_from_ts.is_none()
51    }
52}
53
54/// Transfer Out.
55#[zero_copy]
56#[cfg_attr(feature = "debug", derive(derive_more::Debug))]
57#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
58#[derive(BorshSerialize, BorshDeserialize, Default, InitSpace)]
59pub struct TransferOut {
60    /// Executed.
61    executed: u8,
62    #[cfg_attr(feature = "debug", debug(skip))]
63    padding_0: [u8; 7],
64    /// Final output token.
65    pub final_output_token: u64,
66    /// Secondary output token.
67    pub secondary_output_token: u64,
68    /// Long token.
69    pub long_token: u64,
70    /// Short token.
71    pub short_token: u64,
72    /// Long token amount for claimable account of user.
73    pub long_token_for_claimable_account_of_user: u64,
74    /// Short token amount for cliamable account of user.
75    pub short_token_for_claimable_account_of_user: u64,
76    /// Long token amount for claimable account of holding.
77    pub long_token_for_claimable_account_of_holding: u64,
78    /// Short token amount for claimable account of holding.
79    pub short_token_for_claimable_account_of_holding: u64,
80}
81
82#[cfg(test)]
83impl From<crate::events::EventTransferOut> for TransferOut {
84    fn from(event: crate::events::EventTransferOut) -> Self {
85        let crate::events::EventTransferOut {
86            executed,
87            padding_0,
88            final_output_token,
89            secondary_output_token,
90            long_token,
91            short_token,
92            long_token_for_claimable_account_of_user,
93            short_token_for_claimable_account_of_user,
94            long_token_for_claimable_account_of_holding,
95            short_token_for_claimable_account_of_holding,
96        } = event;
97
98        Self {
99            executed,
100            padding_0,
101            final_output_token,
102            secondary_output_token,
103            long_token,
104            short_token,
105            long_token_for_claimable_account_of_user,
106            short_token_for_claimable_account_of_user,
107            long_token_for_claimable_account_of_holding,
108            short_token_for_claimable_account_of_holding,
109        }
110    }
111}
112
113/// Recevier Kind.
114pub enum CollateralReceiver {
115    Collateral,
116    ClaimableForHolding,
117    ClaimableForUser,
118}
119
120impl TransferOut {
121    const EXECUTED: u8 = u8::MAX;
122    const NOT_EXECUTED: u8 = 0;
123
124    /// Return whether the order is executed.
125    pub fn executed(&self) -> bool {
126        !self.executed == Self::NOT_EXECUTED
127    }
128
129    /// Return whether the output for user is empty.
130    pub fn is_user_output_empty(&self) -> bool {
131        self.final_output_token == 0
132            && self.secondary_output_token == 0
133            && self.long_token == 0
134            && self.short_token == 0
135            && self.long_token_for_claimable_account_of_user == 0
136            && self.short_token_for_claimable_account_of_user == 0
137    }
138
139    pub(crate) fn set_executed(&mut self, executed: bool) -> &mut Self {
140        self.executed = if executed {
141            Self::EXECUTED
142        } else {
143            Self::NOT_EXECUTED
144        };
145        self
146    }
147
148    pub(crate) fn new_failed() -> Self {
149        Self {
150            executed: Self::NOT_EXECUTED,
151            ..Default::default()
152        }
153    }
154
155    pub(crate) fn total_long_token_amount(&self) -> Result<u64> {
156        self.long_token
157            .checked_add(self.long_token_for_claimable_account_of_user)
158            .and_then(|a| a.checked_add(self.long_token_for_claimable_account_of_holding))
159            .ok_or_else(|| error!(CoreError::TokenAmountOverflow))
160    }
161
162    pub(crate) fn total_short_token_amount(&self) -> Result<u64> {
163        self.short_token
164            .checked_add(self.short_token_for_claimable_account_of_user)
165            .and_then(|a| a.checked_add(self.short_token_for_claimable_account_of_holding))
166            .ok_or_else(|| error!(CoreError::TokenAmountOverflow))
167    }
168
169    pub(crate) fn transfer_out(&mut self, is_secondary: bool, amount: u64) -> Result<()> {
170        if amount == 0 {
171            return Ok(());
172        }
173        if is_secondary {
174            self.secondary_output_token = self
175                .secondary_output_token
176                .checked_add(amount)
177                .ok_or_else(|| error!(CoreError::TokenAmountOverflow))?;
178        } else {
179            self.final_output_token = self
180                .final_output_token
181                .checked_add(amount)
182                .ok_or_else(|| error!(CoreError::TokenAmountOverflow))?;
183        }
184        Ok(())
185    }
186
187    pub(crate) fn transfer_out_funding_amounts(
188        &mut self,
189        long_amount: &u128,
190        short_amount: &u128,
191    ) -> Result<()> {
192        self.transfer_out_collateral(
193            true,
194            CollateralReceiver::Collateral,
195            (*long_amount)
196                .try_into()
197                .map_err(|_| error!(CoreError::TokenAmountOverflow))?,
198        )?;
199        self.transfer_out_collateral(
200            false,
201            CollateralReceiver::Collateral,
202            (*short_amount)
203                .try_into()
204                .map_err(|_| error!(CoreError::TokenAmountOverflow))?,
205        )?;
206        Ok(())
207    }
208
209    pub(crate) fn process_claimable_collateral_for_decrease(
210        &mut self,
211        report: &DecreasePositionReport<u128, i128>,
212    ) -> Result<()> {
213        let for_holding = report.claimable_collateral_for_holding();
214        require!(
215            *for_holding.output_token_amount() == 0,
216            CoreError::ClaimableCollateralForHoldingCannotBeInOutputTokens,
217        );
218
219        let is_output_token_long = report.is_output_token_long();
220        let is_secondary_token_long = report.is_secondary_output_token_long();
221
222        let secondary_amount = (*for_holding.secondary_output_token_amount())
223            .try_into()
224            .map_err(|_| error!(CoreError::TokenAmountOverflow))?;
225        self.transfer_out_collateral(
226            is_secondary_token_long,
227            CollateralReceiver::ClaimableForHolding,
228            secondary_amount,
229        )?;
230
231        let for_user = report.claimable_collateral_for_user();
232        self.transfer_out_collateral(
233            is_output_token_long,
234            CollateralReceiver::ClaimableForUser,
235            (*for_user.output_token_amount())
236                .try_into()
237                .map_err(|_| error!(CoreError::TokenAmountOverflow))?,
238        )?;
239        self.transfer_out_collateral(
240            is_secondary_token_long,
241            CollateralReceiver::ClaimableForUser,
242            (*for_user.secondary_output_token_amount())
243                .try_into()
244                .map_err(|_| error!(CoreError::TokenAmountOverflow))?,
245        )?;
246        Ok(())
247    }
248
249    pub(crate) fn transfer_out_collateral(
250        &mut self,
251        is_long: bool,
252        to: CollateralReceiver,
253        amount: u64,
254    ) -> Result<()> {
255        if amount == 0 {
256            return Ok(());
257        }
258        match to {
259            CollateralReceiver::Collateral => {
260                if is_long {
261                    self.long_token = self
262                        .long_token
263                        .checked_add(amount)
264                        .ok_or_else(|| error!(CoreError::TokenAmountOverflow))?;
265                } else {
266                    self.short_token = self
267                        .short_token
268                        .checked_add(amount)
269                        .ok_or_else(|| error!(CoreError::TokenAmountOverflow))?;
270                }
271            }
272            CollateralReceiver::ClaimableForHolding => {
273                if is_long {
274                    self.long_token_for_claimable_account_of_holding = self
275                        .long_token_for_claimable_account_of_holding
276                        .checked_add(amount)
277                        .ok_or_else(|| error!(CoreError::TokenAmountOverflow))?;
278                } else {
279                    self.short_token_for_claimable_account_of_holding = self
280                        .short_token_for_claimable_account_of_holding
281                        .checked_add(amount)
282                        .ok_or_else(|| error!(CoreError::TokenAmountOverflow))?;
283                }
284            }
285            CollateralReceiver::ClaimableForUser => {
286                if is_long {
287                    self.long_token_for_claimable_account_of_user = self
288                        .long_token_for_claimable_account_of_user
289                        .checked_add(amount)
290                        .ok_or_else(|| error!(CoreError::TokenAmountOverflow))?;
291                } else {
292                    self.short_token_for_claimable_account_of_user = self
293                        .short_token_for_claimable_account_of_user
294                        .checked_add(amount)
295                        .ok_or_else(|| error!(CoreError::TokenAmountOverflow))?;
296                }
297            }
298        }
299        Ok(())
300    }
301}
302
303/// Order.
304#[account(zero_copy)]
305#[cfg_attr(feature = "debug", derive(derive_more::Debug))]
306#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
307pub struct Order {
308    /// Action header.
309    pub(crate) header: ActionHeader,
310    /// Market token.
311    pub(crate) market_token: Pubkey,
312    /// Token accounts.
313    pub(crate) tokens: OrderTokenAccounts,
314    /// Swap params.
315    pub(crate) swap: SwapActionParams,
316    #[cfg_attr(feature = "debug", debug(skip))]
317    padding_0: [u8; 4],
318    /// Order params.
319    pub(crate) params: OrderActionParams,
320    pub(crate) gt_reward: u64,
321    #[cfg_attr(feature = "debug", debug(skip))]
322    padding_1: [u8; 8],
323    #[cfg_attr(feature = "debug", debug(skip))]
324    #[cfg_attr(feature = "serde", serde(with = "serde_bytes"))]
325    reserved: [u8; 128],
326}
327
328impl Seed for Order {
329    /// Seed.
330    const SEED: &'static [u8] = b"order";
331}
332
333impl gmsol_utils::InitSpace for Order {
334    const INIT_SPACE: usize = std::mem::size_of::<Self>();
335}
336
337impl Action for Order {
338    const MIN_EXECUTION_LAMPORTS: u64 = 300_000;
339
340    fn header(&self) -> &ActionHeader {
341        &self.header
342    }
343}
344
345impl Closable for Order {
346    type ClosedEvent = OrderRemoved;
347
348    fn to_closed_event(&self, address: &Pubkey, reason: &str) -> Result<Self::ClosedEvent> {
349        OrderRemoved::new(
350            self.header.id,
351            self.header.store,
352            *address,
353            self.params.kind()?,
354            self.market_token,
355            self.header.owner,
356            self.header.action_state()?,
357            reason,
358        )
359    }
360}
361
362impl Order {
363    /// Get rent for position cut.
364    pub(crate) fn position_cut_rent(is_pure: bool, include_execution_fee: bool) -> Result<u64> {
365        use anchor_spl::token::TokenAccount;
366
367        let rent = Rent::get()?;
368        let amount = rent.minimum_balance(Self::INIT_SPACE + 8)
369            + rent.minimum_balance(TokenAccount::LEN) * if is_pure { 1 } else { 2 }
370            + if include_execution_fee {
371                Self::MIN_EXECUTION_LAMPORTS
372            } else {
373                0
374            };
375
376        Ok(amount)
377    }
378
379    /// Get signer.
380    pub fn signer(&self) -> ActionSigner {
381        self.header.signer(Self::SEED)
382    }
383
384    /// Validate that current timestamp >= `valid_from_ts`.
385    pub fn validate_valid_from_ts(&self) -> Result<()> {
386        if self.params.kind()?.is_market() {
387            return Ok(());
388        }
389        require_gte!(Clock::get()?.unix_timestamp, self.params.valid_from_ts);
390        Ok(())
391    }
392
393    /// Validate trigger price.
394    pub fn validate_trigger_price(&self, index_price: &Price<u128>) -> Result<()> {
395        let params = &self.params;
396        let kind = params.kind()?;
397        let is_long = params.side()?.is_long();
398        let trigger_price = &params.trigger_price;
399        match kind {
400            OrderKind::LimitIncrease => {
401                if is_long {
402                    require_gte!(
403                        trigger_price,
404                        index_price.pick_price(true),
405                        CoreError::InvalidTriggerPrice
406                    );
407                } else {
408                    require_gte!(
409                        index_price.pick_price(false),
410                        trigger_price,
411                        CoreError::InvalidTriggerPrice
412                    );
413                }
414            }
415            OrderKind::LimitDecrease => {
416                if is_long {
417                    require_gte!(
418                        index_price.pick_price(false),
419                        trigger_price,
420                        CoreError::InvalidTriggerPrice
421                    );
422                } else {
423                    require_gte!(
424                        trigger_price,
425                        index_price.pick_price(true),
426                        CoreError::InvalidTriggerPrice
427                    );
428                }
429            }
430            OrderKind::StopLossDecrease => {
431                if is_long {
432                    require_gte!(
433                        trigger_price,
434                        index_price.pick_price(false),
435                        CoreError::InvalidTriggerPrice
436                    );
437                } else {
438                    require_gte!(
439                        index_price.pick_price(true),
440                        trigger_price,
441                        CoreError::InvalidTriggerPrice
442                    );
443                }
444            }
445            OrderKind::LimitSwap => {
446                // NOTE: For limit swap orders, the trigger price can be substituted by the min output amount,
447                // so validatoin is not required. In fact, we should prohibit the creation of limit swap orders
448                // with a trigger price.
449            }
450            OrderKind::MarketSwap
451            | OrderKind::MarketIncrease
452            | OrderKind::MarketDecrease
453            | OrderKind::Liquidation
454            | OrderKind::AutoDeleveraging => {}
455            _ => return err!(CoreError::UnknownOrderKind),
456        }
457
458        Ok(())
459    }
460
461    /// Validate output amount.
462    pub fn validate_output_amount(&self, output_amount: u128) -> Result<()> {
463        require_gte!(
464            output_amount,
465            self.params.min_output,
466            CoreError::InsufficientOutputAmount
467        );
468        Ok(())
469    }
470
471    #[inline(never)]
472    pub(crate) fn validate_decrease_output_amounts(
473        &self,
474        oracle: &Oracle,
475        output_token: &Pubkey,
476        output_amount: u64,
477        secondary_output_token: &Pubkey,
478        secondary_output_amount: u64,
479    ) -> Result<()> {
480        let mut total = 0u128;
481        {
482            let price = oracle.get_primary_price(output_token, false)?.min;
483            let output_value = u128::from(output_amount).saturating_mul(price);
484            total = total.saturating_add(output_value);
485        }
486        {
487            let price = oracle.get_primary_price(secondary_output_token, false)?.min;
488            let output_value = u128::from(secondary_output_amount).saturating_mul(price);
489            total = total.saturating_add(output_value);
490        }
491
492        // We use the `min_output_amount` as min output value.
493        self.validate_output_amount(total)?;
494        Ok(())
495    }
496
497    /// Get secondary output token (pnl token).
498    pub fn secondary_output_token(&self) -> Result<Pubkey> {
499        if self.params.side()?.is_long() {
500            self.tokens.long_token.token()
501        } else {
502            self.tokens.short_token.token()
503        }
504        .ok_or_else(|| error!(CoreError::MissingPoolTokens))
505    }
506
507    /// Get order params.
508    pub fn params(&self) -> &OrderActionParams {
509        &self.params
510    }
511
512    /// Get swap params.
513    pub fn swap(&self) -> &SwapActionParams {
514        &self.swap
515    }
516
517    /// Get market token.
518    pub fn market_token(&self) -> &Pubkey {
519        &self.market_token
520    }
521
522    /// Get token accounts.
523    pub fn tokens(&self) -> &OrderTokenAccounts {
524        &self.tokens
525    }
526
527    /// Process GT.
528    /// CHECK: the order must have been successfully executed.
529    #[inline(never)]
530    pub(crate) fn unchecked_process_gt(
531        &mut self,
532        store: &mut Store,
533        user: &mut UserHeader,
534        paid_fee_value: u128,
535        event_emitter: &EventEmitter,
536    ) -> Result<()> {
537        if paid_fee_value == 0 {
538            msg!("[GT] GT is not minted unless an order fee is paid");
539            return Ok(());
540        }
541
542        // Ignore the overflowed value.
543        let next_paid_fee_value = user.gt.paid_fee_value().saturating_add(paid_fee_value);
544        let minted_fee_value = user.gt.minted_fee_value();
545
546        require_gte!(
547            next_paid_fee_value,
548            minted_fee_value,
549            CoreError::InvalidUserAccount
550        );
551
552        let value_to_mint_for = next_paid_fee_value.saturating_sub(minted_fee_value);
553
554        let (minted, delta_minted_value, minting_cost) =
555            store.gt().get_mint_amount(value_to_mint_for)?;
556
557        let next_minted_value = minted_fee_value
558            .checked_add(delta_minted_value)
559            .ok_or_else(|| error!(CoreError::ValueOverflow))?;
560
561        store.gt_mut().mint_to(user, minted)?;
562
563        self.gt_reward = minted;
564        user.gt.paid_fee_value = next_paid_fee_value;
565        user.gt.minted_fee_value = next_minted_value;
566
567        event_emitter
568            .emit_cpi(&GtUpdated::minted(
569                minting_cost,
570                minted,
571                store.gt(),
572                Some(user),
573            ))
574            .expect("failed to emit GT minted event");
575
576        Ok(())
577    }
578
579    pub(crate) fn update(&mut self, id: u64, params: &UpdateOrderParams) -> Result<()> {
580        let current = &mut self.params;
581        require!(current.is_updatable()?, CoreError::InvalidArgument);
582        require!(!params.is_empty(), CoreError::InvalidArgument);
583
584        self.header.id = id;
585
586        if let Some(size_delta_value) = params.size_delta_value {
587            current.size_delta_value = size_delta_value;
588        }
589
590        if let Some(acceptable_price) = params.acceptable_price {
591            current.acceptable_price = acceptable_price;
592        }
593
594        if let Some(trigger_price) = params.trigger_price {
595            current.trigger_price = trigger_price;
596        }
597
598        if let Some(min_output) = params.min_output {
599            if matches!(current.kind()?, OrderKind::LimitSwap) {
600                require_neq!(min_output, 0, CoreError::InvalidArgument);
601            }
602            current.min_output = min_output;
603        }
604
605        if let Some(ts) = params.valid_from_ts {
606            current.valid_from_ts = ts;
607        }
608
609        self.header.updated()?;
610
611        Ok(())
612    }
613}
614
615/// Token accounts for Order.
616#[zero_copy]
617#[cfg_attr(feature = "debug", derive(derive_more::Debug))]
618#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
619pub struct OrderTokenAccounts {
620    /// Initial collateral.
621    pub(crate) initial_collateral: TokenAndAccount,
622    /// Final output token.
623    pub(crate) final_output_token: TokenAndAccount,
624    /// Long token.
625    pub(crate) long_token: TokenAndAccount,
626    /// Short token.
627    pub(crate) short_token: TokenAndAccount,
628    #[cfg_attr(feature = "debug", debug(skip))]
629    #[cfg_attr(feature = "serde", serde(with = "serde_bytes"))]
630    reserved: [u8; 128],
631}
632
633impl OrderTokenAccounts {
634    /// Get initial collateral token info.
635    ///
636    /// Only available for increase and swap orders.
637    pub fn initial_collateral(&self) -> &TokenAndAccount {
638        &self.initial_collateral
639    }
640
641    /// Get final output token info.
642    ///
643    /// Only available for decrease and swap orders.
644    pub fn final_output_token(&self) -> &TokenAndAccount {
645        &self.final_output_token
646    }
647
648    /// Get long token info.
649    ///
650    /// Only available for increase and decrease orders.
651    pub fn long_token(&self) -> &TokenAndAccount {
652        &self.long_token
653    }
654
655    /// Get short token info.
656    ///
657    //// Only available for increase and decrease orders.
658    pub fn short_token(&self) -> &TokenAndAccount {
659        &self.short_token
660    }
661}
662
663/// Order params.
664#[zero_copy]
665#[cfg_attr(feature = "debug", derive(derive_more::Debug))]
666#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
667pub struct OrderActionParams {
668    /// Kind.
669    kind: u8,
670    /// Order side.
671    side: u8,
672    /// Decrease position swap type.
673    decrease_position_swap_type: u8,
674    #[cfg_attr(feature = "debug", debug(skip))]
675    padding_1: [u8; 5],
676    /// Collateral/Output token.
677    collateral_token: Pubkey,
678    /// Position address.
679    position: Pubkey,
680    /// Initial collateral delta amount.
681    pub(crate) initial_collateral_delta_amount: u64,
682    /// Size delta value.
683    pub(crate) size_delta_value: u128,
684    /// Min output amount or value.
685    /// - Used as amount for swap orders.
686    /// - Used as value for decrease position orders.
687    min_output: u128,
688    /// Trigger price (in unit price).
689    pub(crate) trigger_price: u128,
690    /// Acceptable price (in unit price).
691    pub(crate) acceptable_price: u128,
692    pub(crate) valid_from_ts: i64,
693    #[cfg_attr(feature = "debug", debug(skip))]
694    padding_2: [u8; 8],
695    #[cfg_attr(feature = "debug", debug(skip))]
696    #[cfg_attr(feature = "serde", serde(with = "serde_bytes"))]
697    reserved: [u8; 64],
698}
699
700impl OrderActionParams {
701    const DEFAULT_VALID_FROM_TS: i64 = 0;
702
703    pub(crate) fn init_swap(
704        &mut self,
705        kind: OrderKind,
706        collateral_token: Pubkey,
707        swap_in_amount: u64,
708        min_output: Option<u128>,
709        valid_from_ts: Option<i64>,
710    ) -> Result<()> {
711        self.kind = kind.into();
712        self.collateral_token = collateral_token;
713        self.initial_collateral_delta_amount = swap_in_amount;
714        match kind {
715            OrderKind::MarketSwap => {
716                self.min_output = min_output.unwrap_or(0);
717                self.valid_from_ts = Self::DEFAULT_VALID_FROM_TS;
718            }
719            OrderKind::LimitSwap => {
720                let Some(min_output) = min_output else {
721                    return err!(CoreError::InvalidMinOutputAmount);
722                };
723                require!(min_output != 0, CoreError::Internal);
724                self.min_output = min_output;
725
726                self.valid_from_ts = valid_from_ts.unwrap_or(Self::DEFAULT_VALID_FROM_TS);
727            }
728            _ => {
729                return err!(CoreError::Internal);
730            }
731        }
732        Ok(())
733    }
734
735    #[allow(clippy::too_many_arguments)]
736    pub(crate) fn init_increase(
737        &mut self,
738        is_long: bool,
739        kind: OrderKind,
740        position: Pubkey,
741        collateral_token: Pubkey,
742        initial_collateral_delta_amount: u64,
743        size_delta_value: u128,
744        trigger_price: Option<u128>,
745        acceptable_price: Option<u128>,
746        min_output: Option<u128>,
747        valid_from_ts: Option<i64>,
748    ) -> Result<()> {
749        self.kind = kind.into();
750        self.side = if is_long {
751            OrderSide::Long
752        } else {
753            OrderSide::Short
754        }
755        .into();
756        self.collateral_token = collateral_token;
757        self.initial_collateral_delta_amount = initial_collateral_delta_amount;
758        self.size_delta_value = size_delta_value;
759        self.position = position;
760        self.min_output = min_output.unwrap_or(0);
761        match acceptable_price {
762            Some(price) => {
763                self.acceptable_price = price;
764            }
765            None => {
766                if is_long {
767                    self.acceptable_price = u128::MAX;
768                } else {
769                    self.acceptable_price = u128::MIN;
770                }
771            }
772        }
773        match kind {
774            OrderKind::MarketIncrease => {
775                require!(trigger_price.is_none(), CoreError::InvalidTriggerPrice);
776                self.valid_from_ts = Self::DEFAULT_VALID_FROM_TS;
777            }
778            OrderKind::LimitIncrease => {
779                let Some(price) = trigger_price else {
780                    return err!(CoreError::InvalidTriggerPrice);
781                };
782                self.trigger_price = price;
783                self.valid_from_ts = valid_from_ts.unwrap_or(Self::DEFAULT_VALID_FROM_TS);
784            }
785            _ => {
786                return err!(CoreError::Internal);
787            }
788        }
789        Ok(())
790    }
791
792    #[allow(clippy::too_many_arguments)]
793    pub(crate) fn init_decrease(
794        &mut self,
795        is_long: bool,
796        kind: OrderKind,
797        position: Pubkey,
798        collateral_token: Pubkey,
799        initial_collateral_delta_amount: u64,
800        size_delta_value: u128,
801        trigger_price: Option<u128>,
802        acceptable_price: Option<u128>,
803        min_output: Option<u128>,
804        swap_type: DecreasePositionSwapType,
805        valid_from_ts: Option<i64>,
806    ) -> Result<()> {
807        self.kind = kind.into();
808        self.side = if is_long {
809            OrderSide::Long
810        } else {
811            OrderSide::Short
812        }
813        .into();
814        self.decrease_position_swap_type = swap_type.into();
815        self.position = position;
816        self.collateral_token = collateral_token;
817        self.initial_collateral_delta_amount = initial_collateral_delta_amount;
818        self.size_delta_value = size_delta_value;
819        self.min_output = min_output.unwrap_or(0);
820        match acceptable_price {
821            Some(price) => {
822                self.acceptable_price = price;
823            }
824            None => {
825                if is_long {
826                    self.acceptable_price = u128::MIN;
827                } else {
828                    self.acceptable_price = u128::MAX;
829                }
830            }
831        }
832        match kind {
833            OrderKind::MarketDecrease | OrderKind::Liquidation | OrderKind::AutoDeleveraging => {
834                require!(trigger_price.is_none(), CoreError::InvalidTriggerPrice);
835                self.valid_from_ts = Self::DEFAULT_VALID_FROM_TS;
836            }
837            OrderKind::LimitDecrease | OrderKind::StopLossDecrease => {
838                let Some(price) = trigger_price else {
839                    return err!(CoreError::InvalidTriggerPrice);
840                };
841                self.trigger_price = price;
842                self.valid_from_ts = valid_from_ts.unwrap_or(Self::DEFAULT_VALID_FROM_TS);
843            }
844            _ => {
845                return err!(CoreError::Internal);
846            }
847        }
848        Ok(())
849    }
850
851    /// Get order kind.
852    pub fn kind(&self) -> Result<OrderKind> {
853        self.kind
854            .try_into()
855            .map_err(|_| error!(CoreError::UnknownOrderKind))
856    }
857
858    /// Get decrease position swap type.
859    pub fn decrease_position_swap_type(&self) -> Result<DecreasePositionSwapType> {
860        let ty = self
861            .decrease_position_swap_type
862            .try_into()
863            .map_err(|_| CoreError::UnknownDecreasePositionSwapType)?;
864        Ok(ty)
865    }
866
867    /// Return whether the order is updatable.
868    pub fn is_updatable(&self) -> Result<bool> {
869        let kind = self.kind()?;
870        Ok(matches!(
871            kind,
872            OrderKind::LimitSwap
873                | OrderKind::LimitIncrease
874                | OrderKind::LimitDecrease
875                | OrderKind::StopLossDecrease
876        ))
877    }
878
879    /// Get order side.
880    pub fn side(&self) -> Result<OrderSide> {
881        self.side
882            .try_into()
883            .map_err(|_| error!(CoreError::UnknownOrderSide))
884    }
885
886    /// Get position address.
887    pub fn position(&self) -> Option<&Pubkey> {
888        optional_address(&self.position)
889    }
890
891    /// Get initial collateral delta amount.
892    pub fn amount(&self) -> u64 {
893        self.initial_collateral_delta_amount
894    }
895
896    /// Get size delta in value.
897    pub fn size(&self) -> u128 {
898        self.size_delta_value
899    }
900
901    /// Get accetable price (unit price).
902    pub fn acceptable_price(&self) -> u128 {
903        self.acceptable_price
904    }
905
906    /// Get trigger price (unit price).
907    pub fn trigger_price(&self) -> u128 {
908        self.trigger_price
909    }
910
911    /// Get min output.
912    pub fn min_output(&self) -> u128 {
913        self.min_output
914    }
915
916    /// Get valid from ts.
917    pub fn valid_from_ts(&self) -> i64 {
918        self.valid_from_ts
919    }
920}