referrerpolicy=no-referrer-when-downgrade

pallet_uniques/
lib.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//! # Unique (Items) Module
19//!
20//! A simple, secure module for dealing with non-fungible items.
21//!
22//! ## Related Modules
23//!
24//! * [`System`](../frame_system/index.html)
25//! * [`Support`](../frame_support/index.html)
26
27#![recursion_limit = "256"]
28// Ensure we're `no_std` when compiling for Wasm.
29#![cfg_attr(not(feature = "std"), no_std)]
30
31#[cfg(feature = "runtime-benchmarks")]
32mod benchmarking;
33#[cfg(test)]
34pub mod mock;
35#[cfg(test)]
36mod tests;
37
38mod functions;
39mod impl_nonfungibles;
40mod types;
41
42pub mod asset_ops;
43pub mod migration;
44pub mod weights;
45
46extern crate alloc;
47
48use alloc::vec::Vec;
49use codec::{Decode, DecodeWithMemTracking, Encode};
50use frame_support::traits::{
51	tokens::Locker, BalanceStatus::Reserved, Currency, EnsureOriginWithArg, ReservableCurrency,
52};
53use frame_system::Config as SystemConfig;
54use sp_runtime::{
55	traits::{Saturating, StaticLookup, Zero},
56	ArithmeticError, Debug,
57};
58
59pub use pallet::*;
60pub use types::*;
61pub use weights::WeightInfo;
62
63/// The log target for this pallet.
64const LOG_TARGET: &str = "runtime::uniques";
65
66/// A type alias for the account ID type used in the dispatchable functions of this pallet.
67type AccountIdLookupOf<T> = <<T as frame_system::Config>::Lookup as StaticLookup>::Source;
68
69#[frame_support::pallet]
70pub mod pallet {
71	use super::*;
72	use frame_support::pallet_prelude::*;
73	use frame_system::pallet_prelude::*;
74
75	const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
76
77	#[pallet::pallet]
78	#[pallet::storage_version(STORAGE_VERSION)]
79	pub struct Pallet<T, I = ()>(_);
80
81	#[cfg(feature = "runtime-benchmarks")]
82	pub trait BenchmarkHelper<CollectionId, ItemId> {
83		fn collection(i: u16) -> CollectionId;
84		fn item(i: u16) -> ItemId;
85	}
86	#[cfg(feature = "runtime-benchmarks")]
87	impl<CollectionId: From<u16>, ItemId: From<u16>> BenchmarkHelper<CollectionId, ItemId> for () {
88		fn collection(i: u16) -> CollectionId {
89			i.into()
90		}
91		fn item(i: u16) -> ItemId {
92			i.into()
93		}
94	}
95
96	#[pallet::config]
97	/// The module configuration trait.
98	pub trait Config<I: 'static = ()>: frame_system::Config {
99		/// The overarching event type.
100		#[allow(deprecated)]
101		type RuntimeEvent: From<Event<Self, I>>
102			+ IsType<<Self as frame_system::Config>::RuntimeEvent>;
103
104		/// Identifier for the collection of item.
105		type CollectionId: Member + Parameter + MaxEncodedLen;
106
107		/// The type used to identify a unique item within a collection.
108		type ItemId: Member + Parameter + MaxEncodedLen + Copy;
109
110		/// The currency mechanism, used for paying for reserves.
111		type Currency: ReservableCurrency<Self::AccountId>;
112
113		/// The origin which may forcibly create or destroy an item or otherwise alter privileged
114		/// attributes.
115		type ForceOrigin: EnsureOrigin<Self::RuntimeOrigin>;
116
117		/// Standard collection creation is only allowed if the origin attempting it and the
118		/// collection are in this set.
119		type CreateOrigin: EnsureOriginWithArg<
120			Self::RuntimeOrigin,
121			Self::CollectionId,
122			Success = Self::AccountId,
123		>;
124
125		/// Locker trait to enable Locking mechanism downstream.
126		type Locker: Locker<Self::CollectionId, Self::ItemId>;
127
128		/// The basic amount of funds that must be reserved for collection.
129		#[pallet::constant]
130		type CollectionDeposit: Get<DepositBalanceOf<Self, I>>;
131
132		/// The basic amount of funds that must be reserved for an item.
133		#[pallet::constant]
134		type ItemDeposit: Get<DepositBalanceOf<Self, I>>;
135
136		/// The basic amount of funds that must be reserved when adding metadata to your item.
137		#[pallet::constant]
138		type MetadataDepositBase: Get<DepositBalanceOf<Self, I>>;
139
140		/// The basic amount of funds that must be reserved when adding an attribute to an item.
141		#[pallet::constant]
142		type AttributeDepositBase: Get<DepositBalanceOf<Self, I>>;
143
144		/// The additional funds that must be reserved for the number of bytes store in metadata,
145		/// either "normal" metadata or attribute metadata.
146		#[pallet::constant]
147		type DepositPerByte: Get<DepositBalanceOf<Self, I>>;
148
149		/// The maximum length of data stored on-chain.
150		#[pallet::constant]
151		type StringLimit: Get<u32>;
152
153		/// The maximum length of an attribute key.
154		#[pallet::constant]
155		type KeyLimit: Get<u32>;
156
157		/// The maximum length of an attribute value.
158		#[pallet::constant]
159		type ValueLimit: Get<u32>;
160
161		#[cfg(feature = "runtime-benchmarks")]
162		/// A set of helper functions for benchmarking.
163		type Helper: BenchmarkHelper<Self::CollectionId, Self::ItemId>;
164
165		/// Weight information for extrinsics in this pallet.
166		type WeightInfo: WeightInfo;
167	}
168
169	#[pallet::storage]
170	#[pallet::storage_prefix = "Class"]
171	/// Details of a collection.
172	pub type Collection<T: Config<I>, I: 'static = ()> = StorageMap<
173		_,
174		Blake2_128Concat,
175		T::CollectionId,
176		CollectionDetails<T::AccountId, DepositBalanceOf<T, I>>,
177	>;
178
179	#[pallet::storage]
180	/// The collection, if any, of which an account is willing to take ownership.
181	pub type OwnershipAcceptance<T: Config<I>, I: 'static = ()> =
182		StorageMap<_, Blake2_128Concat, T::AccountId, T::CollectionId>;
183
184	#[pallet::storage]
185	/// The items held by any given account; set out this way so that items owned by a single
186	/// account can be enumerated.
187	pub type Account<T: Config<I>, I: 'static = ()> = StorageNMap<
188		_,
189		(
190			NMapKey<Blake2_128Concat, T::AccountId>, // owner
191			NMapKey<Blake2_128Concat, T::CollectionId>,
192			NMapKey<Blake2_128Concat, T::ItemId>,
193		),
194		(),
195		OptionQuery,
196	>;
197
198	#[pallet::storage]
199	#[pallet::storage_prefix = "ClassAccount"]
200	/// The collections owned by any given account; set out this way so that collections owned by
201	/// a single account can be enumerated.
202	pub type CollectionAccount<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
203		_,
204		Blake2_128Concat,
205		T::AccountId,
206		Blake2_128Concat,
207		T::CollectionId,
208		(),
209		OptionQuery,
210	>;
211
212	#[pallet::storage]
213	#[pallet::storage_prefix = "Asset"]
214	/// The items in existence and their ownership details.
215	pub type Item<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
216		_,
217		Blake2_128Concat,
218		T::CollectionId,
219		Blake2_128Concat,
220		T::ItemId,
221		ItemDetails<T::AccountId, DepositBalanceOf<T, I>>,
222		OptionQuery,
223	>;
224
225	#[pallet::storage]
226	#[pallet::storage_prefix = "ClassMetadataOf"]
227	/// Metadata of a collection.
228	pub type CollectionMetadataOf<T: Config<I>, I: 'static = ()> = StorageMap<
229		_,
230		Blake2_128Concat,
231		T::CollectionId,
232		CollectionMetadata<DepositBalanceOf<T, I>, T::StringLimit>,
233		OptionQuery,
234	>;
235
236	#[pallet::storage]
237	#[pallet::storage_prefix = "InstanceMetadataOf"]
238	/// Metadata of an item.
239	pub type ItemMetadataOf<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
240		_,
241		Blake2_128Concat,
242		T::CollectionId,
243		Blake2_128Concat,
244		T::ItemId,
245		ItemMetadata<DepositBalanceOf<T, I>, T::StringLimit>,
246		OptionQuery,
247	>;
248
249	#[pallet::storage]
250	/// Attributes of a collection.
251	pub type Attribute<T: Config<I>, I: 'static = ()> = StorageNMap<
252		_,
253		(
254			NMapKey<Blake2_128Concat, T::CollectionId>,
255			NMapKey<Blake2_128Concat, Option<T::ItemId>>,
256			NMapKey<Blake2_128Concat, BoundedVec<u8, T::KeyLimit>>,
257		),
258		(BoundedVec<u8, T::ValueLimit>, DepositBalanceOf<T, I>),
259		OptionQuery,
260	>;
261
262	#[pallet::storage]
263	/// Price of an asset instance.
264	pub type ItemPriceOf<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
265		_,
266		Blake2_128Concat,
267		T::CollectionId,
268		Blake2_128Concat,
269		T::ItemId,
270		(ItemPrice<T, I>, Option<T::AccountId>),
271		OptionQuery,
272	>;
273
274	#[pallet::storage]
275	/// Keeps track of the number of items a collection might have.
276	pub type CollectionMaxSupply<T: Config<I>, I: 'static = ()> =
277		StorageMap<_, Blake2_128Concat, T::CollectionId, u32, OptionQuery>;
278
279	#[pallet::event]
280	#[pallet::generate_deposit(pub(super) fn deposit_event)]
281	pub enum Event<T: Config<I>, I: 'static = ()> {
282		/// A `collection` was created.
283		Created { collection: T::CollectionId, creator: T::AccountId, owner: T::AccountId },
284		/// A `collection` was force-created.
285		ForceCreated { collection: T::CollectionId, owner: T::AccountId },
286		/// A `collection` was destroyed.
287		Destroyed { collection: T::CollectionId },
288		/// An `item` was issued.
289		Issued { collection: T::CollectionId, item: T::ItemId, owner: T::AccountId },
290		/// An `item` was transferred.
291		Transferred {
292			collection: T::CollectionId,
293			item: T::ItemId,
294			from: T::AccountId,
295			to: T::AccountId,
296		},
297		/// An `item` was destroyed.
298		Burned { collection: T::CollectionId, item: T::ItemId, owner: T::AccountId },
299		/// Some `item` was frozen.
300		Frozen { collection: T::CollectionId, item: T::ItemId },
301		/// Some `item` was thawed.
302		Thawed { collection: T::CollectionId, item: T::ItemId },
303		/// Some `collection` was frozen.
304		CollectionFrozen { collection: T::CollectionId },
305		/// Some `collection` was thawed.
306		CollectionThawed { collection: T::CollectionId },
307		/// The owner changed.
308		OwnerChanged { collection: T::CollectionId, new_owner: T::AccountId },
309		/// The management team changed.
310		TeamChanged {
311			collection: T::CollectionId,
312			issuer: T::AccountId,
313			admin: T::AccountId,
314			freezer: T::AccountId,
315		},
316		/// An `item` of a `collection` has been approved by the `owner` for transfer by
317		/// a `delegate`.
318		ApprovedTransfer {
319			collection: T::CollectionId,
320			item: T::ItemId,
321			owner: T::AccountId,
322			delegate: T::AccountId,
323		},
324		/// An approval for a `delegate` account to transfer the `item` of an item
325		/// `collection` was cancelled by its `owner`.
326		ApprovalCancelled {
327			collection: T::CollectionId,
328			item: T::ItemId,
329			owner: T::AccountId,
330			delegate: T::AccountId,
331		},
332		/// A `collection` has had its attributes changed by the `Force` origin.
333		ItemStatusChanged { collection: T::CollectionId },
334		/// New metadata has been set for a `collection`.
335		CollectionMetadataSet {
336			collection: T::CollectionId,
337			data: BoundedVec<u8, T::StringLimit>,
338			is_frozen: bool,
339		},
340		/// Metadata has been cleared for a `collection`.
341		CollectionMetadataCleared { collection: T::CollectionId },
342		/// New metadata has been set for an item.
343		MetadataSet {
344			collection: T::CollectionId,
345			item: T::ItemId,
346			data: BoundedVec<u8, T::StringLimit>,
347			is_frozen: bool,
348		},
349		/// Metadata has been cleared for an item.
350		MetadataCleared { collection: T::CollectionId, item: T::ItemId },
351		/// Metadata has been cleared for an item.
352		Redeposited { collection: T::CollectionId, successful_items: Vec<T::ItemId> },
353		/// New attribute metadata has been set for a `collection` or `item`.
354		AttributeSet {
355			collection: T::CollectionId,
356			maybe_item: Option<T::ItemId>,
357			key: BoundedVec<u8, T::KeyLimit>,
358			value: BoundedVec<u8, T::ValueLimit>,
359		},
360		/// Attribute metadata has been cleared for a `collection` or `item`.
361		AttributeCleared {
362			collection: T::CollectionId,
363			maybe_item: Option<T::ItemId>,
364			key: BoundedVec<u8, T::KeyLimit>,
365		},
366		/// Ownership acceptance has changed for an account.
367		OwnershipAcceptanceChanged { who: T::AccountId, maybe_collection: Option<T::CollectionId> },
368		/// Max supply has been set for a collection.
369		CollectionMaxSupplySet { collection: T::CollectionId, max_supply: u32 },
370		/// The price was set for the instance.
371		ItemPriceSet {
372			collection: T::CollectionId,
373			item: T::ItemId,
374			price: ItemPrice<T, I>,
375			whitelisted_buyer: Option<T::AccountId>,
376		},
377		/// The price for the instance was removed.
378		ItemPriceRemoved { collection: T::CollectionId, item: T::ItemId },
379		/// An item was bought.
380		ItemBought {
381			collection: T::CollectionId,
382			item: T::ItemId,
383			price: ItemPrice<T, I>,
384			seller: T::AccountId,
385			buyer: T::AccountId,
386		},
387	}
388
389	#[pallet::error]
390	pub enum Error<T, I = ()> {
391		/// The signing account has no permission to do the operation.
392		NoPermission,
393		/// The given item ID is unknown.
394		UnknownCollection,
395		/// The item ID has already been used for an item.
396		AlreadyExists,
397		/// The owner turned out to be different to what was expected.
398		WrongOwner,
399		/// Invalid witness data given.
400		BadWitness,
401		/// The item ID is already taken.
402		InUse,
403		/// The item or collection is frozen.
404		Frozen,
405		/// The delegate turned out to be different to what was expected.
406		WrongDelegate,
407		/// There is no delegate approved.
408		NoDelegate,
409		/// No approval exists that would allow the transfer.
410		Unapproved,
411		/// The named owner has not signed ownership of the collection is acceptable.
412		Unaccepted,
413		/// The item is locked.
414		Locked,
415		/// All items have been minted.
416		MaxSupplyReached,
417		/// The max supply has already been set.
418		MaxSupplyAlreadySet,
419		/// The provided max supply is less to the amount of items a collection already has.
420		MaxSupplyTooSmall,
421		/// The given item ID is unknown.
422		UnknownItem,
423		/// Item is not for sale.
424		NotForSale,
425		/// The provided bid is too low.
426		BidTooLow,
427		/// No metadata is found.
428		NoMetadata,
429		/// Wrong metadata key/value bytes supplied.
430		WrongMetadata,
431		/// An attribute is not found.
432		AttributeNotFound,
433		/// Wrong attribute key/value bytes supplied.
434		WrongAttribute,
435		/// The collection deposit could not be fully moved due to a lock or freeze on the owner.
436		IncompleteDepositTransfer,
437	}
438
439	impl<T: Config<I>, I: 'static> Pallet<T, I> {
440		/// Get the owner of the item, if the item exists.
441		pub fn owner(collection: T::CollectionId, item: T::ItemId) -> Option<T::AccountId> {
442			Item::<T, I>::get(collection, item).map(|i| i.owner)
443		}
444
445		/// Get the owner of the item, if the item exists.
446		pub fn collection_owner(collection: T::CollectionId) -> Option<T::AccountId> {
447			Collection::<T, I>::get(collection).map(|i| i.owner)
448		}
449	}
450
451	#[pallet::call]
452	impl<T: Config<I>, I: 'static> Pallet<T, I> {
453		/// Issue a new collection of non-fungible items from a public origin.
454		///
455		/// This new collection has no items initially and its owner is the origin.
456		///
457		/// The origin must conform to the configured `CreateOrigin` and have sufficient funds free.
458		///
459		/// `ItemDeposit` funds of sender are reserved.
460		///
461		/// Parameters:
462		/// - `collection`: The identifier of the new collection. This must not be currently in use.
463		/// - `admin`: The admin of this collection. The admin is the initial address of each
464		/// member of the collection's admin team.
465		///
466		/// Emits `Created` event when successful.
467		///
468		/// Weight: `O(1)`
469		#[pallet::call_index(0)]
470		#[pallet::weight(T::WeightInfo::create())]
471		pub fn create(
472			origin: OriginFor<T>,
473			collection: T::CollectionId,
474			admin: AccountIdLookupOf<T>,
475		) -> DispatchResult {
476			let owner = T::CreateOrigin::ensure_origin(origin, &collection)?;
477			let admin = T::Lookup::lookup(admin)?;
478
479			Self::do_create_collection(
480				collection.clone(),
481				owner.clone(),
482				admin.clone(),
483				T::CollectionDeposit::get(),
484				false,
485				Event::Created { collection, creator: owner, owner: admin },
486			)
487		}
488
489		/// Issue a new collection of non-fungible items from a privileged origin.
490		///
491		/// This new collection has no items initially.
492		///
493		/// The origin must conform to `ForceOrigin`.
494		///
495		/// Unlike `create`, no funds are reserved.
496		///
497		/// - `collection`: The identifier of the new item. This must not be currently in use.
498		/// - `owner`: The owner of this collection of items. The owner has full superuser
499		///   permissions
500		/// over this item, but may later change and configure the permissions using
501		/// `transfer_ownership` and `set_team`.
502		///
503		/// Emits `ForceCreated` event when successful.
504		///
505		/// Weight: `O(1)`
506		#[pallet::call_index(1)]
507		#[pallet::weight(T::WeightInfo::force_create())]
508		pub fn force_create(
509			origin: OriginFor<T>,
510			collection: T::CollectionId,
511			owner: AccountIdLookupOf<T>,
512			free_holding: bool,
513		) -> DispatchResult {
514			T::ForceOrigin::ensure_origin(origin)?;
515			let owner = T::Lookup::lookup(owner)?;
516
517			Self::do_create_collection(
518				collection.clone(),
519				owner.clone(),
520				owner.clone(),
521				Zero::zero(),
522				free_holding,
523				Event::ForceCreated { collection, owner },
524			)
525		}
526
527		/// Destroy a collection of fungible items.
528		///
529		/// The origin must conform to `ForceOrigin` or must be `Signed` and the sender must be the
530		/// owner of the `collection`.
531		///
532		/// - `collection`: The identifier of the collection to be destroyed.
533		/// - `witness`: Information on the items minted in the collection. This must be
534		/// correct.
535		///
536		/// Emits `Destroyed` event when successful.
537		///
538		/// Weight: `O(n + m)` where:
539		/// - `n = witness.items`
540		/// - `m = witness.item_metadatas`
541		/// - `a = witness.attributes`
542		#[pallet::call_index(2)]
543		#[pallet::weight(T::WeightInfo::destroy(
544			witness.items,
545 			witness.item_metadatas,
546			witness.attributes,
547 		))]
548		pub fn destroy(
549			origin: OriginFor<T>,
550			collection: T::CollectionId,
551			witness: DestroyWitness,
552		) -> DispatchResultWithPostInfo {
553			let maybe_check_owner = match T::ForceOrigin::try_origin(origin) {
554				Ok(_) => None,
555				Err(origin) => Some(ensure_signed(origin)?),
556			};
557			let details = Self::do_destroy_collection(collection, witness, maybe_check_owner)?;
558
559			Ok(Some(T::WeightInfo::destroy(
560				details.items,
561				details.item_metadatas,
562				details.attributes,
563			))
564			.into())
565		}
566
567		/// Mint an item of a particular collection.
568		///
569		/// The origin must be Signed and the sender must be the Issuer of the `collection`.
570		///
571		/// - `collection`: The collection of the item to be minted.
572		/// - `item`: The item value of the item to be minted.
573		/// - `beneficiary`: The initial owner of the minted item.
574		///
575		/// Emits `Issued` event when successful.
576		///
577		/// Weight: `O(1)`
578		#[pallet::call_index(3)]
579		#[pallet::weight(T::WeightInfo::mint())]
580		pub fn mint(
581			origin: OriginFor<T>,
582			collection: T::CollectionId,
583			item: T::ItemId,
584			owner: AccountIdLookupOf<T>,
585		) -> DispatchResult {
586			let origin = ensure_signed(origin)?;
587			let owner = T::Lookup::lookup(owner)?;
588
589			Self::do_mint(collection, item, owner, |collection_details| {
590				ensure!(collection_details.issuer == origin, Error::<T, I>::NoPermission);
591				Ok(())
592			})
593		}
594
595		/// Destroy a single item.
596		///
597		/// Origin must be Signed and the signing account must be either:
598		/// - the Admin of the `collection`;
599		/// - the Owner of the `item`;
600		///
601		/// - `collection`: The collection of the item to be burned.
602		/// - `item`: The item of the item to be burned.
603		/// - `check_owner`: If `Some` then the operation will fail with `WrongOwner` unless the
604		///   item is owned by this value.
605		///
606		/// Emits `Burned` with the actual amount burned.
607		///
608		/// Weight: `O(1)`
609		/// Modes: `check_owner.is_some()`.
610		#[pallet::call_index(4)]
611		#[pallet::weight(T::WeightInfo::burn())]
612		pub fn burn(
613			origin: OriginFor<T>,
614			collection: T::CollectionId,
615			item: T::ItemId,
616			check_owner: Option<AccountIdLookupOf<T>>,
617		) -> DispatchResult {
618			let origin = ensure_signed(origin)?;
619			let check_owner = check_owner.map(T::Lookup::lookup).transpose()?;
620
621			Self::do_burn(collection, item, |collection_details, details| {
622				let is_permitted = collection_details.admin == origin || details.owner == origin;
623				ensure!(is_permitted, Error::<T, I>::NoPermission);
624				ensure!(
625					check_owner.map_or(true, |o| o == details.owner),
626					Error::<T, I>::WrongOwner
627				);
628				Ok(())
629			})
630		}
631
632		/// Move an item from the sender account to another.
633		///
634		/// This resets the approved account of the item.
635		///
636		/// Origin must be Signed and the signing account must be either:
637		/// - the Admin of the `collection`;
638		/// - the Owner of the `item`;
639		/// - the approved delegate for the `item` (in this case, the approval is reset).
640		///
641		/// Arguments:
642		/// - `collection`: The collection of the item to be transferred.
643		/// - `item`: The item of the item to be transferred.
644		/// - `dest`: The account to receive ownership of the item.
645		///
646		/// Emits `Transferred`.
647		///
648		/// Weight: `O(1)`
649		#[pallet::call_index(5)]
650		#[pallet::weight(T::WeightInfo::transfer())]
651		pub fn transfer(
652			origin: OriginFor<T>,
653			collection: T::CollectionId,
654			item: T::ItemId,
655			dest: AccountIdLookupOf<T>,
656		) -> DispatchResult {
657			let origin = ensure_signed(origin)?;
658			let dest = T::Lookup::lookup(dest)?;
659
660			Self::do_transfer(collection, item, dest, |collection_details, details| {
661				if details.owner != origin && collection_details.admin != origin {
662					let approved = details.approved.take().map_or(false, |i| i == origin);
663					ensure!(approved, Error::<T, I>::NoPermission);
664				}
665				Ok(())
666			})
667		}
668
669		/// Reevaluate the deposits on some items.
670		///
671		/// Origin must be Signed and the sender should be the Owner of the `collection`.
672		///
673		/// - `collection`: The collection to be frozen.
674		/// - `items`: The items of the collection whose deposits will be reevaluated.
675		///
676		/// NOTE: This exists as a best-effort function. Any items which are unknown or
677		/// in the case that the owner account does not have reservable funds to pay for a
678		/// deposit increase are ignored. Generally the owner isn't going to call this on items
679		/// whose existing deposit is less than the refreshed deposit as it would only cost them,
680		/// so it's of little consequence.
681		///
682		/// It will still return an error in the case that the collection is unknown of the signer
683		/// is not permitted to call it.
684		///
685		/// Weight: `O(items.len())`
686		#[pallet::call_index(6)]
687		#[pallet::weight(T::WeightInfo::redeposit(items.len() as u32))]
688		pub fn redeposit(
689			origin: OriginFor<T>,
690			collection: T::CollectionId,
691			items: Vec<T::ItemId>,
692		) -> DispatchResult {
693			let origin = ensure_signed(origin)?;
694
695			let mut collection_details =
696				Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
697			ensure!(collection_details.owner == origin, Error::<T, I>::NoPermission);
698			let deposit = match collection_details.free_holding {
699				true => Zero::zero(),
700				false => T::ItemDeposit::get(),
701			};
702
703			let mut successful = Vec::with_capacity(items.len());
704			for item in items.into_iter() {
705				let mut details = match Item::<T, I>::get(&collection, &item) {
706					Some(x) => x,
707					None => continue,
708				};
709				let old = details.deposit;
710				if old > deposit {
711					T::Currency::unreserve(&collection_details.owner, old - deposit);
712				} else if deposit > old {
713					if T::Currency::reserve(&collection_details.owner, deposit - old).is_err() {
714						// NOTE: No alterations made to collection_details in this iteration so far,
715						// so this is OK to do.
716						continue;
717					}
718				} else {
719					continue;
720				}
721				collection_details.total_deposit.saturating_accrue(deposit);
722				collection_details.total_deposit.saturating_reduce(old);
723				details.deposit = deposit;
724				Item::<T, I>::insert(&collection, &item, &details);
725				successful.push(item);
726			}
727			Collection::<T, I>::insert(&collection, &collection_details);
728
729			Self::deposit_event(Event::<T, I>::Redeposited {
730				collection,
731				successful_items: successful,
732			});
733
734			Ok(())
735		}
736
737		/// Disallow further unprivileged transfer of an item.
738		///
739		/// Origin must be Signed and the sender should be the Freezer of the `collection`.
740		///
741		/// - `collection`: The collection of the item to be frozen.
742		/// - `item`: The item of the item to be frozen.
743		///
744		/// Emits `Frozen`.
745		///
746		/// Weight: `O(1)`
747		#[pallet::call_index(7)]
748		#[pallet::weight(T::WeightInfo::freeze())]
749		pub fn freeze(
750			origin: OriginFor<T>,
751			collection: T::CollectionId,
752			item: T::ItemId,
753		) -> DispatchResult {
754			let origin = ensure_signed(origin)?;
755
756			let mut details =
757				Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownCollection)?;
758			let collection_details =
759				Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
760			ensure!(collection_details.freezer == origin, Error::<T, I>::NoPermission);
761
762			details.is_frozen = true;
763			Item::<T, I>::insert(&collection, &item, &details);
764
765			Self::deposit_event(Event::<T, I>::Frozen { collection, item });
766			Ok(())
767		}
768
769		/// Re-allow unprivileged transfer of an item.
770		///
771		/// Origin must be Signed and the sender should be the Freezer of the `collection`.
772		///
773		/// - `collection`: The collection of the item to be thawed.
774		/// - `item`: The item of the item to be thawed.
775		///
776		/// Emits `Thawed`.
777		///
778		/// Weight: `O(1)`
779		#[pallet::call_index(8)]
780		#[pallet::weight(T::WeightInfo::thaw())]
781		pub fn thaw(
782			origin: OriginFor<T>,
783			collection: T::CollectionId,
784			item: T::ItemId,
785		) -> DispatchResult {
786			let origin = ensure_signed(origin)?;
787
788			let mut details =
789				Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownCollection)?;
790			let collection_details =
791				Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
792			ensure!(collection_details.admin == origin, Error::<T, I>::NoPermission);
793
794			details.is_frozen = false;
795			Item::<T, I>::insert(&collection, &item, &details);
796
797			Self::deposit_event(Event::<T, I>::Thawed { collection, item });
798			Ok(())
799		}
800
801		/// Disallow further unprivileged transfers for a whole collection.
802		///
803		/// Origin must be Signed and the sender should be the Freezer of the `collection`.
804		///
805		/// - `collection`: The collection to be frozen.
806		///
807		/// Emits `CollectionFrozen`.
808		///
809		/// Weight: `O(1)`
810		#[pallet::call_index(9)]
811		#[pallet::weight(T::WeightInfo::freeze_collection())]
812		pub fn freeze_collection(
813			origin: OriginFor<T>,
814			collection: T::CollectionId,
815		) -> DispatchResult {
816			let origin = ensure_signed(origin)?;
817
818			Collection::<T, I>::try_mutate(collection.clone(), |maybe_details| {
819				let details = maybe_details.as_mut().ok_or(Error::<T, I>::UnknownCollection)?;
820				ensure!(origin == details.freezer, Error::<T, I>::NoPermission);
821
822				details.is_frozen = true;
823
824				Self::deposit_event(Event::<T, I>::CollectionFrozen { collection });
825				Ok(())
826			})
827		}
828
829		/// Re-allow unprivileged transfers for a whole collection.
830		///
831		/// Origin must be Signed and the sender should be the Admin of the `collection`.
832		///
833		/// - `collection`: The collection to be thawed.
834		///
835		/// Emits `CollectionThawed`.
836		///
837		/// Weight: `O(1)`
838		#[pallet::call_index(10)]
839		#[pallet::weight(T::WeightInfo::thaw_collection())]
840		pub fn thaw_collection(
841			origin: OriginFor<T>,
842			collection: T::CollectionId,
843		) -> DispatchResult {
844			let origin = ensure_signed(origin)?;
845
846			Collection::<T, I>::try_mutate(collection.clone(), |maybe_details| {
847				let details = maybe_details.as_mut().ok_or(Error::<T, I>::UnknownCollection)?;
848				ensure!(origin == details.admin, Error::<T, I>::NoPermission);
849
850				details.is_frozen = false;
851
852				Self::deposit_event(Event::<T, I>::CollectionThawed { collection });
853				Ok(())
854			})
855		}
856
857		/// Change the Owner of a collection.
858		///
859		/// Origin must be Signed and the sender should be the Owner of the `collection`.
860		///
861		/// The collection's total deposit is moved from the current to the new owner. Fails with
862		/// [`Error::IncompleteDepositTransfer`] if a lock or freeze on the current owner blocks
863		/// the full transfer; clear it and retry.
864		///
865		/// - `collection`: The collection whose owner should be changed.
866		/// - `owner`: The new Owner of this collection. They must have called
867		///   `set_accept_ownership` with `collection` in order for this operation to succeed.
868		///
869		/// Emits `OwnerChanged`.
870		///
871		/// Weight: `O(1)`
872		#[pallet::call_index(11)]
873		#[pallet::weight(T::WeightInfo::transfer_ownership())]
874		pub fn transfer_ownership(
875			origin: OriginFor<T>,
876			collection: T::CollectionId,
877			new_owner: AccountIdLookupOf<T>,
878		) -> DispatchResult {
879			let origin = ensure_signed(origin)?;
880			let new_owner = T::Lookup::lookup(new_owner)?;
881
882			let acceptable_collection = OwnershipAcceptance::<T, I>::get(&new_owner);
883			ensure!(acceptable_collection.as_ref() == Some(&collection), Error::<T, I>::Unaccepted);
884
885			Collection::<T, I>::try_mutate(collection.clone(), |maybe_details| {
886				let details = maybe_details.as_mut().ok_or(Error::<T, I>::UnknownCollection)?;
887				ensure!(origin == details.owner, Error::<T, I>::NoPermission);
888				if details.owner == new_owner {
889					return Ok(());
890				}
891
892				// Move the deposit to the new owner. `repatriate_reserved` is best-effort: reject
893				// any partial move so the recorded deposit stays in sync with what is actually
894				// reserved on the owner.
895				let remaining = T::Currency::repatriate_reserved(
896					&details.owner,
897					&new_owner,
898					details.total_deposit,
899					Reserved,
900				)?;
901				ensure!(remaining.is_zero(), Error::<T, I>::IncompleteDepositTransfer);
902
903				CollectionAccount::<T, I>::remove(&details.owner, &collection);
904				CollectionAccount::<T, I>::insert(&new_owner, &collection, ());
905
906				details.owner = new_owner.clone();
907				OwnershipAcceptance::<T, I>::remove(&new_owner);
908				frame_system::Pallet::<T>::dec_consumers(&new_owner);
909
910				Self::deposit_event(Event::OwnerChanged { collection, new_owner });
911				Ok(())
912			})
913		}
914
915		/// Change the Issuer, Admin and Freezer of a collection.
916		///
917		/// Origin must be Signed and the sender should be the Owner of the `collection`.
918		///
919		/// - `collection`: The collection whose team should be changed.
920		/// - `issuer`: The new Issuer of this collection.
921		/// - `admin`: The new Admin of this collection.
922		/// - `freezer`: The new Freezer of this collection.
923		///
924		/// Emits `TeamChanged`.
925		///
926		/// Weight: `O(1)`
927		#[pallet::call_index(12)]
928		#[pallet::weight(T::WeightInfo::set_team())]
929		pub fn set_team(
930			origin: OriginFor<T>,
931			collection: T::CollectionId,
932			issuer: AccountIdLookupOf<T>,
933			admin: AccountIdLookupOf<T>,
934			freezer: AccountIdLookupOf<T>,
935		) -> DispatchResult {
936			let origin = ensure_signed(origin)?;
937			let issuer = T::Lookup::lookup(issuer)?;
938			let admin = T::Lookup::lookup(admin)?;
939			let freezer = T::Lookup::lookup(freezer)?;
940
941			Collection::<T, I>::try_mutate(collection.clone(), |maybe_details| {
942				let details = maybe_details.as_mut().ok_or(Error::<T, I>::UnknownCollection)?;
943				ensure!(origin == details.owner, Error::<T, I>::NoPermission);
944
945				details.issuer = issuer.clone();
946				details.admin = admin.clone();
947				details.freezer = freezer.clone();
948
949				Self::deposit_event(Event::TeamChanged { collection, issuer, admin, freezer });
950				Ok(())
951			})
952		}
953
954		/// Approve an item to be transferred by a delegated third-party account.
955		///
956		/// The origin must conform to `ForceOrigin` or must be `Signed` and the sender must be
957		/// either the owner of the `item` or the admin of the collection.
958		///
959		/// - `collection`: The collection of the item to be approved for delegated transfer.
960		/// - `item`: The item of the item to be approved for delegated transfer.
961		/// - `delegate`: The account to delegate permission to transfer the item.
962		///
963		/// Important NOTE: The `approved` account gets reset after each transfer.
964		///
965		/// Emits `ApprovedTransfer` on success.
966		///
967		/// Weight: `O(1)`
968		#[pallet::call_index(13)]
969		#[pallet::weight(T::WeightInfo::approve_transfer())]
970		pub fn approve_transfer(
971			origin: OriginFor<T>,
972			collection: T::CollectionId,
973			item: T::ItemId,
974			delegate: AccountIdLookupOf<T>,
975		) -> DispatchResult {
976			let maybe_check: Option<T::AccountId> = T::ForceOrigin::try_origin(origin)
977				.map(|_| None)
978				.or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?;
979
980			let delegate = T::Lookup::lookup(delegate)?;
981
982			let collection_details =
983				Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
984			let mut details =
985				Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownCollection)?;
986
987			if let Some(check) = maybe_check {
988				let permitted = check == collection_details.admin || check == details.owner;
989				ensure!(permitted, Error::<T, I>::NoPermission);
990			}
991
992			details.approved = Some(delegate);
993			Item::<T, I>::insert(&collection, &item, &details);
994
995			let delegate = details.approved.expect("set as Some above; qed");
996			Self::deposit_event(Event::ApprovedTransfer {
997				collection,
998				item,
999				owner: details.owner,
1000				delegate,
1001			});
1002
1003			Ok(())
1004		}
1005
1006		/// Cancel the prior approval for the transfer of an item by a delegate.
1007		///
1008		/// Origin must be either:
1009		/// - the `Force` origin;
1010		/// - `Signed` with the signer being the Admin of the `collection`;
1011		/// - `Signed` with the signer being the Owner of the `item`;
1012		///
1013		/// Arguments:
1014		/// - `collection`: The collection of the item of whose approval will be cancelled.
1015		/// - `item`: The item of the item of whose approval will be cancelled.
1016		/// - `maybe_check_delegate`: If `Some` will ensure that the given account is the one to
1017		///   which permission of transfer is delegated.
1018		///
1019		/// Emits `ApprovalCancelled` on success.
1020		///
1021		/// Weight: `O(1)`
1022		#[pallet::call_index(14)]
1023		#[pallet::weight(T::WeightInfo::cancel_approval())]
1024		pub fn cancel_approval(
1025			origin: OriginFor<T>,
1026			collection: T::CollectionId,
1027			item: T::ItemId,
1028			maybe_check_delegate: Option<AccountIdLookupOf<T>>,
1029		) -> DispatchResult {
1030			let maybe_check: Option<T::AccountId> = T::ForceOrigin::try_origin(origin)
1031				.map(|_| None)
1032				.or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?;
1033
1034			let collection_details =
1035				Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
1036			let mut details =
1037				Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownCollection)?;
1038			if let Some(check) = maybe_check {
1039				let permitted = check == collection_details.admin || check == details.owner;
1040				ensure!(permitted, Error::<T, I>::NoPermission);
1041			}
1042			let maybe_check_delegate = maybe_check_delegate.map(T::Lookup::lookup).transpose()?;
1043			let old = details.approved.take().ok_or(Error::<T, I>::NoDelegate)?;
1044			if let Some(check_delegate) = maybe_check_delegate {
1045				ensure!(check_delegate == old, Error::<T, I>::WrongDelegate);
1046			}
1047
1048			Item::<T, I>::insert(&collection, &item, &details);
1049			Self::deposit_event(Event::ApprovalCancelled {
1050				collection,
1051				item,
1052				owner: details.owner,
1053				delegate: old,
1054			});
1055
1056			Ok(())
1057		}
1058
1059		/// Alter the attributes of a given item.
1060		///
1061		/// Origin must be `ForceOrigin`.
1062		///
1063		/// - `collection`: The identifier of the item.
1064		/// - `owner`: The new Owner of this item.
1065		/// - `issuer`: The new Issuer of this item.
1066		/// - `admin`: The new Admin of this item.
1067		/// - `freezer`: The new Freezer of this item.
1068		/// - `free_holding`: Whether a deposit is taken for holding an item of this collection.
1069		/// - `is_frozen`: Whether this collection is frozen except for permissioned/admin
1070		/// instructions.
1071		///
1072		/// Emits `ItemStatusChanged` with the identity of the item.
1073		///
1074		/// Weight: `O(1)`
1075		#[pallet::call_index(15)]
1076		#[pallet::weight(T::WeightInfo::force_item_status())]
1077		pub fn force_item_status(
1078			origin: OriginFor<T>,
1079			collection: T::CollectionId,
1080			owner: AccountIdLookupOf<T>,
1081			issuer: AccountIdLookupOf<T>,
1082			admin: AccountIdLookupOf<T>,
1083			freezer: AccountIdLookupOf<T>,
1084			free_holding: bool,
1085			is_frozen: bool,
1086		) -> DispatchResult {
1087			T::ForceOrigin::ensure_origin(origin)?;
1088
1089			Collection::<T, I>::try_mutate(collection.clone(), |maybe_item| {
1090				let mut item = maybe_item.take().ok_or(Error::<T, I>::UnknownCollection)?;
1091				let old_owner = item.owner;
1092				let new_owner = T::Lookup::lookup(owner)?;
1093				item.owner = new_owner.clone();
1094				item.issuer = T::Lookup::lookup(issuer)?;
1095				item.admin = T::Lookup::lookup(admin)?;
1096				item.freezer = T::Lookup::lookup(freezer)?;
1097				item.free_holding = free_holding;
1098				item.is_frozen = is_frozen;
1099				*maybe_item = Some(item);
1100				CollectionAccount::<T, I>::remove(&old_owner, &collection);
1101				CollectionAccount::<T, I>::insert(&new_owner, &collection, ());
1102
1103				Self::deposit_event(Event::ItemStatusChanged { collection });
1104				Ok(())
1105			})
1106		}
1107
1108		/// Set an attribute for a collection or item.
1109		///
1110		/// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the
1111		/// `collection`.
1112		///
1113		/// If the origin is Signed, then funds of signer are reserved according to the formula:
1114		/// `MetadataDepositBase + DepositPerByte * (key.len + value.len)` taking into
1115		/// account any already reserved funds.
1116		///
1117		/// - `collection`: The identifier of the collection whose item's metadata to set.
1118		/// - `maybe_item`: The identifier of the item whose metadata to set.
1119		/// - `key`: The key of the attribute.
1120		/// - `value`: The value to which to set the attribute.
1121		///
1122		/// Emits `AttributeSet`.
1123		///
1124		/// Weight: `O(1)`
1125		#[pallet::call_index(16)]
1126		#[pallet::weight(T::WeightInfo::set_attribute())]
1127		pub fn set_attribute(
1128			origin: OriginFor<T>,
1129			collection: T::CollectionId,
1130			maybe_item: Option<T::ItemId>,
1131			key: BoundedVec<u8, T::KeyLimit>,
1132			value: BoundedVec<u8, T::ValueLimit>,
1133		) -> DispatchResult {
1134			let maybe_check_owner = T::ForceOrigin::try_origin(origin)
1135				.map(|_| None)
1136				.or_else(|origin| ensure_signed(origin).map(Some))?;
1137
1138			let mut collection_details =
1139				Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
1140			if let Some(check_owner) = &maybe_check_owner {
1141				ensure!(check_owner == &collection_details.owner, Error::<T, I>::NoPermission);
1142			}
1143			let maybe_is_frozen = match maybe_item {
1144				None => CollectionMetadataOf::<T, I>::get(collection.clone()).map(|v| v.is_frozen),
1145				Some(item) => {
1146					ItemMetadataOf::<T, I>::get(collection.clone(), item).map(|v| v.is_frozen)
1147				},
1148			};
1149			ensure!(!maybe_is_frozen.unwrap_or(false), Error::<T, I>::Frozen);
1150
1151			let attribute = Attribute::<T, I>::get((collection.clone(), maybe_item, &key));
1152			if attribute.is_none() {
1153				collection_details.attributes.saturating_inc();
1154			}
1155			let old_deposit = attribute.map_or(Zero::zero(), |m| m.1);
1156			collection_details.total_deposit.saturating_reduce(old_deposit);
1157			let mut deposit = Zero::zero();
1158			if !collection_details.free_holding && maybe_check_owner.is_some() {
1159				deposit = T::DepositPerByte::get()
1160					.saturating_mul(((key.len() + value.len()) as u32).into())
1161					.saturating_add(T::AttributeDepositBase::get());
1162			}
1163			collection_details.total_deposit.saturating_accrue(deposit);
1164			if deposit > old_deposit {
1165				T::Currency::reserve(&collection_details.owner, deposit - old_deposit)?;
1166			} else if deposit < old_deposit {
1167				T::Currency::unreserve(&collection_details.owner, old_deposit - deposit);
1168			}
1169
1170			Attribute::<T, I>::insert((&collection, maybe_item, &key), (&value, deposit));
1171			Collection::<T, I>::insert(collection.clone(), &collection_details);
1172			Self::deposit_event(Event::AttributeSet { collection, maybe_item, key, value });
1173			Ok(())
1174		}
1175
1176		/// Clear an attribute for a collection or item.
1177		///
1178		/// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the
1179		/// `collection`.
1180		///
1181		/// Any deposit is freed for the collection's owner.
1182		///
1183		/// - `collection`: The identifier of the collection whose item's metadata to clear.
1184		/// - `maybe_item`: The identifier of the item whose metadata to clear.
1185		/// - `key`: The key of the attribute.
1186		///
1187		/// Emits `AttributeCleared`.
1188		///
1189		/// Weight: `O(1)`
1190		#[pallet::call_index(17)]
1191		#[pallet::weight(T::WeightInfo::clear_attribute())]
1192		pub fn clear_attribute(
1193			origin: OriginFor<T>,
1194			collection: T::CollectionId,
1195			maybe_item: Option<T::ItemId>,
1196			key: BoundedVec<u8, T::KeyLimit>,
1197		) -> DispatchResult {
1198			let maybe_check_owner = T::ForceOrigin::try_origin(origin)
1199				.map(|_| None)
1200				.or_else(|origin| ensure_signed(origin).map(Some))?;
1201
1202			let mut collection_details =
1203				Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
1204			if let Some(check_owner) = &maybe_check_owner {
1205				ensure!(check_owner == &collection_details.owner, Error::<T, I>::NoPermission);
1206			}
1207			let maybe_is_frozen = match maybe_item {
1208				None => CollectionMetadataOf::<T, I>::get(collection.clone()).map(|v| v.is_frozen),
1209				Some(item) => {
1210					ItemMetadataOf::<T, I>::get(collection.clone(), item).map(|v| v.is_frozen)
1211				},
1212			};
1213			ensure!(!maybe_is_frozen.unwrap_or(false), Error::<T, I>::Frozen);
1214
1215			if let Some((_, deposit)) =
1216				Attribute::<T, I>::take((collection.clone(), maybe_item, &key))
1217			{
1218				collection_details.attributes.saturating_dec();
1219				collection_details.total_deposit.saturating_reduce(deposit);
1220				T::Currency::unreserve(&collection_details.owner, deposit);
1221				Collection::<T, I>::insert(collection.clone(), &collection_details);
1222				Self::deposit_event(Event::AttributeCleared { collection, maybe_item, key });
1223			}
1224			Ok(())
1225		}
1226
1227		/// Set the metadata for an item.
1228		///
1229		/// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the
1230		/// `collection`.
1231		///
1232		/// If the origin is Signed, then funds of signer are reserved according to the formula:
1233		/// `MetadataDepositBase + DepositPerByte * data.len` taking into
1234		/// account any already reserved funds.
1235		///
1236		/// - `collection`: The identifier of the collection whose item's metadata to set.
1237		/// - `item`: The identifier of the item whose metadata to set.
1238		/// - `data`: The general information of this item. Limited in length by `StringLimit`.
1239		/// - `is_frozen`: Whether the metadata should be frozen against further changes.
1240		///
1241		/// Emits `MetadataSet`.
1242		///
1243		/// Weight: `O(1)`
1244		#[pallet::call_index(18)]
1245		#[pallet::weight(T::WeightInfo::set_metadata())]
1246		pub fn set_metadata(
1247			origin: OriginFor<T>,
1248			collection: T::CollectionId,
1249			item: T::ItemId,
1250			data: BoundedVec<u8, T::StringLimit>,
1251			is_frozen: bool,
1252		) -> DispatchResult {
1253			let maybe_check_owner = T::ForceOrigin::try_origin(origin)
1254				.map(|_| None)
1255				.or_else(|origin| ensure_signed(origin).map(Some))?;
1256
1257			let mut collection_details =
1258				Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
1259
1260			if let Some(check_owner) = &maybe_check_owner {
1261				ensure!(check_owner == &collection_details.owner, Error::<T, I>::NoPermission);
1262			}
1263
1264			ItemMetadataOf::<T, I>::try_mutate_exists(collection.clone(), item, |metadata| {
1265				let was_frozen = metadata.as_ref().map_or(false, |m| m.is_frozen);
1266				ensure!(maybe_check_owner.is_none() || !was_frozen, Error::<T, I>::Frozen);
1267
1268				if metadata.is_none() {
1269					collection_details.item_metadatas.saturating_inc();
1270				}
1271				let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit);
1272				collection_details.total_deposit.saturating_reduce(old_deposit);
1273				let mut deposit = Zero::zero();
1274				if !collection_details.free_holding && maybe_check_owner.is_some() {
1275					deposit = T::DepositPerByte::get()
1276						.saturating_mul(((data.len()) as u32).into())
1277						.saturating_add(T::MetadataDepositBase::get());
1278				}
1279				if deposit > old_deposit {
1280					T::Currency::reserve(&collection_details.owner, deposit - old_deposit)?;
1281				} else if deposit < old_deposit {
1282					T::Currency::unreserve(&collection_details.owner, old_deposit - deposit);
1283				}
1284				collection_details.total_deposit.saturating_accrue(deposit);
1285
1286				*metadata = Some(ItemMetadata { deposit, data: data.clone(), is_frozen });
1287
1288				Collection::<T, I>::insert(&collection, &collection_details);
1289				Self::deposit_event(Event::MetadataSet { collection, item, data, is_frozen });
1290				Ok(())
1291			})
1292		}
1293
1294		/// Clear the metadata for an item.
1295		///
1296		/// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the
1297		/// `item`.
1298		///
1299		/// Any deposit is freed for the collection's owner.
1300		///
1301		/// - `collection`: The identifier of the collection whose item's metadata to clear.
1302		/// - `item`: The identifier of the item whose metadata to clear.
1303		///
1304		/// Emits `MetadataCleared`.
1305		///
1306		/// Weight: `O(1)`
1307		#[pallet::call_index(19)]
1308		#[pallet::weight(T::WeightInfo::clear_metadata())]
1309		pub fn clear_metadata(
1310			origin: OriginFor<T>,
1311			collection: T::CollectionId,
1312			item: T::ItemId,
1313		) -> DispatchResult {
1314			let maybe_check_owner = T::ForceOrigin::try_origin(origin)
1315				.map(|_| None)
1316				.or_else(|origin| ensure_signed(origin).map(Some))?;
1317
1318			let mut collection_details =
1319				Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
1320			if let Some(check_owner) = &maybe_check_owner {
1321				ensure!(check_owner == &collection_details.owner, Error::<T, I>::NoPermission);
1322			}
1323
1324			ItemMetadataOf::<T, I>::try_mutate_exists(collection.clone(), item, |metadata| {
1325				let was_frozen = metadata.as_ref().map_or(false, |m| m.is_frozen);
1326				ensure!(maybe_check_owner.is_none() || !was_frozen, Error::<T, I>::Frozen);
1327
1328				if metadata.is_some() {
1329					collection_details.item_metadatas.saturating_dec();
1330				}
1331				let deposit = metadata.take().ok_or(Error::<T, I>::UnknownCollection)?.deposit;
1332				T::Currency::unreserve(&collection_details.owner, deposit);
1333				collection_details.total_deposit.saturating_reduce(deposit);
1334
1335				Collection::<T, I>::insert(&collection, &collection_details);
1336				Self::deposit_event(Event::MetadataCleared { collection, item });
1337				Ok(())
1338			})
1339		}
1340
1341		/// Set the metadata for a collection.
1342		///
1343		/// Origin must be either `ForceOrigin` or `Signed` and the sender should be the Owner of
1344		/// the `collection`.
1345		///
1346		/// If the origin is `Signed`, then funds of signer are reserved according to the formula:
1347		/// `MetadataDepositBase + DepositPerByte * data.len` taking into
1348		/// account any already reserved funds.
1349		///
1350		/// - `collection`: The identifier of the item whose metadata to update.
1351		/// - `data`: The general information of this item. Limited in length by `StringLimit`.
1352		/// - `is_frozen`: Whether the metadata should be frozen against further changes.
1353		///
1354		/// Emits `CollectionMetadataSet`.
1355		///
1356		/// Weight: `O(1)`
1357		#[pallet::call_index(20)]
1358		#[pallet::weight(T::WeightInfo::set_collection_metadata())]
1359		pub fn set_collection_metadata(
1360			origin: OriginFor<T>,
1361			collection: T::CollectionId,
1362			data: BoundedVec<u8, T::StringLimit>,
1363			is_frozen: bool,
1364		) -> DispatchResult {
1365			let maybe_check_owner = T::ForceOrigin::try_origin(origin)
1366				.map(|_| None)
1367				.or_else(|origin| ensure_signed(origin).map(Some))?;
1368
1369			let mut details =
1370				Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
1371			if let Some(check_owner) = &maybe_check_owner {
1372				ensure!(check_owner == &details.owner, Error::<T, I>::NoPermission);
1373			}
1374
1375			CollectionMetadataOf::<T, I>::try_mutate_exists(collection.clone(), |metadata| {
1376				let was_frozen = metadata.as_ref().map_or(false, |m| m.is_frozen);
1377				ensure!(maybe_check_owner.is_none() || !was_frozen, Error::<T, I>::Frozen);
1378
1379				let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit);
1380				details.total_deposit.saturating_reduce(old_deposit);
1381				let mut deposit = Zero::zero();
1382				if maybe_check_owner.is_some() && !details.free_holding {
1383					deposit = T::DepositPerByte::get()
1384						.saturating_mul(((data.len()) as u32).into())
1385						.saturating_add(T::MetadataDepositBase::get());
1386				}
1387				if deposit > old_deposit {
1388					T::Currency::reserve(&details.owner, deposit - old_deposit)?;
1389				} else if deposit < old_deposit {
1390					T::Currency::unreserve(&details.owner, old_deposit - deposit);
1391				}
1392				details.total_deposit.saturating_accrue(deposit);
1393
1394				Collection::<T, I>::insert(&collection, details);
1395
1396				*metadata = Some(CollectionMetadata { deposit, data: data.clone(), is_frozen });
1397
1398				Self::deposit_event(Event::CollectionMetadataSet { collection, data, is_frozen });
1399				Ok(())
1400			})
1401		}
1402
1403		/// Clear the metadata for a collection.
1404		///
1405		/// Origin must be either `ForceOrigin` or `Signed` and the sender should be the Owner of
1406		/// the `collection`.
1407		///
1408		/// Any deposit is freed for the collection's owner.
1409		///
1410		/// - `collection`: The identifier of the collection whose metadata to clear.
1411		///
1412		/// Emits `CollectionMetadataCleared`.
1413		///
1414		/// Weight: `O(1)`
1415		#[pallet::call_index(21)]
1416		#[pallet::weight(T::WeightInfo::clear_collection_metadata())]
1417		pub fn clear_collection_metadata(
1418			origin: OriginFor<T>,
1419			collection: T::CollectionId,
1420		) -> DispatchResult {
1421			let maybe_check_owner = T::ForceOrigin::try_origin(origin)
1422				.map(|_| None)
1423				.or_else(|origin| ensure_signed(origin).map(Some))?;
1424
1425			let mut details =
1426				Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
1427			if let Some(check_owner) = &maybe_check_owner {
1428				ensure!(check_owner == &details.owner, Error::<T, I>::NoPermission);
1429			}
1430
1431			CollectionMetadataOf::<T, I>::try_mutate_exists(collection.clone(), |metadata| {
1432				let was_frozen = metadata.as_ref().map_or(false, |m| m.is_frozen);
1433				ensure!(maybe_check_owner.is_none() || !was_frozen, Error::<T, I>::Frozen);
1434
1435				let deposit = metadata.take().ok_or(Error::<T, I>::UnknownCollection)?.deposit;
1436				T::Currency::unreserve(&details.owner, deposit);
1437				details.total_deposit.saturating_reduce(deposit);
1438				Collection::<T, I>::insert(&collection, details);
1439				Self::deposit_event(Event::CollectionMetadataCleared { collection });
1440				Ok(())
1441			})
1442		}
1443
1444		/// Set (or reset) the acceptance of ownership for a particular account.
1445		///
1446		/// Origin must be `Signed` and if `maybe_collection` is `Some`, then the signer must have a
1447		/// provider reference.
1448		///
1449		/// - `maybe_collection`: The identifier of the collection whose ownership the signer is
1450		///   willing to accept, or if `None`, an indication that the signer is willing to accept no
1451		///   ownership transferal.
1452		///
1453		/// Emits `OwnershipAcceptanceChanged`.
1454		#[pallet::call_index(22)]
1455		#[pallet::weight(T::WeightInfo::set_accept_ownership())]
1456		pub fn set_accept_ownership(
1457			origin: OriginFor<T>,
1458			maybe_collection: Option<T::CollectionId>,
1459		) -> DispatchResult {
1460			let who = ensure_signed(origin)?;
1461			let exists = OwnershipAcceptance::<T, I>::contains_key(&who);
1462			match (exists, maybe_collection.is_some()) {
1463				(false, true) => {
1464					frame_system::Pallet::<T>::inc_consumers(&who)?;
1465				},
1466				(true, false) => {
1467					frame_system::Pallet::<T>::dec_consumers(&who);
1468				},
1469				_ => {},
1470			}
1471			if let Some(collection) = maybe_collection.as_ref() {
1472				OwnershipAcceptance::<T, I>::insert(&who, collection);
1473			} else {
1474				OwnershipAcceptance::<T, I>::remove(&who);
1475			}
1476			Self::deposit_event(Event::OwnershipAcceptanceChanged { who, maybe_collection });
1477			Ok(())
1478		}
1479
1480		/// Set the maximum amount of items a collection could have.
1481		///
1482		/// Origin must be either `ForceOrigin` or `Signed` and the sender should be the Owner of
1483		/// the `collection`.
1484		///
1485		/// Note: This function can only succeed once per collection.
1486		///
1487		/// - `collection`: The identifier of the collection to change.
1488		/// - `max_supply`: The maximum amount of items a collection could have.
1489		///
1490		/// Emits `CollectionMaxSupplySet` event when successful.
1491		#[pallet::call_index(23)]
1492		#[pallet::weight(T::WeightInfo::set_collection_max_supply())]
1493		pub fn set_collection_max_supply(
1494			origin: OriginFor<T>,
1495			collection: T::CollectionId,
1496			max_supply: u32,
1497		) -> DispatchResult {
1498			let maybe_check_owner = T::ForceOrigin::try_origin(origin)
1499				.map(|_| None)
1500				.or_else(|origin| ensure_signed(origin).map(Some))?;
1501
1502			ensure!(
1503				!CollectionMaxSupply::<T, I>::contains_key(&collection),
1504				Error::<T, I>::MaxSupplyAlreadySet
1505			);
1506
1507			let details =
1508				Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
1509			if let Some(check_owner) = &maybe_check_owner {
1510				ensure!(check_owner == &details.owner, Error::<T, I>::NoPermission);
1511			}
1512
1513			ensure!(details.items <= max_supply, Error::<T, I>::MaxSupplyTooSmall);
1514
1515			CollectionMaxSupply::<T, I>::insert(&collection, max_supply);
1516			Self::deposit_event(Event::CollectionMaxSupplySet { collection, max_supply });
1517			Ok(())
1518		}
1519
1520		/// Set (or reset) the price for an item.
1521		///
1522		/// Origin must be Signed and must be the owner of the asset `item`.
1523		///
1524		/// - `collection`: The collection of the item.
1525		/// - `item`: The item to set the price for.
1526		/// - `price`: The price for the item. Pass `None`, to reset the price.
1527		/// - `buyer`: Restricts the buy operation to a specific account.
1528		///
1529		/// Emits `ItemPriceSet` on success if the price is not `None`.
1530		/// Emits `ItemPriceRemoved` on success if the price is `None`.
1531		#[pallet::call_index(24)]
1532		#[pallet::weight(T::WeightInfo::set_price())]
1533		pub fn set_price(
1534			origin: OriginFor<T>,
1535			collection: T::CollectionId,
1536			item: T::ItemId,
1537			price: Option<ItemPrice<T, I>>,
1538			whitelisted_buyer: Option<AccountIdLookupOf<T>>,
1539		) -> DispatchResult {
1540			let origin = ensure_signed(origin)?;
1541			let whitelisted_buyer = whitelisted_buyer.map(T::Lookup::lookup).transpose()?;
1542			Self::do_set_price(collection, item, origin, price, whitelisted_buyer)
1543		}
1544
1545		/// Allows to buy an item if it's up for sale.
1546		///
1547		/// Origin must be Signed and must not be the owner of the `item`.
1548		///
1549		/// - `collection`: The collection of the item.
1550		/// - `item`: The item the sender wants to buy.
1551		/// - `bid_price`: The price the sender is willing to pay.
1552		///
1553		/// Emits `ItemBought` on success.
1554		#[pallet::call_index(25)]
1555		#[pallet::weight(T::WeightInfo::buy_item())]
1556		pub fn buy_item(
1557			origin: OriginFor<T>,
1558			collection: T::CollectionId,
1559			item: T::ItemId,
1560			bid_price: ItemPrice<T, I>,
1561		) -> DispatchResult {
1562			let origin = ensure_signed(origin)?;
1563			Self::do_buy_item(collection, item, origin, bid_price)
1564		}
1565	}
1566}