use num_traits::FromPrimitive;
use crate::{
error::DexError,
state::{CallBackInfo, DexState, FeeTier, UserAccount},
utils::{check_account_key, check_account_owner, fp32_mul},
};
use asset_agnostic_orderbook::{
error::AoError,
state::{
event_queue::{EventQueue, EventRef, FillEvent, FillEventRef, OutEvent, OutEventRef},
AccountTag, Side,
},
};
use bonfida_utils::BorshSize;
use bonfida_utils::InstructionsAccount;
use borsh::BorshDeserialize;
use borsh::BorshSerialize;
use bytemuck::{try_from_bytes, Pod, Zeroable};
use solana_program::{
account_info::{next_account_info, AccountInfo},
entrypoint::ProgramResult,
msg,
program_error::{PrintProgramError, ProgramError},
pubkey::Pubkey,
};
#[derive(Copy, Clone, Zeroable, Pod, BorshDeserialize, BorshSerialize, BorshSize)]
#[repr(C)]
pub struct Params {
pub max_iterations: u64,
pub no_op_err: u64,
}
#[derive(InstructionsAccount)]
pub struct Accounts<'a, T> {
#[cons(writable)]
pub market: &'a T,
#[cons(writable)]
pub orderbook: &'a T,
#[cons(writable)]
pub event_queue: &'a T,
#[cons(writable)]
pub reward_target: &'a T,
#[cons(writable)]
pub user_accounts: &'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)?,
event_queue: next_account_info(accounts_iter)?,
reward_target: next_account_info(accounts_iter)?,
user_accounts: accounts_iter.as_slice(),
};
check_account_owner(a.market, program_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)?;
let Params {
max_iterations,
no_op_err,
} = try_from_bytes(instruction_data).map_err(|_| ProgramError::InvalidInstructionData)?;
let mut market_state = DexState::get(accounts.market)?;
let mut event_queue_guard = accounts.event_queue.data.borrow_mut();
let event_queue =
EventQueue::<CallBackInfo>::from_buffer(&mut event_queue_guard, AccountTag::EventQueue)?;
check_accounts(&market_state, &accounts).unwrap();
let mut total_iterations = 0;
for event in event_queue.iter().take(*max_iterations as usize) {
if consume_event(accounts.user_accounts, event, &mut market_state).is_err() {
break;
}
total_iterations += 1;
}
if total_iterations == 0 {
msg!("Failed to complete one iteration");
if *no_op_err == 1 {
return Err(DexError::NoOp.into());
}
return Ok(());
}
drop(event_queue_guard);
let invoke_params = asset_agnostic_orderbook::instruction::consume_events::Params {
number_of_entries_to_consume: total_iterations,
};
let invoke_accounts = asset_agnostic_orderbook::instruction::consume_events::Accounts {
market: accounts.orderbook,
event_queue: accounts.event_queue,
};
if let Err(error) = asset_agnostic_orderbook::instruction::consume_events::process::<CallBackInfo>(
program_id,
invoke_accounts,
invoke_params,
) {
error.print::<AoError>();
return Err(DexError::AOBError.into());
}
Ok(())
}
fn check_accounts(market_state: &DexState, accounts: &Accounts<AccountInfo>) -> ProgramResult {
check_account_key(
accounts.orderbook,
&market_state.orderbook,
DexError::InvalidOrderbookAccount,
)?;
Ok(())
}
fn consume_event(
accounts: &[AccountInfo],
event: EventRef<CallBackInfo>,
market_state: &mut DexState,
) -> Result<(), DexError> {
match event {
EventRef::Fill(FillEventRef {
event,
maker_callback_info,
taker_callback_info,
}) => {
let FillEvent {
tag: _,
taker_side,
mut quote_size,
maker_order_id: _,
mut base_size,
..
} = event;
quote_size = quote_size
.checked_mul(market_state.quote_currency_multiplier)
.unwrap();
base_size = base_size
.checked_mul(market_state.base_currency_multiplier)
.unwrap();
let maker_account_info = &accounts[accounts
.binary_search_by_key(&maker_callback_info.user_account, |k| *k.key)
.map_err(|_| DexError::MissingUserAccount)?];
let (taker_fee_tier, is_referred) = FeeTier::from_u8(taker_callback_info.fee_tier);
let mut maker_account_data = maker_account_info.data.borrow_mut();
let mut maker_account = UserAccount::from_buffer(&mut maker_account_data).unwrap();
let (maker_fee_tier, _) = FeeTier::from_u8(maker_callback_info.fee_tier);
let taker_fee = taker_fee_tier.taker_fee(quote_size);
let maker_rebate = maker_fee_tier.maker_rebate(quote_size);
let royalties_fee =
market_state.royalties_bps.checked_mul(quote_size).unwrap() / 10_000;
let referral_fee = if is_referred {
taker_fee_tier.referral_fee(quote_size)
} else {
0
};
let total_fees = taker_fee
.checked_sub(maker_rebate)
.and_then(|n| n.checked_sub(referral_fee))
.unwrap();
market_state.accumulated_fees = market_state
.accumulated_fees
.checked_add(total_fees)
.unwrap();
market_state.accumulated_royalties = market_state
.accumulated_royalties
.checked_add(royalties_fee)
.unwrap();
match Side::from_u8(*taker_side).unwrap() {
Side::Bid => {
maker_account.header.quote_token_free = maker_account
.header
.quote_token_free
.checked_add(quote_size + maker_rebate)
.unwrap();
maker_account.header.accumulated_rebates += maker_rebate;
maker_account.header.base_token_locked = maker_account
.header
.base_token_locked
.checked_sub(base_size)
.unwrap();
}
Side::Ask => {
maker_account.header.base_token_free = maker_account
.header
.base_token_free
.checked_add(base_size)
.unwrap();
maker_account.header.quote_token_locked = maker_account
.header
.quote_token_locked
.checked_sub(quote_size)
.unwrap();
maker_account
.header
.quote_token_free
.checked_add(maker_rebate)
.unwrap();
maker_account.header.accumulated_rebates += maker_rebate;
}
};
maker_account.header.accumulated_maker_quote_volume = maker_account
.header
.accumulated_maker_quote_volume
.checked_add(quote_size)
.unwrap();
maker_account.header.accumulated_maker_base_volume = maker_account
.header
.accumulated_maker_base_volume
.checked_add(base_size)
.unwrap();
market_state.quote_volume = market_state.quote_volume.checked_add(quote_size).unwrap();
market_state.base_volume = market_state.base_volume.checked_add(base_size).unwrap();
}
EventRef::Out(OutEventRef {
event,
callback_info,
}) => {
let OutEvent {
side,
order_id,
mut base_size,
..
} = event;
let user_account_info = &accounts[accounts
.binary_search_by_key(&callback_info.user_account, |k| *k.key)
.map_err(|_| DexError::MissingUserAccount)?];
let mut user_account_data = user_account_info.data.borrow_mut();
let mut user_account = UserAccount::from_buffer(&mut user_account_data).unwrap();
base_size = base_size
.checked_mul(market_state.base_currency_multiplier)
.unwrap();
if base_size != 0 {
match Side::from_u8(*side).unwrap() {
Side::Ask => {
user_account.header.base_token_free = user_account
.header
.base_token_free
.checked_add(base_size)
.unwrap();
user_account.header.base_token_locked = user_account
.header
.base_token_locked
.checked_sub(base_size)
.unwrap();
}
Side::Bid => {
let price = (order_id >> 64) as u64;
let qty_to_transfer = fp32_mul(base_size, price);
user_account.header.quote_token_free = user_account
.header
.quote_token_free
.checked_add(qty_to_transfer.unwrap())
.unwrap();
user_account.header.quote_token_locked = user_account
.header
.quote_token_locked
.checked_sub(qty_to_transfer.unwrap())
.unwrap();
}
}
}
let order_index = user_account.find_order_index(*order_id).unwrap();
user_account.remove_order(order_index).unwrap();
}
};
Ok(())
}