diff --git a/Cargo.lock b/Cargo.lock index 1504f2d974f..5a94a28197b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -244,7 +244,7 @@ dependencies = [ "solana-inflation", "solana-keypair", "solana-ledger", - "solana-loader-v3-interface", + "solana-loader-v3-interface 4.0.1", "solana-log-collector", "solana-logger", "solana-measure", @@ -6562,7 +6562,7 @@ dependencies = [ "solana-fee-calculator", "solana-hash", "solana-instruction", - "solana-loader-v3-interface", + "solana-loader-v3-interface 4.0.1", "solana-nonce", "solana-program-option", "solana-program-pack", @@ -7099,7 +7099,7 @@ dependencies = [ "solana-instruction", "solana-keccak-hasher", "solana-last-restart-slot", - "solana-loader-v3-interface", + "solana-loader-v3-interface 4.0.1", "solana-loader-v4-interface", "solana-log-collector", "solana-measure", @@ -7139,7 +7139,7 @@ dependencies = [ "solana-bpf-loader-program", "solana-instruction", "solana-keypair", - "solana-loader-v3-interface", + "solana-loader-v3-interface 4.0.1", "solana-program-test", "solana-pubkey", "solana-sdk-ids", @@ -7352,7 +7352,7 @@ dependencies = [ "solana-hash", "solana-instruction", "solana-keypair", - "solana-loader-v3-interface", + "solana-loader-v3-interface 4.0.1", "solana-loader-v4-interface", "solana-loader-v4-program", "solana-logger", @@ -7790,7 +7790,7 @@ dependencies = [ "solana-instruction", "solana-keypair", "solana-ledger", - "solana-loader-v3-interface", + "solana-loader-v3-interface 4.0.1", "solana-logger", "solana-measure", "solana-message", @@ -8339,7 +8339,7 @@ dependencies = [ "solana-inflation", "solana-keypair", "solana-ledger", - "solana-loader-v3-interface", + "solana-loader-v3-interface 4.0.1", "solana-logger", "solana-native-token", "solana-poh-config", @@ -8808,6 +8808,21 @@ dependencies = [ "solana-system-interface", ] +[[package]] +name = "solana-loader-v3-interface" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5539bcadd5c3b306045563e9d102bbaa42b3643f335ae02bc9b5260a70ad9742" +dependencies = [ + "serde", + "serde_bytes", + "serde_derive", + "solana-instruction", + "solana-pubkey", + "solana-sdk-ids", + "solana-system-interface", +] + [[package]] name = "solana-loader-v4-interface" version = "2.2.1" @@ -8835,7 +8850,7 @@ dependencies = [ "solana-bpf-loader-program", "solana-clock", "solana-instruction", - "solana-loader-v3-interface", + "solana-loader-v3-interface 4.0.1", "solana-loader-v4-interface", "solana-log-collector", "solana-measure", @@ -9336,7 +9351,7 @@ dependencies = [ "solana-keccak-hasher", "solana-last-restart-slot", "solana-loader-v2-interface", - "solana-loader-v3-interface", + "solana-loader-v3-interface 3.0.0", "solana-loader-v4-interface", "solana-message", "solana-msg", @@ -9499,7 +9514,7 @@ dependencies = [ "solana-hash", "solana-instruction", "solana-keypair", - "solana-loader-v3-interface", + "solana-loader-v3-interface 4.0.1", "solana-log-collector", "solana-logger", "solana-message", @@ -10081,7 +10096,7 @@ dependencies = [ "solana-instruction", "solana-keypair", "solana-lattice-hash", - "solana-loader-v3-interface", + "solana-loader-v3-interface 4.0.1", "solana-loader-v4-interface", "solana-logger", "solana-measure", @@ -10798,7 +10813,7 @@ dependencies = [ "solana-instruction", "solana-instructions-sysvar", "solana-keypair", - "solana-loader-v3-interface", + "solana-loader-v3-interface 4.0.1", "solana-loader-v4-interface", "solana-loader-v4-program", "solana-log-collector", @@ -11040,7 +11055,7 @@ dependencies = [ "solana-instruction", "solana-keypair", "solana-ledger", - "solana-loader-v3-interface", + "solana-loader-v3-interface 4.0.1", "solana-logger", "solana-message", "solana-native-token", @@ -11414,7 +11429,7 @@ dependencies = [ "solana-hash", "solana-instruction", "solana-loader-v2-interface", - "solana-loader-v3-interface", + "solana-loader-v3-interface 4.0.1", "solana-message", "solana-program-option", "solana-pubkey", diff --git a/Cargo.toml b/Cargo.toml index 760a6479a3c..6ed7656a09f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -462,7 +462,7 @@ solana-last-restart-slot = "2.2.1" solana-lattice-hash = { path = "lattice-hash", version = "=2.3.0" } solana-ledger = { path = "ledger", version = "=2.3.0" } solana-loader-v2-interface = "2.2.1" -solana-loader-v3-interface = "3.0.0" +solana-loader-v3-interface = "4.0.1" solana-loader-v4-interface = "2.2.1" solana-loader-v4-program = { path = "programs/loader-v4", version = "=2.3.0" } solana-local-cluster = { path = "local-cluster", version = "=2.3.0" } diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 1fd279bacb6..b830d7c4569 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -52,7 +52,7 @@ solana-fee-structure = "=2.2.1" solana-hash = "=2.2.1" solana-instruction = "=2.2.1" solana-keypair = "=2.2.1" -solana-loader-v3-interface = "=3.0.0" +solana-loader-v3-interface = { version = "=4.0.1", features = ["bincode"] } solana-loader-v4-interface = "=2.2.1" solana-loader-v4-program = { workspace = true } solana-logger = "=2.3.1" diff --git a/cli/src/program.rs b/cli/src/program.rs index 1c7f727f232..89080edf0de 100644 --- a/cli/src/program.rs +++ b/cli/src/program.rs @@ -169,8 +169,9 @@ pub enum ProgramCliCommand { use_lamports_unit: bool, bypass_warning: bool, }, - ExtendProgram { + ExtendProgramChecked { program_pubkey: Pubkey, + authority_signer_index: SignerIndex, additional_bytes: u32, }, MigrateProgram { @@ -1009,17 +1010,22 @@ pub fn parse_program_subcommand( let program_pubkey = pubkey_of(matches, "program_id").unwrap(); let additional_bytes = value_of(matches, "additional_bytes").unwrap(); + let (authority_signer, authority_pubkey) = + signer_of(matches, "authority", wallet_manager)?; + let signer_info = default_signer.generate_unique_signers( - vec![Some( - default_signer.signer_from_path(matches, wallet_manager)?, - )], + vec![ + Some(default_signer.signer_from_path(matches, wallet_manager)?), + authority_signer, + ], matches, wallet_manager, )?; CliCommandInfo { - command: CliCommand::Program(ProgramCliCommand::ExtendProgram { + command: CliCommand::Program(ProgramCliCommand::ExtendProgramChecked { program_pubkey, + authority_signer_index: signer_info.index_of(authority_pubkey).unwrap(), additional_bytes, }), signers: signer_info.signers, @@ -1231,10 +1237,17 @@ pub fn process_program_subcommand( *use_lamports_unit, *bypass_warning, ), - ProgramCliCommand::ExtendProgram { + ProgramCliCommand::ExtendProgramChecked { program_pubkey, + authority_signer_index, additional_bytes, - } => process_extend_program(&rpc_client, config, *program_pubkey, *additional_bytes), + } => process_extend_program( + &rpc_client, + config, + *program_pubkey, + *authority_signer_index, + *additional_bytes, + ), ProgramCliCommand::MigrateProgram { program_pubkey, authority_signer_index, @@ -2366,9 +2379,11 @@ fn process_extend_program( rpc_client: &RpcClient, config: &CliConfig, program_pubkey: Pubkey, + authority_signer_index: SignerIndex, additional_bytes: u32, ) -> ProcessResult { let payer_pubkey = config.signers[0].pubkey(); + let authority_signer = config.signers[authority_signer_index]; if additional_bytes == 0 { return Err("Additional bytes must be greater than zero".into()); @@ -2411,23 +2426,39 @@ fn process_extend_program( _ => Err(format!("Program {program_pubkey} is closed")), }?; - match upgrade_authority_address { - None => Err(format!("Program {program_pubkey} is not upgradeable")), - _ => Ok(()), - }?; + let upgrade_authority_address = upgrade_authority_address + .ok_or_else(|| format!("Program {program_pubkey} is not upgradeable"))?; - let blockhash = rpc_client.get_latest_blockhash()?; + if authority_signer.pubkey() != upgrade_authority_address { + return Err(format!( + "Upgrade authority {} does not match {}", + upgrade_authority_address, + authority_signer.pubkey(), + ) + .into()); + } - let mut tx = Transaction::new_unsigned(Message::new( - &[loader_v3_instruction::extend_program( - &program_pubkey, - Some(&payer_pubkey), - additional_bytes, - )], - Some(&payer_pubkey), - )); + let blockhash = rpc_client.get_latest_blockhash()?; + let feature_set = fetch_feature_set(rpc_client)?; + + let instruction = + if feature_set.is_active(&agave_feature_set::enable_extend_program_checked::id()) { + loader_v3_instruction::extend_program_checked( + &program_pubkey, + &upgrade_authority_address, + Some(&payer_pubkey), + additional_bytes, + ) + } else { + loader_v3_instruction::extend_program( + &program_pubkey, + Some(&payer_pubkey), + additional_bytes, + ) + }; + let mut tx = Transaction::new_unsigned(Message::new(&[instruction], Some(&payer_pubkey))); - tx.try_sign(&[config.signers[0]], blockhash)?; + tx.try_sign(&[config.signers[0], authority_signer], blockhash)?; let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config( &tx, config.commitment, @@ -2972,6 +3003,17 @@ fn extend_program_data_if_needed( return Ok(()); }; + let upgrade_authority_address = match program_data_account.state() { + Ok(UpgradeableLoaderState::ProgramData { + slot: _, + upgrade_authority_address, + }) => Ok(upgrade_authority_address), + _ => Err(format!("Program {program_id} is closed")), + }?; + + let upgrade_authority_address = upgrade_authority_address + .ok_or_else(|| format!("Program {program_id} is not upgradeable"))?; + let required_len = UpgradeableLoaderState::size_of_programdata(program_len); let max_permitted_data_length = usize::try_from(MAX_PERMITTED_DATA_LENGTH).unwrap(); if required_len > max_permitted_data_length { @@ -2993,11 +3035,20 @@ fn extend_program_data_if_needed( let additional_bytes = u32::try_from(additional_bytes).expect("`u32` is big enough to hold an account size"); - initial_instructions.push(loader_v3_instruction::extend_program( - program_id, - Some(fee_payer), - additional_bytes, - )); + + let feature_set = fetch_feature_set(rpc_client)?; + let instruction = + if feature_set.is_active(&agave_feature_set::enable_extend_program_checked::id()) { + loader_v3_instruction::extend_program_checked( + program_id, + &upgrade_authority_address, + Some(fee_payer), + additional_bytes, + ) + } else { + loader_v3_instruction::extend_program(program_id, Some(fee_payer), additional_bytes) + }; + initial_instructions.push(instruction); Ok(()) } @@ -3096,7 +3147,12 @@ fn send_deploy_messages( // account to sign the transaction. One (transfer) only requires the fee-payer signature. // This check is to ensure signing does not fail on a KeypairPubkeyMismatch error from an // extraneous signature. - if message.header.num_required_signatures == 2 { + if message.header.num_required_signatures == 3 { + initial_transaction.try_sign( + &[fee_payer_signer, initial_signer, write_signer.unwrap()], + blockhash, + )?; + } else if message.header.num_required_signatures == 2 { initial_transaction.try_sign(&[fee_payer_signer, initial_signer], blockhash)?; } else { initial_transaction.try_sign(&[fee_payer_signer], blockhash)?; @@ -4406,8 +4462,9 @@ mod tests { assert_eq!( parse_command(&test_command, &default_signer, &mut None).unwrap(), CliCommandInfo { - command: CliCommand::Program(ProgramCliCommand::ExtendProgram { + command: CliCommand::Program(ProgramCliCommand::ExtendProgramChecked { program_pubkey, + authority_signer_index: 0, additional_bytes }), signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())], diff --git a/cli/tests/program.rs b/cli/tests/program.rs index 189b70ade26..5c9dea15e0f 100644 --- a/cli/tests/program.rs +++ b/cli/tests/program.rs @@ -1443,9 +1443,10 @@ fn test_cli_program_extend_program() { file.read_to_end(&mut new_program_data).unwrap(); let new_max_len = new_program_data.len(); let additional_bytes = (new_max_len - max_len) as u32; - config.signers = vec![&keypair]; - config.command = CliCommand::Program(ProgramCliCommand::ExtendProgram { + config.signers = vec![&keypair, &upgrade_authority]; + config.command = CliCommand::Program(ProgramCliCommand::ExtendProgramChecked { program_pubkey: program_keypair.pubkey(), + authority_signer_index: 1, additional_bytes: additional_bytes - 1, }); process_command(&config).unwrap(); @@ -1491,9 +1492,10 @@ fn test_cli_program_extend_program() { wait_n_slots(&rpc_client, 1); // Extend 1 last byte - config.signers = vec![&keypair]; - config.command = CliCommand::Program(ProgramCliCommand::ExtendProgram { + config.signers = vec![&keypair, &upgrade_authority]; + config.command = CliCommand::Program(ProgramCliCommand::ExtendProgramChecked { program_pubkey: program_keypair.pubkey(), + authority_signer_index: 1, additional_bytes: 1, }); process_command(&config).unwrap(); diff --git a/feature-set/src/lib.rs b/feature-set/src/lib.rs index 02d92c361c2..ffb965c48ba 100644 --- a/feature-set/src/lib.rs +++ b/feature-set/src/lib.rs @@ -152,6 +152,7 @@ impl FeatureSet { loosen_cpi_size_restriction: self.is_active(&loosen_cpi_size_restriction::id()), increase_tx_account_lock_limit: self.is_active(&increase_tx_account_lock_limit::id()), disable_rent_fees_collection: self.is_active(&disable_rent_fees_collection::id()), + enable_extend_program_checked: self.is_active(&enable_extend_program_checked::id()), } } } @@ -1090,6 +1091,10 @@ pub mod enshrine_slashing_program { solana_pubkey::declare_id!("sProgVaNWkYdP2eTRAy1CPrgb3b9p8yXCASrPEqo6VJ"); } +pub mod enable_extend_program_checked { + solana_pubkey::declare_id!("97QCmR4QtfeQsAti9srfHFk5uMRFP95CvXG8EGr615HM"); +} + pub static FEATURE_NAMES: LazyLock> = LazyLock::new(|| { [ (secp256k1_program_enabled::id(), "secp256k1 program"), @@ -1324,6 +1329,7 @@ pub static FEATURE_NAMES: LazyLock> = LazyLock::n (raise_block_limits_to_60m::id(), "Raise block limit to 60M SIMD-0256"), (mask_out_rent_epoch_in_vm_serialization::id(), "SIMD-0267: Sets rent_epoch to a constant in the VM"), (enshrine_slashing_program::id(), "SIMD-0204: Slashable event verification"), + (enable_extend_program_checked::id(), "Enable ExtendProgramChecked instruction"), /*************** ADD NEW FEATURES HERE ***************/ ] .iter() diff --git a/genesis/Cargo.toml b/genesis/Cargo.toml index b1ffdad255a..1ca9bcde1b7 100644 --- a/genesis/Cargo.toml +++ b/genesis/Cargo.toml @@ -32,7 +32,7 @@ solana-genesis-config = "=2.2.1" solana-inflation = "=2.2.1" solana-keypair = "=2.2.1" solana-ledger = { workspace = true } -solana-loader-v3-interface = "=3.0.0" +solana-loader-v3-interface = "=4.0.1" solana-logger = "=2.3.1" solana-native-token = "=2.2.2" solana-poh-config = "=2.2.1" diff --git a/programs/bpf-loader-tests/tests/extend_program_ix.rs b/programs/bpf-loader-tests/tests/extend_program_ix.rs index 35dd4f54958..54363a92a15 100644 --- a/programs/bpf-loader-tests/tests/extend_program_ix.rs +++ b/programs/bpf-loader-tests/tests/extend_program_ix.rs @@ -4,7 +4,9 @@ use { solana_account::{AccountSharedData, ReadableAccount, WritableAccount}, solana_instruction::error::InstructionError, solana_keypair::Keypair, - solana_loader_v3_interface::{instruction::extend_program, state::UpgradeableLoaderState}, + solana_loader_v3_interface::{ + instruction::extend_program_checked, state::UpgradeableLoaderState, + }, solana_program_test::*, solana_pubkey::Pubkey, solana_sdk_ids::bpf_loader_upgradeable::id, @@ -24,6 +26,7 @@ async fn test_extend_program() { let mut context = setup_test_context().await; let program_file = find_file("noop.so").expect("Failed to find the file"); let data = read_file(program_file); + let upgrade_authority = Keypair::new(); let program_address = Pubkey::new_unique(); let (programdata_address, _) = Pubkey::find_program_address(&[program_address.as_ref()], &id()); @@ -44,7 +47,7 @@ async fn test_extend_program() { &programdata_address, &UpgradeableLoaderState::ProgramData { slot: 0, - upgrade_authority_address: Some(Pubkey::new_unique()), + upgrade_authority_address: Some(upgrade_authority.pubkey()), }, program_data_len, |account| account.data_as_mut_slice()[programdata_data_offset..].copy_from_slice(&data), @@ -56,13 +59,14 @@ async fn test_extend_program() { let recent_blockhash = context.last_blockhash; const ADDITIONAL_BYTES: u32 = 42; let transaction = Transaction::new_signed_with_payer( - &[extend_program( + &[extend_program_checked( &program_address, + &upgrade_authority.pubkey(), Some(&payer.pubkey()), ADDITIONAL_BYTES, )], Some(&payer.pubkey()), - &[payer], + &[payer, &upgrade_authority], recent_blockhash, ); @@ -83,6 +87,7 @@ async fn test_failed_extend_twice_in_same_slot() { let mut context = setup_test_context().await; let program_file = find_file("noop.so").expect("Failed to find the file"); let data = read_file(program_file); + let upgrade_authority = Keypair::new(); let program_address = Pubkey::new_unique(); let (programdata_address, _) = Pubkey::find_program_address(&[program_address.as_ref()], &id()); @@ -103,7 +108,7 @@ async fn test_failed_extend_twice_in_same_slot() { &programdata_address, &UpgradeableLoaderState::ProgramData { slot: 0, - upgrade_authority_address: Some(Pubkey::new_unique()), + upgrade_authority_address: Some(upgrade_authority.pubkey()), }, program_data_len, |account| account.data_as_mut_slice()[programdata_data_offset..].copy_from_slice(&data), @@ -115,13 +120,14 @@ async fn test_failed_extend_twice_in_same_slot() { let recent_blockhash = context.last_blockhash; const ADDITIONAL_BYTES: u32 = 42; let transaction = Transaction::new_signed_with_payer( - &[extend_program( + &[extend_program_checked( &program_address, + &upgrade_authority.pubkey(), Some(&payer.pubkey()), ADDITIONAL_BYTES, )], Some(&payer.pubkey()), - &[payer], + &[payer, &upgrade_authority], recent_blockhash, ); @@ -142,13 +148,14 @@ async fn test_failed_extend_twice_in_same_slot() { .unwrap(); // Extending the program in the same slot should fail let transaction = Transaction::new_signed_with_payer( - &[extend_program( + &[extend_program_checked( &program_address, + &upgrade_authority.pubkey(), Some(&payer.pubkey()), ADDITIONAL_BYTES, )], Some(&payer.pubkey()), - &[payer], + &[payer, &upgrade_authority], recent_blockhash, ); @@ -162,6 +169,88 @@ async fn test_failed_extend_twice_in_same_slot() { ); } +#[tokio::test] +async fn test_failed_extend_upgrade_authority_did_not_sign() { + let mut context = setup_test_context().await; + let program_file = find_file("noop.so").expect("Failed to find the file"); + let data = read_file(program_file); + let upgrade_authority = Keypair::new(); + + let program_address = Pubkey::new_unique(); + let (programdata_address, _) = Pubkey::find_program_address(&[program_address.as_ref()], &id()); + add_upgradeable_loader_account( + &mut context, + &program_address, + &UpgradeableLoaderState::Program { + programdata_address, + }, + UpgradeableLoaderState::size_of_program(), + |_| {}, + ) + .await; + let programdata_data_offset = UpgradeableLoaderState::size_of_programdata_metadata(); + let program_data_len = data.len() + programdata_data_offset; + add_upgradeable_loader_account( + &mut context, + &programdata_address, + &UpgradeableLoaderState::ProgramData { + slot: 0, + upgrade_authority_address: Some(upgrade_authority.pubkey()), + }, + program_data_len, + |account| account.data_as_mut_slice()[programdata_data_offset..].copy_from_slice(&data), + ) + .await; + + let client = &mut context.banks_client; + let payer = &context.payer; + let recent_blockhash = context.last_blockhash; + const ADDITIONAL_BYTES: u32 = 42; + let transaction = Transaction::new_signed_with_payer( + &[extend_program_checked( + &program_address, + &payer.pubkey(), + Some(&payer.pubkey()), + ADDITIONAL_BYTES, + )], + Some(&payer.pubkey()), + &[payer], + recent_blockhash, + ); + + assert_matches!( + client + .process_transaction(transaction) + .await + .unwrap_err() + .unwrap(), + TransactionError::InstructionError(0, InstructionError::IncorrectAuthority) + ); + + let mut ix = extend_program_checked( + &program_address, + &upgrade_authority.pubkey(), + Some(&payer.pubkey()), + ADDITIONAL_BYTES, + ); + ix.accounts[2].is_signer = false; + let transaction = Transaction::new_signed_with_payer( + &[ix], + Some(&payer.pubkey()), + &[payer], + recent_blockhash, + ); + + assert_matches!( + client + .process_transaction(transaction) + .await + .unwrap_err() + .unwrap(), + TransactionError::InstructionError(0, InstructionError::MissingRequiredSignature) + ); +} + #[tokio::test] async fn test_extend_program_not_upgradeable() { let mut context = setup_test_context().await; @@ -193,7 +282,7 @@ async fn test_extend_program_not_upgradeable() { let payer_address = context.payer.pubkey(); assert_ix_error( &mut context, - extend_program(&program_address, Some(&payer_address), 42), + extend_program_checked(&program_address, &payer_address, Some(&payer_address), 42), None, InstructionError::Immutable, "should fail because the program data account isn't upgradeable", @@ -204,6 +293,7 @@ async fn test_extend_program_not_upgradeable() { #[tokio::test] async fn test_extend_program_by_zero_bytes() { let mut context = setup_test_context().await; + let upgrade_authority = Keypair::new(); let program_address = Pubkey::new_unique(); let (programdata_address, _) = Pubkey::find_program_address(&[program_address.as_ref()], &id()); @@ -222,7 +312,7 @@ async fn test_extend_program_by_zero_bytes() { &programdata_address, &UpgradeableLoaderState::ProgramData { slot: 0, - upgrade_authority_address: Some(Pubkey::new_unique()), + upgrade_authority_address: Some(upgrade_authority.pubkey()), }, 100, |_| {}, @@ -232,8 +322,13 @@ async fn test_extend_program_by_zero_bytes() { let payer_address = context.payer.pubkey(); assert_ix_error( &mut context, - extend_program(&program_address, Some(&payer_address), 0), - None, + extend_program_checked( + &program_address, + &upgrade_authority.pubkey(), + Some(&payer_address), + 0, + ), + Some(&upgrade_authority), InstructionError::InvalidInstructionData, "should fail because the program data account must be extended by more than 0 bytes", ) @@ -243,6 +338,7 @@ async fn test_extend_program_by_zero_bytes() { #[tokio::test] async fn test_extend_program_past_max_size() { let mut context = setup_test_context().await; + let upgrade_authority = Keypair::new(); let program_address = Pubkey::new_unique(); let (programdata_address, _) = Pubkey::find_program_address(&[program_address.as_ref()], &id()); @@ -261,7 +357,7 @@ async fn test_extend_program_past_max_size() { &programdata_address, &UpgradeableLoaderState::ProgramData { slot: 0, - upgrade_authority_address: Some(Pubkey::new_unique()), + upgrade_authority_address: Some(upgrade_authority.pubkey()), }, MAX_PERMITTED_DATA_LENGTH as usize, |_| {}, @@ -271,8 +367,13 @@ async fn test_extend_program_past_max_size() { let payer_address = context.payer.pubkey(); assert_ix_error( &mut context, - extend_program(&program_address, Some(&payer_address), 1), - None, + extend_program_checked( + &program_address, + &upgrade_authority.pubkey(), + Some(&payer_address), + 1, + ), + Some(&upgrade_authority), InstructionError::InvalidRealloc, "should fail because the program data account cannot be extended past the max data size", ) @@ -283,6 +384,7 @@ async fn test_extend_program_past_max_size() { async fn test_extend_program_with_invalid_payer() { let mut context = setup_test_context().await; let rent = context.banks_client.get_rent().await.unwrap(); + let upgrade_authority_address = context.payer.pubkey(); let program_address = Pubkey::new_unique(); let (programdata_address, _) = Pubkey::find_program_address(&[program_address.as_ref()], &id()); @@ -301,7 +403,7 @@ async fn test_extend_program_with_invalid_payer() { &programdata_address, &UpgradeableLoaderState::ProgramData { slot: 0, - upgrade_authority_address: Some(Pubkey::new_unique()), + upgrade_authority_address: Some(upgrade_authority_address), }, 100, |_| {}, @@ -328,8 +430,9 @@ async fn test_extend_program_with_invalid_payer() { assert_ix_error( &mut context, - extend_program( + extend_program_checked( &program_address, + &upgrade_authority_address, Some(&payer_with_insufficient_funds.pubkey()), 1024, ), @@ -341,8 +444,9 @@ async fn test_extend_program_with_invalid_payer() { assert_ix_error( &mut context, - extend_program( + extend_program_checked( &program_address, + &upgrade_authority_address, Some(&payer_with_invalid_owner.pubkey()), 1, ), @@ -352,8 +456,9 @@ async fn test_extend_program_with_invalid_payer() { ) .await; - let mut ix = extend_program( + let mut ix = extend_program_checked( &program_address, + &upgrade_authority_address, Some(&payer_with_sufficient_funds.pubkey()), 1, ); @@ -385,6 +490,7 @@ async fn test_extend_program_without_payer() { let program_file = find_file("noop.so").expect("Failed to find the file"); let data = read_file(program_file); + let upgrade_authority = Keypair::new(); let program_address = Pubkey::new_unique(); let (programdata_address, _) = Pubkey::find_program_address(&[program_address.as_ref()], &id()); @@ -405,7 +511,7 @@ async fn test_extend_program_without_payer() { &programdata_address, &UpgradeableLoaderState::ProgramData { slot: 0, - upgrade_authority_address: Some(Pubkey::new_unique()), + upgrade_authority_address: Some(upgrade_authority.pubkey()), }, program_data_len, |account| account.data_as_mut_slice()[programdata_data_offset..].copy_from_slice(&data), @@ -414,8 +520,8 @@ async fn test_extend_program_without_payer() { assert_ix_error( &mut context, - extend_program(&program_address, None, 1024), - None, + extend_program_checked(&program_address, &upgrade_authority.pubkey(), None, 1024), + Some(&upgrade_authority), InstructionError::NotEnoughAccountKeys, "should fail because program data has insufficient funds to cover rent", ) @@ -437,10 +543,15 @@ async fn test_extend_program_without_payer() { &programdata_address, min_balance_increase_for_extend, ), - extend_program(&program_address, None, ADDITIONAL_BYTES), + extend_program_checked( + &program_address, + &upgrade_authority.pubkey(), + None, + ADDITIONAL_BYTES, + ), ], Some(&payer.pubkey()), - &[payer], + &[payer, &upgrade_authority], recent_blockhash, ); @@ -459,6 +570,7 @@ async fn test_extend_program_without_payer() { #[tokio::test] async fn test_extend_program_with_invalid_system_program() { let mut context = setup_test_context().await; + let upgrade_authority = Keypair::new(); let program_address = Pubkey::new_unique(); let (programdata_address, _) = Pubkey::find_program_address(&[program_address.as_ref()], &id()); @@ -478,7 +590,7 @@ async fn test_extend_program_with_invalid_system_program() { &programdata_address, &UpgradeableLoaderState::ProgramData { slot: 0, - upgrade_authority_address: Some(Pubkey::new_unique()), + upgrade_authority_address: Some(upgrade_authority.pubkey()), }, program_data_len, |_| {}, @@ -486,7 +598,12 @@ async fn test_extend_program_with_invalid_system_program() { .await; let payer_address = context.payer.pubkey(); - let mut ix = extend_program(&program_address, Some(&payer_address), 1); + let mut ix = extend_program_checked( + &program_address, + &upgrade_authority.pubkey(), + Some(&payer_address), + 1, + ); // Change system program to an invalid key { @@ -501,7 +618,7 @@ async fn test_extend_program_with_invalid_system_program() { assert_ix_error( &mut context, ix, - None, + Some(&upgrade_authority), InstructionError::MissingAccount, "should fail because the system program is missing", ) @@ -512,6 +629,7 @@ async fn test_extend_program_with_invalid_system_program() { async fn test_extend_program_with_mismatch_program_data() { let mut context = setup_test_context().await; let payer_address = context.payer.pubkey(); + let upgrade_authority = Keypair::new(); let program_address = Pubkey::new_unique(); let (programdata_address, _) = Pubkey::find_program_address(&[program_address.as_ref()], &id()); @@ -532,14 +650,19 @@ async fn test_extend_program_with_mismatch_program_data() { &mismatch_programdata_address, &UpgradeableLoaderState::ProgramData { slot: 0, - upgrade_authority_address: Some(Pubkey::new_unique()), + upgrade_authority_address: Some(upgrade_authority.pubkey()), }, 100, |_| {}, ) .await; - let mut ix = extend_program(&program_address, Some(&payer_address), 1); + let mut ix = extend_program_checked( + &program_address, + &upgrade_authority.pubkey(), + Some(&payer_address), + 1, + ); // Replace ProgramData account meta with invalid account { @@ -554,7 +677,7 @@ async fn test_extend_program_with_mismatch_program_data() { assert_ix_error( &mut context, ix, - None, + Some(&upgrade_authority), InstructionError::InvalidArgument, "should fail because the program data account doesn't match the program", ) @@ -565,6 +688,7 @@ async fn test_extend_program_with_mismatch_program_data() { async fn test_extend_program_with_readonly_program_data() { let mut context = setup_test_context().await; let payer_address = context.payer.pubkey(); + let upgrade_authority = Keypair::new(); let program_address = Pubkey::new_unique(); let (programdata_address, _) = Pubkey::find_program_address(&[program_address.as_ref()], &id()); @@ -583,14 +707,19 @@ async fn test_extend_program_with_readonly_program_data() { &programdata_address, &UpgradeableLoaderState::ProgramData { slot: 0, - upgrade_authority_address: Some(Pubkey::new_unique()), + upgrade_authority_address: Some(upgrade_authority.pubkey()), }, 100, |_| {}, ) .await; - let mut ix = extend_program(&program_address, Some(&payer_address), 1); + let mut ix = extend_program_checked( + &program_address, + &upgrade_authority.pubkey(), + Some(&payer_address), + 1, + ); // Demote ProgramData account meta to read-only { @@ -605,7 +734,7 @@ async fn test_extend_program_with_readonly_program_data() { assert_ix_error( &mut context, ix, - None, + Some(&upgrade_authority), InstructionError::InvalidArgument, "should fail because the program data account is not writable", ) @@ -616,6 +745,7 @@ async fn test_extend_program_with_readonly_program_data() { async fn test_extend_program_with_invalid_program_data_state() { let mut context = setup_test_context().await; let payer_address = context.payer.pubkey(); + let upgrade_authority = Keypair::new(); let program_address = Pubkey::new_unique(); let (programdata_address, _) = Pubkey::find_program_address(&[program_address.as_ref()], &id()); @@ -642,8 +772,13 @@ async fn test_extend_program_with_invalid_program_data_state() { assert_ix_error( &mut context, - extend_program(&program_address, Some(&payer_address), 1024), - None, + extend_program_checked( + &program_address, + &upgrade_authority.pubkey(), + Some(&payer_address), + 1024, + ), + Some(&upgrade_authority), InstructionError::InvalidAccountData, "should fail because the program data account state isn't valid", ) @@ -683,7 +818,7 @@ async fn test_extend_program_with_invalid_program_data_owner() { assert_ix_error( &mut context, - extend_program(&program_address, Some(&payer_address), 1024), + extend_program_checked(&program_address, &payer_address, Some(&payer_address), 1024), None, InstructionError::InvalidAccountOwner, "should fail because the program data account owner isn't valid", @@ -695,6 +830,7 @@ async fn test_extend_program_with_invalid_program_data_owner() { async fn test_extend_program_with_readonly_program() { let mut context = setup_test_context().await; let payer_address = context.payer.pubkey(); + let upgrade_authority = Keypair::new(); let program_address = Pubkey::new_unique(); let (programdata_address, _) = Pubkey::find_program_address(&[program_address.as_ref()], &id()); @@ -713,14 +849,19 @@ async fn test_extend_program_with_readonly_program() { &programdata_address, &UpgradeableLoaderState::ProgramData { slot: 0, - upgrade_authority_address: Some(Pubkey::new_unique()), + upgrade_authority_address: Some(upgrade_authority.pubkey()), }, 100, |_| {}, ) .await; - let mut ix = extend_program(&program_address, Some(&payer_address), 1); + let mut ix = extend_program_checked( + &program_address, + &upgrade_authority.pubkey(), + Some(&payer_address), + 1, + ); // Demote Program account meta to read-only { @@ -735,7 +876,7 @@ async fn test_extend_program_with_readonly_program() { assert_ix_error( &mut context, ix, - None, + Some(&upgrade_authority), InstructionError::InvalidArgument, "should fail because the program account is not writable", ) @@ -746,6 +887,7 @@ async fn test_extend_program_with_readonly_program() { async fn test_extend_program_with_invalid_program_owner() { let mut context = setup_test_context().await; let payer_address = context.payer.pubkey(); + let upgrade_authority = Keypair::new(); let program_address = Pubkey::new_unique(); let (programdata_address, _) = Pubkey::find_program_address(&[program_address.as_ref()], &id()); @@ -765,7 +907,7 @@ async fn test_extend_program_with_invalid_program_owner() { &programdata_address, &UpgradeableLoaderState::ProgramData { slot: 0, - upgrade_authority_address: Some(Pubkey::new_unique()), + upgrade_authority_address: Some(upgrade_authority.pubkey()), }, 100, |_| {}, @@ -774,8 +916,13 @@ async fn test_extend_program_with_invalid_program_owner() { assert_ix_error( &mut context, - extend_program(&program_address, Some(&payer_address), 1024), - None, + extend_program_checked( + &program_address, + &upgrade_authority.pubkey(), + Some(&payer_address), + 1024, + ), + Some(&upgrade_authority), InstructionError::InvalidAccountOwner, "should fail because the program account owner isn't valid", ) @@ -786,6 +933,7 @@ async fn test_extend_program_with_invalid_program_owner() { async fn test_extend_program_with_invalid_program_state() { let mut context = setup_test_context().await; let payer_address = context.payer.pubkey(); + let upgrade_authority = Keypair::new(); let program_address = Pubkey::new_unique(); let (programdata_address, _) = Pubkey::find_program_address(&[program_address.as_ref()], &id()); @@ -805,7 +953,7 @@ async fn test_extend_program_with_invalid_program_state() { &programdata_address, &UpgradeableLoaderState::ProgramData { slot: 0, - upgrade_authority_address: Some(Pubkey::new_unique()), + upgrade_authority_address: Some(upgrade_authority.pubkey()), }, 100, |_| {}, @@ -814,8 +962,13 @@ async fn test_extend_program_with_invalid_program_state() { assert_ix_error( &mut context, - extend_program(&program_address, Some(&payer_address), 1024), - None, + extend_program_checked( + &program_address, + &upgrade_authority.pubkey(), + Some(&payer_address), + 1024, + ), + Some(&upgrade_authority), InstructionError::InvalidAccountData, "should fail because the program account state isn't valid", ) diff --git a/programs/bpf_loader/src/lib.rs b/programs/bpf_loader/src/lib.rs index 870b5c82dd9..cddd9a14079 100644 --- a/programs/bpf_loader/src/lib.rs +++ b/programs/bpf_loader/src/lib.rs @@ -1156,160 +1156,26 @@ fn process_loader_upgradeable_instruction( } } UpgradeableLoaderInstruction::ExtendProgram { additional_bytes } => { - if additional_bytes == 0 { - ic_logger_msg!(log_collector, "Additional bytes must be greater than 0"); - return Err(InstructionError::InvalidInstructionData); - } - - const PROGRAM_DATA_ACCOUNT_INDEX: IndexOfAccount = 0; - const PROGRAM_ACCOUNT_INDEX: IndexOfAccount = 1; - #[allow(dead_code)] - // System program is only required when a CPI is performed - const OPTIONAL_SYSTEM_PROGRAM_ACCOUNT_INDEX: IndexOfAccount = 2; - const OPTIONAL_PAYER_ACCOUNT_INDEX: IndexOfAccount = 3; - - let programdata_account = instruction_context - .try_borrow_instruction_account(transaction_context, PROGRAM_DATA_ACCOUNT_INDEX)?; - let programdata_key = *programdata_account.get_key(); - - if program_id != programdata_account.get_owner() { - ic_logger_msg!(log_collector, "ProgramData owner is invalid"); - return Err(InstructionError::InvalidAccountOwner); - } - if !programdata_account.is_writable() { - ic_logger_msg!(log_collector, "ProgramData is not writable"); - return Err(InstructionError::InvalidArgument); - } - - let program_account = instruction_context - .try_borrow_instruction_account(transaction_context, PROGRAM_ACCOUNT_INDEX)?; - if !program_account.is_writable() { - ic_logger_msg!(log_collector, "Program account is not writable"); - return Err(InstructionError::InvalidArgument); - } - if program_account.get_owner() != program_id { - ic_logger_msg!(log_collector, "Program account not owned by loader"); - return Err(InstructionError::InvalidAccountOwner); - } - let program_key = *program_account.get_key(); - match program_account.get_state()? { - UpgradeableLoaderState::Program { - programdata_address, - } => { - if programdata_address != programdata_key { - ic_logger_msg!( - log_collector, - "Program account does not match ProgramData account" - ); - return Err(InstructionError::InvalidArgument); - } - } - _ => { - ic_logger_msg!(log_collector, "Invalid Program account"); - return Err(InstructionError::InvalidAccountData); - } - } - drop(program_account); - - let old_len = programdata_account.get_data().len(); - let new_len = old_len.saturating_add(additional_bytes as usize); - if new_len > MAX_PERMITTED_DATA_LENGTH as usize { + if invoke_context + .get_feature_set() + .enable_extend_program_checked + { ic_logger_msg!( log_collector, - "Extended ProgramData length of {} bytes exceeds max account data length of {} bytes", - new_len, - MAX_PERMITTED_DATA_LENGTH + "ExtendProgram was superseded by ExtendProgramChecked" ); - return Err(InstructionError::InvalidRealloc); + return Err(InstructionError::InvalidInstructionData); } - - let clock_slot = invoke_context - .get_sysvar_cache() - .get_clock() - .map(|clock| clock.slot)?; - - let upgrade_authority_address = if let UpgradeableLoaderState::ProgramData { - slot, - upgrade_authority_address, - } = programdata_account.get_state()? + common_extend_program(invoke_context, additional_bytes, false)?; + } + UpgradeableLoaderInstruction::ExtendProgramChecked { additional_bytes } => { + if !invoke_context + .get_feature_set() + .enable_extend_program_checked { - if clock_slot == slot { - ic_logger_msg!(log_collector, "Program was extended in this block already"); - return Err(InstructionError::InvalidArgument); - } - - if upgrade_authority_address.is_none() { - ic_logger_msg!( - log_collector, - "Cannot extend ProgramData accounts that are not upgradeable" - ); - return Err(InstructionError::Immutable); - } - upgrade_authority_address - } else { - ic_logger_msg!(log_collector, "ProgramData state is invalid"); - return Err(InstructionError::InvalidAccountData); - }; - - let required_payment = { - let balance = programdata_account.get_lamports(); - let rent = invoke_context.get_sysvar_cache().get_rent()?; - let min_balance = rent.minimum_balance(new_len).max(1); - min_balance.saturating_sub(balance) - }; - - // Borrowed accounts need to be dropped before native_invoke - drop(programdata_account); - - // Dereference the program ID to prevent overlapping mutable/immutable borrow of invoke context - let program_id = *program_id; - if required_payment > 0 { - let payer_key = *transaction_context.get_key_of_account_at_index( - instruction_context.get_index_of_instruction_account_in_transaction( - OPTIONAL_PAYER_ACCOUNT_INDEX, - )?, - )?; - - invoke_context.native_invoke( - system_instruction::transfer(&payer_key, &programdata_key, required_payment) - .into(), - &[], - )?; + return Err(InstructionError::InvalidInstructionData); } - - let transaction_context = &invoke_context.transaction_context; - let instruction_context = transaction_context.get_current_instruction_context()?; - let mut programdata_account = instruction_context - .try_borrow_instruction_account(transaction_context, PROGRAM_DATA_ACCOUNT_INDEX)?; - programdata_account.set_data_length(new_len)?; - - let programdata_data_offset = UpgradeableLoaderState::size_of_programdata_metadata(); - - deploy_program!( - invoke_context, - &program_key, - &program_id, - UpgradeableLoaderState::size_of_program().saturating_add(new_len), - programdata_account - .get_data() - .get(programdata_data_offset..) - .ok_or(InstructionError::AccountDataTooSmall)?, - clock_slot, - ); - drop(programdata_account); - - let mut programdata_account = instruction_context - .try_borrow_instruction_account(transaction_context, PROGRAM_DATA_ACCOUNT_INDEX)?; - programdata_account.set_state(&UpgradeableLoaderState::ProgramData { - slot: clock_slot, - upgrade_authority_address, - })?; - - ic_logger_msg!( - log_collector, - "Extended ProgramData account by {} bytes", - additional_bytes - ); + common_extend_program(invoke_context, additional_bytes, true)?; } UpgradeableLoaderInstruction::Migrate => { if !invoke_context.get_feature_set().enable_loader_v4 { @@ -1489,6 +1355,189 @@ fn process_loader_upgradeable_instruction( Ok(()) } +fn common_extend_program( + invoke_context: &mut InvokeContext, + additional_bytes: u32, + check_authority: bool, +) -> Result<(), InstructionError> { + let log_collector = invoke_context.get_log_collector(); + let transaction_context = &invoke_context.transaction_context; + let instruction_context = transaction_context.get_current_instruction_context()?; + let program_id = instruction_context.get_last_program_key(transaction_context)?; + + const PROGRAM_DATA_ACCOUNT_INDEX: IndexOfAccount = 0; + const PROGRAM_ACCOUNT_INDEX: IndexOfAccount = 1; + const AUTHORITY_ACCOUNT_INDEX: IndexOfAccount = 2; + // let system_program_account_index = if check_authority { 3 } else { 2 }; + let optional_payer_account_index = if check_authority { 4 } else { 3 }; + + if additional_bytes == 0 { + ic_logger_msg!(log_collector, "Additional bytes must be greater than 0"); + return Err(InstructionError::InvalidInstructionData); + } + + let programdata_account = instruction_context + .try_borrow_instruction_account(transaction_context, PROGRAM_DATA_ACCOUNT_INDEX)?; + let programdata_key = *programdata_account.get_key(); + + if program_id != programdata_account.get_owner() { + ic_logger_msg!(log_collector, "ProgramData owner is invalid"); + return Err(InstructionError::InvalidAccountOwner); + } + if !programdata_account.is_writable() { + ic_logger_msg!(log_collector, "ProgramData is not writable"); + return Err(InstructionError::InvalidArgument); + } + + let program_account = instruction_context + .try_borrow_instruction_account(transaction_context, PROGRAM_ACCOUNT_INDEX)?; + if !program_account.is_writable() { + ic_logger_msg!(log_collector, "Program account is not writable"); + return Err(InstructionError::InvalidArgument); + } + if program_account.get_owner() != program_id { + ic_logger_msg!(log_collector, "Program account not owned by loader"); + return Err(InstructionError::InvalidAccountOwner); + } + let program_key = *program_account.get_key(); + match program_account.get_state()? { + UpgradeableLoaderState::Program { + programdata_address, + } => { + if programdata_address != programdata_key { + ic_logger_msg!( + log_collector, + "Program account does not match ProgramData account" + ); + return Err(InstructionError::InvalidArgument); + } + } + _ => { + ic_logger_msg!(log_collector, "Invalid Program account"); + return Err(InstructionError::InvalidAccountData); + } + } + drop(program_account); + + let old_len = programdata_account.get_data().len(); + let new_len = old_len.saturating_add(additional_bytes as usize); + if new_len > MAX_PERMITTED_DATA_LENGTH as usize { + ic_logger_msg!( + log_collector, + "Extended ProgramData length of {} bytes exceeds max account data length of {} bytes", + new_len, + MAX_PERMITTED_DATA_LENGTH + ); + return Err(InstructionError::InvalidRealloc); + } + + let clock_slot = invoke_context + .get_sysvar_cache() + .get_clock() + .map(|clock| clock.slot)?; + + let upgrade_authority_address = if let UpgradeableLoaderState::ProgramData { + slot, + upgrade_authority_address, + } = programdata_account.get_state()? + { + if clock_slot == slot { + ic_logger_msg!(log_collector, "Program was extended in this block already"); + return Err(InstructionError::InvalidArgument); + } + + if upgrade_authority_address.is_none() { + ic_logger_msg!( + log_collector, + "Cannot extend ProgramData accounts that are not upgradeable" + ); + return Err(InstructionError::Immutable); + } + + if check_authority { + let authority_key = Some( + *transaction_context.get_key_of_account_at_index( + instruction_context + .get_index_of_instruction_account_in_transaction(AUTHORITY_ACCOUNT_INDEX)?, + )?, + ); + if upgrade_authority_address != authority_key { + ic_logger_msg!(log_collector, "Incorrect upgrade authority provided"); + return Err(InstructionError::IncorrectAuthority); + } + if !instruction_context.is_instruction_account_signer(AUTHORITY_ACCOUNT_INDEX)? { + ic_logger_msg!(log_collector, "Upgrade authority did not sign"); + return Err(InstructionError::MissingRequiredSignature); + } + } + + upgrade_authority_address + } else { + ic_logger_msg!(log_collector, "ProgramData state is invalid"); + return Err(InstructionError::InvalidAccountData); + }; + + let required_payment = { + let balance = programdata_account.get_lamports(); + let rent = invoke_context.get_sysvar_cache().get_rent()?; + let min_balance = rent.minimum_balance(new_len).max(1); + min_balance.saturating_sub(balance) + }; + + // Borrowed accounts need to be dropped before native_invoke + drop(programdata_account); + + // Dereference the program ID to prevent overlapping mutable/immutable borrow of invoke context + let program_id = *program_id; + if required_payment > 0 { + let payer_key = *transaction_context.get_key_of_account_at_index( + instruction_context + .get_index_of_instruction_account_in_transaction(optional_payer_account_index)?, + )?; + + invoke_context.native_invoke( + system_instruction::transfer(&payer_key, &programdata_key, required_payment).into(), + &[], + )?; + } + + let transaction_context = &invoke_context.transaction_context; + let instruction_context = transaction_context.get_current_instruction_context()?; + let mut programdata_account = instruction_context + .try_borrow_instruction_account(transaction_context, PROGRAM_DATA_ACCOUNT_INDEX)?; + programdata_account.set_data_length(new_len)?; + + let programdata_data_offset = UpgradeableLoaderState::size_of_programdata_metadata(); + + deploy_program!( + invoke_context, + &program_key, + &program_id, + UpgradeableLoaderState::size_of_program().saturating_add(new_len), + programdata_account + .get_data() + .get(programdata_data_offset..) + .ok_or(InstructionError::AccountDataTooSmall)?, + clock_slot, + ); + drop(programdata_account); + + let mut programdata_account = instruction_context + .try_borrow_instruction_account(transaction_context, PROGRAM_DATA_ACCOUNT_INDEX)?; + programdata_account.set_state(&UpgradeableLoaderState::ProgramData { + slot: clock_slot, + upgrade_authority_address, + })?; + + ic_logger_msg!( + log_collector, + "Extended ProgramData account by {} bytes", + additional_bytes + ); + + Ok(()) +} + fn common_close_account( authority_address: &Option, transaction_context: &TransactionContext, diff --git a/programs/sbf/Cargo.lock b/programs/sbf/Cargo.lock index 9c860e0822e..672c62e4d4b 100644 --- a/programs/sbf/Cargo.lock +++ b/programs/sbf/Cargo.lock @@ -5344,7 +5344,7 @@ dependencies = [ "solana-epoch-schedule", "solana-fee-calculator", "solana-instruction", - "solana-loader-v3-interface", + "solana-loader-v3-interface 4.0.1", "solana-nonce", "solana-program-option", "solana-program-pack", @@ -5640,7 +5640,7 @@ dependencies = [ "solana-hash", "solana-instruction", "solana-keccak-hasher", - "solana-loader-v3-interface", + "solana-loader-v3-interface 4.0.1", "solana-loader-v4-interface", "solana-log-collector", "solana-measure", @@ -6047,7 +6047,7 @@ dependencies = [ "solana-instruction", "solana-keypair", "solana-ledger", - "solana-loader-v3-interface", + "solana-loader-v3-interface 4.0.1", "solana-measure", "solana-message", "solana-metrics", @@ -6809,6 +6809,21 @@ dependencies = [ "solana-system-interface", ] +[[package]] +name = "solana-loader-v3-interface" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5539bcadd5c3b306045563e9d102bbaa42b3643f335ae02bc9b5260a70ad9742" +dependencies = [ + "serde", + "serde_bytes", + "serde_derive", + "solana-instruction", + "solana-pubkey", + "solana-sdk-ids", + "solana-system-interface", +] + [[package]] name = "solana-loader-v4-interface" version = "2.2.1" @@ -6834,7 +6849,7 @@ dependencies = [ "solana-bincode", "solana-bpf-loader-program", "solana-instruction", - "solana-loader-v3-interface", + "solana-loader-v3-interface 4.0.1", "solana-loader-v4-interface", "solana-log-collector", "solana-measure", @@ -7172,7 +7187,7 @@ dependencies = [ "solana-keccak-hasher", "solana-last-restart-slot", "solana-loader-v2-interface", - "solana-loader-v3-interface", + "solana-loader-v3-interface 3.0.0", "solana-loader-v4-interface", "solana-message", "solana-msg", @@ -7329,7 +7344,7 @@ dependencies = [ "solana-hash", "solana-instruction", "solana-keypair", - "solana-loader-v3-interface", + "solana-loader-v3-interface 4.0.1", "solana-log-collector", "solana-logger", "solana-message", @@ -7800,7 +7815,7 @@ dependencies = [ "solana-instruction", "solana-keypair", "solana-lattice-hash", - "solana-loader-v3-interface", + "solana-loader-v3-interface 4.0.1", "solana-loader-v4-interface", "solana-measure", "solana-message", @@ -7925,7 +7940,7 @@ dependencies = [ "solana-instruction", "solana-keypair", "solana-ledger", - "solana-loader-v3-interface", + "solana-loader-v3-interface 4.0.1", "solana-loader-v4-interface", "solana-log-collector", "solana-logger", @@ -8920,7 +8935,7 @@ dependencies = [ "solana-hash", "solana-instruction", "solana-instructions-sysvar", - "solana-loader-v3-interface", + "solana-loader-v3-interface 4.0.1", "solana-loader-v4-interface", "solana-loader-v4-program", "solana-log-collector", @@ -9120,7 +9135,7 @@ dependencies = [ "solana-instruction", "solana-keypair", "solana-ledger", - "solana-loader-v3-interface", + "solana-loader-v3-interface 4.0.1", "solana-logger", "solana-message", "solana-native-token", @@ -9356,7 +9371,7 @@ dependencies = [ "solana-hash", "solana-instruction", "solana-loader-v2-interface", - "solana-loader-v3-interface", + "solana-loader-v3-interface 4.0.1", "solana-message", "solana-program-option", "solana-pubkey", diff --git a/programs/sbf/Cargo.toml b/programs/sbf/Cargo.toml index 02b125a6dd6..48096db57d5 100644 --- a/programs/sbf/Cargo.toml +++ b/programs/sbf/Cargo.toml @@ -137,7 +137,7 @@ solana-hash = "2.2.1" solana-instruction = "2.2.1" solana-keypair = "2.2.1" solana-ledger = { workspace = true } -solana-loader-v3-interface = "3.0.0" +solana-loader-v3-interface = "4.0.1" solana-loader-v4-interface = "2.2.1" solana-log-collector = { workspace = true } solana-logger = { workspace = true } diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 0f414cbde4d..daf77cbf7a6 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -86,7 +86,7 @@ solana-inflation = { workspace = true } solana-instruction = { workspace = true } solana-keypair = { workspace = true } solana-lattice-hash = { workspace = true } -solana-loader-v3-interface = { workspace = true } +solana-loader-v3-interface = { workspace = true, features = ["bincode"] } solana-loader-v4-interface = { workspace = true, features = ["serde"] } solana-measure = { workspace = true } solana-message = { workspace = true } @@ -164,7 +164,6 @@ rand0-7 = { package = "rand", version = "0.7" } rand_chacha = { workspace = true } solana-accounts-db = { workspace = true, features = ["dev-context-only-utils"] } solana-builtins = { workspace = true, features = ["dev-context-only-utils"] } -solana-loader-v3-interface = { workspace = true } solana-logger = { workspace = true } # See order-crates-for-publishing.py for using this unusual `path = "."` solana-runtime = { path = ".", features = ["dev-context-only-utils"] } diff --git a/svm-feature-set/src/lib.rs b/svm-feature-set/src/lib.rs index 1c73e6d99c6..d7cb6aa445a 100644 --- a/svm-feature-set/src/lib.rs +++ b/svm-feature-set/src/lib.rs @@ -35,6 +35,7 @@ pub struct SVMFeatureSet { pub loosen_cpi_size_restriction: bool, pub increase_tx_account_lock_limit: bool, pub disable_rent_fees_collection: bool, + pub enable_extend_program_checked: bool, } impl SVMFeatureSet { @@ -75,6 +76,7 @@ impl SVMFeatureSet { loosen_cpi_size_restriction: true, increase_tx_account_lock_limit: true, disable_rent_fees_collection: true, + enable_extend_program_checked: true, } } } diff --git a/svm/Cargo.toml b/svm/Cargo.toml index bf5819501a7..623b8be5619 100644 --- a/svm/Cargo.toml +++ b/svm/Cargo.toml @@ -29,7 +29,7 @@ solana-frozen-abi-macro = { workspace = true, optional = true, features = [ solana-hash = { workspace = true } solana-instruction = { workspace = true, features = ["std"] } solana-instructions-sysvar = { workspace = true } -solana-loader-v3-interface = { workspace = true } +solana-loader-v3-interface = { workspace = true, features = ["bincode"] } solana-loader-v4-interface = { workspace = true } solana-loader-v4-program = { workspace = true } solana-log-collector = { workspace = true } diff --git a/svm/examples/Cargo.lock b/svm/examples/Cargo.lock index 71fecec9b09..823784ef6b3 100644 --- a/svm/examples/Cargo.lock +++ b/svm/examples/Cargo.lock @@ -5189,7 +5189,7 @@ dependencies = [ "solana-epoch-schedule", "solana-fee-calculator", "solana-instruction", - "solana-loader-v3-interface", + "solana-loader-v3-interface 4.0.1", "solana-nonce", "solana-program-option", "solana-program-pack", @@ -5485,7 +5485,7 @@ dependencies = [ "solana-hash", "solana-instruction", "solana-keccak-hasher", - "solana-loader-v3-interface", + "solana-loader-v3-interface 4.0.1", "solana-loader-v4-interface", "solana-log-collector", "solana-measure", @@ -5892,7 +5892,7 @@ dependencies = [ "solana-instruction", "solana-keypair", "solana-ledger", - "solana-loader-v3-interface", + "solana-loader-v3-interface 4.0.1", "solana-measure", "solana-message", "solana-metrics", @@ -6619,6 +6619,21 @@ dependencies = [ "solana-system-interface", ] +[[package]] +name = "solana-loader-v3-interface" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5539bcadd5c3b306045563e9d102bbaa42b3643f335ae02bc9b5260a70ad9742" +dependencies = [ + "serde", + "serde_bytes", + "serde_derive", + "solana-instruction", + "solana-pubkey", + "solana-sdk-ids", + "solana-system-interface", +] + [[package]] name = "solana-loader-v4-interface" version = "2.2.1" @@ -6644,7 +6659,7 @@ dependencies = [ "solana-bincode", "solana-bpf-loader-program", "solana-instruction", - "solana-loader-v3-interface", + "solana-loader-v3-interface 4.0.1", "solana-loader-v4-interface", "solana-log-collector", "solana-measure", @@ -6982,7 +6997,7 @@ dependencies = [ "solana-keccak-hasher", "solana-last-restart-slot", "solana-loader-v2-interface", - "solana-loader-v3-interface", + "solana-loader-v3-interface 3.0.0", "solana-loader-v4-interface", "solana-message", "solana-msg", @@ -7139,7 +7154,7 @@ dependencies = [ "solana-hash", "solana-instruction", "solana-keypair", - "solana-loader-v3-interface", + "solana-loader-v3-interface 4.0.1", "solana-log-collector", "solana-logger", "solana-message", @@ -7610,7 +7625,7 @@ dependencies = [ "solana-instruction", "solana-keypair", "solana-lattice-hash", - "solana-loader-v3-interface", + "solana-loader-v3-interface 4.0.1", "solana-loader-v4-interface", "solana-measure", "solana-message", @@ -8200,7 +8215,7 @@ dependencies = [ "solana-hash", "solana-instruction", "solana-instructions-sysvar", - "solana-loader-v3-interface", + "solana-loader-v3-interface 4.0.1", "solana-loader-v4-interface", "solana-loader-v4-program", "solana-log-collector", @@ -8436,7 +8451,7 @@ dependencies = [ "solana-instruction", "solana-keypair", "solana-ledger", - "solana-loader-v3-interface", + "solana-loader-v3-interface 4.0.1", "solana-logger", "solana-message", "solana-native-token", @@ -8672,7 +8687,7 @@ dependencies = [ "solana-hash", "solana-instruction", "solana-loader-v2-interface", - "solana-loader-v3-interface", + "solana-loader-v3-interface 4.0.1", "solana-message", "solana-program-option", "solana-pubkey", diff --git a/transaction-status/Cargo.toml b/transaction-status/Cargo.toml index 928841c9ab9..afc609775cb 100644 --- a/transaction-status/Cargo.toml +++ b/transaction-status/Cargo.toml @@ -27,7 +27,7 @@ solana-clock = { workspace = true } solana-hash = { workspace = true } solana-instruction = { workspace = true } solana-loader-v2-interface = { workspace = true, features = ["bincode"] } -solana-loader-v3-interface = { workspace = true } +solana-loader-v3-interface = { workspace = true, features = ["bincode"] } solana-message = { workspace = true } solana-program-option = { workspace = true } solana-pubkey = { workspace = true } diff --git a/transaction-status/src/parse_bpf_loader.rs b/transaction-status/src/parse_bpf_loader.rs index e18a9234711..b75bc80b717 100644 --- a/transaction-status/src/parse_bpf_loader.rs +++ b/transaction-status/src/parse_bpf_loader.rs @@ -174,12 +174,12 @@ pub fn parse_bpf_upgradeable_loader( "additionalBytes": additional_bytes, "programDataAccount": account_keys[instruction.accounts[0] as usize].to_string(), "programAccount": account_keys[instruction.accounts[1] as usize].to_string(), - "systemProgram": if instruction.accounts.len() > 3 { + "systemProgram": if instruction.accounts.len() > 2 { Some(account_keys[instruction.accounts[2] as usize].to_string()) } else { None }, - "payerAccount": if instruction.accounts.len() > 4 { + "payerAccount": if instruction.accounts.len() > 3 { Some(account_keys[instruction.accounts[3] as usize].to_string()) } else { None @@ -198,6 +198,28 @@ pub fn parse_bpf_upgradeable_loader( }), }) } + UpgradeableLoaderInstruction::ExtendProgramChecked { additional_bytes } => { + check_num_bpf_upgradeable_loader_accounts(&instruction.accounts, 3)?; + Ok(ParsedInstructionEnum { + instruction_type: "extendProgramChecked".to_string(), + info: json!({ + "additionalBytes": additional_bytes, + "programDataAccount": account_keys[instruction.accounts[0] as usize].to_string(), + "programAccount": account_keys[instruction.accounts[1] as usize].to_string(), + "authority": account_keys[instruction.accounts[2] as usize].to_string(), + "systemProgram": if instruction.accounts.len() > 3 { + Some(account_keys[instruction.accounts[3] as usize].to_string()) + } else { + None + }, + "payerAccount": if instruction.accounts.len() > 4 { + Some(account_keys[instruction.accounts[4] as usize].to_string()) + } else { + None + }, + }), + }) + } } }