1use std::cell::RefMut;
2
3use crate::{
4 global_vault_seeds_with_bump,
5 logs::{emit_stack, GlobalCleanupLog},
6 program::{get_mut_dynamic_account, invoke},
7 quantities::{GlobalAtoms, WrapperU64},
8 require,
9 validation::{loaders::GlobalTradeAccounts, MintAccountInfo, TokenAccountInfo, TokenProgram},
10};
11use hypertree::{DataIndex, NIL};
12#[cfg(not(feature = "no-clock"))]
13use solana_program::sysvar::Sysvar;
14use solana_program::{
15 entrypoint::ProgramResult, program::invoke_signed, program_error::ProgramError, pubkey::Pubkey,
16};
17use spl_token_2022::{
18 extension::{
19 transfer_fee::TransferFeeConfig, transfer_hook::TransferHook, BaseStateWithExtensions,
20 StateWithExtensions,
21 },
22 state::Mint,
23};
24
25use super::{
26 order_type_can_take, GlobalRefMut, OrderType, RestingOrder, GAS_DEPOSIT_LAMPORTS,
27 NO_EXPIRATION_LAST_VALID_SLOT,
28};
29
30pub fn get_now_slot() -> u32 {
31 #[cfg(feature = "no-clock")]
36 let now_slot: u64 = 0;
37 #[cfg(not(feature = "no-clock"))]
38 let now_slot: u64 = solana_program::clock::Clock::get()
39 .unwrap_or(solana_program::clock::Clock {
40 slot: u64::MAX,
41 epoch_start_timestamp: i64::MAX,
42 epoch: u64::MAX,
43 leader_schedule_epoch: u64::MAX,
44 unix_timestamp: i64::MAX,
45 })
46 .slot;
47 now_slot as u32
48}
49
50pub(crate) fn get_now_epoch() -> u64 {
51 #[cfg(feature = "no-clock")]
52 let now_epoch: u64 = 0;
53 #[cfg(not(feature = "no-clock"))]
54 let now_epoch: u64 = solana_program::clock::Clock::get()
55 .unwrap_or(solana_program::clock::Clock {
56 slot: u64::MAX,
57 epoch_start_timestamp: i64::MAX,
58 epoch: u64::MAX,
59 leader_schedule_epoch: u64::MAX,
60 unix_timestamp: i64::MAX,
61 })
62 .slot;
63 now_epoch
64}
65
66#[inline(always)]
67pub(crate) fn remove_from_global(
68 global_trade_accounts_opt: &Option<GlobalTradeAccounts>,
69) -> ProgramResult {
70 if global_trade_accounts_opt.is_none() {
71 return Ok(());
74 }
75 let global_trade_accounts: &GlobalTradeAccounts = &global_trade_accounts_opt.as_ref().unwrap();
76 let GlobalTradeAccounts {
77 global,
78 gas_receiver_opt,
79 ..
80 } = global_trade_accounts;
81
82 if global_trade_accounts.system_program.is_some() {
115 **global.lamports.borrow_mut() -= GAS_DEPOSIT_LAMPORTS;
116 **gas_receiver_opt.as_ref().unwrap().lamports.borrow_mut() += GAS_DEPOSIT_LAMPORTS;
117 }
118
119 Ok(())
120}
121
122pub(crate) fn try_to_add_to_global(
123 global_trade_accounts: &GlobalTradeAccounts,
124 resting_order: &RestingOrder,
125) -> ProgramResult {
126 let GlobalTradeAccounts {
127 global,
128 gas_payer_opt,
129 ..
130 } = global_trade_accounts;
131
132 {
133 let global_data: &mut RefMut<&mut [u8]> = &mut global.try_borrow_mut_data()?;
134 let mut global_dynamic_account: GlobalRefMut = get_mut_dynamic_account(global_data);
135 global_dynamic_account.add_order(resting_order, gas_payer_opt.as_ref().unwrap().key)?;
136 }
137
138 invoke(
146 &solana_program::system_instruction::transfer(
147 &gas_payer_opt.as_ref().unwrap().info.key,
148 &global.key,
149 GAS_DEPOSIT_LAMPORTS,
150 ),
151 &[
152 gas_payer_opt.as_ref().unwrap().info.clone(),
153 global.info.clone(),
154 ],
155 )?;
156
157 Ok(())
158}
159
160pub(crate) fn assert_can_take(order_type: OrderType) -> ProgramResult {
161 require!(
162 order_type_can_take(order_type),
163 crate::program::ManifestError::PostOnlyCrosses,
164 "Post only order would cross",
165 )?;
166 Ok(())
167}
168
169pub(crate) fn assert_not_already_expired(last_valid_slot: u32, now_slot: u32) -> ProgramResult {
170 require!(
171 last_valid_slot == NO_EXPIRATION_LAST_VALID_SLOT || last_valid_slot > now_slot,
172 crate::program::ManifestError::AlreadyExpired,
173 "Placing an already expired order. now: {} last_valid: {}",
174 now_slot,
175 last_valid_slot
176 )?;
177 Ok(())
178}
179
180pub(crate) fn assert_already_has_seat(trader_index: DataIndex) -> ProgramResult {
181 require!(
182 trader_index != NIL,
183 crate::program::ManifestError::AlreadyClaimedSeat,
184 "Need to claim a seat first",
185 )?;
186 Ok(())
187}
188
189pub(crate) fn can_back_order<'a, 'info>(
190 global_trade_accounts_opt: &'a Option<GlobalTradeAccounts<'a, 'info>>,
191 resting_order_trader: &Pubkey,
192 desired_global_atoms: GlobalAtoms,
193) -> bool {
194 if global_trade_accounts_opt.is_none() {
195 return false;
196 }
197 let global_trade_accounts: &GlobalTradeAccounts = &global_trade_accounts_opt.as_ref().unwrap();
198 let GlobalTradeAccounts { global, .. } = global_trade_accounts;
199
200 let global_data: &mut RefMut<&mut [u8]> = &mut global.try_borrow_mut_data().unwrap();
201 let global_dynamic_account: GlobalRefMut = get_mut_dynamic_account(global_data);
202
203 let num_deposited_atoms: GlobalAtoms =
204 global_dynamic_account.get_balance_atoms(resting_order_trader);
205 return desired_global_atoms <= num_deposited_atoms;
206}
207
208pub(crate) fn try_to_move_global_tokens<'a, 'info>(
209 global_trade_accounts_opt: &'a Option<GlobalTradeAccounts<'a, 'info>>,
210 resting_order_trader: &Pubkey,
211 desired_global_atoms: GlobalAtoms,
212) -> Result<bool, ProgramError> {
213 require!(
214 global_trade_accounts_opt.is_some(),
215 crate::program::ManifestError::MissingGlobal,
216 "Missing global accounts when adding a global",
217 )?;
218 let global_trade_accounts: &GlobalTradeAccounts = &global_trade_accounts_opt.as_ref().unwrap();
219 let GlobalTradeAccounts {
220 global,
221 mint_opt,
222 global_vault_opt,
223 gas_receiver_opt,
224 market_vault_opt,
225 token_program_opt,
226 ..
227 } = global_trade_accounts;
228
229 let global_data: &mut RefMut<&mut [u8]> = &mut global.try_borrow_mut_data()?;
230 let mut global_dynamic_account: GlobalRefMut = get_mut_dynamic_account(global_data);
231
232 let num_deposited_atoms: GlobalAtoms =
233 global_dynamic_account.get_balance_atoms(resting_order_trader);
234 if desired_global_atoms > num_deposited_atoms {
240 emit_stack(GlobalCleanupLog {
241 cleaner: *gas_receiver_opt.as_ref().unwrap().key,
242 maker: *resting_order_trader,
243 amount_desired: desired_global_atoms,
244 amount_deposited: num_deposited_atoms,
245 })?;
246 return Ok(false);
247 }
248
249 global_dynamic_account.reduce(resting_order_trader, desired_global_atoms)?;
251
252 let mint_key: &Pubkey = global_dynamic_account.fixed.get_mint();
253
254 let global_vault_bump: u8 = global_dynamic_account.fixed.get_vault_bump();
255
256 let global_vault: &TokenAccountInfo<'a, 'info> = global_vault_opt.as_ref().unwrap();
257 let market_vault: &TokenAccountInfo<'a, 'info> = market_vault_opt.as_ref().unwrap();
258 let token_program: &TokenProgram<'a, 'info> = token_program_opt.as_ref().unwrap();
259
260 if *token_program.key == spl_token_2022::id() {
261 require!(
262 mint_opt.is_some(),
263 crate::program::ManifestError::MissingGlobal,
264 "Missing global mint",
265 )?;
266
267 let mint_account_info: &MintAccountInfo = &mint_opt.as_ref().unwrap();
269 if StateWithExtensions::<Mint>::unpack(&mint_account_info.info.data.borrow())?
270 .get_extension::<TransferFeeConfig>()
271 .is_ok_and(|f| f.get_epoch_fee(get_now_epoch()).transfer_fee_basis_points != 0.into())
272 {
273 solana_program::msg!("Treating global order as unbacked because it has a transfer fee");
274 return Ok(false);
275 }
276 if StateWithExtensions::<Mint>::unpack(&mint_account_info.info.data.borrow())?
277 .get_extension::<TransferHook>()
278 .is_ok_and(|f| f.program_id.0 != Pubkey::default())
279 {
280 solana_program::msg!(
281 "Treating global order as unbacked because it has a transfer hook"
282 );
283 return Ok(false);
284 }
285
286 invoke_signed(
287 &spl_token_2022::instruction::transfer_checked(
288 token_program.key,
289 global_vault.key,
290 mint_account_info.info.key,
291 market_vault.key,
292 global_vault.key,
293 &[],
294 desired_global_atoms.as_u64(),
295 mint_account_info.mint.decimals,
296 )?,
297 &[
298 token_program.as_ref().clone(),
299 global_vault.as_ref().clone(),
300 mint_account_info.as_ref().clone(),
301 market_vault.as_ref().clone(),
302 ],
303 global_vault_seeds_with_bump!(mint_key, global_vault_bump),
304 )?;
305 } else {
306 invoke_signed(
307 &spl_token::instruction::transfer(
308 token_program.key,
309 global_vault.key,
310 market_vault.key,
311 global_vault.key,
312 &[],
313 desired_global_atoms.as_u64(),
314 )?,
315 &[
316 token_program.as_ref().clone(),
317 global_vault.as_ref().clone(),
318 market_vault.as_ref().clone(),
319 ],
320 global_vault_seeds_with_bump!(mint_key, global_vault_bump),
321 )?;
322 }
323
324 Ok(true)
325}