Browse Source

Merge pull request #2795 from iorveth/issue_2624

vNFT: introudce an ability to sling nft back to the original artist
Bedeho Mender 3 years ago
parent
commit
1610ca3a14

+ 35 - 0
runtime-modules/content/src/lib.rs

@@ -1499,6 +1499,40 @@ decl_module! {
             Self::deposit_event(RawEvent::OfferStarted(video_id, owner_id, to, price));
         }
 
+        /// Return NFT back to the original artist at no cost
+        #[weight = 10_000_000] // TODO: adjust weight
+        pub fn sling_nft_back(
+            origin,
+            video_id: T::VideoId,
+            owner_id: ContentActor<CuratorGroupId<T>, CuratorId<T>, MemberId<T>>,
+        ) {
+
+            // Ensure given video exists
+            let video = Self::ensure_video_exists(&video_id)?;
+
+            // Ensure nft is already issued
+            let nft = video.ensure_nft_is_issued::<T>()?;
+
+            // Authorize nft owner
+            ensure_actor_authorized_to_manage_nft::<T>(origin, &owner_id, &nft.owner, video.in_channel)?;
+
+            // Ensure there is no pending offer or existing auction for given nft.
+            nft.ensure_nft_transactional_status_is_idle::<T>()?;
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            // Set nft owner to ChannelOwner
+            let nft = nft.set_owner(NFTOwner::ChannelOwner);
+            let video = video.set_nft_status(nft);
+
+            VideoById::<T>::insert(video_id, video);
+
+            // Trigger event
+            Self::deposit_event(RawEvent::NftSlingedBackToTheOriginalArtist(video_id, owner_id));
+        }
+
         /// Accept incoming NFT offer
         #[weight = 10_000_000] // TODO: adjust weight
         pub fn accept_incoming_offer(
@@ -1897,5 +1931,6 @@ decl_event!(
         NFTSellOrderMade(VideoId, ContentActor, Balance),
         NFTBought(VideoId, MemberId),
         BuyNowCanceled(VideoId, ContentActor),
+        NftSlingedBackToTheOriginalArtist(VideoId, ContentActor),
     }
 );

+ 6 - 0
runtime-modules/content/src/nft/types.rs

@@ -67,6 +67,12 @@ impl<
         }
     }
 
+    /// Set nft owner
+    pub fn set_owner(mut self, owner: NFTOwner<MemberId>) -> Self {
+        self.owner = owner;
+        self
+    }
+
     /// Get nft auction record
     pub fn ensure_auction_state<T: Trait>(
         &self,

+ 1 - 0
runtime-modules/content/src/tests/nft.rs

@@ -10,4 +10,5 @@ mod make_bid;
 mod offer_nft;
 mod pick_open_auction_winner;
 mod sell_nft;
+mod sling_nft_back;
 mod start_nft_auction;

+ 212 - 0
runtime-modules/content/src/tests/nft/sling_nft_back.rs

@@ -0,0 +1,212 @@
+#![cfg(test)]
+
+use crate::tests::mock::*;
+use crate::*;
+use frame_support::{assert_err, assert_ok};
+
+#[test]
+fn sling_nft_back() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            Some(SECOND_MEMBER_ID),
+        ));
+
+        // Runtime tested state before call
+        assert!(matches!(
+            Content::video_by_id(video_id).nft_status,
+            Some(OwnedNFT {
+                owner: NFTOwner::Member(SECOND_MEMBER_ID),
+                ..
+            })
+        ));
+
+        // Events number before tested calls
+        let number_of_events_before_call = System::events().len();
+
+        // Sling nft back to the original artist
+        assert_ok!(Content::sling_nft_back(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            video_id,
+            ContentActor::Member(SECOND_MEMBER_ID),
+        ));
+
+        // Runtime tested state after call
+
+        // Ensure nft slinged back successfully
+        assert!(matches!(
+            Content::video_by_id(video_id).nft_status,
+            Some(OwnedNFT {
+                owner: NFTOwner::ChannelOwner,
+                ..
+            })
+        ));
+
+        let offer_started_event = get_test_event(RawEvent::NftSlingedBackToTheOriginalArtist(
+            video_id,
+            ContentActor::Member(SECOND_MEMBER_ID),
+        ));
+
+        // Last event checked
+        assert_event(offer_started_event, number_of_events_before_call + 1);
+    })
+}
+
+#[test]
+fn sling_nft_back_video_does_not_exist() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        // Make an attempt to sling nft back which corresponding video does not exist
+        let sling_nft_back_result = Content::sling_nft_back(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            video_id,
+            ContentActor::Member(FIRST_MEMBER_ID),
+        );
+
+        // Failure checked
+        assert_err!(sling_nft_back_result, Error::<Test>::VideoDoesNotExist);
+    })
+}
+
+#[test]
+fn sling_nft_back_not_issued() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Make an attempt to sling nft back which is not issued yet
+        let sling_nft_back_result = Content::sling_nft_back(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            video_id,
+            ContentActor::Member(FIRST_MEMBER_ID),
+        );
+
+        // Failure checked
+        assert_err!(sling_nft_back_result, Error::<Test>::NFTDoesNotExist);
+    })
+}
+
+#[test]
+fn sling_nft_back_auth_failed() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        // Make an attempt to sling nft back with wrong credentials
+        let sling_nft_back_result = Content::sling_nft_back(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            video_id,
+            ContentActor::Member(UNKNOWN_ID),
+        );
+
+        // Failure checked
+        assert_err!(sling_nft_back_result, Error::<Test>::MemberAuthFailed);
+    })
+}
+
+#[test]
+fn sling_nft_back_not_authorized() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        // Make an attempt to sling nft back if actor is not authorized
+        let sling_nft_back_result = Content::sling_nft_back(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            video_id,
+            ContentActor::Member(SECOND_MEMBER_ID),
+        );
+
+        // Failure checked
+        assert_err!(sling_nft_back_result, Error::<Test>::ActorNotAuthorized);
+    })
+}
+
+#[test]
+fn sling_nft_back_transactional_status_is_not_idle() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        // Offer nft
+        assert_ok!(Content::offer_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            video_id,
+            ContentActor::Member(FIRST_MEMBER_ID),
+            SECOND_MEMBER_ID,
+            None,
+        ));
+
+        // Make an attempt to sling nft back when it is already offered
+        let sling_nft_back_result = Content::sling_nft_back(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            video_id,
+            ContentActor::Member(FIRST_MEMBER_ID),
+        );
+
+        // Failure checked
+        assert_err!(sling_nft_back_result, Error::<Test>::NftIsNotIdle);
+    })
+}