percli_program/instructions/
withdraw.rs1use anchor_lang::prelude::*;
2use anchor_spl::token::{transfer_checked, Mint, Token, TokenAccount, TransferChecked};
3
4use crate::error::{from_risk_error, PercolatorError};
5use crate::instructions::events;
6use crate::state::{
7 engine_from_account_data, header_from_account_data, market_signer_seeds, MARKET_ACCOUNT_SIZE,
8};
9
10#[derive(Accounts)]
11pub struct Withdraw<'info> {
12 #[account(mut)]
13 pub user: Signer<'info>,
14
15 #[account(
17 mut,
18 owner = crate::ID @ PercolatorError::AccountNotFound,
19 constraint = market.data_len() >= MARKET_ACCOUNT_SIZE @ PercolatorError::AccountNotFound,
20 )]
21 pub market: UncheckedAccount<'info>,
22
23 pub mint: Account<'info, Mint>,
25
26 #[account(
28 mut,
29 constraint = user_token_account.owner == user.key(),
30 constraint = user_token_account.mint == mint.key(),
31 )]
32 pub user_token_account: Account<'info, TokenAccount>,
33
34 #[account(
36 mut,
37 seeds = [b"vault", market.key().as_ref()],
38 bump,
39 constraint = vault.mint == mint.key(),
40 )]
41 pub vault: Account<'info, TokenAccount>,
42
43 pub token_program: Program<'info, Token>,
44}
45
46pub fn handler(
47 ctx: Context<Withdraw>,
48 account_idx: u16,
49 amount: u64,
50 funding_rate: i64,
51) -> Result<()> {
52 require!(amount > 0, PercolatorError::InsufficientBalance);
53
54 let market = &ctx.accounts.market;
56 let mut data = market.try_borrow_mut_data()?;
57
58 require!(
59 &data[0..8] == b"percmrkt",
60 PercolatorError::AccountNotFound
61 );
62
63 let header = header_from_account_data(&data)?;
64 require!(header.mint == ctx.accounts.mint.key(), PercolatorError::Unauthorized);
65
66 let engine = engine_from_account_data(&mut data);
67
68 let account_owner = engine.accounts[account_idx as usize].owner;
70 require!(
71 account_owner == ctx.accounts.user.key().to_bytes(),
72 PercolatorError::Unauthorized
73 );
74
75 let oracle_price = engine.last_oracle_price;
76 let clock = Clock::get()?;
77
78 engine
80 .withdraw_not_atomic(account_idx, amount as u128, oracle_price, clock.slot, funding_rate)
81 .map_err(from_risk_error)?;
82
83 drop(data);
85
86 let bump = [header.bump];
88 let signer_seeds = market_signer_seeds(&header.authority, &bump);
89 transfer_checked(
90 CpiContext::new_with_signer(
91 ctx.accounts.token_program.key(),
92 TransferChecked {
93 from: ctx.accounts.vault.to_account_info(),
94 to: ctx.accounts.user_token_account.to_account_info(),
95 authority: ctx.accounts.market.to_account_info(),
96 mint: ctx.accounts.mint.to_account_info(),
97 },
98 &[&signer_seeds],
99 ),
100 amount,
101 ctx.accounts.mint.decimals,
102 )?;
103
104 emit!(events::Withdrawn {
105 user: ctx.accounts.user.key(),
106 account_idx,
107 amount,
108 });
109
110 Ok(())
111}