referrerpolicy=no-referrer-when-downgrade

pallet_nfts/features/
metadata.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 configure the metadata of collections and items.
19
20use crate::*;
21use alloc::vec::Vec;
22use frame_support::pallet_prelude::*;
23
24impl<T: Config<I>, I: 'static> Pallet<T, I> {
25	/// Sets the metadata for a specific item within a collection.
26	///
27	/// - `maybe_check_origin`: An optional account ID that is allowed to set the metadata. If
28	///   `None`, it's considered the root account.
29	/// - `collection`: The ID of the collection to which the item belongs.
30	/// - `item`: The ID of the item to set the metadata for.
31	/// - `data`: The metadata to set for the item.
32	/// - `maybe_depositor`: An optional account ID that will provide the deposit for the metadata.
33	///   If `None`, the collection's owner provides the deposit.
34	///
35	/// Emits `ItemMetadataSet` event upon successful setting of the metadata.
36	/// Returns `Ok(())` on success, or one of the following dispatch errors:
37	/// - `UnknownCollection`: The specified collection does not exist.
38	/// - `UnknownItem`: The specified item does not exist within the collection.
39	/// - `LockedItemMetadata`: The metadata for the item is locked and cannot be modified.
40	/// - `NoPermission`: The caller does not have the required permission to set the metadata.
41	/// - `DepositExceeded`: The deposit amount exceeds the maximum allowed value.
42	pub(crate) fn do_set_item_metadata(
43		maybe_check_origin: Option<T::AccountId>,
44		collection: T::CollectionId,
45		item: T::ItemId,
46		data: BoundedVec<u8, T::StringLimit>,
47		maybe_depositor: Option<T::AccountId>,
48	) -> DispatchResult {
49		if let Some(check_origin) = &maybe_check_origin {
50			ensure!(
51				Self::has_role(&collection, &check_origin, CollectionRole::Admin),
52				Error::<T, I>::NoPermission
53			);
54		}
55
56		let is_root = maybe_check_origin.is_none();
57		let mut collection_details =
58			Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
59
60		let item_config = Self::get_item_config(&collection, &item)?;
61		ensure!(
62			is_root || item_config.is_setting_enabled(ItemSetting::UnlockedMetadata),
63			Error::<T, I>::LockedItemMetadata
64		);
65
66		let collection_config = Self::get_collection_config(&collection)?;
67
68		ItemMetadataOf::<T, I>::try_mutate_exists(collection, item, |metadata| {
69			if metadata.is_none() {
70				collection_details.item_metadatas.saturating_inc();
71			}
72
73			let old_deposit = metadata
74				.take()
75				.map_or(ItemMetadataDeposit { account: None, amount: Zero::zero() }, |m| m.deposit);
76
77			let mut deposit = Zero::zero();
78			if collection_config.is_setting_enabled(CollectionSetting::DepositRequired) && !is_root
79			{
80				deposit = T::DepositPerByte::get()
81					.saturating_mul(((data.len()) as u32).into())
82					.saturating_add(T::MetadataDepositBase::get());
83			}
84
85			let depositor = maybe_depositor.clone().unwrap_or(collection_details.owner.clone());
86			let old_depositor = old_deposit.account.unwrap_or(collection_details.owner.clone());
87
88			if depositor != old_depositor {
89				T::Currency::unreserve(&old_depositor, old_deposit.amount);
90				T::Currency::reserve(&depositor, deposit)?;
91			} else if deposit > old_deposit.amount {
92				T::Currency::reserve(&depositor, deposit - old_deposit.amount)?;
93			} else if deposit < old_deposit.amount {
94				T::Currency::unreserve(&depositor, old_deposit.amount - deposit);
95			}
96
97			if maybe_depositor.is_none() {
98				collection_details.owner_deposit.saturating_accrue(deposit);
99				collection_details.owner_deposit.saturating_reduce(old_deposit.amount);
100			}
101
102			*metadata = Some(ItemMetadata {
103				deposit: ItemMetadataDeposit { account: maybe_depositor, amount: deposit },
104				data: data.clone(),
105			});
106
107			Collection::<T, I>::insert(&collection, &collection_details);
108			Self::deposit_event(Event::ItemMetadataSet { collection, item, data });
109			Ok(())
110		})
111	}
112
113	/// Clears the metadata for a specific item within a collection.
114	///
115	/// - `maybe_check_origin`: An optional account ID that is allowed to clear the metadata. If
116	///   `None`, it's considered the root account.
117	/// - `collection`: The ID of the collection to which the item belongs.
118	/// - `item`: The ID of the item for which to clear the metadata.
119	///
120	/// Emits `ItemMetadataCleared` event upon successful clearing of the metadata.
121	/// Returns `Ok(())` on success, or one of the following dispatch errors:
122	/// - `UnknownCollection`: The specified collection does not exist.
123	/// - `MetadataNotFound`: The metadata for the specified item was not found.
124	/// - `LockedItemMetadata`: The metadata for the item is locked and cannot be modified.
125	/// - `NoPermission`: The caller does not have the required permission to clear the metadata.
126	pub(crate) fn do_clear_item_metadata(
127		maybe_check_origin: Option<T::AccountId>,
128		collection: T::CollectionId,
129		item: T::ItemId,
130	) -> DispatchResult {
131		if let Some(check_origin) = &maybe_check_origin {
132			ensure!(
133				Self::has_role(&collection, &check_origin, CollectionRole::Admin),
134				Error::<T, I>::NoPermission
135			);
136		}
137
138		let is_root = maybe_check_origin.is_none();
139		let metadata = ItemMetadataOf::<T, I>::take(collection, item)
140			.ok_or(Error::<T, I>::MetadataNotFound)?;
141		let mut collection_details =
142			Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
143
144		let depositor_account =
145			metadata.deposit.account.unwrap_or(collection_details.owner.clone());
146
147		// NOTE: if the item was previously burned, the ItemConfigOf record might not exist
148		let is_locked = Self::get_item_config(&collection, &item)
149			.map_or(false, |c| c.has_disabled_setting(ItemSetting::UnlockedMetadata));
150
151		ensure!(is_root || !is_locked, Error::<T, I>::LockedItemMetadata);
152
153		collection_details.item_metadatas.saturating_dec();
154		T::Currency::unreserve(&depositor_account, metadata.deposit.amount);
155
156		if depositor_account == collection_details.owner {
157			collection_details.owner_deposit.saturating_reduce(metadata.deposit.amount);
158		}
159
160		Collection::<T, I>::insert(&collection, &collection_details);
161		Self::deposit_event(Event::ItemMetadataCleared { collection, item });
162
163		Ok(())
164	}
165
166	/// Sets the metadata for a specific collection.
167	///
168	/// - `maybe_check_origin`: An optional account ID that is allowed to set the collection
169	///   metadata. If `None`, it's considered the root account.
170	/// - `collection`: The ID of the collection for which to set the metadata.
171	/// - `data`: The metadata to set for the collection.
172	///
173	/// Emits `CollectionMetadataSet` event upon successful setting of the metadata.
174	/// Returns `Ok(())` on success, or one of the following dispatch errors:
175	/// - `UnknownCollection`: The specified collection does not exist.
176	/// - `LockedCollectionMetadata`: The metadata for the collection is locked and cannot be
177	///   modified.
178	/// - `NoPermission`: The caller does not have the required permission to set the metadata.
179	pub(crate) fn do_set_collection_metadata(
180		maybe_check_origin: Option<T::AccountId>,
181		collection: T::CollectionId,
182		data: BoundedVec<u8, T::StringLimit>,
183	) -> DispatchResult {
184		if let Some(check_origin) = &maybe_check_origin {
185			ensure!(
186				Self::has_role(&collection, &check_origin, CollectionRole::Admin),
187				Error::<T, I>::NoPermission
188			);
189		}
190
191		let is_root = maybe_check_origin.is_none();
192		let collection_config = Self::get_collection_config(&collection)?;
193		ensure!(
194			is_root || collection_config.is_setting_enabled(CollectionSetting::UnlockedMetadata),
195			Error::<T, I>::LockedCollectionMetadata
196		);
197
198		let mut details =
199			Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
200
201		CollectionMetadataOf::<T, I>::try_mutate_exists(collection, |metadata| {
202			let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit);
203			details.owner_deposit.saturating_reduce(old_deposit);
204			let mut deposit = Zero::zero();
205			if !is_root && collection_config.is_setting_enabled(CollectionSetting::DepositRequired)
206			{
207				deposit = T::DepositPerByte::get()
208					.saturating_mul(((data.len()) as u32).into())
209					.saturating_add(T::MetadataDepositBase::get());
210			}
211			if deposit > old_deposit {
212				T::Currency::reserve(&details.owner, deposit - old_deposit)?;
213			} else if deposit < old_deposit {
214				T::Currency::unreserve(&details.owner, old_deposit - deposit);
215			}
216			details.owner_deposit.saturating_accrue(deposit);
217
218			Collection::<T, I>::insert(&collection, details);
219
220			*metadata = Some(CollectionMetadata { deposit, data: data.clone() });
221
222			Self::deposit_event(Event::CollectionMetadataSet { collection, data });
223			Ok(())
224		})
225	}
226
227	/// Clears the metadata for a specific collection.
228	///
229	/// - `maybe_check_origin`: An optional account ID that is allowed to clear the collection
230	///   metadata. If `None`, it's considered the root account.
231	/// - `collection`: The ID of the collection for which to clear the metadata.
232	///
233	/// Emits `CollectionMetadataCleared` event upon successful clearing of the metadata.
234	/// Returns `Ok(())` on success, or one of the following dispatch errors:
235	/// - `UnknownCollection`: The specified collection does not exist.
236	/// - `MetadataNotFound`: The metadata for the collection was not found.
237	/// - `LockedCollectionMetadata`: The metadata for the collection is locked and cannot be
238	///   modified.
239	/// - `NoPermission`: The caller does not have the required permission to clear the metadata.
240	pub(crate) fn do_clear_collection_metadata(
241		maybe_check_origin: Option<T::AccountId>,
242		collection: T::CollectionId,
243	) -> DispatchResult {
244		if let Some(check_origin) = &maybe_check_origin {
245			ensure!(
246				Self::has_role(&collection, &check_origin, CollectionRole::Admin),
247				Error::<T, I>::NoPermission
248			);
249		}
250
251		let mut details =
252			Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
253		let collection_config = Self::get_collection_config(&collection)?;
254
255		ensure!(
256			maybe_check_origin.is_none() ||
257				collection_config.is_setting_enabled(CollectionSetting::UnlockedMetadata),
258			Error::<T, I>::LockedCollectionMetadata
259		);
260
261		CollectionMetadataOf::<T, I>::try_mutate_exists(collection, |metadata| {
262			let deposit = metadata.take().ok_or(Error::<T, I>::UnknownCollection)?.deposit;
263			T::Currency::unreserve(&details.owner, deposit);
264			details.owner_deposit.saturating_reduce(deposit);
265			Collection::<T, I>::insert(&collection, details);
266			Self::deposit_event(Event::CollectionMetadataCleared { collection });
267			Ok(())
268		})
269	}
270
271	/// A helper method to construct metadata.
272	///
273	/// # Errors
274	///
275	/// This function returns an [`IncorrectMetadata`](crate::Error::IncorrectMetadata) dispatch
276	/// error if the provided metadata is too long.
277	pub fn construct_metadata(
278		metadata: Vec<u8>,
279	) -> Result<BoundedVec<u8, T::StringLimit>, DispatchError> {
280		Ok(BoundedVec::try_from(metadata).map_err(|_| Error::<T, I>::IncorrectMetadata)?)
281	}
282}