diff --git a/packages/rs-dpp/schema/meta_schemas/document/v0/document-meta.json b/packages/rs-dpp/schema/meta_schemas/document/v0/document-meta.json index 78bbeccef3b..24d207e35a3 100644 --- a/packages/rs-dpp/schema/meta_schemas/document/v0/document-meta.json +++ b/packages/rs-dpp/schema/meta_schemas/document/v0/document-meta.json @@ -479,9 +479,16 @@ "enum": [ 0, 1, - 2 + 2, + 3 ], - "description": "Restrictions of document creation. 0 - No restrictions, 1 - Owner only, 2 - No creation (System Only)" + "description": "Restrictions of document creation. 0 - No restrictions, 1 - Owner only, 2 - No creation (System Only), 3 - Any group member" + }, + "creationRestrictionGroup": { + "type": "integer", + "minimum": 0, + "maximum": 65535, + "description": "Group position required when creationRestrictionMode is 3 (Any group member)" }, "requiresIdentityEncryptionBoundedKey": { "type": "integer", @@ -600,10 +607,36 @@ "const": false } }, + "allOf": [ + { + "if": { + "properties": { + "creationRestrictionMode": { + "const": 3 + } + }, + "required": [ + "creationRestrictionMode" + ] + }, + "then": { + "required": [ + "creationRestrictionGroup" + ] + }, + "else": { + "not": { + "required": [ + "creationRestrictionGroup" + ] + } + } + } + ], "required": [ "$schema", "type", "properties", "additionalProperties" ] -} \ No newline at end of file +} diff --git a/packages/rs-dpp/src/data_contract/document_type/accessors/mod.rs b/packages/rs-dpp/src/data_contract/document_type/accessors/mod.rs index 6d049e9b39e..1578b2c43ac 100644 --- a/packages/rs-dpp/src/data_contract/document_type/accessors/mod.rs +++ b/packages/rs-dpp/src/data_contract/document_type/accessors/mod.rs @@ -1,5 +1,6 @@ mod v0; mod v1; +mod v2; use crate::data_contract::document_type::index::Index; use crate::data_contract::document_type::index_level::IndexLevel; @@ -12,7 +13,7 @@ use crate::data_contract::document_type::restricted_creation::CreationRestrictio #[cfg(feature = "validation")] use crate::data_contract::document_type::validator::StatelessJsonSchemaLazyValidator; use crate::data_contract::storage_requirements::keys_for_document_type::StorageKeyRequirements; -use crate::data_contract::TokenContractPosition; +use crate::data_contract::{GroupContractPosition, TokenContractPosition}; use crate::document::transfer::Transferable; use crate::identity::SecurityLevel; use crate::nft::TradeMode; @@ -21,12 +22,14 @@ use indexmap::IndexMap; use std::collections::{BTreeMap, BTreeSet}; pub use v0::*; pub use v1::*; +pub use v2::*; impl DocumentTypeV0MutGetters for DocumentType { fn schema_mut(&mut self) -> &mut Value { match self { DocumentType::V0(v0) => v0.schema_mut(), DocumentType::V1(v1) => v1.schema_mut(), + DocumentType::V2(v2) => v2.schema_mut(), } } } @@ -36,6 +39,7 @@ impl DocumentTypeV0Getters for DocumentType { match self { DocumentType::V0(v0) => v0.name(), DocumentType::V1(v1) => v1.name(), + DocumentType::V2(v2) => v2.name(), } } @@ -43,6 +47,7 @@ impl DocumentTypeV0Getters for DocumentType { match self { DocumentType::V0(v0) => v0.schema(), DocumentType::V1(v1) => v1.schema(), + DocumentType::V2(v2) => v2.schema(), } } @@ -50,6 +55,7 @@ impl DocumentTypeV0Getters for DocumentType { match self { DocumentType::V0(v0) => v0.schema_owned(), DocumentType::V1(v1) => v1.schema_owned(), + DocumentType::V2(v2) => v2.schema_owned(), } } @@ -57,6 +63,7 @@ impl DocumentTypeV0Getters for DocumentType { match self { DocumentType::V0(v0) => v0.indexes(), DocumentType::V1(v1) => v1.indexes(), + DocumentType::V2(v2) => v2.indexes(), } } @@ -64,6 +71,7 @@ impl DocumentTypeV0Getters for DocumentType { match self { DocumentType::V0(v0) => v0.find_contested_index(), DocumentType::V1(v1) => v1.find_contested_index(), + DocumentType::V2(v2) => v2.find_contested_index(), } } @@ -71,6 +79,7 @@ impl DocumentTypeV0Getters for DocumentType { match self { DocumentType::V0(v0) => v0.index_structure(), DocumentType::V1(v1) => v1.index_structure(), + DocumentType::V2(v2) => v2.index_structure(), } } @@ -78,6 +87,7 @@ impl DocumentTypeV0Getters for DocumentType { match self { DocumentType::V0(v0) => v0.flattened_properties(), DocumentType::V1(v1) => v1.flattened_properties(), + DocumentType::V2(v2) => v2.flattened_properties(), } } @@ -85,6 +95,7 @@ impl DocumentTypeV0Getters for DocumentType { match self { DocumentType::V0(v0) => v0.properties(), DocumentType::V1(v1) => v1.properties(), + DocumentType::V2(v2) => v2.properties(), } } @@ -92,6 +103,7 @@ impl DocumentTypeV0Getters for DocumentType { match self { DocumentType::V0(v0) => v0.identifier_paths(), DocumentType::V1(v1) => v1.identifier_paths(), + DocumentType::V2(v2) => v2.identifier_paths(), } } @@ -99,6 +111,7 @@ impl DocumentTypeV0Getters for DocumentType { match self { DocumentType::V0(v0) => v0.binary_paths(), DocumentType::V1(v1) => v1.binary_paths(), + DocumentType::V2(v2) => v2.binary_paths(), } } @@ -106,6 +119,7 @@ impl DocumentTypeV0Getters for DocumentType { match self { DocumentType::V0(v0) => v0.required_fields(), DocumentType::V1(v1) => v1.required_fields(), + DocumentType::V2(v2) => v2.required_fields(), } } @@ -113,6 +127,7 @@ impl DocumentTypeV0Getters for DocumentType { match self { DocumentType::V0(v0) => v0.transient_fields(), DocumentType::V1(v1) => v1.transient_fields(), + DocumentType::V2(v2) => v2.transient_fields(), } } @@ -120,6 +135,7 @@ impl DocumentTypeV0Getters for DocumentType { match self { DocumentType::V0(v0) => v0.documents_keep_history(), DocumentType::V1(v1) => v1.documents_keep_history(), + DocumentType::V2(v2) => v2.documents_keep_history(), } } @@ -127,6 +143,7 @@ impl DocumentTypeV0Getters for DocumentType { match self { DocumentType::V0(v0) => v0.documents_mutable(), DocumentType::V1(v1) => v1.documents_mutable(), + DocumentType::V2(v2) => v2.documents_mutable(), } } @@ -134,6 +151,7 @@ impl DocumentTypeV0Getters for DocumentType { match self { DocumentType::V0(v0) => v0.documents_can_be_deleted(), DocumentType::V1(v1) => v1.documents_can_be_deleted(), + DocumentType::V2(v2) => v2.documents_can_be_deleted(), } } @@ -141,6 +159,7 @@ impl DocumentTypeV0Getters for DocumentType { match self { DocumentType::V0(v0) => v0.documents_transferable(), DocumentType::V1(v1) => v1.documents_transferable(), + DocumentType::V2(v2) => v2.documents_transferable(), } } @@ -148,6 +167,7 @@ impl DocumentTypeV0Getters for DocumentType { match self { DocumentType::V0(v0) => v0.trade_mode(), DocumentType::V1(v1) => v1.trade_mode(), + DocumentType::V2(v2) => v2.trade_mode(), } } @@ -155,6 +175,7 @@ impl DocumentTypeV0Getters for DocumentType { match self { DocumentType::V0(v0) => v0.creation_restriction_mode(), DocumentType::V1(v1) => v1.creation_restriction_mode(), + DocumentType::V2(v2) => v2.creation_restriction_mode(), } } @@ -162,6 +183,7 @@ impl DocumentTypeV0Getters for DocumentType { match self { DocumentType::V0(v0) => v0.data_contract_id(), DocumentType::V1(v1) => v1.data_contract_id(), + DocumentType::V2(v2) => v2.data_contract_id(), } } @@ -169,6 +191,7 @@ impl DocumentTypeV0Getters for DocumentType { match self { DocumentType::V0(v0) => v0.requires_identity_encryption_bounded_key(), DocumentType::V1(v1) => v1.requires_identity_encryption_bounded_key(), + DocumentType::V2(v2) => v2.requires_identity_encryption_bounded_key(), } } @@ -176,6 +199,7 @@ impl DocumentTypeV0Getters for DocumentType { match self { DocumentType::V0(v0) => v0.requires_identity_decryption_bounded_key(), DocumentType::V1(v1) => v1.requires_identity_decryption_bounded_key(), + DocumentType::V2(v2) => v2.requires_identity_decryption_bounded_key(), } } @@ -183,6 +207,7 @@ impl DocumentTypeV0Getters for DocumentType { match self { DocumentType::V0(v0) => v0.security_level_requirement(), DocumentType::V1(v1) => v1.security_level_requirement(), + DocumentType::V2(v2) => v2.security_level_requirement(), } } @@ -191,6 +216,7 @@ impl DocumentTypeV0Getters for DocumentType { match self { DocumentType::V0(v0) => v0.json_schema_validator_ref(), DocumentType::V1(v1) => v1.json_schema_validator_ref(), + DocumentType::V2(v2) => v2.json_schema_validator_ref(), } } } @@ -200,6 +226,7 @@ impl DocumentTypeV0Setters for DocumentType { match self { DocumentType::V0(v0) => v0.set_data_contract_id(data_contract_id), DocumentType::V1(v1) => v1.set_data_contract_id(data_contract_id), + DocumentType::V2(v2) => v2.set_data_contract_id(data_contract_id), } } } @@ -209,6 +236,7 @@ impl DocumentTypeV1Setters for DocumentType { match self { DocumentType::V0(_) => { /* no-op */ } DocumentType::V1(v1) => v1.set_document_creation_token_cost(cost), + DocumentType::V2(v2) => v2.set_document_creation_token_cost(cost), } } @@ -216,6 +244,7 @@ impl DocumentTypeV1Setters for DocumentType { match self { DocumentType::V0(_) => { /* no-op */ } DocumentType::V1(v1) => v1.set_document_replacement_token_cost(cost), + DocumentType::V2(v2) => v2.set_document_replacement_token_cost(cost), } } @@ -223,6 +252,7 @@ impl DocumentTypeV1Setters for DocumentType { match self { DocumentType::V0(_) => { /* no-op */ } DocumentType::V1(v1) => v1.set_document_deletion_token_cost(cost), + DocumentType::V2(v2) => v2.set_document_deletion_token_cost(cost), } } @@ -230,6 +260,7 @@ impl DocumentTypeV1Setters for DocumentType { match self { DocumentType::V0(_) => { /* no-op */ } DocumentType::V1(v1) => v1.set_document_transfer_token_cost(cost), + DocumentType::V2(v2) => v2.set_document_transfer_token_cost(cost), } } @@ -237,6 +268,7 @@ impl DocumentTypeV1Setters for DocumentType { match self { DocumentType::V0(_) => { /* no-op */ } DocumentType::V1(v1) => v1.set_document_price_update_token_cost(cost), + DocumentType::V2(v2) => v2.set_document_price_update_token_cost(cost), } } @@ -244,6 +276,7 @@ impl DocumentTypeV1Setters for DocumentType { match self { DocumentType::V0(_) => { /* no-op */ } DocumentType::V1(v1) => v1.set_document_purchase_token_cost(cost), + DocumentType::V2(v2) => v2.set_document_purchase_token_cost(cost), } } } @@ -253,6 +286,7 @@ impl DocumentTypeV0Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(v0) => v0.name(), DocumentTypeRef::V1(v1) => v1.name(), + DocumentTypeRef::V2(v2) => v2.name(), } } @@ -260,6 +294,7 @@ impl DocumentTypeV0Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(v0) => v0.schema(), DocumentTypeRef::V1(v1) => v1.schema(), + DocumentTypeRef::V2(v2) => v2.schema(), } } @@ -267,6 +302,7 @@ impl DocumentTypeV0Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(v0) => v0.clone().schema_owned(), DocumentTypeRef::V1(v1) => v1.clone().schema_owned(), + DocumentTypeRef::V2(v2) => v2.clone().schema_owned(), } } @@ -274,6 +310,7 @@ impl DocumentTypeV0Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(v0) => v0.indexes(), DocumentTypeRef::V1(v1) => v1.indexes(), + DocumentTypeRef::V2(v2) => v2.indexes(), } } @@ -281,6 +318,7 @@ impl DocumentTypeV0Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(v0) => v0.find_contested_index(), DocumentTypeRef::V1(v1) => v1.find_contested_index(), + DocumentTypeRef::V2(v2) => v2.find_contested_index(), } } @@ -288,6 +326,7 @@ impl DocumentTypeV0Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(v0) => v0.index_structure(), DocumentTypeRef::V1(v1) => v1.index_structure(), + DocumentTypeRef::V2(v2) => v2.index_structure(), } } @@ -295,6 +334,7 @@ impl DocumentTypeV0Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(v0) => v0.flattened_properties(), DocumentTypeRef::V1(v1) => v1.flattened_properties(), + DocumentTypeRef::V2(v2) => v2.flattened_properties(), } } @@ -302,6 +342,7 @@ impl DocumentTypeV0Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(v0) => v0.properties(), DocumentTypeRef::V1(v1) => v1.properties(), + DocumentTypeRef::V2(v2) => v2.properties(), } } @@ -309,6 +350,7 @@ impl DocumentTypeV0Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(v0) => v0.identifier_paths(), DocumentTypeRef::V1(v1) => v1.identifier_paths(), + DocumentTypeRef::V2(v2) => v2.identifier_paths(), } } @@ -316,6 +358,7 @@ impl DocumentTypeV0Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(v0) => v0.binary_paths(), DocumentTypeRef::V1(v1) => v1.binary_paths(), + DocumentTypeRef::V2(v2) => v2.binary_paths(), } } @@ -323,6 +366,7 @@ impl DocumentTypeV0Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(v0) => v0.required_fields(), DocumentTypeRef::V1(v1) => v1.required_fields(), + DocumentTypeRef::V2(v2) => v2.required_fields(), } } @@ -330,6 +374,7 @@ impl DocumentTypeV0Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(v0) => v0.transient_fields(), DocumentTypeRef::V1(v1) => v1.transient_fields(), + DocumentTypeRef::V2(v2) => v2.transient_fields(), } } @@ -337,6 +382,7 @@ impl DocumentTypeV0Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(v0) => v0.documents_keep_history(), DocumentTypeRef::V1(v1) => v1.documents_keep_history(), + DocumentTypeRef::V2(v2) => v2.documents_keep_history(), } } @@ -344,6 +390,7 @@ impl DocumentTypeV0Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(v0) => v0.documents_mutable(), DocumentTypeRef::V1(v1) => v1.documents_mutable(), + DocumentTypeRef::V2(v2) => v2.documents_mutable(), } } @@ -351,6 +398,7 @@ impl DocumentTypeV0Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(v0) => v0.documents_can_be_deleted(), DocumentTypeRef::V1(v1) => v1.documents_can_be_deleted(), + DocumentTypeRef::V2(v2) => v2.documents_can_be_deleted(), } } @@ -358,6 +406,7 @@ impl DocumentTypeV0Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(v0) => v0.documents_transferable(), DocumentTypeRef::V1(v1) => v1.documents_transferable(), + DocumentTypeRef::V2(v2) => v2.documents_transferable(), } } @@ -365,6 +414,7 @@ impl DocumentTypeV0Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(v0) => v0.trade_mode(), DocumentTypeRef::V1(v1) => v1.trade_mode(), + DocumentTypeRef::V2(v2) => v2.trade_mode(), } } @@ -372,6 +422,7 @@ impl DocumentTypeV0Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(v0) => v0.creation_restriction_mode(), DocumentTypeRef::V1(v1) => v1.creation_restriction_mode(), + DocumentTypeRef::V2(v2) => v2.creation_restriction_mode(), } } @@ -379,6 +430,7 @@ impl DocumentTypeV0Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(v0) => v0.data_contract_id(), DocumentTypeRef::V1(v1) => v1.data_contract_id(), + DocumentTypeRef::V2(v2) => v2.data_contract_id(), } } @@ -386,6 +438,7 @@ impl DocumentTypeV0Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(v0) => v0.requires_identity_encryption_bounded_key(), DocumentTypeRef::V1(v1) => v1.requires_identity_encryption_bounded_key(), + DocumentTypeRef::V2(v2) => v2.requires_identity_encryption_bounded_key(), } } @@ -393,6 +446,7 @@ impl DocumentTypeV0Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(v0) => v0.requires_identity_decryption_bounded_key(), DocumentTypeRef::V1(v1) => v1.requires_identity_decryption_bounded_key(), + DocumentTypeRef::V2(v2) => v2.requires_identity_decryption_bounded_key(), } } @@ -400,6 +454,7 @@ impl DocumentTypeV0Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(v0) => v0.security_level_requirement(), DocumentTypeRef::V1(v1) => v1.security_level_requirement(), + DocumentTypeRef::V2(v2) => v2.security_level_requirement(), } } @@ -408,6 +463,7 @@ impl DocumentTypeV0Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(v0) => v0.json_schema_validator_ref(), DocumentTypeRef::V1(v1) => v1.json_schema_validator_ref(), + DocumentTypeRef::V2(v2) => v2.json_schema_validator_ref(), } } } @@ -416,6 +472,7 @@ impl DocumentTypeV0Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(v0) => v0.name(), DocumentTypeMutRef::V1(v1) => v1.name(), + DocumentTypeMutRef::V2(v2) => v2.name(), } } @@ -423,6 +480,7 @@ impl DocumentTypeV0Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(v0) => v0.schema(), DocumentTypeMutRef::V1(v1) => v1.schema(), + DocumentTypeMutRef::V2(v2) => v2.schema(), } } @@ -430,6 +488,7 @@ impl DocumentTypeV0Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(v0) => v0.clone().schema_owned(), DocumentTypeMutRef::V1(v1) => v1.clone().schema_owned(), + DocumentTypeMutRef::V2(v2) => v2.clone().schema_owned(), } } @@ -437,6 +496,7 @@ impl DocumentTypeV0Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(v0) => v0.indexes(), DocumentTypeMutRef::V1(v1) => v1.indexes(), + DocumentTypeMutRef::V2(v2) => v2.indexes(), } } @@ -444,6 +504,7 @@ impl DocumentTypeV0Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(v0) => v0.find_contested_index(), DocumentTypeMutRef::V1(v1) => v1.find_contested_index(), + DocumentTypeMutRef::V2(v2) => v2.find_contested_index(), } } @@ -451,6 +512,7 @@ impl DocumentTypeV0Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(v0) => v0.index_structure(), DocumentTypeMutRef::V1(v1) => v1.index_structure(), + DocumentTypeMutRef::V2(v2) => v2.index_structure(), } } @@ -458,6 +520,7 @@ impl DocumentTypeV0Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(v0) => v0.flattened_properties(), DocumentTypeMutRef::V1(v1) => v1.flattened_properties(), + DocumentTypeMutRef::V2(v2) => v2.flattened_properties(), } } @@ -465,6 +528,7 @@ impl DocumentTypeV0Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(v0) => v0.properties(), DocumentTypeMutRef::V1(v1) => v1.properties(), + DocumentTypeMutRef::V2(v2) => v2.properties(), } } @@ -472,6 +536,7 @@ impl DocumentTypeV0Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(v0) => v0.identifier_paths(), DocumentTypeMutRef::V1(v1) => v1.identifier_paths(), + DocumentTypeMutRef::V2(v2) => v2.identifier_paths(), } } @@ -479,6 +544,7 @@ impl DocumentTypeV0Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(v0) => v0.binary_paths(), DocumentTypeMutRef::V1(v1) => v1.binary_paths(), + DocumentTypeMutRef::V2(v2) => v2.binary_paths(), } } @@ -486,6 +552,7 @@ impl DocumentTypeV0Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(v0) => v0.required_fields(), DocumentTypeMutRef::V1(v1) => v1.required_fields(), + DocumentTypeMutRef::V2(v2) => v2.required_fields(), } } @@ -493,6 +560,7 @@ impl DocumentTypeV0Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(v0) => v0.transient_fields(), DocumentTypeMutRef::V1(v1) => v1.transient_fields(), + DocumentTypeMutRef::V2(v2) => v2.transient_fields(), } } @@ -500,6 +568,7 @@ impl DocumentTypeV0Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(v0) => v0.documents_keep_history(), DocumentTypeMutRef::V1(v1) => v1.documents_keep_history(), + DocumentTypeMutRef::V2(v2) => v2.documents_keep_history(), } } @@ -507,6 +576,7 @@ impl DocumentTypeV0Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(v0) => v0.documents_mutable(), DocumentTypeMutRef::V1(v1) => v1.documents_mutable(), + DocumentTypeMutRef::V2(v2) => v2.documents_mutable(), } } @@ -514,6 +584,7 @@ impl DocumentTypeV0Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(v0) => v0.documents_can_be_deleted(), DocumentTypeMutRef::V1(v1) => v1.documents_can_be_deleted(), + DocumentTypeMutRef::V2(v2) => v2.documents_can_be_deleted(), } } @@ -521,6 +592,7 @@ impl DocumentTypeV0Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(v0) => v0.documents_transferable(), DocumentTypeMutRef::V1(v1) => v1.documents_transferable(), + DocumentTypeMutRef::V2(v2) => v2.documents_transferable(), } } @@ -528,6 +600,7 @@ impl DocumentTypeV0Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(v0) => v0.trade_mode(), DocumentTypeMutRef::V1(v1) => v1.trade_mode(), + DocumentTypeMutRef::V2(v2) => v2.trade_mode(), } } @@ -535,6 +608,7 @@ impl DocumentTypeV0Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(v0) => v0.creation_restriction_mode(), DocumentTypeMutRef::V1(v1) => v1.creation_restriction_mode(), + DocumentTypeMutRef::V2(v2) => v2.creation_restriction_mode(), } } @@ -542,6 +616,7 @@ impl DocumentTypeV0Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(v0) => v0.data_contract_id(), DocumentTypeMutRef::V1(v1) => v1.data_contract_id(), + DocumentTypeMutRef::V2(v2) => v2.data_contract_id(), } } @@ -549,6 +624,7 @@ impl DocumentTypeV0Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(v0) => v0.requires_identity_encryption_bounded_key(), DocumentTypeMutRef::V1(v1) => v1.requires_identity_encryption_bounded_key(), + DocumentTypeMutRef::V2(v2) => v2.requires_identity_encryption_bounded_key(), } } @@ -556,6 +632,7 @@ impl DocumentTypeV0Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(v0) => v0.requires_identity_decryption_bounded_key(), DocumentTypeMutRef::V1(v1) => v1.requires_identity_decryption_bounded_key(), + DocumentTypeMutRef::V2(v2) => v2.requires_identity_decryption_bounded_key(), } } @@ -563,6 +640,7 @@ impl DocumentTypeV0Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(v0) => v0.security_level_requirement(), DocumentTypeMutRef::V1(v1) => v1.security_level_requirement(), + DocumentTypeMutRef::V2(v2) => v2.security_level_requirement(), } } @@ -571,6 +649,7 @@ impl DocumentTypeV0Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(v0) => v0.json_schema_validator_ref(), DocumentTypeMutRef::V1(v1) => v1.json_schema_validator_ref(), + DocumentTypeMutRef::V2(v2) => v2.json_schema_validator_ref(), } } } @@ -580,6 +659,7 @@ impl DocumentTypeV0Setters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(v0) => v0.set_data_contract_id(data_contract_id), DocumentTypeMutRef::V1(v1) => v1.set_data_contract_id(data_contract_id), + DocumentTypeMutRef::V2(v2) => v2.set_data_contract_id(data_contract_id), } } } @@ -589,6 +669,7 @@ impl DocumentTypeV1Getters for DocumentType { match self { DocumentType::V0(_) => None, DocumentType::V1(v1) => v1.document_creation_token_cost(), + DocumentType::V2(v2) => v2.document_creation_token_cost(), } } @@ -596,6 +677,7 @@ impl DocumentTypeV1Getters for DocumentType { match self { DocumentType::V0(_) => None, DocumentType::V1(v1) => v1.document_replacement_token_cost(), + DocumentType::V2(v2) => v2.document_replacement_token_cost(), } } @@ -603,6 +685,7 @@ impl DocumentTypeV1Getters for DocumentType { match self { DocumentType::V0(_) => None, DocumentType::V1(v1) => v1.document_deletion_token_cost(), + DocumentType::V2(v2) => v2.document_deletion_token_cost(), } } @@ -610,6 +693,7 @@ impl DocumentTypeV1Getters for DocumentType { match self { DocumentType::V0(_) => None, DocumentType::V1(v1) => v1.document_transfer_token_cost(), + DocumentType::V2(v2) => v2.document_transfer_token_cost(), } } @@ -617,6 +701,7 @@ impl DocumentTypeV1Getters for DocumentType { match self { DocumentType::V0(_) => None, DocumentType::V1(v1) => v1.document_update_price_token_cost(), + DocumentType::V2(v2) => v2.document_update_price_token_cost(), } } @@ -624,6 +709,7 @@ impl DocumentTypeV1Getters for DocumentType { match self { DocumentType::V0(_) => None, DocumentType::V1(v1) => v1.document_purchase_token_cost(), + DocumentType::V2(v2) => v2.document_purchase_token_cost(), } } @@ -631,6 +717,7 @@ impl DocumentTypeV1Getters for DocumentType { match self { DocumentType::V0(_) => vec![], DocumentType::V1(v1) => v1.all_document_token_costs(), + DocumentType::V2(v2) => v2.all_document_token_costs(), } } @@ -640,6 +727,7 @@ impl DocumentTypeV1Getters for DocumentType { match self { DocumentType::V0(_) => BTreeMap::new(), DocumentType::V1(v1) => v1.all_external_token_costs_contract_tokens(), + DocumentType::V2(v2) => v2.all_external_token_costs_contract_tokens(), } } } @@ -649,6 +737,7 @@ impl DocumentTypeV1Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(_) => None, DocumentTypeRef::V1(v1) => v1.document_creation_token_cost(), + DocumentTypeRef::V2(v2) => v2.document_creation_token_cost(), } } @@ -656,6 +745,7 @@ impl DocumentTypeV1Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(_) => None, DocumentTypeRef::V1(v1) => v1.document_replacement_token_cost(), + DocumentTypeRef::V2(v2) => v2.document_replacement_token_cost(), } } @@ -663,6 +753,7 @@ impl DocumentTypeV1Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(_) => None, DocumentTypeRef::V1(v1) => v1.document_deletion_token_cost(), + DocumentTypeRef::V2(v2) => v2.document_deletion_token_cost(), } } @@ -670,6 +761,7 @@ impl DocumentTypeV1Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(_) => None, DocumentTypeRef::V1(v1) => v1.document_transfer_token_cost(), + DocumentTypeRef::V2(v2) => v2.document_transfer_token_cost(), } } @@ -677,6 +769,7 @@ impl DocumentTypeV1Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(_) => None, DocumentTypeRef::V1(v1) => v1.document_update_price_token_cost(), + DocumentTypeRef::V2(v2) => v2.document_update_price_token_cost(), } } @@ -684,6 +777,7 @@ impl DocumentTypeV1Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(_) => None, DocumentTypeRef::V1(v1) => v1.document_purchase_token_cost(), + DocumentTypeRef::V2(v2) => v2.document_purchase_token_cost(), } } @@ -691,6 +785,7 @@ impl DocumentTypeV1Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(_) => vec![], DocumentTypeRef::V1(v1) => v1.all_document_token_costs(), + DocumentTypeRef::V2(v2) => v2.all_document_token_costs(), } } @@ -700,6 +795,7 @@ impl DocumentTypeV1Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(_) => BTreeMap::new(), DocumentTypeRef::V1(v1) => v1.all_external_token_costs_contract_tokens(), + DocumentTypeRef::V2(v2) => v2.all_external_token_costs_contract_tokens(), } } } @@ -709,6 +805,7 @@ impl DocumentTypeV1Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(_) => None, DocumentTypeMutRef::V1(v1) => v1.document_creation_token_cost(), + DocumentTypeMutRef::V2(v2) => v2.document_creation_token_cost(), } } @@ -716,6 +813,7 @@ impl DocumentTypeV1Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(_) => None, DocumentTypeMutRef::V1(v1) => v1.document_replacement_token_cost(), + DocumentTypeMutRef::V2(v2) => v2.document_replacement_token_cost(), } } @@ -723,6 +821,7 @@ impl DocumentTypeV1Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(_) => None, DocumentTypeMutRef::V1(v1) => v1.document_deletion_token_cost(), + DocumentTypeMutRef::V2(v2) => v2.document_deletion_token_cost(), } } @@ -730,6 +829,7 @@ impl DocumentTypeV1Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(_) => None, DocumentTypeMutRef::V1(v1) => v1.document_transfer_token_cost(), + DocumentTypeMutRef::V2(v2) => v2.document_transfer_token_cost(), } } @@ -737,6 +837,7 @@ impl DocumentTypeV1Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(_) => None, DocumentTypeMutRef::V1(v1) => v1.document_update_price_token_cost(), + DocumentTypeMutRef::V2(v2) => v2.document_update_price_token_cost(), } } @@ -744,6 +845,7 @@ impl DocumentTypeV1Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(_) => None, DocumentTypeMutRef::V1(v1) => v1.document_purchase_token_cost(), + DocumentTypeMutRef::V2(v2) => v2.document_purchase_token_cost(), } } @@ -751,6 +853,7 @@ impl DocumentTypeV1Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(_) => vec![], DocumentTypeMutRef::V1(v1) => v1.all_document_token_costs(), + DocumentTypeMutRef::V2(v2) => v2.all_document_token_costs(), } } @@ -760,6 +863,57 @@ impl DocumentTypeV1Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(_) => BTreeMap::new(), DocumentTypeMutRef::V1(v1) => v1.all_external_token_costs_contract_tokens(), + DocumentTypeMutRef::V2(v2) => v2.all_external_token_costs_contract_tokens(), + } + } +} + +impl DocumentTypeV2Getters for DocumentType { + fn creation_restriction_group(&self) -> Option { + match self { + DocumentType::V0(_) => None, + DocumentType::V1(_) => None, + DocumentType::V2(v2) => v2.creation_restriction_group(), + } + } +} + +impl DocumentTypeV2Getters for DocumentTypeRef<'_> { + fn creation_restriction_group(&self) -> Option { + match self { + DocumentTypeRef::V0(_) => None, + DocumentTypeRef::V1(_) => None, + DocumentTypeRef::V2(v2) => v2.creation_restriction_group(), + } + } +} + +impl DocumentTypeV2Getters for DocumentTypeMutRef<'_> { + fn creation_restriction_group(&self) -> Option { + match self { + DocumentTypeMutRef::V0(_) => None, + DocumentTypeMutRef::V1(_) => None, + DocumentTypeMutRef::V2(v2) => v2.creation_restriction_group(), + } + } +} + +impl DocumentTypeV2Setters for DocumentType { + fn set_creation_restriction_group(&mut self, group: Option) { + match self { + DocumentType::V0(_) => { /* no-op */ } + DocumentType::V1(_) => { /* no-op */ } + DocumentType::V2(v2) => v2.set_creation_restriction_group(group), + } + } +} + +impl DocumentTypeV2Setters for DocumentTypeMutRef<'_> { + fn set_creation_restriction_group(&mut self, group: Option) { + match self { + DocumentTypeMutRef::V0(_) => { /* no-op */ } + DocumentTypeMutRef::V1(_) => { /* no-op */ } + DocumentTypeMutRef::V2(v2) => v2.set_creation_restriction_group(group), } } } diff --git a/packages/rs-dpp/src/data_contract/document_type/accessors/v2/mod.rs b/packages/rs-dpp/src/data_contract/document_type/accessors/v2/mod.rs new file mode 100644 index 00000000000..1b7c6aea273 --- /dev/null +++ b/packages/rs-dpp/src/data_contract/document_type/accessors/v2/mod.rs @@ -0,0 +1,13 @@ +use crate::data_contract::GroupContractPosition; + +/// Trait providing getters for DocumentType V2-specific fields. +pub trait DocumentTypeV2Getters { + /// Returns the creation restriction group position, if applicable. + fn creation_restriction_group(&self) -> Option; +} + +/// Trait providing setters for DocumentType V2-specific fields. +pub trait DocumentTypeV2Setters { + /// Sets the creation restriction group position. + fn set_creation_restriction_group(&mut self, group: Option); +} diff --git a/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/mod.rs b/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/mod.rs index 37de63fe927..0dd3279afeb 100644 --- a/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/mod.rs +++ b/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/mod.rs @@ -1,6 +1,7 @@ use crate::data_contract::config::DataContractConfig; use crate::data_contract::document_type::v0::DocumentTypeV0; use crate::data_contract::document_type::v1::DocumentTypeV1; +use crate::data_contract::document_type::v2::DocumentTypeV2; use crate::data_contract::document_type::{ property_names, DocumentProperty, DocumentPropertyType, DocumentType, }; @@ -17,6 +18,7 @@ use std::collections::{BTreeMap, BTreeSet}; mod v0; mod v1; +mod v2; const NOT_ALLOWED_SYSTEM_PROPERTIES: [&str; 1] = ["$id"]; @@ -73,9 +75,23 @@ impl DocumentType { platform_version, ) .map(|document_type| document_type.into()), + 2 => DocumentTypeV2::try_from_schema( + data_contract_id, + data_contract_system_version, + contract_config_version, + name, + schema, + schema_defs, + token_configurations, + data_contact_config, + full_validation, + validation_operations, + platform_version, + ) + .map(|document_type| document_type.into()), version => Err(ProtocolError::UnknownVersionMismatch { method: "try_from_schema".to_string(), - known_versions: vec![0, 1], + known_versions: vec![0, 1, 2], received: version, }), } diff --git a/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v2/mod.rs b/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v2/mod.rs new file mode 100644 index 00000000000..176d1a0d671 --- /dev/null +++ b/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v2/mod.rs @@ -0,0 +1,226 @@ +use crate::data_contract::config::DataContractConfig; +use crate::data_contract::document_type::accessors::DocumentTypeV2Setters; +use crate::data_contract::document_type::class_methods::{ + consensus_or_protocol_data_contract_error, consensus_or_protocol_value_error, +}; +use crate::data_contract::document_type::property_names::CREATION_RESTRICTION_GROUP; +use crate::data_contract::document_type::restricted_creation::CreationRestrictionMode; +use crate::data_contract::document_type::v1::DocumentTypeV1; +use crate::data_contract::document_type::v2::DocumentTypeV2; +use crate::data_contract::errors::DataContractError; +use crate::data_contract::{GroupContractPosition, TokenConfiguration, TokenContractPosition}; +use crate::validation::operations::ProtocolValidationOperation; +use crate::version::PlatformVersion; +use crate::ProtocolError; +use platform_value::{Identifier, Value}; +use std::collections::BTreeMap; + +impl DocumentTypeV2 { + #[allow(clippy::too_many_arguments)] + pub fn try_from_schema( + data_contract_id: Identifier, + data_contract_system_version: u16, + contract_config_version: u16, + name: &str, + schema: Value, + schema_defs: Option<&BTreeMap>, + token_configurations: &BTreeMap, + data_contact_config: &DataContractConfig, + full_validation: bool, + validation_operations: &mut impl Extend, + platform_version: &PlatformVersion, + ) -> Result { + let mut document_type: DocumentTypeV2 = DocumentTypeV1::try_from_schema( + data_contract_id, + data_contract_system_version, + contract_config_version, + name, + schema, + schema_defs, + token_configurations, + data_contact_config, + full_validation, + validation_operations, + platform_version, + ) + .map(Into::into)?; + + let schema_map = document_type.schema.to_map().map_err(|err| { + consensus_or_protocol_data_contract_error(DataContractError::InvalidContractStructure( + format!("document schema must be an object: {err}"), + )) + })?; + + let creation_restriction_group: Option = + Value::inner_optional_integer_value(schema_map, CREATION_RESTRICTION_GROUP) + .map_err(consensus_or_protocol_value_error)?; + + match document_type.creation_restriction_mode { + CreationRestrictionMode::AnyGroupMember => { + if creation_restriction_group.is_none() { + return Err(consensus_or_protocol_data_contract_error( + DataContractError::InvalidContractStructure( + "creationRestrictionGroup is required when creationRestrictionMode is 3" + .to_string(), + ), + )); + } + } + _ => { + if creation_restriction_group.is_some() { + return Err(consensus_or_protocol_data_contract_error( + DataContractError::InvalidContractStructure( + "creationRestrictionGroup is only allowed when creationRestrictionMode is 3" + .to_string(), + ), + )); + } + } + } + + document_type.set_creation_restriction_group(creation_restriction_group); + + Ok(document_type) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::consensus::basic::BasicError; + use crate::consensus::ConsensusError; + use crate::data_contract::config::DataContractConfig; + use assert_matches::assert_matches; + use platform_value::platform_value; + use std::collections::BTreeMap; + + fn default_config(platform_version: &PlatformVersion) -> DataContractConfig { + DataContractConfig::default_for_version(platform_version) + .expect("should create a default config") + } + + #[test] + fn should_require_group_for_any_group_member_mode() { + let platform_version = PlatformVersion::latest(); + let config = default_config(platform_version); + let schema = platform_value!({ + "type": "object", + "properties": { + "name": { + "type": "string", + "position": 0, + } + }, + "creationRestrictionMode": 3, + "additionalProperties": false, + }); + + let result = DocumentTypeV2::try_from_schema( + Identifier::random(), + 1, + config.version(), + "testDoc", + schema, + None, + &BTreeMap::new(), + &config, + false, + &mut Vec::new(), + platform_version, + ); + + const EXPECTED_ERROR_MSG: &str = + "creationRestrictionGroup is required when creationRestrictionMode is 3"; + assert_matches!( + result, + Err(ProtocolError::ConsensusError(boxed)) => { + assert_matches!( + boxed.as_ref(), + ConsensusError::BasicError( + BasicError::ContractError(DataContractError::InvalidContractStructure(msg)) + ) if msg.eq(EXPECTED_ERROR_MSG) + ) + } + ); + } + + #[test] + fn should_forbid_group_when_not_any_group_member_mode() { + let platform_version = PlatformVersion::latest(); + let config = default_config(platform_version); + let schema = platform_value!({ + "type": "object", + "properties": { + "name": { + "type": "string", + "position": 0, + } + }, + "creationRestrictionMode": 1, + "creationRestrictionGroup": 0, + "additionalProperties": false, + }); + + let result = DocumentTypeV2::try_from_schema( + Identifier::random(), + 1, + config.version(), + "testDoc", + schema, + None, + &BTreeMap::new(), + &config, + false, + &mut Vec::new(), + platform_version, + ); + + const EXPECTED_ERROR_MSG: &str = + "creationRestrictionGroup is only allowed when creationRestrictionMode is 3"; + assert_matches!( + result, + Err(ProtocolError::ConsensusError(boxed)) => { + assert_matches!( + boxed.as_ref(), + ConsensusError::BasicError( + BasicError::ContractError(DataContractError::InvalidContractStructure(msg)) + ) if msg.eq(EXPECTED_ERROR_MSG) + ) + } + ); + } + + #[test] + fn should_accept_group_when_any_group_member_mode() { + let platform_version = PlatformVersion::latest(); + let config = default_config(platform_version); + let schema = platform_value!({ + "type": "object", + "properties": { + "name": { + "type": "string", + "position": 0, + } + }, + "creationRestrictionMode": 3, + "creationRestrictionGroup": 0, + "additionalProperties": false, + }); + + let result = DocumentTypeV2::try_from_schema( + Identifier::random(), + 1, + config.version(), + "testDoc", + schema, + None, + &BTreeMap::new(), + &config, + false, + &mut Vec::new(), + platform_version, + ); + + assert!(result.is_ok()); + } +} diff --git a/packages/rs-dpp/src/data_contract/document_type/methods/versioned_methods.rs b/packages/rs-dpp/src/data_contract/document_type/methods/versioned_methods.rs index 686aaba0c4b..588c826c383 100644 --- a/packages/rs-dpp/src/data_contract/document_type/methods/versioned_methods.rs +++ b/packages/rs-dpp/src/data_contract/document_type/methods/versioned_methods.rs @@ -2,6 +2,7 @@ use crate::data_contract::document_type::accessors::DocumentTypeV0Getters; use crate::data_contract::document_type::methods::DocumentTypeBasicMethods; use crate::data_contract::document_type::v0::DocumentTypeV0; use crate::data_contract::document_type::v1::DocumentTypeV1; +use crate::data_contract::document_type::v2::DocumentTypeV2; use crate::data_contract::document_type::{ DocumentPropertyType, DocumentType, DocumentTypeRef, Index, DEFAULT_HASH_SIZE, MAX_INDEX_SIZE, }; @@ -594,6 +595,7 @@ pub trait DocumentTypeV0MethodsVersioned: DocumentTypeV0Getters + DocumentTypeBa impl DocumentTypeV0MethodsVersioned for DocumentTypeV0 {} impl DocumentTypeV0MethodsVersioned for DocumentTypeV1 {} +impl DocumentTypeV0MethodsVersioned for DocumentTypeV2 {} impl DocumentTypeV0MethodsVersioned for DocumentType {} impl DocumentTypeV0MethodsVersioned for DocumentTypeRef<'_> {} diff --git a/packages/rs-dpp/src/data_contract/document_type/mod.rs b/packages/rs-dpp/src/data_contract/document_type/mod.rs index aadb6eedbfc..b3a04dd9e13 100644 --- a/packages/rs-dpp/src/data_contract/document_type/mod.rs +++ b/packages/rs-dpp/src/data_contract/document_type/mod.rs @@ -18,6 +18,7 @@ pub mod schema; mod token_costs; pub mod v0; pub mod v1; +pub mod v2; #[cfg(feature = "validation")] pub(crate) mod validator; @@ -26,6 +27,7 @@ use crate::data_contract::document_type::methods::{ }; use crate::data_contract::document_type::v0::DocumentTypeV0; use crate::data_contract::document_type::v1::DocumentTypeV1; +use crate::data_contract::document_type::v2::DocumentTypeV2; use crate::document::Document; use crate::fee::Credits; use crate::version::PlatformVersion; @@ -47,6 +49,7 @@ pub(crate) mod property_names { pub const TRADE_MODE: &str = "tradeMode"; pub const CREATION_RESTRICTION_MODE: &str = "creationRestrictionMode"; + pub const CREATION_RESTRICTION_GROUP: &str = "creationRestrictionGroup"; pub const SECURITY_LEVEL_REQUIREMENT: &str = "signatureSecurityLevelRequirement"; pub const REQUIRES_IDENTITY_ENCRYPTION_BOUNDED_KEY: &str = "requiresIdentityEncryptionBoundedKey"; @@ -80,12 +83,14 @@ pub(crate) mod property_names { pub enum DocumentTypeRef<'a> { V0(&'a DocumentTypeV0), V1(&'a DocumentTypeV1), + V2(&'a DocumentTypeV2), } #[derive(Debug)] pub enum DocumentTypeMutRef<'a> { V0(&'a mut DocumentTypeV0), V1(&'a mut DocumentTypeV1), + V2(&'a mut DocumentTypeV2), } #[allow(clippy::large_enum_variant)] @@ -93,6 +98,7 @@ pub enum DocumentTypeMutRef<'a> { pub enum DocumentType { V0(DocumentTypeV0), V1(DocumentTypeV1), + V2(DocumentTypeV2), } impl DocumentType { @@ -100,6 +106,7 @@ impl DocumentType { match self { DocumentType::V0(v0) => DocumentTypeRef::V0(v0), DocumentType::V1(v1) => DocumentTypeRef::V1(v1), + DocumentType::V2(v2) => DocumentTypeRef::V2(v2), } } @@ -107,6 +114,7 @@ impl DocumentType { match self { DocumentType::V0(v0) => DocumentTypeMutRef::V0(v0), DocumentType::V1(v1) => DocumentTypeMutRef::V1(v1), + DocumentType::V2(v2) => DocumentTypeMutRef::V2(v2), } } @@ -122,6 +130,9 @@ impl DocumentType { DocumentType::V1(v1) => { v1.prefunded_voting_balance_for_document(document, platform_version) } + DocumentType::V2(v2) => { + v2.prefunded_voting_balance_for_document(document, platform_version) + } } } } @@ -131,6 +142,7 @@ impl DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(v0) => DocumentType::V0((*v0).to_owned()), DocumentTypeRef::V1(v1) => DocumentType::V1((*v1).to_owned()), + DocumentTypeRef::V2(v2) => DocumentType::V2((*v2).to_owned()), } } } diff --git a/packages/rs-dpp/src/data_contract/document_type/restricted_creation/mod.rs b/packages/rs-dpp/src/data_contract/document_type/restricted_creation/mod.rs index e875a6fe2c4..11d01a12b4f 100644 --- a/packages/rs-dpp/src/data_contract/document_type/restricted_creation/mod.rs +++ b/packages/rs-dpp/src/data_contract/document_type/restricted_creation/mod.rs @@ -11,6 +11,7 @@ pub enum CreationRestrictionMode { NoRestrictions, OwnerOnly, NoCreationAllowed, + AnyGroupMember, } impl Display for CreationRestrictionMode { @@ -19,6 +20,7 @@ impl Display for CreationRestrictionMode { CreationRestrictionMode::NoRestrictions => write!(f, "No Restrictions"), CreationRestrictionMode::OwnerOnly => write!(f, "Owner Only"), CreationRestrictionMode::NoCreationAllowed => write!(f, "No Creation Allowed"), + CreationRestrictionMode::AnyGroupMember => write!(f, "Any Group Member"), } } } @@ -31,10 +33,11 @@ impl TryFrom for CreationRestrictionMode { 0 => Ok(Self::NoRestrictions), 1 => Ok(Self::OwnerOnly), 2 => Ok(Self::NoCreationAllowed), + 3 => Ok(Self::AnyGroupMember), value => Err(ProtocolError::ConsensusError( ConsensusError::BasicError( BasicError::UnknownDocumentCreationRestrictionModeError( - UnknownDocumentCreationRestrictionModeError::new(vec![0, 1, 2], value), + UnknownDocumentCreationRestrictionModeError::new(vec![0, 1, 2, 3], value), ), ) .into(), @@ -42,3 +45,34 @@ impl TryFrom for CreationRestrictionMode { } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::consensus::basic::BasicError; + use crate::consensus::ConsensusError; + use assert_matches::assert_matches; + + #[test] + fn should_parse_any_group_member_mode() { + let mode = CreationRestrictionMode::try_from(3).expect("mode 3 should be valid"); + assert_eq!(mode, CreationRestrictionMode::AnyGroupMember); + } + + #[test] + fn should_include_new_mode_in_unknown_error_allowed_values() { + let result = CreationRestrictionMode::try_from(9); + + assert_matches!( + result, + Err(ProtocolError::ConsensusError(boxed)) => { + assert_matches!( + boxed.as_ref(), + ConsensusError::BasicError( + BasicError::UnknownDocumentCreationRestrictionModeError(err) + ) if err.allowed_values() == vec![0, 1, 2, 3] + ) + } + ); + } +} diff --git a/packages/rs-dpp/src/data_contract/document_type/v2/accessors.rs b/packages/rs-dpp/src/data_contract/document_type/v2/accessors.rs new file mode 100644 index 00000000000..05b380d42ea --- /dev/null +++ b/packages/rs-dpp/src/data_contract/document_type/v2/accessors.rs @@ -0,0 +1,211 @@ +use crate::data_contract::document_type::accessors::{ + DocumentTypeV0Getters, DocumentTypeV0MutGetters, DocumentTypeV0Setters, DocumentTypeV1Getters, + DocumentTypeV2Getters, DocumentTypeV2Setters, +}; +use crate::data_contract::document_type::index::Index; +use crate::data_contract::document_type::index_level::IndexLevel; +use crate::data_contract::document_type::property::DocumentProperty; + +use platform_value::{Identifier, Value}; + +use crate::data_contract::document_type::restricted_creation::CreationRestrictionMode; +use crate::data_contract::document_type::token_costs::accessors::TokenCostGettersV0; +use crate::data_contract::document_type::v2::DocumentTypeV2; +#[cfg(feature = "validation")] +use crate::data_contract::document_type::validator::StatelessJsonSchemaLazyValidator; +use crate::data_contract::storage_requirements::keys_for_document_type::StorageKeyRequirements; +use crate::data_contract::{GroupContractPosition, TokenContractPosition}; +use crate::document::transfer::Transferable; +use crate::identity::SecurityLevel; +use crate::nft::TradeMode; +use crate::tokens::token_amount_on_contract_token::DocumentActionTokenCost; +use indexmap::IndexMap; +use std::collections::{BTreeMap, BTreeSet}; + +impl DocumentTypeV0MutGetters for DocumentTypeV2 { + fn schema_mut(&mut self) -> &mut Value { + &mut self.schema + } +} + +impl DocumentTypeV0Getters for DocumentTypeV2 { + fn name(&self) -> &String { + &self.name + } + + fn schema(&self) -> &Value { + &self.schema + } + + fn schema_owned(self) -> Value { + self.schema + } + + fn indexes(&self) -> &BTreeMap { + &self.indices + } + + fn find_contested_index(&self) -> Option<&Index> { + self.indices + .iter() + .find(|(_, index)| index.contested_index.is_some()) + .map(|(_, contested_index)| contested_index) + } + + fn index_structure(&self) -> &IndexLevel { + &self.index_structure + } + + fn flattened_properties(&self) -> &IndexMap { + &self.flattened_properties + } + + fn properties(&self) -> &IndexMap { + &self.properties + } + + fn identifier_paths(&self) -> &BTreeSet { + &self.identifier_paths + } + + fn binary_paths(&self) -> &BTreeSet { + &self.binary_paths + } + + fn required_fields(&self) -> &BTreeSet { + &self.required_fields + } + fn transient_fields(&self) -> &BTreeSet { + &self.transient_fields + } + + fn documents_keep_history(&self) -> bool { + self.documents_keep_history + } + + fn documents_mutable(&self) -> bool { + self.documents_mutable + } + + fn documents_can_be_deleted(&self) -> bool { + self.documents_can_be_deleted + } + + fn documents_transferable(&self) -> Transferable { + self.documents_transferable + } + + fn trade_mode(&self) -> TradeMode { + self.trade_mode + } + + fn creation_restriction_mode(&self) -> CreationRestrictionMode { + self.creation_restriction_mode + } + + fn data_contract_id(&self) -> Identifier { + self.data_contract_id + } + + fn requires_identity_encryption_bounded_key(&self) -> Option { + self.requires_identity_encryption_bounded_key + } + + fn requires_identity_decryption_bounded_key(&self) -> Option { + self.requires_identity_decryption_bounded_key + } + + fn security_level_requirement(&self) -> SecurityLevel { + self.security_level_requirement + } + + #[cfg(feature = "validation")] + fn json_schema_validator_ref(&self) -> &StatelessJsonSchemaLazyValidator { + &self.json_schema_validator + } +} + +impl DocumentTypeV0Setters for DocumentTypeV2 { + fn set_data_contract_id(&mut self, data_contract_id: Identifier) { + self.data_contract_id = data_contract_id; + } +} + +impl DocumentTypeV1Getters for DocumentTypeV2 { + fn document_creation_token_cost(&self) -> Option { + self.token_costs.document_creation_token_cost() + } + + fn document_replacement_token_cost(&self) -> Option { + self.token_costs.document_replacement_token_cost() + } + + fn document_deletion_token_cost(&self) -> Option { + self.token_costs.document_deletion_token_cost() + } + + fn document_transfer_token_cost(&self) -> Option { + self.token_costs.document_transfer_token_cost() + } + + fn document_update_price_token_cost(&self) -> Option { + self.token_costs.document_price_update_token_cost() + } + + fn document_purchase_token_cost(&self) -> Option { + self.token_costs.document_purchase_token_cost() + } + + fn all_document_token_costs(&self) -> Vec<&DocumentActionTokenCost> { + let mut result = Vec::new(); + + if let Some(cost) = self.token_costs.document_creation_token_cost_ref() { + result.push(cost); + } + if let Some(cost) = self.token_costs.document_replacement_token_cost_ref() { + result.push(cost); + } + if let Some(cost) = self.token_costs.document_deletion_token_cost_ref() { + result.push(cost); + } + if let Some(cost) = self.token_costs.document_transfer_token_cost_ref() { + result.push(cost); + } + if let Some(cost) = self.token_costs.document_price_update_token_cost_ref() { + result.push(cost); + } + if let Some(cost) = self.token_costs.document_purchase_token_cost_ref() { + result.push(cost); + } + + result + } + + fn all_external_token_costs_contract_tokens( + &self, + ) -> BTreeMap> { + let mut map = BTreeMap::new(); + + for cost in self.all_document_token_costs() { + if let Some(contract_id) = cost.contract_id { + map.entry(contract_id) + .or_insert_with(BTreeSet::new) + .insert(cost.token_contract_position); + } + } + + map + } +} + +impl DocumentTypeV2Getters for DocumentTypeV2 { + fn creation_restriction_group(&self) -> Option { + self.creation_restriction_group + } +} + +impl DocumentTypeV2Setters for DocumentTypeV2 { + fn set_creation_restriction_group(&mut self, group: Option) { + self.creation_restriction_group = group; + } +} diff --git a/packages/rs-dpp/src/data_contract/document_type/v2/mod.rs b/packages/rs-dpp/src/data_contract/document_type/v2/mod.rs new file mode 100644 index 00000000000..dc06beeaa96 --- /dev/null +++ b/packages/rs-dpp/src/data_contract/document_type/v2/mod.rs @@ -0,0 +1,137 @@ +use indexmap::IndexMap; +use std::collections::{BTreeMap, BTreeSet}; + +use crate::data_contract::document_type::index::Index; +use crate::data_contract::document_type::index_level::IndexLevel; +use crate::data_contract::document_type::property::DocumentProperty; +use crate::data_contract::storage_requirements::keys_for_document_type::StorageKeyRequirements; + +use crate::data_contract::document_type::accessors::DocumentTypeV1Setters; +use crate::data_contract::document_type::methods::{ + DocumentTypeBasicMethods, DocumentTypeV0Methods, +}; +use crate::data_contract::document_type::restricted_creation::CreationRestrictionMode; +use crate::data_contract::document_type::token_costs::accessors::TokenCostSettersV0; +use crate::data_contract::document_type::token_costs::TokenCosts; +use crate::data_contract::document_type::v1::DocumentTypeV1; +#[cfg(feature = "validation")] +use crate::data_contract::document_type::validator::StatelessJsonSchemaLazyValidator; +use crate::data_contract::GroupContractPosition; +use crate::document::transfer::Transferable; +use crate::identity::SecurityLevel; +use crate::nft::TradeMode; +use crate::tokens::token_amount_on_contract_token::DocumentActionTokenCost; +use platform_value::{Identifier, Value}; + +mod accessors; +#[cfg(feature = "random-document-types")] +pub mod random_document_type; + +#[derive(Debug, PartialEq, Clone)] +pub struct DocumentTypeV2 { + pub(in crate::data_contract) name: String, + pub(in crate::data_contract) schema: Value, + pub(in crate::data_contract) indices: BTreeMap, + pub(in crate::data_contract) index_structure: IndexLevel, + /// Flattened properties flatten all objects for quick lookups for indexes + /// Document field should not contain sub objects. + pub(in crate::data_contract) flattened_properties: IndexMap, + /// Document field can contain sub objects. + pub(in crate::data_contract) properties: IndexMap, + pub(in crate::data_contract) identifier_paths: BTreeSet, + pub(in crate::data_contract) binary_paths: BTreeSet, + /// The required fields on the document type + pub(in crate::data_contract) required_fields: BTreeSet, + /// The transient fields on the document type + pub(in crate::data_contract) transient_fields: BTreeSet, + /// Should documents keep history? + pub(in crate::data_contract) documents_keep_history: bool, + /// Are documents mutable? + pub(in crate::data_contract) documents_mutable: bool, + /// Can documents of this type be deleted? + pub(in crate::data_contract) documents_can_be_deleted: bool, + /// Can documents be transferred without a trade? + pub(in crate::data_contract) documents_transferable: Transferable, + /// How are these documents traded? + pub(in crate::data_contract) trade_mode: TradeMode, + /// Is document creation restricted? + pub(in crate::data_contract) creation_restriction_mode: CreationRestrictionMode, + /// Optional group position for creation restriction + pub(in crate::data_contract) creation_restriction_group: Option, + /// The data contract id + pub(in crate::data_contract) data_contract_id: Identifier, + /// Encryption key storage requirements + pub(in crate::data_contract) requires_identity_encryption_bounded_key: + Option, + /// Decryption key storage requirements + pub(in crate::data_contract) requires_identity_decryption_bounded_key: + Option, + pub(in crate::data_contract) security_level_requirement: SecurityLevel, + #[cfg(feature = "validation")] + pub(in crate::data_contract) json_schema_validator: StatelessJsonSchemaLazyValidator, + /// The token costs associated with state transitions on this document type + pub(in crate::data_contract) token_costs: TokenCosts, +} + +impl DocumentTypeBasicMethods for DocumentTypeV2 {} + +impl DocumentTypeV0Methods for DocumentTypeV2 {} + +impl DocumentTypeV1Setters for DocumentTypeV2 { + fn set_document_creation_token_cost(&mut self, cost: Option) { + self.token_costs.set_document_creation_token_cost(cost) + } + + fn set_document_replacement_token_cost(&mut self, cost: Option) { + self.token_costs.set_document_replacement_token_cost(cost) + } + + fn set_document_deletion_token_cost(&mut self, cost: Option) { + self.token_costs.set_document_deletion_token_cost(cost) + } + + fn set_document_transfer_token_cost(&mut self, cost: Option) { + self.token_costs.set_document_transfer_token_cost(cost) + } + + fn set_document_price_update_token_cost(&mut self, cost: Option) { + self.token_costs.set_document_price_update_token_cost(cost) + } + + fn set_document_purchase_token_cost(&mut self, cost: Option) { + self.token_costs.set_document_purchase_token_cost(cost) + } +} + +impl From for DocumentTypeV2 { + fn from(value: DocumentTypeV1) -> Self { + DocumentTypeV2 { + name: value.name, + schema: value.schema, + indices: value.indices, + index_structure: value.index_structure, + flattened_properties: value.flattened_properties, + properties: value.properties, + identifier_paths: value.identifier_paths, + binary_paths: value.binary_paths, + required_fields: value.required_fields, + transient_fields: value.transient_fields, + documents_keep_history: value.documents_keep_history, + documents_mutable: value.documents_mutable, + documents_can_be_deleted: value.documents_can_be_deleted, + documents_transferable: value.documents_transferable, + trade_mode: value.trade_mode, + creation_restriction_mode: value.creation_restriction_mode, + creation_restriction_group: None, + data_contract_id: value.data_contract_id, + requires_identity_encryption_bounded_key: value + .requires_identity_encryption_bounded_key, + requires_identity_decryption_bounded_key: value + .requires_identity_decryption_bounded_key, + security_level_requirement: value.security_level_requirement, + #[cfg(feature = "validation")] + json_schema_validator: value.json_schema_validator, + token_costs: value.token_costs, + } + } +} diff --git a/packages/rs-dpp/src/data_contract/document_type/v2/random_document_type.rs b/packages/rs-dpp/src/data_contract/document_type/v2/random_document_type.rs new file mode 100644 index 00000000000..849206ab2a6 --- /dev/null +++ b/packages/rs-dpp/src/data_contract/document_type/v2/random_document_type.rs @@ -0,0 +1,40 @@ +use crate::data_contract::document_type::v0::random_document_type::RandomDocumentTypeParameters; +use crate::data_contract::document_type::v1::DocumentTypeV1; +use crate::data_contract::document_type::v2::DocumentTypeV2; +use crate::version::PlatformVersion; +use crate::ProtocolError; +use platform_value::Identifier; +use rand::rngs::StdRng; + +impl DocumentTypeV2 { + pub fn random_document_type( + parameters: RandomDocumentTypeParameters, + data_contract_id: Identifier, + rng: &mut StdRng, + platform_version: &PlatformVersion, + ) -> Result { + Ok(DocumentTypeV1::random_document_type( + parameters, + data_contract_id, + rng, + platform_version, + )? + .into()) + } + + /// This is used to create an invalid random document type, often for testing + pub fn invalid_random_document_type( + parameters: RandomDocumentTypeParameters, + data_contract_id: Identifier, + rng: &mut StdRng, + platform_version: &PlatformVersion, + ) -> Result { + Ok(DocumentTypeV1::invalid_random_document_type( + parameters, + data_contract_id, + rng, + platform_version, + )? + .into()) + } +} diff --git a/packages/rs-drive-abci/src/execution/platform_events/initialization/create_genesis_state/test/tokens.rs b/packages/rs-drive-abci/src/execution/platform_events/initialization/create_genesis_state/test/tokens.rs index 0761c51cb7b..9bf2a70a2ce 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/initialization/create_genesis_state/test/tokens.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/initialization/create_genesis_state/test/tokens.rs @@ -28,7 +28,7 @@ use dpp::group::group_action::GroupAction; use dpp::identifier::Identifier; use dpp::identity::accessors::IdentitySettersV0; use dpp::identity::identity_public_key::accessors::v0::IdentityPublicKeyGettersV0; -use dpp::identity::{Identity, KeyID}; +use dpp::identity::Identity; use dpp::prelude::*; use dpp::tokens::calculate_token_id; use dpp::tokens::status::v0::TokenStatusV0; diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_create_transition_action/advanced_structure_v0/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_create_transition_action/advanced_structure_v0/mod.rs index 15420223eb5..fd172fcc1b0 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_create_transition_action/advanced_structure_v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_create_transition_action/advanced_structure_v0/mod.rs @@ -1,17 +1,23 @@ use dpp::block::block_info::BlockInfo; +use dpp::consensus::basic::data_contract::GroupPositionDoesNotExistError; use dpp::consensus::basic::document::{DocumentCreationNotAllowedError, InvalidDocumentTypeError}; +use dpp::consensus::state::group::IdentityNotMemberOfGroupError; use dpp::consensus::state::document::document_contest_not_paid_for_error::DocumentContestNotPaidForError; use dpp::dashcore::Network; use dpp::data_contract::accessors::v0::DataContractV0Getters; +use dpp::data_contract::accessors::v1::DataContractV1Getters; use dpp::data_contract::document_type::accessors::DocumentTypeV0Getters; +use dpp::data_contract::document_type::accessors::DocumentTypeV2Getters; use dpp::data_contract::document_type::methods::DocumentTypeV0Methods; use dpp::data_contract::document_type::restricted_creation::CreationRestrictionMode; +use dpp::data_contract::group::accessors::v0::GroupV0Getters; use dpp::data_contract::validate_document::DataContractDocumentValidationMethodsV0; use dpp::identifier::Identifier; use dpp::validation::{SimpleConsensusValidationResult}; use drive::state_transition_action::batch::batched_transition::document_transition::document_base_transition_action::DocumentBaseTransitionActionAccessorsV0; use drive::state_transition_action::batch::batched_transition::document_transition::document_create_transition_action::{DocumentCreateTransitionAction, DocumentCreateTransitionActionAccessorsV0}; use dpp::version::PlatformVersion; +use dpp::ProtocolError; use crate::error::Error; pub(in crate::execution::validation::state_transition::state_transitions::batch::action_validation) trait DocumentCreateTransitionActionStructureValidationV0 { @@ -102,6 +108,42 @@ impl DocumentCreateTransitionActionStructureValidationV0 for DocumentCreateTrans .into(), )); } + CreationRestrictionMode::AnyGroupMember => { + let Some(group_position) = document_type.creation_restriction_group() else { + return Ok(SimpleConsensusValidationResult::new_with_error( + DocumentCreationNotAllowedError::new( + self.base().data_contract_id(), + document_type_name.clone(), + document_type.creation_restriction_mode(), + ) + .into(), + )); + }; + + let group = match data_contract.expected_group(group_position) { + Ok(group) => group, + Err(_) => { + return Ok(SimpleConsensusValidationResult::new_with_error( + GroupPositionDoesNotExistError::new(group_position).into(), + )); + } + }; + + match group.member_power(owner_id) { + Ok(_) => {} + Err(ProtocolError::GroupMemberNotFound(_)) => { + return Ok(SimpleConsensusValidationResult::new_with_error( + IdentityNotMemberOfGroupError::new( + owner_id, + data_contract.id(), + group_position, + ) + .into(), + )); + } + Err(e) => return Err(e.into()), + } + } } // Validate user defined properties diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/creation.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/creation.rs index bdc7fbe8516..c1694cc10de 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/creation.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/creation.rs @@ -16,7 +16,10 @@ mod creation_tests { use dpp::fee::fee_result::refunds::FeeRefunds; use dpp::fee::fee_result::FeeResult; use dpp::data_contract::accessors::v0::DataContractV0Setters; + use dpp::data_contract::accessors::v1::DataContractV1Setters; use dpp::data_contract::document_type::restricted_creation::CreationRestrictionMode; + use dpp::data_contract::group::v0::GroupV0; + use dpp::data_contract::group::Group; use dpp::document::Document; use dpp::document::serialization_traits::DocumentPlatformConversionMethodsV0; use dpp::util::hash::hash_double; @@ -49,6 +52,7 @@ mod creation_tests { use dpp::tokens::token_payment_info::v0::TokenPaymentInfoV0; use crate::config::PlatformConfig; use crate::execution::validation::state_transition::tests::{create_card_game_external_token_contract_with_owner_identity, create_card_game_internal_token_contract_with_owner_identity_transfer_tokens, create_token_contract_with_owner_identity}; + use std::collections::BTreeMap; #[test] fn test_document_creation() { @@ -2539,6 +2543,347 @@ mod creation_tests { assert_eq!(consensus_error.to_string(), "Document Creation on 86LHvdC1Tqx5P97LQUSibGFqf2vnKFpB6VkqQ7oso86e:card is not allowed because of the document type's creation restriction mode Owner Only"); } + #[test] + fn test_document_creation_on_restricted_document_type_that_allows_group_member_to_create() { + let mut platform = TestPlatformBuilder::new() + .with_latest_protocol_version() + .build_with_mock_rpc() + .set_initial_state_structure(); + + let (contract_owner, _, _) = setup_identity(&mut platform, 958, dash_to_credits!(0.1)); + let (group_member, group_member_signer, group_member_key) = + setup_identity(&mut platform, 450, dash_to_credits!(0.1)); + + let card_game_path = "tests/supporting_files/contract/crypto-card-game/crypto-card-game-direct-purchase-creation-restricted-to-group.json"; + + let platform_state = platform.state.load(); + let platform_version = platform_state + .current_platform_version() + .expect("expected to get current platform version"); + + let mut contract = json_document_to_contract(card_game_path, true, platform_version) + .expect("expected to get data contract"); + + contract.set_owner_id(contract_owner.id()); + + let mut group_members = BTreeMap::new(); + group_members.insert(contract_owner.id(), 1); + group_members.insert(group_member.id(), 1); + + contract.add_group( + 0, + Group::V0(GroupV0 { + members: group_members, + required_power: 2, + }), + ); + + platform + .drive + .apply_contract( + &contract, + BlockInfo::default(), + true, + StorageFlags::optional_default_as_cow(), + None, + platform_version, + ) + .expect("expected to apply contract successfully"); + + let card_document_type = contract + .document_type_for_name("card") + .expect("expected a card document type"); + + assert_eq!( + card_document_type.creation_restriction_mode(), + CreationRestrictionMode::AnyGroupMember + ); + + let mut rng = StdRng::seed_from_u64(433); + let entropy = Bytes32::random_with_rng(&mut rng); + + let mut document = card_document_type + .random_document_with_identifier_and_entropy( + &mut rng, + group_member.id(), + entropy, + DocumentFieldFillType::DoNotFillIfNotRequired, + DocumentFieldFillSize::AnyDocumentFillSize, + platform_version, + ) + .expect("expected a random document"); + + document.set("attack", 4.into()); + document.set("defense", 7.into()); + + let documents_batch_create_transition = + BatchTransition::new_document_creation_transition_from_document( + document, + card_document_type, + entropy.0, + &group_member_key, + 2, + 0, + None, + &group_member_signer, + platform_version, + None, + ) + .expect("expect to create documents batch transition"); + + let documents_batch_create_serialized_transition = documents_batch_create_transition + .serialize_to_bytes() + .expect("expected documents batch serialized state transition"); + + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![documents_batch_create_serialized_transition.clone()], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + assert_eq!(processing_result.valid_count(), 1); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit transaction"); + } + + #[test] + fn test_document_creation_on_restricted_document_type_fails_for_non_group_member() { + let mut platform = TestPlatformBuilder::new() + .with_latest_protocol_version() + .build_with_mock_rpc() + .set_initial_state_structure(); + + let (contract_owner, _, _) = setup_identity(&mut platform, 958, dash_to_credits!(0.1)); + let (group_member, _, _) = setup_identity(&mut platform, 450, dash_to_credits!(0.1)); + let (non_member, non_member_signer, non_member_key) = + setup_identity(&mut platform, 451, dash_to_credits!(0.1)); + + let card_game_path = "tests/supporting_files/contract/crypto-card-game/crypto-card-game-direct-purchase-creation-restricted-to-group.json"; + + let platform_state = platform.state.load(); + let platform_version = platform_state + .current_platform_version() + .expect("expected to get current platform version"); + + let mut contract = json_document_to_contract(card_game_path, true, platform_version) + .expect("expected to get data contract"); + + contract.set_owner_id(contract_owner.id()); + + let mut group_members = BTreeMap::new(); + group_members.insert(contract_owner.id(), 1); + group_members.insert(group_member.id(), 1); + + contract.add_group( + 0, + Group::V0(GroupV0 { + members: group_members, + required_power: 2, + }), + ); + + platform + .drive + .apply_contract( + &contract, + BlockInfo::default(), + true, + StorageFlags::optional_default_as_cow(), + None, + platform_version, + ) + .expect("expected to apply contract successfully"); + + let card_document_type = contract + .document_type_for_name("card") + .expect("expected a card document type"); + + let mut rng = StdRng::seed_from_u64(433); + let entropy = Bytes32::random_with_rng(&mut rng); + + let mut document = card_document_type + .random_document_with_identifier_and_entropy( + &mut rng, + non_member.id(), + entropy, + DocumentFieldFillType::DoNotFillIfNotRequired, + DocumentFieldFillSize::AnyDocumentFillSize, + platform_version, + ) + .expect("expected a random document"); + + document.set("attack", 8.into()); + document.set("defense", 2.into()); + + let documents_batch_create_transition = + BatchTransition::new_document_creation_transition_from_document( + document, + card_document_type, + entropy.0, + &non_member_key, + 2, + 0, + None, + &non_member_signer, + platform_version, + None, + ) + .expect("expect to create documents batch transition"); + + let documents_batch_create_serialized_transition = documents_batch_create_transition + .serialize_to_bytes() + .expect("expected documents batch serialized state transition"); + + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![documents_batch_create_serialized_transition.clone()], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + assert_matches!( + processing_result.execution_results().as_slice(), + [PaidConsensusError { + error: ConsensusError::StateError(StateError::IdentityNotMemberOfGroupError(_)), + .. + }] + ); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit transaction"); + } + + #[test] + fn test_document_creation_on_restricted_document_type_fails_for_missing_group_position() { + let mut platform = TestPlatformBuilder::new() + .with_latest_protocol_version() + .build_with_mock_rpc() + .set_initial_state_structure(); + + let (contract_owner, _, _) = setup_identity(&mut platform, 958, dash_to_credits!(0.1)); + let (identity, signer, key) = setup_identity(&mut platform, 450, dash_to_credits!(0.1)); + + let card_game_path = "tests/supporting_files/contract/crypto-card-game/crypto-card-game-direct-purchase-creation-restricted-to-group.json"; + + let platform_state = platform.state.load(); + let platform_version = platform_state + .current_platform_version() + .expect("expected to get current platform version"); + + let mut contract = json_document_to_contract(card_game_path, true, platform_version) + .expect("expected to get data contract"); + + contract.set_owner_id(contract_owner.id()); + + platform + .drive + .apply_contract( + &contract, + BlockInfo::default(), + true, + StorageFlags::optional_default_as_cow(), + None, + platform_version, + ) + .expect("expected to apply contract successfully"); + + let card_document_type = contract + .document_type_for_name("card") + .expect("expected a card document type"); + + let mut rng = StdRng::seed_from_u64(433); + let entropy = Bytes32::random_with_rng(&mut rng); + + let mut document = card_document_type + .random_document_with_identifier_and_entropy( + &mut rng, + identity.id(), + entropy, + DocumentFieldFillType::DoNotFillIfNotRequired, + DocumentFieldFillSize::AnyDocumentFillSize, + platform_version, + ) + .expect("expected a random document"); + + document.set("attack", 4.into()); + document.set("defense", 7.into()); + + let documents_batch_create_transition = + BatchTransition::new_document_creation_transition_from_document( + document, + card_document_type, + entropy.0, + &key, + 2, + 0, + None, + &signer, + platform_version, + None, + ) + .expect("expect to create documents batch transition"); + + let documents_batch_create_serialized_transition = documents_batch_create_transition + .serialize_to_bytes() + .expect("expected documents batch serialized state transition"); + + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![documents_batch_create_serialized_transition.clone()], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + assert_matches!( + processing_result.execution_results().as_slice(), + [PaidConsensusError { + error: ConsensusError::BasicError(BasicError::GroupPositionDoesNotExistError(_)), + .. + }] + ); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit transaction"); + } + #[test] fn test_document_creation_on_search_system_contract_fails_due_to_restriction() { // Build test platform 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 8decf78865c..963e6479156 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,9 +1,9 @@ use crate::error::Error; use dpp::consensus::basic::data_contract::{ - DuplicateKeywordsError, InvalidDataContractVersionError, InvalidDescriptionLengthError, - InvalidKeywordCharacterError, InvalidKeywordLengthError, InvalidTokenBaseSupplyError, - NewTokensDestinationIdentityOptionRequiredError, NonContiguousContractTokenPositionsError, - TooManyKeywordsError, + DuplicateKeywordsError, GroupPositionDoesNotExistError, InvalidDataContractVersionError, + InvalidDescriptionLengthError, InvalidKeywordCharacterError, InvalidKeywordLengthError, + InvalidTokenBaseSupplyError, NewTokensDestinationIdentityOptionRequiredError, + NonContiguousContractTokenPositionsError, TooManyKeywordsError, }; use dpp::consensus::basic::BasicError; use dpp::consensus::ConsensusError; @@ -12,7 +12,9 @@ use dpp::data_contract::associated_token::token_configuration::accessors::v0::To use dpp::data_contract::associated_token::token_distribution_rules::accessors::v0::TokenDistributionRulesV0Getters; use dpp::data_contract::associated_token::token_perpetual_distribution::methods::v0::TokenPerpetualDistributionV0Accessors; use dpp::data_contract::change_control_rules::authorized_action_takers::AuthorizedActionTakers; +use dpp::data_contract::errors::DataContractError; use dpp::data_contract::{TokenContractPosition, INITIAL_DATA_CONTRACT_VERSION}; +use dpp::platform_value::Value; use dpp::prelude::DataContract; use dpp::state_transition::data_contract_create_transition::accessors::DataContractCreateTransitionAccessorsV0; use dpp::state_transition::data_contract_create_transition::DataContractCreateTransition; @@ -20,6 +22,9 @@ use dpp::validation::SimpleConsensusValidationResult; use dpp::version::PlatformVersion; use std::collections::HashSet; +const CREATION_RESTRICTION_MODE: &str = "creationRestrictionMode"; +const CREATION_RESTRICTION_GROUP: &str = "creationRestrictionGroup"; + pub(in crate::execution::validation::state_transition::state_transitions::data_contract_create) trait DataContractCreateStateTransitionBasicStructureValidationV0 { fn validate_basic_structure_v0( @@ -44,7 +49,6 @@ impl DataContractCreateStateTransitionBasicStructureValidationV0 for DataContrac .into(), )); } - let groups = self.data_contract().groups(); if !groups.is_empty() { let validation_result = DataContract::validate_groups(groups, platform_version)?; @@ -54,6 +58,62 @@ impl DataContractCreateStateTransitionBasicStructureValidationV0 for DataContrac } } + for schema in self.data_contract().document_schemas().values() { + let schema_map = match schema.to_map() { + Ok(map) => map, + Err(err) => { + return Ok(SimpleConsensusValidationResult::new_with_error( + DataContractError::InvalidContractStructure(format!( + "document schema must be an object: {err}" + )) + .into(), + )); + } + }; + + let creation_restriction_mode: u8 = match Value::inner_optional_integer_value::( + schema_map, + CREATION_RESTRICTION_MODE, + ) { + Ok(value) => value.unwrap_or(0), + Err(err) => { + return Ok(SimpleConsensusValidationResult::new_with_error( + DataContractError::from(err).into(), + )); + } + }; + + if creation_restriction_mode == 3 { + let group_position = match Value::inner_optional_integer_value::( + schema_map, + CREATION_RESTRICTION_GROUP, + ) { + Ok(value) => value, + Err(err) => { + return Ok(SimpleConsensusValidationResult::new_with_error( + DataContractError::from(err).into(), + )); + } + }; + + let Some(group_position) = group_position else { + return Ok(SimpleConsensusValidationResult::new_with_error( + DataContractError::InvalidContractStructure( + "creationRestrictionGroup is required when creationRestrictionMode is 3" + .to_string(), + ) + .into(), + )); + }; + + if !self.data_contract().groups().contains_key(&group_position) { + return Ok(SimpleConsensusValidationResult::new_with_error( + GroupPositionDoesNotExistError::new(group_position).into(), + )); + } + } + } + for (expected_position, (token_contract_position, token_configuration)) in self.data_contract().tokens().iter().enumerate() { @@ -218,10 +278,13 @@ mod tests { mod validate_basic_structure { use super::*; + use dpp::consensus::basic::data_contract::GroupPositionDoesNotExistError; use dpp::consensus::basic::BasicError; use dpp::consensus::ConsensusError; use dpp::data_contract::accessors::v0::DataContractV0Setters; + use dpp::data_contract::serialized_version::DataContractInSerializationFormat; use dpp::data_contract::INITIAL_DATA_CONTRACT_VERSION; + use dpp::platform_value::platform_value; use dpp::prelude::IdentityNonce; use dpp::state_transition::data_contract_create_transition::DataContractCreateTransitionV0; use dpp::tests::fixtures::get_data_contract_fixture; @@ -261,5 +324,64 @@ mod tests { [ConsensusError::BasicError(BasicError::InvalidDataContractVersionError(e))] if e.expected_version() == INITIAL_DATA_CONTRACT_VERSION && e.version() == 6 ); } + + #[test] + fn should_return_invalid_result_when_creation_restriction_group_missing() { + let platform_version = PlatformVersion::latest(); + let identity_nonce = IdentityNonce::default(); + + let data_contract = + get_data_contract_fixture(None, identity_nonce, platform_version.protocol_version) + .data_contract_owned(); + + let mut data_contract_for_serialization = data_contract + .try_into_platform_versioned(platform_version) + .expect("failed to convert data contract"); + + let schema = platform_value!({ + "type": "object", + "properties": { + "name": { + "type": "string", + "position": 0 + } + }, + "creationRestrictionMode": 3, + "creationRestrictionGroup": 1, + "additionalProperties": false + }); + + match &mut data_contract_for_serialization { + DataContractInSerializationFormat::V0(_) => { + panic!("expected data contract serialization format v1"); + } + DataContractInSerializationFormat::V1(v1) => { + v1.document_schemas + .insert("niceDocument".to_string(), schema); + } + } + + let transition: DataContractCreateTransition = DataContractCreateTransitionV0 { + data_contract: data_contract_for_serialization, + identity_nonce, + user_fee_increase: 0, + signature_public_key_id: 0, + signature: Default::default(), + } + .into(); + + let result = transition + .validate_basic_structure_v0(Network::Testnet, &platform_version) + .expect("failed to validate advanced structure"); + + assert_matches!( + result.errors.as_slice(), + [ConsensusError::BasicError( + BasicError::GroupPositionDoesNotExistError( + GroupPositionDoesNotExistError { .. } + ) + )] + ); + } } } diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_update/basic_structure/v0/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_update/basic_structure/v0/mod.rs index 2cdfedb27b3..3a825791a34 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_update/basic_structure/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_update/basic_structure/v0/mod.rs @@ -1,20 +1,25 @@ use crate::error::Error; use dpp::consensus::basic::data_contract::{ - InvalidTokenBaseSupplyError, NewTokensDestinationIdentityOptionRequiredError, - NonContiguousContractTokenPositionsError, + GroupPositionDoesNotExistError, InvalidTokenBaseSupplyError, + NewTokensDestinationIdentityOptionRequiredError, NonContiguousContractTokenPositionsError, }; use dpp::dashcore::Network; use dpp::data_contract::associated_token::token_configuration::accessors::v0::TokenConfigurationV0Getters; use dpp::data_contract::associated_token::token_distribution_rules::accessors::v0::TokenDistributionRulesV0Getters; use dpp::data_contract::associated_token::token_perpetual_distribution::methods::v0::TokenPerpetualDistributionV0Accessors; use dpp::data_contract::change_control_rules::authorized_action_takers::AuthorizedActionTakers; +use dpp::data_contract::errors::DataContractError; use dpp::data_contract::TokenContractPosition; +use dpp::platform_value::Value; use dpp::prelude::DataContract; use dpp::state_transition::data_contract_update_transition::accessors::DataContractUpdateTransitionAccessorsV0; use dpp::state_transition::data_contract_update_transition::DataContractUpdateTransition; use dpp::validation::SimpleConsensusValidationResult; use dpp::version::PlatformVersion; +const CREATION_RESTRICTION_MODE: &str = "creationRestrictionMode"; +const CREATION_RESTRICTION_GROUP: &str = "creationRestrictionGroup"; + pub(in crate::execution::validation::state_transition::state_transitions::data_contract_update) trait DataContractUpdateStateTransitionBasicStructureValidationV0 { fn validate_basic_structure_v0( @@ -39,6 +44,62 @@ impl DataContractUpdateStateTransitionBasicStructureValidationV0 for DataContrac } } + for schema in self.data_contract().document_schemas().values() { + let schema_map = match schema.to_map() { + Ok(map) => map, + Err(err) => { + return Ok(SimpleConsensusValidationResult::new_with_error( + DataContractError::InvalidContractStructure(format!( + "document schema must be an object: {err}" + )) + .into(), + )); + } + }; + + let creation_restriction_mode: u8 = match Value::inner_optional_integer_value::( + schema_map, + CREATION_RESTRICTION_MODE, + ) { + Ok(value) => value.unwrap_or(0), + Err(err) => { + return Ok(SimpleConsensusValidationResult::new_with_error( + DataContractError::from(err).into(), + )); + } + }; + + if creation_restriction_mode == 3 { + let group_position = match Value::inner_optional_integer_value::( + schema_map, + CREATION_RESTRICTION_GROUP, + ) { + Ok(value) => value, + Err(err) => { + return Ok(SimpleConsensusValidationResult::new_with_error( + DataContractError::from(err).into(), + )); + } + }; + + let Some(group_position) = group_position else { + return Ok(SimpleConsensusValidationResult::new_with_error( + DataContractError::InvalidContractStructure( + "creationRestrictionGroup is required when creationRestrictionMode is 3" + .to_string(), + ) + .into(), + )); + }; + + if !self.data_contract().groups().contains_key(&group_position) { + return Ok(SimpleConsensusValidationResult::new_with_error( + GroupPositionDoesNotExistError::new(group_position).into(), + )); + } + } + } + for (expected_position, (token_contract_position, token_configuration)) in self.data_contract().tokens().iter().enumerate() { diff --git a/packages/rs-drive-abci/tests/strategy_tests/test_cases/address_tests.rs b/packages/rs-drive-abci/tests/strategy_tests/test_cases/address_tests.rs index dfc2f85b1e7..7aaab7b3172 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/test_cases/address_tests.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/test_cases/address_tests.rs @@ -2559,7 +2559,7 @@ mod tests { verified.err() ); - let (root_hash, verified_changes) = verified.unwrap(); + let (root_hash, _) = verified.unwrap(); assert!( !root_hash.is_empty(), "root hash should not be empty" @@ -3520,6 +3520,7 @@ mod tests { /// Funding operations create new addresses in "staged" state. After block commit, /// they become "committed" and available for transfers in subsequent blocks. #[test] + #[stack_size(4 * 1024 * 1024)] fn run_chain_high_throughput_address_operations() { drive_abci::logging::init_for_tests(LogLevel::Silent); diff --git a/packages/rs-drive-abci/tests/strategy_tests/test_cases/identity_and_document_tests.rs b/packages/rs-drive-abci/tests/strategy_tests/test_cases/identity_and_document_tests.rs index 632fe8f6f7f..efa33b21178 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/test_cases/identity_and_document_tests.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/test_cases/identity_and_document_tests.rs @@ -668,6 +668,7 @@ mod tests { } #[test] + #[stack_size(4 * 1024 * 1024)] fn run_chain_insert_one_new_identity_per_block_and_one_new_document() { let platform_version = PlatformVersion::latest(); let created_contract = json_document_to_created_contract( @@ -757,6 +758,7 @@ mod tests { } #[test] + #[stack_size(4 * 1024 * 1024)] fn run_chain_insert_one_new_identity_per_block_and_a_document_with_epoch_change() { let platform_version = PlatformVersion::latest(); let created_contract = json_document_to_created_contract( diff --git a/packages/rs-drive-abci/tests/supporting_files/contract/crypto-card-game/crypto-card-game-direct-purchase-creation-restricted-to-group.json b/packages/rs-drive-abci/tests/supporting_files/contract/crypto-card-game/crypto-card-game-direct-purchase-creation-restricted-to-group.json new file mode 100644 index 00000000000..b378e1895c5 --- /dev/null +++ b/packages/rs-drive-abci/tests/supporting_files/contract/crypto-card-game/crypto-card-game-direct-purchase-creation-restricted-to-group.json @@ -0,0 +1,149 @@ +{ + "$format_version": "1", + "id": "B2oN8wG8T2nTmnA8X2nqT2QYYrZ4o2YseGFCXx5xj8dD", + "ownerId": "2QjL594djCH2NyDsn45vd6yQjEDHupMKo7CEGVTHtQxU", + "version": 1, + "config": { + "$format_version": "0", + "canBeDeleted": false, + "readonly": false, + "keepsHistory": false, + "documentsKeepHistoryContractDefault": false, + "documentsMutableContractDefault": true, + "documentsCanBeDeletedContractDefault": true, + "requiresIdentityEncryptionBoundedKey": null, + "requiresIdentityDecryptionBoundedKey": null, + "sizedIntegerTypes": true + }, + "documentSchemas": { + "card": { + "type": "object", + "documentsMutable": false, + "canBeDeleted": true, + "transferable": 1, + "tradeMode": 1, + "creationRestrictionMode": 3, + "creationRestrictionGroup": 0, + "properties": { + "name": { + "type": "string", + "description": "Name of the card", + "maxLength": 63, + "position": 0 + }, + "description": { + "type": "string", + "description": "Description of the card", + "maxLength": 256, + "position": 1 + }, + "imageUrl": { + "type": "string", + "description": "URL of the image associated with the card", + "maxLength": 2048, + "format": "uri", + "position": 2 + }, + "imageHash": { + "type": "array", + "description": "SHA256 hash of the bytes of the image specified by imageUrl", + "byteArray": true, + "minItems": 32, + "maxItems": 32, + "position": 3 + }, + "imageFingerprint": { + "type": "array", + "description": "dHash of the image specified by imageUrl", + "byteArray": true, + "minItems": 8, + "maxItems": 8, + "position": 4 + }, + "attack": { + "type": "integer", + "description": "Attack power of the card", + "minimum": 0, + "position": 5 + }, + "defense": { + "type": "integer", + "description": "Defense level of the card", + "minimum": 0, + "position": 6 + } + }, + "indices": [ + { + "name": "owner", + "properties": [ + { + "$ownerId": "asc" + } + ] + }, + { + "name": "attack", + "properties": [ + { + "attack": "asc" + } + ] + }, + { + "name": "defense", + "properties": [ + { + "defense": "asc" + } + ] + }, + { + "name": "transferredAt", + "properties": [ + { + "$transferredAt": "asc" + } + ] + }, + { + "name": "ownerTransferredAt", + "properties": [ + { + "$ownerId": "asc" + }, + { + "$transferredAt": "asc" + } + ] + }, + { + "name": "transferredAtBlockHeight", + "properties": [ + { + "$transferredAtBlockHeight": "asc" + } + ] + }, + { + "name": "transferredAtCoreBlockHeight", + "properties": [ + { + "$transferredAtCoreBlockHeight": "asc" + } + ] + } + ], + "required": [ + "name", + "$transferredAt", + "$transferredAtBlockHeight", + "$transferredAtCoreBlockHeight", + "attack", + "defense" + ], + "additionalProperties": false + } + }, + "groups": {} +} diff --git a/packages/rs-json-schema-compatibility-validator/src/rules/rule_set.rs b/packages/rs-json-schema-compatibility-validator/src/rules/rule_set.rs index 2962395889a..d3d5d07c12b 100644 --- a/packages/rs-json-schema-compatibility-validator/src/rules/rule_set.rs +++ b/packages/rs-json-schema-compatibility-validator/src/rules/rule_set.rs @@ -1609,6 +1609,46 @@ pub static KEYWORD_COMPATIBILITY_RULES: Lazy = Laz ], }, ), + ( + "creationRestrictionGroup", + CompatibilityRules { + allow_addition: false, + allow_removal: false, + // TODO: ask Sam if we want to allow changing this field + allow_replacement_callback: FALSE_CALLBACK.clone(), + subschema_levels_depth: None, + inner: None, + #[cfg(any(test, feature = "examples"))] + examples: vec![ + ( + json!({}), + json!({ "creationRestrictionGroup": 1 }), + Some(JsonSchemaChange::Add(AddOperation { + path: "/creationRestrictionGroup".to_string(), + value: json!(1), + })), + ) + .into(), + ( + json!({ "creationRestrictionGroup": 1 }), + json!({}), + Some(JsonSchemaChange::Remove(RemoveOperation { + path: "/creationRestrictionGroup".to_string(), + })), + ) + .into(), + ( + json!({ "creationRestrictionGroup": 1 }), + json!({ "creationRestrictionGroup": 2 }), + Some(JsonSchemaChange::Replace(ReplaceOperation { + path: "/creationRestrictionGroup".to_string(), + value: json!(2), + })), + ) + .into(), + ], + }, + ), ( "requiresIdentityEncryptionBoundedKey", CompatibilityRules { diff --git a/packages/rs-platform-version/src/version/dpp_versions/dpp_contract_versions/mod.rs b/packages/rs-platform-version/src/version/dpp_versions/dpp_contract_versions/mod.rs index ea970e7d982..d4148ab1205 100644 --- a/packages/rs-platform-version/src/version/dpp_versions/dpp_contract_versions/mod.rs +++ b/packages/rs-platform-version/src/version/dpp_versions/dpp_contract_versions/mod.rs @@ -2,6 +2,7 @@ use versioned_feature_core::{FeatureVersion, FeatureVersionBounds}; pub mod v1; pub mod v2; pub mod v3; +pub mod v4; #[derive(Clone, Debug, Default)] pub struct DPPContractVersions { diff --git a/packages/rs-platform-version/src/version/dpp_versions/dpp_contract_versions/v4.rs b/packages/rs-platform-version/src/version/dpp_versions/dpp_contract_versions/v4.rs new file mode 100644 index 00000000000..2b37a93168b --- /dev/null +++ b/packages/rs-platform-version/src/version/dpp_versions/dpp_contract_versions/v4.rs @@ -0,0 +1,66 @@ +use crate::version::dpp_versions::dpp_contract_versions::{ + DPPContractVersions, DataContractMethodVersions, DocumentTypeClassMethodVersions, + DocumentTypeIndexVersions, DocumentTypeMethodVersions, DocumentTypeSchemaVersions, + DocumentTypeVersions, RecursiveSchemaValidatorVersions, TokenVersions, +}; +use versioned_feature_core::FeatureVersionBounds; + +// Introduced in protocol version 12, adds DocumentType try_from_schema v2 (DocumentTypeV2) +pub const CONTRACT_VERSIONS_V4: DPPContractVersions = DPPContractVersions { + max_serialized_size: 65000, + contract_serialization_version: FeatureVersionBounds { + min_version: 0, + max_version: 1, + default_current_version: 1, + }, + contract_structure_version: 1, + created_data_contract_structure: 0, + config: FeatureVersionBounds { + min_version: 0, + max_version: 1, + default_current_version: 1, + }, + methods: DataContractMethodVersions { + validate_document: 0, + validate_update: 0, + schema: 0, + validate_groups: 0, + equal_ignoring_time_fields: 0, + registration_cost: 1, + }, + document_type_versions: DocumentTypeVersions { + index_versions: DocumentTypeIndexVersions { + index_levels_from_indices: 0, + }, + class_method_versions: DocumentTypeClassMethodVersions { + try_from_schema: 2, + create_document_types_from_document_schemas: 1, + }, + structure_version: 0, + schema: DocumentTypeSchemaVersions { + should_add_creator_id: 1, + enrich_with_base_schema: 0, + find_identifier_and_binary_paths: 0, + validate_max_depth: 0, + max_depth: 256, + recursive_schema_validator_versions: RecursiveSchemaValidatorVersions { + traversal_validator: 0, + }, + validate_schema_compatibility: 0, + }, + methods: DocumentTypeMethodVersions { + create_document_from_data: 0, + create_document_with_prevalidated_properties: 0, + prefunded_voting_balance_for_document: 0, + contested_vote_poll_for_document: 0, + estimated_size: 0, + index_for_types: 0, + max_size: 0, + serialize_value_for_key: 0, + deserialize_value_for_key: 0, + }, + }, + token_versions: TokenVersions { + validate_structure_interval: 0, + }, +}; diff --git a/packages/rs-platform-version/src/version/v12.rs b/packages/rs-platform-version/src/version/v12.rs index 486f4be1fb5..4f1869ee100 100644 --- a/packages/rs-platform-version/src/version/v12.rs +++ b/packages/rs-platform-version/src/version/v12.rs @@ -1,6 +1,6 @@ use crate::version::consensus_versions::ConsensusVersions; use crate::version::dpp_versions::dpp_asset_lock_versions::v1::DPP_ASSET_LOCK_VERSIONS_V1; -use crate::version::dpp_versions::dpp_contract_versions::v3::CONTRACT_VERSIONS_V3; +use crate::version::dpp_versions::dpp_contract_versions::v4::CONTRACT_VERSIONS_V4; use crate::version::dpp_versions::dpp_costs_versions::v1::DPP_COSTS_VERSIONS_V1; use crate::version::dpp_versions::dpp_document_versions::v3::DOCUMENT_VERSIONS_V3; use crate::version::dpp_versions::dpp_factory_versions::v1::DPP_FACTORY_VERSIONS_V1; @@ -49,7 +49,7 @@ pub const PLATFORM_V12: PlatformVersion = PlatformVersion { state_transition_conversion_versions: STATE_TRANSITION_CONVERSION_VERSIONS_V2, state_transition_method_versions: STATE_TRANSITION_METHOD_VERSIONS_V1, state_transitions: STATE_TRANSITION_VERSIONS_V3, - contract_versions: CONTRACT_VERSIONS_V3, + contract_versions: CONTRACT_VERSIONS_V4, // changed from v3 to v4 document_versions: DOCUMENT_VERSIONS_V3, identity_versions: IDENTITY_VERSIONS_V1, voting_versions: VOTING_VERSION_V2,