Browse Source

content-directory: channel deletion and tests

Mokhtar Naamani 4 years ago
parent
commit
b8b3c825da

+ 2 - 0
Cargo.lock

@@ -3311,7 +3311,9 @@ version = "3.0.0"
 dependencies = [
  "frame-support",
  "frame-system",
+ "pallet-balances",
  "pallet-common",
+ "pallet-timestamp",
  "parity-scale-codec",
  "serde",
  "sp-arithmetic 2.0.0-rc4",

+ 2 - 0
runtime-modules/content/Cargo.toml

@@ -17,6 +17,8 @@ common = { package = 'pallet-common', default-features = false, path = '../commo
 [dev-dependencies]
 sp-io = { package = 'sp-io', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
 sp-core = { package = 'sp-core', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
+balances = { package = 'pallet-balances', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
+pallet-timestamp = { package = 'pallet-timestamp', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
 
 [features]
 default = ['std']

+ 4 - 1
runtime-modules/content/src/errors.rs

@@ -42,7 +42,10 @@ decl_error! {
         BadOrigin,
 
         /// Operation Cannot be perfomed with this Actor
-        OperationDeniedForActor,
+        ActorNotAuthorized,
+
+        /// This content actor cannot own a channel
+        ActorCannotOwnChannel,
 
         /// Channel Category Does not Exist.
         ChannelCategoryDoesNotExist,

+ 40 - 9
runtime-modules/content/src/lib.rs

@@ -2,8 +2,8 @@
 #![cfg_attr(not(feature = "std"), no_std)]
 #![recursion_limit = "256"]
 
-// #[cfg(test)]
-// mod tests;
+#[cfg(test)]
+mod tests;
 
 mod errors;
 mod permissions;
@@ -141,6 +141,16 @@ pub enum ChannelOwner<MemberId, CuratorGroupId, DAOId> {
     Dao(DAOId),
 }
 
+// simplification type
+pub(crate) type ActorToChannelOwnerResult<T> = Result<
+    ChannelOwner<
+        <T as MembershipTypes>::MemberId,
+        <T as ContentActorAuthenticator>::CuratorGroupId,
+        <T as StorageOwnership>::DAOId,
+    >,
+    Error<T>,
+>;
+
 // Default trait implemented only because its used in a Channel which needs to implement a Default trait
 // since it is a StorageValue.
 impl<MemberId: Default, CuratorGroupId, DAOId> Default
@@ -658,15 +668,16 @@ decl_module! {
         pub fn create_channel(
             origin,
             actor: ContentActor<T::CuratorGroupId, T::CuratorId, T::MemberId>,
-            owner: ChannelOwner<T::MemberId, T::CuratorGroupId, T::DAOId>,
             params: ChannelCreationParameters<ContentParameters<T>, T::AccountId>,
         ) -> DispatchResult {
-            ensure_actor_authorized_to_create_update_delete_channel::<T>(
+            ensure_actor_authorized_to_create_channel::<T>(
                 origin,
                 &actor,
-                &owner,
             )?;
 
+            // The channel owner will be..
+            let channel_owner = Self::actor_to_channel_owner(&actor)?;
+
             // Pick out the assets to be uploaded to storage system
             let content_parameters: Vec<ContentParameters<T>> = Self::pick_content_parameters_from_assets(&params.assets);
 
@@ -703,7 +714,7 @@ decl_module! {
             NextChannelId::<T>::mutate(|id| *id += T::ChannelId::one());
 
             let channel: Channel<T> = ChannelRecord {
-                owner: owner.clone(),
+                owner: channel_owner.clone(),
                 videos: vec![],
                 playlists: vec![],
                 series: vec![],
@@ -712,7 +723,7 @@ decl_module! {
             };
             ChannelById::<T>::insert(channel_id, channel.clone());
 
-            if let ChannelOwner::CuratorGroup(curator_group_id) = owner {
+            if let ChannelOwner::CuratorGroup(curator_group_id) = channel_owner {
                 Self::increment_number_of_channels_owned_by_curator_group(curator_group_id);
             }
 
@@ -731,7 +742,7 @@ decl_module! {
             // check that channel exists
             let channel = Self::ensure_channel_exists(&channel_id)?;
 
-            ensure_actor_authorized_to_create_update_delete_channel::<T>(
+            ensure_actor_authorized_to_update_or_delete_channel::<T>(
                 origin,
                 &actor,
                 &channel.owner,
@@ -792,7 +803,7 @@ decl_module! {
             // check that channel exists
             let channel = Self::ensure_channel_exists(&channel_id)?;
 
-            ensure_actor_authorized_to_create_update_delete_channel::<T>(
+            ensure_actor_authorized_to_update_or_delete_channel::<T>(
                 origin,
                 &actor,
                 &channel.owner,
@@ -1204,6 +1215,26 @@ impl<T: Trait> Module<T> {
             })
             .collect()
     }
+
+    fn actor_to_channel_owner(
+        actor: &ContentActor<T::CuratorGroupId, T::CuratorId, T::MemberId>,
+    ) -> ActorToChannelOwnerResult<T> {
+        match actor {
+            // Lead should use their member or curator role to create channels
+            ContentActor::Lead => Err(Error::<T>::ActorCannotOwnChannel),
+            ContentActor::Curator(
+                curator_group_id,
+                _curator_id
+            ) => {
+                Ok(ChannelOwner::CuratorGroup(*curator_group_id))
+            }
+            ContentActor::Member(member_id) => {
+                Ok(ChannelOwner::Member(*member_id))
+            }
+            // TODO:
+            // ContentActor::Dao(id) => Ok(ChannelOwner::Dao(id)),
+        }
+    }
 }
 
 // Some initial config for the module on runtime upgrade

+ 39 - 7
runtime-modules/content/src/permissions/mod.rs

@@ -87,7 +87,39 @@ pub fn ensure_is_lead<T: Trait>(origin: T::Origin) -> DispatchResult {
     Ok(ensure_lead_auth_success::<T>(&account_id)?)
 }
 
-pub fn ensure_actor_authorized_to_create_update_delete_channel<T: Trait>(
+pub fn ensure_actor_authorized_to_create_channel<T: Trait>(
+    origin: T::Origin,
+    actor: &ContentActor<T::CuratorGroupId, T::CuratorId, T::MemberId>,
+) -> DispatchResult {
+    match actor {
+        // Lead should use their member or curator role to create or update channels.
+        ContentActor::Lead => Err(Error::<T>::ActorCannotOwnChannel),
+        ContentActor::Curator(curator_group_id, curator_id) => {
+            let sender = ensure_signed(origin)?;
+
+            // Authorize curator, performing all checks to ensure curator can act
+            CuratorGroup::<T>::perform_curator_in_group_auth(
+                curator_id,
+                curator_group_id,
+                &sender,
+            )?;
+
+            Ok(())
+        }
+        ContentActor::Member(member_id) => {
+            let sender = ensure_signed(origin)?;
+
+            ensure_member_auth_success::<T>(member_id, &sender)?;
+
+            Ok(())
+        }
+        // TODO:
+        // ContentActor::Dao(_daoId) => ...,
+    }?;
+    Ok(())
+}
+
+pub fn ensure_actor_authorized_to_update_or_delete_channel<T: Trait>(
     origin: T::Origin,
     actor: &ContentActor<T::CuratorGroupId, T::CuratorId, T::MemberId>,
     owner: &ChannelOwner<T::MemberId, T::CuratorGroupId, T::DAOId>,
@@ -95,8 +127,7 @@ pub fn ensure_actor_authorized_to_create_update_delete_channel<T: Trait>(
     // Authenticate actor against channel owner
     // Only Owner of a channel can update and delete it.
     match actor {
-        // Lead should use their member or curator role to create or update channels.
-        ContentActor::Lead => Err(Error::<T>::OperationDeniedForActor),
+        ContentActor::Lead => Err(Error::<T>::ActorNotAuthorized),
         ContentActor::Curator(curator_group_id, curator_id) => {
             let sender = ensure_signed(origin)?;
 
@@ -107,10 +138,10 @@ pub fn ensure_actor_authorized_to_create_update_delete_channel<T: Trait>(
                 &sender,
             )?;
 
-            // Ensure curator group specified as channel owner is same as authenticated group id.
+            // Ensure curator group is the channel owner.
             ensure!(
                 *owner == ChannelOwner::CuratorGroup(*curator_group_id),
-                Error::<T>::OperationDeniedForActor
+                Error::<T>::ActorNotAuthorized
             );
 
             Ok(())
@@ -120,15 +151,16 @@ pub fn ensure_actor_authorized_to_create_update_delete_channel<T: Trait>(
 
             ensure_member_auth_success::<T>(member_id, &sender)?;
 
+            // Ensure the member is the channel owner.
             ensure!(
                 *owner == ChannelOwner::Member(*member_id),
-                Error::<T>::OperationDeniedForActor
+                Error::<T>::ActorNotAuthorized
             );
 
             Ok(())
         }
         // TODO:
-        // ContentActor::Dao(_daoId) => Error::<T>::OperationDeniedForActor,
+        // ContentActor::Dao(_daoId) => ...,
     }?;
     Ok(())
 }

+ 23 - 0
runtime-modules/content/src/tests/channels.rs

@@ -0,0 +1,23 @@
+#![cfg(test)]
+
+use super::mock::*;
+use crate::*;
+use frame_support::{assert_err, assert_ok};
+
+#[test]
+fn lead_cannot_create_channel() {
+    with_default_mock_builder(|| {
+        assert_err!(
+            Content::create_channel(
+                Origin::signed(FIRST_MEMBER_ORIGIN),
+                ContentActor::Lead,
+                ChannelCreationParameters {
+                    assets: vec![],
+                    meta: vec![],
+                    reward_account: None,
+                }
+            ),
+            Error::<Test>::ActorCannotOwnChannel
+        );
+    })
+}

+ 278 - 0
runtime-modules/content/src/tests/mock.rs

@@ -0,0 +1,278 @@
+#![cfg(test)]
+
+use crate::*;
+
+// use frame_support::storage::StorageMap;
+// use frame_support::traits::{OnFinalize, OnInitialize};
+use frame_support::{impl_outer_event, impl_outer_origin, parameter_types};
+use sp_core::H256;
+use sp_runtime::{
+    testing::Header,
+    traits::{BlakeTwo256, IdentityLookup},
+    Perbill,
+};
+
+use crate::ContentActorAuthenticator;
+use crate::Trait;
+use common::currency::GovernanceCurrency;
+use common::storage::StorageSystem;
+
+pub type CuratorId = <Test as ContentActorAuthenticator>::CuratorId;
+pub type CuratorGroupId = <Test as ContentActorAuthenticator>::CuratorGroupId;
+pub type MemberId = <Test as MembershipTypes>::MemberId;
+pub type ChannelId = <Test as StorageOwnership>::ChannelId;
+pub type DAOId = <Test as StorageOwnership>::DAOId;
+
+/// Origins
+
+pub const LEAD_ORIGIN: u64 = 1;
+
+pub const FIRST_CURATOR_ORIGIN: u64 = 2;
+pub const SECOND_CURATOR_ORIGIN: u64 = 3;
+
+pub const FIRST_MEMBER_ORIGIN: u64 = 4;
+pub const SECOND_MEMBER_ORIGIN: u64 = 5;
+pub const UNKNOWN_ORIGIN: u64 = 7777;
+
+// Members range from MemberId 1 to 10
+pub const MEMBERS_COUNT: MemberId = 10;
+
+/// Runtime Id's
+
+pub const FIRST_CURATOR_ID: CuratorId = 1;
+pub const SECOND_CURATOR_ID: CuratorId = 2;
+
+pub const FIRST_CURATOR_GROUP_ID: CuratorGroupId = 1;
+pub const SECOND_CURATOR_GROUP_ID: CuratorGroupId = 2;
+
+pub const FIRST_MEMBER_ID: MemberId = 1;
+pub const SECOND_MEMBER_ID: MemberId = 2;
+
+impl_outer_origin! {
+    pub enum Origin for Test {}
+}
+
+mod content {
+    pub use crate::Event;
+}
+
+impl_outer_event! {
+    pub enum MetaEvent for Test {
+        content<T>,
+        system<T>,
+        balances<T>,
+    }
+}
+
+#[derive(Clone, PartialEq, Eq, Debug)]
+pub struct Test;
+
+parameter_types! {
+    pub const BlockHashCount: u64 = 250;
+    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 {
+    type BaseCallFilter = ();
+    type Origin = Origin;
+    type Call = ();
+    type Index = u64;
+    type BlockNumber = u64;
+    type Hash = H256;
+    type Hashing = BlakeTwo256;
+    type AccountId = u64;
+    type Lookup = IdentityLookup<Self::AccountId>;
+    type Header = Header;
+    type Event = MetaEvent;
+    type BlockHashCount = BlockHashCount;
+    type MaximumBlockWeight = MaximumBlockWeight;
+    type DbWeight = ();
+    type BlockExecutionWeight = ();
+    type ExtrinsicBaseWeight = ();
+    type MaximumExtrinsicWeight = ();
+    type MaximumBlockLength = MaximumBlockLength;
+    type AvailableBlockRatio = AvailableBlockRatio;
+    type Version = ();
+    type ModuleToIndex = ();
+    type AccountData = balances::AccountData<u64>;
+    type OnNewAccount = ();
+    type OnKilledAccount = ();
+}
+
+impl pallet_timestamp::Trait for Test {
+    type Moment = u64;
+    type OnTimestampSet = ();
+    type MinimumPeriod = MinimumPeriod;
+}
+
+impl common::MembershipTypes for Test {
+    type MemberId = u64;
+    type ActorId = u64;
+}
+
+impl common::StorageOwnership for Test {
+    type ChannelId = u64;
+    type DAOId = u64;
+    type ContentId = u64;
+    type DataObjectTypeId = u64;
+}
+
+parameter_types! {
+    pub const ExistentialDeposit: u32 = 0;
+}
+
+impl balances::Trait for Test {
+    type Balance = u64;
+    type DustRemoval = ();
+    type Event = MetaEvent;
+    type ExistentialDeposit = ExistentialDeposit;
+    type AccountStore = System;
+}
+
+impl GovernanceCurrency for Test {
+    type Currency = balances::Module<Self>;
+}
+
+impl ContentActorAuthenticator for Test {
+    type CuratorId = u64;
+    type CuratorGroupId = u64;
+
+    fn is_lead(account_id: &Self::AccountId) -> bool {
+        let lead_account_id = ensure_signed(Origin::signed(LEAD_ORIGIN)).unwrap();
+        *account_id == lead_account_id
+    }
+
+    fn is_curator(curator_id: &Self::CuratorId, account_id: &Self::AccountId) -> bool {
+        let first_curator_account_id = ensure_signed(Origin::signed(FIRST_CURATOR_ORIGIN)).unwrap();
+        let second_curator_account_id =
+            ensure_signed(Origin::signed(SECOND_CURATOR_ORIGIN)).unwrap();
+        (first_curator_account_id == *account_id && FIRST_CURATOR_ID == *curator_id)
+            || (second_curator_account_id == *account_id && SECOND_CURATOR_ID == *curator_id)
+    }
+
+    fn is_member(member_id: &Self::MemberId, account_id: &Self::AccountId) -> bool {
+        let unknown_member_account_id = ensure_signed(Origin::signed(UNKNOWN_ORIGIN)).unwrap();
+        *member_id < MEMBERS_COUNT && unknown_member_account_id != *account_id
+    }
+}
+
+pub struct MockStorageSystem {}
+
+// Anyone can upload without restriction
+impl StorageSystem<Test> for MockStorageSystem {
+    fn atomically_add_content(
+        _owner: StorageObjectOwner<Test>,
+        _content_parameters: Vec<ContentParameters<Test>>,
+    ) -> DispatchResult {
+        Ok(())
+    }
+
+    fn can_add_content(
+        _owner: StorageObjectOwner<Test>,
+        _content_parameters: Vec<ContentParameters<Test>>,
+    ) -> DispatchResult {
+        Ok(())
+    }
+}
+
+parameter_types! {
+    pub const MaxNumberOfCuratorsPerGroup: u32 = 10;
+    pub const ChannelOwnershipPaymentEscrowId: [u8; 8] = *b"12345678";
+}
+
+impl Trait for Test {
+    /// The overarching event type.
+    type Event = MetaEvent;
+
+    /// Channel Transfer Payments Escrow Account seed for ModuleId to compute deterministic AccountId
+    type ChannelOwnershipPaymentEscrowId = ChannelOwnershipPaymentEscrowId;
+
+    /// Type of identifier for Videos
+    type VideoId = u64;
+
+    /// Type of identifier for Video Categories
+    type VideoCategoryId = u64;
+
+    /// Type of identifier for Channel Categories
+    type ChannelCategoryId = u64;
+
+    /// Type of identifier for Playlists
+    type PlaylistId = u64;
+
+    /// Type of identifier for Persons
+    type PersonId = u64;
+
+    /// Type of identifier for Channels
+    type SeriesId = u64;
+
+    /// Type of identifier for Channel transfer requests
+    type ChannelOwnershipTransferRequestId = u64;
+
+    /// The maximum number of curators per group constraint
+    type MaxNumberOfCuratorsPerGroup = MaxNumberOfCuratorsPerGroup;
+
+    // Type that handles asset uploads to storage system
+    type StorageSystem = MockStorageSystem;
+}
+
+pub type System = system::Module<Test>;
+pub type Content = Module<Test>;
+// #[derive (Default)]
+pub struct ExtBuilder {
+    next_channel_category_id: u64,
+    next_channel_id: u64,
+    next_video_category_id: u64,
+    next_video_id: u64,
+    next_playlist_id: u64,
+    next_person_id: u64,
+    next_series_id: u64,
+    next_channel_transfer_request_id: u64,
+    next_curator_group_id: u64,
+}
+
+impl Default for ExtBuilder {
+    fn default() -> Self {
+        Self {
+            next_channel_category_id: 1,
+            next_channel_id: 1,
+            next_video_category_id: 1,
+            next_video_id: 1,
+            next_playlist_id: 1,
+            next_person_id: 1,
+            next_series_id: 1,
+            next_channel_transfer_request_id: 1,
+            next_curator_group_id: 1,
+        }
+    }
+}
+
+impl ExtBuilder {
+    pub fn build(self) -> sp_io::TestExternalities {
+        let mut t = system::GenesisConfig::default()
+            .build_storage::<Test>()
+            .unwrap();
+
+        GenesisConfig::<Test> {
+            next_channel_category_id: self.next_channel_category_id,
+            next_channel_id: self.next_channel_id,
+            next_video_category_id: self.next_video_category_id,
+            next_video_id: self.next_video_id,
+            next_playlist_id: self.next_playlist_id,
+            next_person_id: self.next_person_id,
+            next_series_id: self.next_series_id,
+            next_channel_transfer_request_id: self.next_channel_transfer_request_id,
+            next_curator_group_id: self.next_curator_group_id,
+        }
+        .assimilate_storage(&mut t)
+        .unwrap();
+
+        t.into()
+    }
+}
+
+pub fn with_default_mock_builder<R, F: FnOnce() -> R>(f: F) -> R {
+    ExtBuilder::default().build().execute_with(|| f())
+}

+ 4 - 0
runtime-modules/content/src/tests/mod.rs

@@ -0,0 +1,4 @@
+#![cfg(test)]
+
+mod channels;
+mod mock;