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

difftreelog

source

pallets/unique/src/lib.rs29.6 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.sponsorship = SponsorshipState::Unconfirmed(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.sponsorship.pending_sponsor() == Some(&sender),548				Error::<T>::ConfirmUnsetSponsorFail549			);550551			target_collection.sponsorship = SponsorshipState::Confirmed(sender.clone());552553			<Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(554				collection_id,555				sender556			));557558			target_collection.save()559		}560561		/// Switch back to pay-per-own-transaction model.562		///563		/// # Permissions564		///565		/// * Collection owner.566		///567		/// # Arguments568		///569		/// * collection_id.570		#[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]571		#[transactional]572		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {573			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);574575			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;576			target_collection.check_is_owner(&sender)?;577578			target_collection.sponsorship = SponsorshipState::Disabled;579580			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(581				collection_id582			));583			target_collection.save()584		}585586		/// This method creates a concrete instance of NFT Collection created with CreateCollection method.587		///588		/// # Permissions589		///590		/// * Collection Owner.591		/// * Collection Admin.592		/// * Anyone if593		///     * Allow List is enabled, and594		///     * Address is added to allow list, and595		///     * MintPermission is enabled (see SetMintPermission method)596		///597		/// # Arguments598		///599		/// * collection_id: ID of the collection.600		///601		/// * owner: Address, initial owner of the NFT.602		///603		/// * data: Token data to store on chain.604		#[weight = T::CommonWeightInfo::create_item()]605		#[transactional]606		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {607			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);608			let budget = budget::Value::new(2);609610			dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))611		}612613		/// This method creates multiple items in a collection created with CreateCollection method.614		///615		/// # Permissions616		///617		/// * Collection Owner.618		/// * Collection Admin.619		/// * Anyone if620		///     * Allow List is enabled, and621		///     * Address is added to allow list, and622		///     * MintPermission is enabled (see SetMintPermission method)623		///624		/// # Arguments625		///626		/// * collection_id: ID of the collection.627		///628		/// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].629		///630		/// * owner: Address, initial owner of the NFT.631		#[weight = T::CommonWeightInfo::create_multiple_items(&items_data)]632		#[transactional]633		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {634			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);635			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);636			let budget = budget::Value::new(2);637638			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))639		}640641		#[weight = T::CommonWeightInfo::set_collection_properties(properties.len() as u32)]642		#[transactional]643		pub fn set_collection_properties(644			origin,645			collection_id: CollectionId,646			properties: Vec<Property>647		) -> DispatchResultWithPostInfo {648			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);649650			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);651652			dispatch_call::<T, _>(collection_id, |d| d.set_collection_properties(sender, properties))653		}654655		#[weight = T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32)]656		#[transactional]657		pub fn delete_collection_properties(658			origin,659			collection_id: CollectionId,660			property_keys: Vec<PropertyKey>,661		) -> DispatchResultWithPostInfo {662			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);663664			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);665666			dispatch_call::<T, _>(collection_id, |d| d.delete_collection_properties(&sender, property_keys))667		}668669		#[weight = T::CommonWeightInfo::set_token_properties(properties.len() as u32)]670		#[transactional]671		pub fn set_token_properties(672			origin,673			collection_id: CollectionId,674			token_id: TokenId,675			properties: Vec<Property>676		) -> DispatchResultWithPostInfo {677			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);678679			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);680681			dispatch_call::<T, _>(collection_id, |d| d.set_token_properties(sender, token_id, properties))682		}683684		#[weight = T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32)]685		#[transactional]686		pub fn delete_token_properties(687			origin,688			collection_id: CollectionId,689			token_id: TokenId,690			property_keys: Vec<PropertyKey>691		) -> DispatchResultWithPostInfo {692			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);693694			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);695696			dispatch_call::<T, _>(collection_id, |d| d.delete_token_properties(sender, token_id, property_keys))697		}698699		#[weight = T::CommonWeightInfo::set_property_permissions(property_permissions.len() as u32)]700		#[transactional]701		pub fn set_property_permissions(702			origin,703			collection_id: CollectionId,704			property_permissions: Vec<PropertyKeyPermission>,705		) -> DispatchResultWithPostInfo {706			ensure!(!property_permissions.is_empty(), Error::<T>::EmptyArgument);707708			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);709710			dispatch_call::<T, _>(collection_id, |d| d.set_property_permissions(&sender, property_permissions))711		}712713		#[weight = T::CommonWeightInfo::create_multiple_items_ex(&data)]714		#[transactional]715		pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {716			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);717			let budget = budget::Value::new(2);718719			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))720		}721722		// TODO! transaction weight723724		/// Set transfers_enabled value for particular collection725		///726		/// # Permissions727		///728		/// * Collection Owner.729		///730		/// # Arguments731		///732		/// * collection_id: ID of the collection.733		///734		/// * value: New flag value.735		#[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]736		#[transactional]737		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {738			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);739			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;740			target_collection.check_is_owner(&sender)?;741742			// =========743744			target_collection.limits.transfers_enabled = Some(value);745			target_collection.save()746		}747748		/// Destroys a concrete instance of NFT.749		///750		/// # Permissions751		///752		/// * Collection Owner.753		/// * Collection Admin.754		/// * Current NFT Owner.755		///756		/// # Arguments757		///758		/// * collection_id: ID of the collection.759		///760		/// * item_id: ID of NFT to burn.761		#[weight = T::CommonWeightInfo::burn_item()]762		#[transactional]763		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {764			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);765766			let post_info = dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;767			if value == 1 {768				<NftTransferBasket<T>>::remove(collection_id, item_id);769				<NftApproveBasket<T>>::remove(collection_id, item_id);770			}771			// Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?772			// <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());773			// <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));774			Ok(post_info)775		}776777		/// Destroys a concrete instance of NFT on behalf of the owner778		/// See also: [`approve`]779		///780		/// # Permissions781		///782		/// * Collection Owner.783		/// * Collection Admin.784		/// * Current NFT Owner.785		///786		/// # Arguments787		///788		/// * collection_id: ID of the collection.789		///790		/// * item_id: ID of NFT to burn.791		///792		/// * from: owner of item793		#[weight = T::CommonWeightInfo::burn_from()]794		#[transactional]795		pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {796			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);797			let budget = budget::Value::new(2);798799			dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value, &budget))800		}801802		/// Change ownership of the token.803		///804		/// # Permissions805		///806		/// * Collection Owner807		/// * Collection Admin808		/// * Current NFT owner809		///810		/// # Arguments811		///812		/// * recipient: Address of token recipient.813		///814		/// * collection_id.815		///816		/// * item_id: ID of the item817		///     * Non-Fungible Mode: Required.818		///     * Fungible Mode: Ignored.819		///     * Re-Fungible Mode: Required.820		///821		/// * value: Amount to transfer.822		///     * Non-Fungible Mode: Ignored823		///     * Fungible Mode: Must specify transferred amount824		///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)825		#[weight = T::CommonWeightInfo::transfer()]826		#[transactional]827		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {828			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);829			let budget = budget::Value::new(2);830831			dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value, &budget))832		}833834		/// Set, change, or remove approved address to transfer the ownership of the NFT.835		///836		/// # Permissions837		///838		/// * Collection Owner839		/// * Collection Admin840		/// * Current NFT owner841		///842		/// # Arguments843		///844		/// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).845		///846		/// * collection_id.847		///848		/// * item_id: ID of the item.849		#[weight = T::CommonWeightInfo::approve()]850		#[transactional]851		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {852			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);853854			dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))855		}856857		/// 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.858		///859		/// # Permissions860		/// * Collection Owner861		/// * Collection Admin862		/// * Current NFT owner863		/// * Address approved by current NFT owner864		///865		/// # Arguments866		///867		/// * from: Address that owns token.868		///869		/// * recipient: Address of token recipient.870		///871		/// * collection_id.872		///873		/// * item_id: ID of the item.874		///875		/// * value: Amount to transfer.876		#[weight = T::CommonWeightInfo::transfer_from()]877		#[transactional]878		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {879			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);880			let budget = budget::Value::new(2);881882			dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))883		}884885		#[weight = <SelfWeightOf<T>>::set_collection_limits()]886		#[transactional]887		pub fn set_collection_limits(888			origin,889			collection_id: CollectionId,890			new_limit: CollectionLimits,891		) -> DispatchResult {892			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);893			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;894			target_collection.check_is_owner(&sender)?;895			let old_limit = &target_collection.limits;896897			target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;898899			<Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(900				collection_id901			));902903			target_collection.save()904		}905906		#[weight = <SelfWeightOf<T>>::set_collection_limits()]907		#[transactional]908		pub fn set_collection_permissions(909			origin,910			collection_id: CollectionId,911			new_limit: CollectionPermissions,912		) -> DispatchResult {913			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);914			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;915			target_collection.check_is_owner(&sender)?;916			let old_limit = &target_collection.permissions;917918			target_collection.permissions = <PalletCommon<T>>::clamp_permissions(target_collection.mode.clone(), &old_limit, new_limit)?;919920			<Pallet<T>>::deposit_event(Event::<T>::CollectionPermissionSet(921				collection_id922			));923924			target_collection.save()925		}926	}927}