git.delta.rocks / unique-network / refs/commits / 7b3faa5ce573

difftreelog

source

pallets/unique/src/lib.rs29.5 KiBsourcehistory
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![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)]2425use frame_support::{26	decl_module, decl_storage, decl_error, decl_event,27	dispatch::DispatchResult,28	ensure,29	weights::{Weight},30	transactional,31	pallet_prelude::{DispatchResultWithPostInfo, ConstU32},32	BoundedVec,33};34use scale_info::TypeInfo;35use frame_system::{self as system, ensure_signed};36use sp_runtime::{sp_std::prelude::Vec};37use up_data_structs::{38	MAX_COLLECTION_NAME_LENGTH,39	MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH, AccessMode, CreateItemData,40	CollectionLimits, CollectionPermissions, CollectionId, CollectionMode, TokenId, SponsorshipState,41	CreateCollectionData, CreateItemExData, budget, Property, PropertyKey,42	PropertyKeyPermission,43};44use pallet_evm::account::CrossAccountId;45use pallet_common::{46	CollectionHandle, Pallet as PalletCommon, CommonWeightInfo, dispatch::dispatch_call,47	dispatch::CollectionDispatch,48};4950#[cfg(feature = "runtime-benchmarks")]51mod benchmarking;52pub mod weights;53use weights::WeightInfo;5455decl_error! {56	/// Error for non-fungible-token module.57	pub enum Error for Module<T: Config> {58		/// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.59		CollectionDecimalPointLimitExceeded,60		/// This address is not set as sponsor, use setCollectionSponsor first.61		ConfirmUnsetSponsorFail,62		/// Length of items properties must be greater than 0.63		EmptyArgument,64	}65}6667pub trait Config: system::Config + pallet_common::Config + Sized + TypeInfo {68	type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;6970	/// Weight information for extrinsics in this pallet.71	type WeightInfo: WeightInfo;72	type CommonWeightInfo: CommonWeightInfo<Self::CrossAccountId>;73}7475decl_event! {76	pub enum Event<T>77	where78		<T as frame_system::Config>::AccountId,79		<T as pallet_evm::account::Config>::CrossAccountId,80	{81		/// Collection sponsor was removed82		///83		/// # Arguments84		///85		/// * collection_id: Globally unique collection identifier.86		CollectionSponsorRemoved(CollectionId),8788		/// Collection admin was added89		///90		/// # Arguments91		///92		/// * collection_id: Globally unique collection identifier.93		///94		/// * admin:  Admin address.95		CollectionAdminAdded(CollectionId, CrossAccountId),9697		/// Collection owned was change98		///99		/// # Arguments100		///101		/// * collection_id: Globally unique collection identifier.102		///103		/// * owner:  New owner address.104		CollectionOwnedChanged(CollectionId, AccountId),105106		/// Collection sponsor was set107		///108		/// # Arguments109		///110		/// * collection_id: Globally unique collection identifier.111		///112		/// * owner:  New sponsor address.113		CollectionSponsorSet(CollectionId, AccountId),114115		/// const on chain schema was set116		///117		/// # Arguments118		///119		/// * collection_id: Globally unique collection identifier.120		ConstOnChainSchemaSet(CollectionId),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 remove from allow list141		///142		/// # Arguments143		///144		/// * collection_id: Globally unique collection identifier.145		///146		/// * user:  Address.147		AllowListAddressRemoved(CollectionId, CrossAccountId),148149		/// Address was add to allow list150		///151		/// # Arguments152		///153		/// * collection_id: Globally unique collection identifier.154		///155		/// * user:  Address.156		AllowListAddressAdded(CollectionId, CrossAccountId),157158		/// Collection limits was set159		///160		/// # Arguments161		///162		/// * collection_id: Globally unique collection identifier.163		CollectionLimitSet(CollectionId),164165		CollectionPermissionSet(CollectionId),166167		/// Mint permission	was set168		///169		/// # Arguments170		///171		/// * collection_id: Globally unique collection identifier.172		MintPermissionSet(CollectionId),173174		/// Offchain schema was set175		///176		/// # Arguments177		///178		/// * collection_id: Globally unique collection identifier.179		OffchainSchemaSet(CollectionId),180181		/// Public access mode was set182		///183		/// # Arguments184		///185		/// * collection_id: Globally unique collection identifier.186		///187		/// * mode: New access state.188		PublicAccessModeSet(CollectionId, AccessMode),189190		/// Schema version was set191		///192		/// # Arguments193		///194		/// * collection_id: Globally unique collection identifier.195		SchemaVersionSet(CollectionId),196	}197}198199type SelfWeightOf<T> = <T as Config>::WeightInfo;200201// # Used definitions202//203// ## User control levels204//205// chain-controlled - key is uncontrolled by user206//                    i.e autoincrementing index207//                    can use non-cryptographic hash208// real - key is controlled by user209//        but it is hard to generate enough colliding values, i.e owner of signed txs210//        can use non-cryptographic hash211// controlled - key is completly controlled by users212//              i.e maps with mutable keys213//              should use cryptographic hash214//215// ## User control level downgrade reasons216//217// ?1 - chain-controlled -> controlled218//      collections/tokens can be destroyed, resulting in massive holes219// ?2 - chain-controlled -> controlled220//      same as ?1, but can be only added, resulting in easier exploitation221// ?3 - real -> controlled222//      no confirmation required, so addresses can be easily generated223decl_storage! {224	trait Store for Module<T: Config> as Unique {225226		//#region Private members227		/// Used for migrations228		ChainVersion: u64;229		//#endregion230231		//#region Tokens transfer rate limit baskets232		/// (Collection id (controlled?2), who created (real))233		/// TODO: Off chain worker should remove from this map when collection gets removed234		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;235		/// Collection id (controlled?2), token id (controlled?2)236		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;237		/// Collection id (controlled?2), owning user (real)238		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;239		/// Collection id (controlled?2), token id (controlled?2)240		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>;241		//#endregion242243		/// Variable metadata sponsoring244		/// Collection id (controlled?2), token id (controlled?2)245		#[deprecated]246		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;247		pub TokenPropertyBasket get(fn token_property_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;248249		/// Approval sponsoring250		pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;251		pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;252		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>;253	}254}255256decl_module! {257	pub struct Module<T: Config> for enum Call258	where259		origin: T::Origin260	{261		type Error = Error<T>;262263		fn deposit_event() = default;264265		fn on_initialize(_now: T::BlockNumber) -> Weight {266			0267		}268269		fn on_runtime_upgrade() -> Weight {270			let limit = None;271272			<VariableMetaDataBasket<T>>::remove_all(limit);273274			0275		}276277		/// 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.278		///279		/// # Permissions280		///281		/// * Anyone.282		///283		/// # Arguments284		///285		/// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.286		///287		/// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.288		///289		/// * token_prefix: UTF-8 string with token prefix.290		///291		/// * mode: [CollectionMode] collection type and type dependent data.292		// returns collection ID293		#[weight = <SelfWeightOf<T>>::create_collection()]294		#[transactional]295		#[deprecated]296		pub fn create_collection(origin,297								 collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,298								 collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,299								 token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,300								 mode: CollectionMode) -> DispatchResult  {301			let data: CreateCollectionData<T::AccountId> = CreateCollectionData {302				name: collection_name,303				description: collection_description,304				token_prefix,305				mode,306				..Default::default()307			};308			Self::create_collection_ex(origin, data)309		}310311		/// This method creates a collection312		///313		/// Prefer it to deprecated [`created_collection`] method314		#[weight = <SelfWeightOf<T>>::create_collection()]315		#[transactional]316		pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {317			let sender = ensure_signed(origin)?;318319			// =========320321			T::CollectionDispatch::create(sender, data)?;322323			Ok(())324		}325326		/// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.327		///328		/// # Permissions329		///330		/// * Collection Owner.331		///332		/// # Arguments333		///334		/// * collection_id: collection to destroy.335		#[weight = <SelfWeightOf<T>>::destroy_collection()]336		#[transactional]337		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {338			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);339			let collection = <CollectionHandle<T>>::try_get(collection_id)?;340341			// =========342343			T::CollectionDispatch::destroy(sender, collection)?;344345			<NftTransferBasket<T>>::remove_prefix(collection_id, None);346			<FungibleTransferBasket<T>>::remove_prefix(collection_id, None);347			<ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);348349			<NftApproveBasket<T>>::remove_prefix(collection_id, None);350			<FungibleApproveBasket<T>>::remove_prefix(collection_id, None);351			<RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);352353			Ok(())354		}355356		/// Add an address to allow list.357		///358		/// # Permissions359		///360		/// * Collection Owner361		/// * Collection Admin362		///363		/// # Arguments364		///365		/// * collection_id.366		///367		/// * address.368		#[weight = <SelfWeightOf<T>>::add_to_allow_list()]369		#[transactional]370		pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{371372			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);373			let collection = <CollectionHandle<T>>::try_get(collection_id)?;374375			<PalletCommon<T>>::toggle_allowlist(376				&collection,377				&sender,378				&address,379				true,380			)?;381382			Self::deposit_event(Event::<T>::AllowListAddressAdded(383				collection_id,384				address385			));386387			Ok(())388		}389390		/// Remove an address from allow list.391		///392		/// # Permissions393		///394		/// * Collection Owner395		/// * Collection Admin396		///397		/// # Arguments398		///399		/// * collection_id.400		///401		/// * address.402		#[weight = <SelfWeightOf<T>>::remove_from_allow_list()]403		#[transactional]404		pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{405406			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);407			let collection = <CollectionHandle<T>>::try_get(collection_id)?;408409			<PalletCommon<T>>::toggle_allowlist(410				&collection,411				&sender,412				&address,413				false,414			)?;415416			<Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(417				collection_id,418				address419			));420421			Ok(())422		}423424		/// Change the owner of the collection.425		///426		/// # Permissions427		///428		/// * Collection Owner.429		///430		/// # Arguments431		///432		/// * collection_id.433		///434		/// * new_owner.435		#[weight = <SelfWeightOf<T>>::change_collection_owner()]436		#[transactional]437		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {438439			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);440441			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;442			target_collection.check_is_owner(&sender)?;443444			target_collection.owner = new_owner.clone();445			<Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(446				collection_id,447				new_owner448			));449450			target_collection.save()451		}452453		/// Adds an admin of the Collection.454		/// 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.455		///456		/// # Permissions457		///458		/// * Collection Owner.459		/// * Collection Admin.460		///461		/// # Arguments462		///463		/// * collection_id: ID of the Collection to add admin for.464		///465		/// * new_admin_id: Address of new admin to add.466		#[weight = <SelfWeightOf<T>>::add_collection_admin()]467		#[transactional]468		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {469			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);470			let collection = <CollectionHandle<T>>::try_get(collection_id)?;471472			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(473				collection_id,474				new_admin_id.clone()475			));476477			<PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)478		}479480		/// 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.481		///482		/// # Permissions483		///484		/// * Collection Owner.485		/// * Collection Admin.486		///487		/// # Arguments488		///489		/// * collection_id: ID of the Collection to remove admin for.490		///491		/// * account_id: Address of admin to remove.492		#[weight = <SelfWeightOf<T>>::remove_collection_admin()]493		#[transactional]494		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {495			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);496			let collection = <CollectionHandle<T>>::try_get(collection_id)?;497498			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(499				collection_id,500				account_id.clone()501			));502503			<PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)504		}505506		/// # Permissions507		///508		/// * Collection Owner509		///510		/// # Arguments511		///512		/// * collection_id.513		///514		/// * new_sponsor.515		#[weight = <SelfWeightOf<T>>::set_collection_sponsor()]516		#[transactional]517		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {518			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);519520			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;521			target_collection.check_is_owner(&sender)?;522523			target_collection.set_sponsor(new_sponsor.clone());524525			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(526				collection_id,527				new_sponsor528			));529530			target_collection.save()531		}532533		/// # Permissions534		///535		/// * Sponsor.536		///537		/// # Arguments538		///539		/// * collection_id.540		#[weight = <SelfWeightOf<T>>::confirm_sponsorship()]541		#[transactional]542		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {543			let sender = ensure_signed(origin)?;544545			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;546			ensure!(547				target_collection.confirm_sponsorship(&sender),548				Error::<T>::ConfirmUnsetSponsorFail549			);550551			<Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(552				collection_id,553				sender554			));555556			target_collection.save()557		}558559		/// Switch back to pay-per-own-transaction model.560		///561		/// # Permissions562		///563		/// * Collection owner.564		///565		/// # Arguments566		///567		/// * collection_id.568		#[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]569		#[transactional]570		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {571			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);572573			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;574			target_collection.check_is_owner(&sender)?;575576			target_collection.sponsorship = SponsorshipState::Disabled;577578			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(579				collection_id580			));581			target_collection.save()582		}583584		/// This method creates a concrete instance of NFT Collection created with CreateCollection method.585		///586		/// # Permissions587		///588		/// * Collection Owner.589		/// * Collection Admin.590		/// * Anyone if591		///     * Allow List is enabled, and592		///     * Address is added to allow list, and593		///     * MintPermission is enabled (see SetMintPermission method)594		///595		/// # Arguments596		///597		/// * collection_id: ID of the collection.598		///599		/// * owner: Address, initial owner of the NFT.600		///601		/// * data: Token data to store on chain.602		#[weight = T::CommonWeightInfo::create_item()]603		#[transactional]604		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {605			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);606			let budget = budget::Value::new(2);607608			dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))609		}610611		/// This method creates multiple items in a collection created with CreateCollection method.612		///613		/// # Permissions614		///615		/// * Collection Owner.616		/// * Collection Admin.617		/// * Anyone if618		///     * Allow List is enabled, and619		///     * Address is added to allow list, and620		///     * MintPermission is enabled (see SetMintPermission method)621		///622		/// # Arguments623		///624		/// * collection_id: ID of the collection.625		///626		/// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].627		///628		/// * owner: Address, initial owner of the NFT.629		#[weight = T::CommonWeightInfo::create_multiple_items(&items_data)]630		#[transactional]631		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {632			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);633			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);634			let budget = budget::Value::new(2);635636			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))637		}638639		#[weight = T::CommonWeightInfo::set_collection_properties(properties.len() as u32)]640		#[transactional]641		pub fn set_collection_properties(642			origin,643			collection_id: CollectionId,644			properties: Vec<Property>645		) -> DispatchResultWithPostInfo {646			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);647648			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);649650			dispatch_call::<T, _>(collection_id, |d| d.set_collection_properties(sender, properties))651		}652653		#[weight = T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32)]654		#[transactional]655		pub fn delete_collection_properties(656			origin,657			collection_id: CollectionId,658			property_keys: Vec<PropertyKey>,659		) -> DispatchResultWithPostInfo {660			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);661662			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);663664			dispatch_call::<T, _>(collection_id, |d| d.delete_collection_properties(&sender, property_keys))665		}666667		#[weight = T::CommonWeightInfo::set_token_properties(properties.len() as u32)]668		#[transactional]669		pub fn set_token_properties(670			origin,671			collection_id: CollectionId,672			token_id: TokenId,673			properties: Vec<Property>674		) -> DispatchResultWithPostInfo {675			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);676677			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);678679			dispatch_call::<T, _>(collection_id, |d| d.set_token_properties(sender, token_id, properties))680		}681682		#[weight = T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32)]683		#[transactional]684		pub fn delete_token_properties(685			origin,686			collection_id: CollectionId,687			token_id: TokenId,688			property_keys: Vec<PropertyKey>689		) -> DispatchResultWithPostInfo {690			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);691692			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);693694			dispatch_call::<T, _>(collection_id, |d| d.delete_token_properties(sender, token_id, property_keys))695		}696697		#[weight = T::CommonWeightInfo::set_property_permissions(property_permissions.len() as u32)]698		#[transactional]699		pub fn set_property_permissions(700			origin,701			collection_id: CollectionId,702			property_permissions: Vec<PropertyKeyPermission>,703		) -> DispatchResultWithPostInfo {704			ensure!(!property_permissions.is_empty(), Error::<T>::EmptyArgument);705706			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);707708			dispatch_call::<T, _>(collection_id, |d| d.set_property_permissions(&sender, property_permissions))709		}710711		#[weight = T::CommonWeightInfo::create_multiple_items_ex(&data)]712		#[transactional]713		pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {714			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);715			let budget = budget::Value::new(2);716717			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))718		}719720		// TODO! transaction weight721722		/// Set transfers_enabled value for particular collection723		///724		/// # Permissions725		///726		/// * Collection Owner.727		///728		/// # Arguments729		///730		/// * collection_id: ID of the collection.731		///732		/// * value: New flag value.733		#[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]734		#[transactional]735		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {736			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);737			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;738			target_collection.check_is_owner(&sender)?;739740			// =========741742			target_collection.limits.transfers_enabled = Some(value);743			target_collection.save()744		}745746		/// Destroys a concrete instance of NFT.747		///748		/// # Permissions749		///750		/// * Collection Owner.751		/// * Collection Admin.752		/// * Current NFT Owner.753		///754		/// # Arguments755		///756		/// * collection_id: ID of the collection.757		///758		/// * item_id: ID of NFT to burn.759		#[weight = T::CommonWeightInfo::burn_item()]760		#[transactional]761		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {762			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);763764			let post_info = dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;765			if value == 1 {766				<NftTransferBasket<T>>::remove(collection_id, item_id);767				<NftApproveBasket<T>>::remove(collection_id, item_id);768			}769			// Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?770			// <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());771			// <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));772			Ok(post_info)773		}774775		/// Destroys a concrete instance of NFT on behalf of the owner776		/// See also: [`approve`]777		///778		/// # Permissions779		///780		/// * Collection Owner.781		/// * Collection Admin.782		/// * Current NFT Owner.783		///784		/// # Arguments785		///786		/// * collection_id: ID of the collection.787		///788		/// * item_id: ID of NFT to burn.789		///790		/// * from: owner of item791		#[weight = T::CommonWeightInfo::burn_from()]792		#[transactional]793		pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {794			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);795			let budget = budget::Value::new(2);796797			dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value, &budget))798		}799800		/// Change ownership of the token.801		///802		/// # Permissions803		///804		/// * Collection Owner805		/// * Collection Admin806		/// * Current NFT owner807		///808		/// # Arguments809		///810		/// * recipient: Address of token recipient.811		///812		/// * collection_id.813		///814		/// * item_id: ID of the item815		///     * Non-Fungible Mode: Required.816		///     * Fungible Mode: Ignored.817		///     * Re-Fungible Mode: Required.818		///819		/// * value: Amount to transfer.820		///     * Non-Fungible Mode: Ignored821		///     * Fungible Mode: Must specify transferred amount822		///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)823		#[weight = T::CommonWeightInfo::transfer()]824		#[transactional]825		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {826			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);827			let budget = budget::Value::new(2);828829			dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value, &budget))830		}831832		/// Set, change, or remove approved address to transfer the ownership of the NFT.833		///834		/// # Permissions835		///836		/// * Collection Owner837		/// * Collection Admin838		/// * Current NFT owner839		///840		/// # Arguments841		///842		/// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).843		///844		/// * collection_id.845		///846		/// * item_id: ID of the item.847		#[weight = T::CommonWeightInfo::approve()]848		#[transactional]849		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {850			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);851852			dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))853		}854855		/// 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.856		///857		/// # Permissions858		/// * Collection Owner859		/// * Collection Admin860		/// * Current NFT owner861		/// * Address approved by current NFT owner862		///863		/// # Arguments864		///865		/// * from: Address that owns token.866		///867		/// * recipient: Address of token recipient.868		///869		/// * collection_id.870		///871		/// * item_id: ID of the item.872		///873		/// * value: Amount to transfer.874		#[weight = T::CommonWeightInfo::transfer_from()]875		#[transactional]876		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {877			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);878			let budget = budget::Value::new(2);879880			dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))881		}882883		#[weight = <SelfWeightOf<T>>::set_collection_limits()]884		#[transactional]885		pub fn set_collection_limits(886			origin,887			collection_id: CollectionId,888			new_limit: CollectionLimits,889		) -> DispatchResult {890			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);891			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;892			target_collection.check_is_owner(&sender)?;893			let old_limit = &target_collection.limits;894895			target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;896897			<Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(898				collection_id899			));900901			target_collection.save()902		}903904		#[weight = <SelfWeightOf<T>>::set_collection_limits()]905		#[transactional]906		pub fn set_collection_permissions(907			origin,908			collection_id: CollectionId,909			new_limit: CollectionPermissions,910		) -> DispatchResult {911			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);912			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;913			target_collection.check_is_owner(&sender)?;914			let old_limit = &target_collection.permissions;915916			target_collection.permissions = <PalletCommon<T>>::clamp_permissions(target_collection.mode.clone(), &old_limit, new_limit)?;917918			<Pallet<T>>::deposit_event(Event::<T>::CollectionPermissionSet(919				collection_id920			));921922			target_collection.save()923		}924	}925}