use solana_pubkey::Pubkey;
use crate::ix::constants::{
PHOENIX_GLOBAL_CONFIGURATION, PHOENIX_LOG_AUTHORITY, PHOENIX_PROGRAM_ID, SPL_TOKEN_PROGRAM_ID,
deposit_funds_discriminant,
};
use crate::ix::error::PhoenixIxError;
use crate::ix::types::{AccountMeta, Instruction};
#[derive(Debug, Clone)]
pub struct DepositFundsParams {
trader: Pubkey,
trader_account: Pubkey,
canonical_mint: Pubkey,
global_vault: Pubkey,
trader_token_account: Pubkey,
global_trader_index: Vec<Pubkey>,
active_trader_buffer: Vec<Pubkey>,
amount: u64,
permission_account: Option<Pubkey>,
}
impl DepositFundsParams {
pub fn builder() -> DepositFundsParamsBuilder {
DepositFundsParamsBuilder::new()
}
pub fn trader(&self) -> Pubkey {
self.trader
}
pub fn trader_account(&self) -> Pubkey {
self.trader_account
}
pub fn canonical_mint(&self) -> Pubkey {
self.canonical_mint
}
pub fn global_vault(&self) -> Pubkey {
self.global_vault
}
pub fn trader_token_account(&self) -> Pubkey {
self.trader_token_account
}
pub fn global_trader_index(&self) -> &[Pubkey] {
&self.global_trader_index
}
pub fn active_trader_buffer(&self) -> &[Pubkey] {
&self.active_trader_buffer
}
pub fn amount(&self) -> u64 {
self.amount
}
pub fn permission_account(&self) -> Option<Pubkey> {
self.permission_account
}
}
#[derive(Default)]
pub struct DepositFundsParamsBuilder {
trader: Option<Pubkey>,
trader_account: Option<Pubkey>,
canonical_mint: Option<Pubkey>,
global_vault: Option<Pubkey>,
trader_token_account: Option<Pubkey>,
global_trader_index: Option<Vec<Pubkey>>,
active_trader_buffer: Option<Vec<Pubkey>>,
amount: Option<u64>,
permission_account: Option<Pubkey>,
}
impl DepositFundsParamsBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn trader(mut self, trader: Pubkey) -> Self {
self.trader = Some(trader);
self
}
pub fn trader_account(mut self, trader_account: Pubkey) -> Self {
self.trader_account = Some(trader_account);
self
}
pub fn canonical_mint(mut self, canonical_mint: Pubkey) -> Self {
self.canonical_mint = Some(canonical_mint);
self
}
pub fn global_vault(mut self, global_vault: Pubkey) -> Self {
self.global_vault = Some(global_vault);
self
}
pub fn trader_token_account(mut self, trader_token_account: Pubkey) -> Self {
self.trader_token_account = Some(trader_token_account);
self
}
pub fn global_trader_index(mut self, global_trader_index: Vec<Pubkey>) -> Self {
self.global_trader_index = Some(global_trader_index);
self
}
pub fn active_trader_buffer(mut self, active_trader_buffer: Vec<Pubkey>) -> Self {
self.active_trader_buffer = Some(active_trader_buffer);
self
}
pub fn amount(mut self, amount: u64) -> Self {
self.amount = Some(amount);
self
}
pub fn permission_account(mut self, permission_account: Pubkey) -> Self {
self.permission_account = Some(permission_account);
self
}
pub fn build(self) -> Result<DepositFundsParams, PhoenixIxError> {
let amount = self.amount.ok_or(PhoenixIxError::MissingField("amount"))?;
if amount == 0 {
return Err(PhoenixIxError::InvalidDepositAmount);
}
let global_trader_index = self
.global_trader_index
.ok_or(PhoenixIxError::MissingField("global_trader_index"))?;
if global_trader_index.is_empty() {
return Err(PhoenixIxError::EmptyGlobalTraderIndex);
}
let active_trader_buffer = self
.active_trader_buffer
.ok_or(PhoenixIxError::MissingField("active_trader_buffer"))?;
if active_trader_buffer.is_empty() {
return Err(PhoenixIxError::EmptyActiveTraderBuffer);
}
Ok(DepositFundsParams {
trader: self.trader.ok_or(PhoenixIxError::MissingField("trader"))?,
trader_account: self
.trader_account
.ok_or(PhoenixIxError::MissingField("trader_account"))?,
canonical_mint: self
.canonical_mint
.ok_or(PhoenixIxError::MissingField("canonical_mint"))?,
global_vault: self
.global_vault
.ok_or(PhoenixIxError::MissingField("global_vault"))?,
trader_token_account: self
.trader_token_account
.ok_or(PhoenixIxError::MissingField("trader_token_account"))?,
global_trader_index,
active_trader_buffer,
amount,
permission_account: self.permission_account,
})
}
}
pub fn create_deposit_funds_ix(params: DepositFundsParams) -> Result<Instruction, PhoenixIxError> {
let data = encode_deposit_funds(¶ms);
let accounts = build_accounts(¶ms);
Ok(Instruction {
program_id: PHOENIX_PROGRAM_ID,
accounts,
data,
})
}
fn encode_deposit_funds(params: &DepositFundsParams) -> Vec<u8> {
let mut data = Vec::with_capacity(16);
data.extend_from_slice(&deposit_funds_discriminant());
data.extend_from_slice(¶ms.amount().to_le_bytes());
data
}
fn build_accounts(params: &DepositFundsParams) -> Vec<AccountMeta> {
let mut accounts = Vec::new();
accounts.push(AccountMeta::readonly(PHOENIX_PROGRAM_ID));
accounts.push(AccountMeta::readonly(PHOENIX_LOG_AUTHORITY));
accounts.push(AccountMeta::writable(PHOENIX_GLOBAL_CONFIGURATION));
accounts.push(AccountMeta::readonly_signer(params.trader()));
accounts.push(AccountMeta::writable(params.trader_token_account()));
accounts.push(AccountMeta::writable(params.trader_account()));
accounts.push(AccountMeta::writable(params.global_vault()));
accounts.push(AccountMeta::readonly(SPL_TOKEN_PROGRAM_ID));
for addr in params.global_trader_index() {
accounts.push(AccountMeta::writable(*addr));
}
for addr in params.active_trader_buffer() {
accounts.push(AccountMeta::writable(*addr));
}
if let Some(permission_account) = params.permission_account() {
accounts.push(AccountMeta::writable(permission_account));
}
accounts
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_create_deposit_funds_ix() {
let params = DepositFundsParams::builder()
.trader(Pubkey::new_unique())
.trader_account(Pubkey::new_unique())
.canonical_mint(Pubkey::new_unique())
.global_vault(Pubkey::new_unique())
.trader_token_account(Pubkey::new_unique())
.global_trader_index(vec![Pubkey::new_unique()])
.active_trader_buffer(vec![Pubkey::new_unique()])
.amount(100_000_000)
.build()
.unwrap();
let ix = create_deposit_funds_ix(params).unwrap();
assert_eq!(ix.program_id, PHOENIX_PROGRAM_ID);
assert_eq!(ix.accounts.len(), 10);
assert_eq!(&ix.data[..8], &deposit_funds_discriminant());
let amount_bytes: [u8; 8] = ix.data[8..16].try_into().unwrap();
assert_eq!(u64::from_le_bytes(amount_bytes), 100_000_000);
}
#[test]
fn test_deposit_funds_zero_amount() {
let result = DepositFundsParams::builder()
.trader(Pubkey::new_unique())
.trader_account(Pubkey::new_unique())
.canonical_mint(Pubkey::new_unique())
.global_vault(Pubkey::new_unique())
.trader_token_account(Pubkey::new_unique())
.global_trader_index(vec![Pubkey::new_unique()])
.active_trader_buffer(vec![Pubkey::new_unique()])
.amount(0)
.build();
assert!(matches!(result, Err(PhoenixIxError::InvalidDepositAmount)));
}
#[test]
fn test_deposit_funds_empty_global_trader_index() {
let result = DepositFundsParams::builder()
.trader(Pubkey::new_unique())
.trader_account(Pubkey::new_unique())
.canonical_mint(Pubkey::new_unique())
.global_vault(Pubkey::new_unique())
.trader_token_account(Pubkey::new_unique())
.global_trader_index(vec![])
.active_trader_buffer(vec![Pubkey::new_unique()])
.amount(100)
.build();
assert!(matches!(
result,
Err(PhoenixIxError::EmptyGlobalTraderIndex)
));
}
#[test]
fn test_deposit_funds_empty_active_trader_buffer() {
let result = DepositFundsParams::builder()
.trader(Pubkey::new_unique())
.trader_account(Pubkey::new_unique())
.canonical_mint(Pubkey::new_unique())
.global_vault(Pubkey::new_unique())
.trader_token_account(Pubkey::new_unique())
.global_trader_index(vec![Pubkey::new_unique()])
.active_trader_buffer(vec![])
.amount(100)
.build();
assert!(matches!(
result,
Err(PhoenixIxError::EmptyActiveTraderBuffer)
));
}
#[test]
fn test_deposit_funds_account_order() {
let trader = Pubkey::new_unique();
let trader_account = Pubkey::new_unique();
let canonical_mint = Pubkey::new_unique();
let global_vault = Pubkey::new_unique();
let trader_token_account = Pubkey::new_unique();
let gti = Pubkey::new_unique();
let atb = Pubkey::new_unique();
let params = DepositFundsParams::builder()
.trader(trader)
.trader_account(trader_account)
.canonical_mint(canonical_mint)
.global_vault(global_vault)
.trader_token_account(trader_token_account)
.global_trader_index(vec![gti])
.active_trader_buffer(vec![atb])
.amount(1)
.build()
.unwrap();
let ix = create_deposit_funds_ix(params).unwrap();
assert_eq!(ix.accounts[0].pubkey, PHOENIX_PROGRAM_ID);
assert!(!ix.accounts[0].is_writable);
assert_eq!(ix.accounts[1].pubkey, PHOENIX_LOG_AUTHORITY);
assert!(!ix.accounts[1].is_writable);
assert_eq!(ix.accounts[2].pubkey, PHOENIX_GLOBAL_CONFIGURATION);
assert!(ix.accounts[2].is_writable);
assert_eq!(ix.accounts[3].pubkey, trader);
assert!(ix.accounts[3].is_signer);
assert!(!ix.accounts[3].is_writable);
assert_eq!(ix.accounts[4].pubkey, trader_token_account);
assert!(ix.accounts[4].is_writable);
assert_eq!(ix.accounts[5].pubkey, trader_account);
assert!(ix.accounts[5].is_writable);
assert_eq!(ix.accounts[6].pubkey, global_vault);
assert!(ix.accounts[6].is_writable);
assert_eq!(ix.accounts[7].pubkey, SPL_TOKEN_PROGRAM_ID);
assert!(!ix.accounts[7].is_writable);
assert_eq!(ix.accounts[8].pubkey, gti);
assert!(ix.accounts[8].is_writable);
assert_eq!(ix.accounts[9].pubkey, atb);
assert!(ix.accounts[9].is_writable);
}
#[test]
fn test_deposit_funds_multiple_index_accounts() {
let gti1 = Pubkey::new_unique();
let gti2 = Pubkey::new_unique();
let atb1 = Pubkey::new_unique();
let atb2 = Pubkey::new_unique();
let params = DepositFundsParams::builder()
.trader(Pubkey::new_unique())
.trader_account(Pubkey::new_unique())
.canonical_mint(Pubkey::new_unique())
.global_vault(Pubkey::new_unique())
.trader_token_account(Pubkey::new_unique())
.global_trader_index(vec![gti1, gti2])
.active_trader_buffer(vec![atb1, atb2])
.amount(1)
.build()
.unwrap();
let ix = create_deposit_funds_ix(params).unwrap();
assert_eq!(ix.accounts.len(), 12);
assert_eq!(ix.accounts[8].pubkey, gti1);
assert_eq!(ix.accounts[9].pubkey, gti2);
assert_eq!(ix.accounts[10].pubkey, atb1);
assert_eq!(ix.accounts[11].pubkey, atb2);
}
}