phoenix/program/validation/checkers/
phoenix_checkers.rs1use 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 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}