git.delta.rocks / unique-network / refs/commits / 2976d69d82a2

difftreelog

source

pallets/unique/src/lib.rs35.5 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	VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT, OFFCHAIN_SCHEMA_LIMIT,39	MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH,40	AccessMode, CreateItemData, CollectionLimits, CollectionId, CollectionMode, TokenId,41	SchemaVersion, SponsorshipState, MetaUpdatePermission, CreateCollectionData, CustomDataLimit,42	CreateItemExData, budget, CollectionField, Property, PropertyKey, PropertyKeyPermission,43};44use pallet_evm::account::CrossAccountId;45use pallet_common::{46	CollectionHandle, Pallet as PalletCommon, Error as CommonError, CommonWeightInfo,47	dispatch::dispatch_call, dispatch::CollectionDispatch,48};4950#[cfg(feature = "runtime-benchmarks")]51mod benchmarking;52pub mod weights;53use weights::WeightInfo;5455decl_error! {56	/// Error for non-fungible-token module.57	pub enum Error for Module<T: Config> {58		/// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.59		CollectionDecimalPointLimitExceeded,60		/// This address is not set as sponsor, use setCollectionSponsor first.61		ConfirmUnsetSponsorFail,62		/// Length of items properties must be greater than 0.63		EmptyArgument,64	}65}6667pub trait Config: system::Config + pallet_common::Config + Sized + TypeInfo {68	type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;6970	/// Weight information for extrinsics in this pallet.71	type WeightInfo: WeightInfo;72	type CommonWeightInfo: CommonWeightInfo<Self::CrossAccountId>;73}7475decl_event! {76	pub enum Event<T>77	where78		<T as frame_system::Config>::AccountId,79		<T as pallet_evm::account::Config>::CrossAccountId,80	{81		/// Collection sponsor was removed82		///83		/// # Arguments84		///85		/// * collection_id: Globally unique collection identifier.86		CollectionSponsorRemoved(CollectionId),8788		/// Collection admin was added89		///90		/// # Arguments91		///92		/// * collection_id: Globally unique collection identifier.93		///94		/// * admin:  Admin address.95		CollectionAdminAdded(CollectionId, CrossAccountId),9697		/// Collection owned was change98		///99		/// # Arguments100		///101		/// * collection_id: Globally unique collection identifier.102		///103		/// * owner:  New owner address.104		CollectionOwnedChanged(CollectionId, AccountId),105106		/// Collection sponsor was set107		///108		/// # Arguments109		///110		/// * collection_id: Globally unique collection identifier.111		///112		/// * owner:  New sponsor address.113		CollectionSponsorSet(CollectionId, AccountId),114115		/// const on chain schema was set116		///117		/// # Arguments118		///119		/// * collection_id: Globally unique collection identifier.120		ConstOnChainSchemaSet(CollectionId),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		/// Mint permission	was set166		///167		/// # Arguments168		///169		/// * collection_id: Globally unique collection identifier.170		MintPermissionSet(CollectionId),171172		/// Offchain schema was set173		///174		/// # Arguments175		///176		/// * collection_id: Globally unique collection identifier.177		OffchainSchemaSet(CollectionId),178179		/// Public access mode was set180		///181		/// # Arguments182		///183		/// * collection_id: Globally unique collection identifier.184		///185		/// * mode: New access state.186		PublicAccessModeSet(CollectionId, AccessMode),187188		/// Schema version was set189		///190		/// # Arguments191		///192		/// * collection_id: Globally unique collection identifier.193		SchemaVersionSet(CollectionId),194195		/// Variable on chain schema was set196		///197		/// # Arguments198		///199		/// * collection_id: Globally unique collection identifier.200		VariableOnChainSchemaSet(CollectionId),201	}202}203204type SelfWeightOf<T> = <T as Config>::WeightInfo;205206// # Used definitions207//208// ## User control levels209//210// chain-controlled - key is uncontrolled by user211//                    i.e autoincrementing index212//                    can use non-cryptographic hash213// real - key is controlled by user214//        but it is hard to generate enough colliding values, i.e owner of signed txs215//        can use non-cryptographic hash216// controlled - key is completly controlled by users217//              i.e maps with mutable keys218//              should use cryptographic hash219//220// ## User control level downgrade reasons221//222// ?1 - chain-controlled -> controlled223//      collections/tokens can be destroyed, resulting in massive holes224// ?2 - chain-controlled -> controlled225//      same as ?1, but can be only added, resulting in easier exploitation226// ?3 - real -> controlled227//      no confirmation required, so addresses can be easily generated228decl_storage! {229	trait Store for Module<T: Config> as Unique {230231		//#region Private members232		/// Used for migrations233		ChainVersion: u64;234		//#endregion235236		//#region Tokens transfer rate limit baskets237		/// (Collection id (controlled?2), who created (real))238		/// TODO: Off chain worker should remove from this map when collection gets removed239		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;240		/// Collection id (controlled?2), token id (controlled?2)241		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;242		/// Collection id (controlled?2), owning user (real)243		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;244		/// Collection id (controlled?2), token id (controlled?2)245		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>;246		//#endregion247248		/// Variable metadata sponsoring249		/// Collection id (controlled?2), token id (controlled?2)250		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;251		/// 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		/// 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.272		///273		/// # Permissions274		///275		/// * Anyone.276		///277		/// # Arguments278		///279		/// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.280		///281		/// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.282		///283		/// * token_prefix: UTF-8 string with token prefix.284		///285		/// * mode: [CollectionMode] collection type and type dependent data.286		// returns collection ID287		#[weight = <SelfWeightOf<T>>::create_collection()]288		#[transactional]289		#[deprecated]290		pub fn create_collection(origin,291								 collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,292								 collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,293								 token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,294								 mode: CollectionMode) -> DispatchResult  {295			let data: CreateCollectionData<T::AccountId> = CreateCollectionData {296				name: collection_name,297				description: collection_description,298				token_prefix,299				mode,300				..Default::default()301			};302			Self::create_collection_ex(origin, data)303		}304305		/// This method creates a collection306		///307		/// Prefer it to deprecated [`created_collection`] method308		#[weight = <SelfWeightOf<T>>::create_collection()]309		#[transactional]310		pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {311			let sender = ensure_signed(origin)?;312313			// =========314315			T::CollectionDispatch::create(sender, data)?;316317			Ok(())318		}319320		/// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.321		///322		/// # Permissions323		///324		/// * Collection Owner.325		///326		/// # Arguments327		///328		/// * collection_id: collection to destroy.329		#[weight = <SelfWeightOf<T>>::destroy_collection()]330		#[transactional]331		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {332			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);333			let collection = <CollectionHandle<T>>::try_get(collection_id)?;334335			// =========336337			T::CollectionDispatch::destroy(sender, collection)?;338339			<NftTransferBasket<T>>::remove_prefix(collection_id, None);340			<FungibleTransferBasket<T>>::remove_prefix(collection_id, None);341			<ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);342343			<VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);344			<NftApproveBasket<T>>::remove_prefix(collection_id, None);345			<FungibleApproveBasket<T>>::remove_prefix(collection_id, None);346			<RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);347348			Ok(())349		}350351		/// Add an address to allow list.352		///353		/// # Permissions354		///355		/// * Collection Owner356		/// * Collection Admin357		///358		/// # Arguments359		///360		/// * collection_id.361		///362		/// * address.363		#[weight = <SelfWeightOf<T>>::add_to_allow_list()]364		#[transactional]365		pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{366367			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);368			let collection = <CollectionHandle<T>>::try_get(collection_id)?;369370			<PalletCommon<T>>::toggle_allowlist(371				&collection,372				&sender,373				&address,374				true,375			)?;376377			Self::deposit_event(Event::<T>::AllowListAddressAdded(378				collection_id,379				address380			));381382			Ok(())383		}384385		/// Remove an address from allow list.386		///387		/// # Permissions388		///389		/// * Collection Owner390		/// * Collection Admin391		///392		/// # Arguments393		///394		/// * collection_id.395		///396		/// * address.397		#[weight = <SelfWeightOf<T>>::remove_from_allow_list()]398		#[transactional]399		pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{400401			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);402			let collection = <CollectionHandle<T>>::try_get(collection_id)?;403404			<PalletCommon<T>>::toggle_allowlist(405				&collection,406				&sender,407				&address,408				false,409			)?;410411			<Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(412				collection_id,413				address414			));415416			Ok(())417		}418419		/// Toggle between normal and allow list access for the methods with access for `Anyone`.420		///421		/// # Permissions422		///423		/// * Collection Owner.424		///425		/// # Arguments426		///427		/// * collection_id.428		///429		/// * mode: [AccessMode]430		#[weight = <SelfWeightOf<T>>::set_public_access_mode()]431		#[transactional]432		pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult433		{434			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);435436			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;437			target_collection.check_is_owner(&sender)?;438439			target_collection.access = mode.clone();440441			<Pallet<T>>::deposit_event(Event::<T>::PublicAccessModeSet(442				collection_id,443				mode444			));445446			target_collection.save()447		}448449		/// Allows Anyone to create tokens if:450		/// * Allow List is enabled, and451		/// * Address is added to allow list, and452		/// * This method was called with True parameter453		///454		/// # Permissions455		/// * Collection Owner456		///457		/// # Arguments458		///459		/// * collection_id.460		///461		/// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.462		#[weight = <SelfWeightOf<T>>::set_mint_permission()]463		#[transactional]464		pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult465		{466			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);467468			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;469			target_collection.check_is_owner(&sender)?;470471			target_collection.mint_mode = mint_permission;472473			<Pallet<T>>::deposit_event(Event::<T>::MintPermissionSet(474				collection_id475			));476477			target_collection.save()478		}479480		/// Change the owner of the collection.481		///482		/// # Permissions483		///484		/// * Collection Owner.485		///486		/// # Arguments487		///488		/// * collection_id.489		///490		/// * new_owner.491		#[weight = <SelfWeightOf<T>>::change_collection_owner()]492		#[transactional]493		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {494495			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);496497			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;498			target_collection.check_is_owner(&sender)?;499500			target_collection.owner = new_owner.clone();501			<Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(502				collection_id,503				new_owner504			));505506			target_collection.save()507		}508509		/// Adds an admin of the Collection.510		/// 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.511		///512		/// # Permissions513		///514		/// * Collection Owner.515		/// * Collection Admin.516		///517		/// # Arguments518		///519		/// * collection_id: ID of the Collection to add admin for.520		///521		/// * new_admin_id: Address of new admin to add.522		#[weight = <SelfWeightOf<T>>::add_collection_admin()]523		#[transactional]524		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {525			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);526			let collection = <CollectionHandle<T>>::try_get(collection_id)?;527528			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(529				collection_id,530				new_admin_id.clone()531			));532533			<PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)534		}535536		/// 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.537		///538		/// # Permissions539		///540		/// * Collection Owner.541		/// * Collection Admin.542		///543		/// # Arguments544		///545		/// * collection_id: ID of the Collection to remove admin for.546		///547		/// * account_id: Address of admin to remove.548		#[weight = <SelfWeightOf<T>>::remove_collection_admin()]549		#[transactional]550		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {551			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);552			let collection = <CollectionHandle<T>>::try_get(collection_id)?;553554			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(555				collection_id,556				account_id.clone()557			));558559			<PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)560		}561562		/// # Permissions563		///564		/// * Collection Owner565		///566		/// # Arguments567		///568		/// * collection_id.569		///570		/// * new_sponsor.571		#[weight = <SelfWeightOf<T>>::set_collection_sponsor()]572		#[transactional]573		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {574			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);575576			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;577			target_collection.check_is_owner(&sender)?;578579			target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor.clone());580581			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(582				collection_id,583				new_sponsor584			));585586			target_collection.save()587		}588589		/// # Permissions590		///591		/// * Sponsor.592		///593		/// # Arguments594		///595		/// * collection_id.596		#[weight = <SelfWeightOf<T>>::confirm_sponsorship()]597		#[transactional]598		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {599			let sender = ensure_signed(origin)?;600601			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;602			ensure!(603				target_collection.sponsorship.pending_sponsor() == Some(&sender),604				Error::<T>::ConfirmUnsetSponsorFail605			);606607			target_collection.sponsorship = SponsorshipState::Confirmed(sender.clone());608609			<Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(610				collection_id,611				sender612			));613614			target_collection.save()615		}616617		/// Switch back to pay-per-own-transaction model.618		///619		/// # Permissions620		///621		/// * Collection owner.622		///623		/// # Arguments624		///625		/// * collection_id.626		#[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]627		#[transactional]628		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {629			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);630631			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;632			target_collection.check_is_owner(&sender)?;633634			target_collection.sponsorship = SponsorshipState::Disabled;635636			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(637				collection_id638			));639			target_collection.save()640		}641642		/// This method creates a concrete instance of NFT Collection created with CreateCollection method.643		///644		/// # Permissions645		///646		/// * Collection Owner.647		/// * Collection Admin.648		/// * Anyone if649		///     * Allow List is enabled, and650		///     * Address is added to allow list, and651		///     * MintPermission is enabled (see SetMintPermission method)652		///653		/// # Arguments654		///655		/// * collection_id: ID of the collection.656		///657		/// * owner: Address, initial owner of the NFT.658		///659		/// * data: Token data to store on chain.660		#[weight = T::CommonWeightInfo::create_item()]661		#[transactional]662		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {663			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);664			let budget = budget::Value::new(2);665666			dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))667		}668669		/// This method creates multiple items in a collection created with CreateCollection method.670		///671		/// # Permissions672		///673		/// * Collection Owner.674		/// * Collection Admin.675		/// * Anyone if676		///     * Allow List is enabled, and677		///     * Address is added to allow list, and678		///     * MintPermission is enabled (see SetMintPermission method)679		///680		/// # Arguments681		///682		/// * collection_id: ID of the collection.683		///684		/// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].685		///686		/// * owner: Address, initial owner of the NFT.687		#[weight = T::CommonWeightInfo::create_multiple_items(items_data.len() as u32)]688		#[transactional]689		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {690			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);691			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);692			let budget = budget::Value::new(2);693694			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))695		}696697		#[weight = T::CommonWeightInfo::set_collection_properties(properties.len() as u32)]698		#[transactional]699		pub fn set_collection_properties(700			origin,701			collection_id: CollectionId,702			properties: Vec<Property>703		) -> DispatchResultWithPostInfo {704			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);705706			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);707708			dispatch_call::<T, _>(collection_id, |d| d.set_collection_properties(sender, properties))709		}710711		#[weight = T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32)]712		#[transactional]713		pub fn delete_collection_properties(714			origin,715			collection_id: CollectionId,716			property_keys: Vec<PropertyKey>,717		) -> DispatchResultWithPostInfo {718			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);719720			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);721722			dispatch_call::<T, _>(collection_id, |d| d.delete_collection_properties(&sender, property_keys))723		}724725		#[weight = T::CommonWeightInfo::set_token_properties(properties.len() as u32)]726		#[transactional]727		pub fn set_token_properties(728			origin,729			collection_id: CollectionId,730			token_id: TokenId,731			properties: Vec<Property>732		) -> DispatchResultWithPostInfo {733			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);734735			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);736737			dispatch_call::<T, _>(collection_id, |d| d.set_token_properties(sender, token_id, properties))738		}739740		#[weight = T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32)]741		#[transactional]742		pub fn delete_token_properties(743			origin,744			collection_id: CollectionId,745			token_id: TokenId,746			property_keys: Vec<PropertyKey>747		) -> DispatchResultWithPostInfo {748			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);749750			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);751752			dispatch_call::<T, _>(collection_id, |d| d.delete_token_properties(sender, token_id, property_keys))753		}754755		#[weight = T::CommonWeightInfo::set_property_permissions(property_permissions.len() as u32)]756		#[transactional]757		pub fn set_property_permissions(758			origin,759			collection_id: CollectionId,760			property_permissions: Vec<PropertyKeyPermission>,761		) -> DispatchResultWithPostInfo {762			ensure!(!property_permissions.is_empty(), Error::<T>::EmptyArgument);763764			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);765766			dispatch_call::<T, _>(collection_id, |d| d.set_property_permissions(&sender, property_permissions))767		}768769		#[weight = T::CommonWeightInfo::create_multiple_items_ex(&data)]770		#[transactional]771		pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {772			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);773			let budget = budget::Value::new(2);774775			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))776		}777778		// TODO! transaction weight779780		/// Set transfers_enabled value for particular collection781		///782		/// # Permissions783		///784		/// * Collection Owner.785		///786		/// # Arguments787		///788		/// * collection_id: ID of the collection.789		///790		/// * value: New flag value.791		#[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]792		#[transactional]793		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {794			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);795			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;796			target_collection.check_is_owner(&sender)?;797798			// =========799800			target_collection.limits.transfers_enabled = Some(value);801			target_collection.save()802		}803804		/// Destroys a concrete instance of NFT.805		///806		/// # Permissions807		///808		/// * Collection Owner.809		/// * Collection Admin.810		/// * Current NFT Owner.811		///812		/// # Arguments813		///814		/// * collection_id: ID of the collection.815		///816		/// * item_id: ID of NFT to burn.817		#[weight = T::CommonWeightInfo::burn_item()]818		#[transactional]819		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {820			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);821822			let post_info = dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;823			if value == 1 {824				<NftTransferBasket<T>>::remove(collection_id, item_id);825				<NftApproveBasket<T>>::remove(collection_id, item_id);826			}827			// Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?828			// <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());829			// <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));830			Ok(post_info)831		}832833		/// Destroys a concrete instance of NFT on behalf of the owner834		/// See also: [`approve`]835		///836		/// # Permissions837		///838		/// * Collection Owner.839		/// * Collection Admin.840		/// * Current NFT Owner.841		///842		/// # Arguments843		///844		/// * collection_id: ID of the collection.845		///846		/// * item_id: ID of NFT to burn.847		///848		/// * from: owner of item849		#[weight = T::CommonWeightInfo::burn_from()]850		#[transactional]851		pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {852			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);853			let budget = budget::Value::new(2);854855			dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value, &budget))856		}857858		/// Change ownership of the token.859		///860		/// # Permissions861		///862		/// * Collection Owner863		/// * Collection Admin864		/// * Current NFT owner865		///866		/// # Arguments867		///868		/// * recipient: Address of token recipient.869		///870		/// * collection_id.871		///872		/// * item_id: ID of the item873		///     * Non-Fungible Mode: Required.874		///     * Fungible Mode: Ignored.875		///     * Re-Fungible Mode: Required.876		///877		/// * value: Amount to transfer.878		///     * Non-Fungible Mode: Ignored879		///     * Fungible Mode: Must specify transferred amount880		///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)881		#[weight = T::CommonWeightInfo::transfer()]882		#[transactional]883		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {884			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);885			let budget = budget::Value::new(2);886887			dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value, &budget))888		}889890		/// Set, change, or remove approved address to transfer the ownership of the NFT.891		///892		/// # Permissions893		///894		/// * Collection Owner895		/// * Collection Admin896		/// * Current NFT owner897		///898		/// # Arguments899		///900		/// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).901		///902		/// * collection_id.903		///904		/// * item_id: ID of the item.905		#[weight = T::CommonWeightInfo::approve()]906		#[transactional]907		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {908			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);909910			dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))911		}912913		/// 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.914		///915		/// # Permissions916		/// * Collection Owner917		/// * Collection Admin918		/// * Current NFT owner919		/// * Address approved by current NFT owner920		///921		/// # Arguments922		///923		/// * from: Address that owns token.924		///925		/// * recipient: Address of token recipient.926		///927		/// * collection_id.928		///929		/// * item_id: ID of the item.930		///931		/// * value: Amount to transfer.932		#[weight = T::CommonWeightInfo::transfer_from()]933		#[transactional]934		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {935			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);936			let budget = budget::Value::new(2);937938			dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))939		}940941		/// Set off-chain data schema.942		///943		/// # Permissions944		///945		/// * Collection Owner946		/// * Collection Admin947		///948		/// # Arguments949		///950		/// * collection_id.951		///952		/// * schema: String representing the offchain data schema.953		#[weight = T::CommonWeightInfo::set_variable_metadata(data.len() as u32)]954		#[transactional]955		pub fn set_variable_meta_data (956			origin,957			collection_id: CollectionId,958			item_id: TokenId,959			data: BoundedVec<u8, CustomDataLimit>,960		) -> DispatchResultWithPostInfo {961			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);962963			dispatch_call::<T, _>(collection_id, |d| d.set_variable_metadata(sender, item_id, data))964		}965966		/// Set meta_update_permission value for particular collection967		///968		/// # Permissions969		///970		/// * Collection Owner.971		///972		/// # Arguments973		///974		/// * collection_id: ID of the collection.975		///976		/// * value: New flag value.977		#[weight = <SelfWeightOf<T>>::set_meta_update_permission_flag()]978		#[transactional]979		pub fn set_meta_update_permission_flag(origin, collection_id: CollectionId, value: MetaUpdatePermission) -> DispatchResult {980			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);981			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;982983			ensure!(984				target_collection.meta_update_permission != MetaUpdatePermission::None,985				<CommonError<T>>::MetadataFlagFrozen,986			);987			target_collection.check_is_owner(&sender)?;988989			target_collection.meta_update_permission = value;990991			target_collection.save()992		}993994		/// Set schema standard995		/// ImageURL996		/// Unique997		///998		/// # Permissions999		///1000		/// * Collection Owner1001		/// * Collection Admin1002		///1003		/// # Arguments1004		///1005		/// * collection_id.1006		///1007		/// * schema: SchemaVersion: enum1008		#[weight = <SelfWeightOf<T>>::set_schema_version()]1009		#[transactional]1010		pub fn set_schema_version(1011			origin,1012			collection_id: CollectionId,1013			version: SchemaVersion1014		) -> DispatchResult {1015			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1016			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1017			target_collection.check_is_owner_or_admin(&sender)?;1018			target_collection.schema_version = version;10191020			<Pallet<T>>::deposit_event(Event::<T>::SchemaVersionSet(1021				collection_id1022			));10231024			target_collection.save()1025		}10261027		/// Set off-chain data schema.1028		///1029		/// # Permissions1030		///1031		/// * Collection Owner1032		/// * Collection Admin1033		///1034		/// # Arguments1035		///1036		/// * collection_id.1037		///1038		/// * schema: String representing the offchain data schema.1039		#[weight = <SelfWeightOf<T>>::set_offchain_schema(schema.len() as u32)]1040		#[transactional]1041		pub fn set_offchain_schema(1042			origin,1043			collection_id: CollectionId,1044			schema: BoundedVec<u8, ConstU32<OFFCHAIN_SCHEMA_LIMIT>>,1045		) -> DispatchResult {1046			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1047			let collection = <CollectionHandle<T>>::try_get(collection_id)?;10481049			// =========10501051			<PalletCommon<T>>::set_field(&collection, &sender, CollectionField::OffchainSchema, schema.into_inner())?;10521053			<Pallet<T>>::deposit_event(Event::<T>::OffchainSchemaSet(1054				collection_id1055			));1056			Ok(())1057		}10581059		/// Set const on-chain data schema.1060		///1061		/// # Permissions1062		///1063		/// * Collection Owner1064		/// * Collection Admin1065		///1066		/// # Arguments1067		///1068		/// * collection_id.1069		///1070		/// * schema: String representing the const on-chain data schema.1071		#[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1072		#[transactional]1073		pub fn set_const_on_chain_schema (1074			origin,1075			collection_id: CollectionId,1076			schema: BoundedVec<u8, ConstU32<CONST_ON_CHAIN_SCHEMA_LIMIT>>1077		) -> DispatchResult {1078			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1079			let collection = <CollectionHandle<T>>::try_get(collection_id)?;10801081			// =========10821083			<PalletCommon<T>>::set_field(&collection, &sender, CollectionField::ConstOnChainSchema, schema.into_inner())?;10841085			<Pallet<T>>::deposit_event(Event::<T>::ConstOnChainSchemaSet(1086				collection_id1087			));1088			Ok(())1089		}10901091		/// Set variable on-chain data schema.1092		///1093		/// # Permissions1094		///1095		/// * Collection Owner1096		/// * Collection Admin1097		///1098		/// # Arguments1099		///1100		/// * collection_id.1101		///1102		/// * schema: String representing the variable on-chain data schema.1103		#[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1104		#[transactional]1105		pub fn set_variable_on_chain_schema (1106			origin,1107			collection_id: CollectionId,1108			schema: BoundedVec<u8, ConstU32<VARIABLE_ON_CHAIN_SCHEMA_LIMIT>>1109		) -> DispatchResult {1110			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1111			let collection = <CollectionHandle<T>>::try_get(collection_id)?;11121113			// =========11141115			<PalletCommon<T>>::set_field(&collection, &sender, CollectionField::VariableOnChainSchema, schema.into_inner())?;11161117			<Pallet<T>>::deposit_event(Event::<T>::VariableOnChainSchemaSet(1118				collection_id1119			));1120			Ok(())1121		}11221123		#[weight = <SelfWeightOf<T>>::set_collection_limits()]1124		#[transactional]1125		pub fn set_collection_limits(1126			origin,1127			collection_id: CollectionId,1128			new_limit: CollectionLimits,1129		) -> DispatchResult {1130			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1131			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1132			target_collection.check_is_owner(&sender)?;1133			let old_limit = &target_collection.limits;11341135			target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;11361137			<Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(1138				collection_id1139			));11401141			target_collection.save()1142		}1143	}1144}