phoenix/program/validation/checkers/
phoenix_checkers.rs

1use crate::program::{
2    error::assert_with_msg,
3    get_discriminant, get_seat_address,
4    status::{MarketStatus, SeatApprovalStatus},
5    MarketHeader, MarketSizeParams, PhoenixError, Seat,
6};
7use sokoban::node_allocator::ZeroCopy;
8use solana_program::{
9    account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError,
10    pubkey::Pubkey,
11};
12use std::{
13    cell::{Ref, RefMut},
14    mem::size_of,
15    ops::{Deref, DerefMut},
16};
17
18#[derive(Clone)]
19pub(crate) struct MarketAccountInfo<'a, 'info> {
20    pub(crate) info: &'a AccountInfo<'info>,
21    pub(crate) size_params: MarketSizeParams,
22}
23
24impl<'a, 'info> MarketAccountInfo<'a, 'info> {
25    #[inline(always)]
26    fn _new_unchecked(
27        info: &'a AccountInfo<'info>,
28    ) -> Result<MarketAccountInfo<'a, 'info>, ProgramError> {
29        assert_with_msg(
30            info.owner == &crate::ID,
31            ProgramError::IllegalOwner,
32            "Market must be owned by the Phoenix program",
33        )?;
34        Ok(Self {
35            info,
36            size_params: MarketSizeParams::default(),
37        })
38    }
39
40    pub(crate) fn new(
41        info: &'a AccountInfo<'info>,
42    ) -> Result<MarketAccountInfo<'a, 'info>, ProgramError> {
43        let mut market_info = Self::_new_unchecked(info)?;
44        let header = market_info.get_header()?;
45        assert_with_msg(
46            header.discriminant == get_discriminant::<MarketHeader>()?,
47            ProgramError::InvalidAccountData,
48            "Invalid market discriminant",
49        )?;
50        let params = header.market_size_params;
51        drop(header);
52        market_info.size_params = params;
53        Ok(market_info)
54    }
55
56    pub(crate) fn assert_reduce_allowed(&self) -> ProgramResult {
57        let header = self.get_header()?;
58        let status = MarketStatus::from(header.status);
59        assert_with_msg(
60            status.reduce_allowed(),
61            ProgramError::InvalidAccountData,
62            &format!("Reduce order is not allowed, market status is {}", status),
63        )
64    }
65
66    pub(crate) fn assert_cross_allowed(&self) -> ProgramResult {
67        let header = self.get_header()?;
68        let status = MarketStatus::from(header.status);
69        assert_with_msg(
70            status.cross_allowed(),
71            ProgramError::InvalidAccountData,
72            &format!(
73                "FOK and IOC orders are not allowed, market status is {}",
74                status
75            ),
76        )
77    }
78
79    pub(crate) fn assert_post_allowed(&self) -> ProgramResult {
80        let header = self.get_header()?;
81        let status = MarketStatus::from(header.status);
82        assert_with_msg(
83            status.post_allowed(),
84            ProgramError::InvalidAccountData,
85            &format!(
86                "Post only order is not allowed, market status is {}",
87                status
88            ),
89        )
90    }
91
92    pub(crate) fn assert_valid_authority(&self, authority: &Pubkey) -> ProgramResult {
93        let header = self.get_header()?;
94        assert_with_msg(
95            &header.authority == authority,
96            PhoenixError::InvalidMarketAuthority,
97            "Invalid market authority",
98        )
99    }
100
101    pub(crate) fn assert_valid_successor(&self, successor: &Pubkey) -> ProgramResult {
102        let header = self.get_header()?;
103        assert_with_msg(
104            &header.successor == successor,
105            PhoenixError::InvalidMarketAuthority,
106            "Invalid market successor",
107        )
108    }
109
110    pub(crate) fn new_init(
111        info: &'a AccountInfo<'info>,
112    ) -> Result<MarketAccountInfo<'a, 'info>, ProgramError> {
113        let market_bytes = info.try_borrow_data()?;
114        let (header_bytes, _) = market_bytes.split_at(size_of::<MarketHeader>());
115        let header =
116            MarketHeader::load_bytes(header_bytes).ok_or(ProgramError::InvalidAccountData)?;
117        assert_with_msg(
118            info.owner == &crate::ID,
119            ProgramError::IllegalOwner,
120            "Market must be owned by the Phoenix program",
121        )?;
122        // On initialization, the discriminant is not set yet.
123        assert_with_msg(
124            header.discriminant == 0,
125            ProgramError::InvalidAccountData,
126            "Expected uninitialized market with discriminant 0",
127        )?;
128        assert_with_msg(
129            header.status == MarketStatus::Uninitialized as u64,
130            ProgramError::InvalidAccountData,
131            "MarketStatus must be uninitialized",
132        )?;
133        Ok(Self {
134            info,
135            size_params: MarketSizeParams::default(),
136        })
137    }
138
139    pub(crate) fn get_header(&self) -> Result<Ref<'_, MarketHeader>, ProgramError> {
140        let data = self.info.try_borrow_data()?;
141        Ok(Ref::map(data, |data| {
142            return MarketHeader::load_bytes(&data[..size_of::<MarketHeader>()]).unwrap();
143        }))
144    }
145
146    pub(crate) fn get_header_mut(&self) -> Result<RefMut<'_, MarketHeader>, ProgramError> {
147        let data = self.info.try_borrow_mut_data()?;
148        Ok(RefMut::map(data, |data| {
149            return MarketHeader::load_mut_bytes(&mut data[..size_of::<MarketHeader>()]).unwrap();
150        }))
151    }
152}
153
154impl<'a, 'info> AsRef<AccountInfo<'info>> for MarketAccountInfo<'a, 'info> {
155    fn as_ref(&self) -> &AccountInfo<'info> {
156        self.info
157    }
158}
159
160impl<'a, 'info> Deref for MarketAccountInfo<'a, 'info> {
161    type Target = AccountInfo<'info>;
162
163    fn deref(&self) -> &Self::Target {
164        self.info
165    }
166}
167
168#[derive(Clone)]
169pub(crate) struct SeatAccountInfo<'a, 'info> {
170    pub(crate) info: &'a AccountInfo<'info>,
171}
172
173impl<'a, 'info> SeatAccountInfo<'a, 'info> {
174    pub(crate) fn new_with_context(
175        info: &'a AccountInfo<'info>,
176        market: &Pubkey,
177        trader: &Pubkey,
178        approved: bool,
179    ) -> Result<SeatAccountInfo<'a, 'info>, ProgramError> {
180        let (seat_address, _) = get_seat_address(market, trader);
181        assert_with_msg(
182            info.owner == &crate::ID,
183            ProgramError::IllegalOwner,
184            "Seat must be owned by the Phoenix program",
185        )?;
186        assert_with_msg(
187            &seat_address == info.key,
188            ProgramError::InvalidInstructionData,
189            "Invalid address for seat",
190        )?;
191        let seat_bytes = info.try_borrow_data()?;
192        let seat = Seat::load_bytes(&seat_bytes).ok_or(ProgramError::InvalidAccountData)?;
193        assert_with_msg(
194            seat.discriminant == get_discriminant::<Seat>()?,
195            ProgramError::InvalidAccountData,
196            "Invalid discriminant for seat",
197        )?;
198        assert_with_msg(
199            &seat.trader == trader,
200            ProgramError::InvalidAccountData,
201            "Invalid trader for seat",
202        )?;
203        assert_with_msg(
204            &seat.market == market,
205            ProgramError::InvalidAccountData,
206            "Invalid market for seat",
207        )?;
208        let seat_status = SeatApprovalStatus::from(seat.approval_status);
209        if approved {
210            assert_with_msg(
211                matches!(seat_status, SeatApprovalStatus::Approved),
212                PhoenixError::InvalidSeatStatus,
213                "Seat must be approved",
214            )?;
215        } else {
216            assert_with_msg(
217                !matches!(seat_status, SeatApprovalStatus::Approved),
218                PhoenixError::InvalidSeatStatus,
219                "Seat must be unapproved or retired",
220            )?;
221        }
222        Ok(Self { info })
223    }
224
225    pub(crate) fn new(
226        info: &'a AccountInfo<'info>,
227        market: &Pubkey,
228    ) -> Result<SeatAccountInfo<'a, 'info>, ProgramError> {
229        let seat_bytes = info.try_borrow_data()?;
230        let seat = Seat::load_bytes(&seat_bytes).ok_or(ProgramError::InvalidAccountData)?;
231        let (seat_address, _) = get_seat_address(market, &seat.trader);
232        assert_with_msg(
233            seat.market == *market,
234            ProgramError::InvalidAccountData,
235            "Market on seat does not match market in instruction",
236        )?;
237        assert_with_msg(
238            info.owner == &crate::ID,
239            ProgramError::IllegalOwner,
240            "Seat must be owned by the Phoenix program",
241        )?;
242        assert_with_msg(
243            &seat_address == info.key,
244            ProgramError::InvalidInstructionData,
245            "Invalid address for seat",
246        )?;
247        assert_with_msg(
248            seat.discriminant == get_discriminant::<Seat>()?,
249            ProgramError::InvalidAccountData,
250            "Invalid discriminant for seat",
251        )?;
252        Ok(Self { info })
253    }
254
255    pub(crate) fn load_mut(&self) -> Result<RefMut<'_, Seat>, ProgramError> {
256        let data = self.info.try_borrow_mut_data()?;
257        Ok(RefMut::map(data, |data| {
258            return Seat::load_mut_bytes(&mut data.deref_mut()[..]).unwrap();
259        }))
260    }
261}
262
263impl<'a, 'info> AsRef<AccountInfo<'info>> for SeatAccountInfo<'a, 'info> {
264    fn as_ref(&self) -> &AccountInfo<'info> {
265        self.info
266    }
267}
268
269impl<'a, 'info> Deref for SeatAccountInfo<'a, 'info> {
270    type Target = AccountInfo<'info>;
271
272    fn deref(&self) -> &Self::Target {
273        self.info
274    }
275}