1use std::ops::Deref;
2
3use anchor_lang::prelude::*;
4use anchor_spl::{
5 associated_token::AssociatedToken,
6 token::{Mint, Token, TokenAccount},
7};
8use gmsol_utils::InitSpace;
9
10use crate::{
11 check_delegation, constants,
12 events::{EventEmitter, TradeData, TradeEventRef},
13 get_pnl_token,
14 ops::{
15 execution_fee::PayExecutionFeeOperation,
16 order::{PositionCutKind, PositionCutOperation},
17 },
18 states::{
19 common::action::ActionExt,
20 feature::{ActionDisabledFlag, DomainDisabledFlag},
21 order::Order,
22 user::UserHeader,
23 Chainlink, HasMarketMeta, Market, NonceBytes, Oracle, Position, Seed, Store,
24 TokenMapHeader,
25 },
26 utils::internal,
27 validated_recent_timestamp, CoreError,
28};
29
30#[event_cpi]
66#[derive(Accounts)]
67#[instruction(nonce: [u8; 32], recent_timestamp: i64)]
68pub struct PositionCut<'info> {
69 #[account(mut)]
71 pub authority: Signer<'info>,
72 #[account(mut)]
75 pub owner: UncheckedAccount<'info>,
76 #[account(
78 mut,
79 constraint = user.load()?.is_initialized() @ CoreError::InvalidUserAccount,
80 has_one = owner,
81 has_one = store,
82 seeds = [UserHeader::SEED, store.key().as_ref(), owner.key().as_ref()],
83 bump = user.load()?.bump,
84 )]
85 pub user: AccountLoader<'info, UserHeader>,
86 #[account(mut, has_one = token_map)]
88 pub store: AccountLoader<'info, Store>,
89 #[account(has_one = store)]
91 pub token_map: AccountLoader<'info, TokenMapHeader>,
92 #[account(mut, has_one = store)]
94 pub oracle: AccountLoader<'info, Oracle>,
95 #[account(mut, has_one = store)]
97 pub market: AccountLoader<'info, Market>,
98 #[account(
100 init,
101 space = 8 + Order::INIT_SPACE,
102 payer = authority,
103 seeds = [Order::SEED, store.key().as_ref(), authority.key().as_ref(), &nonce],
104 bump,
105 )]
106 pub order: AccountLoader<'info, Order>,
107 #[account(
108 mut,
109 constraint = position.load()?.owner == owner.key(),
110 constraint = position.load()?.store == store.key(),
111 seeds = [
112 Position::SEED,
113 store.key().as_ref(),
114 owner.key().as_ref(),
115 position.load()?.market_token.as_ref(),
116 position.load()?.collateral_token.as_ref(),
117 &[position.load()?.kind],
118 ],
119 bump = position.load()?.bump,
120 )]
121 pub position: AccountLoader<'info, Position>,
122 #[account(mut, has_one = store, has_one = authority)]
124 pub event: AccountLoader<'info, TradeData>,
125 pub long_token: Box<Account<'info, Mint>>,
127 pub short_token: Box<Account<'info, Mint>>,
129 #[account(
131 mut,
132 associated_token::mint = long_token,
133 associated_token::authority = order,
134 )]
135 pub long_token_escrow: Box<Account<'info, TokenAccount>>,
136 #[account(
138 mut,
139 associated_token::mint = short_token,
140 associated_token::authority = order,
141 )]
142 pub short_token_escrow: Box<Account<'info, TokenAccount>>,
143 #[account(
145 mut,
146 token::mint = long_token,
147 token::authority = store,
148 seeds = [
149 constants::MARKET_VAULT_SEED,
150 store.key().as_ref(),
151 long_token_vault.mint.as_ref(),
152 ],
153 bump,
154 )]
155 pub long_token_vault: Box<Account<'info, TokenAccount>>,
156 #[account(
158 mut,
159 token::mint = short_token,
160 token::authority = store,
161 seeds = [
162 constants::MARKET_VAULT_SEED,
163 store.key().as_ref(),
164 short_token_vault.mint.as_ref(),
165 ],
166 bump,
167 )]
168 pub short_token_vault: Box<Account<'info, TokenAccount>>,
169 #[account(
170 mut,
171 token::mint = market.load()?.meta().long_token_mint,
172 token::authority = store,
173 constraint = check_delegation(&claimable_long_token_account_for_user, position.load()?.owner)?,
174 seeds = [
175 constants::CLAIMABLE_ACCOUNT_SEED,
176 store.key().as_ref(),
177 market.load()?.meta().long_token_mint.as_ref(),
178 position.load()?.owner.as_ref(),
179 &store.load()?.claimable_time_key(validated_recent_timestamp(store.load()?.deref(), recent_timestamp)?)?,
180 ],
181 bump,
182 )]
183 pub claimable_long_token_account_for_user: Box<Account<'info, TokenAccount>>,
184 #[account(
185 mut,
186 token::mint = market.load()?.meta().short_token_mint,
187 token::authority = store,
188 constraint = check_delegation(&claimable_short_token_account_for_user, position.load()?.owner)?,
189 seeds = [
190 constants::CLAIMABLE_ACCOUNT_SEED,
191 store.key().as_ref(),
192 market.load()?.meta().short_token_mint.as_ref(),
193 position.load()?.owner.as_ref(),
194 &store.load()?.claimable_time_key(validated_recent_timestamp(store.load()?.deref(), recent_timestamp)?)?,
195 ],
196 bump,
197 )]
198 pub claimable_short_token_account_for_user: Box<Account<'info, TokenAccount>>,
199 #[account(
200 mut,
201 token::mint = get_pnl_token(&Some(position.clone()), market.load()?.deref())?,
202 token::authority = store,
203 constraint = check_delegation(&claimable_pnl_token_account_for_holding, store.load()?.address.holding)?,
204 seeds = [
205 constants::CLAIMABLE_ACCOUNT_SEED,
206 store.key().as_ref(),
207 get_pnl_token(&Some(position.clone()), market.load()?.deref())?.as_ref(),
208 store.load()?.address.holding.as_ref(),
209 &store.load()?.claimable_time_key(validated_recent_timestamp(store.load()?.deref(), recent_timestamp)?)?,
210 ],
211 bump,
212 )]
213 pub claimable_pnl_token_account_for_holding: Box<Account<'info, TokenAccount>>,
214 pub system_program: Program<'info, System>,
217 pub token_program: Program<'info, Token>,
219 pub associated_token_program: Program<'info, AssociatedToken>,
221 pub chainlink_program: Option<Program<'info, Chainlink>>,
223}
224
225pub(crate) fn unchecked_process_position_cut<'info>(
227 mut ctx: Context<'_, '_, 'info, 'info, PositionCut<'info>>,
228 nonce: &NonceBytes,
229 _recent_timestamp: i64,
230 kind: PositionCutKind,
231 execution_fee: u64,
232 should_unwrap_native_token: bool,
233) -> Result<()> {
234 let accounts = &mut ctx.accounts;
235
236 {
238 let store = accounts.store.load()?;
239 let domain = match kind {
240 PositionCutKind::Liquidate => DomainDisabledFlag::Liquidation,
241 PositionCutKind::AutoDeleverage(_) => DomainDisabledFlag::AutoDeleveraging,
242 };
243 store.validate_feature_enabled(domain, ActionDisabledFlag::Create)?;
244 store.validate_feature_enabled(domain, ActionDisabledFlag::Execute)?;
245 }
246
247 let remaining_accounts = ctx.remaining_accounts;
248
249 let (tokens, is_pure_market) = {
250 let market = accounts.market.load()?;
251 let meta = market.meta();
252 (
253 meta.ordered_tokens().into_iter().collect::<Vec<_>>(),
254 meta.is_pure(),
255 )
256 };
257
258 let refund = match kind {
259 PositionCutKind::Liquidate => Order::position_cut_rent(is_pure_market, true)?,
260 PositionCutKind::AutoDeleverage(_) => Order::position_cut_rent(is_pure_market, false)?,
262 };
263
264 let event_emitter = EventEmitter::new(&accounts.event_authority, ctx.bumps.event_authority);
265
266 let ops = PositionCutOperation::builder()
267 .kind(kind)
268 .position(&accounts.position)
269 .order(&accounts.order)
270 .event(&accounts.event)
271 .market(&accounts.market)
272 .store(&accounts.store)
273 .owner(accounts.owner.to_account_info())
274 .user(&accounts.user)
275 .nonce(nonce)
276 .order_bump(ctx.bumps.order)
277 .long_token_mint(&accounts.long_token)
278 .short_token_mint(&accounts.short_token)
279 .long_token_account(&accounts.long_token_escrow)
280 .long_token_vault(&accounts.long_token_vault)
281 .short_token_account(&accounts.short_token_escrow)
282 .short_token_vault(&accounts.short_token_vault)
283 .claimable_long_token_account_for_user(
284 accounts
285 .claimable_long_token_account_for_user
286 .to_account_info(),
287 )
288 .claimable_short_token_account_for_user(
289 accounts
290 .claimable_short_token_account_for_user
291 .to_account_info(),
292 )
293 .claimable_pnl_token_account_for_holding(
294 accounts
295 .claimable_pnl_token_account_for_holding
296 .to_account_info(),
297 )
298 .token_program(accounts.token_program.to_account_info())
299 .system_program(accounts.system_program.to_account_info())
300 .executor(accounts.authority.to_account_info())
302 .refund(refund)
303 .should_unwrap_native_token(should_unwrap_native_token)
304 .event_emitter(event_emitter);
305
306 let should_send_trade_event = accounts.oracle.load_mut()?.with_prices(
307 &accounts.store,
308 &accounts.token_map,
309 &tokens,
310 remaining_accounts,
311 accounts.chainlink_program.as_ref(),
312 |oracle, _remaining_accounts| ops.oracle(oracle).build().execute(),
313 )?;
314
315 if should_send_trade_event {
316 let event_loader = accounts.event.clone();
317 let event = event_loader.load()?;
318 let event = TradeEventRef::from(&*event);
319 event_emitter.emit_cpi(&event)?;
320 }
321
322 accounts.pay_execution_fee(execution_fee)?;
323 Ok(())
324}
325
326impl<'info> internal::Authentication<'info> for PositionCut<'info> {
327 fn authority(&self) -> &Signer<'info> {
328 &self.authority
329 }
330
331 fn store(&self) -> &AccountLoader<'info, Store> {
332 &self.store
333 }
334}
335
336impl PositionCut<'_> {
337 #[inline(never)]
338 fn pay_execution_fee(&self, execution_fee: u64) -> Result<()> {
339 let execution_lamports = self.order.load()?.execution_lamports(execution_fee);
340 PayExecutionFeeOperation::builder()
341 .payer(self.order.to_account_info())
342 .receiver(self.authority.to_account_info())
343 .execution_lamports(execution_lamports)
344 .build()
345 .execute()?;
346 Ok(())
347 }
348}