diff --git a/programs/config/src/config_processor.rs b/programs/config/src/config_processor.rs index 105cf8a36e..0d760cf1d2 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!( @@ -505,6 +515,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 1c76705305..6c645a7efd 100644 --- a/sdk/src/feature_set.rs +++ b/sdk/src/feature_set.rs @@ -150,6 +150,10 @@ pub mod memory_ops_syscalls { solana_sdk::declare_id!("ENQi37wsVhTvFz2gUiZAAbqFEWGN2jwFsqdEDTE8A4MU"); } +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 = [ @@ -186,6 +190,7 @@ lazy_static! { (stake_program_v4::id(), "solana_stake_program v4"), (system_transfer_zero_check::id(), "perform all checks for transfers of 0 lamports"), (memory_ops_syscalls::id(), "add syscalls for memory operations"), + (dedupe_config_program_signers::id(), "dedupe config program signers"), /*************** ADD NEW FEATURES HERE ***************/ ] .iter()