Skip to content

Commit

Permalink
beef up program tests
Browse files Browse the repository at this point in the history
  • Loading branch information
buffalojoec committed Jun 22, 2024
1 parent 16bc66d commit a1945f1
Show file tree
Hide file tree
Showing 3 changed files with 280 additions and 22 deletions.
2 changes: 1 addition & 1 deletion bencher/tests/markdown.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ fn test_markdown() {

let program_id = Pubkey::new_unique();

let instruction = Instruction::new_with_bytes(program_id, &[1], vec![]);
let instruction = Instruction::new_with_bytes(program_id, &[0], vec![]);
let accounts = vec![];

let mollusk = Mollusk::new(&program_id, "test_program");
Expand Down
227 changes: 213 additions & 14 deletions harness/tests/bpf_program.rs
Original file line number Diff line number Diff line change
@@ -1,35 +1,234 @@
use {
mollusk::{result::Check, Mollusk},
solana_sdk::{instruction::Instruction, program_error::ProgramError, pubkey::Pubkey},
mollusk::{program::system_program_account, result::Check, Mollusk},
solana_sdk::{
account::AccountSharedData,
incinerator,
instruction::{AccountMeta, Instruction},
program_error::ProgramError,
pubkey::Pubkey,
system_instruction::SystemError,
system_program,
},
};

#[test]
fn test_set_return_data() {
fn test_write_data() {
std::env::set_var("SBF_OUT_DIR", "../target/deploy");

let program_id = Pubkey::new_unique();

let instruction = Instruction::new_with_bytes(program_id, &[1], vec![]);
let checks = vec![Check::success(), Check::compute_units(143)];

let mollusk = Mollusk::new(&program_id, "test_program");

mollusk.process_and_validate_instruction(&instruction, vec![], &checks);
let data = &[1, 2, 3, 4, 5];
let space = data.len();
let lamports = mollusk.get_rent().minimum_balance(space);

let key = Pubkey::new_unique();
let account = AccountSharedData::new(lamports, space, &program_id);

let instruction = {
let mut instruction_data = vec![1];
instruction_data.extend_from_slice(data);
Instruction::new_with_bytes(
program_id,
&instruction_data,
vec![AccountMeta::new(key, true)],
)
};

// Fail account not signer.
{
let mut account_not_signer_ix = instruction.clone();
account_not_signer_ix.accounts[0].is_signer = false;

mollusk.process_and_validate_instruction(
&account_not_signer_ix,
vec![(key, account.clone())],
&[
Check::err(ProgramError::MissingRequiredSignature),
Check::compute_units(272),
],
);
}

// Fail data too large.
{
let mut data_too_large_ix = instruction.clone();
data_too_large_ix.data = vec![1; space + 2];

mollusk.process_and_validate_instruction(
&data_too_large_ix,
vec![(key, account.clone())],
&[
Check::err(ProgramError::AccountDataTooSmall),
Check::compute_units(281),
],
);
}

// Success.
mollusk.process_and_validate_instruction(
&instruction,
vec![(key, account.clone())],
&[
Check::success(),
Check::compute_units(350),
Check::account(&key)
.data(data)
.lamports(lamports)
.owner(program_id)
.build(),
],
);
}

#[test]
fn test_fail_empty_input() {
fn test_transfer() {
std::env::set_var("SBF_OUT_DIR", "../target/deploy");

let program_id = Pubkey::new_unique();

let instruction = Instruction::new_with_bytes(program_id, &[], vec![]);
let checks = vec![
Check::err(ProgramError::InvalidInstructionData),
Check::compute_units(55),
];
let mollusk = Mollusk::new(&program_id, "test_program");

let payer = Pubkey::new_unique();
let payer_lamports = 100_000_000;
let payer_account = AccountSharedData::new(payer_lamports, 0, &system_program::id());

let recipient = Pubkey::new_unique();
let recipient_lamports = 0;
let recipient_account = AccountSharedData::new(recipient_lamports, 0, &system_program::id());

let transfer_amount = 2_000_000_u64;

let instruction = {
let mut instruction_data = vec![2];
instruction_data.extend_from_slice(&transfer_amount.to_le_bytes());
Instruction::new_with_bytes(
program_id,
&instruction_data,
vec![
AccountMeta::new(payer, true),
AccountMeta::new(recipient, false),
AccountMeta::new_readonly(system_program::id(), false),
],
)
};

// Fail payer not signer.
{
let mut payer_not_signer_ix = instruction.clone();
payer_not_signer_ix.accounts[0].is_signer = false;

mollusk.process_and_validate_instruction(
&payer_not_signer_ix,
vec![
(payer, payer_account.clone()),
(recipient, recipient_account.clone()),
(system_program::id(), system_program_account()),
],
&[
Check::err(ProgramError::MissingRequiredSignature),
Check::compute_units(598),
],
);
}

// Fail insufficient lamports.
{
mollusk.process_and_validate_instruction(
&instruction,
vec![
(payer, AccountSharedData::default()),
(recipient, recipient_account.clone()),
(system_program::id(), system_program_account()),
],
&[
Check::err(ProgramError::Custom(
SystemError::ResultWithNegativeLamports as u32,
)),
Check::compute_units(2256),
],
);
}

// Success.
mollusk.process_and_validate_instruction(
&instruction,
vec![
(payer, payer_account.clone()),
(recipient, recipient_account.clone()),
(system_program::id(), system_program_account()),
],
&[
Check::success(),
Check::compute_units(2366),
Check::account(&payer)
.lamports(payer_lamports - transfer_amount)
.build(),
Check::account(&recipient)
.lamports(recipient_lamports + transfer_amount)
.build(),
],
);
}

#[test]
fn test_close_account() {
std::env::set_var("SBF_OUT_DIR", "../target/deploy");

let program_id = Pubkey::new_unique();

let mollusk = Mollusk::new(&program_id, "test_program");

mollusk.process_and_validate_instruction(&instruction, vec![], &checks);
let key = Pubkey::new_unique();
let account = AccountSharedData::new(50_000_000, 50, &program_id);

let instruction = Instruction::new_with_bytes(
program_id,
&[3],
vec![
AccountMeta::new(key, true),
AccountMeta::new(incinerator::id(), false),
AccountMeta::new_readonly(system_program::id(), false),
],
);

// Fail account not signer.
{
let mut account_not_signer_ix = instruction.clone();
account_not_signer_ix.accounts[0].is_signer = false;

mollusk.process_and_validate_instruction(
&account_not_signer_ix,
vec![
(key, account.clone()),
(incinerator::id(), AccountSharedData::default()),
(system_program::id(), system_program_account()),
],
&[
Check::err(ProgramError::MissingRequiredSignature),
Check::compute_units(597),
],
);
}

// Success.
mollusk.process_and_validate_instruction(
&instruction,
vec![
(key, account.clone()),
(incinerator::id(), AccountSharedData::default()),
(system_program::id(), system_program_account()),
],
&[
Check::success(),
Check::compute_units(2557),
Check::account(&key)
.data(&[])
.lamports(0)
.owner(system_program::id())
.closed()
.build(),
],
);
}
73 changes: 66 additions & 7 deletions test-program/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
use solana_program::{
account_info::AccountInfo, entrypoint::ProgramResult, program::set_return_data,
program_error::ProgramError, pubkey::Pubkey,
account_info::{next_account_info, AccountInfo},
entrypoint::ProgramResult,
incinerator,
program::invoke,
program_error::ProgramError,
pubkey::Pubkey,
system_instruction, system_program,
};

solana_program::declare_id!("239vxAL9Q7e3uLoinJpJ873r3bvT9sPFxH7yekwPppNF");
Expand All @@ -9,14 +14,68 @@ solana_program::entrypoint!(process_instruction);

fn process_instruction(
_program_id: &Pubkey,
_accounts: &[AccountInfo],
accounts: &[AccountInfo],
input: &[u8],
) -> ProgramResult {
if input.is_empty() {
return Err(ProgramError::InvalidInstructionData);
}
let accounts_iter = &mut accounts.iter();

match input.split_first() {
Some((0, _)) => {
// No-op.
}
Some((1, rest)) => {
// Simply write the remaining data to the first account.
let account_info = next_account_info(accounts_iter)?;

if !account_info.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}

if rest.len() > account_info.data_len() {
return Err(ProgramError::AccountDataTooSmall);
}

account_info.try_borrow_mut_data()?[..].copy_from_slice(rest);
}
Some((2, rest)) if rest.len() == 8 => {
// Transfer from the first account to the second.
let payer_info = next_account_info(accounts_iter)?;
let recipient_info = next_account_info(accounts_iter)?;
let _system_program = next_account_info(accounts_iter)?;

if !payer_info.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}

set_return_data(input);
let lamports = u64::from_le_bytes(rest.try_into().unwrap());

invoke(
&system_instruction::transfer(payer_info.key, recipient_info.key, lamports),
&[payer_info.clone(), recipient_info.clone()],
)?;
}
Some((3, _)) => {
// Close the first account and burn its lamports.
let account_info = next_account_info(accounts_iter)?;
let incinerator_info = next_account_info(accounts_iter)?;
let _system_program = next_account_info(accounts_iter)?;

if !account_info.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}

account_info.realloc(0, true)?;
account_info.assign(&system_program::id());

let lamports = account_info.lamports();

invoke(
&system_instruction::transfer(account_info.key, &incinerator::id(), lamports),
&[account_info.clone(), incinerator_info.clone()],
)?;
}
_ => return Err(ProgramError::InvalidInstructionData),
}

Ok(())
}

0 comments on commit a1945f1

Please sign in to comment.