#![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, 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::{ map_inner_instructions, ConfirmedTransactionWithStatusMeta, TransactionStatusMeta, TransactionWithStatusMeta, VersionedTransactionWithStatusMeta, }, std::collections::HashMap, }; use { solana_program_runtime::invoke_context::mock_process_instruction, solana_runtime::{ bank::Bank, bank_client::BankClient, bank_forks::BankForks, 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, RwLock}, time::Duration, }, }; #[cfg(feature = "sbf_rust")] fn process_transaction_and_record_inner( bank: &Bank, tx: Transaction, ) -> ( Result<(), TransactionError>, Vec>, Vec, ) { let signature = tx.signatures.first().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, ) -> Vec> { 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| { map_inner_instructions(inner_instructions).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, bank_forks: &RwLock, loader_id: &Pubkey, payer_keypair: &Keypair, name: &str, ) -> (Arc, Pubkey) { let pubkey = load_program(bank_client, loader_id, payer_keypair, name); ( bank_client .advance_slot(1, bank_forks, &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 { mut genesis_config, mint_keypair, .. } = create_genesis_config(50); // deactivate `disable_bpf_loader_instructions` feature so that the program // can be loaded, finalized and tested. genesis_config .accounts .remove(&feature_set::disable_bpf_loader_instructions::id()); genesis_config .accounts .remove(&feature_set::deprecate_executable_meta_update_in_bpf_loader::id()); let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let mut bank_client = BankClient::new_shared(bank); // Call user program let (_, program_id) = load_program_and_advance_slot( &mut bank_client, bank_forks.as_ref(), &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_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let program_id = create_program(&bank, &bpf_loader_deprecated::id(), program); let mut bank_client = BankClient::new_shared(bank); bank_client .advance_slot(1, bank_forks.as_ref(), &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 { mut genesis_config, mint_keypair, .. } = create_genesis_config(50); // deactivate `disable_bpf_loader_instructions` feature so that the program // can be loaded, finalized and tested. genesis_config .accounts .remove(&feature_set::disable_bpf_loader_instructions::id()); genesis_config .accounts .remove(&feature_set::deprecate_executable_meta_update_in_bpf_loader::id()); let (bank, bank_forks) = Bank::new_with_bank_forks_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 let slot = bank.slot(); drop(bank); let mut bank = Arc::into_inner(bank_forks.write().unwrap().remove(slot).unwrap()).unwrap(); bank.deactivate_feature(&solana_sdk::feature_set::disable_deploy_of_alloc_free_syscall::id()); bank.clear_signatures(); bank.clear_program_cache(); let bank = bank_forks .write() .unwrap() .insert(bank) .clone_without_scheduler(); // 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 bank = bank_forks .write() .unwrap() .insert(Bank::new_from_parent(bank, &Pubkey::default(), new_slot)) .clone_without_scheduler(); // invoke the program assert!(bank.process_transaction(&invoke_tx).is_ok()); // disable _sol_alloc_free let slot = bank.slot(); drop(bank); let mut bank = Arc::try_unwrap(bank_forks.write().unwrap().remove(slot).unwrap()).unwrap(); bank.activate_feature(&solana_sdk::feature_set::disable_deploy_of_alloc_free_syscall::id()); bank.clear_signatures(); let bank = bank_forks .write() .unwrap() .insert(bank) .clone_without_scheduler(); // 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 { mut genesis_config, mint_keypair, .. } = create_genesis_config(50); // deactivate `disable_bpf_loader_instructions` feature so that the program // can be loaded, finalized and tested. genesis_config .accounts .remove(&feature_set::disable_bpf_loader_instructions::id()); genesis_config .accounts .remove(&feature_set::deprecate_executable_meta_update_in_bpf_loader::id()); let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let mut bank_client = BankClient::new_shared(bank.clone()); let (bank, program_id) = load_program_and_advance_slot( &mut bank_client, bank_forks.as_ref(), &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 { mut genesis_config, mint_keypair, .. } = create_genesis_config(50); // deactivate `disable_bpf_loader_instructions` feature so that the program // can be loaded, finalized and tested. genesis_config .accounts .remove(&feature_set::disable_bpf_loader_instructions::id()); genesis_config .accounts .remove(&feature_set::deprecate_executable_meta_update_in_bpf_loader::id()); let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let mut bank_client = BankClient::new_shared(bank); let (_, program_id) = load_program_and_advance_slot( &mut bank_client, bank_forks.as_ref(), &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 { mut genesis_config, mint_keypair, .. } = create_genesis_config(50); // deactivate `disable_bpf_loader_instructions` feature so that the program // can be loaded, finalized and tested. genesis_config .accounts .remove(&feature_set::disable_bpf_loader_instructions::id()); genesis_config .accounts .remove(&feature_set::deprecate_executable_meta_update_in_bpf_loader::id()); let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let mut bank_client = BankClient::new_shared(bank.clone()); let (bank, program_id) = load_program_and_advance_slot( &mut bank_client, bank_forks.as_ref(), &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, false); 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 { mut genesis_config, mint_keypair, .. } = create_genesis_config(50); // deactivate `disable_bpf_loader_instructions` feature so that the program // can be loaded, finalized and tested. genesis_config .accounts .remove(&feature_set::disable_bpf_loader_instructions::id()); genesis_config .accounts .remove(&feature_set::deprecate_executable_meta_update_in_bpf_loader::id()); let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); 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, bank_forks.as_ref(), &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 = 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 = 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>| { 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 = 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 = 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 { mut genesis_config, mint_keypair, .. } = create_genesis_config(50); // deactivate `disable_bpf_loader_instructions` feature so that the program // can be loaded, finalized and tested. genesis_config .accounts .remove(&feature_set::disable_bpf_loader_instructions::id()); genesis_config .accounts .remove(&feature_set::deprecate_executable_meta_update_in_bpf_loader::id()); let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); 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, bank_forks.as_ref(), &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 { mut genesis_config, mint_keypair, .. } = create_genesis_config(50); // deactivate `disable_bpf_loader_instructions` feature so that the program // can be loaded, finalized and tested. genesis_config .accounts .remove(&feature_set::disable_bpf_loader_instructions::id()); genesis_config .accounts .remove(&feature_set::deprecate_executable_meta_update_in_bpf_loader::id()); let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); 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, bank_forks.as_ref(), &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 { mut genesis_config, mint_keypair, .. } = create_genesis_config(50); // deactivate `disable_bpf_loader_instructions` feature so that the program // can be loaded, finalized and tested. genesis_config .accounts .remove(&feature_set::disable_bpf_loader_instructions::id()); genesis_config .accounts .remove(&feature_set::deprecate_executable_meta_update_in_bpf_loader::id()); let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let mut bank_client = BankClient::new_shared(bank.clone()); let (bank, program_pubkey) = load_program_and_advance_slot( &mut bank_client, bank_forks.as_ref(), &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 { mut genesis_config, mint_keypair, .. } = create_genesis_config(50); // deactivate `disable_bpf_loader_instructions` feature so that the program // can be loaded, finalized and tested. genesis_config .accounts .remove(&feature_set::disable_bpf_loader_instructions::id()); genesis_config .accounts .remove(&feature_set::deprecate_executable_meta_update_in_bpf_loader::id()); let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let mut bank_client = BankClient::new_shared(bank); let (_, program_id) = load_program_and_advance_slot( &mut bank_client, bank_forks.as_ref(), &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 { mut genesis_config, mint_keypair, .. } = create_genesis_config(50); // deactivate `disable_bpf_loader_instructions` feature so that the program // can be loaded, finalized and tested. genesis_config .accounts .remove(&feature_set::disable_bpf_loader_instructions::id()); genesis_config .accounts .remove(&feature_set::deprecate_executable_meta_update_in_bpf_loader::id()); let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let mut bank_client = BankClient::new_shared(bank); let (_, program_id) = load_program_and_advance_slot( &mut bank_client, bank_forks.as_ref(), &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 { mut genesis_config, mint_keypair, .. } = create_genesis_config(50_000); // deactivate `disable_bpf_loader_instructions` feature so that the program // can be loaded, finalized and tested. genesis_config .accounts .remove(&feature_set::disable_bpf_loader_instructions::id()); genesis_config .accounts .remove(&feature_set::deprecate_executable_meta_update_in_bpf_loader::id()); let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let mut bank_client = BankClient::new_shared(bank.clone()); let (bank, program_id) = load_program_and_advance_slot( &mut bank_client, bank_forks.as_ref(), &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 { mut genesis_config, mint_keypair, .. } = create_genesis_config(50); // deactivate `disable_bpf_loader_instructions` feature so that the program // can be loaded, finalized and tested. genesis_config .accounts .remove(&feature_set::disable_bpf_loader_instructions::id()); genesis_config .accounts .remove(&feature_set::deprecate_executable_meta_update_in_bpf_loader::id()); let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let mut bank_client = BankClient::new_shared(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, bank_forks.as_ref(), &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, bank_forks.as_ref(), &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, bank_forks.as_ref(), &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_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let mut bank_client = BankClient::new_shared(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, bank_forks.as_ref(), &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, bank_forks.as_ref(), &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, bank_forks.as_ref(), &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_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); 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, bank_forks.as_ref(), &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_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); 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. They need to be called individually to create the correct fork graph in between. bank_client .advance_slot(1, bank_forks.as_ref(), &Pubkey::default()) .unwrap(); let bank = bank_client .advance_slot(1, bank_forks.as_ref(), &Pubkey::default()) .unwrap(); // 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_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); 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. They need to be called individually to create the correct fork graph in between. bank_client .advance_slot(1, bank_forks.as_ref(), &Pubkey::default()) .unwrap(); let bank = bank_client .advance_slot(1, bank_forks.as_ref(), &Pubkey::default()) .unwrap(); // 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 { mut genesis_config, mint_keypair, .. } = create_genesis_config(50); // deactivate `disable_bpf_loader_instructions` feature so that the program // can be loaded, finalized and tested. genesis_config .accounts .remove(&feature_set::disable_bpf_loader_instructions::id()); genesis_config .accounts .remove(&feature_set::deprecate_executable_meta_update_in_bpf_loader::id()); let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let mut bank_client = BankClient::new_shared(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, bank_forks.as_ref(), &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, bank_forks.as_ref(), &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, bank_forks.as_ref(), &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); bank.deactivate_feature( &solana_sdk::feature_set::remove_bpf_loader_incorrect_program_id::id(), ); bank.deactivate_feature(&feature_set::disable_bpf_loader_instructions::id()); bank.deactivate_feature(&feature_set::deprecate_executable_meta_update_in_bpf_loader::id()); let bank = bank.wrap_with_bank_forks_for_tests().0; let bank_client = BankClient::new_shared(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 { mut genesis_config, mint_keypair, .. } = create_genesis_config(50); // deactivate `disable_bpf_loader_instructions` feature so that the program // can be loaded, finalized and tested. genesis_config .accounts .remove(&feature_set::disable_bpf_loader_instructions::id()); genesis_config .accounts .remove(&feature_set::deprecate_executable_meta_update_in_bpf_loader::id()); let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let mut bank_client = BankClient::new_shared(bank); let (_, program_id) = load_program_and_advance_slot( &mut bank_client, bank_forks.as_ref(), &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 { mut genesis_config, mint_keypair, .. } = create_genesis_config(50); // deactivate `disable_bpf_loader_instructions` feature so that the program // can be loaded, finalized and tested. genesis_config .accounts .remove(&feature_set::disable_bpf_loader_instructions::id()); genesis_config .accounts .remove(&feature_set::deprecate_executable_meta_update_in_bpf_loader::id()); let (bank, bank_forks) = Bank::new_with_bank_forks_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_shared(bank); let (_, program_id) = load_program_and_advance_slot( &mut bank_client, bank_forks.as_ref(), &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 { mut genesis_config, mint_keypair, .. } = create_genesis_config(50); // deactivate `disable_bpf_loader_instructions` feature so that the program // can be loaded, finalized and tested. genesis_config .accounts .remove(&feature_set::disable_bpf_loader_instructions::id()); genesis_config .accounts .remove(&feature_set::deprecate_executable_meta_update_in_bpf_loader::id()); let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let mut bank_client = BankClient::new_shared(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, bank_forks.as_ref(), &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, bank_forks.as_ref(), &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 { mut genesis_config, mint_keypair, .. } = create_genesis_config(50); // deactivate `disable_bpf_loader_instructions` feature so that the program // can be loaded, finalized and tested. genesis_config .accounts .remove(&feature_set::disable_bpf_loader_instructions::id()); genesis_config .accounts .remove(&feature_set::deprecate_executable_meta_update_in_bpf_loader::id()); let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let mut bank_client = BankClient::new_shared(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, bank_forks.as_ref(), &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, Transaction, Transaction) { solana_logger::setup(); let GenesisConfigInfo { genesis_config, mint_keypair, .. } = create_genesis_config(2_000_000_000); let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); 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, bank_forks.as_ref(), &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 { mut genesis_config, mint_keypair, .. } = create_genesis_config(50); // deactivate `disable_bpf_loader_instructions` feature so that the program // can be loaded, finalized and tested. genesis_config .accounts .remove(&feature_set::disable_bpf_loader_instructions::id()); genesis_config .accounts .remove(&feature_set::deprecate_executable_meta_update_in_bpf_loader::id()); let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let mut bank_client = BankClient::new_shared(bank.clone()); let (_, program_pubkey) = load_program_and_advance_slot( &mut bank_client, bank_forks.as_ref(), &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, bank_forks.as_ref(), &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 { mut genesis_config, mint_keypair, .. } = create_genesis_config(50); // deactivate `disable_bpf_loader_instructions` feature so that the program // can be loaded, finalized and tested. genesis_config .accounts .remove(&feature_set::disable_bpf_loader_instructions::id()); genesis_config .accounts .remove(&feature_set::deprecate_executable_meta_update_in_bpf_loader::id()); let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let mut bank_client = BankClient::new_shared(bank.clone()); let (bank, program_id) = load_program_and_advance_slot( &mut bank_client, bank_forks.as_ref(), &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 { mut genesis_config, mint_keypair, .. } = create_genesis_config(1_000_000_000_000); // deactivate `disable_bpf_loader_instructions` feature so that the program // can be loaded, finalized and tested. genesis_config .accounts .remove(&feature_set::disable_bpf_loader_instructions::id()); genesis_config .accounts .remove(&feature_set::deprecate_executable_meta_update_in_bpf_loader::id()); 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, bank_forks) = bank.wrap_with_bank_forks_for_tests(); let mut bank_client = BankClient::new_shared(bank.clone()); let (bank, program_id) = load_program_and_advance_slot( &mut bank_client, bank_forks.as_ref(), &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::() }); // 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(); // deactivate `disable_bpf_loader_instructions` feature so that the program // can be loaded, finalized and tested. genesis_config .accounts .remove(&feature_set::disable_bpf_loader_instructions::id()); genesis_config .accounts .remove(&feature_set::deprecate_executable_meta_update_in_bpf_loader::id()); let mint_pubkey = mint_keypair.pubkey(); let signer = &[&mint_keypair]; let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); 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, bank_forks.as_ref(), &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 { mut genesis_config, mint_keypair, .. } = create_genesis_config(50); // deactivate `disable_bpf_loader_instructions` feature so that the program // can be loaded, finalized and tested. genesis_config .accounts .remove(&feature_set::disable_bpf_loader_instructions::id()); genesis_config .accounts .remove(&feature_set::deprecate_executable_meta_update_in_bpf_loader::id()); let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); 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, bank_forks.as_ref(), &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); // deactivate `disable_bpf_loader_instructions` feature so that the program // can be loaded, finalized and tested. genesis_config .accounts .remove(&feature_set::disable_bpf_loader_instructions::id()); genesis_config .accounts .remove(&feature_set::deprecate_executable_meta_update_in_bpf_loader::id()); 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(); let (bank, bank_forks) = bank.wrap_with_bank_forks_for_tests(); let mut bank_client = BankClient::new_shared(bank); let (_, program_id) = load_program_and_advance_slot( &mut bank_client, bank_forks.as_ref(), &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 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()) .unwrap_or_default() .into(), 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 expected_prioritized_fee = fee_structure.calculate_fee( &sanitized_message, congestion_multiplier, &process_compute_budget_instructions(sanitized_message.program_instructions_iter()) .unwrap_or_default() .into(), 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 { mut genesis_config, mint_keypair, .. } = create_genesis_config(100_123_456_789); // deactivate `disable_bpf_loader_instructions` feature so that the program // can be loaded, finalized and tested. genesis_config .accounts .remove(&feature_set::disable_bpf_loader_instructions::id()); genesis_config .accounts .remove(&feature_set::deprecate_executable_meta_update_in_bpf_loader::id()); let bank = Bank::new_for_tests(&genesis_config); let (bank, bank_forks) = bank.wrap_with_bank_forks_for_tests(); let mut bank_client = BankClient::new_shared(bank.clone()); let (_, program_id) = load_program_and_advance_slot( &mut bank_client, bank_forks.as_ref(), &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_forks) = Bank::new_with_bank_forks_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_shared(bank); bank_client .advance_slot(1, bank_forks.as_ref(), &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()); } // deactivate `disable_bpf_loader_instructions` feature so that the program // can be loaded, finalized and tested. feature_set.deactivate(&feature_set::disable_bpf_loader_instructions::id()); feature_set.deactivate(&feature_set::deprecate_executable_meta_update_in_bpf_loader::id()); bank.feature_set = Arc::new(feature_set); let (bank, bank_forks) = bank.wrap_with_bank_forks_for_tests(); 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, bank_forks.as_ref(), &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()); } // deactivate `disable_bpf_loader_instructions` feature so that the program // can be loaded, finalized and tested. feature_set.deactivate(&feature_set::disable_bpf_loader_instructions::id()); feature_set.deactivate(&feature_set::deprecate_executable_meta_update_in_bpf_loader::id()); bank.feature_set = Arc::new(feature_set); let (bank, bank_forks) = bank.wrap_with_bank_forks_for_tests(); 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, bank_forks.as_ref(), &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()); } // deactivate `disable_bpf_loader_instructions` feature so that the program // can be loaded, finalized and tested. feature_set.deactivate(&feature_set::disable_bpf_loader_instructions::id()); feature_set.deactivate(&feature_set::deprecate_executable_meta_update_in_bpf_loader::id()); bank.feature_set = Arc::new(feature_set); let (bank, bank_forks) = bank.wrap_with_bank_forks_for_tests(); 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, bank_forks.as_ref(), &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 { mut genesis_config, mint_keypair, .. } = create_genesis_config(100_123_456_789); // deactivate `disable_bpf_loader_instructions` feature so that the program // can be loaded, finalized and tested. genesis_config .accounts .remove(&feature_set::disable_bpf_loader_instructions::id()); genesis_config .accounts .remove(&feature_set::deprecate_executable_meta_update_in_bpf_loader::id()); let mut bank = Bank::new_for_tests(&genesis_config); 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 all cases CPI will have // to zero the spare bytes correctly. match instruction_data[0] { 0xFE => account.set_data(instruction_data.to_vec()), 0xFD => account.set_data_from_slice(instruction_data), 0xFC => { // Exercise the update_caller_account capacity check where account len != capacity. let mut data = instruction_data.to_vec(); data.reserve_exact(1); account.set_data(data) } _ => panic!(), } 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, bank_forks) = bank.wrap_with_bank_forks_for_tests(); let mut bank_client = BankClient::new_shared(bank); let (bank, invoke_program_id) = load_program_and_advance_slot( &mut bank_client, bank_forks.as_ref(), &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 { mut genesis_config, mint_keypair, .. } = create_genesis_config(100_123_456_789); // deactivate `disable_bpf_loader_instructions` feature so that the program // can be loaded, finalized and tested. genesis_config .accounts .remove(&feature_set::disable_bpf_loader_instructions::id()); genesis_config .accounts .remove(&feature_set::deprecate_executable_meta_update_in_bpf_loader::id()); let bank = Bank::new_for_tests(&genesis_config); let (bank, bank_forks) = bank.wrap_with_bank_forks_for_tests(); 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, bank_forks.as_ref(), &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 { mut genesis_config, mint_keypair, .. } = create_genesis_config(100_123_456_789); // deactivate `disable_bpf_loader_instructions` feature so that the program // can be loaded, finalized and tested. genesis_config .accounts .remove(&feature_set::disable_bpf_loader_instructions::id()); genesis_config .accounts .remove(&feature_set::deprecate_executable_meta_update_in_bpf_loader::id()); 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, bank_forks) = bank.wrap_with_bank_forks_for_tests(); let mut bank_client = BankClient::new_shared(bank); let (_bank, invoke_program_id) = load_program_and_advance_slot( &mut bank_client, bank_forks.as_ref(), &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) ); } }