diff --git a/programs/config/src/config_processor.rs b/programs/config/src/config_processor.rs index d93ed85ac57eaa..09743d38c35357 100644 --- a/programs/config/src/config_processor.rs +++ b/programs/config/src/config_processor.rs @@ -11,6 +11,7 @@ use solana_sdk::{ program_utils::limited_deserialize, pubkey::Pubkey, }; +use std::collections::BTreeSet; pub fn process_instruction( _program_id: &Pubkey, @@ -101,6 +102,15 @@ pub fn process_instruction( } } + if invoke_context.is_feature_active(&feature_set::dedupe_config_program_signers::id()) { + let total_new_keys = key_list.keys.len(); + let unique_new_keys = key_list.keys.into_iter().collect::>(); + if unique_new_keys.len() != total_new_keys { + ic_msg!(invoke_context, "new config contains duplicate keys"); + return Err(InstructionError::InvalidArgument); + } + } + // Check for Config data signers not present in incoming account update if current_signer_keys.len() > counter { ic_msg!( @@ -493,6 +503,96 @@ mod tests { ); } + #[test] + fn test_config_initialize_contains_duplicates_fails() { + solana_logger::setup(); + let config_address = Pubkey::new_unique(); + let signer0_pubkey = Pubkey::new_unique(); + let signer0_account = RefCell::new(AccountSharedData::default()); + let keys = vec![ + (config_address, false), + (signer0_pubkey, true), + (signer0_pubkey, true), + ]; + let (config_keypair, config_account) = create_config_account(keys.clone()); + let config_pubkey = config_keypair.pubkey(); + let my_config = MyConfig::new(42); + + // Attempt initialization with duplicate signer inputs + let instruction = config_instruction::store(&config_pubkey, true, keys, &my_config); + let accounts = vec![ + (true, false, &config_pubkey, &config_account), + (true, false, &signer0_pubkey, &signer0_account), + (true, false, &signer0_pubkey, &signer0_account), + ]; + let keyed_accounts = create_keyed_accounts_unified(&accounts); + assert_eq!( + process_instruction( + &id(), + &instruction.data, + &mut MockInvokeContext::new(keyed_accounts) + ), + Err(InstructionError::InvalidArgument), + ); + } + + #[test] + fn test_config_update_contains_duplicates_fails() { + solana_logger::setup(); + let config_address = Pubkey::new_unique(); + let signer0_pubkey = Pubkey::new_unique(); + let signer1_pubkey = Pubkey::new_unique(); + let signer0_account = RefCell::new(AccountSharedData::default()); + let signer1_account = RefCell::new(AccountSharedData::default()); + let keys = vec![ + (config_address, false), + (signer0_pubkey, true), + (signer1_pubkey, true), + ]; + let (config_keypair, config_account) = create_config_account(keys.clone()); + let config_pubkey = config_keypair.pubkey(); + let my_config = MyConfig::new(42); + + let instruction = config_instruction::store(&config_pubkey, true, keys, &my_config); + let accounts = vec![ + (true, false, &config_pubkey, &config_account), + (true, false, &signer0_pubkey, &signer0_account), + (true, false, &signer1_pubkey, &signer1_account), + ]; + let keyed_accounts = create_keyed_accounts_unified(&accounts); + assert_eq!( + process_instruction( + &id(), + &instruction.data, + &mut MockInvokeContext::new(keyed_accounts) + ), + Ok(()), + ); + + // Attempt update with duplicate signer inputs + let new_config = MyConfig::new(84); + let dupe_keys = vec![ + (config_address, false), + (signer0_pubkey, true), + (signer0_pubkey, true), + ]; + let instruction = config_instruction::store(&config_pubkey, false, dupe_keys, &new_config); + let accounts = vec![ + (false, false, &config_pubkey, &config_account), + (true, false, &signer0_pubkey, &signer0_account), + (true, false, &signer0_pubkey, &signer0_account), + ]; + let keyed_accounts = create_keyed_accounts_unified(&accounts); + assert_eq!( + process_instruction( + &id(), + &instruction.data, + &mut MockInvokeContext::new(keyed_accounts) + ), + Err(InstructionError::InvalidArgument), + ); + } + #[test] fn test_config_updates_requiring_config() { solana_logger::setup(); diff --git a/sdk/src/feature_set.rs b/sdk/src/feature_set.rs index f6cf3f90671043..d7f9eda10f3c87 100644 --- a/sdk/src/feature_set.rs +++ b/sdk/src/feature_set.rs @@ -147,6 +147,10 @@ pub mod system_transfer_zero_check { solana_sdk::declare_id!("BrTR9hzw4WBGFP65AJMbpAo64DcA3U6jdPSga9fMV5cS"); } +pub mod dedupe_config_program_signers { + solana_sdk::declare_id!("8kEuAshXLsgkUEdcFVLqrjCGGHVWFW99ZZpxvAzzMtBp"); +} + lazy_static! { /// Map of feature identifiers to user-visible description pub static ref FEATURE_NAMES: HashMap = [ @@ -184,6 +188,7 @@ lazy_static! { (memory_ops_syscalls::id(), "add syscalls for memory operations"), (add_missing_program_error_mappings::id(), "add missing program error mappings"), (system_transfer_zero_check::id(), "perform all checks for transfers of 0 lamports"), + (dedupe_config_program_signers::id(), "dedupe config program signers"), /*************** ADD NEW FEATURES HERE ***************/ ] .iter()