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

difftreelog

source

pallets/unique/src/lib.rs29.7 KiBsourcehistory
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![recursion_limit = "1024"]18#![cfg_attr(not(feature = "std"), no_std)]19#![allow(20	clippy::too_many_arguments,21	clippy::unnecessary_mut_passed,22	clippy::unused_unit23)]2425use frame_support::{26	decl_module, decl_storage, decl_error, decl_event,27	dispatch::DispatchResult,28	ensure,29	weights::{Weight},30	transactional,31	pallet_prelude::{DispatchResultWithPostInfo, ConstU32},32	BoundedVec,33};34use scale_info::TypeInfo;35use frame_system::{self as system, ensure_signed};36use sp_runtime::{sp_std::prelude::Vec};37use up_data_structs::{38	MAX_COLLECTION_NAME_LENGTH,39	MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH, AccessMode, CreateItemData,40	CollectionLimits, CollectionPermissions, CollectionId, CollectionMode, TokenId, SponsorshipState,41	CreateCollectionData, CreateItemExData, budget, Property, PropertyKey,42	PropertyKeyPermission,43};44use pallet_evm::account::CrossAccountId;45use pallet_common::{46	CollectionHandle, Pallet as PalletCommon, CommonWeightInfo, dispatch::dispatch_call,47	dispatch::CollectionDispatch,48};4950#[cfg(feature = "runtime-benchmarks")]51mod benchmarking;52pub mod weights;53use weights::WeightInfo;5455const NESTING_BUDGET: u32 = 5;5657decl_error! {58	/// Error for non-fungible-token module.59	pub enum Error for Module<T: Config> {60		/// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.61		CollectionDecimalPointLimitExceeded,62		/// This address is not set as sponsor, use setCollectionSponsor first.63		ConfirmUnsetSponsorFail,64		/// Length of items properties must be greater than 0.65		EmptyArgument,66	}67}6869pub trait Config: system::Config + pallet_common::Config + Sized + TypeInfo {70	type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;7172	/// Weight information for extrinsics in this pallet.73	type WeightInfo: WeightInfo;74	type CommonWeightInfo: CommonWeightInfo<Self::CrossAccountId>;75}7677decl_event! {78	pub enum Event<T>79	where80		<T as frame_system::Config>::AccountId,81		<T as pallet_evm::account::Config>::CrossAccountId,82	{83		/// Collection sponsor was removed84		///85		/// # Arguments86		///87		/// * collection_id: Globally unique collection identifier.88		CollectionSponsorRemoved(CollectionId),8990		/// Collection admin was added91		///92		/// # Arguments93		///94		/// * collection_id: Globally unique collection identifier.95		///96		/// * admin:  Admin address.97		CollectionAdminAdded(CollectionId, CrossAccountId),9899		/// Collection owned was change100		///101		/// # Arguments102		///103		/// * collection_id: Globally unique collection identifier.104		///105		/// * owner:  New owner address.106		CollectionOwnedChanged(CollectionId, AccountId),107108		/// Collection sponsor was set109		///110		/// # Arguments111		///112		/// * collection_id: Globally unique collection identifier.113		///114		/// * owner:  New sponsor address.115		CollectionSponsorSet(CollectionId, AccountId),116117		/// const on chain schema was set118		///119		/// # Arguments120		///121		/// * collection_id: Globally unique collection identifier.122		ConstOnChainSchemaSet(CollectionId),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),168169		/// Mint permission	was set170		///171		/// # Arguments172		///173		/// * collection_id: Globally unique collection identifier.174		MintPermissionSet(CollectionId),175176		/// Offchain schema was set177		///178		/// # Arguments179		///180		/// * collection_id: Globally unique collection identifier.181		OffchainSchemaSet(CollectionId),182183		/// Public access mode was set184		///185		/// # Arguments186		///187		/// * collection_id: Globally unique collection identifier.188		///189		/// * mode: New access state.190		PublicAccessModeSet(CollectionId, AccessMode),191192		/// Schema version was set193		///194		/// # Arguments195		///196		/// * collection_id: Globally unique collection identifier.197		SchemaVersionSet(CollectionId),198	}199}200201type SelfWeightOf<T> = <T as Config>::WeightInfo;202203// # Used definitions204//205// ## User control levels206//207// chain-controlled - key is uncontrolled by user208//                    i.e autoincrementing index209//                    can use non-cryptographic hash210// real - key is controlled by user211//        but it is hard to generate enough colliding values, i.e owner of signed txs212//        can use non-cryptographic hash213// controlled - key is completly controlled by users214//              i.e maps with mutable keys215//              should use cryptographic hash216//217// ## User control level downgrade reasons218//219// ?1 - chain-controlled -> controlled220//      collections/tokens can be destroyed, resulting in massive holes221// ?2 - chain-controlled -> controlled222//      same as ?1, but can be only added, resulting in easier exploitation223// ?3 - real -> controlled224//      no confirmation required, so addresses can be easily generated225decl_storage! {226	trait Store for Module<T: Config> as Unique {227228		//#region Private members229		/// Used for migrations230		ChainVersion: u64;231		//#endregion232233		//#region Tokens transfer rate limit baskets234		/// (Collection id (controlled?2), who created (real))235		/// TODO: Off chain worker should remove from this map when collection gets removed236		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;237		/// Collection id (controlled?2), token id (controlled?2)238		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;239		/// Collection id (controlled?2), owning user (real)240		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;241		/// Collection id (controlled?2), token id (controlled?2)242		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>;243		//#endregion244245		/// Variable metadata sponsoring246		/// Collection id (controlled?2), token id (controlled?2)247		#[deprecated]248		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;249		pub TokenPropertyBasket get(fn token_property_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;250251		/// Approval sponsoring252		pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;253		pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;254		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>;255	}256}257258decl_module! {259	pub struct Module<T: Config> for enum Call260	where261		origin: T::Origin262	{263		type Error = Error<T>;264265		fn deposit_event() = default;266267		fn on_initialize(_now: T::BlockNumber) -> Weight {268			0269		}270271		fn on_runtime_upgrade() -> Weight {272			let limit = None;273274			<VariableMetaDataBasket<T>>::remove_all(limit);275276			0277		}278279		/// 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.280		///281		/// # Permissions282		///283		/// * Anyone.284		///285		/// # Arguments286		///287		/// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.288		///289		/// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.290		///291		/// * token_prefix: UTF-8 string with token prefix.292		///293		/// * mode: [CollectionMode] collection type and type dependent data.294		// returns collection ID295		#[weight = <SelfWeightOf<T>>::create_collection()]296		#[transactional]297		#[deprecated]298		pub fn create_collection(origin,299								 collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,300								 collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,301								 token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,302								 mode: CollectionMode) -> DispatchResult  {303			let data: CreateCollectionData<T::AccountId> = CreateCollectionData {304				name: collection_name,305				description: collection_description,306				token_prefix,307				mode,308				..Default::default()309			};310			Self::create_collection_ex(origin, data)311		}312313		/// This method creates a collection314		///315		/// Prefer it to deprecated [`created_collection`] method316		#[weight = <SelfWeightOf<T>>::create_collection()]317		#[transactional]318		pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {319			let sender = ensure_signed(origin)?;320321			// =========322323			T::CollectionDispatch::create(sender, data)?;324325			Ok(())326		}327328		/// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.329		///330		/// # Permissions331		///332		/// * Collection Owner.333		///334		/// # Arguments335		///336		/// * collection_id: collection to destroy.337		#[weight = <SelfWeightOf<T>>::destroy_collection()]338		#[transactional]339		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {340			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);341			let collection = <CollectionHandle<T>>::try_get(collection_id)?;342343			// =========344345			T::CollectionDispatch::destroy(sender, collection)?;346347			<NftTransferBasket<T>>::remove_prefix(collection_id, None);348			<FungibleTransferBasket<T>>::remove_prefix(collection_id, None);349			<ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);350351			<NftApproveBasket<T>>::remove_prefix(collection_id, None);352			<FungibleApproveBasket<T>>::remove_prefix(collection_id, None);353			<RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);354355			Ok(())356		}357358		/// Add an address to allow list.359		///360		/// # Permissions361		///362		/// * Collection Owner363		/// * Collection Admin364		///365		/// # Arguments366		///367		/// * collection_id.368		///369		/// * address.370		#[weight = <SelfWeightOf<T>>::add_to_allow_list()]371		#[transactional]372		pub fn add_to_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)?;376377			<PalletCommon<T>>::toggle_allowlist(378				&collection,379				&sender,380				&address,381				true,382			)?;383384			Self::deposit_event(Event::<T>::AllowListAddressAdded(385				collection_id,386				address387			));388389			Ok(())390		}391392		/// Remove an address from allow list.393		///394		/// # Permissions395		///396		/// * Collection Owner397		/// * Collection Admin398		///399		/// # Arguments400		///401		/// * collection_id.402		///403		/// * address.404		#[weight = <SelfWeightOf<T>>::remove_from_allow_list()]405		#[transactional]406		pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{407408			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);409			let collection = <CollectionHandle<T>>::try_get(collection_id)?;410411			<PalletCommon<T>>::toggle_allowlist(412				&collection,413				&sender,414				&address,415				false,416			)?;417418			<Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(419				collection_id,420				address421			));422423			Ok(())424		}425426		/// Change the owner of the collection.427		///428		/// # Permissions429		///430		/// * Collection Owner.431		///432		/// # Arguments433		///434		/// * collection_id.435		///436		/// * new_owner.437		#[weight = <SelfWeightOf<T>>::change_collection_owner()]438		#[transactional]439		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {440441			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);442443			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;444			target_collection.check_is_owner(&sender)?;445446			target_collection.owner = new_owner.clone();447			<Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(448				collection_id,449				new_owner450			));451452			target_collection.save()453		}454455		/// Adds an admin of the Collection.456		/// 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.457		///458		/// # Permissions459		///460		/// * Collection Owner.461		/// * Collection Admin.462		///463		/// # Arguments464		///465		/// * collection_id: ID of the Collection to add admin for.466		///467		/// * new_admin_id: Address of new admin to add.468		#[weight = <SelfWeightOf<T>>::add_collection_admin()]469		#[transactional]470		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {471			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);472			let collection = <CollectionHandle<T>>::try_get(collection_id)?;473474			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(475				collection_id,476				new_admin_id.clone()477			));478479			<PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)480		}481482		/// 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.483		///484		/// # Permissions485		///486		/// * Collection Owner.487		/// * Collection Admin.488		///489		/// # Arguments490		///491		/// * collection_id: ID of the Collection to remove admin for.492		///493		/// * account_id: Address of admin to remove.494		#[weight = <SelfWeightOf<T>>::remove_collection_admin()]495		#[transactional]496		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {497			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);498			let collection = <CollectionHandle<T>>::try_get(collection_id)?;499500			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(501				collection_id,502				account_id.clone()503			));504505			<PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)506		}507508		/// # Permissions509		///510		/// * Collection Owner511		///512		/// # Arguments513		///514		/// * collection_id.515		///516		/// * new_sponsor.517		#[weight = <SelfWeightOf<T>>::set_collection_sponsor()]518		#[transactional]519		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {520			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);521522			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;523			target_collection.check_is_owner(&sender)?;524525			target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor.clone());526527			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(528				collection_id,529				new_sponsor530			));531532			target_collection.save()533		}534535		/// # Permissions536		///537		/// * Sponsor.538		///539		/// # Arguments540		///541		/// * collection_id.542		#[weight = <SelfWeightOf<T>>::confirm_sponsorship()]543		#[transactional]544		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {545			let sender = ensure_signed(origin)?;546547			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;548			ensure!(549				target_collection.sponsorship.pending_sponsor() == Some(&sender),550				Error::<T>::ConfirmUnsetSponsorFail551			);552553			target_collection.sponsorship = SponsorshipState::Confirmed(sender.clone());554555			<Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(556				collection_id,557				sender558			));559560			target_collection.save()561		}562563		/// Switch back to pay-per-own-transaction model.564		///565		/// # Permissions566		///567		/// * Collection owner.568		///569		/// # Arguments570		///571		/// * collection_id.572		#[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]573		#[transactional]574		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {575			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);576577			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;578			target_collection.check_is_owner(&sender)?;579580			target_collection.sponsorship = SponsorshipState::Disabled;581582			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(583				collection_id584			));585			target_collection.save()586		}587588		/// This method creates a concrete instance of NFT Collection created with CreateCollection method.589		///590		/// # Permissions591		///592		/// * Collection Owner.593		/// * Collection Admin.594		/// * Anyone if595		///     * Allow List is enabled, and596		///     * Address is added to allow list, and597		///     * MintPermission is enabled (see SetMintPermission method)598		///599		/// # Arguments600		///601		/// * collection_id: ID of the collection.602		///603		/// * owner: Address, initial owner of the NFT.604		///605		/// * data: Token data to store on chain.606		#[weight = T::CommonWeightInfo::create_item()]607		#[transactional]608		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {609			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);610			let budget = budget::Value::new(NESTING_BUDGET);611612			dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))613		}614615		/// This method creates multiple items in a collection created with CreateCollection method.616		///617		/// # Permissions618		///619		/// * Collection Owner.620		/// * Collection Admin.621		/// * Anyone if622		///     * Allow List is enabled, and623		///     * Address is added to allow list, and624		///     * MintPermission is enabled (see SetMintPermission method)625		///626		/// # Arguments627		///628		/// * collection_id: ID of the collection.629		///630		/// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].631		///632		/// * owner: Address, initial owner of the NFT.633		#[weight = T::CommonWeightInfo::create_multiple_items(&items_data)]634		#[transactional]635		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {636			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);637			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);638			let budget = budget::Value::new(NESTING_BUDGET);639640			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))641		}642643		#[weight = T::CommonWeightInfo::set_collection_properties(properties.len() as u32)]644		#[transactional]645		pub fn set_collection_properties(646			origin,647			collection_id: CollectionId,648			properties: Vec<Property>649		) -> DispatchResultWithPostInfo {650			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);651652			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);653654			dispatch_call::<T, _>(collection_id, |d| d.set_collection_properties(sender, properties))655		}656657		#[weight = T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32)]658		#[transactional]659		pub fn delete_collection_properties(660			origin,661			collection_id: CollectionId,662			property_keys: Vec<PropertyKey>,663		) -> DispatchResultWithPostInfo {664			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);665666			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);667668			dispatch_call::<T, _>(collection_id, |d| d.delete_collection_properties(&sender, property_keys))669		}670671		#[weight = T::CommonWeightInfo::set_token_properties(properties.len() as u32)]672		#[transactional]673		pub fn set_token_properties(674			origin,675			collection_id: CollectionId,676			token_id: TokenId,677			properties: Vec<Property>678		) -> DispatchResultWithPostInfo {679			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);680681			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);682683			dispatch_call::<T, _>(collection_id, |d| d.set_token_properties(sender, token_id, properties))684		}685686		#[weight = T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32)]687		#[transactional]688		pub fn delete_token_properties(689			origin,690			collection_id: CollectionId,691			token_id: TokenId,692			property_keys: Vec<PropertyKey>693		) -> DispatchResultWithPostInfo {694			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);695696			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);697698			dispatch_call::<T, _>(collection_id, |d| d.delete_token_properties(sender, token_id, property_keys))699		}700701		#[weight = T::CommonWeightInfo::set_property_permissions(property_permissions.len() as u32)]702		#[transactional]703		pub fn set_property_permissions(704			origin,705			collection_id: CollectionId,706			property_permissions: Vec<PropertyKeyPermission>,707		) -> DispatchResultWithPostInfo {708			ensure!(!property_permissions.is_empty(), Error::<T>::EmptyArgument);709710			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);711712			dispatch_call::<T, _>(collection_id, |d| d.set_property_permissions(&sender, property_permissions))713		}714715		#[weight = T::CommonWeightInfo::create_multiple_items_ex(&data)]716		#[transactional]717		pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {718			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);719			let budget = budget::Value::new(NESTING_BUDGET);720721			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))722		}723724		// TODO! transaction weight725726		/// Set transfers_enabled value for particular collection727		///728		/// # Permissions729		///730		/// * Collection Owner.731		///732		/// # Arguments733		///734		/// * collection_id: ID of the collection.735		///736		/// * value: New flag value.737		#[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]738		#[transactional]739		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {740			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);741			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;742			target_collection.check_is_owner(&sender)?;743744			// =========745746			target_collection.limits.transfers_enabled = Some(value);747			target_collection.save()748		}749750		/// Destroys a concrete instance of NFT.751		///752		/// # Permissions753		///754		/// * Collection Owner.755		/// * Collection Admin.756		/// * Current NFT Owner.757		///758		/// # Arguments759		///760		/// * collection_id: ID of the collection.761		///762		/// * item_id: ID of NFT to burn.763		#[weight = T::CommonWeightInfo::burn_item()]764		#[transactional]765		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {766			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);767768			let post_info = dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;769			if value == 1 {770				<NftTransferBasket<T>>::remove(collection_id, item_id);771				<NftApproveBasket<T>>::remove(collection_id, item_id);772			}773			// Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?774			// <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());775			// <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));776			Ok(post_info)777		}778779		/// Destroys a concrete instance of NFT on behalf of the owner780		/// See also: [`approve`]781		///782		/// # Permissions783		///784		/// * Collection Owner.785		/// * Collection Admin.786		/// * Current NFT Owner.787		///788		/// # Arguments789		///790		/// * collection_id: ID of the collection.791		///792		/// * item_id: ID of NFT to burn.793		///794		/// * from: owner of item795		#[weight = T::CommonWeightInfo::burn_from()]796		#[transactional]797		pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {798			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);799			let budget = budget::Value::new(NESTING_BUDGET);800801			dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value, &budget))802		}803804		/// Change ownership of the token.805		///806		/// # Permissions807		///808		/// * Collection Owner809		/// * Collection Admin810		/// * Current NFT owner811		///812		/// # Arguments813		///814		/// * recipient: Address of token recipient.815		///816		/// * collection_id.817		///818		/// * item_id: ID of the item819		///     * Non-Fungible Mode: Required.820		///     * Fungible Mode: Ignored.821		///     * Re-Fungible Mode: Required.822		///823		/// * value: Amount to transfer.824		///     * Non-Fungible Mode: Ignored825		///     * Fungible Mode: Must specify transferred amount826		///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)827		#[weight = T::CommonWeightInfo::transfer()]828		#[transactional]829		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {830			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);831			let budget = budget::Value::new(NESTING_BUDGET);832833			dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value, &budget))834		}835836		/// Set, change, or remove approved address to transfer the ownership of the NFT.837		///838		/// # Permissions839		///840		/// * Collection Owner841		/// * Collection Admin842		/// * Current NFT owner843		///844		/// # Arguments845		///846		/// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).847		///848		/// * collection_id.849		///850		/// * item_id: ID of the item.851		#[weight = T::CommonWeightInfo::approve()]852		#[transactional]853		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {854			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);855856			dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))857		}858859		/// 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.860		///861		/// # Permissions862		/// * Collection Owner863		/// * Collection Admin864		/// * Current NFT owner865		/// * Address approved by current NFT owner866		///867		/// # Arguments868		///869		/// * from: Address that owns token.870		///871		/// * recipient: Address of token recipient.872		///873		/// * collection_id.874		///875		/// * item_id: ID of the item.876		///877		/// * value: Amount to transfer.878		#[weight = T::CommonWeightInfo::transfer_from()]879		#[transactional]880		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {881			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);882			let budget = budget::Value::new(NESTING_BUDGET);883884			dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))885		}886887		#[weight = <SelfWeightOf<T>>::set_collection_limits()]888		#[transactional]889		pub fn set_collection_limits(890			origin,891			collection_id: CollectionId,892			new_limit: CollectionLimits,893		) -> DispatchResult {894			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);895			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;896			target_collection.check_is_owner(&sender)?;897			let old_limit = &target_collection.limits;898899			target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;900901			<Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(902				collection_id903			));904905			target_collection.save()906		}907908		#[weight = <SelfWeightOf<T>>::set_collection_limits()]909		#[transactional]910		pub fn set_collection_permissions(911			origin,912			collection_id: CollectionId,913			new_limit: CollectionPermissions,914		) -> DispatchResult {915			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);916			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;917			target_collection.check_is_owner(&sender)?;918			let old_limit = &target_collection.permissions;919920			target_collection.permissions = <PalletCommon<T>>::clamp_permissions(target_collection.mode.clone(), &old_limit, new_limit)?;921922			<Pallet<T>>::deposit_event(Event::<T>::CollectionPermissionSet(923				collection_id924			));925926			target_collection.save()927		}928	}929}