1use std::mem::size_of;
2
3use crate::quantities::{u64_slice_to_u128, BaseAtoms, QuoteAtomsPerBaseAtom};
4#[cfg(feature = "certora")]
5use crate::quantities::{QuoteAtoms, WrapperU64};
6use borsh::{BorshDeserialize, BorshSerialize};
7use bytemuck::{Pod, Zeroable};
8use hypertree::{DataIndex, PodBool};
9use num_enum::{IntoPrimitive, TryFromPrimitive};
10use shank::ShankType;
11use solana_program::{entrypoint::ProgramResult, program_error::ProgramError};
12use static_assertions::const_assert_eq;
13use std::cmp::Ordering;
14
15use super::{constants::NO_EXPIRATION_LAST_VALID_SLOT, RESTING_ORDER_SIZE};
16
17#[derive(
18 Debug,
19 BorshDeserialize,
20 BorshSerialize,
21 PartialEq,
22 Clone,
23 Copy,
24 ShankType,
25 IntoPrimitive,
26 TryFromPrimitive,
27)]
28#[repr(u8)]
29pub enum OrderType {
30 Limit = 0,
32
33 ImmediateOrCancel = 1,
35
36 PostOnly = 2,
38
39 Global = 3,
41
42 Reverse = 4,
45}
46unsafe impl bytemuck::Zeroable for OrderType {}
47unsafe impl bytemuck::Pod for OrderType {}
48impl Default for OrderType {
49 fn default() -> Self {
50 OrderType::Limit
51 }
52}
53
54pub fn order_type_can_rest(order_type: OrderType) -> bool {
55 order_type != OrderType::ImmediateOrCancel
56}
57
58pub fn order_type_can_take(order_type: OrderType) -> bool {
59 order_type != OrderType::PostOnly && order_type != OrderType::Global
60}
61
62#[repr(C)]
63#[derive(Default, Debug, Copy, Clone, Zeroable, Pod, ShankType)]
64pub struct RestingOrder {
65 price: QuoteAtomsPerBaseAtom,
66 num_base_atoms: BaseAtoms,
67 sequence_number: u64,
68 trader_index: DataIndex,
69 last_valid_slot: u32,
70 is_bid: PodBool,
71 order_type: OrderType,
72 reverse_spread: u16,
74 _padding: [u8; 20],
75}
76
77const_assert_eq!(size_of::<RestingOrder>(), RESTING_ORDER_SIZE);
88const_assert_eq!(size_of::<RestingOrder>() % 8, 0);
89
90impl RestingOrder {
91 pub fn new(
92 trader_index: DataIndex,
93 num_base_atoms: BaseAtoms,
94 price: QuoteAtomsPerBaseAtom,
95 sequence_number: u64,
96 last_valid_slot: u32,
97 is_bid: bool,
98 order_type: OrderType,
99 ) -> Result<Self, ProgramError> {
100 assert!(
103 !(order_type == OrderType::Reverse && last_valid_slot != NO_EXPIRATION_LAST_VALID_SLOT)
104 );
105
106 Ok(RestingOrder {
107 trader_index,
108 num_base_atoms,
109 last_valid_slot,
110 price,
111 sequence_number,
112 is_bid: PodBool::from_bool(is_bid),
113 order_type,
114 reverse_spread: 0,
115 _padding: Default::default(),
116 })
117 }
118
119 pub fn get_trader_index(&self) -> DataIndex {
120 self.trader_index
121 }
122
123 pub fn get_num_base_atoms(&self) -> BaseAtoms {
124 self.num_base_atoms
125 }
126
127 pub fn get_price(&self) -> QuoteAtomsPerBaseAtom {
128 self.price
129 }
130
131 #[cfg(any(test, feature = "no-clock"))]
132 pub fn set_sequence_number(&mut self, sequence_number: u64) {
133 self.sequence_number = sequence_number;
134 }
135 #[cfg(any(test, feature = "no-clock"))]
136 pub fn set_last_valid_slot(&mut self, last_valid_slot: u32) {
137 self.last_valid_slot = last_valid_slot;
138 }
139
140 pub fn get_order_type(&self) -> OrderType {
141 self.order_type
142 }
143
144 #[cfg(feature = "certora")]
145 pub fn is_global(&self) -> bool {
146 false
147 }
148
149 #[cfg(not(feature = "certora"))]
150 pub fn is_global(&self) -> bool {
151 self.order_type == OrderType::Global
152 }
153
154 pub fn is_reverse(&self) -> bool {
155 self.order_type == OrderType::Reverse
156 }
157
158 pub fn get_reverse_spread(self) -> u16 {
159 self.reverse_spread
160 }
161
162 pub fn set_reverse_spread(&mut self, spread: u16) {
163 self.reverse_spread = spread;
164 }
165
166 pub fn get_sequence_number(&self) -> u64 {
167 self.sequence_number
168 }
169
170 pub fn is_expired(&self, current_slot: u32) -> bool {
171 self.last_valid_slot != NO_EXPIRATION_LAST_VALID_SLOT && self.last_valid_slot < current_slot
172 }
173
174 pub fn get_is_bid(&self) -> bool {
175 self.is_bid.0 == 1
176 }
177
178 #[cfg(feature = "certora")]
181 pub fn get_orderbook_atoms(&self) -> Result<(BaseAtoms, QuoteAtoms), ProgramError> {
182 if self.is_global() {
183 return Ok((BaseAtoms::new(0), QuoteAtoms::new(0)));
184 } else if self.get_is_bid() {
185 let quote_amount = self.num_base_atoms.checked_mul(self.price, true)?;
186 return Ok((BaseAtoms::new(0), quote_amount));
187 } else {
188 return Ok((self.num_base_atoms, QuoteAtoms::new(0)));
189 }
190 }
191
192 pub fn reduce(&mut self, size: BaseAtoms) -> ProgramResult {
193 self.num_base_atoms = self.num_base_atoms.checked_sub(size)?;
194 Ok(())
195 }
196
197 pub fn increase(&mut self, size: BaseAtoms) -> ProgramResult {
199 self.num_base_atoms = self.num_base_atoms.checked_add(size)?;
200 Ok(())
201 }
202}
203
204impl Ord for RestingOrder {
205 fn cmp(&self, other: &Self) -> Ordering {
206 debug_assert!(self.get_is_bid() == other.get_is_bid());
209
210 if self.get_is_bid() {
211 (self.price).cmp(&other.price)
212 } else {
213 (other.price).cmp(&(self.price))
214 }
215 }
216}
217
218impl PartialOrd for RestingOrder {
219 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
220 Some(self.cmp(other))
221 }
222}
223
224impl PartialEq for RestingOrder {
225 fn eq(&self, other: &Self) -> bool {
226 if self.trader_index != other.trader_index || self.order_type != other.order_type {
227 return false;
228 }
229 if self.order_type == OrderType::Reverse {
230 self.price == other.price
232 || u64_slice_to_u128(self.price.inner) + 1 == u64_slice_to_u128(other.price.inner)
233 || u64_slice_to_u128(self.price.inner) - 1 == u64_slice_to_u128(other.price.inner)
234 } else {
235 self.price == other.price
237 }
238 }
239}
240
241impl Eq for RestingOrder {}
242
243impl std::fmt::Display for RestingOrder {
244 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
245 write!(f, "{}@{}", self.num_base_atoms, self.price)
246 }
247}
248
249#[cfg(test)]
250mod test {
251 use super::*;
252 use crate::quantities::WrapperU64;
253
254 #[test]
255 fn test_default() {
256 assert_eq!(OrderType::default(), OrderType::Limit);
257 }
258
259 #[test]
260 fn test_display() {
261 let resting_order: RestingOrder = RestingOrder::new(
262 0,
263 BaseAtoms::ZERO,
264 QuoteAtomsPerBaseAtom::ZERO,
265 0,
266 NO_EXPIRATION_LAST_VALID_SLOT,
267 true,
268 OrderType::Limit,
269 )
270 .unwrap();
271 format!("{}", resting_order);
272 }
273
274 #[test]
275 fn test_cmp() {
276 let resting_order_1: RestingOrder = RestingOrder::new(
277 0,
278 BaseAtoms::new(1),
279 QuoteAtomsPerBaseAtom::try_from(1.00000000000001).unwrap(),
280 0,
281 NO_EXPIRATION_LAST_VALID_SLOT,
282 true,
283 OrderType::Limit,
284 )
285 .unwrap();
286 let resting_order_2: RestingOrder = RestingOrder::new(
287 0,
288 BaseAtoms::new(1_000_000_000),
289 QuoteAtomsPerBaseAtom::try_from(1.01).unwrap(),
290 1,
291 NO_EXPIRATION_LAST_VALID_SLOT,
292 true,
293 OrderType::Limit,
294 )
295 .unwrap();
296 assert!(resting_order_1 < resting_order_2);
297 assert!(resting_order_1 != resting_order_2);
298
299 let resting_order_1: RestingOrder = RestingOrder::new(
300 0,
301 BaseAtoms::new(1),
302 QuoteAtomsPerBaseAtom::try_from(1.00000000000001).unwrap(),
303 0,
304 NO_EXPIRATION_LAST_VALID_SLOT,
305 false,
306 OrderType::Limit,
307 )
308 .unwrap();
309 let resting_order_2: RestingOrder = RestingOrder::new(
310 0,
311 BaseAtoms::new(1_000_000_000),
312 QuoteAtomsPerBaseAtom::try_from(1.01).unwrap(),
313 1,
314 NO_EXPIRATION_LAST_VALID_SLOT,
315 false,
316 OrderType::Limit,
317 )
318 .unwrap();
319 assert!(resting_order_1 > resting_order_2);
320 assert!(resting_order_1 != resting_order_2);
321 }
322
323 #[test]
324 fn test_setters() {
325 let mut resting_order: RestingOrder = RestingOrder::new(
326 0,
327 BaseAtoms::ZERO,
328 QuoteAtomsPerBaseAtom::ZERO,
329 0,
330 NO_EXPIRATION_LAST_VALID_SLOT,
331 true,
332 OrderType::Limit,
333 )
334 .unwrap();
335 resting_order.set_last_valid_slot(1);
336 resting_order.set_sequence_number(1);
337 }
338}