git.delta.rocks / unique-network / refs/commits / 19b5b8f85d2f

difftreelog

source

pallets/proxy-rmrk-core/src/lib.rs42.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#![cfg_attr(not(feature = "std"), no_std)]1819use frame_support::{pallet_prelude::*, transactional, BoundedVec, dispatch::DispatchResult};20use frame_system::{pallet_prelude::*, ensure_signed};21use sp_runtime::{DispatchError, Permill, traits::StaticLookup};22use sp_std::vec::Vec;23use up_data_structs::{*, mapping::TokenAddressMapping};24use pallet_common::{25	Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,26};27use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData};28use pallet_structure::{Pallet as PalletStructure, Error as StructureError};29use pallet_evm::account::CrossAccountId;30use core::convert::AsRef;3132pub use pallet::*;3334#[cfg(feature = "runtime-benchmarks")]35pub mod benchmarking;36pub mod misc;37pub mod property;38pub mod weights;3940pub type SelfWeightOf<T> = <T as Config>::WeightInfo;4142use weights::WeightInfo;43use misc::*;44pub use property::*;4546use RmrkProperty::*;4748const NESTING_BUDGET: u32 = 5;4950#[frame_support::pallet]51pub mod pallet {52	use super::*;53	use pallet_evm::account;5455	#[pallet::config]56	pub trait Config:57		frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config58	{59		type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;60		type WeightInfo: WeightInfo;61	}6263	#[pallet::storage]64	#[pallet::getter(fn collection_index)]65	pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;6667	#[pallet::storage]68	pub type UniqueCollectionId<T: Config> =69		StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;7071	#[pallet::storage]72	pub type RmrkInernalCollectionId<T: Config> =73		StorageMap<_, Twox64Concat, CollectionId, RmrkCollectionId, ValueQuery>;7475	#[pallet::pallet]76	#[pallet::generate_store(pub(super) trait Store)]77	pub struct Pallet<T>(_);7879	#[pallet::event]80	#[pallet::generate_deposit(pub(super) fn deposit_event)]81	pub enum Event<T: Config> {82		CollectionCreated {83			issuer: T::AccountId,84			collection_id: RmrkCollectionId,85		},86		CollectionDestroyed {87			issuer: T::AccountId,88			collection_id: RmrkCollectionId,89		},90		IssuerChanged {91			old_issuer: T::AccountId,92			new_issuer: T::AccountId,93			collection_id: RmrkCollectionId,94		},95		CollectionLocked {96			issuer: T::AccountId,97			collection_id: RmrkCollectionId,98		},99		NftMinted {100			owner: T::AccountId,101			collection_id: RmrkCollectionId,102			nft_id: RmrkNftId,103		},104		NFTBurned {105			owner: T::AccountId,106			nft_id: RmrkNftId,107		},108		NFTSent {109			sender: T::AccountId,110			recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,111			collection_id: RmrkCollectionId,112			nft_id: RmrkNftId,113			approval_required: bool,114		},115		NFTAccepted {116			sender: T::AccountId,117			recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,118			collection_id: RmrkCollectionId,119			nft_id: RmrkNftId,120		},121		NFTRejected {122			sender: T::AccountId,123			collection_id: RmrkCollectionId,124			nft_id: RmrkNftId,125		},126		PropertySet {127			collection_id: RmrkCollectionId,128			maybe_nft_id: Option<RmrkNftId>,129			key: RmrkKeyString,130			value: RmrkValueString,131		},132		ResourceAdded {133			nft_id: RmrkNftId,134			resource_id: RmrkResourceId,135		},136		ResourceRemoval {137			nft_id: RmrkNftId,138			resource_id: RmrkResourceId,139		},140		ResourceAccepted {141			nft_id: RmrkNftId,142			resource_id: RmrkResourceId,143		},144		ResourceRemovalAccepted {145			nft_id: RmrkNftId,146			resource_id: RmrkResourceId,147		},148		PrioritySet {149			collection_id: RmrkCollectionId,150			nft_id: RmrkNftId,151		},152	}153154	#[pallet::error]155	pub enum Error<T> {156		/* Unique-specific events */157		CorruptedCollectionType,158		NftTypeEncodeError,159		RmrkPropertyKeyIsTooLong,160		RmrkPropertyValueIsTooLong,161162		/* RMRK compatible events */163		CollectionNotEmpty,164		NoAvailableCollectionId,165		NoAvailableNftId,166		CollectionUnknown,167		NoPermission,168		NonTransferable,169		CollectionFullOrLocked,170		ResourceDoesntExist,171		CannotSendToDescendentOrSelf,172		CannotAcceptNonOwnedNft,173		CannotRejectNonOwnedNft,174		ResourceNotPending,175	}176177	#[pallet::call]178	impl<T: Config> Pallet<T> {179		/// Create a collection180		#[transactional]181		#[pallet::weight(<SelfWeightOf<T>>::create_collection())]182		pub fn create_collection(183			origin: OriginFor<T>,184			metadata: RmrkString,185			max: Option<u32>,186			symbol: RmrkCollectionSymbol,187		) -> DispatchResult {188			let sender = ensure_signed(origin)?;189190			let limits = CollectionLimits {191				owner_can_transfer: Some(false),192				token_limit: max,193				..Default::default()194			};195196			let data = CreateCollectionData {197				limits: Some(limits),198				token_prefix: symbol199					.into_inner()200					.try_into()201					.map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,202				permissions: Some(CollectionPermissions {203					nesting: Some(NestingPermissions {204						token_owner: true,205						admin: false,206						restricted: None,207208						permissive: false,209					}),210					..Default::default()211				}),212				..Default::default()213			};214215			let unique_collection_id = Self::init_collection(216				T::CrossAccountId::from_sub(sender.clone()),217				data,218				[219					Self::rmrk_property(Metadata, &metadata)?,220					Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,221				]222				.into_iter(),223			)?;224			let rmrk_collection_id = <CollectionIndex<T>>::get();225226			<UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);227			<RmrkInernalCollectionId<T>>::insert(unique_collection_id, rmrk_collection_id);228229			<CollectionIndex<T>>::mutate(|n| *n += 1);230231			Self::deposit_event(Event::CollectionCreated {232				issuer: sender,233				collection_id: rmrk_collection_id,234			});235236			Ok(())237		}238239		/// destroy collection240		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]241		#[transactional]242		pub fn destroy_collection(243			origin: OriginFor<T>,244			collection_id: RmrkCollectionId,245		) -> DispatchResult {246			let sender = ensure_signed(origin)?;247			let cross_sender = T::CrossAccountId::from_sub(sender.clone());248249			let collection = Self::get_typed_nft_collection(250				Self::unique_collection_id(collection_id)?,251				misc::CollectionType::Regular,252			)?;253			collection.check_is_external()?;254255			<PalletNft<T>>::destroy_collection(collection, &cross_sender)256				.map_err(Self::map_unique_err_to_proxy)?;257258			Self::deposit_event(Event::CollectionDestroyed {259				issuer: sender,260				collection_id,261			});262263			Ok(())264		}265266		/// Change the issuer of a collection267		///268		/// Parameters:269		/// - `origin`: sender of the transaction270		/// - `collection_id`: collection id of the nft to change issuer of271		/// - `new_issuer`: Collection's new issuer272		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]273		#[transactional]274		pub fn change_collection_issuer(275			origin: OriginFor<T>,276			collection_id: RmrkCollectionId,277			new_issuer: <T::Lookup as StaticLookup>::Source,278		) -> DispatchResult {279			let sender = ensure_signed(origin)?;280281			let collection = Self::get_nft_collection(Self::unique_collection_id(collection_id)?)?;282			collection.check_is_external()?;283284			let new_issuer = T::Lookup::lookup(new_issuer)?;285286			Self::change_collection_owner(287				Self::unique_collection_id(collection_id)?,288				misc::CollectionType::Regular,289				sender.clone(),290				new_issuer.clone(),291			)?;292293			Self::deposit_event(Event::IssuerChanged {294				old_issuer: sender,295				new_issuer,296				collection_id,297			});298299			Ok(())300		}301302		/// lock collection303		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]304		#[transactional]305		pub fn lock_collection(306			origin: OriginFor<T>,307			collection_id: RmrkCollectionId,308		) -> DispatchResult {309			let sender = ensure_signed(origin)?;310			let cross_sender = T::CrossAccountId::from_sub(sender.clone());311312			let collection = Self::get_typed_nft_collection(313				Self::unique_collection_id(collection_id)?,314				misc::CollectionType::Regular,315			)?;316			collection.check_is_external()?;317318			Self::check_collection_owner(&collection, &cross_sender)?;319320			let token_count = collection.total_supply();321322			let mut collection = collection.into_inner();323			collection.limits.token_limit = Some(token_count);324			collection.save()?;325326			Self::deposit_event(Event::CollectionLocked {327				issuer: sender,328				collection_id,329			});330331			Ok(())332		}333334		/// Mints an NFT in the specified collection335		/// Sets metadata and the royalty attribute336		///337		/// Parameters:338		/// - `collection_id`: The class of the asset to be minted.339		/// - `nft_id`: The nft value of the asset to be minted.340		/// - `recipient`: Receiver of the royalty341		/// - `royalty`: Permillage reward from each trade for the Recipient342		/// - `metadata`: Arbitrary data about an nft, e.g. IPFS hash343		/// - `transferable`: Ability to transfer this NFT344		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]345		#[transactional]346		pub fn mint_nft(347			origin: OriginFor<T>,348			owner: T::AccountId,349			collection_id: RmrkCollectionId,350			recipient: Option<T::AccountId>,351			royalty_amount: Option<Permill>,352			metadata: RmrkString,353			transferable: bool,354		) -> DispatchResult {355			let sender = ensure_signed(origin)?;356			let sender = T::CrossAccountId::from_sub(sender);357			let cross_owner = T::CrossAccountId::from_sub(owner.clone());358359			let collection = Self::get_typed_nft_collection(360				Self::unique_collection_id(collection_id)?,361				misc::CollectionType::Regular,362			)?;363			collection.check_is_external()?;364365			let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {366				recipient: recipient.unwrap_or_else(|| owner.clone()),367				amount,368			});369370			let nft_id = Self::create_nft(371				&sender,372				&cross_owner,373				&collection,374				[375					Self::rmrk_property(TokenType, &NftType::Regular)?,376					Self::rmrk_property(Transferable, &transferable)?,377					Self::rmrk_property(PendingNftAccept, &false)?,378					Self::rmrk_property(RoyaltyInfo, &royalty_info)?,379					Self::rmrk_property(Metadata, &metadata)?,380					Self::rmrk_property(Equipped, &false)?,381					Self::rmrk_property(382						ResourceCollection,383						&Self::init_collection(384							sender.clone(),385							CreateCollectionData {386								..Default::default()387							},388							[Self::rmrk_property(389								CollectionType,390								&misc::CollectionType::Resource,391							)?]392							.into_iter(),393						)?,394					)?, // todo possibly add limits to the collection if rmrk warrants them395					Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,396				]397				.into_iter(),398			)399			.map_err(|err| match err {400				DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),401				err => Self::map_unique_err_to_proxy(err),402			})?;403404			Self::deposit_event(Event::NftMinted {405				owner,406				collection_id,407				nft_id: nft_id.0,408			});409410			Ok(())411		}412413		/// burn nft414		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]415		#[transactional]416		pub fn burn_nft(417			origin: OriginFor<T>,418			collection_id: RmrkCollectionId,419			nft_id: RmrkNftId,420			max_burns: u32,421		) -> DispatchResult {422			let sender = ensure_signed(origin)?;423			let cross_sender = T::CrossAccountId::from_sub(sender.clone());424425			let collection = Self::get_typed_nft_collection(426				Self::unique_collection_id(collection_id)?,427				misc::CollectionType::Regular,428			)?;429			collection.check_is_external()?;430431			Self::destroy_nft(432				cross_sender,433				Self::unique_collection_id(collection_id)?,434				nft_id.into(),435				max_burns,436				<Error<T>>::NoPermission,437			)438			.map_err(|err| Self::map_unique_err_to_proxy(err.error))?;439440			Self::deposit_event(Event::NFTBurned {441				owner: sender,442				nft_id,443			});444445			Ok(())446		}447448		/// Transfers a NFT from an Account or NFT A to another Account or NFT B449		///450		/// Parameters:451		/// - `origin`: sender of the transaction452		/// - `rmrk_collection_id`: collection id of the nft to be transferred453		/// - `rmrk_nft_id`: nft id of the nft to be transferred454		/// - `new_owner`: new owner of the nft which can be either an account or a NFT455		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]456		#[transactional]457		pub fn send(458			origin: OriginFor<T>,459			rmrk_collection_id: RmrkCollectionId,460			rmrk_nft_id: RmrkNftId,461			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,462		) -> DispatchResult {463			let sender = ensure_signed(origin.clone())?;464			let cross_sender = T::CrossAccountId::from_sub(sender.clone());465466			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;467			let nft_id = rmrk_nft_id.into();468469			let collection =470				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;471			collection.check_is_external()?;472473			let token_data =474				<TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;475476			let from = token_data.owner;477478			ensure!(479				Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,480				<Error<T>>::NonTransferable481			);482483			ensure!(484				!Self::get_nft_property_decoded(485					collection_id,486					nft_id,487					RmrkProperty::PendingNftAccept488				)?,489				<Error<T>>::NoPermission490			);491492			let target_owner;493			let approval_required;494495			match new_owner {496				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {497					target_owner = T::CrossAccountId::from_sub(account_id.clone());498					approval_required = false;499				}500				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(501					target_collection_id,502					target_nft_id,503				) => {504					let target_collection_id = Self::unique_collection_id(target_collection_id)?;505506					let target_nft_budget = budget::Value::new(NESTING_BUDGET);507508					let target_nft_owner = <PalletStructure<T>>::get_checked_topmost_owner(509						target_collection_id,510						target_nft_id.into(),511						Some((collection_id, nft_id)),512						&target_nft_budget,513					)514					.map_err(Self::map_unique_err_to_proxy)?;515516					approval_required = cross_sender != target_nft_owner;517518					if approval_required {519						target_owner = target_nft_owner;520521						<PalletNft<T>>::set_scoped_token_property(522							collection.id,523							nft_id,524							PropertyScope::Rmrk,525							Self::rmrk_property(PendingNftAccept, &approval_required)?,526						)?;527					} else {528						target_owner = T::CrossTokenAddressMapping::token_to_address(529							target_collection_id,530							target_nft_id.into(),531						);532					}533				}534			}535536			let src_nft_budget = budget::Value::new(NESTING_BUDGET);537538			<PalletNft<T>>::transfer_from(539				&collection,540				&cross_sender,541				&from,542				&target_owner,543				nft_id,544				&src_nft_budget,545			)546			.map_err(Self::map_unique_err_to_proxy)?;547548			Self::deposit_event(Event::NFTSent {549				sender,550				recipient: new_owner,551				collection_id: rmrk_collection_id,552				nft_id: rmrk_nft_id,553				approval_required,554			});555556			Ok(())557		}558559		/// Accepts an NFT sent from another account to self or owned NFT560		///561		/// Parameters:562		/// - `origin`: sender of the transaction563		/// - `rmrk_collection_id`: collection id of the nft to be accepted564		/// - `rmrk_nft_id`: nft id of the nft to be accepted565		/// - `new_owner`: either origin's account ID or origin-owned NFT, whichever the NFT was566		///   sent to567		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]568		#[transactional]569		pub fn accept_nft(570			origin: OriginFor<T>,571			rmrk_collection_id: RmrkCollectionId,572			rmrk_nft_id: RmrkNftId,573			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,574		) -> DispatchResult {575			let sender = ensure_signed(origin.clone())?;576			let cross_sender = T::CrossAccountId::from_sub(sender.clone());577578			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;579			let nft_id = rmrk_nft_id.into();580581			let collection =582				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;583			collection.check_is_external()?;584585			let new_cross_owner = match new_owner {586				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {587					T::CrossAccountId::from_sub(account_id.clone())588				}589				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(590					target_collection_id,591					target_nft_id,592				) => {593					let target_collection_id = Self::unique_collection_id(target_collection_id)?;594595					T::CrossTokenAddressMapping::token_to_address(596						target_collection_id,597						TokenId(target_nft_id),598					)599				}600			};601602			let budget = budget::Value::new(NESTING_BUDGET);603604			<PalletNft<T>>::transfer(605				&collection,606				&cross_sender,607				&new_cross_owner,608				nft_id,609				&budget,610			)611			.map_err(|err| {612				if err == <CommonError<T>>::UserIsNotAllowedToNest.into() {613					<Error<T>>::CannotAcceptNonOwnedNft.into()614				} else {615					Self::map_unique_err_to_proxy(err)616				}617			})?;618619			<PalletNft<T>>::set_scoped_token_property(620				collection.id,621				nft_id,622				PropertyScope::Rmrk,623				Self::rmrk_property(PendingNftAccept, &false)?,624			)?;625626			Self::deposit_event(Event::NFTAccepted {627				sender,628				recipient: new_owner,629				collection_id: rmrk_collection_id,630				nft_id: rmrk_nft_id,631			});632633			Ok(())634		}635636		/// Rejects an NFT sent from another account to self or owned NFT637		///638		/// Parameters:639		/// - `origin`: sender of the transaction640		/// - `rmrk_collection_id`: collection id of the nft to be accepted641		/// - `rmrk_nft_id`: nft id of the nft to be accepted642		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]643		#[transactional]644		pub fn reject_nft(645			origin: OriginFor<T>,646			rmrk_collection_id: RmrkCollectionId,647			rmrk_nft_id: RmrkNftId,648			max_burns: u32,649		) -> DispatchResult {650			let sender = ensure_signed(origin)?;651			let cross_sender = T::CrossAccountId::from_sub(sender.clone());652653			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;654			let nft_id = rmrk_nft_id.into();655656			let collection =657				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;658			collection.check_is_external()?;659660			Self::destroy_nft(661				cross_sender,662				collection_id,663				nft_id,664				max_burns,665				<Error<T>>::CannotRejectNonOwnedNft,666			).map_err(|err| Self::map_unique_err_to_proxy(err.error))?;667668			Self::deposit_event(Event::NFTRejected {669				sender,670				collection_id: rmrk_collection_id,671				nft_id: rmrk_nft_id,672			});673674			Ok(())675		}676677		/// accept the addition of a new resource to an existing NFT678		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]679		#[transactional]680		pub fn accept_resource(681			origin: OriginFor<T>,682			rmrk_collection_id: RmrkCollectionId,683			rmrk_nft_id: RmrkNftId,684			rmrk_resource_id: RmrkResourceId,685		) -> DispatchResult {686			let sender = ensure_signed(origin)?;687			let cross_sender = T::CrossAccountId::from_sub(sender);688689			let collection_id = Self::unique_collection_id(rmrk_collection_id)690				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;691			let collection =692				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;693			collection.check_is_external()?;694695			let nft_id = rmrk_nft_id.into();696			let resource_id = rmrk_resource_id.into();697698			let budget = budget::Value::new(NESTING_BUDGET);699700			let nft_owner =701				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)702					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;703704			let resource_collection_id: CollectionId =705				Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)706					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;707708			let is_pending: bool = Self::get_nft_property_decoded(709				resource_collection_id,710				resource_id,711				PendingResourceAccept,712			)713			.map_err(|_| <Error<T>>::ResourceDoesntExist)?;714715			ensure!(is_pending, <Error<T>>::ResourceNotPending);716717			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);718719			<PalletNft<T>>::set_scoped_token_property(720				resource_collection_id,721				rmrk_resource_id.into(),722				PropertyScope::Rmrk,723				Self::rmrk_property(PendingResourceAccept, &false)?,724			)?;725726			Self::deposit_event(Event::<T>::ResourceAccepted {727				nft_id: rmrk_nft_id,728				resource_id: rmrk_resource_id,729			});730731			Ok(())732		}733734		/// accept the removal of a resource of an existing NFT735		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]736		#[transactional]737		pub fn accept_resource_removal(738			origin: OriginFor<T>,739			rmrk_collection_id: RmrkCollectionId,740			rmrk_nft_id: RmrkNftId,741			rmrk_resource_id: RmrkResourceId,742		) -> DispatchResult {743			let sender = ensure_signed(origin)?;744			let cross_sender = T::CrossAccountId::from_sub(sender);745746			let collection_id = Self::unique_collection_id(rmrk_collection_id)747				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;748			let collection =749				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;750			collection.check_is_external()?;751752			let nft_id = rmrk_nft_id.into();753			let resource_id = rmrk_resource_id.into();754755			let budget = budget::Value::new(NESTING_BUDGET);756757			let nft_owner =758				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)759					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;760761			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);762763			let resource_collection_id: CollectionId =764				Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)765					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;766767			let is_pending: bool = Self::get_nft_property_decoded(768				resource_collection_id,769				resource_id,770				PendingResourceRemoval,771			)772			.map_err(|_| <Error<T>>::ResourceDoesntExist)?;773774			ensure!(is_pending, <Error<T>>::ResourceNotPending);775776			let resource_collection = Self::get_typed_nft_collection(777				resource_collection_id,778				misc::CollectionType::Resource,779			)?;780781			<PalletNft<T>>::burn(&resource_collection, &cross_sender, rmrk_resource_id.into())782				.map_err(Self::map_unique_err_to_proxy)?;783784			Self::deposit_event(Event::<T>::ResourceRemovalAccepted {785				nft_id: rmrk_nft_id,786				resource_id: rmrk_resource_id,787			});788789			Ok(())790		}791792		/// set a custom value on an NFT793		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]794		#[transactional]795		pub fn set_property(796			origin: OriginFor<T>,797			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,798			maybe_nft_id: Option<RmrkNftId>,799			key: RmrkKeyString,800			value: RmrkValueString,801		) -> DispatchResult {802			let sender = ensure_signed(origin)?;803			let sender = T::CrossAccountId::from_sub(sender);804805			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;806			let collection =807				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;808			collection.check_is_external()?;809810			let budget = budget::Value::new(NESTING_BUDGET);811812			match maybe_nft_id {813				Some(nft_id) => {814					let token_id: TokenId = nft_id.into();815816					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;817					Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;818819					<PalletNft<T>>::set_scoped_token_property(820						collection_id,821						token_id,822						PropertyScope::Rmrk,823						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,824					)?;825				}826				None => {827					let collection = Self::get_typed_nft_collection(828						collection_id,829						misc::CollectionType::Regular,830					)?;831832					Self::check_collection_owner(&collection, &sender)?;833834					<PalletCommon<T>>::set_scoped_collection_property(835						collection_id,836						PropertyScope::Rmrk,837						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,838					)?;839				}840			}841842			Self::deposit_event(Event::PropertySet {843				collection_id: rmrk_collection_id,844				maybe_nft_id,845				key,846				value,847			});848849			Ok(())850		}851852		/// set a different order of resource priority853		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]854		#[transactional]855		pub fn set_priority(856			origin: OriginFor<T>,857			rmrk_collection_id: RmrkCollectionId,858			rmrk_nft_id: RmrkNftId,859			priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,860		) -> DispatchResult {861			let sender = ensure_signed(origin)?;862			let sender = T::CrossAccountId::from_sub(sender);863864			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;865			let nft_id = rmrk_nft_id.into();866867			let collection =868				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;869			collection.check_is_external()?;870871			let budget = budget::Value::new(NESTING_BUDGET);872873			Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;874			Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;875876			<PalletNft<T>>::set_scoped_token_property(877				collection_id,878				nft_id,879				PropertyScope::Rmrk,880				Self::rmrk_property(ResourcePriorities, &priorities.into_inner())?,881			)?;882883			Self::deposit_event(Event::<T>::PrioritySet {884				collection_id: rmrk_collection_id,885				nft_id: rmrk_nft_id,886			});887888			Ok(())889		}890891		/// Create basic resource892		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]893		#[transactional]894		pub fn add_basic_resource(895			origin: OriginFor<T>,896			rmrk_collection_id: RmrkCollectionId,897			nft_id: RmrkNftId,898			resource: RmrkBasicResource,899		) -> DispatchResult {900			let sender = ensure_signed(origin.clone())?;901902			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;903			let collection =904				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;905			collection.check_is_external()?;906907			let resource_id = Self::resource_add(908				sender,909				collection_id,910				nft_id.into(),911				[912					Self::rmrk_property(TokenType, &NftType::Resource)?,913					Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,914					Self::rmrk_property(Src, &resource.src)?,915					Self::rmrk_property(Metadata, &resource.metadata)?,916					Self::rmrk_property(License, &resource.license)?,917					Self::rmrk_property(Thumb, &resource.thumb)?,918				]919				.into_iter(),920			)?;921922			Self::deposit_event(Event::ResourceAdded {923				nft_id,924				resource_id,925			});926			Ok(())927		}928929		/// Create composable resource930		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]931		#[transactional]932		pub fn add_composable_resource(933			origin: OriginFor<T>,934			rmrk_collection_id: RmrkCollectionId,935			nft_id: RmrkNftId,936			_resource_id: RmrkBoundedResource,937			resource: RmrkComposableResource,938		) -> DispatchResult {939			let sender = ensure_signed(origin.clone())?;940941			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;942			let collection =943				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;944			collection.check_is_external()?;945946			let resource_id = Self::resource_add(947				sender,948				collection_id,949				nft_id.into(),950				[951					Self::rmrk_property(TokenType, &NftType::Resource)?,952					Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,953					Self::rmrk_property(Parts, &resource.parts)?,954					Self::rmrk_property(Base, &resource.base)?,955					Self::rmrk_property(Src, &resource.src)?,956					Self::rmrk_property(Metadata, &resource.metadata)?,957					Self::rmrk_property(License, &resource.license)?,958					Self::rmrk_property(Thumb, &resource.thumb)?,959				]960				.into_iter(),961			)?;962963			Self::deposit_event(Event::ResourceAdded {964				nft_id,965				resource_id,966			});967			Ok(())968		}969970		/// Create slot resource971		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]972		#[transactional]973		pub fn add_slot_resource(974			origin: OriginFor<T>,975			rmrk_collection_id: RmrkCollectionId,976			nft_id: RmrkNftId,977			resource: RmrkSlotResource,978		) -> DispatchResult {979			let sender = ensure_signed(origin.clone())?;980981			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;982			let collection =983				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;984			collection.check_is_external()?;985986			let resource_id = Self::resource_add(987				sender,988				collection_id,989				nft_id.into(),990				[991					Self::rmrk_property(TokenType, &NftType::Resource)?,992					Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,993					Self::rmrk_property(Base, &resource.base)?,994					Self::rmrk_property(Src, &resource.src)?,995					Self::rmrk_property(Metadata, &resource.metadata)?,996					Self::rmrk_property(Slot, &resource.slot)?,997					Self::rmrk_property(License, &resource.license)?,998					Self::rmrk_property(Thumb, &resource.thumb)?,999				]1000				.into_iter(),1001			)?;10021003			Self::deposit_event(Event::ResourceAdded {1004				nft_id,1005				resource_id,1006			});1007			Ok(())1008		}10091010		/// remove resource1011		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]1012		#[transactional]1013		pub fn remove_resource(1014			origin: OriginFor<T>,1015			rmrk_collection_id: RmrkCollectionId,1016			nft_id: RmrkNftId,1017			resource_id: RmrkResourceId,1018		) -> DispatchResult {1019			let sender = ensure_signed(origin.clone())?;10201021			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1022			let collection =1023				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1024			collection.check_is_external()?;10251026			Self::resource_remove(sender, collection_id, nft_id.into(), resource_id.into())?;10271028			Self::deposit_event(Event::ResourceRemoval {1029				nft_id,1030				resource_id,1031			});1032			Ok(())1033		}1034	}1035}10361037impl<T: Config> Pallet<T> {1038	pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1039		let key = rmrk_key.to_key::<T>()?;10401041		let scoped_key = PropertyScope::Rmrk1042			.apply(key)1043			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;10441045		Ok(scoped_key)1046	}10471048	// todo think about renaming these1049	pub fn rmrk_property<E: Encode>(1050		rmrk_key: RmrkProperty,1051		value: &E,1052	) -> Result<Property, DispatchError> {1053		let key = rmrk_key.to_key::<T>()?;10541055		let value = value1056			.encode()1057			.try_into()1058			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;10591060		let property = Property { key, value };10611062		Ok(property)1063	}10641065	pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {1066		vec.decode()1067			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1068	}10691070	pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1071	where1072		BoundedVec<u8, S>: TryFrom<Vec<u8>>,1073	{1074		vec.rebind()1075			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1076	}10771078	fn init_collection(1079		sender: T::CrossAccountId,1080		data: CreateCollectionData<T::AccountId>,1081		properties: impl Iterator<Item = Property>,1082	) -> Result<CollectionId, DispatchError> {1083		let collection_id = <PalletNft<T>>::init_collection(sender, data, true);10841085		if let Err(DispatchError::Arithmetic(_)) = &collection_id {1086			return Err(<Error<T>>::NoAvailableCollectionId.into());1087		}10881089		<PalletCommon<T>>::set_scoped_collection_properties(1090			collection_id?,1091			PropertyScope::Rmrk,1092			properties,1093		)?;10941095		collection_id1096	}10971098	pub fn create_nft(1099		sender: &T::CrossAccountId,1100		owner: &T::CrossAccountId,1101		collection: &NonfungibleHandle<T>,1102		properties: impl Iterator<Item = Property>,1103	) -> Result<TokenId, DispatchError> {1104		let data = CreateNftExData {1105			properties: BoundedVec::default(),1106			owner: owner.clone(),1107		};11081109		let budget = budget::Value::new(NESTING_BUDGET);11101111		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;11121113		let nft_id = <PalletNft<T>>::current_token_id(collection.id);11141115		<PalletNft<T>>::set_scoped_token_properties(1116			collection.id,1117			nft_id,1118			PropertyScope::Rmrk,1119			properties,1120		)?;11211122		Ok(nft_id)1123	}11241125	fn destroy_nft(1126		sender: T::CrossAccountId,1127		collection_id: CollectionId,1128		token_id: TokenId,1129		max_burns: u32,1130		error_if_not_owned: Error<T>,1131	) -> DispatchResultWithPostInfo {1132		let collection =1133			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;11341135		let token_data =1136			<TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;11371138		let from = token_data.owner;11391140		let owner_check_budget = budget::Value::new(NESTING_BUDGET);11411142		ensure!(1143			<PalletStructure<T>>::check_indirectly_owned(1144				sender.clone(),1145				collection_id,1146				token_id,1147				None,1148				&owner_check_budget1149			)?,1150			error_if_not_owned,1151		);11521153		let burns_budget = budget::Value::new(max_burns);1154		let breadth_budget = budget::Value::new(max_burns);11551156		<PalletNft<T>>::burn_recursively(&collection, &from, token_id, &burns_budget, &breadth_budget)1157	}11581159	fn resource_add(1160		sender: T::AccountId,1161		collection_id: CollectionId,1162		token_id: TokenId,1163		resource_properties: impl Iterator<Item = Property>,1164	) -> Result<RmrkResourceId, DispatchError> {1165		let collection =1166			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1167		ensure!(collection.owner == sender, Error::<T>::NoPermission);11681169		let sender = T::CrossAccountId::from_sub(sender);1170		let budget = budget::Value::new(NESTING_BUDGET);11711172		let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, token_id, &budget)1173			.map_err(Self::map_unique_err_to_proxy)?;11741175		let pending = sender != nft_owner;11761177		let resource_collection_id: CollectionId =1178			Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;1179		let resource_collection =1180			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;11811182		// todo probably add extra connections to bases, slots, etc., when RMRK starts to use them11831184		let resource_id = Self::create_nft(1185			&sender,1186			&nft_owner,1187			&resource_collection,1188			resource_properties.chain(1189				[1190					Self::rmrk_property(PendingResourceAccept, &pending)?,1191					Self::rmrk_property(PendingResourceRemoval, &false)?,1192				]1193				.into_iter(),1194			),1195		)1196		.map_err(|err| match err {1197			DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),1198			err => Self::map_unique_err_to_proxy(err),1199		})?;12001201		Ok(resource_id.0)1202	}12031204	fn resource_remove(1205		sender: T::AccountId,1206		collection_id: CollectionId,1207		nft_id: TokenId,1208		resource_id: TokenId,1209	) -> DispatchResult {1210		let collection =1211			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1212		ensure!(collection.owner == sender, Error::<T>::NoPermission);12131214		let resource_collection_id: CollectionId =1215			Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;1216		let resource_collection =1217			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;1218		ensure!(1219			<PalletNft<T>>::token_exists(&resource_collection, resource_id),1220			Error::<T>::ResourceDoesntExist1221		);12221223		let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1224		let topmost_owner =1225			<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;12261227		let sender = T::CrossAccountId::from_sub(sender);1228		if topmost_owner == sender {1229			<PalletNft<T>>::burn(&resource_collection, &sender, resource_id)1230				.map_err(Self::map_unique_err_to_proxy)?;1231		} else {1232			<PalletNft<T>>::set_scoped_token_property(1233				resource_collection_id,1234				resource_id,1235				PropertyScope::Rmrk,1236				Self::rmrk_property(PendingResourceRemoval, &true)?,1237			)?;1238		}12391240		Ok(())1241	}12421243	fn change_collection_owner(1244		collection_id: CollectionId,1245		collection_type: misc::CollectionType,1246		sender: T::AccountId,1247		new_owner: T::AccountId,1248	) -> DispatchResult {1249		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1250		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;12511252		let mut collection = collection.into_inner();12531254		collection.owner = new_owner;1255		collection.save()1256	}12571258	fn check_collection_owner(1259		collection: &NonfungibleHandle<T>,1260		account: &T::CrossAccountId,1261	) -> DispatchResult {1262		collection1263			.check_is_owner(account)1264			.map_err(Self::map_unique_err_to_proxy)1265	}12661267	pub fn last_collection_idx() -> RmrkCollectionId {1268		<CollectionIndex<T>>::get()1269	}12701271	pub fn unique_collection_id(1272		rmrk_collection_id: RmrkCollectionId,1273	) -> Result<CollectionId, DispatchError> {1274		<UniqueCollectionId<T>>::try_get(rmrk_collection_id)1275			.map_err(|_| <Error<T>>::CollectionUnknown.into())1276	}12771278	pub fn rmrk_collection_id(1279		unique_collection_id: CollectionId,1280	) -> Result<RmrkCollectionId, DispatchError> {1281		<RmrkInernalCollectionId<T>>::try_get(unique_collection_id)1282			.map_err(|_| <Error<T>>::CollectionUnknown.into())1283	}12841285	pub fn get_nft_collection(1286		collection_id: CollectionId,1287	) -> Result<NonfungibleHandle<T>, DispatchError> {1288		let collection = <CollectionHandle<T>>::try_get(collection_id)1289			.map_err(|_| <Error<T>>::CollectionUnknown)?;12901291		match collection.mode {1292			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1293			_ => Err(<Error<T>>::CollectionUnknown.into()),1294		}1295	}12961297	pub fn collection_exists(collection_id: CollectionId) -> bool {1298		<CollectionHandle<T>>::try_get(collection_id).is_ok()1299	}13001301	pub fn get_collection_property(1302		collection_id: CollectionId,1303		key: RmrkProperty,1304	) -> Result<PropertyValue, DispatchError> {1305		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1306			.get(&Self::rmrk_property_key(key)?)1307			.ok_or(<Error<T>>::CollectionUnknown)?1308			.clone();13091310		Ok(collection_property)1311	}13121313	pub fn get_collection_property_decoded<V: Decode>(1314		collection_id: CollectionId,1315		key: RmrkProperty,1316	) -> Result<V, DispatchError> {1317		Self::decode_property(Self::get_collection_property(collection_id, key)?)1318	}13191320	pub fn get_collection_type(1321		collection_id: CollectionId,1322	) -> Result<misc::CollectionType, DispatchError> {1323		Self::get_collection_property_decoded(collection_id, CollectionType)1324			.map_err(|_| <Error<T>>::CorruptedCollectionType.into())1325	}13261327	pub fn ensure_collection_type(1328		collection_id: CollectionId,1329		collection_type: misc::CollectionType,1330	) -> DispatchResult {1331		let actual_type = Self::get_collection_type(collection_id)?;1332		ensure!(1333			actual_type == collection_type,1334			<CommonError<T>>::NoPermission1335		);13361337		Ok(())1338	}13391340	pub fn get_typed_nft_collection(1341		collection_id: CollectionId,1342		collection_type: misc::CollectionType,1343	) -> Result<NonfungibleHandle<T>, DispatchError> {1344		Self::ensure_collection_type(collection_id, collection_type)?;13451346		Self::get_nft_collection(collection_id)1347	}13481349	pub fn get_typed_nft_collection_mapped(1350		rmrk_collection_id: RmrkCollectionId,1351		collection_type: misc::CollectionType,1352	) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1353		let unique_collection_id = match collection_type {1354			misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1355			_ => rmrk_collection_id.into(),1356		};13571358		let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;13591360		Ok((collection, unique_collection_id))1361	}13621363	pub fn get_nft_property(1364		collection_id: CollectionId,1365		nft_id: TokenId,1366		key: RmrkProperty,1367	) -> Result<PropertyValue, DispatchError> {1368		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1369			.get(&Self::rmrk_property_key(key)?)1370			.ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?1371			.clone();13721373		Ok(nft_property)1374	}13751376	pub fn get_nft_property_decoded<V: Decode>(1377		collection_id: CollectionId,1378		nft_id: TokenId,1379		key: RmrkProperty,1380	) -> Result<V, DispatchError> {1381		Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1382	}13831384	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1385		<TokenData<T>>::contains_key((collection_id, nft_id))1386	}13871388	pub fn get_nft_type(1389		collection_id: CollectionId,1390		token_id: TokenId,1391	) -> Result<NftType, DispatchError> {1392		Self::get_nft_property_decoded(collection_id, token_id, TokenType)1393			.map_err(|_| <Error<T>>::NoAvailableNftId.into())1394	}13951396	pub fn ensure_nft_type(1397		collection_id: CollectionId,1398		token_id: TokenId,1399		nft_type: NftType,1400	) -> DispatchResult {1401		let actual_type = Self::get_nft_type(collection_id, token_id)?;1402		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);14031404		Ok(())1405	}14061407	pub fn ensure_nft_owner(1408		collection_id: CollectionId,1409		token_id: TokenId,1410		possible_owner: &T::CrossAccountId,1411		nesting_budget: &dyn budget::Budget,1412	) -> DispatchResult {1413		let is_owned = <PalletStructure<T>>::check_indirectly_owned(1414			possible_owner.clone(),1415			collection_id,1416			token_id,1417			None,1418			nesting_budget,1419		)1420		.map_err(Self::map_unique_err_to_proxy)?;14211422		ensure!(is_owned, <Error<T>>::NoPermission);14231424		Ok(())1425	}14261427	pub fn filter_user_properties<Key, Value, R, Mapper>(1428		collection_id: CollectionId,1429		token_id: Option<TokenId>,1430		filter_keys: Option<Vec<RmrkPropertyKey>>,1431		mapper: Mapper,1432	) -> Result<Vec<R>, DispatchError>1433	where1434		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1435		Value: Decode + Default,1436		Mapper: Fn(Key, Value) -> R,1437	{1438		filter_keys1439			.map(|keys| {1440				let properties = keys1441					.into_iter()1442					.filter_map(|key| {1443						let key: Key = key.try_into().ok()?;14441445						let value = match token_id {1446							Some(token_id) => Self::get_nft_property_decoded(1447								collection_id,1448								token_id,1449								UserProperty(key.as_ref()),1450							),1451							None => Self::get_collection_property_decoded(1452								collection_id,1453								UserProperty(key.as_ref()),1454							),1455						}1456						.ok()?;14571458						Some(mapper(key, value))1459					})1460					.collect();14611462				Ok(properties)1463			})1464			.unwrap_or_else(|| {1465				let properties =1466					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();14671468				Ok(properties)1469			})1470	}14711472	pub fn iterate_user_properties<Key, Value, R, Mapper>(1473		collection_id: CollectionId,1474		token_id: Option<TokenId>,1475		mapper: Mapper,1476	) -> Result<impl Iterator<Item = R>, DispatchError>1477	where1478		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1479		Value: Decode + Default,1480		Mapper: Fn(Key, Value) -> R,1481	{1482		let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;14831484		let properties = match token_id {1485			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1486			None => <PalletCommon<T>>::collection_properties(collection_id),1487		};14881489		let properties = properties.into_iter().filter_map(move |(key, value)| {1490			let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;14911492			let key: Key = key.to_vec().try_into().ok()?;1493			let value: Value = value.decode().ok()?;14941495			Some(mapper(key, value))1496		});14971498		Ok(properties)1499	}15001501	fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1502		map_unique_err_to_proxy! {1503			match err {1504				CommonError::NoPermission => NoPermission,1505				CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1506				CommonError::PublicMintingNotAllowed => NoPermission,1507				CommonError::TokenNotFound => NoAvailableNftId,1508				CommonError::ApprovedValueTooLow => NoPermission,1509				CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1510				StructureError::TokenNotFound => NoAvailableNftId,1511				StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1512			}1513		}1514	}1515}