git.delta.rocks / unique-network / refs/commits / 9b4da675e61d

difftreelog

source

pallets/unique/src/lib.rs33.9 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#![recursion_limit = "1024"]18#![cfg_attr(not(feature = "std"), no_std)]19#![allow(20	clippy::too_many_arguments,21	clippy::unnecessary_mut_passed,22	clippy::unused_unit23)]2425extern crate alloc;2627use frame_support::{28	decl_module, decl_storage, decl_error, decl_event,29	dispatch::DispatchResult,30	ensure, fail,31	weights::{Weight},32	transactional,33	pallet_prelude::{DispatchResultWithPostInfo, ConstU32},34	BoundedVec,35};36use scale_info::TypeInfo;37use frame_system::{self as system, ensure_signed};38use sp_runtime::{sp_std::prelude::Vec};39use up_data_structs::{40	MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH,41	CreateItemData, CollectionLimits, CollectionPermissions, CollectionId, CollectionMode, TokenId,42	SponsorshipState, CreateCollectionData, CreateItemExData, budget, Property, PropertyKey,43	PropertyKeyPermission,44};45use pallet_evm::account::CrossAccountId;46use pallet_common::{47	CollectionHandle, Pallet as PalletCommon, CommonWeightInfo, dispatch::dispatch_tx,48	dispatch::CollectionDispatch, RefungibleExtensionsWeightInfo,49};50pub mod eth;5152#[cfg(feature = "runtime-benchmarks")]53mod benchmarking;54pub mod weights;55use weights::WeightInfo;5657const NESTING_BUDGET: u32 = 5;5859decl_error! {60	/// Error for non-fungible-token module.61	pub enum Error for Module<T: Config> {62		/// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.63		CollectionDecimalPointLimitExceeded,64		/// This address is not set as sponsor, use setCollectionSponsor first.65		ConfirmUnsetSponsorFail,66		/// Length of items properties must be greater than 0.67		EmptyArgument,68		/// Repertition is only supported by refungible collection69		RepartitionCalledOnNonRefungibleCollection,70	}71}7273pub trait Config: system::Config + pallet_common::Config + Sized + TypeInfo {74	type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;7576	/// Weight information for extrinsics in this pallet.77	type WeightInfo: WeightInfo;78	type CommonWeightInfo: CommonWeightInfo<Self::CrossAccountId>;79	type RefungibleExtensionsWeightInfo: RefungibleExtensionsWeightInfo;80}8182decl_event! {83	pub enum Event<T>84	where85		<T as frame_system::Config>::AccountId,86		<T as pallet_evm::account::Config>::CrossAccountId,87	{88		/// Collection sponsor was removed89		///90		/// # Arguments91		///92		/// * collection_id: Globally unique collection identifier.93		CollectionSponsorRemoved(CollectionId),9495		/// Collection admin was added96		///97		/// # Arguments98		///99		/// * collection_id: Globally unique collection identifier.100		///101		/// * admin:  Admin address.102		CollectionAdminAdded(CollectionId, CrossAccountId),103104		/// Collection owned was changed105		///106		/// # Arguments107		///108		/// * collection_id: Globally unique collection identifier.109		///110		/// * owner:  New owner address.111		CollectionOwnedChanged(CollectionId, AccountId),112113		/// Collection sponsor was set114		///115		/// # Arguments116		///117		/// * collection_id: Globally unique collection identifier.118		///119		/// * owner:  New sponsor address.120		CollectionSponsorSet(CollectionId, AccountId),121122		/// New sponsor was confirm123		///124		/// # Arguments125		///126		/// * collection_id: Globally unique collection identifier.127		///128		/// * sponsor:  New sponsor address.129		SponsorshipConfirmed(CollectionId, AccountId),130131		/// Collection admin was removed132		///133		/// # Arguments134		///135		/// * collection_id: Globally unique collection identifier.136		///137		/// * admin:  Admin address.138		CollectionAdminRemoved(CollectionId, CrossAccountId),139140		/// Address was removed from the allow list141		///142		/// # Arguments143		///144		/// * collection_id: Globally unique collection identifier.145		///146		/// * user:  Address.147		AllowListAddressRemoved(CollectionId, CrossAccountId),148149		/// Address was added to the allow list150		///151		/// # Arguments152		///153		/// * collection_id: Globally unique collection identifier.154		///155		/// * user:  Address.156		AllowListAddressAdded(CollectionId, CrossAccountId),157158		/// Collection limits were set159		///160		/// # Arguments161		///162		/// * collection_id: Globally unique collection identifier.163		CollectionLimitSet(CollectionId),164165		/// Collection permissions were set166		///167		/// # Arguments168		///169		/// * collection_id: Globally unique collection identifier.170		CollectionPermissionSet(CollectionId),171	}172}173174type SelfWeightOf<T> = <T as Config>::WeightInfo;175176// # Used definitions177//178// ## User control levels179//180// chain-controlled - key is uncontrolled by user181//                    i.e autoincrementing index182//                    can use non-cryptographic hash183// real - key is controlled by user184//        but it is hard to generate enough colliding values, i.e owner of signed txs185//        can use non-cryptographic hash186// controlled - key is completly controlled by users187//              i.e maps with mutable keys188//              should use cryptographic hash189//190// ## User control level downgrade reasons191//192// ?1 - chain-controlled -> controlled193//      collections/tokens can be destroyed, resulting in massive holes194// ?2 - chain-controlled -> controlled195//      same as ?1, but can be only added, resulting in easier exploitation196// ?3 - real -> controlled197//      no confirmation required, so addresses can be easily generated198decl_storage! {199	trait Store for Module<T: Config> as Unique {200201		//#region Private members202		/// Used for migrations203		ChainVersion: u64;204		//#endregion205206		//#region Tokens transfer sponosoring rate limit baskets207		/// (Collection id (controlled?2), who created (real))208		/// TODO: Off chain worker should remove from this map when collection gets removed209		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;210		/// Collection id (controlled?2), token id (controlled?2)211		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;212		/// Collection id (controlled?2), owning user (real)213		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;214		/// Collection id (controlled?2), token id (controlled?2)215		pub ReFungibleTransferBasket get(fn refungible_transfer_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;216		//#endregion217218		/// Variable metadata sponsoring219		/// Collection id (controlled?2), token id (controlled?2)220		#[deprecated]221		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;222		/// Last sponsoring of token property setting // todo:doc rephrase this and the following223		pub TokenPropertyBasket get(fn token_property_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;224225		/// Last sponsoring of NFT approval in a collection226		pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;227		/// Last sponsoring of fungible tokens approval in a collection228		pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;229		/// Last sponsoring of RFT approval in a collection230		pub RefungibleApproveBasket get(fn refungible_approve_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;231	}232}233234decl_module! {235	pub struct Module<T: Config> for enum Call236	where237		origin: T::Origin238	{239		type Error = Error<T>;240241		fn deposit_event() = default;242243		fn on_initialize(_now: T::BlockNumber) -> Weight {244			0245		}246247		fn on_runtime_upgrade() -> Weight {248			let limit = None;249250			<VariableMetaDataBasket<T>>::remove_all(limit);251252			0253		}254255		/// This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner of the collection is set to the address that signed the transaction and can be changed later.256		///257		/// # Permissions258		///259		/// * Anyone.260		///261		/// # Arguments262		///263		/// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.264		///265		/// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.266		///267		/// * token_prefix: UTF-8 string with token prefix.268		///269		/// * mode: [CollectionMode] collection type and type dependent data.270		// returns collection ID271		#[weight = <SelfWeightOf<T>>::create_collection()]272		#[transactional]273		#[deprecated]274		pub fn create_collection(origin,275								 collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,276								 collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,277								 token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,278								 mode: CollectionMode) -> DispatchResult  {279			let data: CreateCollectionData<T::AccountId> = CreateCollectionData {280				name: collection_name,281				description: collection_description,282				token_prefix,283				mode,284				..Default::default()285			};286			Self::create_collection_ex(origin, data)287		}288289		/// Create a collection with explicit parameters.290		/// Prefer it to the deprecated [`created_collection`] method.291		///292		/// # Permissions293		///294		/// * Anyone.295		///296		/// # Arguments297		///298		/// * data: explicit create-collection data.299		#[weight = <SelfWeightOf<T>>::create_collection()]300		#[transactional]301		pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {302			let sender = ensure_signed(origin)?;303304			// =========305306			T::CollectionDispatch::create(T::CrossAccountId::from_sub(sender), data)?;307308			Ok(())309		}310311		/// Destroy the collection if no tokens exist within.312		///313		/// # Permissions314		///315		/// * Collection Owner316		///317		/// # Arguments318		///319		/// * collection_id: collection to destroy.320		#[weight = <SelfWeightOf<T>>::destroy_collection()]321		#[transactional]322		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {323			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);324			let collection = <CollectionHandle<T>>::try_get(collection_id)?;325			collection.check_is_internal()?;326327			// =========328329			T::CollectionDispatch::destroy(sender, collection)?;330331			<NftTransferBasket<T>>::remove_prefix(collection_id, None);332			<FungibleTransferBasket<T>>::remove_prefix(collection_id, None);333			<ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);334335			<NftApproveBasket<T>>::remove_prefix(collection_id, None);336			<FungibleApproveBasket<T>>::remove_prefix(collection_id, None);337			<RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);338339			Ok(())340		}341342		/// Add an address to allow list.343		///344		/// # Permissions345		///346		/// * Collection Owner347		/// * Collection Admin348		///349		/// # Arguments350		///351		/// * collection_id.352		///353		/// * address.354		#[weight = <SelfWeightOf<T>>::add_to_allow_list()]355		#[transactional]356		pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{357358			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);359			let collection = <CollectionHandle<T>>::try_get(collection_id)?;360			collection.check_is_internal()?;361362			<PalletCommon<T>>::toggle_allowlist(363				&collection,364				&sender,365				&address,366				true,367			)?;368369			Self::deposit_event(Event::<T>::AllowListAddressAdded(370				collection_id,371				address372			));373374			Ok(())375		}376377		/// Remove an address from allow list.378		///379		/// # Permissions380		///381		/// * Collection Owner382		/// * Collection Admin383		///384		/// # Arguments385		///386		/// * collection_id.387		///388		/// * address.389		#[weight = <SelfWeightOf<T>>::remove_from_allow_list()]390		#[transactional]391		pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{392393			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);394			let collection = <CollectionHandle<T>>::try_get(collection_id)?;395			collection.check_is_internal()?;396397			<PalletCommon<T>>::toggle_allowlist(398				&collection,399				&sender,400				&address,401				false,402			)?;403404			<Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(405				collection_id,406				address407			));408409			Ok(())410		}411412		/// Change the owner of the collection.413		///414		/// # Permissions415		///416		/// * Collection Owner417		///418		/// # Arguments419		///420		/// * collection_id.421		///422		/// * new_owner.423		#[weight = <SelfWeightOf<T>>::change_collection_owner()]424		#[transactional]425		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {426427			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);428429			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;430			target_collection.check_is_internal()?;431			target_collection.check_is_owner(&sender)?;432433			target_collection.owner = new_owner.clone();434			<Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(435				collection_id,436				new_owner437			));438439			target_collection.save()440		}441442		/// Adds an admin of the collection.443		/// NFT Collection can be controlled by multiple admin addresses (some which can also be servers, for example). Admins can issue and burn NFTs, as well as add and remove other admins, but cannot change NFT or Collection ownership.444		///445		/// # Permissions446		///447		/// * Collection Owner448		/// * Collection Admin449		///450		/// # Arguments451		///452		/// * collection_id: ID of the Collection to add admin for.453		///454		/// * new_admin: Address of new admin to add.455		#[weight = <SelfWeightOf<T>>::add_collection_admin()]456		#[transactional]457		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin: T::CrossAccountId) -> DispatchResult {458			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);459			let collection = <CollectionHandle<T>>::try_get(collection_id)?;460			collection.check_is_internal()?;461462			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(463				collection_id,464				new_admin.clone()465			));466467			<PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin, true)468		}469470		/// Remove admin address of the Collection. An admin address can remove itself. List of admins may become empty, in which case only Collection Owner will be able to add an Admin.471		///472		/// # Permissions473		///474		/// * Collection Owner475		/// * Collection Admin476		///477		/// # Arguments478		///479		/// * collection_id: ID of the Collection to remove admin for.480		///481		/// * account_id: Address of admin to remove.482		#[weight = <SelfWeightOf<T>>::remove_collection_admin()]483		#[transactional]484		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {485			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);486			let collection = <CollectionHandle<T>>::try_get(collection_id)?;487			collection.check_is_internal()?;488489			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(490				collection_id,491				account_id.clone()492			));493494			<PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)495		}496497		/// Set (invite) a new collection sponsor. If successful, confirmation from the sponsor-to-be will be pending.498		///499		/// # Permissions500		///501		/// * Collection Owner502		/// * Collection Admin503		///504		/// # Arguments505		///506		/// * collection_id.507		///508		/// * new_sponsor.509		#[weight = <SelfWeightOf<T>>::set_collection_sponsor()]510		#[transactional]511		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {512			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);513514			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;515			target_collection.check_is_owner_or_admin(&sender)?;516			target_collection.check_is_internal()?;517518			target_collection.set_sponsor(new_sponsor.clone())?;519520			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(521				collection_id,522				new_sponsor523			));524525			target_collection.save()526		}527528		/// Confirm own sponsorship of a collection.529		///530		/// # Permissions531		///532		/// * The sponsor to-be533		///534		/// # Arguments535		///536		/// * collection_id.537		#[weight = <SelfWeightOf<T>>::confirm_sponsorship()]538		#[transactional]539		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {540			let sender = ensure_signed(origin)?;541542			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;543			target_collection.check_is_internal()?;544			ensure!(545				target_collection.confirm_sponsorship(&sender)?,546				Error::<T>::ConfirmUnsetSponsorFail547			);548549			<Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(550				collection_id,551				sender552			));553554			target_collection.save()555		}556557		/// Switch back to pay-per-own-transaction model.558		///559		/// # Permissions560		///561		/// * Collection Owner562		///563		/// # Arguments564		///565		/// * collection_id.566		#[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]567		#[transactional]568		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {569			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);570571			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;572			target_collection.check_is_internal()?;573			target_collection.check_is_owner(&sender)?;574575			target_collection.sponsorship = SponsorshipState::Disabled;576577			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(578				collection_id579			));580			target_collection.save()581		}582583		/// Create a concrete instance of NFT Collection created with CreateCollection method.584		///585		/// # Permissions586		///587		/// * Collection Owner588		/// * Collection Admin589		/// * Anyone if590		///     * Allow List is enabled, and591		///     * Address is added to allow list, and592		///     * MintPermission is enabled (see SetMintPermission method)593		///594		/// # Arguments595		///596		/// * collection_id: ID of the collection.597		///598		/// * owner: Address, initial owner of the NFT.599		///600		/// * data: Token data to store on chain.601		#[weight = T::CommonWeightInfo::create_item()]602		#[transactional]603		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {604			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);605			let budget = budget::Value::new(NESTING_BUDGET);606607			dispatch_tx::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))608		}609610		/// Create multiple items in a collection created with CreateCollection method.611		///612		/// # Permissions613		///614		/// * Collection Owner615		/// * Collection Admin616		/// * Anyone if617		///     * Allow List is enabled, and618		///     * Address is added to allow list, and619		///     * MintPermission is enabled (see SetMintPermission method)620		///621		/// # Arguments622		///623		/// * collection_id: ID of the collection.624		///625		/// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].626		///627		/// * owner: Address, initial owner of the NFT.628		#[weight = T::CommonWeightInfo::create_multiple_items(&items_data)]629		#[transactional]630		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {631			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);632			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);633			let budget = budget::Value::new(NESTING_BUDGET);634635			dispatch_tx::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))636		}637638		/// Add or change collection properties.639		///640		/// # Permissions641		///642		/// * Collection Owner643		/// * Collection Admin644		///645		/// # Arguments646		///647		/// * collection_id.648		///649		/// * properties: a vector of key-value pairs stored as the collection's metadata. Keys support Latin letters, '-', '_', and '.' as symbols.650		#[weight = T::CommonWeightInfo::set_collection_properties(properties.len() as u32)]651		#[transactional]652		pub fn set_collection_properties(653			origin,654			collection_id: CollectionId,655			properties: Vec<Property>656		) -> DispatchResultWithPostInfo {657			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);658659			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);660661			dispatch_tx::<T, _>(collection_id, |d| d.set_collection_properties(sender, properties))662		}663664		/// Delete specified collection properties.665		///666		/// # Permissions667		///668		/// * Collection Owner669		/// * Collection Admin670		///671		/// # Arguments672		///673		/// * collection_id.674		///675		/// * property_keys: a vector of keys of the properties to be deleted.676		#[weight = T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32)]677		#[transactional]678		pub fn delete_collection_properties(679			origin,680			collection_id: CollectionId,681			property_keys: Vec<PropertyKey>,682		) -> DispatchResultWithPostInfo {683			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);684685			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);686687			dispatch_tx::<T, _>(collection_id, |d| d.delete_collection_properties(&sender, property_keys))688		}689690		/// Add or change token properties according to collection's permissions.691		///692		/// # Permissions693		///694		/// * Depends on collection's token property permissions and specified property mutability:695		/// 	* Collection Owner696		/// 	* Collection Admin697		/// 	* Token Owner698		///699		/// # Arguments700		///701		/// * collection_id.702		///703		/// * token_id.704		///705		/// * properties: a vector of key-value pairs stored as the token's metadata. Keys support Latin letters, '-', '_', and '.' as symbols.706		#[weight = T::CommonWeightInfo::set_token_properties(properties.len() as u32)]707		#[transactional]708		pub fn set_token_properties(709			origin,710			collection_id: CollectionId,711			token_id: TokenId,712			properties: Vec<Property>713		) -> DispatchResultWithPostInfo {714			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);715716			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);717			let budget = budget::Value::new(NESTING_BUDGET);718719			dispatch_tx::<T, _>(collection_id, |d| d.set_token_properties(sender, token_id, properties, &budget))720		}721722		/// Delete specified token properties.723		///724		/// # Permissions725		///726		/// * Depends on collection's token property permissions and specified property mutability:727		/// 	* Collection Owner728		/// 	* Collection Admin729		/// 	* Token Owner730		///731		/// # Arguments732		///733		/// * collection_id.734		///735		/// * token_id.736		///737		/// * property_keys: a vector of keys of the properties to be deleted.738		#[weight = T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32)]739		#[transactional]740		pub fn delete_token_properties(741			origin,742			collection_id: CollectionId,743			token_id: TokenId,744			property_keys: Vec<PropertyKey>745		) -> DispatchResultWithPostInfo {746			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);747748			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);749			let budget = budget::Value::new(NESTING_BUDGET);750751			dispatch_tx::<T, _>(collection_id, |d| d.delete_token_properties(sender, token_id, property_keys, &budget))752		}753754		/// Add or change token property permissions of a collection.755		///756		/// # Permissions757		///758		/// * Collection Owner759		/// * Collection Admin760		///761		/// # Arguments762		///763		/// * collection_id.764		///765		/// * property_permissions: a vector of permissions for property keys. Keys support Latin letters, '-', '_', and '.' as symbols.766		#[weight = T::CommonWeightInfo::set_token_property_permissions(property_permissions.len() as u32)]767		#[transactional]768		pub fn set_token_property_permissions(769			origin,770			collection_id: CollectionId,771			property_permissions: Vec<PropertyKeyPermission>,772		) -> DispatchResultWithPostInfo {773			ensure!(!property_permissions.is_empty(), Error::<T>::EmptyArgument);774775			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);776777			dispatch_tx::<T, _>(collection_id, |d| d.set_token_property_permissions(&sender, property_permissions))778		}779780		/// Create multiple items inside a collection with explicitly specified initial parameters.781		///782		/// # Permissions783		///784		/// * Collection Owner785		/// * Collection Admin786		/// * Anyone if787		///     * Allow List is enabled, and788		///     * Address is added to allow list, and789		///     * MintPermission is enabled (see SetMintPermission method)790		///791		/// # Arguments792		///793		/// * collection_id: ID of the collection.794		///795		/// * data: explicit item creation data.796		#[weight = T::CommonWeightInfo::create_multiple_items_ex(&data)]797		#[transactional]798		pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {799			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);800			let budget = budget::Value::new(NESTING_BUDGET);801802			dispatch_tx::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))803		}804805		/// Set transfers_enabled value for particular collection.806		///807		/// # Permissions808		///809		/// * Collection Owner810		///811		/// # Arguments812		///813		/// * collection_id: ID of the collection.814		///815		/// * value: New flag value.816		#[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]817		#[transactional]818		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {819			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);820			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;821			target_collection.check_is_internal()?;822			target_collection.check_is_owner(&sender)?;823824			// =========825826			target_collection.limits.transfers_enabled = Some(value);827			target_collection.save()828		}829830		/// Destroy a concrete instance of NFT.831		///832		/// # Permissions833		///834		/// * Collection Owner835		/// * Collection Admin836		/// * Current NFT Owner837		///838		/// # Arguments839		///840		/// * collection_id: ID of the collection.841		///842		/// * item_id: ID of NFT to burn.843		#[weight = T::CommonWeightInfo::burn_item()]844		#[transactional]845		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {846			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);847848			let post_info = dispatch_tx::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;849			if value == 1 {850				<NftTransferBasket<T>>::remove(collection_id, item_id);851				<NftApproveBasket<T>>::remove(collection_id, item_id);852			}853			// Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?854			// <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());855			// <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));856			Ok(post_info)857		}858859		/// Destroy a concrete instance of NFT on behalf of the owner.860		/// See also: [`approve`]861		///862		/// # Permissions863		///864		/// * Collection Owner.865		/// * Collection Admin.866		/// * Current NFT Owner.867		///868		/// # Arguments869		///870		/// * collection_id: ID of the collection.871		///872		/// * item_id: ID of NFT to burn.873		///874		/// * from: owner of item875		#[weight = T::CommonWeightInfo::burn_from()]876		#[transactional]877		pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {878			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);879			let budget = budget::Value::new(NESTING_BUDGET);880881			dispatch_tx::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value, &budget))882		}883884		/// Change ownership of the token.885		///886		/// # Permissions887		///888		/// * Collection Owner889		/// * Collection Admin890		/// * Current NFT owner891		///892		/// # Arguments893		///894		/// * recipient: Address of token recipient.895		///896		/// * collection_id.897		///898		/// * item_id: ID of the item899		///     * Non-Fungible Mode: Required.900		///     * Fungible Mode: Ignored.901		///     * Re-Fungible Mode: Required.902		///903		/// * value: Amount to transfer.904		///     * Non-Fungible Mode: Ignored905		///     * Fungible Mode: Must specify transferred amount906		///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)907		#[weight = T::CommonWeightInfo::transfer()]908		#[transactional]909		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {910			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);911			let budget = budget::Value::new(NESTING_BUDGET);912913			dispatch_tx::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value, &budget))914		}915916		/// Set, change, or remove approved address to transfer the ownership of the NFT.917		///918		/// # Permissions919		///920		/// * Collection Owner921		/// * Collection Admin922		/// * Current NFT owner923		///924		/// # Arguments925		///926		/// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).927		///928		/// * collection_id.929		///930		/// * item_id: ID of the item.931		#[weight = T::CommonWeightInfo::approve()]932		#[transactional]933		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {934			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);935936			dispatch_tx::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))937		}938939		/// Change ownership of a NFT on behalf of the owner. See Approve method for additional information. After this method executes, the approval is removed so that the approved address will not be able to transfer this NFT again from this owner.940		///941		/// # Permissions942		///943		/// * Collection Owner944		/// * Collection Admin945		/// * Current NFT owner946		/// * Address approved by current NFT owner947		///948		/// # Arguments949		///950		/// * from: Address that owns token.951		///952		/// * recipient: Address of token recipient.953		///954		/// * collection_id.955		///956		/// * item_id: ID of the item.957		///958		/// * value: Amount to transfer.959		#[weight = T::CommonWeightInfo::transfer_from()]960		#[transactional]961		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {962			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);963			let budget = budget::Value::new(NESTING_BUDGET);964965			dispatch_tx::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))966		}967968		/// Set specific limits of a collection. Empty, or None fields mean chain default.969		///.970		/// # Permissions971		///972		/// * Collection Owner973		/// * Collection Admin974		///975		/// # Arguments976		///977		/// * collection_id.978		///979		/// * new_limit: The new limits of the collection. They will overwrite the current ones.980		#[weight = <SelfWeightOf<T>>::set_collection_limits()]981		#[transactional]982		pub fn set_collection_limits(983			origin,984			collection_id: CollectionId,985			new_limit: CollectionLimits,986		) -> DispatchResult {987			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);988			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;989			target_collection.check_is_internal()?;990			target_collection.check_is_owner_or_admin(&sender)?;991			let old_limit = &target_collection.limits;992993			target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;994995			<Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(996				collection_id997			));998999			target_collection.save()1000		}10011002		/// Set specific permissions of a collection. Empty, or None fields mean chain default.1003		///1004		/// # Permissions1005		///1006		/// * Collection Owner1007		/// * Collection Admin1008		///1009		/// # Arguments1010		///1011		/// * collection_id.1012		///1013		/// * new_permission: The new permissions of the collection. They will overwrite the current ones.1014		#[weight = <SelfWeightOf<T>>::set_collection_limits()]1015		#[transactional]1016		pub fn set_collection_permissions(1017			origin,1018			collection_id: CollectionId,1019			new_permission: CollectionPermissions,1020		) -> DispatchResult {1021			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1022			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1023			target_collection.check_is_internal()?;1024			target_collection.check_is_owner_or_admin(&sender)?;1025			let old_limit = &target_collection.permissions;10261027			target_collection.permissions = <PalletCommon<T>>::clamp_permissions(target_collection.mode.clone(), &old_limit, new_permission)?;10281029			<Pallet<T>>::deposit_event(Event::<T>::CollectionPermissionSet(1030				collection_id1031			));10321033			target_collection.save()1034		}10351036		/// Re-partition a refungible token, while owning all of its parts.1037		///1038		/// # Permissions1039		///1040		/// * Token Owner (must own every part)1041		///1042		/// # Arguments1043		///1044		/// * collection_id.1045		///1046		/// * token: the ID of the RFT.1047		///1048		/// * amount: The new number of parts into which the token shall be partitioned.1049		#[weight = T::RefungibleExtensionsWeightInfo::repartition()]1050		#[transactional]1051		pub fn repartition(1052			origin,1053			collection_id: CollectionId,1054			token: TokenId,1055			amount: u128,1056		) -> DispatchResultWithPostInfo {1057			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1058			dispatch_tx::<T, _>(collection_id, |d| {1059				if let Some(refungible_extensions) = d.refungible_extensions() {1060					refungible_extensions.repartition(&sender, token, amount)1061				} else {1062					fail!(<Error<T>>::RepartitionCalledOnNonRefungibleCollection)1063				}1064			})1065		}1066	}1067}