From f5776f689768d37ab9d22305b6aafa2ef9811a6d Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> Date: Wed, 5 Jul 2023 11:17:08 +0200 Subject: [PATCH] Improve NFT locking (#14510) * Update docs * Prevent locking of the same NFT twice * Validate item is not locked on burn * Cover with tests * chore --- .../frame/nft-fractionalization/src/lib.rs | 4 +-- .../frame/nft-fractionalization/src/tests.rs | 27 +++++++++++++++++++ .../nfts/src/features/create_delete_item.rs | 4 +++ substrate/frame/nfts/src/impl_nonfungibles.rs | 7 +++++ 4 files changed, 40 insertions(+), 2 deletions(-) diff --git a/substrate/frame/nft-fractionalization/src/lib.rs b/substrate/frame/nft-fractionalization/src/lib.rs index a6417cc3857..b1663e95d85 100644 --- a/substrate/frame/nft-fractionalization/src/lib.rs +++ b/substrate/frame/nft-fractionalization/src/lib.rs @@ -338,12 +338,12 @@ pub mod pallet { T::PalletId::get().into_account_truncating() } - /// Transfer the NFT from the account holding that NFT to the pallet's account. + /// Prevent further transferring of NFT. fn do_lock_nft(nft_collection_id: T::NftCollectionId, nft_id: T::NftId) -> DispatchResult { T::Nfts::disable_transfer(&nft_collection_id, &nft_id) } - /// Transfer the NFT to the account returning the tokens. + /// Remove the transfer lock and transfer the NFT to the account returning the tokens. fn do_unlock_nft( nft_collection_id: T::NftCollectionId, nft_id: T::NftId, diff --git a/substrate/frame/nft-fractionalization/src/tests.rs b/substrate/frame/nft-fractionalization/src/tests.rs index 8564b80533e..b82402bda1e 100644 --- a/substrate/frame/nft-fractionalization/src/tests.rs +++ b/substrate/frame/nft-fractionalization/src/tests.rs @@ -122,6 +122,33 @@ fn fractionalize_should_work() { beneficiary: account(2), })); + // owner can't burn an already fractionalized NFT + assert_noop!( + Nfts::burn(RuntimeOrigin::signed(account(1)), nft_collection_id, nft_id), + DispatchError::Module(ModuleError { + index: 4, + error: [12, 0, 0, 0], + message: Some("ItemLocked") + }) + ); + + // can't fractionalize twice + assert_noop!( + NftFractionalization::fractionalize( + RuntimeOrigin::signed(account(1)), + nft_collection_id, + nft_id, + asset_id + 1, + account(2), + fractions, + ), + DispatchError::Module(ModuleError { + index: 4, + error: [12, 0, 0, 0], + message: Some("ItemLocked") + }) + ); + let nft_id = nft_id + 1; assert_noop!( NftFractionalization::fractionalize( diff --git a/substrate/frame/nfts/src/features/create_delete_item.rs b/substrate/frame/nfts/src/features/create_delete_item.rs index a757273445f..e3d1334d48e 100644 --- a/substrate/frame/nfts/src/features/create_delete_item.rs +++ b/substrate/frame/nfts/src/features/create_delete_item.rs @@ -169,6 +169,10 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> { with_details: impl FnOnce(&ItemDetailsFor<T, I>) -> DispatchResult, ) -> DispatchResult { ensure!(!T::Locker::is_locked(collection, item), Error::<T, I>::ItemLocked); + ensure!( + !Self::has_system_attribute(&collection, &item, PalletAttributes::TransferDisabled)?, + Error::<T, I>::ItemLocked + ); let item_config = Self::get_item_config(&collection, &item)?; // NOTE: if item's settings are not empty (e.g. item's metadata is locked) // then we keep the config record and don't remove it diff --git a/substrate/frame/nfts/src/impl_nonfungibles.rs b/substrate/frame/nfts/src/impl_nonfungibles.rs index b9e3951422f..090a6a372c1 100644 --- a/substrate/frame/nfts/src/impl_nonfungibles.rs +++ b/substrate/frame/nfts/src/impl_nonfungibles.rs @@ -341,6 +341,13 @@ impl<T: Config<I>, I: 'static> Transfer<T::AccountId> for Pallet<T, I> { } fn disable_transfer(collection: &Self::CollectionId, item: &Self::ItemId) -> DispatchResult { + let transfer_disabled = + Self::has_system_attribute(&collection, &item, PalletAttributes::TransferDisabled)?; + // Can't lock the item twice + if transfer_disabled { + return Err(Error::<T, I>::ItemLocked.into()) + } + <Self as Mutate<T::AccountId, ItemConfig>>::set_attribute( collection, item, -- GitLab