phoenix/program/validation/
loaders.rs

1//! This file contains all of the code that is used to load and validate account
2//! and instruction data.
3//!
4//! Each loader describes specific account types and constraints that must be met for
5//! the instruction data to be valid. Each AccountInfo is checked according to a particular
6//! checker struct and if the account data is invalid, an error is returned and the instruction will fail.
7//!
8//! The loader structs are used to validate the accounts passed into the program based on the
9//! current instruction.
10
11use super::checkers::{
12    phoenix_checkers::{MarketAccountInfo, SeatAccountInfo},
13    MintAccountInfo, TokenAccountInfo, PDA,
14};
15use crate::{
16    phoenix_log_authority,
17    program::{
18        validation::checkers::{EmptyAccount, Program, Signer},
19        MarketHeader, TokenParams,
20    },
21};
22use core::slice::Iter;
23use solana_program::{
24    account_info::{next_account_info, AccountInfo},
25    program_error::ProgramError,
26    pubkey::Pubkey,
27    system_program,
28};
29use static_assertions::const_assert_eq;
30
31pub fn get_vault_address(market: &Pubkey, mint: &Pubkey) -> (Pubkey, u8) {
32    Pubkey::find_program_address(&[b"vault", market.as_ref(), mint.as_ref()], &crate::ID)
33}
34
35pub fn get_seat_address(market: &Pubkey, trader: &Pubkey) -> (Pubkey, u8) {
36    Pubkey::find_program_address(&[b"seat", market.as_ref(), trader.as_ref()], &crate::ID)
37}
38
39pub(crate) struct PhoenixLogContext<'a, 'info> {
40    pub(crate) phoenix_program: Program<'a, 'info>,
41    pub(crate) log_authority: PDA<'a, 'info>,
42}
43
44impl<'a, 'info> PhoenixLogContext<'a, 'info> {
45    pub(crate) fn load(
46        account_iter: &mut Iter<'a, AccountInfo<'info>>,
47    ) -> Result<Self, ProgramError> {
48        Ok(Self {
49            phoenix_program: Program::new(next_account_info(account_iter)?, &crate::id())?,
50            log_authority: PDA::new(
51                next_account_info(account_iter)?,
52                &phoenix_log_authority::id(),
53            )?,
54        })
55    }
56}
57
58pub(crate) struct PhoenixMarketContext<'a, 'info> {
59    pub(crate) market_info: MarketAccountInfo<'a, 'info>,
60    pub(crate) signer: Signer<'a, 'info>,
61}
62
63impl<'a, 'info> PhoenixMarketContext<'a, 'info> {
64    pub(crate) fn load(
65        account_iter: &mut Iter<'a, AccountInfo<'info>>,
66    ) -> Result<Self, ProgramError> {
67        const_assert_eq!(std::mem::size_of::<MarketHeader>(), 576);
68        Ok(Self {
69            market_info: MarketAccountInfo::new(next_account_info(account_iter)?)?,
70            signer: Signer::new(next_account_info(account_iter)?)?,
71        })
72    }
73
74    pub(crate) fn load_init(
75        account_iter: &mut Iter<'a, AccountInfo<'info>>,
76    ) -> Result<Self, ProgramError> {
77        const_assert_eq!(std::mem::size_of::<MarketHeader>(), 576);
78        Ok(Self {
79            market_info: MarketAccountInfo::new_init(next_account_info(account_iter)?)?,
80            signer: Signer::new(next_account_info(account_iter)?)?,
81        })
82    }
83}
84
85/// These accounts that are required for all market actions that interact with a token vault
86pub(crate) struct PhoenixVaultContext<'a, 'info> {
87    pub(crate) base_account: TokenAccountInfo<'a, 'info>,
88    pub(crate) quote_account: TokenAccountInfo<'a, 'info>,
89    pub(crate) base_vault: TokenAccountInfo<'a, 'info>,
90    pub(crate) quote_vault: TokenAccountInfo<'a, 'info>,
91    pub(crate) token_program: Program<'a, 'info>,
92}
93
94impl<'a, 'info> PhoenixVaultContext<'a, 'info> {
95    pub(crate) fn load_from_iter(
96        account_iter: &mut Iter<'a, AccountInfo<'info>>,
97        base_params: &TokenParams,
98        quote_params: &TokenParams,
99        trader_key: &Pubkey,
100    ) -> Result<Self, ProgramError> {
101        Ok(Self {
102            base_account: TokenAccountInfo::new_with_owner(
103                next_account_info(account_iter)?,
104                &base_params.mint_key,
105                trader_key,
106            )?,
107            quote_account: TokenAccountInfo::new_with_owner(
108                next_account_info(account_iter)?,
109                &quote_params.mint_key,
110                trader_key,
111            )?,
112            base_vault: TokenAccountInfo::new_with_owner_and_key(
113                next_account_info(account_iter)?,
114                &base_params.mint_key,
115                &base_params.vault_key,
116                &base_params.vault_key,
117            )?,
118            quote_vault: TokenAccountInfo::new_with_owner_and_key(
119                next_account_info(account_iter)?,
120                &quote_params.mint_key,
121                &quote_params.vault_key,
122                &quote_params.vault_key,
123            )?,
124            token_program: Program::new(next_account_info(account_iter)?, &spl_token::id())?,
125        })
126    }
127}
128
129pub(crate) struct InitializeMarketContext<'a, 'info> {
130    pub(crate) base_mint: MintAccountInfo<'a, 'info>,
131    pub(crate) quote_mint: MintAccountInfo<'a, 'info>,
132    pub(crate) base_vault: EmptyAccount<'a, 'info>,
133    pub(crate) quote_vault: EmptyAccount<'a, 'info>,
134    pub(crate) system_program: Program<'a, 'info>,
135    pub(crate) token_program: Program<'a, 'info>,
136}
137
138impl<'a, 'info> InitializeMarketContext<'a, 'info> {
139    pub(crate) fn load(accounts: &'a [AccountInfo<'info>]) -> Result<Self, ProgramError> {
140        let account_iter = &mut accounts.iter();
141        let ctx = Self {
142            base_mint: MintAccountInfo::new(next_account_info(account_iter)?)?,
143            quote_mint: MintAccountInfo::new(next_account_info(account_iter)?)?,
144            base_vault: EmptyAccount::new(next_account_info(account_iter)?)?,
145            quote_vault: EmptyAccount::new(next_account_info(account_iter)?)?,
146            system_program: Program::new(next_account_info(account_iter)?, &system_program::id())?,
147            token_program: Program::new(next_account_info(account_iter)?, &spl_token::id())?,
148        };
149        Ok(ctx)
150    }
151}
152
153pub(crate) struct NewOrderContext<'a, 'info> {
154    // This is only used for limit order instructions
155    pub(crate) seat_option: Option<SeatAccountInfo<'a, 'info>>,
156    pub(crate) vault_context: Option<PhoenixVaultContext<'a, 'info>>,
157}
158
159impl<'a, 'info> NewOrderContext<'a, 'info> {
160    pub(crate) fn load_post_allowed(
161        market_context: &PhoenixMarketContext<'a, 'info>,
162        accounts: &'a [AccountInfo<'info>],
163        only_free_funds: bool,
164    ) -> Result<Self, ProgramError> {
165        let PhoenixMarketContext {
166            market_info,
167            signer: trader,
168        } = market_context;
169        market_info.assert_post_allowed()?;
170        let account_iter = &mut accounts.iter();
171        let seat_option = Some(SeatAccountInfo::new_with_context(
172            next_account_info(account_iter)?,
173            market_info.key,
174            trader.key,
175            true,
176        )?);
177        let new_order_token_account_ctx = if only_free_funds {
178            None
179        } else {
180            let (base_params, quote_params) = {
181                let header = market_info.get_header()?;
182                (header.base_params, header.quote_params)
183            };
184            Some(PhoenixVaultContext::load_from_iter(
185                account_iter,
186                &base_params,
187                &quote_params,
188                trader.key,
189            )?)
190        };
191        Ok(Self {
192            seat_option,
193            vault_context: new_order_token_account_ctx,
194        })
195    }
196
197    pub(crate) fn load_cross_only(
198        market_context: &PhoenixMarketContext<'a, 'info>,
199        accounts: &'a [AccountInfo<'info>],
200        only_free_funds: bool,
201    ) -> Result<Self, ProgramError> {
202        let PhoenixMarketContext {
203            market_info,
204            signer: trader,
205        } = market_context;
206        market_info.assert_cross_allowed()?;
207        let account_iter = &mut accounts.iter();
208        let seat_option = if only_free_funds {
209            Some(SeatAccountInfo::new_with_context(
210                next_account_info(account_iter)?,
211                market_info.key,
212                trader.key,
213                true,
214            )?)
215        } else {
216            None
217        };
218        let new_order_token_account_ctx = if only_free_funds {
219            None
220        } else {
221            let (base_params, quote_params) = {
222                let header = market_info.get_header()?;
223                (header.base_params, header.quote_params)
224            };
225            Some(PhoenixVaultContext::load_from_iter(
226                account_iter,
227                &base_params,
228                &quote_params,
229                trader.key,
230            )?)
231        };
232        Ok(Self {
233            seat_option,
234            vault_context: new_order_token_account_ctx,
235        })
236    }
237}
238
239pub(crate) struct CancelOrWithdrawContext<'a, 'info> {
240    pub(crate) vault_context: PhoenixVaultContext<'a, 'info>,
241}
242
243impl<'a, 'info> CancelOrWithdrawContext<'a, 'info> {
244    pub(crate) fn load(
245        market_context: &PhoenixMarketContext<'a, 'info>,
246        accounts: &'a [AccountInfo<'info>],
247    ) -> Result<Self, ProgramError> {
248        let PhoenixMarketContext {
249            market_info,
250            signer: trader,
251        } = market_context;
252        market_info.assert_reduce_allowed()?;
253        let account_iter = &mut accounts.iter();
254        let trader_key = trader.key;
255        let (base_params, quote_params) = {
256            let header = market_info.get_header()?;
257            (header.base_params, header.quote_params)
258        };
259        let ctx = Self {
260            vault_context: PhoenixVaultContext::load_from_iter(
261                account_iter,
262                &base_params,
263                &quote_params,
264                trader_key,
265            )?,
266        };
267        Ok(ctx)
268    }
269}
270
271pub(crate) struct DepositContext<'a, 'info> {
272    _seat: SeatAccountInfo<'a, 'info>,
273    pub(crate) vault_context: PhoenixVaultContext<'a, 'info>,
274}
275
276impl<'a, 'info> DepositContext<'a, 'info> {
277    pub(crate) fn load(
278        market_context: &PhoenixMarketContext<'a, 'info>,
279        accounts: &'a [AccountInfo<'info>],
280    ) -> Result<Self, ProgramError> {
281        let PhoenixMarketContext {
282            market_info,
283            signer: trader,
284        } = market_context;
285        market_info.assert_post_allowed()?;
286        let account_iter = &mut accounts.iter();
287        let market_key = market_info.key;
288        let trader_key = trader.key;
289        let (base_params, quote_params) = {
290            let header = market_info.get_header()?;
291            (header.base_params, header.quote_params)
292        };
293        let ctx = Self {
294            _seat: SeatAccountInfo::new_with_context(
295                next_account_info(account_iter)?,
296                market_key,
297                trader_key,
298                true,
299            )?,
300            vault_context: PhoenixVaultContext::load_from_iter(
301                account_iter,
302                &base_params,
303                &quote_params,
304                trader_key,
305            )?,
306        };
307        Ok(ctx)
308    }
309}
310
311pub(crate) struct AuthorizedActionContext<'a, 'info> {
312    pub(crate) trader: &'a AccountInfo<'info>,
313    _seat: SeatAccountInfo<'a, 'info>,
314    pub(crate) vault_context: PhoenixVaultContext<'a, 'info>,
315}
316
317impl<'a, 'info> AuthorizedActionContext<'a, 'info> {
318    pub(crate) fn load(
319        market_context: &PhoenixMarketContext<'a, 'info>,
320        accounts: &'a [AccountInfo<'info>],
321    ) -> Result<Self, ProgramError> {
322        let PhoenixMarketContext {
323            market_info,
324            signer: authority,
325        } = market_context;
326        market_info.assert_valid_authority(authority.key)?;
327        let (base_params, quote_params) = {
328            let header = market_info.get_header()?;
329            (header.base_params, header.quote_params)
330        };
331        let market_key = *market_info.key;
332
333        let account_iter = &mut accounts.iter();
334        let trader_info = next_account_info(account_iter)?;
335
336        let ctx = Self {
337            trader: trader_info,
338            _seat: SeatAccountInfo::new_with_context(
339                next_account_info(account_iter)?,
340                &market_key,
341                trader_info.key,
342                false,
343            )?,
344            vault_context: PhoenixVaultContext::load_from_iter(
345                account_iter,
346                &base_params,
347                &quote_params,
348                trader_info.key,
349            )?,
350        };
351
352        Ok(ctx)
353    }
354}
355
356pub(crate) struct ChangeMarketStatusContext<'a, 'info> {
357    pub(crate) receiver: Option<&'a AccountInfo<'info>>,
358}
359
360impl<'a, 'info> ChangeMarketStatusContext<'a, 'info> {
361    pub(crate) fn load(accounts: &'a [AccountInfo<'info>]) -> Result<Self, ProgramError> {
362        let account_iter = &mut accounts.iter();
363        let ctx = Self {
364            receiver: next_account_info(account_iter).ok(),
365        };
366        Ok(ctx)
367    }
368}
369
370pub(crate) struct AuthorizedSeatRequestContext<'a, 'info> {
371    pub(crate) payer: Signer<'a, 'info>,
372    pub(crate) trader: &'a AccountInfo<'info>,
373    pub(crate) seat: EmptyAccount<'a, 'info>,
374    pub(crate) system_program: Program<'a, 'info>,
375}
376
377impl<'a, 'info> AuthorizedSeatRequestContext<'a, 'info> {
378    pub(crate) fn load(
379        market_context: &PhoenixMarketContext<'a, 'info>,
380        accounts: &'a [AccountInfo<'info>],
381    ) -> Result<Self, ProgramError> {
382        let PhoenixMarketContext {
383            market_info,
384            signer: authority,
385        } = market_context;
386        market_info.assert_valid_authority(authority.key)?;
387
388        let account_iter = &mut accounts.iter();
389        let ctx = Self {
390            payer: Signer::new_payer(next_account_info(account_iter)?)?,
391            trader: next_account_info(account_iter)?,
392            seat: EmptyAccount::new(next_account_info(account_iter)?)?,
393            system_program: Program::new(next_account_info(account_iter)?, &system_program::id())?,
394        };
395        Ok(ctx)
396    }
397}
398
399pub(crate) struct RequestSeatContext<'a, 'info> {
400    pub(crate) seat: EmptyAccount<'a, 'info>,
401    pub(crate) system_program: Program<'a, 'info>,
402}
403
404impl<'a, 'info> RequestSeatContext<'a, 'info> {
405    pub(crate) fn load(
406        market_context: &PhoenixMarketContext<'a, 'info>,
407        accounts: &'a [AccountInfo<'info>],
408    ) -> Result<Self, ProgramError> {
409        let PhoenixMarketContext { market_info, .. } = market_context;
410        market_info.assert_post_allowed()?;
411
412        let account_iter = &mut accounts.iter();
413        let ctx = Self {
414            seat: EmptyAccount::new(next_account_info(account_iter)?)?,
415            system_program: Program::new(next_account_info(account_iter)?, &system_program::id())?,
416        };
417        Ok(ctx)
418    }
419}
420
421pub(crate) struct ModifySeatContext<'a, 'info> {
422    pub(crate) seat: SeatAccountInfo<'a, 'info>,
423}
424
425impl<'a, 'info> ModifySeatContext<'a, 'info> {
426    pub(crate) fn load(
427        market_context: &PhoenixMarketContext<'a, 'info>,
428        accounts: &'a [AccountInfo<'info>],
429    ) -> Result<Self, ProgramError> {
430        let PhoenixMarketContext {
431            market_info,
432            signer: authority,
433        } = market_context;
434        market_info.assert_valid_authority(authority.key)?;
435
436        let account_iter = &mut accounts.iter();
437        let ctx = Self {
438            seat: SeatAccountInfo::new(next_account_info(account_iter)?, market_info.key)?,
439        };
440        Ok(ctx)
441    }
442}
443
444pub(crate) struct CollectFeesContext<'a, 'info> {
445    pub(crate) fee_recipient_token_account: TokenAccountInfo<'a, 'info>,
446    pub(crate) quote_vault: TokenAccountInfo<'a, 'info>,
447    pub(crate) token_program: Program<'a, 'info>,
448}
449
450impl<'a, 'info> CollectFeesContext<'a, 'info> {
451    pub(crate) fn load(
452        market_context: &PhoenixMarketContext<'a, 'info>,
453        accounts: &'a [AccountInfo<'info>],
454    ) -> Result<Self, ProgramError> {
455        let (quote_params, fee_recipient) = {
456            let header = market_context.market_info.get_header()?;
457            (header.quote_params, header.fee_recipient)
458        };
459        let account_iter = &mut accounts.iter();
460        let ctx = Self {
461            fee_recipient_token_account: TokenAccountInfo::new_with_owner(
462                next_account_info(account_iter)?,
463                &quote_params.mint_key,
464                &fee_recipient,
465            )?,
466            quote_vault: TokenAccountInfo::new_with_owner_and_key(
467                next_account_info(account_iter)?,
468                &quote_params.mint_key,
469                &quote_params.vault_key,
470                &quote_params.vault_key,
471            )?,
472            token_program: Program::new(next_account_info(account_iter)?, &spl_token::id())?,
473        };
474        Ok(ctx)
475    }
476}
477
478pub(crate) struct ChangeFeeRecipientContext<'a, 'info> {
479    pub(crate) new_fee_recipient: AccountInfo<'info>,
480    pub(crate) previous_fee_recipient: Option<Signer<'a, 'info>>,
481}
482
483impl<'a, 'info> ChangeFeeRecipientContext<'a, 'info> {
484    pub(crate) fn load(
485        market_context: &PhoenixMarketContext<'a, 'info>,
486        accounts: &'a [AccountInfo<'info>],
487    ) -> Result<Self, ProgramError> {
488        let PhoenixMarketContext {
489            market_info,
490            signer: authority,
491        } = market_context;
492        market_info.assert_valid_authority(authority.key)?;
493        let current_fee_recipient = {
494            let header = market_info.get_header()?;
495            header.fee_recipient
496        };
497        let account_iter = &mut accounts.iter();
498        let ctx = Self {
499            new_fee_recipient: next_account_info(account_iter)?.clone(),
500            previous_fee_recipient: next_account_info(account_iter)
501                .and_then(|a| Signer::new_with_key(a, &current_fee_recipient))
502                .ok(),
503        };
504        Ok(ctx)
505    }
506}