Преглед на файлове

Merge pull request #1385 from Joystream/content_directory_second_try

Merge content directory second try into babylon
Mokhtar Naamani преди 4 години
родител
ревизия
37d097d115
променени са 67 файла, в които са добавени 17369 реда и са изтрити 990 реда
  1. 42 7
      Cargo.lock
  2. 2 1
      Cargo.toml
  3. 21 11
      node/src/chain_spec/mod.rs
  4. 4 0
      runtime-modules/common/Cargo.toml
  5. 5 1
      runtime-modules/common/src/working_group.rs
  6. 30 0
      runtime-modules/content-directory/Cargo.toml
  7. 244 0
      runtime-modules/content-directory/src/class.rs
  8. 202 0
      runtime-modules/content-directory/src/entity.rs
  9. 236 0
      runtime-modules/content-directory/src/errors.rs
  10. 340 0
      runtime-modules/content-directory/src/helpers.rs
  11. 2710 0
      runtime-modules/content-directory/src/lib.rs
  12. 1029 0
      runtime-modules/content-directory/src/mock.rs
  13. 141 0
      runtime-modules/content-directory/src/operations.rs
  14. 127 0
      runtime-modules/content-directory/src/permissions.rs
  15. 158 0
      runtime-modules/content-directory/src/permissions/class.rs
  16. 116 0
      runtime-modules/content-directory/src/permissions/curator_group.rs
  17. 176 0
      runtime-modules/content-directory/src/permissions/entity.rs
  18. 56 0
      runtime-modules/content-directory/src/permissions/entity_creation_voucher.rs
  19. 91 0
      runtime-modules/content-directory/src/schema.rs
  20. 62 0
      runtime-modules/content-directory/src/schema/convert.rs
  21. 137 0
      runtime-modules/content-directory/src/schema/input.rs
  22. 309 0
      runtime-modules/content-directory/src/schema/output.rs
  23. 646 0
      runtime-modules/content-directory/src/schema/property.rs
  24. 347 0
      runtime-modules/content-directory/src/tests.rs
  25. 507 0
      runtime-modules/content-directory/src/tests/add_class_schema.rs
  26. 59 0
      runtime-modules/content-directory/src/tests/add_curator_group.rs
  27. 131 0
      runtime-modules/content-directory/src/tests/add_curator_to_group.rs
  28. 195 0
      runtime-modules/content-directory/src/tests/add_maintainer_to_class.rs
  29. 1458 0
      runtime-modules/content-directory/src/tests/add_schema_support_to_entity.rs
  30. 557 0
      runtime-modules/content-directory/src/tests/clear_entity_property_vector.rs
  31. 275 0
      runtime-modules/content-directory/src/tests/create_class.rs
  32. 446 0
      runtime-modules/content-directory/src/tests/create_entity.rs
  33. 1271 0
      runtime-modules/content-directory/src/tests/insert_at_entity_property_vector.rs
  34. 776 0
      runtime-modules/content-directory/src/tests/remove_at_entity_property_vector.rs
  35. 131 0
      runtime-modules/content-directory/src/tests/remove_curator_from_group.rs
  36. 110 0
      runtime-modules/content-directory/src/tests/remove_curator_group.rs
  37. 296 0
      runtime-modules/content-directory/src/tests/remove_entity.rs
  38. 147 0
      runtime-modules/content-directory/src/tests/remove_maintainer_from_class.rs
  39. 84 0
      runtime-modules/content-directory/src/tests/set_curator_group_status.rs
  40. 106 0
      runtime-modules/content-directory/src/tests/transaction.rs
  41. 738 0
      runtime-modules/content-directory/src/tests/transfer_entity_ownership.rs
  42. 178 0
      runtime-modules/content-directory/src/tests/update_class_permissions.rs
  43. 145 0
      runtime-modules/content-directory/src/tests/update_class_schema_status.rs
  44. 197 0
      runtime-modules/content-directory/src/tests/update_entity_creation_voucher.rs
  45. 104 0
      runtime-modules/content-directory/src/tests/update_entity_permissions.rs
  46. 1025 0
      runtime-modules/content-directory/src/tests/update_entity_property_values.rs
  47. 1 1
      runtime-modules/governance/src/mock.rs
  48. 1 1
      runtime-modules/membership/src/mock.rs
  49. 1 1
      runtime-modules/membership/src/tests.rs
  50. 1 4
      runtime-modules/proposals/codex/Cargo.toml
  51. 1 99
      runtime-modules/proposals/codex/src/lib.rs
  52. 0 20
      runtime-modules/proposals/codex/src/proposal_types/mod.rs
  53. 0 29
      runtime-modules/proposals/codex/src/proposal_types/parameters.rs
  54. 17 11
      runtime-modules/proposals/codex/src/tests/mock.rs
  55. 131 206
      runtime-modules/proposals/codex/src/tests/mod.rs
  56. 2 1
      runtime-modules/working-group/src/lib.rs
  57. 3 1
      runtime/Cargo.toml
  58. 44 0
      runtime/src/integration/content_directory.rs
  59. 1 0
      runtime/src/integration/content_working_group.rs
  60. 1 0
      runtime/src/integration/mod.rs
  61. 3 8
      runtime/src/integration/proposals/proposal_encoder.rs
  62. 47 3
      runtime/src/integration/working_group.rs
  63. 61 4
      runtime/src/lib.rs
  64. 3 0
      runtime/src/primitives.rs
  65. 1 0
      runtime/src/tests/mod.rs
  66. 0 57
      runtime/src/tests/proposals_integration/mod.rs
  67. 883 524
      runtime/src/tests/proposals_integration/working_group_proposals.rs

+ 42 - 7
Cargo.lock

@@ -2067,6 +2067,7 @@ dependencies = [
  "pallet-balances",
  "pallet-collective",
  "pallet-common",
+ "pallet-content-directory",
  "pallet-content-working-group",
  "pallet-finality-tracker",
  "pallet-forum",
@@ -2116,6 +2117,7 @@ dependencies = [
  "sp-std",
  "sp-transaction-pool",
  "sp-version",
+ "strum 0.19.2",
  "substrate-wasm-builder-runner",
 ]
 
@@ -3300,6 +3302,23 @@ dependencies = [
  "parity-scale-codec",
  "serde",
  "sp-runtime",
+ "strum 0.19.2",
+ "strum_macros 0.19.2",
+]
+
+[[package]]
+name = "pallet-content-directory"
+version = "3.0.0"
+dependencies = [
+ "frame-support",
+ "frame-system",
+ "parity-scale-codec",
+ "serde",
+ "sp-arithmetic",
+ "sp-core",
+ "sp-io",
+ "sp-runtime",
+ "sp-std",
 ]
 
 [[package]]
@@ -3513,7 +3532,6 @@ dependencies = [
  "frame-system",
  "pallet-balances",
  "pallet-common",
- "pallet-content-working-group",
  "pallet-governance",
  "pallet-hiring",
  "pallet-membership",
@@ -3525,8 +3543,6 @@ dependencies = [
  "pallet-staking-reward-curve",
  "pallet-timestamp",
  "pallet-token-mint",
- "pallet-versioned-store",
- "pallet-versioned-store-permissions",
  "pallet-working-group",
  "parity-scale-codec",
  "serde",
@@ -3536,6 +3552,7 @@ dependencies = [
  "sp-runtime",
  "sp-staking",
  "sp-std",
+ "strum 0.19.2",
 ]
 
 [[package]]
@@ -3980,9 +3997,9 @@ dependencies = [
 
 [[package]]
 name = "parity-scale-codec-derive"
-version = "1.2.0"
+version = "1.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a0ec292e92e8ec7c58e576adacc1e3f399c597c8f263c42f18420abe58e7245"
+checksum = "198db82bb1c18fc00176004462dd809b2a6d851669550aa17af6dacd21ae0c14"
 dependencies = [
  "proc-macro-crate",
  "proc-macro2",
@@ -6443,7 +6460,7 @@ dependencies = [
  "lazy_static",
  "sp-core",
  "sp-runtime",
- "strum",
+ "strum 0.16.0",
 ]
 
 [[package]]
@@ -6786,9 +6803,15 @@ version = "0.16.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6138f8f88a16d90134763314e3fc76fa3ed6a7db4725d6acf9a3ef95a3188d22"
 dependencies = [
- "strum_macros",
+ "strum_macros 0.16.0",
 ]
 
+[[package]]
+name = "strum"
+version = "0.19.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3924a58d165da3b7b2922c667ab0673c7b5fd52b5c19ea3442747bcb3cd15abe"
+
 [[package]]
 name = "strum_macros"
 version = "0.16.0"
@@ -6801,6 +6824,18 @@ dependencies = [
  "syn 1.0.17",
 ]
 
+[[package]]
+name = "strum_macros"
+version = "0.19.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d2ab682ecdcae7f5f45ae85cd7c1e6c8e68ea42c8a612d47fedf831c037146a"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote 1.0.7",
+ "syn 1.0.17",
+]
+
 [[package]]
 name = "substrate-bip39"
 version = "0.4.1"

+ 2 - 1
Cargo.toml

@@ -19,10 +19,11 @@ members = [
 	"runtime-modules/versioned-store",
 	"runtime-modules/versioned-store-permissions",
 	"runtime-modules/working-group",
+	"runtime-modules/content-directory",
 	"node",
 	"utils/chain-spec-builder/"
 ]
 
 [profile.release]
 # Substrate runtime requires unwinding.
-panic = "unwind"
+panic = "unwind"

+ 21 - 11
node/src/chain_spec/mod.rs

@@ -30,11 +30,12 @@ use sp_runtime::Perbill;
 
 use node_runtime::{
     membership, AuthorityDiscoveryConfig, BabeConfig, Balance, BalancesConfig,
-    ContentWorkingGroupConfig, CouncilConfig, CouncilElectionConfig, DataDirectoryConfig,
-    DataObjectStorageRegistryConfig, DataObjectTypeRegistryConfig, ElectionParameters, ForumConfig,
-    GrandpaConfig, ImOnlineConfig, MembersConfig, Moment, ProposalsCodexConfig, SessionConfig,
-    SessionKeys, Signature, StakerStatus, StakingConfig, StorageWorkingGroupConfig, SudoConfig,
-    SystemConfig, VersionedStoreConfig, VersionedStorePermissionsConfig, DAYS, WASM_BINARY,
+    ContentDirectoryConfig, ContentDirectoryWorkingGroupConfig, ContentWorkingGroupConfig,
+    CouncilConfig, CouncilElectionConfig, DataDirectoryConfig, DataObjectStorageRegistryConfig,
+    DataObjectTypeRegistryConfig, ElectionParameters, ForumConfig, GrandpaConfig, ImOnlineConfig,
+    MembersConfig, Moment, ProposalsCodexConfig, SessionConfig, SessionKeys, Signature,
+    StakerStatus, StakingConfig, StorageWorkingGroupConfig, SudoConfig, SystemConfig,
+    VersionedStoreConfig, VersionedStorePermissionsConfig, DAYS, WASM_BINARY,
 };
 
 // Exported to be used by chain-spec-builder
@@ -320,6 +321,21 @@ pub fn testnet_genesis(
             worker_application_human_readable_text_constraint: default_text_constraint,
             worker_exit_rationale_text_constraint: default_text_constraint,
         }),
+        working_group_Instance3: Some(ContentDirectoryWorkingGroupConfig {
+            phantom: Default::default(),
+            storage_working_group_mint_capacity: 0,
+            opening_human_readable_text_constraint: default_text_constraint,
+            worker_application_human_readable_text_constraint: default_text_constraint,
+            worker_exit_rationale_text_constraint: default_text_constraint,
+        }),
+        content_directory: Some({
+            ContentDirectoryConfig {
+                curator_group_by_id: vec![],
+                next_class_id: 1,
+                next_entity_id: 1,
+                next_curator_group_id: 1,
+            }
+        }),
         versioned_store: Some(versioned_store_config),
         versioned_store_permissions: Some(versioned_store_permissions_config),
         content_wg: Some(content_working_group_config),
@@ -336,12 +352,6 @@ pub fn testnet_genesis(
                 .set_election_parameters_proposal_voting_period,
             set_election_parameters_proposal_grace_period: cpcp
                 .set_election_parameters_proposal_grace_period,
-            set_content_working_group_mint_capacity_proposal_voting_period: cpcp
-                .set_content_working_group_mint_capacity_proposal_voting_period,
-            set_content_working_group_mint_capacity_proposal_grace_period: cpcp
-                .set_content_working_group_mint_capacity_proposal_grace_period,
-            set_lead_proposal_voting_period: cpcp.set_lead_proposal_voting_period,
-            set_lead_proposal_grace_period: cpcp.set_lead_proposal_grace_period,
             spending_proposal_voting_period: cpcp.spending_proposal_voting_period,
             spending_proposal_grace_period: cpcp.spending_proposal_grace_period,
             add_working_group_opening_proposal_voting_period: cpcp

+ 4 - 0
runtime-modules/common/Cargo.toml

@@ -6,6 +6,8 @@ edition = '2018'
 
 [dependencies]
 serde = { version = "1.0.101", optional = true, features = ["derive"] }
+strum = {version = "0.19", optional = true}
+strum_macros = {version = "0.19", optional = true}
 codec = { package = 'parity-scale-codec', version = '1.3.1', default-features = false, features = ['derive'] }
 sp-runtime = { package = 'sp-runtime', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
 frame-support = { package = 'frame-support', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
@@ -16,6 +18,8 @@ pallet-timestamp = { package = 'pallet-timestamp', default-features = false, git
 default = ['std']
 std = [
 	'serde',
+	'strum',
+	'strum_macros',
 	'codec/std',
 	'sp-runtime/std',
 	'frame-support/std',

+ 5 - 1
runtime-modules/common/src/working_group.rs

@@ -1,9 +1,11 @@
 use codec::{Decode, Encode};
 #[cfg(feature = "std")]
 use serde::{Deserialize, Serialize};
+#[cfg(feature = "std")]
+use strum_macros::EnumIter;
 
 /// Defines well-known working groups.
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize, EnumIter))]
 #[derive(Encode, Decode, Clone, PartialEq, Eq, Copy, Debug)]
 pub enum WorkingGroup {
     /* Reserved
@@ -12,4 +14,6 @@ pub enum WorkingGroup {
     */
     /// Storage working group: working_group::Instance2.
     Storage,
+    /// Storage working group: working_group::Instance3.
+    Content,
 }

+ 30 - 0
runtime-modules/content-directory/Cargo.toml

@@ -0,0 +1,30 @@
+[package]
+name = 'pallet-content-directory'
+version = '3.0.0'
+authors = ['Joystream contributors']
+edition = '2018'
+
+[dependencies]
+sp-std = { package = 'sp-std', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
+sp-runtime = { package = 'sp-runtime', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
+frame-support = { package = 'frame-support', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
+system = { package = 'frame-system', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
+sp-arithmetic = { package = 'sp-arithmetic', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
+codec = { package = 'parity-scale-codec', version = '1.3.1', default-features = false, features = ['derive'] }
+serde = {version = '1.0.101', features = ['derive'], optional = true}
+
+[dev-dependencies]
+sp-io = { package = 'sp-io', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
+sp-core = { package = 'sp-core', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
+
+[features]
+default = ['std']
+std = [
+	'sp-std/std',
+	'sp-runtime/std',
+	'frame-support/std',
+	'system/std',
+	'sp-arithmetic/std',
+	'codec/std',
+	'serde',
+]

+ 244 - 0
runtime-modules/content-directory/src/class.rs

@@ -0,0 +1,244 @@
+use super::*;
+
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
+#[derive(Encode, Decode, Eq, PartialEq, Clone)]
+pub struct Class<T: Trait> {
+    /// Permissions for an instance of a Class.
+    class_permissions: ClassPermissions<T>,
+    /// All properties that have been used on this class across different class schemas.
+    /// Unlikely to be more than roughly 20 properties per class, often less.
+    /// For Person, think "height", "weight", etc.
+    properties: Vec<Property<T>>,
+
+    /// All schemas that are available for this class, think v0.0 Person, v.1.0 Person, etc.
+    schemas: Vec<Schema>,
+
+    name: Vec<u8>,
+
+    description: Vec<u8>,
+
+    /// The maximum number of entities which can be created.
+    maximum_entities_count: T::EntityId,
+
+    /// The current number of entities which exist.
+    current_number_of_entities: T::EntityId,
+
+    /// How many entities a given controller may create at most.
+    default_entity_creation_voucher_upper_bound: T::EntityId,
+}
+
+impl<T: Trait> Default for Class<T> {
+    fn default() -> Self {
+        Self {
+            class_permissions: ClassPermissions::<T>::default(),
+            properties: vec![],
+            schemas: vec![],
+            name: vec![],
+            description: vec![],
+            maximum_entities_count: T::EntityId::default(),
+            current_number_of_entities: T::EntityId::default(),
+            default_entity_creation_voucher_upper_bound: T::EntityId::default(),
+        }
+    }
+}
+
+impl<T: Trait> Class<T> {
+    /// Create new `Class` with provided parameters
+    pub fn new(
+        class_permissions: ClassPermissions<T>,
+        name: Vec<u8>,
+        description: Vec<u8>,
+        maximum_entities_count: T::EntityId,
+        default_entity_creation_voucher_upper_bound: T::EntityId,
+    ) -> Self {
+        Self {
+            class_permissions,
+            properties: vec![],
+            schemas: vec![],
+            name,
+            description,
+            maximum_entities_count,
+            current_number_of_entities: T::EntityId::zero(),
+            default_entity_creation_voucher_upper_bound,
+        }
+    }
+
+    pub fn get_name(&self) -> &[u8] {
+        &self.name
+    }
+
+    pub fn get_description(&self) -> &[u8] {
+        &self.description
+    }
+
+    pub fn set_name(&mut self, name: Vec<u8>) {
+        self.name = name;
+    }
+
+    pub fn set_description(&mut self, description: Vec<u8>) {
+        self.description = description;
+    }
+
+    /// Used to update `Schema` status under given `schema_index`
+    pub fn update_schema_status(&mut self, schema_index: SchemaId, schema_status: bool) {
+        if let Some(schema) = self.schemas.get_mut(schema_index as usize) {
+            schema.set_status(schema_status);
+        };
+    }
+
+    /// Used to update `Class` permissions
+    pub fn update_permissions(&mut self, permissions: ClassPermissions<T>) {
+        self.class_permissions = permissions
+    }
+
+    /// Get Class schemas by mutable reference
+    pub fn get_schemas_mut(&mut self) -> &mut Vec<Schema> {
+        &mut self.schemas
+    }
+
+    /// Get Class schemas by reference
+    pub fn get_schemas(&self) -> &Vec<Schema> {
+        &self.schemas
+    }
+
+    /// Increment number of entities, associated with this class
+    pub fn increment_entities_count(&mut self) {
+        self.current_number_of_entities += T::EntityId::one();
+    }
+
+    /// Decrement number of entities, associated with this class
+    pub fn decrement_entities_count(&mut self) {
+        self.current_number_of_entities -= T::EntityId::one();
+    }
+
+    /// Retrieve `ClassPermissions` by mutable reference
+    pub fn get_permissions_mut(&mut self) -> &mut ClassPermissions<T> {
+        &mut self.class_permissions
+    }
+
+    /// Retrieve `ClassPermissions` by reference
+    pub fn get_permissions_ref(&self) -> &ClassPermissions<T> {
+        &self.class_permissions
+    }
+
+    /// Retrieve `ClassPermissions` by value
+    pub fn get_permissions(self) -> ClassPermissions<T> {
+        self.class_permissions
+    }
+
+    /// Retrieve `Class` properties by value  
+    pub fn get_properties(self) -> Vec<Property<T>> {
+        self.properties
+    }
+
+    /// Replace `Class` properties with updated_class_properties
+    pub fn set_properties(&mut self, updated_class_properties: Vec<Property<T>>) {
+        self.properties = updated_class_properties;
+    }
+
+    /// Get per controller `Class`- specific limit
+    pub fn get_default_entity_creation_voucher_upper_bound(&self) -> T::EntityId {
+        self.default_entity_creation_voucher_upper_bound
+    }
+
+    /// Retrive the maximum entities count, which can be created for given `Class`
+    pub fn get_maximum_entities_count(&self) -> T::EntityId {
+        self.maximum_entities_count
+    }
+
+    /// Set per controller `Class`- specific limit
+    pub fn set_default_entity_creation_voucher_upper_bound(
+        &mut self,
+        new_default_entity_creation_voucher_upper_bound: T::EntityId,
+    ) {
+        self.default_entity_creation_voucher_upper_bound =
+            new_default_entity_creation_voucher_upper_bound;
+    }
+
+    /// Set the maximum entities count, which can be created for given `Class`
+    pub fn set_maximum_entities_count(&mut self, maximum_entities_count: T::EntityId) {
+        self.maximum_entities_count = maximum_entities_count;
+    }
+
+    /// Ensure `Class` `Schema` under given index exist, return corresponding `Schema`
+    pub fn ensure_schema_exists(&self, schema_index: SchemaId) -> Result<&Schema, Error<T>> {
+        self.schemas
+            .get(schema_index as usize)
+            .ok_or(Error::<T>::UnknownClassSchemaId)
+    }
+
+    /// Ensure `schema_id` is a valid index of `Class` schemas vector
+    pub fn ensure_schema_id_exists(&self, schema_id: SchemaId) -> Result<(), Error<T>> {
+        ensure!(
+            schema_id < self.schemas.len() as SchemaId,
+            Error::<T>::UnknownClassSchemaId
+        );
+        Ok(())
+    }
+
+    /// Ensure `Schema`s limit per `Class` not reached
+    pub fn ensure_schemas_limit_not_reached(&self) -> Result<(), Error<T>> {
+        ensure!(
+            (self.schemas.len() as MaxNumber) < T::MaxNumberOfSchemasPerClass::get(),
+            Error::<T>::ClassSchemasLimitReached
+        );
+        Ok(())
+    }
+
+    /// Ensure properties limit per `Schema` not reached
+    pub fn ensure_properties_limit_not_reached(
+        &self,
+        new_properties: &[Property<T>],
+    ) -> Result<(), Error<T>> {
+        ensure!(
+            T::MaxNumberOfPropertiesPerSchema::get()
+                >= (self.properties.len() + new_properties.len()) as MaxNumber,
+            Error::<T>::SchemaPropertiesLimitReached
+        );
+        Ok(())
+    }
+
+    /// Ensure `Class` specific entities limit not reached
+    pub fn ensure_maximum_entities_count_limit_not_reached(&self) -> Result<(), Error<T>> {
+        ensure!(
+            self.current_number_of_entities < self.maximum_entities_count,
+            Error::<T>::NumberOfEntitiesPerClassLimitReached
+        );
+        Ok(())
+    }
+
+    /// Ensure `Property` under given `PropertyId` is unlocked from actor with given `EntityAccessLevel`
+    /// return corresponding `Property` by value
+    pub fn ensure_class_property_type_unlocked_from(
+        &self,
+        in_class_schema_property_id: PropertyId,
+        entity_access_level: EntityAccessLevel,
+    ) -> Result<Property<T>, Error<T>> {
+        // Ensure property values were not locked on Class level
+        self.ensure_property_values_unlocked()?;
+
+        // Get class-level information about this `Property`
+        let class_property = self
+            .properties
+            .get(in_class_schema_property_id as usize)
+            // Throw an error if a property was not found on class
+            // by an in-class index of a property.
+            .ok_or(Error::<T>::ClassPropertyNotFound)?;
+
+        // Ensure Property is unlocked from Actor with given EntityAccessLevel
+        class_property.ensure_unlocked_from(entity_access_level)?;
+
+        Ok(class_property.to_owned())
+    }
+
+    /// Ensure property values were not locked on `Class` level
+    pub fn ensure_property_values_unlocked(&self) -> Result<(), Error<T>> {
+        ensure!(
+            !self
+                .get_permissions_ref()
+                .all_entity_property_values_locked(),
+            Error::<T>::AllPropertiesWereLockedOnClassLevel
+        );
+        Ok(())
+    }
+}

+ 202 - 0
runtime-modules/content-directory/src/entity.rs

@@ -0,0 +1,202 @@
+use super::*;
+
+/// Represents `Entity`, related to a specific `Class`
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
+#[derive(Encode, Decode, Clone, PartialEq, Eq)]
+pub struct Entity<T: Trait> {
+    /// Permissions for an instance of an Entity.
+    entity_permissions: EntityPermissions<T>,
+
+    /// The class id of this entity.
+    class_id: T::ClassId,
+
+    /// What schemas under which entity of the respective class is available, think
+    /// v.2.0 Person schema for John, v3.0 Person schema for John
+    /// Unlikely to be more than roughly 20ish, assuming schemas for a given class eventually stableize,
+    /// or that very old schema are eventually removed.
+    supported_schemas: BTreeSet<SchemaId>, // indices of schema in corresponding class
+
+    /// Values for properties on class that are used by some schema used by this entity
+    /// Length is no more than Class.properties.
+    values: BTreeMap<PropertyId, StoredPropertyValue<T>>,
+
+    /// Number of property values referencing current entity
+    reference_counter: InboundReferenceCounter,
+}
+
+impl<T: Trait> Default for Entity<T> {
+    fn default() -> Self {
+        Self {
+            entity_permissions: EntityPermissions::<T>::default(),
+            class_id: T::ClassId::default(),
+            supported_schemas: BTreeSet::new(),
+            values: BTreeMap::new(),
+            reference_counter: InboundReferenceCounter::default(),
+        }
+    }
+}
+
+impl<T: Trait> Entity<T> {
+    /// Create new `Entity` instance, related to a given `class_id` with provided parameters,  
+    pub fn new(
+        controller: EntityController<T>,
+        class_id: T::ClassId,
+        supported_schemas: BTreeSet<SchemaId>,
+        values: BTreeMap<PropertyId, StoredPropertyValue<T>>,
+    ) -> Self {
+        Self {
+            entity_permissions: EntityPermissions::<T>::default_with_controller(controller),
+            class_id,
+            supported_schemas,
+            values,
+            reference_counter: InboundReferenceCounter::default(),
+        }
+    }
+
+    /// Get `class_id` of this `Entity`
+    pub fn get_class_id(&self) -> T::ClassId {
+        self.class_id
+    }
+
+    /// Get Entity supported schemas by mutable reference
+    pub fn get_supported_schemas_mut(&mut self) -> &mut BTreeSet<SchemaId> {
+        &mut self.supported_schemas
+    }
+
+    /// Get `Entity` values by value
+    pub fn get_values(self) -> BTreeMap<PropertyId, StoredPropertyValue<T>> {
+        self.values
+    }
+
+    /// Get `Entity` values by reference
+    pub fn get_values_ref(&self) -> &BTreeMap<PropertyId, StoredPropertyValue<T>> {
+        &self.values
+    }
+
+    /// Get `Entity` values by mutable reference
+    pub fn get_values_mut(&mut self) -> &mut BTreeMap<PropertyId, StoredPropertyValue<T>> {
+        &mut self.values
+    }
+
+    /// Get mutable reference to `Entity` values
+    pub fn set_values(&mut self, new_values: BTreeMap<PropertyId, StoredPropertyValue<T>>) {
+        self.values = new_values;
+    }
+
+    /// Get mutable `EntityPermissions` reference, related to given `Entity`
+    pub fn get_permissions_mut(&mut self) -> &mut EntityPermissions<T> {
+        &mut self.entity_permissions
+    }
+
+    /// Get `EntityPermissions` reference, related to given `Entity`
+    pub fn get_permissions_ref(&self) -> &EntityPermissions<T> {
+        &self.entity_permissions
+    }
+
+    /// Get `EntityPermissions`, related to given `Entity` by value
+    pub fn get_permissions(self) -> EntityPermissions<T> {
+        self.entity_permissions
+    }
+
+    /// Update existing `EntityPermissions` with newly provided
+    pub fn update_permissions(&mut self, permissions: EntityPermissions<T>) {
+        self.entity_permissions = permissions
+    }
+
+    /// Ensure `Schema` under given id is not added to given `Entity` yet
+    pub fn ensure_schema_id_is_not_added(&self, schema_id: SchemaId) -> Result<(), Error<T>> {
+        let schema_not_added = !self.supported_schemas.contains(&schema_id);
+        ensure!(schema_not_added, Error::<T>::SchemaAlreadyAddedToTheEntity);
+        Ok(())
+    }
+
+    /// Ensure provided `property_values` are not added to the `Entity` `values` map yet
+    pub fn ensure_property_values_are_not_added(
+        &self,
+        property_values: &BTreeMap<PropertyId, InputPropertyValue<T>>,
+    ) -> Result<(), Error<T>> {
+        ensure!(
+            property_values
+                .keys()
+                .all(|key| !self.values.contains_key(key)),
+            Error::<T>::EntityAlreadyContainsGivenPropertyId
+        );
+        Ok(())
+    }
+
+    /// Ensure InputPropertyValue under given `in_class_schema_property_id` is Vector
+    pub fn ensure_property_value_is_vec(
+        &self,
+        in_class_schema_property_id: PropertyId,
+    ) -> Result<VecStoredPropertyValue<T>, Error<T>> {
+        self.values
+            .get(&in_class_schema_property_id)
+            // Throw an error if a property was not found on entity
+            // by an in-class index of a property.
+            .ok_or(Error::<T>::UnknownEntityPropertyId)?
+            .as_vec_property_value()
+            .map(|property_value_vec| property_value_vec.to_owned())
+            // Ensure prop value under given class schema property id is vector
+            .ok_or(Error::<T>::PropertyValueUnderGivenIndexIsNotAVector)
+    }
+
+    /// Ensure any `InputPropertyValue` from external entity does not point to the given `Entity`
+    pub fn ensure_rc_is_zero(&self) -> Result<(), Error<T>> {
+        ensure!(
+            self.reference_counter.is_total_equal_to_zero(),
+            Error::<T>::EntityRcDoesNotEqualToZero
+        );
+        Ok(())
+    }
+
+    /// Ensure any inbound `InputPropertyValue` with `same_owner` flag set points to the given `Entity`
+    pub fn ensure_inbound_same_owner_rc_is_zero(&self) -> Result<(), Error<T>> {
+        ensure!(
+            self.reference_counter.is_same_owner_equal_to_zero(),
+            Error::<T>::EntityInboundSameOwnerRcDoesNotEqualToZero
+        );
+        Ok(())
+    }
+
+    /// Get mutable reference to the `Entity`'s `InboundReferenceCounter` instance
+    pub fn get_reference_counter_mut(&mut self) -> &mut InboundReferenceCounter {
+        &mut self.reference_counter
+    }
+}
+
+/// Structure, respresenting inbound entity rcs for each `Entity`
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Copy)]
+pub struct InboundReferenceCounter {
+    /// Total number of inbound references from another entities
+    pub total: u32,
+    /// Number of inbound references from another entities with `SameOwner` flag set
+    pub same_owner: u32,
+}
+
+impl InboundReferenceCounter {
+    /// Create simple `InboundReferenceCounter` instance, based on `same_owner` flag provided
+    pub fn new(reference_counter: u32, same_owner: bool) -> Self {
+        if same_owner {
+            Self {
+                total: reference_counter,
+                same_owner: reference_counter,
+            }
+        } else {
+            Self {
+                total: reference_counter,
+                same_owner: 0,
+            }
+        }
+    }
+
+    /// Check if `total` is equal to zero
+    pub fn is_total_equal_to_zero(self) -> bool {
+        self.total == 0
+    }
+
+    /// Check if `same_owner` is equal to zero
+    pub fn is_same_owner_equal_to_zero(self) -> bool {
+        self.same_owner == 0
+    }
+}

+ 236 - 0
runtime-modules/content-directory/src/errors.rs

@@ -0,0 +1,236 @@
+use crate::*;
+use frame_support::decl_error;
+
+decl_error! {
+    /// Content directory errors
+    pub enum Error for Module<T: Trait> {
+
+        /// Validation errors
+        /// --------------------------------------
+
+
+        /// Property name is too short
+        PropertyNameTooShort,
+
+        /// Property name is too long
+        PropertyNameTooLong,
+
+        /// Property description is too short
+        PropertyDescriptionTooShort,
+
+        /// Property description is too long
+        PropertyDescriptionTooLong,
+
+        /// Class name is too short
+        ClassNameTooShort,
+
+        /// Class name is too long
+        ClassNameTooLong,
+
+        /// Class description is too short
+        ClassDescriptionTooShort,
+
+        /// Class description is too long
+        ClassDescriptionTooLong,
+
+        /// Maximum number of classes limit reached
+        ClassLimitReached,
+
+        /// Maximum number of given class schemas limit reached
+        ClassSchemasLimitReached,
+
+        /// Maximum number of properties in schema limit reached
+        SchemaPropertiesLimitReached,
+
+        /// Entities creation limit per controller should be less than overall entities creation limit
+        PerControllerEntitiesCreationLimitExceedsOverallLimit,
+
+        /// Number of entities per class is to big
+        EntitiesNumberPerClassConstraintViolated,
+
+        /// Number of class entities per actor constraint violated
+        NumberOfClassEntitiesPerActorConstraintViolated,
+
+        /// Individual number of class entities per actor is too big
+        IndividualNumberOfClassEntitiesPerActorIsTooBig,
+
+        /// Number of operations during atomic batching limit reached
+        NumberOfOperationsDuringAtomicBatchingLimitReached,
+
+        /// Text property is too long
+        TextPropertyTooLong,
+
+        /// Text property to be hashed is too long
+        HashedTextPropertyTooLong,
+
+        /// Vector property is too long
+        VecPropertyTooLong,
+
+        /// Propery value vector can`t contain more values
+        EntityPropertyValueVectorIsTooLong,
+
+        /// Given property value vector index is out of range
+        EntityPropertyValueVectorIndexIsOutOfRange,
+
+
+        /// Main logic errors
+        /// --------------------------------------
+
+
+        /// Class was not found by id
+        ClassNotFound,
+
+        /// Class property under given index not found
+        ClassPropertyNotFound,
+
+        /// Unknown class schema id
+        UnknownClassSchemaId,
+
+        /// Given class schema is not active
+        ClassSchemaNotActive,
+
+        /// New class schema refers to an unknown property index
+        ClassSchemaRefersUnknownPropertyIndex,
+
+        /// New class schema refers to an unknown class id
+        ClassSchemaRefersUnknownClass,
+
+        /// Cannot add a class schema with an empty list of properties
+        NoPropertiesInClassSchema,
+
+        /// Entity was not found by id
+        EntityNotFound,
+
+        /// Cannot add a schema that is already added to this entity
+        SchemaAlreadyAddedToTheEntity,
+
+        /// Some of the provided property values don't match the expected property type
+        PropertyValueDoNotMatchType,
+
+        /// Property value don't match the expected vector property type
+        PropertyValueDoNotMatchVecType,
+
+        /// Property value under given index is not a vector
+        PropertyValueUnderGivenIndexIsNotAVector,
+
+        /// Current property value vector nonce does not equal to provided one
+        PropertyValueVecNoncesDoesNotMatch,
+
+        /// Property name is not unique within its class
+        PropertyNameNotUniqueInAClass,
+
+        /// Some required property was not found when adding schema support to entity
+        MissingRequiredProperty,
+
+        /// Schema under provided schema_id does not contain given property
+        SchemaDoesNotContainProvidedPropertyId,
+
+        /// Some of the provided property ids cannot be found on the current list of propery values of this entity
+        UnknownEntityPropertyId,
+
+        /// Entity already contains property under provided index
+        EntityAlreadyContainsGivenPropertyId,
+
+        /// Propery value type does not match internal entity vector type
+        PropertyValueTypeDoesNotMatchInternalVectorType,
+
+        /// Provided property references entity, which class_id is not equal to class_id, declared in corresponding property type
+        ReferencedEntityDoesNotMatchItsClass,
+
+        /// Entity removal can`t be completed, as there are some property values pointing to given entity
+        EntityRcDoesNotEqualToZero,
+
+        /// Entity ownership transfer can`t be completed, as there are some property values pointing to given entity with same owner flag set
+        EntityInboundSameOwnerRcDoesNotEqualToZero,
+
+        /// Provided entity controller is equal to the current one
+        ProvidedEntityControllerIsEqualToTheCurrentOne,
+
+        /// All ids of new property value references with same owner flag set should match their respective Properties defined on Class level
+        AllProvidedPropertyValueIdsMustBeReferencesWithSameOwnerFlagSet,
+
+        /// Entity was not created in batched transaction
+        EntityNotCreatedByOperation,
+
+        /// Permission errors
+        /// --------------------------------------
+
+        /// Curator group can`t be removed, as it currently maintains at least one class
+        CuratorGroupRemovalForbidden,
+
+        /// All property values, related to a given Entity were locked on Class level
+        AllPropertiesWereLockedOnClassLevel,
+
+        /// Curator under provided curator id is not a member of curaror group under given id
+        CuratorIsNotAMemberOfGivenCuratorGroup,
+
+        /// Given curator group does not exist
+        CuratorGroupDoesNotExist,
+
+        /// Entity should be referenced from the entity, owned by the same controller
+        SameControllerConstraintViolation,
+
+        /// Given maintainer does not exist
+        MaintainerDoesNotExist,
+
+        /// Given maintainer already exist
+        MaintainerAlreadyExists,
+
+        /// Provided actor can`t create entities of given class
+        ActorCanNotCreateEntities,
+
+        /// Maximum numbers of entities per class limit reached
+        NumberOfEntitiesPerClassLimitReached,
+
+        /// Current class entities creation blocked
+        EntitiesCreationBlocked,
+
+        /// Entities voucher limit reached
+        VoucherLimitReached,
+
+        /// Lead authentication failed
+        LeadAuthFailed,
+
+        /// Member authentication failed
+        MemberAuthFailed,
+
+        /// Curator authentication failed
+        CuratorAuthFailed,
+
+        /// Expected root or signed origin
+        BadOrigin,
+
+        /// Entity removal access denied
+        EntityRemovalAccessDenied,
+
+        /// Add entity schema support access denied
+        EntityAddSchemaSupportAccessDenied,
+
+        /// Class access denied
+        ClassAccessDenied,
+
+        /// Entity access denied
+        EntityAccessDenied,
+
+        /// Given entity can`t be referenced
+        EntityCanNotBeReferenced,
+
+        /// Given class property type is locked for given actor
+        ClassPropertyTypeLockedForGivenActor,
+
+        /// Number of maintainers per class limit reached
+        ClassMaintainersLimitReached,
+
+        /// Max number of curators per group limit reached
+        CuratorsPerGroupLimitReached,
+
+        /// Curator group is not active
+        CuratorGroupIsNotActive,
+
+        /// Origin cannot be made into raw origin
+        OriginCanNotBeMadeIntoRawOrigin,
+
+        /// Property value should be unique across all Entities of this Class
+        PropertyValueShouldBeUnique
+    }
+}

+ 340 - 0
runtime-modules/content-directory/src/helpers.rs

@@ -0,0 +1,340 @@
+use crate::*;
+use core::ops::{Deref, DerefMut};
+
+/// Wrapper for existing `InputPropertyValue` and its respective `Class` `Property`
+pub struct InputValueForExistingProperty<'a, T: Trait>(&'a Property<T>, &'a InputPropertyValue<T>);
+
+impl<'a, T: Trait> InputValueForExistingProperty<'a, T> {
+    /// Create single instance of `InputValueForExistingProperty` from provided `property` and `value`
+    fn new(property: &'a Property<T>, value: &'a InputPropertyValue<T>) -> Self {
+        Self(property, value)
+    }
+
+    /// Retrieve `Property` reference
+    pub fn get_property(&self) -> &Property<T> {
+        self.0
+    }
+
+    /// Retrieve `InputPropertyValue` reference
+    pub fn get_value(&self) -> &InputPropertyValue<T> {
+        self.1
+    }
+
+    /// Retrieve `Property` and `InputPropertyValue` references
+    pub fn unzip(&self) -> (&Property<T>, &InputPropertyValue<T>) {
+        (self.0, self.1)
+    }
+}
+
+/// Mapping, used to represent `PropertyId` relation to its respective `InputValueForExistingProperty` structure
+pub struct InputValuesForExistingProperties<'a, T: Trait>(
+    BTreeMap<PropertyId, InputValueForExistingProperty<'a, T>>,
+);
+
+impl<'a, T: Trait> Default for InputValuesForExistingProperties<'a, T> {
+    fn default() -> Self {
+        Self(BTreeMap::default())
+    }
+}
+
+impl<'a, T: Trait> Deref for InputValuesForExistingProperties<'a, T> {
+    type Target = BTreeMap<PropertyId, InputValueForExistingProperty<'a, T>>;
+
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
+impl<'a, T: Trait> DerefMut for InputValuesForExistingProperties<'a, T> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.0
+    }
+}
+
+impl<'a, T: Trait> InputValuesForExistingProperties<'a, T> {
+    /// Create `InputValuesForExistingProperties` helper structure from provided `property_values` and their corresponding `Class` properties.
+    /// Throws an error, when `Class` `Property` under `property_id`, corresponding to provided `property_value` not found
+    pub fn from(
+        properties: &'a [Property<T>],
+        property_values: &'a BTreeMap<PropertyId, InputPropertyValue<T>>,
+    ) -> Result<Self, Error<T>> {
+        let mut values_for_existing_properties = InputValuesForExistingProperties::<T>::default();
+        for (&property_id, property_value) in property_values {
+            let property = properties
+                .get(property_id as usize)
+                .ok_or(Error::<T>::ClassPropertyNotFound)?;
+            values_for_existing_properties.insert(
+                property_id,
+                InputValueForExistingProperty::new(property, property_value),
+            );
+        }
+        Ok(values_for_existing_properties)
+    }
+}
+
+/// Wrapper for existing `StoredPropertyValue` and its respective `Class` `Property`
+pub struct StoredValueForExistingProperty<'a, T: Trait>(
+    &'a Property<T>,
+    &'a StoredPropertyValue<T>,
+);
+
+impl<'a, T: Trait> StoredValueForExistingProperty<'a, T> {
+    /// Create single instance of `StoredValueForExistingProperty` from provided `property` and `value`
+    pub fn new(property: &'a Property<T>, value: &'a StoredPropertyValue<T>) -> Self {
+        Self(property, value)
+    }
+
+    /// Retrieve `Property` reference
+    pub fn get_property(&self) -> &Property<T> {
+        self.0
+    }
+
+    /// Retrieve `StoredPropertyValue` reference
+    pub fn get_value(&self) -> &StoredPropertyValue<T> {
+        self.1
+    }
+
+    /// Retrieve `Property` and `StoredPropertyValue` references
+    pub fn unzip(&self) -> (&Property<T>, &StoredPropertyValue<T>) {
+        (self.0, self.1)
+    }
+
+    /// Check if Property is default and non `required`
+    pub fn is_default(&self) -> bool {
+        let (property, property_value) = self.unzip();
+        !property.required && *property_value == StoredPropertyValue::<T>::default()
+    }
+}
+
+/// Mapping, used to represent `PropertyId` relation to its respective `StoredValuesForExistingProperties` structure
+pub struct StoredValuesForExistingProperties<'a, T: Trait>(
+    BTreeMap<PropertyId, StoredValueForExistingProperty<'a, T>>,
+);
+
+impl<'a, T: Trait> Default for StoredValuesForExistingProperties<'a, T> {
+    fn default() -> Self {
+        Self(BTreeMap::default())
+    }
+}
+
+impl<'a, T: Trait> Deref for StoredValuesForExistingProperties<'a, T> {
+    type Target = BTreeMap<PropertyId, StoredValueForExistingProperty<'a, T>>;
+
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
+impl<'a, T: Trait> DerefMut for StoredValuesForExistingProperties<'a, T> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.0
+    }
+}
+
+impl<'a, T: Trait> StoredValuesForExistingProperties<'a, T> {
+    /// Create `StoredValuesForExistingProperties` helper structure from provided `property_values` and their corresponding `Class` properties.
+    pub fn from(
+        properties: &'a [Property<T>],
+        property_values: &'a BTreeMap<PropertyId, StoredPropertyValue<T>>,
+    ) -> Result<Self, Error<T>> {
+        let mut values_for_existing_properties = StoredValuesForExistingProperties::<T>::default();
+
+        for (&property_id, property_value) in property_values {
+            let property = properties
+                .get(property_id as usize)
+                .ok_or(Error::<T>::ClassPropertyNotFound)?;
+            values_for_existing_properties.insert(
+                property_id,
+                StoredValueForExistingProperty::new(property, property_value),
+            );
+        }
+        Ok(values_for_existing_properties)
+    }
+
+    /// Used to compute hashes from `StoredPropertyValue`s and their respective property ids, which respective `Properties` have `unique` flag set
+    /// (skip `PropertyId`s, which respective `property values` under this `Entity` are default and non `required`)
+    pub fn compute_unique_hashes(&self) -> BTreeMap<PropertyId, T::Hash> {
+        self.iter()
+            .filter(|(_, value_for_property)| {
+                // skip `PropertyId`s, which respective `property values` under this `Entity` are default and non `required`
+                value_for_property.get_property().unique && !value_for_property.is_default()
+            })
+            .map(|(&property_id, property_value)| {
+                (
+                    property_id,
+                    property_value.get_value().compute_unique_hash(property_id),
+                )
+            })
+            .collect()
+    }
+}
+
+/// Length constraint for input validation
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
+#[derive(Encode, Decode, Default, Clone, Copy, PartialEq, Eq)]
+pub struct InputValidationLengthConstraint {
+    /// Minimum length
+    min: u16,
+
+    /// Difference between minimum length and max length.
+    /// While having max would have been more direct, this
+    /// way makes max < min unrepresentable semantically,
+    /// which is safer.
+    max_min_diff: u16,
+}
+
+impl InputValidationLengthConstraint {
+    /// Create new `InputValidationLengthConstraint` constraint
+    pub const fn new(min: u16, max_min_diff: u16) -> Self {
+        Self { min, max_min_diff }
+    }
+
+    /// Helper for computing max
+    pub fn max(self) -> u16 {
+        self.min + self.max_min_diff
+    }
+
+    /// Retrieve min length value
+    pub fn min(self) -> u16 {
+        self.min
+    }
+
+    /// Ensure length is valid
+    pub fn ensure_valid<T: Trait>(
+        self,
+        len: usize,
+        too_short_msg: Error<T>,
+        too_long_msg: Error<T>,
+    ) -> Result<(), Error<T>> {
+        let length = len as u16;
+        if length < self.min {
+            Err(too_short_msg)
+        } else if length > self.max() {
+            Err(too_long_msg)
+        } else {
+            Ok(())
+        }
+    }
+}
+
+/// Enum, used to specify, which mode of operation should be chosen
+#[derive(Clone, PartialEq, Eq, Copy)]
+pub enum DeltaMode {
+    Increment,
+    Decrement,
+}
+
+impl Default for DeltaMode {
+    fn default() -> Self {
+        Self::Increment
+    }
+}
+
+/// Representing delta on which respective `InboundReferenceCounter` should be changed.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
+#[derive(Encode, Decode, Default, PartialEq, Eq)]
+pub struct EntityReferenceCounterSideEffect {
+    /// Delta number of all inbound references from another entities
+    pub total: i32,
+    /// Delta number of inbound references from another entities with `SameOwner` flag set
+    pub same_owner: i32,
+}
+
+impl Clone for EntityReferenceCounterSideEffect {
+    fn clone(&self) -> Self {
+        *self
+    }
+}
+
+impl Copy for EntityReferenceCounterSideEffect {}
+
+impl EntityReferenceCounterSideEffect {
+    /// Create atomic `EntityReferenceCounterSideEffect` instance, based on `same_owner` flag provided and `DeltaMode`
+    pub fn atomic(same_owner: bool, delta_mode: DeltaMode) -> Self {
+        let counter = if let DeltaMode::Increment = delta_mode {
+            1
+        } else {
+            -1
+        };
+
+        if same_owner {
+            Self {
+                total: counter,
+                same_owner: counter,
+            }
+        } else {
+            Self {
+                total: counter,
+                same_owner: 0,
+            }
+        }
+    }
+}
+
+impl AddAssign for EntityReferenceCounterSideEffect {
+    fn add_assign(&mut self, other: EntityReferenceCounterSideEffect) {
+        *self = Self {
+            total: self.total + other.total,
+            same_owner: self.same_owner + other.same_owner,
+        };
+    }
+}
+
+/// The net side effect on a set of entities from some operations.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
+#[derive(Encode, Decode, Clone, PartialEq, Eq)]
+pub struct ReferenceCounterSideEffects<T: Trait>(
+    BTreeMap<T::EntityId, EntityReferenceCounterSideEffect>,
+);
+
+impl<T: Trait> Deref for ReferenceCounterSideEffects<T> {
+    type Target = BTreeMap<T::EntityId, EntityReferenceCounterSideEffect>;
+
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
+impl<T: Trait> DerefMut for ReferenceCounterSideEffects<T> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.0
+    }
+}
+
+impl<T: Trait> Default for ReferenceCounterSideEffects<T> {
+    fn default() -> Self {
+        Self(BTreeMap::default())
+    }
+}
+
+impl<T: Trait> ReferenceCounterSideEffects<T> {
+    /// Updates all the elements of `other` with `Self`
+    pub fn update(mut self, other: Self) -> Self {
+        // Make a set, that includes both self and other entity_id keys
+        let entity_ids: BTreeSet<T::EntityId> = self.keys().chain(other.keys()).copied().collect();
+
+        for entity_id in entity_ids {
+            // If `self` contains value under provided `entity_id`,
+            // increase it on `EntityReferenceCounterSideEffect` value from `other` if exists,
+            // otherwise update `self` entry under provided `entity_id` with `EntityReferenceCounterSideEffect` from `other`
+            match (self.get_mut(&entity_id), other.get(&entity_id)) {
+                (Some(self_entity_rc_side_effect), Some(other_entity_rc_side_effect)) => {
+                    *self_entity_rc_side_effect += *other_entity_rc_side_effect
+                }
+                (_, Some(other_entity_rc_side_effect)) => {
+                    self.insert(entity_id, *other_entity_rc_side_effect);
+                }
+                _ => (),
+            }
+        }
+        self
+    }
+
+    /// Traverse `ReferenceCounterSideEffects`, updating each `Entity` respective reference counters
+    pub fn update_entities_rcs(&self) {
+        self.iter()
+            .for_each(|(entity_id, inbound_reference_counter_delta)| {
+                Module::<T>::update_entity_rc(*entity_id, *inbound_reference_counter_delta);
+            });
+    }
+}

+ 2710 - 0
runtime-modules/content-directory/src/lib.rs

@@ -0,0 +1,2710 @@
+//! # Content Directory Module
+//!
+//! The content directory is an on-chain index of all content and metadata,
+//! and related concepts - such as channels and playlists.
+//!
+//! - [`substrate_content_directory_module::Trait`](./trait.Trait.html)
+//! - [`Call`](./enum.Call.html)
+//! - [`Module`](./struct.Module.html)
+//!
+//! ## Overview
+//!
+//! The content directory provides functions for:
+//!
+//! - Creating/removal and managing curator groups
+//! - Creating classes and managing their permissions
+//! - Adding schemas to the class
+//! - Creating and removal of entities and managing their permissions
+//! - Adding schemas support to the respective class entities
+//! - Transfering entities ownership
+//! - Updating entity property values
+//!
+//! ## Terminology
+//!
+//! ### Class
+//!
+//! - **Class Properties:** All properties that have been used on this class across different class schemas.
+//! Unlikely to be more than roughly 20 properties per class, often less.
+//! For Person, think "height", "weight", etc.
+//!
+//! - **Schemas:**  All schemas, that are available for this class, think v0.0 Person, v.1.0 Person, etc.
+//!
+//! ### Entity
+//!
+//! - **Supported Schemas:**  What schemas under which entity of the respective class is available, think
+//! v.2.0 Person schema for John, v3.0 Person schema for John
+//! Unlikely to be more than roughly 20ish, assuming schemas for a given class eventually stableize,
+//! or that very old schema are eventually removed.
+//!
+//! - **Property Values:**  Values for properties, declared on class level,
+//! that are used in respective Class Entity after adding Schema support.
+//!
+//! ## Interface
+//!
+//! ### Dispatchable Functions
+//!
+//! #### Curator groups
+//!
+//! - `add_curator_group` - Add new curator group to the runtime storage
+//! - `remove_curator_group` - Remove curator group under given `curator_group_id` from runtime storage.
+//! The origin of this call must be a blog owner.
+//! - `set_curator_group_status` - Set activity status for curator group under given `curator_group_id`
+//! - `add_curator_to_group` - Add curator to curator group under given `curator_group_id`
+//! - `remove_curator_from_group` - Remove curator from a given curator group.
+//!
+//! #### Classes
+//!
+//! - `create_class` - Create new class with provided parameters
+//! - `add_maintainer_to_class` - Add curator group under given curator_group_id as class maintainer
+//! - `remove_maintainer_from_class` - Remove curator group under given curator_group_id from class maintainers set
+//! - `update_class_permissions` - Update class permissions under specific class_id
+//! - `add_class_schema` - Create new class schema from existing property ids and new properties
+//! - `update_class_schema_status` - Update schema status  under specific schema_id in class
+//!
+//! #### Entities
+//!
+//! - `create_entity` - Create new entity of respective class
+//! - `remove_entity` - Remove entity under provided entity_id
+//! - `update_entity_permissions` - Update entity permissions
+//! - `add_schema_support_to_entity` - add schema support to entity under given schema_id and provided property values
+//! - `update_entity_property_values` - Update entity property values with provided ones
+//! - `clear_entity_property_vector` - Clear property value vector under given entity_id & in class schema property id
+//! - `remove_at_entity_property_vector` - Remove value at given index_in_property_vector
+//! from property values vector under in_class schema property id
+//! - `insert_at_entity_property_vector` - Insert single input property values at given index in property vector
+//! into  property values vector under in class schema property id
+//!
+//! #### Others
+//!
+//! - `update_entity_creation_voucher` - Update/create new entity creation voucher for given entity controller with individual limit
+//! - `transaction` - This extrinsic allows a batch operation, which is atomic, over the following operations:
+//! **Entity creation**
+//! **Adding schema support to the entity**
+//! **Update property values of the entity**
+//!
+//! ## Usage
+//!
+//! The following example shows how to use the content directory module in your custom module.
+//!
+//! ### Prerequisites
+//!
+//! Import the content directory module into your custom module and derive the module configuration
+//! trait from the content directory trait.
+//!
+//! ### Add curator group
+//!
+//! ```
+//! use frame_support::{decl_module, assert_ok};
+//! use system::{self as system, ensure_signed};
+//!
+//! pub trait Trait: pallet_content_directory::Trait {}
+//!
+//! decl_module! {
+//!     pub struct Module<T: Trait> for enum Call where origin: T::Origin {
+//!         #[weight = 10_000_000]
+//!         pub fn add_curator_group(origin) -> Result<(), &'static str> {
+//!             <pallet_content_directory::Module<T>>::add_curator_group(origin)?;
+//!             Ok(())
+//!         }
+//!     }
+//! }
+//! # fn main() {}
+//! ```
+
+// Ensure we're `no_std` when compiling for Wasm.
+#![cfg_attr(not(feature = "std"), no_std)]
+#![recursion_limit = "256"]
+
+#[cfg(test)]
+mod tests;
+
+mod class;
+mod entity;
+mod errors;
+mod helpers;
+mod mock;
+mod operations;
+mod permissions;
+mod schema;
+
+pub use class::*;
+pub use entity::*;
+pub use errors::*;
+pub use helpers::*;
+pub use operations::*;
+pub use permissions::*;
+pub use schema::*;
+
+use core::hash::Hash;
+use core::ops::AddAssign;
+
+use codec::{Codec, Decode, Encode};
+use frame_support::storage::IterableStorageMap;
+
+use frame_support::{
+    decl_event, decl_module, decl_storage, dispatch::DispatchResult, ensure, traits::Get, Parameter,
+};
+#[cfg(feature = "std")]
+pub use serde::{Deserialize, Serialize};
+use sp_arithmetic::traits::{BaseArithmetic, One, Zero};
+use sp_runtime::traits::{MaybeSerializeDeserialize, Member};
+use sp_std::borrow::ToOwned;
+use sp_std::collections::{btree_map::BTreeMap, btree_set::BTreeSet};
+use sp_std::vec;
+use sp_std::vec::Vec;
+use system::ensure_signed;
+
+pub use errors::Error;
+
+use core::debug_assert;
+
+/// Type, used in diffrent numeric constraints representations
+pub type MaxNumber = u32;
+
+/// Module configuration trait for this Substrate module.
+pub trait Trait: system::Trait + ActorAuthenticator + Clone {
+    /// The overarching event type.
+    type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
+
+    /// Nonce type is used to avoid data race update conditions, when performing property value vector operations
+    type Nonce: Parameter
+        + Member
+        + BaseArithmetic
+        + Codec
+        + Default
+        + Copy
+        + Clone
+        + MaybeSerializeDeserialize
+        + Eq
+        + PartialEq
+        + Ord
+        + From<u32>;
+
+    /// Type of identifier for classes
+    type ClassId: Parameter
+        + Member
+        + BaseArithmetic
+        + Codec
+        + Default
+        + Copy
+        + Clone
+        + Hash
+        + MaybeSerializeDeserialize
+        + Eq
+        + PartialEq
+        + Ord;
+
+    /// Type of identifier for entities
+    type EntityId: Parameter
+        + Member
+        + BaseArithmetic
+        + Codec
+        + Default
+        + Copy
+        + Clone
+        + Hash
+        + MaybeSerializeDeserialize
+        + Eq
+        + PartialEq
+        + Ord;
+
+    /// Security/configuration constraints
+
+    /// Type, representing min & max property name length constraints
+    type PropertyNameLengthConstraint: Get<InputValidationLengthConstraint>;
+
+    /// Type, representing min & max property description length constraints
+    type PropertyDescriptionLengthConstraint: Get<InputValidationLengthConstraint>;
+
+    /// Type, representing min & max class name length constraints
+    type ClassNameLengthConstraint: Get<InputValidationLengthConstraint>;
+
+    /// Type, representing min & max class description length constraints
+    type ClassDescriptionLengthConstraint: Get<InputValidationLengthConstraint>;
+
+    /// The maximum number of classes
+    type MaxNumberOfClasses: Get<MaxNumber>;
+
+    /// The maximum number of maintainers per class constraint
+    type MaxNumberOfMaintainersPerClass: Get<MaxNumber>;
+
+    /// The maximum number of curators per group constraint
+    type MaxNumberOfCuratorsPerGroup: Get<MaxNumber>;
+
+    /// The maximum number of schemas per class constraint
+    type MaxNumberOfSchemasPerClass: Get<MaxNumber>;
+
+    /// The maximum number of properties per class constraint
+    type MaxNumberOfPropertiesPerSchema: Get<MaxNumber>;
+
+    /// The maximum number of operations during single invocation of `transaction`
+    type MaxNumberOfOperationsDuringAtomicBatching: Get<MaxNumber>;
+
+    /// The maximum length of vector property value constarint
+    type VecMaxLengthConstraint: Get<VecMaxLength>;
+
+    /// The maximum length of text property value constarint
+    type TextMaxLengthConstraint: Get<TextMaxLength>;
+
+    /// The maximum length of text, that will be hashed property value constarint
+    type HashedTextMaxLengthConstraint: Get<HashedTextMaxLength>;
+
+    /// Entities creation constraint per class
+    type MaxNumberOfEntitiesPerClass: Get<Self::EntityId>;
+
+    /// Entities creation constraint per individual
+    type IndividualEntitiesCreationLimit: Get<Self::EntityId>;
+}
+
+decl_storage! {
+    trait Store for Module<T: Trait> as ContentDirectory {
+
+        /// Map, representing ClassId -> Class relation
+        pub ClassById get(fn class_by_id): map hasher(blake2_128_concat) T::ClassId => Class<T>;
+
+        /// Map, representing EntityId -> Entity relation
+        pub EntityById get(fn entity_by_id): map hasher(blake2_128_concat) T::EntityId => Entity<T>;
+
+        /// Map, representing  CuratorGroupId -> CuratorGroup relation
+        pub CuratorGroupById get(fn curator_group_by_id) config(): map hasher(blake2_128_concat) T::CuratorGroupId => CuratorGroup<T>;
+
+        /// Mapping of class id and its property id to the respective entity id and property value hash.
+        pub UniquePropertyValueHashes get(fn unique_property_value_hashes): double_map hasher(blake2_128_concat) (T::ClassId, PropertyId), hasher(blake2_128_concat) T::Hash => ();
+
+        /// Next runtime storage values used to maintain next id value, used on creation of respective curator groups, classes and entities
+
+        pub NextClassId get(fn next_class_id) config(): T::ClassId;
+
+        pub NextEntityId get(fn next_entity_id) config(): T::EntityId;
+
+        pub NextCuratorGroupId get(fn next_curator_group_id) config(): T::CuratorGroupId;
+
+        // The voucher associated with entity creation for a given class and controller.
+        // Is updated whenever an entity is created in a given class by a given controller.
+        // Constraint is updated by Root, an initial value comes from `ClassPermissions::default_entity_creation_voucher_upper_bound`.
+        pub EntityCreationVouchers get(fn entity_creation_vouchers):
+            double_map hasher(blake2_128_concat) T::ClassId, hasher(blake2_128_concat) EntityController<T> => EntityCreationVoucher<T>;
+    }
+}
+
+decl_module! {
+    pub struct Module<T: Trait> for enum Call where origin: T::Origin {
+
+        // ======
+        // Next set of extrinsics can only be invoked by lead.
+        // ======
+
+        /// Initializing events
+        fn deposit_event() = default;
+
+        /// Add new curator group to runtime storage
+        #[weight = 10_000_000] // TODO: adjust weight
+        pub fn add_curator_group(
+            origin,
+        ) -> DispatchResult {
+
+            // Ensure given origin is lead
+            ensure_is_lead::<T>(origin)?;
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            let curator_group_id = Self::next_curator_group_id();
+
+            // Insert empty curator group with `active` parameter set to false
+            <CuratorGroupById<T>>::insert(curator_group_id, CuratorGroup::<T>::default());
+
+            // Increment the next curator curator_group_id:
+            <NextCuratorGroupId<T>>::mutate(|n| *n += T::CuratorGroupId::one());
+
+            // Trigger event
+            Self::deposit_event(RawEvent::CuratorGroupAdded(curator_group_id));
+            Ok(())
+        }
+
+        /// Remove curator group under given `curator_group_id` from runtime storage
+        #[weight = 10_000_000] // TODO: adjust weight
+        pub fn remove_curator_group(
+            origin,
+            curator_group_id: T::CuratorGroupId,
+        ) -> DispatchResult {
+
+            // Ensure given origin is lead
+            ensure_is_lead::<T>(origin)?;
+
+            // Ensure CuratorGroup under given curator_group_id exists
+            let curator_group = Self::ensure_curator_group_exists(&curator_group_id)?;
+
+            // We should previously ensure that curator_group  maintains no classes to be able to remove it
+            curator_group.ensure_curator_group_maintains_no_classes()?;
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+
+            // Remove curator group under given curator group id from runtime storage
+            <CuratorGroupById<T>>::remove(curator_group_id);
+
+            // Trigger event
+            Self::deposit_event(RawEvent::CuratorGroupRemoved(curator_group_id));
+            Ok(())
+        }
+
+        /// Set `is_active` status for curator group under given `curator_group_id`
+        #[weight = 10_000_000] // TODO: adjust weight
+        pub fn set_curator_group_status(
+            origin,
+            curator_group_id: T::CuratorGroupId,
+            is_active: bool,
+        ) -> DispatchResult {
+
+            // Ensure given origin is lead
+            ensure_is_lead::<T>(origin)?;
+
+            // Ensure curator group under provided curator_group_id already exist
+            Self::ensure_curator_group_under_given_id_exists(&curator_group_id)?;
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            // Set `is_active` status for curator group under given `curator_group_id`
+            <CuratorGroupById<T>>::mutate(curator_group_id, |curator_group| {
+                curator_group.set_status(is_active)
+            });
+
+            // Trigger event
+            Self::deposit_event(RawEvent::CuratorGroupStatusSet(curator_group_id, is_active));
+            Ok(())
+        }
+
+        /// Add curator to curator group under given `curator_group_id`
+        #[weight = 10_000_000] // TODO: adjust weight
+        pub fn add_curator_to_group(
+            origin,
+            curator_group_id: T::CuratorGroupId,
+            curator_id: T::CuratorId,
+        ) -> DispatchResult {
+
+            // Ensure given origin is lead
+            ensure_is_lead::<T>(origin)?;
+
+            // Ensure curator group under provided curator_group_id already exist, retrieve corresponding one
+            let curator_group = Self::ensure_curator_group_exists(&curator_group_id)?;
+
+            // Ensure max number of curators per group limit not reached yet
+            curator_group.ensure_max_number_of_curators_limit_not_reached()?;
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            // Insert curator_id into curator_group under given curator_group_id
+            <CuratorGroupById<T>>::mutate(curator_group_id, |curator_group| {
+                curator_group.get_curators_mut().insert(curator_id);
+            });
+
+            // Trigger event
+            Self::deposit_event(RawEvent::CuratorAdded(curator_group_id, curator_id));
+            Ok(())
+        }
+
+        /// Remove curator from a given curator group
+        #[weight = 10_000_000] // TODO: adjust weight
+        pub fn remove_curator_from_group(
+            origin,
+            curator_group_id: T::CuratorGroupId,
+            curator_id: T::CuratorId,
+        ) -> DispatchResult {
+
+            // Ensure given origin is lead
+            ensure_is_lead::<T>(origin)?;
+
+            // Ensure curator group under provided curator_group_id already exist, retrieve corresponding one
+            let curator_group = Self::ensure_curator_group_exists(&curator_group_id)?;
+
+            // Ensure curator under provided curator_id is CuratorGroup member
+            curator_group.ensure_curator_in_group_exists(&curator_id)?;
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            // Remove curator_id from curator_group under given curator_group_id
+            <CuratorGroupById<T>>::mutate(curator_group_id, |curator_group| {
+                curator_group.get_curators_mut().remove(&curator_id);
+            });
+
+            // Trigger event
+            Self::deposit_event(RawEvent::CuratorRemoved(curator_group_id, curator_id));
+            Ok(())
+        }
+
+        /// Updates or creates new `EntityCreationVoucher` for given `EntityController` with individual limit
+        #[weight = 10_000_000] // TODO: adjust weight
+        pub fn update_entity_creation_voucher(
+            origin,
+            class_id: T::ClassId,
+            controller: EntityController<T>,
+            maximum_entities_count: T::EntityId
+        ) -> DispatchResult {
+
+            // Ensure given origin is lead
+            ensure_is_lead::<T>(origin)?;
+
+            // Ensure Class under given id exists, return corresponding one
+            Self::ensure_known_class_id(class_id)?;
+
+            // Ensure maximum_entities_count does not exceed individual entities creation limit
+            Self::ensure_valid_number_of_class_entities_per_actor_constraint(maximum_entities_count)?;
+
+            // Check voucher existance
+            let voucher_exists = <EntityCreationVouchers<T>>::contains_key(class_id, &controller);
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            if voucher_exists {
+
+                // Set new maximum_entities_count limit for selected voucher
+                let mut entity_creation_voucher = Self::entity_creation_vouchers(class_id, &controller);
+
+                entity_creation_voucher.set_maximum_entities_count(maximum_entities_count);
+
+                <EntityCreationVouchers<T>>::insert(class_id, controller.clone(), entity_creation_voucher.clone());
+
+                // Trigger event
+                Self::deposit_event(RawEvent::EntityCreationVoucherUpdated(controller, entity_creation_voucher))
+            } else {
+                // Create new EntityCreationVoucher instance with provided maximum_entities_count
+                let entity_creation_voucher = EntityCreationVoucher::new(maximum_entities_count);
+
+                // Add newly created `EntityCreationVoucher` into `EntityCreationVouchers`
+                // runtime storage under given `class_id`, `controller` key
+                <EntityCreationVouchers<T>>::insert(class_id, controller.clone(), entity_creation_voucher.clone());
+
+                // Trigger event
+                Self::deposit_event(RawEvent::EntityCreationVoucherCreated(controller, entity_creation_voucher));
+            }
+
+            Ok(())
+        }
+
+        /// Create new `Class` with provided parameters
+        #[weight = 10_000_000] // TODO: adjust weight
+        pub fn create_class(
+            origin,
+            name: Vec<u8>,
+            description: Vec<u8>,
+            class_permissions: ClassPermissions<T>,
+            maximum_entities_count: T::EntityId,
+            default_entity_creation_voucher_upper_bound: T::EntityId
+        ) -> DispatchResult {
+
+            // Ensure given origin is lead
+            ensure_is_lead::<T>(origin)?;
+
+            // Ensure, that all entities creation limits, defined for a given Class, are valid
+            Self::ensure_entities_creation_limits_are_valid(maximum_entities_count, default_entity_creation_voucher_upper_bound)?;
+
+            // Ensure max number of classes limit not reached
+            Self::ensure_class_limit_not_reached()?;
+
+            // Ensure ClassNameLengthConstraint conditions satisfied
+            Self::ensure_class_name_is_valid(&name)?;
+
+            // Ensure ClassDescriptionLengthConstraint conditions satisfied
+            Self::ensure_class_description_is_valid(&description)?;
+
+            // Perform required checks to ensure class_maintainers under provided class_permissions are valid
+            let class_maintainers = class_permissions.get_maintainers();
+            Self::ensure_class_maintainers_are_valid(class_maintainers)?;
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            // Create new Class instance from provided values
+            let class = Class::new(
+                class_permissions, name, description, maximum_entities_count, default_entity_creation_voucher_upper_bound
+            );
+
+            let class_id = Self::next_class_id();
+
+            // Add new `Class` to runtime storage
+            <ClassById<T>>::insert(&class_id, class);
+
+            // Increment the next class id:
+            <NextClassId<T>>::mutate(|n| *n += T::ClassId::one());
+
+            // Trigger event
+            Self::deposit_event(RawEvent::ClassCreated(class_id));
+            Ok(())
+        }
+
+        /// Add curator group under given `curator_group_id` as `Class` maintainer
+        #[weight = 10_000_000] // TODO: adjust weight
+        pub fn add_maintainer_to_class(
+            origin,
+            class_id: T::ClassId,
+            curator_group_id: T::CuratorGroupId,
+        ) -> DispatchResult {
+
+            // Ensure given origin is lead
+            ensure_is_lead::<T>(origin)?;
+
+            // Ensure Class under provided class_id exist, retrieve corresponding one
+            let class = Self::ensure_known_class_id(class_id)?;
+
+            // Ensure CuratorGroup under provided curator_group_id exist, retrieve corresponding one
+            Self::ensure_curator_group_under_given_id_exists(&curator_group_id)?;
+
+            // Ensure the max number of maintainers per Class limit not reached
+            let class_permissions = class.get_permissions_ref();
+
+            // Ensure max number of maintainers per Class constraint satisfied
+            Self::ensure_maintainers_limit_not_reached(class_permissions.get_maintainers())?;
+
+            // Ensure maintainer under provided curator_group_id is not added to the Class maintainers set yet
+            class_permissions.ensure_maintainer_does_not_exist(&curator_group_id)?;
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            // Insert `curator_group_id` into `maintainers` set, associated with given `Class`
+            <ClassById<T>>::mutate(class_id, |class|
+                class.get_permissions_mut().get_maintainers_mut().insert(curator_group_id)
+            );
+
+            // Increment the number of classes, curator group under given `curator_group_id` maintains
+            <CuratorGroupById<T>>::mutate(curator_group_id, |curator_group| {
+                curator_group.increment_number_of_classes_maintained_count();
+            });
+
+            // Trigger event
+            Self::deposit_event(RawEvent::MaintainerAdded(class_id, curator_group_id));
+            Ok(())
+        }
+
+        /// Remove curator group under given `curator_group_id` from `Class` maintainers set
+        #[weight = 10_000_000] // TODO: adjust weight
+        pub fn remove_maintainer_from_class(
+            origin,
+            class_id: T::ClassId,
+            curator_group_id: T::CuratorGroupId,
+        ) -> DispatchResult {
+
+            // Ensure given origin is lead
+            ensure_is_lead::<T>(origin)?;
+
+            // Ensure Class under given id exists, return corresponding one
+            let class = Self::ensure_known_class_id(class_id)?;
+
+            // Ensure maintainer under provided curator_group_id was previously added
+            // to the maintainers set, associated with corresponding Class
+            class.get_permissions_ref().ensure_maintainer_exists(&curator_group_id)?;
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            // Remove `curator_group_id` from `maintainers` set, associated with given `Class`
+            <ClassById<T>>::mutate(class_id, |class|
+                class.get_permissions_mut().get_maintainers_mut().remove(&curator_group_id)
+            );
+
+            // Decrement the number of classes, curator group under given `curator_group_id` maintains
+            <CuratorGroupById<T>>::mutate(curator_group_id, |curator_group| {
+                curator_group.decrement_number_of_classes_maintained_count();
+            });
+
+            // Trigger event
+            Self::deposit_event(RawEvent::MaintainerRemoved(class_id, curator_group_id));
+            Ok(())
+        }
+
+        /// Update `ClassPermissions` under specific `class_id`
+        #[weight = 10_000_000] // TODO: adjust weight
+        pub fn update_class_permissions(
+            origin,
+            class_id: T::ClassId,
+            updated_any_member: Option<bool>,
+            updated_entity_creation_blocked: Option<bool>,
+            updated_all_entity_property_values_locked: Option<bool>,
+            updated_maintainers: Option<BTreeSet<T::CuratorGroupId>>,
+        ) -> DispatchResult {
+
+            // Ensure given origin is lead
+            ensure_is_lead::<T>(origin)?;
+
+            // Ensure Class under given id exists, return corresponding one
+            let class = Self::ensure_known_class_id(class_id)?;
+
+            // Perform required checks to ensure class_maintainers are valid
+            if let Some(ref updated_maintainers) = updated_maintainers {
+                Self::ensure_class_maintainers_are_valid(updated_maintainers)?;
+            }
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            let class_permissions = class.get_permissions();
+
+            // Make updated class_permissions from parameters provided
+            let updated_class_permissions = Self::make_updated_class_permissions(
+                class_permissions, updated_any_member, updated_entity_creation_blocked,
+                updated_all_entity_property_values_locked, updated_maintainers
+            );
+
+            // If class_permissions update has been performed
+            if let Some(updated_class_permissions) = updated_class_permissions  {
+
+                // Update `class_permissions` under given class id
+                <ClassById<T>>::mutate(class_id, |class| {
+                    class.update_permissions(updated_class_permissions)
+                });
+
+                // Trigger event
+                Self::deposit_event(RawEvent::ClassPermissionsUpdated(class_id));
+            }
+
+            Ok(())
+        }
+
+        /// Create new class schema from existing property ids and new properties
+        #[weight = 10_000_000] // TODO: adjust weight
+        pub fn add_class_schema(
+            origin,
+            class_id: T::ClassId,
+            existing_properties: BTreeSet<PropertyId>,
+            new_properties: Vec<Property<T>>
+        ) -> DispatchResult {
+
+            // Ensure given origin is lead
+            ensure_is_lead::<T>(origin)?;
+
+            // Ensure Class under given id exists, return corresponding one
+            let class = Self::ensure_known_class_id(class_id)?;
+
+            // Ensure Schemas limit per Class not reached
+            class.ensure_schemas_limit_not_reached()?;
+
+            // Ensure both existing and new properties for future Schema are not empty
+            Self::ensure_non_empty_schema(&existing_properties, &new_properties)?;
+
+            // Ensure max number of properties per Schema limit not reached
+            class.ensure_properties_limit_not_reached(&new_properties)?;
+
+            // Complete all checks to ensure all provided new_properties are valid
+            Self::ensure_all_properties_are_valid(&new_properties)?;
+
+            // Id of next Class Schema being added
+            let schema_id = class.get_schemas().len() as SchemaId;
+
+            let class_properties = class.get_properties();
+
+            // Ensure all Property names are unique within Class
+            Self::ensure_all_property_names_are_unique(&class_properties, &new_properties)?;
+
+            // Ensure existing_properties are valid indices of properties, corresponding to chosen Class
+            Self::ensure_schema_properties_are_valid_indices(&existing_properties, &class_properties)?;
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            // Create `Schema` instance from existing and new property ids
+            let schema = Self::create_class_schema(existing_properties, &class_properties, &new_properties);
+
+            // Update class properties after new `Schema` added
+            let updated_class_properties = Self::make_updated_class_properties(class_properties, new_properties);
+
+            // Update Class properties and schemas
+            <ClassById<T>>::mutate(class_id, |class| {
+                class.set_properties(updated_class_properties);
+                class.get_schemas_mut().push(schema);
+            });
+
+            // Trigger event
+            Self::deposit_event(RawEvent::ClassSchemaAdded(class_id, schema_id));
+
+            Ok(())
+        }
+
+        /// Update `schema_status` under specific `schema_id` in `Class`
+        #[weight = 10_000_000] // TODO: adjust weight
+        pub fn update_class_schema_status(
+            origin,
+            class_id: T::ClassId,
+            schema_id: SchemaId,
+            schema_status: bool
+        ) -> DispatchResult {
+
+            // Ensure given origin is lead
+            ensure_is_lead::<T>(origin)?;
+
+            // Ensure Class under given id exists, return corresponding one
+            let class = Self::ensure_known_class_id(class_id)?;
+
+            // Ensure Class already contains schema under provided schema_id
+            class.ensure_schema_id_exists(schema_id)?;
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            // Update class schema status
+            <ClassById<T>>::mutate(class_id, |class| {
+                class.update_schema_status(schema_id, schema_status)
+            });
+
+            // Trigger event
+            Self::deposit_event(RawEvent::ClassSchemaStatusUpdated(class_id, schema_id, schema_status));
+            Ok(())
+        }
+
+        /// Update entity permissions
+        #[weight = 10_000_000] // TODO: adjust weight
+        pub fn update_entity_permissions(
+            origin,
+            entity_id: T::EntityId,
+            updated_frozen: Option<bool>,
+            updated_referenceable: Option<bool>
+        ) -> DispatchResult {
+
+            // Ensure given origin is lead
+            ensure_is_lead::<T>(origin)?;
+
+            // Ensure Entity under given id exists, return corresponding one
+            let entity = Self::ensure_known_entity_id(entity_id)?;
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            // Make updated entity_permissions from parameters provided
+            let entity_permissions = entity.get_permissions();
+
+            let updated_entity_permissions =
+                Self::make_updated_entity_permissions(entity_permissions, updated_frozen, updated_referenceable);
+
+            // Update entity permissions under given entity id
+            if let Some(updated_entity_permissions) = updated_entity_permissions {
+
+                <EntityById<T>>::mutate(entity_id, |entity| {
+                    entity.update_permissions(updated_entity_permissions)
+                });
+
+                // Trigger event
+                Self::deposit_event(RawEvent::EntityPermissionsUpdated(entity_id));
+            }
+            Ok(())
+        }
+
+        /// Transfer ownership to new `EntityController` for `Entity` under given `entity_id`
+        /// `new_property_value_references_with_same_owner_flag_set` should be provided manually
+        #[weight = 10_000_000] // TODO: adjust weight
+        pub fn transfer_entity_ownership(
+            origin,
+            entity_id: T::EntityId,
+            new_controller: EntityController<T>,
+            new_property_value_references_with_same_owner_flag_set: BTreeMap<PropertyId, InputPropertyValue<T>>
+        ) -> DispatchResult {
+
+            // Ensure given origin is lead
+            ensure_is_lead::<T>(origin)?;
+
+            // Ensure Entity under given entity_id exists, retrieve corresponding Entity & Class
+            let (entity, class) = Self::ensure_known_entity_and_class(entity_id)?;
+
+            // Ensure provided new_entity_controller is not equal to current one
+            entity.get_permissions_ref().ensure_controllers_are_not_equal(&new_controller)?;
+
+            // Ensure any inbound InputPropertyValue::Reference with same_owner flag set points to the given Entity
+            entity.ensure_inbound_same_owner_rc_is_zero()?;
+
+            let class_properties = class.get_properties();
+
+            let class_id = entity.get_class_id();
+
+            let entity_property_values = entity.get_values();
+
+            // Create wrapper structure from provided entity_property_values and their corresponding Class properties
+            let values_for_existing_properties = match StoredValuesForExistingProperties::from(&class_properties, &entity_property_values) {
+                Ok(values_for_existing_properties) => values_for_existing_properties,
+                Err(e) => {
+                    debug_assert!(false, "Should not fail! {:?}", e);
+                    return Err(e.into())
+                }
+            };
+
+            // Filter provided values_for_existing_properties, leaving only `Reference`'s with `SameOwner` flag set
+            // Retrieve the set of corresponding property ids
+            let entity_property_id_references_with_same_owner_flag_set =
+                Self::get_property_id_references_with_same_owner_flag_set(values_for_existing_properties);
+
+            // Ensure all ids of provided `new_property_value_references_with_same_owner_flag_set`
+            // corresponding to property ids of respective Class Property references with same owner flag set
+            Self::ensure_only_reference_ids_with_same_owner_flag_set_provided(
+                &entity_property_id_references_with_same_owner_flag_set,
+                &new_property_value_references_with_same_owner_flag_set
+            )?;
+
+            // Retrieve ids of all entity property values, that are references with same owner flag set and which are not provided
+            // in new property value references with same owner flag set
+            let unused_property_id_references_with_same_owner_flag_set = Self::compute_unused_property_ids(
+                &new_property_value_references_with_same_owner_flag_set, &entity_property_id_references_with_same_owner_flag_set
+            );
+
+            // Perform checks to ensure all required property_values under provided unused_schema_property_ids provided
+            Self::ensure_all_required_properties_provided(&class_properties, &unused_property_id_references_with_same_owner_flag_set)?;
+
+            // Create wrapper structure from provided new_property_value_references_with_same_owner_flag_set and their corresponding Class properties
+            let new_values_for_existing_properties = InputValuesForExistingProperties::from(
+                &class_properties, &new_property_value_references_with_same_owner_flag_set
+            )?;
+
+            // Ensure all provided `new_property_value_references_with_same_owner_flag_set` are valid
+            Self::ensure_are_valid_references_with_same_owner_flag_set(
+                new_values_for_existing_properties, &new_controller
+            )?;
+
+            let new_output_property_value_references_with_same_owner_flag_set = Self::make_output_property_values(new_property_value_references_with_same_owner_flag_set);
+
+            // Compute StoredPropertyValues, which respective Properties have unique flag set
+            // (skip PropertyIds, which respective property values under this Entity are default and non required)
+            let new_output_values_for_existing_properties =
+                StoredValuesForExistingProperties::from(&class_properties, &new_output_property_value_references_with_same_owner_flag_set)?;
+
+            // Compute new unique property value hashes.
+            // Ensure new property value hashes with `unique` flag set are `unique` on `Class` level
+            let new_unique_hashes = Self::ensure_new_property_values_respect_uniquness(
+                class_id, new_output_values_for_existing_properties,
+            )?;
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            // Used to compute old unique hashes, that should be substituted with new ones.
+            let old_unique_hashes =
+                Self::compute_old_unique_hashes(&new_output_property_value_references_with_same_owner_flag_set, &entity_property_values);
+
+            // Add property values, that should be unique on Class level
+            Self::add_unique_property_value_hashes(class_id, new_unique_hashes);
+
+            // Remove unique hashes, that were substituted with new ones.
+            Self::remove_unique_property_value_hashes(class_id, old_unique_hashes);
+
+            // Make updated entity_property_values from parameters provided
+            let entity_property_values_updated =
+                    Self::make_updated_property_value_references_with_same_owner_flag_set(
+                        unused_property_id_references_with_same_owner_flag_set, &entity_property_values,
+                        &new_output_property_value_references_with_same_owner_flag_set,
+                    );
+
+            // Transfer entity ownership
+            let entities_inbound_rcs_delta = if let Some(entity_property_values_updated) = entity_property_values_updated {
+
+
+                // Calculate entities reference counter side effects for current operation
+                let entities_inbound_rcs_delta =
+                    Self::get_updated_inbound_rcs_delta(
+                        entity_id, class_properties, entity_property_values, new_output_property_value_references_with_same_owner_flag_set
+                    )?;
+
+                // Update InboundReferenceCounter, based on previously calculated ReferenceCounterSideEffects, for each Entity involved
+                Self::update_entities_rcs(&entities_inbound_rcs_delta);
+
+                <EntityById<T>>::mutate(entity_id, |entity| {
+
+                    // Update current Entity property values with updated ones
+                    entity.set_values(entity_property_values_updated);
+
+                    // Set up new controller for the current Entity instance
+                    entity.get_permissions_mut().set_conroller(new_controller.clone());
+                });
+
+                entities_inbound_rcs_delta
+            } else {
+                // Set up new controller for the current Entity instance
+                <EntityById<T>>::mutate(entity_id, |entity| {
+                    entity.get_permissions_mut().set_conroller(new_controller.clone());
+                });
+
+                None
+            };
+
+            // Trigger event
+            Self::deposit_event(RawEvent::EntityOwnershipTransfered(entity_id, new_controller, entities_inbound_rcs_delta));
+
+            Ok(())
+        }
+
+        // ======
+        // The next set of extrinsics can be invoked by anyone who can properly sign for provided value of `Actor<T>`.
+        // ======
+
+        /// Create entity.
+        /// If someone is making an entity of this class for first time,
+        /// then a voucher is also added with the class limit as the default limit value.
+        #[weight = 10_000_000] // TODO: adjust weight
+        pub fn create_entity(
+            origin,
+            class_id: T::ClassId,
+            actor: Actor<T>,
+        ) -> DispatchResult {
+
+            let account_id = ensure_signed(origin)?;
+
+            // Ensure Class under given id exists, return corresponding one
+            let class = Self::ensure_class_exists(class_id)?;
+
+            // Ensure maximum entities limit per class not reached
+            class.ensure_maximum_entities_count_limit_not_reached()?;
+
+            let class_permissions = class.get_permissions_ref();
+
+            // Ensure entities creation is not blocked on Class level
+            class_permissions.ensure_entity_creation_not_blocked()?;
+
+            // Ensure actor can create entities
+            class_permissions.ensure_can_create_entities(&account_id, &actor)?;
+
+            let entity_controller = EntityController::from_actor(&actor);
+
+            // Check if entity creation voucher exists
+            let voucher_exists = if <EntityCreationVouchers<T>>::contains_key(class_id, &entity_controller) {
+
+                // Ensure voucher limit not reached
+                Self::entity_creation_vouchers(class_id, &entity_controller).ensure_voucher_limit_not_reached()?;
+                true
+            } else {
+                false
+            };
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            // Create voucher, update if exists
+
+            if voucher_exists {
+
+                // Increment number of created entities count, if specified voucher already exist
+                <EntityCreationVouchers<T>>::mutate(class_id, &entity_controller, |entity_creation_voucher| {
+                    entity_creation_voucher.increment_created_entities_count()
+                });
+            } else {
+
+                // Create new voucher for given entity creator with default limit
+                let mut entity_creation_voucher = EntityCreationVoucher::new(class.get_default_entity_creation_voucher_upper_bound());
+
+                // Increase created entities count by 1 to maintain valid entity_creation_voucher state after following Entity added
+                entity_creation_voucher.increment_created_entities_count();
+                <EntityCreationVouchers<T>>::insert(class_id, entity_controller.clone(), entity_creation_voucher);
+            }
+
+            // Create new entity
+
+            let entity_id = Self::next_entity_id();
+
+            let new_entity = Entity::<T>::new(
+                entity_controller,
+                class_id,
+                BTreeSet::new(),
+                BTreeMap::new(),
+            );
+
+            // Save newly created entity:
+            EntityById::insert(entity_id, new_entity);
+
+            // Increment the next entity id:
+            <NextEntityId<T>>::mutate(|n| *n += T::EntityId::one());
+
+            // Increment number of entities, associated with this class
+            <ClassById<T>>::mutate(class_id, |class| {
+                class.increment_entities_count();
+            });
+
+            // Trigger event
+            Self::deposit_event(RawEvent::EntityCreated(actor, entity_id));
+            Ok(())
+        }
+
+        /// Remove `Entity` under provided `entity_id`
+        #[weight = 10_000_000] // TODO: adjust weight
+        pub fn remove_entity(
+            origin,
+            actor: Actor<T>,
+            entity_id: T::EntityId,
+        ) -> DispatchResult {
+
+            let account_id = ensure_signed(origin)?;
+
+            // Retrieve Class, Entity and EntityAccessLevel for the actor, attemting to perform operation
+            let (class, entity, access_level) = Self::ensure_class_entity_and_access_level(account_id, entity_id, &actor)?;
+
+            // Ensure actor with given EntityAccessLevel can remove entity
+            EntityPermissions::<T>::ensure_group_can_remove_entity(access_level)?;
+
+            // Ensure any inbound InputPropertyValue::Reference points to the given Entity
+            entity.ensure_rc_is_zero()?;
+
+            let class_properties = class.get_properties();
+
+            let class_id = entity.get_class_id();
+
+            let entity_values = entity.get_values();
+
+            let unique_property_value_hashes = match StoredValuesForExistingProperties::from(&class_properties, &entity_values) {
+                Ok(values_for_existing_properties) => values_for_existing_properties.compute_unique_hashes(),
+                Err(e) => {
+                    debug_assert!(false, "Should not fail! {:?}", e);
+                    return Err(e.into())
+                }
+            };
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            // Remove property value entries, that should be unique on Class level
+            Self::remove_unique_property_value_hashes(class_id, unique_property_value_hashes);
+
+            // Remove entity
+            <EntityById<T>>::remove(entity_id);
+
+            // Decrement class entities counter
+            <ClassById<T>>::mutate(class_id, |class| class.decrement_entities_count());
+
+            let entity_controller = EntityController::<T>::from_actor(&actor);
+
+            // Decrement entity_creation_voucher after entity removal perfomed
+            <EntityCreationVouchers<T>>::mutate(class_id, entity_controller, |entity_creation_voucher| {
+                entity_creation_voucher.decrement_created_entities_count();
+            });
+
+            // Trigger event
+            Self::deposit_event(RawEvent::EntityRemoved(actor, entity_id));
+            Ok(())
+        }
+
+        /// Add schema support to entity under given `schema_id` and provided `property_values`
+        #[weight = 10_000_000] // TODO: adjust weight
+        pub fn add_schema_support_to_entity(
+            origin,
+            actor: Actor<T>,
+            entity_id: T::EntityId,
+            schema_id: SchemaId,
+            new_property_values: BTreeMap<PropertyId, InputPropertyValue<T>>
+        ) -> DispatchResult {
+
+            let account_id = ensure_signed(origin)?;
+
+            // Retrieve Class, Entity and EntityAccessLevel for the actor, attemting to perform operation
+            let (class, entity, _) = Self::ensure_class_entity_and_access_level(account_id, entity_id, &actor)?;
+
+            // Ensure Class Schema under given index exists, return corresponding Schema
+            let schema = class.ensure_schema_exists(schema_id)?.to_owned();
+
+            let class_properties = class.get_properties();
+
+            // Create wrapper structure from provided new_property_values and their corresponding Class properties
+            let new_values_for_existing_properties = InputValuesForExistingProperties::from(&class_properties, &new_property_values)?;
+
+            // Ensure Schema under given id is not added to given Entity yet
+            entity.ensure_schema_id_is_not_added(schema_id)?;
+
+            // Ensure provided new_property_values are not added to the Entity values map yet
+            entity.ensure_property_values_are_not_added(&new_property_values)?;
+
+            // Ensure provided schema can be added to the Entity
+            schema.ensure_is_active::<T>()?;
+
+            // Ensure all provided new property values are for properties in the given schema
+            schema.ensure_has_properties(&new_property_values)?;
+
+            // Retrieve Schema property ids, which are not provided in new_property_values
+            let unused_schema_property_ids = Self::compute_unused_property_ids(&new_property_values, schema.get_properties());
+
+            // Perform checks to ensure all required property_values under provided unused_schema_property_ids provided
+            Self::ensure_all_required_properties_provided(&class_properties, &unused_schema_property_ids)?;
+
+            // Ensure all property_values under given Schema property ids are valid
+            let entity_controller = entity.get_permissions_ref().get_controller();
+
+            // Validate all values, provided in new_values_for_existing_properties,
+            // against the type of its Property and check any additional constraints
+            Self::ensure_property_values_are_valid(&entity_controller, &new_values_for_existing_properties)?;
+
+            let class_id = entity.get_class_id();
+
+            let entity_property_values = entity.get_values();
+
+            let new_output_property_values = Self::make_output_property_values(new_property_values);
+
+            // Compute updated entity values, after new schema support added
+            let entity_values_updated = Self::make_updated_entity_property_values(
+                schema, entity_property_values, &new_output_property_values
+            );
+
+            let new_output_values_for_existing_properties = StoredValuesForExistingProperties::from(&class_properties, &new_output_property_values)?;
+
+            // Retrieve StoredPropertyValues, which respective Properties have unique flag set
+            // (skip PropertyIds, which respective property values under this Entity are default and non required)
+            let new_unique_property_value_hashes = new_output_values_for_existing_properties.compute_unique_hashes();
+
+            // Ensure all provided Properties with unique flag set are unique on Class level
+            Self::ensure_property_value_hashes_unique_option_satisfied(class_id, &new_unique_property_value_hashes)?;
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            // Add property value hashes, that should be unique on Class level
+            Self::add_unique_property_value_hashes(class_id, new_unique_property_value_hashes);
+
+            // Calculate entities reference counter side effects for current operation
+            let entities_inbound_rcs_delta = Self::calculate_entities_inbound_rcs_delta(
+                entity_id, new_output_values_for_existing_properties, DeltaMode::Increment
+            );
+
+            // Update InboundReferenceCounter, based on previously calculated entities_inbound_rcs_delta, for each Entity involved
+            Self::update_entities_rcs(&entities_inbound_rcs_delta);
+
+            // Add schema support to `Entity` under given `entity_id`
+            <EntityById<T>>::mutate(entity_id, |entity| {
+
+                // Add a new schema to the list of schemas supported by this entity.
+                entity.get_supported_schemas_mut().insert(schema_id);
+
+                // Update entity values only if new properties have been added.
+                if entity_values_updated.len() > entity.get_values_ref().len() {
+                    entity.set_values(entity_values_updated);
+                }
+            });
+
+            // Trigger event
+            Self::deposit_event(RawEvent::EntitySchemaSupportAdded(actor, entity_id, schema_id, entities_inbound_rcs_delta));
+            Ok(())
+        }
+
+        /// Update `Entity` `InputPropertyValue`'s with provided ones
+        #[weight = 10_000_000] // TODO: adjust weight
+        pub fn update_entity_property_values(
+            origin,
+            actor: Actor<T>,
+            entity_id: T::EntityId,
+            new_property_values: BTreeMap<PropertyId, InputPropertyValue<T>>
+        ) -> DispatchResult {
+
+            let account_id = ensure_signed(origin)?;
+
+            // Retrieve Class, Entity and EntityAccessLevel for the actor, attemting to perform operation
+            let (class, entity, access_level) = Self::ensure_class_entity_and_access_level(account_id, entity_id, &actor)?;
+
+            // Ensure property values were not locked on Class level
+            class.ensure_property_values_unlocked()?;
+
+            let entity_values_ref = entity.get_values_ref();
+
+            // Filter new_property_values, that are identical to entity_property_values.
+            // Get `new_property_values`, that are not in `entity_property_values`
+            let new_property_values = Self::try_filter_identical_property_values(entity_values_ref, new_property_values);
+
+            // Ensure all provided new_property_values are already added to the current Entity instance
+            Self::ensure_all_property_values_are_already_added(entity_values_ref, &new_property_values)?;
+
+            let class_properties = class.get_properties();
+
+            // Create wrapper structure from new_property_values and their corresponding Class properties
+            let new_values_for_existing_properties = InputValuesForExistingProperties::from(&class_properties, &new_property_values)?;
+
+            // Ensure all provided property values are unlocked for the actor with given access_level
+            Self::ensure_all_property_values_are_unlocked_from(&new_values_for_existing_properties, access_level)?;
+
+            let entity_controller = entity.get_permissions_ref().get_controller();
+
+            // Validate all values, provided in values_for_existing_properties,
+            // against the type of its Property and check any additional constraints
+            Self::ensure_property_values_are_valid(&entity_controller, &new_values_for_existing_properties)?;
+
+            let class_id = entity.get_class_id();
+
+            // Get current property values of an Entity
+
+            let entity_property_values = entity.get_values();
+
+            let new_output_property_values = Self::make_output_property_values(new_property_values);
+
+            // Compute StoredPropertyValues, which respective Properties have unique flag set
+            // (skip PropertyIds, which respective property values under this Entity are default and non required)
+            let new_output_values_for_existing_properties =
+                StoredValuesForExistingProperties::from(&class_properties, &new_output_property_values)?;
+
+            // Compute new unique property value hashes.
+            // Ensure new property value hashes with `unique` flag set are `unique` on `Class` level
+            let new_unique_hashes = Self::ensure_new_property_values_respect_uniquness(
+                class_id, new_output_values_for_existing_properties,
+            )?;
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            // Used to compute old unique hashes, that should be substituted with new ones.
+            let old_unique_hashes =
+                Self::compute_old_unique_hashes(&new_output_property_values, &entity_property_values);
+
+            // Add property value hashes, that should be unique on Class level
+            Self::add_unique_property_value_hashes(class_id, new_unique_hashes);
+
+            // Remove unique hashes, that were substituted with new ones. (if some).
+            Self::remove_unique_property_value_hashes(class_id, old_unique_hashes);
+
+            // Make updated entity_property_values from current entity_property_values and new_output_property_values provided
+            let entity_property_values_updated =
+                Self::make_updated_property_values(&entity_property_values, &new_output_property_values);
+
+            // If property values should be updated
+            if let Some(entity_property_values_updated) = entity_property_values_updated {
+
+                // Calculate entities reference counter side effects for current operation (should always be safe)
+                let entities_inbound_rcs_delta =
+                    Self::get_updated_inbound_rcs_delta(entity_id, class_properties, entity_property_values, new_output_property_values)?;
+
+                // Update InboundReferenceCounter, based on previously calculated entities_inbound_rcs_delta, for each Entity involved
+                Self::update_entities_rcs(&entities_inbound_rcs_delta);
+
+                // Update entity property values
+                <EntityById<T>>::mutate(entity_id, |entity| {
+                    entity.set_values(entity_property_values_updated);
+                });
+
+                // Trigger event
+                Self::deposit_event(RawEvent::EntityPropertyValuesUpdated(actor, entity_id, entities_inbound_rcs_delta));
+            }
+
+            Ok(())
+        }
+
+        /// Clear `PropertyValueVec` under given `entity_id` & `in_class_schema_property_id`
+        #[weight = 10_000_000] // TODO: adjust weight
+        pub fn clear_entity_property_vector(
+            origin,
+            actor: Actor<T>,
+            entity_id: T::EntityId,
+            in_class_schema_property_id: PropertyId
+        ) -> DispatchResult {
+
+            let account_id = ensure_signed(origin)?;
+
+            // Retrieve Class, Entity and EntityAccessLevel for the actor, attemting to perform operation
+            let (class, entity, access_level) = Self::ensure_class_entity_and_access_level(account_id, entity_id, &actor)?;
+
+            // Ensure Property under given PropertyId is unlocked from actor with given EntityAccessLevel
+            // Retrieve corresponding Property by value
+            let property = class.ensure_class_property_type_unlocked_from(
+                in_class_schema_property_id,
+                access_level,
+            )?;
+
+            // Ensure InputPropertyValue under given in_class_schema_property_id is Vector
+            let property_value_vector =
+                entity.ensure_property_value_is_vec(in_class_schema_property_id)?;
+
+            // Calculate side effects for clear_property_vector operation, based on property_value_vector provided and its respective property.
+            let entities_inbound_rcs_delta = Self::make_side_effects_for_clear_property_vector_operation(&property_value_vector, &property);
+
+            // Clear property_value_vector.
+            let empty_property_value_vector = Self::clear_property_vector(property_value_vector.clone());
+
+            let class_id = entity.get_class_id();
+
+            // Compute old and new vec unique property value hash.
+            // Ensure new property value hash with `unique` flag set is `unique` on `Class` level
+            let vec_property_value_hashes = if property.unique {
+                Some(
+                    Self::ensure_vec_property_value_hashes(class_id, in_class_schema_property_id, &empty_property_value_vector, property_value_vector)?
+                )
+            } else {
+                None
+            };
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            if let Some((new_property_value_hash, old_property_value_hash)) = vec_property_value_hashes {
+                // Add property value hash, that should be unique on `Class` level
+                Self::add_unique_property_value_hash(class_id, in_class_schema_property_id, new_property_value_hash);
+
+                // Remove property value hash, that should be unique on `Class` level
+                Self::remove_unique_property_value_hash(class_id, in_class_schema_property_id, old_property_value_hash);
+            }
+
+            // Decrease reference counters of involved entities (if some)
+            Self::update_entities_rcs(&entities_inbound_rcs_delta);
+
+            // Insert empty_property_value_vector into entity_property_values mapping at in_class_schema_property_id.
+            // Retrieve updated entity_property_values
+            let entity_values_updated = Self::insert_at_in_class_schema_property_id(
+                entity.get_values(), in_class_schema_property_id, empty_property_value_vector
+            );
+
+            // Update entity property values
+            <EntityById<T>>::mutate(entity_id, |entity| {
+                entity.set_values(entity_values_updated);
+            });
+
+            // Trigger event
+            Self::deposit_event(
+                RawEvent::VectorCleared(
+                    actor, entity_id, in_class_schema_property_id, entities_inbound_rcs_delta
+                )
+            );
+
+            Ok(())
+        }
+
+        /// Remove value at given `index_in_property_vector`
+        /// from `PropertyValueVec` under `in_class_schema_property_id`
+        #[weight = 10_000_000] // TODO: adjust weight
+        pub fn remove_at_entity_property_vector(
+            origin,
+            actor: Actor<T>,
+            entity_id: T::EntityId,
+            in_class_schema_property_id: PropertyId,
+            index_in_property_vector: VecMaxLength,
+            nonce: T::Nonce
+        ) -> DispatchResult {
+
+            let account_id = ensure_signed(origin)?;
+
+            // Retrieve Class, Entity and EntityAccessLevel for the actor, attemting to perform operation
+            let (class, entity, access_level) = Self::ensure_class_entity_and_access_level(account_id, entity_id, &actor)?;
+
+            // Ensure Property under given PropertyId is unlocked from actor with given EntityAccessLevel
+            // Retrieve corresponding Property by value
+            let property = class.ensure_class_property_type_unlocked_from(
+                in_class_schema_property_id,
+                access_level,
+            )?;
+
+            // Ensure InputPropertyValue under given in_class_schema_property_id is Vector
+            let property_value_vector =
+                entity.ensure_property_value_is_vec(in_class_schema_property_id)?;
+
+            // Ensure `VecInputPropertyValue` nonce is equal to the provided one.
+            // Used to to avoid possible data races, when performing vector specific operations
+            property_value_vector.ensure_nonce_equality(nonce)?;
+
+            // Ensure, provided index_in_property_vec is valid index of VecInputValue
+            property_value_vector
+                .ensure_index_in_property_vector_is_valid(index_in_property_vector)?;
+
+            let involved_entity_id = property_value_vector
+                .get_vec_value_ref()
+                .get_involved_entities()
+                .and_then(|involved_entities| involved_entities.get(index_in_property_vector as usize).copied());
+
+            // Remove value at in_class_schema_property_id in property value vector
+            // Get VecInputPropertyValue wrapped in InputPropertyValue
+            let property_value_vector_updated = Self::remove_at_index_in_property_vector(
+                property_value_vector.clone(), index_in_property_vector
+            );
+
+            let class_id = entity.get_class_id();
+
+            // Compute old and new vec unique property value hash.
+            // Ensure new property value hash with `unique` flag set is `unique` on `Class` level
+            let vec_property_value_hashes = if property.unique {
+                Some(
+                    Self::ensure_vec_property_value_hashes(class_id, in_class_schema_property_id, &property_value_vector_updated, property_value_vector)?
+                )
+            } else {
+                None
+            };
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            if let Some((new_property_value_hash, old_property_value_hash)) = vec_property_value_hashes {
+                // Add property value hash, that should be unique on `Class` level
+                Self::add_unique_property_value_hash(class_id, in_class_schema_property_id, new_property_value_hash);
+
+                // Remove property value hash, that should be unique on `Class` level
+                Self::remove_unique_property_value_hash(class_id, in_class_schema_property_id, old_property_value_hash);
+            }
+
+            // Insert updated propery value into entity_property_values mapping at in_class_schema_property_id.
+            let entity_values_updated = Self::insert_at_in_class_schema_property_id(
+                entity.get_values(), in_class_schema_property_id, property_value_vector_updated
+            );
+
+            let involved_entity_and_side_effect = if let Some(involved_entity_id) = involved_entity_id {
+                // Decrease reference counter of involved entity (if some)
+                let same_controller_status = property.property_type.same_controller_status();
+                let rc_delta = EntityReferenceCounterSideEffect::atomic(same_controller_status, DeltaMode::Decrement);
+
+                // Update InboundReferenceCounter of involved entity, based on previously calculated rc_delta
+                Self::update_entity_rc(involved_entity_id, rc_delta);
+                Some((involved_entity_id, rc_delta))
+            } else {
+                None
+            };
+
+            // Update entity property values
+            <EntityById<T>>::mutate(entity_id, |entity| {
+                entity.set_values(entity_values_updated);
+            });
+
+            // Trigger event
+            Self::deposit_event(
+                RawEvent::RemovedAtVectorIndex(
+                    actor, entity_id, in_class_schema_property_id, index_in_property_vector,
+                    nonce + T::Nonce::one(), involved_entity_and_side_effect
+                )
+            );
+
+            Ok(())
+        }
+
+        /// Insert `SingleInputPropertyValue` at given `index_in_property_vector`
+        /// into `PropertyValueVec` under `in_class_schema_property_id`
+        #[weight = 10_000_000] // TODO: adjust weight
+        pub fn insert_at_entity_property_vector(
+            origin,
+            actor: Actor<T>,
+            entity_id: T::EntityId,
+            in_class_schema_property_id: PropertyId,
+            index_in_property_vector: VecMaxLength,
+            value: InputValue<T>,
+            nonce: T::Nonce
+        ) -> DispatchResult {
+
+            let account_id = ensure_signed(origin)?;
+
+            // Retrieve Class, Entity and EntityAccessLevel for the actor, attemting to perform operation
+            let (class, entity, access_level) = Self::ensure_class_entity_and_access_level(account_id, entity_id, &actor)?;
+
+            // Ensure Property under given PropertyId is unlocked from actor with given EntityAccessLevel
+            // Retrieve corresponding Property by value
+            let property = class.ensure_class_property_type_unlocked_from(
+                in_class_schema_property_id,
+                access_level,
+            )?;
+
+            // Ensure InputPropertyValue under given in_class_schema_property_id is Vector
+            let property_value_vector =
+                entity.ensure_property_value_is_vec(in_class_schema_property_id)?;
+
+            // Ensure `VecInputPropertyValue` nonce is equal to the provided one.
+            // Used to to avoid possible data races, when performing vector specific operations
+            property_value_vector.ensure_nonce_equality(nonce)?;
+
+            let entity_controller = entity.get_permissions_ref().get_controller();
+
+            // Ensure property_value type is equal to the property_value_vector type and check all constraints
+            property.ensure_property_value_can_be_inserted_at_property_vector(
+                &value,
+                &property_value_vector,
+                index_in_property_vector,
+                entity_controller,
+            )?;
+
+            let involved_entity = value.get_involved_entity();
+
+            // Insert SingleInputPropertyValue at in_class_schema_property_id into property value vector
+            // Get VecInputPropertyValue wrapped in InputPropertyValue
+            let property_value_vector_updated = Self::insert_at_index_in_property_vector(
+                property_value_vector.clone(), index_in_property_vector, value
+            );
+
+            let class_id = entity.get_class_id();
+
+            // Compute old and new vec unique property value hash.
+            // Ensure new property value hash with `unique` flag set is `unique` on `Class` level
+            let vec_property_value_hashes = if property.unique {
+                Some(
+                    Self::ensure_vec_property_value_hashes(class_id, in_class_schema_property_id, &property_value_vector_updated, property_value_vector)?
+                )
+            } else {
+                None
+            };
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            if let Some((new_property_value_hash, old_property_value_hash)) = vec_property_value_hashes {
+                // Add property value hash, that should be unique on `Class` level
+                Self::add_unique_property_value_hash(class_id, in_class_schema_property_id, new_property_value_hash);
+
+                // Remove property value hash, that should be unique on `Class` level
+                Self::remove_unique_property_value_hash(class_id, in_class_schema_property_id, old_property_value_hash);
+            }
+
+            // Insert updated property value into entity_property_values mapping at in_class_schema_property_id.
+            // Retrieve updated entity_property_values
+            let entity_values_updated = Self::insert_at_in_class_schema_property_id(
+                entity.get_values(), in_class_schema_property_id, property_value_vector_updated
+            );
+
+            // Increase reference counter of involved entity (if some)
+            let involved_entity_and_side_effect = if let Some(entity_rc_to_increment) = involved_entity {
+                let same_controller_status = property.property_type.same_controller_status();
+                let rc_delta = EntityReferenceCounterSideEffect::atomic(same_controller_status, DeltaMode::Increment);
+
+                // Update InboundReferenceCounter of involved entity, based on previously calculated ReferenceCounterSideEffect
+                Self::update_entity_rc(entity_rc_to_increment, rc_delta);
+                Some((entity_rc_to_increment, rc_delta))
+            } else {
+                None
+            };
+
+            // Update entity property values
+            <EntityById<T>>::mutate(entity_id, |entity| {
+                entity.set_values(entity_values_updated);
+            });
+
+            // Trigger event
+            Self::deposit_event(
+                RawEvent::InsertedAtVectorIndex(
+                    actor, entity_id, in_class_schema_property_id, index_in_property_vector,
+                    nonce + T::Nonce::one(), involved_entity_and_side_effect
+                )
+            );
+
+            Ok(())
+        }
+
+        /// Batch transaction
+        #[weight = 10_000_000] // TODO: adjust weight
+        pub fn transaction(origin, actor: Actor<T>, operations: Vec<OperationType<T>>) -> DispatchResult {
+
+            // Ensure maximum number of operations during atomic batching limit not reached
+            Self::ensure_number_of_operations_during_atomic_batching_limit_not_reached(&operations)?;
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            // This BTreeMap holds the T::EntityId of the entity created as a result of executing a `CreateEntity` `Operation`
+            let mut entity_created_in_operation = BTreeMap::new();
+
+            // Create raw origin
+            let raw_origin = origin.into().map_err(|_| Error::<T>::OriginCanNotBeMadeIntoRawOrigin)?;
+
+            for (index, operation_type) in operations.into_iter().enumerate() {
+                let origin = T::Origin::from(raw_origin.clone());
+                let actor = actor.clone();
+                match operation_type {
+                    OperationType::CreateEntity(create_entity_operation) => {
+                        Self::create_entity(origin, create_entity_operation.class_id, actor)?;
+
+                        // entity id of newly created entity
+                        let entity_id = Self::next_entity_id() - T::EntityId::one();
+                        entity_created_in_operation.insert(index, entity_id);
+                    },
+                    OperationType::AddSchemaSupportToEntity(add_schema_support_to_entity_operation) => {
+                        let entity_id = operations::parametrized_entity_to_entity_id(
+                            &entity_created_in_operation, add_schema_support_to_entity_operation.entity_id
+                        )?;
+                        let schema_id = add_schema_support_to_entity_operation.schema_id;
+                        let property_values = operations::parametrized_property_values_to_property_values(
+                            &entity_created_in_operation, add_schema_support_to_entity_operation.parametrized_property_values
+                        )?;
+                        Self::add_schema_support_to_entity(origin, actor, entity_id, schema_id, property_values)?;
+                    },
+                    OperationType::UpdatePropertyValues(update_property_values_operation) => {
+                        let entity_id = operations::parametrized_entity_to_entity_id(
+                            &entity_created_in_operation, update_property_values_operation.entity_id
+                        )?;
+                        let property_values = operations::parametrized_property_values_to_property_values(
+                            &entity_created_in_operation, update_property_values_operation.new_parametrized_property_values
+                        )?;
+                        Self::update_entity_property_values(origin, actor, entity_id, property_values)?;
+                    },
+                }
+            }
+
+            // Trigger event
+            Self::deposit_event(RawEvent::TransactionCompleted(actor));
+
+            Ok(())
+        }
+    }
+}
+
+impl<T: Trait> Module<T> {
+    /// Updates corresponding `Entity` `reference_counter` by `reference_counter_delta`.
+    fn update_entity_rc(
+        entity_id: T::EntityId,
+        reference_counter_delta: EntityReferenceCounterSideEffect,
+    ) {
+        // Update both `total` and `same owner` number of inbound references for the Entity instance under given `entity_id`
+        <EntityById<T>>::mutate(entity_id, |entity| {
+            let entity_inbound_rc = entity.get_reference_counter_mut();
+            entity_inbound_rc.total =
+                (entity_inbound_rc.total as i32 + reference_counter_delta.total) as u32;
+            entity_inbound_rc.same_owner =
+                (entity_inbound_rc.same_owner as i32 + reference_counter_delta.same_owner) as u32;
+        })
+    }
+
+    /// Add property value hash, that should be unique on `Class` level
+    pub fn add_unique_property_value_hash(
+        class_id: T::ClassId,
+        property_id: PropertyId,
+        hash: T::Hash,
+    ) {
+        <UniquePropertyValueHashes<T>>::insert((class_id, property_id), hash, ());
+    }
+
+    /// Remove property value hash, that should be unique on `Class` level
+    pub fn remove_unique_property_value_hash(
+        class_id: T::ClassId,
+        property_id: PropertyId,
+        hash: T::Hash,
+    ) {
+        <UniquePropertyValueHashes<T>>::remove((class_id, property_id), hash);
+    }
+
+    /// Add property value hashes, that should be unique on `Class` level
+    pub fn add_unique_property_value_hashes(
+        class_id: T::ClassId,
+        unique_property_value_hashes: BTreeMap<PropertyId, T::Hash>,
+    ) {
+        unique_property_value_hashes
+            .into_iter()
+            .for_each(|(property_id, hash)| {
+                Self::add_unique_property_value_hash(class_id, property_id, hash);
+            });
+    }
+
+    /// Remove property value hashes, that should be unique on `Class` level
+    pub fn remove_unique_property_value_hashes(
+        class_id: T::ClassId,
+        unique_property_value_hashes: BTreeMap<PropertyId, T::Hash>,
+    ) {
+        unique_property_value_hashes
+            .into_iter()
+            .for_each(|(property_id, hash)| {
+                Self::remove_unique_property_value_hash(class_id, property_id, hash);
+            });
+    }
+
+    /// Convert all provided `InputPropertyValue`'s into `StoredPropertyValue`'s
+    pub fn make_output_property_values(
+        input_property_values: BTreeMap<PropertyId, InputPropertyValue<T>>,
+    ) -> BTreeMap<PropertyId, StoredPropertyValue<T>> {
+        input_property_values
+            .into_iter()
+            .map(|(property_id, property_value)| (property_id, property_value.into()))
+            .collect()
+    }
+
+    /// Update `entity_property_values` with `property_values`
+    /// Returns updated `entity_property_values`
+    fn make_updated_entity_property_values(
+        schema: Schema,
+        entity_property_values: BTreeMap<PropertyId, StoredPropertyValue<T>>,
+        output_property_values: &BTreeMap<PropertyId, StoredPropertyValue<T>>,
+    ) -> BTreeMap<PropertyId, StoredPropertyValue<T>> {
+        // Concatenate existing `entity_property_values` with `property_values`, provided, when adding `Schema` support.
+        let updated_entity_property_values: BTreeMap<PropertyId, StoredPropertyValue<T>> =
+            entity_property_values
+                .into_iter()
+                .chain(output_property_values.to_owned().into_iter())
+                .collect();
+
+        // Write all missing non required `Schema` `property_values` as `InputPropertyValue::default()`
+        let non_required_property_values: BTreeMap<PropertyId, StoredPropertyValue<T>> = schema
+            .get_properties()
+            .iter()
+            .filter_map(|property_id| {
+                if !updated_entity_property_values.contains_key(property_id) {
+                    Some((*property_id, StoredPropertyValue::default()))
+                } else {
+                    None
+                }
+            })
+            .collect();
+
+        // Extend updated_entity_property_values with given Schema non_required_property_values
+        updated_entity_property_values
+            .into_iter()
+            .chain(non_required_property_values.into_iter())
+            .collect()
+    }
+
+    /// Calculate side effects for clear_property_vector operation, based on `property_value_vector` provided and its respective `property`.
+    /// Returns calculated `ReferenceCounterSideEffects`
+    pub fn make_side_effects_for_clear_property_vector_operation(
+        property_value_vector: &VecStoredPropertyValue<T>,
+        property: &Property<T>,
+    ) -> Option<ReferenceCounterSideEffects<T>> {
+        let entity_ids_to_decrease_rc = property_value_vector
+            .get_vec_value_ref()
+            .get_involved_entities();
+
+        if let Some(entity_ids_to_decrease_rcs) = entity_ids_to_decrease_rc {
+            // Calculate `ReferenceCounterSideEffects`, based on entity_ids involved, same_controller_status and chosen `DeltaMode`
+            let same_controller_status = property.property_type.same_controller_status();
+            let entities_inbound_rcs_delta = Self::perform_entities_inbound_rcs_delta_calculation(
+                ReferenceCounterSideEffects::<T>::default(),
+                entity_ids_to_decrease_rcs,
+                same_controller_status,
+                DeltaMode::Decrement,
+            );
+
+            if !entities_inbound_rcs_delta.is_empty() {
+                Some(entities_inbound_rcs_delta)
+            } else {
+                None
+            }
+        } else {
+            None
+        }
+    }
+
+    /// Update `inbound_rcs_delta`, based on `involved_entity_ids`, `same_controller_status` provided and chosen `DeltaMode`
+    /// Returns updated `inbound_rcs_delta`
+    fn perform_entities_inbound_rcs_delta_calculation(
+        mut inbound_rcs_delta: ReferenceCounterSideEffects<T>,
+        involved_entity_ids: Vec<T::EntityId>,
+        same_controller_status: bool,
+        delta_mode: DeltaMode,
+    ) -> ReferenceCounterSideEffects<T> {
+        for involved_entity_id in involved_entity_ids {
+            // If inbound_rcs_delta already contains entry for the given involved_entity_id, increment it
+            // with atomic EntityReferenceCounterSideEffect instance, based on same_owner flag provided and DeltaMode,
+            // otherwise create new atomic EntityReferenceCounterSideEffect instance
+            if let Some(inbound_rc_delta) = inbound_rcs_delta.get_mut(&involved_entity_id) {
+                *inbound_rc_delta +=
+                    EntityReferenceCounterSideEffect::atomic(same_controller_status, delta_mode);
+            } else {
+                inbound_rcs_delta.insert(
+                    involved_entity_id,
+                    EntityReferenceCounterSideEffect::atomic(same_controller_status, delta_mode),
+                );
+            }
+        }
+        inbound_rcs_delta
+    }
+
+    /// Filter references, pointing to the same `Entity`
+    fn filter_references_to_the_same_entity(
+        current_entity_id: T::EntityId,
+        involved_entity_ids: Vec<T::EntityId>,
+    ) -> Vec<T::EntityId> {
+        involved_entity_ids
+            .into_iter()
+            .filter(|involved_entity_id| current_entity_id != *involved_entity_id)
+            .collect()
+    }
+
+    /// Calculate `ReferenceCounterSideEffects`, based on `values_for_existing_properties` provided and chosen `DeltaMode`
+    /// Returns calculated `ReferenceCounterSideEffects`
+    fn calculate_entities_inbound_rcs_delta(
+        current_entity_id: T::EntityId,
+        values_for_existing_properties: StoredValuesForExistingProperties<T>,
+        delta_mode: DeltaMode,
+    ) -> Option<ReferenceCounterSideEffects<T>> {
+        let entities_inbound_rcs_delta = values_for_existing_properties
+            .values()
+            .map(|value_for_existing_property| value_for_existing_property.unzip())
+            .filter_map(|(property, value)| {
+                let involved_entity_ids =
+                    value.get_involved_entities().map(|involved_entity_ids| {
+                        Self::filter_references_to_the_same_entity(
+                            current_entity_id,
+                            involved_entity_ids,
+                        )
+                    });
+                match involved_entity_ids {
+                    Some(involved_entity_ids) if !involved_entity_ids.is_empty() => Some((
+                        involved_entity_ids,
+                        property.property_type.same_controller_status(),
+                    )),
+                    _ => None,
+                }
+            })
+            // Aggeregate all sideffects on a single entity together into one side effect map
+            .fold(
+                ReferenceCounterSideEffects::default(),
+                |inbound_rcs_delta, (involved_entity_ids, same_controller_status)| {
+                    Self::perform_entities_inbound_rcs_delta_calculation(
+                        inbound_rcs_delta,
+                        involved_entity_ids,
+                        same_controller_status,
+                        delta_mode,
+                    )
+                },
+            );
+
+        if !entities_inbound_rcs_delta.is_empty() {
+            Some(entities_inbound_rcs_delta)
+        } else {
+            None
+        }
+    }
+
+    /// Compute `ReferenceCounterSideEffects`, based on `InputPropertyValue` `Reference`'s involved into update process.
+    /// Returns updated `ReferenceCounterSideEffects`
+    pub fn get_updated_inbound_rcs_delta(
+        current_entity_id: T::EntityId,
+        class_properties: Vec<Property<T>>,
+        entity_property_values: BTreeMap<PropertyId, StoredPropertyValue<T>>,
+        new_output_property_values: BTreeMap<PropertyId, StoredPropertyValue<T>>,
+    ) -> Result<Option<ReferenceCounterSideEffects<T>>, Error<T>> {
+        // Filter entity_property_values to get only those, which will be substituted with new_property_values
+        let entity_property_values_to_update: BTreeMap<PropertyId, StoredPropertyValue<T>> =
+            entity_property_values
+                .into_iter()
+                .filter(|(entity_id, _)| new_output_property_values.contains_key(entity_id))
+                .collect();
+
+        // Calculate entities reference counter side effects for update operation
+
+        let stored_values_for_entity_property_values_to_update =
+            match StoredValuesForExistingProperties::from(
+                &class_properties,
+                &entity_property_values_to_update,
+            ) {
+                Ok(stored_values_for_entity_property_values_to_update) => {
+                    stored_values_for_entity_property_values_to_update
+                }
+                Err(e) => {
+                    debug_assert!(false, "Should not fail! {:?}", e);
+                    return Err(e);
+                }
+            };
+
+        // Calculate entities inbound reference counter delta with Decrement DeltaMode for entity_property_values_to_update,
+        // as involved InputPropertyValue References will be substituted with new ones
+        let decremental_reference_counter_side_effects = Self::calculate_entities_inbound_rcs_delta(
+            current_entity_id,
+            stored_values_for_entity_property_values_to_update,
+            DeltaMode::Decrement,
+        );
+
+        // Calculate entities inbound reference counter delta with Increment DeltaMode for new_property_values,
+        // as involved InputPropertyValue References will substitute the old ones
+        let incremental_reference_counter_side_effects = Self::calculate_entities_inbound_rcs_delta(
+            current_entity_id,
+            StoredValuesForExistingProperties::from(
+                &class_properties,
+                &new_output_property_values,
+            )?,
+            DeltaMode::Increment,
+        );
+
+        // Add up both net decremental_reference_counter_side_effects and incremental_reference_counter_side_effects
+        // to get one net sideffect per entity.
+        Ok(Self::calculate_updated_inbound_rcs_delta(
+            decremental_reference_counter_side_effects,
+            incremental_reference_counter_side_effects,
+        ))
+    }
+
+    /// Add up both net first_reference_counter_side_effects and second_reference_counter_side_effects (if some)
+    /// to get one net sideffect per entity.
+    /// Returns updated `ReferenceCounterSideEffects`
+    pub fn calculate_updated_inbound_rcs_delta(
+        first_reference_counter_side_effects: Option<ReferenceCounterSideEffects<T>>,
+        second_reference_counter_side_effects: Option<ReferenceCounterSideEffects<T>>,
+    ) -> Option<ReferenceCounterSideEffects<T>> {
+        match (
+            first_reference_counter_side_effects,
+            second_reference_counter_side_effects,
+        ) {
+            (
+                Some(first_reference_counter_side_effects),
+                Some(second_reference_counter_side_effects),
+            ) => {
+                let reference_counter_side_effects = first_reference_counter_side_effects
+                    .update(second_reference_counter_side_effects);
+                Some(reference_counter_side_effects)
+            }
+            (Some(first_reference_counter_side_effects), _) => {
+                Some(first_reference_counter_side_effects)
+            }
+            (_, Some(second_reference_counter_side_effects)) => {
+                Some(second_reference_counter_side_effects)
+            }
+            _ => None,
+        }
+    }
+
+    /// Used to update `class_permissions` with parameters provided.
+    /// Returns updated `class_permissions` if update performed
+    pub fn make_updated_class_permissions(
+        class_permissions: ClassPermissions<T>,
+        updated_any_member: Option<bool>,
+        updated_entity_creation_blocked: Option<bool>,
+        updated_all_entity_property_values_locked: Option<bool>,
+        updated_maintainers: Option<BTreeSet<T::CuratorGroupId>>,
+    ) -> Option<ClassPermissions<T>> {
+        // Used to check if update performed
+        let mut updated_class_permissions = class_permissions.clone();
+
+        if let Some(updated_any_member) = updated_any_member {
+            updated_class_permissions.set_any_member_status(updated_any_member);
+        }
+
+        if let Some(updated_entity_creation_blocked) = updated_entity_creation_blocked {
+            updated_class_permissions.set_entity_creation_blocked(updated_entity_creation_blocked);
+        }
+
+        if let Some(updated_all_entity_property_values_locked) =
+            updated_all_entity_property_values_locked
+        {
+            updated_class_permissions
+                .set_all_entity_property_values_locked(updated_all_entity_property_values_locked);
+        }
+
+        if let Some(updated_maintainers) = updated_maintainers {
+            updated_class_permissions.set_maintainers(updated_maintainers);
+        }
+
+        if updated_class_permissions != class_permissions {
+            Some(updated_class_permissions)
+        } else {
+            None
+        }
+    }
+
+    /// Used to update `entity_permissions` with parameters provided.
+    /// Returns updated `entity_permissions` if update performed
+    pub fn make_updated_entity_permissions(
+        entity_permissions: EntityPermissions<T>,
+        updated_frozen: Option<bool>,
+        updated_referenceable: Option<bool>,
+    ) -> Option<EntityPermissions<T>> {
+        // Used to check if update performed
+        let mut updated_entity_permissions = entity_permissions.clone();
+
+        if let Some(updated_frozen) = updated_frozen {
+            updated_entity_permissions.set_frozen(updated_frozen);
+        }
+
+        if let Some(updated_referenceable) = updated_referenceable {
+            updated_entity_permissions.set_referencable(updated_referenceable);
+        }
+
+        if updated_entity_permissions != entity_permissions {
+            Some(updated_entity_permissions)
+        } else {
+            None
+        }
+    }
+
+    /// Ensure property value hash with `unique` flag set is `unique` on `Class` level
+    pub fn ensure_property_value_hash_unique_option_satisfied(
+        class_id: T::ClassId,
+        property_id: PropertyId,
+        unique_property_value_hash: &T::Hash,
+    ) -> Result<(), Error<T>> {
+        ensure!(
+            !<UniquePropertyValueHashes<T>>::contains_key(
+                (class_id, property_id),
+                unique_property_value_hash
+            ),
+            Error::<T>::PropertyValueShouldBeUnique
+        );
+        Ok(())
+    }
+
+    /// Ensure all property value hashes with `unique` flag set are `unique` on `Class` level
+    pub fn ensure_property_value_hashes_unique_option_satisfied(
+        class_id: T::ClassId,
+        unique_property_value_hashes: &BTreeMap<PropertyId, T::Hash>,
+    ) -> Result<(), Error<T>> {
+        for (&property_id, unique_property_value_hash) in unique_property_value_hashes {
+            Self::ensure_property_value_hash_unique_option_satisfied(
+                class_id,
+                property_id,
+                unique_property_value_hash,
+            )?;
+        }
+        Ok(())
+    }
+
+    /// Compute old and new vec unique property value hash.
+    /// Ensure new property value hash with `unique` flag set is `unique` on `Class` level
+    pub fn ensure_vec_property_value_hashes(
+        class_id: T::ClassId,
+        in_class_schema_property_id: PropertyId,
+        property_value_vector_updated: &StoredPropertyValue<T>,
+        property_value_vector: VecStoredPropertyValue<T>,
+    ) -> Result<(T::Hash, T::Hash), Error<T>> {
+        // Compute new hash from unique property value and its respective property id
+        let new_property_value_hash =
+            property_value_vector_updated.compute_unique_hash(in_class_schema_property_id);
+
+        // Ensure `Property` with `unique` flag set is `unique` on `Class` level
+        Self::ensure_property_value_hash_unique_option_satisfied(
+            class_id,
+            in_class_schema_property_id,
+            &new_property_value_hash,
+        )?;
+
+        // Compute old hash from the old unique property value and its respective property id
+        let old_property_value_hash =
+            property_value_vector.compute_unique_hash(in_class_schema_property_id);
+
+        Ok((new_property_value_hash, old_property_value_hash))
+    }
+
+    /// Compute new unique property value hashes.
+    /// Ensure new property value hashes with `unique` flag set are `unique` on `Class` level
+    pub fn ensure_new_property_values_respect_uniquness(
+        class_id: T::ClassId,
+        new_output_values_for_existing_properties: StoredValuesForExistingProperties<T>,
+    ) -> Result<BTreeMap<PropertyId, T::Hash>, Error<T>> {
+        let new_unique_property_value_hashes =
+            new_output_values_for_existing_properties.compute_unique_hashes();
+
+        // Ensure all provided Properties with unique flag set are unique on Class level
+        Self::ensure_property_value_hashes_unique_option_satisfied(
+            class_id,
+            &new_unique_property_value_hashes,
+        )?;
+
+        Ok(new_unique_property_value_hashes)
+    }
+
+    /// Returns the stored `Class` if exist, error otherwise.
+    fn ensure_class_exists(class_id: T::ClassId) -> Result<Class<T>, Error<T>> {
+        ensure!(
+            <ClassById<T>>::contains_key(class_id),
+            Error::<T>::ClassNotFound
+        );
+        Ok(Self::class_by_id(class_id))
+    }
+
+    /// Returns `Class` and `Entity` under given id, if exists, and `EntityAccessLevel` corresponding to `origin`, if permitted
+    fn ensure_class_entity_and_access_level(
+        account_id: T::AccountId,
+        entity_id: T::EntityId,
+        actor: &Actor<T>,
+    ) -> Result<(Class<T>, Entity<T>, EntityAccessLevel), Error<T>> {
+        // Ensure Entity under given id exists, retrieve corresponding one
+        let entity = Self::ensure_known_entity_id(entity_id)?;
+
+        // Retrieve corresponding Class
+        let class = Self::class_by_id(entity.get_class_id());
+
+        // Derive EntityAccessLevel for the actor, attempting to act.
+        let access_level = EntityAccessLevel::derive(
+            &account_id,
+            entity.get_permissions_ref(),
+            class.get_permissions_ref(),
+            actor,
+        )?;
+
+        Ok((class, entity, access_level))
+    }
+
+    /// Ensure `Entity` under given `entity_id` exists, retrieve corresponding `Entity` & `Class`
+    pub fn ensure_known_entity_and_class(
+        entity_id: T::EntityId,
+    ) -> Result<(Entity<T>, Class<T>), Error<T>> {
+        // Ensure Entity under given id exists, retrieve corresponding one
+        let entity = Self::ensure_known_entity_id(entity_id)?;
+
+        let class = ClassById::get(entity.get_class_id());
+        Ok((entity, class))
+    }
+
+    /// Filter `provided values_for_existing_properties`, leaving only `Reference`'s with `SameOwner` flag set
+    /// Returns the set of corresponding property ids
+    pub fn get_property_id_references_with_same_owner_flag_set(
+        values_for_existing_properties: StoredValuesForExistingProperties<T>,
+    ) -> BTreeSet<PropertyId> {
+        values_for_existing_properties
+            // Iterate over the PropertyId's
+            .keys()
+            // Filter provided values_for_existing_properties, leaving only `Reference`'s with `SameOwner` flag set
+            .filter(|property_id| {
+                if let Some(value_for_existing_property) =
+                    values_for_existing_properties.get(property_id)
+                {
+                    value_for_existing_property
+                        .get_property()
+                        .property_type
+                        .same_controller_status()
+                } else {
+                    false
+                }
+            })
+            .copied()
+            .collect()
+    }
+
+    /// Ensure all ids of provided `new_property_value_references_with_same_owner_flag_set`
+    /// corresponding to property ids of respective Class Property references with same owner flag set
+    pub fn ensure_only_reference_ids_with_same_owner_flag_set_provided(
+        entity_property_id_references_with_same_owner_flag_set: &BTreeSet<PropertyId>,
+        new_property_value_references_with_same_owner_flag_set: &BTreeMap<
+            PropertyId,
+            InputPropertyValue<T>,
+        >,
+    ) -> Result<(), Error<T>> {
+        let new_property_value_id_references_with_same_owner_flag_set: BTreeSet<PropertyId> =
+            new_property_value_references_with_same_owner_flag_set
+                .keys()
+                .copied()
+                .collect();
+
+        ensure!(
+            new_property_value_id_references_with_same_owner_flag_set
+                .is_subset(entity_property_id_references_with_same_owner_flag_set),
+            Error::<T>::AllProvidedPropertyValueIdsMustBeReferencesWithSameOwnerFlagSet
+        );
+        Ok(())
+    }
+
+    /// Ensure all provided `new_property_value_references_with_same_owner_flag_set` are valid
+    fn ensure_are_valid_references_with_same_owner_flag_set(
+        new_property_value_references_with_same_owner_flag_set: InputValuesForExistingProperties<T>,
+        new_controller: &EntityController<T>,
+    ) -> Result<(), Error<T>> {
+        for updated_value_for_existing_property in
+            new_property_value_references_with_same_owner_flag_set.values()
+        {
+            let (property, value) = updated_value_for_existing_property.unzip();
+
+            // Perform all required checks to ensure provided property values are valid references
+            property.ensure_property_value_is_valid_reference(value, new_controller)?;
+        }
+        Ok(())
+    }
+
+    /// Used to update entity_property_values with parameters provided.
+    /// Returns updated `entity_property_values`, if update performed
+    pub fn make_updated_property_value_references_with_same_owner_flag_set(
+        unused_property_id_references_with_same_owner_flag_set: BTreeSet<PropertyId>,
+        entity_property_values: &BTreeMap<PropertyId, StoredPropertyValue<T>>,
+        new_property_value_references_with_same_owner_flag_set: &BTreeMap<
+            PropertyId,
+            StoredPropertyValue<T>,
+        >,
+    ) -> Option<BTreeMap<PropertyId, StoredPropertyValue<T>>> {
+        // Used to check if update performed
+        let mut entity_property_values_updated = entity_property_values.clone();
+
+        for (property_id, new_property_value_reference_with_same_owner_flag_set) in
+            new_property_value_references_with_same_owner_flag_set
+        {
+            // Update entity_property_values map at property_id with new_property_value_reference_with_same_owner_flag_set
+            entity_property_values_updated.insert(
+                *property_id,
+                new_property_value_reference_with_same_owner_flag_set.to_owned(),
+            );
+        }
+
+        // Throw away old non required property value references with same owner flag set
+        // and replace them with Default ones
+        for unused_property_id_reference_with_same_owner_flag_set in
+            unused_property_id_references_with_same_owner_flag_set
+        {
+            entity_property_values_updated.insert(
+                unused_property_id_reference_with_same_owner_flag_set,
+                StoredPropertyValue::default(),
+            );
+        }
+
+        if *entity_property_values != entity_property_values_updated {
+            Some(entity_property_values_updated)
+        } else {
+            None
+        }
+    }
+
+    /// Update InboundReferenceCounter, based on previously calculated entities_inbound_rcs_delta, for each Entity involved
+    pub fn update_entities_rcs(
+        entities_inbound_rcs_delta: &Option<ReferenceCounterSideEffects<T>>,
+    ) {
+        if let Some(entities_inbound_rcs_delta) = entities_inbound_rcs_delta {
+            entities_inbound_rcs_delta.update_entities_rcs();
+        }
+    }
+
+    /// Retrieve `property_ids`, that are not in `property_values`
+    pub fn compute_unused_property_ids(
+        property_values: &BTreeMap<PropertyId, InputPropertyValue<T>>,
+        property_ids: &BTreeSet<PropertyId>,
+    ) -> BTreeSet<PropertyId> {
+        let property_value_indices: BTreeSet<PropertyId> =
+            property_values.keys().cloned().collect();
+
+        property_ids
+            .difference(&property_value_indices)
+            .copied()
+            .collect()
+    }
+
+    /// Used to compute old unique hashes, that should be substituted with new ones.
+    pub fn compute_old_unique_hashes(
+        new_output_property_values: &BTreeMap<PropertyId, StoredPropertyValue<T>>,
+        entity_values: &BTreeMap<PropertyId, StoredPropertyValue<T>>,
+    ) -> BTreeMap<PropertyId, T::Hash> {
+        entity_values
+            .iter()
+            .filter(|(property_id, _)| new_output_property_values.contains_key(property_id))
+            .map(|(&property_id, property_value)| {
+                (property_id, property_value.compute_unique_hash(property_id))
+            })
+            .collect()
+    }
+
+    /// Perform checks to ensure all required `property_values` under provided `unused_schema_property_ids` provided
+    pub fn ensure_all_required_properties_provided(
+        class_properties: &[Property<T>],
+        unused_schema_property_ids: &BTreeSet<PropertyId>,
+    ) -> Result<(), Error<T>> {
+        for &unused_schema_property_id in unused_schema_property_ids {
+            let class_property = &class_properties
+                .get(unused_schema_property_id as usize)
+                .ok_or(Error::<T>::ClassPropertyNotFound)?;
+
+            // All required property values should be provided
+            ensure!(
+                !class_property.required,
+                Error::<T>::MissingRequiredProperty
+            );
+        }
+        Ok(())
+    }
+
+    /// Validate all values, provided in `values_for_existing_properties`, against the type of its `Property`
+    /// and check any additional constraints
+    pub fn ensure_property_values_are_valid(
+        entity_controller: &EntityController<T>,
+        values_for_existing_properties: &InputValuesForExistingProperties<T>,
+    ) -> Result<(), Error<T>> {
+        for value_for_existing_property in values_for_existing_properties.values() {
+            let (property, value) = value_for_existing_property.unzip();
+
+            // Validate new InputPropertyValue against the type of this Property and check any additional constraints
+            property.ensure_property_value_to_update_is_valid(value, entity_controller)?;
+        }
+
+        Ok(())
+    }
+
+    /// Ensure all provided `new_property_values` are already exist in `entity_property_values` map
+    pub fn ensure_all_property_values_are_already_added(
+        entity_property_values: &BTreeMap<PropertyId, StoredPropertyValue<T>>,
+        new_property_values: &BTreeMap<PropertyId, InputPropertyValue<T>>,
+    ) -> Result<(), Error<T>> {
+        ensure!(
+            new_property_values
+                .keys()
+                .all(|key| entity_property_values.contains_key(key)),
+            Error::<T>::UnknownEntityPropertyId
+        );
+        Ok(())
+    }
+
+    /// Ensure `new_values_for_existing_properties` are accessible for actor with given `access_level`
+    pub fn ensure_all_property_values_are_unlocked_from(
+        new_values_for_existing_properties: &InputValuesForExistingProperties<T>,
+        access_level: EntityAccessLevel,
+    ) -> Result<(), Error<T>> {
+        for value_for_new_property in new_values_for_existing_properties.values() {
+            // Ensure Property is unlocked from Actor with given EntityAccessLevel
+            value_for_new_property
+                .get_property()
+                .ensure_unlocked_from(access_level)?;
+        }
+        Ok(())
+    }
+
+    /// Filter `new_property_values` identical to `entity_property_values`.
+    /// Return only `new_property_values`, that are not in `entity_property_values`
+    pub fn try_filter_identical_property_values(
+        entity_property_values: &BTreeMap<PropertyId, StoredPropertyValue<T>>,
+        new_property_values: BTreeMap<PropertyId, InputPropertyValue<T>>,
+    ) -> BTreeMap<PropertyId, InputPropertyValue<T>> {
+        new_property_values
+            .into_iter()
+            .filter(|(id, new_property_value)| {
+                if let Some(entity_property_value) = entity_property_values.get(id) {
+                    StoredPropertyValue::<T>::from(new_property_value.to_owned())
+                        != *entity_property_value
+                } else {
+                    true
+                }
+            })
+            .collect()
+    }
+
+    /// Update existing `entity_property_values` with `new_property_values`.
+    /// if update performed, returns updated entity property values
+    pub fn make_updated_property_values(
+        entity_property_values: &BTreeMap<PropertyId, StoredPropertyValue<T>>,
+        new_output_property_values: &BTreeMap<PropertyId, StoredPropertyValue<T>>,
+    ) -> Option<BTreeMap<PropertyId, StoredPropertyValue<T>>> {
+        // Used to check if updated performed
+        let mut entity_property_values_updated = entity_property_values.to_owned();
+
+        new_output_property_values
+            .iter()
+            .for_each(|(id, new_property_value)| {
+                if let Some(entity_property_value) = entity_property_values_updated.get_mut(&id) {
+                    entity_property_value.update(new_property_value.to_owned());
+                }
+            });
+
+        if entity_property_values_updated != *entity_property_values {
+            Some(entity_property_values_updated)
+        } else {
+            None
+        }
+    }
+
+    /// Insert `InputValue` into `VecStoredPropertyValue` at `index_in_property_vector`.
+    /// Returns `VecStoredPropertyValue` wrapped in `StoredPropertyValue`
+    pub fn insert_at_index_in_property_vector(
+        mut property_value_vector: VecStoredPropertyValue<T>,
+        index_in_property_vector: VecMaxLength,
+        value: InputValue<T>,
+    ) -> StoredPropertyValue<T> {
+        property_value_vector.insert_at(index_in_property_vector, value.into());
+        StoredPropertyValue::Vector(property_value_vector)
+    }
+
+    /// Remove `InputValue` at `index_in_property_vector` in `VecInputPropertyValue`.
+    /// Returns `VecInputPropertyValue` wrapped in `InputPropertyValue`
+    pub fn remove_at_index_in_property_vector(
+        mut property_value_vector: VecStoredPropertyValue<T>,
+        index_in_property_vector: VecMaxLength,
+    ) -> StoredPropertyValue<T> {
+        property_value_vector.remove_at(index_in_property_vector);
+        StoredPropertyValue::Vector(property_value_vector)
+    }
+
+    /// Clear `VecStoredPropertyValue`.
+    /// Returns empty `VecStoredPropertyValue` wrapped in `StoredPropertyValue`
+    pub fn clear_property_vector(
+        mut property_value_vector: VecStoredPropertyValue<T>,
+    ) -> StoredPropertyValue<T> {
+        property_value_vector.clear();
+        StoredPropertyValue::Vector(property_value_vector)
+    }
+
+    /// Insert `InputPropertyValue` into `entity_property_values` mapping at `in_class_schema_property_id`.
+    /// Returns updated `entity_property_values`
+    pub fn insert_at_in_class_schema_property_id(
+        mut entity_property_values: BTreeMap<PropertyId, StoredPropertyValue<T>>,
+        in_class_schema_property_id: PropertyId,
+        property_value: StoredPropertyValue<T>,
+    ) -> BTreeMap<PropertyId, StoredPropertyValue<T>> {
+        entity_property_values.insert(in_class_schema_property_id, property_value);
+        entity_property_values
+    }
+
+    /// Ensure `Class` under given id exists, return corresponding one
+    pub fn ensure_known_class_id(class_id: T::ClassId) -> Result<Class<T>, Error<T>> {
+        ensure!(
+            <ClassById<T>>::contains_key(class_id),
+            Error::<T>::ClassNotFound
+        );
+        Ok(Self::class_by_id(class_id))
+    }
+
+    /// Ensure `Entity` under given id exists, return corresponding one
+    pub fn ensure_known_entity_id(entity_id: T::EntityId) -> Result<Entity<T>, Error<T>> {
+        ensure!(
+            <EntityById<T>>::contains_key(entity_id),
+            Error::<T>::EntityNotFound
+        );
+        Ok(Self::entity_by_id(entity_id))
+    }
+
+    /// Ensure `CuratorGroup` under given id exists
+    pub fn ensure_curator_group_under_given_id_exists(
+        curator_group_id: &T::CuratorGroupId,
+    ) -> Result<(), Error<T>> {
+        ensure!(
+            <CuratorGroupById<T>>::contains_key(curator_group_id),
+            Error::<T>::CuratorGroupDoesNotExist
+        );
+        Ok(())
+    }
+
+    /// Ensure `CuratorGroup` under given id exists, return corresponding one
+    pub fn ensure_curator_group_exists(
+        curator_group_id: &T::CuratorGroupId,
+    ) -> Result<CuratorGroup<T>, Error<T>> {
+        Self::ensure_curator_group_under_given_id_exists(curator_group_id)?;
+        Ok(Self::curator_group_by_id(curator_group_id))
+    }
+
+    /// Ensure `MaxNumberOfMaintainersPerClass` constraint satisfied
+    pub fn ensure_maintainers_limit_not_reached(
+        curator_groups: &BTreeSet<T::CuratorGroupId>,
+    ) -> Result<(), Error<T>> {
+        ensure!(
+            curator_groups.len() < T::MaxNumberOfMaintainersPerClass::get() as usize,
+            Error::<T>::ClassMaintainersLimitReached
+        );
+        Ok(())
+    }
+
+    /// Ensure all `CuratorGroup`'s under given ids exist
+    pub fn ensure_curator_groups_exist(
+        curator_groups: &BTreeSet<T::CuratorGroupId>,
+    ) -> Result<(), Error<T>> {
+        for curator_group in curator_groups {
+            // Ensure CuratorGroup under given id exists
+            Self::ensure_curator_group_exists(curator_group)?;
+        }
+        Ok(())
+    }
+
+    /// Perform security checks to ensure provided `class_maintainers` are valid
+    pub fn ensure_class_maintainers_are_valid(
+        class_maintainers: &BTreeSet<T::CuratorGroupId>,
+    ) -> Result<(), Error<T>> {
+        // Ensure max number of maintainers per Class constraint satisfied
+        ensure!(
+            class_maintainers.len() <= T::MaxNumberOfMaintainersPerClass::get() as usize,
+            Error::<T>::ClassMaintainersLimitReached
+        );
+
+        // Ensure all curator groups provided are already exist in runtime
+        Self::ensure_curator_groups_exist(class_maintainers)?;
+        Ok(())
+    }
+
+    /// Ensure new `Schema` is not empty
+    pub fn ensure_non_empty_schema(
+        existing_properties: &BTreeSet<PropertyId>,
+        new_properties: &[Property<T>],
+    ) -> Result<(), Error<T>> {
+        // Schema is empty if both existing_properties and new_properties are empty
+        let non_empty_schema = !existing_properties.is_empty() || !new_properties.is_empty();
+        ensure!(non_empty_schema, Error::<T>::NoPropertiesInClassSchema);
+        Ok(())
+    }
+
+    /// Ensure `ClassNameLengthConstraint` conditions satisfied
+    pub fn ensure_class_name_is_valid(text: &[u8]) -> Result<(), Error<T>> {
+        T::ClassNameLengthConstraint::get().ensure_valid(
+            text.len(),
+            Error::<T>::ClassNameTooShort,
+            Error::<T>::ClassNameTooLong,
+        )
+    }
+
+    /// Ensure `ClassDescriptionLengthConstraint` conditions satisfied
+    pub fn ensure_class_description_is_valid(text: &[u8]) -> Result<(), Error<T>> {
+        T::ClassDescriptionLengthConstraint::get().ensure_valid(
+            text.len(),
+            Error::<T>::ClassDescriptionTooShort,
+            Error::<T>::ClassDescriptionTooLong,
+        )
+    }
+
+    /// Ensure `MaxNumberOfClasses` constraint satisfied
+    pub fn ensure_class_limit_not_reached() -> Result<(), Error<T>> {
+        ensure!(
+            (<ClassById<T>>::iter().count() as MaxNumber) < T::MaxNumberOfClasses::get(),
+            Error::<T>::ClassLimitReached
+        );
+        Ok(())
+    }
+
+    /// Ensure `MaxNumberOfEntitiesPerClass` constraint satisfied
+    pub fn ensure_valid_number_of_entities_per_class(
+        maximum_entities_count: T::EntityId,
+    ) -> Result<(), Error<T>> {
+        ensure!(
+            maximum_entities_count <= T::MaxNumberOfEntitiesPerClass::get(),
+            Error::<T>::EntitiesNumberPerClassConstraintViolated
+        );
+        Ok(())
+    }
+
+    /// Ensure `IndividualEntitiesCreationLimit` constraint satisfied
+    pub fn ensure_valid_number_of_class_entities_per_actor_constraint(
+        number_of_class_entities_per_actor: T::EntityId,
+    ) -> Result<(), Error<T>> {
+        ensure!(
+            number_of_class_entities_per_actor <= T::IndividualEntitiesCreationLimit::get(),
+            Error::<T>::NumberOfClassEntitiesPerActorConstraintViolated
+        );
+        Ok(())
+    }
+
+    /// Ensure all entities creation limits, defined for a given `Class`, are valid
+    pub fn ensure_entities_creation_limits_are_valid(
+        maximum_entities_count: T::EntityId,
+        default_entity_creation_voucher_upper_bound: T::EntityId,
+    ) -> Result<(), Error<T>> {
+        // Ensure default_entity_creation_voucher_upper_bound does not exceed default_entity_creation_voucher_upper_bound
+        ensure!(
+            default_entity_creation_voucher_upper_bound <= maximum_entities_count,
+            Error::<T>::PerControllerEntitiesCreationLimitExceedsOverallLimit
+        );
+
+        // Ensure maximum_entities_count does not exceed MaxNumberOfEntitiesPerClass limit
+        Self::ensure_valid_number_of_entities_per_class(maximum_entities_count)?;
+
+        // Ensure default_entity_creation_voucher_upper_bound constraint does not exceed IndividualEntitiesCreationLimit
+        Self::ensure_valid_number_of_class_entities_per_actor_constraint(
+            default_entity_creation_voucher_upper_bound,
+        )
+    }
+
+    /// Ensure maximum number of operations during atomic batching constraint satisfied
+    pub fn ensure_number_of_operations_during_atomic_batching_limit_not_reached(
+        operations: &[OperationType<T>],
+    ) -> Result<(), Error<T>> {
+        ensure!(
+            operations.len() <= T::MaxNumberOfOperationsDuringAtomicBatching::get() as usize,
+            Error::<T>::NumberOfOperationsDuringAtomicBatchingLimitReached
+        );
+        Ok(())
+    }
+
+    /// Complete all checks to ensure each `Property` is valid
+    pub fn ensure_all_properties_are_valid(new_properties: &[Property<T>]) -> Result<(), Error<T>> {
+        for new_property in new_properties.iter() {
+            // Ensure PropertyNameLengthConstraint satisfied
+            new_property.ensure_name_is_valid()?;
+
+            // Ensure PropertyDescriptionLengthConstraint satisfied
+            new_property.ensure_description_is_valid()?;
+
+            // Ensure Type specific constraints satisfied
+            new_property.ensure_property_type_size_is_valid()?;
+
+            // Ensure refers to existing class_id, if If Property Type is Reference,
+            new_property.ensure_property_type_reference_is_valid()?;
+        }
+        Ok(())
+    }
+
+    /// Ensure all `Property` names are  unique within `Class`
+    pub fn ensure_all_property_names_are_unique(
+        class_properties: &[Property<T>],
+        new_properties: &[Property<T>],
+    ) -> Result<(), Error<T>> {
+        // Used to ensure all property names are unique within class
+        let mut unique_prop_names = BTreeSet::new();
+
+        for property in class_properties.iter() {
+            unique_prop_names.insert(property.name.to_owned());
+        }
+
+        for new_property in new_properties {
+            // Ensure name of a new property is unique within its class.
+            ensure!(
+                !unique_prop_names.contains(&new_property.name),
+                Error::<T>::PropertyNameNotUniqueInAClass
+            );
+
+            unique_prop_names.insert(new_property.name.to_owned());
+        }
+
+        Ok(())
+    }
+
+    /// Ensure provided indices of `existing_properties`  are valid indices of `Class` properties
+    pub fn ensure_schema_properties_are_valid_indices(
+        existing_properties: &BTreeSet<PropertyId>,
+        class_properties: &[Property<T>],
+    ) -> Result<(), Error<T>> {
+        let has_unknown_properties = existing_properties
+            .iter()
+            .any(|&prop_id| prop_id >= class_properties.len() as PropertyId);
+        ensure!(
+            !has_unknown_properties,
+            Error::<T>::ClassSchemaRefersUnknownPropertyIndex
+        );
+        Ok(())
+    }
+
+    /// Create new `Schema` from existing and new property ids
+    pub fn create_class_schema(
+        existing_properties: BTreeSet<PropertyId>,
+        class_properties: &[Property<T>],
+        new_properties: &[Property<T>],
+    ) -> Schema {
+        // Calcualate new property ids
+        let properties = new_properties
+            .iter()
+            .enumerate()
+            .map(|(i, _)| (class_properties.len() + i) as PropertyId)
+            // Concatenate them with existing ones
+            .chain(existing_properties.into_iter())
+            .collect();
+
+        Schema::new(properties)
+    }
+
+    /// Update existing `Class` properties with new ones provided, return updated ones
+    pub fn make_updated_class_properties(
+        class_properties: Vec<Property<T>>,
+        new_properties: Vec<Property<T>>,
+    ) -> Vec<Property<T>> {
+        class_properties
+            .into_iter()
+            .chain(new_properties.into_iter())
+            .collect()
+    }
+}
+
+decl_event!(
+    pub enum Event<T>
+    where
+        CuratorGroupId = <T as ActorAuthenticator>::CuratorGroupId,
+        CuratorId = <T as ActorAuthenticator>::CuratorId,
+        ClassId = <T as Trait>::ClassId,
+        EntityId = <T as Trait>::EntityId,
+        EntityController = EntityController<T>,
+        EntityCreationVoucher = EntityCreationVoucher<T>,
+        Status = bool,
+        Actor = Actor<T>,
+        Nonce = <T as Trait>::Nonce,
+        SideEffects = Option<ReferenceCounterSideEffects<T>>,
+        SideEffect = Option<(<T as Trait>::EntityId, EntityReferenceCounterSideEffect)>,
+    {
+        CuratorGroupAdded(CuratorGroupId),
+        CuratorGroupRemoved(CuratorGroupId),
+        CuratorGroupStatusSet(CuratorGroupId, Status),
+        CuratorAdded(CuratorGroupId, CuratorId),
+        CuratorRemoved(CuratorGroupId, CuratorId),
+        MaintainerAdded(ClassId, CuratorGroupId),
+        MaintainerRemoved(ClassId, CuratorGroupId),
+        EntityCreationVoucherUpdated(EntityController, EntityCreationVoucher),
+        EntityCreationVoucherCreated(EntityController, EntityCreationVoucher),
+        ClassCreated(ClassId),
+        ClassPermissionsUpdated(ClassId),
+        ClassSchemaAdded(ClassId, SchemaId),
+        ClassSchemaStatusUpdated(ClassId, SchemaId, Status),
+        EntityPermissionsUpdated(EntityId),
+        EntityCreated(Actor, EntityId),
+        EntityRemoved(Actor, EntityId),
+        EntitySchemaSupportAdded(Actor, EntityId, SchemaId, SideEffects),
+        EntityPropertyValuesUpdated(Actor, EntityId, SideEffects),
+        VectorCleared(Actor, EntityId, PropertyId, SideEffects),
+        RemovedAtVectorIndex(Actor, EntityId, PropertyId, VecMaxLength, Nonce, SideEffect),
+        InsertedAtVectorIndex(Actor, EntityId, PropertyId, VecMaxLength, Nonce, SideEffect),
+        EntityOwnershipTransfered(EntityId, EntityController, SideEffects),
+        TransactionCompleted(Actor),
+    }
+);

+ 1029 - 0
runtime-modules/content-directory/src/mock.rs

@@ -0,0 +1,1029 @@
+#![cfg(test)]
+
+use crate::InputValidationLengthConstraint;
+use crate::*;
+use core::iter::FromIterator;
+use frame_support::traits::{OnFinalize, OnInitialize};
+pub use frame_support::{
+    assert_err, assert_ok, impl_outer_event, impl_outer_origin, parameter_types,
+};
+use sp_core::H256;
+use sp_runtime::{
+    testing::Header,
+    traits::{BlakeTwo256, IdentityLookup},
+    Perbill,
+};
+use std::cell::RefCell;
+
+/// Runtime Types
+
+type ClassId = <Runtime as Trait>::ClassId;
+type EntityId = <Runtime as Trait>::EntityId;
+type Nonce = <Runtime as Trait>::Nonce;
+
+type CuratorId = <Runtime as ActorAuthenticator>::CuratorId;
+pub type CuratorGroupId = <Runtime as ActorAuthenticator>::CuratorGroupId;
+type MemberId = <Runtime as ActorAuthenticator>::MemberId;
+
+/// Origins
+
+pub const LEAD_ORIGIN: u64 = 1;
+
+pub const FIRST_CURATOR_ORIGIN: u64 = 2;
+pub const SECOND_CURATOR_ORIGIN: u64 = 3;
+
+pub const FIRST_MEMBER_ORIGIN: u64 = 4;
+pub const SECOND_MEMBER_ORIGIN: u64 = 5;
+pub const UNKNOWN_ORIGIN: u64 = 7777;
+
+/// Runtime Id's
+
+pub const FIRST_CURATOR_ID: CuratorId = 1;
+pub const SECOND_CURATOR_ID: CuratorId = 2;
+
+pub const FIRST_CURATOR_GROUP_ID: CuratorGroupId = 1;
+pub const SECOND_CURATOR_GROUP_ID: CuratorGroupId = 2;
+
+pub const FIRST_MEMBER_ID: MemberId = 1;
+pub const SECOND_MEMBER_ID: MemberId = 2;
+
+pub const FIRST_CLASS_ID: ClassId = 1;
+pub const SECOND_CLASS_ID: ClassId = 2;
+
+pub const FIRST_ENTITY_ID: EntityId = 1;
+pub const SECOND_ENTITY_ID: EntityId = 2;
+pub const THIRD_ENTITY_ID: EntityId = 3;
+
+pub const UNKNOWN_CLASS_ID: ClassId = 111;
+pub const UNKNOWN_ENTITY_ID: EntityId = 222;
+pub const UNKNOWN_PROPERTY_ID: PropertyId = 333;
+pub const UNKNOWN_SCHEMA_ID: SchemaId = 444;
+
+pub const UNKNOWN_CURATOR_GROUP_ID: CuratorGroupId = 555;
+pub const UNKNOWN_CURATOR_ID: CuratorGroupId = 555;
+pub const UNKNOWN_MEMBER_ID: CuratorGroupId = 777;
+
+pub const FIRST_SCHEMA_ID: SchemaId = 0;
+pub const SECOND_SCHEMA_ID: SchemaId = 1;
+
+pub const FIRST_PROPERTY_ID: SchemaId = 0;
+pub const SECOND_PROPERTY_ID: SchemaId = 1;
+
+impl_outer_origin! {
+    pub enum Origin for Runtime {}
+}
+
+// Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted.
+#[derive(Clone, Default, PartialEq, Eq, Debug)]
+pub struct Runtime;
+parameter_types! {
+    pub const BlockHashCount: u64 = 250;
+    pub const MaximumBlockWeight: u32 = 1024;
+    pub const MaximumBlockLength: u32 = 2 * 1024;
+    pub const AvailableBlockRatio: Perbill = Perbill::one();
+    pub const MinimumPeriod: u64 = 5;
+}
+
+thread_local! {
+    static PROPERTY_NAME_CONSTRAINT: RefCell<InputValidationLengthConstraint> = RefCell::new(InputValidationLengthConstraint::default());
+    static PROPERTY_DESCRIPTION_CONSTRAINT: RefCell<InputValidationLengthConstraint> = RefCell::new(InputValidationLengthConstraint::default());
+    static CLASS_NAME_CONSTRAINT: RefCell<InputValidationLengthConstraint> = RefCell::new(InputValidationLengthConstraint::default());
+    static CLASS_DESCRIPTION_CONSTRAINT: RefCell<InputValidationLengthConstraint> = RefCell::new(InputValidationLengthConstraint::default());
+    static MAX_NUMBER_OF_CLASSES: RefCell<MaxNumber> = RefCell::new(0);
+    static MAX_NUMBER_OF_MAINTAINERS_PER_CLASS: RefCell<MaxNumber> = RefCell::new(0);
+    static MAX_NUMBER_OF_SCHEMAS_PER_CLASS: RefCell<MaxNumber> = RefCell::new(0);
+    static MAX_NUMBER_OF_PROPERTIES_PER_CLASS: RefCell<MaxNumber> = RefCell::new(0);
+    static MAX_NUMBER_OF_ENTITIES_PER_CLASS: RefCell<EntityId> = RefCell::new(0);
+    static MAX_NUMBER_OF_CURATORS_PER_GROUP: RefCell<MaxNumber> = RefCell::new(0);
+    static MAX_NUMBER_OF_OPERATIONS_DURING_ATOMIC_BATCHING: RefCell<MaxNumber> = RefCell::new(0);
+    static VEC_MAX_LENGTH_CONSTRAINT: RefCell<VecMaxLength> = RefCell::new(0);
+    static TEXT_MAX_LENGTH_CONSTRAINT: RefCell<TextMaxLength> = RefCell::new(0);
+    static HASHED_TEXT_MAX_LENGTH_CONSTRAINT: RefCell<HashedTextMaxLength> = RefCell::new(Some(0));
+    static INDIVIDUAL_ENTITIES_CREATION_LIMIT: RefCell<EntityId> = RefCell::new(0);
+}
+
+pub struct PropertyNameLengthConstraint;
+impl Get<InputValidationLengthConstraint> for PropertyNameLengthConstraint {
+    fn get() -> InputValidationLengthConstraint {
+        PROPERTY_NAME_CONSTRAINT.with(|v| *v.borrow())
+    }
+}
+
+pub struct PropertyDescriptionLengthConstraint;
+impl Get<InputValidationLengthConstraint> for PropertyDescriptionLengthConstraint {
+    fn get() -> InputValidationLengthConstraint {
+        PROPERTY_DESCRIPTION_CONSTRAINT.with(|v| *v.borrow())
+    }
+}
+
+pub struct ClassNameLengthConstraint;
+impl Get<InputValidationLengthConstraint> for ClassNameLengthConstraint {
+    fn get() -> InputValidationLengthConstraint {
+        CLASS_NAME_CONSTRAINT.with(|v| *v.borrow())
+    }
+}
+
+pub struct ClassDescriptionLengthConstraint;
+impl Get<InputValidationLengthConstraint> for ClassDescriptionLengthConstraint {
+    fn get() -> InputValidationLengthConstraint {
+        CLASS_DESCRIPTION_CONSTRAINT.with(|v| *v.borrow())
+    }
+}
+
+pub struct MaxNumberOfClasses;
+impl Get<MaxNumber> for MaxNumberOfClasses {
+    fn get() -> MaxNumber {
+        MAX_NUMBER_OF_CLASSES.with(|v| *v.borrow())
+    }
+}
+
+pub struct MaxNumberOfMaintainersPerClass;
+impl Get<MaxNumber> for MaxNumberOfMaintainersPerClass {
+    fn get() -> MaxNumber {
+        MAX_NUMBER_OF_MAINTAINERS_PER_CLASS.with(|v| *v.borrow())
+    }
+}
+
+pub struct MaxNumberOfCuratorsPerGroup;
+impl Get<MaxNumber> for MaxNumberOfCuratorsPerGroup {
+    fn get() -> MaxNumber {
+        MAX_NUMBER_OF_CURATORS_PER_GROUP.with(|v| *v.borrow())
+    }
+}
+
+pub struct MaxNumberOfSchemasPerClass;
+impl Get<MaxNumber> for MaxNumberOfSchemasPerClass {
+    fn get() -> MaxNumber {
+        MAX_NUMBER_OF_SCHEMAS_PER_CLASS.with(|v| *v.borrow())
+    }
+}
+
+pub struct MaxNumberOfPropertiesPerSchema;
+impl Get<MaxNumber> for MaxNumberOfPropertiesPerSchema {
+    fn get() -> MaxNumber {
+        MAX_NUMBER_OF_PROPERTIES_PER_CLASS.with(|v| *v.borrow())
+    }
+}
+
+pub struct MaxNumberOfOperationsDuringAtomicBatching;
+impl Get<MaxNumber> for MaxNumberOfOperationsDuringAtomicBatching {
+    fn get() -> MaxNumber {
+        MAX_NUMBER_OF_OPERATIONS_DURING_ATOMIC_BATCHING.with(|v| *v.borrow())
+    }
+}
+
+pub struct VecMaxLengthConstraint;
+impl Get<VecMaxLength> for VecMaxLengthConstraint {
+    fn get() -> VecMaxLength {
+        VEC_MAX_LENGTH_CONSTRAINT.with(|v| *v.borrow())
+    }
+}
+
+pub struct TextMaxLengthConstraint;
+impl Get<TextMaxLength> for TextMaxLengthConstraint {
+    fn get() -> TextMaxLength {
+        TEXT_MAX_LENGTH_CONSTRAINT.with(|v| *v.borrow())
+    }
+}
+
+pub struct HashedTextMaxLengthConstraint;
+impl Get<HashedTextMaxLength> for HashedTextMaxLengthConstraint {
+    fn get() -> HashedTextMaxLength {
+        HASHED_TEXT_MAX_LENGTH_CONSTRAINT.with(|v| *v.borrow())
+    }
+}
+
+pub struct MaxNumberOfEntitiesPerClass;
+impl Get<EntityId> for MaxNumberOfEntitiesPerClass {
+    fn get() -> EntityId {
+        MAX_NUMBER_OF_ENTITIES_PER_CLASS.with(|v| *v.borrow())
+    }
+}
+
+pub struct IndividualEntitiesCreationLimit;
+impl Get<EntityId> for IndividualEntitiesCreationLimit {
+    fn get() -> EntityId {
+        INDIVIDUAL_ENTITIES_CREATION_LIMIT.with(|v| *v.borrow())
+    }
+}
+
+impl system::Trait for Runtime {
+    type BaseCallFilter = ();
+    type Origin = Origin;
+    type Call = ();
+    type Index = u64;
+    type BlockNumber = u64;
+    type Hash = H256;
+    type Hashing = BlakeTwo256;
+    type AccountId = u64;
+    type Lookup = IdentityLookup<Self::AccountId>;
+    type Header = Header;
+    type Event = TestEvent;
+    type BlockHashCount = BlockHashCount;
+    type MaximumBlockWeight = MaximumBlockWeight;
+    type DbWeight = ();
+    type BlockExecutionWeight = ();
+    type ExtrinsicBaseWeight = ();
+    type MaximumExtrinsicWeight = ();
+    type MaximumBlockLength = MaximumBlockLength;
+    type AvailableBlockRatio = AvailableBlockRatio;
+    type Version = ();
+    type ModuleToIndex = ();
+    type AccountData = ();
+    type OnNewAccount = ();
+    type OnKilledAccount = ();
+}
+
+mod test_events {
+    pub use crate::Event;
+}
+
+impl_outer_event! {
+    pub enum TestEvent for Runtime {
+        test_events<T>,
+        system<T>,
+    }
+}
+
+impl Trait for Runtime {
+    type Event = TestEvent;
+    type Nonce = u64;
+    type ClassId = u64;
+    type EntityId = u64;
+    type PropertyNameLengthConstraint = PropertyNameLengthConstraint;
+    type PropertyDescriptionLengthConstraint = PropertyDescriptionLengthConstraint;
+    type ClassNameLengthConstraint = ClassNameLengthConstraint;
+    type ClassDescriptionLengthConstraint = ClassDescriptionLengthConstraint;
+    type MaxNumberOfClasses = MaxNumberOfClasses;
+    type MaxNumberOfMaintainersPerClass = MaxNumberOfMaintainersPerClass;
+    type MaxNumberOfSchemasPerClass = MaxNumberOfSchemasPerClass;
+    type MaxNumberOfPropertiesPerSchema = MaxNumberOfPropertiesPerSchema;
+    type MaxNumberOfEntitiesPerClass = MaxNumberOfEntitiesPerClass;
+    type MaxNumberOfCuratorsPerGroup = MaxNumberOfCuratorsPerGroup;
+    type MaxNumberOfOperationsDuringAtomicBatching = MaxNumberOfOperationsDuringAtomicBatching;
+    type VecMaxLengthConstraint = VecMaxLengthConstraint;
+    type TextMaxLengthConstraint = TextMaxLengthConstraint;
+    type HashedTextMaxLengthConstraint = HashedTextMaxLengthConstraint;
+    type IndividualEntitiesCreationLimit = IndividualEntitiesCreationLimit;
+}
+
+impl ActorAuthenticator for Runtime {
+    type CuratorId = u64;
+    type MemberId = u64;
+    type CuratorGroupId = u64;
+
+    // Consider lazy_static crate?
+
+    fn is_lead(account_id: &Self::AccountId) -> bool {
+        let lead_account_id = ensure_signed(Origin::signed(LEAD_ORIGIN)).unwrap();
+        *account_id == lead_account_id
+    }
+
+    fn is_curator(curator_id: &Self::CuratorId, account_id: &Self::AccountId) -> bool {
+        let first_curator_account_id = ensure_signed(Origin::signed(FIRST_CURATOR_ORIGIN)).unwrap();
+        let second_curator_account_id =
+            ensure_signed(Origin::signed(SECOND_CURATOR_ORIGIN)).unwrap();
+        (first_curator_account_id == *account_id && FIRST_CURATOR_ID == *curator_id)
+            || (second_curator_account_id == *account_id && SECOND_CURATOR_ID == *curator_id)
+    }
+
+    fn is_member(member_id: &Self::MemberId, account_id: &Self::AccountId) -> bool {
+        let unknown_member_account_id = ensure_signed(Origin::signed(UNKNOWN_ORIGIN)).unwrap();
+        *member_id < MaxNumberOfEntitiesPerClass::get() && unknown_member_account_id != *account_id
+    }
+}
+
+pub struct ExtBuilder {
+    property_name_constraint: InputValidationLengthConstraint,
+    property_description_constraint: InputValidationLengthConstraint,
+    class_name_constraint: InputValidationLengthConstraint,
+    class_description_constraint: InputValidationLengthConstraint,
+    max_number_of_classes: MaxNumber,
+    max_number_of_maintainers_per_class: MaxNumber,
+    max_number_of_schemas_per_class: MaxNumber,
+    max_number_of_properties_per_class: MaxNumber,
+    max_number_of_entities_per_class: EntityId,
+    max_number_of_curators_per_group: MaxNumber,
+    max_number_of_operations_during_atomic_batching: MaxNumber,
+    vec_max_length_constraint: VecMaxLength,
+    text_max_length_constraint: TextMaxLength,
+    hashed_text_max_length_constraint: HashedTextMaxLength,
+    individual_entities_creation_limit: EntityId,
+}
+
+impl Default for ExtBuilder {
+    fn default() -> Self {
+        Self {
+            property_name_constraint: InputValidationLengthConstraint::new(1, 49),
+            property_description_constraint: InputValidationLengthConstraint::new(1, 500),
+            class_name_constraint: InputValidationLengthConstraint::new(1, 49),
+            class_description_constraint: InputValidationLengthConstraint::new(1, 500),
+            max_number_of_classes: 100,
+            max_number_of_maintainers_per_class: 10,
+            max_number_of_schemas_per_class: 20,
+            max_number_of_properties_per_class: 40,
+            max_number_of_entities_per_class: 400,
+            max_number_of_curators_per_group: 50,
+            max_number_of_operations_during_atomic_batching: 500,
+            vec_max_length_constraint: 200,
+            text_max_length_constraint: 5000,
+            hashed_text_max_length_constraint: Some(25000),
+            individual_entities_creation_limit: 50,
+        }
+    }
+}
+
+impl ExtBuilder {
+    pub fn set_associated_consts(&self) {
+        PROPERTY_NAME_CONSTRAINT.with(|v| *v.borrow_mut() = self.property_name_constraint);
+        PROPERTY_DESCRIPTION_CONSTRAINT
+            .with(|v| *v.borrow_mut() = self.property_description_constraint);
+        CLASS_NAME_CONSTRAINT.with(|v| *v.borrow_mut() = self.class_name_constraint);
+        CLASS_DESCRIPTION_CONSTRAINT.with(|v| *v.borrow_mut() = self.class_description_constraint);
+        MAX_NUMBER_OF_CLASSES.with(|v| *v.borrow_mut() = self.max_number_of_classes);
+        MAX_NUMBER_OF_MAINTAINERS_PER_CLASS
+            .with(|v| *v.borrow_mut() = self.max_number_of_maintainers_per_class);
+        MAX_NUMBER_OF_SCHEMAS_PER_CLASS
+            .with(|v| *v.borrow_mut() = self.max_number_of_schemas_per_class);
+        MAX_NUMBER_OF_PROPERTIES_PER_CLASS
+            .with(|v| *v.borrow_mut() = self.max_number_of_properties_per_class);
+        MAX_NUMBER_OF_ENTITIES_PER_CLASS
+            .with(|v| *v.borrow_mut() = self.max_number_of_entities_per_class);
+        MAX_NUMBER_OF_CURATORS_PER_GROUP
+            .with(|v| *v.borrow_mut() = self.max_number_of_curators_per_group);
+        MAX_NUMBER_OF_OPERATIONS_DURING_ATOMIC_BATCHING
+            .with(|v| *v.borrow_mut() = self.max_number_of_operations_during_atomic_batching);
+        VEC_MAX_LENGTH_CONSTRAINT.with(|v| *v.borrow_mut() = self.vec_max_length_constraint);
+        TEXT_MAX_LENGTH_CONSTRAINT.with(|v| *v.borrow_mut() = self.text_max_length_constraint);
+        HASHED_TEXT_MAX_LENGTH_CONSTRAINT
+            .with(|v| *v.borrow_mut() = self.hashed_text_max_length_constraint);
+        INDIVIDUAL_ENTITIES_CREATION_LIMIT
+            .with(|v| *v.borrow_mut() = self.individual_entities_creation_limit);
+    }
+
+    pub fn build(self, config: GenesisConfig<Runtime>) -> sp_io::TestExternalities {
+        self.set_associated_consts();
+        let mut t = system::GenesisConfig::default()
+            .build_storage::<Runtime>()
+            .unwrap();
+        config.assimilate_storage(&mut t).unwrap();
+        t.into()
+    }
+}
+
+// This function basically just builds a genesis storage key/value store according to
+// our desired mockup.
+
+fn default_content_directory_genesis_config() -> GenesisConfig<Runtime> {
+    GenesisConfig {
+        curator_group_by_id: vec![],
+        next_class_id: 1,
+        next_entity_id: 1,
+        next_curator_group_id: 1,
+    }
+}
+
+pub fn with_test_externalities<R, F: FnOnce() -> R>(f: F) -> R {
+    let default_genesis_config = default_content_directory_genesis_config();
+    /*
+        Events are not emitted on block 0.
+        So any dispatchable calls made during genesis block formation will have no events emitted.
+        https://substrate.dev/recipes/2-appetizers/4-events.html
+    */
+    let func = || {
+        run_to_block(1);
+        f()
+    };
+
+    ExtBuilder::default()
+        .build(default_genesis_config)
+        .execute_with(func)
+}
+
+pub fn generate_text(len: usize) -> Vec<u8> {
+    vec![b'x'; len]
+}
+
+impl<T: Trait> Property<T> {
+    pub fn required(mut self) -> Self {
+        self.required = true;
+        self
+    }
+
+    pub fn unique(mut self) -> Self {
+        self.unique = true;
+        self
+    }
+}
+
+// Events
+
+type RawTestEvent = RawEvent<
+    CuratorGroupId,
+    CuratorId,
+    ClassId,
+    EntityId,
+    EntityController<Runtime>,
+    EntityCreationVoucher<Runtime>,
+    bool,
+    Actor<Runtime>,
+    Nonce,
+    Option<ReferenceCounterSideEffects<Runtime>>,
+    Option<(EntityId, EntityReferenceCounterSideEffect)>,
+>;
+
+pub fn get_test_event(raw_event: RawTestEvent) -> TestEvent {
+    TestEvent::test_events(raw_event)
+}
+
+pub fn assert_event_success(tested_event: TestEvent, number_of_events_after_call: usize) {
+    // Ensure  runtime events length is equal to expected number of events after call
+    assert_eq!(System::events().len(), number_of_events_after_call);
+
+    // Ensure  last emitted event is equal to expected one
+    assert!(matches!(
+            System::events()
+                .iter()
+                .last(),
+            Some(last_event) if last_event.event == tested_event
+    ));
+}
+
+pub fn assert_failure(
+    call_result: DispatchResult,
+    expected_error: Error<Runtime>,
+    number_of_events_before_call: usize,
+) {
+    // Ensure  call result is equal to expected error
+    assert_err!(call_result, expected_error);
+
+    // Ensure  no other events emitted after call
+    assert_eq!(System::events().len(), number_of_events_before_call);
+}
+
+// Curator groups
+
+pub fn next_curator_group_id() -> CuratorGroupId {
+    TestModule::next_curator_group_id()
+}
+
+pub fn add_curator_group(lead_origin: u64) -> DispatchResult {
+    TestModule::add_curator_group(Origin::signed(lead_origin))
+}
+
+pub fn remove_curator_group(lead_origin: u64, curator_group_id: CuratorGroupId) -> DispatchResult {
+    TestModule::remove_curator_group(Origin::signed(lead_origin), curator_group_id)
+}
+
+pub fn add_curator_to_group(
+    lead_origin: u64,
+    curator_group_id: CuratorGroupId,
+    curator_id: CuratorId,
+) -> DispatchResult {
+    TestModule::add_curator_to_group(Origin::signed(lead_origin), curator_group_id, curator_id)
+}
+
+pub fn remove_curator_from_group(
+    lead_origin: u64,
+    curator_group_id: CuratorGroupId,
+    curator_id: CuratorId,
+) -> DispatchResult {
+    TestModule::remove_curator_from_group(Origin::signed(lead_origin), curator_group_id, curator_id)
+}
+
+pub fn set_curator_group_status(
+    lead_origin: u64,
+    curator_group_id: CuratorGroupId,
+    is_active: bool,
+) -> DispatchResult {
+    TestModule::set_curator_group_status(Origin::signed(lead_origin), curator_group_id, is_active)
+}
+
+pub fn curator_group_by_id(curator_group_id: CuratorGroupId) -> CuratorGroup<Runtime> {
+    TestModule::curator_group_by_id(curator_group_id)
+}
+
+pub fn curator_group_exists(curator_group_id: CuratorGroupId) -> bool {
+    CuratorGroupById::<Runtime>::contains_key(curator_group_id)
+}
+
+// Classes
+
+pub enum ClassType {
+    Valid,
+    NameTooLong,
+    NameTooShort,
+    DescriptionTooLong,
+    DescriptionTooShort,
+    InvalidMaximumEntitiesCount,
+    InvalidDefaultVoucherUpperBound,
+    DefaultVoucherUpperBoundExceedsMaximumEntitiesCount,
+    MaintainersLimitReached,
+    CuratorGroupDoesNotExist,
+}
+
+pub fn create_simple_class(lead_origin: u64, class_type: ClassType) -> DispatchResult {
+    let mut class = create_class_with_default_permissions();
+    match class_type {
+        ClassType::Valid => (),
+        ClassType::NameTooShort => {
+            class.set_name(generate_text(
+                ClassNameLengthConstraint::get().min() as usize - 1,
+            ));
+        }
+        ClassType::NameTooLong => {
+            class.set_name(generate_text(
+                ClassNameLengthConstraint::get().max() as usize + 1,
+            ));
+        }
+        ClassType::DescriptionTooLong => {
+            class.set_description(generate_text(
+                ClassDescriptionLengthConstraint::get().max() as usize + 1,
+            ));
+        }
+        ClassType::DescriptionTooShort => {
+            class.set_description(generate_text(
+                ClassDescriptionLengthConstraint::get().min() as usize - 1,
+            ));
+        }
+        ClassType::InvalidMaximumEntitiesCount => {
+            class.set_maximum_entities_count(MaxNumberOfEntitiesPerClass::get() + 1);
+        }
+        ClassType::InvalidDefaultVoucherUpperBound => {
+            class.set_default_entity_creation_voucher_upper_bound(
+                IndividualEntitiesCreationLimit::get() + 1,
+            );
+        }
+        ClassType::DefaultVoucherUpperBoundExceedsMaximumEntitiesCount => {
+            class.set_maximum_entities_count(5);
+
+            class.set_maximum_entities_count(3);
+        }
+        ClassType::MaintainersLimitReached => {
+            let mut maintainers = BTreeSet::new();
+            for curator_group_id in 1..=(MaxNumberOfMaintainersPerClass::get() + 1) {
+                maintainers.insert(curator_group_id as CuratorGroupId);
+            }
+            class.get_permissions_mut().set_maintainers(maintainers);
+        }
+        ClassType::CuratorGroupDoesNotExist => {
+            let maintainers = BTreeSet::from_iter(vec![UNKNOWN_CURATOR_GROUP_ID].into_iter());
+            class.get_permissions_mut().set_maintainers(maintainers);
+        }
+    };
+    TestModule::create_class(
+        Origin::signed(lead_origin),
+        class.get_name().to_owned(),
+        class.get_description().to_owned(),
+        class.get_permissions_ref().to_owned(),
+        class.get_maximum_entities_count(),
+        class.get_default_entity_creation_voucher_upper_bound(),
+    )
+}
+
+pub fn create_class_with_default_permissions() -> Class<Runtime> {
+    Class::new(
+        ClassPermissions::default(),
+        generate_text(ClassNameLengthConstraint::get().max() as usize),
+        generate_text(ClassDescriptionLengthConstraint::get().max() as usize),
+        MaxNumberOfEntitiesPerClass::get(),
+        IndividualEntitiesCreationLimit::get(),
+    )
+}
+
+pub fn add_maintainer_to_class(
+    lead_origin: u64,
+    class_id: ClassId,
+    curator_group_id: CuratorGroupId,
+) -> DispatchResult {
+    TestModule::add_maintainer_to_class(Origin::signed(lead_origin), class_id, curator_group_id)
+}
+
+pub fn remove_maintainer_from_class(
+    lead_origin: u64,
+    class_id: ClassId,
+    curator_group_id: CuratorGroupId,
+) -> DispatchResult {
+    TestModule::remove_maintainer_from_class(
+        Origin::signed(lead_origin),
+        class_id,
+        curator_group_id,
+    )
+}
+
+pub fn update_class_permissions(
+    lead_origin: u64,
+    class_id: ClassId,
+    updated_any_member: Option<bool>,
+    updated_entity_creation_blocked: Option<bool>,
+    updated_all_entity_property_values_locked: Option<bool>,
+    updated_maintainers: Option<BTreeSet<CuratorGroupId>>,
+) -> DispatchResult {
+    TestModule::update_class_permissions(
+        Origin::signed(lead_origin),
+        class_id,
+        updated_any_member,
+        updated_entity_creation_blocked,
+        updated_all_entity_property_values_locked,
+        updated_maintainers,
+    )
+}
+
+pub fn add_class_schema(
+    lead_origin: u64,
+    class_id: ClassId,
+    existing_properties: BTreeSet<PropertyId>,
+    new_properties: Vec<Property<Runtime>>,
+) -> DispatchResult {
+    TestModule::add_class_schema(
+        Origin::signed(lead_origin),
+        class_id,
+        existing_properties,
+        new_properties,
+    )
+}
+
+pub fn update_class_schema_status(
+    lead_origin: u64,
+    class_id: ClassId,
+    schema_id: SchemaId,
+    status: bool,
+) -> DispatchResult {
+    TestModule::update_class_schema_status(Origin::signed(lead_origin), class_id, schema_id, status)
+}
+
+pub fn next_class_id() -> ClassId {
+    TestModule::next_class_id()
+}
+
+pub fn class_by_id(class_id: ClassId) -> Class<Runtime> {
+    TestModule::class_by_id(class_id)
+}
+
+pub fn class_exists(class_id: ClassId) -> bool {
+    ClassById::<Runtime>::contains_key(class_id)
+}
+
+// Vouchers
+
+pub fn update_entity_creation_voucher(
+    lead_origin: u64,
+    class_id: ClassId,
+    controller: EntityController<Runtime>,
+    maximum_entities_count: EntityId,
+) -> DispatchResult {
+    TestModule::update_entity_creation_voucher(
+        Origin::signed(lead_origin),
+        class_id,
+        controller,
+        maximum_entities_count,
+    )
+}
+
+pub fn entity_creation_vouchers(
+    class_id: ClassId,
+    entity_controller: &EntityController<Runtime>,
+) -> EntityCreationVoucher<Runtime> {
+    TestModule::entity_creation_vouchers(class_id, entity_controller)
+}
+
+pub fn entity_creation_voucher_exists(
+    class_id: ClassId,
+    entity_controller: &EntityController<Runtime>,
+) -> bool {
+    EntityCreationVouchers::<Runtime>::contains_key(class_id, entity_controller)
+}
+
+// Entities
+
+pub fn entity_exists(entity_id: EntityId) -> bool {
+    EntityById::<Runtime>::contains_key(entity_id)
+}
+
+pub fn entity_by_id(entity_id: EntityId) -> Entity<Runtime> {
+    TestModule::entity_by_id(entity_id)
+}
+
+pub fn next_entity_id() -> EntityId {
+    TestModule::next_entity_id()
+}
+
+pub fn create_entity(origin: u64, class_id: ClassId, actor: Actor<Runtime>) -> DispatchResult {
+    TestModule::create_entity(Origin::signed(origin), class_id, actor)
+}
+
+pub fn remove_entity(origin: u64, actor: Actor<Runtime>, entity_id: EntityId) -> DispatchResult {
+    TestModule::remove_entity(Origin::signed(origin), actor, entity_id)
+}
+
+pub fn update_entity_permissions(
+    lead_origin: u64,
+    entity_id: EntityId,
+    updated_frozen: Option<bool>,
+    updated_referenceable: Option<bool>,
+) -> DispatchResult {
+    TestModule::update_entity_permissions(
+        Origin::signed(lead_origin),
+        entity_id,
+        updated_frozen,
+        updated_referenceable,
+    )
+}
+
+pub fn add_schema_support_to_entity(
+    origin: u64,
+    actor: Actor<Runtime>,
+    entity_id: EntityId,
+    schema_id: SchemaId,
+    new_property_values: BTreeMap<PropertyId, InputPropertyValue<Runtime>>,
+) -> DispatchResult {
+    TestModule::add_schema_support_to_entity(
+        Origin::signed(origin),
+        actor,
+        entity_id,
+        schema_id,
+        new_property_values,
+    )
+}
+
+pub fn update_entity_property_values(
+    origin: u64,
+    actor: Actor<Runtime>,
+    entity_id: EntityId,
+    new_property_values: BTreeMap<PropertyId, InputPropertyValue<Runtime>>,
+) -> DispatchResult {
+    TestModule::update_entity_property_values(
+        Origin::signed(origin),
+        actor,
+        entity_id,
+        new_property_values,
+    )
+}
+
+pub fn clear_entity_property_vector(
+    origin: u64,
+    actor: Actor<Runtime>,
+    entity_id: EntityId,
+    in_class_schema_property_id: PropertyId,
+) -> DispatchResult {
+    TestModule::clear_entity_property_vector(
+        Origin::signed(origin),
+        actor,
+        entity_id,
+        in_class_schema_property_id,
+    )
+}
+
+pub fn insert_at_entity_property_vector(
+    origin: u64,
+    actor: Actor<Runtime>,
+    entity_id: EntityId,
+    in_class_schema_property_id: PropertyId,
+    index_in_property_vector: VecMaxLength,
+    property_value: InputValue<Runtime>,
+    nonce: Nonce,
+) -> DispatchResult {
+    TestModule::insert_at_entity_property_vector(
+        Origin::signed(origin),
+        actor,
+        entity_id,
+        in_class_schema_property_id,
+        index_in_property_vector,
+        property_value,
+        nonce,
+    )
+}
+
+pub fn remove_at_entity_property_vector(
+    origin: u64,
+    actor: Actor<Runtime>,
+    entity_id: EntityId,
+    in_class_schema_property_id: PropertyId,
+    index_in_property_vector: VecMaxLength,
+    nonce: Nonce,
+) -> DispatchResult {
+    TestModule::remove_at_entity_property_vector(
+        Origin::signed(origin),
+        actor,
+        entity_id,
+        in_class_schema_property_id,
+        index_in_property_vector,
+        nonce,
+    )
+}
+
+pub fn transfer_entity_ownership(
+    origin: u64,
+    entity_id: EntityId,
+    new_controller: EntityController<Runtime>,
+    new_property_value_references_with_same_owner_flag_set: BTreeMap<
+        PropertyId,
+        InputPropertyValue<Runtime>,
+    >,
+) -> DispatchResult {
+    TestModule::transfer_entity_ownership(
+        Origin::signed(origin),
+        entity_id,
+        new_controller,
+        new_property_value_references_with_same_owner_flag_set,
+    )
+}
+
+// Transaction
+
+pub fn transaction(
+    origin: u64,
+    actor: Actor<Runtime>,
+    operations: Vec<OperationType<Runtime>>,
+) -> DispatchResult {
+    TestModule::transaction(Origin::signed(origin), actor, operations)
+}
+
+pub enum InvalidPropertyType {
+    NameTooLong,
+    NameTooShort,
+    DescriptionTooLong,
+    DescriptionTooShort,
+    TextIsTooLong,
+    TextHashIsTooLong,
+    VecIsTooLong,
+}
+
+impl<T: Trait> Property<T> {
+    pub fn default_with_name(name_len: usize) -> Self {
+        let name = generate_text(name_len);
+        let description = generate_text(PropertyDescriptionLengthConstraint::get().min() as usize);
+        Self {
+            name,
+            description,
+            ..Property::<T>::default()
+        }
+    }
+
+    pub fn with_name_and_type(
+        name_len: usize,
+        property_type: PropertyType<T>,
+        required: bool,
+        unique: bool,
+    ) -> Self {
+        let name = generate_text(name_len);
+        let description = generate_text(PropertyDescriptionLengthConstraint::get().min() as usize);
+        Self {
+            name,
+            description,
+            property_type,
+            required,
+            unique,
+            ..Property::<T>::default()
+        }
+    }
+
+    pub fn invalid(invalid_property_type: InvalidPropertyType) -> Property<Runtime> {
+        let mut default_property = Property::<Runtime>::default_with_name(
+            PropertyNameLengthConstraint::get().min() as usize,
+        );
+        match invalid_property_type {
+            InvalidPropertyType::NameTooLong => {
+                default_property.name =
+                    generate_text(PropertyNameLengthConstraint::get().max() as usize + 1);
+            }
+            InvalidPropertyType::NameTooShort => {
+                default_property.name =
+                    generate_text(PropertyNameLengthConstraint::get().min() as usize - 1);
+            }
+            InvalidPropertyType::DescriptionTooLong => {
+                default_property.description =
+                    generate_text(PropertyDescriptionLengthConstraint::get().max() as usize + 1);
+            }
+            InvalidPropertyType::DescriptionTooShort => {
+                default_property.description =
+                    generate_text(PropertyDescriptionLengthConstraint::get().min() as usize - 1);
+            }
+            InvalidPropertyType::TextIsTooLong => {
+                default_property.property_type =
+                    PropertyType::<Runtime>::single_text(TextMaxLengthConstraint::get() + 1);
+            }
+            InvalidPropertyType::TextHashIsTooLong => {
+                if let Some(hashed_text_max_len) = HashedTextMaxLengthConstraint::get() {
+                    default_property.property_type =
+                        PropertyType::<Runtime>::single_text_hash(Some(hashed_text_max_len + 1));
+                }
+            }
+            InvalidPropertyType::VecIsTooLong => {
+                default_property.property_type = PropertyType::<Runtime>::vec_reference(
+                    FIRST_CLASS_ID,
+                    true,
+                    VecMaxLengthConstraint::get() + 1,
+                );
+            }
+        };
+        default_property
+    }
+}
+
+impl<T: Trait> PropertyType<T> {
+    pub fn vec_reference(
+        class_id: ClassId,
+        same_controller: bool,
+        max_length: VecMaxLength,
+    ) -> PropertyType<Runtime> {
+        let vec_type = Type::<Runtime>::Reference(class_id, same_controller);
+        let vec_reference = VecPropertyType::<Runtime>::new(vec_type, max_length);
+        PropertyType::<Runtime>::Vector(vec_reference)
+    }
+
+    pub fn vec_text(
+        text_max_len: TextMaxLength,
+        vec_max_length: VecMaxLength,
+    ) -> PropertyType<Runtime> {
+        let vec_type = Type::<Runtime>::Text(text_max_len);
+        let vec_text = VecPropertyType::<Runtime>::new(vec_type, vec_max_length);
+        PropertyType::<Runtime>::Vector(vec_text)
+    }
+
+    pub fn single_text(text_max_len: TextMaxLength) -> PropertyType<Runtime> {
+        let text_type = Type::<Runtime>::Text(text_max_len);
+        PropertyType::<Runtime>::Single(text_type)
+    }
+
+    pub fn single_text_hash(text_hash_max_len: HashedTextMaxLength) -> PropertyType<Runtime> {
+        let text_type = Type::<Runtime>::Hash(text_hash_max_len);
+        PropertyType::<Runtime>::Single(text_type)
+    }
+
+    pub fn vec_text_hash(
+        text_hash_max_len: HashedTextMaxLength,
+        vec_max_length: VecMaxLength,
+    ) -> PropertyType<Runtime> {
+        let vec_type = Type::<Runtime>::Hash(text_hash_max_len);
+        let vec_text_hash = VecPropertyType::<Runtime>::new(vec_type, vec_max_length);
+        PropertyType::<Runtime>::Vector(vec_text_hash)
+    }
+}
+
+impl<T: Trait> InputPropertyValue<T> {
+    pub fn vec_reference(entity_ids: Vec<EntityId>) -> InputPropertyValue<Runtime> {
+        let vec_value = VecInputValue::<Runtime>::Reference(entity_ids);
+        InputPropertyValue::<Runtime>::Vector(vec_value)
+    }
+
+    pub fn vec_text(texts: Vec<Vec<u8>>) -> InputPropertyValue<Runtime> {
+        let vec_value = VecInputValue::<Runtime>::Text(texts);
+        InputPropertyValue::<Runtime>::Vector(vec_value)
+    }
+
+    pub fn vec_text_to_hash(texts: Vec<Vec<u8>>) -> InputPropertyValue<Runtime> {
+        let vec_value = VecInputValue::<Runtime>::TextToHash(texts);
+        InputPropertyValue::<Runtime>::Vector(vec_value)
+    }
+
+    pub fn single_text(text_len: TextMaxLength) -> InputPropertyValue<Runtime> {
+        let text_value = InputValue::<Runtime>::Text(generate_text(text_len as usize));
+        InputPropertyValue::<Runtime>::Single(text_value)
+    }
+
+    pub fn single_text_to_hash(text_len: TextMaxLength) -> InputPropertyValue<Runtime> {
+        let text_value = InputValue::<Runtime>::TextToHash(generate_text(text_len as usize));
+        InputPropertyValue::<Runtime>::Single(text_value)
+    }
+}
+
+impl From<InboundReferenceCounter> for EntityReferenceCounterSideEffect {
+    fn from(inbound_rc: InboundReferenceCounter) -> Self {
+        Self {
+            total: inbound_rc.total as i32,
+            same_owner: inbound_rc.same_owner as i32,
+        }
+    }
+}
+
+impl EntityReferenceCounterSideEffect {
+    pub fn new(total: i32, same_owner: i32) -> Self {
+        Self { total, same_owner }
+    }
+}
+
+impl PropertyLockingPolicy {
+    pub fn new(is_locked_from_maintainer: bool, is_locked_from_controller: bool) -> Self {
+        Self {
+            is_locked_from_maintainer,
+            is_locked_from_controller,
+        }
+    }
+}
+
+// Assign back to type variables so we can make dispatched calls of these modules later.
+pub type System = system::Module<Runtime>;
+pub type TestModule = Module<Runtime>;
+
+// Recommendation from Parity on testing on_finalize
+// https://substrate.dev/docs/en/next/development/module/tests
+pub fn run_to_block(n: u64) {
+    while System::block_number() < n {
+        <System as OnFinalize<u64>>::on_finalize(System::block_number());
+        <TestModule as OnFinalize<u64>>::on_finalize(System::block_number());
+        System::set_block_number(System::block_number() + 1);
+        <System as OnInitialize<u64>>::on_initialize(System::block_number());
+        <TestModule as OnInitialize<u64>>::on_initialize(System::block_number());
+    }
+}

+ 141 - 0
runtime-modules/content-directory/src/operations.rs

@@ -0,0 +1,141 @@
+use crate::{Error, InputPropertyValue, InputValue, PropertyId, SchemaId, Trait, VecInputValue};
+use codec::{Decode, Encode};
+use sp_std::collections::btree_map::BTreeMap;
+use sp_std::prelude::*;
+
+/// Parametrized entity property value
+#[derive(Encode, Decode, Eq, PartialEq, Clone)]
+pub enum ParametrizedPropertyValue<T: Trait> {
+    /// Same fields as normal InputPropertyValue
+    InputPropertyValue(InputPropertyValue<T>),
+
+    /// This is the index of an operation creating an entity in the transaction/batch operations
+    InternalEntityJustAdded(u32), // should really be usize but it doesn't have Encode/Decode support
+
+    /// Vector of mix of Entities already existing and just added in a recent operation
+    InternalEntityVec(Vec<ParameterizedEntity<T>>),
+}
+
+/// Parametrized entity
+#[derive(Encode, Decode, Eq, PartialEq, Clone)]
+pub enum ParameterizedEntity<T: Trait> {
+    InternalEntityJustAdded(u32),
+    ExistingEntity(T::EntityId),
+}
+
+/// Parametrized class property value
+#[derive(Encode, Decode, Eq, PartialEq, Clone)]
+pub struct ParametrizedClassPropertyValue<T: Trait> {
+    /// Index is into properties vector of class.
+    pub in_class_index: PropertyId,
+
+    /// InputValue of property with index `in_class_index` in a given class.
+    pub value: ParametrizedPropertyValue<T>,
+}
+
+/// Operation, that represents `Entity` creation
+#[derive(Encode, Decode, Eq, PartialEq, Clone)]
+pub struct CreateEntityOperation<T: Trait> {
+    /// Class of an Entity
+    pub class_id: T::ClassId,
+}
+
+/// Operation, that represents property values update
+#[derive(Encode, Decode, Eq, PartialEq, Clone)]
+pub struct UpdatePropertyValuesOperation<T: Trait> {
+    /// Entity id to perfrom operation
+    pub entity_id: ParameterizedEntity<T>,
+    /// Property values, that should be updated
+    pub new_parametrized_property_values: Vec<ParametrizedClassPropertyValue<T>>,
+}
+
+/// Operation, that represents adding `Entity` `Schema` support
+#[derive(Encode, Decode, Eq, PartialEq, Clone)]
+pub struct AddSchemaSupportToEntityOperation<T: Trait> {
+    /// Entity id to perfrom operation
+    pub entity_id: ParameterizedEntity<T>,
+    /// Schema id defined on `Class` level to be added to the `Entity`
+    pub schema_id: SchemaId,
+    /// Property values, that should be added for the underlying schema_id
+    pub parametrized_property_values: Vec<ParametrizedClassPropertyValue<T>>,
+}
+
+/// The type of operation performed
+#[derive(Encode, Decode, Eq, PartialEq, Clone)]
+pub enum OperationType<T: Trait> {
+    CreateEntity(CreateEntityOperation<T>),
+    UpdatePropertyValues(UpdatePropertyValuesOperation<T>),
+    AddSchemaSupportToEntity(AddSchemaSupportToEntityOperation<T>),
+}
+
+impl<T: Trait> core::fmt::Debug for OperationType<T> {
+    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        write!(formatter, "OperationType {:?}", self)
+    }
+}
+
+/// Retrieve entity_id of parametrized `Entity`
+pub fn parametrized_entity_to_entity_id<T: Trait>(
+    created_entities: &BTreeMap<usize, T::EntityId>,
+    entity: ParameterizedEntity<T>,
+) -> Result<T::EntityId, Error<T>> {
+    match entity {
+        ParameterizedEntity::ExistingEntity(entity_id) => Ok(entity_id),
+        ParameterizedEntity::InternalEntityJustAdded(op_index_u32) => {
+            let op_index = op_index_u32 as usize;
+            Ok(*created_entities
+                .get(&op_index)
+                .ok_or(Error::<T>::EntityNotCreatedByOperation)?)
+        }
+    }
+}
+
+/// Convert parametrized property values into property values
+pub fn parametrized_property_values_to_property_values<T: Trait>(
+    created_entities: &BTreeMap<usize, T::EntityId>,
+    parametrized_property_values: Vec<ParametrizedClassPropertyValue<T>>,
+) -> Result<BTreeMap<PropertyId, InputPropertyValue<T>>, Error<T>> {
+    let mut class_property_values = BTreeMap::new();
+
+    for parametrized_class_property_value in parametrized_property_values.into_iter() {
+        let property_value = match parametrized_class_property_value.value {
+            ParametrizedPropertyValue::InputPropertyValue(value) => value,
+            ParametrizedPropertyValue::InternalEntityJustAdded(
+                entity_created_in_operation_index,
+            ) => {
+                // Verify that referenced entity was indeed created created
+                let op_index = entity_created_in_operation_index as usize;
+                let entity_id = created_entities
+                    .get(&op_index)
+                    .ok_or(Error::<T>::EntityNotCreatedByOperation)?;
+                InputPropertyValue::Single(InputValue::Reference(*entity_id))
+            }
+            ParametrizedPropertyValue::InternalEntityVec(parametrized_entities) => {
+                let mut entities: Vec<T::EntityId> = vec![];
+
+                for parametrized_entity in parametrized_entities.into_iter() {
+                    match parametrized_entity {
+                        ParameterizedEntity::ExistingEntity(id) => entities.push(id),
+                        ParameterizedEntity::InternalEntityJustAdded(
+                            entity_created_in_operation_index,
+                        ) => {
+                            let op_index = entity_created_in_operation_index as usize;
+                            let entity_id = created_entities
+                                .get(&op_index)
+                                .ok_or(Error::<T>::EntityNotCreatedByOperation)?;
+                            entities.push(*entity_id);
+                        }
+                    }
+                }
+                InputPropertyValue::Vector(VecInputValue::Reference(entities))
+            }
+        };
+
+        class_property_values.insert(
+            parametrized_class_property_value.in_class_index,
+            property_value,
+        );
+    }
+
+    Ok(class_property_values)
+}

+ 127 - 0
runtime-modules/content-directory/src/permissions.rs

@@ -0,0 +1,127 @@
+mod class;
+mod curator_group;
+mod entity;
+mod entity_creation_voucher;
+
+pub use class::*;
+pub use curator_group::*;
+pub use entity::*;
+pub use entity_creation_voucher::*;
+
+pub use crate::errors::*;
+use crate::*;
+pub use codec::{Codec, Decode, Encode};
+use core::fmt::Debug;
+use frame_support::{ensure, Parameter};
+#[cfg(feature = "std")]
+pub use serde::{Deserialize, Serialize};
+use sp_arithmetic::traits::BaseArithmetic;
+use sp_runtime::traits::{MaybeSerializeDeserialize, Member};
+
+/// Model of authentication manager.
+pub trait ActorAuthenticator: system::Trait {
+    /// Curator identifier
+    type CuratorId: Parameter
+        + Member
+        + BaseArithmetic
+        + Codec
+        + Default
+        + Copy
+        + Clone
+        + MaybeSerializeDeserialize
+        + Eq
+        + PartialEq
+        + Ord;
+
+    /// Member identifier
+    type MemberId: Parameter
+        + Member
+        + BaseArithmetic
+        + Codec
+        + Default
+        + Copy
+        + Clone
+        + MaybeSerializeDeserialize
+        + Eq
+        + PartialEq
+        + Ord;
+
+    /// Curator group identifier
+    type CuratorGroupId: Parameter
+        + Member
+        + BaseArithmetic
+        + Codec
+        + Default
+        + Copy
+        + Clone
+        + MaybeSerializeDeserialize
+        + Eq
+        + PartialEq
+        + Ord;
+
+    /// Authorize actor as lead
+    fn is_lead(account_id: &Self::AccountId) -> bool;
+
+    /// Authorize actor as curator
+    fn is_curator(curator_id: &Self::CuratorId, account_id: &Self::AccountId) -> bool;
+
+    /// Authorize actor as member
+    fn is_member(member_id: &Self::MemberId, account_id: &Self::AccountId) -> bool;
+}
+
+/// Ensure curator authorization performed succesfully
+pub fn ensure_curator_auth_success<T: Trait>(
+    curator_id: &T::CuratorId,
+    account_id: &T::AccountId,
+) -> Result<(), Error<T>> {
+    ensure!(
+        T::is_curator(curator_id, account_id),
+        Error::<T>::CuratorAuthFailed
+    );
+    Ok(())
+}
+
+/// Ensure member authorization performed succesfully
+pub fn ensure_member_auth_success<T: Trait>(
+    member_id: &T::MemberId,
+    account_id: &T::AccountId,
+) -> Result<(), Error<T>> {
+    ensure!(
+        T::is_member(member_id, account_id),
+        Error::<T>::MemberAuthFailed
+    );
+    Ok(())
+}
+
+/// Ensure lead authorization performed succesfully
+pub fn ensure_lead_auth_success<T: Trait>(account_id: &T::AccountId) -> Result<(), Error<T>> {
+    ensure!(T::is_lead(account_id), Error::<T>::LeadAuthFailed);
+    Ok(())
+}
+
+/// Ensure given `Origin` is lead
+pub fn ensure_is_lead<T: Trait>(origin: T::Origin) -> DispatchResult {
+    let account_id = ensure_signed(origin)?;
+    Ok(ensure_lead_auth_success::<T>(&account_id)?)
+}
+
+/// Enum, representing all possible `Actor`s
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Eq, PartialEq, Clone, Copy)]
+pub enum Actor<T: Trait> {
+    Curator(T::CuratorGroupId, T::CuratorId),
+    Member(T::MemberId),
+    Lead,
+}
+
+impl<T: Trait> Default for Actor<T> {
+    fn default() -> Self {
+        Self::Lead
+    }
+}
+
+impl<T: Trait> core::fmt::Debug for Actor<T> {
+    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        write!(formatter, "Actor {:?}", self)
+    }
+}

+ 158 - 0
runtime-modules/content-directory/src/permissions/class.rs

@@ -0,0 +1,158 @@
+use super::*;
+
+/// Permissions for an instance of a `Class` in the versioned store.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Eq, PartialEq, Clone)]
+pub struct ClassPermissions<T: Trait> {
+    /// For this permission, the individual member is allowed to create the entity and become controller.
+    any_member: bool,
+
+    /// Whether to prevent everyone from creating an entity.
+    ///
+    /// This could be useful in order to quickly, and possibly temporarily, block new entity creation, without
+    /// having to tear down `can_create_entities`.
+    entity_creation_blocked: bool,
+
+    /// Whether to prevent everyone from updating entity properties.
+    ///
+    /// This could be useful in order to quickly, and probably temporarily, block any editing of entities,
+    /// rather than for example having to set, and later clear.
+    all_entity_property_values_locked: bool,
+
+    /// Current class maintainer curator groups
+    maintainers: BTreeSet<T::CuratorGroupId>,
+}
+
+impl<T: Trait> core::fmt::Debug for ClassPermissions<T> {
+    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        write!(formatter, "ClassPermissions {:?}", self)
+    }
+}
+
+impl<T: Trait> Default for ClassPermissions<T> {
+    fn default() -> Self {
+        Self {
+            any_member: false,
+            entity_creation_blocked: false,
+            all_entity_property_values_locked: false,
+            maintainers: BTreeSet::new(),
+        }
+    }
+}
+
+impl<T: Trait> ClassPermissions<T> {
+    /// Retieve `all_entity_property_values_locked` status
+    pub fn all_entity_property_values_locked(&self) -> bool {
+        self.all_entity_property_values_locked
+    }
+
+    /// Retieve `any_member` status
+    pub fn any_member_status(&self) -> bool {
+        self.any_member
+    }
+
+    /// Check if given `curator_group_id` is maintainer of current `Class`
+    pub fn is_maintainer(&self, curator_group_id: &T::CuratorGroupId) -> bool {
+        self.maintainers.contains(curator_group_id)
+    }
+
+    /// Get `Class` maintainers by reference
+    pub fn get_maintainers(&self) -> &BTreeSet<T::CuratorGroupId> {
+        &self.maintainers
+    }
+
+    /// Get `Class` maintainers by mutable reference
+    pub fn get_maintainers_mut(&mut self) -> &mut BTreeSet<T::CuratorGroupId> {
+        &mut self.maintainers
+    }
+
+    /// Set `entity_creation_blocked` flag, as provided
+    pub fn set_entity_creation_blocked(&mut self, entity_creation_blocked: bool) {
+        self.entity_creation_blocked = entity_creation_blocked
+    }
+
+    /// Set `all_entity_property_values_locked` flag, as provided
+    pub fn set_all_entity_property_values_locked(
+        &mut self,
+        all_entity_property_values_locked: bool,
+    ) {
+        self.all_entity_property_values_locked = all_entity_property_values_locked
+    }
+
+    /// Set `any_member` flag, as provided
+    pub fn set_any_member_status(&mut self, any_member: bool) {
+        self.any_member = any_member;
+    }
+
+    /// Update `maintainers` set with provided one
+    pub fn set_maintainers(&mut self, maintainers: BTreeSet<T::CuratorGroupId>) {
+        self.maintainers = maintainers
+    }
+
+    /// Ensure provided actor can create entities of current `Class`
+    pub fn ensure_can_create_entities(
+        &self,
+        account_id: &T::AccountId,
+        actor: &Actor<T>,
+    ) -> Result<(), Error<T>> {
+        let can_create = match &actor {
+            Actor::Lead => {
+                // Ensure lead authorization performed succesfully
+                ensure_lead_auth_success::<T>(account_id)?;
+                true
+            }
+            Actor::Member(member_id) if self.any_member => {
+                // Ensure member authorization performed succesfully
+                ensure_member_auth_success::<T>(member_id, account_id)?;
+                true
+            }
+            Actor::Curator(curator_group_id, curator_id)
+                if self.maintainers.contains(curator_group_id) =>
+            {
+                // Authorize curator, performing all checks to ensure curator can act
+                CuratorGroup::<T>::perform_curator_in_group_auth(
+                    curator_id,
+                    curator_group_id,
+                    account_id,
+                )?;
+                true
+            }
+            _ => false,
+        };
+        ensure!(can_create, Error::<T>::ActorCanNotCreateEntities);
+        Ok(())
+    }
+
+    /// Ensure entities creation is not blocked on `Class` level
+    pub fn ensure_entity_creation_not_blocked(&self) -> Result<(), Error<T>> {
+        ensure!(
+            !self.entity_creation_blocked,
+            Error::<T>::EntitiesCreationBlocked
+        );
+        Ok(())
+    }
+
+    /// Ensure maintainer, associated with given `curator_group_id` is already added to `maintainers` set
+    pub fn ensure_maintainer_exists(
+        &self,
+        curator_group_id: &T::CuratorGroupId,
+    ) -> Result<(), Error<T>> {
+        ensure!(
+            self.maintainers.contains(curator_group_id),
+            Error::<T>::MaintainerDoesNotExist
+        );
+        Ok(())
+    }
+
+    /// Ensure maintainer, associated with given `curator_group_id` is not yet added to `maintainers` set
+    pub fn ensure_maintainer_does_not_exist(
+        &self,
+        curator_group_id: &T::CuratorGroupId,
+    ) -> Result<(), Error<T>> {
+        ensure!(
+            !self.maintainers.contains(curator_group_id),
+            Error::<T>::MaintainerAlreadyExists
+        );
+        Ok(())
+    }
+}

+ 116 - 0
runtime-modules/content-directory/src/permissions/curator_group.rs

@@ -0,0 +1,116 @@
+use super::*;
+
+/// A group, that consists of `curators` set
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
+#[derive(Encode, Decode, Eq, PartialEq, Clone)]
+pub struct CuratorGroup<T: Trait> {
+    /// Curators set, associated with a iven curator group
+    curators: BTreeSet<T::CuratorId>,
+
+    /// When `false`, curator in a given group is forbidden to act
+    active: bool,
+
+    /// Used to count the number of `Class`(es), given curator group maintains
+    number_of_classes_maintained: u32,
+}
+
+impl<T: Trait> Default for CuratorGroup<T> {
+    fn default() -> Self {
+        Self {
+            curators: BTreeSet::new(),
+            // default curator group status right after creation
+            active: false,
+            number_of_classes_maintained: 0,
+        }
+    }
+}
+
+impl<T: Trait> CuratorGroup<T> {
+    /// Check if `CuratorGroup` contains curator under given `curator_id`
+    pub fn is_curator(&self, curator_id: &T::CuratorId) -> bool {
+        self.curators.contains(curator_id)
+    }
+
+    /// Check if `CuratorGroup` is active
+    pub fn is_active(&self) -> bool {
+        self.active
+    }
+
+    /// Set `CuratorGroup` status as provided
+    pub fn set_status(&mut self, is_active: bool) {
+        self.active = is_active
+    }
+
+    /// Retrieve set of all curator_ids related to `CuratorGroup` by reference
+    pub fn get_curators(&self) -> &BTreeSet<T::CuratorId> {
+        &self.curators
+    }
+
+    /// Retrieve set of all curator_ids related to `CuratorGroup` by mutable  reference
+    pub fn get_curators_mut(&mut self) -> &mut BTreeSet<T::CuratorId> {
+        &mut self.curators
+    }
+
+    /// Increment number of classes `CuratorGroup` maintains
+    pub fn increment_number_of_classes_maintained_count(&mut self) {
+        self.number_of_classes_maintained += 1;
+    }
+
+    /// Decrement number of classes `CuratorGroup` maintains
+    pub fn decrement_number_of_classes_maintained_count(&mut self) {
+        self.number_of_classes_maintained -= 1;
+    }
+
+    /// Ensure curator group does not maintain any `Class`
+    pub fn ensure_curator_group_maintains_no_classes(&self) -> Result<(), Error<T>> {
+        ensure!(
+            self.number_of_classes_maintained == 0,
+            Error::<T>::CuratorGroupRemovalForbidden
+        );
+        Ok(())
+    }
+
+    /// Ensure `MaxNumberOfCuratorsPerGroup` constraint satisfied
+    pub fn ensure_max_number_of_curators_limit_not_reached(&self) -> Result<(), Error<T>> {
+        ensure!(
+            self.curators.len() < T::MaxNumberOfCuratorsPerGroup::get() as usize,
+            Error::<T>::CuratorsPerGroupLimitReached
+        );
+        Ok(())
+    }
+
+    /// Ensure curator under given `curator_id` exists in `CuratorGroup`
+    pub fn ensure_curator_in_group_exists(
+        &self,
+        curator_id: &T::CuratorId,
+    ) -> Result<(), Error<T>> {
+        ensure!(
+            self.get_curators().contains(curator_id),
+            Error::<T>::CuratorIsNotAMemberOfGivenCuratorGroup
+        );
+        Ok(())
+    }
+
+    /// Authorize curator, performing all checks to ensure curator can act
+    pub fn perform_curator_in_group_auth(
+        curator_id: &T::CuratorId,
+        curator_group_id: &T::CuratorGroupId,
+        account_id: &T::AccountId,
+    ) -> Result<(), Error<T>> {
+        // Ensure curator authorization performed succesfully
+        ensure_curator_auth_success::<T>(curator_id, account_id)?;
+
+        // Retrieve corresponding curator group
+        let curator_group = Module::<T>::curator_group_by_id(curator_group_id);
+
+        // Ensure curator group is active
+        ensure!(
+            curator_group.is_active(),
+            Error::<T>::CuratorGroupIsNotActive
+        );
+
+        // Ensure curator under given curator_id exists in CuratorGroup
+        Self::ensure_curator_in_group_exists(&curator_group, curator_id)?;
+        Ok(())
+    }
+}

+ 176 - 0
runtime-modules/content-directory/src/permissions/entity.rs

@@ -0,0 +1,176 @@
+use super::*;
+
+/// Owner of an `Entity`.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq)]
+pub enum EntityController<T: Trait> {
+    Maintainers,
+    Member(T::MemberId),
+    Lead,
+}
+
+impl<T: Trait> EntityController<T> {
+    /// Create `EntityController` enum representation, using provided `Actor`
+    pub fn from_actor(actor: &Actor<T>) -> Self {
+        match &actor {
+            Actor::Lead => Self::Lead,
+            Actor::Member(member_id) => Self::Member(*member_id),
+            Actor::Curator(_, _) => Self::Maintainers,
+        }
+    }
+}
+
+impl<T: Trait> Default for EntityController<T> {
+    fn default() -> Self {
+        Self::Lead
+    }
+}
+
+impl<T: Trait> core::fmt::Debug for EntityController<T> {
+    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        write!(formatter, "EntityController {:?}", self)
+    }
+}
+
+/// Permissions for a given entity.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
+#[derive(Encode, Decode, Clone, PartialEq, Eq)]
+pub struct EntityPermissions<T: Trait> {
+    /// Current controller, which is initially set based on who created entity
+    pub controller: EntityController<T>,
+
+    /// Forbid groups to mutate any property value.
+    /// Can be useful to use in concert with some curation censorship policy
+    pub frozen: bool,
+
+    /// Prevent from being referenced by any entity (including self-references).
+    /// Can be useful to use in concert with some curation censorship policy,
+    /// e.g. to block content from being included in some public playlist.
+    pub referenceable: bool,
+}
+
+impl<T: Trait> Default for EntityPermissions<T> {
+    fn default() -> Self {
+        Self {
+            controller: EntityController::<T>::default(),
+            frozen: false,
+            referenceable: true,
+        }
+    }
+}
+
+impl<T: Trait> EntityPermissions<T> {
+    /// Create an instance of `EntityPermissions` with `EntityController` equal to provided one
+    pub fn default_with_controller(controller: EntityController<T>) -> Self {
+        Self {
+            controller,
+            ..EntityPermissions::default()
+        }
+    }
+
+    /// Set current `controller` as provided
+    pub fn set_conroller(&mut self, controller: EntityController<T>) {
+        self.controller = controller
+    }
+
+    /// Check if inner `controller` is equal to the provided one
+    pub fn controller_is_equal_to(&self, new_entity_controller: &EntityController<T>) -> bool {
+        self.controller == *new_entity_controller
+    }
+
+    /// Set `frozen` flag as provided
+    pub fn set_frozen(&mut self, frozen: bool) {
+        self.frozen = frozen
+    }
+
+    /// Set `referenceable` flag as provided
+    pub fn set_referencable(&mut self, referenceable: bool) {
+        self.referenceable = referenceable;
+    }
+
+    /// Retrieve `referenceable` flag
+    pub fn is_referancable(&self) -> bool {
+        self.referenceable
+    }
+
+    /// Get current `controller` by reference
+    pub fn get_controller(&self) -> &EntityController<T> {
+        &self.controller
+    }
+
+    /// Ensure actor with given `EntityAccessLevel` can remove entity
+    pub fn ensure_group_can_remove_entity(access_level: EntityAccessLevel) -> Result<(), Error<T>> {
+        match access_level {
+            EntityAccessLevel::EntityController => Ok(()),
+            EntityAccessLevel::EntityControllerAndMaintainer => Ok(()),
+            _ => Err(Error::<T>::EntityRemovalAccessDenied),
+        }
+    }
+
+    /// Ensure provided new_entity_controller is not equal to current one
+    pub fn ensure_controllers_are_not_equal(
+        &self,
+        new_entity_controller: &EntityController<T>,
+    ) -> Result<(), Error<T>> {
+        ensure!(
+            !self.controller_is_equal_to(new_entity_controller),
+            Error::<T>::ProvidedEntityControllerIsEqualToTheCurrentOne
+        );
+        Ok(())
+    }
+}
+
+/// Type, derived from dispatchable call, identifies the caller
+#[derive(Encode, Decode, Eq, PartialEq, Ord, PartialOrd, Clone, Copy)]
+pub enum EntityAccessLevel {
+    /// Caller identified as the entity maintainer
+    EntityMaintainer,
+
+    /// Caller identified as the entity controller
+    EntityController,
+
+    /// Caller, that can act as controller and maintainer simultaneously
+    /// (can be useful, when controller and maintainer have features, that do not intersect)
+    EntityControllerAndMaintainer,
+}
+
+impl EntityAccessLevel {
+    /// Derives the `EntityAccessLevel` for the actor, attempting to act.
+    pub fn derive<T: Trait>(
+        account_id: &T::AccountId,
+        entity_permissions: &EntityPermissions<T>,
+        class_permissions: &ClassPermissions<T>,
+        actor: &Actor<T>,
+    ) -> Result<Self, Error<T>> {
+        let controller = EntityController::<T>::from_actor(actor);
+        match actor {
+            Actor::Lead if entity_permissions.controller_is_equal_to(&controller) => {
+                // Ensure lead authorization performed succesfully
+                ensure_lead_auth_success::<T>(account_id).map(|_| Self::EntityController)
+            }
+            Actor::Member(member_id) if entity_permissions.controller_is_equal_to(&controller) => {
+                // Ensure member authorization performed succesfully
+                ensure_member_auth_success::<T>(member_id, account_id)
+                    .map(|_| Self::EntityController)
+            }
+            Actor::Curator(curator_group_id, curator_id) => {
+                // Authorize curator, performing all checks to ensure curator can act
+                CuratorGroup::<T>::perform_curator_in_group_auth(
+                    curator_id,
+                    curator_group_id,
+                    account_id,
+                )?;
+                match (
+                    entity_permissions.controller_is_equal_to(&controller),
+                    class_permissions.is_maintainer(curator_group_id),
+                ) {
+                    (true, true) => Ok(Self::EntityControllerAndMaintainer),
+                    (false, true) => Ok(Self::EntityMaintainer),
+                    // Curator cannot be controller, but not maintainer simultaneously
+                    _ => Err(Error::<T>::EntityAccessDenied),
+                }
+            }
+            _ => Err(Error::<T>::EntityAccessDenied),
+        }
+    }
+}

+ 56 - 0
runtime-modules/content-directory/src/permissions/entity_creation_voucher.rs

@@ -0,0 +1,56 @@
+use super::*;
+
+/// A voucher for `Entity` creation
+#[derive(Encode, Decode, Clone, Copy, Debug, PartialEq, Eq)]
+pub struct EntityCreationVoucher<T: Trait> {
+    /// How many are allowed in total
+    pub maximum_entities_count: T::EntityId,
+
+    /// How many have currently been created
+    pub entities_created: T::EntityId,
+}
+
+impl<T: Trait> Default for EntityCreationVoucher<T> {
+    fn default() -> Self {
+        Self {
+            maximum_entities_count: T::EntityId::zero(),
+            entities_created: T::EntityId::zero(),
+        }
+    }
+}
+
+impl<T: Trait> EntityCreationVoucher<T> {
+    /// Create a new instance of `EntityCreationVoucher` with specified limit
+    pub fn new(maximum_entities_count: T::EntityId) -> Self {
+        Self {
+            maximum_entities_count,
+            entities_created: T::EntityId::zero(),
+        }
+    }
+
+    /// Set new `maximum_entities_count` limit
+    pub fn set_maximum_entities_count(&mut self, maximum_entities_count: T::EntityId) {
+        self.maximum_entities_count = maximum_entities_count
+    }
+
+    /// Increase `entities_created` by 1
+    pub fn increment_created_entities_count(&mut self) {
+        self.entities_created += T::EntityId::one();
+    }
+
+    /// Decrease `entities_created` by 1
+    pub fn decrement_created_entities_count(&mut self) {
+        self.entities_created -= T::EntityId::one();
+    }
+
+    /// Check if `entities_created` is less than `maximum_entities_count` limit set to this `EntityCreationVoucher`
+    pub fn limit_not_reached(&self) -> bool {
+        self.entities_created < self.maximum_entities_count
+    }
+
+    /// Ensure voucher limit not reached
+    pub fn ensure_voucher_limit_not_reached(&self) -> Result<(), Error<T>> {
+        ensure!(self.limit_not_reached(), Error::<T>::VoucherLimitReached);
+        Ok(())
+    }
+}

+ 91 - 0
runtime-modules/content-directory/src/schema.rs

@@ -0,0 +1,91 @@
+mod convert;
+mod input;
+mod output;
+mod property;
+
+pub use convert::*;
+pub use input::*;
+pub use output::*;
+pub use property::*;
+
+pub use crate::{permissions::EntityAccessLevel, *};
+pub use codec::{Decode, Encode};
+use core::ops::Deref;
+#[cfg(feature = "std")]
+pub use serde::{Deserialize, Serialize};
+
+/// Type identificator for schema id
+pub type SchemaId = u16;
+
+/// A schema defines what properties describe an entity
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
+#[derive(Encode, Decode, Clone, PartialEq, Eq)]
+pub struct Schema {
+    /// Indices into properties vector for the corresponding class.
+    properties: BTreeSet<PropertyId>,
+    /// If schema can be added to an entity
+    is_active: bool,
+}
+
+impl Default for Schema {
+    fn default() -> Self {
+        Self {
+            properties: BTreeSet::new(),
+            // Default schema status
+            is_active: true,
+        }
+    }
+}
+
+impl Schema {
+    /// Create new schema with provided properties
+    pub fn new(properties: BTreeSet<PropertyId>) -> Self {
+        Self {
+            properties,
+            // Default schema status
+            is_active: true,
+        }
+    }
+
+    /// If `Schema` can be added to an entity
+    pub fn is_active(&self) -> bool {
+        self.is_active
+    }
+
+    /// Ensure schema in `active` status
+    pub fn ensure_is_active<T: Trait>(&self) -> Result<(), Error<T>> {
+        ensure!(self.is_active, Error::<T>::ClassSchemaNotActive);
+        Ok(())
+    }
+
+    /// Get `Schema` `properties` by reference
+    pub fn get_properties(&self) -> &BTreeSet<PropertyId> {
+        &self.properties
+    }
+
+    /// Ensure keys of provided `property_values` are valid indices of current `Schema`
+    pub fn ensure_has_properties<T: Trait>(
+        &self,
+        property_values: &BTreeMap<PropertyId, InputPropertyValue<T>>,
+    ) -> Result<(), Error<T>> {
+        let property_value_indices: BTreeSet<PropertyId> =
+            property_values.keys().cloned().collect();
+
+        ensure!(
+            property_value_indices.is_subset(&self.properties),
+            Error::<T>::SchemaDoesNotContainProvidedPropertyId
+        );
+
+        Ok(())
+    }
+
+    /// Get `Schema` `properties` by mutable reference
+    pub fn get_properties_mut(&mut self) -> &mut BTreeSet<PropertyId> {
+        &mut self.properties
+    }
+
+    /// Set `Schema`'s `is_active` flag as provided
+    pub fn set_status(&mut self, is_active: bool) {
+        self.is_active = is_active;
+    }
+}

+ 62 - 0
runtime-modules/content-directory/src/schema/convert.rs

@@ -0,0 +1,62 @@
+use super::*;
+use sp_runtime::traits::Hash;
+
+impl<T: Trait> From<InputPropertyValue<T>> for StoredPropertyValue<T> {
+    fn from(input_property_value: InputPropertyValue<T>) -> Self {
+        match input_property_value {
+            InputPropertyValue::Single(input_value) => {
+                StoredPropertyValue::Single(input_value.into())
+            }
+            InputPropertyValue::Vector(vector_input_value) => {
+                let vec_output_property_value =
+                    VecStoredPropertyValue::new(vector_input_value.into(), T::Nonce::default());
+                StoredPropertyValue::Vector(vec_output_property_value)
+            }
+        }
+    }
+}
+
+impl<T: Trait> From<InputValue<T>> for StoredValue<T> {
+    fn from(input_value: InputValue<T>) -> Self {
+        match input_value {
+            InputValue::Bool(value) => StoredValue::Bool(value),
+            InputValue::Uint16(value) => StoredValue::Uint16(value),
+            InputValue::Uint32(value) => StoredValue::Uint32(value),
+            InputValue::Uint64(value) => StoredValue::Uint64(value),
+            InputValue::Int16(value) => StoredValue::Int16(value),
+            InputValue::Int32(value) => StoredValue::Int32(value),
+            InputValue::Int64(value) => StoredValue::Int64(value),
+            InputValue::Text(value) => StoredValue::Text(value),
+
+            InputValue::TextToHash(value) => {
+                let hash_value = value.using_encoded(<T as system::Trait>::Hashing::hash);
+                StoredValue::Hash(hash_value)
+            }
+            InputValue::Reference(value) => StoredValue::Reference(value),
+        }
+    }
+}
+
+impl<T: Trait> From<VecInputValue<T>> for VecStoredValue<T> {
+    fn from(vec_input_value: VecInputValue<T>) -> Self {
+        match vec_input_value {
+            VecInputValue::Bool(vec_value) => VecStoredValue::Bool(vec_value),
+            VecInputValue::Uint16(vec_value) => VecStoredValue::Uint16(vec_value),
+            VecInputValue::Uint32(vec_value) => VecStoredValue::Uint32(vec_value),
+            VecInputValue::Uint64(vec_value) => VecStoredValue::Uint64(vec_value),
+            VecInputValue::Int16(vec_value) => VecStoredValue::Int16(vec_value),
+            VecInputValue::Int32(vec_value) => VecStoredValue::Int32(vec_value),
+            VecInputValue::Int64(vec_value) => VecStoredValue::Int64(vec_value),
+            VecInputValue::Text(vec_value) => VecStoredValue::Text(vec_value),
+
+            VecInputValue::TextToHash(vec_value) => {
+                let hash_vec_value: Vec<_> = vec_value
+                    .into_iter()
+                    .map(|value| value.using_encoded(<T as system::Trait>::Hashing::hash))
+                    .collect();
+                VecStoredValue::Hash(hash_vec_value)
+            }
+            VecInputValue::Reference(value) => VecStoredValue::Reference(value),
+        }
+    }
+}

+ 137 - 0
runtime-modules/content-directory/src/schema/input.rs

@@ -0,0 +1,137 @@
+use super::*;
+
+/// Enum, representing either `SingleInputPropertyValue` or `VecInputPropertyValue`
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Clone, PartialEq, Eq)]
+pub enum InputPropertyValue<T: Trait> {
+    Single(InputValue<T>),
+    Vector(VecInputValue<T>),
+}
+
+impl<T: Trait> core::fmt::Debug for InputPropertyValue<T> {
+    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        write!(formatter, "InputPropertyValue {:?}", self)
+    }
+}
+
+impl<T: Trait> InputPropertyValue<T> {
+    pub fn as_single_value(&self) -> Option<&InputValue<T>> {
+        if let InputPropertyValue::Single(single_value) = self {
+            Some(single_value)
+        } else {
+            None
+        }
+    }
+
+    pub fn as_vec_value(&self) -> Option<&VecInputValue<T>> {
+        if let InputPropertyValue::Vector(vec_value) = self {
+            Some(vec_value)
+        } else {
+            None
+        }
+    }
+
+    pub fn as_vec_value_mut(&mut self) -> Option<&mut VecInputValue<T>> {
+        if let InputPropertyValue::Vector(vec_value) = self {
+            Some(vec_value)
+        } else {
+            None
+        }
+    }
+
+    /// Retrieve all involved `entity_id`'s, if current `InputPropertyValue` is reference
+    pub fn get_involved_entities(&self) -> Option<Vec<T::EntityId>> {
+        match self {
+            InputPropertyValue::Single(single_property_value) => {
+                if let Some(entity_id) = single_property_value.get_involved_entity() {
+                    Some(vec![entity_id])
+                } else {
+                    None
+                }
+            }
+            InputPropertyValue::Vector(vector_property_value) => {
+                vector_property_value.get_involved_entities()
+            }
+        }
+    }
+}
+
+impl<T: Trait> Default for InputPropertyValue<T> {
+    fn default() -> Self {
+        InputPropertyValue::Single(InputValue::default())
+    }
+}
+
+/// InputValue enum representation, related to corresponding `SingleInputPropertyValue` structure
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Clone, PartialEq, Eq)]
+pub enum InputValue<T: Trait> {
+    Bool(bool),
+    Uint16(u16),
+    Uint32(u32),
+    Uint64(u64),
+    Int16(i16),
+    Int32(i32),
+    Int64(i64),
+    Text(Vec<u8>),
+    // Used to pass text value, which respective hash should be stored
+    TextToHash(Vec<u8>),
+    Reference(T::EntityId),
+}
+
+impl<T: Trait> core::fmt::Debug for InputValue<T> {
+    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> sp_std::fmt::Result {
+        write!(formatter, "InputValue {:?}", self)
+    }
+}
+
+impl<T: Trait> Default for InputValue<T> {
+    fn default() -> InputValue<T> {
+        Self::Bool(false)
+    }
+}
+
+impl<T: Trait> InputValue<T> {
+    /// Retrieve involved `entity_id`, if current `InputValue` is reference
+    pub fn get_involved_entity(&self) -> Option<T::EntityId> {
+        if let InputValue::Reference(entity_id) = self {
+            Some(*entity_id)
+        } else {
+            None
+        }
+    }
+}
+
+/// Vector value enum representation
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
+#[derive(Encode, Decode, Clone, PartialEq, Eq)]
+pub enum VecInputValue<T: Trait> {
+    Bool(Vec<bool>),
+    Uint16(Vec<u16>),
+    Uint32(Vec<u32>),
+    Uint64(Vec<u64>),
+    Int16(Vec<i16>),
+    Int32(Vec<i32>),
+    Int64(Vec<i64>),
+    // Used to pass text vec value, which respective hashes should be stored
+    TextToHash(Vec<Vec<u8>>),
+    Text(Vec<Vec<u8>>),
+    Reference(Vec<T::EntityId>),
+}
+
+impl<T: Trait> Default for VecInputValue<T> {
+    fn default() -> Self {
+        Self::Bool(vec![])
+    }
+}
+
+impl<T: Trait> VecInputValue<T> {
+    /// Retrieve all involved `entity_id`'s, if current `VecInputValue` is reference
+    pub fn get_involved_entities(&self) -> Option<Vec<T::EntityId>> {
+        if let Self::Reference(entity_ids) = self {
+            Some(entity_ids.to_owned())
+        } else {
+            None
+        }
+    }
+}

+ 309 - 0
runtime-modules/content-directory/src/schema/output.rs

@@ -0,0 +1,309 @@
+use super::*;
+use sp_runtime::traits::Hash;
+
+/// Enum, representing either `StoredValue` or `VecStoredPropertyValue`
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
+#[derive(Encode, Decode, Clone, PartialEq, Eq)]
+pub enum StoredPropertyValue<T: Trait> {
+    Single(StoredValue<T>),
+    Vector(VecStoredPropertyValue<T>),
+}
+
+impl<T: Trait> StoredPropertyValue<T> {
+    /// Returns single property value by reference if `StoredPropertyValue` is Single
+    pub fn as_single_value(&self) -> Option<&StoredValue<T>> {
+        if let StoredPropertyValue::Single(single_value) = self {
+            Some(single_value)
+        } else {
+            None
+        }
+    }
+
+    /// Returns vector property value by reference if `StoredPropertyValue` is Single
+    pub fn as_vec_property_value(&self) -> Option<&VecStoredPropertyValue<T>> {
+        if let StoredPropertyValue::Vector(vec_property_value) = self {
+            Some(vec_property_value)
+        } else {
+            None
+        }
+    }
+
+    /// Returns vector property value by mutable reference if `StoredPropertyValue` is Single
+    pub fn as_vec_property_value_mut(&mut self) -> Option<&mut VecStoredPropertyValue<T>> {
+        if let StoredPropertyValue::Vector(vec_property_value) = self {
+            Some(vec_property_value)
+        } else {
+            None
+        }
+    }
+
+    /// Update `Self` with provided `StoredPropertyValue`
+    pub fn update(&mut self, mut new_value: Self) {
+        if let (Some(vec_property_value), Some(new_vec_property_value)) = (
+            self.as_vec_property_value_mut(),
+            new_value.as_vec_property_value_mut(),
+        ) {
+            new_vec_property_value.nonce = vec_property_value.nonce;
+        }
+        *self = new_value
+    }
+
+    /// Retrieve all involved `entity_id`'s, if current `StoredPropertyValue` is reference
+    pub fn get_involved_entities(&self) -> Option<Vec<T::EntityId>> {
+        match self {
+            StoredPropertyValue::Single(single_property_value) => {
+                if let Some(entity_id) = single_property_value.get_involved_entity() {
+                    Some(vec![entity_id])
+                } else {
+                    None
+                }
+            }
+            StoredPropertyValue::Vector(vector_property_value) => vector_property_value
+                .get_vec_value_ref()
+                .get_involved_entities(),
+        }
+    }
+
+    /// Compute hash from unique property value and its respective property_id
+    pub fn compute_unique_hash(&self, property_id: PropertyId) -> T::Hash {
+        match self {
+            StoredPropertyValue::Single(output_value) => {
+                (property_id, output_value).using_encoded(<T as system::Trait>::Hashing::hash)
+            }
+            StoredPropertyValue::Vector(vector_output_value) => {
+                vector_output_value.compute_unique_hash(property_id)
+            }
+        }
+    }
+}
+
+impl<T: Trait> Default for StoredPropertyValue<T> {
+    fn default() -> Self {
+        StoredPropertyValue::Single(StoredValue::default())
+    }
+}
+
+/// StoredValue enum representation, related to corresponding `SingleStoredPropertyValue` structure
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
+#[derive(Encode, Decode, Hash, Clone, PartialEq, PartialOrd, Ord, Eq)]
+pub enum StoredValue<T: Trait> {
+    Bool(bool),
+    Uint16(u16),
+    Uint32(u32),
+    Uint64(u64),
+    Int16(i16),
+    Int32(i32),
+    Int64(i64),
+    Text(Vec<u8>),
+    Hash(T::Hash),
+    Reference(T::EntityId),
+}
+
+impl<T: Trait> Default for StoredValue<T> {
+    fn default() -> StoredValue<T> {
+        Self::Bool(false)
+    }
+}
+
+impl<T: Trait> StoredValue<T> {
+    /// Retrieve involved `entity_id`, if current `StoredValue` is reference
+    pub fn get_involved_entity(&self) -> Option<T::EntityId> {
+        if let StoredValue::Reference(entity_id) = self {
+            Some(*entity_id)
+        } else {
+            None
+        }
+    }
+}
+
+/// Consists of `VecStoredPropertyValue` enum representation and `nonce`, used to avoid vector data race update conditions
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq)]
+pub struct VecStoredPropertyValue<T: Trait> {
+    vec_value: VecStoredValue<T>,
+    nonce: T::Nonce,
+}
+
+impl<T: Trait> VecStoredPropertyValue<T> {
+    /// Compute hash from unique vec property value and its respective property_id
+    pub fn compute_unique_hash(&self, property_id: PropertyId) -> T::Hash {
+        // Do not hash nonce
+        (property_id, &self.vec_value).using_encoded(<T as system::Trait>::Hashing::hash)
+    }
+
+    /// Increase nonce by 1
+    fn increment_nonce(&mut self) -> T::Nonce {
+        self.nonce += T::Nonce::one();
+        self.nonce
+    }
+
+    /// Create new `VecStoredPropertyValue` from `vec value` provided and `nonce`
+    pub fn new(vec_value: VecStoredValue<T>, nonce: T::Nonce) -> Self {
+        Self { vec_value, nonce }
+    }
+
+    /// Retrieve `VecStoredValue`
+    pub fn get_vec_value(self) -> VecStoredValue<T> {
+        self.vec_value
+    }
+
+    /// Retrieve `VecStoredValue` by reference
+    pub fn get_vec_value_ref(&self) -> &VecStoredValue<T> {
+        &self.vec_value
+    }
+
+    fn len(&self) -> usize {
+        match &self.vec_value {
+            VecStoredValue::Bool(vec) => vec.len(),
+            VecStoredValue::Uint16(vec) => vec.len(),
+            VecStoredValue::Uint32(vec) => vec.len(),
+            VecStoredValue::Uint64(vec) => vec.len(),
+            VecStoredValue::Int16(vec) => vec.len(),
+            VecStoredValue::Int32(vec) => vec.len(),
+            VecStoredValue::Int64(vec) => vec.len(),
+            VecStoredValue::Text(vec) => vec.len(),
+            VecStoredValue::Hash(vec) => vec.len(),
+            VecStoredValue::Reference(vec) => vec.len(),
+        }
+    }
+
+    /// Clear current `vec_value`
+    pub fn clear(&mut self) {
+        match &mut self.vec_value {
+            VecStoredValue::Bool(vec) => *vec = vec![],
+            VecStoredValue::Uint16(vec) => *vec = vec![],
+            VecStoredValue::Uint32(vec) => *vec = vec![],
+            VecStoredValue::Uint64(vec) => *vec = vec![],
+            VecStoredValue::Int16(vec) => *vec = vec![],
+            VecStoredValue::Int32(vec) => *vec = vec![],
+            VecStoredValue::Int64(vec) => *vec = vec![],
+            VecStoredValue::Text(vec) => *vec = vec![],
+            VecStoredValue::Hash(vec) => *vec = vec![],
+            VecStoredValue::Reference(vec) => *vec = vec![],
+        }
+    }
+
+    /// Perform removal at given `index_in_property_vec`, increment `nonce`
+    pub fn remove_at(&mut self, index_in_property_vec: VecMaxLength) {
+        fn remove_at_checked<T>(vec: &mut Vec<T>, index_in_property_vec: VecMaxLength) {
+            if (index_in_property_vec as usize) < vec.len() {
+                vec.remove(index_in_property_vec as usize);
+            }
+        }
+
+        match &mut self.vec_value {
+            VecStoredValue::Bool(vec) => remove_at_checked(vec, index_in_property_vec),
+            VecStoredValue::Uint16(vec) => remove_at_checked(vec, index_in_property_vec),
+            VecStoredValue::Uint32(vec) => remove_at_checked(vec, index_in_property_vec),
+            VecStoredValue::Uint64(vec) => remove_at_checked(vec, index_in_property_vec),
+            VecStoredValue::Int16(vec) => remove_at_checked(vec, index_in_property_vec),
+            VecStoredValue::Int32(vec) => remove_at_checked(vec, index_in_property_vec),
+            VecStoredValue::Int64(vec) => remove_at_checked(vec, index_in_property_vec),
+            VecStoredValue::Text(vec) => remove_at_checked(vec, index_in_property_vec),
+            VecStoredValue::Hash(vec) => remove_at_checked(vec, index_in_property_vec),
+            VecStoredValue::Reference(vec) => remove_at_checked(vec, index_in_property_vec),
+        }
+
+        self.increment_nonce();
+    }
+
+    /// Insert provided `StoredValue` at given `index_in_property_vec`, increment `nonce`
+    pub fn insert_at(&mut self, index_in_property_vec: VecMaxLength, single_value: StoredValue<T>) {
+        fn insert_at<T>(vec: &mut Vec<T>, index_in_property_vec: VecMaxLength, value: T) {
+            if (index_in_property_vec as usize) < vec.len() {
+                vec.insert(index_in_property_vec as usize, value);
+            }
+        }
+
+        match (&mut self.vec_value, single_value) {
+            (VecStoredValue::Bool(vec), StoredValue::Bool(value)) => {
+                insert_at(vec, index_in_property_vec, value)
+            }
+            (VecStoredValue::Uint16(vec), StoredValue::Uint16(value)) => {
+                insert_at(vec, index_in_property_vec, value)
+            }
+            (VecStoredValue::Uint32(vec), StoredValue::Uint32(value)) => {
+                insert_at(vec, index_in_property_vec, value)
+            }
+            (VecStoredValue::Uint64(vec), StoredValue::Uint64(value)) => {
+                insert_at(vec, index_in_property_vec, value)
+            }
+            (VecStoredValue::Int16(vec), StoredValue::Int16(value)) => {
+                insert_at(vec, index_in_property_vec, value)
+            }
+            (VecStoredValue::Int32(vec), StoredValue::Int32(value)) => {
+                insert_at(vec, index_in_property_vec, value)
+            }
+            (VecStoredValue::Int64(vec), StoredValue::Int64(value)) => {
+                insert_at(vec, index_in_property_vec, value)
+            }
+
+            // Match by move, when https://github.com/rust-lang/rust/issues/68354 stableize
+            (VecStoredValue::Text(vec), StoredValue::Text(ref value)) => {
+                insert_at(vec, index_in_property_vec, value.to_owned())
+            }
+            (VecStoredValue::Reference(vec), StoredValue::Reference(value)) => {
+                insert_at(vec, index_in_property_vec, value)
+            }
+            _ => return,
+        }
+
+        self.increment_nonce();
+    }
+
+    /// Ensure `VecStoredPropertyValue` nonce is equal to the provided one.
+    /// Used to to avoid possible data races, when performing vector specific operations
+    pub fn ensure_nonce_equality(&self, new_nonce: T::Nonce) -> Result<(), Error<T>> {
+        ensure!(
+            self.nonce == new_nonce,
+            Error::<T>::PropertyValueVecNoncesDoesNotMatch
+        );
+        Ok(())
+    }
+
+    /// Ensure, provided `index_in_property_vec` is valid index of `VecStoredValue`
+    pub fn ensure_index_in_property_vector_is_valid(
+        &self,
+        index_in_property_vec: VecMaxLength,
+    ) -> Result<(), Error<T>> {
+        ensure!(
+            (index_in_property_vec as usize) <= self.len(),
+            Error::<T>::EntityPropertyValueVectorIndexIsOutOfRange
+        );
+
+        Ok(())
+    }
+}
+
+/// Vector value enum representation, related to corresponding `VecStoredPropertyValue` structure
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
+#[derive(Encode, Decode, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
+pub enum VecStoredValue<T: Trait> {
+    Bool(Vec<bool>),
+    Uint16(Vec<u16>),
+    Uint32(Vec<u32>),
+    Uint64(Vec<u64>),
+    Int16(Vec<i16>),
+    Int32(Vec<i32>),
+    Int64(Vec<i64>),
+    Hash(Vec<T::Hash>),
+    Text(Vec<Vec<u8>>),
+    Reference(Vec<T::EntityId>),
+}
+
+impl<T: Trait> Default for VecStoredValue<T> {
+    fn default() -> Self {
+        Self::Bool(vec![])
+    }
+}
+
+impl<T: Trait> VecStoredValue<T> {
+    /// Retrieve all involved `entity_id`'s, if current `VecStoredValue` is reference
+    pub fn get_involved_entities(&self) -> Option<Vec<T::EntityId>> {
+        if let Self::Reference(entity_ids) = self {
+            Some(entity_ids.to_owned())
+        } else {
+            None
+        }
+    }
+}

+ 646 - 0
runtime-modules/content-directory/src/schema/property.rs

@@ -0,0 +1,646 @@
+use super::*;
+
+/// Type identificator for property id
+pub type PropertyId = u16;
+
+/// Type representing max length of vector property type
+pub type VecMaxLength = u16;
+
+/// Type representing max length of text property type
+pub type TextMaxLength = u16;
+
+/// Type representing optional max length of text property type, that will be subsequently hashed
+pub type HashedTextMaxLength = Option<u16>;
+
+/// Used to force property values to only reference entities, owned by the same controller
+type SameController = bool;
+
+/// Locking policy, representing `Property` locking status for both controller and maintainer
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
+#[derive(Encode, Default, Decode, Clone, Copy, PartialEq, Eq)]
+pub struct PropertyLockingPolicy {
+    /// If property is locked from maintainer
+    pub is_locked_from_maintainer: bool,
+    /// If property is locked from controller
+    pub is_locked_from_controller: bool,
+}
+
+/// Enum, used for `PropertyType` representation
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
+#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq)]
+pub enum Type<T: Trait> {
+    Bool,
+    Uint16,
+    Uint32,
+    Uint64,
+    Int16,
+    Int32,
+    Int64,
+    /// Max length of text item.
+    Text(TextMaxLength),
+    Hash(HashedTextMaxLength),
+    /// Can reference only specific class id entities
+    Reference(T::ClassId, SameController),
+}
+
+impl<T: Trait> Default for Type<T> {
+    fn default() -> Self {
+        Self::Bool
+    }
+}
+
+impl<T: Trait> Type<T> {
+    /// Ensure `Type` specific `TextMaxLengthConstraint` or `HashedTextMaxLengthConstraint` satisfied
+    pub fn ensure_property_type_size_is_valid(&self) -> Result<(), Error<T>> {
+        if let Type::Text(text_max_len) = self {
+            ensure!(
+                *text_max_len <= T::TextMaxLengthConstraint::get(),
+                Error::<T>::TextPropertyTooLong
+            );
+        }
+
+        if let Type::Hash(hashed_text_max_len) = self {
+            ensure!(
+                *hashed_text_max_len <= T::HashedTextMaxLengthConstraint::get(),
+                Error::<T>::HashedTextPropertyTooLong
+            );
+        }
+
+        Ok(())
+    }
+}
+
+/// Vector property type representation
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
+#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq)]
+pub struct VecPropertyType<T: Trait> {
+    vec_type: Type<T>,
+    /// Max length of vector, corresponding to a given type
+    max_length: VecMaxLength,
+}
+
+impl<T: Trait> Default for VecPropertyType<T> {
+    fn default() -> Self {
+        Self {
+            vec_type: Type::default(),
+            max_length: 0,
+        }
+    }
+}
+
+impl<T: Trait> VecPropertyType<T> {
+    /// Create new `VecPropertyType` from provided `type` and `max_length`
+    pub fn new(vec_type: Type<T>, max_length: VecMaxLength) -> Self {
+        Self {
+            vec_type,
+            max_length,
+        }
+    }
+
+    /// Ensure `Type` specific `TextMaxLengthConstraint` & `VecMaxLengthConstraint` satisfied
+    fn ensure_property_type_size_is_valid(&self) -> Result<(), Error<T>> {
+        // Ensure Type specific TextMaxLengthConstraint or HashedTextMaxLengthConstraint satisfied
+        self.vec_type.ensure_property_type_size_is_valid()?;
+
+        ensure!(
+            self.max_length <= T::VecMaxLengthConstraint::get(),
+            Error::<T>::VecPropertyTooLong
+        );
+        Ok(())
+    }
+
+    fn get_vec_type(&self) -> &Type<T> {
+        &self.vec_type
+    }
+
+    fn get_max_len(&self) -> VecMaxLength {
+        self.max_length
+    }
+}
+
+/// Enum, representing either `Type` or `VecPropertyType`
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
+#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq)]
+pub enum PropertyType<T: Trait> {
+    Single(Type<T>),
+    Vector(VecPropertyType<T>),
+}
+
+impl<T: Trait> Default for PropertyType<T> {
+    fn default() -> Self {
+        Self::Single(Type::default())
+    }
+}
+
+impl<T: Trait> PropertyType<T> {
+    fn as_single_value_type(&self) -> Option<&Type<T>> {
+        if let PropertyType::Single(single_value_property_type) = self {
+            Some(single_value_property_type)
+        } else {
+            None
+        }
+    }
+
+    pub fn as_vec_type(&self) -> Option<&VecPropertyType<T>> {
+        if let PropertyType::Vector(vec_value_property_type) = self {
+            Some(vec_value_property_type)
+        } else {
+            None
+        }
+    }
+
+    fn get_inner_type(&self) -> &Type<T> {
+        match self {
+            PropertyType::Single(single_property_type) => single_property_type,
+            PropertyType::Vector(vec_property_type) => vec_property_type.get_vec_type(),
+        }
+    }
+
+    /// Retrives `same_controller` flag.
+    /// Always returns false if `Type` is not a reference,
+    pub fn same_controller_status(&self) -> SameController {
+        if let Type::Reference(_, same_controller) = self.get_inner_type() {
+            *same_controller
+        } else {
+            false
+        }
+    }
+}
+
+/// `Property` representation, related to a given `Class`
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Clone, PartialEq, Eq)]
+pub struct Property<T: Trait> {
+    /// The type of `Property`
+    pub property_type: PropertyType<T>,
+    /// If property value can be skipped, when adding entity schema support
+    pub required: bool,
+    /// Used to enforce uniquness of a property across all entities that have this property
+    pub unique: bool,
+    /// Property name
+    pub name: Vec<u8>,
+    /// Property description
+    pub description: Vec<u8>,
+    /// Locking policy, representing `Property` locking status for both controller and maintainer
+    pub locking_policy: PropertyLockingPolicy,
+}
+
+impl<T: Trait> Default for Property<T> {
+    fn default() -> Self {
+        Self {
+            property_type: PropertyType::<T>::default(),
+            required: false,
+            unique: false,
+            name: vec![],
+            description: vec![],
+            locking_policy: PropertyLockingPolicy::default(),
+        }
+    }
+}
+
+impl<T: Trait> core::fmt::Debug for Property<T> {
+    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        write!(formatter, "Property {:?}", self)
+    }
+}
+
+impl<T: Trait> Property<T> {
+    /// Check if property is locked from actor with provided `EntityAccessLevel`
+    pub fn is_locked_from(&self, access_level: EntityAccessLevel) -> bool {
+        let is_locked_from_controller = self.locking_policy.is_locked_from_controller;
+        let is_locked_from_maintainer = self.locking_policy.is_locked_from_maintainer;
+        match access_level {
+            EntityAccessLevel::EntityControllerAndMaintainer => {
+                is_locked_from_controller && is_locked_from_maintainer
+            }
+            EntityAccessLevel::EntityController => is_locked_from_controller,
+            EntityAccessLevel::EntityMaintainer => is_locked_from_maintainer,
+        }
+    }
+
+    /// Ensure `Property` is unlocked from `Actor` with given `EntityAccessLevel`
+    pub fn ensure_unlocked_from(&self, access_level: EntityAccessLevel) -> Result<(), Error<T>> {
+        ensure!(
+            !self.is_locked_from(access_level),
+            Error::<T>::ClassPropertyTypeLockedForGivenActor
+        );
+        Ok(())
+    }
+
+    /// Validate new `InputPropertyValue` against the type of this `Property`
+    /// and check any additional constraints
+    pub fn ensure_property_value_to_update_is_valid(
+        &self,
+        value: &InputPropertyValue<T>,
+        current_entity_controller: &EntityController<T>,
+    ) -> Result<(), Error<T>> {
+        // Ensure provided InputPropertyValue matches its Type
+        self.ensure_property_value_matches_its_type(value)?;
+
+        // Perform all required checks to ensure provided InputPropertyValue is valid, when current PropertyType is Reference
+        self.ensure_property_value_is_valid_reference(value, current_entity_controller)?;
+
+        // Ensure text property does not exceed its max length
+        self.validate_max_len_if_text_property(value)?;
+
+        // Ensure vector property does not exceed its max length
+        self.validate_max_len_if_vec_property(value)?;
+        Ok(())
+    }
+
+    /// Ensure property vector length after value inserted is valid
+    fn validate_property_vector_length_after_value_insert<V>(
+        vec: &[V],
+        max_len: VecMaxLength,
+    ) -> Result<(), Error<T>> {
+        ensure!(
+            vec.len() < max_len as usize,
+            Error::<T>::EntityPropertyValueVectorIsTooLong
+        );
+        Ok(())
+    }
+
+    /// Ensure `SingleInputPropertyValue` type is equal to the `VecInputPropertyValue` type
+    /// and check all constraints
+    pub fn ensure_property_value_can_be_inserted_at_property_vector(
+        &self,
+        single_value: &InputValue<T>,
+        vec_value: &VecStoredPropertyValue<T>,
+        index_in_property_vec: VecMaxLength,
+        current_entity_controller: &EntityController<T>,
+    ) -> Result<(), Error<T>> {
+        // Ensure, provided index_in_property_vec is valid index of VecInputValue
+        vec_value.ensure_index_in_property_vector_is_valid(index_in_property_vec)?;
+
+        let property_type_vec = self
+            .property_type
+            .as_vec_type()
+            .ok_or(Error::<T>::PropertyValueTypeDoesNotMatchInternalVectorType)?;
+
+        let max_vec_len = property_type_vec.get_max_len();
+
+        match (
+            single_value,
+            vec_value.get_vec_value_ref(),
+            property_type_vec.get_vec_type(),
+        ) {
+            // Single values
+            (InputValue::Bool(_), VecStoredValue::Bool(vec), Type::Bool) => {
+                Self::validate_property_vector_length_after_value_insert(vec, max_vec_len)
+            }
+            (InputValue::Uint16(_), VecStoredValue::Uint16(vec), Type::Uint16) => {
+                Self::validate_property_vector_length_after_value_insert(vec, max_vec_len)
+            }
+            (InputValue::Uint32(_), VecStoredValue::Uint32(vec), Type::Uint32) => {
+                Self::validate_property_vector_length_after_value_insert(vec, max_vec_len)
+            }
+            (InputValue::Uint64(_), VecStoredValue::Uint64(vec), Type::Uint64) => {
+                Self::validate_property_vector_length_after_value_insert(vec, max_vec_len)
+            }
+            (InputValue::Int16(_), VecStoredValue::Int16(vec), Type::Int16) => {
+                Self::validate_property_vector_length_after_value_insert(vec, max_vec_len)
+            }
+            (InputValue::Int32(_), VecStoredValue::Int32(vec), Type::Int32) => {
+                Self::validate_property_vector_length_after_value_insert(vec, max_vec_len)
+            }
+            (InputValue::Int64(_), VecStoredValue::Int64(vec), Type::Int64) => {
+                Self::validate_property_vector_length_after_value_insert(vec, max_vec_len)
+            }
+            (InputValue::Text(text_item), VecStoredValue::Text(vec), Type::Text(text_max_len)) => {
+                Self::validate_max_len_of_text(text_item, *text_max_len)?;
+                Self::validate_property_vector_length_after_value_insert(vec, max_vec_len)
+            }
+            (
+                InputValue::TextToHash(text_item),
+                VecStoredValue::Hash(vec),
+                Type::Hash(text_max_len),
+            ) => {
+                if let Some(text_max_len) = text_max_len {
+                    Self::validate_max_len_of_text_to_be_hashed(text_item, *text_max_len)?;
+                }
+                Self::validate_property_vector_length_after_value_insert(vec, max_vec_len)
+            }
+            (
+                InputValue::Reference(entity_id),
+                VecStoredValue::Reference(vec),
+                Type::Reference(class_id, same_controller_status),
+            ) => {
+                // Ensure class_id of Entity under provided entity_id references Entity,
+                // which class_id is equal to class_id, declared in corresponding PropertyType
+                // Retrieve corresponding Entity
+                let entity = Self::ensure_referenced_entity_match_its_class(*entity_id, *class_id)?;
+                // Ensure Entity can be referenced.
+                Self::ensure_entity_can_be_referenced(
+                    entity,
+                    *same_controller_status,
+                    current_entity_controller,
+                )?;
+                Self::validate_property_vector_length_after_value_insert(vec, max_vec_len)
+            }
+            _ => Err(Error::<T>::PropertyValueTypeDoesNotMatchInternalVectorType),
+        }
+    }
+
+    /// Ensure text property does not exceed its max len
+    pub fn validate_max_len_if_text_property(
+        &self,
+        value: &InputPropertyValue<T>,
+    ) -> Result<(), Error<T>> {
+        let single_value = value.as_single_value();
+
+        match (single_value, &self.property_type.as_single_value_type()) {
+            (Some(InputValue::Text(text)), Some(Type::Text(text_max_len))) => {
+                Self::validate_max_len_of_text(text, *text_max_len)
+            }
+            (
+                Some(InputValue::TextToHash(text_to_be_hashed)),
+                Some(Type::Hash(Some(text_to_be_hashed_max_len))),
+            ) => Self::validate_max_len_of_text_to_be_hashed(
+                text_to_be_hashed,
+                *text_to_be_hashed_max_len,
+            ),
+            _ => Ok(()),
+        }
+    }
+
+    fn validate_max_len_of_text(text: &[u8], text_max_len: TextMaxLength) -> Result<(), Error<T>> {
+        ensure!(
+            text.len() <= text_max_len as usize,
+            Error::<T>::TextPropertyTooLong
+        );
+        Ok(())
+    }
+
+    fn validate_max_len_of_text_to_be_hashed(
+        text_to_be_hashed: &[u8],
+        text_to_be_hashed_max_len: u16,
+    ) -> Result<(), Error<T>> {
+        ensure!(
+            text_to_be_hashed.len() <= text_to_be_hashed_max_len as usize,
+            Error::<T>::HashedTextPropertyTooLong
+        );
+        Ok(())
+    }
+
+    fn validate_vec_len<V>(vec: &[V], max_len: VecMaxLength) -> Result<(), Error<T>> {
+        ensure!(
+            vec.len() <= max_len as usize,
+            Error::<T>::VecPropertyTooLong
+        );
+        Ok(())
+    }
+
+    /// Ensure `VecInputValue` does not exceed its max len
+    pub fn validate_max_len_if_vec_property(
+        &self,
+        value: &InputPropertyValue<T>,
+    ) -> Result<(), Error<T>> {
+        let (vec_value, vec_property_type) = if let (Some(vec_value), Some(vec_property_type)) =
+            (value.as_vec_value(), self.property_type.as_vec_type())
+        {
+            (vec_value, vec_property_type)
+        } else {
+            return Ok(());
+        };
+
+        let max_len = vec_property_type.get_max_len();
+
+        match vec_value {
+            VecInputValue::Bool(vec) => Self::validate_vec_len(vec, max_len),
+            VecInputValue::Uint16(vec) => Self::validate_vec_len(vec, max_len),
+            VecInputValue::Uint32(vec) => Self::validate_vec_len(vec, max_len),
+            VecInputValue::Uint64(vec) => Self::validate_vec_len(vec, max_len),
+            VecInputValue::Int16(vec) => Self::validate_vec_len(vec, max_len),
+            VecInputValue::Int32(vec) => Self::validate_vec_len(vec, max_len),
+            VecInputValue::Int64(vec) => Self::validate_vec_len(vec, max_len),
+            VecInputValue::TextToHash(vec) => {
+                Self::validate_vec_len(vec, max_len)?;
+                if let Type::Hash(Some(text_to_be_hashed_max_len)) =
+                    vec_property_type.get_vec_type()
+                {
+                    for text_to_be_hashed_item in vec.iter() {
+                        Self::validate_max_len_of_text_to_be_hashed(
+                            text_to_be_hashed_item,
+                            *text_to_be_hashed_max_len,
+                        )?;
+                    }
+                }
+                Ok(())
+            }
+            VecInputValue::Text(vec) => {
+                Self::validate_vec_len(vec, max_len)?;
+                if let Type::Text(text_max_len) = vec_property_type.get_vec_type() {
+                    for text_item in vec.iter() {
+                        Self::validate_max_len_of_text(text_item, *text_max_len)?;
+                    }
+                }
+                Ok(())
+            }
+            VecInputValue::Reference(vec) => Self::validate_vec_len(vec, max_len),
+        }
+    }
+
+    /// Ensure provided `InputPropertyValue` matches its `Type`
+    pub fn ensure_property_value_matches_its_type(
+        &self,
+        value: &InputPropertyValue<T>,
+    ) -> Result<(), Error<T>> {
+        ensure!(
+            self.does_prop_value_match_type(value),
+            Error::<T>::PropertyValueDoNotMatchType
+        );
+        Ok(())
+    }
+
+    /// Check if provided `InputPropertyValue` matches its `Type`
+    pub fn does_prop_value_match_type(&self, value: &InputPropertyValue<T>) -> bool {
+        // A non required property can be updated to Bool(false):
+        if !self.required && *value == InputPropertyValue::default() {
+            return true;
+        }
+        match (value, &self.property_type) {
+            (
+                InputPropertyValue::Single(single_property_value),
+                PropertyType::Single(ref single_property_type),
+            ) => match (single_property_value, single_property_type.deref()) {
+                (InputValue::Bool(_), Type::Bool)
+                | (InputValue::Uint16(_), Type::Uint16)
+                | (InputValue::Uint32(_), Type::Uint32)
+                | (InputValue::Uint64(_), Type::Uint64)
+                | (InputValue::Int16(_), Type::Int16)
+                | (InputValue::Int32(_), Type::Int32)
+                | (InputValue::Int64(_), Type::Int64)
+                | (InputValue::Text(_), Type::Text(_))
+                | (InputValue::TextToHash(_), Type::Hash(_))
+                | (InputValue::Reference(_), Type::Reference(_, _)) => true,
+                _ => false,
+            },
+            (
+                InputPropertyValue::Vector(vec_value),
+                PropertyType::Vector(ref vec_property_type),
+            ) => match (vec_value, vec_property_type.get_vec_type()) {
+                (VecInputValue::Bool(_), Type::Bool)
+                | (VecInputValue::Uint16(_), Type::Uint16)
+                | (VecInputValue::Uint32(_), Type::Uint32)
+                | (VecInputValue::Uint64(_), Type::Uint64)
+                | (VecInputValue::Int16(_), Type::Int16)
+                | (VecInputValue::Int32(_), Type::Int32)
+                | (VecInputValue::Int64(_), Type::Int64)
+                | (VecInputValue::Text(_), Type::Text(_))
+                | (VecInputValue::TextToHash(_), Type::Hash(_))
+                | (VecInputValue::Reference(_), Type::Reference(_, _)) => true,
+                _ => false,
+            },
+            _ => false,
+        }
+    }
+
+    /// Perform all required checks to ensure provided `InputPropertyValue` is valid,
+    /// when current `PropertyType` is `Reference`
+    pub fn ensure_property_value_is_valid_reference(
+        &self,
+        value: &InputPropertyValue<T>,
+        current_entity_controller: &EntityController<T>,
+    ) -> Result<(), Error<T>> {
+        match (value, &self.property_type) {
+            (
+                InputPropertyValue::Single(single_property_value),
+                PropertyType::Single(single_property_type),
+            ) => {
+                if let (
+                    InputValue::Reference(entity_id),
+                    Type::Reference(class_id, same_controller_status),
+                ) = (single_property_value, single_property_type.deref())
+                {
+                    // Ensure class_id of Entity under provided entity_id references Entity,
+                    // which class_id is equal to class_id, declared in corresponding PropertyType
+                    // Retrieve corresponding Entity
+                    let entity =
+                        Self::ensure_referenced_entity_match_its_class(*entity_id, *class_id)?;
+
+                    // Ensure Entity can be referenced.
+                    Self::ensure_entity_can_be_referenced(
+                        entity,
+                        *same_controller_status,
+                        current_entity_controller,
+                    )?;
+                }
+            }
+            (InputPropertyValue::Vector(vec_value), PropertyType::Vector(vec_property_type)) => {
+                if let (
+                    VecInputValue::Reference(entity_ids),
+                    Type::Reference(class_id, same_controller_status),
+                ) = (vec_value, vec_property_type.get_vec_type())
+                {
+                    for entity_id in entity_ids.iter() {
+                        // Ensure class_id of Entity under provided entity_id references Entity,
+                        // which class_id is equal to class_id, declared in corresponding PropertyType
+                        // Retrieve corresponding Entity
+                        let entity =
+                            Self::ensure_referenced_entity_match_its_class(*entity_id, *class_id)?;
+
+                        // Ensure Entity can be referenced.
+                        Self::ensure_entity_can_be_referenced(
+                            entity,
+                            *same_controller_status,
+                            current_entity_controller,
+                        )?;
+                    }
+                }
+            }
+            _ => (),
+        }
+        Ok(())
+    }
+
+    /// Ensure `class_id` of `Entity` under provided `entity_id` references `Entity`, which `class_id` is equal to `class_id`,
+    /// declared in corresponding `PropertyType`.
+    /// Returns  corresponding `Entity` instance
+    pub fn ensure_referenced_entity_match_its_class(
+        entity_id: T::EntityId,
+        class_id: T::ClassId,
+    ) -> Result<Entity<T>, Error<T>> {
+        // Ensure Entity under given id exists
+        Module::<T>::ensure_known_entity_id(entity_id)?;
+
+        let entity = Module::<T>::entity_by_id(entity_id);
+        ensure!(
+            entity.get_class_id() == class_id,
+            Error::<T>::ReferencedEntityDoesNotMatchItsClass
+        );
+        Ok(entity)
+    }
+
+    /// Ensure `Entity` can be referenced.
+    pub fn ensure_entity_can_be_referenced(
+        entity: Entity<T>,
+        same_controller_status: bool,
+        current_entity_controller: &EntityController<T>,
+    ) -> Result<(), Error<T>> {
+        let entity_permissions = entity.get_permissions();
+
+        // Ensure Entity is referencable
+        ensure!(
+            entity_permissions.is_referancable(),
+            Error::<T>::EntityCanNotBeReferenced
+        );
+
+        if same_controller_status {
+            // Ensure Entity controller is equal to the provided one
+            ensure!(
+                entity_permissions.controller_is_equal_to(current_entity_controller),
+                Error::<T>::SameControllerConstraintViolation
+            );
+        }
+        Ok(())
+    }
+
+    /// Ensure `PropertyNameLengthConstraint` satisfied
+    pub fn ensure_name_is_valid(&self) -> Result<(), Error<T>> {
+        T::PropertyNameLengthConstraint::get().ensure_valid(
+            self.name.len(),
+            Error::<T>::PropertyNameTooShort,
+            Error::<T>::PropertyNameTooLong,
+        )
+    }
+
+    /// Ensure `PropertyDescriptionLengthConstraint` satisfied
+    pub fn ensure_description_is_valid(&self) -> Result<(), Error<T>> {
+        T::PropertyDescriptionLengthConstraint::get().ensure_valid(
+            self.description.len(),
+            Error::<T>::PropertyDescriptionTooShort,
+            Error::<T>::PropertyDescriptionTooLong,
+        )
+    }
+
+    /// Ensure `Type` specific constraints satisfied
+    pub fn ensure_property_type_size_is_valid(&self) -> Result<(), Error<T>> {
+        match &self.property_type {
+            PropertyType::Single(single_property_type) => {
+                // Ensure Type specific TextMaxLengthConstraint satisfied
+                single_property_type.ensure_property_type_size_is_valid()
+            }
+            PropertyType::Vector(vec_property_type) => {
+                // Ensure Type specific TextMaxLengthConstraint & VecMaxLengthConstraint satisfied
+                vec_property_type.ensure_property_type_size_is_valid()
+            }
+        }
+    }
+
+    /// Ensure refers to existing `class_id`, if If `Property` `Type` is `Reference`,
+    pub fn ensure_property_type_reference_is_valid(&self) -> Result<(), Error<T>> {
+        let has_unknown_reference =
+            if let Type::Reference(other_class_id, _) = self.property_type.get_inner_type() {
+                !<ClassById<T>>::contains_key(other_class_id)
+            } else {
+                false
+            };
+
+        ensure!(
+            !has_unknown_reference,
+            Error::<T>::ClassSchemaRefersUnknownClass
+        );
+
+        Ok(())
+    }
+}

+ 347 - 0
runtime-modules/content-directory/src/tests.rs

@@ -0,0 +1,347 @@
+mod add_class_schema;
+mod add_curator_group;
+mod add_curator_to_group;
+mod add_maintainer_to_class;
+mod add_schema_support_to_entity;
+mod clear_entity_property_vector;
+mod create_class;
+mod create_entity;
+mod insert_at_entity_property_vector;
+mod remove_at_entity_property_vector;
+mod remove_curator_from_group;
+mod remove_curator_group;
+mod remove_entity;
+mod remove_maintainer_from_class;
+mod set_curator_group_status;
+mod transaction;
+mod transfer_entity_ownership;
+mod update_class_permissions;
+mod update_class_schema_status;
+mod update_entity_creation_voucher;
+mod update_entity_permissions;
+mod update_entity_property_values;
+
+use super::*;
+use crate::mock::*;
+use core::iter::FromIterator;
+
+pub fn add_entity_schemas_support() -> (Entity<Runtime>, Entity<Runtime>) {
+    // Create first class with default permissions
+    assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+    // Create second class with default permissions
+    assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+    let actor = Actor::Lead;
+
+    // Create first entity
+    assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.to_owned()));
+
+    // Create second entity
+    assert_ok!(create_entity(
+        LEAD_ORIGIN,
+        SECOND_CLASS_ID,
+        actor.to_owned()
+    ));
+
+    // Create first property
+    let first_property =
+        Property::<Runtime>::default_with_name(PropertyNameLengthConstraint::get().max() as usize);
+
+    // Create second property
+    let second_property_type = PropertyType::<Runtime>::vec_reference(SECOND_CLASS_ID, true, 5);
+
+    let second_property = Property::<Runtime>::with_name_and_type(
+        (PropertyNameLengthConstraint::get().max() - 1) as usize,
+        second_property_type,
+        true,
+        false,
+    );
+
+    // Add first Schema to the first Class
+    assert_ok!(add_class_schema(
+        LEAD_ORIGIN,
+        FIRST_CLASS_ID,
+        BTreeSet::new(),
+        vec![first_property]
+    ));
+
+    // Add second Schema to the first Class
+    assert_ok!(add_class_schema(
+        LEAD_ORIGIN,
+        FIRST_CLASS_ID,
+        BTreeSet::new(),
+        vec![second_property]
+    ));
+
+    // Runtime state before tested call
+
+    // Events number before tested calls
+    let number_of_events_before_calls = System::events().len();
+
+    // Used to ensure schema support added succesfully
+    let mut first_entity = entity_by_id(FIRST_ENTITY_ID);
+
+    // Used to ensure reference counter updated succesfully
+    let mut second_entity = entity_by_id(SECOND_ENTITY_ID);
+
+    let mut first_schema_property_values = BTreeMap::new();
+    first_schema_property_values.insert(FIRST_PROPERTY_ID, InputPropertyValue::default());
+
+    // Add first schema support to the first entity
+    assert_ok!(add_schema_support_to_entity(
+        LEAD_ORIGIN,
+        actor.to_owned(),
+        FIRST_ENTITY_ID,
+        FIRST_SCHEMA_ID,
+        first_schema_property_values.clone()
+    ));
+
+    let mut second_schema_property_values = BTreeMap::new();
+    let second_schema_property_value = InputPropertyValue::<Runtime>::vec_reference(vec![
+        SECOND_ENTITY_ID,
+        SECOND_ENTITY_ID,
+        SECOND_ENTITY_ID,
+    ]);
+
+    second_schema_property_values.insert(SECOND_PROPERTY_ID, second_schema_property_value);
+
+    // Add second schema support to the first entity
+    assert_ok!(add_schema_support_to_entity(
+        LEAD_ORIGIN,
+        actor.to_owned(),
+        FIRST_ENTITY_ID,
+        SECOND_SCHEMA_ID,
+        second_schema_property_values.clone()
+    ));
+
+    // Update supported schemas set and properties of first entity
+    *first_entity.get_supported_schemas_mut() =
+        BTreeSet::from_iter(vec![FIRST_SCHEMA_ID, SECOND_SCHEMA_ID].into_iter());
+
+    first_schema_property_values.append(&mut second_schema_property_values);
+
+    first_entity.set_values(TestModule::make_output_property_values(
+        first_schema_property_values,
+    ));
+
+    // Update reference counter of second entity
+    let inbound_rc = InboundReferenceCounter::new(3, true);
+    *second_entity.get_reference_counter_mut() = inbound_rc.clone();
+
+    // Create side-effect
+    let side_effect: EntityReferenceCounterSideEffect = inbound_rc.into();
+    let mut side_effects = ReferenceCounterSideEffects::default();
+    side_effects.insert(SECOND_ENTITY_ID, side_effect);
+
+    let entity_schema_support_added_event = get_test_event(RawEvent::EntitySchemaSupportAdded(
+        actor,
+        FIRST_ENTITY_ID,
+        SECOND_SCHEMA_ID,
+        Some(side_effects),
+    ));
+
+    // Last event checked
+    assert_event_success(
+        entity_schema_support_added_event,
+        number_of_events_before_calls + 2,
+    );
+
+    (first_entity, second_entity)
+}
+
+pub enum EntityAccessStateFailureType {
+    EntityNotFound,
+    LeadAuthFailed,
+    MemberAuthFailed,
+    CuratorAuthFailed,
+    CuratorNotFoundInCuratorGroup,
+    EntityAccessDenied,
+    PropertyValuesLocked,
+}
+
+pub fn emulate_entity_access_state_for_failure_case(
+    entity_access_level_failure_type: EntityAccessStateFailureType,
+) -> Actor<Runtime> {
+    // Create class with default permissions
+    assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+    match entity_access_level_failure_type {
+        EntityAccessStateFailureType::EntityNotFound => Actor::Lead,
+        EntityAccessStateFailureType::LeadAuthFailed => {
+            let actor = Actor::Lead;
+
+            // Create entity
+            assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+            actor
+        }
+        EntityAccessStateFailureType::MemberAuthFailed => {
+            // Update class permissions to force any member be available to create entities
+            assert_ok!(update_class_permissions(
+                LEAD_ORIGIN,
+                FIRST_CLASS_ID,
+                Some(true),
+                None,
+                None,
+                None
+            ));
+
+            let actor = Actor::Member(FIRST_MEMBER_ID);
+
+            // Create entity
+            assert_ok!(create_entity(
+                FIRST_MEMBER_ORIGIN,
+                FIRST_CLASS_ID,
+                actor.clone()
+            ));
+            actor
+        }
+        EntityAccessStateFailureType::CuratorAuthFailed => {
+            // Add curator group
+            assert_ok!(add_curator_group(LEAD_ORIGIN));
+
+            // Add curator to group
+            assert_ok!(add_curator_to_group(
+                LEAD_ORIGIN,
+                FIRST_CURATOR_GROUP_ID,
+                FIRST_CURATOR_ID,
+            ));
+
+            // Add curator group as class maintainer
+            assert_ok!(add_maintainer_to_class(
+                LEAD_ORIGIN,
+                FIRST_CLASS_ID,
+                FIRST_CURATOR_GROUP_ID
+            ));
+
+            // Make curator group active
+            assert_ok!(set_curator_group_status(
+                LEAD_ORIGIN,
+                FIRST_CURATOR_GROUP_ID,
+                true
+            ));
+
+            let actor = Actor::Curator(FIRST_CURATOR_GROUP_ID, FIRST_CURATOR_ID);
+
+            // Create Entity
+            assert_ok!(create_entity(
+                FIRST_CURATOR_ORIGIN,
+                FIRST_CLASS_ID,
+                actor.clone()
+            ));
+
+            actor
+        }
+        EntityAccessStateFailureType::CuratorNotFoundInCuratorGroup => {
+            // Add curator group
+            assert_ok!(add_curator_group(LEAD_ORIGIN));
+
+            // Add curator to group
+            assert_ok!(add_curator_to_group(
+                LEAD_ORIGIN,
+                FIRST_CURATOR_GROUP_ID,
+                FIRST_CURATOR_ID,
+            ));
+
+            // Make curator group active
+            assert_ok!(set_curator_group_status(
+                LEAD_ORIGIN,
+                FIRST_CURATOR_GROUP_ID,
+                true
+            ));
+
+            // Add curator group as class maintainer
+            assert_ok!(add_maintainer_to_class(
+                LEAD_ORIGIN,
+                FIRST_CLASS_ID,
+                FIRST_CURATOR_GROUP_ID
+            ));
+
+            // Create entity
+            assert_ok!(create_entity(
+                FIRST_CURATOR_ORIGIN,
+                FIRST_CLASS_ID,
+                Actor::Curator(FIRST_CURATOR_GROUP_ID, FIRST_CURATOR_ID)
+            ));
+
+            Actor::Curator(FIRST_CURATOR_GROUP_ID, SECOND_CURATOR_ID)
+        }
+        EntityAccessStateFailureType::EntityAccessDenied => {
+            // Update class permissions to force any member be available to create entities
+            assert_ok!(update_class_permissions(
+                LEAD_ORIGIN,
+                FIRST_CLASS_ID,
+                Some(true),
+                None,
+                None,
+                None
+            ));
+
+            // Create entity
+            assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, Actor::Lead));
+
+            Actor::Member(SECOND_MEMBER_ID)
+        }
+        EntityAccessStateFailureType::PropertyValuesLocked => {
+            // Update class permissions to force lock all entity property values from update being performed
+            assert_ok!(update_class_permissions(
+                LEAD_ORIGIN,
+                FIRST_CLASS_ID,
+                None,
+                None,
+                Some(true),
+                None
+            ));
+
+            // Create entity
+            assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, Actor::Lead));
+
+            Actor::Lead
+        }
+    }
+}
+
+///  Create class reference schema
+pub fn add_unique_class_reference_schema() {
+    // Create property
+    let property_type =
+        PropertyType::<Runtime>::vec_reference(FIRST_CLASS_ID, true, VecMaxLengthConstraint::get());
+
+    let property = Property::<Runtime>::with_name_and_type(
+        (PropertyNameLengthConstraint::get().max() - 1) as usize,
+        property_type,
+        true,
+        true,
+    );
+
+    // Add Schema to the Class
+    assert_ok!(add_class_schema(
+        LEAD_ORIGIN,
+        FIRST_CLASS_ID,
+        BTreeSet::new(),
+        vec![property]
+    ));
+}
+
+///  Create class reference schema and add corresponding schema support to the Entity
+pub fn add_unique_class_reference_schema_and_entity_schema_support(
+    actor: &Actor<Runtime>,
+    origin: u64,
+) {
+    add_unique_class_reference_schema();
+
+    let schema_property_value =
+        InputPropertyValue::<Runtime>::vec_reference(vec![FIRST_ENTITY_ID, FIRST_ENTITY_ID]);
+
+    let mut schema_property_values = BTreeMap::new();
+    schema_property_values.insert(FIRST_PROPERTY_ID, schema_property_value);
+
+    // Add schema support to the entity
+    assert_ok!(add_schema_support_to_entity(
+        origin,
+        actor.to_owned(),
+        FIRST_ENTITY_ID,
+        FIRST_SCHEMA_ID,
+        schema_property_values
+    ));
+}

+ 507 - 0
runtime-modules/content-directory/src/tests/add_class_schema.rs

@@ -0,0 +1,507 @@
+use super::*;
+
+#[test]
+fn add_class_schema_success() {
+    with_test_externalities(|| {
+        // Create simple class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Runtime tested state before call
+
+        // Events number before tested calls
+        let number_of_events_before_call = System::events().len();
+
+        let first_property =
+            Property::default_with_name(PropertyNameLengthConstraint::get().max() as usize);
+
+        let second_property =
+            Property::default_with_name((PropertyNameLengthConstraint::get().max() - 1) as usize);
+
+        // Add first class schema
+        assert_ok!(add_class_schema(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            BTreeSet::new(),
+            vec![first_property.clone()]
+        ));
+
+        // Add second class schema
+        assert_ok!(add_class_schema(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            BTreeSet::from_iter(vec![FIRST_PROPERTY_ID].into_iter()),
+            vec![second_property.clone()]
+        ));
+
+        // Runtime tested state after call
+
+        // Ensure class schemas added succesfully
+        let mut class = create_class_with_default_permissions();
+
+        class.set_properties(vec![first_property, second_property]);
+        *class.get_schemas_mut() = vec![
+            Schema::new(BTreeSet::from_iter(vec![FIRST_PROPERTY_ID].into_iter())),
+            Schema::new(BTreeSet::from_iter(
+                vec![FIRST_PROPERTY_ID, SECOND_PROPERTY_ID].into_iter(),
+            )),
+        ];
+
+        assert_eq!(class_by_id(FIRST_CLASS_ID), class);
+
+        let class_schema_added_event =
+            get_test_event(RawEvent::ClassSchemaAdded(FIRST_CLASS_ID, SECOND_SCHEMA_ID));
+
+        // Last event checked
+        assert_event_success(class_schema_added_event, number_of_events_before_call + 2);
+    })
+}
+
+#[test]
+fn add_class_schema_lead_auth_failied() {
+    with_test_externalities(|| {
+        // Create simple class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Runtime tested state before call
+
+        // Events number before tested calls
+        let number_of_events_before_call = System::events().len();
+
+        let property =
+            Property::default_with_name(PropertyNameLengthConstraint::get().max() as usize);
+
+        // Make an attempt to add class schema under non lead origin
+        let add_class_schema_result = add_class_schema(
+            FIRST_MEMBER_ORIGIN,
+            FIRST_CLASS_ID,
+            BTreeSet::new(),
+            vec![property],
+        );
+
+        // Failure checked
+        assert_failure(
+            add_class_schema_result,
+            Error::<Runtime>::LeadAuthFailed,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn add_class_schema_class_does_not_exist() {
+    with_test_externalities(|| {
+        // Runtime tested state before call
+
+        // Events number before tested calls
+        let number_of_events_before_call = System::events().len();
+
+        let property =
+            Property::default_with_name(PropertyNameLengthConstraint::get().max() as usize);
+
+        // Make an attempt to add class schema to class, which does not exist in runtime
+        let add_class_schema_result = add_class_schema(
+            LEAD_ORIGIN,
+            UNKNOWN_CLASS_ID,
+            BTreeSet::new(),
+            vec![property],
+        );
+
+        // Failure checked
+        assert_failure(
+            add_class_schema_result,
+            Error::<Runtime>::ClassNotFound,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn add_class_schema_limit_reached() {
+    with_test_externalities(|| {
+        // Create simple class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Runtime tested state before call
+
+        // Events number before tested calls
+        let number_of_events_before_call = System::events().len();
+
+        let mut number_of_schemas_added = 0;
+
+        // Add schemas to the class until MaxNumberOfSchemasPerClass limit reached
+        let add_class_schema_result = loop {
+            // property name must be unique
+            let property = Property::default_with_name(
+                PropertyNameLengthConstraint::get().max() as usize - number_of_schemas_added,
+            );
+
+            // Add class schema
+            let add_class_schema_result =
+                add_class_schema(LEAD_ORIGIN, FIRST_CLASS_ID, BTreeSet::new(), vec![property]);
+
+            if add_class_schema_result.is_err() {
+                break add_class_schema_result;
+            } else {
+                number_of_schemas_added += 1;
+            }
+        };
+
+        // Ensure number of schemas added is equal to MaxNumberOfSchemasPerClass
+        assert_eq!(
+            number_of_schemas_added,
+            MaxNumberOfSchemasPerClass::get() as usize
+        );
+
+        // Failure checked
+        assert_failure(
+            add_class_schema_result,
+            Error::<Runtime>::ClassSchemasLimitReached,
+            number_of_events_before_call + number_of_schemas_added,
+        );
+    })
+}
+
+#[test]
+fn add_class_schema_no_props_in_schema() {
+    with_test_externalities(|| {
+        // Create simple class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Runtime tested state before call
+
+        // Events number before tested calls
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to add class schema with both empty existing and new properties
+        let add_class_schema_result =
+            add_class_schema(LEAD_ORIGIN, FIRST_CLASS_ID, BTreeSet::new(), vec![]);
+
+        // Failure checked
+        assert_failure(
+            add_class_schema_result,
+            Error::<Runtime>::NoPropertiesInClassSchema,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn add_class_schema_properties_limit_reached() {
+    with_test_externalities(|| {
+        // Create simple class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Runtime tested state before call
+
+        // Events number before tested calls
+        let number_of_events_before_call = System::events().len();
+
+        // Add properties to Vec until it exceed MaxNumberOfPropertiesPerSchema runtime constraint
+        let properties = (1..=MaxNumberOfPropertiesPerSchema::get() + 1)
+            .into_iter()
+            .map(|property_id| {
+                // property name must be unique
+                let name_len = PropertyNameLengthConstraint::get().max() as u32 - property_id;
+                Property::default_with_name(name_len as usize)
+            })
+            .collect();
+
+        // Make an attempt to add class schema, providing properties, which total number exceeds MaxNumberOfPropertiesPerSchema limit
+        let add_class_schema_result =
+            add_class_schema(LEAD_ORIGIN, FIRST_CLASS_ID, BTreeSet::new(), properties);
+
+        // Failure checked
+        assert_failure(
+            add_class_schema_result,
+            Error::<Runtime>::SchemaPropertiesLimitReached,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn add_class_schema_prop_name_not_unique() {
+    with_test_externalities(|| {
+        // Create simple class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Runtime tested state before call
+
+        let first_property =
+            Property::default_with_name(PropertyNameLengthConstraint::get().max() as usize);
+
+        let second_property =
+            Property::default_with_name((PropertyNameLengthConstraint::get().max()) as usize);
+
+        // Add first class schema
+        assert_ok!(add_class_schema(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            BTreeSet::new(),
+            vec![first_property]
+        ));
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to add second class schema with the same name
+        let add_class_schema_result = add_class_schema(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            BTreeSet::from_iter(vec![FIRST_PROPERTY_ID].into_iter()),
+            vec![second_property],
+        );
+
+        // Failure checked
+        assert_failure(
+            add_class_schema_result,
+            Error::<Runtime>::PropertyNameNotUniqueInAClass,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn add_class_schema_refers_invalid_property_index() {
+    with_test_externalities(|| {
+        // Create simple class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Runtime tested state before call
+
+        // Events number before tested calls
+        let number_of_events_before_call = System::events().len();
+
+        let property =
+            Property::default_with_name(PropertyNameLengthConstraint::get().max() as usize);
+
+        // Make an attempt to add class schema, providing existing property index, which corresponding property does not added yet
+        let add_class_schema_result = add_class_schema(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            BTreeSet::from_iter(vec![UNKNOWN_PROPERTY_ID].into_iter()),
+            vec![property],
+        );
+
+        // Failure checked
+        assert_failure(
+            add_class_schema_result,
+            Error::<Runtime>::ClassSchemaRefersUnknownPropertyIndex,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn add_class_schema_property_name_too_long() {
+    with_test_externalities(|| {
+        // Create simple class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Runtime tested state before call
+
+        // Events number before tested calls
+        let number_of_events_before_call = System::events().len();
+
+        let property = Property::<Runtime>::invalid(InvalidPropertyType::NameTooLong);
+
+        // Make an attempt to add class schema, providing property with name, which length exceeds PropertyNameLengthConstraint
+        let add_class_schema_result =
+            add_class_schema(LEAD_ORIGIN, FIRST_CLASS_ID, BTreeSet::new(), vec![property]);
+
+        // Failure checked
+        assert_failure(
+            add_class_schema_result,
+            Error::<Runtime>::PropertyNameTooLong,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn add_class_schema_property_name_too_short() {
+    with_test_externalities(|| {
+        // Create simple class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Runtime tested state before call
+
+        // Events number before tested calls
+        let number_of_events_before_call = System::events().len();
+
+        let property = Property::<Runtime>::invalid(InvalidPropertyType::NameTooShort);
+
+        // Make an attempt to add class schema, providing property with name, which length is less than min value of PropertyNameLengthConstraint
+        let add_class_schema_result =
+            add_class_schema(LEAD_ORIGIN, FIRST_CLASS_ID, BTreeSet::new(), vec![property]);
+
+        // Failure checked
+        assert_failure(
+            add_class_schema_result,
+            Error::<Runtime>::PropertyNameTooShort,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn add_class_schema_property_description_too_long() {
+    with_test_externalities(|| {
+        // Create simple class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Runtime tested state before call
+
+        // Events number before tested calls
+        let number_of_events_before_call = System::events().len();
+
+        let property = Property::<Runtime>::invalid(InvalidPropertyType::DescriptionTooLong);
+
+        // Make an attempt to add class schema, providing property with description, which length exceeds PropertyDescriptionLengthConstraint
+        let add_class_schema_result =
+            add_class_schema(LEAD_ORIGIN, FIRST_CLASS_ID, BTreeSet::new(), vec![property]);
+
+        // Failure checked
+        assert_failure(
+            add_class_schema_result,
+            Error::<Runtime>::PropertyDescriptionTooLong,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn add_class_schema_property_description_too_short() {
+    with_test_externalities(|| {
+        // Create simple class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Runtime tested state before call
+
+        // Events number before tested calls
+        let number_of_events_before_call = System::events().len();
+
+        let property = Property::<Runtime>::invalid(InvalidPropertyType::DescriptionTooShort);
+
+        // Make an attempt to add class schema, providing property with description, which length is less than min value of PropertyDescriptionLengthConstraint
+        let add_class_schema_result =
+            add_class_schema(LEAD_ORIGIN, FIRST_CLASS_ID, BTreeSet::new(), vec![property]);
+
+        // Failure checked
+        assert_failure(
+            add_class_schema_result,
+            Error::<Runtime>::PropertyDescriptionTooShort,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn add_class_schema_text_property_is_too_long() {
+    with_test_externalities(|| {
+        // Create simple class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Runtime tested state before call
+
+        // Events number before tested calls
+        let number_of_events_before_call = System::events().len();
+
+        let property = Property::<Runtime>::invalid(InvalidPropertyType::TextIsTooLong);
+
+        // Make an attempt to add class schema, providing property with Text type, which TextMaxLength exceeds corresponding TextMaxLengthConstraint
+        let add_class_schema_result =
+            add_class_schema(LEAD_ORIGIN, FIRST_CLASS_ID, BTreeSet::new(), vec![property]);
+
+        // Failure checked
+        assert_failure(
+            add_class_schema_result,
+            Error::<Runtime>::TextPropertyTooLong,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn add_class_schema_text_hash_property_is_too_long() {
+    with_test_externalities(|| {
+        // Create simple class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Runtime tested state before call
+
+        // Events number before tested calls
+        let number_of_events_before_call = System::events().len();
+
+        let property = Property::<Runtime>::invalid(InvalidPropertyType::TextHashIsTooLong);
+
+        // Make an attempt to add class schema, providing property with Hash type,
+        // which HashedTextMaxLength exceeds corresponding HashedTextMaxLengthConstraint
+        let add_class_schema_result =
+            add_class_schema(LEAD_ORIGIN, FIRST_CLASS_ID, BTreeSet::new(), vec![property]);
+
+        // Failure checked
+        assert_failure(
+            add_class_schema_result,
+            Error::<Runtime>::HashedTextPropertyTooLong,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn add_class_schema_property_vec_property_is_too_long() {
+    with_test_externalities(|| {
+        // Create simple class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Runtime tested state before call
+
+        // Events number before tested calls
+        let number_of_events_before_call = System::events().len();
+
+        let property = Property::<Runtime>::invalid(InvalidPropertyType::VecIsTooLong);
+
+        // Make an attempt to add class schema, providing Vector property, which VecMaxLength exceeds corresponding VecMaxLengthConstraint
+        let add_class_schema_result =
+            add_class_schema(LEAD_ORIGIN, FIRST_CLASS_ID, BTreeSet::new(), vec![property]);
+
+        // Failure checked
+        assert_failure(
+            add_class_schema_result,
+            Error::<Runtime>::VecPropertyTooLong,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn add_class_schema_property_refers_unknown_class() {
+    with_test_externalities(|| {
+        // Create simple class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Runtime tested state before call
+
+        // Events number before tested calls
+        let number_of_events_before_call = System::events().len();
+
+        let reference_vec_type = PropertyType::<Runtime>::vec_reference(
+            UNKNOWN_CLASS_ID,
+            true,
+            VecMaxLengthConstraint::get(),
+        );
+        let property = Property::<Runtime>::with_name_and_type(1, reference_vec_type, true, true);
+
+        // Make an attempt to add class schema, providing property with Type::Reference, which refers to unknown ClassId
+        let add_class_schema_result =
+            add_class_schema(LEAD_ORIGIN, FIRST_CLASS_ID, BTreeSet::new(), vec![property]);
+
+        // Failure checked
+        assert_failure(
+            add_class_schema_result,
+            Error::<Runtime>::ClassSchemaRefersUnknownClass,
+            number_of_events_before_call,
+        );
+    })
+}

+ 59 - 0
runtime-modules/content-directory/src/tests/add_curator_group.rs

@@ -0,0 +1,59 @@
+use super::*;
+
+///Root Origin
+
+#[test]
+fn add_curator_group_success() {
+    with_test_externalities(|| {
+        // Runtime tested state before call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        assert_eq!(next_curator_group_id(), FIRST_CURATOR_GROUP_ID);
+        assert!(!curator_group_exists(FIRST_CURATOR_GROUP_ID));
+
+        // Add curator group
+        assert_ok!(add_curator_group(LEAD_ORIGIN));
+
+        // Runtime tested state after call
+
+        // Ensure new curator group exists and equal to default one right after creation
+        let curator_group = CuratorGroup::default();
+        assert_eq!(curator_group_by_id(FIRST_CURATOR_GROUP_ID), curator_group);
+
+        // Overall curator groups counter after curator group creation creation checked
+        assert_eq!(next_curator_group_id(), SECOND_CURATOR_GROUP_ID);
+
+        assert!(curator_group_exists(FIRST_CURATOR_GROUP_ID));
+
+        let curator_group_created_event =
+            get_test_event(RawEvent::CuratorGroupAdded(FIRST_CURATOR_GROUP_ID));
+
+        // Event checked
+        assert_event_success(
+            curator_group_created_event,
+            number_of_events_before_call + 1,
+        );
+    })
+}
+
+#[test]
+fn add_curator_group_lead_auth_failed() {
+    with_test_externalities(|| {
+        // Runtime tested state before call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // An attemt to add curator group from non lead origin
+        let add_curator_group_result = add_curator_group(FIRST_MEMBER_ORIGIN);
+
+        // Failure checked
+        assert_failure(
+            add_curator_group_result,
+            Error::<Runtime>::LeadAuthFailed,
+            number_of_events_before_call,
+        );
+    })
+}

+ 131 - 0
runtime-modules/content-directory/src/tests/add_curator_to_group.rs

@@ -0,0 +1,131 @@
+use super::*;
+
+#[test]
+fn add_curator_to_group_success() {
+    with_test_externalities(|| {
+        // Add curator group
+        assert_ok!(add_curator_group(LEAD_ORIGIN));
+
+        // Runtime tested state before call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Add curator to group
+        assert_ok!(add_curator_to_group(
+            LEAD_ORIGIN,
+            FIRST_CURATOR_GROUP_ID,
+            FIRST_CURATOR_ID
+        ));
+
+        // Runtime tested state after call
+
+        // Ensure curator added to group
+        let mut curator_group = CuratorGroup::default();
+        curator_group.get_curators_mut().insert(FIRST_CURATOR_ID);
+        assert_eq!(curator_group_by_id(FIRST_CURATOR_GROUP_ID), curator_group);
+
+        let curator_group_curator_added_event = get_test_event(RawEvent::CuratorAdded(
+            FIRST_CURATOR_GROUP_ID,
+            FIRST_CURATOR_ID,
+        ));
+
+        // Event checked
+        assert_event_success(
+            curator_group_curator_added_event,
+            number_of_events_before_call + 1,
+        );
+    })
+}
+
+#[test]
+fn add_curator_to_group_lead_auth_failed() {
+    with_test_externalities(|| {
+        // Add curator group
+        assert_ok!(add_curator_group(LEAD_ORIGIN));
+
+        // Runtime tested state before call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to add curator to group from non lead origin
+        let add_curator_to_group_result = add_curator_to_group(
+            FIRST_MEMBER_ORIGIN,
+            FIRST_CURATOR_GROUP_ID,
+            FIRST_CURATOR_ID,
+        );
+
+        // Failure checked
+        assert_failure(
+            add_curator_to_group_result,
+            Error::<Runtime>::LeadAuthFailed,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn add_curator_to_non_existent_group() {
+    with_test_externalities(|| {
+        // Runtime tested state before call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to add curator to group that does not exist
+        let add_curator_to_group_result =
+            add_curator_to_group(LEAD_ORIGIN, UNKNOWN_CURATOR_GROUP_ID, FIRST_CURATOR_ID);
+
+        // Failure checked
+        assert_failure(
+            add_curator_to_group_result,
+            Error::<Runtime>::CuratorGroupDoesNotExist,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn add_curator_to_group_curators_limit_reached() {
+    with_test_externalities(|| {
+        // Add curator group
+        assert_ok!(add_curator_group(LEAD_ORIGIN));
+
+        // Runtime tested state before call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let mut number_of_curators_added = 0;
+
+        // Adding curators to the same group until limit reached
+        let add_curator_to_group_result = loop {
+            // Add curator to group
+            let add_curator_to_group_result = add_curator_to_group(
+                LEAD_ORIGIN,
+                FIRST_CURATOR_GROUP_ID,
+                number_of_curators_added,
+            );
+
+            if add_curator_to_group_result.is_err() {
+                break add_curator_to_group_result;
+            } else {
+                number_of_curators_added += 1;
+            }
+        };
+
+        // Ensure number of curators added is equal to the MaxNumberOfCuratorsPerGroup runtime limit
+        assert_eq!(
+            number_of_curators_added as u32,
+            MaxNumberOfCuratorsPerGroup::get()
+        );
+
+        // Failure checked
+        assert_failure(
+            add_curator_to_group_result,
+            Error::<Runtime>::CuratorsPerGroupLimitReached,
+            number_of_events_before_call + number_of_curators_added as usize,
+        );
+    })
+}

+ 195 - 0
runtime-modules/content-directory/src/tests/add_maintainer_to_class.rs

@@ -0,0 +1,195 @@
+use super::*;
+
+#[test]
+fn add_maintainer_to_class_success() {
+    with_test_externalities(|| {
+        // Create simple class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Add curator group
+        assert_ok!(add_curator_group(LEAD_ORIGIN));
+        // Runtime tested state before call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        assert_ok!(add_maintainer_to_class(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            FIRST_CURATOR_GROUP_ID
+        ));
+
+        // Runtime tested state after call
+
+        // Ensure curator_group added as class maintainer
+        let mut class = create_class_with_default_permissions();
+        class
+            .get_permissions_mut()
+            .get_maintainers_mut()
+            .insert(FIRST_CURATOR_GROUP_ID);
+        assert_eq!(class_by_id(FIRST_CLASS_ID), class);
+
+        let maintainer_added_event = get_test_event(RawEvent::MaintainerAdded(
+            FIRST_CLASS_ID,
+            FIRST_CURATOR_GROUP_ID,
+        ));
+
+        // Event checked
+        assert_event_success(maintainer_added_event, number_of_events_before_call + 1);
+    })
+}
+
+#[test]
+fn add_maintainer_to_class_lead_auth_failed() {
+    with_test_externalities(|| {
+        // Create simple class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Add curator group
+        assert_ok!(add_curator_group(LEAD_ORIGIN));
+
+        // Runtime tested state before call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to add maintainer to class from non lead origin
+        let add_maintainer_result =
+            add_maintainer_to_class(SECOND_MEMBER_ORIGIN, FIRST_CLASS_ID, FIRST_CURATOR_GROUP_ID);
+
+        // Failure checked
+        assert_failure(
+            add_maintainer_result,
+            Error::<Runtime>::LeadAuthFailed,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn add_non_existent_maintainer_to_class() {
+    with_test_externalities(|| {
+        // Create simple class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Runtime tested state before call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to add non existent maintainer curator group to class
+        let add_maintainer_result =
+            add_maintainer_to_class(LEAD_ORIGIN, FIRST_CLASS_ID, UNKNOWN_CURATOR_GROUP_ID);
+
+        // Failure checked
+        assert_failure(
+            add_maintainer_result,
+            Error::<Runtime>::CuratorGroupDoesNotExist,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn add_maintainer_to_non_existent_class() {
+    with_test_externalities(|| {
+        // Add curator group
+        assert_ok!(add_curator_group(LEAD_ORIGIN));
+        // Runtime tested state before call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to add maintainer to non existent class
+        let add_maintainer_result =
+            add_maintainer_to_class(LEAD_ORIGIN, UNKNOWN_CLASS_ID, FIRST_CURATOR_GROUP_ID);
+
+        // Failure checked
+        assert_failure(
+            add_maintainer_result,
+            Error::<Runtime>::ClassNotFound,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn add_maintainer_to_class_limit_reached() {
+    with_test_externalities(|| {
+        // Create simple class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Add curator groups to runtime storage, that will be used to be added as class maintainers
+        for _ in 0..=MaxNumberOfMaintainersPerClass::get() {
+            assert_ok!(add_curator_group(LEAD_ORIGIN));
+        }
+
+        // Runtime tested state before call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let mut number_of_maintainers_added_to_class = 0_usize;
+
+        // Adding curator group maintainers to the same class until limit reached
+        let add_maintainer_result = loop {
+            let curator_group_id = (number_of_maintainers_added_to_class + 1) as CuratorGroupId;
+
+            let add_maintainer_result =
+                add_maintainer_to_class(LEAD_ORIGIN, FIRST_CLASS_ID, curator_group_id);
+
+            if add_maintainer_result.is_err() {
+                break add_maintainer_result;
+            } else {
+                number_of_maintainers_added_to_class += 1;
+            }
+        };
+
+        // Ensure number of maintainers added to the class is equal to MaxNumberOfMaintainersPerClass constraint defined on class level
+        assert_eq!(
+            number_of_maintainers_added_to_class,
+            MaxNumberOfMaintainersPerClass::get() as usize
+        );
+
+        // Failure checked
+        assert_failure(
+            add_maintainer_result,
+            Error::<Runtime>::ClassMaintainersLimitReached,
+            number_of_events_before_call + number_of_maintainers_added_to_class,
+        );
+    })
+}
+
+#[test]
+fn add_maintainer_that_is_already_exist() {
+    with_test_externalities(|| {
+        // Create simple class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Add curator group
+        assert_ok!(add_curator_group(LEAD_ORIGIN));
+
+        // Add maintainer
+        assert_ok!(add_maintainer_to_class(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            FIRST_CURATOR_GROUP_ID
+        ));
+
+        // Runtime tested state before call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to add curator group maintainer, that is already added to given class maintainers set
+        let add_maintainer_result =
+            add_maintainer_to_class(LEAD_ORIGIN, FIRST_CLASS_ID, FIRST_CURATOR_GROUP_ID);
+
+        // Failure checked
+        assert_failure(
+            add_maintainer_result,
+            Error::<Runtime>::MaintainerAlreadyExists,
+            number_of_events_before_call,
+        );
+    })
+}

+ 1458 - 0
runtime-modules/content-directory/src/tests/add_schema_support_to_entity.rs

@@ -0,0 +1,1458 @@
+use super::*;
+
+#[test]
+fn add_schema_support_to_entity_success() {
+    with_test_externalities(|| {
+        // Add entity schemas support
+        let (first_entity, second_entity) = add_entity_schemas_support();
+
+        // Ensure supported schemas set and properties of first entity updated succesfully
+        assert_eq!(first_entity, entity_by_id(FIRST_ENTITY_ID));
+
+        // Ensure reference counter of second entity updated succesfully
+        assert_eq!(second_entity, entity_by_id(SECOND_ENTITY_ID));
+    })
+}
+
+#[test]
+fn add_schema_support_to_non_existent_entity() {
+    with_test_externalities(|| {
+        let actor = emulate_entity_access_state_for_failure_case(
+            EntityAccessStateFailureType::EntityNotFound,
+        );
+
+        // Create property
+        let property = Property::<Runtime>::default_with_name(
+            PropertyNameLengthConstraint::get().max() as usize,
+        );
+
+        // Add Schema to the Class
+        assert_ok!(add_class_schema(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            BTreeSet::new(),
+            vec![property]
+        ));
+
+        let mut schema_property_values = BTreeMap::new();
+        schema_property_values.insert(FIRST_PROPERTY_ID, InputPropertyValue::default());
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to add schema support to non existent Entity
+        let add_schema_support_to_entity_result = add_schema_support_to_entity(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            schema_property_values,
+        );
+
+        // Failure checked
+        assert_failure(
+            add_schema_support_to_entity_result,
+            Error::<Runtime>::EntityNotFound,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn add_schema_support_lead_auth_failed() {
+    with_test_externalities(|| {
+        let actor = emulate_entity_access_state_for_failure_case(
+            EntityAccessStateFailureType::LeadAuthFailed,
+        );
+
+        // Create property
+        let property = Property::<Runtime>::default_with_name(
+            PropertyNameLengthConstraint::get().max() as usize,
+        );
+
+        // Add Schema to the Class
+        assert_ok!(add_class_schema(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            BTreeSet::new(),
+            vec![property]
+        ));
+
+        let mut schema_property_values = BTreeMap::new();
+        schema_property_values.insert(FIRST_PROPERTY_ID, InputPropertyValue::default());
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to add schema support under non lead origin
+        let add_schema_support_to_entity_result = add_schema_support_to_entity(
+            UNKNOWN_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            schema_property_values,
+        );
+
+        // Failure checked
+        assert_failure(
+            add_schema_support_to_entity_result,
+            Error::<Runtime>::LeadAuthFailed,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn add_schema_support_member_auth_failed() {
+    with_test_externalities(|| {
+        let actor = emulate_entity_access_state_for_failure_case(
+            EntityAccessStateFailureType::MemberAuthFailed,
+        );
+
+        // Create property
+        let property = Property::<Runtime>::default_with_name(
+            PropertyNameLengthConstraint::get().max() as usize,
+        );
+
+        // Add Schema to the Class
+        assert_ok!(add_class_schema(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            BTreeSet::new(),
+            vec![property]
+        ));
+
+        let mut schema_property_values = BTreeMap::new();
+        schema_property_values.insert(FIRST_PROPERTY_ID, InputPropertyValue::default());
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to add schema support to entity using unknown origin and member actor
+        let add_schema_support_to_entity_result = add_schema_support_to_entity(
+            UNKNOWN_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            schema_property_values,
+        );
+
+        // Failure checked
+        assert_failure(
+            add_schema_support_to_entity_result,
+            Error::<Runtime>::MemberAuthFailed,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn add_schema_support_curator_group_is_not_active() {
+    with_test_externalities(|| {
+        let actor = emulate_entity_access_state_for_failure_case(
+            EntityAccessStateFailureType::CuratorAuthFailed,
+        );
+
+        // Make curator group inactive to block it from any entity operations
+        assert_ok!(set_curator_group_status(
+            LEAD_ORIGIN,
+            FIRST_CURATOR_GROUP_ID,
+            false
+        ));
+
+        // Create property
+        let property = Property::<Runtime>::default_with_name(
+            PropertyNameLengthConstraint::get().max() as usize,
+        );
+
+        // Add Schema to the Class
+        assert_ok!(add_class_schema(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            BTreeSet::new(),
+            vec![property]
+        ));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let mut schema_property_values = BTreeMap::new();
+        schema_property_values.insert(FIRST_PROPERTY_ID, InputPropertyValue::default());
+
+        // Make an attempt to add schema support to entity using curator group, which is not active as actor
+        let add_schema_support_to_entity_result = add_schema_support_to_entity(
+            FIRST_CURATOR_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            schema_property_values,
+        );
+
+        // Failure checked
+        assert_failure(
+            add_schema_support_to_entity_result,
+            Error::<Runtime>::CuratorGroupIsNotActive,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn add_schema_support_curator_auth_failed() {
+    with_test_externalities(|| {
+        let actor = emulate_entity_access_state_for_failure_case(
+            EntityAccessStateFailureType::CuratorAuthFailed,
+        );
+
+        // Create property
+        let property = Property::<Runtime>::default_with_name(
+            PropertyNameLengthConstraint::get().max() as usize,
+        );
+
+        // Add Schema to the Class
+        assert_ok!(add_class_schema(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            BTreeSet::new(),
+            vec![property]
+        ));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let mut schema_property_values = BTreeMap::new();
+        schema_property_values.insert(FIRST_PROPERTY_ID, InputPropertyValue::default());
+
+        // Make an attempt to add schema support to entity under unknown origin and curator actor,
+        // which corresponding group is current entity controller
+        let add_schema_support_to_entity_result = add_schema_support_to_entity(
+            UNKNOWN_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            schema_property_values,
+        );
+
+        // Failure checked
+        assert_failure(
+            add_schema_support_to_entity_result,
+            Error::<Runtime>::CuratorAuthFailed,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn add_schema_support_curator_not_found_in_curator_group() {
+    with_test_externalities(|| {
+        let actor = emulate_entity_access_state_for_failure_case(
+            EntityAccessStateFailureType::CuratorNotFoundInCuratorGroup,
+        );
+
+        // Create property
+        let property = Property::<Runtime>::default_with_name(
+            PropertyNameLengthConstraint::get().max() as usize,
+        );
+
+        // Add Schema to the Class
+        assert_ok!(add_class_schema(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            BTreeSet::new(),
+            vec![property]
+        ));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let mut schema_property_values = BTreeMap::new();
+        schema_property_values.insert(FIRST_PROPERTY_ID, InputPropertyValue::default());
+
+        // Make an attempt to add schema support to entity, using actor in group,
+        // which curator id was not added to corresponding group set
+        let add_schema_support_to_entity_result = add_schema_support_to_entity(
+            SECOND_CURATOR_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            schema_property_values,
+        );
+
+        // Failure checked
+        assert_failure(
+            add_schema_support_to_entity_result,
+            Error::<Runtime>::CuratorIsNotAMemberOfGivenCuratorGroup,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn add_schema_support_access_denied() {
+    with_test_externalities(|| {
+        let actor = emulate_entity_access_state_for_failure_case(
+            EntityAccessStateFailureType::EntityAccessDenied,
+        );
+
+        // Create property
+        let property = Property::<Runtime>::default_with_name(
+            PropertyNameLengthConstraint::get().max() as usize,
+        );
+
+        // Add Schema to the Class
+        assert_ok!(add_class_schema(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            BTreeSet::new(),
+            vec![property]
+        ));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let mut schema_property_values = BTreeMap::new();
+        schema_property_values.insert(FIRST_PROPERTY_ID, InputPropertyValue::default());
+
+        // Make an attempt to add schema support to entity, using origin,
+        // which corresponding actor is neither entity maintainer, nor controller.
+        let add_schema_support_to_entity_result = add_schema_support_to_entity(
+            SECOND_MEMBER_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            schema_property_values,
+        );
+
+        // Failure checked
+        assert_failure(
+            add_schema_support_to_entity_result,
+            Error::<Runtime>::EntityAccessDenied,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn add_schema_support_to_entity_schema_does_not_exist() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        let actor = Actor::Lead;
+
+        // Create entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let mut schema_property_values = BTreeMap::new();
+        schema_property_values.insert(FIRST_PROPERTY_ID, InputPropertyValue::default());
+
+        // Make an attempt to add schema support to entity, providing schema_id,
+        // which corresponding Schema does not exist on Class level
+        let add_schema_support_to_entity_result = add_schema_support_to_entity(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            schema_property_values,
+        );
+
+        // Failure checked
+        assert_failure(
+            add_schema_support_to_entity_result,
+            Error::<Runtime>::UnknownClassSchemaId,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn add_schema_support_to_entity_class_property_not_found() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Create property
+        let property = Property::<Runtime>::default_with_name(
+            PropertyNameLengthConstraint::get().max() as usize,
+        );
+
+        // Add Schema to the Class
+        assert_ok!(add_class_schema(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            BTreeSet::new(),
+            vec![property]
+        ));
+
+        let actor = Actor::Lead;
+
+        // Create entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let mut schema_property_values = BTreeMap::new();
+        schema_property_values.insert(SECOND_PROPERTY_ID, InputPropertyValue::default());
+
+        // Make an attempt to add schema support to Entity, providing property value under property_id,
+        // which does not not yet added to corresponding Class properties
+        let add_schema_support_to_entity_result = add_schema_support_to_entity(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            schema_property_values,
+        );
+
+        // Failure checked
+        assert_failure(
+            add_schema_support_to_entity_result,
+            Error::<Runtime>::ClassPropertyNotFound,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn add_schema_support_already_added_to_the_entity() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Create property
+        let property = Property::<Runtime>::default_with_name(
+            PropertyNameLengthConstraint::get().max() as usize,
+        );
+
+        // Add Schema to the Class
+        assert_ok!(add_class_schema(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            BTreeSet::new(),
+            vec![property]
+        ));
+
+        let actor = Actor::Lead;
+
+        // Create entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        let mut schema_property_values = BTreeMap::new();
+        schema_property_values.insert(FIRST_PROPERTY_ID, InputPropertyValue::default());
+
+        assert_ok!(add_schema_support_to_entity(
+            LEAD_ORIGIN,
+            actor.clone(),
+            FIRST_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            schema_property_values.clone(),
+        ));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to add schema support to entity, providing schema_id,
+        // which was already added to the Entity
+        let add_schema_support_to_entity_result = add_schema_support_to_entity(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            schema_property_values,
+        );
+
+        // Failure checked
+        assert_failure(
+            add_schema_support_to_entity_result,
+            Error::<Runtime>::SchemaAlreadyAddedToTheEntity,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn add_schema_support_already_contains_given_property_id() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Create first property
+        let first_property = Property::<Runtime>::default_with_name(
+            PropertyNameLengthConstraint::get().max() as usize,
+        );
+
+        // Add first Schema to the Class
+        assert_ok!(add_class_schema(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            BTreeSet::new(),
+            vec![first_property]
+        ));
+
+        // Create second property
+        let second_property_type =
+            PropertyType::<Runtime>::single_text(TextMaxLengthConstraint::get());
+        let second_property = Property::<Runtime>::with_name_and_type(
+            PropertyNameLengthConstraint::get().max() as usize - 1,
+            second_property_type,
+            true,
+            false,
+        );
+
+        // Add second Schema to the Class
+        assert_ok!(add_class_schema(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            BTreeSet::from_iter(vec![FIRST_PROPERTY_ID].into_iter()),
+            vec![second_property]
+        ));
+
+        let actor = Actor::Lead;
+
+        // Create entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        let mut schema_property_values = BTreeMap::new();
+        schema_property_values.insert(FIRST_PROPERTY_ID, InputPropertyValue::default());
+
+        assert_ok!(add_schema_support_to_entity(
+            LEAD_ORIGIN,
+            actor.clone(),
+            FIRST_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            schema_property_values.clone(),
+        ));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        schema_property_values.insert(
+            SECOND_PROPERTY_ID,
+            InputPropertyValue::<Runtime>::single_text(TextMaxLengthConstraint::get()),
+        );
+
+        // Make an attempt to add schema support to entity, providing Schema property_values,
+        // some of which were already added to this Entity
+        let add_schema_support_to_entity_result = add_schema_support_to_entity(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            SECOND_SCHEMA_ID,
+            schema_property_values,
+        );
+
+        // Failure checked
+        assert_failure(
+            add_schema_support_to_entity_result,
+            Error::<Runtime>::EntityAlreadyContainsGivenPropertyId,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn add_schema_support_is_not_active() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Create property
+        let property = Property::<Runtime>::default_with_name(
+            PropertyNameLengthConstraint::get().max() as usize,
+        );
+
+        // Add Schema to the Class
+        assert_ok!(add_class_schema(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            BTreeSet::new(),
+            vec![property]
+        ));
+
+        let actor = Actor::Lead;
+
+        // Create entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        // Make Class Schema inactive
+        assert_ok!(update_class_schema_status(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            FIRST_SCHEMA_ID,
+            false
+        ));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let mut schema_property_values = BTreeMap::new();
+        schema_property_values.insert(FIRST_PROPERTY_ID, InputPropertyValue::default());
+
+        // Make an attempt to add schema support to Entity, providing schema id,
+        // which corresponding class Schema is not active
+        let add_schema_support_to_entity_result = add_schema_support_to_entity(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            schema_property_values,
+        );
+
+        // Failure checked
+        assert_failure(
+            add_schema_support_to_entity_result,
+            Error::<Runtime>::ClassSchemaNotActive,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn add_schema_support_does_not_contain_provided_property_id() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Create first property
+        let first_property = Property::<Runtime>::default_with_name(
+            PropertyNameLengthConstraint::get().max() as usize,
+        );
+
+        // Add Schema to the Class
+        assert_ok!(add_class_schema(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            BTreeSet::new(),
+            vec![first_property]
+        ));
+
+        // Create second property
+        let second_property = Property::<Runtime>::default_with_name(
+            PropertyNameLengthConstraint::get().max() as usize - 1,
+        );
+
+        // Add Schema to the Class
+        assert_ok!(add_class_schema(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            BTreeSet::new(),
+            vec![second_property]
+        ));
+
+        let actor = Actor::Lead;
+
+        // Create entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let mut schema_property_values = BTreeMap::new();
+        schema_property_values.insert(SECOND_PROPERTY_ID, InputPropertyValue::default());
+
+        // Make an attempt to add schema support to Entity, providing property values, which are not a members of
+        // provided Schema
+        let add_schema_support_to_entity_result = add_schema_support_to_entity(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            schema_property_values,
+        );
+
+        // Failure checked
+        assert_failure(
+            add_schema_support_to_entity_result,
+            Error::<Runtime>::SchemaDoesNotContainProvidedPropertyId,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn add_schema_support_missing_required_property() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Create first property
+        let first_property = Property::<Runtime>::default_with_name(
+            PropertyNameLengthConstraint::get().max() as usize,
+        );
+
+        // Create second property
+        let second_property_type =
+            PropertyType::<Runtime>::single_text(TextMaxLengthConstraint::get());
+        let second_property = Property::<Runtime>::with_name_and_type(
+            PropertyNameLengthConstraint::get().max() as usize - 1,
+            second_property_type,
+            true,
+            false,
+        );
+
+        // Add Schema to the Class
+        assert_ok!(add_class_schema(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            BTreeSet::new(),
+            vec![first_property, second_property]
+        ));
+
+        let actor = Actor::Lead;
+
+        // Create entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let mut schema_property_values = BTreeMap::new();
+        schema_property_values.insert(FIRST_PROPERTY_ID, InputPropertyValue::default());
+
+        // Make an attempt to add schema support to Entity, do not providing some of required property values
+        let add_schema_support_to_entity_result = add_schema_support_to_entity(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            schema_property_values,
+        );
+
+        // Failure checked
+        assert_failure(
+            add_schema_support_to_entity_result,
+            Error::<Runtime>::MissingRequiredProperty,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn add_schema_support_dont_match_type() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Create property
+        let property_type = PropertyType::<Runtime>::single_text(TextMaxLengthConstraint::get());
+        let property = Property::<Runtime>::with_name_and_type(
+            PropertyNameLengthConstraint::get().max() as usize - 1,
+            property_type,
+            true,
+            false,
+        );
+
+        // Add Schema to the Class
+        assert_ok!(add_class_schema(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            BTreeSet::new(),
+            vec![property]
+        ));
+
+        let actor = Actor::Lead;
+
+        // Create entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let mut schema_property_values = BTreeMap::new();
+        schema_property_values.insert(FIRST_PROPERTY_ID, InputPropertyValue::default());
+
+        // Make an attempt to add schema support to Entity, providing property values, some of which do not match
+        // Class level Property Type
+        let add_schema_support_to_entity_result = add_schema_support_to_entity(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            schema_property_values,
+        );
+
+        // Failure checked
+        assert_failure(
+            add_schema_support_to_entity_result,
+            Error::<Runtime>::PropertyValueDoNotMatchType,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn add_schema_support_referenced_entity_does_not_match_class() {
+    with_test_externalities(|| {
+        // Create first class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Create second class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        let actor = Actor::Lead;
+
+        // Create first entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.to_owned()));
+
+        // Create second entity
+        assert_ok!(create_entity(
+            LEAD_ORIGIN,
+            SECOND_CLASS_ID,
+            actor.to_owned()
+        ));
+
+        // Create property
+        let property_type = PropertyType::<Runtime>::vec_reference(FIRST_CLASS_ID, true, 5);
+
+        let property = Property::<Runtime>::with_name_and_type(
+            (PropertyNameLengthConstraint::get().max() - 1) as usize,
+            property_type,
+            true,
+            false,
+        );
+
+        // Add Schema to the first Class
+        assert_ok!(add_class_schema(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            BTreeSet::new(),
+            vec![property]
+        ));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let mut schema_property_values = BTreeMap::new();
+        let schema_property_value =
+            InputPropertyValue::<Runtime>::vec_reference(vec![SECOND_ENTITY_ID, SECOND_ENTITY_ID]);
+
+        schema_property_values.insert(FIRST_PROPERTY_ID, schema_property_value);
+
+        // Make an attempt to add schema support to the Entity, when provided schema property value(s) refer(s) Entity, which Class
+        // does not match the class in corresponding Class Property
+        let add_schema_support_to_entity_result = add_schema_support_to_entity(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            schema_property_values,
+        );
+
+        // Failure checked
+        assert_failure(
+            add_schema_support_to_entity_result,
+            Error::<Runtime>::ReferencedEntityDoesNotMatchItsClass,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn add_schema_support_referenced_entity_does_not_exist() {
+    with_test_externalities(|| {
+        // Create first class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Create second class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        let actor = Actor::Lead;
+
+        // Create entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.to_owned()));
+
+        // Create property
+        let property_type = PropertyType::<Runtime>::vec_reference(SECOND_CLASS_ID, true, 5);
+
+        let property = Property::<Runtime>::with_name_and_type(
+            (PropertyNameLengthConstraint::get().max() - 1) as usize,
+            property_type,
+            true,
+            false,
+        );
+
+        // Add Schema to the first Class
+        assert_ok!(add_class_schema(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            BTreeSet::new(),
+            vec![property]
+        ));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let mut schema_property_values = BTreeMap::new();
+        let schema_property_value = InputPropertyValue::<Runtime>::vec_reference(vec![
+            UNKNOWN_ENTITY_ID,
+            UNKNOWN_ENTITY_ID,
+        ]);
+
+        schema_property_values.insert(FIRST_PROPERTY_ID, schema_property_value);
+
+        // Make an attempt to add schema support to the first entity, if provided property value(s) refer(s) to another Entity,
+        // which does not exist in runtime
+        let add_schema_support_to_entity_result = add_schema_support_to_entity(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            schema_property_values,
+        );
+
+        // Failure checked
+        assert_failure(
+            add_schema_support_to_entity_result,
+            Error::<Runtime>::EntityNotFound,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn add_schema_support_entity_can_not_be_referenced() {
+    with_test_externalities(|| {
+        // Create first class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        let actor = Actor::Lead;
+
+        // Create first entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.to_owned()));
+
+        // Create second entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.to_owned()));
+
+        // Update second entity permissions to forbid it from being referencable
+        assert_ok!(update_entity_permissions(
+            LEAD_ORIGIN,
+            SECOND_ENTITY_ID,
+            None,
+            Some(false)
+        ));
+
+        // Create property
+        let property_type = PropertyType::<Runtime>::vec_reference(FIRST_CLASS_ID, true, 5);
+
+        let property = Property::<Runtime>::with_name_and_type(
+            (PropertyNameLengthConstraint::get().max() - 1) as usize,
+            property_type,
+            true,
+            false,
+        );
+
+        // Add Schema to the Class
+        assert_ok!(add_class_schema(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            BTreeSet::new(),
+            vec![property]
+        ));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let mut schema_property_values = BTreeMap::new();
+        let schema_property_value =
+            InputPropertyValue::<Runtime>::vec_reference(vec![SECOND_ENTITY_ID, SECOND_ENTITY_ID]);
+
+        schema_property_values.insert(FIRST_PROPERTY_ID, schema_property_value);
+
+        // Make an attempt to add schema support to the first entity, when provided schema property value(s)
+        // refer(s) to Entity which can not be referenced
+        let add_schema_support_to_entity_result = add_schema_support_to_entity(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            schema_property_values,
+        );
+
+        // Failure checked
+        assert_failure(
+            add_schema_support_to_entity_result,
+            Error::<Runtime>::EntityCanNotBeReferenced,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn add_schema_support_same_controller_constraint_violation() {
+    with_test_externalities(|| {
+        // Create first class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Update class permissions to force any member be available to create Entities
+        assert_ok!(update_class_permissions(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            Some(true),
+            None,
+            None,
+            None
+        ));
+
+        let actor = Actor::Lead;
+
+        // Create first entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.to_owned()));
+
+        // Create second entity
+        assert_ok!(create_entity(
+            FIRST_MEMBER_ORIGIN,
+            FIRST_CLASS_ID,
+            Actor::Member(FIRST_MEMBER_ID)
+        ));
+
+        // Create property
+        let property_type = PropertyType::<Runtime>::vec_reference(FIRST_CLASS_ID, true, 5);
+
+        let property = Property::<Runtime>::with_name_and_type(
+            (PropertyNameLengthConstraint::get().max() - 1) as usize,
+            property_type,
+            true,
+            false,
+        );
+
+        // Add Schema to the Class
+        assert_ok!(add_class_schema(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            BTreeSet::new(),
+            vec![property]
+        ));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let mut schema_property_values = BTreeMap::new();
+        let schema_property_value =
+            InputPropertyValue::<Runtime>::vec_reference(vec![SECOND_ENTITY_ID, SECOND_ENTITY_ID]);
+
+        schema_property_values.insert(FIRST_PROPERTY_ID, schema_property_value);
+
+        // Make an attempt to add schema support to the first entity, providing reference property value(s) in case,
+        // when corresponding Entity can only be referenced from Entity with the same controller.
+        let add_schema_support_to_entity_result = add_schema_support_to_entity(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            schema_property_values,
+        );
+
+        // Failure checked
+        assert_failure(
+            add_schema_support_to_entity_result,
+            Error::<Runtime>::SameControllerConstraintViolation,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn add_schema_support_text_property_is_too_long() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        let actor = Actor::Lead;
+
+        // Create entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.to_owned()));
+
+        // Create text property
+        let property_type = PropertyType::<Runtime>::single_text(TextMaxLengthConstraint::get());
+
+        let property = Property::<Runtime>::with_name_and_type(
+            PropertyNameLengthConstraint::get().max() as usize,
+            property_type,
+            true,
+            false,
+        );
+
+        // Add Schema to the first Class
+        assert_ok!(add_class_schema(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            BTreeSet::new(),
+            vec![property]
+        ));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let mut schema_property_values = BTreeMap::new();
+
+        let schema_property_value =
+            InputPropertyValue::<Runtime>::single_text(TextMaxLengthConstraint::get() + 1);
+
+        schema_property_values.insert(FIRST_PROPERTY_ID, schema_property_value);
+
+        // Make an attempt to add schema support to the entity, providing text property value(s), which
+        // length exceeds TextMaxLengthConstraint.
+        let add_schema_support_to_entity_result = add_schema_support_to_entity(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            schema_property_values,
+        );
+
+        // Failure checked
+        assert_failure(
+            add_schema_support_to_entity_result,
+            Error::<Runtime>::TextPropertyTooLong,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn add_schema_support_text_hash_property_is_too_long() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        let actor = Actor::Lead;
+
+        // Create entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.to_owned()));
+
+        let hashed_text_max_length_constraint = HashedTextMaxLengthConstraint::get();
+
+        // Create hash property
+        let property_type =
+            PropertyType::<Runtime>::single_text_hash(hashed_text_max_length_constraint);
+
+        let property = Property::<Runtime>::with_name_and_type(
+            PropertyNameLengthConstraint::get().max() as usize,
+            property_type,
+            true,
+            false,
+        );
+
+        // Add Schema to the first Class
+        assert_ok!(add_class_schema(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            BTreeSet::new(),
+            vec![property]
+        ));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let mut schema_property_values = BTreeMap::new();
+
+        let schema_property_value = InputPropertyValue::<Runtime>::single_text_to_hash(
+            hashed_text_max_length_constraint.unwrap() + 1,
+        );
+
+        schema_property_values.insert(FIRST_PROPERTY_ID, schema_property_value);
+
+        // Make an attempt to add schema support to the entity, providing text property value(s), which
+        // length exceeds HashedTextMaxLengthConstraint.
+        let add_schema_support_to_entity_result = add_schema_support_to_entity(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            schema_property_values,
+        );
+
+        // Failure checked
+        assert_failure(
+            add_schema_support_to_entity_result,
+            Error::<Runtime>::HashedTextPropertyTooLong,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn add_schema_support_vec_property_is_too_long() {
+    with_test_externalities(|| {
+        // Create first class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Create second class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        let actor = Actor::Lead;
+
+        // Create first entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.to_owned()));
+
+        // Create second entity
+        assert_ok!(create_entity(
+            LEAD_ORIGIN,
+            SECOND_CLASS_ID,
+            actor.to_owned()
+        ));
+
+        // Create vec property
+        let property_type = PropertyType::<Runtime>::vec_reference(
+            SECOND_CLASS_ID,
+            true,
+            VecMaxLengthConstraint::get(),
+        );
+
+        let property = Property::<Runtime>::with_name_and_type(
+            (PropertyNameLengthConstraint::get().max() - 1) as usize,
+            property_type,
+            true,
+            false,
+        );
+
+        // Add Schema to the first Class
+        assert_ok!(add_class_schema(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            BTreeSet::new(),
+            vec![property]
+        ));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let mut schema_property_values = BTreeMap::new();
+        let schema_property_value = InputPropertyValue::<Runtime>::vec_reference(vec![
+                SECOND_ENTITY_ID;
+                VecMaxLengthConstraint::get() as usize + 1
+            ]);
+
+        schema_property_values.insert(FIRST_PROPERTY_ID, schema_property_value);
+
+        // Make an attempt to add schema support to the Entity, providing vector property value(s), which
+        // length exceeds VecMaxLengthConstraint.
+        let add_schema_support_to_entity_result = add_schema_support_to_entity(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            schema_property_values,
+        );
+
+        // Failure checked
+        assert_failure(
+            add_schema_support_to_entity_result,
+            Error::<Runtime>::VecPropertyTooLong,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn add_schema_support_property_should_be_unique() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        let actor = Actor::Lead;
+
+        // Create first entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.to_owned()));
+
+        // Create second entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.to_owned()));
+
+        let property_type = PropertyType::<Runtime>::single_text(TextMaxLengthConstraint::get());
+
+        // Create text property
+
+        let property = Property::<Runtime>::with_name_and_type(
+            PropertyNameLengthConstraint::get().max() as usize,
+            property_type,
+            true,
+            true,
+        );
+
+        // Add Schema to the Class
+        assert_ok!(add_class_schema(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            BTreeSet::new(),
+            vec![property]
+        ));
+
+        let mut schema_property_values = BTreeMap::new();
+
+        let schema_property_value =
+            InputPropertyValue::<Runtime>::single_text(TextMaxLengthConstraint::get());
+
+        schema_property_values.insert(FIRST_PROPERTY_ID, schema_property_value);
+
+        // Add Entity Schema support to the first Entity
+        assert_ok!(add_schema_support_to_entity(
+            LEAD_ORIGIN,
+            actor.to_owned(),
+            FIRST_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            schema_property_values.clone(),
+        ));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to add schema support to the Entity, providing property values, which respective Class properties have
+        // unique flag set and same property values under same property_ids were already added to any Entity of this Class
+        let add_schema_support_to_entity_result = add_schema_support_to_entity(
+            LEAD_ORIGIN,
+            actor,
+            SECOND_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            schema_property_values,
+        );
+
+        // Failure checked
+        assert_failure(
+            add_schema_support_to_entity_result,
+            Error::<Runtime>::PropertyValueShouldBeUnique,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn add_schema_support_properties_should_be_unique() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        let actor = Actor::Lead;
+
+        // Create first entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.to_owned()));
+
+        // Create second entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.to_owned()));
+
+        // Create third entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.to_owned()));
+
+        let property_type = PropertyType::<Runtime>::single_text(TextMaxLengthConstraint::get());
+
+        // Create text property
+
+        let property = Property::<Runtime>::with_name_and_type(
+            PropertyNameLengthConstraint::get().max() as usize,
+            property_type,
+            true,
+            true,
+        );
+
+        // Add Schema to the Class
+        assert_ok!(add_class_schema(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            BTreeSet::new(),
+            vec![property]
+        ));
+
+        let mut first_schema_property_values = BTreeMap::new();
+
+        let first_schema_property_value =
+            InputPropertyValue::<Runtime>::single_text(TextMaxLengthConstraint::get());
+
+        first_schema_property_values.insert(FIRST_PROPERTY_ID, first_schema_property_value);
+
+        // Add Entity Schema support to the first Entity (property unique on the Class level added)
+        assert_ok!(add_schema_support_to_entity(
+            LEAD_ORIGIN,
+            actor.to_owned(),
+            FIRST_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            first_schema_property_values.clone(),
+        ));
+
+        let mut second_schema_property_values = BTreeMap::new();
+
+        let second_schema_property_value =
+            InputPropertyValue::<Runtime>::single_text(TextMaxLengthConstraint::get() - 1);
+
+        second_schema_property_values.insert(FIRST_PROPERTY_ID, second_schema_property_value);
+
+        // Add Entity Schema support to the second Entity (property unique on the Class level added)
+        assert_ok!(add_schema_support_to_entity(
+            LEAD_ORIGIN,
+            actor.to_owned(),
+            SECOND_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            second_schema_property_values,
+        ));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to add schema support to the Entity, providing property values, which respective Class properties have
+        // unique flag set and same property values under same property_ids were already added to any Entity of this Class
+        let add_schema_support_to_entity_result = add_schema_support_to_entity(
+            LEAD_ORIGIN,
+            actor,
+            THIRD_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            first_schema_property_values,
+        );
+
+        // Failure checked
+        assert_failure(
+            add_schema_support_to_entity_result,
+            Error::<Runtime>::PropertyValueShouldBeUnique,
+            number_of_events_before_call,
+        );
+    })
+}

+ 557 - 0
runtime-modules/content-directory/src/tests/clear_entity_property_vector.rs

@@ -0,0 +1,557 @@
+use super::*;
+
+#[test]
+fn clear_entity_property_vector_success() {
+    with_test_externalities(|| {
+        let actor = Actor::Lead;
+
+        // Add entity schemas support
+        let (mut first_entity, mut second_entity) = add_entity_schemas_support();
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_calls = System::events().len();
+
+        // Clear property_vector under given `entity_id` & `in_class_schema_property_id`
+        assert_ok!(clear_entity_property_vector(
+            LEAD_ORIGIN,
+            actor.clone(),
+            FIRST_ENTITY_ID,
+            SECOND_PROPERTY_ID
+        ));
+
+        // Runtime tested state after call
+
+        // Ensure first entity properties updated succesfully
+        if let Some(second_schema_old_property_value) = first_entity
+            .get_values_mut()
+            .get_mut(&SECOND_PROPERTY_ID)
+            .and_then(|property_value| property_value.as_vec_property_value_mut())
+        {
+            second_schema_old_property_value.clear();
+        }
+
+        assert_eq!(first_entity, entity_by_id(FIRST_ENTITY_ID));
+
+        // Ensure reference counter of second entity updated succesfully
+        let inbound_rc = InboundReferenceCounter::new(0, true);
+        *second_entity.get_reference_counter_mut() = inbound_rc.clone();
+
+        assert_eq!(second_entity, entity_by_id(SECOND_ENTITY_ID));
+
+        // Create side-effect
+        let side_effect = EntityReferenceCounterSideEffect::new(-3, -3);
+        let mut side_effects = ReferenceCounterSideEffects::default();
+        side_effects.insert(SECOND_ENTITY_ID, side_effect);
+
+        let entity_property_vector_cleared_event = get_test_event(RawEvent::VectorCleared(
+            actor,
+            FIRST_ENTITY_ID,
+            SECOND_PROPERTY_ID,
+            Some(side_effects),
+        ));
+
+        // Last event checked
+        assert_event_success(
+            entity_property_vector_cleared_event,
+            number_of_events_before_calls + 1,
+        );
+    })
+}
+
+#[test]
+fn clear_entity_property_vector_entity_not_found() {
+    with_test_externalities(|| {
+        let actor = emulate_entity_access_state_for_failure_case(
+            EntityAccessStateFailureType::EntityNotFound,
+        );
+
+        // Create class reference schema
+        add_unique_class_reference_schema();
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to clear property_vector under given `entity_id` & `in_class_schema_property_id`
+        // in case when corresponding Entity does not exist
+        let clear_entity_property_vector_result =
+            clear_entity_property_vector(LEAD_ORIGIN, actor, FIRST_ENTITY_ID, FIRST_PROPERTY_ID);
+
+        // Failure checked
+        assert_failure(
+            clear_entity_property_vector_result,
+            Error::<Runtime>::EntityNotFound,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn clear_entity_property_vector_lead_auth_failed() {
+    with_test_externalities(|| {
+        let actor = emulate_entity_access_state_for_failure_case(
+            EntityAccessStateFailureType::LeadAuthFailed,
+        );
+
+        // Create class reference schema and add corresponding schema support to the Entity
+        add_unique_class_reference_schema_and_entity_schema_support(&actor, LEAD_ORIGIN);
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to clear property_vector under given `entity_id` & `in_class_schema_property_id`
+        // using unknown origin and lead actor
+        let clear_entity_property_vector_result = clear_entity_property_vector(
+            UNKNOWN_ORIGIN,
+            actor.clone(),
+            FIRST_ENTITY_ID,
+            FIRST_PROPERTY_ID,
+        );
+
+        // Failure checked
+        assert_failure(
+            clear_entity_property_vector_result,
+            Error::<Runtime>::LeadAuthFailed,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn clear_entity_property_vector_member_auth_failed() {
+    with_test_externalities(|| {
+        let actor = emulate_entity_access_state_for_failure_case(
+            EntityAccessStateFailureType::MemberAuthFailed,
+        );
+
+        // Create class reference schema and add corresponding schema support to the Entity
+        add_unique_class_reference_schema_and_entity_schema_support(&actor, FIRST_MEMBER_ORIGIN);
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to clear property_vector under given `entity_id` & `in_class_schema_property_id`
+        // using unknown origin an member actor
+        let clear_entity_property_vector_result = clear_entity_property_vector(
+            UNKNOWN_ORIGIN,
+            actor.clone(),
+            FIRST_ENTITY_ID,
+            FIRST_PROPERTY_ID,
+        );
+
+        // Failure checked
+        assert_failure(
+            clear_entity_property_vector_result,
+            Error::<Runtime>::MemberAuthFailed,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn clear_entity_property_vector_curator_auth_failed() {
+    with_test_externalities(|| {
+        let actor = emulate_entity_access_state_for_failure_case(
+            EntityAccessStateFailureType::CuratorAuthFailed,
+        );
+
+        // Create class reference schema and add corresponding schema support to the Entity
+        add_unique_class_reference_schema_and_entity_schema_support(&actor, FIRST_CURATOR_ORIGIN);
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to clear property_vector under given `entity_id` & `in_class_schema_property_id`
+        // using unknown origin and curator actor
+        let clear_entity_property_vector_result =
+            clear_entity_property_vector(UNKNOWN_ORIGIN, actor, FIRST_ENTITY_ID, FIRST_PROPERTY_ID);
+
+        // Failure checked
+        assert_failure(
+            clear_entity_property_vector_result,
+            Error::<Runtime>::CuratorAuthFailed,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn clear_entity_property_vector_curator_group_is_not_active() {
+    with_test_externalities(|| {
+        let actor = emulate_entity_access_state_for_failure_case(
+            EntityAccessStateFailureType::CuratorAuthFailed,
+        );
+
+        // Create class reference schema and add corresponding schema support to the Entity
+        add_unique_class_reference_schema_and_entity_schema_support(&actor, FIRST_CURATOR_ORIGIN);
+
+        // Make curator group inactive to block it from any entity operations
+        assert_ok!(set_curator_group_status(
+            LEAD_ORIGIN,
+            FIRST_CURATOR_GROUP_ID,
+            false
+        ));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to clear property_vector under given `entity_id` & `in_class_schema_property_id`
+        // using curator group, which is not active as actor
+        let clear_entity_property_vector_result = clear_entity_property_vector(
+            FIRST_CURATOR_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_PROPERTY_ID,
+        );
+
+        // Failure checked
+        assert_failure(
+            clear_entity_property_vector_result,
+            Error::<Runtime>::CuratorGroupIsNotActive,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn clear_entity_property_vector_curator_not_found_in_curator_group() {
+    with_test_externalities(|| {
+        let actor = emulate_entity_access_state_for_failure_case(
+            EntityAccessStateFailureType::CuratorNotFoundInCuratorGroup,
+        );
+
+        // Create class reference schema and add corresponding schema support to the Entity
+        add_unique_class_reference_schema_and_entity_schema_support(
+            &Actor::Curator(FIRST_CURATOR_GROUP_ID, FIRST_CURATOR_ID),
+            FIRST_CURATOR_ORIGIN,
+        );
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to clear property_vector under given `entity_id` & `in_class_schema_property_id`
+        // using actor in group, which curator id was not added to corresponding group set
+        let clear_entity_property_vector_result = clear_entity_property_vector(
+            SECOND_CURATOR_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_PROPERTY_ID,
+        );
+
+        // Failure checked
+        assert_failure(
+            clear_entity_property_vector_result,
+            Error::<Runtime>::CuratorIsNotAMemberOfGivenCuratorGroup,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn clear_entity_property_vector_entity_access_denied() {
+    with_test_externalities(|| {
+        let actor = emulate_entity_access_state_for_failure_case(
+            EntityAccessStateFailureType::EntityAccessDenied,
+        );
+
+        // Create class reference schema and add corresponding schema support to the Entity
+        add_unique_class_reference_schema_and_entity_schema_support(&Actor::Lead, LEAD_ORIGIN);
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to clear property_vector under given `entity_id` & `in_class_schema_property_id`
+        // using origin, which corresponding actor is neither entity maintainer, nor controller.
+        let clear_entity_property_vector_result = clear_entity_property_vector(
+            SECOND_CURATOR_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_PROPERTY_ID,
+        );
+
+        // Failure checked
+        assert_failure(
+            clear_entity_property_vector_result,
+            Error::<Runtime>::EntityAccessDenied,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn clear_entity_property_vector_values_locked_on_class_level() {
+    with_test_externalities(|| {
+        let actor = emulate_entity_access_state_for_failure_case(
+            EntityAccessStateFailureType::PropertyValuesLocked,
+        );
+
+        // Create class reference schema and add corresponding schema support to the Entity
+        add_unique_class_reference_schema_and_entity_schema_support(&Actor::Lead, LEAD_ORIGIN);
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to clear property_vector under given `entity_id` & `in_class_schema_property_id`
+        // in the case, when all property values were locked on Class level
+        let clear_entity_property_vector_result =
+            clear_entity_property_vector(LEAD_ORIGIN, actor, FIRST_ENTITY_ID, FIRST_PROPERTY_ID);
+
+        // Failure checked
+        assert_failure(
+            clear_entity_property_vector_result,
+            Error::<Runtime>::AllPropertiesWereLockedOnClassLevel,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn clear_entity_property_vector_class_property_not_found() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        let actor = Actor::Lead;
+
+        // Create entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to clear property_vector under given `entity_id` & `in_class_schema_property_id`
+        // in the case, when Property under corresponding PropertyId was not found on Class level
+        let clear_entity_property_vector_result =
+            clear_entity_property_vector(LEAD_ORIGIN, actor, FIRST_ENTITY_ID, FIRST_PROPERTY_ID);
+
+        // Failure checked
+        assert_failure(
+            clear_entity_property_vector_result,
+            Error::<Runtime>::ClassPropertyNotFound,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn clear_entity_property_vector_is_locked_for_given_actor() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Create property
+        let property_type = PropertyType::<Runtime>::vec_reference(FIRST_CLASS_ID, true, 5);
+
+        let mut property = Property::<Runtime>::with_name_and_type(
+            (PropertyNameLengthConstraint::get().max() - 1) as usize,
+            property_type,
+            true,
+            false,
+        );
+
+        property.locking_policy = PropertyLockingPolicy::new(false, true);
+
+        // Add Schema to the Class
+        assert_ok!(add_class_schema(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            BTreeSet::new(),
+            vec![property]
+        ));
+
+        let actor = Actor::Lead;
+
+        // Create entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        let schema_property_value =
+            InputPropertyValue::<Runtime>::vec_reference(vec![FIRST_ENTITY_ID, FIRST_ENTITY_ID]);
+
+        let mut schema_property_values = BTreeMap::new();
+        schema_property_values.insert(FIRST_PROPERTY_ID, schema_property_value);
+
+        // Add schema support to the entity
+        assert_ok!(add_schema_support_to_entity(
+            LEAD_ORIGIN,
+            actor.clone(),
+            FIRST_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            schema_property_values
+        ));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to clear property_vector under given `entity_id` & `in_class_schema_property_id` under lead origin,
+        // which is current Entity controller, in the case, when corresponding class Property was locked from controller on Class level
+        let clear_entity_property_vector_result =
+            clear_entity_property_vector(LEAD_ORIGIN, actor, FIRST_ENTITY_ID, FIRST_PROPERTY_ID);
+
+        // Failure checked
+        assert_failure(
+            clear_entity_property_vector_result,
+            Error::<Runtime>::ClassPropertyTypeLockedForGivenActor,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn clear_entity_property_vector_unknown_entity_property_id() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Create class reference schema
+        add_unique_class_reference_schema();
+
+        let actor = Actor::Lead;
+
+        // Create entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to clear property_vector under given `entity_id` & `in_class_schema_property_id`
+        // in the case, when property value was not added to current Entity values yet.
+        let clear_entity_property_vector_result =
+            clear_entity_property_vector(LEAD_ORIGIN, actor, FIRST_ENTITY_ID, FIRST_PROPERTY_ID);
+
+        // Failure checked
+        assert_failure(
+            clear_entity_property_vector_result,
+            Error::<Runtime>::UnknownEntityPropertyId,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn clear_entity_property_vector_value_under_given_index_is_not_a_vector() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Create property
+        let property = Property::<Runtime>::default_with_name(
+            PropertyNameLengthConstraint::get().max() as usize,
+        );
+
+        // Add Schema to the Class
+        assert_ok!(add_class_schema(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            BTreeSet::new(),
+            vec![property]
+        ));
+
+        let actor = Actor::Lead;
+
+        // Create entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        let mut schema_property_values = BTreeMap::new();
+        schema_property_values.insert(FIRST_PROPERTY_ID, InputPropertyValue::default());
+
+        // Add schema support to the entity
+        assert_ok!(add_schema_support_to_entity(
+            LEAD_ORIGIN,
+            actor.clone(),
+            FIRST_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            schema_property_values,
+        ));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to clear property_vector under given `entity_id` & `in_class_schema_property_id`
+        // in the case, when entity property value corresponding to a given in_class_schema_property_id is not a vector.
+        let clear_entity_property_vector_result =
+            clear_entity_property_vector(LEAD_ORIGIN, actor, FIRST_ENTITY_ID, FIRST_PROPERTY_ID);
+
+        // Failure checked
+        assert_failure(
+            clear_entity_property_vector_result,
+            Error::<Runtime>::PropertyValueUnderGivenIndexIsNotAVector,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn clear_entity_property_vector_property_should_be_unique() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        let actor = Actor::Lead;
+
+        // Create first Entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        // Create unique class reference schema and add corresponding schema support to the first Entity
+        add_unique_class_reference_schema_and_entity_schema_support(&actor, LEAD_ORIGIN);
+
+        // Create second Entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        // Create blank vec reference
+        let schema_property_value = InputPropertyValue::<Runtime>::vec_reference(vec![]);
+
+        let mut schema_property_values = BTreeMap::new();
+        schema_property_values.insert(FIRST_PROPERTY_ID, schema_property_value);
+
+        // Add schema support to the second Entity
+        assert_ok!(add_schema_support_to_entity(
+            LEAD_ORIGIN,
+            actor.to_owned(),
+            SECOND_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            schema_property_values
+        ));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to clear property_vector under given `entity_id` & `in_class_schema_property_id`
+        // in case, when the same blank required & unique property value vector already added to another Entity of this Class.
+        let clear_entity_property_vector_result =
+            clear_entity_property_vector(LEAD_ORIGIN, actor, FIRST_ENTITY_ID, FIRST_PROPERTY_ID);
+
+        // Failure checked
+        assert_failure(
+            clear_entity_property_vector_result,
+            Error::<Runtime>::PropertyValueShouldBeUnique,
+            number_of_events_before_call,
+        );
+    })
+}

+ 275 - 0
runtime-modules/content-directory/src/tests/create_class.rs

@@ -0,0 +1,275 @@
+use super::*;
+
+#[test]
+fn create_class_success() {
+    with_test_externalities(|| {
+        // Runtime tested state before call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        assert_eq!(next_class_id(), FIRST_CLASS_ID);
+        assert!(!class_exists(FIRST_CLASS_ID));
+
+        // Create simple class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Runtime tested state after call
+
+        // Ensure class under given if is equal to default one
+        let default_class = create_class_with_default_permissions();
+        assert_eq!(class_by_id(FIRST_CLASS_ID), default_class);
+
+        let class_created_event = get_test_event(RawEvent::ClassCreated(FIRST_CLASS_ID));
+
+        // Event checked
+        assert_event_success(class_created_event, number_of_events_before_call + 1);
+    })
+}
+
+#[test]
+fn create_class_lead_auth_failed() {
+    with_test_externalities(|| {
+        // Runtime tested state before call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // An attempt to create class with non lead origin
+        let create_class_result = create_simple_class(FIRST_MEMBER_ORIGIN, ClassType::Valid);
+
+        // Failure checked
+        assert_failure(
+            create_class_result,
+            Error::<Runtime>::LeadAuthFailed,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn create_class_limit_reached() {
+    with_test_externalities(|| {
+        // Runtime tested state before call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let mut number_of_classes_created = 0;
+
+        // Creating classes before limit reached
+        let create_class_result = loop {
+            let create_class_result = create_simple_class(LEAD_ORIGIN, ClassType::Valid);
+            if create_class_result.is_err() {
+                break create_class_result;
+            } else {
+                number_of_classes_created += 1;
+            }
+        };
+
+        // Ensure number of classes created is equal to MaxNumberOfClasses runtime constraint
+        assert_eq!(
+            number_of_classes_created,
+            MaxNumberOfClasses::get() as usize
+        );
+
+        // Failure checked
+        assert_failure(
+            create_class_result,
+            Error::<Runtime>::ClassLimitReached,
+            number_of_events_before_call + number_of_classes_created,
+        );
+    })
+}
+
+#[test]
+fn create_class_name_is_too_long() {
+    with_test_externalities(|| {
+        // Runtime tested state before call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // An attempt to create class with invalid name
+        let create_class_result = create_simple_class(LEAD_ORIGIN, ClassType::NameTooLong);
+
+        // Failure checked
+        assert_failure(
+            create_class_result,
+            Error::<Runtime>::ClassNameTooLong,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn create_class_name_is_too_short() {
+    with_test_externalities(|| {
+        // Runtime tested state before call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // An attempt to create class with invalid name
+        let create_class_result = create_simple_class(LEAD_ORIGIN, ClassType::NameTooShort);
+
+        // Failure checked
+        assert_failure(
+            create_class_result,
+            Error::<Runtime>::ClassNameTooShort,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn create_class_description_is_too_long() {
+    with_test_externalities(|| {
+        // Runtime tested state before call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // An attempt to create class with invalid description
+        let create_class_result = create_simple_class(LEAD_ORIGIN, ClassType::DescriptionTooLong);
+
+        // Failure checked
+        assert_failure(
+            create_class_result,
+            Error::<Runtime>::ClassDescriptionTooLong,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn create_class_description_is_too_short() {
+    with_test_externalities(|| {
+        // Runtime tested state before call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // An attempt to create class with invalid description
+        let create_class_result = create_simple_class(LEAD_ORIGIN, ClassType::DescriptionTooShort);
+
+        // Failure checked
+        assert_failure(
+            create_class_result,
+            Error::<Runtime>::ClassDescriptionTooShort,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn create_class_invalid_maximum_entities_count() {
+    with_test_externalities(|| {
+        // Runtime tested state before call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // An attempt to create class with invalid maximum entities count value
+        let create_class_result =
+            create_simple_class(LEAD_ORIGIN, ClassType::InvalidMaximumEntitiesCount);
+
+        // Failure checked
+        assert_failure(
+            create_class_result,
+            Error::<Runtime>::EntitiesNumberPerClassConstraintViolated,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn create_class_invalid_default_voucher_upper_bound() {
+    with_test_externalities(|| {
+        // Runtime tested state before call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // An attempt to create class with invalid default voucher upper bound value
+        let create_class_result =
+            create_simple_class(LEAD_ORIGIN, ClassType::InvalidDefaultVoucherUpperBound);
+
+        // Failure checked
+        assert_failure(
+            create_class_result,
+            Error::<Runtime>::NumberOfClassEntitiesPerActorConstraintViolated,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn create_class_per_controller_creation_limit_exceeds_overall_limit() {
+    with_test_externalities(|| {
+        // Runtime tested state before call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // An attempt to create class with per controller creation limit that exceeds overall entities creation limit
+        let create_class_result = create_simple_class(
+            LEAD_ORIGIN,
+            ClassType::DefaultVoucherUpperBoundExceedsMaximumEntitiesCount,
+        );
+
+        // Failure checked
+        assert_failure(
+            create_class_result,
+            Error::<Runtime>::PerControllerEntitiesCreationLimitExceedsOverallLimit,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn create_class_maintainers_limit_reached() {
+    with_test_externalities(|| {
+        // Runtime tested state before call
+
+        // Add curator groups to runtime
+        for _ in 1..=MaxNumberOfMaintainersPerClass::get() {
+            assert_ok!(add_curator_group(LEAD_ORIGIN));
+        }
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // An attempt to create class with number of maintainers that exceeds runtime limit
+        let create_class_result =
+            create_simple_class(LEAD_ORIGIN, ClassType::MaintainersLimitReached);
+
+        // Failure checked
+        assert_failure(
+            create_class_result,
+            Error::<Runtime>::ClassMaintainersLimitReached,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn create_class_curator_group_does_not_exist() {
+    with_test_externalities(|| {
+        // Runtime tested state before call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // An attempt to create class with curator group maintainer, which does not exist in runtime
+        let create_class_result =
+            create_simple_class(LEAD_ORIGIN, ClassType::CuratorGroupDoesNotExist);
+
+        // Failure checked
+        assert_failure(
+            create_class_result,
+            Error::<Runtime>::CuratorGroupDoesNotExist,
+            number_of_events_before_call,
+        );
+    })
+}

+ 446 - 0
runtime-modules/content-directory/src/tests/create_entity.rs

@@ -0,0 +1,446 @@
+use super::*;
+
+#[test]
+fn create_entity_success() {
+    with_test_externalities(|| {
+        // Create simple class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Update class permissions to force any member be available to create entities
+        assert_ok!(update_class_permissions(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            Some(true),
+            None,
+            None,
+            None
+        ));
+
+        // Runtime state before tested call
+
+        assert_eq!(next_entity_id(), FIRST_ENTITY_ID);
+        assert!(!entity_exists(FIRST_ENTITY_ID));
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let actor = Actor::Member(FIRST_MEMBER_ID);
+
+        // Create entity
+        assert_ok!(create_entity(
+            FIRST_MEMBER_ORIGIN,
+            FIRST_CLASS_ID,
+            actor.clone()
+        ));
+
+        // Runtime tested state after call
+
+        // Ensure Class `current_number_of_entities` value updated succesfully
+        let mut class = create_class_with_default_permissions();
+        class.get_permissions_mut().set_any_member_status(true);
+        class.increment_entities_count();
+
+        assert_eq!(class_by_id(FIRST_CLASS_ID), class);
+
+        // Ensure  entity creation voucher with `default_entity_creation_voucher_upper_bound` for given entity controller created succesfully.
+        let mut entity_voucher =
+            EntityCreationVoucher::new(class.get_default_entity_creation_voucher_upper_bound());
+        entity_voucher.increment_created_entities_count();
+
+        let entity_controller = EntityController::from_actor(&actor);
+
+        assert_eq!(
+            entity_creation_vouchers(FIRST_CLASS_ID, &entity_controller),
+            entity_voucher,
+        );
+
+        // Ensure new entity created
+        let entity = Entity::<Runtime>::new(
+            entity_controller,
+            FIRST_CLASS_ID,
+            BTreeSet::new(),
+            BTreeMap::new(),
+        );
+
+        assert_eq!(entity_by_id(FIRST_ENTITY_ID), entity);
+
+        // Ensure `NextEntityId` storage value updated
+        assert_eq!(next_entity_id(), SECOND_ENTITY_ID);
+
+        let entity_created_event =
+            get_test_event(RawEvent::EntityCreated(actor, next_entity_id() - 1));
+
+        // Last event checked
+        assert_event_success(entity_created_event, number_of_events_before_call + 1);
+    })
+}
+
+#[test]
+fn create_entity_of_non_existent_class() {
+    with_test_externalities(|| {
+        // Create simple class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let actor = Actor::Lead;
+
+        // Make an attempt to create Entity of non existent Class
+        let create_entity_result = create_entity(LEAD_ORIGIN, UNKNOWN_CLASS_ID, actor);
+
+        // Failure checked
+        assert_failure(
+            create_entity_result,
+            Error::<Runtime>::ClassNotFound,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn create_entity_creation_limit_reached() {
+    with_test_externalities(|| {
+        // Create simple class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Update class permissions to force any member be available to create entities
+        assert_ok!(update_class_permissions(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            Some(true),
+            None,
+            None,
+            None
+        ));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let mut number_of_entities_created = 0;
+
+        // Create entities of the same Class entities limit per class reached
+        let create_entity_result = loop {
+            let create_entity_result = create_entity(
+                number_of_entities_created,
+                FIRST_CLASS_ID,
+                Actor::Member(number_of_entities_created),
+            );
+            if create_entity_result.is_err() {
+                break create_entity_result;
+            } else {
+                number_of_entities_created += 1;
+            }
+        };
+
+        // Runtime tested state after call
+
+        // Ensure number of entities created is equal to MaxNumberOfEntitiesPerClass runtime constraint.
+        assert_eq!(
+            number_of_entities_created,
+            MaxNumberOfEntitiesPerClass::get()
+        );
+
+        // Failure checked
+        assert_failure(
+            create_entity_result,
+            Error::<Runtime>::NumberOfEntitiesPerClassLimitReached,
+            number_of_events_before_call + number_of_entities_created as usize,
+        );
+    })
+}
+
+#[test]
+fn create_entity_creation_blocked_on_class_level() {
+    with_test_externalities(|| {
+        // Create simple class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Update class permissions to block entity creation on class level
+        assert_ok!(update_class_permissions(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            None,
+            Some(true),
+            None,
+            None
+        ));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let actor = Actor::Lead;
+
+        // Make an attempt to create Entity, when entiti creation was previously blocked on class level
+        let create_entity_result = create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor);
+
+        // Runtime tested state after call
+
+        // Failure checked
+        assert_failure(
+            create_entity_result,
+            Error::<Runtime>::EntitiesCreationBlocked,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn create_entity_lead_auth_failed() {
+    with_test_externalities(|| {
+        // Create simple class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let actor = Actor::Lead;
+
+        // Make an attempt to authorize as Lead under non lead origin
+        let create_entity_result = create_entity(FIRST_MEMBER_ORIGIN, FIRST_CLASS_ID, actor);
+
+        // Failure checked
+        assert_failure(
+            create_entity_result,
+            Error::<Runtime>::LeadAuthFailed,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn create_entity_member_auth_failed() {
+    with_test_externalities(|| {
+        // Create simple class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Update class permissions to force any member be available to create entities
+        assert_ok!(update_class_permissions(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            Some(true),
+            None,
+            None,
+            None
+        ));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let actor = Actor::Member(UNKNOWN_MEMBER_ID);
+
+        // Make an attempt to authorize under non existent member id
+        let create_entity_result = create_entity(UNKNOWN_ORIGIN, FIRST_CLASS_ID, actor);
+
+        // Failure checked
+        assert_failure(
+            create_entity_result,
+            Error::<Runtime>::MemberAuthFailed,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn create_entity_actor_can_not_create_entities() {
+    with_test_externalities(|| {
+        // Create simple class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let actor = Actor::Member(UNKNOWN_MEMBER_ID);
+
+        // Make an attempt to create entity, authorizing as member in case, when members are not permitted to create entities on class level
+        let create_entity_result = create_entity(UNKNOWN_ORIGIN, FIRST_CLASS_ID, actor);
+
+        // Failure checked
+        assert_failure(
+            create_entity_result,
+            Error::<Runtime>::ActorCanNotCreateEntities,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn create_entity_unknown_curator_id() {
+    with_test_externalities(|| {
+        // Create simple class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Add curator group
+        assert_ok!(add_curator_group(LEAD_ORIGIN));
+
+        // Make curator group active
+        assert_ok!(set_curator_group_status(
+            LEAD_ORIGIN,
+            FIRST_CURATOR_GROUP_ID,
+            true
+        ));
+
+        // Add curator group as class maintainer
+        assert_ok!(add_maintainer_to_class(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            FIRST_CURATOR_GROUP_ID
+        ));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let actor = Actor::Curator(FIRST_CURATOR_GROUP_ID, UNKNOWN_CURATOR_ID);
+
+        // Make an attempt to create entity, authorizing as curator in group,
+        // in case, when provided curator id wasn`t added to respective curator group set
+        let create_entity_result = create_entity(FIRST_CURATOR_ORIGIN, FIRST_CLASS_ID, actor);
+
+        // Failure checked
+        assert_failure(
+            create_entity_result,
+            Error::<Runtime>::CuratorAuthFailed,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn create_entity_curator_group_is_not_active() {
+    with_test_externalities(|| {
+        // Create simple class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Add curator group
+        assert_ok!(add_curator_group(LEAD_ORIGIN));
+
+        // Add curator to group
+        assert_ok!(add_curator_to_group(
+            LEAD_ORIGIN,
+            FIRST_CURATOR_GROUP_ID,
+            FIRST_CURATOR_ID,
+        ));
+
+        // Add curator group as class maintainer
+        assert_ok!(add_maintainer_to_class(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            FIRST_CURATOR_GROUP_ID
+        ));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let actor = Actor::Curator(FIRST_CURATOR_GROUP_ID, FIRST_CURATOR_ID);
+
+        // Make an attempt to create entity, authorizing as curator in group, in case, when
+        // corresponding curator group is not active. (default status of curator group right after creation)
+        let create_entity_result = create_entity(FIRST_CURATOR_ORIGIN, FIRST_CLASS_ID, actor);
+
+        // Failure checked
+        assert_failure(
+            create_entity_result,
+            Error::<Runtime>::CuratorGroupIsNotActive,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn create_entity_curator_not_found_in_curator_group() {
+    with_test_externalities(|| {
+        // Create simple class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Add curator group
+        assert_ok!(add_curator_group(LEAD_ORIGIN));
+
+        // Make curator group active
+        assert_ok!(set_curator_group_status(
+            LEAD_ORIGIN,
+            FIRST_CURATOR_GROUP_ID,
+            true
+        ));
+
+        // Add curator group as class maintainer
+        assert_ok!(add_maintainer_to_class(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            FIRST_CURATOR_GROUP_ID
+        ));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let actor = Actor::Curator(FIRST_CURATOR_GROUP_ID, FIRST_CURATOR_ID);
+
+        // Make an attempt to create entity, authorizing as curator in group, in case, when
+        // curator was not added to corresponding curator group.
+        let create_entity_result = create_entity(FIRST_CURATOR_ORIGIN, FIRST_CLASS_ID, actor);
+
+        // Failure checked
+        assert_failure(
+            create_entity_result,
+            Error::<Runtime>::CuratorIsNotAMemberOfGivenCuratorGroup,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn create_entity_voucher_limit_reached() {
+    with_test_externalities(|| {
+        // Create simple class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let actor = Actor::Lead;
+
+        let mut number_of_entities_created = 0;
+
+        // Create entities until individual creation limit reached
+        let create_entity_result = loop {
+            let create_entity_result = create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone());
+            if create_entity_result.is_err() {
+                break create_entity_result;
+            } else {
+                number_of_entities_created += 1;
+            }
+        };
+
+        // Ensure number of entities created is equal to IndividualEntitiesCreationLimit runtime constraint.
+        assert_eq!(
+            number_of_entities_created,
+            IndividualEntitiesCreationLimit::get()
+        );
+
+        // Failure checked
+        assert_failure(
+            create_entity_result,
+            Error::<Runtime>::VoucherLimitReached,
+            number_of_events_before_call + number_of_entities_created as usize,
+        );
+    })
+}

+ 1271 - 0
runtime-modules/content-directory/src/tests/insert_at_entity_property_vector.rs

@@ -0,0 +1,1271 @@
+use super::*;
+
+#[test]
+fn insert_at_entity_property_vector_success() {
+    with_test_externalities(|| {
+        let actor = Actor::Lead;
+
+        // Add entity schemas support
+        let (mut first_entity, mut second_entity) = add_entity_schemas_support();
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_calls = System::events().len();
+
+        // Insert `InputValue` at given `index_in_property_vector`
+        // into `VecStoredPropertyValue` under `in_class_schema_property_id`
+        let nonce = 0;
+        let index_in_property_vector = 1;
+        let input_value = InputValue::Reference(SECOND_ENTITY_ID);
+
+        assert_ok!(insert_at_entity_property_vector(
+            LEAD_ORIGIN,
+            actor.clone(),
+            FIRST_ENTITY_ID,
+            SECOND_PROPERTY_ID,
+            index_in_property_vector,
+            input_value,
+            nonce
+        ));
+
+        // Runtime tested state after call
+
+        // Ensure first entity properties updated succesfully
+        if let Some(second_schema_old_property_value) = first_entity
+            .get_values_mut()
+            .get_mut(&SECOND_PROPERTY_ID)
+            .and_then(|property_value| property_value.as_vec_property_value_mut())
+        {
+            second_schema_old_property_value.insert_at(
+                index_in_property_vector,
+                StoredValue::Reference(SECOND_ENTITY_ID),
+            );
+        }
+
+        assert_eq!(first_entity, entity_by_id(FIRST_ENTITY_ID));
+
+        // Ensure reference counter of second entity updated succesfully
+        let inbound_rc = InboundReferenceCounter::new(4, true);
+        *second_entity.get_reference_counter_mut() = inbound_rc;
+
+        assert_eq!(second_entity, entity_by_id(SECOND_ENTITY_ID));
+
+        // Create side-effect
+        let side_effect = EntityReferenceCounterSideEffect::new(1, 1);
+
+        let inserted_at_vector_index_event = get_test_event(RawEvent::InsertedAtVectorIndex(
+            actor,
+            FIRST_ENTITY_ID,
+            SECOND_PROPERTY_ID,
+            index_in_property_vector,
+            nonce + 1,
+            Some((SECOND_ENTITY_ID, side_effect)),
+        ));
+
+        // Last event checked
+        assert_event_success(
+            inserted_at_vector_index_event,
+            number_of_events_before_calls + 1,
+        );
+    })
+}
+
+#[test]
+fn insert_at_entity_property_vector_entity_not_found() {
+    with_test_externalities(|| {
+        let actor = emulate_entity_access_state_for_failure_case(
+            EntityAccessStateFailureType::EntityNotFound,
+        );
+
+        // Create class reference schema
+        add_unique_class_reference_schema();
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let nonce = 0;
+        let index_in_property_vector = 1;
+        let input_value = InputValue::Reference(FIRST_ENTITY_ID);
+
+        // Make an attempt to insert value at given `index_in_property_vector`
+        // into `VecStoredPropertyValue` under `in_class_schema_property_id` in case when corresponding Entity does not exist
+        let insert_at_entity_property_vector_result = insert_at_entity_property_vector(
+            LEAD_ORIGIN,
+            actor.clone(),
+            FIRST_ENTITY_ID,
+            FIRST_PROPERTY_ID,
+            index_in_property_vector,
+            input_value,
+            nonce,
+        );
+
+        // Failure checked
+        assert_failure(
+            insert_at_entity_property_vector_result,
+            Error::<Runtime>::EntityNotFound,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn insert_at_entity_property_vector_lead_auth_failed() {
+    with_test_externalities(|| {
+        let actor = emulate_entity_access_state_for_failure_case(
+            EntityAccessStateFailureType::LeadAuthFailed,
+        );
+
+        // Create class reference schema and add corresponding schema support to the Entity
+        add_unique_class_reference_schema_and_entity_schema_support(&actor, LEAD_ORIGIN);
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let nonce = 0;
+        let index_in_property_vector = 1;
+        let input_value = InputValue::Reference(FIRST_ENTITY_ID);
+
+        // Make an attempt to insert value at given `index_in_property_vector`
+        // into `VecStoredPropertyValue` under `in_class_schema_property_id` using unknown origin and lead actor
+        let insert_at_entity_property_vector_result = insert_at_entity_property_vector(
+            UNKNOWN_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_PROPERTY_ID,
+            index_in_property_vector,
+            input_value,
+            nonce,
+        );
+
+        // Failure checked
+        assert_failure(
+            insert_at_entity_property_vector_result,
+            Error::<Runtime>::LeadAuthFailed,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn insert_at_entity_property_vector_member_auth_failed() {
+    with_test_externalities(|| {
+        let actor = emulate_entity_access_state_for_failure_case(
+            EntityAccessStateFailureType::MemberAuthFailed,
+        );
+
+        // Create class reference schema and add corresponding schema support to the Entity
+        add_unique_class_reference_schema_and_entity_schema_support(&actor, LEAD_ORIGIN);
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let nonce = 0;
+        let index_in_property_vector = 0;
+        let input_value = InputValue::Reference(FIRST_ENTITY_ID);
+
+        // Make an attempt to insert value at given `index_in_property_vector`
+        // into `VecStoredPropertyValue` under `in_class_schema_property_id` using unknown origin and member actor
+        let insert_at_entity_property_vector_result = insert_at_entity_property_vector(
+            UNKNOWN_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_PROPERTY_ID,
+            index_in_property_vector,
+            input_value,
+            nonce,
+        );
+
+        // Failure checked
+        assert_failure(
+            insert_at_entity_property_vector_result,
+            Error::<Runtime>::MemberAuthFailed,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn insert_at_entity_property_vector_curator_group_is_not_active() {
+    with_test_externalities(|| {
+        let actor = emulate_entity_access_state_for_failure_case(
+            EntityAccessStateFailureType::CuratorAuthFailed,
+        );
+
+        // Create class reference schema and add corresponding schema support to the Entity
+        add_unique_class_reference_schema_and_entity_schema_support(&actor, FIRST_CURATOR_ORIGIN);
+
+        // Make curator group inactive to block it from any entity operations
+        assert_ok!(set_curator_group_status(
+            LEAD_ORIGIN,
+            FIRST_CURATOR_GROUP_ID,
+            false
+        ));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let nonce = 0;
+        let index_in_property_vector = 0;
+        let input_value = InputValue::Reference(FIRST_ENTITY_ID);
+
+        // Make an attempt to insert value at given `index_in_property_vector`
+        // into `VecStoredPropertyValue` under `in_class_schema_property_id` using curator group, which is not active as actor
+        let insert_at_entity_property_vector_result = insert_at_entity_property_vector(
+            FIRST_CURATOR_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_PROPERTY_ID,
+            index_in_property_vector,
+            input_value,
+            nonce,
+        );
+
+        // Failure checked
+        assert_failure(
+            insert_at_entity_property_vector_result,
+            Error::<Runtime>::CuratorGroupIsNotActive,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn insert_at_entity_property_vector_curator_auth_failed() {
+    with_test_externalities(|| {
+        let actor = emulate_entity_access_state_for_failure_case(
+            EntityAccessStateFailureType::CuratorAuthFailed,
+        );
+
+        // Create class reference schema and add corresponding schema support to the Entity
+        add_unique_class_reference_schema_and_entity_schema_support(
+            &Actor::Curator(FIRST_CURATOR_GROUP_ID, FIRST_CURATOR_ID),
+            FIRST_CURATOR_ORIGIN,
+        );
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let nonce = 0;
+        let index_in_property_vector = 0;
+        let input_value = InputValue::Reference(FIRST_ENTITY_ID);
+
+        // Make an attempt to insert value at given `index_in_property_vector`
+        // into `VecStoredPropertyValue` under `in_class_schema_property_id` using unknown origin and curator actor
+        let insert_at_entity_property_vector_result = insert_at_entity_property_vector(
+            UNKNOWN_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_PROPERTY_ID,
+            index_in_property_vector,
+            input_value,
+            nonce,
+        );
+
+        // Failure checked
+        assert_failure(
+            insert_at_entity_property_vector_result,
+            Error::<Runtime>::CuratorAuthFailed,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn insert_at_entity_property_vector_curator_not_found_in_curator_group() {
+    with_test_externalities(|| {
+        let actor = emulate_entity_access_state_for_failure_case(
+            EntityAccessStateFailureType::CuratorNotFoundInCuratorGroup,
+        );
+
+        // Create class reference schema and add corresponding schema support to the Entity
+        add_unique_class_reference_schema_and_entity_schema_support(
+            &Actor::Curator(FIRST_CURATOR_GROUP_ID, FIRST_CURATOR_ID),
+            FIRST_CURATOR_ORIGIN,
+        );
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let nonce = 0;
+        let index_in_property_vector = 0;
+        let input_value = InputValue::Reference(FIRST_ENTITY_ID);
+
+        // Make an attempt to insert value at given `index_in_property_vector`
+        // into `VecStoredPropertyValue` under `in_class_schema_property_id`,
+        // using actor in group, which curator id was not added to corresponding group set
+        let insert_at_entity_property_vector_result = insert_at_entity_property_vector(
+            SECOND_CURATOR_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_PROPERTY_ID,
+            index_in_property_vector,
+            input_value,
+            nonce,
+        );
+
+        // Failure checked
+        assert_failure(
+            insert_at_entity_property_vector_result,
+            Error::<Runtime>::CuratorIsNotAMemberOfGivenCuratorGroup,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn insert_at_entity_property_vector_access_denied() {
+    with_test_externalities(|| {
+        let actor = emulate_entity_access_state_for_failure_case(
+            EntityAccessStateFailureType::EntityAccessDenied,
+        );
+
+        // Create class reference schema and add corresponding schema support to the Entity
+        add_unique_class_reference_schema_and_entity_schema_support(&Actor::Lead, LEAD_ORIGIN);
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let nonce = 0;
+        let index_in_property_vector = 0;
+        let input_value = InputValue::Reference(FIRST_ENTITY_ID);
+
+        // Make an attempt to insert value at given `index_in_property_vector`
+        // into `VecStoredPropertyValue` under `in_class_schema_property_id`,
+        // using origin, which corresponding actor is neither entity maintainer, nor controller.
+        let insert_at_entity_property_vector_result = insert_at_entity_property_vector(
+            SECOND_CURATOR_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_PROPERTY_ID,
+            index_in_property_vector,
+            input_value,
+            nonce,
+        );
+
+        // Failure checked
+        assert_failure(
+            insert_at_entity_property_vector_result,
+            Error::<Runtime>::EntityAccessDenied,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn insert_at_entity_property_vector_values_locked_on_class_level() {
+    with_test_externalities(|| {
+        let actor = emulate_entity_access_state_for_failure_case(
+            EntityAccessStateFailureType::PropertyValuesLocked,
+        );
+
+        // Create class reference schema and add corresponding schema support to the Entity
+        add_unique_class_reference_schema_and_entity_schema_support(&Actor::Lead, LEAD_ORIGIN);
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let nonce = 0;
+        let index_in_property_vector = 0;
+        let input_value = InputValue::Reference(FIRST_ENTITY_ID);
+
+        // Make an attempt to insert value at given `index_in_property_vector`
+        // into `VecStoredPropertyValue` under `in_class_schema_property_id`,
+        // in the case, when all property values were locked on Class level.
+        let insert_at_entity_property_vector_result = insert_at_entity_property_vector(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_PROPERTY_ID,
+            index_in_property_vector,
+            input_value,
+            nonce,
+        );
+
+        // Failure checked
+        assert_failure(
+            insert_at_entity_property_vector_result,
+            Error::<Runtime>::AllPropertiesWereLockedOnClassLevel,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn insert_at_entity_property_vector_class_property_not_found() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        let actor = Actor::Lead;
+
+        // Create entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let nonce = 0;
+        let index_in_property_vector = 0;
+        let input_value = InputValue::Reference(FIRST_ENTITY_ID);
+
+        // Make an attempt to insert value at given `index_in_property_vector`
+        // into `VecStoredPropertyValue` under `in_class_schema_property_id`,
+        // in the case, when Property under corresponding PropertyId was not found on Class level.
+        let insert_at_entity_property_vector_result = insert_at_entity_property_vector(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_PROPERTY_ID,
+            index_in_property_vector,
+            input_value,
+            nonce,
+        );
+
+        // Failure checked
+        assert_failure(
+            insert_at_entity_property_vector_result,
+            Error::<Runtime>::ClassPropertyNotFound,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn insert_at_entity_property_vector_is_locked_for_given_actor() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Create property
+        let property_type = PropertyType::<Runtime>::vec_reference(FIRST_CLASS_ID, true, 5);
+
+        let mut property = Property::<Runtime>::with_name_and_type(
+            (PropertyNameLengthConstraint::get().max() - 1) as usize,
+            property_type,
+            true,
+            false,
+        );
+
+        property.locking_policy = PropertyLockingPolicy::new(false, true);
+
+        // Add Schema to the Class
+        assert_ok!(add_class_schema(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            BTreeSet::new(),
+            vec![property]
+        ));
+
+        let actor = Actor::Lead;
+
+        // Create entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        let schema_property_value =
+            InputPropertyValue::<Runtime>::vec_reference(vec![FIRST_ENTITY_ID, FIRST_ENTITY_ID]);
+
+        let mut schema_property_values = BTreeMap::new();
+        schema_property_values.insert(FIRST_PROPERTY_ID, schema_property_value);
+
+        // Add schema support to the entity
+        assert_ok!(add_schema_support_to_entity(
+            LEAD_ORIGIN,
+            actor.clone(),
+            FIRST_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            schema_property_values
+        ));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let nonce = 0;
+        let index_in_property_vector = 0;
+        let input_value = InputValue::Reference(FIRST_ENTITY_ID);
+
+        // Make an attempt to remove value at given `index_in_property_vector`
+        // from `VecStoredPropertyValue` under `in_class_schema_property_id`,
+        // under lead origin, which is current Entity controller, in the case,
+        // when corresponding class Property was locked from controller on Class level
+        let insert_at_entity_property_vector_result = insert_at_entity_property_vector(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_PROPERTY_ID,
+            index_in_property_vector,
+            input_value,
+            nonce,
+        );
+
+        // Failure checked
+        assert_failure(
+            insert_at_entity_property_vector_result,
+            Error::<Runtime>::ClassPropertyTypeLockedForGivenActor,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn insert_at_entity_property_vector_unknown_entity_property_id() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Create class reference schema
+        add_unique_class_reference_schema();
+
+        let actor = Actor::Lead;
+
+        // Create entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let nonce = 0;
+        let index_in_property_vector = 0;
+        let input_value = InputValue::Reference(FIRST_ENTITY_ID);
+
+        // Make an attempt to insert value at given `index_in_property_vector`
+        // into `VecStoredPropertyValue` under `in_class_schema_property_id`,
+        // in the case, when property value was not added to current Entity values yet.
+        let insert_at_entity_property_vector_result = insert_at_entity_property_vector(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_PROPERTY_ID,
+            index_in_property_vector,
+            input_value,
+            nonce,
+        );
+
+        // Failure checked
+        assert_failure(
+            insert_at_entity_property_vector_result,
+            Error::<Runtime>::UnknownEntityPropertyId,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn insert_at_entity_property_vector_value_under_given_index_is_not_a_vector() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Create property
+        let property = Property::<Runtime>::default_with_name(
+            PropertyNameLengthConstraint::get().max() as usize,
+        );
+
+        // Add Schema to the Class
+        assert_ok!(add_class_schema(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            BTreeSet::new(),
+            vec![property]
+        ));
+
+        let actor = Actor::Lead;
+
+        // Create entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        let mut schema_property_values = BTreeMap::new();
+        schema_property_values.insert(FIRST_PROPERTY_ID, InputPropertyValue::default());
+
+        // Add schema support to the entity
+        assert_ok!(add_schema_support_to_entity(
+            LEAD_ORIGIN,
+            actor.clone(),
+            FIRST_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            schema_property_values,
+        ));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let nonce = 0;
+        let index_in_property_vector = 0;
+        let input_value = InputValue::Reference(FIRST_ENTITY_ID);
+
+        // Make an attempt to insert value at given `index_in_property_vector`
+        // into `VecStoredPropertyValue` under `in_class_schema_property_id`,
+        // in the case, when entity property value corresponding to a given in_class_schema_property_id is not a vector.
+        let insert_at_entity_property_vector_result = insert_at_entity_property_vector(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_PROPERTY_ID,
+            index_in_property_vector,
+            input_value,
+            nonce,
+        );
+
+        // Failure checked
+        assert_failure(
+            insert_at_entity_property_vector_result,
+            Error::<Runtime>::PropertyValueUnderGivenIndexIsNotAVector,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn insert_at_entity_property_vector_nonces_does_not_match() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        let actor = Actor::Lead;
+
+        // Create entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        // Create class reference schema and add corresponding schema support to the Entity
+        add_unique_class_reference_schema_and_entity_schema_support(&actor, LEAD_ORIGIN);
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let nonce = 1;
+        let index_in_property_vector = 0;
+        let input_value = InputValue::Reference(FIRST_ENTITY_ID);
+
+        // Make an attempt to insert value at given `index_in_property_vector`
+        // into `VecStoredPropertyValue` under `in_class_schema_property_id`,
+        // providing nonce that does not corresponding property value vector one.
+        let insert_at_entity_property_vector_result = insert_at_entity_property_vector(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_PROPERTY_ID,
+            index_in_property_vector,
+            input_value,
+            nonce,
+        );
+
+        // Failure checked
+        assert_failure(
+            insert_at_entity_property_vector_result,
+            Error::<Runtime>::PropertyValueVecNoncesDoesNotMatch,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn insert_at_entity_property_vector_index_is_out_of_range() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        let actor = Actor::Lead;
+
+        // Create entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        // Create class reference schema
+        add_unique_class_reference_schema();
+
+        let entity_ids = vec![FIRST_ENTITY_ID, FIRST_ENTITY_ID];
+        let schema_property_value =
+            InputPropertyValue::<Runtime>::vec_reference(entity_ids.clone());
+
+        let mut schema_property_values = BTreeMap::new();
+        schema_property_values.insert(FIRST_PROPERTY_ID, schema_property_value);
+
+        // Add schema support to the entity
+        assert_ok!(add_schema_support_to_entity(
+            LEAD_ORIGIN,
+            actor.to_owned(),
+            FIRST_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            schema_property_values
+        ));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to insert value at given `index_in_property_vector`
+        // into `VecStoredPropertyValue` under `in_class_schema_property_id` in case,
+        // when provided index_in_property_vector is out of range of the related vector
+        let nonce = 0;
+        let index_in_property_vector = entity_ids.len() as u16 + 1;
+        let input_value = InputValue::Reference(FIRST_ENTITY_ID);
+
+        let insert_at_entity_property_vector_result = insert_at_entity_property_vector(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_PROPERTY_ID,
+            index_in_property_vector,
+            input_value,
+            nonce,
+        );
+
+        // Failure checked
+        assert_failure(
+            insert_at_entity_property_vector_result,
+            Error::<Runtime>::EntityPropertyValueVectorIndexIsOutOfRange,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn insert_at_entity_property_vector_is_too_long() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        let actor = Actor::Lead;
+
+        // Create entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        // Create class reference schema
+        add_unique_class_reference_schema();
+
+        let entity_ids = vec![FIRST_ENTITY_ID; VecMaxLengthConstraint::get() as usize];
+        let schema_property_value =
+            InputPropertyValue::<Runtime>::vec_reference(entity_ids.clone());
+
+        let mut schema_property_values = BTreeMap::new();
+        schema_property_values.insert(FIRST_PROPERTY_ID, schema_property_value);
+
+        // Add schema support to the entity
+        assert_ok!(add_schema_support_to_entity(
+            LEAD_ORIGIN,
+            actor.to_owned(),
+            FIRST_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            schema_property_values
+        ));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to insert value at given `index_in_property_vector`
+        // into `VecStoredPropertyValue` under `in_class_schema_property_id` in case,
+        // when corresponding property_vector can not contain more values
+        let nonce = 0;
+        let index_in_property_vector = 1;
+        let input_value = InputValue::Reference(FIRST_ENTITY_ID);
+
+        let insert_at_entity_property_vector_result = insert_at_entity_property_vector(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_PROPERTY_ID,
+            index_in_property_vector,
+            input_value,
+            nonce,
+        );
+
+        // Failure checked
+        assert_failure(
+            insert_at_entity_property_vector_result,
+            Error::<Runtime>::EntityPropertyValueVectorIsTooLong,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn insert_at_entity_property_vector_text_prop_is_too_long() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        let actor = Actor::Lead;
+
+        // Create entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        // Create property
+        let property_type = PropertyType::<Runtime>::vec_text(
+            TextMaxLengthConstraint::get(),
+            VecMaxLengthConstraint::get(),
+        );
+
+        let property = Property::<Runtime>::with_name_and_type(
+            (PropertyNameLengthConstraint::get().max() - 1) as usize,
+            property_type,
+            true,
+            false,
+        );
+
+        // Add Schema to the Class
+        assert_ok!(add_class_schema(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            BTreeSet::new(),
+            vec![property]
+        ));
+
+        let schema_property_value = InputPropertyValue::<Runtime>::vec_text(vec![]);
+
+        let mut schema_property_values = BTreeMap::new();
+        schema_property_values.insert(FIRST_PROPERTY_ID, schema_property_value);
+
+        // Add schema support to the entity
+        assert_ok!(add_schema_support_to_entity(
+            LEAD_ORIGIN,
+            actor.to_owned(),
+            FIRST_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            schema_property_values
+        ));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to insert value at given `index_in_property_vector`
+        // into `VecStoredPropertyValue` under `in_class_schema_property_id` in case,
+        // when corresponding property text value is too long
+        let nonce = 0;
+        let index_in_property_vector = 0;
+        let input_value =
+            InputValue::Text(generate_text(TextMaxLengthConstraint::get() as usize + 1));
+
+        let insert_at_entity_property_vector_result = insert_at_entity_property_vector(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_PROPERTY_ID,
+            index_in_property_vector,
+            input_value,
+            nonce,
+        );
+
+        // Failure checked
+        assert_failure(
+            insert_at_entity_property_vector_result,
+            Error::<Runtime>::TextPropertyTooLong,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn insert_at_entity_property_vector_hashed_text_prop_is_too_long() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        let actor = Actor::Lead;
+
+        // Create entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        let hashed_text_max_length_constraint = HashedTextMaxLengthConstraint::get();
+
+        // Create vec text hash property
+        let property_type = PropertyType::<Runtime>::vec_text_hash(
+            hashed_text_max_length_constraint,
+            VecMaxLengthConstraint::get(),
+        );
+
+        let property = Property::<Runtime>::with_name_and_type(
+            PropertyNameLengthConstraint::get().max() as usize,
+            property_type,
+            true,
+            false,
+        );
+
+        // Add Schema to the Class
+        assert_ok!(add_class_schema(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            BTreeSet::new(),
+            vec![property]
+        ));
+
+        let schema_property_value = InputPropertyValue::<Runtime>::vec_text_to_hash(vec![]);
+
+        let mut schema_property_values = BTreeMap::new();
+        schema_property_values.insert(FIRST_PROPERTY_ID, schema_property_value);
+
+        // Add schema support to the entity
+        assert_ok!(add_schema_support_to_entity(
+            LEAD_ORIGIN,
+            actor.to_owned(),
+            FIRST_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            schema_property_values
+        ));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to insert value at given `index_in_property_vector`
+        // into `VecStoredPropertyValue` under `in_class_schema_property_id` in case,
+        // when corresponding property text to hash value is too long
+        let nonce = 0;
+        let index_in_property_vector = 0;
+        let input_value = InputValue::TextToHash(generate_text(
+            hashed_text_max_length_constraint.unwrap() as usize + 1,
+        ));
+
+        let insert_at_entity_property_vector_result = insert_at_entity_property_vector(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_PROPERTY_ID,
+            index_in_property_vector,
+            input_value,
+            nonce,
+        );
+
+        // Failure checked
+        assert_failure(
+            insert_at_entity_property_vector_result,
+            Error::<Runtime>::HashedTextPropertyTooLong,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn insert_at_entity_property_vector_prop_type_does_not_match_internal_vec_property() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        let actor = Actor::Lead;
+
+        // Create entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        // Create property
+        let property_type = PropertyType::<Runtime>::vec_text(
+            TextMaxLengthConstraint::get(),
+            VecMaxLengthConstraint::get(),
+        );
+
+        let property = Property::<Runtime>::with_name_and_type(
+            (PropertyNameLengthConstraint::get().max() - 1) as usize,
+            property_type,
+            true,
+            false,
+        );
+
+        // Add Schema to the Class
+        assert_ok!(add_class_schema(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            BTreeSet::new(),
+            vec![property]
+        ));
+
+        let schema_property_value = InputPropertyValue::<Runtime>::vec_text(vec![]);
+
+        let mut schema_property_values = BTreeMap::new();
+        schema_property_values.insert(FIRST_PROPERTY_ID, schema_property_value);
+
+        // Add schema support to the entity
+        assert_ok!(add_schema_support_to_entity(
+            LEAD_ORIGIN,
+            actor.to_owned(),
+            FIRST_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            schema_property_values
+        ));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to insert value at given `index_in_property_vector`
+        // into `VecStoredPropertyValue` under `in_class_schema_property_id` in case,
+        // when corresponding property type does not match internal vector property type
+        let nonce = 0;
+        let index_in_property_vector = 0;
+        let input_value = InputValue::default();
+
+        let insert_at_entity_property_vector_result = insert_at_entity_property_vector(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_PROPERTY_ID,
+            index_in_property_vector,
+            input_value,
+            nonce,
+        );
+
+        // Failure checked
+        assert_failure(
+            insert_at_entity_property_vector_result,
+            Error::<Runtime>::PropertyValueTypeDoesNotMatchInternalVectorType,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn insert_at_entity_property_vector_referenced_entity_not_found() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        let actor = Actor::Lead;
+
+        // Create entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        // Create class reference schema and add corresponding schema support to the Entity
+        add_unique_class_reference_schema_and_entity_schema_support(&actor, LEAD_ORIGIN);
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to insert value at given `index_in_property_vector`
+        // into `VecStoredPropertyValue` under `in_class_schema_property_id` in case,
+        // when corresponding input_value referes to unknown Entity
+        let nonce = 0;
+        let index_in_property_vector = 0;
+        let input_value = InputValue::Reference(SECOND_ENTITY_ID);
+
+        let insert_at_entity_property_vector_result = insert_at_entity_property_vector(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_PROPERTY_ID,
+            index_in_property_vector,
+            input_value,
+            nonce,
+        );
+
+        // Failure checked
+        assert_failure(
+            insert_at_entity_property_vector_result,
+            Error::<Runtime>::EntityNotFound,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn insert_at_entity_property_vector_entity_can_not_be_referenced() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        let actor = Actor::Lead;
+
+        // Create first Entity of first Class
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        // Create class reference schema and add corresponding schema support to the first Entity
+        add_unique_class_reference_schema_and_entity_schema_support(&actor, LEAD_ORIGIN);
+
+        // Create second Entity of first Class
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        // Update second entity permissions to forbid it from being referencable
+        assert_ok!(update_entity_permissions(
+            LEAD_ORIGIN,
+            SECOND_ENTITY_ID,
+            None,
+            Some(false)
+        ));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to insert value at given `index_in_property_vector`
+        // into `VecStoredPropertyValue` under `in_class_schema_property_id` in case,
+        // when corresponding Entity can not be referenced
+        let nonce = 0;
+        let index_in_property_vector = 0;
+        let input_value = InputValue::Reference(SECOND_ENTITY_ID);
+
+        let insert_at_entity_property_vector_result = insert_at_entity_property_vector(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_PROPERTY_ID,
+            index_in_property_vector,
+            input_value,
+            nonce,
+        );
+
+        // Failure checked
+        assert_failure(
+            insert_at_entity_property_vector_result,
+            Error::<Runtime>::EntityCanNotBeReferenced,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn insert_at_entity_property_vector_same_controller_constraint_violation() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Update class permissions to force any member be available to create Entities
+        assert_ok!(update_class_permissions(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            Some(true),
+            None,
+            None,
+            None
+        ));
+
+        let actor = Actor::Lead;
+
+        // Create first Entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        // Create class reference schema and add corresponding schema support to the first  Entity
+        add_unique_class_reference_schema_and_entity_schema_support(&actor, LEAD_ORIGIN);
+
+        // Create second Entity
+        assert_ok!(create_entity(
+            FIRST_MEMBER_ORIGIN,
+            FIRST_CLASS_ID,
+            Actor::Member(FIRST_MEMBER_ID)
+        ));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to insert value at given `index_in_property_vector`
+        // into `VecStoredPropertyValue` under `in_class_schema_property_id` in case,
+        // when corresponding Entity can only be referenced from Entity with the same controller.
+        let nonce = 0;
+        let index_in_property_vector = 0;
+        let input_value = InputValue::Reference(SECOND_ENTITY_ID);
+
+        let insert_at_entity_property_vector_result = insert_at_entity_property_vector(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_PROPERTY_ID,
+            index_in_property_vector,
+            input_value,
+            nonce,
+        );
+
+        // Failure checked
+        assert_failure(
+            insert_at_entity_property_vector_result,
+            Error::<Runtime>::SameControllerConstraintViolation,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn insert_at_entity_property_vector_property_should_be_unique() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        let actor = Actor::Lead;
+
+        // Create first Entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        // Create unique class reference schema and add corresponding schema support to the first Entity
+        add_unique_class_reference_schema_and_entity_schema_support(&actor, LEAD_ORIGIN);
+
+        // Create second Entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        // Create vec reference
+        let schema_property_value =
+            InputPropertyValue::<Runtime>::vec_reference(vec![FIRST_ENTITY_ID]);
+
+        let mut schema_property_values = BTreeMap::new();
+        schema_property_values.insert(FIRST_PROPERTY_ID, schema_property_value);
+
+        // Add schema support to the second Entity
+        assert_ok!(add_schema_support_to_entity(
+            LEAD_ORIGIN,
+            actor.to_owned(),
+            SECOND_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            schema_property_values
+        ));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to insert value at given `index_in_property_vector`
+        // into `VecStoredPropertyValue` under `in_class_schema_property_id` in case,
+        // when in result we`ll get required & unique property value vector,
+        // which is already added to another Entity of this Class.
+        let nonce = 0;
+        let index_in_property_vector = 0;
+        let input_value = InputValue::Reference(FIRST_ENTITY_ID);
+
+        let insert_at_entity_property_vector_result = insert_at_entity_property_vector(
+            LEAD_ORIGIN,
+            actor,
+            SECOND_ENTITY_ID,
+            FIRST_PROPERTY_ID,
+            index_in_property_vector,
+            input_value,
+            nonce,
+        );
+
+        // Failure checked
+        assert_failure(
+            insert_at_entity_property_vector_result,
+            Error::<Runtime>::PropertyValueShouldBeUnique,
+            number_of_events_before_call,
+        );
+    })
+}

+ 776 - 0
runtime-modules/content-directory/src/tests/remove_at_entity_property_vector.rs

@@ -0,0 +1,776 @@
+use super::*;
+
+#[test]
+fn remove_at_entity_property_vector_success() {
+    with_test_externalities(|| {
+        let actor = Actor::Lead;
+
+        // Add entity schemas support
+        let (mut first_entity, mut second_entity) = add_entity_schemas_support();
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_calls = System::events().len();
+
+        // Remove value at given `index_in_property_vector`
+        // from `PropertyValueVec` under in_class_schema_property_id
+        let nonce = 0;
+        let index_in_property_vector = 0;
+
+        assert_ok!(remove_at_entity_property_vector(
+            LEAD_ORIGIN,
+            actor.clone(),
+            FIRST_ENTITY_ID,
+            SECOND_PROPERTY_ID,
+            index_in_property_vector,
+            nonce
+        ));
+
+        // Runtime tested state after call
+
+        // Ensure first entity properties updated succesfully
+        if let Some(second_schema_old_property_value) = first_entity
+            .get_values_mut()
+            .get_mut(&SECOND_PROPERTY_ID)
+            .and_then(|property_value| property_value.as_vec_property_value_mut())
+        {
+            second_schema_old_property_value.remove_at(index_in_property_vector);
+        }
+
+        assert_eq!(first_entity, entity_by_id(FIRST_ENTITY_ID));
+
+        // Ensure reference counter of second entity updated succesfully
+        let inbound_rc = InboundReferenceCounter::new(2, true);
+        *second_entity.get_reference_counter_mut() = inbound_rc;
+
+        assert_eq!(second_entity, entity_by_id(SECOND_ENTITY_ID));
+
+        // Create side-effect
+        let side_effect = EntityReferenceCounterSideEffect::new(-1, -1);
+
+        let removed_at_vector_index_event = get_test_event(RawEvent::RemovedAtVectorIndex(
+            actor,
+            FIRST_ENTITY_ID,
+            SECOND_PROPERTY_ID,
+            index_in_property_vector,
+            nonce + 1,
+            Some((SECOND_ENTITY_ID, side_effect)),
+        ));
+
+        // Last event checked
+        assert_event_success(
+            removed_at_vector_index_event,
+            number_of_events_before_calls + 1,
+        );
+    })
+}
+
+#[test]
+fn remove_at_entity_property_vector_entity_not_found() {
+    with_test_externalities(|| {
+        let actor = emulate_entity_access_state_for_failure_case(
+            EntityAccessStateFailureType::EntityNotFound,
+        );
+
+        // Create class reference schema
+        add_unique_class_reference_schema();
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let nonce = 0;
+        let index_in_property_vector = 0;
+
+        // Make an attempt to remove value at given `index_in_property_vector`
+        // from `PropertyValueVec` under `in_class_schema_property_id` in case when corresponding Entity does not exist
+        let remove_at_entity_property_vector_result = remove_at_entity_property_vector(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_PROPERTY_ID,
+            index_in_property_vector,
+            nonce,
+        );
+
+        // Failure checked
+        assert_failure(
+            remove_at_entity_property_vector_result,
+            Error::<Runtime>::EntityNotFound,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn remove_at_entity_property_vector_lead_auth_failed() {
+    with_test_externalities(|| {
+        let actor = emulate_entity_access_state_for_failure_case(
+            EntityAccessStateFailureType::LeadAuthFailed,
+        );
+
+        // Create class reference schema and add corresponding schema support to the Entity
+        add_unique_class_reference_schema_and_entity_schema_support(&actor, LEAD_ORIGIN);
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let nonce = 0;
+        let index_in_property_vector = 0;
+
+        // Make an attempt to remove value at given `index_in_property_vector`
+        // from `PropertyValueVec` under `in_class_schema_property_id` using unknown origin and lead actor
+        let remove_at_entity_property_vector_result = remove_at_entity_property_vector(
+            UNKNOWN_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_PROPERTY_ID,
+            index_in_property_vector,
+            nonce,
+        );
+
+        // Failure checked
+        assert_failure(
+            remove_at_entity_property_vector_result,
+            Error::<Runtime>::LeadAuthFailed,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn remove_at_entity_property_vector_member_auth_failed() {
+    with_test_externalities(|| {
+        let actor = emulate_entity_access_state_for_failure_case(
+            EntityAccessStateFailureType::MemberAuthFailed,
+        );
+
+        // Create class reference schema and add corresponding schema support to the Entity
+        add_unique_class_reference_schema_and_entity_schema_support(&actor, LEAD_ORIGIN);
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let nonce = 0;
+        let index_in_property_vector = 0;
+
+        // Make an attempt to remove value at given `index_in_property_vector`
+        // from `PropertyValueVec` under `in_class_schema_property_id` using unknown origin and member actor
+        let remove_at_entity_property_vector_result = remove_at_entity_property_vector(
+            UNKNOWN_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_PROPERTY_ID,
+            index_in_property_vector,
+            nonce,
+        );
+
+        // Failure checked
+        assert_failure(
+            remove_at_entity_property_vector_result,
+            Error::<Runtime>::MemberAuthFailed,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn remove_at_entity_property_vector_curator_group_is_not_active() {
+    with_test_externalities(|| {
+        let actor = emulate_entity_access_state_for_failure_case(
+            EntityAccessStateFailureType::CuratorAuthFailed,
+        );
+
+        // Create class reference schema and add corresponding schema support to the Entity
+        add_unique_class_reference_schema_and_entity_schema_support(&actor, FIRST_CURATOR_ORIGIN);
+
+        // Make curator group inactive to block it from any entity operations
+        assert_ok!(set_curator_group_status(
+            LEAD_ORIGIN,
+            FIRST_CURATOR_GROUP_ID,
+            false
+        ));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let nonce = 0;
+        let index_in_property_vector = 0;
+
+        // Make an attempt to remove value at given `index_in_property_vector`
+        // from `PropertyValueVec` under `in_class_schema_property_id` using curator group, which is not active as actor
+        let remove_at_entity_property_vector_result = remove_at_entity_property_vector(
+            FIRST_CURATOR_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_PROPERTY_ID,
+            index_in_property_vector,
+            nonce,
+        );
+
+        // Failure checked
+        assert_failure(
+            remove_at_entity_property_vector_result,
+            Error::<Runtime>::CuratorGroupIsNotActive,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn remove_at_entity_property_vector_curator_auth_failed() {
+    with_test_externalities(|| {
+        let actor = emulate_entity_access_state_for_failure_case(
+            EntityAccessStateFailureType::CuratorAuthFailed,
+        );
+
+        // Create class reference schema and add corresponding schema support to the Entity
+        add_unique_class_reference_schema_and_entity_schema_support(&actor, FIRST_CURATOR_ORIGIN);
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let nonce = 0;
+        let index_in_property_vector = 0;
+
+        // Make an attempt to remove value at given `index_in_property_vector`
+        // from `PropertyValueVec` under `in_class_schema_property_id` using unknown origin and curator actor
+        let remove_at_entity_property_vector_result = remove_at_entity_property_vector(
+            UNKNOWN_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_PROPERTY_ID,
+            index_in_property_vector,
+            nonce,
+        );
+
+        // Failure checked
+        assert_failure(
+            remove_at_entity_property_vector_result,
+            Error::<Runtime>::CuratorAuthFailed,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn remove_at_entity_property_vector_curator_not_found_in_curator_group() {
+    with_test_externalities(|| {
+        let actor = emulate_entity_access_state_for_failure_case(
+            EntityAccessStateFailureType::CuratorNotFoundInCuratorGroup,
+        );
+
+        // Create class reference schema and add corresponding schema support to the Entity
+        add_unique_class_reference_schema_and_entity_schema_support(
+            &Actor::Curator(FIRST_CURATOR_GROUP_ID, FIRST_CURATOR_ID),
+            FIRST_CURATOR_ORIGIN,
+        );
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let nonce = 0;
+        let index_in_property_vector = 0;
+
+        // Make an attempt to remove value at given `index_in_property_vector`
+        // from `PropertyValueVec` under `in_class_schema_property_id`,
+        // using actor in group, which curator id was not added to corresponding group set
+        let remove_at_entity_property_vector_result = remove_at_entity_property_vector(
+            SECOND_CURATOR_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_PROPERTY_ID,
+            index_in_property_vector,
+            nonce,
+        );
+
+        // Failure checked
+        assert_failure(
+            remove_at_entity_property_vector_result,
+            Error::<Runtime>::CuratorIsNotAMemberOfGivenCuratorGroup,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn remove_at_entity_property_vector_access_denied() {
+    with_test_externalities(|| {
+        let actor = emulate_entity_access_state_for_failure_case(
+            EntityAccessStateFailureType::EntityAccessDenied,
+        );
+
+        // Create class reference schema and add corresponding schema support to the Entity
+        add_unique_class_reference_schema_and_entity_schema_support(&Actor::Lead, LEAD_ORIGIN);
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let nonce = 0;
+        let index_in_property_vector = 0;
+
+        // Make an attempt to remove value at given `index_in_property_vector`
+        // from `PropertyValueVec` under `in_class_schema_property_id`,
+        // using origin, which corresponding actor is neither entity maintainer, nor controller.
+        let remove_at_entity_property_vector_result = remove_at_entity_property_vector(
+            SECOND_CURATOR_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_PROPERTY_ID,
+            index_in_property_vector,
+            nonce,
+        );
+
+        // Failure checked
+        assert_failure(
+            remove_at_entity_property_vector_result,
+            Error::<Runtime>::EntityAccessDenied,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn remove_at_entity_property_vector_values_locked_on_class_level() {
+    with_test_externalities(|| {
+        let actor = emulate_entity_access_state_for_failure_case(
+            EntityAccessStateFailureType::PropertyValuesLocked,
+        );
+
+        // Create class reference schema and add corresponding schema support to the Entity
+        add_unique_class_reference_schema_and_entity_schema_support(&Actor::Lead, LEAD_ORIGIN);
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let nonce = 0;
+        let index_in_property_vector = 0;
+
+        // Make an attempt to remove value at given `index_in_property_vector`
+        // from `PropertyValueVec` under `in_class_schema_property_id`,
+        // in the case, when all property values were locked on Class level.
+        let remove_at_entity_property_vector_result = remove_at_entity_property_vector(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_PROPERTY_ID,
+            index_in_property_vector,
+            nonce,
+        );
+
+        // Failure checked
+        assert_failure(
+            remove_at_entity_property_vector_result,
+            Error::<Runtime>::AllPropertiesWereLockedOnClassLevel,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn remove_at_entity_property_vector_class_property_not_found() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        let actor = Actor::Lead;
+
+        // Create entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let nonce = 0;
+        let index_in_property_vector = 0;
+
+        // Make an attempt to remove value at given `index_in_property_vector`
+        // from `PropertyValueVec` under `in_class_schema_property_id`,
+        // in the case, when Property under corresponding PropertyId was not found on Class level.
+        let remove_at_entity_property_vector_result = remove_at_entity_property_vector(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_PROPERTY_ID,
+            index_in_property_vector,
+            nonce,
+        );
+
+        // Failure checked
+        assert_failure(
+            remove_at_entity_property_vector_result,
+            Error::<Runtime>::ClassPropertyNotFound,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn remove_at_entity_property_vector_is_locked_for_given_actor() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Create property
+        let property_type = PropertyType::<Runtime>::vec_reference(FIRST_CLASS_ID, true, 5);
+
+        let mut property = Property::<Runtime>::with_name_and_type(
+            (PropertyNameLengthConstraint::get().max() - 1) as usize,
+            property_type,
+            true,
+            false,
+        );
+
+        property.locking_policy = PropertyLockingPolicy::new(false, true);
+
+        // Add Schema to the Class
+        assert_ok!(add_class_schema(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            BTreeSet::new(),
+            vec![property]
+        ));
+
+        let actor = Actor::Lead;
+
+        // Create entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        let schema_property_value =
+            InputPropertyValue::<Runtime>::vec_reference(vec![FIRST_ENTITY_ID, FIRST_ENTITY_ID]);
+
+        let mut schema_property_values = BTreeMap::new();
+        schema_property_values.insert(FIRST_PROPERTY_ID, schema_property_value);
+
+        // Add schema support to the entity
+        assert_ok!(add_schema_support_to_entity(
+            LEAD_ORIGIN,
+            actor.clone(),
+            FIRST_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            schema_property_values
+        ));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let nonce = 0;
+        let index_in_property_vector = 0;
+
+        // Make an attempt to remove value at given `index_in_property_vector`
+        // from `PropertyValueVec` under `in_class_schema_property_id`,
+        // under lead origin, which is current Entity controller, in the case,
+        // when corresponding class Property was locked from controller on Class level
+        let remove_at_entity_property_vector_result = remove_at_entity_property_vector(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_PROPERTY_ID,
+            index_in_property_vector,
+            nonce,
+        );
+
+        // Failure checked
+        assert_failure(
+            remove_at_entity_property_vector_result,
+            Error::<Runtime>::ClassPropertyTypeLockedForGivenActor,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn remove_at_entity_property_vector_unknown_entity_property_id() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Create class reference schema
+        add_unique_class_reference_schema();
+
+        let actor = Actor::Lead;
+
+        // Create entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let nonce = 0;
+        let index_in_property_vector = 0;
+
+        // Make an attempt to remove value at given `index_in_property_vector`
+        // from `PropertyValueVec` under `in_class_schema_property_id`,
+        // in the case, when property value was not added to current Entity values yet.
+        let remove_at_entity_property_vector_result = remove_at_entity_property_vector(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_PROPERTY_ID,
+            index_in_property_vector,
+            nonce,
+        );
+
+        // Failure checked
+        assert_failure(
+            remove_at_entity_property_vector_result,
+            Error::<Runtime>::UnknownEntityPropertyId,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn remove_at_entity_property_vector_value_under_given_index_is_not_a_vector() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Create property
+        let property = Property::<Runtime>::default_with_name(
+            PropertyNameLengthConstraint::get().max() as usize,
+        );
+
+        // Add Schema to the Class
+        assert_ok!(add_class_schema(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            BTreeSet::new(),
+            vec![property]
+        ));
+
+        let actor = Actor::Lead;
+
+        // Create entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        let mut schema_property_values = BTreeMap::new();
+        schema_property_values.insert(FIRST_PROPERTY_ID, InputPropertyValue::default());
+
+        // Add schema support to the entity
+        assert_ok!(add_schema_support_to_entity(
+            LEAD_ORIGIN,
+            actor.clone(),
+            FIRST_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            schema_property_values,
+        ));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let nonce = 0;
+        let index_in_property_vector = 0;
+
+        // Make an attempt to remove value at given `index_in_property_vector`
+        // from `PropertyValueVec` under `in_class_schema_property_id`,
+        // in the case, when entity property value corresponding to a given in_class_schema_property_id is not a vector.
+        let remove_at_entity_property_vector_result = remove_at_entity_property_vector(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_PROPERTY_ID,
+            index_in_property_vector,
+            nonce,
+        );
+
+        // Failure checked
+        assert_failure(
+            remove_at_entity_property_vector_result,
+            Error::<Runtime>::PropertyValueUnderGivenIndexIsNotAVector,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn remove_at_entity_property_vector_nonces_does_not_match() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        let actor = Actor::Lead;
+
+        // Create entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        // Create class reference schema and add corresponding schema support to the Entity
+        add_unique_class_reference_schema_and_entity_schema_support(&Actor::Lead, LEAD_ORIGIN);
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let nonce = 1;
+        let index_in_property_vector = 0;
+
+        // Make an attempt to remove value at given `index_in_property_vector`
+        // from `PropertyValueVec` under `in_class_schema_property_id`,
+        // providing nonce that does not corresponding property value vector one.
+        let remove_at_entity_property_vector_result = remove_at_entity_property_vector(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_PROPERTY_ID,
+            index_in_property_vector,
+            nonce,
+        );
+
+        // Failure checked
+        assert_failure(
+            remove_at_entity_property_vector_result,
+            Error::<Runtime>::PropertyValueVecNoncesDoesNotMatch,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn remove_at_entity_property_vector_index_is_out_of_range() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        let actor = Actor::Lead;
+
+        // Create entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        // Create class reference schema
+        add_unique_class_reference_schema();
+
+        let entity_ids = vec![FIRST_ENTITY_ID, FIRST_ENTITY_ID];
+        let schema_property_value =
+            InputPropertyValue::<Runtime>::vec_reference(entity_ids.clone());
+
+        let mut schema_property_values = BTreeMap::new();
+        schema_property_values.insert(FIRST_PROPERTY_ID, schema_property_value);
+
+        // Add schema support to the entity
+        assert_ok!(add_schema_support_to_entity(
+            LEAD_ORIGIN,
+            actor.to_owned(),
+            FIRST_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            schema_property_values
+        ));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to remove value at given `index_in_property_vector`
+        // from `PropertyValueVec` under `in_class_schema_property_id` in case,
+        // when provided index_in_property_vector is out of range of the related vector
+        let nonce = 0;
+        let index_in_property_vector = entity_ids.len() as u16 + 1;
+
+        let remove_at_entity_property_vector_result = remove_at_entity_property_vector(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_PROPERTY_ID,
+            index_in_property_vector,
+            nonce,
+        );
+
+        // Failure checked
+        assert_failure(
+            remove_at_entity_property_vector_result,
+            Error::<Runtime>::EntityPropertyValueVectorIndexIsOutOfRange,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn remove_at_entity_property_vector_property_should_be_unique() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        let actor = Actor::Lead;
+
+        // Create first Entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        // Create unique class reference schema and add corresponding schema support to the first Entity
+        add_unique_class_reference_schema_and_entity_schema_support(&actor, LEAD_ORIGIN);
+
+        // Create second Entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        // Create vec reference
+        let schema_property_value = InputPropertyValue::<Runtime>::vec_reference(vec![
+            FIRST_ENTITY_ID,
+            FIRST_ENTITY_ID,
+            FIRST_ENTITY_ID,
+        ]);
+
+        let mut schema_property_values = BTreeMap::new();
+        schema_property_values.insert(FIRST_PROPERTY_ID, schema_property_value);
+
+        // Add schema support to the second Entity
+        assert_ok!(add_schema_support_to_entity(
+            LEAD_ORIGIN,
+            actor.to_owned(),
+            SECOND_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            schema_property_values
+        ));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let nonce = 0;
+        let index_in_property_vector = 0;
+
+        // Make an attempt to remove value at given `index_in_property_vector`
+        // from `PropertyValueVec` under `in_class_schema_property_id` in case,
+        // when in result we`ll get required & unique property value vector,
+        // which is already added to another Entity of this Class.
+        let remove_at_entity_property_vector_result = remove_at_entity_property_vector(
+            LEAD_ORIGIN,
+            actor,
+            SECOND_ENTITY_ID,
+            FIRST_PROPERTY_ID,
+            index_in_property_vector,
+            nonce,
+        );
+
+        // Failure checked
+        assert_failure(
+            remove_at_entity_property_vector_result,
+            Error::<Runtime>::PropertyValueShouldBeUnique,
+            number_of_events_before_call,
+        );
+    })
+}

+ 131 - 0
runtime-modules/content-directory/src/tests/remove_curator_from_group.rs

@@ -0,0 +1,131 @@
+use super::*;
+
+#[test]
+fn remove_curator_from_group_success() {
+    with_test_externalities(|| {
+        // Add curator group
+        assert_ok!(add_curator_group(LEAD_ORIGIN));
+
+        // Add first curator to group
+        assert_ok!(add_curator_to_group(
+            LEAD_ORIGIN,
+            FIRST_CURATOR_GROUP_ID,
+            FIRST_CURATOR_ID
+        ));
+        // Add second curator to group
+        assert_ok!(add_curator_to_group(
+            LEAD_ORIGIN,
+            FIRST_CURATOR_GROUP_ID,
+            SECOND_CURATOR_ID
+        ));
+
+        // Runtime tested state before call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Remove first curator from group
+        assert_ok!(remove_curator_from_group(
+            LEAD_ORIGIN,
+            FIRST_CURATOR_GROUP_ID,
+            FIRST_CURATOR_ID
+        ));
+
+        // Runtime tested state after call
+
+        // Ensure group contains only second curator
+        let mut curator_group = CuratorGroup::default();
+        curator_group.get_curators_mut().insert(SECOND_CURATOR_ID);
+        assert_eq!(curator_group_by_id(FIRST_CURATOR_GROUP_ID), curator_group);
+
+        let curator_group_curator_removed_event = get_test_event(RawEvent::CuratorRemoved(
+            FIRST_CURATOR_GROUP_ID,
+            FIRST_CURATOR_ID,
+        ));
+
+        // Event checked
+        assert_event_success(
+            curator_group_curator_removed_event,
+            number_of_events_before_call + 1,
+        );
+    })
+}
+
+#[test]
+fn remove_curator_from_group_lead_auth_failed() {
+    with_test_externalities(|| {
+        // Add curator group
+        assert_ok!(add_curator_group(LEAD_ORIGIN));
+
+        // Add curator to group
+        assert_ok!(add_curator_to_group(
+            LEAD_ORIGIN,
+            FIRST_CURATOR_GROUP_ID,
+            FIRST_CURATOR_ID
+        ));
+
+        // Runtime tested state before call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to remove curator from group using non lead origin
+        let remove_curator_from_group_result = remove_curator_from_group(
+            FIRST_MEMBER_ORIGIN,
+            FIRST_CURATOR_GROUP_ID,
+            FIRST_CURATOR_ID,
+        );
+
+        // Failure checked
+        assert_failure(
+            remove_curator_from_group_result,
+            Error::<Runtime>::LeadAuthFailed,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn remove_curator_from_group_curator_is_not_a_member() {
+    with_test_externalities(|| {
+        // Add curator group
+        assert_ok!(add_curator_group(LEAD_ORIGIN));
+
+        // Runtime tested state before call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to remove curator that does not added to the provided curator group
+        let remove_curator_from_group_result =
+            remove_curator_from_group(LEAD_ORIGIN, FIRST_CURATOR_GROUP_ID, UNKNOWN_CURATOR_ID);
+
+        // Failure checked
+        assert_failure(
+            remove_curator_from_group_result,
+            Error::<Runtime>::CuratorIsNotAMemberOfGivenCuratorGroup,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn remove_curator_from_non_existent_group() {
+    with_test_externalities(|| {
+        // Runtime tested state before call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to remove curator from group using non lead origin
+        let remove_curator_from_group_result =
+            remove_curator_from_group(LEAD_ORIGIN, UNKNOWN_CURATOR_GROUP_ID, FIRST_CURATOR_ID);
+
+        // Failure checked
+        assert_failure(
+            remove_curator_from_group_result,
+            Error::<Runtime>::CuratorGroupDoesNotExist,
+            number_of_events_before_call,
+        );
+    })
+}

+ 110 - 0
runtime-modules/content-directory/src/tests/remove_curator_group.rs

@@ -0,0 +1,110 @@
+use super::*;
+
+#[test]
+fn remove_curator_group_success() {
+    with_test_externalities(|| {
+        // Add curator group
+        assert_ok!(add_curator_group(LEAD_ORIGIN));
+
+        // Runtime tested state before call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        assert_ok!(remove_curator_group(LEAD_ORIGIN, FIRST_CURATOR_GROUP_ID));
+
+        // Runtime tested state after call
+
+        assert_eq!(next_curator_group_id(), SECOND_CURATOR_GROUP_ID);
+
+        // Ensure curator group removed
+        assert!(!curator_group_exists(FIRST_CURATOR_GROUP_ID));
+
+        let curator_group_removed_event =
+            get_test_event(RawEvent::CuratorGroupRemoved(FIRST_CURATOR_GROUP_ID));
+
+        // Event checked
+        assert_event_success(
+            curator_group_removed_event,
+            number_of_events_before_call + 1,
+        );
+    })
+}
+
+#[test]
+fn remove_curator_group_lead_auth_failed() {
+    with_test_externalities(|| {
+        // Runtime tested state before call
+
+        // Add curator group
+        assert_ok!(add_curator_group(LEAD_ORIGIN));
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // An attemt to remove curator group from non lead origin
+        let remove_curator_group_result =
+            remove_curator_group(FIRST_MEMBER_ORIGIN, FIRST_CURATOR_GROUP_ID);
+
+        // Failure checked
+        assert_failure(
+            remove_curator_group_result,
+            Error::<Runtime>::LeadAuthFailed,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn remove_non_existent_curator_group() {
+    with_test_externalities(|| {
+        // Runtime tested state before call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // An attemt to remove non existent curator group
+        let remove_curator_group_result =
+            remove_curator_group(LEAD_ORIGIN, UNKNOWN_CURATOR_GROUP_ID);
+
+        // Failure checked
+        assert_failure(
+            remove_curator_group_result,
+            Error::<Runtime>::CuratorGroupDoesNotExist,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn curator_group_removal_forbidden() {
+    with_test_externalities(|| {
+        // Runtime tested state before call
+
+        // Create class
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Add curator group
+        assert_ok!(add_curator_group(LEAD_ORIGIN));
+
+        // Add curator group as class maintainer
+        assert_ok!(add_maintainer_to_class(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            FIRST_CURATOR_GROUP_ID
+        ));
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // An attemt to remove curator group, that have classes maintained
+        let remove_curator_group_result = remove_curator_group(LEAD_ORIGIN, FIRST_CURATOR_ID);
+
+        // Failure checked
+        assert_failure(
+            remove_curator_group_result,
+            Error::<Runtime>::CuratorGroupRemovalForbidden,
+            number_of_events_before_call,
+        );
+    })
+}

+ 296 - 0
runtime-modules/content-directory/src/tests/remove_entity.rs

@@ -0,0 +1,296 @@
+use super::*;
+
+#[test]
+fn remove_entity_success() {
+    with_test_externalities(|| {
+        // Create simple class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        let actor = Actor::Lead;
+
+        // Create entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Remove entity
+        assert_ok!(remove_entity(LEAD_ORIGIN, actor.clone(), FIRST_ENTITY_ID));
+
+        // Runtime tested state after call
+
+        // Ensure entity under corresponding id was succesfully removed from runtime storage
+        assert!(!entity_exists(FIRST_ENTITY_ID));
+
+        // Ensure number of entities_created under respective entity creation voucher decremented succesfully.
+        let entity_voucher = EntityCreationVoucher::new(IndividualEntitiesCreationLimit::get());
+
+        let entity_controller = EntityController::from_actor(&actor);
+
+        assert_eq!(
+            entity_creation_vouchers(FIRST_CLASS_ID, &entity_controller),
+            entity_voucher,
+        );
+
+        let entity_removed_event =
+            get_test_event(RawEvent::EntityRemoved(actor, next_entity_id() - 1));
+
+        // Last event checked
+        assert_event_success(entity_removed_event, number_of_events_before_call + 1);
+    })
+}
+
+#[test]
+fn remove_non_existent_entity() {
+    with_test_externalities(|| {
+        let actor = emulate_entity_access_state_for_failure_case(
+            EntityAccessStateFailureType::EntityNotFound,
+        );
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to remove Entity, which does not exist in runtime
+        let remove_entity_result = remove_entity(LEAD_ORIGIN, actor, UNKNOWN_ENTITY_ID);
+
+        // Failure checked
+        assert_failure(
+            remove_entity_result,
+            Error::<Runtime>::EntityNotFound,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn remove_entity_lead_auth_failed() {
+    with_test_externalities(|| {
+        let actor = emulate_entity_access_state_for_failure_case(
+            EntityAccessStateFailureType::LeadAuthFailed,
+        );
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to remove entity under non lead origin
+        let remove_entity_result = remove_entity(UNKNOWN_ORIGIN, actor, FIRST_ENTITY_ID);
+
+        // Failure checked
+        assert_failure(
+            remove_entity_result,
+            Error::<Runtime>::LeadAuthFailed,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn remove_entity_member_auth_failed() {
+    with_test_externalities(|| {
+        let actor = emulate_entity_access_state_for_failure_case(
+            EntityAccessStateFailureType::MemberAuthFailed,
+        );
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to remove entity using unknown origin and member actor, which is current Entity controller
+        let remove_entity_result = remove_entity(UNKNOWN_ORIGIN, actor, FIRST_ENTITY_ID);
+
+        // Failure checked
+        assert_failure(
+            remove_entity_result,
+            Error::<Runtime>::MemberAuthFailed,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn create_entity_curator_group_is_not_active() {
+    with_test_externalities(|| {
+        let actor = emulate_entity_access_state_for_failure_case(
+            EntityAccessStateFailureType::CuratorAuthFailed,
+        );
+
+        // Make curator group inactive to block it from any entity operations
+        assert_ok!(set_curator_group_status(
+            LEAD_ORIGIN,
+            FIRST_CURATOR_GROUP_ID,
+            false
+        ));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to remove entity using curator group, which is not active as actor
+        let remove_entity_result = remove_entity(FIRST_CURATOR_ORIGIN, actor, FIRST_ENTITY_ID);
+
+        // Failure checked
+        assert_failure(
+            remove_entity_result,
+            Error::<Runtime>::CuratorGroupIsNotActive,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn remove_entity_curator_auth_failed() {
+    with_test_externalities(|| {
+        let actor = emulate_entity_access_state_for_failure_case(
+            EntityAccessStateFailureType::CuratorAuthFailed,
+        );
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to remove entity under unknown origin and curator actor, which corresponding group is current entity controller
+        let remove_entity_result = remove_entity(UNKNOWN_ORIGIN, actor, FIRST_ENTITY_ID);
+
+        // Failure checked
+        assert_failure(
+            remove_entity_result,
+            Error::<Runtime>::CuratorAuthFailed,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn remove_entity_curator_not_found_in_curator_group() {
+    with_test_externalities(|| {
+        let actor = emulate_entity_access_state_for_failure_case(
+            EntityAccessStateFailureType::CuratorNotFoundInCuratorGroup,
+        );
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to remove entity, using actor in group,
+        // which curator id was not added to corresponding group set
+        let remove_entity_result = remove_entity(SECOND_CURATOR_ORIGIN, actor, FIRST_ENTITY_ID);
+
+        // Failure checked
+        assert_failure(
+            remove_entity_result,
+            Error::<Runtime>::CuratorIsNotAMemberOfGivenCuratorGroup,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn remove_entity_access_denied() {
+    with_test_externalities(|| {
+        let actor = emulate_entity_access_state_for_failure_case(
+            EntityAccessStateFailureType::EntityAccessDenied,
+        );
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to remove entity, using origin, which corresponding actor is neither entity maintainer, nor controller.
+        let remove_entity_result = remove_entity(SECOND_MEMBER_ORIGIN, actor, FIRST_ENTITY_ID);
+
+        // Failure checked
+        assert_failure(
+            remove_entity_result,
+            Error::<Runtime>::EntityAccessDenied,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn remove_entity_removal_access_denied() {
+    with_test_externalities(|| {
+        // Create simple class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Add curator group
+        assert_ok!(add_curator_group(LEAD_ORIGIN));
+
+        // Add curator to group
+        assert_ok!(add_curator_to_group(
+            LEAD_ORIGIN,
+            FIRST_CURATOR_GROUP_ID,
+            FIRST_CURATOR_ID,
+        ));
+
+        // Make curator group active
+        assert_ok!(set_curator_group_status(
+            LEAD_ORIGIN,
+            FIRST_CURATOR_GROUP_ID,
+            true
+        ));
+
+        // Add curator group as class maintainer
+        assert_ok!(add_maintainer_to_class(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            FIRST_CURATOR_GROUP_ID
+        ));
+
+        // Runtime state before tested call
+
+        let actor = Actor::Curator(FIRST_CURATOR_GROUP_ID, FIRST_CURATOR_ID);
+
+        // Create entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, Actor::Lead));
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to remove entity, using origin, which corresponding actor is not an entity controller
+        let remove_entity_result = remove_entity(FIRST_CURATOR_ORIGIN, actor, FIRST_ENTITY_ID);
+
+        // Failure checked
+        assert_failure(
+            remove_entity_result,
+            Error::<Runtime>::EntityRemovalAccessDenied,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn remove_entity_rc_does_not_equal_to_zero() {
+    with_test_externalities(|| {
+        let actor = Actor::Lead;
+
+        // Create class, two corresponding entities and force first entity have schema support with property value referencing second entity
+        add_entity_schemas_support();
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to remove entity, which rc does not equal to zero
+        let remove_entity_result = remove_entity(LEAD_ORIGIN, actor, SECOND_ENTITY_ID);
+
+        // Failure checked
+        assert_failure(
+            remove_entity_result,
+            Error::<Runtime>::EntityRcDoesNotEqualToZero,
+            number_of_events_before_call,
+        );
+    })
+}

+ 147 - 0
runtime-modules/content-directory/src/tests/remove_maintainer_from_class.rs

@@ -0,0 +1,147 @@
+use super::*;
+
+#[test]
+fn remove_maintainer_from_class_success() {
+    with_test_externalities(|| {
+        // Create simple class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Add first curator group
+        assert_ok!(add_curator_group(LEAD_ORIGIN));
+
+        // Add second curator group
+        assert_ok!(add_curator_group(LEAD_ORIGIN));
+
+        // Add first maintainer to class
+        assert_ok!(add_maintainer_to_class(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            FIRST_CURATOR_GROUP_ID
+        ));
+
+        // Add second maintainer to class
+        assert_ok!(add_maintainer_to_class(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            SECOND_CURATOR_GROUP_ID
+        ));
+
+        // Runtime tested state before call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Remove first maintainer from class
+        assert_ok!(remove_maintainer_from_class(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            FIRST_CURATOR_GROUP_ID
+        ));
+
+        // Runtime tested state after call
+
+        // Ensure curator_group removed from class maintainers set
+        let mut class = create_class_with_default_permissions();
+        class
+            .get_permissions_mut()
+            .get_maintainers_mut()
+            .insert(SECOND_CURATOR_GROUP_ID);
+        assert_eq!(class_by_id(FIRST_CLASS_ID), class);
+
+        let maintainer_removed_event = get_test_event(RawEvent::MaintainerRemoved(
+            FIRST_CLASS_ID,
+            FIRST_CURATOR_GROUP_ID,
+        ));
+
+        // Event checked
+        assert_event_success(maintainer_removed_event, number_of_events_before_call + 1);
+    })
+}
+
+#[test]
+fn remove_maintainer_from_class_lead_auth_failed() {
+    with_test_externalities(|| {
+        // Create simple class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Add curator group
+        assert_ok!(add_curator_group(LEAD_ORIGIN));
+
+        // Add maintainer to class
+        assert_ok!(add_maintainer_to_class(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            FIRST_CURATOR_GROUP_ID
+        ));
+
+        // Runtime tested state before call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to remove maintainer from class under non lead origin
+        let remove_maintainer_from_class_result = remove_maintainer_from_class(
+            FIRST_MEMBER_ORIGIN,
+            FIRST_CLASS_ID,
+            FIRST_CURATOR_GROUP_ID,
+        );
+
+        // Failure checked
+        assert_failure(
+            remove_maintainer_from_class_result,
+            Error::<Runtime>::LeadAuthFailed,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn remove_maintainer_from_non_existent_class() {
+    with_test_externalities(|| {
+        // Add curator group
+        assert_ok!(add_curator_group(LEAD_ORIGIN));
+
+        // Runtime tested state before call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to remove maintainer from non existent class
+        let remove_maintainer_from_class_result =
+            remove_maintainer_from_class(LEAD_ORIGIN, UNKNOWN_CLASS_ID, FIRST_CURATOR_GROUP_ID);
+
+        // Failure checked
+        assert_failure(
+            remove_maintainer_from_class_result,
+            Error::<Runtime>::ClassNotFound,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn remove_maintainer_that_was_not_added_to_class_maintainers_set() {
+    with_test_externalities(|| {
+        // Create simple class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Add curator group
+        assert_ok!(add_curator_group(LEAD_ORIGIN));
+
+        // Runtime tested state before call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to remove curator group maintainer, that was not added to corresponding class maintainers set yet
+        let remove_maintainer_from_class_result =
+            remove_maintainer_from_class(LEAD_ORIGIN, FIRST_CLASS_ID, UNKNOWN_CURATOR_GROUP_ID);
+
+        // Failure checked
+        assert_failure(
+            remove_maintainer_from_class_result,
+            Error::<Runtime>::MaintainerDoesNotExist,
+            number_of_events_before_call,
+        );
+    })
+}

+ 84 - 0
runtime-modules/content-directory/src/tests/set_curator_group_status.rs

@@ -0,0 +1,84 @@
+use super::*;
+
+#[test]
+fn set_curator_group_status_success() {
+    with_test_externalities(|| {
+        // Add curator group
+        assert_ok!(add_curator_group(LEAD_ORIGIN));
+
+        // Runtime tested state before call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Change curator group status
+        assert_ok!(set_curator_group_status(
+            LEAD_ORIGIN,
+            FIRST_CURATOR_GROUP_ID,
+            true
+        ));
+
+        // Runtime tested state after call
+
+        // Ensure curator group status changed
+        let mut curator_group = CuratorGroup::default();
+        curator_group.set_status(true);
+        assert_eq!(curator_group_by_id(FIRST_CURATOR_GROUP_ID), curator_group);
+
+        let curator_group_status_set_event = get_test_event(RawEvent::CuratorGroupStatusSet(
+            FIRST_CURATOR_GROUP_ID,
+            true,
+        ));
+
+        // Event checked
+        assert_event_success(
+            curator_group_status_set_event,
+            number_of_events_before_call + 1,
+        );
+    })
+}
+
+#[test]
+fn set_curator_group_status_lead_auth_failed() {
+    with_test_externalities(|| {
+        // Add curator group
+        assert_ok!(add_curator_group(LEAD_ORIGIN));
+
+        // Runtime tested state before call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // An attemt to change curator group status from non lead origin
+        let set_curator_group_status_result =
+            set_curator_group_status(FIRST_MEMBER_ORIGIN, FIRST_CURATOR_GROUP_ID, true);
+
+        // Failure checked
+        assert_failure(
+            set_curator_group_status_result,
+            Error::<Runtime>::LeadAuthFailed,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn set_curator_group_status_for_non_existent_curator_group() {
+    with_test_externalities(|| {
+        // Runtime tested state before call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // An attemt to change curator group status for non existent curator group
+        let set_curator_group_status_result =
+            set_curator_group_status(LEAD_ORIGIN, FIRST_CURATOR_GROUP_ID, true);
+
+        // Failure checked
+        assert_failure(
+            set_curator_group_status_result,
+            Error::<Runtime>::CuratorGroupDoesNotExist,
+            number_of_events_before_call,
+        );
+    })
+}

+ 106 - 0
runtime-modules/content-directory/src/tests/transaction.rs

@@ -0,0 +1,106 @@
+use super::*;
+
+#[test]
+fn transaction_success() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Create single reference property
+        let property_type_reference = Type::Reference(FIRST_CLASS_ID, true);
+        let property = Property::<Runtime>::with_name_and_type(
+            PropertyNameLengthConstraint::get().max() as usize,
+            PropertyType::Single(property_type_reference),
+            true,
+            false,
+        );
+
+        // Add Schema to the Class
+        assert_ok!(add_class_schema(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            BTreeSet::new(),
+            vec![property]
+        ));
+
+        let operations = vec![
+            OperationType::CreateEntity(CreateEntityOperation {
+                class_id: FIRST_CLASS_ID,
+            }),
+            OperationType::AddSchemaSupportToEntity(AddSchemaSupportToEntityOperation {
+                entity_id: ParameterizedEntity::InternalEntityJustAdded(0), // index 0 (prior operation)
+                schema_id: 0,
+                parametrized_property_values: vec![ParametrizedClassPropertyValue {
+                    in_class_index: 0,
+                    value: ParametrizedPropertyValue::InternalEntityJustAdded(0),
+                }],
+            }),
+            OperationType::CreateEntity(CreateEntityOperation {
+                class_id: FIRST_CLASS_ID,
+            }),
+            OperationType::UpdatePropertyValues(UpdatePropertyValuesOperation {
+                entity_id: ParameterizedEntity::InternalEntityJustAdded(0), // index 0 (prior operation)
+                new_parametrized_property_values: vec![ParametrizedClassPropertyValue {
+                    in_class_index: 0,
+                    value: ParametrizedPropertyValue::InternalEntityJustAdded(2),
+                }],
+            }),
+        ];
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_calls = System::events().len();
+
+        let actor = Actor::Lead;
+
+        // Number of operations to be performed
+        let operations_count = operations.len();
+
+        // Complete transaction
+        assert_ok!(transaction(LEAD_ORIGIN, actor.clone(), operations));
+
+        // Runtime tested state after call
+
+        let entity_ownership_transfered_event =
+            get_test_event(RawEvent::TransactionCompleted(actor));
+
+        // Last event checked
+        assert_event_success(
+            entity_ownership_transfered_event,
+            number_of_events_before_calls + operations_count + 1,
+        );
+    })
+}
+
+#[test]
+fn transaction_limit_reached() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        let operation = OperationType::CreateEntity(CreateEntityOperation {
+            class_id: FIRST_CLASS_ID,
+        });
+
+        let operations =
+            vec![operation; MaxNumberOfOperationsDuringAtomicBatching::get() as usize + 1];
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let actor = Actor::Lead;
+
+        // Make an attempt to complete transaction with operations, which total number exceeds MaxNumberOfOperationsDuringAtomicBatching runtime constraint
+        let transaction_result = transaction(LEAD_ORIGIN, actor, operations);
+
+        // Failure checked
+        assert_failure(
+            transaction_result,
+            Error::<Runtime>::NumberOfOperationsDuringAtomicBatchingLimitReached,
+            number_of_events_before_call,
+        );
+    })
+}

+ 738 - 0
runtime-modules/content-directory/src/tests/transfer_entity_ownership.rs

@@ -0,0 +1,738 @@
+use super::*;
+
+#[test]
+fn transfer_entity_ownership_success() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        let actor = Actor::Lead;
+
+        // Create entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_calls = System::events().len();
+
+        // Used to ensure controller changed succesfully
+        let mut entity = entity_by_id(FIRST_ENTITY_ID);
+
+        let new_controller = EntityController::Member(FIRST_MEMBER_ID);
+
+        // Ensure Entity controller set to EntityController::Lead
+        assert!(entity
+            .get_permissions_ref()
+            .controller_is_equal_to(&EntityController::Lead));
+
+        // Transfer entity ownership to new controller
+        assert_ok!(transfer_entity_ownership(
+            LEAD_ORIGIN,
+            FIRST_ENTITY_ID,
+            new_controller.clone(),
+            // Given entity does not have property references with same_owner flag set
+            BTreeMap::new()
+        ));
+
+        // Runtime tested state after call
+
+        // Ensure Entity controller changed to the new_controller
+        entity
+            .get_permissions_mut()
+            .set_conroller(new_controller.clone());
+
+        assert!(entity
+            .get_permissions_ref()
+            .controller_is_equal_to(&new_controller));
+
+        let entity_ownership_transfered_event = get_test_event(
+            RawEvent::EntityOwnershipTransfered(FIRST_ENTITY_ID, new_controller, None),
+        );
+
+        // Last event checked
+        assert_event_success(
+            entity_ownership_transfered_event,
+            number_of_events_before_calls + 1,
+        );
+    })
+}
+
+#[test]
+fn transfer_entity_ownership_lead_auth_failed() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        let actor = Actor::Lead;
+
+        // Create entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let new_controller = EntityController::Member(FIRST_MEMBER_ID);
+
+        // Make an attempt to transfer entity ownership to new controller under non lead origin
+        let transfer_entity_ownership_result = transfer_entity_ownership(
+            UNKNOWN_ORIGIN,
+            FIRST_ENTITY_ID,
+            new_controller,
+            // Given entity does not have property references with same_owner flag set
+            BTreeMap::new(),
+        );
+
+        // Failure checked
+        assert_failure(
+            transfer_entity_ownership_result,
+            Error::<Runtime>::LeadAuthFailed,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn transfer_entity_ownership_entity_not_found() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let new_controller = EntityController::Member(FIRST_MEMBER_ID);
+
+        // Make an attempt to transfer entity ownership of non existent Entity
+        let transfer_entity_ownership_result = transfer_entity_ownership(
+            LEAD_ORIGIN,
+            FIRST_ENTITY_ID,
+            new_controller,
+            // Given entity does not have property references with same_owner flag set
+            BTreeMap::new(),
+        );
+
+        // Failure checked
+        assert_failure(
+            transfer_entity_ownership_result,
+            Error::<Runtime>::EntityNotFound,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn transfer_entity_ownership_provided_entity_controller_is_equal_to_the_current_one() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        let actor = Actor::Lead;
+
+        // Create entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let new_controller = EntityController::Lead;
+
+        // Make an attempt to transfer entity ownership, providing new Entity controller, which is equal to the current one
+        let transfer_entity_ownership_result = transfer_entity_ownership(
+            LEAD_ORIGIN,
+            FIRST_ENTITY_ID,
+            new_controller,
+            // Given entity does not have property references with same_owner flag set
+            BTreeMap::new(),
+        );
+
+        // Failure checked
+        assert_failure(
+            transfer_entity_ownership_result,
+            Error::<Runtime>::ProvidedEntityControllerIsEqualToTheCurrentOne,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn transfer_entity_ownership_inbound_same_owner_rc_does_not_equal_to_zero() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        add_unique_class_reference_schema();
+
+        let actor = Actor::Lead;
+
+        // Create first entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        // Create second entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        let schema_property_value =
+            InputPropertyValue::<Runtime>::vec_reference(vec![FIRST_ENTITY_ID, FIRST_ENTITY_ID]);
+
+        let mut schema_property_values = BTreeMap::new();
+        schema_property_values.insert(FIRST_PROPERTY_ID, schema_property_value);
+
+        // Add schema support to the second Entity
+        assert_ok!(add_schema_support_to_entity(
+            LEAD_ORIGIN,
+            actor,
+            SECOND_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            schema_property_values
+        ));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let new_controller = EntityController::Member(FIRST_MEMBER_ID);
+
+        // Make an attempt to transfer ownership of Entity, which is referenced by property values
+        // of another entities with same owner flag set
+        let transfer_entity_ownership_result = transfer_entity_ownership(
+            LEAD_ORIGIN,
+            FIRST_ENTITY_ID,
+            new_controller,
+            // Given entity does not have property references with same_owner flag set
+            BTreeMap::new(),
+        );
+
+        // Failure checked
+        assert_failure(
+            transfer_entity_ownership_result,
+            Error::<Runtime>::EntityInboundSameOwnerRcDoesNotEqualToZero,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn transfer_entity_ownership_provided_property_value_ids_must_be_references_with_same_owner_flag_set(
+) {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        add_unique_class_reference_schema();
+
+        // Update class permissions to force any member be available to create Entities
+        assert_ok!(update_class_permissions(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            Some(true),
+            None,
+            None,
+            None
+        ));
+
+        let actor = Actor::Lead;
+
+        // Create first entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        // Create second entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        let schema_property_value =
+            InputPropertyValue::<Runtime>::vec_reference(vec![SECOND_ENTITY_ID, SECOND_ENTITY_ID]);
+
+        let mut schema_property_values = BTreeMap::new();
+        schema_property_values.insert(FIRST_PROPERTY_ID, schema_property_value);
+
+        // Add schema support to the second Entity
+        assert_ok!(add_schema_support_to_entity(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            schema_property_values
+        ));
+
+        // Create third entity
+        assert_ok!(create_entity(
+            FIRST_MEMBER_ORIGIN,
+            FIRST_CLASS_ID,
+            Actor::Member(FIRST_MEMBER_ID)
+        ));
+
+        let new_controller = EntityController::Member(FIRST_MEMBER_ID);
+
+        let schema_property_value = InputPropertyValue::<Runtime>::vec_reference(vec![
+            THIRD_ENTITY_ID,
+            THIRD_ENTITY_ID,
+            THIRD_ENTITY_ID,
+        ]);
+
+        let mut schema_property_values = BTreeMap::new();
+        schema_property_values.insert(SECOND_PROPERTY_ID, schema_property_value);
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to transfer ownership of Entity, providing new property value references with same owner flag set,
+        // which respective property ids are not references with same owner flag set on Class Property level
+        let transfer_entity_ownership_result = transfer_entity_ownership(
+            LEAD_ORIGIN,
+            FIRST_ENTITY_ID,
+            new_controller,
+            schema_property_values,
+        );
+
+        // Failure checked
+        assert_failure(
+            transfer_entity_ownership_result,
+            Error::<Runtime>::AllProvidedPropertyValueIdsMustBeReferencesWithSameOwnerFlagSet,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn transfer_entity_ownership_provided_new_property_value_referencing_entity_that_can_not_be_referenced(
+) {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        add_unique_class_reference_schema();
+
+        // Update class permissions to force any member be available to create Entities
+        assert_ok!(update_class_permissions(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            Some(true),
+            None,
+            None,
+            None
+        ));
+
+        let actor = Actor::Lead;
+
+        // Create first entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        // Create second entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        let schema_property_value =
+            InputPropertyValue::<Runtime>::vec_reference(vec![SECOND_ENTITY_ID, SECOND_ENTITY_ID]);
+
+        let mut schema_property_values = BTreeMap::new();
+        schema_property_values.insert(FIRST_PROPERTY_ID, schema_property_value);
+
+        // Add schema support to the second Entity
+        assert_ok!(add_schema_support_to_entity(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            schema_property_values
+        ));
+
+        // Create third entity
+        assert_ok!(create_entity(
+            FIRST_MEMBER_ORIGIN,
+            FIRST_CLASS_ID,
+            Actor::Member(FIRST_MEMBER_ID)
+        ));
+
+        // Update third entity permissions to forbid it from being referencable
+        assert_ok!(update_entity_permissions(
+            LEAD_ORIGIN,
+            THIRD_ENTITY_ID,
+            None,
+            Some(false)
+        ));
+
+        let new_controller = EntityController::Member(FIRST_MEMBER_ID);
+
+        let schema_property_value = InputPropertyValue::<Runtime>::vec_reference(vec![
+            THIRD_ENTITY_ID,
+            THIRD_ENTITY_ID,
+            THIRD_ENTITY_ID,
+        ]);
+
+        let mut schema_property_values = BTreeMap::new();
+        schema_property_values.insert(FIRST_PROPERTY_ID, schema_property_value);
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to transfer ownership of Entity, providing new property value reference(s) with same owner flag set,
+        // which refer(s) Entity, that can not be referenced
+        let transfer_entity_ownership_result = transfer_entity_ownership(
+            LEAD_ORIGIN,
+            FIRST_ENTITY_ID,
+            new_controller,
+            schema_property_values,
+        );
+
+        // Failure checked
+        assert_failure(
+            transfer_entity_ownership_result,
+            Error::<Runtime>::EntityCanNotBeReferenced,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn transfer_entity_ownership_provided_new_property_value_referencing_non_existent_entity() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        add_unique_class_reference_schema();
+
+        let actor = Actor::Lead;
+
+        // Create first entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        // Create second entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        let schema_property_value =
+            InputPropertyValue::<Runtime>::vec_reference(vec![SECOND_ENTITY_ID, SECOND_ENTITY_ID]);
+
+        let mut schema_property_values = BTreeMap::new();
+        schema_property_values.insert(FIRST_PROPERTY_ID, schema_property_value);
+
+        // Add schema support to the second Entity
+        assert_ok!(add_schema_support_to_entity(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            schema_property_values
+        ));
+
+        let new_controller = EntityController::Member(FIRST_MEMBER_ID);
+
+        let schema_property_value = InputPropertyValue::<Runtime>::vec_reference(vec![
+            THIRD_ENTITY_ID,
+            THIRD_ENTITY_ID,
+            THIRD_ENTITY_ID,
+        ]);
+
+        let mut schema_property_values = BTreeMap::new();
+        schema_property_values.insert(FIRST_PROPERTY_ID, schema_property_value);
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to transfer ownership of Entity, providing new property value reference(s) with same owner flag set,
+        // which refer(s) to Entity that does not exist
+        let transfer_entity_ownership_result = transfer_entity_ownership(
+            LEAD_ORIGIN,
+            FIRST_ENTITY_ID,
+            new_controller,
+            schema_property_values,
+        );
+
+        // Failure checked
+        assert_failure(
+            transfer_entity_ownership_result,
+            Error::<Runtime>::EntityNotFound,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn transfer_entity_ownership_provided_new_property_value_referencing_entity_controlled_by_another_actor(
+) {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        add_unique_class_reference_schema();
+
+        // Update class permissions to force any member be available to create Entities
+        assert_ok!(update_class_permissions(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            Some(true),
+            None,
+            None,
+            None
+        ));
+
+        let actor = Actor::Lead;
+
+        // Create first entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        // Create second entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        let schema_property_value =
+            InputPropertyValue::<Runtime>::vec_reference(vec![SECOND_ENTITY_ID, SECOND_ENTITY_ID]);
+
+        let mut schema_property_values = BTreeMap::new();
+        schema_property_values.insert(FIRST_PROPERTY_ID, schema_property_value);
+
+        // Add schema support to the second Entity
+        assert_ok!(add_schema_support_to_entity(
+            LEAD_ORIGIN,
+            actor.to_owned(),
+            FIRST_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            schema_property_values
+        ));
+
+        // Create third entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor));
+
+        let new_controller = EntityController::Member(FIRST_MEMBER_ID);
+
+        let schema_property_value = InputPropertyValue::<Runtime>::vec_reference(vec![
+            THIRD_ENTITY_ID,
+            THIRD_ENTITY_ID,
+            THIRD_ENTITY_ID,
+        ]);
+
+        let mut schema_property_values = BTreeMap::new();
+        schema_property_values.insert(FIRST_PROPERTY_ID, schema_property_value);
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to transfer ownership of Entity, providing new property value reference(s) with same owner flag set,
+        // which refer(s) Entity, controlled by another actor
+        let transfer_entity_ownership_result = transfer_entity_ownership(
+            LEAD_ORIGIN,
+            FIRST_ENTITY_ID,
+            new_controller,
+            schema_property_values,
+        );
+
+        // Failure checked
+        assert_failure(
+            transfer_entity_ownership_result,
+            Error::<Runtime>::SameControllerConstraintViolation,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn transfer_entity_ownership_required_property_was_not_provided() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        add_unique_class_reference_schema();
+
+        // Update class permissions to force any member be available to create Entities
+        assert_ok!(update_class_permissions(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            Some(true),
+            None,
+            None,
+            None
+        ));
+
+        let actor = Actor::Lead;
+
+        // Create first entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        // Create second entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        let schema_property_value =
+            InputPropertyValue::<Runtime>::vec_reference(vec![SECOND_ENTITY_ID, SECOND_ENTITY_ID]);
+
+        let mut schema_property_values = BTreeMap::new();
+        schema_property_values.insert(FIRST_PROPERTY_ID, schema_property_value);
+
+        // Add schema support to the second Entity
+        assert_ok!(add_schema_support_to_entity(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            schema_property_values
+        ));
+
+        // Create third entity
+        assert_ok!(create_entity(
+            FIRST_MEMBER_ORIGIN,
+            FIRST_CLASS_ID,
+            Actor::Member(FIRST_MEMBER_ID)
+        ));
+
+        let new_controller = EntityController::Member(FIRST_MEMBER_ID);
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to transfer ownership of Entity without providing required
+        // new property value references with same owner flag set
+        let transfer_entity_ownership_result = transfer_entity_ownership(
+            LEAD_ORIGIN,
+            FIRST_ENTITY_ID,
+            new_controller,
+            BTreeMap::new(),
+        );
+
+        // Failure checked
+        assert_failure(
+            transfer_entity_ownership_result,
+            Error::<Runtime>::MissingRequiredProperty,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn transfer_entity_ownership_unique_constraint_violation() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Create unique reference property with same_controller flag set
+        let property_type = PropertyType::<Runtime>::vec_reference(
+            FIRST_CLASS_ID,
+            true,
+            VecMaxLengthConstraint::get(),
+        );
+
+        let property = Property::<Runtime>::with_name_and_type(
+            PropertyNameLengthConstraint::get().max() as usize,
+            property_type,
+            true,
+            true,
+        );
+
+        // Add Schema to the Class
+        assert_ok!(add_class_schema(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            BTreeSet::new(),
+            vec![property]
+        ));
+
+        // Update class permissions to force any member be available to create Entities
+        assert_ok!(update_class_permissions(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            Some(true),
+            None,
+            None,
+            None
+        ));
+
+        let actor = Actor::Lead;
+
+        // Create first entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        // Create second entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        let first_schema_property_value =
+            InputPropertyValue::<Runtime>::vec_reference(vec![SECOND_ENTITY_ID, SECOND_ENTITY_ID]);
+
+        let mut schema_property_values = BTreeMap::new();
+        schema_property_values.insert(FIRST_PROPERTY_ID, first_schema_property_value);
+
+        // Add schema support to the first Entity
+        assert_ok!(add_schema_support_to_entity(
+            LEAD_ORIGIN,
+            actor.to_owned(),
+            FIRST_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            schema_property_values
+        ));
+
+        let second_schema_property_value = InputPropertyValue::<Runtime>::vec_reference(vec![
+            SECOND_ENTITY_ID,
+            SECOND_ENTITY_ID,
+            SECOND_ENTITY_ID,
+        ]);
+
+        let mut schema_property_values = BTreeMap::new();
+        schema_property_values.insert(FIRST_PROPERTY_ID, second_schema_property_value);
+
+        // Add schema support to the second Entity
+        assert_ok!(add_schema_support_to_entity(
+            LEAD_ORIGIN,
+            actor,
+            SECOND_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            schema_property_values
+        ));
+
+        // Create third entity
+        assert_ok!(create_entity(
+            FIRST_MEMBER_ORIGIN,
+            FIRST_CLASS_ID,
+            Actor::Member(FIRST_MEMBER_ID)
+        ));
+
+        let new_controller = EntityController::Member(FIRST_MEMBER_ID);
+
+        let schema_property_value = InputPropertyValue::<Runtime>::vec_reference(vec![
+            THIRD_ENTITY_ID,
+            THIRD_ENTITY_ID,
+            THIRD_ENTITY_ID,
+        ]);
+
+        let mut schema_property_values = BTreeMap::new();
+        schema_property_values.insert(FIRST_PROPERTY_ID, schema_property_value);
+
+        assert_ok!(transfer_entity_ownership(
+            LEAD_ORIGIN,
+            FIRST_ENTITY_ID,
+            new_controller.clone(),
+            schema_property_values.clone(),
+        ));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to transfer ownership of Entity, providing new property value reference(s)
+        // with same owner flag set, which are identical to thouse, are already added to the another Entity of this Class,
+        // though should be unique on Class Property level
+        let transfer_entity_ownership_result = transfer_entity_ownership(
+            LEAD_ORIGIN,
+            SECOND_ENTITY_ID,
+            new_controller,
+            schema_property_values,
+        );
+
+        // Failure checked
+        assert_failure(
+            transfer_entity_ownership_result,
+            Error::<Runtime>::PropertyValueShouldBeUnique,
+            number_of_events_before_call,
+        );
+    })
+}

+ 178 - 0
runtime-modules/content-directory/src/tests/update_class_permissions.rs

@@ -0,0 +1,178 @@
+use super::*;
+
+#[test]
+fn update_class_permissions_success() {
+    with_test_externalities(|| {
+        // Create simple class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Add first curator group
+        assert_ok!(add_curator_group(LEAD_ORIGIN));
+
+        // Add second curator group
+        assert_ok!(add_curator_group(LEAD_ORIGIN));
+
+        // Runtime tested state before call
+
+        let mut class_permissions = ClassPermissions::default();
+
+        // Ensure class permissions of newly created Class are equal to default ones
+        assert_eq!(
+            class_by_id(FIRST_CLASS_ID).get_permissions(),
+            class_permissions
+        );
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let maintainers =
+            BTreeSet::from_iter(vec![FIRST_CURATOR_GROUP_ID, SECOND_CURATOR_GROUP_ID]);
+
+        // Update class permissions
+        assert_ok!(update_class_permissions(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            None,
+            Some(true),
+            None,
+            Some(maintainers.clone())
+        ));
+
+        // Runtime tested state after call
+
+        // Ensure class permissions updated succesfully
+
+        *class_permissions.get_maintainers_mut() = maintainers;
+        class_permissions.set_entity_creation_blocked(true);
+
+        assert_eq!(
+            class_by_id(FIRST_CLASS_ID).get_permissions(),
+            class_permissions
+        );
+
+        let class_permissions_updated_event =
+            get_test_event(RawEvent::ClassPermissionsUpdated(FIRST_CLASS_ID));
+
+        // Event checked
+        assert_event_success(
+            class_permissions_updated_event,
+            number_of_events_before_call + 1,
+        );
+    })
+}
+
+#[test]
+fn update_class_permissions_lead_auth_failed() {
+    with_test_externalities(|| {
+        // Create simple class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to update class permissions under non lead origin
+        let update_class_permissions_result = update_class_permissions(
+            FIRST_MEMBER_ORIGIN,
+            FIRST_CLASS_ID,
+            None,
+            Some(true),
+            None,
+            None,
+        );
+
+        // Failure checked
+        assert_failure(
+            update_class_permissions_result,
+            Error::<Runtime>::LeadAuthFailed,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn update_class_permissions_of_non_existent_class() {
+    with_test_externalities(|| {
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to update class permissions of non existent class
+        let update_class_permissions_result =
+            update_class_permissions(LEAD_ORIGIN, FIRST_CLASS_ID, None, Some(true), None, None);
+
+        // Failure checked
+        assert_failure(
+            update_class_permissions_result,
+            Error::<Runtime>::ClassNotFound,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn update_class_permissions_curator_group_does_not_exist() {
+    with_test_externalities(|| {
+        // Create simple class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let maintainers = BTreeSet::from_iter(vec![UNKNOWN_CURATOR_GROUP_ID]);
+
+        // Make an attempt to update class permissions, providing curator group maintainers which aren`t exist in runtime yet
+        let update_class_permissions_result = update_class_permissions(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            None,
+            Some(true),
+            None,
+            Some(maintainers),
+        );
+
+        // Failure checked
+        assert_failure(
+            update_class_permissions_result,
+            Error::<Runtime>::CuratorGroupDoesNotExist,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn update_class_permissions_maintainers_limit_reached() {
+    with_test_externalities(|| {
+        // Create simple class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Add curator groups, which total number exceeds MaxNumberOfMaintainersPerClass runtime limit
+        // and initialize class maintainers set with their respective ids
+        let mut maintainers = BTreeSet::new();
+        (1..=(MaxNumberOfMaintainersPerClass::get() + 1))
+            .into_iter()
+            .for_each(|curator_group_id| {
+                assert_ok!(add_curator_group(LEAD_ORIGIN));
+                maintainers.insert(curator_group_id as CuratorGroupId);
+            });
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to update class permissions, providing curator group maintainers,
+        // which total number exceeds MaxNumberOfMaintainersPerClass runtime limit
+        let update_class_permissions_result = update_class_permissions(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            None,
+            Some(true),
+            None,
+            Some(maintainers),
+        );
+
+        // Failure checked
+        assert_failure(
+            update_class_permissions_result,
+            Error::<Runtime>::ClassMaintainersLimitReached,
+            number_of_events_before_call,
+        );
+    })
+}

+ 145 - 0
runtime-modules/content-directory/src/tests/update_class_schema_status.rs

@@ -0,0 +1,145 @@
+use super::*;
+
+#[test]
+fn update_class_schema_status_success() {
+    with_test_externalities(|| {
+        // Create simple class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Runtime tested state before call
+
+        let property =
+            Property::default_with_name(PropertyNameLengthConstraint::get().max() as usize);
+
+        // Add class schema (default class schema active flag set true)
+        assert_ok!(add_class_schema(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            BTreeSet::new(),
+            vec![property.clone()]
+        ));
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        assert_ok!(update_class_schema_status(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            FIRST_SCHEMA_ID,
+            false
+        ));
+
+        // Runtime tested state after call
+
+        // Ensure class schema status updated succesfully
+        let mut class = create_class_with_default_permissions();
+        let mut schema = Schema::new(BTreeSet::from_iter(vec![FIRST_PROPERTY_ID].into_iter()));
+
+        schema.set_status(false);
+        class.set_properties(vec![property]);
+        *class.get_schemas_mut() = vec![schema];
+
+        assert_eq!(class_by_id(FIRST_CLASS_ID), class);
+
+        let class_schema_status_updated_event = get_test_event(RawEvent::ClassSchemaStatusUpdated(
+            FIRST_CLASS_ID,
+            FIRST_SCHEMA_ID,
+            false,
+        ));
+
+        // Last event checked
+        assert_event_success(
+            class_schema_status_updated_event,
+            number_of_events_before_call + 1,
+        );
+    })
+}
+
+#[test]
+fn update_class_schema_status_lead_auth_failed() {
+    with_test_externalities(|| {
+        // Create simple class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Runtime tested state before call
+
+        let property =
+            Property::default_with_name(PropertyNameLengthConstraint::get().max() as usize);
+
+        // Add class schema (default class schema active flag set true)
+        assert_ok!(add_class_schema(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            BTreeSet::new(),
+            vec![property]
+        ));
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // make an attempt to update class schema status under non lead origin
+        let update_class_schema_status_result =
+            update_class_schema_status(FIRST_MEMBER_ORIGIN, FIRST_CLASS_ID, FIRST_SCHEMA_ID, false);
+
+        // Failure checked
+        assert_failure(
+            update_class_schema_status_result,
+            Error::<Runtime>::LeadAuthFailed,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn update_class_schema_status_of_non_existent_class() {
+    with_test_externalities(|| {
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to update class schema status of non existent class
+        let update_class_schema_status_result =
+            update_class_schema_status(LEAD_ORIGIN, UNKNOWN_CLASS_ID, FIRST_SCHEMA_ID, false);
+
+        // Failure checked
+        assert_failure(
+            update_class_schema_status_result,
+            Error::<Runtime>::ClassNotFound,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn update_class_schema_status_for_non_existent_schema() {
+    with_test_externalities(|| {
+        // Create simple class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Runtime tested state before call
+
+        let property =
+            Property::default_with_name(PropertyNameLengthConstraint::get().max() as usize);
+
+        // Add class schema (default class schema active flag set true)
+        assert_ok!(add_class_schema(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            BTreeSet::new(),
+            vec![property]
+        ));
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to update  status of Schema, which does not exist in this Class yet.
+        let update_class_schema_status_result =
+            update_class_schema_status(LEAD_ORIGIN, FIRST_CLASS_ID, UNKNOWN_SCHEMA_ID, false);
+
+        // Failure checked
+        assert_failure(
+            update_class_schema_status_result,
+            Error::<Runtime>::UnknownClassSchemaId,
+            number_of_events_before_call,
+        );
+    })
+}

+ 197 - 0
runtime-modules/content-directory/src/tests/update_entity_creation_voucher.rs

@@ -0,0 +1,197 @@
+use super::*;
+
+#[test]
+fn create_entity_creation_voucher_success() {
+    with_test_externalities(|| {
+        // Create simple class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Runtime state before tested call
+
+        let entity_controller = EntityController::Member(FIRST_MEMBER_ID);
+        assert!(!entity_creation_voucher_exists(
+            FIRST_CLASS_ID,
+            &entity_controller
+        ));
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Create entities creation voucher for chosen controller
+        assert_ok!(update_entity_creation_voucher(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            entity_controller.clone(),
+            IndividualEntitiesCreationLimit::get()
+        ));
+
+        // Runtime tested state after call
+
+        // Ensure entity creation voucher for chosen controller created succesfully
+        let entity_voucher = EntityCreationVoucher::new(IndividualEntitiesCreationLimit::get());
+
+        assert_eq!(
+            entity_creation_vouchers(FIRST_CLASS_ID, &entity_controller),
+            entity_voucher,
+        );
+
+        let entity_creation_voucher_created_event = get_test_event(
+            RawEvent::EntityCreationVoucherCreated(entity_controller, entity_voucher),
+        );
+
+        // Last event checked
+        assert_event_success(
+            entity_creation_voucher_created_event,
+            number_of_events_before_call + 1,
+        );
+    })
+}
+
+#[test]
+fn update_entity_creation_voucher_success() {
+    with_test_externalities(|| {
+        // Create simple class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        let actor = Actor::Member(FIRST_MEMBER_ID);
+
+        // Update class permissions to force any member be available to create entities
+        assert_ok!(update_class_permissions(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            Some(true),
+            None,
+            None,
+            None
+        ));
+
+        let entity_controller = EntityController::from_actor(&actor);
+
+        // Create entity
+        assert_ok!(create_entity(FIRST_MEMBER_ORIGIN, FIRST_CLASS_ID, actor));
+
+        // Runtime state before tested call
+
+        let mut entity_creation_voucher =
+            entity_creation_vouchers(FIRST_CLASS_ID, &entity_controller);
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Update entities creation voucher for chosen controller
+        let new_maximum_entities_count = entity_creation_voucher.maximum_entities_count - 1;
+        assert_ok!(update_entity_creation_voucher(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            entity_controller.clone(),
+            new_maximum_entities_count
+        ));
+
+        // Runtime tested state after call
+
+        // Ensure  entity creation voucher for chosen controller updated succesfully
+        entity_creation_voucher.set_maximum_entities_count(new_maximum_entities_count);
+        assert_eq!(
+            entity_creation_vouchers(FIRST_CLASS_ID, &entity_controller),
+            entity_creation_voucher
+        );
+
+        let entity_creation_voucher_created_event = get_test_event(
+            RawEvent::EntityCreationVoucherUpdated(entity_controller, entity_creation_voucher),
+        );
+
+        // Last event checked
+        assert_event_success(
+            entity_creation_voucher_created_event,
+            number_of_events_before_call + 1,
+        );
+    })
+}
+
+#[test]
+fn update_entity_creation_voucher_lead_auth_failed() {
+    with_test_externalities(|| {
+        // Create simple class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Runtime state before tested call
+
+        let entity_controller = EntityController::Member(FIRST_MEMBER_ID);
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to create entities creation voucher for chosen controller under non lead origin
+        let update_entity_creation_voucher_result = update_entity_creation_voucher(
+            FIRST_MEMBER_ORIGIN,
+            FIRST_CLASS_ID,
+            entity_controller,
+            IndividualEntitiesCreationLimit::get(),
+        );
+
+        // Failure checked
+        assert_failure(
+            update_entity_creation_voucher_result,
+            Error::<Runtime>::LeadAuthFailed,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn update_entity_creation_voucher_class_does_not_exist() {
+    with_test_externalities(|| {
+        // Runtime state before tested call
+
+        let entity_controller = EntityController::Member(FIRST_MEMBER_ID);
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to create entities creation voucher for chosen controller and non existent class
+        let update_entity_creation_voucher_result = update_entity_creation_voucher(
+            LEAD_ORIGIN,
+            UNKNOWN_CLASS_ID,
+            entity_controller,
+            IndividualEntitiesCreationLimit::get(),
+        );
+
+        // Failure checked
+        assert_failure(
+            update_entity_creation_voucher_result,
+            Error::<Runtime>::ClassNotFound,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn update_entity_creation_voucher_individual_creation_limit_exceed() {
+    with_test_externalities(|| {
+        // Create simple class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Runtime state before tested call
+
+        let entity_controller = EntityController::Member(FIRST_MEMBER_ID);
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to create entities creation voucher for chosen controller with maximum_entities_count
+        // value that exceeds IndividualEntitiesCreationLimit
+        let update_entity_creation_voucher_result = update_entity_creation_voucher(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            entity_controller,
+            IndividualEntitiesCreationLimit::get() + 1,
+        );
+
+        // Failure checked
+        assert_failure(
+            update_entity_creation_voucher_result,
+            Error::<Runtime>::NumberOfClassEntitiesPerActorConstraintViolated,
+            number_of_events_before_call,
+        );
+    })
+}

+ 104 - 0
runtime-modules/content-directory/src/tests/update_entity_permissions.rs

@@ -0,0 +1,104 @@
+use super::*;
+
+#[test]
+fn update_entity_permissions_success() {
+    with_test_externalities(|| {
+        // Create simple class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        let actor = Actor::Lead;
+
+        // Create entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor));
+
+        // Runtime state before tested call
+
+        // Ensure entity permissions of newly created Entity are equal to default ones.
+        let mut entity_permissions = EntityPermissions::default();
+        assert_eq!(
+            entity_by_id(FIRST_ENTITY_ID).get_permissions(),
+            entity_permissions
+        );
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Update entity permissions for chosen entity
+        assert_ok!(update_entity_permissions(
+            LEAD_ORIGIN,
+            FIRST_ENTITY_ID,
+            None,
+            Some(false)
+        ));
+
+        // Runtime tested state after call
+
+        entity_permissions.set_referencable(false);
+        assert_eq!(
+            entity_by_id(FIRST_ENTITY_ID).get_permissions(),
+            entity_permissions
+        );
+
+        let entity_permissions_updated_event =
+            get_test_event(RawEvent::EntityPermissionsUpdated(FIRST_ENTITY_ID));
+
+        // Last event checked
+        assert_event_success(
+            entity_permissions_updated_event,
+            number_of_events_before_call + 1,
+        );
+    })
+}
+
+#[test]
+fn update_entity_permissions_lead_auth_failed() {
+    with_test_externalities(|| {
+        // Create simple class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        let actor = Actor::Lead;
+
+        // Create entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to update entity permissions for chosen entity under non lead origin
+        let update_entity_permissions_result =
+            update_entity_permissions(FIRST_MEMBER_ORIGIN, FIRST_ENTITY_ID, None, Some(false));
+
+        // Failure checked
+        assert_failure(
+            update_entity_permissions_result,
+            Error::<Runtime>::LeadAuthFailed,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn update_entity_permissions_of_non_existent_entity() {
+    with_test_externalities(|| {
+        // Create simple class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        // Make an attempt to update entity permissions for chosen entity under non lead origin
+        let update_entity_permissions_result =
+            update_entity_permissions(LEAD_ORIGIN, UNKNOWN_ENTITY_ID, None, Some(false));
+
+        // Failure checked
+        assert_failure(
+            update_entity_permissions_result,
+            Error::<Runtime>::EntityNotFound,
+            number_of_events_before_call,
+        );
+    })
+}

+ 1025 - 0
runtime-modules/content-directory/src/tests/update_entity_property_values.rs

@@ -0,0 +1,1025 @@
+use super::*;
+
+#[test]
+fn update_entity_property_values_success() {
+    with_test_externalities(|| {
+        let actor = Actor::Lead;
+
+        // Add entity schemas support
+        let (mut first_entity, mut second_entity) = add_entity_schemas_support();
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_calls = System::events().len();
+
+        let mut second_schema_new_property_values = BTreeMap::new();
+        let second_schema_new_property_value =
+            InputPropertyValue::<Runtime>::vec_reference(vec![SECOND_ENTITY_ID, SECOND_ENTITY_ID]);
+
+        second_schema_new_property_values
+            .insert(SECOND_PROPERTY_ID, second_schema_new_property_value.clone());
+
+        // Update entity property values
+        assert_ok!(update_entity_property_values(
+            LEAD_ORIGIN,
+            actor.clone(),
+            FIRST_ENTITY_ID,
+            second_schema_new_property_values
+        ));
+
+        // Runtime tested state after call
+
+        // Ensure first entity properties updated succesfully
+        if let Some(second_schema_old_property_value) =
+            first_entity.get_values_mut().get_mut(&SECOND_PROPERTY_ID)
+        {
+            second_schema_old_property_value.update(second_schema_new_property_value.into());
+        }
+
+        assert_eq!(first_entity, entity_by_id(FIRST_ENTITY_ID));
+
+        // Ensure reference counter of second entity updated succesfully
+        let inbound_rc = InboundReferenceCounter::new(2, true);
+        *second_entity.get_reference_counter_mut() = inbound_rc;
+
+        assert_eq!(second_entity, entity_by_id(SECOND_ENTITY_ID));
+
+        // Create side-effect
+        let side_effect = EntityReferenceCounterSideEffect::atomic(true, DeltaMode::Decrement);
+        let mut side_effects = ReferenceCounterSideEffects::default();
+        side_effects.insert(SECOND_ENTITY_ID, side_effect);
+
+        let entity_property_values_updated_event = get_test_event(
+            RawEvent::EntityPropertyValuesUpdated(actor, FIRST_ENTITY_ID, Some(side_effects)),
+        );
+
+        // Last event checked
+        assert_event_success(
+            entity_property_values_updated_event,
+            number_of_events_before_calls + 1,
+        );
+    })
+}
+
+#[test]
+fn update_entity_property_values_entity_not_found() {
+    with_test_externalities(|| {
+        let actor = emulate_entity_access_state_for_failure_case(
+            EntityAccessStateFailureType::EntityNotFound,
+        );
+
+        // Create class reference schema
+        add_unique_class_reference_schema();
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let mut schema_new_property_values = BTreeMap::new();
+        let schema_new_property_value =
+            InputPropertyValue::<Runtime>::vec_reference(vec![FIRST_ENTITY_ID, FIRST_ENTITY_ID]);
+
+        schema_new_property_values.insert(FIRST_PROPERTY_ID, schema_new_property_value);
+
+        // Make an attempt to update entity property values in case when corresponding Entity does not exist
+        let update_entity_property_values_result = update_entity_property_values(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            schema_new_property_values,
+        );
+
+        // Failure checked
+        assert_failure(
+            update_entity_property_values_result,
+            Error::<Runtime>::EntityNotFound,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn update_entity_property_values_lead_auth_failed() {
+    with_test_externalities(|| {
+        let actor = emulate_entity_access_state_for_failure_case(
+            EntityAccessStateFailureType::LeadAuthFailed,
+        );
+
+        // Create class reference schema and add corresponding schema support to the Entity
+        add_unique_class_reference_schema_and_entity_schema_support(&actor, LEAD_ORIGIN);
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let mut schema_new_property_values = BTreeMap::new();
+        let schema_new_property_value =
+            InputPropertyValue::<Runtime>::vec_reference(vec![FIRST_ENTITY_ID, FIRST_ENTITY_ID]);
+
+        schema_new_property_values.insert(FIRST_PROPERTY_ID, schema_new_property_value);
+
+        // Make an attempt to update entity property values using unknown origin and lead actor
+        let update_entity_property_values_result = update_entity_property_values(
+            UNKNOWN_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            schema_new_property_values,
+        );
+
+        // Failure checked
+        assert_failure(
+            update_entity_property_values_result,
+            Error::<Runtime>::LeadAuthFailed,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn update_entity_property_values_member_auth_failed() {
+    with_test_externalities(|| {
+        let actor = emulate_entity_access_state_for_failure_case(
+            EntityAccessStateFailureType::MemberAuthFailed,
+        );
+
+        // Create class reference schema and add corresponding schema support to the Entity
+        add_unique_class_reference_schema_and_entity_schema_support(&actor, LEAD_ORIGIN);
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let mut schema_new_property_values = BTreeMap::new();
+        let schema_new_property_value =
+            InputPropertyValue::<Runtime>::vec_reference(vec![FIRST_ENTITY_ID, FIRST_ENTITY_ID]);
+
+        schema_new_property_values.insert(FIRST_PROPERTY_ID, schema_new_property_value);
+
+        // Make an attempt to update entity property values using unknown origin and member actor
+        let update_entity_property_values_result = update_entity_property_values(
+            UNKNOWN_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            schema_new_property_values,
+        );
+
+        // Failure checked
+        assert_failure(
+            update_entity_property_values_result,
+            Error::<Runtime>::MemberAuthFailed,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn update_entity_property_values_curator_group_is_not_active() {
+    with_test_externalities(|| {
+        let actor = emulate_entity_access_state_for_failure_case(
+            EntityAccessStateFailureType::CuratorAuthFailed,
+        );
+
+        // Create class reference schema and add corresponding schema support to the Entity
+        add_unique_class_reference_schema_and_entity_schema_support(
+            &Actor::Curator(FIRST_CURATOR_GROUP_ID, FIRST_CURATOR_ID),
+            FIRST_CURATOR_ORIGIN,
+        );
+
+        // Make curator group inactive to block it from any entity operations
+        assert_ok!(set_curator_group_status(
+            LEAD_ORIGIN,
+            FIRST_CURATOR_GROUP_ID,
+            false
+        ));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let mut schema_new_property_values = BTreeMap::new();
+        let schema_new_property_value =
+            InputPropertyValue::<Runtime>::vec_reference(vec![FIRST_ENTITY_ID, FIRST_ENTITY_ID]);
+
+        schema_new_property_values.insert(FIRST_PROPERTY_ID, schema_new_property_value);
+
+        // Make an attempt to update entity property values using curator group, which is not active as actor
+        let update_entity_property_values_result = update_entity_property_values(
+            FIRST_CURATOR_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            schema_new_property_values,
+        );
+
+        // Failure checked
+        assert_failure(
+            update_entity_property_values_result,
+            Error::<Runtime>::CuratorGroupIsNotActive,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn update_entity_property_values_curator_auth_failed() {
+    with_test_externalities(|| {
+        let actor = emulate_entity_access_state_for_failure_case(
+            EntityAccessStateFailureType::CuratorAuthFailed,
+        );
+
+        // Create class reference schema and add corresponding schema support to the Entity
+        add_unique_class_reference_schema_and_entity_schema_support(
+            &Actor::Curator(FIRST_CURATOR_GROUP_ID, FIRST_CURATOR_ID),
+            FIRST_CURATOR_ORIGIN,
+        );
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let mut schema_new_property_values = BTreeMap::new();
+        let schema_new_property_value =
+            InputPropertyValue::<Runtime>::vec_reference(vec![FIRST_ENTITY_ID, FIRST_ENTITY_ID]);
+
+        schema_new_property_values.insert(FIRST_PROPERTY_ID, schema_new_property_value);
+
+        // Make an attempt to update entity property values using unknown origin and curator actor
+        let update_entity_property_values_result = update_entity_property_values(
+            UNKNOWN_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            schema_new_property_values,
+        );
+
+        // Failure checked
+        assert_failure(
+            update_entity_property_values_result,
+            Error::<Runtime>::CuratorAuthFailed,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn update_entity_property_values_curator_not_found_in_curator_group() {
+    with_test_externalities(|| {
+        let actor = emulate_entity_access_state_for_failure_case(
+            EntityAccessStateFailureType::CuratorNotFoundInCuratorGroup,
+        );
+
+        // Create class reference schema and add corresponding schema support to the Entity
+        add_unique_class_reference_schema_and_entity_schema_support(
+            &Actor::Curator(FIRST_CURATOR_GROUP_ID, FIRST_CURATOR_ID),
+            FIRST_CURATOR_ORIGIN,
+        );
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let mut schema_new_property_values = BTreeMap::new();
+        let schema_new_property_value =
+            InputPropertyValue::<Runtime>::vec_reference(vec![FIRST_ENTITY_ID, FIRST_ENTITY_ID]);
+
+        schema_new_property_values.insert(FIRST_PROPERTY_ID, schema_new_property_value);
+
+        // Make an attempt to update entity property values using actor in group, which curator id was not added to corresponding group set
+        let update_entity_property_values_result = update_entity_property_values(
+            SECOND_CURATOR_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            schema_new_property_values,
+        );
+
+        // Failure checked
+        assert_failure(
+            update_entity_property_values_result,
+            Error::<Runtime>::CuratorIsNotAMemberOfGivenCuratorGroup,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn update_entity_property_values_entity_access_denied() {
+    with_test_externalities(|| {
+        let actor = emulate_entity_access_state_for_failure_case(
+            EntityAccessStateFailureType::EntityAccessDenied,
+        );
+
+        // Create class reference schema and add corresponding schema support to the Entity
+        add_unique_class_reference_schema_and_entity_schema_support(&Actor::Lead, LEAD_ORIGIN);
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let mut schema_new_property_values = BTreeMap::new();
+        let schema_new_property_value =
+            InputPropertyValue::<Runtime>::vec_reference(vec![FIRST_ENTITY_ID, FIRST_ENTITY_ID]);
+
+        schema_new_property_values.insert(FIRST_PROPERTY_ID, schema_new_property_value);
+
+        // Make an attempt to update entity property values using actor in group,
+        // using origin, which corresponding actor is neither entity maintainer, nor controller.
+        let update_entity_property_values_result = update_entity_property_values(
+            SECOND_CURATOR_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            schema_new_property_values,
+        );
+
+        // Failure checked
+        assert_failure(
+            update_entity_property_values_result,
+            Error::<Runtime>::EntityAccessDenied,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn update_entity_property_values_locked_on_class_level() {
+    with_test_externalities(|| {
+        let actor = emulate_entity_access_state_for_failure_case(
+            EntityAccessStateFailureType::PropertyValuesLocked,
+        );
+
+        // Create class reference schema and add corresponding schema support to the Entity
+        add_unique_class_reference_schema_and_entity_schema_support(&Actor::Lead, LEAD_ORIGIN);
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let mut schema_new_property_values = BTreeMap::new();
+        let schema_new_property_value =
+            InputPropertyValue::<Runtime>::vec_reference(vec![FIRST_ENTITY_ID, FIRST_ENTITY_ID]);
+
+        schema_new_property_values.insert(FIRST_PROPERTY_ID, schema_new_property_value);
+
+        // Make an attempt to update entity property values using actor in group,
+        // in the case, when all property values were locked on Class level.
+        let update_entity_property_values_result = update_entity_property_values(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            schema_new_property_values,
+        );
+
+        // Failure checked
+        assert_failure(
+            update_entity_property_values_result,
+            Error::<Runtime>::AllPropertiesWereLockedOnClassLevel,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn update_entity_property_values_is_locked_for_given_actor() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Create property
+        let property_type = PropertyType::<Runtime>::vec_reference(FIRST_CLASS_ID, true, 5);
+
+        let mut property = Property::<Runtime>::with_name_and_type(
+            (PropertyNameLengthConstraint::get().max() - 1) as usize,
+            property_type,
+            true,
+            false,
+        );
+
+        property.locking_policy = PropertyLockingPolicy::new(false, true);
+
+        // Add Schema to the Class
+        assert_ok!(add_class_schema(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            BTreeSet::new(),
+            vec![property]
+        ));
+
+        let actor = Actor::Lead;
+
+        // Create second entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        let schema_property_value = InputPropertyValue::<Runtime>::vec_reference(vec![
+            FIRST_ENTITY_ID,
+            FIRST_ENTITY_ID,
+            FIRST_ENTITY_ID,
+        ]);
+
+        let mut schema_property_values = BTreeMap::new();
+        schema_property_values.insert(FIRST_PROPERTY_ID, schema_property_value);
+
+        // Add schema support to the entity
+        assert_ok!(add_schema_support_to_entity(
+            LEAD_ORIGIN,
+            actor.clone(),
+            FIRST_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            schema_property_values
+        ));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let mut schema_new_property_values = BTreeMap::new();
+        let schema_new_property_value =
+            InputPropertyValue::<Runtime>::vec_reference(vec![FIRST_ENTITY_ID, FIRST_ENTITY_ID]);
+
+        schema_new_property_values.insert(FIRST_PROPERTY_ID, schema_new_property_value);
+
+        // Make an attempt to update entity property values
+        // under lead origin, which is current Entity controller, in the case,
+        // when corresponding class Property was locked from controller on Class level
+        let update_entity_property_values_result = update_entity_property_values(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            schema_new_property_values,
+        );
+
+        // Failure checked
+        assert_failure(
+            update_entity_property_values_result,
+            Error::<Runtime>::ClassPropertyTypeLockedForGivenActor,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn update_entity_property_values_unknown_entity_property_id() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        let actor = Actor::Lead;
+
+        // Create entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        // Create class reference schema and add corresponding schema support to the Entity
+        add_unique_class_reference_schema_and_entity_schema_support(&actor, LEAD_ORIGIN);
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let mut schema_new_property_values = BTreeMap::new();
+        let schema_new_property_value =
+            InputPropertyValue::<Runtime>::vec_reference(vec![FIRST_ENTITY_ID, FIRST_ENTITY_ID]);
+
+        schema_new_property_values.insert(SECOND_PROPERTY_ID, schema_new_property_value);
+
+        // Make an attempt to update entity property values in the case, when Property
+        // under corresponding property id was not added to the current Entity yet
+        let update_entity_property_values_result = update_entity_property_values(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            schema_new_property_values,
+        );
+
+        // Failure checked
+        assert_failure(
+            update_entity_property_values_result,
+            Error::<Runtime>::UnknownEntityPropertyId,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn update_entity_property_values_prop_value_do_not_match_type() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        let actor = Actor::Lead;
+
+        // Create entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        // Create class reference schema and add corresponding schema support to the Entity
+        add_unique_class_reference_schema_and_entity_schema_support(&actor, LEAD_ORIGIN);
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let mut schema_new_property_values = BTreeMap::new();
+        let schema_new_property_value = InputPropertyValue::<Runtime>::default();
+
+        schema_new_property_values.insert(FIRST_PROPERTY_ID, schema_new_property_value);
+
+        // Make an attempt to update entity property values in the case, providing property values,
+        // some of which do not match Class level Property Type
+        let update_entity_property_values_result = update_entity_property_values(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            schema_new_property_values,
+        );
+
+        // Failure checked
+        assert_failure(
+            update_entity_property_values_result,
+            Error::<Runtime>::PropertyValueDoNotMatchType,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn update_entity_property_values_vec_prop_is_too_long() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        let actor = Actor::Lead;
+
+        // Create entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        // Create class reference schema and add corresponding schema support to the Entity
+        add_unique_class_reference_schema_and_entity_schema_support(&actor, LEAD_ORIGIN);
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let mut schema_new_property_values = BTreeMap::new();
+        let schema_new_property_value = InputPropertyValue::<Runtime>::vec_reference(vec![
+                FIRST_ENTITY_ID;
+                VecMaxLengthConstraint::get() as usize + 1
+            ]);
+
+        schema_new_property_values.insert(FIRST_PROPERTY_ID, schema_new_property_value);
+
+        // Make an attempt to update entity property values providing vector property value(s), which
+        // length exceeds VecMaxLengthConstraint.
+        let update_entity_property_values_result = update_entity_property_values(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            schema_new_property_values,
+        );
+
+        // Failure checked
+        assert_failure(
+            update_entity_property_values_result,
+            Error::<Runtime>::VecPropertyTooLong,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn update_entity_property_values_text_prop_is_too_long() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        let actor = Actor::Lead;
+
+        // Create entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.to_owned()));
+
+        // Create text property
+        let property_type = PropertyType::<Runtime>::single_text(TextMaxLengthConstraint::get());
+
+        let property = Property::<Runtime>::with_name_and_type(
+            PropertyNameLengthConstraint::get().max() as usize,
+            property_type,
+            true,
+            false,
+        );
+
+        // Add Schema to the Class
+        assert_ok!(add_class_schema(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            BTreeSet::new(),
+            vec![property]
+        ));
+
+        let mut schema_property_values = BTreeMap::new();
+
+        let schema_property_value =
+            InputPropertyValue::<Runtime>::single_text(TextMaxLengthConstraint::get());
+
+        schema_property_values.insert(FIRST_PROPERTY_ID, schema_property_value);
+
+        // Add schema support to the entity
+        assert_ok!(add_schema_support_to_entity(
+            LEAD_ORIGIN,
+            actor.to_owned(),
+            FIRST_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            schema_property_values
+        ));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let mut schema_new_property_values = BTreeMap::new();
+        let schema_new_property_value =
+            InputPropertyValue::<Runtime>::single_text(TextMaxLengthConstraint::get() + 1);
+
+        schema_new_property_values.insert(FIRST_PROPERTY_ID, schema_new_property_value);
+
+        // Make an attempt to update entity property values providing text property value(s), which
+        // length exceeds TextMaxLengthConstraint.
+        let update_entity_property_values_result = update_entity_property_values(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            schema_new_property_values,
+        );
+
+        // Failure checked
+        assert_failure(
+            update_entity_property_values_result,
+            Error::<Runtime>::TextPropertyTooLong,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn update_entity_property_values_hashed_text_prop_is_too_long() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        let actor = Actor::Lead;
+
+        // Create entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.to_owned()));
+
+        let hashed_text_max_length_constraint = HashedTextMaxLengthConstraint::get();
+
+        // Create hash property
+        let property_type =
+            PropertyType::<Runtime>::single_text_hash(hashed_text_max_length_constraint);
+
+        let property = Property::<Runtime>::with_name_and_type(
+            PropertyNameLengthConstraint::get().max() as usize,
+            property_type,
+            true,
+            false,
+        );
+
+        // Add Schema to the first Class
+        assert_ok!(add_class_schema(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            BTreeSet::new(),
+            vec![property]
+        ));
+
+        let mut schema_property_values = BTreeMap::new();
+
+        let schema_property_value = InputPropertyValue::<Runtime>::single_text_to_hash(
+            hashed_text_max_length_constraint.unwrap(),
+        );
+
+        schema_property_values.insert(FIRST_PROPERTY_ID, schema_property_value);
+
+        // Add schema support to the entity
+        assert_ok!(add_schema_support_to_entity(
+            LEAD_ORIGIN,
+            actor.clone(),
+            FIRST_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            schema_property_values,
+        ));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let mut schema_new_property_values = BTreeMap::new();
+        let schema_new_property_value = InputPropertyValue::<Runtime>::single_text_to_hash(
+            hashed_text_max_length_constraint.unwrap() + 1,
+        );
+
+        schema_new_property_values.insert(FIRST_PROPERTY_ID, schema_new_property_value);
+
+        // Make an attempt to update entity property values providing text to hash property value(s), which
+        // length exceeds length exceeds HashedTextMaxLengthConstraint.
+        let update_entity_property_values_result = update_entity_property_values(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            schema_new_property_values,
+        );
+
+        // Failure checked
+        assert_failure(
+            update_entity_property_values_result,
+            Error::<Runtime>::HashedTextPropertyTooLong,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn update_entity_property_values_referenced_entity_not_found() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        let actor = Actor::Lead;
+
+        // Create entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        // Create class reference schema and add corresponding schema support to the Entity
+        add_unique_class_reference_schema_and_entity_schema_support(&actor, LEAD_ORIGIN);
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let mut schema_new_property_values = BTreeMap::new();
+        let schema_new_property_value = InputPropertyValue::<Runtime>::vec_reference(vec![
+                SECOND_ENTITY_ID;
+                VecMaxLengthConstraint::get() as usize
+            ]);
+
+        schema_new_property_values.insert(FIRST_PROPERTY_ID, schema_new_property_value);
+
+        // Make an attempt to update entity property values, providing new property value(s),
+        // some of which refer(s) to another Entity, which does not exist in runtime
+        let update_entity_property_values_result = update_entity_property_values(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            schema_new_property_values,
+        );
+
+        // Failure checked
+        assert_failure(
+            update_entity_property_values_result,
+            Error::<Runtime>::EntityNotFound,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn update_entity_property_values_referenced_entity_does_not_match_its_class() {
+    with_test_externalities(|| {
+        // Create first class
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Create second class
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        let actor = Actor::Lead;
+
+        // Create first entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        // Create class reference schema and add corresponding schema support to the first Entity
+        add_unique_class_reference_schema_and_entity_schema_support(&actor, LEAD_ORIGIN);
+
+        // Create second entity
+        assert_ok!(create_entity(LEAD_ORIGIN, SECOND_CLASS_ID, actor.clone()));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let mut schema_new_property_values = BTreeMap::new();
+        let schema_new_property_value = InputPropertyValue::<Runtime>::vec_reference(vec![
+                SECOND_ENTITY_ID;
+                VecMaxLengthConstraint::get() as usize
+            ]);
+
+        schema_new_property_values.insert(FIRST_PROPERTY_ID, schema_new_property_value);
+
+        // Make an attempt to update entity property values, when provided schema new property value(s)
+        // refer(s) Entity, which Class does not match the class in corresponding Class Property
+        let update_entity_property_values_result = update_entity_property_values(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            schema_new_property_values,
+        );
+
+        // Failure checked
+        assert_failure(
+            update_entity_property_values_result,
+            Error::<Runtime>::ReferencedEntityDoesNotMatchItsClass,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn update_entity_property_values_entity_can_not_be_referenced() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        let actor = Actor::Lead;
+
+        // Create first Entity of first Class
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        // Create class reference schema and add corresponding schema support to the first Entity
+        add_unique_class_reference_schema_and_entity_schema_support(&actor, LEAD_ORIGIN);
+
+        // Create second Entity of first Class
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        // Update second entity permissions to forbid it from being referencable
+        assert_ok!(update_entity_permissions(
+            LEAD_ORIGIN,
+            SECOND_ENTITY_ID,
+            None,
+            Some(false)
+        ));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let mut schema_new_property_values = BTreeMap::new();
+        let schema_new_property_value = InputPropertyValue::<Runtime>::vec_reference(vec![
+                SECOND_ENTITY_ID;
+                VecMaxLengthConstraint::get() as usize
+            ]);
+
+        schema_new_property_values.insert(FIRST_PROPERTY_ID, schema_new_property_value);
+
+        // Make an attempt to update entity property values, when provided schema new property value(s)
+        // refer(s) to Entity which can not be referenced
+        let update_entity_property_values_result = update_entity_property_values(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            schema_new_property_values,
+        );
+
+        // Failure checked
+        assert_failure(
+            update_entity_property_values_result,
+            Error::<Runtime>::EntityCanNotBeReferenced,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn update_entity_property_values_same_controller_constraint_violation() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        // Update class permissions to force any member be available to create Entities
+        assert_ok!(update_class_permissions(
+            LEAD_ORIGIN,
+            FIRST_CLASS_ID,
+            Some(true),
+            None,
+            None,
+            None
+        ));
+
+        let actor = Actor::Lead;
+
+        // Create first Entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        // Create class reference schema and add corresponding schema support to the first  Entity
+        add_unique_class_reference_schema_and_entity_schema_support(&actor, LEAD_ORIGIN);
+
+        // Create second Entity
+        assert_ok!(create_entity(
+            FIRST_MEMBER_ORIGIN,
+            FIRST_CLASS_ID,
+            Actor::Member(FIRST_MEMBER_ID)
+        ));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let mut schema_new_property_values = BTreeMap::new();
+        let schema_new_property_value = InputPropertyValue::<Runtime>::vec_reference(vec![
+                SECOND_ENTITY_ID;
+                VecMaxLengthConstraint::get() as usize
+            ]);
+
+        schema_new_property_values.insert(FIRST_PROPERTY_ID, schema_new_property_value);
+
+        // Make an attempt to update entity property values, providing new reference property value(s) in case,
+        // when corresponding Entity can only be referenced from Entity with the same controller.
+        let update_entity_property_values_result = update_entity_property_values(
+            LEAD_ORIGIN,
+            actor,
+            FIRST_ENTITY_ID,
+            schema_new_property_values,
+        );
+
+        // Failure checked
+        assert_failure(
+            update_entity_property_values_result,
+            Error::<Runtime>::SameControllerConstraintViolation,
+            number_of_events_before_call,
+        );
+    })
+}
+
+#[test]
+fn update_entity_property_values_property_should_be_unique() {
+    with_test_externalities(|| {
+        // Create class with default permissions
+        assert_ok!(create_simple_class(LEAD_ORIGIN, ClassType::Valid));
+
+        let actor = Actor::Lead;
+
+        // Create first Entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        // Create Class reference schema and add corresponding schema support to the first Entity
+        add_unique_class_reference_schema_and_entity_schema_support(&actor, LEAD_ORIGIN);
+
+        // Create second Entity
+        assert_ok!(create_entity(LEAD_ORIGIN, FIRST_CLASS_ID, actor.clone()));
+
+        let schema_property_value = InputPropertyValue::<Runtime>::vec_reference(vec![
+            FIRST_ENTITY_ID,
+            FIRST_ENTITY_ID,
+            FIRST_ENTITY_ID,
+        ]);
+
+        let mut schema_property_values = BTreeMap::new();
+        schema_property_values.insert(FIRST_PROPERTY_ID, schema_property_value);
+
+        // Add schema support to the entity
+        assert_ok!(add_schema_support_to_entity(
+            LEAD_ORIGIN,
+            actor.to_owned(),
+            SECOND_ENTITY_ID,
+            FIRST_SCHEMA_ID,
+            schema_property_values
+        ));
+
+        // Runtime state before tested call
+
+        // Events number before tested call
+        let number_of_events_before_call = System::events().len();
+
+        let mut schema_new_property_values = BTreeMap::new();
+        let schema_new_property_value =
+            InputPropertyValue::<Runtime>::vec_reference(vec![FIRST_ENTITY_ID, FIRST_ENTITY_ID]);
+
+        schema_new_property_values.insert(FIRST_PROPERTY_ID, schema_new_property_value);
+
+        // Make an attempt to update entity property values, providing property value(s), which are identical to thouse,
+        // are already added to the another Entity of this Class, though should be unique on Class Property level
+        let update_entity_property_values_result = update_entity_property_values(
+            LEAD_ORIGIN,
+            actor,
+            SECOND_ENTITY_ID,
+            schema_new_property_values,
+        );
+
+        // Failure checked
+        assert_failure(
+            update_entity_property_values_result,
+            Error::<Runtime>::PropertyValueShouldBeUnique,
+            number_of_events_before_call,
+        );
+    })
+}

+ 1 - 1
runtime-modules/governance/src/mock.rs

@@ -71,7 +71,7 @@ impl election::Trait for Test {
 }
 impl membership::Trait for Test {
     type Event = ();
-    type MemberId = u32;
+    type MemberId = u64;
     type SubscriptionId = u32;
     type PaidTermId = u32;
     type ActorId = u32;

+ 1 - 1
runtime-modules/membership/src/mock.rs

@@ -80,7 +80,7 @@ impl GovernanceCurrency for Test {
 
 impl Trait for Test {
     type Event = ();
-    type MemberId = u32;
+    type MemberId = u64;
     type PaidTermId = u32;
     type SubscriptionId = u32;
     type ActorId = u32;

+ 1 - 1
runtime-modules/membership/src/tests.rs

@@ -5,7 +5,7 @@ use super::mock::*;
 
 use frame_support::*;
 
-fn get_membership_by_id(member_id: u32) -> crate::Membership<Test> {
+fn get_membership_by_id(member_id: u64) -> crate::Membership<Test> {
     if <crate::MembershipById<Test>>::contains_key(member_id) {
         Members::membership(member_id)
     } else {

+ 1 - 4
runtime-modules/proposals/codex/Cargo.toml

@@ -21,7 +21,6 @@ governance = { package = 'pallet-governance', default-features = false, path = '
 hiring = { package = 'pallet-hiring', default-features = false, path = '../../hiring'}
 minting = { package = 'pallet-token-mint', default-features = false, path = '../../token-minting'}
 working-group = { package = 'pallet-working-group', default-features = false, path = '../../working-group'}
-content-working-group = { package = 'pallet-content-working-group', default-features = false, path = '../../content-working-group'}
 common = { package = 'pallet-common', default-features = false, path = '../../common'}
 proposals-engine = { package = 'pallet-proposals-engine', default-features = false, path = '../engine'}
 proposals-discussion = { package = 'pallet-proposals-discussion', default-features = false, path = '../discussion'}
@@ -32,8 +31,7 @@ sp-core = { package = 'sp-core', default-features = false, git = 'https://github
 sp-staking = { package = 'sp-staking', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
 pallet-staking-reward-curve = { package = 'pallet-staking-reward-curve', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
 recurring-rewards = { package = 'pallet-recurring-reward', default-features = false, path = '../../recurring-reward'}
-versioned-store = { package = 'pallet-versioned-store', default-features = false, path = '../../versioned-store'}
-versioned-store-permissions = { package = 'pallet-versioned-store-permissions', default-features = false, path = '../../versioned-store-permissions'}
+strum = {version = "0.19", default-features = false}
 
 [features]
 default = ['std']
@@ -53,7 +51,6 @@ std = [
     'governance/std',
     'hiring/std',
     'minting/std',
-    'content-working-group/std',
     'working-group/std',
     'common/std',
     'proposals-engine/std',

+ 1 - 99
runtime-modules/proposals/codex/src/lib.rs

@@ -21,10 +21,6 @@
 //! - [create_set_election_parameters_proposal](./struct.Module.html#method.create_set_election_parameters_proposal)
 //! - [create_spending_proposal](./struct.Module.html#method.create_spending_proposal)
 //!
-//! ### Content working group proposals
-//! - [create_set_lead_proposal](./struct.Module.html#method.create_set_lead_proposal)
-//! - [create_set_content_working_group_mint_capacity_proposal](./struct.Module.html#method.create_set_content_working_group_mint_capacity_proposal)
-//!
 //! ### Working group proposals
 //! - [create_add_working_group_leader_opening_proposal](./struct.Module.html#method.create_add_working_group_leader_opening_proposal)
 //! - [create_begin_review_working_group_leader_applications_proposal](./struct.Module.html#method.create_begin_review_working_group_leader_applications_proposal)
@@ -86,8 +82,6 @@ pub use proposal_types::{ProposalDetails, ProposalDetailsOf, ProposalEncoder};
 
 // 'Set working group mint capacity' proposal limit
 const WORKING_GROUP_MINT_CAPACITY_MAX_VALUE: u32 = 5_000_000;
-// 'Set content working group mint capacity' proposal limit
-const CONTENT_WORKING_GROUP_MINT_CAPACITY_MAX_VALUE: u32 = 1_000_000;
 // Max allowed value for 'spending' proposal
 const MAX_SPENDING_PROPOSAL_VALUE: u32 = 5_000_000_u32;
 // Max validator count for the 'set validator count' proposal
@@ -145,7 +139,7 @@ pub trait Trait:
     + proposals_discussion::Trait
     + membership::Trait
     + governance::election::Trait
-    + content_working_group::Trait
+    + hiring::Trait
     + staking::Trait
 {
     /// Defines max allowed text proposal length.
@@ -294,22 +288,6 @@ decl_storage! {
         /// Grace period for the 'text' proposal
         pub TextProposalGracePeriod get(fn text_proposal_grace_period) config(): T::BlockNumber;
 
-        /// Voting period for the 'set content working group mint capacity' proposal
-        pub SetContentWorkingGroupMintCapacityProposalVotingPeriod get(fn set_content_working_group_mint_capacity_proposal_voting_period)
-            config(): T::BlockNumber;
-
-        /// Grace period for the 'set content working group mint capacity' proposal
-        pub SetContentWorkingGroupMintCapacityProposalGracePeriod get(fn set_content_working_group_mint_capacity_proposal_grace_period)
-            config(): T::BlockNumber;
-
-        /// Voting period for the 'set lead' proposal
-        pub SetLeadProposalVotingPeriod get(fn set_lead_proposal_voting_period)
-            config(): T::BlockNumber;
-
-        /// Grace period for the 'set lead' proposal
-        pub SetLeadProposalGracePeriod get(fn set_lead_proposal_grace_period)
-            config(): T::BlockNumber;
-
         /// Voting period for the 'spending' proposal
         pub SpendingProposalVotingPeriod get(fn spending_proposal_voting_period) config(): T::BlockNumber;
 
@@ -477,37 +455,6 @@ decl_module! {
             Self::create_proposal(params)?;
         }
 
-        /// Create 'Set content working group mint capacity' proposal type.
-        /// This proposal uses `set_mint_capacity()` extrinsic from the `content-working-group`  module.
-        #[weight = 10_000_000] // TODO: adjust weight
-        pub fn create_set_content_working_group_mint_capacity_proposal(
-            origin,
-            member_id: MemberId<T>,
-            title: Vec<u8>,
-            description: Vec<u8>,
-            stake_balance: Option<BalanceOf<T>>,
-            mint_balance: BalanceOfMint<T>,
-        ) {
-            ensure!(
-                mint_balance <= <BalanceOfMint<T>>::from(CONTENT_WORKING_GROUP_MINT_CAPACITY_MAX_VALUE),
-                Error::<T>::InvalidContentWorkingGroupMintCapacity
-            );
-
-            let proposal_details = ProposalDetails::SetContentWorkingGroupMintCapacity(mint_balance);
-            let params = CreateProposalParameters{
-                origin,
-                member_id,
-                title,
-                description,
-                stake_balance,
-                proposal_details: proposal_details.clone(),
-                proposal_parameters: proposal_types::parameters::set_content_working_group_mint_capacity_proposal::<T>(),
-                proposal_code: T::ProposalEncoder::encode_proposal(proposal_details)
-            };
-
-            Self::create_proposal(params)?;
-        }
-
         /// Create 'Spending' proposal type.
         /// This proposal uses `spend_from_council_mint()` extrinsic from the `governance::council`  module.
         #[weight = 10_000_000] // TODO: adjust weight
@@ -541,39 +488,6 @@ decl_module! {
             Self::create_proposal(params)?;
         }
 
-        /// Create 'Set lead' proposal type.
-        /// This proposal uses `replace_lead()` extrinsic from the `content_working_group`  module.
-        #[weight = 10_000_000] // TODO: adjust weight
-        pub fn create_set_lead_proposal(
-            origin,
-            member_id: MemberId<T>,
-            title: Vec<u8>,
-            description: Vec<u8>,
-            stake_balance: Option<BalanceOf<T>>,
-            new_lead: Option<(T::MemberId, T::AccountId)>
-        ) {
-            if let Some(lead) = new_lead.clone() {
-                let account_id = lead.1;
-                ensure!(
-                    !<governance::council::Module<T>>::is_councilor(&account_id),
-                    Error::<T>::InvalidSetLeadParameterCannotBeCouncilor
-                );
-            }
-            let proposal_details = ProposalDetails::SetLead(new_lead);
-            let params = CreateProposalParameters{
-                origin,
-                member_id,
-                title,
-                description,
-                stake_balance,
-                proposal_details: proposal_details.clone(),
-                proposal_parameters: proposal_types::parameters::set_lead_proposal::<T>(),
-                proposal_code: T::ProposalEncoder::encode_proposal(proposal_details)
-            };
-
-            Self::create_proposal(params)?;
-        }
-
         /// Create 'Evict storage provider' proposal type.
         /// This proposal uses `set_validator_count()` extrinsic from the Substrate `staking`  module.
         #[weight = 10_000_000] // TODO: adjust weight
@@ -1063,18 +977,6 @@ impl<T: Trait> Module<T> {
         <SetElectionParametersProposalGracePeriod<T>>::put(T::BlockNumber::from(
             p.set_election_parameters_proposal_grace_period,
         ));
-        <SetContentWorkingGroupMintCapacityProposalVotingPeriod<T>>::put(T::BlockNumber::from(
-            p.set_content_working_group_mint_capacity_proposal_voting_period,
-        ));
-        <SetContentWorkingGroupMintCapacityProposalGracePeriod<T>>::put(T::BlockNumber::from(
-            p.set_content_working_group_mint_capacity_proposal_grace_period,
-        ));
-        <SetLeadProposalVotingPeriod<T>>::put(T::BlockNumber::from(
-            p.set_lead_proposal_voting_period,
-        ));
-        <SetLeadProposalGracePeriod<T>>::put(T::BlockNumber::from(
-            p.set_lead_proposal_grace_period,
-        ));
         <SpendingProposalVotingPeriod<T>>::put(T::BlockNumber::from(
             p.spending_proposal_voting_period,
         ));

+ 0 - 20
runtime-modules/proposals/codex/src/proposal_types/mod.rs

@@ -22,7 +22,6 @@ pub type ProposalDetailsOf<T> = ProposalDetails<
     crate::BalanceOfGovernanceCurrency<T>,
     <T as system::Trait>::BlockNumber,
     <T as system::Trait>::AccountId,
-    crate::MemberId<T>,
     working_group::OpeningId<T>,
     working_group::ApplicationId<T>,
     crate::BalanceOf<T>,
@@ -37,7 +36,6 @@ pub enum ProposalDetails<
     CurrencyBalance,
     BlockNumber,
     AccountId,
-    MemberId,
     OpeningId,
     ApplicationId,
     StakeBalance,
@@ -55,12 +53,6 @@ pub enum ProposalDetails<
     /// Balance and destination account for the `spending` proposal
     Spending(MintedBalance, AccountId),
 
-    /// New leader memberId and account_id for the `set lead` proposal
-    SetLead(Option<(MemberId, AccountId)>),
-
-    /// Balance for the `set content working group mint capacity` proposal
-    SetContentWorkingGroupMintCapacity(MintedBalance),
-
     /// ********** Deprecated during the Nicaea release.
     /// It is kept only for backward compatibility in the Pioneer. **********
     /// AccountId for the `evict storage provider` proposal
@@ -106,7 +98,6 @@ impl<
         CurrencyBalance,
         BlockNumber,
         AccountId,
-        MemberId,
         OpeningId,
         ApplicationId,
         StakeBalance,
@@ -117,7 +108,6 @@ impl<
         CurrencyBalance,
         BlockNumber,
         AccountId,
-        MemberId,
         OpeningId,
         ApplicationId,
         StakeBalance,
@@ -246,12 +236,6 @@ pub struct ProposalsConfigParameters {
     /// 'Set election parameters' proposal grace period
     pub set_election_parameters_proposal_grace_period: u32,
 
-    /// 'Set content working group mint capacity' proposal voting period
-    pub set_content_working_group_mint_capacity_proposal_voting_period: u32,
-
-    /// 'Set content working group mint capacity' proposal grace period
-    pub set_content_working_group_mint_capacity_proposal_grace_period: u32,
-
     /// 'Set lead' proposal voting period
     pub set_lead_proposal_voting_period: u32,
 
@@ -324,8 +308,6 @@ impl Default for ProposalsConfigParameters {
             text_proposal_grace_period: 0u32,
             set_election_parameters_proposal_voting_period: 72000u32,
             set_election_parameters_proposal_grace_period: 201_601_u32,
-            set_content_working_group_mint_capacity_proposal_voting_period: 43200u32,
-            set_content_working_group_mint_capacity_proposal_grace_period: 0u32,
             set_lead_proposal_voting_period: 43200u32,
             set_lead_proposal_grace_period: 0u32,
             spending_proposal_voting_period: 72000u32,
@@ -364,8 +346,6 @@ impl ProposalsConfigParameters {
             text_proposal_grace_period: 0,
             set_election_parameters_proposal_voting_period: voting_period,
             set_election_parameters_proposal_grace_period: grace_period,
-            set_content_working_group_mint_capacity_proposal_voting_period: voting_period,
-            set_content_working_group_mint_capacity_proposal_grace_period: 0,
             set_lead_proposal_voting_period: voting_period,
             set_lead_proposal_grace_period: 0,
             spending_proposal_voting_period: voting_period,

+ 0 - 29
runtime-modules/proposals/codex/src/proposal_types/parameters.rs

@@ -55,21 +55,6 @@ pub(crate) fn set_election_parameters_proposal<T: crate::Trait>(
     }
 }
 
-// Proposal parameters for the 'Set content working group mint capacity' proposal
-pub(crate) fn set_content_working_group_mint_capacity_proposal<T: crate::Trait>(
-) -> ProposalParameters<T::BlockNumber, BalanceOf<T>> {
-    ProposalParameters {
-        voting_period: <Module<T>>::set_content_working_group_mint_capacity_proposal_voting_period(
-        ),
-        grace_period: <Module<T>>::set_content_working_group_mint_capacity_proposal_grace_period(),
-        approval_quorum_percentage: 60,
-        approval_threshold_percentage: 75,
-        slashing_quorum_percentage: 60,
-        slashing_threshold_percentage: 80,
-        required_stake: Some(<BalanceOf<T>>::from(50000u32)),
-    }
-}
-
 // Proposal parameters for the 'Spending' proposal
 pub(crate) fn spending_proposal<T: crate::Trait>(
 ) -> ProposalParameters<T::BlockNumber, BalanceOf<T>> {
@@ -84,20 +69,6 @@ pub(crate) fn spending_proposal<T: crate::Trait>(
     }
 }
 
-// Proposal parameters for the 'Set lead' proposal
-pub(crate) fn set_lead_proposal<T: crate::Trait>(
-) -> ProposalParameters<T::BlockNumber, BalanceOf<T>> {
-    ProposalParameters {
-        voting_period: <Module<T>>::set_lead_proposal_voting_period(),
-        grace_period: <Module<T>>::set_lead_proposal_grace_period(),
-        approval_quorum_percentage: 60,
-        approval_threshold_percentage: 75,
-        slashing_quorum_percentage: 60,
-        slashing_threshold_percentage: 80,
-        required_stake: Some(<BalanceOf<T>>::from(50000u32)),
-    }
-}
-
 // Proposal parameters for the 'Add working group leader' proposal
 pub(crate) fn add_working_group_leader_opening_proposal<T: crate::Trait>(
 ) -> ProposalParameters<T::BlockNumber, BalanceOf<T>> {

+ 17 - 11
runtime-modules/proposals/codex/src/tests/mock.rs

@@ -154,8 +154,24 @@ impl governance::election::Trait for Test {
     type CouncilElected = ();
 }
 
-impl content_working_group::Trait for Test {
+// The content directory working group instance alias.
+pub type ContentDirectoryWorkingGroupInstance = working_group::Instance3;
+
+// The storage working group instance alias.
+pub type StorageWorkingGroupInstance = working_group::Instance2;
+
+parameter_types! {
+    pub const MaxWorkerNumberLimit: u32 = 100;
+}
+
+impl working_group::Trait<ContentDirectoryWorkingGroupInstance> for Test {
     type Event = ();
+    type MaxWorkerNumberLimit = MaxWorkerNumberLimit;
+}
+
+impl working_group::Trait<StorageWorkingGroupInstance> for Test {
+    type Event = ();
+    type MaxWorkerNumberLimit = MaxWorkerNumberLimit;
 }
 
 impl recurring_rewards::Trait for Test {
@@ -164,16 +180,6 @@ impl recurring_rewards::Trait for Test {
     type RewardRelationshipId = u64;
 }
 
-impl versioned_store_permissions::Trait for Test {
-    type Credential = u64;
-    type CredentialChecker = ();
-    type CreateClassPermissionsChecker = ();
-}
-
-impl versioned_store::Trait for Test {
-    type Event = ();
-}
-
 impl hiring::Trait for Test {
     type OpeningId = u64;
     type ApplicationId = u64;

+ 131 - 206
runtime-modules/proposals/codex/src/tests/mod.rs

@@ -16,6 +16,8 @@ use crate::*;
 use crate::{BalanceOf, Error, ProposalDetails};
 pub use mock::*;
 
+use strum::IntoEnumIterator;
+
 pub(crate) fn increase_total_balance_issuance(balance: u64) {
     increase_total_balance_issuance_using_account_id(999, balance);
 }
@@ -40,7 +42,7 @@ where
     invalid_stake_call: InvalidStakeCall,
     successful_call: SuccessfulCall,
     proposal_parameters: ProposalParameters<u64, u64>,
-    proposal_details: ProposalDetails<u64, u64, u64, u64, u64, u64, u64, u64, u64>,
+    proposal_details: ProposalDetails<u64, u64, u64, u64, u64, u64, u64, u64>,
 }
 
 impl<InsufficientRightsCall, EmptyStakeCall, InvalidStakeCall, SuccessfulCall>
@@ -471,78 +473,6 @@ fn create_set_election_parameters_call_fails_with_incorrect_parameters() {
     });
 }
 
-#[test]
-fn create_content_working_group_mint_capacity_proposal_fails_with_invalid_parameters() {
-    initial_test_ext().execute_with(|| {
-        increase_total_balance_issuance_using_account_id(1, 500000);
-
-        assert_eq!(
-            ProposalCodex::create_set_content_working_group_mint_capacity_proposal(
-                RawOrigin::Signed(1).into(),
-                1,
-                b"title".to_vec(),
-                b"body".to_vec(),
-                Some(<BalanceOf<Test>>::from(50000u32)),
-                (crate::CONTENT_WORKING_GROUP_MINT_CAPACITY_MAX_VALUE + 1) as u64,
-            ),
-            Err(Error::<Test>::InvalidContentWorkingGroupMintCapacity.into())
-        );
-    });
-}
-
-#[test]
-fn create_set_content_working_group_mint_capacity_proposal_common_checks_succeed() {
-    initial_test_ext().execute_with(|| {
-        increase_total_balance_issuance(500000);
-
-        let proposal_fixture = ProposalTestFixture {
-            insufficient_rights_call: || {
-                ProposalCodex::create_set_content_working_group_mint_capacity_proposal(
-                    RawOrigin::None.into(),
-                    1,
-                    b"title".to_vec(),
-                    b"body".to_vec(),
-                    None,
-                    0,
-                )
-            },
-            empty_stake_call: || {
-                ProposalCodex::create_set_content_working_group_mint_capacity_proposal(
-                    RawOrigin::Signed(1).into(),
-                    1,
-                    b"title".to_vec(),
-                    b"body".to_vec(),
-                    None,
-                    0,
-                )
-            },
-            invalid_stake_call: || {
-                ProposalCodex::create_set_content_working_group_mint_capacity_proposal(
-                    RawOrigin::Signed(1).into(),
-                    1,
-                    b"title".to_vec(),
-                    b"body".to_vec(),
-                    Some(<BalanceOf<Test>>::from(5000u32)),
-                    0,
-                )
-            },
-            successful_call: || {
-                ProposalCodex::create_set_content_working_group_mint_capacity_proposal(
-                    RawOrigin::Signed(1).into(),
-                    1,
-                    b"title".to_vec(),
-                    b"body".to_vec(),
-                    Some(<BalanceOf<Test>>::from(50000u32)),
-                    10,
-                )
-            },
-            proposal_parameters: crate::proposal_types::parameters::set_content_working_group_mint_capacity_proposal::<Test>(),
-            proposal_details: ProposalDetails::SetContentWorkingGroupMintCapacity(10),
-        };
-        proposal_fixture.check_all();
-    });
-}
-
 #[test]
 fn create_spending_proposal_common_checks_succeed() {
     initial_test_ext().execute_with(|| {
@@ -633,85 +563,6 @@ fn create_spending_proposal_call_fails_with_incorrect_balance() {
     });
 }
 
-#[test]
-fn create_set_lead_proposal_fails_with_proposed_councilor() {
-    initial_test_ext().execute_with(|| {
-        increase_total_balance_issuance_using_account_id(1, 500000);
-
-        let lead_account_id = 20;
-        <governance::council::Module<Test>>::set_council(
-            RawOrigin::Root.into(),
-            vec![lead_account_id],
-        )
-        .unwrap();
-
-        assert_eq!(
-            ProposalCodex::create_set_lead_proposal(
-                RawOrigin::Signed(1).into(),
-                1,
-                b"title".to_vec(),
-                b"body".to_vec(),
-                Some(<BalanceOf<Test>>::from(1250u32)),
-                Some((20, lead_account_id)),
-            ),
-            Err(Error::<Test>::InvalidSetLeadParameterCannotBeCouncilor.into())
-        );
-    });
-}
-
-#[test]
-fn create_set_lead_proposal_common_checks_succeed() {
-    initial_test_ext().execute_with(|| {
-        increase_total_balance_issuance(500000);
-
-        let proposal_fixture = ProposalTestFixture {
-            insufficient_rights_call: || {
-                ProposalCodex::create_set_lead_proposal(
-                    RawOrigin::None.into(),
-                    1,
-                    b"title".to_vec(),
-                    b"body".to_vec(),
-                    None,
-                    Some((20, 10)),
-                )
-            },
-            empty_stake_call: || {
-                ProposalCodex::create_set_lead_proposal(
-                    RawOrigin::Signed(1).into(),
-                    1,
-                    b"title".to_vec(),
-                    b"body".to_vec(),
-                    None,
-                    Some((20, 10)),
-                )
-            },
-            invalid_stake_call: || {
-                ProposalCodex::create_set_lead_proposal(
-                    RawOrigin::Signed(1).into(),
-                    1,
-                    b"title".to_vec(),
-                    b"body".to_vec(),
-                    Some(<BalanceOf<Test>>::from(5000u32)),
-                    Some((20, 10)),
-                )
-            },
-            successful_call: || {
-                ProposalCodex::create_set_lead_proposal(
-                    RawOrigin::Signed(1).into(),
-                    1,
-                    b"title".to_vec(),
-                    b"body".to_vec(),
-                    Some(<BalanceOf<Test>>::from(50000u32)),
-                    Some((20, 10)),
-                )
-            },
-            proposal_parameters: crate::proposal_types::parameters::set_lead_proposal::<Test>(),
-            proposal_details: ProposalDetails::SetLead(Some((20, 10))),
-        };
-        proposal_fixture.check_all();
-    });
-}
-
 #[test]
 fn create_set_validator_count_proposal_common_checks_succeed() {
     initial_test_ext().execute_with(|| {
@@ -838,22 +689,6 @@ fn set_default_proposal_parameters_succeeded() {
             <SetElectionParametersProposalGracePeriod<Test>>::get(),
             p.set_election_parameters_proposal_grace_period as u64
         );
-        assert_eq!(
-            <SetContentWorkingGroupMintCapacityProposalVotingPeriod<Test>>::get(),
-            p.set_content_working_group_mint_capacity_proposal_voting_period as u64
-        );
-        assert_eq!(
-            <SetContentWorkingGroupMintCapacityProposalGracePeriod<Test>>::get(),
-            p.set_content_working_group_mint_capacity_proposal_grace_period as u64
-        );
-        assert_eq!(
-            <SetLeadProposalVotingPeriod<Test>>::get(),
-            p.set_lead_proposal_voting_period as u64
-        );
-        assert_eq!(
-            <SetLeadProposalGracePeriod<Test>>::get(),
-            p.set_lead_proposal_grace_period as u64
-        );
         assert_eq!(
             <SpendingProposalVotingPeriod<Test>>::get(),
             p.spending_proposal_voting_period as u64
@@ -931,12 +766,21 @@ fn set_default_proposal_parameters_succeeded() {
 
 #[test]
 fn create_add_working_group_leader_opening_proposal_common_checks_succeed() {
+    // This uses strum crate for enum iteration
+    for group in WorkingGroup::iter() {
+        run_create_add_working_group_leader_opening_proposal_common_checks_succeed(group);
+    }
+}
+
+fn run_create_add_working_group_leader_opening_proposal_common_checks_succeed(
+    working_group: WorkingGroup,
+) {
     initial_test_ext().execute_with(|| {
         let add_opening_parameters = AddOpeningParameters {
             activate_at: ActivateOpeningAt::CurrentBlock,
             commitment: OpeningPolicyCommitment::default(),
             human_readable_text: b"some text".to_vec(),
-            working_group: WorkingGroup::Storage,
+            working_group,
         };
 
         increase_total_balance_issuance_using_account_id(1, 500000);
@@ -993,6 +837,17 @@ fn create_add_working_group_leader_opening_proposal_common_checks_succeed() {
 
 #[test]
 fn create_begin_review_working_group_leader_applications_proposal_common_checks_succeed() {
+    // This uses strum crate for enum iteration
+    for group in WorkingGroup::iter() {
+        run_create_begin_review_working_group_leader_applications_proposal_common_checks_succeed(
+            group,
+        );
+    }
+}
+
+fn run_create_begin_review_working_group_leader_applications_proposal_common_checks_succeed(
+    working_group: WorkingGroup,
+) {
     initial_test_ext().execute_with(|| {
         let opening_id = 1; // random opening id.
 
@@ -1007,7 +862,7 @@ fn create_begin_review_working_group_leader_applications_proposal_common_checks_
                     b"body".to_vec(),
                     None,
                     opening_id,
-                    WorkingGroup::Storage
+                    working_group
                 )
             },
             empty_stake_call: || {
@@ -1018,7 +873,7 @@ fn create_begin_review_working_group_leader_applications_proposal_common_checks_
                     b"body".to_vec(),
                     None,
                     opening_id,
-                    WorkingGroup::Storage
+                    working_group
                 )
             },
             invalid_stake_call: || {
@@ -1029,7 +884,7 @@ fn create_begin_review_working_group_leader_applications_proposal_common_checks_
                     b"body".to_vec(),
                     Some(<BalanceOf<Test>>::from(5000u32)),
                     opening_id,
-                    WorkingGroup::Storage
+                    working_group
                 )
             },
             successful_call: || {
@@ -1040,14 +895,14 @@ fn create_begin_review_working_group_leader_applications_proposal_common_checks_
                     b"body".to_vec(),
                     Some(<BalanceOf<Test>>::from(25000u32)),
                     opening_id,
-                    WorkingGroup::Storage
+                    working_group
                 )
             },
             proposal_parameters: crate::proposal_types::parameters::begin_review_working_group_leader_applications_proposal::<
                 Test,
             >(),
             proposal_details: ProposalDetails::BeginReviewWorkingGroupLeaderApplications(opening_id,
-                WorkingGroup::Storage),
+                working_group),
         };
         proposal_fixture.check_all();
     });
@@ -1055,6 +910,15 @@ fn create_begin_review_working_group_leader_applications_proposal_common_checks_
 
 #[test]
 fn create_fill_working_group_leader_opening_proposal_common_checks_succeed() {
+    // This uses strum crate for enum iteration
+    for group in WorkingGroup::iter() {
+        run_create_fill_working_group_leader_opening_proposal_common_checks_succeed(group);
+    }
+}
+
+fn run_create_fill_working_group_leader_opening_proposal_common_checks_succeed(
+    working_group: WorkingGroup,
+) {
     initial_test_ext().execute_with(|| {
         let opening_id = 1; // random opening id.
 
@@ -1062,7 +926,7 @@ fn create_fill_working_group_leader_opening_proposal_common_checks_succeed() {
             opening_id,
             successful_application_id: 1,
             reward_policy: None,
-            working_group: WorkingGroup::Storage,
+            working_group,
         };
 
         increase_total_balance_issuance_using_account_id(1, 500000);
@@ -1119,6 +983,15 @@ fn create_fill_working_group_leader_opening_proposal_common_checks_succeed() {
 
 #[test]
 fn create_working_group_mint_capacity_proposal_fails_with_invalid_parameters() {
+    // This uses strum crate for enum iteration
+    for group in WorkingGroup::iter() {
+        run_create_working_group_mint_capacity_proposal_fails_with_invalid_parameters(group);
+    }
+}
+
+fn run_create_working_group_mint_capacity_proposal_fails_with_invalid_parameters(
+    working_group: WorkingGroup,
+) {
     initial_test_ext().execute_with(|| {
         increase_total_balance_issuance_using_account_id(1, 500000);
 
@@ -1130,7 +1003,7 @@ fn create_working_group_mint_capacity_proposal_fails_with_invalid_parameters() {
                 b"body".to_vec(),
                 Some(<BalanceOf<Test>>::from(50000u32)),
                 (crate::WORKING_GROUP_MINT_CAPACITY_MAX_VALUE + 1) as u64,
-                WorkingGroup::Storage,
+                working_group,
             ),
             Err(Error::<Test>::InvalidWorkingGroupMintCapacity.into())
         );
@@ -1139,6 +1012,15 @@ fn create_working_group_mint_capacity_proposal_fails_with_invalid_parameters() {
 
 #[test]
 fn create_set_working_group_mint_capacity_proposal_common_checks_succeed() {
+    // This uses strum crate for enum iteration
+    for group in WorkingGroup::iter() {
+        run_create_set_working_group_mint_capacity_proposal_common_checks_succeed(group);
+    }
+}
+
+fn run_create_set_working_group_mint_capacity_proposal_common_checks_succeed(
+    working_group: WorkingGroup,
+) {
     initial_test_ext().execute_with(|| {
         increase_total_balance_issuance(500000);
 
@@ -1151,7 +1033,7 @@ fn create_set_working_group_mint_capacity_proposal_common_checks_succeed() {
                     b"body".to_vec(),
                     None,
                     0,
-                    WorkingGroup::Storage,
+                    working_group,
                 )
             },
             empty_stake_call: || {
@@ -1162,7 +1044,7 @@ fn create_set_working_group_mint_capacity_proposal_common_checks_succeed() {
                     b"body".to_vec(),
                     None,
                     0,
-                    WorkingGroup::Storage,
+                    working_group,
                 )
             },
             invalid_stake_call: || {
@@ -1173,7 +1055,7 @@ fn create_set_working_group_mint_capacity_proposal_common_checks_succeed() {
                     b"body".to_vec(),
                     Some(<BalanceOf<Test>>::from(5000u32)),
                     0,
-                    WorkingGroup::Storage,
+                    working_group,
                 )
             },
             successful_call: || {
@@ -1184,16 +1066,13 @@ fn create_set_working_group_mint_capacity_proposal_common_checks_succeed() {
                     b"body".to_vec(),
                     Some(<BalanceOf<Test>>::from(50000u32)),
                     10,
-                    WorkingGroup::Storage,
+                    working_group,
                 )
             },
             proposal_parameters:
                 crate::proposal_types::parameters::set_working_group_mint_capacity_proposal::<Test>(
                 ),
-            proposal_details: ProposalDetails::SetWorkingGroupMintCapacity(
-                10,
-                WorkingGroup::Storage,
-            ),
+            proposal_details: ProposalDetails::SetWorkingGroupMintCapacity(10, working_group),
         };
         proposal_fixture.check_all();
     });
@@ -1201,6 +1080,15 @@ fn create_set_working_group_mint_capacity_proposal_common_checks_succeed() {
 
 #[test]
 fn create_decrease_working_group_leader_stake_proposal_common_checks_succeed() {
+    // This uses strum crate for enum iteration
+    for group in WorkingGroup::iter() {
+        run_create_decrease_working_group_leader_stake_proposal_common_checks_succeed(group);
+    }
+}
+
+fn run_create_decrease_working_group_leader_stake_proposal_common_checks_succeed(
+    working_group: WorkingGroup,
+) {
     initial_test_ext().execute_with(|| {
         increase_total_balance_issuance(500000);
 
@@ -1214,7 +1102,7 @@ fn create_decrease_working_group_leader_stake_proposal_common_checks_succeed() {
                     None,
                     0,
                     10,
-                    WorkingGroup::Storage,
+                    working_group,
                 )
             },
             empty_stake_call: || {
@@ -1226,7 +1114,7 @@ fn create_decrease_working_group_leader_stake_proposal_common_checks_succeed() {
                     None,
                     0,
                     10,
-                    WorkingGroup::Storage,
+                    working_group,
                 )
             },
             invalid_stake_call: || {
@@ -1238,7 +1126,7 @@ fn create_decrease_working_group_leader_stake_proposal_common_checks_succeed() {
                     Some(<BalanceOf<Test>>::from(5000u32)),
                     0,
                     10,
-                    WorkingGroup::Storage,
+                    working_group,
                 )
             },
             successful_call: || {
@@ -1250,7 +1138,7 @@ fn create_decrease_working_group_leader_stake_proposal_common_checks_succeed() {
                     Some(<BalanceOf<Test>>::from(50000u32)),
                     10,
                     10,
-                    WorkingGroup::Storage,
+                    working_group,
                 )
             },
             proposal_parameters:
@@ -1260,7 +1148,7 @@ fn create_decrease_working_group_leader_stake_proposal_common_checks_succeed() {
             proposal_details: ProposalDetails::DecreaseWorkingGroupLeaderStake(
                 10,
                 10,
-                WorkingGroup::Storage,
+                working_group,
             ),
         };
         proposal_fixture.check_all();
@@ -1269,6 +1157,15 @@ fn create_decrease_working_group_leader_stake_proposal_common_checks_succeed() {
 
 #[test]
 fn create_slash_working_group_leader_stake_proposal_common_checks_succeed() {
+    // This uses strum crate for enum iteration
+    for group in WorkingGroup::iter() {
+        run_create_slash_working_group_leader_stake_proposal_common_checks_succeed(group);
+    }
+}
+
+fn run_create_slash_working_group_leader_stake_proposal_common_checks_succeed(
+    working_group: WorkingGroup,
+) {
     initial_test_ext().execute_with(|| {
         increase_total_balance_issuance(500000);
 
@@ -1282,7 +1179,7 @@ fn create_slash_working_group_leader_stake_proposal_common_checks_succeed() {
                     None,
                     0,
                     10,
-                    WorkingGroup::Storage,
+                    working_group,
                 )
             },
             empty_stake_call: || {
@@ -1294,7 +1191,7 @@ fn create_slash_working_group_leader_stake_proposal_common_checks_succeed() {
                     None,
                     0,
                     10,
-                    WorkingGroup::Storage,
+                    working_group,
                 )
             },
             invalid_stake_call: || {
@@ -1306,7 +1203,7 @@ fn create_slash_working_group_leader_stake_proposal_common_checks_succeed() {
                     Some(<BalanceOf<Test>>::from(5000u32)),
                     0,
                     10,
-                    WorkingGroup::Storage,
+                    working_group,
                 )
             },
             successful_call: || {
@@ -1318,7 +1215,7 @@ fn create_slash_working_group_leader_stake_proposal_common_checks_succeed() {
                     Some(<BalanceOf<Test>>::from(50000u32)),
                     10,
                     10,
-                    WorkingGroup::Storage,
+                    working_group,
                 )
             },
             proposal_parameters:
@@ -1328,7 +1225,7 @@ fn create_slash_working_group_leader_stake_proposal_common_checks_succeed() {
             proposal_details: ProposalDetails::SlashWorkingGroupLeaderStake(
                 10,
                 10,
-                WorkingGroup::Storage,
+                working_group,
             ),
         };
         proposal_fixture.check_all();
@@ -1337,6 +1234,13 @@ fn create_slash_working_group_leader_stake_proposal_common_checks_succeed() {
 
 #[test]
 fn slash_stake_with_zero_staking_balance_fails() {
+    // This uses strum crate for enum iteration
+    for group in WorkingGroup::iter() {
+        run_slash_stake_with_zero_staking_balance_fails(group);
+    }
+}
+
+fn run_slash_stake_with_zero_staking_balance_fails(working_group: WorkingGroup) {
     initial_test_ext().execute_with(|| {
         increase_total_balance_issuance_using_account_id(1, 500000);
 
@@ -1356,7 +1260,7 @@ fn slash_stake_with_zero_staking_balance_fails() {
                 Some(<BalanceOf<Test>>::from(50000u32)),
                 10,
                 0,
-                WorkingGroup::Storage,
+                working_group,
             ),
             Err(Error::<Test>::SlashingStakeIsZero.into())
         );
@@ -1365,6 +1269,13 @@ fn slash_stake_with_zero_staking_balance_fails() {
 
 #[test]
 fn decrease_stake_with_zero_staking_balance_fails() {
+    // This uses strum crate for enum iteration
+    for group in WorkingGroup::iter() {
+        run_decrease_stake_with_zero_staking_balance_fails(group);
+    }
+}
+
+fn run_decrease_stake_with_zero_staking_balance_fails(working_group: WorkingGroup) {
     initial_test_ext().execute_with(|| {
         increase_total_balance_issuance_using_account_id(1, 500000);
 
@@ -1384,7 +1295,7 @@ fn decrease_stake_with_zero_staking_balance_fails() {
                 Some(<BalanceOf<Test>>::from(50000u32)),
                 10,
                 0,
-                WorkingGroup::Storage,
+                working_group,
             ),
             Err(Error::<Test>::DecreasingStakeIsZero.into())
         );
@@ -1393,6 +1304,15 @@ fn decrease_stake_with_zero_staking_balance_fails() {
 
 #[test]
 fn create_set_working_group_leader_reward_proposal_common_checks_succeed() {
+    // This uses strum crate for enum iteration
+    for group in WorkingGroup::iter() {
+        run_create_set_working_group_leader_reward_proposal_common_checks_succeed(group);
+    }
+}
+
+fn run_create_set_working_group_leader_reward_proposal_common_checks_succeed(
+    working_group: WorkingGroup,
+) {
     initial_test_ext().execute_with(|| {
         let proposal_fixture = ProposalTestFixture {
             insufficient_rights_call: || {
@@ -1404,7 +1324,7 @@ fn create_set_working_group_leader_reward_proposal_common_checks_succeed() {
                     None,
                     0,
                     10,
-                    WorkingGroup::Storage,
+                    working_group,
                 )
             },
             empty_stake_call: || {
@@ -1416,7 +1336,7 @@ fn create_set_working_group_leader_reward_proposal_common_checks_succeed() {
                     None,
                     0,
                     10,
-                    WorkingGroup::Storage,
+                    working_group,
                 )
             },
             invalid_stake_call: || {
@@ -1428,7 +1348,7 @@ fn create_set_working_group_leader_reward_proposal_common_checks_succeed() {
                     Some(<BalanceOf<Test>>::from(5000u32)),
                     0,
                     10,
-                    WorkingGroup::Storage,
+                    working_group,
                 )
             },
             successful_call: || {
@@ -1440,17 +1360,13 @@ fn create_set_working_group_leader_reward_proposal_common_checks_succeed() {
                     Some(<BalanceOf<Test>>::from(50000u32)),
                     10,
                     10,
-                    WorkingGroup::Storage,
+                    working_group,
                 )
             },
             proposal_parameters:
                 crate::proposal_types::parameters::set_working_group_leader_reward_proposal::<Test>(
                 ),
-            proposal_details: ProposalDetails::SetWorkingGroupLeaderReward(
-                10,
-                10,
-                WorkingGroup::Storage,
-            ),
+            proposal_details: ProposalDetails::SetWorkingGroupLeaderReward(10, 10, working_group),
         };
         proposal_fixture.check_all();
     });
@@ -1458,6 +1374,15 @@ fn create_set_working_group_leader_reward_proposal_common_checks_succeed() {
 
 #[test]
 fn create_terminate_working_group_leader_role_proposal_common_checks_succeed() {
+    // This uses strum crate for enum iteration
+    for group in WorkingGroup::iter() {
+        run_create_terminate_working_group_leader_role_proposal_common_checks_succeed(group);
+    }
+}
+
+fn run_create_terminate_working_group_leader_role_proposal_common_checks_succeed(
+    working_group: WorkingGroup,
+) {
     initial_test_ext().execute_with(|| {
         increase_total_balance_issuance(500000);
 
@@ -1465,7 +1390,7 @@ fn create_terminate_working_group_leader_role_proposal_common_checks_succeed() {
             worker_id: 10,
             rationale: Vec::new(),
             slash: false,
-            working_group: WorkingGroup::Storage,
+            working_group,
         };
 
         let proposal_fixture = ProposalTestFixture {

+ 2 - 1
runtime-modules/working-group/src/lib.rs

@@ -1298,7 +1298,8 @@ impl<T: Trait<I>, I: Instance> Module<T, I> {
         Ok(worker)
     }
 
-    fn ensure_worker_exists(worker_id: &WorkerId<T>) -> Result<WorkerOf<T>, Error<T, I>> {
+    /// Ensures worker under given id already exists
+    pub fn ensure_worker_exists(worker_id: &WorkerId<T>) -> Result<WorkerOf<T>, Error<T, I>> {
         ensure!(
             WorkerById::<T, I>::contains_key(worker_id),
             Error::<T, I>::WorkerDoesNotExist

+ 3 - 1
runtime/Cargo.toml

@@ -78,10 +78,11 @@ service-discovery = { package = 'pallet-service-discovery', default-features = f
 proposals-engine = { package = 'pallet-proposals-engine', default-features = false, path = '../runtime-modules/proposals/engine'}
 proposals-discussion = { package = 'pallet-proposals-discussion', default-features = false, path = '../runtime-modules/proposals/discussion'}
 proposals-codex = { package = 'pallet-proposals-codex', default-features = false, path = '../runtime-modules/proposals/codex'}
+content-directory = { package = 'pallet-content-directory', default-features = false, path = '../runtime-modules/content-directory' }
 
 [dev-dependencies]
 sp-io = { package = 'sp-io', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
-
+strum = {version = "0.19", default-features = false}
 [build-dependencies]
 wasm-builder-runner = { package = "substrate-wasm-builder-runner", git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
 
@@ -154,6 +155,7 @@ std = [
     'proposals-engine/std',
     'proposals-discussion/std',
     'proposals-codex/std',
+    'content-directory/std',
 ]
 runtime-benchmarks = [
     "system/runtime-benchmarks",

+ 44 - 0
runtime/src/integration/content_directory.rs

@@ -0,0 +1,44 @@
+use crate::{AccountId, ContentDirectoryWorkingGroupInstance, MemberId, Runtime};
+
+// Alias for content directory working group
+pub(crate) type ContentDirectoryWorkingGroup<T> =
+    working_group::Module<T, ContentDirectoryWorkingGroupInstance>;
+
+impl content_directory::ActorAuthenticator for Runtime {
+    type CuratorId = u64;
+    type MemberId = MemberId;
+    type CuratorGroupId = u64;
+
+    fn is_lead(account_id: &AccountId) -> bool {
+        // get current lead id
+        let maybe_current_lead_id = ContentDirectoryWorkingGroup::<Runtime>::current_lead();
+        if let Some(ref current_lead_id) = maybe_current_lead_id {
+            if let Ok(worker) =
+                ContentDirectoryWorkingGroup::<Runtime>::ensure_worker_exists(current_lead_id)
+            {
+                *account_id == worker.role_account_id
+            } else {
+                false
+            }
+        } else {
+            false
+        }
+    }
+
+    fn is_curator(curator_id: &Self::CuratorId, account_id: &AccountId) -> bool {
+        if let Ok(worker) =
+            ContentDirectoryWorkingGroup::<Runtime>::ensure_worker_exists(curator_id)
+        {
+            *account_id == worker.role_account_id
+        } else {
+            false
+        }
+    }
+
+    fn is_member(member_id: &Self::MemberId, account_id: &AccountId) -> bool {
+        membership::Module::<Runtime>::ensure_is_controller_account_for_member(
+            member_id, account_id,
+        )
+        .is_ok()
+    }
+}

+ 1 - 0
runtime/src/integration/content_working_group.rs

@@ -73,6 +73,7 @@ impl versioned_store_permissions::CredentialChecker<Runtime> for ContentWorkingG
     }
 }
 
+#[allow(dead_code)]
 pub struct ContentWorkingGroupStakingEventHandler {}
 impl stake::StakingEventsHandler<Runtime> for ContentWorkingGroupStakingEventHandler {
     fn unstaked(

+ 1 - 0
runtime/src/integration/mod.rs

@@ -1,3 +1,4 @@
+pub mod content_directory;
 pub mod content_working_group;
 pub mod forum;
 pub mod proposals;

+ 3 - 8
runtime/src/integration/proposals/proposal_encoder.rs

@@ -18,6 +18,9 @@ use sp_std::vec::Vec;
 macro_rules! wrap_working_group_call {
     ($working_group:expr, $working_group_instance_call:expr) => {{
         match $working_group {
+            WorkingGroup::Content => {
+                Call::ContentDirectoryWorkingGroup($working_group_instance_call)
+            }
             WorkingGroup::Storage => Call::StorageWorkingGroup($working_group_instance_call),
         }
     }};
@@ -35,17 +38,9 @@ impl ProposalEncoder<Runtime> for ExtrinsicProposalEncoder {
             ProposalDetails::SetElectionParameters(election_parameters) => Call::CouncilElection(
                 governance::election::Call::set_election_parameters(election_parameters),
             ),
-            ProposalDetails::SetContentWorkingGroupMintCapacity(mint_balance) => {
-                Call::ContentWorkingGroup(content_working_group::Call::set_mint_capacity(
-                    mint_balance,
-                ))
-            }
             ProposalDetails::Spending(balance, destination) => Call::Council(
                 governance::council::Call::spend_from_council_mint(balance, destination),
             ),
-            ProposalDetails::SetLead(new_lead) => {
-                Call::ContentWorkingGroup(content_working_group::Call::replace_lead(new_lead))
-            }
             ProposalDetails::SetValidatorCount(new_validator_count) => Call::Staking(
                 pallet_staking::Call::set_validator_count(new_validator_count),
             ),

+ 47 - 3
runtime/src/integration/working_group.rs

@@ -1,15 +1,59 @@
 use frame_support::StorageMap;
 use sp_std::marker::PhantomData;
 
-use crate::StorageWorkingGroupInstance;
+use crate::{ContentDirectoryWorkingGroupInstance, StorageWorkingGroupInstance};
 use stake::{BalanceOf, NegativeImbalance};
 
-pub struct StakingEventsHandler<T> {
+pub struct ContentDirectoryWGStakingEventsHandler<T> {
+    pub marker: PhantomData<T>,
+}
+
+impl<T: stake::Trait + working_group::Trait<ContentDirectoryWorkingGroupInstance>>
+    stake::StakingEventsHandler<T> for ContentDirectoryWGStakingEventsHandler<T>
+{
+    /// Unstake remaining sum back to the source_account_id
+    fn unstaked(
+        stake_id: &<T as stake::Trait>::StakeId,
+        _unstaked_amount: BalanceOf<T>,
+        remaining_imbalance: NegativeImbalance<T>,
+    ) -> NegativeImbalance<T> {
+        // Stake not related to a staked role managed by the hiring module.
+        if !hiring::ApplicationIdByStakingId::<T>::contains_key(*stake_id) {
+            return remaining_imbalance;
+        }
+
+        let hiring_application_id = hiring::ApplicationIdByStakingId::<T>::get(*stake_id);
+
+        if working_group::MemberIdByHiringApplicationId::<T, ContentDirectoryWorkingGroupInstance>::contains_key(
+            hiring_application_id,
+        ) {
+            return <working_group::Module<T, ContentDirectoryWorkingGroupInstance>>::refund_working_group_stake(
+				*stake_id,
+				remaining_imbalance,
+			);
+        }
+
+        remaining_imbalance
+    }
+
+    /// Empty handler for the slashing.
+    fn slashed(
+        _: &<T as stake::Trait>::StakeId,
+        _: Option<<T as stake::Trait>::SlashId>,
+        _: BalanceOf<T>,
+        _: BalanceOf<T>,
+        remaining_imbalance: NegativeImbalance<T>,
+    ) -> NegativeImbalance<T> {
+        remaining_imbalance
+    }
+}
+
+pub struct StorageWgStakingEventsHandler<T> {
     pub marker: PhantomData<T>,
 }
 
 impl<T: stake::Trait + working_group::Trait<StorageWorkingGroupInstance>>
-    stake::StakingEventsHandler<T> for StakingEventsHandler<T>
+    stake::StakingEventsHandler<T> for StorageWgStakingEventsHandler<T>
 {
     /// Unstake remaining sum back to the source_account_id
     fn unstaked(

+ 61 - 4
runtime/src/lib.rs

@@ -65,6 +65,11 @@ pub use versioned_store;
 pub use versioned_store_permissions;
 pub use working_group;
 
+pub use content_directory;
+pub use content_directory::{
+    HashedTextMaxLength, InputValidationLengthConstraint, MaxNumber, TextMaxLength, VecMaxLength,
+};
+
 /// This runtime version.
 pub const VERSION: RuntimeVersion = RuntimeVersion {
     spec_name: create_runtime_str!("joystream-node"),
@@ -380,6 +385,48 @@ impl versioned_store_permissions::Trait for Runtime {
         integration::versioned_store_permissions::ContentLeadOrSudoKeyCanCreateClasses;
 }
 
+type EntityId = <Runtime as content_directory::Trait>::EntityId;
+
+parameter_types! {
+    pub const PropertyNameLengthConstraint: InputValidationLengthConstraint = InputValidationLengthConstraint::new(1, 49);
+    pub const PropertyDescriptionLengthConstraint: InputValidationLengthConstraint = InputValidationLengthConstraint::new(1, 500);
+    pub const ClassNameLengthConstraint: InputValidationLengthConstraint = InputValidationLengthConstraint::new(1, 49);
+    pub const ClassDescriptionLengthConstraint: InputValidationLengthConstraint = InputValidationLengthConstraint::new(1, 500);
+    pub const MaxNumberOfClasses: MaxNumber = 100;
+    pub const MaxNumberOfMaintainersPerClass: MaxNumber = 10;
+    pub const MaxNumberOfSchemasPerClass: MaxNumber = 20;
+    pub const MaxNumberOfPropertiesPerSchema: MaxNumber = 40;
+    pub const MaxNumberOfEntitiesPerClass: MaxNumber = 400;
+    pub const MaxNumberOfCuratorsPerGroup: MaxNumber = 50;
+    pub const MaxNumberOfOperationsDuringAtomicBatching: MaxNumber = 500;
+    pub const VecMaxLengthConstraint: VecMaxLength = 200;
+    pub const TextMaxLengthConstraint: TextMaxLength = 5000;
+    pub const HashedTextMaxLengthConstraint: HashedTextMaxLength = Some(25000);
+    pub const IndividualEntitiesCreationLimit: EntityId = 50;
+}
+
+impl content_directory::Trait for Runtime {
+    type Event = Event;
+    type Nonce = u64;
+    type ClassId = u64;
+    type EntityId = u64;
+    type PropertyNameLengthConstraint = PropertyNameLengthConstraint;
+    type PropertyDescriptionLengthConstraint = PropertyDescriptionLengthConstraint;
+    type ClassNameLengthConstraint = ClassNameLengthConstraint;
+    type ClassDescriptionLengthConstraint = ClassDescriptionLengthConstraint;
+    type MaxNumberOfClasses = MaxNumberOfClasses;
+    type MaxNumberOfMaintainersPerClass = MaxNumberOfMaintainersPerClass;
+    type MaxNumberOfSchemasPerClass = MaxNumberOfSchemasPerClass;
+    type MaxNumberOfPropertiesPerSchema = MaxNumberOfPropertiesPerSchema;
+    type MaxNumberOfEntitiesPerClass = MaxNumberOfEntitiesPerClass;
+    type MaxNumberOfCuratorsPerGroup = MaxNumberOfCuratorsPerGroup;
+    type MaxNumberOfOperationsDuringAtomicBatching = MaxNumberOfOperationsDuringAtomicBatching;
+    type VecMaxLengthConstraint = VecMaxLengthConstraint;
+    type TextMaxLengthConstraint = TextMaxLengthConstraint;
+    type HashedTextMaxLengthConstraint = HashedTextMaxLengthConstraint;
+    type IndividualEntitiesCreationLimit = IndividualEntitiesCreationLimit;
+}
+
 impl hiring::Trait for Runtime {
     type OpeningId = u64;
     type ApplicationId = u64;
@@ -406,10 +453,10 @@ impl stake::Trait for Runtime {
     type Currency = <Self as common::currency::GovernanceCurrency>::Currency;
     type StakePoolId = StakePoolId;
     type StakingEventsHandler = (
-        crate::integration::content_working_group::ContentWorkingGroupStakingEventHandler,
+        crate::integration::proposals::StakingEventsHandler<Self>,
         (
-            crate::integration::proposals::StakingEventsHandler<Self>,
-            crate::integration::working_group::StakingEventsHandler<Self>,
+            crate::integration::working_group::ContentDirectoryWGStakingEventsHandler<Self>,
+            crate::integration::working_group::StorageWgStakingEventsHandler<Self>,
         ),
     );
     type StakeId = u64;
@@ -464,7 +511,7 @@ impl storage::data_object_storage_registry::Trait for Runtime {
 
 impl membership::Trait for Runtime {
     type Event = Event;
-    type MemberId = u64;
+    type MemberId = MemberId;
     type PaidTermId = u64;
     type SubscriptionId = u64;
     type ActorId = ActorId;
@@ -480,6 +527,9 @@ impl forum::Trait for Runtime {
 // The storage working group instance alias.
 pub type StorageWorkingGroupInstance = working_group::Instance2;
 
+// The content directory working group instance alias.
+pub type ContentDirectoryWorkingGroupInstance = working_group::Instance3;
+
 parameter_types! {
     pub const MaxWorkerNumberLimit: u32 = 100;
 }
@@ -489,6 +539,11 @@ impl working_group::Trait<StorageWorkingGroupInstance> for Runtime {
     type MaxWorkerNumberLimit = MaxWorkerNumberLimit;
 }
 
+impl working_group::Trait<ContentDirectoryWorkingGroupInstance> for Runtime {
+    type Event = Event;
+    type MaxWorkerNumberLimit = MaxWorkerNumberLimit;
+}
+
 impl service_discovery::Trait for Runtime {
     type Event = Event;
 }
@@ -612,6 +667,7 @@ construct_runtime!(
         RecurringRewards: recurring_rewards::{Module, Call, Storage},
         Hiring: hiring::{Module, Call, Storage},
         ContentWorkingGroup: content_wg::{Module, Call, Storage, Event<T>, Config<T>},
+        ContentDirectory: content_directory::{Module, Call, Storage, Event<T>, Config<T>},
         // --- Storage
         DataObjectTypeRegistry: data_object_type_registry::{Module, Call, Storage, Event<T>, Config<T>},
         DataDirectory: data_directory::{Module, Call, Storage, Event<T>, Config<T>},
@@ -624,5 +680,6 @@ construct_runtime!(
         // --- Working groups
         // reserved for the future use: ForumWorkingGroup: working_group::<Instance1>::{Module, Call, Storage, Event<T>},
         StorageWorkingGroup: working_group::<Instance2>::{Module, Call, Storage, Config<T>, Event<T>},
+        ContentDirectoryWorkingGroup: working_group::<Instance3>::{Module, Call, Storage, Config<T>, Event<T>},
     }
 );

+ 3 - 0
runtime/src/primitives.rs

@@ -63,6 +63,9 @@ pub type PostId = u64;
 /// Represent an actor in membership group, which is the same in the working groups.
 pub type ActorId = u64;
 
+/// Represent an member in membership group, which is the same in the working groups.
+pub type MemberId = u64;
+
 /// App-specific crypto used for reporting equivocation/misbehavior in BABE and
 /// GRANDPA. Any rewards for misbehavior reporting will be paid out to this
 /// account.

+ 1 - 0
runtime/src/tests/mod.rs

@@ -1,6 +1,7 @@
 //! The Joystream Substrate Node runtime integration tests.
 
 #![cfg(test)]
+#[macro_use]
 
 mod proposals_integration;
 mod storage_integration;

+ 0 - 57
runtime/src/tests/proposals_integration/mod.rs

@@ -31,7 +31,6 @@ pub type ProposalsEngine = proposals_engine::Module<Runtime>;
 pub type Council = governance::council::Module<Runtime>;
 pub type Election = governance::election::Module<Runtime>;
 pub type ProposalCodex = proposals_codex::Module<Runtime>;
-pub type Mint = minting::Module<Runtime>;
 
 fn setup_members(count: u8) {
     let authority_account_id = <Runtime as system::Trait>::AccountId::default();
@@ -567,32 +566,6 @@ fn text_proposal_execution_succeeds() {
     });
 }
 
-#[test]
-fn set_lead_proposal_execution_succeeds() {
-    initial_test_ext().execute_with(|| {
-        let member_id = 10;
-        let account_id: [u8; 32] = [member_id; 32];
-
-        let codex_extrinsic_test_fixture = CodexProposalTestFixture::default_for_call(|| {
-            ProposalCodex::create_set_lead_proposal(
-                RawOrigin::Signed(account_id.clone().into()).into(),
-                member_id as u64,
-                b"title".to_vec(),
-                b"body".to_vec(),
-                Some(<BalanceOf<Runtime>>::from(50000u32)),
-                Some((member_id as u64, account_id.into())),
-            )
-        })
-        .with_member_id(member_id as u64);
-
-        assert!(content_working_group::Module::<Runtime>::ensure_lead_is_set().is_err());
-
-        codex_extrinsic_test_fixture.call_extrinsic_and_assert();
-
-        assert!(content_working_group::Module::<Runtime>::ensure_lead_is_set().is_ok());
-    });
-}
-
 #[test]
 fn spending_proposal_execution_succeeds() {
     initial_test_ext().execute_with(|| {
@@ -626,36 +599,6 @@ fn spending_proposal_execution_succeeds() {
     });
 }
 
-#[test]
-fn set_content_working_group_mint_capacity_execution_succeeds() {
-    initial_test_ext().execute_with(|| {
-        let member_id = 1;
-        let account_id: [u8; 32] = [member_id; 32];
-        let new_balance = <BalanceOf<Runtime>>::from(55u32);
-
-        let mint_id =
-            Mint::add_mint(0, None).expect("Failed to create a mint for the content working group");
-        <content_working_group::Mint<Runtime>>::put(mint_id);
-
-        assert_eq!(Mint::get_mint_capacity(mint_id), Ok(0));
-
-        let codex_extrinsic_test_fixture = CodexProposalTestFixture::default_for_call(|| {
-            ProposalCodex::create_set_content_working_group_mint_capacity_proposal(
-                RawOrigin::Signed(account_id.clone().into()).into(),
-                member_id as u64,
-                b"title".to_vec(),
-                b"body".to_vec(),
-                Some(<BalanceOf<Runtime>>::from(50000u32)),
-                new_balance,
-            )
-        });
-
-        codex_extrinsic_test_fixture.call_extrinsic_and_assert();
-
-        assert_eq!(Mint::get_mint_capacity(mint_id), Ok(new_balance));
-    });
-}
-
 #[test]
 fn set_election_parameters_proposal_execution_succeeds() {
     initial_test_ext().execute_with(|| {

+ 883 - 524
runtime/src/tests/proposals_integration/working_group_proposals.rs

@@ -1,3 +1,6 @@
+#![allow(unnameable_test_items)]
+#![allow(dead_code)]
+
 use super::*;
 
 use system::RawOrigin;
@@ -7,33 +10,53 @@ use hiring::ActivateOpeningAt;
 use proposals_codex::AddOpeningParameters;
 use working_group::{OpeningPolicyCommitment, RewardPolicy};
 
-use crate::{Balance, BlockNumber, StorageWorkingGroupInstance};
+use crate::{
+    Balance, BlockNumber, ContentDirectoryWorkingGroup, ContentDirectoryWorkingGroupInstance,
+    StorageWorkingGroup, StorageWorkingGroupInstance,
+};
 use sp_std::collections::btree_set::BTreeSet;
 
-type StorageWorkingGroup = working_group::Module<Runtime, StorageWorkingGroupInstance>;
+use crate::primitives::{ActorId, MemberId};
+use frame_support::traits;
+use strum::IntoEnumIterator;
+
+type WorkingGroupInstance<T, I> = working_group::Module<T, I>;
 
 type Hiring = hiring::Module<Runtime>;
 
 fn add_opening(
-    member_id: u8,
+    member_id: MemberId,
     account_id: [u8; 32],
     activate_at: hiring::ActivateOpeningAt<BlockNumber>,
     opening_policy_commitment: Option<OpeningPolicyCommitment<BlockNumber, u128>>,
     sequence_number: u32, // action sequence number to align with other actions
+    working_group: WorkingGroup,
 ) -> u64 {
     let expected_proposal_id = sequence_number;
     let run_to_block = sequence_number * 2;
 
-    let opening_id = StorageWorkingGroup::next_opening_id();
-
-    assert!(!<working_group::OpeningById<
-        Runtime,
-        StorageWorkingGroupInstance,
-    >>::contains_key(opening_id));
+    let opening_id = match working_group {
+        WorkingGroup::Content => {
+            let opening_id = ContentDirectoryWorkingGroup::next_opening_id();
+            assert!(!<working_group::OpeningById<
+                Runtime,
+                ContentDirectoryWorkingGroupInstance,
+            >>::contains_key(opening_id));
+            opening_id
+        }
+        WorkingGroup::Storage => {
+            let opening_id = StorageWorkingGroup::next_opening_id();
+            assert!(!<working_group::OpeningById<
+                Runtime,
+                StorageWorkingGroupInstance,
+            >>::contains_key(opening_id));
+            opening_id
+        }
+    };
 
     let codex_extrinsic_test_fixture = CodexProposalTestFixture::default_for_call(|| {
         ProposalCodex::create_add_working_group_leader_opening_proposal(
-            RawOrigin::Signed(account_id.clone().into()).into(),
+            RawOrigin::Signed(account_id.into()).into(),
             member_id as u64,
             b"title".to_vec(),
             b"body".to_vec(),
@@ -44,7 +67,7 @@ fn add_opening(
                     .clone()
                     .unwrap_or(OpeningPolicyCommitment::default()),
                 human_readable_text: Vec::new(),
-                working_group: WorkingGroup::Storage,
+                working_group,
             },
         )
     })
@@ -57,23 +80,24 @@ fn add_opening(
 }
 
 fn begin_review_applications(
-    member_id: u8,
+    member_id: MemberId,
     account_id: [u8; 32],
     opening_id: u64,
     sequence_number: u32, // action sequence number to align with other actions
+    working_group: WorkingGroup,
 ) {
     let expected_proposal_id = sequence_number;
     let run_to_block = sequence_number * 2;
 
     let codex_extrinsic_test_fixture = CodexProposalTestFixture::default_for_call(|| {
         ProposalCodex::create_begin_review_working_group_leader_applications_proposal(
-            RawOrigin::Signed(account_id.clone().into()).into(),
-            member_id as u64,
+            RawOrigin::Signed(account_id.into()).into(),
+            member_id,
             b"title".to_vec(),
             b"body".to_vec(),
             Some(<BalanceOf<Runtime>>::from(25_000_u32)),
             opening_id,
-            WorkingGroup::Storage,
+            working_group,
         )
     })
     .disable_setup_enviroment()
@@ -84,20 +108,21 @@ fn begin_review_applications(
 }
 
 fn fill_opening(
-    member_id: u8,
+    member_id: MemberId,
     account_id: [u8; 32],
     opening_id: u64,
     successful_application_id: u64,
     reward_policy: Option<RewardPolicy<Balance, BlockNumber>>,
     sequence_number: u32, // action sequence number to align with other actions
+    working_group: WorkingGroup,
 ) {
     let expected_proposal_id = sequence_number;
     let run_to_block = sequence_number * 2;
 
     let codex_extrinsic_test_fixture = CodexProposalTestFixture::default_for_call(|| {
         ProposalCodex::create_fill_working_group_leader_opening_proposal(
-            RawOrigin::Signed(account_id.clone().into()).into(),
-            member_id as u64,
+            RawOrigin::Signed(account_id.into()).into(),
+            member_id,
             b"title".to_vec(),
             b"body".to_vec(),
             Some(<BalanceOf<Runtime>>::from(50_000_u32)),
@@ -105,7 +130,7 @@ fn fill_opening(
                 opening_id,
                 successful_application_id,
                 reward_policy: reward_policy.clone(),
-                working_group: WorkingGroup::Storage,
+                working_group,
             },
         )
     })
@@ -125,25 +150,26 @@ fn get_stake_balance(stake: stake::Stake<BlockNumber, Balance, u64>) -> Balance
 }
 
 fn decrease_stake(
-    member_id: u8,
+    member_id: u64,
     account_id: [u8; 32],
     leader_worker_id: u64,
     stake_amount: Balance,
     sequence_number: u32, // action sequence number to align with other actions
+    working_group: WorkingGroup,
 ) {
     let expected_proposal_id = sequence_number;
     let run_to_block = sequence_number * 2;
 
     let codex_extrinsic_test_fixture = CodexProposalTestFixture::default_for_call(|| {
         ProposalCodex::create_decrease_working_group_leader_stake_proposal(
-            RawOrigin::Signed(account_id.clone().into()).into(),
-            member_id as u64,
+            RawOrigin::Signed(account_id.into()).into(),
+            member_id,
             b"title".to_vec(),
             b"body".to_vec(),
             Some(<BalanceOf<Runtime>>::from(50_000_u32)),
             leader_worker_id,
             stake_amount,
-            WorkingGroup::Storage,
+            working_group,
         )
     })
     .disable_setup_enviroment()
@@ -154,25 +180,26 @@ fn decrease_stake(
 }
 
 fn slash_stake(
-    member_id: u8,
+    member_id: MemberId,
     account_id: [u8; 32],
-    leader_worker_id: u64,
+    leader_worker_id: ActorId,
     stake_amount: Balance,
     sequence_number: u32, // action sequence number to align with other actions
+    working_group: WorkingGroup,
 ) {
     let expected_proposal_id = sequence_number;
     let run_to_block = sequence_number * 2;
 
     let codex_extrinsic_test_fixture = CodexProposalTestFixture::default_for_call(|| {
         ProposalCodex::create_slash_working_group_leader_stake_proposal(
-            RawOrigin::Signed(account_id.clone().into()).into(),
-            member_id as u64,
+            RawOrigin::Signed(account_id.into()).into(),
+            member_id,
             b"title".to_vec(),
             b"body".to_vec(),
             Some(<BalanceOf<Runtime>>::from(50_000_u32)),
             leader_worker_id,
             stake_amount,
-            WorkingGroup::Storage,
+            working_group,
         )
     })
     .disable_setup_enviroment()
@@ -183,25 +210,26 @@ fn slash_stake(
 }
 
 fn set_reward(
-    member_id: u8,
+    member_id: MemberId,
     account_id: [u8; 32],
     leader_worker_id: u64,
     reward_amount: Balance,
     sequence_number: u32, // action sequence number to align with other actions
+    working_group: WorkingGroup,
 ) {
     let expected_proposal_id = sequence_number;
     let run_to_block = sequence_number * 2;
 
     let codex_extrinsic_test_fixture = CodexProposalTestFixture::default_for_call(|| {
         ProposalCodex::create_set_working_group_leader_reward_proposal(
-            RawOrigin::Signed(account_id.clone().into()).into(),
+            RawOrigin::Signed(account_id.into()).into(),
             member_id as u64,
             b"title".to_vec(),
             b"body".to_vec(),
             Some(<BalanceOf<Runtime>>::from(50_000_u32)),
             leader_worker_id,
             reward_amount,
-            WorkingGroup::Storage,
+            working_group,
         )
     })
     .disable_setup_enviroment()
@@ -211,31 +239,38 @@ fn set_reward(
     codex_extrinsic_test_fixture.call_extrinsic_and_assert();
 }
 
-fn set_mint_capacity(
-    member_id: u8,
+fn set_mint_capacity<
+    T: working_group::Trait<I> + system::Trait + minting::Trait,
+    I: working_group::Instance,
+>(
+    member_id: MemberId,
     account_id: [u8; 32],
     mint_capacity: Balance,
     sequence_number: u32, // action sequence number to align with other actions
     setup_environment: bool,
-) {
+    working_group: WorkingGroup,
+) where
+    <T as minting::Trait>::MintId: From<u64>,
+{
     let expected_proposal_id = sequence_number;
     let run_to_block = sequence_number * 2;
 
     let mint_id_result = <minting::Module<Runtime>>::add_mint(0, None);
 
     if let Ok(mint_id) = mint_id_result {
-        <working_group::Mint<Runtime, StorageWorkingGroupInstance>>::put(mint_id);
+        let mint_id: <T as minting::Trait>::MintId = mint_id.into();
+        <working_group::Mint<T, I>>::put(mint_id);
     }
 
     let codex_extrinsic_test_fixture = CodexProposalTestFixture::default_for_call(|| {
         ProposalCodex::create_set_working_group_mint_capacity_proposal(
-            RawOrigin::Signed(account_id.clone().into()).into(),
-            member_id as u64,
+            RawOrigin::Signed(account_id.into()).into(),
+            member_id,
             b"title".to_vec(),
             b"body".to_vec(),
             Some(<BalanceOf<Runtime>>::from(50_000_u32)),
             mint_capacity,
-            WorkingGroup::Storage,
+            working_group,
         )
     })
     .with_setup_enviroment(setup_environment)
@@ -246,19 +281,20 @@ fn set_mint_capacity(
 }
 
 fn terminate_role(
-    member_id: u8,
+    member_id: MemberId,
     account_id: [u8; 32],
     leader_worker_id: u64,
     slash: bool,
     sequence_number: u32, // action sequence number to align with other actions
+    working_group: WorkingGroup,
 ) {
     let expected_proposal_id = sequence_number;
     let run_to_block = sequence_number * 2;
 
     let codex_extrinsic_test_fixture = CodexProposalTestFixture::default_for_call(|| {
         ProposalCodex::create_terminate_working_group_leader_role_proposal(
-            RawOrigin::Signed(account_id.clone().into()).into(),
-            member_id as u64,
+            RawOrigin::Signed(account_id.into()).into(),
+            member_id,
             b"title".to_vec(),
             b"body".to_vec(),
             Some(<BalanceOf<Runtime>>::from(100_000_u32)),
@@ -266,7 +302,7 @@ fn terminate_role(
                 worker_id: leader_worker_id,
                 rationale: Vec::new(),
                 slash,
-                working_group: WorkingGroup::Storage,
+                working_group,
             },
         )
     })
@@ -279,53 +315,110 @@ fn terminate_role(
 
 #[test]
 fn create_add_working_group_leader_opening_proposal_execution_succeeds() {
+    // This uses strum crate for enum iteration
+    for group in WorkingGroup::iter() {
+        match group {
+            WorkingGroup::Content => {
+                run_create_add_working_group_leader_opening_proposal_execution_succeeds::<
+                    Runtime,
+                    ContentDirectoryWorkingGroupInstance,
+                >(group);
+            }
+            WorkingGroup::Storage => {
+                run_create_add_working_group_leader_opening_proposal_execution_succeeds::<
+                    Runtime,
+                    StorageWorkingGroupInstance,
+                >(group);
+            }
+        }
+    }
+}
+
+fn run_create_add_working_group_leader_opening_proposal_execution_succeeds<
+    T: working_group::Trait<I> + system::Trait + stake::Trait,
+    I: working_group::Instance,
+>(
+    working_group: WorkingGroup,
+) where
+    <T as membership::Trait>::MemberId: From<u64>,
+    <T as hiring::Trait>::OpeningId: From<u64>,
+{
     initial_test_ext().execute_with(|| {
-        let member_id = 1;
-        let account_id: [u8; 32] = [member_id; 32];
+        let member_id: MemberId = 1;
+        let account_id: [u8; 32] = [member_id as u8; 32];
 
-        let next_opening_id = StorageWorkingGroup::next_opening_id();
+        let next_opening_id = WorkingGroupInstance::<T, I>::next_opening_id();
 
-        assert!(!<working_group::OpeningById<
-            Runtime,
-            StorageWorkingGroupInstance,
-        >>::contains_key(next_opening_id));
+        assert!(!<working_group::OpeningById<T, I>>::contains_key(
+            next_opening_id
+        ));
 
-        let opening_id = add_opening(
+        let opening_id: <T as hiring::Trait>::OpeningId = add_opening(
             member_id,
             account_id,
             ActivateOpeningAt::CurrentBlock,
             None,
             1,
-        );
+            working_group,
+        )
+        .into();
 
         // Check for expected opening id.
         assert_eq!(opening_id, next_opening_id);
 
         // Check for the new opening creation.
-        assert!(<working_group::OpeningById<
-            Runtime,
-            StorageWorkingGroupInstance,
-        >>::contains_key(opening_id));
+        assert!(<working_group::OpeningById<T, I>>::contains_key(opening_id));
     });
 }
 
 #[test]
 fn create_begin_review_working_group_leader_applications_proposal_execution_succeeds() {
+    // This uses strum crate for enum iteration
+    for group in WorkingGroup::iter() {
+        match group {
+            WorkingGroup::Content => {
+                run_create_begin_review_working_group_leader_applications_proposal_execution_succeeds::<
+                Runtime,
+                ContentDirectoryWorkingGroupInstance,
+            >(group);
+            }
+            WorkingGroup::Storage => {
+                run_create_begin_review_working_group_leader_applications_proposal_execution_succeeds::<
+                Runtime,
+                StorageWorkingGroupInstance,
+            >(group);
+            }
+        }
+    }
+}
+
+fn run_create_begin_review_working_group_leader_applications_proposal_execution_succeeds<
+    T: working_group::Trait<I> + system::Trait + stake::Trait,
+    I: working_group::Instance,
+>(
+    working_group: WorkingGroup,
+) where
+    <T as hiring::Trait>::OpeningId: From<u64> + Into<u64>,
+{
     initial_test_ext().execute_with(|| {
-        let member_id = 1;
-        let account_id: [u8; 32] = [member_id; 32];
+        let member_id: MemberId = 1;
+        let account_id: [u8; 32] = [member_id as u8; 32];
 
         let opening_id = add_opening(
             member_id,
-            account_id.clone(),
+            account_id,
             ActivateOpeningAt::CurrentBlock,
             None,
             1,
+            working_group,
         );
 
-        let opening = StorageWorkingGroup::opening_by_id(opening_id);
+        let opening = WorkingGroupInstance::<T, I>::opening_by_id(
+            <T as hiring::Trait>::OpeningId::from(opening_id),
+        );
 
-        let hiring_opening = Hiring::opening_by_id(opening.hiring_opening_id);
+        let hiring_opening_id: u64 = opening.hiring_opening_id.into();
+        let hiring_opening = Hiring::opening_by_id(hiring_opening_id);
         assert_eq!(
             hiring_opening.stage,
             hiring::OpeningStage::Active {
@@ -339,9 +432,9 @@ fn create_begin_review_working_group_leader_applications_proposal_execution_succ
             }
         );
 
-        begin_review_applications(member_id, account_id, opening_id, 2);
+        begin_review_applications(member_id, account_id, opening_id, 2, working_group);
 
-        let hiring_opening = Hiring::opening_by_id(opening.hiring_opening_id);
+        let hiring_opening = Hiring::opening_by_id(hiring_opening_id);
         assert_eq!(
             hiring_opening.stage,
             hiring::OpeningStage::Active {
@@ -360,475 +453,741 @@ fn create_begin_review_working_group_leader_applications_proposal_execution_succ
 
 #[test]
 fn create_fill_working_group_leader_opening_proposal_execution_succeeds() {
-    initial_test_ext().execute_with(|| {
-        let member_id = 1;
-        let account_id: [u8; 32] = [member_id; 32];
-
-        let opening_id = add_opening(
-            member_id,
-            account_id.clone(),
-            ActivateOpeningAt::CurrentBlock,
-            None,
-            1,
-        );
-
-        let apply_result = StorageWorkingGroup::apply_on_opening(
-            RawOrigin::Signed(account_id.clone().into()).into(),
-            member_id as u64,
-            opening_id,
-            account_id.clone().into(),
-            None,
-            None,
-            Vec::new(),
-        );
-
-        assert_eq!(apply_result, Ok(()));
-
-        let expected_application_id = 0;
-
-        begin_review_applications(member_id, account_id, opening_id, 2);
-
-        let lead = StorageWorkingGroup::current_lead();
-        assert!(lead.is_none());
-
-        fill_opening(
-            member_id,
-            account_id,
-            opening_id,
-            expected_application_id,
-            None,
-            3,
-        );
-
-        let lead = StorageWorkingGroup::current_lead();
-        assert!(lead.is_some());
-    });
-}
-
-#[test]
-fn create_decrease_group_leader_stake_proposal_execution_succeeds() {
-    initial_test_ext().execute_with(|| {
-        let member_id = 1;
-        let account_id: [u8; 32] = [member_id; 32];
-        let stake_amount = 100;
-
-        let opening_policy_commitment = OpeningPolicyCommitment {
-            role_staking_policy: Some(hiring::StakingPolicy {
-                amount: 100,
-                amount_mode: hiring::StakingAmountLimitMode::AtLeast,
-                crowded_out_unstaking_period_length: None,
-                review_period_expired_unstaking_period_length: None,
-            }),
-            ..OpeningPolicyCommitment::default()
-        };
-
-        let opening_id = add_opening(
-            member_id,
-            account_id.clone(),
-            ActivateOpeningAt::CurrentBlock,
-            Some(opening_policy_commitment),
-            1,
-        );
-
-        let apply_result = StorageWorkingGroup::apply_on_opening(
-            RawOrigin::Signed(account_id.clone().into()).into(),
-            member_id as u64,
-            opening_id,
-            account_id.clone().into(),
-            Some(stake_amount),
-            None,
-            Vec::new(),
-        );
-
-        assert_eq!(apply_result, Ok(()));
-
-        let expected_application_id = 0;
-
-        begin_review_applications(member_id, account_id, opening_id, 2);
-
-        let lead = StorageWorkingGroup::current_lead();
-        assert!(lead.is_none());
-
-        fill_opening(
-            member_id,
-            account_id,
-            opening_id,
-            expected_application_id,
-            None,
-            3,
-        );
-
-        let leader_worker_id = StorageWorkingGroup::current_lead().unwrap();
-
-        let stake_id = 1;
-        let old_balance = Balances::free_balance(&account_id.into());
-        let old_stake = <stake::Module<Runtime>>::stakes(stake_id);
-
-        assert_eq!(get_stake_balance(old_stake), stake_amount);
-
-        let decreasing_stake_amount = 30;
-        decrease_stake(
-            member_id,
-            account_id,
-            leader_worker_id,
-            decreasing_stake_amount,
-            4,
-        );
-
-        let new_balance = Balances::free_balance(&account_id.into());
-        let new_stake = <stake::Module<Runtime>>::stakes(stake_id);
-
-        assert_eq!(
-            get_stake_balance(new_stake),
-            stake_amount - decreasing_stake_amount
-        );
-        assert_eq!(new_balance, old_balance + decreasing_stake_amount);
-    });
-}
-
-#[test]
-fn create_slash_group_leader_stake_proposal_execution_succeeds() {
-    initial_test_ext().execute_with(|| {
-        let member_id = 1;
-        let account_id: [u8; 32] = [member_id; 32];
-        let stake_amount = 100;
-
-        let opening_policy_commitment = OpeningPolicyCommitment {
-            role_staking_policy: Some(hiring::StakingPolicy {
-                amount: 100,
-                amount_mode: hiring::StakingAmountLimitMode::AtLeast,
-                crowded_out_unstaking_period_length: None,
-                review_period_expired_unstaking_period_length: None,
-            }),
-            ..OpeningPolicyCommitment::default()
-        };
-
-        let opening_id = add_opening(
-            member_id,
-            account_id.clone(),
-            ActivateOpeningAt::CurrentBlock,
-            Some(opening_policy_commitment),
-            1,
-        );
-
-        let apply_result = StorageWorkingGroup::apply_on_opening(
-            RawOrigin::Signed(account_id.clone().into()).into(),
-            member_id as u64,
-            opening_id,
-            account_id.clone().into(),
-            Some(stake_amount),
-            None,
-            Vec::new(),
-        );
-
-        assert_eq!(apply_result, Ok(()));
-
-        let expected_application_id = 0;
-
-        begin_review_applications(member_id, account_id, opening_id, 2);
-
-        let lead = StorageWorkingGroup::current_lead();
-        assert!(lead.is_none());
-
-        fill_opening(
-            member_id,
-            account_id,
-            opening_id,
-            expected_application_id,
-            None,
-            3,
-        );
-
-        let leader_worker_id = StorageWorkingGroup::current_lead().unwrap();
-
-        let stake_id = 1;
-        let old_balance = Balances::free_balance(&account_id.into());
-        let old_stake = <stake::Module<Runtime>>::stakes(stake_id);
-
-        assert_eq!(get_stake_balance(old_stake), stake_amount);
-
-        let slashing_stake_amount = 30;
-        slash_stake(
-            member_id,
-            account_id,
-            leader_worker_id,
-            slashing_stake_amount,
-            4,
-        );
-
-        let new_balance = Balances::free_balance(&account_id.into());
-        let new_stake = <stake::Module<Runtime>>::stakes(stake_id);
-
-        assert_eq!(
-            get_stake_balance(new_stake),
-            stake_amount - slashing_stake_amount
-        );
-        assert_eq!(new_balance, old_balance);
-    });
-}
-
-#[test]
-fn create_set_working_group_mint_capacity_proposal_execution_succeeds() {
-    initial_test_ext().execute_with(|| {
-        let member_id = 1;
-        let account_id: [u8; 32] = [member_id; 32];
-
-        assert_eq!(StorageWorkingGroup::mint(), 0);
-
-        let mint_capacity = 999999;
-        set_mint_capacity(member_id, account_id, mint_capacity, 1, true);
-
-        let mint_id = StorageWorkingGroup::mint();
-        let mint = <minting::Module<Runtime>>::mints(mint_id);
-
-        assert_eq!(mint.capacity(), mint_capacity);
-    });
-}
-
-#[test]
-fn create_set_group_leader_reward_proposal_execution_succeeds() {
-    initial_test_ext().execute_with(|| {
-        let member_id = 1;
-        let account_id: [u8; 32] = [member_id; 32];
-        let stake_amount = 100;
-
-        let opening_policy_commitment = OpeningPolicyCommitment {
-            role_staking_policy: Some(hiring::StakingPolicy {
-                amount: 100,
-                amount_mode: hiring::StakingAmountLimitMode::AtLeast,
-                crowded_out_unstaking_period_length: None,
-                review_period_expired_unstaking_period_length: None,
-            }),
-            ..OpeningPolicyCommitment::default()
-        };
-
-        let opening_id = add_opening(
-            member_id,
-            account_id.clone(),
-            ActivateOpeningAt::CurrentBlock,
-            Some(opening_policy_commitment),
-            1,
-        );
-
-        let apply_result = StorageWorkingGroup::apply_on_opening(
-            RawOrigin::Signed(account_id.clone().into()).into(),
-            member_id as u64,
-            opening_id,
-            account_id.clone().into(),
-            Some(stake_amount),
-            None,
-            Vec::new(),
-        );
-
-        assert_eq!(apply_result, Ok(()));
-
-        let expected_application_id = 0;
-
-        begin_review_applications(member_id, account_id, opening_id, 2);
-
-        let lead = StorageWorkingGroup::current_lead();
-        assert!(lead.is_none());
+    // This uses strum crate for enum iteration
+    for group in WorkingGroup::iter() {
+        match group {
+            WorkingGroup::Content => {
+                run_create_fill_working_group_leader_opening_proposal_execution_succeeds::<
+                    Runtime,
+                    ContentDirectoryWorkingGroupInstance,
+                >(group);
+            }
+            WorkingGroup::Storage => {
+                run_create_fill_working_group_leader_opening_proposal_execution_succeeds::<
+                    Runtime,
+                    StorageWorkingGroupInstance,
+                >(group);
+            }
+        }
+    }
 
-        let old_reward_amount = 100;
-        let reward_policy = Some(RewardPolicy {
-            amount_per_payout: old_reward_amount,
-            next_payment_at_block: 9999,
-            payout_interval: None,
+    fn run_create_fill_working_group_leader_opening_proposal_execution_succeeds<
+        T: working_group::Trait<I> + system::Trait + stake::Trait,
+        I: working_group::Instance,
+    >(
+        working_group: WorkingGroup,
+    ) where
+        <T as system::Trait>::AccountId: From<[u8; 32]>,
+        <T as membership::Trait>::MemberId: From<u64>,
+        <T as hiring::Trait>::OpeningId: From<u64>,
+    {
+        initial_test_ext().execute_with(|| {
+            let member_id: MemberId = 1;
+            let account_id: [u8; 32] = [member_id as u8; 32];
+
+            let opening_id = add_opening(
+                member_id,
+                account_id,
+                ActivateOpeningAt::CurrentBlock,
+                None,
+                1,
+                working_group,
+            );
+
+            let apply_result = WorkingGroupInstance::<T, I>::apply_on_opening(
+                RawOrigin::Signed(account_id.into()).into(),
+                member_id.into(),
+                opening_id.into(),
+                account_id.into(),
+                None,
+                None,
+                Vec::new(),
+            );
+
+            assert_eq!(apply_result, Ok(()));
+
+            let expected_application_id = 0;
+
+            begin_review_applications(member_id, account_id, opening_id, 2, working_group);
+
+            let lead = WorkingGroupInstance::<T, I>::current_lead();
+            assert!(lead.is_none());
+
+            fill_opening(
+                member_id,
+                account_id,
+                opening_id,
+                expected_application_id,
+                None,
+                3,
+                working_group,
+            );
+
+            let lead = WorkingGroupInstance::<T, I>::current_lead();
+            assert!(lead.is_some());
         });
+    }
 
-        set_mint_capacity(member_id, account_id, 999999, 3, false);
-
-        fill_opening(
-            member_id,
-            account_id,
-            opening_id,
-            expected_application_id,
-            reward_policy,
-            4,
-        );
-
-        let leader_worker_id = StorageWorkingGroup::current_lead().unwrap();
-
-        let worker = StorageWorkingGroup::worker_by_id(leader_worker_id);
-        let relationship_id = worker.reward_relationship.unwrap();
-
-        let relationship = recurring_rewards::RewardRelationships::<Runtime>::get(relationship_id);
-        assert_eq!(relationship.amount_per_payout, old_reward_amount);
-
-        let new_reward_amount = 999;
-        set_reward(
-            member_id,
-            account_id,
-            leader_worker_id,
-            new_reward_amount,
-            5,
-        );
-
-        let relationship = recurring_rewards::RewardRelationships::<Runtime>::get(relationship_id);
-        assert_eq!(relationship.amount_per_payout, new_reward_amount);
-    });
-}
-
-#[test]
-fn create_terminate_group_leader_role_proposal_execution_succeeds() {
-    initial_test_ext().execute_with(|| {
-        let member_id = 1;
-        let account_id: [u8; 32] = [member_id; 32];
-        let stake_amount = 100;
-
-        let opening_policy_commitment = OpeningPolicyCommitment {
-            role_staking_policy: Some(hiring::StakingPolicy {
-                amount: 100,
-                amount_mode: hiring::StakingAmountLimitMode::AtLeast,
-                crowded_out_unstaking_period_length: None,
-                review_period_expired_unstaking_period_length: None,
-            }),
-            ..OpeningPolicyCommitment::default()
-        };
-
-        let opening_id = add_opening(
-            member_id,
-            account_id.clone(),
-            ActivateOpeningAt::CurrentBlock,
-            Some(opening_policy_commitment),
-            1,
-        );
-
-        let apply_result = StorageWorkingGroup::apply_on_opening(
-            RawOrigin::Signed(account_id.clone().into()).into(),
-            member_id as u64,
-            opening_id,
-            account_id.clone().into(),
-            Some(stake_amount),
-            None,
-            Vec::new(),
-        );
-
-        assert_eq!(apply_result, Ok(()));
-
-        let expected_application_id = 0;
-
-        begin_review_applications(member_id, account_id, opening_id, 2);
-
-        let lead = StorageWorkingGroup::current_lead();
-        assert!(lead.is_none());
+    #[test]
+    fn create_decrease_group_leader_stake_proposal_execution_succeeds() {
+        // This uses strum crate for enum iteration
+        for group in WorkingGroup::iter() {
+            match group {
+                WorkingGroup::Content => {
+                    run_create_decrease_group_leader_stake_proposal_execution_succeeds::<
+                        Runtime,
+                        ContentDirectoryWorkingGroupInstance,
+                    >(group);
+                }
+                WorkingGroup::Storage => {
+                    run_create_decrease_group_leader_stake_proposal_execution_succeeds::<
+                        Runtime,
+                        StorageWorkingGroupInstance,
+                    >(group);
+                }
+            }
+        }
+    }
 
-        let old_reward_amount = 100;
-        let reward_policy = Some(RewardPolicy {
-            amount_per_payout: old_reward_amount,
-            next_payment_at_block: 9999,
-            payout_interval: None,
+fn run_create_decrease_group_leader_stake_proposal_execution_succeeds<
+    T: working_group::Trait<I> + system::Trait + stake::Trait,
+    I: working_group::Instance,
+>(
+    working_group: WorkingGroup,
+) where
+    <T as system::Trait>::AccountId: From<[u8; 32]>,
+    <T as hiring::Trait>::OpeningId: From<u64>,
+    <T as membership::Trait>::MemberId: From<u64>,
+    <T as membership::Trait>::ActorId: Into<u64>,
+    <<T as stake::Trait>::Currency as traits::Currency<<T as system::Trait>::AccountId>>::Balance:
+        From<u128>,
+{
+        initial_test_ext().execute_with(|| {
+            let member_id: MemberId = 1;
+            let account_id: [u8; 32] = [member_id as u8; 32];
+            let stake_amount: Balance = 100;
+
+            let opening_policy_commitment = OpeningPolicyCommitment {
+                role_staking_policy: Some(hiring::StakingPolicy {
+                    amount: 100,
+                    amount_mode: hiring::StakingAmountLimitMode::AtLeast,
+                    crowded_out_unstaking_period_length: None,
+                    review_period_expired_unstaking_period_length: None,
+                }),
+                ..OpeningPolicyCommitment::default()
+            };
+
+            let opening_id = add_opening(
+                member_id,
+                account_id,
+                ActivateOpeningAt::CurrentBlock,
+                Some(opening_policy_commitment),
+                1,
+                working_group,
+            );
+
+            let apply_result = WorkingGroupInstance::<T, I>::apply_on_opening(
+                RawOrigin::Signed(account_id.into()).into(),
+                member_id.into(),
+                opening_id.into(),
+                account_id.into(),
+                Some(stake_amount.into()),
+                None,
+                Vec::new(),
+            );
+
+            assert_eq!(apply_result, Ok(()));
+
+            let expected_application_id = 0;
+
+            begin_review_applications(member_id, account_id, opening_id, 2, working_group);
+
+            let lead = WorkingGroupInstance::<T, I>::current_lead();
+            assert!(lead.is_none());
+
+            fill_opening(
+                member_id,
+                account_id,
+                opening_id,
+                expected_application_id,
+                None,
+                3,
+                working_group,
+            );
+
+            let leader_worker_id = WorkingGroupInstance::<T, I>::current_lead().unwrap();
+
+            let stake_id = 1;
+            let old_balance = Balances::free_balance(&account_id.into());
+            let old_stake = <stake::Module<Runtime>>::stakes(stake_id);
+
+            assert_eq!(get_stake_balance(old_stake), stake_amount);
+
+            let decreasing_stake_amount = 30;
+            decrease_stake(
+                member_id,
+                account_id,
+                leader_worker_id.into(),
+                decreasing_stake_amount,
+                4,
+                working_group,
+            );
+
+            let new_balance = Balances::free_balance(&account_id.into());
+            let new_stake = <stake::Module<Runtime>>::stakes(stake_id);
+
+            assert_eq!(
+                get_stake_balance(new_stake),
+                stake_amount - decreasing_stake_amount
+            );
+            assert_eq!(new_balance, old_balance + decreasing_stake_amount);
         });
+    }
 
-        set_mint_capacity(member_id, account_id, 999999, 3, false);
-
-        fill_opening(
-            member_id,
-            account_id,
-            opening_id,
-            expected_application_id,
-            reward_policy,
-            4,
-        );
-
-        let leader_worker_id = StorageWorkingGroup::current_lead().unwrap();
-
-        let stake_id = 1;
-        let old_balance = Balances::free_balance(&account_id.into());
-        let old_stake = <stake::Module<Runtime>>::stakes(stake_id);
-
-        assert_eq!(get_stake_balance(old_stake), stake_amount);
-
-        terminate_role(member_id, account_id, leader_worker_id, false, 5);
-
-        assert!(StorageWorkingGroup::current_lead().is_none());
-
-        let new_balance = Balances::free_balance(&account_id.into());
-        let new_stake = <stake::Module<Runtime>>::stakes(stake_id);
-
-        assert_eq!(new_stake.staking_status, stake::StakingStatus::NotStaked);
-        assert_eq!(new_balance, old_balance + stake_amount);
-    });
-}
-
-#[test]
-fn create_terminate_group_leader_role_proposal_with_slashing_execution_succeeds() {
-    initial_test_ext().execute_with(|| {
-        let member_id = 1;
-        let account_id: [u8; 32] = [member_id; 32];
-        let stake_amount = 100;
-
-        let opening_policy_commitment = OpeningPolicyCommitment {
-            role_staking_policy: Some(hiring::StakingPolicy {
-                amount: 100,
-                amount_mode: hiring::StakingAmountLimitMode::AtLeast,
-                crowded_out_unstaking_period_length: None,
-                review_period_expired_unstaking_period_length: None,
-            }),
-            ..OpeningPolicyCommitment::default()
-        };
-
-        let opening_id = add_opening(
-            member_id,
-            account_id.clone(),
-            ActivateOpeningAt::CurrentBlock,
-            Some(opening_policy_commitment),
-            1,
-        );
-
-        let apply_result = StorageWorkingGroup::apply_on_opening(
-            RawOrigin::Signed(account_id.clone().into()).into(),
-            member_id as u64,
-            opening_id,
-            account_id.clone().into(),
-            Some(stake_amount),
-            None,
-            Vec::new(),
-        );
-
-        assert_eq!(apply_result, Ok(()));
-
-        let expected_application_id = 0;
-
-        begin_review_applications(member_id, account_id, opening_id, 2);
-
-        let lead = StorageWorkingGroup::current_lead();
-        assert!(lead.is_none());
+    #[test]
+    fn create_slash_group_leader_stake_proposal_execution_succeeds() {
+        // This uses strum crate for enum iteration
+        for group in WorkingGroup::iter() {
+            match group {
+                WorkingGroup::Content => {
+                    run_create_slash_group_leader_stake_proposal_execution_succeeds::<
+                        Runtime,
+                        ContentDirectoryWorkingGroupInstance,
+                    >(group)
+                }
+                WorkingGroup::Storage => {
+                    run_create_slash_group_leader_stake_proposal_execution_succeeds::<
+                        Runtime,
+                        StorageWorkingGroupInstance,
+                    >(group)
+                }
+            }
+        }
+    }
 
-        let old_reward_amount = 100;
-        let reward_policy = Some(RewardPolicy {
-            amount_per_payout: old_reward_amount,
-            next_payment_at_block: 9999,
-            payout_interval: None,
+fn run_create_slash_group_leader_stake_proposal_execution_succeeds<
+    T: working_group::Trait<I> + system::Trait + stake::Trait,
+    I: working_group::Instance,
+>(
+    working_group: WorkingGroup,
+) where
+    <T as system::Trait>::AccountId: From<[u8; 32]>,
+    <T as hiring::Trait>::OpeningId: From<u64>,
+    <T as membership::Trait>::MemberId: From<u64>,
+    <T as membership::Trait>::ActorId: Into<u64>,
+    <<T as stake::Trait>::Currency as traits::Currency<<T as system::Trait>::AccountId>>::Balance:
+        From<u128>,
+{
+        initial_test_ext().execute_with(|| {
+            let member_id: MemberId = 1;
+            let account_id: [u8; 32] = [member_id as u8; 32];
+            let stake_amount: Balance = 100;
+
+            let opening_policy_commitment = OpeningPolicyCommitment {
+                role_staking_policy: Some(hiring::StakingPolicy {
+                    amount: 100,
+                    amount_mode: hiring::StakingAmountLimitMode::AtLeast,
+                    crowded_out_unstaking_period_length: None,
+                    review_period_expired_unstaking_period_length: None,
+                }),
+                ..OpeningPolicyCommitment::default()
+            };
+
+            let opening_id = add_opening(
+                member_id,
+                account_id,
+                ActivateOpeningAt::CurrentBlock,
+                Some(opening_policy_commitment),
+                1,
+                working_group,
+            );
+
+            let apply_result = WorkingGroupInstance::<T, I>::apply_on_opening(
+                RawOrigin::Signed(account_id.into()).into(),
+                member_id.into(),
+                opening_id.into(),
+                account_id.into(),
+                Some(stake_amount.into()),
+                None,
+                Vec::new(),
+            );
+
+            assert_eq!(apply_result, Ok(()));
+
+            let expected_application_id = 0;
+
+            begin_review_applications(member_id, account_id, opening_id, 2, working_group);
+
+            let lead = WorkingGroupInstance::<T, I>::current_lead();
+
+            assert!(lead.is_none());
+
+            fill_opening(
+                member_id,
+                account_id,
+                opening_id,
+                expected_application_id,
+                None,
+                3,
+                working_group,
+            );
+
+            let leader_worker_id = WorkingGroupInstance::<T, I>::current_lead().unwrap();
+
+            let stake_id = 1;
+            let old_balance = Balances::free_balance(&account_id.into());
+            let old_stake = <stake::Module<Runtime>>::stakes(stake_id);
+
+            assert_eq!(get_stake_balance(old_stake), stake_amount);
+
+            let slashing_stake_amount = 30;
+            slash_stake(
+                member_id,
+                account_id,
+                leader_worker_id.into(),
+                slashing_stake_amount,
+                4,
+                working_group,
+            );
+
+            let new_balance = Balances::free_balance(&account_id.into());
+            let new_stake = <stake::Module<Runtime>>::stakes(stake_id);
+
+            assert_eq!(
+                get_stake_balance(new_stake),
+                stake_amount as u128 - slashing_stake_amount
+            );
+            assert_eq!(new_balance, old_balance);
         });
+    }
 
-        set_mint_capacity(member_id, account_id, 999999, 3, false);
-
-        fill_opening(
-            member_id,
-            account_id,
-            opening_id,
-            expected_application_id,
-            reward_policy,
-            4,
-        );
-
-        let leader_worker_id = StorageWorkingGroup::current_lead().unwrap();
-
-        let stake_id = 1;
-        let old_balance = Balances::free_balance(&account_id.into());
-        let old_stake = <stake::Module<Runtime>>::stakes(stake_id);
-
-        assert_eq!(get_stake_balance(old_stake), stake_amount);
-
-        terminate_role(member_id, account_id, leader_worker_id, true, 5);
-
-        assert!(StorageWorkingGroup::current_lead().is_none());
-
-        let new_balance = Balances::free_balance(&account_id.into());
-        let new_stake = <stake::Module<Runtime>>::stakes(stake_id);
-
-        assert_eq!(new_stake.staking_status, stake::StakingStatus::NotStaked);
-        assert_eq!(new_balance, old_balance);
-    });
+    #[test]
+    fn create_set_working_group_mint_capacity_proposal_execution_succeeds() {
+        // This uses strum crate for enum iteration
+        for group in WorkingGroup::iter() {
+            match group {
+                WorkingGroup::Content => {
+                    run_create_set_working_group_mint_capacity_proposal_execution_succeeds::<
+                        Runtime,
+                        ContentDirectoryWorkingGroupInstance,
+                    >(group);
+                }
+                WorkingGroup::Storage => {
+                    run_create_set_working_group_mint_capacity_proposal_execution_succeeds::<
+                        Runtime,
+                        StorageWorkingGroupInstance,
+                    >(group);
+                }
+            }
+        }
+
+        fn run_create_set_working_group_mint_capacity_proposal_execution_succeeds<
+            T: working_group::Trait<I> + system::Trait + minting::Trait,
+            I: working_group::Instance,
+        >(
+            working_group: WorkingGroup,
+        ) where
+            <T as system::Trait>::AccountId: From<[u8; 32]>,
+            <T as membership::Trait>::MemberId: From<u64>,
+            <T as minting::Trait>::MintId: From<u64>,
+            <<T as minting::Trait>::Currency as traits::Currency<
+                <T as system::Trait>::AccountId,
+            >>::Balance: From<u128>,
+        {
+            initial_test_ext().execute_with(|| {
+                let member_id: MemberId = 1;
+                let account_id: [u8; 32] = [member_id as u8; 32];
+
+                assert_eq!(WorkingGroupInstance::<T, I>::mint(), 0.into());
+
+                let mint_capacity = 999999;
+                set_mint_capacity::<T, I>(
+                    member_id,
+                    account_id,
+                    mint_capacity,
+                    1,
+                    true,
+                    working_group,
+                );
+
+                let mint_id = WorkingGroupInstance::<T, I>::mint();
+                let mint = <minting::Module<T>>::mints(mint_id);
+
+                assert_eq!(mint.capacity(), mint_capacity.into());
+            });
+        }
+
+        #[test]
+        fn create_set_group_leader_reward_proposal_execution_succeeds() {
+            // This uses strum crate for enum iteration
+            for group in WorkingGroup::iter() {
+                match group {
+                    WorkingGroup::Content => {
+                        run_create_set_working_group_mint_capacity_proposal_execution_succeeds::<
+                            Runtime,
+                            ContentDirectoryWorkingGroupInstance,
+                        >(group);
+                    }
+                    WorkingGroup::Storage => {
+                        run_create_set_working_group_mint_capacity_proposal_execution_succeeds::<
+                            Runtime,
+                            StorageWorkingGroupInstance,
+                        >(group);
+                    }
+                }
+            }
+        }
+
+        fn run_create_set_group_leader_reward_proposal_execution_succeeds<
+            T: working_group::Trait<I> + system::Trait + minting::Trait,
+            I: working_group::Instance,
+        >(
+            working_group: WorkingGroup,
+        ) where
+            <T as system::Trait>::AccountId: From<[u8; 32]>,
+            <T as membership::Trait>::MemberId: From<u64>,
+            <T as membership::Trait>::ActorId: Into<u64>,
+            <T as minting::Trait>::MintId: From<u64>,
+            <T as hiring::Trait>::OpeningId: From<u64>,
+            <<T as minting::Trait>::Currency as traits::Currency<
+                <T as system::Trait>::AccountId,
+            >>::Balance: From<u128>,
+        {
+            initial_test_ext().execute_with(|| {
+                let member_id: MemberId = 1;
+                let account_id: [u8; 32] = [member_id as u8; 32];
+                let stake_amount = 100;
+
+                let opening_policy_commitment = OpeningPolicyCommitment {
+                    role_staking_policy: Some(hiring::StakingPolicy {
+                        amount: 100,
+                        amount_mode: hiring::StakingAmountLimitMode::AtLeast,
+                        crowded_out_unstaking_period_length: None,
+                        review_period_expired_unstaking_period_length: None,
+                    }),
+                    ..OpeningPolicyCommitment::default()
+                };
+
+                let opening_id = add_opening(
+                    member_id,
+                    account_id,
+                    ActivateOpeningAt::CurrentBlock,
+                    Some(opening_policy_commitment),
+                    1,
+                    working_group,
+                );
+
+                let apply_result = WorkingGroupInstance::<T, I>::apply_on_opening(
+                    RawOrigin::Signed(account_id.into()).into(),
+                    member_id.into(),
+                    opening_id.into(),
+                    account_id.into(),
+                    Some(stake_amount.into()),
+                    None,
+                    Vec::new(),
+                );
+
+                assert_eq!(apply_result, Ok(()));
+
+                let expected_application_id = 0;
+
+                begin_review_applications(member_id, account_id, opening_id, 2, working_group);
+
+                let lead = WorkingGroupInstance::<T, I>::current_lead();
+                assert!(lead.is_none());
+
+                let old_reward_amount = 100;
+                let reward_policy = Some(RewardPolicy {
+                    amount_per_payout: old_reward_amount,
+                    next_payment_at_block: 9999,
+                    payout_interval: None,
+                });
+
+                set_mint_capacity::<T, I>(member_id, account_id, 999999, 3, false, working_group);
+
+                fill_opening(
+                    member_id,
+                    account_id,
+                    opening_id,
+                    expected_application_id,
+                    reward_policy,
+                    4,
+                    working_group,
+                );
+
+                let leader_worker_id = WorkingGroupInstance::<T, I>::current_lead().unwrap();
+
+                let worker = WorkingGroupInstance::<T, I>::worker_by_id(leader_worker_id);
+                let relationship_id = worker.reward_relationship.unwrap();
+
+                let relationship =
+                    recurring_rewards::RewardRelationships::<T>::get(relationship_id);
+                assert_eq!(relationship.amount_per_payout, old_reward_amount.into());
+
+                let new_reward_amount = 999;
+                set_reward(
+                    member_id,
+                    account_id,
+                    leader_worker_id.into(),
+                    new_reward_amount,
+                    5,
+                    working_group,
+                );
+
+                let relationship =
+                    recurring_rewards::RewardRelationships::<T>::get(relationship_id);
+                assert_eq!(relationship.amount_per_payout, new_reward_amount.into());
+            });
+        }
+
+        #[test]
+        fn create_terminate_group_leader_role_proposal_execution_succeeds() {
+            // This uses strum crate for enum iteration
+            for group in WorkingGroup::iter() {
+                match group {
+                    WorkingGroup::Content => {
+                        run_create_terminate_group_leader_role_proposal_execution_succeeds::<
+                            Runtime,
+                            ContentDirectoryWorkingGroupInstance,
+                        >(group);
+                    }
+                    WorkingGroup::Storage => {
+                        run_create_terminate_group_leader_role_proposal_execution_succeeds::<
+                            Runtime,
+                            StorageWorkingGroupInstance,
+                        >(group);
+                    }
+                }
+            }
+        }
+
+        fn run_create_terminate_group_leader_role_proposal_execution_succeeds<
+            T: working_group::Trait<I> + system::Trait + minting::Trait,
+            I: working_group::Instance,
+        >(
+            working_group: WorkingGroup,
+        ) where
+            <T as system::Trait>::AccountId: From<[u8; 32]>,
+            <T as membership::Trait>::MemberId: From<u64>,
+            <T as membership::Trait>::ActorId: Into<u64>,
+            <T as minting::Trait>::MintId: From<u64>,
+            <T as hiring::Trait>::OpeningId: From<u64>,
+            <<T as stake::Trait>::Currency as traits::Currency<
+                <T as system::Trait>::AccountId,
+            >>::Balance: From<u128>,
+        {
+            initial_test_ext().execute_with(|| {
+                let member_id: MemberId = 1;
+                let account_id: [u8; 32] = [0; 32];
+                let stake_amount = 100_u128;
+
+                let opening_policy_commitment = OpeningPolicyCommitment {
+                    role_staking_policy: Some(hiring::StakingPolicy {
+                        amount: 100,
+                        amount_mode: hiring::StakingAmountLimitMode::AtLeast,
+                        crowded_out_unstaking_period_length: None,
+                        review_period_expired_unstaking_period_length: None,
+                    }),
+                    ..OpeningPolicyCommitment::default()
+                };
+
+                let opening_id = add_opening(
+                    member_id.into(),
+                    account_id,
+                    ActivateOpeningAt::CurrentBlock,
+                    Some(opening_policy_commitment),
+                    1,
+                    working_group,
+                );
+
+                let apply_result = WorkingGroupInstance::<T, I>::apply_on_opening(
+                    RawOrigin::Signed(account_id.into()).into(),
+                    member_id.into(),
+                    opening_id.into(),
+                    account_id.into(),
+                    Some(stake_amount.into()),
+                    None,
+                    Vec::new(),
+                );
+
+                assert_eq!(apply_result, Ok(()));
+
+                let expected_application_id = 0;
+
+                begin_review_applications(member_id, account_id, opening_id, 2, working_group);
+
+                let lead = WorkingGroupInstance::<T, I>::current_lead();
+                assert!(lead.is_none());
+
+                let old_reward_amount = 100;
+                let reward_policy = Some(RewardPolicy {
+                    amount_per_payout: old_reward_amount,
+                    next_payment_at_block: 9999,
+                    payout_interval: None,
+                });
+
+                set_mint_capacity::<T, I>(member_id, account_id, 999999, 3, false, working_group);
+
+                fill_opening(
+                    member_id,
+                    account_id,
+                    opening_id,
+                    expected_application_id,
+                    reward_policy,
+                    4,
+                    working_group,
+                );
+
+                let stake_id = 1;
+                let old_balance = Balances::free_balance(&account_id.into());
+                let old_stake = <stake::Module<Runtime>>::stakes(stake_id);
+
+                assert_eq!(get_stake_balance(old_stake), stake_amount);
+
+                let leader_worker_id = WorkingGroupInstance::<T, I>::current_lead().unwrap();
+
+                terminate_role(
+                    member_id,
+                    account_id,
+                    leader_worker_id.into(),
+                    false,
+                    5,
+                    working_group,
+                );
+
+                assert!(WorkingGroupInstance::<T, I>::current_lead().is_none());
+
+                let new_balance = Balances::free_balance(&account_id.into());
+                let new_stake = <stake::Module<Runtime>>::stakes(stake_id);
+
+                assert_eq!(new_stake.staking_status, stake::StakingStatus::NotStaked);
+                assert_eq!(new_balance, old_balance + stake_amount);
+            });
+        }
+
+        #[test]
+        fn create_terminate_group_leader_role_proposal_with_slashing_execution_succeeds() {
+            // This uses strum crate for enum iteration
+            for group in WorkingGroup::iter() {
+                match group {
+                    WorkingGroup::Content => {
+                        run_create_terminate_group_leader_role_proposal_with_slashing_execution_succeeds::<Runtime, ContentDirectoryWorkingGroupInstance>(group);
+                    }
+                    WorkingGroup::Storage => {
+                        run_create_terminate_group_leader_role_proposal_with_slashing_execution_succeeds::<Runtime, StorageWorkingGroupInstance>(group);
+                    }
+                }
+            }
+        }
+
+        fn run_create_terminate_group_leader_role_proposal_with_slashing_execution_succeeds<
+            T: working_group::Trait<I> + system::Trait + minting::Trait,
+            I: working_group::Instance,
+        >(
+            working_group: WorkingGroup,
+        ) where
+            <T as system::Trait>::AccountId: From<[u8; 32]>,
+            <T as membership::Trait>::MemberId: From<u64>,
+            <T as membership::Trait>::ActorId: Into<u64>,
+            <T as minting::Trait>::MintId: From<u64>,
+            <T as hiring::Trait>::OpeningId: From<u64>,
+            <<T as stake::Trait>::Currency as traits::Currency<
+                <T as system::Trait>::AccountId,
+            >>::Balance: From<u128>,
+        {
+            initial_test_ext().execute_with(|| {
+                let member_id: MemberId = 1;
+                let account_id: [u8; 32] = [0; 32];
+                let stake_amount = 100_u128;
+
+                let opening_policy_commitment = OpeningPolicyCommitment {
+                    role_staking_policy: Some(hiring::StakingPolicy {
+                        amount: 100,
+                        amount_mode: hiring::StakingAmountLimitMode::AtLeast,
+                        crowded_out_unstaking_period_length: None,
+                        review_period_expired_unstaking_period_length: None,
+                    }),
+                    ..OpeningPolicyCommitment::default()
+                };
+
+                let opening_id = add_opening(
+                    member_id,
+                    account_id,
+                    ActivateOpeningAt::CurrentBlock,
+                    Some(opening_policy_commitment),
+                    1,
+                    working_group,
+                );
+
+                let apply_result = WorkingGroupInstance::<T, I>::apply_on_opening(
+                    RawOrigin::Signed(account_id.into()).into(),
+                    member_id.into(),
+                    opening_id.into(),
+                    account_id.into(),
+                    Some(stake_amount.into()),
+                    None,
+                    Vec::new(),
+                );
+
+                assert_eq!(apply_result, Ok(()));
+
+                let expected_application_id = 0;
+
+                begin_review_applications(
+                    member_id,
+                    account_id,
+                    opening_id.into(),
+                    2,
+                    working_group,
+                );
+
+                let lead = WorkingGroupInstance::<T, I>::current_lead();
+                assert!(lead.is_none());
+
+                let old_reward_amount = 100;
+                let reward_policy = Some(RewardPolicy {
+                    amount_per_payout: old_reward_amount,
+                    next_payment_at_block: 9999,
+                    payout_interval: None,
+                });
+
+                set_mint_capacity::<T, I>(member_id, account_id, 999999, 3, false, working_group);
+
+                fill_opening(
+                    member_id,
+                    account_id,
+                    opening_id,
+                    expected_application_id,
+                    reward_policy,
+                    4,
+                    working_group,
+                );
+
+                let stake_id = 1;
+                let old_balance = Balances::free_balance(&account_id.into());
+                let old_stake = <stake::Module<Runtime>>::stakes(stake_id);
+
+                assert_eq!(get_stake_balance(old_stake), stake_amount);
+
+                let leader_worker_id = WorkingGroupInstance::<T, I>::current_lead().unwrap();
+
+                terminate_role(
+                    member_id,
+                    account_id,
+                    leader_worker_id.into(),
+                    true,
+                    5,
+                    working_group,
+                );
+
+                assert!(WorkingGroupInstance::<T, I>::current_lead().is_none());
+
+                let new_balance = Balances::free_balance(&account_id.into());
+                let new_stake = <stake::Module<Runtime>>::stakes(stake_id);
+
+                assert_eq!(new_stake.staking_status, stake::StakingStatus::NotStaked);
+                assert_eq!(new_balance, old_balance);
+            });
+        }
+    }
 }