diff --git a/src/lib.rs b/src/lib.rs index 99fef0a8a3..24cb66350c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,10 +16,12 @@ extern crate parity_codec_derive; pub mod governance; use governance::{election, council, proposals}; +pub mod storage; +use storage::{data_object_type_registry}; mod memo; +mod traits; mod membership; use membership::members; -mod traits; mod migration; use rstd::prelude::*; @@ -230,6 +232,11 @@ impl memo::Trait for Runtime { type Event = Event; } +impl storage::data_object_type_registry::Trait for Runtime { + type Event = Event; + type DataObjectTypeID = u64; +} + impl members::Trait for Runtime { type Event = Event; type MemberId = u64; @@ -263,6 +270,7 @@ construct_runtime!( Memo: memo::{Module, Call, Storage, Event}, Members: members::{Module, Call, Storage, Event, Config}, Migration: migration::{Module, Call, Storage, Event}, + DataObjectTypeRegistry: data_object_type_registry::{Module, Call, Storage, Event, Config}, } ); diff --git a/src/storage/data_object_type_registry.rs b/src/storage/data_object_type_registry.rs new file mode 100644 index 0000000000..7b87d219e4 --- /dev/null +++ b/src/storage/data_object_type_registry.rs @@ -0,0 +1,154 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +use rstd::prelude::*; +use parity_codec::Codec; +use parity_codec_derive::{Encode, Decode}; +use srml_support::{StorageMap, StorageValue, decl_module, decl_storage, decl_event, ensure, Parameter}; +use runtime_primitives::traits::{SimpleArithmetic, As, Member, MaybeSerializeDebug, MaybeDebug}; +use system::{self, ensure_root}; +use crate::traits; + +pub trait Trait: system::Trait + MaybeDebug +{ + type Event: From> + Into<::Event>; + + type DataObjectTypeID: Parameter + Member + SimpleArithmetic + Codec + Default + Copy + + As + As + MaybeSerializeDebug + PartialEq; +} + + +static MSG_REQUIRE_NEW_DO_TYPE: &str = "New Data Object Type required; the provided one seems to be in use already!"; +static MSG_DO_TYPE_NOT_FOUND: &str = "Data Object Type with the given ID not found!"; +static MSG_REQUIRE_DO_TYPE_ID: &str = "Can only update Data Object Types that are already registered (with an ID)!"; + +const DEFAULT_FIRST_DATA_OBJECT_TYPE_ID: u64 = 1; + +#[derive(Clone, Encode, Decode, PartialEq)] +#[cfg_attr(feature = "std", derive(Debug))] +pub struct DataObjectType +{ + // If the OT is registered, an ID must exist, otherwise it's a new OT. + pub id: Option, + pub description: Vec, + pub active: bool, + + // TODO in future releases + // - maximum size + // - replication factor + // - storage tranches (empty is ok) +} + +decl_storage! { + trait Store for Module as DataObjectTypeRegistry + { + // Start at this value + pub FirstDataObjectTypeID get(first_data_object_type_id) config(first_data_object_type_id): T::DataObjectTypeID = T::DataObjectTypeID::sa(DEFAULT_FIRST_DATA_OBJECT_TYPE_ID); + + // Increment + pub NextDataObjectTypeID get(next_data_object_type_id) build(|config: &GenesisConfig| config.first_data_object_type_id): T::DataObjectTypeID = T::DataObjectTypeID::sa(DEFAULT_FIRST_DATA_OBJECT_TYPE_ID); + + // Mapping of Data object types + pub DataObjectTypeMap get(data_object_type): map T::DataObjectTypeID => Option>; + } +} + +decl_event! { + pub enum Event where + ::DataObjectTypeID + { + DataObjectTypeAdded(DataObjectTypeID), + DataObjectTypeUpdated(DataObjectTypeID), + } +} + + + +impl traits::IsActiveDataObjectType for Module +{ + fn is_active_data_object_type(which: &T::DataObjectTypeID) -> bool + { + match Self::ensure_data_object_type(*which) + { + Ok(do_type) => do_type.active, + Err(_err) => false + } + } +} + + +decl_module! { + pub struct Module for enum Call where origin: T::Origin + { + fn deposit_event() = default; + + pub fn register_data_object_type(origin, data_object_type: DataObjectType) + { + ensure_root(origin)?; + ensure!(data_object_type.id.is_none(), MSG_REQUIRE_NEW_DO_TYPE); + + let new_do_type_id = Self::next_data_object_type_id(); + let do_type: DataObjectType = DataObjectType { + id: Some(new_do_type_id), + description: data_object_type.description.clone(), + active: data_object_type.active, + }; + + >::insert(new_do_type_id, do_type); + >::mutate(|n| { *n += T::DataObjectTypeID::sa(1); }); + + Self::deposit_event(RawEvent::DataObjectTypeAdded(new_do_type_id)); + } + + pub fn update_data_object_type(origin, data_object_type: DataObjectType) + { + ensure_root(origin)?; + ensure!(data_object_type.id.is_some(), MSG_REQUIRE_DO_TYPE_ID); + + let id = data_object_type.id.unwrap(); + let mut do_type = Self::ensure_data_object_type(id)?; + + do_type.description = data_object_type.description.clone(); + do_type.active = data_object_type.active; + + >::insert(id, do_type); + + Self::deposit_event(RawEvent::DataObjectTypeUpdated(id)); + } + + // Activate and deactivate functions as separate functions, because + // toggling DO types is likely a more common operation than updating + // other aspects. + pub fn activate_data_object_type(origin, id: T::DataObjectTypeID) + { + ensure_root(origin)?; + let mut do_type = Self::ensure_data_object_type(id)?; + + do_type.active = true; + + >::insert(id, do_type); + + Self::deposit_event(RawEvent::DataObjectTypeUpdated(id)); + } + + pub fn deactivate_data_object_type(origin, id: T::DataObjectTypeID) + { + ensure_root(origin)?; + let mut do_type = Self::ensure_data_object_type(id)?; + + do_type.active = false; + + >::insert(id, do_type); + + Self::deposit_event(RawEvent::DataObjectTypeUpdated(id)); + } + + } +} + +impl Module +{ + fn ensure_data_object_type(id: T::DataObjectTypeID) -> Result, &'static str> + { + return Self::data_object_type(&id).ok_or(MSG_DO_TYPE_NOT_FOUND); + } +} diff --git a/src/storage/mock.rs b/src/storage/mock.rs new file mode 100644 index 0000000000..96191c1610 --- /dev/null +++ b/src/storage/mock.rs @@ -0,0 +1,89 @@ +#![cfg(test)] + +use rstd::prelude::*; +pub use super::{data_object_type_registry}; +pub use system; + +pub use primitives::{H256, Blake2Hasher}; +pub use runtime_primitives::{ + BuildStorage, + traits::{BlakeTwo256, OnFinalise, IdentityLookup}, + testing::{Digest, DigestItem, Header, UintAuthorityId} +}; + +use srml_support::{impl_outer_origin, impl_outer_event}; + +impl_outer_origin! { + pub enum Origin for Test {} +} + +impl_outer_event! { + pub enum MetaEvent for Test + { + data_object_type_registry, + } +} + +// For testing the module, we construct most of a mock runtime. This means +// first constructing a configuration type (`Test`) which `impl`s each of the +// configuration traits of modules we want to use. +#[derive(Clone, Eq, PartialEq, Debug)] +pub struct Test; +impl system::Trait for Test +{ + type Origin = Origin; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type Digest = Digest; + type AccountId = u64; + type Header = Header; + type Event = MetaEvent; + type Log = DigestItem; + type Lookup = IdentityLookup; +} +impl data_object_type_registry::Trait for Test +{ + type Event = MetaEvent; + type DataObjectTypeID = u64; +} + +pub struct ExtBuilder +{ + first_data_object_type_id: u64, +} + +impl Default for ExtBuilder +{ + fn default() -> Self + { + Self { + first_data_object_type_id: 1, + } + } +} + +impl ExtBuilder +{ + pub fn first_data_object_type_id(mut self, first_data_object_type_id: u64) -> Self + { + self.first_data_object_type_id = first_data_object_type_id; + self + } + pub fn build(self) -> runtime_io::TestExternalities + { + let mut t = system::GenesisConfig::::default().build_storage().unwrap().0; + + t.extend(data_object_type_registry::GenesisConfig::{ + first_data_object_type_id: self.first_data_object_type_id, + }.build_storage().unwrap().0); + + t.into() + } +} + + +pub type System = system::Module; +pub type TestDataObjectTypeRegistry = data_object_type_registry::Module; +pub type TestDataObjectType = data_object_type_registry::DataObjectType; diff --git a/src/storage/mod.rs b/src/storage/mod.rs new file mode 100644 index 0000000000..c2255437b7 --- /dev/null +++ b/src/storage/mod.rs @@ -0,0 +1,6 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +pub mod data_object_type_registry; + +mod mock; +mod tests; diff --git a/src/storage/tests.rs b/src/storage/tests.rs new file mode 100644 index 0000000000..dbacde2ee3 --- /dev/null +++ b/src/storage/tests.rs @@ -0,0 +1,169 @@ +#![cfg(test)] + +use super::*; +use super::mock::*; + +use runtime_io::with_externalities; +use srml_support::*; +use system::{self, Phase, EventRecord}; + +#[test] +fn initial_state() +{ + const DEFAULT_FIRST_ID: u64 = 1000; + + with_externalities(&mut ExtBuilder::default() + .first_data_object_type_id(DEFAULT_FIRST_ID).build(), || + { + assert_eq!(TestDataObjectTypeRegistry::first_data_object_type_id(), DEFAULT_FIRST_ID); + }); +} + +#[test] +fn fail_register_without_root() +{ + const DEFAULT_FIRST_ID: u64 = 1000; + + with_externalities(&mut ExtBuilder::default() + .first_data_object_type_id(DEFAULT_FIRST_ID).build(), || + { + let data: TestDataObjectType = TestDataObjectType { + id: None, + description: "foo".as_bytes().to_vec(), + active: false, + }; + let res = TestDataObjectTypeRegistry::register_data_object_type(Origin::signed(1), data); + assert!(res.is_err()); + }); +} + +#[test] +fn succeed_register_as_root() +{ + const DEFAULT_FIRST_ID: u64 = 1000; + + with_externalities(&mut ExtBuilder::default() + .first_data_object_type_id(DEFAULT_FIRST_ID).build(), || + { + let data: TestDataObjectType = TestDataObjectType { + id: None, + description: "foo".as_bytes().to_vec(), + active: false, + }; + let res = TestDataObjectTypeRegistry::register_data_object_type(Origin::ROOT, data); + assert!(res.is_ok()); + }); +} + +#[test] +fn update_existing() +{ + const DEFAULT_FIRST_ID: u64 = 1000; + + with_externalities(&mut ExtBuilder::default() + .first_data_object_type_id(DEFAULT_FIRST_ID).build(), || + { + // First register a type + let data: TestDataObjectType = TestDataObjectType { + id: None, + description: "foo".as_bytes().to_vec(), + active: false, + }; + let id_res = TestDataObjectTypeRegistry::register_data_object_type(Origin::ROOT, data); + assert!(id_res.is_ok()); + assert_eq!(*System::events().last().unwrap(), + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::data_object_type_registry(data_object_type_registry::RawEvent::DataObjectTypeAdded(DEFAULT_FIRST_ID)), + } + ); + + + // Now update it with new data - we need the ID to be the same as in + // returned by the previous call. First, though, try and fail without + let updated1: TestDataObjectType = TestDataObjectType { + id: None, + description: "bar".as_bytes().to_vec(), + active: false, + }; + let res = TestDataObjectTypeRegistry::update_data_object_type(Origin::ROOT, updated1); + assert!(res.is_err()); + + // Now try with a bad ID + let updated2: TestDataObjectType = TestDataObjectType { + id: Some(DEFAULT_FIRST_ID + 1), + description: "bar".as_bytes().to_vec(), + active: false, + }; + let res = TestDataObjectTypeRegistry::update_data_object_type(Origin::ROOT, updated2); + assert!(res.is_err()); + + // Finally with an existing ID, it should work. + let updated3: TestDataObjectType = TestDataObjectType { + id: Some(DEFAULT_FIRST_ID), + description: "bar".as_bytes().to_vec(), + active: false, + }; + let res = TestDataObjectTypeRegistry::update_data_object_type(Origin::ROOT, updated3); + assert!(res.is_ok()); + assert_eq!(*System::events().last().unwrap(), + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::data_object_type_registry(data_object_type_registry::RawEvent::DataObjectTypeUpdated(DEFAULT_FIRST_ID)), + } + ); + }); +} + + +#[test] +fn activate_existing() +{ + const DEFAULT_FIRST_ID: u64 = 1000; + + with_externalities(&mut ExtBuilder::default() + .first_data_object_type_id(DEFAULT_FIRST_ID).build(), || + { + // First register a type + let data: TestDataObjectType = TestDataObjectType { + id: None, + description: "foo".as_bytes().to_vec(), + active: false, + }; + let id_res = TestDataObjectTypeRegistry::register_data_object_type(Origin::ROOT, data); + assert!(id_res.is_ok()); + assert_eq!(*System::events().last().unwrap(), + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::data_object_type_registry(data_object_type_registry::RawEvent::DataObjectTypeAdded(DEFAULT_FIRST_ID)), + } + ); + + // Retrieve, and ensure it's not active. + let data = TestDataObjectTypeRegistry::data_object_type(DEFAULT_FIRST_ID); + assert!(data.is_some()); + assert!(!data.unwrap().active); + + // Now activate the data object type + let res = TestDataObjectTypeRegistry::activate_data_object_type(Origin::ROOT, DEFAULT_FIRST_ID); + assert!(res.is_ok()); + assert_eq!(*System::events().last().unwrap(), + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::data_object_type_registry(data_object_type_registry::RawEvent::DataObjectTypeUpdated(DEFAULT_FIRST_ID)), + } + ); + + // Ensure that the item is actually activated. + let data = TestDataObjectTypeRegistry::data_object_type(DEFAULT_FIRST_ID); + assert!(data.is_some()); + assert!(data.unwrap().active); + + // Deactivate again. + let res = TestDataObjectTypeRegistry::deactivate_data_object_type(Origin::ROOT, DEFAULT_FIRST_ID); + assert!(res.is_ok()); + let data = TestDataObjectTypeRegistry::data_object_type(DEFAULT_FIRST_ID); + assert!(data.is_some()); + assert!(!data.unwrap().active); + }); +} diff --git a/src/traits.rs b/src/traits.rs index 8b24b830da..96185de357 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,9 +1,18 @@ #![cfg_attr(not(feature = "std"), no_std)] +use crate::storage::data_object_type_registry; use system; +pub trait IsActiveDataObjectType +{ + fn is_active_data_object_type(which: &T::DataObjectTypeID) -> bool + { + false + } +} + pub trait IsActiveMember { fn is_active_member(account_id: &T::AccountId) -> bool { false } -} \ No newline at end of file +}