diff --git a/.github/workflows/tests-rs-package.yml b/.github/workflows/tests-rs-package.yml index 7730485c626..be6f8a6715f 100644 --- a/.github/workflows/tests-rs-package.yml +++ b/.github/workflows/tests-rs-package.yml @@ -205,7 +205,7 @@ jobs: check_each_feature: name: Check each feature runs-on: ubuntu-24.04 - timeout-minutes: 10 + timeout-minutes: 15 if: ${{ inputs.check-each-feature }} steps: - name: Check out repo 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 e084dffc38f..acfbe376d1d 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 +1,45 @@ -mod v0; +pub mod v0; + +use crate::data_contract::associated_token::token_configuration::accessors::v0::{ + TokenConfigurationV0Getters, TokenConfigurationV0Setters, +}; +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::TokenConfigurationLocalization; +use std::collections::BTreeMap; + +impl TokenConfigurationConventionV0Getters for TokenConfigurationConvention { + fn singular_form_by_language_code_or_default(&self, language_code: &str) -> &str { + match self { + TokenConfigurationConvention::V0(v0) => { + v0.singular_form_by_language_code_or_default(language_code) + } + } + } + + fn plural_form_by_language_code_or_default(&self, language_code: &str) -> &str { + match self { + TokenConfigurationConvention::V0(v0) => { + v0.plural_form_by_language_code_or_default(language_code) + } + } + } + + fn localizations(&self) -> &BTreeMap { + match self { + TokenConfigurationConvention::V0(v0) => v0.localizations(), + } + } + + fn localizations_mut(&mut self) -> &mut BTreeMap { + match self { + TokenConfigurationConvention::V0(v0) => v0.localizations_mut(), + } + } + + fn decimals(&self) -> u16 { + match self { + TokenConfigurationConvention::V0(v0) => v0.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 1554382cc11..0eac97413e4 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 @@ -3,6 +3,10 @@ use std::collections::BTreeMap; /// Accessor trait for getters of `TokenConfigurationConventionV0` pub trait TokenConfigurationConventionV0Getters { + /// Returns the localized token name in singular form + fn singular_form_by_language_code_or_default(&self, language_code: &str) -> &str; + /// Returns the localized token name in plural form + fn plural_form_by_language_code_or_default(&self, language_code: &str) -> &str; /// Returns a reference to the localizations. fn localizations(&self) -> &BTreeMap; diff --git a/packages/rs-dpp/src/data_contract/associated_token/token_configuration_convention/methods/mod.rs b/packages/rs-dpp/src/data_contract/associated_token/token_configuration_convention/methods/mod.rs new file mode 100644 index 00000000000..3bf17581453 --- /dev/null +++ b/packages/rs-dpp/src/data_contract/associated_token/token_configuration_convention/methods/mod.rs @@ -0,0 +1 @@ +mod validate_localizations; diff --git a/packages/rs-dpp/src/data_contract/associated_token/token_configuration_convention/methods/validate_localizations/mod.rs b/packages/rs-dpp/src/data_contract/associated_token/token_configuration_convention/methods/validate_localizations/mod.rs new file mode 100644 index 00000000000..62569166425 --- /dev/null +++ b/packages/rs-dpp/src/data_contract/associated_token/token_configuration_convention/methods/validate_localizations/mod.rs @@ -0,0 +1,27 @@ +use crate::data_contract::associated_token::token_configuration_convention::TokenConfigurationConvention; +use crate::validation::SimpleConsensusValidationResult; +use crate::ProtocolError; +use platform_version::version::PlatformVersion; + +mod v0; + +impl TokenConfigurationConvention { + pub fn validate_localizations( + &self, + platform_version: &PlatformVersion, + ) -> Result { + match platform_version + .dpp + .validation + .data_contract + .validate_localizations + { + 0 => Ok(self.validate_localizations_v0()), + version => Err(ProtocolError::UnknownVersionMismatch { + method: "validate_localizations".to_string(), + known_versions: vec![0], + received: version, + }), + } + } +} 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 new file mode 100644 index 00000000000..72670aeb830 --- /dev/null +++ b/packages/rs-dpp/src/data_contract/associated_token/token_configuration_convention/methods/validate_localizations/v0/mod.rs @@ -0,0 +1,22 @@ +use crate::consensus::basic::token::MissingDefaultLocalizationError; +use crate::data_contract::associated_token::token_configuration_convention::TokenConfigurationConvention; +use crate::validation::SimpleConsensusValidationResult; + +impl TokenConfigurationConvention { + #[inline(always)] + pub(super) fn validate_localizations_v0(&self) -> SimpleConsensusValidationResult { + let english_localization = match self { + TokenConfigurationConvention::V0(v0) => v0.localizations.get("en"), + }; + + // If there is no English localization, return an error + if english_localization.is_none() { + return SimpleConsensusValidationResult::new_with_error( + MissingDefaultLocalizationError::new().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/mod.rs b/packages/rs-dpp/src/data_contract/associated_token/token_configuration_convention/mod.rs index f4b332a6752..58d7bd6d03e 100644 --- a/packages/rs-dpp/src/data_contract/associated_token/token_configuration_convention/mod.rs +++ b/packages/rs-dpp/src/data_contract/associated_token/token_configuration_convention/mod.rs @@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize}; use std::fmt; mod accessors; +mod methods; pub mod v0; #[derive(Serialize, Deserialize, Encode, Decode, Debug, Clone, PartialEq, Eq, PartialOrd, From)] 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 57875837132..aa497f3561d 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,3 +1,5 @@ +use crate::data_contract::associated_token::token_configuration_convention::accessors::v0::TokenConfigurationConventionV0Getters; +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; use platform_serialization::de::Decode; @@ -5,11 +7,15 @@ use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; use std::fmt; +pub const ENGLISH_ISO_639: &str = "en"; + #[derive( Serialize, Deserialize, Decode, Encode, Debug, Clone, PartialEq, Eq, PartialOrd, Default, )] #[serde(rename_all = "camelCase")] pub struct TokenConfigurationConventionV0 { + /// Localizations for the token name. + /// The key must be a ISO 639 2-chars language code #[serde(default)] pub localizations: BTreeMap, #[serde(default = "default_decimals")] @@ -37,3 +43,31 @@ impl fmt::Display for TokenConfigurationConventionV0 { ) } } + +impl TokenConfigurationConventionV0Getters for TokenConfigurationConventionV0 { + fn singular_form_by_language_code_or_default(&self, language_code: &str) -> &str { + self.localizations + .get(language_code) + .map(|localization| localization.singular_form()) + .unwrap_or_else(|| self.localizations[ENGLISH_ISO_639].singular_form()) + } + + fn plural_form_by_language_code_or_default(&self, language_code: &str) -> &str { + self.localizations + .get(language_code) + .map(|localization| localization.plural_form()) + .unwrap_or_else(|| self.localizations[ENGLISH_ISO_639].plural_form()) + } + + fn localizations(&self) -> &BTreeMap { + &self.localizations + } + + fn localizations_mut(&mut self) -> &mut BTreeMap { + &mut self.localizations + } + + fn decimals(&self) -> u16 { + self.decimals + } +} diff --git a/packages/rs-dpp/src/data_contract/associated_token/token_configuration_localization/mod.rs b/packages/rs-dpp/src/data_contract/associated_token/token_configuration_localization/mod.rs index 424d64b0115..6d078d6d2eb 100644 --- a/packages/rs-dpp/src/data_contract/associated_token/token_configuration_localization/mod.rs +++ b/packages/rs-dpp/src/data_contract/associated_token/token_configuration_localization/mod.rs @@ -1,3 +1,4 @@ +use crate::data_contract::associated_token::token_configuration_localization::accessors::v0::TokenConfigurationLocalizationV0Getters; use crate::data_contract::associated_token::token_configuration_localization::v0::TokenConfigurationLocalizationV0; use bincode::Encode; use derive_more::From; 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 70a4f1fc737..33eedfb1a47 100644 --- a/packages/rs-dpp/src/errors/consensus/basic/basic_error.rs +++ b/packages/rs-dpp/src/errors/consensus/basic/basic_error.rs @@ -79,7 +79,7 @@ use crate::consensus::basic::token::{ ChoosingTokenMintRecipientNotAllowedError, ContractHasNoTokensError, DestinationIdentityForTokenMintingNotSetError, InvalidActionIdError, InvalidTokenAmountError, InvalidTokenConfigUpdateNoChangeError, InvalidTokenIdError, InvalidTokenNoteTooBigError, - InvalidTokenPositionError, TokenTransferToOurselfError, + InvalidTokenPositionError, MissingDefaultLocalizationError, TokenTransferToOurselfError, }; use crate::consensus::basic::unsupported_version_error::UnsupportedVersionError; use crate::consensus::basic::value_error::ValueError; @@ -503,6 +503,9 @@ pub enum BasicError { GroupNonUnilateralMemberPowerHasLessThanRequiredPowerError( GroupNonUnilateralMemberPowerHasLessThanRequiredPowerError, ), + + #[error(transparent)] + MissingDefaultLocalizationError(MissingDefaultLocalizationError), } impl From for ConsensusError { diff --git a/packages/rs-dpp/src/errors/consensus/basic/token/missing_default_localization.rs b/packages/rs-dpp/src/errors/consensus/basic/token/missing_default_localization.rs new file mode 100644 index 00000000000..1c81c84b4a4 --- /dev/null +++ b/packages/rs-dpp/src/errors/consensus/basic/token/missing_default_localization.rs @@ -0,0 +1,24 @@ +use crate::consensus::basic::BasicError; +use crate::consensus::ConsensusError; +use crate::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("Missing english ('en') localization which is using by default")] +#[platform_serialize(unversioned)] +pub struct MissingDefaultLocalizationError {} + +impl MissingDefaultLocalizationError { + pub fn new() -> Self { + Self {} + } +} + +impl From for ConsensusError { + fn from(err: MissingDefaultLocalizationError) -> Self { + Self::BasicError(BasicError::MissingDefaultLocalizationError(err)) + } +} diff --git a/packages/rs-dpp/src/errors/consensus/basic/token/mod.rs b/packages/rs-dpp/src/errors/consensus/basic/token/mod.rs index 971cd92a4b4..79ad589ee41 100644 --- a/packages/rs-dpp/src/errors/consensus/basic/token/mod.rs +++ b/packages/rs-dpp/src/errors/consensus/basic/token/mod.rs @@ -7,6 +7,7 @@ mod invalid_token_config_update_no_change_error; mod invalid_token_id_error; mod invalid_token_note_too_big_error; mod invalid_token_position_error; +mod missing_default_localization; mod token_transfer_to_ourselves_error; pub use choosing_token_mint_recipient_not_allowed_error::*; @@ -18,4 +19,5 @@ pub use invalid_token_config_update_no_change_error::*; pub use invalid_token_id_error::*; pub use invalid_token_note_too_big_error::*; pub use invalid_token_position_error::*; +pub use missing_default_localization::*; pub use token_transfer_to_ourselves_error::*; diff --git a/packages/rs-dpp/src/errors/consensus/codes.rs b/packages/rs-dpp/src/errors/consensus/codes.rs index 62066426094..cfd6302971f 100644 --- a/packages/rs-dpp/src/errors/consensus/codes.rs +++ b/packages/rs-dpp/src/errors/consensus/codes.rs @@ -103,9 +103,10 @@ impl ErrorWithCode for BasicError { Self::NonContiguousContractGroupPositionsError(_) => 10252, Self::NonContiguousContractTokenPositionsError(_) => 10253, Self::InvalidTokenDistributionFunctionDivideByZeroError(_) => 10254, - Self::InvalidTokenDistributionFunctionInvalidParameterError(_) => 10254, - Self::InvalidTokenDistributionFunctionInvalidParameterTupleError(_) => 10255, - Self::InvalidTokenDistributionFunctionIncoherenceError(_) => 10256, + Self::InvalidTokenDistributionFunctionInvalidParameterError(_) => 10255, + Self::InvalidTokenDistributionFunctionInvalidParameterTupleError(_) => 10256, + Self::InvalidTokenDistributionFunctionIncoherenceError(_) => 10257, + Self::MissingDefaultLocalizationError(_) => 10258, // Group Errors: 10350-10399 Self::GroupPositionDoesNotExistError(_) => 10350, diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/advanced_structure/v0/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/advanced_structure/v0/mod.rs index f4ca13d3a20..cb0983a8109 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/advanced_structure/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/advanced_structure/v0/mod.rs @@ -118,6 +118,20 @@ impl DataContractCreatedStateTransitionAdvancedStructureValidationV0 )); } + let validation_result = token_configuration + .conventions() + .validate_localizations(platform_version)?; + if !validation_result.is_valid() { + let bump_action = StateTransitionAction::BumpIdentityNonceAction( + BumpIdentityNonceAction::from_borrowed_data_contract_create_transition(self), + ); + + return Ok(ConsensusValidationResult::new_with_data_and_errors( + bump_action, + validation_result.errors, + )); + } + let validation_result = token_configuration.validate_token_config_groups_exist( self.data_contract().groups(), platform_version, diff --git a/packages/rs-drive-abci/tests/supporting_files/contract/basic-token/basic-token.json b/packages/rs-drive-abci/tests/supporting_files/contract/basic-token/basic-token.json index bbd55bbc8ce..f795a05f330 100644 --- a/packages/rs-drive-abci/tests/supporting_files/contract/basic-token/basic-token.json +++ b/packages/rs-drive-abci/tests/supporting_files/contract/basic-token/basic-token.json @@ -9,10 +9,18 @@ "$format_version": "0", "conventions": { "$format_version": "0", + "localizations": { + "en": { + "$format_version": "0", + "shouldCapitalize": false, + "pluralForm": "tests", + "singularForm": "test" + } + }, "decimals": 8 }, "baseSupply": 100000, "maxSupply": null } } -} \ No newline at end of file +} diff --git a/packages/rs-platform-version/src/version/dpp_versions/dpp_validation_versions/mod.rs b/packages/rs-platform-version/src/version/dpp_versions/dpp_validation_versions/mod.rs index b96453f6236..ebd9adc8180 100644 --- a/packages/rs-platform-version/src/version/dpp_versions/dpp_validation_versions/mod.rs +++ b/packages/rs-platform-version/src/version/dpp_versions/dpp_validation_versions/mod.rs @@ -21,6 +21,7 @@ pub struct DataContractValidationVersions { pub validate_not_defined_properties: FeatureVersion, pub validate_property_definition: FeatureVersion, pub validate_token_config_groups_exist: FeatureVersion, + pub validate_localizations: FeatureVersion, } #[derive(Clone, Debug, Default)] diff --git a/packages/rs-platform-version/src/version/dpp_versions/dpp_validation_versions/v1.rs b/packages/rs-platform-version/src/version/dpp_versions/dpp_validation_versions/v1.rs index 6b92b0d5493..61e85ffea12 100644 --- a/packages/rs-platform-version/src/version/dpp_versions/dpp_validation_versions/v1.rs +++ b/packages/rs-platform-version/src/version/dpp_versions/dpp_validation_versions/v1.rs @@ -19,6 +19,7 @@ pub const DPP_VALIDATION_VERSIONS_V1: DPPValidationVersions = DPPValidationVersi validate_not_defined_properties: 0, validate_property_definition: 0, validate_token_config_groups_exist: 0, + validate_localizations: 0, }, document_type: DocumentTypeValidationVersions { validate_update: 0, diff --git a/packages/rs-platform-version/src/version/dpp_versions/dpp_validation_versions/v2.rs b/packages/rs-platform-version/src/version/dpp_versions/dpp_validation_versions/v2.rs index 05f6c867066..ad370c2a999 100644 --- a/packages/rs-platform-version/src/version/dpp_versions/dpp_validation_versions/v2.rs +++ b/packages/rs-platform-version/src/version/dpp_versions/dpp_validation_versions/v2.rs @@ -19,6 +19,7 @@ pub const DPP_VALIDATION_VERSIONS_V2: DPPValidationVersions = DPPValidationVersi validate_not_defined_properties: 0, validate_property_definition: 0, validate_token_config_groups_exist: 0, + validate_localizations: 0, }, document_type: DocumentTypeValidationVersions { validate_update: 0, diff --git a/packages/token-history-contract/test/unit/tokenHistoryContract.spec.js b/packages/token-history-contract/test/unit/tokenHistoryContract.spec.js index 126bf84fa5a..ce3d9195664 100644 --- a/packages/token-history-contract/test/unit/tokenHistoryContract.spec.js +++ b/packages/token-history-contract/test/unit/tokenHistoryContract.spec.js @@ -288,13 +288,13 @@ describe('Token History Contract', () => { }); describe('tokenId', () => { - it('should be 32 bytes', async () => { - rawUnfreezeDocument.tokenId = crypto.randomBytes(33); + it('should be defined', async () => { + delete rawUnfreezeDocument.tokenId; const document = dpp.document.create(dataContract, identityId, 'unfreeze', rawUnfreezeDocument); const validationResult = document.validate(dpp.protocolVersion); const error = expectJsonSchemaError(validationResult); - expect(error.keyword).to.equal('maxItems'); - expect(error.instancePath).to.equal('/tokenId'); + expect(error.keyword).to.equal('required'); + expect(error.params.missingProperty).to.equal('tokenId'); }); }); @@ -389,7 +389,7 @@ describe('Token History Contract', () => { const document = dpp.document.create(dataContract, identityId, 'emergencyAction', rawEmergencyActionDocument); const validationResult = document.validate(dpp.protocolVersion); const error = expectJsonSchemaError(validationResult); - expect(error.keyword).to.equal('minimum'); + expect(error.keyword).to.equal('enum'); }); }); diff --git a/packages/wasm-dpp/src/errors/consensus/consensus_error.rs b/packages/wasm-dpp/src/errors/consensus/consensus_error.rs index de6c3a88c2f..302145b4e89 100644 --- a/packages/wasm-dpp/src/errors/consensus/consensus_error.rs +++ b/packages/wasm-dpp/src/errors/consensus/consensus_error.rs @@ -66,7 +66,7 @@ use dpp::consensus::basic::document::{ContestedDocumentsTemporarilyNotAllowedErr use dpp::consensus::basic::group::GroupActionNotAllowedOnTransitionError; use dpp::consensus::basic::identity::{DataContractBoundsNotPresentError, DisablingKeyIdAlsoBeingAddedInSameTransitionError, InvalidIdentityCreditWithdrawalTransitionAmountError, InvalidIdentityUpdateTransitionDisableKeysError, InvalidIdentityUpdateTransitionEmptyError, TooManyMasterPublicKeyError, WithdrawalOutputScriptNotAllowedWhenSigningWithOwnerKeyError}; use dpp::consensus::basic::overflow_error::OverflowError; -use dpp::consensus::basic::token::{ChoosingTokenMintRecipientNotAllowedError, ContractHasNoTokensError, DestinationIdentityForTokenMintingNotSetError, InvalidActionIdError, InvalidTokenAmountError, InvalidTokenConfigUpdateNoChangeError, InvalidTokenIdError, InvalidTokenNoteTooBigError, InvalidTokenPositionError, TokenTransferToOurselfError}; +use dpp::consensus::basic::token::{ChoosingTokenMintRecipientNotAllowedError, ContractHasNoTokensError, DestinationIdentityForTokenMintingNotSetError, InvalidActionIdError, InvalidTokenAmountError, InvalidTokenConfigUpdateNoChangeError, InvalidTokenIdError, InvalidTokenNoteTooBigError, InvalidTokenPositionError, MissingDefaultLocalizationError, TokenTransferToOurselfError}; use dpp::consensus::state::data_contract::data_contract_update_action_not_allowed_error::DataContractUpdateActionNotAllowedError; use dpp::consensus::state::data_contract::document_type_update_error::DocumentTypeUpdateError; use dpp::consensus::state::document::document_contest_currently_locked_error::DocumentContestCurrentlyLockedError; @@ -728,6 +728,9 @@ fn from_basic_error(basic_error: &BasicError) -> JsValue { BasicError::InvalidTokenNoteTooBigError(e) => { generic_consensus_error!(InvalidTokenNoteTooBigError, e).into() } + BasicError::MissingDefaultLocalizationError(e) => { + generic_consensus_error!(MissingDefaultLocalizationError, e).into() + } } }