use solana_pubkey::Pubkey;
use crate::ix::constants::SPL_TOKEN_PROGRAM_ID;
use crate::ix::error::PhoenixIxError;
use crate::ix::types::{AccountMeta, Instruction};
#[derive(Debug, Clone)]
pub struct SplApproveParams {
source: Pubkey,
delegate: Pubkey,
owner: Pubkey,
amount: u64,
}
impl SplApproveParams {
pub fn builder() -> SplApproveParamsBuilder {
SplApproveParamsBuilder::new()
}
pub fn source(&self) -> Pubkey {
self.source
}
pub fn delegate(&self) -> Pubkey {
self.delegate
}
pub fn owner(&self) -> Pubkey {
self.owner
}
pub fn amount(&self) -> u64 {
self.amount
}
}
#[derive(Default)]
pub struct SplApproveParamsBuilder {
source: Option<Pubkey>,
delegate: Option<Pubkey>,
owner: Option<Pubkey>,
amount: Option<u64>,
}
impl SplApproveParamsBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn source(mut self, source: Pubkey) -> Self {
self.source = Some(source);
self
}
pub fn delegate(mut self, delegate: Pubkey) -> Self {
self.delegate = Some(delegate);
self
}
pub fn owner(mut self, owner: Pubkey) -> Self {
self.owner = Some(owner);
self
}
pub fn amount(mut self, amount: u64) -> Self {
self.amount = Some(amount);
self
}
pub fn build(self) -> Result<SplApproveParams, PhoenixIxError> {
Ok(SplApproveParams {
source: self.source.ok_or(PhoenixIxError::MissingField("source"))?,
delegate: self
.delegate
.ok_or(PhoenixIxError::MissingField("delegate"))?,
owner: self.owner.ok_or(PhoenixIxError::MissingField("owner"))?,
amount: self.amount.ok_or(PhoenixIxError::MissingField("amount"))?,
})
}
}
pub fn create_spl_approve_ix(params: SplApproveParams) -> Result<Instruction, PhoenixIxError> {
let data = encode_spl_approve(¶ms);
let accounts = build_accounts(¶ms);
Ok(Instruction {
program_id: SPL_TOKEN_PROGRAM_ID,
accounts,
data,
})
}
fn encode_spl_approve(params: &SplApproveParams) -> Vec<u8> {
let mut data = Vec::with_capacity(9);
data.push(4);
data.extend_from_slice(¶ms.amount().to_le_bytes());
data
}
fn build_accounts(params: &SplApproveParams) -> Vec<AccountMeta> {
vec![
AccountMeta::writable(params.source()),
AccountMeta::readonly(params.delegate()),
AccountMeta::readonly_signer(params.owner()),
]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_create_spl_approve_ix() {
let params = SplApproveParams::builder()
.source(Pubkey::new_unique())
.delegate(Pubkey::new_unique())
.owner(Pubkey::new_unique())
.amount(100_000_000)
.build()
.unwrap();
let ix = create_spl_approve_ix(params).unwrap();
assert_eq!(ix.program_id, SPL_TOKEN_PROGRAM_ID);
assert_eq!(ix.accounts.len(), 3);
assert_eq!(ix.data[0], 4); let amount_bytes: [u8; 8] = ix.data[1..9].try_into().unwrap();
assert_eq!(u64::from_le_bytes(amount_bytes), 100_000_000);
}
#[test]
fn test_spl_approve_missing_source() {
let result = SplApproveParams::builder()
.delegate(Pubkey::new_unique())
.owner(Pubkey::new_unique())
.amount(100)
.build();
assert!(matches!(
result,
Err(PhoenixIxError::MissingField("source"))
));
}
#[test]
fn test_spl_approve_account_order() {
let source = Pubkey::new_unique();
let delegate = Pubkey::new_unique();
let owner = Pubkey::new_unique();
let params = SplApproveParams::builder()
.source(source)
.delegate(delegate)
.owner(owner)
.amount(1)
.build()
.unwrap();
let ix = create_spl_approve_ix(params).unwrap();
assert_eq!(ix.accounts[0].pubkey, source);
assert!(ix.accounts[0].is_writable);
assert!(!ix.accounts[0].is_signer);
assert_eq!(ix.accounts[1].pubkey, delegate);
assert!(!ix.accounts[1].is_writable);
assert!(!ix.accounts[1].is_signer);
assert_eq!(ix.accounts[2].pubkey, owner);
assert!(!ix.accounts[2].is_writable);
assert!(ix.accounts[2].is_signer);
}
}