diff --git a/runtime-modules/content-directory/src/errors.rs b/runtime-modules/content-directory/src/errors.rs index 14180fe095..36e5f70add 100644 --- a/runtime-modules/content-directory/src/errors.rs +++ b/runtime-modules/content-directory/src/errors.rs @@ -29,6 +29,12 @@ pub const ERROR_SCHEMA_ALREADY_ADDED_TO_ENTITY: &str = "Cannot add a schema that is already added to this entity"; pub const ERROR_PROP_VALUE_DONT_MATCH_TYPE: &str = "Some of the provided property values don't match the expected property type"; +pub const ERROR_PROP_VALUE_DONT_MATCH_VEC_TYPE: &str = + "Property value don't match the expected vector property type"; +pub const ERROR_PROP_VALUE_UNDER_GIVEN_INDEX_IS_NOT_A_VECTOR: &str = + "Property value under given index is not a vector"; +pub const ERROR_PROP_VALUE_VEC_NONCES_DOES_NOT_MATCH: &str = + "Current property value vector nonce does not equal to provided one"; pub const ERROR_PROP_NAME_NOT_UNIQUE_IN_CLASS: &str = "Property name is not unique within its class"; pub const ERROR_MISSING_REQUIRED_PROP: &str = @@ -36,5 +42,13 @@ pub const ERROR_MISSING_REQUIRED_PROP: &str = pub const ERROR_UNKNOWN_ENTITY_PROP_ID: &str = "Some of the provided property ids cannot be found on the current list of propery values of this entity"; pub const ERROR_TEXT_PROP_IS_TOO_LONG: &str = "Text propery is too long"; pub const ERROR_VEC_PROP_IS_TOO_LONG: &str = "Vector propery is too long"; -pub const ERROR_INTERNAL_RPOP_DOES_NOT_MATCH_ITS_CLASS: &str = +pub const ERROR_ENTITY_PROP_VALUE_VECTOR_IS_TOO_LONG: &str = + "Propery value vector can`t contain more values"; +pub const ERROR_ENTITY_PROP_VALUE_VECTOR_INDEX_IS_OUT_OF_RANGE: &str = + "Given property value vector index is out of range"; +pub const ERROR_PROP_VALUE_TYPE_DOESNT_MATCH_INTERNAL_ENTITY_VECTOR_TYPE: &str = + "Propery value type does not match internal entity vector type"; +pub const ERROR_INTERNAL_PROP_DOES_NOT_MATCH_ITS_CLASS: &str = "Internal property does not match its class"; +pub const ERROR_ENTITY_REFERENCE_COUNTER_DOES_NOT_EQUAL_TO_ZERO: &str = + "Entity removal can`t be completed, as there are some property values pointing to given entity"; diff --git a/runtime-modules/content-directory/src/example.rs b/runtime-modules/content-directory/src/example.rs index 99d9c65d1e..2497348771 100644 --- a/runtime-modules/content-directory/src/example.rs +++ b/runtime-modules/content-directory/src/example.rs @@ -354,7 +354,7 @@ fn create_podcast_class_schema() { b"A podcast channel".to_vec(), ),); - let channel_schema_id: u16 = 0; + let channel_schema_id: SchemaId = 0; assert_ok!( TestModule::append_class_schema(channel_class_id, vec![], channel_props), @@ -370,7 +370,7 @@ fn create_podcast_class_schema() { b"A podcast episode".to_vec(), ),); - let episode_schema_id: u16 = 0; + let episode_schema_id: SchemaId = 0; assert_ok!( TestModule::append_class_schema(episode_class_id, vec![], episode_props,), @@ -421,10 +421,10 @@ fn create_podcast_class_schema() { // 15 p.next_text_value(b"crypto,blockchain,governance,staking,bitcoin,ethereum".to_vec()); // 16 - p.next_value(PropertyValue::TextVec(vec![ - b"Technology".to_vec(), - b"Software How-To".to_vec(), - ])); + p.next_value(PropertyValue::TextVec( + vec![b"Technology".to_vec(), b"Software How-To".to_vec()], + ::Nonce::default(), + )); // 17 p.next_text_value( b"https://ssl-static.libsyn.com/p/assets/2/d/2/5/2d25eb5fa72739f7/iTunes_Cover.png" @@ -518,20 +518,20 @@ fn create_podcast_class_schema() { }) } -struct PropHelper { - prop_idx: u16, - property_values: BTreeMap, +struct PropHelper { + prop_idx: PropertyId, + property_values: BTreeMap>, } -impl PropHelper { - fn new() -> PropHelper { +impl PropHelper { + fn new() -> PropHelper { PropHelper { prop_idx: 0, property_values: BTreeMap::new(), } } - fn next_value(&mut self, value: PropertyValue) { + fn next_value(&mut self, value: PropertyValue) { self.property_values.insert(self.prop_idx, value); self.prop_idx += 1; } @@ -540,7 +540,7 @@ impl PropHelper { self.next_value(PropertyValue::Text(text)) } - fn get_property_values(self) -> BTreeMap { + fn get_property_values(self) -> BTreeMap> { self.property_values } } diff --git a/runtime-modules/content-directory/src/lib.rs b/runtime-modules/content-directory/src/lib.rs index 71060e048c..e1537695ca 100755 --- a/runtime-modules/content-directory/src/lib.rs +++ b/runtime-modules/content-directory/src/lib.rs @@ -4,9 +4,12 @@ use codec::{Codec, Decode, Encode}; use rstd::collections::{btree_map::BTreeMap, btree_set::BTreeSet}; use rstd::prelude::*; -use runtime_primitives::traits::{MaybeSerialize, Member, SimpleArithmetic}; -use srml_support::{decl_module, decl_storage, dispatch, ensure, traits::Get, Parameter}; -use system; +use runtime_primitives::traits::{ + MaybeSerialize, MaybeSerializeDeserialize, Member, One, SimpleArithmetic, Zero, +}; +use srml_support::{ + decl_module, decl_storage, dispatch, ensure, traits::Get, Parameter, StorageMap, +}; #[cfg(feature = "std")] pub use serde::{Deserialize, Serialize}; @@ -23,12 +26,13 @@ mod permissions; mod tests; pub use constraint::*; +use core::fmt::Debug; pub use credentials::*; pub use errors::*; pub use operations::*; pub use permissions::*; -pub trait Trait: system::Trait { +pub trait Trait: system::Trait + Debug { /// Type that represents an actor or group of actors in the system. type Credential: Parameter + Member @@ -42,6 +46,21 @@ pub trait Trait: system::Trait { + PartialEq + Ord; + type Nonce: Parameter + + Member + + SimpleArithmetic + + Codec + + Default + + Copy + + Clone + + One + + Zero + + MaybeSerializeDeserialize + + Eq + + PartialEq + + Ord + + From; + /// Security/configuration constraints type PropertyNameConstraint: Get; @@ -113,12 +132,12 @@ impl InputValidationLengthConstraint { } /// Helper for computing max - pub fn max(&self) -> u16 { + pub fn max(self) -> u16 { self.min + self.max_min_diff } pub fn ensure_valid( - &self, + self, len: usize, too_short_msg: &'static str, too_long_msg: &'static str, @@ -136,6 +155,10 @@ impl InputValidationLengthConstraint { pub type ClassId = u64; pub type EntityId = u64; +pub type PropertyId = u16; +pub type SchemaId = u16; +pub type VecMaxLength = u16; +pub type TextMaxLength = u16; #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] #[derive(Encode, Decode, Eq, PartialEq, Clone, Debug)] @@ -183,12 +206,12 @@ impl Class { } } - fn is_active_schema(&self, schema_index: u16) -> bool { + fn is_active_schema(&self, schema_index: SchemaId) -> bool { // Such indexing is safe, when length bounds were previously checked self.schemas[schema_index as usize].is_active } - fn update_schema_status(&mut self, schema_index: u16, schema_status: bool) { + fn update_schema_status(&mut self, schema_index: SchemaId, schema_status: bool) { // Such indexing is safe, when length bounds were previously checked self.schemas[schema_index as usize].is_active = schema_status; } @@ -206,24 +229,55 @@ impl Class { } } -pub type ClassPermissionsType = - ClassPermissions::Credential, u16, ::BlockNumber>; +pub type ClassPermissionsType = ClassPermissions< + ClassId, + ::Credential, + PropertyId, + ::BlockNumber, +>; #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)] -pub struct Entity { +#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)] +pub struct Entity { /// The class id of this entity. pub class_id: 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 + 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, - // pub deleted: bool, + pub values: BTreeMap>, + // pub deleted: bool + pub reference_count: u32, +} + +impl Default for Entity { + fn default() -> Self { + Self { + class_id: ClassId::default(), + supported_schemas: BTreeSet::new(), + values: BTreeMap::new(), + reference_count: 0, + } + } +} + +impl Entity { + fn new( + class_id: ClassId, + supported_schemas: BTreeSet, + values: BTreeMap>, + ) -> Self { + Self { + class_id, + supported_schemas, + values, + ..Self::default() + } + } } /// A schema defines what properties describe an entity @@ -231,7 +285,7 @@ pub struct Entity { #[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)] pub struct Schema { /// Indices into properties vector for the corresponding class. - pub properties: Vec, + pub properties: Vec, pub is_active: bool, } @@ -246,7 +300,7 @@ impl Default for Schema { } impl Schema { - fn new(properties: Vec) -> Self { + fn new(properties: Vec) -> Self { Self { properties, // Default schema status @@ -265,7 +319,7 @@ pub struct Property { } #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)] +#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, Debug)] pub enum PropertyType { // Single value: Bool, @@ -275,27 +329,27 @@ pub enum PropertyType { Int16, Int32, Int64, - Text(u16), + Text(TextMaxLength), Reference(ClassId), // Vector of values. - // The first u16 value is the max length of this vector. - BoolVec(u16), - Uint16Vec(u16), - Uint32Vec(u16), - Uint64Vec(u16), - Int16Vec(u16), - Int32Vec(u16), - Int64Vec(u16), - - /// The first u16 value is the max length of this vector. - /// The second u16 value is the max length of every text item in this vector. - TextVec(u16, u16), - - /// The first u16 value is the max length of this vector. + // The first value is the max length of this vector. + BoolVec(VecMaxLength), + Uint16Vec(VecMaxLength), + Uint32Vec(VecMaxLength), + Uint64Vec(VecMaxLength), + Int16Vec(VecMaxLength), + Int32Vec(VecMaxLength), + Int64Vec(VecMaxLength), + + /// The first value is the max length of this vector. + /// The second value is the max length of every text item in this vector. + TextVec(VecMaxLength, TextMaxLength), + + /// The first value is the max length of this vector. /// The second ClassId value tells that an every element of this vector /// should be of a specific ClassId. - ReferenceVec(u16, ClassId), + ReferenceVec(VecMaxLength, ClassId), // External(ExternalProperty), // ExternalVec(u16, ExternalProperty), } @@ -308,7 +362,7 @@ impl Default for PropertyType { #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] #[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)] -pub enum PropertyValue { +pub enum PropertyValue { // Single value: Bool(bool), Uint16(u16), @@ -320,21 +374,176 @@ pub enum PropertyValue { Text(Vec), Reference(EntityId), - // Vector of values: - BoolVec(Vec), - Uint16Vec(Vec), - Uint32Vec(Vec), - Uint64Vec(Vec), - Int16Vec(Vec), - Int32Vec(Vec), - Int64Vec(Vec), - TextVec(Vec>), - ReferenceVec(Vec), + // Vector of values, second value - nonce used to avoid race update conditions: + BoolVec(Vec, T::Nonce), + Uint16Vec(Vec, T::Nonce), + Uint32Vec(Vec, T::Nonce), + Uint64Vec(Vec, T::Nonce), + Int16Vec(Vec, T::Nonce), + Int32Vec(Vec, T::Nonce), + Int64Vec(Vec, T::Nonce), + TextVec(Vec>, T::Nonce), + ReferenceVec(Vec, T::Nonce), // External(ExternalPropertyType), // ExternalVec(Vec), } -impl Default for PropertyValue { +impl PropertyValue { + fn update(&mut self, new_value: PropertyValue) { + if let Some(new_nonce) = self.try_increment_nonce() { + *self = new_value; + self.try_set_nonce(new_nonce) + } else { + *self = new_value; + } + } + + fn try_increment_nonce(&mut self) -> Option { + // Increment nonce if property value is vec + match self { + PropertyValue::BoolVec(_, nonce) + | PropertyValue::Uint16Vec(_, nonce) + | PropertyValue::Uint32Vec(_, nonce) + | PropertyValue::Uint64Vec(_, nonce) + | PropertyValue::Int16Vec(_, nonce) + | PropertyValue::Int32Vec(_, nonce) + | PropertyValue::Int64Vec(_, nonce) + | PropertyValue::TextVec(_, nonce) + | PropertyValue::ReferenceVec(_, nonce) => { + *nonce += T::Nonce::one(); + Some(*nonce) + } + _ => None, + } + } + + fn try_set_nonce(&mut self, new_nonce: T::Nonce) { + // Set new nonce if property value is vec + match self { + PropertyValue::BoolVec(_, nonce) + | PropertyValue::Uint16Vec(_, nonce) + | PropertyValue::Uint32Vec(_, nonce) + | PropertyValue::Uint64Vec(_, nonce) + | PropertyValue::Int16Vec(_, nonce) + | PropertyValue::Int32Vec(_, nonce) + | PropertyValue::Int64Vec(_, nonce) + | PropertyValue::TextVec(_, nonce) + | PropertyValue::ReferenceVec(_, nonce) => *nonce = new_nonce, + _ => (), + } + } + + fn get_nonce(&self) -> Option { + match self { + PropertyValue::BoolVec(_, nonce) + | PropertyValue::Uint16Vec(_, nonce) + | PropertyValue::Uint32Vec(_, nonce) + | PropertyValue::Uint64Vec(_, nonce) + | PropertyValue::Int16Vec(_, nonce) + | PropertyValue::Int32Vec(_, nonce) + | PropertyValue::Int64Vec(_, nonce) + | PropertyValue::TextVec(_, nonce) + | PropertyValue::ReferenceVec(_, nonce) => Some(*nonce), + _ => None, + } + } + + fn is_vec(&self) -> bool { + match self { + PropertyValue::BoolVec(_, _) + | PropertyValue::Uint16Vec(_, _) + | PropertyValue::Uint32Vec(_, _) + | PropertyValue::Uint64Vec(_, _) + | PropertyValue::Int16Vec(_, _) + | PropertyValue::Int32Vec(_, _) + | PropertyValue::Int64Vec(_, _) + | PropertyValue::TextVec(_, _) + | PropertyValue::ReferenceVec(_, _) => true, + _ => false, + } + } + + fn vec_clear(&mut self) { + match self { + PropertyValue::BoolVec(vec, _) => *vec = vec![], + PropertyValue::Uint16Vec(vec, _) => *vec = vec![], + PropertyValue::Uint32Vec(vec, _) => *vec = vec![], + PropertyValue::Uint64Vec(vec, _) => *vec = vec![], + PropertyValue::Int16Vec(vec, _) => *vec = vec![], + PropertyValue::Int32Vec(vec, _) => *vec = vec![], + PropertyValue::Int64Vec(vec, _) => *vec = vec![], + PropertyValue::TextVec(vec, _) => *vec = vec![], + PropertyValue::ReferenceVec(vec, _) => *vec = vec![], + _ => (), + } + self.try_increment_nonce(); + } + + fn vec_remove_at(&mut self, index_in_property_vec: VecMaxLength) { + fn remove_at_checked(vec: &mut Vec, index_in_property_vec: VecMaxLength) { + if (index_in_property_vec as usize) < vec.len() { + vec.remove(index_in_property_vec as usize); + } + } + + match self { + PropertyValue::BoolVec(vec, _) => remove_at_checked(vec, index_in_property_vec), + PropertyValue::Uint16Vec(vec, _) => remove_at_checked(vec, index_in_property_vec), + PropertyValue::Uint32Vec(vec, _) => remove_at_checked(vec, index_in_property_vec), + PropertyValue::Uint64Vec(vec, _) => remove_at_checked(vec, index_in_property_vec), + PropertyValue::Int16Vec(vec, _) => remove_at_checked(vec, index_in_property_vec), + PropertyValue::Int32Vec(vec, _) => remove_at_checked(vec, index_in_property_vec), + PropertyValue::Int64Vec(vec, _) => remove_at_checked(vec, index_in_property_vec), + PropertyValue::TextVec(vec, _) => remove_at_checked(vec, index_in_property_vec), + PropertyValue::ReferenceVec(vec, _) => remove_at_checked(vec, index_in_property_vec), + _ => (), + } + self.try_increment_nonce(); + } + + fn vec_insert_at(&mut self, index_in_property_vec: VecMaxLength, property_value: Self) { + fn insert_at(vec: &mut Vec, 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); + } + } + + self.try_increment_nonce(); + + match (self, property_value) { + (PropertyValue::BoolVec(vec, _), PropertyValue::Bool(value)) => { + insert_at(vec, index_in_property_vec, value) + } + (PropertyValue::Uint16Vec(vec, _), PropertyValue::Uint16(value)) => { + insert_at(vec, index_in_property_vec, value) + } + (PropertyValue::Uint32Vec(vec, _), PropertyValue::Uint32(value)) => { + insert_at(vec, index_in_property_vec, value) + } + (PropertyValue::Uint64Vec(vec, _), PropertyValue::Uint64(value)) => { + insert_at(vec, index_in_property_vec, value) + } + (PropertyValue::Int16Vec(vec, _), PropertyValue::Int16(value)) => { + insert_at(vec, index_in_property_vec, value) + } + (PropertyValue::Int32Vec(vec, _), PropertyValue::Int32(value)) => { + insert_at(vec, index_in_property_vec, value) + } + (PropertyValue::Int64Vec(vec, _), PropertyValue::Int64(value)) => { + insert_at(vec, index_in_property_vec, value) + } + (PropertyValue::TextVec(vec, _), PropertyValue::Text(ref value)) => { + insert_at(vec, index_in_property_vec, value.to_owned()) + } + (PropertyValue::ReferenceVec(vec, _), PropertyValue::Reference(value)) => { + insert_at(vec, index_in_property_vec, value) + } + _ => (), + } + } +} + +impl Default for PropertyValue { fn default() -> Self { PropertyValue::Bool(false) } @@ -349,7 +558,7 @@ decl_storage! { /// ClassPermissions of corresponding Classes in the versioned store pub ClassById get(class_by_id) config(): linked_map ClassId => Class; - pub EntityById get(entity_by_id) config(): map EntityId => Entity; + pub EntityById get(entity_by_id) config(): map EntityId => Entity; /// Owner of an entity in the versioned store. If it is None then it is owned by the system. pub EntityMaintainerByEntityId get(entity_maintainer_by_entity_id): linked_map EntityId => Option; @@ -490,7 +699,7 @@ decl_module! { origin, with_credential: Option, class_id: ClassId, - constraint: ReferenceConstraint + constraint: ReferenceConstraint ) -> dispatch::Result { let raw_origin = Self::ensure_root_or_signed(origin)?; @@ -565,7 +774,7 @@ decl_module! { origin, with_credential: Option, class_id: ClassId, - existing_properties: Vec, + existing_properties: Vec, new_properties: Vec ) -> dispatch::Result { let raw_origin = Self::ensure_root_or_signed(origin)?; @@ -591,7 +800,7 @@ decl_module! { origin, with_credential: Option, class_id: ClassId, - schema_id: u16, // Do not type alias u16!! - u16, + schema_id: SchemaId, is_active: bool ) -> dispatch::Result { let raw_origin = Self::ensure_root_or_signed(origin)?; @@ -607,7 +816,7 @@ decl_module! { // at this point we don't enforce anything about reference constraints // because of the chicken and egg problem. Instead enforcement is done // at the time of creating an entity. - let _schema_index = Self::complete_class_schema_status_update(class_id, schema_id, is_active)?; + Self::complete_class_schema_status_update(class_id, schema_id, is_active)?; Ok(()) } ) @@ -621,17 +830,26 @@ decl_module! { class_id: ClassId ) -> dispatch::Result { let raw_origin = Self::ensure_root_or_signed(origin)?; - let _entity_id = Self::do_create_entity(&raw_origin, with_credential, class_id)?; + Self::do_create_entity(&raw_origin, with_credential, class_id)?; Ok(()) } + pub fn remove_entity( + origin, + with_credential: Option, + entity_id: EntityId, + ) -> dispatch::Result { + let raw_origin = Self::ensure_root_or_signed(origin)?; + Self::do_remove_entity(&raw_origin, with_credential, entity_id) + } + pub fn add_schema_support_to_entity( origin, with_credential: Option, as_entity_maintainer: bool, entity_id: EntityId, - schema_id: u16, // Do not type alias u16!! - u16, - property_values: BTreeMap + schema_id: SchemaId, + property_values: BTreeMap> ) -> dispatch::Result { let raw_origin = Self::ensure_root_or_signed(origin)?; Self::do_add_schema_support_to_entity(&raw_origin, with_credential, as_entity_maintainer, entity_id, schema_id, property_values) @@ -642,13 +860,60 @@ decl_module! { with_credential: Option, as_entity_maintainer: bool, entity_id: EntityId, - property_values: BTreeMap + property_values: BTreeMap> ) -> dispatch::Result { let raw_origin = Self::ensure_root_or_signed(origin)?; Self::do_update_entity_property_values(&raw_origin, with_credential, as_entity_maintainer, entity_id, property_values) } - pub fn transaction(origin, operations: Vec>) -> dispatch::Result { + pub fn clear_entity_property_vector( + origin, + with_credential: Option, + as_entity_maintainer: bool, + entity_id: EntityId, + in_class_schema_property_id: PropertyId + ) -> dispatch::Result { + let raw_origin = Self::ensure_root_or_signed(origin)?; + Self::do_clear_entity_property_vector(&raw_origin, with_credential, as_entity_maintainer, entity_id, in_class_schema_property_id) + } + + pub fn remove_at_entity_property_vector( + origin, + with_credential: Option, + as_entity_maintainer: bool, + entity_id: EntityId, + in_class_schema_property_id: PropertyId, + index_in_property_vec: VecMaxLength, + nonce: T::Nonce + ) -> dispatch::Result { + let raw_origin = Self::ensure_root_or_signed(origin)?; + Self::do_remove_at_entity_property_vector(&raw_origin, with_credential, as_entity_maintainer, entity_id, in_class_schema_property_id, index_in_property_vec, nonce) + } + + pub fn insert_at_entity_property_vector( + origin, + with_credential: Option, + as_entity_maintainer: bool, + entity_id: EntityId, + in_class_schema_property_id: PropertyId, + index_in_property_vec: VecMaxLength, + property_value: PropertyValue, + nonce: T::Nonce + ) -> dispatch::Result { + let raw_origin = Self::ensure_root_or_signed(origin)?; + Self::do_insert_at_entity_property_vector( + &raw_origin, + with_credential, + as_entity_maintainer, + entity_id, + in_class_schema_property_id, + index_in_property_vec, + property_value, + nonce + ) + } + + pub fn transaction(origin, operations: Vec>) -> dispatch::Result { // This map holds the EntityId of the entity created as a result of executing a CreateEntity Operation // keyed by the indexed of the operation, in the operations vector. let mut entity_created_in_operation: BTreeMap = BTreeMap::new(); @@ -734,14 +999,28 @@ impl Module { ) } + fn do_remove_entity( + raw_origin: &system::RawOrigin, + with_credential: Option, + entity_id: EntityId, + ) -> dispatch::Result { + // class id of the entity being removed + let class_id = Self::get_class_id_by_entity_id(entity_id)?; + + Self::if_class_permissions_satisfied( + raw_origin, + with_credential, + None, + ClassPermissions::can_remove_entity, + class_id, + |_class_permissions, _access_level| Self::complete_entity_removal(entity_id), + ) + } + fn perform_entity_creation(class_id: ClassId) -> EntityId { let entity_id = NextEntityId::get(); - let new_entity = Entity { - class_id, - supported_schemas: BTreeSet::new(), - values: BTreeMap::new(), - }; + let new_entity = Entity::::new(class_id, BTreeSet::new(), BTreeMap::new()); // Save newly created entity: EntityById::insert(entity_id, new_entity); @@ -757,7 +1036,7 @@ impl Module { with_credential: Option, as_entity_maintainer: bool, entity_id: EntityId, - property_values: BTreeMap, + property_values: BTreeMap>, ) -> dispatch::Result { let class_id = Self::get_class_id_by_entity_id(entity_id)?; @@ -781,9 +1060,117 @@ impl Module { ) } + fn do_clear_entity_property_vector( + raw_origin: &system::RawOrigin, + with_credential: Option, + as_entity_maintainer: bool, + entity_id: EntityId, + in_class_schema_property_id: PropertyId, + ) -> dispatch::Result { + let class_id = Self::get_class_id_by_entity_id(entity_id)?; + + let as_entity_maintainer = if as_entity_maintainer { + Some(entity_id) + } else { + None + }; + + Self::if_class_permissions_satisfied( + raw_origin, + with_credential, + as_entity_maintainer, + ClassPermissions::can_update_entity, + class_id, + |_class_permissions, _access_level| { + Self::complete_entity_property_vector_cleaning( + entity_id, + in_class_schema_property_id, + ) + }, + ) + } + + fn do_remove_at_entity_property_vector( + raw_origin: &system::RawOrigin, + with_credential: Option, + as_entity_maintainer: bool, + entity_id: EntityId, + in_class_schema_property_id: PropertyId, + index_in_property_vec: VecMaxLength, + nonce: T::Nonce, + ) -> dispatch::Result { + let class_id = Self::get_class_id_by_entity_id(entity_id)?; + + let as_entity_maintainer = if as_entity_maintainer { + Some(entity_id) + } else { + None + }; + + Self::if_class_permissions_satisfied( + raw_origin, + with_credential, + as_entity_maintainer, + ClassPermissions::can_update_entity, + class_id, + |_class_permissions, _access_level| { + Self::complete_remove_at_entity_property_vector( + entity_id, + in_class_schema_property_id, + index_in_property_vec, + nonce, + ) + }, + ) + } + + fn do_insert_at_entity_property_vector( + raw_origin: &system::RawOrigin, + with_credential: Option, + as_entity_maintainer: bool, + entity_id: EntityId, + in_class_schema_property_id: PropertyId, + index_in_property_vec: VecMaxLength, + property_value: PropertyValue, + nonce: T::Nonce, + ) -> dispatch::Result { + let class_id = Self::get_class_id_by_entity_id(entity_id)?; + + let as_entity_maintainer = if as_entity_maintainer { + Some(entity_id) + } else { + None + }; + + Self::if_class_permissions_satisfied( + raw_origin, + with_credential, + as_entity_maintainer, + ClassPermissions::can_update_entity, + class_id, + |_class_permissions, _access_level| { + Self::complete_insert_at_entity_property_vector( + entity_id, + in_class_schema_property_id, + index_in_property_vec, + property_value, + nonce, + ) + }, + ) + } + + fn complete_entity_removal(entity_id: EntityId) -> dispatch::Result { + // Ensure there is no property values pointing to given entity + Self::ensure_rc_is_zero(entity_id)?; + >::remove(entity_id); + >::remove(entity_id); + Ok(()) + } + pub fn complete_class_schema_status_update( class_id: ClassId, - schema_id: u16, // Do not type alias u16!! - u16, + schema_id: SchemaId, schema_status: bool, ) -> dispatch::Result { // Check that schema_id is a valid index of class schemas vector: @@ -796,47 +1183,197 @@ impl Module { pub fn complete_entity_property_values_update( entity_id: EntityId, - new_property_values: BTreeMap, + new_property_values: BTreeMap>, ) -> dispatch::Result { - Self::ensure_known_entity_id(&entity_id)?; + Self::ensure_known_entity_id(entity_id)?; let (entity, class) = Self::get_entity_and_class(entity_id); // Get current property values of an entity as a mutable vector, // so we can update them if new values provided present in new_property_values. let mut updated_values = entity.values; - let mut updates_count = 0; + let mut updated = false; + let mut entities_rc_to_decrement_vec = vec![]; + let mut entities_rc_to_increment_vec = vec![]; // Iterate over a vector of new values and update corresponding properties // of this entity if new values are valid. - for (id, new_value) in new_property_values.iter() { + for (id, new_value) in new_property_values.into_iter() { // Try to find a current property value in the entity // by matching its id to the id of a property with an updated value. - if let Some(current_prop_value) = updated_values.get_mut(id) { - // Get class-level information about this property - let class_prop = &class.properties[*id as usize]; - - // Validate a new property value against the type of this property - // and check any additional constraints like the length of a vector - // if it's a vector property or the length of a text if it's a text property. - Self::ensure_property_value_is_valid(new_value, class_prop)?; - - // Update a current prop value in a mutable vector, if a new value is valid. - *current_prop_value = new_value.to_owned(); - updates_count += 1; - } else { + let current_prop_value = updated_values + .get_mut(&id) // Throw an error if a property was not found on entity // by an in-class index of a property update. - return Err(ERROR_UNKNOWN_ENTITY_PROP_ID); + .ok_or(ERROR_UNKNOWN_ENTITY_PROP_ID)?; + // Get class-level information about this property + if let Some(class_prop) = class.properties.get(id as usize) { + if new_value != *current_prop_value { + // Validate a new property value against the type of this property + // and check any additional constraints like the length of a vector + // if it's a vector property or the length of a text if it's a text property. + Self::ensure_property_value_to_update_is_valid(&new_value, class_prop)?; + // Get unique entity ids to update rc + if let (Some(entities_rc_to_increment), Some(entities_rc_to_decrement)) = ( + Self::get_involved_entities(&new_value), + Self::get_involved_entities(¤t_prop_value), + ) { + let (entities_rc_to_decrement, entities_rc_to_increment): ( + Vec, + Vec, + ) = entities_rc_to_decrement + .into_iter() + .zip(entities_rc_to_increment.into_iter()) + .filter(|(entity_rc_to_decrement, entity_rc_to_increment)| { + entity_rc_to_decrement != entity_rc_to_increment + }) + .unzip(); + entities_rc_to_increment_vec.push(entities_rc_to_increment); + entities_rc_to_decrement_vec.push(entities_rc_to_decrement); + } + // Update a current prop value in a mutable vector, if a new value is valid. + current_prop_value.update(new_value); + updated = true; + } } } - // If at least one of the entity property values should be update: - if updates_count > 0 { - EntityById::mutate(entity_id, |entity| { + // If property values should be updated: + if updated { + >::mutate(entity_id, |entity| { entity.values = updated_values; }); + entities_rc_to_increment_vec + .iter() + .for_each(|entities_rc_to_increment| { + Self::increment_entities_rc(entities_rc_to_increment); + }); + entities_rc_to_decrement_vec + .iter() + .for_each(|entities_rc_to_decrement| { + Self::decrement_entities_rc(entities_rc_to_decrement); + }); + } + + Ok(()) + } + + fn complete_entity_property_vector_cleaning( + entity_id: EntityId, + in_class_schema_property_id: PropertyId, + ) -> dispatch::Result { + Self::ensure_known_entity_id(entity_id)?; + let entity = Self::entity_by_id(entity_id); + let current_prop_value = entity + .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)?; + + // Ensure prop value under given class schema property id is vector + ensure!( + current_prop_value.is_vec(), + ERROR_PROP_VALUE_UNDER_GIVEN_INDEX_IS_NOT_A_VECTOR + ); + + let entities_rc_to_decrement = Self::get_involved_entities(¤t_prop_value); + + // Clear property value vector: + >::mutate(entity_id, |entity| { + if let Some(current_property_value_vec) = + entity.values.get_mut(&in_class_schema_property_id) + { + current_property_value_vec.vec_clear(); + } + if let Some(entities_rc_to_decrement) = entities_rc_to_decrement { + Self::decrement_entities_rc(&entities_rc_to_decrement); + } + }); + + Ok(()) + } + + fn complete_remove_at_entity_property_vector( + entity_id: EntityId, + in_class_schema_property_id: PropertyId, + index_in_property_vec: VecMaxLength, + nonce: T::Nonce, + ) -> dispatch::Result { + Self::ensure_known_entity_id(entity_id)?; + let entity = Self::entity_by_id(entity_id); + + let current_prop_value = entity + .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)?; + + // Ensure property value vector nonces equality to avoid possible data races, + // when performing vector specific operations + Self::ensure_nonce_equality(current_prop_value, nonce)?; + Self::ensure_index_in_property_vector_is_valid(current_prop_value, index_in_property_vec)?; + let involved_entity_id = Self::get_involved_entities(¤t_prop_value) + .map(|involved_entities| involved_entities[index_in_property_vec as usize]); + + // Remove property value vector + >::mutate(entity_id, |entity| { + if let Some(current_prop_value) = entity.values.get_mut(&in_class_schema_property_id) { + current_prop_value.vec_remove_at(index_in_property_vec) + } + }); + if let Some(involved_entity_id) = involved_entity_id { + >::mutate(involved_entity_id, |entity| entity.reference_count -= 1) } + Ok(()) + } + + fn complete_insert_at_entity_property_vector( + entity_id: EntityId, + in_class_schema_property_id: PropertyId, + index_in_property_vec: VecMaxLength, + property_value: PropertyValue, + nonce: T::Nonce, + ) -> dispatch::Result { + Self::ensure_known_entity_id(entity_id)?; + + let (entity, class) = Self::get_entity_and_class(entity_id); + + // Get class-level information about this property + let class_prop = class + .properties + .get(in_class_schema_property_id as usize) + // Throw an error if a property was not found on entity + // by an in-class index of a property update. + .ok_or(ERROR_UNKNOWN_ENTITY_PROP_ID)?; + + // Try to find a current property value in the entity + // by matching its id to the id of a property with an updated value. + if let Some(entity_prop_value) = entity.values.get(&in_class_schema_property_id) { + // Ensure property value vector nonces equality to avoid possible data races, + // when performing vector specific operations + Self::ensure_nonce_equality(entity_prop_value, nonce)?; + // Validate a new property value against the type of this property + // and check any additional constraints like the length of a vector + // if it's a vector property or the length of a text if it's a text property. + Self::ensure_prop_value_can_be_inserted_at_prop_vec( + &property_value, + entity_prop_value, + index_in_property_vec, + class_prop, + )?; + }; + + // Insert property value into property value vector + >::mutate(entity_id, |entity| { + if let Some(entities_rc_to_increment) = Self::get_involved_entities(&property_value) { + Self::increment_entities_rc(&entities_rc_to_increment); + } + if let Some(current_prop_value) = entity.values.get_mut(&in_class_schema_property_id) { + current_prop_value.vec_insert_at(index_in_property_vec, property_value) + } + }); Ok(()) } @@ -846,8 +1383,8 @@ impl Module { with_credential: Option, as_entity_maintainer: bool, entity_id: EntityId, - schema_id: u16, - property_values: BTreeMap, + schema_id: SchemaId, + property_values: BTreeMap>, ) -> dispatch::Result { // class id of the entity being updated let class_id = Self::get_class_id_by_entity_id(entity_id)?; @@ -883,27 +1420,25 @@ impl Module { system::RawOrigin::Root => Ok(AccessLevel::System), system::RawOrigin::Signed(account_id) => { if let Some(credential) = with_credential { - if T::CredentialChecker::account_has_credential(&account_id, credential) { - if let Some(entity_id) = as_entity_maintainer { - // is entity maintained by system - ensure!( - >::exists(entity_id), - "NotEnityMaintainer" - ); - // ensure entity maintainer matches - match Self::entity_maintainer_by_entity_id(entity_id) { - Some(maintainer_credential) - if credential == maintainer_credential => - { - Ok(AccessLevel::EntityMaintainer) - } - _ => Err("NotEnityMaintainer"), + ensure!( + T::CredentialChecker::account_has_credential(&account_id, credential), + "OriginCannotActWithRequestedCredential" + ); + if let Some(entity_id) = as_entity_maintainer { + // is entity maintained by system + ensure!( + >::exists(entity_id), + "NotEnityMaintainer" + ); + // ensure entity maintainer matches + match Self::entity_maintainer_by_entity_id(entity_id) { + Some(maintainer_credential) if credential == maintainer_credential => { + Ok(AccessLevel::EntityMaintainer) } - } else { - Ok(AccessLevel::Credential(credential)) + _ => Err("NotEnityMaintainer"), } } else { - Err("OriginCannotActWithRequestedCredential") + Ok(AccessLevel::Credential(credential)) } } else { Ok(AccessLevel::Unspecified) @@ -913,6 +1448,18 @@ impl Module { } } + fn increment_entities_rc(entity_ids: &[EntityId]) { + entity_ids.iter().for_each(|entity_id| { + >::mutate(entity_id, |entity| entity.reference_count += 1) + }); + } + + fn decrement_entities_rc(entity_ids: &[EntityId]) { + entity_ids.iter().for_each(|entity_id| { + >::mutate(entity_id, |entity| entity.reference_count -= 1) + }); + } + /// Returns the stored class if exist, error otherwise. fn ensure_class_exists(class_id: ClassId) -> Result, &'static str> { ensure!(>::exists(class_id), ERROR_CLASS_NOT_FOUND); @@ -991,16 +1538,16 @@ impl Module { fn get_class_id_by_entity_id(entity_id: EntityId) -> Result { // use a utility method on versioned_store module - ensure!(EntityById::exists(entity_id), "EntityNotFound"); + ensure!(>::exists(entity_id), ERROR_ENTITY_NOT_FOUND); let entity = Self::entity_by_id(entity_id); Ok(entity.class_id) } - // Ensures property_values of type Internal that point to a class, + // Ensures property_values of type Reference that point to a class, // the target entity and class exists and constraint allows it. fn ensure_internal_property_values_permitted( source_class_id: ClassId, - property_values: &BTreeMap, + property_values: &BTreeMap>, ) -> dispatch::Result { for (in_class_index, property_value) in property_values.iter() { if let PropertyValue::Reference(ref target_entity_id) = property_value { @@ -1016,14 +1563,14 @@ impl Module { Err("EntityCannotReferenceTargetEntity") } ReferenceConstraint::Restricted(permitted_properties) => { - if permitted_properties.contains(&PropertyOfClass { - class_id: source_class_id, - property_index: *in_class_index, - }) { - Ok(()) - } else { - Err("EntityCannotReferenceTargetEntity") - } + ensure!( + permitted_properties.contains(&PropertyOfClass { + class_id: source_class_id, + property_index: *in_class_index, + }), + "EntityCannotReferenceTargetEntity" + ); + Ok(()) } }?; } @@ -1033,13 +1580,26 @@ impl Module { Ok(()) } + fn ensure_nonce_equality( + vec_value: &PropertyValue, + new_nonce: T::Nonce, + ) -> dispatch::Result { + if let Some(nonce) = vec_value.get_nonce() { + ensure!( + nonce == new_nonce, + ERROR_PROP_VALUE_VEC_NONCES_DOES_NOT_MATCH + ); + } + Ok(()) + } + /// Returns an index of a newly added class schema on success. pub fn append_class_schema( class_id: ClassId, - existing_properties: Vec, + existing_properties: Vec, new_properties: Vec, - ) -> Result { - Self::ensure_known_class_id(&class_id)?; + ) -> Result { + Self::ensure_known_class_id(class_id)?; let non_empty_schema = !existing_properties.is_empty() || !new_properties.is_empty(); @@ -1070,7 +1630,7 @@ impl Module { // Check that existing props are valid indices of class properties vector: let has_unknown_props = existing_properties .iter() - .any(|&prop_id| prop_id >= class.properties.len() as u16); + .any(|&prop_id| prop_id >= class.properties.len() as PropertyId); ensure!( !has_unknown_props, ERROR_CLASS_SCHEMA_REFERS_UNKNOWN_PROP_INDEX @@ -1088,13 +1648,13 @@ impl Module { // Use the current length of schemas in this class as an index // for the next schema that will be sent in a result of this function. - let schema_idx = class.schemas.len() as u16; + let schema_idx = class.schemas.len() as SchemaId; let mut schema = Schema::new(existing_properties); let mut updated_class_props = class.properties; new_properties.into_iter().for_each(|prop| { - let prop_id = updated_class_props.len() as u16; + let prop_id = updated_class_props.len() as PropertyId; updated_class_props.push(prop); schema.properties.push(prop_id); }); @@ -1109,10 +1669,10 @@ impl Module { pub fn add_entity_schema_support( entity_id: EntityId, - schema_id: u16, - property_values: BTreeMap, + schema_id: SchemaId, + property_values: BTreeMap>, ) -> dispatch::Result { - Self::ensure_known_entity_id(&entity_id)?; + Self::ensure_known_entity_id(entity_id)?; let (entity, class) = Self::get_entity_and_class(entity_id); @@ -1126,10 +1686,11 @@ impl Module { Self::ensure_schema_id_is_not_added(&entity, schema_id)?; let class_schema_opt = class.schemas.get(schema_id as usize); - let schema_prop_ids = class_schema_opt.unwrap().properties.clone(); + let schema_prop_ids = &class_schema_opt.unwrap().properties; let current_entity_values = entity.values.clone(); let mut appended_entity_values = entity.values; + let mut entities_rc_to_increment_vec = vec![]; for prop_id in schema_prop_ids.iter() { if current_entity_values.contains_key(prop_id) { @@ -1142,22 +1703,20 @@ impl Module { // If a value was not povided for the property of this schema: if let Some(new_value) = property_values.get(prop_id) { - Self::ensure_property_value_is_valid(new_value, class_prop)?; - + Self::ensure_property_value_to_update_is_valid(new_value, class_prop)?; + if let Some(entities_rc_to_increment) = Self::get_involved_entities(new_value) { + entities_rc_to_increment_vec.push(entities_rc_to_increment); + } appended_entity_values.insert(*prop_id, new_value.to_owned()); } else { // All required prop values should be are provided - if class_prop.required { - return Err(ERROR_MISSING_REQUIRED_PROP); - } - // Add all missing non required schema prop values as PropertyValue::None - else { - appended_entity_values.insert(*prop_id, PropertyValue::Bool(false)); - } + ensure!(!class_prop.required, ERROR_MISSING_REQUIRED_PROP); + // Add all missing non required schema prop values as PropertyValue::Bool(false) + appended_entity_values.insert(*prop_id, PropertyValue::Bool(false)); } } - EntityById::mutate(entity_id, |entity| { + >::mutate(entity_id, |entity| { // Add a new schema to the list of schemas supported by this entity. entity.supported_schemas.insert(schema_id); @@ -1166,6 +1725,11 @@ impl Module { entity.values = appended_entity_values; } }); + entities_rc_to_increment_vec + .iter() + .for_each(|entities_rc_to_increment| { + Self::increment_entities_rc(entities_rc_to_increment); + }); Ok(()) } @@ -1188,25 +1752,40 @@ impl Module { // Helper functions: // ---------------------------------------------------------------- - pub fn ensure_known_class_id(class_id: &ClassId) -> dispatch::Result { + pub fn ensure_known_class_id(class_id: ClassId) -> dispatch::Result { ensure!(>::exists(class_id), ERROR_CLASS_NOT_FOUND); Ok(()) } - pub fn ensure_known_entity_id(entity_id: &EntityId) -> dispatch::Result { - ensure!(EntityById::exists(entity_id), ERROR_ENTITY_NOT_FOUND); + pub fn ensure_known_entity_id(entity_id: EntityId) -> dispatch::Result { + ensure!(>::exists(entity_id), ERROR_ENTITY_NOT_FOUND); + Ok(()) + } + + pub fn ensure_rc_is_zero(entity_id: EntityId) -> dispatch::Result { + let entity = Self::entity_by_id(entity_id); + ensure!( + entity.reference_count == 0, + ERROR_ENTITY_REFERENCE_COUNTER_DOES_NOT_EQUAL_TO_ZERO + ); Ok(()) } - pub fn ensure_class_schema_id_exists(class: &Class, schema_id: u16) -> dispatch::Result { + pub fn ensure_class_schema_id_exists( + class: &Class, + schema_id: SchemaId, + ) -> dispatch::Result { ensure!( - schema_id < class.schemas.len() as u16, + schema_id < class.schemas.len() as SchemaId, ERROR_UNKNOWN_CLASS_SCHEMA_ID ); Ok(()) } - pub fn ensure_class_schema_is_active(class: &Class, schema_id: u16) -> dispatch::Result { + pub fn ensure_class_schema_is_active( + class: &Class, + schema_id: SchemaId, + ) -> dispatch::Result { ensure!( class.is_active_schema(schema_id), ERROR_CLASS_SCHEMA_NOT_ACTIVE @@ -1214,44 +1793,86 @@ impl Module { Ok(()) } - pub fn ensure_schema_id_is_not_added(entity: &Entity, schema_id: u16) -> dispatch::Result { + pub fn ensure_schema_id_is_not_added( + entity: &Entity, + schema_id: SchemaId, + ) -> dispatch::Result { let schema_not_added = !entity.supported_schemas.contains(&schema_id); ensure!(schema_not_added, ERROR_SCHEMA_ALREADY_ADDED_TO_ENTITY); Ok(()) } - pub fn ensure_valid_internal_prop(value: &PropertyValue, prop: &Property) -> dispatch::Result { - match (value, &prop.prop_type) { + pub fn ensure_valid_internal_prop( + value: &PropertyValue, + prop: &Property, + ) -> dispatch::Result { + match (value, prop.prop_type) { (PV::Reference(entity_id), PT::Reference(class_id)) => { Self::ensure_known_class_id(class_id)?; - Self::ensure_known_entity_id(entity_id)?; + Self::ensure_known_entity_id(*entity_id)?; let entity = Self::entity_by_id(entity_id); ensure!( - entity.class_id == *class_id, - ERROR_INTERNAL_RPOP_DOES_NOT_MATCH_ITS_CLASS + entity.class_id == class_id, + ERROR_INTERNAL_PROP_DOES_NOT_MATCH_ITS_CLASS ); - Ok(()) } - _ => Ok(()), + _ => (), } + Ok(()) } - pub fn is_unknown_internal_entity_id(id: PropertyValue) -> bool { + pub fn ensure_index_in_property_vector_is_valid( + value: &PropertyValue, + index_in_property_vec: VecMaxLength, + ) -> dispatch::Result { + fn is_valid_index(vec: &[T], index_in_property_vec: VecMaxLength) -> bool { + (index_in_property_vec as usize) < vec.len() + } + + let is_valid_index = match value { + PropertyValue::BoolVec(vec, _) => is_valid_index(vec, index_in_property_vec), + PropertyValue::Uint16Vec(vec, _) => is_valid_index(vec, index_in_property_vec), + PropertyValue::Uint32Vec(vec, _) => is_valid_index(vec, index_in_property_vec), + PropertyValue::Uint64Vec(vec, _) => is_valid_index(vec, index_in_property_vec), + PropertyValue::Int16Vec(vec, _) => is_valid_index(vec, index_in_property_vec), + PropertyValue::Int32Vec(vec, _) => is_valid_index(vec, index_in_property_vec), + PropertyValue::Int64Vec(vec, _) => is_valid_index(vec, index_in_property_vec), + PropertyValue::TextVec(vec, _) => is_valid_index(vec, index_in_property_vec), + PropertyValue::ReferenceVec(vec, _) => is_valid_index(vec, index_in_property_vec), + _ => return Err(ERROR_PROP_VALUE_UNDER_GIVEN_INDEX_IS_NOT_A_VECTOR), + }; + + ensure!( + is_valid_index, + ERROR_ENTITY_PROP_VALUE_VECTOR_INDEX_IS_OUT_OF_RANGE + ); + Ok(()) + } + + pub fn is_unknown_internal_entity_id(id: PropertyValue) -> bool { if let PropertyValue::Reference(entity_id) = id { - !EntityById::exists(entity_id) + !>::exists(entity_id) } else { false } } - pub fn get_entity_and_class(entity_id: EntityId) -> (Entity, Class) { - let entity = EntityById::get(entity_id); + pub fn get_entity_and_class(entity_id: EntityId) -> (Entity, Class) { + let entity = >::get(entity_id); let class = ClassById::get(entity.class_id); (entity, class) } - pub fn ensure_property_value_is_valid( - value: &PropertyValue, + pub fn get_involved_entities(current_prop_value: &PropertyValue) -> Option> { + match current_prop_value { + PropertyValue::Reference(entity_id) => Some(vec![*entity_id]), + PropertyValue::ReferenceVec(entity_ids_vec, _) => Some(entity_ids_vec.clone()), + _ => None, + } + } + + pub fn ensure_property_value_to_update_is_valid( + value: &PropertyValue, prop: &Property, ) -> dispatch::Result { Self::ensure_prop_value_matches_its_type(value, prop)?; @@ -1261,8 +1882,76 @@ impl Module { Ok(()) } + pub fn ensure_prop_value_can_be_inserted_at_prop_vec( + value: &PropertyValue, + entity_prop_value: &PropertyValue, + index_in_property_vec: VecMaxLength, + prop: &Property, + ) -> dispatch::Result { + Self::ensure_index_in_property_vector_is_valid(entity_prop_value, index_in_property_vec)?; + + fn validate_prop_vec_len_after_value_insert(vec: &[T], max_len: VecMaxLength) -> bool { + vec.len() < max_len as usize + } + + let is_valid_len = match (value, entity_prop_value, prop.prop_type) { + // Single values + (PV::Bool(_), PV::BoolVec(vec, _), PT::BoolVec(max_len)) => { + validate_prop_vec_len_after_value_insert(vec, max_len) + } + (PV::Uint16(_), PV::Uint16Vec(vec, _), PT::Uint16Vec(max_len)) => { + validate_prop_vec_len_after_value_insert(vec, max_len) + } + (PV::Uint32(_), PV::Uint32Vec(vec, _), PT::Uint32Vec(max_len)) => { + validate_prop_vec_len_after_value_insert(vec, max_len) + } + (PV::Uint64(_), PV::Uint64Vec(vec, _), PT::Uint64Vec(max_len)) => { + validate_prop_vec_len_after_value_insert(vec, max_len) + } + (PV::Int16(_), PV::Int16Vec(vec, _), PT::Int16Vec(max_len)) => { + validate_prop_vec_len_after_value_insert(vec, max_len) + } + (PV::Int32(_), PV::Int32Vec(vec, _), PT::Int32Vec(max_len)) => { + validate_prop_vec_len_after_value_insert(vec, max_len) + } + (PV::Int64(_), PV::Int64Vec(vec, _), PT::Int64Vec(max_len)) => { + validate_prop_vec_len_after_value_insert(vec, max_len) + } + (PV::Text(text_item), PV::TextVec(vec, _), PT::TextVec(vec_max_len, text_max_len)) => { + if validate_prop_vec_len_after_value_insert(vec, vec_max_len) { + Self::validate_max_len_of_text(text_item, text_max_len)?; + true + } else { + false + } + } + ( + PV::Reference(entity_id), + PV::ReferenceVec(vec, _), + PT::ReferenceVec(vec_max_len, class_id), + ) => { + Self::ensure_known_class_id(class_id)?; + if validate_prop_vec_len_after_value_insert(vec, vec_max_len) { + Self::ensure_known_entity_id(*entity_id)?; + let entity = Self::entity_by_id(entity_id); + ensure!( + entity.class_id == class_id, + ERROR_INTERNAL_PROP_DOES_NOT_MATCH_ITS_CLASS + ); + true + } else { + false + } + } + _ => return Err(ERROR_PROP_VALUE_TYPE_DOESNT_MATCH_INTERNAL_ENTITY_VECTOR_TYPE), + }; + + ensure!(is_valid_len, ERROR_ENTITY_PROP_VALUE_VECTOR_IS_TOO_LONG); + Ok(()) + } + pub fn validate_max_len_if_text_prop( - value: &PropertyValue, + value: &PropertyValue, prop: &Property, ) -> dispatch::Result { match (value, &prop.prop_type) { @@ -1271,35 +1960,32 @@ impl Module { } } - pub fn validate_max_len_of_text(text: &[u8], max_len: u16) -> dispatch::Result { - if text.len() <= max_len as usize { - Ok(()) - } else { - Err(ERROR_TEXT_PROP_IS_TOO_LONG) - } + pub fn validate_max_len_of_text(text: &[u8], max_len: TextMaxLength) -> dispatch::Result { + ensure!(text.len() <= max_len as usize, ERROR_TEXT_PROP_IS_TOO_LONG); + Ok(()) } pub fn validate_max_len_if_vec_prop( - value: &PropertyValue, + value: &PropertyValue, prop: &Property, ) -> dispatch::Result { - fn validate_slice_len(vec: &[T], max_len: &u16) -> bool { - vec.len() <= *max_len as usize + fn validate_vec_len(vec: &[T], max_len: VecMaxLength) -> bool { + vec.len() <= max_len as usize } - let is_valid_len = match (value, &prop.prop_type) { - (PV::BoolVec(vec), PT::BoolVec(max_len)) => validate_slice_len(vec, max_len), - (PV::Uint16Vec(vec), PT::Uint16Vec(max_len)) => validate_slice_len(vec, max_len), - (PV::Uint32Vec(vec), PT::Uint32Vec(max_len)) => validate_slice_len(vec, max_len), - (PV::Uint64Vec(vec), PT::Uint64Vec(max_len)) => validate_slice_len(vec, max_len), - (PV::Int16Vec(vec), PT::Int16Vec(max_len)) => validate_slice_len(vec, max_len), - (PV::Int32Vec(vec), PT::Int32Vec(max_len)) => validate_slice_len(vec, max_len), - (PV::Int64Vec(vec), PT::Int64Vec(max_len)) => validate_slice_len(vec, max_len), - - (PV::TextVec(vec), PT::TextVec(vec_max_len, text_max_len)) => { - if validate_slice_len(vec, vec_max_len) { + let is_valid_len = match (value, prop.prop_type) { + (PV::BoolVec(vec, _), PT::BoolVec(max_len)) => validate_vec_len(vec, max_len), + (PV::Uint16Vec(vec, _), PT::Uint16Vec(max_len)) => validate_vec_len(vec, max_len), + (PV::Uint32Vec(vec, _), PT::Uint32Vec(max_len)) => validate_vec_len(vec, max_len), + (PV::Uint64Vec(vec, _), PT::Uint64Vec(max_len)) => validate_vec_len(vec, max_len), + (PV::Int16Vec(vec, _), PT::Int16Vec(max_len)) => validate_vec_len(vec, max_len), + (PV::Int32Vec(vec, _), PT::Int32Vec(max_len)) => validate_vec_len(vec, max_len), + (PV::Int64Vec(vec, _), PT::Int64Vec(max_len)) => validate_vec_len(vec, max_len), + + (PV::TextVec(vec, _), PT::TextVec(vec_max_len, text_max_len)) => { + if validate_vec_len(vec, vec_max_len) { for text_item in vec.iter() { - Self::validate_max_len_of_text(text_item, *text_max_len)?; + Self::validate_max_len_of_text(text_item, text_max_len)?; } true } else { @@ -1307,15 +1993,15 @@ impl Module { } } - (PV::ReferenceVec(vec), PT::ReferenceVec(vec_max_len, class_id)) => { + (PV::ReferenceVec(vec, _), PT::ReferenceVec(vec_max_len, class_id)) => { Self::ensure_known_class_id(class_id)?; - if validate_slice_len(vec, vec_max_len) { + if validate_vec_len(vec, vec_max_len) { for entity_id in vec.iter() { - Self::ensure_known_entity_id(entity_id)?; + Self::ensure_known_entity_id(*entity_id)?; let entity = Self::entity_by_id(entity_id); ensure!( - entity.class_id == *class_id, - ERROR_INTERNAL_RPOP_DOES_NOT_MATCH_ITS_CLASS + entity.class_id == class_id, + ERROR_INTERNAL_PROP_DOES_NOT_MATCH_ITS_CLASS ); } true @@ -1327,25 +2013,22 @@ impl Module { _ => true, }; - if is_valid_len { - Ok(()) - } else { - Err(ERROR_VEC_PROP_IS_TOO_LONG) - } + ensure!(is_valid_len, ERROR_VEC_PROP_IS_TOO_LONG); + Ok(()) } pub fn ensure_prop_value_matches_its_type( - value: &PropertyValue, + value: &PropertyValue, prop: &Property, ) -> dispatch::Result { - if Self::does_prop_value_match_type(value, prop) { - Ok(()) - } else { - Err(ERROR_PROP_VALUE_DONT_MATCH_TYPE) - } + ensure!( + Self::does_prop_value_match_type(value, prop), + ERROR_PROP_VALUE_DONT_MATCH_TYPE + ); + Ok(()) } - pub fn does_prop_value_match_type(value: &PropertyValue, prop: &Property) -> bool { + pub fn does_prop_value_match_type(value: &PropertyValue, prop: &Property) -> bool { // A non required property can be updated to None: if !prop.required && *value == PV::Bool(false) { return true; @@ -1362,22 +2045,22 @@ impl Module { (PV::Text(_), PT::Text(_)) | (PV::Reference(_), PT::Reference(_)) | // Vectors: - (PV::BoolVec(_), PT::BoolVec(_)) | - (PV::Uint16Vec(_), PT::Uint16Vec(_)) | - (PV::Uint32Vec(_), PT::Uint32Vec(_)) | - (PV::Uint64Vec(_), PT::Uint64Vec(_)) | - (PV::Int16Vec(_), PT::Int16Vec(_)) | - (PV::Int32Vec(_), PT::Int32Vec(_)) | - (PV::Int64Vec(_), PT::Int64Vec(_)) | - (PV::TextVec(_), PT::TextVec(_, _)) | - (PV::ReferenceVec(_), PT::ReferenceVec(_, _)) => true, + (PV::BoolVec(_, _), PT::BoolVec(_)) | + (PV::Uint16Vec(_, _), PT::Uint16Vec(_)) | + (PV::Uint32Vec(_, _), PT::Uint32Vec(_)) | + (PV::Uint64Vec(_, _), PT::Uint64Vec(_)) | + (PV::Int16Vec(_, _), PT::Int16Vec(_)) | + (PV::Int32Vec(_, _), PT::Int32Vec(_)) | + (PV::Int64Vec(_, _), PT::Int64Vec(_)) | + (PV::TextVec(_, _), PT::TextVec(_, _)) | + (PV::ReferenceVec(_, _), PT::ReferenceVec(_, _)) => true, // (PV::External(_), PT::External(_)) => true, // (PV::ExternalVec(_), PT::ExternalVec(_, _)) => true, _ => false, } } - pub fn ensure_property_name_is_valid(text: &Vec) -> dispatch::Result { + pub fn ensure_property_name_is_valid(text: &[u8]) -> dispatch::Result { T::PropertyNameConstraint::get().ensure_valid( text.len(), ERROR_PROPERTY_NAME_TOO_SHORT, @@ -1385,7 +2068,7 @@ impl Module { ) } - pub fn ensure_property_description_is_valid(text: &Vec) -> dispatch::Result { + pub fn ensure_property_description_is_valid(text: &[u8]) -> dispatch::Result { T::PropertyDescriptionConstraint::get().ensure_valid( text.len(), ERROR_PROPERTY_DESCRIPTION_TOO_SHORT, @@ -1393,7 +2076,7 @@ impl Module { ) } - pub fn ensure_class_name_is_valid(text: &Vec) -> dispatch::Result { + pub fn ensure_class_name_is_valid(text: &[u8]) -> dispatch::Result { T::ClassNameConstraint::get().ensure_valid( text.len(), ERROR_CLASS_NAME_TOO_SHORT, @@ -1401,7 +2084,7 @@ impl Module { ) } - pub fn ensure_class_description_is_valid(text: &Vec) -> dispatch::Result { + pub fn ensure_class_description_is_valid(text: &[u8]) -> dispatch::Result { T::ClassDescriptionConstraint::get().ensure_valid( text.len(), ERROR_CLASS_DESCRIPTION_TOO_SHORT, diff --git a/runtime-modules/content-directory/src/mock.rs b/runtime-modules/content-directory/src/mock.rs index 8ad72604c9..ed12611d25 100644 --- a/runtime-modules/content-directory/src/mock.rs +++ b/runtime-modules/content-directory/src/mock.rs @@ -19,15 +19,25 @@ pub const MEMBER_TWO_WITH_CREDENTIAL_ONE: u64 = 103; pub const UNKNOWN_CLASS_ID: ClassId = 111; pub const UNKNOWN_ENTITY_ID: EntityId = 222; -pub const UNKNOWN_PROP_ID: u16 = 333; -pub const UNKNOWN_SCHEMA_ID: u16 = 444; +pub const UNKNOWN_PROP_ID: PropertyId = 333; +pub const UNKNOWN_SCHEMA_ID: SchemaId = 444; -pub const SCHEMA_ID_0: u16 = 0; -pub const SCHEMA_ID_1: u16 = 1; +pub const SCHEMA_ID_0: SchemaId = 0; +pub const SCHEMA_ID_1: SchemaId = 1; -pub const PROP_ID_BOOL: u16 = 0; -pub const PROP_ID_U32: u16 = 1; -pub const PROP_ID_INTERNAL: u16 = 2; +pub const ZERO_NONCE: ::Nonce = 0; +pub const FIRST_NONCE: ::Nonce = 1; +pub const SECOND_NONCE: ::Nonce = 2; + +pub const VALID_PROPERTY_VEC_INDEX: VecMaxLength = 0; +pub const INVALID_PROPERTY_VEC_INDEX: VecMaxLength = 5; + +pub const PROP_ID_BOOL: PropertyId = 0; +pub const PROP_ID_REFERENCE_VEC: PropertyId = 1; +pub const PROP_ID_U32: PropertyId = 1; +pub const PROP_ID_REFERENCE: PropertyId = 2; +pub const PROP_ID_U32_VEC: PropertyId = 3; +pub const PROP_ID_U32_VEC_MAX_LEN: PropertyId = 20; pub const PRINCIPAL_GROUP_MEMBERS: [[u64; 2]; 2] = [ [ @@ -123,6 +133,7 @@ impl Get for ClassDescriptionConstraint { impl Trait for Runtime { type Credential = u64; + type Nonce = u64; type CredentialChecker = MockCredentialChecker; type CreateClassPermissionsChecker = MockCreateClassPermissionsChecker; type PropertyNameConstraint = PropertyNameConstraint; @@ -249,10 +260,9 @@ pub fn with_test_externalities R>(f: F) -> R { } impl Property { - fn required(&self) -> Property { - let mut new_self = self.clone(); - new_self.required = true; - new_self + pub fn required(mut self) -> Self { + self.required = true; + self } } @@ -261,7 +271,7 @@ pub fn assert_class_props(class_id: ClassId, expected_props: Vec) { assert_eq!(class.properties, expected_props); } -pub fn assert_class_schemas(class_id: ClassId, expected_schema_prop_ids: Vec>) { +pub fn assert_class_schemas(class_id: ClassId, expected_schema_prop_ids: Vec>) { let class = TestModule::class_by_id(class_id); let schemas: Vec<_> = expected_schema_prop_ids .iter() @@ -283,7 +293,7 @@ pub fn simple_test_schema() -> Vec { }] } -pub fn simple_test_entity_property_values() -> BTreeMap { +pub fn simple_test_entity_property_values() -> BTreeMap> { let mut property_values = BTreeMap::new(); property_values.insert(0, PropertyValue::Int64(1337)); property_values @@ -338,6 +348,10 @@ pub fn create_entity_with_schema_support() -> EntityId { let (_, schema_id, entity_id) = create_class_with_schema_and_entity(); let mut property_values = BTreeMap::new(); property_values.insert(PROP_ID_BOOL, PropertyValue::Bool(true)); + property_values.insert( + PROP_ID_U32_VEC, + PropertyValue::Uint32Vec(vec![123, 234, 44], ::Nonce::default()), + ); assert_ok!(TestModule::add_entity_schema_support( entity_id, schema_id, @@ -346,7 +360,7 @@ pub fn create_entity_with_schema_support() -> EntityId { entity_id } -pub fn create_class_with_schema() -> (ClassId, u16) { +pub fn create_class_with_schema() -> (ClassId, SchemaId) { let class_id = create_simple_class_with_default_permissions(); let schema_id = TestModule::append_class_schema( class_id, @@ -354,14 +368,15 @@ pub fn create_class_with_schema() -> (ClassId, u16) { vec![ good_prop_bool().required(), good_prop_u32(), - new_internal_class_prop(class_id), + new_reference_class_prop(class_id), + good_prop_u32_vec(), ], ) .expect("This should not happen"); (class_id, schema_id) } -pub fn create_class_with_schema_and_entity() -> (ClassId, u16, EntityId) { +pub fn create_class_with_schema_and_entity() -> (ClassId, SchemaId, EntityId) { let (class_id, schema_id) = create_class_with_schema(); let entity_id = create_entity_of_class(class_id); (class_id, schema_id, entity_id) @@ -385,6 +400,15 @@ pub fn good_prop_u32() -> Property { } } +pub fn good_prop_u32_vec() -> Property { + Property { + prop_type: PropertyType::Uint32Vec(PROP_ID_U32_VEC_MAX_LEN), + required: false, + name: b"Name of a u32 vec property".to_vec(), + description: b"Description of a u32 vec property".to_vec(), + } +} + pub fn good_prop_text() -> Property { Property { prop_type: PropertyType::Text(20), @@ -394,7 +418,7 @@ pub fn good_prop_text() -> Property { } } -pub fn new_internal_class_prop(class_id: ClassId) -> Property { +pub fn new_reference_class_prop(class_id: ClassId) -> Property { Property { prop_type: PropertyType::Reference(class_id), required: false, @@ -403,6 +427,15 @@ pub fn new_internal_class_prop(class_id: ClassId) -> Property { } } +pub fn new_reference_class_prop_vec(class_id: ClassId) -> Property { + Property { + prop_type: PropertyType::ReferenceVec(PROP_ID_U32_VEC_MAX_LEN, class_id), + required: false, + name: b"Name of a internal property".to_vec(), + description: b"Description of a internal property".to_vec(), + } +} + pub fn good_class_name() -> Vec { b"Name of a class".to_vec() } @@ -415,17 +448,20 @@ pub fn good_props() -> Vec { vec![good_prop_bool(), good_prop_u32()] } -pub fn good_prop_ids() -> Vec { +pub fn good_prop_ids() -> Vec { vec![0, 1] } -pub fn bool_prop_value() -> BTreeMap { +pub fn bool_prop_value() -> BTreeMap> { let mut property_values = BTreeMap::new(); property_values.insert(0, PropertyValue::Bool(true)); property_values } -pub fn prop_value(index: u16, value: PropertyValue) -> BTreeMap { +pub fn prop_value( + index: PropertyId, + value: PropertyValue, +) -> BTreeMap> { let mut property_values = BTreeMap::new(); property_values.insert(index, value); property_values diff --git a/runtime-modules/content-directory/src/operations.rs b/runtime-modules/content-directory/src/operations.rs index 06d4c8ca85..b6702023e0 100644 --- a/runtime-modules/content-directory/src/operations.rs +++ b/runtime-modules/content-directory/src/operations.rs @@ -1,12 +1,12 @@ -use crate::{ClassId, EntityId, PropertyValue}; +use crate::{ClassId, EntityId, PropertyId, PropertyValue, SchemaId, Trait}; use codec::{Decode, Encode}; use rstd::collections::btree_map::BTreeMap; use rstd::prelude::*; #[derive(Encode, Decode, Eq, PartialEq, Clone, Debug)] -pub enum ParametrizedPropertyValue { +pub enum ParametrizedPropertyValue { /// Same fields as normal PropertyValue - PropertyValue(PropertyValue), + PropertyValue(PropertyValue), /// 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 @@ -22,12 +22,12 @@ pub enum ParameterizedEntity { } #[derive(Encode, Decode, Eq, PartialEq, Clone, Debug)] -pub struct ParametrizedClassPropertyValue { +pub struct ParametrizedClassPropertyValue { /// Index is into properties vector of class. - pub in_class_index: u16, + pub in_class_index: PropertyId, /// Value of property with index `in_class_index` in a given class. - pub value: ParametrizedPropertyValue, + pub value: ParametrizedPropertyValue, } #[derive(Encode, Decode, Eq, PartialEq, Clone, Debug)] @@ -36,30 +36,30 @@ pub struct CreateEntityOperation { } #[derive(Encode, Decode, Eq, PartialEq, Clone, Debug)] -pub struct UpdatePropertyValuesOperation { +pub struct UpdatePropertyValuesOperation { pub entity_id: ParameterizedEntity, - pub new_parametrized_property_values: Vec, + pub new_parametrized_property_values: Vec>, } #[derive(Encode, Decode, Eq, PartialEq, Clone, Debug)] -pub struct AddSchemaSupportToEntityOperation { +pub struct AddSchemaSupportToEntityOperation { pub entity_id: ParameterizedEntity, - pub schema_id: u16, - pub parametrized_property_values: Vec, + pub schema_id: SchemaId, + pub parametrized_property_values: Vec>, } #[derive(Encode, Decode, Eq, PartialEq, Clone, Debug)] -pub enum OperationType { +pub enum OperationType { CreateEntity(CreateEntityOperation), - UpdatePropertyValues(UpdatePropertyValuesOperation), - AddSchemaSupportToEntity(AddSchemaSupportToEntityOperation), + UpdatePropertyValues(UpdatePropertyValuesOperation), + AddSchemaSupportToEntity(AddSchemaSupportToEntityOperation), } #[derive(Encode, Decode, Eq, PartialEq, Clone, Debug)] -pub struct Operation { +pub struct Operation { pub with_credential: Option, pub as_entity_maintainer: bool, - pub operation_type: OperationType, + pub operation_type: OperationType, } pub fn parametrized_entity_to_entity_id( @@ -70,20 +70,17 @@ pub fn parametrized_entity_to_entity_id( ParameterizedEntity::ExistingEntity(entity_id) => Ok(entity_id), ParameterizedEntity::InternalEntityJustAdded(op_index_u32) => { let op_index = op_index_u32 as usize; - if created_entities.contains_key(&op_index) { - let entity_id = created_entities.get(&op_index).unwrap(); - Ok(*entity_id) - } else { - Err("EntityNotCreatedByOperation") - } + Ok(*created_entities + .get(&op_index) + .ok_or("EntityNotCreatedByOperation")?) } } } -pub fn parametrized_property_values_to_property_values( +pub fn parametrized_property_values_to_property_values( created_entities: &BTreeMap, - parametrized_property_values: Vec, -) -> Result, &'static str> { + parametrized_property_values: Vec>, +) -> Result>, &'static str> { let mut class_property_values = BTreeMap::new(); for parametrized_class_property_value in parametrized_property_values.into_iter() { @@ -94,12 +91,10 @@ pub fn parametrized_property_values_to_property_values( ) => { // Verify that referenced entity was indeed created created let op_index = entity_created_in_operation_index as usize; - if created_entities.contains_key(&op_index) { - let entity_id = created_entities.get(&op_index).unwrap(); - PropertyValue::Reference(*entity_id) - } else { - return Err("EntityNotCreatedByOperation"); - } + let entity_id = created_entities + .get(&op_index) + .ok_or("EntityNotCreatedByOperation")?; + PropertyValue::Reference(*entity_id) } ParametrizedPropertyValue::InternalEntityVec(parametrized_entities) => { let mut entities: Vec = vec![]; @@ -111,17 +106,15 @@ pub fn parametrized_property_values_to_property_values( entity_created_in_operation_index, ) => { let op_index = entity_created_in_operation_index as usize; - if created_entities.contains_key(&op_index) { - let entity_id = created_entities.get(&op_index).unwrap(); - entities.push(*entity_id); - } else { - return Err("EntityNotCreatedByOperation"); - } + let entity_id = created_entities + .get(&op_index) + .ok_or("EntityNotCreatedByOperation")?; + entities.push(*entity_id); } } } - PropertyValue::ReferenceVec(entities) + PropertyValue::ReferenceVec(entities, T::Nonce::default()) } }; diff --git a/runtime-modules/content-directory/src/permissions.rs b/runtime-modules/content-directory/src/permissions.rs index 8d2514ec4f..962befcd35 100644 --- a/runtime-modules/content-directory/src/permissions.rs +++ b/runtime-modules/content-directory/src/permissions.rs @@ -1,8 +1,5 @@ use codec::{Decode, Encode}; -use srml_support::dispatch; - -#[cfg(feature = "std")] -use serde::{Deserialize, Serialize}; +use srml_support::{dispatch, ensure}; use crate::constraint::*; use crate::credentials::*; @@ -33,6 +30,9 @@ where /// Who can create new entities in the versioned store of this class pub create_entities: CredentialSet, + /// Who can remove entities from the versioned store of this class + pub remove_entities: CredentialSet, + /// The type of constraint on referencing the class from other entities. pub reference_constraint: ReferenceConstraint, @@ -77,11 +77,11 @@ where match access_level { AccessLevel::System => Ok(()), AccessLevel::Credential(credential) => { - if class_permissions.add_schemas.contains(credential) { - Ok(()) - } else { - Err("NotInAddSchemasSet") - } + ensure!( + class_permissions.add_schemas.contains(credential), + "NotInAddSchemasSet" + ); + Ok(()) } AccessLevel::Unspecified => Err("UnspecifiedActor"), AccessLevel::EntityMaintainer => Err("AccessLevel::EntityMaintainer-UsedOutOfPlace"), @@ -90,16 +90,17 @@ where pub fn can_update_schema_status( class_permissions: &Self, + access_level: &AccessLevel, ) -> dispatch::Result { match access_level { AccessLevel::System => Ok(()), AccessLevel::Credential(credential) => { - if class_permissions.update_schemas_status.contains(credential) { - Ok(()) - } else { - Err("NotInUpdateSchemasStatusSet") - } + ensure!( + class_permissions.update_schemas_status.contains(credential), + "NotInUpdateSchemasStatusSet" + ); + Ok(()) } AccessLevel::Unspecified => Err("UnspecifiedActor"), AccessLevel::EntityMaintainer => Err("AccessLevel::EntityMaintainer-UsedOutOfPlace"), @@ -126,6 +127,26 @@ where } } + pub fn can_remove_entity( + class_permissions: &Self, + access_level: &AccessLevel, + ) -> dispatch::Result { + match access_level { + AccessLevel::System => Ok(()), + AccessLevel::Credential(credential) => { + if !class_permissions.entities_can_be_created { + Err("EntitiesCannotBeRemoved") + } else if class_permissions.remove_entities.contains(credential) { + Ok(()) + } else { + Err("NotInRemoveEntitiesSet") + } + } + AccessLevel::Unspecified => Err("UnspecifiedActor"), + AccessLevel::EntityMaintainer => Err("AccessLevel::EntityMaintainer-UsedOutOfPlace"), + } + } + pub fn can_update_entity( class_permissions: &Self, access_level: &AccessLevel, diff --git a/runtime-modules/content-directory/src/tests.rs b/runtime-modules/content-directory/src/tests.rs index 3726d05898..3a91b50e32 100644 --- a/runtime-modules/content-directory/src/tests.rs +++ b/runtime-modules/content-directory/src/tests.rs @@ -577,8 +577,8 @@ fn batch_transaction_simple() { )); // two entities created - assert!(EntityById::exists(entity_id)); - assert!(EntityById::exists(entity_id + 1)); + assert!(>::exists(entity_id)); + assert!(>::exists(entity_id + 1)); }) } @@ -658,20 +658,23 @@ fn batch_transaction_vector_of_entities() { )); // three entities created - assert!(EntityById::exists(entity_id)); - assert!(EntityById::exists(entity_id + 1)); - assert!(EntityById::exists(entity_id + 2)); + assert!(>::exists(entity_id)); + assert!(>::exists(entity_id + 1)); + assert!(>::exists(entity_id + 2)); assert_eq!( - EntityById::get(entity_id), - Entity { - class_id: new_class_id, - supported_schemas: BTreeSet::from_iter(vec![SCHEMA_ID_0].into_iter()), - values: prop_value( + TestModule::entity_by_id(entity_id), + Entity::new( + new_class_id, + BTreeSet::from_iter(vec![SCHEMA_ID_0].into_iter()), + prop_value( 0, - PropertyValue::ReferenceVec(vec![entity_id + 1, entity_id + 2,]) + PropertyValue::ReferenceVec( + vec![entity_id + 1, entity_id + 2,], + ::Nonce::default() + ) ) - } + ) ); }) } @@ -738,7 +741,7 @@ fn cannot_add_class_schema_when_it_refers_unknown_prop_index() { fn cannot_add_class_schema_when_it_refers_unknown_internal_id() { with_test_externalities(|| { let class_id = create_simple_class_with_default_permissions(); - let bad_internal_prop = new_internal_class_prop(UNKNOWN_CLASS_ID); + let bad_internal_prop = new_reference_class_prop(UNKNOWN_CLASS_ID); assert_err!( TestModule::append_class_schema( @@ -755,7 +758,7 @@ fn cannot_add_class_schema_when_it_refers_unknown_internal_id() { fn should_add_class_schema_with_internal_class_prop() { with_test_externalities(|| { let class_id = create_simple_class_with_default_permissions(); - let internal_class_prop = new_internal_class_prop(class_id); + let internal_class_prop = new_reference_class_prop(class_id); // Add first schema with new props. // No other props on the class at this time. @@ -1049,7 +1052,7 @@ fn cannot_add_schema_to_entity_when_prop_value_dont_match_type() { with_test_externalities(|| { let (_, schema_id, entity_id) = create_class_with_schema_and_entity(); let mut prop_values = bool_prop_value(); - prop_values.append(&mut prop_value(PROP_ID_U32, PropertyValue::Bool(true))); + prop_values.insert(PROP_ID_U32, PropertyValue::Bool(true)); assert_err!( TestModule::add_entity_schema_support(entity_id, schema_id, prop_values), ERROR_PROP_VALUE_DONT_MATCH_TYPE @@ -1062,10 +1065,10 @@ fn cannot_add_schema_to_entity_when_unknown_internal_entity_id() { with_test_externalities(|| { let (_, schema_id, entity_id) = create_class_with_schema_and_entity(); let mut prop_values = bool_prop_value(); - prop_values.append(&mut prop_value( - PROP_ID_INTERNAL, + prop_values.insert( + PROP_ID_REFERENCE, PropertyValue::Reference(UNKNOWN_ENTITY_ID), - )); + ); assert_err!( TestModule::add_entity_schema_support(entity_id, schema_id, prop_values), ERROR_ENTITY_NOT_FOUND @@ -1093,12 +1096,12 @@ fn should_add_schema_to_entity_when_some_optional_props_provided() { with_test_externalities(|| { let (_, schema_id, entity_id) = create_class_with_schema_and_entity(); let mut prop_values = bool_prop_value(); - prop_values.append(&mut prop_value(PROP_ID_U32, PropertyValue::Uint32(123))); + prop_values.insert(PROP_ID_U32, PropertyValue::Uint32(123)); assert_ok!(TestModule::add_entity_schema_support( entity_id, schema_id, // Note that an optional internal prop is not provided here. - prop_values + prop_values.clone() )); let entity = TestModule::entity_by_id(entity_id); @@ -1106,12 +1109,8 @@ fn should_add_schema_to_entity_when_some_optional_props_provided() { entity.supported_schemas, BTreeSet::from_iter(vec![SCHEMA_ID_0].into_iter()) ); - prop_values = bool_prop_value(); - prop_values.append(&mut prop_value(PROP_ID_U32, PropertyValue::Uint32(123))); - prop_values.append(&mut prop_value( - PROP_ID_INTERNAL, - PropertyValue::Bool(false), - )); + prop_values.insert(PROP_ID_REFERENCE, PropertyValue::Bool(false)); + prop_values.insert(PROP_ID_U32_VEC, PropertyValue::Bool(false)); assert_eq!(entity.values, prop_values); }) } @@ -1119,6 +1118,33 @@ fn should_add_schema_to_entity_when_some_optional_props_provided() { // Update entity properties // -------------------------------------- +#[test] +fn update_entity_props_successfully() { + with_test_externalities(|| { + let entity_id = create_entity_with_schema_support(); + let mut prop_values = prop_value(PROP_ID_BOOL, PropertyValue::Bool(true)); + prop_values.insert(PROP_ID_U32, PropertyValue::Bool(false)); + prop_values.insert(PROP_ID_REFERENCE, PropertyValue::Bool(false)); + prop_values.insert( + PROP_ID_U32_VEC, + PropertyValue::Uint32Vec(vec![123, 234, 44], ::Nonce::default()), + ); + assert_eq!(TestModule::entity_by_id(entity_id).values, prop_values); + prop_values = prop_value(PROP_ID_BOOL, PropertyValue::Bool(false)); + prop_values.insert(PROP_ID_U32, PropertyValue::Uint32(123)); + prop_values.insert(PROP_ID_REFERENCE, PropertyValue::Reference(entity_id)); + prop_values.insert( + PROP_ID_U32_VEC, + PropertyValue::Uint32Vec(vec![123, 234, 44, 88, 43], ::Nonce::one()), + ); + assert_ok!(TestModule::complete_entity_property_values_update( + entity_id, + prop_values.clone() + )); + assert_eq!(TestModule::entity_by_id(entity_id).values, prop_values); + }) +} + #[test] fn cannot_update_entity_props_when_entity_not_found() { with_test_externalities(|| { @@ -1151,7 +1177,7 @@ fn cannot_update_entity_props_when_unknown_internal_entity_id() { TestModule::complete_entity_property_values_update( entity_id, prop_value( - PROP_ID_INTERNAL, + PROP_ID_REFERENCE, PropertyValue::Reference(UNKNOWN_ENTITY_ID) ) ), @@ -1174,31 +1200,457 @@ fn cannot_update_entity_props_when_unknown_entity_prop_id() { }) } +// Entity property vector cleaning +// -------------------------------------- + #[test] -fn update_entity_props_successfully() { +fn complete_entity_property_vector_cleaning_successfully() { with_test_externalities(|| { let entity_id = create_entity_with_schema_support(); let mut prop_values = prop_value(PROP_ID_BOOL, PropertyValue::Bool(true)); - prop_values.append(&mut prop_value(PROP_ID_U32, PropertyValue::Bool(false))); - prop_values.append(&mut prop_value( - PROP_ID_INTERNAL, - PropertyValue::Bool(false), + prop_values.insert(PROP_ID_U32, PropertyValue::Bool(false)); + prop_values.insert( + PROP_ID_U32_VEC, + PropertyValue::Uint32Vec(vec![123, 234, 44], ::Nonce::default()), + ); + prop_values.insert(PROP_ID_REFERENCE, PropertyValue::Bool(false)); + + // Check property values runtime storage related to an entity before cleaning of entity property vector value under given schema id + assert_eq!(TestModule::entity_by_id(entity_id).values, prop_values); + + // Perform cleaning of entity property vector value under given schema id + assert_ok!(TestModule::complete_entity_property_vector_cleaning( + entity_id, + PROP_ID_U32_VEC )); + + // Update entity property values to compare with runtime storage entity value under given schema id + prop_values.insert( + PROP_ID_U32_VEC, + PropertyValue::Uint32Vec(vec![], ::Nonce::one()), + ); + + // Check property values runtime storage related to a entity right after + // cleaning entity property vector under given schema id assert_eq!(TestModule::entity_by_id(entity_id).values, prop_values); - prop_values = prop_value(PROP_ID_BOOL, PropertyValue::Bool(false)); - prop_values.append(&mut prop_value(PROP_ID_U32, PropertyValue::Uint32(123))); - prop_values.append(&mut prop_value( - PROP_ID_INTERNAL, - PropertyValue::Reference(entity_id), + }) +} + +#[test] +fn cannot_complete_entity_property_vector_cleaning_when_entity_not_found() { + with_test_externalities(|| { + assert_entity_not_found(TestModule::complete_entity_property_vector_cleaning( + UNKNOWN_ENTITY_ID, + PROP_ID_U32_VEC, )); - assert_ok!(TestModule::complete_entity_property_values_update( + }) +} + +#[test] +fn cannot_complete_entity_property_vector_cleaning_when_unknown_entity_prop_id() { + with_test_externalities(|| { + let entity_id = create_entity_with_schema_support(); + assert_err!( + TestModule::complete_entity_property_vector_cleaning(entity_id, UNKNOWN_PROP_ID), + ERROR_UNKNOWN_ENTITY_PROP_ID + ); + }) +} + +#[test] +fn cannot_complete_entity_property_vector_cleaning_when_entity_prop_id_is_not_a_vector() { + with_test_externalities(|| { + let entity_id = create_entity_with_schema_support(); + assert_err!( + TestModule::complete_entity_property_vector_cleaning(entity_id, PROP_ID_U32), + ERROR_PROP_VALUE_UNDER_GIVEN_INDEX_IS_NOT_A_VECTOR + ); + }) +} + +// Remove at entity property vector +// -------------------------------------- + +fn complete_remove_at_entity_property_vector() -> EntityId { + let entity_id = create_entity_with_schema_support(); + let mut prop_values = prop_value(PROP_ID_BOOL, PropertyValue::Bool(true)); + prop_values.insert(PROP_ID_U32, PropertyValue::Bool(false)); + prop_values.insert( + PROP_ID_U32_VEC, + PropertyValue::Uint32Vec(vec![123, 234, 44], ::Nonce::default()), + ); + prop_values.insert(PROP_ID_REFERENCE, PropertyValue::Bool(false)); + + // Check property values runtime storage related to an entity before removing at given index of entity property vector value + assert_eq!(TestModule::entity_by_id(entity_id).values, prop_values); + + // Perform removing at given index of entity property vector value + assert_ok!(TestModule::complete_remove_at_entity_property_vector( + entity_id, + PROP_ID_U32_VEC, + VALID_PROPERTY_VEC_INDEX, + ZERO_NONCE + )); + + // Update entity property values to compare with runtime storage entity value under given schema id + prop_values.insert( + PROP_ID_U32_VEC, + PropertyValue::Uint32Vec(vec![234, 44], ::Nonce::one()), + ); + + // Check property values runtime storage related to a entity right after + // removing at given index of entity property vector value + assert_eq!(TestModule::entity_by_id(entity_id).values, prop_values); + entity_id +} + +#[test] +fn complete_remove_at_entity_property_vector_successfully() { + with_test_externalities(|| { + let entity_id = complete_remove_at_entity_property_vector(); + // Perform second removal at given index of entity property vector value with new nonce + assert_ok!(TestModule::complete_remove_at_entity_property_vector( entity_id, - prop_values.clone() + PROP_ID_U32_VEC, + VALID_PROPERTY_VEC_INDEX, + FIRST_NONCE + )); + }) +} + +#[test] +fn cannot_complete_remove_at_entity_property_vector_when_entity_not_found() { + with_test_externalities(|| { + assert_entity_not_found(TestModule::complete_remove_at_entity_property_vector( + UNKNOWN_ENTITY_ID, + PROP_ID_U32_VEC, + VALID_PROPERTY_VEC_INDEX, + ZERO_NONCE, )); + }) +} + +#[test] +fn cannot_complete_remove_at_entity_property_vector_when_unknown_entity_prop_id() { + with_test_externalities(|| { + let entity_id = create_entity_with_schema_support(); + assert_err!( + TestModule::complete_remove_at_entity_property_vector( + entity_id, + UNKNOWN_PROP_ID, + VALID_PROPERTY_VEC_INDEX, + ZERO_NONCE + ), + ERROR_UNKNOWN_ENTITY_PROP_ID + ); + }) +} + +#[test] +fn cannot_complete_remove_at_entity_property_vector_when_entity_prop_vector_index_out_of_range() { + with_test_externalities(|| { + let entity_id = create_entity_with_schema_support(); + assert_err!( + TestModule::complete_remove_at_entity_property_vector( + entity_id, + PROP_ID_U32, + INVALID_PROPERTY_VEC_INDEX, + ZERO_NONCE + ), + ERROR_PROP_VALUE_UNDER_GIVEN_INDEX_IS_NOT_A_VECTOR + ); + }) +} + +#[test] +fn cannot_complete_remove_at_entity_property_vector_when_entity_prop_id_is_not_a_vector() { + with_test_externalities(|| { + let entity_id = create_entity_with_schema_support(); + assert_err!( + TestModule::complete_remove_at_entity_property_vector( + entity_id, + PROP_ID_U32, + VALID_PROPERTY_VEC_INDEX, + ZERO_NONCE + ), + ERROR_PROP_VALUE_UNDER_GIVEN_INDEX_IS_NOT_A_VECTOR + ); + }) +} + +#[test] +fn cannot_complete_remove_at_entity_property_vector_when_already_updated() { + with_test_externalities(|| { + let entity_id = complete_remove_at_entity_property_vector(); + assert_err!( + TestModule::complete_remove_at_entity_property_vector( + entity_id, + PROP_ID_U32_VEC, + VALID_PROPERTY_VEC_INDEX, + SECOND_NONCE + ), + ERROR_PROP_VALUE_VEC_NONCES_DOES_NOT_MATCH + ); + }) +} + +#[test] +fn complete_insert_at_entity_property_vector_successfully() { + with_test_externalities(|| { + let entity_id = create_entity_with_schema_support(); + let mut prop_values = prop_value(PROP_ID_BOOL, PropertyValue::Bool(true)); + prop_values.insert(PROP_ID_U32, PropertyValue::Bool(false)); + prop_values.insert( + PROP_ID_U32_VEC, + PropertyValue::Uint32Vec(vec![123, 234, 44], ::Nonce::default()), + ); + prop_values.insert(PROP_ID_REFERENCE, PropertyValue::Bool(false)); + + // Check property values runtime storage related to an entity before inserting at given index of entity property vector value + assert_eq!(TestModule::entity_by_id(entity_id).values, prop_values); + + // Perform inserting at given index of entity property vector value + assert_ok!(TestModule::complete_insert_at_entity_property_vector( + entity_id, + PROP_ID_U32_VEC, + VALID_PROPERTY_VEC_INDEX, + PropertyValue::Uint32(33), + ZERO_NONCE + )); + + // Perform second inserting at given index of entity property vector value with new nonce + assert_ok!(TestModule::complete_insert_at_entity_property_vector( + entity_id, + PROP_ID_U32_VEC, + VALID_PROPERTY_VEC_INDEX, + PropertyValue::Uint32(55), + FIRST_NONCE + )); + + // Update entity property values to compare with runtime storage entity value under given schema id + prop_values.insert( + PROP_ID_U32_VEC, + PropertyValue::Uint32Vec(vec![55, 33, 123, 234, 44], 2_u32.into()), + ); + + // Check property values runtime storage related to a entity right after + // inserting at given index of entity property vector value assert_eq!(TestModule::entity_by_id(entity_id).values, prop_values); }) } +#[test] +fn cannot_complete_insert_at_entity_property_vector_when_entity_not_found() { + with_test_externalities(|| { + assert_entity_not_found(TestModule::complete_insert_at_entity_property_vector( + UNKNOWN_ENTITY_ID, + PROP_ID_U32_VEC, + VALID_PROPERTY_VEC_INDEX, + PropertyValue::Uint32(33), + ZERO_NONCE, + )); + }) +} + +#[test] +fn cannot_complete_insert_at_entity_property_vector_when_unknown_entity_prop_id() { + with_test_externalities(|| { + let entity_id = create_entity_with_schema_support(); + assert_err!( + TestModule::complete_insert_at_entity_property_vector( + entity_id, + UNKNOWN_PROP_ID, + VALID_PROPERTY_VEC_INDEX, + PropertyValue::Uint32(33), + ZERO_NONCE + ), + ERROR_UNKNOWN_ENTITY_PROP_ID + ); + }) +} + +#[test] +fn cannot_complete_insert_at_entity_property_vector_when_entity_prop_id_is_not_a_vector() { + with_test_externalities(|| { + let entity_id = create_entity_with_schema_support(); + assert_err!( + TestModule::complete_insert_at_entity_property_vector( + entity_id, + PROP_ID_U32, + VALID_PROPERTY_VEC_INDEX, + PropertyValue::Uint32(17), + ZERO_NONCE + ), + ERROR_PROP_VALUE_UNDER_GIVEN_INDEX_IS_NOT_A_VECTOR + ); + }) +} + +#[test] +fn cannot_complete_insert_at_entity_when_entity_prop_value_vector_index_out_of_range() { + with_test_externalities(|| { + let entity_id = create_entity_with_schema_support(); + assert_err!( + TestModule::complete_insert_at_entity_property_vector( + entity_id, + PROP_ID_U32_VEC, + INVALID_PROPERTY_VEC_INDEX, + PropertyValue::Uint32(33), + ZERO_NONCE + ), + ERROR_ENTITY_PROP_VALUE_VECTOR_INDEX_IS_OUT_OF_RANGE + ); + }) +} + +#[test] +fn cannot_complete_insert_at_entity_when_property_type_does_not_match_internal_entity_vector_type() +{ + with_test_externalities(|| { + let entity_id = create_entity_with_schema_support(); + assert_err!( + TestModule::complete_insert_at_entity_property_vector( + entity_id, + PROP_ID_U32_VEC, + VALID_PROPERTY_VEC_INDEX, + PropertyValue::Uint16(33), + ZERO_NONCE + ), + ERROR_PROP_VALUE_TYPE_DOESNT_MATCH_INTERNAL_ENTITY_VECTOR_TYPE + ); + }) +} + +#[test] +fn cannot_complete_insert_at_entity_property_vector_when_entity_prop_value_vector_is_too_long() { + with_test_externalities(|| { + let (_, schema_id, entity_id) = create_class_with_schema_and_entity(); + let mut property_values = BTreeMap::new(); + property_values.insert(PROP_ID_BOOL, PropertyValue::Bool(true)); + property_values.insert( + PROP_ID_U32_VEC, + PropertyValue::Uint32Vec( + vec![5; PROP_ID_U32_VEC_MAX_LEN as usize], + ::Nonce::default(), + ), + ); + assert_ok!(TestModule::add_entity_schema_support( + entity_id, + schema_id, + property_values + )); + assert_err!( + TestModule::complete_insert_at_entity_property_vector( + entity_id, + PROP_ID_U32_VEC, + VALID_PROPERTY_VEC_INDEX, + PropertyValue::Uint32(33), + ZERO_NONCE + ), + ERROR_ENTITY_PROP_VALUE_VECTOR_IS_TOO_LONG + ); + }) +} + +#[test] +fn cannot_complete_insert_at_entity_property_vector_when_nonce_does_not_match() { + with_test_externalities(|| { + let entity_id = complete_remove_at_entity_property_vector(); + assert_err!( + TestModule::complete_insert_at_entity_property_vector( + entity_id, + PROP_ID_U32_VEC, + VALID_PROPERTY_VEC_INDEX, + PropertyValue::Uint32(33), + SECOND_NONCE + ), + ERROR_PROP_VALUE_VEC_NONCES_DOES_NOT_MATCH + ); + }) +} + +fn create_entity_with_prop_value_referencing_another_entity() -> (EntityId, EntityId) { + let class_id = create_simple_class_with_default_permissions(); + let schema_id = TestModule::append_class_schema( + class_id, + vec![], + vec![ + good_prop_bool().required(), + new_reference_class_prop_vec(class_id), + ], + ) + .expect("This should not happen"); + let entity_id = create_entity_of_class(class_id); + let entity_id_2 = create_entity_of_class(class_id); + let mut property_values = BTreeMap::new(); + property_values.insert(PROP_ID_BOOL, PropertyValue::Bool(true)); + property_values.insert( + PROP_ID_REFERENCE_VEC, + PropertyValue::ReferenceVec(vec![entity_id_2], ::Nonce::default()), + ); + assert_ok!(TestModule::add_entity_schema_support( + entity_id, + schema_id, + property_values + )); + (entity_id, entity_id_2) +} + +#[test] +fn cannot_complete_insert_at_entity_property_vector_when_unknown_internal_entity_id() { + with_test_externalities(|| { + let (entity_id, _) = create_entity_with_prop_value_referencing_another_entity(); + assert_err!( + TestModule::complete_insert_at_entity_property_vector( + entity_id, + PROP_ID_REFERENCE_VEC, + VALID_PROPERTY_VEC_INDEX, + PropertyValue::Reference(UNKNOWN_ENTITY_ID), + ZERO_NONCE + ), + ERROR_ENTITY_NOT_FOUND + ); + }) +} + +// Remove entity +// -------------------------------------- + +#[test] +fn remove_entity_successfully() { + with_test_externalities(|| { + let (_, _, entity_id) = create_class_with_schema_and_entity(); + assert_ok!(TestModule::remove_entity(Origin::ROOT, None, entity_id)); + // Ensure entity related storage was cleared successfully. + assert_eq!( + TestModule::entity_by_id(entity_id), + Entity::::default() + ); + assert_eq!(TestModule::entity_maintainer_by_entity_id(entity_id), None); + }) +} + +#[test] +fn remove_entity_not_found() { + with_test_externalities(|| { + assert_err!( + TestModule::remove_entity(Origin::ROOT, None, UNKNOWN_ENTITY_ID), + ERROR_ENTITY_NOT_FOUND + ); + }) +} + +#[test] +fn remove_entity_reference_counter_does_not_equal_zero() { + with_test_externalities(|| { + let (_, entity_by_id_2) = create_entity_with_prop_value_referencing_another_entity(); + assert_err!( + TestModule::remove_entity(Origin::ROOT, None, entity_by_id_2), + ERROR_ENTITY_REFERENCE_COUNTER_DOES_NOT_EQUAL_TO_ZERO + ); + }) +} + // TODO test text max len // TODO test vec max len diff --git a/runtime-modules/content-working-group/src/lib.rs b/runtime-modules/content-working-group/src/lib.rs index b0c6aeea93..970181b9b9 100755 --- a/runtime-modules/content-working-group/src/lib.rs +++ b/runtime-modules/content-working-group/src/lib.rs @@ -1191,7 +1191,7 @@ decl_module! { // Increment NextChannelId NextChannelId::::mutate(|id| *id += as One>::one()); - /// CREDENTIAL STUFF /// + // CREDENTIAL STUFF // // Dial out to membership module and inform about new role as channe owner. let registered_role = >::register_role_on_member(owner, &member_in_role).is_ok();