diff --git a/contracts/manage_hub/src/royalty.rs b/contracts/manage_hub/src/royalty.rs index 287d2ed1..61512ab9 100644 --- a/contracts/manage_hub/src/royalty.rs +++ b/contracts/manage_hub/src/royalty.rs @@ -20,11 +20,17 @@ impl RoyaltyModule { fn validate_config(recipients: &Vec) -> Result { let mut total_percentage: u32 = 0; - for recipient in recipients.iter() { + for (index, recipient) in recipients.iter().enumerate() { if recipient.percentage == 0 { return Err(Error::InvalidPaymentAmount); } + for previous_index in 0..index { + if recipients.get(previous_index).unwrap().address == recipient.address { + return Err(Error::InvalidPaymentAmount); + } + } + total_percentage = total_percentage .checked_add(recipient.percentage) .ok_or(Error::InvalidPaymentAmount)?; @@ -98,6 +104,8 @@ impl RoyaltyModule { return Ok(0); } + Self::validate_config(&cfg.recipients)?; + // Calculate royalties with deterministic rounding (CT-16) // Ensure total_royalty <= sale_price let mut total_royalty_amount: i128 = 0; diff --git a/contracts/manage_hub/src/test.rs b/contracts/manage_hub/src/test.rs index 53b944c0..0b4641bd 100644 --- a/contracts/manage_hub/src/test.rs +++ b/contracts/manage_hub/src/test.rs @@ -4234,6 +4234,75 @@ fn test_royalty_exact_full_allocation_accepted() { assert_eq!(info.config.recipients.len(), 2); } +#[test] +fn test_royalty_rejected_updates_preserve_existing_config() { + let env = Env::default(); + env.mock_all_auths(); + + let contract_id = env.register(Contract, ()); + let client = ContractClient::new(&env, &contract_id); + + let admin = Address::generate(&env); + client.set_admin(&admin); + + let token_id = BytesN::<32>::random(&env); + let owner = Address::generate(&env); + client.issue_token(&token_id, &owner, &(env.ledger().timestamp() + 1000)); + + let first = Address::generate(&env); + let second = Address::generate(&env); + let valid_recipients = vec![ + &env, + types::RoyaltyRecipient { + address: first.clone(), + percentage: 3000, + }, + types::RoyaltyRecipient { + address: second.clone(), + percentage: 2000, + }, + ]; + client.set_royalty(&token_id, &valid_recipients); + + let duplicate_recipients = vec![ + &env, + types::RoyaltyRecipient { + address: first.clone(), + percentage: 1000, + }, + types::RoyaltyRecipient { + address: first.clone(), + percentage: 1000, + }, + ]; + assert_eq!( + client.try_set_royalty(&token_id, &duplicate_recipients), + Err(Ok(Error::InvalidPaymentAmount)) + ); + let info_after_duplicate = client.get_royalty_info(&token_id).unwrap(); + assert_eq!(info_after_duplicate.config.recipients, valid_recipients); + assert_eq!(info_after_duplicate.total_percentage, 5000); + + let excessive_recipients = vec![ + &env, + types::RoyaltyRecipient { + address: first, + percentage: 6000, + }, + types::RoyaltyRecipient { + address: second, + percentage: 5000, + }, + ]; + assert_eq!( + client.try_set_royalty(&token_id, &excessive_recipients), + Err(Ok(Error::InvalidPaymentAmount)) + ); + let info_after_excessive = client.get_royalty_info(&token_id).unwrap(); + assert_eq!(info_after_excessive.config.recipients, valid_recipients); + assert_eq!(info_after_excessive.total_percentage, 5000); +} + #[test] fn test_transfer_with_royalty_events() { let env = Env::default();