manifest/program/processor/
withdraw.rs

1use 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    // Validation verifies that the mint is either base or quote.
72    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/** Transfer from base (quote) vault to base (quote) trader using SPL Token **/
134#[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")]
163/** (Summary) Transfer from base (quote) vault to base (quote) trader using SPL Token **/
164fn 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/** Transfer from base (quote) vault to base (quote) trader using SPL Token 2022 **/
177#[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// TODO: Share these with swap and deposit.
211#[cfg(feature = "certora")]
212/** (Summary) Transfer from base (quote) vault to base (quote) trader using SPL Token 2022 **/
213fn 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}