git.delta.rocks / unique-network / refs/commits / 008dfe41f9df

difftreelog

source

pallets/unique/src/lib.rs45.5 KiBsourcehistory
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # Unique Pallet18//!19//! A pallet governing Unique transactions.20//!21//! - [`Config`]22//! - [`Call`]23//! - [`Pallet`]24//!25//! ## Overview26//!27//! The Unique pallet's purpose is to be the primary interface between28//! external users and the inner structure of the Unique chains.29//!30//! It also contains an implementation of [`CollectionHelpers`][`eth`],31//! an Ethereum contract dealing with collection operations.32//!33//! ## Interface34//!35//! ### Dispatchables36//!37//! - `create_collection` - Create a collection of tokens. **Deprecated**, use `create_collection_ex`.38//! - `create_collection_ex` - Create a collection of tokens with explicit parameters.39//! - `destroy_collection` - Destroy a collection if no tokens exist within.40//! - `add_to_allow_list` - Add an address to allow list.41//! - `remove_from_allow_list` - Remove an address from allow list.42//! - `change_collection_owner` - Change the owner of the collection.43//! - `add_collection_admin` - Add an admin to a collection.44//! - `remove_collection_admin` - Remove admin of a collection.45//! - `set_collection_sponsor` - Invite a new collection sponsor.46//! - `confirm_sponsorship` - Confirm own sponsorship of a collection, becoming the sponsor.47//! - `remove_collection_sponsor` - Remove a sponsor from a collection.48//! - `create_item` - Create an item within a collection.49//! - `create_multiple_items` - Create multiple items within a collection.50//! - `set_collection_properties` - Add or change collection properties.51//! - `delete_collection_properties` - Delete specified collection properties.52//! - `set_token_properties` - Add or change token properties.53//! - `delete_token_properties` - Delete token properties.54//! - `set_token_property_permissions` - Add or change token property permissions of a collection.55//! - `create_multiple_items_ex` - Create multiple items within a collection with explicitly specified initial parameters.56//! - `set_transfers_enabled_flag` - Completely allow or disallow transfers for a particular collection.57//! - `burn_item` - Destroy an item.58//! - `burn_from` - Destroy an item on behalf of the owner as a non-owner account.59//! - `transfer` - Change ownership of the token.60//! - `transfer_from` - Change ownership of the token on behalf of the owner as a non-owner account.61//! - `approve` - Allow a non-permissioned address to transfer or burn an item.62//! - `set_collection_limits` - Set specific limits of a collection.63//! - `set_collection_permissions` - Set specific permissions of a collection.64//! - `repartition` - Re-partition a refungible token, while owning all of its parts.6566#![recursion_limit = "1024"]67#![cfg_attr(not(feature = "std"), no_std)]68#![allow(69	clippy::too_many_arguments,70	clippy::unnecessary_mut_passed,71	clippy::unused_unit72)]7374extern crate alloc;7576pub use pallet::*;77use frame_support::pallet_prelude::*;78use frame_system::pallet_prelude::*;79pub mod eth;8081#[cfg(feature = "runtime-benchmarks")]82pub mod benchmarking;83pub mod weights;8485#[frame_support::pallet]86pub mod pallet {87	use super::*;8889	use frame_support::{dispatch::DispatchResult, ensure, fail, BoundedVec, storage::Key};90	use scale_info::TypeInfo;91	use frame_system::{ensure_signed, ensure_root};92	use sp_std::{vec, vec::Vec};93	use up_data_structs::{94		MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH,95		MAX_PROPERTIES_PER_ITEM, MAX_PROPERTY_KEY_LENGTH, MAX_PROPERTY_VALUE_LENGTH,96		MAX_COLLECTION_PROPERTIES_SIZE, COLLECTION_ADMINS_LIMIT, MAX_TOKEN_PROPERTIES_SIZE,97		CreateItemData, CollectionLimits, CollectionPermissions, CollectionId, CollectionMode,98		TokenId, CreateCollectionData, CreateItemExData, budget, Property, PropertyKey,99		PropertyKeyPermission,100	};101	use pallet_evm::account::CrossAccountId;102	use pallet_common::{103		CollectionHandle, Pallet as PalletCommon, CommonWeightInfo, dispatch::dispatch_tx,104		dispatch::CollectionDispatch, RefungibleExtensionsWeightInfo,105	};106	use weights::WeightInfo;107108	/// A maximum number of levels of depth in the token nesting tree.109	pub const NESTING_BUDGET: u32 = 5;110111	/// Errors for the common Unique transactions.112	#[pallet::error]113	pub enum Error<T> {114		/// Decimal_points parameter must be lower than [`up_data_structs::MAX_DECIMAL_POINTS`].115		CollectionDecimalPointLimitExceeded,116		/// Length of items properties must be greater than 0.117		EmptyArgument,118		/// Repertition is only supported by refungible collection.119		RepartitionCalledOnNonRefungibleCollection,120	}121122	/// Configuration trait of this pallet.123	#[pallet::config]124	pub trait Config: frame_system::Config + pallet_common::Config + Sized + TypeInfo {125		/// Weight information for extrinsics in this pallet.126		type WeightInfo: WeightInfo;127128		/// Weight information for common pallet operations.129		type CommonWeightInfo: CommonWeightInfo<Self::CrossAccountId>;130131		/// Weight info information for extra refungible pallet operations.132		type RefungibleExtensionsWeightInfo: RefungibleExtensionsWeightInfo;133	}134135	#[pallet::pallet]136	pub struct Pallet<T>(_);137138	pub type SelfWeightOf<T> = <T as Config>::WeightInfo;139140	// # Used definitions141	//142	// ## User control levels143	//144	// chain-controlled - key is uncontrolled by user145	//                    i.e autoincrementing index146	//                    can use non-cryptographic hash147	// real - key is controlled by user148	//        but it is hard to generate enough colliding values, i.e owner of signed txs149	//        can use non-cryptographic hash150	// controlled - key is completly controlled by users151	//              i.e maps with mutable keys152	//              should use cryptographic hash153	//154	// ## User control level downgrade reasons155	//156	// ?1 - chain-controlled -> controlled157	//      collections/tokens can be destroyed, resulting in massive holes158	// ?2 - chain-controlled -> controlled159	//      same as ?1, but can be only added, resulting in easier exploitation160	// ?3 - real -> controlled161	//      no confirmation required, so addresses can be easily generated162163	//#region Private members164	/// Used for migrations165	#[pallet::storage]166	pub type ChainVersion<T> = StorageValue<_, u64, ValueQuery>;167	//#endregion168169	//#region Tokens transfer sponosoring rate limit baskets170	/// (Collection id (controlled?2), who created (real))171	/// TODO: Off chain worker should remove from this map when collection gets removed172	#[pallet::storage]173	#[pallet::getter(fn create_item_busket)]174	pub type CreateItemBasket<T: Config> = StorageMap<175		Hasher = Blake2_128Concat,176		Key = (CollectionId, T::AccountId),177		Value = T::BlockNumber,178		QueryKind = OptionQuery,179	>;180	/// Collection id (controlled?2), token id (controlled?2)181	#[pallet::storage]182	#[pallet::getter(fn nft_transfer_basket)]183	pub type NftTransferBasket<T: Config> = StorageDoubleMap<184		Hasher1 = Blake2_128Concat,185		Key1 = CollectionId,186		Hasher2 = Blake2_128Concat,187		Key2 = TokenId,188		Value = T::BlockNumber,189		QueryKind = OptionQuery,190	>;191	/// Collection id (controlled?2), owning user (real)192	#[pallet::storage]193	#[pallet::getter(fn fungible_transfer_basket)]194	pub type FungibleTransferBasket<T: Config> = StorageDoubleMap<195		Hasher1 = Blake2_128Concat,196		Key1 = CollectionId,197		Hasher2 = Twox64Concat,198		Key2 = T::AccountId,199		Value = T::BlockNumber,200		QueryKind = OptionQuery,201	>;202	/// Collection id (controlled?2), token id (controlled?2)203	#[pallet::storage]204	#[pallet::getter(fn refungible_transfer_basket)]205	pub type ReFungibleTransferBasket<T: Config> = StorageNMap<206		Key = (207			Key<Blake2_128Concat, CollectionId>,208			Key<Blake2_128Concat, TokenId>,209			Key<Twox64Concat, T::AccountId>,210		),211		Value = T::BlockNumber,212		QueryKind = OptionQuery,213	>;214	//#endregion215216	/// Last sponsoring of token property setting // todo:doc rephrase this and the following217	#[pallet::storage]218	#[pallet::getter(fn token_property_basket)]219	pub type TokenPropertyBasket<T: Config> = StorageDoubleMap<220		Hasher1 = Blake2_128Concat,221		Key1 = CollectionId,222		Hasher2 = Blake2_128Concat,223		Key2 = TokenId,224		Value = T::BlockNumber,225		QueryKind = OptionQuery,226	>;227228	/// Last sponsoring of NFT approval in a collection229	#[pallet::storage]230	#[pallet::getter(fn nft_approve_basket)]231	pub type NftApproveBasket<T: Config> = StorageDoubleMap<232		Hasher1 = Blake2_128Concat,233		Key1 = CollectionId,234		Hasher2 = Blake2_128Concat,235		Key2 = TokenId,236		Value = T::BlockNumber,237		QueryKind = OptionQuery,238	>;239	/// Last sponsoring of fungible tokens approval in a collection240	#[pallet::storage]241	#[pallet::getter(fn fungible_approve_basket)]242	pub type FungibleApproveBasket<T: Config> = StorageDoubleMap<243		Hasher1 = Blake2_128Concat,244		Key1 = CollectionId,245		Hasher2 = Twox64Concat,246		Key2 = T::AccountId,247		Value = T::BlockNumber,248		QueryKind = OptionQuery,249	>;250	/// Last sponsoring of RFT approval in a collection251	#[pallet::storage]252	#[pallet::getter(fn refungible_approve_basket)]253	pub type RefungibleApproveBasket<T: Config> = StorageNMap<254		Key = (255			Key<Blake2_128Concat, CollectionId>,256			Key<Blake2_128Concat, TokenId>,257			Key<Twox64Concat, T::AccountId>,258		),259		Value = T::BlockNumber,260		QueryKind = OptionQuery,261	>;262263	#[pallet::extra_constants]264	impl<T: Config> Pallet<T> {265		/// A maximum number of levels of depth in the token nesting tree.266		fn nesting_budget() -> u32 {267			NESTING_BUDGET268		}269270		/// Maximal length of a collection name.271		fn max_collection_name_length() -> u32 {272			MAX_COLLECTION_NAME_LENGTH273		}274275		/// Maximal length of a collection description.276		fn max_collection_description_length() -> u32 {277			MAX_COLLECTION_DESCRIPTION_LENGTH278		}279280		/// Maximal length of a token prefix.281		fn max_token_prefix_length() -> u32 {282			MAX_TOKEN_PREFIX_LENGTH283		}284285		/// Maximum admins per collection.286		fn collection_admins_limit() -> u32 {287			COLLECTION_ADMINS_LIMIT288		}289290		/// Maximal length of a property key.291		fn max_property_key_length() -> u32 {292			MAX_PROPERTY_KEY_LENGTH293		}294295		/// Maximal length of a property value.296		fn max_property_value_length() -> u32 {297			MAX_PROPERTY_VALUE_LENGTH298		}299300		/// A maximum number of token properties.301		fn max_properties_per_item() -> u32 {302			MAX_PROPERTIES_PER_ITEM303		}304305		/// Maximum size for all collection properties.306		fn max_collection_properties_size() -> u32 {307			MAX_COLLECTION_PROPERTIES_SIZE308		}309310		/// Maximum size of all token properties.311		fn max_token_properties_size() -> u32 {312			MAX_TOKEN_PROPERTIES_SIZE313		}314315		/// Default NFT collection limit.316		fn nft_default_collection_limits() -> CollectionLimits {317			CollectionLimits::with_default_limits(CollectionMode::NFT)318		}319320		/// Default RFT collection limit.321		fn rft_default_collection_limits() -> CollectionLimits {322			CollectionLimits::with_default_limits(CollectionMode::ReFungible)323		}324325		/// Default FT collection limit.326		fn ft_default_collection_limits() -> CollectionLimits {327			CollectionLimits::with_default_limits(CollectionMode::Fungible(0))328		}329	}330331	/// Type alias to Pallet, to be used by construct_runtime.332	#[pallet::call]333	impl<T: Config> Pallet<T> {334		/// Create a collection of tokens.335		///336		/// Each Token may have multiple properties encoded as an array of bytes337		/// of certain length. The initial owner of the collection is set338		/// to the address that signed the transaction and can be changed later.339		///340		/// Prefer the more advanced [`create_collection_ex`][`Pallet::create_collection_ex`] instead.341		///342		/// # Permissions343		///344		/// * Anyone - becomes the owner of the new collection.345		///346		/// # Arguments347		///348		/// * `collection_name`: Wide-character string with collection name349		/// (limit [`MAX_COLLECTION_NAME_LENGTH`]).350		/// * `collection_description`: Wide-character string with collection description351		/// (limit [`MAX_COLLECTION_DESCRIPTION_LENGTH`]).352		/// * `token_prefix`: Byte string containing the token prefix to mark a collection353		/// to which a token belongs (limit [`MAX_TOKEN_PREFIX_LENGTH`]).354		/// * `mode`: Type of items stored in the collection and type dependent data.355		///356		/// returns collection ID357		///358		/// Deprecated: `create_collection_ex` is more up-to-date and advanced, prefer it instead.359		#[pallet::call_index(0)]360		#[pallet::weight(<SelfWeightOf<T>>::create_collection())]361		pub fn create_collection(362			origin: OriginFor<T>,363			collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,364			collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,365			token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,366			mode: CollectionMode,367		) -> DispatchResult {368			let data: CreateCollectionData<T::AccountId> = CreateCollectionData {369				name: collection_name,370				description: collection_description,371				token_prefix,372				mode,373				..Default::default()374			};375			Self::create_collection_ex(origin, data)376		}377378		/// Create a collection with explicit parameters.379		///380		/// Prefer it to the deprecated [`create_collection`][`Pallet::create_collection`] method.381		///382		/// # Permissions383		///384		/// * Anyone - becomes the owner of the new collection.385		///386		/// # Arguments387		///388		/// * `data`: Explicit data of a collection used for its creation.389		#[pallet::call_index(1)]390		#[pallet::weight(<SelfWeightOf<T>>::create_collection())]391		pub fn create_collection_ex(392			origin: OriginFor<T>,393			data: CreateCollectionData<T::AccountId>,394		) -> DispatchResult {395			let sender = ensure_signed(origin)?;396397			// =========398			let sender = T::CrossAccountId::from_sub(sender);399			let _id =400				T::CollectionDispatch::create(sender.clone(), sender, data, Default::default())?;401402			Ok(())403		}404405		/// Destroy a collection if no tokens exist within.406		///407		/// # Permissions408		///409		/// * Collection owner410		///411		/// # Arguments412		///413		/// * `collection_id`: Collection to destroy.414		#[pallet::call_index(2)]415		#[pallet::weight(<SelfWeightOf<T>>::destroy_collection())]416		pub fn destroy_collection(417			origin: OriginFor<T>,418			collection_id: CollectionId,419		) -> DispatchResult {420			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);421422			Self::destroy_collection_internal(sender, collection_id)423		}424425		/// Add an address to allow list.426		///427		/// # Permissions428		///429		/// * Collection owner430		/// * Collection admin431		///432		/// # Arguments433		///434		/// * `collection_id`: ID of the modified collection.435		/// * `address`: ID of the address to be added to the allowlist.436		#[pallet::call_index(3)]437		#[pallet::weight(<SelfWeightOf<T>>::add_to_allow_list())]438		pub fn add_to_allow_list(439			origin: OriginFor<T>,440			collection_id: CollectionId,441			address: T::CrossAccountId,442		) -> DispatchResult {443			if collection_id == pallet_common::NATIVE_FINGIBLE_COLLECTION_ID {444				fail!(<pallet_common::Error<T>>::UnsupportedOperation);445			}446447			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);448			let collection = <CollectionHandle<T>>::try_get(collection_id)?;449			collection.check_is_internal()?;450451			<PalletCommon<T>>::toggle_allowlist(&collection, &sender, &address, true)?;452453			Ok(())454		}455456		/// Remove an address from allow list.457		///458		/// # Permissions459		///460		/// * Collection owner461		/// * Collection admin462		///463		/// # Arguments464		///465		/// * `collection_id`: ID of the modified collection.466		/// * `address`: ID of the address to be removed from the allowlist.467		#[pallet::call_index(4)]468		#[pallet::weight(<SelfWeightOf<T>>::remove_from_allow_list())]469		pub fn remove_from_allow_list(470			origin: OriginFor<T>,471			collection_id: CollectionId,472			address: T::CrossAccountId,473		) -> DispatchResult {474			if collection_id == pallet_common::NATIVE_FINGIBLE_COLLECTION_ID {475				fail!(<pallet_common::Error<T>>::UnsupportedOperation);476			}477478			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);479			let collection = <CollectionHandle<T>>::try_get(collection_id)?;480			collection.check_is_internal()?;481482			<PalletCommon<T>>::toggle_allowlist(&collection, &sender, &address, false)?;483484			Ok(())485		}486487		/// Change the owner of the collection.488		///489		/// # Permissions490		///491		/// * Collection owner492		///493		/// # Arguments494		///495		/// * `collection_id`: ID of the modified collection.496		/// * `new_owner`: ID of the account that will become the owner.497		#[pallet::call_index(5)]498		#[pallet::weight(<SelfWeightOf<T>>::change_collection_owner())]499		pub fn change_collection_owner(500			origin: OriginFor<T>,501			collection_id: CollectionId,502			new_owner: T::AccountId,503		) -> DispatchResult {504			if collection_id == pallet_common::NATIVE_FINGIBLE_COLLECTION_ID {505				fail!(<pallet_common::Error<T>>::UnsupportedOperation);506			}507			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);508			let new_owner = T::CrossAccountId::from_sub(new_owner);509			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;510			target_collection.change_owner(sender, new_owner.clone())511		}512513		/// Add an admin to a collection.514		///515		/// NFT Collection can be controlled by multiple admin addresses516		/// (some which can also be servers, for example). Admins can issue517		/// and burn NFTs, as well as add and remove other admins,518		/// but cannot change NFT or Collection ownership.519		///520		/// # Permissions521		///522		/// * Collection owner523		/// * Collection admin524		///525		/// # Arguments526		///527		/// * `collection_id`: ID of the Collection to add an admin for.528		/// * `new_admin`: Address of new admin to add.529		#[pallet::call_index(6)]530		#[pallet::weight(<SelfWeightOf<T>>::add_collection_admin())]531		pub fn add_collection_admin(532			origin: OriginFor<T>,533			collection_id: CollectionId,534			new_admin_id: T::CrossAccountId,535		) -> DispatchResult {536			if collection_id == pallet_common::NATIVE_FINGIBLE_COLLECTION_ID {537				fail!(<pallet_common::Error<T>>::UnsupportedOperation);538			}539			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);540			let collection = <CollectionHandle<T>>::try_get(collection_id)?;541			<PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)542		}543544		/// Remove admin of a collection.545		///546		/// An admin address can remove itself. List of admins may become empty,547		/// in which case only Collection Owner will be able to add an Admin.548		///549		/// # Permissions550		///551		/// * Collection owner552		/// * Collection admin553		///554		/// # Arguments555		///556		/// * `collection_id`: ID of the collection to remove the admin for.557		/// * `account_id`: Address of the admin to remove.558		#[pallet::call_index(7)]559		#[pallet::weight(<SelfWeightOf<T>>::remove_collection_admin())]560		pub fn remove_collection_admin(561			origin: OriginFor<T>,562			collection_id: CollectionId,563			account_id: T::CrossAccountId,564		) -> DispatchResult {565			if collection_id == pallet_common::NATIVE_FINGIBLE_COLLECTION_ID {566				fail!(<pallet_common::Error<T>>::UnsupportedOperation);567			}568			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);569			let collection = <CollectionHandle<T>>::try_get(collection_id)?;570			<PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)571		}572573		/// Set (invite) a new collection sponsor.574		///575		/// If successful, confirmation from the sponsor-to-be will be pending.576		///577		/// # Permissions578		///579		/// * Collection owner580		/// * Collection admin581		///582		/// # Arguments583		///584		/// * `collection_id`: ID of the modified collection.585		/// * `new_sponsor`: ID of the account of the sponsor-to-be.586		#[pallet::call_index(8)]587		#[pallet::weight(<SelfWeightOf<T>>::set_collection_sponsor())]588		pub fn set_collection_sponsor(589			origin: OriginFor<T>,590			collection_id: CollectionId,591			new_sponsor: T::AccountId,592		) -> DispatchResult {593			if collection_id == pallet_common::NATIVE_FINGIBLE_COLLECTION_ID {594				fail!(<pallet_common::Error<T>>::UnsupportedOperation);595			}596			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);597			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;598			target_collection.set_sponsor(&sender, new_sponsor.clone())599		}600601		/// Confirm own sponsorship of a collection, becoming the sponsor.602		///603		/// An invitation must be pending, see [`set_collection_sponsor`][`Pallet::set_collection_sponsor`].604		/// Sponsor can pay the fees of a transaction instead of the sender,605		/// but only within specified limits.606		///607		/// # Permissions608		///609		/// * Sponsor-to-be610		///611		/// # Arguments612		///613		/// * `collection_id`: ID of the collection with the pending sponsor.614		#[pallet::call_index(9)]615		#[pallet::weight(<SelfWeightOf<T>>::confirm_sponsorship())]616		pub fn confirm_sponsorship(617			origin: OriginFor<T>,618			collection_id: CollectionId,619		) -> DispatchResult {620			if collection_id == pallet_common::NATIVE_FINGIBLE_COLLECTION_ID {621				fail!(<pallet_common::Error<T>>::UnsupportedOperation);622			}623			let sender = ensure_signed(origin)?;624			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;625			target_collection.confirm_sponsorship(&sender)626		}627628		/// Remove a collection's a sponsor, making everyone pay for their own transactions.629		///630		/// # Permissions631		///632		/// * Collection owner633		///634		/// # Arguments635		///636		/// * `collection_id`: ID of the collection with the sponsor to remove.637		#[pallet::call_index(10)]638		#[pallet::weight(<SelfWeightOf<T>>::remove_collection_sponsor())]639		pub fn remove_collection_sponsor(640			origin: OriginFor<T>,641			collection_id: CollectionId,642		) -> DispatchResult {643			if collection_id == pallet_common::NATIVE_FINGIBLE_COLLECTION_ID {644				fail!(<pallet_common::Error<T>>::UnsupportedOperation);645			}646			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);647			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;648			target_collection.remove_sponsor(&sender)649		}650651		/// Mint an item within a collection.652		///653		/// A collection must exist first, see [`create_collection_ex`][`Pallet::create_collection_ex`].654		///655		/// # Permissions656		///657		/// * Collection owner658		/// * Collection admin659		/// * Anyone if660		///     * Allow List is enabled, and661		///     * Address is added to allow list, and662		///     * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])663		///664		/// # Arguments665		///666		/// * `collection_id`: ID of the collection to which an item would belong.667		/// * `owner`: Address of the initial owner of the item.668		/// * `data`: Token data describing the item to store on chain.669		#[pallet::call_index(11)]670		#[pallet::weight(T::CommonWeightInfo::create_item(&data))]671		pub fn create_item(672			origin: OriginFor<T>,673			collection_id: CollectionId,674			owner: T::CrossAccountId,675			data: CreateItemData,676		) -> DispatchResultWithPostInfo {677			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);678			let budget = budget::Value::new(NESTING_BUDGET);679680			dispatch_tx::<T, _>(collection_id, |d| {681				d.create_item(sender, owner, data, &budget)682			})683		}684685		/// Create multiple items within a collection.686		///687		/// A collection must exist first, see [`create_collection_ex`][`Pallet::create_collection_ex`].688		///689		/// # Permissions690		///691		/// * Collection owner692		/// * Collection admin693		/// * Anyone if694		///     * Allow List is enabled, and695		///     * Address is added to the allow list, and696		///     * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])697		///698		/// # Arguments699		///700		/// * `collection_id`: ID of the collection to which the tokens would belong.701		/// * `owner`: Address of the initial owner of the tokens.702		/// * `items_data`: Vector of data describing each item to be created.703		#[pallet::call_index(12)]704		#[pallet::weight(T::CommonWeightInfo::create_multiple_items(&items_data))]705		pub fn create_multiple_items(706			origin: OriginFor<T>,707			collection_id: CollectionId,708			owner: T::CrossAccountId,709			items_data: Vec<CreateItemData>,710		) -> DispatchResultWithPostInfo {711			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);712			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);713			let budget = budget::Value::new(NESTING_BUDGET);714715			dispatch_tx::<T, _>(collection_id, |d| {716				d.create_multiple_items(sender, owner, items_data, &budget)717			})718		}719720		/// Add or change collection properties.721		///722		/// # Permissions723		///724		/// * Collection owner725		/// * Collection admin726		///727		/// # Arguments728		///729		/// * `collection_id`: ID of the modified collection.730		/// * `properties`: Vector of key-value pairs stored as the collection's metadata.731		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.732		#[pallet::call_index(13)]733		#[pallet::weight(T::CommonWeightInfo::set_collection_properties(properties.len() as u32))]734		pub fn set_collection_properties(735			origin: OriginFor<T>,736			collection_id: CollectionId,737			properties: Vec<Property>,738		) -> DispatchResultWithPostInfo {739			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);740741			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);742743			dispatch_tx::<T, _>(collection_id, |d| {744				d.set_collection_properties(sender, properties)745			})746		}747748		/// Delete specified collection properties.749		///750		/// # Permissions751		///752		/// * Collection Owner753		/// * Collection Admin754		///755		/// # Arguments756		///757		/// * `collection_id`: ID of the modified collection.758		/// * `property_keys`: Vector of keys of the properties to be deleted.759		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.760		#[pallet::call_index(14)]761		#[pallet::weight(T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32))]762		pub fn delete_collection_properties(763			origin: OriginFor<T>,764			collection_id: CollectionId,765			property_keys: Vec<PropertyKey>,766		) -> DispatchResultWithPostInfo {767			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);768769			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);770771			dispatch_tx::<T, _>(collection_id, |d| {772				d.delete_collection_properties(&sender, property_keys)773			})774		}775776		/// Add or change token properties according to collection's permissions.777		/// Currently properties only work with NFTs.778		///779		/// # Permissions780		///781		/// * Depends on collection's token property permissions and specified property mutability:782		/// 	* Collection owner783		/// 	* Collection admin784		/// 	* Token owner785		///786		/// See [`set_token_property_permissions`][`Pallet::set_token_property_permissions`].787		///788		/// # Arguments789		///790		/// * `collection_id: ID of the collection to which the token belongs.791		/// * `token_id`: ID of the modified token.792		/// * `properties`: Vector of key-value pairs stored as the token's metadata.793		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.794		#[pallet::call_index(15)]795		#[pallet::weight(T::CommonWeightInfo::set_token_properties(properties.len() as u32))]796		pub fn set_token_properties(797			origin: OriginFor<T>,798			collection_id: CollectionId,799			token_id: TokenId,800			properties: Vec<Property>,801		) -> DispatchResultWithPostInfo {802			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);803804			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);805			let budget = budget::Value::new(NESTING_BUDGET);806807			dispatch_tx::<T, _>(collection_id, |d| {808				d.set_token_properties(sender, token_id, properties, &budget)809			})810		}811812		/// Delete specified token properties. Currently properties only work with NFTs.813		///814		/// # Permissions815		///816		/// * Depends on collection's token property permissions and specified property mutability:817		/// 	* Collection owner818		/// 	* Collection admin819		/// 	* Token owner820		///821		/// # Arguments822		///823		/// * `collection_id`: ID of the collection to which the token belongs.824		/// * `token_id`: ID of the modified token.825		/// * `property_keys`: Vector of keys of the properties to be deleted.826		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.827		#[pallet::call_index(16)]828		#[pallet::weight(T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32))]829		pub fn delete_token_properties(830			origin: OriginFor<T>,831			collection_id: CollectionId,832			token_id: TokenId,833			property_keys: Vec<PropertyKey>,834		) -> DispatchResultWithPostInfo {835			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);836837			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);838			let budget = budget::Value::new(NESTING_BUDGET);839840			dispatch_tx::<T, _>(collection_id, |d| {841				d.delete_token_properties(sender, token_id, property_keys, &budget)842			})843		}844845		/// Add or change token property permissions of a collection.846		///847		/// Without a permission for a particular key, a property with that key848		/// cannot be created in a token.849		///850		/// # Permissions851		///852		/// * Collection owner853		/// * Collection admin854		///855		/// # Arguments856		///857		/// * `collection_id`: ID of the modified collection.858		/// * `property_permissions`: Vector of permissions for property keys.859		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.860		#[pallet::call_index(17)]861		#[pallet::weight(T::CommonWeightInfo::set_token_property_permissions(property_permissions.len() as u32))]862		pub fn set_token_property_permissions(863			origin: OriginFor<T>,864			collection_id: CollectionId,865			property_permissions: Vec<PropertyKeyPermission>,866		) -> DispatchResultWithPostInfo {867			ensure!(!property_permissions.is_empty(), Error::<T>::EmptyArgument);868869			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);870871			dispatch_tx::<T, _>(collection_id, |d| {872				d.set_token_property_permissions(&sender, property_permissions)873			})874		}875876		/// Create multiple items within a collection with explicitly specified initial parameters.877		///878		/// # Permissions879		///880		/// * Collection owner881		/// * Collection admin882		/// * Anyone if883		///     * Allow List is enabled, and884		///     * Address is added to allow list, and885		///     * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])886		///887		/// # Arguments888		///889		/// * `collection_id`: ID of the collection to which the tokens would belong.890		/// * `data`: Explicit item creation data.891		#[pallet::call_index(18)]892		#[pallet::weight(T::CommonWeightInfo::create_multiple_items_ex(&data))]893		pub fn create_multiple_items_ex(894			origin: OriginFor<T>,895			collection_id: CollectionId,896			data: CreateItemExData<T::CrossAccountId>,897		) -> DispatchResultWithPostInfo {898			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);899			let budget = budget::Value::new(NESTING_BUDGET);900901			dispatch_tx::<T, _>(collection_id, |d| {902				d.create_multiple_items_ex(sender, data, &budget)903			})904		}905906		/// Completely allow or disallow transfers for a particular collection.907		///908		/// # Permissions909		///910		/// * Collection owner911		///912		/// # Arguments913		///914		/// * `collection_id`: ID of the collection.915		/// * `value`: New value of the flag, are transfers allowed?916		#[pallet::call_index(19)]917		#[pallet::weight(<SelfWeightOf<T>>::set_transfers_enabled_flag())]918		pub fn set_transfers_enabled_flag(919			origin: OriginFor<T>,920			collection_id: CollectionId,921			value: bool,922		) -> DispatchResult {923			if collection_id == pallet_common::NATIVE_FINGIBLE_COLLECTION_ID {924				fail!(<pallet_common::Error<T>>::UnsupportedOperation);925			}926			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);927			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;928			target_collection.check_is_internal()?;929			target_collection.check_is_owner(&sender)?;930931			// =========932933			target_collection.limits.transfers_enabled = Some(value);934			target_collection.save()935		}936937		/// Destroy an item.938		///939		/// # Permissions940		///941		/// * Collection owner942		/// * Collection admin943		/// * Current item owner944		///945		/// # Arguments946		///947		/// * `collection_id`: ID of the collection to which the item belongs.948		/// * `item_id`: ID of item to burn.949		/// * `value`: Number of pieces of the item to destroy.950		/// 	* Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.951		///     * Fungible Mode: The desired number of pieces to burn.952		///     * Re-Fungible Mode: The desired number of pieces to burn.953		#[pallet::call_index(20)]954		#[pallet::weight(T::CommonWeightInfo::burn_item())]955		pub fn burn_item(956			origin: OriginFor<T>,957			collection_id: CollectionId,958			item_id: TokenId,959			value: u128,960		) -> DispatchResultWithPostInfo {961			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);962963			let post_info =964				dispatch_tx::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;965			if value == 1 {966				<NftTransferBasket<T>>::remove(collection_id, item_id);967				<NftApproveBasket<T>>::remove(collection_id, item_id);968			}969			// Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?970			// <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());971			// <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));972			Ok(post_info)973		}974975		/// Destroy a token on behalf of the owner as a non-owner account.976		///977		/// See also: [`approve`][`Pallet::approve`].978		///979		/// After this method executes, one approval is removed from the total so that980		/// the approved address will not be able to transfer this item again from this owner.981		///982		/// # Permissions983		///984		/// * Collection owner985		/// * Collection admin986		/// * Current token owner987		/// * Address approved by current item owner988		///989		/// # Arguments990		///991		/// * `from`: The owner of the burning item.992		/// * `collection_id`: ID of the collection to which the item belongs.993		/// * `item_id`: ID of item to burn.994		/// * `value`: Number of pieces to burn.995		/// 	* Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.996		///     * Fungible Mode: The desired number of pieces to burn.997		///     * Re-Fungible Mode: The desired number of pieces to burn.998		#[pallet::call_index(21)]999		#[pallet::weight(T::CommonWeightInfo::burn_from())]1000		pub fn burn_from(1001			origin: OriginFor<T>,1002			collection_id: CollectionId,1003			from: T::CrossAccountId,1004			item_id: TokenId,1005			value: u128,1006		) -> DispatchResultWithPostInfo {1007			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1008			let budget = budget::Value::new(NESTING_BUDGET);10091010			dispatch_tx::<T, _>(collection_id, |d| {1011				d.burn_from(sender, from, item_id, value, &budget)1012			})1013		}10141015		/// Change ownership of the token.1016		///1017		/// # Permissions1018		///1019		/// * Collection owner1020		/// * Collection admin1021		/// * Current token owner1022		///1023		/// # Arguments1024		///1025		/// * `recipient`: Address of token recipient.1026		/// * `collection_id`: ID of the collection the item belongs to.1027		/// * `item_id`: ID of the item.1028		///     * Non-Fungible Mode: Required.1029		///     * Fungible Mode: Ignored.1030		///     * Re-Fungible Mode: Required.1031		///1032		/// * `value`: Amount to transfer.1033		/// 	* Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.1034		///     * Fungible Mode: The desired number of pieces to transfer.1035		///     * Re-Fungible Mode: The desired number of pieces to transfer.1036		#[pallet::call_index(22)]1037		#[pallet::weight(T::CommonWeightInfo::transfer())]1038		pub fn transfer(1039			origin: OriginFor<T>,1040			recipient: T::CrossAccountId,1041			collection_id: CollectionId,1042			item_id: TokenId,1043			value: u128,1044		) -> DispatchResultWithPostInfo {1045			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1046			let budget = budget::Value::new(NESTING_BUDGET);10471048			dispatch_tx::<T, _>(collection_id, |d| {1049				d.transfer(sender, recipient, item_id, value, &budget)1050			})1051		}10521053		/// Allow a non-permissioned address to transfer or burn an item.1054		///1055		/// # Permissions1056		///1057		/// * Collection owner1058		/// * Collection admin1059		/// * Current item owner1060		///1061		/// # Arguments1062		///1063		/// * `spender`: Account to be approved to make specific transactions on non-owned tokens.1064		/// * `collection_id`: ID of the collection the item belongs to.1065		/// * `item_id`: ID of the item transactions on which are now approved.1066		/// * `amount`: Number of pieces of the item approved for a transaction (maximum of 1 for NFTs).1067		/// Set to 0 to revoke the approval.1068		#[pallet::call_index(23)]1069		#[pallet::weight(T::CommonWeightInfo::approve())]1070		pub fn approve(1071			origin: OriginFor<T>,1072			spender: T::CrossAccountId,1073			collection_id: CollectionId,1074			item_id: TokenId,1075			amount: u128,1076		) -> DispatchResultWithPostInfo {1077			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);10781079			dispatch_tx::<T, _>(collection_id, |d| {1080				d.approve(sender, spender, item_id, amount)1081			})1082		}10831084		/// Allow a non-permissioned address to transfer or burn an item from owner's eth mirror.1085		///1086		/// # Permissions1087		///1088		/// * Collection owner1089		/// * Collection admin1090		/// * Current item owner1091		///1092		/// # Arguments1093		///1094		/// * `from`: Owner's account eth mirror1095		/// * `to`: Account to be approved to make specific transactions on non-owned tokens.1096		/// * `collection_id`: ID of the collection the item belongs to.1097		/// * `item_id`: ID of the item transactions on which are now approved.1098		/// * `amount`: Number of pieces of the item approved for a transaction (maximum of 1 for NFTs).1099		/// Set to 0 to revoke the approval.1100		#[pallet::call_index(24)]1101		#[pallet::weight(T::CommonWeightInfo::approve_from())]1102		pub fn approve_from(1103			origin: OriginFor<T>,1104			from: T::CrossAccountId,1105			to: T::CrossAccountId,1106			collection_id: CollectionId,1107			item_id: TokenId,1108			amount: u128,1109		) -> DispatchResultWithPostInfo {1110			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);11111112			dispatch_tx::<T, _>(collection_id, |d| {1113				d.approve_from(sender, from, to, item_id, amount)1114			})1115		}11161117		/// Change ownership of an item on behalf of the owner as a non-owner account.1118		///1119		/// See the [`approve`][`Pallet::approve`] method for additional information.1120		///1121		/// After this method executes, one approval is removed from the total so that1122		/// the approved address will not be able to transfer this item again from this owner.1123		///1124		/// # Permissions1125		///1126		/// * Collection owner1127		/// * Collection admin1128		/// * Current item owner1129		/// * Address approved by current item owner1130		///1131		/// # Arguments1132		///1133		/// * `from`: Address that currently owns the token.1134		/// * `recipient`: Address of the new token-owner-to-be.1135		/// * `collection_id`: ID of the collection the item.1136		/// * `item_id`: ID of the item to be transferred.1137		/// * `value`: Amount to transfer.1138		/// 	* Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.1139		///     * Fungible Mode: The desired number of pieces to transfer.1140		///     * Re-Fungible Mode: The desired number of pieces to transfer.1141		#[pallet::call_index(25)]1142		#[pallet::weight(T::CommonWeightInfo::transfer_from())]1143		pub fn transfer_from(1144			origin: OriginFor<T>,1145			from: T::CrossAccountId,1146			recipient: T::CrossAccountId,1147			collection_id: CollectionId,1148			item_id: TokenId,1149			value: u128,1150		) -> DispatchResultWithPostInfo {1151			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1152			let budget = budget::Value::new(NESTING_BUDGET);11531154			dispatch_tx::<T, _>(collection_id, |d| {1155				d.transfer_from(sender, from, recipient, item_id, value, &budget)1156			})1157		}11581159		/// Set specific limits of a collection. Empty, or None fields mean chain default.1160		///1161		/// # Permissions1162		///1163		/// * Collection owner1164		/// * Collection admin1165		///1166		/// # Arguments1167		///1168		/// * `collection_id`: ID of the modified collection.1169		/// * `new_limit`: New limits of the collection. Fields that are not set (None)1170		/// will not overwrite the old ones.1171		#[pallet::call_index(26)]1172		#[pallet::weight(<SelfWeightOf<T>>::set_collection_limits())]1173		pub fn set_collection_limits(1174			origin: OriginFor<T>,1175			collection_id: CollectionId,1176			new_limit: CollectionLimits,1177		) -> DispatchResult {1178			if collection_id == pallet_common::NATIVE_FINGIBLE_COLLECTION_ID {1179				fail!(<pallet_common::Error<T>>::UnsupportedOperation);1180			}1181			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1182			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1183			<PalletCommon<T>>::update_limits(&sender, &mut target_collection, new_limit)1184		}11851186		/// Set specific permissions of a collection. Empty, or None fields mean chain default.1187		///1188		/// # Permissions1189		///1190		/// * Collection owner1191		/// * Collection admin1192		///1193		/// # Arguments1194		///1195		/// * `collection_id`: ID of the modified collection.1196		/// * `new_permission`: New permissions of the collection. Fields that are not set (None)1197		/// will not overwrite the old ones.1198		#[pallet::call_index(27)]1199		#[pallet::weight(<SelfWeightOf<T>>::set_collection_limits())]1200		pub fn set_collection_permissions(1201			origin: OriginFor<T>,1202			collection_id: CollectionId,1203			new_permission: CollectionPermissions,1204		) -> DispatchResult {1205			if collection_id == pallet_common::NATIVE_FINGIBLE_COLLECTION_ID {1206				fail!(<pallet_common::Error<T>>::UnsupportedOperation);1207			}1208			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1209			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1210			<PalletCommon<T>>::update_permissions(&sender, &mut target_collection, new_permission)1211		}12121213		/// Re-partition a refungible token, while owning all of its parts/pieces.1214		///1215		/// # Permissions1216		///1217		/// * Token owner (must own every part)1218		///1219		/// # Arguments1220		///1221		/// * `collection_id`: ID of the collection the RFT belongs to.1222		/// * `token_id`: ID of the RFT.1223		/// * `amount`: New number of parts/pieces into which the token shall be partitioned.1224		#[pallet::call_index(28)]1225		#[pallet::weight(T::RefungibleExtensionsWeightInfo::repartition())]1226		pub fn repartition(1227			origin: OriginFor<T>,1228			collection_id: CollectionId,1229			token_id: TokenId,1230			amount: u128,1231		) -> DispatchResultWithPostInfo {1232			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1233			dispatch_tx::<T, _>(collection_id, |d| {1234				if let Some(refungible_extensions) = d.refungible_extensions() {1235					refungible_extensions.repartition(&sender, token_id, amount)1236				} else {1237					fail!(<Error<T>>::RepartitionCalledOnNonRefungibleCollection)1238				}1239			})1240		}12411242		/// Sets or unsets the approval of a given operator.1243		///1244		/// The `operator` is allowed to transfer all tokens of the `owner` on their behalf.1245		///1246		/// # Arguments1247		///1248		/// * `owner`: Token owner1249		/// * `operator`: Operator1250		/// * `approve`: Should operator status be granted or revoked?1251		#[pallet::call_index(29)]1252		#[pallet::weight(T::CommonWeightInfo::set_allowance_for_all())]1253		pub fn set_allowance_for_all(1254			origin: OriginFor<T>,1255			collection_id: CollectionId,1256			operator: T::CrossAccountId,1257			approve: bool,1258		) -> DispatchResultWithPostInfo {1259			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1260			dispatch_tx::<T, _>(collection_id, |d| {1261				d.set_allowance_for_all(sender, operator, approve)1262			})1263		}12641265		/// Repairs a collection if the data was somehow corrupted.1266		///1267		/// # Arguments1268		///1269		/// * `collection_id`: ID of the collection to repair.1270		#[pallet::call_index(30)]1271		#[pallet::weight(<SelfWeightOf<T>>::force_repair_collection())]1272		pub fn force_repair_collection(1273			origin: OriginFor<T>,1274			collection_id: CollectionId,1275		) -> DispatchResult {1276			if collection_id == pallet_common::NATIVE_FINGIBLE_COLLECTION_ID {1277				fail!(<pallet_common::Error<T>>::UnsupportedOperation);1278			}1279			ensure_root(origin)?;1280			<PalletCommon<T>>::repair_collection(collection_id)1281		}12821283		/// Repairs a token if the data was somehow corrupted.1284		///1285		/// # Arguments1286		///1287		/// * `collection_id`: ID of the collection the item belongs to.1288		/// * `item_id`: ID of the item.1289		#[pallet::call_index(31)]1290		#[pallet::weight(T::CommonWeightInfo::force_repair_item())]1291		pub fn force_repair_item(1292			origin: OriginFor<T>,1293			collection_id: CollectionId,1294			item_id: TokenId,1295		) -> DispatchResultWithPostInfo {1296			ensure_root(origin)?;1297			dispatch_tx::<T, _>(collection_id, |d| d.repair_item(item_id))1298		}1299	}13001301	impl<T: Config> Pallet<T> {1302		/// Force set `sponsor` for `collection`.1303		///1304		/// Differs from [`set_collection_sponsor`][`Pallet::set_collection_sponsor`] in that confirmation1305		/// from the `sponsor` is not required.1306		///1307		/// # Arguments1308		///1309		/// * `sponsor`: ID of the account of the sponsor-to-be.1310		/// * `collection_id`: ID of the modified collection.1311		pub fn force_set_sponsor(1312			sponsor: T::AccountId,1313			collection_id: CollectionId,1314		) -> DispatchResult {1315			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1316			target_collection.force_set_sponsor(sponsor.clone())1317		}13181319		/// Force remove `sponsor` for `collection`.1320		///1321		/// Differs from `remove_sponsor` in that1322		/// it doesn't require consent from the `owner` of the collection.1323		///1324		/// # Arguments1325		///1326		/// * `collection_id`: ID of the modified collection.1327		pub fn force_remove_collection_sponsor(collection_id: CollectionId) -> DispatchResult {1328			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1329			target_collection.force_remove_sponsor()1330		}13311332		#[inline(always)]1333		pub(crate) fn destroy_collection_internal(1334			sender: T::CrossAccountId,1335			collection_id: CollectionId,1336		) -> DispatchResult {1337			T::CollectionDispatch::destroy(sender, collection_id)?;13381339			// TODO: basket cleanup should be moved elsewhere1340			// Maybe runtime dispatch.rs should perform it?13411342			let _ = <NftTransferBasket<T>>::clear_prefix(collection_id, u32::MAX, None);1343			let _ = <FungibleTransferBasket<T>>::clear_prefix(collection_id, u32::MAX, None);1344			let _ = <ReFungibleTransferBasket<T>>::clear_prefix((collection_id,), u32::MAX, None);13451346			let _ = <NftApproveBasket<T>>::clear_prefix(collection_id, u32::MAX, None);1347			let _ = <FungibleApproveBasket<T>>::clear_prefix(collection_id, u32::MAX, None);1348			let _ = <RefungibleApproveBasket<T>>::clear_prefix((collection_id,), u32::MAX, None);13491350			Ok(())1351		}1352	}1353}