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

difftreelog

source

pallets/proxy-rmrk-core/src/lib.rs45.6 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#![cfg_attr(not(feature = "std"), no_std)]1819use frame_support::{20	pallet_prelude::*,21	transactional,22	BoundedVec,23	dispatch::DispatchResult,24};25use frame_system::{pallet_prelude::*, ensure_signed};26use sp_runtime::{DispatchError, Permill, traits::StaticLookup};27use sp_std::{vec::Vec, collections::{btree_set::BTreeSet, btree_map::BTreeMap}};28use up_data_structs::{*, mapping::TokenAddressMapping};29use pallet_common::{30	Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,31};32use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData};33use pallet_structure::{Pallet as PalletStructure, Error as StructureError};34use pallet_evm::account::CrossAccountId;35use core::convert::AsRef;3637pub use pallet::*;3839#[cfg(feature = "runtime-benchmarks")]40pub mod benchmarking;41pub mod misc;42pub mod property;43pub mod rpc;44pub mod weights;4546pub type SelfWeightOf<T> = <T as Config>::WeightInfo;4748use weights::WeightInfo;49use misc::*;50pub use property::*;5152use RmrkProperty::*;5354pub const NESTING_BUDGET: u32 = 5;5556type PendingTarget = (CollectionId, TokenId);57type PendingChild = (RmrkCollectionId, RmrkNftId);58type PendingChildrenSet = BTreeSet<PendingChild>;5960type BasesMap = BTreeMap<RmrkBaseId, u32>;6162#[frame_support::pallet]63pub mod pallet {64	use super::*;65	use pallet_evm::account;6667	#[pallet::config]68	pub trait Config:69		frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config70	{71		type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;72		type WeightInfo: WeightInfo;73	}7475	#[pallet::storage]76	#[pallet::getter(fn collection_index)]77	pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;7879	#[pallet::storage]80	pub type UniqueCollectionId<T: Config> =81		StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;8283	#[pallet::pallet]84	#[pallet::generate_store(pub(super) trait Store)]85	pub struct Pallet<T>(_);8687	#[pallet::event]88	#[pallet::generate_deposit(pub(super) fn deposit_event)]89	pub enum Event<T: Config> {90		CollectionCreated {91			issuer: T::AccountId,92			collection_id: RmrkCollectionId,93		},94		CollectionDestroyed {95			issuer: T::AccountId,96			collection_id: RmrkCollectionId,97		},98		IssuerChanged {99			old_issuer: T::AccountId,100			new_issuer: T::AccountId,101			collection_id: RmrkCollectionId,102		},103		CollectionLocked {104			issuer: T::AccountId,105			collection_id: RmrkCollectionId,106		},107		NftMinted {108			owner: T::AccountId,109			collection_id: RmrkCollectionId,110			nft_id: RmrkNftId,111		},112		NFTBurned {113			owner: T::AccountId,114			nft_id: RmrkNftId,115		},116		NFTSent {117			sender: T::AccountId,118			recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,119			collection_id: RmrkCollectionId,120			nft_id: RmrkNftId,121			approval_required: bool,122		},123		NFTAccepted {124			sender: T::AccountId,125			recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,126			collection_id: RmrkCollectionId,127			nft_id: RmrkNftId,128		},129		NFTRejected {130			sender: T::AccountId,131			collection_id: RmrkCollectionId,132			nft_id: RmrkNftId,133		},134		PropertySet {135			collection_id: RmrkCollectionId,136			maybe_nft_id: Option<RmrkNftId>,137			key: RmrkKeyString,138			value: RmrkValueString,139		},140		ResourceAdded {141			nft_id: RmrkNftId,142			resource_id: RmrkResourceId,143		},144		ResourceRemoval {145			nft_id: RmrkNftId,146			resource_id: RmrkResourceId,147		},148		ResourceAccepted {149			nft_id: RmrkNftId,150			resource_id: RmrkResourceId,151		},152		ResourceRemovalAccepted {153			nft_id: RmrkNftId,154			resource_id: RmrkResourceId,155		},156		PrioritySet {157			collection_id: RmrkCollectionId,158			nft_id: RmrkNftId,159		},160	}161162	#[pallet::error]163	pub enum Error<T> {164		/* Unique-specific events */165		CorruptedCollectionType,166		NftTypeEncodeError,167		RmrkPropertyKeyIsTooLong,168		RmrkPropertyValueIsTooLong,169		RmrkPropertyIsNotFound,170		UnableToDecodeRmrkData,171172		/* RMRK compatible events */173		CollectionNotEmpty,174		NoAvailableCollectionId,175		NoAvailableNftId,176		CollectionUnknown,177		NoPermission,178		NonTransferable,179		CollectionFullOrLocked,180		ResourceDoesntExist,181		CannotSendToDescendentOrSelf,182		CannotAcceptNonOwnedNft,183		CannotRejectNonOwnedNft,184		CannotRejectNonPendingNft,185		ResourceNotPending,186		NoAvailableResourceId,187	}188189	#[pallet::call]190	impl<T: Config> Pallet<T> {191		/// Create a collection192		#[transactional]193		#[pallet::weight(<SelfWeightOf<T>>::create_collection())]194		pub fn create_collection(195			origin: OriginFor<T>,196			metadata: RmrkString,197			max: Option<u32>,198			symbol: RmrkCollectionSymbol,199		) -> DispatchResult {200			let sender = ensure_signed(origin)?;201202			let limits = CollectionLimits {203				owner_can_transfer: Some(false),204				token_limit: max,205				..Default::default()206			};207208			let data = CreateCollectionData {209				limits: Some(limits),210				token_prefix: symbol211					.into_inner()212					.try_into()213					.map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,214				permissions: Some(CollectionPermissions {215					nesting: Some(NestingPermissions {216						token_owner: true,217						collection_admin: false,218						restricted: None,219						#[cfg(feature = "runtime-benchmarks")]220						permissive: false,221					}),222					..Default::default()223				}),224				..Default::default()225			};226227			let unique_collection_id = Self::init_collection(228				T::CrossAccountId::from_sub(sender.clone()),229				data,230				[231					Self::rmrk_property(Metadata, &metadata)?,232					Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,233				]234				.into_iter(),235			)?;236			let rmrk_collection_id = <CollectionIndex<T>>::get();237238			<UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);239240			<PalletCommon<T>>::set_scoped_collection_property(241				unique_collection_id,242				PropertyScope::Rmrk,243				Self::rmrk_property(RmrkInternalCollectionId, &rmrk_collection_id)?,244			)?;245246			<CollectionIndex<T>>::mutate(|n| *n += 1);247248			Self::deposit_event(Event::CollectionCreated {249				issuer: sender,250				collection_id: rmrk_collection_id,251			});252253			Ok(())254		}255256		/// destroy collection257		#[transactional]258		#[pallet::weight(<SelfWeightOf<T>>::destroy_collection())]259		pub fn destroy_collection(260			origin: OriginFor<T>,261			collection_id: RmrkCollectionId,262		) -> DispatchResult {263			let sender = ensure_signed(origin)?;264			let cross_sender = T::CrossAccountId::from_sub(sender.clone());265266			let collection = Self::get_typed_nft_collection(267				Self::unique_collection_id(collection_id)?,268				misc::CollectionType::Regular,269			)?;270			collection.check_is_external()?;271272			<PalletNft<T>>::destroy_collection(collection, &cross_sender)273				.map_err(Self::map_unique_err_to_proxy)?;274275			Self::deposit_event(Event::CollectionDestroyed {276				issuer: sender,277				collection_id,278			});279280			Ok(())281		}282283		/// Change the issuer of a collection284		///285		/// Parameters:286		/// - `origin`: sender of the transaction287		/// - `collection_id`: collection id of the nft to change issuer of288		/// - `new_issuer`: Collection's new issuer289		#[transactional]290		#[pallet::weight(<SelfWeightOf<T>>::change_collection_issuer())]291		pub fn change_collection_issuer(292			origin: OriginFor<T>,293			collection_id: RmrkCollectionId,294			new_issuer: <T::Lookup as StaticLookup>::Source,295		) -> DispatchResult {296			let sender = ensure_signed(origin)?;297298			let collection = Self::get_nft_collection(Self::unique_collection_id(collection_id)?)?;299			collection.check_is_external()?;300301			let new_issuer = T::Lookup::lookup(new_issuer)?;302303			Self::change_collection_owner(304				Self::unique_collection_id(collection_id)?,305				misc::CollectionType::Regular,306				sender.clone(),307				new_issuer.clone(),308			)?;309310			Self::deposit_event(Event::IssuerChanged {311				old_issuer: sender,312				new_issuer,313				collection_id,314			});315316			Ok(())317		}318319		/// lock collection320		#[transactional]321		#[pallet::weight(<SelfWeightOf<T>>::lock_collection())]322		pub fn lock_collection(323			origin: OriginFor<T>,324			collection_id: RmrkCollectionId,325		) -> DispatchResult {326			let sender = ensure_signed(origin)?;327			let cross_sender = T::CrossAccountId::from_sub(sender.clone());328329			let collection = Self::get_typed_nft_collection(330				Self::unique_collection_id(collection_id)?,331				misc::CollectionType::Regular,332			)?;333			collection.check_is_external()?;334335			Self::check_collection_owner(&collection, &cross_sender)?;336337			let token_count = collection.total_supply();338339			let mut collection = collection.into_inner();340			collection.limits.token_limit = Some(token_count);341			collection.save()?;342343			Self::deposit_event(Event::CollectionLocked {344				issuer: sender,345				collection_id,346			});347348			Ok(())349		}350351		/// Mints an NFT in the specified collection352		/// Sets metadata and the royalty attribute353		///354		/// Parameters:355		/// - `collection_id`: The class of the asset to be minted.356		/// - `nft_id`: The nft value of the asset to be minted.357		/// - `recipient`: Receiver of the royalty358		/// - `royalty`: Permillage reward from each trade for the Recipient359		/// - `metadata`: Arbitrary data about an nft, e.g. IPFS hash360		/// - `transferable`: Ability to transfer this NFT361		#[transactional]362		#[pallet::weight(<SelfWeightOf<T>>::mint_nft(resources.as_ref().map(|r| r.len() as u32).unwrap_or(0)))]363		pub fn mint_nft(364			origin: OriginFor<T>,365			owner: Option<T::AccountId>,366			collection_id: RmrkCollectionId,367			recipient: Option<T::AccountId>,368			royalty_amount: Option<Permill>,369			metadata: RmrkString,370			transferable: bool,371			resources: Option<BoundedVec<RmrkResourceTypes, MaxResourcesOnMint>>,372		) -> DispatchResult {373			let sender = ensure_signed(origin)?;374			let cross_sender = T::CrossAccountId::from_sub(sender.clone());375376			let owner = owner.unwrap_or(sender.clone());377			let cross_owner = T::CrossAccountId::from_sub(owner.clone());378379			let collection = Self::get_typed_nft_collection(380				Self::unique_collection_id(collection_id)?,381				misc::CollectionType::Regular,382			)?;383			collection.check_is_external()?;384385			let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {386				recipient: recipient.unwrap_or_else(|| owner.clone()),387				amount,388			});389390			let nft_id = Self::create_nft(391				&cross_sender,392				&cross_owner,393				&collection,394				[395					Self::rmrk_property(TokenType, &NftType::Regular)?,396					Self::rmrk_property(Transferable, &transferable)?,397					Self::rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,398					Self::rmrk_property(RoyaltyInfo, &royalty_info)?,399					Self::rmrk_property(Metadata, &metadata)?,400					Self::rmrk_property(Equipped, &false)?,401					Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,402					Self::rmrk_property(NextResourceId, &(0 as RmrkResourceId))?,403					Self::rmrk_property(PendingChildren, &PendingChildrenSet::new())?,404					Self::rmrk_property(AssociatedBases, &BasesMap::new())?,405				]406				.into_iter(),407			)408			.map_err(|err| match err {409				DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),410				err => Self::map_unique_err_to_proxy(err),411			})?;412413			if let Some(resources) = resources {414				for resource in resources {415					Self::resource_add(sender.clone(), collection.id, nft_id, resource)?;416				}417			}418419			Self::deposit_event(Event::NftMinted {420				owner,421				collection_id,422				nft_id: nft_id.0,423			});424425			Ok(())426		}427428		/// burn nft429		#[transactional]430		#[pallet::weight(<SelfWeightOf<T>>::burn_nft(*max_burns))]431		pub fn burn_nft(432			origin: OriginFor<T>,433			collection_id: RmrkCollectionId,434			nft_id: RmrkNftId,435			max_burns: u32,436		) -> DispatchResult {437			let sender = ensure_signed(origin)?;438			let cross_sender = T::CrossAccountId::from_sub(sender.clone());439440			let collection = Self::get_typed_nft_collection(441				Self::unique_collection_id(collection_id)?,442				misc::CollectionType::Regular,443			)?;444			collection.check_is_external()?;445446			Self::destroy_nft(447				cross_sender,448				Self::unique_collection_id(collection_id)?,449				nft_id.into(),450				max_burns,451				<Error<T>>::NoPermission,452			)453			.map_err(|err| Self::map_unique_err_to_proxy(err.error))?;454455			Self::deposit_event(Event::NFTBurned {456				owner: sender,457				nft_id,458			});459460			Ok(())461		}462463		/// Transfers a NFT from an Account or NFT A to another Account or NFT B464		///465		/// Parameters:466		/// - `origin`: sender of the transaction467		/// - `rmrk_collection_id`: collection id of the nft to be transferred468		/// - `rmrk_nft_id`: nft id of the nft to be transferred469		/// - `new_owner`: new owner of the nft which can be either an account or a NFT470		#[transactional]471		#[pallet::weight(<SelfWeightOf<T>>::send())]472		pub fn send(473			origin: OriginFor<T>,474			rmrk_collection_id: RmrkCollectionId,475			rmrk_nft_id: RmrkNftId,476			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,477		) -> DispatchResult {478			let sender = ensure_signed(origin.clone())?;479			let cross_sender = T::CrossAccountId::from_sub(sender.clone());480481			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;482			let nft_id = rmrk_nft_id.into();483484			let collection =485				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;486			collection.check_is_external()?;487488			let token_data =489				<TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;490491			let from = token_data.owner;492493			ensure!(494				Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,495				<Error<T>>::NonTransferable496			);497498			ensure!(499				Self::get_nft_property_decoded::<Option<PendingTarget>>(500					collection_id,501					nft_id,502					RmrkProperty::PendingNftAccept503				)?.is_none(),504				<Error<T>>::NoPermission505			);506507			let target_owner;508			let approval_required;509510			match new_owner {511				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {512					target_owner = T::CrossAccountId::from_sub(account_id.clone());513					approval_required = false;514				}515				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(516					target_collection_id,517					target_nft_id,518				) => {519					let target_collection_id = Self::unique_collection_id(target_collection_id)?;520521					let target_nft_budget = budget::Value::new(NESTING_BUDGET);522523					let target_nft_owner = <PalletStructure<T>>::get_checked_topmost_owner(524						target_collection_id,525						target_nft_id.into(),526						Some((collection_id, nft_id)),527						&target_nft_budget,528					)529					.map_err(Self::map_unique_err_to_proxy)?;530531					approval_required = cross_sender != target_nft_owner;532533					if approval_required {534						target_owner = target_nft_owner;535536						<PalletNft<T>>::set_scoped_token_property(537							collection.id,538							nft_id,539							PropertyScope::Rmrk,540							Self::rmrk_property::<Option<PendingTarget>>(541								PendingNftAccept,542								&Some((target_collection_id, target_nft_id.into()))543							)?,544						)?;545546						Self::insert_pending_child(547							(target_collection_id, target_nft_id.into()),548							(rmrk_collection_id, rmrk_nft_id),549						)?;550					} else {551						target_owner = T::CrossTokenAddressMapping::token_to_address(552							target_collection_id,553							target_nft_id.into(),554						);555					}556				}557			}558559			let src_nft_budget = budget::Value::new(NESTING_BUDGET);560561			<PalletNft<T>>::transfer_from(562				&collection,563				&cross_sender,564				&from,565				&target_owner,566				nft_id,567				&src_nft_budget,568			)569			.map_err(Self::map_unique_err_to_proxy)?;570571			Self::deposit_event(Event::NFTSent {572				sender,573				recipient: new_owner,574				collection_id: rmrk_collection_id,575				nft_id: rmrk_nft_id,576				approval_required,577			});578579			Ok(())580		}581582		/// Accepts an NFT sent from another account to self or owned NFT583		///584		/// Parameters:585		/// - `origin`: sender of the transaction586		/// - `rmrk_collection_id`: collection id of the nft to be accepted587		/// - `rmrk_nft_id`: nft id of the nft to be accepted588		/// - `new_owner`: either origin's account ID or origin-owned NFT, whichever the NFT was589		///   sent to590		#[transactional]591		#[pallet::weight(<SelfWeightOf<T>>::accept_nft())]592		pub fn accept_nft(593			origin: OriginFor<T>,594			rmrk_collection_id: RmrkCollectionId,595			rmrk_nft_id: RmrkNftId,596			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,597		) -> DispatchResult {598			let sender = ensure_signed(origin.clone())?;599			let cross_sender = T::CrossAccountId::from_sub(sender.clone());600601			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;602			let nft_id = rmrk_nft_id.into();603604			let collection =605				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;606			collection.check_is_external()?;607608			let new_cross_owner = match new_owner {609				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {610					T::CrossAccountId::from_sub(account_id.clone())611				}612				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(613					target_collection_id,614					target_nft_id,615				) => {616					let target_collection_id = Self::unique_collection_id(target_collection_id)?;617618					T::CrossTokenAddressMapping::token_to_address(619						target_collection_id,620						TokenId(target_nft_id),621					)622				}623			};624625			let budget = budget::Value::new(NESTING_BUDGET);626627			<PalletNft<T>>::transfer(628				&collection,629				&cross_sender,630				&new_cross_owner,631				nft_id,632				&budget,633			)634			.map_err(|err| {635				if err == <CommonError<T>>::UserIsNotAllowedToNest.into() {636					<Error<T>>::CannotAcceptNonOwnedNft.into()637				} else {638					Self::map_unique_err_to_proxy(err)639				}640			})?;641642			let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(643				collection_id,644				nft_id,645				RmrkProperty::PendingNftAccept646			)?;647648			if let Some(pending_target) = pending_target {649				Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?;650651				<PalletNft<T>>::set_scoped_token_property(652					collection.id,653					nft_id,654					PropertyScope::Rmrk,655					Self::rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,656				)?;657			}658659			Self::deposit_event(Event::NFTAccepted {660				sender,661				recipient: new_owner,662				collection_id: rmrk_collection_id,663				nft_id: rmrk_nft_id,664			});665666			Ok(())667		}668669		/// Rejects an NFT sent from another account to self or owned NFT670		///671		/// Parameters:672		/// - `origin`: sender of the transaction673		/// - `rmrk_collection_id`: collection id of the nft to be accepted674		/// - `rmrk_nft_id`: nft id of the nft to be accepted675		#[transactional]676		#[pallet::weight(<SelfWeightOf<T>>::reject_nft())]677		pub fn reject_nft(678			origin: OriginFor<T>,679			rmrk_collection_id: RmrkCollectionId,680			rmrk_nft_id: RmrkNftId,681		) -> DispatchResult {682			let sender = ensure_signed(origin)?;683			let cross_sender = T::CrossAccountId::from_sub(sender.clone());684685			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;686			let nft_id = rmrk_nft_id.into();687688			let collection =689				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;690			collection.check_is_external()?;691692			ensure!(693				<TokenData<T>>::get((collection_id, nft_id)).is_some(),694				<Error<T>>::NoAvailableNftId695			);696697698			let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(699				collection_id,700				nft_id,701				RmrkProperty::PendingNftAccept702			)?;703704			match pending_target {705				Some(pending_target) => Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?,706				None => return Err(<Error<T>>::CannotRejectNonPendingNft.into()),707			}708709			Self::destroy_nft(710				cross_sender,711				collection_id,712				nft_id,713				NESTING_BUDGET,714				<Error<T>>::CannotRejectNonOwnedNft,715			)716			.map_err(|err| Self::map_unique_err_to_proxy(err.error))?;717718			Self::deposit_event(Event::NFTRejected {719				sender,720				collection_id: rmrk_collection_id,721				nft_id: rmrk_nft_id,722			});723724			Ok(())725		}726727		/// accept the addition of a new resource to an existing NFT728		#[transactional]729		#[pallet::weight(<SelfWeightOf<T>>::accept_resource())]730		pub fn accept_resource(731			origin: OriginFor<T>,732			rmrk_collection_id: RmrkCollectionId,733			rmrk_nft_id: RmrkNftId,734			resource_id: RmrkResourceId,735		) -> DispatchResult {736			let sender = ensure_signed(origin)?;737			let cross_sender = T::CrossAccountId::from_sub(sender);738739			let collection_id = Self::unique_collection_id(rmrk_collection_id)740				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;741			let collection =742				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;743			collection.check_is_external()?;744745			let nft_id = rmrk_nft_id.into();746747			let budget = budget::Value::new(NESTING_BUDGET);748749			let nft_owner =750				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)751					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;752753			Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {754				ensure!(res.pending, <Error<T>>::ResourceNotPending);755				ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);756757				res.pending = false;758759				Ok(())760			})?;761762			Self::deposit_event(Event::<T>::ResourceAccepted {763				nft_id: rmrk_nft_id,764				resource_id,765			});766767			Ok(())768		}769770		/// accept the removal of a resource of an existing NFT771		#[transactional]772		#[pallet::weight(<SelfWeightOf<T>>::accept_resource_removal())]773		pub fn accept_resource_removal(774			origin: OriginFor<T>,775			rmrk_collection_id: RmrkCollectionId,776			rmrk_nft_id: RmrkNftId,777			resource_id: RmrkResourceId,778		) -> DispatchResult {779			let sender = ensure_signed(origin)?;780			let cross_sender = T::CrossAccountId::from_sub(sender);781782			let collection_id = Self::unique_collection_id(rmrk_collection_id)783				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;784			let collection =785				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;786			collection.check_is_external()?;787788			let nft_id = rmrk_nft_id.into();789790			let budget = budget::Value::new(NESTING_BUDGET);791792			let nft_owner =793				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)794					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;795796			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);797798			let resource_id_key = Self::rmrk_property_key(ResourceId(resource_id))?;799800			let resource_info = <PalletNft<T>>::token_aux_property((801				collection_id,802				nft_id,803				PropertyScope::Rmrk,804				resource_id_key.clone(),805			))806			.ok_or(<Error<T>>::ResourceDoesntExist)?;807808			let resource_info: RmrkResourceInfo = Self::decode_property(&resource_info)?;809810			ensure!(811				resource_info.pending_removal,812				<Error<T>>::ResourceNotPending813			);814815			<PalletNft<T>>::remove_token_aux_property(816				collection_id,817				nft_id,818				PropertyScope::Rmrk,819				resource_id_key,820			);821822			if let RmrkResourceTypes::Composable(resource) = resource_info.resource {823				let base_id = resource.base;824825				Self::remove_associated_base_id(collection_id, nft_id, base_id)?;826			}827828			Self::deposit_event(Event::<T>::ResourceRemovalAccepted {829				nft_id: rmrk_nft_id,830				resource_id,831			});832833			Ok(())834		}835836		/// set a custom value on an NFT837		#[transactional]838		#[pallet::weight(<SelfWeightOf<T>>::set_property())]839		pub fn set_property(840			origin: OriginFor<T>,841			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,842			maybe_nft_id: Option<RmrkNftId>,843			key: RmrkKeyString,844			value: RmrkValueString,845		) -> DispatchResult {846			let sender = ensure_signed(origin)?;847			let sender = T::CrossAccountId::from_sub(sender);848849			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;850			let collection =851				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;852			collection.check_is_external()?;853854			let budget = budget::Value::new(NESTING_BUDGET);855856			match maybe_nft_id {857				Some(nft_id) => {858					let token_id: TokenId = nft_id.into();859860					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;861					Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;862863					<PalletNft<T>>::set_scoped_token_property(864						collection_id,865						token_id,866						PropertyScope::Rmrk,867						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,868					)?;869				}870				None => {871					let collection = Self::get_typed_nft_collection(872						collection_id,873						misc::CollectionType::Regular,874					)?;875876					Self::check_collection_owner(&collection, &sender)?;877878					<PalletCommon<T>>::set_scoped_collection_property(879						collection_id,880						PropertyScope::Rmrk,881						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,882					)?;883				}884			}885886			Self::deposit_event(Event::PropertySet {887				collection_id: rmrk_collection_id,888				maybe_nft_id,889				key,890				value,891			});892893			Ok(())894		}895896		/// set a different order of resource priority897		#[transactional]898		#[pallet::weight(<SelfWeightOf<T>>::set_priority())]899		pub fn set_priority(900			origin: OriginFor<T>,901			rmrk_collection_id: RmrkCollectionId,902			rmrk_nft_id: RmrkNftId,903			priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,904		) -> DispatchResult {905			let sender = ensure_signed(origin)?;906			let sender = T::CrossAccountId::from_sub(sender);907908			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;909			let nft_id = rmrk_nft_id.into();910911			let collection =912				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;913			collection.check_is_external()?;914915			let budget = budget::Value::new(NESTING_BUDGET);916917			Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;918			Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;919920			<PalletNft<T>>::set_scoped_token_property(921				collection_id,922				nft_id,923				PropertyScope::Rmrk,924				Self::rmrk_property(ResourcePriorities, &priorities.into_inner())?,925			)?;926927			Self::deposit_event(Event::<T>::PrioritySet {928				collection_id: rmrk_collection_id,929				nft_id: rmrk_nft_id,930			});931932			Ok(())933		}934935		/// Create basic resource936		#[transactional]937		#[pallet::weight(<SelfWeightOf<T>>::add_basic_resource())]938		pub fn add_basic_resource(939			origin: OriginFor<T>,940			rmrk_collection_id: RmrkCollectionId,941			nft_id: RmrkNftId,942			resource: RmrkBasicResource,943		) -> DispatchResult {944			let sender = ensure_signed(origin.clone())?;945946			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;947			let collection =948				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;949			collection.check_is_external()?;950951			let resource_id = Self::resource_add(952				sender,953				collection_id,954				nft_id.into(),955				RmrkResourceTypes::Basic(resource),956			)?;957958			Self::deposit_event(Event::ResourceAdded {959				nft_id,960				resource_id,961			});962			Ok(())963		}964965		/// Create composable resource966		#[transactional]967		#[pallet::weight(<SelfWeightOf<T>>::add_composable_resource())]968		pub fn add_composable_resource(969			origin: OriginFor<T>,970			rmrk_collection_id: RmrkCollectionId,971			nft_id: RmrkNftId,972			resource: RmrkComposableResource,973		) -> DispatchResult {974			let sender = ensure_signed(origin.clone())?;975976			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;977			let collection =978				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;979			collection.check_is_external()?;980981			let base_id = resource.base;982983			let resource_id = Self::resource_add(984				sender,985				collection_id,986				nft_id.into(),987				RmrkResourceTypes::Composable(resource),988			)?;989990			<PalletNft<T>>::try_mutate_token_aux_property(991				collection_id,992				nft_id.into(),993				PropertyScope::Rmrk,994				Self::rmrk_property_key(AssociatedBases)?,995				|value| -> DispatchResult {996					let mut bases: BasesMap = match value {997						Some(value) => Self::decode_property(value)?,998						None => BasesMap::new()999					};1000	1001					*bases.entry(base_id).or_insert(0) += 1;1002	1003					*value = Some(Self::encode_property(&bases)?);1004					Ok(())1005				}1006			)?;10071008			Self::deposit_event(Event::ResourceAdded {1009				nft_id,1010				resource_id,1011			});1012			Ok(())1013		}10141015		/// Create slot resource1016		#[transactional]1017		#[pallet::weight(<SelfWeightOf<T>>::add_slot_resource())]1018		pub fn add_slot_resource(1019			origin: OriginFor<T>,1020			rmrk_collection_id: RmrkCollectionId,1021			nft_id: RmrkNftId,1022			resource: RmrkSlotResource,1023		) -> DispatchResult {1024			let sender = ensure_signed(origin.clone())?;10251026			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1027			let collection =1028				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1029			collection.check_is_external()?;10301031			let resource_id = Self::resource_add(1032				sender,1033				collection_id,1034				nft_id.into(),1035				RmrkResourceTypes::Slot(resource),1036			)?;10371038			Self::deposit_event(Event::ResourceAdded {1039				nft_id,1040				resource_id,1041			});1042			Ok(())1043		}10441045		/// remove resource1046		#[transactional]1047		#[pallet::weight(<SelfWeightOf<T>>::remove_resource())]1048		pub fn remove_resource(1049			origin: OriginFor<T>,1050			rmrk_collection_id: RmrkCollectionId,1051			nft_id: RmrkNftId,1052			resource_id: RmrkResourceId,1053		) -> DispatchResult {1054			let sender = ensure_signed(origin.clone())?;10551056			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1057			let collection =1058				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1059			collection.check_is_external()?;10601061			Self::resource_remove(sender, collection_id, nft_id.into(), resource_id)?;10621063			Self::deposit_event(Event::ResourceRemoval {1064				nft_id,1065				resource_id,1066			});1067			Ok(())1068		}1069	}1070}10711072impl<T: Config> Pallet<T> {1073	pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1074		let key = rmrk_key.to_key::<T>()?;10751076		let scoped_key = PropertyScope::Rmrk1077			.apply(key)1078			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;10791080		Ok(scoped_key)1081	}10821083	// todo think about renaming these1084	pub fn rmrk_property<E: Encode>(1085		rmrk_key: RmrkProperty,1086		value: &E,1087	) -> Result<Property, DispatchError> {1088		let key = rmrk_key.to_key::<T>()?;10891090		let value = Self::encode_property(value)?;10911092		let property = Property { key, value };10931094		Ok(property)1095	}10961097	pub fn encode_property<E: Encode, S: Get<u32>>(1098		value: &E,1099	) -> Result<BoundedBytes<S>, DispatchError> {1100		let value = value1101			.encode()1102			.try_into()1103			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;11041105		Ok(value)1106	}11071108	pub fn decode_property<D: Decode, S: Get<u32>>(1109		vec: &BoundedBytes<S>,1110	) -> Result<D, DispatchError> {1111		vec.decode()1112			.map_err(|_| <Error<T>>::UnableToDecodeRmrkData.into())1113	}11141115	pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1116	where1117		BoundedVec<u8, S>: TryFrom<Vec<u8>>,1118	{1119		vec.rebind()1120			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1121	}11221123	fn init_collection(1124		sender: T::CrossAccountId,1125		data: CreateCollectionData<T::AccountId>,1126		properties: impl Iterator<Item = Property>,1127	) -> Result<CollectionId, DispatchError> {1128		let collection_id = <PalletNft<T>>::init_collection(sender, data, true);11291130		if let Err(DispatchError::Arithmetic(_)) = &collection_id {1131			return Err(<Error<T>>::NoAvailableCollectionId.into());1132		}11331134		<PalletCommon<T>>::set_scoped_collection_properties(1135			collection_id?,1136			PropertyScope::Rmrk,1137			properties,1138		)?;11391140		collection_id1141	}11421143	pub fn create_nft(1144		sender: &T::CrossAccountId,1145		owner: &T::CrossAccountId,1146		collection: &NonfungibleHandle<T>,1147		properties: impl Iterator<Item = Property>,1148	) -> Result<TokenId, DispatchError> {1149		let data = CreateNftExData {1150			properties: BoundedVec::default(),1151			owner: owner.clone(),1152		};11531154		let budget = budget::Value::new(NESTING_BUDGET);11551156		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;11571158		let nft_id = <PalletNft<T>>::current_token_id(collection.id);11591160		<PalletNft<T>>::set_scoped_token_properties(1161			collection.id,1162			nft_id,1163			PropertyScope::Rmrk,1164			properties,1165		)?;11661167		Ok(nft_id)1168	}11691170	fn destroy_nft(1171		sender: T::CrossAccountId,1172		collection_id: CollectionId,1173		token_id: TokenId,1174		max_burns: u32,1175		error_if_not_owned: Error<T>,1176	) -> DispatchResultWithPostInfo {1177		let collection =1178			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;11791180		let token_data =1181			<TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;11821183		let from = token_data.owner;11841185		let owner_check_budget = budget::Value::new(NESTING_BUDGET);11861187		ensure!(1188			<PalletStructure<T>>::check_indirectly_owned(1189				sender.clone(),1190				collection_id,1191				token_id,1192				None,1193				&owner_check_budget1194			)?,1195			error_if_not_owned,1196		);11971198		let burns_budget = budget::Value::new(max_burns);1199		let breadth_budget = budget::Value::new(max_burns);12001201		<PalletNft<T>>::burn_recursively(1202			&collection,1203			&from,1204			token_id,1205			&burns_budget,1206			&breadth_budget,1207		)1208	}12091210	fn insert_pending_child(1211		target: (CollectionId, TokenId),1212		child: (RmrkCollectionId, RmrkNftId),1213	) -> DispatchResult {1214		Self::mutate_pending_child(target, |pending_children| {1215			pending_children.insert(child);1216		})1217	}12181219	fn remove_pending_child(1220		target: (CollectionId, TokenId),1221		child: (RmrkCollectionId, RmrkNftId),1222	) -> DispatchResult {1223		Self::mutate_pending_child(target, |pending_children| {1224			pending_children.remove(&child);1225		})1226	}12271228	fn mutate_pending_child(1229		(target_collection_id, target_nft_id): (CollectionId, TokenId),1230		f: impl FnOnce(&mut PendingChildrenSet),1231	) -> DispatchResult {1232		<PalletNft<T>>::try_mutate_token_aux_property(1233			target_collection_id,1234			target_nft_id,1235			PropertyScope::Rmrk,1236			Self::rmrk_property_key(PendingChildren)?,1237			|pending_children| -> DispatchResult {1238				let mut map = match pending_children {1239					Some(map) => Self::decode_property(map)?,1240					None => PendingChildrenSet::new(),1241				};12421243				f(&mut map);12441245				*pending_children = Some(Self::encode_property(&map)?);12461247				Ok(())1248			},1249		)1250	}12511252	fn iterate_pending_children(collection_id: CollectionId, nft_id: TokenId) -> Result<impl Iterator<Item=PendingChild>, DispatchError> {1253		let property = <PalletNft<T>>::token_aux_property((1254			collection_id,1255			nft_id,1256			PropertyScope::Rmrk,1257			Self::rmrk_property_key(PendingChildren)?1258		));12591260		let pending_children = match property {1261			Some(map) => Self::decode_property(&map)?,1262			None => PendingChildrenSet::new(),1263		};12641265		Ok(pending_children.into_iter())1266	}12671268	fn acquire_next_resource_id(1269		collection_id: CollectionId,1270		nft_id: TokenId,1271	) -> Result<RmrkResourceId, DispatchError> {1272		let resource_id: RmrkResourceId =1273			Self::get_nft_property_decoded(collection_id, nft_id, NextResourceId)?;12741275		let next_id = resource_id1276			.checked_add(1)1277			.ok_or(<Error<T>>::NoAvailableResourceId)?;12781279		<PalletNft<T>>::set_scoped_token_property(1280			collection_id,1281			nft_id,1282			PropertyScope::Rmrk,1283			Self::rmrk_property(NextResourceId, &next_id)?,1284		)?;12851286		Ok(resource_id)1287	}12881289	fn resource_add(1290		sender: T::AccountId,1291		collection_id: CollectionId,1292		nft_id: TokenId,1293		resource: RmrkResourceTypes,1294	) -> Result<RmrkResourceId, DispatchError> {1295		let collection =1296			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1297		ensure!(collection.owner == sender, Error::<T>::NoPermission);12981299		let sender = T::CrossAccountId::from_sub(sender);1300		let budget = budget::Value::new(NESTING_BUDGET);13011302		let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1303			.map_err(Self::map_unique_err_to_proxy)?;13041305		let pending = sender != nft_owner;13061307		let id = Self::acquire_next_resource_id(collection_id, nft_id)?;13081309		let resource_info = RmrkResourceInfo {1310			id,1311			resource,1312			pending,1313			pending_removal: false,1314		};13151316		<PalletNft<T>>::try_mutate_token_aux_property(1317			collection_id,1318			nft_id,1319			PropertyScope::Rmrk,1320			Self::rmrk_property_key(ResourceId(id))?,1321			|value| -> DispatchResult {1322				*value = Some(Self::encode_property(&resource_info)?);13231324				Ok(())1325			},1326		)?;13271328		Ok(id)1329	}13301331	fn resource_remove(1332		sender: T::AccountId,1333		collection_id: CollectionId,1334		nft_id: TokenId,1335		resource_id: RmrkResourceId,1336	) -> DispatchResult {1337		let collection =1338			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1339		ensure!(collection.owner == sender, Error::<T>::NoPermission);13401341		let resource_id_key = Self::rmrk_property_key(ResourceId(resource_id))?;1342		let scope = PropertyScope::Rmrk;13431344		let resource = <PalletNft<T>>::token_aux_property((1345			collection_id,1346			nft_id,1347			scope,1348			resource_id_key.clone()1349		)).ok_or(<Error<T>>::ResourceDoesntExist)?;13501351		let resource_info: RmrkResourceInfo = Self::decode_property(&resource)?;13521353		let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1354		let topmost_owner =1355			<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;13561357		let sender = T::CrossAccountId::from_sub(sender);1358		if topmost_owner == sender {1359			<PalletNft<T>>::remove_token_aux_property(1360				collection_id,1361				nft_id,1362				PropertyScope::Rmrk,1363				Self::rmrk_property_key(ResourceId(resource_id))?,1364			);13651366			if let RmrkResourceTypes::Composable(resource) = resource_info.resource {1367				let base_id = resource.base;13681369				Self::remove_associated_base_id(collection_id, nft_id, base_id)?;1370			}1371		} else {1372			Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {1373				res.pending_removal = true;13741375				Ok(())1376			})?;1377		}13781379		Ok(())1380	}13811382	fn remove_associated_base_id(1383		collection_id: CollectionId,1384		nft_id: TokenId,1385		base_id: RmrkBaseId,1386	) -> DispatchResult {1387		<PalletNft<T>>::try_mutate_token_aux_property(1388			collection_id,1389			nft_id,1390			PropertyScope::Rmrk,1391			Self::rmrk_property_key(AssociatedBases)?,1392			|value| -> DispatchResult {1393				let mut bases: BasesMap = match value {1394					Some(value) => Self::decode_property(value)?,1395					None => BasesMap::new()1396				};13971398				let remaining = bases.get(&base_id);13991400				if let Some(remaining) = remaining {1401					if let Some(0) | None = remaining.checked_sub(1) {1402						bases.remove(&base_id);1403					}1404				}14051406				*value = Some(Self::encode_property(&bases)?);1407				Ok(())1408			}1409		)1410	}14111412	fn try_mutate_resource_info(1413		collection_id: CollectionId,1414		nft_id: TokenId,1415		resource_id: RmrkResourceId,1416		f: impl FnOnce(&mut RmrkResourceInfo) -> DispatchResult,1417	) -> DispatchResult {1418		<PalletNft<T>>::try_mutate_token_aux_property(1419			collection_id,1420			nft_id,1421			PropertyScope::Rmrk,1422			Self::rmrk_property_key(ResourceId(resource_id))?,1423			|value| match value {1424				Some(value) => {1425					let mut resource_info: RmrkResourceInfo = Self::decode_property(value)?;14261427					f(&mut resource_info)?;14281429					*value = Self::encode_property(&resource_info)?;14301431					Ok(())1432				}1433				None => Err(<Error<T>>::ResourceDoesntExist.into()),1434			},1435		)1436	}14371438	fn change_collection_owner(1439		collection_id: CollectionId,1440		collection_type: misc::CollectionType,1441		sender: T::AccountId,1442		new_owner: T::AccountId,1443	) -> DispatchResult {1444		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1445		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;14461447		let mut collection = collection.into_inner();14481449		collection.owner = new_owner;1450		collection.save()1451	}14521453	pub fn check_collection_owner(1454		collection: &NonfungibleHandle<T>,1455		account: &T::CrossAccountId,1456	) -> DispatchResult {1457		collection1458			.check_is_owner(account)1459			.map_err(Self::map_unique_err_to_proxy)1460	}14611462	pub fn last_collection_idx() -> RmrkCollectionId {1463		<CollectionIndex<T>>::get()1464	}14651466	pub fn unique_collection_id(1467		rmrk_collection_id: RmrkCollectionId,1468	) -> Result<CollectionId, DispatchError> {1469		<UniqueCollectionId<T>>::try_get(rmrk_collection_id)1470			.map_err(|_| <Error<T>>::CollectionUnknown.into())1471	}14721473	pub fn rmrk_collection_id(1474		unique_collection_id: CollectionId,1475	) -> Result<RmrkCollectionId, DispatchError> {1476		Self::get_collection_property_decoded(unique_collection_id, RmrkInternalCollectionId)1477	}14781479	pub fn get_nft_collection(1480		collection_id: CollectionId,1481	) -> Result<NonfungibleHandle<T>, DispatchError> {1482		let collection = <CollectionHandle<T>>::try_get(collection_id)1483			.map_err(|_| <Error<T>>::CollectionUnknown)?;14841485		match collection.mode {1486			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1487			_ => Err(<Error<T>>::CollectionUnknown.into()),1488		}1489	}14901491	pub fn collection_exists(collection_id: CollectionId) -> bool {1492		<CollectionHandle<T>>::try_get(collection_id).is_ok()1493	}14941495	pub fn get_collection_property(1496		collection_id: CollectionId,1497		key: RmrkProperty,1498	) -> Result<PropertyValue, DispatchError> {1499		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1500			.get(&Self::rmrk_property_key(key)?)1501			.ok_or(<Error<T>>::CollectionUnknown)?1502			.clone();15031504		Ok(collection_property)1505	}15061507	pub fn get_collection_property_decoded<V: Decode>(1508		collection_id: CollectionId,1509		key: RmrkProperty,1510	) -> Result<V, DispatchError> {1511		Self::decode_property(&Self::get_collection_property(collection_id, key)?)1512	}15131514	pub fn get_collection_type(1515		collection_id: CollectionId,1516	) -> Result<misc::CollectionType, DispatchError> {1517		Self::get_collection_property_decoded(collection_id, CollectionType).map_err(|err| {1518			if err != <Error<T>>::CollectionUnknown.into() {1519				<Error<T>>::CorruptedCollectionType.into()1520			} else {1521				err1522			}1523		})1524	}15251526	pub fn ensure_collection_type(1527		collection_id: CollectionId,1528		collection_type: misc::CollectionType,1529	) -> DispatchResult {1530		let actual_type = Self::get_collection_type(collection_id)?;1531		ensure!(1532			actual_type == collection_type,1533			<CommonError<T>>::NoPermission1534		);15351536		Ok(())1537	}15381539	pub fn get_typed_nft_collection(1540		collection_id: CollectionId,1541		collection_type: misc::CollectionType,1542	) -> Result<NonfungibleHandle<T>, DispatchError> {1543		Self::ensure_collection_type(collection_id, collection_type)?;15441545		Self::get_nft_collection(collection_id)1546	}15471548	pub fn get_typed_nft_collection_mapped(1549		rmrk_collection_id: RmrkCollectionId,1550		collection_type: misc::CollectionType,1551	) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1552		let unique_collection_id = match collection_type {1553			misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1554			_ => rmrk_collection_id.into(),1555		};15561557		let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;15581559		Ok((collection, unique_collection_id))1560	}15611562	pub fn get_nft_property(1563		collection_id: CollectionId,1564		nft_id: TokenId,1565		key: RmrkProperty,1566	) -> Result<PropertyValue, DispatchError> {1567		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1568			.get(&Self::rmrk_property_key(key)?)1569			.ok_or(<Error<T>>::RmrkPropertyIsNotFound)?1570			.clone();15711572		Ok(nft_property)1573	}15741575	pub fn get_nft_property_decoded<V: Decode>(1576		collection_id: CollectionId,1577		nft_id: TokenId,1578		key: RmrkProperty,1579	) -> Result<V, DispatchError> {1580		Self::decode_property(&Self::get_nft_property(collection_id, nft_id, key)?)1581	}15821583	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1584		<TokenData<T>>::contains_key((collection_id, nft_id))1585	}15861587	pub fn get_nft_type(1588		collection_id: CollectionId,1589		token_id: TokenId,1590	) -> Result<NftType, DispatchError> {1591		Self::get_nft_property_decoded(collection_id, token_id, TokenType)1592			.map_err(|_| <Error<T>>::NoAvailableNftId.into())1593	}15941595	pub fn ensure_nft_type(1596		collection_id: CollectionId,1597		token_id: TokenId,1598		nft_type: NftType,1599	) -> DispatchResult {1600		let actual_type = Self::get_nft_type(collection_id, token_id)?;1601		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);16021603		Ok(())1604	}16051606	pub fn ensure_nft_owner(1607		collection_id: CollectionId,1608		token_id: TokenId,1609		possible_owner: &T::CrossAccountId,1610		nesting_budget: &dyn budget::Budget,1611	) -> DispatchResult {1612		let is_owned = <PalletStructure<T>>::check_indirectly_owned(1613			possible_owner.clone(),1614			collection_id,1615			token_id,1616			None,1617			nesting_budget,1618		)1619		.map_err(Self::map_unique_err_to_proxy)?;16201621		ensure!(is_owned, <Error<T>>::NoPermission);16221623		Ok(())1624	}16251626	pub fn filter_user_properties<Key, Value, R, Mapper>(1627		collection_id: CollectionId,1628		token_id: Option<TokenId>,1629		filter_keys: Option<Vec<RmrkPropertyKey>>,1630		mapper: Mapper,1631	) -> Result<Vec<R>, DispatchError>1632	where1633		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1634		Value: Decode + Default,1635		Mapper: Fn(Key, Value) -> R,1636	{1637		filter_keys1638			.map(|keys| {1639				let properties = keys1640					.into_iter()1641					.filter_map(|key| {1642						let key: Key = key.try_into().ok()?;16431644						let value = match token_id {1645							Some(token_id) => Self::get_nft_property_decoded(1646								collection_id,1647								token_id,1648								UserProperty(key.as_ref()),1649							),1650							None => Self::get_collection_property_decoded(1651								collection_id,1652								UserProperty(key.as_ref()),1653							),1654						}1655						.ok()?;16561657						Some(mapper(key, value))1658					})1659					.collect();16601661				Ok(properties)1662			})1663			.unwrap_or_else(|| {1664				let properties =1665					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();16661667				Ok(properties)1668			})1669	}16701671	pub fn iterate_user_properties<Key, Value, R, Mapper>(1672		collection_id: CollectionId,1673		token_id: Option<TokenId>,1674		mapper: Mapper,1675	) -> Result<impl Iterator<Item = R>, DispatchError>1676	where1677		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1678		Value: Decode + Default,1679		Mapper: Fn(Key, Value) -> R,1680	{1681		let properties = match token_id {1682			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1683			None => <PalletCommon<T>>::collection_properties(collection_id),1684		};16851686		let properties = properties.into_iter().filter_map(move |(key, value)| {1687			let key = strip_key_prefix(&key, USER_PROPERTY_PREFIX)?;16881689			let key: Key = key.to_vec().try_into().ok()?;1690			let value: Value = value.decode().ok()?;16911692			Some(mapper(key, value))1693		});16941695		Ok(properties)1696	}16971698	fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1699		map_unique_err_to_proxy! {1700			match err {1701				CommonError::NoPermission => NoPermission,1702				CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1703				CommonError::PublicMintingNotAllowed => NoPermission,1704				CommonError::TokenNotFound => NoAvailableNftId,1705				CommonError::ApprovedValueTooLow => NoPermission,1706				CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1707				StructureError::TokenNotFound => NoAvailableNftId,1708				StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1709			}1710		}1711	}1712}