diff --git a/bencher/tests/markdown.rs b/bencher/tests/markdown.rs index 9077df5..9f32ef4 100644 --- a/bencher/tests/markdown.rs +++ b/bencher/tests/markdown.rs @@ -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"); diff --git a/harness/tests/bpf_program.rs b/harness/tests/bpf_program.rs index 373eeef..274ac05 100644 --- a/harness/tests/bpf_program.rs +++ b/harness/tests/bpf_program.rs @@ -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(), + ], + ); } diff --git a/test-program/src/lib.rs b/test-program/src/lib.rs index 7cc955c..dc9d36f 100644 --- a/test-program/src/lib.rs +++ b/test-program/src/lib.rs @@ -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"); @@ -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(()) }