diff --git a/program-runtime/src/invoke_context.rs b/program-runtime/src/invoke_context.rs index ed064ccd35..ec4dca5a36 100644 --- a/program-runtime/src/invoke_context.rs +++ b/program-runtime/src/invoke_context.rs @@ -9,7 +9,8 @@ use solana_sdk::{ compute_budget::ComputeBudget, feature_set::{ demote_program_write_locks, do_support_realloc, neon_evm_compute_budget, - remove_native_loader, requestable_heap_size, tx_wide_compute_cap, FeatureSet, + reject_empty_instruction_without_program, remove_native_loader, requestable_heap_size, + tx_wide_compute_cap, FeatureSet, }, hash::Hash, instruction::{AccountMeta, CompiledInstruction, Instruction, InstructionError}, @@ -218,6 +219,13 @@ impl<'a> InvokeContext<'a> { let program_id = program_indices .last() .map(|index_of_program_id| &self.accounts[*index_of_program_id].0); + if program_id.is_none() + && self + .feature_set + .is_active(&reject_empty_instruction_without_program::id()) + { + return Err(InstructionError::UnsupportedProgramId); + } if self.invoke_stack.is_empty() { let mut compute_budget = self.compute_budget; if !self.feature_set.is_active(&tx_wide_compute_cap::id()) diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index a6ef2f3745..2693519979 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -6506,6 +6506,7 @@ pub(crate) mod tests { compute_budget::ComputeBudgetInstruction, epoch_schedule::MINIMUM_SLOTS_PER_EPOCH, feature::Feature, + feature_set::reject_empty_instruction_without_program, genesis_config::create_genesis_config, hash, instruction::{AccountMeta, CompiledInstruction, Instruction, InstructionError}, @@ -15445,17 +15446,23 @@ pub(crate) mod tests { } #[test] - fn test_an_empty_transaction_without_program() { + fn test_an_empty_instruction_without_program() { let (genesis_config, mint_keypair) = create_genesis_config(1); - let bank = Bank::new_for_tests(&genesis_config); - let destination = solana_sdk::pubkey::new_rand(); let mut ix = system_instruction::transfer(&mint_keypair.pubkey(), &destination, 0); ix.program_id = native_loader::id(); // Empty executable account chain - let message = Message::new(&[ix], Some(&mint_keypair.pubkey())); let tx = Transaction::new(&[&mint_keypair], message, genesis_config.hash()); + + let bank = Bank::new_for_tests(&genesis_config); bank.process_transaction(&tx).unwrap(); + + let mut bank = Bank::new_for_tests(&genesis_config); + bank.activate_feature(&reject_empty_instruction_without_program::id()); + assert_eq!( + bank.process_transaction(&tx).unwrap_err(), + TransactionError::InstructionError(0, InstructionError::UnsupportedProgramId), + ); } #[test] diff --git a/sdk/src/feature_set.rs b/sdk/src/feature_set.rs index dcd32784f4..5d77e65823 100644 --- a/sdk/src/feature_set.rs +++ b/sdk/src/feature_set.rs @@ -257,6 +257,10 @@ pub mod leave_nonce_on_success { solana_sdk::declare_id!("E8MkiWZNNPGU6n55jkGzyj8ghUmjCHRmDFdYYFYHxWhQ"); } +pub mod reject_empty_instruction_without_program { + solana_sdk::declare_id!("9kdtFSrXHQg3hKkbXkQ6trJ3Ja1xpJ22CTFSNAciEwmL"); +} + lazy_static! { /// Map of feature identifiers to user-visible description pub static ref FEATURE_NAMES: HashMap = [ @@ -316,6 +320,7 @@ lazy_static! { (nonce_must_be_writable::id(), "nonce must be writable"), (spl_token_v3_3_0_release::id(), "spl-token v3.3.0 release"), (leave_nonce_on_success::id(), "leave nonce as is on success"), + (reject_empty_instruction_without_program::id(), "fail instructions which have native_loader as program_id directly"), /*************** ADD NEW FEATURES HERE ***************/ ] .iter()