use solana_instruction::Instruction;
use solana_pubkey::Pubkey;
use crate::phoenix_rise_ix::flight::{ProxyInstructionParams, create_proxy_instruction_ix};
use crate::phoenix_rise_ix::{
PhoenixIxError, place_limit_order_discriminant, place_market_order_discriminant,
};
use crate::phoenix_rise_types::TraderKey;
#[derive(Debug, Clone, Copy)]
pub struct PhoenixFlightClient {
pub builder_authority: Pubkey,
pub builder_pda_index: u8,
pub builder_subaccount_index: u8,
}
impl PhoenixFlightClient {
pub fn new(
builder_authority: Pubkey,
builder_pda_index: u8,
builder_subaccount_index: u8,
) -> Self {
Self {
builder_authority,
builder_pda_index,
builder_subaccount_index,
}
}
pub fn builder_trader_account(&self) -> Pubkey {
TraderKey::derive_pda(
&self.builder_authority,
self.builder_pda_index,
self.builder_subaccount_index,
)
}
pub fn try_wrap_order_instruction(
&self,
ix: Instruction,
trader_wallet: Pubkey,
) -> Result<Instruction, PhoenixIxError> {
if !is_order_placing_instruction(&ix) {
return Ok(ix);
}
let inner = to_phoenix_rise_ix_instruction(ix);
let params = ProxyInstructionParams::builder()
.builder_authority(self.builder_authority)
.builder_trader_account(self.builder_trader_account())
.trader_wallet(trader_wallet)
.inner_instruction(inner)
.build()?;
Ok(create_proxy_instruction_ix(params)?.into())
}
}
pub fn is_order_placing_instruction(ix: &Instruction) -> bool {
has_discriminant(&ix.data, &place_limit_order_discriminant())
|| has_discriminant(&ix.data, &place_market_order_discriminant())
}
fn has_discriminant(data: &[u8], discriminant: &[u8; 8]) -> bool {
data.len() >= discriminant.len() && data[..discriminant.len()] == *discriminant
}
fn to_phoenix_rise_ix_instruction(ix: Instruction) -> crate::phoenix_rise_ix::Instruction {
let accounts = ix
.accounts
.into_iter()
.map(|meta| crate::phoenix_rise_ix::AccountMeta {
pubkey: meta.pubkey,
is_signer: meta.is_signer,
is_writable: meta.is_writable,
})
.collect();
crate::phoenix_rise_ix::Instruction {
program_id: ix.program_id,
accounts,
data: ix.data,
}
}
#[cfg(test)]
mod tests {
use solana_instruction::AccountMeta as SolAccountMeta;
use super::*;
use crate::phoenix_rise_ix::{LimitOrderParams, Side, create_place_limit_order_ix};
fn build_sample_limit_ix() -> Instruction {
let params = LimitOrderParams::builder()
.trader(Pubkey::new_unique())
.trader_account(Pubkey::new_unique())
.perp_asset_map(Pubkey::new_unique())
.orderbook(Pubkey::new_unique())
.spline_collection(Pubkey::new_unique())
.global_trader_index(vec![Pubkey::new_unique()])
.active_trader_buffer(vec![Pubkey::new_unique()])
.side(Side::Bid)
.price_in_ticks(1000)
.num_base_lots(100)
.build()
.unwrap();
create_place_limit_order_ix(params).unwrap().into()
}
#[test]
fn test_wraps_order_placing_ix() {
let client = PhoenixFlightClient::new(Pubkey::new_unique(), 0, 0);
let trader_wallet = Pubkey::new_unique();
let inner = build_sample_limit_ix();
let inner_data = inner.data.clone();
let wrapped = client
.try_wrap_order_instruction(inner, trader_wallet)
.unwrap();
assert_eq!(
wrapped.program_id,
crate::phoenix_rise_ix::flight::FLIGHT_PROGRAM_ID
);
assert_eq!(
&wrapped.data[..8],
&crate::phoenix_rise_ix::flight::flight_proxy_instruction_discriminant()
);
assert_eq!(&wrapped.data[8..], &inner_data[..]);
}
#[test]
fn test_non_order_ix_passthrough() {
let client = PhoenixFlightClient::new(Pubkey::new_unique(), 0, 0);
let trader_wallet = Pubkey::new_unique();
let unrelated = Instruction {
program_id: Pubkey::new_unique(),
accounts: vec![SolAccountMeta::new_readonly(Pubkey::new_unique(), false)],
data: vec![0xDE, 0xAD, 0xBE, 0xEF],
};
let result = client
.try_wrap_order_instruction(unrelated.clone(), trader_wallet)
.unwrap();
assert_eq!(result.program_id, unrelated.program_id);
assert_eq!(result.data, unrelated.data);
assert_eq!(result.accounts.len(), unrelated.accounts.len());
}
#[test]
fn test_is_order_placing_instruction() {
assert!(is_order_placing_instruction(&build_sample_limit_ix()));
let fake = Instruction {
program_id: Pubkey::new_unique(),
accounts: vec![],
data: vec![0u8; 8],
};
assert!(!is_order_placing_instruction(&fake));
}
}