manifest/program/processor/
global_clean.rs

1use std::cell::RefMut;
2
3use borsh::{BorshDeserialize, BorshSerialize};
4use hypertree::{get_helper, trace, DataIndex, RBNode};
5use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey};
6
7use crate::{
8    program::{batch_update::MarketDataTreeNodeType, get_mut_dynamic_account},
9    quantities::{GlobalAtoms, WrapperU64},
10    require,
11    state::{utils::get_now_slot, GlobalRefMut, MarketRefMut, RestingOrder, MARKET_BLOCK_SIZE},
12    validation::loaders::{GlobalCleanContext, GlobalTradeAccounts},
13};
14
15#[derive(BorshDeserialize, BorshSerialize)]
16pub struct GlobalCleanParams {
17    pub order_index: DataIndex,
18}
19
20impl GlobalCleanParams {
21    pub fn new(order_index: DataIndex) -> Self {
22        GlobalCleanParams { order_index }
23    }
24}
25
26pub(crate) fn process_global_clean(
27    _program_id: &Pubkey,
28    accounts: &[AccountInfo],
29    data: &[u8],
30) -> ProgramResult {
31    trace!("process_global_clean accs={accounts:?}");
32    let global_clean_context: GlobalCleanContext = GlobalCleanContext::load(accounts)?;
33
34    let GlobalCleanContext {
35        payer,
36        market,
37        global,
38        system_program,
39    } = global_clean_context;
40
41    let global_trade_accounts: GlobalTradeAccounts = GlobalTradeAccounts {
42        mint_opt: None,
43        global: global.clone(),
44        global_vault_opt: None,
45        market_vault_opt: None,
46        token_program_opt: None,
47        system_program: Some(system_program),
48        market: *market.key,
49        gas_payer_opt: None,
50        gas_receiver_opt: Some(payer),
51    };
52
53    let GlobalCleanParams { order_index } = GlobalCleanParams::try_from_slice(data)?;
54
55    let market_data: &mut RefMut<&mut [u8]> = &mut market.try_borrow_mut_data()?;
56    let mut market_dynamic_account: MarketRefMut = get_mut_dynamic_account(market_data);
57
58    let global_data: &mut RefMut<&mut [u8]> = &mut global.try_borrow_mut_data()?;
59    let global_dynamic_account: GlobalRefMut = get_mut_dynamic_account(global_data);
60
61    // Get the resting order and do some checks to make sure the order index is
62    // valid and that the global account is correct.
63    let resting_order: &RestingOrder = {
64        // Sanity check on the order index
65        require!(
66            order_index % (MARKET_BLOCK_SIZE as DataIndex) == 0,
67            crate::program::ManifestError::WrongIndexHintParams,
68            "Invalid order index {}",
69            order_index,
70        )?;
71        let resting_order_node: &RBNode<RestingOrder> =
72            get_helper::<RBNode<RestingOrder>>(&market_dynamic_account.dynamic, order_index);
73        require!(
74            resting_order_node.get_payload_type() == MarketDataTreeNodeType::RestingOrder as u8,
75            crate::program::ManifestError::WrongIndexHintParams,
76            "Invalid order index {}",
77            order_index,
78        )?;
79
80        let resting_order: &RestingOrder = resting_order_node.get_value();
81        let expected_global_mint: &Pubkey = if resting_order.get_is_bid() {
82            market_dynamic_account.get_quote_mint()
83        } else {
84            market_dynamic_account.get_base_mint()
85        };
86        let global_mint: &Pubkey = global_dynamic_account.fixed.get_mint();
87
88        // Verify that the resting order uses the global account given.
89        require!(
90            *expected_global_mint == *global_mint,
91            crate::program::ManifestError::InvalidClean,
92            "Wrong global provided",
93        )?;
94
95        // Do not need to require that the order is global. This ix is useful
96        // for cleaning up markets with all expired orders.
97
98        resting_order
99    };
100    let maker_index: DataIndex = resting_order.get_trader_index();
101    let maker: &Pubkey = market_dynamic_account.get_trader_key_by_index(maker_index);
102
103    // Verify that the RestingOrder is clean eligible
104    let is_expired: bool = resting_order.is_expired(get_now_slot());
105    // Balance is zero when evicted.
106    let maker_global_balance: GlobalAtoms = global_dynamic_account.get_balance_atoms(maker);
107    let required_global_atoms: u64 = if resting_order.get_is_bid() {
108        resting_order
109            .get_num_base_atoms()
110            .checked_mul(resting_order.get_price(), false)
111            .unwrap()
112            .as_u64()
113    } else {
114        resting_order.get_num_base_atoms().as_u64()
115    };
116
117    require!(
118        is_expired || maker_global_balance.as_u64() < required_global_atoms,
119        crate::program::ManifestError::InvalidClean,
120        "Ineligible clean order index {}",
121        order_index,
122    )?;
123
124    // Do the actual clean on the market.
125    let global_trade_accounts: [Option<GlobalTradeAccounts>; 2] = if resting_order.get_is_bid() {
126        [None, Some(global_trade_accounts)]
127    } else {
128        [Some(global_trade_accounts), None]
129    };
130
131    // Should drop global, but cancel_order_by_index actually does not need to
132    // borrow in this case.
133
134    market_dynamic_account.cancel_order_by_index(order_index, &global_trade_accounts)?;
135
136    // The global account itself only accounting on remove_order is that it
137    // tracks unclaimed gas deposits for informational purposes and this is
138    // claiming anyways.
139
140    Ok(())
141}