From 8d09bad43eff70becb2fa8c08ff867558a36c555 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Wed, 7 May 2025 18:21:09 +0700 Subject: [PATCH 1/4] fix: correct decimal validation errors and improve error handling --- .../accessors/mod.rs | 23 ++++- .../accessors/v0/mod.rs | 4 +- .../methods/validate_localizations/v0/mod.rs | 92 +++++++++++++++++++ .../token_configuration_convention/v0/mod.rs | 23 ++++- .../methods/validate_update/v0/mod.rs | 21 ++++- .../src/data_contract/v1/serialization/mod.rs | 5 +- .../src/errors/consensus/basic/basic_error.rs | 35 +++++-- .../invalid_keyword_character_error.rs | 45 +++++++++ .../invalid_token_language_code_error.rs | 34 +++++++ .../invalid_token_name_character_error.rs | 41 +++++++++ .../invalid_token_name_length_error.rs | 56 +++++++++++ .../data_contract/keywords_over_limit.rs | 2 +- .../consensus/basic/data_contract/mod.rs | 10 ++ .../token_decimals_over_limit_error.rs | 44 +++++++++ packages/rs-dpp/src/errors/consensus/codes.rs | 11 ++- .../basic_structure/v0/mod.rs | 21 ++++- .../src/errors/consensus/consensus_error.rs | 17 +++- 17 files changed, 455 insertions(+), 29 deletions(-) create mode 100644 packages/rs-dpp/src/errors/consensus/basic/data_contract/invalid_keyword_character_error.rs create mode 100644 packages/rs-dpp/src/errors/consensus/basic/data_contract/invalid_token_language_code_error.rs create mode 100644 packages/rs-dpp/src/errors/consensus/basic/data_contract/invalid_token_name_character_error.rs create mode 100644 packages/rs-dpp/src/errors/consensus/basic/data_contract/invalid_token_name_length_error.rs create mode 100644 packages/rs-dpp/src/errors/consensus/basic/data_contract/token_decimals_over_limit_error.rs diff --git a/packages/rs-dpp/src/data_contract/associated_token/token_configuration_convention/accessors/mod.rs b/packages/rs-dpp/src/data_contract/associated_token/token_configuration_convention/accessors/mod.rs index c045ba50fd0..83455a5f2e4 100644 --- a/packages/rs-dpp/src/data_contract/associated_token/token_configuration_convention/accessors/mod.rs +++ b/packages/rs-dpp/src/data_contract/associated_token/token_configuration_convention/accessors/mod.rs @@ -1,6 +1,8 @@ pub mod v0; -use crate::data_contract::associated_token::token_configuration_convention::accessors::v0::TokenConfigurationConventionV0Getters; +use crate::data_contract::associated_token::token_configuration_convention::accessors::v0::{ + TokenConfigurationConventionV0Getters, TokenConfigurationConventionV0Setters, +}; use crate::data_contract::associated_token::token_configuration_convention::TokenConfigurationConvention; use crate::data_contract::associated_token::token_configuration_localization::TokenConfigurationLocalization; use std::collections::BTreeMap; @@ -34,9 +36,26 @@ impl TokenConfigurationConventionV0Getters for TokenConfigurationConvention { } } - fn decimals(&self) -> u16 { + fn decimals(&self) -> u8 { match self { TokenConfigurationConvention::V0(v0) => v0.decimals(), } } } + +impl TokenConfigurationConventionV0Setters for TokenConfigurationConvention { + fn set_localizations( + &mut self, + localizations: BTreeMap, + ) { + match self { + TokenConfigurationConvention::V0(v0) => v0.set_localizations(localizations), + } + } + + fn set_decimals(&mut self, decimals: u8) { + match self { + TokenConfigurationConvention::V0(v0) => v0.set_decimals(decimals), + } + } +} diff --git a/packages/rs-dpp/src/data_contract/associated_token/token_configuration_convention/accessors/v0/mod.rs b/packages/rs-dpp/src/data_contract/associated_token/token_configuration_convention/accessors/v0/mod.rs index 0eac97413e4..c4cd587bd13 100644 --- a/packages/rs-dpp/src/data_contract/associated_token/token_configuration_convention/accessors/v0/mod.rs +++ b/packages/rs-dpp/src/data_contract/associated_token/token_configuration_convention/accessors/v0/mod.rs @@ -14,7 +14,7 @@ pub trait TokenConfigurationConventionV0Getters { fn localizations_mut(&mut self) -> &mut BTreeMap; /// Returns the decimals value. - fn decimals(&self) -> u16; + fn decimals(&self) -> u8; } /// Accessor trait for setters of `TokenConfigurationConventionV0` @@ -26,5 +26,5 @@ pub trait TokenConfigurationConventionV0Setters { ); /// Sets the decimals value. - fn set_decimals(&mut self, decimals: u16); + fn set_decimals(&mut self, decimals: u8); } diff --git a/packages/rs-dpp/src/data_contract/associated_token/token_configuration_convention/methods/validate_localizations/v0/mod.rs b/packages/rs-dpp/src/data_contract/associated_token/token_configuration_convention/methods/validate_localizations/v0/mod.rs index 72670aeb830..dbf044fd425 100644 --- a/packages/rs-dpp/src/data_contract/associated_token/token_configuration_convention/methods/validate_localizations/v0/mod.rs +++ b/packages/rs-dpp/src/data_contract/associated_token/token_configuration_convention/methods/validate_localizations/v0/mod.rs @@ -1,6 +1,18 @@ +use crate::consensus::basic::data_contract::{ + DecimalsOverLimitError, InvalidTokenLanguageCodeError, InvalidTokenNameCharacterError, + InvalidTokenNameLengthError, +}; use crate::consensus::basic::token::MissingDefaultLocalizationError; +use crate::data_contract::associated_token::token_configuration_convention::accessors::v0::TokenConfigurationConventionV0Getters; use crate::data_contract::associated_token::token_configuration_convention::TokenConfigurationConvention; +use crate::data_contract::associated_token::token_configuration_localization::accessors::v0::TokenConfigurationLocalizationV0Getters; use crate::validation::SimpleConsensusValidationResult; +use once_cell::sync::Lazy; +use regex::Regex; + +static LANG_CODE_REGEX: Lazy = Lazy::new(|| { + Regex::new(r"^[a-zA-Z]{2,3}(-[a-zA-Z]{4})?(-[a-zA-Z]{2}|\d{3})?(-[a-zA-Z0-9]{4,8})*(-[a-wy-zA-WY-Z0-9](-[a-zA-Z0-9]{2,8})+)*(-x(-[a-zA-Z0-9]{1,8})+)?$").unwrap() +}); impl TokenConfigurationConvention { #[inline(always)] @@ -16,6 +28,86 @@ impl TokenConfigurationConvention { ); } + // Max decimals is defined as 16 + if self.decimals() > 16 { + return SimpleConsensusValidationResult::new_with_error( + DecimalsOverLimitError::new(self.decimals(), 16).into(), + ); + } + + for (language, localization) in self.localizations() { + let singular_form = localization.singular_form(); + let plural_form = localization.plural_form(); + + if !singular_form + .chars() + .all(|c| !c.is_control() && !c.is_whitespace()) + { + // This would mean we have an invalid character + return SimpleConsensusValidationResult::new_with_error( + InvalidTokenNameCharacterError::new( + "singular form".to_string(), + singular_form.to_string(), + ) + .into(), + ); + } + + if !plural_form + .chars() + .all(|c| !c.is_control() && !c.is_whitespace()) + { + // This would mean we have an invalid character + return SimpleConsensusValidationResult::new_with_error( + InvalidTokenNameCharacterError::new( + "plural form".to_string(), + plural_form.to_string(), + ) + .into(), + ); + } + + if !language + .chars() + .all(|c| !c.is_control() && !c.is_whitespace()) + { + // This would mean we have an invalid character + return SimpleConsensusValidationResult::new_with_error( + InvalidTokenNameCharacterError::new( + "language code".to_string(), + language.clone(), + ) + .into(), + ); + } + + if singular_form.len() < 3 || singular_form.len() > 25 { + return SimpleConsensusValidationResult::new_with_error( + InvalidTokenNameLengthError::new(singular_form.len(), 3, 25, "singular form") + .into(), + ); + } + if plural_form.len() < 3 || plural_form.len() > 25 { + return SimpleConsensusValidationResult::new_with_error( + InvalidTokenNameLengthError::new(plural_form.len(), 3, 25, "plural form") + .into(), + ); + } + + if language.len() < 2 || language.len() > 12 { + return SimpleConsensusValidationResult::new_with_error( + InvalidTokenNameLengthError::new(plural_form.len(), 2, 12, "language code") + .into(), + ); + } + + if !LANG_CODE_REGEX.is_match(language) { + return SimpleConsensusValidationResult::new_with_error( + InvalidTokenLanguageCodeError::new(language.clone()).into(), + ); + } + } + // If we reach here with no errors, return an empty result SimpleConsensusValidationResult::new() } diff --git a/packages/rs-dpp/src/data_contract/associated_token/token_configuration_convention/v0/mod.rs b/packages/rs-dpp/src/data_contract/associated_token/token_configuration_convention/v0/mod.rs index d571572679e..f36aa4d12b0 100644 --- a/packages/rs-dpp/src/data_contract/associated_token/token_configuration_convention/v0/mod.rs +++ b/packages/rs-dpp/src/data_contract/associated_token/token_configuration_convention/v0/mod.rs @@ -1,4 +1,6 @@ -use crate::data_contract::associated_token::token_configuration_convention::accessors::v0::TokenConfigurationConventionV0Getters; +use crate::data_contract::associated_token::token_configuration_convention::accessors::v0::{ + TokenConfigurationConventionV0Getters, TokenConfigurationConventionV0Setters, +}; use crate::data_contract::associated_token::token_configuration_localization::accessors::v0::TokenConfigurationLocalizationV0Getters; use crate::data_contract::associated_token::token_configuration_localization::TokenConfigurationLocalization; use bincode::Encode; @@ -34,11 +36,11 @@ pub struct TokenConfigurationConventionV0 { /// /// This value is used by clients to determine formatting and user interface display. #[serde(default = "default_decimals")] - pub decimals: u16, + pub decimals: u8, } // Default function for `decimals` -fn default_decimals() -> u16 { +fn default_decimals() -> u8 { 8 // Default value for decimals } @@ -82,7 +84,20 @@ impl TokenConfigurationConventionV0Getters for TokenConfigurationConventionV0 { &mut self.localizations } - fn decimals(&self) -> u16 { + fn decimals(&self) -> u8 { self.decimals } } + +impl TokenConfigurationConventionV0Setters for TokenConfigurationConventionV0 { + fn set_localizations( + &mut self, + localizations: BTreeMap, + ) { + self.localizations = localizations; + } + + fn set_decimals(&mut self, decimals: u8) { + self.decimals = decimals; + } +} 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 edfe992a22b..8a2eeecb00e 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 @@ -7,7 +7,8 @@ use crate::data_contract::accessors::v0::DataContractV0Getters; use crate::consensus::basic::data_contract::{ DuplicateKeywordsError, IncompatibleDataContractSchemaError, InvalidDataContractVersionError, - InvalidDescriptionLengthError, InvalidKeywordLengthError, TooManyKeywordsError, + InvalidDescriptionLengthError, InvalidKeywordCharacterError, InvalidKeywordLengthError, + TooManyKeywordsError, }; use crate::consensus::state::data_contract::data_contract_update_action_not_allowed_error::DataContractUpdateActionNotAllowedError; use crate::consensus::state::data_contract::data_contract_update_permission_error::DataContractUpdatePermissionError; @@ -267,8 +268,8 @@ impl DataContract { } if self.keywords() != new_data_contract.keywords() { - // Validate there are no more than 20 keywords - if new_data_contract.keywords().len() > 20 { + // Validate there are no more than 50 keywords + if new_data_contract.keywords().len() > 50 { return Ok(SimpleConsensusValidationResult::new_with_error( TooManyKeywordsError::new(self.id(), self.keywords().len() as u8).into(), )); @@ -284,6 +285,20 @@ impl DataContract { )); } + if !keyword + .chars() + .all(|c| !c.is_control() && !c.is_whitespace()) + { + // This would mean we have an invalid character + return Ok(SimpleConsensusValidationResult::new_with_error( + InvalidKeywordCharacterError::new( + new_data_contract.id(), + keyword.to_string(), + ) + .into(), + )); + } + // Then check uniqueness if !seen_keywords.insert(keyword) { return Ok(SimpleConsensusValidationResult::new_with_error( diff --git a/packages/rs-dpp/src/data_contract/v1/serialization/mod.rs b/packages/rs-dpp/src/data_contract/v1/serialization/mod.rs index 2bb9d2ee660..1000499143e 100644 --- a/packages/rs-dpp/src/data_contract/v1/serialization/mod.rs +++ b/packages/rs-dpp/src/data_contract/v1/serialization/mod.rs @@ -173,7 +173,10 @@ impl DataContractV1 { updated_at_epoch, groups, tokens, - keywords, + keywords: keywords + .into_iter() + .map(|keyword| keyword.to_lowercase()) + .collect(), description, }; diff --git a/packages/rs-dpp/src/errors/consensus/basic/basic_error.rs b/packages/rs-dpp/src/errors/consensus/basic/basic_error.rs index 1f04318af90..b890b217b47 100644 --- a/packages/rs-dpp/src/errors/consensus/basic/basic_error.rs +++ b/packages/rs-dpp/src/errors/consensus/basic/basic_error.rs @@ -8,18 +8,20 @@ use crate::consensus::basic::data_contract::{ ContestedUniqueIndexOnMutableDocumentTypeError, ContestedUniqueIndexWithUniqueIndexError, DataContractHaveNewUniqueIndexError, DataContractImmutablePropertiesUpdateError, DataContractInvalidIndexDefinitionUpdateError, DataContractTokenConfigurationUpdateError, - DataContractUniqueIndicesChangedError, DuplicateIndexError, DuplicateIndexNameError, - GroupExceedsMaxMembersError, GroupMemberHasPowerOfZeroError, GroupMemberHasPowerOverLimitError, - GroupNonUnilateralMemberPowerHasLessThanRequiredPowerError, GroupPositionDoesNotExistError, - GroupTotalPowerLessThanRequiredError, IncompatibleDataContractSchemaError, - IncompatibleDocumentTypeSchemaError, IncompatibleRe2PatternError, InvalidCompoundIndexError, - InvalidDataContractIdError, InvalidDataContractVersionError, InvalidDocumentTypeNameError, + DataContractUniqueIndicesChangedError, DecimalsOverLimitError, DuplicateIndexError, + DuplicateIndexNameError, GroupExceedsMaxMembersError, GroupMemberHasPowerOfZeroError, + GroupMemberHasPowerOverLimitError, GroupNonUnilateralMemberPowerHasLessThanRequiredPowerError, + GroupPositionDoesNotExistError, GroupTotalPowerLessThanRequiredError, + IncompatibleDataContractSchemaError, IncompatibleDocumentTypeSchemaError, + IncompatibleRe2PatternError, InvalidCompoundIndexError, InvalidDataContractIdError, + InvalidDataContractVersionError, InvalidDocumentTypeNameError, InvalidDocumentTypeRequiredSecurityLevelError, InvalidIndexPropertyTypeError, - InvalidIndexedPropertyConstraintError, InvalidTokenBaseSupplyError, - InvalidTokenDistributionFunctionDivideByZeroError, + InvalidIndexedPropertyConstraintError, InvalidKeywordCharacterError, + InvalidTokenBaseSupplyError, InvalidTokenDistributionFunctionDivideByZeroError, InvalidTokenDistributionFunctionIncoherenceError, InvalidTokenDistributionFunctionInvalidParameterError, - InvalidTokenDistributionFunctionInvalidParameterTupleError, + InvalidTokenDistributionFunctionInvalidParameterTupleError, InvalidTokenLanguageCodeError, + InvalidTokenNameCharacterError, InvalidTokenNameLengthError, NewTokensDestinationIdentityOptionRequiredError, NonContiguousContractGroupPositionsError, NonContiguousContractTokenPositionsError, SystemPropertyIndexAlreadyPresentError, UndefinedIndexPropertyError, UniqueIndicesLimitReachedError, @@ -537,6 +539,21 @@ pub enum BasicError { NewTokensDestinationIdentityOptionRequiredError( NewTokensDestinationIdentityOptionRequiredError, ), + + #[error(transparent)] + InvalidKeywordCharacterError(InvalidKeywordCharacterError), + + #[error(transparent)] + InvalidTokenNameCharacterError(InvalidTokenNameCharacterError), + + #[error(transparent)] + DecimalsOverLimitError(DecimalsOverLimitError), + + #[error(transparent)] + InvalidTokenNameLengthError(InvalidTokenNameLengthError), + + #[error(transparent)] + InvalidTokenLanguageCodeError(InvalidTokenLanguageCodeError), } impl From for ConsensusError { diff --git a/packages/rs-dpp/src/errors/consensus/basic/data_contract/invalid_keyword_character_error.rs b/packages/rs-dpp/src/errors/consensus/basic/data_contract/invalid_keyword_character_error.rs new file mode 100644 index 00000000000..48e02d3431c --- /dev/null +++ b/packages/rs-dpp/src/errors/consensus/basic/data_contract/invalid_keyword_character_error.rs @@ -0,0 +1,45 @@ +use crate::consensus::basic::BasicError; +use crate::consensus::ConsensusError; +use crate::errors::ProtocolError; +use bincode::{Decode, Encode}; +use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize}; +use platform_value::Identifier; +use thiserror::Error; + +#[derive( + Error, Debug, Clone, PartialEq, Eq, Encode, Decode, PlatformSerialize, PlatformDeserialize, +)] +#[error("Data contract {data_contract_id} has a keyword with invalid characters. Keywords must not contain whitespace or control characters.")] +#[platform_serialize(unversioned)] +pub struct InvalidKeywordCharacterError { + /* + + DO NOT CHANGE ORDER OF FIELDS WITHOUT INTRODUCING OF NEW VERSION + + */ + data_contract_id: Identifier, + keyword: String, +} + +impl InvalidKeywordCharacterError { + pub fn new(data_contract_id: Identifier, keyword: String) -> Self { + Self { + data_contract_id, + keyword, + } + } + + pub fn data_contract_id(&self) -> &Identifier { + &self.data_contract_id + } + + pub fn keyword(&self) -> &str { + &self.keyword + } +} + +impl From for ConsensusError { + fn from(err: InvalidKeywordCharacterError) -> Self { + Self::BasicError(BasicError::InvalidKeywordCharacterError(err)) + } +} diff --git a/packages/rs-dpp/src/errors/consensus/basic/data_contract/invalid_token_language_code_error.rs b/packages/rs-dpp/src/errors/consensus/basic/data_contract/invalid_token_language_code_error.rs new file mode 100644 index 00000000000..a5a6d0d03c9 --- /dev/null +++ b/packages/rs-dpp/src/errors/consensus/basic/data_contract/invalid_token_language_code_error.rs @@ -0,0 +1,34 @@ +use crate::consensus::basic::BasicError; +use crate::consensus::ConsensusError; +use crate::errors::ProtocolError; +use bincode::{Decode, Encode}; +use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize}; +use thiserror::Error; + +#[derive( + Error, Debug, Clone, PartialEq, Eq, Encode, Decode, PlatformSerialize, PlatformDeserialize, +)] +#[error("Invalid token language code: '{language_code}'")] +#[platform_serialize(unversioned)] +pub struct InvalidTokenLanguageCodeError { + /* + DO NOT CHANGE ORDER OF FIELDS WITHOUT INTRODUCING A NEW VERSION + */ + language_code: String, +} + +impl InvalidTokenLanguageCodeError { + pub fn new(language_code: String) -> Self { + Self { language_code } + } + + pub fn language_code(&self) -> &str { + &self.language_code + } +} + +impl From for ConsensusError { + fn from(err: InvalidTokenLanguageCodeError) -> Self { + Self::BasicError(BasicError::InvalidTokenLanguageCodeError(err)) + } +} diff --git a/packages/rs-dpp/src/errors/consensus/basic/data_contract/invalid_token_name_character_error.rs b/packages/rs-dpp/src/errors/consensus/basic/data_contract/invalid_token_name_character_error.rs new file mode 100644 index 00000000000..a15763539d1 --- /dev/null +++ b/packages/rs-dpp/src/errors/consensus/basic/data_contract/invalid_token_name_character_error.rs @@ -0,0 +1,41 @@ +use crate::consensus::basic::BasicError; +use crate::consensus::ConsensusError; +use crate::errors::ProtocolError; +use bincode::{Decode, Encode}; +use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize}; +use thiserror::Error; + +#[derive( + Error, Debug, Clone, PartialEq, Eq, Encode, Decode, PlatformSerialize, PlatformDeserialize, +)] +#[error("Data contract has a token name {token_name} in {form} with invalid characters. Token names must not contain whitespace or control characters.")] +#[platform_serialize(unversioned)] +pub struct InvalidTokenNameCharacterError { + /* + + DO NOT CHANGE ORDER OF FIELDS WITHOUT INTRODUCING OF NEW VERSION + + */ + form: String, + token_name: String, +} + +impl InvalidTokenNameCharacterError { + pub fn new(form: String, token_name: String) -> Self { + Self { form, token_name } + } + + pub fn form(&self) -> &str { + &self.form + } + + pub fn token_name(&self) -> &str { + &self.token_name + } +} + +impl From for ConsensusError { + fn from(err: InvalidTokenNameCharacterError) -> Self { + Self::BasicError(BasicError::InvalidTokenNameCharacterError(err)) + } +} diff --git a/packages/rs-dpp/src/errors/consensus/basic/data_contract/invalid_token_name_length_error.rs b/packages/rs-dpp/src/errors/consensus/basic/data_contract/invalid_token_name_length_error.rs new file mode 100644 index 00000000000..333ec475fd0 --- /dev/null +++ b/packages/rs-dpp/src/errors/consensus/basic/data_contract/invalid_token_name_length_error.rs @@ -0,0 +1,56 @@ +use crate::consensus::basic::BasicError; +use crate::consensus::ConsensusError; +use crate::errors::ProtocolError; +use bincode::{Decode, Encode}; +use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize}; +use thiserror::Error; + +#[derive( + Error, Debug, Clone, PartialEq, Eq, Encode, Decode, PlatformSerialize, PlatformDeserialize, +)] +#[error("Token {form} length was {actual}, but must be between {min} and {max} characters.")] +#[platform_serialize(unversioned)] +pub struct InvalidTokenNameLengthError { + /* + + DO NOT CHANGE ORDER OF FIELDS WITHOUT INTRODUCING A NEW VERSION + + */ + actual: usize, + min: usize, + max: usize, + form: String, +} + +impl InvalidTokenNameLengthError { + pub fn new(actual: usize, min: usize, max: usize, form: impl Into) -> Self { + Self { + actual, + min, + max, + form: form.into(), + } + } + + pub fn actual(&self) -> usize { + self.actual + } + + pub fn min(&self) -> usize { + self.min + } + + pub fn max(&self) -> usize { + self.max + } + + pub fn form(&self) -> &str { + &self.form + } +} + +impl From for ConsensusError { + fn from(err: InvalidTokenNameLengthError) -> Self { + ConsensusError::BasicError(BasicError::InvalidTokenNameLengthError(err)) + } +} diff --git a/packages/rs-dpp/src/errors/consensus/basic/data_contract/keywords_over_limit.rs b/packages/rs-dpp/src/errors/consensus/basic/data_contract/keywords_over_limit.rs index 9fd590bc3c4..9a0b6b20481 100644 --- a/packages/rs-dpp/src/errors/consensus/basic/data_contract/keywords_over_limit.rs +++ b/packages/rs-dpp/src/errors/consensus/basic/data_contract/keywords_over_limit.rs @@ -10,7 +10,7 @@ use thiserror::Error; Error, Debug, Clone, PartialEq, Eq, Encode, Decode, PlatformSerialize, PlatformDeserialize, )] #[error( - "Data contract {data_contract_id} has too many keywords: '{keywords_len}'. The maximum is 20." + "Data contract {data_contract_id} has too many keywords: '{keywords_len}'. The maximum is 50." )] #[platform_serialize(unversioned)] pub struct TooManyKeywordsError { diff --git a/packages/rs-dpp/src/errors/consensus/basic/data_contract/mod.rs b/packages/rs-dpp/src/errors/consensus/basic/data_contract/mod.rs index 98ed1fd638a..3653a3a47fb 100644 --- a/packages/rs-dpp/src/errors/consensus/basic/data_contract/mod.rs +++ b/packages/rs-dpp/src/errors/consensus/basic/data_contract/mod.rs @@ -28,17 +28,22 @@ mod invalid_document_type_required_security_level; mod invalid_index_property_type_error; mod invalid_indexed_property_constraint_error; mod invalid_json_schema_ref_error; +mod invalid_keyword_character_error; mod invalid_keyword_length_error; mod invalid_token_base_supply_error; mod invalid_token_distribution_function_divide_by_zero_error; mod invalid_token_distribution_function_incoherence_error; mod invalid_token_distribution_function_invalid_parameter_error; mod invalid_token_distribution_function_invalid_parameter_tuple_error; +mod invalid_token_language_code_error; +mod invalid_token_name_character_error; +mod invalid_token_name_length_error; mod keywords_over_limit; mod new_tokens_destination_identity_option_required_error; mod non_contiguous_contract_group_positions_error; mod non_contiguous_contract_token_positions_error; mod system_property_index_already_present_error; +mod token_decimals_over_limit_error; mod token_payment_by_burning_only_allowed_on_internal_token_error; mod undefined_index_property_error; mod unique_indices_limit_reached_error; @@ -84,16 +89,21 @@ pub use group_total_power_has_less_than_required_power_error::*; pub use incompatible_document_type_schema_error::*; pub use invalid_description_length_error::*; pub use invalid_document_type_name_error::*; +pub use invalid_keyword_character_error::*; pub use invalid_keyword_length_error::*; pub use invalid_token_base_supply_error::*; pub use invalid_token_distribution_function_divide_by_zero_error::*; pub use invalid_token_distribution_function_incoherence_error::*; pub use invalid_token_distribution_function_invalid_parameter_error::*; pub use invalid_token_distribution_function_invalid_parameter_tuple_error::*; +pub use invalid_token_language_code_error::*; +pub use invalid_token_name_character_error::*; +pub use invalid_token_name_length_error::*; pub use keywords_over_limit::*; pub use new_tokens_destination_identity_option_required_error::*; pub use non_contiguous_contract_group_positions_error::*; pub use non_contiguous_contract_token_positions_error::*; +pub use token_decimals_over_limit_error::*; pub use token_payment_by_burning_only_allowed_on_internal_token_error::*; pub use unknown_document_action_token_effect_error::*; pub use unknown_document_creation_restriction_mode_error::*; diff --git a/packages/rs-dpp/src/errors/consensus/basic/data_contract/token_decimals_over_limit_error.rs b/packages/rs-dpp/src/errors/consensus/basic/data_contract/token_decimals_over_limit_error.rs new file mode 100644 index 00000000000..88159657f6b --- /dev/null +++ b/packages/rs-dpp/src/errors/consensus/basic/data_contract/token_decimals_over_limit_error.rs @@ -0,0 +1,44 @@ +use crate::consensus::basic::BasicError; +use crate::consensus::ConsensusError; +use crate::errors::ProtocolError; +use bincode::{Decode, Encode}; +use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize}; +use thiserror::Error; + +#[derive( + Error, Debug, Clone, PartialEq, Eq, Encode, Decode, PlatformSerialize, PlatformDeserialize, +)] +#[error("Data contract is trying to define a token with {decimals} decimal places, which exceeds the maximum allowed: {max_decimals}.")] +#[platform_serialize(unversioned)] +pub struct DecimalsOverLimitError { + /* + + DO NOT CHANGE ORDER OF FIELDS WITHOUT INTRODUCING OF NEW VERSION + + */ + decimals: u8, + max_decimals: u8, +} + +impl DecimalsOverLimitError { + pub fn new(decimals: u8, max_decimals: u8) -> Self { + Self { + decimals, + max_decimals, + } + } + + pub fn decimals(&self) -> u8 { + self.decimals + } + + pub fn max_decimals(&self) -> u8 { + self.max_decimals + } +} + +impl From for ConsensusError { + fn from(err: DecimalsOverLimitError) -> Self { + Self::BasicError(BasicError::DecimalsOverLimitError(err)) + } +} diff --git a/packages/rs-dpp/src/errors/consensus/codes.rs b/packages/rs-dpp/src/errors/consensus/codes.rs index aa20cc42fb5..a5dc3aaee61 100644 --- a/packages/rs-dpp/src/errors/consensus/codes.rs +++ b/packages/rs-dpp/src/errors/consensus/codes.rs @@ -108,9 +108,14 @@ impl ErrorWithCode for BasicError { Self::TokenPaymentByBurningOnlyAllowedOnInternalTokenError(_) => 10261, Self::TooManyKeywordsError(_) => 10262, Self::DuplicateKeywordsError(_) => 10263, - Self::InvalidKeywordLengthError(_) => 10264, - Self::InvalidDescriptionLengthError(_) => 10265, - Self::NewTokensDestinationIdentityOptionRequiredError(_) => 10266, + Self::InvalidDescriptionLengthError(_) => 10264, + Self::NewTokensDestinationIdentityOptionRequiredError(_) => 10265, + Self::InvalidTokenNameCharacterError(_) => 10266, + Self::InvalidTokenNameLengthError(_) => 10267, + Self::InvalidTokenLanguageCodeError(_) => 10268, + Self::InvalidKeywordCharacterError(_) => 10269, + Self::InvalidKeywordLengthError(_) => 10270, + Self::DecimalsOverLimitError(_) => 10271, // Group Errors: 10350-10399 Self::GroupPositionDoesNotExistError(_) => 10350, diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/basic_structure/v0/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/basic_structure/v0/mod.rs index 5b9a79df558..3740a3d1971 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/basic_structure/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/basic_structure/v0/mod.rs @@ -1,7 +1,7 @@ use crate::error::Error; use dpp::consensus::basic::data_contract::{ DuplicateKeywordsError, InvalidDataContractVersionError, InvalidDescriptionLengthError, - InvalidKeywordLengthError, InvalidTokenBaseSupplyError, + InvalidKeywordCharacterError, InvalidKeywordLengthError, InvalidTokenBaseSupplyError, NewTokensDestinationIdentityOptionRequiredError, NonContiguousContractTokenPositionsError, TooManyKeywordsError, }; @@ -117,8 +117,8 @@ impl DataContractCreateStateTransitionBasicStructureValidationV0 for DataContrac } } - // Validate there are no more than 20 keywords - if self.data_contract().keywords().len() > 20 { + // Validate there are no more than 50 keywords + if self.data_contract().keywords().len() > 50 { return Ok(SimpleConsensusValidationResult::new_with_error( ConsensusError::BasicError(BasicError::TooManyKeywordsError( TooManyKeywordsError::new( @@ -144,6 +144,21 @@ impl DataContractCreateStateTransitionBasicStructureValidationV0 for DataContrac )); } + if !keyword + .chars() + .all(|c| !c.is_control() && !c.is_whitespace()) + { + // This would mean we have an invalid character + return Ok(SimpleConsensusValidationResult::new_with_error( + ConsensusError::BasicError(BasicError::InvalidKeywordCharacterError( + InvalidKeywordCharacterError::new( + self.data_contract().id(), + keyword.to_string(), + ), + )), + )); + } + // Then check uniqueness if !seen_keywords.insert(keyword) { return Ok(SimpleConsensusValidationResult::new_with_error( diff --git a/packages/wasm-dpp/src/errors/consensus/consensus_error.rs b/packages/wasm-dpp/src/errors/consensus/consensus_error.rs index ee1dc4eecfd..10fc8374422 100644 --- a/packages/wasm-dpp/src/errors/consensus/consensus_error.rs +++ b/packages/wasm-dpp/src/errors/consensus/consensus_error.rs @@ -61,7 +61,7 @@ use dpp::consensus::state::data_trigger::DataTriggerError::{ DataTriggerConditionError, DataTriggerExecutionError, DataTriggerInvalidResultError, }; use wasm_bindgen::{JsError, JsValue}; -use dpp::consensus::basic::data_contract::{ContestedUniqueIndexOnMutableDocumentTypeError, ContestedUniqueIndexWithUniqueIndexError, DataContractTokenConfigurationUpdateError, DuplicateKeywordsError, GroupExceedsMaxMembersError, GroupMemberHasPowerOfZeroError, GroupMemberHasPowerOverLimitError, GroupNonUnilateralMemberPowerHasLessThanRequiredPowerError, GroupPositionDoesNotExistError, GroupTotalPowerLessThanRequiredError, InvalidDescriptionLengthError, InvalidDocumentTypeRequiredSecurityLevelError, InvalidKeywordLengthError, InvalidTokenBaseSupplyError, InvalidTokenDistributionFunctionDivideByZeroError, InvalidTokenDistributionFunctionIncoherenceError, InvalidTokenDistributionFunctionInvalidParameterError, InvalidTokenDistributionFunctionInvalidParameterTupleError, NewTokensDestinationIdentityOptionRequiredError, NonContiguousContractGroupPositionsError, NonContiguousContractTokenPositionsError, TokenPaymentByBurningOnlyAllowedOnInternalTokenError, TooManyKeywordsError, UnknownDocumentActionTokenEffectError, UnknownDocumentCreationRestrictionModeError, UnknownGasFeesPaidByError, UnknownSecurityLevelError, UnknownStorageKeyRequirementsError, UnknownTradeModeError, UnknownTransferableTypeError}; +use dpp::consensus::basic::data_contract::{ContestedUniqueIndexOnMutableDocumentTypeError, ContestedUniqueIndexWithUniqueIndexError, DataContractTokenConfigurationUpdateError, DecimalsOverLimitError, DuplicateKeywordsError, GroupExceedsMaxMembersError, GroupMemberHasPowerOfZeroError, GroupMemberHasPowerOverLimitError, GroupNonUnilateralMemberPowerHasLessThanRequiredPowerError, GroupPositionDoesNotExistError, GroupTotalPowerLessThanRequiredError, InvalidDescriptionLengthError, InvalidDocumentTypeRequiredSecurityLevelError, InvalidKeywordCharacterError, InvalidKeywordLengthError, InvalidTokenBaseSupplyError, InvalidTokenDistributionFunctionDivideByZeroError, InvalidTokenDistributionFunctionIncoherenceError, InvalidTokenDistributionFunctionInvalidParameterError, InvalidTokenDistributionFunctionInvalidParameterTupleError, InvalidTokenLanguageCodeError, InvalidTokenNameCharacterError, InvalidTokenNameLengthError, NewTokensDestinationIdentityOptionRequiredError, NonContiguousContractGroupPositionsError, NonContiguousContractTokenPositionsError, TokenPaymentByBurningOnlyAllowedOnInternalTokenError, TooManyKeywordsError, UnknownDocumentActionTokenEffectError, UnknownDocumentCreationRestrictionModeError, UnknownGasFeesPaidByError, UnknownSecurityLevelError, UnknownStorageKeyRequirementsError, UnknownTradeModeError, UnknownTransferableTypeError}; use dpp::consensus::basic::document::{ContestedDocumentsTemporarilyNotAllowedError, DocumentCreationNotAllowedError, DocumentFieldMaxSizeExceededError, MaxDocumentsTransitionsExceededError, MissingPositionsInDocumentTypePropertiesError}; use dpp::consensus::basic::group::GroupActionNotAllowedOnTransitionError; use dpp::consensus::basic::identity::{DataContractBoundsNotPresentError, DisablingKeyIdAlsoBeingAddedInSameTransitionError, InvalidIdentityCreditWithdrawalTransitionAmountError, InvalidIdentityUpdateTransitionDisableKeysError, InvalidIdentityUpdateTransitionEmptyError, TooManyMasterPublicKeyError, WithdrawalOutputScriptNotAllowedWhenSigningWithOwnerKeyError}; @@ -792,6 +792,21 @@ fn from_basic_error(basic_error: &BasicError) -> JsValue { BasicError::NewTokensDestinationIdentityOptionRequiredError(e) => { generic_consensus_error!(NewTokensDestinationIdentityOptionRequiredError, e).into() } + BasicError::InvalidKeywordCharacterError(e) => { + generic_consensus_error!(InvalidKeywordCharacterError, e).into() + } + BasicError::InvalidTokenNameCharacterError(e) => { + generic_consensus_error!(InvalidTokenNameCharacterError, e).into() + } + BasicError::DecimalsOverLimitError(e) => { + generic_consensus_error!(DecimalsOverLimitError, e).into() + } + BasicError::InvalidTokenNameLengthError(e) => { + generic_consensus_error!(InvalidTokenNameLengthError, e).into() + } + BasicError::InvalidTokenLanguageCodeError(e) => { + generic_consensus_error!(InvalidTokenLanguageCodeError, e).into() + } } } From 67e62b0ee8a48a605568370f62cc907aa81dfed3 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Wed, 7 May 2025 18:47:26 +0700 Subject: [PATCH 2/4] fix --- .../methods/validate_localizations/v0/mod.rs | 3 +-- .../data_contract/methods/validate_update/v0/mod.rs | 3 ++- .../state_transitions/data_contract_create/mod.rs | 4 ++-- .../state_transitions/data_contract_update/mod.rs | 11 +++++++---- .../contested_document_resource_vote_poll/resolve.rs | 1 + 5 files changed, 13 insertions(+), 9 deletions(-) diff --git a/packages/rs-dpp/src/data_contract/associated_token/token_configuration_convention/methods/validate_localizations/v0/mod.rs b/packages/rs-dpp/src/data_contract/associated_token/token_configuration_convention/methods/validate_localizations/v0/mod.rs index dbf044fd425..bafcb311ef0 100644 --- a/packages/rs-dpp/src/data_contract/associated_token/token_configuration_convention/methods/validate_localizations/v0/mod.rs +++ b/packages/rs-dpp/src/data_contract/associated_token/token_configuration_convention/methods/validate_localizations/v0/mod.rs @@ -96,8 +96,7 @@ impl TokenConfigurationConvention { if language.len() < 2 || language.len() > 12 { return SimpleConsensusValidationResult::new_with_error( - InvalidTokenNameLengthError::new(plural_form.len(), 2, 12, "language code") - .into(), + InvalidTokenNameLengthError::new(language.len(), 2, 12, "language code").into(), ); } 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 8a2eeecb00e..013797b4800 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 @@ -271,7 +271,8 @@ impl DataContract { // Validate there are no more than 50 keywords if new_data_contract.keywords().len() > 50 { return Ok(SimpleConsensusValidationResult::new_with_error( - TooManyKeywordsError::new(self.id(), self.keywords().len() as u8).into(), + TooManyKeywordsError::new(self.id(), new_data_contract.keywords().len() as u8) + .into(), )); } diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/mod.rs index a3b98f6e8a4..fe6de87683f 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/mod.rs @@ -3346,7 +3346,7 @@ mod tests { }; #[test] - fn test_data_contract_creation_fails_with_more_than_twenty_keywords() { + fn test_data_contract_creation_fails_with_more_than_fifty_keywords() { let platform_version = PlatformVersion::latest(); let mut platform = TestPlatformBuilder::new() .build_with_mock_rpc() @@ -3374,7 +3374,7 @@ mod tests { // Insert 21 keywords to exceed the max limit let mut excessive_keywords: Vec = vec![]; - for i in 0..21 { + for i in 0..51 { excessive_keywords.push(Value::Text(format!("keyword{}", i))); } contract_value["keywords"] = Value::Array(excessive_keywords); diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_update/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_update/mod.rs index 88d48caa5c8..bbb775beda5 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_update/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_update/mod.rs @@ -2496,6 +2496,9 @@ mod tests { [ "kw0", "kw1", "kw2", "kw3", "kw4", "kw5", "kw6", "kw7", "kw8", "kw9", "kw10", "kw11", "kw12", "kw13", "kw14", "kw15", "kw16", "kw17", "kw18", "kw19", "kw20", + "kw21", "kw22", "kw23", "kw24", "kw25", "kw26", "kw27", "kw28", "kw29", "kw30", + "kw31", "kw32", "kw33", "kw34", "kw35", "kw36", "kw37", "kw38", "kw39", "kw40", + "kw41", "kw42", "kw43", "kw44", "kw45", "kw46", "kw47", "kw48", "kw49", "kw50", ], BasicError::TooManyKeywordsError(_) ); @@ -2566,7 +2569,7 @@ mod tests { .unwrap(); assert_eq!( *fetched.contract.keywords(), - ["newA", "newB", "newC"] + ["newa", "newb", "newc"] .iter() .map(|&s| s.to_string()) .collect::>() @@ -2575,9 +2578,9 @@ mod tests { // search‑contract docs updated? let docs_after = keyword_docs_for_contract(&platform, cid, platform_version); assert_eq!(docs_after.len(), 3); - assert!(docs_after.contains(&"newA".to_string())); - assert!(docs_after.contains(&"newB".to_string())); - assert!(docs_after.contains(&"newC".to_string())); + assert!(docs_after.contains(&"newa".to_string())); + assert!(docs_after.contains(&"newb".to_string())); + assert!(docs_after.contains(&"newc".to_string())); // old docs gone assert!(!docs_after.contains(&"old1".to_string())); assert!(!docs_after.contains(&"old2".to_string())); diff --git a/packages/rs-drive/src/drive/votes/resolved/vote_polls/contested_document_resource_vote_poll/resolve.rs b/packages/rs-drive/src/drive/votes/resolved/vote_polls/contested_document_resource_vote_poll/resolve.rs index e18e18a3139..933c7477241 100644 --- a/packages/rs-drive/src/drive/votes/resolved/vote_polls/contested_document_resource_vote_poll/resolve.rs +++ b/packages/rs-drive/src/drive/votes/resolved/vote_polls/contested_document_resource_vote_poll/resolve.rs @@ -10,6 +10,7 @@ use crate::error::contract::DataContractError; use crate::error::Error; #[cfg(feature = "verify")] use crate::query::ContractLookupFn; +#[cfg(feature = "server")] use crate::util::object_size_info::DataContractOwnedResolvedInfo; #[cfg(any(feature = "server", feature = "verify"))] use crate::util::object_size_info::DataContractResolvedInfo; From 175c1c9e505d5d60190f08b32f62c392483a2938 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Wed, 7 May 2025 19:11:41 +0700 Subject: [PATCH 3/4] fix --- .../rs-dpp/src/data_contract/methods/validate_update/v0/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 013797b4800..af02d509a37 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 @@ -312,7 +312,8 @@ impl DataContract { if self.description() != new_data_contract.description() { // Validate the description is between 3 and 100 characters if let Some(description) = new_data_contract.description() { - if !(description.len() >= 3 && description.len() <= 100) { + let char_count = description.chars().count(); + if !(char_count >= 3 && char_count <= 100) { return Ok(SimpleConsensusValidationResult::new_with_error( InvalidDescriptionLengthError::new(self.id(), description.to_string()) .into(), From e9af0eec419841124ffe567e4ab2c443b6621b35 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Wed, 7 May 2025 19:23:19 +0700 Subject: [PATCH 4/4] fix --- .../rs-dpp/src/data_contract/methods/validate_update/v0/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 af02d509a37..45aeef36e7b 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 @@ -313,7 +313,7 @@ impl DataContract { // Validate the description is between 3 and 100 characters if let Some(description) = new_data_contract.description() { let char_count = description.chars().count(); - if !(char_count >= 3 && char_count <= 100) { + if !(3..=100).contains(&char_count) { return Ok(SimpleConsensusValidationResult::new_with_error( InvalidDescriptionLengthError::new(self.id(), description.to_string()) .into(),