From 76f030e8b81ac3f86a427f55b48ee012ea654960 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Sun, 23 Oct 2022 07:38:01 +0200 Subject: [PATCH 1/5] Move do_set_price() and do_buy_item() to buy_sell.rs --- frame/nfts/src/features/buy_sell.rs | 90 +++++++++++++++++++++++++++- frame/nfts/src/functions.rs | 93 +---------------------------- 2 files changed, 90 insertions(+), 93 deletions(-) diff --git a/frame/nfts/src/features/buy_sell.rs b/frame/nfts/src/features/buy_sell.rs index c1e29057af9c9..8ba5171f8d822 100644 --- a/frame/nfts/src/features/buy_sell.rs +++ b/frame/nfts/src/features/buy_sell.rs @@ -18,7 +18,7 @@ use crate::*; use frame_support::{ pallet_prelude::*, - traits::{Currency, ExistenceRequirement::KeepAlive}, + traits::{Currency, ExistenceRequirement, ExistenceRequirement::KeepAlive}, }; impl, I: 'static> Pallet { @@ -39,4 +39,92 @@ impl, I: 'static> Pallet { } Ok(()) } + + pub(crate) fn do_set_price( + collection: T::CollectionId, + item: T::ItemId, + sender: T::AccountId, + price: Option>, + whitelisted_buyer: Option, + ) -> DispatchResult { + ensure!( + Self::is_pallet_feature_enabled(PalletFeature::Trading), + Error::::MethodDisabled + ); + + let details = Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; + ensure!(details.owner == sender, Error::::NoPermission); + + let collection_config = Self::get_collection_config(&collection)?; + ensure!( + collection_config.is_setting_enabled(CollectionSetting::TransferableItems), + Error::::ItemsNonTransferable + ); + + let item_config = Self::get_item_config(&collection, &item)?; + ensure!( + item_config.is_setting_enabled(ItemSetting::Transferable), + Error::::ItemLocked + ); + + if let Some(ref price) = price { + ItemPriceOf::::insert(&collection, &item, (price, whitelisted_buyer.clone())); + Self::deposit_event(Event::ItemPriceSet { + collection, + item, + price: *price, + whitelisted_buyer, + }); + } else { + ItemPriceOf::::remove(&collection, &item); + Self::deposit_event(Event::ItemPriceRemoved { collection, item }); + } + + Ok(()) + } + + pub(crate) fn do_buy_item( + collection: T::CollectionId, + item: T::ItemId, + buyer: T::AccountId, + bid_price: ItemPrice, + ) -> DispatchResult { + ensure!( + Self::is_pallet_feature_enabled(PalletFeature::Trading), + Error::::MethodDisabled + ); + + let details = Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; + ensure!(details.owner != buyer, Error::::NoPermission); + + let price_info = + ItemPriceOf::::get(&collection, &item).ok_or(Error::::NotForSale)?; + + ensure!(bid_price >= price_info.0, Error::::BidTooLow); + + if let Some(only_buyer) = price_info.1 { + ensure!(only_buyer == buyer, Error::::NoPermission); + } + + T::Currency::transfer( + &buyer, + &details.owner, + price_info.0, + ExistenceRequirement::KeepAlive, + )?; + + let old_owner = details.owner.clone(); + + Self::do_transfer(collection, item, buyer.clone(), |_, _| Ok(()))?; + + Self::deposit_event(Event::ItemBought { + collection, + item, + price: price_info.0, + seller: old_owner, + buyer, + }); + + Ok(()) + } } diff --git a/frame/nfts/src/functions.rs b/frame/nfts/src/functions.rs index d556bdd4b628f..9adc406a46fd1 100644 --- a/frame/nfts/src/functions.rs +++ b/frame/nfts/src/functions.rs @@ -18,10 +18,7 @@ //! Various pieces of common functionality. use super::*; -use frame_support::{ - ensure, - traits::{ExistenceRequirement, Get}, -}; +use frame_support::{ensure, traits::Get}; use sp_runtime::{DispatchError, DispatchResult}; impl, I: 'static> Pallet { @@ -277,94 +274,6 @@ impl, I: 'static> Pallet { Ok(()) } - pub fn do_set_price( - collection: T::CollectionId, - item: T::ItemId, - sender: T::AccountId, - price: Option>, - whitelisted_buyer: Option, - ) -> DispatchResult { - ensure!( - Self::is_pallet_feature_enabled(PalletFeature::Trading), - Error::::MethodDisabled - ); - - let details = Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; - ensure!(details.owner == sender, Error::::NoPermission); - - let collection_config = Self::get_collection_config(&collection)?; - ensure!( - collection_config.is_setting_enabled(CollectionSetting::TransferableItems), - Error::::ItemsNonTransferable - ); - - let item_config = Self::get_item_config(&collection, &item)?; - ensure!( - item_config.is_setting_enabled(ItemSetting::Transferable), - Error::::ItemLocked - ); - - if let Some(ref price) = price { - ItemPriceOf::::insert(&collection, &item, (price, whitelisted_buyer.clone())); - Self::deposit_event(Event::ItemPriceSet { - collection, - item, - price: *price, - whitelisted_buyer, - }); - } else { - ItemPriceOf::::remove(&collection, &item); - Self::deposit_event(Event::ItemPriceRemoved { collection, item }); - } - - Ok(()) - } - - pub fn do_buy_item( - collection: T::CollectionId, - item: T::ItemId, - buyer: T::AccountId, - bid_price: ItemPrice, - ) -> DispatchResult { - ensure!( - Self::is_pallet_feature_enabled(PalletFeature::Trading), - Error::::MethodDisabled - ); - - let details = Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; - ensure!(details.owner != buyer, Error::::NoPermission); - - let price_info = - ItemPriceOf::::get(&collection, &item).ok_or(Error::::NotForSale)?; - - ensure!(bid_price >= price_info.0, Error::::BidTooLow); - - if let Some(only_buyer) = price_info.1 { - ensure!(only_buyer == buyer, Error::::NoPermission); - } - - T::Currency::transfer( - &buyer, - &details.owner, - price_info.0, - ExistenceRequirement::KeepAlive, - )?; - - let old_owner = details.owner.clone(); - - Self::do_transfer(collection, item, buyer.clone(), |_, _| Ok(()))?; - - Self::deposit_event(Event::ItemBought { - collection, - item, - price: price_info.0, - seller: old_owner, - buyer, - }); - - Ok(()) - } - #[cfg(any(test, feature = "runtime-benchmarks"))] pub fn set_next_id(id: T::CollectionId) { NextCollectionId::::set(Some(id)); From b1d76d03c80f3a614dd976d363a96bc2999c7b8e Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Sun, 23 Oct 2022 07:55:11 +0200 Subject: [PATCH 2/5] Move approvals to feature file --- frame/nfts/src/features/approvals.rs | 138 +++++++++++++++++++++++++++ frame/nfts/src/features/mod.rs | 1 + frame/nfts/src/lib.rs | 106 ++------------------ 3 files changed, 148 insertions(+), 97 deletions(-) create mode 100644 frame/nfts/src/features/approvals.rs diff --git a/frame/nfts/src/features/approvals.rs b/frame/nfts/src/features/approvals.rs new file mode 100644 index 0000000000000..fa572bea88fb9 --- /dev/null +++ b/frame/nfts/src/features/approvals.rs @@ -0,0 +1,138 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::*; +use frame_support::pallet_prelude::*; + +impl, I: 'static> Pallet { + pub(crate) fn do_approve_transfer( + maybe_check_origin: Option, + collection: T::CollectionId, + item: T::ItemId, + delegate: T::AccountId, + maybe_deadline: Option<::BlockNumber>, + ) -> DispatchResult { + ensure!( + Self::is_pallet_feature_enabled(PalletFeature::Approvals), + Error::::MethodDisabled + ); + let mut details = + Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; + + let collection_config = Self::get_collection_config(&collection)?; + ensure!( + collection_config.is_setting_enabled(CollectionSetting::TransferableItems), + Error::::ItemsNonTransferable + ); + + let collection_config = Self::get_collection_config(&collection)?; + ensure!( + collection_config.is_setting_enabled(CollectionSetting::TransferableItems), + Error::::ItemsNonTransferable + ); + + if let Some(check_origin) = maybe_check_origin { + let is_admin = Self::has_role(&collection, &check_origin, CollectionRole::Admin); + let permitted = is_admin || check_origin == details.owner; + ensure!(permitted, Error::::NoPermission); + } + + let now = frame_system::Pallet::::block_number(); + let deadline = maybe_deadline.map(|d| d.saturating_add(now)); + + details + .approvals + .try_insert(delegate.clone(), deadline) + .map_err(|_| Error::::ReachedApprovalLimit)?; + Item::::insert(&collection, &item, &details); + + Self::deposit_event(Event::ApprovedTransfer { + collection, + item, + owner: details.owner, + delegate, + deadline, + }); + + Ok(()) + } + + pub(crate) fn do_cancel_approval( + maybe_check_origin: Option, + collection: T::CollectionId, + item: T::ItemId, + delegate: T::AccountId, + ) -> DispatchResult { + let mut details = + Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; + + let maybe_deadline = details.approvals.get(&delegate).ok_or(Error::::NotDelegate)?; + + let is_past_deadline = if let Some(deadline) = maybe_deadline { + let now = frame_system::Pallet::::block_number(); + now > *deadline + } else { + false + }; + + if !is_past_deadline { + if let Some(check_origin) = maybe_check_origin { + let is_admin = Self::has_role(&collection, &check_origin, CollectionRole::Admin); + let permitted = is_admin || check_origin == details.owner; + ensure!(permitted, Error::::NoPermission); + } + } + + details.approvals.remove(&delegate); + Item::::insert(&collection, &item, &details); + + Self::deposit_event(Event::ApprovalCancelled { + collection, + item, + owner: details.owner, + delegate, + }); + + Ok(()) + } + + pub(crate) fn do_clear_all_transfer_approvals( + maybe_check_origin: Option, + collection: T::CollectionId, + item: T::ItemId, + ) -> DispatchResult { + let mut details = + Item::::get(&collection, &item).ok_or(Error::::UnknownCollection)?; + + if let Some(check_origin) = maybe_check_origin { + let is_admin = Self::has_role(&collection, &check_origin, CollectionRole::Admin); + let permitted = is_admin || check_origin == details.owner; + ensure!(permitted, Error::::NoPermission); + } + + details.approvals.clear(); + Item::::insert(&collection, &item, &details); + + Self::deposit_event(Event::AllApprovalsCancelled { + collection, + item, + owner: details.owner, + }); + + Ok(()) + } +} diff --git a/frame/nfts/src/features/mod.rs b/frame/nfts/src/features/mod.rs index f814d696d774b..5c67824878303 100644 --- a/frame/nfts/src/features/mod.rs +++ b/frame/nfts/src/features/mod.rs @@ -15,6 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +pub mod approvals; pub mod atomic_swap; pub mod buy_sell; pub mod lock; diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 618614d103f91..98d19e9aed827 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -1120,55 +1120,17 @@ pub mod pallet { delegate: AccountIdLookupOf, maybe_deadline: Option<::BlockNumber>, ) -> DispatchResult { - ensure!( - Self::is_pallet_feature_enabled(PalletFeature::Approvals), - Error::::MethodDisabled - ); - let maybe_check: Option = T::ForceOrigin::try_origin(origin) + let maybe_check_origin = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; - let delegate = T::Lookup::lookup(delegate)?; - - let mut details = - Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; - - let collection_config = Self::get_collection_config(&collection)?; - ensure!( - collection_config.is_setting_enabled(CollectionSetting::TransferableItems), - Error::::ItemsNonTransferable - ); - - let collection_config = Self::get_collection_config(&collection)?; - ensure!( - collection_config.is_setting_enabled(CollectionSetting::TransferableItems), - Error::::ItemsNonTransferable - ); - - if let Some(check) = maybe_check { - let is_admin = Self::has_role(&collection, &check, CollectionRole::Admin); - let permitted = is_admin || check == details.owner; - ensure!(permitted, Error::::NoPermission); - } - - let now = frame_system::Pallet::::block_number(); - let deadline = maybe_deadline.map(|d| d.saturating_add(now)); - - details - .approvals - .try_insert(delegate.clone(), deadline) - .map_err(|_| Error::::ReachedApprovalLimit)?; - Item::::insert(&collection, &item, &details); - - Self::deposit_event(Event::ApprovedTransfer { + Self::do_approve_transfer( + maybe_check_origin, collection, item, - owner: details.owner, delegate, - deadline, - }); - - Ok(()) + maybe_deadline, + ) } /// Cancel one of the transfer approvals for a specific item. @@ -1193,43 +1155,11 @@ pub mod pallet { item: T::ItemId, delegate: AccountIdLookupOf, ) -> DispatchResult { - let maybe_check: Option = T::ForceOrigin::try_origin(origin) + let maybe_check_origin = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; - let delegate = T::Lookup::lookup(delegate)?; - - let mut details = - Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; - - let maybe_deadline = - details.approvals.get(&delegate).ok_or(Error::::NotDelegate)?; - - let is_past_deadline = if let Some(deadline) = maybe_deadline { - let now = frame_system::Pallet::::block_number(); - now > *deadline - } else { - false - }; - - if !is_past_deadline { - if let Some(check) = maybe_check { - let is_admin = Self::has_role(&collection, &check, CollectionRole::Admin); - let permitted = is_admin || check == details.owner; - ensure!(permitted, Error::::NoPermission); - } - } - - details.approvals.remove(&delegate); - Item::::insert(&collection, &item, &details); - Self::deposit_event(Event::ApprovalCancelled { - collection, - item, - owner: details.owner, - delegate, - }); - - Ok(()) + Self::do_cancel_approval(maybe_check_origin, collection, item, delegate) } /// Cancel all the approvals of a specific item. @@ -1252,28 +1182,10 @@ pub mod pallet { collection: T::CollectionId, item: T::ItemId, ) -> DispatchResult { - let maybe_check: Option = T::ForceOrigin::try_origin(origin) + let maybe_check_origin = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; - - let mut details = - Item::::get(&collection, &item).ok_or(Error::::UnknownCollection)?; - - if let Some(check) = maybe_check { - let is_admin = Self::has_role(&collection, &check, CollectionRole::Admin); - let permitted = is_admin || check == details.owner; - ensure!(permitted, Error::::NoPermission); - } - - details.approvals.clear(); - Item::::insert(&collection, &item, &details); - Self::deposit_event(Event::AllApprovalsCancelled { - collection, - item, - owner: details.owner, - }); - - Ok(()) + Self::do_clear_all_transfer_approvals(maybe_check_origin, collection, item) } /// Alter the attributes of a given collection. From 9178de724e30468ffa6608ebd741099ca2caad7b Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Thu, 27 Oct 2022 10:25:09 +0200 Subject: [PATCH 3/5] Move metadata to feature files --- frame/nfts/src/common_functions.rs | 32 ++++ frame/nfts/src/features/metadata.rs | 173 +++++++++++++++++ frame/nfts/src/features/mod.rs | 1 + frame/nfts/src/functions.rs | 286 ---------------------------- frame/nfts/src/lib.rs | 132 +------------ 5 files changed, 210 insertions(+), 414 deletions(-) create mode 100644 frame/nfts/src/common_functions.rs create mode 100644 frame/nfts/src/features/metadata.rs delete mode 100644 frame/nfts/src/functions.rs diff --git a/frame/nfts/src/common_functions.rs b/frame/nfts/src/common_functions.rs new file mode 100644 index 0000000000000..61b2965867505 --- /dev/null +++ b/frame/nfts/src/common_functions.rs @@ -0,0 +1,32 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Various pieces of common functionality. + +use super::*; + +impl, I: 'static> Pallet { + #[cfg(any(test, feature = "runtime-benchmarks"))] + pub fn set_next_id(id: T::CollectionId) { + NextCollectionId::::set(Some(id)); + } + + #[cfg(test)] + pub fn get_next_id() -> T::CollectionId { + NextCollectionId::::get().unwrap_or(T::CollectionId::initial_value()) + } +} diff --git a/frame/nfts/src/features/metadata.rs b/frame/nfts/src/features/metadata.rs new file mode 100644 index 0000000000000..0092a70f34767 --- /dev/null +++ b/frame/nfts/src/features/metadata.rs @@ -0,0 +1,173 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::*; +use frame_support::pallet_prelude::*; + +impl, I: 'static> Pallet { + pub(crate) fn do_set_item_metadata( + maybe_check_owner: Option, + collection: T::CollectionId, + item: T::ItemId, + data: BoundedVec, + ) -> DispatchResult { + let mut collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + + let item_config = Self::get_item_config(&collection, &item)?; + ensure!( + maybe_check_owner.is_none() || + item_config.is_setting_enabled(ItemSetting::UnlockedMetadata), + Error::::LockedItemMetadata + ); + + if let Some(check_owner) = &maybe_check_owner { + ensure!(check_owner == &collection_details.owner, Error::::NoPermission); + } + + let collection_config = Self::get_collection_config(&collection)?; + + ItemMetadataOf::::try_mutate_exists(collection, item, |metadata| { + if metadata.is_none() { + collection_details.item_metadatas.saturating_inc(); + } + let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit); + collection_details.total_deposit.saturating_reduce(old_deposit); + let mut deposit = Zero::zero(); + if collection_config.is_setting_enabled(CollectionSetting::DepositRequired) && + maybe_check_owner.is_some() + { + deposit = T::DepositPerByte::get() + .saturating_mul(((data.len()) as u32).into()) + .saturating_add(T::MetadataDepositBase::get()); + } + if deposit > old_deposit { + T::Currency::reserve(&collection_details.owner, deposit - old_deposit)?; + } else if deposit < old_deposit { + T::Currency::unreserve(&collection_details.owner, old_deposit - deposit); + } + collection_details.total_deposit.saturating_accrue(deposit); + + *metadata = Some(ItemMetadata { deposit, data: data.clone() }); + + Collection::::insert(&collection, &collection_details); + Self::deposit_event(Event::MetadataSet { collection, item, data }); + Ok(()) + }) + } + + pub(crate) fn do_clear_item_metadata( + maybe_check_owner: Option, + collection: T::CollectionId, + item: T::ItemId, + ) -> DispatchResult { + let mut collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + if let Some(check_owner) = &maybe_check_owner { + ensure!(check_owner == &collection_details.owner, Error::::NoPermission); + } + + // NOTE: if the item was previously burned, the ItemConfigOf record might not exists + let is_locked = Self::get_item_config(&collection, &item) + .map_or(false, |c| c.has_disabled_setting(ItemSetting::UnlockedMetadata)); + + ensure!(maybe_check_owner.is_none() || !is_locked, Error::::LockedItemMetadata); + + ItemMetadataOf::::try_mutate_exists(collection, item, |metadata| { + if metadata.is_some() { + collection_details.item_metadatas.saturating_dec(); + } + let deposit = metadata.take().ok_or(Error::::UnknownItem)?.deposit; + T::Currency::unreserve(&collection_details.owner, deposit); + collection_details.total_deposit.saturating_reduce(deposit); + + Collection::::insert(&collection, &collection_details); + Self::deposit_event(Event::MetadataCleared { collection, item }); + Ok(()) + }) + } + + pub(crate) fn do_set_collection_metadata( + maybe_check_owner: Option, + collection: T::CollectionId, + data: BoundedVec, + ) -> DispatchResult { + let collection_config = Self::get_collection_config(&collection)?; + ensure!( + maybe_check_owner.is_none() || + collection_config.is_setting_enabled(CollectionSetting::UnlockedMetadata), + Error::::LockedCollectionMetadata + ); + + let mut details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + if let Some(check_owner) = &maybe_check_owner { + ensure!(check_owner == &details.owner, Error::::NoPermission); + } + + CollectionMetadataOf::::try_mutate_exists(collection, |metadata| { + let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit); + details.total_deposit.saturating_reduce(old_deposit); + let mut deposit = Zero::zero(); + if maybe_check_owner.is_some() && + collection_config.is_setting_enabled(CollectionSetting::DepositRequired) + { + deposit = T::DepositPerByte::get() + .saturating_mul(((data.len()) as u32).into()) + .saturating_add(T::MetadataDepositBase::get()); + } + if deposit > old_deposit { + T::Currency::reserve(&details.owner, deposit - old_deposit)?; + } else if deposit < old_deposit { + T::Currency::unreserve(&details.owner, old_deposit - deposit); + } + details.total_deposit.saturating_accrue(deposit); + + Collection::::insert(&collection, details); + + *metadata = Some(CollectionMetadata { deposit, data: data.clone() }); + + Self::deposit_event(Event::CollectionMetadataSet { collection, data }); + Ok(()) + }) + } + + pub(crate) fn do_clear_collection_metadata( + maybe_check_owner: Option, + collection: T::CollectionId, + ) -> DispatchResult { + let details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + if let Some(check_owner) = &maybe_check_owner { + ensure!(check_owner == &details.owner, Error::::NoPermission); + } + + let collection_config = Self::get_collection_config(&collection)?; + ensure!( + maybe_check_owner.is_none() || + collection_config.is_setting_enabled(CollectionSetting::UnlockedMetadata), + Error::::LockedCollectionMetadata + ); + + CollectionMetadataOf::::try_mutate_exists(collection, |metadata| { + let deposit = metadata.take().ok_or(Error::::UnknownCollection)?.deposit; + T::Currency::unreserve(&details.owner, deposit); + Self::deposit_event(Event::CollectionMetadataCleared { collection }); + Ok(()) + }) + } +} diff --git a/frame/nfts/src/features/mod.rs b/frame/nfts/src/features/mod.rs index 5c67824878303..5453dea895cc7 100644 --- a/frame/nfts/src/features/mod.rs +++ b/frame/nfts/src/features/mod.rs @@ -19,5 +19,6 @@ pub mod approvals; pub mod atomic_swap; pub mod buy_sell; pub mod lock; +pub mod metadata; pub mod roles; pub mod settings; diff --git a/frame/nfts/src/functions.rs b/frame/nfts/src/functions.rs deleted file mode 100644 index 9adc406a46fd1..0000000000000 --- a/frame/nfts/src/functions.rs +++ /dev/null @@ -1,286 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Various pieces of common functionality. - -use super::*; -use frame_support::{ensure, traits::Get}; -use sp_runtime::{DispatchError, DispatchResult}; - -impl, I: 'static> Pallet { - pub fn do_transfer( - collection: T::CollectionId, - item: T::ItemId, - dest: T::AccountId, - with_details: impl FnOnce( - &CollectionDetailsFor, - &mut ItemDetailsFor, - ) -> DispatchResult, - ) -> DispatchResult { - let collection_details = - Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - ensure!(!T::Locker::is_locked(collection, item), Error::::ItemLocked); - - let collection_config = Self::get_collection_config(&collection)?; - ensure!( - collection_config.is_setting_enabled(CollectionSetting::TransferableItems), - Error::::ItemsNonTransferable - ); - - let item_config = Self::get_item_config(&collection, &item)?; - ensure!( - item_config.is_setting_enabled(ItemSetting::Transferable), - Error::::ItemLocked - ); - - let mut details = - Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; - with_details(&collection_details, &mut details)?; - - if details.deposit.account == details.owner { - // Move the deposit to the new owner. - T::Currency::repatriate_reserved( - &details.owner, - &dest, - details.deposit.amount, - Reserved, - )?; - } - - Account::::remove((&details.owner, &collection, &item)); - Account::::insert((&dest, &collection, &item), ()); - let origin = details.owner; - details.owner = dest; - - // The approved accounts have to be reset to None, because otherwise pre-approve attack - // would be possible, where the owner can approve his second account before making the - // transaction and then claiming the item back. - details.approvals.clear(); - - Item::::insert(&collection, &item, &details); - ItemPriceOf::::remove(&collection, &item); - PendingSwapOf::::remove(&collection, &item); - - Self::deposit_event(Event::Transferred { - collection, - item, - from: origin, - to: details.owner, - }); - Ok(()) - } - - pub fn do_create_collection( - collection: T::CollectionId, - owner: T::AccountId, - admin: T::AccountId, - config: CollectionConfigFor, - deposit: DepositBalanceOf, - event: Event, - ) -> DispatchResult { - ensure!(!Collection::::contains_key(collection), Error::::CollectionIdInUse); - - T::Currency::reserve(&owner, deposit)?; - - Collection::::insert( - collection, - CollectionDetails { - owner: owner.clone(), - total_deposit: deposit, - items: 0, - item_metadatas: 0, - attributes: 0, - }, - ); - CollectionRoleOf::::insert( - collection, - admin, - CollectionRoles( - CollectionRole::Admin | CollectionRole::Freezer | CollectionRole::Issuer, - ), - ); - - let next_id = collection.increment(); - - CollectionConfigOf::::insert(&collection, config); - CollectionAccount::::insert(&owner, &collection, ()); - NextCollectionId::::set(Some(next_id)); - - Self::deposit_event(Event::NextCollectionIdIncremented { next_id }); - Self::deposit_event(event); - Ok(()) - } - - pub fn do_destroy_collection( - collection: T::CollectionId, - witness: DestroyWitness, - maybe_check_owner: Option, - ) -> Result { - Collection::::try_mutate_exists(collection, |maybe_details| { - let collection_details = - maybe_details.take().ok_or(Error::::UnknownCollection)?; - if let Some(check_owner) = maybe_check_owner { - ensure!(collection_details.owner == check_owner, Error::::NoPermission); - } - ensure!(collection_details.items == witness.items, Error::::BadWitness); - ensure!( - collection_details.item_metadatas == witness.item_metadatas, - Error::::BadWitness - ); - ensure!(collection_details.attributes == witness.attributes, Error::::BadWitness); - - for (item, details) in Item::::drain_prefix(&collection) { - Account::::remove((&details.owner, &collection, &item)); - T::Currency::unreserve(&details.deposit.account, details.deposit.amount); - } - #[allow(deprecated)] - ItemMetadataOf::::remove_prefix(&collection, None); - #[allow(deprecated)] - ItemPriceOf::::remove_prefix(&collection, None); - #[allow(deprecated)] - PendingSwapOf::::remove_prefix(&collection, None); - CollectionMetadataOf::::remove(&collection); - Self::clear_roles(&collection)?; - #[allow(deprecated)] - Attribute::::remove_prefix((&collection,), None); - CollectionAccount::::remove(&collection_details.owner, &collection); - T::Currency::unreserve(&collection_details.owner, collection_details.total_deposit); - CollectionConfigOf::::remove(&collection); - let _ = ItemConfigOf::::clear_prefix(&collection, witness.items, None); - - Self::deposit_event(Event::Destroyed { collection }); - - Ok(DestroyWitness { - items: collection_details.items, - item_metadatas: collection_details.item_metadatas, - attributes: collection_details.attributes, - }) - }) - } - - pub fn do_mint( - collection: T::CollectionId, - item: T::ItemId, - owner: T::AccountId, - item_config: ItemConfig, - deposit_collection_owner: bool, - with_details_and_config: impl FnOnce( - &CollectionDetailsFor, - &CollectionConfigFor, - ) -> DispatchResult, - ) -> DispatchResult { - ensure!(!Item::::contains_key(collection, item), Error::::AlreadyExists); - - Collection::::try_mutate( - &collection, - |maybe_collection_details| -> DispatchResult { - let collection_details = - maybe_collection_details.as_mut().ok_or(Error::::UnknownCollection)?; - - let collection_config = Self::get_collection_config(&collection)?; - with_details_and_config(collection_details, &collection_config)?; - - if let Some(max_supply) = collection_config.max_supply { - ensure!(collection_details.items < max_supply, Error::::MaxSupplyReached); - } - - let items = - collection_details.items.checked_add(1).ok_or(ArithmeticError::Overflow)?; - collection_details.items = items; - - let collection_config = Self::get_collection_config(&collection)?; - let deposit_amount = match collection_config - .is_setting_enabled(CollectionSetting::DepositRequired) - { - true => T::ItemDeposit::get(), - false => Zero::zero(), - }; - let deposit_account = match deposit_collection_owner { - true => collection_details.owner.clone(), - false => owner.clone(), - }; - - let owner = owner.clone(); - Account::::insert((&owner, &collection, &item), ()); - - if let Ok(existing_config) = ItemConfigOf::::try_get(&collection, &item) { - ensure!(existing_config == item_config, Error::::InconsistentItemConfig); - } else { - ItemConfigOf::::insert(&collection, &item, item_config); - } - - T::Currency::reserve(&deposit_account, deposit_amount)?; - - let deposit = ItemDeposit { account: deposit_account, amount: deposit_amount }; - let details = - ItemDetails { owner, approvals: ApprovalsOf::::default(), deposit }; - Item::::insert(&collection, &item, details); - Ok(()) - }, - )?; - - Self::deposit_event(Event::Issued { collection, item, owner }); - Ok(()) - } - - pub fn do_burn( - collection: T::CollectionId, - item: T::ItemId, - with_details: impl FnOnce(&ItemDetailsFor) -> DispatchResult, - ) -> DispatchResult { - let owner = Collection::::try_mutate( - &collection, - |maybe_collection_details| -> Result { - let collection_details = - maybe_collection_details.as_mut().ok_or(Error::::UnknownCollection)?; - let details = Item::::get(&collection, &item) - .ok_or(Error::::UnknownCollection)?; - with_details(&details)?; - - // Return the deposit. - T::Currency::unreserve(&details.deposit.account, details.deposit.amount); - collection_details.items.saturating_dec(); - Ok(details.owner) - }, - )?; - - Item::::remove(&collection, &item); - Account::::remove((&owner, &collection, &item)); - ItemPriceOf::::remove(&collection, &item); - PendingSwapOf::::remove(&collection, &item); - - // NOTE: if item's settings are not empty (e.g. item's metadata is locked) - // then we keep the record and don't remove it - let config = Self::get_item_config(&collection, &item)?; - if !config.has_disabled_settings() { - ItemConfigOf::::remove(&collection, &item); - } - - Self::deposit_event(Event::Burned { collection, item, owner }); - Ok(()) - } - - #[cfg(any(test, feature = "runtime-benchmarks"))] - pub fn set_next_id(id: T::CollectionId) { - NextCollectionId::::set(Some(id)); - } - - #[cfg(test)] - pub fn get_next_id() -> T::CollectionId { - NextCollectionId::::get().unwrap_or(T::CollectionId::initial_value()) - } -} diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 98d19e9aed827..e7315eb1df4c9 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -1451,50 +1451,7 @@ pub mod pallet { let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some))?; - - let mut collection_details = - Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - - let item_config = Self::get_item_config(&collection, &item)?; - ensure!( - maybe_check_owner.is_none() || - item_config.is_setting_enabled(ItemSetting::UnlockedMetadata), - Error::::LockedItemMetadata - ); - - if let Some(check_owner) = &maybe_check_owner { - ensure!(check_owner == &collection_details.owner, Error::::NoPermission); - } - - let collection_config = Self::get_collection_config(&collection)?; - - ItemMetadataOf::::try_mutate_exists(collection, item, |metadata| { - if metadata.is_none() { - collection_details.item_metadatas.saturating_inc(); - } - let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit); - collection_details.total_deposit.saturating_reduce(old_deposit); - let mut deposit = Zero::zero(); - if collection_config.is_setting_enabled(CollectionSetting::DepositRequired) && - maybe_check_owner.is_some() - { - deposit = T::DepositPerByte::get() - .saturating_mul(((data.len()) as u32).into()) - .saturating_add(T::MetadataDepositBase::get()); - } - if deposit > old_deposit { - T::Currency::reserve(&collection_details.owner, deposit - old_deposit)?; - } else if deposit < old_deposit { - T::Currency::unreserve(&collection_details.owner, old_deposit - deposit); - } - collection_details.total_deposit.saturating_accrue(deposit); - - *metadata = Some(ItemMetadata { deposit, data: data.clone() }); - - Collection::::insert(&collection, &collection_details); - Self::deposit_event(Event::MetadataSet { collection, item, data }); - Ok(()) - }) + Self::do_set_item_metadata(maybe_check_owner, collection, item, data) } /// Clear the metadata for an item. @@ -1519,31 +1476,7 @@ pub mod pallet { let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some))?; - - let mut collection_details = - Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - if let Some(check_owner) = &maybe_check_owner { - ensure!(check_owner == &collection_details.owner, Error::::NoPermission); - } - - // NOTE: if the item was previously burned, the ItemConfigOf record might not exists - let is_locked = Self::get_item_config(&collection, &item) - .map_or(false, |c| c.has_disabled_setting(ItemSetting::UnlockedMetadata)); - - ensure!(maybe_check_owner.is_none() || !is_locked, Error::::LockedItemMetadata); - - ItemMetadataOf::::try_mutate_exists(collection, item, |metadata| { - if metadata.is_some() { - collection_details.item_metadatas.saturating_dec(); - } - let deposit = metadata.take().ok_or(Error::::UnknownItem)?.deposit; - T::Currency::unreserve(&collection_details.owner, deposit); - collection_details.total_deposit.saturating_reduce(deposit); - - Collection::::insert(&collection, &collection_details); - Self::deposit_event(Event::MetadataCleared { collection, item }); - Ok(()) - }) + Self::do_clear_item_metadata(maybe_check_owner, collection, item) } /// Set the metadata for a collection. @@ -1570,45 +1503,7 @@ pub mod pallet { let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some))?; - - let collection_config = Self::get_collection_config(&collection)?; - ensure!( - maybe_check_owner.is_none() || - collection_config.is_setting_enabled(CollectionSetting::UnlockedMetadata), - Error::::LockedCollectionMetadata - ); - - let mut details = - Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - if let Some(check_owner) = &maybe_check_owner { - ensure!(check_owner == &details.owner, Error::::NoPermission); - } - - CollectionMetadataOf::::try_mutate_exists(collection, |metadata| { - let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit); - details.total_deposit.saturating_reduce(old_deposit); - let mut deposit = Zero::zero(); - if maybe_check_owner.is_some() && - collection_config.is_setting_enabled(CollectionSetting::DepositRequired) - { - deposit = T::DepositPerByte::get() - .saturating_mul(((data.len()) as u32).into()) - .saturating_add(T::MetadataDepositBase::get()); - } - if deposit > old_deposit { - T::Currency::reserve(&details.owner, deposit - old_deposit)?; - } else if deposit < old_deposit { - T::Currency::unreserve(&details.owner, old_deposit - deposit); - } - details.total_deposit.saturating_accrue(deposit); - - Collection::::insert(&collection, details); - - *metadata = Some(CollectionMetadata { deposit, data: data.clone() }); - - Self::deposit_event(Event::CollectionMetadataSet { collection, data }); - Ok(()) - }) + Self::do_set_collection_metadata(maybe_check_owner, collection, data) } /// Clear the metadata for a collection. @@ -1631,26 +1526,7 @@ pub mod pallet { let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some))?; - - let details = - Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - if let Some(check_owner) = &maybe_check_owner { - ensure!(check_owner == &details.owner, Error::::NoPermission); - } - - let collection_config = Self::get_collection_config(&collection)?; - ensure!( - maybe_check_owner.is_none() || - collection_config.is_setting_enabled(CollectionSetting::UnlockedMetadata), - Error::::LockedCollectionMetadata - ); - - CollectionMetadataOf::::try_mutate_exists(collection, |metadata| { - let deposit = metadata.take().ok_or(Error::::UnknownCollection)?.deposit; - T::Currency::unreserve(&details.owner, deposit); - Self::deposit_event(Event::CollectionMetadataCleared { collection }); - Ok(()) - }) + Self::do_clear_collection_metadata(maybe_check_owner, collection) } /// Set (or reset) the acceptance of ownership for a particular account. From 35d40bb0c776b18a2e1c5e447b75f1785781c63d Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Thu, 27 Oct 2022 10:25:38 +0200 Subject: [PATCH 4/5] Move the rest of methods to feature files --- frame/nfts/src/common_functions.rs | 10 + frame/nfts/src/features/attributes.rs | 123 +++++++++ .../src/features/create_delete_collection.rs | 109 ++++++++ frame/nfts/src/features/create_delete_item.rs | 123 +++++++++ frame/nfts/src/features/mod.rs | 4 + frame/nfts/src/features/roles.rs | 28 ++ frame/nfts/src/features/settings.rs | 89 ++++++- frame/nfts/src/features/transfer.rs | 138 ++++++++++ frame/nfts/src/lib.rs | 243 +----------------- 9 files changed, 633 insertions(+), 234 deletions(-) create mode 100644 frame/nfts/src/features/attributes.rs create mode 100644 frame/nfts/src/features/create_delete_collection.rs create mode 100644 frame/nfts/src/features/create_delete_item.rs create mode 100644 frame/nfts/src/features/transfer.rs diff --git a/frame/nfts/src/common_functions.rs b/frame/nfts/src/common_functions.rs index 61b2965867505..b3cac7f69ec0e 100644 --- a/frame/nfts/src/common_functions.rs +++ b/frame/nfts/src/common_functions.rs @@ -20,6 +20,16 @@ use super::*; impl, I: 'static> Pallet { + /// Get the owner of the item, if the item exists. + pub fn owner(collection: T::CollectionId, item: T::ItemId) -> Option { + Item::::get(collection, item).map(|i| i.owner) + } + + /// Get the owner of the item, if the item exists. + pub fn collection_owner(collection: T::CollectionId) -> Option { + Collection::::get(collection).map(|i| i.owner) + } + #[cfg(any(test, feature = "runtime-benchmarks"))] pub fn set_next_id(id: T::CollectionId) { NextCollectionId::::set(Some(id)); diff --git a/frame/nfts/src/features/attributes.rs b/frame/nfts/src/features/attributes.rs new file mode 100644 index 0000000000000..6871ea51667aa --- /dev/null +++ b/frame/nfts/src/features/attributes.rs @@ -0,0 +1,123 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::*; +use frame_support::pallet_prelude::*; + +impl, I: 'static> Pallet { + pub(crate) fn do_set_attribute( + maybe_check_owner: Option, + collection: T::CollectionId, + maybe_item: Option, + key: BoundedVec, + value: BoundedVec, + ) -> DispatchResult { + ensure!( + Self::is_pallet_feature_enabled(PalletFeature::Attributes), + Error::::MethodDisabled + ); + + let mut collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + + if let Some(check_owner) = &maybe_check_owner { + ensure!(check_owner == &collection_details.owner, Error::::NoPermission); + } + + let collection_config = Self::get_collection_config(&collection)?; + match maybe_item { + None => { + ensure!( + collection_config.is_setting_enabled(CollectionSetting::UnlockedAttributes), + Error::::LockedCollectionAttributes + ) + }, + Some(item) => { + let maybe_is_locked = Self::get_item_config(&collection, &item) + .map(|c| c.has_disabled_setting(ItemSetting::UnlockedAttributes))?; + ensure!(!maybe_is_locked, Error::::LockedItemAttributes); + }, + }; + + let attribute = Attribute::::get((collection, maybe_item, &key)); + if attribute.is_none() { + collection_details.attributes.saturating_inc(); + } + let old_deposit = attribute.map_or(Zero::zero(), |m| m.1); + collection_details.total_deposit.saturating_reduce(old_deposit); + let mut deposit = Zero::zero(); + if collection_config.is_setting_enabled(CollectionSetting::DepositRequired) && + maybe_check_owner.is_some() + { + deposit = T::DepositPerByte::get() + .saturating_mul(((key.len() + value.len()) as u32).into()) + .saturating_add(T::AttributeDepositBase::get()); + } + collection_details.total_deposit.saturating_accrue(deposit); + if deposit > old_deposit { + T::Currency::reserve(&collection_details.owner, deposit - old_deposit)?; + } else if deposit < old_deposit { + T::Currency::unreserve(&collection_details.owner, old_deposit - deposit); + } + + Attribute::::insert((&collection, maybe_item, &key), (&value, deposit)); + Collection::::insert(collection, &collection_details); + Self::deposit_event(Event::AttributeSet { collection, maybe_item, key, value }); + Ok(()) + } + + pub(crate) fn do_clear_attribute( + maybe_check_owner: Option, + collection: T::CollectionId, + maybe_item: Option, + key: BoundedVec, + ) -> DispatchResult { + let mut collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + if let Some(check_owner) = &maybe_check_owner { + ensure!(check_owner == &collection_details.owner, Error::::NoPermission); + } + + if maybe_check_owner.is_some() { + match maybe_item { + None => { + let collection_config = Self::get_collection_config(&collection)?; + ensure!( + collection_config.is_setting_enabled(CollectionSetting::UnlockedAttributes), + Error::::LockedCollectionAttributes + ) + }, + Some(item) => { + // NOTE: if the item was previously burned, the ItemConfigOf record might + // not exists. In that case, we allow to clear the attribute. + let maybe_is_locked = Self::get_item_config(&collection, &item) + .map_or(false, |c| c.has_disabled_setting(ItemSetting::UnlockedAttributes)); + ensure!(!maybe_is_locked, Error::::LockedItemAttributes); + }, + }; + } + + if let Some((_, deposit)) = Attribute::::take((collection, maybe_item, &key)) { + collection_details.attributes.saturating_dec(); + collection_details.total_deposit.saturating_reduce(deposit); + T::Currency::unreserve(&collection_details.owner, deposit); + Collection::::insert(collection, &collection_details); + Self::deposit_event(Event::AttributeCleared { collection, maybe_item, key }); + } + Ok(()) + } +} diff --git a/frame/nfts/src/features/create_delete_collection.rs b/frame/nfts/src/features/create_delete_collection.rs new file mode 100644 index 0000000000000..b9530e88b18cd --- /dev/null +++ b/frame/nfts/src/features/create_delete_collection.rs @@ -0,0 +1,109 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::*; +use frame_support::pallet_prelude::*; + +impl, I: 'static> Pallet { + pub fn do_create_collection( + collection: T::CollectionId, + owner: T::AccountId, + admin: T::AccountId, + config: CollectionConfigFor, + deposit: DepositBalanceOf, + event: Event, + ) -> DispatchResult { + ensure!(!Collection::::contains_key(collection), Error::::CollectionIdInUse); + + T::Currency::reserve(&owner, deposit)?; + + Collection::::insert( + collection, + CollectionDetails { + owner: owner.clone(), + total_deposit: deposit, + items: 0, + item_metadatas: 0, + attributes: 0, + }, + ); + CollectionRoleOf::::insert( + collection, + admin, + CollectionRoles( + CollectionRole::Admin | CollectionRole::Freezer | CollectionRole::Issuer, + ), + ); + + let next_id = collection.increment(); + + CollectionConfigOf::::insert(&collection, config); + CollectionAccount::::insert(&owner, &collection, ()); + NextCollectionId::::set(Some(next_id)); + + Self::deposit_event(Event::NextCollectionIdIncremented { next_id }); + Self::deposit_event(event); + Ok(()) + } + + pub fn do_destroy_collection( + collection: T::CollectionId, + witness: DestroyWitness, + maybe_check_owner: Option, + ) -> Result { + Collection::::try_mutate_exists(collection, |maybe_details| { + let collection_details = + maybe_details.take().ok_or(Error::::UnknownCollection)?; + if let Some(check_owner) = maybe_check_owner { + ensure!(collection_details.owner == check_owner, Error::::NoPermission); + } + ensure!(collection_details.items == witness.items, Error::::BadWitness); + ensure!( + collection_details.item_metadatas == witness.item_metadatas, + Error::::BadWitness + ); + ensure!(collection_details.attributes == witness.attributes, Error::::BadWitness); + + for (item, details) in Item::::drain_prefix(&collection) { + Account::::remove((&details.owner, &collection, &item)); + T::Currency::unreserve(&details.deposit.account, details.deposit.amount); + } + #[allow(deprecated)] + ItemMetadataOf::::remove_prefix(&collection, None); + #[allow(deprecated)] + ItemPriceOf::::remove_prefix(&collection, None); + #[allow(deprecated)] + PendingSwapOf::::remove_prefix(&collection, None); + CollectionMetadataOf::::remove(&collection); + Self::clear_roles(&collection)?; + #[allow(deprecated)] + Attribute::::remove_prefix((&collection,), None); + CollectionAccount::::remove(&collection_details.owner, &collection); + T::Currency::unreserve(&collection_details.owner, collection_details.total_deposit); + CollectionConfigOf::::remove(&collection); + let _ = ItemConfigOf::::clear_prefix(&collection, witness.items, None); + + Self::deposit_event(Event::Destroyed { collection }); + + Ok(DestroyWitness { + items: collection_details.items, + item_metadatas: collection_details.item_metadatas, + attributes: collection_details.attributes, + }) + }) + } +} diff --git a/frame/nfts/src/features/create_delete_item.rs b/frame/nfts/src/features/create_delete_item.rs new file mode 100644 index 0000000000000..10670f4b10c1c --- /dev/null +++ b/frame/nfts/src/features/create_delete_item.rs @@ -0,0 +1,123 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::*; +use frame_support::pallet_prelude::*; + +impl, I: 'static> Pallet { + pub fn do_mint( + collection: T::CollectionId, + item: T::ItemId, + owner: T::AccountId, + item_config: ItemConfig, + deposit_collection_owner: bool, + with_details_and_config: impl FnOnce( + &CollectionDetailsFor, + &CollectionConfigFor, + ) -> DispatchResult, + ) -> DispatchResult { + ensure!(!Item::::contains_key(collection, item), Error::::AlreadyExists); + + Collection::::try_mutate( + &collection, + |maybe_collection_details| -> DispatchResult { + let collection_details = + maybe_collection_details.as_mut().ok_or(Error::::UnknownCollection)?; + + let collection_config = Self::get_collection_config(&collection)?; + with_details_and_config(collection_details, &collection_config)?; + + if let Some(max_supply) = collection_config.max_supply { + ensure!(collection_details.items < max_supply, Error::::MaxSupplyReached); + } + + let items = + collection_details.items.checked_add(1).ok_or(ArithmeticError::Overflow)?; + collection_details.items = items; + + let collection_config = Self::get_collection_config(&collection)?; + let deposit_amount = match collection_config + .is_setting_enabled(CollectionSetting::DepositRequired) + { + true => T::ItemDeposit::get(), + false => Zero::zero(), + }; + let deposit_account = match deposit_collection_owner { + true => collection_details.owner.clone(), + false => owner.clone(), + }; + + let owner = owner.clone(); + Account::::insert((&owner, &collection, &item), ()); + + if let Ok(existing_config) = ItemConfigOf::::try_get(&collection, &item) { + ensure!(existing_config == item_config, Error::::InconsistentItemConfig); + } else { + ItemConfigOf::::insert(&collection, &item, item_config); + } + + T::Currency::reserve(&deposit_account, deposit_amount)?; + + let deposit = ItemDeposit { account: deposit_account, amount: deposit_amount }; + let details = + ItemDetails { owner, approvals: ApprovalsOf::::default(), deposit }; + Item::::insert(&collection, &item, details); + Ok(()) + }, + )?; + + Self::deposit_event(Event::Issued { collection, item, owner }); + Ok(()) + } + + pub fn do_burn( + collection: T::CollectionId, + item: T::ItemId, + with_details: impl FnOnce(&ItemDetailsFor) -> DispatchResult, + ) -> DispatchResult { + let owner = Collection::::try_mutate( + &collection, + |maybe_collection_details| -> Result { + let collection_details = + maybe_collection_details.as_mut().ok_or(Error::::UnknownCollection)?; + let details = Item::::get(&collection, &item) + .ok_or(Error::::UnknownCollection)?; + with_details(&details)?; + + // Return the deposit. + T::Currency::unreserve(&details.deposit.account, details.deposit.amount); + collection_details.items.saturating_dec(); + Ok(details.owner) + }, + )?; + + Item::::remove(&collection, &item); + Account::::remove((&owner, &collection, &item)); + ItemPriceOf::::remove(&collection, &item); + PendingSwapOf::::remove(&collection, &item); + + // NOTE: if item's settings are not empty (e.g. item's metadata is locked) + // then we keep the record and don't remove it + let config = Self::get_item_config(&collection, &item)?; + if !config.has_disabled_settings() { + ItemConfigOf::::remove(&collection, &item); + } + + Self::deposit_event(Event::Burned { collection, item, owner }); + Ok(()) + } +} diff --git a/frame/nfts/src/features/mod.rs b/frame/nfts/src/features/mod.rs index 5453dea895cc7..b77ee9bf2491b 100644 --- a/frame/nfts/src/features/mod.rs +++ b/frame/nfts/src/features/mod.rs @@ -17,8 +17,12 @@ pub mod approvals; pub mod atomic_swap; +pub mod attributes; pub mod buy_sell; +pub mod create_delete_collection; +pub mod create_delete_item; pub mod lock; pub mod metadata; pub mod roles; pub mod settings; +pub mod transfer; diff --git a/frame/nfts/src/features/roles.rs b/frame/nfts/src/features/roles.rs index e961779725b6e..7f6f42363fe33 100644 --- a/frame/nfts/src/features/roles.rs +++ b/frame/nfts/src/features/roles.rs @@ -20,6 +20,34 @@ use frame_support::pallet_prelude::*; use sp_std::collections::btree_map::BTreeMap; impl, I: 'static> Pallet { + pub(crate) fn do_set_team( + origin: T::AccountId, + collection: T::CollectionId, + issuer: T::AccountId, + admin: T::AccountId, + freezer: T::AccountId, + ) -> DispatchResult { + Collection::::try_mutate(collection, |maybe_details| { + let details = maybe_details.as_mut().ok_or(Error::::UnknownCollection)?; + ensure!(origin == details.owner, Error::::NoPermission); + + // delete previous values + Self::clear_roles(&collection)?; + + let account_to_role = Self::group_roles_by_account(vec![ + (issuer.clone(), CollectionRole::Issuer), + (admin.clone(), CollectionRole::Admin), + (freezer.clone(), CollectionRole::Freezer), + ]); + for (account, roles) in account_to_role { + CollectionRoleOf::::insert(&collection, &account, roles); + } + + Self::deposit_event(Event::TeamChanged { collection, issuer, admin, freezer }); + Ok(()) + }) + } + /// Clears all the roles in a specified collection. /// /// - `collection_id`: A collection to clear the roles in. diff --git a/frame/nfts/src/features/settings.rs b/frame/nfts/src/features/settings.rs index 3d80a38ab99eb..59444f728140c 100644 --- a/frame/nfts/src/features/settings.rs +++ b/frame/nfts/src/features/settings.rs @@ -18,11 +18,92 @@ use crate::*; use frame_support::pallet_prelude::*; -/// The helper methods bellow allow to read and validate different -/// collection/item/pallet settings. -/// For example, those settings allow to disable NFTs trading on a pallet level, or for a particular -/// collection, or for a specific item. impl, I: 'static> Pallet { + pub(crate) fn do_force_collection_status( + collection: T::CollectionId, + new_owner: T::AccountId, + issuer: T::AccountId, + admin: T::AccountId, + freezer: T::AccountId, + config: CollectionConfigFor, + ) -> DispatchResult { + Collection::::try_mutate(collection, |maybe_collection| { + let mut collection_info = + maybe_collection.take().ok_or(Error::::UnknownCollection)?; + let old_owner = collection_info.owner; + collection_info.owner = new_owner.clone(); + *maybe_collection = Some(collection_info); + CollectionAccount::::remove(&old_owner, &collection); + CollectionAccount::::insert(&new_owner, &collection, ()); + CollectionConfigOf::::insert(&collection, config); + + // delete previous values + Self::clear_roles(&collection)?; + + let account_to_role = Self::group_roles_by_account(vec![ + (issuer, CollectionRole::Issuer), + (admin, CollectionRole::Admin), + (freezer, CollectionRole::Freezer), + ]); + for (account, roles) in account_to_role { + CollectionRoleOf::::insert(&collection, &account, roles); + } + + Self::deposit_event(Event::CollectionStatusChanged { collection }); + Ok(()) + }) + } + + pub(crate) fn do_set_collection_max_supply( + maybe_check_owner: Option, + collection: T::CollectionId, + max_supply: u32, + ) -> DispatchResult { + let collection_config = Self::get_collection_config(&collection)?; + ensure!( + collection_config.is_setting_enabled(CollectionSetting::UnlockedMaxSupply), + Error::::MaxSupplyLocked + ); + + let details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + if let Some(check_owner) = &maybe_check_owner { + ensure!(check_owner == &details.owner, Error::::NoPermission); + } + + ensure!(details.items <= max_supply, Error::::MaxSupplyTooSmall); + + CollectionConfigOf::::try_mutate(collection, |maybe_config| { + let config = maybe_config.as_mut().ok_or(Error::::NoConfig)?; + config.max_supply = Some(max_supply); + Self::deposit_event(Event::CollectionMaxSupplySet { collection, max_supply }); + Ok(()) + }) + } + + pub(crate) fn do_update_mint_settings( + maybe_check_owner: Option, + collection: T::CollectionId, + mint_settings: MintSettings< + BalanceOf, + ::BlockNumber, + T::CollectionId, + >, + ) -> DispatchResult { + let details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + if let Some(check_owner) = &maybe_check_owner { + ensure!(check_owner == &details.owner, Error::::NoPermission); + } + + CollectionConfigOf::::try_mutate(collection, |maybe_config| { + let config = maybe_config.as_mut().ok_or(Error::::NoConfig)?; + config.mint_settings = mint_settings; + Self::deposit_event(Event::CollectionMintSettingsUpdated { collection }); + Ok(()) + }) + } + pub(crate) fn get_collection_config( collection_id: &T::CollectionId, ) -> Result, DispatchError> { diff --git a/frame/nfts/src/features/transfer.rs b/frame/nfts/src/features/transfer.rs new file mode 100644 index 0000000000000..f2040566d5f7a --- /dev/null +++ b/frame/nfts/src/features/transfer.rs @@ -0,0 +1,138 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::*; +use frame_support::pallet_prelude::*; + +impl, I: 'static> Pallet { + pub fn do_transfer( + collection: T::CollectionId, + item: T::ItemId, + dest: T::AccountId, + with_details: impl FnOnce( + &CollectionDetailsFor, + &mut ItemDetailsFor, + ) -> DispatchResult, + ) -> DispatchResult { + let collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + ensure!(!T::Locker::is_locked(collection, item), Error::::ItemLocked); + + let collection_config = Self::get_collection_config(&collection)?; + ensure!( + collection_config.is_setting_enabled(CollectionSetting::TransferableItems), + Error::::ItemsNonTransferable + ); + + let item_config = Self::get_item_config(&collection, &item)?; + ensure!( + item_config.is_setting_enabled(ItemSetting::Transferable), + Error::::ItemLocked + ); + + let mut details = + Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; + with_details(&collection_details, &mut details)?; + + if details.deposit.account == details.owner { + // Move the deposit to the new owner. + T::Currency::repatriate_reserved( + &details.owner, + &dest, + details.deposit.amount, + Reserved, + )?; + } + + Account::::remove((&details.owner, &collection, &item)); + Account::::insert((&dest, &collection, &item), ()); + let origin = details.owner; + details.owner = dest; + + // The approved accounts have to be reset to None, because otherwise pre-approve attack + // would be possible, where the owner can approve his second account before making the + // transaction and then claiming the item back. + details.approvals.clear(); + + Item::::insert(&collection, &item, &details); + ItemPriceOf::::remove(&collection, &item); + PendingSwapOf::::remove(&collection, &item); + + Self::deposit_event(Event::Transferred { + collection, + item, + from: origin, + to: details.owner, + }); + Ok(()) + } + + pub(crate) fn do_transfer_ownership( + origin: T::AccountId, + collection: T::CollectionId, + owner: T::AccountId, + ) -> DispatchResult { + let acceptable_collection = OwnershipAcceptance::::get(&owner); + ensure!(acceptable_collection.as_ref() == Some(&collection), Error::::Unaccepted); + + Collection::::try_mutate(collection, |maybe_details| { + let details = maybe_details.as_mut().ok_or(Error::::UnknownCollection)?; + ensure!(origin == details.owner, Error::::NoPermission); + if details.owner == owner { + return Ok(()) + } + + // Move the deposit to the new owner. + T::Currency::repatriate_reserved( + &details.owner, + &owner, + details.total_deposit, + Reserved, + )?; + CollectionAccount::::remove(&details.owner, &collection); + CollectionAccount::::insert(&owner, &collection, ()); + details.owner = owner.clone(); + OwnershipAcceptance::::remove(&owner); + + Self::deposit_event(Event::OwnerChanged { collection, new_owner: owner }); + Ok(()) + }) + } + + pub(crate) fn do_set_accept_ownership( + who: T::AccountId, + maybe_collection: Option, + ) -> DispatchResult { + let old = OwnershipAcceptance::::get(&who); + match (old.is_some(), maybe_collection.is_some()) { + (false, true) => { + frame_system::Pallet::::inc_consumers(&who)?; + }, + (true, false) => { + frame_system::Pallet::::dec_consumers(&who); + }, + _ => {}, + } + if let Some(collection) = maybe_collection.as_ref() { + OwnershipAcceptance::::insert(&who, collection); + } else { + OwnershipAcceptance::::remove(&who); + } + Self::deposit_event(Event::OwnershipAcceptanceChanged { who, maybe_collection }); + Ok(()) + } +} diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index e7315eb1df4c9..0bfa25548bf15 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -35,8 +35,8 @@ pub mod mock; #[cfg(test)] mod tests; +mod common_functions; mod features; -mod functions; mod impl_nonfungibles; mod types; @@ -557,18 +557,6 @@ pub mod pallet { MintEnded, } - impl, I: 'static> Pallet { - /// Get the owner of the item, if the item exists. - pub fn owner(collection: T::CollectionId, item: T::ItemId) -> Option { - Item::::get(collection, item).map(|i| i.owner) - } - - /// Get the owner of the item, if the item exists. - pub fn collection_owner(collection: T::CollectionId) -> Option { - Collection::::get(collection).map(|i| i.owner) - } - } - #[pallet::call] impl, I: 'static> Pallet { /// Issue a new collection of non-fungible items from a public origin. @@ -1025,32 +1013,7 @@ pub mod pallet { ) -> DispatchResult { let origin = ensure_signed(origin)?; let owner = T::Lookup::lookup(owner)?; - - let acceptable_collection = OwnershipAcceptance::::get(&owner); - ensure!(acceptable_collection.as_ref() == Some(&collection), Error::::Unaccepted); - - Collection::::try_mutate(collection, |maybe_details| { - let details = maybe_details.as_mut().ok_or(Error::::UnknownCollection)?; - ensure!(origin == details.owner, Error::::NoPermission); - if details.owner == owner { - return Ok(()) - } - - // Move the deposit to the new owner. - T::Currency::repatriate_reserved( - &details.owner, - &owner, - details.total_deposit, - Reserved, - )?; - CollectionAccount::::remove(&details.owner, &collection); - CollectionAccount::::insert(&owner, &collection, ()); - details.owner = owner.clone(); - OwnershipAcceptance::::remove(&owner); - - Self::deposit_event(Event::OwnerChanged { collection, new_owner: owner }); - Ok(()) - }) + Self::do_transfer_ownership(origin, collection, owner) } /// Change the Issuer, Admin and Freezer of a collection. @@ -1077,26 +1040,7 @@ pub mod pallet { let issuer = T::Lookup::lookup(issuer)?; let admin = T::Lookup::lookup(admin)?; let freezer = T::Lookup::lookup(freezer)?; - - Collection::::try_mutate(collection, |maybe_details| { - let details = maybe_details.as_mut().ok_or(Error::::UnknownCollection)?; - ensure!(origin == details.owner, Error::::NoPermission); - - // delete previous values - Self::clear_roles(&collection)?; - - let account_to_role = Self::group_roles_by_account(vec![ - (issuer.clone(), CollectionRole::Issuer), - (admin.clone(), CollectionRole::Admin), - (freezer.clone(), CollectionRole::Freezer), - ]); - for (account, roles) in account_to_role { - CollectionRoleOf::::insert(&collection, &account, roles); - } - - Self::deposit_event(Event::TeamChanged { collection, issuer, admin, freezer }); - Ok(()) - }) + Self::do_set_team(origin, collection, issuer, admin, freezer) } /// Approve an item to be transferred by a delegated third-party account. @@ -1213,37 +1157,11 @@ pub mod pallet { config: CollectionConfigFor, ) -> DispatchResult { T::ForceOrigin::ensure_origin(origin)?; - - Collection::::try_mutate(collection, |maybe_collection| { - let mut collection_info = - maybe_collection.take().ok_or(Error::::UnknownCollection)?; - let old_owner = collection_info.owner; - let new_owner = T::Lookup::lookup(owner)?; - collection_info.owner = new_owner.clone(); - *maybe_collection = Some(collection_info); - CollectionAccount::::remove(&old_owner, &collection); - CollectionAccount::::insert(&new_owner, &collection, ()); - CollectionConfigOf::::insert(&collection, config); - - let issuer = T::Lookup::lookup(issuer)?; - let admin = T::Lookup::lookup(admin)?; - let freezer = T::Lookup::lookup(freezer)?; - - // delete previous values - Self::clear_roles(&collection)?; - - let account_to_role = Self::group_roles_by_account(vec![ - (issuer, CollectionRole::Issuer), - (admin, CollectionRole::Admin), - (freezer, CollectionRole::Freezer), - ]); - for (account, roles) in account_to_role { - CollectionRoleOf::::insert(&collection, &account, roles); - } - - Self::deposit_event(Event::CollectionStatusChanged { collection }); - Ok(()) - }) + let new_owner = T::Lookup::lookup(owner)?; + let issuer = T::Lookup::lookup(issuer)?; + let admin = T::Lookup::lookup(admin)?; + let freezer = T::Lookup::lookup(freezer)?; + Self::do_force_collection_status(collection, new_owner, issuer, admin, freezer, config) } /// Disallows changing the metadata of attributes of the item. @@ -1305,61 +1223,10 @@ pub mod pallet { key: BoundedVec, value: BoundedVec, ) -> DispatchResult { - ensure!( - Self::is_pallet_feature_enabled(PalletFeature::Attributes), - Error::::MethodDisabled - ); let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some))?; - - let mut collection_details = - Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - - if let Some(check_owner) = &maybe_check_owner { - ensure!(check_owner == &collection_details.owner, Error::::NoPermission); - } - - let collection_config = Self::get_collection_config(&collection)?; - match maybe_item { - None => { - ensure!( - collection_config.is_setting_enabled(CollectionSetting::UnlockedAttributes), - Error::::LockedCollectionAttributes - ) - }, - Some(item) => { - let maybe_is_locked = Self::get_item_config(&collection, &item) - .map(|c| c.has_disabled_setting(ItemSetting::UnlockedAttributes))?; - ensure!(!maybe_is_locked, Error::::LockedItemAttributes); - }, - }; - - let attribute = Attribute::::get((collection, maybe_item, &key)); - if attribute.is_none() { - collection_details.attributes.saturating_inc(); - } - let old_deposit = attribute.map_or(Zero::zero(), |m| m.1); - collection_details.total_deposit.saturating_reduce(old_deposit); - let mut deposit = Zero::zero(); - if collection_config.is_setting_enabled(CollectionSetting::DepositRequired) && - maybe_check_owner.is_some() - { - deposit = T::DepositPerByte::get() - .saturating_mul(((key.len() + value.len()) as u32).into()) - .saturating_add(T::AttributeDepositBase::get()); - } - collection_details.total_deposit.saturating_accrue(deposit); - if deposit > old_deposit { - T::Currency::reserve(&collection_details.owner, deposit - old_deposit)?; - } else if deposit < old_deposit { - T::Currency::unreserve(&collection_details.owner, old_deposit - deposit); - } - - Attribute::::insert((&collection, maybe_item, &key), (&value, deposit)); - Collection::::insert(collection, &collection_details); - Self::deposit_event(Event::AttributeSet { collection, maybe_item, key, value }); - Ok(()) + Self::do_set_attribute(maybe_check_owner, collection, maybe_item, key, value) } /// Clear an attribute for a collection or item. @@ -1386,43 +1253,7 @@ pub mod pallet { let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some))?; - - let mut collection_details = - Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - if let Some(check_owner) = &maybe_check_owner { - ensure!(check_owner == &collection_details.owner, Error::::NoPermission); - } - - if maybe_check_owner.is_some() { - match maybe_item { - None => { - let collection_config = Self::get_collection_config(&collection)?; - ensure!( - collection_config - .is_setting_enabled(CollectionSetting::UnlockedAttributes), - Error::::LockedCollectionAttributes - ) - }, - Some(item) => { - // NOTE: if the item was previously burned, the ItemConfigOf record might - // not exists. In that case, we allow to clear the attribute. - let maybe_is_locked = Self::get_item_config(&collection, &item) - .map_or(false, |c| { - c.has_disabled_setting(ItemSetting::UnlockedAttributes) - }); - ensure!(!maybe_is_locked, Error::::LockedItemAttributes); - }, - }; - } - - if let Some((_, deposit)) = Attribute::::take((collection, maybe_item, &key)) { - collection_details.attributes.saturating_dec(); - collection_details.total_deposit.saturating_reduce(deposit); - T::Currency::unreserve(&collection_details.owner, deposit); - Collection::::insert(collection, &collection_details); - Self::deposit_event(Event::AttributeCleared { collection, maybe_item, key }); - } - Ok(()) + Self::do_clear_attribute(maybe_check_owner, collection, maybe_item, key) } /// Set the metadata for an item. @@ -1545,23 +1376,7 @@ pub mod pallet { maybe_collection: Option, ) -> DispatchResult { let who = ensure_signed(origin)?; - let old = OwnershipAcceptance::::get(&who); - match (old.is_some(), maybe_collection.is_some()) { - (false, true) => { - frame_system::Pallet::::inc_consumers(&who)?; - }, - (true, false) => { - frame_system::Pallet::::dec_consumers(&who); - }, - _ => {}, - } - if let Some(collection) = maybe_collection.as_ref() { - OwnershipAcceptance::::insert(&who, collection); - } else { - OwnershipAcceptance::::remove(&who); - } - Self::deposit_event(Event::OwnershipAcceptanceChanged { who, maybe_collection }); - Ok(()) + Self::do_set_accept_ownership(who, maybe_collection) } /// Set the maximum amount of items a collection could have. @@ -1582,27 +1397,7 @@ pub mod pallet { let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some))?; - - let collection_config = Self::get_collection_config(&collection)?; - ensure!( - collection_config.is_setting_enabled(CollectionSetting::UnlockedMaxSupply), - Error::::MaxSupplyLocked - ); - - let details = - Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - if let Some(check_owner) = &maybe_check_owner { - ensure!(check_owner == &details.owner, Error::::NoPermission); - } - - ensure!(details.items <= max_supply, Error::::MaxSupplyTooSmall); - - CollectionConfigOf::::try_mutate(collection, |maybe_config| { - let config = maybe_config.as_mut().ok_or(Error::::NoConfig)?; - config.max_supply = Some(max_supply); - Self::deposit_event(Event::CollectionMaxSupplySet { collection, max_supply }); - Ok(()) - }) + Self::do_set_collection_max_supply(maybe_check_owner, collection, max_supply) } /// Update mint settings. @@ -1627,19 +1422,7 @@ pub mod pallet { let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some))?; - - let details = - Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - if let Some(check_owner) = &maybe_check_owner { - ensure!(check_owner == &details.owner, Error::::NoPermission); - } - - CollectionConfigOf::::try_mutate(collection, |maybe_config| { - let config = maybe_config.as_mut().ok_or(Error::::NoConfig)?; - config.mint_settings = mint_settings; - Self::deposit_event(Event::CollectionMintSettingsUpdated { collection }); - Ok(()) - }) + Self::do_update_mint_settings(maybe_check_owner, collection, mint_settings) } /// Set (or reset) the price for an item. From 67c28fbaf236b53502a88811311d666ad3696fd9 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko Date: Fri, 28 Oct 2022 16:46:33 +0200 Subject: [PATCH 5/5] Remove artifacts --- frame/nfts/src/features/approvals.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/frame/nfts/src/features/approvals.rs b/frame/nfts/src/features/approvals.rs index fa572bea88fb9..0cbceb9113d0c 100644 --- a/frame/nfts/src/features/approvals.rs +++ b/frame/nfts/src/features/approvals.rs @@ -39,12 +39,6 @@ impl, I: 'static> Pallet { Error::::ItemsNonTransferable ); - let collection_config = Self::get_collection_config(&collection)?; - ensure!( - collection_config.is_setting_enabled(CollectionSetting::TransferableItems), - Error::::ItemsNonTransferable - ); - if let Some(check_origin) = maybe_check_origin { let is_admin = Self::has_role(&collection, &check_origin, CollectionRole::Admin); let permitted = is_admin || check_origin == details.owner;