#![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)] #[macro_use] extern crate solana_bpf_loader_program; #[cfg(feature = "sbf_rust")] use { itertools::izip, solana_account_decoder::parse_bpf_loader::{ parse_bpf_upgradeable_loader, BpfUpgradeableLoaderAccountType, }, solana_ledger::token_balances::collect_token_balances, solana_program_runtime::{ compute_budget::ComputeBudget, invoke_context::InvokeContext, timings::ExecuteTimings, }, solana_rbpf::vm::ContextObject, solana_runtime::{ bank::{ DurableNonceFee, InnerInstruction, TransactionBalancesSet, TransactionExecutionDetails, TransactionExecutionResult, TransactionResults, }, 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::FeatureSet, fee::FeeStructure, loader_instruction, message::{v0::LoadedAddresses, SanitizedMessage}, signature::keypair_from_seed, stake, system_instruction::{self, MAX_PERMITTED_DATA_LENGTH}, sysvar::{self, clock, rent}, transaction::VersionedTransaction, }, solana_transaction_status::{ ConfirmedTransactionWithStatusMeta, InnerInstructions, TransactionStatusMeta, TransactionWithStatusMeta, VersionedTransactionWithStatusMeta, }, std::collections::HashMap, }; use { solana_program_runtime::invoke_context::mock_process_instruction, solana_runtime::{ bank::Bank, bank_client::BankClient, genesis_utils::{ bootstrap_validator_stake_lamports, create_genesis_config, create_genesis_config_with_leader_ex, GenesisConfigInfo, }, }, solana_sdk::{ account::AccountSharedData, bpf_loader, bpf_loader_deprecated, client::SyncClient, clock::UnixTimestamp, fee_calculator::FeeRateGovernor, genesis_config::ClusterType, hash::Hash, instruction::{AccountMeta, Instruction, InstructionError}, message::Message, pubkey::Pubkey, rent::Rent, signature::{Keypair, Signer}, system_program, transaction::{SanitizedTransaction, Transaction, TransactionError}, }, std::{str::FromStr, sync::Arc, time::Duration}, }; #[cfg(feature = "sbf_rust")] fn process_transaction_and_record_inner( bank: &Bank, tx: Transaction, ) -> ( Result<(), TransactionError>, Vec>, Vec, ) { let signature = tx.signatures.get(0).unwrap().clone(); let txs = vec![tx]; let tx_batch = bank.prepare_batch_for_tests(txs); let mut results = bank .load_execute_and_commit_transactions( &tx_batch, MAX_PROCESSING_AGE, false, true, true, false, &mut ExecuteTimings::default(), None, ) .0; let result = results .fee_collection_results .swap_remove(0) .and_then(|_| bank.get_signature_status(&signature).unwrap()); let execution_details = results .execution_results .swap_remove(0) .details() .expect("tx should be executed") .clone(); let inner_instructions = execution_details .inner_instructions .expect("cpi recording should be enabled"); let log_messages = execution_details .log_messages .expect("log recording should be enabled"); (result, inner_instructions, log_messages) } #[cfg(feature = "sbf_rust")] fn execute_transactions( bank: &Bank, txs: Vec, ) -> 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| { inner_instructions .into_iter() .enumerate() .map(|(index, instructions)| InnerInstructions { index: index as u8, instructions: instructions .into_iter() .map(|ix| solana_transaction_status::InnerInstruction { instruction: ix.instruction, stack_height: Some(u32::from(ix.stack_height)), }) .collect(), }) .filter(|i| !i.instructions.is_empty()) .collect() }); let tx_status_meta = TransactionStatusMeta { status, fee, pre_balances, post_balances, pre_token_balances: Some(pre_token_balances), post_token_balances: Some(post_token_balances), inner_instructions, log_messages, rewards: None, loaded_addresses: LoadedAddresses::default(), return_data, compute_units_consumed: Some(executed_units), }; Ok(ConfirmedTransactionWithStatusMeta { slot: bank.slot(), tx_with_meta: TransactionWithStatusMeta::Complete( VersionedTransactionWithStatusMeta { transaction: VersionedTransaction::from(tx.clone()), meta: tx_status_meta, }, ), block_time: None, }) } TransactionExecutionResult::NotExecuted(err) => Err(err.clone()), } }, ) .collect() } #[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), ("sbf_to_sbf", true), ("float", true), ("multiple_static", true), ("noop", true), ("noop++", true), ("panic", false), ("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_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_rand", true), ("solana_sbf_rust_sanity", true), ("solana_sbf_rust_secp256k1_recover", true), ("solana_sbf_rust_sha", true), ]); } for program in programs.iter() { println!("Test program: {:?}", program.0); let GenesisConfigInfo { genesis_config, mint_keypair, .. } = create_genesis_config(50); let mut bank = Bank::new_for_tests(&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_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 = "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 mut bank = Bank::new_for_tests(&genesis_config); let (name, id, entrypoint) = solana_bpf_loader_deprecated_program!(); bank.add_builtin(&name, &id, entrypoint); let program_id = create_program(&bank, &bpf_loader_deprecated::id(), program); let bank_client = BankClient::new(bank); 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] #[cfg(feature = "sbf_rust")] fn test_sol_alloc_free_no_longer_deployable() { solana_logger::setup(); let program_keypair = Keypair::new(); let program_address = program_keypair.pubkey(); let GenesisConfigInfo { genesis_config, mint_keypair, .. } = create_genesis_config(50); let mut bank = Bank::new_for_tests(&genesis_config); let (name, id, entrypoint) = solana_bpf_loader_program!(); bank.add_builtin(&name, &id, entrypoint); // 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, &[1], vec![AccountMeta::new(mint_keypair.pubkey(), true)], )], Some(&mint_keypair.pubkey()), ), bank.last_blockhash(), ); // Try and deploy a program that depends on _sol_alloc_free assert_eq!( bank.process_transaction(&finalize_tx).unwrap_err(), TransactionError::InstructionError(0, InstructionError::InvalidAccountData) ); // Enable _sol_alloc_free syscall bank.deactivate_feature(&solana_sdk::feature_set::disable_deploy_of_alloc_free_syscall::id()); bank.clear_signatures(); bank.clear_executors(); // Try and finalize the program now that sol_alloc_free is re-enabled assert!(bank.process_transaction(&finalize_tx).is_ok()); // invoke the program assert!(bank.process_transaction(&invoke_tx).is_ok()); // disable _sol_alloc_free bank.activate_feature(&solana_sdk::feature_set::disable_deploy_of_alloc_free_syscall::id()); bank.clear_signatures(); // invoke should still succeed because cached assert!(bank.process_transaction(&invoke_tx).is_ok()); bank.clear_signatures(); bank.clear_executors(); // invoke should still succeed on execute because the program is already deployed assert!(bank.process_transaction(&invoke_tx).is_ok()); } #[test] #[cfg(feature = "sbf_rust")] fn test_program_sbf_duplicate_accounts() { solana_logger::setup(); let mut programs = Vec::new(); #[cfg(feature = "sbf_c")] { programs.extend_from_slice(&[("dup_accounts")]); } #[cfg(feature = "sbf_rust")] { programs.extend_from_slice(&[("solana_sbf_rust_dup_accounts")]); } for program in programs.iter() { println!("Test program: {:?}", program); let GenesisConfigInfo { genesis_config, mint_keypair, .. } = create_genesis_config(50); let mut bank = Bank::new_for_tests(&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_program(&bank_client, &bpf_loader::id(), &mint_keypair, program); let payee_account = AccountSharedData::new(10, 1, &program_id); let payee_pubkey = Pubkey::new_unique(); bank.store_account(&payee_pubkey, &payee_account); let account = AccountSharedData::new(10, 1, &program_id); let pubkey = Pubkey::new_unique(); let account_metas = vec![ AccountMeta::new(mint_keypair.pubkey(), true), AccountMeta::new(payee_pubkey, false), AccountMeta::new(pubkey, false), AccountMeta::new(pubkey, false), ]; bank.store_account(&pubkey, &account); let instruction = Instruction::new_with_bytes(program_id, &[1], account_metas.clone()); let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); let data = bank_client.get_account_data(&pubkey).unwrap().unwrap(); assert!(result.is_ok()); assert_eq!(data[0], 1); bank.store_account(&pubkey, &account); let instruction = Instruction::new_with_bytes(program_id, &[2], account_metas.clone()); let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); let data = bank_client.get_account_data(&pubkey).unwrap().unwrap(); assert!(result.is_ok()); assert_eq!(data[0], 2); bank.store_account(&pubkey, &account); let instruction = Instruction::new_with_bytes(program_id, &[3], account_metas.clone()); let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); let data = bank_client.get_account_data(&pubkey).unwrap().unwrap(); assert!(result.is_ok()); assert_eq!(data[0], 3); bank.store_account(&pubkey, &account); let instruction = Instruction::new_with_bytes(program_id, &[4], account_metas.clone()); let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); let lamports = bank_client.get_balance(&pubkey).unwrap(); assert!(result.is_ok()); assert_eq!(lamports, 11); bank.store_account(&pubkey, &account); let instruction = Instruction::new_with_bytes(program_id, &[5], account_metas.clone()); let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); let lamports = bank_client.get_balance(&pubkey).unwrap(); assert!(result.is_ok()); assert_eq!(lamports, 12); bank.store_account(&pubkey, &account); let instruction = Instruction::new_with_bytes(program_id, &[6], account_metas.clone()); let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); let lamports = bank_client.get_balance(&pubkey).unwrap(); assert!(result.is_ok()); assert_eq!(lamports, 13); let keypair = Keypair::new(); let pubkey = keypair.pubkey(); let account_metas = vec![ AccountMeta::new(mint_keypair.pubkey(), true), AccountMeta::new(payee_pubkey, false), AccountMeta::new(pubkey, false), AccountMeta::new_readonly(pubkey, true), AccountMeta::new_readonly(program_id, false), ]; bank.store_account(&pubkey, &account); let instruction = Instruction::new_with_bytes(program_id, &[7], account_metas.clone()); let message = Message::new(&[instruction], Some(&mint_keypair.pubkey())); let result = bank_client.send_and_confirm_message(&[&mint_keypair, &keypair], message); assert!(result.is_ok()); } } #[test] #[cfg(feature = "sbf_rust")] fn test_program_sbf_error_handling() { solana_logger::setup(); let mut programs = Vec::new(); #[cfg(feature = "sbf_c")] { programs.extend_from_slice(&[("error_handling")]); } #[cfg(feature = "sbf_rust")] { programs.extend_from_slice(&[("solana_sbf_rust_error_handling")]); } for program in programs.iter() { println!("Test program: {:?}", program); let GenesisConfigInfo { genesis_config, mint_keypair, .. } = create_genesis_config(50); let mut bank = Bank::new_for_tests(&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_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] #[cfg(any(feature = "sbf_c", feature = "sbf_rust"))] fn test_return_data_and_log_data_syscall() { solana_logger::setup(); let mut programs = Vec::new(); #[cfg(feature = "sbf_c")] { programs.extend_from_slice(&[("log_data")]); } #[cfg(feature = "sbf_rust")] { programs.extend_from_slice(&[("solana_sbf_rust_log_data")]); } for program in programs.iter() { let GenesisConfigInfo { genesis_config, mint_keypair, .. } = create_genesis_config(50); let mut bank = Bank::new_for_tests(&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_program(&bank_client, &bpf_loader::id(), &mint_keypair, program); bank.freeze(); let account_metas = vec![AccountMeta::new(mint_keypair.pubkey(), true)]; let instruction = Instruction::new_with_bytes(program_id, &[1, 2, 3, 0, 4, 5, 6], account_metas); let blockhash = bank.last_blockhash(); let message = Message::new(&[instruction], Some(&mint_keypair.pubkey())); let transaction = Transaction::new(&[&mint_keypair], message, blockhash); let sanitized_tx = SanitizedTransaction::from_transaction_for_tests(transaction); let result = bank.simulate_transaction(sanitized_tx); assert!(result.result.is_ok()); assert_eq!(result.logs[1], "Program data: AQID BAUG"); assert_eq!( result.logs[3], format!("Program return: {} CAFE", program_id) ); } } #[test] #[cfg(feature = "sbf_rust")] fn test_program_sbf_invoke_sanity() { solana_logger::setup(); #[allow(dead_code)] #[derive(Debug)] enum Languages { C, Rust, } let mut programs = Vec::new(); #[cfg(feature = "sbf_c")] { programs.push((Languages::C, "invoke", "invoked", "noop")); } #[cfg(feature = "sbf_rust")] { programs.push(( Languages::Rust, "solana_sbf_rust_invoke", "solana_sbf_rust_invoked", "solana_sbf_rust_noop", )); } for program in programs.iter() { println!("Test program: {:?}", program); let GenesisConfigInfo { genesis_config, mint_keypair, .. } = create_genesis_config(50); let mut bank = Bank::new_for_tests(&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_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 noop_program_id = load_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, &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 { 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 "Program failed to complete: Invoked an instruction with data that is too large (10241 > 10240)".into(), format!("Program {invoke_program_id} failed: Program failed to complete"), ]), ); 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 "Program failed to complete: Invoked an instruction with too many accounts (256 > 255)".into(), format!("Program {invoke_program_id} failed: Program failed to complete"), ]), ); 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 "Program failed to complete: Invoked an instruction with too many account info's (129 > 128)".into(), format!("Program {invoke_program_id} failed: Program failed to complete"), ]), ); 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![system_program::id()]); assert_eq!( result.unwrap_err(), TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete) ); } } #[test] #[cfg(feature = "sbf_rust")] fn test_program_sbf_program_id_spoofing() { let GenesisConfigInfo { genesis_config, mint_keypair, .. } = create_genesis_config(50); let mut bank = Bank::new_for_tests(&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_program( &bank_client, &bpf_loader::id(), &mint_keypair, "solana_sbf_rust_spoof1", ); let malicious_system_pubkey = load_program( &bank_client, &bpf_loader::id(), &mint_keypair, "solana_sbf_rust_spoof1_system", ); let from_pubkey = Pubkey::new_unique(); let account = AccountSharedData::new(10, 0, &system_program::id()); bank.store_account(&from_pubkey, &account); let to_pubkey = Pubkey::new_unique(); let account = AccountSharedData::new(0, 0, &system_program::id()); bank.store_account(&to_pubkey, &account); let account_metas = vec![ AccountMeta::new_readonly(system_program::id(), false), AccountMeta::new_readonly(malicious_system_pubkey, false), AccountMeta::new(from_pubkey, false), AccountMeta::new(to_pubkey, false), ]; let instruction = Instruction::new_with_bytes(malicious_swap_pubkey, &[], account_metas.clone()); let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); assert_eq!( result.unwrap_err().unwrap(), TransactionError::InstructionError(0, InstructionError::MissingRequiredSignature) ); assert_eq!(10, bank.get_balance(&from_pubkey)); assert_eq!(0, bank.get_balance(&to_pubkey)); } #[test] #[cfg(feature = "sbf_rust")] fn test_program_sbf_caller_has_access_to_cpi_program() { let GenesisConfigInfo { genesis_config, mint_keypair, .. } = create_genesis_config(50); let mut bank = Bank::new_for_tests(&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_program( &bank_client, &bpf_loader::id(), &mint_keypair, "solana_sbf_rust_caller_access", ); let caller2_pubkey = load_program( &bank_client, &bpf_loader::id(), &mint_keypair, "solana_sbf_rust_caller_access", ); let account_metas = vec![ AccountMeta::new_readonly(caller_pubkey, false), AccountMeta::new_readonly(caller2_pubkey, false), ]; let instruction = Instruction::new_with_bytes(caller_pubkey, &[1], account_metas.clone()); let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); assert_eq!( result.unwrap_err().unwrap(), TransactionError::InstructionError(0, InstructionError::MissingAccount) ); } #[test] #[cfg(feature = "sbf_rust")] fn test_program_sbf_ro_modify() { solana_logger::setup(); let GenesisConfigInfo { genesis_config, mint_keypair, .. } = create_genesis_config(50); let mut bank = Bank::new_for_tests(&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_program( &bank_client, &bpf_loader::id(), &mint_keypair, "solana_sbf_rust_ro_modify", ); let test_keypair = Keypair::new(); let account = AccountSharedData::new(10, 0, &system_program::id()); bank.store_account(&test_keypair.pubkey(), &account); let account_metas = vec![ AccountMeta::new_readonly(system_program::id(), false), AccountMeta::new(test_keypair.pubkey(), true), ]; let instruction = Instruction::new_with_bytes(program_pubkey, &[1], account_metas.clone()); let message = Message::new(&[instruction], Some(&mint_keypair.pubkey())); let result = bank_client.send_and_confirm_message(&[&mint_keypair, &test_keypair], message); assert_eq!( result.unwrap_err().unwrap(), TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete) ); let instruction = Instruction::new_with_bytes(program_pubkey, &[3], account_metas.clone()); let message = Message::new(&[instruction], Some(&mint_keypair.pubkey())); let result = bank_client.send_and_confirm_message(&[&mint_keypair, &test_keypair], message); assert_eq!( result.unwrap_err().unwrap(), TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete) ); let instruction = Instruction::new_with_bytes(program_pubkey, &[4], account_metas.clone()); let message = Message::new(&[instruction], Some(&mint_keypair.pubkey())); let result = bank_client.send_and_confirm_message(&[&mint_keypair, &test_keypair], message); assert_eq!( result.unwrap_err().unwrap(), TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete) ); } #[test] #[cfg(feature = "sbf_rust")] fn test_program_sbf_call_depth() { solana_logger::setup(); let GenesisConfigInfo { genesis_config, mint_keypair, .. } = create_genesis_config(50); let mut bank = Bank::new_for_tests(&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_program( &bank_client, &bpf_loader::id(), &mint_keypair, "solana_sbf_rust_call_depth", ); let instruction = Instruction::new_with_bincode( program_id, &(ComputeBudget::default().max_call_depth - 1), vec![], ); let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); assert!(result.is_ok()); let instruction = Instruction::new_with_bincode(program_id, &ComputeBudget::default().max_call_depth, vec![]); let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); assert!(result.is_err()); } #[test] #[cfg(feature = "sbf_rust")] fn test_program_sbf_compute_budget() { solana_logger::setup(); let GenesisConfigInfo { genesis_config, mint_keypair, .. } = create_genesis_config(50); let mut bank = Bank::new_for_tests(&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_program( &bank_client, &bpf_loader::id(), &mint_keypair, "solana_sbf_rust_noop", ); let message = Message::new( &[ ComputeBudgetInstruction::set_compute_unit_limit(150), Instruction::new_with_bincode(program_id, &0, vec![]), ], Some(&mint_keypair.pubkey()), ); let result = bank_client.send_and_confirm_message(&[&mint_keypair], message); assert_eq!( result.unwrap_err().unwrap(), TransactionError::InstructionError(1, InstructionError::ProgramFailedToComplete), ); } #[test] fn assert_instruction_count() { solana_logger::setup(); let mut programs = Vec::new(); #[cfg(feature = "sbf_c")] { programs.extend_from_slice(&[ ("alloc", 11502), ("sbf_to_sbf", 313), ("multiple_static", 208), ("noop", 5), ("noop++", 5), ("relative_call", 210), ("return_data", 980), ("sanity", 3259), ("sanity++", 3159), ("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", 5067), ("solana_sbf_rust_custom_heap", 398), ("solana_sbf_rust_dep_crate", 2), ("solana_sbf_rust_external_spend", 288), ("solana_sbf_rust_iter", 1013), ("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", 10759), ("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, 8, &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); transaction_accounts[1] .1 .set_state(expected_consumption) .unwrap(); print!(" {:36} {:8}", program_name, *expected_consumption); mock_process_instruction( &loader_id, vec![0], &[], transaction_accounts, instruction_accounts, Ok(()), |invoke_context: &mut InvokeContext| { let expected_consumption: u64 = invoke_context .transaction_context .get_current_instruction_context() .unwrap() .try_borrow_instruction_account(&invoke_context.transaction_context, 0) .unwrap() .get_state() .unwrap(); let prev_compute_meter = invoke_context.get_remaining(); let _result = solana_bpf_loader_program::process_instruction(invoke_context); let consumption = prev_compute_meter.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_eq!(consumption, expected_consumption); Ok(()) }, |_invoke_context| {}, ); } } #[test] #[cfg(any(feature = "sbf_rust"))] fn test_program_sbf_instruction_introspection() { solana_logger::setup(); let GenesisConfigInfo { genesis_config, mint_keypair, .. } = create_genesis_config(50_000); let mut bank = Bank::new_for_tests(&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_program( &bank_client, &bpf_loader::id(), &mint_keypair, "solana_sbf_rust_instruction_introspection", ); // Passing transaction let account_metas = vec![ AccountMeta::new_readonly(program_id, false), AccountMeta::new_readonly(sysvar::instructions::id(), false), ]; let instruction0 = Instruction::new_with_bytes(program_id, &[0u8, 0u8], account_metas.clone()); let instruction1 = Instruction::new_with_bytes(program_id, &[0u8, 1u8], account_metas.clone()); let instruction2 = Instruction::new_with_bytes(program_id, &[0u8, 2u8], account_metas); let message = Message::new( &[instruction0, instruction1, instruction2], Some(&mint_keypair.pubkey()), ); let result = bank_client.send_and_confirm_message(&[&mint_keypair], message); assert!(result.is_ok()); // writable special instructions11111 key, should not be allowed let account_metas = vec![AccountMeta::new(sysvar::instructions::id(), false)]; let instruction = Instruction::new_with_bytes(program_id, &[0], account_metas); let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); assert_eq!( result.unwrap_err().unwrap(), // sysvar write locks are demoted to read only. So this will no longer // cause InvalidAccountIndex error. TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete), ); // No accounts, should error let instruction = Instruction::new_with_bytes(program_id, &[0], vec![]); let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); assert!(result.is_err()); assert_eq!( result.unwrap_err().unwrap(), TransactionError::InstructionError(0, InstructionError::NotEnoughAccountKeys) ); assert!(bank.get_account(&sysvar::instructions::id()).is_none()); } #[test] #[cfg(feature = "sbf_rust")] fn test_program_sbf_test_use_latest_executor() { solana_logger::setup(); let GenesisConfigInfo { genesis_config, mint_keypair, .. } = create_genesis_config(50); let mut bank = Bank::new_for_tests(&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_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()), ); 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", ); 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()), ); 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 mut bank = Bank::new_for_tests(&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_program( &bank_client, &mint_keypair, &buffer_keypair, &program_keypair, &authority_keypair, "solana_sbf_rust_upgradeable", ); 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() }); // 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", ); // 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 mut bank = Bank::new_for_tests(&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 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 mut bank = Bank::new_for_tests(&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 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(); // 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 mut bank = Bank::new_for_tests(&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 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), ], ); // 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 mut bank = Bank::new_for_tests(&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 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), ], ); // Prepare undeployment let (programdata_address, _) = Pubkey::find_program_address( &[program_keypair.pubkey().as_ref()], &bpf_loader_upgradeable::id(), ); let undeployment_instruction = bpf_loader_upgradeable::close_any( &programdata_address, &mint_keypair.pubkey(), Some(&authority_keypair.pubkey()), Some(&program_id), ); // Undeployment is visible to both top-level-instructions and CPI instructions for invoke_instruction in [invoke_instruction, indirect_invoke_instruction] { // Call upgradeable program let result = bank_client.send_and_confirm_instruction(&mint_keypair, invoke_instruction.clone()); assert!(result.is_ok()); // Upgrade the program and invoke in same tx let message = Message::new( &[undeployment_instruction.clone(), invoke_instruction], Some(&mint_keypair.pubkey()), ); let tx = Transaction::new( &[&mint_keypair, &authority_keypair], message.clone(), bank.last_blockhash(), ); let (result, _, _) = process_transaction_and_record_inner(&bank, tx); assert_eq!( result.unwrap_err(), TransactionError::InstructionError(1, InstructionError::InvalidAccountData), ); } } #[test] #[cfg(feature = "sbf_rust")] fn test_program_sbf_invoke_upgradeable_via_cpi() { solana_logger::setup(); let GenesisConfigInfo { genesis_config, mint_keypair, .. } = create_genesis_config(50); let mut bank = Bank::new_for_tests(&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_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", ); 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() }); // 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", ); // Call original program instruction.data[0] += 1; let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction.clone()); assert_eq!( result.unwrap_err().unwrap(), TransactionError::InstructionError(0, InstructionError::Custom(42)) ); } #[test] #[cfg(any(feature = "sbf_c", feature = "sbf_rust"))] fn test_program_sbf_disguised_as_sbf_loader() { solana_logger::setup(); let mut programs = Vec::new(); #[cfg(feature = "sbf_c")] { programs.extend_from_slice(&[("noop")]); } #[cfg(feature = "sbf_rust")] { programs.extend_from_slice(&[("solana_sbf_rust_noop")]); } for program in programs.iter() { let GenesisConfigInfo { genesis_config, mint_keypair, .. } = create_genesis_config(50); let mut bank = Bank::new_for_tests(&genesis_config); // disable native_programs_consume_cu feature to allow test program // not consume units. let mut feature_set = FeatureSet::all_enabled(); feature_set.deactivate(&solana_sdk::feature_set::native_programs_consume_cu::id()); bank.feature_set = Arc::new(feature_set); let (name, id, entrypoint) = solana_bpf_loader_program!(); bank.add_builtin(&name, &id, entrypoint); bank.deactivate_feature( &solana_sdk::feature_set::remove_bpf_loader_incorrect_program_id::id(), ); let bank_client = BankClient::new(bank); let program_id = load_program(&bank_client, &bpf_loader::id(), &mint_keypair, program); let account_metas = vec![AccountMeta::new_readonly(program_id, false)]; let instruction = Instruction::new_with_bytes(bpf_loader::id(), &[1], account_metas); let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); assert_eq!( result.unwrap_err().unwrap(), TransactionError::InstructionError(0, InstructionError::IncorrectProgramId) ); } } #[test] #[cfg(feature = "sbf_c")] fn test_program_reads_from_program_account() { solana_logger::setup(); let GenesisConfigInfo { genesis_config, mint_keypair, .. } = create_genesis_config(50); let mut bank = Bank::new_for_tests(&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_program( &bank_client, &bpf_loader::id(), &mint_keypair, "read_program", ); let account_metas = vec![AccountMeta::new_readonly(program_id, false)]; let instruction = Instruction::new_with_bytes(program_id, &[], account_metas); bank_client .send_and_confirm_instruction(&mint_keypair, instruction) .unwrap(); } #[test] #[cfg(feature = "sbf_c")] fn test_program_sbf_c_dup() { solana_logger::setup(); let GenesisConfigInfo { genesis_config, mint_keypair, .. } = create_genesis_config(50); let mut bank = Bank::new_for_tests(&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], &system_program::id()).unwrap(); bank.store_account(&account_address, &account); let bank_client = BankClient::new(bank); let program_id = load_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(); } #[test] #[cfg(feature = "sbf_rust")] fn test_program_sbf_upgrade_via_cpi() { solana_logger::setup(); let GenesisConfigInfo { genesis_config, mint_keypair, .. } = create_genesis_config(50); let mut bank = Bank::new_for_tests(&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_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", ); 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_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(); // Call the upgraded program instruction.data[0] += 1; let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction.clone()); assert_eq!( result.unwrap_err().unwrap(), TransactionError::InstructionError(0, InstructionError::Custom(43)) ); // Validate that the programdata was actually overwritten let programdata = bank_client .get_account_data(&programdata_address) .unwrap() .unwrap(); assert_ne!(programdata, original_programdata); } #[test] #[cfg(feature = "sbf_rust")] fn test_program_sbf_set_upgrade_authority_via_cpi() { solana_logger::setup(); let GenesisConfigInfo { genesis_config, mint_keypair, .. } = create_genesis_config(50); let mut bank = Bank::new_for_tests(&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_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", ); // 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 mut bank = Bank::new_for_tests(&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_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", ); bank_client .send_and_confirm_instruction( &mint_keypair, system_instruction::transfer( &mint_keypair.pubkey(), &payer_keypair.pubkey(), 1_000_000_000, ), ) .unwrap(); let invoke_tx = Transaction::new( &[payer_keypair], Message::new( &[Instruction::new_with_bytes( program_keypair.pubkey(), &[0; 0], vec![], )], Some(&payer_keypair.pubkey()), ), bank.last_blockhash(), ); let upgrade_tx = Transaction::new( &[payer_keypair], Message::new( &[bpf_loader_upgradeable::upgrade( &program_keypair.pubkey(), &buffer_keypair.pubkey(), &payer_keypair.pubkey(), &payer_keypair.pubkey(), )], Some(&payer_keypair.pubkey()), ), bank.last_blockhash(), ); (bank, invoke_tx, upgrade_tx) } let payer_keypair = keypair_from_seed(&[56u8; 32]).unwrap(); let buffer_keypair = keypair_from_seed(&[11; 32]).unwrap(); let program_keypair = keypair_from_seed(&[77u8; 32]).unwrap(); let results1 = { let (bank, invoke_tx, upgrade_tx) = setup_program_upgradeable_locks(&payer_keypair, &buffer_keypair, &program_keypair); execute_transactions(&bank, vec![upgrade_tx, invoke_tx]) }; let results2 = { let (bank, invoke_tx, upgrade_tx) = setup_program_upgradeable_locks(&payer_keypair, &buffer_keypair, &program_keypair); execute_transactions(&bank, vec![invoke_tx, upgrade_tx]) }; assert!(matches!( results1[0], Ok(ConfirmedTransactionWithStatusMeta { tx_with_meta: TransactionWithStatusMeta::Complete(VersionedTransactionWithStatusMeta { meta: TransactionStatusMeta { status: Ok(()), .. }, .. }), .. }) )); assert_eq!(results1[1], Err(TransactionError::AccountInUse)); assert!(matches!( results2[0], Ok(ConfirmedTransactionWithStatusMeta { tx_with_meta: TransactionWithStatusMeta::Complete(VersionedTransactionWithStatusMeta { meta: TransactionStatusMeta { status: Err(TransactionError::InstructionError( 0, InstructionError::ProgramFailedToComplete )), .. }, .. }), .. }) )); assert_eq!(results2[1], Err(TransactionError::AccountInUse)); } #[test] #[cfg(feature = "sbf_rust")] fn test_program_sbf_finalize() { solana_logger::setup(); let GenesisConfigInfo { genesis_config, mint_keypair, .. } = create_genesis_config(50); let mut bank = Bank::new_for_tests(&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_program( &bank_client, &bpf_loader::id(), &mint_keypair, "solana_sbf_rust_finalize", ); // Write the noop program into the same program account let (program_keypair, _instruction) = load_and_finalize_program( &bank_client, &bpf_loader::id(), None, &mint_keypair, "solana_sbf_rust_noop", ); let account_metas = vec![ AccountMeta::new(program_keypair.pubkey(), true), AccountMeta::new_readonly(bpf_loader::id(), false), AccountMeta::new(rent::id(), false), ]; let instruction = Instruction::new_with_bytes(program_pubkey, &[], account_metas.clone()); let message = Message::new(&[instruction], Some(&mint_keypair.pubkey())); let result = bank_client.send_and_confirm_message(&[&mint_keypair, &program_keypair], message); assert_eq!( result.unwrap_err().unwrap(), TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete) ); } #[test] #[cfg(feature = "sbf_rust")] fn test_program_sbf_ro_account_modify() { solana_logger::setup(); let GenesisConfigInfo { genesis_config, mint_keypair, .. } = create_genesis_config(50); let mut bank = Bank::new_for_tests(&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_program( &bank_client, &bpf_loader::id(), &mint_keypair, "solana_sbf_rust_ro_account_modify", ); let argument_keypair = Keypair::new(); let account = AccountSharedData::new(42, 100, &program_id); bank.store_account(&argument_keypair.pubkey(), &account); let from_keypair = Keypair::new(); let account = AccountSharedData::new(84, 0, &system_program::id()); bank.store_account(&from_keypair.pubkey(), &account); let mint_pubkey = mint_keypair.pubkey(); let account_metas = vec![ AccountMeta::new_readonly(argument_keypair.pubkey(), false), AccountMeta::new_readonly(program_id, false), ]; let instruction = Instruction::new_with_bytes(program_id, &[0], account_metas.clone()); let message = Message::new(&[instruction], Some(&mint_pubkey)); let result = bank_client.send_and_confirm_message(&[&mint_keypair], message); assert_eq!( result.unwrap_err().unwrap(), TransactionError::InstructionError(0, InstructionError::ReadonlyDataModified) ); let instruction = Instruction::new_with_bytes(program_id, &[1], account_metas.clone()); let message = Message::new(&[instruction], Some(&mint_pubkey)); let result = bank_client.send_and_confirm_message(&[&mint_keypair], message); assert_eq!( result.unwrap_err().unwrap(), TransactionError::InstructionError(0, InstructionError::ReadonlyDataModified) ); let instruction = Instruction::new_with_bytes(program_id, &[2], account_metas.clone()); let message = Message::new(&[instruction], Some(&mint_pubkey)); let result = bank_client.send_and_confirm_message(&[&mint_keypair], message); assert_eq!( result.unwrap_err().unwrap(), TransactionError::InstructionError(0, InstructionError::ReadonlyDataModified) ); } #[test] #[cfg(feature = "sbf_rust")] fn test_program_sbf_realloc() { solana_logger::setup(); const START_BALANCE: u64 = 100_000_000_000; let GenesisConfigInfo { genesis_config, mint_keypair, .. } = create_genesis_config(1_000_000_000_000); let mint_pubkey = mint_keypair.pubkey(); let signer = &[&mint_keypair]; let mut bank = Bank::new_for_tests(&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_program( &bank_client, &bpf_loader::id(), &mint_keypair, "solana_sbf_rust_realloc", ); let mut bump = 0; let keypair = Keypair::new(); let pubkey = keypair.pubkey(); let account = AccountSharedData::new(START_BALANCE, 5, &program_id); bank.store_account(&pubkey, &account); // Realloc RO account let mut instruction = realloc(&program_id, &pubkey, 0, &mut bump); instruction.accounts[0].is_writable = false; assert_eq!( bank_client .send_and_confirm_message(signer, Message::new(&[instruction], Some(&mint_pubkey),),) .unwrap_err() .unwrap(), TransactionError::InstructionError(0, InstructionError::ReadonlyDataModified) ); // Realloc account to overflow assert_eq!( bank_client .send_and_confirm_message( signer, Message::new( &[realloc(&program_id, &pubkey, usize::MAX, &mut bump)], Some(&mint_pubkey), ), ) .unwrap_err() .unwrap(), TransactionError::InstructionError(0, InstructionError::InvalidRealloc) ); // Realloc account to 0 bank_client .send_and_confirm_message( signer, Message::new( &[realloc(&program_id, &pubkey, 0, &mut bump)], Some(&mint_pubkey), ), ) .unwrap(); let data = bank_client.get_account_data(&pubkey).unwrap().unwrap(); assert_eq!(0, data.len()); // Realloc account to max then undo bank_client .send_and_confirm_message( signer, Message::new( &[realloc_extend_and_undo( &program_id, &pubkey, MAX_PERMITTED_DATA_INCREASE, &mut bump, )], Some(&mint_pubkey), ), ) .unwrap(); let data = bank_client.get_account_data(&pubkey).unwrap().unwrap(); assert_eq!(0, data.len()); // Realloc account to max + 1 then undo assert_eq!( bank_client .send_and_confirm_message( signer, Message::new( &[realloc_extend_and_undo( &program_id, &pubkey, MAX_PERMITTED_DATA_INCREASE + 1, &mut bump, )], Some(&mint_pubkey), ), ) .unwrap_err() .unwrap(), TransactionError::InstructionError(0, InstructionError::InvalidRealloc) ); // Realloc to max + 1 assert_eq!( bank_client .send_and_confirm_message( signer, Message::new( &[realloc( &program_id, &pubkey, MAX_PERMITTED_DATA_INCREASE + 1, &mut bump )], Some(&mint_pubkey), ), ) .unwrap_err() .unwrap(), TransactionError::InstructionError(0, InstructionError::InvalidRealloc) ); // Realloc to max length in max increase increments for i in 0..MAX_PERMITTED_DATA_LENGTH as usize / MAX_PERMITTED_DATA_INCREASE { let mut bump = i as u64; bank_client .send_and_confirm_message( signer, Message::new( &[realloc_extend_and_fill( &program_id, &pubkey, MAX_PERMITTED_DATA_INCREASE, 1, &mut bump, )], Some(&mint_pubkey), ), ) .unwrap(); let data = bank_client.get_account_data(&pubkey).unwrap().unwrap(); assert_eq!((i + 1) * MAX_PERMITTED_DATA_INCREASE, data.len()); } for i in 0..data.len() { assert_eq!(data[i], 1); } // and one more time should fail assert_eq!( bank_client .send_and_confirm_message( signer, Message::new( &[realloc_extend( &program_id, &pubkey, MAX_PERMITTED_DATA_INCREASE, &mut bump )], Some(&mint_pubkey), ) ) .unwrap_err() .unwrap(), TransactionError::InstructionError(0, InstructionError::InvalidRealloc) ); // Realloc to 0 bank_client .send_and_confirm_message( signer, Message::new( &[realloc(&program_id, &pubkey, 0, &mut bump)], Some(&mint_pubkey), ), ) .unwrap(); let data = bank_client.get_account_data(&pubkey).unwrap().unwrap(); assert_eq!(0, data.len()); // Realloc and assign bank_client .send_and_confirm_message( signer, Message::new( &[Instruction::new_with_bytes( program_id, &[REALLOC_AND_ASSIGN], vec![AccountMeta::new(pubkey, false)], )], Some(&mint_pubkey), ), ) .unwrap(); let account = bank.get_account(&pubkey).unwrap(); assert_eq!(&solana_sdk::system_program::id(), account.owner()); let data = bank_client.get_account_data(&pubkey).unwrap().unwrap(); assert_eq!(MAX_PERMITTED_DATA_INCREASE, data.len()); // Realloc to 0 with wrong owner assert_eq!( bank_client .send_and_confirm_message( signer, Message::new( &[realloc(&program_id, &pubkey, 0, &mut bump)], Some(&mint_pubkey), ), ) .unwrap_err() .unwrap(), TransactionError::InstructionError(0, InstructionError::AccountDataSizeChanged) ); // realloc and assign to self via cpi assert_eq!( bank_client .send_and_confirm_message( &[&mint_keypair, &keypair], Message::new( &[Instruction::new_with_bytes( program_id, &[REALLOC_AND_ASSIGN_TO_SELF_VIA_SYSTEM_PROGRAM], vec![ AccountMeta::new(pubkey, true), AccountMeta::new(solana_sdk::system_program::id(), false), ], )], Some(&mint_pubkey), ) ) .unwrap_err() .unwrap(), TransactionError::InstructionError(0, InstructionError::AccountDataSizeChanged) ); // Assign to self and realloc via cpi bank_client .send_and_confirm_message( &[&mint_keypair, &keypair], Message::new( &[Instruction::new_with_bytes( program_id, &[ASSIGN_TO_SELF_VIA_SYSTEM_PROGRAM_AND_REALLOC], vec![ AccountMeta::new(pubkey, true), AccountMeta::new(solana_sdk::system_program::id(), false), ], )], Some(&mint_pubkey), ), ) .unwrap(); let account = bank.get_account(&pubkey).unwrap(); assert_eq!(&program_id, account.owner()); let data = bank_client.get_account_data(&pubkey).unwrap().unwrap(); assert_eq!(2 * MAX_PERMITTED_DATA_INCREASE, data.len()); // Realloc to 0 bank_client .send_and_confirm_message( signer, Message::new( &[realloc(&program_id, &pubkey, 0, &mut bump)], Some(&mint_pubkey), ), ) .unwrap(); let data = bank_client.get_account_data(&pubkey).unwrap().unwrap(); assert_eq!(0, data.len()); // zero-init bank_client .send_and_confirm_message( &[&mint_keypair, &keypair], Message::new( &[Instruction::new_with_bytes( program_id, &[ZERO_INIT], vec![AccountMeta::new(pubkey, true)], )], Some(&mint_pubkey), ), ) .unwrap(); } #[test] #[cfg(feature = "sbf_rust")] fn test_program_sbf_realloc_invoke() { solana_logger::setup(); const START_BALANCE: u64 = 100_000_000_000; let GenesisConfigInfo { mut genesis_config, mint_keypair, .. } = create_genesis_config(1_000_000_000_000); genesis_config.rent = Rent::default(); let mint_pubkey = mint_keypair.pubkey(); let signer = &[&mint_keypair]; let mut bank = Bank::new_for_tests(&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 realloc_program_id = load_program( &bank_client, &bpf_loader::id(), &mint_keypair, "solana_sbf_rust_realloc", ); let realloc_invoke_program_id = load_program( &bank_client, &bpf_loader::id(), &mint_keypair, "solana_sbf_rust_realloc_invoke", ); let mut bump = 0; let keypair = Keypair::new(); let pubkey = keypair.pubkey().clone(); let account = AccountSharedData::new(START_BALANCE, 5, &realloc_program_id); bank.store_account(&pubkey, &account); let invoke_keypair = Keypair::new(); let invoke_pubkey = invoke_keypair.pubkey().clone(); // Realloc RO account assert_eq!( bank_client .send_and_confirm_message( signer, Message::new( &[Instruction::new_with_bytes( realloc_invoke_program_id, &[INVOKE_REALLOC_ZERO_RO], vec![ AccountMeta::new_readonly(pubkey, false), AccountMeta::new_readonly(realloc_program_id, false), ], )], Some(&mint_pubkey), ) ) .unwrap_err() .unwrap(), TransactionError::InstructionError(0, InstructionError::ReadonlyDataModified) ); let account = bank.get_account(&pubkey).unwrap(); assert_eq!(account.lamports(), START_BALANCE); // Realloc account to 0 bank_client .send_and_confirm_message( signer, Message::new( &[realloc(&realloc_program_id, &pubkey, 0, &mut bump)], Some(&mint_pubkey), ), ) .unwrap(); let account = bank.get_account(&pubkey).unwrap(); assert_eq!(account.lamports(), START_BALANCE); let data = bank_client.get_account_data(&pubkey).unwrap().unwrap(); assert_eq!(0, data.len()); // Realloc to max + 1 assert_eq!( bank_client .send_and_confirm_message( signer, Message::new( &[Instruction::new_with_bytes( realloc_invoke_program_id, &[INVOKE_REALLOC_MAX_PLUS_ONE], vec![ AccountMeta::new(pubkey, false), AccountMeta::new_readonly(realloc_program_id, false), ], )], Some(&mint_pubkey), ) ) .unwrap_err() .unwrap(), TransactionError::InstructionError(0, InstructionError::InvalidRealloc) ); // Realloc to max twice assert_eq!( bank_client .send_and_confirm_message( signer, Message::new( &[Instruction::new_with_bytes( realloc_invoke_program_id, &[INVOKE_REALLOC_MAX_TWICE], vec![ AccountMeta::new(pubkey, false), AccountMeta::new_readonly(realloc_program_id, false), ], )], Some(&mint_pubkey), ) ) .unwrap_err() .unwrap(), TransactionError::InstructionError(0, InstructionError::InvalidRealloc) ); // Realloc account to 0 bank_client .send_and_confirm_message( signer, Message::new( &[realloc(&realloc_program_id, &pubkey, 0, &mut bump)], Some(&mint_pubkey), ), ) .unwrap(); let account = bank.get_account(&pubkey).unwrap(); assert_eq!(account.lamports(), START_BALANCE); let data = bank_client.get_account_data(&pubkey).unwrap().unwrap(); assert_eq!(0, data.len()); // Realloc and assign bank_client .send_and_confirm_message( signer, Message::new( &[Instruction::new_with_bytes( realloc_invoke_program_id, &[INVOKE_REALLOC_AND_ASSIGN], vec![ AccountMeta::new(pubkey, false), AccountMeta::new_readonly(realloc_program_id, false), ], )], Some(&mint_pubkey), ), ) .unwrap(); let account = bank.get_account(&pubkey).unwrap(); assert_eq!(&solana_sdk::system_program::id(), account.owner()); let data = bank_client.get_account_data(&pubkey).unwrap().unwrap(); assert_eq!(MAX_PERMITTED_DATA_INCREASE, data.len()); // Realloc to 0 with wrong owner assert_eq!( bank_client .send_and_confirm_message( signer, Message::new( &[realloc(&realloc_program_id, &pubkey, 0, &mut bump)], Some(&mint_pubkey), ) ) .unwrap_err() .unwrap(), TransactionError::InstructionError(0, InstructionError::AccountDataSizeChanged) ); // realloc and assign to self via system program assert_eq!( bank_client .send_and_confirm_message( &[&mint_keypair, &keypair], Message::new( &[Instruction::new_with_bytes( realloc_invoke_program_id, &[INVOKE_REALLOC_AND_ASSIGN_TO_SELF_VIA_SYSTEM_PROGRAM], vec![ AccountMeta::new(pubkey, true), AccountMeta::new_readonly(realloc_program_id, false), AccountMeta::new_readonly(solana_sdk::system_program::id(), false), ], )], Some(&mint_pubkey), ) ) .unwrap_err() .unwrap(), TransactionError::InstructionError(0, InstructionError::AccountDataSizeChanged) ); // Assign to self and realloc via system program bank_client .send_and_confirm_message( &[&mint_keypair, &keypair], Message::new( &[Instruction::new_with_bytes( realloc_invoke_program_id, &[INVOKE_ASSIGN_TO_SELF_VIA_SYSTEM_PROGRAM_AND_REALLOC], vec![ AccountMeta::new(pubkey, true), AccountMeta::new_readonly(realloc_program_id, false), AccountMeta::new_readonly(solana_sdk::system_program::id(), false), ], )], Some(&mint_pubkey), ), ) .unwrap(); let account = bank.get_account(&pubkey).unwrap(); assert_eq!(&realloc_program_id, account.owner()); let data = bank_client.get_account_data(&pubkey).unwrap().unwrap(); assert_eq!(2 * MAX_PERMITTED_DATA_INCREASE, data.len()); // Realloc to 0 bank_client .send_and_confirm_message( signer, Message::new( &[realloc(&realloc_program_id, &pubkey, 0, &mut bump)], Some(&mint_pubkey), ), ) .unwrap(); let data = bank_client.get_account_data(&pubkey).unwrap().unwrap(); assert_eq!(0, data.len()); // Realloc to 100 and check via CPI let invoke_account = AccountSharedData::new(START_BALANCE, 5, &realloc_invoke_program_id); bank.store_account(&invoke_pubkey, &invoke_account); bank_client .send_and_confirm_message( signer, Message::new( &[Instruction::new_with_bytes( realloc_invoke_program_id, &[INVOKE_REALLOC_INVOKE_CHECK], vec![ AccountMeta::new(invoke_pubkey, false), AccountMeta::new_readonly(realloc_program_id, false), ], )], Some(&mint_pubkey), ), ) .unwrap(); let data = bank_client .get_account_data(&invoke_pubkey) .unwrap() .unwrap(); assert_eq!(100, data.len()); for i in 0..5 { assert_eq!(data[i], 0); } for i in 5..data.len() { assert_eq!(data[i], 2); } // Create account, realloc, check let new_keypair = Keypair::new(); let new_pubkey = new_keypair.pubkey().clone(); let mut instruction_data = vec![]; instruction_data.extend_from_slice(&[INVOKE_CREATE_ACCOUNT_REALLOC_CHECK, 1]); instruction_data.extend_from_slice(&100_usize.to_le_bytes()); bank_client .send_and_confirm_message( &[&mint_keypair, &new_keypair], Message::new( &[Instruction::new_with_bytes( realloc_invoke_program_id, &instruction_data, vec![ AccountMeta::new(mint_pubkey, true), AccountMeta::new(new_pubkey, true), AccountMeta::new(solana_sdk::system_program::id(), false), AccountMeta::new_readonly(realloc_invoke_program_id, false), ], )], Some(&mint_pubkey), ), ) .unwrap(); let data = bank_client.get_account_data(&new_pubkey).unwrap().unwrap(); assert_eq!(200, data.len()); let account = bank.get_account(&new_pubkey).unwrap(); assert_eq!(&realloc_invoke_program_id, account.owner()); // Invoke, dealloc, and assign let pre_len = 100; let new_len = pre_len * 2; let mut invoke_account = AccountSharedData::new(START_BALANCE, pre_len, &realloc_program_id); invoke_account.set_data_from_slice(&vec![1; pre_len]); bank.store_account(&invoke_pubkey, &invoke_account); let mut instruction_data = vec![]; instruction_data.extend_from_slice(&[INVOKE_DEALLOC_AND_ASSIGN, 1]); instruction_data.extend_from_slice(&pre_len.to_le_bytes()); bank_client .send_and_confirm_message( signer, Message::new( &[Instruction::new_with_bytes( realloc_invoke_program_id, &instruction_data, vec![ AccountMeta::new(invoke_pubkey, false), AccountMeta::new_readonly(realloc_invoke_program_id, false), AccountMeta::new_readonly(realloc_program_id, false), ], )], Some(&mint_pubkey), ), ) .unwrap(); let data = bank_client .get_account_data(&invoke_pubkey) .unwrap() .unwrap(); assert_eq!(new_len, data.len()); for i in 0..new_len { assert_eq!(data[i], 0); } // Realloc to max invoke max let invoke_account = AccountSharedData::new(42, 0, &realloc_invoke_program_id); bank.store_account(&invoke_pubkey, &invoke_account); assert_eq!( bank_client .send_and_confirm_message( signer, Message::new( &[Instruction::new_with_bytes( realloc_invoke_program_id, &[INVOKE_REALLOC_MAX_INVOKE_MAX], vec![ AccountMeta::new(invoke_pubkey, false), AccountMeta::new_readonly(realloc_program_id, false), ], )], Some(&mint_pubkey), ) ) .unwrap_err() .unwrap(), TransactionError::InstructionError(0, InstructionError::InvalidRealloc) ); // CPI realloc extend then local realloc extend for (cpi_extend_bytes, local_extend_bytes, should_succeed) in [ (0, 0, true), (MAX_PERMITTED_DATA_INCREASE, 0, true), (0, MAX_PERMITTED_DATA_INCREASE, true), (MAX_PERMITTED_DATA_INCREASE, 1, false), (1, MAX_PERMITTED_DATA_INCREASE, false), ] { let invoke_account = AccountSharedData::new(100_000_000, 0, &realloc_invoke_program_id); bank.store_account(&invoke_pubkey, &invoke_account); let mut instruction_data = vec![]; instruction_data.extend_from_slice(&[INVOKE_REALLOC_TO_THEN_LOCAL_REALLOC_EXTEND, 1]); instruction_data.extend_from_slice(&cpi_extend_bytes.to_le_bytes()); instruction_data.extend_from_slice(&local_extend_bytes.to_le_bytes()); let result = bank_client.send_and_confirm_message( signer, Message::new( &[Instruction::new_with_bytes( realloc_invoke_program_id, &instruction_data, vec![ AccountMeta::new(invoke_pubkey, false), AccountMeta::new_readonly(realloc_invoke_program_id, false), ], )], Some(&mint_pubkey), ), ); if should_succeed { assert!( result.is_ok(), "cpi: {cpi_extend_bytes} local: {local_extend_bytes}, err: {:?}", result.err() ); } else { assert_eq!( result.unwrap_err().unwrap(), TransactionError::InstructionError(0, InstructionError::InvalidRealloc), "cpi: {cpi_extend_bytes} local: {local_extend_bytes}", ); } } // Realloc invoke max twice let invoke_account = AccountSharedData::new(42, 0, &realloc_program_id); bank.store_account(&invoke_pubkey, &invoke_account); assert_eq!( bank_client .send_and_confirm_message( signer, Message::new( &[Instruction::new_with_bytes( realloc_invoke_program_id, &[INVOKE_INVOKE_MAX_TWICE], vec![ AccountMeta::new(invoke_pubkey, false), AccountMeta::new_readonly(realloc_invoke_program_id, false), AccountMeta::new_readonly(realloc_program_id, false), ], )], Some(&mint_pubkey), ) ) .unwrap_err() .unwrap(), TransactionError::InstructionError(0, InstructionError::InvalidRealloc) ); // Realloc to 0 bank_client .send_and_confirm_message( signer, Message::new( &[realloc(&realloc_program_id, &pubkey, 0, &mut bump)], Some(&mint_pubkey), ), ) .unwrap(); let data = bank_client.get_account_data(&pubkey).unwrap().unwrap(); assert_eq!(0, data.len()); // Realloc to max length in max increase increments for i in 0..MAX_PERMITTED_DATA_LENGTH as usize / MAX_PERMITTED_DATA_INCREASE { bank_client .send_and_confirm_message( signer, Message::new( &[Instruction::new_with_bytes( realloc_invoke_program_id, &[INVOKE_REALLOC_EXTEND_MAX, 1, i as u8, (i / 255) as u8], vec![ AccountMeta::new(pubkey, false), AccountMeta::new_readonly(realloc_program_id, false), ], )], Some(&mint_pubkey), ), ) .unwrap(); let data = bank_client.get_account_data(&pubkey).unwrap().unwrap(); assert_eq!((i + 1) * MAX_PERMITTED_DATA_INCREASE, data.len()); } for i in 0..data.len() { assert_eq!(data[i], 1); } // and one more time should fail assert_eq!( bank_client .send_and_confirm_message( signer, Message::new( &[Instruction::new_with_bytes( realloc_invoke_program_id, &[INVOKE_REALLOC_EXTEND_MAX, 2, 1, 1], vec![ AccountMeta::new(pubkey, false), AccountMeta::new_readonly(realloc_program_id, false), ], )], Some(&mint_pubkey), ) ) .unwrap_err() .unwrap(), TransactionError::InstructionError(0, InstructionError::InvalidRealloc) ); // Realloc rescursively and fill data let invoke_keypair = Keypair::new(); let invoke_pubkey = invoke_keypair.pubkey().clone(); let invoke_account = AccountSharedData::new(START_BALANCE, 0, &realloc_invoke_program_id); bank.store_account(&invoke_pubkey, &invoke_account); let mut instruction_data = vec![]; instruction_data.extend_from_slice(&[INVOKE_REALLOC_RECURSIVE, 1]); instruction_data.extend_from_slice(&100_usize.to_le_bytes()); bank_client .send_and_confirm_message( signer, Message::new( &[Instruction::new_with_bytes( realloc_invoke_program_id, &instruction_data, vec![ AccountMeta::new(invoke_pubkey, false), AccountMeta::new_readonly(realloc_invoke_program_id, false), ], )], Some(&mint_pubkey), ), ) .unwrap(); let data = bank_client .get_account_data(&invoke_pubkey) .unwrap() .unwrap(); assert_eq!(200, data.len()); for i in 0..100 { assert_eq!(data[i], 1); } for i in 100..200 { assert_eq!(data[i], 2); } } #[test] #[cfg(any(feature = "sbf_rust"))] fn test_program_sbf_processed_inner_instruction() { solana_logger::setup(); let GenesisConfigInfo { genesis_config, mint_keypair, .. } = create_genesis_config(50); let mut bank = Bank::new_for_tests(&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 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( &bank_client, &bpf_loader::id(), &mint_keypair, "solana_sbf_rust_invoke_and_return", ); let instruction2 = Instruction::new_with_bytes( noop_program_id, &[43], vec![ AccountMeta::new_readonly(noop_program_id, false), AccountMeta::new(mint_keypair.pubkey(), true), ], ); let instruction1 = Instruction::new_with_bytes( noop_program_id, &[42], vec![ AccountMeta::new(mint_keypair.pubkey(), true), AccountMeta::new_readonly(noop_program_id, false), ], ); let instruction0 = Instruction::new_with_bytes( sibling_program_id, &[1, 2, 3, 0, 4, 5, 6], vec![ AccountMeta::new(mint_keypair.pubkey(), true), AccountMeta::new_readonly(noop_program_id, false), AccountMeta::new_readonly(invoke_and_return_program_id, false), AccountMeta::new_readonly(sibling_inner_program_id, false), ], ); let message = Message::new( &[instruction2, instruction1, instruction0], Some(&mint_keypair.pubkey()), ); assert!(bank_client .send_and_confirm_message(&[&mint_keypair], message) .is_ok()); } #[test] #[cfg(feature = "sbf_rust")] fn test_program_fees() { solana_logger::setup(); let congestion_multiplier = 1; let GenesisConfigInfo { mut genesis_config, mint_keypair, .. } = create_genesis_config(500_000_000); genesis_config.fee_rate_governor = FeeRateGovernor::new(congestion_multiplier, 0); let mut bank = Bank::new_for_tests(&genesis_config); let fee_structure = FeeStructure::new(0.000005, 0.0, vec![(200, 0.0000005), (1400000, 0.000005)]); bank.fee_structure = fee_structure.clone(); bank.feature_set = Arc::new(FeatureSet::all_enabled()); let (name, id, entrypoint) = solana_bpf_loader_program!(); bank.add_builtin(&name, &id, entrypoint); let bank_client = BankClient::new(bank); let program_id = load_program( &bank_client, &bpf_loader::id(), &mint_keypair, "solana_sbf_rust_noop", ); let pre_balance = bank_client.get_balance(&mint_keypair.pubkey()).unwrap(); let message = Message::new( &[Instruction::new_with_bytes(program_id, &[], vec![])], Some(&mint_keypair.pubkey()), ); let sanitized_message = SanitizedMessage::try_from(message.clone()).unwrap(); let expected_normal_fee = Bank::calculate_fee( &sanitized_message, congestion_multiplier, &fee_structure, true, false, true, true, true, false, ); bank_client .send_and_confirm_message(&[&mint_keypair], message) .unwrap(); let post_balance = bank_client.get_balance(&mint_keypair.pubkey()).unwrap(); assert_eq!(pre_balance - post_balance, expected_normal_fee); let pre_balance = bank_client.get_balance(&mint_keypair.pubkey()).unwrap(); let message = Message::new( &[ ComputeBudgetInstruction::set_compute_unit_price(1), Instruction::new_with_bytes(program_id, &[], vec![]), ], Some(&mint_keypair.pubkey()), ); let sanitized_message = SanitizedMessage::try_from(message.clone()).unwrap(); let expected_prioritized_fee = Bank::calculate_fee( &sanitized_message, congestion_multiplier, &fee_structure, true, false, true, true, true, false, ); assert!(expected_normal_fee < expected_prioritized_fee); bank_client .send_and_confirm_message(&[&mint_keypair], message) .unwrap(); let post_balance = bank_client.get_balance(&mint_keypair.pubkey()).unwrap(); assert_eq!(pre_balance - post_balance, expected_prioritized_fee); } #[test] #[cfg(feature = "sbf_rust")] fn test_get_minimum_delegation() { let GenesisConfigInfo { genesis_config, mint_keypair, .. } = create_genesis_config(100_123_456_789); let mut bank = Bank::new_for_tests(&genesis_config); bank.feature_set = Arc::new(FeatureSet::all_enabled()); let (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_program( &bank_client, &bpf_loader::id(), &mint_keypair, "solana_sbf_rust_get_minimum_delegation", ); let account_metas = vec![AccountMeta::new_readonly(stake::program::id(), false)]; let instruction = Instruction::new_with_bytes(program_id, &[], account_metas); let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); assert!(result.is_ok()); } #[test] #[cfg(feature = "sbf_rust")] fn test_program_sbf_inner_instruction_alignment_checks() { solana_logger::setup(); let GenesisConfigInfo { genesis_config, mint_keypair, .. } = create_genesis_config(50); let mut bank = Bank::new_for_tests(&genesis_config); let (name, id, entrypoint) = solana_bpf_loader_program!(); bank.add_builtin(&name, &id, entrypoint); let (name, id, entrypoint) = solana_bpf_loader_deprecated_program!(); bank.add_builtin(&name, &id, entrypoint); 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 bank_client = BankClient::new(bank); 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()); }