git.delta.rocks / unique-network / refs/commits / 7a8820bcb682

difftreelog

source

pallets/unique/src/lib.rs33.1 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	CONST_ON_CHAIN_SCHEMA_LIMIT, OFFCHAIN_SCHEMA_LIMIT, MAX_COLLECTION_NAME_LENGTH,39	MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH, AccessMode, CreateItemData,40	CollectionLimits, CollectionId, CollectionMode, TokenId, SchemaVersion, SponsorshipState,41	CreateCollectionData, CreateItemExData, budget, CollectionField, 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;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),194	}195}196197type SelfWeightOf<T> = <T as Config>::WeightInfo;198199// # Used definitions200//201// ## User control levels202//203// chain-controlled - key is uncontrolled by user204//                    i.e autoincrementing index205//                    can use non-cryptographic hash206// real - key is controlled by user207//        but it is hard to generate enough colliding values, i.e owner of signed txs208//        can use non-cryptographic hash209// controlled - key is completly controlled by users210//              i.e maps with mutable keys211//              should use cryptographic hash212//213// ## User control level downgrade reasons214//215// ?1 - chain-controlled -> controlled216//      collections/tokens can be destroyed, resulting in massive holes217// ?2 - chain-controlled -> controlled218//      same as ?1, but can be only added, resulting in easier exploitation219// ?3 - real -> controlled220//      no confirmation required, so addresses can be easily generated221decl_storage! {222	trait Store for Module<T: Config> as Unique {223224		//#region Private members225		/// Used for migrations226		ChainVersion: u64;227		//#endregion228229		//#region Tokens transfer rate limit baskets230		/// (Collection id (controlled?2), who created (real))231		/// TODO: Off chain worker should remove from this map when collection gets removed232		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;233		/// Collection id (controlled?2), token id (controlled?2)234		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;235		/// Collection id (controlled?2), owning user (real)236		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;237		/// Collection id (controlled?2), token id (controlled?2)238		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>;239		//#endregion240241		/// Variable metadata sponsoring242		/// Collection id (controlled?2), token id (controlled?2)243		#[deprecated]244		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;245		pub TokenPropertyBasket get(fn token_property_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;246247		/// Approval sponsoring248		pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;249		pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;250		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>;251	}252}253254decl_module! {255	pub struct Module<T: Config> for enum Call256	where257		origin: T::Origin258	{259		type Error = Error<T>;260261		fn deposit_event() = default;262263		fn on_initialize(_now: T::BlockNumber) -> Weight {264			0265		}266267		fn on_runtime_upgrade() -> Weight {268			let limit = None;269270			<VariableMetaDataBasket<T>>::remove_all(limit);271272			0273		}274275		/// 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.276		///277		/// # Permissions278		///279		/// * Anyone.280		///281		/// # Arguments282		///283		/// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.284		///285		/// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.286		///287		/// * token_prefix: UTF-8 string with token prefix.288		///289		/// * mode: [CollectionMode] collection type and type dependent data.290		// returns collection ID291		#[weight = <SelfWeightOf<T>>::create_collection()]292		#[transactional]293		#[deprecated]294		pub fn create_collection(origin,295								 collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,296								 collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,297								 token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,298								 mode: CollectionMode) -> DispatchResult  {299			let data: CreateCollectionData<T::AccountId> = CreateCollectionData {300				name: collection_name,301				description: collection_description,302				token_prefix,303				mode,304				..Default::default()305			};306			Self::create_collection_ex(origin, data)307		}308309		/// This method creates a collection310		///311		/// Prefer it to deprecated [`created_collection`] method312		#[weight = <SelfWeightOf<T>>::create_collection()]313		#[transactional]314		pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {315			let sender = ensure_signed(origin)?;316317			// =========318319			T::CollectionDispatch::create(sender, data)?;320321			Ok(())322		}323324		/// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.325		///326		/// # Permissions327		///328		/// * Collection Owner.329		///330		/// # Arguments331		///332		/// * collection_id: collection to destroy.333		#[weight = <SelfWeightOf<T>>::destroy_collection()]334		#[transactional]335		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {336			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);337			let collection = <CollectionHandle<T>>::try_get(collection_id)?;338339			// =========340341			T::CollectionDispatch::destroy(sender, collection)?;342343			<NftTransferBasket<T>>::remove_prefix(collection_id, None);344			<FungibleTransferBasket<T>>::remove_prefix(collection_id, None);345			<ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);346347			<NftApproveBasket<T>>::remove_prefix(collection_id, None);348			<FungibleApproveBasket<T>>::remove_prefix(collection_id, None);349			<RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);350351			Ok(())352		}353354		/// Add an address to allow list.355		///356		/// # Permissions357		///358		/// * Collection Owner359		/// * Collection Admin360		///361		/// # Arguments362		///363		/// * collection_id.364		///365		/// * address.366		#[weight = <SelfWeightOf<T>>::add_to_allow_list()]367		#[transactional]368		pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{369370			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);371			let collection = <CollectionHandle<T>>::try_get(collection_id)?;372373			<PalletCommon<T>>::toggle_allowlist(374				&collection,375				&sender,376				&address,377				true,378			)?;379380			Self::deposit_event(Event::<T>::AllowListAddressAdded(381				collection_id,382				address383			));384385			Ok(())386		}387388		/// Remove an address from allow list.389		///390		/// # Permissions391		///392		/// * Collection Owner393		/// * Collection Admin394		///395		/// # Arguments396		///397		/// * collection_id.398		///399		/// * address.400		#[weight = <SelfWeightOf<T>>::remove_from_allow_list()]401		#[transactional]402		pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{403404			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);405			let collection = <CollectionHandle<T>>::try_get(collection_id)?;406407			<PalletCommon<T>>::toggle_allowlist(408				&collection,409				&sender,410				&address,411				false,412			)?;413414			<Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(415				collection_id,416				address417			));418419			Ok(())420		}421422		/// Toggle between normal and allow list access for the methods with access for `Anyone`.423		///424		/// # Permissions425		///426		/// * Collection Owner.427		///428		/// # Arguments429		///430		/// * collection_id.431		///432		/// * mode: [AccessMode]433		#[weight = <SelfWeightOf<T>>::set_public_access_mode()]434		#[transactional]435		pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult436		{437			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);438439			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;440			target_collection.check_is_owner(&sender)?;441442			target_collection.access = mode.clone();443444			<Pallet<T>>::deposit_event(Event::<T>::PublicAccessModeSet(445				collection_id,446				mode447			));448449			target_collection.save()450		}451452		/// Allows Anyone to create tokens if:453		/// * Allow List is enabled, and454		/// * Address is added to allow list, and455		/// * This method was called with True parameter456		///457		/// # Permissions458		/// * Collection Owner459		///460		/// # Arguments461		///462		/// * collection_id.463		///464		/// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.465		#[weight = <SelfWeightOf<T>>::set_mint_permission()]466		#[transactional]467		pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult468		{469			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);470471			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;472			target_collection.check_is_owner(&sender)?;473474			target_collection.mint_mode = mint_permission;475476			<Pallet<T>>::deposit_event(Event::<T>::MintPermissionSet(477				collection_id478			));479480			target_collection.save()481		}482483		/// Change the owner of the collection.484		///485		/// # Permissions486		///487		/// * Collection Owner.488		///489		/// # Arguments490		///491		/// * collection_id.492		///493		/// * new_owner.494		#[weight = <SelfWeightOf<T>>::change_collection_owner()]495		#[transactional]496		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {497498			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);499500			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;501			target_collection.check_is_owner(&sender)?;502503			target_collection.owner = new_owner.clone();504			<Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(505				collection_id,506				new_owner507			));508509			target_collection.save()510		}511512		/// Adds an admin of the Collection.513		/// 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.514		///515		/// # Permissions516		///517		/// * Collection Owner.518		/// * Collection Admin.519		///520		/// # Arguments521		///522		/// * collection_id: ID of the Collection to add admin for.523		///524		/// * new_admin_id: Address of new admin to add.525		#[weight = <SelfWeightOf<T>>::add_collection_admin()]526		#[transactional]527		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {528			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);529			let collection = <CollectionHandle<T>>::try_get(collection_id)?;530531			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(532				collection_id,533				new_admin_id.clone()534			));535536			<PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)537		}538539		/// 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.540		///541		/// # Permissions542		///543		/// * Collection Owner.544		/// * Collection Admin.545		///546		/// # Arguments547		///548		/// * collection_id: ID of the Collection to remove admin for.549		///550		/// * account_id: Address of admin to remove.551		#[weight = <SelfWeightOf<T>>::remove_collection_admin()]552		#[transactional]553		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {554			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);555			let collection = <CollectionHandle<T>>::try_get(collection_id)?;556557			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(558				collection_id,559				account_id.clone()560			));561562			<PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)563		}564565		/// # Permissions566		///567		/// * Collection Owner568		///569		/// # Arguments570		///571		/// * collection_id.572		///573		/// * new_sponsor.574		#[weight = <SelfWeightOf<T>>::set_collection_sponsor()]575		#[transactional]576		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {577			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);578579			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;580			target_collection.check_is_owner(&sender)?;581582			target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor.clone());583584			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(585				collection_id,586				new_sponsor587			));588589			target_collection.save()590		}591592		/// # Permissions593		///594		/// * Sponsor.595		///596		/// # Arguments597		///598		/// * collection_id.599		#[weight = <SelfWeightOf<T>>::confirm_sponsorship()]600		#[transactional]601		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {602			let sender = ensure_signed(origin)?;603604			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;605			ensure!(606				target_collection.sponsorship.pending_sponsor() == Some(&sender),607				Error::<T>::ConfirmUnsetSponsorFail608			);609610			target_collection.sponsorship = SponsorshipState::Confirmed(sender.clone());611612			<Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(613				collection_id,614				sender615			));616617			target_collection.save()618		}619620		/// Switch back to pay-per-own-transaction model.621		///622		/// # Permissions623		///624		/// * Collection owner.625		///626		/// # Arguments627		///628		/// * collection_id.629		#[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]630		#[transactional]631		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {632			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);633634			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;635			target_collection.check_is_owner(&sender)?;636637			target_collection.sponsorship = SponsorshipState::Disabled;638639			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(640				collection_id641			));642			target_collection.save()643		}644645		/// This method creates a concrete instance of NFT Collection created with CreateCollection method.646		///647		/// # Permissions648		///649		/// * Collection Owner.650		/// * Collection Admin.651		/// * Anyone if652		///     * Allow List is enabled, and653		///     * Address is added to allow list, and654		///     * MintPermission is enabled (see SetMintPermission method)655		///656		/// # Arguments657		///658		/// * collection_id: ID of the collection.659		///660		/// * owner: Address, initial owner of the NFT.661		///662		/// * data: Token data to store on chain.663		#[weight = T::CommonWeightInfo::create_item()]664		#[transactional]665		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {666			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);667			let budget = budget::Value::new(2);668669			dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))670		}671672		/// This method creates multiple items in a collection created with CreateCollection method.673		///674		/// # Permissions675		///676		/// * Collection Owner.677		/// * Collection Admin.678		/// * Anyone if679		///     * Allow List is enabled, and680		///     * Address is added to allow list, and681		///     * MintPermission is enabled (see SetMintPermission method)682		///683		/// # Arguments684		///685		/// * collection_id: ID of the collection.686		///687		/// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].688		///689		/// * owner: Address, initial owner of the NFT.690		#[weight = T::CommonWeightInfo::create_multiple_items(items_data.len() as u32)]691		#[transactional]692		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {693			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);694			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);695			let budget = budget::Value::new(2);696697			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))698		}699700		#[weight = T::CommonWeightInfo::set_collection_properties(properties.len() as u32)]701		#[transactional]702		pub fn set_collection_properties(703			origin,704			collection_id: CollectionId,705			properties: Vec<Property>706		) -> DispatchResultWithPostInfo {707			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);708709			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);710711			dispatch_call::<T, _>(collection_id, |d| d.set_collection_properties(sender, properties))712		}713714		#[weight = T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32)]715		#[transactional]716		pub fn delete_collection_properties(717			origin,718			collection_id: CollectionId,719			property_keys: Vec<PropertyKey>,720		) -> DispatchResultWithPostInfo {721			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);722723			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);724725			dispatch_call::<T, _>(collection_id, |d| d.delete_collection_properties(&sender, property_keys))726		}727728		#[weight = T::CommonWeightInfo::set_token_properties(properties.len() as u32)]729		#[transactional]730		pub fn set_token_properties(731			origin,732			collection_id: CollectionId,733			token_id: TokenId,734			properties: Vec<Property>735		) -> DispatchResultWithPostInfo {736			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);737738			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);739740			dispatch_call::<T, _>(collection_id, |d| d.set_token_properties(sender, token_id, properties))741		}742743		#[weight = T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32)]744		#[transactional]745		pub fn delete_token_properties(746			origin,747			collection_id: CollectionId,748			token_id: TokenId,749			property_keys: Vec<PropertyKey>750		) -> DispatchResultWithPostInfo {751			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);752753			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);754755			dispatch_call::<T, _>(collection_id, |d| d.delete_token_properties(sender, token_id, property_keys))756		}757758		#[weight = T::CommonWeightInfo::set_property_permissions(property_permissions.len() as u32)]759		#[transactional]760		pub fn set_property_permissions(761			origin,762			collection_id: CollectionId,763			property_permissions: Vec<PropertyKeyPermission>,764		) -> DispatchResultWithPostInfo {765			ensure!(!property_permissions.is_empty(), Error::<T>::EmptyArgument);766767			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);768769			dispatch_call::<T, _>(collection_id, |d| d.set_property_permissions(&sender, property_permissions))770		}771772		#[weight = T::CommonWeightInfo::create_multiple_items_ex(&data)]773		#[transactional]774		pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {775			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);776			let budget = budget::Value::new(2);777778			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))779		}780781		// TODO! transaction weight782783		/// Set transfers_enabled value for particular collection784		///785		/// # Permissions786		///787		/// * Collection Owner.788		///789		/// # Arguments790		///791		/// * collection_id: ID of the collection.792		///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_owner(&sender)?;800801			// =========802803			target_collection.limits.transfers_enabled = Some(value);804			target_collection.save()805		}806807		/// Destroys a concrete instance of NFT.808		///809		/// # Permissions810		///811		/// * Collection Owner.812		/// * Collection Admin.813		/// * Current NFT Owner.814		///815		/// # Arguments816		///817		/// * collection_id: ID of the collection.818		///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_call::<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		/// Destroys a concrete instance of NFT on behalf of the owner837		/// 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		///849		/// * item_id: ID of NFT to burn.850		///851		/// * from: owner of item852		#[weight = T::CommonWeightInfo::burn_from()]853		#[transactional]854		pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {855			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);856			let budget = budget::Value::new(2);857858			dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value, &budget))859		}860861		/// Change ownership of the token.862		///863		/// # Permissions864		///865		/// * Collection Owner866		/// * Collection Admin867		/// * Current NFT owner868		///869		/// # Arguments870		///871		/// * recipient: Address of token recipient.872		///873		/// * collection_id.874		///875		/// * item_id: ID of the item876		///     * Non-Fungible Mode: Required.877		///     * Fungible Mode: Ignored.878		///     * Re-Fungible Mode: Required.879		///880		/// * value: Amount to transfer.881		///     * Non-Fungible Mode: Ignored882		///     * Fungible Mode: Must specify transferred amount883		///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)884		#[weight = T::CommonWeightInfo::transfer()]885		#[transactional]886		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {887			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);888			let budget = budget::Value::new(2);889890			dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value, &budget))891		}892893		/// Set, change, or remove approved address to transfer the ownership of the NFT.894		///895		/// # Permissions896		///897		/// * Collection Owner898		/// * Collection Admin899		/// * Current NFT owner900		///901		/// # Arguments902		///903		/// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).904		///905		/// * collection_id.906		///907		/// * item_id: ID of the item.908		#[weight = T::CommonWeightInfo::approve()]909		#[transactional]910		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {911			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);912913			dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))914		}915916		/// 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.917		///918		/// # Permissions919		/// * Collection Owner920		/// * Collection Admin921		/// * Current NFT owner922		/// * Address approved by current NFT owner923		///924		/// # Arguments925		///926		/// * from: Address that owns token.927		///928		/// * recipient: Address of token recipient.929		///930		/// * collection_id.931		///932		/// * item_id: ID of the item.933		///934		/// * value: Amount to transfer.935		#[weight = T::CommonWeightInfo::transfer_from()]936		#[transactional]937		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {938			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);939			let budget = budget::Value::new(2);940941			dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))942		}943944		/// Set schema standard945		/// ImageURL946		/// Unique947		///948		/// # Permissions949		///950		/// * Collection Owner951		/// * Collection Admin952		///953		/// # Arguments954		///955		/// * collection_id.956		///957		/// * schema: SchemaVersion: enum958		#[weight = <SelfWeightOf<T>>::set_schema_version()]959		#[transactional]960		pub fn set_schema_version(961			origin,962			collection_id: CollectionId,963			version: SchemaVersion964		) -> DispatchResult {965			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);966			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;967			target_collection.check_is_owner_or_admin(&sender)?;968			target_collection.schema_version = version;969970			<Pallet<T>>::deposit_event(Event::<T>::SchemaVersionSet(971				collection_id972			));973974			target_collection.save()975		}976977		/// Set off-chain data schema.978		///979		/// # Permissions980		///981		/// * Collection Owner982		/// * Collection Admin983		///984		/// # Arguments985		///986		/// * collection_id.987		///988		/// * schema: String representing the offchain data schema.989		#[weight = <SelfWeightOf<T>>::set_offchain_schema(schema.len() as u32)]990		#[transactional]991		pub fn set_offchain_schema(992			origin,993			collection_id: CollectionId,994			schema: BoundedVec<u8, ConstU32<OFFCHAIN_SCHEMA_LIMIT>>,995		) -> DispatchResult {996			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);997			let collection = <CollectionHandle<T>>::try_get(collection_id)?;998999			// =========10001001			<PalletCommon<T>>::set_field(&collection, &sender, CollectionField::OffchainSchema, schema.into_inner())?;10021003			<Pallet<T>>::deposit_event(Event::<T>::OffchainSchemaSet(1004				collection_id1005			));1006			Ok(())1007		}10081009		/// Set const on-chain data schema.1010		///1011		/// # Permissions1012		///1013		/// * Collection Owner1014		/// * Collection Admin1015		///1016		/// # Arguments1017		///1018		/// * collection_id.1019		///1020		/// * schema: String representing the const on-chain data schema.1021		#[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1022		#[transactional]1023		pub fn set_const_on_chain_schema (1024			origin,1025			collection_id: CollectionId,1026			schema: BoundedVec<u8, ConstU32<CONST_ON_CHAIN_SCHEMA_LIMIT>>1027		) -> DispatchResult {1028			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1029			let collection = <CollectionHandle<T>>::try_get(collection_id)?;10301031			// =========10321033			<PalletCommon<T>>::set_field(&collection, &sender, CollectionField::ConstOnChainSchema, schema.into_inner())?;10341035			<Pallet<T>>::deposit_event(Event::<T>::ConstOnChainSchemaSet(1036				collection_id1037			));1038			Ok(())1039		}10401041		#[weight = <SelfWeightOf<T>>::set_collection_limits()]1042		#[transactional]1043		pub fn set_collection_limits(1044			origin,1045			collection_id: CollectionId,1046			new_limit: CollectionLimits,1047		) -> DispatchResult {1048			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1049			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1050			target_collection.check_is_owner(&sender)?;1051			let old_limit = &target_collection.limits;10521053			target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;10541055			<Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(1056				collection_id1057			));10581059			target_collection.save()1060		}1061	}1062}