git.delta.rocks / unique-network / refs/commits / 99c2d03b9527

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},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,41	MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH, AccessMode, CreateItemData,42	CollectionLimits, CollectionPermissions, CollectionId, CollectionMode, TokenId, SponsorshipState,43	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}7980decl_event! {81	pub enum Event<T>82	where83		<T as frame_system::Config>::AccountId,84		<T as pallet_evm::account::Config>::CrossAccountId,85	{86		/// Collection sponsor was removed87		///88		/// # Arguments89		///90		/// * collection_id: Globally unique collection identifier.91		CollectionSponsorRemoved(CollectionId),9293		/// Collection admin was added94		///95		/// # Arguments96		///97		/// * collection_id: Globally unique collection identifier.98		///99		/// * admin:  Admin address.100		CollectionAdminAdded(CollectionId, CrossAccountId),101102		/// Collection owned was change103		///104		/// # Arguments105		///106		/// * collection_id: Globally unique collection identifier.107		///108		/// * owner:  New owner address.109		CollectionOwnedChanged(CollectionId, AccountId),110111		/// Collection sponsor was set112		///113		/// # Arguments114		///115		/// * collection_id: Globally unique collection identifier.116		///117		/// * owner:  New sponsor address.118		CollectionSponsorSet(CollectionId, AccountId),119120		/// New sponsor was confirm121		///122		/// # Arguments123		///124		/// * collection_id: Globally unique collection identifier.125		///126		/// * sponsor:  New sponsor address.127		SponsorshipConfirmed(CollectionId, AccountId),128129		/// Collection admin was removed130		///131		/// # Arguments132		///133		/// * collection_id: Globally unique collection identifier.134		///135		/// * admin:  Admin address.136		CollectionAdminRemoved(CollectionId, CrossAccountId),137138		/// Address was remove from allow list139		///140		/// # Arguments141		///142		/// * collection_id: Globally unique collection identifier.143		///144		/// * user:  Address.145		AllowListAddressRemoved(CollectionId, CrossAccountId),146147		/// Address was add to allow list148		///149		/// # Arguments150		///151		/// * collection_id: Globally unique collection identifier.152		///153		/// * user:  Address.154		AllowListAddressAdded(CollectionId, CrossAccountId),155156		/// Collection limits was set157		///158		/// # Arguments159		///160		/// * collection_id: Globally unique collection identifier.161		CollectionLimitSet(CollectionId),162163		CollectionPermissionSet(CollectionId),164	}165}166167type SelfWeightOf<T> = <T as Config>::WeightInfo;168169// # Used definitions170//171// ## User control levels172//173// chain-controlled - key is uncontrolled by user174//                    i.e autoincrementing index175//                    can use non-cryptographic hash176// real - key is controlled by user177//        but it is hard to generate enough colliding values, i.e owner of signed txs178//        can use non-cryptographic hash179// controlled - key is completly controlled by users180//              i.e maps with mutable keys181//              should use cryptographic hash182//183// ## User control level downgrade reasons184//185// ?1 - chain-controlled -> controlled186//      collections/tokens can be destroyed, resulting in massive holes187// ?2 - chain-controlled -> controlled188//      same as ?1, but can be only added, resulting in easier exploitation189// ?3 - real -> controlled190//      no confirmation required, so addresses can be easily generated191decl_storage! {192	trait Store for Module<T: Config> as Unique {193194		//#region Private members195		/// Used for migrations196		ChainVersion: u64;197		//#endregion198199		//#region Tokens transfer rate limit baskets200		/// (Collection id (controlled?2), who created (real))201		/// TODO: Off chain worker should remove from this map when collection gets removed202		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;203		/// Collection id (controlled?2), token id (controlled?2)204		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;205		/// Collection id (controlled?2), owning user (real)206		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;207		/// Collection id (controlled?2), token id (controlled?2)208		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>;209		//#endregion210211		/// Variable metadata sponsoring212		/// Collection id (controlled?2), token id (controlled?2)213		#[deprecated]214		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;215		pub TokenPropertyBasket get(fn token_property_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;216217		/// Approval sponsoring218		pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;219		pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;220		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>;221	}222}223224decl_module! {225	pub struct Module<T: Config> for enum Call226	where227		origin: T::Origin228	{229		type Error = Error<T>;230231		fn deposit_event() = default;232233		fn on_initialize(_now: T::BlockNumber) -> Weight {234			0235		}236237		fn on_runtime_upgrade() -> Weight {238			let limit = None;239240			<VariableMetaDataBasket<T>>::remove_all(limit);241242			0243		}244245		/// 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.246		///247		/// # Permissions248		///249		/// * Anyone.250		///251		/// # Arguments252		///253		/// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.254		///255		/// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.256		///257		/// * token_prefix: UTF-8 string with token prefix.258		///259		/// * mode: [CollectionMode] collection type and type dependent data.260		// returns collection ID261		#[weight = <SelfWeightOf<T>>::create_collection()]262		#[transactional]263		#[deprecated]264		pub fn create_collection(origin,265								 collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,266								 collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,267								 token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,268								 mode: CollectionMode) -> DispatchResult  {269			let data: CreateCollectionData<T::AccountId> = CreateCollectionData {270				name: collection_name,271				description: collection_description,272				token_prefix,273				mode,274				..Default::default()275			};276			Self::create_collection_ex(origin, data)277		}278279		/// This method creates a collection280		///281		/// Prefer it to deprecated [`created_collection`] method282		#[weight = <SelfWeightOf<T>>::create_collection()]283		#[transactional]284		pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {285			let sender = ensure_signed(origin)?;286287			// =========288289			T::CollectionDispatch::create(sender, data)?;290291			Ok(())292		}293294		/// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.295		///296		/// # Permissions297		///298		/// * Collection Owner.299		///300		/// # Arguments301		///302		/// * collection_id: collection to destroy.303		#[weight = <SelfWeightOf<T>>::destroy_collection()]304		#[transactional]305		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {306			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);307			let collection = <CollectionHandle<T>>::try_get(collection_id)?;308309			// =========310311			T::CollectionDispatch::destroy(sender, collection)?;312313			<NftTransferBasket<T>>::remove_prefix(collection_id, None);314			<FungibleTransferBasket<T>>::remove_prefix(collection_id, None);315			<ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);316317			<NftApproveBasket<T>>::remove_prefix(collection_id, None);318			<FungibleApproveBasket<T>>::remove_prefix(collection_id, None);319			<RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);320321			Ok(())322		}323324		/// Add an address to allow list.325		///326		/// # Permissions327		///328		/// * Collection Owner329		/// * Collection Admin330		///331		/// # Arguments332		///333		/// * collection_id.334		///335		/// * address.336		#[weight = <SelfWeightOf<T>>::add_to_allow_list()]337		#[transactional]338		pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{339340			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);341			let collection = <CollectionHandle<T>>::try_get(collection_id)?;342343			<PalletCommon<T>>::toggle_allowlist(344				&collection,345				&sender,346				&address,347				true,348			)?;349350			Self::deposit_event(Event::<T>::AllowListAddressAdded(351				collection_id,352				address353			));354355			Ok(())356		}357358		/// Remove an address from allow list.359		///360		/// # Permissions361		///362		/// * Collection Owner363		/// * Collection Admin364		///365		/// # Arguments366		///367		/// * collection_id.368		///369		/// * address.370		#[weight = <SelfWeightOf<T>>::remove_from_allow_list()]371		#[transactional]372		pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{373374			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);375			let collection = <CollectionHandle<T>>::try_get(collection_id)?;376377			<PalletCommon<T>>::toggle_allowlist(378				&collection,379				&sender,380				&address,381				false,382			)?;383384			<Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(385				collection_id,386				address387			));388389			Ok(())390		}391392		/// Change the owner of the collection.393		///394		/// # Permissions395		///396		/// * Collection Owner.397		///398		/// # Arguments399		///400		/// * collection_id.401		///402		/// * new_owner.403		#[weight = <SelfWeightOf<T>>::change_collection_owner()]404		#[transactional]405		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {406407			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);408409			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;410			target_collection.check_is_owner(&sender)?;411412			target_collection.owner = new_owner.clone();413			<Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(414				collection_id,415				new_owner416			));417418			target_collection.save()419		}420421		/// Adds an admin of the Collection.422		/// 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.423		///424		/// # Permissions425		///426		/// * Collection Owner.427		/// * Collection Admin.428		///429		/// # Arguments430		///431		/// * collection_id: ID of the Collection to add admin for.432		///433		/// * new_admin_id: Address of new admin to add.434		#[weight = <SelfWeightOf<T>>::add_collection_admin()]435		#[transactional]436		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {437			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);438			let collection = <CollectionHandle<T>>::try_get(collection_id)?;439440			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(441				collection_id,442				new_admin_id.clone()443			));444445			<PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)446		}447448		/// 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.449		///450		/// # Permissions451		///452		/// * Collection Owner.453		/// * Collection Admin.454		///455		/// # Arguments456		///457		/// * collection_id: ID of the Collection to remove admin for.458		///459		/// * account_id: Address of admin to remove.460		#[weight = <SelfWeightOf<T>>::remove_collection_admin()]461		#[transactional]462		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {463			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);464			let collection = <CollectionHandle<T>>::try_get(collection_id)?;465466			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(467				collection_id,468				account_id.clone()469			));470471			<PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)472		}473474		/// # Permissions475		///476		/// * Collection Owner477		///478		/// # Arguments479		///480		/// * collection_id.481		///482		/// * new_sponsor.483		#[weight = <SelfWeightOf<T>>::set_collection_sponsor()]484		#[transactional]485		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {486			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);487488			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;489			target_collection.check_is_owner(&sender)?;490491			target_collection.set_sponsor(new_sponsor.clone());492493			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(494				collection_id,495				new_sponsor496			));497498			target_collection.save()499		}500501		/// # Permissions502		///503		/// * Sponsor.504		///505		/// # Arguments506		///507		/// * collection_id.508		#[weight = <SelfWeightOf<T>>::confirm_sponsorship()]509		#[transactional]510		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {511			let sender = ensure_signed(origin)?;512513			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;514			ensure!(515				target_collection.confirm_sponsorship(&sender),516				Error::<T>::ConfirmUnsetSponsorFail517			);518519			<Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(520				collection_id,521				sender522			));523524			target_collection.save()525		}526527		/// Switch back to pay-per-own-transaction model.528		///529		/// # Permissions530		///531		/// * Collection owner.532		///533		/// # Arguments534		///535		/// * collection_id.536		#[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]537		#[transactional]538		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {539			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);540541			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;542			target_collection.check_is_owner(&sender)?;543544			target_collection.sponsorship = SponsorshipState::Disabled;545546			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(547				collection_id548			));549			target_collection.save()550		}551552		/// This method creates a concrete instance of NFT Collection created with CreateCollection method.553		///554		/// # Permissions555		///556		/// * Collection Owner.557		/// * Collection Admin.558		/// * Anyone if559		///     * Allow List is enabled, and560		///     * Address is added to allow list, and561		///     * MintPermission is enabled (see SetMintPermission method)562		///563		/// # Arguments564		///565		/// * collection_id: ID of the collection.566		///567		/// * owner: Address, initial owner of the NFT.568		///569		/// * data: Token data to store on chain.570		#[weight = T::CommonWeightInfo::create_item()]571		#[transactional]572		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {573			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);574			let budget = budget::Value::new(NESTING_BUDGET);575576			dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))577		}578579		/// This method creates multiple items in a collection created with CreateCollection method.580		///581		/// # Permissions582		///583		/// * Collection Owner.584		/// * Collection Admin.585		/// * Anyone if586		///     * Allow List is enabled, and587		///     * Address is added to allow list, and588		///     * MintPermission is enabled (see SetMintPermission method)589		///590		/// # Arguments591		///592		/// * collection_id: ID of the collection.593		///594		/// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].595		///596		/// * owner: Address, initial owner of the NFT.597		#[weight = T::CommonWeightInfo::create_multiple_items(&items_data)]598		#[transactional]599		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {600			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);601			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);602			let budget = budget::Value::new(NESTING_BUDGET);603604			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))605		}606607		#[weight = T::CommonWeightInfo::set_collection_properties(properties.len() as u32)]608		#[transactional]609		pub fn set_collection_properties(610			origin,611			collection_id: CollectionId,612			properties: Vec<Property>613		) -> DispatchResultWithPostInfo {614			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);615616			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);617618			dispatch_call::<T, _>(collection_id, |d| d.set_collection_properties(sender, properties))619		}620621		#[weight = T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32)]622		#[transactional]623		pub fn delete_collection_properties(624			origin,625			collection_id: CollectionId,626			property_keys: Vec<PropertyKey>,627		) -> DispatchResultWithPostInfo {628			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);629630			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);631632			dispatch_call::<T, _>(collection_id, |d| d.delete_collection_properties(&sender, property_keys))633		}634635		#[weight = T::CommonWeightInfo::set_token_properties(properties.len() as u32)]636		#[transactional]637		pub fn set_token_properties(638			origin,639			collection_id: CollectionId,640			token_id: TokenId,641			properties: Vec<Property>642		) -> DispatchResultWithPostInfo {643			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);644645			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);646647			dispatch_call::<T, _>(collection_id, |d| d.set_token_properties(sender, token_id, properties))648		}649650		#[weight = T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32)]651		#[transactional]652		pub fn delete_token_properties(653			origin,654			collection_id: CollectionId,655			token_id: TokenId,656			property_keys: Vec<PropertyKey>657		) -> DispatchResultWithPostInfo {658			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);659660			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);661662			dispatch_call::<T, _>(collection_id, |d| d.delete_token_properties(sender, token_id, property_keys))663		}664665		#[weight = T::CommonWeightInfo::set_property_permissions(property_permissions.len() as u32)]666		#[transactional]667		pub fn set_property_permissions(668			origin,669			collection_id: CollectionId,670			property_permissions: Vec<PropertyKeyPermission>,671		) -> DispatchResultWithPostInfo {672			ensure!(!property_permissions.is_empty(), Error::<T>::EmptyArgument);673674			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);675676			dispatch_call::<T, _>(collection_id, |d| d.set_property_permissions(&sender, property_permissions))677		}678679		#[weight = T::CommonWeightInfo::create_multiple_items_ex(&data)]680		#[transactional]681		pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {682			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);683			let budget = budget::Value::new(NESTING_BUDGET);684685			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))686		}687688		// TODO! transaction weight689690		/// Set transfers_enabled value for particular collection691		///692		/// # Permissions693		///694		/// * Collection Owner.695		///696		/// # Arguments697		///698		/// * collection_id: ID of the collection.699		///700		/// * value: New flag value.701		#[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]702		#[transactional]703		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {704			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);705			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;706			target_collection.check_is_owner(&sender)?;707708			// =========709710			target_collection.limits.transfers_enabled = Some(value);711			target_collection.save()712		}713714		/// Destroys a concrete instance of NFT.715		///716		/// # Permissions717		///718		/// * Collection Owner.719		/// * Collection Admin.720		/// * Current NFT Owner.721		///722		/// # Arguments723		///724		/// * collection_id: ID of the collection.725		///726		/// * item_id: ID of NFT to burn.727		#[weight = T::CommonWeightInfo::burn_item()]728		#[transactional]729		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {730			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);731732			let post_info = dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;733			if value == 1 {734				<NftTransferBasket<T>>::remove(collection_id, item_id);735				<NftApproveBasket<T>>::remove(collection_id, item_id);736			}737			// Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?738			// <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());739			// <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));740			Ok(post_info)741		}742743		/// Destroys a concrete instance of NFT on behalf of the owner744		/// See also: [`approve`]745		///746		/// # Permissions747		///748		/// * Collection Owner.749		/// * Collection Admin.750		/// * Current NFT Owner.751		///752		/// # Arguments753		///754		/// * collection_id: ID of the collection.755		///756		/// * item_id: ID of NFT to burn.757		///758		/// * from: owner of item759		#[weight = T::CommonWeightInfo::burn_from()]760		#[transactional]761		pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {762			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);763			let budget = budget::Value::new(NESTING_BUDGET);764765			dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value, &budget))766		}767768		/// Change ownership of the token.769		///770		/// # Permissions771		///772		/// * Collection Owner773		/// * Collection Admin774		/// * Current NFT owner775		///776		/// # Arguments777		///778		/// * recipient: Address of token recipient.779		///780		/// * collection_id.781		///782		/// * item_id: ID of the item783		///     * Non-Fungible Mode: Required.784		///     * Fungible Mode: Ignored.785		///     * Re-Fungible Mode: Required.786		///787		/// * value: Amount to transfer.788		///     * Non-Fungible Mode: Ignored789		///     * Fungible Mode: Must specify transferred amount790		///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)791		#[weight = T::CommonWeightInfo::transfer()]792		#[transactional]793		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {794			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);795			let budget = budget::Value::new(NESTING_BUDGET);796797			dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value, &budget))798		}799800		/// Set, change, or remove approved address to transfer the ownership of the NFT.801		///802		/// # Permissions803		///804		/// * Collection Owner805		/// * Collection Admin806		/// * Current NFT owner807		///808		/// # Arguments809		///810		/// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).811		///812		/// * collection_id.813		///814		/// * item_id: ID of the item.815		#[weight = T::CommonWeightInfo::approve()]816		#[transactional]817		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {818			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);819820			dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))821		}822823		/// 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.824		///825		/// # Permissions826		/// * Collection Owner827		/// * Collection Admin828		/// * Current NFT owner829		/// * Address approved by current NFT owner830		///831		/// # Arguments832		///833		/// * from: Address that owns token.834		///835		/// * recipient: Address of token recipient.836		///837		/// * collection_id.838		///839		/// * item_id: ID of the item.840		///841		/// * value: Amount to transfer.842		#[weight = T::CommonWeightInfo::transfer_from()]843		#[transactional]844		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {845			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);846			let budget = budget::Value::new(NESTING_BUDGET);847848			dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))849		}850851		#[weight = <SelfWeightOf<T>>::set_collection_limits()]852		#[transactional]853		pub fn set_collection_limits(854			origin,855			collection_id: CollectionId,856			new_limit: CollectionLimits,857		) -> DispatchResult {858			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);859			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;860			target_collection.check_is_owner(&sender)?;861			let old_limit = &target_collection.limits;862863			target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;864865			<Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(866				collection_id867			));868869			target_collection.save()870		}871872		#[weight = <SelfWeightOf<T>>::set_collection_limits()]873		#[transactional]874		pub fn set_collection_permissions(875			origin,876			collection_id: CollectionId,877			new_limit: CollectionPermissions,878		) -> DispatchResult {879			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);880			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;881			target_collection.check_is_owner(&sender)?;882			let old_limit = &target_collection.permissions;883884			target_collection.permissions = <PalletCommon<T>>::clamp_permissions(target_collection.mode.clone(), &old_limit, new_limit)?;885886			<Pallet<T>>::deposit_event(Event::<T>::CollectionPermissionSet(887				collection_id888			));889890			target_collection.save()891		}892	}893}