git.delta.rocks / unique-network / refs/commits / 7523de2ff68e

difftreelog

source

pallets/unique/src/lib.rs28.8 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,31	weights::{Weight},32	transactional,33	pallet_prelude::{DispatchResultWithPostInfo, ConstU32, Get},34	BoundedVec,35};36use sp_core::H160;37use scale_info::TypeInfo;38use frame_system::{self as system, ensure_signed};39use sp_runtime::{sp_std::prelude::Vec};40use up_data_structs::{41	MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH,42	CreateItemData, CollectionLimits, CollectionPermissions, CollectionId, CollectionMode, TokenId,43	SponsorshipState, CreateCollectionData, CreateItemExData, budget, Property, PropertyKey,44	PropertyKeyPermission,45};46use pallet_evm::account::CrossAccountId;47use pallet_common::{48	CollectionHandle, Pallet as PalletCommon, CommonWeightInfo, dispatch::dispatch_call,49	dispatch::CollectionDispatch,50};51pub mod eth;5253#[cfg(feature = "runtime-benchmarks")]54mod benchmarking;55pub mod weights;56use weights::WeightInfo;5758const NESTING_BUDGET: u32 = 5;5960decl_error! {61	/// Error for non-fungible-token module.62	pub enum Error for Module<T: Config> {63		/// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.64		CollectionDecimalPointLimitExceeded,65		/// This address is not set as sponsor, use setCollectionSponsor first.66		ConfirmUnsetSponsorFail,67		/// Length of items properties must be greater than 0.68		EmptyArgument,69	}70}7172pub trait Config: system::Config + pallet_common::Config + Sized + TypeInfo {73	type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;7475	/// Weight information for extrinsics in this pallet.76	type WeightInfo: WeightInfo;77	type CommonWeightInfo: CommonWeightInfo<Self::CrossAccountId>;78	type ContractAddress: Get<H160>;79}8081decl_event! {82	pub enum Event<T>83	where84		<T as frame_system::Config>::AccountId,85		<T as pallet_evm::account::Config>::CrossAccountId,86	{87		/// Collection sponsor was removed88		///89		/// # Arguments90		///91		/// * collection_id: Globally unique collection identifier.92		CollectionSponsorRemoved(CollectionId),9394		/// Collection admin was added95		///96		/// # Arguments97		///98		/// * collection_id: Globally unique collection identifier.99		///100		/// * admin:  Admin address.101		CollectionAdminAdded(CollectionId, CrossAccountId),102103		/// Collection owned was change104		///105		/// # Arguments106		///107		/// * collection_id: Globally unique collection identifier.108		///109		/// * owner:  New owner address.110		CollectionOwnedChanged(CollectionId, AccountId),111112		/// Collection sponsor was set113		///114		/// # Arguments115		///116		/// * collection_id: Globally unique collection identifier.117		///118		/// * owner:  New sponsor address.119		CollectionSponsorSet(CollectionId, AccountId),120121		/// New sponsor was confirm122		///123		/// # Arguments124		///125		/// * collection_id: Globally unique collection identifier.126		///127		/// * sponsor:  New sponsor address.128		SponsorshipConfirmed(CollectionId, AccountId),129130		/// Collection admin was removed131		///132		/// # Arguments133		///134		/// * collection_id: Globally unique collection identifier.135		///136		/// * admin:  Admin address.137		CollectionAdminRemoved(CollectionId, CrossAccountId),138139		/// Address was remove from allow list140		///141		/// # Arguments142		///143		/// * collection_id: Globally unique collection identifier.144		///145		/// * user:  Address.146		AllowListAddressRemoved(CollectionId, CrossAccountId),147148		/// Address was add to allow list149		///150		/// # Arguments151		///152		/// * collection_id: Globally unique collection identifier.153		///154		/// * user:  Address.155		AllowListAddressAdded(CollectionId, CrossAccountId),156157		/// Collection limits was set158		///159		/// # Arguments160		///161		/// * collection_id: Globally unique collection identifier.162		CollectionLimitSet(CollectionId),163164		CollectionPermissionSet(CollectionId),165	}166}167168type SelfWeightOf<T> = <T as Config>::WeightInfo;169170// # Used definitions171//172// ## User control levels173//174// chain-controlled - key is uncontrolled by user175//                    i.e autoincrementing index176//                    can use non-cryptographic hash177// real - key is controlled by user178//        but it is hard to generate enough colliding values, i.e owner of signed txs179//        can use non-cryptographic hash180// controlled - key is completly controlled by users181//              i.e maps with mutable keys182//              should use cryptographic hash183//184// ## User control level downgrade reasons185//186// ?1 - chain-controlled -> controlled187//      collections/tokens can be destroyed, resulting in massive holes188// ?2 - chain-controlled -> controlled189//      same as ?1, but can be only added, resulting in easier exploitation190// ?3 - real -> controlled191//      no confirmation required, so addresses can be easily generated192decl_storage! {193	trait Store for Module<T: Config> as Unique {194195		//#region Private members196		/// Used for migrations197		ChainVersion: u64;198		//#endregion199200		//#region Tokens transfer rate limit baskets201		/// (Collection id (controlled?2), who created (real))202		/// TODO: Off chain worker should remove from this map when collection gets removed203		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;204		/// Collection id (controlled?2), token id (controlled?2)205		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;206		/// Collection id (controlled?2), owning user (real)207		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;208		/// Collection id (controlled?2), token id (controlled?2)209		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>;210		//#endregion211212		/// Variable metadata sponsoring213		/// Collection id (controlled?2), token id (controlled?2)214		#[deprecated]215		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;216		pub TokenPropertyBasket get(fn token_property_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;217218		/// Approval sponsoring219		pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;220		pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;221		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>;222	}223}224225decl_module! {226	pub struct Module<T: Config> for enum Call227	where228		origin: T::Origin229	{230		type Error = Error<T>;231232		fn deposit_event() = default;233234		fn on_initialize(_now: T::BlockNumber) -> Weight {235			0236		}237238		fn on_runtime_upgrade() -> Weight {239			let limit = None;240241			<VariableMetaDataBasket<T>>::remove_all(limit);242243			0244		}245246		/// 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.247		///248		/// # Permissions249		///250		/// * Anyone.251		///252		/// # Arguments253		///254		/// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.255		///256		/// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.257		///258		/// * token_prefix: UTF-8 string with token prefix.259		///260		/// * mode: [CollectionMode] collection type and type dependent data.261		// returns collection ID262		#[weight = <SelfWeightOf<T>>::create_collection()]263		#[transactional]264		#[deprecated]265		pub fn create_collection(origin,266								 collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,267								 collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,268								 token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,269								 mode: CollectionMode) -> DispatchResult  {270			let data: CreateCollectionData<T::AccountId> = CreateCollectionData {271				name: collection_name,272				description: collection_description,273				token_prefix,274				mode,275				..Default::default()276			};277			Self::create_collection_ex(origin, data)278		}279280		/// This method creates a collection281		///282		/// Prefer it to deprecated [`created_collection`] method283		#[weight = <SelfWeightOf<T>>::create_collection()]284		#[transactional]285		pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {286			let sender = ensure_signed(origin)?;287288			// =========289290			T::CollectionDispatch::create(sender, data)?;291292			Ok(())293		}294295		/// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.296		///297		/// # Permissions298		///299		/// * Collection Owner.300		///301		/// # Arguments302		///303		/// * collection_id: collection to destroy.304		#[weight = <SelfWeightOf<T>>::destroy_collection()]305		#[transactional]306		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {307			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);308			let collection = <CollectionHandle<T>>::try_get(collection_id)?;309310			// =========311312			T::CollectionDispatch::destroy(sender, collection)?;313314			<NftTransferBasket<T>>::remove_prefix(collection_id, None);315			<FungibleTransferBasket<T>>::remove_prefix(collection_id, None);316			<ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);317318			<NftApproveBasket<T>>::remove_prefix(collection_id, None);319			<FungibleApproveBasket<T>>::remove_prefix(collection_id, None);320			<RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);321322			Ok(())323		}324325		/// Add an address to allow list.326		///327		/// # Permissions328		///329		/// * Collection Owner330		/// * Collection Admin331		///332		/// # Arguments333		///334		/// * collection_id.335		///336		/// * address.337		#[weight = <SelfWeightOf<T>>::add_to_allow_list()]338		#[transactional]339		pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{340341			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);342			let collection = <CollectionHandle<T>>::try_get(collection_id)?;343344			<PalletCommon<T>>::toggle_allowlist(345				&collection,346				&sender,347				&address,348				true,349			)?;350351			Self::deposit_event(Event::<T>::AllowListAddressAdded(352				collection_id,353				address354			));355356			Ok(())357		}358359		/// Remove an address from allow list.360		///361		/// # Permissions362		///363		/// * Collection Owner364		/// * Collection Admin365		///366		/// # Arguments367		///368		/// * collection_id.369		///370		/// * address.371		#[weight = <SelfWeightOf<T>>::remove_from_allow_list()]372		#[transactional]373		pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{374375			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);376			let collection = <CollectionHandle<T>>::try_get(collection_id)?;377378			<PalletCommon<T>>::toggle_allowlist(379				&collection,380				&sender,381				&address,382				false,383			)?;384385			<Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(386				collection_id,387				address388			));389390			Ok(())391		}392393		/// Change the owner of the collection.394		///395		/// # Permissions396		///397		/// * Collection Owner.398		///399		/// # Arguments400		///401		/// * collection_id.402		///403		/// * new_owner.404		#[weight = <SelfWeightOf<T>>::change_collection_owner()]405		#[transactional]406		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {407408			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);409410			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;411			target_collection.check_is_owner(&sender)?;412413			target_collection.owner = new_owner.clone();414			<Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(415				collection_id,416				new_owner417			));418419			target_collection.save()420		}421422		/// Adds an admin of the Collection.423		/// 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.424		///425		/// # Permissions426		///427		/// * Collection Owner.428		/// * Collection Admin.429		///430		/// # Arguments431		///432		/// * collection_id: ID of the Collection to add admin for.433		///434		/// * new_admin_id: Address of new admin to add.435		#[weight = <SelfWeightOf<T>>::add_collection_admin()]436		#[transactional]437		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {438			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);439			let collection = <CollectionHandle<T>>::try_get(collection_id)?;440441			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(442				collection_id,443				new_admin_id.clone()444			));445446			<PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)447		}448449		/// 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.450		///451		/// # Permissions452		///453		/// * Collection Owner.454		/// * Collection Admin.455		///456		/// # Arguments457		///458		/// * collection_id: ID of the Collection to remove admin for.459		///460		/// * account_id: Address of admin to remove.461		#[weight = <SelfWeightOf<T>>::remove_collection_admin()]462		#[transactional]463		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {464			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);465			let collection = <CollectionHandle<T>>::try_get(collection_id)?;466467			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(468				collection_id,469				account_id.clone()470			));471472			<PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)473		}474475		/// # Permissions476		///477		/// * Collection Owner478		///479		/// # Arguments480		///481		/// * collection_id.482		///483		/// * new_sponsor.484		#[weight = <SelfWeightOf<T>>::set_collection_sponsor()]485		#[transactional]486		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {487			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);488489			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;490			target_collection.check_is_owner(&sender)?;491492			target_collection.set_sponsor(new_sponsor.clone());493494			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(495				collection_id,496				new_sponsor497			));498499			target_collection.save()500		}501502		/// # Permissions503		///504		/// * Sponsor.505		///506		/// # Arguments507		///508		/// * collection_id.509		#[weight = <SelfWeightOf<T>>::confirm_sponsorship()]510		#[transactional]511		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {512			let sender = ensure_signed(origin)?;513514			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;515			ensure!(516				target_collection.confirm_sponsorship(&sender),517				Error::<T>::ConfirmUnsetSponsorFail518			);519520			<Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(521				collection_id,522				sender523			));524525			target_collection.save()526		}527528		/// Switch back to pay-per-own-transaction model.529		///530		/// # Permissions531		///532		/// * Collection owner.533		///534		/// # Arguments535		///536		/// * collection_id.537		#[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]538		#[transactional]539		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {540			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);541542			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;543			target_collection.check_is_owner(&sender)?;544545			target_collection.sponsorship = SponsorshipState::Disabled;546547			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(548				collection_id549			));550			target_collection.save()551		}552553		/// This method creates a concrete instance of NFT Collection created with CreateCollection method.554		///555		/// # Permissions556		///557		/// * Collection Owner.558		/// * Collection Admin.559		/// * Anyone if560		///     * Allow List is enabled, and561		///     * Address is added to allow list, and562		///     * MintPermission is enabled (see SetMintPermission method)563		///564		/// # Arguments565		///566		/// * collection_id: ID of the collection.567		///568		/// * owner: Address, initial owner of the NFT.569		///570		/// * data: Token data to store on chain.571		#[weight = T::CommonWeightInfo::create_item()]572		#[transactional]573		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {574			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);575			let budget = budget::Value::new(NESTING_BUDGET);576577			dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))578		}579580		/// This method creates multiple items in a collection created with CreateCollection method.581		///582		/// # Permissions583		///584		/// * Collection Owner.585		/// * Collection Admin.586		/// * Anyone if587		///     * Allow List is enabled, and588		///     * Address is added to allow list, and589		///     * MintPermission is enabled (see SetMintPermission method)590		///591		/// # Arguments592		///593		/// * collection_id: ID of the collection.594		///595		/// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].596		///597		/// * owner: Address, initial owner of the NFT.598		#[weight = T::CommonWeightInfo::create_multiple_items(&items_data)]599		#[transactional]600		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {601			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);602			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);603			let budget = budget::Value::new(NESTING_BUDGET);604605			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))606		}607608		#[weight = T::CommonWeightInfo::set_collection_properties(properties.len() as u32)]609		#[transactional]610		pub fn set_collection_properties(611			origin,612			collection_id: CollectionId,613			properties: Vec<Property>614		) -> DispatchResultWithPostInfo {615			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);616617			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);618619			dispatch_call::<T, _>(collection_id, |d| d.set_collection_properties(sender, properties))620		}621622		#[weight = T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32)]623		#[transactional]624		pub fn delete_collection_properties(625			origin,626			collection_id: CollectionId,627			property_keys: Vec<PropertyKey>,628		) -> DispatchResultWithPostInfo {629			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);630631			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);632633			dispatch_call::<T, _>(collection_id, |d| d.delete_collection_properties(&sender, property_keys))634		}635636		#[weight = T::CommonWeightInfo::set_token_properties(properties.len() as u32)]637		#[transactional]638		pub fn set_token_properties(639			origin,640			collection_id: CollectionId,641			token_id: TokenId,642			properties: Vec<Property>643		) -> DispatchResultWithPostInfo {644			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);645646			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);647648			dispatch_call::<T, _>(collection_id, |d| d.set_token_properties(sender, token_id, properties))649		}650651		#[weight = T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32)]652		#[transactional]653		pub fn delete_token_properties(654			origin,655			collection_id: CollectionId,656			token_id: TokenId,657			property_keys: Vec<PropertyKey>658		) -> DispatchResultWithPostInfo {659			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);660661			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);662663			dispatch_call::<T, _>(collection_id, |d| d.delete_token_properties(sender, token_id, property_keys))664		}665666		#[weight = T::CommonWeightInfo::set_property_permissions(property_permissions.len() as u32)]667		#[transactional]668		pub fn set_property_permissions(669			origin,670			collection_id: CollectionId,671			property_permissions: Vec<PropertyKeyPermission>,672		) -> DispatchResultWithPostInfo {673			ensure!(!property_permissions.is_empty(), Error::<T>::EmptyArgument);674675			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);676677			dispatch_call::<T, _>(collection_id, |d| d.set_property_permissions(&sender, property_permissions))678		}679680		#[weight = T::CommonWeightInfo::create_multiple_items_ex(&data)]681		#[transactional]682		pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {683			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);684			let budget = budget::Value::new(NESTING_BUDGET);685686			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))687		}688689		// TODO! transaction weight690691		/// Set transfers_enabled value for particular collection692		///693		/// # Permissions694		///695		/// * Collection Owner.696		///697		/// # Arguments698		///699		/// * collection_id: ID of the collection.700		///701		/// * value: New flag value.702		#[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]703		#[transactional]704		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {705			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);706			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;707			target_collection.check_is_owner(&sender)?;708709			// =========710711			target_collection.limits.transfers_enabled = Some(value);712			target_collection.save()713		}714715		/// Destroys a concrete instance of NFT.716		///717		/// # Permissions718		///719		/// * Collection Owner.720		/// * Collection Admin.721		/// * Current NFT Owner.722		///723		/// # Arguments724		///725		/// * collection_id: ID of the collection.726		///727		/// * item_id: ID of NFT to burn.728		#[weight = T::CommonWeightInfo::burn_item()]729		#[transactional]730		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {731			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);732733			let post_info = dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;734			if value == 1 {735				<NftTransferBasket<T>>::remove(collection_id, item_id);736				<NftApproveBasket<T>>::remove(collection_id, item_id);737			}738			// Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?739			// <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());740			// <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));741			Ok(post_info)742		}743744		/// Destroys a concrete instance of NFT on behalf of the owner745		/// See also: [`approve`]746		///747		/// # Permissions748		///749		/// * Collection Owner.750		/// * Collection Admin.751		/// * Current NFT Owner.752		///753		/// # Arguments754		///755		/// * collection_id: ID of the collection.756		///757		/// * item_id: ID of NFT to burn.758		///759		/// * from: owner of item760		#[weight = T::CommonWeightInfo::burn_from()]761		#[transactional]762		pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {763			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);764			let budget = budget::Value::new(NESTING_BUDGET);765766			dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value, &budget))767		}768769		/// Change ownership of the token.770		///771		/// # Permissions772		///773		/// * Collection Owner774		/// * Collection Admin775		/// * Current NFT owner776		///777		/// # Arguments778		///779		/// * recipient: Address of token recipient.780		///781		/// * collection_id.782		///783		/// * item_id: ID of the item784		///     * Non-Fungible Mode: Required.785		///     * Fungible Mode: Ignored.786		///     * Re-Fungible Mode: Required.787		///788		/// * value: Amount to transfer.789		///     * Non-Fungible Mode: Ignored790		///     * Fungible Mode: Must specify transferred amount791		///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)792		#[weight = T::CommonWeightInfo::transfer()]793		#[transactional]794		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {795			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);796			let budget = budget::Value::new(NESTING_BUDGET);797798			dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value, &budget))799		}800801		/// Set, change, or remove approved address to transfer the ownership of the NFT.802		///803		/// # Permissions804		///805		/// * Collection Owner806		/// * Collection Admin807		/// * Current NFT owner808		///809		/// # Arguments810		///811		/// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).812		///813		/// * collection_id.814		///815		/// * item_id: ID of the item.816		#[weight = T::CommonWeightInfo::approve()]817		#[transactional]818		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {819			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);820821			dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))822		}823824		/// 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.825		///826		/// # Permissions827		/// * Collection Owner828		/// * Collection Admin829		/// * Current NFT owner830		/// * Address approved by current NFT owner831		///832		/// # Arguments833		///834		/// * from: Address that owns token.835		///836		/// * recipient: Address of token recipient.837		///838		/// * collection_id.839		///840		/// * item_id: ID of the item.841		///842		/// * value: Amount to transfer.843		#[weight = T::CommonWeightInfo::transfer_from()]844		#[transactional]845		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {846			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);847			let budget = budget::Value::new(NESTING_BUDGET);848849			dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))850		}851852		#[weight = <SelfWeightOf<T>>::set_collection_limits()]853		#[transactional]854		pub fn set_collection_limits(855			origin,856			collection_id: CollectionId,857			new_limit: CollectionLimits,858		) -> DispatchResult {859			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);860			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;861			target_collection.check_is_owner(&sender)?;862			let old_limit = &target_collection.limits;863864			target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;865866			<Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(867				collection_id868			));869870			target_collection.save()871		}872873		#[weight = <SelfWeightOf<T>>::set_collection_limits()]874		#[transactional]875		pub fn set_collection_permissions(876			origin,877			collection_id: CollectionId,878			new_limit: CollectionPermissions,879		) -> DispatchResult {880			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);881			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;882			target_collection.check_is_owner(&sender)?;883			let old_limit = &target_collection.permissions;884885			target_collection.permissions = <PalletCommon<T>>::clamp_permissions(target_collection.mode.clone(), &old_limit, new_limit)?;886887			<Pallet<T>>::deposit_event(Event::<T>::CollectionPermissionSet(888				collection_id889			));890891			target_collection.save()892		}893	}894}