git.delta.rocks / unique-network / refs/commits / 77688fbffa89

difftreelog

source

pallets/unique/src/lib.rs29.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)]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 =336			<SelfWeightOf<T>>::destroy_collection()337			+ <SelfWeightOf<T>>::burn_children_in_collection(*max_children_to_burn)338		]339		#[transactional]340		pub fn destroy_collection(341			origin,342			collection_id: CollectionId,343			max_children_to_burn: u32,344		) -> DispatchResult {345			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);346			let collection = <CollectionHandle<T>>::try_get(collection_id)?;347348			let budget = budget::Value::new(max_children_to_burn);349350			// =========351352			T::CollectionDispatch::destroy(sender, collection, &budget)?;353354			<NftTransferBasket<T>>::remove_prefix(collection_id, None);355			<FungibleTransferBasket<T>>::remove_prefix(collection_id, None);356			<ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);357358			<NftApproveBasket<T>>::remove_prefix(collection_id, None);359			<FungibleApproveBasket<T>>::remove_prefix(collection_id, None);360			<RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);361362			Ok(())363		}364365		/// Add an address to allow list.366		///367		/// # Permissions368		///369		/// * Collection Owner370		/// * Collection Admin371		///372		/// # Arguments373		///374		/// * collection_id.375		///376		/// * address.377		#[weight = <SelfWeightOf<T>>::add_to_allow_list()]378		#[transactional]379		pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{380381			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);382			let collection = <CollectionHandle<T>>::try_get(collection_id)?;383384			<PalletCommon<T>>::toggle_allowlist(385				&collection,386				&sender,387				&address,388				true,389			)?;390391			Self::deposit_event(Event::<T>::AllowListAddressAdded(392				collection_id,393				address394			));395396			Ok(())397		}398399		/// Remove an address from allow list.400		///401		/// # Permissions402		///403		/// * Collection Owner404		/// * Collection Admin405		///406		/// # Arguments407		///408		/// * collection_id.409		///410		/// * address.411		#[weight = <SelfWeightOf<T>>::remove_from_allow_list()]412		#[transactional]413		pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{414415			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);416			let collection = <CollectionHandle<T>>::try_get(collection_id)?;417418			<PalletCommon<T>>::toggle_allowlist(419				&collection,420				&sender,421				&address,422				false,423			)?;424425			<Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(426				collection_id,427				address428			));429430			Ok(())431		}432433		/// Change the owner of the collection.434		///435		/// # Permissions436		///437		/// * Collection Owner.438		///439		/// # Arguments440		///441		/// * collection_id.442		///443		/// * new_owner.444		#[weight = <SelfWeightOf<T>>::change_collection_owner()]445		#[transactional]446		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {447448			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);449450			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;451			target_collection.check_is_owner(&sender)?;452453			target_collection.owner = new_owner.clone();454			<Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(455				collection_id,456				new_owner457			));458459			target_collection.save()460		}461462		/// Adds an admin of the Collection.463		/// 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.464		///465		/// # Permissions466		///467		/// * Collection Owner.468		/// * Collection Admin.469		///470		/// # Arguments471		///472		/// * collection_id: ID of the Collection to add admin for.473		///474		/// * new_admin_id: Address of new admin to add.475		#[weight = <SelfWeightOf<T>>::add_collection_admin()]476		#[transactional]477		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {478			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);479			let collection = <CollectionHandle<T>>::try_get(collection_id)?;480481			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(482				collection_id,483				new_admin_id.clone()484			));485486			<PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)487		}488489		/// 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.490		///491		/// # Permissions492		///493		/// * Collection Owner.494		/// * Collection Admin.495		///496		/// # Arguments497		///498		/// * collection_id: ID of the Collection to remove admin for.499		///500		/// * account_id: Address of admin to remove.501		#[weight = <SelfWeightOf<T>>::remove_collection_admin()]502		#[transactional]503		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {504			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);505			let collection = <CollectionHandle<T>>::try_get(collection_id)?;506507			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(508				collection_id,509				account_id.clone()510			));511512			<PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)513		}514515		/// # Permissions516		///517		/// * Collection Owner518		///519		/// # Arguments520		///521		/// * collection_id.522		///523		/// * new_sponsor.524		#[weight = <SelfWeightOf<T>>::set_collection_sponsor()]525		#[transactional]526		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {527			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);528529			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;530			target_collection.check_is_owner(&sender)?;531532			target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor.clone());533534			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(535				collection_id,536				new_sponsor537			));538539			target_collection.save()540		}541542		/// # Permissions543		///544		/// * Sponsor.545		///546		/// # Arguments547		///548		/// * collection_id.549		#[weight = <SelfWeightOf<T>>::confirm_sponsorship()]550		#[transactional]551		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {552			let sender = ensure_signed(origin)?;553554			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;555			ensure!(556				target_collection.sponsorship.pending_sponsor() == Some(&sender),557				Error::<T>::ConfirmUnsetSponsorFail558			);559560			target_collection.sponsorship = SponsorshipState::Confirmed(sender.clone());561562			<Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(563				collection_id,564				sender565			));566567			target_collection.save()568		}569570		/// Switch back to pay-per-own-transaction model.571		///572		/// # Permissions573		///574		/// * Collection owner.575		///576		/// # Arguments577		///578		/// * collection_id.579		#[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]580		#[transactional]581		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {582			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);583584			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;585			target_collection.check_is_owner(&sender)?;586587			target_collection.sponsorship = SponsorshipState::Disabled;588589			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(590				collection_id591			));592			target_collection.save()593		}594595		/// This method creates a concrete instance of NFT Collection created with CreateCollection method.596		///597		/// # Permissions598		///599		/// * Collection Owner.600		/// * Collection Admin.601		/// * Anyone if602		///     * Allow List is enabled, and603		///     * Address is added to allow list, and604		///     * MintPermission is enabled (see SetMintPermission method)605		///606		/// # Arguments607		///608		/// * collection_id: ID of the collection.609		///610		/// * owner: Address, initial owner of the NFT.611		///612		/// * data: Token data to store on chain.613		#[weight = T::CommonWeightInfo::create_item()]614		#[transactional]615		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {616			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);617			let budget = budget::Value::new(2);618619			dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))620		}621622		/// This method creates multiple items in a collection created with CreateCollection method.623		///624		/// # Permissions625		///626		/// * Collection Owner.627		/// * Collection Admin.628		/// * Anyone if629		///     * Allow List is enabled, and630		///     * Address is added to allow list, and631		///     * MintPermission is enabled (see SetMintPermission method)632		///633		/// # Arguments634		///635		/// * collection_id: ID of the collection.636		///637		/// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].638		///639		/// * owner: Address, initial owner of the NFT.640		#[weight = T::CommonWeightInfo::create_multiple_items(&items_data)]641		#[transactional]642		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {643			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);644			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);645			let budget = budget::Value::new(2);646647			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))648		}649650		#[weight = T::CommonWeightInfo::set_collection_properties(properties.len() as u32)]651		#[transactional]652		pub fn set_collection_properties(653			origin,654			collection_id: CollectionId,655			properties: Vec<Property>656		) -> DispatchResultWithPostInfo {657			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);658659			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);660661			dispatch_call::<T, _>(collection_id, |d| d.set_collection_properties(sender, properties))662		}663664		#[weight = T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32)]665		#[transactional]666		pub fn delete_collection_properties(667			origin,668			collection_id: CollectionId,669			property_keys: Vec<PropertyKey>,670		) -> DispatchResultWithPostInfo {671			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);672673			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);674675			dispatch_call::<T, _>(collection_id, |d| d.delete_collection_properties(&sender, property_keys))676		}677678		#[weight = T::CommonWeightInfo::set_token_properties(properties.len() as u32)]679		#[transactional]680		pub fn set_token_properties(681			origin,682			collection_id: CollectionId,683			token_id: TokenId,684			properties: Vec<Property>685		) -> DispatchResultWithPostInfo {686			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);687688			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);689690			dispatch_call::<T, _>(collection_id, |d| d.set_token_properties(sender, token_id, properties))691		}692693		#[weight = T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32)]694		#[transactional]695		pub fn delete_token_properties(696			origin,697			collection_id: CollectionId,698			token_id: TokenId,699			property_keys: Vec<PropertyKey>700		) -> DispatchResultWithPostInfo {701			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);702703			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);704705			dispatch_call::<T, _>(collection_id, |d| d.delete_token_properties(sender, token_id, property_keys))706		}707708		#[weight = T::CommonWeightInfo::set_property_permissions(property_permissions.len() as u32)]709		#[transactional]710		pub fn set_property_permissions(711			origin,712			collection_id: CollectionId,713			property_permissions: Vec<PropertyKeyPermission>,714		) -> DispatchResultWithPostInfo {715			ensure!(!property_permissions.is_empty(), Error::<T>::EmptyArgument);716717			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);718719			dispatch_call::<T, _>(collection_id, |d| d.set_property_permissions(&sender, property_permissions))720		}721722		#[weight = T::CommonWeightInfo::create_multiple_items_ex(&data)]723		#[transactional]724		pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {725			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);726			let budget = budget::Value::new(2);727728			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))729		}730731		// TODO! transaction weight732733		/// Set transfers_enabled value for particular collection734		///735		/// # Permissions736		///737		/// * Collection Owner.738		///739		/// # Arguments740		///741		/// * collection_id: ID of the collection.742		///743		/// * value: New flag value.744		#[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]745		#[transactional]746		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {747			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);748			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;749			target_collection.check_is_owner(&sender)?;750751			// =========752753			target_collection.limits.transfers_enabled = Some(value);754			target_collection.save()755		}756757		/// Destroys a concrete instance of NFT.758		///759		/// # Permissions760		///761		/// * Collection Owner.762		/// * Collection Admin.763		/// * Current NFT Owner.764		///765		/// # Arguments766		///767		/// * collection_id: ID of the collection.768		///769		/// * item_id: ID of NFT to burn.770		#[weight = T::CommonWeightInfo::burn_item()]771		#[transactional]772		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {773			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);774775			let post_info = dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;776			if value == 1 {777				<NftTransferBasket<T>>::remove(collection_id, item_id);778				<NftApproveBasket<T>>::remove(collection_id, item_id);779			}780			// Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?781			// <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());782			// <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));783			Ok(post_info)784		}785786		/// Destroys a concrete instance of NFT on behalf of the owner787		/// See also: [`approve`]788		///789		/// # Permissions790		///791		/// * Collection Owner.792		/// * Collection Admin.793		/// * Current NFT Owner.794		///795		/// # Arguments796		///797		/// * collection_id: ID of the collection.798		///799		/// * item_id: ID of NFT to burn.800		///801		/// * from: owner of item802		#[weight = T::CommonWeightInfo::burn_from()]803		#[transactional]804		pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {805			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);806			let budget = budget::Value::new(2);807808			dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value, &budget))809		}810811		/// Change ownership of the token.812		///813		/// # Permissions814		///815		/// * Collection Owner816		/// * Collection Admin817		/// * Current NFT owner818		///819		/// # Arguments820		///821		/// * recipient: Address of token recipient.822		///823		/// * collection_id.824		///825		/// * item_id: ID of the item826		///     * Non-Fungible Mode: Required.827		///     * Fungible Mode: Ignored.828		///     * Re-Fungible Mode: Required.829		///830		/// * value: Amount to transfer.831		///     * Non-Fungible Mode: Ignored832		///     * Fungible Mode: Must specify transferred amount833		///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)834		#[weight = T::CommonWeightInfo::transfer()]835		#[transactional]836		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {837			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);838			let budget = budget::Value::new(2);839840			dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value, &budget))841		}842843		/// Set, change, or remove approved address to transfer the ownership of the NFT.844		///845		/// # Permissions846		///847		/// * Collection Owner848		/// * Collection Admin849		/// * Current NFT owner850		///851		/// # Arguments852		///853		/// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).854		///855		/// * collection_id.856		///857		/// * item_id: ID of the item.858		#[weight = T::CommonWeightInfo::approve()]859		#[transactional]860		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {861			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);862863			dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))864		}865866		/// 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.867		///868		/// # Permissions869		/// * Collection Owner870		/// * Collection Admin871		/// * Current NFT owner872		/// * Address approved by current NFT owner873		///874		/// # Arguments875		///876		/// * from: Address that owns token.877		///878		/// * recipient: Address of token recipient.879		///880		/// * collection_id.881		///882		/// * item_id: ID of the item.883		///884		/// * value: Amount to transfer.885		#[weight = T::CommonWeightInfo::transfer_from()]886		#[transactional]887		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {888			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);889			let budget = budget::Value::new(2);890891			dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))892		}893894		#[weight = <SelfWeightOf<T>>::set_collection_limits()]895		#[transactional]896		pub fn set_collection_limits(897			origin,898			collection_id: CollectionId,899			new_limit: CollectionLimits,900		) -> DispatchResult {901			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);902			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;903			target_collection.check_is_owner(&sender)?;904			let old_limit = &target_collection.limits;905906			target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;907908			<Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(909				collection_id910			));911912			target_collection.save()913		}914915		#[weight = <SelfWeightOf<T>>::set_collection_limits()]916		#[transactional]917		pub fn set_collection_permissions(918			origin,919			collection_id: CollectionId,920			new_limit: CollectionPermissions,921		) -> DispatchResult {922			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);923			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;924			target_collection.check_is_owner(&sender)?;925			let old_limit = &target_collection.permissions;926927			target_collection.permissions = <PalletCommon<T>>::clamp_permissions(target_collection.mode.clone(), &old_limit, new_limit)?;928929			<Pallet<T>>::deposit_event(Event::<T>::CollectionPermissionSet(930				collection_id931			));932933			target_collection.save()934		}935	}936}