From 5de4dd8f9b23eff315ee3c9befe5f82b1814b1f1 Mon Sep 17 00:00:00 2001 From: 0xripleys <105607696+0xripleys@users.noreply.github.com> Date: Tue, 1 Nov 2022 09:34:04 +0100 Subject: [PATCH] Add SetAuthorityChecked instruction to bpf loader (#28424) * SetAuthorityChecked * restore old logic for loader * add more upgrade authority checked test cases * setBufferAuthority checked tests * format * add set_buffer_authority_checked instruction to sdk * Update transaction-status/src/parse_bpf_loader.rs Co-authored-by: Justin Starry * add is_set_authority_checked function * fix set_buffer_authority_checked sdk instruction * feature gate setAuthorityChecked * add bpf loader tests for setAuthorityChecked ixs * test that you can set to same authority * allow set_authority_checked to be called via cpi (if feature is enabled) * fix ci * fmt Co-authored-by: Justin Starry Co-authored-by: Justin Starry --- programs/bpf_loader/src/lib.rs | 517 ++++++++++++++++++ programs/bpf_loader/src/syscalls/cpi.rs | 13 +- sdk/program/src/bpf_loader_upgradeable.rs | 52 ++ .../src/loader_upgradeable_instruction.rs | 13 + sdk/src/feature_set.rs | 5 + transaction-status/src/parse_bpf_loader.rs | 82 +++ 6 files changed, 680 insertions(+), 2 deletions(-) diff --git a/programs/bpf_loader/src/lib.rs b/programs/bpf_loader/src/lib.rs index b139af9d40f0b8..de727cdbc17b54 100644 --- a/programs/bpf_loader/src/lib.rs +++ b/programs/bpf_loader/src/lib.rs @@ -48,6 +48,7 @@ use { cap_accounts_data_allocations_per_transaction, cap_bpf_program_instruction_accounts, check_slice_translation_size, disable_deploy_of_alloc_free_syscall, disable_deprecated_loader, enable_bpf_loader_extend_program_ix, + enable_bpf_loader_set_authority_checked_ix, error_on_syscall_bpf_function_hash_collisions, limit_max_instruction_trace_length, reject_callx_r10, FeatureSet, }, @@ -1015,6 +1016,79 @@ fn process_loader_upgradeable_instruction( ic_logger_msg!(log_collector, "New authority {:?}", new_authority); } + UpgradeableLoaderInstruction::SetAuthorityChecked => { + if !invoke_context + .feature_set + .is_active(&enable_bpf_loader_set_authority_checked_ix::id()) + { + return Err(InstructionError::InvalidInstructionData); + } + + instruction_context.check_number_of_instruction_accounts(3)?; + let mut account = + instruction_context.try_borrow_instruction_account(transaction_context, 0)?; + let present_authority_key = transaction_context.get_key_of_account_at_index( + instruction_context.get_index_of_instruction_account_in_transaction(1)?, + )?; + let new_authority_key = transaction_context.get_key_of_account_at_index( + instruction_context.get_index_of_instruction_account_in_transaction(2)?, + )?; + + match account.get_state()? { + UpgradeableLoaderState::Buffer { authority_address } => { + if authority_address.is_none() { + ic_logger_msg!(log_collector, "Buffer is immutable"); + return Err(InstructionError::Immutable); + } + if authority_address != Some(*present_authority_key) { + ic_logger_msg!(log_collector, "Incorrect buffer authority provided"); + return Err(InstructionError::IncorrectAuthority); + } + if !instruction_context.is_instruction_account_signer(1)? { + ic_logger_msg!(log_collector, "Buffer authority did not sign"); + return Err(InstructionError::MissingRequiredSignature); + } + if !instruction_context.is_instruction_account_signer(2)? { + ic_logger_msg!(log_collector, "New authority did not sign"); + return Err(InstructionError::MissingRequiredSignature); + } + account.set_state(&UpgradeableLoaderState::Buffer { + authority_address: Some(*new_authority_key), + })?; + } + UpgradeableLoaderState::ProgramData { + slot, + upgrade_authority_address, + } => { + if upgrade_authority_address.is_none() { + ic_logger_msg!(log_collector, "Program not upgradeable"); + return Err(InstructionError::Immutable); + } + if upgrade_authority_address != Some(*present_authority_key) { + ic_logger_msg!(log_collector, "Incorrect upgrade authority provided"); + return Err(InstructionError::IncorrectAuthority); + } + if !instruction_context.is_instruction_account_signer(1)? { + ic_logger_msg!(log_collector, "Upgrade authority did not sign"); + return Err(InstructionError::MissingRequiredSignature); + } + if !instruction_context.is_instruction_account_signer(2)? { + ic_logger_msg!(log_collector, "New authority did not sign"); + return Err(InstructionError::MissingRequiredSignature); + } + account.set_state(&UpgradeableLoaderState::ProgramData { + slot, + upgrade_authority_address: Some(*new_authority_key), + })?; + } + _ => { + ic_logger_msg!(log_collector, "Account does not support authorities"); + return Err(InstructionError::InvalidArgument); + } + } + + ic_logger_msg!(log_collector, "New authority {:?}", new_authority_key); + } UpgradeableLoaderInstruction::Close => { instruction_context.check_number_of_instruction_accounts(2)?; if instruction_context.get_index_of_instruction_account_in_transaction(0)? @@ -2927,6 +3001,251 @@ mod tests { ); } + #[test] + fn test_bpf_loader_upgradeable_set_upgrade_authority_checked() { + let instruction = + bincode::serialize(&UpgradeableLoaderInstruction::SetAuthorityChecked).unwrap(); + let loader_id = bpf_loader_upgradeable::id(); + let slot = 0; + let upgrade_authority_address = Pubkey::new_unique(); + let upgrade_authority_account = AccountSharedData::new(1, 0, &Pubkey::new_unique()); + let new_upgrade_authority_address = Pubkey::new_unique(); + let new_upgrade_authority_account = AccountSharedData::new(1, 0, &Pubkey::new_unique()); + let program_address = Pubkey::new_unique(); + let (programdata_address, _) = Pubkey::find_program_address( + &[program_address.as_ref()], + &bpf_loader_upgradeable::id(), + ); + let mut programdata_account = AccountSharedData::new( + 1, + UpgradeableLoaderState::size_of_programdata(0), + &bpf_loader_upgradeable::id(), + ); + programdata_account + .set_state(&UpgradeableLoaderState::ProgramData { + slot, + upgrade_authority_address: Some(upgrade_authority_address), + }) + .unwrap(); + let programdata_meta = AccountMeta { + pubkey: programdata_address, + is_signer: false, + is_writable: true, + }; + let upgrade_authority_meta = AccountMeta { + pubkey: upgrade_authority_address, + is_signer: true, + is_writable: false, + }; + let new_upgrade_authority_meta = AccountMeta { + pubkey: new_upgrade_authority_address, + is_signer: true, + is_writable: false, + }; + + // Case: Set to new authority + let accounts = process_instruction( + &loader_id, + &[], + &instruction, + vec![ + (programdata_address, programdata_account.clone()), + (upgrade_authority_address, upgrade_authority_account.clone()), + ( + new_upgrade_authority_address, + new_upgrade_authority_account.clone(), + ), + ], + vec![ + programdata_meta.clone(), + upgrade_authority_meta.clone(), + new_upgrade_authority_meta.clone(), + ], + Ok(()), + ); + + let state: UpgradeableLoaderState = accounts.first().unwrap().state().unwrap(); + assert_eq!( + state, + UpgradeableLoaderState::ProgramData { + slot, + upgrade_authority_address: Some(new_upgrade_authority_address), + } + ); + + // Case: set to same authority + process_instruction( + &loader_id, + &[], + &instruction, + vec![ + (programdata_address, programdata_account.clone()), + (upgrade_authority_address, upgrade_authority_account.clone()), + ], + vec![ + programdata_meta.clone(), + upgrade_authority_meta.clone(), + upgrade_authority_meta.clone(), + ], + Ok(()), + ); + + // Case: present authority not in instruction + process_instruction( + &loader_id, + &[], + &instruction, + vec![ + (programdata_address, programdata_account.clone()), + (upgrade_authority_address, upgrade_authority_account.clone()), + ( + new_upgrade_authority_address, + new_upgrade_authority_account.clone(), + ), + ], + vec![programdata_meta.clone(), new_upgrade_authority_meta.clone()], + Err(InstructionError::NotEnoughAccountKeys), + ); + + // Case: new authority not in instruction + process_instruction( + &loader_id, + &[], + &instruction, + vec![ + (programdata_address, programdata_account.clone()), + (upgrade_authority_address, upgrade_authority_account.clone()), + ( + new_upgrade_authority_address, + new_upgrade_authority_account.clone(), + ), + ], + vec![programdata_meta.clone(), upgrade_authority_meta.clone()], + Err(InstructionError::NotEnoughAccountKeys), + ); + + // Case: present authority did not sign + process_instruction( + &loader_id, + &[], + &instruction, + vec![ + (programdata_address, programdata_account.clone()), + (upgrade_authority_address, upgrade_authority_account.clone()), + ( + new_upgrade_authority_address, + new_upgrade_authority_account.clone(), + ), + ], + vec![ + programdata_meta.clone(), + AccountMeta { + pubkey: upgrade_authority_address, + is_signer: false, + is_writable: false, + }, + new_upgrade_authority_meta.clone(), + ], + Err(InstructionError::MissingRequiredSignature), + ); + + // Case: New authority did not sign + process_instruction( + &loader_id, + &[], + &instruction, + vec![ + (programdata_address, programdata_account.clone()), + (upgrade_authority_address, upgrade_authority_account.clone()), + ( + new_upgrade_authority_address, + new_upgrade_authority_account.clone(), + ), + ], + vec![ + programdata_meta.clone(), + upgrade_authority_meta.clone(), + AccountMeta { + pubkey: new_upgrade_authority_address, + is_signer: false, + is_writable: false, + }, + ], + Err(InstructionError::MissingRequiredSignature), + ); + + // Case: wrong present authority + let invalid_upgrade_authority_address = Pubkey::new_unique(); + process_instruction( + &loader_id, + &[], + &instruction, + vec![ + (programdata_address, programdata_account.clone()), + ( + invalid_upgrade_authority_address, + upgrade_authority_account.clone(), + ), + (new_upgrade_authority_address, new_upgrade_authority_account), + ], + vec![ + programdata_meta.clone(), + AccountMeta { + pubkey: invalid_upgrade_authority_address, + is_signer: true, + is_writable: false, + }, + new_upgrade_authority_meta.clone(), + ], + Err(InstructionError::IncorrectAuthority), + ); + + // Case: programdata is immutable + programdata_account + .set_state(&UpgradeableLoaderState::ProgramData { + slot, + upgrade_authority_address: None, + }) + .unwrap(); + process_instruction( + &loader_id, + &[], + &instruction, + vec![ + (programdata_address, programdata_account.clone()), + (upgrade_authority_address, upgrade_authority_account.clone()), + ], + vec![ + programdata_meta.clone(), + upgrade_authority_meta.clone(), + new_upgrade_authority_meta.clone(), + ], + Err(InstructionError::Immutable), + ); + + // Case: Not a ProgramData account + programdata_account + .set_state(&UpgradeableLoaderState::Program { + programdata_address: Pubkey::new_unique(), + }) + .unwrap(); + process_instruction( + &loader_id, + &[], + &instruction, + vec![ + (programdata_address, programdata_account.clone()), + (upgrade_authority_address, upgrade_authority_account), + ], + vec![ + programdata_meta, + upgrade_authority_meta, + new_upgrade_authority_meta, + ], + Err(InstructionError::InvalidArgument), + ); + } + #[test] fn test_bpf_loader_upgradeable_set_buffer_authority() { let instruction = bincode::serialize(&UpgradeableLoaderInstruction::SetAuthority).unwrap(); @@ -3099,6 +3418,204 @@ mod tests { ); } + #[test] + fn test_bpf_loader_upgradeable_set_buffer_authority_checked() { + let instruction = + bincode::serialize(&UpgradeableLoaderInstruction::SetAuthorityChecked).unwrap(); + let loader_id = bpf_loader_upgradeable::id(); + let invalid_authority_address = Pubkey::new_unique(); + let authority_address = Pubkey::new_unique(); + let authority_account = AccountSharedData::new(1, 0, &Pubkey::new_unique()); + let new_authority_address = Pubkey::new_unique(); + let new_authority_account = AccountSharedData::new(1, 0, &Pubkey::new_unique()); + let buffer_address = Pubkey::new_unique(); + let mut buffer_account = + AccountSharedData::new(1, UpgradeableLoaderState::size_of_buffer(0), &loader_id); + buffer_account + .set_state(&UpgradeableLoaderState::Buffer { + authority_address: Some(authority_address), + }) + .unwrap(); + let mut transaction_accounts = vec![ + (buffer_address, buffer_account.clone()), + (authority_address, authority_account.clone()), + (new_authority_address, new_authority_account.clone()), + ]; + let buffer_meta = AccountMeta { + pubkey: buffer_address, + is_signer: false, + is_writable: true, + }; + let authority_meta = AccountMeta { + pubkey: authority_address, + is_signer: true, + is_writable: false, + }; + let new_authority_meta = AccountMeta { + pubkey: new_authority_address, + is_signer: true, + is_writable: false, + }; + + // Case: Set to new authority + buffer_account + .set_state(&UpgradeableLoaderState::Buffer { + authority_address: Some(authority_address), + }) + .unwrap(); + let accounts = process_instruction( + &loader_id, + &[], + &instruction, + transaction_accounts.clone(), + vec![ + buffer_meta.clone(), + authority_meta.clone(), + new_authority_meta.clone(), + ], + Ok(()), + ); + let state: UpgradeableLoaderState = accounts.first().unwrap().state().unwrap(); + assert_eq!( + state, + UpgradeableLoaderState::Buffer { + authority_address: Some(new_authority_address), + } + ); + + // Case: set to same authority + process_instruction( + &loader_id, + &[], + &instruction, + transaction_accounts.clone(), + vec![ + buffer_meta.clone(), + authority_meta.clone(), + authority_meta.clone(), + ], + Ok(()), + ); + + // Case: Missing current authority + process_instruction( + &loader_id, + &[], + &instruction, + transaction_accounts.clone(), + vec![buffer_meta.clone(), new_authority_meta.clone()], + Err(InstructionError::NotEnoughAccountKeys), + ); + + // Case: Missing new authority + process_instruction( + &loader_id, + &[], + &instruction, + transaction_accounts.clone(), + vec![buffer_meta.clone(), authority_meta.clone()], + Err(InstructionError::NotEnoughAccountKeys), + ); + + // Case: wrong present authority + process_instruction( + &loader_id, + &[], + &instruction, + vec![ + (buffer_address, buffer_account.clone()), + (invalid_authority_address, authority_account), + (new_authority_address, new_authority_account), + ], + vec![ + buffer_meta.clone(), + AccountMeta { + pubkey: invalid_authority_address, + is_signer: true, + is_writable: false, + }, + new_authority_meta.clone(), + ], + Err(InstructionError::IncorrectAuthority), + ); + + // Case: present authority did not sign + process_instruction( + &loader_id, + &[], + &instruction, + transaction_accounts.clone(), + vec![ + buffer_meta.clone(), + AccountMeta { + pubkey: authority_address, + is_signer: false, + is_writable: false, + }, + new_authority_meta.clone(), + ], + Err(InstructionError::MissingRequiredSignature), + ); + + // Case: new authority did not sign + process_instruction( + &loader_id, + &[], + &instruction, + transaction_accounts.clone(), + vec![ + buffer_meta.clone(), + authority_meta.clone(), + AccountMeta { + pubkey: new_authority_address, + is_signer: false, + is_writable: false, + }, + ], + Err(InstructionError::MissingRequiredSignature), + ); + + // Case: Not a Buffer account + transaction_accounts + .get_mut(0) + .unwrap() + .1 + .set_state(&UpgradeableLoaderState::Program { + programdata_address: Pubkey::new_unique(), + }) + .unwrap(); + process_instruction( + &loader_id, + &[], + &instruction, + transaction_accounts.clone(), + vec![ + buffer_meta.clone(), + authority_meta.clone(), + new_authority_meta.clone(), + ], + Err(InstructionError::InvalidArgument), + ); + + // Case: Buffer is immutable + transaction_accounts + .get_mut(0) + .unwrap() + .1 + .set_state(&UpgradeableLoaderState::Buffer { + authority_address: None, + }) + .unwrap(); + process_instruction( + &loader_id, + &[], + &instruction, + transaction_accounts.clone(), + vec![buffer_meta, authority_meta, new_authority_meta], + Err(InstructionError::Immutable), + ); + } + #[test] fn test_bpf_loader_upgradeable_close() { let instruction = bincode::serialize(&UpgradeableLoaderInstruction::Close).unwrap(); diff --git a/programs/bpf_loader/src/syscalls/cpi.rs b/programs/bpf_loader/src/syscalls/cpi.rs index 7eb2290266cb5b..b23ee5bf086790 100644 --- a/programs/bpf_loader/src/syscalls/cpi.rs +++ b/programs/bpf_loader/src/syscalls/cpi.rs @@ -1,8 +1,11 @@ use { super::*, crate::declare_syscall, - solana_sdk::syscalls::{ - MAX_CPI_ACCOUNT_INFOS, MAX_CPI_INSTRUCTION_ACCOUNTS, MAX_CPI_INSTRUCTION_DATA_LEN, + solana_sdk::{ + feature_set::enable_bpf_loader_set_authority_checked_ix, + syscalls::{ + MAX_CPI_ACCOUNT_INFOS, MAX_CPI_INSTRUCTION_ACCOUNTS, MAX_CPI_INSTRUCTION_DATA_LEN, + }, }, }; @@ -833,6 +836,12 @@ fn check_authorized_program( || (bpf_loader_upgradeable::check_id(program_id) && !(bpf_loader_upgradeable::is_upgrade_instruction(instruction_data) || bpf_loader_upgradeable::is_set_authority_instruction(instruction_data) + || (invoke_context + .feature_set + .is_active(&enable_bpf_loader_set_authority_checked_ix::id()) + && bpf_loader_upgradeable::is_set_authority_checked_instruction( + instruction_data, + )) || bpf_loader_upgradeable::is_close_instruction(instruction_data))) || is_precompile(program_id, |feature_id: &Pubkey| { invoke_context.feature_set.is_active(feature_id) diff --git a/sdk/program/src/bpf_loader_upgradeable.rs b/sdk/program/src/bpf_loader_upgradeable.rs index 40b96583ac5f20..95e84af0ccfb13 100644 --- a/sdk/program/src/bpf_loader_upgradeable.rs +++ b/sdk/program/src/bpf_loader_upgradeable.rs @@ -231,6 +231,10 @@ pub fn is_close_instruction(instruction_data: &[u8]) -> bool { !instruction_data.is_empty() && 5 == instruction_data[0] } +pub fn is_set_authority_checked_instruction(instruction_data: &[u8]) -> bool { + !instruction_data.is_empty() && 7 == instruction_data[0] +} + /// Returns the instructions required to set a buffers's authority. pub fn set_buffer_authority( buffer_address: &Pubkey, @@ -248,6 +252,24 @@ pub fn set_buffer_authority( ) } +/// Returns the instructions required to set a buffers's authority. If using this instruction, the new authority +/// must sign. +pub fn set_buffer_authority_checked( + buffer_address: &Pubkey, + current_authority_address: &Pubkey, + new_authority_address: &Pubkey, +) -> Instruction { + Instruction::new_with_bincode( + id(), + &UpgradeableLoaderInstruction::SetAuthorityChecked, + vec![ + AccountMeta::new(*buffer_address, false), + AccountMeta::new_readonly(*current_authority_address, true), + AccountMeta::new_readonly(*new_authority_address, true), + ], + ) +} + /// Returns the instructions required to set a program's authority. pub fn set_upgrade_authority( program_address: &Pubkey, @@ -266,6 +288,27 @@ pub fn set_upgrade_authority( Instruction::new_with_bincode(id(), &UpgradeableLoaderInstruction::SetAuthority, metas) } +/// Returns the instructions required to set a program's authority. If using this instruction, the new authority +/// must sign. +pub fn set_upgrade_authority_checked( + program_address: &Pubkey, + current_authority_address: &Pubkey, + new_authority_address: &Pubkey, +) -> Instruction { + let (programdata_address, _) = Pubkey::find_program_address(&[program_address.as_ref()], &id()); + + let metas = vec![ + AccountMeta::new(programdata_address, false), + AccountMeta::new_readonly(*current_authority_address, true), + AccountMeta::new_readonly(*new_authority_address, true), + ]; + Instruction::new_with_bincode( + id(), + &UpgradeableLoaderInstruction::SetAuthorityChecked, + metas, + ) +} + /// Returns the instructions required to close a buffer account pub fn close( close_address: &Pubkey, @@ -455,6 +498,15 @@ mod tests { ); } + #[test] + fn test_is_set_authority_checked_instruction() { + assert!(!is_set_authority_checked_instruction(&[])); + assert_is_instruction( + is_set_authority_checked_instruction, + UpgradeableLoaderInstruction::SetAuthorityChecked {}, + ); + } + #[test] fn test_is_upgrade_instruction() { assert!(!is_upgrade_instruction(&[])); diff --git a/sdk/program/src/loader_upgradeable_instruction.rs b/sdk/program/src/loader_upgradeable_instruction.rs index b8832c5329e9de..1d2a9a8f9bfad9 100644 --- a/sdk/program/src/loader_upgradeable_instruction.rs +++ b/sdk/program/src/loader_upgradeable_instruction.rs @@ -147,4 +147,17 @@ pub enum UpgradeableLoaderInstruction { /// Number of bytes to extend the program data. additional_bytes: u32, }, + + /// Set a new authority that is allowed to write the buffer or upgrade the + /// program. + /// + /// This instruction differs from SetAuthority in that the new authority is a + /// required signer. + /// + /// # Account references + /// 0. `[writable]` The Buffer or ProgramData account to change the + /// authority of. + /// 1. `[signer]` The current authority. + /// 2. `[signer]` The new authority. + SetAuthorityChecked, } diff --git a/sdk/src/feature_set.rs b/sdk/src/feature_set.rs index 10d64285bdb55e..bb103bc10f6185 100644 --- a/sdk/src/feature_set.rs +++ b/sdk/src/feature_set.rs @@ -534,6 +534,10 @@ pub mod check_syscall_outputs_do_not_overlap { solana_sdk::declare_id!("3uRVPBpyEJRo1emLCrq38eLRFGcu6uKSpUXqGvU8T7SZ"); } +pub mod enable_bpf_loader_set_authority_checked_ix { + solana_sdk::declare_id!("5x3825XS7M2A3Ekbn5VGGkvFoAg5qrRWkTrY4bARP1GL"); +} + lazy_static! { /// Map of feature identifiers to user-visible description pub static ref FEATURE_NAMES: HashMap = [ @@ -662,6 +666,7 @@ lazy_static! { (increase_tx_account_lock_limit::id(), "increase tx account lock limit to 128 #27241"), (limit_max_instruction_trace_length::id(), "limit max instruction trace length #27939"), (check_syscall_outputs_do_not_overlap::id(), "check syscall outputs do_not overlap #28600"), + (enable_bpf_loader_set_authority_checked_ix::id(), "enable bpf upgradeable loader SetAuthorityChecked instruction #28424"), /*************** ADD NEW FEATURES HERE ***************/ ] .iter() diff --git a/transaction-status/src/parse_bpf_loader.rs b/transaction-status/src/parse_bpf_loader.rs index ee1a744599975b..313c2dd4fb496e 100644 --- a/transaction-status/src/parse_bpf_loader.rs +++ b/transaction-status/src/parse_bpf_loader.rs @@ -139,6 +139,17 @@ pub fn parse_bpf_upgradeable_loader( }), }) } + UpgradeableLoaderInstruction::SetAuthorityChecked => { + check_num_bpf_upgradeable_loader_accounts(&instruction.accounts, 3)?; + Ok(ParsedInstructionEnum { + instruction_type: "setAuthorityChecked".to_string(), + info: json!({ + "account": account_keys[instruction.accounts[0] as usize].to_string(), + "authority": account_keys[instruction.accounts[1] as usize].to_string(), + "newAuthority": account_keys[instruction.accounts[2] as usize].to_string(), + }), + }) + } UpgradeableLoaderInstruction::Close => { check_num_bpf_upgradeable_loader_accounts(&instruction.accounts, 3)?; Ok(ParsedInstructionEnum { @@ -536,6 +547,39 @@ mod test { .is_err()); } + #[test] + fn test_parse_bpf_upgradeable_loader_set_buffer_authority_checked_ix() { + let buffer_address = Pubkey::new_unique(); + let current_authority_address = Pubkey::new_unique(); + let new_authority_address = Pubkey::new_unique(); + let instruction = bpf_loader_upgradeable::set_buffer_authority_checked( + &buffer_address, + ¤t_authority_address, + &new_authority_address, + ); + let message = Message::new(&[instruction], None); + assert_eq!( + parse_bpf_upgradeable_loader( + &message.instructions[0], + &AccountKeys::new(&message.account_keys, None) + ) + .unwrap(), + ParsedInstructionEnum { + instruction_type: "setAuthorityChecked".to_string(), + info: json!({ + "account": buffer_address.to_string(), + "authority": current_authority_address.to_string(), + "newAuthority": new_authority_address.to_string(), + }), + } + ); + assert!(parse_bpf_upgradeable_loader( + &message.instructions[0], + &AccountKeys::new(&message.account_keys[0..2], None) + ) + .is_err()); + } + #[test] fn test_parse_bpf_upgradeable_loader_set_upgrade_authority_ix() { let program_address = Pubkey::new_unique(); @@ -615,6 +659,44 @@ mod test { .is_err()); } + #[test] + fn test_parse_bpf_upgradeable_loader_set_upgrade_authority_checked_ix() { + let program_address = Pubkey::new_unique(); + let current_authority_address = Pubkey::new_unique(); + let new_authority_address = Pubkey::new_unique(); + let (programdata_address, _) = Pubkey::find_program_address( + &[program_address.as_ref()], + &bpf_loader_upgradeable::id(), + ); + let instruction = bpf_loader_upgradeable::set_upgrade_authority_checked( + &program_address, + ¤t_authority_address, + &new_authority_address, + ); + let message = Message::new(&[instruction], None); + assert_eq!( + parse_bpf_upgradeable_loader( + &message.instructions[0], + &AccountKeys::new(&message.account_keys, None) + ) + .unwrap(), + ParsedInstructionEnum { + instruction_type: "setAuthorityChecked".to_string(), + info: json!({ + "account": programdata_address.to_string(), + "authority": current_authority_address.to_string(), + "newAuthority": new_authority_address.to_string(), + }), + } + ); + + assert!(parse_bpf_upgradeable_loader( + &message.instructions[0], + &AccountKeys::new(&message.account_keys[0..2], None) + ) + .is_err()); + } + #[test] fn test_parse_bpf_upgradeable_loader_close_buffer_ix() { let close_address = Pubkey::new_unique();