1use anchor_lang::prelude::*;
2use anchor_spl::{
3 associated_token::AssociatedToken,
4 token::{transfer_checked, Mint, Token, TokenAccount, TransferChecked},
5};
6use gmsol_utils::InitSpace;
7
8use crate::{
9 events::{EventEmitter, WithdrawalCreated},
10 ops::withdrawal::{CreateWithdrawalOperation, CreateWithdrawalParams},
11 states::{
12 common::action::{Action, ActionExt},
13 feature::{ActionDisabledFlag, DomainDisabledFlag},
14 withdrawal::Withdrawal,
15 Market, NonceBytes, RoleKey, Seed, Store, StoreWalletSigner,
16 },
17 utils::{
18 internal,
19 token::{is_associated_token_account, is_associated_token_account_or_owner},
20 },
21 CoreError,
22};
23
24#[derive(Accounts)]
34#[instruction(nonce: [u8; 32])]
35pub struct CreateWithdrawal<'info> {
36 #[account(mut)]
38 pub owner: Signer<'info>,
39 pub receiver: UncheckedAccount<'info>,
42 pub store: AccountLoader<'info, Store>,
44 #[account(mut, has_one = store)]
46 pub market: AccountLoader<'info, Market>,
47 #[account(
49 init,
50 space = 8 + Withdrawal::INIT_SPACE,
51 payer = owner,
52 seeds = [Withdrawal::SEED, store.key().as_ref(), owner.key().as_ref(), &nonce],
53 bump,
54 )]
55 pub withdrawal: AccountLoader<'info, Withdrawal>,
56 #[account(constraint = market.load()?.meta().market_token_mint == market_token.key() @ CoreError::MarketTokenMintMismatched)]
58 pub market_token: Box<Account<'info, Mint>>,
59 pub final_long_token: Box<Account<'info, Mint>>,
61 pub final_short_token: Box<Account<'info, Mint>>,
63 #[account(
65 mut,
66 associated_token::mint = market_token,
67 associated_token::authority = withdrawal,
68 )]
69 pub market_token_escrow: Box<Account<'info, TokenAccount>>,
70 #[account(
72 mut,
73 associated_token::mint = final_long_token,
74 associated_token::authority = withdrawal,
75 )]
76 pub final_long_token_escrow: Box<Account<'info, TokenAccount>>,
77 #[account(
79 mut,
80 associated_token::mint = final_short_token,
81 associated_token::authority = withdrawal,
82 )]
83 pub final_short_token_escrow: Box<Account<'info, TokenAccount>>,
84 #[account(
86 mut,
87 token::mint = market_token,
88 )]
89 pub market_token_source: Box<Account<'info, TokenAccount>>,
90 pub system_program: Program<'info, System>,
92 pub token_program: Program<'info, Token>,
94 pub associated_token_program: Program<'info, AssociatedToken>,
96}
97
98impl<'info> internal::Create<'info, Withdrawal> for CreateWithdrawal<'info> {
99 type CreateParams = CreateWithdrawalParams;
100
101 fn action(&self) -> AccountInfo<'info> {
102 self.withdrawal.to_account_info()
103 }
104
105 fn payer(&self) -> AccountInfo<'info> {
106 self.owner.to_account_info()
107 }
108
109 fn system_program(&self) -> AccountInfo<'info> {
110 self.system_program.to_account_info()
111 }
112
113 fn validate(&self, _params: &Self::CreateParams) -> Result<()> {
114 self.store
115 .load()?
116 .validate_not_restarted()?
117 .validate_feature_enabled(DomainDisabledFlag::Withdrawal, ActionDisabledFlag::Create)?;
118 Ok(())
119 }
120
121 fn create_impl(
122 &mut self,
123 params: &Self::CreateParams,
124 nonce: &NonceBytes,
125 bumps: &Self::Bumps,
126 remaining_accounts: &'info [AccountInfo<'info>],
127 ) -> Result<()> {
128 self.transfer_tokens(params)?;
129 CreateWithdrawalOperation::builder()
130 .withdrawal(self.withdrawal.clone())
131 .market(self.market.clone())
132 .store(self.store.clone())
133 .owner(&self.owner)
134 .receiver(&self.receiver)
135 .nonce(nonce)
136 .bump(bumps.withdrawal)
137 .final_long_token(&self.final_long_token_escrow)
138 .final_short_token(&self.final_short_token_escrow)
139 .market_token(&self.market_token_escrow)
140 .params(params)
141 .swap_paths(remaining_accounts)
142 .build()
143 .execute()?;
144 emit!(WithdrawalCreated::new(
145 self.store.key(),
146 self.withdrawal.key(),
147 )?);
148 Ok(())
149 }
150}
151
152impl CreateWithdrawal<'_> {
153 fn transfer_tokens(&mut self, params: &CreateWithdrawalParams) -> Result<()> {
154 let amount = params.market_token_amount;
155 let source = &self.market_token_source;
156 let target = &mut self.market_token_escrow;
157 let mint = &self.market_token;
158 if amount != 0 {
159 transfer_checked(
160 CpiContext::new(
161 self.token_program.to_account_info(),
162 TransferChecked {
163 from: source.to_account_info(),
164 mint: mint.to_account_info(),
165 to: target.to_account_info(),
166 authority: self.owner.to_account_info(),
167 },
168 ),
169 amount,
170 mint.decimals,
171 )?;
172 target.reload()?;
173 }
174 Ok(())
175 }
176}
177
178#[event_cpi]
181#[derive(Accounts)]
182pub struct CloseWithdrawal<'info> {
183 pub executor: Signer<'info>,
185 pub store: AccountLoader<'info, Store>,
187 #[account(mut, seeds = [Store::WALLET_SEED, store.key().as_ref()], bump)]
189 pub store_wallet: SystemAccount<'info>,
190 #[account(mut)]
193 pub owner: UncheckedAccount<'info>,
194 #[account(mut)]
197 pub receiver: UncheckedAccount<'info>,
198 #[account(
200 constraint = withdrawal.load()?.tokens.market_token() == market_token.key() @ CoreError::MarketTokenMintMismatched
201 )]
202 pub market_token: Box<Account<'info, Mint>>,
203 #[account(constraint = withdrawal.load()?.tokens.final_long_token() == final_long_token.key() @ CoreError::TokenMintMismatched)]
205 pub final_long_token: Box<Account<'info, Mint>>,
206 #[account(constraint = withdrawal.load()?.tokens.final_short_token() == final_short_token.key() @ CoreError::TokenMintMismatched)]
208 pub final_short_token: Box<Account<'info, Mint>>,
209 #[account(
211 mut,
212 constraint = withdrawal.load()?.header.owner == owner.key() @ CoreError::OwnerMismatched,
213 constraint = withdrawal.load()?.header.receiver() == receiver.key() @ CoreError::ReceiverMismatched,
214 constraint = withdrawal.load()?.header.rent_receiver() == owner.key @ CoreError::RentReceiverMismatched,
216 constraint = withdrawal.load()?.header.store == store.key() @ CoreError::StoreMismatched,
217 constraint = withdrawal.load()?.tokens.market_token_account() == market_token_escrow.key() @ CoreError::MarketTokenAccountMismatched,
218 constraint = withdrawal.load()?.tokens.final_long_token_account() == final_long_token_escrow.key() @ CoreError::MarketTokenAccountMismatched,
219 constraint = withdrawal.load()?.tokens.final_short_token_account() == final_short_token_escrow.key() @ CoreError::MarketTokenAccountMismatched,
220 )]
221 pub withdrawal: AccountLoader<'info, Withdrawal>,
222 #[account(
224 mut,
225 associated_token::mint = market_token,
226 associated_token::authority = withdrawal,
227 )]
228 pub market_token_escrow: Box<Account<'info, TokenAccount>>,
229 #[account(
231 mut,
232 associated_token::mint = final_long_token,
233 associated_token::authority = withdrawal,
234 )]
235 pub final_long_token_escrow: Box<Account<'info, TokenAccount>>,
236 #[account(
238 mut,
239 associated_token::mint = final_short_token,
240 associated_token::authority = withdrawal,
241 )]
242 pub final_short_token_escrow: Box<Account<'info, TokenAccount>>,
243 #[account(
246 mut,
247 constraint = is_associated_token_account(market_token_ata.key, owner.key, &market_token.key()) @ CoreError::NotAnATA,
248 )]
249 pub market_token_ata: UncheckedAccount<'info>,
250 #[account(
253 mut,
254 constraint = is_associated_token_account_or_owner(final_long_token_ata.key, receiver.key, &final_long_token.key()) @ CoreError::NotAnATA,
255 )]
256 pub final_long_token_ata: UncheckedAccount<'info>,
257 #[account(
260 mut,
261 constraint = is_associated_token_account_or_owner(final_short_token_ata.key, receiver.key, &final_short_token.key()) @ CoreError::NotAnATA,
262 )]
263 pub final_short_token_ata: UncheckedAccount<'info>,
264 pub system_program: Program<'info, System>,
266 pub token_program: Program<'info, Token>,
268 pub associated_token_program: Program<'info, AssociatedToken>,
270}
271
272impl<'info> internal::Authentication<'info> for CloseWithdrawal<'info> {
273 fn authority(&self) -> &Signer<'info> {
274 &self.executor
275 }
276
277 fn store(&self) -> &AccountLoader<'info, Store> {
278 &self.store
279 }
280}
281
282impl<'info> internal::Close<'info, Withdrawal> for CloseWithdrawal<'info> {
283 fn expected_keeper_role(&self) -> &str {
284 RoleKey::ORDER_KEEPER
285 }
286
287 fn rent_receiver(&self) -> AccountInfo<'info> {
288 debug_assert!(
289 self.withdrawal.load().unwrap().header.rent_receiver() == self.owner.key,
290 "The rent receiver must have been checked to be the owner"
291 );
292 self.owner.to_account_info()
293 }
294
295 fn store_wallet_bump(&self, bumps: &Self::Bumps) -> u8 {
296 bumps.store_wallet
297 }
298
299 fn validate(&self) -> Result<()> {
300 let withdrawal = self.withdrawal.load()?;
301 if withdrawal.header.action_state()?.is_pending() {
302 self.store
303 .load()?
304 .validate_not_restarted()?
305 .validate_feature_enabled(
306 DomainDisabledFlag::Withdrawal,
307 ActionDisabledFlag::Cancel,
308 )?;
309 }
310 Ok(())
311 }
312
313 fn process(
314 &self,
315 init_if_needed: bool,
316 store_wallet_signer: &StoreWalletSigner,
317 _event_emitter: &EventEmitter<'_, 'info>,
318 ) -> Result<internal::Success> {
319 use crate::utils::token::TransferAllFromEscrowToATA;
320
321 let signer = self.withdrawal.load()?.signer();
322 let seeds = signer.as_seeds();
323
324 let builder = TransferAllFromEscrowToATA::builder()
325 .store_wallet(self.store_wallet.to_account_info())
326 .store_wallet_signer(store_wallet_signer)
327 .system_program(self.system_program.to_account_info())
328 .token_program(self.token_program.to_account_info())
329 .associated_token_program(self.associated_token_program.to_account_info())
330 .payer(self.executor.to_account_info())
331 .escrow_authority(self.withdrawal.to_account_info())
332 .escrow_authority_seeds(&seeds)
333 .init_if_needed(init_if_needed)
334 .rent_receiver(self.rent_receiver())
335 .should_unwrap_native(
336 self.withdrawal
337 .load()?
338 .header()
339 .should_unwrap_native_token(),
340 );
341
342 if !builder
344 .clone()
345 .mint(self.market_token.to_account_info())
346 .decimals(self.market_token.decimals)
347 .ata(self.market_token_ata.to_account_info())
348 .escrow(self.market_token_escrow.to_account_info())
349 .owner(self.owner.to_account_info())
350 .build()
351 .unchecked_execute()?
352 {
353 return Ok(false);
354 }
355
356 if !builder
358 .clone()
359 .mint(self.final_long_token.to_account_info())
360 .decimals(self.final_long_token.decimals)
361 .ata(self.final_long_token_ata.to_account_info())
362 .escrow(self.final_long_token_escrow.to_account_info())
363 .owner(self.receiver.to_account_info())
364 .build()
365 .unchecked_execute()?
366 {
367 return Ok(false);
368 }
369
370 if self.final_long_token_escrow.key() != self.final_short_token_escrow.key() {
371 if !builder
373 .clone()
374 .mint(self.final_short_token.to_account_info())
375 .decimals(self.final_short_token.decimals)
376 .ata(self.final_short_token_ata.to_account_info())
377 .escrow(self.final_short_token_escrow.to_account_info())
378 .owner(self.receiver.to_account_info())
379 .build()
380 .unchecked_execute()?
381 {
382 return Ok(false);
383 }
384 }
385 Ok(true)
386 }
387
388 fn event_authority(&self, bumps: &Self::Bumps) -> (AccountInfo<'info>, u8) {
389 (
390 self.event_authority.to_account_info(),
391 bumps.event_authority,
392 )
393 }
394
395 fn action(&self) -> &AccountLoader<'info, Withdrawal> {
396 &self.withdrawal
397 }
398}