use crate::{
program::{
dispatch_market::load_with_dispatch_init,
error::{assert_with_msg, PhoenixError},
loaders::{get_vault_address, InitializeMarketContext},
system_utils::create_account,
MarketHeader, MarketSizeParams, PhoenixMarketContext, TokenParams,
},
quantities::{
BaseAtomsPerBaseUnit, BaseLotsPerBaseUnit, QuoteAtomsPerQuoteUnit,
QuoteLotsPerBaseUnitPerTick, QuoteLotsPerQuoteUnit, WrapperU64,
},
};
use borsh::{BorshDeserialize, BorshSerialize};
use solana_program::{
account_info::AccountInfo, entrypoint::ProgramResult, program::invoke,
program_error::ProgramError, program_pack::Pack, pubkey::Pubkey, rent::Rent, sysvar::Sysvar,
};
use std::{mem::size_of, ops::DerefMut};
#[derive(BorshDeserialize, BorshSerialize)]
pub struct InitializeParams {
pub market_size_params: MarketSizeParams,
pub num_quote_lots_per_quote_unit: u64,
pub tick_size_in_quote_lots_per_base_unit: u64,
pub num_base_lots_per_base_unit: u64,
pub taker_fee_bps: u16,
pub fee_collector: Pubkey,
pub raw_base_units_per_base_unit: Option<u32>,
}
pub(crate) fn process_initialize_market<'a, 'info>(
_program_id: &Pubkey,
market_context: &PhoenixMarketContext<'a, 'info>,
accounts: &'a [AccountInfo<'info>],
data: &[u8],
) -> ProgramResult {
let PhoenixMarketContext {
market_info,
signer: market_creator,
} = market_context;
let InitializeMarketContext {
base_mint,
quote_mint,
base_vault,
quote_vault,
system_program,
token_program,
..
} = InitializeMarketContext::load(accounts)?;
let InitializeParams {
market_size_params,
tick_size_in_quote_lots_per_base_unit,
num_quote_lots_per_quote_unit,
num_base_lots_per_base_unit,
taker_fee_bps,
fee_collector,
raw_base_units_per_base_unit,
} = InitializeParams::try_from_slice(data)?;
let tick_size_in_quote_lots_per_base_unit =
QuoteLotsPerBaseUnitPerTick::new(tick_size_in_quote_lots_per_base_unit);
let num_quote_lots_per_quote_unit = QuoteLotsPerQuoteUnit::new(num_quote_lots_per_quote_unit);
let num_base_lots_per_base_unit = BaseLotsPerBaseUnit::new(num_base_lots_per_base_unit);
assert_with_msg(
taker_fee_bps <= 10000,
ProgramError::InvalidInstructionData,
"Taker fee must be less than or equal to 10000 basis points (100%)",
)?;
let base_atoms_per_base_unit = BaseAtomsPerBaseUnit::new(
10u64.pow(base_mint.decimals as u32) * raw_base_units_per_base_unit.unwrap_or(1) as u64,
);
let quote_atoms_per_quote_unit =
QuoteAtomsPerQuoteUnit::new(10u64.pow(quote_mint.decimals as u32));
assert_with_msg(
base_atoms_per_base_unit % num_base_lots_per_base_unit == 0,
PhoenixError::InvalidLotSize,
&format!(
"Base lots per base unit ({}) must be a factor of base atoms per base unit ({})",
num_base_lots_per_base_unit, base_atoms_per_base_unit
),
)?;
assert_with_msg(
quote_atoms_per_quote_unit % num_quote_lots_per_quote_unit == 0,
PhoenixError::InvalidLotSize,
&format!(
"Quote lots per quote unit ({}) must be a factor of quote atoms per quote unit ({})",
num_quote_lots_per_quote_unit, quote_atoms_per_quote_unit
),
)?;
let quote_lot_size = quote_atoms_per_quote_unit / num_quote_lots_per_quote_unit;
let tick_size_in_quote_atoms_per_base_unit =
quote_lot_size * tick_size_in_quote_lots_per_base_unit;
phoenix_log!(
"Market parameters:
num_quote_lots_per_quote_unit: {},
tick_size_in_quote_lots_per_base_unit: {},
num_base_lots_per_base_unit: {},
tick_size_in_quote_atoms_per_base_unit: {},",
num_quote_lots_per_quote_unit,
tick_size_in_quote_lots_per_base_unit,
num_base_lots_per_base_unit,
tick_size_in_quote_atoms_per_base_unit,
);
assert_with_msg(
tick_size_in_quote_lots_per_base_unit % num_base_lots_per_base_unit == 0,
ProgramError::InvalidInstructionData,
"The number of quote lots per tick be a multiple of the number of base lots per base unit",
)?;
let rent = Rent::get()?;
let mut bumps = vec![];
for (token_account, mint) in [
(base_vault.as_ref(), base_mint.as_ref()),
(quote_vault.as_ref(), quote_mint.as_ref()),
] {
let (vault_key, bump) = get_vault_address(market_info.key, mint.key);
assert_with_msg(
vault_key == *token_account.key,
PhoenixError::InvalidMarketSigner,
&format!(
"Supplied vault ({}) does not match computed key ({})",
token_account.key, vault_key
),
)?;
let space = spl_token::state::Account::LEN;
let seeds = vec![
b"vault".to_vec(),
market_info.key.as_ref().to_vec(),
mint.key.as_ref().to_vec(),
vec![bump],
];
create_account(
market_creator.as_ref(),
token_account,
system_program.as_ref(),
&spl_token::id(),
&rent,
space as u64,
seeds,
)?;
invoke(
&spl_token::instruction::initialize_account3(
&spl_token::id(),
token_account.key,
mint.key,
token_account.key,
)?,
&[
market_creator.as_ref().clone(),
token_account.clone(),
mint.clone(),
token_program.as_ref().clone(),
],
)?;
bumps.push(bump);
}
{
let market_bytes = &mut market_info.try_borrow_mut_data()?[size_of::<MarketHeader>()..];
let market = load_with_dispatch_init(&market_size_params, market_bytes)?.inner;
assert_with_msg(
market.get_sequence_number() == 0,
PhoenixError::MarketAlreadyInitialized,
"Market must have a sequence number of 0",
)?;
market.initialize_with_params(
tick_size_in_quote_lots_per_base_unit,
num_base_lots_per_base_unit,
);
market.set_fee(taker_fee_bps as u64);
}
let mut header = market_info.get_header_mut()?;
*header.deref_mut() = MarketHeader::new(
market_size_params,
TokenParams {
vault_bump: bumps[0] as u32,
decimals: base_mint.decimals as u32,
mint_key: *base_mint.as_ref().key,
vault_key: *base_vault.key,
},
base_atoms_per_base_unit / num_base_lots_per_base_unit,
TokenParams {
vault_bump: bumps[1] as u32,
decimals: quote_mint.decimals as u32,
mint_key: *quote_mint.as_ref().key,
vault_key: *quote_vault.key,
},
quote_lot_size,
tick_size_in_quote_atoms_per_base_unit,
*market_creator.key,
*market_creator.key,
fee_collector,
raw_base_units_per_base_unit.unwrap_or(1),
);
drop(header);
Ok(())
}