git.delta.rocks / unique-network / refs/commits / 631b2ed4504b

difftreelog

source

pallets/unique/src/lib.rs28.7 KiBsourcehistory
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![recursion_limit = "1024"]18#![cfg_attr(not(feature = "std"), no_std)]19#![allow(20	clippy::too_many_arguments,21	clippy::unnecessary_mut_passed,22	clippy::unused_unit23)]2425extern crate alloc;2627use frame_support::{28	decl_module, decl_storage, decl_error, decl_event,29	dispatch::DispatchResult,30	ensure,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,41	MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH, AccessMode, CreateItemData,42	CollectionLimits, CollectionPermissions, CollectionId, CollectionMode, TokenId, SponsorshipState,43	CreateCollectionData, CreateItemExData, budget, Property, PropertyKey,44	PropertyKeyPermission,45};46use pallet_evm::account::CrossAccountId;47use pallet_common::{48	CollectionHandle, Pallet as PalletCommon, CommonWeightInfo, dispatch::dispatch_call,49	dispatch::CollectionDispatch,50};51pub mod eth;5253#[cfg(feature = "runtime-benchmarks")]54mod benchmarking;55pub mod weights;56use weights::WeightInfo;5758decl_error! {59	/// Error for non-fungible-token module.60	pub enum Error for Module<T: Config> {61		/// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.62		CollectionDecimalPointLimitExceeded,63		/// This address is not set as sponsor, use setCollectionSponsor first.64		ConfirmUnsetSponsorFail,65		/// Length of items properties must be greater than 0.66		EmptyArgument,67	}68}6970pub trait Config: system::Config + pallet_common::Config + Sized + TypeInfo {71	type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;7273	/// Weight information for extrinsics in this pallet.74	type WeightInfo: WeightInfo;75	type CommonWeightInfo: CommonWeightInfo<Self::CrossAccountId>;76}7778decl_event! {79	pub enum Event<T>80	where81		<T as frame_system::Config>::AccountId,82		<T as pallet_evm::account::Config>::CrossAccountId,83	{84		/// Collection sponsor was removed85		///86		/// # Arguments87		///88		/// * collection_id: Globally unique collection identifier.89		CollectionSponsorRemoved(CollectionId),9091		/// Collection admin was added92		///93		/// # Arguments94		///95		/// * collection_id: Globally unique collection identifier.96		///97		/// * admin:  Admin address.98		CollectionAdminAdded(CollectionId, CrossAccountId),99100		/// Collection owned was change101		///102		/// # Arguments103		///104		/// * collection_id: Globally unique collection identifier.105		///106		/// * owner:  New owner address.107		CollectionOwnedChanged(CollectionId, AccountId),108109		/// Collection sponsor was set110		///111		/// # Arguments112		///113		/// * collection_id: Globally unique collection identifier.114		///115		/// * owner:  New sponsor address.116		CollectionSponsorSet(CollectionId, AccountId),117118		/// New sponsor was confirm119		///120		/// # Arguments121		///122		/// * collection_id: Globally unique collection identifier.123		///124		/// * sponsor:  New sponsor address.125		SponsorshipConfirmed(CollectionId, AccountId),126127		/// Collection admin was removed128		///129		/// # Arguments130		///131		/// * collection_id: Globally unique collection identifier.132		///133		/// * admin:  Admin address.134		CollectionAdminRemoved(CollectionId, CrossAccountId),135136		/// Address was remove from allow list137		///138		/// # Arguments139		///140		/// * collection_id: Globally unique collection identifier.141		///142		/// * user:  Address.143		AllowListAddressRemoved(CollectionId, CrossAccountId),144145		/// Address was add to allow list146		///147		/// # Arguments148		///149		/// * collection_id: Globally unique collection identifier.150		///151		/// * user:  Address.152		AllowListAddressAdded(CollectionId, CrossAccountId),153154		/// Collection limits was set155		///156		/// # Arguments157		///158		/// * collection_id: Globally unique collection identifier.159		CollectionLimitSet(CollectionId),160161		CollectionPermissionSet(CollectionId),162	}163}164165type SelfWeightOf<T> = <T as Config>::WeightInfo;166167// # Used definitions168//169// ## User control levels170//171// chain-controlled - key is uncontrolled by user172//                    i.e autoincrementing index173//                    can use non-cryptographic hash174// real - key is controlled by user175//        but it is hard to generate enough colliding values, i.e owner of signed txs176//        can use non-cryptographic hash177// controlled - key is completly controlled by users178//              i.e maps with mutable keys179//              should use cryptographic hash180//181// ## User control level downgrade reasons182//183// ?1 - chain-controlled -> controlled184//      collections/tokens can be destroyed, resulting in massive holes185// ?2 - chain-controlled -> controlled186//      same as ?1, but can be only added, resulting in easier exploitation187// ?3 - real -> controlled188//      no confirmation required, so addresses can be easily generated189decl_storage! {190	trait Store for Module<T: Config> as Unique {191192		//#region Private members193		/// Used for migrations194		ChainVersion: u64;195		//#endregion196197		//#region Tokens transfer rate limit baskets198		/// (Collection id (controlled?2), who created (real))199		/// TODO: Off chain worker should remove from this map when collection gets removed200		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;201		/// Collection id (controlled?2), token id (controlled?2)202		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;203		/// Collection id (controlled?2), owning user (real)204		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;205		/// Collection id (controlled?2), token id (controlled?2)206		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>;207		//#endregion208209		/// Variable metadata sponsoring210		/// Collection id (controlled?2), token id (controlled?2)211		#[deprecated]212		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;213		pub TokenPropertyBasket get(fn token_property_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;214215		/// Approval sponsoring216		pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;217		pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;218		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>;219	}220}221222decl_module! {223	pub struct Module<T: Config> for enum Call224	where225		origin: T::Origin226	{227		type Error = Error<T>;228229		fn deposit_event() = default;230231		fn on_initialize(_now: T::BlockNumber) -> Weight {232			0233		}234235		fn on_runtime_upgrade() -> Weight {236			let limit = None;237238			<VariableMetaDataBasket<T>>::remove_all(limit);239240			0241		}242243		/// This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner of the collection is set to the address that signed the transaction and can be changed later.244		///245		/// # Permissions246		///247		/// * Anyone.248		///249		/// # Arguments250		///251		/// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.252		///253		/// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.254		///255		/// * token_prefix: UTF-8 string with token prefix.256		///257		/// * mode: [CollectionMode] collection type and type dependent data.258		// returns collection ID259		#[weight = <SelfWeightOf<T>>::create_collection()]260		#[transactional]261		#[deprecated]262		pub fn create_collection(origin,263								 collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,264								 collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,265								 token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,266								 mode: CollectionMode) -> DispatchResult  {267			let data: CreateCollectionData<T::AccountId> = CreateCollectionData {268				name: collection_name,269				description: collection_description,270				token_prefix,271				mode,272				..Default::default()273			};274			Self::create_collection_ex(origin, data)275		}276277		/// This method creates a collection278		///279		/// Prefer it to deprecated [`created_collection`] method280		#[weight = <SelfWeightOf<T>>::create_collection()]281		#[transactional]282		pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {283			let sender = ensure_signed(origin)?;284285			// =========286287			T::CollectionDispatch::create(sender, data)?;288289			Ok(())290		}291292		/// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.293		///294		/// # Permissions295		///296		/// * Collection Owner.297		///298		/// # Arguments299		///300		/// * collection_id: collection to destroy.301		#[weight = <SelfWeightOf<T>>::destroy_collection()]302		#[transactional]303		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {304			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);305			let collection = <CollectionHandle<T>>::try_get(collection_id)?;306307			// =========308309			T::CollectionDispatch::destroy(sender, collection)?;310311			<NftTransferBasket<T>>::remove_prefix(collection_id, None);312			<FungibleTransferBasket<T>>::remove_prefix(collection_id, None);313			<ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);314315			<NftApproveBasket<T>>::remove_prefix(collection_id, None);316			<FungibleApproveBasket<T>>::remove_prefix(collection_id, None);317			<RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);318319			Ok(())320		}321322		/// Add an address to allow list.323		///324		/// # Permissions325		///326		/// * Collection Owner327		/// * Collection Admin328		///329		/// # Arguments330		///331		/// * collection_id.332		///333		/// * address.334		#[weight = <SelfWeightOf<T>>::add_to_allow_list()]335		#[transactional]336		pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{337338			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);339			let collection = <CollectionHandle<T>>::try_get(collection_id)?;340341			<PalletCommon<T>>::toggle_allowlist(342				&collection,343				&sender,344				&address,345				true,346			)?;347348			Self::deposit_event(Event::<T>::AllowListAddressAdded(349				collection_id,350				address351			));352353			Ok(())354		}355356		/// Remove an address from allow list.357		///358		/// # Permissions359		///360		/// * Collection Owner361		/// * Collection Admin362		///363		/// # Arguments364		///365		/// * collection_id.366		///367		/// * address.368		#[weight = <SelfWeightOf<T>>::remove_from_allow_list()]369		#[transactional]370		pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{371372			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);373			let collection = <CollectionHandle<T>>::try_get(collection_id)?;374375			<PalletCommon<T>>::toggle_allowlist(376				&collection,377				&sender,378				&address,379				false,380			)?;381382			<Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(383				collection_id,384				address385			));386387			Ok(())388		}389390		/// Change the owner of the collection.391		///392		/// # Permissions393		///394		/// * Collection Owner.395		///396		/// # Arguments397		///398		/// * collection_id.399		///400		/// * new_owner.401		#[weight = <SelfWeightOf<T>>::change_collection_owner()]402		#[transactional]403		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {404405			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);406407			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;408			target_collection.check_is_owner(&sender)?;409410			target_collection.owner = new_owner.clone();411			<Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(412				collection_id,413				new_owner414			));415416			target_collection.save()417		}418419		/// Adds an admin of the Collection.420		/// 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.421		///422		/// # Permissions423		///424		/// * Collection Owner.425		/// * Collection Admin.426		///427		/// # Arguments428		///429		/// * collection_id: ID of the Collection to add admin for.430		///431		/// * new_admin_id: Address of new admin to add.432		#[weight = <SelfWeightOf<T>>::add_collection_admin()]433		#[transactional]434		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {435			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);436			let collection = <CollectionHandle<T>>::try_get(collection_id)?;437438			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(439				collection_id,440				new_admin_id.clone()441			));442443			<PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)444		}445446		/// 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.447		///448		/// # Permissions449		///450		/// * Collection Owner.451		/// * Collection Admin.452		///453		/// # Arguments454		///455		/// * collection_id: ID of the Collection to remove admin for.456		///457		/// * account_id: Address of admin to remove.458		#[weight = <SelfWeightOf<T>>::remove_collection_admin()]459		#[transactional]460		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {461			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);462			let collection = <CollectionHandle<T>>::try_get(collection_id)?;463464			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(465				collection_id,466				account_id.clone()467			));468469			<PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)470		}471472		/// # Permissions473		///474		/// * Collection Owner475		///476		/// # Arguments477		///478		/// * collection_id.479		///480		/// * new_sponsor.481		#[weight = <SelfWeightOf<T>>::set_collection_sponsor()]482		#[transactional]483		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {484			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);485486			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;487			target_collection.check_is_owner(&sender)?;488489			target_collection.set_sponsor(new_sponsor.clone());490491			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(492				collection_id,493				new_sponsor494			));495496			target_collection.save()497		}498499		/// # Permissions500		///501		/// * Sponsor.502		///503		/// # Arguments504		///505		/// * collection_id.506		#[weight = <SelfWeightOf<T>>::confirm_sponsorship()]507		#[transactional]508		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {509			let sender = ensure_signed(origin)?;510511			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;512			ensure!(513				target_collection.confirm_sponsorship(&sender),514				Error::<T>::ConfirmUnsetSponsorFail515			);516517			<Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(518				collection_id,519				sender520			));521522			target_collection.save()523		}524525		/// Switch back to pay-per-own-transaction model.526		///527		/// # Permissions528		///529		/// * Collection owner.530		///531		/// # Arguments532		///533		/// * collection_id.534		#[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]535		#[transactional]536		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {537			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);538539			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;540			target_collection.check_is_owner(&sender)?;541542			target_collection.sponsorship = SponsorshipState::Disabled;543544			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(545				collection_id546			));547			target_collection.save()548		}549550		/// This method creates a concrete instance of NFT Collection created with CreateCollection method.551		///552		/// # Permissions553		///554		/// * Collection Owner.555		/// * Collection Admin.556		/// * Anyone if557		///     * Allow List is enabled, and558		///     * Address is added to allow list, and559		///     * MintPermission is enabled (see SetMintPermission method)560		///561		/// # Arguments562		///563		/// * collection_id: ID of the collection.564		///565		/// * owner: Address, initial owner of the NFT.566		///567		/// * data: Token data to store on chain.568		#[weight = T::CommonWeightInfo::create_item()]569		#[transactional]570		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {571			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);572			let budget = budget::Value::new(2);573574			dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))575		}576577		/// This method creates multiple items in a collection created with CreateCollection method.578		///579		/// # Permissions580		///581		/// * Collection Owner.582		/// * Collection Admin.583		/// * Anyone if584		///     * Allow List is enabled, and585		///     * Address is added to allow list, and586		///     * MintPermission is enabled (see SetMintPermission method)587		///588		/// # Arguments589		///590		/// * collection_id: ID of the collection.591		///592		/// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].593		///594		/// * owner: Address, initial owner of the NFT.595		#[weight = T::CommonWeightInfo::create_multiple_items(&items_data)]596		#[transactional]597		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {598			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);599			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);600			let budget = budget::Value::new(2);601602			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))603		}604605		#[weight = T::CommonWeightInfo::set_collection_properties(properties.len() as u32)]606		#[transactional]607		pub fn set_collection_properties(608			origin,609			collection_id: CollectionId,610			properties: Vec<Property>611		) -> DispatchResultWithPostInfo {612			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);613614			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);615616			dispatch_call::<T, _>(collection_id, |d| d.set_collection_properties(sender, properties))617		}618619		#[weight = T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32)]620		#[transactional]621		pub fn delete_collection_properties(622			origin,623			collection_id: CollectionId,624			property_keys: Vec<PropertyKey>,625		) -> DispatchResultWithPostInfo {626			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);627628			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);629630			dispatch_call::<T, _>(collection_id, |d| d.delete_collection_properties(&sender, property_keys))631		}632633		#[weight = T::CommonWeightInfo::set_token_properties(properties.len() as u32)]634		#[transactional]635		pub fn set_token_properties(636			origin,637			collection_id: CollectionId,638			token_id: TokenId,639			properties: Vec<Property>640		) -> DispatchResultWithPostInfo {641			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);642643			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);644645			dispatch_call::<T, _>(collection_id, |d| d.set_token_properties(sender, token_id, properties))646		}647648		#[weight = T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32)]649		#[transactional]650		pub fn delete_token_properties(651			origin,652			collection_id: CollectionId,653			token_id: TokenId,654			property_keys: Vec<PropertyKey>655		) -> DispatchResultWithPostInfo {656			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);657658			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);659660			dispatch_call::<T, _>(collection_id, |d| d.delete_token_properties(sender, token_id, property_keys))661		}662663		#[weight = T::CommonWeightInfo::set_property_permissions(property_permissions.len() as u32)]664		#[transactional]665		pub fn set_property_permissions(666			origin,667			collection_id: CollectionId,668			property_permissions: Vec<PropertyKeyPermission>,669		) -> DispatchResultWithPostInfo {670			ensure!(!property_permissions.is_empty(), Error::<T>::EmptyArgument);671672			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);673674			dispatch_call::<T, _>(collection_id, |d| d.set_property_permissions(&sender, property_permissions))675		}676677		#[weight = T::CommonWeightInfo::create_multiple_items_ex(&data)]678		#[transactional]679		pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {680			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);681			let budget = budget::Value::new(2);682683			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))684		}685686		// TODO! transaction weight687688		/// Set transfers_enabled value for particular collection689		///690		/// # Permissions691		///692		/// * Collection Owner.693		///694		/// # Arguments695		///696		/// * collection_id: ID of the collection.697		///698		/// * value: New flag value.699		#[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]700		#[transactional]701		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {702			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);703			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;704			target_collection.check_is_owner(&sender)?;705706			// =========707708			target_collection.limits.transfers_enabled = Some(value);709			target_collection.save()710		}711712		/// Destroys a concrete instance of NFT.713		///714		/// # Permissions715		///716		/// * Collection Owner.717		/// * Collection Admin.718		/// * Current NFT Owner.719		///720		/// # Arguments721		///722		/// * collection_id: ID of the collection.723		///724		/// * item_id: ID of NFT to burn.725		#[weight = T::CommonWeightInfo::burn_item()]726		#[transactional]727		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {728			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);729730			let post_info = dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;731			if value == 1 {732				<NftTransferBasket<T>>::remove(collection_id, item_id);733				<NftApproveBasket<T>>::remove(collection_id, item_id);734			}735			// Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?736			// <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());737			// <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));738			Ok(post_info)739		}740741		/// Destroys a concrete instance of NFT on behalf of the owner742		/// See also: [`approve`]743		///744		/// # Permissions745		///746		/// * Collection Owner.747		/// * Collection Admin.748		/// * Current NFT Owner.749		///750		/// # Arguments751		///752		/// * collection_id: ID of the collection.753		///754		/// * item_id: ID of NFT to burn.755		///756		/// * from: owner of item757		#[weight = T::CommonWeightInfo::burn_from()]758		#[transactional]759		pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {760			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);761			let budget = budget::Value::new(2);762763			dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value, &budget))764		}765766		/// Change ownership of the token.767		///768		/// # Permissions769		///770		/// * Collection Owner771		/// * Collection Admin772		/// * Current NFT owner773		///774		/// # Arguments775		///776		/// * recipient: Address of token recipient.777		///778		/// * collection_id.779		///780		/// * item_id: ID of the item781		///     * Non-Fungible Mode: Required.782		///     * Fungible Mode: Ignored.783		///     * Re-Fungible Mode: Required.784		///785		/// * value: Amount to transfer.786		///     * Non-Fungible Mode: Ignored787		///     * Fungible Mode: Must specify transferred amount788		///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)789		#[weight = T::CommonWeightInfo::transfer()]790		#[transactional]791		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {792			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);793			let budget = budget::Value::new(2);794795			dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value, &budget))796		}797798		/// Set, change, or remove approved address to transfer the ownership of the NFT.799		///800		/// # Permissions801		///802		/// * Collection Owner803		/// * Collection Admin804		/// * Current NFT owner805		///806		/// # Arguments807		///808		/// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).809		///810		/// * collection_id.811		///812		/// * item_id: ID of the item.813		#[weight = T::CommonWeightInfo::approve()]814		#[transactional]815		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {816			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);817818			dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))819		}820821		/// 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.822		///823		/// # Permissions824		/// * Collection Owner825		/// * Collection Admin826		/// * Current NFT owner827		/// * Address approved by current NFT owner828		///829		/// # Arguments830		///831		/// * from: Address that owns token.832		///833		/// * recipient: Address of token recipient.834		///835		/// * collection_id.836		///837		/// * item_id: ID of the item.838		///839		/// * value: Amount to transfer.840		#[weight = T::CommonWeightInfo::transfer_from()]841		#[transactional]842		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {843			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);844			let budget = budget::Value::new(2);845846			dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))847		}848849		#[weight = <SelfWeightOf<T>>::set_collection_limits()]850		#[transactional]851		pub fn set_collection_limits(852			origin,853			collection_id: CollectionId,854			new_limit: CollectionLimits,855		) -> DispatchResult {856			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);857			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;858			target_collection.check_is_owner(&sender)?;859			let old_limit = &target_collection.limits;860861			target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;862863			<Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(864				collection_id865			));866867			target_collection.save()868		}869870		#[weight = <SelfWeightOf<T>>::set_collection_limits()]871		#[transactional]872		pub fn set_collection_permissions(873			origin,874			collection_id: CollectionId,875			new_limit: CollectionPermissions,876		) -> DispatchResult {877			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);878			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;879			target_collection.check_is_owner(&sender)?;880			let old_limit = &target_collection.permissions;881882			target_collection.permissions = <PalletCommon<T>>::clamp_permissions(target_collection.mode.clone(), &old_limit, new_limit)?;883884			<Pallet<T>>::deposit_event(Event::<T>::CollectionPermissionSet(885				collection_id886			));887888			target_collection.save()889		}890	}891}