Browse Source

Content: move vnft-auction runtime structures into content pallet

iorveth 3 years ago
parent
commit
491a131403

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

@@ -13,6 +13,7 @@ sp-arithmetic = { package = 'sp-arithmetic', default-features = false, git = 'ht
 codec = { package = 'parity-scale-codec', version = '1.3.4', default-features = false, features = ['derive'] }
 serde = {version = '1.0.101', features = ['derive'], optional = true}
 common = { package = 'pallet-common', default-features = false, path = '../common'}
+pallet-timestamp = { package = 'pallet-timestamp', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '2cd20966cc09b059817c3ebe12fc130cdd850d62'}
 
 [dev-dependencies]
 sp-io = { package = 'sp-io', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '2cd20966cc09b059817c3ebe12fc130cdd850d62'}

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

@@ -62,6 +62,81 @@ decl_error! {
         VideoInSeason,
 
         /// Curators can only censor non-curator group owned channels
-        CannotCensoreCuratorGroupOwnedChannels
+        CannotCensoreCuratorGroupOwnedChannels,
+
+        // Auction Errors
+        // ---------------------
+
+        /// Auction does not exist
+        AuctionDoesNotExist,
+
+        /// Vnft auction for given video_id have been started already
+        AuctionAlreadyStarted,
+
+        /// vNFT does not exist
+        VNFTDoesNotExist,
+
+        /// vNFT for given video id already exists
+        VNFTAlreadyExists,
+
+        /// Overflow or underflow error happened
+        OverflowOrUnderflowHappened,
+
+        /// Actor origin authorization error happened
+        ActorOriginAuthError,
+
+        /// Actor not authorized to issue nft
+        ActorNotAuthorizedToIssueNft,
+
+        /// Given origin does not own vnft
+        DoesNotOwnVNFT,
+
+        /// Royalty Upper Bound Exceeded
+        RoyaltyUpperBoundExceeded,
+
+        /// Royalty Lower Bound Exceeded
+        RoyaltyLowerBoundExceeded,
+
+        /// Round time upper bound exceeded
+        RoundTimeUpperBoundExceeded,
+
+        /// Round time lower bound exceeded
+        RoundTimeLowerBoundExceeded,
+
+        /// Starting price upper bound exceeded
+        StartingPriceUpperBoundExceeded,
+
+        /// Starting price lower bound exceeded
+        StartingPriceLowerBoundExceeded,
+
+        /// Auction bid step upper bound exceeded
+        AuctionBidStepUpperBoundExceeded,
+
+        /// Auction bid step lower bound exceeded
+        AuctionBidStepLowerBoundExceeded,
+
+        /// Insufficient balance
+        InsufficientBalance,
+
+        /// Invalid bid
+        InvalidBid,
+
+        /// Already active auction cannot be cancelled
+        ActionIsAlreadyActive,
+
+        /// Provided actor is not an auction auctioneer.
+        ActorIsNotAnAuctioneer,
+
+        /// Can not create aution for vNFT, which is already locked for transfer
+        PendingTransferFound,
+
+        /// No pending transfers for given vNFT
+        PendingTransferDoesNotExist,
+
+        /// Pending transfer for given vNFT already exists
+        PendingAlreadyExists,
+
+        // No incoming transfers for given vnft origin
+        NoIncomingTransfers,
     }
 }

+ 36 - 360
runtime-modules/content/src/lib.rs

@@ -7,9 +7,13 @@ mod tests;
 
 mod errors;
 mod permissions;
+mod types;
+mod vnft_auction;
 
 pub use errors::*;
 pub use permissions::*;
+pub use types::*;
+pub use vnft_auction::*;
 
 use core::hash::Hash;
 
@@ -24,6 +28,7 @@ use frame_system::ensure_signed;
 pub use serde::{Deserialize, Serialize};
 use sp_arithmetic::traits::{BaseArithmetic, One, Zero};
 use sp_runtime::traits::{MaybeSerializeDeserialize, Member};
+pub use sp_runtime::Perbill;
 use sp_std::collections::btree_set::BTreeSet;
 use sp_std::vec;
 use sp_std::vec::Vec;
@@ -39,21 +44,6 @@ pub use common::{
     MembershipTypes, StorageOwnership, Url,
 };
 
-pub(crate) type ContentId<T> = <T as StorageOwnership>::ContentId;
-
-pub(crate) type DataObjectTypeId<T> = <T as StorageOwnership>::DataObjectTypeId;
-
-pub(crate) type ContentParameters<T> = ContentParametersRecord<ContentId<T>, DataObjectTypeId<T>>;
-
-pub(crate) type StorageObjectOwner<T> = StorageObjectOwnerRecord<
-    <T as MembershipTypes>::MemberId,
-    <T as StorageOwnership>::ChannelId,
-    <T as StorageOwnership>::DAOId,
->;
-
-/// Type, used in diffrent numeric constraints representations
-pub type MaxNumber = u32;
-
 /// A numeric identifier trait
 pub trait NumericIdentifier:
     Parameter
@@ -77,6 +67,7 @@ impl NumericIdentifier for u64 {}
 /// Module configuration trait for Content Directory Module
 pub trait Trait:
     frame_system::Trait
+    + pallet_timestamp::Trait
     + ContentActorAuthenticator
     + Clone
     + StorageOwnership
@@ -117,352 +108,13 @@ pub trait Trait:
     type StorageSystem: StorageSystem<Self>;
 }
 
-/// Specifies how a new asset will be provided on creating and updating
-/// Channels, Videos, Series and Person
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)]
-pub enum NewAsset<ContentParameters> {
-    /// Upload to the storage frame_system
-    Upload(ContentParameters),
-    /// Multiple url strings pointing at an asset
-    Urls(Vec<Url>),
-}
-
-/// The owner of a channel, is the authorized "actor" that can update
-/// or delete or transfer a channel and its contents.
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)]
-pub enum ChannelOwner<MemberId, CuratorGroupId, DAOId> {
-    /// A Member owns the channel
-    Member(MemberId),
-    /// A specific curation group owns the channel
-    CuratorGroup(CuratorGroupId),
-    // Native DAO owns the channel
-    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
-    for ChannelOwner<MemberId, CuratorGroupId, DAOId>
-{
-    fn default() -> Self {
-        ChannelOwner::Member(MemberId::default())
-    }
-}
-
-/// A category which channels can belong to.
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
-pub struct ChannelCategory {
-    // No runtime information is currently stored for a Category.
-}
-
-/// Information on the category being created.
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
-pub struct ChannelCategoryCreationParameters {
-    /// Metadata for the category.
-    meta: Vec<u8>,
-}
-
-/// Information on the category being updated.
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
-pub struct ChannelCategoryUpdateParameters {
-    // as this is the only field it is not an Option
-    /// Metadata update for the category.
-    new_meta: Vec<u8>,
-}
-
-/// Type representing an owned channel which videos, playlists, and series can belong to.
-/// If a channel is deleted, all videos, playlists and series will also be deleted.
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
-pub struct ChannelRecord<MemberId, CuratorGroupId, DAOId, AccountId, VideoId, PlaylistId, SeriesId>
-{
-    /// The owner of a channel
-    owner: ChannelOwner<MemberId, CuratorGroupId, DAOId>,
-    /// The videos under this channel
-    pub videos: Vec<VideoId>,
-    /// The playlists under this channel
-    playlists: Vec<PlaylistId>,
-    /// The series under this channel
-    series: Vec<SeriesId>,
-    /// If curators have censored this channel or not
-    is_censored: bool,
-    /// Reward account where revenue is sent if set.
-    reward_account: Option<AccountId>,
-}
-
-// Channel alias type for simplification.
-pub type Channel<T> = ChannelRecord<
-    <T as MembershipTypes>::MemberId,
-    <T as ContentActorAuthenticator>::CuratorGroupId,
-    <T as StorageOwnership>::DAOId,
-    <T as frame_system::Trait>::AccountId,
-    <T as Trait>::VideoId,
-    <T as Trait>::PlaylistId,
-    <T as Trait>::SeriesId,
->;
-
-/// A request to buy a channel by a new ChannelOwner.
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
-pub struct ChannelOwnershipTransferRequestRecord<
-    ChannelId,
-    MemberId,
-    CuratorGroupId,
-    DAOId,
-    Balance,
-    AccountId,
-> {
-    channel_id: ChannelId,
-    new_owner: ChannelOwner<MemberId, CuratorGroupId, DAOId>,
-    payment: Balance,
-    new_reward_account: Option<AccountId>,
-}
-
-// ChannelOwnershipTransferRequest type alias for simplification.
-pub type ChannelOwnershipTransferRequest<T> = ChannelOwnershipTransferRequestRecord<
-    <T as StorageOwnership>::ChannelId,
-    <T as MembershipTypes>::MemberId,
-    <T as ContentActorAuthenticator>::CuratorGroupId,
-    <T as StorageOwnership>::DAOId,
-    BalanceOf<T>,
-    <T as frame_system::Trait>::AccountId,
->;
-
-/// Information about channel being created.
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)]
-pub struct ChannelCreationParameters<ContentParameters, AccountId> {
-    /// Assets referenced by metadata
-    assets: Vec<NewAsset<ContentParameters>>,
-    /// Metadata about the channel.
-    meta: Vec<u8>,
-    /// optional reward account
-    reward_account: Option<AccountId>,
-}
-
-/// Information about channel being updated.
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
-pub struct ChannelUpdateParameters<ContentParameters, AccountId> {
-    /// Assets referenced by metadata
-    assets: Option<Vec<NewAsset<ContentParameters>>>,
-    /// If set, metadata update for the channel.
-    new_meta: Option<Vec<u8>>,
-    /// If set, updates the reward account of the channel
-    reward_account: Option<Option<AccountId>>,
-}
-
-/// A category that videos can belong to.
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
-pub struct VideoCategory {
-    // No runtime information is currently stored for a Category.
-}
-
-/// Information about the video category being created.
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
-pub struct VideoCategoryCreationParameters {
-    /// Metadata about the video category.
-    meta: Vec<u8>,
-}
-
-/// Information about the video category being updated.
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
-pub struct VideoCategoryUpdateParameters {
-    // Because it is the only field it is not an Option
-    /// Metadata update for the video category.
-    new_meta: Vec<u8>,
-}
-
-/// Information about the video being created.
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)]
-pub struct VideoCreationParameters<ContentParameters> {
-    /// Assets referenced by metadata
-    assets: Vec<NewAsset<ContentParameters>>,
-    /// Metadata for the video.
-    meta: Vec<u8>,
-}
-
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
-pub struct VideoUpdateParameters<ContentParameters> {
-    /// Assets referenced by metadata
-    assets: Option<Vec<NewAsset<ContentParameters>>>,
-    /// If set, metadata update for the video.
-    new_meta: Option<Vec<u8>>,
-}
-
-/// A video which belongs to a channel. A video may be part of a series or playlist.
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
-pub struct Video<ChannelId, SeriesId> {
-    pub in_channel: ChannelId,
-    // keep track of which season the video is in if it is an 'episode'
-    // - prevent removing a video if it is in a season (because order is important)
-    pub in_series: Option<SeriesId>,
-    /// Whether the curators have censored the video or not.
-    pub is_censored: bool,
-}
-
-/// Information about the plyalist being created.
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
-pub struct PlaylistCreationParameters {
-    /// Metadata about the playlist.
-    meta: Vec<u8>,
-}
-
-/// Information about the playlist being updated.
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
-pub struct PlaylistUpdateParameters {
-    // It is the only field so its not an Option
-    /// Metadata update for the playlist.
-    new_meta: Vec<u8>,
-}
-
-/// A playlist is an ordered collection of videos.
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
-pub struct Playlist<ChannelId> {
-    /// The channel the playlist belongs to.
-    in_channel: ChannelId,
-}
-
-/// Information about the episode being created or updated.
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)]
-pub enum EpisodeParameters<VideoId, ContentParameters> {
-    /// A new video is being added as the episode.
-    NewVideo(VideoCreationParameters<ContentParameters>),
-    /// An existing video is being made into an episode.
-    ExistingVideo(VideoId),
-}
-
-/// Information about the season being created or updated.
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
-pub struct SeasonParameters<VideoId, ContentParameters> {
-    /// Season assets referenced by metadata
-    assets: Option<Vec<NewAsset<ContentParameters>>>,
-    // ?? It might just be more straighforward to always provide full list of episodes at cost of larger tx.
-    /// If set, updates the episodes of a season. Extends the number of episodes in a season
-    /// when length of new_episodes is greater than previously set. Last elements must all be
-    /// 'Some' in that case.
-    /// Will truncate existing season when length of new_episodes is less than previously set.
-    episodes: Option<Vec<Option<EpisodeParameters<VideoId, ContentParameters>>>>,
-    /// If set, Metadata update for season.
-    meta: Option<Vec<u8>>,
-}
-
-/// Information about the series being created or updated.
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
-pub struct SeriesParameters<VideoId, ContentParameters> {
-    /// Series assets referenced by metadata
-    assets: Option<Vec<NewAsset<ContentParameters>>>,
-    // ?? It might just be more straighforward to always provide full list of seasons at cost of larger tx.
-    /// If set, updates the seasons of a series. Extend a series when length of seasons is
-    /// greater than previoulsy set. Last elements must all be 'Some' in that case.
-    /// Will truncate existing series when length of seasons is less than previously set.
-    seasons: Option<Vec<Option<SeasonParameters<VideoId, ContentParameters>>>>,
-    meta: Option<Vec<u8>>,
-}
-
-/// A season is an ordered list of videos (episodes).
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
-pub struct Season<VideoId> {
-    episodes: Vec<VideoId>,
-}
-
-/// A series is an ordered list of seasons that belongs to a channel.
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
-pub struct Series<ChannelId, VideoId> {
-    in_channel: ChannelId,
-    seasons: Vec<Season<VideoId>>,
-}
-
-// The actor the caller/origin is trying to act as for Person creation and update and delete calls.
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)]
-pub enum PersonActor<MemberId, CuratorId> {
-    Member(MemberId),
-    Curator(CuratorId),
-}
-
-/// The authorized actor that may update or delete a Person.
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)]
-pub enum PersonController<MemberId> {
-    /// Member controls the person
-    Member(MemberId),
-    /// Any curator controls the person
-    Curators,
-}
-
-// Default trait implemented only because its used in Person which needs to implement a Default trait
-// since it is a StorageValue.
-impl<MemberId: Default> Default for PersonController<MemberId> {
-    fn default() -> Self {
-        PersonController::Member(MemberId::default())
-    }
-}
-
-/// Information for Person being created.
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)]
-pub struct PersonCreationParameters<ContentParameters> {
-    /// Assets referenced by metadata
-    assets: Vec<NewAsset<ContentParameters>>,
-    /// Metadata for person.
-    meta: Vec<u8>,
-}
-
-/// Information for Persion being updated.
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
-pub struct PersonUpdateParameters<ContentParameters> {
-    /// Assets referenced by metadata
-    assets: Option<Vec<NewAsset<ContentParameters>>>,
-    /// Metadata to update person.
-    new_meta: Option<Vec<u8>>,
-}
-
-/// A Person represents a real person that may be associated with a video.
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
-pub struct Person<MemberId> {
-    /// Who can update or delete this person.
-    controlled_by: PersonController<MemberId>,
-}
-
 decl_storage! {
     trait Store for Module<T: Trait> as Content {
         pub ChannelById get(fn channel_by_id): map hasher(blake2_128_concat) T::ChannelId => Channel<T>;
 
         pub ChannelCategoryById get(fn channel_category_by_id): map hasher(blake2_128_concat) T::ChannelCategoryId => ChannelCategory;
 
-        pub VideoById get(fn video_by_id): map hasher(blake2_128_concat) T::VideoId => Video<T::ChannelId, T::SeriesId>;
+        pub VideoById get(fn video_by_id): map hasher(blake2_128_concat) T::VideoId => Video<T>;
 
         pub VideoCategoryById get(fn video_category_by_id): map hasher(blake2_128_concat) T::VideoCategoryId => VideoCategory;
 
@@ -495,6 +147,30 @@ decl_storage! {
 
         /// Map, representing  CuratorGroupId -> CuratorGroup relation
         pub CuratorGroupById get(fn curator_group_by_id): map hasher(blake2_128_concat) T::CuratorGroupId => CuratorGroup<T>;
+
+        /// Min auction round time
+        pub MinRoundTime get(fn min_round_time) config(): T::Moment;
+
+        /// Max auction round time
+        pub MaxRoundTime get(fn max_round_time) config(): T::Moment;
+
+        /// Min auction staring price
+        pub MinStartingPrice get(fn min_starting_price) config(): BalanceOf<T>;
+
+        /// Max auction staring price
+        pub MaxStartingPrice get(fn max_starting_price) config(): BalanceOf<T>;
+
+        /// Min creator royalty
+        pub MinCreatorRoyalty get(fn min_creator_royalty) config(): Perbill;
+
+        /// Max creator royalty
+        pub MaxCreatorRoyalty get(fn max_creator_royalty) config(): Perbill;
+
+        /// Min auction bid step
+        pub MinBidStep get(fn min_bid_step) config(): BalanceOf<T>;
+
+        /// Max auction bid step
+        pub MaxBidStep get(fn max_bid_step) config(): BalanceOf<T>;
     }
 }
 
@@ -928,13 +604,15 @@ decl_module! {
             // == MUTATION SAFE ==
             //
 
-            let video: Video<T::ChannelId, T::SeriesId> = Video {
+            let video: Video<T> = VideoRecord {
                 in_channel: channel_id,
                 // keep track of which season the video is in if it is an 'episode'
                 // - prevent removing a video if it is in a season (because order is important)
                 in_series: None,
                 /// Whether the curators have censored the video or not.
                 is_censored: false,
+                /// Newly created video has no nft
+                nft_status: NFTStatus::NoneIssued,
             };
 
             VideoById::<T>::insert(video_id, video);
@@ -1290,9 +968,7 @@ impl<T: Trait> Module<T> {
         Ok(ChannelById::<T>::get(channel_id))
     }
 
-    fn ensure_video_exists(
-        video_id: &T::VideoId,
-    ) -> Result<Video<T::ChannelId, T::SeriesId>, Error<T>> {
+    fn ensure_video_exists(video_id: &T::VideoId) -> Result<Video<T>, Error<T>> {
         ensure!(
             VideoById::<T>::contains_key(video_id),
             Error::<T>::VideoDoesNotExist
@@ -1301,7 +977,7 @@ impl<T: Trait> Module<T> {
     }
 
     // Ensure given video is not in season
-    fn ensure_video_can_be_removed(video: Video<T::ChannelId, T::SeriesId>) -> DispatchResult {
+    fn ensure_video_can_be_removed(video: Video<T>) -> DispatchResult {
         ensure!(video.in_series.is_none(), Error::<T>::VideoInSeason);
         Ok(())
     }

+ 378 - 0
runtime-modules/content/src/types.rs

@@ -0,0 +1,378 @@
+use crate::*;
+
+pub(crate) type ContentId<T> = <T as StorageOwnership>::ContentId;
+
+pub(crate) type DataObjectTypeId<T> = <T as StorageOwnership>::DataObjectTypeId;
+
+pub(crate) type ContentParameters<T> = ContentParametersRecord<ContentId<T>, DataObjectTypeId<T>>;
+
+pub(crate) type StorageObjectOwner<T> = StorageObjectOwnerRecord<
+    <T as MembershipTypes>::MemberId,
+    <T as StorageOwnership>::ChannelId,
+    <T as StorageOwnership>::DAOId,
+>;
+
+/// Type, used in diffrent numeric constraints representations
+pub type MaxNumber = u32;
+
+/// Specifies how a new asset will be provided on creating and updating
+/// Channels, Videos, Series and Person
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)]
+pub enum NewAsset<ContentParameters> {
+    /// Upload to the storage frame_system
+    Upload(ContentParameters),
+    /// Multiple url strings pointing at an asset
+    Urls(Vec<Url>),
+}
+
+/// The owner of a channel, is the authorized "actor" that can update
+/// or delete or transfer a channel and its contents.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)]
+pub enum ChannelOwner<MemberId, CuratorGroupId, DAOId> {
+    /// A Member owns the channel
+    Member(MemberId),
+    /// A specific curation group owns the channel
+    CuratorGroup(CuratorGroupId),
+    // Native DAO owns the channel
+    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
+    for ChannelOwner<MemberId, CuratorGroupId, DAOId>
+{
+    fn default() -> Self {
+        ChannelOwner::Member(MemberId::default())
+    }
+}
+
+/// A category which channels can belong to.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
+pub struct ChannelCategory {
+    // No runtime information is currently stored for a Category.
+}
+
+/// Information on the category being created.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
+pub struct ChannelCategoryCreationParameters {
+    /// Metadata for the category.
+    meta: Vec<u8>,
+}
+
+/// Information on the category being updated.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
+pub struct ChannelCategoryUpdateParameters {
+    // as this is the only field it is not an Option
+    /// Metadata update for the category.
+    new_meta: Vec<u8>,
+}
+
+/// Type representing an owned channel which videos, playlists, and series can belong to.
+/// If a channel is deleted, all videos, playlists and series will also be deleted.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
+pub struct ChannelRecord<MemberId, CuratorGroupId, DAOId, AccountId, VideoId, PlaylistId, SeriesId>
+{
+    /// The owner of a channel
+    pub owner: ChannelOwner<MemberId, CuratorGroupId, DAOId>,
+    /// The videos under this channel
+    pub videos: Vec<VideoId>,
+    /// The playlists under this channel
+    pub playlists: Vec<PlaylistId>,
+    /// The series under this channel
+    pub series: Vec<SeriesId>,
+    /// If curators have censored this channel or not
+    pub is_censored: bool,
+    /// Reward account where revenue is sent if set.
+    pub reward_account: Option<AccountId>,
+}
+
+// Channel alias type for simplification.
+pub type Channel<T> = ChannelRecord<
+    <T as MembershipTypes>::MemberId,
+    <T as ContentActorAuthenticator>::CuratorGroupId,
+    <T as StorageOwnership>::DAOId,
+    <T as frame_system::Trait>::AccountId,
+    <T as Trait>::VideoId,
+    <T as Trait>::PlaylistId,
+    <T as Trait>::SeriesId,
+>;
+
+/// A request to buy a channel by a new ChannelOwner.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
+pub struct ChannelOwnershipTransferRequestRecord<
+    ChannelId,
+    MemberId,
+    CuratorGroupId,
+    DAOId,
+    Balance,
+    AccountId,
+> {
+    pub channel_id: ChannelId,
+    pub new_owner: ChannelOwner<MemberId, CuratorGroupId, DAOId>,
+    pub payment: Balance,
+    pub new_reward_account: Option<AccountId>,
+}
+
+// ChannelOwnershipTransferRequest type alias for simplification.
+pub type ChannelOwnershipTransferRequest<T> = ChannelOwnershipTransferRequestRecord<
+    <T as StorageOwnership>::ChannelId,
+    <T as MembershipTypes>::MemberId,
+    <T as ContentActorAuthenticator>::CuratorGroupId,
+    <T as StorageOwnership>::DAOId,
+    BalanceOf<T>,
+    <T as frame_system::Trait>::AccountId,
+>;
+
+/// Information about channel being created.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)]
+pub struct ChannelCreationParameters<ContentParameters, AccountId> {
+    /// Assets referenced by metadata
+    pub assets: Vec<NewAsset<ContentParameters>>,
+    /// Metadata about the channel.
+    pub meta: Vec<u8>,
+    /// optional reward account
+    pub reward_account: Option<AccountId>,
+}
+
+/// Information about channel being updated.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
+pub struct ChannelUpdateParameters<ContentParameters, AccountId> {
+    /// Assets referenced by metadata
+    pub assets: Option<Vec<NewAsset<ContentParameters>>>,
+    /// If set, metadata update for the channel.
+    pub new_meta: Option<Vec<u8>>,
+    /// If set, updates the reward account of the channel
+    pub reward_account: Option<Option<AccountId>>,
+}
+
+/// A category that videos can belong to.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
+pub struct VideoCategory {
+    // No runtime information is currently stored for a Category.
+}
+
+/// Information about the video category being created.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
+pub struct VideoCategoryCreationParameters {
+    /// Metadata about the video category.
+    pub meta: Vec<u8>,
+}
+
+/// Information about the video category being updated.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
+pub struct VideoCategoryUpdateParameters {
+    // Because it is the only field it is not an Option
+    /// Metadata update for the video category.
+    pub new_meta: Vec<u8>,
+}
+
+/// Information about the video being created.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)]
+pub struct VideoCreationParameters<ContentParameters> {
+    /// Assets referenced by metadata
+    pub assets: Vec<NewAsset<ContentParameters>>,
+    /// Metadata for the video.
+    pub meta: Vec<u8>,
+}
+
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
+pub struct VideoUpdateParameters<ContentParameters> {
+    /// Assets referenced by metadata
+    pub assets: Option<Vec<NewAsset<ContentParameters>>>,
+    /// If set, metadata update for the video.
+    pub new_meta: Option<Vec<u8>>,
+}
+
+/// A video which belongs to a channel. A video may be part of a series or playlist.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
+pub struct VideoRecord<
+    ChannelId,
+    SeriesId,
+    AccountId,
+    Moment: BaseArithmetic + Copy,
+    CuratorGroupId: Default + Copy,
+    CuratorId: Default + Copy,
+    MemberId: Default + Copy,
+    Balance,
+> {
+    pub in_channel: ChannelId,
+    // keep track of which season the video is in if it is an 'episode'
+    // - prevent removing a video if it is in a season (because order is important)
+    pub in_series: Option<SeriesId>,
+    /// Whether the curators have censored the video or not.
+    pub is_censored: bool,
+    /// Whether nft for this video was issued.
+    pub nft_status: NFTStatus<AccountId, Moment, CuratorGroupId, CuratorId, MemberId, Balance>,
+}
+
+// Video alias type for simplification.
+pub type Video<T> = VideoRecord<
+    <T as StorageOwnership>::ChannelId,
+    <T as Trait>::SeriesId,
+    <T as frame_system::Trait>::AccountId,
+    <T as pallet_timestamp::Trait>::Moment,
+    <T as ContentActorAuthenticator>::CuratorGroupId,
+    <T as ContentActorAuthenticator>::CuratorId,
+    <T as MembershipTypes>::MemberId,
+    BalanceOf<T>,
+>;
+
+/// Information about the plyalist being created.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
+pub struct PlaylistCreationParameters {
+    /// Metadata about the playlist.
+    pub meta: Vec<u8>,
+}
+
+/// Information about the playlist being updated.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
+pub struct PlaylistUpdateParameters {
+    // It is the only field so its not an Option
+    /// Metadata update for the playlist.
+    pub new_meta: Vec<u8>,
+}
+
+/// A playlist is an ordered collection of videos.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
+pub struct Playlist<ChannelId> {
+    /// The channel the playlist belongs to.
+    pub in_channel: ChannelId,
+}
+
+/// Information about the episode being created or updated.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)]
+pub enum EpisodeParameters<VideoId, ContentParameters> {
+    /// A new video is being added as the episode.
+    NewVideo(VideoCreationParameters<ContentParameters>),
+    /// An existing video is being made into an episode.
+    ExistingVideo(VideoId),
+}
+
+/// Information about the season being created or updated.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
+pub struct SeasonParameters<VideoId, ContentParameters> {
+    /// Season assets referenced by metadata
+    pub assets: Option<Vec<NewAsset<ContentParameters>>>,
+    // ?? It might just be more straighforward to always provide full list of episodes at cost of larger tx.
+    /// If set, updates the episodes of a season. Extends the number of episodes in a season
+    /// when length of new_episodes is greater than previously set. Last elements must all be
+    /// 'Some' in that case.
+    /// Will truncate existing season when length of new_episodes is less than previously set.
+    pub episodes: Option<Vec<Option<EpisodeParameters<VideoId, ContentParameters>>>>,
+    /// If set, Metadata update for season.
+    pub meta: Option<Vec<u8>>,
+}
+
+/// Information about the series being created or updated.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
+pub struct SeriesParameters<VideoId, ContentParameters> {
+    /// Series assets referenced by metadata
+    pub assets: Option<Vec<NewAsset<ContentParameters>>>,
+    // ?? It might just be more straighforward to always provide full list of seasons at cost of larger tx.
+    /// If set, updates the seasons of a series. Extend a series when length of seasons is
+    /// greater than previoulsy set. Last elements must all be 'Some' in that case.
+    /// Will truncate existing series when length of seasons is less than previously set.
+    pub seasons: Option<Vec<Option<SeasonParameters<VideoId, ContentParameters>>>>,
+    pub meta: Option<Vec<u8>>,
+}
+
+/// A season is an ordered list of videos (episodes).
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
+pub struct Season<VideoId> {
+    pub episodes: Vec<VideoId>,
+}
+
+/// A series is an ordered list of seasons that belongs to a channel.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
+pub struct Series<ChannelId, VideoId> {
+    pub in_channel: ChannelId,
+    pub seasons: Vec<Season<VideoId>>,
+}
+
+// The actor the caller/origin is trying to act as for Person creation and update and delete calls.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)]
+pub enum PersonActor<MemberId, CuratorId> {
+    Member(MemberId),
+    Curator(CuratorId),
+}
+
+/// The authorized actor that may update or delete a Person.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)]
+pub enum PersonController<MemberId> {
+    /// Member controls the person
+    Member(MemberId),
+    /// Any curator controls the person
+    Curators,
+}
+
+// Default trait implemented only because its used in Person which needs to implement a Default trait
+// since it is a StorageValue.
+impl<MemberId: Default> Default for PersonController<MemberId> {
+    fn default() -> Self {
+        PersonController::Member(MemberId::default())
+    }
+}
+
+/// Information for Person being created.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)]
+pub struct PersonCreationParameters<ContentParameters> {
+    /// Assets referenced by metadata
+    pub assets: Vec<NewAsset<ContentParameters>>,
+    /// Metadata for person.
+    pub meta: Vec<u8>,
+}
+
+/// Information for Persion being updated.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
+pub struct PersonUpdateParameters<ContentParameters> {
+    /// Assets referenced by metadata
+    pub assets: Option<Vec<NewAsset<ContentParameters>>>,
+    /// Metadata to update person.
+    pub new_meta: Option<Vec<u8>>,
+}
+
+/// A Person represents a real person that may be associated with a video.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
+pub struct Person<MemberId> {
+    /// Who can update or delete this person.
+    pub controlled_by: PersonController<MemberId>,
+}

+ 313 - 0
runtime-modules/content/src/vnft_auction/mod.rs

@@ -0,0 +1,313 @@
+mod types;
+pub use types::*;
+
+use frame_support::StorageMap;
+
+use crate::*;
+
+impl<T: Trait> Module<T> {
+    // /// Authorize auctioneer
+    // pub(crate) fn authorize_auctioneer(
+    //     origin: T::Origin,
+    //     actor: &ContentActor<CuratorGroupId<T>, CuratorId<T>, MemberId<T>>,
+    //     auction_params: &AuctionParams<
+    //         <T as timestamp::Trait>::Moment,
+    //         BalanceOf<T>,
+    //     >,
+    // ) -> Result<T::AccountId, DispatchError> {
+    //     if let AuctionMode::WithoutIsuance(vnft_id) = auction_params.auction_mode {
+    //         let vnft = Self::ensure_vnft_exists(vnft_id)?;
+
+    //         // Only members are supposed to start auctions for already existing nfts
+    //         if let ContentActor::Member(member_id) = actor {
+    //             let account_id = Self::authorize_participant(origin, *member_id)?;
+
+    //             vnft.ensure_ownership::<T>(&account_id)?;
+
+    //             Ok(account_id)
+    //         } else {
+    //             Err(Error::<T>::AuctionDoesNotExist.into())
+    //         }
+    //     } else {
+    //         // TODO: Move to common pallet
+    //         Self::authorize_content_actor(origin.clone(), actor)?;
+    //         let account_id = ensure_signed(origin)?;
+    //         Ok(account_id)
+    //     }
+    // }
+
+    // /// Authorize participant under given member id
+    // pub(crate) fn authorize_participant(
+    //     origin: T::Origin,
+    //     member_id: MemberId<T>,
+    // ) -> Result<T::AccountId, Error<T>> {
+    //     T::MemberOriginValidator::ensure_actor_origin(origin, member_id)
+    //         .map_err(|_| Error::<T>::ActorOriginAuthError)
+    // }
+
+    // /// Authorize content actor
+    // pub(crate) fn authorize_content_actor(
+    //     origin: T::Origin,
+    //     actor: &ContentActor<CuratorGroupId<T>, CuratorId<T>, MemberId<T>>,
+    // ) -> Result<(), Error<T>> {
+    //     // TODO: Move to common pallet
+    //     content::ensure_actor_authorized_to_create_channel::<T>(origin.clone(), actor)
+    //         .map_err(|_| Error::<T>::ActorNotAuthorizedToIssueNft)
+    // }
+
+    // /// Ensure auction participant has sufficient balance to make bid
+    // pub(crate) fn ensure_has_sufficient_balance(
+    //     participant: &T::AccountId,
+    //     bid: BalanceOf<T>,
+    // ) -> DispatchResult {
+    //     ensure!(
+    //         T::NftCurrencyProvider::can_reserve(participant, bid),
+    //         Error::<T>::InsufficientBalance
+    //     );
+    //     Ok(())
+    // }
+
+    /// Safety/bound checks for auction parameters
+    pub(crate) fn validate_auction_params(
+        auction_params: &AuctionParams<<T as pallet_timestamp::Trait>::Moment, BalanceOf<T>>,
+    ) -> DispatchResult {
+        match auction_params.auction_mode {
+            AuctionMode::WithIssuance(Some(royalty), _) => {
+                // Self::ensure_vnft_does_not_exist(video_id)?;
+                Self::ensure_royalty_bounds_satisfied(royalty)?;
+            }
+            AuctionMode::WithoutIsuance => {
+                // Self::ensure_pending_transfer_does_not_exist(vnft_id)?;
+            }
+            _ => (),
+        }
+
+        Self::ensure_round_time_bounds_satisfied(auction_params.round_time)?;
+        Self::ensure_starting_price_bounds_satisfied(auction_params.starting_price)?;
+        Self::ensure_bid_step_bounds_satisfied(auction_params.minimal_bid_step)?;
+
+        Ok(())
+    }
+
+    /// Ensure royalty bounds satisfied
+    pub(crate) fn ensure_royalty_bounds_satisfied(royalty: Perbill) -> DispatchResult {
+        ensure!(
+            royalty <= Self::max_creator_royalty(),
+            Error::<T>::RoyaltyUpperBoundExceeded
+        );
+        ensure!(
+            royalty >= Self::min_creator_royalty(),
+            Error::<T>::RoyaltyLowerBoundExceeded
+        );
+        Ok(())
+    }
+
+    /// Ensure bid step bounds satisfied
+    pub(crate) fn ensure_bid_step_bounds_satisfied(bid_step: BalanceOf<T>) -> DispatchResult {
+        ensure!(
+            bid_step <= Self::max_bid_step(),
+            Error::<T>::AuctionBidStepUpperBoundExceeded
+        );
+        ensure!(
+            bid_step >= Self::min_bid_step(),
+            Error::<T>::AuctionBidStepLowerBoundExceeded
+        );
+        Ok(())
+    }
+
+    /// Ensure royalty bounds satisfied
+    pub(crate) fn ensure_round_time_bounds_satisfied(round_time: T::Moment) -> DispatchResult {
+        ensure!(
+            round_time <= Self::max_round_time(),
+            Error::<T>::RoundTimeUpperBoundExceeded
+        );
+        ensure!(
+            round_time >= Self::min_round_time(),
+            Error::<T>::RoundTimeLowerBoundExceeded
+        );
+        Ok(())
+    }
+
+    /// Ensure royalty bounds satisfied
+    pub(crate) fn ensure_starting_price_bounds_satisfied(
+        starting_price: BalanceOf<T>,
+    ) -> DispatchResult {
+        ensure!(
+            starting_price >= Self::max_starting_price(),
+            Error::<T>::StartingPriceUpperBoundExceeded
+        );
+        ensure!(
+            starting_price <= Self::min_starting_price(),
+            Error::<T>::StartingPriceLowerBoundExceeded
+        );
+        Ok(())
+    }
+
+    // /// Check whether auction for given video id exists
+    // pub(crate) fn is_auction_exist(auction_id: AuctionId<VideoId<T>, T::VNFTId>) -> bool {
+    //     <AuctionById<T>>::contains_key(auction_id)
+    // }
+
+    // /// Ensure auction for given id exists
+    // pub(crate) fn ensure_auction_exists(
+    //     auction_id: AuctionId<VideoId<T>, T::VNFTId>,
+    // ) -> Result<Auction<T>, Error<T>> {
+    //     ensure!(
+    //         Self::is_auction_exist(auction_id),
+    //         Error::<T>::AuctionDoesNotExist
+    //     );
+    //     Ok(Self::auction_by_id(auction_id))
+    // }
+
+    // /// Whether pending tansfer exist
+    // pub(crate) fn is_pending_transfer_exists(vnft_id: T::VNFTId) -> bool {
+    //     <PendingTransfers<T>>::iter_prefix_values(vnft_id).count() == 1
+    // }
+
+    // /// Ensure vNFT has pending transfer
+    // pub(crate) fn ensure_pending_transfer_exists(vnft_id: T::VNFTId) -> DispatchResult {
+    //     ensure!(
+    //         Self::is_pending_transfer_exists(vnft_id),
+    //         Error::<T>::PendingTransferDoesNotExist
+    //     );
+    //     Ok(())
+    // }
+
+    // /// Ensure vNFT has no pending transfers
+    // pub(crate) fn ensure_pending_transfer_does_not_exist(vnft_id: T::VNFTId) -> DispatchResult {
+    //     ensure!(
+    //         !Self::is_pending_transfer_exists(vnft_id),
+    //         Error::<T>::PendingAlreadyExists
+    //     );
+    //     Ok(())
+    // }
+
+    // /// Ensure new pending transfer for given participant available to proceed
+    // pub(crate) fn ensure_new_pending_transfer_available(
+    //     vnft_id: T::VNFTId,
+    //     participant: MemberId<T>,
+    // ) -> DispatchResult {
+    //     ensure!(
+    //         <PendingTransfers<T>>::contains_key(vnft_id, participant),
+    //         Error::<T>::NoIncomingTransfers
+    //     );
+    //     Ok(())
+    // }
+
+    // /// Ensure auction for given id does not exist
+    // pub(crate) fn ensure_auction_does_not_exist(
+    //     auction_id: AuctionId<VideoId<T>, T::VNFTId>,
+    // ) -> Result<Auction<T>, Error<T>> {
+    //     ensure!(
+    //         Self::is_auction_exist(auction_id),
+    //         Error::<T>::AuctionDoesNotExist
+    //     );
+    //     Ok(Self::auction_by_id(auction_id))
+    // }
+
+    // /// Ensure given vnft exists
+    // pub(crate) fn ensure_vnft_exists(vnft_id: T::VNFTId) -> Result<VNFT<T::AccountId>, Error<T>> {
+    //     ensure!(
+    //         <VNFTById<T>>::contains_key(vnft_id),
+    //         Error::<T>::VNFTDoesNotExist
+    //     );
+    //     Ok(Self::vnft_by_vnft_id(vnft_id))
+    // }
+
+    // /// Ensure given video id vnft relation does not exist
+    // pub(crate) fn ensure_vnft_does_not_exist(video_id: T::VideoId) -> DispatchResult {
+    //     ensure!(
+    //         !<VNFTIdByVideo<T>>::contains_key(video_id),
+    //         Error::<T>::VNFTAlreadyExists
+    //     );
+    //     Ok(())
+    // }
+
+    // /// Try complete auction when round time expired
+    // pub(crate) fn try_complete_auction(auction: &Auction<T>) -> bool {
+    //     let now = timestamp::Module::<T>::now();
+    //     if (now - auction.last_bid_time) >= auction.round_time {
+    //         Self::complete_auction(auction);
+    //         true
+    //     } else {
+    //         false
+    //     }
+    // }
+
+    // /// Complete auction
+    // pub(crate) fn complete_auction(auction: &Auction<T>) {
+    //     let auction_id = auction.auction_mode.get_auction_id();
+
+    //     // Remove auction entry
+    //     <AuctionById<T>>::remove(auction_id);
+
+    //     match auction.auction_mode.clone() {
+    //         AuctionMode::WithIssuance(video_id, royalty, _) => {
+    //             let last_bid = auction.last_bid;
+
+    //             // Slash last bidder bid and deposit it into auctioneer account
+    //             T::NftCurrencyProvider::slash_reserved(&auction.last_bidder, last_bid);
+    //             T::NftCurrencyProvider::deposit_creating(&auction.auctioneer_account_id, last_bid);
+
+    //             let creator_royalty = if let Some(royalty) = royalty {
+    //                 Some((auction.auctioneer_account_id.clone(), royalty))
+    //             } else {
+    //                 None
+    //             };
+
+    //             <Module<T>>::issue_vnft(auction.last_bidder.clone(), video_id, creator_royalty)
+    //         }
+    //         AuctionMode::WithoutIsuance(vnft_id) => {
+    //             <Module<T>>::complete_vnft_auction_transfer(auction, vnft_id)
+    //         }
+    //     }
+    //     Self::deposit_event(RawEvent::AuctionCompleted(auction.to_owned()));
+    // }
+
+    // /// Issue vnft and update mapping relations
+    // pub(crate) fn issue_vnft(
+    //     who: T::AccountId,
+    //     video_id: T::VideoId,
+    //     creator_royalty: Option<(T::AccountId, Royalty)>,
+    // ) {
+    //     // Issue vnft
+    //     let vnft_id = Self::next_video_nft_id();
+
+    //     <VNFTIdByVideo<T>>::insert(video_id, vnft_id);
+
+    //     <VNFTById<T>>::insert(vnft_id, VNFT::new(who, creator_royalty.clone()));
+
+    //     NextVNFTId::<T>::put(vnft_id + T::VNFTId::one());
+
+    //     Self::deposit_event(RawEvent::NftIssued(video_id, vnft_id, creator_royalty));
+    // }
+
+    // /// Complete vnft transfer
+    // pub(crate) fn complete_vnft_auction_transfer(auction: &Auction<T>, vnft_id: T::VNFTId) {
+    //     let vnft = Self::vnft_by_vnft_id(vnft_id);
+    //     let last_bid = auction.last_bid;
+
+    //     if let Some((creator_account_id, creator_royalty)) = vnft.creator_royalty {
+    //         let royalty = creator_royalty * last_bid;
+
+    //         // Slash last bidder bid
+    //         T::NftCurrencyProvider::slash_reserved(&auction.last_bidder, last_bid);
+
+    //         // Deposit bid, exluding royalty amount into auctioneer account
+    //         T::NftCurrencyProvider::deposit_creating(
+    //             &auction.auctioneer_account_id,
+    //             last_bid - royalty,
+    //         );
+
+    //         // Deposit royalty into creator account
+    //         T::NftCurrencyProvider::deposit_creating(&creator_account_id, royalty);
+    //     } else {
+    //         // Slash last bidder bid and deposit it into auctioneer account
+    //         T::NftCurrencyProvider::slash_reserved(&auction.last_bidder, last_bid);
+
+    //         T::NftCurrencyProvider::deposit_creating(&auction.auctioneer_account_id, last_bid);
+    //     }
+
+    //     <VNFTById<T>>::mutate(vnft_id, |vnft| vnft.owner = auction.last_bidder.clone());
+    // }
+}

+ 264 - 0
runtime-modules/content/src/vnft_auction/types.rs

@@ -0,0 +1,264 @@
+use super::*;
+
+/// Metadata for vNFT issuance
+type Metadata = Vec<u8>;
+
+pub type CuratorGroupId<T> = <T as ContentActorAuthenticator>::CuratorGroupId;
+pub type CuratorId<T> = <T as ContentActorAuthenticator>::CuratorId;
+pub type MemberId<T> = <T as MembershipTypes>::MemberId;
+
+/// Owner royalty
+pub type Royalty = Perbill;
+
+/// NFT transactional status
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)]
+pub enum TransactionalStatus<
+    AccountId,
+    Moment: BaseArithmetic + Copy,
+    CuratorGroupId: Default + Copy,
+    CuratorId: Default + Copy,
+    MemberId: Default + Copy,
+    Balance,
+> {
+    Idle,
+    PendingTransferTo(MemberId),
+    Auction(AuctionRecord<AccountId, Moment, CuratorGroupId, CuratorId, MemberId, Balance>),
+}
+
+impl<
+        AccountId,
+        Moment: BaseArithmetic + Copy,
+        CuratorGroupId: Default + Copy,
+        CuratorId: Default + Copy,
+        MemberId: Default + Copy,
+        Balance,
+    > Default
+    for TransactionalStatus<AccountId, Moment, CuratorGroupId, CuratorId, MemberId, Balance>
+{
+    fn default() -> Self {
+        Self::Idle
+    }
+}
+
+impl<
+        AccountId: Default + PartialEq,
+        Moment: BaseArithmetic + Copy,
+        CuratorGroupId: Default + Copy,
+        CuratorId: Default + Copy,
+        MemberId: Default + Copy,
+        Balance,
+    > OwnedNFT<AccountId, Moment, CuratorGroupId, CuratorId, MemberId, Balance>
+{
+    /// Create new vNFT
+    pub fn new(owner: AccountId, creator_royalty: Option<(AccountId, Royalty)>) -> Self {
+        Self {
+            owner,
+            transactional_status: TransactionalStatus::Idle,
+            creator_royalty,
+        }
+    }
+
+    /// Ensure given account id is vNFT owner
+    pub fn ensure_ownership<T: Trait>(&self, owner: &AccountId) -> DispatchResult {
+        ensure!(self.owner.eq(owner), Error::<T>::DoesNotOwnVNFT);
+        Ok(())
+    }
+}
+
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
+pub struct OwnedNFT<
+    AccountId,
+    Moment: BaseArithmetic + Copy,
+    CuratorGroupId: Default + Copy,
+    CuratorId: Default + Copy,
+    MemberId: Default + Copy,
+    Balance,
+> {
+    pub owner: AccountId,
+    pub transactional_status:
+        TransactionalStatus<AccountId, Moment, CuratorGroupId, CuratorId, MemberId, Balance>,
+    pub creator_royalty: Option<(AccountId, Royalty)>,
+}
+
+/// Enum, representing nft issuance status
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)]
+pub enum NFTStatus<
+    AccountId,
+    Moment: BaseArithmetic + Copy,
+    CuratorGroupId: Default + Copy,
+    CuratorId: Default + Copy,
+    MemberId: Default + Copy,
+    Balance,
+> {
+    NoneIssued,
+    Owned(OwnedNFT<AccountId, Moment, CuratorGroupId, CuratorId, MemberId, Balance>),
+}
+
+impl<
+        AccountId,
+        Moment: BaseArithmetic + Copy,
+        CuratorGroupId: Default + Copy,
+        CuratorId: Default + Copy,
+        MemberId: Default + Copy,
+        Balance,
+    > Default for NFTStatus<AccountId, Moment, CuratorGroupId, CuratorId, MemberId, Balance>
+{
+    fn default() -> Self {
+        Self::NoneIssued
+    }
+}
+
+/// Either new auction, which requires vNFT issance or auction for already existing nft.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)]
+pub enum AuctionMode {
+    // Auction, where nft issued at the end
+    WithIssuance(Option<Royalty>, Metadata),
+    // Auction for already existing nft
+    WithoutIsuance,
+}
+
+impl Default for AuctionMode {
+    fn default() -> Self {
+        Self::WithoutIsuance
+    }
+}
+
+/// Information on the auction being created.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
+pub struct AuctionRecord<
+    AccountId,
+    Moment: BaseArithmetic + Copy,
+    CuratorGroupId: Default + Copy,
+    CuratorId: Default + Copy,
+    MemberId: Default + Copy,
+    Balance,
+> {
+    pub auctioneer: ContentActor<CuratorGroupId, CuratorId, MemberId>,
+    pub auctioneer_account_id: AccountId,
+    pub auction_mode: AuctionMode,
+    pub starting_price: Balance,
+    pub buy_now_price: Option<Balance>,
+    pub round_time: Moment,
+    pub minimal_bid_step: Balance,
+    pub last_bid_time: Moment,
+    pub last_bid: Balance,
+    pub last_bidder: AccountId,
+}
+
+impl<
+        AccountId: Default + PartialEq,
+        Moment: BaseArithmetic + Copy + Default,
+        CuratorGroupId: Default + Copy + PartialEq,
+        CuratorId: Default + Copy + PartialEq,
+        MemberId: Default + Copy + PartialEq,
+        Balance: Default + BaseArithmetic,
+    > AuctionRecord<AccountId, Moment, CuratorGroupId, CuratorId, MemberId, Balance>
+{
+    /// Create a new auction record with provided parameters
+    pub fn new(
+        auctioneer: ContentActor<CuratorGroupId, CuratorId, MemberId>,
+        auctioneer_account_id: AccountId,
+        auction_params: AuctionParams<Moment, Balance>,
+    ) -> Self {
+        let AuctionParams {
+            auction_mode,
+            round_time,
+            starting_price,
+            buy_now_price,
+            minimal_bid_step,
+            ..
+        } = auction_params;
+        Self {
+            auctioneer,
+            auctioneer_account_id,
+            auction_mode,
+            starting_price,
+            buy_now_price,
+            round_time,
+            minimal_bid_step,
+            last_bid_time: Moment::default(),
+            last_bid: Balance::default(),
+            last_bidder: AccountId::default(),
+        }
+    }
+
+    /// Ensure new bid is greater then last bid + minimal bid step
+    pub fn ensure_is_valid_bid<T: Trait>(&self, new_bid: Balance) -> DispatchResult {
+        // Always allow to buy now
+        match &self.buy_now_price {
+            Some(buy_now_price) if new_bid >= *buy_now_price => (),
+            // Ensure new bid is greater then last bid + minimal bid step
+            _ => ensure!(
+                self.last_bid
+                    .checked_add(&self.minimal_bid_step)
+                    .ok_or(Error::<T>::OverflowOrUnderflowHappened)?
+                    < new_bid,
+                Error::<T>::InvalidBid
+            ),
+        }
+
+        Ok(())
+    }
+
+    /// Make auction bid
+    pub fn make_bid<T: Trait>(
+        mut self,
+        who: AccountId,
+        bid: Balance,
+        last_bid_time: Moment,
+    ) -> Self {
+        self.last_bidder = who;
+        self.last_bid = bid;
+        self.last_bid_time = last_bid_time;
+        self
+    }
+
+    // We assume that default AccountId can not make any bids
+    fn is_active(&self) -> bool {
+        self.last_bidder.eq(&AccountId::default())
+    }
+
+    /// Ensure auction is not active
+    pub fn ensure_is_not_active<T: Trait>(&self) -> DispatchResult {
+        ensure!(self.is_active(), Error::<T>::ActionIsAlreadyActive);
+        Ok(())
+    }
+
+    /// Ensure provided account id is auctioneer
+    pub fn ensure_is_auctioneer<T: Trait>(
+        &self,
+        auctioneer: &ContentActor<CuratorGroupId, CuratorId, MemberId>,
+    ) -> DispatchResult {
+        ensure!(
+            self.auctioneer.eq(auctioneer),
+            Error::<T>::ActorIsNotAnAuctioneer
+        );
+        Ok(())
+    }
+}
+
+/// Auction alias type for simplification.
+pub type Auction<T> = AuctionRecord<
+    <T as frame_system::Trait>::AccountId,
+    <T as pallet_timestamp::Trait>::Moment,
+    CuratorGroupId<T>,
+    CuratorId<T>,
+    MemberId<T>,
+    BalanceOf<T>,
+>;
+
+/// Parameters, needed for auction start
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
+pub struct AuctionParams<Moment, Balance> {
+    pub auction_mode: AuctionMode,
+    pub round_time: Moment,
+    pub starting_price: Balance,
+    pub minimal_bid_step: Balance,
+    pub buy_now_price: Option<Balance>,
+}