phoenix/program/processor/
withdraw.rs

1use crate::{
2    program::{
3        dispatch_market::load_with_dispatch_mut,
4        error::{assert_with_msg, PhoenixError},
5        loaders::CancelOrWithdrawContext as Withdraw,
6        token_utils::try_withdraw,
7        validation::checkers::phoenix_checkers::MarketAccountInfo,
8        MarketHeader, PhoenixMarketContext, PhoenixVaultContext,
9    },
10    quantities::{BaseLots, QuoteLots, WrapperU64},
11    state::MatchingEngineResponse,
12};
13use borsh::{BorshDeserialize, BorshSerialize};
14use solana_program::{
15    account_info::AccountInfo, entrypoint::ProgramResult, log::sol_log_compute_units,
16    pubkey::Pubkey,
17};
18use std::mem::size_of;
19
20#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, BorshDeserialize, BorshSerialize)]
21pub struct WithdrawParams {
22    pub quote_lots_to_withdraw: Option<u64>,
23    pub base_lots_to_withdraw: Option<u64>,
24}
25
26pub(crate) fn process_withdraw_funds<'a, 'info>(
27    _program_id: &Pubkey,
28    market_context: &PhoenixMarketContext<'a, 'info>,
29    accounts: &'a [AccountInfo<'info>],
30    data: &[u8],
31) -> ProgramResult {
32    let Withdraw { vault_context } = Withdraw::load(market_context, accounts)?;
33    let WithdrawParams {
34        quote_lots_to_withdraw,
35        base_lots_to_withdraw,
36    } = WithdrawParams::try_from_slice(data)?;
37    let PhoenixMarketContext {
38        market_info,
39        signer: trader,
40    } = market_context;
41    process_withdraw(
42        market_info,
43        trader.as_ref().clone(),
44        vault_context,
45        quote_lots_to_withdraw,
46        base_lots_to_withdraw,
47        false,
48    )
49}
50
51#[allow(clippy::too_many_arguments)]
52pub(crate) fn process_withdraw<'a, 'info>(
53    market_info: &MarketAccountInfo<'a, 'info>,
54    trader: AccountInfo<'info>,
55    vault_context: PhoenixVaultContext<'a, 'info>,
56    quote_lots_to_withdraw: Option<u64>,
57    base_lots_to_withdraw: Option<u64>,
58    evict_seat: bool,
59) -> ProgramResult {
60    sol_log_compute_units();
61
62    let PhoenixVaultContext {
63        base_account,
64        quote_account,
65        base_vault,
66        quote_vault,
67        token_program,
68    } = vault_context;
69    let MatchingEngineResponse {
70        num_quote_lots_out,
71        num_base_lots_out,
72        ..
73    } = {
74        sol_log_compute_units();
75        let market_bytes = &mut market_info.try_borrow_mut_data()?[size_of::<MarketHeader>()..];
76        let market = load_with_dispatch_mut(&market_info.size_params, market_bytes)?.inner;
77        let response = market
78            .claim_funds(
79                trader.key,
80                quote_lots_to_withdraw.map(QuoteLots::new),
81                base_lots_to_withdraw.map(BaseLots::new),
82                evict_seat,
83            )
84            .ok_or(PhoenixError::WithdrawFundsError)?;
85        sol_log_compute_units();
86        if evict_seat {
87            assert_with_msg(
88                market.get_trader_index(trader.key).is_none(),
89                PhoenixError::EvictionError,
90                "Trader was not evicted, there are still orders on the book",
91            )?;
92        }
93        response
94    };
95    sol_log_compute_units();
96    let header = market_info.get_header()?;
97
98    try_withdraw(
99        market_info.key,
100        &header.base_params,
101        &header.quote_params,
102        token_program.as_ref(),
103        quote_account.as_ref(),
104        quote_vault,
105        base_account.as_ref(),
106        base_vault,
107        num_quote_lots_out * header.get_quote_lot_size(),
108        num_base_lots_out * header.get_base_lot_size(),
109    )?;
110
111    Ok(())
112}