phoenix/
lib.rs

1//! Phoenix is a limit order book exchange on the Solana blockchain.
2//!
3//! It exposes a set of instructions to create, cancel, and fill orders.
4//! Each event that modifies the state of the book is recorded in an event log which can
5//! be queried from a transaction signature after each transaction is confirmed. This
6//! allows clients to build their own order book and trade history.
7//!
8//! The program is able to atomically match orders and settle trades on chain. This
9//! is because each market has a fixed set of users that are allowed to place limit
10//! orders on the book. Users who swap against the book will have their funds settle
11//! instantaneously, while the funds of users who place orders on the book will be
12//! immediately available for withdraw post fill.
13//!
14
15#[macro_use]
16mod log;
17pub mod program;
18pub mod quantities;
19// Note this mod is private and only exists for the purposes of IDL generation
20mod shank_structs;
21pub mod state;
22
23use crate::program::processor::*;
24
25use borsh::BorshSerialize;
26// You need to import Pubkey prior to using the declare_id macro
27use ellipsis_macros::declare_id;
28use solana_program::{program::set_return_data, pubkey::Pubkey};
29
30use program::{
31    assert_with_msg, event_recorder::EventRecorder, PhoenixInstruction, PhoenixLogContext,
32    PhoenixMarketContext,
33};
34use solana_program::{
35    account_info::{next_account_info, AccountInfo},
36    entrypoint::ProgramResult,
37    program_error::ProgramError,
38};
39use state::markets::MarketEvent;
40
41#[cfg(not(feature = "no-entrypoint"))]
42use solana_security_txt::security_txt;
43
44#[cfg(not(feature = "no-entrypoint"))]
45security_txt! {
46    // Required fields
47    name: "Phoenix V1",
48    project_url: "https://ellipsislabs.xyz/",
49    contacts: "email:maintainers@ellipsislabs.xyz",
50    policy: "https://github.com/Ellipsis-Labs/phoenix-v1/blob/master/SECURITY.md",
51    // Optional Fields
52    preferred_languages: "en",
53    source_code: "https://github.com/Ellipsis-Labs/phoenix-v1",
54    auditors: "contact@osec.io"
55}
56
57declare_id!("PhoeNiXZ8ByJGLkxNfZRnkUfjvmuYqLR89jjFHGqdXY");
58
59/// This is a static PDA with seeds: [b"log"]
60/// If the program id changes, this will also need to be updated
61pub mod phoenix_log_authority {
62    // You need to import Pubkey prior to using the declare_pda macro
63    use ellipsis_macros::declare_pda;
64    use solana_program::pubkey::Pubkey;
65
66    // This creates a static PDA with seeds: [b"log"]
67    // The address of the PDA is 7aDTsspkQNGKmrexAN7FLx9oxU3iPczSSvHNggyuqYkR
68    // The bump seed is stored in a variable called bump()
69    declare_pda!(
70        "7aDTsspkQNGKmrexAN7FLx9oxU3iPczSSvHNggyuqYkR",
71        "PhoeNiXZ8ByJGLkxNfZRnkUfjvmuYqLR89jjFHGqdXY",
72        "log"
73    );
74
75    #[test]
76    fn check_pda() {
77        use crate::phoenix_log_authority;
78        use solana_program::pubkey::Pubkey;
79        assert_eq!(
80            phoenix_log_authority::ID,
81            Pubkey::create_program_address(
82                &["log".as_ref(), &[phoenix_log_authority::bump()]],
83                &super::id()
84            )
85            .unwrap()
86        );
87    }
88}
89
90#[cfg(not(feature = "no-entrypoint"))]
91solana_program::entrypoint!(process_instruction);
92
93pub fn process_instruction(
94    program_id: &Pubkey,
95    accounts: &[AccountInfo],
96    instruction_data: &[u8],
97) -> ProgramResult {
98    let (tag, data) = instruction_data
99        .split_first()
100        .ok_or(ProgramError::InvalidInstructionData)?;
101
102    let instruction =
103        PhoenixInstruction::try_from(*tag).or(Err(ProgramError::InvalidInstructionData))?;
104
105    // This is a special instruction that is only used for recording
106    // inner instruction data from recursive CPI calls.
107    //
108    // Market events can be searched by querying the transaction hash and parsing
109    // the inner instruction data according to a pre-defined schema.
110    //
111    // Only the log authority is allowed to call this instruction.
112    if let PhoenixInstruction::Log = instruction {
113        let authority = next_account_info(&mut accounts.iter())?;
114        assert_with_msg(
115            authority.is_signer,
116            ProgramError::MissingRequiredSignature,
117            "Log authority must sign through CPI",
118        )?;
119        assert_with_msg(
120            authority.key == &phoenix_log_authority::id(),
121            ProgramError::InvalidArgument,
122            "Invalid log authority",
123        )?;
124        return Ok(());
125    }
126
127    let (program_accounts, accounts) = accounts.split_at(4);
128    let accounts_iter = &mut program_accounts.iter();
129    let phoenix_log_context = PhoenixLogContext::load(accounts_iter)?;
130    let market_context = if instruction == PhoenixInstruction::InitializeMarket {
131        PhoenixMarketContext::load_init(accounts_iter)?
132    } else {
133        PhoenixMarketContext::load(accounts_iter)?
134    };
135
136    let mut event_recorder = EventRecorder::new(phoenix_log_context, &market_context, instruction)?;
137
138    let mut record_event_fn = |e: MarketEvent<Pubkey>| event_recorder.add_event(e);
139    let mut order_ids = Vec::new();
140
141    match instruction {
142        PhoenixInstruction::InitializeMarket => {
143            phoenix_log!("PhoenixInstruction::Initialize");
144            initialize::process_initialize_market(program_id, &market_context, accounts, data)?
145        }
146        PhoenixInstruction::Swap => {
147            phoenix_log!("PhoenixInstruction::Swap");
148            new_order::process_swap(
149                program_id,
150                &market_context,
151                accounts,
152                data,
153                &mut record_event_fn,
154            )?;
155        }
156        PhoenixInstruction::SwapWithFreeFunds => {
157            phoenix_log!("PhoenixInstruction::SwapWithFreeFunds");
158            new_order::process_swap_with_free_funds(
159                program_id,
160                &market_context,
161                accounts,
162                data,
163                &mut record_event_fn,
164            )?;
165        }
166        PhoenixInstruction::PlaceLimitOrder => {
167            phoenix_log!("PhoenixInstruction::PlaceLimitOrder");
168            new_order::process_place_limit_order(
169                program_id,
170                &market_context,
171                accounts,
172                data,
173                &mut record_event_fn,
174                &mut order_ids,
175            )?
176        }
177        PhoenixInstruction::PlaceLimitOrderWithFreeFunds => {
178            phoenix_log!("PhoenixInstruction::PlaceLimitOrderWithFreeFunds");
179            new_order::process_place_limit_order_with_free_funds(
180                program_id,
181                &market_context,
182                accounts,
183                data,
184                &mut record_event_fn,
185                &mut order_ids,
186            )?;
187        }
188        PhoenixInstruction::PlaceMultiplePostOnlyOrders => {
189            phoenix_log!("PhoenixInstruction::PlaceMultiplePostOnlyOrders");
190            new_order::process_place_multiple_post_only_orders(
191                program_id,
192                &market_context,
193                accounts,
194                data,
195                &mut record_event_fn,
196                &mut order_ids,
197            )?;
198        }
199        PhoenixInstruction::PlaceMultiplePostOnlyOrdersWithFreeFunds => {
200            phoenix_log!("PhoenixInstruction::PlaceMultiplePostOnlyOrdersWithFreeFunds");
201            new_order::process_place_multiple_post_only_orders_with_free_funds(
202                program_id,
203                &market_context,
204                accounts,
205                data,
206                &mut record_event_fn,
207                &mut order_ids,
208            )?;
209        }
210        PhoenixInstruction::ReduceOrder => {
211            phoenix_log!("PhoenixInstruction::ReduceOrder");
212            reduce_order::process_reduce_order(
213                program_id,
214                &market_context,
215                accounts,
216                data,
217                true,
218                &mut record_event_fn,
219            )?
220        }
221        PhoenixInstruction::ReduceOrderWithFreeFunds => {
222            phoenix_log!("PhoenixInstruction::ReduceOrderWithFreeFunds");
223            reduce_order::process_reduce_order(
224                program_id,
225                &market_context,
226                accounts,
227                data,
228                false,
229                &mut record_event_fn,
230            )?
231        }
232        PhoenixInstruction::CancelAllOrders => {
233            phoenix_log!("PhoenixInstruction::CancelAllOrders");
234            cancel_multiple_orders::process_cancel_all_orders(
235                program_id,
236                &market_context,
237                accounts,
238                data,
239                true,
240                &mut record_event_fn,
241            )?
242        }
243        PhoenixInstruction::CancelAllOrdersWithFreeFunds => {
244            phoenix_log!("PhoenixInstruction::CancelAllOrdersWithFreeFunds");
245            cancel_multiple_orders::process_cancel_all_orders(
246                program_id,
247                &market_context,
248                accounts,
249                data,
250                false,
251                &mut record_event_fn,
252            )?
253        }
254        PhoenixInstruction::CancelUpTo => {
255            phoenix_log!("PhoenixInstruction::CancelMultipleOrders");
256            cancel_multiple_orders::process_cancel_up_to(
257                program_id,
258                &market_context,
259                accounts,
260                data,
261                true,
262                &mut record_event_fn,
263            )?
264        }
265        PhoenixInstruction::CancelUpToWithFreeFunds => {
266            phoenix_log!("PhoenixInstruction::CancelUpToWithFreeFunds");
267            cancel_multiple_orders::process_cancel_up_to(
268                program_id,
269                &market_context,
270                accounts,
271                data,
272                false,
273                &mut record_event_fn,
274            )?
275        }
276        PhoenixInstruction::CancelMultipleOrdersById => {
277            phoenix_log!("PhoenixInstruction::CancelMultipleOrdersById");
278            cancel_multiple_orders::process_cancel_multiple_orders_by_id(
279                program_id,
280                &market_context,
281                accounts,
282                data,
283                true,
284                &mut record_event_fn,
285            )?
286        }
287        PhoenixInstruction::CancelMultipleOrdersByIdWithFreeFunds => {
288            phoenix_log!("PhoenixInstruction::CancelMultipleOrdersByIdWithFreeFunds");
289            cancel_multiple_orders::process_cancel_multiple_orders_by_id(
290                program_id,
291                &market_context,
292                accounts,
293                data,
294                false,
295                &mut record_event_fn,
296            )?
297        }
298        PhoenixInstruction::WithdrawFunds => {
299            phoenix_log!("PhoenixInstruction::WithdrawFunds");
300            withdraw::process_withdraw_funds(program_id, &market_context, accounts, data)?;
301        }
302        PhoenixInstruction::DepositFunds => {
303            phoenix_log!("PhoenixInstruction::DepositFunds");
304            deposit::process_deposit_funds(program_id, &market_context, accounts, data)?
305        }
306        PhoenixInstruction::ForceCancelOrders => {
307            phoenix_log!("PhoenixInstruction::ForceCancelOrders");
308            governance::process_force_cancel_orders(
309                program_id,
310                &market_context,
311                accounts,
312                data,
313                &mut record_event_fn,
314            )?
315        }
316        PhoenixInstruction::EvictSeat => {
317            phoenix_log!("PhoenixInstruction::EvictSeat");
318            governance::process_evict_seat(program_id, &market_context, accounts, data)?
319        }
320        PhoenixInstruction::ClaimAuthority => {
321            phoenix_log!("PhoenixInstruction::ClaimAuthority");
322            governance::process_claim_authority(program_id, &market_context, data)?
323        }
324        PhoenixInstruction::NameSuccessor => {
325            phoenix_log!("PhoenixInstruction::NameSuccessor");
326            governance::process_name_successor(program_id, &market_context, data)?
327        }
328        PhoenixInstruction::ChangeMarketStatus => {
329            phoenix_log!("PhoenixInstruction::ChangeMarketStatus");
330            governance::process_change_market_status(program_id, &market_context, accounts, data)?
331        }
332        PhoenixInstruction::RequestSeatAuthorized => {
333            phoenix_log!("PhoenixInstruction::RequestSeatAuthorized");
334            manage_seat::process_request_seat_authorized(
335                program_id,
336                &market_context,
337                accounts,
338                data,
339            )?
340        }
341        PhoenixInstruction::RequestSeat => {
342            phoenix_log!("PhoenixInstruction::RequestSeat");
343            manage_seat::process_request_seat(program_id, &market_context, accounts, data)?
344        }
345        PhoenixInstruction::ChangeSeatStatus => {
346            phoenix_log!("PhoenixInstruction::ChangeSeatStatus");
347            manage_seat::process_change_seat_status(program_id, &market_context, accounts, data)?;
348        }
349        PhoenixInstruction::CollectFees => {
350            phoenix_log!("PhoenixInstruction::CollectFees");
351            fees::process_collect_fees(
352                program_id,
353                &market_context,
354                accounts,
355                data,
356                &mut record_event_fn,
357            )?
358        }
359        PhoenixInstruction::ChangeFeeRecipient => {
360            phoenix_log!("PhoenixInstruction::ChangeFeeRecipient");
361            fees::process_change_fee_recipient(program_id, &market_context, accounts, data)?
362        }
363        _ => unreachable!(),
364    }
365    event_recorder.increment_market_sequence_number_and_flush(market_context.market_info)?;
366    // We set the order ids at the end of the instruction because the return data gets cleared after
367    // every CPI call.
368    if !order_ids.is_empty() {
369        set_return_data(order_ids.try_to_vec()?.as_ref());
370    }
371    Ok(())
372}