solana/programs/sbf/tests/programs.rs

4586 lines
162 KiB
Rust

#![cfg(any(feature = "sbf_c", feature = "sbf_rust"))]
#![allow(clippy::clone_on_copy)]
#![allow(clippy::needless_range_loop)]
#![allow(clippy::redundant_clone)]
#![allow(clippy::needless_borrow)]
#![allow(clippy::cmp_owned)]
#![allow(clippy::needless_collect)]
#![allow(clippy::match_like_matches_macro)]
#![allow(clippy::unnecessary_cast)]
#![allow(clippy::uninlined_format_args)]
#[cfg(feature = "sbf_rust")]
use {
itertools::izip,
solana_account_decoder::parse_bpf_loader::{
parse_bpf_upgradeable_loader, BpfUpgradeableLoaderAccountType,
},
solana_accounts_db::transaction_results::{
DurableNonceFee, InnerInstruction, TransactionExecutionDetails, TransactionExecutionResult,
TransactionResults,
},
solana_ledger::token_balances::collect_token_balances,
solana_program_runtime::{
compute_budget::ComputeBudget,
compute_budget_processor::process_compute_budget_instructions, timings::ExecuteTimings,
},
solana_rbpf::vm::ContextObject,
solana_runtime::{
bank::TransactionBalancesSet,
loader_utils::{
create_program, load_and_finalize_program, load_program, load_program_from_file,
load_upgradeable_buffer, load_upgradeable_program, set_upgrade_authority,
upgrade_program,
},
},
solana_sbf_rust_invoke::instructions::*,
solana_sbf_rust_realloc::instructions::*,
solana_sbf_rust_realloc_invoke::instructions::*,
solana_sdk::{
account::{ReadableAccount, WritableAccount},
account_utils::StateMut,
bpf_loader_upgradeable,
clock::MAX_PROCESSING_AGE,
compute_budget::ComputeBudgetInstruction,
entrypoint::MAX_PERMITTED_DATA_INCREASE,
feature_set::{self, remove_deprecated_request_unit_ix, FeatureSet},
fee::FeeStructure,
loader_instruction,
message::{v0::LoadedAddresses, SanitizedMessage},
signature::keypair_from_seed,
stake,
system_instruction::{self, MAX_PERMITTED_DATA_LENGTH},
sysvar::{self, clock, rent},
transaction::VersionedTransaction,
},
solana_transaction_status::{
ConfirmedTransactionWithStatusMeta, InnerInstructions, TransactionStatusMeta,
TransactionWithStatusMeta, VersionedTransactionWithStatusMeta,
},
std::collections::HashMap,
};
use {
solana_program_runtime::invoke_context::mock_process_instruction,
solana_runtime::{
bank::Bank,
bank_client::BankClient,
genesis_utils::{
bootstrap_validator_stake_lamports, create_genesis_config,
create_genesis_config_with_leader_ex, GenesisConfigInfo,
},
},
solana_sdk::{
account::AccountSharedData,
bpf_loader, bpf_loader_deprecated,
client::SyncClient,
clock::UnixTimestamp,
fee_calculator::FeeRateGovernor,
genesis_config::ClusterType,
hash::Hash,
instruction::{AccountMeta, Instruction, InstructionError},
message::Message,
pubkey::Pubkey,
rent::Rent,
signature::{Keypair, Signer},
system_program,
transaction::{SanitizedTransaction, Transaction, TransactionError},
},
std::{cell::RefCell, str::FromStr, sync::Arc, time::Duration},
};
#[cfg(feature = "sbf_rust")]
fn process_transaction_and_record_inner(
bank: &Bank,
tx: Transaction,
) -> (
Result<(), TransactionError>,
Vec<Vec<InnerInstruction>>,
Vec<String>,
) {
let signature = tx.signatures.get(0).unwrap().clone();
let txs = vec![tx];
let tx_batch = bank.prepare_batch_for_tests(txs);
let mut results = bank
.load_execute_and_commit_transactions(
&tx_batch,
MAX_PROCESSING_AGE,
false,
true,
true,
false,
&mut ExecuteTimings::default(),
None,
)
.0;
let result = results
.fee_collection_results
.swap_remove(0)
.and_then(|_| bank.get_signature_status(&signature).unwrap());
let execution_details = results
.execution_results
.swap_remove(0)
.details()
.expect("tx should be executed")
.clone();
let inner_instructions = execution_details
.inner_instructions
.expect("cpi recording should be enabled");
let log_messages = execution_details
.log_messages
.expect("log recording should be enabled");
(result, inner_instructions, log_messages)
}
#[cfg(feature = "sbf_rust")]
fn execute_transactions(
bank: &Bank,
txs: Vec<Transaction>,
) -> Vec<Result<ConfirmedTransactionWithStatusMeta, TransactionError>> {
let batch = bank.prepare_batch_for_tests(txs.clone());
let mut timings = ExecuteTimings::default();
let mut mint_decimals = HashMap::new();
let tx_pre_token_balances = collect_token_balances(&bank, &batch, &mut mint_decimals);
let (
TransactionResults {
execution_results, ..
},
TransactionBalancesSet {
pre_balances,
post_balances,
..
},
) = bank.load_execute_and_commit_transactions(
&batch,
std::usize::MAX,
true,
true,
true,
true,
&mut timings,
None,
);
let tx_post_token_balances = collect_token_balances(&bank, &batch, &mut mint_decimals);
izip!(
txs.iter(),
execution_results.into_iter(),
pre_balances.into_iter(),
post_balances.into_iter(),
tx_pre_token_balances.into_iter(),
tx_post_token_balances.into_iter(),
)
.map(
|(
tx,
execution_result,
pre_balances,
post_balances,
pre_token_balances,
post_token_balances,
)| {
match execution_result {
TransactionExecutionResult::Executed { details, .. } => {
let TransactionExecutionDetails {
status,
log_messages,
inner_instructions,
durable_nonce_fee,
return_data,
executed_units,
..
} = details;
let lamports_per_signature = match durable_nonce_fee {
Some(DurableNonceFee::Valid(lamports_per_signature)) => {
Some(lamports_per_signature)
}
Some(DurableNonceFee::Invalid) => None,
None => bank.get_lamports_per_signature_for_blockhash(
&tx.message().recent_blockhash,
),
}
.expect("lamports_per_signature must be available");
let fee = bank.get_fee_for_message_with_lamports_per_signature(
&SanitizedMessage::try_from(tx.message().clone()).unwrap(),
lamports_per_signature,
);
let inner_instructions = inner_instructions.map(|inner_instructions| {
inner_instructions
.into_iter()
.enumerate()
.map(|(index, instructions)| InnerInstructions {
index: index as u8,
instructions: instructions
.into_iter()
.map(|ix| solana_transaction_status::InnerInstruction {
instruction: ix.instruction,
stack_height: Some(u32::from(ix.stack_height)),
})
.collect(),
})
.filter(|i| !i.instructions.is_empty())
.collect()
});
let tx_status_meta = TransactionStatusMeta {
status,
fee,
pre_balances,
post_balances,
pre_token_balances: Some(pre_token_balances),
post_token_balances: Some(post_token_balances),
inner_instructions,
log_messages,
rewards: None,
loaded_addresses: LoadedAddresses::default(),
return_data,
compute_units_consumed: Some(executed_units),
};
Ok(ConfirmedTransactionWithStatusMeta {
slot: bank.slot(),
tx_with_meta: TransactionWithStatusMeta::Complete(
VersionedTransactionWithStatusMeta {
transaction: VersionedTransaction::from(tx.clone()),
meta: tx_status_meta,
},
),
block_time: None,
})
}
TransactionExecutionResult::NotExecuted(err) => Err(err.clone()),
}
},
)
.collect()
}
fn load_program_and_advance_slot(
bank_client: &mut BankClient,
loader_id: &Pubkey,
payer_keypair: &Keypair,
name: &str,
) -> (Arc<Bank>, Pubkey) {
let pubkey = load_program(bank_client, loader_id, payer_keypair, name);
(
bank_client
.advance_slot(1, &Pubkey::default())
.expect("Failed to advance the slot"),
pubkey,
)
}
#[test]
#[cfg(any(feature = "sbf_c", feature = "sbf_rust"))]
fn test_program_sbf_sanity() {
solana_logger::setup();
let mut programs = Vec::new();
#[cfg(feature = "sbf_c")]
{
programs.extend_from_slice(&[
("alloc", true),
("alt_bn128", true),
("alt_bn128_compression", true),
("sbf_to_sbf", true),
("float", true),
("multiple_static", true),
("noop", true),
("noop++", true),
("panic", false),
("poseidon", true),
("relative_call", true),
("return_data", true),
("sanity", true),
("sanity++", true),
("secp256k1_recover", true),
("sha", true),
("stdlib", true),
("struct_pass", true),
("struct_ret", true),
]);
}
#[cfg(feature = "sbf_rust")]
{
programs.extend_from_slice(&[
("solana_sbf_rust_128bit", true),
("solana_sbf_rust_alloc", true),
("solana_sbf_rust_alt_bn128", true),
("solana_sbf_rust_alt_bn128_compression", true),
("solana_sbf_rust_curve25519", true),
("solana_sbf_rust_custom_heap", true),
("solana_sbf_rust_dep_crate", true),
("solana_sbf_rust_external_spend", false),
("solana_sbf_rust_iter", true),
("solana_sbf_rust_many_args", true),
("solana_sbf_rust_membuiltins", true),
("solana_sbf_rust_noop", true),
("solana_sbf_rust_panic", false),
("solana_sbf_rust_param_passing", true),
("solana_sbf_rust_poseidon", true),
("solana_sbf_rust_rand", true),
("solana_sbf_rust_sanity", true),
("solana_sbf_rust_secp256k1_recover", true),
("solana_sbf_rust_sha", true),
]);
}
for program in programs.iter() {
println!("Test program: {:?}", program.0);
let GenesisConfigInfo {
genesis_config,
mint_keypair,
..
} = create_genesis_config(50);
let bank = Bank::new_for_tests(&genesis_config);
let mut bank_client = BankClient::new(bank);
// Call user program
let (_, program_id) = load_program_and_advance_slot(
&mut bank_client,
&bpf_loader::id(),
&mint_keypair,
program.0,
);
let account_metas = vec![
AccountMeta::new(mint_keypair.pubkey(), true),
AccountMeta::new(Keypair::new().pubkey(), false),
];
let instruction = Instruction::new_with_bytes(program_id, &[1], account_metas);
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
if program.1 {
assert!(result.is_ok(), "{result:?}");
} else {
assert!(result.is_err(), "{result:?}");
}
}
}
#[test]
#[cfg(any(feature = "sbf_c", feature = "sbf_rust"))]
fn test_program_sbf_loader_deprecated() {
solana_logger::setup();
let mut programs = Vec::new();
#[cfg(feature = "sbf_c")]
{
programs.extend_from_slice(&[("deprecated_loader")]);
}
#[cfg(feature = "sbf_rust")]
{
programs.extend_from_slice(&[("solana_sbf_rust_deprecated_loader")]);
}
for program in programs.iter() {
println!("Test program: {:?}", program);
let GenesisConfigInfo {
mut genesis_config,
mint_keypair,
..
} = create_genesis_config(50);
genesis_config
.accounts
.remove(&solana_sdk::feature_set::disable_deploy_of_alloc_free_syscall::id())
.unwrap();
let bank = Bank::new_for_tests(&genesis_config);
let program_id = create_program(&bank, &bpf_loader_deprecated::id(), program);
let mut bank_client = BankClient::new(bank);
bank_client
.advance_slot(1, &Pubkey::default())
.expect("Failed to advance the slot");
let account_metas = vec![AccountMeta::new(mint_keypair.pubkey(), true)];
let instruction = Instruction::new_with_bytes(program_id, &[255], account_metas);
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
assert!(result.is_ok());
}
}
#[test]
#[cfg(feature = "sbf_rust")]
fn test_sol_alloc_free_no_longer_deployable() {
solana_logger::setup();
let program_keypair = Keypair::new();
let program_address = program_keypair.pubkey();
let GenesisConfigInfo {
genesis_config,
mint_keypair,
..
} = create_genesis_config(50);
let mut bank = Bank::new_for_tests(&genesis_config);
// Populate loader account with elf that depends on _sol_alloc_free syscall
let elf = load_program_from_file("solana_sbf_rust_deprecated_loader");
let mut program_account = AccountSharedData::new(1, elf.len(), &bpf_loader::id());
program_account
.data_as_mut_slice()
.get_mut(..)
.unwrap()
.copy_from_slice(&elf);
bank.store_account(&program_address, &program_account);
let finalize_tx = Transaction::new(
&[&mint_keypair, &program_keypair],
Message::new(
&[loader_instruction::finalize(
&program_keypair.pubkey(),
&bpf_loader::id(),
)],
Some(&mint_keypair.pubkey()),
),
bank.last_blockhash(),
);
let invoke_tx = Transaction::new(
&[&mint_keypair],
Message::new(
&[Instruction::new_with_bytes(
program_address,
&[255],
vec![AccountMeta::new(mint_keypair.pubkey(), true)],
)],
Some(&mint_keypair.pubkey()),
),
bank.last_blockhash(),
);
// Try and deploy a program that depends on _sol_alloc_free
assert_eq!(
bank.process_transaction(&finalize_tx).unwrap_err(),
TransactionError::InstructionError(0, InstructionError::InvalidAccountData)
);
// Enable _sol_alloc_free syscall
bank.deactivate_feature(&solana_sdk::feature_set::disable_deploy_of_alloc_free_syscall::id());
bank.clear_signatures();
bank.clear_program_cache();
// Try and finalize the program now that sol_alloc_free is re-enabled
assert!(bank.process_transaction(&finalize_tx).is_ok());
let new_slot = bank.slot() + 1;
let mut bank = Bank::new_from_parent(Arc::new(bank), &Pubkey::default(), new_slot);
// invoke the program
assert!(bank.process_transaction(&invoke_tx).is_ok());
// disable _sol_alloc_free
bank.activate_feature(&solana_sdk::feature_set::disable_deploy_of_alloc_free_syscall::id());
bank.clear_signatures();
// invoke should still succeed because cached
assert!(bank.process_transaction(&invoke_tx).is_ok());
bank.clear_signatures();
bank.clear_program_cache();
// invoke should still succeed on execute because the program is already deployed
assert!(bank.process_transaction(&invoke_tx).is_ok());
}
#[test]
#[cfg(feature = "sbf_rust")]
fn test_program_sbf_duplicate_accounts() {
solana_logger::setup();
let mut programs = Vec::new();
#[cfg(feature = "sbf_c")]
{
programs.extend_from_slice(&[("dup_accounts")]);
}
#[cfg(feature = "sbf_rust")]
{
programs.extend_from_slice(&[("solana_sbf_rust_dup_accounts")]);
}
for program in programs.iter() {
println!("Test program: {:?}", program);
let GenesisConfigInfo {
genesis_config,
mint_keypair,
..
} = create_genesis_config(50);
let bank = Bank::new_for_tests(&genesis_config);
let bank = Arc::new(bank);
let mut bank_client = BankClient::new_shared(bank.clone());
let (bank, program_id) = load_program_and_advance_slot(
&mut bank_client,
&bpf_loader::id(),
&mint_keypair,
program,
);
let payee_account = AccountSharedData::new(10, 1, &program_id);
let payee_pubkey = Pubkey::new_unique();
bank.store_account(&payee_pubkey, &payee_account);
let account = AccountSharedData::new(10, 1, &program_id);
let pubkey = Pubkey::new_unique();
let account_metas = vec![
AccountMeta::new(mint_keypair.pubkey(), true),
AccountMeta::new(payee_pubkey, false),
AccountMeta::new(pubkey, false),
AccountMeta::new(pubkey, false),
];
bank.store_account(&pubkey, &account);
let instruction = Instruction::new_with_bytes(program_id, &[1], account_metas.clone());
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
assert!(result.is_ok());
assert_eq!(data[0], 1);
bank.store_account(&pubkey, &account);
let instruction = Instruction::new_with_bytes(program_id, &[2], account_metas.clone());
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
assert!(result.is_ok());
assert_eq!(data[0], 2);
bank.store_account(&pubkey, &account);
let instruction = Instruction::new_with_bytes(program_id, &[3], account_metas.clone());
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
assert!(result.is_ok());
assert_eq!(data[0], 3);
bank.store_account(&pubkey, &account);
let instruction = Instruction::new_with_bytes(program_id, &[4], account_metas.clone());
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
let lamports = bank_client.get_balance(&pubkey).unwrap();
assert!(result.is_ok());
assert_eq!(lamports, 11);
bank.store_account(&pubkey, &account);
let instruction = Instruction::new_with_bytes(program_id, &[5], account_metas.clone());
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
let lamports = bank_client.get_balance(&pubkey).unwrap();
assert!(result.is_ok());
assert_eq!(lamports, 12);
bank.store_account(&pubkey, &account);
let instruction = Instruction::new_with_bytes(program_id, &[6], account_metas.clone());
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
let lamports = bank_client.get_balance(&pubkey).unwrap();
assert!(result.is_ok());
assert_eq!(lamports, 13);
let keypair = Keypair::new();
let pubkey = keypair.pubkey();
let account_metas = vec![
AccountMeta::new(mint_keypair.pubkey(), true),
AccountMeta::new(payee_pubkey, false),
AccountMeta::new(pubkey, false),
AccountMeta::new_readonly(pubkey, true),
AccountMeta::new_readonly(program_id, false),
];
bank.store_account(&pubkey, &account);
let instruction = Instruction::new_with_bytes(program_id, &[7], account_metas.clone());
let message = Message::new(&[instruction], Some(&mint_keypair.pubkey()));
let result = bank_client.send_and_confirm_message(&[&mint_keypair, &keypair], message);
assert!(result.is_ok());
}
}
#[test]
#[cfg(feature = "sbf_rust")]
fn test_program_sbf_error_handling() {
solana_logger::setup();
let mut programs = Vec::new();
#[cfg(feature = "sbf_c")]
{
programs.extend_from_slice(&[("error_handling")]);
}
#[cfg(feature = "sbf_rust")]
{
programs.extend_from_slice(&[("solana_sbf_rust_error_handling")]);
}
for program in programs.iter() {
println!("Test program: {:?}", program);
let GenesisConfigInfo {
genesis_config,
mint_keypair,
..
} = create_genesis_config(50);
let bank = Bank::new_for_tests(&genesis_config);
let mut bank_client = BankClient::new(bank);
let (_, program_id) = load_program_and_advance_slot(
&mut bank_client,
&bpf_loader::id(),
&mint_keypair,
program,
);
let account_metas = vec![AccountMeta::new(mint_keypair.pubkey(), true)];
let instruction = Instruction::new_with_bytes(program_id, &[1], account_metas.clone());
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
assert!(result.is_ok());
let instruction = Instruction::new_with_bytes(program_id, &[2], account_metas.clone());
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
assert_eq!(
result.unwrap_err().unwrap(),
TransactionError::InstructionError(0, InstructionError::InvalidAccountData)
);
let instruction = Instruction::new_with_bytes(program_id, &[3], account_metas.clone());
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
assert_eq!(
result.unwrap_err().unwrap(),
TransactionError::InstructionError(0, InstructionError::Custom(0))
);
let instruction = Instruction::new_with_bytes(program_id, &[4], account_metas.clone());
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
assert_eq!(
result.unwrap_err().unwrap(),
TransactionError::InstructionError(0, InstructionError::Custom(42))
);
let instruction = Instruction::new_with_bytes(program_id, &[5], account_metas.clone());
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
let result = result.unwrap_err().unwrap();
if TransactionError::InstructionError(0, InstructionError::InvalidInstructionData) != result
{
assert_eq!(
result,
TransactionError::InstructionError(0, InstructionError::InvalidError)
);
}
let instruction = Instruction::new_with_bytes(program_id, &[6], account_metas.clone());
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
let result = result.unwrap_err().unwrap();
if TransactionError::InstructionError(0, InstructionError::InvalidInstructionData) != result
{
assert_eq!(
result,
TransactionError::InstructionError(0, InstructionError::InvalidError)
);
}
let instruction = Instruction::new_with_bytes(program_id, &[7], account_metas.clone());
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
let result = result.unwrap_err().unwrap();
if TransactionError::InstructionError(0, InstructionError::InvalidInstructionData) != result
{
assert_eq!(
result,
TransactionError::InstructionError(0, InstructionError::AccountBorrowFailed)
);
}
let instruction = Instruction::new_with_bytes(program_id, &[8], account_metas.clone());
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
assert_eq!(
result.unwrap_err().unwrap(),
TransactionError::InstructionError(0, InstructionError::InvalidInstructionData)
);
let instruction = Instruction::new_with_bytes(program_id, &[9], account_metas.clone());
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
assert_eq!(
result.unwrap_err().unwrap(),
TransactionError::InstructionError(0, InstructionError::MaxSeedLengthExceeded)
);
}
}
#[test]
#[cfg(any(feature = "sbf_c", feature = "sbf_rust"))]
fn test_return_data_and_log_data_syscall() {
solana_logger::setup();
let mut programs = Vec::new();
#[cfg(feature = "sbf_c")]
{
programs.extend_from_slice(&[("log_data")]);
}
#[cfg(feature = "sbf_rust")]
{
programs.extend_from_slice(&[("solana_sbf_rust_log_data")]);
}
for program in programs.iter() {
let GenesisConfigInfo {
genesis_config,
mint_keypair,
..
} = create_genesis_config(50);
let bank = Bank::new_for_tests(&genesis_config);
let bank = Arc::new(bank);
let mut bank_client = BankClient::new_shared(bank.clone());
let (bank, program_id) = load_program_and_advance_slot(
&mut bank_client,
&bpf_loader::id(),
&mint_keypair,
program,
);
bank.freeze();
let account_metas = vec![AccountMeta::new(mint_keypair.pubkey(), true)];
let instruction =
Instruction::new_with_bytes(program_id, &[1, 2, 3, 0, 4, 5, 6], account_metas);
let blockhash = bank.last_blockhash();
let message = Message::new(&[instruction], Some(&mint_keypair.pubkey()));
let transaction = Transaction::new(&[&mint_keypair], message, blockhash);
let sanitized_tx = SanitizedTransaction::from_transaction_for_tests(transaction);
let result = bank.simulate_transaction(sanitized_tx);
assert!(result.result.is_ok());
assert_eq!(result.logs[1], "Program data: AQID BAUG");
assert_eq!(
result.logs[3],
format!("Program return: {} CAFE", program_id)
);
}
}
#[test]
#[cfg(feature = "sbf_rust")]
fn test_program_sbf_invoke_sanity() {
solana_logger::setup();
#[allow(dead_code)]
#[derive(Debug)]
enum Languages {
C,
Rust,
}
let mut programs = Vec::new();
#[cfg(feature = "sbf_c")]
{
programs.push((Languages::C, "invoke", "invoked", "noop"));
}
#[cfg(feature = "sbf_rust")]
{
programs.push((
Languages::Rust,
"solana_sbf_rust_invoke",
"solana_sbf_rust_invoked",
"solana_sbf_rust_noop",
));
}
for program in programs.iter() {
println!("Test program: {:?}", program);
let GenesisConfigInfo {
genesis_config,
mint_keypair,
..
} = create_genesis_config(50);
let bank = Bank::new_for_tests(&genesis_config);
let bank = Arc::new(bank);
let mut bank_client = BankClient::new_shared(bank.clone());
let invoke_program_id =
load_program(&bank_client, &bpf_loader::id(), &mint_keypair, program.1);
let invoked_program_id =
load_program(&bank_client, &bpf_loader::id(), &mint_keypair, program.2);
let (bank, noop_program_id) = load_program_and_advance_slot(
&mut bank_client,
&bpf_loader::id(),
&mint_keypair,
program.3,
);
let argument_keypair = Keypair::new();
let account = AccountSharedData::new(42, 100, &invoke_program_id);
bank.store_account(&argument_keypair.pubkey(), &account);
let invoked_argument_keypair = Keypair::new();
let account = AccountSharedData::new(10, 10, &invoked_program_id);
bank.store_account(&invoked_argument_keypair.pubkey(), &account);
let from_keypair = Keypair::new();
let account = AccountSharedData::new(84, 0, &system_program::id());
bank.store_account(&from_keypair.pubkey(), &account);
let (derived_key1, bump_seed1) =
Pubkey::find_program_address(&[b"You pass butter"], &invoke_program_id);
let (derived_key2, bump_seed2) =
Pubkey::find_program_address(&[b"Lil'", b"Bits"], &invoked_program_id);
let (derived_key3, bump_seed3) =
Pubkey::find_program_address(&[derived_key2.as_ref()], &invoked_program_id);
let mint_pubkey = mint_keypair.pubkey();
let account_metas = vec![
AccountMeta::new(mint_pubkey, true),
AccountMeta::new(argument_keypair.pubkey(), true),
AccountMeta::new_readonly(invoked_program_id, false),
AccountMeta::new(invoked_argument_keypair.pubkey(), true),
AccountMeta::new_readonly(invoked_program_id, false),
AccountMeta::new(argument_keypair.pubkey(), true),
AccountMeta::new(derived_key1, false),
AccountMeta::new(derived_key2, false),
AccountMeta::new_readonly(derived_key3, false),
AccountMeta::new_readonly(system_program::id(), false),
AccountMeta::new(from_keypair.pubkey(), true),
AccountMeta::new_readonly(solana_sdk::ed25519_program::id(), false),
AccountMeta::new_readonly(invoke_program_id, false),
];
// success cases
let instruction = Instruction::new_with_bytes(
invoke_program_id,
&[TEST_SUCCESS, bump_seed1, bump_seed2, bump_seed3],
account_metas.clone(),
);
let noop_instruction = Instruction::new_with_bytes(noop_program_id, &[], vec![]);
let message = Message::new(&[instruction, noop_instruction], Some(&mint_pubkey));
let tx = Transaction::new(
&[
&mint_keypair,
&argument_keypair,
&invoked_argument_keypair,
&from_keypair,
],
message.clone(),
bank.last_blockhash(),
);
let (result, inner_instructions, _log_messages) =
process_transaction_and_record_inner(&bank, tx);
assert_eq!(result, Ok(()));
let invoked_programs: Vec<Pubkey> = inner_instructions[0]
.iter()
.map(|ix| &message.account_keys[ix.instruction.program_id_index as usize])
.cloned()
.collect();
let expected_invoked_programs = match program.0 {
Languages::C => vec![
system_program::id(),
system_program::id(),
invoked_program_id.clone(),
invoked_program_id.clone(),
invoked_program_id.clone(),
invoked_program_id.clone(),
invoked_program_id.clone(),
invoked_program_id.clone(),
invoked_program_id.clone(),
invoked_program_id.clone(),
invoked_program_id.clone(),
invoked_program_id.clone(),
invoked_program_id.clone(),
invoked_program_id.clone(),
invoked_program_id.clone(),
invoked_program_id.clone(),
invoked_program_id.clone(),
invoked_program_id.clone(),
invoked_program_id.clone(),
],
Languages::Rust => vec![
system_program::id(),
system_program::id(),
invoked_program_id.clone(),
invoked_program_id.clone(),
invoked_program_id.clone(),
invoked_program_id.clone(),
invoked_program_id.clone(),
invoked_program_id.clone(),
invoked_program_id.clone(),
invoked_program_id.clone(),
invoked_program_id.clone(),
invoked_program_id.clone(),
invoked_program_id.clone(),
invoked_program_id.clone(),
invoked_program_id.clone(),
invoked_program_id.clone(),
invoked_program_id.clone(),
invoked_program_id.clone(),
invoked_program_id.clone(),
invoked_program_id.clone(),
invoked_program_id.clone(),
system_program::id(),
invoked_program_id.clone(),
invoked_program_id.clone(),
],
};
assert_eq!(invoked_programs.len(), expected_invoked_programs.len());
assert_eq!(invoked_programs, expected_invoked_programs);
let no_invoked_programs: Vec<Pubkey> = inner_instructions[1]
.iter()
.map(|ix| &message.account_keys[ix.instruction.program_id_index as usize])
.cloned()
.collect();
assert_eq!(no_invoked_programs.len(), 0);
// failure cases
let do_invoke_failure_test_local =
|test: u8,
expected_error: TransactionError,
expected_invoked_programs: &[Pubkey],
expected_log_messages: Option<Vec<String>>| {
println!("Running failure test #{:?}", test);
let instruction_data = &[test, bump_seed1, bump_seed2, bump_seed3];
let signers = vec![
&mint_keypair,
&argument_keypair,
&invoked_argument_keypair,
&from_keypair,
];
let instruction = Instruction::new_with_bytes(
invoke_program_id,
instruction_data,
account_metas.clone(),
);
let message = Message::new(&[instruction], Some(&mint_pubkey));
let tx = Transaction::new(&signers, message.clone(), bank.last_blockhash());
let (result, inner_instructions, log_messages) =
process_transaction_and_record_inner(&bank, tx);
let invoked_programs: Vec<Pubkey> = inner_instructions[0]
.iter()
.map(|ix| &message.account_keys[ix.instruction.program_id_index as usize])
.cloned()
.collect();
assert_eq!(result, Err(expected_error));
assert_eq!(invoked_programs, expected_invoked_programs);
if let Some(expected_log_messages) = expected_log_messages {
assert_eq!(log_messages.len(), expected_log_messages.len());
expected_log_messages
.into_iter()
.zip(log_messages)
.for_each(|(expected_log_message, log_message)| {
if expected_log_message != String::from("skip") {
assert_eq!(log_message, expected_log_message);
}
});
}
};
let program_lang = match program.0 {
Languages::Rust => "Rust",
Languages::C => "C",
};
do_invoke_failure_test_local(
TEST_PRIVILEGE_ESCALATION_SIGNER,
TransactionError::InstructionError(0, InstructionError::PrivilegeEscalation),
&[invoked_program_id.clone()],
None,
);
do_invoke_failure_test_local(
TEST_PRIVILEGE_ESCALATION_WRITABLE,
TransactionError::InstructionError(0, InstructionError::PrivilegeEscalation),
&[invoked_program_id.clone()],
None,
);
do_invoke_failure_test_local(
TEST_PPROGRAM_NOT_EXECUTABLE,
TransactionError::InstructionError(0, InstructionError::AccountNotExecutable),
&[],
None,
);
do_invoke_failure_test_local(
TEST_EMPTY_ACCOUNTS_SLICE,
TransactionError::InstructionError(0, InstructionError::MissingAccount),
&[],
None,
);
do_invoke_failure_test_local(
TEST_CAP_SEEDS,
TransactionError::InstructionError(0, InstructionError::MaxSeedLengthExceeded),
&[],
None,
);
do_invoke_failure_test_local(
TEST_CAP_SIGNERS,
TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete),
&[],
None,
);
do_invoke_failure_test_local(
TEST_MAX_INSTRUCTION_DATA_LEN_EXCEEDED,
TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete),
&[],
Some(vec![
format!("Program {invoke_program_id} invoke [1]"),
format!("Program log: invoke {program_lang} program"),
"Program log: Test max instruction data len exceeded".into(),
"skip".into(), // don't compare compute consumption logs
format!("Program {invoke_program_id} failed: Invoked an instruction with data that is too large (10241 > 10240)"),
]),
);
do_invoke_failure_test_local(
TEST_MAX_INSTRUCTION_ACCOUNTS_EXCEEDED,
TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete),
&[],
Some(vec![
format!("Program {invoke_program_id} invoke [1]"),
format!("Program log: invoke {program_lang} program"),
"Program log: Test max instruction accounts exceeded".into(),
"skip".into(), // don't compare compute consumption logs
format!("Program {invoke_program_id} failed: Invoked an instruction with too many accounts (256 > 255)"),
]),
);
do_invoke_failure_test_local(
TEST_MAX_ACCOUNT_INFOS_EXCEEDED,
TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete),
&[],
Some(vec![
format!("Program {invoke_program_id} invoke [1]"),
format!("Program log: invoke {program_lang} program"),
"Program log: Test max account infos exceeded".into(),
"skip".into(), // don't compare compute consumption logs
format!("Program {invoke_program_id} failed: Invoked an instruction with too many account info's (129 > 128)"),
]),
);
do_invoke_failure_test_local(
TEST_RETURN_ERROR,
TransactionError::InstructionError(0, InstructionError::Custom(42)),
&[invoked_program_id.clone()],
None,
);
do_invoke_failure_test_local(
TEST_PRIVILEGE_DEESCALATION_ESCALATION_SIGNER,
TransactionError::InstructionError(0, InstructionError::PrivilegeEscalation),
&[invoked_program_id.clone()],
None,
);
do_invoke_failure_test_local(
TEST_PRIVILEGE_DEESCALATION_ESCALATION_WRITABLE,
TransactionError::InstructionError(0, InstructionError::PrivilegeEscalation),
&[invoked_program_id.clone()],
None,
);
do_invoke_failure_test_local(
TEST_WRITABLE_DEESCALATION_WRITABLE,
TransactionError::InstructionError(0, InstructionError::ReadonlyDataModified),
&[invoked_program_id.clone()],
None,
);
do_invoke_failure_test_local(
TEST_NESTED_INVOKE_TOO_DEEP,
TransactionError::InstructionError(0, InstructionError::CallDepth),
&[
invoked_program_id.clone(),
invoked_program_id.clone(),
invoked_program_id.clone(),
invoked_program_id.clone(),
invoked_program_id.clone(),
],
None,
);
do_invoke_failure_test_local(
TEST_CALL_PRECOMPILE,
TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete),
&[],
None,
);
do_invoke_failure_test_local(
TEST_RETURN_DATA_TOO_LARGE,
TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete),
&[],
None,
);
do_invoke_failure_test_local(
TEST_DUPLICATE_PRIVILEGE_ESCALATION_SIGNER,
TransactionError::InstructionError(0, InstructionError::PrivilegeEscalation),
&[invoked_program_id.clone()],
None,
);
do_invoke_failure_test_local(
TEST_DUPLICATE_PRIVILEGE_ESCALATION_WRITABLE,
TransactionError::InstructionError(0, InstructionError::PrivilegeEscalation),
&[invoked_program_id.clone()],
None,
);
// Check resulting state
assert_eq!(43, bank.get_balance(&derived_key1));
let account = bank.get_account(&derived_key1).unwrap();
assert_eq!(&invoke_program_id, account.owner());
assert_eq!(
MAX_PERMITTED_DATA_INCREASE,
bank.get_account(&derived_key1).unwrap().data().len()
);
for i in 0..20 {
assert_eq!(i as u8, account.data()[i]);
}
// Attempt to realloc into unauthorized address space
let account = AccountSharedData::new(84, 0, &system_program::id());
bank.store_account(&from_keypair.pubkey(), &account);
bank.store_account(&derived_key1, &AccountSharedData::default());
let instruction = Instruction::new_with_bytes(
invoke_program_id,
&[
TEST_ALLOC_ACCESS_VIOLATION,
bump_seed1,
bump_seed2,
bump_seed3,
],
account_metas.clone(),
);
let message = Message::new(&[instruction], Some(&mint_pubkey));
let tx = Transaction::new(
&[
&mint_keypair,
&argument_keypair,
&invoked_argument_keypair,
&from_keypair,
],
message.clone(),
bank.last_blockhash(),
);
let (result, inner_instructions, _log_messages) =
process_transaction_and_record_inner(&bank, tx);
let invoked_programs: Vec<Pubkey> = inner_instructions[0]
.iter()
.map(|ix| &message.account_keys[ix.instruction.program_id_index as usize])
.cloned()
.collect();
assert_eq!(invoked_programs, vec![]);
assert_eq!(
result.unwrap_err(),
TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete)
);
}
}
#[test]
#[cfg(feature = "sbf_rust")]
fn test_program_sbf_program_id_spoofing() {
let GenesisConfigInfo {
genesis_config,
mint_keypair,
..
} = create_genesis_config(50);
let bank = Bank::new_for_tests(&genesis_config);
let bank = Arc::new(bank);
let mut bank_client = BankClient::new_shared(bank.clone());
let malicious_swap_pubkey = load_program(
&bank_client,
&bpf_loader::id(),
&mint_keypair,
"solana_sbf_rust_spoof1",
);
let (bank, malicious_system_pubkey) = load_program_and_advance_slot(
&mut bank_client,
&bpf_loader::id(),
&mint_keypair,
"solana_sbf_rust_spoof1_system",
);
let from_pubkey = Pubkey::new_unique();
let account = AccountSharedData::new(10, 0, &system_program::id());
bank.store_account(&from_pubkey, &account);
let to_pubkey = Pubkey::new_unique();
let account = AccountSharedData::new(0, 0, &system_program::id());
bank.store_account(&to_pubkey, &account);
let account_metas = vec![
AccountMeta::new_readonly(system_program::id(), false),
AccountMeta::new_readonly(malicious_system_pubkey, false),
AccountMeta::new(from_pubkey, false),
AccountMeta::new(to_pubkey, false),
];
let instruction =
Instruction::new_with_bytes(malicious_swap_pubkey, &[], account_metas.clone());
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
assert_eq!(
result.unwrap_err().unwrap(),
TransactionError::InstructionError(0, InstructionError::MissingRequiredSignature)
);
assert_eq!(10, bank.get_balance(&from_pubkey));
assert_eq!(0, bank.get_balance(&to_pubkey));
}
#[test]
#[cfg(feature = "sbf_rust")]
fn test_program_sbf_caller_has_access_to_cpi_program() {
let GenesisConfigInfo {
genesis_config,
mint_keypair,
..
} = create_genesis_config(50);
let bank = Bank::new_for_tests(&genesis_config);
let bank = Arc::new(bank);
let mut bank_client = BankClient::new_shared(bank.clone());
let caller_pubkey = load_program(
&bank_client,
&bpf_loader::id(),
&mint_keypair,
"solana_sbf_rust_caller_access",
);
let (_, caller2_pubkey) = load_program_and_advance_slot(
&mut bank_client,
&bpf_loader::id(),
&mint_keypair,
"solana_sbf_rust_caller_access",
);
let account_metas = vec![
AccountMeta::new_readonly(caller_pubkey, false),
AccountMeta::new_readonly(caller2_pubkey, false),
];
let instruction = Instruction::new_with_bytes(caller_pubkey, &[1], account_metas.clone());
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
assert_eq!(
result.unwrap_err().unwrap(),
TransactionError::InstructionError(0, InstructionError::MissingAccount)
);
}
#[test]
#[cfg(feature = "sbf_rust")]
fn test_program_sbf_ro_modify() {
solana_logger::setup();
let GenesisConfigInfo {
genesis_config,
mint_keypair,
..
} = create_genesis_config(50);
let bank = Bank::new_for_tests(&genesis_config);
let bank = Arc::new(bank);
let mut bank_client = BankClient::new_shared(bank.clone());
let (bank, program_pubkey) = load_program_and_advance_slot(
&mut bank_client,
&bpf_loader::id(),
&mint_keypair,
"solana_sbf_rust_ro_modify",
);
let test_keypair = Keypair::new();
let account = AccountSharedData::new(10, 0, &system_program::id());
bank.store_account(&test_keypair.pubkey(), &account);
let account_metas = vec![
AccountMeta::new_readonly(system_program::id(), false),
AccountMeta::new(test_keypair.pubkey(), true),
];
let instruction = Instruction::new_with_bytes(program_pubkey, &[1], account_metas.clone());
let message = Message::new(&[instruction], Some(&mint_keypair.pubkey()));
let result = bank_client.send_and_confirm_message(&[&mint_keypair, &test_keypair], message);
assert_eq!(
result.unwrap_err().unwrap(),
TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete)
);
let instruction = Instruction::new_with_bytes(program_pubkey, &[3], account_metas.clone());
let message = Message::new(&[instruction], Some(&mint_keypair.pubkey()));
let result = bank_client.send_and_confirm_message(&[&mint_keypair, &test_keypair], message);
assert_eq!(
result.unwrap_err().unwrap(),
TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete)
);
let instruction = Instruction::new_with_bytes(program_pubkey, &[4], account_metas.clone());
let message = Message::new(&[instruction], Some(&mint_keypair.pubkey()));
let result = bank_client.send_and_confirm_message(&[&mint_keypair, &test_keypair], message);
assert_eq!(
result.unwrap_err().unwrap(),
TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete)
);
}
#[test]
#[cfg(feature = "sbf_rust")]
fn test_program_sbf_call_depth() {
solana_logger::setup();
let GenesisConfigInfo {
genesis_config,
mint_keypair,
..
} = create_genesis_config(50);
let bank = Bank::new_for_tests(&genesis_config);
let mut bank_client = BankClient::new(bank);
let (_, program_id) = load_program_and_advance_slot(
&mut bank_client,
&bpf_loader::id(),
&mint_keypair,
"solana_sbf_rust_call_depth",
);
let instruction = Instruction::new_with_bincode(
program_id,
&(ComputeBudget::default().max_call_depth - 1),
vec![],
);
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
assert!(result.is_ok());
let instruction =
Instruction::new_with_bincode(program_id, &ComputeBudget::default().max_call_depth, vec![]);
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
assert!(result.is_err());
}
#[test]
#[cfg(feature = "sbf_rust")]
fn test_program_sbf_compute_budget() {
solana_logger::setup();
let GenesisConfigInfo {
genesis_config,
mint_keypair,
..
} = create_genesis_config(50);
let bank = Bank::new_for_tests(&genesis_config);
let mut bank_client = BankClient::new(bank);
let (_, program_id) = load_program_and_advance_slot(
&mut bank_client,
&bpf_loader::id(),
&mint_keypair,
"solana_sbf_rust_noop",
);
let message = Message::new(
&[
ComputeBudgetInstruction::set_compute_unit_limit(150),
Instruction::new_with_bincode(program_id, &0, vec![]),
],
Some(&mint_keypair.pubkey()),
);
let result = bank_client.send_and_confirm_message(&[&mint_keypair], message);
assert_eq!(
result.unwrap_err().unwrap(),
TransactionError::InstructionError(1, InstructionError::ProgramFailedToComplete),
);
}
#[test]
fn assert_instruction_count() {
solana_logger::setup();
let mut programs = Vec::new();
#[cfg(feature = "sbf_c")]
{
programs.extend_from_slice(&[
("alloc", 11502),
("sbf_to_sbf", 313),
("multiple_static", 208),
("noop", 5),
("noop++", 5),
("relative_call", 210),
("return_data", 980),
("sanity", 2377),
("sanity++", 2277),
("secp256k1_recover", 25383),
("sha", 1355),
("struct_pass", 108),
("struct_ret", 122),
]);
}
#[cfg(feature = "sbf_rust")]
{
programs.extend_from_slice(&[
("solana_sbf_rust_128bit", 1218),
("solana_sbf_rust_alloc", 5077),
("solana_sbf_rust_custom_heap", 398),
("solana_sbf_rust_dep_crate", 2),
("solana_sbf_rust_iter", 1514),
("solana_sbf_rust_many_args", 1289),
("solana_sbf_rust_mem", 2067),
("solana_sbf_rust_membuiltins", 1539),
("solana_sbf_rust_noop", 275),
("solana_sbf_rust_param_passing", 146),
("solana_sbf_rust_rand", 378),
("solana_sbf_rust_sanity", 51953),
("solana_sbf_rust_secp256k1_recover", 91185),
("solana_sbf_rust_sha", 24059),
]);
}
println!("\n {:36} expected actual diff", "SBF program");
for (program_name, expected_consumption) in programs.iter() {
let loader_id = bpf_loader::id();
let program_key = Pubkey::new_unique();
let mut transaction_accounts = vec![
(program_key, AccountSharedData::new(0, 0, &loader_id)),
(
Pubkey::new_unique(),
AccountSharedData::new(0, 0, &program_key),
),
];
let instruction_accounts = vec![AccountMeta {
pubkey: transaction_accounts[1].0,
is_signer: false,
is_writable: false,
}];
transaction_accounts[0]
.1
.set_data_from_slice(&load_program_from_file(program_name));
transaction_accounts[0].1.set_executable(true);
let prev_compute_meter = RefCell::new(0);
print!(" {:36} {:8}", program_name, *expected_consumption);
mock_process_instruction(
&loader_id,
vec![0],
&[],
transaction_accounts,
instruction_accounts,
Ok(()),
solana_bpf_loader_program::Entrypoint::vm,
|invoke_context| {
*prev_compute_meter.borrow_mut() = invoke_context.get_remaining();
solana_bpf_loader_program::test_utils::load_all_invoked_programs(invoke_context);
},
|invoke_context| {
let consumption = prev_compute_meter
.borrow()
.saturating_sub(invoke_context.get_remaining());
let diff: i64 = consumption as i64 - *expected_consumption as i64;
println!(
"{:6} {:+5} ({:+3.0}%)",
consumption,
diff,
100.0_f64 * consumption as f64 / *expected_consumption as f64 - 100.0_f64,
);
assert!(consumption <= *expected_consumption);
},
);
}
}
#[test]
#[cfg(feature = "sbf_rust")]
fn test_program_sbf_instruction_introspection() {
solana_logger::setup();
let GenesisConfigInfo {
genesis_config,
mint_keypair,
..
} = create_genesis_config(50_000);
let bank = Bank::new_for_tests(&genesis_config);
let bank = Arc::new(bank);
let mut bank_client = BankClient::new_shared(bank.clone());
let (bank, program_id) = load_program_and_advance_slot(
&mut bank_client,
&bpf_loader::id(),
&mint_keypair,
"solana_sbf_rust_instruction_introspection",
);
// Passing transaction
let account_metas = vec![
AccountMeta::new_readonly(program_id, false),
AccountMeta::new_readonly(sysvar::instructions::id(), false),
];
let instruction0 = Instruction::new_with_bytes(program_id, &[0u8, 0u8], account_metas.clone());
let instruction1 = Instruction::new_with_bytes(program_id, &[0u8, 1u8], account_metas.clone());
let instruction2 = Instruction::new_with_bytes(program_id, &[0u8, 2u8], account_metas);
let message = Message::new(
&[instruction0, instruction1, instruction2],
Some(&mint_keypair.pubkey()),
);
let result = bank_client.send_and_confirm_message(&[&mint_keypair], message);
assert!(result.is_ok());
// writable special instructions11111 key, should not be allowed
let account_metas = vec![AccountMeta::new(sysvar::instructions::id(), false)];
let instruction = Instruction::new_with_bytes(program_id, &[0], account_metas);
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
assert_eq!(
result.unwrap_err().unwrap(),
// sysvar write locks are demoted to read only. So this will no longer
// cause InvalidAccountIndex error.
TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete),
);
// No accounts, should error
let instruction = Instruction::new_with_bytes(program_id, &[0], vec![]);
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
assert!(result.is_err());
assert_eq!(
result.unwrap_err().unwrap(),
TransactionError::InstructionError(0, InstructionError::NotEnoughAccountKeys)
);
assert!(bank.get_account(&sysvar::instructions::id()).is_none());
}
#[test]
#[cfg(feature = "sbf_rust")]
fn test_program_sbf_test_use_latest_executor() {
solana_logger::setup();
let GenesisConfigInfo {
genesis_config,
mint_keypair,
..
} = create_genesis_config(50);
let bank = Bank::new_for_tests(&genesis_config);
let mut bank_client = BankClient::new(bank);
let panic_id = load_program(
&bank_client,
&bpf_loader::id(),
&mint_keypair,
"solana_sbf_rust_panic",
);
// Write the panic program into the program account
let (program_keypair, instruction) = load_and_finalize_program(
&bank_client,
&bpf_loader::id(),
None,
&mint_keypair,
"solana_sbf_rust_panic",
);
// Finalize the panic program, but fail the tx
let message = Message::new(
&[
instruction,
Instruction::new_with_bytes(panic_id, &[0], vec![]),
],
Some(&mint_keypair.pubkey()),
);
bank_client
.advance_slot(1, &Pubkey::default())
.expect("Failed to advance the slot");
assert!(bank_client
.send_and_confirm_message(&[&mint_keypair, &program_keypair], message)
.is_err());
// Write the noop program into the same program account
let (program_keypair, instruction) = load_and_finalize_program(
&bank_client,
&bpf_loader::id(),
Some(program_keypair),
&mint_keypair,
"solana_sbf_rust_noop",
);
bank_client
.advance_slot(1, &Pubkey::default())
.expect("Failed to advance the slot");
let message = Message::new(&[instruction], Some(&mint_keypair.pubkey()));
bank_client
.send_and_confirm_message(&[&mint_keypair, &program_keypair], message)
.unwrap();
// Call the noop program, should get noop not panic
let message = Message::new(
&[Instruction::new_with_bytes(
program_keypair.pubkey(),
&[0],
vec![],
)],
Some(&mint_keypair.pubkey()),
);
bank_client
.advance_slot(1, &Pubkey::default())
.expect("Failed to advance the slot");
assert!(bank_client
.send_and_confirm_message(&[&mint_keypair], message)
.is_ok());
}
#[test]
#[cfg(feature = "sbf_rust")]
fn test_program_sbf_upgrade() {
solana_logger::setup();
let GenesisConfigInfo {
genesis_config,
mint_keypair,
..
} = create_genesis_config(50);
let bank = Bank::new_for_tests(&genesis_config);
let mut bank_client = BankClient::new(bank);
// Deploy upgrade program
let buffer_keypair = Keypair::new();
let program_keypair = Keypair::new();
let program_id = program_keypair.pubkey();
let authority_keypair = Keypair::new();
load_upgradeable_program(
&bank_client,
&mint_keypair,
&buffer_keypair,
&program_keypair,
&authority_keypair,
"solana_sbf_rust_upgradeable",
);
bank_client
.advance_slot(1, &Pubkey::default())
.expect("Failed to advance the slot");
let mut instruction =
Instruction::new_with_bytes(program_id, &[0], vec![AccountMeta::new(clock::id(), false)]);
// Call upgrade program
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction.clone());
assert_eq!(
result.unwrap_err().unwrap(),
TransactionError::InstructionError(0, InstructionError::Custom(42))
);
// Upgrade program
let buffer_keypair = Keypair::new();
upgrade_program(
&bank_client,
&mint_keypair,
&buffer_keypair,
&program_id,
&authority_keypair,
"solana_sbf_rust_upgraded",
);
bank_client.set_sysvar_for_tests(&clock::Clock {
slot: 2,
..clock::Clock::default()
});
bank_client
.advance_slot(1, &Pubkey::default())
.expect("Failed to advance the slot");
// Call upgraded program
instruction.data[0] += 1;
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction.clone());
assert_eq!(
result.unwrap_err().unwrap(),
TransactionError::InstructionError(0, InstructionError::Custom(43))
);
// Set a new authority
let new_authority_keypair = Keypair::new();
set_upgrade_authority(
&bank_client,
&mint_keypair,
&program_id,
&authority_keypair,
Some(&new_authority_keypair.pubkey()),
);
// Upgrade back to the original program
let buffer_keypair = Keypair::new();
upgrade_program(
&bank_client,
&mint_keypair,
&buffer_keypair,
&program_id,
&new_authority_keypair,
"solana_sbf_rust_upgradeable",
);
bank_client
.advance_slot(1, &Pubkey::default())
.expect("Failed to advance the slot");
// Call original program
instruction.data[0] += 1;
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
assert_eq!(
result.unwrap_err().unwrap(),
TransactionError::InstructionError(0, InstructionError::Custom(42))
);
}
fn get_stable_genesis_config() -> GenesisConfigInfo {
let validator_pubkey =
Pubkey::from_str("GLh546CXmtZdvpEzL8sxzqhhUf7KPvmGaRpFHB5W1sjV").unwrap();
let mint_keypair = Keypair::from_base58_string(
"4YTH9JSRgZocmK9ezMZeJCCV2LVeR2NatTBA8AFXkg2x83fqrt8Vwyk91961E7ns4vee9yUBzuDfztb8i9iwTLFd",
);
let voting_keypair = Keypair::from_base58_string(
"4EPWEn72zdNY1JSKkzyZ2vTZcKdPW3jM5WjAgUadnoz83FR5cDFApbo7s5mwBcYXn8afVe2syReJaqBi4fkhG3mH",
);
let stake_pubkey = Pubkey::from_str("HGq9JF77xFXRgWRJy8VQuhdbdugrT856RvQDzr1KJo6E").unwrap();
let mut genesis_config = create_genesis_config_with_leader_ex(
123,
&mint_keypair.pubkey(),
&validator_pubkey,
&voting_keypair.pubkey(),
&stake_pubkey,
bootstrap_validator_stake_lamports(),
42,
FeeRateGovernor::new(0, 0), // most tests can't handle transaction fees
Rent::free(), // most tests don't expect rent
ClusterType::Development,
vec![],
);
genesis_config.creation_time = Duration::ZERO.as_secs() as UnixTimestamp;
GenesisConfigInfo {
genesis_config,
mint_keypair,
voting_keypair,
validator_pubkey,
}
}
#[test]
#[ignore]
#[cfg(feature = "sbf_rust")]
fn test_program_sbf_invoke_stable_genesis_and_bank() {
// The purpose of this test is to exercise various code branches of runtime/VM and
// assert that the resulting bank hash matches with the expected value.
// The assert check is commented out by default. Please refer to the last few lines
// of the test to enable the assertion.
solana_logger::setup();
let GenesisConfigInfo {
genesis_config,
mint_keypair,
..
} = get_stable_genesis_config();
let bank = Bank::new_for_tests(&genesis_config);
let bank = Arc::new(bank);
let bank_client = BankClient::new_shared(bank.clone());
// Deploy upgradeable program
let buffer_keypair = Keypair::from_base58_string(
"4q4UvWxh2oMifTGbChDeWCbdN8eJEUQ1E6cuNnmymJ6AN5CMUT2VW5A1RKnG9dy7ypLczB9inMUAafh5TkpXrtxg",
);
let program_keypair = Keypair::from_base58_string(
"3LQpBxgpaFNJPit5a8t51pJKMkUmNUn5PhSTcuuhuuBxe43cTeqVPhMtKkFNr5VpFzCExf4ihibvuZgGxmjy6t8n",
);
let program_id = program_keypair.pubkey();
let authority_keypair = Keypair::from_base58_string(
"285XFW2NTWd6CMvtHzvYYS1kWzmzcGBnyEXbH1v8hq6YJqJsLMTYMPkbEQqeE7m7UqhoMeK5V3HMJLf9DdxwU2Gy",
);
let instruction =
Instruction::new_with_bytes(program_id, &[0], vec![AccountMeta::new(clock::id(), false)]);
// Call program before its deployed
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction.clone());
assert_eq!(
result.unwrap_err().unwrap(),
TransactionError::ProgramAccountNotFound
);
load_upgradeable_program(
&bank_client,
&mint_keypair,
&buffer_keypair,
&program_keypair,
&authority_keypair,
"solana_sbf_rust_noop",
);
// Deploy indirect invocation program
let indirect_program_keypair = Keypair::from_base58_string(
"2BgE4gD5wUCwiAVPYbmWd2xzXSsD9W2fWgNjwmVkm8WL7i51vK9XAXNnX1VB6oKQZmjaUPRd5RzE6RggB9DeKbZC",
);
load_upgradeable_program(
&bank_client,
&mint_keypair,
&buffer_keypair,
&indirect_program_keypair,
&authority_keypair,
"solana_sbf_rust_invoke_and_return",
);
let invoke_instruction =
Instruction::new_with_bytes(program_id, &[0], vec![AccountMeta::new(clock::id(), false)]);
let indirect_invoke_instruction = Instruction::new_with_bytes(
indirect_program_keypair.pubkey(),
&[0],
vec![
AccountMeta::new_readonly(program_id, false),
AccountMeta::new_readonly(clock::id(), false),
],
);
// Prepare redeployment
let buffer_keypair = Keypair::from_base58_string(
"5T5L31FiUphXh4N6mxiWhEKPrdLhvMJSbaHo1Ne7zZYkw6YT1fVkqsWdA6pHMtqATiMTc4sfx5yTV9M9AnWDoBkW",
);
load_upgradeable_buffer(
&bank_client,
&mint_keypair,
&buffer_keypair,
&authority_keypair,
"solana_sbf_rust_panic",
);
let redeployment_instruction = bpf_loader_upgradeable::upgrade(
&program_id,
&buffer_keypair.pubkey(),
&authority_keypair.pubkey(),
&mint_keypair.pubkey(),
);
// Redeployment causes programs to be unavailable to both top-level-instructions and CPI instructions
for invoke_instruction in [invoke_instruction, indirect_invoke_instruction] {
// Call upgradeable program
let result =
bank_client.send_and_confirm_instruction(&mint_keypair, invoke_instruction.clone());
assert!(result.is_ok());
// Upgrade the program and invoke in same tx
let message = Message::new(
&[redeployment_instruction.clone(), invoke_instruction],
Some(&mint_keypair.pubkey()),
);
let tx = Transaction::new(
&[&mint_keypair, &authority_keypair],
message.clone(),
bank.last_blockhash(),
);
let (result, _, _) = process_transaction_and_record_inner(&bank, tx);
assert_eq!(
result.unwrap_err(),
TransactionError::InstructionError(1, InstructionError::InvalidAccountData),
);
}
// Prepare undeployment
let (programdata_address, _) = Pubkey::find_program_address(
&[program_keypair.pubkey().as_ref()],
&bpf_loader_upgradeable::id(),
);
let undeployment_instruction = bpf_loader_upgradeable::close_any(
&programdata_address,
&mint_keypair.pubkey(),
Some(&authority_keypair.pubkey()),
Some(&program_id),
);
let invoke_instruction =
Instruction::new_with_bytes(program_id, &[1], vec![AccountMeta::new(clock::id(), false)]);
let indirect_invoke_instruction = Instruction::new_with_bytes(
indirect_program_keypair.pubkey(),
&[1],
vec![
AccountMeta::new_readonly(program_id, false),
AccountMeta::new_readonly(clock::id(), false),
],
);
// Undeployment is visible to both top-level-instructions and CPI instructions
for invoke_instruction in [invoke_instruction, indirect_invoke_instruction] {
// Call upgradeable program
let result =
bank_client.send_and_confirm_instruction(&mint_keypair, invoke_instruction.clone());
assert!(result.is_ok());
// Undeploy the program and invoke in same tx
let message = Message::new(
&[undeployment_instruction.clone(), invoke_instruction],
Some(&mint_keypair.pubkey()),
);
let tx = Transaction::new(
&[&mint_keypair, &authority_keypair],
message.clone(),
bank.last_blockhash(),
);
let (result, _, _) = process_transaction_and_record_inner(&bank, tx);
assert_eq!(
result.unwrap_err(),
TransactionError::InstructionError(1, InstructionError::InvalidAccountData),
);
}
bank.freeze();
let expected_hash = Hash::from_str("2A2vqbUKExRbnaAzSnDFXdsBZRZSpCjGZCAA3mFZG2sV")
.expect("Failed to generate hash");
println!("Stable test produced bank hash: {}", bank.hash());
println!("Expected hash: {}", expected_hash);
// Enable the following code to match the bank hash with the expected bank hash.
// Follow these steps.
// 1. Run this test on the baseline/master commit, and get the expected bank hash.
// 2. Update the `expected_hash` to match the expected bank hash.
// 3. Run the test in the PR branch that's being tested.
// If the hash doesn't match, the PR likely has runtime changes that can lead to
// consensus failure.
// assert_eq!(bank.hash(), expected_hash);
}
#[test]
#[cfg(feature = "sbf_rust")]
fn test_program_sbf_invoke_in_same_tx_as_deployment() {
solana_logger::setup();
let GenesisConfigInfo {
genesis_config,
mint_keypair,
..
} = create_genesis_config(50);
let bank = Bank::new_for_tests(&genesis_config);
let bank = Arc::new(bank);
let mut bank_client = BankClient::new_shared(bank.clone());
// Deploy upgradeable program
let buffer_keypair = Keypair::new();
let program_keypair = Keypair::new();
let program_id = program_keypair.pubkey();
let authority_keypair = Keypair::new();
// Deploy indirect invocation program
let indirect_program_keypair = Keypair::new();
load_upgradeable_program(
&bank_client,
&mint_keypair,
&buffer_keypair,
&indirect_program_keypair,
&authority_keypair,
"solana_sbf_rust_invoke_and_return",
);
let invoke_instruction =
Instruction::new_with_bytes(program_id, &[0], vec![AccountMeta::new(clock::id(), false)]);
let indirect_invoke_instruction = Instruction::new_with_bytes(
indirect_program_keypair.pubkey(),
&[0],
vec![
AccountMeta::new_readonly(program_id, false),
AccountMeta::new_readonly(clock::id(), false),
],
);
// Prepare deployment
let program = load_upgradeable_buffer(
&bank_client,
&mint_keypair,
&buffer_keypair,
&authority_keypair,
"solana_sbf_rust_noop",
);
let deployment_instructions = bpf_loader_upgradeable::deploy_with_max_program_len(
&mint_keypair.pubkey(),
&program_keypair.pubkey(),
&buffer_keypair.pubkey(),
&authority_keypair.pubkey(),
1.max(
bank_client
.get_minimum_balance_for_rent_exemption(
bpf_loader_upgradeable::UpgradeableLoaderState::size_of_program(),
)
.unwrap(),
),
program.len() * 2,
)
.unwrap();
let bank = bank_client
.advance_slot(1, &Pubkey::default())
.expect("Failed to advance slot");
// Deployment is invisible to both top-level-instructions and CPI instructions
for (index, invoke_instruction) in [invoke_instruction, indirect_invoke_instruction]
.into_iter()
.enumerate()
{
let mut instructions = deployment_instructions.clone();
instructions.push(invoke_instruction);
let tx = Transaction::new(
&[&mint_keypair, &program_keypair, &authority_keypair],
Message::new(&instructions, Some(&mint_keypair.pubkey())),
bank.last_blockhash(),
);
if index == 0 {
let results = execute_transactions(&bank, vec![tx]);
assert_eq!(
results[0].as_ref().unwrap_err(),
&TransactionError::ProgramAccountNotFound,
);
} else {
let (result, _, _) = process_transaction_and_record_inner(&bank, tx);
assert_eq!(
result.unwrap_err(),
TransactionError::InstructionError(2, InstructionError::InvalidAccountData),
);
}
}
}
#[test]
#[cfg(feature = "sbf_rust")]
fn test_program_sbf_invoke_in_same_tx_as_redeployment() {
solana_logger::setup();
let GenesisConfigInfo {
genesis_config,
mint_keypair,
..
} = create_genesis_config(50);
let bank = Bank::new_for_tests(&genesis_config);
let bank = Arc::new(bank);
let mut bank_client = BankClient::new_shared(bank.clone());
// Deploy upgradeable program
let buffer_keypair = Keypair::new();
let program_keypair = Keypair::new();
let program_id = program_keypair.pubkey();
let authority_keypair = Keypair::new();
load_upgradeable_program(
&bank_client,
&mint_keypair,
&buffer_keypair,
&program_keypair,
&authority_keypair,
"solana_sbf_rust_noop",
);
// Deploy indirect invocation program
let indirect_program_keypair = Keypair::new();
load_upgradeable_program(
&bank_client,
&mint_keypair,
&buffer_keypair,
&indirect_program_keypair,
&authority_keypair,
"solana_sbf_rust_invoke_and_return",
);
// Deploy panic program
let panic_program_keypair = Keypair::new();
load_upgradeable_program(
&bank_client,
&mint_keypair,
&buffer_keypair,
&panic_program_keypair,
&authority_keypair,
"solana_sbf_rust_panic",
);
let invoke_instruction =
Instruction::new_with_bytes(program_id, &[0], vec![AccountMeta::new(clock::id(), false)]);
let indirect_invoke_instruction = Instruction::new_with_bytes(
indirect_program_keypair.pubkey(),
&[0],
vec![
AccountMeta::new_readonly(program_id, false),
AccountMeta::new_readonly(clock::id(), false),
],
);
// load_upgradeable_program sets clock sysvar to 1, which causes the program to be effective
// after 2 slots. So we need to advance the bank client by 2 slots here.
let bank = bank_client
.advance_slot(2, &Pubkey::default())
.expect("Failed to advance slot");
// Prepare redeployment
let buffer_keypair = Keypair::new();
load_upgradeable_buffer(
&bank_client,
&mint_keypair,
&buffer_keypair,
&authority_keypair,
"solana_sbf_rust_panic",
);
let redeployment_instruction = bpf_loader_upgradeable::upgrade(
&program_id,
&buffer_keypair.pubkey(),
&authority_keypair.pubkey(),
&mint_keypair.pubkey(),
);
// Redeployment causes programs to be unavailable to both top-level-instructions and CPI instructions
for invoke_instruction in [invoke_instruction, indirect_invoke_instruction] {
// Call upgradeable program
let result =
bank_client.send_and_confirm_instruction(&mint_keypair, invoke_instruction.clone());
assert!(result.is_ok());
// Upgrade the program and invoke in same tx
let message = Message::new(
&[redeployment_instruction.clone(), invoke_instruction],
Some(&mint_keypair.pubkey()),
);
let tx = Transaction::new(
&[&mint_keypair, &authority_keypair],
message.clone(),
bank.last_blockhash(),
);
let (result, _, _) = process_transaction_and_record_inner(&bank, tx);
assert_eq!(
result.unwrap_err(),
TransactionError::InstructionError(1, InstructionError::InvalidAccountData),
);
}
}
#[test]
#[cfg(feature = "sbf_rust")]
fn test_program_sbf_invoke_in_same_tx_as_undeployment() {
solana_logger::setup();
let GenesisConfigInfo {
genesis_config,
mint_keypair,
..
} = create_genesis_config(50);
let bank = Bank::new_for_tests(&genesis_config);
let bank = Arc::new(bank);
let mut bank_client = BankClient::new_shared(bank.clone());
// Deploy upgradeable program
let buffer_keypair = Keypair::new();
let program_keypair = Keypair::new();
let program_id = program_keypair.pubkey();
let authority_keypair = Keypair::new();
load_upgradeable_program(
&bank_client,
&mint_keypair,
&buffer_keypair,
&program_keypair,
&authority_keypair,
"solana_sbf_rust_noop",
);
// Deploy indirect invocation program
let indirect_program_keypair = Keypair::new();
load_upgradeable_program(
&bank_client,
&mint_keypair,
&buffer_keypair,
&indirect_program_keypair,
&authority_keypair,
"solana_sbf_rust_invoke_and_return",
);
let invoke_instruction =
Instruction::new_with_bytes(program_id, &[0], vec![AccountMeta::new(clock::id(), false)]);
let indirect_invoke_instruction = Instruction::new_with_bytes(
indirect_program_keypair.pubkey(),
&[0],
vec![
AccountMeta::new_readonly(program_id, false),
AccountMeta::new_readonly(clock::id(), false),
],
);
// load_upgradeable_program sets clock sysvar to 1, which causes the program to be effective
// after 2 slots. So we need to advance the bank client by 2 slots here.
let bank = bank_client
.advance_slot(2, &Pubkey::default())
.expect("Failed to advance slot");
// Prepare undeployment
let (programdata_address, _) = Pubkey::find_program_address(
&[program_keypair.pubkey().as_ref()],
&bpf_loader_upgradeable::id(),
);
let undeployment_instruction = bpf_loader_upgradeable::close_any(
&programdata_address,
&mint_keypair.pubkey(),
Some(&authority_keypair.pubkey()),
Some(&program_id),
);
// Undeployment is visible to both top-level-instructions and CPI instructions
for invoke_instruction in [invoke_instruction, indirect_invoke_instruction] {
// Call upgradeable program
let result =
bank_client.send_and_confirm_instruction(&mint_keypair, invoke_instruction.clone());
assert!(result.is_ok());
// Upgrade the program and invoke in same tx
let message = Message::new(
&[undeployment_instruction.clone(), invoke_instruction],
Some(&mint_keypair.pubkey()),
);
let tx = Transaction::new(
&[&mint_keypair, &authority_keypair],
message.clone(),
bank.last_blockhash(),
);
let (result, _, _) = process_transaction_and_record_inner(&bank, tx);
assert_eq!(
result.unwrap_err(),
TransactionError::InstructionError(1, InstructionError::InvalidAccountData),
);
}
}
#[test]
#[cfg(feature = "sbf_rust")]
fn test_program_sbf_invoke_upgradeable_via_cpi() {
solana_logger::setup();
let GenesisConfigInfo {
genesis_config,
mint_keypair,
..
} = create_genesis_config(50);
let bank = Bank::new_for_tests(&genesis_config);
let mut bank_client = BankClient::new(bank);
let invoke_and_return = load_program(
&bank_client,
&bpf_loader::id(),
&mint_keypair,
"solana_sbf_rust_invoke_and_return",
);
// Deploy upgradeable program
let buffer_keypair = Keypair::new();
let program_keypair = Keypair::new();
let program_id = program_keypair.pubkey();
let authority_keypair = Keypair::new();
load_upgradeable_program(
&bank_client,
&mint_keypair,
&buffer_keypair,
&program_keypair,
&authority_keypair,
"solana_sbf_rust_upgradeable",
);
bank_client
.advance_slot(1, &Pubkey::default())
.expect("Failed to advance slot");
let mut instruction = Instruction::new_with_bytes(
invoke_and_return,
&[0],
vec![
AccountMeta::new_readonly(program_id, false),
AccountMeta::new_readonly(clock::id(), false),
],
);
// Call invoker program to invoke the upgradeable program
instruction.data[0] += 1;
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction.clone());
assert_eq!(
result.unwrap_err().unwrap(),
TransactionError::InstructionError(0, InstructionError::Custom(42))
);
// Upgrade program
let buffer_keypair = Keypair::new();
upgrade_program(
&bank_client,
&mint_keypair,
&buffer_keypair,
&program_id,
&authority_keypair,
"solana_sbf_rust_upgraded",
);
bank_client.set_sysvar_for_tests(&clock::Clock {
slot: 2,
..clock::Clock::default()
});
bank_client
.advance_slot(1, &Pubkey::default())
.expect("Failed to advance slot");
// Call the upgraded program
instruction.data[0] += 1;
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction.clone());
assert_eq!(
result.unwrap_err().unwrap(),
TransactionError::InstructionError(0, InstructionError::Custom(43))
);
// Set a new authority
let new_authority_keypair = Keypair::new();
set_upgrade_authority(
&bank_client,
&mint_keypair,
&program_id,
&authority_keypair,
Some(&new_authority_keypair.pubkey()),
);
// Upgrade back to the original program
let buffer_keypair = Keypair::new();
upgrade_program(
&bank_client,
&mint_keypair,
&buffer_keypair,
&program_id,
&new_authority_keypair,
"solana_sbf_rust_upgradeable",
);
bank_client
.advance_slot(1, &Pubkey::default())
.expect("Failed to advance slot");
// Call original program
instruction.data[0] += 1;
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction.clone());
assert_eq!(
result.unwrap_err().unwrap(),
TransactionError::InstructionError(0, InstructionError::Custom(42))
);
}
#[test]
#[cfg(any(feature = "sbf_c", feature = "sbf_rust"))]
fn test_program_sbf_disguised_as_sbf_loader() {
solana_logger::setup();
let mut programs = Vec::new();
#[cfg(feature = "sbf_c")]
{
programs.extend_from_slice(&[("noop")]);
}
#[cfg(feature = "sbf_rust")]
{
programs.extend_from_slice(&[("solana_sbf_rust_noop")]);
}
for program in programs.iter() {
let GenesisConfigInfo {
genesis_config,
mint_keypair,
..
} = create_genesis_config(50);
let mut bank = Bank::new_for_tests(&genesis_config);
// disable native_programs_consume_cu feature to allow test program
// not consume units.
let mut feature_set = FeatureSet::all_enabled();
feature_set.deactivate(&solana_sdk::feature_set::native_programs_consume_cu::id());
bank.feature_set = Arc::new(feature_set);
bank.deactivate_feature(
&solana_sdk::feature_set::remove_bpf_loader_incorrect_program_id::id(),
);
let bank_client = BankClient::new(bank);
let program_id = load_program(&bank_client, &bpf_loader::id(), &mint_keypair, program);
let account_metas = vec![AccountMeta::new_readonly(program_id, false)];
let instruction = Instruction::new_with_bytes(bpf_loader::id(), &[1], account_metas);
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
assert_eq!(
result.unwrap_err().unwrap(),
TransactionError::InstructionError(0, InstructionError::IncorrectProgramId)
);
}
}
#[test]
#[cfg(feature = "sbf_c")]
fn test_program_reads_from_program_account() {
solana_logger::setup();
let GenesisConfigInfo {
genesis_config,
mint_keypair,
..
} = create_genesis_config(50);
let bank = Bank::new_for_tests(&genesis_config);
let mut bank_client = BankClient::new(bank);
let (_, program_id) = load_program_and_advance_slot(
&mut bank_client,
&bpf_loader::id(),
&mint_keypair,
"read_program",
);
let account_metas = vec![AccountMeta::new_readonly(program_id, false)];
let instruction = Instruction::new_with_bytes(program_id, &[], account_metas);
bank_client
.send_and_confirm_instruction(&mint_keypair, instruction)
.unwrap();
}
#[test]
#[cfg(feature = "sbf_c")]
fn test_program_sbf_c_dup() {
solana_logger::setup();
let GenesisConfigInfo {
genesis_config,
mint_keypair,
..
} = create_genesis_config(50);
let bank = Bank::new_for_tests(&genesis_config);
let account_address = Pubkey::new_unique();
let account = AccountSharedData::new_data(42, &[1_u8, 2, 3], &system_program::id()).unwrap();
bank.store_account(&account_address, &account);
let mut bank_client = BankClient::new(bank);
let (_, program_id) =
load_program_and_advance_slot(&mut bank_client, &bpf_loader::id(), &mint_keypair, "ser");
let account_metas = vec![
AccountMeta::new_readonly(account_address, false),
AccountMeta::new_readonly(account_address, false),
];
let instruction = Instruction::new_with_bytes(program_id, &[4, 5, 6, 7], account_metas);
bank_client
.send_and_confirm_instruction(&mint_keypair, instruction)
.unwrap();
}
#[test]
#[cfg(feature = "sbf_rust")]
fn test_program_sbf_upgrade_via_cpi() {
solana_logger::setup();
let GenesisConfigInfo {
genesis_config,
mint_keypair,
..
} = create_genesis_config(50);
let bank = Bank::new_for_tests(&genesis_config);
let mut bank_client = BankClient::new(bank);
let invoke_and_return = load_program(
&bank_client,
&bpf_loader::id(),
&mint_keypair,
"solana_sbf_rust_invoke_and_return",
);
// Deploy upgradeable program
let buffer_keypair = Keypair::new();
let program_keypair = Keypair::new();
let program_id = program_keypair.pubkey();
let authority_keypair = Keypair::new();
load_upgradeable_program(
&bank_client,
&mint_keypair,
&buffer_keypair,
&program_keypair,
&authority_keypair,
"solana_sbf_rust_upgradeable",
);
bank_client
.advance_slot(1, &Pubkey::default())
.expect("Failed to advance the slot");
let program_account = bank_client.get_account(&program_id).unwrap().unwrap();
let Ok(bpf_loader_upgradeable::UpgradeableLoaderState::Program {
programdata_address,
}) = program_account.state()
else {
unreachable!()
};
let original_programdata = bank_client
.get_account_data(&programdata_address)
.unwrap()
.unwrap();
let mut instruction = Instruction::new_with_bytes(
invoke_and_return,
&[0],
vec![
AccountMeta::new_readonly(program_id, false),
AccountMeta::new_readonly(clock::id(), false),
],
);
// Call the upgradable program
instruction.data[0] += 1;
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction.clone());
assert_eq!(
result.unwrap_err().unwrap(),
TransactionError::InstructionError(0, InstructionError::Custom(42))
);
// Load the buffer account
let buffer_keypair = Keypair::new();
load_upgradeable_buffer(
&bank_client,
&mint_keypair,
&buffer_keypair,
&authority_keypair,
"solana_sbf_rust_upgraded",
);
// Upgrade program via CPI
let mut upgrade_instruction = bpf_loader_upgradeable::upgrade(
&program_id,
&buffer_keypair.pubkey(),
&authority_keypair.pubkey(),
&mint_keypair.pubkey(),
);
upgrade_instruction.program_id = invoke_and_return;
upgrade_instruction
.accounts
.insert(0, AccountMeta::new(bpf_loader_upgradeable::id(), false));
let message = Message::new(&[upgrade_instruction], Some(&mint_keypair.pubkey()));
bank_client
.send_and_confirm_message(&[&mint_keypair, &authority_keypair], message)
.unwrap();
bank_client
.advance_slot(1, &Pubkey::default())
.expect("Failed to advance the slot");
// Call the upgraded program
instruction.data[0] += 1;
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction.clone());
assert_eq!(
result.unwrap_err().unwrap(),
TransactionError::InstructionError(0, InstructionError::Custom(43))
);
// Validate that the programdata was actually overwritten
let programdata = bank_client
.get_account_data(&programdata_address)
.unwrap()
.unwrap();
assert_ne!(programdata, original_programdata);
}
#[test]
#[cfg(feature = "sbf_rust")]
fn test_program_sbf_set_upgrade_authority_via_cpi() {
solana_logger::setup();
let GenesisConfigInfo {
genesis_config,
mint_keypair,
..
} = create_genesis_config(50);
let bank = Bank::new_for_tests(&genesis_config);
let mut bank_client = BankClient::new(bank);
// Deploy CPI invoker program
let invoke_and_return = load_program(
&bank_client,
&bpf_loader::id(),
&mint_keypair,
"solana_sbf_rust_invoke_and_return",
);
// Deploy upgradeable program
let buffer_keypair = Keypair::new();
let program_keypair = Keypair::new();
let program_id = program_keypair.pubkey();
let authority_keypair = Keypair::new();
load_upgradeable_program(
&bank_client,
&mint_keypair,
&buffer_keypair,
&program_keypair,
&authority_keypair,
"solana_sbf_rust_upgradeable",
);
bank_client
.advance_slot(1, &Pubkey::default())
.expect("Failed to advance the slot");
// Set program upgrade authority instruction to invoke via CPI
let new_upgrade_authority_key = Keypair::new().pubkey();
let mut set_upgrade_authority_instruction = bpf_loader_upgradeable::set_upgrade_authority(
&program_id,
&authority_keypair.pubkey(),
Some(&new_upgrade_authority_key),
);
// Invoke set_upgrade_authority via CPI invoker program
set_upgrade_authority_instruction.program_id = invoke_and_return;
set_upgrade_authority_instruction
.accounts
.insert(0, AccountMeta::new(bpf_loader_upgradeable::id(), false));
let message = Message::new(
&[set_upgrade_authority_instruction],
Some(&mint_keypair.pubkey()),
);
bank_client
.send_and_confirm_message(&[&mint_keypair, &authority_keypair], message)
.unwrap();
// Assert upgrade authority was changed
let program_account_data = bank_client.get_account_data(&program_id).unwrap().unwrap();
let program_account = parse_bpf_upgradeable_loader(&program_account_data).unwrap();
let upgrade_authority_key = match program_account {
BpfUpgradeableLoaderAccountType::Program(ui_program) => {
let program_data_account_key = Pubkey::from_str(&ui_program.program_data).unwrap();
let program_data_account_data = bank_client
.get_account_data(&program_data_account_key)
.unwrap()
.unwrap();
let program_data_account =
parse_bpf_upgradeable_loader(&program_data_account_data).unwrap();
match program_data_account {
BpfUpgradeableLoaderAccountType::ProgramData(ui_program_data) => ui_program_data
.authority
.map(|a| Pubkey::from_str(&a).unwrap()),
_ => None,
}
}
_ => None,
};
assert_eq!(Some(new_upgrade_authority_key), upgrade_authority_key);
}
#[test]
#[cfg(feature = "sbf_rust")]
fn test_program_upgradeable_locks() {
fn setup_program_upgradeable_locks(
payer_keypair: &Keypair,
buffer_keypair: &Keypair,
program_keypair: &Keypair,
) -> (Arc<Bank>, Transaction, Transaction) {
solana_logger::setup();
let GenesisConfigInfo {
genesis_config,
mint_keypair,
..
} = create_genesis_config(2_000_000_000);
let bank = Bank::new_for_tests(&genesis_config);
let bank = Arc::new(bank);
let mut bank_client = BankClient::new_shared(bank.clone());
load_upgradeable_program(
&bank_client,
&mint_keypair,
buffer_keypair,
program_keypair,
payer_keypair,
"solana_sbf_rust_panic",
);
// Load the buffer account
load_upgradeable_buffer(
&bank_client,
&mint_keypair,
buffer_keypair,
&payer_keypair,
"solana_sbf_rust_noop",
);
let bank = bank_client
.advance_slot(1, &Pubkey::default())
.expect("Failed to advance the slot");
bank_client
.send_and_confirm_instruction(
&mint_keypair,
system_instruction::transfer(
&mint_keypair.pubkey(),
&payer_keypair.pubkey(),
1_000_000_000,
),
)
.unwrap();
let invoke_tx = Transaction::new(
&[payer_keypair],
Message::new(
&[Instruction::new_with_bytes(
program_keypair.pubkey(),
&[0; 0],
vec![],
)],
Some(&payer_keypair.pubkey()),
),
bank.last_blockhash(),
);
let upgrade_tx = Transaction::new(
&[payer_keypair],
Message::new(
&[bpf_loader_upgradeable::upgrade(
&program_keypair.pubkey(),
&buffer_keypair.pubkey(),
&payer_keypair.pubkey(),
&payer_keypair.pubkey(),
)],
Some(&payer_keypair.pubkey()),
),
bank.last_blockhash(),
);
(bank, invoke_tx, upgrade_tx)
}
let payer_keypair = keypair_from_seed(&[56u8; 32]).unwrap();
let buffer_keypair = keypair_from_seed(&[11; 32]).unwrap();
let program_keypair = keypair_from_seed(&[77u8; 32]).unwrap();
let results1 = {
let (bank, invoke_tx, upgrade_tx) =
setup_program_upgradeable_locks(&payer_keypair, &buffer_keypair, &program_keypair);
execute_transactions(&bank, vec![upgrade_tx, invoke_tx])
};
let results2 = {
let (bank, invoke_tx, upgrade_tx) =
setup_program_upgradeable_locks(&payer_keypair, &buffer_keypair, &program_keypair);
execute_transactions(&bank, vec![invoke_tx, upgrade_tx])
};
assert!(matches!(
results1[0],
Ok(ConfirmedTransactionWithStatusMeta {
tx_with_meta: TransactionWithStatusMeta::Complete(VersionedTransactionWithStatusMeta {
meta: TransactionStatusMeta { status: Ok(()), .. },
..
}),
..
})
));
assert_eq!(results1[1], Err(TransactionError::AccountInUse));
assert!(matches!(
results2[0],
Ok(ConfirmedTransactionWithStatusMeta {
tx_with_meta: TransactionWithStatusMeta::Complete(VersionedTransactionWithStatusMeta {
meta: TransactionStatusMeta {
status: Err(TransactionError::InstructionError(
0,
InstructionError::ProgramFailedToComplete
)),
..
},
..
}),
..
})
));
assert_eq!(results2[1], Err(TransactionError::AccountInUse));
}
#[test]
#[cfg(feature = "sbf_rust")]
fn test_program_sbf_finalize() {
solana_logger::setup();
let GenesisConfigInfo {
genesis_config,
mint_keypair,
..
} = create_genesis_config(50);
let bank = Bank::new_for_tests(&genesis_config);
let bank = Arc::new(bank);
let mut bank_client = BankClient::new_shared(bank.clone());
let (_, program_pubkey) = load_program_and_advance_slot(
&mut bank_client,
&bpf_loader::id(),
&mint_keypair,
"solana_sbf_rust_finalize",
);
// Write the noop program into the same program account
let (program_keypair, _instruction) = load_and_finalize_program(
&bank_client,
&bpf_loader::id(),
None,
&mint_keypair,
"solana_sbf_rust_noop",
);
bank_client
.advance_slot(1, &Pubkey::default())
.expect("Failed to advance the slot");
let account_metas = vec![
AccountMeta::new(program_keypair.pubkey(), true),
AccountMeta::new_readonly(bpf_loader::id(), false),
AccountMeta::new(rent::id(), false),
];
let instruction = Instruction::new_with_bytes(program_pubkey, &[], account_metas.clone());
let message = Message::new(&[instruction], Some(&mint_keypair.pubkey()));
let result = bank_client.send_and_confirm_message(&[&mint_keypair, &program_keypair], message);
assert_eq!(
result.unwrap_err().unwrap(),
TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete)
);
}
#[test]
#[cfg(feature = "sbf_rust")]
fn test_program_sbf_ro_account_modify() {
solana_logger::setup();
let GenesisConfigInfo {
genesis_config,
mint_keypair,
..
} = create_genesis_config(50);
let bank = Bank::new_for_tests(&genesis_config);
let bank = Arc::new(bank);
let mut bank_client = BankClient::new_shared(bank.clone());
let (bank, program_id) = load_program_and_advance_slot(
&mut bank_client,
&bpf_loader::id(),
&mint_keypair,
"solana_sbf_rust_ro_account_modify",
);
let argument_keypair = Keypair::new();
let account = AccountSharedData::new(42, 100, &program_id);
bank.store_account(&argument_keypair.pubkey(), &account);
let from_keypair = Keypair::new();
let account = AccountSharedData::new(84, 0, &system_program::id());
bank.store_account(&from_keypair.pubkey(), &account);
let mint_pubkey = mint_keypair.pubkey();
let account_metas = vec![
AccountMeta::new_readonly(argument_keypair.pubkey(), false),
AccountMeta::new_readonly(program_id, false),
];
let instruction = Instruction::new_with_bytes(program_id, &[0], account_metas.clone());
let message = Message::new(&[instruction], Some(&mint_pubkey));
let result = bank_client.send_and_confirm_message(&[&mint_keypair], message);
assert_eq!(
result.unwrap_err().unwrap(),
TransactionError::InstructionError(0, InstructionError::ReadonlyDataModified)
);
let instruction = Instruction::new_with_bytes(program_id, &[1], account_metas.clone());
let message = Message::new(&[instruction], Some(&mint_pubkey));
let result = bank_client.send_and_confirm_message(&[&mint_keypair], message);
assert_eq!(
result.unwrap_err().unwrap(),
TransactionError::InstructionError(0, InstructionError::ReadonlyDataModified)
);
let instruction = Instruction::new_with_bytes(program_id, &[2], account_metas.clone());
let message = Message::new(&[instruction], Some(&mint_pubkey));
let result = bank_client.send_and_confirm_message(&[&mint_keypair], message);
assert_eq!(
result.unwrap_err().unwrap(),
TransactionError::InstructionError(0, InstructionError::ReadonlyDataModified)
);
}
#[test]
#[cfg(feature = "sbf_rust")]
fn test_program_sbf_realloc() {
solana_logger::setup();
const START_BALANCE: u64 = 100_000_000_000;
let GenesisConfigInfo {
genesis_config,
mint_keypair,
..
} = create_genesis_config(1_000_000_000_000);
let mint_pubkey = mint_keypair.pubkey();
let signer = &[&mint_keypair];
for direct_mapping in [false, true] {
let mut bank = Bank::new_for_tests(&genesis_config);
let feature_set = Arc::make_mut(&mut bank.feature_set);
// by default test banks have all features enabled, so we only need to
// disable when needed
if !direct_mapping {
feature_set.deactivate(&feature_set::bpf_account_data_direct_mapping::id());
}
let bank = Arc::new(bank);
let mut bank_client = BankClient::new_shared(bank.clone());
let (bank, program_id) = load_program_and_advance_slot(
&mut bank_client,
&bpf_loader::id(),
&mint_keypair,
"solana_sbf_rust_realloc",
);
let mut bump = 0;
let keypair = Keypair::new();
let pubkey = keypair.pubkey();
let account = AccountSharedData::new(START_BALANCE, 5, &program_id);
bank.store_account(&pubkey, &account);
// Realloc RO account
let mut instruction = realloc(&program_id, &pubkey, 0, &mut bump);
instruction.accounts[0].is_writable = false;
assert_eq!(
bank_client
.send_and_confirm_message(signer, Message::new(&[instruction], Some(&mint_pubkey),),)
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::ReadonlyDataModified)
);
// Realloc account to overflow
assert_eq!(
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[realloc(&program_id, &pubkey, usize::MAX, &mut bump)],
Some(&mint_pubkey),
),
)
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::InvalidRealloc)
);
// Realloc account to 0
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[realloc(&program_id, &pubkey, 0, &mut bump)],
Some(&mint_pubkey),
),
)
.unwrap();
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
assert_eq!(0, data.len());
// Realloc account to max then undo
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[realloc_extend_and_undo(
&program_id,
&pubkey,
MAX_PERMITTED_DATA_INCREASE,
&mut bump,
)],
Some(&mint_pubkey),
),
)
.unwrap();
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
assert_eq!(0, data.len());
// Realloc account to max + 1 then undo
assert_eq!(
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[realloc_extend_and_undo(
&program_id,
&pubkey,
MAX_PERMITTED_DATA_INCREASE + 1,
&mut bump,
)],
Some(&mint_pubkey),
),
)
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::InvalidRealloc)
);
// Realloc to max + 1
assert_eq!(
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[realloc(
&program_id,
&pubkey,
MAX_PERMITTED_DATA_INCREASE + 1,
&mut bump
)],
Some(&mint_pubkey),
),
)
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::InvalidRealloc)
);
// Realloc to max length in max increase increments
for i in 0..MAX_PERMITTED_DATA_LENGTH as usize / MAX_PERMITTED_DATA_INCREASE {
let mut bump = i as u64;
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[realloc_extend_and_fill(
&program_id,
&pubkey,
MAX_PERMITTED_DATA_INCREASE,
1,
&mut bump,
)],
Some(&mint_pubkey),
),
)
.unwrap();
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
assert_eq!((i + 1) * MAX_PERMITTED_DATA_INCREASE, data.len());
}
for i in 0..data.len() {
assert_eq!(data[i], 1);
}
// and one more time should fail
assert_eq!(
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[realloc_extend(
&program_id,
&pubkey,
MAX_PERMITTED_DATA_INCREASE,
&mut bump
)],
Some(&mint_pubkey),
)
)
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::InvalidRealloc)
);
// Realloc to 6 bytes
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[realloc(&program_id, &pubkey, 6, &mut bump)],
Some(&mint_pubkey),
),
)
.unwrap();
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
assert_eq!(6, data.len());
// Extend by 2 bytes and write a u64. This ensures that we can do writes that span the original
// account length (6 bytes) and the realloc data (2 bytes).
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[extend_and_write_u64(
&program_id,
&pubkey,
0x1122334455667788,
)],
Some(&mint_pubkey),
),
)
.unwrap();
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
assert_eq!(8, data.len());
assert_eq!(0x1122334455667788, unsafe { *data.as_ptr().cast::<u64>() });
// Realloc to 0
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[realloc(&program_id, &pubkey, 0, &mut bump)],
Some(&mint_pubkey),
),
)
.unwrap();
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
assert_eq!(0, data.len());
// Realloc and assign
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[Instruction::new_with_bytes(
program_id,
&[REALLOC_AND_ASSIGN],
vec![AccountMeta::new(pubkey, false)],
)],
Some(&mint_pubkey),
),
)
.unwrap();
let account = bank.get_account(&pubkey).unwrap();
assert_eq!(&solana_sdk::system_program::id(), account.owner());
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
assert_eq!(MAX_PERMITTED_DATA_INCREASE, data.len());
// Realloc to 0 with wrong owner
assert_eq!(
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[realloc(&program_id, &pubkey, 0, &mut bump)],
Some(&mint_pubkey),
),
)
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::AccountDataSizeChanged)
);
// realloc and assign to self via cpi
assert_eq!(
bank_client
.send_and_confirm_message(
&[&mint_keypair, &keypair],
Message::new(
&[Instruction::new_with_bytes(
program_id,
&[REALLOC_AND_ASSIGN_TO_SELF_VIA_SYSTEM_PROGRAM],
vec![
AccountMeta::new(pubkey, true),
AccountMeta::new(solana_sdk::system_program::id(), false),
],
)],
Some(&mint_pubkey),
)
)
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::AccountDataSizeChanged)
);
// Assign to self and realloc via cpi
bank_client
.send_and_confirm_message(
&[&mint_keypair, &keypair],
Message::new(
&[Instruction::new_with_bytes(
program_id,
&[ASSIGN_TO_SELF_VIA_SYSTEM_PROGRAM_AND_REALLOC],
vec![
AccountMeta::new(pubkey, true),
AccountMeta::new(solana_sdk::system_program::id(), false),
],
)],
Some(&mint_pubkey),
),
)
.unwrap();
let account = bank.get_account(&pubkey).unwrap();
assert_eq!(&program_id, account.owner());
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
assert_eq!(2 * MAX_PERMITTED_DATA_INCREASE, data.len());
// Realloc to 0
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[realloc(&program_id, &pubkey, 0, &mut bump)],
Some(&mint_pubkey),
),
)
.unwrap();
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
assert_eq!(0, data.len());
// zero-init
bank_client
.send_and_confirm_message(
&[&mint_keypair, &keypair],
Message::new(
&[Instruction::new_with_bytes(
program_id,
&[ZERO_INIT],
vec![AccountMeta::new(pubkey, true)],
)],
Some(&mint_pubkey),
),
)
.unwrap();
}
}
#[test]
#[cfg(feature = "sbf_rust")]
fn test_program_sbf_realloc_invoke() {
solana_logger::setup();
const START_BALANCE: u64 = 100_000_000_000;
let GenesisConfigInfo {
mut genesis_config,
mint_keypair,
..
} = create_genesis_config(1_000_000_000_000);
genesis_config.rent = Rent::default();
let mint_pubkey = mint_keypair.pubkey();
let signer = &[&mint_keypair];
let bank = Bank::new_for_tests(&genesis_config);
let bank = Arc::new(bank);
let mut bank_client = BankClient::new_shared(bank.clone());
let realloc_program_id = load_program(
&bank_client,
&bpf_loader::id(),
&mint_keypair,
"solana_sbf_rust_realloc",
);
let (bank, realloc_invoke_program_id) = load_program_and_advance_slot(
&mut bank_client,
&bpf_loader::id(),
&mint_keypair,
"solana_sbf_rust_realloc_invoke",
);
let mut bump = 0;
let keypair = Keypair::new();
let pubkey = keypair.pubkey().clone();
let account = AccountSharedData::new(START_BALANCE, 5, &realloc_program_id);
bank.store_account(&pubkey, &account);
let invoke_keypair = Keypair::new();
let invoke_pubkey = invoke_keypair.pubkey().clone();
// Realloc RO account
assert_eq!(
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&[INVOKE_REALLOC_ZERO_RO],
vec![
AccountMeta::new_readonly(pubkey, false),
AccountMeta::new_readonly(realloc_program_id, false),
],
)],
Some(&mint_pubkey),
)
)
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::ReadonlyDataModified)
);
let account = bank.get_account(&pubkey).unwrap();
assert_eq!(account.lamports(), START_BALANCE);
// Realloc account to 0
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[realloc(&realloc_program_id, &pubkey, 0, &mut bump)],
Some(&mint_pubkey),
),
)
.unwrap();
let account = bank.get_account(&pubkey).unwrap();
assert_eq!(account.lamports(), START_BALANCE);
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
assert_eq!(0, data.len());
// Realloc to max + 1
assert_eq!(
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&[INVOKE_REALLOC_MAX_PLUS_ONE],
vec![
AccountMeta::new(pubkey, false),
AccountMeta::new_readonly(realloc_program_id, false),
],
)],
Some(&mint_pubkey),
)
)
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::InvalidRealloc)
);
// Realloc to max twice
assert_eq!(
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&[INVOKE_REALLOC_MAX_TWICE],
vec![
AccountMeta::new(pubkey, false),
AccountMeta::new_readonly(realloc_program_id, false),
],
)],
Some(&mint_pubkey),
)
)
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::InvalidRealloc)
);
// Realloc account to 0
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[realloc(&realloc_program_id, &pubkey, 0, &mut bump)],
Some(&mint_pubkey),
),
)
.unwrap();
let account = bank.get_account(&pubkey).unwrap();
assert_eq!(account.lamports(), START_BALANCE);
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
assert_eq!(0, data.len());
// Realloc and assign
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&[INVOKE_REALLOC_AND_ASSIGN],
vec![
AccountMeta::new(pubkey, false),
AccountMeta::new_readonly(realloc_program_id, false),
],
)],
Some(&mint_pubkey),
),
)
.unwrap();
let account = bank.get_account(&pubkey).unwrap();
assert_eq!(&solana_sdk::system_program::id(), account.owner());
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
assert_eq!(MAX_PERMITTED_DATA_INCREASE, data.len());
// Realloc to 0 with wrong owner
assert_eq!(
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[realloc(&realloc_program_id, &pubkey, 0, &mut bump)],
Some(&mint_pubkey),
)
)
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::AccountDataSizeChanged)
);
// realloc and assign to self via system program
assert_eq!(
bank_client
.send_and_confirm_message(
&[&mint_keypair, &keypair],
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&[INVOKE_REALLOC_AND_ASSIGN_TO_SELF_VIA_SYSTEM_PROGRAM],
vec![
AccountMeta::new(pubkey, true),
AccountMeta::new_readonly(realloc_program_id, false),
AccountMeta::new_readonly(solana_sdk::system_program::id(), false),
],
)],
Some(&mint_pubkey),
)
)
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::AccountDataSizeChanged)
);
// Assign to self and realloc via system program
bank_client
.send_and_confirm_message(
&[&mint_keypair, &keypair],
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&[INVOKE_ASSIGN_TO_SELF_VIA_SYSTEM_PROGRAM_AND_REALLOC],
vec![
AccountMeta::new(pubkey, true),
AccountMeta::new_readonly(realloc_program_id, false),
AccountMeta::new_readonly(solana_sdk::system_program::id(), false),
],
)],
Some(&mint_pubkey),
),
)
.unwrap();
let account = bank.get_account(&pubkey).unwrap();
assert_eq!(&realloc_program_id, account.owner());
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
assert_eq!(2 * MAX_PERMITTED_DATA_INCREASE, data.len());
// Realloc to 0
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[realloc(&realloc_program_id, &pubkey, 0, &mut bump)],
Some(&mint_pubkey),
),
)
.unwrap();
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
assert_eq!(0, data.len());
// Realloc to 100 and check via CPI
let invoke_account = AccountSharedData::new(START_BALANCE, 5, &realloc_invoke_program_id);
bank.store_account(&invoke_pubkey, &invoke_account);
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&[INVOKE_REALLOC_INVOKE_CHECK],
vec![
AccountMeta::new(invoke_pubkey, false),
AccountMeta::new_readonly(realloc_program_id, false),
],
)],
Some(&mint_pubkey),
),
)
.unwrap();
let data = bank_client
.get_account_data(&invoke_pubkey)
.unwrap()
.unwrap();
assert_eq!(100, data.len());
for i in 0..5 {
assert_eq!(data[i], 0);
}
for i in 5..data.len() {
assert_eq!(data[i], 2);
}
// Create account, realloc, check
let new_keypair = Keypair::new();
let new_pubkey = new_keypair.pubkey().clone();
let mut instruction_data = vec![];
instruction_data.extend_from_slice(&[INVOKE_CREATE_ACCOUNT_REALLOC_CHECK, 1]);
instruction_data.extend_from_slice(&100_usize.to_le_bytes());
bank_client
.send_and_confirm_message(
&[&mint_keypair, &new_keypair],
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&instruction_data,
vec![
AccountMeta::new(mint_pubkey, true),
AccountMeta::new(new_pubkey, true),
AccountMeta::new(solana_sdk::system_program::id(), false),
AccountMeta::new_readonly(realloc_invoke_program_id, false),
],
)],
Some(&mint_pubkey),
),
)
.unwrap();
let data = bank_client.get_account_data(&new_pubkey).unwrap().unwrap();
assert_eq!(200, data.len());
let account = bank.get_account(&new_pubkey).unwrap();
assert_eq!(&realloc_invoke_program_id, account.owner());
// Invoke, dealloc, and assign
let pre_len = 100;
let new_len = pre_len * 2;
let mut invoke_account = AccountSharedData::new(START_BALANCE, pre_len, &realloc_program_id);
invoke_account.set_data_from_slice(&vec![1; pre_len]);
bank.store_account(&invoke_pubkey, &invoke_account);
let mut instruction_data = vec![];
instruction_data.extend_from_slice(&[INVOKE_DEALLOC_AND_ASSIGN, 1]);
instruction_data.extend_from_slice(&pre_len.to_le_bytes());
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&instruction_data,
vec![
AccountMeta::new(invoke_pubkey, false),
AccountMeta::new_readonly(realloc_invoke_program_id, false),
AccountMeta::new_readonly(realloc_program_id, false),
],
)],
Some(&mint_pubkey),
),
)
.unwrap();
let data = bank_client
.get_account_data(&invoke_pubkey)
.unwrap()
.unwrap();
assert_eq!(new_len, data.len());
for i in 0..new_len {
assert_eq!(data[i], 0);
}
// Realloc to max invoke max
let invoke_account = AccountSharedData::new(42, 0, &realloc_invoke_program_id);
bank.store_account(&invoke_pubkey, &invoke_account);
assert_eq!(
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&[INVOKE_REALLOC_MAX_INVOKE_MAX],
vec![
AccountMeta::new(invoke_pubkey, false),
AccountMeta::new_readonly(realloc_program_id, false),
],
)],
Some(&mint_pubkey),
)
)
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::InvalidRealloc)
);
// CPI realloc extend then local realloc extend
for (cpi_extend_bytes, local_extend_bytes, should_succeed) in [
(0, 0, true),
(MAX_PERMITTED_DATA_INCREASE, 0, true),
(0, MAX_PERMITTED_DATA_INCREASE, true),
(MAX_PERMITTED_DATA_INCREASE, 1, false),
(1, MAX_PERMITTED_DATA_INCREASE, false),
] {
let invoke_account = AccountSharedData::new(100_000_000, 0, &realloc_invoke_program_id);
bank.store_account(&invoke_pubkey, &invoke_account);
let mut instruction_data = vec![];
instruction_data.extend_from_slice(&[INVOKE_REALLOC_TO_THEN_LOCAL_REALLOC_EXTEND, 1]);
instruction_data.extend_from_slice(&cpi_extend_bytes.to_le_bytes());
instruction_data.extend_from_slice(&local_extend_bytes.to_le_bytes());
let result = bank_client.send_and_confirm_message(
signer,
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&instruction_data,
vec![
AccountMeta::new(invoke_pubkey, false),
AccountMeta::new_readonly(realloc_invoke_program_id, false),
],
)],
Some(&mint_pubkey),
),
);
if should_succeed {
assert!(
result.is_ok(),
"cpi: {cpi_extend_bytes} local: {local_extend_bytes}, err: {:?}",
result.err()
);
} else {
assert_eq!(
result.unwrap_err().unwrap(),
TransactionError::InstructionError(0, InstructionError::InvalidRealloc),
"cpi: {cpi_extend_bytes} local: {local_extend_bytes}",
);
}
}
// Realloc invoke max twice
let invoke_account = AccountSharedData::new(42, 0, &realloc_program_id);
bank.store_account(&invoke_pubkey, &invoke_account);
assert_eq!(
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&[INVOKE_INVOKE_MAX_TWICE],
vec![
AccountMeta::new(invoke_pubkey, false),
AccountMeta::new_readonly(realloc_invoke_program_id, false),
AccountMeta::new_readonly(realloc_program_id, false),
],
)],
Some(&mint_pubkey),
)
)
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::InvalidRealloc)
);
// Realloc to 0
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[realloc(&realloc_program_id, &pubkey, 0, &mut bump)],
Some(&mint_pubkey),
),
)
.unwrap();
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
assert_eq!(0, data.len());
// Realloc to max length in max increase increments
for i in 0..MAX_PERMITTED_DATA_LENGTH as usize / MAX_PERMITTED_DATA_INCREASE {
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&[INVOKE_REALLOC_EXTEND_MAX, 1, i as u8, (i / 255) as u8],
vec![
AccountMeta::new(pubkey, false),
AccountMeta::new_readonly(realloc_program_id, false),
],
)],
Some(&mint_pubkey),
),
)
.unwrap();
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
assert_eq!((i + 1) * MAX_PERMITTED_DATA_INCREASE, data.len());
}
for i in 0..data.len() {
assert_eq!(data[i], 1);
}
// and one more time should fail
assert_eq!(
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&[INVOKE_REALLOC_EXTEND_MAX, 2, 1, 1],
vec![
AccountMeta::new(pubkey, false),
AccountMeta::new_readonly(realloc_program_id, false),
],
)],
Some(&mint_pubkey),
)
)
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::InvalidRealloc)
);
// Realloc rescursively and fill data
let invoke_keypair = Keypair::new();
let invoke_pubkey = invoke_keypair.pubkey().clone();
let invoke_account = AccountSharedData::new(START_BALANCE, 0, &realloc_invoke_program_id);
bank.store_account(&invoke_pubkey, &invoke_account);
let mut instruction_data = vec![];
instruction_data.extend_from_slice(&[INVOKE_REALLOC_RECURSIVE, 1]);
instruction_data.extend_from_slice(&100_usize.to_le_bytes());
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&instruction_data,
vec![
AccountMeta::new(invoke_pubkey, false),
AccountMeta::new_readonly(realloc_invoke_program_id, false),
],
)],
Some(&mint_pubkey),
),
)
.unwrap();
let data = bank_client
.get_account_data(&invoke_pubkey)
.unwrap()
.unwrap();
assert_eq!(200, data.len());
for i in 0..100 {
assert_eq!(data[i], 1);
}
for i in 100..200 {
assert_eq!(data[i], 2);
}
}
#[test]
#[cfg(feature = "sbf_rust")]
fn test_program_sbf_processed_inner_instruction() {
solana_logger::setup();
let GenesisConfigInfo {
genesis_config,
mint_keypair,
..
} = create_genesis_config(50);
let bank = Bank::new_for_tests(&genesis_config);
let bank = Arc::new(bank);
let mut bank_client = BankClient::new_shared(bank.clone());
let sibling_program_id = load_program(
&bank_client,
&bpf_loader::id(),
&mint_keypair,
"solana_sbf_rust_sibling_instructions",
);
let sibling_inner_program_id = load_program(
&bank_client,
&bpf_loader::id(),
&mint_keypair,
"solana_sbf_rust_sibling_inner_instructions",
);
let noop_program_id = load_program(
&bank_client,
&bpf_loader::id(),
&mint_keypair,
"solana_sbf_rust_noop",
);
let (_, invoke_and_return_program_id) = load_program_and_advance_slot(
&mut bank_client,
&bpf_loader::id(),
&mint_keypair,
"solana_sbf_rust_invoke_and_return",
);
let instruction2 = Instruction::new_with_bytes(
noop_program_id,
&[43],
vec![
AccountMeta::new_readonly(noop_program_id, false),
AccountMeta::new(mint_keypair.pubkey(), true),
],
);
let instruction1 = Instruction::new_with_bytes(
noop_program_id,
&[42],
vec![
AccountMeta::new(mint_keypair.pubkey(), true),
AccountMeta::new_readonly(noop_program_id, false),
],
);
let instruction0 = Instruction::new_with_bytes(
sibling_program_id,
&[1, 2, 3, 0, 4, 5, 6],
vec![
AccountMeta::new(mint_keypair.pubkey(), true),
AccountMeta::new_readonly(noop_program_id, false),
AccountMeta::new_readonly(invoke_and_return_program_id, false),
AccountMeta::new_readonly(sibling_inner_program_id, false),
],
);
let message = Message::new(
&[instruction2, instruction1, instruction0],
Some(&mint_keypair.pubkey()),
);
assert!(bank_client
.send_and_confirm_message(&[&mint_keypair], message)
.is_ok());
}
#[test]
#[cfg(feature = "sbf_rust")]
fn test_program_fees() {
solana_logger::setup();
let congestion_multiplier = 1;
let GenesisConfigInfo {
mut genesis_config,
mint_keypair,
..
} = create_genesis_config(500_000_000);
genesis_config.fee_rate_governor = FeeRateGovernor::new(congestion_multiplier, 0);
let mut bank = Bank::new_for_tests(&genesis_config);
let fee_structure =
FeeStructure::new(0.000005, 0.0, vec![(200, 0.0000005), (1400000, 0.000005)]);
bank.fee_structure = fee_structure.clone();
bank.feature_set = Arc::new(FeatureSet::all_enabled());
let mut bank_client = BankClient::new(bank);
let (_, program_id) = load_program_and_advance_slot(
&mut bank_client,
&bpf_loader::id(),
&mint_keypair,
"solana_sbf_rust_noop",
);
let pre_balance = bank_client.get_balance(&mint_keypair.pubkey()).unwrap();
let message = Message::new(
&[Instruction::new_with_bytes(program_id, &[], vec![])],
Some(&mint_keypair.pubkey()),
);
let mut feature_set = FeatureSet::all_enabled();
feature_set.deactivate(&remove_deprecated_request_unit_ix::id());
let sanitized_message = SanitizedMessage::try_from(message.clone()).unwrap();
let expected_normal_fee = fee_structure.calculate_fee(
&sanitized_message,
congestion_multiplier,
&process_compute_budget_instructions(
sanitized_message.program_instructions_iter(),
&feature_set,
)
.unwrap_or_default()
.into(),
true,
false,
);
bank_client
.send_and_confirm_message(&[&mint_keypair], message)
.unwrap();
let post_balance = bank_client.get_balance(&mint_keypair.pubkey()).unwrap();
assert_eq!(pre_balance - post_balance, expected_normal_fee);
let pre_balance = bank_client.get_balance(&mint_keypair.pubkey()).unwrap();
let message = Message::new(
&[
ComputeBudgetInstruction::set_compute_unit_price(1),
Instruction::new_with_bytes(program_id, &[], vec![]),
],
Some(&mint_keypair.pubkey()),
);
let sanitized_message = SanitizedMessage::try_from(message.clone()).unwrap();
let mut feature_set = FeatureSet::all_enabled();
feature_set.deactivate(&remove_deprecated_request_unit_ix::id());
let expected_prioritized_fee = fee_structure.calculate_fee(
&sanitized_message,
congestion_multiplier,
&process_compute_budget_instructions(
sanitized_message.program_instructions_iter(),
&feature_set,
)
.unwrap_or_default()
.into(),
true,
false,
);
assert!(expected_normal_fee < expected_prioritized_fee);
bank_client
.send_and_confirm_message(&[&mint_keypair], message)
.unwrap();
let post_balance = bank_client.get_balance(&mint_keypair.pubkey()).unwrap();
assert_eq!(pre_balance - post_balance, expected_prioritized_fee);
}
#[test]
#[cfg(feature = "sbf_rust")]
fn test_get_minimum_delegation() {
let GenesisConfigInfo {
genesis_config,
mint_keypair,
..
} = create_genesis_config(100_123_456_789);
let mut bank = Bank::new_for_tests(&genesis_config);
bank.feature_set = Arc::new(FeatureSet::all_enabled());
let bank = Arc::new(bank);
let mut bank_client = BankClient::new_shared(bank.clone());
let (_, program_id) = load_program_and_advance_slot(
&mut bank_client,
&bpf_loader::id(),
&mint_keypair,
"solana_sbf_rust_get_minimum_delegation",
);
let account_metas = vec![AccountMeta::new_readonly(stake::program::id(), false)];
let instruction = Instruction::new_with_bytes(program_id, &[], account_metas);
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
assert!(result.is_ok());
}
#[test]
#[cfg(feature = "sbf_rust")]
fn test_program_sbf_inner_instruction_alignment_checks() {
solana_logger::setup();
let GenesisConfigInfo {
genesis_config,
mint_keypair,
..
} = create_genesis_config(50);
let bank = Bank::new_for_tests(&genesis_config);
let noop = create_program(&bank, &bpf_loader_deprecated::id(), "solana_sbf_rust_noop");
let inner_instruction_alignment_check = create_program(
&bank,
&bpf_loader_deprecated::id(),
"solana_sbf_rust_inner_instruction_alignment_check",
);
// invoke unaligned program, which will call aligned program twice,
// unaligned should be allowed once invoke completes
let mut bank_client = BankClient::new(bank);
bank_client
.advance_slot(1, &Pubkey::default())
.expect("Failed to advance the slot");
let mut instruction = Instruction::new_with_bytes(
inner_instruction_alignment_check,
&[0],
vec![
AccountMeta::new_readonly(noop, false),
AccountMeta::new_readonly(mint_keypair.pubkey(), false),
],
);
instruction.data[0] += 1;
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction.clone());
assert!(result.is_ok(), "{result:?}");
}
#[test]
#[cfg(feature = "sbf_rust")]
fn test_cpi_account_ownership_writability() {
solana_logger::setup();
for direct_mapping in [false, true] {
let GenesisConfigInfo {
genesis_config,
mint_keypair,
..
} = create_genesis_config(100_123_456_789);
let mut bank = Bank::new_for_tests(&genesis_config);
let mut feature_set = FeatureSet::all_enabled();
if !direct_mapping {
feature_set.deactivate(&feature_set::bpf_account_data_direct_mapping::id());
}
bank.feature_set = Arc::new(feature_set);
let bank = Arc::new(bank);
let mut bank_client = BankClient::new_shared(bank);
let invoke_program_id = load_program(
&bank_client,
&bpf_loader::id(),
&mint_keypair,
"solana_sbf_rust_invoke",
);
let invoked_program_id = load_program(
&bank_client,
&bpf_loader::id(),
&mint_keypair,
"solana_sbf_rust_invoked",
);
let (bank, realloc_program_id) = load_program_and_advance_slot(
&mut bank_client,
&bpf_loader::id(),
&mint_keypair,
"solana_sbf_rust_realloc",
);
let account_keypair = Keypair::new();
let mint_pubkey = mint_keypair.pubkey();
let account_metas = vec![
AccountMeta::new(mint_pubkey, true),
AccountMeta::new(account_keypair.pubkey(), false),
AccountMeta::new_readonly(invoked_program_id, false),
AccountMeta::new_readonly(invoke_program_id, false),
AccountMeta::new_readonly(realloc_program_id, false),
];
for (account_size, byte_index) in [
(0, 0), // first realloc byte
(0, MAX_PERMITTED_DATA_INCREASE as u8), // last realloc byte
(2, 0), // first data byte
(2, 1), // last data byte
(2, 3), // first realloc byte
(2, 2 + MAX_PERMITTED_DATA_INCREASE as u8), // last realloc byte
] {
for instruction_id in [
TEST_FORBID_WRITE_AFTER_OWNERSHIP_CHANGE_IN_CALLEE,
TEST_FORBID_WRITE_AFTER_OWNERSHIP_CHANGE_IN_CALLER,
] {
bank.register_unique_recent_blockhash_for_test();
let account = AccountSharedData::new(42, account_size, &invoke_program_id);
bank.store_account(&account_keypair.pubkey(), &account);
let instruction = Instruction::new_with_bytes(
invoke_program_id,
&[instruction_id, byte_index, 42, 42],
account_metas.clone(),
);
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
if (byte_index as usize) < account_size || direct_mapping {
assert_eq!(
result.unwrap_err().unwrap(),
TransactionError::InstructionError(
0,
InstructionError::ExternalAccountDataModified
)
);
} else {
// without direct mapping, changes to the realloc padding
// outside the account length are ignored
assert!(result.is_ok(), "{result:?}");
}
}
}
// Test that the CPI code that updates `ref_to_len_in_vm` fails if we
// make it write to an invalid location. This is the first variant which
// correctly triggers ExternalAccountDataModified when direct mapping is
// disabled. When direct mapping is enabled this tests fails early
// because we move the account data pointer.
// TEST_FORBID_LEN_UPDATE_AFTER_OWNERSHIP_CHANGE is able to make more
// progress when direct mapping is on.
let account = AccountSharedData::new(42, 0, &invoke_program_id);
bank.store_account(&account_keypair.pubkey(), &account);
let instruction_data = vec![
TEST_FORBID_LEN_UPDATE_AFTER_OWNERSHIP_CHANGE_MOVING_DATA_POINTER,
42,
42,
42,
];
let instruction = Instruction::new_with_bytes(
invoke_program_id,
&instruction_data,
account_metas.clone(),
);
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
assert_eq!(
result.unwrap_err().unwrap(),
if direct_mapping {
// We move the data pointer, direct mapping doesn't allow it
// anymore so it errors out earlier. See
// test_cpi_invalid_account_info_pointers.
TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete)
} else {
// We managed to make CPI write into the account data, but the
// usual checks still apply and we get an error.
TransactionError::InstructionError(0, InstructionError::ExternalAccountDataModified)
}
);
// We're going to try and make CPI write ref_to_len_in_vm into a 2nd
// account, so we add an extra one here.
let account2_keypair = Keypair::new();
let mut account_metas = account_metas.clone();
account_metas.push(AccountMeta::new(account2_keypair.pubkey(), false));
for target_account in [1, account_metas.len() as u8 - 1] {
// Similar to the test above where we try to make CPI write into account
// data. This variant is for when direct mapping is enabled.
let account = AccountSharedData::new(42, 0, &invoke_program_id);
bank.store_account(&account_keypair.pubkey(), &account);
let account = AccountSharedData::new(42, 0, &invoke_program_id);
bank.store_account(&account2_keypair.pubkey(), &account);
let instruction_data = vec![
TEST_FORBID_LEN_UPDATE_AFTER_OWNERSHIP_CHANGE,
target_account,
42,
42,
];
let instruction = Instruction::new_with_bytes(
invoke_program_id,
&instruction_data,
account_metas.clone(),
);
let message = Message::new(&[instruction], Some(&mint_pubkey));
let tx = Transaction::new(&[&mint_keypair], message.clone(), bank.last_blockhash());
let (result, _, logs) = process_transaction_and_record_inner(&bank, tx);
if direct_mapping {
assert_eq!(
result.unwrap_err(),
TransactionError::InstructionError(
0,
InstructionError::ProgramFailedToComplete
)
);
// We haven't moved the data pointer, but ref_to_len_vm _is_ in
// the account data vm range and that's not allowed either.
assert!(
logs.iter().any(|log| log.contains("Invalid pointer")),
"{logs:?}"
);
} else {
// we expect this to succeed as after updating `ref_to_len_in_vm`,
// CPI will sync the actual account data between the callee and the
// caller, _always_ writing over the location pointed by
// `ref_to_len_in_vm`. To verify this, we check that the account
// data is in fact all zeroes like it is in the callee.
result.unwrap();
let account = bank.get_account(&account_keypair.pubkey()).unwrap();
assert_eq!(account.data(), vec![0; 40]);
}
}
}
}
#[test]
#[cfg(feature = "sbf_rust")]
fn test_cpi_account_data_updates() {
solana_logger::setup();
for direct_mapping in [false, true] {
let GenesisConfigInfo {
genesis_config,
mint_keypair,
..
} = create_genesis_config(100_123_456_789);
let mut bank = Bank::new_for_tests(&genesis_config);
let mut feature_set = FeatureSet::all_enabled();
if !direct_mapping {
feature_set.deactivate(&feature_set::bpf_account_data_direct_mapping::id());
}
bank.feature_set = Arc::new(feature_set);
let bank = Arc::new(bank);
let mut bank_client = BankClient::new_shared(bank);
let invoke_program_id = load_program(
&bank_client,
&bpf_loader::id(),
&mint_keypair,
"solana_sbf_rust_invoke",
);
let (bank, realloc_program_id) = load_program_and_advance_slot(
&mut bank_client,
&bpf_loader::id(),
&mint_keypair,
"solana_sbf_rust_realloc",
);
let account_keypair = Keypair::new();
let mint_pubkey = mint_keypair.pubkey();
let account_metas = vec![
AccountMeta::new(mint_pubkey, true),
AccountMeta::new(account_keypair.pubkey(), false),
AccountMeta::new_readonly(realloc_program_id, false),
AccountMeta::new_readonly(invoke_program_id, false),
];
// This tests the case where a caller extends an account beyond the original
// data length. The callee should see the extended data (asserted in the
// callee program, not here).
let mut account = AccountSharedData::new(42, 0, &invoke_program_id);
account.set_data(b"foo".to_vec());
bank.store_account(&account_keypair.pubkey(), &account);
let mut instruction_data = vec![TEST_CPI_ACCOUNT_UPDATE_CALLER_GROWS];
instruction_data.extend_from_slice(b"bar");
let instruction = Instruction::new_with_bytes(
invoke_program_id,
&instruction_data,
account_metas.clone(),
);
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
assert!(result.is_ok(), "{result:?}");
let account = bank.get_account(&account_keypair.pubkey()).unwrap();
// "bar" here was copied from the realloc region
assert_eq!(account.data(), b"foobar");
// This tests the case where a callee extends an account beyond the original
// data length. The caller should see the extended data where the realloc
// region contains the new data. In this test the callee owns the account,
// the caller can't write but the CPI glue still updates correctly.
let mut account = AccountSharedData::new(42, 0, &realloc_program_id);
account.set_data(b"foo".to_vec());
bank.store_account(&account_keypair.pubkey(), &account);
let mut instruction_data = vec![TEST_CPI_ACCOUNT_UPDATE_CALLEE_GROWS];
instruction_data.extend_from_slice(b"bar");
let instruction = Instruction::new_with_bytes(
invoke_program_id,
&instruction_data,
account_metas.clone(),
);
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
result.unwrap();
let account = bank.get_account(&account_keypair.pubkey()).unwrap();
// "bar" here was copied from the realloc region
assert_eq!(account.data(), b"foobar");
// This tests the case where a callee shrinks an account, the caller data
// slice must be truncated accordingly and post_len..original_data_len must
// be zeroed (zeroing is checked in the invoked program not here). Same as
// above, the callee owns the account but the changes are still reflected in
// the caller even if things are readonly from the caller's POV.
let mut account = AccountSharedData::new(42, 0, &realloc_program_id);
account.set_data(b"foobar".to_vec());
bank.store_account(&account_keypair.pubkey(), &account);
let mut instruction_data =
vec![TEST_CPI_ACCOUNT_UPDATE_CALLEE_SHRINKS_SMALLER_THAN_ORIGINAL_LEN];
instruction_data.extend_from_slice(4usize.to_le_bytes().as_ref());
let instruction = Instruction::new_with_bytes(
invoke_program_id,
&instruction_data,
account_metas.clone(),
);
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
assert!(result.is_ok(), "{result:?}");
let account = bank.get_account(&account_keypair.pubkey()).unwrap();
assert_eq!(account.data(), b"foob");
// This tests the case where the program extends an account, then calls
// itself and in the inner call it shrinks the account to a size that is
// still larger than the original size. The account data must be set to the
// correct value in the caller frame, and the realloc region must be zeroed
// (again tested in the invoked program).
let mut account = AccountSharedData::new(42, 0, &invoke_program_id);
account.set_data(b"foo".to_vec());
bank.store_account(&account_keypair.pubkey(), &account);
let mut instruction_data = vec![TEST_CPI_ACCOUNT_UPDATE_CALLER_GROWS_CALLEE_SHRINKS];
// realloc to "foobazbad" then shrink to "foobazb"
instruction_data.extend_from_slice(7usize.to_le_bytes().as_ref());
instruction_data.extend_from_slice(b"bazbad");
let instruction = Instruction::new_with_bytes(
invoke_program_id,
&instruction_data,
account_metas.clone(),
);
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
assert!(result.is_ok(), "{result:?}");
let account = bank.get_account(&account_keypair.pubkey()).unwrap();
assert_eq!(account.data(), b"foobazb");
// Similar to the test above, but this time the nested invocation shrinks to
// _below_ the original data length. Both the spare capacity in the account
// data _end_ the realloc region must be zeroed.
let mut account = AccountSharedData::new(42, 0, &invoke_program_id);
account.set_data(b"foo".to_vec());
bank.store_account(&account_keypair.pubkey(), &account);
let mut instruction_data = vec![TEST_CPI_ACCOUNT_UPDATE_CALLER_GROWS_CALLEE_SHRINKS];
// realloc to "foobazbad" then shrink to "f"
instruction_data.extend_from_slice(1usize.to_le_bytes().as_ref());
instruction_data.extend_from_slice(b"bazbad");
let instruction = Instruction::new_with_bytes(
invoke_program_id,
&instruction_data,
account_metas.clone(),
);
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
assert!(result.is_ok(), "{result:?}");
let account = bank.get_account(&account_keypair.pubkey()).unwrap();
assert_eq!(account.data(), b"f");
}
}
#[test]
#[cfg(feature = "sbf_rust")]
fn test_cpi_deprecated_loader_realloc() {
solana_logger::setup();
for direct_mapping in [false, true] {
let GenesisConfigInfo {
genesis_config,
mint_keypair,
..
} = create_genesis_config(100_123_456_789);
let mut bank = Bank::new_for_tests(&genesis_config);
let mut feature_set = FeatureSet::all_enabled();
if !direct_mapping {
feature_set.deactivate(&feature_set::bpf_account_data_direct_mapping::id());
}
bank.feature_set = Arc::new(feature_set);
let bank = Arc::new(bank);
let deprecated_program_id = create_program(
&bank,
&bpf_loader_deprecated::id(),
"solana_sbf_rust_deprecated_loader",
);
let mut bank_client = BankClient::new_shared(bank);
let (bank, invoke_program_id) = load_program_and_advance_slot(
&mut bank_client,
&bpf_loader::id(),
&mint_keypair,
"solana_sbf_rust_invoke",
);
let account_keypair = Keypair::new();
let mint_pubkey = mint_keypair.pubkey();
let account_metas = vec![
AccountMeta::new(mint_pubkey, true),
AccountMeta::new(account_keypair.pubkey(), false),
AccountMeta::new_readonly(deprecated_program_id, false),
AccountMeta::new_readonly(deprecated_program_id, false),
AccountMeta::new_readonly(invoke_program_id, false),
];
// If a bpf_loader_deprecated program extends an account, the callee
// accidentally sees the extended data when direct mapping is off, but
// direct mapping fixes the issue
let mut account = AccountSharedData::new(42, 0, &deprecated_program_id);
account.set_data(b"foo".to_vec());
bank.store_account(&account_keypair.pubkey(), &account);
let mut instruction_data = vec![TEST_CPI_ACCOUNT_UPDATE_CALLER_GROWS];
instruction_data.extend_from_slice(b"bar");
let instruction = Instruction::new_with_bytes(
deprecated_program_id,
&instruction_data,
account_metas.clone(),
);
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
// when direct mapping is off, the realloc will accidentally clobber
// whatever comes after the data slice (owner, executable, rent epoch
// etc). When direct mapping is on, you get an InvalidRealloc error.
if direct_mapping {
assert_eq!(
result.unwrap_err().unwrap(),
TransactionError::InstructionError(0, InstructionError::InvalidRealloc)
);
} else {
assert_eq!(
result.unwrap_err().unwrap(),
TransactionError::InstructionError(0, InstructionError::ModifiedProgramId)
);
}
// check that if a bpf_loader_deprecated program extends an account, the
// extended data is ignored
let mut account = AccountSharedData::new(42, 0, &deprecated_program_id);
account.set_data(b"foo".to_vec());
bank.store_account(&account_keypair.pubkey(), &account);
let mut instruction_data = vec![TEST_CPI_ACCOUNT_UPDATE_CALLEE_GROWS];
instruction_data.extend_from_slice(b"bar");
let instruction = Instruction::new_with_bytes(
invoke_program_id,
&instruction_data,
account_metas.clone(),
);
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
assert!(result.is_ok(), "{result:?}");
let account = bank.get_account(&account_keypair.pubkey()).unwrap();
assert_eq!(account.data(), b"foo");
// check that if a bpf_loader_deprecated program truncates an account,
// the caller doesn't see the truncation
let mut account = AccountSharedData::new(42, 0, &deprecated_program_id);
account.set_data(b"foobar".to_vec());
bank.store_account(&account_keypair.pubkey(), &account);
let mut instruction_data =
vec![TEST_CPI_ACCOUNT_UPDATE_CALLEE_SHRINKS_SMALLER_THAN_ORIGINAL_LEN];
instruction_data.extend_from_slice(4usize.to_le_bytes().as_ref());
let instruction = Instruction::new_with_bytes(
invoke_program_id,
&instruction_data,
account_metas.clone(),
);
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
assert!(result.is_ok(), "{result:?}");
let account = bank.get_account(&account_keypair.pubkey()).unwrap();
assert_eq!(account.data(), b"foobar");
}
}
#[test]
#[cfg(feature = "sbf_rust")]
fn test_cpi_change_account_data_memory_allocation() {
use solana_program_runtime::{declare_process_instruction, loaded_programs::LoadedProgram};
solana_logger::setup();
let GenesisConfigInfo {
genesis_config,
mint_keypair,
..
} = create_genesis_config(100_123_456_789);
let mut bank = Bank::new_for_tests(&genesis_config);
let feature_set = FeatureSet::all_enabled();
bank.feature_set = Arc::new(feature_set);
declare_process_instruction!(MockBuiltin, 42, |invoke_context| {
let transaction_context = &invoke_context.transaction_context;
let instruction_context = transaction_context.get_current_instruction_context()?;
let instruction_data = instruction_context.get_instruction_data();
let index_in_transaction =
instruction_context.get_index_of_instruction_account_in_transaction(0)?;
let mut account = transaction_context
.accounts()
.get(index_in_transaction)
.unwrap()
.borrow_mut();
// Test changing the account data both in place and by changing the
// underlying vector. CPI will have to detect the vector change and
// update the corresponding memory region. In both cases CPI will have
// to zero the spare bytes correctly.
if instruction_data[0] == 0xFE {
account.set_data(instruction_data.to_vec());
} else {
account.set_data_from_slice(instruction_data);
}
Ok(())
});
let builtin_program_id = Pubkey::new_unique();
bank.add_builtin(
builtin_program_id,
"test_cpi_change_account_data_memory_allocation_builtin".to_string(),
LoadedProgram::new_builtin(0, 42, MockBuiltin::vm),
);
let bank = Arc::new(bank);
let mut bank_client = BankClient::new_shared(bank);
let (bank, invoke_program_id) = load_program_and_advance_slot(
&mut bank_client,
&bpf_loader::id(),
&mint_keypair,
"solana_sbf_rust_invoke",
);
let account_keypair = Keypair::new();
let mint_pubkey = mint_keypair.pubkey();
let account_metas = vec![
AccountMeta::new(mint_pubkey, true),
AccountMeta::new(account_keypair.pubkey(), false),
AccountMeta::new_readonly(builtin_program_id, false),
AccountMeta::new_readonly(invoke_program_id, false),
];
let mut account = AccountSharedData::new(42, 20, &builtin_program_id);
account.set_data(vec![0xFF; 20]);
bank.store_account(&account_keypair.pubkey(), &account);
let mut instruction_data = vec![TEST_CPI_CHANGE_ACCOUNT_DATA_MEMORY_ALLOCATION];
instruction_data.extend_from_slice(builtin_program_id.as_ref());
let instruction =
Instruction::new_with_bytes(invoke_program_id, &instruction_data, account_metas.clone());
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
assert!(result.is_ok(), "{result:?}");
}
#[test]
#[cfg(feature = "sbf_rust")]
fn test_cpi_invalid_account_info_pointers() {
solana_logger::setup();
let GenesisConfigInfo {
genesis_config,
mint_keypair,
..
} = create_genesis_config(100_123_456_789);
let mut bank = Bank::new_for_tests(&genesis_config);
let feature_set = FeatureSet::all_enabled();
bank.feature_set = Arc::new(feature_set);
let bank = Arc::new(bank);
let mut bank_client = BankClient::new_shared(bank);
let c_invoke_program_id =
load_program(&bank_client, &bpf_loader::id(), &mint_keypair, "invoke");
let (bank, invoke_program_id) = load_program_and_advance_slot(
&mut bank_client,
&bpf_loader::id(),
&mint_keypair,
"solana_sbf_rust_invoke",
);
let account_keypair = Keypair::new();
let mint_pubkey = mint_keypair.pubkey();
let account_metas = vec![
AccountMeta::new(mint_pubkey, true),
AccountMeta::new(account_keypair.pubkey(), false),
AccountMeta::new_readonly(invoke_program_id, false),
AccountMeta::new_readonly(c_invoke_program_id, false),
];
for invoke_program_id in [invoke_program_id, c_invoke_program_id] {
for ix in [
TEST_CPI_INVALID_KEY_POINTER,
TEST_CPI_INVALID_LAMPORTS_POINTER,
TEST_CPI_INVALID_OWNER_POINTER,
TEST_CPI_INVALID_DATA_POINTER,
] {
let account = AccountSharedData::new(42, 5, &invoke_program_id);
bank.store_account(&account_keypair.pubkey(), &account);
let instruction = Instruction::new_with_bytes(
invoke_program_id,
&[ix, 42, 42, 42],
account_metas.clone(),
);
let message = Message::new(&[instruction], Some(&mint_pubkey));
let tx = Transaction::new(&[&mint_keypair], message.clone(), bank.last_blockhash());
let (result, _, logs) = process_transaction_and_record_inner(&bank, tx);
assert!(result.is_err(), "{result:?}");
assert!(
logs.iter().any(|log| log.contains("Invalid pointer")),
"{logs:?}"
);
}
}
}
#[test]
#[cfg(feature = "sbf_rust")]
fn test_deny_executable_write() {
solana_logger::setup();
let GenesisConfigInfo {
genesis_config,
mint_keypair,
..
} = create_genesis_config(100_123_456_789);
for direct_mapping in [false, true] {
let mut bank = Bank::new_for_tests(&genesis_config);
let feature_set = Arc::make_mut(&mut bank.feature_set);
// by default test banks have all features enabled, so we only need to
// disable when needed
if !direct_mapping {
feature_set.deactivate(&feature_set::bpf_account_data_direct_mapping::id());
}
let bank = Arc::new(bank);
let mut bank_client = BankClient::new_shared(bank);
let (_bank, invoke_program_id) = load_program_and_advance_slot(
&mut bank_client,
&bpf_loader::id(),
&mint_keypair,
"solana_sbf_rust_invoke",
);
let account_keypair = Keypair::new();
let mint_pubkey = mint_keypair.pubkey();
let account_metas = vec![
AccountMeta::new(mint_pubkey, true),
AccountMeta::new(account_keypair.pubkey(), false),
AccountMeta::new_readonly(invoke_program_id, false),
];
let mut instruction_data = vec![TEST_WRITE_ACCOUNT, 2];
instruction_data.extend_from_slice(4usize.to_le_bytes().as_ref());
instruction_data.push(42);
let instruction = Instruction::new_with_bytes(
invoke_program_id,
&instruction_data,
account_metas.clone(),
);
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
assert_eq!(
result.unwrap_err().unwrap(),
TransactionError::InstructionError(0, InstructionError::ExecutableDataModified)
);
}
}