diff --git a/packages/rs-dpp/src/data_contract/associated_token/token_perpetual_distribution/distribution_function/validation.rs b/packages/rs-dpp/src/data_contract/associated_token/token_perpetual_distribution/distribution_function/validation.rs index c9738c71f1..0d7d782db5 100644 --- a/packages/rs-dpp/src/data_contract/associated_token/token_perpetual_distribution/distribution_function/validation.rs +++ b/packages/rs-dpp/src/data_contract/associated_token/token_perpetual_distribution/distribution_function/validation.rs @@ -1127,6 +1127,162 @@ mod tests { .first_error() .is_some()); } + + #[test] + fn test_step_decreasing_amount_invalid_zero_distribution_start() { + let dist = DistributionFunction::StepDecreasingAmount { + step_count: 10, + decrease_per_interval_numerator: 1, + decrease_per_interval_denominator: 2, + start_decreasing_offset: Some(0), + max_interval_count: None, + distribution_start_amount: 0, + trailing_distribution_interval_amount: 0, + min_value: Some(10), + }; + let result = dist.validate(START_MOMENT, PlatformVersion::latest()); + assert!(result + .expect("no error on test_step_decreasing_amount_invalid_zero_distribution_start") + .first_error() + .is_some()); + } + + #[test] + fn test_step_decreasing_amount_invalid_max_distribution_start() { + let dist = DistributionFunction::StepDecreasingAmount { + step_count: 10, + decrease_per_interval_numerator: 1, + decrease_per_interval_denominator: 2, + start_decreasing_offset: Some(0), + max_interval_count: None, + distribution_start_amount: MAX_DISTRIBUTION_PARAM + 1, + trailing_distribution_interval_amount: 0, + min_value: Some(10), + }; + let result = dist.validate(START_MOMENT, PlatformVersion::latest()); + assert!(result + .expect("no error on test_step_decreasing_amount_invalid_max_distribution_start") + .first_error() + .is_some()); + } + + #[test] + fn test_step_decreasing_amount_invalid_trailing_exceeds_start() { + let dist = DistributionFunction::StepDecreasingAmount { + step_count: 10, + decrease_per_interval_numerator: 1, + decrease_per_interval_denominator: 2, + start_decreasing_offset: Some(0), + max_interval_count: None, + distribution_start_amount: 100, + trailing_distribution_interval_amount: 101, + min_value: Some(10), + }; + let result = dist.validate(START_MOMENT, PlatformVersion::latest()); + assert!(result + .expect("no error on test_step_decreasing_amount_invalid_trailing_exceeds_start") + .first_error() + .is_some()); + } + + #[test] + fn test_step_decreasing_amount_invalid_max_interval_count_too_low() { + let dist = DistributionFunction::StepDecreasingAmount { + step_count: 10, + decrease_per_interval_numerator: 1, + decrease_per_interval_denominator: 2, + start_decreasing_offset: Some(0), + max_interval_count: Some(1), + distribution_start_amount: 100, + trailing_distribution_interval_amount: 0, + min_value: Some(10), + }; + let result = dist.validate(START_MOMENT, PlatformVersion::latest()); + assert!(result + .expect( + "no error on test_step_decreasing_amount_invalid_max_interval_count_too_low" + ) + .first_error() + .is_some()); + } + + #[test] + fn test_step_decreasing_amount_invalid_max_interval_count_too_high() { + let dist = DistributionFunction::StepDecreasingAmount { + step_count: 10, + decrease_per_interval_numerator: 1, + decrease_per_interval_denominator: 2, + start_decreasing_offset: Some(0), + max_interval_count: Some(1025), + distribution_start_amount: 100, + trailing_distribution_interval_amount: 0, + min_value: Some(10), + }; + let result = dist.validate(START_MOMENT, PlatformVersion::latest()); + assert!(result + .expect( + "no error on test_step_decreasing_amount_invalid_max_interval_count_too_high" + ) + .first_error() + .is_some()); + } + + #[test] + fn test_step_decreasing_amount_invalid_zero_numerator() { + let dist = DistributionFunction::StepDecreasingAmount { + step_count: 10, + decrease_per_interval_numerator: 0, + decrease_per_interval_denominator: 2, + start_decreasing_offset: Some(0), + max_interval_count: None, + distribution_start_amount: 100, + trailing_distribution_interval_amount: 0, + min_value: Some(10), + }; + let result = dist.validate(START_MOMENT, PlatformVersion::latest()); + assert!(result + .expect("no error on test_step_decreasing_amount_invalid_zero_numerator") + .first_error() + .is_some()); + } + + #[test] + fn test_step_decreasing_amount_invalid_numerator_gte_denominator() { + let dist = DistributionFunction::StepDecreasingAmount { + step_count: 10, + decrease_per_interval_numerator: 5, + decrease_per_interval_denominator: 5, + start_decreasing_offset: Some(0), + max_interval_count: None, + distribution_start_amount: 100, + trailing_distribution_interval_amount: 0, + min_value: Some(10), + }; + let result = dist.validate(START_MOMENT, PlatformVersion::latest()); + assert!(result + .expect("no error on test_step_decreasing_amount_invalid_numerator_gte_denominator") + .first_error() + .is_some()); + } + + #[test] + fn test_step_decreasing_amount_invalid_min_value_exceeds_start() { + let dist = DistributionFunction::StepDecreasingAmount { + step_count: 10, + decrease_per_interval_numerator: 1, + decrease_per_interval_denominator: 2, + start_decreasing_offset: Some(0), + max_interval_count: None, + distribution_start_amount: 50, + trailing_distribution_interval_amount: 0, + min_value: Some(100), + }; + let result = dist.validate(START_MOMENT, PlatformVersion::latest()); + assert!(result + .expect("no error on test_step_decreasing_amount_invalid_min_value_exceeds_start") + .first_error() + .is_some()); + } } mod stepwise { use super::*; diff --git a/packages/rs-dpp/src/data_contract/methods/validate_update/v0/mod.rs b/packages/rs-dpp/src/data_contract/methods/validate_update/v0/mod.rs index 9657db4afa..5f34dc19df 100644 --- a/packages/rs-dpp/src/data_contract/methods/validate_update/v0/mod.rs +++ b/packages/rs-dpp/src/data_contract/methods/validate_update/v0/mod.rs @@ -612,6 +612,195 @@ mod tests { assert!(result.is_valid()); } + #[test] + fn should_return_invalid_result_if_too_many_keywords() { + let platform_version = PlatformVersion::latest(); + + let old_data_contract = get_data_contract_fixture( + None, + IdentityNonce::default(), + platform_version.protocol_version, + ) + .data_contract_owned(); + + let mut new_data_contract = old_data_contract.clone(); + new_data_contract.set_version(old_data_contract.version() + 1); + new_data_contract.set_keywords((0..51).map(|i| format!("keyword{i:03}")).collect()); + + let result = old_data_contract + .validate_update_v0(&new_data_contract, &BlockInfo::default(), platform_version) + .expect("failed validate update"); + + assert_matches!( + result.errors.as_slice(), + [ConsensusError::BasicError( + BasicError::TooManyKeywordsError(_) + )] + ); + } + + #[test] + fn should_return_invalid_result_if_keyword_too_short() { + let platform_version = PlatformVersion::latest(); + + let old_data_contract = get_data_contract_fixture( + None, + IdentityNonce::default(), + platform_version.protocol_version, + ) + .data_contract_owned(); + + let mut new_data_contract = old_data_contract.clone(); + new_data_contract.set_version(old_data_contract.version() + 1); + new_data_contract.set_keywords(vec!["ab".to_string()]); + + let result = old_data_contract + .validate_update_v0(&new_data_contract, &BlockInfo::default(), platform_version) + .expect("failed validate update"); + + assert_matches!( + result.errors.as_slice(), + [ConsensusError::BasicError( + BasicError::InvalidKeywordLengthError(_) + )] + ); + } + + #[test] + fn should_return_invalid_result_if_keyword_too_long() { + let platform_version = PlatformVersion::latest(); + + let old_data_contract = get_data_contract_fixture( + None, + IdentityNonce::default(), + platform_version.protocol_version, + ) + .data_contract_owned(); + + let mut new_data_contract = old_data_contract.clone(); + new_data_contract.set_version(old_data_contract.version() + 1); + new_data_contract.set_keywords(vec!["a".repeat(51)]); + + let result = old_data_contract + .validate_update_v0(&new_data_contract, &BlockInfo::default(), platform_version) + .expect("failed validate update"); + + assert_matches!( + result.errors.as_slice(), + [ConsensusError::BasicError( + BasicError::InvalidKeywordLengthError(_) + )] + ); + } + + #[test] + fn should_return_invalid_result_if_keyword_has_invalid_chars() { + let platform_version = PlatformVersion::latest(); + + let old_data_contract = get_data_contract_fixture( + None, + IdentityNonce::default(), + platform_version.protocol_version, + ) + .data_contract_owned(); + + let mut new_data_contract = old_data_contract.clone(); + new_data_contract.set_version(old_data_contract.version() + 1); + new_data_contract.set_keywords(vec!["hello world".to_string()]); + + let result = old_data_contract + .validate_update_v0(&new_data_contract, &BlockInfo::default(), platform_version) + .expect("failed validate update"); + + assert_matches!( + result.errors.as_slice(), + [ConsensusError::BasicError( + BasicError::InvalidKeywordCharacterError(_) + )] + ); + } + + #[test] + fn should_return_invalid_result_if_duplicate_keywords() { + let platform_version = PlatformVersion::latest(); + + let old_data_contract = get_data_contract_fixture( + None, + IdentityNonce::default(), + platform_version.protocol_version, + ) + .data_contract_owned(); + + let mut new_data_contract = old_data_contract.clone(); + new_data_contract.set_version(old_data_contract.version() + 1); + new_data_contract.set_keywords(vec!["keyword".to_string(), "keyword".to_string()]); + + let result = old_data_contract + .validate_update_v0(&new_data_contract, &BlockInfo::default(), platform_version) + .expect("failed validate update"); + + assert_matches!( + result.errors.as_slice(), + [ConsensusError::BasicError( + BasicError::DuplicateKeywordsError(_) + )] + ); + } + + #[test] + fn should_return_invalid_result_if_description_too_short() { + let platform_version = PlatformVersion::latest(); + + let old_data_contract = get_data_contract_fixture( + None, + IdentityNonce::default(), + platform_version.protocol_version, + ) + .data_contract_owned(); + + let mut new_data_contract = old_data_contract.clone(); + new_data_contract.set_version(old_data_contract.version() + 1); + new_data_contract.set_description(Some("ab".to_string())); + + let result = old_data_contract + .validate_update_v0(&new_data_contract, &BlockInfo::default(), platform_version) + .expect("failed validate update"); + + assert_matches!( + result.errors.as_slice(), + [ConsensusError::BasicError( + BasicError::InvalidDescriptionLengthError(_) + )] + ); + } + + #[test] + fn should_return_invalid_result_if_description_too_long() { + let platform_version = PlatformVersion::latest(); + + let old_data_contract = get_data_contract_fixture( + None, + IdentityNonce::default(), + platform_version.protocol_version, + ) + .data_contract_owned(); + + let mut new_data_contract = old_data_contract.clone(); + new_data_contract.set_version(old_data_contract.version() + 1); + new_data_contract.set_description(Some("a".repeat(101))); + + let result = old_data_contract + .validate_update_v0(&new_data_contract, &BlockInfo::default(), platform_version) + .expect("failed validate update"); + + assert_matches!( + result.errors.as_slice(), + [ConsensusError::BasicError( + BasicError::InvalidDescriptionLengthError(_) + )] + ); + } + // // ────────────────────────────────────────────────────────────────────────── // Group‑related rules