git.delta.rocks / unique-network / refs/commits / d8b2d6d11657

difftreelog

source

pallets/unique/src/lib.rs44.0 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			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);444			let collection = <CollectionHandle<T>>::try_get(collection_id)?;445			collection.check_is_internal()?;446447			<PalletCommon<T>>::toggle_allowlist(&collection, &sender, &address, true)?;448449			Ok(())450		}451452		/// Remove an address from allow list.453		///454		/// # Permissions455		///456		/// * Collection owner457		/// * Collection admin458		///459		/// # Arguments460		///461		/// * `collection_id`: ID of the modified collection.462		/// * `address`: ID of the address to be removed from the allowlist.463		#[pallet::call_index(4)]464		#[pallet::weight(<SelfWeightOf<T>>::remove_from_allow_list())]465		pub fn remove_from_allow_list(466			origin: OriginFor<T>,467			collection_id: CollectionId,468			address: T::CrossAccountId,469		) -> DispatchResult {470			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);471			let collection = <CollectionHandle<T>>::try_get(collection_id)?;472			collection.check_is_internal()?;473474			<PalletCommon<T>>::toggle_allowlist(&collection, &sender, &address, false)?;475476			Ok(())477		}478479		/// Change the owner of the collection.480		///481		/// # Permissions482		///483		/// * Collection owner484		///485		/// # Arguments486		///487		/// * `collection_id`: ID of the modified collection.488		/// * `new_owner`: ID of the account that will become the owner.489		#[pallet::call_index(5)]490		#[pallet::weight(<SelfWeightOf<T>>::change_collection_owner())]491		pub fn change_collection_owner(492			origin: OriginFor<T>,493			collection_id: CollectionId,494			new_owner: T::AccountId,495		) -> DispatchResult {496			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);497			let new_owner = T::CrossAccountId::from_sub(new_owner);498			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;499			target_collection.change_owner(sender, new_owner.clone())500		}501502		/// Add an admin to a collection.503		///504		/// NFT Collection can be controlled by multiple admin addresses505		/// (some which can also be servers, for example). Admins can issue506		/// and burn NFTs, as well as add and remove other admins,507		/// but cannot change NFT or Collection ownership.508		///509		/// # Permissions510		///511		/// * Collection owner512		/// * Collection admin513		///514		/// # Arguments515		///516		/// * `collection_id`: ID of the Collection to add an admin for.517		/// * `new_admin`: Address of new admin to add.518		#[pallet::call_index(6)]519		#[pallet::weight(<SelfWeightOf<T>>::add_collection_admin())]520		pub fn add_collection_admin(521			origin: OriginFor<T>,522			collection_id: CollectionId,523			new_admin_id: T::CrossAccountId,524		) -> DispatchResult {525			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);526			let collection = <CollectionHandle<T>>::try_get(collection_id)?;527			<PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)528		}529530		/// Remove admin of a collection.531		///532		/// An admin address can remove itself. List of admins may become empty,533		/// in which case only Collection Owner will be able to add an Admin.534		///535		/// # Permissions536		///537		/// * Collection owner538		/// * Collection admin539		///540		/// # Arguments541		///542		/// * `collection_id`: ID of the collection to remove the admin for.543		/// * `account_id`: Address of the admin to remove.544		#[pallet::call_index(7)]545		#[pallet::weight(<SelfWeightOf<T>>::remove_collection_admin())]546		pub fn remove_collection_admin(547			origin: OriginFor<T>,548			collection_id: CollectionId,549			account_id: T::CrossAccountId,550		) -> DispatchResult {551			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);552			let collection = <CollectionHandle<T>>::try_get(collection_id)?;553			<PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)554		}555556		/// Set (invite) a new collection sponsor.557		///558		/// If successful, confirmation from the sponsor-to-be will be pending.559		///560		/// # Permissions561		///562		/// * Collection owner563		/// * Collection admin564		///565		/// # Arguments566		///567		/// * `collection_id`: ID of the modified collection.568		/// * `new_sponsor`: ID of the account of the sponsor-to-be.569		#[pallet::call_index(8)]570		#[pallet::weight(<SelfWeightOf<T>>::set_collection_sponsor())]571		pub fn set_collection_sponsor(572			origin: OriginFor<T>,573			collection_id: CollectionId,574			new_sponsor: T::AccountId,575		) -> DispatchResult {576			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);577			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;578			target_collection.set_sponsor(&sender, new_sponsor.clone())579		}580581		/// Confirm own sponsorship of a collection, becoming the sponsor.582		///583		/// An invitation must be pending, see [`set_collection_sponsor`][`Pallet::set_collection_sponsor`].584		/// Sponsor can pay the fees of a transaction instead of the sender,585		/// but only within specified limits.586		///587		/// # Permissions588		///589		/// * Sponsor-to-be590		///591		/// # Arguments592		///593		/// * `collection_id`: ID of the collection with the pending sponsor.594		#[pallet::call_index(9)]595		#[pallet::weight(<SelfWeightOf<T>>::confirm_sponsorship())]596		pub fn confirm_sponsorship(597			origin: OriginFor<T>,598			collection_id: CollectionId,599		) -> DispatchResult {600			let sender = ensure_signed(origin)?;601			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;602			target_collection.confirm_sponsorship(&sender)603		}604605		/// Remove a collection's a sponsor, making everyone pay for their own transactions.606		///607		/// # Permissions608		///609		/// * Collection owner610		///611		/// # Arguments612		///613		/// * `collection_id`: ID of the collection with the sponsor to remove.614		#[pallet::call_index(10)]615		#[pallet::weight(<SelfWeightOf<T>>::remove_collection_sponsor())]616		pub fn remove_collection_sponsor(617			origin: OriginFor<T>,618			collection_id: CollectionId,619		) -> DispatchResult {620			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);621			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;622			target_collection.remove_sponsor(&sender)623		}624625		/// Mint an item within a collection.626		///627		/// A collection must exist first, see [`create_collection_ex`][`Pallet::create_collection_ex`].628		///629		/// # Permissions630		///631		/// * Collection owner632		/// * Collection admin633		/// * Anyone if634		///     * Allow List is enabled, and635		///     * Address is added to allow list, and636		///     * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])637		///638		/// # Arguments639		///640		/// * `collection_id`: ID of the collection to which an item would belong.641		/// * `owner`: Address of the initial owner of the item.642		/// * `data`: Token data describing the item to store on chain.643		#[pallet::call_index(11)]644		#[pallet::weight(T::CommonWeightInfo::create_item(&data))]645		pub fn create_item(646			origin: OriginFor<T>,647			collection_id: CollectionId,648			owner: T::CrossAccountId,649			data: CreateItemData,650		) -> DispatchResultWithPostInfo {651			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);652			let budget = budget::Value::new(NESTING_BUDGET);653654			dispatch_tx::<T, _>(collection_id, |d| {655				d.create_item(sender, owner, data, &budget)656			})657		}658659		/// Create multiple items within a collection.660		///661		/// A collection must exist first, see [`create_collection_ex`][`Pallet::create_collection_ex`].662		///663		/// # Permissions664		///665		/// * Collection owner666		/// * Collection admin667		/// * Anyone if668		///     * Allow List is enabled, and669		///     * Address is added to the allow list, and670		///     * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])671		///672		/// # Arguments673		///674		/// * `collection_id`: ID of the collection to which the tokens would belong.675		/// * `owner`: Address of the initial owner of the tokens.676		/// * `items_data`: Vector of data describing each item to be created.677		#[pallet::call_index(12)]678		#[pallet::weight(T::CommonWeightInfo::create_multiple_items(&items_data))]679		pub fn create_multiple_items(680			origin: OriginFor<T>,681			collection_id: CollectionId,682			owner: T::CrossAccountId,683			items_data: Vec<CreateItemData>,684		) -> DispatchResultWithPostInfo {685			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);686			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);687			let budget = budget::Value::new(NESTING_BUDGET);688689			dispatch_tx::<T, _>(collection_id, |d| {690				d.create_multiple_items(sender, owner, items_data, &budget)691			})692		}693694		/// Add or change collection properties.695		///696		/// # Permissions697		///698		/// * Collection owner699		/// * Collection admin700		///701		/// # Arguments702		///703		/// * `collection_id`: ID of the modified collection.704		/// * `properties`: Vector of key-value pairs stored as the collection's metadata.705		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.706		#[pallet::call_index(13)]707		#[pallet::weight(T::CommonWeightInfo::set_collection_properties(properties.len() as u32))]708		pub fn set_collection_properties(709			origin: OriginFor<T>,710			collection_id: CollectionId,711			properties: Vec<Property>,712		) -> DispatchResultWithPostInfo {713			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);714715			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);716717			dispatch_tx::<T, _>(collection_id, |d| {718				d.set_collection_properties(sender, properties)719			})720		}721722		/// Delete specified collection properties.723		///724		/// # Permissions725		///726		/// * Collection Owner727		/// * Collection Admin728		///729		/// # Arguments730		///731		/// * `collection_id`: ID of the modified collection.732		/// * `property_keys`: Vector of keys of the properties to be deleted.733		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.734		#[pallet::call_index(14)]735		#[pallet::weight(T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32))]736		pub fn delete_collection_properties(737			origin: OriginFor<T>,738			collection_id: CollectionId,739			property_keys: Vec<PropertyKey>,740		) -> DispatchResultWithPostInfo {741			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);742743			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);744745			dispatch_tx::<T, _>(collection_id, |d| {746				d.delete_collection_properties(&sender, property_keys)747			})748		}749750		/// Add or change token properties according to collection's permissions.751		/// Currently properties only work with NFTs.752		///753		/// # Permissions754		///755		/// * Depends on collection's token property permissions and specified property mutability:756		/// 	* Collection owner757		/// 	* Collection admin758		/// 	* Token owner759		///760		/// See [`set_token_property_permissions`][`Pallet::set_token_property_permissions`].761		///762		/// # Arguments763		///764		/// * `collection_id: ID of the collection to which the token belongs.765		/// * `token_id`: ID of the modified token.766		/// * `properties`: Vector of key-value pairs stored as the token's metadata.767		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.768		#[pallet::call_index(15)]769		#[pallet::weight(T::CommonWeightInfo::set_token_properties(properties.len() as u32))]770		pub fn set_token_properties(771			origin: OriginFor<T>,772			collection_id: CollectionId,773			token_id: TokenId,774			properties: Vec<Property>,775		) -> DispatchResultWithPostInfo {776			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);777778			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);779			let budget = budget::Value::new(NESTING_BUDGET);780781			dispatch_tx::<T, _>(collection_id, |d| {782				d.set_token_properties(sender, token_id, properties, &budget)783			})784		}785786		/// Delete specified token properties. Currently properties only work with NFTs.787		///788		/// # Permissions789		///790		/// * Depends on collection's token property permissions and specified property mutability:791		/// 	* Collection owner792		/// 	* Collection admin793		/// 	* Token owner794		///795		/// # Arguments796		///797		/// * `collection_id`: ID of the collection to which the token belongs.798		/// * `token_id`: ID of the modified token.799		/// * `property_keys`: Vector of keys of the properties to be deleted.800		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.801		#[pallet::call_index(16)]802		#[pallet::weight(T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32))]803		pub fn delete_token_properties(804			origin: OriginFor<T>,805			collection_id: CollectionId,806			token_id: TokenId,807			property_keys: Vec<PropertyKey>,808		) -> DispatchResultWithPostInfo {809			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);810811			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);812			let budget = budget::Value::new(NESTING_BUDGET);813814			dispatch_tx::<T, _>(collection_id, |d| {815				d.delete_token_properties(sender, token_id, property_keys, &budget)816			})817		}818819		/// Add or change token property permissions of a collection.820		///821		/// Without a permission for a particular key, a property with that key822		/// cannot be created in a token.823		///824		/// # Permissions825		///826		/// * Collection owner827		/// * Collection admin828		///829		/// # Arguments830		///831		/// * `collection_id`: ID of the modified collection.832		/// * `property_permissions`: Vector of permissions for property keys.833		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.834		#[pallet::call_index(17)]835		#[pallet::weight(T::CommonWeightInfo::set_token_property_permissions(property_permissions.len() as u32))]836		pub fn set_token_property_permissions(837			origin: OriginFor<T>,838			collection_id: CollectionId,839			property_permissions: Vec<PropertyKeyPermission>,840		) -> DispatchResultWithPostInfo {841			ensure!(!property_permissions.is_empty(), Error::<T>::EmptyArgument);842843			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);844845			dispatch_tx::<T, _>(collection_id, |d| {846				d.set_token_property_permissions(&sender, property_permissions)847			})848		}849850		/// Create multiple items within a collection with explicitly specified initial parameters.851		///852		/// # Permissions853		///854		/// * Collection owner855		/// * Collection admin856		/// * Anyone if857		///     * Allow List is enabled, and858		///     * Address is added to allow list, and859		///     * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])860		///861		/// # Arguments862		///863		/// * `collection_id`: ID of the collection to which the tokens would belong.864		/// * `data`: Explicit item creation data.865		#[pallet::call_index(18)]866		#[pallet::weight(T::CommonWeightInfo::create_multiple_items_ex(&data))]867		pub fn create_multiple_items_ex(868			origin: OriginFor<T>,869			collection_id: CollectionId,870			data: CreateItemExData<T::CrossAccountId>,871		) -> DispatchResultWithPostInfo {872			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);873			let budget = budget::Value::new(NESTING_BUDGET);874875			dispatch_tx::<T, _>(collection_id, |d| {876				d.create_multiple_items_ex(sender, data, &budget)877			})878		}879880		/// Completely allow or disallow transfers for a particular collection.881		///882		/// # Permissions883		///884		/// * Collection owner885		///886		/// # Arguments887		///888		/// * `collection_id`: ID of the collection.889		/// * `value`: New value of the flag, are transfers allowed?890		#[pallet::call_index(19)]891		#[pallet::weight(<SelfWeightOf<T>>::set_transfers_enabled_flag())]892		pub fn set_transfers_enabled_flag(893			origin: OriginFor<T>,894			collection_id: CollectionId,895			value: bool,896		) -> DispatchResult {897			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);898			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;899			target_collection.check_is_internal()?;900			target_collection.check_is_owner(&sender)?;901902			// =========903904			target_collection.limits.transfers_enabled = Some(value);905			target_collection.save()906		}907908		/// Destroy an item.909		///910		/// # Permissions911		///912		/// * Collection owner913		/// * Collection admin914		/// * Current item owner915		///916		/// # Arguments917		///918		/// * `collection_id`: ID of the collection to which the item belongs.919		/// * `item_id`: ID of item to burn.920		/// * `value`: Number of pieces of the item to destroy.921		/// 	* Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.922		///     * Fungible Mode: The desired number of pieces to burn.923		///     * Re-Fungible Mode: The desired number of pieces to burn.924		#[pallet::call_index(20)]925		#[pallet::weight(T::CommonWeightInfo::burn_item())]926		pub fn burn_item(927			origin: OriginFor<T>,928			collection_id: CollectionId,929			item_id: TokenId,930			value: u128,931		) -> DispatchResultWithPostInfo {932			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);933934			let post_info =935				dispatch_tx::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;936			if value == 1 {937				<NftTransferBasket<T>>::remove(collection_id, item_id);938				<NftApproveBasket<T>>::remove(collection_id, item_id);939			}940			// Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?941			// <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());942			// <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));943			Ok(post_info)944		}945946		/// Destroy a token on behalf of the owner as a non-owner account.947		///948		/// See also: [`approve`][`Pallet::approve`].949		///950		/// After this method executes, one approval is removed from the total so that951		/// the approved address will not be able to transfer this item again from this owner.952		///953		/// # Permissions954		///955		/// * Collection owner956		/// * Collection admin957		/// * Current token owner958		/// * Address approved by current item owner959		///960		/// # Arguments961		///962		/// * `from`: The owner of the burning item.963		/// * `collection_id`: ID of the collection to which the item belongs.964		/// * `item_id`: ID of item to burn.965		/// * `value`: Number of pieces to burn.966		/// 	* Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.967		///     * Fungible Mode: The desired number of pieces to burn.968		///     * Re-Fungible Mode: The desired number of pieces to burn.969		#[pallet::call_index(21)]970		#[pallet::weight(T::CommonWeightInfo::burn_from())]971		pub fn burn_from(972			origin: OriginFor<T>,973			collection_id: CollectionId,974			from: T::CrossAccountId,975			item_id: TokenId,976			value: u128,977		) -> DispatchResultWithPostInfo {978			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);979			let budget = budget::Value::new(NESTING_BUDGET);980981			dispatch_tx::<T, _>(collection_id, |d| {982				d.burn_from(sender, from, item_id, value, &budget)983			})984		}985986		/// Change ownership of the token.987		///988		/// # Permissions989		///990		/// * Collection owner991		/// * Collection admin992		/// * Current token owner993		///994		/// # Arguments995		///996		/// * `recipient`: Address of token recipient.997		/// * `collection_id`: ID of the collection the item belongs to.998		/// * `item_id`: ID of the item.999		///     * Non-Fungible Mode: Required.1000		///     * Fungible Mode: Ignored.1001		///     * Re-Fungible Mode: Required.1002		///1003		/// * `value`: Amount to transfer.1004		/// 	* Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.1005		///     * Fungible Mode: The desired number of pieces to transfer.1006		///     * Re-Fungible Mode: The desired number of pieces to transfer.1007		#[pallet::call_index(22)]1008		#[pallet::weight(T::CommonWeightInfo::transfer())]1009		pub fn transfer(1010			origin: OriginFor<T>,1011			recipient: T::CrossAccountId,1012			collection_id: CollectionId,1013			item_id: TokenId,1014			value: u128,1015		) -> DispatchResultWithPostInfo {1016			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1017			let budget = budget::Value::new(NESTING_BUDGET);10181019			dispatch_tx::<T, _>(collection_id, |d| {1020				d.transfer(sender, recipient, item_id, value, &budget)1021			})1022		}10231024		/// Allow a non-permissioned address to transfer or burn an item.1025		///1026		/// # Permissions1027		///1028		/// * Collection owner1029		/// * Collection admin1030		/// * Current item owner1031		///1032		/// # Arguments1033		///1034		/// * `spender`: Account to be approved to make specific transactions on non-owned tokens.1035		/// * `collection_id`: ID of the collection the item belongs to.1036		/// * `item_id`: ID of the item transactions on which are now approved.1037		/// * `amount`: Number of pieces of the item approved for a transaction (maximum of 1 for NFTs).1038		/// Set to 0 to revoke the approval.1039		#[pallet::call_index(23)]1040		#[pallet::weight(T::CommonWeightInfo::approve())]1041		pub fn approve(1042			origin: OriginFor<T>,1043			spender: T::CrossAccountId,1044			collection_id: CollectionId,1045			item_id: TokenId,1046			amount: u128,1047		) -> DispatchResultWithPostInfo {1048			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);10491050			dispatch_tx::<T, _>(collection_id, |d| {1051				d.approve(sender, spender, item_id, amount)1052			})1053		}10541055		/// Allow a non-permissioned address to transfer or burn an item from owner's eth mirror.1056		///1057		/// # Permissions1058		///1059		/// * Collection owner1060		/// * Collection admin1061		/// * Current item owner1062		///1063		/// # Arguments1064		///1065		/// * `from`: Owner's account eth mirror1066		/// * `to`: Account to be approved to make specific transactions on non-owned tokens.1067		/// * `collection_id`: ID of the collection the item belongs to.1068		/// * `item_id`: ID of the item transactions on which are now approved.1069		/// * `amount`: Number of pieces of the item approved for a transaction (maximum of 1 for NFTs).1070		/// Set to 0 to revoke the approval.1071		#[pallet::call_index(24)]1072		#[pallet::weight(T::CommonWeightInfo::approve_from())]1073		pub fn approve_from(1074			origin: OriginFor<T>,1075			from: T::CrossAccountId,1076			to: T::CrossAccountId,1077			collection_id: CollectionId,1078			item_id: TokenId,1079			amount: u128,1080		) -> DispatchResultWithPostInfo {1081			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);10821083			dispatch_tx::<T, _>(collection_id, |d| {1084				d.approve_from(sender, from, to, item_id, amount)1085			})1086		}10871088		/// Change ownership of an item on behalf of the owner as a non-owner account.1089		///1090		/// See the [`approve`][`Pallet::approve`] method for additional information.1091		///1092		/// After this method executes, one approval is removed from the total so that1093		/// the approved address will not be able to transfer this item again from this owner.1094		///1095		/// # Permissions1096		///1097		/// * Collection owner1098		/// * Collection admin1099		/// * Current item owner1100		/// * Address approved by current item owner1101		///1102		/// # Arguments1103		///1104		/// * `from`: Address that currently owns the token.1105		/// * `recipient`: Address of the new token-owner-to-be.1106		/// * `collection_id`: ID of the collection the item.1107		/// * `item_id`: ID of the item to be transferred.1108		/// * `value`: Amount to transfer.1109		/// 	* Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.1110		///     * Fungible Mode: The desired number of pieces to transfer.1111		///     * Re-Fungible Mode: The desired number of pieces to transfer.1112		#[pallet::call_index(25)]1113		#[pallet::weight(T::CommonWeightInfo::transfer_from())]1114		pub fn transfer_from(1115			origin: OriginFor<T>,1116			from: T::CrossAccountId,1117			recipient: T::CrossAccountId,1118			collection_id: CollectionId,1119			item_id: TokenId,1120			value: u128,1121		) -> DispatchResultWithPostInfo {1122			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1123			let budget = budget::Value::new(NESTING_BUDGET);11241125			dispatch_tx::<T, _>(collection_id, |d| {1126				d.transfer_from(sender, from, recipient, item_id, value, &budget)1127			})1128		}11291130		/// Set specific limits of a collection. Empty, or None fields mean chain default.1131		///1132		/// # Permissions1133		///1134		/// * Collection owner1135		/// * Collection admin1136		///1137		/// # Arguments1138		///1139		/// * `collection_id`: ID of the modified collection.1140		/// * `new_limit`: New limits of the collection. Fields that are not set (None)1141		/// will not overwrite the old ones.1142		#[pallet::call_index(26)]1143		#[pallet::weight(<SelfWeightOf<T>>::set_collection_limits())]1144		pub fn set_collection_limits(1145			origin: OriginFor<T>,1146			collection_id: CollectionId,1147			new_limit: CollectionLimits,1148		) -> DispatchResult {1149			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1150			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1151			<PalletCommon<T>>::update_limits(&sender, &mut target_collection, new_limit)1152		}11531154		/// Set specific permissions of a collection. Empty, or None fields mean chain default.1155		///1156		/// # Permissions1157		///1158		/// * Collection owner1159		/// * Collection admin1160		///1161		/// # Arguments1162		///1163		/// * `collection_id`: ID of the modified collection.1164		/// * `new_permission`: New permissions of the collection. Fields that are not set (None)1165		/// will not overwrite the old ones.1166		#[pallet::call_index(27)]1167		#[pallet::weight(<SelfWeightOf<T>>::set_collection_limits())]1168		pub fn set_collection_permissions(1169			origin: OriginFor<T>,1170			collection_id: CollectionId,1171			new_permission: CollectionPermissions,1172		) -> DispatchResult {1173			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1174			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1175			<PalletCommon<T>>::update_permissions(&sender, &mut target_collection, new_permission)1176		}11771178		/// Re-partition a refungible token, while owning all of its parts/pieces.1179		///1180		/// # Permissions1181		///1182		/// * Token owner (must own every part)1183		///1184		/// # Arguments1185		///1186		/// * `collection_id`: ID of the collection the RFT belongs to.1187		/// * `token_id`: ID of the RFT.1188		/// * `amount`: New number of parts/pieces into which the token shall be partitioned.1189		#[pallet::call_index(28)]1190		#[pallet::weight(T::RefungibleExtensionsWeightInfo::repartition())]1191		pub fn repartition(1192			origin: OriginFor<T>,1193			collection_id: CollectionId,1194			token_id: TokenId,1195			amount: u128,1196		) -> DispatchResultWithPostInfo {1197			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1198			dispatch_tx::<T, _>(collection_id, |d| {1199				if let Some(refungible_extensions) = d.refungible_extensions() {1200					refungible_extensions.repartition(&sender, token_id, amount)1201				} else {1202					fail!(<Error<T>>::RepartitionCalledOnNonRefungibleCollection)1203				}1204			})1205		}12061207		/// Sets or unsets the approval of a given operator.1208		///1209		/// The `operator` is allowed to transfer all tokens of the `owner` on their behalf.1210		///1211		/// # Arguments1212		///1213		/// * `owner`: Token owner1214		/// * `operator`: Operator1215		/// * `approve`: Should operator status be granted or revoked?1216		#[pallet::call_index(29)]1217		#[pallet::weight(T::CommonWeightInfo::set_allowance_for_all())]1218		pub fn set_allowance_for_all(1219			origin: OriginFor<T>,1220			collection_id: CollectionId,1221			operator: T::CrossAccountId,1222			approve: bool,1223		) -> DispatchResultWithPostInfo {1224			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1225			dispatch_tx::<T, _>(collection_id, |d| {1226				d.set_allowance_for_all(sender, operator, approve)1227			})1228		}12291230		/// Repairs a collection if the data was somehow corrupted.1231		///1232		/// # Arguments1233		///1234		/// * `collection_id`: ID of the collection to repair.1235		#[pallet::call_index(30)]1236		#[pallet::weight(<SelfWeightOf<T>>::force_repair_collection())]1237		pub fn force_repair_collection(1238			origin: OriginFor<T>,1239			collection_id: CollectionId,1240		) -> DispatchResult {1241			ensure_root(origin)?;1242			<PalletCommon<T>>::repair_collection(collection_id)1243		}12441245		/// Repairs a token if the data was somehow corrupted.1246		///1247		/// # Arguments1248		///1249		/// * `collection_id`: ID of the collection the item belongs to.1250		/// * `item_id`: ID of the item.1251		#[pallet::call_index(31)]1252		#[pallet::weight(T::CommonWeightInfo::force_repair_item())]1253		pub fn force_repair_item(1254			origin: OriginFor<T>,1255			collection_id: CollectionId,1256			item_id: TokenId,1257		) -> DispatchResultWithPostInfo {1258			ensure_root(origin)?;1259			dispatch_tx::<T, _>(collection_id, |d| d.repair_item(item_id))1260		}1261	}12621263	impl<T: Config> Pallet<T> {1264		/// Force set `sponsor` for `collection`.1265		///1266		/// Differs from [`set_collection_sponsor`][`Pallet::set_collection_sponsor`] in that confirmation1267		/// from the `sponsor` is not required.1268		///1269		/// # Arguments1270		///1271		/// * `sponsor`: ID of the account of the sponsor-to-be.1272		/// * `collection_id`: ID of the modified collection.1273		pub fn force_set_sponsor(1274			sponsor: T::AccountId,1275			collection_id: CollectionId,1276		) -> DispatchResult {1277			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1278			target_collection.force_set_sponsor(sponsor.clone())1279		}12801281		/// Force remove `sponsor` for `collection`.1282		///1283		/// Differs from `remove_sponsor` in that1284		/// it doesn't require consent from the `owner` of the collection.1285		///1286		/// # Arguments1287		///1288		/// * `collection_id`: ID of the modified collection.1289		pub fn force_remove_collection_sponsor(collection_id: CollectionId) -> DispatchResult {1290			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1291			target_collection.force_remove_sponsor()1292		}12931294		#[inline(always)]1295		pub(crate) fn destroy_collection_internal(1296			sender: T::CrossAccountId,1297			collection_id: CollectionId,1298		) -> DispatchResult {1299			let collection = <CollectionHandle<T>>::try_get(collection_id)?;1300			collection.check_is_internal()?;13011302			T::CollectionDispatch::destroy(sender, collection)?;13031304			// TODO: basket cleanup should be moved elsewhere1305			// Maybe runtime dispatch.rs should perform it?13061307			let _ = <NftTransferBasket<T>>::clear_prefix(collection_id, u32::MAX, None);1308			let _ = <FungibleTransferBasket<T>>::clear_prefix(collection_id, u32::MAX, None);1309			let _ = <ReFungibleTransferBasket<T>>::clear_prefix((collection_id,), u32::MAX, None);13101311			let _ = <NftApproveBasket<T>>::clear_prefix(collection_id, u32::MAX, None);1312			let _ = <FungibleApproveBasket<T>>::clear_prefix(collection_id, u32::MAX, None);1313			let _ = <RefungibleApproveBasket<T>>::clear_prefix((collection_id,), u32::MAX, None);13141315			Ok(())1316		}1317	}1318}