diff --git a/runtime-modules/content-directory/src/class.rs b/runtime-modules/content-directory/src/class.rs new file mode 100644 index 0000000000..08546beda1 --- /dev/null +++ b/runtime-modules/content-directory/src/class.rs @@ -0,0 +1,244 @@ +use super::*; + +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[derive(Encode, Decode, Eq, PartialEq, Clone, Debug)] +pub struct Class { + /// Permissions for an instance of a Class. + class_permissions: ClassPermissions, + /// 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>, + + /// All schemas that are available for this class, think v0.0 Person, v.1.0 Person, etc. + schemas: Vec, + + name: Vec, + + description: Vec, + + /// 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 Default for Class { + fn default() -> Self { + Self { + class_permissions: ClassPermissions::::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 Class { + /// Create new `Class` with provided parameters + pub fn new( + class_permissions: ClassPermissions, + name: Vec, + description: Vec, + 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) { + self.name = name; + } + + pub fn set_description(&mut self, description: Vec) { + 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) { + self.class_permissions = permissions + } + + /// Get Class schemas by mutable reference + pub fn get_schemas_mut(&mut self) -> &mut Vec { + &mut self.schemas + } + + /// Get Class schemas by reference + pub fn get_schemas(&self) -> &Vec { + &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 { + &mut self.class_permissions + } + + /// Retrieve `ClassPermissions` by reference + pub fn get_permissions_ref(&self) -> &ClassPermissions { + &self.class_permissions + } + + /// Retrieve `ClassPermissions` by value + pub fn get_permissions(self) -> ClassPermissions { + self.class_permissions + } + + /// Retrieve `Class` properties by value + pub fn get_properties(self) -> Vec> { + self.properties + } + + /// Replace `Class` properties with updated_class_properties + pub fn set_properties(&mut self, updated_class_properties: Vec>) { + 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, &'static str> { + self.schemas + .get(schema_index as usize) + .ok_or(ERROR_UNKNOWN_CLASS_SCHEMA_ID) + } + + /// Ensure `schema_id` is a valid index of `Class` schemas vector + pub fn ensure_schema_id_exists(&self, schema_id: SchemaId) -> dispatch::Result { + ensure!( + schema_id < self.schemas.len() as SchemaId, + ERROR_UNKNOWN_CLASS_SCHEMA_ID + ); + Ok(()) + } + + /// Ensure `Schema`s limit per `Class` not reached + pub fn ensure_schemas_limit_not_reached(&self) -> dispatch::Result { + ensure!( + (self.schemas.len() as MaxNumber) < T::MaxNumberOfSchemasPerClass::get(), + ERROR_CLASS_SCHEMAS_LIMIT_REACHED + ); + Ok(()) + } + + /// Ensure properties limit per `Schema` not reached + pub fn ensure_properties_limit_not_reached( + &self, + new_properties: &[Property], + ) -> dispatch::Result { + ensure!( + T::MaxNumberOfPropertiesPerSchema::get() + >= (self.properties.len() + new_properties.len()) as MaxNumber, + ERROR_SCHEMA_PROPERTIES_LIMIT_REACHED + ); + Ok(()) + } + + /// Ensure `Class` specific entities limit not reached + pub fn ensure_maximum_entities_count_limit_not_reached(&self) -> dispatch::Result { + ensure!( + self.current_number_of_entities < self.maximum_entities_count, + ERROR_MAX_NUMBER_OF_ENTITIES_PER_CLASS_LIMIT_REACHED + ); + 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, &'static str> { + // 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_CLASS_PROP_NOT_FOUND)?; + + // 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) -> dispatch::Result { + ensure!( + !self + .get_permissions_ref() + .all_entity_property_values_locked(), + ERROR_ALL_PROP_WERE_LOCKED_ON_CLASS_LEVEL + ); + Ok(()) + } +} diff --git a/runtime-modules/content-directory/src/entity.rs b/runtime-modules/content-directory/src/entity.rs new file mode 100644 index 0000000000..15b5055aa8 --- /dev/null +++ b/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))] +#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)] +pub struct Entity { + /// Permissions for an instance of an Entity. + entity_permissions: EntityPermissions, + + /// The class id of this entity. + class_id: T::ClassId, + + /// What schemas under which this entity of a 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, // 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>, + + /// Number of property values referencing current entity + reference_counter: InboundReferenceCounter, +} + +impl Default for Entity { + fn default() -> Self { + Self { + entity_permissions: EntityPermissions::::default(), + class_id: T::ClassId::default(), + supported_schemas: BTreeSet::new(), + values: BTreeMap::new(), + reference_counter: InboundReferenceCounter::default(), + } + } +} + +impl Entity { + /// Create new `Entity` instance, related to a given `class_id` with provided parameters, + pub fn new( + controller: EntityController, + class_id: T::ClassId, + supported_schemas: BTreeSet, + values: BTreeMap>, + ) -> Self { + Self { + entity_permissions: EntityPermissions::::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 { + &mut self.supported_schemas + } + + /// Get `Entity` values by value + pub fn get_values(self) -> BTreeMap> { + self.values + } + + /// Get `Entity` values by reference + pub fn get_values_ref(&self) -> &BTreeMap> { + &self.values + } + + /// Get `Entity` values by mutable reference + pub fn get_values_mut(&mut self) -> &mut BTreeMap> { + &mut self.values + } + + /// Get mutable reference to `Entity` values + pub fn set_values(&mut self, new_values: BTreeMap>) { + self.values = new_values; + } + + /// Get mutable `EntityPermissions` reference, related to given `Entity` + pub fn get_permissions_mut(&mut self) -> &mut EntityPermissions { + &mut self.entity_permissions + } + + /// Get `EntityPermissions` reference, related to given `Entity` + pub fn get_permissions_ref(&self) -> &EntityPermissions { + &self.entity_permissions + } + + /// Get `EntityPermissions`, related to given `Entity` by value + pub fn get_permissions(self) -> EntityPermissions { + self.entity_permissions + } + + /// Update existing `EntityPermissions` with newly provided + pub fn update_permissions(&mut self, permissions: EntityPermissions) { + 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) -> dispatch::Result { + let schema_not_added = !self.supported_schemas.contains(&schema_id); + ensure!(schema_not_added, ERROR_SCHEMA_ALREADY_ADDED_TO_THE_ENTITY); + 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>, + ) -> dispatch::Result { + ensure!( + property_values + .keys() + .all(|key| !self.values.contains_key(key)), + ERROR_ENTITY_ALREADY_CONTAINS_GIVEN_PROPERTY_ID + ); + 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, &'static str> { + 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_UNKNOWN_ENTITY_PROP_ID)? + .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_PROP_VALUE_UNDER_GIVEN_INDEX_IS_NOT_A_VECTOR) + } + + /// Ensure any `InputPropertyValue` from external entity does not point to the given `Entity` + pub fn ensure_rc_is_zero(&self) -> dispatch::Result { + ensure!( + self.reference_counter.is_total_equal_to_zero(), + ERROR_ENTITY_RC_DOES_NOT_EQUAL_TO_ZERO + ); + 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) -> dispatch::Result { + ensure!( + self.reference_counter.is_same_owner_equal_to_zero(), + ERROR_ENTITY_SAME_OWNER_RC_DOES_NOT_EQUAL_TO_ZERO + ); + 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))] +#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Copy, Debug)] +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 + } +} diff --git a/runtime-modules/content-directory/src/lib.rs b/runtime-modules/content-directory/src/lib.rs index 7c3f504d25..85aa443046 100755 --- a/runtime-modules/content-directory/src/lib.rs +++ b/runtime-modules/content-directory/src/lib.rs @@ -5,6 +5,8 @@ #[cfg(test)] mod tests; +mod class; +mod entity; mod errors; mod helpers; mod mock; @@ -12,6 +14,14 @@ 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::fmt::Debug; use core::hash::Hash; use core::ops::AddAssign; @@ -29,12 +39,6 @@ use system::ensure_signed; #[cfg(feature = "std")] pub use serde::{Deserialize, Serialize}; -pub use errors::*; -pub use helpers::*; -pub use operations::*; -pub use permissions::*; -pub use schema::*; - /// Type, used in diffrent numeric constraints representations type MaxNumber = u32; @@ -137,380 +141,6 @@ pub trait Trait: system::Trait + ActorAuthenticator + Debug + Clone { type IndividualEntitiesCreationLimit: Get; } -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -#[derive(Encode, Decode, Eq, PartialEq, Clone, Debug)] -pub struct Class { - /// Permissions for an instance of a Class. - class_permissions: ClassPermissions, - /// 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. - pub properties: Vec>, - - /// All schemas that are available for this class, think v0.0 Person, v.1.0 Person, etc. - pub schemas: Vec, - - pub name: Vec, - - pub description: Vec, - - /// 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 Default for Class { - fn default() -> Self { - Self { - class_permissions: ClassPermissions::::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 Class { - /// Create new `Class` with provided parameters - fn new( - class_permissions: ClassPermissions, - name: Vec, - description: Vec, - 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, - } - } - - /// Used to update `Schema` status under given `schema_index` - 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 - fn update_permissions(&mut self, permissions: ClassPermissions) { - self.class_permissions = permissions - } - - /// Increment number of entities, associated with this class - fn increment_entities_count(&mut self) { - self.current_number_of_entities += T::EntityId::one(); - } - - /// Decrement number of entities, associated with this class - fn decrement_entities_count(&mut self) { - self.current_number_of_entities -= T::EntityId::one(); - } - - /// Retrieve `ClassPermissions` by mutable reference - fn get_permissions_mut(&mut self) -> &mut ClassPermissions { - &mut self.class_permissions - } - - /// Retrieve `ClassPermissions` by reference - fn get_permissions_ref(&self) -> &ClassPermissions { - &self.class_permissions - } - - /// Retrieve `ClassPermissions` by value - fn get_permissions(self) -> ClassPermissions { - self.class_permissions - } - - /// Retrieve `Class` properties by value - fn get_properties(self) -> Vec> { - self.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 - } - - /// Ensure `Class` `Schema` under given index exist, return corresponding `Schema` - fn ensure_schema_exists(&self, schema_index: SchemaId) -> Result<&Schema, &'static str> { - self.schemas - .get(schema_index as usize) - .ok_or(ERROR_UNKNOWN_CLASS_SCHEMA_ID) - } - - /// Ensure `schema_id` is a valid index of `Class` schemas vector - pub fn ensure_schema_id_exists(&self, schema_id: SchemaId) -> dispatch::Result { - ensure!( - schema_id < self.schemas.len() as SchemaId, - ERROR_UNKNOWN_CLASS_SCHEMA_ID - ); - Ok(()) - } - - /// Ensure `Schema`s limit per `Class` not reached - pub fn ensure_schemas_limit_not_reached(&self) -> dispatch::Result { - ensure!( - (self.schemas.len() as MaxNumber) < T::MaxNumberOfSchemasPerClass::get(), - ERROR_CLASS_SCHEMAS_LIMIT_REACHED - ); - Ok(()) - } - - /// Ensure properties limit per `Schema` not reached - pub fn ensure_properties_limit_not_reached( - &self, - new_properties: &[Property], - ) -> dispatch::Result { - ensure!( - T::MaxNumberOfPropertiesPerSchema::get() - >= (self.properties.len() + new_properties.len()) as MaxNumber, - ERROR_SCHEMA_PROPERTIES_LIMIT_REACHED - ); - Ok(()) - } - - /// Ensure `Class` specific entities limit not reached - pub fn ensure_maximum_entities_count_limit_not_reached(&self) -> dispatch::Result { - ensure!( - self.current_number_of_entities < self.maximum_entities_count, - ERROR_MAX_NUMBER_OF_ENTITIES_PER_CLASS_LIMIT_REACHED - ); - 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, &'static str> { - // 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_CLASS_PROP_NOT_FOUND)?; - - // 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) -> dispatch::Result { - ensure!( - !self - .get_permissions_ref() - .all_entity_property_values_locked(), - ERROR_ALL_PROP_WERE_LOCKED_ON_CLASS_LEVEL - ); - Ok(()) - } -} - -/// Structure, respresenting inbound entity rcs for each `Entity` -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Copy, Debug)] -pub struct InboundReferenceCounter { - /// Total number of inbound references from another entities - total: u32, - /// Number of inbound references from another entities with `SameOwner` flag set - 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 - } -} - -/// Represents `Entity`, related to a specific `Class` -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)] -pub struct Entity { - /// Permissions for an instance of an Entity. - pub entity_permissions: EntityPermissions, - - /// The class id of this entity. - pub class_id: T::ClassId, - - /// What schemas under which this entity of a 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. - pub supported_schemas: BTreeSet, // 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. - pub values: BTreeMap>, - - /// Number of property values referencing current entity - pub reference_counter: InboundReferenceCounter, -} - -impl Default for Entity { - fn default() -> Self { - Self { - entity_permissions: EntityPermissions::::default(), - class_id: T::ClassId::default(), - supported_schemas: BTreeSet::new(), - values: BTreeMap::new(), - reference_counter: InboundReferenceCounter::default(), - } - } -} - -impl Entity { - /// Create new `Entity` instance, related to a given `class_id` with provided parameters, - fn new( - controller: EntityController, - class_id: T::ClassId, - supported_schemas: BTreeSet, - values: BTreeMap>, - ) -> Self { - Self { - entity_permissions: EntityPermissions::::default_with_controller(controller), - class_id, - supported_schemas, - values, - reference_counter: InboundReferenceCounter::default(), - } - } - - /// Get `Entity` values by value - fn get_values(self) -> BTreeMap> { - self.values - } - - /// Get mutable `EntityPermissions` reference, related to given `Entity` - fn get_permissions_mut(&mut self) -> &mut EntityPermissions { - &mut self.entity_permissions - } - - /// Get `EntityPermissions` reference, related to given `Entity` - fn get_permissions_ref(&self) -> &EntityPermissions { - &self.entity_permissions - } - - /// Get `EntityPermissions`, related to given `Entity` by value - fn get_permissions(self) -> EntityPermissions { - self.entity_permissions - } - - /// Update existing `EntityPermissions` with newly provided - pub fn update_permissions(&mut self, permissions: EntityPermissions) { - 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) -> dispatch::Result { - let schema_not_added = !self.supported_schemas.contains(&schema_id); - ensure!(schema_not_added, ERROR_SCHEMA_ALREADY_ADDED_TO_THE_ENTITY); - 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>, - ) -> dispatch::Result { - ensure!( - property_values - .keys() - .all(|key| !self.values.contains_key(key)), - ERROR_ENTITY_ALREADY_CONTAINS_GIVEN_PROPERTY_ID - ); - Ok(()) - } - - /// Ensure InputPropertyValue under given `in_class_schema_property_id` is Vector - fn ensure_property_value_is_vec( - &self, - in_class_schema_property_id: PropertyId, - ) -> Result, &'static str> { - 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_UNKNOWN_ENTITY_PROP_ID)? - .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_PROP_VALUE_UNDER_GIVEN_INDEX_IS_NOT_A_VECTOR) - } - - /// Ensure any `InputPropertyValue` from external entity does not point to the given `Entity` - pub fn ensure_rc_is_zero(&self) -> dispatch::Result { - ensure!( - self.reference_counter.is_total_equal_to_zero(), - ERROR_ENTITY_RC_DOES_NOT_EQUAL_TO_ZERO - ); - 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) -> dispatch::Result { - ensure!( - self.reference_counter.is_same_owner_equal_to_zero(), - ERROR_ENTITY_SAME_OWNER_RC_DOES_NOT_EQUAL_TO_ZERO - ); - Ok(()) - } - - /// Get mutable reference to the `Entity`'s `InboundReferenceCounter` instance - pub fn get_reference_counter_mut(&mut self) -> &mut InboundReferenceCounter { - &mut self.reference_counter - } -} - decl_storage! { trait Store for Module as ContentDirectory { @@ -945,7 +575,7 @@ decl_module! { Self::ensure_all_properties_are_valid(&new_properties)?; // Id of next Class Schema being added - let schema_id = class.schemas.len() as SchemaId; + let schema_id = class.get_schemas().len() as SchemaId; let class_properties = class.get_properties(); @@ -967,8 +597,8 @@ decl_module! { // Update Class properties and schemas >::mutate(class_id, |class| { - class.properties = updated_class_properties; - class.schemas.push(schema); + class.set_properties(updated_class_properties); + class.get_schemas_mut().push(schema); }); // Trigger event @@ -1066,9 +696,9 @@ decl_module! { // 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.properties; + let class_properties = class.get_properties(); - let entity_property_values = entity.values; + 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 = OutputValuesForExistingProperties::from(&class_properties, &entity_property_values)?; @@ -1145,7 +775,7 @@ decl_module! { >::mutate(entity_id, |entity| { // Update current Entity property values with updated ones - entity.values = entity_property_values_updated; + 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()); @@ -1281,12 +911,12 @@ decl_module! { >::remove(entity_id); // Decrement class entities counter - >::mutate(entity.class_id, |class| class.decrement_entities_count()); + >::mutate(entity.get_class_id(), |class| class.decrement_entities_count()); let entity_controller = EntityController::::from_actor(&actor); // Decrement entity_creation_voucher after entity removal perfomed - >::mutate(entity.class_id, entity_controller, |entity_creation_voucher| { + >::mutate(entity.get_class_id(), entity_controller, |entity_creation_voucher| { entity_creation_voucher.decrement_created_entities_count(); }); @@ -1310,7 +940,7 @@ decl_module! { // Ensure Class Schema under given index exists, return corresponding Schema let schema = class.ensure_schema_exists(schema_id)?.to_owned(); - let class_properties = class.properties; + 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)?; @@ -1377,11 +1007,11 @@ decl_module! { >::mutate(entity_id, |entity| { // Add a new schema to the list of schemas supported by this entity. - entity.supported_schemas.insert(schema_id); + entity.get_supported_schemas_mut().insert(schema_id); // Update entity values only if new properties have been added. - if entity_values_updated.len() > entity.values.len() { - entity.values = entity_values_updated; + if entity_values_updated.len() > entity.get_values_ref().len() { + entity.set_values(entity_values_updated); } }); @@ -1404,14 +1034,16 @@ decl_module! { // 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, new_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, &new_property_values)?; + Self::ensure_all_property_values_are_already_added(entity_values_ref, &new_property_values)?; - let class_properties = class.properties; + 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)?; @@ -1428,7 +1060,7 @@ decl_module! { // Get current property values of an entity, // so we can update them if new values provided present in new_property_values. - let entity_property_values = entity.values; + let entity_property_values = entity.get_values(); // Make updated entity_property_values from current entity_property_values and new_property_values provided let entity_property_values_updated = if let Some(entity_property_values_updated) = @@ -1463,7 +1095,7 @@ decl_module! { // Update entity property values >::mutate(entity_id, |entity| { - entity.values = entity_property_values_updated; + entity.set_values(entity_property_values_updated); }); // Trigger event @@ -1511,12 +1143,12 @@ decl_module! { // 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.values, in_class_schema_property_id, empty_property_value_vector + entity.get_values(), in_class_schema_property_id, empty_property_value_vector ); // Update entity property values >::mutate(entity_id, |entity| { - entity.values = entity_values_updated + entity.set_values(entity_values_updated); }); // Trigger event @@ -1591,12 +1223,12 @@ decl_module! { // 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.values, in_class_schema_property_id, property_value_vector_updated + entity.get_values(), in_class_schema_property_id, property_value_vector_updated ); // Update entity property values >::mutate(entity_id, |entity| { - entity.values = entity_values_updated; + entity.set_values(entity_values_updated); }); // Trigger event @@ -1675,12 +1307,12 @@ decl_module! { // 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.values, in_class_schema_property_id, property_value_vector_updated + entity.get_values(), in_class_schema_property_id, property_value_vector_updated ); // Update entity property values >::mutate(entity_id, |entity| { - entity.values = entity_values_updated + entity.set_values(entity_values_updated); }); // Trigger event @@ -2059,7 +1691,7 @@ impl Module { let entity = Self::ensure_known_entity_id(entity_id)?; // Retrieve corresponding Class - let class = Self::class_by_id(entity.class_id); + let class = Self::class_by_id(entity.get_class_id()); // Derive EntityAccessLevel for the actor, attempting to act. let access_level = EntityAccessLevel::derive( @@ -2079,7 +1711,7 @@ impl Module { // Ensure Entity under given id exists, retrieve corresponding one let entity = Self::ensure_known_entity_id(entity_id)?; - let class = ClassById::get(entity.class_id); + let class = ClassById::get(entity.get_class_id()); Ok((entity, class)) } diff --git a/runtime-modules/content-directory/src/mock.rs b/runtime-modules/content-directory/src/mock.rs index 454f06b58f..05dfcc1128 100644 --- a/runtime-modules/content-directory/src/mock.rs +++ b/runtime-modules/content-directory/src/mock.rs @@ -106,7 +106,7 @@ thread_local! { static VEC_MAX_LENGTH_CONSTRAINT: RefCell = RefCell::new(0); static TEXT_MAX_LENGTH_CONSTRAINT: RefCell = RefCell::new(0); - static HASHED_TEXT_MAX_LENGTH_CONSTRAINT: RefCell = RefCell::new(0); + static HASHED_TEXT_MAX_LENGTH_CONSTRAINT: RefCell = RefCell::new(Some(0)); static INDIVIDUAL_ENTITIES_CREATION_LIMIT: RefCell = RefCell::new(0); } @@ -343,7 +343,7 @@ impl Default for ExtBuilder { vec_max_length_constraint: 200, text_max_length_constraint: 5000, - hashed_text_max_length_constraint: 25000, + hashed_text_max_length_constraint: Some(25000), individual_entities_creation_limit: 50, } @@ -545,30 +545,37 @@ pub fn create_simple_class(lead_origin: u64, class_type: ClassType) -> Result<() match class_type { ClassType::Valid => (), ClassType::NameTooShort => { - class.name = generate_text(ClassNameLengthConstraint::get().min() as usize - 1); + class.set_name(generate_text( + ClassNameLengthConstraint::get().min() as usize - 1, + )); } ClassType::NameTooLong => { - class.name = generate_text(ClassNameLengthConstraint::get().max() as usize + 1); + class.set_name(generate_text( + ClassNameLengthConstraint::get().max() as usize + 1, + )); } ClassType::DescriptionTooLong => { - class.description = - generate_text(ClassDescriptionLengthConstraint::get().max() as usize + 1); + class.set_description(generate_text( + ClassDescriptionLengthConstraint::get().max() as usize + 1, + )); } ClassType::DescriptionTooShort => { - class.description = - generate_text(ClassDescriptionLengthConstraint::get().min() as usize - 1); + class.set_description(generate_text( + ClassDescriptionLengthConstraint::get().min() as usize - 1, + )); } ClassType::InvalidMaximumEntitiesCount => { - class.maximum_entities_count = MaxNumberOfEntitiesPerClass::get() + 1; + class.set_maximum_entities_count(MaxNumberOfEntitiesPerClass::get() + 1); } ClassType::InvalidDefaultVoucherUpperBound => { - class.default_entity_creation_voucher_upper_bound = - IndividualEntitiesCreationLimit::get() + 1; + class.set_default_entity_creation_voucher_upper_bound( + IndividualEntitiesCreationLimit::get() + 1, + ); } ClassType::DefaultVoucherUpperBoundExceedsMaximumEntitiesCount => { - class.default_entity_creation_voucher_upper_bound = 5; + class.set_maximum_entities_count(5); - class.maximum_entities_count = 3; + class.set_maximum_entities_count(3); } ClassType::MaintainersLimitReached => { let mut maintainers = BTreeSet::new(); @@ -584,11 +591,11 @@ pub fn create_simple_class(lead_origin: u64, class_type: ClassType) -> Result<() }; TestModule::create_class( Origin::signed(lead_origin), - class.name, - class.description, - class.class_permissions, - class.maximum_entities_count, - class.default_entity_creation_voucher_upper_bound, + 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(), ) } @@ -864,6 +871,7 @@ pub enum InvalidPropertyType { DescriptionTooLong, DescriptionTooShort, TextIsTooLong, + TextHashIsTooLong, VecIsTooLong, } @@ -921,6 +929,12 @@ impl Property { default_property.property_type = PropertyType::::single_text(TextMaxLengthConstraint::get() + 1); } + InvalidPropertyType::TextHashIsTooLong => { + if let Some(hashed_text_max_len) = HashedTextMaxLengthConstraint::get() { + default_property.property_type = + PropertyType::::single_text_hash(Some(hashed_text_max_len + 1)); + } + } InvalidPropertyType::VecIsTooLong => { default_property.property_type = PropertyType::::vec_reference( FIRST_CLASS_ID, @@ -957,6 +971,20 @@ impl PropertyType { let text_type = SingleValuePropertyType(Type::::Text(text_max_len)); PropertyType::::Single(text_type) } + + pub fn single_text_hash(text_hash_max_len: HashedTextMaxLength) -> PropertyType { + let text_type = SingleValuePropertyType(Type::::Hash(text_hash_max_len)); + PropertyType::::Single(text_type) + } + + pub fn vec_text_hash( + text_hash_max_len: HashedTextMaxLength, + vec_max_length: VecMaxLength, + ) -> PropertyType { + let vec_type = Type::::Hash(text_hash_max_len); + let vec_text_hash = VecPropertyType::::new(vec_type, vec_max_length); + PropertyType::::Vector(vec_text_hash) + } } impl InputPropertyValue { @@ -970,10 +998,20 @@ impl InputPropertyValue { InputPropertyValue::::Vector(vec_value) } + pub fn vec_text_to_hash(texts: Vec>) -> InputPropertyValue { + let vec_value = VecInputValue::::TextToHash(texts); + InputPropertyValue::::Vector(vec_value) + } + pub fn single_text(text_len: TextMaxLength) -> InputPropertyValue { let text_value = InputValue::::Text(generate_text(text_len as usize)); InputPropertyValue::::Single(text_value) } + + pub fn single_text_to_hash(text_len: TextMaxLength) -> InputPropertyValue { + let text_value = InputValue::::TextToHash(generate_text(text_len as usize)); + InputPropertyValue::::Single(text_value) + } } impl From for EntityReferenceCounterSideEffect { diff --git a/runtime-modules/content-directory/src/permissions.rs b/runtime-modules/content-directory/src/permissions.rs index 93d0744af6..5acd758df6 100644 --- a/runtime-modules/content-directory/src/permissions.rs +++ b/runtime-modules/content-directory/src/permissions.rs @@ -1,6 +1,16 @@ -use crate::errors::*; +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::*; -use codec::{Codec, Decode, Encode}; +pub use codec::{Codec, Decode, Encode}; use core::fmt::Debug; use runtime_primitives::traits::{MaybeSerializeDeserialize, Member, SimpleArithmetic}; @@ -98,182 +108,6 @@ pub fn ensure_is_lead(origin: T::Origin) -> dispatch::Res ensure_lead_auth_success::(&account_id) } -/// 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, -) -> dispatch::Result { - // Ensure curator authorization performed succesfully - ensure_curator_auth_success::(curator_id, account_id)?; - - // Retrieve corresponding curator group - let curator_group = Module::::curator_group_by_id(curator_group_id); - - // Ensure curator group is active - ensure!(curator_group.is_active(), ERROR_CURATOR_GROUP_IS_NOT_ACTIVE); - - // Ensure curator under given curator_id exists in CuratorGroup - CuratorGroup::::ensure_curator_in_group_exists(&curator_group, curator_id)?; - Ok(()) -} - -/// A group, that consists of `curators` set -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -#[derive(Encode, Decode, Eq, PartialEq, Clone, Debug)] -pub struct CuratorGroup { - /// Curators set, associated with a iven curator group - curators: BTreeSet, - - /// 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 Default for CuratorGroup { - fn default() -> Self { - Self { - curators: BTreeSet::new(), - // default curator group status right after creation - active: false, - number_of_classes_maintained: 0, - } - } -} - -impl CuratorGroup { - /// 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 { - &self.curators - } - - /// Retrieve set of all curator_ids related to `CuratorGroup` by mutable reference - pub fn get_curators_mut(&mut self) -> &mut BTreeSet { - &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) -> dispatch::Result { - ensure!( - self.number_of_classes_maintained == 0, - ERROR_CURATOR_GROUP_REMOVAL_FORBIDDEN - ); - Ok(()) - } - - /// Ensure `MaxNumberOfCuratorsPerGroup` constraint satisfied - pub fn ensure_max_number_of_curators_limit_not_reached(&self) -> dispatch::Result { - ensure!( - self.curators.len() < T::MaxNumberOfCuratorsPerGroup::get() as usize, - ERROR_NUMBER_OF_CURATORS_PER_GROUP_LIMIT_REACHED - ); - Ok(()) - } - - /// Ensure curator under given `curator_id` exists in `CuratorGroup` - pub fn ensure_curator_in_group_exists(&self, curator_id: &T::CuratorId) -> dispatch::Result { - ensure!( - self.get_curators().contains(curator_id), - ERROR_CURATOR_IS_NOT_A_MEMBER_OF_A_GIVEN_CURATOR_GROUP - ); - Ok(()) - } -} - -/// A voucher for `Entity` creation -#[derive(Encode, Decode, Clone, Copy, Debug, PartialEq, Eq)] -pub struct EntityCreationVoucher { - /// How many are allowed in total - pub maximum_entities_count: T::EntityId, - - /// How many have currently been created - pub entities_created: T::EntityId, -} - -impl Default for EntityCreationVoucher { - fn default() -> Self { - Self { - maximum_entities_count: T::EntityId::zero(), - entities_created: T::EntityId::zero(), - } - } -} - -impl EntityCreationVoucher { - /// 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 new voucher`s max entities count is less than number of already created entities in this `EntityCreationVoucher` - pub fn ensure_new_max_entities_count_is_valid( - self, - maximum_entities_count: T::EntityId, - ) -> dispatch::Result { - ensure!( - maximum_entities_count >= self.entities_created, - ERROR_NEW_ENTITIES_MAX_COUNT_IS_LESS_THAN_NUMBER_OF_ALREADY_CREATED - ); - Ok(()) - } - - /// Ensure voucher limit not reached - pub fn ensure_voucher_limit_not_reached(&self) -> dispatch::Result { - ensure!(self.limit_not_reached(), ERROR_VOUCHER_LIMIT_REACHED); - Ok(()) - } -} - /// Enum, representing all possible `Actor`s #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] #[derive(Encode, Decode, Eq, PartialEq, Clone, Copy, Debug)] @@ -288,312 +122,3 @@ impl Default for Actor { Self::Lead } } - -/// Permissions for an instance of a `Class` in the versioned store. -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -#[derive(Encode, Decode, Eq, PartialEq, Clone, Debug)] -pub struct ClassPermissions { - /// 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, -} - -impl Default for ClassPermissions { - fn default() -> Self { - Self { - any_member: false, - entity_creation_blocked: false, - all_entity_property_values_locked: false, - maintainers: BTreeSet::new(), - } - } -} - -impl ClassPermissions { - /// 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 { - &self.maintainers - } - - /// Get `Class` maintainers by mutable reference - pub fn get_maintainers_mut(&mut self) -> &mut BTreeSet { - &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) { - 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, - ) -> dispatch::Result { - let can_create = match &actor { - Actor::Lead => { - // Ensure lead authorization performed succesfully - ensure_lead_auth_success::(account_id)?; - true - } - Actor::Member(member_id) if self.any_member => { - // Ensure member authorization performed succesfully - ensure_member_auth_success::(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 - perform_curator_in_group_auth::(curator_id, curator_group_id, account_id)?; - true - } - _ => false, - }; - ensure!(can_create, ERROR_ACTOR_CAN_NOT_CREATE_ENTITIES); - Ok(()) - } - - /// Ensure entities creation is not blocked on `Class` level - pub fn ensure_entity_creation_not_blocked(&self) -> dispatch::Result { - ensure!(!self.entity_creation_blocked, ERROR_ENTITY_CREATION_BLOCKED); - 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, - ) -> dispatch::Result { - ensure!( - self.maintainers.contains(curator_group_id), - ERROR_MAINTAINER_DOES_NOT_EXIST - ); - 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, - ) -> dispatch::Result { - ensure!( - !self.maintainers.contains(curator_group_id), - ERROR_MAINTAINER_ALREADY_EXISTS - ); - Ok(()) - } -} - -/// Owner of an `Entity`. -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, Debug)] -pub enum EntityController { - Maintainers, - Member(T::MemberId), - Lead, -} - -impl EntityController { - /// Create `EntityController` enum representation, using provided `Actor` - pub fn from_actor(actor: &Actor) -> Self { - match &actor { - Actor::Lead => Self::Lead, - Actor::Member(member_id) => Self::Member(*member_id), - Actor::Curator(_, _) => Self::Maintainers, - } - } -} - -impl Default for EntityController { - fn default() -> Self { - Self::Lead - } -} - -/// Permissions for a given entity. -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)] -pub struct EntityPermissions { - /// Current controller, which is initially set based on who created entity - pub controller: EntityController, - - /// 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 Default for EntityPermissions { - fn default() -> Self { - Self { - controller: EntityController::::default(), - frozen: false, - referenceable: true, - } - } -} - -impl EntityPermissions { - /// Create an instance of `EntityPermissions` with `EntityController` equal to provided one - pub fn default_with_controller(controller: EntityController) -> Self { - Self { - controller, - ..EntityPermissions::default() - } - } - - /// Set current `controller` as provided - pub fn set_conroller(&mut self, controller: EntityController) { - self.controller = controller - } - - /// Check if inner `controller` is equal to the provided one - pub fn controller_is_equal_to(&self, new_entity_controller: &EntityController) -> 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 { - &self.controller - } - - /// Ensure actor with given `EntityAccessLevel` can remove entity - pub fn ensure_group_can_remove_entity(access_level: EntityAccessLevel) -> dispatch::Result { - match access_level { - EntityAccessLevel::EntityController => Ok(()), - EntityAccessLevel::EntityControllerAndMaintainer => Ok(()), - _ => Err(ERROR_ENTITY_REMOVAL_ACCESS_DENIED), - } - } - - /// Ensure provided new_entity_controller is not equal to current one - pub fn ensure_controllers_are_not_equal( - &self, - new_entity_controller: &EntityController, - ) -> dispatch::Result { - ensure!( - !self.controller_is_equal_to(new_entity_controller), - ERROR_PROVIDED_ENTITY_CONTROLLER_IS_EQUAL_TO_CURRENT_ONE - ); - Ok(()) - } -} - -/// Type, derived from dispatchable call, identifies the caller -#[derive(Encode, Decode, Eq, PartialEq, Ord, PartialOrd, Clone, Copy, Debug)] -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( - account_id: &T::AccountId, - entity_permissions: &EntityPermissions, - class_permissions: &ClassPermissions, - actor: &Actor, - ) -> Result { - let controller = EntityController::::from_actor(actor); - match actor { - Actor::Lead if entity_permissions.controller_is_equal_to(&controller) => { - // Ensure lead authorization performed succesfully - ensure_lead_auth_success::(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::(member_id, account_id) - .map(|_| Self::EntityController) - } - Actor::Curator(curator_group_id, curator_id) => { - // Authorize curator, performing all checks to ensure curator can act - 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_ENTITY_ACCESS_DENIED), - } - } - _ => Err(ERROR_ENTITY_ACCESS_DENIED), - } - } -} diff --git a/runtime-modules/content-directory/src/permissions/class.rs b/runtime-modules/content-directory/src/permissions/class.rs new file mode 100644 index 0000000000..5d90bb6ff9 --- /dev/null +++ b/runtime-modules/content-directory/src/permissions/class.rs @@ -0,0 +1,149 @@ +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, Debug)] +pub struct ClassPermissions { + /// 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, +} + +impl Default for ClassPermissions { + fn default() -> Self { + Self { + any_member: false, + entity_creation_blocked: false, + all_entity_property_values_locked: false, + maintainers: BTreeSet::new(), + } + } +} + +impl ClassPermissions { + /// 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 { + &self.maintainers + } + + /// Get `Class` maintainers by mutable reference + pub fn get_maintainers_mut(&mut self) -> &mut BTreeSet { + &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) { + 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, + ) -> dispatch::Result { + let can_create = match &actor { + Actor::Lead => { + // Ensure lead authorization performed succesfully + ensure_lead_auth_success::(account_id)?; + true + } + Actor::Member(member_id) if self.any_member => { + // Ensure member authorization performed succesfully + ensure_member_auth_success::(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::::perform_curator_in_group_auth( + curator_id, + curator_group_id, + account_id, + )?; + true + } + _ => false, + }; + ensure!(can_create, ERROR_ACTOR_CAN_NOT_CREATE_ENTITIES); + Ok(()) + } + + /// Ensure entities creation is not blocked on `Class` level + pub fn ensure_entity_creation_not_blocked(&self) -> dispatch::Result { + ensure!(!self.entity_creation_blocked, ERROR_ENTITY_CREATION_BLOCKED); + 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, + ) -> dispatch::Result { + ensure!( + self.maintainers.contains(curator_group_id), + ERROR_MAINTAINER_DOES_NOT_EXIST + ); + 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, + ) -> dispatch::Result { + ensure!( + !self.maintainers.contains(curator_group_id), + ERROR_MAINTAINER_ALREADY_EXISTS + ); + Ok(()) + } +} diff --git a/runtime-modules/content-directory/src/permissions/curator_group.rs b/runtime-modules/content-directory/src/permissions/curator_group.rs new file mode 100644 index 0000000000..dd520333ee --- /dev/null +++ b/runtime-modules/content-directory/src/permissions/curator_group.rs @@ -0,0 +1,110 @@ +use super::*; + +/// A group, that consists of `curators` set +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[derive(Encode, Decode, Eq, PartialEq, Clone, Debug)] +pub struct CuratorGroup { + /// Curators set, associated with a iven curator group + curators: BTreeSet, + + /// 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 Default for CuratorGroup { + fn default() -> Self { + Self { + curators: BTreeSet::new(), + // default curator group status right after creation + active: false, + number_of_classes_maintained: 0, + } + } +} + +impl CuratorGroup { + /// 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 { + &self.curators + } + + /// Retrieve set of all curator_ids related to `CuratorGroup` by mutable reference + pub fn get_curators_mut(&mut self) -> &mut BTreeSet { + &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) -> dispatch::Result { + ensure!( + self.number_of_classes_maintained == 0, + ERROR_CURATOR_GROUP_REMOVAL_FORBIDDEN + ); + Ok(()) + } + + /// Ensure `MaxNumberOfCuratorsPerGroup` constraint satisfied + pub fn ensure_max_number_of_curators_limit_not_reached(&self) -> dispatch::Result { + ensure!( + self.curators.len() < T::MaxNumberOfCuratorsPerGroup::get() as usize, + ERROR_NUMBER_OF_CURATORS_PER_GROUP_LIMIT_REACHED + ); + Ok(()) + } + + /// Ensure curator under given `curator_id` exists in `CuratorGroup` + pub fn ensure_curator_in_group_exists(&self, curator_id: &T::CuratorId) -> dispatch::Result { + ensure!( + self.get_curators().contains(curator_id), + ERROR_CURATOR_IS_NOT_A_MEMBER_OF_A_GIVEN_CURATOR_GROUP + ); + 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, + ) -> dispatch::Result { + // Ensure curator authorization performed succesfully + ensure_curator_auth_success::(curator_id, account_id)?; + + // Retrieve corresponding curator group + let curator_group = Module::::curator_group_by_id(curator_group_id); + + // Ensure curator group is active + ensure!(curator_group.is_active(), ERROR_CURATOR_GROUP_IS_NOT_ACTIVE); + + // Ensure curator under given curator_id exists in CuratorGroup + Self::ensure_curator_in_group_exists(&curator_group, curator_id)?; + Ok(()) + } +} diff --git a/runtime-modules/content-directory/src/permissions/entity.rs b/runtime-modules/content-directory/src/permissions/entity.rs new file mode 100644 index 0000000000..5657798d3a --- /dev/null +++ b/runtime-modules/content-directory/src/permissions/entity.rs @@ -0,0 +1,170 @@ +use super::*; + +/// Owner of an `Entity`. +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, Debug)] +pub enum EntityController { + Maintainers, + Member(T::MemberId), + Lead, +} + +impl EntityController { + /// Create `EntityController` enum representation, using provided `Actor` + pub fn from_actor(actor: &Actor) -> Self { + match &actor { + Actor::Lead => Self::Lead, + Actor::Member(member_id) => Self::Member(*member_id), + Actor::Curator(_, _) => Self::Maintainers, + } + } +} + +impl Default for EntityController { + fn default() -> Self { + Self::Lead + } +} + +/// Permissions for a given entity. +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)] +pub struct EntityPermissions { + /// Current controller, which is initially set based on who created entity + pub controller: EntityController, + + /// 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 Default for EntityPermissions { + fn default() -> Self { + Self { + controller: EntityController::::default(), + frozen: false, + referenceable: true, + } + } +} + +impl EntityPermissions { + /// Create an instance of `EntityPermissions` with `EntityController` equal to provided one + pub fn default_with_controller(controller: EntityController) -> Self { + Self { + controller, + ..EntityPermissions::default() + } + } + + /// Set current `controller` as provided + pub fn set_conroller(&mut self, controller: EntityController) { + self.controller = controller + } + + /// Check if inner `controller` is equal to the provided one + pub fn controller_is_equal_to(&self, new_entity_controller: &EntityController) -> 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 { + &self.controller + } + + /// Ensure actor with given `EntityAccessLevel` can remove entity + pub fn ensure_group_can_remove_entity(access_level: EntityAccessLevel) -> dispatch::Result { + match access_level { + EntityAccessLevel::EntityController => Ok(()), + EntityAccessLevel::EntityControllerAndMaintainer => Ok(()), + _ => Err(ERROR_ENTITY_REMOVAL_ACCESS_DENIED), + } + } + + /// Ensure provided new_entity_controller is not equal to current one + pub fn ensure_controllers_are_not_equal( + &self, + new_entity_controller: &EntityController, + ) -> dispatch::Result { + ensure!( + !self.controller_is_equal_to(new_entity_controller), + ERROR_PROVIDED_ENTITY_CONTROLLER_IS_EQUAL_TO_CURRENT_ONE + ); + Ok(()) + } +} + +/// Type, derived from dispatchable call, identifies the caller +#[derive(Encode, Decode, Eq, PartialEq, Ord, PartialOrd, Clone, Copy, Debug)] +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( + account_id: &T::AccountId, + entity_permissions: &EntityPermissions, + class_permissions: &ClassPermissions, + actor: &Actor, + ) -> Result { + let controller = EntityController::::from_actor(actor); + match actor { + Actor::Lead if entity_permissions.controller_is_equal_to(&controller) => { + // Ensure lead authorization performed succesfully + ensure_lead_auth_success::(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::(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::::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_ENTITY_ACCESS_DENIED), + } + } + _ => Err(ERROR_ENTITY_ACCESS_DENIED), + } + } +} diff --git a/runtime-modules/content-directory/src/permissions/entity_creation_voucher.rs b/runtime-modules/content-directory/src/permissions/entity_creation_voucher.rs new file mode 100644 index 0000000000..24d687e2ba --- /dev/null +++ b/runtime-modules/content-directory/src/permissions/entity_creation_voucher.rs @@ -0,0 +1,68 @@ +use super::*; + +/// A voucher for `Entity` creation +#[derive(Encode, Decode, Clone, Copy, Debug, PartialEq, Eq)] +pub struct EntityCreationVoucher { + /// How many are allowed in total + pub maximum_entities_count: T::EntityId, + + /// How many have currently been created + pub entities_created: T::EntityId, +} + +impl Default for EntityCreationVoucher { + fn default() -> Self { + Self { + maximum_entities_count: T::EntityId::zero(), + entities_created: T::EntityId::zero(), + } + } +} + +impl EntityCreationVoucher { + /// 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 new voucher`s max entities count is less than number of already created entities in this `EntityCreationVoucher` + pub fn ensure_new_max_entities_count_is_valid( + self, + maximum_entities_count: T::EntityId, + ) -> dispatch::Result { + ensure!( + maximum_entities_count >= self.entities_created, + ERROR_NEW_ENTITIES_MAX_COUNT_IS_LESS_THAN_NUMBER_OF_ALREADY_CREATED + ); + Ok(()) + } + + /// Ensure voucher limit not reached + pub fn ensure_voucher_limit_not_reached(&self) -> dispatch::Result { + ensure!(self.limit_not_reached(), ERROR_VOUCHER_LIMIT_REACHED); + Ok(()) + } +} diff --git a/runtime-modules/content-directory/src/schema.rs b/runtime-modules/content-directory/src/schema.rs index 933d7e8a90..cecec57669 100644 --- a/runtime-modules/content-directory/src/schema.rs +++ b/runtime-modules/content-directory/src/schema.rs @@ -19,7 +19,7 @@ pub type VecMaxLength = u16; pub type TextMaxLength = u16; /// Type representing max length of text property type, that will be subsequently hashed -pub type HashedTextMaxLength = u16; +pub type HashedTextMaxLength = Option; /// Type identificator for property id pub type PropertyId = u16; @@ -130,7 +130,7 @@ impl Default for SingleValuePropertyType { } impl SingleValuePropertyType { - /// Ensure `Type` specific `TextMaxLengthConstraint` satisfied + /// Ensure `Type` specific `TextMaxLengthConstraint` or `HashedTextMaxLengthConstraint` satisfied fn ensure_property_type_size_is_valid(&self) -> dispatch::Result { if let Type::Text(text_max_len) = self.0 { ensure!( @@ -138,6 +138,14 @@ impl SingleValuePropertyType { ERROR_TEXT_PROP_IS_TOO_LONG ); } + + if let Type::Hash(hashed_text_max_len) = self.0 { + ensure!( + hashed_text_max_len <= T::HashedTextMaxLengthConstraint::get(), + ERROR_HASHED_TEXT_PROP_IS_TOO_LONG + ); + } + Ok(()) } } @@ -428,6 +436,16 @@ impl Property { Self::validate_max_len_of_text(text_item, *text_max_len)?; validate_property_vector_length_after_value_insert(vec, max_vec_len) } + ( + InputValue::TextToHash(text_item), + VecOutputValue::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)?; + } + validate_property_vector_length_after_value_insert(vec, max_vec_len) + } ( InputValue::Reference(entity_id), VecOutputValue::Reference(vec), @@ -462,7 +480,7 @@ impl Property { } ( Some(InputValue::TextToHash(text_to_be_hashed)), - Some(Type::Hash(text_to_be_hashed_max_len)), + 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, @@ -481,7 +499,7 @@ impl Property { pub fn validate_max_len_of_text_to_be_hashed( text_to_be_hashed: &[u8], - text_to_be_hashed_max_len: HashedTextMaxLength, + text_to_be_hashed_max_len: u16, ) -> dispatch::Result { ensure!( text_to_be_hashed.len() <= text_to_be_hashed_max_len as usize, @@ -520,7 +538,9 @@ impl Property { VecInputValue::Int64(vec) => Self::validate_vec_len(vec, max_len), VecInputValue::TextToHash(vec) => { Self::validate_vec_len(vec, max_len)?; - if let Type::Hash(text_to_be_hashed_max_len) = vec_property_type.get_vec_type() { + 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, @@ -574,6 +594,7 @@ impl Property { | (InputValue::Int32(_), Type::Int32) | (InputValue::Int64(_), Type::Int64) | (InputValue::Text(_), Type::Text(_)) + | (InputValue::TextToHash(_), Type::Hash(_)) | (InputValue::Reference(_), Type::Reference(_, _)) => true, _ => false, }, @@ -589,6 +610,7 @@ impl Property { | (VecInputValue::Int32(_), Type::Int32) | (VecInputValue::Int64(_), Type::Int64) | (VecInputValue::Text(_), Type::Text(_)) + | (VecInputValue::TextToHash(_), Type::Hash(_)) | (VecInputValue::Reference(_), Type::Reference(_, _)) => true, _ => false, }, @@ -666,7 +688,7 @@ impl Property { let entity = Module::::entity_by_id(entity_id); ensure!( - entity.class_id == class_id, + entity.get_class_id() == class_id, ERROR_REFERENCED_ENTITY_DOES_NOT_MATCH_ITS_CLASS ); Ok(entity) diff --git a/runtime-modules/content-directory/src/tests.rs b/runtime-modules/content-directory/src/tests.rs index 7fb8c62c8e..083fc19dac 100644 --- a/runtime-modules/content-directory/src/tests.rs +++ b/runtime-modules/content-directory/src/tests.rs @@ -116,12 +116,14 @@ pub fn add_entity_schemas_support() -> (Entity, Entity) { )); // Update supported schemas set and properties of first entity - first_entity.supported_schemas = + *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.values = TestModule::make_output_property_values(first_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); diff --git a/runtime-modules/content-directory/src/tests/add_class_schema.rs b/runtime-modules/content-directory/src/tests/add_class_schema.rs index 8fe7f88fb7..c4ccf8cfb9 100644 --- a/runtime-modules/content-directory/src/tests/add_class_schema.rs +++ b/runtime-modules/content-directory/src/tests/add_class_schema.rs @@ -38,8 +38,8 @@ fn add_class_schema_success() { // Ensure class schemas added succesfully let mut class = create_class_with_default_permissions(); - class.properties = vec![first_property, second_property]; - class.schemas = vec![ + 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(), @@ -397,7 +397,7 @@ fn add_class_schema_property_description_too_short() { } #[test] -fn add_class_schema_property_text_property_is_too_long() { +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)); @@ -422,6 +422,33 @@ fn add_class_schema_property_text_property_is_too_long() { }) } +#[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::::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_HASHED_TEXT_PROP_IS_TOO_LONG, + number_of_events_before_call, + ); + }) +} + #[test] fn add_class_schema_property_vec_property_is_too_long() { with_test_externalities(|| { diff --git a/runtime-modules/content-directory/src/tests/add_schema_support_to_entity.rs b/runtime-modules/content-directory/src/tests/add_schema_support_to_entity.rs index 59667b6e8d..4b5657d78a 100644 --- a/runtime-modules/content-directory/src/tests/add_schema_support_to_entity.rs +++ b/runtime-modules/content-directory/src/tests/add_schema_support_to_entity.rs @@ -1153,6 +1153,70 @@ fn add_schema_support_text_property_is_too_long() { }) } +#[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::::single_text_hash(hashed_text_max_length_constraint); + + let property = Property::::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::::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_HASHED_TEXT_PROP_IS_TOO_LONG, + number_of_events_before_call, + ); + }) +} + #[test] fn add_schema_support_vec_property_is_too_long() { with_test_externalities(|| { diff --git a/runtime-modules/content-directory/src/tests/clear_entity_property_vector.rs b/runtime-modules/content-directory/src/tests/clear_entity_property_vector.rs index 06e1f3d46c..3e65afbb53 100644 --- a/runtime-modules/content-directory/src/tests/clear_entity_property_vector.rs +++ b/runtime-modules/content-directory/src/tests/clear_entity_property_vector.rs @@ -25,7 +25,7 @@ fn clear_entity_property_vector_success() { // Ensure first entity properties updated succesfully if let Some(second_schema_old_property_value) = first_entity - .values + .get_values_mut() .get_mut(&SECOND_PROPERTY_ID) .and_then(|property_value| property_value.as_vec_property_value_mut()) { diff --git a/runtime-modules/content-directory/src/tests/create_entity.rs b/runtime-modules/content-directory/src/tests/create_entity.rs index 50d168c7c6..465259dcab 100644 --- a/runtime-modules/content-directory/src/tests/create_entity.rs +++ b/runtime-modules/content-directory/src/tests/create_entity.rs @@ -44,7 +44,7 @@ fn create_entity_success() { // Ensure entity creation voucher with `default_entity_creation_voucher_upper_bound` for given entity controller created succesfully. let mut entity_voucher = - EntityCreationVoucher::new(class.default_entity_creation_voucher_upper_bound); + EntityCreationVoucher::new(class.get_default_entity_creation_voucher_upper_bound()); entity_voucher.increment_created_entities_count(); let entity_controller = EntityController::from_actor(&actor); diff --git a/runtime-modules/content-directory/src/tests/insert_at_entity_property_vector.rs b/runtime-modules/content-directory/src/tests/insert_at_entity_property_vector.rs index 220de61ee0..17d314d8cf 100644 --- a/runtime-modules/content-directory/src/tests/insert_at_entity_property_vector.rs +++ b/runtime-modules/content-directory/src/tests/insert_at_entity_property_vector.rs @@ -33,7 +33,7 @@ fn insert_at_entity_property_vector_success() { // Ensure first entity properties updated succesfully if let Some(second_schema_old_property_value) = first_entity - .values + .get_values_mut() .get_mut(&SECOND_PROPERTY_ID) .and_then(|property_value| property_value.as_vec_property_value_mut()) { @@ -551,7 +551,7 @@ fn insert_at_entity_property_vector_unknown_entity_property_id() { let input_value = InputValue::Reference(FIRST_ENTITY_ID); // Make an attempt to insert value at given `index_in_property_vector` - // intto `VecOutputPropertyValue` under `in_class_schema_property_id`, + // into `VecOutputPropertyValue` 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, @@ -884,6 +884,86 @@ fn insert_at_entity_property_vector_text_prop_is_too_long() { }) } +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::::vec_text_hash( + hashed_text_max_length_constraint, + VecMaxLengthConstraint::get(), + ); + + let property = Property::::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::::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 `VecOutputPropertyValue` 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_HASHED_TEXT_PROP_IS_TOO_LONG, + number_of_events_before_call, + ); + }) +} + #[test] fn insert_at_entity_property_vector_prop_type_does_not_match_internal_vec_property() { with_test_externalities(|| { diff --git a/runtime-modules/content-directory/src/tests/remove_at_entity_property_vector.rs b/runtime-modules/content-directory/src/tests/remove_at_entity_property_vector.rs index 5f5e6969d1..e3bf93b746 100644 --- a/runtime-modules/content-directory/src/tests/remove_at_entity_property_vector.rs +++ b/runtime-modules/content-directory/src/tests/remove_at_entity_property_vector.rs @@ -31,7 +31,7 @@ fn remove_at_entity_property_vector_success() { // Ensure first entity properties updated succesfully if let Some(second_schema_old_property_value) = first_entity - .values + .get_values_mut() .get_mut(&SECOND_PROPERTY_ID) .and_then(|property_value| property_value.as_vec_property_value_mut()) { diff --git a/runtime-modules/content-directory/src/tests/update_class_schema_status.rs b/runtime-modules/content-directory/src/tests/update_class_schema_status.rs index d78283c7c6..2236ed3c2e 100644 --- a/runtime-modules/content-directory/src/tests/update_class_schema_status.rs +++ b/runtime-modules/content-directory/src/tests/update_class_schema_status.rs @@ -36,8 +36,8 @@ fn update_class_schema_status_success() { let mut schema = Schema::new(BTreeSet::from_iter(vec![FIRST_PROPERTY_ID].into_iter())); schema.set_status(false); - class.properties = vec![property]; - class.schemas = vec![schema]; + class.set_properties(vec![property]); + *class.get_schemas_mut() = vec![schema]; assert_eq!(class_by_id(FIRST_CLASS_ID), class); diff --git a/runtime-modules/content-directory/src/tests/update_entity_property_values.rs b/runtime-modules/content-directory/src/tests/update_entity_property_values.rs index 2e296a1fe3..bd33c76add 100644 --- a/runtime-modules/content-directory/src/tests/update_entity_property_values.rs +++ b/runtime-modules/content-directory/src/tests/update_entity_property_values.rs @@ -32,7 +32,7 @@ fn update_entity_property_values_success() { // Ensure first entity properties updated succesfully if let Some(second_schema_old_property_value) = - first_entity.values.get_mut(&SECOND_PROPERTY_ID) + first_entity.get_values_mut().get_mut(&SECOND_PROPERTY_ID) { second_schema_old_property_value.update(second_schema_new_property_value.into()); } @@ -667,6 +667,85 @@ fn update_entity_property_values_text_prop_is_too_long() { }) } +#[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::::single_text_hash(hashed_text_max_length_constraint); + + let property = Property::::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::::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::::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_HASHED_TEXT_PROP_IS_TOO_LONG, + number_of_events_before_call, + ); + }) +} + #[test] fn update_entity_property_values_referenced_entity_not_found() { with_test_externalities(|| {