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

difftreelog

source

pallets/unique/src/lib.rs33.8 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 changed105		///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 removed from the allow list141		///142		/// # Arguments143		///144		/// * collection_id: Globally unique collection identifier.145		///146		/// * user:  Address.147		AllowListAddressRemoved(CollectionId, CrossAccountId),148149		/// Address was added to the allow list150		///151		/// # Arguments152		///153		/// * collection_id: Globally unique collection identifier.154		///155		/// * user:  Address.156		AllowListAddressAdded(CollectionId, CrossAccountId),157158		/// Collection limits were set159		///160		/// # Arguments161		///162		/// * collection_id: Globally unique collection identifier.163		CollectionLimitSet(CollectionId),164165		/// Collection permissions were set166		///167		/// # Arguments168		///169		/// * collection_id: Globally unique collection identifier.170		CollectionPermissionSet(CollectionId),171	}172}173174type SelfWeightOf<T> = <T as Config>::WeightInfo;175176// # Used definitions177//178// ## User control levels179//180// chain-controlled - key is uncontrolled by user181//                    i.e autoincrementing index182//                    can use non-cryptographic hash183// real - key is controlled by user184//        but it is hard to generate enough colliding values, i.e owner of signed txs185//        can use non-cryptographic hash186// controlled - key is completly controlled by users187//              i.e maps with mutable keys188//              should use cryptographic hash189//190// ## User control level downgrade reasons191//192// ?1 - chain-controlled -> controlled193//      collections/tokens can be destroyed, resulting in massive holes194// ?2 - chain-controlled -> controlled195//      same as ?1, but can be only added, resulting in easier exploitation196// ?3 - real -> controlled197//      no confirmation required, so addresses can be easily generated198decl_storage! {199	trait Store for Module<T: Config> as Unique {200201		//#region Private members202		/// Used for migrations203		ChainVersion: u64;204		//#endregion205206		//#region Tokens transfer sponosoring rate limit baskets207		/// (Collection id (controlled?2), who created (real))208		/// TODO: Off chain worker should remove from this map when collection gets removed209		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;210		/// Collection id (controlled?2), token id (controlled?2)211		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;212		/// Collection id (controlled?2), owning user (real)213		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;214		/// Collection id (controlled?2), token id (controlled?2)215		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>;216		//#endregion217218		/// Variable metadata sponsoring219		/// Collection id (controlled?2), token id (controlled?2)220		#[deprecated]221		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;222		/// Last sponsoring of token property setting // todo:doc rephrase this and the following223		pub TokenPropertyBasket get(fn token_property_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;224225		/// Last sponsoring of NFT approval in a collection226		pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;227		/// Last sponsoring of fungible tokens approval in a collection228		pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;229		/// Last sponsoring of RFT approval in a collection230		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>;231	}232}233234decl_module! {235	pub struct Module<T: Config> for enum Call236	where237		origin: T::Origin238	{239		type Error = Error<T>;240241		fn deposit_event() = default;242243		fn on_initialize(_now: T::BlockNumber) -> Weight {244			0245		}246247		fn on_runtime_upgrade() -> Weight {248			let limit = None;249250			<VariableMetaDataBasket<T>>::remove_all(limit);251252			0253		}254255		/// 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.256		///257		/// # Permissions258		///259		/// * Anyone.260		///261		/// # Arguments262		///263		/// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.264		/// * collection_description - UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.265		/// * token_prefix - UTF-8 string with token prefix.266		/// * mode - [CollectionMode] collection type and type dependent data.267		// returns collection ID268		#[weight = <SelfWeightOf<T>>::create_collection()]269		#[transactional]270		#[deprecated]271		pub fn create_collection(origin,272								 collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,273								 collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,274								 token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,275								 mode: CollectionMode) -> DispatchResult  {276			let data: CreateCollectionData<T::AccountId> = CreateCollectionData {277				name: collection_name,278				description: collection_description,279				token_prefix,280				mode,281				..Default::default()282			};283			Self::create_collection_ex(origin, data)284		}285286		/// Create a collection with explicit parameters.287		/// Prefer it to the deprecated [`created_collection`] method.288		///289		/// # Permissions290		///291		/// * Anyone.292		///293		/// # Arguments294		///295		/// * data: explicit create-collection data.296		#[weight = <SelfWeightOf<T>>::create_collection()]297		#[transactional]298		pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {299			let sender = ensure_signed(origin)?;300301			// =========302303			T::CollectionDispatch::create(T::CrossAccountId::from_sub(sender), data)?;304305			Ok(())306		}307308		/// Destroy the collection if no tokens exist within.309		///310		/// # Permissions311		///312		/// * Collection Owner313		///314		/// # Arguments315		///316		/// * collection_id - collection to destroy.317		#[weight = <SelfWeightOf<T>>::destroy_collection()]318		#[transactional]319		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {320			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);321			let collection = <CollectionHandle<T>>::try_get(collection_id)?;322			collection.check_is_internal()?;323324			// =========325326			T::CollectionDispatch::destroy(sender, collection)?;327328			<NftTransferBasket<T>>::remove_prefix(collection_id, None);329			<FungibleTransferBasket<T>>::remove_prefix(collection_id, None);330			<ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);331332			<NftApproveBasket<T>>::remove_prefix(collection_id, None);333			<FungibleApproveBasket<T>>::remove_prefix(collection_id, None);334			<RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);335336			Ok(())337		}338339		/// Add an address to allow list.340		///341		/// # Permissions342		///343		/// * Collection Owner344		/// * Collection Admin345		///346		/// # Arguments347		///348		/// * collection_id.349		/// * address.350		#[weight = <SelfWeightOf<T>>::add_to_allow_list()]351		#[transactional]352		pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{353354			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);355			let collection = <CollectionHandle<T>>::try_get(collection_id)?;356			collection.check_is_internal()?;357358			<PalletCommon<T>>::toggle_allowlist(359				&collection,360				&sender,361				&address,362				true,363			)?;364365			Self::deposit_event(Event::<T>::AllowListAddressAdded(366				collection_id,367				address368			));369370			Ok(())371		}372373		/// Remove an address from allow list.374		///375		/// # Permissions376		///377		/// * Collection Owner378		/// * Collection Admin379		///380		/// # Arguments381		///382		/// * collection_id.383		/// * address.384		#[weight = <SelfWeightOf<T>>::remove_from_allow_list()]385		#[transactional]386		pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{387388			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);389			let collection = <CollectionHandle<T>>::try_get(collection_id)?;390			collection.check_is_internal()?;391392			<PalletCommon<T>>::toggle_allowlist(393				&collection,394				&sender,395				&address,396				false,397			)?;398399			<Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(400				collection_id,401				address402			));403404			Ok(())405		}406407		/// Change the owner of the collection.408		///409		/// # Permissions410		///411		/// * Collection Owner412		///413		/// # Arguments414		///415		/// * collection_id.416		/// * new_owner.417		#[weight = <SelfWeightOf<T>>::change_collection_owner()]418		#[transactional]419		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {420421			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);422423			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;424			target_collection.check_is_internal()?;425			target_collection.check_is_owner(&sender)?;426427			target_collection.owner = new_owner.clone();428			<Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(429				collection_id,430				new_owner431			));432433			target_collection.save()434		}435436		/// Adds an admin of the collection.437		/// 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.438		///439		/// # Permissions440		///441		/// * Collection Owner442		/// * Collection Admin443		///444		/// # Arguments445		///446		/// * collection_id - ID of the Collection to add admin for.447		/// * new_admin - Address of new admin to add.448		#[weight = <SelfWeightOf<T>>::add_collection_admin()]449		#[transactional]450		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin: T::CrossAccountId) -> DispatchResult {451			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);452			let collection = <CollectionHandle<T>>::try_get(collection_id)?;453			collection.check_is_internal()?;454455			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(456				collection_id,457				new_admin.clone()458			));459460			<PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin, true)461		}462463		/// 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.464		///465		/// # Permissions466		///467		/// * Collection Owner468		/// * Collection Admin469		///470		/// # Arguments471		///472		/// * collection_id - ID of the Collection to remove admin for.473		/// * account_id - Address of admin to remove.474		#[weight = <SelfWeightOf<T>>::remove_collection_admin()]475		#[transactional]476		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {477			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);478			let collection = <CollectionHandle<T>>::try_get(collection_id)?;479			collection.check_is_internal()?;480481			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(482				collection_id,483				account_id.clone()484			));485486			<PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)487		}488489		/// Set (invite) a new collection sponsor. If successful, confirmation from the sponsor-to-be will be pending.490		///491		/// # Permissions492		///493		/// * Collection Owner494		/// * Collection Admin495		///496		/// # Arguments497		///498		/// * collection_id.499		/// * new_sponsor.500		#[weight = <SelfWeightOf<T>>::set_collection_sponsor()]501		#[transactional]502		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {503			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);504505			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;506			target_collection.check_is_owner_or_admin(&sender)?;507			target_collection.check_is_internal()?;508509			target_collection.set_sponsor(new_sponsor.clone())?;510511			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(512				collection_id,513				new_sponsor514			));515516			target_collection.save()517		}518519		/// Confirm own sponsorship of a collection.520		///521		/// # Permissions522		///523		/// * Sponsor-to-be524		///525		/// # Arguments526		///527		/// * collection_id.528		#[weight = <SelfWeightOf<T>>::confirm_sponsorship()]529		#[transactional]530		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {531			let sender = ensure_signed(origin)?;532533			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;534			target_collection.check_is_internal()?;535			ensure!(536				target_collection.confirm_sponsorship(&sender)?,537				Error::<T>::ConfirmUnsetSponsorFail538			);539540			<Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(541				collection_id,542				sender543			));544545			target_collection.save()546		}547548		/// Switch back to pay-per-own-transaction model.549		///550		/// # Permissions551		///552		/// * Collection Owner553		///554		/// # Arguments555		///556		/// * collection_id.557		#[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]558		#[transactional]559		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {560			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);561562			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;563			target_collection.check_is_internal()?;564			target_collection.check_is_owner(&sender)?;565566			target_collection.sponsorship = SponsorshipState::Disabled;567568			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(569				collection_id570			));571			target_collection.save()572		}573574		/// Create a concrete instance of NFT Collection created with CreateCollection method.575		///576		/// # Permissions577		///578		/// * Collection Owner579		/// * Collection Admin580		/// * Anyone if581		///     * Allow List is enabled, and582		///     * Address is added to allow list, and583		///     * MintPermission is enabled (see SetMintPermission method)584		///585		/// # Arguments586		///587		/// * collection_id - ID of the collection.588		/// * owner - Address, initial owner of the NFT.589		/// * data - Token data to store on chain.590		#[weight = T::CommonWeightInfo::create_item()]591		#[transactional]592		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {593			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);594			let budget = budget::Value::new(NESTING_BUDGET);595596			dispatch_tx::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))597		}598599		/// Create multiple items in a collection created with CreateCollection method.600		///601		/// # Permissions602		///603		/// * Collection Owner604		/// * Collection Admin605		/// * Anyone if606		///     * Allow List is enabled, and607		///     * Address is added to allow list, and608		///     * MintPermission is enabled (see SetMintPermission method)609		///610		/// # Arguments611		///612		/// * collection_id - ID of the collection.613		/// * owner - Address, initial owner of the NFT.614		/// * items_data - Array items properties. Each property is an array of bytes itself, see [`create_item`].615		#[weight = T::CommonWeightInfo::create_multiple_items(&items_data)]616		#[transactional]617		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {618			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);619			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);620			let budget = budget::Value::new(NESTING_BUDGET);621622			dispatch_tx::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))623		}624625		/// Add or change collection properties.626		///627		/// # Permissions628		///629		/// * Collection Owner630		/// * Collection Admin631		///632		/// # Arguments633		///634		/// * collection_id.635		/// * properties - Vector of key-value pairs stored as the collection's metadata. Keys support Latin letters, '-', '_', and '.' as symbols.636		#[weight = T::CommonWeightInfo::set_collection_properties(properties.len() as u32)]637		#[transactional]638		pub fn set_collection_properties(639			origin,640			collection_id: CollectionId,641			properties: Vec<Property>642		) -> DispatchResultWithPostInfo {643			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);644645			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);646647			dispatch_tx::<T, _>(collection_id, |d| d.set_collection_properties(sender, properties))648		}649650		/// Delete specified collection properties.651		///652		/// # Permissions653		///654		/// * Collection Owner655		/// * Collection Admin656		///657		/// # Arguments658		///659		/// * collection_id.660		/// * property_keys - Vector of keys of the properties to be deleted.661		#[weight = T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32)]662		#[transactional]663		pub fn delete_collection_properties(664			origin,665			collection_id: CollectionId,666			property_keys: Vec<PropertyKey>,667		) -> DispatchResultWithPostInfo {668			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);669670			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);671672			dispatch_tx::<T, _>(collection_id, |d| d.delete_collection_properties(&sender, property_keys))673		}674675		/// Add or change token properties according to collection's permissions.676		///677		/// # Permissions678		///679		/// * Depends on collection's token property permissions and specified property mutability:680		/// 	* Collection Owner681		/// 	* Collection Admin682		/// 	* Token Owner683		///684		/// # Arguments685		///686		/// * collection_id.687		/// * token_id.688		/// * properties - Vector of key-value pairs stored as the token's metadata. Keys support Latin letters, `-`, `_`, and `.` as symbols.689		#[weight = T::CommonWeightInfo::set_token_properties(properties.len() as u32)]690		#[transactional]691		pub fn set_token_properties(692			origin,693			collection_id: CollectionId,694			token_id: TokenId,695			properties: Vec<Property>696		) -> DispatchResultWithPostInfo {697			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);698699			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);700			let budget = budget::Value::new(NESTING_BUDGET);701702			dispatch_tx::<T, _>(collection_id, |d| d.set_token_properties(sender, token_id, properties, &budget))703		}704705		/// Delete specified token properties.706		///707		/// # Permissions708		///709		/// * Depends on collection's token property permissions and specified property mutability:710		/// 	* Collection Owner711		/// 	* Collection Admin712		/// 	* Token Owner713		///714		/// # Arguments715		///716		/// * collection_id.717		/// * token_id.718		/// * property_keys - Vector of keys of the properties to be deleted.719		#[weight = T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32)]720		#[transactional]721		pub fn delete_token_properties(722			origin,723			collection_id: CollectionId,724			token_id: TokenId,725			property_keys: Vec<PropertyKey>726		) -> DispatchResultWithPostInfo {727			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);728729			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);730			let budget = budget::Value::new(NESTING_BUDGET);731732			dispatch_tx::<T, _>(collection_id, |d| d.delete_token_properties(sender, token_id, property_keys, &budget))733		}734735		/// Add or change token property permissions of a collection.736		///737		/// # Permissions738		///739		/// * Collection Owner740		/// * Collection Admin741		///742		/// # Arguments743		///744		/// * collection_id.745		/// * property_permissions - Vector of permissions for property keys. Keys support Latin letters, `-`, `_`, and `.` as symbols.746		#[weight = T::CommonWeightInfo::set_token_property_permissions(property_permissions.len() as u32)]747		#[transactional]748		pub fn set_token_property_permissions(749			origin,750			collection_id: CollectionId,751			property_permissions: Vec<PropertyKeyPermission>,752		) -> DispatchResultWithPostInfo {753			ensure!(!property_permissions.is_empty(), Error::<T>::EmptyArgument);754755			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);756757			dispatch_tx::<T, _>(collection_id, |d| d.set_token_property_permissions(&sender, property_permissions))758		}759760		/// Create multiple items inside a collection with explicitly specified initial parameters.761		///762		/// # Permissions763		///764		/// * Collection Owner765		/// * Collection Admin766		/// * Anyone if767		///     * Allow List is enabled, and768		///     * Address is added to allow list, and769		///     * MintPermission is enabled (see SetMintPermission method)770		///771		/// # Arguments772		///773		/// * collection_id - ID of the collection.774		/// * data - Explicit item creation data.775		#[weight = T::CommonWeightInfo::create_multiple_items_ex(&data)]776		#[transactional]777		pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {778			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);779			let budget = budget::Value::new(NESTING_BUDGET);780781			dispatch_tx::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))782		}783784		/// Set transfers_enabled value for particular collection.785		///786		/// # Permissions787		///788		/// * Collection Owner789		///790		/// # Arguments791		///792		/// * collection_id - ID of the collection.793		/// * value - New flag value.794		#[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]795		#[transactional]796		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {797			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);798			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;799			target_collection.check_is_internal()?;800			target_collection.check_is_owner(&sender)?;801802			// =========803804			target_collection.limits.transfers_enabled = Some(value);805			target_collection.save()806		}807808		/// Destroy a concrete instance of NFT.809		///810		/// # Permissions811		///812		/// * Collection Owner813		/// * Collection Admin814		/// * Current NFT Owner815		///816		/// # Arguments817		///818		/// * collection_id - ID of the collection.819		/// * item_id - ID of NFT to burn.820		#[weight = T::CommonWeightInfo::burn_item()]821		#[transactional]822		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {823			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);824825			let post_info = dispatch_tx::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;826			if value == 1 {827				<NftTransferBasket<T>>::remove(collection_id, item_id);828				<NftApproveBasket<T>>::remove(collection_id, item_id);829			}830			// Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?831			// <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());832			// <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));833			Ok(post_info)834		}835836		/// Destroy a concrete instance of NFT on behalf of the owner.837		/// See also: [`approve`]838		///839		/// # Permissions840		///841		/// * Collection Owner.842		/// * Collection Admin.843		/// * Current NFT Owner.844		///845		/// # Arguments846		///847		/// * collection_id - ID of the collection.848		/// * item_id - ID of NFT to burn.849		/// * from - The owner of the item from whom it is taken away.850		#[weight = T::CommonWeightInfo::burn_from()]851		#[transactional]852		pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, 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.burn_from(sender, from, item_id, value, &budget))857		}858859		/// Change ownership of the token.860		///861		/// # Permissions862		///863		/// * Collection Owner864		/// * Collection Admin865		/// * Current NFT owner866		///867		/// # Arguments868		///869		/// * recipient - Address of token recipient.870		///871		/// * collection_id.872		///873		/// * item_id - ID of the item874		///     * Non-Fungible Mode: Required.875		///     * Fungible Mode: Ignored.876		///     * Re-Fungible Mode: Required.877		///878		/// * value - Amount to transfer.879		///     * Non-Fungible Mode: Ignored880		///     * Fungible Mode: Must specify transferred amount881		///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)882		#[weight = T::CommonWeightInfo::transfer()]883		#[transactional]884		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {885			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);886			let budget = budget::Value::new(NESTING_BUDGET);887888			dispatch_tx::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value, &budget))889		}890891		/// Set, change, or remove approved address to transfer the ownership of the NFT.892		///893		/// # Permissions894		///895		/// * Collection Owner896		/// * Collection Admin897		/// * Current NFT owner898		///899		/// # Arguments900		///901		/// * approved - Address that is approved to transfer this NFT or zero (if needed to remove approval).902		/// * collection_id.903		/// * item_id - ID of the item.904		#[weight = T::CommonWeightInfo::approve()]905		#[transactional]906		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {907			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);908909			dispatch_tx::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))910		}911912		/// 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.913		///914		/// # Permissions915		///916		/// * Collection Owner917		/// * Collection Admin918		/// * Current NFT owner919		/// * Address approved by current NFT owner920		///921		/// # Arguments922		///923		/// * from - Address that currently owns the token.924		/// * recipient - Address of the new token-owner-to-be.925		/// * collection_id.926		/// * item_id - ID of the item to be transferred.927		/// * value - Amount to transfer.928		#[weight = T::CommonWeightInfo::transfer_from()]929		#[transactional]930		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {931			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);932			let budget = budget::Value::new(NESTING_BUDGET);933934			dispatch_tx::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))935		}936937		/// Set specific limits of a collection. Empty, or None fields mean chain default.938		///939		/// # Permissions940		///941		/// * Collection Owner942		/// * Collection Admin943		///944		/// # Arguments945		///946		/// * collection_id.947		/// * new_limit - New limits of the collection. They will overwrite the current ones.948		#[weight = <SelfWeightOf<T>>::set_collection_limits()]949		#[transactional]950		pub fn set_collection_limits(951			origin,952			collection_id: CollectionId,953			new_limit: CollectionLimits,954		) -> DispatchResult {955			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);956			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;957			target_collection.check_is_internal()?;958			target_collection.check_is_owner_or_admin(&sender)?;959			let old_limit = &target_collection.limits;960961			target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;962963			<Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(964				collection_id965			));966967			target_collection.save()968		}969970		/// Set specific permissions of a collection. Empty, or None fields mean chain default.971		///972		/// # Permissions973		///974		/// * Collection Owner975		/// * Collection Admin976		///977		/// # Arguments978		///979		/// * collection_id.980		/// * new_permission - New permissions of the collection. They will overwrite the current ones.981		#[weight = <SelfWeightOf<T>>::set_collection_limits()]982		#[transactional]983		pub fn set_collection_permissions(984			origin,985			collection_id: CollectionId,986			new_permission: CollectionPermissions,987		) -> DispatchResult {988			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);989			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;990			target_collection.check_is_internal()?;991			target_collection.check_is_owner_or_admin(&sender)?;992			let old_limit = &target_collection.permissions;993994			target_collection.permissions = <PalletCommon<T>>::clamp_permissions(target_collection.mode.clone(), &old_limit, new_permission)?;995996			<Pallet<T>>::deposit_event(Event::<T>::CollectionPermissionSet(997				collection_id998			));9991000			target_collection.save()1001		}10021003		/// Re-partition a refungible token, while owning all of its parts.1004		///1005		/// # Permissions1006		///1007		/// * Token Owner (must own every part)1008		///1009		/// # Arguments1010		///1011		/// * collection_id.1012		/// * token_id - ID of the RFT.1013		/// * amount - New number of parts into which the token shall be partitioned.1014		#[weight = T::RefungibleExtensionsWeightInfo::repartition()]1015		#[transactional]1016		pub fn repartition(1017			origin,1018			collection_id: CollectionId,1019			token_id: TokenId,1020			amount: u128,1021		) -> DispatchResultWithPostInfo {1022			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1023			dispatch_tx::<T, _>(collection_id, |d| {1024				if let Some(refungible_extensions) = d.refungible_extensions() {1025					refungible_extensions.repartition(&sender, token, amount)1026				} else {1027					fail!(<Error<T>>::RepartitionCalledOnNonRefungibleCollection)1028				}1029			})1030		}1031	}1032}