#![allow(unsafe_code, dead_code)]
use core::alloc::Layout;
use core::mem::MaybeUninit;
use core::ptr::copy_nonoverlapping;
use std::alloc::alloc;
use std::alloc::dealloc;
use std::vec;
use std::vec::Vec;
use pina::*;
use pinocchio::account::MAX_PERMITTED_DATA_INCREASE;
const TEST_PROGRAM_ID: Address = address!("GJQcuWrT2f3f4KNuJcXhhwUa1ZQTYbxzzJ1hotzKu8hS");
#[discriminator(crate = ::pina)]
#[derive(Debug)]
pub enum TestInstruction {
Initialize = 0,
Update = 1,
Close = 2,
}
#[discriminator(crate = ::pina)]
pub enum TestAccountType {
TestState = 1,
}
#[account(crate = ::pina, discriminator = TestAccountType)]
pub struct TestState {
pub bump: u8,
pub _padding: u8,
pub _padding2: u8,
pub value: PodU64,
}
#[instruction(crate = ::pina, discriminator = TestInstruction, variant = Initialize)]
pub struct InitializeInstr {
pub bump: u8,
pub initial_value: PodU64,
}
#[instruction(crate = ::pina, discriminator = TestInstruction, variant = Update)]
pub struct UpdateInstr {
pub new_value: PodU64,
}
#[instruction(crate = ::pina, discriminator = TestInstruction, variant = Close)]
pub struct CloseInstr {}
#[derive(Accounts, Debug)]
#[pina(crate = pina)]
pub struct InitializeAccounts<'a> {
pub authority: &'a AccountView,
pub state_account: &'a AccountView,
pub system_program: &'a AccountView,
}
#[derive(Accounts, Debug)]
#[pina(crate = pina)]
pub struct UpdateAccounts<'a> {
pub authority: &'a AccountView,
pub state_account: &'a AccountView,
}
#[derive(Accounts, Debug)]
#[pina(crate = pina)]
pub struct CloseAccounts<'a> {
pub authority: &'a AccountView,
pub state_account: &'a AccountView,
}
impl<'a> ProcessAccountInfos<'a> for InitializeAccounts<'a> {
fn process(&self, data: &[u8]) -> ProgramResult {
let args = InitializeInstr::try_from_bytes(data)?;
self.authority.assert_signer()?;
self.state_account.assert_writable()?;
self.system_program.assert_address(&system::ID)?;
let new_state = TestState::builder()
.bump(args.bump)
._padding(0)
._padding2(0)
.value(args.initial_value)
.build();
let state_bytes = bytemuck::bytes_of(&new_state);
let mut account_data = self.state_account.try_borrow_mut()?;
if account_data.len() < state_bytes.len() {
return Err(ProgramError::AccountDataTooSmall);
}
account_data[..state_bytes.len()].copy_from_slice(state_bytes);
Ok(())
}
}
impl<'a> ProcessAccountInfos<'a> for UpdateAccounts<'a> {
fn process(&self, data: &[u8]) -> ProgramResult {
let args = UpdateInstr::try_from_bytes(data)?;
self.authority.assert_signer()?;
self.state_account
.assert_not_empty()?
.assert_writable()?
.assert_type::<TestState>(&TEST_PROGRAM_ID)?;
let state = self
.state_account
.as_account_mut::<TestState>(&TEST_PROGRAM_ID)?;
state.value = args.new_value;
Ok(())
}
}
impl<'a> ProcessAccountInfos<'a> for CloseAccounts<'a> {
fn process(&self, data: &[u8]) -> ProgramResult {
let _ = CloseInstr::try_from_bytes(data)?;
self.authority.assert_signer()?.assert_writable()?;
self.state_account
.assert_not_empty()?
.assert_writable()?
.assert_type::<TestState>(&TEST_PROGRAM_ID)?;
let state = self
.state_account
.as_account_mut::<TestState>(&TEST_PROGRAM_ID)?;
state.zeroed();
self.state_account
.send(self.state_account.lamports(), self.authority)?;
Ok(())
}
}
fn process_instruction(
program_id: &Address,
accounts: &[AccountView],
data: &[u8],
) -> ProgramResult {
let instruction: TestInstruction = parse_instruction(program_id, &TEST_PROGRAM_ID, data)?;
match instruction {
TestInstruction::Initialize => InitializeAccounts::try_from(accounts)?.process(data),
TestInstruction::Update => UpdateAccounts::try_from(accounts)?.process(data),
TestInstruction::Close => CloseAccounts::try_from(accounts)?.process(data),
}
}
const BPF_ALIGN_OF_U128: usize = 8;
const UNINIT: MaybeUninit<AccountView> = MaybeUninit::<AccountView>::uninit();
const STATIC_ACCOUNT_DATA: usize = 88 + MAX_PERMITTED_DATA_INCREASE;
struct AccountBuilder {
address: Address,
owner: Address,
lamports: u64,
data: Vec<u8>,
is_signer: bool,
is_writable: bool,
executable: bool,
}
impl AccountBuilder {
fn new() -> Self {
Self {
address: Address::default(),
owner: Address::default(),
lamports: 0,
data: Vec::new(),
is_signer: false,
is_writable: false,
executable: false,
}
}
fn address(mut self, address: Address) -> Self {
self.address = address;
self
}
fn owner(mut self, owner: Address) -> Self {
self.owner = owner;
self
}
fn lamports(mut self, lamports: u64) -> Self {
self.lamports = lamports;
self
}
fn data(mut self, data: &[u8]) -> Self {
self.data = data.to_vec();
self
}
fn is_signer(mut self, is_signer: bool) -> Self {
self.is_signer = is_signer;
self
}
fn is_writable(mut self, is_writable: bool) -> Self {
self.is_writable = is_writable;
self
}
fn executable(mut self, executable: bool) -> Self {
self.executable = executable;
self
}
}
struct AlignedMemory {
ptr: *mut u8,
layout: Layout,
}
impl AlignedMemory {
fn new(len: usize) -> Self {
let layout = Layout::from_size_align(len, BPF_ALIGN_OF_U128)
.unwrap_or_else(|e| panic!("invalid layout: {e:?}"));
unsafe {
let ptr = alloc(layout);
if ptr.is_null() {
std::alloc::handle_alloc_error(layout);
}
AlignedMemory { ptr, layout }
}
}
unsafe fn write(&mut self, data: &[u8], offset: usize) {
unsafe {
copy_nonoverlapping(data.as_ptr(), self.ptr.add(offset), data.len());
}
}
fn as_mut_ptr(&mut self) -> *mut u8 {
self.ptr
}
}
impl Drop for AlignedMemory {
fn drop(&mut self) {
unsafe {
dealloc(self.ptr, self.layout);
}
}
}
fn compute_input_size(accounts: &[AccountBuilder], instruction_data: &[u8]) -> usize {
let mut size = size_of::<u64>();
for builder in accounts {
let data_len = builder.data.len();
let account_buf_size = STATIC_ACCOUNT_DATA + size_of::<u64>();
size += account_buf_size;
let padding = (data_len + (BPF_ALIGN_OF_U128 - 1)) & !(BPF_ALIGN_OF_U128 - 1);
size += padding;
}
size += size_of::<u64>(); size += instruction_data.len(); size += 32;
size
}
unsafe fn create_test_input(accounts: &[AccountBuilder], instruction_data: &[u8]) -> AlignedMemory {
let total_size = compute_input_size(accounts, instruction_data);
let mut input = AlignedMemory::new(total_size);
unsafe {
input.write(&(accounts.len() as u64).to_le_bytes(), 0);
}
let mut offset = size_of::<u64>();
for builder in accounts {
let data_len = builder.data.len();
let account_buf_size = STATIC_ACCOUNT_DATA + size_of::<u64>();
let mut account_buf = vec![0u8; account_buf_size];
account_buf[0] = entrypoint::NON_DUP_MARKER;
account_buf[1] = u8::from(builder.is_signer);
account_buf[2] = u8::from(builder.is_writable);
account_buf[3] = u8::from(builder.executable);
account_buf[8..40].copy_from_slice(builder.address.as_ref());
account_buf[40..72].copy_from_slice(builder.owner.as_ref());
account_buf[72..80].copy_from_slice(&builder.lamports.to_le_bytes());
account_buf[80..88].copy_from_slice(&(data_len as u64).to_le_bytes());
if !builder.data.is_empty() {
account_buf[88..88 + data_len].copy_from_slice(&builder.data);
}
unsafe {
input.write(&account_buf, offset);
}
offset += account_buf_size;
let padding = (data_len + (BPF_ALIGN_OF_U128 - 1)) & !(BPF_ALIGN_OF_U128 - 1);
if padding > 0 {
unsafe {
input.write(&vec![0u8; padding], offset);
}
offset += padding;
}
}
unsafe {
input.write(&instruction_data.len().to_le_bytes(), offset);
}
offset += size_of::<u64>();
unsafe {
input.write(instruction_data, offset);
}
offset += instruction_data.len();
unsafe {
input.write(TEST_PROGRAM_ID.as_ref(), offset);
}
input
}
unsafe fn deserialize_test_input<const MAX_ACCOUNTS: usize>(
input: &mut AlignedMemory,
accounts: &mut [MaybeUninit<AccountView>; MAX_ACCOUNTS],
) -> (
&'static Address,
&'static [AccountView],
&'static [u8],
usize,
) {
let (program_id, count, ix_data) =
unsafe { entrypoint::deserialize::<MAX_ACCOUNTS>(input.as_mut_ptr(), accounts) };
let accounts: &[AccountView] =
unsafe { core::slice::from_raw_parts(accounts.as_ptr().cast(), count) };
(program_id, accounts, ix_data, count)
}
fn build_test_state_bytes(bump: u8, value: u64) -> Vec<u8> {
let state = TestState::builder()
.bump(bump)
._padding(0)
._padding2(0)
.value(PodU64::from_primitive(value))
.build();
bytemuck::bytes_of(&state).to_vec()
}
#[test]
fn full_account_lifecycle() {
let authority_key: Address = address!("BHvLHF6mJpWxywWY5S2tsHdDtHirHyeRxoS6uF6T5FoY");
let state_key: Address = address!("3Jiy8N6ZGv3ueH9k3svLRaHscmQbE6v7W9FHJaGH2mki");
let rent_lamports: u64 = 1_000_000;
let state_data = vec![0u8; size_of::<TestState>()];
let init_data = InitializeInstr::builder()
.bump(42)
.initial_value(PodU64::from_primitive(100))
.build();
let init_bytes = bytemuck::bytes_of(&init_data);
let accounts = [
AccountBuilder::new()
.address(authority_key)
.owner(system::ID)
.lamports(10_000_000)
.is_signer(true)
.is_writable(true),
AccountBuilder::new()
.address(state_key)
.owner(TEST_PROGRAM_ID)
.lamports(rent_lamports)
.data(&state_data)
.is_writable(true),
AccountBuilder::new().address(system::ID).executable(true),
];
let mut input = unsafe { create_test_input(&accounts, init_bytes) };
let mut accts = [UNINIT; 10];
let (program_id, account_views, ix_data, _) =
unsafe { deserialize_test_input::<10>(&mut input, &mut accts) };
let result = process_instruction(program_id, account_views, ix_data);
assert!(result.is_ok(), "Initialize should succeed, got: {result:?}");
let state = account_views[1]
.as_account::<TestState>(&TEST_PROGRAM_ID)
.unwrap_or_else(|e| panic!("failed to read state after init: {e:?}"));
assert_eq!(state.bump, 42, "bump should be 42");
assert_eq!(u64::from(state.value), 100, "initial value should be 100");
let update_data = UpdateInstr::builder()
.new_value(PodU64::from_primitive(999))
.build();
let update_bytes = bytemuck::bytes_of(&update_data);
let state_bytes = build_test_state_bytes(42, 100);
let accounts = [
AccountBuilder::new()
.address(authority_key)
.owner(system::ID)
.lamports(10_000_000)
.is_signer(true)
.is_writable(true),
AccountBuilder::new()
.address(state_key)
.owner(TEST_PROGRAM_ID)
.lamports(rent_lamports)
.data(&state_bytes)
.is_writable(true),
];
let mut input = unsafe { create_test_input(&accounts, update_bytes) };
let mut accts = [UNINIT; 10];
let (program_id, account_views, ix_data, _) =
unsafe { deserialize_test_input::<10>(&mut input, &mut accts) };
let result = process_instruction(program_id, account_views, ix_data);
assert!(result.is_ok(), "Update should succeed, got: {result:?}");
let state = account_views[1]
.as_account::<TestState>(&TEST_PROGRAM_ID)
.unwrap_or_else(|e| panic!("failed to read state after update: {e:?}"));
assert_eq!(u64::from(state.value), 999, "value should be 999");
assert_eq!(state.bump, 42, "bump should remain 42");
let state_bytes = build_test_state_bytes(42, 999);
let close_data = CloseInstr::builder().build();
let close_bytes = bytemuck::bytes_of(&close_data);
let accounts = [
AccountBuilder::new()
.address(authority_key)
.owner(system::ID)
.lamports(10_000_000)
.is_signer(true)
.is_writable(true),
AccountBuilder::new()
.address(state_key)
.owner(TEST_PROGRAM_ID)
.lamports(rent_lamports)
.data(&state_bytes)
.is_writable(true),
];
let mut input = unsafe { create_test_input(&accounts, close_bytes) };
let mut accts = [UNINIT; 10];
let (program_id, account_views, ix_data, _) =
unsafe { deserialize_test_input::<10>(&mut input, &mut accts) };
let authority_lamports_before = account_views[0].lamports();
let state_lamports_before = account_views[1].lamports();
let result = process_instruction(program_id, account_views, ix_data);
assert!(result.is_ok(), "Close should succeed, got: {result:?}");
assert_eq!(
account_views[0].lamports(),
authority_lamports_before + state_lamports_before,
"authority should receive all rent lamports"
);
assert_eq!(
account_views[1].lamports(),
0,
"state account should have 0 lamports after close"
);
}
#[test]
fn multi_instruction_flow_initialize_then_update() {
let authority_key: Address = address!("BHvLHF6mJpWxywWY5S2tsHdDtHirHyeRxoS6uF6T5FoY");
let state_key: Address = address!("3Jiy8N6ZGv3ueH9k3svLRaHscmQbE6v7W9FHJaGH2mki");
let state_data = vec![0u8; size_of::<TestState>()];
let init_data = InitializeInstr::builder()
.bump(7)
.initial_value(PodU64::from_primitive(50))
.build();
let init_bytes = bytemuck::bytes_of(&init_data);
let accounts = [
AccountBuilder::new()
.address(authority_key)
.owner(system::ID)
.lamports(5_000_000)
.is_signer(true)
.is_writable(true),
AccountBuilder::new()
.address(state_key)
.owner(TEST_PROGRAM_ID)
.lamports(890_880)
.data(&state_data)
.is_writable(true),
AccountBuilder::new().address(system::ID).executable(true),
];
let mut input = unsafe { create_test_input(&accounts, init_bytes) };
let mut accts = [UNINIT; 10];
let (program_id, account_views, ix_data, _) =
unsafe { deserialize_test_input::<10>(&mut input, &mut accts) };
let result = process_instruction(program_id, account_views, ix_data);
assert!(result.is_ok(), "Initialize failed: {result:?}");
let state = account_views[1]
.as_account::<TestState>(&TEST_PROGRAM_ID)
.unwrap_or_else(|e| panic!("read failed: {e:?}"));
assert_eq!(u64::from(state.value), 50);
assert_eq!(state.bump, 7);
let state_bytes = build_test_state_bytes(7, 50);
let update_data = UpdateInstr::builder()
.new_value(PodU64::from_primitive(200))
.build();
let update_bytes = bytemuck::bytes_of(&update_data);
let accounts = [
AccountBuilder::new()
.address(authority_key)
.owner(system::ID)
.lamports(5_000_000)
.is_signer(true)
.is_writable(true),
AccountBuilder::new()
.address(state_key)
.owner(TEST_PROGRAM_ID)
.lamports(890_880)
.data(&state_bytes)
.is_writable(true),
];
let mut input = unsafe { create_test_input(&accounts, update_bytes) };
let mut accts = [UNINIT; 10];
let (program_id, account_views, ix_data, _) =
unsafe { deserialize_test_input::<10>(&mut input, &mut accts) };
let result = process_instruction(program_id, account_views, ix_data);
assert!(result.is_ok(), "Update failed: {result:?}");
let state = account_views[1]
.as_account::<TestState>(&TEST_PROGRAM_ID)
.unwrap_or_else(|e| panic!("read failed: {e:?}"));
assert_eq!(u64::from(state.value), 200);
assert_eq!(state.bump, 7, "bump should be preserved across update");
}
#[test]
fn multi_update_flow() {
let authority_key: Address = address!("BHvLHF6mJpWxywWY5S2tsHdDtHirHyeRxoS6uF6T5FoY");
let state_key: Address = address!("3Jiy8N6ZGv3ueH9k3svLRaHscmQbE6v7W9FHJaGH2mki");
let mut current_value: u64 = 10;
for new_value in [20u64, 30, 40, 50, u64::MAX] {
let state_bytes = build_test_state_bytes(1, current_value);
let update_data = UpdateInstr::builder()
.new_value(PodU64::from_primitive(new_value))
.build();
let update_bytes = bytemuck::bytes_of(&update_data);
let accounts = [
AccountBuilder::new()
.address(authority_key)
.owner(system::ID)
.lamports(5_000_000)
.is_signer(true)
.is_writable(true),
AccountBuilder::new()
.address(state_key)
.owner(TEST_PROGRAM_ID)
.lamports(890_880)
.data(&state_bytes)
.is_writable(true),
];
let mut input = unsafe { create_test_input(&accounts, update_bytes) };
let mut accts = [UNINIT; 10];
let (program_id, account_views, ix_data, _) =
unsafe { deserialize_test_input::<10>(&mut input, &mut accts) };
let result = process_instruction(program_id, account_views, ix_data);
assert!(result.is_ok(), "Update to {new_value} failed: {result:?}");
let state = account_views[1]
.as_account::<TestState>(&TEST_PROGRAM_ID)
.unwrap_or_else(|e| panic!("read failed: {e:?}"));
assert_eq!(
u64::from(state.value),
new_value,
"value should be {new_value}"
);
current_value = new_value;
}
}
#[test]
fn error_missing_signer_rejected() {
let authority_key: Address = address!("BHvLHF6mJpWxywWY5S2tsHdDtHirHyeRxoS6uF6T5FoY");
let state_key: Address = address!("3Jiy8N6ZGv3ueH9k3svLRaHscmQbE6v7W9FHJaGH2mki");
let state_data = vec![0u8; size_of::<TestState>()];
let init_data = InitializeInstr::builder()
.bump(1)
.initial_value(PodU64::from_primitive(0))
.build();
let init_bytes = bytemuck::bytes_of(&init_data);
let accounts = [
AccountBuilder::new()
.address(authority_key)
.owner(system::ID)
.lamports(5_000_000)
.is_signer(false) .is_writable(true),
AccountBuilder::new()
.address(state_key)
.owner(TEST_PROGRAM_ID)
.lamports(890_880)
.data(&state_data)
.is_writable(true),
AccountBuilder::new().address(system::ID).executable(true),
];
let mut input = unsafe { create_test_input(&accounts, init_bytes) };
let mut accts = [UNINIT; 10];
let (program_id, account_views, ix_data, _) =
unsafe { deserialize_test_input::<10>(&mut input, &mut accts) };
let result = process_instruction(program_id, account_views, ix_data);
assert!(result.is_err(), "should fail without signer");
assert_eq!(
result.unwrap_err(),
ProgramError::MissingRequiredSignature,
"error should be MissingRequiredSignature"
);
}
#[test]
fn error_wrong_program_owner_rejected() {
let authority_key: Address = address!("BHvLHF6mJpWxywWY5S2tsHdDtHirHyeRxoS6uF6T5FoY");
let state_key: Address = address!("3Jiy8N6ZGv3ueH9k3svLRaHscmQbE6v7W9FHJaGH2mki");
let state_bytes = build_test_state_bytes(1, 100);
let update_data = UpdateInstr::builder()
.new_value(PodU64::from_primitive(200))
.build();
let update_bytes = bytemuck::bytes_of(&update_data);
let wrong_owner = system::ID;
let accounts = [
AccountBuilder::new()
.address(authority_key)
.owner(system::ID)
.lamports(5_000_000)
.is_signer(true)
.is_writable(true),
AccountBuilder::new()
.address(state_key)
.owner(wrong_owner) .lamports(890_880)
.data(&state_bytes)
.is_writable(true),
];
let mut input = unsafe { create_test_input(&accounts, update_bytes) };
let mut accts = [UNINIT; 10];
let (program_id, account_views, ix_data, _) =
unsafe { deserialize_test_input::<10>(&mut input, &mut accts) };
let result = process_instruction(program_id, account_views, ix_data);
assert!(result.is_err(), "should fail with wrong owner");
assert_eq!(
result.unwrap_err(),
ProgramError::InvalidAccountOwner,
"error should be InvalidAccountOwner"
);
}
#[test]
fn error_discriminator_mismatch_rejected() {
let authority_key: Address = address!("BHvLHF6mJpWxywWY5S2tsHdDtHirHyeRxoS6uF6T5FoY");
let state_key: Address = address!("3Jiy8N6ZGv3ueH9k3svLRaHscmQbE6v7W9FHJaGH2mki");
let mut bad_data = vec![0u8; size_of::<TestState>()];
bad_data[0] = 99;
let update_data = UpdateInstr::builder()
.new_value(PodU64::from_primitive(200))
.build();
let update_bytes = bytemuck::bytes_of(&update_data);
let accounts = [
AccountBuilder::new()
.address(authority_key)
.owner(system::ID)
.lamports(5_000_000)
.is_signer(true)
.is_writable(true),
AccountBuilder::new()
.address(state_key)
.owner(TEST_PROGRAM_ID)
.lamports(890_880)
.data(&bad_data)
.is_writable(true),
];
let mut input = unsafe { create_test_input(&accounts, update_bytes) };
let mut accts = [UNINIT; 10];
let (program_id, account_views, ix_data, _) =
unsafe { deserialize_test_input::<10>(&mut input, &mut accts) };
let result = process_instruction(program_id, account_views, ix_data);
assert!(result.is_err(), "should fail with discriminator mismatch");
assert_eq!(
result.unwrap_err(),
ProgramError::InvalidAccountData,
"error should be InvalidAccountData for discriminator mismatch"
);
}
#[test]
fn error_data_length_mismatch_rejected() {
let authority_key: Address = address!("BHvLHF6mJpWxywWY5S2tsHdDtHirHyeRxoS6uF6T5FoY");
let state_key: Address = address!("3Jiy8N6ZGv3ueH9k3svLRaHscmQbE6v7W9FHJaGH2mki");
let short_data = vec![TestAccountType::TestState as u8, 0, 0, 0, 0];
let update_data = UpdateInstr::builder()
.new_value(PodU64::from_primitive(200))
.build();
let update_bytes = bytemuck::bytes_of(&update_data);
let accounts = [
AccountBuilder::new()
.address(authority_key)
.owner(system::ID)
.lamports(5_000_000)
.is_signer(true)
.is_writable(true),
AccountBuilder::new()
.address(state_key)
.owner(TEST_PROGRAM_ID)
.lamports(890_880)
.data(&short_data)
.is_writable(true),
];
let mut input = unsafe { create_test_input(&accounts, update_bytes) };
let mut accts = [UNINIT; 10];
let (program_id, account_views, ix_data, _) =
unsafe { deserialize_test_input::<10>(&mut input, &mut accts) };
let result = process_instruction(program_id, account_views, ix_data);
assert!(result.is_err(), "should fail with data length mismatch");
assert_eq!(
result.unwrap_err(),
ProgramError::AccountDataTooSmall,
"error should be AccountDataTooSmall for size mismatch"
);
}
#[test]
fn error_invalid_instruction_discriminator() {
let authority_key: Address = address!("BHvLHF6mJpWxywWY5S2tsHdDtHirHyeRxoS6uF6T5FoY");
let state_key: Address = address!("3Jiy8N6ZGv3ueH9k3svLRaHscmQbE6v7W9FHJaGH2mki");
let bad_ix_data = [99u8, 0, 0, 0, 0, 0, 0, 0, 0, 0];
let accounts = [
AccountBuilder::new()
.address(authority_key)
.owner(system::ID)
.lamports(5_000_000)
.is_signer(true)
.is_writable(true),
AccountBuilder::new()
.address(state_key)
.owner(TEST_PROGRAM_ID)
.lamports(890_880)
.is_writable(true),
];
let mut input = unsafe { create_test_input(&accounts, &bad_ix_data) };
let mut accts = [UNINIT; 10];
let (program_id, account_views, ix_data, _) =
unsafe { deserialize_test_input::<10>(&mut input, &mut accts) };
let result = process_instruction(program_id, account_views, ix_data);
assert!(result.is_err(), "should fail with invalid discriminator");
assert_eq!(
result.unwrap_err(),
ProgramError::InvalidInstructionData,
"error should be InvalidInstructionData"
);
}
#[test]
fn error_empty_instruction_data() {
let authority_key: Address = address!("BHvLHF6mJpWxywWY5S2tsHdDtHirHyeRxoS6uF6T5FoY");
let accounts = [AccountBuilder::new()
.address(authority_key)
.owner(system::ID)
.lamports(5_000_000)
.is_signer(true)
.is_writable(true)];
let mut input = unsafe { create_test_input(&accounts, &[]) };
let mut accts = [UNINIT; 10];
let (program_id, account_views, ix_data, _) =
unsafe { deserialize_test_input::<10>(&mut input, &mut accts) };
let result = process_instruction(program_id, account_views, ix_data);
assert!(result.is_err(), "should fail with empty data");
assert_eq!(
result.unwrap_err(),
ProgramError::InvalidInstructionData,
"error should be InvalidInstructionData for empty data"
);
}
#[test]
fn error_wrong_program_id() {
let authority_key: Address = address!("BHvLHF6mJpWxywWY5S2tsHdDtHirHyeRxoS6uF6T5FoY");
let wrong_program_id = system::ID;
let init_data = InitializeInstr::builder()
.bump(1)
.initial_value(PodU64::from_primitive(0))
.build();
let init_bytes = bytemuck::bytes_of(&init_data);
let accounts = [AccountBuilder::new()
.address(authority_key)
.owner(system::ID)
.lamports(5_000_000)
.is_signer(true)
.is_writable(true)];
let result =
parse_instruction::<TestInstruction>(&wrong_program_id, &TEST_PROGRAM_ID, init_bytes);
assert!(result.is_err(), "should fail with wrong program ID");
assert_eq!(
result.unwrap_err(),
ProgramError::IncorrectProgramId,
"error should be IncorrectProgramId"
);
drop(accounts);
}
#[test]
fn error_not_enough_accounts() {
let authority_key: Address = address!("BHvLHF6mJpWxywWY5S2tsHdDtHirHyeRxoS6uF6T5FoY");
let init_data = InitializeInstr::builder()
.bump(1)
.initial_value(PodU64::from_primitive(0))
.build();
let init_bytes = bytemuck::bytes_of(&init_data);
let accounts = [AccountBuilder::new()
.address(authority_key)
.owner(system::ID)
.lamports(5_000_000)
.is_signer(true)
.is_writable(true)];
let mut input = unsafe { create_test_input(&accounts, init_bytes) };
let mut accts = [UNINIT; 10];
let (program_id, account_views, ix_data, _) =
unsafe { deserialize_test_input::<10>(&mut input, &mut accts) };
let result = process_instruction(program_id, account_views, ix_data);
assert!(result.is_err(), "should fail with not enough accounts");
assert_eq!(
result.unwrap_err(),
ProgramError::NotEnoughAccountKeys,
"error should be NotEnoughAccountKeys"
);
}
#[test]
fn error_non_writable_rejected() {
let authority_key: Address = address!("BHvLHF6mJpWxywWY5S2tsHdDtHirHyeRxoS6uF6T5FoY");
let state_key: Address = address!("3Jiy8N6ZGv3ueH9k3svLRaHscmQbE6v7W9FHJaGH2mki");
let state_bytes = build_test_state_bytes(1, 100);
let update_data = UpdateInstr::builder()
.new_value(PodU64::from_primitive(200))
.build();
let update_bytes = bytemuck::bytes_of(&update_data);
let accounts = [
AccountBuilder::new()
.address(authority_key)
.owner(system::ID)
.lamports(5_000_000)
.is_signer(true)
.is_writable(true),
AccountBuilder::new()
.address(state_key)
.owner(TEST_PROGRAM_ID)
.lamports(890_880)
.data(&state_bytes)
.is_writable(false), ];
let mut input = unsafe { create_test_input(&accounts, update_bytes) };
let mut accts = [UNINIT; 10];
let (program_id, account_views, ix_data, _) =
unsafe { deserialize_test_input::<10>(&mut input, &mut accts) };
let result = process_instruction(program_id, account_views, ix_data);
assert!(result.is_err(), "should fail with non-writable account");
assert_eq!(
result.unwrap_err(),
ProgramError::InvalidAccountData,
"error should be InvalidAccountData for non-writable"
);
}
#[test]
fn error_empty_account_rejected_for_update() {
let authority_key: Address = address!("BHvLHF6mJpWxywWY5S2tsHdDtHirHyeRxoS6uF6T5FoY");
let state_key: Address = address!("3Jiy8N6ZGv3ueH9k3svLRaHscmQbE6v7W9FHJaGH2mki");
let update_data = UpdateInstr::builder()
.new_value(PodU64::from_primitive(200))
.build();
let update_bytes = bytemuck::bytes_of(&update_data);
let accounts = [
AccountBuilder::new()
.address(authority_key)
.owner(system::ID)
.lamports(5_000_000)
.is_signer(true)
.is_writable(true),
AccountBuilder::new()
.address(state_key)
.owner(TEST_PROGRAM_ID)
.lamports(0)
.is_writable(true),
];
let mut input = unsafe { create_test_input(&accounts, update_bytes) };
let mut accts = [UNINIT; 10];
let (program_id, account_views, ix_data, _) =
unsafe { deserialize_test_input::<10>(&mut input, &mut accts) };
let result = process_instruction(program_id, account_views, ix_data);
assert!(result.is_err(), "should fail on empty account");
assert_eq!(
result.unwrap_err(),
ProgramError::UninitializedAccount,
"error should be UninitializedAccount for empty account"
);
}
#[test]
fn lamport_transfer_send() {
let sender_key: Address = address!("BHvLHF6mJpWxywWY5S2tsHdDtHirHyeRxoS6uF6T5FoY");
let recipient_key: Address = address!("3Jiy8N6ZGv3ueH9k3svLRaHscmQbE6v7W9FHJaGH2mki");
let accounts = [
AccountBuilder::new()
.address(sender_key)
.owner(TEST_PROGRAM_ID) .lamports(1_000_000)
.is_writable(true),
AccountBuilder::new()
.address(recipient_key)
.owner(TEST_PROGRAM_ID)
.lamports(500_000)
.is_writable(true),
];
let dummy_data: &[u8] = &[0u8];
let mut input = unsafe { create_test_input(&accounts, dummy_data) };
let mut accts = [UNINIT; 10];
let (_, account_views, ..) = unsafe { deserialize_test_input::<10>(&mut input, &mut accts) };
let result = account_views[0].send(300_000, &account_views[1]);
assert!(result.is_ok(), "send should succeed: {result:?}");
assert_eq!(
account_views[0].lamports(),
700_000,
"sender should have 700_000"
);
assert_eq!(
account_views[1].lamports(),
800_000,
"recipient should have 800_000"
);
}
#[test]
fn lamport_transfer_insufficient_funds() {
let sender_key: Address = address!("BHvLHF6mJpWxywWY5S2tsHdDtHirHyeRxoS6uF6T5FoY");
let recipient_key: Address = address!("3Jiy8N6ZGv3ueH9k3svLRaHscmQbE6v7W9FHJaGH2mki");
let accounts = [
AccountBuilder::new()
.address(sender_key)
.owner(TEST_PROGRAM_ID)
.lamports(100)
.is_writable(true),
AccountBuilder::new()
.address(recipient_key)
.owner(TEST_PROGRAM_ID)
.lamports(0)
.is_writable(true),
];
let dummy_data: &[u8] = &[0u8];
let mut input = unsafe { create_test_input(&accounts, dummy_data) };
let mut accts = [UNINIT; 10];
let (_, account_views, ..) = unsafe { deserialize_test_input::<10>(&mut input, &mut accts) };
let result = account_views[0].send(101, &account_views[1]);
assert!(result.is_err(), "should fail with insufficient funds");
assert_eq!(
result.unwrap_err(),
ProgramError::InsufficientFunds,
"error should be InsufficientFunds"
);
assert_eq!(account_views[0].lamports(), 100);
assert_eq!(account_views[1].lamports(), 0);
}
#[test]
fn lamport_transfer_same_account_rejected() {
let key: Address = address!("BHvLHF6mJpWxywWY5S2tsHdDtHirHyeRxoS6uF6T5FoY");
let accounts = [AccountBuilder::new()
.address(key)
.owner(TEST_PROGRAM_ID)
.lamports(1_000)
.is_writable(true)];
let dummy_data: &[u8] = &[0u8];
let mut input = unsafe { create_test_input(&accounts, dummy_data) };
let mut accts = [UNINIT; 10];
let (_, account_views, ..) = unsafe { deserialize_test_input::<10>(&mut input, &mut accts) };
let result = account_views[0].send(500, &account_views[0]);
assert!(result.is_err(), "should fail sending to self");
assert_eq!(
result.unwrap_err(),
ProgramError::InvalidArgument,
"error should be InvalidArgument for same account"
);
}
#[test]
fn close_account_with_recipient() {
let account_key: Address = address!("BHvLHF6mJpWxywWY5S2tsHdDtHirHyeRxoS6uF6T5FoY");
let recipient_key: Address = address!("3Jiy8N6ZGv3ueH9k3svLRaHscmQbE6v7W9FHJaGH2mki");
let state_data = build_test_state_bytes(1, 42);
let accounts = [
AccountBuilder::new()
.address(account_key)
.owner(TEST_PROGRAM_ID)
.lamports(1_000_000)
.data(&state_data)
.is_writable(true),
AccountBuilder::new()
.address(recipient_key)
.owner(system::ID)
.lamports(500_000)
.is_writable(true),
];
let dummy_data: &[u8] = &[0u8];
let mut input = unsafe { create_test_input(&accounts, dummy_data) };
let mut accts = [UNINIT; 10];
let (_, account_views, ..) = unsafe { deserialize_test_input::<10>(&mut input, &mut accts) };
let result = account_views[0].close_with_recipient(&account_views[1]);
assert!(result.is_ok(), "close should succeed: {result:?}");
assert_eq!(
account_views[0].lamports(),
0,
"closed account should have 0 lamports"
);
assert_eq!(
account_views[1].lamports(),
1_500_000,
"recipient should have 1_500_000 lamports"
);
}
#[test]
fn account_view_validation_chain() {
let key: Address = address!("BHvLHF6mJpWxywWY5S2tsHdDtHirHyeRxoS6uF6T5FoY");
let state_bytes = build_test_state_bytes(5, 77);
let accounts = [AccountBuilder::new()
.address(key)
.owner(TEST_PROGRAM_ID)
.lamports(1_000_000)
.data(&state_bytes)
.is_signer(true)
.is_writable(true)];
let dummy_data: &[u8] = &[0u8];
let mut input = unsafe { create_test_input(&accounts, dummy_data) };
let mut accts = [UNINIT; 10];
let (_, account_views, ..) = unsafe { deserialize_test_input::<10>(&mut input, &mut accts) };
let account = &account_views[0];
let result = account
.assert_signer()
.and_then(|a| a.assert_writable())
.and_then(|a| a.assert_not_empty())
.and_then(|a| a.assert_owner(&TEST_PROGRAM_ID))
.and_then(|a| a.assert_address(&key))
.and_then(|a| a.assert_data_len(size_of::<TestState>()))
.and_then(|a| a.assert_type::<TestState>(&TEST_PROGRAM_ID));
assert!(
result.is_ok(),
"validation chain should succeed: {result:?}"
);
}
#[test]
fn account_view_validation_chain_short_circuits() {
let key: Address = address!("BHvLHF6mJpWxywWY5S2tsHdDtHirHyeRxoS6uF6T5FoY");
let state_bytes = build_test_state_bytes(5, 77);
let accounts = [AccountBuilder::new()
.address(key)
.owner(TEST_PROGRAM_ID)
.lamports(1_000_000)
.data(&state_bytes)
.is_signer(false) .is_writable(true)];
let dummy_data: &[u8] = &[0u8];
let mut input = unsafe { create_test_input(&accounts, dummy_data) };
let mut accts = [UNINIT; 10];
let (_, account_views, ..) = unsafe { deserialize_test_input::<10>(&mut input, &mut accts) };
let account = &account_views[0];
let result = account
.assert_signer()
.and_then(|a| a.assert_writable())
.and_then(|a| a.assert_owner(&TEST_PROGRAM_ID));
assert!(result.is_err());
assert_eq!(result.unwrap_err(), ProgramError::MissingRequiredSignature);
}
#[test]
fn account_data_roundtrip_through_account_view() {
let key: Address = address!("BHvLHF6mJpWxywWY5S2tsHdDtHirHyeRxoS6uF6T5FoY");
let state_data = vec![0u8; size_of::<TestState>()];
let accounts = [AccountBuilder::new()
.address(key)
.owner(TEST_PROGRAM_ID)
.lamports(1_000_000)
.data(&state_data)
.is_writable(true)];
let dummy_data: &[u8] = &[0u8];
let mut input = unsafe { create_test_input(&accounts, dummy_data) };
let mut accts = [UNINIT; 10];
let (_, account_views, ..) = unsafe { deserialize_test_input::<10>(&mut input, &mut accts) };
{
let new_state = TestState::builder()
.bump(123)
._padding(0)
._padding2(0)
.value(PodU64::from_primitive(u64::MAX))
.build();
let state_bytes = bytemuck::bytes_of(&new_state);
let mut account_data = account_views[0]
.try_borrow_mut()
.unwrap_or_else(|e| panic!("borrow failed: {e:?}"));
account_data[..state_bytes.len()].copy_from_slice(state_bytes);
}
let state = account_views[0]
.as_account::<TestState>(&TEST_PROGRAM_ID)
.unwrap_or_else(|e| panic!("read failed: {e:?}"));
assert_eq!(state.bump, 123);
assert_eq!(u64::from(state.value), u64::MAX);
}
#[test]
fn account_data_mutation_persists() {
let key: Address = address!("BHvLHF6mJpWxywWY5S2tsHdDtHirHyeRxoS6uF6T5FoY");
let state_bytes = build_test_state_bytes(10, 500);
let accounts = [AccountBuilder::new()
.address(key)
.owner(TEST_PROGRAM_ID)
.lamports(1_000_000)
.data(&state_bytes)
.is_writable(true)];
let dummy_data: &[u8] = &[0u8];
let mut input = unsafe { create_test_input(&accounts, dummy_data) };
let mut accts = [UNINIT; 10];
let (_, account_views, ..) = unsafe { deserialize_test_input::<10>(&mut input, &mut accts) };
{
let state = account_views[0]
.as_account_mut::<TestState>(&TEST_PROGRAM_ID)
.unwrap_or_else(|e| panic!("write failed: {e:?}"));
state.value = PodU64::from_primitive(12345);
}
let state = account_views[0]
.as_account::<TestState>(&TEST_PROGRAM_ID)
.unwrap_or_else(|e| panic!("read failed: {e:?}"));
assert_eq!(u64::from(state.value), 12345);
assert_eq!(state.bump, 10, "bump should be unchanged");
}
#[test]
fn try_from_account_infos_maps_correctly() {
let authority_key: Address = address!("BHvLHF6mJpWxywWY5S2tsHdDtHirHyeRxoS6uF6T5FoY");
let state_key: Address = address!("3Jiy8N6ZGv3ueH9k3svLRaHscmQbE6v7W9FHJaGH2mki");
let state_bytes = build_test_state_bytes(1, 100);
let accounts = [
AccountBuilder::new()
.address(authority_key)
.owner(system::ID)
.lamports(5_000_000)
.is_signer(true)
.is_writable(true),
AccountBuilder::new()
.address(state_key)
.owner(TEST_PROGRAM_ID)
.lamports(890_880)
.data(&state_bytes)
.is_writable(true),
];
let dummy_data: &[u8] = &[0u8];
let mut input = unsafe { create_test_input(&accounts, dummy_data) };
let mut accts = [UNINIT; 10];
let (_, account_views, ..) = unsafe { deserialize_test_input::<10>(&mut input, &mut accts) };
let update_accounts = UpdateAccounts::try_from(account_views)
.unwrap_or_else(|e| panic!("failed to deserialize accounts: {e:?}"));
assert_eq!(
update_accounts.authority.address(),
&authority_key,
"authority should match"
);
assert_eq!(
update_accounts.state_account.address(),
&state_key,
"state_account should match"
);
}
#[test]
fn try_from_account_infos_rejects_too_many() {
let accounts = [
AccountBuilder::new()
.address(address!("BHvLHF6mJpWxywWY5S2tsHdDtHirHyeRxoS6uF6T5FoY"))
.is_signer(true),
AccountBuilder::new().address(address!("3Jiy8N6ZGv3ueH9k3svLRaHscmQbE6v7W9FHJaGH2mki")),
AccountBuilder::new().address(address!("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA")),
];
let dummy_data: &[u8] = &[0u8];
let mut input = unsafe { create_test_input(&accounts, dummy_data) };
let mut accts = [UNINIT; 10];
let (_, account_views, ..) = unsafe { deserialize_test_input::<10>(&mut input, &mut accts) };
let result = UpdateAccounts::try_from(account_views);
assert!(result.is_err(), "should fail with too many accounts");
assert!(
result.is_err_and(|error| error.eq(&PinaProgramError::TooManyAccountKeys.into())),
"error should be TooManyAccountKeys"
);
}
#[test]
fn pda_derive_and_verify_roundtrip() {
let seeds: &[&[u8]] = &[b"test", b"pda"];
let (pda, bump) = try_find_program_address(seeds, &TEST_PROGRAM_ID)
.unwrap_or_else(|| panic!("should derive PDA"));
let bump_seed = [bump];
let seeds_with_bump: &[&[u8]] = &[b"test", b"pda", &bump_seed];
let recreated = create_program_address(seeds_with_bump, &TEST_PROGRAM_ID)
.unwrap_or_else(|e| panic!("failed to recreate: {e:?}"));
assert_eq!(pda, recreated, "PDA should match after round-trip");
let (pda2, bump2) = try_find_program_address(seeds, &TEST_PROGRAM_ID)
.unwrap_or_else(|| panic!("second derivation failed"));
assert_eq!(pda, pda2, "PDA derivation should be deterministic");
assert_eq!(bump, bump2, "bump should be deterministic");
}
#[test]
fn pda_assert_seeds_with_bump_on_account_view() {
let seeds: &[&[u8]] = &[b"view", b"test"];
let (pda, bump) = try_find_program_address(seeds, &TEST_PROGRAM_ID)
.unwrap_or_else(|| panic!("should derive PDA"));
let bump_seed = [bump];
let seeds_with_bump: &[&[u8]] = &[b"view", b"test", &bump_seed];
let accounts = [AccountBuilder::new()
.address(pda)
.owner(TEST_PROGRAM_ID)
.lamports(1_000_000)
.is_writable(true)];
let dummy_data: &[u8] = &[0u8];
let mut input = unsafe { create_test_input(&accounts, dummy_data) };
let mut accts = [UNINIT; 10];
let (_, account_views, ..) = unsafe { deserialize_test_input::<10>(&mut input, &mut accts) };
assert_eq!(
account_views[0].address(),
&pda,
"account address should match the PDA"
);
let recreated = create_program_address(seeds_with_bump, &TEST_PROGRAM_ID)
.unwrap_or_else(|e| panic!("create_program_address failed: {e:?}"));
assert_eq!(
account_views[0].address(),
&recreated,
"AccountView address should match PDA from create_program_address"
);
let result = account_views[0].assert_seeds_with_bump(seeds_with_bump, &TEST_PROGRAM_ID);
assert!(
result.is_ok(),
"assert_seeds_with_bump should pass: {result:?}"
);
let result = account_views[0].assert_seeds(seeds, &TEST_PROGRAM_ID);
assert!(result.is_ok(), "assert_seeds should pass: {result:?}");
let result_bump = account_views[0]
.assert_canonical_bump(seeds, &TEST_PROGRAM_ID)
.unwrap_or_else(|e| panic!("assert_canonical_bump failed: {e:?}"));
assert_eq!(result_bump, bump, "canonical bump should match");
}
#[test]
fn pda_assert_seeds_rejects_wrong_address() {
let seeds: &[&[u8]] = &[b"test", b"pda"];
let wrong_address: Address = address!("BHvLHF6mJpWxywWY5S2tsHdDtHirHyeRxoS6uF6T5FoY");
let accounts = [AccountBuilder::new()
.address(wrong_address)
.owner(TEST_PROGRAM_ID)
.lamports(1_000_000)
.is_writable(true)];
let dummy_data: &[u8] = &[0u8];
let mut input = unsafe { create_test_input(&accounts, dummy_data) };
let mut accts = [UNINIT; 10];
let (_, account_views, ..) = unsafe { deserialize_test_input::<10>(&mut input, &mut accts) };
let result = account_views[0].assert_seeds(seeds, &TEST_PROGRAM_ID);
assert!(result.is_err(), "should fail with wrong address");
assert_eq!(result.unwrap_err(), ProgramError::InvalidSeeds);
}
#[test]
fn pda_assert_canonical_bump() {
let seeds: &[&[u8]] = &[b"canonical", b"bump"];
let (pda, expected_bump) = try_find_program_address(seeds, &TEST_PROGRAM_ID)
.unwrap_or_else(|| panic!("should derive PDA"));
let bump_seed = [expected_bump];
let seeds_with_bump: &[&[u8]] = &[b"canonical", b"bump", &bump_seed];
let recreated = create_program_address(seeds_with_bump, &TEST_PROGRAM_ID)
.unwrap_or_else(|e| panic!("failed to recreate with bump: {e:?}"));
assert_eq!(pda, recreated, "PDA should match with canonical bump");
if expected_bump > 0 {
let non_canonical_bump = [expected_bump - 1];
let non_canonical_seeds: &[&[u8]] = &[b"canonical", b"bump", &non_canonical_bump];
if let Ok(other_pda) = create_program_address(non_canonical_seeds, &TEST_PROGRAM_ID) {
assert_ne!(
pda, other_pda,
"non-canonical bump should produce a different PDA"
);
}
}
}
#[test]
fn discriminator_dispatch_all_variants() {
for (byte, expected_name) in [(0u8, "Initialize"), (1u8, "Update"), (2u8, "Close")] {
let data = [byte];
let result: TestInstruction = parse_instruction(&TEST_PROGRAM_ID, &TEST_PROGRAM_ID, &data)
.unwrap_or_else(|e| panic!("parse variant {expected_name} failed: {e:?}"));
match (byte, result) {
(0, TestInstruction::Initialize) => {}
(1, TestInstruction::Update) => {}
(2, TestInstruction::Close) => {}
_ => panic!("unexpected dispatch for byte {byte}"),
}
}
}
#[test]
fn has_discriminator_matches_for_account_type() {
assert!(TestState::matches_discriminator(&[
TestAccountType::TestState as u8
]));
assert!(!TestState::matches_discriminator(&[0u8]));
assert!(!TestState::matches_discriminator(&[99u8]));
assert!(!TestState::matches_discriminator(&[]));
}
#[test]
fn assert_address_succeeds() {
let key: Address = address!("BHvLHF6mJpWxywWY5S2tsHdDtHirHyeRxoS6uF6T5FoY");
let accounts = [AccountBuilder::new().address(key)];
let dummy_data: &[u8] = &[0u8];
let mut input = unsafe { create_test_input(&accounts, dummy_data) };
let mut accts = [UNINIT; 10];
let (_, account_views, ..) = unsafe { deserialize_test_input::<10>(&mut input, &mut accts) };
let result = account_views[0].assert_address(&key);
assert!(result.is_ok());
}
#[test]
fn assert_address_fails_for_wrong_address() {
let key: Address = address!("BHvLHF6mJpWxywWY5S2tsHdDtHirHyeRxoS6uF6T5FoY");
let wrong: Address = address!("3Jiy8N6ZGv3ueH9k3svLRaHscmQbE6v7W9FHJaGH2mki");
let accounts = [AccountBuilder::new().address(key)];
let dummy_data: &[u8] = &[0u8];
let mut input = unsafe { create_test_input(&accounts, dummy_data) };
let mut accts = [UNINIT; 10];
let (_, account_views, ..) = unsafe { deserialize_test_input::<10>(&mut input, &mut accts) };
let result = account_views[0].assert_address(&wrong);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), ProgramError::InvalidAccountData);
}
#[test]
fn assert_addresses_succeeds_for_matching() {
let key: Address = address!("BHvLHF6mJpWxywWY5S2tsHdDtHirHyeRxoS6uF6T5FoY");
let other: Address = address!("3Jiy8N6ZGv3ueH9k3svLRaHscmQbE6v7W9FHJaGH2mki");
let accounts = [AccountBuilder::new().address(key)];
let dummy_data: &[u8] = &[0u8];
let mut input = unsafe { create_test_input(&accounts, dummy_data) };
let mut accts = [UNINIT; 10];
let (_, account_views, ..) = unsafe { deserialize_test_input::<10>(&mut input, &mut accts) };
let result = account_views[0].assert_addresses(&[other, key]);
assert!(result.is_ok());
}
#[test]
fn assert_addresses_fails_for_no_match() {
let key: Address = address!("BHvLHF6mJpWxywWY5S2tsHdDtHirHyeRxoS6uF6T5FoY");
let other1: Address = address!("3Jiy8N6ZGv3ueH9k3svLRaHscmQbE6v7W9FHJaGH2mki");
let other2: Address = address!("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
let accounts = [AccountBuilder::new().address(key)];
let dummy_data: &[u8] = &[0u8];
let mut input = unsafe { create_test_input(&accounts, dummy_data) };
let mut accts = [UNINIT; 10];
let (_, account_views, ..) = unsafe { deserialize_test_input::<10>(&mut input, &mut accts) };
let result = account_views[0].assert_addresses(&[other1, other2]);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), ProgramError::InvalidAccountData);
}