use solana_instruction::{AccountMeta, Instruction};
use solana_program_error::ProgramError;
use solana_pubkey::Pubkey;
use super::InstructionInputParsing;
use crate::SettlementInstruction;
pub struct ReclaimOrder {
pub program_id: Pubkey,
pub order_pda: Pubkey,
pub reclaim_recipient: Pubkey,
pub owner: Option<Pubkey>,
}
impl ReclaimOrder {
pub fn instruction(self) -> Instruction {
let mut accounts = vec![
AccountMeta::new(self.order_pda, false),
AccountMeta::new(self.reclaim_recipient, false),
];
if let Some(owner) = self.owner {
accounts.push(AccountMeta::new_readonly(owner, true));
}
Instruction {
program_id: self.program_id,
accounts,
data: vec![SettlementInstruction::ReclaimOrder.discriminator()],
}
}
}
pub struct ReclaimOrderInput<'a, A> {
pub order_pda: &'a A,
pub reclaim_recipient: &'a A,
pub owner: Option<&'a A>,
}
impl<'a, A> InstructionInputParsing<'a, A> for ReclaimOrderInput<'a, A> {
const DISCRIMINATOR: SettlementInstruction = SettlementInstruction::ReclaimOrder;
fn parse_body(instruction_data: &'a [u8], accounts: &'a [A]) -> Result<Self, ProgramError> {
if !instruction_data.is_empty() {
return Err(ProgramError::InvalidInstructionData);
}
let [order_pda, reclaim_recipient, remaining @ ..] = accounts else {
return Err(ProgramError::NotEnoughAccountKeys);
};
Ok(Self {
order_pda,
reclaim_recipient,
owner: remaining.first(),
})
}
}
#[cfg(any(test, feature = "test-fixtures"))]
pub mod fixtures {
use solana_address::Address;
use super::ReclaimOrder;
pub const NUM_ACCOUNTS: usize = 2;
pub fn default_reclaim_data() -> Vec<u8> {
let zero = Address::new_from_array([0; 32]);
ReclaimOrder {
program_id: zero,
order_pda: zero,
reclaim_recipient: zero,
owner: None,
}
.instruction()
.data
}
}
#[cfg(test)]
mod tests {
use super::fixtures::{default_reclaim_data, NUM_ACCOUNTS};
use super::*;
use crate::fixtures::pubkey_from_seed;
use crate::instruction::fixtures::{fake_account, fake_sequential_accounts};
use crate::instruction::tests::{assert_readonly_signer, assert_writable_nonsigner};
use solana_account_view::AccountView;
#[test]
fn reclaim_order_input_parses_valid_input() {
let program_id = pubkey_from_seed("program id");
let order_pda = pubkey_from_seed("order pda");
let reclaim_recipient = pubkey_from_seed("reclaim recipient");
let data = ReclaimOrder {
program_id,
order_pda,
reclaim_recipient,
owner: None,
}
.instruction()
.data;
let accounts = [fake_account(order_pda), fake_account(reclaim_recipient)];
let ReclaimOrderInput {
order_pda: derived_order_pda,
reclaim_recipient: derived_reclaim_recipient,
owner,
} = ReclaimOrderInput::parse(&data, &accounts).expect("parse should succeed");
assert!(owner.is_none());
assert_eq!(*derived_order_pda.address(), order_pda);
assert_eq!(*derived_reclaim_recipient.address(), reclaim_recipient);
}
#[test]
fn reclaim_order_input_rejects_extra_data() {
let mut data = default_reclaim_data();
data.push(0);
let accounts = fake_sequential_accounts::<NUM_ACCOUNTS>();
assert_eq!(
ReclaimOrderInput::parse(&data, &accounts).err(),
Some(ProgramError::InvalidInstructionData),
);
}
#[test]
fn reclaim_order_input_rejects_missing_accounts() {
let data = default_reclaim_data();
let mut accounts: Vec<AccountView> = fake_sequential_accounts::<NUM_ACCOUNTS>().into();
accounts.pop();
assert_eq!(
ReclaimOrderInput::parse(&data, &accounts).err(),
Some(ProgramError::NotEnoughAccountKeys),
);
}
#[test]
fn instruction_data_has_expected_layout() {
let program_id = pubkey_from_seed("program id");
let order_pda = pubkey_from_seed("order pda");
let reclaim_recipient = pubkey_from_seed("reclaim recipient");
let ix = ReclaimOrder {
program_id,
order_pda,
reclaim_recipient,
owner: None,
}
.instruction();
assert_eq!(
ix.data,
vec![SettlementInstruction::ReclaimOrder.discriminator()]
);
}
#[test]
fn instruction_data_has_expected_accounts() {
let program_id = pubkey_from_seed("program id");
let order_pda = pubkey_from_seed("order pda");
let reclaim_recipient = pubkey_from_seed("reclaim recipient");
let ix = ReclaimOrder {
program_id,
order_pda,
reclaim_recipient,
owner: None,
}
.instruction();
assert_eq!(ix.accounts.len(), 2);
assert_writable_nonsigner(&ix.accounts[0], order_pda);
assert_writable_nonsigner(&ix.accounts[1], reclaim_recipient);
}
#[test]
fn optional_owner_is_parsed_and_requests_a_readonly_signature() {
let owner = pubkey_from_seed("owner");
let ix = ReclaimOrder {
program_id: pubkey_from_seed("program id"),
order_pda: pubkey_from_seed("order pda"),
reclaim_recipient: pubkey_from_seed("reclaim recipient"),
owner: Some(owner),
}
.instruction();
assert_eq!(ix.accounts.len(), 3);
assert_readonly_signer(&ix.accounts[2], owner);
let accounts = ix
.accounts
.iter()
.map(|meta| fake_account(meta.pubkey))
.collect::<Vec<_>>();
let parsed = ReclaimOrderInput::parse(&ix.data, &accounts).unwrap();
assert_eq!(parsed.owner.unwrap().address(), &owner);
}
}