1use anchor_lang::prelude::*;
2use anchor_spl::token::{Mint, TokenAccount};
3use gmsol_callback::interface::ActionKind;
4use gmsol_model::{
5 action::decrease_position::{DecreasePositionFlags, DecreasePositionSwapType},
6 num::Unsigned,
7 price::Prices,
8 BaseMarket, BaseMarketExt, MarketAction, PnlFactorKind, Position as _, PositionMut,
9 PositionMutExt, PositionState, PositionStateExt,
10};
11use gmsol_utils::action::ActionCallbackKind;
12use typed_builder::TypedBuilder;
13
14use crate::{
15 events::{EventEmitter, OrderUpdated, PositionDecreased, PositionIncreased, TradeData},
16 states::{
17 callback::CallbackAuthority,
18 common::{
19 action::{Action, ActionExt, ActionParams, On},
20 swap::SwapActionParamsExt,
21 },
22 market::{
23 revertible::{
24 market::RevertibleMarket,
25 revertible_position::RevertiblePosition,
26 swap_market::{SwapDirection, SwapMarkets},
27 Revertible, Revision,
28 },
29 utils::{Adl, ClosableMarket, ValidateMarketBalances},
30 },
31 order::{Order, OrderActionParams, OrderKind, OrderTokenAccounts, TransferOut},
32 position::PositionKind,
33 user::UserHeader,
34 AmountKey, HasMarketMeta, Market, MarketPriceOptions, NonceBytes, Oracle, Position, Store,
35 ValidateOracleTime,
36 },
37 CoreError, ModelError,
38};
39
40use super::{
41 execution_fee::TransferExecutionFeeOperation,
42 market::{MarketTransferOutOperation, RemainingAccountsForMarket},
43};
44
45pub use gmsol_utils::order::PositionCutKind;
46
47#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug, InitSpace)]
49pub struct CreateOrderParams {
50 pub kind: OrderKind,
52 pub decrease_position_swap_type: Option<DecreasePositionSwapType>,
54 pub execution_lamports: u64,
56 pub swap_path_length: u8,
58 pub initial_collateral_delta_amount: u64,
60 pub size_delta_value: u128,
62 pub is_long: bool,
64 pub is_collateral_long: bool,
66 pub min_output: Option<u128>,
68 pub trigger_price: Option<u128>,
70 pub acceptable_price: Option<u128>,
72 pub should_unwrap_native_token: bool,
74 pub valid_from_ts: Option<i64>,
76}
77
78impl ActionParams for CreateOrderParams {
79 fn execution_lamports(&self) -> u64 {
80 self.execution_lamports
81 }
82}
83
84impl CreateOrderParams {
85 pub fn to_position_kind(&self) -> Result<PositionKind> {
87 if self.kind.is_swap() {
88 return err!(CoreError::PositionItNotRequired);
89 }
90 if self.is_long {
91 Ok(PositionKind::Long)
92 } else {
93 Ok(PositionKind::Short)
94 }
95 }
96
97 pub fn collateral_token<'a>(&'a self, meta: &'a impl HasMarketMeta) -> &'a Pubkey {
99 let meta = meta.market_meta();
100 if self.is_collateral_long {
101 &meta.long_token_mint
102 } else {
103 &meta.short_token_mint
104 }
105 }
106}
107
108#[derive(TypedBuilder)]
110pub(crate) struct CreateOrderOperation<'a, 'info> {
111 order: AccountLoader<'info, Order>,
112 market: AccountLoader<'info, Market>,
113 store: AccountLoader<'info, Store>,
114 owner: AccountInfo<'info>,
115 receiver: AccountInfo<'info>,
116 #[builder(
117 default,
118 setter(
119 strip_option,
120 doc = "Set the creator of this order. CHECK: It must be the address deriving the order account",
121 )
122 )]
123 creator: Option<AccountInfo<'info>>,
124 nonce: &'a NonceBytes,
125 bump: u8,
126 params: &'a CreateOrderParams,
127 swap_path: &'info [AccountInfo<'info>],
128 callback_version: Option<u8>,
129 callback_authority: Option<&'a Account<'info, CallbackAuthority>>,
130 callback_program: Option<&'a AccountInfo<'info>>,
131 callback_shared_data_account: Option<&'a AccountInfo<'info>>,
132 callback_partitioned_data_account: Option<&'a AccountInfo<'info>>,
133 #[builder(default)]
134 allow_closed: bool,
135 #[builder(setter(into))]
136 event_emitter: Option<EventEmitter<'a, 'info>>,
137}
138
139impl<'a, 'info> CreateOrderOperation<'a, 'info> {
140 pub(crate) fn swap(
141 self,
142 ) -> CreateSwapOrderOperationBuilder<'a, 'info, ((CreateOrderOperation<'a, 'info>,), (), ())>
143 {
144 CreateSwapOrderOperation::builder().common(self)
145 }
146
147 pub(crate) fn increase(
148 self,
149 ) -> CreateIncreaseOrderOperationBuilder<
150 'a,
151 'info,
152 ((CreateOrderOperation<'a, 'info>,), (), (), (), ()),
153 > {
154 CreateIncreaseOrderOperation::builder().common(self)
155 }
156
157 pub(crate) fn decrease(
158 self,
159 ) -> CreateDecreaseOrderOperationBuilder<
160 'a,
161 'info,
162 ((CreateOrderOperation<'a, 'info>,), (), (), (), ()),
163 > {
164 CreateDecreaseOrderOperation::builder().common(self)
165 }
166
167 fn validate(&self) -> Result<()> {
168 self.market
169 .load()?
170 .validate_with_options(&self.store.key(), self.allow_closed)?;
171 ActionExt::validate_balance(&self.order, self.params.execution_lamports)?;
172 Ok(())
173 }
174
175 #[inline(never)]
176 fn init_with(
177 &self,
178 f: impl FnOnce(
179 &CreateOrderParams,
180 &mut OrderTokenAccounts,
181 &mut OrderActionParams,
182 ) -> Result<(Pubkey, Pubkey)>,
183 position: Option<&AccountInfo<'info>>,
184 ) -> Result<()> {
185 let id = self.market.load_mut()?.indexer_mut().next_order_id()?;
186 {
187 let mut order = self.order.load_init()?;
188 let Order {
189 header,
190 market_token,
191 tokens,
192 params,
193 swap,
194 ..
195 } = &mut *order;
196
197 header.init(
198 id,
199 self.store.key(),
200 self.market.key(),
201 self.owner.key(),
202 self.receiver.key(),
203 *self.nonce,
204 self.bump,
205 self.params.execution_lamports,
206 self.params.should_unwrap_native_token,
207 )?;
208
209 if let Some(creator) = self.creator.as_ref() {
210 header.unchecked_set_creator(creator.key());
211 }
212
213 *market_token = self.market.load()?.meta().market_token_mint;
214
215 let (from, to) = (f)(self.params, tokens, params)?;
216
217 let market = self.market.load()?;
218 let meta = market.meta();
219 let swap_path = self.swap_path;
220 swap.validate_and_init(
222 meta,
223 self.params.swap_path_length,
224 0,
225 swap_path,
226 &self.store.key(),
227 (&from, &from),
228 (&to, &from),
229 )?;
230 }
231 self.handle_created(position)
232 }
233
234 #[inline(never)]
235 fn handle_created(&self, position: Option<&AccountInfo<'info>>) -> Result<()> {
236 self.order.exit(&crate::ID)?;
238 if let Some(version) = self.callback_version.as_ref() {
239 require_eq!(*version, 0, {
240 msg!("[Callback] orders currently support only callback version `0`");
241 CoreError::InvalidArgument
242 });
243
244 let authority = self
245 .callback_authority
246 .as_ref()
247 .ok_or_else(|| error!(CoreError::InvalidArgument))?;
248 let program = self
249 .callback_program
250 .as_ref()
251 .ok_or_else(|| error!(CoreError::InvalidArgument))?;
252 let shared_data = self
253 .callback_shared_data_account
254 .as_ref()
255 .ok_or_else(|| error!(CoreError::InvalidArgument))?;
256 let partitioned_data = self
257 .callback_partitioned_data_account
258 .as_ref()
259 .ok_or_else(|| error!(CoreError::InvalidArgument))?;
260 let position = position.unwrap_or(program);
261
262 self.order.load_mut()?.header.set_general_callback(
263 program.key,
264 *version,
265 shared_data.key,
266 partitioned_data.key,
267 )?;
268
269 self.order.load()?.header.invoke_general_callback(
270 On::Created(ActionKind::Order),
271 authority,
272 program,
273 shared_data,
274 partitioned_data,
275 &self.owner,
276 self.order.as_ref(),
277 &[position.clone()],
278 )?;
279 }
280
281 if let Some(event_emitter) = self.event_emitter {
282 event_emitter.emit_cpi(&OrderUpdated::new(
283 true,
284 &self.order.key(),
285 &*self.order.load()?,
286 )?)?;
287 }
288
289 Ok(())
290 }
291}
292
293#[derive(TypedBuilder)]
295pub(crate) struct CreateSwapOrderOperation<'a, 'info> {
296 common: CreateOrderOperation<'a, 'info>,
297 swap_in_token: &'a Account<'info, TokenAccount>,
298 swap_out_token: &'a Account<'info, TokenAccount>,
299}
300
301impl CreateSwapOrderOperation<'_, '_> {
302 pub(crate) fn execute(self) -> Result<()> {
303 self.common.validate()?;
304 self.validate_params_excluding_swap()?;
305
306 self.common.init_with(
307 |create, tokens, params| {
308 tokens.initial_collateral.init(self.swap_in_token);
309 tokens.final_output_token.init(self.swap_out_token);
310 params.init_swap(
311 create.kind,
312 self.swap_out_token.mint,
313 create.initial_collateral_delta_amount,
314 create.min_output,
315 create.valid_from_ts,
316 )?;
317 Ok((self.swap_in_token.mint, self.swap_out_token.mint))
318 },
319 None,
320 )?;
321 Ok(())
322 }
323
324 fn validate_params_excluding_swap(&self) -> Result<()> {
325 require!(self.common.params.kind.is_swap(), CoreError::Internal);
326 require!(
327 self.common.params.initial_collateral_delta_amount != 0,
328 CoreError::EmptyOrder
329 );
330 require_gte!(
331 self.swap_in_token.amount,
332 self.common.params.initial_collateral_delta_amount,
333 CoreError::NotEnoughTokenAmount
334 );
335 require!(
336 self.common
337 .market
338 .load()?
339 .meta()
340 .is_collateral_token(&self.swap_out_token.mint),
341 CoreError::TokenMintMismatched
342 );
343 Ok(())
344 }
345}
346
347#[derive(TypedBuilder)]
349pub(crate) struct CreateIncreaseOrderOperation<'a, 'info> {
350 common: CreateOrderOperation<'a, 'info>,
351 position: &'a AccountLoader<'info, Position>,
352 initial_collateral_token: &'a Account<'info, TokenAccount>,
353 long_token: &'a Account<'info, TokenAccount>,
354 short_token: &'a Account<'info, TokenAccount>,
355}
356
357impl CreateIncreaseOrderOperation<'_, '_> {
358 pub(crate) fn execute(self) -> Result<()> {
359 self.common.validate()?;
360 self.validate_params_excluding_swap()?;
361
362 let collateral_token = if self.common.params.is_collateral_long {
363 self.common.market.load()?.meta().long_token_mint
364 } else {
365 self.common.market.load()?.meta().short_token_mint
366 };
367
368 self.common.init_with(
369 |create, tokens, params| {
370 tokens
371 .initial_collateral
372 .init(self.initial_collateral_token);
373 tokens.long_token.init(self.long_token);
374 tokens.short_token.init(self.short_token);
375 params.init_increase(
376 create.is_long,
377 create.kind,
378 self.position.key(),
379 collateral_token,
380 create.initial_collateral_delta_amount,
381 create.size_delta_value,
382 create.trigger_price,
383 create.acceptable_price,
384 create.min_output,
385 create.valid_from_ts,
386 )?;
387 Ok((self.initial_collateral_token.mint, collateral_token))
388 },
389 Some(self.position.as_ref()),
390 )?;
391
392 Ok(())
393 }
394
395 fn validate_params_excluding_swap(&self) -> Result<()> {
396 require!(
397 self.common.params.kind.is_increase_position(),
398 CoreError::Internal
399 );
400 require!(
401 self.common.params.size_delta_value != 0
402 || self.common.params.initial_collateral_delta_amount != 0,
403 CoreError::EmptyOrder
404 );
405 require_gte!(
406 self.initial_collateral_token.amount,
407 self.common.params.initial_collateral_delta_amount,
408 CoreError::NotEnoughTokenAmount
409 );
410
411 {
412 let market = self.common.market.load()?;
413 require_keys_eq!(
414 market.meta().long_token_mint,
415 self.long_token.mint,
416 CoreError::TokenMintMismatched
417 );
418 require_keys_eq!(
419 market.meta().short_token_mint,
420 self.short_token.mint,
421 CoreError::TokenMintMismatched
422 );
423 self.position
424 .load()?
425 .validate_for_market(&market, self.common.allow_closed)
426 .map_err(ModelError::from)?;
427 }
428
429 Ok(())
430 }
431}
432
433#[derive(TypedBuilder)]
435pub(crate) struct CreateDecreaseOrderOperation<'a, 'info> {
436 common: CreateOrderOperation<'a, 'info>,
437 position: &'a AccountLoader<'info, Position>,
438 final_output_token: &'a Account<'info, TokenAccount>,
439 long_token: &'a Account<'info, TokenAccount>,
440 short_token: &'a Account<'info, TokenAccount>,
441}
442
443impl CreateDecreaseOrderOperation<'_, '_> {
444 pub(crate) fn execute(self) -> Result<()> {
445 self.common.validate()?;
446 self.validate_params_excluding_swap()?;
447
448 let collateral_token = if self.common.params.is_collateral_long {
449 self.common.market.load()?.meta().long_token_mint
450 } else {
451 self.common.market.load()?.meta().short_token_mint
452 };
453
454 self.common.init_with(
455 |create, tokens, params| {
456 tokens.final_output_token.init(self.final_output_token);
457 tokens.long_token.init(self.long_token);
458 tokens.short_token.init(self.short_token);
459 params.init_decrease(
460 create.is_long,
461 create.kind,
462 self.position.key(),
463 collateral_token,
464 create.initial_collateral_delta_amount,
465 create.size_delta_value,
466 create.trigger_price,
467 create.acceptable_price,
468 create.min_output,
469 create.decrease_position_swap_type.unwrap_or_default(),
470 create.valid_from_ts,
471 )?;
472 Ok((collateral_token, self.final_output_token.mint))
473 },
474 Some(self.position.as_ref()),
475 )?;
476 Ok(())
477 }
478
479 fn validate_params_excluding_swap(&self) -> Result<()> {
480 require!(
481 self.common.params.kind.is_decrease_position(),
482 CoreError::Internal
483 );
484
485 require!(
488 self.common.params.size_delta_value != 0
489 || self.common.params.initial_collateral_delta_amount != 0
490 || self.common.params.kind.is_market_decrease(),
491 CoreError::EmptyOrder
492 );
493
494 {
495 let market = self.common.market.load()?;
496 require_keys_eq!(
497 market.meta().long_token_mint,
498 self.long_token.mint,
499 CoreError::TokenMintMismatched
500 );
501 require_keys_eq!(
502 market.meta().short_token_mint,
503 self.short_token.mint,
504 CoreError::TokenMintMismatched
505 );
506 self.position
507 .load()?
508 .validate_for_market(&market, self.common.allow_closed)
509 .map_err(ModelError::from)?;
510 }
511 Ok(())
512 }
513}
514
515#[derive(TypedBuilder)]
517pub(crate) struct ProcessTransferOutOperation<'a, 'info> {
518 token_program: AccountInfo<'info>,
519 store: &'a AccountLoader<'info, Store>,
520 market: &'a AccountLoader<'info, Market>,
521 is_pnl_token_long_token: bool,
522 #[builder(default, setter(strip_option))]
523 final_output_market: Option<&'a AccountLoader<'info, Market>>,
524 final_output_token: Option<&'a Account<'info, Mint>>,
525 final_output_token_account: Option<AccountInfo<'info>>,
526 final_output_token_vault: Option<&'a Account<'info, TokenAccount>>,
527 long_token: Option<&'a Account<'info, Mint>>,
528 long_token_account: Option<AccountInfo<'info>>,
529 long_token_vault: Option<&'a Account<'info, TokenAccount>>,
530 short_token: Option<&'a Account<'info, Mint>>,
531 short_token_account: Option<AccountInfo<'info>>,
532 short_token_vault: Option<&'a Account<'info, TokenAccount>>,
533 pub(crate) claimable_long_token_account_for_user: Option<AccountInfo<'info>>,
534 pub(crate) claimable_short_token_account_for_user: Option<AccountInfo<'info>>,
535 pub(crate) claimable_pnl_token_account_for_holding: Option<AccountInfo<'info>>,
536 transfer_out: &'a TransferOut,
537 allow_closed: bool,
538 #[builder(setter(into))]
539 event_emitter: EventEmitter<'a, 'info>,
540}
541
542impl<'info> ProcessTransferOutOperation<'_, 'info> {
543 pub(crate) fn execute(self) -> Result<()> {
544 let TransferOut {
545 final_output_token,
546 secondary_output_token,
547 long_token,
548 short_token,
549 long_token_for_claimable_account_of_user,
550 short_token_for_claimable_account_of_user,
551 long_token_for_claimable_account_of_holding,
552 short_token_for_claimable_account_of_holding,
553 ..
554 } = self.transfer_out;
555
556 if *final_output_token != 0 {
557 let (token, market, vault, account) = self.final_output()?;
558 MarketTransferOutOperation::builder()
559 .store(self.store)
560 .market(market)
561 .amount(*final_output_token)
562 .to(account.clone())
563 .vault(vault.to_account_info())
564 .decimals(token.decimals)
565 .token_mint(token.to_account_info())
566 .token_program(self.token_program.clone())
567 .allow_closed(self.allow_closed)
568 .event_emitter(self.event_emitter)
569 .build()
570 .execute()?;
571 }
572
573 let (long_token_amount, short_token_amount) = if self.is_pnl_token_long_token {
574 (
575 secondary_output_token
576 .checked_add(*long_token)
577 .ok_or(error!(CoreError::TokenAmountOverflow))?,
578 *short_token,
579 )
580 } else {
581 (
582 *long_token,
583 secondary_output_token
584 .checked_add(*short_token)
585 .ok_or(error!(CoreError::TokenAmountOverflow))?,
586 )
587 };
588
589 if long_token_amount != 0 {
590 let (token, vault, account) = self.long_token()?;
591 MarketTransferOutOperation::builder()
592 .store(self.store)
593 .token_program(self.token_program.clone())
594 .market(self.market)
595 .amount(long_token_amount)
596 .vault(vault.to_account_info())
597 .decimals(token.decimals)
598 .token_mint(token.to_account_info())
599 .to(account.clone())
600 .allow_closed(self.allow_closed)
601 .event_emitter(self.event_emitter)
602 .build()
603 .execute()?;
604 }
605
606 if short_token_amount != 0 {
607 let (token, vault, account) = self.short_token()?;
608 MarketTransferOutOperation::builder()
609 .store(self.store)
610 .token_program(self.token_program.clone())
611 .market(self.market)
612 .amount(short_token_amount)
613 .vault(vault.to_account_info())
614 .decimals(token.decimals)
615 .token_mint(token.to_account_info())
616 .to(account.clone())
617 .allow_closed(self.allow_closed)
618 .event_emitter(self.event_emitter)
619 .build()
620 .execute()?;
621 }
622
623 if *long_token_for_claimable_account_of_user != 0 {
624 let (token, vault, account) = self.claimable_long_token_account_for_user()?;
625 MarketTransferOutOperation::builder()
626 .store(self.store)
627 .token_program(self.token_program.clone())
628 .market(self.market)
629 .amount(*long_token_for_claimable_account_of_user)
630 .vault(vault.to_account_info())
631 .decimals(token.decimals)
632 .token_mint(token.to_account_info())
633 .to(account.clone())
634 .allow_closed(self.allow_closed)
635 .event_emitter(self.event_emitter)
636 .build()
637 .execute()?;
638 }
639
640 if *short_token_for_claimable_account_of_user != 0 {
641 let (token, vault, account) = self.claimable_short_token_account_for_user()?;
642 MarketTransferOutOperation::builder()
643 .store(self.store)
644 .token_program(self.token_program.clone())
645 .market(self.market)
646 .amount(*short_token_for_claimable_account_of_user)
647 .vault(vault.to_account_info())
648 .decimals(token.decimals)
649 .token_mint(token.to_account_info())
650 .to(account.clone())
651 .allow_closed(self.allow_closed)
652 .event_emitter(self.event_emitter)
653 .build()
654 .execute()?;
655 }
656
657 if *long_token_for_claimable_account_of_holding != 0 {
658 let (token, vault, account) = self.claimable_long_token_account_for_holding()?;
659 MarketTransferOutOperation::builder()
660 .store(self.store)
661 .token_program(self.token_program.clone())
662 .market(self.market)
663 .amount(*long_token_for_claimable_account_of_holding)
664 .vault(vault.to_account_info())
665 .decimals(token.decimals)
666 .token_mint(token.to_account_info())
667 .to(account.clone())
668 .allow_closed(self.allow_closed)
669 .event_emitter(self.event_emitter)
670 .build()
671 .execute()?;
672 }
673
674 if *short_token_for_claimable_account_of_holding != 0 {
675 let (token, vault, account) = self.claimable_short_token_account_for_holding()?;
676 MarketTransferOutOperation::builder()
677 .store(self.store)
678 .token_program(self.token_program.clone())
679 .market(self.market)
680 .amount(*short_token_for_claimable_account_of_holding)
681 .vault(vault.to_account_info())
682 .decimals(token.decimals)
683 .token_mint(token.to_account_info())
684 .to(account.clone())
685 .event_emitter(self.event_emitter)
686 .allow_closed(self.allow_closed)
687 .build()
688 .execute()?;
689 }
690 Ok(())
691 }
692
693 #[allow(clippy::type_complexity)]
694 fn final_output(
695 &self,
696 ) -> Result<(
697 &Account<'info, Mint>,
698 &AccountLoader<'info, Market>,
699 &Account<'info, TokenAccount>,
700 &AccountInfo<'info>,
701 )> {
702 let token = self
703 .final_output_token
704 .ok_or(error!(CoreError::TokenMintNotProvided))?;
705 let market = self
706 .final_output_market
707 .ok_or(error!(CoreError::MarketMismatched))?;
708 let vault = self
709 .final_output_token_vault
710 .as_ref()
711 .ok_or(error!(CoreError::TokenAccountNotProvided))?;
712 let account = self
713 .final_output_token_account
714 .as_ref()
715 .ok_or(error!(CoreError::TokenAccountNotProvided))?;
716 Ok((token, market, vault, account))
717 }
718
719 fn long_token(
720 &self,
721 ) -> Result<(
722 &Account<'info, Mint>,
723 &Account<'info, TokenAccount>,
724 &AccountInfo<'info>,
725 )> {
726 let token = self
727 .long_token
728 .ok_or(error!(CoreError::TokenMintNotProvided))?;
729 let vault = self
730 .long_token_vault
731 .as_ref()
732 .ok_or(error!(CoreError::TokenAccountNotProvided))?;
733 let account = self
734 .long_token_account
735 .as_ref()
736 .ok_or(error!(CoreError::TokenAccountNotProvided))?;
737 Ok((token, vault, account))
738 }
739
740 fn short_token(
741 &self,
742 ) -> Result<(
743 &Account<'info, Mint>,
744 &Account<'info, TokenAccount>,
745 &AccountInfo<'info>,
746 )> {
747 let token = self
748 .short_token
749 .ok_or(error!(CoreError::TokenMintNotProvided))?;
750 let vault = self
751 .short_token_vault
752 .as_ref()
753 .ok_or(error!(CoreError::TokenAccountNotProvided))?;
754 let account = self
755 .short_token_account
756 .as_ref()
757 .ok_or(error!(CoreError::TokenAccountNotProvided))?;
758 Ok((token, vault, account))
759 }
760
761 fn claimable_long_token_account_for_user(
762 &self,
763 ) -> Result<(
764 &Account<'info, Mint>,
765 &Account<'info, TokenAccount>,
766 &AccountInfo<'info>,
767 )> {
768 let token = self
769 .long_token
770 .ok_or(error!(CoreError::TokenMintNotProvided))?;
771 let vault = self
772 .long_token_vault
773 .as_ref()
774 .ok_or(error!(CoreError::TokenAccountNotProvided))?;
775 let account = self
776 .claimable_long_token_account_for_user
777 .as_ref()
778 .ok_or(error!(CoreError::TokenAccountNotProvided))?;
779 Ok((token, vault, account))
780 }
781
782 fn claimable_short_token_account_for_user(
783 &self,
784 ) -> Result<(
785 &Account<'info, Mint>,
786 &Account<'info, TokenAccount>,
787 &AccountInfo<'info>,
788 )> {
789 let token = self
790 .short_token
791 .ok_or(error!(CoreError::TokenMintNotProvided))?;
792 let vault = self
793 .short_token_vault
794 .as_ref()
795 .ok_or(error!(CoreError::TokenAccountNotProvided))?;
796 let account = self
797 .claimable_short_token_account_for_user
798 .as_ref()
799 .ok_or(error!(CoreError::TokenAccountNotProvided))?;
800 Ok((token, vault, account))
801 }
802
803 fn claimable_long_token_account_for_holding(
804 &self,
805 ) -> Result<(
806 &Account<'info, Mint>,
807 &Account<'info, TokenAccount>,
808 &AccountInfo<'info>,
809 )> {
810 let token = self
811 .long_token
812 .ok_or(error!(CoreError::TokenMintNotProvided))?;
813 let vault = self
814 .long_token_vault
815 .as_ref()
816 .ok_or(error!(CoreError::TokenAccountNotProvided))?;
817 let account = self
818 .claimable_pnl_token_account_for_holding
819 .as_ref()
820 .ok_or(error!(CoreError::TokenAccountNotProvided))?;
821 Ok((token, vault, account))
822 }
823
824 fn claimable_short_token_account_for_holding(
825 &self,
826 ) -> Result<(
827 &Account<'info, Mint>,
828 &Account<'info, TokenAccount>,
829 &AccountInfo<'info>,
830 )> {
831 let token = self
832 .short_token
833 .ok_or(error!(CoreError::TokenMintNotProvided))?;
834 let vault = self
835 .short_token_vault
836 .as_ref()
837 .ok_or(error!(CoreError::TokenAccountNotProvided))?;
838 let account = self
839 .claimable_pnl_token_account_for_holding
840 .as_ref()
841 .ok_or(error!(CoreError::TokenAccountNotProvided))?;
842 Ok((token, vault, account))
843 }
844}
845
846#[derive(TypedBuilder)]
848pub(crate) struct ExecuteOrderOperation<'a, 'info> {
849 executor: AccountInfo<'info>,
850 user: &'a AccountLoader<'info, UserHeader>,
851 store: &'a AccountLoader<'info, Store>,
852 market: &'a AccountLoader<'info, Market>,
853 order: &'a AccountLoader<'info, Order>,
854 owner: AccountInfo<'info>,
855 position: Option<&'a AccountLoader<'info, Position>>,
856 event: Option<&'a AccountLoader<'info, TradeData>>,
857 oracle: &'a Oracle,
858 remaining_accounts: &'info [AccountInfo<'info>],
859 throw_on_execution_error: bool,
860 #[builder(default)]
861 refund: u64,
862 #[builder(setter(into))]
863 event_emitter: EventEmitter<'a, 'info>,
864 callback_authority: Option<&'a Account<'info, CallbackAuthority>>,
865 callback_program: Option<&'a AccountInfo<'info>>,
866 callback_shared_data_account: Option<&'a AccountInfo<'info>>,
867 callback_partitioned_data_account: Option<&'a AccountInfo<'info>>,
868 #[builder(default)]
869 allow_closed: bool,
870}
871
872pub(crate) type RemovePosition = bool;
873pub(crate) type ShouldSendTradeEvent = bool;
874
875enum SecondaryOrderType {
876 Liquidation,
877 AutoDeleveraging,
878}
879
880impl ExecuteOrderOperation<'_, '_> {
881 fn price_options(&self) -> MarketPriceOptions {
882 MarketPriceOptions {
883 allow_index_closed: self.allow_closed,
884 allow_long_closed: false,
885 allow_short_closed: false,
886 }
887 }
888
889 fn prices(&self) -> Result<Prices<u128>> {
890 self.oracle
891 .market_prices_with_options(&*self.market.load()?, self.price_options())
892 }
893
894 #[inline(never)]
895 pub(crate) fn execute(
896 self,
897 ) -> Result<(RemovePosition, Box<TransferOut>, ShouldSendTradeEvent)> {
898 let mut remove_position = false;
899
900 self.order.load()?.validate_valid_from_ts()?;
901
902 match self.validate_oracle() {
903 Ok(()) => {}
904 Err(CoreError::OracleTimestampsAreLargerThanRequired)
905 if !self.throw_on_execution_error =>
906 {
907 msg!(
908 "Order expired at {}",
909 self.oracle_updated_before()
910 .ok()
911 .flatten()
912 .expect("must have an expiration time"),
913 );
914 return Ok((false, Box::new(TransferOut::new_failed()), false));
916 }
917 Err(err) => {
918 return Err(error!(err));
919 }
920 }
921
922 let mut should_throw_error = false;
923 let prices = self.prices()?;
924 let discount = self.validate_and_get_order_fee_discount()?;
925 let res = match self.perform_execution(&mut should_throw_error, prices, discount) {
926 Ok((should_remove_position, mut transfer_out, should_send_trade_event)) => {
927 transfer_out.set_executed(true);
928 remove_position = should_remove_position;
929 Ok((transfer_out, should_send_trade_event))
930 }
931 Err(err) if !(should_throw_error || self.throw_on_execution_error) => {
932 msg!("Execute order error: {}", err);
933 remove_position = self
934 .position
935 .as_ref()
936 .map(|a| Result::Ok(a.load()?.state.is_empty()))
937 .transpose()?
938 .unwrap_or(false);
939 Ok((Default::default(), false))
940 }
941 Err(err) => Err(err),
942 };
943
944 let (transfer_out, should_send_trade_event) = res?;
945
946 self.handle_executed(
947 transfer_out.executed(),
948 !remove_position,
949 should_send_trade_event,
950 )?;
951
952 let keep_position_account = self.order.load()?.params().should_keep_position_account();
953 if remove_position && !keep_position_account {
954 self.close_position()?;
955 }
956
957 Ok((remove_position, transfer_out, should_send_trade_event))
958 }
959
960 #[inline(never)]
961 fn validate_and_get_order_fee_discount(&self) -> Result<u128> {
962 require!(
963 self.user.load()?.is_initialized(),
964 CoreError::InvalidUserAccount
965 );
966 let (rank, is_referred) = {
967 let user = self.user.load()?;
968 (user.gt.rank(), user.referral.referrer().is_some())
969 };
970 let discount_factor = self
971 .store
972 .load()?
973 .order_fee_discount_factor(rank, is_referred)?;
974 msg!(
975 "[Order] apply a {} order fee discount (factor) for this {} rank {} user",
976 discount_factor,
977 if is_referred {
978 "referred"
979 } else {
980 "non-referred"
981 },
982 rank,
983 );
984 Ok(discount_factor)
985 }
986
987 #[inline(never)]
988 fn perform_execution(
989 &self,
990 should_throw_error: &mut bool,
991 prices: Prices<u128>,
992 order_fee_discount_factor: u128,
993 ) -> Result<(RemovePosition, Box<TransferOut>, ShouldSendTradeEvent)> {
994 self.validate_market()?;
995 self.validate_order(should_throw_error, &prices)?;
996
997 let gt_minting_enabled = self.market.load()?.is_gt_minting_enabled();
999 let current_market_token = self.market.load()?.market_meta().market_token_mint;
1000
1001 let remaining_accounts = RemainingAccountsForMarket::new(
1002 self.remaining_accounts,
1003 current_market_token,
1004 Some(self.order.load()?.swap()),
1005 )?;
1006 let virtual_inventories = remaining_accounts.load_virtual_inventories()?;
1007
1008 let mut market =
1009 RevertibleMarket::new(self.market, Some(&virtual_inventories), self.event_emitter)?
1010 .with_order_fee_discount_factor(order_fee_discount_factor);
1011 let mut swap_markets = SwapMarkets::new(
1012 &self.store.key(),
1013 remaining_accounts.swap_market_loaders(),
1014 Some(¤t_market_token),
1015 &virtual_inventories,
1016 self.event_emitter,
1017 )?;
1018 let mut transfer_out = Box::default();
1019
1020 market.update_fees_state(&prices)?;
1021
1022 let kind = self.order.load()?.params.kind()?;
1023 let mut should_send_trade_event = false;
1024 let should_remove_position = match &kind {
1025 OrderKind::MarketSwap | OrderKind::LimitSwap => {
1026 execute_swap(
1027 should_throw_error,
1028 self.oracle,
1029 &mut market,
1030 &mut swap_markets,
1031 &mut transfer_out,
1032 &mut *self.order.load_mut()?,
1033 )?;
1034 market.commit();
1035 false
1036 }
1037 OrderKind::MarketIncrease
1038 | OrderKind::MarketDecrease
1039 | OrderKind::Liquidation
1040 | OrderKind::AutoDeleveraging
1041 | OrderKind::LimitIncrease
1042 | OrderKind::LimitDecrease
1043 | OrderKind::StopLossDecrease => {
1044 let position_loader = self
1045 .position
1046 .as_ref()
1047 .ok_or(error!(CoreError::PositionIsRequired))?;
1048 let event_loader = self
1049 .event
1050 .as_ref()
1051 .ok_or(error!(CoreError::EventBufferNotProvided))?;
1052 {
1053 let position = position_loader.load()?;
1054 let mut event = event_loader.load_mut()?;
1055 let is_collateral_long = market
1056 .market_meta()
1057 .to_token_side(&position.collateral_token)
1058 .map_err(CoreError::from)?;
1059 event.init(
1060 kind.is_increase_position(),
1061 is_collateral_long,
1062 position_loader.key(),
1063 &position,
1064 self.order.key(),
1065 )?;
1066 should_send_trade_event = true;
1067 }
1068 let mut position =
1069 RevertiblePosition::new(market, position_loader, self.allow_closed)?;
1070
1071 position.on_validate().map_err(ModelError::from)?;
1072
1073 let (should_remove_position, paid_fee_value) = match kind {
1074 OrderKind::MarketIncrease | OrderKind::LimitIncrease => {
1075 let paid_fee_value = execute_increase_position(
1076 self.oracle,
1077 prices,
1078 &mut position,
1079 &mut swap_markets,
1080 &mut transfer_out,
1081 &mut *event_loader.load_mut()?,
1082 &mut *self.order.load_mut()?,
1083 )?;
1084 (false, paid_fee_value)
1085 }
1086 OrderKind::Liquidation => execute_decrease_position(
1087 self.oracle,
1088 prices,
1089 &mut position,
1090 &mut swap_markets,
1091 &mut transfer_out,
1092 &mut *event_loader.load_mut()?,
1093 &mut *self.order.load_mut()?,
1094 true,
1095 Some(SecondaryOrderType::Liquidation),
1096 )?,
1097 OrderKind::AutoDeleveraging => execute_decrease_position(
1098 self.oracle,
1099 prices,
1100 &mut position,
1101 &mut swap_markets,
1102 &mut transfer_out,
1103 &mut *event_loader.load_mut()?,
1104 &mut *self.order.load_mut()?,
1105 true,
1106 Some(SecondaryOrderType::AutoDeleveraging),
1107 )?,
1108 OrderKind::MarketDecrease
1109 | OrderKind::LimitDecrease
1110 | OrderKind::StopLossDecrease => execute_decrease_position(
1111 self.oracle,
1112 prices,
1113 &mut position,
1114 &mut swap_markets,
1115 &mut transfer_out,
1116 &mut *event_loader.load_mut()?,
1117 &mut *self.order.load_mut()?,
1118 false,
1119 None,
1120 )?,
1121 _ => unreachable!(),
1122 };
1123
1124 position.write_to_event(&mut *event_loader.load_mut()?)?;
1125 event_loader
1126 .load_mut()?
1127 .update_with_transfer_out(&transfer_out)?;
1128
1129 if gt_minting_enabled {
1130 self.order.load_mut()?.unchecked_process_gt(
1131 &mut *self.store.load_mut()?,
1132 &mut *self.user.load_mut()?,
1133 paid_fee_value,
1134 position.event_emitter(),
1135 )?;
1136 } else {
1137 msg!("[GT] GT minting is disabled for this market");
1138 }
1139
1140 position.commit();
1141 msg!(
1142 "[Position] executed with trade_id={}",
1143 self.position
1144 .as_ref()
1145 .unwrap()
1146 .load()
1147 .unwrap()
1148 .state
1149 .trade_id
1150 );
1151 should_remove_position
1152 }
1153 _ => return err!(CoreError::UnknownOrderKind),
1154 };
1155 swap_markets.commit();
1156 virtual_inventories.commit();
1157 Ok((
1158 should_remove_position,
1159 transfer_out,
1160 should_send_trade_event,
1161 ))
1162 }
1163
1164 fn close_position(&self) -> Result<()> {
1165 let Some(position) = self.position else {
1166 return err!(CoreError::PositionIsRequired);
1167 };
1168
1169 let balance = position.to_account_info().lamports();
1170
1171 if balance < self.refund {
1172 msg!(
1173 "Warn: not enough balance to pay the executor, balance = {}, refund = {}",
1174 balance,
1175 self.refund,
1176 );
1177 }
1178
1179 let refund_to_owner = balance.saturating_sub(self.refund);
1180 let refund_to_executor = balance.checked_sub(refund_to_owner).expect("must success");
1181
1182 position.close(self.order.to_account_info())?;
1185
1186 if refund_to_owner != 0 {
1187 self.order.sub_lamports(refund_to_owner)?;
1188 self.owner.add_lamports(refund_to_owner)?;
1189 }
1190
1191 if refund_to_executor != 0 {
1192 self.order.sub_lamports(refund_to_executor)?;
1193 self.executor.add_lamports(refund_to_executor)?;
1194 }
1195
1196 Ok(())
1197 }
1198
1199 fn validate_oracle(&self) -> crate::CoreResult<()> {
1200 self.oracle.validate_time(self)?;
1201 let (kind, is_long) = {
1202 let order = self.order.load().map_err(|_| CoreError::LoadAccountError)?;
1203 (
1204 order
1205 .params
1206 .kind()
1207 .map_err(|_| CoreError::InvalidArgument)?,
1208 order
1209 .params
1210 .side()
1211 .map_err(|_| CoreError::InvalidArgument)?
1212 .is_long(),
1213 )
1214 };
1215 if let OrderKind::AutoDeleveraging = kind {
1217 let max_staleness = *self
1218 .store
1219 .load()
1220 .map_err(|_| CoreError::LoadAccountError)?
1221 .get_amount_by_key(AmountKey::AdlPricesMaxStaleness)
1222 .ok_or(CoreError::Unimplemented)?;
1223 self.market
1224 .load()
1225 .map_err(|_| CoreError::LoadAccountError)?
1226 .validate_adl(self.oracle, is_long, max_staleness)?;
1227 };
1228 if self.allow_closed {
1230 let store = self.store.load().map_err(|_| CoreError::LoadAccountError)?;
1231 self.market
1232 .load()
1233 .map_err(|_| CoreError::LoadAccountError)?
1234 .validate_open_or_nonstale_oracle_with_store(self.oracle, &store)?;
1235 }
1236 Ok(())
1237 }
1238
1239 fn validate_market(&self) -> Result<()> {
1240 self.market
1241 .load()?
1242 .validate_with_options(&self.store.key(), self.allow_closed)?;
1243 Ok(())
1244 }
1245
1246 fn validate_order(&self, should_throw_error: &mut bool, prices: &Prices<u128>) -> Result<()> {
1247 self.validate_non_empty_order()?;
1248 match self.validate_trigger_price(prices) {
1249 Ok(()) => Ok(()),
1250 Err(err) => {
1251 if !self.order.load()?.params.kind()?.is_market() {
1252 *should_throw_error = true;
1253 }
1254 Err(err)
1255 }
1256 }
1257 }
1258
1259 fn validate_non_empty_order(&self) -> Result<()> {
1260 let order = self.order.load()?;
1261 let params = &order.params;
1262 let kind = params.kind()?;
1263
1264 if kind.is_increase_position() || kind.is_decrease_position() {
1265 require!(
1268 params.size_delta_value != 0
1269 || params.initial_collateral_delta_amount != 0
1270 || kind.is_market_decrease(),
1271 CoreError::EmptyOrder
1272 );
1273 } else if kind.is_swap() {
1274 require!(
1275 params.initial_collateral_delta_amount != 0,
1276 CoreError::EmptyOrder
1277 );
1278 } else {
1279 unreachable!()
1280 }
1281 Ok(())
1282 }
1283
1284 fn validate_trigger_price(&self, prices: &Prices<u128>) -> Result<()> {
1285 self.order
1286 .load()?
1287 .validate_trigger_price(&prices.index_token_price)
1288 }
1289
1290 #[inline(never)]
1291 fn handle_executed(
1292 &self,
1293 success: bool,
1294 may_have_position: bool,
1295 has_trade_event: bool,
1296 ) -> Result<()> {
1297 match self.order.load()?.header.callback_kind()? {
1298 ActionCallbackKind::Disabled => {}
1299 ActionCallbackKind::General => {
1300 let authority = self.callback_authority.as_ref().ok_or_else(|| {
1301 msg!("[Callback] callback is specified, but required accounts are missing");
1302 error!(CoreError::InvalidArgument)
1303 })?;
1304 let program = self
1305 .callback_program
1306 .ok_or_else(|| error!(CoreError::InvalidArgument))?;
1307 let shared_data = self
1308 .callback_shared_data_account
1309 .ok_or_else(|| error!(CoreError::InvalidArgument))?;
1310 let partitioned_data = self
1311 .callback_partitioned_data_account
1312 .ok_or_else(|| error!(CoreError::InvalidArgument))?;
1313 let position = (may_have_position)
1314 .then_some(())
1315 .and_then(|_| self.position.as_ref().map(|p| p.as_ref()))
1316 .unwrap_or(program);
1317 let trade_event = (has_trade_event)
1318 .then_some(())
1319 .and_then(|_| self.event.as_ref().map(|a| a.as_ref()))
1320 .unwrap_or(program);
1321
1322 self.order.load()?.header.invoke_general_callback(
1323 On::Executed(ActionKind::Order, success),
1324 authority,
1325 program,
1326 shared_data,
1327 partitioned_data,
1328 &self.owner,
1329 self.order.as_ref(),
1330 &[position.clone(), trade_event.clone()],
1331 )?;
1332 }
1333 kind => {
1334 msg!("[Callback] unsupported callback kind: {}", kind);
1335 }
1336 }
1337 Ok(())
1338 }
1339}
1340
1341impl ValidateOracleTime for ExecuteOrderOperation<'_, '_> {
1342 fn oracle_updated_after(&self) -> crate::CoreResult<Option<i64>> {
1343 let (kind, updated_at, valid_from_ts) = {
1344 let order = self.order.load().map_err(|_| CoreError::LoadAccountError)?;
1345 (
1346 order
1347 .params()
1348 .kind()
1349 .map_err(|_| CoreError::InvalidArgument)?,
1350 order.header.updated_at,
1351 order.params().valid_from_ts,
1352 )
1353 };
1354
1355 match kind {
1356 OrderKind::MarketSwap | OrderKind::MarketIncrease => Ok(Some(updated_at)),
1357 OrderKind::MarketDecrease => {
1358 let position = self
1359 .position
1360 .as_ref()
1361 .ok_or(CoreError::PositionIsRequired)?
1362 .load()
1363 .map_err(|_| CoreError::LoadAccountError)?;
1364 Ok(Some(updated_at.max(position.state.increased_at)))
1365 }
1366 OrderKind::LimitSwap | OrderKind::LimitIncrease => {
1367 Ok(Some(updated_at.max(valid_from_ts)))
1368 }
1369 OrderKind::LimitDecrease | OrderKind::StopLossDecrease => {
1370 let position = self
1371 .position
1372 .as_ref()
1373 .ok_or(CoreError::PositionIsRequired)?
1374 .load()
1375 .map_err(|_| CoreError::LoadAccountError)?;
1376 let last_updated = updated_at.max(position.state.increased_at);
1377 Ok(Some(last_updated.max(valid_from_ts)))
1378 }
1379 OrderKind::Liquidation => {
1380 let position = self
1381 .position
1382 .as_ref()
1383 .ok_or(CoreError::PositionIsRequired)?
1384 .load()
1385 .map_err(|_| CoreError::LoadAccountError)?;
1386 Ok(Some(
1387 position.state.increased_at.max(position.state.decreased_at),
1388 ))
1389 }
1390 OrderKind::AutoDeleveraging => Ok(None),
1392 _ => Err(CoreError::UnknownOrderKind),
1393 }
1394 }
1395
1396 fn oracle_updated_before(&self) -> crate::CoreResult<Option<i64>> {
1397 let (kind, updated_at) = {
1398 let order = self.order.load().map_err(|_| CoreError::LoadAccountError)?;
1399 (
1400 order
1401 .params
1402 .kind()
1403 .map_err(|_| CoreError::InvalidArgument)?,
1404 order.header().updated_at,
1405 )
1406 };
1407 let ts = match kind {
1408 OrderKind::MarketSwap | OrderKind::MarketIncrease | OrderKind::MarketDecrease => {
1409 Some(updated_at)
1410 }
1411 _ => None,
1412 };
1413 ts.map(|ts| {
1414 self.store
1415 .load()
1416 .map_err(|_| CoreError::LoadAccountError)?
1417 .request_expiration_at(ts)
1418 })
1419 .transpose()
1420 }
1421
1422 fn oracle_updated_after_slot(&self) -> crate::CoreResult<Option<u64>> {
1423 let (kind, updated_at_slot) = {
1424 let order = self.order.load().map_err(|_| CoreError::LoadAccountError)?;
1425 (
1426 order
1427 .params
1428 .kind()
1429 .map_err(|_| CoreError::InvalidArgument)?,
1430 order.header().updated_at_slot,
1431 )
1432 };
1433 let after = match kind {
1434 OrderKind::Liquidation | OrderKind::AutoDeleveraging => None,
1435 _ => Some(updated_at_slot),
1436 };
1437 Ok(after)
1438 }
1439}
1440
1441#[inline(never)]
1442fn execute_swap(
1443 should_throw_error: &mut bool,
1444 oracle: &Oracle,
1445 market: &mut RevertibleMarket<'_, '_>,
1446 swap_markets: &mut SwapMarkets<'_, '_>,
1447 transfer_out: &mut TransferOut,
1448 order: &mut Order,
1449) -> Result<()> {
1450 let swap_out_token = order
1451 .tokens
1452 .final_output_token
1453 .token()
1454 .ok_or(error!(CoreError::MissingFinalOutputToken))?;
1455 let swap_out_amount = {
1457 let swap = &order.swap;
1458 let initial_collateral_token = order
1459 .tokens
1460 .initial_collateral
1461 .token()
1462 .ok_or(error!(CoreError::MissingInitialCollateralToken))?;
1463 let amount = order.params.initial_collateral_delta_amount;
1464 let (swap_out_amount, _) = swap_markets.revertible_swap(
1465 SwapDirection::Into(market),
1466 oracle,
1467 swap,
1468 (swap_out_token, swap_out_token),
1469 (Some(initial_collateral_token), None),
1470 (amount, 0),
1471 )?;
1472 swap_out_amount
1473 };
1474 if let Err(err) = order.validate_output_amount(swap_out_amount.into()) {
1475 if !order.params.kind()?.is_market() {
1476 *should_throw_error = true;
1477 }
1478 return Err(err);
1479 }
1480 transfer_out.transfer_out(false, swap_out_amount)?;
1481 Ok(())
1482}
1483
1484#[inline(never)]
1485fn execute_increase_position(
1486 oracle: &Oracle,
1487 prices: Prices<u128>,
1488 position: &mut RevertiblePosition<'_, '_>,
1489 swap_markets: &mut SwapMarkets<'_, '_>,
1490 transfer_out: &mut TransferOut,
1491 event: &mut TradeData,
1492 order: &mut Order,
1493) -> Result<u128> {
1494 let params = &order.params;
1495
1496 let collateral_increment_amount = {
1498 let initial_collateral_token = order
1499 .tokens
1500 .initial_collateral
1501 .token()
1502 .ok_or(error!(CoreError::MissingInitialCollateralToken))?;
1503 let swap = &order.swap;
1504 let collateral_token = *position.collateral_token();
1505 let (collateral_increment_amount, _) = swap_markets.revertible_swap(
1506 SwapDirection::Into(position.market_mut()),
1507 oracle,
1508 swap,
1509 (collateral_token, collateral_token),
1510 (Some(initial_collateral_token), None),
1511 (params.initial_collateral_delta_amount, 0),
1512 )?;
1513 collateral_increment_amount
1514 };
1515
1516 order.validate_output_amount(collateral_increment_amount.into())?;
1520
1521 let (long_amount, short_amount, paid_order_fee_value) = {
1523 let size_delta_usd = params.size_delta_value;
1524 let acceptable_price = params.acceptable_price;
1525 let report = position
1526 .increase(
1527 prices,
1528 collateral_increment_amount.into(),
1529 size_delta_usd,
1530 Some(acceptable_price),
1531 )
1532 .and_then(|a| a.execute())
1533 .map_err(ModelError::from)?;
1534
1535 let (&long_amount, &short_amount) = report.claimable_funding_amounts();
1536 let paid_fee_value = *report.fees().paid_order_and_borrowing_fee_value();
1537 event.update_with_increase_report(&report)?;
1538
1539 position
1540 .event_emitter()
1541 .emit_cpi(&PositionIncreased::from_report(
1542 position.market().rev(),
1543 position.market().market_meta().market_token_mint,
1544 report,
1545 ))?;
1546 msg!("[Position] increased");
1547
1548 (long_amount, short_amount, paid_fee_value)
1549 };
1550
1551 transfer_out.transfer_out_funding_amounts(&long_amount, &short_amount)?;
1553
1554 position.market().validate_market_balances(
1555 long_amount
1556 .try_into()
1557 .map_err(|_| error!(CoreError::TokenAmountOverflow))?,
1558 short_amount
1559 .try_into()
1560 .map_err(|_| error!(CoreError::TokenAmountOverflow))?,
1561 )?;
1562
1563 Ok(paid_order_fee_value)
1564}
1565
1566#[allow(clippy::too_many_arguments)]
1567#[inline(never)]
1568fn execute_decrease_position(
1569 oracle: &Oracle,
1570 prices: Prices<u128>,
1571 position: &mut RevertiblePosition<'_, '_>,
1572 swap_markets: &mut SwapMarkets<'_, '_>,
1573 transfer_out: &mut TransferOut,
1574 event: &mut TradeData,
1575 order: &mut Order,
1576 is_insolvent_close_allowed: bool,
1577 secondary_order_type: Option<SecondaryOrderType>,
1578) -> Result<(RemovePosition, u128)> {
1579 let report = {
1581 let params = &order.params;
1582 let decrease_position_swap_type = params.decrease_position_swap_type()?;
1583 let collateral_withdrawal_amount = params.initial_collateral_delta_amount as u128;
1584 let size_delta_usd = params.size_delta_value;
1585 let acceptable_price = params.acceptable_price;
1586 let is_liquidation_order =
1587 matches!(secondary_order_type, Some(SecondaryOrderType::Liquidation));
1588 let is_adl_order = matches!(
1589 secondary_order_type,
1590 Some(SecondaryOrderType::AutoDeleveraging)
1591 );
1592
1593 let is_cap_size_delta_usd_allowed = matches!(
1594 order.params().kind()?,
1595 OrderKind::LimitDecrease | OrderKind::StopLossDecrease
1596 );
1597
1598 let mut pnl_factor_before_execution = None;
1600
1601 if is_liquidation_order {
1603 require_gte!(
1604 size_delta_usd,
1605 *position.size_in_usd(),
1606 CoreError::InvalidArgument
1607 );
1608 }
1609
1610 if is_adl_order {
1612 let Some(pnl_factor) = position
1613 .market()
1614 .pnl_factor_exceeded(&prices, PnlFactorKind::ForAdl, params.side()?.is_long())
1615 .map_err(ModelError::from)?
1616 .map(|exceeded| exceeded.pnl_factor)
1617 else {
1618 return err!(CoreError::AdlNotRequired);
1619 };
1620 pnl_factor_before_execution = Some(pnl_factor);
1621 }
1622
1623 let report = position
1624 .decrease(
1625 prices,
1626 size_delta_usd,
1627 Some(acceptable_price),
1628 collateral_withdrawal_amount,
1629 DecreasePositionFlags {
1630 is_insolvent_close_allowed,
1631 is_liquidation_order,
1632 is_cap_size_delta_usd_allowed,
1633 },
1634 )
1635 .map(|a| a.set_swap(decrease_position_swap_type))
1636 .and_then(|a| a.execute())
1637 .map_err(ModelError::from)?;
1638
1639 if is_adl_order {
1641 let pnl_factor_after_execution = position
1642 .market()
1643 .pnl_factor(&prices, params.side()?.is_long(), true)
1644 .map_err(ModelError::from)?;
1645 require_gt!(
1646 pnl_factor_before_execution.expect("must be some"),
1647 pnl_factor_after_execution,
1648 CoreError::InvalidAdl
1649 );
1650 let min_pnl_factor = position
1651 .market()
1652 .pnl_factor_config(PnlFactorKind::MinAfterAdl, params.side()?.is_long())
1653 .and_then(|factor| factor.to_signed())
1654 .map_err(ModelError::from)?;
1655 require_gte!(
1656 pnl_factor_after_execution,
1657 min_pnl_factor,
1658 CoreError::InvalidAdl
1659 );
1660 }
1661
1662 event.update_with_decrease_report(&report, &prices)?;
1663 report
1664 };
1665 let should_remove_position = report.should_remove();
1666
1667 {
1669 require!(
1670 *report.secondary_output_amount() == 0
1671 || (report.is_output_token_long() != report.is_secondary_output_token_long()),
1672 CoreError::SameOutputTokensNotMerged,
1673 );
1674 let (is_output_token_long, output_amount, secondary_output_amount) = (
1675 report.is_output_token_long(),
1676 (*report.output_amount())
1677 .try_into()
1678 .map_err(|_| error!(CoreError::TokenAmountOverflow))?,
1679 (*report.secondary_output_amount())
1680 .try_into()
1681 .map_err(|_| error!(CoreError::TokenAmountOverflow))?,
1682 );
1683
1684 let meta = *position.market().market_meta();
1686 let token_ins = if is_output_token_long {
1687 (Some(meta.long_token_mint), Some(meta.short_token_mint))
1688 } else {
1689 (Some(meta.short_token_mint), Some(meta.long_token_mint))
1690 };
1691
1692 let final_output_token = order
1696 .tokens
1697 .final_output_token
1698 .token()
1699 .ok_or(error!(CoreError::MissingFinalOutputToken))?;
1700 let secondary_output_token = order.secondary_output_token()?;
1701 let swap = &order.swap;
1702 let (output_amount, secondary_output_amount) = swap_markets.revertible_swap(
1703 SwapDirection::From(position.market_mut()),
1704 oracle,
1705 swap,
1706 (final_output_token, secondary_output_token),
1707 token_ins,
1708 (output_amount, secondary_output_amount),
1709 )?;
1710 order.validate_decrease_output_amounts(
1711 oracle,
1712 &final_output_token,
1713 output_amount,
1714 &secondary_output_token,
1715 secondary_output_amount,
1716 )?;
1717 transfer_out.transfer_out(false, output_amount)?;
1718 transfer_out.transfer_out(true, secondary_output_amount)?;
1719 event.set_final_output_token(&final_output_token);
1720 }
1721
1722 {
1724 let (long_amount, short_amount) = report.claimable_funding_amounts();
1725 transfer_out.transfer_out_funding_amounts(long_amount, short_amount)?;
1726 transfer_out.process_claimable_collateral_for_decrease(&report)?;
1727 }
1728
1729 let mut long_transfer_out = transfer_out.total_long_token_amount()?;
1731 let mut short_transfer_out = transfer_out.total_short_token_amount()?;
1732 let mut add_to_amount = |is_long_token: bool, amount: u64| {
1733 let acc = if is_long_token {
1734 &mut long_transfer_out
1735 } else {
1736 &mut short_transfer_out
1737 };
1738 *acc = acc
1739 .checked_add(amount)
1740 .ok_or(error!(CoreError::TokenAmountOverflow))?;
1741 Result::Ok(())
1742 };
1743 let current_market_token = position.market().key();
1744 let meta = position.market().market_meta();
1745 let tokens = &order.tokens;
1746 let output_token_market = order
1747 .swap
1748 .last_market_token(true)
1749 .unwrap_or(¤t_market_token);
1750 let secondary_token_market = order
1751 .swap
1752 .last_market_token(false)
1753 .unwrap_or(¤t_market_token);
1754 if transfer_out.final_output_token != 0 && *output_token_market == current_market_token {
1755 (add_to_amount)(
1756 meta.to_token_side(
1757 tokens
1758 .final_output_token
1759 .token()
1760 .as_ref()
1761 .ok_or(error!(CoreError::MissingFinalOutputToken))?,
1762 )
1763 .map_err(CoreError::from)?,
1764 transfer_out.final_output_token,
1765 )?;
1766 }
1767 if transfer_out.secondary_output_token != 0 && *secondary_token_market == current_market_token {
1768 (add_to_amount)(
1769 order.params.side()?.is_long(),
1770 transfer_out.secondary_output_token,
1771 )?;
1772 }
1773 position
1774 .market()
1775 .validate_market_balances(long_transfer_out, short_transfer_out)?;
1776
1777 let paid_fee_value = *report.fees().paid_order_and_borrowing_fee_value();
1778
1779 msg!("[Position] decreased");
1780 position
1781 .event_emitter()
1782 .emit_cpi(&PositionDecreased::from_report(
1783 position.market().rev(),
1784 position.market().market_meta().market_token_mint,
1785 report,
1786 ))?;
1787
1788 Ok((should_remove_position, paid_fee_value))
1789}
1790
1791#[derive(TypedBuilder)]
1793pub struct PositionCutOperation<'a, 'info> {
1794 kind: PositionCutKind,
1795 #[builder(setter(
1796 doc = "Set the executor of this operation. CHECK: the address of the `order` must be derived from its address"
1797 ))]
1798 executor: AccountInfo<'info>,
1799 position: &'a AccountLoader<'info, Position>,
1800 event: &'a AccountLoader<'info, TradeData>,
1801 order: &'a AccountLoader<'info, Order>,
1802 market: &'a AccountLoader<'info, Market>,
1803 store: &'a AccountLoader<'info, Store>,
1804 oracle: &'a Oracle,
1805 owner: AccountInfo<'info>,
1806 user: &'a AccountLoader<'info, UserHeader>,
1807 nonce: &'a NonceBytes,
1808 order_bump: u8,
1809 long_token_mint: &'a Account<'info, Mint>,
1810 short_token_mint: &'a Account<'info, Mint>,
1811 long_token_account: &'a Account<'info, TokenAccount>,
1812 short_token_account: &'a Account<'info, TokenAccount>,
1813 long_token_vault: &'a Account<'info, TokenAccount>,
1814 short_token_vault: &'a Account<'info, TokenAccount>,
1815 claimable_long_token_account_for_user: AccountInfo<'info>,
1816 claimable_short_token_account_for_user: AccountInfo<'info>,
1817 claimable_pnl_token_account_for_holding: AccountInfo<'info>,
1818 token_program: AccountInfo<'info>,
1819 system_program: AccountInfo<'info>,
1820 refund: u64,
1821 should_unwrap_native_token: bool,
1822 allow_closed: bool,
1823 #[builder(setter(into))]
1824 event_emitter: EventEmitter<'a, 'info>,
1825 remaining_accounts: &'info [AccountInfo<'info>],
1826}
1827
1828impl PositionCutOperation<'_, '_> {
1829 pub(crate) fn execute(self) -> Result<ShouldSendTradeEvent> {
1830 let (size_in_usd, is_long, is_collateral_long) = {
1831 let position = self.position.load()?;
1832 let market = self.market.load()?;
1833 let is_collateral_token_long = market
1834 .meta
1835 .to_token_side(&position.collateral_token)
1836 .map_err(CoreError::from)?;
1837 (
1838 position.state.size_in_usd,
1839 position.try_is_long()?,
1840 is_collateral_token_long,
1841 )
1842 };
1843 self.create_order(size_in_usd, is_long, is_collateral_long)?;
1844 let (is_position_removed, transfer_out, should_send_trade_event) = self.execute_order()?;
1845 require!(transfer_out.executed(), CoreError::Internal);
1846 self.order.load_mut()?.header.completed()?;
1847 if is_position_removed {
1848 msg!("[Position] the position is removed");
1849 } else {
1850 msg!(
1851 "[Position] the position is not removed, setting the rent receiver to the executor"
1852 );
1853 self.order
1854 .load_mut()?
1855 .header
1856 .set_rent_receiver(self.executor.key());
1857 }
1858 self.process_transfer_out(&transfer_out, is_long, is_collateral_long)?;
1859 Ok(should_send_trade_event)
1860 }
1861
1862 #[inline(never)]
1863 fn create_order(
1864 &self,
1865 size_in_usd: u128,
1866 is_long: bool,
1867 is_collateral_long: bool,
1868 ) -> Result<()> {
1869 TransferExecutionFeeOperation::builder()
1870 .payment(self.order.to_account_info())
1871 .payer(self.executor.to_account_info())
1872 .execution_lamports(Order::MIN_EXECUTION_LAMPORTS)
1873 .system_program(self.system_program.to_account_info())
1874 .build()
1875 .execute()?;
1876 let params = CreateOrderParams {
1877 kind: self.kind.to_order_kind(),
1878 decrease_position_swap_type: Some(DecreasePositionSwapType::PnlTokenToCollateralToken),
1879 execution_lamports: Order::MIN_EXECUTION_LAMPORTS,
1880 swap_path_length: 0,
1881 initial_collateral_delta_amount: 0,
1882 size_delta_value: self.kind.size_delta_usd(size_in_usd),
1883 is_long,
1884 is_collateral_long,
1885 min_output: None,
1886 trigger_price: None,
1887 acceptable_price: None,
1888 should_unwrap_native_token: self.should_unwrap_native_token,
1889 valid_from_ts: None,
1890 };
1891 let output_token_account = if is_collateral_long {
1892 self.long_token_account
1893 } else {
1894 self.short_token_account
1895 };
1896 CreateOrderOperation::builder()
1897 .order(self.order.clone())
1898 .market(self.market.clone())
1899 .store(self.store.clone())
1900 .owner(self.owner.clone())
1901 .receiver(self.owner.clone())
1902 .creator(self.executor.clone())
1903 .nonce(self.nonce)
1904 .bump(self.order_bump)
1905 .params(¶ms)
1906 .swap_path(&[])
1907 .callback_version(None)
1908 .callback_authority(None)
1909 .callback_program(None)
1910 .callback_shared_data_account(None)
1911 .callback_partitioned_data_account(None)
1912 .allow_closed(self.allow_closed)
1913 .event_emitter(self.event_emitter)
1914 .build()
1915 .decrease()
1916 .position(self.position)
1917 .final_output_token(output_token_account)
1918 .long_token(self.long_token_account)
1919 .short_token(self.short_token_account)
1920 .build()
1921 .execute()?;
1922 self.order.exit(&crate::ID)?;
1924 Ok(())
1925 }
1926
1927 #[inline(never)]
1928 fn execute_order(&self) -> Result<(RemovePosition, Box<TransferOut>, ShouldSendTradeEvent)> {
1929 ExecuteOrderOperation::builder()
1930 .store(self.store)
1931 .market(self.market)
1932 .order(self.order)
1933 .owner(self.owner.clone())
1934 .user(self.user)
1935 .position(Some(self.position))
1936 .event(Some(self.event))
1937 .oracle(self.oracle)
1938 .remaining_accounts(self.remaining_accounts)
1939 .throw_on_execution_error(true)
1940 .refund(self.refund)
1941 .executor(self.executor.clone())
1942 .event_emitter(self.event_emitter)
1943 .callback_authority(None)
1944 .callback_program(None)
1945 .callback_shared_data_account(None)
1946 .callback_partitioned_data_account(None)
1947 .allow_closed(self.allow_closed)
1948 .build()
1949 .execute()
1950 }
1951
1952 #[inline(never)]
1953 fn process_transfer_out(
1954 &self,
1955 transfer_out: &TransferOut,
1956 is_long: bool,
1957 is_collateral_long: bool,
1958 ) -> Result<()> {
1959 let (output_token, output_token_account, output_token_vault) = if is_collateral_long {
1960 (
1961 self.long_token_mint,
1962 self.long_token_account,
1963 self.long_token_vault,
1964 )
1965 } else {
1966 (
1967 self.short_token_mint,
1968 self.short_token_account,
1969 self.short_token_vault,
1970 )
1971 };
1972 ProcessTransferOutOperation::builder()
1973 .token_program(self.token_program.clone())
1974 .store(self.store)
1975 .market(self.market)
1976 .is_pnl_token_long_token(is_long)
1977 .final_output_market(self.market)
1978 .final_output_token(Some(output_token))
1979 .final_output_token_account(Some(output_token_account.to_account_info()))
1980 .final_output_token_vault(Some(output_token_vault))
1981 .long_token(Some(self.long_token_mint))
1982 .long_token_account(Some(self.long_token_account.to_account_info()))
1983 .long_token_vault(Some(self.long_token_vault))
1984 .short_token(Some(self.short_token_mint))
1985 .short_token_account(Some(self.short_token_account.to_account_info()))
1986 .short_token_vault(Some(self.short_token_vault))
1987 .claimable_long_token_account_for_user(Some(
1988 self.claimable_long_token_account_for_user.clone(),
1989 ))
1990 .claimable_short_token_account_for_user(Some(
1991 self.claimable_short_token_account_for_user.clone(),
1992 ))
1993 .claimable_pnl_token_account_for_holding(Some(
1994 self.claimable_pnl_token_account_for_holding.clone(),
1995 ))
1996 .transfer_out(transfer_out)
1997 .allow_closed(self.allow_closed)
1998 .event_emitter(self.event_emitter)
1999 .build()
2000 .execute()?;
2001 Ok(())
2002 }
2003}