phoenix/program/processor/
initialize.rs

1use crate::{
2    program::{
3        dispatch_market::load_with_dispatch_init,
4        error::{assert_with_msg, PhoenixError},
5        loaders::{get_vault_address, InitializeMarketContext},
6        system_utils::create_account,
7        MarketHeader, MarketSizeParams, PhoenixMarketContext, TokenParams,
8    },
9    quantities::{
10        BaseAtomsPerBaseUnit, BaseLotsPerBaseUnit, QuoteAtomsPerQuoteUnit,
11        QuoteLotsPerBaseUnitPerTick, QuoteLotsPerQuoteUnit, WrapperU64,
12    },
13};
14use borsh::{BorshDeserialize, BorshSerialize};
15use solana_program::{
16    account_info::AccountInfo, entrypoint::ProgramResult, program::invoke,
17    program_error::ProgramError, program_pack::Pack, pubkey::Pubkey, rent::Rent, sysvar::Sysvar,
18};
19use std::{mem::size_of, ops::DerefMut};
20
21#[derive(BorshDeserialize, BorshSerialize)]
22pub struct InitializeParams {
23    /// These parameters define the number of orders on each side of the market as well as the maximum
24    /// number of supported traders. They are used to deserialize the market state (see `dispatch_market.rs`).
25    pub market_size_params: MarketSizeParams,
26
27    /// Number of quote lots to make up a full quote unit. Quote lots are the smallest measurement for
28    /// the quote currency that can be processed by the market. 1 "unit" is the standard measure of a currency
29    /// (e.g. 1 US Dollar, 1 Euro, or 1 BTC).
30    ///
31    /// Assume the quote mint is USDC.
32    /// If num_quote_lots_per_quote_unit is equal to 10000, this means that the smallest unit that the exchange
33    /// can process is $0.0001. Because USDC has 6 decimals, this means the equivalent quote_lot_size (quote atoms per quote lot)
34    /// is equal to 1e6 / 10000 = 100.
35    pub num_quote_lots_per_quote_unit: u64,
36
37    /// Tick size, in quote lots per base units. A tick is the smallest price increment for a market.
38    ///
39    /// Assume the quote mint is USDC and num_quote_lots_per_quote_unit is equal to 10000 (quote_lot_size = 100).
40    /// If tick size is equal to $0.01 (10000 atoms), this means that tick_size_in_quote_lots_per_base_unit is equal to
41    /// tick_size / quote_lot_size = 10000 / 100 = 100.
42    pub tick_size_in_quote_lots_per_base_unit: u64,
43
44    /// Number of base lots to make up a full base unit. Base lots are the smallest measurement for
45    /// the base currency that can be processed by the market.
46    ///
47    /// Assume the base mint is SOL.
48    /// If num_base_lots_per_base_unit is equal to 1000, this means that the smallest unit that the exchange
49    /// can process is 0.0001 SOL. Because SOL has 9 decimals, this means the equivalent base_lot_size is equal
50    /// to 1e9 / 1000 = 1e6.
51    pub num_base_lots_per_base_unit: u64,
52
53    /// Market fee charged to takers, in basis points (0.01%). This fee is charged on the quote currency.
54    pub taker_fee_bps: u16,
55
56    /// The Pubkey of the account that will receive fees for this market.
57    pub fee_collector: Pubkey,
58
59    /// 1 raw base unit is defined as 10^base_mint_decimals atoms.
60    /// By default, raw_base_units_per_base_unit is set to 1 (if the Option is passed in as `None`).
61    /// It is highly recommended to be a power of 10.
62    ///
63    /// If this parameter is supplied, the market will treat the number of base atoms in a base unit as
64    /// `(10^base_mint_decimals) * raw_base_units_per_base_unit`.
65    pub raw_base_units_per_base_unit: Option<u32>,
66}
67
68pub(crate) fn process_initialize_market<'a, 'info>(
69    _program_id: &Pubkey,
70    market_context: &PhoenixMarketContext<'a, 'info>,
71    accounts: &'a [AccountInfo<'info>],
72    data: &[u8],
73) -> ProgramResult {
74    let PhoenixMarketContext {
75        market_info,
76        signer: market_creator,
77    } = market_context;
78    let InitializeMarketContext {
79        base_mint,
80        quote_mint,
81        base_vault,
82        quote_vault,
83        system_program,
84        token_program,
85        ..
86    } = InitializeMarketContext::load(accounts)?;
87
88    let InitializeParams {
89        market_size_params,
90        tick_size_in_quote_lots_per_base_unit,
91        num_quote_lots_per_quote_unit,
92        num_base_lots_per_base_unit,
93        taker_fee_bps,
94        fee_collector,
95        raw_base_units_per_base_unit,
96    } = InitializeParams::try_from_slice(data)?;
97
98    let tick_size_in_quote_lots_per_base_unit =
99        QuoteLotsPerBaseUnitPerTick::new(tick_size_in_quote_lots_per_base_unit);
100    let num_quote_lots_per_quote_unit = QuoteLotsPerQuoteUnit::new(num_quote_lots_per_quote_unit);
101    let num_base_lots_per_base_unit = BaseLotsPerBaseUnit::new(num_base_lots_per_base_unit);
102    assert_with_msg(
103        taker_fee_bps <= 10000,
104        ProgramError::InvalidInstructionData,
105        "Taker fee must be less than or equal to 10000 basis points (100%)",
106    )?;
107
108    let base_atoms_per_base_unit = BaseAtomsPerBaseUnit::new(
109        10u64.pow(base_mint.decimals as u32) * raw_base_units_per_base_unit.unwrap_or(1) as u64,
110    );
111    let quote_atoms_per_quote_unit =
112        QuoteAtomsPerQuoteUnit::new(10u64.pow(quote_mint.decimals as u32));
113
114    assert_with_msg(
115        base_atoms_per_base_unit % num_base_lots_per_base_unit == 0,
116        PhoenixError::InvalidLotSize,
117        &format!(
118            "Base lots per base unit ({}) must be a factor of base atoms per base unit ({})",
119            num_base_lots_per_base_unit, base_atoms_per_base_unit
120        ),
121    )?;
122    assert_with_msg(
123        quote_atoms_per_quote_unit % num_quote_lots_per_quote_unit == 0,
124        PhoenixError::InvalidLotSize,
125        &format!(
126            "Quote lots per quote unit ({}) must be a factor of quote atoms per quote unit ({})",
127            num_quote_lots_per_quote_unit, quote_atoms_per_quote_unit
128        ),
129    )?;
130
131    let quote_lot_size = quote_atoms_per_quote_unit / num_quote_lots_per_quote_unit;
132    let tick_size_in_quote_atoms_per_base_unit =
133        quote_lot_size * tick_size_in_quote_lots_per_base_unit;
134
135    phoenix_log!(
136        "Market parameters:
137        num_quote_lots_per_quote_unit: {}, 
138        tick_size_in_quote_lots_per_base_unit: {}, 
139        num_base_lots_per_base_unit: {},
140        tick_size_in_quote_atoms_per_base_unit: {},",
141        num_quote_lots_per_quote_unit,
142        tick_size_in_quote_lots_per_base_unit,
143        num_base_lots_per_base_unit,
144        tick_size_in_quote_atoms_per_base_unit,
145    );
146    // A trade of 1 base lot at the minimum tick price of 1 must result in an integer number of quote lots
147    // Suppose there are T quote lots per tick and there are B base lots per base unit.
148    // At a price of 1 tick per base unit, for a trade of size 1 base lot, the resulting quote lots N must be an integer
149    // T (quote lots/tick) * 1 (tick/base unit) * 1/B (base units/base lots) * 1 (base lots) = N (quote lots)
150    // T/B  = N => B | T (B divides T)
151    assert_with_msg(
152        tick_size_in_quote_lots_per_base_unit % num_base_lots_per_base_unit == 0,
153        ProgramError::InvalidInstructionData,
154        "The number of quote lots per tick be a multiple of the number of base lots per base unit",
155    )?;
156
157    // Create the base and quote vaults of this market
158    let rent = Rent::get()?;
159    let mut bumps = vec![];
160    for (token_account, mint) in [
161        (base_vault.as_ref(), base_mint.as_ref()),
162        (quote_vault.as_ref(), quote_mint.as_ref()),
163    ] {
164        let (vault_key, bump) = get_vault_address(market_info.key, mint.key);
165        assert_with_msg(
166            vault_key == *token_account.key,
167            PhoenixError::InvalidMarketSigner,
168            &format!(
169                "Supplied vault ({}) does not match computed key ({})",
170                token_account.key, vault_key
171            ),
172        )?;
173        let space = spl_token::state::Account::LEN;
174        let seeds = vec![
175            b"vault".to_vec(),
176            market_info.key.as_ref().to_vec(),
177            mint.key.as_ref().to_vec(),
178            vec![bump],
179        ];
180        create_account(
181            market_creator.as_ref(),
182            token_account,
183            system_program.as_ref(),
184            &spl_token::id(),
185            &rent,
186            space as u64,
187            seeds,
188        )?;
189        invoke(
190            &spl_token::instruction::initialize_account3(
191                &spl_token::id(),
192                token_account.key,
193                mint.key,
194                token_account.key,
195            )?,
196            &[
197                market_creator.as_ref().clone(),
198                token_account.clone(),
199                mint.clone(),
200                token_program.as_ref().clone(),
201            ],
202        )?;
203        bumps.push(bump);
204    }
205
206    // Setup the initial market state
207    {
208        let market_bytes = &mut market_info.try_borrow_mut_data()?[size_of::<MarketHeader>()..];
209        let market = load_with_dispatch_init(&market_size_params, market_bytes)?.inner;
210        assert_with_msg(
211            market.get_sequence_number() == 0,
212            PhoenixError::MarketAlreadyInitialized,
213            "Market must have a sequence number of 0",
214        )?;
215
216        market.initialize_with_params(
217            tick_size_in_quote_lots_per_base_unit,
218            num_base_lots_per_base_unit,
219        );
220        market.set_fee(taker_fee_bps as u64);
221    }
222
223    // Populate the header data
224    let mut header = market_info.get_header_mut()?;
225    // All markets are initialized with a status of `PostOnly`
226    *header.deref_mut() = MarketHeader::new(
227        market_size_params,
228        TokenParams {
229            vault_bump: bumps[0] as u32,
230            decimals: base_mint.decimals as u32,
231            mint_key: *base_mint.as_ref().key,
232            vault_key: *base_vault.key,
233        },
234        base_atoms_per_base_unit / num_base_lots_per_base_unit,
235        TokenParams {
236            vault_bump: bumps[1] as u32,
237            decimals: quote_mint.decimals as u32,
238            mint_key: *quote_mint.as_ref().key,
239            vault_key: *quote_vault.key,
240        },
241        quote_lot_size,
242        tick_size_in_quote_atoms_per_base_unit,
243        *market_creator.key,
244        *market_creator.key,
245        fee_collector,
246        raw_base_units_per_base_unit.unwrap_or(1),
247    );
248
249    drop(header);
250    Ok(())
251}