Browse Source

Merge branch 'data-directory' into silence-warnings

Jens Finkhaeuser 6 years ago
parent
commit
1816a47ab2

+ 3 - 3
src/governance/election.rs

@@ -17,14 +17,14 @@ use super::sealed_vote::SealedVote;
 pub use super::{ GovernanceCurrency, BalanceOf };
 use super::council;
 
-use crate::traits::{IsActiveMember};
+use crate::traits::{Members};
 
 pub trait Trait: system::Trait + council::Trait + GovernanceCurrency {
     type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
 
     type CouncilElected: CouncilElected<Seats<Self::AccountId, BalanceOf<Self>>, Self::BlockNumber>;
 
-    type IsActiveMember: IsActiveMember<Self>;
+    type Members: Members<Self>;
 }
 
 #[derive(Clone, Copy, Encode, Decode)]
@@ -154,7 +154,7 @@ impl<T: Trait> Module<T> {
 
 
     fn can_participate(sender: &T::AccountId) -> bool {
-        !T::Currency::free_balance(sender).is_zero() && T::IsActiveMember::is_active_member(sender)
+        !T::Currency::free_balance(sender).is_zero() && T::Members::is_active_member(sender)
     }
 
     // PUBLIC IMMUTABLES

+ 15 - 8
src/governance/mock.rs

@@ -2,7 +2,7 @@
 
 pub use super::{election, council, proposals, GovernanceCurrency};
 pub use system;
-use crate::traits;
+use crate::traits::{Members};
 
 pub use primitives::{H256, Blake2Hasher};
 pub use runtime_primitives::{
@@ -17,16 +17,23 @@ impl_outer_origin! {
     pub enum Origin for Test {}
 }
 
-pub struct AnyAccountIsMember {}
-impl<T: system::Trait> traits::IsActiveMember<T> for AnyAccountIsMember {
-    fn is_active_member(_who: &T::AccountId) -> bool {
+pub struct MockMembership {}
+impl<T: system::Trait> Members<T> for MockMembership {
+    type Id = u32;
+    fn is_active_member(who: &T::AccountId) -> bool {
+        // all accounts are considered members.
+        // There is currently no test coverage for non-members.
+        // Should add some coverage, and update this method to reflect which accounts are or are not members
         true
     }
+    fn lookup_member_id(account_id: &T::AccountId) -> Result<Self::Id, &'static str> {
+        Err("not implemented!")
+    }
+    fn lookup_account_by_member_id(id: Self::Id) -> Result<T::AccountId, &'static str> {
+        Err("not implemented!")
+    }
 }
 
-// default trait implementation - any account is not a member
-// impl<T: system::Trait> traits::IsActiveMember<T> for () {}
-
 // 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.
@@ -64,7 +71,7 @@ impl election::Trait for Test {
 
     type CouncilElected = (Council,);
 
-    type IsActiveMember = AnyAccountIsMember;
+    type Members = MockMembership;
 }
 
 impl balances::Trait for Test {

+ 27 - 17
src/governance/proposals.rs

@@ -1,4 +1,4 @@
-use srml_support::{StorageValue, StorageMap, dispatch::Result, decl_module, decl_event, decl_storage, ensure};
+use srml_support::{StorageValue, StorageMap, dispatch, decl_module, decl_event, decl_storage, ensure};
 use srml_support::traits::{Currency};
 use runtime_primitives::traits::{As, Hash, Zero};
 use runtime_io::print;
@@ -11,7 +11,7 @@ use primitives::{storage::well_known_keys};
 
 use super::council;
 pub use super::{ GovernanceCurrency, BalanceOf };
-use crate::traits::{IsActiveMember};
+use crate::traits::{Members};
 
 const DEFAULT_APPROVAL_QUORUM: u32 = 60;
 const DEFAULT_MIN_STAKE: u64 = 100;
@@ -120,7 +120,7 @@ pub trait Trait: timestamp::Trait + council::Trait + GovernanceCurrency {
     /// The overarching event type.
     type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
 
-    type IsActiveMember: IsActiveMember<Self>;
+    type Members: Members<Self>;
 }
 
 decl_event!(
@@ -352,7 +352,7 @@ impl<T: Trait> Module<T> {
     }
 
     fn can_participate(sender: T::AccountId) -> bool {
-        !T::Currency::free_balance(&sender).is_zero() && T::IsActiveMember::is_active_member(&sender)
+        !T::Currency::free_balance(&sender).is_zero() && T::Members::is_active_member(&sender)
     }
 
     fn is_councilor(sender: &T::AccountId) -> bool {
@@ -371,7 +371,7 @@ impl<T: Trait> Module<T> {
         Self::current_block() >= proposed_at + Self::voting_period()
     }
 
-    fn _process_vote(voter: T::AccountId, proposal_id: u32, vote: VoteKind) -> Result {
+    fn _process_vote(voter: T::AccountId, proposal_id: u32, vote: VoteKind) -> dispatch::Result {
         let new_vote = (voter.clone(), vote.clone());
         if <VotesByProposal<T>>::exists(proposal_id) {
             // Append a new vote to other votes on this proposal:
@@ -385,7 +385,7 @@ impl<T: Trait> Module<T> {
         Ok(())
     }
 
-    fn end_block(_now: T::BlockNumber) -> Result {
+    fn end_block(_now: T::BlockNumber) -> dispatch::Result {
 
         // TODO refactor this method
 
@@ -398,7 +398,7 @@ impl<T: Trait> Module<T> {
     }
 
     /// Get the voters for the current proposal.
-    pub fn tally(/* proposal_id: u32 */) -> Result {
+    pub fn tally(/* proposal_id: u32 */) -> dispatch::Result {
 
         let councilors: u32 = Self::councilors_count();
         let quorum: u32 = Self::approval_quorum_seats();
@@ -478,7 +478,7 @@ impl<T: Trait> Module<T> {
     }
 
     /// Updates proposal status and removes proposal from active ids.
-    fn _update_proposal_status(proposal_id: u32, new_status: ProposalStatus) -> Result {
+    fn _update_proposal_status(proposal_id: u32, new_status: ProposalStatus) -> dispatch::Result {
         let all_active_ids = Self::active_proposal_ids();
         let all_len = all_active_ids.len();
         let other_active_ids: Vec<u32> = all_active_ids
@@ -506,7 +506,7 @@ impl<T: Trait> Module<T> {
     }
 
     /// Slash a proposal. The staked deposit will be slashed.
-    fn _slash_proposal(proposal_id: u32) -> Result {
+    fn _slash_proposal(proposal_id: u32) -> dispatch::Result {
         let proposal = Self::proposals(proposal_id);
 
         // Slash proposer's stake:
@@ -516,7 +516,7 @@ impl<T: Trait> Module<T> {
     }
 
     /// Reject a proposal. The staked deposit will be returned to a proposer.
-    fn _reject_proposal(proposal_id: u32) -> Result {
+    fn _reject_proposal(proposal_id: u32) -> dispatch::Result {
         let proposal = Self::proposals(proposal_id);
         let proposer = proposal.proposer;
 
@@ -532,7 +532,7 @@ impl<T: Trait> Module<T> {
     }
 
     /// Approve a proposal. The staked deposit will be returned.
-    fn _approve_proposal(proposal_id: u32) -> Result {
+    fn _approve_proposal(proposal_id: u32) -> dispatch::Result {
         let proposal = Self::proposals(proposal_id);
         let wasm_code = Self::wasm_code_by_hash(proposal.wasm_hash);
 
@@ -617,14 +617,24 @@ mod tests {
 
     impl Trait for Test {
         type Event = ();
-        type IsActiveMember = AnyAccountIsMember;
+        type Members = MockMembership;
     }
 
-    pub struct AnyAccountIsMember {}
-    impl<T: system::Trait> IsActiveMember<T> for AnyAccountIsMember {
-        fn is_active_member(_who: &T::AccountId) -> bool {
+    pub struct MockMembership {}
+    impl<T: system::Trait> Members<T> for MockMembership {
+        type Id = u32;
+        fn is_active_member(who: &T::AccountId) -> bool {
+            // all accounts are considered members.
+            // There is currently no test coverage for non-members.
+            // Should add some coverage, and update this method to reflect which accounts are or are not members
             true
         }
+        fn lookup_member_id(account_id: &T::AccountId) -> Result<Self::Id, &'static str> {
+            Err("not implemented!")
+        }
+        fn lookup_account_by_member_id(id: Self::Id) -> Result<T::AccountId, &'static str> {
+            Err("not implemented!")
+        }
     }
 
     type System = system::Module<Test>;
@@ -718,7 +728,7 @@ mod tests {
         b"Proposal Wasm Code".to_vec()
     }
 
-    fn _create_default_proposal() -> Result {
+    fn _create_default_proposal() -> dispatch::Result {
         _create_proposal(None, None, None, None, None)
     }
 
@@ -728,7 +738,7 @@ mod tests {
         name: Option<Vec<u8>>,
         description: Option<Vec<u8>>,
         wasm_code: Option<Vec<u8>>
-    ) -> Result {
+    ) -> dispatch::Result {
         Proposals::create_proposal(
             Origin::signed(origin.unwrap_or(PROPOSER1)),
             stake.unwrap_or(min_stake()),

+ 14 - 5
src/lib.rs

@@ -23,6 +23,8 @@ mod traits;
 mod membership;
 use membership::members;
 mod migration;
+mod roles;
+use roles::actors;
 
 use rstd::prelude::*;
 #[cfg(feature = "std")]
@@ -187,7 +189,7 @@ impl balances::Trait for Runtime {
 	/// What to do if a new account is created.
 	type OnNewAccount = Indices;
 	/// Restrict whether an account can transfer funds. We don't place any further restrictions.
-	type EnsureAccountLiquid = Staking;
+	type EnsureAccountLiquid = (Staking, Actors);
 	/// The uniquitous event type.
 	type Event = Event;
 }
@@ -215,13 +217,13 @@ impl governance::GovernanceCurrency for Runtime {
 
 impl governance::proposals::Trait for Runtime {
 	type Event = Event;
-	type IsActiveMember = Members;
+	type Members = Members;
 }
 
 impl governance::election::Trait for Runtime {
 	type Event = Event;
 	type CouncilElected = (Council,);
-	type IsActiveMember = Members;
+	type Members = Members;
 }
 
 impl governance::council::Trait for Runtime {
@@ -242,7 +244,7 @@ impl storage::data_directory::Trait for Runtime
 {
 	type Event = Event;
 	type ContentId = ContentId;
-	type IsActiveMember = Members;
+	type Members = Members;
 	type IsActiveDataObjectType = DataObjectTypeRegistry;
 }
 
@@ -257,7 +259,7 @@ impl storage::data_object_storage_registry::Trait for Runtime
 {
 	type Event = Event;
 	type DataObjectStorageRelationshipId = u64;
-	type IsActiveMember = Members;
+	type Members = Members;
 	type ContentIdExists = DataDirectory;
 }
 
@@ -267,12 +269,18 @@ impl members::Trait for Runtime {
 	type MemberId = u64;
 	type PaidTermId = u64;
 	type SubscriptionId = u64;
+	type Roles = Actors;
 }
 
 impl migration::Trait for Runtime {
 	type Event = Event;
 }
 
+impl actors::Trait for Runtime {
+	type Event = Event;
+	type Members = Members;
+}
+
 construct_runtime!(
 	pub enum Runtime with Log(InternalLog: DigestItem<Hash, Ed25519AuthorityId>) where
 		Block = Block,
@@ -295,6 +303,7 @@ construct_runtime!(
 		Memo: memo::{Module, Call, Storage, Event<T>},
 		Members: members::{Module, Call, Storage, Event<T>, Config<T>},
 		Migration: migration::{Module, Call, Storage, Event<T>},
+		Actors: actors::{Module, Call, Storage, Event<T>},
 		DataObjectTypeRegistry: data_object_type_registry::{Module, Call, Storage, Event<T>, Config<T>},
 		DataDirectory: data_directory::{Module, Call, Storage, Event<T>},
 		DataObjectStorageRegistry: data_object_storage_registry::{Module, Call, Storage, Event<T>, Config<T>},

+ 25 - 3
src/membership/members.rs

@@ -9,7 +9,7 @@ use runtime_primitives::traits::{SimpleArithmetic, As, Member, MaybeSerializeDeb
 use system::{self, ensure_signed};
 use crate::governance::{GovernanceCurrency, BalanceOf };
 use {timestamp};
-use crate::traits;
+use crate::traits::{Members, Roles};
 
 pub trait Trait: system::Trait + GovernanceCurrency + timestamp::Trait {
     type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
@@ -22,6 +22,8 @@ pub trait Trait: system::Trait + GovernanceCurrency + timestamp::Trait {
 
     type SubscriptionId: Parameter + Member + SimpleArithmetic + Codec + Default + Copy
         + As<usize> + As<u64> + MaybeSerializeDebug + PartialEq;
+
+    type Roles: Roles<Self>;
 }
 
 const DEFAULT_FIRST_MEMBER_ID: u64 = 1;
@@ -173,7 +175,9 @@ impl<T: Trait> Module<T> {
     }
 }
 
-impl<T: Trait> traits::IsActiveMember<T> for Module<T> {
+impl<T: Trait> Members<T> for Module<T> {
+    type Id = T::MemberId;
+
     fn is_active_member(who: &T::AccountId) -> bool {
         match Self::ensure_is_member(who)
             .and_then(|member_id| Self::ensure_profile(member_id))
@@ -182,6 +186,18 @@ impl<T: Trait> traits::IsActiveMember<T> for Module<T> {
             Err(_err) => false
         }
     }
+
+    fn lookup_member_id(who: &T::AccountId) -> Result<Self::Id, &'static str> {
+        Self::ensure_is_member(who)
+    }
+
+    fn lookup_account_by_member_id(id: Self::Id) -> Result<T::AccountId, &'static str> {
+        if <AccountIdByMemberId<T>>::exists(&id) {
+            Ok(Self::account_id_by_member_id(&id))
+        } else {
+            Err("member id doesn't exist")
+        }
+    }
 }
 
 decl_module! {
@@ -198,6 +214,9 @@ decl_module! {
             // ensure key not associated with an existing membership
             Self::ensure_not_member(&who)?;
 
+            // ensure account is not in a bonded role
+            ensure!(!T::Roles::is_role_account(&who), "role key cannot be used for membership");
+
             // ensure paid_terms_id is active
             let terms = Self::ensure_active_terms_id(paid_terms_id)?;
 
@@ -270,6 +289,9 @@ decl_module! {
             // ensure key not associated with an existing membership
             Self::ensure_not_member(&new_member)?;
 
+            // ensure account is not in a bonded role
+            ensure!(!T::Roles::is_role_account(&new_member), "role key cannot be used for membership");
+
             let user_info = Self::check_user_registration_info(user_info)?;
 
             // ensure handle is not already registered
@@ -292,7 +314,7 @@ impl<T: Trait> Module<T> {
         Ok(())
     }
 
-    fn ensure_is_member(who: &T::AccountId) -> Result<T::MemberId, &'static str> {
+    pub fn ensure_is_member(who: &T::AccountId) -> Result<T::MemberId, &'static str> {
         let member_id = Self::member_id_by_account_id(who).ok_or("no member id found for accountid")?;
         Ok(member_id)
     }

+ 1 - 0
src/membership/mock.rs

@@ -73,6 +73,7 @@ impl members::Trait for Test {
     type MemberId = u32;
     type PaidTermId = u32;
     type SubscriptionId = u32;
+    type Roles = ();
 }
 
 pub struct ExtBuilder {

+ 38 - 18
src/migration.rs

@@ -6,24 +6,9 @@ use rstd::prelude::*;
 use runtime_io::print;
 use crate::{VERSION};
 use crate::membership::members;
-
-pub trait Trait: system::Trait + members::Trait {
-    type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
-}
-
-decl_storage! {
-    trait Store for Module<T: Trait> as Migration {
-        /// Records at what runtime spec version the store was initialized. This allows the runtime
-        /// to know when to run initialize code if it was installed as an update.
-        pub SpecVersion get(spec_version) build(|_| Some(VERSION.spec_version)) : Option<u32>;
-    }
-}
-
-decl_event! {
-    pub enum Event<T> where <T as system::Trait>::BlockNumber {
-        Migrated(BlockNumber, u32),
-    }
-}
+use crate::roles::actors;
+use crate::governance::{GovernanceCurrency, BalanceOf };
+use runtime_primitives::traits::{Zero, Bounded, SimpleArithmetic, As};
 
 // When preparing a new major runtime release version bump this value to match it and update
 // the initialization code in runtime_initialization(). Because of the way substrate runs runtime code
@@ -40,6 +25,23 @@ impl<T: Trait> Module<T> {
 
         <members::Module<T>>::initialize_storage();
 
+        // Initialize Storage provider role parameters
+        <actors::Module<T>>::set_role_parameters(actors::Role::Storage, actors::RoleParameters {
+            min_stake: BalanceOf::<T>::sa(3000),
+            max_actors: 10,
+            reward: BalanceOf::<T>::sa(10),
+            reward_period: T::BlockNumber::sa(600),
+            unbonding_period: T::BlockNumber::sa(600),
+            entry_request_fee: BalanceOf::<T>::sa(50),
+
+            // not currently used
+            min_actors: 5,
+            bonding_period: T::BlockNumber::sa(600),
+            min_service_period: T::BlockNumber::sa(600),
+            startup_grace_period: T::BlockNumber::sa(600),
+        });
+        <actors::Module<T>>::set_available_roles(vec![actors::Role::Storage]);
+
         // ...
         // add initialization of other modules introduced in this runtime
         // ...
@@ -48,6 +50,24 @@ impl<T: Trait> Module<T> {
     }
 }
 
+pub trait Trait: system::Trait + members::Trait + actors::Trait {
+    type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
+}
+
+decl_storage! {
+    trait Store for Module<T: Trait> as Migration {
+        /// Records at what runtime spec version the store was initialized. This allows the runtime
+        /// to know when to run initialize code if it was installed as an update.
+        pub SpecVersion get(spec_version) build(|_| Some(VERSION.spec_version)) : Option<u32>;
+    }
+}
+
+decl_event! {
+    pub enum Event<T> where <T as system::Trait>::BlockNumber {
+        Migrated(BlockNumber, u32),
+    }
+}
+
 decl_module! {
     pub struct Module<T: Trait> for enum Call where origin: T::Origin {
         fn deposit_event<T>() = default;

+ 381 - 0
src/roles/actors.rs

@@ -0,0 +1,381 @@
+#![cfg_attr(not(feature = "std"), no_std)]
+
+use crate::governance::{BalanceOf, GovernanceCurrency};
+use parity_codec_derive::{Decode, Encode};
+use rstd::prelude::*;
+use runtime_primitives::traits::{
+    As, Bounded, MaybeDebug, Zero,
+};
+use srml_support::traits::{Currency, EnsureAccountLiquid};
+use srml_support::{
+    decl_event, decl_module, decl_storage, dispatch, ensure, StorageMap, StorageValue,
+};
+use system::{self, ensure_signed};
+
+use crate::traits::{Members, Roles};
+
+#[derive(Encode, Decode, Copy, Clone, Eq, PartialEq, Debug)]
+pub enum Role {
+    Storage,
+}
+
+#[cfg_attr(feature = "std", derive(Debug))]
+#[derive(Encode, Decode, Copy, Clone, Eq, PartialEq)]
+pub struct RoleParameters<T: Trait> {
+    // minium balance required to stake to enter a role
+    pub min_stake: BalanceOf<T>,
+
+    // minimum actors to maintain - if role is unstaking
+    // and remaining actors would be less that this value - prevent or punish for unstaking
+    pub min_actors: u32,
+
+    // the maximum number of spots available to fill for a role
+    pub max_actors: u32,
+
+    // fixed amount of tokens paid to actors' primary account
+    pub reward: BalanceOf<T>,
+
+    // payouts are made at this block interval
+    pub reward_period: T::BlockNumber,
+
+    // minimum amount of time before being able to unstake
+    pub bonding_period: T::BlockNumber,
+
+    // how long tokens remain locked for after unstaking
+    pub unbonding_period: T::BlockNumber,
+
+    // minimum period required to be in service. unbonding before this time is highly penalized
+    pub min_service_period: T::BlockNumber,
+
+    // "startup" time allowed for roles that need to sync their infrastructure
+    // with other providers before they are considered in service and punishable for
+    // not delivering required level of service.
+    pub startup_grace_period: T::BlockNumber,
+
+    // small fee burned to make a request to enter role
+    pub entry_request_fee: BalanceOf<T>,
+}
+
+#[derive(Encode, Decode, Clone)]
+pub struct Actor<T: Trait> {
+    pub member_id: MemberId<T>,
+    pub role: Role,
+    pub account: T::AccountId,
+    pub joined_at: T::BlockNumber,
+}
+
+pub trait Trait: system::Trait + GovernanceCurrency + MaybeDebug {
+    type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
+
+    type Members: Members<Self>;
+}
+
+pub type MemberId<T> = <<T as Trait>::Members as Members<T>>::Id;
+// actor account, memberid, role, expires
+pub type Request<T> = (<T as system::Trait>::AccountId, MemberId<T>, Role, <T as system::Trait>::BlockNumber);
+pub type Requests<T> = Vec<Request<T>>;
+
+pub const REQUEST_LIFETIME: u64 = 300;
+pub const DEFAULT_REQUEST_CLEARING_INTERVAL: u64 = 100;
+
+decl_storage! {
+    trait Store for Module<T: Trait> as Actors {
+        /// requirements to enter and maintain status in roles
+        pub Parameters get(parameters) : map Role => Option<RoleParameters<T>>;
+
+        /// the roles members can enter into
+        pub AvailableRoles get(available_roles) : Vec<Role>;
+
+        /// Actors list
+        pub ActorAccountIds get(actor_account_ids) : Vec<T::AccountId>;
+
+        /// actor accounts mapped to their actor
+        pub ActorByAccountId get(actor_by_account_id) : map T::AccountId => Option<Actor<T>>;
+
+        /// actor accounts associated with a role
+        pub AccountIdsByRole get(account_ids_by_role) : map Role => Vec<T::AccountId>;
+
+        /// actor accounts associated with a member id
+        pub AccountIdsByMemberId get(account_ids_by_member_id) : map MemberId<T> => Vec<T::AccountId>;
+
+        /// tokens locked until given block number
+        pub Bondage get(bondage) : map T::AccountId => T::BlockNumber;
+
+        /// First step before enter a role is registering intent with a new account/key.
+        /// This is done by sending a role_entry_request() from the new account.
+        /// The member must then send a stake() transaction to approve the request and enter the desired role.
+        /// The account making the request will be bonded and must have
+        /// sufficient balance to cover the minimum stake for the role.
+        /// Bonding only occurs after successful entry into a role.
+        /// The request expires after REQUEST_LIFETIME blocks
+        pub RoleEntryRequests get(role_entry_requests) : Requests<T>;
+    }
+}
+
+decl_event! {
+    pub enum Event<T> where
+      <T as system::Trait>::AccountId {
+        EntryRequested(AccountId, Role),
+        Staked(AccountId, Role),
+        Unstaked(AccountId, Role),
+    }
+}
+
+impl<T: Trait> Module<T> {
+    fn is_role_available(role: Role) -> bool {
+        Self::available_roles().into_iter().any(|r| role == r)
+    }
+
+    fn ensure_actor(role_key: &T::AccountId) -> Result<Actor<T>, &'static str> {
+        Self::actor_by_account_id(role_key).ok_or("not role key")
+    }
+
+    fn ensure_actor_is_member(
+        role_key: &T::AccountId,
+        member_id: MemberId<T>,
+    ) -> Result<Actor<T>, &'static str> {
+        let actor = Self::ensure_actor(role_key)?;
+        if actor.member_id == member_id {
+            Ok(actor)
+        } else {
+            Err("actor not owned by member")
+        }
+    }
+
+    fn ensure_role_parameters(role: Role) -> Result<RoleParameters<T>, &'static str> {
+        Self::parameters(role).ok_or("no parameters for role")
+    }
+
+    // Mutating
+
+    fn remove_actor_from_service(actor_account: T::AccountId, role: Role, member_id: MemberId<T>) {
+        let accounts: Vec<T::AccountId> = Self::account_ids_by_role(role)
+            .into_iter()
+            .filter(|account| !(*account == actor_account))
+            .collect();
+        <AccountIdsByRole<T>>::insert(role, accounts);
+
+        let accounts: Vec<T::AccountId> = Self::account_ids_by_member_id(&member_id)
+            .into_iter()
+            .filter(|account| !(*account == actor_account))
+            .collect();
+        <AccountIdsByMemberId<T>>::insert(&member_id, accounts);
+
+        let accounts: Vec<T::AccountId> = Self::actor_account_ids()
+            .into_iter()
+            .filter(|account| !(*account == actor_account))
+            .collect();
+        <ActorAccountIds<T>>::put(accounts);
+
+        <ActorByAccountId<T>>::remove(&actor_account);
+    }
+
+    fn apply_unstake(
+        actor_account: T::AccountId,
+        role: Role,
+        member_id: MemberId<T>,
+        unbonding_period: T::BlockNumber,
+    ) {
+        // simple unstaking ...only applying unbonding period
+        <Bondage<T>>::insert(
+            &actor_account,
+            <system::Module<T>>::block_number() + unbonding_period,
+        );
+
+        Self::remove_actor_from_service(actor_account, role, member_id);
+    }
+}
+
+impl<T: Trait> Roles<T> for Module<T> {
+    fn is_role_account(account_id: &T::AccountId) -> bool {
+        <ActorByAccountId<T>>::exists(account_id) || <Bondage<T>>::exists(account_id)
+    }
+}
+
+decl_module! {
+    pub struct Module<T: Trait> for enum Call where origin: T::Origin {
+        fn deposit_event<T>() = default;
+
+        fn on_initialise(now: T::BlockNumber) {
+            // clear expired requests
+            if now % T::BlockNumber::sa(DEFAULT_REQUEST_CLEARING_INTERVAL) == T::BlockNumber::zero() {
+                let requests: Requests<T> = Self::role_entry_requests()
+                    .into_iter()
+                    .filter(|request| request.3 < now)
+                    .collect();
+
+                <RoleEntryRequests<T>>::put(requests);
+            }
+        }
+
+        fn on_finalise(now: T::BlockNumber) {
+
+            // payout rewards to actors
+            for role in Self::available_roles().iter() {
+                if let Some(params) = Self::parameters(role) {
+                    if !(now % params.reward_period == T::BlockNumber::zero()) { continue }
+                    let accounts = Self::account_ids_by_role(role);
+                    for actor in accounts.into_iter().map(|account| Self::actor_by_account_id(account)) {
+                        if let Some(actor) = actor {
+                            if now > actor.joined_at + params.reward_period {
+                                // send reward to member account - not the actor account
+                                if let Ok(member_account) = T::Members::lookup_account_by_member_id(actor.member_id) {
+                                    let _ = T::Currency::reward(&member_account, params.reward);
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+
+            if now % T::BlockNumber::sa(100) == T::BlockNumber::zero() {
+                // clear unbonded accounts
+                let actor_accounts: Vec<T::AccountId> = Self::actor_account_ids()
+                    .into_iter()
+                    .filter(|account| {
+                        if <Bondage<T>>::exists(account) {
+                            if Self::bondage(account) > now {
+                                true
+                            } else {
+                                <Bondage<T>>::remove(account);
+                                false
+                            }
+                        } else {
+                            true
+                        }
+                    })
+                    .collect();
+                <ActorAccountIds<T>>::put(actor_accounts);
+            }
+
+            // eject actors not staking the minimum
+            // iterating over available roles, so if a role has been removed at some point
+            // and an actor hasn't unstaked .. this will not apply to them.. which doesn't really matter
+            // because they are no longer incentivised to stay in the role anyway
+            // TODO: this needs a bit more preparation. The right time to check for each actor is different, as they enter
+            // role at different times.
+            // for role in Self::available_roles().iter() {
+            // }
+
+        }
+
+        pub fn role_entry_request(origin, role: Role, member_id: MemberId<T>) {
+            let sender = ensure_signed(origin)?;
+
+            ensure!(T::Members::lookup_member_id(&sender).is_err(), "account is a member");
+            ensure!(!Self::is_role_account(&sender), "account already used");
+
+            ensure!(Self::is_role_available(role), "inactive role");
+
+            let role_parameters = Self::ensure_role_parameters(role)?;
+
+            // pay (burn) entry fee - spam filter
+            let fee = role_parameters.entry_request_fee;
+            ensure!(T::Currency::can_slash(&sender, fee), "cannot pay role entry request fee");
+            let _ = T::Currency::slash(&sender, fee);
+
+            <RoleEntryRequests<T>>::mutate(|requests| {
+                let expires = <system::Module<T>>::block_number()+ T::BlockNumber::sa(REQUEST_LIFETIME);
+                requests.push((sender.clone(), member_id, role, expires));
+            });
+            Self::deposit_event(RawEvent::EntryRequested(sender, role));
+        }
+
+        /// Member activating entry request
+        pub fn stake(origin, role: Role, actor_account: T::AccountId) {
+            let sender = ensure_signed(origin)?;
+            let member_id = T::Members::lookup_member_id(&sender)?;
+
+            if !Self::role_entry_requests()
+                .iter()
+                .any(|request| request.0 == actor_account && request.1 == member_id && request.2 == role)
+            {
+                return Err("no role entry request matches");
+            }
+
+            ensure!(T::Members::lookup_member_id(&actor_account).is_err(), "account is a member");
+            ensure!(!Self::is_role_account(&actor_account), "account already used");
+
+            // make sure role is still available
+            ensure!(Self::is_role_available(role), "inactive role");
+            let role_parameters = Self::ensure_role_parameters(role)?;
+
+            let accounts_in_role = Self::account_ids_by_role(role);
+
+            // ensure there is an empty slot for the role
+            ensure!(accounts_in_role.len() < role_parameters.max_actors as usize, "role slots full");
+
+            // ensure the actor account has enough balance
+            ensure!(T::Currency::free_balance(&actor_account) >= role_parameters.min_stake, "not enough balance to stake");
+
+            <AccountIdsByRole<T>>::mutate(role, |accounts| accounts.push(actor_account.clone()));
+            <AccountIdsByMemberId<T>>::mutate(&member_id, |accounts| accounts.push(actor_account.clone()));
+            <Bondage<T>>::insert(&actor_account, T::BlockNumber::max_value());
+            <ActorByAccountId<T>>::insert(&actor_account, Actor {
+                member_id,
+                account: actor_account.clone(),
+                role,
+                joined_at: <system::Module<T>>::block_number()
+            });
+            <ActorAccountIds<T>>::mutate(|accounts| accounts.push(actor_account.clone()));
+
+            let requests: Requests<T> = Self::role_entry_requests()
+                .into_iter()
+                .filter(|request| request.0 != actor_account)
+                .collect();
+            <RoleEntryRequests<T>>::put(requests);
+
+            Self::deposit_event(RawEvent::Staked(actor_account, role));
+        }
+
+        pub fn unstake(origin, actor_account: T::AccountId) {
+            let sender = ensure_signed(origin)?;
+            let member_id = T::Members::lookup_member_id(&sender)?;
+
+            let actor = Self::ensure_actor_is_member(&actor_account, member_id)?;
+
+            let role_parameters = Self::ensure_role_parameters(actor.role)?;
+
+            Self::apply_unstake(actor.account.clone(), actor.role, actor.member_id, role_parameters.unbonding_period);
+
+            Self::deposit_event(RawEvent::Unstaked(actor.account, actor.role));
+        }
+
+        pub fn set_role_parameters(role: Role, params: RoleParameters<T>) {
+            <Parameters<T>>::insert(role, params);
+        }
+
+        pub fn set_available_roles(roles: Vec<Role>) {
+            <AvailableRoles<T>>::put(roles);
+        }
+
+        pub fn add_to_available_roles(role: Role) {
+            if !Self::available_roles().into_iter().any(|r| r == role) {
+                <AvailableRoles<T>>::mutate(|roles| roles.push(role));
+            }
+        }
+
+        pub fn remove_from_available_roles(role: Role) {
+            // Should we eject actors in the role being removed?
+            let roles: Vec<Role> = Self::available_roles().into_iter().filter(|r| role != *r).collect();
+            <AvailableRoles<T>>::put(roles);
+        }
+
+        pub fn remove_actor(actor_account: T::AccountId) {
+            let member_id = T::Members::lookup_member_id(&actor_account)?;
+            let actor = Self::ensure_actor_is_member(&actor_account, member_id)?;
+            let role_parameters = Self::ensure_role_parameters(actor.role)?;
+            Self::apply_unstake(actor.account, actor.role, actor.member_id, role_parameters.unbonding_period);
+        }
+    }
+}
+
+impl<T: Trait> EnsureAccountLiquid<T::AccountId> for Module<T> {
+    fn ensure_account_liquid(who: &T::AccountId) -> dispatch::Result {
+        if Self::bondage(who) <= <system::Module<T>>::block_number() {
+            Ok(())
+        } else {
+            Err("cannot transfer illiquid funds")
+        }
+    }
+}

+ 136 - 0
src/roles/mock.rs

@@ -0,0 +1,136 @@
+#![cfg(test)]
+
+pub use super::actors;
+pub use crate::governance::GovernanceCurrency;
+use crate::traits::Members;
+pub use system;
+
+pub use primitives::{Blake2Hasher, H256};
+pub use runtime_primitives::{
+    testing::{Digest, DigestItem, Header, UintAuthorityId},
+    traits::{BlakeTwo256, IdentityLookup, OnFinalise},
+    BuildStorage,
+};
+
+use srml_support::impl_outer_origin;
+
+impl_outer_origin! {
+    pub enum Origin for Test {}
+}
+
+// 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 = ();
+    type Log = DigestItem;
+    type Lookup = IdentityLookup<u64>;
+}
+impl timestamp::Trait for Test {
+    type Moment = u64;
+    type OnTimestampSet = ();
+}
+impl consensus::Trait for Test {
+    type SessionKey = UintAuthorityId;
+    type InherentOfflineReport = ();
+    type Log = DigestItem;
+}
+
+impl balances::Trait for Test {
+    type Event = ();
+
+    /// The balance of an account.
+    type Balance = u32;
+
+    /// A function which is invoked when the free-balance has fallen below the existential deposit and
+    /// has been reduced to zero.
+    ///
+    /// Gives a chance to clean up resources associated with the given account.
+    type OnFreeBalanceZero = ();
+
+    /// Handler for when a new account is created.
+    type OnNewAccount = ();
+
+    /// A function that returns true iff a given account can transfer its funds to another account.
+    type EnsureAccountLiquid = ();
+}
+
+impl GovernanceCurrency for Test {
+    type Currency = balances::Module<Self>;
+}
+
+pub struct MockMembers {}
+
+impl MockMembers {
+    pub fn alice_id() -> u32 {
+        1
+    }
+    pub fn alice_account() -> u64 {
+        1
+    }
+    pub fn bob_id() -> u32 {
+        2
+    }
+    pub fn bob_account() -> u64 {
+        2
+    }
+}
+
+impl Members<Test> for MockMembers {
+    type Id = u32;
+    fn is_active_member(who: &u64) -> bool {
+        if *who == Self::alice_account() {
+            return true;
+        }
+        if *who == Self::bob_account() {
+            return true;
+        }
+        false
+    }
+    fn lookup_member_id(who: &u64) -> Result<u32, &'static str> {
+        if *who == Self::alice_account() {
+            return Ok(Self::alice_id());
+        }
+        if *who == Self::bob_account() {
+            return Ok(Self::bob_id());
+        }
+        Err("member not found")
+    }
+    fn lookup_account_by_member_id(id: Self::Id) -> Result<u64, &'static str> {
+        if id == Self::alice_id() {
+            return Ok(Self::alice_account());
+        }
+        if id == Self::bob_id() {
+            return Ok(Self::bob_account());
+        }
+        Err("account not found")
+    }
+}
+
+impl actors::Trait for Test {
+    type Event = ();
+    type Members = MockMembers;
+}
+
+pub fn initial_test_ext() -> runtime_io::TestExternalities<Blake2Hasher> {
+    let t = system::GenesisConfig::<Test>::default()
+        .build_storage()
+        .unwrap()
+        .0;
+
+    runtime_io::TestExternalities::new(t)
+}
+
+pub type System = system::Module<Test>;
+pub type Balances = balances::Module<Test>;
+pub type Actors = actors::Module<Test>;

+ 6 - 0
src/roles/mod.rs

@@ -0,0 +1,6 @@
+#![cfg_attr(not(feature = "std"), no_std)]
+
+pub mod actors;
+
+mod mock;
+mod tests;

+ 191 - 0
src/roles/tests.rs

@@ -0,0 +1,191 @@
+#![cfg(test)]
+
+use super::mock::*;
+//use super::*;
+
+use runtime_io::with_externalities;
+use srml_support::*;
+
+fn init_storage_role() {
+    let roles: Vec<actors::Role> = vec![actors::Role::Storage];
+    assert!(Actors::set_available_roles(roles).is_ok(), "");
+}
+
+fn init_storage_parmeters() -> actors::RoleParameters<Test> {
+    let params = actors::RoleParameters {
+        // minium balance required to stake to enter a role
+        min_stake: 100 as u32,
+        min_actors: 1 as u32,
+        max_actors: 2 as u32,
+        reward: 100 as u32,
+        reward_period: 100 as u64,
+        bonding_period: 100 as u64,
+        unbonding_period: 100 as u64,
+        min_service_period: 100 as u64,
+        startup_grace_period: 100 as u64,
+        entry_request_fee: 10 as u32,
+    };
+    assert!(
+        Actors::set_role_parameters(actors::Role::Storage, params.clone()).is_ok(),
+        ""
+    );
+    params
+}
+
+#[test]
+fn adding_roles() {
+    with_externalities(&mut initial_test_ext(), || {
+        init_storage_role();
+        assert_eq!(Actors::available_roles(), vec![actors::Role::Storage]);
+    });
+}
+
+#[test]
+fn adding_role_parameters() {
+    with_externalities(&mut initial_test_ext(), || {
+        init_storage_role();
+        let params = init_storage_parmeters();
+        assert_eq!(Actors::parameters(actors::Role::Storage), Some(params));
+    });
+}
+
+#[test]
+fn make_entry_request() {
+    with_externalities(&mut initial_test_ext(), || {
+        init_storage_role();
+        let storage_params = init_storage_parmeters();
+
+        let actor_account = 5 as u64;
+
+        let starting_block = 1;
+        System::set_block_number(starting_block);
+
+        let requests = Actors::role_entry_requests();
+        assert_eq!(requests.len(), 0);
+
+        assert!(
+            Actors::role_entry_request(
+                Origin::signed(actor_account),
+                actors::Role::Storage,
+                MockMembers::alice_id()
+            )
+            .is_err(),
+            ""
+        );
+
+        let surplus_balance = 100;
+        Balances::set_free_balance(
+            &actor_account,
+            storage_params.entry_request_fee + surplus_balance,
+        );
+
+        assert!(
+            Actors::role_entry_request(
+                Origin::signed(actor_account),
+                actors::Role::Storage,
+                MockMembers::alice_id()
+            )
+            .is_ok(),
+            ""
+        );
+
+        assert_eq!(Balances::free_balance(&actor_account), surplus_balance);
+
+        let requests = Actors::role_entry_requests();
+        assert_eq!(requests.len(), 1);
+        let request = requests[0];
+        assert_eq!(request.0, actor_account);
+        assert_eq!(request.1, MockMembers::alice_id());
+        assert_eq!(request.2, actors::Role::Storage);
+        assert_eq!(request.3, starting_block + actors::REQUEST_LIFETIME);
+    });
+}
+
+#[test]
+fn staking() {
+    with_externalities(&mut initial_test_ext(), || {
+        init_storage_role();
+        let storage_params = init_storage_parmeters();
+        let actor_account = 5;
+
+        let request: actors::Request<Test> = (
+            actor_account,
+            MockMembers::alice_id(),
+            actors::Role::Storage,
+            1000,
+        );
+
+        <actors::RoleEntryRequests<Test>>::put(vec![request]);
+
+        Balances::set_free_balance(&actor_account, storage_params.min_stake);
+
+        assert!(Actors::stake(
+            Origin::signed(MockMembers::alice_account()),
+            actors::Role::Storage,
+            actor_account
+        )
+        .is_ok());
+
+        let ids = Actors::actor_account_ids();
+        assert_eq!(ids, vec![actor_account]);
+
+        let actor = Actors::actor_by_account_id(actor_account);
+        assert!(actor.is_some());
+
+        let accounts_in_role = Actors::account_ids_by_role(actors::Role::Storage);
+        assert_eq!(accounts_in_role, vec![actor_account]);
+
+        let account_ids_for_member = Actors::account_ids_by_member_id(MockMembers::alice_id());
+        assert_eq!(account_ids_for_member, vec![actor_account]);
+
+        assert!(<actors::Bondage<Test>>::exists(actor_account));
+    });
+}
+
+#[test]
+fn unstaking() {
+    with_externalities(&mut initial_test_ext(), || {
+        init_storage_role();
+        let storage_params = init_storage_parmeters();
+        let actor_account = 5;
+
+        assert!(
+            Actors::unstake(Origin::signed(MockMembers::alice_account()), actor_account).is_err()
+        );
+
+        let actor: actors::Actor<Test> = actors::Actor {
+            role: actors::Role::Storage,
+            member_id: MockMembers::alice_id(),
+            account: actor_account,
+            joined_at: 1,
+        };
+        <actors::ActorAccountIds<Test>>::put(vec![actor_account]);
+        <actors::ActorByAccountId<Test>>::insert(&actor_account, actor);
+        <actors::AccountIdsByRole<Test>>::insert(actors::Role::Storage, vec![actor_account]);
+        <actors::AccountIdsByMemberId<Test>>::insert(MockMembers::alice_id(), vec![actor_account]);
+        <actors::Bondage<Test>>::insert(&actor_account, 10000);
+        let current_block = 500;
+
+        System::set_block_number(current_block);
+        assert!(
+            Actors::unstake(Origin::signed(MockMembers::alice_account()), actor_account).is_ok()
+        );
+
+        assert_eq!(Actors::actor_account_ids().len(), 0);
+
+        let actor = Actors::actor_by_account_id(actor_account);
+        assert!(actor.is_none());
+
+        let accounts_in_role = Actors::account_ids_by_role(actors::Role::Storage);
+        assert_eq!(accounts_in_role.len(), 0);
+
+        let account_ids_for_member = Actors::account_ids_by_member_id(MockMembers::alice_id());
+        assert_eq!(account_ids_for_member.len(), 0);
+
+        assert!(<actors::Bondage<Test>>::exists(actor_account));
+        assert_eq!(
+            Actors::bondage(actor_account),
+            current_block + storage_params.unbonding_period
+        );
+    });
+}

+ 187 - 46
src/storage/data_directory.rs

@@ -1,21 +1,22 @@
 #![cfg_attr(not(feature = "std"), no_std)]
 
-use rstd::prelude::*;
+use crate::storage::data_object_type_registry::Trait as DOTRTrait;
+use crate::traits::{ContentIdExists, IsActiveDataObjectType, Members};
 use parity_codec::Codec;
-use parity_codec_derive::{Encode, Decode};
-use srml_support::{StorageMap, decl_module, decl_storage, decl_event, ensure, Parameter, dispatch};
-use runtime_primitives::traits::{Member, MaybeSerializeDebug, MaybeDebug};
+use parity_codec_derive::{Decode, Encode};
+use rstd::prelude::*;
+use runtime_primitives::traits::{MaybeDebug, MaybeSerializeDebug, Member};
+use srml_support::{
+    decl_event, decl_module, decl_storage, dispatch, ensure, Parameter, StorageMap,
+};
 use system::{self, ensure_signed};
-use crate::traits::{IsActiveMember, IsActiveDataObjectType, ContentIdExists};
-use crate::storage::data_object_type_registry::Trait as DOTRTrait;
 
 pub trait Trait: timestamp::Trait + system::Trait + DOTRTrait + MaybeDebug {
     type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
 
-    type ContentId: Parameter + Member + Codec + Default + Copy
-        + MaybeSerializeDebug + PartialEq;
+    type ContentId: Parameter + Member + Codec + Default + Clone + MaybeSerializeDebug + PartialEq;
 
-    type IsActiveMember: IsActiveMember<Self>;
+    type Members: Members<Self>;
     type IsActiveDataObjectType: IsActiveDataObjectType<Self>;
 }
 
@@ -23,12 +24,12 @@ static MSG_DUPLICATE_CID: &str = "Content with this ID already exists!";
 static MSG_CID_NOT_FOUND: &str = "Content with this ID not found!";
 static MSG_LIAISON_REQUIRED: &str = "Only the liaison for the content may modify its status!";
 static MSG_CREATOR_MUST_BE_MEMBER: &str = "Only active members may create content!";
-static MSG_DO_TYPE_MUST_BE_ACTIVE: &str = "Cannot create content for inactive of missing data object type!";
+static MSG_DO_TYPE_MUST_BE_ACTIVE: &str =
+    "Cannot create content for inactive or missing data object type!";
 
 #[derive(Clone, Encode, Decode, PartialEq)]
 #[cfg_attr(feature = "std", derive(Debug))]
-pub enum LiaisonJudgement
-{
+pub enum LiaisonJudgement {
     Pending,
     Rejected,
     Accepted,
@@ -47,7 +48,7 @@ pub struct DataObject<T: Trait> {
     pub size: u64,
     pub added_at_block: T::BlockNumber,
     pub added_at_time: T::Moment,
-    pub origin: T::AccountId,
+    pub owner: T::AccountId,
     pub liaison: T::AccountId,
     pub liaison_judgement: LiaisonJudgement,
 }
@@ -55,7 +56,7 @@ pub struct DataObject<T: Trait> {
 decl_storage! {
     trait Store for Module<T: Trait> as DataDirectory {
         // Mapping of Content ID to Data Object
-        pub ContentMap get(content): map T::ContentId => Option<DataObject<T>>;
+        pub Contents get(contents): map T::ContentId => Option<DataObject<T>>;
     }
 }
 
@@ -64,19 +65,22 @@ decl_event! {
         <T as Trait>::ContentId,
         <T as system::Trait>::AccountId
     {
+        // The account is the Liaison that was selected
         ContentAdded(ContentId, AccountId),
-        ContentAccepted(ContentId),
-        ContentRejected(ContentId),
+
+        // The account is the liaison again - only they can reject or accept
+        ContentAccepted(ContentId, AccountId),
+        ContentRejected(ContentId, AccountId),
     }
 }
 
 impl<T: Trait> ContentIdExists<T> for Module<T> {
     fn has_content(which: &T::ContentId) -> bool {
-        Self::content(*which).is_some()
+        Self::contents(which.clone()).is_some()
     }
 
     fn get_data_object(which: &T::ContentId) -> Result<DataObject<T>, &'static str> {
-        match Self::content(*which) {
+        match Self::contents(which.clone()) {
             None => Err(MSG_CID_NOT_FOUND),
             Some(data) => Ok(data),
         }
@@ -90,19 +94,15 @@ decl_module! {
         pub fn add_content(origin, data_object_type_id: <T as DOTRTrait>::DataObjectTypeId,
                            id: T::ContentId, size: u64) {
             // Origin has to be a member
-            let who = ensure_signed(origin).clone().unwrap();
-            if !T::IsActiveMember::is_active_member(&who) {
-                return Err(MSG_CREATOR_MUST_BE_MEMBER);
-            }
+            let who = ensure_signed(origin)?;
+            ensure!(T::Members::is_active_member(&who), MSG_CREATOR_MUST_BE_MEMBER);
 
             // Data object type has to be active
-            if !T::IsActiveDataObjectType::is_active_data_object_type(&data_object_type_id) {
-                return Err(MSG_DO_TYPE_MUST_BE_ACTIVE);
-            }
+            ensure!(T::IsActiveDataObjectType::is_active_data_object_type(&data_object_type_id), MSG_DO_TYPE_MUST_BE_ACTIVE);
 
             // We essentially accept the content ID and size at face value. All we
             // need to know is that it doesn't yet exist.
-            let found = Self::content(&id);
+            let found = Self::contents(&id);
             ensure!(found.is_none(), MSG_DUPLICATE_CID);
 
             // The liaison is something we need to take from staked roles. The idea
@@ -117,51 +117,192 @@ decl_module! {
                 size: size,
                 added_at_block: <system::Module<T>>::block_number(),
                 added_at_time: <timestamp::Module<T>>::now(),
-                origin: who,
+                owner: who,
                 liaison: liaison.clone(),
                 liaison_judgement: LiaisonJudgement::Pending,
             };
 
             // If we've constructed the data, we can store it and send an event.
-            <ContentMap<T>>::insert(id, data);
+            <Contents<T>>::insert(id.clone(), data);
             Self::deposit_event(RawEvent::ContentAdded(id, liaison));
         }
 
         // The LiaisonJudgement can be updated, but only by the liaison.
-        fn accept_content(origin, id: T::ContentId)
-        {
-            Self::update_content_judgement(origin, id, LiaisonJudgement::Accepted)?;
-            Self::deposit_event(RawEvent::ContentAccepted(id));
+        fn accept_content(origin, id: T::ContentId) {
+            let who = ensure_signed(origin)?;
+            Self::update_content_judgement(&who, id.clone(), LiaisonJudgement::Accepted)?;
+            Self::deposit_event(RawEvent::ContentAccepted(id, who));
         }
 
-        fn reject_content(origin, id: T::ContentId)
-        {
-            Self::update_content_judgement(origin, id, LiaisonJudgement::Rejected)?;
-            Self::deposit_event(RawEvent::ContentAccepted(id));
+        fn reject_content(origin, id: T::ContentId) {
+            let who = ensure_signed(origin)?;
+            Self::update_content_judgement(&who, id.clone(), LiaisonJudgement::Rejected)?;
+            Self::deposit_event(RawEvent::ContentRejected(id, who));
         }
     }
 }
 
-impl <T: Trait> Module<T> {
-    fn update_content_judgement(origin: T::Origin, id: T::ContentId, judgement: LiaisonJudgement) -> dispatch::Result
-    {
-        let who = ensure_signed(origin);
-
+impl<T: Trait> Module<T> {
+    fn update_content_judgement(
+        who: &T::AccountId,
+        id: T::ContentId,
+        judgement: LiaisonJudgement,
+    ) -> dispatch::Result {
         // Find the data
-        let found = Self::content(&id).ok_or(MSG_CID_NOT_FOUND);
+        let found = Self::contents(&id).ok_or(MSG_CID_NOT_FOUND);
 
         // Make sure the liaison matches
         let mut data = found.unwrap();
-        if data.liaison != who.unwrap() {
-            return Err(MSG_LIAISON_REQUIRED);
-        }
+        ensure!(data.liaison == *who, MSG_LIAISON_REQUIRED);
 
         // At this point we can update the data.
         data.liaison_judgement = judgement;
 
         // Update and send event.
-        <ContentMap<T>>::insert(id, data);
+        <Contents<T>>::insert(id, data);
 
         Ok(())
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::storage::mock::*;
+
+    use system::{self, EventRecord, Phase};
+
+    #[test]
+    fn succeed_adding_content() {
+        with_default_mock_builder(|| {
+            // Register a content name "foo" with 1234 bytes of type 1, which should be recognized.
+            let res = TestDataDirectory::add_content(
+                Origin::signed(1),
+                1,
+                "foo".as_bytes().to_vec(),
+                1234,
+            );
+            assert!(res.is_ok());
+
+            // Register the same under a different name
+            let res = TestDataDirectory::add_content(
+                Origin::signed(1),
+                1,
+                "bar".as_bytes().to_vec(),
+                1234,
+            );
+            assert!(res.is_ok());
+        });
+    }
+
+    #[test]
+    fn fail_adding_content_twice() {
+        with_default_mock_builder(|| {
+            // Register a content name "foo" with 1234 bytes of type 1, which should be recognized.
+            let res = TestDataDirectory::add_content(
+                Origin::signed(1),
+                1,
+                "foo".as_bytes().to_vec(),
+                1234,
+            );
+            assert!(res.is_ok());
+
+            // The second time must fail
+            let res = TestDataDirectory::add_content(
+                Origin::signed(1),
+                1,
+                "foo".as_bytes().to_vec(),
+                1234,
+            );
+            assert!(res.is_err());
+
+            // Also from a different origin must fail
+            let res = TestDataDirectory::add_content(
+                Origin::signed(2),
+                1,
+                "foo".as_bytes().to_vec(),
+                1234,
+            );
+            assert!(res.is_err());
+
+            // Also with a different size must fail
+            let res = TestDataDirectory::add_content(
+                Origin::signed(2),
+                1,
+                "foo".as_bytes().to_vec(),
+                4321,
+            );
+            assert!(res.is_err());
+        });
+    }
+
+    #[test]
+    fn accept_content_as_liaison() {
+        with_default_mock_builder(|| {
+            let res = TestDataDirectory::add_content(
+                Origin::signed(1),
+                1,
+                "foo".as_bytes().to_vec(),
+                1234,
+            );
+            assert!(res.is_ok());
+
+            // An appropriate event should have been fired.
+            let liaison = *match &System::events().last().unwrap().event {
+                MetaEvent::data_directory(data_directory::RawEvent::ContentAdded(
+                    _content_id,
+                    liaison,
+                )) => liaison,
+                _ => &0xdeadbeefu64, // invalid value, unlikely to match
+            };
+            assert_ne!(liaison, 0xdeadbeefu64);
+
+            // Accepting content should not work with some random origin
+            let res =
+                TestDataDirectory::accept_content(Origin::signed(42), "foo".as_bytes().to_vec());
+            assert!(res.is_err());
+
+            // However, with the liaison as origin it should.
+            let res = TestDataDirectory::accept_content(
+                Origin::signed(liaison),
+                "foo".as_bytes().to_vec(),
+            );
+            assert!(res.is_ok());
+        });
+    }
+
+    #[test]
+    fn reject_content_as_liaison() {
+        with_default_mock_builder(|| {
+            let res = TestDataDirectory::add_content(
+                Origin::signed(1),
+                1,
+                "foo".as_bytes().to_vec(),
+                1234,
+            );
+            assert!(res.is_ok());
+
+            // An appropriate event should have been fired.
+            let liaison = *match &System::events().last().unwrap().event {
+                MetaEvent::data_directory(data_directory::RawEvent::ContentAdded(
+                    _content_id,
+                    liaison,
+                )) => liaison,
+                _ => &0xdeadbeefu64, // invalid value, unlikely to match
+            };
+            assert_ne!(liaison, 0xdeadbeefu64);
+
+            // Rejecting content should not work with some random origin
+            let res =
+                TestDataDirectory::reject_content(Origin::signed(42), "foo".as_bytes().to_vec());
+            assert!(res.is_err());
+
+            // However, with the liaison as origin it should.
+            let res = TestDataDirectory::reject_content(
+                Origin::signed(liaison),
+                "foo".as_bytes().to_vec(),
+            );
+            assert!(res.is_ok());
+        });
+    }
+}

+ 60 - 45
src/storage/data_object_storage_registry.rs

@@ -1,29 +1,40 @@
 #![cfg_attr(not(feature = "std"), no_std)]
 
-use rstd::prelude::*;
+use crate::storage::data_directory::Trait as DDTrait;
+use crate::traits::{ContentHasStorage, ContentIdExists, Members};
 use parity_codec::Codec;
-use parity_codec_derive::{Encode, Decode};
-use srml_support::{StorageMap, StorageValue, decl_module, decl_storage, decl_event, Parameter, dispatch};
-use runtime_primitives::traits::{SimpleArithmetic, As, Member, MaybeSerializeDebug, MaybeDebug};
+use parity_codec_derive::{Decode, Encode};
+use rstd::prelude::*;
+use runtime_primitives::traits::{As, MaybeDebug, MaybeSerializeDebug, Member, SimpleArithmetic};
+use srml_support::{
+    decl_event, decl_module, decl_storage, ensure, Parameter, StorageMap, StorageValue,
+};
 use system::{self, ensure_signed};
-use crate::traits::{IsActiveMember, ContentIdExists, ContentHasStorage};
-use crate::storage::data_directory::Trait as DDTrait;
 
 pub trait Trait: timestamp::Trait + system::Trait + DDTrait + MaybeDebug {
     type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
 
-    type DataObjectStorageRelationshipId: Parameter + Member + SimpleArithmetic + Codec + Default + Copy
-        + As<usize> + As<u64> + MaybeSerializeDebug + PartialEq;
-
-    type IsActiveMember: IsActiveMember<Self>;
+    type DataObjectStorageRelationshipId: Parameter
+        + Member
+        + SimpleArithmetic
+        + Codec
+        + Default
+        + Copy
+        + As<usize>
+        + As<u64>
+        + MaybeSerializeDebug
+        + PartialEq;
+
+    type Members: Members<Self>;
     type ContentIdExists: ContentIdExists<Self>;
 }
 
 static MSG_CID_NOT_FOUND: &str = "Content with this ID not found!";
 static MSG_DOSR_NOT_FOUND: &str = "No data object storage relationship found for this ID.";
-static MSG_ONLY_STORAGE_PROVIDER_MAY_CLAIM_READY: &str = "Only the storage provider in a DOSR can decide whether they're ready.";
+static MSG_ONLY_STORAGE_PROVIDER_MAY_CLAIM_READY: &str =
+    "Only the storage provider in a DOSR can decide whether they're ready.";
 
-const DEFAULT_FIRST_DATA_OBJECT_STORAGE_RELATIONSHIP_ID: u64 = 1;
+const DEFAULT_FIRST_RELATIONSHIP_ID: u64 = 1;
 
 #[derive(Clone, Encode, Decode, PartialEq)]
 #[cfg_attr(feature = "std", derive(Debug))]
@@ -36,16 +47,16 @@ pub struct DataObjectStorageRelationship<T: Trait> {
 decl_storage! {
     trait Store for Module<T: Trait> as DataObjectStorageRegistry {
         // Start at this value
-        pub FirstDataObjectStorageRelationshipId get(first_data_object_storage_relationship_id) config(first_data_object_storage_relationship_id): T::DataObjectStorageRelationshipId = T::DataObjectStorageRelationshipId::sa(DEFAULT_FIRST_DATA_OBJECT_STORAGE_RELATIONSHIP_ID);
+        pub FirstRelationshipId get(first_relationship_id) config(first_relationship_id): T::DataObjectStorageRelationshipId = T::DataObjectStorageRelationshipId::sa(DEFAULT_FIRST_RELATIONSHIP_ID);
 
         // Increment
-        pub NextDataObjectStorageRelationshipId get(next_data_object_storage_relationship_id) build(|config: &GenesisConfig<T>| config.first_data_object_storage_relationship_id): T::DataObjectStorageRelationshipId = T::DataObjectStorageRelationshipId::sa(DEFAULT_FIRST_DATA_OBJECT_STORAGE_RELATIONSHIP_ID);
+        pub NextRelationshipId get(next_relationship_id) build(|config: &GenesisConfig<T>| config.first_relationship_id): T::DataObjectStorageRelationshipId = T::DataObjectStorageRelationshipId::sa(DEFAULT_FIRST_RELATIONSHIP_ID);
 
         // Mapping of Data object types
-        pub DataObjectStorageRelationshipMap get(data_object_storage_relationship): map T::DataObjectStorageRelationshipId => Option<DataObjectStorageRelationship<T>>;
+        pub Relationships get(relationships): map T::DataObjectStorageRelationshipId => Option<DataObjectStorageRelationship<T>>;
 
         // Keep a list of storage relationships per CID
-        pub DataObjectStorageRelationships get(data_object_storage_relationships): map T::ContentId => Vec<T::DataObjectStorageRelationshipId>;
+        pub RelationshipsByContentId get(relationships_by_content_id): map T::ContentId => Vec<T::DataObjectStorageRelationshipId>;
     }
 }
 
@@ -62,88 +73,92 @@ decl_event! {
 
 impl<T: Trait> ContentHasStorage<T> for Module<T> {
     fn has_storage_provider(which: &T::ContentId) -> bool {
-        let dosr_list = Self::data_object_storage_relationships(which);
-        return dosr_list.iter().any(|&dosr_id| Self::data_object_storage_relationship(dosr_id).unwrap().ready);
+        let dosr_list = Self::relationships_by_content_id(which);
+        return dosr_list
+            .iter()
+            .any(|&dosr_id| Self::relationships(dosr_id).unwrap().ready);
     }
 
     fn is_ready_at_storage_provider(which: &T::ContentId, provider: &T::AccountId) -> bool {
-        let dosr_list = Self::data_object_storage_relationships(which);
+        let dosr_list = Self::relationships_by_content_id(which);
         return dosr_list.iter().any(|&dosr_id| {
-            let dosr = Self::data_object_storage_relationship(dosr_id).unwrap();
+            let dosr = Self::relationships(dosr_id).unwrap();
             dosr.storage_provider == *provider && dosr.ready
         });
     }
 }
 
-
-
 decl_module! {
     pub struct Module<T: Trait> for enum Call where origin: T::Origin {
         fn deposit_event<T>() = default;
 
-        pub fn add_data_object_storage_relationship(origin, cid: T::ContentId) {
+        pub fn add_relationship(origin, cid: T::ContentId) {
             // Origin has to be a storage provider
-            let who = ensure_signed(origin).clone().unwrap();
+            let who = ensure_signed(origin)?;
             // TODO check for being staked as a storage provider
-            // if !T::IsActiveMember::is_active_member(&who) {
+            // if !T::Members::is_active_member(&who) {
             //     return Err(MSG_CREATOR_MUST_BE_MEMBER);
             // }
 
             // Content ID must exist
-            if !T::ContentIdExists::has_content(&cid) {
-                return Err(MSG_CID_NOT_FOUND);
-            }
+            ensure!(T::ContentIdExists::has_content(&cid), MSG_CID_NOT_FOUND);
 
             // Create new ID, data.
-            let new_id = Self::next_data_object_storage_relationship_id();
+            let new_id = Self::next_relationship_id();
             let dosr: DataObjectStorageRelationship<T> = DataObjectStorageRelationship {
                 content_id: cid.clone(),
                 storage_provider: who.clone(),
                 ready: false,
             };
 
-            <DataObjectStorageRelationshipMap<T>>::insert(new_id, dosr);
-            <NextDataObjectStorageRelationshipId<T>>::mutate(|n| { *n += T::DataObjectStorageRelationshipId::sa(1); });
+            <Relationships<T>>::insert(new_id, dosr);
+            <NextRelationshipId<T>>::mutate(|n| { *n += T::DataObjectStorageRelationshipId::sa(1); });
 
             // Also add the DOSR to the list of DOSRs for the CID. Uniqueness is guaranteed
             // by the map, so we can just append the new_id to the list.
-            let mut dosr_list = Self::data_object_storage_relationships(cid);
+            let mut dosr_list = Self::relationships_by_content_id(cid.clone());
             dosr_list.push(new_id);
+            <RelationshipsByContentId<T>>::insert(cid.clone(), dosr_list);
 
             // Emit event
             Self::deposit_event(RawEvent::DataObjectStorageRelationshipAdded(new_id, cid, who));
         }
 
         // A storage provider may flip their own ready state, but nobody else.
-        pub fn set_data_object_storage_relationship_ready(origin, id: T::DataObjectStorageRelationshipId) {
+        pub fn set_relationship_ready(origin, id: T::DataObjectStorageRelationshipId) {
             Self::toggle_dosr_ready(origin, id, true)?;
         }
 
-        pub fn unset_data_object_storage_relationship_ready(origin, id: T::DataObjectStorageRelationshipId) {
+        pub fn unset_relationship_ready(origin, id: T::DataObjectStorageRelationshipId) {
             Self::toggle_dosr_ready(origin, id, false)?;
         }
     }
 }
 
-impl <T: Trait> Module<T> {
-    fn toggle_dosr_ready(origin: T::Origin, id: T::DataObjectStorageRelationshipId, ready: bool) -> dispatch::Result
-    {
+impl<T: Trait> Module<T> {
+    fn toggle_dosr_ready(
+        origin: T::Origin,
+        id: T::DataObjectStorageRelationshipId,
+        ready: bool,
+    ) -> Result<(), &'static str> {
         // Origin has to be the storage provider mentioned in the DOSR
-        let who = ensure_signed(origin).clone().unwrap();
+        let who = ensure_signed(origin)?;
 
         // For that, we need to fetch the identified DOSR
-        let found = Self::data_object_storage_relationship(id).ok_or(MSG_DOSR_NOT_FOUND);
-        let mut dosr = found.unwrap();
-        if dosr.storage_provider != who {
-            return Err(MSG_ONLY_STORAGE_PROVIDER_MAY_CLAIM_READY);
-        }
+        let mut dosr = Self::relationships(id).ok_or(MSG_DOSR_NOT_FOUND)?;
+        ensure!(
+            dosr.storage_provider == who,
+            MSG_ONLY_STORAGE_PROVIDER_MAY_CLAIM_READY
+        );
 
         // Flip to ready
         dosr.ready = ready;
 
         // Update DOSR and fire event.
-        <DataObjectStorageRelationshipMap<T>>::insert(id, dosr);
-        Self::deposit_event(RawEvent::DataObjectStorageRelationshipReadyUpdated(id, true));
+        <Relationships<T>>::insert(id, dosr);
+        Self::deposit_event(RawEvent::DataObjectStorageRelationshipReadyUpdated(
+            id, true,
+        ));
 
         Ok(())
     }

+ 195 - 16
src/storage/data_object_type_registry.rs

@@ -1,24 +1,35 @@
 #![cfg_attr(not(feature = "std"), no_std)]
 
-use rstd::prelude::*;
+use crate::traits;
 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 parity_codec_derive::{Decode, Encode};
+use rstd::prelude::*;
+use runtime_primitives::traits::{As, MaybeDebug, MaybeSerializeDebug, Member, SimpleArithmetic};
+use srml_support::{
+    decl_event, decl_module, decl_storage, ensure, Parameter, StorageMap, StorageValue,
+};
 use system::{self, ensure_root};
-use crate::traits;
 
 pub trait Trait: system::Trait + MaybeDebug {
     type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
 
-    type DataObjectTypeId: Parameter + Member + SimpleArithmetic + Codec + Default + Copy
-        + As<usize> + As<u64> + MaybeSerializeDebug + PartialEq;
+    type DataObjectTypeId: Parameter
+        + Member
+        + SimpleArithmetic
+        + Codec
+        + Default
+        + Copy
+        + As<usize>
+        + As<u64>
+        + 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_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)!";
+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;
 
@@ -28,7 +39,6 @@ pub struct DataObjectType<T: Trait> {
     pub id: Option<T::DataObjectTypeId>,
     pub description: Vec<u8>,
     pub active: bool,
-
     // TODO in future releases
     // - maximum size
     // - replication factor
@@ -56,18 +66,15 @@ decl_event! {
     }
 }
 
-
-
 impl<T: Trait> traits::IsActiveDataObjectType<T> for Module<T> {
     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
+            Err(_err) => false,
         }
     }
 }
 
-
 decl_module! {
     pub struct Module<T: Trait> for enum Call where origin: T::Origin {
         fn deposit_event<T>() = default;
@@ -132,8 +139,180 @@ decl_module! {
     }
 }
 
-impl <T: Trait> Module<T> {
+impl<T: Trait> Module<T> {
     fn ensure_data_object_type(id: T::DataObjectTypeId) -> Result<DataObjectType<T>, &'static str> {
         return Self::data_object_type(&id).ok_or(MSG_DO_TYPE_NOT_FOUND);
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::storage::mock::*;
+
+    use system::{self, EventRecord, Phase};
+
+    #[test]
+    fn initial_state() {
+        with_default_mock_builder(|| {
+            assert_eq!(
+                TestDataObjectTypeRegistry::first_data_object_type_id(),
+                TEST_FIRST_DATA_OBJECT_TYPE_ID
+            );
+        });
+    }
+
+    #[test]
+    fn fail_register_without_root() {
+        with_default_mock_builder(|| {
+            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() {
+        with_default_mock_builder(|| {
+            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() {
+        with_default_mock_builder(|| {
+            // 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::DataObjectTypeRegistered(
+                            TEST_FIRST_DATA_OBJECT_TYPE_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(TEST_FIRST_DATA_OBJECT_TYPE_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(TEST_FIRST_DATA_OBJECT_TYPE_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(
+                            TEST_FIRST_DATA_OBJECT_TYPE_ID
+                        )
+                    ),
+                }
+            );
+        });
+    }
+
+    #[test]
+    fn activate_existing() {
+        with_default_mock_builder(|| {
+            // 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::DataObjectTypeRegistered(
+                            TEST_FIRST_DATA_OBJECT_TYPE_ID
+                        )
+                    ),
+                }
+            );
+
+            // Retrieve, and ensure it's not active.
+            let data = TestDataObjectTypeRegistry::data_object_type(TEST_FIRST_DATA_OBJECT_TYPE_ID);
+            assert!(data.is_some());
+            assert!(!data.unwrap().active);
+
+            // Now activate the data object type
+            let res = TestDataObjectTypeRegistry::activate_data_object_type(
+                Origin::ROOT,
+                TEST_FIRST_DATA_OBJECT_TYPE_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(
+                            TEST_FIRST_DATA_OBJECT_TYPE_ID
+                        )
+                    ),
+                }
+            );
+
+            // Ensure that the item is actually activated.
+            let data = TestDataObjectTypeRegistry::data_object_type(TEST_FIRST_DATA_OBJECT_TYPE_ID);
+            assert!(data.is_some());
+            assert!(data.unwrap().active);
+
+            // Deactivate again.
+            let res = TestDataObjectTypeRegistry::deactivate_data_object_type(
+                Origin::ROOT,
+                TEST_FIRST_DATA_OBJECT_TYPE_ID,
+            );
+            assert!(res.is_ok());
+            let data = TestDataObjectTypeRegistry::data_object_type(TEST_FIRST_DATA_OBJECT_TYPE_ID);
+            assert!(data.is_some());
+            assert!(!data.unwrap().active);
+        });
+    }
+}

+ 46 - 45
src/storage/downloads.rs

@@ -1,20 +1,30 @@
 #![cfg_attr(not(feature = "std"), no_std)]
 
-use rstd::prelude::*;
+use crate::storage::data_directory::Trait as DDTrait;
+use crate::storage::data_object_storage_registry::Trait as DOSRTrait;
+use crate::traits::{ContentHasStorage, ContentIdExists};
 use parity_codec::Codec;
-use parity_codec_derive::{Encode, Decode};
-use srml_support::{StorageMap, StorageValue, decl_module, decl_storage, decl_event, Parameter};
-use runtime_primitives::traits::{SimpleArithmetic, As, Member, MaybeSerializeDebug};
+use parity_codec_derive::{Decode, Encode};
+use rstd::prelude::*;
+use runtime_primitives::traits::{As, MaybeSerializeDebug, Member, SimpleArithmetic};
+use srml_support::{
+    decl_event, decl_module, decl_storage, ensure, Parameter, StorageMap, StorageValue,
+};
 use system::{self, ensure_signed};
-use crate::traits::{ContentIdExists, ContentHasStorage};
-use crate::storage::data_object_storage_registry::Trait as DOSRTrait;
-use crate::storage::data_directory::Trait as DDTrait;
 
 pub trait Trait: timestamp::Trait + system::Trait + DOSRTrait + DDTrait {
     type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
 
-    type DownloadSessionId: Parameter + Member + SimpleArithmetic + Codec + Default + Copy
-        + As<usize> + As<u64> + MaybeSerializeDebug + PartialEq;
+    type DownloadSessionId: Parameter
+        + Member
+        + SimpleArithmetic
+        + Codec
+        + Default
+        + Copy
+        + As<usize>
+        + As<u64>
+        + MaybeSerializeDebug
+        + PartialEq;
 
     type ContentHasStorage: ContentHasStorage<Self>;
 }
@@ -23,13 +33,14 @@ static MSG_SESSION_NOT_FOUND: &str = "Download session with the given ID not fou
 static MSG_SESSION_HAS_ENDED: &str = "Download session with the given ID has already ended.";
 static MSG_CONSUMER_REQUIRED: &str = "Download session can only be modified by the downloader";
 static MSG_INVALID_TRANSMITTED_VALUE: &str = "Invalid update to transmitted bytes value";
+static MSG_NEED_STORAGE_PROVIDER: &str =
+    "Cannnot download without at least one active storage relationship!";
 
 const DEFAULT_FIRST_DOWNLOAD_SESSION_ID: u64 = 1;
 
 #[derive(Clone, Encode, Decode, PartialEq)]
 #[cfg_attr(feature = "std", derive(Debug))]
-pub enum DownloadState
-{
+pub enum DownloadState {
     Started,
     Ended,
 }
@@ -40,17 +51,16 @@ impl Default for DownloadState {
     }
 }
 
-
 #[derive(Clone, Encode, Decode, PartialEq)]
 #[cfg_attr(feature = "std", derive(Debug))]
 pub struct DownloadSession<T: Trait> {
-    pub cid: <T as DDTrait>::ContentId,
+    pub content_id: <T as DDTrait>::ContentId,
     pub consumer: T::AccountId,
     pub distributor: T::AccountId,
     pub initiated_at_block: T::BlockNumber,
     pub initiated_at_time: T::Moment,
     pub state: DownloadState,
-    pub transmitted: u64,
+    pub transmitted_bytes: u64,
 }
 
 decl_storage! {
@@ -62,7 +72,7 @@ decl_storage! {
         pub NextDownloadSessionId get(next_download_session_id) build(|config: &GenesisConfig<T>| config.first_download_session_id): T::DownloadSessionId = T::DownloadSessionId::sa(DEFAULT_FIRST_DOWNLOAD_SESSION_ID);
 
         // Mapping of Data object types
-        pub DownloadSessionMap get(download_session): map T::DownloadSessionId => Option<DownloadSession<T>>;
+        pub DownloadSessions get(download_sessions): map T::DownloadSessionId => Option<DownloadSession<T>>;
     }
 }
 
@@ -86,81 +96,72 @@ decl_module! {
 
         // Origin starts a download from distributor. It's the origin's responsibility to
         // start this, and hand the session ID to the distributor as proof they did.
-        pub fn start_download(origin, cid: <T as DDTrait>::ContentId, from: T::AccountId) {
+        pub fn start_download(origin, content_id: <T as DDTrait>::ContentId, from: T::AccountId) {
             // Origin can be anyone, it doesn't have to be a member.
-            let who = ensure_signed(origin).clone().unwrap();
+            let who = ensure_signed(origin)?;
 
             // There has to be a storage relationship for the content ID and storage provider.
-            if !T::ContentHasStorage::is_ready_at_storage_provider(&cid, &from) {
-                return Err("NOPETYNOPE");
-            }
+            ensure!(T::ContentHasStorage::is_ready_at_storage_provider(&content_id, &from), MSG_NEED_STORAGE_PROVIDER);
 
             // Let's create the entry then
             let new_id = Self::next_download_session_id();
             let session: DownloadSession<T> = DownloadSession {
-                cid: cid,
+                content_id: content_id.clone(),
                 consumer: who,
                 distributor: from.clone(),
                 initiated_at_block: <system::Module<T>>::block_number(),
                 initiated_at_time: <timestamp::Module<T>>::now(),
                 state: DownloadState::Started,
-                transmitted: 0u64,
+                transmitted_bytes: 0u64,
             };
 
-            <DownloadSessionMap<T>>::insert(new_id, session);
+            <DownloadSessions<T>>::insert(new_id, session);
             <NextDownloadSessionId<T>>::mutate(|n| { *n += T::DownloadSessionId::sa(1); });
 
             // Fire off event
-            Self::deposit_event(RawEvent::DownloadStarted(cid));
+            Self::deposit_event(RawEvent::DownloadStarted(content_id));
         }
 
         // The downloader can also update the transmitted size, as long as it's
         // strictly larger.
-        pub fn update_transmitted(origin, session_id: T::DownloadSessionId, transmitted: u64)
+        pub fn update_transmitted(origin, session_id: T::DownloadSessionId, transmitted_bytes: u64)
         {
             // Origin can be anyone, it doesn't have to be a member.
-            let who = ensure_signed(origin).clone().unwrap();
+            let who = ensure_signed(origin)?;
 
             // Get session
-            let found = Self::download_session(session_id).ok_or(MSG_SESSION_NOT_FOUND);
+            let found = Self::download_sessions(session_id).ok_or(MSG_SESSION_NOT_FOUND);
             let mut session = found.unwrap();
 
             // Ensure that the session hasn't ended yet.
-            if session.state != DownloadState::Started {
-                return Err(MSG_SESSION_HAS_ENDED);
-            }
+            ensure!(session.state == DownloadState::Started, MSG_SESSION_HAS_ENDED);
 
             // Ensure that the origin is the consumer
-            if session.consumer != who {
-                return Err(MSG_CONSUMER_REQUIRED);
-            }
+            ensure!(session.consumer == who, MSG_CONSUMER_REQUIRED);
 
             // Ensure that the new transmitted size is larger than the old one
-            if transmitted <= session.transmitted {
-                return Err(MSG_INVALID_TRANSMITTED_VALUE);
-            }
+            ensure!(transmitted_bytes > session.transmitted_bytes, MSG_INVALID_TRANSMITTED_VALUE);
 
             // By fetching the content information, we can ensure that the transmitted
             // field also does not exceed the content size. Furthermore, we can
             // automatically detect when the download ended.
-            let data_object = T::ContentIdExists::get_data_object(&session.cid)?;
-            if transmitted > data_object.size {
-                return Err(MSG_INVALID_TRANSMITTED_VALUE);
-            }
-            let finished = transmitted == data_object.size;
+            let data_object = T::ContentIdExists::get_data_object(&session.content_id)?;
+            ensure!(transmitted_bytes <= data_object.size, MSG_INVALID_TRANSMITTED_VALUE);
+
+            let finished = transmitted_bytes == data_object.size;
 
             // Ok we can update the data.
-            session.transmitted = transmitted;
+            session.transmitted_bytes = transmitted_bytes;
             session.state = match finished {
                 true => DownloadState::Ended,
                 false => DownloadState::Started,
             };
-            let cid = session.cid.clone();
-            <DownloadSessionMap<T>>::insert(session_id, session);
+            let content_id = session.content_id.clone();
+            <DownloadSessions<T>>::insert(session_id, session);
 
             // Also announce if the download finished
             if finished {
-                Self::deposit_event(RawEvent::DownloadEnded(cid, transmitted));
+                Self::deposit_event(RawEvent::DownloadEnded(content_id, transmitted_bytes));
             }
         }
     }

+ 78 - 10
src/storage/mock.rs

@@ -1,16 +1,19 @@
 #![cfg(test)]
 
-pub use super::{data_object_type_registry};
+pub use super::{data_directory, data_object_type_registry};
+use crate::traits;
+use rstd::prelude::*;
+use runtime_io::with_externalities;
 pub use system;
 
-pub use primitives::{H256, Blake2Hasher};
+pub use primitives::{Blake2Hasher, H256};
 pub use runtime_primitives::{
+    testing::{Digest, DigestItem, Header, UintAuthorityId},
+    traits::{BlakeTwo256, IdentityLookup, OnFinalise},
     BuildStorage,
-    traits::{BlakeTwo256, OnFinalise, IdentityLookup},
-    testing::{Digest, DigestItem, Header, UintAuthorityId}
 };
 
-use srml_support::{impl_outer_origin, impl_outer_event};
+use srml_support::{impl_outer_event, impl_outer_origin};
 
 impl_outer_origin! {
     pub enum Origin for Test {}
@@ -19,6 +22,33 @@ impl_outer_origin! {
 impl_outer_event! {
     pub enum MetaEvent for Test {
         data_object_type_registry<T>,
+        data_directory<T>,
+    }
+}
+
+pub struct AnyAccountIsMember {}
+impl<T: system::Trait> traits::Members<T> for AnyAccountIsMember {
+    type Id = u64;
+
+    fn is_active_member(_who: &T::AccountId) -> bool {
+        true
+    }
+
+    fn lookup_member_id(_account_id: &T::AccountId) -> Result<Self::Id, &'static str> {
+        Err("not implemented for tests")
+    }
+
+    fn lookup_account_by_member_id(_member_id: Self::Id) -> Result<T::AccountId, &'static str> {
+        Err("not implemented for tests")
+    }
+}
+
+pub struct AnyDataObjectTypeIsActive {}
+impl<T: data_object_type_registry::Trait> traits::IsActiveDataObjectType<T>
+    for AnyDataObjectTypeIsActive
+{
+    fn is_active_data_object_type(_which: &T::DataObjectTypeId) -> bool {
+        true
     }
 }
 
@@ -40,11 +70,30 @@ impl system::Trait for Test {
     type Log = DigestItem;
     type Lookup = IdentityLookup<u64>;
 }
+
 impl data_object_type_registry::Trait for Test {
     type Event = MetaEvent;
     type DataObjectTypeId = u64;
 }
 
+impl data_directory::Trait for Test {
+    type Event = MetaEvent;
+    type ContentId = Vec<u8>;
+    type Members = AnyAccountIsMember;
+    type IsActiveDataObjectType = AnyDataObjectTypeIsActive;
+}
+
+impl timestamp::Trait for Test {
+    type Moment = u64;
+    type OnTimestampSet = ();
+}
+
+impl consensus::Trait for Test {
+    type SessionKey = UintAuthorityId;
+    type InherentOfflineReport = ();
+    type Log = DigestItem;
+}
+
 pub struct ExtBuilder {
     first_data_object_type_id: u64,
 }
@@ -63,17 +112,36 @@ impl ExtBuilder {
         self
     }
     pub fn build(self) -> runtime_io::TestExternalities<Blake2Hasher> {
-        let mut t = system::GenesisConfig::<Test>::default().build_storage().unwrap().0;
+        let mut t = system::GenesisConfig::<Test>::default()
+            .build_storage()
+            .unwrap()
+            .0;
 
-        t.extend(data_object_type_registry::GenesisConfig::<Test>{
-            first_data_object_type_id: self.first_data_object_type_id,
-        }.build_storage().unwrap().0);
+        t.extend(
+            data_object_type_registry::GenesisConfig::<Test> {
+                first_data_object_type_id: self.first_data_object_type_id,
+            }
+            .build_storage()
+            .unwrap()
+            .0,
+        );
 
         t.into()
     }
 }
 
-
 pub type System = system::Module<Test>;
 pub type TestDataObjectTypeRegistry = data_object_type_registry::Module<Test>;
 pub type TestDataObjectType = data_object_type_registry::DataObjectType<Test>;
+pub type TestDataDirectory = data_directory::Module<Test>;
+pub type TestDataObject = data_directory::DataObject<Test>;
+
+pub const TEST_FIRST_DATA_OBJECT_TYPE_ID: u64 = 1000;
+pub fn with_default_mock_builder<R, F: FnOnce() -> R>(f: F) -> R {
+    with_externalities(
+        &mut ExtBuilder::default()
+            .first_data_object_type_id(TEST_FIRST_DATA_OBJECT_TYPE_ID)
+            .build(),
+        || f(),
+    )
+}

+ 1 - 2
src/storage/mod.rs

@@ -1,9 +1,8 @@
 #![cfg_attr(not(feature = "std"), no_std)]
 
-pub mod data_object_type_registry;
 pub mod data_directory;
 pub mod data_object_storage_registry;
+pub mod data_object_type_registry;
 pub mod downloads;
 
 mod mock;
-mod tests;

+ 43 - 23
src/traits.rs

@@ -1,40 +1,60 @@
 #![cfg_attr(not(feature = "std"), no_std)]
 
-use crate::storage::{data_object_type_registry, data_directory, data_object_storage_registry};
+use crate::storage::{data_directory, data_object_storage_registry, data_object_type_registry};
 use system;
+use parity_codec::Codec;
+use srml_support::{Parameter};
+use runtime_primitives::traits::{SimpleArithmetic, As, Member, MaybeSerializeDebug};
 
-// Storage
+// Members
+pub trait Members<T: system::Trait> {
+    type Id : Parameter + Member + SimpleArithmetic + Codec + Default + Copy
+        + As<usize> + As<u64> + MaybeSerializeDebug + PartialEq;
 
-pub trait IsActiveDataObjectType<T: data_object_type_registry::Trait> {
-    fn is_active_data_object_type(_which: &T::DataObjectTypeId) -> bool {
-        false
-    }
+    fn is_active_member(account_id: &T::AccountId) -> bool;
+
+    fn lookup_member_id(account_id: &T::AccountId) -> Result<Self::Id, &'static str>;
+
+    fn lookup_account_by_member_id(member_id: Self::Id) -> Result<T::AccountId, &'static str>;
 }
 
-pub trait ContentIdExists<T: data_directory::Trait> {
-    fn has_content(_which: &T::ContentId) -> bool {
+impl<T: system::Trait> Members<T> for () {
+    type Id = u32;
+    fn is_active_member(_account_id: &T::AccountId) -> bool {
         false
     }
-
-    fn get_data_object(_which: &T::ContentId) -> Result<data_directory::DataObject<T>,  &'static str> {
-        Err("not implemented")
+    fn lookup_member_id(_account_id: &T::AccountId) -> Result<Self::Id, &'static str> {
+        Err("member not found")
+    }
+    fn lookup_account_by_member_id(_member_id: Self::Id) -> Result<T::AccountId, &'static str> {
+        Err("account not found")
     }
 }
 
-pub trait ContentHasStorage<T: data_object_storage_registry::Trait> {
-    fn has_storage_provider(_which: &T::ContentId) -> bool {
-        false
-    }
+// Roles
+pub trait Roles<T: system::Trait> {
+    fn is_role_account(account_id: &T::AccountId) -> bool;
+}
 
-    fn is_ready_at_storage_provider(_which: &T::ContentId, _provider: &T::AccountId) -> bool {
-        false
-    }
+impl<T: system::Trait> Roles<T> for () {
+	fn is_role_account(_who: &T::AccountId) -> bool { false }
 }
 
-// Membership
+// Storage
+pub trait IsActiveDataObjectType<T: data_object_type_registry::Trait> {
+    fn is_active_data_object_type(_which: &T::DataObjectTypeId) -> bool;
+}
 
-pub trait IsActiveMember<T: system::Trait> {
-    fn is_active_member(_account_id: &T::AccountId) -> bool {
-        false
-    }
+pub trait ContentIdExists<T: data_directory::Trait> {
+    fn has_content(_which: &T::ContentId) -> bool;
+
+    fn get_data_object(
+        _which: &T::ContentId,
+    ) -> Result<data_directory::DataObject<T>, &'static str>;
+}
+
+pub trait ContentHasStorage<T: data_object_storage_registry::Trait> {
+    fn has_storage_provider(_which: &T::ContentId) -> bool;
+
+    fn is_ready_at_storage_provider(_which: &T::ContentId, _provider: &T::AccountId) -> bool;
 }