use crate::{
error::DexError,
state::{AccountTag, CallBackInfo, DexState, MarketFeeType},
utils::{check_account_owner, check_metadata_account, verify_metadata},
};
use asset_agnostic_orderbook::error::AoError;
use bonfida_utils::checks::check_rent_exempt;
use bonfida_utils::BorshSize;
use bonfida_utils::InstructionsAccount;
use borsh::BorshDeserialize;
use borsh::BorshSerialize;
use bytemuck::{try_from_bytes, Pod, Zeroable};
use mpl_token_metadata::state::{Metadata, TokenMetadataAccount};
use solana_program::{
account_info::{next_account_info, AccountInfo},
clock::Clock,
entrypoint::ProgramResult,
msg,
program_error::{PrintProgramError, ProgramError},
program_pack::Pack,
pubkey::Pubkey,
sysvar::Sysvar,
};
#[derive(Copy, Clone, Zeroable, Pod, BorshDeserialize, BorshSerialize, BorshSize)]
#[repr(C)]
pub struct Params {
pub signer_nonce: u64,
pub min_base_order_size: u64,
pub tick_size: u64,
pub base_currency_multiplier: u64,
pub quote_currency_multiplier: u64,
}
#[derive(InstructionsAccount)]
pub struct Accounts<'a, T> {
#[cons(writable)]
pub market: &'a T,
#[cons(writable)]
pub orderbook: &'a T,
pub base_vault: &'a T,
pub quote_vault: &'a T,
pub market_admin: &'a T,
#[cons(writable)]
pub event_queue: &'a T,
#[cons(writable)]
pub asks: &'a T,
#[cons(writable)]
pub bids: &'a T,
pub token_metadata: &'a T,
}
impl<'a, 'b: 'a> Accounts<'a, AccountInfo<'b>> {
pub fn parse(
program_id: &Pubkey,
accounts: &'a [AccountInfo<'b>],
) -> Result<Self, ProgramError> {
let accounts_iter = &mut accounts.iter();
let a = Self {
market: next_account_info(accounts_iter)?,
orderbook: next_account_info(accounts_iter)?,
base_vault: next_account_info(accounts_iter)?,
quote_vault: next_account_info(accounts_iter)?,
market_admin: next_account_info(accounts_iter)?,
event_queue: next_account_info(accounts_iter)?,
asks: next_account_info(accounts_iter)?,
bids: next_account_info(accounts_iter)?,
token_metadata: next_account_info(accounts_iter)?,
};
check_account_owner(a.market, program_id, DexError::InvalidStateAccountOwner)?;
check_account_owner(a.orderbook, program_id, DexError::InvalidStateAccountOwner)?;
check_account_owner(
a.base_vault,
&spl_token::ID,
DexError::InvalidStateAccountOwner,
)?;
check_account_owner(
a.quote_vault,
&spl_token::ID,
DexError::InvalidStateAccountOwner,
)?;
Ok(a)
}
}
pub(crate) fn process(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
let accounts = Accounts::parse(program_id, accounts)?;
check_rent(&accounts)?;
let Params {
signer_nonce,
min_base_order_size,
tick_size,
base_currency_multiplier,
quote_currency_multiplier,
} = try_from_bytes(instruction_data).map_err(|_| ProgramError::InvalidInstructionData)?;
if base_currency_multiplier == &0 || quote_currency_multiplier == &0 || tick_size == &0 {
msg!("The currency multipliers and ticksize should be nonzero!");
return Err(ProgramError::InvalidArgument);
}
let market_signer = Pubkey::create_program_address(
&[&accounts.market.key.to_bytes(), &[*signer_nonce as u8]],
program_id,
)?;
let base_mint = check_vault_account_and_get_mint(accounts.base_vault, &market_signer)?;
let quote_mint = check_vault_account_and_get_mint(accounts.quote_vault, &market_signer)?;
#[cfg(not(feature = "disable-mpl-checks"))]
check_metadata_account(accounts.token_metadata, &base_mint)?;
let current_timestamp = Clock::get()?.unix_timestamp;
if accounts.market.data.borrow()[0] != AccountTag::Uninitialized as u8 {
msg!("The market account contains initialized state!");
return Err(ProgramError::InvalidArgument);
}
let mut market_state = DexState::get_unchecked(accounts.market);
let royalties_bps = if accounts.token_metadata.data_len() != 0 {
let metadata: Metadata = Metadata::from_account_info(accounts.token_metadata)?;
if let Some(creators) = &metadata.data.creators {
#[cfg(not(feature = "disable-mpl-checks"))]
verify_metadata(creators)?;
metadata.data.seller_fee_basis_points
} else {
0
}
} else {
0
};
*market_state = DexState {
tag: AccountTag::DexState as u64,
signer_nonce: *signer_nonce as u8,
base_mint,
quote_mint,
base_vault: *accounts.base_vault.key,
quote_vault: *accounts.quote_vault.key,
orderbook: *accounts.orderbook.key,
admin: *accounts.market_admin.key,
creation_timestamp: current_timestamp,
base_volume: 0,
quote_volume: 0,
accumulated_fees: 0,
min_base_order_size: *min_base_order_size,
fee_type: MarketFeeType::Default as u8,
_padding: [0; 6],
royalties_bps: royalties_bps as u64,
accumulated_royalties: 0,
base_currency_multiplier: *base_currency_multiplier,
quote_currency_multiplier: *quote_currency_multiplier,
};
let invoke_params = asset_agnostic_orderbook::instruction::create_market::Params {
min_base_order_size: *min_base_order_size / *base_currency_multiplier,
tick_size: *tick_size,
};
let invoke_accounts = asset_agnostic_orderbook::instruction::create_market::Accounts {
market: accounts.orderbook,
event_queue: accounts.event_queue,
bids: accounts.bids,
asks: accounts.asks,
};
if let Err(error) = asset_agnostic_orderbook::instruction::create_market::process::<CallBackInfo>(
program_id,
invoke_accounts,
invoke_params,
) {
error.print::<AoError>();
return Err(DexError::AOBError.into());
}
Ok(())
}
fn check_vault_account_and_get_mint(
account: &AccountInfo,
market_signer: &Pubkey,
) -> Result<Pubkey, ProgramError> {
let acc = spl_token::state::Account::unpack(&account.data.borrow())?;
if &acc.owner != market_signer {
msg!("The vault account should be owned by the market signer");
return Err(ProgramError::InvalidArgument);
}
if acc.close_authority.is_some() || acc.delegate.is_some() {
msg!("Invalid vault account provided");
return Err(ProgramError::InvalidArgument);
}
Ok(acc.mint)
}
fn check_rent<'a>(accounts: &Accounts<'a, AccountInfo>) -> ProgramResult {
check_rent_exempt(accounts.market)?;
check_rent_exempt(accounts.orderbook)?;
check_rent_exempt(accounts.base_vault)?;
check_rent_exempt(accounts.quote_vault)?;
check_rent_exempt(accounts.event_queue)?;
check_rent_exempt(accounts.asks)?;
check_rent_exempt(accounts.bids)?;
Ok(())
}