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

difftreelog

source

pallets/unique/src/lib.rs30.0 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, RefungibleExtensionsWeightInfo,49};50pub mod eth;5152#[cfg(feature = "runtime-benchmarks")]53mod benchmarking;54pub mod weights;55use weights::WeightInfo;5657const NESTING_BUDGET: u32 = 5;5859decl_error! {60	/// Error for non-fungible-token module.61	pub enum Error for Module<T: Config> {62		/// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.63		CollectionDecimalPointLimitExceeded,64		/// This address is not set as sponsor, use setCollectionSponsor first.65		ConfirmUnsetSponsorFail,66		/// Length of items properties must be greater than 0.67		EmptyArgument,68		/// Repertition is only supported by refungible collection69		RepartitionCalledOnNonRefungibleCollection,70	}71}7273pub trait Config: system::Config + pallet_common::Config + Sized + TypeInfo {74	type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;7576	/// Weight information for extrinsics in this pallet.77	type WeightInfo: WeightInfo;78	type CommonWeightInfo: CommonWeightInfo<Self::CrossAccountId>;79	type RefungibleExtensionsWeightInfo: RefungibleExtensionsWeightInfo;80}8182decl_event! {83	pub enum Event<T>84	where85		<T as frame_system::Config>::AccountId,86		<T as pallet_evm::account::Config>::CrossAccountId,87	{88		/// Collection sponsor was removed89		///90		/// # Arguments91		///92		/// * collection_id: Globally unique collection identifier.93		CollectionSponsorRemoved(CollectionId),9495		/// Collection admin was added96		///97		/// # Arguments98		///99		/// * collection_id: Globally unique collection identifier.100		///101		/// * admin:  Admin address.102		CollectionAdminAdded(CollectionId, CrossAccountId),103104		/// Collection owned was change105		///106		/// # Arguments107		///108		/// * collection_id: Globally unique collection identifier.109		///110		/// * owner:  New owner address.111		CollectionOwnedChanged(CollectionId, AccountId),112113		/// Collection sponsor was set114		///115		/// # Arguments116		///117		/// * collection_id: Globally unique collection identifier.118		///119		/// * owner:  New sponsor address.120		CollectionSponsorSet(CollectionId, AccountId),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),166	}167}168169type SelfWeightOf<T> = <T as Config>::WeightInfo;170171// # Used definitions172//173// ## User control levels174//175// chain-controlled - key is uncontrolled by user176//                    i.e autoincrementing index177//                    can use non-cryptographic hash178// real - key is controlled by user179//        but it is hard to generate enough colliding values, i.e owner of signed txs180//        can use non-cryptographic hash181// controlled - key is completly controlled by users182//              i.e maps with mutable keys183//              should use cryptographic hash184//185// ## User control level downgrade reasons186//187// ?1 - chain-controlled -> controlled188//      collections/tokens can be destroyed, resulting in massive holes189// ?2 - chain-controlled -> controlled190//      same as ?1, but can be only added, resulting in easier exploitation191// ?3 - real -> controlled192//      no confirmation required, so addresses can be easily generated193decl_storage! {194	trait Store for Module<T: Config> as Unique {195196		//#region Private members197		/// Used for migrations198		ChainVersion: u64;199		//#endregion200201		//#region Tokens transfer rate limit baskets202		/// (Collection id (controlled?2), who created (real))203		/// TODO: Off chain worker should remove from this map when collection gets removed204		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;205		/// Collection id (controlled?2), token id (controlled?2)206		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;207		/// Collection id (controlled?2), owning user (real)208		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;209		/// Collection id (controlled?2), token id (controlled?2)210		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>;211		//#endregion212213		/// Variable metadata sponsoring214		/// Collection id (controlled?2), token id (controlled?2)215		#[deprecated]216		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;217		pub TokenPropertyBasket get(fn token_property_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;218219		/// Approval sponsoring220		pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;221		pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;222		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>;223	}224}225226decl_module! {227	pub struct Module<T: Config> for enum Call228	where229		origin: T::Origin230	{231		type Error = Error<T>;232233		fn deposit_event() = default;234235		fn on_initialize(_now: T::BlockNumber) -> Weight {236			0237		}238239		fn on_runtime_upgrade() -> Weight {240			0241		}242243		/// 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.244		///245		/// # Permissions246		///247		/// * Anyone.248		///249		/// # Arguments250		///251		/// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.252		///253		/// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.254		///255		/// * token_prefix: UTF-8 string with token prefix.256		///257		/// * mode: [CollectionMode] collection type and type dependent data.258		// returns collection ID259		#[weight = <SelfWeightOf<T>>::create_collection()]260		#[transactional]261		#[deprecated]262		pub fn create_collection(origin,263								 collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,264								 collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,265								 token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,266								 mode: CollectionMode) -> DispatchResult  {267			let data: CreateCollectionData<T::AccountId> = CreateCollectionData {268				name: collection_name,269				description: collection_description,270				token_prefix,271				mode,272				..Default::default()273			};274			Self::create_collection_ex(origin, data)275		}276277		/// This method creates a collection278		///279		/// Prefer it to deprecated [`created_collection`] method280		#[weight = <SelfWeightOf<T>>::create_collection()]281		#[transactional]282		pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {283			let sender = ensure_signed(origin)?;284285			// =========286287			T::CollectionDispatch::create(T::CrossAccountId::from_sub(sender), data)?;288289			Ok(())290		}291292		/// Destroys collection if no tokens within this collection293		///294		/// # Permissions295		///296		/// * Collection Owner.297		///298		/// # Arguments299		///300		/// * collection_id: collection to destroy.301		#[weight = <SelfWeightOf<T>>::destroy_collection()]302		#[transactional]303		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {304			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);305			let collection = <CollectionHandle<T>>::try_get(collection_id)?;306			collection.check_is_internal()?;307308			// =========309310			T::CollectionDispatch::destroy(sender, collection)?;311312			<NftTransferBasket<T>>::remove_prefix(collection_id, None);313			<FungibleTransferBasket<T>>::remove_prefix(collection_id, None);314			<ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);315316			<NftApproveBasket<T>>::remove_prefix(collection_id, None);317			<FungibleApproveBasket<T>>::remove_prefix(collection_id, None);318			<RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);319320			Ok(())321		}322323		/// Add an address to allow list.324		///325		/// # Permissions326		///327		/// * Collection Owner328		/// * Collection Admin329		///330		/// # Arguments331		///332		/// * collection_id.333		///334		/// * address.335		#[weight = <SelfWeightOf<T>>::add_to_allow_list()]336		#[transactional]337		pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{338339			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);340			let collection = <CollectionHandle<T>>::try_get(collection_id)?;341			collection.check_is_internal()?;342343			<PalletCommon<T>>::toggle_allowlist(344				&collection,345				&sender,346				&address,347				true,348			)?;349350			Self::deposit_event(Event::<T>::AllowListAddressAdded(351				collection_id,352				address353			));354355			Ok(())356		}357358		/// Remove an address from allow list.359		///360		/// # Permissions361		///362		/// * Collection Owner363		/// * Collection Admin364		///365		/// # Arguments366		///367		/// * collection_id.368		///369		/// * address.370		#[weight = <SelfWeightOf<T>>::remove_from_allow_list()]371		#[transactional]372		pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{373374			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);375			let collection = <CollectionHandle<T>>::try_get(collection_id)?;376			collection.check_is_internal()?;377378			<PalletCommon<T>>::toggle_allowlist(379				&collection,380				&sender,381				&address,382				false,383			)?;384385			<Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(386				collection_id,387				address388			));389390			Ok(())391		}392393		/// Change the owner of the collection.394		///395		/// # Permissions396		///397		/// * Collection Owner.398		///399		/// # Arguments400		///401		/// * collection_id.402		///403		/// * new_owner.404		#[weight = <SelfWeightOf<T>>::change_collection_owner()]405		#[transactional]406		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {407408			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);409410			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;411			target_collection.check_is_internal()?;412			target_collection.check_is_owner(&sender)?;413414			target_collection.owner = new_owner.clone();415			<Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(416				collection_id,417				new_owner418			));419420			target_collection.save()421		}422423		/// Adds an admin of the Collection.424		/// 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.425		///426		/// # Permissions427		///428		/// * Collection Owner.429		/// * Collection Admin.430		///431		/// # Arguments432		///433		/// * collection_id: ID of the Collection to add admin for.434		///435		/// * new_admin_id: Address of new admin to add.436		#[weight = <SelfWeightOf<T>>::add_collection_admin()]437		#[transactional]438		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {439			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);440			let collection = <CollectionHandle<T>>::try_get(collection_id)?;441			collection.check_is_internal()?;442443			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(444				collection_id,445				new_admin_id.clone()446			));447448			<PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)449		}450451		/// 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.452		///453		/// # Permissions454		///455		/// * Collection Owner.456		/// * Collection Admin.457		///458		/// # Arguments459		///460		/// * collection_id: ID of the Collection to remove admin for.461		///462		/// * account_id: Address of admin to remove.463		#[weight = <SelfWeightOf<T>>::remove_collection_admin()]464		#[transactional]465		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {466			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);467			let collection = <CollectionHandle<T>>::try_get(collection_id)?;468			collection.check_is_internal()?;469470			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(471				collection_id,472				account_id.clone()473			));474475			<PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)476		}477478		/// # Permissions479		///480		/// * Collection Owner481		///482		/// # Arguments483		///484		/// * collection_id.485		///486		/// * new_sponsor.487		#[weight = <SelfWeightOf<T>>::set_collection_sponsor()]488		#[transactional]489		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {490			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);491492			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;493			target_collection.check_is_owner_or_admin(&sender)?;494			target_collection.check_is_internal()?;495496			target_collection.set_sponsor(new_sponsor.clone())?;497498			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(499				collection_id,500				new_sponsor501			));502503			target_collection.save()504		}505506		/// # Permissions507		///508		/// * Sponsor.509		///510		/// # Arguments511		///512		/// * collection_id.513		#[weight = <SelfWeightOf<T>>::confirm_sponsorship()]514		#[transactional]515		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {516			let sender = ensure_signed(origin)?;517518			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;519			target_collection.check_is_internal()?;520			ensure!(521				target_collection.confirm_sponsorship(&sender)?,522				Error::<T>::ConfirmUnsetSponsorFail523			);524525			<Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(526				collection_id,527				sender528			));529530			target_collection.save()531		}532533		/// Switch back to pay-per-own-transaction model.534		///535		/// # Permissions536		///537		/// * Collection owner.538		///539		/// # Arguments540		///541		/// * collection_id.542		#[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]543		#[transactional]544		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {545			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);546547			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;548			target_collection.check_is_internal()?;549			target_collection.check_is_owner(&sender)?;550551			target_collection.sponsorship = SponsorshipState::Disabled;552553			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(554				collection_id555			));556			target_collection.save()557		}558559		/// This method creates a concrete instance of NFT Collection created with CreateCollection method.560		///561		/// # Permissions562		///563		/// * Collection Owner.564		/// * Collection Admin.565		/// * Anyone if566		///     * Allow List is enabled, and567		///     * Address is added to allow list, and568		///     * MintPermission is enabled (see SetMintPermission method)569		///570		/// # Arguments571		///572		/// * collection_id: ID of the collection.573		///574		/// * owner: Address, initial owner of the NFT.575		///576		/// * data: Token data to store on chain.577		#[weight = T::CommonWeightInfo::create_item()]578		#[transactional]579		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {580			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);581			let budget = budget::Value::new(NESTING_BUDGET);582583			dispatch_tx::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))584		}585586		/// This method creates multiple items in a 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		/// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].602		///603		/// * owner: Address, initial owner of the NFT.604		#[weight = T::CommonWeightInfo::create_multiple_items(&items_data)]605		#[transactional]606		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {607			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);608			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);609			let budget = budget::Value::new(NESTING_BUDGET);610611			dispatch_tx::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))612		}613614		#[weight = T::CommonWeightInfo::set_collection_properties(properties.len() as u32)]615		#[transactional]616		pub fn set_collection_properties(617			origin,618			collection_id: CollectionId,619			properties: Vec<Property>620		) -> DispatchResultWithPostInfo {621			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);622623			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);624625			dispatch_tx::<T, _>(collection_id, |d| d.set_collection_properties(sender, properties))626		}627628		#[weight = T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32)]629		#[transactional]630		pub fn delete_collection_properties(631			origin,632			collection_id: CollectionId,633			property_keys: Vec<PropertyKey>,634		) -> DispatchResultWithPostInfo {635			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);636637			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);638639			dispatch_tx::<T, _>(collection_id, |d| d.delete_collection_properties(&sender, property_keys))640		}641642		#[weight = T::CommonWeightInfo::set_token_properties(properties.len() as u32)]643		#[transactional]644		pub fn set_token_properties(645			origin,646			collection_id: CollectionId,647			token_id: TokenId,648			properties: Vec<Property>649		) -> DispatchResultWithPostInfo {650			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);651652			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);653			let budget = budget::Value::new(NESTING_BUDGET);654655			dispatch_tx::<T, _>(collection_id, |d| d.set_token_properties(sender, token_id, properties, &budget))656		}657658		#[weight = T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32)]659		#[transactional]660		pub fn delete_token_properties(661			origin,662			collection_id: CollectionId,663			token_id: TokenId,664			property_keys: Vec<PropertyKey>665		) -> DispatchResultWithPostInfo {666			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);667668			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);669			let budget = budget::Value::new(NESTING_BUDGET);670671			dispatch_tx::<T, _>(collection_id, |d| d.delete_token_properties(sender, token_id, property_keys, &budget))672		}673674		#[weight = T::CommonWeightInfo::set_token_property_permissions(property_permissions.len() as u32)]675		#[transactional]676		pub fn set_token_property_permissions(677			origin,678			collection_id: CollectionId,679			property_permissions: Vec<PropertyKeyPermission>,680		) -> DispatchResultWithPostInfo {681			ensure!(!property_permissions.is_empty(), Error::<T>::EmptyArgument);682683			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);684685			dispatch_tx::<T, _>(collection_id, |d| d.set_token_property_permissions(&sender, property_permissions))686		}687688		#[weight = T::CommonWeightInfo::create_multiple_items_ex(&data)]689		#[transactional]690		pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {691			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);692			let budget = budget::Value::new(NESTING_BUDGET);693694			dispatch_tx::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))695		}696697		/// Set transfers_enabled value for particular collection698		///699		/// # Permissions700		///701		/// * Collection Owner.702		///703		/// # Arguments704		///705		/// * collection_id: ID of the collection.706		///707		/// * value: New flag value.708		#[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]709		#[transactional]710		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {711			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);712			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;713			target_collection.check_is_internal()?;714			target_collection.check_is_owner(&sender)?;715716			// =========717718			target_collection.limits.transfers_enabled = Some(value);719			target_collection.save()720		}721722		/// Destroys a concrete instance of NFT.723		///724		/// # Permissions725		///726		/// * Collection Owner.727		/// * Collection Admin.728		/// * Current NFT Owner.729		///730		/// # Arguments731		///732		/// * collection_id: ID of the collection.733		///734		/// * item_id: ID of NFT to burn.735		#[weight = T::CommonWeightInfo::burn_item()]736		#[transactional]737		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {738			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);739740			let post_info = dispatch_tx::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;741			if value == 1 {742				<NftTransferBasket<T>>::remove(collection_id, item_id);743				<NftApproveBasket<T>>::remove(collection_id, item_id);744			}745			// Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?746			// <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());747			// <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));748			Ok(post_info)749		}750751		/// Destroys a concrete instance of NFT on behalf of the owner752		/// See also: [`approve`]753		///754		/// # Permissions755		///756		/// * Collection Owner.757		/// * Collection Admin.758		/// * Current NFT Owner.759		///760		/// # Arguments761		///762		/// * collection_id: ID of the collection.763		///764		/// * item_id: ID of NFT to burn.765		///766		/// * from: owner of item767		#[weight = T::CommonWeightInfo::burn_from()]768		#[transactional]769		pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {770			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);771			let budget = budget::Value::new(NESTING_BUDGET);772773			dispatch_tx::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value, &budget))774		}775776		/// Change ownership of the token.777		///778		/// # Permissions779		///780		/// * Collection Owner781		/// * Collection Admin782		/// * Current NFT owner783		///784		/// # Arguments785		///786		/// * recipient: Address of token recipient.787		///788		/// * collection_id.789		///790		/// * item_id: ID of the item791		///     * Non-Fungible Mode: Required.792		///     * Fungible Mode: Ignored.793		///     * Re-Fungible Mode: Required.794		///795		/// * value: Amount to transfer.796		///     * Non-Fungible Mode: Ignored797		///     * Fungible Mode: Must specify transferred amount798		///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)799		#[weight = T::CommonWeightInfo::transfer()]800		#[transactional]801		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {802			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);803			let budget = budget::Value::new(NESTING_BUDGET);804805			dispatch_tx::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value, &budget))806		}807808		/// Set, change, or remove approved address to transfer the ownership of the NFT.809		///810		/// # Permissions811		///812		/// * Collection Owner813		/// * Collection Admin814		/// * Current NFT owner815		///816		/// # Arguments817		///818		/// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).819		///820		/// * collection_id.821		///822		/// * item_id: ID of the item.823		#[weight = T::CommonWeightInfo::approve()]824		#[transactional]825		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {826			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);827828			dispatch_tx::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))829		}830831		/// 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.832		///833		/// # Permissions834		/// * Collection Owner835		/// * Collection Admin836		/// * Current NFT owner837		/// * Address approved by current NFT owner838		///839		/// # Arguments840		///841		/// * from: Address that owns token.842		///843		/// * recipient: Address of token recipient.844		///845		/// * collection_id.846		///847		/// * item_id: ID of the item.848		///849		/// * value: Amount to transfer.850		#[weight = T::CommonWeightInfo::transfer_from()]851		#[transactional]852		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {853			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);854			let budget = budget::Value::new(NESTING_BUDGET);855856			dispatch_tx::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))857		}858859		#[weight = <SelfWeightOf<T>>::set_collection_limits()]860		#[transactional]861		pub fn set_collection_limits(862			origin,863			collection_id: CollectionId,864			new_limit: CollectionLimits,865		) -> DispatchResult {866			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);867			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;868			target_collection.check_is_internal()?;869			target_collection.check_is_owner_or_admin(&sender)?;870			let old_limit = &target_collection.limits;871872			target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;873874			<Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(875				collection_id876			));877878			target_collection.save()879		}880881		#[weight = <SelfWeightOf<T>>::set_collection_limits()]882		#[transactional]883		pub fn set_collection_permissions(884			origin,885			collection_id: CollectionId,886			new_limit: CollectionPermissions,887		) -> DispatchResult {888			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);889			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;890			target_collection.check_is_internal()?;891			target_collection.check_is_owner_or_admin(&sender)?;892			let old_limit = &target_collection.permissions;893894			target_collection.permissions = <PalletCommon<T>>::clamp_permissions(target_collection.mode.clone(), &old_limit, new_limit)?;895896			<Pallet<T>>::deposit_event(Event::<T>::CollectionPermissionSet(897				collection_id898			));899900			target_collection.save()901		}902903		#[weight = T::RefungibleExtensionsWeightInfo::repartition()]904		#[transactional]905		pub fn repartition(906			origin,907			collection_id: CollectionId,908			token: TokenId,909			amount: u128,910		) -> DispatchResultWithPostInfo {911			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);912			dispatch_tx::<T, _>(collection_id, |d| {913				if let Some(refungible_extensions) = d.refungible_extensions() {914					refungible_extensions.repartition(&sender, token, amount)915				} else {916					fail!(<Error<T>>::RepartitionCalledOnNonRefungibleCollection)917				}918			})919		}920	}921}