use solana_instruction::{AccountMeta, Instruction};
use solana_program_error::ProgramError;
use solana_pubkey::Pubkey;
pub use solana_system_interface::program::ID as SYSTEM_PROGRAM_ID;
use super::InstructionInputParsing;
use crate::SettlementInstruction;
pub use spl_token_interface::ID as SPL_TOKEN_PROGRAM_ID;
pub struct CreateBuffers<'a> {
pub program_id: Pubkey,
pub payer: Pubkey,
pub buffers: &'a [(Pubkey, Pubkey)],
}
impl From<CreateBuffers<'_>> for Instruction {
fn from(builder: CreateBuffers<'_>) -> Self {
let mut accounts = vec![
AccountMeta::new(builder.payer, true),
AccountMeta::new_readonly(SYSTEM_PROGRAM_ID, false),
AccountMeta::new_readonly(SPL_TOKEN_PROGRAM_ID, false),
];
for (buffer_pda, mint) in builder.buffers {
accounts.push(AccountMeta::new(*buffer_pda, false));
accounts.push(AccountMeta::new_readonly(*mint, false));
}
Instruction {
program_id: builder.program_id,
accounts,
data: vec![SettlementInstruction::CreateBuffer.discriminator()],
}
}
}
pub struct CreateBufferInput<'a, A> {
pub payer: &'a A,
pub token_program: &'a A,
pub buffers: &'a [[A; 2]],
}
impl<'a, A> InstructionInputParsing<'a, A> for CreateBufferInput<'a, A> {
const DISCRIMINATOR: SettlementInstruction = SettlementInstruction::CreateBuffer;
fn parse_body(instruction_data: &[u8], accounts: &'a mut [A]) -> Result<Self, ProgramError> {
if !instruction_data.is_empty() {
return Err(ProgramError::InvalidInstructionData);
}
let [payer, _system, token_program, rest @ ..] = accounts else {
return Err(ProgramError::NotEnoughAccountKeys);
};
let rest: &'a [A] = rest;
let (buffers, remainder) = rest.as_chunks::<2>();
if !remainder.is_empty() || buffers.is_empty() {
return Err(ProgramError::NotEnoughAccountKeys);
}
Ok(Self {
payer,
token_program,
buffers,
})
}
}
#[cfg(any(test, feature = "test-fixtures"))]
pub mod fixtures {
use solana_address::Address;
use super::{CreateBuffers, Instruction};
pub const NUM_SHARED_ACCOUNTS: usize = 3;
pub fn create_buffer_data() -> Vec<u8> {
let zero = Address::new_from_array([0; 32]);
Instruction::from(CreateBuffers {
program_id: zero,
payer: zero,
buffers: &[(zero, zero)],
})
.data
}
}
#[cfg(test)]
mod tests {
use super::fixtures::{create_buffer_data, NUM_SHARED_ACCOUNTS};
use super::*;
use crate::instruction::fixtures::{
fake_account, fake_account_from_array, fake_sequential_accounts,
};
use crate::instruction::tests::{
assert_readonly_nonsigner, assert_writable_nonsigner, assert_writable_signer,
};
use solana_account_view::AccountView;
use solana_address::Address;
#[test]
fn create_buffer_input_parses_valid_input() {
let program_id: Address = Address::new_from_array([1; 32]);
let payer: Address = Address::new_from_array([2; 32]);
let system_program = fake_account_from_array([4; 32]);
let token_program = Address::new_from_array([3; 32]);
let buffer_pda = Address::new_from_array([5; 32]);
let mint = Address::new_from_array([6; 32]);
let data = Instruction::from(CreateBuffers {
program_id,
payer,
buffers: &[(buffer_pda, mint)],
})
.data;
let mut accounts = [
fake_account(payer),
system_program,
fake_account(token_program),
fake_account(buffer_pda),
fake_account(mint),
];
let CreateBufferInput {
payer: parsed_payer,
token_program: parsed_token_program,
buffers,
} = CreateBufferInput::parse(&data, &mut accounts).expect("parse should succeed");
assert_eq!(*parsed_payer.address(), payer);
assert_eq!(*parsed_token_program.address(), token_program);
assert_eq!(buffers.len(), 1, "one buffer is one (pda, mint) pair");
assert_eq!(*buffers[0][0].address(), buffer_pda);
assert_eq!(*buffers[0][1].address(), mint);
}
#[test]
fn create_buffer_input_parses_multiple_buffers() {
let program_id = Address::new_from_array([1; 32]);
let payer = Address::new_from_array([2; 32]);
let token_program = Address::new_from_array([3; 32]);
let buffer_a = Address::new_from_array([5; 32]);
let mint_a = Address::new_from_array([6; 32]);
let buffer_b = Address::new_from_array([7; 32]);
let mint_b = Address::new_from_array([8; 32]);
let data = Instruction::from(CreateBuffers {
program_id,
payer,
buffers: &[(buffer_a, mint_a), (buffer_b, mint_b)],
})
.data;
let mut accounts = [
fake_account(payer),
fake_account_from_array([4; 32]),
fake_account(token_program),
fake_account(buffer_a),
fake_account(mint_a),
fake_account(buffer_b),
fake_account(mint_b),
];
let CreateBufferInput { buffers, .. } =
CreateBufferInput::parse(&data, &mut accounts).expect("parse should succeed");
assert_eq!(
buffers[0].each_ref().map(|a| *a.address()),
[buffer_a, mint_a]
);
assert_eq!(
buffers[1].each_ref().map(|a| *a.address()),
[buffer_b, mint_b]
);
}
#[test]
fn create_buffer_input_rejects_zero_buffers() {
let data = vec![SettlementInstruction::CreateBuffer.discriminator()];
let mut accounts = fake_sequential_accounts::<NUM_SHARED_ACCOUNTS>();
assert_eq!(
CreateBufferInput::parse(&data, &mut accounts).err(),
Some(ProgramError::NotEnoughAccountKeys),
"an instruction that creates no buffers is rejected",
);
}
#[test]
fn create_buffer_input_rejects_long_data() {
let mut data = create_buffer_data();
data.push(0); let mut accounts: [AccountView; 0] = [];
assert_eq!(
CreateBufferInput::parse(&data, &mut accounts).err(),
Some(ProgramError::InvalidInstructionData),
);
}
#[test]
fn create_buffer_input_rejects_missing_accounts() {
let data = create_buffer_data();
let mut accounts = fake_sequential_accounts::<{ NUM_SHARED_ACCOUNTS - 1 }>();
assert_eq!(
CreateBufferInput::parse(&data, &mut accounts).err(),
Some(ProgramError::NotEnoughAccountKeys),
);
}
#[test]
fn create_buffer_input_rejects_odd_pair_accounts() {
let data = create_buffer_data();
let mut accounts = fake_sequential_accounts::<4>();
assert_eq!(
CreateBufferInput::parse(&data, &mut accounts).err(),
Some(ProgramError::NotEnoughAccountKeys),
);
}
#[test]
fn instruction_data_has_expected_layout() {
let program_id = Pubkey::new_from_array([1; 32]);
let payer = Pubkey::new_from_array([2; 32]);
let buffer_pda = Pubkey::new_from_array([3; 32]);
let mint = Pubkey::new_from_array([4; 32]);
let Instruction { data, .. } = CreateBuffers {
program_id,
payer,
buffers: &[(buffer_pda, mint)],
}
.into();
assert_eq!(
data,
vec![SettlementInstruction::CreateBuffer.discriminator()]
);
}
#[test]
fn single_buffer_has_expected_accounts() {
let program_id = Pubkey::new_from_array([1; 32]);
let payer = Pubkey::new_from_array([2; 32]);
let buffer_pda = Pubkey::new_from_array([3; 32]);
let mint = Pubkey::new_from_array([4; 32]);
let Instruction { accounts, .. } = CreateBuffers {
program_id,
payer,
buffers: &[(buffer_pda, mint)],
}
.into();
assert_eq!(accounts.len(), 5);
assert_writable_signer(&accounts[0], payer);
assert_readonly_nonsigner(&accounts[1], SYSTEM_PROGRAM_ID);
assert_readonly_nonsigner(&accounts[2], SPL_TOKEN_PROGRAM_ID);
assert_writable_nonsigner(&accounts[3], buffer_pda);
assert_readonly_nonsigner(&accounts[4], mint);
}
#[test]
fn multiple_buffers_append_pairs_after_shared_accounts() {
let program_id = Pubkey::new_from_array([1; 32]);
let payer = Pubkey::new_from_array([2; 32]);
let buffer_a = Pubkey::new_from_array([3; 32]);
let mint_a = Pubkey::new_from_array([4; 32]);
let buffer_b = Pubkey::new_from_array([5; 32]);
let mint_b = Pubkey::new_from_array([6; 32]);
let Instruction { accounts, .. } = CreateBuffers {
program_id,
payer,
buffers: &[(buffer_a, mint_a), (buffer_b, mint_b)],
}
.into();
assert_eq!(accounts.len(), 3 + 2 * 2);
assert_writable_nonsigner(&accounts[3], buffer_a);
assert_readonly_nonsigner(&accounts[4], mint_a);
assert_writable_nonsigner(&accounts[5], buffer_b);
assert_readonly_nonsigner(&accounts[6], mint_b);
}
#[test]
fn empty_buffers_has_only_shared_accounts() {
let program_id = Pubkey::new_from_array([1; 32]);
let payer = Pubkey::new_from_array([2; 32]);
let Instruction { accounts, .. } = CreateBuffers {
program_id,
payer,
buffers: &[],
}
.into();
assert_eq!(accounts.len(), 3);
}
}