1use std::{cmp::Ordering, mem::size_of};
8
9use bytemuck::{Pod, Zeroable};
10use hypertree::{
11 get_helper, get_mut_helper, DataIndex, FreeList, Get, HyperTreeReadOperations,
12 HyperTreeWriteOperations, RBNode, RedBlackTree, RedBlackTreeReadOnly, NIL,
13};
14use shank::ShankType;
15use solana_program::{entrypoint::ProgramResult, pubkey::Pubkey};
16use static_assertions::const_assert_eq;
17
18use crate::{
19 quantities::{GlobalAtoms, WrapperU64},
20 require,
21 validation::{get_global_address, get_global_vault_address, ManifestAccount},
22};
23
24use super::{
25 DerefOrBorrow, DerefOrBorrowMut, DynamicAccount, RestingOrder, GLOBAL_BLOCK_SIZE,
26 GLOBAL_DEPOSIT_SIZE, GLOBAL_FIXED_DISCRIMINANT, GLOBAL_FIXED_SIZE, GLOBAL_FREE_LIST_BLOCK_SIZE,
27 GLOBAL_TRADER_SIZE, MAX_GLOBAL_SEATS,
28};
29
30#[repr(C)]
31#[derive(Default, Copy, Clone, Zeroable, Pod, ShankType)]
32pub struct GlobalFixed {
33 pub discriminant: u64,
35
36 mint: Pubkey,
38
39 vault: Pubkey,
41
42 global_traders_root_index: DataIndex,
44
45 global_deposits_root_index: DataIndex,
47 global_deposits_max_index: DataIndex,
50
51 free_list_head_index: DataIndex,
53
54 num_bytes_allocated: DataIndex,
56
57 vault_bump: u8,
58
59 global_bump: u8,
61
62 num_seats_claimed: u16,
63}
64const_assert_eq!(
65 size_of::<GlobalFixed>(),
66 8 + 32 + 32 + 4 + 4 + 4 + 4 + 4 + 1 + 1 + 2 );
78const_assert_eq!(size_of::<GlobalFixed>(), GLOBAL_FIXED_SIZE);
79const_assert_eq!(size_of::<GlobalFixed>() % 8, 0);
80impl Get for GlobalFixed {}
81
82#[repr(C, packed)]
83#[derive(Default, Copy, Clone, Pod, Zeroable)]
84struct GlobalUnusedFreeListPadding {
85 _padding: [u64; 7],
86 _padding2: [u8; 4],
87}
88const_assert_eq!(
90 size_of::<GlobalUnusedFreeListPadding>(),
91 GLOBAL_FREE_LIST_BLOCK_SIZE
92);
93#[repr(C)]
96#[derive(Default, Copy, Clone, Zeroable, Pod, ShankType)]
97pub struct GlobalTrader {
98 trader: Pubkey,
100
101 deposit_index: DataIndex,
102 _padding: u32,
103 _padding2: u64,
104}
105const_assert_eq!(size_of::<GlobalTrader>(), GLOBAL_TRADER_SIZE);
106const_assert_eq!(size_of::<GlobalTrader>() % 8, 0);
107
108impl Ord for GlobalTrader {
109 fn cmp(&self, other: &Self) -> Ordering {
110 (self.trader).cmp(&(other.trader))
111 }
112}
113impl PartialOrd for GlobalTrader {
114 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
115 Some(self.cmp(other))
116 }
117}
118impl PartialEq for GlobalTrader {
119 fn eq(&self, other: &Self) -> bool {
120 (self.trader) == (other.trader)
121 }
122}
123impl Eq for GlobalTrader {}
124impl std::fmt::Display for GlobalTrader {
125 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
126 write!(f, "{}", self.trader)
127 }
128}
129
130#[repr(C)]
131#[derive(Default, Copy, Clone, Zeroable, Pod, ShankType)]
132pub struct GlobalDeposit {
133 trader: Pubkey,
135
136 balance_atoms: GlobalAtoms,
139 _padding: u64,
140}
141const_assert_eq!(size_of::<GlobalDeposit>(), GLOBAL_DEPOSIT_SIZE);
142const_assert_eq!(size_of::<GlobalDeposit>() % 8, 0);
143
144impl Ord for GlobalDeposit {
145 fn cmp(&self, other: &Self) -> Ordering {
146 (other.balance_atoms).cmp(&(self.balance_atoms))
148 }
149}
150impl PartialOrd for GlobalDeposit {
151 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
152 Some(self.cmp(other))
153 }
154}
155impl PartialEq for GlobalDeposit {
156 fn eq(&self, other: &Self) -> bool {
157 (self.trader) == (other.trader)
158 }
159}
160impl Eq for GlobalDeposit {}
161impl std::fmt::Display for GlobalDeposit {
162 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
163 write!(f, "{}", self.trader)
164 }
165}
166
167impl GlobalFixed {
168 pub fn new_empty(mint: &Pubkey) -> Self {
169 let (vault, vault_bump) = get_global_vault_address(mint);
170 let (_, global_bump) = get_global_address(mint);
171 GlobalFixed {
172 discriminant: GLOBAL_FIXED_DISCRIMINANT,
173 mint: *mint,
174 vault,
175 global_traders_root_index: NIL,
176 global_deposits_root_index: NIL,
177 global_deposits_max_index: NIL,
178 free_list_head_index: NIL,
179 num_bytes_allocated: 0,
180 vault_bump,
181 global_bump,
182 num_seats_claimed: 0,
183 }
184 }
185 pub fn get_mint(&self) -> &Pubkey {
186 &self.mint
187 }
188 pub fn get_vault(&self) -> &Pubkey {
189 &self.vault
190 }
191 pub fn get_vault_bump(&self) -> u8 {
192 self.vault_bump
193 }
194}
195
196impl ManifestAccount for GlobalFixed {
197 fn verify_discriminant(&self) -> ProgramResult {
198 require!(
200 self.discriminant == GLOBAL_FIXED_DISCRIMINANT,
201 solana_program::program_error::ProgramError::InvalidAccountData,
202 "Invalid market discriminant actual: {} expected: {}",
203 self.discriminant,
204 GLOBAL_FIXED_DISCRIMINANT
205 )?;
206 Ok(())
207 }
208}
209
210impl GlobalTrader {
211 pub fn new_empty(trader: &Pubkey, deposit_index: DataIndex) -> Self {
212 GlobalTrader {
213 trader: *trader,
214 deposit_index,
215 _padding: 0,
216 _padding2: 0,
217 }
218 }
219}
220
221impl GlobalDeposit {
222 pub fn new_empty(trader: &Pubkey) -> Self {
223 GlobalDeposit {
224 trader: *trader,
225 balance_atoms: GlobalAtoms::ZERO,
226 _padding: 0,
227 }
228 }
229}
230
231pub type GlobalTraderTree<'a> = RedBlackTree<'a, GlobalTrader>;
232pub type GlobalTraderTreeReadOnly<'a> = RedBlackTreeReadOnly<'a, GlobalTrader>;
233pub type GlobalDepositTree<'a> = RedBlackTree<'a, GlobalDeposit>;
234pub type GlobalDepositTreeReadOnly<'a> = RedBlackTreeReadOnly<'a, GlobalDeposit>;
235
236pub type GlobalValue = DynamicAccount<GlobalFixed, Vec<u8>>;
238pub type GlobalRef<'a> = DynamicAccount<&'a GlobalFixed, &'a [u8]>;
240pub type GlobalRefMut<'a> = DynamicAccount<&'a mut GlobalFixed, &'a mut [u8]>;
242
243impl<Fixed: DerefOrBorrow<GlobalFixed>, Dynamic: DerefOrBorrow<[u8]>>
244 DynamicAccount<Fixed, Dynamic>
245{
246 fn borrow_global(&self) -> GlobalRef {
247 GlobalRef {
248 fixed: self.fixed.deref_or_borrow(),
249 dynamic: self.dynamic.deref_or_borrow(),
250 }
251 }
252
253 pub fn get_balance_atoms(&self, trader: &Pubkey) -> GlobalAtoms {
254 let DynamicAccount { fixed, dynamic } = self.borrow_global();
255 let global_balance_or: Option<&GlobalDeposit> = get_global_deposit(fixed, dynamic, trader);
257 if let Some(global_deposit) = global_balance_or {
258 global_deposit.balance_atoms
259 } else {
260 GlobalAtoms::ZERO
261 }
262 }
263
264 pub fn verify_min_balance(&self, trader: &Pubkey) -> ProgramResult {
265 let DynamicAccount { fixed, dynamic } = self.borrow_global();
266
267 let existing_global_trader_opt: Option<&GlobalTrader> =
268 get_global_trader(fixed, dynamic, trader);
269 require!(
270 existing_global_trader_opt.is_some(),
271 crate::program::ManifestError::MissingGlobal,
272 "Could not find global trader for {}",
273 trader
274 )?;
275 let existing_global_trader: GlobalTrader = *existing_global_trader_opt.unwrap();
276 let global_trader_tree: GlobalTraderTreeReadOnly = GlobalTraderTreeReadOnly::new(
277 dynamic,
278 fixed.global_traders_root_index,
279 fixed.global_deposits_max_index,
280 );
281 let existing_trader_index: DataIndex =
282 global_trader_tree.lookup_index(&existing_global_trader);
283 let existing_global_trader: &GlobalTrader =
284 get_helper::<RBNode<GlobalTrader>>(dynamic, existing_trader_index).get_value();
285 let existing_deposit_index: DataIndex = existing_global_trader.deposit_index;
286
287 require!(
288 existing_deposit_index == fixed.global_deposits_max_index,
289 crate::program::ManifestError::GlobalInsufficient,
290 "Only can remove trader with lowest deposit"
291 )?;
292
293 Ok(())
294 }
295}
296
297impl<Fixed: DerefOrBorrowMut<GlobalFixed>, Dynamic: DerefOrBorrowMut<[u8]>>
298 DynamicAccount<Fixed, Dynamic>
299{
300 fn borrow_mut_global(&mut self) -> GlobalRefMut {
301 GlobalRefMut {
302 fixed: self.fixed.deref_or_borrow_mut(),
303 dynamic: self.dynamic.deref_or_borrow_mut(),
304 }
305 }
306
307 pub fn global_expand(&mut self) -> ProgramResult {
308 let DynamicAccount { fixed, dynamic } = self.borrow_mut_global();
309
310 require!(
311 fixed.free_list_head_index == NIL,
312 crate::program::ManifestError::InvalidFreeList,
313 "Expected empty free list, but expand wasnt needed",
314 )?;
315
316 let mut free_list: FreeList<GlobalUnusedFreeListPadding> =
317 FreeList::new(dynamic, fixed.free_list_head_index);
318
319 free_list.add(fixed.num_bytes_allocated);
321 free_list.add(fixed.num_bytes_allocated + GLOBAL_BLOCK_SIZE as u32);
322 fixed.num_bytes_allocated += 2 * GLOBAL_BLOCK_SIZE as u32;
323 fixed.free_list_head_index = free_list.get_head();
324 Ok(())
325 }
326
327 pub fn reduce(&mut self, trader: &Pubkey, num_atoms: GlobalAtoms) -> ProgramResult {
328 let DynamicAccount { fixed, dynamic } = self.borrow_mut_global();
329 let global_deposit_opt: Option<&mut GlobalDeposit> =
330 get_mut_global_deposit(fixed, dynamic, trader);
331 require!(
332 global_deposit_opt.is_some(),
333 crate::program::ManifestError::MissingGlobal,
334 "Could not find global deposit for {}",
335 trader
336 )?;
337 let global_deposit: &mut GlobalDeposit = global_deposit_opt.unwrap();
338 global_deposit.balance_atoms = global_deposit.balance_atoms.checked_sub(num_atoms)?;
339 Ok(())
340 }
341
342 pub fn add_trader(&mut self, trader: &Pubkey) -> ProgramResult {
344 let DynamicAccount { fixed, dynamic } = self.borrow_mut_global();
345
346 let free_address_trader: DataIndex = get_free_address_on_global_fixed(fixed, dynamic);
347 let free_address_deposit: DataIndex = get_free_address_on_global_fixed(fixed, dynamic);
348 let mut global_trader_tree: GlobalTraderTree =
349 GlobalTraderTree::new(dynamic, fixed.global_traders_root_index, NIL);
350 let global_trader: GlobalTrader = GlobalTrader::new_empty(trader, free_address_deposit);
351
352 require!(
353 global_trader_tree.lookup_index(&global_trader) == NIL,
354 crate::program::ManifestError::AlreadyClaimedSeat,
355 "Already claimed global trader seat",
356 )?;
357
358 global_trader_tree.insert(free_address_trader, global_trader);
359 fixed.global_traders_root_index = global_trader_tree.get_root_index();
360 require!(
361 fixed.num_seats_claimed < MAX_GLOBAL_SEATS,
362 crate::program::ManifestError::TooManyGlobalSeats,
363 "There is a strict limit on number of seats available in a global, use evict",
364 )?;
365
366 fixed.num_seats_claimed += 1;
367
368 let global_deposit: GlobalDeposit = GlobalDeposit::new_empty(trader);
369 let mut global_deposit_tree: GlobalDepositTree = GlobalDepositTree::new(
370 dynamic,
371 fixed.global_deposits_root_index,
372 fixed.global_deposits_max_index,
373 );
374 global_deposit_tree.insert(free_address_deposit, global_deposit);
375 fixed.global_deposits_root_index = global_deposit_tree.get_root_index();
376 fixed.global_deposits_max_index = global_deposit_tree.get_max_index();
377
378 Ok(())
379 }
380
381 pub fn evict_and_take_seat(
383 &mut self,
384 existing_trader: &Pubkey,
385 new_trader: &Pubkey,
386 ) -> ProgramResult {
387 let DynamicAccount { fixed, dynamic } = self.borrow_mut_global();
388
389 let existing_global_trader_opt: Option<&GlobalTrader> =
390 get_global_trader(fixed, dynamic, existing_trader);
391 require!(
392 existing_global_trader_opt.is_some(),
393 crate::program::ManifestError::MissingGlobal,
394 "Could not find global trader for {}",
395 existing_trader
396 )?;
397 let existing_global_trader: GlobalTrader = *existing_global_trader_opt.unwrap();
398
399 let existing_global_deposit_opt: Option<&mut GlobalDeposit> =
400 get_mut_global_deposit(fixed, dynamic, existing_trader);
401 require!(
402 existing_global_deposit_opt.is_some(),
403 crate::program::ManifestError::MissingGlobal,
404 "Could not find global deposit for {}",
405 existing_trader
406 )?;
407 let existing_global_deposit: &mut GlobalDeposit = existing_global_deposit_opt.unwrap();
408
409 let existing_global_atoms_deposited: GlobalAtoms = existing_global_deposit.balance_atoms;
410 require!(
411 existing_global_atoms_deposited == GlobalAtoms::ZERO,
412 crate::program::ManifestError::GlobalInsufficient,
413 "Error in emptying the existing global",
414 )?;
415
416 let global_trader_tree: GlobalTraderTree =
418 GlobalTraderTree::new(dynamic, fixed.global_traders_root_index, NIL);
419 let existing_trader_index: DataIndex =
420 global_trader_tree.lookup_index(&existing_global_trader);
421 let existing_global_trader: &GlobalTrader =
422 get_helper::<RBNode<GlobalTrader>>(dynamic, existing_trader_index).get_value();
423 let existing_deposit_index: DataIndex = existing_global_trader.deposit_index;
424
425 {
427 let mut global_trader_tree: GlobalTraderTree =
428 GlobalTraderTree::new(dynamic, fixed.global_traders_root_index, NIL);
429 require!(
430 existing_deposit_index == fixed.global_deposits_max_index,
431 crate::program::ManifestError::GlobalInsufficient,
432 "Only can remove trader with lowest deposit"
433 )?;
434 let new_global_trader: GlobalTrader =
435 GlobalTrader::new_empty(new_trader, fixed.global_deposits_max_index);
436
437 global_trader_tree.remove_by_index(existing_trader_index);
438
439 require!(
441 global_trader_tree.lookup_index(&new_global_trader) == NIL,
442 crate::program::ManifestError::AlreadyClaimedSeat,
443 "Already claimed global trader seat",
444 )?;
445
446 global_trader_tree.insert(existing_trader_index, new_global_trader);
447 fixed.global_traders_root_index = global_trader_tree.get_root_index();
448 }
449
450 {
452 let new_global_deposit: GlobalDeposit = GlobalDeposit::new_empty(new_trader);
453 let mut global_deposit_tree: GlobalDepositTree = GlobalDepositTree::new(
454 dynamic,
455 fixed.global_deposits_root_index,
456 fixed.global_deposits_max_index,
457 );
458 global_deposit_tree.remove_by_index(existing_deposit_index);
459 global_deposit_tree.insert(existing_deposit_index, new_global_deposit);
460 fixed.global_deposits_max_index = global_deposit_tree.get_max_index();
461 fixed.global_deposits_root_index = global_deposit_tree.get_root_index();
462 }
463
464 Ok(())
465 }
466
467 pub fn add_order(
469 &mut self,
470 resting_order: &RestingOrder,
471 global_trade_owner: &Pubkey,
472 ) -> ProgramResult {
473 let DynamicAccount { fixed, dynamic } = self.borrow_mut_global();
474
475 let num_global_atoms: GlobalAtoms = if resting_order.get_is_bid() {
476 GlobalAtoms::new(
477 resting_order
478 .get_num_base_atoms()
479 .checked_mul(resting_order.get_price(), true)
480 .unwrap()
481 .as_u64(),
482 )
483 } else {
484 GlobalAtoms::new(resting_order.get_num_base_atoms().as_u64())
485 };
486
487 {
489 let global_deposit_opt: Option<&mut GlobalDeposit> =
490 get_mut_global_deposit(fixed, dynamic, global_trade_owner);
491 require!(
492 global_deposit_opt.is_some(),
493 crate::program::ManifestError::MissingGlobal,
494 "Could not find global deposit for {}",
495 global_trade_owner
496 )?;
497 let global_deposit: &mut GlobalDeposit = global_deposit_opt.unwrap();
498
499 let global_atoms_deposited: GlobalAtoms = global_deposit.balance_atoms;
500
501 require!(
504 num_global_atoms <= global_atoms_deposited,
505 crate::program::ManifestError::GlobalInsufficient,
506 "Insufficient funds for global order needed {} has {}",
507 num_global_atoms,
508 global_atoms_deposited
509 )?;
510 }
511
512 Ok(())
513 }
514
515 pub fn deposit_global(&mut self, trader: &Pubkey, num_atoms: GlobalAtoms) -> ProgramResult {
517 let DynamicAccount { fixed, dynamic } = self.borrow_mut_global();
518 let global_deposit_opt: Option<&mut GlobalDeposit> =
519 get_mut_global_deposit(fixed, dynamic, trader);
520 require!(
521 global_deposit_opt.is_some(),
522 crate::program::ManifestError::MissingGlobal,
523 "Could not find global deposit for {}",
524 trader
525 )?;
526 let global_deposit: &mut GlobalDeposit = global_deposit_opt.unwrap();
527 global_deposit.balance_atoms = global_deposit.balance_atoms.checked_add(num_atoms)?;
528
529 Ok(())
530 }
531
532 pub fn withdraw_global(&mut self, trader: &Pubkey, num_atoms: GlobalAtoms) -> ProgramResult {
534 let DynamicAccount { fixed, dynamic } = self.borrow_mut_global();
535 let global_deposit_opt: Option<&mut GlobalDeposit> =
536 get_mut_global_deposit(fixed, dynamic, trader);
537 require!(
538 global_deposit_opt.is_some(),
539 crate::program::ManifestError::MissingGlobal,
540 "Could not find global deposit for {}",
541 trader
542 )?;
543 let global_deposit: &mut GlobalDeposit = global_deposit_opt.unwrap();
544 global_deposit.balance_atoms = global_deposit.balance_atoms.checked_sub(num_atoms)?;
546
547 Ok(())
548 }
549}
550
551fn get_free_address_on_global_fixed(fixed: &mut GlobalFixed, dynamic: &mut [u8]) -> DataIndex {
552 let mut free_list: FreeList<GlobalUnusedFreeListPadding> =
553 FreeList::new(dynamic, fixed.free_list_head_index);
554 let free_address: DataIndex = free_list.remove();
555 fixed.free_list_head_index = free_list.get_head();
556 free_address
557}
558
559fn get_global_trader<'a>(
560 fixed: &'a GlobalFixed,
561 dynamic: &'a [u8],
562 trader: &'a Pubkey,
563) -> Option<&'a GlobalTrader> {
564 let global_trader_tree: GlobalTraderTreeReadOnly =
565 GlobalTraderTreeReadOnly::new(dynamic, fixed.global_traders_root_index, NIL);
566 let global_trader_index: DataIndex =
567 global_trader_tree.lookup_index(&GlobalTrader::new_empty(trader, NIL));
568 if global_trader_index == NIL {
569 return None;
570 }
571 let global_trader: &GlobalTrader =
572 get_helper::<RBNode<GlobalTrader>>(dynamic, global_trader_index).get_value();
573 Some(global_trader)
574}
575
576fn get_mut_global_deposit<'a>(
577 fixed: &'a mut GlobalFixed,
578 dynamic: &'a mut [u8],
579 trader: &'a Pubkey,
580) -> Option<&'a mut GlobalDeposit> {
581 let global_trader_tree: GlobalTraderTree =
582 GlobalTraderTree::new(dynamic, fixed.global_traders_root_index, NIL);
583 let global_trader_index: DataIndex =
584 global_trader_tree.lookup_index(&GlobalTrader::new_empty(trader, NIL));
585 if global_trader_index == NIL {
586 return None;
587 }
588 let global_trader: &GlobalTrader =
589 get_helper::<RBNode<GlobalTrader>>(dynamic, global_trader_index).get_value();
590 let global_deposit_index: DataIndex = global_trader.deposit_index;
591 Some(get_mut_helper::<RBNode<GlobalDeposit>>(dynamic, global_deposit_index).get_mut_value())
592}
593
594fn get_global_deposit<'a>(
595 fixed: &'a GlobalFixed,
596 dynamic: &'a [u8],
597 trader: &'a Pubkey,
598) -> Option<&'a GlobalDeposit> {
599 let global_trader_tree: GlobalTraderTreeReadOnly =
600 GlobalTraderTreeReadOnly::new(dynamic, fixed.global_traders_root_index, NIL);
601 let global_trader_index: DataIndex =
602 global_trader_tree.lookup_index(&GlobalTrader::new_empty(trader, NIL));
603 if global_trader_index == NIL {
604 return None;
605 }
606 let global_trader: &GlobalTrader =
607 get_helper::<RBNode<GlobalTrader>>(dynamic, global_trader_index).get_value();
608 let global_deposit_index: DataIndex = global_trader.deposit_index;
609 Some(get_helper::<RBNode<GlobalDeposit>>(dynamic, global_deposit_index).get_value())
610}
611
612#[cfg(test)]
613mod test {
614 use super::*;
615
616 #[test]
617 fn test_display_trader() {
618 format!("{}", GlobalTrader::default());
619 }
620
621 #[test]
622 fn test_cmp_trader() {
623 let global_trader1: GlobalTrader = GlobalTrader::new_empty(&spl_token::id(), NIL);
625 let global_trader2: GlobalTrader = GlobalTrader::new_empty(&spl_token_2022::id(), NIL);
626 assert!(global_trader1 < global_trader2);
627 assert!(global_trader1 != global_trader2);
628 }
629
630 #[test]
631 fn test_display_deposit() {
632 format!("{}", GlobalDeposit::default());
633 }
634
635 #[test]
636 fn test_cmp_deposit() {
637 let global_deposit1: GlobalDeposit = GlobalDeposit::new_empty(&Pubkey::new_unique());
638 let mut global_deposit2: GlobalDeposit = GlobalDeposit::new_empty(&Pubkey::new_unique());
639 global_deposit2.balance_atoms = GlobalAtoms::new(1);
640 assert!(global_deposit1 > global_deposit2);
642 assert!(global_deposit1 != global_deposit2);
643 }
644}