#![cfg(any(feature = "bpf_c", feature = "bpf_rust"))] #[macro_use] extern crate solana_bpf_loader_program; use itertools::izip; use log::{log_enabled, trace, Level::Trace}; use solana_account_decoder::parse_bpf_loader::{ parse_bpf_upgradeable_loader, BpfUpgradeableLoaderAccountType, }; use solana_bpf_loader_program::{ create_vm, serialization::{deserialize_parameters, serialize_parameters}, syscalls::register_syscalls, BpfError, ThisInstructionMeter, }; use solana_cli_output::display::println_transaction; use solana_rbpf::{ static_analysis::Analysis, vm::{Config, Executable, Tracer}, }; use solana_runtime::{ bank::{Bank, ExecuteTimings, NonceRollbackInfo, TransactionBalancesSet, TransactionResults}, bank_client::BankClient, genesis_utils::{create_genesis_config, GenesisConfigInfo}, loader_utils::{ load_buffer_account, load_program, load_upgradeable_program, set_upgrade_authority, upgrade_program, }, }; use solana_sdk::{ account::{AccountSharedData, ReadableAccount}, account_utils::StateMut, bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable, client::SyncClient, clock::MAX_PROCESSING_AGE, entrypoint::{MAX_PERMITTED_DATA_INCREASE, SUCCESS}, instruction::{AccountMeta, CompiledInstruction, Instruction, InstructionError}, keyed_account::KeyedAccount, message::Message, process_instruction::{InvokeContext, MockInvokeContext}, pubkey::Pubkey, signature::{keypair_from_seed, Keypair, Signer}, system_instruction, sysvar::{clock, fees, rent}, transaction::{Transaction, TransactionError}, }; use solana_transaction_status::{ token_balances::collect_token_balances, ConfirmedTransaction, InnerInstructions, TransactionStatusMeta, TransactionWithStatusMeta, UiTransactionEncoding, }; use std::{ cell::RefCell, collections::HashMap, env, fs::File, io::Read, path::PathBuf, str::FromStr, sync::Arc, }; /// BPF program file extension const PLATFORM_FILE_EXTENSION_BPF: &str = "so"; /// Create a BPF program file name fn create_bpf_path(name: &str) -> PathBuf { let mut pathbuf = { let current_exe = env::current_exe().unwrap(); PathBuf::from(current_exe.parent().unwrap().parent().unwrap()) }; pathbuf.push("bpf/"); pathbuf.push(name); pathbuf.set_extension(PLATFORM_FILE_EXTENSION_BPF); pathbuf } fn load_bpf_program( bank_client: &BankClient, loader_id: &Pubkey, payer_keypair: &Keypair, name: &str, ) -> Pubkey { let elf = read_bpf_program(name); load_program(bank_client, payer_keypair, loader_id, elf) } fn read_bpf_program(name: &str) -> Vec { let path = create_bpf_path(name); let mut file = File::open(&path).unwrap_or_else(|err| { panic!("Failed to open {}: {}", path.display(), err); }); let mut elf = Vec::new(); file.read_to_end(&mut elf).unwrap(); elf } #[cfg(feature = "bpf_rust")] fn write_bpf_program( bank_client: &BankClient, loader_id: &Pubkey, payer_keypair: &Keypair, program_keypair: &Keypair, elf: &[u8], ) { use solana_sdk::loader_instruction; let chunk_size = 256; // Size of chunk just needs to fit into tx let mut offset = 0; for chunk in elf.chunks(chunk_size) { let instruction = loader_instruction::write(&program_keypair.pubkey(), loader_id, offset, chunk.to_vec()); let message = Message::new(&[instruction], Some(&payer_keypair.pubkey())); bank_client .send_and_confirm_message(&[payer_keypair, &program_keypair], message) .unwrap(); offset += chunk_size as u32; } } fn load_upgradeable_bpf_program( bank_client: &BankClient, payer_keypair: &Keypair, buffer_keypair: &Keypair, executable_keypair: &Keypair, authority_keypair: &Keypair, name: &str, ) { let path = create_bpf_path(name); let mut file = File::open(&path).unwrap_or_else(|err| { panic!("Failed to open {}: {}", path.display(), err); }); let mut elf = Vec::new(); file.read_to_end(&mut elf).unwrap(); load_upgradeable_program( bank_client, payer_keypair, buffer_keypair, executable_keypair, authority_keypair, elf, ); } fn load_upgradeable_buffer( bank_client: &BankClient, payer_keypair: &Keypair, buffer_keypair: &Keypair, buffer_authority_keypair: &Keypair, name: &str, ) { let path = create_bpf_path(name); let mut file = File::open(&path).unwrap_or_else(|err| { panic!("Failed to open {}: {}", path.display(), err); }); let mut elf = Vec::new(); file.read_to_end(&mut elf).unwrap(); load_buffer_account( bank_client, payer_keypair, &buffer_keypair, buffer_authority_keypair, &elf, ); } fn upgrade_bpf_program( bank_client: &BankClient, payer_keypair: &Keypair, buffer_keypair: &Keypair, executable_pubkey: &Pubkey, authority_keypair: &Keypair, name: &str, ) { load_upgradeable_buffer( bank_client, payer_keypair, buffer_keypair, authority_keypair, name, ); upgrade_program( bank_client, payer_keypair, executable_pubkey, &buffer_keypair.pubkey(), &authority_keypair, &payer_keypair.pubkey(), ); } fn run_program( name: &str, program_id: &Pubkey, parameter_accounts: Vec, instruction_data: &[u8], ) -> Result { let path = create_bpf_path(name); let mut file = File::open(path).unwrap(); let mut data = vec![]; file.read_to_end(&mut data).unwrap(); let loader_id = bpf_loader::id(); let parameter_bytes = serialize_parameters( &bpf_loader::id(), program_id, ¶meter_accounts, &instruction_data, ) .unwrap(); let mut invoke_context = MockInvokeContext::new(parameter_accounts); let compute_meter = invoke_context.get_compute_meter(); let mut instruction_meter = ThisInstructionMeter { compute_meter }; let config = Config { max_call_depth: 20, stack_frame_size: 4096, enable_instruction_meter: true, enable_instruction_tracing: true, }; let mut executable = >::from_elf(&data, None, config).unwrap(); executable.set_syscall_registry(register_syscalls(&mut invoke_context).unwrap()); executable.jit_compile().unwrap(); let mut instruction_count = 0; let mut tracer = None; for i in 0..2 { let mut parameter_bytes = parameter_bytes.clone(); { let mut vm = create_vm( &loader_id, executable.as_ref(), parameter_bytes.as_slice_mut(), &mut invoke_context, ) .unwrap(); let result = if i == 0 { vm.execute_program_interpreted(&mut instruction_meter) } else { vm.execute_program_jit(&mut instruction_meter) }; assert_eq!(SUCCESS, result.unwrap()); if i == 1 { assert_eq!(instruction_count, vm.get_total_instruction_count()); } instruction_count = vm.get_total_instruction_count(); if config.enable_instruction_tracing { if i == 1 { if !Tracer::compare(tracer.as_ref().unwrap(), vm.get_tracer()) { let analysis = Analysis::from_executable(executable.as_ref()); let stdout = std::io::stdout(); println!("TRACE (interpreted):"); tracer .as_ref() .unwrap() .write(&mut stdout.lock(), &analysis) .unwrap(); println!("TRACE (jit):"); vm.get_tracer() .write(&mut stdout.lock(), &analysis) .unwrap(); assert!(false); } else if log_enabled!(Trace) { let analysis = Analysis::from_executable(executable.as_ref()); let mut trace_buffer = Vec::::new(); tracer .as_ref() .unwrap() .write(&mut trace_buffer, &analysis) .unwrap(); let trace_string = String::from_utf8(trace_buffer).unwrap(); trace!("BPF Program Instruction Trace:\n{}", trace_string); } } tracer = Some(vm.get_tracer().clone()); } } let parameter_accounts = invoke_context.get_keyed_accounts().unwrap(); deserialize_parameters( &bpf_loader::id(), parameter_accounts, parameter_bytes.as_slice(), ) .unwrap(); } Ok(instruction_count) } fn process_transaction_and_record_inner( bank: &Bank, tx: Transaction, ) -> (Result<(), TransactionError>, Vec>) { let signature = tx.signatures.get(0).unwrap().clone(); let txs = vec![tx]; let tx_batch = bank.prepare_batch(txs.iter()); let (mut results, _, mut inner, _transaction_logs) = bank.load_execute_and_commit_transactions( &tx_batch, MAX_PROCESSING_AGE, false, true, false, &mut ExecuteTimings::default(), ); let inner_instructions = if inner.is_empty() { Some(vec![vec![]]) } else { inner.swap_remove(0) }; let result = results .fee_collection_results .swap_remove(0) .and_then(|_| bank.get_signature_status(&signature).unwrap()); ( result, inner_instructions.expect("cpi recording should be enabled"), ) } fn execute_transactions(bank: &Bank, txs: &[Transaction]) -> Vec { let batch = bank.prepare_batch(txs.iter()); 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, .. }, mut inner_instructions, mut transaction_logs, ) = bank.load_execute_and_commit_transactions( &batch, std::usize::MAX, true, true, true, &mut timings, ); let tx_post_token_balances = collect_token_balances(&bank, &batch, &mut mint_decimals); for _ in 0..(txs.len() - transaction_logs.len()) { transaction_logs.push(vec![]); } for _ in 0..(txs.len() - inner_instructions.len()) { inner_instructions.push(None); } izip!( txs.iter(), execution_results.into_iter(), inner_instructions.into_iter(), pre_balances.into_iter(), post_balances.into_iter(), tx_pre_token_balances.into_iter(), tx_post_token_balances.into_iter(), transaction_logs.into_iter(), ) .map( |( tx, (execute_result, nonce_rollback), inner_instructions, pre_balances, post_balances, pre_token_balances, post_token_balances, log_messages, )| { let fee_calculator = nonce_rollback .map(|nonce_rollback| nonce_rollback.fee_calculator()) .unwrap_or_else(|| bank.get_fee_calculator(&tx.message().recent_blockhash)) .expect("FeeCalculator must exist"); let fee = fee_calculator.calculate_fee(tx.message()); let inner_instructions = inner_instructions.map(|inner_instructions| { inner_instructions .into_iter() .enumerate() .map(|(index, instructions)| InnerInstructions { index: index as u8, instructions, }) .filter(|i| !i.instructions.is_empty()) .collect() }); let tx_status_meta = TransactionStatusMeta { status: execute_result, fee, pre_balances, post_balances, pre_token_balances: Some(pre_token_balances), post_token_balances: Some(post_token_balances), inner_instructions, log_messages: Some(log_messages), rewards: None, }; ConfirmedTransaction { slot: bank.slot(), transaction: TransactionWithStatusMeta { transaction: tx.clone(), meta: Some(tx_status_meta), }, block_time: None, } }, ) .collect() } fn print_confirmed_tx(name: &str, confirmed_tx: ConfirmedTransaction) { let block_time = confirmed_tx.block_time; let tx = confirmed_tx.transaction.transaction.clone(); let encoded = confirmed_tx.encode(UiTransactionEncoding::JsonParsed); println!("EXECUTE {} (slot {})", name, encoded.slot); println_transaction(&tx, &encoded.transaction.meta, " ", None, block_time); } #[test] #[cfg(any(feature = "bpf_c", feature = "bpf_rust"))] fn test_program_bpf_sanity() { solana_logger::setup(); let mut programs = Vec::new(); #[cfg(feature = "bpf_c")] { programs.extend_from_slice(&[ ("alloc", true), ("bpf_to_bpf", true), ("float", true), ("multiple_static", true), ("noop", true), ("noop++", true), ("panic", false), ("relative_call", true), ("sanity", true), ("sanity++", true), ("sha", true), ("struct_pass", true), ("struct_ret", true), ]); } #[cfg(feature = "bpf_rust")] { programs.extend_from_slice(&[ ("solana_bpf_rust_128bit", true), ("solana_bpf_rust_alloc", true), ("solana_bpf_rust_custom_heap", true), ("solana_bpf_rust_dep_crate", true), ("solana_bpf_rust_external_spend", false), ("solana_bpf_rust_iter", true), ("solana_bpf_rust_many_args", true), ("solana_bpf_rust_membuiltins", true), ("solana_bpf_rust_noop", true), ("solana_bpf_rust_panic", false), ("solana_bpf_rust_param_passing", true), ("solana_bpf_rust_rand", true), ("solana_bpf_rust_sanity", true), ("solana_bpf_rust_sha", true), ]); } for program in programs.iter() { println!("Test program: {:?}", program.0); let GenesisConfigInfo { genesis_config, mint_keypair, .. } = create_genesis_config(50); let mut bank = Bank::new(&genesis_config); let (name, id, entrypoint) = solana_bpf_loader_program!(); bank.add_builtin(&name, id, entrypoint); let bank_client = BankClient::new(bank); // Call user program let program_id = load_bpf_program(&bank_client, &bpf_loader::id(), &mint_keypair, program.0); let account_metas = vec![ AccountMeta::new(mint_keypair.pubkey(), true), AccountMeta::new(Keypair::new().pubkey(), false), ]; let instruction = Instruction::new_with_bytes(program_id, &[1], account_metas); let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); if program.1 { assert!(result.is_ok()); } else { assert!(result.is_err()); } } } #[test] #[cfg(any(feature = "bpf_c", feature = "bpf_rust"))] fn test_program_bpf_loader_deprecated() { solana_logger::setup(); let mut programs = Vec::new(); #[cfg(feature = "bpf_c")] { programs.extend_from_slice(&[("deprecated_loader")]); } #[cfg(feature = "bpf_rust")] { programs.extend_from_slice(&[("solana_bpf_rust_deprecated_loader")]); } for program in programs.iter() { println!("Test program: {:?}", program); let GenesisConfigInfo { genesis_config, mint_keypair, .. } = create_genesis_config(50); let mut bank = Bank::new(&genesis_config); let (name, id, entrypoint) = solana_bpf_loader_deprecated_program!(); bank.add_builtin(&name, id, entrypoint); let bank_client = BankClient::new(bank); let program_id = load_bpf_program( &bank_client, &bpf_loader_deprecated::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); let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); assert!(result.is_ok()); } } #[test] fn test_program_bpf_duplicate_accounts() { solana_logger::setup(); let mut programs = Vec::new(); #[cfg(feature = "bpf_c")] { programs.extend_from_slice(&[("dup_accounts")]); } #[cfg(feature = "bpf_rust")] { programs.extend_from_slice(&[("solana_bpf_rust_dup_accounts")]); } for program in programs.iter() { println!("Test program: {:?}", program); let GenesisConfigInfo { genesis_config, mint_keypair, .. } = create_genesis_config(50); let mut bank = Bank::new(&genesis_config); let (name, id, entrypoint) = solana_bpf_loader_program!(); bank.add_builtin(&name, id, entrypoint); let bank = Arc::new(bank); let bank_client = BankClient::new_shared(&bank); let program_id = load_bpf_program(&bank_client, &bpf_loader::id(), &mint_keypair, program); let payee_account = AccountSharedData::new(10, 1, &program_id); let payee_pubkey = solana_sdk::pubkey::new_rand(); bank.store_account(&payee_pubkey, &payee_account); let account = AccountSharedData::new(10, 1, &program_id); let pubkey = solana_sdk::pubkey::new_rand(); 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] fn test_program_bpf_error_handling() { solana_logger::setup(); let mut programs = Vec::new(); #[cfg(feature = "bpf_c")] { programs.extend_from_slice(&[("error_handling")]); } #[cfg(feature = "bpf_rust")] { programs.extend_from_slice(&[("solana_bpf_rust_error_handling")]); } for program in programs.iter() { println!("Test program: {:?}", program); let GenesisConfigInfo { genesis_config, mint_keypair, .. } = create_genesis_config(50); let mut bank = Bank::new(&genesis_config); let (name, id, entrypoint) = solana_bpf_loader_program!(); bank.add_builtin(&name, id, entrypoint); let bank_client = BankClient::new(bank); let program_id = load_bpf_program(&bank_client, &bpf_loader::id(), &mint_keypair, program); let account_metas = vec![AccountMeta::new(mint_keypair.pubkey(), true)]; let instruction = Instruction::new_with_bytes(program_id, &[1], account_metas.clone()); let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); assert!(result.is_ok()); let instruction = Instruction::new_with_bytes(program_id, &[2], account_metas.clone()); let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); assert_eq!( result.unwrap_err().unwrap(), TransactionError::InstructionError(0, InstructionError::InvalidAccountData) ); let instruction = Instruction::new_with_bytes(program_id, &[3], account_metas.clone()); let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); assert_eq!( result.unwrap_err().unwrap(), TransactionError::InstructionError(0, InstructionError::Custom(0)) ); let instruction = Instruction::new_with_bytes(program_id, &[4], account_metas.clone()); let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); assert_eq!( result.unwrap_err().unwrap(), TransactionError::InstructionError(0, InstructionError::Custom(42)) ); let instruction = Instruction::new_with_bytes(program_id, &[5], account_metas.clone()); let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); let result = result.unwrap_err().unwrap(); if TransactionError::InstructionError(0, InstructionError::InvalidInstructionData) != result { assert_eq!( result, TransactionError::InstructionError(0, InstructionError::InvalidError) ); } let instruction = Instruction::new_with_bytes(program_id, &[6], account_metas.clone()); let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); let result = result.unwrap_err().unwrap(); if TransactionError::InstructionError(0, InstructionError::InvalidInstructionData) != result { assert_eq!( result, TransactionError::InstructionError(0, InstructionError::InvalidError) ); } let instruction = Instruction::new_with_bytes(program_id, &[7], account_metas.clone()); let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); let result = result.unwrap_err().unwrap(); if TransactionError::InstructionError(0, InstructionError::InvalidInstructionData) != result { assert_eq!( result, TransactionError::InstructionError(0, InstructionError::AccountBorrowFailed) ); } let instruction = Instruction::new_with_bytes(program_id, &[8], account_metas.clone()); let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); assert_eq!( result.unwrap_err().unwrap(), TransactionError::InstructionError(0, InstructionError::InvalidInstructionData) ); let instruction = Instruction::new_with_bytes(program_id, &[9], account_metas.clone()); let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); assert_eq!( result.unwrap_err().unwrap(), TransactionError::InstructionError(0, InstructionError::MaxSeedLengthExceeded) ); } } #[test] fn test_program_bpf_invoke_sanity() { solana_logger::setup(); const TEST_SUCCESS: u8 = 1; const TEST_PRIVILEGE_ESCALATION_SIGNER: u8 = 2; const TEST_PRIVILEGE_ESCALATION_WRITABLE: u8 = 3; const TEST_PPROGRAM_NOT_EXECUTABLE: u8 = 4; const TEST_EMPTY_ACCOUNTS_SLICE: u8 = 5; const TEST_CAP_SEEDS: u8 = 6; const TEST_CAP_SIGNERS: u8 = 7; const TEST_ALLOC_ACCESS_VIOLATION: u8 = 8; const TEST_INSTRUCTION_DATA_TOO_LARGE: u8 = 9; const TEST_INSTRUCTION_META_TOO_LARGE: u8 = 10; const TEST_RETURN_ERROR: u8 = 11; const TEST_PRIVILEGE_DEESCALATION_ESCALATION_SIGNER: u8 = 12; const TEST_PRIVILEGE_DEESCALATION_ESCALATION_WRITABLE: u8 = 13; const TEST_WRITABLE_DEESCALATION_WRITABLE: u8 = 14; const TEST_NESTED_INVOKE_TOO_DEEP: u8 = 15; #[allow(dead_code)] #[derive(Debug)] enum Languages { C, Rust, } let mut programs = Vec::new(); #[cfg(feature = "bpf_c")] { programs.push((Languages::C, "invoke", "invoked", "noop")); } #[cfg(feature = "bpf_rust")] { programs.push(( Languages::Rust, "solana_bpf_rust_invoke", "solana_bpf_rust_invoked", "solana_bpf_rust_noop", )); } for program in programs.iter() { println!("Test program: {:?}", program); let GenesisConfigInfo { genesis_config, mint_keypair, .. } = create_genesis_config(50); let mut bank = Bank::new(&genesis_config); let (name, id, entrypoint) = solana_bpf_loader_program!(); bank.add_builtin(&name, id, entrypoint); let bank = Arc::new(bank); let bank_client = BankClient::new_shared(&bank); let invoke_program_id = load_bpf_program(&bank_client, &bpf_loader::id(), &mint_keypair, program.1); let invoked_program_id = load_bpf_program(&bank_client, &bpf_loader::id(), &mint_keypair, program.2); let noop_program_id = load_bpf_program(&bank_client, &bpf_loader::id(), &mint_keypair, program.3); let argument_keypair = Keypair::new(); let account = AccountSharedData::new(42, 100, &invoke_program_id); bank.store_account(&argument_keypair.pubkey(), &account); let invoked_argument_keypair = Keypair::new(); let account = AccountSharedData::new(10, 10, &invoked_program_id); bank.store_account(&invoked_argument_keypair.pubkey(), &account); let from_keypair = Keypair::new(); let account = AccountSharedData::new(84, 0, &solana_sdk::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(solana_sdk::system_program::id(), false), AccountMeta::new(from_keypair.pubkey(), true), ]; // 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) = process_transaction_and_record_inner(&bank, tx); assert!(result.is_ok()); let invoked_programs: Vec = inner_instructions[0] .iter() .map(|ix| message.account_keys[ix.program_id_index as usize].clone()) .collect(); let expected_invoked_programs = match program.0 { Languages::C => vec![ solana_sdk::system_program::id(), solana_sdk::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(), ], Languages::Rust => vec![ solana_sdk::system_program::id(), solana_sdk::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(), solana_sdk::system_program::id(), ], }; 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.program_id_index as usize].clone()) .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]| { 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) = process_transaction_and_record_inner(&bank, tx); let invoked_programs: Vec = inner_instructions[0] .iter() .map(|ix| message.account_keys[ix.program_id_index as usize].clone()) .collect(); assert_eq!(result.unwrap_err(), expected_error); assert_eq!(invoked_programs, expected_invoked_programs); }; do_invoke_failure_test_local( TEST_PRIVILEGE_ESCALATION_SIGNER, TransactionError::InstructionError(0, InstructionError::PrivilegeEscalation), &[invoked_program_id.clone()], ); do_invoke_failure_test_local( TEST_PRIVILEGE_ESCALATION_WRITABLE, TransactionError::InstructionError(0, InstructionError::PrivilegeEscalation), &[invoked_program_id.clone()], ); do_invoke_failure_test_local( TEST_PPROGRAM_NOT_EXECUTABLE, TransactionError::InstructionError(0, InstructionError::AccountNotExecutable), &[], ); do_invoke_failure_test_local( TEST_EMPTY_ACCOUNTS_SLICE, TransactionError::InstructionError(0, InstructionError::MissingAccount), &[], ); do_invoke_failure_test_local( TEST_CAP_SEEDS, TransactionError::InstructionError(0, InstructionError::MaxSeedLengthExceeded), &[], ); do_invoke_failure_test_local( TEST_CAP_SIGNERS, TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete), &[], ); do_invoke_failure_test_local( TEST_INSTRUCTION_DATA_TOO_LARGE, TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete), &[], ); do_invoke_failure_test_local( TEST_INSTRUCTION_META_TOO_LARGE, TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete), &[], ); do_invoke_failure_test_local( TEST_RETURN_ERROR, TransactionError::InstructionError(0, InstructionError::Custom(42)), &[invoked_program_id.clone()], ); do_invoke_failure_test_local( TEST_PRIVILEGE_DEESCALATION_ESCALATION_SIGNER, TransactionError::InstructionError(0, InstructionError::PrivilegeEscalation), &[invoked_program_id.clone()], ); do_invoke_failure_test_local( TEST_PRIVILEGE_DEESCALATION_ESCALATION_WRITABLE, TransactionError::InstructionError(0, InstructionError::PrivilegeEscalation), &[invoked_program_id.clone()], ); do_invoke_failure_test_local( TEST_WRITABLE_DEESCALATION_WRITABLE, TransactionError::InstructionError(0, InstructionError::ReadonlyDataModified), &[invoked_program_id.clone()], ); 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(), ], ); // 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, &solana_sdk::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) = process_transaction_and_record_inner(&bank, tx); let invoked_programs: Vec = inner_instructions[0] .iter() .map(|ix| message.account_keys[ix.program_id_index as usize].clone()) .collect(); assert_eq!(invoked_programs, vec![solana_sdk::system_program::id()]); assert_eq!( result.unwrap_err(), TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete) ); } } #[cfg(feature = "bpf_rust")] #[test] fn test_program_bpf_program_id_spoofing() { let GenesisConfigInfo { genesis_config, mint_keypair, .. } = create_genesis_config(50); let mut bank = Bank::new(&genesis_config); let (name, id, entrypoint) = solana_bpf_loader_program!(); bank.add_builtin(&name, id, entrypoint); let bank = Arc::new(bank); let bank_client = BankClient::new_shared(&bank); let malicious_swap_pubkey = load_bpf_program( &bank_client, &bpf_loader::id(), &mint_keypair, "solana_bpf_rust_spoof1", ); let malicious_system_pubkey = load_bpf_program( &bank_client, &bpf_loader::id(), &mint_keypair, "solana_bpf_rust_spoof1_system", ); let from_pubkey = Pubkey::new_unique(); let account = AccountSharedData::new(10, 0, &solana_sdk::system_program::id()); bank.store_account(&from_pubkey, &account); let to_pubkey = Pubkey::new_unique(); let account = AccountSharedData::new(0, 0, &solana_sdk::system_program::id()); bank.store_account(&to_pubkey, &account); let account_metas = vec![ AccountMeta::new_readonly(solana_sdk::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)); } #[cfg(feature = "bpf_rust")] #[test] fn test_program_bpf_caller_has_access_to_cpi_program() { let GenesisConfigInfo { genesis_config, mint_keypair, .. } = create_genesis_config(50); let mut bank = Bank::new(&genesis_config); let (name, id, entrypoint) = solana_bpf_loader_program!(); bank.add_builtin(&name, id, entrypoint); let bank = Arc::new(bank); let bank_client = BankClient::new_shared(&bank); let caller_pubkey = load_bpf_program( &bank_client, &bpf_loader::id(), &mint_keypair, "solana_bpf_rust_caller_access", ); let caller2_pubkey = load_bpf_program( &bank_client, &bpf_loader::id(), &mint_keypair, "solana_bpf_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) ); } #[cfg(feature = "bpf_rust")] #[test] fn test_program_bpf_ro_modify() { solana_logger::setup(); let GenesisConfigInfo { genesis_config, mint_keypair, .. } = create_genesis_config(50); let mut bank = Bank::new(&genesis_config); let (name, id, entrypoint) = solana_bpf_loader_program!(); bank.add_builtin(&name, id, entrypoint); let bank = Arc::new(bank); let bank_client = BankClient::new_shared(&bank); let program_pubkey = load_bpf_program( &bank_client, &bpf_loader::id(), &mint_keypair, "solana_bpf_rust_ro_modify", ); let test_keypair = Keypair::new(); let account = AccountSharedData::new(10, 0, &solana_sdk::system_program::id()); bank.store_account(&test_keypair.pubkey(), &account); let account_metas = vec![ AccountMeta::new_readonly(solana_sdk::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) ); } #[cfg(feature = "bpf_rust")] #[test] fn test_program_bpf_call_depth() { use solana_sdk::process_instruction::BpfComputeBudget; solana_logger::setup(); println!("Test program: solana_bpf_rust_call_depth"); let GenesisConfigInfo { genesis_config, mint_keypair, .. } = create_genesis_config(50); let mut bank = Bank::new(&genesis_config); let (name, id, entrypoint) = solana_bpf_loader_program!(); bank.add_builtin(&name, id, entrypoint); let bank_client = BankClient::new(bank); let program_id = load_bpf_program( &bank_client, &bpf_loader::id(), &mint_keypair, "solana_bpf_rust_call_depth", ); let instruction = Instruction::new_with_bincode( program_id, &(BpfComputeBudget::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, &BpfComputeBudget::default().max_call_depth, vec![], ); let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); assert!(result.is_err()); } #[test] fn assert_instruction_count() { solana_logger::setup(); let mut programs = Vec::new(); #[cfg(feature = "bpf_c")] { programs.extend_from_slice(&[ ("alloc", 1137), ("bpf_to_bpf", 13), ("multiple_static", 8), ("noop", 5), ("noop++", 5), ("relative_call", 10), ("sanity", 169), ("sanity++", 168), ("sha", 1040), ("struct_pass", 8), ("struct_ret", 22), ]); } #[cfg(feature = "bpf_rust")] { programs.extend_from_slice(&[ ("solana_bpf_rust_128bit", 584), ("solana_bpf_rust_alloc", 7082), ("solana_bpf_rust_custom_heap", 522), ("solana_bpf_rust_dep_crate", 2), ("solana_bpf_rust_external_spend", 504), ("solana_bpf_rust_iter", 724), ("solana_bpf_rust_many_args", 233), ("solana_bpf_rust_mem", 3119), ("solana_bpf_rust_membuiltins", 4065), ("solana_bpf_rust_noop", 478), ("solana_bpf_rust_param_passing", 46), ("solana_bpf_rust_rand", 481), ("solana_bpf_rust_sanity", 873), ("solana_bpf_rust_sha", 32295), ]); } let mut passed = true; println!("\n {:30} expected actual diff", "BPF program"); for program in programs.iter() { let program_id = solana_sdk::pubkey::new_rand(); let key = solana_sdk::pubkey::new_rand(); let mut account = RefCell::new(AccountSharedData::default()); let parameter_accounts = vec![KeyedAccount::new(&key, false, &mut account)]; let count = run_program(program.0, &program_id, parameter_accounts, &[]).unwrap(); let diff: i64 = count as i64 - program.1 as i64; println!( " {:30} {:8} {:6} {:+5} ({:+3.0}%)", program.0, program.1, count, diff, 100.0_f64 * count as f64 / program.1 as f64 - 100.0_f64, ); if count > program.1 { passed = false; } } assert!(passed); } #[cfg(any(feature = "bpf_rust"))] #[test] fn test_program_bpf_instruction_introspection() { solana_logger::setup(); let GenesisConfigInfo { genesis_config, mint_keypair, .. } = create_genesis_config(50_000); let mut bank = Bank::new(&genesis_config); let (name, id, entrypoint) = solana_bpf_loader_program!(); bank.add_builtin(&name, id, entrypoint); let bank = Arc::new(bank); let bank_client = BankClient::new_shared(&bank); let program_id = load_bpf_program( &bank_client, &bpf_loader::id(), &mint_keypair, "solana_bpf_rust_instruction_introspection", ); // Passing transaction let account_metas = vec![AccountMeta::new_readonly( solana_sdk::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( solana_sdk::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, solana_sdk::instruction::InstructionError::NotEnoughAccountKeys ) ); assert!(bank .get_account(&solana_sdk::sysvar::instructions::id()) .is_none()); } #[cfg(feature = "bpf_rust")] #[test] fn test_program_bpf_test_use_latest_executor() { use solana_sdk::{loader_instruction, system_instruction}; solana_logger::setup(); let GenesisConfigInfo { genesis_config, mint_keypair, .. } = create_genesis_config(50); let mut bank = Bank::new(&genesis_config); let (name, id, entrypoint) = solana_bpf_loader_program!(); bank.add_builtin(&name, id, entrypoint); let bank_client = BankClient::new(bank); let panic_id = load_bpf_program( &bank_client, &bpf_loader::id(), &mint_keypair, "solana_bpf_rust_panic", ); let program_keypair = Keypair::new(); // Write the panic program into the program account let elf = read_bpf_program("solana_bpf_rust_panic"); let message = Message::new( &[system_instruction::create_account( &mint_keypair.pubkey(), &program_keypair.pubkey(), 1, elf.len() as u64 * 2, &bpf_loader::id(), )], Some(&mint_keypair.pubkey()), ); assert!(bank_client .send_and_confirm_message(&[&mint_keypair, &program_keypair], message) .is_ok()); write_bpf_program( &bank_client, &bpf_loader::id(), &mint_keypair, &program_keypair, &elf, ); // Finalize the panic program, but fail the tx let message = Message::new( &[ loader_instruction::finalize(&program_keypair.pubkey(), &bpf_loader::id()), Instruction::new_with_bytes(panic_id, &[0], vec![]), ], Some(&mint_keypair.pubkey()), ); assert!(bank_client .send_and_confirm_message(&[&mint_keypair, &program_keypair], message) .is_err()); // Write the noop program into the same program account let elf = read_bpf_program("solana_bpf_rust_noop"); write_bpf_program( &bank_client, &bpf_loader::id(), &mint_keypair, &program_keypair, &elf, ); // Finalize the noop program let message = Message::new( &[loader_instruction::finalize( &program_keypair.pubkey(), &bpf_loader::id(), )], Some(&mint_keypair.pubkey()), ); assert!(bank_client .send_and_confirm_message(&[&mint_keypair, &program_keypair], message) .is_ok()); // 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()), ); assert!(bank_client .send_and_confirm_message(&[&mint_keypair], message) .is_ok()); } #[ignore] // Invoking BPF loaders from CPI not allowed #[cfg(feature = "bpf_rust")] #[test] fn test_program_bpf_test_use_latest_executor2() { use solana_sdk::{loader_instruction, system_instruction}; solana_logger::setup(); let GenesisConfigInfo { genesis_config, mint_keypair, .. } = create_genesis_config(50); let mut bank = Bank::new(&genesis_config); let (name, id, entrypoint) = solana_bpf_loader_program!(); bank.add_builtin(&name, id, entrypoint); let bank_client = BankClient::new(bank); let invoke_and_error = load_bpf_program( &bank_client, &bpf_loader::id(), &mint_keypair, "solana_bpf_rust_invoke_and_error", ); let invoke_and_ok = load_bpf_program( &bank_client, &bpf_loader::id(), &mint_keypair, "solana_bpf_rust_invoke_and_ok", ); let program_keypair = Keypair::new(); // Write the panic program into the program account let elf = read_bpf_program("solana_bpf_rust_panic"); let message = Message::new( &[system_instruction::create_account( &mint_keypair.pubkey(), &program_keypair.pubkey(), 1, elf.len() as u64 * 2, &bpf_loader::id(), )], Some(&mint_keypair.pubkey()), ); assert!(bank_client .send_and_confirm_message(&[&mint_keypair, &program_keypair], message) .is_ok()); write_bpf_program( &bank_client, &bpf_loader::id(), &mint_keypair, &program_keypair, &elf, ); // - invoke finalize and return error, swallow error let mut instruction = loader_instruction::finalize(&program_keypair.pubkey(), &bpf_loader::id()); instruction.accounts.insert( 0, AccountMeta { is_signer: false, is_writable: false, pubkey: instruction.program_id, }, ); instruction.program_id = invoke_and_ok; instruction.accounts.insert( 0, AccountMeta { is_signer: false, is_writable: false, pubkey: invoke_and_error, }, ); let message = Message::new(&[instruction], Some(&mint_keypair.pubkey())); assert!(bank_client .send_and_confirm_message(&[&mint_keypair, &program_keypair], message) .is_ok()); // invoke program, verify not found let message = Message::new( &[Instruction::new_with_bytes( program_keypair.pubkey(), &[0], vec![], )], Some(&mint_keypair.pubkey()), ); assert_eq!( bank_client .send_and_confirm_message(&[&mint_keypair], message) .unwrap_err() .unwrap(), TransactionError::InvalidProgramForExecution ); // Write the noop program into the same program account let elf = read_bpf_program("solana_bpf_rust_noop"); write_bpf_program( &bank_client, &bpf_loader::id(), &mint_keypair, &program_keypair, &elf, ); // Finalize the noop program let message = Message::new( &[loader_instruction::finalize( &program_keypair.pubkey(), &bpf_loader::id(), )], Some(&mint_keypair.pubkey()), ); assert!(bank_client .send_and_confirm_message(&[&mint_keypair, &program_keypair], message) .is_ok()); // Call the program, should get noop, not panic let message = Message::new( &[Instruction::new_with_bytes( program_keypair.pubkey(), &[0], vec![], )], Some(&mint_keypair.pubkey()), ); assert!(bank_client .send_and_confirm_message(&[&mint_keypair], message) .is_ok()); } #[cfg(feature = "bpf_rust")] #[test] fn test_program_bpf_upgrade() { solana_logger::setup(); let GenesisConfigInfo { genesis_config, mint_keypair, .. } = create_genesis_config(50); let mut bank = Bank::new(&genesis_config); let (name, id, entrypoint) = solana_bpf_loader_upgradeable_program!(); bank.add_builtin(&name, id, entrypoint); let bank_client = BankClient::new(bank); // Deploy upgrade program let buffer_keypair = Keypair::new(); let program_keypair = Keypair::new(); let program_id = program_keypair.pubkey(); let authority_keypair = Keypair::new(); load_upgradeable_bpf_program( &bank_client, &mint_keypair, &buffer_keypair, &program_keypair, &authority_keypair, "solana_bpf_rust_upgradeable", ); let mut instruction = Instruction::new_with_bytes( program_id, &[0], vec![ AccountMeta::new(program_id.clone(), false), AccountMeta::new(clock::id(), false), AccountMeta::new(fees::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_bpf_program( &bank_client, &mint_keypair, &buffer_keypair, &program_id, &authority_keypair, "solana_bpf_rust_upgraded", ); // 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_bpf_program( &bank_client, &mint_keypair, &buffer_keypair, &program_id, &new_authority_keypair, "solana_bpf_rust_upgradeable", ); // 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)) ); } #[cfg(feature = "bpf_rust")] #[test] fn test_program_bpf_upgrade_and_invoke_in_same_tx() { solana_logger::setup(); let GenesisConfigInfo { genesis_config, mint_keypair, .. } = create_genesis_config(50); let mut bank = Bank::new(&genesis_config); let (name, id, entrypoint) = solana_bpf_loader_upgradeable_program!(); bank.add_builtin(&name, id, entrypoint); let bank = Arc::new(bank); let 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_bpf_program( &bank_client, &mint_keypair, &buffer_keypair, &program_keypair, &authority_keypair, "solana_bpf_rust_noop", ); let invoke_instruction = Instruction::new_with_bytes( program_id, &[0], vec![ AccountMeta::new(program_id.clone(), false), AccountMeta::new(clock::id(), false), AccountMeta::new(fees::id(), false), ], ); // Call upgradeable program let result = bank_client.send_and_confirm_instruction(&mint_keypair, invoke_instruction.clone()); assert!(result.is_ok()); // Prepare for upgrade let buffer_keypair = Keypair::new(); load_upgradeable_buffer( &bank_client, &mint_keypair, &buffer_keypair, &authority_keypair, "solana_bpf_rust_panic", ); // Invoke, then upgrade the program, and then invoke again in same tx let message = Message::new( &[ invoke_instruction.clone(), bpf_loader_upgradeable::upgrade( &program_id, &buffer_keypair.pubkey(), &authority_keypair.pubkey(), &mint_keypair.pubkey(), ), 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(2, InstructionError::ProgramFailedToComplete) ); } #[cfg(feature = "bpf_rust")] #[test] fn test_program_bpf_invoke_upgradeable_via_cpi() { solana_logger::setup(); let GenesisConfigInfo { genesis_config, mint_keypair, .. } = create_genesis_config(50); let mut bank = Bank::new(&genesis_config); let (name, id, entrypoint) = solana_bpf_loader_program!(); bank.add_builtin(&name, id, entrypoint); let (name, id, entrypoint) = solana_bpf_loader_upgradeable_program!(); bank.add_builtin(&name, id, entrypoint); let bank_client = BankClient::new(bank); let invoke_and_return = load_bpf_program( &bank_client, &bpf_loader::id(), &mint_keypair, "solana_bpf_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_bpf_program( &bank_client, &mint_keypair, &buffer_keypair, &program_keypair, &authority_keypair, "solana_bpf_rust_upgradeable", ); let mut instruction = Instruction::new_with_bytes( invoke_and_return, &[0], vec![ AccountMeta::new(program_id, false), AccountMeta::new(program_id, false), AccountMeta::new(clock::id(), false), AccountMeta::new(fees::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_bpf_program( &bank_client, &mint_keypair, &buffer_keypair, &program_id, &authority_keypair, "solana_bpf_rust_upgraded", ); // 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_bpf_program( &bank_client, &mint_keypair, &buffer_keypair, &program_id, &new_authority_keypair, "solana_bpf_rust_upgradeable", ); // 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 = "bpf_c", feature = "bpf_rust"))] fn test_program_bpf_disguised_as_bpf_loader() { solana_logger::setup(); let mut programs = Vec::new(); #[cfg(feature = "bpf_c")] { programs.extend_from_slice(&[("noop")]); } #[cfg(feature = "bpf_rust")] { programs.extend_from_slice(&[("solana_bpf_rust_noop")]); } for program in programs.iter() { let GenesisConfigInfo { genesis_config, mint_keypair, .. } = create_genesis_config(50); let mut bank = Bank::new(&genesis_config); let (name, id, entrypoint) = solana_bpf_loader_deprecated_program!(); bank.add_builtin(&name, id, entrypoint); let bank_client = BankClient::new(bank); let program_id = load_bpf_program( &bank_client, &bpf_loader_deprecated::id(), &mint_keypair, program, ); let account_metas = vec![AccountMeta::new_readonly(program_id, false)]; let instruction = Instruction::new_with_bytes(bpf_loader_deprecated::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 = "bpf_c")] fn test_program_bpf_c_dup() { solana_logger::setup(); let GenesisConfigInfo { genesis_config, mint_keypair, .. } = create_genesis_config(50); let mut bank = Bank::new(&genesis_config); let (name, id, entrypoint) = solana_bpf_loader_program!(); bank.add_builtin(&name, id, entrypoint); let account_address = Pubkey::new_unique(); let account = AccountSharedData::new_data(42, &[1_u8, 2, 3], &solana_sdk::system_program::id()).unwrap(); bank.store_account(&account_address, &account); let bank_client = BankClient::new(bank); let program_id = load_bpf_program(&bank_client, &bpf_loader::id(), &mint_keypair, "ser"); let account_metas = vec![ AccountMeta::new_readonly(account_address, false), AccountMeta::new_readonly(account_address, false), ]; let instruction = Instruction::new_with_bytes(program_id, &[4, 5, 6, 7], account_metas); bank_client .send_and_confirm_instruction(&mint_keypair, instruction) .unwrap(); } #[cfg(feature = "bpf_rust")] #[test] fn test_program_bpf_upgrade_via_cpi() { solana_logger::setup(); let GenesisConfigInfo { genesis_config, mint_keypair, .. } = create_genesis_config(50); let mut bank = Bank::new(&genesis_config); let (name, id, entrypoint) = solana_bpf_loader_program!(); bank.add_builtin(&name, id, entrypoint); let (name, id, entrypoint) = solana_bpf_loader_upgradeable_program!(); bank.add_builtin(&name, id, entrypoint); let bank_client = BankClient::new(bank); let invoke_and_return = load_bpf_program( &bank_client, &bpf_loader::id(), &mint_keypair, "solana_bpf_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_bpf_program( &bank_client, &mint_keypair, &buffer_keypair, &program_keypair, &authority_keypair, "solana_bpf_rust_upgradeable", ); let program_account = bank_client.get_account(&program_id).unwrap().unwrap(); let programdata_address = match program_account.state() { Ok(bpf_loader_upgradeable::UpgradeableLoaderState::Program { programdata_address, }) => programdata_address, _ => 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(program_id, false), AccountMeta::new(program_id, false), AccountMeta::new(clock::id(), false), AccountMeta::new(fees::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 path = create_bpf_path("solana_bpf_rust_upgraded"); let mut file = File::open(&path).unwrap_or_else(|err| { panic!("Failed to open {}: {}", path.display(), err); }); let mut elf = Vec::new(); file.read_to_end(&mut elf).unwrap(); let buffer_keypair = Keypair::new(); load_buffer_account( &bank_client, &mint_keypair, &buffer_keypair, &authority_keypair, &elf, ); // 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(); // 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); } #[cfg(feature = "bpf_rust")] #[test] fn test_program_bpf_upgrade_self_via_cpi() { solana_logger::setup(); let GenesisConfigInfo { genesis_config, mint_keypair, .. } = create_genesis_config(50); let mut bank = Bank::new(&genesis_config); let (name, id, entrypoint) = solana_bpf_loader_program!(); bank.add_builtin(&name, id, entrypoint); let (name, id, entrypoint) = solana_bpf_loader_upgradeable_program!(); bank.add_builtin(&name, id, entrypoint); let bank = Arc::new(bank); let bank_client = BankClient::new_shared(&bank); let noop_program_id = load_bpf_program( &bank_client, &bpf_loader::id(), &mint_keypair, "solana_bpf_rust_noop", ); // 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_bpf_program( &bank_client, &mint_keypair, &buffer_keypair, &program_keypair, &authority_keypair, "solana_bpf_rust_invoke_and_return", ); let mut invoke_instruction = Instruction::new_with_bytes( program_id, &[0], vec![ AccountMeta::new(noop_program_id, false), AccountMeta::new(noop_program_id, false), AccountMeta::new(clock::id(), false), AccountMeta::new(fees::id(), false), ], ); // Call the upgraded program invoke_instruction.data[0] += 1; let result = bank_client.send_and_confirm_instruction(&mint_keypair, invoke_instruction.clone()); assert!(result.is_ok()); // Prepare for upgrade let buffer_keypair = Keypair::new(); load_upgradeable_buffer( &bank_client, &mint_keypair, &buffer_keypair, &authority_keypair, "solana_bpf_rust_panic", ); // Invoke, then upgrade the program, and then invoke again in same tx let message = Message::new( &[ invoke_instruction.clone(), bpf_loader_upgradeable::upgrade( &program_id, &buffer_keypair.pubkey(), &authority_keypair.pubkey(), &mint_keypair.pubkey(), ), 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(2, InstructionError::ProgramFailedToComplete) ); } #[cfg(feature = "bpf_rust")] #[test] fn test_program_bpf_set_upgrade_authority_via_cpi() { solana_logger::setup(); let GenesisConfigInfo { genesis_config, mint_keypair, .. } = create_genesis_config(50); let mut bank = Bank::new(&genesis_config); let (name, id, entrypoint) = solana_bpf_loader_program!(); bank.add_builtin(&name, id, entrypoint); let (name, id, entrypoint) = solana_bpf_loader_upgradeable_program!(); bank.add_builtin(&name, id, entrypoint); let bank_client = BankClient::new(bank); // Deploy CPI invoker program let invoke_and_return = load_bpf_program( &bank_client, &bpf_loader::id(), &mint_keypair, "solana_bpf_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_bpf_program( &bank_client, &mint_keypair, &buffer_keypair, &program_keypair, &authority_keypair, "solana_bpf_rust_upgradeable", ); // 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); } #[cfg(feature = "bpf_rust")] #[test] 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 mut bank = Bank::new(&genesis_config); let (name, id, entrypoint) = solana_bpf_loader_upgradeable_program!(); bank.add_builtin(&name, id, entrypoint); let bank = Arc::new(bank); let bank_client = BankClient::new_shared(&bank); load_upgradeable_bpf_program( &bank_client, &mint_keypair, buffer_keypair, program_keypair, payer_keypair, "solana_bpf_rust_panic", ); // Load the buffer account let path = create_bpf_path("solana_bpf_rust_noop"); let mut file = File::open(&path).unwrap_or_else(|err| { panic!("Failed to open {}: {}", path.display(), err); }); let mut elf = Vec::new(); file.read_to_end(&mut elf).unwrap(); load_buffer_account( &bank_client, &mint_keypair, buffer_keypair, &payer_keypair, &elf, ); 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, &[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, &[invoke_tx, upgrade_tx]) }; if false { println!("upgrade and invoke"); for result in &results1 { print_confirmed_tx("result", result.clone()); } println!("invoke and upgrade"); for result in &results2 { print_confirmed_tx("result", result.clone()); } } if let Some(ref meta) = results1[0].transaction.meta { assert_eq!(meta.status, Ok(())); } else { panic!("no meta"); } if let Some(ref meta) = results1[1].transaction.meta { assert_eq!(meta.status, Err(TransactionError::AccountInUse)); } else { panic!("no meta"); } if let Some(ref meta) = results2[0].transaction.meta { assert_eq!( meta.status, Err(TransactionError::InstructionError( 0, InstructionError::ProgramFailedToComplete )) ); } else { panic!("no meta"); } if let Some(ref meta) = results2[1].transaction.meta { assert_eq!(meta.status, Err(TransactionError::AccountInUse)); } else { panic!("no meta"); } } #[cfg(feature = "bpf_rust")] #[test] fn test_program_bpf_finalize() { solana_logger::setup(); let GenesisConfigInfo { genesis_config, mint_keypair, .. } = create_genesis_config(50); let mut bank = Bank::new(&genesis_config); let (name, id, entrypoint) = solana_bpf_loader_program!(); bank.add_builtin(&name, id, entrypoint); let bank = Arc::new(bank); let bank_client = BankClient::new_shared(&bank); let program_pubkey = load_bpf_program( &bank_client, &bpf_loader::id(), &mint_keypair, "solana_bpf_rust_finalize", ); let noop_keypair = Keypair::new(); // Write the noop program into the same program account let elf = read_bpf_program("solana_bpf_rust_noop"); let message = Message::new( &[system_instruction::create_account( &mint_keypair.pubkey(), &noop_keypair.pubkey(), 1, elf.len() as u64 * 2, &bpf_loader::id(), )], Some(&mint_keypair.pubkey()), ); assert!(bank_client .send_and_confirm_message(&[&mint_keypair, &noop_keypair], message) .is_ok()); write_bpf_program( &bank_client, &bpf_loader::id(), &mint_keypair, &noop_keypair, &elf, ); let account_metas = vec![ AccountMeta::new(noop_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, &noop_keypair], message); assert_eq!( result.unwrap_err().unwrap(), TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete) ); } #[cfg(feature = "bpf_rust")] #[test] fn test_program_bpf_ro_account_modify() { solana_logger::setup(); let GenesisConfigInfo { genesis_config, mint_keypair, .. } = create_genesis_config(50); let mut bank = Bank::new(&genesis_config); let (name, id, entrypoint) = solana_bpf_loader_program!(); bank.add_builtin(&name, id, entrypoint); let bank = Arc::new(bank); let bank_client = BankClient::new_shared(&bank); let program_id = load_bpf_program( &bank_client, &bpf_loader::id(), &mint_keypair, "solana_bpf_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, &solana_sdk::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); println!("result: {:?}", result); 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); println!("result: {:?}", result); 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); println!("result: {:?}", result); assert_eq!( result.unwrap_err().unwrap(), TransactionError::InstructionError(0, InstructionError::ReadonlyDataModified) ); }