git.delta.rocks / unique-network / refs/commits / 3db55eb8a3c0

difftreelog

source

pallets/unique/src/lib.rs33.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)]2425extern crate alloc;2627pub use serde::{Serialize, Deserialize};2829use frame_support::{30	decl_module, decl_storage, decl_error, decl_event,31	dispatch::DispatchResult,32	ensure,33	weights::{Weight},34	transactional,35	pallet_prelude::{DispatchResultWithPostInfo, ConstU32},36	BoundedVec,37};38use scale_info::TypeInfo;39use frame_system::{self as system, ensure_signed};40use sp_runtime::{sp_std::prelude::Vec};41use up_data_structs::{42	VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT, OFFCHAIN_SCHEMA_LIMIT,43	MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH,44	AccessMode, CreateItemData, CollectionLimits, CollectionId, CollectionMode, TokenId,45	SchemaVersion, SponsorshipState, MetaUpdatePermission, CreateCollectionData, CustomDataLimit,46	CreateItemExData, budget,47};48use pallet_evm::account::CrossAccountId;49use pallet_common::{50	CollectionHandle, Pallet as PalletCommon, Error as CommonError, CommonWeightInfo,51	dispatch::dispatch_call, dispatch::CollectionDispatch,52};5354#[cfg(test)]55mod mock;5657#[cfg(test)]58mod tests;5960mod eth;61mod sponsorship;62pub use sponsorship::{UniqueSponsorshipHandler, UniqueSponsorshipPredict};63pub use eth::sponsoring::UniqueEthSponsorshipHandler;6465pub mod common;66use common::CommonWeights;6768#[cfg(feature = "runtime-benchmarks")]69mod benchmarking;70pub mod weights;71use weights::WeightInfo;7273pub trait SponsorshipPredict<T: Config> {74	fn predict(collection: CollectionId, account: T::CrossAccountId, token: TokenId) -> Option<u64>75	where76		u64: From<<T as frame_system::Config>::BlockNumber>;77}7879decl_error! {80	/// Error for non-fungible-token module.81	pub enum Error for Module<T: Config> {82		/// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.83		CollectionDecimalPointLimitExceeded,84		/// This address is not set as sponsor, use setCollectionSponsor first.85		ConfirmUnsetSponsorFail,86		/// Length of items properties must be greater than 0.87		EmptyArgument,88	}89}9091pub trait Config:92	system::Config93	+ pallet_evm_coder_substrate::Config94	+ pallet_common::Config95	+ pallet_nonfungible::Config96	+ pallet_refungible::Config97	+ pallet_fungible::Config98	+ Sized99	+ TypeInfo100{101	type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;102103	/// Weight information for extrinsics in this pallet.104	type WeightInfo: WeightInfo;105}106107decl_event! {108	pub enum Event<T>109	where110		<T as frame_system::Config>::AccountId,111		<T as pallet_evm::account::Config>::CrossAccountId,112	{113		/// Collection sponsor was removed114		///115		/// # Arguments116		///117		/// * collection_id: Globally unique collection identifier.118		CollectionSponsorRemoved(CollectionId),119120		/// Collection admin was added121		///122		/// # Arguments123		///124		/// * collection_id: Globally unique collection identifier.125		///126		/// * admin:  Admin address.127		CollectionAdminAdded(CollectionId, CrossAccountId),128129		/// Collection owned was change130		///131		/// # Arguments132		///133		/// * collection_id: Globally unique collection identifier.134		///135		/// * owner:  New owner address.136		CollectionOwnedChanged(CollectionId, AccountId),137138		/// Collection sponsor was set139		///140		/// # Arguments141		///142		/// * collection_id: Globally unique collection identifier.143		///144		/// * owner:  New sponsor address.145		CollectionSponsorSet(CollectionId, AccountId),146147		/// const on chain schema was set148		///149		/// # Arguments150		///151		/// * collection_id: Globally unique collection identifier.152		ConstOnChainSchemaSet(CollectionId),153154		/// New sponsor was confirm155		///156		/// # Arguments157		///158		/// * collection_id: Globally unique collection identifier.159		///160		/// * sponsor:  New sponsor address.161		SponsorshipConfirmed(CollectionId, AccountId),162163		/// Collection admin was removed164		///165		/// # Arguments166		///167		/// * collection_id: Globally unique collection identifier.168		///169		/// * admin:  Admin address.170		CollectionAdminRemoved(CollectionId, CrossAccountId),171172		/// Address was remove from allow list173		///174		/// # Arguments175		///176		/// * collection_id: Globally unique collection identifier.177		///178		/// * user:  Address.179		AllowListAddressRemoved(CollectionId, CrossAccountId),180181		/// Address was add to allow list182		///183		/// # Arguments184		///185		/// * collection_id: Globally unique collection identifier.186		///187		/// * user:  Address.188		AllowListAddressAdded(CollectionId, CrossAccountId),189190		/// Collection limits was set191		///192		/// # Arguments193		///194		/// * collection_id: Globally unique collection identifier.195		CollectionLimitSet(CollectionId),196197		/// Mint permission	was set198		///199		/// # Arguments200		///201		/// * collection_id: Globally unique collection identifier.202		MintPermissionSet(CollectionId),203204		/// Offchain schema was set205		///206		/// # Arguments207		///208		/// * collection_id: Globally unique collection identifier.209		OffchainSchemaSet(CollectionId),210211		/// Public access mode was set212		///213		/// # Arguments214		///215		/// * collection_id: Globally unique collection identifier.216		///217		/// * mode: New access state.218		PublicAccessModeSet(CollectionId, AccessMode),219220		/// Schema version was set221		///222		/// # Arguments223		///224		/// * collection_id: Globally unique collection identifier.225		SchemaVersionSet(CollectionId),226227		/// Variable on chain schema was set228		///229		/// # Arguments230		///231		/// * collection_id: Globally unique collection identifier.232		VariableOnChainSchemaSet(CollectionId),233	}234}235236type SelfWeightOf<T> = <T as Config>::WeightInfo;237238// # Used definitions239//240// ## User control levels241//242// chain-controlled - key is uncontrolled by user243//                    i.e autoincrementing index244//                    can use non-cryptographic hash245// real - key is controlled by user246//        but it is hard to generate enough colliding values, i.e owner of signed txs247//        can use non-cryptographic hash248// controlled - key is completly controlled by users249//              i.e maps with mutable keys250//              should use cryptographic hash251//252// ## User control level downgrade reasons253//254// ?1 - chain-controlled -> controlled255//      collections/tokens can be destroyed, resulting in massive holes256// ?2 - chain-controlled -> controlled257//      same as ?1, but can be only added, resulting in easier exploitation258// ?3 - real -> controlled259//      no confirmation required, so addresses can be easily generated260decl_storage! {261	trait Store for Module<T: Config> as Unique {262263		//#region Private members264		/// Used for migrations265		ChainVersion: u64;266		//#endregion267268		//#region Tokens transfer rate limit baskets269		/// (Collection id (controlled?2), who created (real))270		/// TODO: Off chain worker should remove from this map when collection gets removed271		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;272		/// Collection id (controlled?2), token id (controlled?2)273		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;274		/// Collection id (controlled?2), owning user (real)275		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;276		/// Collection id (controlled?2), token id (controlled?2)277		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>;278		//#endregion279280		/// Variable metadata sponsoring281		/// Collection id (controlled?2), token id (controlled?2)282		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;283		/// Approval sponsoring284		pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;285		pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;286		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>;287	}288}289290decl_module! {291	pub struct Module<T: Config> for enum Call292	where293		origin: T::Origin294	{295		type Error = Error<T>;296297		fn deposit_event() = default;298299		fn on_initialize(_now: T::BlockNumber) -> Weight {300			0301		}302303		/// 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.304		///305		/// # Permissions306		///307		/// * Anyone.308		///309		/// # Arguments310		///311		/// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.312		///313		/// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.314		///315		/// * token_prefix: UTF-8 string with token prefix.316		///317		/// * mode: [CollectionMode] collection type and type dependent data.318		// returns collection ID319		#[weight = <SelfWeightOf<T>>::create_collection()]320		#[transactional]321		#[deprecated]322		pub fn create_collection(origin,323								 collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,324								 collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,325								 token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,326								 mode: CollectionMode) -> DispatchResult  {327			let data: CreateCollectionData<T::AccountId> = CreateCollectionData {328				name: collection_name,329				description: collection_description,330				token_prefix,331				mode,332				..Default::default()333			};334			Self::create_collection_ex(origin, data)335		}336337		/// This method creates a collection338		///339		/// Prefer it to deprecated [`created_collection`] method340		#[weight = <SelfWeightOf<T>>::create_collection()]341		#[transactional]342		pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {343			let sender = ensure_signed(origin)?;344345			// =========346347			T::CollectionDispatch::create(sender, data)?;348349			Ok(())350		}351352		/// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.353		///354		/// # Permissions355		///356		/// * Collection Owner.357		///358		/// # Arguments359		///360		/// * collection_id: collection to destroy.361		#[weight = <SelfWeightOf<T>>::destroy_collection()]362		#[transactional]363		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {364			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);365			let collection = <CollectionHandle<T>>::try_get(collection_id)?;366367			// =========368369			T::CollectionDispatch::destroy(sender, collection)?;370371			<NftTransferBasket<T>>::remove_prefix(collection_id, None);372			<FungibleTransferBasket<T>>::remove_prefix(collection_id, None);373			<ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);374375			<VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);376			<NftApproveBasket<T>>::remove_prefix(collection_id, None);377			<FungibleApproveBasket<T>>::remove_prefix(collection_id, None);378			<RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);379380			Ok(())381		}382383		/// Add an address to allow list.384		///385		/// # Permissions386		///387		/// * Collection Owner388		/// * Collection Admin389		///390		/// # Arguments391		///392		/// * collection_id.393		///394		/// * address.395		#[weight = <SelfWeightOf<T>>::add_to_allow_list()]396		#[transactional]397		pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{398399			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);400			let collection = <CollectionHandle<T>>::try_get(collection_id)?;401402			<PalletCommon<T>>::toggle_allowlist(403				&collection,404				&sender,405				&address,406				true,407			)?;408409			Self::deposit_event(Event::<T>::AllowListAddressAdded(410				collection_id,411				address412			));413414			Ok(())415		}416417		/// Remove an address from allow list.418		///419		/// # Permissions420		///421		/// * Collection Owner422		/// * Collection Admin423		///424		/// # Arguments425		///426		/// * collection_id.427		///428		/// * address.429		#[weight = <SelfWeightOf<T>>::remove_from_allow_list()]430		#[transactional]431		pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{432433			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);434			let collection = <CollectionHandle<T>>::try_get(collection_id)?;435436			<PalletCommon<T>>::toggle_allowlist(437				&collection,438				&sender,439				&address,440				false,441			)?;442443			<Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(444				collection_id,445				address446			));447448			Ok(())449		}450451		/// Toggle between normal and allow list access for the methods with access for `Anyone`.452		///453		/// # Permissions454		///455		/// * Collection Owner.456		///457		/// # Arguments458		///459		/// * collection_id.460		///461		/// * mode: [AccessMode]462		#[weight = <SelfWeightOf<T>>::set_public_access_mode()]463		#[transactional]464		pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult465		{466			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);467468			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;469			target_collection.check_is_owner(&sender)?;470471			target_collection.access = mode.clone();472473			<Pallet<T>>::deposit_event(Event::<T>::PublicAccessModeSet(474				collection_id,475				mode476			));477478			target_collection.save()479		}480481		/// Allows Anyone to create tokens if:482		/// * Allow List is enabled, and483		/// * Address is added to allow list, and484		/// * This method was called with True parameter485		///486		/// # Permissions487		/// * Collection Owner488		///489		/// # Arguments490		///491		/// * collection_id.492		///493		/// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.494		#[weight = <SelfWeightOf<T>>::set_mint_permission()]495		#[transactional]496		pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult497		{498			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);499500			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;501			target_collection.check_is_owner(&sender)?;502503			target_collection.mint_mode = mint_permission;504505			<Pallet<T>>::deposit_event(Event::<T>::MintPermissionSet(506				collection_id507			));508509			target_collection.save()510		}511512		/// Change the owner of the collection.513		///514		/// # Permissions515		///516		/// * Collection Owner.517		///518		/// # Arguments519		///520		/// * collection_id.521		///522		/// * new_owner.523		#[weight = <SelfWeightOf<T>>::change_collection_owner()]524		#[transactional]525		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {526527			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.owner = new_owner.clone();533			<Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(534				collection_id,535				new_owner536			));537538			target_collection.save()539		}540541		/// Adds an admin of the Collection.542		/// 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.543		///544		/// # Permissions545		///546		/// * Collection Owner.547		/// * Collection Admin.548		///549		/// # Arguments550		///551		/// * collection_id: ID of the Collection to add admin for.552		///553		/// * new_admin_id: Address of new admin to add.554		#[weight = <SelfWeightOf<T>>::add_collection_admin()]555		#[transactional]556		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {557			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);558			let collection = <CollectionHandle<T>>::try_get(collection_id)?;559560			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(561				collection_id,562				new_admin_id.clone()563			));564565			<PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)566		}567568		/// 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.569		///570		/// # Permissions571		///572		/// * Collection Owner.573		/// * Collection Admin.574		///575		/// # Arguments576		///577		/// * collection_id: ID of the Collection to remove admin for.578		///579		/// * account_id: Address of admin to remove.580		#[weight = <SelfWeightOf<T>>::remove_collection_admin()]581		#[transactional]582		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {583			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);584			let collection = <CollectionHandle<T>>::try_get(collection_id)?;585586			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(587				collection_id,588				account_id.clone()589			));590591			<PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)592		}593594		/// # Permissions595		///596		/// * Collection Owner597		///598		/// # Arguments599		///600		/// * collection_id.601		///602		/// * new_sponsor.603		#[weight = <SelfWeightOf<T>>::set_collection_sponsor()]604		#[transactional]605		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {606			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);607608			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;609			target_collection.check_is_owner(&sender)?;610611			target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor.clone());612613			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(614				collection_id,615				new_sponsor616			));617618			target_collection.save()619		}620621		/// # Permissions622		///623		/// * Sponsor.624		///625		/// # Arguments626		///627		/// * collection_id.628		#[weight = <SelfWeightOf<T>>::confirm_sponsorship()]629		#[transactional]630		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {631			let sender = ensure_signed(origin)?;632633			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;634			ensure!(635				target_collection.sponsorship.pending_sponsor() == Some(&sender),636				Error::<T>::ConfirmUnsetSponsorFail637			);638639			target_collection.sponsorship = SponsorshipState::Confirmed(sender.clone());640641			<Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(642				collection_id,643				sender644			));645646			target_collection.save()647		}648649		/// Switch back to pay-per-own-transaction model.650		///651		/// # Permissions652		///653		/// * Collection owner.654		///655		/// # Arguments656		///657		/// * collection_id.658		#[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]659		#[transactional]660		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {661			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);662663			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;664			target_collection.check_is_owner(&sender)?;665666			target_collection.sponsorship = SponsorshipState::Disabled;667668			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(669				collection_id670			));671			target_collection.save()672		}673674		/// This method creates a concrete instance of NFT Collection created with CreateCollection method.675		///676		/// # Permissions677		///678		/// * Collection Owner.679		/// * Collection Admin.680		/// * Anyone if681		///     * Allow List is enabled, and682		///     * Address is added to allow list, and683		///     * MintPermission is enabled (see SetMintPermission method)684		///685		/// # Arguments686		///687		/// * collection_id: ID of the collection.688		///689		/// * owner: Address, initial owner of the NFT.690		///691		/// * data: Token data to store on chain.692		#[weight = <CommonWeights<T>>::create_item()]693		#[transactional]694		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {695			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);696			let budget = budget::Value::new(2);697698			dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))699		}700701		/// This method creates multiple items in a collection created with CreateCollection method.702		///703		/// # Permissions704		///705		/// * Collection Owner.706		/// * Collection Admin.707		/// * Anyone if708		///     * Allow List is enabled, and709		///     * Address is added to allow list, and710		///     * MintPermission is enabled (see SetMintPermission method)711		///712		/// # Arguments713		///714		/// * collection_id: ID of the collection.715		///716		/// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].717		///718		/// * owner: Address, initial owner of the NFT.719		#[weight = <CommonWeights<T>>::create_multiple_items(items_data.len() as u32)]720		#[transactional]721		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {722			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);723			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);724			let budget = budget::Value::new(2);725726			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))727		}728729		#[weight = <CommonWeights<T>>::create_multiple_items_ex(&data)]730		#[transactional]731		pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {732			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);733			let budget = budget::Value::new(2);734735			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))736		}737738		// TODO! transaction weight739740		/// Set transfers_enabled value for particular collection741		///742		/// # Permissions743		///744		/// * Collection Owner.745		///746		/// # Arguments747		///748		/// * collection_id: ID of the collection.749		///750		/// * value: New flag value.751		#[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]752		#[transactional]753		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {754			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);755			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;756			target_collection.check_is_owner(&sender)?;757758			// =========759760			target_collection.limits.transfers_enabled = Some(value);761			target_collection.save()762		}763764		/// Destroys a concrete instance of NFT.765		///766		/// # Permissions767		///768		/// * Collection Owner.769		/// * Collection Admin.770		/// * Current NFT Owner.771		///772		/// # Arguments773		///774		/// * collection_id: ID of the collection.775		///776		/// * item_id: ID of NFT to burn.777		#[weight = <CommonWeights<T>>::burn_item()]778		#[transactional]779		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {780			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);781782			let post_info = dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;783			if value == 1 {784				<NftTransferBasket<T>>::remove(collection_id, item_id);785				<NftApproveBasket<T>>::remove(collection_id, item_id);786			}787			// Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?788			// <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());789			// <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));790			Ok(post_info)791		}792793		/// Destroys a concrete instance of NFT on behalf of the owner794		/// See also: [`approve`]795		///796		/// # Permissions797		///798		/// * Collection Owner.799		/// * Collection Admin.800		/// * Current NFT Owner.801		///802		/// # Arguments803		///804		/// * collection_id: ID of the collection.805		///806		/// * item_id: ID of NFT to burn.807		///808		/// * from: owner of item809		#[weight = <CommonWeights<T>>::burn_from()]810		#[transactional]811		pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {812			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);813			let budget = budget::Value::new(2);814815			dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value, &budget))816		}817818		/// Change ownership of the token.819		///820		/// # Permissions821		///822		/// * Collection Owner823		/// * Collection Admin824		/// * Current NFT owner825		///826		/// # Arguments827		///828		/// * recipient: Address of token recipient.829		///830		/// * collection_id.831		///832		/// * item_id: ID of the item833		///     * Non-Fungible Mode: Required.834		///     * Fungible Mode: Ignored.835		///     * Re-Fungible Mode: Required.836		///837		/// * value: Amount to transfer.838		///     * Non-Fungible Mode: Ignored839		///     * Fungible Mode: Must specify transferred amount840		///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)841		#[weight = <CommonWeights<T>>::transfer()]842		#[transactional]843		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {844			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);845			let budget = budget::Value::new(2);846847			dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value, &budget))848		}849850		/// Set, change, or remove approved address to transfer the ownership of the NFT.851		///852		/// # Permissions853		///854		/// * Collection Owner855		/// * Collection Admin856		/// * Current NFT owner857		///858		/// # Arguments859		///860		/// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).861		///862		/// * collection_id.863		///864		/// * item_id: ID of the item.865		#[weight = <CommonWeights<T>>::approve()]866		#[transactional]867		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {868			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);869870			dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))871		}872873		/// 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.874		///875		/// # Permissions876		/// * Collection Owner877		/// * Collection Admin878		/// * Current NFT owner879		/// * Address approved by current NFT owner880		///881		/// # Arguments882		///883		/// * from: Address that owns token.884		///885		/// * recipient: Address of token recipient.886		///887		/// * collection_id.888		///889		/// * item_id: ID of the item.890		///891		/// * value: Amount to transfer.892		#[weight = <CommonWeights<T>>::transfer_from()]893		#[transactional]894		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {895			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);896			let budget = budget::Value::new(2);897898			dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))899		}900901		/// Set off-chain data schema.902		///903		/// # Permissions904		///905		/// * Collection Owner906		/// * Collection Admin907		///908		/// # Arguments909		///910		/// * collection_id.911		///912		/// * schema: String representing the offchain data schema.913		#[weight = <CommonWeights<T>>::set_variable_metadata(data.len() as u32)]914		#[transactional]915		pub fn set_variable_meta_data (916			origin,917			collection_id: CollectionId,918			item_id: TokenId,919			data: BoundedVec<u8, CustomDataLimit>,920		) -> DispatchResultWithPostInfo {921			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);922923			dispatch_call::<T, _>(collection_id, |d| d.set_variable_metadata(sender, item_id, data))924		}925926		/// Set meta_update_permission value for particular collection927		///928		/// # Permissions929		///930		/// * Collection Owner.931		///932		/// # Arguments933		///934		/// * collection_id: ID of the collection.935		///936		/// * value: New flag value.937		#[weight = <SelfWeightOf<T>>::set_meta_update_permission_flag()]938		#[transactional]939		pub fn set_meta_update_permission_flag(origin, collection_id: CollectionId, value: MetaUpdatePermission) -> DispatchResult {940			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);941			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;942943			ensure!(944				target_collection.meta_update_permission != MetaUpdatePermission::None,945				<CommonError<T>>::MetadataFlagFrozen,946			);947			target_collection.check_is_owner(&sender)?;948949			target_collection.meta_update_permission = value;950951			target_collection.save()952		}953954		/// Set schema standard955		/// ImageURL956		/// Unique957		///958		/// # Permissions959		///960		/// * Collection Owner961		/// * Collection Admin962		///963		/// # Arguments964		///965		/// * collection_id.966		///967		/// * schema: SchemaVersion: enum968		#[weight = <SelfWeightOf<T>>::set_schema_version()]969		#[transactional]970		pub fn set_schema_version(971			origin,972			collection_id: CollectionId,973			version: SchemaVersion974		) -> DispatchResult {975			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);976			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;977			target_collection.check_is_owner_or_admin(&sender)?;978			target_collection.schema_version = version;979980			<Pallet<T>>::deposit_event(Event::<T>::SchemaVersionSet(981				collection_id982			));983984			target_collection.save()985		}986987		/// Set off-chain data schema.988		///989		/// # Permissions990		///991		/// * Collection Owner992		/// * Collection Admin993		///994		/// # Arguments995		///996		/// * collection_id.997		///998		/// * schema: String representing the offchain data schema.999		#[weight = <SelfWeightOf<T>>::set_offchain_schema(schema.len() as u32)]1000		#[transactional]1001		pub fn set_offchain_schema(1002			origin,1003			collection_id: CollectionId,1004			schema: BoundedVec<u8, ConstU32<OFFCHAIN_SCHEMA_LIMIT>>,1005		) -> DispatchResult {1006			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1007			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1008			target_collection.check_is_owner_or_admin(&sender)?;10091010			target_collection.offchain_schema = schema;10111012			<Pallet<T>>::deposit_event(Event::<T>::OffchainSchemaSet(1013				collection_id1014			));10151016			target_collection.save()1017		}10181019		/// Set const on-chain data schema.1020		///1021		/// # Permissions1022		///1023		/// * Collection Owner1024		/// * Collection Admin1025		///1026		/// # Arguments1027		///1028		/// * collection_id.1029		///1030		/// * schema: String representing the const on-chain data schema.1031		#[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1032		#[transactional]1033		pub fn set_const_on_chain_schema (1034			origin,1035			collection_id: CollectionId,1036			schema: BoundedVec<u8, ConstU32<CONST_ON_CHAIN_SCHEMA_LIMIT>>1037		) -> DispatchResult {1038			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1039			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1040			target_collection.check_is_owner_or_admin(&sender)?;10411042			target_collection.const_on_chain_schema = schema;10431044			<Pallet<T>>::deposit_event(Event::<T>::ConstOnChainSchemaSet(1045				collection_id1046			));10471048			target_collection.save()1049		}10501051		/// Set variable on-chain data schema.1052		///1053		/// # Permissions1054		///1055		/// * Collection Owner1056		/// * Collection Admin1057		///1058		/// # Arguments1059		///1060		/// * collection_id.1061		///1062		/// * schema: String representing the variable on-chain data schema.1063		#[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1064		#[transactional]1065		pub fn set_variable_on_chain_schema (1066			origin,1067			collection_id: CollectionId,1068			schema: BoundedVec<u8, ConstU32<VARIABLE_ON_CHAIN_SCHEMA_LIMIT>>1069		) -> DispatchResult {1070			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1071			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1072			target_collection.check_is_owner_or_admin(&sender)?;10731074			target_collection.variable_on_chain_schema = schema;10751076			<Pallet<T>>::deposit_event(Event::<T>::VariableOnChainSchemaSet(1077				collection_id1078			));10791080			target_collection.save()1081		}10821083		#[weight = <SelfWeightOf<T>>::set_collection_limits()]1084		#[transactional]1085		pub fn set_collection_limits(1086			origin,1087			collection_id: CollectionId,1088			new_limit: CollectionLimits,1089		) -> DispatchResult {1090			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1091			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1092			target_collection.check_is_owner(&sender)?;1093			let old_limit = &target_collection.limits;10941095			target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;10961097			<Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(1098				collection_id1099			));11001101			target_collection.save()1102		}1103	}1104}