Browse Source

Merge branch 'development' into use-polkadot-master-substrate-version

Mokhtar Naamani 5 years ago
parent
commit
0e471a1dd1

+ 1 - 1
.travis.yml

@@ -1,6 +1,6 @@
 language: rust
 rust:
-  - 1.37.0
+  - 1.38.0
 cache:
   - cargo
 before_script:

+ 20 - 17
build.rs

@@ -14,30 +14,33 @@
 // You should have received a copy of the GNU General Public License
 // along with Substrate.  If not, see <http://www.gnu.org/licenses/>.
 
+use std::{env, process::Command, string::String};
 use wasm_builder_runner::{build_current_project_with_rustflags, WasmBuilderSource};
 
-/*
-Error appears in Cargo.toml in VisualStudio Code
-
-failed to run custom build command for `joystream-node-runtime v6.0.0 (/Users/mokhtar/joystream/runtime)`
-process didn't exit successfully: `/Users/mokhtar/joystream/runtime/target/rls/debug/build/joystream-node-runtime-c5155b3ff357b0c8/build-script-build` (exit code: 1)
---- stdout
-Unknown argument 'run'. Supported arguments:
-
-    --version or -V to print the version and commit info
-    --help or -h for this message
-    --cli starts the RLS in command line mode
-    No input starts the RLS as a language server
-
-seems `rls run ...` is being run instead of `rustup run ...`
-*/
-
 fn main() {
+    if !in_real_cargo_environment() {
+        env::set_var("BUILD_DUMMY_WASM_BINARY", "1");
+        println!("Building DUMMY Wasm binary");
+    }
+
     build_current_project_with_rustflags(
         "wasm_binary.rs",
-        WasmBuilderSource::Crates("1.0.4"),
+        WasmBuilderSource::Crates("1.0.8"),
         // This instructs LLD to export __heap_base as a global variable, which is used by the
         // external memory allocator.
         "-Clink-arg=--export=__heap_base",
     );
 }
+
+fn in_real_cargo_environment() -> bool {
+    let cargo =
+        env::var("CARGO").expect("`CARGO` env variable is always set when executing `build.rs`.");
+    let mut cmd = Command::new(cargo);
+    cmd.arg("--version");
+    let output = cmd.output().expect("failed to get cargo version");
+    let version = String::from_utf8(output.stdout).unwrap();
+    println!("{}", version);
+    // if we are building in an IDE (Code or Atom) with RLS extension the version
+    // would start with "rls"
+    version.starts_with("cargo")
+}

+ 6 - 5
src/governance/election.rs

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

+ 39 - 21
src/governance/mock.rs

@@ -2,7 +2,7 @@
 
 pub use super::{council, election, proposals};
 pub use crate::currency::GovernanceCurrency;
-use crate::traits::Members;
+use crate::membership;
 pub use system;
 
 pub use primitives::{Blake2Hasher, H256};
@@ -19,23 +19,6 @@ impl_outer_origin! {
     pub enum Origin for Test {}
 }
 
-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!")
-    }
-}
-
 // Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted.
 #[derive(Clone, PartialEq, Eq, Debug)]
 pub struct Test;
@@ -79,8 +62,14 @@ impl election::Trait for Test {
     type Event = ();
 
     type CouncilElected = (Council,);
-
-    type Members = MockMembership;
+}
+impl membership::members::Trait for Test {
+    type Event = ();
+    type MemberId = u32;
+    type SubscriptionId = u32;
+    type PaidTermId = u32;
+    type ActorId = u32;
+    type InitialMembersBalance = InitialMembersBalance;
 }
 
 parameter_types! {
@@ -89,6 +78,7 @@ parameter_types! {
     pub const CreationFee: u32 = 0;
     pub const TransactionBaseFee: u32 = 1;
     pub const TransactionByteFee: u32 = 0;
+    pub const InitialMembersBalance: u32 = 0;
 }
 
 impl balances::Trait for Test {
@@ -117,10 +107,38 @@ impl GovernanceCurrency for Test {
 // This function basically just builds a genesis storage key/value store according to
 // our desired mockup.
 pub fn initial_test_ext() -> runtime_io::TestExternalities {
-    let t = system::GenesisConfig::default()
+    let mut t = system::GenesisConfig::default()
         .build_storage::<Test>()
         .unwrap();
 
+    membership::members::GenesisConfig::<Test> {
+        default_paid_membership_fee: 0,
+        members: vec![
+            (1, "member1".into(), "".into(), "".into()),
+            (2, "member2".into(), "".into(), "".into()),
+            (3, "member3".into(), "".into(), "".into()),
+            (4, "member4".into(), "".into(), "".into()),
+            (5, "member5".into(), "".into(), "".into()),
+            (6, "member6".into(), "".into(), "".into()),
+            (7, "member7".into(), "".into(), "".into()),
+            (8, "member8".into(), "".into(), "".into()),
+            (9, "member9".into(), "".into(), "".into()),
+            (10, "member10".into(), "".into(), "".into()),
+            (11, "member11".into(), "".into(), "".into()),
+            (12, "member12".into(), "".into(), "".into()),
+            (13, "member13".into(), "".into(), "".into()),
+            (14, "member14".into(), "".into(), "".into()),
+            (15, "member15".into(), "".into(), "".into()),
+            (16, "member16".into(), "".into(), "".into()),
+            (17, "member17".into(), "".into(), "".into()),
+            (18, "member18".into(), "".into(), "".into()),
+            (19, "member19".into(), "".into(), "".into()),
+            (20, "member20".into(), "".into(), "".into()),
+        ],
+    }
+    .assimilate_storage(&mut t)
+    .unwrap();
+
     t.into()
 }
 

+ 31 - 24
src/governance/proposals.rs

@@ -16,7 +16,7 @@ use primitives::storage::well_known_keys;
 
 use super::council;
 pub use crate::currency::{BalanceOf, GovernanceCurrency};
-use crate::traits::Members;
+use crate::membership;
 
 const DEFAULT_APPROVAL_QUORUM: u32 = 60;
 const DEFAULT_MIN_STAKE: u32 = 100;
@@ -121,11 +121,11 @@ pub struct TallyResult<BlockNumber> {
     finalized_at: BlockNumber,
 }
 
-pub trait Trait: timestamp::Trait + council::Trait + GovernanceCurrency {
+pub trait Trait:
+    timestamp::Trait + council::Trait + GovernanceCurrency + membership::members::Trait
+{
     /// The overarching event type.
     type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
-
-    type Members: Members<Self>;
 }
 
 decl_event!(
@@ -232,7 +232,7 @@ decl_module! {
         ) {
 
             let proposer = ensure_signed(origin)?;
-            ensure!(Self::can_participate(proposer.clone()), MSG_ONLY_MEMBERS_CAN_PROPOSE);
+            ensure!(Self::can_participate(&proposer), MSG_ONLY_MEMBERS_CAN_PROPOSE);
             ensure!(stake >= Self::min_stake(), MSG_STAKE_IS_TOO_LOW);
 
             ensure!(!name.is_empty(), MSG_EMPTY_NAME_PROVIDED);
@@ -357,8 +357,9 @@ impl<T: Trait> Module<T> {
         <system::Module<T>>::block_number()
     }
 
-    fn can_participate(sender: T::AccountId) -> bool {
-        !T::Currency::free_balance(&sender).is_zero() && T::Members::is_active_member(&sender)
+    fn can_participate(sender: &T::AccountId) -> bool {
+        !T::Currency::free_balance(sender).is_zero()
+            && <membership::members::Module<T>>::is_member_account(sender)
     }
 
     fn is_councilor(sender: &T::AccountId) -> bool {
@@ -611,6 +612,7 @@ mod tests {
         pub const CreationFee: u32 = 0;
         pub const TransactionBaseFee: u32 = 1;
         pub const TransactionByteFee: u32 = 0;
+        pub const InitialMembersBalance: u32 = 0;
     }
 
     impl balances::Trait for Test {
@@ -639,26 +641,17 @@ mod tests {
         type Currency = balances::Module<Self>;
     }
 
-    impl Trait for Test {
+    impl membership::members::Trait for Test {
         type Event = ();
-        type Members = MockMembership;
+        type MemberId = u32;
+        type PaidTermId = u32;
+        type SubscriptionId = u32;
+        type ActorId = u32;
+        type InitialMembersBalance = InitialMembersBalance;
     }
 
-    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!")
-        }
+    impl Trait for Test {
+        type Event = ();
     }
 
     type System = system::Module<Test>;
@@ -716,6 +709,20 @@ mod tests {
         .assimilate_storage(&mut t)
         .unwrap();
 
+        membership::members::GenesisConfig::<Test> {
+            default_paid_membership_fee: 0,
+            members: vec![
+                (PROPOSER1, "alice".into(), "".into(), "".into()),
+                (PROPOSER2, "bobby".into(), "".into(), "".into()),
+                (COUNCILOR1, "councilor1".into(), "".into(), "".into()),
+                (COUNCILOR2, "councilor2".into(), "".into(), "".into()),
+                (COUNCILOR3, "councilor3".into(), "".into(), "".into()),
+                (COUNCILOR4, "councilor4".into(), "".into(), "".into()),
+                (COUNCILOR5, "councilor5".into(), "".into(), "".into()),
+            ],
+        }
+        .assimilate_storage(&mut t)
+        .unwrap();
         // t.extend(GenesisConfig::<Test>{
         //     // Here we can override defaults.
         // }.build_storage().unwrap().0);

+ 8 - 17
src/lib.rs

@@ -5,6 +5,8 @@
 #![recursion_limit = "256"]
 
 // Make the WASM binary available.
+// This is required only by the node build.
+// A dummy wasm_binary.rs will be built for the IDE.
 #[cfg(feature = "std")]
 include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs"));
 
@@ -242,6 +244,7 @@ parameter_types! {
     pub const CreationFee: u128 = 0;
     pub const TransactionBaseFee: u128 = 1;
     pub const TransactionByteFee: u128 = 0;
+    pub const InitialMembersBalance: u32 = 2000;
 }
 
 impl balances::Trait for Runtime {
@@ -391,7 +394,7 @@ pub mod currency;
 pub mod governance;
 use governance::{council, election, proposals};
 pub mod storage;
-use storage::{data_directory, data_object_storage_registry, data_object_type_registry, downloads};
+use storage::{data_directory, data_object_storage_registry, data_object_type_registry};
 mod membership;
 mod memo;
 mod traits;
@@ -413,13 +416,11 @@ impl currency::GovernanceCurrency for Runtime {
 
 impl governance::proposals::Trait for Runtime {
     type Event = Event;
-    type Members = Members;
 }
 
 impl governance::election::Trait for Runtime {
     type Event = Event;
     type CouncilElected = (Council,);
-    type Members = Members;
 }
 
 impl governance::council::Trait for Runtime {
@@ -440,21 +441,13 @@ impl storage::data_directory::Trait for Runtime {
     type Event = Event;
     type ContentId = ContentId;
     type SchemaId = u64;
-    type Members = Members;
     type Roles = LookupRoles;
     type IsActiveDataObjectType = DataObjectTypeRegistry;
 }
 
-impl storage::downloads::Trait for Runtime {
-    type Event = Event;
-    type DownloadSessionId = u64;
-    type ContentHasStorage = DataObjectStorageRegistry;
-}
-
 impl storage::data_object_storage_registry::Trait for Runtime {
     type Event = Event;
     type DataObjectStorageRelationshipId = u64;
-    type Members = Members;
     type Roles = LookupRoles;
     type ContentIdExists = DataDirectory;
 }
@@ -505,7 +498,8 @@ impl members::Trait for Runtime {
     type MemberId = u64;
     type PaidTermId = u64;
     type SubscriptionId = u64;
-    type Roles = LookupRoles;
+    type ActorId = u64;
+    type InitialMembersBalance = InitialMembersBalance;
 }
 
 /*
@@ -524,14 +518,13 @@ pub struct ShimMembershipRegistry {}
 
 impl forum::ForumUserRegistry<AccountId> for ShimMembershipRegistry {
     fn get_forum_user(id: &AccountId) -> Option<forum::ForumUser<AccountId>> {
-        if let Some(_profile) = members::Module::<Runtime>::get_profile(id) {
-            // For now the profile is not used for anything,
+        if members::Module::<Runtime>::is_member_account(id) {
+            // For now we don't retreive the members profile since it is not used for anything,
             // but in the future we may need it to read out more
             // information possibly required to construct a
             // ForumUser.
 
             // Now convert member profile to a forum user
-
             Some(forum::ForumUser { id: id.clone() })
         } else {
             None
@@ -550,7 +543,6 @@ impl migration::Trait for Runtime {
 
 impl actors::Trait for Runtime {
     type Event = Event;
-    type Members = Members;
     type OnActorRemoved = HandleActorRemoved;
 }
 
@@ -601,7 +593,6 @@ construct_runtime!(
 		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>},
-		DownloadSessions: downloads::{Module, Call, Storage, Event<T>, Config<T>},
         Discovery: discovery::{Module, Call, Storage, Event<T>},
 	}
 );

+ 300 - 130
src/membership/members.rs

@@ -1,13 +1,17 @@
 use crate::currency::{BalanceOf, GovernanceCurrency};
-use crate::traits::{Members, Roles};
 use codec::{Codec, Decode, Encode};
+
 use rstd::prelude::*;
-use runtime_primitives::traits::{MaybeSerializeDebug, Member, SimpleArithmetic};
-use srml_support::traits::Currency;
+#[cfg(feature = "std")]
+use runtime_primitives::traits::{MaybeSerialize, Member, One, SimpleArithmetic};
+use srml_support::traits::{Currency, Get};
 use srml_support::{decl_event, decl_module, decl_storage, dispatch, ensure, Parameter};
+
 use system::{self, ensure_root, ensure_signed};
 use timestamp;
 
+pub use super::role_types::*;
+
 pub trait Trait: system::Trait + GovernanceCurrency + timestamp::Trait {
     type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
 
@@ -17,7 +21,7 @@ pub trait Trait: system::Trait + GovernanceCurrency + timestamp::Trait {
         + Codec
         + Default
         + Copy
-        + MaybeSerializeDebug
+        + MaybeSerialize
         + PartialEq;
 
     type PaidTermId: Parameter
@@ -26,7 +30,7 @@ pub trait Trait: system::Trait + GovernanceCurrency + timestamp::Trait {
         + Codec
         + Default
         + Copy
-        + MaybeSerializeDebug
+        + MaybeSerialize
         + PartialEq;
 
     type SubscriptionId: Parameter
@@ -35,17 +39,27 @@ pub trait Trait: system::Trait + GovernanceCurrency + timestamp::Trait {
         + Codec
         + Default
         + Copy
-        + MaybeSerializeDebug
+        + MaybeSerialize
         + PartialEq;
 
-    type Roles: Roles<Self>;
+    type ActorId: Parameter
+        + Member
+        + SimpleArithmetic
+        + Codec
+        + Default
+        + Copy
+        + MaybeSerialize
+        + PartialEq
+        + Ord;
+
+    /// Initial balance of members created at genesis
+    type InitialMembersBalance: Get<BalanceOf<Self>>;
 }
 
-const DEFAULT_FIRST_MEMBER_ID: u32 = 1;
 const FIRST_PAID_TERMS_ID: u32 = 1;
 
 // Default paid membership terms
-const DEFAULT_PAID_TERM_ID: u32 = 0;
+pub const DEFAULT_PAID_TERM_ID: u32 = 0;
 const DEFAULT_PAID_TERM_FEE: u32 = 100; // Can be overidden in genesis config
 const DEFAULT_PAID_TERM_TEXT: &str = "Default Paid Term TOS...";
 
@@ -59,19 +73,47 @@ const DEFAULT_MAX_ABOUT_TEXT_LENGTH: u32 = 2048;
 #[derive(Encode, Decode)]
 /// Stored information about a registered user
 pub struct Profile<T: Trait> {
-    pub id: T::MemberId,
+    /// The unique handle chosen by member
     pub handle: Vec<u8>,
+
+    /// A Url to member's Avatar image
     pub avatar_uri: Vec<u8>,
+
+    /// Short text chosen by member to share information about themselves
     pub about: Vec<u8>,
+
+    /// Blocknumber when member was registered
     pub registered_at_block: T::BlockNumber,
+
+    /// Timestamp when member was registered
     pub registered_at_time: T::Moment,
+
+    /// How the member was registered
     pub entry: EntryMethod<T>,
+
+    /// Wether the member is suspended or not.
     pub suspended: bool,
+
+    /// The type of subsction the member has purchased if any.
     pub subscription: Option<T::SubscriptionId>,
+
+    /// Member's root account id. Only the root account is permitted to set a new root account
+    /// and update the controller account. Other modules may only allow certain actions if
+    /// signed with root account. It is intended to be an account that can remain offline and
+    /// potentially hold a member's funds, and be a source for staking roles.
+    pub root_account: T::AccountId,
+
+    /// Member's controller account id. This account is intended to be used by
+    /// a member to act under their identity in other modules. It will usually be used more
+    /// online and will have less funds in its balance.
+    pub controller_account: T::AccountId,
+
+    /// The set of registered roles the member has enrolled in.
+    pub roles: ActorInRoleSet<T::ActorId>,
 }
 
 #[derive(Clone, Debug, Encode, Decode, PartialEq)]
-/// Structure used to batch user configurable profile information when registering or updating info
+/// A "Partial" struct used to batch user configurable profile information when registering or updating info
 pub struct UserInfo {
     pub handle: Option<Vec<u8>>,
     pub avatar_uri: Option<Vec<u8>>,
@@ -89,13 +131,12 @@ struct CheckedUserInfo {
 pub enum EntryMethod<T: Trait> {
     Paid(T::PaidTermId),
     Screening(T::AccountId),
+    Genesis,
 }
 
 //#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
 #[derive(Encode, Decode, Eq, PartialEq)]
 pub struct PaidMembershipTerms<T: Trait> {
-    /// Unique identifier - the term id
-    pub id: T::PaidTermId,
     /// Quantity of native tokens which must be provably burned
     pub fee: BalanceOf<T>,
     /// String of capped length describing human readable conditions which are being agreed upon
@@ -105,7 +146,6 @@ pub struct PaidMembershipTerms<T: Trait> {
 impl<T: Trait> Default for PaidMembershipTerms<T> {
     fn default() -> Self {
         PaidMembershipTerms {
-            id: T::PaidTermId::from(DEFAULT_PAID_TERM_ID),
             fee: BalanceOf::<T>::from(DEFAULT_PAID_TERM_FEE),
             text: DEFAULT_PAID_TERM_TEXT.as_bytes().to_vec(),
         }
@@ -114,24 +154,21 @@ impl<T: Trait> Default for PaidMembershipTerms<T> {
 
 decl_storage! {
     trait Store for Module<T: Trait> as Membership {
-        /// MemberId's start at this value
-        pub FirstMemberId get(first_member_id) config(first_member_id): T::MemberId = T::MemberId::from(DEFAULT_FIRST_MEMBER_ID);
-
-        /// MemberId to assign to next member that is added to the registry
-        pub NextMemberId get(next_member_id) build(|config: &GenesisConfig<T>| config.first_member_id): T::MemberId = T::MemberId::from(DEFAULT_FIRST_MEMBER_ID);
-
-        /// Mapping of member ids to their corresponding primary accountid
-        pub AccountIdByMemberId get(account_id_by_member_id) : map T::MemberId => T::AccountId;
-
-        /// Mapping of members' account ids to their member id.
-        pub MemberIdByAccountId get(member_id_by_account_id) : map T::AccountId => Option<T::MemberId>;
+        /// MemberId to assign to next member that is added to the registry, and is also the
+        /// total number of members created. MemberIds start at Zero.
+        pub MembersCreated get(members_created) : T::MemberId;
 
         /// Mapping of member's id to their membership profile
-        // Value is Option<Profile> because it is not meaningful to have a Default value for Profile
         pub MemberProfile get(member_profile) : map T::MemberId => Option<Profile<T>>;
 
+        /// Mapping of a root account id to vector of member ids it controls
+        pub MemberIdsByRootAccountId get(member_ids_by_root_account_id) : map T::AccountId => Vec<T::MemberId>;
+
+        /// Mapping of a controller account id to vector of member ids it controls
+        pub MemberIdsByControllerAccountId get(member_ids_by_controller_account_id) : map T::AccountId => Vec<T::MemberId>;
+
         /// Registered unique handles and their mapping to their owner
-        pub Handles get(handles) : map Vec<u8> => Option<T::MemberId>;
+        pub Handles get(handles) : map Vec<u8> => T::MemberId;
 
         /// Next paid membership terms id
         pub NextPaidMembershipTermsId get(next_paid_membership_terms_id) : T::PaidTermId = T::PaidTermId::from(FIRST_PAID_TERMS_ID);
@@ -146,7 +183,7 @@ decl_storage! {
             // Initialization for updated runtime is done in run_migration()
             let mut terms: PaidMembershipTerms<T> = Default::default();
             terms.fee = config.default_paid_membership_fee;
-            vec![(terms.id, terms)]
+            vec![(T::PaidTermId::from(DEFAULT_PAID_TERM_ID), terms)]
         }) : map T::PaidTermId => Option<PaidMembershipTerms<T>>;
 
         /// Active Paid membership terms
@@ -163,53 +200,41 @@ decl_storage! {
         pub MaxHandleLength get(max_handle_length) : u32 = DEFAULT_MAX_HANDLE_LENGTH;
         pub MaxAvatarUriLength get(max_avatar_uri_length) : u32 = DEFAULT_MAX_AVATAR_URI_LENGTH;
         pub MaxAboutTextLength get(max_about_text_length) : u32 = DEFAULT_MAX_ABOUT_TEXT_LENGTH;
+
+        pub MembershipIdByActorInRole get(membership_id_by_actor_in_role): map ActorInRole<T::ActorId> => T::MemberId;
     }
     add_extra_genesis {
         config(default_paid_membership_fee): BalanceOf<T>;
+        config(members) : Vec<(T::AccountId, Vec<u8>, Vec<u8>, Vec<u8>)>;
+        build(|config: &GenesisConfig<T>| {
+            for (who, handle, avatar_uri, about) in &config.members {
+                let user_info = CheckedUserInfo {
+                    handle: handle.clone(), avatar_uri: avatar_uri.clone(), about: about.clone()
+                };
+                <Module<T>>::insert_member(&who, &user_info, EntryMethod::Genesis);
+
+                // Give member starting balance
+                T::Currency::deposit_creating(&who, T::InitialMembersBalance::get());
+            }
+
+            <MembersCreated<T>>::put(T::MemberId::from(config.members.len() as u32));
+        });
     }
 }
 
 decl_event! {
     pub enum Event<T> where
       <T as system::Trait>::AccountId,
-      <T as Trait>::MemberId {
+      <T as Trait>::MemberId,
+      <T as Trait>::ActorId, {
         MemberRegistered(MemberId, AccountId),
         MemberUpdatedAboutText(MemberId),
         MemberUpdatedAvatar(MemberId),
         MemberUpdatedHandle(MemberId),
-    }
-}
-
-/// Initialization step that runs when the runtime is installed as a runtime upgrade
-/// This will and should ONLY be called from the migration module that keeps track of
-/// the store version!
-impl<T: Trait> Module<T> {
-    pub fn initialize_storage() {
-        let default_terms: PaidMembershipTerms<T> = Default::default();
-        <PaidMembershipTermsById<T>>::insert(default_terms.id, default_terms);
-    }
-}
-
-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)) {
-            Ok(profile) => !profile.suspended,
-            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")
-        }
+        MemberSetRootAccount(MemberId, AccountId),
+        MemberSetControllerAccount(MemberId, AccountId),
+        MemberRegisteredRole(MemberId, ActorInRole<ActorId>),
+        MemberUnregisteredRole(MemberId, ActorInRole<ActorId>),
     }
 }
 
@@ -224,12 +249,6 @@ decl_module! {
             // make sure we are accepting new memberships
             ensure!(Self::new_memberships_allowed(), "new members not allowed");
 
-            // 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)?;
 
@@ -248,31 +267,46 @@ decl_module! {
         }
 
         /// Change member's about text
-        pub fn change_member_about_text(origin, text: Vec<u8>) {
-            let who = ensure_signed(origin)?;
-            let member_id = Self::ensure_is_member_primary_account(who.clone())?;
+        pub fn change_member_about_text(origin, member_id: T::MemberId, text: Vec<u8>) {
+            let sender = ensure_signed(origin)?;
+
+            let profile = Self::ensure_profile(member_id)?;
+
+            ensure!(profile.controller_account == sender, "only controller account can update member about text");
+
             Self::_change_member_about_text(member_id, &text)?;
         }
 
         /// Change member's avatar
-        pub fn change_member_avatar(origin, uri: Vec<u8>) {
-            let who = ensure_signed(origin)?;
-            let member_id = Self::ensure_is_member_primary_account(who.clone())?;
+        pub fn change_member_avatar(origin, member_id: T::MemberId, uri: Vec<u8>) {
+            let sender = ensure_signed(origin)?;
+
+            let profile = Self::ensure_profile(member_id)?;
+
+            ensure!(profile.controller_account == sender, "only controller account can update member avatar");
+
             Self::_change_member_avatar(member_id, &uri)?;
         }
 
         /// Change member's handle. Will ensure new handle is unique and old one will be available
         /// for other members to use.
-        pub fn change_member_handle(origin, handle: Vec<u8>) {
-            let who = ensure_signed(origin)?;
-            let member_id = Self::ensure_is_member_primary_account(who.clone())?;
+        pub fn change_member_handle(origin, member_id: T::MemberId, handle: Vec<u8>) {
+            let sender = ensure_signed(origin)?;
+
+            let profile = Self::ensure_profile(member_id)?;
+
+            ensure!(profile.controller_account == sender, "only controller account can update member handle");
+
             Self::_change_member_handle(member_id, handle)?;
         }
 
         /// Update member's all or some of handle, avatar and about text.
-        pub fn update_profile(origin, user_info: UserInfo) {
-            let who = ensure_signed(origin)?;
-            let member_id = Self::ensure_is_member_primary_account(who.clone())?;
+        pub fn update_profile(origin, member_id: T::MemberId, user_info: UserInfo) {
+            let sender = ensure_signed(origin)?;
+
+            let profile = Self::ensure_profile(member_id)?;
+
+            ensure!(profile.controller_account == sender, "only controller account can update member info");
 
             if let Some(uri) = user_info.avatar_uri {
                 Self::_change_member_avatar(member_id, &uri)?;
@@ -285,7 +319,52 @@ decl_module! {
             }
         }
 
-        pub fn add_screened_member(origin, new_member: T::AccountId, user_info: UserInfo) {
+        pub fn set_controller_account(origin, member_id: T::MemberId, new_controller_account: T::AccountId) {
+            let sender = ensure_signed(origin)?;
+
+            let mut profile = Self::ensure_profile(member_id)?;
+
+            ensure!(profile.root_account == sender, "only root account can set new controller account");
+
+            // only update if new_controller_account is different than current one
+            if profile.controller_account != new_controller_account {
+                <MemberIdsByControllerAccountId<T>>::mutate(&profile.controller_account, |ids| {
+                    ids.retain(|id| *id != member_id);
+                });
+
+                <MemberIdsByControllerAccountId<T>>::mutate(&new_controller_account, |ids| {
+                    ids.push(member_id);
+                });
+
+                profile.controller_account = new_controller_account.clone();
+                <MemberProfile<T>>::insert(member_id, profile);
+                Self::deposit_event(RawEvent::MemberSetControllerAccount(member_id, new_controller_account));
+            }
+        }
+
+        pub fn set_root_account(origin, member_id: T::MemberId, new_root_account: T::AccountId) {
+            let sender = ensure_signed(origin)?;
+
+            let mut profile = Self::ensure_profile(member_id)?;
+
+            ensure!(profile.root_account == sender, "only root account can set new root account");
+
+            // only update if new root account is different than current one
+            if profile.root_account != new_root_account {
+                <MemberIdsByRootAccountId<T>>::mutate(&profile.root_account, |ids| {
+                    ids.retain(|id| *id != member_id);
+                });
+
+                <MemberIdsByRootAccountId<T>>::mutate(&new_root_account, |ids| {
+                    ids.push(member_id);
+                });
+
+                profile.root_account = new_root_account.clone();
+                Self::deposit_event(RawEvent::MemberSetRootAccount(member_id, new_root_account));
+            }
+        }
+
+        pub fn add_screened_member(origin, new_member_account: T::AccountId, user_info: UserInfo) {
             // ensure sender is screening authority
             let sender = ensure_signed(origin)?;
 
@@ -299,20 +378,14 @@ decl_module! {
             // make sure we are accepting new memberships
             ensure!(Self::new_memberships_allowed(), "new members not allowed");
 
-            // 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
             Self::ensure_unique_handle(&user_info.handle)?;
 
-            let member_id = Self::insert_member(&new_member, &user_info, EntryMethod::Screening(sender));
+            let member_id = Self::insert_member(&new_member_account, &user_info, EntryMethod::Screening(sender));
 
-            Self::deposit_event(RawEvent::MemberRegistered(member_id, new_member.clone()));
+            Self::deposit_event(RawEvent::MemberRegistered(member_id, new_member_account));
         }
 
         pub fn set_screening_authority(origin, authority: T::AccountId) {
@@ -323,41 +396,15 @@ decl_module! {
 }
 
 impl<T: Trait> Module<T> {
-    pub fn get_profile(id: &T::AccountId) -> Option<Profile<T>> {
-        if let Some(member_id) = Self::ensure_is_member(id).ok() {
-            // This option _must_ be set
-            Self::member_profile(&member_id)
-        } else {
-            None
-        }
-    }
-
-    fn ensure_not_member(who: &T::AccountId) -> dispatch::Result {
-        ensure!(
-            !<MemberIdByAccountId<T>>::exists(who),
-            "account already associated with a membership"
-        );
-        Ok(())
+    /// Provided that the memberid exists return its profile. Returns error otherwise.
+    pub fn ensure_profile(id: T::MemberId) -> Result<Profile<T>, &'static str> {
+        Self::member_profile(&id).ok_or("member profile not found")
     }
 
-    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)
-    }
-
-    fn ensure_is_member_primary_account(who: T::AccountId) -> Result<T::MemberId, &'static str> {
-        let member_id = Self::ensure_is_member(&who)?;
-        ensure!(
-            Self::account_id_by_member_id(member_id) == who,
-            "not primary account"
-        );
-        Ok(member_id)
-    }
-
-    fn ensure_profile(id: T::MemberId) -> Result<Profile<T>, &'static str> {
-        let profile = Self::member_profile(&id).ok_or("member profile not found")?;
-        Ok(profile)
+    /// Returns true if account is either a member's root or controller account
+    pub fn is_member_account(who: &T::AccountId) -> bool {
+        <MemberIdsByRootAccountId<T>>::exists(who)
+            || <MemberIdsByControllerAccountId<T>>::exists(who)
     }
 
     fn ensure_active_terms_id(
@@ -423,16 +470,14 @@ impl<T: Trait> Module<T> {
         })
     }
 
-    // Mutating methods
     fn insert_member(
         who: &T::AccountId,
         user_info: &CheckedUserInfo,
         entry_method: EntryMethod<T>,
     ) -> T::MemberId {
-        let new_member_id = Self::next_member_id();
+        let new_member_id = Self::members_created();
 
         let profile: Profile<T> = Profile {
-            id: new_member_id,
             handle: user_info.handle.clone(),
             avatar_uri: user_info.avatar_uri.clone(),
             about: user_info.about.clone(),
@@ -441,15 +486,21 @@ impl<T: Trait> Module<T> {
             entry: entry_method,
             suspended: false,
             subscription: None,
+            root_account: who.clone(),
+            controller_account: who.clone(),
+            roles: ActorInRoleSet::new(),
         };
 
-        <MemberIdByAccountId<T>>::insert(who.clone(), new_member_id);
-        <AccountIdByMemberId<T>>::insert(new_member_id, who.clone());
+        <MemberIdsByRootAccountId<T>>::mutate(who, |ids| {
+            ids.push(new_member_id);
+        });
+        <MemberIdsByControllerAccountId<T>>::mutate(who, |ids| {
+            ids.push(new_member_id);
+        });
+
         <MemberProfile<T>>::insert(new_member_id, profile);
         <Handles<T>>::insert(user_info.handle.clone(), new_member_id);
-        <NextMemberId<T>>::mutate(|n| {
-            *n += T::MemberId::from(1);
-        });
+        <MembersCreated<T>>::put(new_member_id + One::one());
 
         new_member_id
     }
@@ -483,4 +534,123 @@ impl<T: Trait> Module<T> {
         <MemberProfile<T>>::insert(id, profile);
         Ok(())
     }
+
+    /// Determines if the signing account is a controller account of a member that has the registered
+    /// actor_in_role.
+    pub fn key_can_sign_for_role(
+        signing_account: &T::AccountId,
+        actor_in_role: ActorInRole<T::ActorId>,
+    ) -> bool {
+        Self::member_ids_by_controller_account_id(signing_account)
+            .iter()
+            .any(|member_id| {
+                let profile = Self::member_profile(member_id).unwrap(); // must exist
+                profile.roles.has_registered_role(&actor_in_role)
+            })
+    }
+
+    /// Returns true if member identified by their member id occupies a Role at least once
+    pub fn member_is_in_role(member_id: T::MemberId, role: Role) -> bool {
+        Self::ensure_profile(member_id)
+            .ok()
+            .map_or(false, |profile| profile.roles.occupies_role(role))
+    }
+
+    // policy across all roles is:
+    // members can only occupy a role at most once at a time
+    // members can enter any role
+    // no limit on total number of roles a member can enter
+    // ** Note ** Role specific policies should be enforced by the client modules
+    // this method should handle higher level policies
+    pub fn can_register_role_on_member(
+        signing_account: &T::AccountId,
+        member_id: T::MemberId,
+        actor_in_role: ActorInRole<T::ActorId>,
+    ) -> Result<(), &'static str> {
+        let profile = Self::ensure_profile(member_id)?;
+
+        ensure!(
+            profile.controller_account == *signing_account,
+            "OnlyMemberControllerCanRegisterRole"
+        );
+
+        // ensure is active member
+        ensure!(!profile.suspended, "SuspendedMemberCannotEnterRole");
+
+        // guard against duplicate ActorInRole
+        ensure!(
+            !<MembershipIdByActorInRole<T>>::exists(&actor_in_role),
+            "ActorInRoleAlreadyExists"
+        );
+
+        ensure!(
+            !profile.roles.occupies_role(actor_in_role.role),
+            "MemberAlreadyInRole"
+        );
+
+        // Other possible checks:
+        // How long the member has been registered
+        // Minimum balance
+        // EntryMethod
+
+        Ok(())
+    }
+
+    pub fn register_role_on_member(
+        signing_account: &T::AccountId,
+        member_id: T::MemberId,
+        actor_in_role: ActorInRole<T::ActorId>,
+    ) -> Result<(), &'static str> {
+        // policy check
+        Self::can_register_role_on_member(signing_account, member_id, actor_in_role)?;
+
+        let mut profile = Self::ensure_profile(member_id)?; // .expect().. ?
+        assert!(profile.roles.register_role(&actor_in_role));
+
+        <MemberProfile<T>>::insert(member_id, profile);
+        <MembershipIdByActorInRole<T>>::insert(&actor_in_role, member_id);
+        Self::deposit_event(RawEvent::MemberRegisteredRole(member_id, actor_in_role));
+        Ok(())
+    }
+
+    pub fn can_unregister_role_on_member(
+        signing_account: &T::AccountId,
+        member_id: T::MemberId,
+        actor_in_role: ActorInRole<T::ActorId>,
+    ) -> Result<(), &'static str> {
+        let profile = Self::ensure_profile(member_id)?;
+
+        ensure!(
+            profile.controller_account == *signing_account,
+            "OnlyMemberControllerCanUnregisterRole"
+        );
+
+        ensure!(
+            <MembershipIdByActorInRole<T>>::exists(&actor_in_role),
+            "ActorInRoleNotFound"
+        );
+        ensure!(
+            <MembershipIdByActorInRole<T>>::get(&actor_in_role) == member_id,
+            "ActorInRoleNotRegisteredForMember"
+        );
+        Ok(())
+    }
+
+    pub fn unregister_role_on_member(
+        signing_account: &T::AccountId,
+        member_id: T::MemberId,
+        actor_in_role: ActorInRole<T::ActorId>,
+    ) -> Result<(), &'static str> {
+        Self::can_unregister_role_on_member(signing_account, member_id, actor_in_role)?;
+
+        let mut profile = Self::ensure_profile(member_id)?; // .expect().. ?
+
+        assert!(profile.roles.unregister_role(&actor_in_role));
+
+        <MemberProfile<T>>::insert(member_id, profile);
+
+        Self::deposit_event(RawEvent::MemberUnregisteredRole(member_id, actor_in_role));
+
+        Ok(())
+    }
 }

+ 15 - 9
src/membership/mock.rs

@@ -1,6 +1,6 @@
 #![cfg(test)]
 
-pub use super::members;
+pub use super::members::{self, DEFAULT_PAID_TERM_ID};
 pub use crate::currency::GovernanceCurrency;
 pub use srml_support::traits::Currency;
 pub use system;
@@ -60,6 +60,7 @@ parameter_types! {
     pub const CreationFee: u32 = 0;
     pub const TransactionBaseFee: u32 = 1;
     pub const TransactionByteFee: u32 = 0;
+    pub const InitialMembersBalance: u64 = 2000;
 }
 
 impl balances::Trait for Test {
@@ -88,39 +89,44 @@ impl members::Trait for Test {
     type MemberId = u32;
     type PaidTermId = u32;
     type SubscriptionId = u32;
-    type Roles = ();
+    type ActorId = u32;
+    type InitialMembersBalance = InitialMembersBalance;
 }
 
 pub struct ExtBuilder {
-    first_member_id: u32,
     default_paid_membership_fee: u64,
+    members: Vec<(u64)>,
 }
 impl Default for ExtBuilder {
     fn default() -> Self {
         Self {
-            first_member_id: 1,
             default_paid_membership_fee: 100,
+            members: vec![],
         }
     }
 }
 
 impl ExtBuilder {
-    pub fn first_member_id(mut self, first_member_id: u32) -> Self {
-        self.first_member_id = first_member_id;
-        self
-    }
     pub fn default_paid_membership_fee(mut self, default_paid_membership_fee: u64) -> Self {
         self.default_paid_membership_fee = default_paid_membership_fee;
         self
     }
+    pub fn members(mut self, members: Vec<u64>) -> Self {
+        self.members = members;
+        self
+    }
     pub fn build(self) -> runtime_io::TestExternalities {
         let mut t = system::GenesisConfig::default()
             .build_storage::<Test>()
             .unwrap();
 
         members::GenesisConfig::<Test> {
-            first_member_id: self.first_member_id,
             default_paid_membership_fee: self.default_paid_membership_fee,
+            members: self
+                .members
+                .iter()
+                .map(|account_id| (*account_id, "".into(), "".into(), "".into()))
+                .collect(),
         }
         .assimilate_storage(&mut t)
         .unwrap();

+ 1 - 0
src/membership/mod.rs

@@ -1,4 +1,5 @@
 pub mod members;
+pub mod role_types;
 
 mod mock;
 mod tests;

+ 58 - 0
src/membership/role_types.rs

@@ -0,0 +1,58 @@
+use codec::{Decode, Encode};
+use rstd::collections::btree_set::BTreeSet;
+use rstd::prelude::*;
+
+#[derive(Encode, Decode, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Debug)]
+pub enum Role {
+    StorageProvider,
+    Publisher,
+    CuratorLead,
+    Curator,
+}
+
+#[derive(Encode, Decode, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
+pub struct ActorInRole<ActorId> {
+    pub role: Role,
+    pub actor_id: ActorId,
+}
+
+impl<ActorId> ActorInRole<ActorId> {
+    pub fn new(role: Role, actor_id: ActorId) -> Self {
+        ActorInRole { role, actor_id }
+    }
+}
+
+#[derive(Encode, Decode, Eq, PartialEq, Ord, PartialOrd)]
+pub struct ActorInRoleSet<ActorId: Ord + Copy>(BTreeSet<ActorInRole<ActorId>>);
+
+impl<ActorId: Ord + Copy> ActorInRoleSet<ActorId> {
+    pub fn new() -> Self {
+        Self(BTreeSet::<ActorInRole<ActorId>>::new())
+    }
+
+    fn role_instance_count(&self, role: Role) -> usize {
+        self.0.iter().fold(0, |count, actor_in_role| {
+            if actor_in_role.role == role {
+                count + 1
+            } else {
+                count
+            }
+        })
+    }
+
+    pub fn occupies_role(&self, role: Role) -> bool {
+        self.role_instance_count(role) > 0
+    }
+
+    pub fn register_role(&mut self, actor_in_role: &ActorInRole<ActorId>) -> bool {
+        self.0.insert(*actor_in_role)
+    }
+
+    pub fn unregister_role(&mut self, actor_in_role: &ActorInRole<ActorId>) -> bool {
+        self.0.remove(actor_in_role)
+    }
+
+    pub fn has_registered_role(&self, actor_in_role: &ActorInRole<ActorId>) -> bool {
+        self.0.contains(actor_in_role)
+    }
+}

+ 194 - 53
src/membership/tests.rs

@@ -8,12 +8,21 @@ fn assert_ok_unwrap<T>(value: Option<T>, err: &'static str) -> T {
     match value {
         None => {
             assert!(false, err);
+            // although this code path is not reached, we need to return correct type
+            // in match arm. Using panic! would remove this need, but assert! gives us better error in
+            // console output
             value.unwrap()
         }
         Some(v) => v,
     }
 }
 
+fn assert_dispatch_error_message(result: Result<(), &'static str>, expected_message: &'static str) {
+    assert!(result.is_err());
+    let message = result.err().unwrap();
+    assert_eq!(message, expected_message);
+}
+
 fn get_alice_info() -> members::UserInfo {
     members::UserInfo {
         handle: Some(String::from("alice").as_bytes().to_vec()),
@@ -39,12 +48,11 @@ fn get_bob_info() -> members::UserInfo {
 }
 
 const ALICE_ACCOUNT_ID: u64 = 1;
-const DEFAULT_TERMS_ID: u32 = 0;
 
 fn buy_default_membership_as_alice() -> dispatch::Result {
     Members::buy_membership(
         Origin::signed(ALICE_ACCOUNT_ID),
-        DEFAULT_TERMS_ID,
+        DEFAULT_PAID_TERM_ID,
         get_alice_info(),
     )
 }
@@ -56,49 +64,49 @@ fn set_alice_free_balance(balance: u64) {
 #[test]
 fn initial_state() {
     const DEFAULT_FEE: u64 = 500;
-    const DEFAULT_FIRST_ID: u32 = 1000;
+    let initial_members = [1, 2, 3];
 
     ExtBuilder::default()
         .default_paid_membership_fee(DEFAULT_FEE)
-        .first_member_id(DEFAULT_FIRST_ID)
+        .members(initial_members.to_vec())
         .build()
         .execute_with(|| {
-            assert_eq!(Members::first_member_id(), DEFAULT_FIRST_ID);
-            assert_eq!(Members::next_member_id(), DEFAULT_FIRST_ID);
-
             let default_terms = assert_ok_unwrap(
-                Members::paid_membership_terms_by_id(DEFAULT_TERMS_ID),
+                Members::paid_membership_terms_by_id(DEFAULT_PAID_TERM_ID),
                 "default terms not initialized",
             );
 
-            assert_eq!(default_terms.id, DEFAULT_TERMS_ID);
             assert_eq!(default_terms.fee, DEFAULT_FEE);
+
+            // initial balance
+            assert_eq!(
+                Balances::free_balance(initial_members[0]),
+                <Test as members::Trait>::InitialMembersBalance::get()
+            );
         });
 }
 
 #[test]
 fn buy_membership() {
     const DEFAULT_FEE: u64 = 500;
-    const DEFAULT_FIRST_ID: u32 = 1000;
     const SURPLUS_BALANCE: u64 = 500;
 
     ExtBuilder::default()
         .default_paid_membership_fee(DEFAULT_FEE)
-        .first_member_id(DEFAULT_FIRST_ID)
         .build()
         .execute_with(|| {
             let initial_balance = DEFAULT_FEE + SURPLUS_BALANCE;
             set_alice_free_balance(initial_balance);
 
+            let next_member_id = Members::members_created();
+
             assert_ok!(buy_default_membership_as_alice());
 
-            let member_id = assert_ok_unwrap(
-                Members::member_id_by_account_id(&ALICE_ACCOUNT_ID),
-                "member id not assigned",
-            );
+            let member_ids = Members::member_ids_by_root_account_id(&ALICE_ACCOUNT_ID);
+            assert_eq!(member_ids, vec![next_member_id]);
 
             let profile = assert_ok_unwrap(
-                Members::member_profile(&member_id),
+                Members::member_profile(&next_member_id),
                 "member profile not created",
             );
 
@@ -107,6 +115,13 @@ fn buy_membership() {
             assert_eq!(Some(profile.about), get_alice_info().about);
 
             assert_eq!(Balances::free_balance(&ALICE_ACCOUNT_ID), SURPLUS_BALANCE);
+
+            // controller account initially set to primary account
+            assert_eq!(profile.controller_account, ALICE_ACCOUNT_ID);
+            assert_eq!(
+                Members::member_ids_by_controller_account_id(ALICE_ACCOUNT_ID),
+                vec![next_member_id]
+            );
         });
 }
 
@@ -121,7 +136,10 @@ fn buy_membership_fails_without_enough_balance() {
             let initial_balance = DEFAULT_FEE - 1;
             set_alice_free_balance(initial_balance);
 
-            assert!(buy_default_membership_as_alice().is_err());
+            assert_dispatch_error_message(
+                buy_default_membership_as_alice(),
+                "not enough balance to buy membership",
+            );
         });
 }
 
@@ -138,27 +156,10 @@ fn new_memberships_allowed_flag() {
 
             members::NewMembershipsAllowed::put(false);
 
-            assert!(buy_default_membership_as_alice().is_err());
-        });
-}
-
-#[test]
-fn account_cannot_create_multiple_memberships() {
-    const DEFAULT_FEE: u64 = 500;
-    const SURPLUS_BALANCE: u64 = 500;
-
-    ExtBuilder::default()
-        .default_paid_membership_fee(DEFAULT_FEE)
-        .build()
-        .execute_with(|| {
-            let initial_balance = DEFAULT_FEE + SURPLUS_BALANCE;
-            set_alice_free_balance(initial_balance);
-
-            // First time it works
-            assert_ok!(buy_default_membership_as_alice());
-
-            // second attempt should fail
-            assert!(buy_default_membership_as_alice().is_err());
+            assert_dispatch_error_message(
+                buy_default_membership_as_alice(),
+                "new members not allowed",
+            );
         });
 }
 
@@ -178,7 +179,10 @@ fn unique_handles() {
             <members::Handles<Test>>::insert(get_alice_info().handle.unwrap(), 1);
 
             // should not be allowed to buy membership with that handle
-            assert!(buy_default_membership_as_alice().is_err());
+            assert_dispatch_error_message(
+                buy_default_membership_as_alice(),
+                "handle already registered",
+            );
         });
 }
 
@@ -194,21 +198,19 @@ fn update_profile() {
             let initial_balance = DEFAULT_FEE + SURPLUS_BALANCE;
             set_alice_free_balance(initial_balance);
 
+            let next_member_id = Members::members_created();
+
             assert_ok!(buy_default_membership_as_alice());
 
             assert_ok!(Members::update_profile(
                 Origin::signed(ALICE_ACCOUNT_ID),
+                next_member_id,
                 get_bob_info()
             ));
 
-            let member_id = assert_ok_unwrap(
-                Members::member_id_by_account_id(&ALICE_ACCOUNT_ID),
-                "member id not assigned",
-            );
-
             let profile = assert_ok_unwrap(
-                Members::member_profile(&member_id),
-                "member profile created",
+                Members::member_profile(&next_member_id),
+                "member profile not created",
             );
 
             assert_eq!(Some(profile.handle), get_bob_info().handle);
@@ -223,20 +225,17 @@ fn add_screened_member() {
         let screening_authority = 5;
         <members::ScreeningAuthority<Test>>::put(&screening_authority);
 
+        let next_member_id = Members::members_created();
+
         assert_ok!(Members::add_screened_member(
             Origin::signed(screening_authority),
             ALICE_ACCOUNT_ID,
             get_alice_info()
         ));
 
-        let member_id = assert_ok_unwrap(
-            Members::member_id_by_account_id(&ALICE_ACCOUNT_ID),
-            "member id not assigned",
-        );
-
         let profile = assert_ok_unwrap(
-            Members::member_profile(&member_id),
-            "member profile created",
+            Members::member_profile(&next_member_id),
+            "member profile not created",
         );
 
         assert_eq!(Some(profile.handle), get_alice_info().handle);
@@ -248,3 +247,145 @@ fn add_screened_member() {
         );
     });
 }
+
+#[test]
+fn set_controller_key() {
+    let initial_members = [ALICE_ACCOUNT_ID];
+    const ALICE_CONTROLLER_ID: u64 = 2;
+
+    ExtBuilder::default()
+        .members(initial_members.to_vec())
+        .build()
+        .execute_with(|| {
+            let member_id = Members::member_ids_by_root_account_id(&ALICE_ACCOUNT_ID)[0];
+
+            assert_ok!(Members::set_controller_account(
+                Origin::signed(ALICE_ACCOUNT_ID),
+                member_id,
+                ALICE_CONTROLLER_ID
+            ));
+
+            let profile = assert_ok_unwrap(
+                Members::member_profile(&member_id),
+                "member profile not created",
+            );
+
+            assert_eq!(profile.controller_account, ALICE_CONTROLLER_ID);
+            assert_eq!(
+                Members::member_ids_by_controller_account_id(&ALICE_CONTROLLER_ID),
+                vec![member_id]
+            );
+            assert!(Members::member_ids_by_controller_account_id(&ALICE_ACCOUNT_ID).is_empty());
+        });
+}
+
+#[test]
+fn set_root_account() {
+    let initial_members = [ALICE_ACCOUNT_ID];
+    const ALICE_NEW_ROOT_ACCOUNT: u64 = 2;
+
+    ExtBuilder::default()
+        .members(initial_members.to_vec())
+        .build()
+        .execute_with(|| {
+            let member_id_1 = Members::member_ids_by_root_account_id(&ALICE_ACCOUNT_ID)[0];
+
+            assert_ok!(Members::set_root_account(
+                Origin::signed(ALICE_ACCOUNT_ID),
+                member_id_1,
+                ALICE_NEW_ROOT_ACCOUNT
+            ));
+
+            let member_id_2 = Members::member_ids_by_root_account_id(&ALICE_NEW_ROOT_ACCOUNT)[0];
+
+            assert_eq!(member_id_1, member_id_2);
+
+            assert!(Members::member_ids_by_root_account_id(&ALICE_ACCOUNT_ID).is_empty());
+        });
+}
+
+#[test]
+fn registering_and_unregistering_roles_on_member() {
+    let initial_members = [1, 2];
+
+    ExtBuilder::default()
+        .members(initial_members.to_vec())
+        .build()
+        .execute_with(|| {
+            const DUMMY_ACTOR_ID: u32 = 100;
+            let member_id_1 = Members::member_ids_by_root_account_id(&1)[0];
+            let member_id_2 = Members::member_ids_by_root_account_id(&2)[0];
+
+            // no initial roles for member
+            assert!(!Members::member_is_in_role(
+                member_id_1,
+                members::Role::Publisher
+            ));
+
+            // REGISTERING
+
+            // successful registration
+            assert_ok!(Members::register_role_on_member(
+                &1,
+                member_id_1,
+                members::ActorInRole::new(members::Role::Publisher, DUMMY_ACTOR_ID)
+            ));
+            assert!(Members::member_is_in_role(
+                member_id_1,
+                members::Role::Publisher
+            ));
+
+            // enter role a second time should fail
+            assert_dispatch_error_message(
+                Members::register_role_on_member(
+                    &1,
+                    member_id_1,
+                    members::ActorInRole::new(members::Role::Publisher, DUMMY_ACTOR_ID + 1),
+                ),
+                "MemberAlreadyInRole",
+            );
+
+            // registering another member in same role and actorid combination should fail
+            assert_dispatch_error_message(
+                Members::register_role_on_member(
+                    &2,
+                    member_id_2,
+                    members::ActorInRole::new(members::Role::Publisher, DUMMY_ACTOR_ID),
+                ),
+                "ActorInRoleAlreadyExists",
+            );
+
+            // UNREGISTERING
+
+            // trying to unregister non existant actor role should fail
+            assert_dispatch_error_message(
+                Members::unregister_role_on_member(
+                    &1,
+                    member_id_1,
+                    members::ActorInRole::new(members::Role::Curator, 222),
+                ),
+                "ActorInRoleNotFound",
+            );
+
+            // trying to unregister actor role on wrong member should fail
+            assert_dispatch_error_message(
+                Members::unregister_role_on_member(
+                    &2,
+                    member_id_2,
+                    members::ActorInRole::new(members::Role::Publisher, DUMMY_ACTOR_ID),
+                ),
+                "ActorInRoleNotRegisteredForMember",
+            );
+
+            // successfully unregister role
+            assert_ok!(Members::unregister_role_on_member(
+                &1,
+                member_id_1,
+                members::ActorInRole::new(members::Role::Publisher, DUMMY_ACTOR_ID)
+            ));
+            assert!(!Members::member_is_in_role(
+                member_id_1,
+                members::Role::Publisher
+            ));
+        });
+}

+ 55 - 60
src/roles/actors.rs

@@ -1,27 +1,23 @@
 use crate::currency::{BalanceOf, GovernanceCurrency};
 use codec::{Decode, Encode};
 use rstd::prelude::*;
-use runtime_primitives::traits::{Bounded, MaybeDebug, Zero};
+use runtime_primitives::traits::{Bounded, Zero};
 use srml_support::traits::{
     Currency, LockIdentifier, LockableCurrency, WithdrawReason, WithdrawReasons,
 };
 use srml_support::{decl_event, decl_module, decl_storage, ensure};
 use system::{self, ensure_root, ensure_signed};
 
-use crate::traits::Members;
+use crate::membership;
+pub use membership::members::Role;
 
 const STAKING_ID: LockIdentifier = *b"role_stk";
 
-#[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> {
+pub struct RoleParameters<Balance, BlockNumber> {
     // minium balance required to stake to enter a role
-    pub min_stake: BalanceOf<T>,
+    pub min_stake: Balance,
 
     // minimum actors to maintain - if role is unstaking
     // and remaining actors would be less that this value - prevent or punish for unstaking
@@ -31,44 +27,44 @@ pub struct RoleParameters<T: Trait> {
     pub max_actors: u32,
 
     // fixed amount of tokens paid to actors' primary account
-    pub reward: BalanceOf<T>,
+    pub reward: Balance,
 
     // payouts are made at this block interval
-    pub reward_period: T::BlockNumber,
+    pub reward_period: BlockNumber,
 
     // minimum amount of time before being able to unstake
-    pub bonding_period: T::BlockNumber,
+    pub bonding_period: BlockNumber,
 
     // how long tokens remain locked for after unstaking
-    pub unbonding_period: T::BlockNumber,
+    pub unbonding_period: BlockNumber,
 
     // minimum period required to be in service. unbonding before this time is highly penalized
-    pub min_service_period: T::BlockNumber,
+    pub min_service_period: 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,
+    pub startup_grace_period: BlockNumber,
 
     // small fee burned to make a request to enter role
-    pub entry_request_fee: BalanceOf<T>,
+    pub entry_request_fee: Balance,
 }
 
-impl<T: Trait> Default for RoleParameters<T> {
+impl<Balance: From<u32>, BlockNumber: From<u32>> Default for RoleParameters<Balance, BlockNumber> {
     fn default() -> Self {
         Self {
-            min_stake: BalanceOf::<T>::from(3000),
+            min_stake: Balance::from(3000),
             max_actors: 10,
-            reward: BalanceOf::<T>::from(10),
-            reward_period: T::BlockNumber::from(600),
-            unbonding_period: T::BlockNumber::from(600),
-            entry_request_fee: BalanceOf::<T>::from(50),
+            reward: Balance::from(10),
+            reward_period: BlockNumber::from(600),
+            unbonding_period: BlockNumber::from(600),
+            entry_request_fee: Balance::from(50),
 
             // not currently used
             min_actors: 5,
-            bonding_period: T::BlockNumber::from(600),
-            min_service_period: T::BlockNumber::from(600),
-            startup_grace_period: T::BlockNumber::from(600),
+            bonding_period: BlockNumber::from(600),
+            min_service_period: BlockNumber::from(600),
+            startup_grace_period: BlockNumber::from(600),
         }
     }
 }
@@ -85,15 +81,13 @@ pub trait ActorRemoved<T: Trait> {
     fn actor_removed(actor: &T::AccountId);
 }
 
-pub trait Trait: system::Trait + GovernanceCurrency + MaybeDebug {
+pub trait Trait: system::Trait + GovernanceCurrency + membership::members::Trait {
     type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
 
-    type Members: Members<Self>;
-
     type OnActorRemoved: ActorRemoved<Self>;
 }
 
-pub type MemberId<T> = <<T as Trait>::Members as Members<T>>::Id;
+pub type MemberId<T> = <T as membership::members::Trait>::MemberId;
 // actor account, memberid, role, expires
 pub type Request<T> = (
     <T as system::Trait>::AccountId,
@@ -111,17 +105,17 @@ decl_storage! {
         /// requirements to enter and maintain status in roles
         pub Parameters get(parameters) build(|config: &GenesisConfig| {
             if config.enable_storage_role {
-                let storage_params: RoleParameters<T> = Default::default();
-                vec![(Role::Storage, storage_params)]
+                let storage_params: RoleParameters<BalanceOf<T>, T::BlockNumber> = Default::default();
+                vec![(Role::StorageProvider, storage_params)]
             } else {
                 vec![]
             }
-        }): map Role => Option<RoleParameters<T>>;
+        }): map Role => Option<RoleParameters<BalanceOf<T>, T::BlockNumber>>;
 
         /// the roles members can enter into
         pub AvailableRoles get(available_roles) build(|config: &GenesisConfig| {
             if config.enable_storage_role {
-                vec![(Role::Storage)]
+                vec![(Role::StorageProvider)]
             } else {
                 vec![]
             }
@@ -173,19 +167,9 @@ impl<T: Trait> Module<T> {
         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> {
+    fn ensure_role_parameters(
+        role: Role,
+    ) -> Result<RoleParameters<BalanceOf<T>, T::BlockNumber>, &'static str> {
         Self::parameters(role).ok_or("no parameters for role")
     }
 
@@ -286,9 +270,9 @@ decl_module! {
                                 if balance < params.min_stake {
                                     let _ = T::Currency::deposit_into_existing(&actor.account, params.reward);
                                 } else {
-                                    // otherwise it should go the the member account
-                                    if let Ok(member_account) = T::Members::lookup_account_by_member_id(actor.member_id) {
-                                        let _ = T::Currency::deposit_into_existing(&member_account, params.reward);
+                                    // otherwise it should go the the member's root account
+                                    if let Some(profile) = <membership::members::Module<T>>::member_profile(&actor.member_id) {
+                                        let _ = T::Currency::deposit_into_existing(&profile.root_account, params.reward);
                                     }
                                 }
                             }
@@ -301,13 +285,14 @@ decl_module! {
         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)?;
 
+            <membership::members::Module<T>>::ensure_profile(member_id)?;
+
             // 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");
@@ -323,16 +308,25 @@ decl_module! {
         /// 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)?;
+            ensure!(<membership::members::Module<T>>::is_member_account(&sender), "members only can accept storage entry request");
 
-            if !Self::role_entry_requests()
+            // get member ids from requests that are controller by origin
+            let ids = 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");
-            }
+                .filter(|request| request.0 == actor_account && request.2 == role)
+                .map(|request| request.1)
+                .filter(|member_id|
+                    <membership::members::Module<T>>::ensure_profile(*member_id)
+                        .ok()
+                        .map_or(false, |profile| profile.root_account == sender || profile.controller_account == sender)
+                )
+                .collect::<Vec<_>>();
+
+            ensure!(!ids.is_empty(), "no role entry request matches");
+
+            // take first matching id
+            let member_id = ids[0];
 
-            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
@@ -371,9 +365,10 @@ decl_module! {
 
         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(&actor_account)?;
 
-            let actor = Self::ensure_actor_is_member(&actor_account, member_id)?;
+            let profile = <membership::members::Module<T>>::ensure_profile(actor.member_id)?;
+            ensure!(profile.root_account == sender || profile.controller_account == sender, "only member can unstake storage provider");
 
             let role_parameters = Self::ensure_role_parameters(actor.role)?;
 
@@ -382,7 +377,7 @@ decl_module! {
             Self::deposit_event(RawEvent::Unstaked(actor.account, actor.role));
         }
 
-        pub fn set_role_parameters(origin, role: Role, params: RoleParameters<T>) {
+        pub fn set_role_parameters(origin, role: Role, params: RoleParameters<BalanceOf<T>, T::BlockNumber>) {
             ensure_root(origin)?;
             let new_stake = params.min_stake.clone();
             <Parameters<T>>::insert(role, params);

+ 30 - 47
src/roles/mock.rs

@@ -2,7 +2,7 @@
 
 pub use super::actors;
 pub use crate::currency::GovernanceCurrency;
-use crate::traits::Members;
+use crate::membership;
 pub use srml_support::traits::Currency;
 pub use system;
 
@@ -20,6 +20,13 @@ impl_outer_origin! {
     pub enum Origin for Test {}
 }
 
+pub fn alice_id() -> u32 {
+    Members::member_ids_by_root_account_id(alice_account())[0]
+}
+pub fn alice_account() -> u64 {
+    1
+}
+
 // Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted.
 #[derive(Clone, PartialEq, Eq, Debug)]
 pub struct Test;
@@ -28,6 +35,7 @@ parameter_types! {
     pub const MaximumBlockWeight: u32 = 1024;
     pub const MaximumBlockLength: u32 = 2 * 1024;
     pub const AvailableBlockRatio: Perbill = Perbill::one();
+    pub const MinimumPeriod: u64 = 5;
 }
 
 impl system::Trait for Test {
@@ -54,6 +62,7 @@ parameter_types! {
     pub const CreationFee: u32 = 0;
     pub const TransactionBaseFee: u32 = 1;
     pub const TransactionByteFee: u32 = 0;
+    pub const InitialMembersBalance: u32 = 2000;
 }
 
 impl balances::Trait for Test {
@@ -77,57 +86,23 @@ 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 timestamp::Trait for Test {
+    type Moment = u64;
+    type OnTimestampSet = ();
+    type MinimumPeriod = MinimumPeriod;
 }
 
-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 membership::members::Trait for Test {
+    type Event = ();
+    type MemberId = u32;
+    type SubscriptionId = u32;
+    type PaidTermId = u32;
+    type ActorId = u32;
+    type InitialMembersBalance = InitialMembersBalance;
 }
 
 impl actors::Trait for Test {
     type Event = ();
-    type Members = MockMembers;
     type OnActorRemoved = ();
 }
 
@@ -136,13 +111,21 @@ impl actors::ActorRemoved<Test> for () {
 }
 
 pub fn initial_test_ext() -> runtime_io::TestExternalities {
-    let t = system::GenesisConfig::default()
+    let mut t = system::GenesisConfig::default()
         .build_storage::<Test>()
         .unwrap();
 
+    membership::members::GenesisConfig::<Test> {
+        default_paid_membership_fee: 0,
+        members: vec![(alice_account(), "alice".into(), "".into(), "".into())],
+    }
+    .assimilate_storage(&mut t)
+    .unwrap();
+
     t.into()
 }
 
 pub type System = system::Module<Test>;
 pub type Balances = balances::Module<Test>;
 pub type Actors = actors::Module<Test>;
+pub type Members = membership::members::Module<Test>;

+ 34 - 29
src/roles/tests.rs

@@ -5,14 +5,14 @@ use super::mock::*;
 use srml_support::*;
 
 fn init_storage_role() {
-    let roles: Vec<actors::Role> = vec![actors::Role::Storage];
+    let roles: Vec<actors::Role> = vec![actors::Role::StorageProvider];
     assert!(
         Actors::set_available_roles(system::RawOrigin::Root.into(), roles).is_ok(),
         ""
     );
 }
 
-fn init_storage_parmeters() -> actors::RoleParameters<Test> {
+fn init_storage_parmeters() -> actors::RoleParameters<u64, u64> {
     let params = actors::RoleParameters {
         // minium balance required to stake to enter a role
         min_stake: 100 as u64,
@@ -29,7 +29,7 @@ fn init_storage_parmeters() -> actors::RoleParameters<Test> {
     assert!(
         Actors::set_role_parameters(
             system::RawOrigin::Root.into(),
-            actors::Role::Storage,
+            actors::Role::StorageProvider,
             params.clone()
         )
         .is_ok(),
@@ -42,7 +42,10 @@ fn init_storage_parmeters() -> actors::RoleParameters<Test> {
 fn adding_roles() {
     initial_test_ext().execute_with(|| {
         init_storage_role();
-        assert_eq!(Actors::available_roles(), vec![actors::Role::Storage]);
+        assert_eq!(
+            Actors::available_roles(),
+            vec![actors::Role::StorageProvider]
+        );
     });
 }
 
@@ -51,7 +54,10 @@ fn adding_role_parameters() {
     initial_test_ext().execute_with(|| {
         init_storage_role();
         let params = init_storage_parmeters();
-        assert_eq!(Actors::parameters(actors::Role::Storage), Some(params));
+        assert_eq!(
+            Actors::parameters(actors::Role::StorageProvider),
+            Some(params)
+        );
     });
 }
 
@@ -72,8 +78,8 @@ fn make_entry_request() {
         assert!(
             Actors::role_entry_request(
                 Origin::signed(actor_account),
-                actors::Role::Storage,
-                MockMembers::alice_id()
+                actors::Role::StorageProvider,
+                alice_id()
             )
             .is_err(),
             ""
@@ -88,8 +94,8 @@ fn make_entry_request() {
         assert!(
             Actors::role_entry_request(
                 Origin::signed(actor_account),
-                actors::Role::Storage,
-                MockMembers::alice_id()
+                actors::Role::StorageProvider,
+                alice_id()
             )
             .is_ok(),
             ""
@@ -101,8 +107,8 @@ fn make_entry_request() {
         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.1, alice_id());
+        assert_eq!(request.2, actors::Role::StorageProvider);
         assert_eq!(
             request.3,
             starting_block
@@ -120,8 +126,8 @@ fn staking() {
 
         let request: actors::Request<Test> = (
             actor_account,
-            MockMembers::alice_id(),
-            actors::Role::Storage,
+            alice_id(),
+            actors::Role::StorageProvider,
             1000,
         );
 
@@ -130,8 +136,8 @@ fn staking() {
         let _ = Balances::deposit_creating(&actor_account, storage_params.min_stake);
 
         assert!(Actors::stake(
-            Origin::signed(MockMembers::alice_account()),
-            actors::Role::Storage,
+            Origin::signed(alice_account()),
+            actors::Role::StorageProvider,
             actor_account
         )
         .is_ok());
@@ -142,10 +148,10 @@ fn staking() {
         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);
+        let accounts_in_role = Actors::account_ids_by_role(actors::Role::StorageProvider);
         assert_eq!(accounts_in_role, vec![actor_account]);
 
-        let account_ids_for_member = Actors::account_ids_by_member_id(MockMembers::alice_id());
+        let account_ids_for_member = Actors::account_ids_by_member_id(alice_id());
         assert_eq!(account_ids_for_member, vec![actor_account]);
 
         let num_of_locks = Balances::locks(&actor_account).len();
@@ -160,36 +166,35 @@ fn unstaking() {
         let storage_params = init_storage_parmeters();
         let actor_account = 5;
 
-        assert!(
-            Actors::unstake(Origin::signed(MockMembers::alice_account()), actor_account).is_err()
-        );
+        assert!(Actors::unstake(Origin::signed(alice_account()), actor_account).is_err());
 
         let actor: actors::Actor<Test> = actors::Actor {
-            role: actors::Role::Storage,
-            member_id: MockMembers::alice_id(),
+            role: actors::Role::StorageProvider,
+            member_id: 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::AccountIdsByRole<Test>>::insert(
+            actors::Role::StorageProvider,
+            vec![actor_account],
+        );
+        <actors::AccountIdsByMemberId<Test>>::insert(alice_id(), vec![actor_account]);
         let current_block = 500;
 
         System::set_block_number(current_block);
-        assert!(
-            Actors::unstake(Origin::signed(MockMembers::alice_account()), actor_account).is_ok()
-        );
+        assert!(Actors::unstake(Origin::signed(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);
+        let accounts_in_role = Actors::account_ids_by_role(actors::Role::StorageProvider);
         assert_eq!(accounts_in_role.len(), 0);
 
-        let account_ids_for_member = Actors::account_ids_by_member_id(MockMembers::alice_id());
+        let account_ids_for_member = Actors::account_ids_by_member_id(alice_id());
         assert_eq!(account_ids_for_member.len(), 0);
 
         let lock = Balances::locks(&actor_account)[0].clone();

+ 23 - 19
src/storage/data_directory.rs

@@ -1,18 +1,17 @@
+use crate::membership;
 use crate::roles::actors;
 use crate::storage::data_object_type_registry::Trait as DOTRTrait;
-use crate::traits::{ContentIdExists, IsActiveDataObjectType, Members, Roles};
+use crate::traits::{ContentIdExists, IsActiveDataObjectType, Roles};
 use codec::{Codec, Decode, Encode};
 use rstd::prelude::*;
-use runtime_primitives::traits::{
-    MaybeDebug, MaybeDisplay, MaybeSerializeDebug, Member, SimpleArithmetic,
-};
+use runtime_primitives::traits::{MaybeSerialize, Member, SimpleArithmetic};
 use srml_support::{decl_event, decl_module, decl_storage, dispatch, ensure, Parameter};
 use system::{self, ensure_root, ensure_signed};
 
-pub trait Trait: timestamp::Trait + system::Trait + DOTRTrait + MaybeDebug {
+pub trait Trait: timestamp::Trait + system::Trait + DOTRTrait + membership::members::Trait {
     type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
 
-    type ContentId: Parameter + Member + MaybeSerializeDebug + MaybeDisplay + Copy + Ord + Default;
+    type ContentId: Parameter + Member + MaybeSerialize + Copy + Ord + Default;
 
     type SchemaId: Parameter
         + Member
@@ -20,10 +19,9 @@ pub trait Trait: timestamp::Trait + system::Trait + DOTRTrait + MaybeDebug {
         + Codec
         + Default
         + Copy
-        + MaybeSerializeDebug
+        + MaybeSerialize
         + PartialEq;
 
-    type Members: Members<Self>;
     type Roles: Roles<Self>;
     type IsActiveDataObjectType: IsActiveDataObjectType<Self>;
 }
@@ -99,10 +97,10 @@ pub struct ContentMetadata<T: Trait> {
 
 #[derive(Clone, Encode, Decode, PartialEq)]
 #[cfg_attr(feature = "std", derive(Debug))]
-pub struct ContentMetadataUpdate<T: Trait> {
-    pub children_ids: Option<Vec<T::ContentId>>,
+pub struct ContentMetadataUpdate<ContentId, SchemaId> {
+    pub children_ids: Option<Vec<ContentId>>,
     pub visibility: Option<ContentVisibility>,
-    pub schema: Option<T::SchemaId>,
+    pub schema: Option<SchemaId>,
     pub json: Option<Vec<u8>>,
 }
 
@@ -157,7 +155,7 @@ decl_module! {
             ipfs_content_id: Vec<u8>
         ) {
             let who = ensure_signed(origin)?;
-            ensure!(T::Members::is_active_member(&who), MSG_CREATOR_MUST_BE_MEMBER);
+            ensure!(<membership::members::Module<T>>::is_member_account(&who), MSG_CREATOR_MUST_BE_MEMBER);
 
             ensure!(T::IsActiveDataObjectType::is_active_data_object_type(&type_id),
                 MSG_DO_TYPE_MUST_BE_ACTIVE);
@@ -170,7 +168,7 @@ decl_module! {
                 Some(primary_liaison) => primary_liaison,
 
                 // Select liaison from staked roles if available
-                _ => T::Roles::random_account_for_role(actors::Role::Storage)?
+                _ => T::Roles::random_account_for_role(actors::Role::StorageProvider)?
             };
 
             // Let's create the entry then
@@ -204,10 +202,10 @@ decl_module! {
         fn add_metadata(
             origin,
             content_id: T::ContentId,
-            update: ContentMetadataUpdate<T>
+            update: ContentMetadataUpdate<T::ContentId, T::SchemaId>
         ) {
             let who = ensure_signed(origin)?;
-            ensure!(T::Members::is_active_member(&who),
+            ensure!(<membership::members::Module<T>>::is_member_account(&who),
                 "Only active members can add content metadata");
 
             ensure!(!<MetadataByContentId<T>>::exists(&content_id),
@@ -239,12 +237,12 @@ decl_module! {
         fn update_metadata(
             origin,
             content_id: T::ContentId,
-            update: ContentMetadataUpdate<T>
+            update: ContentMetadataUpdate<T::ContentId, T::SchemaId>
         ) {
             let who = ensure_signed(origin)?;
 
             // Even if origin is an owner of metadata, they stil need to be an active member.
-            ensure!(T::Members::is_active_member(&who),
+            ensure!(<membership::members::Module<T>>::is_member_account(&who),
                 "Only active members can update content metadata");
 
             let has_updates = update.schema.is_some() || update.json.is_some();
@@ -354,9 +352,15 @@ mod tests {
     #[test]
     fn succeed_adding_content() {
         with_default_mock_builder(|| {
+            let sender = 1 as u64;
             // Register a content with 1234 bytes of type 1, which should be recognized.
-            let res =
-                TestDataDirectory::add_content(Origin::signed(1), 1, 1234, 0, vec![1, 3, 3, 7]);
+            let res = TestDataDirectory::add_content(
+                Origin::signed(sender),
+                1,
+                1234,
+                0,
+                vec![1, 3, 3, 7],
+            );
             assert!(res.is_ok());
         });
     }

+ 5 - 6
src/storage/data_object_storage_registry.rs

@@ -1,13 +1,13 @@
 use crate::roles::actors;
 use crate::storage::data_directory::Trait as DDTrait;
-use crate::traits::{ContentHasStorage, ContentIdExists, Members, Roles};
+use crate::traits::{ContentHasStorage, ContentIdExists, Roles};
 use codec::{Codec, Decode, Encode};
 use rstd::prelude::*;
-use runtime_primitives::traits::{MaybeDebug, MaybeSerializeDebug, Member, SimpleArithmetic};
+use runtime_primitives::traits::{MaybeSerialize, Member, SimpleArithmetic};
 use srml_support::{decl_event, decl_module, decl_storage, ensure, Parameter};
 use system::{self, ensure_signed};
 
-pub trait Trait: timestamp::Trait + system::Trait + DDTrait + MaybeDebug {
+pub trait Trait: timestamp::Trait + system::Trait + DDTrait {
     type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
 
     // TODO deprecated
@@ -17,10 +17,9 @@ pub trait Trait: timestamp::Trait + system::Trait + DDTrait + MaybeDebug {
         + Codec
         + Default
         + Copy
-        + MaybeSerializeDebug
+        + MaybeSerialize
         + PartialEq;
 
-    type Members: Members<Self>;
     type Roles: Roles<Self>;
     type ContentIdExists: ContentIdExists<Self>;
 }
@@ -131,7 +130,7 @@ decl_module! {
             let who = ensure_signed(origin)?;
 
             // Check that the origin is a storage provider
-            ensure!(<T as Trait>::Roles::account_has_role(&who, actors::Role::Storage), MSG_ONLY_STORAGE_PROVIDER_MAY_CREATE_DOSR);
+            ensure!(<T as Trait>::Roles::account_has_role(&who, actors::Role::StorageProvider), MSG_ONLY_STORAGE_PROVIDER_MAY_CREATE_DOSR);
 
             // Content ID must exist
             ensure!(T::ContentIdExists::has_content(&cid), MSG_CID_NOT_FOUND);

+ 3 - 3
src/storage/data_object_type_registry.rs

@@ -1,11 +1,11 @@
 use crate::traits;
 use codec::{Codec, Decode, Encode};
 use rstd::prelude::*;
-use runtime_primitives::traits::{MaybeDebug, MaybeSerializeDebug, Member, SimpleArithmetic};
+use runtime_primitives::traits::{MaybeSerialize, Member, SimpleArithmetic};
 use srml_support::{decl_event, decl_module, decl_storage, Parameter};
 use system::ensure_root;
 
-pub trait Trait: system::Trait + MaybeDebug {
+pub trait Trait: system::Trait {
     type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
 
     type DataObjectTypeId: Parameter
@@ -14,7 +14,7 @@ pub trait Trait: system::Trait + MaybeDebug {
         + Codec
         + Default
         + Copy
-        + MaybeSerializeDebug
+        + MaybeSerialize
         + PartialEq;
 }
 

+ 0 - 168
src/storage/downloads.rs

@@ -1,168 +0,0 @@
-/*
- * XXX This module is not really supposed to be used this way, and therefore also lacks tests.
- *
- *     This is a straightforward implementation of the whitepaper's specs, and is intended to
- *     be iterated over. Please don't use it as-is.
- */
-
-use crate::storage::data_directory::Trait as DDTrait;
-use crate::storage::data_object_storage_registry::Trait as DOSRTrait;
-use crate::traits::{ContentHasStorage, ContentIdExists};
-use codec::{Codec, Decode, Encode};
-use rstd::prelude::*;
-use runtime_primitives::traits::{MaybeSerializeDebug, Member, SimpleArithmetic};
-use srml_support::{decl_event, decl_module, decl_storage, ensure, Parameter};
-use system::{self, ensure_signed};
-
-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
-        + MaybeSerializeDebug
-        + PartialEq;
-
-    type ContentHasStorage: ContentHasStorage<Self>;
-}
-
-static MSG_SESSION_NOT_FOUND: &str = "Download session with the given ID not found.";
-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: u32 = 1;
-
-#[derive(Clone, Encode, Decode, PartialEq)]
-#[cfg_attr(feature = "std", derive(Debug))]
-pub enum DownloadState {
-    Started,
-    Ended,
-}
-
-impl Default for DownloadState {
-    fn default() -> Self {
-        DownloadState::Started
-    }
-}
-
-#[derive(Clone, Encode, Decode, PartialEq)]
-#[cfg_attr(feature = "std", derive(Debug))]
-pub struct DownloadSession<T: Trait> {
-    pub content_id: <T as DDTrait>::ContentId,
-    pub consumer: T::AccountId,
-    pub distributor: T::AccountId,
-    // TODO use BlockAndTime instead
-    pub initiated_at_block: T::BlockNumber,
-    pub initiated_at_time: T::Moment,
-    pub state: DownloadState,
-    pub transmitted_bytes: u64,
-}
-
-decl_storage! {
-    trait Store for Module<T: Trait> as DownloadSessions {
-        // Start at this value
-        pub FirstDownloadSessionId get(first_download_session_id) config(first_download_session_id): T::DownloadSessionId = T::DownloadSessionId::from(DEFAULT_FIRST_DOWNLOAD_SESSION_ID);
-
-        // Increment
-        pub NextDownloadSessionId get(next_download_session_id) build(|config: &GenesisConfig<T>| config.first_download_session_id): T::DownloadSessionId = T::DownloadSessionId::from(DEFAULT_FIRST_DOWNLOAD_SESSION_ID);
-
-        // Mapping of Data object types
-        pub DownloadSessions get(download_sessions): map T::DownloadSessionId => Option<DownloadSession<T>>;
-    }
-}
-
-decl_event! {
-    pub enum Event<T> where
-        <T as DDTrait>::ContentId
-    {
-        // We send the content ID *only* because while we already leak download
-        // session information by storing it on the public chain, there's no
-        // need to advertise each download even more.
-        DownloadStarted(ContentId),
-
-        // Transmitted size is included in the ended event.
-        DownloadEnded(ContentId, u64),
-    }
-}
-
-decl_module! {
-    pub struct Module<T: Trait> for enum Call where origin: T::Origin {
-        fn deposit_event() = default;
-
-        // 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, 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)?;
-
-            // There has to be a storage relationship for the content ID and storage provider.
-            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 {
-                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_bytes: 0u64,
-            };
-
-            <DownloadSessions<T>>::insert(new_id, session);
-            <NextDownloadSessionId<T>>::mutate(|n| { *n += T::DownloadSessionId::from(1); });
-
-            // Fire off event
-            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_bytes: u64)
-        {
-            // Origin can be anyone, it doesn't have to be a member.
-            let who = ensure_signed(origin)?;
-
-            // Get session
-            let mut session = Self::download_sessions(session_id).ok_or(MSG_SESSION_NOT_FOUND)?;
-
-            // Ensure that the session hasn't ended yet.
-            ensure!(session.state == DownloadState::Started, MSG_SESSION_HAS_ENDED);
-
-            // Ensure that the origin is the consumer
-            ensure!(session.consumer == who, MSG_CONSUMER_REQUIRED);
-
-            // Ensure that the new transmitted size is larger than the old one
-            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.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_bytes = transmitted_bytes;
-            session.state = match finished {
-                true => DownloadState::Ended,
-                false => DownloadState::Started,
-            };
-            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(content_id, transmitted_bytes));
-            }
-        }
-    }
-}

+ 21 - 21
src/storage/mock.rs

@@ -2,6 +2,8 @@
 
 pub use super::{data_directory, data_object_storage_registry, data_object_type_registry};
 pub use crate::currency::GovernanceCurrency;
+use crate::membership;
+use crate::membership::members;
 use crate::roles::actors;
 use crate::traits;
 pub use system;
@@ -27,6 +29,7 @@ impl_outer_event! {
         data_object_storage_registry<T>,
         actors<T>,
         balances<T>,
+        members<T>,
     }
 }
 
@@ -38,23 +41,6 @@ pub const TEST_FIRST_METADATA_ID: u64 = 4000;
 pub const TEST_MOCK_LIAISON: u64 = 0xd00du64;
 pub const TEST_MOCK_EXISTING_CID: u64 = 42;
 
-pub struct MockMembers {}
-impl<T: system::Trait> traits::Members<T> for MockMembers {
-    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 MockRoles {}
 impl traits::Roles<Test> for MockRoles {
     fn is_role_account(_account_id: &<Test as system::Trait>::AccountId) -> bool {
@@ -153,6 +139,7 @@ parameter_types! {
     pub const CreationFee: u32 = 0;
     pub const TransactionBaseFee: u32 = 1;
     pub const TransactionByteFee: u32 = 0;
+    pub const InitialMembersBalance: u32 = 2000;
 }
 
 impl balances::Trait for Test {
@@ -184,7 +171,6 @@ impl data_object_type_registry::Trait for Test {
 impl data_directory::Trait for Test {
     type Event = MetaEvent;
     type ContentId = u64;
-    type Members = MockMembers;
     type Roles = MockRoles;
     type IsActiveDataObjectType = AnyDataObjectTypeIsActive;
     type SchemaId = u64;
@@ -193,14 +179,21 @@ impl data_directory::Trait for Test {
 impl data_object_storage_registry::Trait for Test {
     type Event = MetaEvent;
     type DataObjectStorageRelationshipId = u64;
-    type Members = MockMembers;
     type Roles = MockRoles;
     type ContentIdExists = MockContent;
 }
 
+impl members::Trait for Test {
+    type Event = MetaEvent;
+    type MemberId = u32;
+    type SubscriptionId = u32;
+    type PaidTermId = u32;
+    type ActorId = u32;
+    type InitialMembersBalance = InitialMembersBalance;
+}
+
 impl actors::Trait for Test {
     type Event = MetaEvent;
-    type Members = MockMembers;
     type OnActorRemoved = ();
 }
 
@@ -260,6 +253,13 @@ impl ExtBuilder {
         .assimilate_storage(&mut t)
         .unwrap();
 
+        membership::members::GenesisConfig::<Test> {
+            default_paid_membership_fee: 0,
+            members: vec![(1, "alice".into(), "".into(), "".into())],
+        }
+        .assimilate_storage(&mut t)
+        .unwrap();
+
         t.into()
     }
 }
@@ -280,7 +280,7 @@ pub fn with_default_mock_builder<R, F: FnOnce() -> R>(f: F) -> R {
         .first_metadata_id(TEST_FIRST_METADATA_ID)
         .build()
         .execute_with(|| {
-            let roles: Vec<actors::Role> = vec![actors::Role::Storage];
+            let roles: Vec<actors::Role> = vec![actors::Role::StorageProvider];
             assert!(
                 TestActors::set_available_roles(system::RawOrigin::Root.into(), roles).is_ok(),
                 ""

+ 0 - 1
src/storage/mod.rs

@@ -1,6 +1,5 @@
 pub mod data_directory;
 pub mod data_object_storage_registry;
 pub mod data_object_type_registry;
-pub mod downloads;
 
 mod mock;

+ 0 - 34
src/traits.rs

@@ -1,41 +1,7 @@
 use crate::roles::actors;
 use crate::storage::{data_directory, data_object_storage_registry, data_object_type_registry};
-use codec::Codec;
-use runtime_primitives::traits::{MaybeSerializeDebug, Member, SimpleArithmetic};
-use srml_support::Parameter;
 use system;
 
-// Members
-pub trait Members<T: system::Trait> {
-    type Id: Parameter
-        + Member
-        + SimpleArithmetic
-        + Codec
-        + Default
-        + Copy
-        + MaybeSerializeDebug
-        + PartialEq;
-
-    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>;
-}
-
-impl<T: system::Trait> Members<T> for () {
-    type Id = u32;
-    fn is_active_member(_account_id: &T::AccountId) -> bool {
-        false
-    }
-    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")
-    }
-}
-
 // Roles
 pub trait Roles<T: system::Trait> {
     fn is_role_account(account_id: &T::AccountId) -> bool;