manifest/program/processor/
withdraw.rs1use std::cell::RefMut;
2
3use super::get_trader_index_with_hint;
4use crate::{
5 logs::{emit_stack, WithdrawLog},
6 program::get_mut_dynamic_account,
7 state::MarketRefMut,
8 validation::{loaders::WithdrawContext, MintAccountInfo, TokenAccountInfo, TokenProgram},
9};
10use borsh::{BorshDeserialize, BorshSerialize};
11use hypertree::DataIndex;
12use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey};
13
14#[cfg(not(feature = "certora"))]
15use {crate::market_vault_seeds_with_bump, solana_program::program::invoke_signed};
16
17#[cfg(feature = "certora")]
18use {
19 early_panic::early_panic,
20 solana_cvt::token::{spl_token_2022_transfer, spl_token_transfer},
21};
22
23#[derive(BorshDeserialize, BorshSerialize)]
24pub struct WithdrawParams {
25 pub amount_atoms: u64,
26 pub trader_index_hint: Option<DataIndex>,
27}
28
29impl WithdrawParams {
30 pub fn new(amount_atoms: u64, trader_index_hint: Option<DataIndex>) -> Self {
31 WithdrawParams {
32 amount_atoms,
33 trader_index_hint,
34 }
35 }
36}
37
38pub(crate) fn process_withdraw(
39 program_id: &Pubkey,
40 accounts: &[AccountInfo],
41 data: &[u8],
42) -> ProgramResult {
43 let params = WithdrawParams::try_from_slice(data)?;
44 process_withdraw_core(program_id, accounts, params)
45}
46
47#[cfg_attr(all(feature = "certora", not(feature = "certora-test")), early_panic)]
48pub(crate) fn process_withdraw_core(
49 _program_id: &Pubkey,
50 accounts: &[AccountInfo],
51 params: WithdrawParams,
52) -> ProgramResult {
53 let withdraw_context: WithdrawContext = WithdrawContext::load(accounts)?;
54 let WithdrawParams {
55 amount_atoms,
56 trader_index_hint,
57 } = params;
58
59 let WithdrawContext {
60 market,
61 payer,
62 trader_token,
63 vault,
64 token_program,
65 mint,
66 } = withdraw_context;
67
68 let market_data: &mut RefMut<&mut [u8]> = &mut market.try_borrow_mut_data()?;
69 let mut dynamic_account: MarketRefMut = get_mut_dynamic_account(market_data);
70
71 let is_base: bool =
73 &trader_token.try_borrow_data()?[0..32] == dynamic_account.get_base_mint().as_ref();
74
75 let mint_key: &Pubkey = if is_base {
76 dynamic_account.get_base_mint()
77 } else {
78 dynamic_account.get_quote_mint()
79 };
80
81 let bump: u8 = if is_base {
82 dynamic_account.fixed.get_base_vault_bump()
83 } else {
84 dynamic_account.fixed.get_quote_vault_bump()
85 };
86
87 if *vault.owner == spl_token_2022::id() {
88 spl_token_2022_transfer_from_vault_to_trader_fixed(
89 &token_program,
90 Some(mint),
91 mint_key,
92 &vault,
93 &trader_token,
94 amount_atoms,
95 if is_base {
96 dynamic_account.fixed.get_base_mint_decimals()
97 } else {
98 dynamic_account.fixed.get_quote_mint_decimals()
99 },
100 market.key,
101 bump,
102 )?;
103 } else {
104 spl_token_transfer_from_vault_to_trader(
105 &token_program,
106 &vault,
107 &trader_token,
108 amount_atoms,
109 market.key,
110 bump,
111 mint_key,
112 )?;
113 }
114
115 let trader_index: DataIndex =
116 get_trader_index_with_hint(trader_index_hint, &dynamic_account, &payer)?;
117 dynamic_account.withdraw(trader_index, amount_atoms, is_base)?;
118
119 emit_stack(WithdrawLog {
120 market: *market.key,
121 trader: *payer.key,
122 mint: if is_base {
123 *dynamic_account.get_base_mint()
124 } else {
125 *dynamic_account.get_quote_mint()
126 },
127 amount_atoms,
128 })?;
129
130 Ok(())
131}
132
133#[cfg(not(feature = "certora"))]
135fn spl_token_transfer_from_vault_to_trader<'a, 'info>(
136 token_program: &TokenProgram<'a, 'info>,
137 vault: &TokenAccountInfo<'a, 'info>,
138 trader_account: &TokenAccountInfo<'a, 'info>,
139 amount: u64,
140 market_key: &Pubkey,
141 vault_bump: u8,
142 mint_pubkey: &Pubkey,
143) -> ProgramResult {
144 invoke_signed(
145 &spl_token::instruction::transfer(
146 token_program.key,
147 vault.key,
148 trader_account.key,
149 vault.key,
150 &[],
151 amount,
152 )?,
153 &[
154 token_program.as_ref().clone(),
155 vault.as_ref().clone(),
156 trader_account.as_ref().clone(),
157 ],
158 market_vault_seeds_with_bump!(market_key, mint_pubkey, vault_bump),
159 )
160}
161
162#[cfg(feature = "certora")]
163fn spl_token_transfer_from_vault_to_trader<'a, 'info>(
165 _token_program: &TokenProgram<'a, 'info>,
166 vault: &TokenAccountInfo<'a, 'info>,
167 trader_account: &TokenAccountInfo<'a, 'info>,
168 amount: u64,
169 _market_key: &Pubkey,
170 _vault_bump: u8,
171 _mint_pubkey: &Pubkey,
172) -> ProgramResult {
173 spl_token_transfer(vault.info, trader_account.info, vault.info, amount)
174}
175
176#[cfg(not(feature = "certora"))]
178fn spl_token_2022_transfer_from_vault_to_trader_fixed<'a, 'info>(
179 token_program: &TokenProgram<'a, 'info>,
180 mint: Option<MintAccountInfo<'a, 'info>>,
181 mint_key: &Pubkey,
182 vault: &TokenAccountInfo<'a, 'info>,
183 trader_token: &TokenAccountInfo<'a, 'info>,
184 amount_atoms: u64,
185 decimals: u8,
186 market_key: &Pubkey,
187 bump: u8,
188) -> ProgramResult {
189 invoke_signed(
190 &spl_token_2022::instruction::transfer_checked(
191 token_program.key,
192 vault.key,
193 mint_key,
194 trader_token.key,
195 vault.key,
196 &[],
197 amount_atoms,
198 decimals,
199 )?,
200 &[
201 token_program.as_ref().clone(),
202 vault.as_ref().clone(),
203 mint.unwrap().as_ref().clone(),
204 trader_token.as_ref().clone(),
205 ],
206 market_vault_seeds_with_bump!(market_key, mint_key, bump),
207 )
208}
209
210#[cfg(feature = "certora")]
212fn spl_token_2022_transfer_from_vault_to_trader_fixed<'a, 'info>(
214 _token_program: &TokenProgram<'a, 'info>,
215 _mint: Option<MintAccountInfo<'a, 'info>>,
216 _mint_key: &Pubkey,
217 vault: &TokenAccountInfo<'a, 'info>,
218 trader_token: &TokenAccountInfo<'a, 'info>,
219 amount_atoms: u64,
220 _decimals: u8,
221 _market_key: &Pubkey,
222 _bump: u8,
223) -> ProgramResult {
224 spl_token_2022_transfer(vault.info, trader_token.info, vault.info, amount_atoms)
225}