referrerpolicy=no-referrer-when-downgrade

pallet_nfts/features/
create_delete_collection.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! This module contains helper methods to perform functionality associated with creating and
19//! destroying collections for the NFTs pallet.
20
21use crate::*;
22use frame_support::pallet_prelude::*;
23
24impl<T: Config<I>, I: 'static> Pallet<T, I> {
25	/// Create a new collection with the given `collection`, `owner`, `admin`, `config`, `deposit`,
26	/// and `event`.
27	///
28	/// This function creates a new collection with the provided parameters. It reserves the
29	/// required deposit from the owner's account, sets the collection details, assigns admin roles,
30	/// and inserts the provided configuration. Finally, it emits the specified event upon success.
31	///
32	/// # Errors
33	///
34	/// This function returns a [`CollectionIdInUse`](crate::Error::CollectionIdInUse) error if the
35	/// collection ID is already in use.
36	pub fn do_create_collection(
37		collection: T::CollectionId,
38		owner: T::AccountId,
39		admin: T::AccountId,
40		config: CollectionConfigFor<T, I>,
41		deposit: DepositBalanceOf<T, I>,
42		event: Event<T, I>,
43	) -> DispatchResult {
44		ensure!(!Collection::<T, I>::contains_key(collection), Error::<T, I>::CollectionIdInUse);
45
46		T::Currency::reserve(&owner, deposit)?;
47
48		Collection::<T, I>::insert(
49			collection,
50			CollectionDetails {
51				owner: owner.clone(),
52				owner_deposit: deposit,
53				items: 0,
54				item_metadatas: 0,
55				item_configs: 0,
56				attributes: 0,
57			},
58		);
59		CollectionRoleOf::<T, I>::insert(
60			collection,
61			admin,
62			CollectionRoles(
63				CollectionRole::Admin | CollectionRole::Freezer | CollectionRole::Issuer,
64			),
65		);
66
67		CollectionConfigOf::<T, I>::insert(&collection, config);
68		CollectionAccount::<T, I>::insert(&owner, &collection, ());
69
70		Self::deposit_event(event);
71
72		if let Some(max_supply) = config.max_supply {
73			Self::deposit_event(Event::CollectionMaxSupplySet { collection, max_supply });
74		}
75
76		Ok(())
77	}
78
79	/// Destroy the specified collection with the given `collection`, `witness`, and
80	/// `maybe_check_owner`.
81	///
82	/// This function destroys the specified collection if it exists and meets the necessary
83	/// conditions. It checks the provided `witness` against the actual collection details and
84	/// removes the collection along with its associated metadata, attributes, and configurations.
85	/// The necessary deposits are returned to the corresponding accounts, and the roles and
86	/// configurations for the collection are cleared. Finally, it emits the `Destroyed` event upon
87	/// successful destruction.
88	///
89	/// # Errors
90	///
91	/// This function returns a dispatch error in the following cases:
92	/// - If the collection ID is not found
93	///   ([`UnknownCollection`](crate::Error::UnknownCollection)).
94	/// - If the provided `maybe_check_owner` does not match the actual owner
95	///   ([`NoPermission`](crate::Error::NoPermission)).
96	/// - If the collection is not empty (contains items)
97	///   ([`CollectionNotEmpty`](crate::Error::CollectionNotEmpty)).
98	/// - If the `witness` does not match the actual collection details
99	///   ([`BadWitness`](crate::Error::BadWitness)).
100	pub fn do_destroy_collection(
101		collection: T::CollectionId,
102		witness: DestroyWitness,
103		maybe_check_owner: Option<T::AccountId>,
104	) -> Result<DestroyWitness, DispatchError> {
105		Collection::<T, I>::try_mutate_exists(collection, |maybe_details| {
106			let collection_details =
107				maybe_details.take().ok_or(Error::<T, I>::UnknownCollection)?;
108			if let Some(check_owner) = maybe_check_owner {
109				ensure!(collection_details.owner == check_owner, Error::<T, I>::NoPermission);
110			}
111			ensure!(collection_details.items == 0, Error::<T, I>::CollectionNotEmpty);
112			ensure!(collection_details.attributes == witness.attributes, Error::<T, I>::BadWitness);
113			ensure!(
114				collection_details.item_metadatas == witness.item_metadatas,
115				Error::<T, I>::BadWitness
116			);
117			ensure!(
118				collection_details.item_configs == witness.item_configs,
119				Error::<T, I>::BadWitness
120			);
121
122			for (_, metadata) in ItemMetadataOf::<T, I>::drain_prefix(&collection) {
123				if let Some(depositor) = metadata.deposit.account {
124					T::Currency::unreserve(&depositor, metadata.deposit.amount);
125				}
126			}
127
128			CollectionMetadataOf::<T, I>::remove(&collection);
129			Self::clear_roles(&collection)?;
130
131			for (_, (_, deposit)) in Attribute::<T, I>::drain_prefix((&collection,)) {
132				if !deposit.amount.is_zero() {
133					if let Some(account) = deposit.account {
134						T::Currency::unreserve(&account, deposit.amount);
135					}
136				}
137			}
138
139			CollectionAccount::<T, I>::remove(&collection_details.owner, &collection);
140			T::Currency::unreserve(&collection_details.owner, collection_details.owner_deposit);
141			CollectionConfigOf::<T, I>::remove(&collection);
142			let _ = ItemConfigOf::<T, I>::clear_prefix(&collection, witness.item_configs, None);
143
144			Self::deposit_event(Event::Destroyed { collection });
145
146			Ok(DestroyWitness {
147				item_metadatas: collection_details.item_metadatas,
148				item_configs: collection_details.item_configs,
149				attributes: collection_details.attributes,
150			})
151		})
152	}
153}