git.delta.rocks / unique-network / refs/commits / af3427ec9590

difftreelog

source

pallets/unique/src/lib.rs30.2 KiBsourcehistory
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![recursion_limit = "1024"]18#![cfg_attr(not(feature = "std"), no_std)]19#![allow(20	clippy::too_many_arguments,21	clippy::unnecessary_mut_passed,22	clippy::unused_unit23)]2425extern crate alloc;2627use frame_support::{28	decl_module, decl_storage, decl_error, decl_event,29	dispatch::DispatchResult,30	ensure, fail,31	weights::{Weight},32	transactional,33	pallet_prelude::{DispatchResultWithPostInfo, ConstU32},34	BoundedVec,35};36use scale_info::TypeInfo;37use frame_system::{self as system, ensure_signed};38use sp_runtime::{sp_std::prelude::Vec};39use up_data_structs::{40	MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH,41	CreateItemData, CollectionLimits, CollectionPermissions, CollectionId, CollectionMode, TokenId,42	SponsorshipState, CreateCollectionData, CreateItemExData, budget, Property, PropertyKey,43	PropertyKeyPermission,44};45use pallet_evm::account::CrossAccountId;46use pallet_common::{47	CollectionHandle, Pallet as PalletCommon, CommonWeightInfo, dispatch::dispatch_tx,48	dispatch::CollectionDispatch,49};50use pallet_refungible::{Pallet as PalletRefungible, RefungibleHandle};51pub mod eth;5253#[cfg(feature = "runtime-benchmarks")]54mod benchmarking;55pub mod weights;56use weights::WeightInfo;5758const NESTING_BUDGET: u32 = 5;5960decl_error! {61	/// Error for non-fungible-token module.62	pub enum Error for Module<T: Config> {63		/// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.64		CollectionDecimalPointLimitExceeded,65		/// This address is not set as sponsor, use setCollectionSponsor first.66		ConfirmUnsetSponsorFail,67		/// Length of items properties must be greater than 0.68		EmptyArgument,69		/// Repertition is only supported by refungible collection70		RepartitionCalledOnNonRefungibleCollection,71	}72}7374pub trait Config:75	system::Config + pallet_common::Config + pallet_refungible::Config + Sized + TypeInfo76{77	type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;7879	/// Weight information for extrinsics in this pallet.80	type WeightInfo: WeightInfo;81	type CommonWeightInfo: CommonWeightInfo<Self::CrossAccountId>;82}8384decl_event! {85	pub enum Event<T>86	where87		<T as frame_system::Config>::AccountId,88		<T as pallet_evm::account::Config>::CrossAccountId,89	{90		/// Collection sponsor was removed91		///92		/// # Arguments93		///94		/// * collection_id: Globally unique collection identifier.95		CollectionSponsorRemoved(CollectionId),9697		/// Collection admin was added98		///99		/// # Arguments100		///101		/// * collection_id: Globally unique collection identifier.102		///103		/// * admin:  Admin address.104		CollectionAdminAdded(CollectionId, CrossAccountId),105106		/// Collection owned was change107		///108		/// # Arguments109		///110		/// * collection_id: Globally unique collection identifier.111		///112		/// * owner:  New owner address.113		CollectionOwnedChanged(CollectionId, AccountId),114115		/// Collection sponsor was set116		///117		/// # Arguments118		///119		/// * collection_id: Globally unique collection identifier.120		///121		/// * owner:  New sponsor address.122		CollectionSponsorSet(CollectionId, AccountId),123124		/// New sponsor was confirm125		///126		/// # Arguments127		///128		/// * collection_id: Globally unique collection identifier.129		///130		/// * sponsor:  New sponsor address.131		SponsorshipConfirmed(CollectionId, AccountId),132133		/// Collection admin was removed134		///135		/// # Arguments136		///137		/// * collection_id: Globally unique collection identifier.138		///139		/// * admin:  Admin address.140		CollectionAdminRemoved(CollectionId, CrossAccountId),141142		/// Address was remove from allow list143		///144		/// # Arguments145		///146		/// * collection_id: Globally unique collection identifier.147		///148		/// * user:  Address.149		AllowListAddressRemoved(CollectionId, CrossAccountId),150151		/// Address was add to allow list152		///153		/// # Arguments154		///155		/// * collection_id: Globally unique collection identifier.156		///157		/// * user:  Address.158		AllowListAddressAdded(CollectionId, CrossAccountId),159160		/// Collection limits was set161		///162		/// # Arguments163		///164		/// * collection_id: Globally unique collection identifier.165		CollectionLimitSet(CollectionId),166167		CollectionPermissionSet(CollectionId),168	}169}170171type SelfWeightOf<T> = <T as Config>::WeightInfo;172173// # Used definitions174//175// ## User control levels176//177// chain-controlled - key is uncontrolled by user178//                    i.e autoincrementing index179//                    can use non-cryptographic hash180// real - key is controlled by user181//        but it is hard to generate enough colliding values, i.e owner of signed txs182//        can use non-cryptographic hash183// controlled - key is completly controlled by users184//              i.e maps with mutable keys185//              should use cryptographic hash186//187// ## User control level downgrade reasons188//189// ?1 - chain-controlled -> controlled190//      collections/tokens can be destroyed, resulting in massive holes191// ?2 - chain-controlled -> controlled192//      same as ?1, but can be only added, resulting in easier exploitation193// ?3 - real -> controlled194//      no confirmation required, so addresses can be easily generated195decl_storage! {196	trait Store for Module<T: Config> as Unique {197198		//#region Private members199		/// Used for migrations200		ChainVersion: u64;201		//#endregion202203		//#region Tokens transfer rate limit baskets204		/// (Collection id (controlled?2), who created (real))205		/// TODO: Off chain worker should remove from this map when collection gets removed206		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;207		/// Collection id (controlled?2), token id (controlled?2)208		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;209		/// Collection id (controlled?2), owning user (real)210		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;211		/// Collection id (controlled?2), token id (controlled?2)212		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>;213		//#endregion214215		/// Variable metadata sponsoring216		/// Collection id (controlled?2), token id (controlled?2)217		#[deprecated]218		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;219		pub TokenPropertyBasket get(fn token_property_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;220221		/// Approval sponsoring222		pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;223		pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;224		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>;225	}226}227228decl_module! {229	pub struct Module<T: Config> for enum Call230	where231		origin: T::Origin232	{233		type Error = Error<T>;234235		fn deposit_event() = default;236237		fn on_initialize(_now: T::BlockNumber) -> Weight {238			0239		}240241		fn on_runtime_upgrade() -> Weight {242			let limit = None;243244			<VariableMetaDataBasket<T>>::remove_all(limit);245246			0247		}248249		/// 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.250		///251		/// # Permissions252		///253		/// * Anyone.254		///255		/// # Arguments256		///257		/// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.258		///259		/// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.260		///261		/// * token_prefix: UTF-8 string with token prefix.262		///263		/// * mode: [CollectionMode] collection type and type dependent data.264		// returns collection ID265		#[weight = <SelfWeightOf<T>>::create_collection()]266		#[transactional]267		#[deprecated]268		pub fn create_collection(origin,269								 collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,270								 collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,271								 token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,272								 mode: CollectionMode) -> DispatchResult  {273			let data: CreateCollectionData<T::AccountId> = CreateCollectionData {274				name: collection_name,275				description: collection_description,276				token_prefix,277				mode,278				..Default::default()279			};280			Self::create_collection_ex(origin, data)281		}282283		/// This method creates a collection284		///285		/// Prefer it to deprecated [`created_collection`] method286		#[weight = <SelfWeightOf<T>>::create_collection()]287		#[transactional]288		pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {289			let sender = ensure_signed(origin)?;290291			// =========292293			T::CollectionDispatch::create(T::CrossAccountId::from_sub(sender), data)?;294295			Ok(())296		}297298		/// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.299		///300		/// # Permissions301		///302		/// * Collection Owner.303		///304		/// # Arguments305		///306		/// * collection_id: collection to destroy.307		#[weight = <SelfWeightOf<T>>::destroy_collection()]308		#[transactional]309		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {310			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);311			let collection = <CollectionHandle<T>>::try_get(collection_id)?;312			collection.check_is_internal()?;313314			// =========315316			T::CollectionDispatch::destroy(sender, collection)?;317318			<NftTransferBasket<T>>::remove_prefix(collection_id, None);319			<FungibleTransferBasket<T>>::remove_prefix(collection_id, None);320			<ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);321322			<NftApproveBasket<T>>::remove_prefix(collection_id, None);323			<FungibleApproveBasket<T>>::remove_prefix(collection_id, None);324			<RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);325326			Ok(())327		}328329		/// Add an address to allow list.330		///331		/// # Permissions332		///333		/// * Collection Owner334		/// * Collection Admin335		///336		/// # Arguments337		///338		/// * collection_id.339		///340		/// * address.341		#[weight = <SelfWeightOf<T>>::add_to_allow_list()]342		#[transactional]343		pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{344345			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);346			let collection = <CollectionHandle<T>>::try_get(collection_id)?;347			collection.check_is_internal()?;348349			<PalletCommon<T>>::toggle_allowlist(350				&collection,351				&sender,352				&address,353				true,354			)?;355356			Self::deposit_event(Event::<T>::AllowListAddressAdded(357				collection_id,358				address359			));360361			Ok(())362		}363364		/// Remove an address from allow list.365		///366		/// # Permissions367		///368		/// * Collection Owner369		/// * Collection Admin370		///371		/// # Arguments372		///373		/// * collection_id.374		///375		/// * address.376		#[weight = <SelfWeightOf<T>>::remove_from_allow_list()]377		#[transactional]378		pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{379380			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);381			let collection = <CollectionHandle<T>>::try_get(collection_id)?;382			collection.check_is_internal()?;383384			<PalletCommon<T>>::toggle_allowlist(385				&collection,386				&sender,387				&address,388				false,389			)?;390391			<Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(392				collection_id,393				address394			));395396			Ok(())397		}398399		/// Change the owner of the collection.400		///401		/// # Permissions402		///403		/// * Collection Owner.404		///405		/// # Arguments406		///407		/// * collection_id.408		///409		/// * new_owner.410		#[weight = <SelfWeightOf<T>>::change_collection_owner()]411		#[transactional]412		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {413414			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);415416			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;417			target_collection.check_is_internal()?;418			target_collection.check_is_owner(&sender)?;419420			target_collection.owner = new_owner.clone();421			<Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(422				collection_id,423				new_owner424			));425426			target_collection.save()427		}428429		/// Adds an admin of the Collection.430		/// 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.431		///432		/// # Permissions433		///434		/// * Collection Owner.435		/// * Collection Admin.436		///437		/// # Arguments438		///439		/// * collection_id: ID of the Collection to add admin for.440		///441		/// * new_admin_id: Address of new admin to add.442		#[weight = <SelfWeightOf<T>>::add_collection_admin()]443		#[transactional]444		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {445			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);446			let collection = <CollectionHandle<T>>::try_get(collection_id)?;447			collection.check_is_internal()?;448449			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(450				collection_id,451				new_admin_id.clone()452			));453454			<PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)455		}456457		/// 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.458		///459		/// # Permissions460		///461		/// * Collection Owner.462		/// * Collection Admin.463		///464		/// # Arguments465		///466		/// * collection_id: ID of the Collection to remove admin for.467		///468		/// * account_id: Address of admin to remove.469		#[weight = <SelfWeightOf<T>>::remove_collection_admin()]470		#[transactional]471		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {472			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);473			let collection = <CollectionHandle<T>>::try_get(collection_id)?;474			collection.check_is_internal()?;475476			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(477				collection_id,478				account_id.clone()479			));480481			<PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)482		}483484		/// # Permissions485		///486		/// * Collection Owner487		///488		/// # Arguments489		///490		/// * collection_id.491		///492		/// * new_sponsor.493		#[weight = <SelfWeightOf<T>>::set_collection_sponsor()]494		#[transactional]495		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {496			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);497498			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;499			target_collection.check_is_owner_or_admin(&sender)?;500			target_collection.check_is_internal()?;501502			target_collection.set_sponsor(new_sponsor.clone())?;503504			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(505				collection_id,506				new_sponsor507			));508509			target_collection.save()510		}511512		/// # Permissions513		///514		/// * Sponsor.515		///516		/// # Arguments517		///518		/// * collection_id.519		#[weight = <SelfWeightOf<T>>::confirm_sponsorship()]520		#[transactional]521		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {522			let sender = ensure_signed(origin)?;523524			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;525			target_collection.check_is_internal()?;526			ensure!(527				target_collection.confirm_sponsorship(&sender)?,528				Error::<T>::ConfirmUnsetSponsorFail529			);530531			<Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(532				collection_id,533				sender534			));535536			target_collection.save()537		}538539		/// Switch back to pay-per-own-transaction model.540		///541		/// # Permissions542		///543		/// * Collection owner.544		///545		/// # Arguments546		///547		/// * collection_id.548		#[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]549		#[transactional]550		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {551			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);552553			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;554			target_collection.check_is_internal()?;555			target_collection.check_is_owner(&sender)?;556557			target_collection.sponsorship = SponsorshipState::Disabled;558559			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(560				collection_id561			));562			target_collection.save()563		}564565		/// This method creates a concrete instance of NFT Collection created with CreateCollection method.566		///567		/// # Permissions568		///569		/// * Collection Owner.570		/// * Collection Admin.571		/// * Anyone if572		///     * Allow List is enabled, and573		///     * Address is added to allow list, and574		///     * MintPermission is enabled (see SetMintPermission method)575		///576		/// # Arguments577		///578		/// * collection_id: ID of the collection.579		///580		/// * owner: Address, initial owner of the NFT.581		///582		/// * data: Token data to store on chain.583		#[weight = T::CommonWeightInfo::create_item()]584		#[transactional]585		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {586			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);587			let budget = budget::Value::new(NESTING_BUDGET);588589			dispatch_tx::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))590		}591592		/// This method creates multiple items in a collection created with CreateCollection method.593		///594		/// # Permissions595		///596		/// * Collection Owner.597		/// * Collection Admin.598		/// * Anyone if599		///     * Allow List is enabled, and600		///     * Address is added to allow list, and601		///     * MintPermission is enabled (see SetMintPermission method)602		///603		/// # Arguments604		///605		/// * collection_id: ID of the collection.606		///607		/// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].608		///609		/// * owner: Address, initial owner of the NFT.610		#[weight = T::CommonWeightInfo::create_multiple_items(&items_data)]611		#[transactional]612		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {613			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);614			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);615			let budget = budget::Value::new(NESTING_BUDGET);616617			dispatch_tx::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))618		}619620		#[weight = T::CommonWeightInfo::set_collection_properties(properties.len() as u32)]621		#[transactional]622		pub fn set_collection_properties(623			origin,624			collection_id: CollectionId,625			properties: Vec<Property>626		) -> DispatchResultWithPostInfo {627			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);628629			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);630631			dispatch_tx::<T, _>(collection_id, |d| d.set_collection_properties(sender, properties))632		}633634		#[weight = T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32)]635		#[transactional]636		pub fn delete_collection_properties(637			origin,638			collection_id: CollectionId,639			property_keys: Vec<PropertyKey>,640		) -> DispatchResultWithPostInfo {641			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);642643			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);644645			dispatch_tx::<T, _>(collection_id, |d| d.delete_collection_properties(&sender, property_keys))646		}647648		#[weight = T::CommonWeightInfo::set_token_properties(properties.len() as u32)]649		#[transactional]650		pub fn set_token_properties(651			origin,652			collection_id: CollectionId,653			token_id: TokenId,654			properties: Vec<Property>655		) -> DispatchResultWithPostInfo {656			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);657658			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);659660			dispatch_tx::<T, _>(collection_id, |d| d.set_token_properties(sender, token_id, properties))661		}662663		#[weight = T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32)]664		#[transactional]665		pub fn delete_token_properties(666			origin,667			collection_id: CollectionId,668			token_id: TokenId,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_tx::<T, _>(collection_id, |d| d.delete_token_properties(sender, token_id, property_keys))676		}677678		#[weight = T::CommonWeightInfo::set_token_property_permissions(property_permissions.len() as u32)]679		#[transactional]680		pub fn set_token_property_permissions(681			origin,682			collection_id: CollectionId,683			property_permissions: Vec<PropertyKeyPermission>,684		) -> DispatchResultWithPostInfo {685			ensure!(!property_permissions.is_empty(), Error::<T>::EmptyArgument);686687			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);688689			dispatch_tx::<T, _>(collection_id, |d| d.set_token_property_permissions(&sender, property_permissions))690		}691692		#[weight = T::CommonWeightInfo::create_multiple_items_ex(&data)]693		#[transactional]694		pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {695			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);696			let budget = budget::Value::new(NESTING_BUDGET);697698			dispatch_tx::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))699		}700701		/// Set transfers_enabled value for particular collection702		///703		/// # Permissions704		///705		/// * Collection Owner.706		///707		/// # Arguments708		///709		/// * collection_id: ID of the collection.710		///711		/// * value: New flag value.712		#[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]713		#[transactional]714		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {715			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);716			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;717			target_collection.check_is_internal()?;718			target_collection.check_is_owner(&sender)?;719720			// =========721722			target_collection.limits.transfers_enabled = Some(value);723			target_collection.save()724		}725726		/// Destroys a concrete instance of NFT.727		///728		/// # Permissions729		///730		/// * Collection Owner.731		/// * Collection Admin.732		/// * Current NFT Owner.733		///734		/// # Arguments735		///736		/// * collection_id: ID of the collection.737		///738		/// * item_id: ID of NFT to burn.739		#[weight = T::CommonWeightInfo::burn_item()]740		#[transactional]741		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {742			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);743744			let post_info = dispatch_tx::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;745			if value == 1 {746				<NftTransferBasket<T>>::remove(collection_id, item_id);747				<NftApproveBasket<T>>::remove(collection_id, item_id);748			}749			// Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?750			// <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());751			// <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));752			Ok(post_info)753		}754755		/// Destroys a concrete instance of NFT on behalf of the owner756		/// See also: [`approve`]757		///758		/// # Permissions759		///760		/// * Collection Owner.761		/// * Collection Admin.762		/// * Current NFT Owner.763		///764		/// # Arguments765		///766		/// * collection_id: ID of the collection.767		///768		/// * item_id: ID of NFT to burn.769		///770		/// * from: owner of item771		#[weight = T::CommonWeightInfo::burn_from()]772		#[transactional]773		pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {774			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);775			let budget = budget::Value::new(NESTING_BUDGET);776777			dispatch_tx::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value, &budget))778		}779780		/// Change ownership of the token.781		///782		/// # Permissions783		///784		/// * Collection Owner785		/// * Collection Admin786		/// * Current NFT owner787		///788		/// # Arguments789		///790		/// * recipient: Address of token recipient.791		///792		/// * collection_id.793		///794		/// * item_id: ID of the item795		///     * Non-Fungible Mode: Required.796		///     * Fungible Mode: Ignored.797		///     * Re-Fungible Mode: Required.798		///799		/// * value: Amount to transfer.800		///     * Non-Fungible Mode: Ignored801		///     * Fungible Mode: Must specify transferred amount802		///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)803		#[weight = T::CommonWeightInfo::transfer()]804		#[transactional]805		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {806			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);807			let budget = budget::Value::new(NESTING_BUDGET);808809			dispatch_tx::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value, &budget))810		}811812		/// Set, change, or remove approved address to transfer the ownership of the NFT.813		///814		/// # Permissions815		///816		/// * Collection Owner817		/// * Collection Admin818		/// * Current NFT owner819		///820		/// # Arguments821		///822		/// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).823		///824		/// * collection_id.825		///826		/// * item_id: ID of the item.827		#[weight = T::CommonWeightInfo::approve()]828		#[transactional]829		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {830			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);831832			dispatch_tx::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))833		}834835		/// 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.836		///837		/// # Permissions838		/// * Collection Owner839		/// * Collection Admin840		/// * Current NFT owner841		/// * Address approved by current NFT owner842		///843		/// # Arguments844		///845		/// * from: Address that owns token.846		///847		/// * recipient: Address of token recipient.848		///849		/// * collection_id.850		///851		/// * item_id: ID of the item.852		///853		/// * value: Amount to transfer.854		#[weight = T::CommonWeightInfo::transfer_from()]855		#[transactional]856		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {857			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);858			let budget = budget::Value::new(NESTING_BUDGET);859860			dispatch_tx::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))861		}862863		#[weight = <SelfWeightOf<T>>::set_collection_limits()]864		#[transactional]865		pub fn set_collection_limits(866			origin,867			collection_id: CollectionId,868			new_limit: CollectionLimits,869		) -> DispatchResult {870			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);871			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;872			target_collection.check_is_internal()?;873			target_collection.check_is_owner_or_admin(&sender)?;874			let old_limit = &target_collection.limits;875876			target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;877878			<Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(879				collection_id880			));881882			target_collection.save()883		}884885		#[weight = <SelfWeightOf<T>>::set_collection_limits()]886		#[transactional]887		pub fn set_collection_permissions(888			origin,889			collection_id: CollectionId,890			new_limit: CollectionPermissions,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_internal()?;895			target_collection.check_is_owner_or_admin(&sender)?;896			let old_limit = &target_collection.permissions;897898			target_collection.permissions = <PalletCommon<T>>::clamp_permissions(target_collection.mode.clone(), &old_limit, new_limit)?;899900			<Pallet<T>>::deposit_event(Event::<T>::CollectionPermissionSet(901				collection_id902			));903904			target_collection.save()905		}906907		#[weight = <SelfWeightOf<T>>::set_collection_limits()]908		#[transactional]909		pub fn repartition(910			origin,911			collection_id: CollectionId,912			token: TokenId,913			amount: u128,914		) -> DispatchResult {915			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);916			let target_collection = <CollectionHandle<T>>::try_get(collection_id)?;917			target_collection.check_is_internal()?;918			let refungible_collection = match target_collection.mode {919				CollectionMode::ReFungible => RefungibleHandle::cast(target_collection),920				_ => fail!(<Error<T>>::RepartitionCalledOnNonRefungibleCollection),921			};922			<PalletRefungible<T>>::repartition(&sender, &refungible_collection, token, amount)?;923			Ok(())924		}925	}926}